Merge tag 'usb-6.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 18 Jan 2024 19:43:55 +0000 (11:43 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 18 Jan 2024 19:43:55 +0000 (11:43 -0800)
Pull USB / Thunderbolt updates from Greg KH:
 "Here is the big set of USB and Thunderbolt changes for 6.8-rc1.
  Included in here are the following:

   - Thunderbolt subsystem and driver updates for USB 4 hardware and
     issues reported by real devices

   - xhci driver updates

   - dwc3 driver updates

   - uvc_video gadget driver updates

   - typec driver updates

   - gadget string functions cleaned up

   - other small changes

  All of these have been in the linux-next tree for a while with no
  reported issues"

* tag 'usb-6.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (169 commits)
  usb: typec: tipd: fix use of device-specific init function
  usb: typec: tipd: Separate reset for TPS6598x
  usb: mon: Fix atomicity violation in mon_bin_vma_fault
  usb: gadget: uvc: Remove nested locking
  usb: gadget: uvc: Fix use are free during STREAMOFF
  usb: typec: class: fix typec_altmode_put_partner to put plugs
  dt-bindings: usb: dwc3: Limit num-hc-interrupters definition
  dt-bindings: usb: xhci: Add num-hc-interrupters definition
  xhci: add support to allocate several interrupters
  USB: core: Use device_driver directly in struct usb_driver and usb_device_driver
  arm64: dts: mediatek: mt8195: Add 'rx-fifo-depth' for cherry
  usb: xhci-mtk: fix a short packet issue of gen1 isoc-in transfer
  dt-bindings: usb: mtk-xhci: add a property for Gen1 isoc-in transfer issue
  arm64: dts: qcom: msm8996: Remove PNoC clock from MSS
  arm64: dts: qcom: msm8996: Remove AGGRE2 clock from SLPI
  arm64: dts: qcom: msm8998: Remove AGGRE2 clock from SLPI
  arm64: dts: qcom: msm8939: Drop RPM bus clocks
  arm64: dts: qcom: sdm630: Drop RPM bus clocks
  arm64: dts: qcom: qcs404: Drop RPM bus clocks
  arm64: dts: qcom: msm8996: Drop RPM bus clocks
  ...

142 files changed:
Documentation/admin-guide/kernel-parameters.txt
Documentation/devicetree/bindings/connector/usb-connector.yaml
Documentation/devicetree/bindings/usb/generic-xhci.yaml
Documentation/devicetree/bindings/usb/genesys,gl850g.yaml
Documentation/devicetree/bindings/usb/mediatek,mtk-xhci.yaml
Documentation/devicetree/bindings/usb/nxp,ptn5110.yaml
Documentation/devicetree/bindings/usb/qcom,dwc3.yaml
Documentation/devicetree/bindings/usb/qcom,wcd939x-usbss.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/usb/renesas,usbhs.yaml
Documentation/devicetree/bindings/usb/snps,dwc3.yaml
Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
Documentation/devicetree/bindings/usb/usb-xhci.yaml
Documentation/usb/gadget-testing.rst
Documentation/usb/raw-gadget.rst
arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
arch/arm/boot/dts/broadcom/bcm2711.dtsi
arch/arm64/boot/dts/mediatek/mt8195-cherry.dtsi
arch/arm64/boot/dts/qcom/msm8916.dtsi
arch/arm64/boot/dts/qcom/msm8939.dtsi
arch/arm64/boot/dts/qcom/msm8996.dtsi
arch/arm64/boot/dts/qcom/msm8998.dtsi
arch/arm64/boot/dts/qcom/qcs404.dtsi
arch/arm64/boot/dts/qcom/qrb5165-rb5.dts
arch/arm64/boot/dts/qcom/sdm630.dtsi
drivers/bluetooth/btusb.c
drivers/net/can/usb/peak_usb/pcan_usb_core.c
drivers/net/usb/r8152.c
drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
drivers/net/wireless/marvell/mwifiex/usb.c
drivers/platform/x86/intel/chtwc_int33fe.c
drivers/thunderbolt/domain.c
drivers/thunderbolt/icm.c
drivers/thunderbolt/nhi.c
drivers/thunderbolt/nhi.h
drivers/thunderbolt/switch.c
drivers/thunderbolt/tb.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tmu.c
drivers/thunderbolt/tunnel.c
drivers/thunderbolt/xdomain.c
drivers/tty/tty_io.c
drivers/usb/atm/ueagle-atm.c
drivers/usb/cdns3/cdns3-gadget.c
drivers/usb/cdns3/cdns3-gadget.h
drivers/usb/cdns3/cdns3-plat.c
drivers/usb/cdns3/cdns3-starfive.c
drivers/usb/cdns3/cdnsp-debug.h
drivers/usb/chipidea/ci_hdrc_imx.c
drivers/usb/chipidea/core.c
drivers/usb/chipidea/udc.c
drivers/usb/class/cdc-acm.c
drivers/usb/core/driver.c
drivers/usb/core/generic.c
drivers/usb/core/hub.c
drivers/usb/core/quirks.c
drivers/usb/core/usb.c
drivers/usb/core/usb.h
drivers/usb/dwc2/params.c
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/dwc3-imx8mp.c
drivers/usb/dwc3/dwc3-qcom.c
drivers/usb/dwc3/dwc3-xilinx.c
drivers/usb/dwc3/ep0.c
drivers/usb/dwc3/gadget.c
drivers/usb/dwc3/gadget.h
drivers/usb/fotg210/fotg210-hcd.c
drivers/usb/fotg210/fotg210-udc.c
drivers/usb/gadget/configfs.c
drivers/usb/gadget/function/f_fs.c
drivers/usb/gadget/function/f_midi.c
drivers/usb/gadget/function/f_ncm.c
drivers/usb/gadget/function/f_tcm.c
drivers/usb/gadget/function/f_uac1.c
drivers/usb/gadget/function/f_uac2.c
drivers/usb/gadget/function/f_uvc.c
drivers/usb/gadget/function/f_uvc.h
drivers/usb/gadget/function/u_ether.c
drivers/usb/gadget/function/u_ncm.h
drivers/usb/gadget/function/u_uvc.h
drivers/usb/gadget/function/uvc.h
drivers/usb/gadget/function/uvc_configfs.c
drivers/usb/gadget/function/uvc_v4l2.c
drivers/usb/gadget/function/uvc_video.c
drivers/usb/gadget/function/uvc_video.h
drivers/usb/gadget/legacy/webcam.c
drivers/usb/gadget/udc/at91_udc.c
drivers/usb/gadget/udc/atmel_usba_udc.c
drivers/usb/gadget/udc/cdns2/cdns2-debug.h
drivers/usb/gadget/udc/fsl_udc_core.c
drivers/usb/gadget/udc/gr_udc.c
drivers/usb/gadget/udc/lpc32xx_udc.c
drivers/usb/gadget/udc/mv_udc_core.c
drivers/usb/gadget/udc/pxa25x_udc.c
drivers/usb/host/max3421-hcd.c
drivers/usb/host/xhci-dbgcap.c
drivers/usb/host/xhci-dbgcap.h
drivers/usb/host/xhci-debugfs.c
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-mtk.c
drivers/usb/host/xhci-mtk.h
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci-plat.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h
drivers/usb/misc/iowarrior.c
drivers/usb/misc/onboard_usb_hub.c
drivers/usb/misc/onboard_usb_hub.h
drivers/usb/misc/qcom_eud.c
drivers/usb/misc/yurex.c
drivers/usb/mon/mon_bin.c
drivers/usb/mon/mon_stat.c
drivers/usb/mon/mon_text.c
drivers/usb/phy/phy-generic.c
drivers/usb/phy/phy-mxs-usb.c
drivers/usb/phy/phy-twl6030-usb.c
drivers/usb/serial/bus.c
drivers/usb/serial/usb-serial.c
drivers/usb/storage/sierra_ms.c
drivers/usb/storage/uas.c
drivers/usb/typec/class.c
drivers/usb/typec/mux/Kconfig
drivers/usb/typec/mux/Makefile
drivers/usb/typec/mux/wcd939x-usbss.c [new file with mode: 0644]
drivers/usb/typec/pd.c
drivers/usb/typec/tcpm/tcpci_maxim_core.c
drivers/usb/typec/tcpm/tcpm.c
drivers/usb/typec/tipd/core.c
drivers/usb/typec/tipd/tps6598x.h
drivers/usb/usbip/stub_main.c
drivers/usb/usbip/vudc.h
drivers/usb/usbip/vudc_dev.c
drivers/usb/usbip/vudc_main.c
include/linux/thunderbolt.h
include/linux/usb.h
include/linux/usb/gadget.h
include/linux/usb/hcd.h
include/linux/usb/quirks.h
include/linux/usb/tcpci.h
include/linux/usb/tcpm.h
include/uapi/linux/usb/functionfs.h

index 4ef4109..2e76c34 100644 (file)
                                        pause after every control message);
                                o = USB_QUIRK_HUB_SLOW_RESET (Hub needs extra
                                        delay after resetting its port);
+                               p = USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT
+                                       (Reduce timeout of the SET_ADDRESS
+                                       request from 5000 ms to 500 ms);
                        Example: quirks=0781:5580:bk,0a5c:5834:gij
 
        usbhid.mousepoll=
index 7c8a3e8..fb216ce 100644 (file)
@@ -66,7 +66,6 @@ properties:
       Particularly, if use an output GPIO to control a VBUS regulator, should
       model it as a regulator. See bindings/regulator/fixed-regulator.yaml
 
-  # The following are optional properties for "usb-c-connector".
   power-role:
     description: Determines the power role that the Type C connector will
       support. "dual" refers to Dual Role Port (DRP).
@@ -119,30 +118,6 @@ properties:
 
   # The following are optional properties for "usb-c-connector" with power
   # delivery support.
-  source-pdos:
-    description: An array of u32 with each entry providing supported power
-      source data object(PDO), the detailed bit definitions of PDO can be found
-      in "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.2
-      Source_Capabilities Message, the order of each entry(PDO) should follow
-      the PD spec chapter 6.4.1. Required for power source and power dual role.
-      User can specify the source PDO array via PDO_FIXED/BATT/VAR/PPS_APDO()
-      defined in dt-bindings/usb/pd.h.
-    minItems: 1
-    maxItems: 7
-    $ref: /schemas/types.yaml#/definitions/uint32-array
-
-  sink-pdos:
-    description: An array of u32 with each entry providing supported power sink
-      data object(PDO), the detailed bit definitions of PDO can be found in
-      "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.3
-      Sink Capabilities Message, the order of each entry(PDO) should follow the
-      PD spec chapter 6.4.1. Required for power sink and power dual role. User
-      can specify the sink PDO array via PDO_FIXED/BATT/VAR/PPS_APDO() defined
-      in dt-bindings/usb/pd.h.
-    minItems: 1
-    maxItems: 7
-    $ref: /schemas/types.yaml#/definitions/uint32-array
-
   sink-vdos:
     description: An array of u32 with each entry, a Vendor Defined Message Object (VDO),
       providing additional information corresponding to the product, the detailed bit
@@ -166,10 +141,43 @@ properties:
     maxItems: 6
     $ref: /schemas/types.yaml#/definitions/uint32-array
 
-  op-sink-microwatt:
-    description: Sink required operating power in microwatt, if source can't
-      offer the power, Capability Mismatch is set. Required for power sink and
-      power dual role.
+  accessory-mode-audio:
+    type: boolean
+    description: Whether the device supports Audio Adapter Accessory Mode. This
+      is only necessary if there are no other means to discover supported
+      alternative modes (e.g. through the UCSI firmware interface).
+
+  accessory-mode-debug:
+    type: boolean
+    description: Whether the device supports Debug Accessory Mode. This
+      is only necessary if there are no other means to discover supported
+      alternative modes (e.g. through the UCSI firmware interface).
+
+  altmodes:
+    type: object
+    description: List of Alternative Modes supported by the schematics on the
+      particular device. This is only necessary if there are no other means to
+      discover supported alternative modes (e.g. through the UCSI firmware
+      interface).
+
+    additionalProperties: false
+
+    patternProperties:
+      "^(displayport)$":
+        type: object
+        description:
+          A single USB-C Alternative Mode as supported by the USB-C connector logic.
+
+        additionalProperties: false
+
+        properties:
+          svid:
+            $ref: /schemas/types.yaml#/definitions/uint16
+            description: Unique value assigned by USB-IF to the Vendor / AltMode.
+            enum: [ 0xff01 ]
+          vdo:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            description: VDO returned by Discover Modes USB PD command.
 
   port:
     $ref: /schemas/graph.yaml#/properties/port
@@ -231,6 +239,20 @@ properties:
       SNK_READY for non-pd link.
     type: boolean
 
+  capabilities:
+    description: A child node to contain all the selectable USB Power Delivery capabilities.
+    type: object
+
+    patternProperties:
+      "^caps-[0-9]+$":
+        description: Child nodes under "capabilities" node. Each node contains a selectable USB
+          Power Delivery capability.
+        type: object
+        $ref: "#/$defs/capabilities"
+        unevaluatedProperties: false
+
+    additionalProperties: false
+
 dependencies:
   sink-vdos-v1: [ sink-vdos ]
   sink-vdos: [ sink-vdos-v1 ]
@@ -238,7 +260,42 @@ dependencies:
 required:
   - compatible
 
+$defs:
+  capabilities:
+    type: object
+
+    properties:
+      source-pdos:
+        description: An array of u32 with each entry providing supported power
+          source data object(PDO), the detailed bit definitions of PDO can be found
+          in "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.2
+          Source_Capabilities Message, the order of each entry(PDO) should follow
+          the PD spec chapter 6.4.1. Required for power source and power dual role.
+          User can specify the source PDO array via PDO_FIXED/BATT/VAR/PPS_APDO()
+          defined in dt-bindings/usb/pd.h.
+        minItems: 1
+        maxItems: 7
+        $ref: /schemas/types.yaml#/definitions/uint32-array
+
+      sink-pdos:
+        description: An array of u32 with each entry providing supported power sink
+          data object(PDO), the detailed bit definitions of PDO can be found in
+          "Universal Serial Bus Power Delivery Specification" chapter 6.4.1.3
+          Sink Capabilities Message, the order of each entry(PDO) should follow the
+          PD spec chapter 6.4.1. Required for power sink and power dual role. User
+          can specify the sink PDO array via PDO_FIXED/BATT/VAR/PPS_APDO() defined
+          in dt-bindings/usb/pd.h.
+        minItems: 1
+        maxItems: 7
+        $ref: /schemas/types.yaml#/definitions/uint32-array
+
+      op-sink-microwatt:
+        description: Sink required operating power in microwatt, if source can't
+          offer the power, Capability Mismatch is set. Required for power sink and
+          power dual role.
+
 allOf:
+  - $ref: "#/$defs/capabilities"
   - if:
       properties:
         compatible:
@@ -267,7 +324,7 @@ anyOf:
         - typec-power-opmode
         - new-source-frs-typec-current
 
-additionalProperties: false
+unevaluatedProperties: false
 
 examples:
   # Micro-USB connector with HS lines routed via controller (MUIC).
@@ -289,6 +346,13 @@ examples:
             compatible = "usb-c-connector";
             label = "USB-C";
 
+            altmodes {
+                displayport {
+                    svid = /bits/ 16 <0xff01>;
+                    vdo = <0x00001c46>;
+                };
+            };
+
             ports {
                 #address-cells = <1>;
                 #size-cells = <0>;
index 594ebb3..6ceafa4 100644 (file)
@@ -9,9 +9,6 @@ title: USB xHCI Controller
 maintainers:
   - Mathias Nyman <mathias.nyman@intel.com>
 
-allOf:
-  - $ref: usb-xhci.yaml#
-
 properties:
   compatible:
     oneOf:
@@ -25,6 +22,11 @@ properties:
               - marvell,armada-380-xhci
               - marvell,armada-8k-xhci
           - const: generic-xhci
+      - description: Broadcom SoCs with power domains
+        items:
+          - enum:
+              - brcm,bcm2711-xhci
+          - const: brcm,xhci-brcm-v2
       - description: Broadcom STB SoCs with xHCI
         enum:
           - brcm,xhci-brcm-v2
@@ -49,6 +51,9 @@ properties:
       - const: core
       - const: reg
 
+  power-domains:
+    maxItems: 1
+
 unevaluatedProperties: false
 
 required:
@@ -56,6 +61,20 @@ required:
   - reg
   - interrupts
 
+allOf:
+  - $ref: usb-xhci.yaml#
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: brcm,bcm2711-xhci
+    then:
+      required:
+        - power-domains
+    else:
+      properties:
+        power-domains: false
+
 examples:
   - |
     usb@f0931000 {
index ee08b9c..37cf524 100644 (file)
@@ -29,6 +29,11 @@ properties:
     description:
       the regulator that provides 3.3V core power to the hub.
 
+  peer-hub:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      phandle to the peer hub on the controller.
+
 required:
   - compatible
   - reg
index e9644e3..924fd3d 100644 (file)
@@ -124,6 +124,17 @@ properties:
       defined in the xHCI spec on MTK's controller.
     default: 5000
 
+  rx-fifo-depth:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      It is a quirk used to work around Gen1 isoc-in endpoint transfer issue
+      that still send out unexpected ACK after device finishes the burst
+      transfer with a short packet and cause an exception, specially on a 4K
+      camera device, it happens on controller before about IPM v1.6.0;
+      the side-effect is that it may cause performance drop about 10%,
+      including bulk transfer, prefer to use 3k here. The size is in bytes.
+    enum: [1024, 2048, 3072, 4096]
+
   # the following properties are only used for case 1
   wakeup-source:
     description: enable USB remote wakeup, see power/wakeup-source.txt
index 28eb25e..eaedb4c 100644 (file)
@@ -4,7 +4,7 @@
 $id: http://devicetree.org/schemas/usb/nxp,ptn5110.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: NXP PTN5110 Typec Port Cotroller
+title: NXP PTN5110 Type-C Port Controller
 
 maintainers:
   - Li Jun <jun.li@nxp.com>
index 915c820..63d150b 100644 (file)
@@ -46,6 +46,8 @@ properties:
           - qcom,sm8350-dwc3
           - qcom,sm8450-dwc3
           - qcom,sm8550-dwc3
+          - qcom,sm8650-dwc3
+          - qcom,x1e80100-dwc3
       - const: qcom,dwc3
 
   reg:
@@ -97,12 +99,29 @@ properties:
       - const: apps-usb
 
   interrupts:
-    minItems: 1
-    maxItems: 4
+    description: |
+      Different types of interrupts are used based on HS PHY used on target:
+        - pwr_event: Used for wakeup based on other power events.
+        - hs_phY_irq: Apart from DP/DM/QUSB2 PHY interrupts, there is
+                       hs_phy_irq which is not triggered by default and its
+                       functionality is mutually exclusive to that of
+                       {dp/dm}_hs_phy_irq and qusb2_phy_irq.
+        - qusb2_phy: SoCs with QUSB2 PHY do not have separate DP/DM IRQs and
+                      expose only a single IRQ whose behavior can be modified
+                      by the QUSB2PHY_INTR_CTRL register. The required DPSE/
+                      DMSE configuration is done in QUSB2PHY_INTR_CTRL register
+                      of PHY address space.
+        - {dp/dm}_hs_phy_irq: These IRQ's directly reflect changes on the DP/
+                               DM pads of the SoC. These are used for wakeup
+                               only on SoCs with non-QUSB2 targets with
+                               exception of SDM670/SDM845/SM6350.
+        - ss_phy_irq: Used for remote wakeup in Super Speed mode of operation.
+    minItems: 2
+    maxItems: 5
 
   interrupt-names:
-    minItems: 1
-    maxItems: 4
+    minItems: 2
+    maxItems: 5
 
   qcom,select-utmi-as-pipe-clk:
     description:
@@ -263,6 +282,7 @@ allOf:
           contains:
             enum:
               - qcom,sc8280xp-dwc3
+              - qcom,x1e80100-dwc3
     then:
       properties:
         clocks:
@@ -288,8 +308,8 @@ allOf:
     then:
       properties:
         clocks:
-          minItems: 5
-          maxItems: 6
+          minItems: 4
+          maxItems: 5
         clock-names:
           oneOf:
             - items:
@@ -298,13 +318,11 @@ allOf:
                 - const: iface
                 - const: sleep
                 - const: mock_utmi
-                - const: bus
             - items:
                 - const: cfg_noc
                 - const: core
                 - const: sleep
                 - const: mock_utmi
-                - const: bus
 
   - if:
       properties:
@@ -318,6 +336,7 @@ allOf:
               - qcom,sm8250-dwc3
               - qcom,sm8450-dwc3
               - qcom,sm8550-dwc3
+              - qcom,sm8650-dwc3
     then:
       properties:
         clocks:
@@ -357,59 +376,20 @@ allOf:
         compatible:
           contains:
             enum:
-              - qcom,ipq4019-dwc3
+              - qcom,ipq5018-dwc3
               - qcom,ipq6018-dwc3
-              - qcom,ipq8064-dwc3
               - qcom,ipq8074-dwc3
-              - qcom,msm8994-dwc3
-              - qcom,qcs404-dwc3
-              - qcom,sc7180-dwc3
-              - qcom,sdm670-dwc3
-              - qcom,sdm845-dwc3
-              - qcom,sdx55-dwc3
-              - qcom,sdx65-dwc3
-              - qcom,sdx75-dwc3
-              - qcom,sm4250-dwc3
-              - qcom,sm6125-dwc3
-              - qcom,sm6350-dwc3
-              - qcom,sm8150-dwc3
-              - qcom,sm8250-dwc3
-              - qcom,sm8350-dwc3
-              - qcom,sm8450-dwc3
-              - qcom,sm8550-dwc3
-    then:
-      properties:
-        interrupts:
-          items:
-            - description: The interrupt that is asserted
-                when a wakeup event is received on USB2 bus.
-            - description: The interrupt that is asserted
-                when a wakeup event is received on USB3 bus.
-            - description: Wakeup event on DM line.
-            - description: Wakeup event on DP line.
-        interrupt-names:
-          items:
-            - const: hs_phy_irq
-            - const: ss_phy_irq
-            - const: dm_hs_phy_irq
-            - const: dp_hs_phy_irq
-
-  - if:
-      properties:
-        compatible:
-          contains:
-            enum:
               - qcom,msm8953-dwc3
-              - qcom,msm8996-dwc3
               - qcom,msm8998-dwc3
-              - qcom,sm6115-dwc3
     then:
       properties:
         interrupts:
-          maxItems: 2
+          minItems: 2
+          maxItems: 3
         interrupt-names:
           items:
-            - const: hs_phy_irq
+            - const: pwr_event
+            - const: qusb2_phy
             - const: ss_phy_irq
 
   - if:
@@ -417,37 +397,21 @@ allOf:
         compatible:
           contains:
             enum:
-              - qcom,ipq5018-dwc3
-              - qcom,ipq5332-dwc3
+              - qcom,msm8996-dwc3
+              - qcom,qcs404-dwc3
               - qcom,sdm660-dwc3
-    then:
-      properties:
-        interrupts:
-          minItems: 1
-          maxItems: 2
-        interrupt-names:
-          minItems: 1
-          items:
-            - const: hs_phy_irq
-            - const: ss_phy_irq
-
-  - if:
-      properties:
-        compatible:
-          contains:
-            enum:
-              - qcom,sc7280-dwc3
+              - qcom,sm6115-dwc3
+              - qcom,sm6125-dwc3
     then:
       properties:
         interrupts:
           minItems: 3
           maxItems: 4
         interrupt-names:
-          minItems: 3
           items:
+            - const: pwr_event
+            - const: qusb2_phy
             - const: hs_phy_irq
-            - const: dp_hs_phy_irq
-            - const: dm_hs_phy_irq
             - const: ss_phy_irq
 
   - if:
@@ -455,7 +419,8 @@ allOf:
         compatible:
           contains:
             enum:
-              - qcom,sc8280xp-dwc3
+              - qcom,ipq5332-dwc3
+              - qcom,x1e80100-dwc3
     then:
       properties:
         interrupts:
@@ -472,16 +437,35 @@ allOf:
         compatible:
           contains:
             enum:
+              - qcom,ipq4019-dwc3
+              - qcom,ipq8064-dwc3
+              - qcom,msm8994-dwc3
               - qcom,sa8775p-dwc3
+              - qcom,sc7180-dwc3
+              - qcom,sc7280-dwc3
+              - qcom,sc8280xp-dwc3
+              - qcom,sdm670-dwc3
+              - qcom,sdm845-dwc3
+              - qcom,sdx55-dwc3
+              - qcom,sdx65-dwc3
+              - qcom,sdx75-dwc3
+              - qcom,sm4250-dwc3
+              - qcom,sm6350-dwc3
+              - qcom,sm8150-dwc3
+              - qcom,sm8250-dwc3
+              - qcom,sm8350-dwc3
+              - qcom,sm8450-dwc3
+              - qcom,sm8550-dwc3
+              - qcom,sm8650-dwc3
     then:
       properties:
         interrupts:
-          minItems: 3
-          maxItems: 4
+          minItems: 4
+          maxItems: 5
         interrupt-names:
-          minItems: 3
           items:
             - const: pwr_event
+            - const: hs_phy_irq
             - const: dp_hs_phy_irq
             - const: dm_hs_phy_irq
             - const: ss_phy_irq
@@ -519,12 +503,13 @@ examples:
                           <&gcc GCC_USB30_PRIM_MASTER_CLK>;
             assigned-clock-rates = <19200000>, <150000000>;
 
-            interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>,
-                         <GIC_SPI 486 IRQ_TYPE_LEVEL_HIGH>,
+            interrupts = <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>,
+                         <GIC_SPI 489 IRQ_TYPE_EDGE_BOTH>,
                          <GIC_SPI 488 IRQ_TYPE_EDGE_BOTH>,
-                         <GIC_SPI 489 IRQ_TYPE_EDGE_BOTH>;
-            interrupt-names = "hs_phy_irq", "ss_phy_irq",
-                          "dm_hs_phy_irq", "dp_hs_phy_irq";
+                         <GIC_SPI 486 IRQ_TYPE_LEVEL_HIGH>;
+            interrupt-names = "pwr_event", "hs_phy_irq",
+                          "dp_hs_phy_irq", "dm_hs_phy_irq", "ss_phy_irq";
 
             power-domains = <&gcc USB30_PRIM_GDSC>;
 
diff --git a/Documentation/devicetree/bindings/usb/qcom,wcd939x-usbss.yaml b/Documentation/devicetree/bindings/usb/qcom,wcd939x-usbss.yaml
new file mode 100644 (file)
index 0000000..7ddfd33
--- /dev/null
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/qcom,wcd939x-usbss.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WCD9380/WCD9385 USB SubSystem Altmode/Analog Audio Switch
+
+maintainers:
+  - Neil Armstrong <neil.armstrong@linaro.org>
+
+description:
+  Qualcomm WCD9390/WCD9395 is a standalone Hi-Fi audio codec IC with a
+  functionally separate USB SubSystem for Altmode/Analog Audio Switch
+  accessible over an I2C interface.
+  The Audio Headphone and Microphone data path between the Codec and the
+  USB-C Mux subsystems are external to the IC, thus requiring DT port-endpoint
+  graph description to handle USB-C altmode & orientation switching for Audio
+  Accessory Mode.
+
+properties:
+  compatible:
+    oneOf:
+      - const: qcom,wcd9390-usbss
+      - items:
+          - const: qcom,wcd9395-usbss
+          - const: qcom,wcd9390-usbss
+
+  reg:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vdd-supply:
+    description: USBSS VDD power supply
+
+  mode-switch:
+    description: Flag the port as possible handle of altmode switching
+    type: boolean
+
+  orientation-switch:
+    description: Flag the port as possible handler of orientation switching
+    type: boolean
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description:
+          A port node to link the WCD939x USB SubSystem to a TypeC controller for the
+          purpose of handling altmode muxing and orientation switching.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description:
+          A port node to link the WCD939x USB SubSystem to the Codec SubSystem for the
+          purpose of handling USB-C Audio Accessory Mode muxing and orientation switching.
+
+required:
+  - compatible
+  - reg
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        typec-mux@42 {
+            compatible = "qcom,wcd9390-usbss";
+            reg = <0x42>;
+
+            vdd-supply = <&vreg_bob>;
+
+            mode-switch;
+            orientation-switch;
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+                    wcd9390_usbss_sbu: endpoint {
+                        remote-endpoint = <&typec_sbu>;
+                    };
+                };
+                port@1 {
+                    reg = <1>;
+                    wcd9390_usbss_codec: endpoint {
+                        remote-endpoint = <&wcd9390_codec_usbss>;
+                    };
+                };
+            };
+        };
+    };
+...
index bad55df..40ada78 100644 (file)
@@ -19,7 +19,7 @@ properties:
       - items:
           - enum:
               - renesas,usbhs-r7s9210   # RZ/A2
-              - renesas,usbhs-r9a07g043 # RZ/G2UL
+              - renesas,usbhs-r9a07g043 # RZ/G2UL and RZ/Five
               - renesas,usbhs-r9a07g044 # RZ/G2{L,LC}
               - renesas,usbhs-r9a07g054 # RZ/V2L
           - const: renesas,rza2-usbhs
index ee5af4b..203a1eb 100644 (file)
@@ -432,6 +432,10 @@ properties:
     items:
       enum: [1, 4, 8, 16, 32, 64, 128, 256]
 
+  num-hc-interrupters:
+    maximum: 8
+    default: 1
+
   port:
     $ref: /schemas/graph.yaml#/properties/port
     description:
index 323d664..1745e28 100644 (file)
@@ -38,6 +38,10 @@ properties:
       - const: main
       - const: patch-address
 
+  reset-gpios:
+    description: GPIO used for the HRESET pin.
+    maxItems: 1
+
   wakeup-source: true
 
   interrupts:
@@ -90,6 +94,7 @@ additionalProperties: false
 
 examples:
   - |
+    #include <dt-bindings/gpio/gpio.h>
     #include <dt-bindings/interrupt-controller/irq.h>
     i2c {
         #address-cells = <1>;
@@ -106,6 +111,7 @@ examples:
 
             pinctrl-names = "default";
             pinctrl-0 = <&typec_pins>;
+            reset-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
 
             typec_con: connector {
                 compatible = "usb-c-connector";
index 180a261..4238ae8 100644 (file)
@@ -29,6 +29,12 @@ properties:
     description: Interrupt moderation interval
     default: 5000
 
+  num-hc-interrupters:
+    description: Maximum number of interrupters to allocate
+    $ref: /schemas/types.yaml#/definitions/uint16
+    minimum: 1
+    maximum: 1024
+
 additionalProperties: true
 
 examples:
index 29072c1..8cd62c4 100644 (file)
@@ -448,15 +448,17 @@ Function-specific configfs interface
 The function name to use when creating the function directory is "ncm".
 The NCM function provides these attributes in its function directory:
 
-       =============== ==================================================
-       ifname          network device interface name associated with this
-                       function instance
-       qmult           queue length multiplier for high and super speed
-       host_addr       MAC address of host's end of this
-                       Ethernet over USB link
-       dev_addr        MAC address of device's end of this
-                       Ethernet over USB link
-       =============== ==================================================
+       ===============   ==================================================
+       ifname            network device interface name associated with this
+                         function instance
+       qmult             queue length multiplier for high and super speed
+       host_addr         MAC address of host's end of this
+                         Ethernet over USB link
+       dev_addr          MAC address of device's end of this
+                         Ethernet over USB link
+       max_segment_size  Segment size required for P2P connections. This
+                         will set MTU to (max_segment_size - 14 bytes)
+       ===============   ==================================================
 
 and after creating the functions/ncm.<instance name> they contain default
 values: qmult is 5, dev_addr and host_addr are randomly selected.
index 818a164..59b2132 100644 (file)
@@ -81,9 +81,6 @@ feature must be kept in the implementation.
 Potential future improvements
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- Report more events (suspend, resume, etc.) through
-  ``USB_RAW_IOCTL_EVENT_FETCH``.
-
 - Support ``O_NONBLOCK`` I/O. This would be another mode of operation, where
   Raw Gadget would not wait until the completion of each USB request.
 
index 98817a6..d233a19 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "bcm2835-rpi.dtsi"
 
+#include <dt-bindings/power/raspberrypi-power.h>
 #include <dt-bindings/reset/raspberrypi,firmware-reset.h>
 
 / {
@@ -76,3 +77,7 @@
 &vchiq {
        interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
 };
+
+&xhci {
+       power-domains = <&power RPI_POWER_DOMAIN_USB>;
+};
index 4a379a1..22c7f15 100644 (file)
                        };
                };
 
+               xhci: usb@7e9c0000 {
+                       compatible = "brcm,bcm2711-xhci", "brcm,xhci-brcm-v2";
+                       reg = <0x0 0x7e9c0000 0x100000>;
+                       #address-cells = <1>;
+                       #size-cells = <0>;
+                       interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
+                       /* DWC2 and this IP block share the same USB PHY,
+                        * enabling both at the same time results in lockups.
+                        * So keep this node disabled and let the bootloader
+                        * decide which interface should be enabled.
+                        */
+                       status = "disabled";
+               };
+
                v3d: gpu@7ec00000 {
                        compatible = "brcm,2711-v3d";
                        reg = <0x0 0x7ec00000 0x4000>,
index bbdcd44..3c6079e 100644 (file)
 &xhci0 {
        status = "okay";
 
+       rx-fifo-depth = <3072>;
        vusb33-supply = <&mt6359_vusb_ldo_reg>;
        vbus-supply = <&usb_vbus>;
 };
 &xhci1 {
        status = "okay";
 
+       rx-fifo-depth = <3072>;
        vusb33-supply = <&mt6359_vusb_ldo_reg>;
        vbus-supply = <&usb_vbus>;
 };
index 7f8327b..e423c57 100644 (file)
                        compatible = "qcom,msm8916-bimc";
                        reg = <0x00400000 0x62000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_BIMC_CLK>,
-                                <&rpmcc RPM_SMD_BIMC_A_CLK>;
                };
 
                tsens: thermal-sensor@4a9000 {
                        compatible = "qcom,msm8916-pcnoc";
                        reg = <0x00500000 0x11000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_PCNOC_CLK>,
-                                <&rpmcc RPM_SMD_PCNOC_A_CLK>;
                };
 
                snoc: interconnect@580000 {
                        compatible = "qcom,msm8916-snoc";
                        reg = <0x00580000 0x14000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_SNOC_CLK>,
-                                <&rpmcc RPM_SMD_SNOC_A_CLK>;
                };
 
                stm: stm@802000 {
index 29f6bd9..82d85ff 100644 (file)
                bimc: interconnect@400000 {
                        compatible = "qcom,msm8939-bimc";
                        reg = <0x00400000 0x62000>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_BIMC_CLK>,
-                                <&rpmcc RPM_SMD_BIMC_A_CLK>;
                        #interconnect-cells = <1>;
                };
 
                pcnoc: interconnect@500000 {
                        compatible = "qcom,msm8939-pcnoc";
                        reg = <0x00500000 0x11000>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_PCNOC_CLK>,
-                                <&rpmcc RPM_SMD_PCNOC_A_CLK>;
                        #interconnect-cells = <1>;
                };
 
                snoc: interconnect@580000 {
                        compatible = "qcom,msm8939-snoc";
                        reg = <0x00580000 0x14080>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_SNOC_CLK>,
-                                <&rpmcc RPM_SMD_SNOC_A_CLK>;
                        #interconnect-cells = <1>;
 
                        snoc_mm: interconnect-snoc {
                                compatible = "qcom,msm8939-snoc-mm";
-                               clock-names = "bus", "bus_a";
-                               clocks = <&rpmcc RPM_SMD_SYSMMNOC_CLK>,
-                                        <&rpmcc RPM_SMD_SYSMMNOC_A_CLK>;
                                #interconnect-cells = <1>;
                        };
                };
index 8c6a7ef..8d41ed2 100644 (file)
                        compatible = "qcom,msm8996-bimc";
                        reg = <0x00408000 0x5a000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_BIMC_CLK>,
-                                <&rpmcc RPM_SMD_BIMC_A_CLK>;
                };
 
                tsens0: thermal-sensor@4a9000 {
                        compatible = "qcom,msm8996-cnoc";
                        reg = <0x00500000 0x1000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_CNOC_CLK>,
-                                <&rpmcc RPM_SMD_CNOC_A_CLK>;
                };
 
                snoc: interconnect@524000 {
                        compatible = "qcom,msm8996-snoc";
                        reg = <0x00524000 0x1c000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_SNOC_CLK>,
-                                <&rpmcc RPM_SMD_SNOC_A_CLK>;
                };
 
                a0noc: interconnect@543000 {
                        compatible = "qcom,msm8996-a1noc";
                        reg = <0x00562000 0x5000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_AGGR1_NOC_CLK>,
-                                <&rpmcc RPM_SMD_AGGR1_NOC_A_CLK>;
                };
 
                a2noc: interconnect@583000 {
                        compatible = "qcom,msm8996-a2noc";
                        reg = <0x00583000 0x7000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a", "aggre2_ufs_axi", "ufs_axi";
-                       clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>,
-                                <&rpmcc RPM_SMD_AGGR2_NOC_A_CLK>,
-                                <&gcc GCC_AGGRE2_UFS_AXI_CLK>,
+                       clock-names = "aggre2_ufs_axi", "ufs_axi";
+                       clocks = <&gcc GCC_AGGRE2_UFS_AXI_CLK>,
                                 <&gcc GCC_UFS_AXI_CLK>;
                };
 
                        compatible = "qcom,msm8996-mnoc";
                        reg = <0x005a4000 0x1c000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a", "iface";
-                       clocks = <&rpmcc RPM_SMD_MMAXI_CLK>,
-                                <&rpmcc RPM_SMD_MMAXI_A_CLK>,
-                                <&mmcc AHB_CLK_SRC>;
+                       clock-names = "iface";
+                       clocks = <&mmcc AHB_CLK_SRC>;
                };
 
                pnoc: interconnect@5c0000 {
                        compatible = "qcom,msm8996-pnoc";
                        reg = <0x005c0000 0x3000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_PCNOC_CLK>,
-                                <&rpmcc RPM_SMD_PCNOC_A_CLK>;
                };
 
                tcsr_mutex: hwlock@740000 {
                                          "handover",
                                          "stop-ack";
 
-                       clocks = <&xo_board>,
-                                <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
-                       clock-names = "xo", "aggre2";
+                       clocks = <&xo_board>;
+                       clock-names = "xo";
 
                        memory-region = <&slpi_mem>;
 
                                 <&gcc GCC_MSS_GPLL0_DIV_CLK>,
                                 <&gcc GCC_MSS_SNOC_AXI_CLK>,
                                 <&gcc GCC_MSS_MNOC_BIMC_AXI_CLK>,
-                                <&rpmcc RPM_SMD_PCNOC_CLK>,
                                 <&rpmcc RPM_SMD_QDSS_CLK>;
-                       clock-names = "iface", "bus", "mem", "xo", "gpll0_mss",
-                                     "snoc_axi", "mnoc_axi", "pnoc", "qdss";
+                       clock-names = "iface",
+                                     "bus",
+                                     "mem",
+                                     "xo",
+                                     "gpll0_mss",
+                                     "snoc_axi",
+                                     "mnoc_axi",
+                                     "qdss";
 
                        resets = <&gcc GCC_MSS_RESTART>;
                        reset-names = "mss_restart";
index bb591c6..2793cc2 100644 (file)
 
                        px-supply = <&vreg_lvs2a_1p8>;
 
-                       clocks = <&rpmcc RPM_SMD_XO_CLK_SRC>,
-                                <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
-                       clock-names = "xo", "aggre2";
+                       clocks = <&rpmcc RPM_SMD_XO_CLK_SRC>;
+                       clock-names = "xo";
 
                        memory-region = <&slpi_mem>;
 
index 6ac64ce..2f2eeaf 100644 (file)
                        reg = <0x00400000 0x80000>;
                        compatible = "qcom,qcs404-bimc";
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_BIMC_CLK>,
-                               <&rpmcc RPM_SMD_BIMC_A_CLK>;
                };
 
                tsens: thermal-sensor@4a9000 {
                        reg = <0x00500000 0x15080>;
                        compatible = "qcom,qcs404-pcnoc";
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_PNOC_CLK>,
-                               <&rpmcc RPM_SMD_PNOC_A_CLK>;
                };
 
                snoc: interconnect@580000 {
                        reg = <0x00580000 0x23080>;
                        compatible = "qcom,qcs404-snoc";
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_SNOC_CLK>,
-                               <&rpmcc RPM_SMD_SNOC_A_CLK>;
                };
 
                remoteproc_cdsp: remoteproc@b00000 {
index 0431845..cd0db4f 100644 (file)
 
                altmodes {
                        displayport {
-                               svid = <0xff01>;
+                               svid = /bits/ 16 <0xff01>;
                                vdo = <0x00001c46>;
                        };
                };
index 775700f..513fe5e 100644 (file)
                        compatible = "qcom,sdm660-bimc";
                        reg = <0x01008000 0x78000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_BIMC_CLK>,
-                                <&rpmcc RPM_SMD_BIMC_A_CLK>;
                };
 
                restart@10ac000 {
                        compatible = "qcom,sdm660-cnoc";
                        reg = <0x01500000 0x10000>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_CNOC_CLK>,
-                                <&rpmcc RPM_SMD_CNOC_A_CLK>;
                };
 
                snoc: interconnect@1626000 {
                        compatible = "qcom,sdm660-snoc";
                        reg = <0x01626000 0x7090>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a";
-                       clocks = <&rpmcc RPM_SMD_SNOC_CLK>,
-                                <&rpmcc RPM_SMD_SNOC_A_CLK>;
                };
 
                anoc2_smmu: iommu@16c0000 {
                        compatible = "qcom,sdm630-smmu-v2", "qcom,smmu-v2";
                        reg = <0x016c0000 0x40000>;
-
-                       assigned-clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
-                       assigned-clock-rates = <1000>;
-                       clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
-                       clock-names = "bus";
                        #global-interrupts = <2>;
                        #iommu-cells = <1>;
 
                        compatible = "qcom,sdm660-a2noc";
                        reg = <0x01704000 0xc100>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus",
-                                     "bus_a",
-                                     "ipa",
+                       clock-names = "ipa",
                                      "ufs_axi",
                                      "aggre2_ufs_axi",
                                      "aggre2_usb3_axi",
                                      "cfg_noc_usb2_axi";
-                       clocks = <&rpmcc RPM_SMD_AGGR2_NOC_CLK>,
-                                <&rpmcc RPM_SMD_AGGR2_NOC_A_CLK>,
-                                <&rpmcc RPM_SMD_IPA_CLK>,
+                       clocks = <&rpmcc RPM_SMD_IPA_CLK>,
                                 <&gcc GCC_UFS_AXI_CLK>,
                                 <&gcc GCC_AGGRE2_UFS_AXI_CLK>,
                                 <&gcc GCC_AGGRE2_USB3_AXI_CLK>,
                        compatible = "qcom,sdm660-mnoc";
                        reg = <0x01745000 0xa010>;
                        #interconnect-cells = <1>;
-                       clock-names = "bus", "bus_a", "iface";
-                       clocks = <&rpmcc RPM_SMD_MMSSNOC_AXI_CLK>,
-                                <&rpmcc RPM_SMD_MMSSNOC_AXI_CLK_A>,
-                                <&mmcc AHB_CLK_SRC>;
+                       clock-names = "iface";
+                       clocks = <&mmcc AHB_CLK_SRC>;
                };
 
                tsens: thermal-sensor@10ae000 {
                        clocks = <&gcc GCC_GPU_CFG_AHB_CLK>,
                                 <&gcc GCC_BIMC_GFX_CLK>,
                                 <&gcc GCC_GPU_BIMC_GFX_CLK>;
-                       clock-names = "iface", "mem", "mem_iface";
+                       clock-names = "iface",
+                                     "mem",
+                                     "mem_iface";
                        #global-interrupts = <2>;
                        #iommu-cells = <1>;
 
                                 <&gcc GCC_USB30_MASTER_CLK>,
                                 <&gcc GCC_AGGRE2_USB3_AXI_CLK>,
                                 <&gcc GCC_USB30_SLEEP_CLK>,
-                                <&gcc GCC_USB30_MOCK_UTMI_CLK>,
-                                <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
+                                <&gcc GCC_USB30_MOCK_UTMI_CLK>;
                        clock-names = "cfg_noc",
                                      "core",
                                      "iface",
                                      "sleep",
-                                     "mock_utmi",
-                                     "bus";
+                                     "mock_utmi";
 
                        assigned-clocks = <&gcc GCC_USB30_MOCK_UTMI_CLK>,
-                                         <&gcc GCC_USB30_MASTER_CLK>,
-                                         <&rpmcc RPM_SMD_AGGR2_NOC_CLK>;
-                       assigned-clock-rates = <19200000>, <120000000>,
-                                              <19200000>;
+                                         <&gcc GCC_USB30_MASTER_CLK>;
+                       assigned-clock-rates = <19200000>, <120000000>;
 
                        interrupts = <GIC_SPI 347 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 243 IRQ_TYPE_LEVEL_HIGH>;
 
                        clocks = <&mmcc MNOC_AHB_CLK>,
                                 <&mmcc BIMC_SMMU_AHB_CLK>,
-                                <&rpmcc RPM_SMD_MMSSNOC_AXI_CLK>,
                                 <&mmcc BIMC_SMMU_AXI_CLK>;
                        clock-names = "iface-mm", "iface-smmu",
-                                     "bus-mm", "bus-smmu";
+                                     "bus-smmu";
                        #global-interrupts = <2>;
                        #iommu-cells = <1>;
 
                        compatible = "qcom,sdm660-gnoc";
                        reg = <0x17900000 0xe000>;
                        #interconnect-cells = <1>;
-                       /*
-                        * This one apparently features no clocks,
-                        * so let's not mess with the driver needlessly
-                        */
-                       clock-names = "bus", "bus_a";
-                       clocks = <&xo_board>, <&xo_board>;
                };
 
                apcs_glb: mailbox@17911000 {
index 7835170..d31edad 100644 (file)
@@ -4796,10 +4796,8 @@ static struct usb_driver btusb_driver = {
        .disable_hub_initiated_lpm = 1,
 
 #ifdef CONFIG_DEV_COREDUMP
-       .drvwrap = {
-               .driver = {
-                       .coredump = btusb_coredump,
-               },
+       .driver = {
+               .coredump = btusb_coredump,
        },
 #endif
 };
index 24ad9f5..1efa39e 100644 (file)
@@ -1143,7 +1143,7 @@ static void __exit peak_usb_exit(void)
        int err;
 
        /* last chance do send any synchronous commands here */
-       err = driver_for_each_device(&peak_usb_driver.drvwrap.driver, NULL,
+       err = driver_for_each_device(&peak_usb_driver.driver, NULL,
                                     NULL, peak_usb_do_device_exit);
        if (err)
                pr_err("%s: failed to stop all can devices (err %d)\n",
index 9bf2140..0d0672d 100644 (file)
@@ -10069,7 +10069,7 @@ static struct usb_driver rtl8152_driver = {
        .disable_hub_initiated_lpm = 1,
 };
 
-static int rtl8152_cfgselector_probe(struct usb_device *udev)
+static int rtl8152_cfgselector_choose_configuration(struct usb_device *udev)
 {
        struct usb_host_config *c;
        int i, num_configs;
@@ -10096,19 +10096,13 @@ static int rtl8152_cfgselector_probe(struct usb_device *udev)
        if (i == num_configs)
                return -ENODEV;
 
-       if (usb_set_configuration(udev, c->desc.bConfigurationValue)) {
-               dev_err(&udev->dev, "Failed to set configuration %d\n",
-                       c->desc.bConfigurationValue);
-               return -ENODEV;
-       }
-
-       return 0;
+       return c->desc.bConfigurationValue;
 }
 
 static struct usb_device_driver rtl8152_cfgselector_driver = {
-       .name =         MODULENAME "-cfgselector",
-       .probe =        rtl8152_cfgselector_probe,
-       .id_table =     rtl8152_table,
+       .name = MODULENAME "-cfgselector",
+       .choose_configuration = rtl8152_cfgselector_choose_configuration,
+       .id_table = rtl8152_table,
        .generic_subclass = 1,
        .supports_autosuspend = 1,
 };
index 2178675..0ccf735 100644 (file)
@@ -1581,7 +1581,7 @@ static int brcmf_usb_reset_device(struct device *dev, void *notused)
 
 void brcmf_usb_exit(void)
 {
-       struct device_driver *drv = &brcmf_usbdrvr.drvwrap.driver;
+       struct device_driver *drv = &brcmf_usbdrvr.driver;
        int ret;
 
        brcmf_dbg(USB, "Enter\n");
index d3ab957..515e6db 100644 (file)
@@ -687,7 +687,7 @@ static struct usb_driver mwifiex_usb_driver = {
        .suspend = mwifiex_usb_suspend,
        .resume = mwifiex_usb_resume,
        .soft_unbind = 1,
-       .drvwrap.driver = {
+       .driver = {
                .coredump = mwifiex_usb_coredump,
        },
 };
index 848baec..93f75ba 100644 (file)
@@ -136,7 +136,7 @@ static const struct software_node altmodes_node = {
 };
 
 static const struct property_entry dp_altmode_properties[] = {
-       PROPERTY_ENTRY_U32("svid", 0xff01),
+       PROPERTY_ENTRY_U16("svid", 0xff01),
        PROPERTY_ENTRY_U32("vdo", 0x0c0086),
        { }
 };
index ec7b5f6..9fb1a64 100644 (file)
@@ -307,7 +307,7 @@ static const struct attribute_group *domain_attr_groups[] = {
        NULL,
 };
 
-struct bus_type tb_bus_type = {
+const struct bus_type tb_bus_type = {
        .name = "thunderbolt",
        .match = tb_service_match,
        .probe = tb_service_probe,
index d8b9c73..56790d5 100644 (file)
@@ -1020,7 +1020,7 @@ icm_tr_driver_ready(struct tb *tb, enum tb_security_level *security_level,
 
        memset(&reply, 0, sizeof(reply));
        ret = icm_request(tb, &request, sizeof(request), &reply, sizeof(reply),
-                         1, 10, 2000);
+                         1, 10, 250);
        if (ret)
                return ret;
 
index 4b7bec7..fb4f46e 100644 (file)
@@ -1517,6 +1517,10 @@ static struct pci_device_id nhi_ids[] = {
          .driver_data = (kernel_ulong_t)&icl_nhi_ops },
        { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1),
          .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+       { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0),
+         .driver_data = (kernel_ulong_t)&icl_nhi_ops },
+       { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1),
+         .driver_data = (kernel_ulong_t)&icl_nhi_ops },
        { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) },
        { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) },
 
index 0f029ce..7a07c7c 100644 (file)
@@ -90,6 +90,8 @@ extern const struct tb_nhi_ops icl_nhi_ops;
 #define PCI_DEVICE_ID_INTEL_TGL_H_NHI1                 0x9a21
 #define PCI_DEVICE_ID_INTEL_RPL_NHI0                   0xa73e
 #define PCI_DEVICE_ID_INTEL_RPL_NHI1                   0xa76d
+#define PCI_DEVICE_ID_INTEL_LNL_NHI0                   0xa833
+#define PCI_DEVICE_ID_INTEL_LNL_NHI1                   0xa834
 
 #define PCI_CLASS_SERIAL_USB_USB4                      0x0c0340
 
index 44e9b09..900114b 100644 (file)
@@ -941,22 +941,6 @@ int tb_port_get_link_generation(struct tb_port *port)
        }
 }
 
-static const char *width_name(enum tb_link_width width)
-{
-       switch (width) {
-       case TB_LINK_WIDTH_SINGLE:
-               return "symmetric, single lane";
-       case TB_LINK_WIDTH_DUAL:
-               return "symmetric, dual lanes";
-       case TB_LINK_WIDTH_ASYM_TX:
-               return "asymmetric, 3 transmitters, 1 receiver";
-       case TB_LINK_WIDTH_ASYM_RX:
-               return "asymmetric, 3 receivers, 1 transmitter";
-       default:
-               return "unknown";
-       }
-}
-
 /**
  * tb_port_get_link_width() - Get current link width
  * @port: Port to check (USB4 or CIO)
@@ -2769,7 +2753,7 @@ static void tb_switch_link_init(struct tb_switch *sw)
                return;
 
        tb_sw_dbg(sw, "current link speed %u.0 Gb/s\n", sw->link_speed);
-       tb_sw_dbg(sw, "current link width %s\n", width_name(sw->link_width));
+       tb_sw_dbg(sw, "current link width %s\n", tb_width_name(sw->link_width));
 
        bonded = sw->link_width >= TB_LINK_WIDTH_DUAL;
 
@@ -2789,6 +2773,19 @@ static void tb_switch_link_init(struct tb_switch *sw)
        if (down->dual_link_port)
                down->dual_link_port->bonded = bonded;
        tb_port_update_credits(down);
+
+       if (tb_port_get_link_generation(up) < 4)
+               return;
+
+       /*
+        * Set the Gen 4 preferred link width. This is what the router
+        * prefers when the link is brought up. If the router does not
+        * support asymmetric link configuration, this also will be set
+        * to TB_LINK_WIDTH_DUAL.
+        */
+       sw->preferred_link_width = sw->link_width;
+       tb_sw_dbg(sw, "preferred link width %s\n",
+                 tb_width_name(sw->preferred_link_width));
 }
 
 /**
@@ -3029,7 +3026,7 @@ int tb_switch_set_link_width(struct tb_switch *sw, enum tb_link_width width)
 
        tb_switch_update_link_attributes(sw);
 
-       tb_sw_dbg(sw, "link width set to %s\n", width_name(width));
+       tb_sw_dbg(sw, "link width set to %s\n", tb_width_name(width));
        return ret;
 }
 
index fd49f86..846d281 100644 (file)
@@ -513,8 +513,6 @@ static void tb_port_unconfigure_xdomain(struct tb_port *port)
                usb4_port_unconfigure_xdomain(port);
        else
                tb_lc_unconfigure_xdomain(port);
-
-       tb_port_enable(port->dual_link_port);
 }
 
 static void tb_scan_xdomain(struct tb_port *port)
@@ -1087,15 +1085,14 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
                             struct tb_port *dst_port, int requested_up,
                             int requested_down)
 {
+       bool clx = false, clx_disabled = false, downstream;
        struct tb_switch *sw;
-       bool clx, downstream;
        struct tb_port *up;
        int ret = 0;
 
        if (!asym_threshold)
                return 0;
 
-       /* Disable CL states before doing any transitions */
        downstream = tb_port_path_direction_downstream(src_port, dst_port);
        /* Pick up router deepest in the hierarchy */
        if (downstream)
@@ -1103,11 +1100,10 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
        else
                sw = src_port->sw;
 
-       clx = tb_disable_clx(sw);
-
        tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
+               struct tb_port *down = tb_switch_downstream_port(up->sw);
+               enum tb_link_width width_up, width_down;
                int consumed_up, consumed_down;
-               enum tb_link_width width;
 
                ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
                                               &consumed_up, &consumed_down);
@@ -1128,7 +1124,8 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
                        if (consumed_down + requested_down < asym_threshold)
                                continue;
 
-                       width = TB_LINK_WIDTH_ASYM_RX;
+                       width_up = TB_LINK_WIDTH_ASYM_RX;
+                       width_down = TB_LINK_WIDTH_ASYM_TX;
                } else {
                        /* Upstream, the opposite of above */
                        if (consumed_down + requested_down >= TB_ASYM_MIN) {
@@ -1138,22 +1135,34 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
                        if (consumed_up + requested_up < asym_threshold)
                                continue;
 
-                       width = TB_LINK_WIDTH_ASYM_TX;
+                       width_up = TB_LINK_WIDTH_ASYM_TX;
+                       width_down = TB_LINK_WIDTH_ASYM_RX;
                }
 
-               if (up->sw->link_width == width)
+               if (up->sw->link_width == width_up)
                        continue;
 
-               if (!tb_port_width_supported(up, width))
+               if (!tb_port_width_supported(up, width_up) ||
+                   !tb_port_width_supported(down, width_down))
                        continue;
 
+               /*
+                * Disable CL states before doing any transitions. We
+                * delayed it until now that we know there is a real
+                * transition taking place.
+                */
+               if (!clx_disabled) {
+                       clx = tb_disable_clx(sw);
+                       clx_disabled = true;
+               }
+
                tb_sw_dbg(up->sw, "configuring asymmetric link\n");
 
                /*
                 * Here requested + consumed > threshold so we need to
                 * transtion the link into asymmetric now.
                 */
-               ret = tb_switch_set_link_width(up->sw, width);
+               ret = tb_switch_set_link_width(up->sw, width_up);
                if (ret) {
                        tb_sw_warn(up->sw, "failed to set link width\n");
                        break;
@@ -1174,24 +1183,24 @@ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
  * @dst_port: Destination adapter
  * @requested_up: New lower bandwidth request upstream (Mb/s)
  * @requested_down: New lower bandwidth request downstream (Mb/s)
+ * @keep_asym: Keep asymmetric link if preferred
  *
  * Goes over each link from @src_port to @dst_port and tries to
  * transition the link to symmetric if the currently consumed bandwidth
- * allows.
+ * allows and link asymmetric preference is ignored (if @keep_asym is %false).
  */
 static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
                            struct tb_port *dst_port, int requested_up,
-                           int requested_down)
+                           int requested_down, bool keep_asym)
 {
+       bool clx = false, clx_disabled = false, downstream;
        struct tb_switch *sw;
-       bool clx, downstream;
        struct tb_port *up;
        int ret = 0;
 
        if (!asym_threshold)
                return 0;
 
-       /* Disable CL states before doing any transitions */
        downstream = tb_port_path_direction_downstream(src_port, dst_port);
        /* Pick up router deepest in the hierarchy */
        if (downstream)
@@ -1199,8 +1208,6 @@ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
        else
                sw = src_port->sw;
 
-       clx = tb_disable_clx(sw);
-
        tb_for_each_upstream_port_on_path(src_port, dst_port, up) {
                int consumed_up, consumed_down;
 
@@ -1233,6 +1240,25 @@ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
                if (up->sw->link_width == TB_LINK_WIDTH_DUAL)
                        continue;
 
+               /*
+                * Here consumed < threshold so we can transition the
+                * link to symmetric.
+                *
+                * However, if the router prefers asymmetric link we
+                * honor that (unless @keep_asym is %false).
+                */
+               if (keep_asym &&
+                   up->sw->preferred_link_width > TB_LINK_WIDTH_DUAL) {
+                       tb_sw_dbg(up->sw, "keeping preferred asymmetric link\n");
+                       continue;
+               }
+
+               /* Disable CL states before doing any transitions */
+               if (!clx_disabled) {
+                       clx = tb_disable_clx(sw);
+                       clx_disabled = true;
+               }
+
                tb_sw_dbg(up->sw, "configuring symmetric link\n");
 
                ret = tb_switch_set_link_width(up->sw, TB_LINK_WIDTH_DUAL);
@@ -1280,7 +1306,7 @@ static void tb_configure_link(struct tb_port *down, struct tb_port *up,
                struct tb_port *host_port;
 
                host_port = tb_port_at(tb_route(sw), tb->root_switch);
-               tb_configure_sym(tb, host_port, up, 0, 0);
+               tb_configure_sym(tb, host_port, up, 0, 0, false);
        }
 
        /* Set the link configured */
@@ -1465,7 +1491,7 @@ static void tb_deactivate_and_free_tunnel(struct tb_tunnel *tunnel)
                 * If bandwidth on a link is < asym_threshold
                 * transition the link to symmetric.
                 */
-               tb_configure_sym(tb, src_port, dst_port, 0, 0);
+               tb_configure_sym(tb, src_port, dst_port, 0, 0, true);
                /* Now we can allow the domain to runtime suspend again */
                pm_runtime_mark_last_busy(&dst_port->sw->dev);
                pm_runtime_put_autosuspend(&dst_port->sw->dev);
@@ -1901,7 +1927,7 @@ static void tb_dp_resource_available(struct tb *tb, struct tb_port *port)
                        return;
        }
 
-       tb_port_dbg(port, "DP %s resource available\n",
+       tb_port_dbg(port, "DP %s resource available after hotplug\n",
                    tb_port_is_dpin(port) ? "IN" : "OUT");
        list_add_tail(&port->list, &tcm->dp_resources);
 
@@ -2287,7 +2313,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
                 * If bandwidth on a link is < asym_threshold transition
                 * the link to symmetric.
                 */
-               tb_configure_sym(tb, in, out, *requested_up, *requested_down);
+               tb_configure_sym(tb, in, out, *requested_up, *requested_down, true);
                /*
                 * If requested bandwidth is less or equal than what is
                 * currently allocated to that tunnel we simply change
@@ -2330,7 +2356,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
                ret = tb_configure_asym(tb, in, out, *requested_up,
                                        *requested_down);
                if (ret) {
-                       tb_configure_sym(tb, in, out, 0, 0);
+                       tb_configure_sym(tb, in, out, 0, 0, true);
                        return ret;
                }
 
@@ -2338,7 +2364,7 @@ static int tb_alloc_dp_bandwidth(struct tb_tunnel *tunnel, int *requested_up,
                                                requested_down);
                if (ret) {
                        tb_tunnel_warn(tunnel, "failed to allocate bandwidth\n");
-                       tb_configure_sym(tb, in, out, 0, 0);
+                       tb_configure_sym(tb, in, out, 0, 0, true);
                }
        } else {
                ret = -ENOBUFS;
index e299e53..997c5a5 100644 (file)
@@ -125,6 +125,7 @@ struct tb_switch_tmu {
  * @device_name: Name of the device (or %NULL if not known)
  * @link_speed: Speed of the link in Gb/s
  * @link_width: Width of the upstream facing link
+ * @preferred_link_width: Router preferred link width (only set for Gen 4 links)
  * @link_usb4: Upstream link is USB4
  * @generation: Switch Thunderbolt generation
  * @cap_plug_events: Offset to the plug events capability (%0 if not found)
@@ -178,6 +179,7 @@ struct tb_switch {
        const char *device_name;
        unsigned int link_speed;
        enum tb_link_width link_width;
+       enum tb_link_width preferred_link_width;
        bool link_usb4;
        unsigned int generation;
        int cap_plug_events;
@@ -568,6 +570,22 @@ static inline struct tb_port *tb_port_at(u64 route, struct tb_switch *sw)
        return &sw->ports[port];
 }
 
+static inline const char *tb_width_name(enum tb_link_width width)
+{
+       switch (width) {
+       case TB_LINK_WIDTH_SINGLE:
+               return "symmetric, single lane";
+       case TB_LINK_WIDTH_DUAL:
+               return "symmetric, dual lanes";
+       case TB_LINK_WIDTH_ASYM_TX:
+               return "asymmetric, 3 transmitters, 1 receiver";
+       case TB_LINK_WIDTH_ASYM_RX:
+               return "asymmetric, 3 receivers, 1 transmitter";
+       default:
+               return "unknown";
+       }
+}
+
 /**
  * tb_port_has_remote() - Does the port have switch connected downstream
  * @port: Port to check
index 11f2aec..9a259c7 100644 (file)
@@ -894,7 +894,7 @@ static int tb_switch_tmu_change_mode(struct tb_switch *sw)
 
        ret = tb_switch_set_tmu_mode_params(sw, sw->tmu.mode_request);
        if (ret)
-               return ret;
+               goto out;
 
        /* Program the new mode and the downstream router lane adapter */
        switch (sw->tmu.mode_request) {
index 7534cd3..6fffb2c 100644 (file)
@@ -173,16 +173,28 @@ static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable)
        int ret;
 
        /* Only supported of both routers are at least USB4 v2 */
-       if (tb_port_get_link_generation(port) < 4)
+       if ((usb4_switch_version(tunnel->src_port->sw) < 2) ||
+          (usb4_switch_version(tunnel->dst_port->sw) < 2))
+               return 0;
+
+       if (enable && tb_port_get_link_generation(port) < 4)
                return 0;
 
        ret = usb4_pci_port_set_ext_encapsulation(tunnel->src_port, enable);
        if (ret)
                return ret;
 
+       /*
+        * Downstream router could be unplugged so disable of encapsulation
+        * in upstream router is still possible.
+        */
        ret = usb4_pci_port_set_ext_encapsulation(tunnel->dst_port, enable);
-       if (ret)
-               return ret;
+       if (ret) {
+               if (enable)
+                       return ret;
+               if (ret != -ENODEV)
+                       return ret;
+       }
 
        tb_tunnel_dbg(tunnel, "extended encapsulation %s\n",
                      str_enabled_disabled(enable));
@@ -199,14 +211,21 @@ static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate)
                        return res;
        }
 
-       res = tb_pci_port_enable(tunnel->src_port, activate);
+       if (activate)
+               res = tb_pci_port_enable(tunnel->dst_port, activate);
+       else
+               res = tb_pci_port_enable(tunnel->src_port, activate);
        if (res)
                return res;
 
-       if (tb_port_is_pcie_up(tunnel->dst_port)) {
-               res = tb_pci_port_enable(tunnel->dst_port, activate);
+
+       if (activate) {
+               res = tb_pci_port_enable(tunnel->src_port, activate);
                if (res)
                        return res;
+       } else {
+               /* Downstream router could be unplugged */
+               tb_pci_port_enable(tunnel->dst_port, activate);
        }
 
        return activate ? 0 : tb_pci_set_ext_encapsulation(tunnel, activate);
@@ -1067,8 +1086,7 @@ static int tb_dp_alloc_bandwidth(struct tb_tunnel *tunnel, int *alloc_up,
        return 0;
 }
 
-static int tb_dp_read_dprx(struct tb_tunnel *tunnel, u32 *rate, u32 *lanes,
-                          int timeout_msec)
+static int tb_dp_wait_dprx(struct tb_tunnel *tunnel, int timeout_msec)
 {
        ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
        struct tb_port *in = tunnel->src_port;
@@ -1087,15 +1105,13 @@ static int tb_dp_read_dprx(struct tb_tunnel *tunnel, u32 *rate, u32 *lanes,
                        return ret;
 
                if (val & DP_COMMON_CAP_DPRX_DONE) {
-                       *rate = tb_dp_cap_get_rate(val);
-                       *lanes = tb_dp_cap_get_lanes(val);
-
                        tb_tunnel_dbg(tunnel, "DPRX read done\n");
                        return 0;
                }
                usleep_range(100, 150);
        } while (ktime_before(ktime_get(), timeout));
 
+       tb_tunnel_dbg(tunnel, "DPRX read timeout\n");
        return -ETIMEDOUT;
 }
 
@@ -1110,6 +1126,7 @@ static int tb_dp_read_cap(struct tb_tunnel *tunnel, unsigned int cap, u32 *rate,
        switch (cap) {
        case DP_LOCAL_CAP:
        case DP_REMOTE_CAP:
+       case DP_COMMON_CAP:
                break;
 
        default:
@@ -1182,7 +1199,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
                 * reduced one). Otherwise return the remote (possibly
                 * reduced) caps.
                 */
-               ret = tb_dp_read_dprx(tunnel, &rate, &lanes, 150);
+               ret = tb_dp_wait_dprx(tunnel, 150);
                if (ret) {
                        if (ret == -ETIMEDOUT)
                                ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP,
@@ -1190,6 +1207,9 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up,
                        if (ret)
                                return ret;
                }
+               ret = tb_dp_read_cap(tunnel, DP_COMMON_CAP, &rate, &lanes);
+               if (ret)
+                       return ret;
        } else if (sw->generation >= 2) {
                ret = tb_dp_read_cap(tunnel, DP_REMOTE_CAP, &rate, &lanes);
                if (ret)
@@ -1313,8 +1333,6 @@ static void tb_dp_dump(struct tb_tunnel *tunnel)
                      "DP IN maximum supported bandwidth %u Mb/s x%u = %u Mb/s\n",
                      rate, lanes, tb_dp_bandwidth(rate, lanes));
 
-       out = tunnel->dst_port;
-
        if (tb_port_read(out, &dp_cap, TB_CFG_PORT,
                         out->cap_adap + DP_LOCAL_CAP, 1))
                return;
index 9803f0b..9495742 100644 (file)
@@ -1462,6 +1462,11 @@ static int tb_xdomain_get_properties(struct tb_xdomain *xd)
                                tb_port_disable(port->dual_link_port);
                }
 
+               dev_dbg(&xd->dev, "current link speed %u.0 Gb/s\n",
+                       xd->link_speed);
+               dev_dbg(&xd->dev, "current link width %s\n",
+                       tb_width_name(xd->link_width));
+
                if (device_add(&xd->dev)) {
                        dev_err(&xd->dev, "failed to add XDomain device\n");
                        return -ENODEV;
@@ -1895,6 +1900,50 @@ struct device_type tb_xdomain_type = {
 };
 EXPORT_SYMBOL_GPL(tb_xdomain_type);
 
+static void tb_xdomain_link_init(struct tb_xdomain *xd, struct tb_port *down)
+{
+       if (!down->dual_link_port)
+               return;
+
+       /*
+        * Gen 4 links come up already as bonded so only update the port
+        * structures here.
+        */
+       if (tb_port_get_link_generation(down) >= 4) {
+               down->bonded = true;
+               down->dual_link_port->bonded = true;
+       } else {
+               xd->bonding_possible = true;
+       }
+}
+
+static void tb_xdomain_link_exit(struct tb_xdomain *xd)
+{
+       struct tb_port *down = tb_xdomain_downstream_port(xd);
+
+       if (!down->dual_link_port)
+               return;
+
+       if (tb_port_get_link_generation(down) >= 4) {
+               down->bonded = false;
+               down->dual_link_port->bonded = false;
+       } else if (xd->link_width > TB_LINK_WIDTH_SINGLE) {
+               /*
+                * Just return port structures back to way they were and
+                * update credits. No need to update userspace because
+                * the XDomain is removed soon anyway.
+                */
+               tb_port_lane_bonding_disable(down);
+               tb_port_update_credits(down);
+       } else if (down->dual_link_port) {
+               /*
+                * Re-enable the lane 1 adapter we disabled at the end
+                * of tb_xdomain_get_properties().
+                */
+               tb_port_enable(down->dual_link_port);
+       }
+}
+
 /**
  * tb_xdomain_alloc() - Allocate new XDomain object
  * @tb: Domain where the XDomain belongs
@@ -1945,7 +1994,8 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
                        goto err_free_local_uuid;
        } else {
                xd->needs_uuid = true;
-               xd->bonding_possible = !!down->dual_link_port;
+
+               tb_xdomain_link_init(xd, down);
        }
 
        device_initialize(&xd->dev);
@@ -2014,6 +2064,8 @@ void tb_xdomain_remove(struct tb_xdomain *xd)
 
        device_for_each_child_reverse(&xd->dev, xd, unregister_service);
 
+       tb_xdomain_link_exit(xd);
+
        /*
         * Undo runtime PM here explicitly because it is possible that
         * the XDomain was never added to the bus and thus device_del()
index 9c620af..407b0d8 100644 (file)
@@ -2493,6 +2493,9 @@ static int send_break(struct tty_struct *tty, unsigned int duration)
        if (!retval) {
                msleep_interruptible(duration);
                retval = tty->ops->break_ctl(tty, 0);
+       } else if (retval == -EOPNOTSUPP) {
+               /* some drivers can tell only dynamically */
+               retval = 0;
        }
        tty_write_unlock(tty);
 
index 5812f7e..1670381 100644 (file)
@@ -546,7 +546,7 @@ MODULE_PARM_DESC(annex,
 
 #define uea_wait(sc, cond, timeo) \
 ({ \
-       int _r = wait_event_interruptible_timeout(sc->sync_q, \
+       int _r = wait_event_freezable_timeout(sc->sync_q, \
                        (cond) || kthread_should_stop(), timeo); \
        if (kthread_should_stop()) \
                _r = -ENODEV; \
@@ -1896,7 +1896,6 @@ static int uea_kthread(void *data)
                        ret = sc->stat(sc);
                if (ret != -EAGAIN)
                        uea_wait(sc, 0, msecs_to_jiffies(1000));
-               try_to_freeze();
        }
        uea_leaves(INS_TO_USBDEV(sc));
        return ret;
@@ -2252,7 +2251,7 @@ static ssize_t stat_status_show(struct device *dev, struct device_attribute *att
        sc = dev_to_uea(dev);
        if (!sc)
                goto out;
-       ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.state);
+       ret = sysfs_emit(buf, "%08x\n", sc->stats.phy.state);
 out:
        mutex_unlock(&uea_mutex);
        return ret;
@@ -2318,19 +2317,19 @@ static ssize_t stat_human_status_show(struct device *dev,
 
        switch (modem_state) {
        case 0:
-               ret = sprintf(buf, "Modem is booting\n");
+               ret = sysfs_emit(buf, "Modem is booting\n");
                break;
        case 1:
-               ret = sprintf(buf, "Modem is initializing\n");
+               ret = sysfs_emit(buf, "Modem is initializing\n");
                break;
        case 2:
-               ret = sprintf(buf, "Modem is operational\n");
+               ret = sysfs_emit(buf, "Modem is operational\n");
                break;
        case 3:
-               ret = sprintf(buf, "Modem synchronization failed\n");
+               ret = sysfs_emit(buf, "Modem synchronization failed\n");
                break;
        default:
-               ret = sprintf(buf, "Modem state is unknown\n");
+               ret = sysfs_emit(buf, "Modem state is unknown\n");
                break;
        }
 out:
@@ -2364,7 +2363,7 @@ static ssize_t stat_delin_show(struct device *dev, struct device_attribute *attr
                        delin = "LOSS";
        }
 
-       ret = sprintf(buf, "%s\n", delin);
+       ret = sysfs_emit(buf, "%s\n", delin);
 out:
        mutex_unlock(&uea_mutex);
        return ret;
@@ -2384,7 +2383,7 @@ static ssize_t stat_##name##_show(struct device *dev,             \
        sc = dev_to_uea(dev);                                   \
        if (!sc)                                                \
                goto out;                                       \
-       ret = snprintf(buf, 10, "%08x\n", sc->stats.phy.name);  \
+       ret = sysfs_emit(buf, "%08x\n", sc->stats.phy.name);    \
        if (reset)                                              \
                sc->stats.phy.name = 0;                         \
 out:                                                           \
index 11a5b34..aeca902 100644 (file)
@@ -1119,6 +1119,8 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
        dma_addr_t trb_dma;
        u32 togle_pcs = 1;
        int sg_iter = 0;
+       int num_trb_req;
+       int trb_burst;
        int num_trb;
        int address;
        u32 control;
@@ -1126,16 +1128,15 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
        u16 total_tdl = 0;
        struct scatterlist *s = NULL;
        bool sg_supported = !!(request->num_mapped_sgs);
+       u32 ioc = request->no_interrupt ? 0 : TRB_IOC;
 
+       num_trb_req = sg_supported ? request->num_mapped_sgs : 1;
+
+       /* ISO transfer require each SOF have a TD, each TD include some TRBs */
        if (priv_ep->type == USB_ENDPOINT_XFER_ISOC)
-               num_trb = priv_ep->interval;
+               num_trb = priv_ep->interval * num_trb_req;
        else
-               num_trb = sg_supported ? request->num_mapped_sgs : 1;
-
-       if (num_trb > priv_ep->free_trbs) {
-               priv_ep->flags |= EP_RING_FULL;
-               return -ENOBUFS;
-       }
+               num_trb = num_trb_req;
 
        priv_req = to_cdns3_request(request);
        address = priv_ep->endpoint.desc->bEndpointAddress;
@@ -1184,14 +1185,31 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
 
                link_trb->control = cpu_to_le32(((priv_ep->pcs) ? TRB_CYCLE : 0) |
                                    TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit);
+
+               if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) {
+                       /*
+                        * ISO require LINK TRB must be first one of TD.
+                        * Fill LINK TRBs for left trb space to simply software process logic.
+                        */
+                       while (priv_ep->enqueue) {
+                               *trb = *link_trb;
+                               trace_cdns3_prepare_trb(priv_ep, trb);
+
+                               cdns3_ep_inc_enq(priv_ep);
+                               trb = priv_ep->trb_pool + priv_ep->enqueue;
+                               priv_req->trb = trb;
+                       }
+               }
+       }
+
+       if (num_trb > priv_ep->free_trbs) {
+               priv_ep->flags |= EP_RING_FULL;
+               return -ENOBUFS;
        }
 
        if (priv_dev->dev_ver <= DEV_VER_V2)
                togle_pcs = cdns3_wa1_update_guard(priv_ep, trb);
 
-       if (sg_supported)
-               s = request->sg;
-
        /* set incorrect Cycle Bit for first trb*/
        control = priv_ep->pcs ? 0 : TRB_CYCLE;
        trb->length = 0;
@@ -1209,6 +1227,9 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
        do {
                u32 length;
 
+               if (!(sg_iter % num_trb_req) && sg_supported)
+                       s = request->sg;
+
                /* fill TRB */
                control |= TRB_TYPE(TRB_NORMAL);
                if (sg_supported) {
@@ -1223,7 +1244,36 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
                        total_tdl += DIV_ROUND_UP(length,
                                               priv_ep->endpoint.maxpacket);
 
-               trb->length |= cpu_to_le32(TRB_BURST_LEN(priv_ep->trb_burst_size) |
+               trb_burst = priv_ep->trb_burst_size;
+
+               /*
+                * Supposed DMA cross 4k bounder problem should be fixed at DEV_VER_V2, but still
+                * met problem when do ISO transfer if sg enabled.
+                *
+                * Data pattern likes below when sg enabled, package size is 1k and mult is 2
+                *       [UVC Header(8B) ] [data(3k - 8)] ...
+                *
+                * The received data at offset 0xd000 will get 0xc000 data, len 0x70. Error happen
+                * as below pattern:
+                *      0xd000: wrong
+                *      0xe000: wrong
+                *      0xf000: correct
+                *      0x10000: wrong
+                *      0x11000: wrong
+                *      0x12000: correct
+                *      ...
+                *
+                * But it is still unclear about why error have not happen below 0xd000, it should
+                * cross 4k bounder. But anyway, the below code can fix this problem.
+                *
+                * To avoid DMA cross 4k bounder at ISO transfer, reduce burst len according to 16.
+                */
+               if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && priv_dev->dev_ver <= DEV_VER_V2)
+                       if (ALIGN_DOWN(trb->buffer, SZ_4K) !=
+                           ALIGN_DOWN(trb->buffer + length, SZ_4K))
+                               trb_burst = 16;
+
+               trb->length |= cpu_to_le32(TRB_BURST_LEN(trb_burst) |
                                        TRB_LEN(length));
                pcs = priv_ep->pcs ? TRB_CYCLE : 0;
 
@@ -1235,11 +1285,11 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
                        control |= pcs;
 
                if (priv_ep->type == USB_ENDPOINT_XFER_ISOC  && !priv_ep->dir) {
-                       control |= TRB_IOC | TRB_ISP;
+                       control |= ioc | TRB_ISP;
                } else {
                        /* for last element in TD or in SG list */
                        if (sg_iter == (num_trb - 1) && sg_iter != 0)
-                               control |= pcs | TRB_IOC | TRB_ISP;
+                               control |= pcs | ioc | TRB_ISP;
                }
 
                if (sg_iter)
@@ -1250,7 +1300,7 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
                if (sg_supported) {
                        trb->control |= cpu_to_le32(TRB_ISP);
                        /* Don't set chain bit for last TRB */
-                       if (sg_iter < num_trb - 1)
+                       if ((sg_iter % num_trb_req) < num_trb_req - 1)
                                trb->control |= cpu_to_le32(TRB_CHAIN);
 
                        s = sg_next(s);
@@ -1270,7 +1320,7 @@ static int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep,
        priv_req->num_of_trb = num_trb;
 
        if (sg_iter == 1)
-               trb->control |= cpu_to_le32(TRB_IOC | TRB_ISP);
+               trb->control |= cpu_to_le32(ioc | TRB_ISP);
 
        if (priv_dev->dev_ver < DEV_VER_V2 &&
            (priv_ep->flags & EP_TDLCHK_EN)) {
@@ -1508,6 +1558,12 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
 
                /* The TRB was changed as link TRB, and the request was handled at ep_dequeue */
                while (TRB_FIELD_TO_TYPE(le32_to_cpu(trb->control)) == TRB_LINK) {
+
+                       /* ISO ep_traddr may stop at LINK TRB */
+                       if (priv_ep->dequeue == cdns3_get_dma_pos(priv_dev, priv_ep) &&
+                           priv_ep->type == USB_ENDPOINT_XFER_ISOC)
+                               break;
+
                        trace_cdns3_complete_trb(priv_ep, trb);
                        cdns3_ep_inc_deq(priv_ep);
                        trb = priv_ep->trb_pool + priv_ep->dequeue;
@@ -1540,6 +1596,10 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev,
                        }
 
                        if (request_handled) {
+                               /* TRBs are duplicated by priv_ep->interval time for ISO IN */
+                               if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && priv_ep->dir)
+                                       request->actual /= priv_ep->interval;
+
                                cdns3_gadget_giveback(priv_ep, priv_req, 0);
                                request_handled = false;
                                transfer_end = false;
@@ -2035,11 +2095,10 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable)
        bool is_iso_ep = (priv_ep->type == USB_ENDPOINT_XFER_ISOC);
        struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
        u32 bEndpointAddress = priv_ep->num | priv_ep->dir;
-       u32 max_packet_size = 0;
-       u8 maxburst = 0;
+       u32 max_packet_size = priv_ep->wMaxPacketSize;
+       u8 maxburst = priv_ep->bMaxBurst;
        u32 ep_cfg = 0;
        u8 buffering;
-       u8 mult = 0;
        int ret;
 
        buffering = priv_dev->ep_buf_size - 1;
@@ -2061,8 +2120,7 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable)
                break;
        default:
                ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC);
-               mult = priv_dev->ep_iso_burst - 1;
-               buffering = mult + 1;
+               buffering = (priv_ep->bMaxBurst + 1) * (priv_ep->mult + 1) - 1;
        }
 
        switch (priv_dev->gadget.speed) {
@@ -2073,17 +2131,8 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable)
                max_packet_size = is_iso_ep ? 1024 : 512;
                break;
        case USB_SPEED_SUPER:
-               /* It's limitation that driver assumes in driver. */
-               mult = 0;
-               max_packet_size = 1024;
-               if (priv_ep->type == USB_ENDPOINT_XFER_ISOC) {
-                       maxburst = priv_dev->ep_iso_burst - 1;
-                       buffering = (mult + 1) *
-                                   (maxburst + 1);
-
-                       if (priv_ep->interval > 1)
-                               buffering++;
-               } else {
+               if (priv_ep->type != USB_ENDPOINT_XFER_ISOC) {
+                       max_packet_size = 1024;
                        maxburst = priv_dev->ep_buf_size - 1;
                }
                break;
@@ -2112,7 +2161,6 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable)
        if (priv_dev->dev_ver < DEV_VER_V2)
                priv_ep->trb_burst_size = 16;
 
-       mult = min_t(u8, mult, EP_CFG_MULT_MAX);
        buffering = min_t(u8, buffering, EP_CFG_BUFFERING_MAX);
        maxburst = min_t(u8, maxburst, EP_CFG_MAXBURST_MAX);
 
@@ -2146,7 +2194,7 @@ int cdns3_ep_config(struct cdns3_endpoint *priv_ep, bool enable)
        }
 
        ep_cfg |= EP_CFG_MAXPKTSIZE(max_packet_size) |
-                 EP_CFG_MULT(mult) |
+                 EP_CFG_MULT(priv_ep->mult) |                  /* must match EP setting */
                  EP_CFG_BUFFERING(buffering) |
                  EP_CFG_MAXBURST(maxburst);
 
@@ -2236,6 +2284,13 @@ usb_ep *cdns3_gadget_match_ep(struct usb_gadget *gadget,
        priv_ep->type = usb_endpoint_type(desc);
        priv_ep->flags |= EP_CLAIMED;
        priv_ep->interval = desc->bInterval ? BIT(desc->bInterval - 1) : 0;
+       priv_ep->wMaxPacketSize =  usb_endpoint_maxp(desc);
+       priv_ep->mult = USB_EP_MAXP_MULT(priv_ep->wMaxPacketSize);
+       priv_ep->wMaxPacketSize &= USB_ENDPOINT_MAXP_MASK;
+       if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && comp_desc) {
+               priv_ep->mult =  USB_SS_MULT(comp_desc->bmAttributes) - 1;
+               priv_ep->bMaxBurst = comp_desc->bMaxBurst;
+       }
 
        spin_unlock_irqrestore(&priv_dev->lock, flags);
        return &priv_ep->endpoint;
@@ -3019,22 +3074,40 @@ static int cdns3_gadget_check_config(struct usb_gadget *gadget)
        struct cdns3_endpoint *priv_ep;
        struct usb_ep *ep;
        int n_in = 0;
+       int iso = 0;
+       int out = 1;
        int total;
+       int n;
 
        list_for_each_entry(ep, &gadget->ep_list, ep_list) {
                priv_ep = ep_to_cdns3_ep(ep);
-               if ((priv_ep->flags & EP_CLAIMED) && (ep->address & USB_DIR_IN))
-                       n_in++;
+               if (!(priv_ep->flags & EP_CLAIMED))
+                       continue;
+
+               n = (priv_ep->mult + 1) * (priv_ep->bMaxBurst + 1);
+               if (ep->address & USB_DIR_IN) {
+                       /*
+                        * ISO transfer: DMA start move data when get ISO, only transfer
+                        * data as min(TD size, iso). No benefit for allocate bigger
+                        * internal memory than 'iso'.
+                        */
+                       if (priv_ep->type == USB_ENDPOINT_XFER_ISOC)
+                               iso += n;
+                       else
+                               n_in++;
+               } else {
+                       if (priv_ep->type == USB_ENDPOINT_XFER_ISOC)
+                               out = max_t(int, out, n);
+               }
        }
 
        /* 2KB are reserved for EP0, 1KB for out*/
-       total = 2 + n_in + 1;
+       total = 2 + n_in + out + iso;
 
        if (total > priv_dev->onchip_buffers)
                return -ENOMEM;
 
-       priv_dev->ep_buf_size = priv_dev->ep_iso_burst =
-                       (priv_dev->onchip_buffers - 2) / (n_in + 1);
+       priv_dev->ep_buf_size = (priv_dev->onchip_buffers - 2 - iso) / (n_in + out);
 
        return 0;
 }
index fbe4a8e..086a7bb 100644 (file)
@@ -1168,6 +1168,9 @@ struct cdns3_endpoint {
        u8                      dir;
        u8                      num;
        u8                      type;
+       u8                      mult;
+       u8                      bMaxBurst;
+       u16                     wMaxPacketSize;
        int                     interval;
 
        int                     free_trbs;
index 2c1aca8..3ef8e3c 100644 (file)
@@ -87,16 +87,20 @@ static int cdns3_plat_probe(struct platform_device *pdev)
        cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral");
 
        if (cdns->dev_irq < 0)
-               return cdns->dev_irq;
+               return dev_err_probe(dev, cdns->dev_irq,
+                                    "Failed to get peripheral IRQ\n");
 
        regs = devm_platform_ioremap_resource_byname(pdev, "dev");
        if (IS_ERR(regs))
-               return PTR_ERR(regs);
+               return dev_err_probe(dev, PTR_ERR(regs),
+                                    "Failed to get dev base\n");
+
        cdns->dev_regs  = regs;
 
        cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
        if (cdns->otg_irq < 0)
-               return cdns->otg_irq;
+               return dev_err_probe(dev, cdns->otg_irq,
+                                    "Failed to get otg IRQ\n");
 
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
        if (!res) {
@@ -119,7 +123,8 @@ static int cdns3_plat_probe(struct platform_device *pdev)
 
        cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
        if (IS_ERR(cdns->usb2_phy))
-               return PTR_ERR(cdns->usb2_phy);
+               return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy),
+                                    "Failed to get cdn3,usb2-phy\n");
 
        ret = phy_init(cdns->usb2_phy);
        if (ret)
@@ -127,7 +132,8 @@ static int cdns3_plat_probe(struct platform_device *pdev)
 
        cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
        if (IS_ERR(cdns->usb3_phy))
-               return PTR_ERR(cdns->usb3_phy);
+               return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy),
+                                    "Failed to get cdn3,usb3-phy\n");
 
        ret = phy_init(cdns->usb3_phy);
        if (ret)
index a7265b8..c04d196 100644 (file)
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
-/**
+/*
  * cdns3-starfive.c - StarFive specific Glue layer for Cadence USB Controller
  *
  * Copyright (C) 2023 StarFive Technology Co., Ltd.
index ad617b7..cd138ac 100644 (file)
@@ -187,202 +187,202 @@ static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0,
 
        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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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:
-               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 = scnprintf(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 = scnprintf(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 = scnprintf(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 = scnprintf(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');
+               ret = scnprintf(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 = scnprintf(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");
+       if (ret == size - 1)
+               pr_info("CDNSP: buffer may be truncated.\n");
 
        return str;
 }
@@ -465,32 +465,32 @@ static inline const char *cdnsp_decode_portsc(char *str, size_t size,
 {
        int ret;
 
-       ret = snprintf(str, size, "%s %s %s Link:%s PortSpeed:%d ",
-                      portsc & PORT_POWER ? "Powered" : "Powered-off",
-                      portsc & PORT_CONNECT ? "Connected" : "Not-connected",
-                      portsc & PORT_PED ? "Enabled" : "Disabled",
-                      cdnsp_portsc_link_state_string(portsc),
-                      DEV_PORT_SPEED(portsc));
+       ret = scnprintf(str, size, "%s %s %s Link:%s PortSpeed:%d ",
+                       portsc & PORT_POWER ? "Powered" : "Powered-off",
+                       portsc & PORT_CONNECT ? "Connected" : "Not-connected",
+                       portsc & PORT_PED ? "Enabled" : "Disabled",
+                       cdnsp_portsc_link_state_string(portsc),
+                       DEV_PORT_SPEED(portsc));
 
        if (portsc & PORT_RESET)
-               ret += snprintf(str + ret, size - ret, "In-Reset ");
+               ret += scnprintf(str + ret, size - ret, "In-Reset ");
 
-       ret += snprintf(str + ret, size - ret, "Change: ");
+       ret += scnprintf(str + ret, size - ret, "Change: ");
        if (portsc & PORT_CSC)
-               ret += snprintf(str + ret, size - ret, "CSC ");
+               ret += scnprintf(str + ret, size - ret, "CSC ");
        if (portsc & PORT_WRC)
-               ret += snprintf(str + ret, size - ret, "WRC ");
+               ret += scnprintf(str + ret, size - ret, "WRC ");
        if (portsc & PORT_RC)
-               ret += snprintf(str + ret, size - ret, "PRC ");
+               ret += scnprintf(str + ret, size - ret, "PRC ");
        if (portsc & PORT_PLC)
-               ret += snprintf(str + ret, size - ret, "PLC ");
+               ret += scnprintf(str + ret, size - ret, "PLC ");
        if (portsc & PORT_CEC)
-               ret += snprintf(str + ret, size - ret, "CEC ");
-       ret += snprintf(str + ret, size - ret, "Wake: ");
+               ret += scnprintf(str + ret, size - ret, "CEC ");
+       ret += scnprintf(str + ret, size - ret, "Wake: ");
        if (portsc & PORT_WKCONN_E)
-               ret += snprintf(str + ret, size - ret, "WCE ");
+               ret += scnprintf(str + ret, size - ret, "WCE ");
        if (portsc & PORT_WKDISC_E)
-               ret += snprintf(str + ret, size - ret, "WDE ");
+               ret += scnprintf(str + ret, size - ret, "WDE ");
 
        return str;
 }
@@ -562,20 +562,20 @@ static inline const char *cdnsp_decode_ep_context(char *str, size_t size,
 
        avg = EP_AVG_TRB_LENGTH(tx_info);
 
-       ret = snprintf(str, size, "State %s mult %d max P. Streams %d %s",
-                      cdnsp_ep_state_string(ep_state), mult,
-                      max_pstr, lsa ? "LSA " : "");
+       ret = scnprintf(str, size, "State %s mult %d max P. Streams %d %s",
+                       cdnsp_ep_state_string(ep_state), mult,
+                       max_pstr, lsa ? "LSA " : "");
 
-       ret += snprintf(str + ret, size - ret,
-                       "interval %d us max ESIT payload %d CErr %d ",
-                       (1 << interval) * 125, esit, cerr);
+       ret += scnprintf(str + ret, size - ret,
+                        "interval %d us max ESIT payload %d CErr %d ",
+                        (1 << interval) * 125, esit, cerr);
 
-       ret += snprintf(str + ret, size - ret,
-                       "Type %s %sburst %d maxp %d deq %016llx ",
-                       cdnsp_ep_type_string(ep_type), hid ? "HID" : "",
-                       burst, maxp, deq);
+       ret += scnprintf(str + ret, size - ret,
+                        "Type %s %sburst %d maxp %d deq %016llx ",
+                        cdnsp_ep_type_string(ep_type), hid ? "HID" : "",
+                        burst, maxp, deq);
 
-       ret += snprintf(str + ret, size - ret, "avg trb len %d", avg);
+       ret += scnprintf(str + ret, size - ret, "avg trb len %d", avg);
 
        return str;
 }
index e28bb2f..ae9a6a1 100644 (file)
@@ -96,6 +96,7 @@ struct ci_hdrc_imx_data {
        struct usb_phy *phy;
        struct platform_device *ci_pdev;
        struct clk *clk;
+       struct clk *clk_wakeup;
        struct imx_usbmisc_data *usbmisc_data;
        bool supports_runtime_pm;
        bool override_phy_control;
@@ -199,7 +200,7 @@ static int imx_get_clks(struct device *dev)
 
        data->clk_ipg = devm_clk_get(dev, "ipg");
        if (IS_ERR(data->clk_ipg)) {
-               /* If the platform only needs one clocks */
+               /* If the platform only needs one primary clock */
                data->clk = devm_clk_get(dev, NULL);
                if (IS_ERR(data->clk)) {
                        ret = PTR_ERR(data->clk);
@@ -208,6 +209,13 @@ static int imx_get_clks(struct device *dev)
                                PTR_ERR(data->clk), PTR_ERR(data->clk_ipg));
                        return ret;
                }
+               /* Get wakeup clock. Not all of the platforms need to
+                * handle this clock. So make it optional.
+                */
+               data->clk_wakeup = devm_clk_get_optional(dev, "usb_wakeup_clk");
+               if (IS_ERR(data->clk_wakeup))
+                       ret = dev_err_probe(dev, PTR_ERR(data->clk_wakeup),
+                                       "Failed to get wakeup clk\n");
                return ret;
        }
 
@@ -423,6 +431,10 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
        if (ret)
                goto disable_hsic_regulator;
 
+       ret = clk_prepare_enable(data->clk_wakeup);
+       if (ret)
+               goto err_wakeup_clk;
+
        data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
        if (IS_ERR(data->phy)) {
                ret = PTR_ERR(data->phy);
@@ -504,6 +516,8 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
 disable_device:
        ci_hdrc_remove_device(data->ci_pdev);
 err_clk:
+       clk_disable_unprepare(data->clk_wakeup);
+err_wakeup_clk:
        imx_disable_unprepare_clks(dev);
 disable_hsic_regulator:
        if (data->hsic_pad_regulator)
@@ -530,6 +544,7 @@ static void ci_hdrc_imx_remove(struct platform_device *pdev)
                usb_phy_shutdown(data->phy);
        if (data->ci_pdev) {
                imx_disable_unprepare_clks(&pdev->dev);
+               clk_disable_unprepare(data->clk_wakeup);
                if (data->plat_data->flags & CI_HDRC_PMQOS)
                        cpu_latency_qos_remove_request(&data->pm_qos_req);
                if (data->hsic_pad_regulator)
index 7ac39a2..41014f9 100644 (file)
@@ -523,6 +523,13 @@ static irqreturn_t ci_irq_handler(int irq, void *data)
        u32 otgsc = 0;
 
        if (ci->in_lpm) {
+               /*
+                * If we already have a wakeup irq pending there,
+                * let's just return to wait resume finished firstly.
+                */
+               if (ci->wakeup_int)
+                       return IRQ_HANDLED;
+
                disable_irq_nosync(irq);
                ci->wakeup_int = true;
                pm_runtime_get(ci->dev);
@@ -862,7 +869,7 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
        if (ret)
                return ERR_PTR(ret);
 
-       id = ida_simple_get(&ci_ida, 0, 0, GFP_KERNEL);
+       id = ida_alloc(&ci_ida, GFP_KERNEL);
        if (id < 0)
                return ERR_PTR(id);
 
@@ -892,7 +899,7 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
 err:
        platform_device_put(pdev);
 put_id:
-       ida_simple_remove(&ci_ida, id);
+       ida_free(&ci_ida, id);
        return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(ci_hdrc_add_device);
@@ -901,7 +908,7 @@ void ci_hdrc_remove_device(struct platform_device *pdev)
 {
        int id = pdev->id;
        platform_device_unregister(pdev);
-       ida_simple_remove(&ci_ida, id);
+       ida_free(&ci_ida, id);
 }
 EXPORT_SYMBOL_GPL(ci_hdrc_remove_device);
 
index 0b7bd3c..2d7f616 100644 (file)
@@ -688,7 +688,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq)
                if ((TD_STATUS_ACTIVE & tmptoken) != 0) {
                        int n = hw_ep_bit(hwep->num, hwep->dir);
 
-                       if (ci->rev == CI_REVISION_24)
+                       if (ci->rev == CI_REVISION_24 ||
+                           ci->rev == CI_REVISION_22)
                                if (!hw_read(ci, OP_ENDPTSTAT, BIT(n)))
                                        reprime_dtd(ci, hwep, node);
                        hwreq->req.status = -EALREADY;
index a1f4e1e..0e7439d 100644 (file)
@@ -916,6 +916,9 @@ static int acm_tty_break_ctl(struct tty_struct *tty, int state)
        struct acm *acm = tty->driver_data;
        int retval;
 
+       if (!(acm->ctrl_caps & USB_CDC_CAP_BRK))
+               return -EOPNOTSUPP;
+
        retval = acm_send_break(acm, state ? 0xffff : 0);
        if (retval < 0)
                dev_dbg(&acm->control->dev,
index f58a029..e01b191 100644 (file)
@@ -189,13 +189,13 @@ static int usb_create_newid_files(struct usb_driver *usb_drv)
                goto exit;
 
        if (usb_drv->probe != NULL) {
-               error = driver_create_file(&usb_drv->drvwrap.driver,
+               error = driver_create_file(&usb_drv->driver,
                                           &driver_attr_new_id);
                if (error == 0) {
-                       error = driver_create_file(&usb_drv->drvwrap.driver,
+                       error = driver_create_file(&usb_drv->driver,
                                        &driver_attr_remove_id);
                        if (error)
-                               driver_remove_file(&usb_drv->drvwrap.driver,
+                               driver_remove_file(&usb_drv->driver,
                                                &driver_attr_new_id);
                }
        }
@@ -209,9 +209,9 @@ static void usb_remove_newid_files(struct usb_driver *usb_drv)
                return;
 
        if (usb_drv->probe != NULL) {
-               driver_remove_file(&usb_drv->drvwrap.driver,
+               driver_remove_file(&usb_drv->driver,
                                &driver_attr_remove_id);
-               driver_remove_file(&usb_drv->drvwrap.driver,
+               driver_remove_file(&usb_drv->driver,
                                   &driver_attr_new_id);
        }
 }
@@ -290,7 +290,10 @@ static int usb_probe_device(struct device *dev)
         * specialised device drivers prior to setting the
         * use_generic_driver bit.
         */
-       error = udriver->probe(udev);
+       if (udriver->probe)
+               error = udriver->probe(udev);
+       else if (!udriver->generic_subclass)
+               error = -EINVAL;
        if (error == -ENODEV && udriver != &usb_generic_driver &&
            (udriver->id_table || udriver->match)) {
                udev->use_generic_driver = 1;
@@ -549,7 +552,7 @@ int usb_driver_claim_interface(struct usb_driver *driver,
        if (!iface->authorized)
                return -ENODEV;
 
-       dev->driver = &driver->drvwrap.driver;
+       dev->driver = &driver->driver;
        usb_set_intfdata(iface, data);
        iface->needs_binding = 0;
 
@@ -612,7 +615,7 @@ void usb_driver_release_interface(struct usb_driver *driver,
        struct device *dev = &iface->dev;
 
        /* this should never happen, don't release something that's not ours */
-       if (!dev->driver || dev->driver != &driver->drvwrap.driver)
+       if (!dev->driver || dev->driver != &driver->driver)
                return;
 
        /* don't release from within disconnect() */
@@ -947,7 +950,7 @@ static int __usb_bus_reprobe_drivers(struct device *dev, void *data)
        int ret;
 
        /* Don't reprobe if current driver isn't usb_generic_driver */
-       if (dev->driver != &usb_generic_driver.drvwrap.driver)
+       if (dev->driver != &usb_generic_driver.driver)
                return 0;
 
        udev = to_usb_device(dev);
@@ -961,6 +964,11 @@ static int __usb_bus_reprobe_drivers(struct device *dev, void *data)
        return 0;
 }
 
+bool is_usb_device_driver(const struct device_driver *drv)
+{
+       return drv->probe == usb_probe_device;
+}
+
 /**
  * usb_register_device_driver - register a USB device (not interface) driver
  * @new_udriver: USB operations for the device driver
@@ -980,15 +988,14 @@ int usb_register_device_driver(struct usb_device_driver *new_udriver,
        if (usb_disabled())
                return -ENODEV;
 
-       new_udriver->drvwrap.for_devices = 1;
-       new_udriver->drvwrap.driver.name = new_udriver->name;
-       new_udriver->drvwrap.driver.bus = &usb_bus_type;
-       new_udriver->drvwrap.driver.probe = usb_probe_device;
-       new_udriver->drvwrap.driver.remove = usb_unbind_device;
-       new_udriver->drvwrap.driver.owner = owner;
-       new_udriver->drvwrap.driver.dev_groups = new_udriver->dev_groups;
+       new_udriver->driver.name = new_udriver->name;
+       new_udriver->driver.bus = &usb_bus_type;
+       new_udriver->driver.probe = usb_probe_device;
+       new_udriver->driver.remove = usb_unbind_device;
+       new_udriver->driver.owner = owner;
+       new_udriver->driver.dev_groups = new_udriver->dev_groups;
 
-       retval = driver_register(&new_udriver->drvwrap.driver);
+       retval = driver_register(&new_udriver->driver);
 
        if (!retval) {
                pr_info("%s: registered new device driver %s\n",
@@ -1020,7 +1027,7 @@ void usb_deregister_device_driver(struct usb_device_driver *udriver)
        pr_info("%s: deregistering device driver %s\n",
                        usbcore_name, udriver->name);
 
-       driver_unregister(&udriver->drvwrap.driver);
+       driver_unregister(&udriver->driver);
 }
 EXPORT_SYMBOL_GPL(usb_deregister_device_driver);
 
@@ -1048,18 +1055,17 @@ int usb_register_driver(struct usb_driver *new_driver, struct module *owner,
        if (usb_disabled())
                return -ENODEV;
 
-       new_driver->drvwrap.for_devices = 0;
-       new_driver->drvwrap.driver.name = new_driver->name;
-       new_driver->drvwrap.driver.bus = &usb_bus_type;
-       new_driver->drvwrap.driver.probe = usb_probe_interface;
-       new_driver->drvwrap.driver.remove = usb_unbind_interface;
-       new_driver->drvwrap.driver.owner = owner;
-       new_driver->drvwrap.driver.mod_name = mod_name;
-       new_driver->drvwrap.driver.dev_groups = new_driver->dev_groups;
+       new_driver->driver.name = new_driver->name;
+       new_driver->driver.bus = &usb_bus_type;
+       new_driver->driver.probe = usb_probe_interface;
+       new_driver->driver.remove = usb_unbind_interface;
+       new_driver->driver.owner = owner;
+       new_driver->driver.mod_name = mod_name;
+       new_driver->driver.dev_groups = new_driver->dev_groups;
        spin_lock_init(&new_driver->dynids.lock);
        INIT_LIST_HEAD(&new_driver->dynids.list);
 
-       retval = driver_register(&new_driver->drvwrap.driver);
+       retval = driver_register(&new_driver->driver);
        if (retval)
                goto out;
 
@@ -1074,7 +1080,7 @@ out:
        return retval;
 
 out_newid:
-       driver_unregister(&new_driver->drvwrap.driver);
+       driver_unregister(&new_driver->driver);
 
        pr_err("%s: error %d registering interface driver %s\n",
                usbcore_name, retval, new_driver->name);
@@ -1099,7 +1105,7 @@ void usb_deregister(struct usb_driver *driver)
                        usbcore_name, driver->name);
 
        usb_remove_newid_files(driver);
-       driver_unregister(&driver->drvwrap.driver);
+       driver_unregister(&driver->driver);
        usb_free_dynids(driver);
 }
 EXPORT_SYMBOL_GPL(usb_deregister);
index 740342a..b134bff 100644 (file)
@@ -59,10 +59,26 @@ int usb_choose_configuration(struct usb_device *udev)
        int num_configs;
        int insufficient_power = 0;
        struct usb_host_config *c, *best;
+       struct usb_device_driver *udriver;
+
+       /*
+        * If a USB device (not an interface) doesn't have a driver then the
+        * kernel has no business trying to select or install a configuration
+        * for it.
+        */
+       if (!udev->dev.driver)
+               return -1;
+       udriver = to_usb_device_driver(udev->dev.driver);
 
        if (usb_device_is_owned(udev))
                return 0;
 
+       if (udriver->choose_configuration) {
+               i = udriver->choose_configuration(udev);
+               if (i >= 0)
+                       return i;
+       }
+
        best = NULL;
        c = udev->config;
        num_configs = udev->descriptor.bNumConfigurations;
index 87480a6..ffd7c99 100644 (file)
 #define USB_VENDOR_TEXAS_INSTRUMENTS           0x0451
 #define USB_PRODUCT_TUSB8041_USB3              0x8140
 #define USB_PRODUCT_TUSB8041_USB2              0x8142
-#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND       0x01
-#define HUB_QUIRK_DISABLE_AUTOSUSPEND          0x02
+#define USB_VENDOR_MICROCHIP                   0x0424
+#define USB_PRODUCT_USB4913                    0x4913
+#define USB_PRODUCT_USB4914                    0x4914
+#define USB_PRODUCT_USB4915                    0x4915
+#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND       BIT(0)
+#define HUB_QUIRK_DISABLE_AUTOSUSPEND          BIT(1)
+#define HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL  BIT(2)
 
 #define USB_TP_TRANSMISSION_DELAY      40      /* ns */
 #define USB_TP_TRANSMISSION_DELAY_MAX  65535   /* ns */
 #define USB_PING_RESPONSE_TIME         400     /* ns */
+#define USB_REDUCE_FRAME_INTR_BINTERVAL        9
+
+/*
+ * The SET_ADDRESS request timeout will be 500 ms when
+ * USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT quirk flag is set.
+ */
+#define USB_SHORT_SET_ADDRESS_REQ_TIMEOUT      500  /* ms */
 
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
@@ -1904,6 +1916,14 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
                usb_autopm_get_interface_no_resume(intf);
        }
 
+       if ((id->driver_info & HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL) &&
+           desc->endpoint[0].desc.bInterval > USB_REDUCE_FRAME_INTR_BINTERVAL) {
+               desc->endpoint[0].desc.bInterval =
+                       USB_REDUCE_FRAME_INTR_BINTERVAL;
+               /* Tell the HCD about the interrupt ep's new bInterval */
+               usb_set_interface(hdev, 0, 0);
+       }
+
        if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) {
                onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs);
 
@@ -4626,7 +4646,12 @@ EXPORT_SYMBOL_GPL(usb_ep0_reinit);
 static int hub_set_address(struct usb_device *udev, int devnum)
 {
        int retval;
+       unsigned int timeout_ms = USB_CTRL_SET_TIMEOUT;
        struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+       struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
+
+       if (hub->hdev->quirks & USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT)
+               timeout_ms = USB_SHORT_SET_ADDRESS_REQ_TIMEOUT;
 
        /*
         * The host controller will choose the device address,
@@ -4639,11 +4664,11 @@ static int hub_set_address(struct usb_device *udev, int devnum)
        if (udev->state != USB_STATE_DEFAULT)
                return -EINVAL;
        if (hcd->driver->address_device)
-               retval = hcd->driver->address_device(hcd, udev);
+               retval = hcd->driver->address_device(hcd, udev, timeout_ms);
        else
                retval = usb_control_msg(udev, usb_sndaddr0pipe(),
                                USB_REQ_SET_ADDRESS, 0, devnum, 0,
-                               NULL, 0, USB_CTRL_SET_TIMEOUT);
+                               NULL, 0, timeout_ms);
        if (retval == 0) {
                update_devnum(udev, devnum);
                /* Device now using proper address. */
@@ -5895,6 +5920,21 @@ static const struct usb_device_id hub_id_table[] = {
       .idVendor = USB_VENDOR_TEXAS_INSTRUMENTS,
       .idProduct = USB_PRODUCT_TUSB8041_USB3,
       .driver_info = HUB_QUIRK_DISABLE_AUTOSUSPEND},
+       { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+                       | USB_DEVICE_ID_MATCH_PRODUCT,
+         .idVendor = USB_VENDOR_MICROCHIP,
+         .idProduct = USB_PRODUCT_USB4913,
+         .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
+       { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+                       | USB_DEVICE_ID_MATCH_PRODUCT,
+         .idVendor = USB_VENDOR_MICROCHIP,
+         .idProduct = USB_PRODUCT_USB4914,
+         .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
+       { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
+                       | USB_DEVICE_ID_MATCH_PRODUCT,
+         .idVendor = USB_VENDOR_MICROCHIP,
+         .idProduct = USB_PRODUCT_USB4915,
+         .driver_info = HUB_QUIRK_REDUCE_FRAME_INTR_BINTERVAL},
     { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
       .bDeviceClass = USB_CLASS_HUB},
     { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
index 15e9bd1..b478357 100644 (file)
@@ -138,6 +138,9 @@ static int quirks_param_set(const char *value, const struct kernel_param *kp)
                        case 'o':
                                flags |= USB_QUIRK_HUB_SLOW_RESET;
                                break;
+                       case 'p':
+                               flags |= USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT;
+                               break;
                        /* Ignore unrecognized flag characters */
                        }
                }
@@ -527,6 +530,10 @@ static const struct usb_device_id usb_quirk_list[] = {
 
        { USB_DEVICE(0x2386, 0x350e), .driver_info = USB_QUIRK_NO_LPM },
 
+       /* APTIV AUTOMOTIVE HUB */
+       { USB_DEVICE(0x2c48, 0x0132), .driver_info =
+                       USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT },
+
        /* DJI CineSSD */
        { USB_DEVICE(0x2ca3, 0x0031), .driver_info = USB_QUIRK_NO_LPM },
 
index 2a938cf..dc8d922 100644 (file)
@@ -431,7 +431,7 @@ struct usb_interface *usb_find_interface(struct usb_driver *drv, int minor)
        struct device *dev;
 
        argb.minor = minor;
-       argb.drv = &drv->drvwrap.driver;
+       argb.drv = &drv->driver;
 
        dev = bus_find_device(&usb_bus_type, NULL, &argb, __find_interface);
 
index 6036315..bfecb50 100644 (file)
@@ -175,13 +175,7 @@ static inline int is_root_hub(struct usb_device *udev)
        return (udev->parent == NULL);
 }
 
-/* Do the same for device drivers and interface drivers. */
-
-static inline int is_usb_device_driver(struct device_driver *drv)
-{
-       return container_of(drv, struct usbdrv_wrap, driver)->
-                       for_devices;
-}
+extern bool is_usb_device_driver(const struct device_driver *drv);
 
 /* for labeling diagnostics */
 extern const char *usbcore_name;
index fb03162..eb677c3 100644 (file)
@@ -130,6 +130,7 @@ static void dwc2_set_rk_params(struct dwc2_hsotg *hsotg)
        p->lpm_clock_gating = false;
        p->besl = false;
        p->hird_threshold_en = false;
+       p->no_clock_gating = true;
 }
 
 static void dwc2_set_ltq_params(struct dwc2_hsotg *hsotg)
index b101dbf..3e55838 100644 (file)
@@ -277,48 +277,11 @@ int dwc3_core_soft_reset(struct dwc3 *dwc)
        /*
         * We're resetting only the device side because, if we're in host mode,
         * XHCI driver will reset the host block. If dwc3 was configured for
-        * host-only mode or current role is host, then we can return early.
+        * host-only mode, then we can return early.
         */
        if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST)
                return 0;
 
-       /*
-        * If the dr_mode is host and the dwc->current_dr_role is not the
-        * corresponding DWC3_GCTL_PRTCAP_HOST, then the dwc3_core_init_mode
-        * isn't executed yet. Ensure the phy is ready before the controller
-        * updates the GCTL.PRTCAPDIR or other settings by soft-resetting
-        * the phy.
-        *
-        * Note: GUSB3PIPECTL[n] and GUSB2PHYCFG[n] are port settings where n
-        * is port index. If this is a multiport host, then we need to reset
-        * all active ports.
-        */
-       if (dwc->dr_mode == USB_DR_MODE_HOST) {
-               u32 usb3_port;
-               u32 usb2_port;
-
-               usb3_port = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
-               usb3_port |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
-               dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), usb3_port);
-
-               usb2_port = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
-               usb2_port |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
-               dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), usb2_port);
-
-               /* Small delay for phy reset assertion */
-               usleep_range(1000, 2000);
-
-               usb3_port &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
-               dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), usb3_port);
-
-               usb2_port &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
-               dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), usb2_port);
-
-               /* Wait for clock synchronization */
-               msleep(50);
-               return 0;
-       }
-
        reg = dwc3_readl(dwc->regs, DWC3_DCTL);
        reg |= DWC3_DCTL_CSFTRST;
        reg &= ~DWC3_DCTL_RUN_STOP;
@@ -1367,6 +1330,18 @@ static int dwc3_core_init(struct dwc3 *dwc)
 
        dwc3_config_threshold(dwc);
 
+       /*
+        * Modify this for all supported Super Speed ports when
+        * multiport support is added.
+        */
+       if (hw_mode != DWC3_GHWPARAMS0_MODE_GADGET &&
+           (DWC3_IP_IS(DWC31)) &&
+           dwc->maximum_speed == USB_SPEED_SUPER) {
+               reg = dwc3_readl(dwc->regs, DWC3_LLUCTL);
+               reg |= DWC3_LLUCTL_FORCE_GEN1;
+               dwc3_writel(dwc->regs, DWC3_LLUCTL, reg);
+       }
+
        return 0;
 
 err_power_off_phy:
@@ -2340,12 +2315,15 @@ static int dwc3_resume(struct device *dev)
 
        pinctrl_pm_select_default_state(dev);
 
+       pm_runtime_disable(dev);
+       pm_runtime_set_active(dev);
+
        ret = dwc3_resume_common(dwc, PMSG_RESUME);
-       if (ret)
+       if (ret) {
+               pm_runtime_set_suspended(dev);
                return ret;
+       }
 
-       pm_runtime_disable(dev);
-       pm_runtime_set_active(dev);
        pm_runtime_enable(dev);
 
        return 0;
index efe6caf..e3eea96 100644 (file)
 #define DWC3_OEVTEN            0xcc0C
 #define DWC3_OSTS              0xcc10
 
+#define DWC3_LLUCTL            0xd024
+
 /* Bit fields */
 
 /* Global SoC Bus Configuration INCRx Register 0 */
 /* Global HWPARAMS4 Register */
 #define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n)   (((n) & (0x0f << 13)) >> 13)
 #define DWC3_MAX_HIBER_SCRATCHBUFS             15
+#define DWC3_EXT_BUFF_CONTROL          BIT(21)
 
 /* Global HWPARAMS6 Register */
 #define DWC3_GHWPARAMS6_BCSUPPORT              BIT(14)
 #define DWC3_OSTS_VBUSVLD              BIT(1)
 #define DWC3_OSTS_CONIDSTS             BIT(0)
 
+/* Force Gen1 speed on Gen2 link */
+#define DWC3_LLUCTL_FORCE_GEN1         BIT(10)
+
 /* Structures */
 
 struct dwc3_trb;
index a1e15f2..8ee4480 100644 (file)
@@ -363,8 +363,10 @@ static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev)
        }
 
        ret = clk_prepare_enable(dwc3_imx->hsio_clk);
-       if (ret)
+       if (ret) {
+               clk_disable_unprepare(dwc3_imx->suspend_clk);
                return ret;
+       }
 
        ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME);
 
index fdf6d5d..dbd6a5b 100644 (file)
@@ -57,7 +57,7 @@ struct dwc3_acpi_pdata {
        u32                     qscratch_base_offset;
        u32                     qscratch_base_size;
        u32                     dwc3_core_base_size;
-       int                     hs_phy_irq_index;
+       int                     qusb2_phy_irq_index;
        int                     dp_hs_phy_irq_index;
        int                     dm_hs_phy_irq_index;
        int                     ss_phy_irq_index;
@@ -73,7 +73,7 @@ struct dwc3_qcom {
        int                     num_clocks;
        struct reset_control    *resets;
 
-       int                     hs_phy_irq;
+       int                     qusb2_phy_irq;
        int                     dp_hs_phy_irq;
        int                     dm_hs_phy_irq;
        int                     ss_phy_irq;
@@ -372,7 +372,7 @@ static void dwc3_qcom_disable_wakeup_irq(int irq)
 
 static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
 {
-       dwc3_qcom_disable_wakeup_irq(qcom->hs_phy_irq);
+       dwc3_qcom_disable_wakeup_irq(qcom->qusb2_phy_irq);
 
        if (qcom->usb2_speed == USB_SPEED_LOW) {
                dwc3_qcom_disable_wakeup_irq(qcom->dm_hs_phy_irq);
@@ -389,7 +389,7 @@ static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
 
 static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
 {
-       dwc3_qcom_enable_wakeup_irq(qcom->hs_phy_irq, 0);
+       dwc3_qcom_enable_wakeup_irq(qcom->qusb2_phy_irq, 0);
 
        /*
         * Configure DP/DM line interrupts based on the USB2 device attached to
@@ -542,19 +542,19 @@ static int dwc3_qcom_setup_irq(struct platform_device *pdev)
        int irq;
        int ret;
 
-       irq = dwc3_qcom_get_irq(pdev, "hs_phy_irq",
-                               pdata ? pdata->hs_phy_irq_index : -1);
+       irq = dwc3_qcom_get_irq(pdev, "qusb2_phy",
+                               pdata ? pdata->qusb2_phy_irq_index : -1);
        if (irq > 0) {
                /* Keep wakeup interrupts disabled until suspend */
                ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
                                        qcom_dwc3_resume_irq,
                                        IRQF_ONESHOT | IRQF_NO_AUTOEN,
-                                       "qcom_dwc3 HS", qcom);
+                                       "qcom_dwc3 QUSB2", qcom);
                if (ret) {
-                       dev_err(qcom->dev, "hs_phy_irq failed: %d\n", ret);
+                       dev_err(qcom->dev, "qusb2_phy_irq failed: %d\n", ret);
                        return ret;
                }
-               qcom->hs_phy_irq = irq;
+               qcom->qusb2_phy_irq = irq;
        }
 
        irq = dwc3_qcom_get_irq(pdev, "dp_hs_phy_irq",
@@ -1058,7 +1058,7 @@ static const struct dwc3_acpi_pdata sdm845_acpi_pdata = {
        .qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET,
        .qscratch_base_size = SDM845_QSCRATCH_SIZE,
        .dwc3_core_base_size = SDM845_DWC3_CORE_SIZE,
-       .hs_phy_irq_index = 1,
+       .qusb2_phy_irq_index = 1,
        .dp_hs_phy_irq_index = 4,
        .dm_hs_phy_irq_index = 3,
        .ss_phy_irq_index = 2
@@ -1068,7 +1068,7 @@ static const struct dwc3_acpi_pdata sdm845_acpi_urs_pdata = {
        .qscratch_base_offset = SDM845_QSCRATCH_BASE_OFFSET,
        .qscratch_base_size = SDM845_QSCRATCH_SIZE,
        .dwc3_core_base_size = SDM845_DWC3_CORE_SIZE,
-       .hs_phy_irq_index = 1,
+       .qusb2_phy_irq_index = 1,
        .dp_hs_phy_irq_index = 4,
        .dm_hs_phy_irq_index = 3,
        .ss_phy_irq_index = 2,
index 5b7e92f..6095f4d 100644 (file)
@@ -293,11 +293,15 @@ static int dwc3_xlnx_probe(struct platform_device *pdev)
                goto err_clk_put;
 
        pm_runtime_set_active(dev);
-       pm_runtime_enable(dev);
+       ret = devm_pm_runtime_enable(dev);
+       if (ret < 0)
+               goto err_pm_set_suspended;
+
        pm_suspend_ignore_children(dev, false);
-       pm_runtime_get_sync(dev);
+       return pm_runtime_resume_and_get(dev);
 
-       return 0;
+err_pm_set_suspended:
+       pm_runtime_set_suspended(dev);
 
 err_clk_put:
        clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
@@ -315,7 +319,6 @@ static void dwc3_xlnx_remove(struct platform_device *pdev)
        clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
        priv_data->num_clocks = 0;
 
-       pm_runtime_disable(dev);
        pm_runtime_put_noidle(dev);
        pm_runtime_set_suspended(dev);
 }
index b942432..6ae8a36 100644 (file)
@@ -238,7 +238,10 @@ void dwc3_ep0_stall_and_restart(struct dwc3 *dwc)
                struct dwc3_request     *req;
 
                req = next_request(&dep->pending_list);
-               dwc3_gadget_giveback(dep, req, -ECONNRESET);
+               if (!dwc->connected)
+                       dwc3_gadget_giveback(dep, req, -ESHUTDOWN);
+               else
+                       dwc3_gadget_giveback(dep, req, -ECONNRESET);
        }
 
        dwc->eps[0]->trb_enqueue = 0;
index 858fe4c..019368f 100644 (file)
@@ -673,6 +673,12 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
                params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(bInterval_m1);
        }
 
+       if (dep->endpoint.fifo_mode) {
+               if (!(dwc->hwparams.hwparams4 & DWC3_EXT_BUFF_CONTROL))
+                       return -EINVAL;
+               params.param1 |= DWC3_DEPCFG_EBC_HWO_NOWB | DWC3_DEPCFG_USE_EBC;
+       }
+
        return dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, &params);
 }
 
@@ -2103,7 +2109,17 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
 
        list_for_each_entry(r, &dep->pending_list, list) {
                if (r == req) {
-                       dwc3_gadget_giveback(dep, req, -ECONNRESET);
+                       /*
+                        * Explicitly check for EP0/1 as dequeue for those
+                        * EPs need to be handled differently.  Control EP
+                        * only deals with one USB req, and giveback will
+                        * occur during dwc3_ep0_stall_and_restart().  EP0
+                        * requests are never added to started_list.
+                        */
+                       if (dep->number > 1)
+                               dwc3_gadget_giveback(dep, req, -ECONNRESET);
+                       else
+                               dwc3_ep0_reset_state(dwc);
                        goto out;
                }
        }
@@ -3973,6 +3989,13 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
        usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED);
 
        dwc3_ep0_reset_state(dwc);
+
+       /*
+        * Request PM idle to address condition where usage count is
+        * already decremented to zero, but waiting for the disconnect
+        * interrupt to set dwc->connected to FALSE.
+        */
+       pm_request_idle(dwc->dev);
 }
 
 static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
index 55a56cf..fd7a4e9 100644 (file)
@@ -26,6 +26,8 @@ struct dwc3;
 #define DWC3_DEPCFG_XFER_NOT_READY_EN  BIT(10)
 #define DWC3_DEPCFG_FIFO_ERROR_EN      BIT(11)
 #define DWC3_DEPCFG_STREAM_EVENT_EN    BIT(13)
+#define DWC3_DEPCFG_EBC_HWO_NOWB       BIT(14)
+#define DWC3_DEPCFG_USE_EBC            BIT(15)
 #define DWC3_DEPCFG_BINTERVAL_M1(n)    (((n) & 0xff) << 16)
 #define DWC3_DEPCFG_STREAM_CAPABLE     BIT(24)
 #define DWC3_DEPCFG_EP_NUMBER(n)       (((n) & 0x1f) << 25)
index 7bf810a..8c5aaf8 100644 (file)
@@ -404,9 +404,9 @@ static void qh_lines(struct fotg210_hcd *fotg210, struct fotg210_qh *qh,
                        else if (td->hw_alt_next != list_end)
                                mark = '/';
                }
-               temp = snprintf(next, size,
-                               "\n\t%p%c%s len=%d %08x urb %p",
-                               td, mark, ({ char *tmp;
+               temp = scnprintf(next, size,
+                                "\n\t%p%c%s len=%d %08x urb %p",
+                                td, mark, ({ char *tmp;
                                switch ((scratch>>8)&0x03) {
                                case 0:
                                        tmp = "out";
@@ -424,15 +424,11 @@ static void qh_lines(struct fotg210_hcd *fotg210, struct fotg210_qh *qh,
                                (scratch >> 16) & 0x7fff,
                                scratch,
                                td->urb);
-               if (size < temp)
-                       temp = size;
                size -= temp;
                next += temp;
        }
 
-       temp = snprintf(next, size, "\n");
-       if (size < temp)
-               temp = size;
+       temp = scnprintf(next, size, "\n");
 
        size -= temp;
        next += temp;
index f7ea840..0bae12e 100644 (file)
@@ -1094,10 +1094,10 @@ static int fotg210_udc_stop(struct usb_gadget *g)
 
 /**
  * fotg210_vbus_session - Called by external transceiver to enable/disable udc
- * @_gadget: usb gadget
+ * @g: usb gadget
  * @is_active: 0 if should disable UDC VBUS, 1 if should enable
  *
- * Returns 0
+ * Returns: %0
  */
 static int fotg210_vbus_session(struct usb_gadget *g, int is_active)
 {
@@ -1122,7 +1122,7 @@ static const struct usb_gadget_ops fotg210_gadget_ops = {
  *
  * Called by the USB Phy when a cable connect or disconnect is sensed.
  *
- * Returns NOTIFY_OK or NOTIFY_DONE
+ * Returns: NOTIFY_OK or NOTIFY_DONE
  */
 static int fotg210_phy_event(struct notifier_block *nb, unsigned long action,
                             void *data)
index 4c639e9..ce3cfa1 100644 (file)
@@ -606,10 +606,11 @@ static struct config_group *function_make(
        char *instance_name;
        int ret;
 
-       ret = snprintf(buf, MAX_NAME_LEN, "%s", name);
-       if (ret >= MAX_NAME_LEN)
+       if (strlen(name) >= MAX_NAME_LEN)
                return ERR_PTR(-ENAMETOOLONG);
 
+       scnprintf(buf, MAX_NAME_LEN, "%s", name);
+
        func_name = buf;
        instance_name = strchr(func_name, '.');
        if (!instance_name) {
@@ -701,10 +702,12 @@ static struct config_group *config_desc_make(
        int ret;
 
        gi = container_of(group, struct gadget_info, configs_group);
-       ret = snprintf(buf, MAX_NAME_LEN, "%s", name);
-       if (ret >= MAX_NAME_LEN)
+
+       if (strlen(name) >= MAX_NAME_LEN)
                return ERR_PTR(-ENAMETOOLONG);
 
+       scnprintf(buf, MAX_NAME_LEN, "%s", name);
+
        num_str = strchr(buf, '.');
        if (!num_str) {
                pr_err("Unable to locate . in name.bConfigurationValue\n");
@@ -812,7 +815,7 @@ static ssize_t gadget_string_s_show(struct config_item *item, char *page)
        struct gadget_string *string = to_gadget_string(item);
        int ret;
 
-       ret = snprintf(page, sizeof(string->string), "%s\n", string->string);
+       ret = sysfs_emit(page, "%s\n", string->string);
        return ret;
 }
 
index fdd0fc7..6bff6cb 100644 (file)
@@ -2931,9 +2931,8 @@ static int __ffs_func_bind_do_os_desc(enum ffs_os_desc_type type,
 
                t = &func->function.os_desc_table[desc->bFirstInterfaceNumber];
                t->if_id = func->interfaces_nums[desc->bFirstInterfaceNumber];
-               memcpy(t->os_desc->ext_compat_id, &desc->CompatibleID,
-                      ARRAY_SIZE(desc->CompatibleID) +
-                      ARRAY_SIZE(desc->SubCompatibleID));
+               memcpy(t->os_desc->ext_compat_id, &desc->IDs,
+                      sizeof_field(struct usb_ext_compat_desc, IDs));
                length = sizeof(*desc);
        }
                break;
index 5335845..20c6fbd 100644 (file)
@@ -1177,11 +1177,11 @@ F_MIDI_OPT(out_ports, true, MAX_PORTS);
 static ssize_t f_midi_opts_id_show(struct config_item *item, char *page)
 {
        struct f_midi_opts *opts = to_f_midi_opts(item);
-       int result;
+       ssize_t result;
 
        mutex_lock(&opts->lock);
        if (opts->id) {
-               result = strlcpy(page, opts->id, PAGE_SIZE);
+               result = strscpy(page, opts->id, PAGE_SIZE);
        } else {
                page[0] = 0;
                result = 0;
index cc0ed29..a1575a0 100644 (file)
@@ -103,6 +103,16 @@ static inline struct f_ncm *func_to_ncm(struct usb_function *f)
 /* Delay for the transmit to wait before sending an unfilled NTB frame. */
 #define TX_TIMEOUT_NSECS       300000
 
+/*
+ * Although max mtu as dictated by u_ether is 15412 bytes, setting
+ * max_segment_sizeto 15426 would not be efficient. If user chooses segment
+ * size to be (>= 8192), then we can't aggregate more than one  buffer in each
+ * NTB (assuming each packet coming from network layer is >= 8192 bytes) as ep
+ * maxpacket limit is 16384. So let max_segment_size be limited to 8000 to allow
+ * at least 2 packets to be aggregated reducing wastage of NTB buffer space
+ */
+#define MAX_DATAGRAM_SIZE      8000
+
 #define FORMATS_SUPPORTED      (USB_CDC_NCM_NTB16_SUPPORTED |  \
                                 USB_CDC_NCM_NTB32_SUPPORTED)
 
@@ -179,7 +189,6 @@ static struct usb_cdc_ether_desc ecm_desc = {
        /* this descriptor actually adds value, surprise! */
        /* .iMACAddress = DYNAMIC */
        .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */
-       .wMaxSegmentSize =      cpu_to_le16(ETH_FRAME_LEN),
        .wNumberMCFilters =     cpu_to_le16(0),
        .bNumberPowerFilters =  0,
 };
@@ -1166,11 +1175,15 @@ static int ncm_unwrap_ntb(struct gether *port,
        struct sk_buff  *skb2;
        int             ret = -EINVAL;
        unsigned        ntb_max = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
-       unsigned        frame_max = le16_to_cpu(ecm_desc.wMaxSegmentSize);
+       unsigned        frame_max;
        const struct ndp_parser_opts *opts = ncm->parser_opts;
        unsigned        crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
        int             dgram_counter;
        int             to_process = skb->len;
+       struct f_ncm_opts *ncm_opts;
+
+       ncm_opts = container_of(port->func.fi, struct f_ncm_opts, func_inst);
+       frame_max = ncm_opts->max_segment_size;
 
 parse_ntb:
        tmp = (__le16 *)ntb_ptr;
@@ -1430,8 +1443,10 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
 
        mutex_lock(&ncm_opts->lock);
        gether_set_gadget(ncm_opts->net, cdev->gadget);
-       if (!ncm_opts->bound)
+       if (!ncm_opts->bound) {
+               ncm_opts->net->mtu = (ncm_opts->max_segment_size - ETH_HLEN);
                status = gether_register_netdev(ncm_opts->net);
+       }
        mutex_unlock(&ncm_opts->lock);
 
        if (status)
@@ -1474,6 +1489,8 @@ static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
        ncm_data_intf.bInterfaceNumber = status;
        ncm_union_desc.bSlaveInterface0 = status;
 
+       ecm_desc.wMaxSegmentSize = ncm_opts->max_segment_size;
+
        status = -ENODEV;
 
        /* allocate instance-specific endpoints */
@@ -1576,11 +1593,56 @@ USB_ETHERNET_CONFIGFS_ITEM_ATTR_QMULT(ncm);
 /* f_ncm_opts_ifname */
 USB_ETHERNET_CONFIGFS_ITEM_ATTR_IFNAME(ncm);
 
+static ssize_t ncm_opts_max_segment_size_show(struct config_item *item,
+                                             char *page)
+{
+       struct f_ncm_opts *opts = to_f_ncm_opts(item);
+       u16 segment_size;
+
+       mutex_lock(&opts->lock);
+       segment_size = opts->max_segment_size;
+       mutex_unlock(&opts->lock);
+
+       return sysfs_emit(page, "%u\n", segment_size);
+}
+
+static ssize_t ncm_opts_max_segment_size_store(struct config_item *item,
+                                              const char *page, size_t len)
+{
+       struct f_ncm_opts *opts = to_f_ncm_opts(item);
+       u16 segment_size;
+       int ret;
+
+       mutex_lock(&opts->lock);
+       if (opts->refcnt) {
+               ret = -EBUSY;
+               goto out;
+       }
+
+       ret = kstrtou16(page, 0, &segment_size);
+       if (ret)
+               goto out;
+
+       if (segment_size > MAX_DATAGRAM_SIZE) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       opts->max_segment_size = segment_size;
+       ret = len;
+out:
+       mutex_unlock(&opts->lock);
+       return ret;
+}
+
+CONFIGFS_ATTR(ncm_opts_, max_segment_size);
+
 static struct configfs_attribute *ncm_attrs[] = {
        &ncm_opts_attr_dev_addr,
        &ncm_opts_attr_host_addr,
        &ncm_opts_attr_qmult,
        &ncm_opts_attr_ifname,
+       &ncm_opts_attr_max_segment_size,
        NULL,
 };
 
@@ -1623,6 +1685,7 @@ static struct usb_function_instance *ncm_alloc_inst(void)
                kfree(opts);
                return ERR_CAST(net);
        }
+       opts->max_segment_size = cpu_to_le16(ETH_FRAME_LEN);
        INIT_LIST_HEAD(&opts->ncm_os_desc.ext_prop);
 
        descs[0] = &opts->ncm_os_desc;
index ff33f31..37befd6 100644 (file)
@@ -1504,8 +1504,8 @@ static ssize_t tcm_usbg_tpg_nexus_show(struct config_item *item, char *page)
                ret = -ENODEV;
                goto out;
        }
-       ret = snprintf(page, PAGE_SIZE, "%s\n",
-                       tv_nexus->tvn_se_sess->se_node_acl->initiatorname);
+       ret = sysfs_emit(page, "%s\n",
+                        tv_nexus->tvn_se_sess->se_node_acl->initiatorname);
 out:
        mutex_unlock(&tpg->tpg_mutex);
        return ret;
index 6f0e1d8..7de74a3 100644 (file)
@@ -292,6 +292,77 @@ static struct usb_descriptor_header *f_audio_desc[] = {
        NULL,
 };
 
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor ss_as_out_ep_desc  = {
+       .bLength =              USB_DT_ENDPOINT_AUDIO_SIZE,
+       .bDescriptorType =      USB_DT_ENDPOINT,
+       .bEndpointAddress =     USB_DIR_OUT,
+       .bmAttributes =         USB_ENDPOINT_SYNC_ADAPTIVE
+                               | USB_ENDPOINT_XFER_ISOC,
+       .wMaxPacketSize =       cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+       .bInterval =            4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_as_out_ep_desc_comp = {
+       .bLength                = sizeof(ss_as_out_ep_desc_comp),
+       .bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+       .bMaxBurst              = 0,
+       .bmAttributes           = 0,
+       /* wBytesPerInterval = DYNAMIC */
+};
+
+/* Standard ISO OUT Endpoint Descriptor */
+static struct usb_endpoint_descriptor ss_as_in_ep_desc  = {
+       .bLength =              USB_DT_ENDPOINT_AUDIO_SIZE,
+       .bDescriptorType =      USB_DT_ENDPOINT,
+       .bEndpointAddress =     USB_DIR_IN,
+       .bmAttributes =         USB_ENDPOINT_SYNC_ASYNC
+                               | USB_ENDPOINT_XFER_ISOC,
+       .wMaxPacketSize =       cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE),
+       .bInterval =            4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_as_in_ep_desc_comp = {
+       .bLength                = sizeof(ss_as_in_ep_desc_comp),
+       .bDescriptorType        = USB_DT_SS_ENDPOINT_COMP,
+       .bMaxBurst              = 0,
+       .bmAttributes           = 0,
+       /* wBytesPerInterval = DYNAMIC */
+};
+
+static struct usb_descriptor_header *f_audio_ss_desc[] = {
+       (struct usb_descriptor_header *)&ac_interface_desc,
+       (struct usb_descriptor_header *)&ac_header_desc,
+
+       (struct usb_descriptor_header *)&usb_out_it_desc,
+       (struct usb_descriptor_header *)&io_out_ot_desc,
+       (struct usb_descriptor_header *)&io_in_it_desc,
+       (struct usb_descriptor_header *)&usb_in_ot_desc,
+
+       (struct usb_descriptor_header *)&as_out_interface_alt_0_desc,
+       (struct usb_descriptor_header *)&as_out_interface_alt_1_desc,
+       (struct usb_descriptor_header *)&as_out_header_desc,
+
+       (struct usb_descriptor_header *)&as_out_type_i_desc,
+
+       //(struct usb_descriptor_header *)&as_out_ep_desc,
+       (struct usb_descriptor_header *)&ss_as_out_ep_desc,
+       (struct usb_descriptor_header *)&ss_as_out_ep_desc_comp,
+       (struct usb_descriptor_header *)&as_iso_out_desc,
+
+       (struct usb_descriptor_header *)&as_in_interface_alt_0_desc,
+       (struct usb_descriptor_header *)&as_in_interface_alt_1_desc,
+       (struct usb_descriptor_header *)&as_in_header_desc,
+
+       (struct usb_descriptor_header *)&as_in_type_i_desc,
+
+       //(struct usb_descriptor_header *)&as_in_ep_desc,
+       (struct usb_descriptor_header *)&ss_as_in_ep_desc,
+       (struct usb_descriptor_header *)&ss_as_in_ep_desc_comp,
+       (struct usb_descriptor_header *)&as_iso_in_desc,
+       NULL,
+};
+
 enum {
        STR_AC_IF,
        STR_USB_OUT_IT,
@@ -1352,6 +1423,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
                ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc);
                if (!ep)
                        goto err_free_fu;
+               ss_as_out_ep_desc.bEndpointAddress = as_out_ep_desc.bEndpointAddress;
                audio->out_ep = ep;
                audio->out_ep->desc = &as_out_ep_desc;
        }
@@ -1360,6 +1432,7 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
                ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc);
                if (!ep)
                        goto err_free_fu;
+               ss_as_in_ep_desc.bEndpointAddress = as_in_ep_desc.bEndpointAddress;
                audio->in_ep = ep;
                audio->in_ep->desc = &as_in_ep_desc;
        }
@@ -1367,8 +1440,8 @@ static int f_audio_bind(struct usb_configuration *c, struct usb_function *f)
        setup_descriptor(audio_opts);
 
        /* copy descriptors, and track endpoint copies */
-       status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL,
-                                       NULL);
+       status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, f_audio_ss_desc,
+                                       f_audio_ss_desc);
        if (status)
                goto err_free_fu;
 
@@ -1561,7 +1634,7 @@ static ssize_t f_uac1_opts_##name##_show(struct config_item *item,        \
        int result;                                                     \
                                                                        \
        mutex_lock(&opts->lock);                                        \
-       result = snprintf(page, sizeof(opts->name), "%s", opts->name);  \
+       result = scnprintf(page, sizeof(opts->name), "%s", opts->name); \
        mutex_unlock(&opts->lock);                                      \
                                                                        \
        return result;                                                  \
@@ -1579,7 +1652,7 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item,       \
                goto end;                                               \
        }                                                               \
                                                                        \
-       ret = snprintf(opts->name, min(sizeof(opts->name), len),        \
+       ret = scnprintf(opts->name, min(sizeof(opts->name), len),       \
                        "%s", page);                                    \
                                                                        \
 end:                                                                   \
@@ -1685,7 +1758,7 @@ static struct usb_function_instance *f_audio_alloc_inst(void)
 
        opts->req_number = UAC1_DEF_REQ_NUM;
 
-       snprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
+       scnprintf(opts->function_name, sizeof(opts->function_name), "AC Interface");
 
        return &opts->func_inst;
 }
index f9a0f07..383f685 100644 (file)
@@ -2045,7 +2045,7 @@ static ssize_t f_uac2_opts_##name##_show(struct config_item *item,        \
        int result;                                                     \
                                                                        \
        mutex_lock(&opts->lock);                                        \
-       result = snprintf(page, sizeof(opts->name), "%s", opts->name);  \
+       result = scnprintf(page, sizeof(opts->name), "%s", opts->name); \
        mutex_unlock(&opts->lock);                                      \
                                                                        \
        return result;                                                  \
@@ -2063,7 +2063,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item,       \
                goto end;                                               \
        }                                                               \
                                                                        \
-       ret = snprintf(opts->name, min(sizeof(opts->name), len),        \
+       ret = scnprintf(opts->name, min(sizeof(opts->name), len),       \
                        "%s", page);                                    \
                                                                        \
 end:                                                                   \
@@ -2187,7 +2187,7 @@ 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");
+       scnprintf(opts->function_name, sizeof(opts->function_name), "Source/Sink");
 
        opts->p_terminal_type = UAC2_DEF_P_TERM_TYPE;
        opts->c_terminal_type = UAC2_DEF_C_TERM_TYPE;
index 786379f..9296668 100644 (file)
@@ -263,10 +263,13 @@ uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
        return 0;
 }
 
-void uvc_function_setup_continue(struct uvc_device *uvc)
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep)
 {
        struct usb_composite_dev *cdev = uvc->func.config->cdev;
 
+       if (disable_ep && uvc->video.ep)
+               usb_ep_disable(uvc->video.ep);
+
        usb_composite_setup_continue(cdev);
 }
 
@@ -337,15 +340,11 @@ uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
                if (uvc->state != UVC_STATE_STREAMING)
                        return 0;
 
-               if (uvc->video.ep)
-                       usb_ep_disable(uvc->video.ep);
-
                memset(&v4l2_event, 0, sizeof(v4l2_event));
                v4l2_event.type = UVC_EVENT_STREAMOFF;
                v4l2_event_queue(&uvc->vdev, &v4l2_event);
 
-               uvc->state = UVC_STATE_CONNECTED;
-               return 0;
+               return USB_GADGET_DELAYED_STATUS;
 
        case 1:
                if (uvc->state != UVC_STATE_CONNECTED)
@@ -722,13 +721,29 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f)
        }
        uvc->enable_interrupt_ep = opts->enable_interrupt_ep;
 
-       ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep);
+       /*
+        * gadget_is_{super|dual}speed() API check UDC controller capitblity. It should pass down
+        * highest speed endpoint descriptor to UDC controller. So UDC controller driver can reserve
+        * enough resource at check_config(), especially mult and maxburst. So UDC driver (such as
+        * cdns3) can know need at least (mult + 1) * (maxburst + 1) * wMaxPacketSize internal
+        * memory for this uvc functions. This is the only straightforward method to resolve the UDC
+        * resource allocation issue in the current gadget framework.
+        */
+       if (gadget_is_superspeed(c->cdev->gadget))
+               ep = usb_ep_autoconfig_ss(cdev->gadget, &uvc_ss_streaming_ep,
+                                         &uvc_ss_streaming_comp);
+       else if (gadget_is_dualspeed(cdev->gadget))
+               ep = usb_ep_autoconfig(cdev->gadget, &uvc_hs_streaming_ep);
+       else
+               ep = usb_ep_autoconfig(cdev->gadget, &uvc_fs_streaming_ep);
+
        if (!ep) {
                uvcg_info(f, "Unable to allocate streaming EP\n");
                goto error;
        }
        uvc->video.ep = ep;
 
+       uvc_fs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
        uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address;
        uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address;
 
@@ -960,7 +975,8 @@ static void uvc_free(struct usb_function *f)
        struct uvc_device *uvc = to_uvc(f);
        struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts,
                                               func_inst);
-       config_item_put(&uvc->header->item);
+       if (!opts->header)
+               config_item_put(&uvc->header->item);
        --opts->refcnt;
        kfree(uvc);
 }
@@ -1052,25 +1068,29 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
        uvc->desc.hs_streaming = opts->hs_streaming;
        uvc->desc.ss_streaming = opts->ss_streaming;
 
-       streaming = config_group_find_item(&opts->func_inst.group, "streaming");
-       if (!streaming)
-               goto err_config;
-
-       header = config_group_find_item(to_config_group(streaming), "header");
-       config_item_put(streaming);
-       if (!header)
-               goto err_config;
-
-       h = config_group_find_item(to_config_group(header), "h");
-       config_item_put(header);
-       if (!h)
-               goto err_config;
-
-       uvc->header = to_uvcg_streaming_header(h);
-       if (!uvc->header->linked) {
-               mutex_unlock(&opts->lock);
-               kfree(uvc);
-               return ERR_PTR(-EBUSY);
+       if (opts->header) {
+               uvc->header = opts->header;
+       } else {
+               streaming = config_group_find_item(&opts->func_inst.group, "streaming");
+               if (!streaming)
+                       goto err_config;
+
+               header = config_group_find_item(to_config_group(streaming), "header");
+               config_item_put(streaming);
+               if (!header)
+                       goto err_config;
+
+               h = config_group_find_item(to_config_group(header), "h");
+               config_item_put(header);
+               if (!h)
+                       goto err_config;
+
+               uvc->header = to_uvcg_streaming_header(h);
+               if (!uvc->header->linked) {
+                       mutex_unlock(&opts->lock);
+                       kfree(uvc);
+                       return ERR_PTR(-EBUSY);
+               }
        }
 
        uvc->desc.extension_units = &opts->extension_units;
index 1db972d..083aef0 100644 (file)
@@ -11,7 +11,7 @@
 
 struct uvc_device;
 
-void uvc_function_setup_continue(struct uvc_device *uvc);
+void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 
 void uvc_function_connect(struct uvc_device *uvc);
 
index 9d1c40c..3c5a6f6 100644 (file)
@@ -1163,6 +1163,8 @@ struct net_device *gether_connect(struct gether *link)
                if (netif_running(dev->net))
                        eth_start(dev, GFP_ATOMIC);
 
+               netif_device_attach(dev->net);
+
        /* on error, disable any endpoints  */
        } else {
                (void) usb_ep_disable(link->out_ep);
index 5408854..49ec095 100644 (file)
@@ -31,6 +31,8 @@ struct f_ncm_opts {
         */
        struct mutex                    lock;
        int                             refcnt;
+
+       u16                             max_segment_size;
 };
 
 #endif /* U_NCM_H */
index 1ce58f6..3ac392c 100644 (file)
@@ -98,6 +98,12 @@ struct f_uvc_opts {
         */
        struct mutex                    lock;
        int                             refcnt;
+
+       /*
+        * Only for legacy gadget. Shall be NULL for configfs-composed gadgets,
+        * which is guaranteed by alloc_inst implementation of f_uvc doing kzalloc.
+        */
+       struct uvcg_streaming_header    *header;
 };
 
 #endif /* U_UVC_H */
index 6751de8..cb35687 100644 (file)
@@ -81,6 +81,7 @@ struct uvc_request {
        struct sg_table sgt;
        u8 header[UVCG_REQUEST_HEADER_LEN];
        struct uvc_buffer *last_buf;
+       struct list_head list;
 };
 
 struct uvc_video {
@@ -101,9 +102,18 @@ struct uvc_video {
        unsigned int uvc_num_requests;
 
        /* Requests */
+       bool is_enabled; /* tracks whether video stream is enabled */
        unsigned int req_size;
-       struct uvc_request *ureq;
+       struct list_head ureqs; /* all uvc_requests allocated by uvc_video */
+
+       /* USB requests that the video pump thread can encode into */
        struct list_head req_free;
+
+       /*
+        * USB requests video pump thread has already encoded into. These are
+        * ready to be queued to the endpoint.
+        */
+       struct list_head req_ready;
        spinlock_t req_lock;
 
        unsigned int req_int_count;
@@ -177,7 +187,7 @@ struct uvc_file_handle {
  * Functions
  */
 
-extern void uvc_function_setup_continue(struct uvc_device *uvc);
+extern void uvc_function_setup_continue(struct uvc_device *uvc, int disable_ep);
 extern void uvc_function_connect(struct uvc_device *uvc);
 extern void uvc_function_disconnect(struct uvc_device *uvc);
 
index 9bf0e98..7e704b2 100644 (file)
@@ -3414,7 +3414,7 @@ static ssize_t f_uvc_opts_string_##cname##_show(struct config_item *item,\
        int result;                                                     \
                                                                        \
        mutex_lock(&opts->lock);                                        \
-       result = snprintf(page, sizeof(opts->aname), "%s", opts->aname);\
+       result = scnprintf(page, sizeof(opts->aname), "%s", opts->aname);\
        mutex_unlock(&opts->lock);                                      \
                                                                        \
        return result;                                                  \
index 3f0a979..c7e5fa4 100644 (file)
@@ -443,7 +443,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
                return -EINVAL;
 
        /* Enable UVC video. */
-       ret = uvcg_video_enable(video, 1);
+       ret = uvcg_video_enable(video);
        if (ret < 0)
                return ret;
 
@@ -451,7 +451,7 @@ uvc_v4l2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
         * Complete the alternate setting selection setup phase now that
         * userspace is ready to provide video frames.
         */
-       uvc_function_setup_continue(uvc);
+       uvc_function_setup_continue(uvc, 0);
        uvc->state = UVC_STATE_STREAMING;
 
        return 0;
@@ -463,11 +463,18 @@ uvc_v4l2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
        struct video_device *vdev = video_devdata(file);
        struct uvc_device *uvc = video_get_drvdata(vdev);
        struct uvc_video *video = &uvc->video;
+       int ret = 0;
 
        if (type != video->queue.queue.type)
                return -EINVAL;
 
-       return uvcg_video_enable(video, 0);
+       ret = uvcg_video_disable(video);
+       if (ret < 0)
+               return ret;
+
+       uvc->state = UVC_STATE_CONNECTED;
+       uvc_function_setup_continue(uvc, 1);
+       return 0;
 }
 
 static int
@@ -500,7 +507,7 @@ uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 static void uvc_v4l2_disable(struct uvc_device *uvc)
 {
        uvc_function_disconnect(uvc);
-       uvcg_video_enable(&uvc->video, 0);
+       uvcg_video_disable(&uvc->video);
        uvcg_free_buffers(&uvc->video.queue);
        uvc->func_connected = false;
        wake_up_interruptible(&uvc->func_connected_queue);
@@ -647,4 +654,3 @@ const struct v4l2_file_operations uvc_v4l2_fops = {
        .get_unmapped_area = uvcg_v4l2_get_unmapped_area,
 #endif
 };
-
index 91af3b1..dd3241f 100644 (file)
@@ -227,6 +227,28 @@ uvc_video_encode_isoc(struct usb_request *req, struct uvc_video *video,
  * Request handling
  */
 
+/*
+ * Callers must take care to hold req_lock when this function may be called
+ * from multiple threads. For example, when frames are streaming to the host.
+ */
+static void
+uvc_video_free_request(struct uvc_request *ureq, struct usb_ep *ep)
+{
+       sg_free_table(&ureq->sgt);
+       if (ureq->req && ep) {
+               usb_ep_free_request(ep, ureq->req);
+               ureq->req = NULL;
+       }
+
+       kfree(ureq->req_buffer);
+       ureq->req_buffer = NULL;
+
+       if (!list_empty(&ureq->list))
+               list_del_init(&ureq->list);
+
+       kfree(ureq);
+}
+
 static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
 {
        int ret;
@@ -247,14 +269,127 @@ static int uvcg_video_ep_queue(struct uvc_video *video, struct usb_request *req)
        return ret;
 }
 
+/* This function must be called with video->req_lock held. */
+static int uvcg_video_usb_req_queue(struct uvc_video *video,
+       struct usb_request *req, bool queue_to_ep)
+{
+       bool is_bulk = video->max_payload_size;
+       struct list_head *list = NULL;
+
+       if (!video->is_enabled)
+               return -ENODEV;
+
+       if (queue_to_ep) {
+               struct uvc_request *ureq = req->context;
+               /*
+                * With USB3 handling more requests at a higher speed, we can't
+                * afford to generate an interrupt for every request. Decide to
+                * interrupt:
+                *
+                * - When no more requests are available in the free queue, as
+                *   this may be our last chance to refill the endpoint's
+                *   request queue.
+                *
+                * - When this is request is the last request for the video
+                *   buffer, as we want to start sending the next video buffer
+                *   ASAP in case it doesn't get started already in the next
+                *   iteration of this loop.
+                *
+                * - Four times over the length of the requests queue (as
+                *   indicated by video->uvc_num_requests), as a trade-off
+                *   between latency and interrupt load.
+                */
+               if (list_empty(&video->req_free) || ureq->last_buf ||
+                       !(video->req_int_count %
+                       DIV_ROUND_UP(video->uvc_num_requests, 4))) {
+                       video->req_int_count = 0;
+                       req->no_interrupt = 0;
+               } else {
+                       req->no_interrupt = 1;
+               }
+               video->req_int_count++;
+               return uvcg_video_ep_queue(video, req);
+       }
+       /*
+        * If we're not queuing to the ep, for isoc we're queuing
+        * to the req_ready list, otherwise req_free.
+        */
+       list = is_bulk ? &video->req_free : &video->req_ready;
+       list_add_tail(&req->list, list);
+       return 0;
+}
+
+/*
+ * Must only be called from uvcg_video_enable - since after that we only want to
+ * queue requests to the endpoint from the uvc_video_complete complete handler.
+ * This function is needed in order to 'kick start' the flow of requests from
+ * gadget driver to the usb controller.
+ */
+static void uvc_video_ep_queue_initial_requests(struct uvc_video *video)
+{
+       struct usb_request *req = NULL;
+       unsigned long flags = 0;
+       unsigned int count = 0;
+       int ret = 0;
+
+       /*
+        * We only queue half of the free list since we still want to have
+        * some free usb_requests in the free list for the video_pump async_wq
+        * thread to encode uvc buffers into. Otherwise we could get into a
+        * situation where the free list does not have any usb requests to
+        * encode into - we always end up queueing 0 length requests to the
+        * end point.
+        */
+       unsigned int half_list_size = video->uvc_num_requests / 2;
+
+       spin_lock_irqsave(&video->req_lock, flags);
+       /*
+        * Take these requests off the free list and queue them all to the
+        * endpoint. Since we queue 0 length requests with the req_lock held,
+        * there isn't any 'data' race involved here with the complete handler.
+        */
+       while (count < half_list_size) {
+               req = list_first_entry(&video->req_free, struct usb_request,
+                                       list);
+               list_del(&req->list);
+               req->length = 0;
+               ret = uvcg_video_ep_queue(video, req);
+               if (ret < 0) {
+                       uvcg_queue_cancel(&video->queue, 0);
+                       break;
+               }
+               count++;
+       }
+       spin_unlock_irqrestore(&video->req_lock, flags);
+}
+
 static void
 uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
 {
        struct uvc_request *ureq = req->context;
        struct uvc_video *video = ureq->video;
        struct uvc_video_queue *queue = &video->queue;
-       struct uvc_device *uvc = video->uvc;
+       struct uvc_buffer *last_buf;
        unsigned long flags;
+       bool is_bulk = video->max_payload_size;
+       int ret = 0;
+
+       spin_lock_irqsave(&video->req_lock, flags);
+       if (!video->is_enabled) {
+               /*
+                * When is_enabled is false, uvcg_video_disable() ensures
+                * that in-flight uvc_buffers are returned, so we can
+                * safely call free_request without worrying about
+                * last_buf.
+                */
+               uvc_video_free_request(ureq, ep);
+               spin_unlock_irqrestore(&video->req_lock, flags);
+               return;
+       }
+
+       last_buf = ureq->last_buf;
+       ureq->last_buf = NULL;
+       spin_unlock_irqrestore(&video->req_lock, flags);
 
        switch (req->status) {
        case 0:
@@ -277,44 +412,85 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
                uvcg_queue_cancel(queue, 0);
        }
 
-       if (ureq->last_buf) {
-               uvcg_complete_buffer(&video->queue, ureq->last_buf);
-               ureq->last_buf = NULL;
+       if (last_buf) {
+               spin_lock_irqsave(&queue->irqlock, flags);
+               uvcg_complete_buffer(queue, last_buf);
+               spin_unlock_irqrestore(&queue->irqlock, flags);
        }
 
        spin_lock_irqsave(&video->req_lock, flags);
-       list_add_tail(&req->list, &video->req_free);
+       /*
+        * Video stream might have been disabled while we were
+        * processing the current usb_request. So make sure
+        * we're still streaming before queueing the usb_request
+        * back to req_free
+        */
+       if (video->is_enabled) {
+               /*
+                * Here we check whether any request is available in the ready
+                * list. If it is, queue it to the ep and add the current
+                * usb_request to the req_free list - for video_pump to fill in.
+                * Otherwise, just use the current usb_request to queue a 0
+                * length request to the ep. Since we always add to the req_free
+                * list if we dequeue from the ready list, there will never
+                * be a situation where the req_free list is completely out of
+                * requests and cannot recover.
+                */
+               struct usb_request *to_queue = req;
+
+               to_queue->length = 0;
+               if (!list_empty(&video->req_ready)) {
+                       to_queue = list_first_entry(&video->req_ready,
+                               struct usb_request, list);
+                       list_del(&to_queue->list);
+                       list_add_tail(&req->list, &video->req_free);
+                       /*
+                        * Queue work to the wq as well since it is possible that a
+                        * buffer may not have been completely encoded with the set of
+                        * in-flight usb requests for whih the complete callbacks are
+                        * firing.
+                        * In that case, if we do not queue work to the worker thread,
+                        * the buffer will never be marked as complete - and therefore
+                        * not be returned to userpsace. As a result,
+                        * dequeue -> queue -> dequeue flow of uvc buffers will not
+                        * happen.
+                        */
+                       queue_work(video->async_wq, &video->pump);
+               }
+               /*
+                * Queue to the endpoint. The actual queueing to ep will
+                * only happen on one thread - the async_wq for bulk endpoints
+                * and this thread for isoc endpoints.
+                */
+               ret = uvcg_video_usb_req_queue(video, to_queue, !is_bulk);
+               if (ret < 0) {
+                       /*
+                        * Endpoint error, but the stream is still enabled.
+                        * Put request back in req_free for it to be cleaned
+                        * up later.
+                        */
+                       list_add_tail(&to_queue->list, &video->req_free);
+               }
+       } else {
+               uvc_video_free_request(ureq, ep);
+               ret = 0;
+       }
        spin_unlock_irqrestore(&video->req_lock, flags);
-
-       if (uvc->state == UVC_STATE_STREAMING)
-               queue_work(video->async_wq, &video->pump);
+       if (ret < 0)
+               uvcg_queue_cancel(queue, 0);
 }
 
 static int
 uvc_video_free_requests(struct uvc_video *video)
 {
-       unsigned int i;
-
-       if (video->ureq) {
-               for (i = 0; i < video->uvc_num_requests; ++i) {
-                       sg_free_table(&video->ureq[i].sgt);
+       struct uvc_request *ureq, *temp;
 
-                       if (video->ureq[i].req) {
-                               usb_ep_free_request(video->ep, video->ureq[i].req);
-                               video->ureq[i].req = NULL;
-                       }
-
-                       if (video->ureq[i].req_buffer) {
-                               kfree(video->ureq[i].req_buffer);
-                               video->ureq[i].req_buffer = NULL;
-                       }
-               }
-
-               kfree(video->ureq);
-               video->ureq = NULL;
-       }
+       list_for_each_entry_safe(ureq, temp, &video->ureqs, list)
+               uvc_video_free_request(ureq, video->ep);
 
+       INIT_LIST_HEAD(&video->ureqs);
        INIT_LIST_HEAD(&video->req_free);
+       INIT_LIST_HEAD(&video->req_ready);
        video->req_size = 0;
        return 0;
 }
@@ -322,6 +498,7 @@ uvc_video_free_requests(struct uvc_video *video)
 static int
 uvc_video_alloc_requests(struct uvc_video *video)
 {
+       struct uvc_request *ureq;
        unsigned int req_size;
        unsigned int i;
        int ret = -ENOMEM;
@@ -332,29 +509,33 @@ uvc_video_alloc_requests(struct uvc_video *video)
                 * max_t(unsigned int, video->ep->maxburst, 1)
                 * (video->ep->mult);
 
-       video->ureq = kcalloc(video->uvc_num_requests, sizeof(struct uvc_request), GFP_KERNEL);
-       if (video->ureq == NULL)
-               return -ENOMEM;
+       for (i = 0; i < video->uvc_num_requests; i++) {
+               ureq = kzalloc(sizeof(struct uvc_request), GFP_KERNEL);
+               if (ureq == NULL)
+                       goto error;
+
+               INIT_LIST_HEAD(&ureq->list);
 
-       for (i = 0; i < video->uvc_num_requests; ++i) {
-               video->ureq[i].req_buffer = kmalloc(req_size, GFP_KERNEL);
-               if (video->ureq[i].req_buffer == NULL)
+               list_add_tail(&ureq->list, &video->ureqs);
+
+               ureq->req_buffer = kmalloc(req_size, GFP_KERNEL);
+               if (ureq->req_buffer == NULL)
                        goto error;
 
-               video->ureq[i].req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
-               if (video->ureq[i].req == NULL)
+               ureq->req = usb_ep_alloc_request(video->ep, GFP_KERNEL);
+               if (ureq->req == NULL)
                        goto error;
 
-               video->ureq[i].req->buf = video->ureq[i].req_buffer;
-               video->ureq[i].req->length = 0;
-               video->ureq[i].req->complete = uvc_video_complete;
-               video->ureq[i].req->context = &video->ureq[i];
-               video->ureq[i].video = video;
-               video->ureq[i].last_buf = NULL;
+               ureq->req->buf = ureq->req_buffer;
+               ureq->req->length = 0;
+               ureq->req->complete = uvc_video_complete;
+               ureq->req->context = ureq;
+               ureq->video = video;
+               ureq->last_buf = NULL;
 
-               list_add_tail(&video->ureq[i].req->list, &video->req_free);
+               list_add_tail(&ureq->req->list, &video->req_free);
                /* req_size/PAGE_SIZE + 1 for overruns and + 1 for header */
-               sg_alloc_table(&video->ureq[i].sgt,
+               sg_alloc_table(&ureq->sgt,
                               DIV_ROUND_UP(req_size - UVCG_REQUEST_HEADER_LEN,
                                            PAGE_SIZE) + 2, GFP_KERNEL);
        }
@@ -387,16 +568,18 @@ static void uvcg_video_pump(struct work_struct *work)
        struct usb_request *req = NULL;
        struct uvc_buffer *buf;
        unsigned long flags;
-       bool buf_done;
-       int ret;
+       int ret = 0;
+
+       while (true) {
+               if (!video->ep->enabled)
+                       return;
 
-       while (video->ep->enabled) {
                /*
-                * Retrieve the first available USB request, protected by the
-                * request lock.
+                * Check is_enabled and retrieve the first available USB
+                * request, protected by the request lock.
                 */
                spin_lock_irqsave(&video->req_lock, flags);
-               if (list_empty(&video->req_free)) {
+               if (!video->is_enabled || list_empty(&video->req_free)) {
                        spin_unlock_irqrestore(&video->req_lock, flags);
                        return;
                }
@@ -414,15 +597,6 @@ static void uvcg_video_pump(struct work_struct *work)
 
                if (buf != NULL) {
                        video->encode(req, video, buf);
-                       buf_done = buf->state == UVC_BUF_STATE_DONE;
-               } else if (!(queue->flags & UVC_QUEUE_DISCONNECTED) && !is_bulk) {
-                       /*
-                        * No video buffer available; the queue is still connected and
-                        * we're transferring over ISOC. Queue a 0 length request to
-                        * prevent missed ISOC transfers.
-                        */
-                       req->length = 0;
-                       buf_done = false;
                } else {
                        /*
                         * Either the queue has been disconnected or no video buffer
@@ -433,83 +607,139 @@ static void uvcg_video_pump(struct work_struct *work)
                        break;
                }
 
-               /*
-                * With USB3 handling more requests at a higher speed, we can't
-                * afford to generate an interrupt for every request. Decide to
-                * interrupt:
-                *
-                * - When no more requests are available in the free queue, as
-                *   this may be our last chance to refill the endpoint's
-                *   request queue.
-                *
-                * - When this is request is the last request for the video
-                *   buffer, as we want to start sending the next video buffer
-                *   ASAP in case it doesn't get started already in the next
-                *   iteration of this loop.
-                *
-                * - Four times over the length of the requests queue (as
-                *   indicated by video->uvc_num_requests), as a trade-off
-                *   between latency and interrupt load.
-                */
-               if (list_empty(&video->req_free) || buf_done ||
-                   !(video->req_int_count %
-                      DIV_ROUND_UP(video->uvc_num_requests, 4))) {
-                       video->req_int_count = 0;
-                       req->no_interrupt = 0;
-               } else {
-                       req->no_interrupt = 1;
-               }
-
-               /* Queue the USB request */
-               ret = uvcg_video_ep_queue(video, req);
                spin_unlock_irqrestore(&queue->irqlock, flags);
 
+               spin_lock_irqsave(&video->req_lock, flags);
+               /* For bulk end points we queue from the worker thread
+                * since we would preferably not want to wait on requests
+                * to be ready, in the uvcg_video_complete() handler.
+                * For isoc endpoints we add the request to the ready list
+                * and only queue it to the endpoint from the complete handler.
+                */
+               ret = uvcg_video_usb_req_queue(video, req, is_bulk);
+               spin_unlock_irqrestore(&video->req_lock, flags);
+
                if (ret < 0) {
                        uvcg_queue_cancel(queue, 0);
                        break;
                }
 
-               /* Endpoint now owns the request */
+               /* The request is owned by  the endpoint / ready list. */
                req = NULL;
-               video->req_int_count++;
        }
 
        if (!req)
                return;
 
        spin_lock_irqsave(&video->req_lock, flags);
-       list_add_tail(&req->list, &video->req_free);
+       if (video->is_enabled)
+               list_add_tail(&req->list, &video->req_free);
+       else
+               uvc_video_free_request(req->context, video->ep);
        spin_unlock_irqrestore(&video->req_lock, flags);
-       return;
 }
 
 /*
- * Enable or disable the video stream.
+ * Disable the video stream
  */
-int uvcg_video_enable(struct uvc_video *video, int enable)
+int
+uvcg_video_disable(struct uvc_video *video)
 {
-       unsigned int i;
-       int ret;
+       unsigned long flags;
+       struct list_head inflight_bufs;
+       struct usb_request *req, *temp;
+       struct uvc_buffer *buf, *btemp;
+       struct uvc_request *ureq, *utemp;
 
        if (video->ep == NULL) {
                uvcg_info(&video->uvc->func,
-                         "Video enable failed, device is uninitialized.\n");
+                         "Video disable failed, device is uninitialized.\n");
                return -ENODEV;
        }
 
-       if (!enable) {
-               cancel_work_sync(&video->pump);
-               uvcg_queue_cancel(&video->queue, 0);
+       INIT_LIST_HEAD(&inflight_bufs);
+       spin_lock_irqsave(&video->req_lock, flags);
+       video->is_enabled = false;
+
+       /*
+        * Remove any in-flight buffers from the uvc_requests
+        * because we want to return them before cancelling the
+        * queue. This ensures that we aren't stuck waiting for
+        * all complete callbacks to come through before disabling
+        * vb2 queue.
+        */
+       list_for_each_entry(ureq, &video->ureqs, list) {
+               if (ureq->last_buf) {
+                       list_add_tail(&ureq->last_buf->queue, &inflight_bufs);
+                       ureq->last_buf = NULL;
+               }
+       }
+       spin_unlock_irqrestore(&video->req_lock, flags);
 
-               for (i = 0; i < video->uvc_num_requests; ++i)
-                       if (video->ureq && video->ureq[i].req)
-                               usb_ep_dequeue(video->ep, video->ureq[i].req);
+       cancel_work_sync(&video->pump);
+       uvcg_queue_cancel(&video->queue, 0);
+
+       spin_lock_irqsave(&video->req_lock, flags);
+       /*
+        * Remove all uvc_requests from ureqs with list_del_init
+        * This lets uvc_video_free_request correctly identify
+        * if the uvc_request is attached to a list or not when freeing
+        * memory.
+        */
+       list_for_each_entry_safe(ureq, utemp, &video->ureqs, list)
+               list_del_init(&ureq->list);
+
+       list_for_each_entry_safe(req, temp, &video->req_free, list) {
+               list_del(&req->list);
+               uvc_video_free_request(req->context, video->ep);
+       }
 
-               uvc_video_free_requests(video);
-               uvcg_queue_enable(&video->queue, 0);
-               return 0;
+       list_for_each_entry_safe(req, temp, &video->req_ready, list) {
+               list_del(&req->list);
+               uvc_video_free_request(req->context, video->ep);
        }
 
+       INIT_LIST_HEAD(&video->ureqs);
+       INIT_LIST_HEAD(&video->req_free);
+       INIT_LIST_HEAD(&video->req_ready);
+       video->req_size = 0;
+       spin_unlock_irqrestore(&video->req_lock, flags);
+
+       /*
+        * Return all the video buffers before disabling the queue.
+        */
+       spin_lock_irqsave(&video->queue.irqlock, flags);
+       list_for_each_entry_safe(buf, btemp, &inflight_bufs, queue) {
+               list_del(&buf->queue);
+               uvcg_complete_buffer(&video->queue, buf);
+       }
+       spin_unlock_irqrestore(&video->queue.irqlock, flags);
+
+       uvcg_queue_enable(&video->queue, 0);
+       return 0;
+}
+
+/*
+ * Enable the video stream.
+ */
+int uvcg_video_enable(struct uvc_video *video)
+{
+       int ret;
+
+       if (video->ep == NULL) {
+               uvcg_info(&video->uvc->func,
+                         "Video enable failed, device is uninitialized.\n");
+               return -ENODEV;
+       }
+
+       /*
+        * Safe to access request related fields without req_lock because
+        * this is the only thread currently active, and no other
+        * request handling thread will become active until this function
+        * returns.
+        */
+       video->is_enabled = true;
+
        if ((ret = uvcg_queue_enable(&video->queue, 1)) < 0)
                return ret;
 
@@ -525,7 +755,7 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
 
        video->req_int_count = 0;
 
-       queue_work(video->async_wq, &video->pump);
+       uvc_video_ep_queue_initial_requests(video);
 
        return ret;
 }
@@ -535,7 +765,10 @@ int uvcg_video_enable(struct uvc_video *video, int enable)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
+       video->is_enabled = false;
+       INIT_LIST_HEAD(&video->ureqs);
        INIT_LIST_HEAD(&video->req_free);
+       INIT_LIST_HEAD(&video->req_ready);
        spin_lock_init(&video->req_lock);
        INIT_WORK(&video->pump, uvcg_video_pump);
 
index 03adeef..8ef6259 100644 (file)
@@ -14,7 +14,8 @@
 
 struct uvc_video;
 
-int uvcg_video_enable(struct uvc_video *video, int enable);
+int uvcg_video_enable(struct uvc_video *video);
+int uvcg_video_disable(struct uvc_video *video);
 
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc);
 
index c06dd1a..c395438 100644 (file)
@@ -12,6 +12,7 @@
 #include <linux/usb/video.h>
 
 #include "u_uvc.h"
+#include "uvc_configfs.h"
 
 USB_GADGET_COMPOSITE_OPTIONS();
 
@@ -84,8 +85,6 @@ static struct usb_device_descriptor webcam_device_descriptor = {
        .bNumConfigurations     = 0, /* dynamic */
 };
 
-DECLARE_UVC_HEADER_DESCRIPTOR(1);
-
 static const struct UVC_HEADER_DESCRIPTOR(1) uvc_control_header = {
        .bLength                = UVC_DT_HEADER_SIZE(1),
        .bDescriptorType        = USB_DT_CS_INTERFACE,
@@ -158,43 +157,112 @@ static const struct UVC_INPUT_HEADER_DESCRIPTOR(1, 2) uvc_input_header = {
        .bmaControls[1][0]      = 4,
 };
 
-static const struct uvc_format_uncompressed uvc_format_yuv = {
-       .bLength                = UVC_DT_FORMAT_UNCOMPRESSED_SIZE,
-       .bDescriptorType        = USB_DT_CS_INTERFACE,
-       .bDescriptorSubType     = UVC_VS_FORMAT_UNCOMPRESSED,
-       .bFormatIndex           = 1,
-       .bNumFrameDescriptors   = 2,
-       .guidFormat             =
-               { 'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00,
-                0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71},
-       .bBitsPerPixel          = 16,
-       .bDefaultFrameIndex     = 1,
-       .bAspectRatioX          = 0,
-       .bAspectRatioY          = 0,
-       .bmInterlaceFlags       = 0,
-       .bCopyProtect           = 0,
+static const struct uvcg_color_matching uvcg_color_matching = {
+       .desc = {
+               .bLength                = UVC_DT_COLOR_MATCHING_SIZE,
+               .bDescriptorType        = USB_DT_CS_INTERFACE,
+               .bDescriptorSubType     = UVC_VS_COLORFORMAT,
+               .bColorPrimaries        = 1,
+               .bTransferCharacteristics       = 1,
+               .bMatrixCoefficients    = 4,
+       },
+};
+
+static struct uvcg_uncompressed uvcg_format_yuv = {
+       .fmt = {
+               .type                   = UVCG_UNCOMPRESSED,
+               /* add to .frames and fill .num_frames at runtime */
+               .color_matching         = (struct uvcg_color_matching *)&uvcg_color_matching,
+       },
+       .desc = {
+               .bLength                = UVC_DT_FORMAT_UNCOMPRESSED_SIZE,
+               .bDescriptorType        = USB_DT_CS_INTERFACE,
+               .bDescriptorSubType     = UVC_VS_FORMAT_UNCOMPRESSED,
+               .bFormatIndex           = 1,
+               .bNumFrameDescriptors   = 2,
+               .guidFormat             = {
+                       'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00,
+                        0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
+               },
+               .bBitsPerPixel          = 16,
+               .bDefaultFrameIndex     = 1,
+               .bAspectRatioX          = 0,
+               .bAspectRatioY          = 0,
+               .bmInterlaceFlags       = 0,
+               .bCopyProtect           = 0,
+       },
+};
+
+static struct uvcg_format_ptr uvcg_format_ptr_yuv = {
+       .fmt = &uvcg_format_yuv.fmt,
 };
 
 DECLARE_UVC_FRAME_UNCOMPRESSED(1);
 DECLARE_UVC_FRAME_UNCOMPRESSED(3);
 
+#define UVCG_WIDTH_360P                        640
+#define UVCG_HEIGHT_360P               360
+#define UVCG_MIN_BITRATE_360P          18432000
+#define UVCG_MAX_BITRATE_360P          55296000
+#define UVCG_MAX_VIDEO_FB_SZ_360P      460800
+#define UVCG_FRM_INTERV_0_360P         666666
+#define UVCG_FRM_INTERV_1_360P         1000000
+#define UVCG_FRM_INTERV_2_360P         5000000
+#define UVCG_DEFAULT_FRM_INTERV_360P   UVCG_FRM_INTERV_0_360P
+
 static const struct UVC_FRAME_UNCOMPRESSED(3) uvc_frame_yuv_360p = {
        .bLength                = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3),
        .bDescriptorType        = USB_DT_CS_INTERFACE,
        .bDescriptorSubType     = UVC_VS_FRAME_UNCOMPRESSED,
        .bFrameIndex            = 1,
        .bmCapabilities         = 0,
-       .wWidth                 = cpu_to_le16(640),
-       .wHeight                = cpu_to_le16(360),
-       .dwMinBitRate           = cpu_to_le32(18432000),
-       .dwMaxBitRate           = cpu_to_le32(55296000),
-       .dwMaxVideoFrameBufferSize      = cpu_to_le32(460800),
-       .dwDefaultFrameInterval = cpu_to_le32(666666),
+       .wWidth                 = cpu_to_le16(UVCG_WIDTH_360P),
+       .wHeight                = cpu_to_le16(UVCG_HEIGHT_360P),
+       .dwMinBitRate           = cpu_to_le32(UVCG_MIN_BITRATE_360P),
+       .dwMaxBitRate           = cpu_to_le32(UVCG_MAX_BITRATE_360P),
+       .dwMaxVideoFrameBufferSize      = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_360P),
+       .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_360P),
        .bFrameIntervalType     = 3,
-       .dwFrameInterval[0]     = cpu_to_le32(666666),
-       .dwFrameInterval[1]     = cpu_to_le32(1000000),
-       .dwFrameInterval[2]     = cpu_to_le32(5000000),
+       .dwFrameInterval[0]     = cpu_to_le32(UVCG_FRM_INTERV_0_360P),
+       .dwFrameInterval[1]     = cpu_to_le32(UVCG_FRM_INTERV_1_360P),
+       .dwFrameInterval[2]     = cpu_to_le32(UVCG_FRM_INTERV_2_360P),
+};
+
+static u32 uvcg_frame_yuv_360p_dw_frame_interval[] = {
+       [0] = UVCG_FRM_INTERV_0_360P,
+       [1] = UVCG_FRM_INTERV_1_360P,
+       [2] = UVCG_FRM_INTERV_2_360P,
+};
+
+static const struct uvcg_frame uvcg_frame_yuv_360p = {
+       .fmt_type               = UVCG_UNCOMPRESSED,
+       .frame = {
+               .b_length                       = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3),
+               .b_descriptor_type              = USB_DT_CS_INTERFACE,
+               .b_descriptor_subtype           = UVC_VS_FRAME_UNCOMPRESSED,
+               .b_frame_index                  = 1,
+               .bm_capabilities                = 0,
+               .w_width                        = UVCG_WIDTH_360P,
+               .w_height                       = UVCG_HEIGHT_360P,
+               .dw_min_bit_rate                = UVCG_MIN_BITRATE_360P,
+               .dw_max_bit_rate                = UVCG_MAX_BITRATE_360P,
+               .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_360P,
+               .dw_default_frame_interval      = UVCG_DEFAULT_FRM_INTERV_360P,
+               .b_frame_interval_type          = 3,
+       },
+       .dw_frame_interval      = uvcg_frame_yuv_360p_dw_frame_interval,
+};
+
+static struct uvcg_frame_ptr uvcg_frame_ptr_yuv_360p = {
+       .frm = (struct uvcg_frame *)&uvcg_frame_yuv_360p,
 };
+#define UVCG_WIDTH_720P                        1280
+#define UVCG_HEIGHT_720P               720
+#define UVCG_MIN_BITRATE_720P          29491200
+#define UVCG_MAX_BITRATE_720P          29491200
+#define UVCG_MAX_VIDEO_FB_SZ_720P      1843200
+#define UVCG_FRM_INTERV_0_720P         5000000
+#define UVCG_DEFAULT_FRM_INTERV_720P   UVCG_FRM_INTERV_0_720P
 
 static const struct UVC_FRAME_UNCOMPRESSED(1) uvc_frame_yuv_720p = {
        .bLength                = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1),
@@ -202,28 +270,66 @@ static const struct UVC_FRAME_UNCOMPRESSED(1) uvc_frame_yuv_720p = {
        .bDescriptorSubType     = UVC_VS_FRAME_UNCOMPRESSED,
        .bFrameIndex            = 2,
        .bmCapabilities         = 0,
-       .wWidth                 = cpu_to_le16(1280),
-       .wHeight                = cpu_to_le16(720),
-       .dwMinBitRate           = cpu_to_le32(29491200),
-       .dwMaxBitRate           = cpu_to_le32(29491200),
-       .dwMaxVideoFrameBufferSize      = cpu_to_le32(1843200),
-       .dwDefaultFrameInterval = cpu_to_le32(5000000),
+       .wWidth                 = cpu_to_le16(UVCG_WIDTH_720P),
+       .wHeight                = cpu_to_le16(UVCG_HEIGHT_720P),
+       .dwMinBitRate           = cpu_to_le32(UVCG_MIN_BITRATE_720P),
+       .dwMaxBitRate           = cpu_to_le32(UVCG_MAX_BITRATE_720P),
+       .dwMaxVideoFrameBufferSize      = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_720P),
+       .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_720P),
        .bFrameIntervalType     = 1,
-       .dwFrameInterval[0]     = cpu_to_le32(5000000),
+       .dwFrameInterval[0]     = cpu_to_le32(UVCG_FRM_INTERV_0_720P),
 };
 
-static const struct uvc_format_mjpeg uvc_format_mjpg = {
-       .bLength                = UVC_DT_FORMAT_MJPEG_SIZE,
-       .bDescriptorType        = USB_DT_CS_INTERFACE,
-       .bDescriptorSubType     = UVC_VS_FORMAT_MJPEG,
-       .bFormatIndex           = 2,
-       .bNumFrameDescriptors   = 2,
-       .bmFlags                = 0,
-       .bDefaultFrameIndex     = 1,
-       .bAspectRatioX          = 0,
-       .bAspectRatioY          = 0,
-       .bmInterlaceFlags       = 0,
-       .bCopyProtect           = 0,
+static u32 uvcg_frame_yuv_720p_dw_frame_interval[] = {
+       [0] = UVCG_FRM_INTERV_0_720P,
+};
+
+static const struct uvcg_frame uvcg_frame_yuv_720p = {
+       .fmt_type               = UVCG_UNCOMPRESSED,
+       .frame = {
+               .b_length                       = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1),
+               .b_descriptor_type              = USB_DT_CS_INTERFACE,
+               .b_descriptor_subtype           = UVC_VS_FRAME_UNCOMPRESSED,
+               .b_frame_index                  = 2,
+               .bm_capabilities                = 0,
+               .w_width                        = UVCG_WIDTH_720P,
+               .w_height                       = UVCG_HEIGHT_720P,
+               .dw_min_bit_rate                = UVCG_MIN_BITRATE_720P,
+               .dw_max_bit_rate                = UVCG_MAX_BITRATE_720P,
+               .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_720P,
+               .dw_default_frame_interval      = UVCG_DEFAULT_FRM_INTERV_720P,
+               .b_frame_interval_type          = 1,
+       },
+       .dw_frame_interval      = uvcg_frame_yuv_720p_dw_frame_interval,
+};
+
+static struct uvcg_frame_ptr uvcg_frame_ptr_yuv_720p = {
+       .frm = (struct uvcg_frame *)&uvcg_frame_yuv_720p,
+};
+
+static struct uvcg_mjpeg uvcg_format_mjpeg = {
+       .fmt = {
+               .type                   = UVCG_MJPEG,
+               /* add to .frames and fill .num_frames at runtime */
+               .color_matching         = (struct uvcg_color_matching *)&uvcg_color_matching,
+       },
+       .desc = {
+               .bLength                = UVC_DT_FORMAT_MJPEG_SIZE,
+               .bDescriptorType        = USB_DT_CS_INTERFACE,
+               .bDescriptorSubType     = UVC_VS_FORMAT_MJPEG,
+               .bFormatIndex           = 2,
+               .bNumFrameDescriptors   = 2,
+               .bmFlags                = 0,
+               .bDefaultFrameIndex     = 1,
+               .bAspectRatioX          = 0,
+               .bAspectRatioY          = 0,
+               .bmInterlaceFlags       = 0,
+               .bCopyProtect           = 0,
+       },
+};
+
+static struct uvcg_format_ptr uvcg_format_ptr_mjpeg = {
+       .fmt = &uvcg_format_mjpeg.fmt,
 };
 
 DECLARE_UVC_FRAME_MJPEG(1);
@@ -235,16 +341,45 @@ static const struct UVC_FRAME_MJPEG(3) uvc_frame_mjpg_360p = {
        .bDescriptorSubType     = UVC_VS_FRAME_MJPEG,
        .bFrameIndex            = 1,
        .bmCapabilities         = 0,
-       .wWidth                 = cpu_to_le16(640),
-       .wHeight                = cpu_to_le16(360),
-       .dwMinBitRate           = cpu_to_le32(18432000),
-       .dwMaxBitRate           = cpu_to_le32(55296000),
-       .dwMaxVideoFrameBufferSize      = cpu_to_le32(460800),
-       .dwDefaultFrameInterval = cpu_to_le32(666666),
+       .wWidth                 = cpu_to_le16(UVCG_WIDTH_360P),
+       .wHeight                = cpu_to_le16(UVCG_HEIGHT_360P),
+       .dwMinBitRate           = cpu_to_le32(UVCG_MIN_BITRATE_360P),
+       .dwMaxBitRate           = cpu_to_le32(UVCG_MAX_BITRATE_360P),
+       .dwMaxVideoFrameBufferSize      = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_360P),
+       .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_360P),
        .bFrameIntervalType     = 3,
-       .dwFrameInterval[0]     = cpu_to_le32(666666),
-       .dwFrameInterval[1]     = cpu_to_le32(1000000),
-       .dwFrameInterval[2]     = cpu_to_le32(5000000),
+       .dwFrameInterval[0]     = cpu_to_le32(UVCG_FRM_INTERV_0_360P),
+       .dwFrameInterval[1]     = cpu_to_le32(UVCG_FRM_INTERV_1_360P),
+       .dwFrameInterval[2]     = cpu_to_le32(UVCG_FRM_INTERV_2_360P),
+};
+
+static u32 uvcg_frame_mjpeg_360p_dw_frame_interval[] = {
+       [0] = UVCG_FRM_INTERV_0_360P,
+       [1] = UVCG_FRM_INTERV_1_360P,
+       [2] = UVCG_FRM_INTERV_2_360P,
+};
+
+static const struct uvcg_frame uvcg_frame_mjpeg_360p = {
+       .fmt_type               = UVCG_MJPEG,
+       .frame = {
+               .b_length                       = UVC_DT_FRAME_MJPEG_SIZE(3),
+               .b_descriptor_type              = USB_DT_CS_INTERFACE,
+               .b_descriptor_subtype           = UVC_VS_FRAME_MJPEG,
+               .b_frame_index                  = 1,
+               .bm_capabilities                = 0,
+               .w_width                        = UVCG_WIDTH_360P,
+               .w_height                       = UVCG_HEIGHT_360P,
+               .dw_min_bit_rate                = UVCG_MIN_BITRATE_360P,
+               .dw_max_bit_rate                = UVCG_MAX_BITRATE_360P,
+               .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_360P,
+               .dw_default_frame_interval      = UVCG_DEFAULT_FRM_INTERV_360P,
+               .b_frame_interval_type          = 3,
+       },
+       .dw_frame_interval      = uvcg_frame_mjpeg_360p_dw_frame_interval,
+};
+
+static struct uvcg_frame_ptr uvcg_frame_ptr_mjpeg_360p = {
+       .frm = (struct uvcg_frame *)&uvcg_frame_mjpeg_360p,
 };
 
 static const struct UVC_FRAME_MJPEG(1) uvc_frame_mjpg_720p = {
@@ -253,23 +388,44 @@ static const struct UVC_FRAME_MJPEG(1) uvc_frame_mjpg_720p = {
        .bDescriptorSubType     = UVC_VS_FRAME_MJPEG,
        .bFrameIndex            = 2,
        .bmCapabilities         = 0,
-       .wWidth                 = cpu_to_le16(1280),
-       .wHeight                = cpu_to_le16(720),
-       .dwMinBitRate           = cpu_to_le32(29491200),
-       .dwMaxBitRate           = cpu_to_le32(29491200),
-       .dwMaxVideoFrameBufferSize      = cpu_to_le32(1843200),
-       .dwDefaultFrameInterval = cpu_to_le32(5000000),
+       .wWidth                 = cpu_to_le16(UVCG_WIDTH_720P),
+       .wHeight                = cpu_to_le16(UVCG_HEIGHT_720P),
+       .dwMinBitRate           = cpu_to_le32(UVCG_MIN_BITRATE_720P),
+       .dwMaxBitRate           = cpu_to_le32(UVCG_MAX_BITRATE_720P),
+       .dwMaxVideoFrameBufferSize      = cpu_to_le32(UVCG_MAX_VIDEO_FB_SZ_720P),
+       .dwDefaultFrameInterval = cpu_to_le32(UVCG_DEFAULT_FRM_INTERV_720P),
        .bFrameIntervalType     = 1,
-       .dwFrameInterval[0]     = cpu_to_le32(5000000),
+       .dwFrameInterval[0]     = cpu_to_le32(UVCG_FRM_INTERV_0_720P),
 };
 
-static const struct uvc_color_matching_descriptor uvc_color_matching = {
-       .bLength                = UVC_DT_COLOR_MATCHING_SIZE,
-       .bDescriptorType        = USB_DT_CS_INTERFACE,
-       .bDescriptorSubType     = UVC_VS_COLORFORMAT,
-       .bColorPrimaries        = 1,
-       .bTransferCharacteristics       = 1,
-       .bMatrixCoefficients    = 4,
+static u32 uvcg_frame_mjpeg_720p_dw_frame_interval[] = {
+       [0] = UVCG_FRM_INTERV_0_720P,
+};
+
+static const struct uvcg_frame uvcg_frame_mjpeg_720p = {
+       .fmt_type               = UVCG_MJPEG,
+       .frame = {
+               .b_length                       = UVC_DT_FRAME_MJPEG_SIZE(1),
+               .b_descriptor_type              = USB_DT_CS_INTERFACE,
+               .b_descriptor_subtype           = UVC_VS_FRAME_MJPEG,
+               .b_frame_index                  = 2,
+               .bm_capabilities                = 0,
+               .w_width                        = UVCG_WIDTH_720P,
+               .w_height                       = UVCG_HEIGHT_720P,
+               .dw_min_bit_rate                = UVCG_MIN_BITRATE_720P,
+               .dw_max_bit_rate                = UVCG_MAX_BITRATE_720P,
+               .dw_max_video_frame_buffer_size = UVCG_MAX_VIDEO_FB_SZ_720P,
+               .dw_default_frame_interval      = UVCG_DEFAULT_FRM_INTERV_720P,
+               .b_frame_interval_type          = 1,
+       },
+       .dw_frame_interval      = uvcg_frame_mjpeg_720p_dw_frame_interval,
+};
+
+static struct uvcg_frame_ptr uvcg_frame_ptr_mjpeg_720p = {
+       .frm = (struct uvcg_frame *)&uvcg_frame_mjpeg_720p,
+};
+
+static struct uvcg_streaming_header uvcg_streaming_header = {
 };
 
 static const struct uvc_descriptor_header * const uvc_fs_control_cls[] = {
@@ -290,40 +446,40 @@ static const struct uvc_descriptor_header * const uvc_ss_control_cls[] = {
 
 static const struct uvc_descriptor_header * const uvc_fs_streaming_cls[] = {
        (const struct uvc_descriptor_header *) &uvc_input_header,
-       (const struct uvc_descriptor_header *) &uvc_format_yuv,
+       (const struct uvc_descriptor_header *) &uvcg_format_yuv.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
-       (const struct uvc_descriptor_header *) &uvc_format_mjpg,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
+       (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
        NULL,
 };
 
 static const struct uvc_descriptor_header * const uvc_hs_streaming_cls[] = {
        (const struct uvc_descriptor_header *) &uvc_input_header,
-       (const struct uvc_descriptor_header *) &uvc_format_yuv,
+       (const struct uvc_descriptor_header *) &uvcg_format_yuv.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
-       (const struct uvc_descriptor_header *) &uvc_format_mjpg,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
+       (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
        NULL,
 };
 
 static const struct uvc_descriptor_header * const uvc_ss_streaming_cls[] = {
        (const struct uvc_descriptor_header *) &uvc_input_header,
-       (const struct uvc_descriptor_header *) &uvc_format_yuv,
+       (const struct uvc_descriptor_header *) &uvcg_format_yuv.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_yuv_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
-       (const struct uvc_descriptor_header *) &uvc_format_mjpg,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
+       (const struct uvc_descriptor_header *) &uvcg_format_mjpeg.desc,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p,
        (const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p,
-       (const struct uvc_descriptor_header *) &uvc_color_matching,
+       (const struct uvc_descriptor_header *) &uvcg_color_matching.desc,
        NULL,
 };
 
@@ -387,6 +543,23 @@ webcam_bind(struct usb_composite_dev *cdev)
        uvc_opts->hs_streaming = uvc_hs_streaming_cls;
        uvc_opts->ss_streaming = uvc_ss_streaming_cls;
 
+       INIT_LIST_HEAD(&uvcg_format_yuv.fmt.frames);
+       list_add_tail(&uvcg_frame_ptr_yuv_360p.entry, &uvcg_format_yuv.fmt.frames);
+       list_add_tail(&uvcg_frame_ptr_yuv_720p.entry, &uvcg_format_yuv.fmt.frames);
+       uvcg_format_yuv.fmt.num_frames = 2;
+
+       INIT_LIST_HEAD(&uvcg_format_mjpeg.fmt.frames);
+       list_add_tail(&uvcg_frame_ptr_mjpeg_360p.entry, &uvcg_format_mjpeg.fmt.frames);
+       list_add_tail(&uvcg_frame_ptr_mjpeg_720p.entry, &uvcg_format_mjpeg.fmt.frames);
+       uvcg_format_mjpeg.fmt.num_frames = 2;
+
+       INIT_LIST_HEAD(&uvcg_streaming_header.formats);
+       list_add_tail(&uvcg_format_ptr_yuv.entry, &uvcg_streaming_header.formats);
+       list_add_tail(&uvcg_format_ptr_mjpeg.entry, &uvcg_streaming_header.formats);
+       uvcg_streaming_header.num_fmt = 2;
+
+       uvc_opts->header = &uvcg_streaming_header;
+
        /* Allocate string descriptor numbers ... note that string contents
         * can be overridden by the composite_dev glue.
         */
index 30ea4a9..e3bf17a 100644 (file)
@@ -1924,7 +1924,7 @@ err_unprepare_fclk:
        return retval;
 }
 
-static int at91udc_remove(struct platform_device *pdev)
+static void at91udc_remove(struct platform_device *pdev)
 {
        struct at91_udc *udc = platform_get_drvdata(pdev);
        unsigned long   flags;
@@ -1932,8 +1932,11 @@ static int at91udc_remove(struct platform_device *pdev)
        DBG("remove\n");
 
        usb_del_gadget_udc(&udc->gadget);
-       if (udc->driver)
-               return -EBUSY;
+       if (udc->driver) {
+               dev_err(&pdev->dev,
+                       "Driver still in use but removing anyhow\n");
+               return;
+       }
 
        spin_lock_irqsave(&udc->lock, flags);
        pullup(udc, 0);
@@ -1943,8 +1946,6 @@ static int at91udc_remove(struct platform_device *pdev)
        remove_debug_file(udc);
        clk_unprepare(udc->fclk);
        clk_unprepare(udc->iclk);
-
-       return 0;
 }
 
 #ifdef CONFIG_PM
@@ -2001,7 +2002,7 @@ static int at91udc_resume(struct platform_device *pdev)
 
 static struct platform_driver at91_udc_driver = {
        .probe          = at91udc_probe,
-       .remove         = at91udc_remove,
+       .remove_new     = at91udc_remove,
        .shutdown       = at91udc_shutdown,
        .suspend        = at91udc_suspend,
        .resume         = at91udc_resume,
index 02b1bef..b76885d 100644 (file)
@@ -94,7 +94,7 @@ static ssize_t queue_dbg_read(struct file *file, char __user *buf,
 
        inode_lock(file_inode(file));
        list_for_each_entry_safe(req, tmp_req, queue, queue) {
-               len = snprintf(tmpbuf, sizeof(tmpbuf),
+               len = scnprintf(tmpbuf, sizeof(tmpbuf),
                                "%8p %08x %c%c%c %5d %c%c%c\n",
                                req->req.buf, req->req.length,
                                req->req.no_interrupt ? 'i' : 'I',
@@ -104,7 +104,6 @@ static ssize_t queue_dbg_read(struct file *file, char __user *buf,
                                req->submitted ? 'F' : 'f',
                                req->using_dma ? 'D' : 'd',
                                req->last_transaction ? 'L' : 'l');
-               len = min(len, sizeof(tmpbuf));
                if (len > nbytes)
                        break;
 
index be9ae0d..f5f3300 100644 (file)
@@ -16,34 +16,34 @@ static inline const char *cdns2_decode_usb_irq(char *str, size_t size,
 {
        int ret;
 
-       ret = snprintf(str, size, "usbirq: 0x%02x - ", usb_irq);
+       ret = scnprintf(str, size, "usbirq: 0x%02x - ", usb_irq);
 
        if (usb_irq & USBIRQ_SOF)
-               ret += snprintf(str + ret, size - ret, "SOF ");
+               ret += scnprintf(str + ret, size - ret, "SOF ");
        if (usb_irq & USBIRQ_SUTOK)
-               ret += snprintf(str + ret, size - ret, "SUTOK ");
+               ret += scnprintf(str + ret, size - ret, "SUTOK ");
        if (usb_irq & USBIRQ_SUDAV)
-               ret += snprintf(str + ret, size - ret, "SETUP ");
+               ret += scnprintf(str + ret, size - ret, "SETUP ");
        if (usb_irq & USBIRQ_SUSPEND)
-               ret += snprintf(str + ret, size - ret, "Suspend ");
+               ret += scnprintf(str + ret, size - ret, "Suspend ");
        if (usb_irq & USBIRQ_URESET)
-               ret += snprintf(str + ret, size - ret, "Reset ");
+               ret += scnprintf(str + ret, size - ret, "Reset ");
        if (usb_irq & USBIRQ_HSPEED)
-               ret += snprintf(str + ret, size - ret, "HS ");
+               ret += scnprintf(str + ret, size - ret, "HS ");
        if (usb_irq & USBIRQ_LPM)
-               ret += snprintf(str + ret, size - ret, "LPM ");
+               ret += scnprintf(str + ret, size - ret, "LPM ");
 
-       ret += snprintf(str + ret, size - ret, ", EXT: 0x%02x - ", ext_irq);
+       ret += scnprintf(str + ret, size - ret, ", EXT: 0x%02x - ", ext_irq);
 
        if (ext_irq & EXTIRQ_WAKEUP)
-               ret += snprintf(str + ret, size - ret, "Wakeup ");
+               ret += scnprintf(str + ret, size - ret, "Wakeup ");
        if (ext_irq & EXTIRQ_VBUSFAULT_FALL)
-               ret += snprintf(str + ret, size - ret, "VBUS_FALL ");
+               ret += scnprintf(str + ret, size - ret, "VBUS_FALL ");
        if (ext_irq & EXTIRQ_VBUSFAULT_RISE)
-               ret += snprintf(str + ret, size - ret, "VBUS_RISE ");
+               ret += scnprintf(str + ret, size - ret, "VBUS_RISE ");
 
-       if (ret >= size)
-               pr_info("CDNS2: buffer overflowed.\n");
+       if (ret == size - 1)
+               pr_info("CDNS2: buffer may be truncated.\n");
 
        return str;
 }
@@ -54,28 +54,28 @@ static inline const char *cdns2_decode_dma_irq(char *str, size_t size,
 {
        int ret;
 
-       ret = snprintf(str, size, "ISTS: %08x, %s: %08x ",
-                      ep_ists, ep_name, ep_sts);
+       ret = scnprintf(str, size, "ISTS: %08x, %s: %08x ",
+                       ep_ists, ep_name, ep_sts);
 
        if (ep_sts & DMA_EP_STS_IOC)
-               ret += snprintf(str + ret, size - ret, "IOC ");
+               ret += scnprintf(str + ret, size - ret, "IOC ");
        if (ep_sts & DMA_EP_STS_ISP)
-               ret += snprintf(str + ret, size - ret, "ISP ");
+               ret += scnprintf(str + ret, size - ret, "ISP ");
        if (ep_sts & DMA_EP_STS_DESCMIS)
-               ret += snprintf(str + ret, size - ret, "DESCMIS ");
+               ret += scnprintf(str + ret, size - ret, "DESCMIS ");
        if (ep_sts & DMA_EP_STS_TRBERR)
-               ret += snprintf(str + ret, size - ret, "TRBERR ");
+               ret += scnprintf(str + ret, size - ret, "TRBERR ");
        if (ep_sts & DMA_EP_STS_OUTSMM)
-               ret += snprintf(str + ret, size - ret, "OUTSMM ");
+               ret += scnprintf(str + ret, size - ret, "OUTSMM ");
        if (ep_sts & DMA_EP_STS_ISOERR)
-               ret += snprintf(str + ret, size - ret, "ISOERR ");
+               ret += scnprintf(str + ret, size - ret, "ISOERR ");
        if (ep_sts & DMA_EP_STS_DBUSY)
-               ret += snprintf(str + ret, size - ret, "DBUSY ");
+               ret += scnprintf(str + ret, size - ret, "DBUSY ");
        if (DMA_EP_STS_CCS(ep_sts))
-               ret += snprintf(str + ret, size - ret, "CCS ");
+               ret += scnprintf(str + ret, size - ret, "CCS ");
 
-       if (ret >= size)
-               pr_info("CDNS2: buffer overflowed.\n");
+       if (ret == size - 1)
+               pr_info("CDNS2: buffer may be truncated.\n");
 
        return str;
 }
@@ -105,43 +105,43 @@ static inline const char *cdns2_raw_ring(struct cdns2_endpoint *pep,
        int ret;
        int i;
 
-       ret = snprintf(str, size, "\n\t\tTR for %s:", pep->name);
+       ret = scnprintf(str, size, "\n\t\tTR for %s:", pep->name);
 
        trb = &trbs[ring->dequeue];
        dma = cdns2_trb_virt_to_dma(pep, trb);
-       ret += snprintf(str + ret, size - ret,
-                       "\n\t\tRing deq index: %d, trb: V=%p, P=0x%pad\n",
-                       ring->dequeue, trb, &dma);
+       ret += scnprintf(str + ret, size - ret,
+                        "\n\t\tRing deq index: %d, trb: V=%p, P=0x%pad\n",
+                        ring->dequeue, trb, &dma);
 
        trb = &trbs[ring->enqueue];
        dma = cdns2_trb_virt_to_dma(pep, trb);
-       ret += snprintf(str + ret, size - ret,
-                       "\t\tRing enq index: %d, trb: V=%p, P=0x%pad\n",
-                       ring->enqueue, trb, &dma);
+       ret += scnprintf(str + ret, size - ret,
+                        "\t\tRing enq index: %d, trb: V=%p, P=0x%pad\n",
+                        ring->enqueue, trb, &dma);
 
-       ret += snprintf(str + ret, size - ret,
-                       "\t\tfree trbs: %d, CCS=%d, PCS=%d\n",
-                       ring->free_trbs, ring->ccs, ring->pcs);
+       ret += scnprintf(str + ret, size - ret,
+                        "\t\tfree trbs: %d, CCS=%d, PCS=%d\n",
+                        ring->free_trbs, ring->ccs, ring->pcs);
 
        if (TRBS_PER_SEGMENT > 40) {
-               ret += snprintf(str + ret, size - ret,
-                               "\t\tTransfer ring %d too big\n", TRBS_PER_SEGMENT);
+               ret += scnprintf(str + ret, size - ret,
+                                "\t\tTransfer ring %d too big\n", TRBS_PER_SEGMENT);
                return str;
        }
 
        dma = ring->dma;
        for (i = 0; i < TRBS_PER_SEGMENT; ++i) {
                trb = &trbs[i];
-               ret += snprintf(str + ret, size - ret,
-                               "\t\t@%pad %08x %08x %08x\n", &dma,
-                               le32_to_cpu(trb->buffer),
-                               le32_to_cpu(trb->length),
-                               le32_to_cpu(trb->control));
+               ret += scnprintf(str + ret, size - ret,
+                                "\t\t@%pad %08x %08x %08x\n", &dma,
+                                le32_to_cpu(trb->buffer),
+                                le32_to_cpu(trb->length),
+                                le32_to_cpu(trb->control));
                dma += sizeof(*trb);
        }
 
-       if (ret >= size)
-               pr_info("CDNS2: buffer overflowed.\n");
+       if (ret == size - 1)
+               pr_info("CDNS2: buffer may be truncated.\n");
 
        return str;
 }
@@ -166,36 +166,36 @@ static inline const char *cdns2_decode_trb(char *str, size_t size, u32 flags,
 
        switch (type) {
        case TRB_LINK:
-               ret = snprintf(str, size,
-                              "LINK %08x type '%s' flags %c:%c:%c%c:%c",
-                              buffer, cdns2_trb_type_string(type),
-                              flags & TRB_CYCLE ? 'C' : 'c',
-                              flags & TRB_TOGGLE ? 'T' : 't',
-                              flags & TRB_CHAIN ? 'C' : 'c',
-                              flags & TRB_CHAIN ? 'H' : 'h',
-                              flags & TRB_IOC ? 'I' : 'i');
+               ret = scnprintf(str, size,
+                               "LINK %08x type '%s' flags %c:%c:%c%c:%c",
+                               buffer, cdns2_trb_type_string(type),
+                               flags & TRB_CYCLE ? 'C' : 'c',
+                               flags & TRB_TOGGLE ? 'T' : 't',
+                               flags & TRB_CHAIN ? 'C' : 'c',
+                               flags & TRB_CHAIN ? 'H' : 'h',
+                               flags & TRB_IOC ? 'I' : 'i');
                break;
        case TRB_NORMAL:
-               ret = snprintf(str, size,
-                              "type: '%s', Buffer: %08x, length: %ld, burst len: %ld, "
-                              "flags %c:%c:%c%c:%c",
-                              cdns2_trb_type_string(type),
-                              buffer, TRB_LEN(length),
-                              TRB_FIELD_TO_BURST(length),
-                              flags & TRB_CYCLE ? 'C' : 'c',
-                              flags & TRB_ISP ? 'I' : 'i',
-                              flags & TRB_CHAIN ? 'C' : 'c',
-                              flags & TRB_CHAIN ? 'H' : 'h',
-                              flags & TRB_IOC ? 'I' : 'i');
+               ret = scnprintf(str, size,
+                               "type: '%s', Buffer: %08x, length: %ld, burst len: %ld, "
+                               "flags %c:%c:%c%c:%c",
+                               cdns2_trb_type_string(type),
+                               buffer, TRB_LEN(length),
+                               TRB_FIELD_TO_BURST(length),
+                               flags & TRB_CYCLE ? 'C' : 'c',
+                               flags & TRB_ISP ? 'I' : 'i',
+                               flags & TRB_CHAIN ? 'C' : 'c',
+                               flags & TRB_CHAIN ? 'H' : 'h',
+                               flags & TRB_IOC ? 'I' : 'i');
                break;
        default:
-               ret = snprintf(str, size, "type '%s' -> raw %08x %08x %08x",
-                              cdns2_trb_type_string(type),
-                              buffer, length, flags);
+               ret = scnprintf(str, size, "type '%s' -> raw %08x %08x %08x",
+                               cdns2_trb_type_string(type),
+                               buffer, length, flags);
        }
 
-       if (ret >= size)
-               pr_info("CDNS2: buffer overflowed.\n");
+       if (ret == size - 1)
+               pr_info("CDNS2: buffer may be truncated.\n");
 
        return str;
 }
index 2693a10..e8042c1 100644 (file)
@@ -1360,7 +1360,7 @@ static void ch9getstatus(struct fsl_udc *udc, u8 request_type, u16 value,
        udc->ep0_dir = USB_DIR_IN;
        /* Borrow the per device status_req */
        req = udc->status_req;
-       /* Fill in the reqest structure */
+       /* Fill in the request structure */
        *((u16 *) req->req.buf) = cpu_to_le16(tmp);
 
        req->ep = ep;
@@ -2532,15 +2532,18 @@ err_kfree:
 /* Driver removal function
  * Free resources and finish pending transactions
  */
-static int fsl_udc_remove(struct platform_device *pdev)
+static void fsl_udc_remove(struct platform_device *pdev)
 {
        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        struct fsl_usb2_platform_data *pdata = dev_get_platdata(&pdev->dev);
 
        DECLARE_COMPLETION_ONSTACK(done);
 
-       if (!udc_controller)
-               return -ENODEV;
+       if (!udc_controller) {
+               dev_err(&pdev->dev,
+                       "Driver still in use but removing anyhow\n");
+               return;
+       }
 
        udc_controller->done = &done;
        usb_del_gadget_udc(&udc_controller->gadget);
@@ -2568,8 +2571,6 @@ static int fsl_udc_remove(struct platform_device *pdev)
         */
        if (pdata->exit)
                pdata->exit(pdev);
-
-       return 0;
 }
 
 /*-----------------------------------------------------------------
@@ -2667,7 +2668,7 @@ static const struct platform_device_id fsl_udc_devtype[] = {
 MODULE_DEVICE_TABLE(platform, fsl_udc_devtype);
 static struct platform_driver udc_driver = {
        .probe          = fsl_udc_probe,
-       .remove         = fsl_udc_remove,
+       .remove_new     = fsl_udc_remove,
        .id_table       = fsl_udc_devtype,
        /* these suspend and resume are not usb suspend and resume */
        .suspend        = fsl_udc_suspend,
index c6dfa7c..fb901be 100644 (file)
@@ -2089,15 +2089,18 @@ static void gr_ep_remove(struct gr_udc *dev, int num, int is_in)
                                  ep->tailbuf, ep->tailbuf_paddr);
 }
 
-static int gr_remove(struct platform_device *pdev)
+static void gr_remove(struct platform_device *pdev)
 {
        struct gr_udc *dev = platform_get_drvdata(pdev);
        int i;
 
        if (dev->added)
                usb_del_gadget_udc(&dev->gadget); /* Shuts everything down */
-       if (dev->driver)
-               return -EBUSY;
+       if (dev->driver) {
+               dev_err(&pdev->dev,
+                       "Driver still in use but removing anyhow\n");
+               return;
+       }
 
        gr_dfs_delete(dev);
        dma_pool_destroy(dev->desc_pool);
@@ -2110,8 +2113,6 @@ static int gr_remove(struct platform_device *pdev)
                gr_ep_remove(dev, i, 0);
        for (i = 0; i < dev->nepi; i++)
                gr_ep_remove(dev, i, 1);
-
-       return 0;
 }
 static int gr_request_irq(struct gr_udc *dev, int irq)
 {
@@ -2248,7 +2249,7 @@ static struct platform_driver gr_driver = {
                .of_match_table = gr_match,
        },
        .probe = gr_probe,
-       .remove = gr_remove,
+       .remove_new = gr_remove,
 };
 module_platform_driver(gr_driver);
 
index a917cc9..d5f29f8 100644 (file)
@@ -3174,13 +3174,16 @@ i2c_fail:
        return retval;
 }
 
-static int lpc32xx_udc_remove(struct platform_device *pdev)
+static void lpc32xx_udc_remove(struct platform_device *pdev)
 {
        struct lpc32xx_udc *udc = platform_get_drvdata(pdev);
 
        usb_del_gadget_udc(&udc->gadget);
-       if (udc->driver)
-               return -EBUSY;
+       if (udc->driver) {
+               dev_err(&pdev->dev,
+                       "Driver still in use but removing anyhow\n");
+               return;
+       }
 
        udc_clk_set(udc, 1);
        udc_disable(udc);
@@ -3194,8 +3197,6 @@ static int lpc32xx_udc_remove(struct platform_device *pdev)
                          udc->udca_v_base, udc->udca_p_base);
 
        clk_disable_unprepare(udc->usb_slv_clk);
-
-       return 0;
 }
 
 #ifdef CONFIG_PM
@@ -3255,7 +3256,7 @@ MODULE_DEVICE_TABLE(of, lpc32xx_udc_of_match);
 
 static struct platform_driver lpc32xx_udc_driver = {
        .probe          = lpc32xx_udc_probe,
-       .remove         = lpc32xx_udc_remove,
+       .remove_new     = lpc32xx_udc_remove,
        .shutdown       = lpc32xx_udc_shutdown,
        .suspend        = lpc32xx_udc_suspend,
        .resume         = lpc32xx_udc_resume,
index d888dcd..78308b6 100644 (file)
@@ -1451,7 +1451,7 @@ udc_prime_status(struct mv_udc *udc, u8 direction, u16 status, bool empty)
 
        req = udc->status_req;
 
-       /* fill in the reqest structure */
+       /* fill in the request structure */
        if (empty == false) {
                *((u16 *) req->req.buf) = cpu_to_le16(status);
                req->req.length = 2;
index df0551e..1ac26cb 100644 (file)
@@ -2397,12 +2397,15 @@ static void pxa25x_udc_shutdown(struct platform_device *_dev)
        pullup_off();
 }
 
-static int pxa25x_udc_remove(struct platform_device *pdev)
+static void pxa25x_udc_remove(struct platform_device *pdev)
 {
        struct pxa25x_udc *dev = platform_get_drvdata(pdev);
 
-       if (dev->driver)
-               return -EBUSY;
+       if (dev->driver) {
+               dev_err(&pdev->dev,
+                       "Driver still in use but removing anyhow\n");
+               return;
+       }
 
        usb_del_gadget_udc(&dev->gadget);
        dev->pullup = 0;
@@ -2414,7 +2417,6 @@ static int pxa25x_udc_remove(struct platform_device *pdev)
                dev->transceiver = NULL;
 
        the_controller = NULL;
-       return 0;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -2472,7 +2474,7 @@ static int pxa25x_udc_resume(struct platform_device *dev)
 static struct platform_driver udc_driver = {
        .shutdown       = pxa25x_udc_shutdown,
        .probe          = pxa25x_udc_probe,
-       .remove         = pxa25x_udc_remove,
+       .remove_new     = pxa25x_udc_remove,
        .suspend        = pxa25x_udc_suspend,
        .resume         = pxa25x_udc_resume,
        .driver         = {
index d152d72..9fe4f48 100644 (file)
@@ -1158,12 +1158,12 @@ dump_eps(struct usb_hcd *hcd)
                end = dp + sizeof(ubuf);
                *dp = '\0';
                list_for_each_entry(urb, &ep->urb_list, urb_list) {
-                       ret = snprintf(dp, end - dp, " %p(%d.%s %d/%d)", urb,
-                                      usb_pipetype(urb->pipe),
-                                      usb_urb_dir_in(urb) ? "IN" : "OUT",
-                                      urb->actual_length,
-                                      urb->transfer_buffer_length);
-                       if (ret < 0 || ret >= end - dp)
+                       ret = scnprintf(dp, end - dp, " %p(%d.%s %d/%d)", urb,
+                                       usb_pipetype(urb->pipe),
+                                       usb_urb_dir_in(urb) ? "IN" : "OUT",
+                                       urb->actual_length,
+                                       urb->transfer_buffer_length);
+                       if (ret == end - dp - 1)
                                break;  /* error or buffer full */
                        dp += ret;
                }
@@ -1255,9 +1255,9 @@ max3421_handle_irqs(struct usb_hcd *hcd)
                        end = sbuf + sizeof(sbuf);
                        *dp = '\0';
                        for (i = 0; i < 16; ++i) {
-                               int ret = snprintf(dp, end - dp, " %lu",
-                                                  max3421_hcd->err_stat[i]);
-                               if (ret < 0 || ret >= end - dp)
+                               int ret = scnprintf(dp, end - dp, " %lu",
+                                                   max3421_hcd->err_stat[i]);
+                               if (ret == end - dp - 1)
                                        break;  /* error or buffer full */
                                dp += ret;
                        }
index b40d923..d82935d 100644 (file)
@@ -6,9 +6,24 @@
  *
  * Author: Lu Baolu <baolu.lu@linux.intel.com>
  */
+#include <linux/bug.h>
+#include <linux/device.h>
 #include <linux/dma-mapping.h>
-#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/kstrtox.h>
+#include <linux/list.h>
 #include <linux/nls.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <linux/io-64-nonatomic-lo-hi.h>
+
+#include <asm/byteorder.h>
 
 #include "xhci.h"
 #include "xhci-trace.h"
@@ -28,7 +43,7 @@ static void dbc_ring_free(struct device *dev, struct xhci_ring *ring)
        if (!ring)
                return;
 
-       if (ring->first_seg && ring->first_seg->trbs) {
+       if (ring->first_seg) {
                dma_free_coherent(dev, TRB_SEGMENT_SIZE,
                                  ring->first_seg->trbs,
                                  ring->first_seg->dma);
@@ -374,13 +389,13 @@ static void xhci_dbc_eps_init(struct xhci_dbc *dbc)
 
 static void xhci_dbc_eps_exit(struct xhci_dbc *dbc)
 {
-       memset(dbc->eps, 0, sizeof(struct dbc_ep) * ARRAY_SIZE(dbc->eps));
+       memset(dbc->eps, 0, sizeof_field(struct xhci_dbc, eps));
 }
 
 static int dbc_erst_alloc(struct device *dev, struct xhci_ring *evt_ring,
                    struct xhci_erst *erst, gfp_t flags)
 {
-       erst->entries = dma_alloc_coherent(dev, sizeof(struct xhci_erst_entry),
+       erst->entries = dma_alloc_coherent(dev, sizeof(*erst->entries),
                                           &erst->erst_dma_addr, flags);
        if (!erst->entries)
                return -ENOMEM;
@@ -394,9 +409,8 @@ static int dbc_erst_alloc(struct device *dev, struct xhci_ring *evt_ring,
 
 static void dbc_erst_free(struct device *dev, struct xhci_erst *erst)
 {
-       if (erst->entries)
-               dma_free_coherent(dev, sizeof(struct xhci_erst_entry),
-                                 erst->entries, erst->erst_dma_addr);
+       dma_free_coherent(dev, sizeof(*erst->entries), erst->entries,
+                         erst->erst_dma_addr);
        erst->entries = NULL;
 }
 
@@ -495,7 +509,7 @@ static int xhci_dbc_mem_init(struct xhci_dbc *dbc, gfp_t flags)
                goto ctx_fail;
 
        /* Allocate the string table: */
-       dbc->string_size = sizeof(struct dbc_str_descs);
+       dbc->string_size = sizeof(*dbc->string);
        dbc->string = dma_alloc_coherent(dev, dbc->string_size,
                                         &dbc->string_dma, flags);
        if (!dbc->string)
@@ -543,11 +557,8 @@ static void xhci_dbc_mem_cleanup(struct xhci_dbc *dbc)
 
        xhci_dbc_eps_exit(dbc);
 
-       if (dbc->string) {
-               dma_free_coherent(dbc->dev, dbc->string_size,
-                                 dbc->string, dbc->string_dma);
-               dbc->string = NULL;
-       }
+       dma_free_coherent(dbc->dev, dbc->string_size, dbc->string, dbc->string_dma);
+       dbc->string = NULL;
 
        dbc_free_ctx(dbc->dev, dbc->ctx);
        dbc->ctx = NULL;
@@ -597,7 +608,7 @@ static int xhci_do_dbc_start(struct xhci_dbc *dbc)
 static int xhci_do_dbc_stop(struct xhci_dbc *dbc)
 {
        if (dbc->state == DS_DISABLED)
-               return -1;
+               return -EINVAL;
 
        writel(0, &dbc->regs->control);
        dbc->state = DS_DISABLED;
@@ -650,11 +661,11 @@ static void xhci_dbc_stop(struct xhci_dbc *dbc)
        spin_lock_irqsave(&dbc->lock, flags);
        ret = xhci_do_dbc_stop(dbc);
        spin_unlock_irqrestore(&dbc->lock, flags);
+       if (ret)
+               return;
 
-       if (!ret) {
-               xhci_dbc_mem_cleanup(dbc);
-               pm_runtime_put_sync(dbc->dev); /* note, was self.controller */
-       }
+       xhci_dbc_mem_cleanup(dbc);
+       pm_runtime_put_sync(dbc->dev); /* note, was self.controller */
 }
 
 static void
@@ -914,41 +925,29 @@ static void xhci_dbc_handle_events(struct work_struct *work)
        mod_delayed_work(system_wq, &dbc->event_work, 1);
 }
 
+static const char * const dbc_state_strings[DS_MAX] = {
+       [DS_DISABLED] = "disabled",
+       [DS_INITIALIZED] = "initialized",
+       [DS_ENABLED] = "enabled",
+       [DS_CONNECTED] = "connected",
+       [DS_CONFIGURED] = "configured",
+       [DS_STALLED] = "stalled",
+};
+
 static ssize_t dbc_show(struct device *dev,
                        struct device_attribute *attr,
                        char *buf)
 {
-       const char              *p;
        struct xhci_dbc         *dbc;
        struct xhci_hcd         *xhci;
 
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       switch (dbc->state) {
-       case DS_DISABLED:
-               p = "disabled";
-               break;
-       case DS_INITIALIZED:
-               p = "initialized";
-               break;
-       case DS_ENABLED:
-               p = "enabled";
-               break;
-       case DS_CONNECTED:
-               p = "connected";
-               break;
-       case DS_CONFIGURED:
-               p = "configured";
-               break;
-       case DS_STALLED:
-               p = "stalled";
-               break;
-       default:
-               p = "unknown";
-       }
+       if (dbc->state >= ARRAY_SIZE(dbc_state_strings))
+               return sysfs_emit(buf, "unknown\n");
 
-       return sprintf(buf, "%s\n", p);
+       return sysfs_emit(buf, "%s\n", dbc_state_strings[dbc->state]);
 }
 
 static ssize_t dbc_store(struct device *dev,
@@ -961,9 +960,9 @@ static ssize_t dbc_store(struct device *dev,
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       if (!strncmp(buf, "enable", 6))
+       if (sysfs_streq(buf, "enable"))
                xhci_dbc_start(dbc);
-       else if (!strncmp(buf, "disable", 7))
+       else if (sysfs_streq(buf, "disable"))
                xhci_dbc_stop(dbc);
        else
                return -EINVAL;
@@ -981,7 +980,7 @@ static ssize_t dbc_idVendor_show(struct device *dev,
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       return sprintf(buf, "%04x\n", dbc->idVendor);
+       return sysfs_emit(buf, "%04x\n", dbc->idVendor);
 }
 
 static ssize_t dbc_idVendor_store(struct device *dev,
@@ -993,9 +992,11 @@ static ssize_t dbc_idVendor_store(struct device *dev,
        void __iomem            *ptr;
        u16                     value;
        u32                     dev_info;
+       int ret;
 
-       if (kstrtou16(buf, 0, &value))
-               return -EINVAL;
+       ret = kstrtou16(buf, 0, &value);
+       if (ret)
+               return ret;
 
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
@@ -1021,7 +1022,7 @@ static ssize_t dbc_idProduct_show(struct device *dev,
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       return sprintf(buf, "%04x\n", dbc->idProduct);
+       return sysfs_emit(buf, "%04x\n", dbc->idProduct);
 }
 
 static ssize_t dbc_idProduct_store(struct device *dev,
@@ -1033,9 +1034,11 @@ static ssize_t dbc_idProduct_store(struct device *dev,
        void __iomem            *ptr;
        u32                     dev_info;
        u16                     value;
+       int ret;
 
-       if (kstrtou16(buf, 0, &value))
-               return -EINVAL;
+       ret = kstrtou16(buf, 0, &value);
+       if (ret)
+               return ret;
 
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
@@ -1060,7 +1063,7 @@ static ssize_t dbc_bcdDevice_show(struct device *dev,
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       return sprintf(buf, "%04x\n", dbc->bcdDevice);
+       return sysfs_emit(buf, "%04x\n", dbc->bcdDevice);
 }
 
 static ssize_t dbc_bcdDevice_store(struct device *dev,
@@ -1072,9 +1075,11 @@ static ssize_t dbc_bcdDevice_store(struct device *dev,
        void __iomem *ptr;
        u32 dev_info;
        u16 value;
+       int ret;
 
-       if (kstrtou16(buf, 0, &value))
-               return -EINVAL;
+       ret = kstrtou16(buf, 0, &value);
+       if (ret)
+               return ret;
 
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
@@ -1100,7 +1105,7 @@ static ssize_t dbc_bInterfaceProtocol_show(struct device *dev,
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
        dbc = xhci->dbc;
 
-       return sprintf(buf, "%02x\n", dbc->bInterfaceProtocol);
+       return sysfs_emit(buf, "%02x\n", dbc->bInterfaceProtocol);
 }
 
 static ssize_t dbc_bInterfaceProtocol_store(struct device *dev,
@@ -1114,9 +1119,13 @@ static ssize_t dbc_bInterfaceProtocol_store(struct device *dev,
        u8 value;
        int ret;
 
-       /* bInterfaceProtocol is 8 bit, but xhci only supports values 0 and 1 */
+       /* bInterfaceProtocol is 8 bit, but... */
        ret = kstrtou8(buf, 0, &value);
-       if (ret || value > 1)
+       if (ret)
+               return ret;
+
+       /* ...xhci only supports values 0 and 1 */
+       if (value > 1)
                return -EINVAL;
 
        xhci = hcd_to_xhci(dev_get_drvdata(dev));
@@ -1139,7 +1148,7 @@ static DEVICE_ATTR_RW(dbc_idProduct);
 static DEVICE_ATTR_RW(dbc_bcdDevice);
 static DEVICE_ATTR_RW(dbc_bInterfaceProtocol);
 
-static struct attribute *dbc_dev_attributes[] = {
+static struct attribute *dbc_dev_attrs[] = {
        &dev_attr_dbc.attr,
        &dev_attr_dbc_idVendor.attr,
        &dev_attr_dbc_idProduct.attr,
@@ -1147,10 +1156,7 @@ static struct attribute *dbc_dev_attributes[] = {
        &dev_attr_dbc_bInterfaceProtocol.attr,
        NULL
 };
-
-static const struct attribute_group dbc_dev_attrib_grp = {
-       .attrs = dbc_dev_attributes,
-};
+ATTRIBUTE_GROUPS(dbc_dev);
 
 struct xhci_dbc *
 xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver)
@@ -1176,7 +1182,7 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *
        INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
        spin_lock_init(&dbc->lock);
 
-       ret = sysfs_create_group(&dev->kobj, &dbc_dev_attrib_grp);
+       ret = sysfs_create_groups(&dev->kobj, dbc_dev_groups);
        if (ret)
                goto err;
 
@@ -1195,7 +1201,7 @@ void xhci_dbc_remove(struct xhci_dbc *dbc)
        xhci_dbc_stop(dbc);
 
        /* remove sysfs files */
-       sysfs_remove_group(&dbc->dev->kobj, &dbc_dev_attrib_grp);
+       sysfs_remove_groups(&dbc->dev->kobj, dbc_dev_groups);
 
        kfree(dbc);
 }
index 51a7ab3..e39e3ae 100644 (file)
@@ -82,6 +82,7 @@ enum dbc_state {
        DS_CONNECTED,
        DS_CONFIGURED,
        DS_STALLED,
+       DS_MAX
 };
 
 struct dbc_ep {
index 6d142cd..f8ba15e 100644 (file)
@@ -693,7 +693,7 @@ void xhci_debugfs_init(struct xhci_hcd *xhci)
                                     "command-ring",
                                     xhci->debugfs_root);
 
-       xhci_debugfs_create_ring_dir(xhci, &xhci->interrupter->event_ring,
+       xhci_debugfs_create_ring_dir(xhci, &xhci->interrupters[0]->event_ring,
                                     "event-ring",
                                     xhci->debugfs_root);
 
index 6211658..4460fa7 100644 (file)
@@ -323,6 +323,7 @@ void xhci_initialize_ring_info(struct xhci_ring *ring,
         */
        ring->num_trbs_free = ring->num_segs * (TRBS_PER_SEGMENT - 1) - 1;
 }
+EXPORT_SYMBOL_GPL(xhci_initialize_ring_info);
 
 /* Allocate segments and link them for a ring */
 static int xhci_alloc_segments_for_ring(struct xhci_hcd *xhci,
@@ -1739,6 +1740,8 @@ struct xhci_command *xhci_alloc_command(struct xhci_hcd *xhci,
        }
 
        command->status = 0;
+       /* set default timeout to 5000 ms */
+       command->timeout_ms = XHCI_CMD_DEFAULT_TIMEOUT;
        INIT_LIST_HEAD(&command->cmd_list);
        return command;
 }
@@ -1853,6 +1856,31 @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
        kfree(ir);
 }
 
+void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir)
+{
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+       unsigned int intr_num;
+
+       /* interrupter 0 is primary interrupter, don't touch it */
+       if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters)
+               xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n");
+
+       /* fixme, should we check xhci->interrupter[intr_num] == ir */
+       /* fixme locking */
+
+       spin_lock_irq(&xhci->lock);
+
+       intr_num = ir->intr_num;
+
+       xhci_remove_interrupter(xhci, ir);
+       xhci->interrupters[intr_num] = NULL;
+
+       spin_unlock_irq(&xhci->lock);
+
+       xhci_free_interrupter(xhci, ir);
+}
+EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter);
+
 void xhci_mem_cleanup(struct xhci_hcd *xhci)
 {
        struct device   *dev = xhci_to_hcd(xhci)->self.sysdev;
@@ -1860,10 +1888,14 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
 
        cancel_delayed_work_sync(&xhci->cmd_timer);
 
-       xhci_remove_interrupter(xhci, xhci->interrupter);
-       xhci_free_interrupter(xhci, xhci->interrupter);
-       xhci->interrupter = NULL;
-       xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring");
+       for (i = 0; i < xhci->max_interrupters; i++) {
+               if (xhci->interrupters[i]) {
+                       xhci_remove_interrupter(xhci, xhci->interrupters[i]);
+                       xhci_free_interrupter(xhci, xhci->interrupters[i]);
+                       xhci->interrupters[i] = NULL;
+               }
+       }
+       xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed interrupters");
 
        if (xhci->cmd_ring)
                xhci_ring_free(xhci, xhci->cmd_ring);
@@ -1933,6 +1965,7 @@ no_bw:
        for (i = 0; i < xhci->num_port_caps; i++)
                kfree(xhci->port_caps[i].psi);
        kfree(xhci->port_caps);
+       kfree(xhci->interrupters);
        xhci->num_port_caps = 0;
 
        xhci->usb2_rhub.ports = NULL;
@@ -1941,6 +1974,7 @@ no_bw:
        xhci->rh_bw = NULL;
        xhci->ext_caps = NULL;
        xhci->port_caps = NULL;
+       xhci->interrupters = NULL;
 
        xhci->page_size = 0;
        xhci->page_shift = 0;
@@ -2246,18 +2280,20 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags)
 }
 
 static struct xhci_interrupter *
-xhci_alloc_interrupter(struct xhci_hcd *xhci, gfp_t flags)
+xhci_alloc_interrupter(struct xhci_hcd *xhci, int segs, gfp_t flags)
 {
        struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
        struct xhci_interrupter *ir;
-       unsigned int num_segs;
+       unsigned int num_segs = segs;
        int ret;
 
        ir = kzalloc_node(sizeof(*ir), flags, dev_to_node(dev));
        if (!ir)
                return NULL;
 
-       num_segs = min_t(unsigned int, 1 << HCS_ERST_MAX(xhci->hcs_params2),
+       /* number of ring segments should be greater than 0 */
+       if (segs <= 0)
+               num_segs = min_t(unsigned int, 1 << HCS_ERST_MAX(xhci->hcs_params2),
                         ERST_MAX_SEGS);
 
        ir->event_ring = xhci_ring_alloc(xhci, num_segs, 1, TYPE_EVENT, 0,
@@ -2292,6 +2328,13 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
                return -EINVAL;
        }
 
+       if (xhci->interrupters[intr_num]) {
+               xhci_warn(xhci, "Interrupter %d\n already set up", intr_num);
+               return -EINVAL;
+       }
+
+       xhci->interrupters[intr_num] = ir;
+       ir->intr_num = intr_num;
        ir->ir_set = &xhci->run_regs->ir_set[intr_num];
 
        /* set ERST count with the number of entries in the segment table */
@@ -2311,10 +2354,52 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
        return 0;
 }
 
+struct xhci_interrupter *
+xhci_create_secondary_interrupter(struct usb_hcd *hcd, int num_seg)
+{
+       struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+       struct xhci_interrupter *ir;
+       unsigned int i;
+       int err = -ENOSPC;
+
+       if (!xhci->interrupters || xhci->max_interrupters <= 1)
+               return NULL;
+
+       ir = xhci_alloc_interrupter(xhci, num_seg, GFP_KERNEL);
+       if (!ir)
+               return NULL;
+
+       spin_lock_irq(&xhci->lock);
+
+       /* Find available secondary interrupter, interrupter 0 is reserved for primary */
+       for (i = 1; i < xhci->max_interrupters; i++) {
+               if (xhci->interrupters[i] == NULL) {
+                       err = xhci_add_interrupter(xhci, ir, i);
+                       break;
+               }
+       }
+
+       spin_unlock_irq(&xhci->lock);
+
+       if (err) {
+               xhci_warn(xhci, "Failed to add secondary interrupter, max interrupters %d\n",
+                         xhci->max_interrupters);
+               xhci_free_interrupter(xhci, ir);
+               return NULL;
+       }
+
+       xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n",
+                i, xhci->max_interrupters);
+
+       return ir;
+}
+EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter);
+
 int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
 {
-       dma_addr_t      dma;
+       struct xhci_interrupter *ir;
        struct device   *dev = xhci_to_hcd(xhci)->self.sysdev;
+       dma_addr_t      dma;
        unsigned int    val, val2;
        u64             val_64;
        u32             page_size, temp;
@@ -2438,11 +2523,14 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
        /* Allocate and set up primary interrupter 0 with an event ring. */
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                       "Allocating primary event ring");
-       xhci->interrupter = xhci_alloc_interrupter(xhci, flags);
-       if (!xhci->interrupter)
+       xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters),
+                                         flags, dev_to_node(dev));
+
+       ir = xhci_alloc_interrupter(xhci, 0, flags);
+       if (!ir)
                goto fail;
 
-       if (xhci_add_interrupter(xhci, xhci->interrupter, 0))
+       if (xhci_add_interrupter(xhci, ir, 0))
                goto fail;
 
        xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
index bbdf1b0..3252e3d 100644 (file)
@@ -7,6 +7,7 @@
  *  Chunfeng Yun <chunfeng.yun@mediatek.com>
  */
 
+#include <linux/bitfield.h>
 #include <linux/dma-mapping.h>
 #include <linux/iopoll.h>
 #include <linux/kernel.h>
@@ -73,6 +74,9 @@
 #define FRMCNT_LEV1_RANG       (0x12b << 8)
 #define FRMCNT_LEV1_RANG_MASK  GENMASK(19, 8)
 
+#define HSCH_CFG1              0x960
+#define SCH3_RXFIFO_DEPTH_MASK GENMASK(21, 20)
+
 #define SS_GEN2_EOF_CFG                0x990
 #define SSG2EOF_OFFSET         0x3c
 
 #define SSC_IP_SLEEP_EN        BIT(4)
 #define SSC_SPM_INT_EN         BIT(1)
 
+#define SCH_FIFO_TO_KB(x)      ((x) >> 10)
+
 enum ssusb_uwk_vers {
        SSUSB_UWK_V1 = 1,
        SSUSB_UWK_V2,
@@ -165,6 +171,35 @@ static void xhci_mtk_set_frame_interval(struct xhci_hcd_mtk *mtk)
        writel(value, hcd->regs + SS_GEN2_EOF_CFG);
 }
 
+/*
+ * workaround: usb3.2 gen1 isoc rx hw issue
+ * host send out unexpected ACK afer device fininsh a burst transfer with
+ * a short packet.
+ */
+static void xhci_mtk_rxfifo_depth_set(struct xhci_hcd_mtk *mtk)
+{
+       struct usb_hcd *hcd = mtk->hcd;
+       u32 value;
+
+       if (!mtk->rxfifo_depth)
+               return;
+
+       value = readl(hcd->regs + HSCH_CFG1);
+       value &= ~SCH3_RXFIFO_DEPTH_MASK;
+       value |= FIELD_PREP(SCH3_RXFIFO_DEPTH_MASK,
+                           SCH_FIFO_TO_KB(mtk->rxfifo_depth) - 1);
+       writel(value, hcd->regs + HSCH_CFG1);
+}
+
+static void xhci_mtk_init_quirk(struct xhci_hcd_mtk *mtk)
+{
+       /* workaround only for mt8195 */
+       xhci_mtk_set_frame_interval(mtk);
+
+       /* workaround for SoCs using SSUSB about before IPM v1.6.0 */
+       xhci_mtk_rxfifo_depth_set(mtk);
+}
+
 static int xhci_mtk_host_enable(struct xhci_hcd_mtk *mtk)
 {
        struct mu3c_ippc_regs __iomem *ippc = mtk->ippc_regs;
@@ -448,8 +483,7 @@ static int xhci_mtk_setup(struct usb_hcd *hcd)
                if (ret)
                        return ret;
 
-               /* workaround only for mt8195 */
-               xhci_mtk_set_frame_interval(mtk);
+               xhci_mtk_init_quirk(mtk);
        }
 
        ret = xhci_gen_setup(hcd, xhci_mtk_quirks);
@@ -527,6 +561,8 @@ static int xhci_mtk_probe(struct platform_device *pdev)
        of_property_read_u32(node, "mediatek,u2p-dis-msk",
                             &mtk->u2p_dis_msk);
 
+       of_property_read_u32(node, "rx-fifo-depth", &mtk->rxfifo_depth);
+
        ret = usb_wakeup_of_property_parse(mtk, node);
        if (ret) {
                dev_err(dev, "failed to parse uwk property\n");
index 39f7ae7..f5e2bd6 100644 (file)
@@ -171,6 +171,8 @@ struct xhci_hcd_mtk {
        struct regmap *uwk;
        u32 uwk_reg_base;
        u32 uwk_vers;
+       /* quirk */
+       u32 rxfifo_depth;
 };
 
 static inline struct xhci_hcd_mtk *hcd_to_mtk(struct usb_hcd *hcd)
index d6fc08e..b534ca9 100644 (file)
@@ -95,10 +95,9 @@ static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
 
        if (hcd->msix_enabled) {
                struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
-               int i;
 
-               for (i = 0; i < xhci->msix_count; i++)
-                       synchronize_irq(pci_irq_vector(pdev, i));
+               /* for now, the driver only supports one primary interrupter */
+               synchronize_irq(pci_irq_vector(pdev, 0));
        }
 }
 
@@ -112,100 +111,18 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci)
        if (hcd->irq > 0)
                return;
 
-       if (hcd->msix_enabled) {
-               int i;
-
-               for (i = 0; i < xhci->msix_count; i++)
-                       free_irq(pci_irq_vector(pdev, i), xhci_to_hcd(xhci));
-       } else {
-               free_irq(pci_irq_vector(pdev, 0), xhci_to_hcd(xhci));
-       }
-
+       free_irq(pci_irq_vector(pdev, 0), xhci_to_hcd(xhci));
        pci_free_irq_vectors(pdev);
        hcd->msix_enabled = 0;
 }
 
-/*
- * Set up MSI
- */
-static int xhci_setup_msi(struct xhci_hcd *xhci)
-{
-       int ret;
-       /*
-        * TODO:Check with MSI Soc for sysdev
-        */
-       struct pci_dev  *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
-
-       ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
-       if (ret < 0) {
-               xhci_dbg_trace(xhci, trace_xhci_dbg_init,
-                               "failed to allocate MSI entry");
-               return ret;
-       }
-
-       ret = request_irq(pdev->irq, xhci_msi_irq,
-                               0, "xhci_hcd", xhci_to_hcd(xhci));
-       if (ret) {
-               xhci_dbg_trace(xhci, trace_xhci_dbg_init,
-                               "disable MSI interrupt");
-               pci_free_irq_vectors(pdev);
-       }
-
-       return ret;
-}
-
-/*
- * Set up MSI-X
- */
-static int xhci_setup_msix(struct xhci_hcd *xhci)
-{
-       int i, ret;
-       struct usb_hcd *hcd = xhci_to_hcd(xhci);
-       struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
-
-       /*
-        * calculate number of msi-x vectors supported.
-        * - HCS_MAX_INTRS: the max number of interrupts the host can handle,
-        *   with max number of interrupters based on the xhci HCSPARAMS1.
-        * - num_online_cpus: maximum msi-x vectors per CPUs core.
-        *   Add additional 1 vector to ensure always available interrupt.
-        */
-       xhci->msix_count = min(num_online_cpus() + 1,
-                               HCS_MAX_INTRS(xhci->hcs_params1));
-
-       ret = pci_alloc_irq_vectors(pdev, xhci->msix_count, xhci->msix_count,
-                       PCI_IRQ_MSIX);
-       if (ret < 0) {
-               xhci_dbg_trace(xhci, trace_xhci_dbg_init,
-                               "Failed to enable MSI-X");
-               return ret;
-       }
-
-       for (i = 0; i < xhci->msix_count; i++) {
-               ret = request_irq(pci_irq_vector(pdev, i), xhci_msi_irq, 0,
-                               "xhci_hcd", xhci_to_hcd(xhci));
-               if (ret)
-                       goto disable_msix;
-       }
-
-       hcd->msix_enabled = 1;
-       return ret;
-
-disable_msix:
-       xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable MSI-X interrupt");
-       while (--i >= 0)
-               free_irq(pci_irq_vector(pdev, i), xhci_to_hcd(xhci));
-       pci_free_irq_vectors(pdev);
-       return ret;
-}
-
+/* Try enabling MSI-X with MSI and legacy IRQ as fallback */
 static int xhci_try_enable_msi(struct usb_hcd *hcd)
 {
+       struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-       struct pci_dev  *pdev;
        int ret;
 
-       pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
        /*
         * Some Fresco Logic host controllers advertise MSI, but fail to
         * generate interrupts.  Don't even try to enable MSI.
@@ -218,32 +135,53 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
                free_irq(hcd->irq, hcd);
        hcd->irq = 0;
 
-       ret = xhci_setup_msix(xhci);
-       if (ret)
-               /* fall back to msi*/
-               ret = xhci_setup_msi(xhci);
+       /*
+        * calculate number of MSI-X vectors supported.
+        * - HCS_MAX_INTRS: the max number of interrupts the host can handle,
+        *   with max number of interrupters based on the xhci HCSPARAMS1.
+        * - num_online_cpus: maximum MSI-X vectors per CPUs core.
+        *   Add additional 1 vector to ensure always available interrupt.
+        */
+       xhci->nvecs = min(num_online_cpus() + 1,
+                         HCS_MAX_INTRS(xhci->hcs_params1));
 
-       if (!ret) {
-               hcd->msi_enabled = 1;
-               return 0;
+       /* TODO: Check with MSI Soc for sysdev */
+       xhci->nvecs = pci_alloc_irq_vectors(pdev, 1, xhci->nvecs,
+                                           PCI_IRQ_MSIX | PCI_IRQ_MSI);
+       if (xhci->nvecs < 0) {
+               xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+                              "failed to allocate IRQ vectors");
+               goto legacy_irq;
        }
 
+       ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd",
+                         xhci_to_hcd(xhci));
+       if (ret)
+               goto free_irq_vectors;
+
+       hcd->msi_enabled = 1;
+       hcd->msix_enabled = pdev->msix_enabled;
+       return 0;
+
+free_irq_vectors:
+       xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable %s interrupt",
+                      pdev->msix_enabled ? "MSI-X" : "MSI");
+       pci_free_irq_vectors(pdev);
+
+legacy_irq:
        if (!pdev->irq) {
                xhci_err(xhci, "No msi-x/msi found and no IRQ in BIOS\n");
                return -EINVAL;
        }
 
- legacy_irq:
        if (!strlen(hcd->irq_descr))
                snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
                         hcd->driver->description, hcd->self.busnum);
 
-       /* fall back to legacy interrupt*/
-       ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED,
-                       hcd->irq_descr, hcd);
+       /* fall back to legacy interrupt */
+       ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, hcd->irq_descr, hcd);
        if (ret) {
-               xhci_err(xhci, "request interrupt %d failed\n",
-                               pdev->irq);
+               xhci_err(xhci, "request interrupt %d failed\n", pdev->irq);
                return ret;
        }
        hcd->irq = pdev->irq;
index 732cdeb..f04fde1 100644 (file)
@@ -130,6 +130,9 @@ static const struct of_device_id usb_xhci_of_match[] = {
        }, {
                .compatible = "brcm,xhci-brcm-v2",
                .data = &xhci_plat_brcm,
+       }, {
+               .compatible = "brcm,bcm2711-xhci",
+               .data = &xhci_plat_brcm,
        }, {
                .compatible = "brcm,bcm7445-xhci",
                .data = &xhci_plat_brcm,
@@ -433,7 +436,7 @@ void xhci_plat_remove(struct platform_device *dev)
 }
 EXPORT_SYMBOL_GPL(xhci_plat_remove);
 
-static int __maybe_unused xhci_plat_suspend(struct device *dev)
+static int xhci_plat_suspend(struct device *dev)
 {
        struct usb_hcd  *hcd = dev_get_drvdata(dev);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
@@ -461,7 +464,7 @@ static int __maybe_unused xhci_plat_suspend(struct device *dev)
        return 0;
 }
 
-static int __maybe_unused xhci_plat_resume(struct device *dev)
+static int xhci_plat_resume_common(struct device *dev, struct pm_message pmsg)
 {
        struct usb_hcd  *hcd = dev_get_drvdata(dev);
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
@@ -483,7 +486,7 @@ static int __maybe_unused xhci_plat_resume(struct device *dev)
        if (ret)
                goto disable_clks;
 
-       ret = xhci_resume(xhci, PMSG_RESUME);
+       ret = xhci_resume(xhci, pmsg);
        if (ret)
                goto disable_clks;
 
@@ -502,6 +505,16 @@ disable_clks:
        return ret;
 }
 
+static int xhci_plat_resume(struct device *dev)
+{
+       return xhci_plat_resume_common(dev, PMSG_RESUME);
+}
+
+static int xhci_plat_restore(struct device *dev)
+{
+       return xhci_plat_resume_common(dev, PMSG_RESTORE);
+}
+
 static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev)
 {
        struct usb_hcd  *hcd = dev_get_drvdata(dev);
@@ -524,7 +537,12 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
 }
 
 const struct dev_pm_ops xhci_plat_pm_ops = {
-       SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume)
+       .suspend = pm_sleep_ptr(xhci_plat_suspend),
+       .resume = pm_sleep_ptr(xhci_plat_resume),
+       .freeze = pm_sleep_ptr(xhci_plat_suspend),
+       .thaw = pm_sleep_ptr(xhci_plat_resume),
+       .poweroff = pm_sleep_ptr(xhci_plat_suspend),
+       .restore = pm_sleep_ptr(xhci_plat_restore),
 
        SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
                           xhci_plat_runtime_resume,
index f3b5e63..33806ae 100644 (file)
@@ -366,9 +366,10 @@ void xhci_ring_cmd_db(struct xhci_hcd *xhci)
        readl(&xhci->dba->doorbell[0]);
 }
 
-static bool xhci_mod_cmd_timer(struct xhci_hcd *xhci, unsigned long delay)
+static bool xhci_mod_cmd_timer(struct xhci_hcd *xhci)
 {
-       return mod_delayed_work(system_wq, &xhci->cmd_timer, delay);
+       return mod_delayed_work(system_wq, &xhci->cmd_timer,
+                       msecs_to_jiffies(xhci->current_cmd->timeout_ms));
 }
 
 static struct xhci_command *xhci_next_queued_cmd(struct xhci_hcd *xhci)
@@ -412,7 +413,7 @@ static void xhci_handle_stopped_cmd_ring(struct xhci_hcd *xhci,
        if ((xhci->cmd_ring->dequeue != xhci->cmd_ring->enqueue) &&
            !(xhci->xhc_state & XHCI_STATE_DYING)) {
                xhci->current_cmd = cur_cmd;
-               xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
+               xhci_mod_cmd_timer(xhci);
                xhci_ring_cmd_db(xhci);
        }
 }
@@ -1787,7 +1788,7 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
        if (!list_is_singular(&xhci->cmd_list)) {
                xhci->current_cmd = list_first_entry(&cmd->cmd_list,
                                                struct xhci_command, cmd_list);
-               xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
+               xhci_mod_cmd_timer(xhci);
        } else if (xhci->current_cmd == cmd) {
                xhci->current_cmd = NULL;
        }
@@ -3060,7 +3061,7 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
        writel(status, &xhci->op_regs->status);
 
        /* This is the handler of the primary interrupter */
-       ir = xhci->interrupter;
+       ir = xhci->interrupters[0];
        if (!hcd->msi_enabled) {
                u32 irq_pending;
                irq_pending = readl(&ir->ir_set->irq_pending);
@@ -4287,7 +4288,7 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
        /* if there are no other commands queued we start the timeout timer */
        if (list_empty(&xhci->cmd_list)) {
                xhci->current_cmd = cmd;
-               xhci_mod_cmd_timer(xhci, XHCI_CMD_DEFAULT_TIMEOUT);
+               xhci_mod_cmd_timer(xhci);
        }
 
        list_add_tail(&cmd->cmd_list, &xhci->cmd_list);
index 884b089..c057c42 100644 (file)
@@ -480,7 +480,7 @@ static int xhci_init(struct usb_hcd *hcd)
 
 static int xhci_run_finished(struct xhci_hcd *xhci)
 {
-       struct xhci_interrupter *ir = xhci->interrupter;
+       struct xhci_interrupter *ir = xhci->interrupters[0];
        unsigned long   flags;
        u32             temp;
 
@@ -532,7 +532,7 @@ int xhci_run(struct usb_hcd *hcd)
        u64 temp_64;
        int ret;
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-       struct xhci_interrupter *ir = xhci->interrupter;
+       struct xhci_interrupter *ir = xhci->interrupters[0];
        /* Start the xHCI host controller running only after the USB 2.0 roothub
         * is setup.
         */
@@ -596,7 +596,7 @@ void xhci_stop(struct usb_hcd *hcd)
 {
        u32 temp;
        struct xhci_hcd *xhci = hcd_to_xhci(hcd);
-       struct xhci_interrupter *ir = xhci->interrupter;
+       struct xhci_interrupter *ir = xhci->interrupters[0];
 
        mutex_lock(&xhci->mutex);
 
@@ -692,36 +692,51 @@ EXPORT_SYMBOL_GPL(xhci_shutdown);
 #ifdef CONFIG_PM
 static void xhci_save_registers(struct xhci_hcd *xhci)
 {
-       struct xhci_interrupter *ir = xhci->interrupter;
+       struct xhci_interrupter *ir;
+       unsigned int i;
 
        xhci->s3.command = readl(&xhci->op_regs->command);
        xhci->s3.dev_nt = readl(&xhci->op_regs->dev_notification);
        xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr);
        xhci->s3.config_reg = readl(&xhci->op_regs->config_reg);
 
-       if (!ir)
-               return;
+       /* save both primary and all secondary interrupters */
+       /* fixme, shold we lock  to prevent race with remove secondary interrupter? */
+       for (i = 0; i < xhci->max_interrupters; i++) {
+               ir = xhci->interrupters[i];
+               if (!ir)
+                       continue;
 
-       ir->s3_erst_size = readl(&ir->ir_set->erst_size);
-       ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
-       ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
-       ir->s3_irq_pending = readl(&ir->ir_set->irq_pending);
-       ir->s3_irq_control = readl(&ir->ir_set->irq_control);
+               ir->s3_erst_size = readl(&ir->ir_set->erst_size);
+               ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base);
+               ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue);
+               ir->s3_irq_pending = readl(&ir->ir_set->irq_pending);
+               ir->s3_irq_control = readl(&ir->ir_set->irq_control);
+       }
 }
 
 static void xhci_restore_registers(struct xhci_hcd *xhci)
 {
-       struct xhci_interrupter *ir = xhci->interrupter;
+       struct xhci_interrupter *ir;
+       unsigned int i;
 
        writel(xhci->s3.command, &xhci->op_regs->command);
        writel(xhci->s3.dev_nt, &xhci->op_regs->dev_notification);
        xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr);
        writel(xhci->s3.config_reg, &xhci->op_regs->config_reg);
-       writel(ir->s3_erst_size, &ir->ir_set->erst_size);
-       xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base);
-       xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue);
-       writel(ir->s3_irq_pending, &ir->ir_set->irq_pending);
-       writel(ir->s3_irq_control, &ir->ir_set->irq_control);
+
+       /* FIXME should we lock to protect against freeing of interrupters */
+       for (i = 0; i < xhci->max_interrupters; i++) {
+               ir = xhci->interrupters[i];
+               if (!ir)
+                       continue;
+
+               writel(ir->s3_erst_size, &ir->ir_set->erst_size);
+               xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base);
+               xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue);
+               writel(ir->s3_irq_pending, &ir->ir_set->irq_pending);
+               writel(ir->s3_irq_control, &ir->ir_set->irq_control);
+       }
 }
 
 static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci)
@@ -1084,7 +1099,7 @@ int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg)
                xhci_dbg(xhci, "// Disabling event ring interrupts\n");
                temp = readl(&xhci->op_regs->status);
                writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
-               xhci_disable_interrupter(xhci->interrupter);
+               xhci_disable_interrupter(xhci->interrupters[0]);
 
                xhci_dbg(xhci, "cleaning up memory\n");
                xhci_mem_cleanup(xhci);
@@ -1438,10 +1453,8 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci,
  * descriptor.  If the usb_device's max packet size changes after that point,
  * we need to issue an evaluate context command and wait on it.
  */
-static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
-               unsigned int ep_index, struct urb *urb, gfp_t mem_flags)
+static int xhci_check_ep0_maxpacket(struct xhci_hcd *xhci, struct xhci_virt_device *vdev)
 {
-       struct xhci_container_ctx *out_ctx;
        struct xhci_input_control_ctx *ctrl_ctx;
        struct xhci_ep_ctx *ep_ctx;
        struct xhci_command *command;
@@ -1449,11 +1462,15 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
        int hw_max_packet_size;
        int ret = 0;
 
-       out_ctx = xhci->devs[slot_id]->out_ctx;
-       ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index);
+       ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, 0);
        hw_max_packet_size = MAX_PACKET_DECODED(le32_to_cpu(ep_ctx->ep_info2));
-       max_packet_size = usb_endpoint_maxp(&urb->dev->ep0.desc);
-       if (hw_max_packet_size != max_packet_size) {
+       max_packet_size = usb_endpoint_maxp(&vdev->udev->ep0.desc);
+
+       if (hw_max_packet_size == max_packet_size)
+               return 0;
+
+       switch (max_packet_size) {
+       case 8: case 16: case 32: case 64: case 9:
                xhci_dbg_trace(xhci,  trace_xhci_dbg_context_change,
                                "Max Packet Size for ep 0 changed.");
                xhci_dbg_trace(xhci,  trace_xhci_dbg_context_change,
@@ -1465,28 +1482,22 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
                xhci_dbg_trace(xhci,  trace_xhci_dbg_context_change,
                                "Issuing evaluate context command.");
 
-               /* Set up the input context flags for the command */
-               /* FIXME: This won't work if a non-default control endpoint
-                * changes max packet sizes.
-                */
-
-               command = xhci_alloc_command(xhci, true, mem_flags);
+               command = xhci_alloc_command(xhci, true, GFP_KERNEL);
                if (!command)
                        return -ENOMEM;
 
-               command->in_ctx = xhci->devs[slot_id]->in_ctx;
+               command->in_ctx = vdev->in_ctx;
                ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx);
                if (!ctrl_ctx) {
                        xhci_warn(xhci, "%s: Could not get input context, bad type.\n",
                                        __func__);
                        ret = -ENOMEM;
-                       goto command_cleanup;
+                       break;
                }
                /* Set up the modified control endpoint 0 */
-               xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx,
-                               xhci->devs[slot_id]->out_ctx, ep_index);
+               xhci_endpoint_copy(xhci, vdev->in_ctx, vdev->out_ctx, 0);
 
-               ep_ctx = xhci_get_ep_ctx(xhci, command->in_ctx, ep_index);
+               ep_ctx = xhci_get_ep_ctx(xhci, command->in_ctx, 0);
                ep_ctx->ep_info &= cpu_to_le32(~EP_STATE_MASK);/* must clear */
                ep_ctx->ep_info2 &= cpu_to_le32(~MAX_PACKET_MASK);
                ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet_size));
@@ -1494,17 +1505,20 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id,
                ctrl_ctx->add_flags = cpu_to_le32(EP0_FLAG);
                ctrl_ctx->drop_flags = 0;
 
-               ret = xhci_configure_endpoint(xhci, urb->dev, command,
-                               true, false);
-
-               /* Clean up the input context for later use by bandwidth
-                * functions.
-                */
+               ret = xhci_configure_endpoint(xhci, vdev->udev, command,
+                                             true, false);
+               /* Clean up the input context for later use by bandwidth functions */
                ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG);
-command_cleanup:
-               kfree(command->completion);
-               kfree(command);
+               break;
+       default:
+               dev_dbg(&vdev->udev->dev, "incorrect max packet size %d for ep0\n",
+                       max_packet_size);
+               return -EINVAL;
        }
+
+       kfree(command->completion);
+       kfree(command);
+
        return ret;
 }
 
@@ -1522,24 +1536,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
        struct urb_priv *urb_priv;
        int num_tds;
 
-       if (!urb)
-               return -EINVAL;
-       ret = xhci_check_args(hcd, urb->dev, urb->ep,
-                                       true, true, __func__);
-       if (ret <= 0)
-               return ret ? ret : -EINVAL;
-
-       slot_id = urb->dev->slot_id;
        ep_index = xhci_get_endpoint_index(&urb->ep->desc);
-       ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state;
-
-       if (!HCD_HW_ACCESSIBLE(hcd))
-               return -ESHUTDOWN;
-
-       if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
-               xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
-               return -ENODEV;
-       }
 
        if (usb_endpoint_xfer_isoc(&urb->ep->desc))
                num_tds = urb->number_of_packets;
@@ -1561,22 +1558,27 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 
        trace_xhci_urb_enqueue(urb);
 
-       if (usb_endpoint_xfer_control(&urb->ep->desc)) {
-               /* Check to see if the max packet size for the default control
-                * endpoint changed during FS device enumeration
-                */
-               if (urb->dev->speed == USB_SPEED_FULL) {
-                       ret = xhci_check_maxpacket(xhci, slot_id,
-                                       ep_index, urb, mem_flags);
-                       if (ret < 0) {
-                               xhci_urb_free_priv(urb_priv);
-                               urb->hcpriv = NULL;
-                               return ret;
-                       }
-               }
+       spin_lock_irqsave(&xhci->lock, flags);
+
+       ret = xhci_check_args(hcd, urb->dev, urb->ep,
+                             true, true, __func__);
+       if (ret <= 0) {
+               ret = ret ? ret : -EINVAL;
+               goto free_priv;
        }
 
-       spin_lock_irqsave(&xhci->lock, flags);
+       slot_id = urb->dev->slot_id;
+
+       if (!HCD_HW_ACCESSIBLE(hcd)) {
+               ret = -ESHUTDOWN;
+               goto free_priv;
+       }
+
+       if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
+               xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
+               ret = -ENODEV;
+               goto free_priv;
+       }
 
        if (xhci->xhc_state & XHCI_STATE_DYING) {
                xhci_dbg(xhci, "Ep 0x%x: URB %p submitted for non-responsive xHCI host.\n",
@@ -1584,6 +1586,9 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
                ret = -ESHUTDOWN;
                goto free_priv;
        }
+
+       ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state;
+
        if (*ep_state & (EP_GETTING_STREAMS | EP_GETTING_NO_STREAMS)) {
                xhci_warn(xhci, "WARN: Can't enqueue URB, ep in streams transition state %x\n",
                          *ep_state);
@@ -3087,6 +3092,9 @@ done:
  * of an endpoint that isn't in the halted state this function will issue a
  * configure endpoint command with the Drop and Add bits set for the target
  * endpoint. Refer to the additional note in xhci spcification section 4.6.8.
+ *
+ * vdev may be lost due to xHC restore error and re-initialization during S3/S4
+ * resume. A new vdev will be allocated later by xhci_discover_or_reset_device()
  */
 
 static void xhci_endpoint_reset(struct usb_hcd *hcd,
@@ -3104,19 +3112,37 @@ static void xhci_endpoint_reset(struct usb_hcd *hcd,
        int err;
 
        xhci = hcd_to_xhci(hcd);
+       ep_index = xhci_get_endpoint_index(&host_ep->desc);
+
+       /*
+        * Usb core assumes a max packet value for ep0 on FS devices until the
+        * real value is read from the descriptor. Core resets Ep0 if values
+        * mismatch. Reconfigure the xhci ep0 endpoint context here in that case
+        */
+       if (usb_endpoint_xfer_control(&host_ep->desc) && ep_index == 0) {
+
+               udev = container_of(host_ep, struct usb_device, ep0);
+               if (udev->speed != USB_SPEED_FULL || !udev->slot_id)
+                       return;
+
+               vdev = xhci->devs[udev->slot_id];
+               if (!vdev || vdev->udev != udev)
+                       return;
+
+               xhci_check_ep0_maxpacket(xhci, vdev);
+
+               /* Nothing else should be done here for ep0 during ep reset */
+               return;
+       }
+
        if (!host_ep->hcpriv)
                return;
        udev = (struct usb_device *) host_ep->hcpriv;
        vdev = xhci->devs[udev->slot_id];
 
-       /*
-        * vdev may be lost due to xHC restore error and re-initialization
-        * during S3/S4 resume. A new vdev will be allocated later by
-        * xhci_discover_or_reset_device()
-        */
        if (!udev->slot_id || !vdev)
                return;
-       ep_index = xhci_get_endpoint_index(&host_ep->desc);
+
        ep = &vdev->eps[ep_index];
 
        /* Bail out if toggle is already being cleared by a endpoint reset */
@@ -4029,12 +4055,18 @@ disable_slot:
        return 0;
 }
 
-/*
- * Issue an Address Device command and optionally send a corresponding
- * SetAddress request to the device.
+/**
+ * xhci_setup_device - issues an Address Device command to assign a unique
+ *                     USB bus address.
+ * @hcd: USB host controller data structure.
+ * @udev: USB dev structure representing the connected device.
+ * @setup: Enum specifying setup mode: address only or with context.
+ * @timeout_ms: Max wait time (ms) for the command operation to complete.
+ *
+ * Return: 0 if successful; otherwise, negative error code.
  */
 static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
-                            enum xhci_setup_dev setup)
+                            enum xhci_setup_dev setup, unsigned int timeout_ms)
 {
        const char *act = setup == SETUP_CONTEXT_ONLY ? "context" : "address";
        unsigned long flags;
@@ -4091,6 +4123,7 @@ static int xhci_setup_device(struct usb_hcd *hcd, struct usb_device *udev,
        }
 
        command->in_ctx = virt_dev->in_ctx;
+       command->timeout_ms = timeout_ms;
 
        slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx);
        ctrl_ctx = xhci_get_input_control_ctx(virt_dev->in_ctx);
@@ -4217,14 +4250,16 @@ out:
        return ret;
 }
 
-static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev)
+static int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev,
+                              unsigned int timeout_ms)
 {
-       return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS);
+       return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ADDRESS, timeout_ms);
 }
 
 static int xhci_enable_device(struct usb_hcd *hcd, struct usb_device *udev)
 {
-       return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY);
+       return xhci_setup_device(hcd, udev, SETUP_CONTEXT_ONLY,
+                                XHCI_CMD_DEFAULT_TIMEOUT);
 }
 
 /*
index 3ea5c09..a5c72a6 100644 (file)
@@ -791,6 +791,8 @@ struct xhci_command {
        struct completion               *completion;
        union xhci_trb                  *command_trb;
        struct list_head                cmd_list;
+       /* xHCI command response timeout in milliseconds */
+       unsigned int                    timeout_ms;
 };
 
 /* drop context bitmasks */
@@ -1550,8 +1552,11 @@ struct xhci_td {
        unsigned int            num_trbs;
 };
 
-/* xHCI command default timeout value */
-#define XHCI_CMD_DEFAULT_TIMEOUT       (5 * HZ)
+/*
+ * xHCI command default timeout value in milliseconds.
+ * USB 3.2 spec, section 9.2.6.1
+ */
+#define XHCI_CMD_DEFAULT_TIMEOUT       5000
 
 /* command descriptor */
 struct xhci_cd {
@@ -1760,8 +1765,8 @@ struct xhci_hcd {
        int             page_size;
        /* Valid values are 12 to 20, inclusive */
        int             page_shift;
-       /* msi-x vectors */
-       int             msix_count;
+       /* MSI-X/MSI vectors */
+       int             nvecs;
        /* optional clocks */
        struct clk              *clk;
        struct clk              *reg_clk;
@@ -1769,7 +1774,7 @@ struct xhci_hcd {
        struct reset_control *reset;
        /* data structures */
        struct xhci_device_context_array *dcbaa;
-       struct xhci_interrupter *interrupter;
+       struct xhci_interrupter **interrupters;
        struct xhci_ring        *cmd_ring;
        unsigned int            cmd_ring_state;
 #define CMD_RING_STATE_RUNNING         (1 << 0)
@@ -2080,6 +2085,10 @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci,
                int type, gfp_t flags);
 void xhci_free_container_ctx(struct xhci_hcd *xhci,
                struct xhci_container_ctx *ctx);
+struct xhci_interrupter *
+xhci_create_secondary_interrupter(struct usb_hcd *hcd, int num_seg);
+void xhci_remove_secondary_interrupter(struct usb_hcd
+                                      *hcd, struct xhci_interrupter *ir);
 
 /* xHCI host controller glue */
 typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
index 1e3df27..6d28467 100644 (file)
@@ -501,7 +501,6 @@ static long iowarrior_ioctl(struct file *file, unsigned int cmd,
                dev->minor, cmd, arg);
 
        retval = 0;
-       io_res = 0;
        switch (cmd) {
        case IOW_WRITE:
                if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 ||
index 2b45404..0dd2b03 100644 (file)
@@ -5,8 +5,10 @@
  * Copyright (c) 2022, Google LLC
  */
 
+#include <linux/clk.h>
 #include <linux/device.h>
 #include <linux/export.h>
+#include <linux/err.h>
 #include <linux/gpio/consumer.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
@@ -60,15 +62,22 @@ struct onboard_hub {
        bool going_away;
        struct list_head udev_list;
        struct mutex lock;
+       struct clk *clk;
 };
 
 static int onboard_hub_power_on(struct onboard_hub *hub)
 {
        int err;
 
+       err = clk_prepare_enable(hub->clk);
+       if (err) {
+               dev_err(hub->dev, "failed to enable clock: %pe\n", ERR_PTR(err));
+               return err;
+       }
+
        err = regulator_bulk_enable(hub->pdata->num_supplies, hub->supplies);
        if (err) {
-               dev_err(hub->dev, "failed to enable supplies: %d\n", err);
+               dev_err(hub->dev, "failed to enable supplies: %pe\n", ERR_PTR(err));
                return err;
        }
 
@@ -88,10 +97,12 @@ static int onboard_hub_power_off(struct onboard_hub *hub)
 
        err = regulator_bulk_disable(hub->pdata->num_supplies, hub->supplies);
        if (err) {
-               dev_err(hub->dev, "failed to disable supplies: %d\n", err);
+               dev_err(hub->dev, "failed to disable supplies: %pe\n", ERR_PTR(err));
                return err;
        }
 
+       clk_disable_unprepare(hub->clk);
+
        hub->is_powered_on = false;
 
        return 0;
@@ -233,9 +244,9 @@ static void onboard_hub_attach_usb_driver(struct work_struct *work)
 {
        int err;
 
-       err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver);
+       err = driver_attach(&onboard_hub_usbdev_driver.driver);
        if (err)
-               pr_err("Failed to attach USB driver: %d\n", err);
+               pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err));
 }
 
 static int onboard_hub_probe(struct platform_device *pdev)
@@ -262,10 +273,14 @@ static int onboard_hub_probe(struct platform_device *pdev)
 
        err = devm_regulator_bulk_get(dev, hub->pdata->num_supplies, hub->supplies);
        if (err) {
-               dev_err(dev, "Failed to get regulator supplies: %d\n", err);
+               dev_err(dev, "Failed to get regulator supplies: %pe\n", ERR_PTR(err));
                return err;
        }
 
+       hub->clk = devm_clk_get_optional(dev, NULL);
+       if (IS_ERR(hub->clk))
+               return dev_err_probe(dev, PTR_ERR(hub->clk), "failed to get clock\n");
+
        hub->reset_gpio = devm_gpiod_get_optional(dev, "reset",
                                                  GPIOD_OUT_HIGH);
        if (IS_ERR(hub->reset_gpio))
@@ -426,6 +441,7 @@ static void onboard_hub_usbdev_disconnect(struct usb_device *udev)
 static const struct usb_device_id onboard_hub_id_table[] = {
        { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB33{0,1,2}x/CYUSB230x 3.0 */
        { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB33{0,1,2}x/CYUSB230x 2.0 */
+       { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6570) }, /* CY7C6563x 2.0 */
        { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */
        { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */
        { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 */
index 292110e..f360d5c 100644 (file)
@@ -36,6 +36,11 @@ static const struct onboard_hub_pdata cypress_hx3_data = {
        .num_supplies = 2,
 };
 
+static const struct onboard_hub_pdata cypress_hx2vl_data = {
+       .reset_us = 1,
+       .num_supplies = 1,
+};
+
 static const struct onboard_hub_pdata genesys_gl850g_data = {
        .reset_us = 3,
        .num_supplies = 1,
@@ -61,6 +66,7 @@ static const struct of_device_id onboard_hub_match[] = {
        { .compatible = "usb451,8142", .data = &ti_tusb8041_data, },
        { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, },
        { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, },
+       { .compatible = "usb4b4,6570", .data = &cypress_hx2vl_data, },
        { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
        { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
        { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
index 7f371ea..26e9b87 100644 (file)
@@ -205,6 +205,9 @@ static int eud_probe(struct platform_device *pdev)
                return PTR_ERR(chip->mode_mgr);
 
        chip->irq = platform_get_irq(pdev, 0);
+       if (chip->irq < 0)
+               return chip->irq;
+
        ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
                        handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
        if (ret)
index c640f98..9a0649d 100644 (file)
@@ -34,6 +34,8 @@
 #define YUREX_BUF_SIZE         8
 #define YUREX_WRITE_TIMEOUT    (HZ*2)
 
+#define MAX_S64_STRLEN 20 /* {-}922337203685477580{7,8} */
+
 /* table of devices that work with this driver */
 static struct usb_device_id yurex_table[] = {
        { USB_DEVICE(YUREX_VENDOR_ID, YUREX_PRODUCT_ID) },
@@ -401,7 +403,7 @@ static ssize_t yurex_read(struct file *file, char __user *buffer, size_t count,
 {
        struct usb_yurex *dev;
        int len = 0;
-       char in_buffer[20];
+       char in_buffer[MAX_S64_STRLEN];
        unsigned long flags;
 
        dev = file->private_data;
@@ -412,14 +414,16 @@ static ssize_t yurex_read(struct file *file, char __user *buffer, size_t count,
                return -ENODEV;
        }
 
+       if (WARN_ON_ONCE(dev->bbu > S64_MAX || dev->bbu < S64_MIN)) {
+               mutex_unlock(&dev->io_mutex);
+               return -EIO;
+       }
+
        spin_lock_irqsave(&dev->lock, flags);
-       len = snprintf(in_buffer, 20, "%lld\n", dev->bbu);
+       scnprintf(in_buffer, MAX_S64_STRLEN, "%lld\n", dev->bbu);
        spin_unlock_irqrestore(&dev->lock, flags);
        mutex_unlock(&dev->io_mutex);
 
-       if (WARN_ON_ONCE(len >= sizeof(in_buffer)))
-               return -EIO;
-
        return simple_read_from_buffer(buffer, count, ppos, in_buffer, len);
 }
 
index 9ca9305..4e30de4 100644 (file)
@@ -1250,14 +1250,19 @@ static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf)
        struct mon_reader_bin *rp = vmf->vma->vm_private_data;
        unsigned long offset, chunk_idx;
        struct page *pageptr;
+       unsigned long flags;
 
+       spin_lock_irqsave(&rp->b_lock, flags);
        offset = vmf->pgoff << PAGE_SHIFT;
-       if (offset >= rp->b_size)
+       if (offset >= rp->b_size) {
+               spin_unlock_irqrestore(&rp->b_lock, flags);
                return VM_FAULT_SIGBUS;
+       }
        chunk_idx = offset / CHUNK_SIZE;
        pageptr = rp->b_vec[chunk_idx].pg;
        get_page(pageptr);
        vmf->page = pageptr;
+       spin_unlock_irqrestore(&rp->b_lock, flags);
        return 0;
 }
 
index 98ab0cc..3c23805 100644 (file)
@@ -35,9 +35,9 @@ static int mon_stat_open(struct inode *inode, struct file *file)
 
        mbus = inode->i_private;
 
-       sp->slen = snprintf(sp->str, STAT_BUF_SIZE,
-           "nreaders %d events %u text_lost %u\n",
-           mbus->nreaders, mbus->cnt_events, mbus->cnt_text_lost);
+       sp->slen = scnprintf(sp->str, STAT_BUF_SIZE,
+                            "nreaders %d events %u text_lost %u\n",
+                            mbus->nreaders, mbus->cnt_events, mbus->cnt_text_lost);
 
        file->private_data = sp;
        return 0;
index 39cb141..2fe9b95 100644 (file)
@@ -352,7 +352,7 @@ static int mon_text_open(struct inode *inode, struct file *file)
        rp->r.rnf_error = mon_text_error;
        rp->r.rnf_complete = mon_text_complete;
 
-       snprintf(rp->slab_name, SLAB_NAME_SZ, "mon_text_%p", rp);
+       scnprintf(rp->slab_name, SLAB_NAME_SZ, "mon_text_%p", rp);
        rp->e_slab = kmem_cache_create(rp->slab_name,
            sizeof(struct mon_event_text), sizeof(long), 0,
            mon_text_ctor);
@@ -700,46 +700,28 @@ static const struct file_operations mon_fops_text_u = {
 
 int mon_text_add(struct mon_bus *mbus, const struct usb_bus *ubus)
 {
-       enum { NAMESZ = 10 };
+       enum { NAMESZ = 12 };
        char name[NAMESZ];
        int busnum = ubus? ubus->busnum: 0;
-       int rc;
 
        if (mon_dir == NULL)
                return 0;
 
        if (ubus != NULL) {
-               rc = snprintf(name, NAMESZ, "%dt", busnum);
-               if (rc <= 0 || rc >= NAMESZ)
-                       goto err_print_t;
+               scnprintf(name, NAMESZ, "%dt", busnum);
                mbus->dent_t = debugfs_create_file(name, 0600, mon_dir, mbus,
                                                             &mon_fops_text_t);
        }
 
-       rc = snprintf(name, NAMESZ, "%du", busnum);
-       if (rc <= 0 || rc >= NAMESZ)
-               goto err_print_u;
+       scnprintf(name, NAMESZ, "%du", busnum);
        mbus->dent_u = debugfs_create_file(name, 0600, mon_dir, mbus,
                                           &mon_fops_text_u);
 
-       rc = snprintf(name, NAMESZ, "%ds", busnum);
-       if (rc <= 0 || rc >= NAMESZ)
-               goto err_print_s;
+       scnprintf(name, NAMESZ, "%ds", busnum);
        mbus->dent_s = debugfs_create_file(name, 0600, mon_dir, mbus,
                                           &mon_fops_stat);
 
        return 1;
-
-err_print_s:
-       debugfs_remove(mbus->dent_u);
-       mbus->dent_u = NULL;
-err_print_u:
-       if (ubus != NULL) {
-               debugfs_remove(mbus->dent_t);
-               mbus->dent_t = NULL;
-       }
-err_print_t:
-       return 0;
 }
 
 void mon_text_del(struct mon_bus *mbus)
index 770081b..9ab50f2 100644 (file)
@@ -46,15 +46,21 @@ EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
 static int nop_set_suspend(struct usb_phy *x, int suspend)
 {
        struct usb_phy_generic *nop = dev_get_drvdata(x->dev);
+       int ret = 0;
 
-       if (!IS_ERR(nop->clk)) {
-               if (suspend)
+       if (suspend) {
+               if (!IS_ERR(nop->clk))
                        clk_disable_unprepare(nop->clk);
-               else
+               if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
+                       ret = regulator_disable(nop->vcc);
+       } else {
+               if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
+                       ret = regulator_enable(nop->vcc);
+               if (!IS_ERR(nop->clk))
                        clk_prepare_enable(nop->clk);
        }
 
-       return 0;
+       return ret;
 }
 
 static void nop_reset(struct usb_phy_generic *nop)
index acd46b7..920a32c 100644 (file)
@@ -388,8 +388,7 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
 
 static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy)
 {
-       return IS_ENABLED(CONFIG_USB_OTG) &&
-               mxs_phy->phy.last_event == USB_EVENT_ID;
+       return mxs_phy->phy.last_event == USB_EVENT_ID;
 }
 
 static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
index c3ce6b1..da09cff 100644 (file)
@@ -179,16 +179,16 @@ static ssize_t vbus_show(struct device *dev,
 
        switch (twl->linkstat) {
        case MUSB_VBUS_VALID:
-              ret = snprintf(buf, PAGE_SIZE, "vbus\n");
+              ret = sysfs_emit(buf, "vbus\n");
               break;
        case MUSB_ID_GROUND:
-              ret = snprintf(buf, PAGE_SIZE, "id\n");
+              ret = sysfs_emit(buf, "id\n");
               break;
        case MUSB_VBUS_OFF:
-              ret = snprintf(buf, PAGE_SIZE, "none\n");
+              ret = sysfs_emit(buf, "none\n");
               break;
        default:
-              ret = snprintf(buf, PAGE_SIZE, "UNKNOWN\n");
+              ret = sysfs_emit(buf, "UNKNOWN\n");
        }
        spin_unlock_irqrestore(&twl->lock, flags);
 
index 3eb8dc3..6c812d0 100644 (file)
@@ -113,7 +113,7 @@ static ssize_t new_id_store(struct device_driver *driver,
        if (retval >= 0 && usb_drv->usb_driver != NULL)
                retval = usb_store_new_id(&usb_drv->usb_driver->dynids,
                                          usb_drv->usb_driver->id_table,
-                                         &usb_drv->usb_driver->drvwrap.driver,
+                                         &usb_drv->usb_driver->driver,
                                          buf, count);
        return retval;
 }
index 17b09f0..f1e91eb 100644 (file)
@@ -1521,7 +1521,7 @@ int usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers[]
 
        /* Now set udriver's id_table and look for matches */
        udriver->id_table = id_table;
-       rc = driver_attach(&udriver->drvwrap.driver);
+       rc = driver_attach(&udriver->driver);
        return 0;
 
 err_deregister_drivers:
index 0774ba2..177fa6c 100644 (file)
@@ -98,26 +98,26 @@ static ssize_t truinst_show(struct device *dev, struct device_attribute *attr,
        struct usb_device *udev = interface_to_usbdev(intf);
        int result;
        if (swi_tru_install == TRU_FORCE_MS) {
-               result = snprintf(buf, PAGE_SIZE, "Forced Mass Storage\n");
+               result = sysfs_emit(buf, "Forced Mass Storage\n");
        } else {
                swocInfo = kmalloc(sizeof(struct swoc_info), GFP_KERNEL);
                if (!swocInfo) {
-                       snprintf(buf, PAGE_SIZE, "Error\n");
+                       sysfs_emit(buf, "Error\n");
                        return -ENOMEM;
                }
                result = sierra_get_swoc_info(udev, swocInfo);
                if (result < 0) {
                        dev_dbg(dev, "SWIMS: failed SWoC query\n");
                        kfree(swocInfo);
-                       snprintf(buf, PAGE_SIZE, "Error\n");
+                       sysfs_emit(buf, "Error\n");
                        return -EIO;
                }
                debug_swoc(dev, swocInfo);
-               result = snprintf(buf, PAGE_SIZE,
-                       "REV=%02d SKU=%04X VER=%04X\n",
-                       swocInfo->rev,
-                       swocInfo->LinuxSKU,
-                       swocInfo->LinuxVer);
+               result = sysfs_emit(buf,
+                                   "REV=%02d SKU=%04X VER=%04X\n",
+                                   swocInfo->rev,
+                                   swocInfo->LinuxSKU,
+                                   swocInfo->LinuxVer);
                kfree(swocInfo);
        }
        return result;
index 696bb0b..9707f53 100644 (file)
@@ -1246,7 +1246,7 @@ static struct usb_driver uas_driver = {
        .suspend = uas_suspend,
        .resume = uas_resume,
        .reset_resume = uas_reset_resume,
-       .drvwrap.driver.shutdown = uas_shutdown,
+       .driver.shutdown = uas_shutdown,
        .id_table = uas_usb_ids,
 };
 
index 16a6708..015aa92 100644 (file)
@@ -263,11 +263,13 @@ static void typec_altmode_put_partner(struct altmode *altmode)
 {
        struct altmode *partner = altmode->partner;
        struct typec_altmode *adev;
+       struct typec_altmode *partner_adev;
 
        if (!partner)
                return;
 
        adev = &altmode->adev;
+       partner_adev = &partner->adev;
 
        if (is_typec_plug(adev->dev.parent)) {
                struct typec_plug *plug = to_typec_plug(adev->dev.parent);
@@ -276,7 +278,7 @@ static void typec_altmode_put_partner(struct altmode *altmode)
        } else {
                partner->partner = NULL;
        }
-       put_device(&adev->dev);
+       put_device(&partner_adev->dev);
 }
 
 /**
@@ -476,7 +478,7 @@ static int altmode_id_get(struct device *dev)
        else
                ids = &to_typec_port(dev)->mode_ids;
 
-       return ida_simple_get(ids, 0, 0, GFP_KERNEL);
+       return ida_alloc(ids, GFP_KERNEL);
 }
 
 static void altmode_id_remove(struct device *dev, int id)
@@ -490,7 +492,7 @@ static void altmode_id_remove(struct device *dev, int id)
        else
                ids = &to_typec_port(dev)->mode_ids;
 
-       ida_simple_remove(ids, id);
+       ida_free(ids, id);
 }
 
 static void typec_altmode_release(struct device *dev)
@@ -1798,7 +1800,7 @@ static void typec_release(struct device *dev)
 {
        struct typec_port *port = to_typec_port(dev);
 
-       ida_simple_remove(&typec_index_ida, port->id);
+       ida_free(&typec_index_ida, port->id);
        ida_destroy(&port->mode_ids);
        typec_switch_put(port->sw);
        typec_mux_put(port->mux);
@@ -2231,7 +2233,8 @@ void typec_port_register_altmodes(struct typec_port *port,
        struct typec_altmode_desc desc;
        struct typec_altmode *alt;
        size_t index = 0;
-       u32 svid, vdo;
+       u16 svid;
+       u32 vdo;
        int ret;
 
        altmodes_node = device_get_named_child_node(&port->dev, "altmodes");
@@ -2239,7 +2242,7 @@ void typec_port_register_altmodes(struct typec_port *port,
                return; /* No altmodes specified */
 
        fwnode_for_each_child_node(altmodes_node, child) {
-               ret = fwnode_property_read_u32(child, "svid", &svid);
+               ret = fwnode_property_read_u16(child, "svid", &svid);
                if (ret) {
                        dev_err(&port->dev, "Error reading svid for altmode %s\n",
                                fwnode_get_name(child));
@@ -2297,7 +2300,7 @@ struct typec_port *typec_register_port(struct device *parent,
        if (!port)
                return ERR_PTR(-ENOMEM);
 
-       id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
+       id = ida_alloc(&typec_index_ida, GFP_KERNEL);
        if (id < 0) {
                kfree(port);
                return ERR_PTR(id);
index 38416fb..d2cb5e7 100644 (file)
@@ -56,4 +56,14 @@ config TYPEC_MUX_PTN36502
          Say Y or M if your system has a NXP PTN36502 Type-C redriver chip
          found on some devices with a Type-C port.
 
+config TYPEC_MUX_WCD939X_USBSS
+       tristate "Qualcomm WCD939x USBSS Analog Audio Switch driver"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Driver for the Qualcomm WCD939x Audio Codec USBSS domain which
+         provides support for muxing analog audio and sideband signals on a
+         common USB Type-C connector.
+         If compiled as a module, the module will be named wcd939x-usbss.
+
 endmenu
index 9d6a555..57dc9ac 100644 (file)
@@ -6,3 +6,4 @@ obj-$(CONFIG_TYPEC_MUX_PI3USB30532)     += pi3usb30532.o
 obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)      += intel_pmc_mux.o
 obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)     += nb7vpq904m.o
 obj-$(CONFIG_TYPEC_MUX_PTN36502)       += ptn36502.o
+obj-$(CONFIG_TYPEC_MUX_WCD939X_USBSS)  += wcd939x-usbss.o
diff --git a/drivers/usb/typec/mux/wcd939x-usbss.c b/drivers/usb/typec/mux/wcd939x-usbss.c
new file mode 100644 (file)
index 0000000..d46c353
--- /dev/null
@@ -0,0 +1,779 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (C) 2023 Linaro Ltd.
+ */
+
+#include <linux/bits.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/bitfield.h>
+#include <linux/gpio/consumer.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+
+#define WCD_USBSS_PMP_OUT1                     0x2
+
+#define WCD_USBSS_DP_DN_MISC1                  0x20
+
+#define WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN                        BIT(3)
+#define WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN                        BIT(0)
+
+#define WCD_USBSS_MG1_EN                       0x24
+
+#define WCD_USBSS_MG1_EN_CT_SNS_EN                                     BIT(1)
+
+#define WCD_USBSS_MG1_BIAS                     0x25
+
+#define WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN                            BIT(3)
+
+#define WCD_USBSS_MG1_MISC                     0x27
+
+#define WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN                      BIT(5)
+
+#define WCD_USBSS_MG2_EN                       0x28
+
+#define WCD_USBSS_MG2_EN_CT_SNS_EN                                     BIT(1)
+
+#define WCD_USBSS_MG2_BIAS                     0x29
+
+#define WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN                            BIT(3)
+
+#define WCD_USBSS_MG2_MISC                     0x30
+
+#define WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN                      BIT(5)
+
+#define WCD_USBSS_DISP_AUXP_THRESH             0x80
+
+#define WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM                  GENMASK(7, 5)
+
+#define WCD_USBSS_DISP_AUXP_CTL                        0x81
+
+#define WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF                    GENMASK(2, 0)
+
+#define WCD_USBSS_CPLDO_CTL2                   0xa1
+
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE       0x403
+
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE                 BIT(7)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES       BIT(6)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES       BIT(5)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES                  BIT(4)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES                  BIT(3)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES                        BIT(2)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES                  BIT(1)
+#define WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES                 BIT(0)
+
+#define WCD_USBSS_SWITCH_SELECT0               0x404
+
+#define WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES                      BIT(7)  /* 1-> MG2 */
+#define WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES                      BIT(6)  /* 1-> MG2 */
+#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES                          GENMASK(5, 4)
+#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES                          GENMASK(3, 2)
+#define WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES                                BIT(1)  /* 1-> SBU2 */
+#define WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES                          BIT(0)  /* 1-> MG2 */
+
+#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L          0
+#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN         1
+#define WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN2                2
+
+#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R          0
+#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP         1
+#define WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DR2                2
+
+#define WCD_USBSS_SWITCH_SELECT1               0x405
+
+#define WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES                         BIT(0)  /* 1-> MG2 */
+
+#define WCD_USBSS_DELAY_R_SW                   0x40d
+#define WCD_USBSS_DELAY_MIC_SW                 0x40e
+#define WCD_USBSS_DELAY_SENSE_SW               0x40f
+#define WCD_USBSS_DELAY_GND_SW                 0x410
+#define WCD_USBSS_DELAY_L_SW                   0x411
+
+#define WCD_USBSS_FUNCTION_ENABLE              0x413
+
+#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT                                GENMASK(1, 0)
+
+#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL         1
+#define WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_AUDIO_FSM      2
+
+#define WCD_USBSS_EQUALIZER1                   0x415
+
+#define WCD_USBSS_EQUALIZER1_EQ_EN                                     BIT(7)
+#define WCD_USBSS_EQUALIZER1_BW_SETTINGS                               GENMASK(6, 3)
+
+#define WCD_USBSS_USB_SS_CNTL                  0x419
+
+#define WCD_USBSS_USB_SS_CNTL_STANDBY_STATE                            BIT(4)
+#define WCD_USBSS_USB_SS_CNTL_RCO_EN                                   BIT(3)
+#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE                              GENMASK(2, 0)
+
+#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC         2
+#define WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB          5
+
+#define WCD_USBSS_AUDIO_FSM_START              0x433
+
+#define WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG                 BIT(0)
+
+#define WCD_USBSS_RATIO_SPKR_REXT_L_LSB                0x461
+#define WCD_USBSS_RATIO_SPKR_REXT_L_MSB                0x462
+#define WCD_USBSS_RATIO_SPKR_REXT_R_LSB                0x463
+#define WCD_USBSS_RATIO_SPKR_REXT_R_MSB                0x464
+#define WCD_USBSS_AUD_COEF_L_K0_0              0x475
+#define WCD_USBSS_AUD_COEF_L_K0_1              0x476
+#define WCD_USBSS_AUD_COEF_L_K0_2              0x477
+#define WCD_USBSS_AUD_COEF_L_K1_0              0x478
+#define WCD_USBSS_AUD_COEF_L_K1_1              0x479
+#define WCD_USBSS_AUD_COEF_L_K2_0              0x47a
+#define WCD_USBSS_AUD_COEF_L_K2_1              0x47b
+#define WCD_USBSS_AUD_COEF_L_K3_0              0x47c
+#define WCD_USBSS_AUD_COEF_L_K3_1              0x47d
+#define WCD_USBSS_AUD_COEF_L_K4_0              0x47e
+#define WCD_USBSS_AUD_COEF_L_K4_1              0x47f
+#define WCD_USBSS_AUD_COEF_L_K5_0              0x480
+#define WCD_USBSS_AUD_COEF_L_K5_1              0x481
+#define WCD_USBSS_AUD_COEF_R_K0_0              0x482
+#define WCD_USBSS_AUD_COEF_R_K0_1              0x483
+#define WCD_USBSS_AUD_COEF_R_K0_2              0x484
+#define WCD_USBSS_AUD_COEF_R_K1_0              0x485
+#define WCD_USBSS_AUD_COEF_R_K1_1              0x486
+#define WCD_USBSS_AUD_COEF_R_K2_0              0x487
+#define WCD_USBSS_AUD_COEF_R_K2_1              0x488
+#define WCD_USBSS_AUD_COEF_R_K3_0              0x489
+#define WCD_USBSS_AUD_COEF_R_K3_1              0x48a
+#define WCD_USBSS_AUD_COEF_R_K4_0              0x48b
+#define WCD_USBSS_AUD_COEF_R_K4_1              0x48c
+#define WCD_USBSS_AUD_COEF_R_K5_0              0x48d
+#define WCD_USBSS_AUD_COEF_R_K5_1              0x48e
+#define WCD_USBSS_GND_COEF_L_K0_0              0x48f
+#define WCD_USBSS_GND_COEF_L_K0_1              0x490
+#define WCD_USBSS_GND_COEF_L_K0_2              0x491
+#define WCD_USBSS_GND_COEF_L_K1_0              0x492
+#define WCD_USBSS_GND_COEF_L_K1_1              0x493
+#define WCD_USBSS_GND_COEF_L_K2_0              0x494
+#define WCD_USBSS_GND_COEF_L_K2_1              0x495
+#define WCD_USBSS_GND_COEF_L_K3_0              0x496
+#define WCD_USBSS_GND_COEF_L_K3_1              0x497
+#define WCD_USBSS_GND_COEF_L_K4_0              0x498
+#define WCD_USBSS_GND_COEF_L_K4_1              0x499
+#define WCD_USBSS_GND_COEF_L_K5_0              0x49a
+#define WCD_USBSS_GND_COEF_L_K5_1              0x49b
+#define WCD_USBSS_GND_COEF_R_K0_0              0x49c
+#define WCD_USBSS_GND_COEF_R_K0_1              0x49d
+#define WCD_USBSS_GND_COEF_R_K0_2              0x49e
+#define WCD_USBSS_GND_COEF_R_K1_0              0x49f
+#define WCD_USBSS_GND_COEF_R_K1_1              0x4a0
+#define WCD_USBSS_GND_COEF_R_K2_0              0x4a1
+#define WCD_USBSS_GND_COEF_R_K2_1              0x4a2
+#define WCD_USBSS_GND_COEF_R_K3_0              0x4a3
+#define WCD_USBSS_GND_COEF_R_K3_1              0x4a4
+#define WCD_USBSS_GND_COEF_R_K4_0              0x4a5
+#define WCD_USBSS_GND_COEF_R_K4_1              0x4a6
+#define WCD_USBSS_GND_COEF_R_K5_0              0x4a7
+#define WCD_USBSS_GND_COEF_R_K5_1              0x4a8
+
+#define WCD_USBSS_MAX_REGISTER                 0x4c1
+
+struct wcd939x_usbss {
+       struct i2c_client *client;
+       struct gpio_desc *reset_gpio;
+       struct regulator *vdd_supply;
+
+       /* used to serialize concurrent change requests */
+       struct mutex lock;
+
+       struct typec_switch_dev *sw;
+       struct typec_mux_dev *mux;
+
+       struct regmap *regmap;
+
+       struct typec_mux *codec;
+       struct typec_switch *codec_switch;
+
+       enum typec_orientation orientation;
+       unsigned long mode;
+       unsigned int svid;
+};
+
+static const struct regmap_range_cfg wcd939x_usbss_ranges[] = {
+       {
+               .range_min = 0,
+               .range_max = WCD_USBSS_MAX_REGISTER,
+               .selector_reg = 0x0,
+               .selector_mask = 0xff,
+               .selector_shift = 0,
+               .window_start = 0,
+               .window_len = 0x100,
+       },
+};
+
+static const struct regmap_config wcd939x_usbss_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .max_register = WCD_USBSS_MAX_REGISTER,
+       .ranges = wcd939x_usbss_ranges,
+       .num_ranges = ARRAY_SIZE(wcd939x_usbss_ranges),
+};
+
+/* Linearlizer coefficients for 32ohm load */
+static const struct {
+       unsigned int offset;
+       unsigned int mask;
+       unsigned int value;
+} wcd939x_usbss_coeff_init[] = {
+       { WCD_USBSS_AUD_COEF_L_K5_0, GENMASK(7, 0), 0x39 },
+       { WCD_USBSS_AUD_COEF_R_K5_0, GENMASK(7, 0), 0x39 },
+       { WCD_USBSS_GND_COEF_L_K2_0, GENMASK(7, 0), 0xe8 },
+       { WCD_USBSS_GND_COEF_L_K4_0, GENMASK(7, 0), 0x73 },
+       { WCD_USBSS_GND_COEF_R_K2_0, GENMASK(7, 0), 0xe8 },
+       { WCD_USBSS_GND_COEF_R_K4_0, GENMASK(7, 0), 0x73 },
+       { WCD_USBSS_RATIO_SPKR_REXT_L_LSB, GENMASK(7, 0), 0x00 },
+       { WCD_USBSS_RATIO_SPKR_REXT_L_MSB, GENMASK(6, 0), 0x04 },
+       { WCD_USBSS_RATIO_SPKR_REXT_R_LSB, GENMASK(7, 0), 0x00 },
+       { WCD_USBSS_RATIO_SPKR_REXT_R_MSB, GENMASK(6, 0), 0x04 },
+};
+
+static int wcd939x_usbss_set(struct wcd939x_usbss *usbss)
+{
+       bool reverse = (usbss->orientation == TYPEC_ORIENTATION_REVERSE);
+       bool enable_audio = false;
+       bool enable_usb = false;
+       bool enable_dp = false;
+       int ret;
+
+       /* USB Mode */
+       if (usbss->mode < TYPEC_STATE_MODAL ||
+           (!usbss->svid && (usbss->mode == TYPEC_MODE_USB2 ||
+                             usbss->mode == TYPEC_MODE_USB3))) {
+               enable_usb = true;
+       } else if (usbss->svid) {
+               switch (usbss->mode) {
+               /* DP Only */
+               case TYPEC_DP_STATE_C:
+               case TYPEC_DP_STATE_E:
+                       enable_dp = true;
+                       break;
+
+               /* DP + USB */
+               case TYPEC_DP_STATE_D:
+               case TYPEC_DP_STATE_F:
+                       enable_usb = true;
+                       enable_dp = true;
+                       break;
+
+               default:
+                       return -EOPNOTSUPP;
+               }
+       } else if (usbss->mode == TYPEC_MODE_AUDIO) {
+               enable_audio = true;
+       } else {
+               return -EOPNOTSUPP;
+       }
+
+       /* Disable all switches */
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES |
+                               WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES);
+       if (ret)
+               return ret;
+
+       /* Clear switches */
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                               WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES);
+       if (ret)
+               return ret;
+
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1,
+                               WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES);
+       if (ret)
+               return ret;
+
+       /* Enable OVP_MG1_BIAS PCOMP_DYN_BST_EN */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_BIAS,
+                             WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN);
+       if (ret)
+               return ret;
+
+       /* Enable OVP_MG2_BIAS PCOMP_DYN_BST_EN */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_BIAS,
+                             WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN);
+       if (ret)
+               return ret;
+
+       /* Disable Equalizer in safe mode */
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_EQUALIZER1,
+                               WCD_USBSS_EQUALIZER1_EQ_EN);
+       if (ret)
+               return ret;
+
+       /* Start FSM with all disabled, force write */
+       ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START,
+                               WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG,
+                               WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG);
+
+       /* 35us to allow the SBU switch to turn off */
+       usleep_range(35, 1000);
+
+       /* Setup Audio Accessory mux/switch */
+       if (enable_audio) {
+               int i;
+
+               /*
+                * AATC switch configuration:
+                * "Normal":
+                * - R: DNR
+                * - L: DNL
+                * - Sense: GSBU2
+                * - Mic: MG1
+                * - AGND: MG2
+                * "Swapped":
+                * - R: DNR
+                * - L: DNL
+                * - Sense: GSBU1
+                * - Mic: MG2
+                * - AGND: MG1
+                * Swapped information is given by the codec MBHC logic
+                */
+
+               /* Set AATC mode */
+               ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL,
+                                        WCD_USBSS_USB_SS_CNTL_USB_SS_MODE,
+                                        FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE,
+                                                   WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_AATC));
+               if (ret)
+                       return ret;
+
+               /* Select L for DNL_SWITCHES and R for DPR_SWITCHES */
+               ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                               WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES |
+                               WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES,
+                               FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES,
+                                       WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_L) |
+                               FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES,
+                                       WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_R));
+               if (ret)
+                       return ret;
+
+               if (reverse)
+                       /* Select MG2 for MIC, SBU1 for Sense */
+                       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                                                WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES,
+                                                WCD_USBSS_SWITCH_SELECT0_MIC_SWITCHES);
+               else
+                       /* Select MG1 for MIC, SBU2 for Sense */
+                       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                                                WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES,
+                                                WCD_USBSS_SWITCH_SELECT0_SENSE_SWITCHES);
+               if (ret)
+                       return ret;
+
+               if (reverse)
+                       /* Disable OVP_MG1_BIAS PCOMP_DYN_BST_EN */
+                       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG1_BIAS,
+                                               WCD_USBSS_MG1_BIAS_PCOMP_DYN_BST_EN);
+               else
+                       /* Disable OVP_MG2_BIAS PCOMP_DYN_BST_EN */
+                       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_MG2_BIAS,
+                                               WCD_USBSS_MG2_BIAS_PCOMP_DYN_BST_EN);
+               if (ret)
+                       return ret;
+
+               /*  Enable SENSE, MIC switches */
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_SENSE_SWITCHES |
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_MIC_SWITCHES);
+               if (ret)
+                       return ret;
+
+               if (reverse)
+                       /* Select MG1 for AGND_SWITCHES */
+                       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1,
+                                               WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES);
+               else
+                       /* Select MG2 for AGND_SWITCHES */
+                       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT1,
+                                             WCD_USBSS_SWITCH_SELECT1_AGND_SWITCHES);
+               if (ret)
+                       return ret;
+
+               /* Enable AGND switches */
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_AGND_SWITCHES);
+               if (ret)
+                       return ret;
+
+               /* Enable DPR, DNL switches */
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES |
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES);
+               if (ret)
+                       return ret;
+
+               /* Setup FSM delays */
+               ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_L_SW, 0x02);
+               if (ret)
+                       return ret;
+
+               ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_R_SW, 0x02);
+               if (ret)
+                       return ret;
+
+               ret = regmap_write(usbss->regmap, WCD_USBSS_DELAY_MIC_SW, 0x01);
+               if (ret)
+                       return ret;
+
+               /* Start FSM, force write */
+               ret = regmap_write_bits(usbss->regmap, WCD_USBSS_AUDIO_FSM_START,
+                                       WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG,
+                                       WCD_USBSS_AUDIO_FSM_START_AUDIO_FSM_AUDIO_TRIG);
+               if (ret)
+                       return ret;
+
+               /* Default Linearlizer coefficients */
+               for (i = 0; i < ARRAY_SIZE(wcd939x_usbss_coeff_init); ++i)
+                       regmap_update_bits(usbss->regmap,
+                                          wcd939x_usbss_coeff_init[i].offset,
+                                          wcd939x_usbss_coeff_init[i].mask,
+                                          wcd939x_usbss_coeff_init[i].value);
+
+               return 0;
+       }
+
+       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL,
+                                WCD_USBSS_USB_SS_CNTL_USB_SS_MODE,
+                                FIELD_PREP(WCD_USBSS_USB_SS_CNTL_USB_SS_MODE,
+                                           WCD_USBSS_USB_SS_CNTL_USB_SS_MODE_USB));
+       if (ret)
+               return ret;
+
+       /* Enable USB muxes */
+       if (enable_usb) {
+               /* Do not enable Equalizer in safe mode */
+               if (usbss->mode != TYPEC_STATE_SAFE) {
+                       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_EQUALIZER1,
+                                             WCD_USBSS_EQUALIZER1_EQ_EN);
+                       if (ret)
+                               return ret;
+               }
+
+               /* Select DN for DNL_SWITCHES and DP for DPR_SWITCHES */
+               ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                                        WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES |
+                                        WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES,
+                                        FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DNL_SWITCHES,
+                                                   WCD_USBSS_SWITCH_SELECT0_DNL_SWITCH_DN) |
+                                        FIELD_PREP(WCD_USBSS_SWITCH_SELECT0_DPR_SWITCHES,
+                                                   WCD_USBSS_SWITCH_SELECT0_DPR_SWITCH_DP));
+               if (ret)
+                       return ret;
+
+               /* Enable DNL_SWITCHES and DPR_SWITCHES */
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DPR_SWITCHES |
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DNL_SWITCHES);
+               if (ret)
+                       return ret;
+       }
+
+       /* Enable DP AUX muxes */
+       if (enable_dp) {
+               /* Update Leakage Canceller Coefficient for AUXP pins */
+               ret = regmap_update_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_CTL,
+                                        WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF,
+                                        FIELD_PREP(WCD_USBSS_DISP_AUXP_CTL_LK_CANCEL_TRK_COEFF,
+                                                   5));
+               if (ret)
+                       return ret;
+
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DISP_AUXP_THRESH,
+                                     WCD_USBSS_DISP_AUXP_THRESH_DISP_AUXP_OVPON_CM);
+               if (ret)
+                       return ret;
+
+               if (reverse)
+                       /* Select MG2 for AUXP and MG1 for AUXM */
+                       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES |
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES,
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES);
+               else
+                       /* Select MG1 for AUXP and MG2 for AUXM */
+                       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_SWITCH_SELECT0,
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXP_SWITCHES |
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES,
+                                                WCD_USBSS_SWITCH_SELECT0_DP_AUXM_SWITCHES);
+               if (ret)
+                       return ret;
+
+               /* Enable DP_AUXP_TO_MGX and DP_AUXM_TO_MGX switches */
+               ret = regmap_set_bits(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXP_TO_MGX_SWITCHES |
+                                     WCD_USBSS_SWITCH_SETTINGS_ENABLE_DP_AUXM_TO_MGX_SWITCHES);
+
+               /* 15us to allow the SBU switch to turn on again */
+               usleep_range(15, 1000);
+       }
+
+       return 0;
+}
+
+static int wcd939x_usbss_switch_set(struct typec_switch_dev *sw,
+                                   enum typec_orientation orientation)
+{
+       struct wcd939x_usbss *usbss = typec_switch_get_drvdata(sw);
+       int ret = 0;
+
+       mutex_lock(&usbss->lock);
+
+       if (usbss->orientation != orientation) {
+               usbss->orientation = orientation;
+
+               ret = wcd939x_usbss_set(usbss);
+       }
+
+       mutex_unlock(&usbss->lock);
+
+       if (ret)
+               return ret;
+
+       /* Report orientation to codec after switch has been done */
+       return typec_switch_set(usbss->codec_switch, orientation);
+}
+
+static int wcd939x_usbss_mux_set(struct typec_mux_dev *mux,
+                                struct typec_mux_state *state)
+{
+       struct wcd939x_usbss *usbss = typec_mux_get_drvdata(mux);
+       int ret = 0;
+
+       mutex_lock(&usbss->lock);
+
+       if (usbss->mode != state->mode) {
+               usbss->mode = state->mode;
+
+               if (state->alt)
+                       usbss->svid = state->alt->svid;
+               else
+                       usbss->svid = 0; // No SVID
+
+               ret = wcd939x_usbss_set(usbss);
+       }
+
+       mutex_unlock(&usbss->lock);
+
+       if (ret)
+               return ret;
+
+       /* Report event to codec after switch has been done */
+       return typec_mux_set(usbss->codec, state);
+}
+
+static int wcd939x_usbss_probe(struct i2c_client *client)
+{
+       struct device *dev = &client->dev;
+       struct typec_switch_desc sw_desc = { };
+       struct typec_mux_desc mux_desc = { };
+       struct wcd939x_usbss *usbss;
+       int ret;
+
+       usbss = devm_kzalloc(dev, sizeof(*usbss), GFP_KERNEL);
+       if (!usbss)
+               return -ENOMEM;
+
+       usbss->client = client;
+       mutex_init(&usbss->lock);
+
+       usbss->regmap = devm_regmap_init_i2c(client, &wcd939x_usbss_regmap_config);
+       if (IS_ERR(usbss->regmap))
+               return dev_err_probe(dev, PTR_ERR(usbss->regmap), "failed to initialize regmap\n");
+
+       usbss->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(usbss->reset_gpio))
+               return dev_err_probe(dev, PTR_ERR(usbss->reset_gpio),
+                                    "unable to acquire reset gpio\n");
+
+       usbss->vdd_supply = devm_regulator_get_optional(dev, "vdd");
+       if (IS_ERR(usbss->vdd_supply))
+               return PTR_ERR(usbss->vdd_supply);
+
+       /* Get Codec's MUX & Switch devices */
+       usbss->codec = fwnode_typec_mux_get(dev->fwnode);
+       if (IS_ERR(usbss->codec))
+               return dev_err_probe(dev, PTR_ERR(usbss->codec),
+                                    "failed to acquire codec mode-switch\n");
+
+       usbss->codec_switch = fwnode_typec_switch_get(dev->fwnode);
+       if (IS_ERR(usbss->codec_switch)) {
+               ret = dev_err_probe(dev, PTR_ERR(usbss->codec_switch),
+                                   "failed to acquire codec orientation-switch\n");
+               goto err_mux_put;
+       }
+
+       usbss->mode = TYPEC_STATE_SAFE;
+       usbss->orientation = TYPEC_ORIENTATION_NONE;
+
+       gpiod_set_value(usbss->reset_gpio, 1);
+
+       ret = regulator_enable(usbss->vdd_supply);
+       if (ret) {
+               dev_err(dev, "Failed to enable vdd: %d\n", ret);
+               goto err_mux_switch;
+       }
+
+       msleep(20);
+
+       gpiod_set_value(usbss->reset_gpio, 0);
+
+       msleep(20);
+
+       /* Disable standby */
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL,
+                               WCD_USBSS_USB_SS_CNTL_STANDBY_STATE);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Set manual mode by default */
+       ret = regmap_update_bits(usbss->regmap, WCD_USBSS_FUNCTION_ENABLE,
+                                WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT,
+                                FIELD_PREP(WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT,
+                                           WCD_USBSS_FUNCTION_ENABLE_SOURCE_SELECT_MANUAL));
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Enable dynamic boosting for DP and DN */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_DP_DN_MISC1,
+                             WCD_USBSS_DP_DN_MISC1_DP_PCOMP_2X_DYN_BST_ON_EN |
+                             WCD_USBSS_DP_DN_MISC1_DN_PCOMP_2X_DYN_BST_ON_EN);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Enable dynamic boosting for MG1 OVP */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG1_MISC,
+                             WCD_USBSS_MG1_MISC_PCOMP_2X_DYN_BST_ON_EN);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Enable dynamic boosting for MG2 OVP */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_MG2_MISC,
+                             WCD_USBSS_MG2_MISC_PCOMP_2X_DYN_BST_ON_EN);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Write 0xFF to WCD_USBSS_CPLDO_CTL2 */
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_CPLDO_CTL2, 0xff);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Set RCO_EN: WCD_USBSS_USB_SS_CNTL Bit<3> --> 0x0 --> 0x1 */
+       ret = regmap_clear_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL,
+                               WCD_USBSS_USB_SS_CNTL_RCO_EN);
+       if (ret)
+               goto err_regulator_disable;
+
+       ret = regmap_set_bits(usbss->regmap, WCD_USBSS_USB_SS_CNTL,
+                             WCD_USBSS_USB_SS_CNTL_RCO_EN);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Disable all switches but enable the mux */
+       ret = regmap_write(usbss->regmap, WCD_USBSS_SWITCH_SETTINGS_ENABLE,
+                          WCD_USBSS_SWITCH_SETTINGS_ENABLE_DEVICE_ENABLE);
+       if (ret)
+               goto err_regulator_disable;
+
+       /* Setup in SAFE mode */
+       ret = wcd939x_usbss_set(usbss);
+       if (ret)
+               goto err_regulator_disable;
+
+       sw_desc.drvdata = usbss;
+       sw_desc.fwnode = dev_fwnode(dev);
+       sw_desc.set = wcd939x_usbss_switch_set;
+
+       usbss->sw = typec_switch_register(dev, &sw_desc);
+       if (IS_ERR(usbss->sw)) {
+               ret = dev_err_probe(dev, PTR_ERR(usbss->sw), "failed to register typec switch\n");
+               goto err_regulator_disable;
+       }
+
+       mux_desc.drvdata = usbss;
+       mux_desc.fwnode = dev_fwnode(dev);
+       mux_desc.set = wcd939x_usbss_mux_set;
+
+       usbss->mux = typec_mux_register(dev, &mux_desc);
+       if (IS_ERR(usbss->mux)) {
+               ret = dev_err_probe(dev, PTR_ERR(usbss->mux), "failed to register typec mux\n");
+               goto err_switch_unregister;
+       }
+
+       i2c_set_clientdata(client, usbss);
+
+       return 0;
+
+err_switch_unregister:
+       typec_switch_unregister(usbss->sw);
+
+err_regulator_disable:
+       regulator_disable(usbss->vdd_supply);
+
+err_mux_switch:
+       typec_switch_put(usbss->codec_switch);
+
+err_mux_put:
+       typec_mux_put(usbss->codec);
+
+       return ret;
+}
+
+static void wcd939x_usbss_remove(struct i2c_client *client)
+{
+       struct wcd939x_usbss *usbss = i2c_get_clientdata(client);
+
+       typec_mux_unregister(usbss->mux);
+       typec_switch_unregister(usbss->sw);
+
+       regulator_disable(usbss->vdd_supply);
+
+       typec_switch_put(usbss->codec_switch);
+       typec_mux_put(usbss->codec);
+}
+
+static const struct i2c_device_id wcd939x_usbss_table[] = {
+       { "wcd9390-usbss" },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, wcd939x_usbss_table);
+
+static const struct of_device_id wcd939x_usbss_of_table[] = {
+       { .compatible = "qcom,wcd9390-usbss" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, wcd939x_usbss_of_table);
+
+static struct i2c_driver wcd939x_usbss_driver = {
+       .driver = {
+               .name = "wcd939x-usbss",
+               .of_match_table = wcd939x_usbss_of_table,
+       },
+       .probe          = wcd939x_usbss_probe,
+       .remove         = wcd939x_usbss_remove,
+       .id_table       = wcd939x_usbss_table,
+};
+module_i2c_driver(wcd939x_usbss_driver);
+
+MODULE_DESCRIPTION("Qualcomm WCD939x USBSS driver");
+MODULE_LICENSE("GPL");
index 85d015c..b9cca2b 100644 (file)
@@ -468,7 +468,7 @@ static struct device_type pd_capabilities_type = {
 /**
  * usb_power_delivery_register_capabilities - Register a set of capabilities.
  * @pd: The USB PD instance that the capabilities belong to.
- * @desc: Description of the Capablities Message.
+ * @desc: Description of the Capabilities Message.
  *
  * This function registers a Capabilities Message described in @desc. The
  * capabilities will have their own sub-directory under @pd in sysfs.
@@ -571,7 +571,7 @@ static void pd_release(struct device *dev)
 {
        struct usb_power_delivery *pd = to_usb_power_delivery(dev);
 
-       ida_simple_remove(&pd_ida, pd->id);
+       ida_free(&pd_ida, pd->id);
        kfree(pd);
 }
 
@@ -616,7 +616,7 @@ usb_power_delivery_register(struct device *parent, struct usb_power_delivery_des
        if (!pd)
                return ERR_PTR(-ENOMEM);
 
-       ret = ida_simple_get(&pd_ida, 0, 0, GFP_KERNEL);
+       ret = ida_alloc(&pd_ida, GFP_KERNEL);
        if (ret < 0) {
                kfree(pd);
                return ERR_PTR(ret);
index 9454b12..7fb966f 100644 (file)
@@ -92,11 +92,16 @@ static void max_tcpci_init_regs(struct max_tcpci_chip *chip)
                return;
        }
 
+       /* Vconn Over Current Protection */
+       ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS_MASK, TCPC_FAULT_STATUS_MASK_VCONN_OC);
+       if (ret < 0)
+               return;
+
        alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED |
                TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS |
                TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS |
                /* Enable Extended alert for detecting Fast Role Swap Signal */
-               TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS;
+               TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS | TCPC_ALERT_FAULT;
 
        ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask);
        if (ret < 0) {
@@ -295,6 +300,19 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status)
                }
        }
 
+       if (status & TCPC_ALERT_FAULT) {
+               ret = max_tcpci_read8(chip, TCPC_FAULT_STATUS, &reg_status);
+               if (ret < 0)
+                       return ret;
+
+               ret = max_tcpci_write8(chip, TCPC_FAULT_STATUS, reg_status);
+               if (ret < 0)
+                       return ret;
+
+               if (reg_status & TCPC_FAULT_STATUS_VCONN_OC)
+                       tcpm_port_error_recovery(chip->port);
+       }
+
        if (status & TCPC_ALERT_EXTND) {
                ret = max_tcpci_read8(chip, TCPC_ALERT_EXTENDED, &reg_status);
                if (ret < 0)
index bfb6f94..5945e3a 100644 (file)
@@ -251,6 +251,7 @@ enum frs_typec_current {
 #define TCPM_FRS_EVENT         BIT(3)
 #define TCPM_SOURCING_VBUS     BIT(4)
 #define TCPM_PORT_CLEAN                BIT(5)
+#define TCPM_PORT_ERROR                BIT(6)
 
 #define LOG_BUFFER_ENTRIES     1024
 #define LOG_BUFFER_ENTRY_SIZE  128
@@ -296,6 +297,15 @@ struct pd_pps_data {
        bool active;
 };
 
+struct pd_data {
+       struct usb_power_delivery *pd;
+       struct usb_power_delivery_capabilities *source_cap;
+       struct usb_power_delivery_capabilities_desc source_desc;
+       struct usb_power_delivery_capabilities *sink_cap;
+       struct usb_power_delivery_capabilities_desc sink_desc;
+       unsigned int operating_snk_mw;
+};
+
 struct tcpm_port {
        struct device *dev;
 
@@ -397,12 +407,14 @@ struct tcpm_port {
        unsigned int rx_msgid;
 
        /* USB PD objects */
-       struct usb_power_delivery *pd;
+       struct usb_power_delivery **pds;
+       struct pd_data **pd_list;
        struct usb_power_delivery_capabilities *port_source_caps;
        struct usb_power_delivery_capabilities *port_sink_caps;
        struct usb_power_delivery *partner_pd;
        struct usb_power_delivery_capabilities *partner_source_caps;
        struct usb_power_delivery_capabilities *partner_sink_caps;
+       struct usb_power_delivery *selected_pd;
 
        /* Partner capabilities/requests */
        u32 sink_request;
@@ -412,6 +424,7 @@ struct tcpm_port {
        unsigned int nr_sink_caps;
 
        /* Local capabilities */
+       unsigned int pd_count;
        u32 src_pdo[PDO_MAX_OBJECTS];
        unsigned int nr_src_pdo;
        u32 snk_pdo[PDO_MAX_OBJECTS];
@@ -2847,7 +2860,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
                                           PD_MSG_CTRL_NOT_SUPP,
                                           NONE_AMS);
                } else {
-                       if (port->send_discover) {
+                       if (port->send_discover && port->negotiated_rev < PD_REV30) {
                                tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
                                break;
                        }
@@ -2863,7 +2876,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
                                           PD_MSG_CTRL_NOT_SUPP,
                                           NONE_AMS);
                } else {
-                       if (port->send_discover) {
+                       if (port->send_discover && port->negotiated_rev < PD_REV30) {
                                tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
                                break;
                        }
@@ -2872,7 +2885,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
                }
                break;
        case PD_CTRL_VCONN_SWAP:
-               if (port->send_discover) {
+               if (port->send_discover && port->negotiated_rev < PD_REV30) {
                        tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
                        break;
                }
@@ -4401,7 +4414,8 @@ static void run_state_machine(struct tcpm_port *port)
                        tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
                tcpm_swap_complete(port, 0);
                tcpm_typec_connect(port);
-               mod_enable_frs_delayed_work(port, 0);
+               if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE)
+                       mod_enable_frs_delayed_work(port, 0);
                tcpm_pps_complete(port, port->pps_status);
 
                if (port->ams != NONE_AMS)
@@ -5487,6 +5501,10 @@ static void tcpm_pd_event_handler(struct kthread_work *work)
                                        tcpm_set_state(port, tcpm_default_state(port), 0);
                        }
                }
+               if (events & TCPM_PORT_ERROR) {
+                       tcpm_log(port, "port triggering error recovery");
+                       tcpm_set_state(port, ERROR_RECOVERY, 0);
+               }
 
                spin_lock(&port->pd_event_lock);
        }
@@ -5554,6 +5572,15 @@ bool tcpm_port_is_toggling(struct tcpm_port *port)
 }
 EXPORT_SYMBOL_GPL(tcpm_port_is_toggling);
 
+void tcpm_port_error_recovery(struct tcpm_port *port)
+{
+       spin_lock(&port->pd_event_lock);
+       port->pd_events |= TCPM_PORT_ERROR;
+       spin_unlock(&port->pd_event_lock);
+       kthread_queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_port_error_recovery);
+
 static void tcpm_enable_frs_work(struct kthread_work *work)
 {
        struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs);
@@ -6045,12 +6072,114 @@ port_unlock:
        return 0;
 }
 
+static struct pd_data *tcpm_find_pd_data(struct tcpm_port *port, struct usb_power_delivery *pd)
+{
+       int i;
+
+       for (i = 0; port->pd_list[i]; i++) {
+               if (port->pd_list[i]->pd == pd)
+                       return port->pd_list[i];
+       }
+
+       return ERR_PTR(-ENODATA);
+}
+
+static struct usb_power_delivery **tcpm_pd_get(struct typec_port *p)
+{
+       struct tcpm_port *port = typec_get_drvdata(p);
+
+       return port->pds;
+}
+
+static int tcpm_pd_set(struct typec_port *p, struct usb_power_delivery *pd)
+{
+       struct tcpm_port *port = typec_get_drvdata(p);
+       struct pd_data *data;
+       int i, ret = 0;
+
+       mutex_lock(&port->lock);
+
+       if (port->selected_pd == pd)
+               goto unlock;
+
+       data = tcpm_find_pd_data(port, pd);
+       if (IS_ERR(data)) {
+               ret = PTR_ERR(data);
+               goto unlock;
+       }
+
+       if (data->sink_desc.pdo[0]) {
+               for (i = 0; i < PDO_MAX_OBJECTS && data->sink_desc.pdo[i]; i++)
+                       port->snk_pdo[i] = data->sink_desc.pdo[i];
+               port->nr_snk_pdo = i + 1;
+               port->operating_snk_mw = data->operating_snk_mw;
+       }
+
+       if (data->source_desc.pdo[0]) {
+               for (i = 0; i < PDO_MAX_OBJECTS && data->source_desc.pdo[i]; i++)
+                       port->snk_pdo[i] = data->source_desc.pdo[i];
+               port->nr_src_pdo = i + 1;
+       }
+
+       switch (port->state) {
+       case SRC_UNATTACHED:
+       case SRC_ATTACH_WAIT:
+       case SRC_TRYWAIT:
+               tcpm_set_cc(port, tcpm_rp_cc(port));
+               break;
+       case SRC_SEND_CAPABILITIES:
+       case SRC_SEND_CAPABILITIES_TIMEOUT:
+       case SRC_NEGOTIATE_CAPABILITIES:
+       case SRC_READY:
+       case SRC_WAIT_NEW_CAPABILITIES:
+               port->caps_count = 0;
+               port->upcoming_state = SRC_SEND_CAPABILITIES;
+               ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+               if (ret == -EAGAIN) {
+                       port->upcoming_state = INVALID_STATE;
+                       goto unlock;
+               }
+               break;
+       case SNK_NEGOTIATE_CAPABILITIES:
+       case SNK_NEGOTIATE_PPS_CAPABILITIES:
+       case SNK_READY:
+       case SNK_TRANSITION_SINK:
+       case SNK_TRANSITION_SINK_VBUS:
+               if (port->pps_data.active)
+                       port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
+               else if (port->pd_capable)
+                       port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES;
+               else
+                       break;
+
+               port->update_sink_caps = true;
+
+               ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+               if (ret == -EAGAIN) {
+                       port->upcoming_state = INVALID_STATE;
+                       goto unlock;
+               }
+               break;
+       default:
+               break;
+       }
+
+       port->port_source_caps = data->source_cap;
+       port->port_sink_caps = data->sink_cap;
+       port->selected_pd = pd;
+unlock:
+       mutex_unlock(&port->lock);
+       return ret;
+}
+
 static const struct typec_operations tcpm_ops = {
        .try_role = tcpm_try_role,
        .dr_set = tcpm_dr_set,
        .pr_set = tcpm_pr_set,
        .vconn_set = tcpm_vconn_set,
-       .port_type_set = tcpm_port_type_set
+       .port_type_set = tcpm_port_type_set,
+       .pd_get = tcpm_pd_get,
+       .pd_set = tcpm_pd_set
 };
 
 void tcpm_tcpc_reset(struct tcpm_port *port)
@@ -6064,58 +6193,63 @@ EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
 
 static void tcpm_port_unregister_pd(struct tcpm_port *port)
 {
-       usb_power_delivery_unregister_capabilities(port->port_sink_caps);
+       int i;
+
        port->port_sink_caps = NULL;
-       usb_power_delivery_unregister_capabilities(port->port_source_caps);
        port->port_source_caps = NULL;
-       usb_power_delivery_unregister(port->pd);
-       port->pd = NULL;
+       for (i = 0; i < port->pd_count; i++) {
+               usb_power_delivery_unregister_capabilities(port->pd_list[i]->sink_cap);
+               kfree(port->pd_list[i]->sink_cap);
+               usb_power_delivery_unregister_capabilities(port->pd_list[i]->source_cap);
+               kfree(port->pd_list[i]->source_cap);
+               devm_kfree(port->dev, port->pd_list[i]);
+               port->pd_list[i] = NULL;
+               usb_power_delivery_unregister(port->pds[i]);
+               port->pds[i] = NULL;
+       }
 }
 
 static int tcpm_port_register_pd(struct tcpm_port *port)
 {
        struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision };
-       struct usb_power_delivery_capabilities_desc caps = { };
        struct usb_power_delivery_capabilities *cap;
-       int ret;
+       int ret, i;
 
        if (!port->nr_src_pdo && !port->nr_snk_pdo)
                return 0;
 
-       port->pd = usb_power_delivery_register(port->dev, &desc);
-       if (IS_ERR(port->pd)) {
-               ret = PTR_ERR(port->pd);
-               goto err_unregister;
-       }
-
-       if (port->nr_src_pdo) {
-               memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->src_pdo,
-                              port->nr_src_pdo * sizeof(u32), 0);
-               caps.role = TYPEC_SOURCE;
-
-               cap = usb_power_delivery_register_capabilities(port->pd, &caps);
-               if (IS_ERR(cap)) {
-                       ret = PTR_ERR(cap);
+       for (i = 0; i < port->pd_count; i++) {
+               port->pds[i] = usb_power_delivery_register(port->dev, &desc);
+               if (IS_ERR(port->pds[i])) {
+                       ret = PTR_ERR(port->pds[i]);
                        goto err_unregister;
                }
-
-               port->port_source_caps = cap;
-       }
-
-       if (port->nr_snk_pdo) {
-               memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->snk_pdo,
-                              port->nr_snk_pdo * sizeof(u32), 0);
-               caps.role = TYPEC_SINK;
-
-               cap = usb_power_delivery_register_capabilities(port->pd, &caps);
-               if (IS_ERR(cap)) {
-                       ret = PTR_ERR(cap);
-                       goto err_unregister;
+               port->pd_list[i]->pd = port->pds[i];
+
+               if (port->pd_list[i]->source_desc.pdo[0]) {
+                       cap = usb_power_delivery_register_capabilities(port->pds[i],
+                                                               &port->pd_list[i]->source_desc);
+                       if (IS_ERR(cap)) {
+                               ret = PTR_ERR(cap);
+                               goto err_unregister;
+                       }
+                       port->pd_list[i]->source_cap = cap;
                }
 
-               port->port_sink_caps = cap;
+               if (port->pd_list[i]->sink_desc.pdo[0]) {
+                       cap = usb_power_delivery_register_capabilities(port->pds[i],
+                                                               &port->pd_list[i]->sink_desc);
+                       if (IS_ERR(cap)) {
+                               ret = PTR_ERR(cap);
+                               goto err_unregister;
+                       }
+                       port->pd_list[i]->sink_cap = cap;
+               }
        }
 
+       port->port_source_caps = port->pd_list[0]->source_cap;
+       port->port_sink_caps = port->pd_list[0]->sink_cap;
+       port->selected_pd = port->pds[0];
        return 0;
 
 err_unregister:
@@ -6124,12 +6258,15 @@ err_unregister:
        return ret;
 }
 
-static int tcpm_fw_get_caps(struct tcpm_port *port,
-                           struct fwnode_handle *fwnode)
+static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode)
 {
+       struct fwnode_handle *capabilities, *child, *caps = NULL;
+       unsigned int nr_src_pdo, nr_snk_pdo;
        const char *opmode_str;
-       int ret;
-       u32 mw, frs_current;
+       u32 *src_pdo, *snk_pdo;
+       u32 uw, frs_current;
+       int ret = 0, i;
+       int mode;
 
        if (!fwnode)
                return -EINVAL;
@@ -6147,30 +6284,20 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
        if (ret < 0)
                return ret;
 
+       mode = 0;
+
+       if (fwnode_property_read_bool(fwnode, "accessory-mode-audio"))
+               port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_AUDIO;
+
+       if (fwnode_property_read_bool(fwnode, "accessory-mode-debug"))
+               port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_DEBUG;
+
        port->port_type = port->typec_caps.type;
        port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
-
        port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop");
-       if (port->port_type == TYPEC_PORT_SNK)
-               goto sink;
-
-       /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */
-       if (port->pd_supported) {
-               ret = fwnode_property_count_u32(fwnode, "source-pdos");
-               if (ret == 0)
-                       return -EINVAL;
-               else if (ret < 0)
-                       return ret;
+       port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
 
-               port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
-               ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
-                                                    port->src_pdo, port->nr_src_pdo);
-               if (ret)
-                       return ret;
-               ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo);
-               if (ret)
-                       return ret;
-       } else {
+       if (!port->pd_supported) {
                ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str);
                if (ret)
                        return ret;
@@ -6178,45 +6305,150 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
                if (ret < 0)
                        return ret;
                port->src_rp = tcpm_pwr_opmode_to_rp(ret);
-       }
-
-       if (port->port_type == TYPEC_PORT_SRC)
                return 0;
+       }
 
-sink:
-       port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
-
-       if (!port->pd_supported)
-               return 0;
-
-       /* Get sink pdos */
-       ret = fwnode_property_count_u32(fwnode, "sink-pdos");
-       if (ret <= 0)
-               return -EINVAL;
-
-       port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
-       ret = fwnode_property_read_u32_array(fwnode, "sink-pdos",
-                                            port->snk_pdo, port->nr_snk_pdo);
-       if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo,
-                                           port->nr_snk_pdo))
-               return -EINVAL;
-
-       if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0)
-               return -EINVAL;
-       port->operating_snk_mw = mw / 1000;
+       /* The following code are applicable to pd-capable ports, i.e. pd_supported is true. */
 
        /* FRS can only be supported by DRP ports */
        if (port->port_type == TYPEC_PORT_DRP) {
                ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current",
                                               &frs_current);
-               if (ret >= 0 && frs_current <= FRS_5V_3A)
+               if (!ret && frs_current <= FRS_5V_3A)
                        port->new_source_frs_current = frs_current;
+
+               if (ret)
+                       ret = 0;
        }
 
+       /* For the backward compatibility, "capabilities" node is optional. */
+       capabilities = fwnode_get_named_child_node(fwnode, "capabilities");
+       if (!capabilities) {
+               port->pd_count = 1;
+       } else {
+               fwnode_for_each_child_node(capabilities, child)
+                       port->pd_count++;
+
+               if (!port->pd_count) {
+                       ret = -ENODATA;
+                       goto put_capabilities;
+               }
+       }
+
+       port->pds = devm_kcalloc(port->dev, port->pd_count, sizeof(struct usb_power_delivery *),
+                                GFP_KERNEL);
+       if (!port->pds) {
+               ret = -ENOMEM;
+               goto put_capabilities;
+       }
+
+       port->pd_list = devm_kcalloc(port->dev, port->pd_count, sizeof(struct pd_data *),
+                                    GFP_KERNEL);
+       if (!port->pd_list) {
+               ret = -ENOMEM;
+               goto put_capabilities;
+       }
+
+       for (i = 0; i < port->pd_count; i++) {
+               port->pd_list[i] = devm_kzalloc(port->dev, sizeof(struct pd_data), GFP_KERNEL);
+               if (!port->pd_list[i]) {
+                       ret = -ENOMEM;
+                       goto put_capabilities;
+               }
+
+               src_pdo = port->pd_list[i]->source_desc.pdo;
+               port->pd_list[i]->source_desc.role = TYPEC_SOURCE;
+               snk_pdo = port->pd_list[i]->sink_desc.pdo;
+               port->pd_list[i]->sink_desc.role = TYPEC_SINK;
+
+               /* If "capabilities" is NULL, fall back to single pd cap population. */
+               if (!capabilities)
+                       caps = fwnode;
+               else
+                       caps = fwnode_get_next_child_node(capabilities, caps);
+
+               if (port->port_type != TYPEC_PORT_SNK) {
+                       ret = fwnode_property_count_u32(caps, "source-pdos");
+                       if (ret == 0) {
+                               ret = -EINVAL;
+                               goto put_caps;
+                       }
+                       if (ret < 0)
+                               goto put_caps;
+
+                       nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
+                       ret = fwnode_property_read_u32_array(caps, "source-pdos", src_pdo,
+                                                            nr_src_pdo);
+                       if (ret)
+                               goto put_caps;
+
+                       ret = tcpm_validate_caps(port, src_pdo, nr_src_pdo);
+                       if (ret)
+                               goto put_caps;
+
+                       if (i == 0) {
+                               port->nr_src_pdo = nr_src_pdo;
+                               memcpy_and_pad(port->src_pdo, sizeof(u32) * PDO_MAX_OBJECTS,
+                                              port->pd_list[0]->source_desc.pdo,
+                                              sizeof(u32) * nr_src_pdo,
+                                              0);
+                       }
+               }
+
+               if (port->port_type != TYPEC_PORT_SRC) {
+                       ret = fwnode_property_count_u32(caps, "sink-pdos");
+                       if (ret == 0) {
+                               ret = -EINVAL;
+                               goto put_caps;
+                       }
+
+                       if (ret < 0)
+                               goto put_caps;
+
+                       nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
+                       ret = fwnode_property_read_u32_array(caps, "sink-pdos", snk_pdo,
+                                                            nr_snk_pdo);
+                       if (ret)
+                               goto put_caps;
+
+                       ret = tcpm_validate_caps(port, snk_pdo, nr_snk_pdo);
+                       if (ret)
+                               goto put_caps;
+
+                       if (fwnode_property_read_u32(caps, "op-sink-microwatt", &uw) < 0) {
+                               ret = -EINVAL;
+                               goto put_caps;
+                       }
+
+                       port->pd_list[i]->operating_snk_mw = uw / 1000;
+
+                       if (i == 0) {
+                               port->nr_snk_pdo = nr_snk_pdo;
+                               memcpy_and_pad(port->snk_pdo, sizeof(u32) * PDO_MAX_OBJECTS,
+                                              port->pd_list[0]->sink_desc.pdo,
+                                              sizeof(u32) * nr_snk_pdo,
+                                              0);
+                               port->operating_snk_mw = port->pd_list[0]->operating_snk_mw;
+                       }
+               }
+       }
+
+put_caps:
+       if (caps != fwnode)
+               fwnode_handle_put(caps);
+put_capabilities:
+       fwnode_handle_put(capabilities);
+       return ret;
+}
+
+static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fwnode)
+{
+       int ret;
+
        /* sink-vdos is optional */
        ret = fwnode_property_count_u32(fwnode, "sink-vdos");
        if (ret < 0)
-               ret = 0;
+               return 0;
 
        port->nr_snk_vdo = min(ret, VDO_MAX_OBJECTS);
        if (port->nr_snk_vdo) {
@@ -6582,12 +6814,14 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
        tcpm_debugfs_init(port);
 
        err = tcpm_fw_get_caps(port, tcpc->fwnode);
+       if (err < 0)
+               goto out_destroy_wq;
+       err = tcpm_fw_get_snk_vdos(port, tcpc->fwnode);
        if (err < 0)
                goto out_destroy_wq;
 
        port->try_role = port->typec_caps.prefer_role;
 
-       port->typec_caps.fwnode = tcpc->fwnode;
        port->typec_caps.revision = 0x0120;     /* Type-C spec release 1.2 */
        port->typec_caps.pd_revision = 0x0300;  /* USB-PD spec release 3.0 */
        port->typec_caps.svdm_version = SVDM_VER_2_0;
@@ -6596,7 +6830,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
        port->typec_caps.orientation_aware = 1;
 
        port->partner_desc.identity = &port->partner_ident;
-       port->port_type = port->typec_caps.type;
 
        port->role_sw = usb_role_switch_get(port->dev);
        if (!port->role_sw)
@@ -6615,7 +6848,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
        if (err)
                goto out_role_sw_put;
 
-       port->typec_caps.pd = port->pd;
+       port->typec_caps.pd = port->pds[0];
 
        port->typec_port = typec_register_port(port->dev, &port->typec_caps);
        if (IS_ERR(port->typec_port)) {
index 196535a..0717cfc 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <linux/i2c.h>
 #include <linux/acpi.h>
+#include <linux/gpio/consumer.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/power_supply.h>
@@ -64,6 +65,9 @@
 #define TPS_PBMC_RC    0 /* Return code */
 #define TPS_PBMC_DPCS  2 /* device patch complete status */
 
+/* reset de-assertion to ready for operation */
+#define TPS_SETUP_MS                   1000
+
 enum {
        TPS_PORTINFO_SINK,
        TPS_PORTINFO_SINK_ACCESSORY,
@@ -111,6 +115,8 @@ struct tipd_data {
        void (*trace_power_status)(u16 status);
        void (*trace_status)(u32 status);
        int (*apply_patch)(struct tps6598x *tps);
+       int (*init)(struct tps6598x *tps);
+       int (*reset)(struct tps6598x *tps);
 };
 
 struct tps6598x {
@@ -119,6 +125,7 @@ struct tps6598x {
        struct mutex lock; /* device lock */
        u8 i2c_protocol:1;
 
+       struct gpio_desc *reset;
        struct typec_port *port;
        struct typec_partner *partner;
        struct usb_pd_identity partner_identity;
@@ -323,7 +330,7 @@ static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
 }
 
 static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd,
-                            size_t in_len, u8 *in_data,
+                            size_t in_len, const u8 *in_data,
                             size_t out_len, u8 *out_data,
                             u32 cmd_timeout_ms, u32 res_delay_ms)
 {
@@ -389,7 +396,7 @@ static int tps6598x_exec_cmd_tmo(struct tps6598x *tps, const char *cmd,
 }
 
 static int tps6598x_exec_cmd(struct tps6598x *tps, const char *cmd,
-                            size_t in_len, u8 *in_data,
+                            size_t in_len, const u8 *in_data,
                             size_t out_len, u8 *out_data)
 {
        return tps6598x_exec_cmd_tmo(tps, cmd, in_len, in_data,
@@ -866,6 +873,30 @@ tps6598x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
        return 0;
 }
 
+static int tps_request_firmware(struct tps6598x *tps, const struct firmware **fw)
+{
+       const char *firmware_name;
+       int ret;
+
+       ret = device_property_read_string(tps->dev, "firmware-name",
+                                         &firmware_name);
+       if (ret)
+               return ret;
+
+       ret = request_firmware(fw, firmware_name, tps->dev);
+       if (ret) {
+               dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name);
+               return ret;
+       }
+
+       if ((*fw)->size == 0) {
+               release_firmware(*fw);
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
 static int
 tps25750_write_firmware(struct tps6598x *tps,
                        u8 bpms_addr, const u8 *data, size_t len)
@@ -954,16 +985,9 @@ static int tps25750_start_patch_burst_mode(struct tps6598x *tps)
        if (ret)
                return ret;
 
-       ret = request_firmware(&fw, firmware_name, tps->dev);
-       if (ret) {
-               dev_err(tps->dev, "failed to retrieve \"%s\"\n", firmware_name);
+       ret = tps_request_firmware(tps, &fw);
+       if (ret)
                return ret;
-       }
-
-       if (fw->size == 0) {
-               ret = -EINVAL;
-               goto release_fw;
-       }
 
        ret = of_property_match_string(np, "reg-names", "patch-address");
        if (ret < 0) {
@@ -1101,6 +1125,76 @@ wait_for_app:
        return 0;
 };
 
+static int tps6598x_apply_patch(struct tps6598x *tps)
+{
+       u8 in = TPS_PTCS_CONTENT_DEV | TPS_PTCS_CONTENT_APP;
+       u8 out[TPS_MAX_LEN] = {0};
+       size_t in_len = sizeof(in);
+       size_t copied_bytes = 0;
+       size_t bytes_left;
+       const struct firmware *fw;
+       const char *firmware_name;
+       int ret;
+
+       ret = device_property_read_string(tps->dev, "firmware-name",
+                                         &firmware_name);
+       if (ret)
+               return ret;
+
+       ret = tps_request_firmware(tps, &fw);
+       if (ret)
+               return ret;
+
+       ret = tps6598x_exec_cmd(tps, "PTCs", in_len, &in,
+                               TPS_PTCS_OUT_BYTES, out);
+       if (ret || out[TPS_PTCS_STATUS] == TPS_PTCS_STATUS_FAIL) {
+               if (!ret)
+                       ret = -EBUSY;
+               dev_err(tps->dev, "Update start failed (%d)\n", ret);
+               goto release_fw;
+       }
+
+       bytes_left = fw->size;
+       while (bytes_left) {
+               if (bytes_left < TPS_MAX_LEN)
+                       in_len = bytes_left;
+               else
+                       in_len = TPS_MAX_LEN;
+               ret = tps6598x_exec_cmd(tps, "PTCd", in_len,
+                                       fw->data + copied_bytes,
+                                       TPS_PTCD_OUT_BYTES, out);
+               if (ret || out[TPS_PTCD_TRANSFER_STATUS] ||
+                   out[TPS_PTCD_LOADING_STATE] == TPS_PTCD_LOAD_ERR) {
+                       if (!ret)
+                               ret = -EBUSY;
+                       dev_err(tps->dev, "Patch download failed (%d)\n", ret);
+                       goto release_fw;
+               }
+               copied_bytes += in_len;
+               bytes_left -= in_len;
+       }
+
+       ret = tps6598x_exec_cmd(tps, "PTCc", 0, NULL, TPS_PTCC_OUT_BYTES, out);
+       if (ret || out[TPS_PTCC_DEV] || out[TPS_PTCC_APP]) {
+               if (!ret)
+                       ret = -EBUSY;
+               dev_err(tps->dev, "Update completion failed (%d)\n", ret);
+               goto release_fw;
+       }
+       msleep(TPS_SETUP_MS);
+       dev_info(tps->dev, "Firmware update succeeded\n");
+
+release_fw:
+       release_firmware(fw);
+
+       return ret;
+};
+
+static int cd321x_init(struct tps6598x *tps)
+{
+       return 0;
+}
+
 static int tps25750_init(struct tps6598x *tps)
 {
        int ret;
@@ -1119,6 +1213,26 @@ static int tps25750_init(struct tps6598x *tps)
        return 0;
 }
 
+static int tps6598x_init(struct tps6598x *tps)
+{
+       return tps->data->apply_patch(tps);
+}
+
+static int cd321x_reset(struct tps6598x *tps)
+{
+       return 0;
+}
+
+static int tps25750_reset(struct tps6598x *tps)
+{
+       return tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
+}
+
+static int tps6598x_reset(struct tps6598x *tps)
+{
+       return 0;
+}
+
 static int
 tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
 {
@@ -1182,7 +1296,6 @@ static int tps6598x_probe(struct i2c_client *client)
        u32 vid;
        int ret;
        u64 mask1;
-       bool is_tps25750;
 
        tps = devm_kzalloc(&client->dev, sizeof(*tps), GFP_KERNEL);
        if (!tps)
@@ -1191,12 +1304,18 @@ static int tps6598x_probe(struct i2c_client *client)
        mutex_init(&tps->lock);
        tps->dev = &client->dev;
 
+       tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW);
+       if (IS_ERR(tps->reset))
+               return dev_err_probe(tps->dev, PTR_ERR(tps->reset),
+                                    "failed to get reset GPIO\n");
+       if (tps->reset)
+               msleep(TPS_SETUP_MS);
+
        tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config);
        if (IS_ERR(tps->regmap))
                return PTR_ERR(tps->regmap);
 
-       is_tps25750 = device_is_compatible(tps->dev, "ti,tps25750");
-       if (!is_tps25750) {
+       if (!device_is_compatible(tps->dev, "ti,tps25750")) {
                ret = tps6598x_read32(tps, TPS_REG_VID, &vid);
                if (ret < 0 || !vid)
                        return -ENODEV;
@@ -1239,8 +1358,8 @@ static int tps6598x_probe(struct i2c_client *client)
        if (ret < 0)
                return ret;
 
-       if (is_tps25750 && ret == TPS_MODE_PTCH) {
-               ret = tps25750_init(tps);
+       if (ret == TPS_MODE_PTCH) {
+               ret = tps->data->init(tps);
                if (ret)
                        return ret;
        }
@@ -1328,8 +1447,8 @@ err_clear_mask:
        tps6598x_write64(tps, TPS_REG_INT_MASK1, 0);
 err_reset_controller:
        /* Reset PD controller to remove any applied patch */
-       if (is_tps25750)
-               tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
+       tps->data->reset(tps);
+
        return ret;
 }
 
@@ -1346,8 +1465,10 @@ static void tps6598x_remove(struct i2c_client *client)
        usb_role_switch_put(tps->role_sw);
 
        /* Reset PD controller to remove any applied patch */
-       if (device_is_compatible(tps->dev, "ti,tps25750"))
-               tps6598x_exec_cmd_tmo(tps, "GAID", 0, NULL, 0, NULL, 2000, 0);
+       tps->data->reset(tps);
+
+       if (tps->reset)
+               gpiod_set_value_cansleep(tps->reset, 1);
 }
 
 static int __maybe_unused tps6598x_suspend(struct device *dev)
@@ -1358,6 +1479,8 @@ static int __maybe_unused tps6598x_suspend(struct device *dev)
        if (tps->wakeup) {
                disable_irq(client->irq);
                enable_irq_wake(client->irq);
+       } else if (tps->reset) {
+               gpiod_set_value_cansleep(tps->reset, 1);
        }
 
        if (!client->irq)
@@ -1376,8 +1499,8 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
        if (ret < 0)
                return ret;
 
-       if (device_is_compatible(tps->dev, "ti,tps25750") && ret == TPS_MODE_PTCH) {
-               ret = tps25750_init(tps);
+       if (ret == TPS_MODE_PTCH) {
+               ret = tps->data->init(tps);
                if (ret)
                        return ret;
        }
@@ -1385,6 +1508,9 @@ static int __maybe_unused tps6598x_resume(struct device *dev)
        if (tps->wakeup) {
                disable_irq_wake(client->irq);
                enable_irq(client->irq);
+       } else if (tps->reset) {
+               gpiod_set_value_cansleep(tps->reset, 0);
+               msleep(TPS_SETUP_MS);
        }
 
        if (!client->irq)
@@ -1403,6 +1529,8 @@ static const struct tipd_data cd321x_data = {
        .register_port = tps6598x_register_port,
        .trace_power_status = trace_tps6598x_power_status,
        .trace_status = trace_tps6598x_status,
+       .init = cd321x_init,
+       .reset = cd321x_reset,
 };
 
 static const struct tipd_data tps6598x_data = {
@@ -1410,6 +1538,9 @@ static const struct tipd_data tps6598x_data = {
        .register_port = tps6598x_register_port,
        .trace_power_status = trace_tps6598x_power_status,
        .trace_status = trace_tps6598x_status,
+       .apply_patch = tps6598x_apply_patch,
+       .init = tps6598x_init,
+       .reset = tps6598x_reset,
 };
 
 static const struct tipd_data tps25750_data = {
@@ -1418,6 +1549,8 @@ static const struct tipd_data tps25750_data = {
        .trace_power_status = trace_tps25750_power_status,
        .trace_status = trace_tps25750_status,
        .apply_patch = tps25750_apply_patch,
+       .init = tps25750_init,
+       .reset = tps25750_reset,
 };
 
 static const struct of_device_id tps6598x_of_match[] = {
index 01609bf..89b2451 100644 (file)
 /* SLEEP CONF REG */
 #define TPS_SLEEP_CONF_SLEEP_MODE_ALLOWED      BIT(0)
 
+/* Start Patch Download Sequence */
+#define TPS_PTCS_CONTENT_APP                   BIT(0)
+#define TPS_PTCS_CONTENT_DEV                   BIT(1)
+#define TPS_PTCS_OUT_BYTES                     4
+#define TPS_PTCS_STATUS                                1
+
+#define TPS_PTCS_STATUS_FAIL                   0x80
+/* Patch Download */
+#define TPS_PTCD_OUT_BYTES                     10
+#define TPS_PTCD_TRANSFER_STATUS               1
+#define TPS_PTCD_LOADING_STATE                 2
+
+#define TPS_PTCD_LOAD_ERR                      0x09
+/* Patch Download Complete */
+#define TPS_PTCC_OUT_BYTES                     4
+#define TPS_PTCC_DEV                           2
+#define TPS_PTCC_APP                           3
+
 #endif /* __TPS6598X_H__ */
index 0a6624d..79110a6 100644 (file)
@@ -377,14 +377,14 @@ static int __init usbip_host_init(void)
                goto err_usb_register;
        }
 
-       ret = driver_create_file(&stub_driver.drvwrap.driver,
+       ret = driver_create_file(&stub_driver.driver,
                                 &driver_attr_match_busid);
        if (ret) {
                pr_err("driver_create_file failed\n");
                goto err_create_file;
        }
 
-       ret = driver_create_file(&stub_driver.drvwrap.driver,
+       ret = driver_create_file(&stub_driver.driver,
                                 &driver_attr_rebind);
        if (ret) {
                pr_err("driver_create_file failed\n");
@@ -402,10 +402,10 @@ err_usb_register:
 
 static void __exit usbip_host_exit(void)
 {
-       driver_remove_file(&stub_driver.drvwrap.driver,
+       driver_remove_file(&stub_driver.driver,
                           &driver_attr_match_busid);
 
-       driver_remove_file(&stub_driver.drvwrap.driver,
+       driver_remove_file(&stub_driver.driver,
                           &driver_attr_rebind);
 
        /*
index 1bd4bc0..faf61c9 100644 (file)
@@ -173,6 +173,6 @@ struct vudc_device *alloc_vudc_device(int devid);
 void put_vudc_device(struct vudc_device *udc_dev);
 
 int vudc_probe(struct platform_device *pdev);
-int vudc_remove(struct platform_device *pdev);
+void vudc_remove(struct platform_device *pdev);
 
 #endif /* __USBIP_VUDC_H */
index 44b04c5..f115350 100644 (file)
@@ -628,12 +628,11 @@ out:
        return ret;
 }
 
-int vudc_remove(struct platform_device *pdev)
+void vudc_remove(struct platform_device *pdev)
 {
        struct vudc *udc = platform_get_drvdata(pdev);
 
        usb_del_gadget_udc(&udc->gadget);
        cleanup_vudc_hw(udc);
        kfree(udc);
-       return 0;
 }
index 993e721..8bee553 100644 (file)
@@ -19,7 +19,7 @@ MODULE_PARM_DESC(num, "number of emulated controllers");
 
 static struct platform_driver vudc_driver = {
        .probe          = vudc_probe,
-       .remove         = vudc_remove,
+       .remove_new     = vudc_remove,
        .driver         = {
                .name   = GADGET_NAME,
                .dev_groups = vudc_groups,
index 6151c21..2c835e5 100644 (file)
@@ -86,7 +86,7 @@ struct tb {
        unsigned long privdata[];
 };
 
-extern struct bus_type tb_bus_type;
+extern const struct bus_type tb_bus_type;
 extern struct device_type tb_service_type;
 extern struct device_type tb_xdomain_type;
 
index 8c61643..9e52179 100644 (file)
@@ -632,7 +632,6 @@ struct usb3_lpm_parameters {
  * @reset_resume: needs reset instead of resume
  * @port_is_suspended: the upstream port is suspended (L2 or U3)
  * @slot_id: Slot ID assigned by xHCI
- * @removable: Device can be physically removed from this port
  * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout.
  * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout.
  * @u2_params: exit latencies for USB3 U2 LPM state, and hub-initiated timeout.
@@ -1144,16 +1143,6 @@ extern ssize_t usb_store_new_id(struct usb_dynids *dynids,
 
 extern ssize_t usb_show_dynids(struct usb_dynids *dynids, char *buf);
 
-/**
- * struct usbdrv_wrap - wrapper for driver-model structure
- * @driver: The driver-model core driver structure.
- * @for_devices: Non-zero for device drivers, 0 for interface drivers.
- */
-struct usbdrv_wrap {
-       struct device_driver driver;
-       int for_devices;
-};
-
 /**
  * struct usb_driver - identifies USB interface driver to usbcore
  * @name: The driver name should be unique among USB drivers,
@@ -1194,7 +1183,7 @@ struct usbdrv_wrap {
  *     is bound to the driver.
  * @dynids: used internally to hold the list of dynamically added device
  *     ids for this driver.
- * @drvwrap: Driver-model core structure wrapper.
+ * @driver: The driver-model core driver structure.
  * @no_dynamic_id: if set to 1, the USB core will not allow dynamic ids to be
  *     added to this driver by preventing the sysfs file from being created.
  * @supports_autosuspend: if set to 0, the USB core will not allow autosuspend
@@ -1242,13 +1231,13 @@ struct usb_driver {
        const struct attribute_group **dev_groups;
 
        struct usb_dynids dynids;
-       struct usbdrv_wrap drvwrap;
+       struct device_driver driver;
        unsigned int no_dynamic_id:1;
        unsigned int supports_autosuspend:1;
        unsigned int disable_hub_initiated_lpm:1;
        unsigned int soft_unbind:1;
 };
-#define        to_usb_driver(d) container_of(d, struct usb_driver, drvwrap.driver)
+#define        to_usb_driver(d) container_of(d, struct usb_driver, driver)
 
 /**
  * struct usb_device_driver - identifies USB device driver to usbcore
@@ -1264,9 +1253,12 @@ struct usb_driver {
  *     module is being unloaded.
  * @suspend: Called when the device is going to be suspended by the system.
  * @resume: Called when the device is being resumed by the system.
+ * @choose_configuration: If non-NULL, called instead of the default
+ *     usb_choose_configuration(). If this returns an error then we'll go
+ *     on to call the normal usb_choose_configuration().
  * @dev_groups: Attributes attached to the device that will be created once it
  *     is bound to the driver.
- * @drvwrap: Driver-model core structure wrapper.
+ * @driver: The driver-model core driver structure.
  * @id_table: used with @match() to select better matching driver at
  *     probe() time.
  * @supports_autosuspend: if set to 0, the USB core will not allow autosuspend
@@ -1275,7 +1267,7 @@ struct usb_driver {
  *     resume and suspend functions will be called in addition to the driver's
  *     own, so this part of the setup does not need to be replicated.
  *
- * USB drivers must provide all the fields listed above except drvwrap,
+ * USB drivers must provide all the fields listed above except driver,
  * match, and id_table.
  */
 struct usb_device_driver {
@@ -1287,14 +1279,17 @@ struct usb_device_driver {
 
        int (*suspend) (struct usb_device *udev, pm_message_t message);
        int (*resume) (struct usb_device *udev, pm_message_t message);
+
+       int (*choose_configuration) (struct usb_device *udev);
+
        const struct attribute_group **dev_groups;
-       struct usbdrv_wrap drvwrap;
+       struct device_driver driver;
        const struct usb_device_id *id_table;
        unsigned int supports_autosuspend:1;
        unsigned int generic_subclass:1;
 };
 #define        to_usb_device_driver(d) container_of(d, struct usb_device_driver, \
-               drvwrap.driver)
+               driver)
 
 /**
  * struct usb_class_driver - identifies a USB driver that wants to use the USB major number
index 6532beb..a771ccc 100644 (file)
@@ -236,6 +236,7 @@ struct usb_ep {
        unsigned                max_streams:16;
        unsigned                mult:2;
        unsigned                maxburst:5;
+       unsigned                fifo_mode:1;
        u8                      address;
        const struct usb_endpoint_descriptor    *desc;
        const struct usb_ss_ep_comp_descriptor  *comp_desc;
index 00724b4..cd77fc6 100644 (file)
@@ -372,8 +372,9 @@ struct hc_driver {
                 * or bandwidth constraints.
                 */
        void    (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
-               /* Returns the hardware-chosen device address */
-       int     (*address_device)(struct usb_hcd *, struct usb_device *udev);
+               /* Set the hardware-chosen device address */
+       int     (*address_device)(struct usb_hcd *, struct usb_device *udev,
+                                 unsigned int timeout_ms);
                /* prepares the hardware to send commands to the device */
        int     (*enable_device)(struct usb_hcd *, struct usb_device *udev);
                /* Notifies the HCD after a hub descriptor is fetched.
index eeb7c21..59409c1 100644 (file)
@@ -72,4 +72,7 @@
 /* device has endpoints that should be ignored */
 #define USB_QUIRK_ENDPOINT_IGNORE              BIT(15)
 
+/* short SET_ADDRESS request timeout */
+#define USB_QUIRK_SHORT_SET_ADDRESS_REQ_TIMEOUT        BIT(16)
+
 #endif /* __LINUX_USB_QUIRKS_H */
index 8337647..467e804 100644 (file)
@@ -36,7 +36,9 @@
 
 #define TCPC_ALERT_MASK                        0x12
 #define TCPC_POWER_STATUS_MASK         0x14
-#define TCPC_FAULT_STATUS_MASK         0x15
+
+#define TCPC_FAULT_STATUS_MASK                 0x15
+#define TCPC_FAULT_STATUS_MASK_VCONN_OC                BIT(1)
 
 #define TCPC_EXTENDED_STATUS_MASK              0x16
 #define TCPC_EXTENDED_STATUS_MASK_VSAFE0V      BIT(0)
 
 #define TCPC_FAULT_STATUS              0x1f
 #define TCPC_FAULT_STATUS_ALL_REG_RST_TO_DEFAULT BIT(7)
+#define TCPC_FAULT_STATUS_VCONN_OC     BIT(1)
 
 #define TCPC_ALERT_EXTENDED            0x21
 
index ab7ca87..65fac5e 100644 (file)
@@ -173,5 +173,6 @@ void tcpm_pd_hard_reset(struct tcpm_port *port);
 void tcpm_tcpc_reset(struct tcpm_port *port);
 void tcpm_port_clean(struct tcpm_port *port);
 bool tcpm_port_is_toggling(struct tcpm_port *port);
+void tcpm_port_error_recovery(struct tcpm_port *port);
 
 #endif /* __LINUX_USB_TCPM_H */
index d77ee6b..078098e 100644 (file)
@@ -73,8 +73,10 @@ struct usb_os_desc_header {
 struct usb_ext_compat_desc {
        __u8    bFirstInterfaceNumber;
        __u8    Reserved1;
-       __u8    CompatibleID[8];
-       __u8    SubCompatibleID[8];
+       __struct_group(/* no tag */, IDs, /* no attrs */,
+               __u8    CompatibleID[8];
+               __u8    SubCompatibleID[8];
+       );
        __u8    Reserved2[6];
 };