Merge tag 'usb-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 4 Nov 2023 02:00:42 +0000 (16:00 -1000)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 4 Nov 2023 02:00:42 +0000 (16:00 -1000)
Pull USB/Thunderbolt updates from Greg KH:
 "Here is the "big" set of USB and Thunderbolt changes for 6.7-rc1.
  Nothing really major in here, just lots of constant development for
  new hardware. Included in here are:

   - Thunderbolt (i.e. USB4) fixes for reported issues and support for
     new hardware types and devices

   - USB typec additions of new drivers and cleanups for some existing
     ones

   - xhci cleanups and expanded tracing support and some platform
     specific updates

   - USB "La Jolla Cove Adapter (LJCA)" support added, and the gpio,
     spi, and i2c drivers for that type of device (all acked by the
     respective subsystem maintainers.)

   - lots of USB gadget driver updates and cleanups

   - new USB dwc3 platforms supported, as well as other dwc3 fixes and
     cleanups

   - USB chipidea driver updates

   - other smaller driver cleanups and additions, full details in the
     shortlog

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

* tag 'usb-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (167 commits)
  usb: gadget: uvc: Add missing initialization of ssp config descriptor
  usb: storage: set 1.50 as the lower bcdDevice for older "Super Top" compatibility
  usb: raw-gadget: report suspend, resume, reset, and disconnect events
  usb: raw-gadget: don't disable device if usb_ep_queue fails
  usb: raw-gadget: properly handle interrupted requests
  usb:cdnsp: remove TRB_FLUSH_ENDPOINT command
  usb: gadget: aspeed_udc: Convert to platform remove callback returning void
  dt-bindings: usb: fsa4480: Add compatible for OCP96011
  usb: typec: fsa4480: Add support to swap SBU orientation
  dt-bindings: usb: fsa4480: Add data-lanes property to endpoint
  usb: typec: tcpm: Fix NULL pointer dereference in tcpm_pd_svdm()
  Revert "dt-bindings: usb: Add bindings for multiport properties on DWC3 controller"
  Revert "dt-bindings: usb: qcom,dwc3: Add bindings for SC8280 Multiport"
  thunderbolt: Fix one kernel-doc comment
  usb: gadget: f_ncm: Always set current gadget in ncm_bind()
  usb: core: Remove duplicated check in usb_hub_create_port_device
  usb: typec: tcpm: Add additional checks for contaminant
  arm64: dts: rockchip: rk3588s: Add USB3 host controller
  usb: dwc3: add optional PHY interface clocks
  dt-bindings: usb: add rk3588 compatible to rockchip,dwc3
  ...

13 files changed:
1  2 
Documentation/devicetree/bindings/usb/rockchip,dwc3.yaml
Documentation/devicetree/bindings/usb/ti,tps6598x.yaml
arch/arm64/boot/dts/qcom/sm8550-mtp.dts
arch/arm64/boot/dts/qcom/sm8550-qrd.dts
arch/arm64/boot/dts/rockchip/rk3588s.dtsi
drivers/gpio/Kconfig
drivers/platform/chrome/cros_ec_typec.c
drivers/power/supply/tps65217_charger.c
drivers/spi/Kconfig
drivers/thunderbolt/tb.c
drivers/usb/gadget/legacy/inode.c
drivers/usb/host/xhci.h
drivers/usb/typec/altmodes/displayport.c

@@@ -15,14 -15,11 +15,11 @@@ description
    Phy documentation is provided in the following places.
  
    USB2.0 PHY
 -  Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.yaml
 +  Documentation/devicetree/bindings/phy/rockchip,inno-usb2phy.yaml
  
    Type-C PHY
    Documentation/devicetree/bindings/phy/phy-rockchip-typec.txt
  
- allOf:
-   - $ref: snps,dwc3.yaml#
  select:
    properties:
      compatible:
@@@ -30,6 -27,7 +27,7 @@@
          enum:
            - rockchip,rk3328-dwc3
            - rockchip,rk3568-dwc3
+           - rockchip,rk3588-dwc3
    required:
      - compatible
  
@@@ -39,6 -37,7 +37,7 @@@ properties
        - enum:
            - rockchip,rk3328-dwc3
            - rockchip,rk3568-dwc3
+           - rockchip,rk3588-dwc3
        - const: snps,dwc3
  
    reg:
@@@ -58,7 -57,9 +57,9 @@@
            Master/Core clock, must to be >= 62.5 MHz for SS
            operation and >= 30MHz for HS operation
        - description:
-           Controller grf clock
+           Controller grf clock OR UTMI clock
+       - description:
+           PIPE clock
  
    clock-names:
      minItems: 3
        - const: ref_clk
        - const: suspend_clk
        - const: bus_clk
-       - const: grf_clk
+       - enum:
+           - grf_clk
+           - utmi
+       - const: pipe
  
    power-domains:
      maxItems: 1
@@@ -86,6 -90,52 +90,52 @@@ required
    - clocks
    - clock-names
  
+ allOf:
+   - $ref: snps,dwc3.yaml#
+   - if:
+       properties:
+         compatible:
+           contains:
+             const: rockchip,rk3328-dwc3
+     then:
+       properties:
+         clocks:
+           minItems: 3
+           maxItems: 4
+         clock-names:
+           minItems: 3
+           items:
+             - const: ref_clk
+             - const: suspend_clk
+             - const: bus_clk
+             - const: grf_clk
+   - if:
+       properties:
+         compatible:
+           contains:
+             const: rockchip,rk3568-dwc3
+     then:
+       properties:
+         clocks:
+           maxItems: 3
+         clock-names:
+           maxItems: 3
+   - if:
+       properties:
+         compatible:
+           contains:
+             const: rockchip,rk3588-dwc3
+     then:
+       properties:
+         clock-names:
+           minItems: 3
+           items:
+             - const: ref_clk
+             - const: suspend_clk
+             - const: bus_clk
+             - const: utmi
+             - const: pipe
  examples:
    - |
      #include <dt-bindings/clock/rk3328-cru.h>
@@@ -20,8 -20,23 +20,23 @@@ properties
      enum:
        - ti,tps6598x
        - apple,cd321x
+       - ti,tps25750
    reg:
-     maxItems: 1
+     minItems: 1
+     items:
+       - description: main PD controller address
+       - description: |
+           I2C slave address field in PBMs input data
+           which is used as the device address when writing the
+           patch for TPS25750.
+           The patch address can be any value except 0x00, 0x20,
+           0x21, 0x22, and 0x23
+   reg-names:
+     items:
+       - const: main
+       - const: patch-address
  
    wakeup-source: true
  
      items:
        - const: irq
  
 +  connector:
 +    $ref: /schemas/connector/usb-connector.yaml#
 +
+   firmware-name:
+     description: |
+       Should contain the name of the default patch binary
+       file located on the firmware search path which is
+       used to switch the controller into APP mode.
+       This is used when tps25750 doesn't have an EEPROM
+       connected to it.
+     maxItems: 1
  required:
    - compatible
    - reg
  
 -additionalProperties: true
+ allOf:
+   - if:
+       properties:
+         compatible:
+           contains:
+             const: ti,tps25750
+     then:
+       properties:
+         reg:
+           maxItems: 2
+         connector:
+           required:
+             - data-role
+       required:
+         - connector
+         - reg-names
+     else:
+       properties:
+         reg:
+           maxItems: 1
 +additionalProperties: false
  
  examples:
    - |
              };
          };
      };
+   - |
+     #include <dt-bindings/interrupt-controller/irq.h>
+     i2c {
+         #address-cells = <1>;
+         #size-cells = <0>;
+         typec@21 {
+             compatible = "ti,tps25750";
+             reg = <0x21>, <0x0f>;
+             reg-names = "main", "patch-address";
+             interrupt-parent = <&msmgpio>;
+             interrupts = <100 IRQ_TYPE_LEVEL_LOW>;
+             interrupt-names = "irq";
+             firmware-name = "tps25750.bin";
+             pinctrl-names = "default";
+             pinctrl-0 = <&typec_pins>;
+             typec_con0: connector {
+                 compatible = "usb-c-connector";
+                 label = "USB-C";
+                 data-role = "dual";
+                 port {
+                     typec_ep0: endpoint {
+                         remote-endpoint = <&otg_ep>;
+                     };
+                 };
+             };
+         };
+     };
  ...
@@@ -13,8 -13,7 +13,8 @@@
  #include "pm8550ve.dtsi"
  #include "pm8550vs.dtsi"
  #include "pmk8550.dtsi"
 -#include "pmr735d.dtsi"
 +#include "pmr735d_a.dtsi"
 +#include "pmr735d_b.dtsi"
  
  / {
        model = "Qualcomm Technologies, Inc. SM8550 MTP";
@@@ -59,6 -58,7 +59,7 @@@
                compatible = "qcom,sm8550-pmic-glink", "qcom,pmic-glink";
                #address-cells = <1>;
                #size-cells = <0>;
+               orientation-gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>;
  
                connector@0 {
                        compatible = "usb-c-connector";
        vcc-max-microamp = <1300000>;
        vccq-supply = <&vreg_l1g_1p2>;
        vccq-max-microamp = <1200000>;
 -      vccq2-supply = <&vreg_l3g_1p2>;
 -      vccq2-max-microamp = <100>;
 +      vdd-hba-supply = <&vreg_l3g_1p2>;
  
        status = "okay";
  };
@@@ -14,8 -14,7 +14,8 @@@
  #include "pm8550ve.dtsi"
  #include "pm8550vs.dtsi"
  #include "pmk8550.dtsi"
 -#include "pmr735d.dtsi"
 +#include "pmr735d_a.dtsi"
 +#include "pmr735d_b.dtsi"
  
  / {
        model = "Qualcomm Technologies, Inc. SM8550 QRD";
@@@ -24,7 -23,6 +24,7 @@@
  
        aliases {
                serial0 = &uart7;
 +              serial1 = &uart14;
        };
  
        wcd938x: audio-codec {
@@@ -77,6 -75,7 +77,7 @@@
                compatible = "qcom,sm8550-pmic-glink", "qcom,pmic-glink";
                #address-cells = <1>;
                #size-cells = <0>;
+               orientation-gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>;
  
                connector@0 {
                        compatible = "usb-c-connector";
        status = "okay";
  };
  
 +&qupv3_id_1 {
 +      status = "okay";
 +};
 +
  &remoteproc_adsp {
        firmware-name = "qcom/sm8550/adsp.mbn",
                        "qcom/sm8550/adsp_dtb.mbn";
  &tlmm {
        gpio-reserved-ranges = <32 8>;
  
 +      bt_default: bt-default-state {
 +              bt-en-pins {
 +                      pins = "gpio81";
 +                      function = "gpio";
 +                      drive-strength = <16>;
 +                      bias-disable;
 +              };
 +
 +              sw-ctrl-pins {
 +                      pins = "gpio82";
 +                      function = "gpio";
 +                      bias-pull-down;
 +              };
 +      };
 +
        sde_dsi_active: sde-dsi-active-state {
                pins = "gpio133";
                function = "gpio";
        status = "okay";
  };
  
 +&uart14 {
 +      status = "okay";
 +
 +      bluetooth {
 +              compatible = "qcom,wcn7850-bt";
 +
 +              vddio-supply = <&vreg_l15b_1p8>;
 +              vddaon-supply = <&vreg_s4e_0p95>;
 +              vdddig-supply = <&vreg_s4e_0p95>;
 +              vddrfa0p8-supply = <&vreg_s4e_0p95>;
 +              vddrfa1p2-supply = <&vreg_s4g_1p25>;
 +              vddrfa1p9-supply = <&vreg_s6g_1p86>;
 +
 +              max-speed = <3200000>;
 +
 +              enable-gpios = <&tlmm 81 GPIO_ACTIVE_HIGH>;
 +              swctrl-gpios = <&tlmm 82 GPIO_ACTIVE_HIGH>;
 +
 +              pinctrl-0 = <&bt_default>;
 +              pinctrl-names = "default";
 +      };
 +};
 +
  &ufs_mem_hc {
        reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
        vcc-supply = <&vreg_l17b_2p5>;
        vcc-max-microamp = <1300000>;
        vccq-supply = <&vreg_l1g_1p2>;
        vccq-max-microamp = <1200000>;
 -      vccq2-supply = <&vreg_l3g_1p2>;
 -      vccq2-max-microamp = <100>;
 +      vdd-hba-supply = <&vreg_l3g_1p2>;
  
        status = "okay";
  };
                status = "disabled";
        };
  
+       usb_host2_xhci: usb@fcd00000 {
+               compatible = "rockchip,rk3588-dwc3", "snps,dwc3";
+               reg = <0x0 0xfcd00000 0x0 0x400000>;
+               interrupts = <GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH 0>;
+               clocks = <&cru REF_CLK_USB3OTG2>, <&cru SUSPEND_CLK_USB3OTG2>,
+                        <&cru ACLK_USB3OTG2>, <&cru CLK_UTMI_OTG2>,
+                        <&cru CLK_PIPEPHY2_PIPE_U3_G>;
+               clock-names = "ref_clk", "suspend_clk", "bus_clk", "utmi", "pipe";
+               dr_mode = "host";
+               phys = <&combphy2_psu PHY_TYPE_USB3>;
+               phy-names = "usb3-phy";
+               phy_type = "utmi_wide";
+               resets = <&cru SRST_A_USB3OTG2>;
+               snps,dis_enblslpm_quirk;
+               snps,dis-u2-freeclk-exists-quirk;
+               snps,dis-del-phy-power-chg-quirk;
+               snps,dis-tx-ipgap-linecheck-quirk;
+               snps,dis_rxdet_inp3_quirk;
+               status = "disabled";
+       };
 +      pmu1grf: syscon@fd58a000 {
 +              compatible = "rockchip,rk3588-pmugrf", "syscon", "simple-mfd";
 +              reg = <0x0 0xfd58a000 0x0 0x10000>;
 +      };
 +
        sys_grf: syscon@fd58c000 {
                compatible = "rockchip,rk3588-sys-grf", "syscon";
                reg = <0x0 0xfd58c000 0x0 0x1000>;
                };
        };
  
 +      dfi: dfi@fe060000 {
 +              reg = <0x00 0xfe060000 0x00 0x10000>;
 +              compatible = "rockchip,rk3588-dfi";
 +              interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH 0>,
 +                           <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH 0>,
 +                           <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH 0>,
 +                           <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH 0>;
 +              interrupt-names = "ch0", "ch1", "ch2", "ch3";
 +              rockchip,pmu = <&pmu1grf>;
 +      };
 +
        gmac1: ethernet@fe1c0000 {
                compatible = "rockchip,rk3588-gmac", "snps,dwmac-4.20a";
                reg = <0x0 0xfe1c0000 0x0 0x10000>;
                };
        };
  
 +      sfc: spi@fe2b0000 {
 +              compatible = "rockchip,sfc";
 +              reg = <0x0 0xfe2b0000 0x0 0x4000>;
 +              interrupts = <GIC_SPI 206 IRQ_TYPE_LEVEL_HIGH 0>;
 +              clocks = <&cru SCLK_SFC>, <&cru HCLK_SFC>;
 +              clock-names = "clk_sfc", "hclk_sfc";
 +              #address-cells = <1>;
 +              #size-cells = <0>;
 +              status = "disabled";
 +      };
 +
        sdmmc: mmc@fe2c0000 {
                compatible = "rockchip,rk3588-dw-mshc", "rockchip,rk3288-dw-mshc";
                reg = <0x0 0xfe2c0000 0x0 0x4000>;
                        #interrupt-cells = <2>;
                };
        };
 +
 +      av1d: video-codec@fdc70000 {
 +              compatible = "rockchip,rk3588-av1-vpu";
 +              reg = <0x0 0xfdc70000 0x0 0x800>;
 +              interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH 0>;
 +              interrupt-names = "vdpu";
 +              assigned-clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>;
 +              assigned-clock-rates = <400000000>, <400000000>;
 +              clocks = <&cru ACLK_AV1>, <&cru PCLK_AV1>;
 +              clock-names = "aclk", "hclk";
 +              power-domains = <&power RK3588_PD_AV1>;
 +              resets = <&cru SRST_A_AV1>, <&cru SRST_P_AV1>, <&cru SRST_A_AV1_BIU>, <&cru SRST_P_AV1_BIU>;
 +      };
  };
  
  #include "rk3588s-pinctrl.dtsi"
diff --combined drivers/gpio/Kconfig
@@@ -1312,9 -1312,9 +1312,9 @@@ config GPIO_KEMPL
  
  config GPIO_LJCA
        tristate "INTEL La Jolla Cove Adapter GPIO support"
-       depends on MFD_LJCA
+       depends on USB_LJCA
        select GPIOLIB_IRQCHIP
-       default MFD_LJCA
+       default USB_LJCA
        help
          Select this option to enable GPIO driver for the INTEL
          La Jolla Cove Adapter (LJCA) board.
@@@ -1790,11 -1790,9 +1790,11 @@@ config GPIO_LATC
          connected to other GPIOs.
  
  config GPIO_MOCKUP
 -      tristate "GPIO Testing Driver"
 +      tristate "GPIO Testing Driver (DEPRECATED)"
        select IRQ_SIM
        help
 +        This module is DEPRECATED. Please consider using gpio-sim instead.
 +
          This enables GPIO Testing driver, which provides a way to test GPIO
          subsystem through sysfs (or char device) and debugfs.
          User could use it through the script in
@@@ -80,28 -80,28 +80,28 @@@ static int cros_typec_get_switch_handle
        port->mux = fwnode_typec_mux_get(fwnode);
        if (IS_ERR(port->mux)) {
                ret = PTR_ERR(port->mux);
 -              dev_dbg(dev, "Mux handle not found: %d.\n", ret);
 +              dev_err_probe(dev, ret, "Mux handle not found\n");
                goto mux_err;
        }
  
        port->retimer = fwnode_typec_retimer_get(fwnode);
        if (IS_ERR(port->retimer)) {
                ret = PTR_ERR(port->retimer);
 -              dev_dbg(dev, "Retimer handle not found: %d.\n", ret);
 +              dev_err_probe(dev, ret, "Retimer handle not found\n");
                goto retimer_sw_err;
        }
  
        port->ori_sw = fwnode_typec_switch_get(fwnode);
        if (IS_ERR(port->ori_sw)) {
                ret = PTR_ERR(port->ori_sw);
 -              dev_dbg(dev, "Orientation switch handle not found: %d\n", ret);
 +              dev_err_probe(dev, ret, "Orientation switch handle not found\n");
                goto ori_sw_err;
        }
  
        port->role_sw = fwnode_usb_role_switch_get(fwnode);
        if (IS_ERR(port->role_sw)) {
                ret = PTR_ERR(port->role_sw);
 -              dev_dbg(dev, "USB role switch handle not found: %d\n", ret);
 +              dev_err_probe(dev, ret, "USB role switch handle not found\n");
                goto role_sw_err;
        }
  
@@@ -271,9 -271,9 +271,9 @@@ static int cros_typec_register_port_alt
        struct typec_altmode *amode;
  
        /* All PD capable CrOS devices are assumed to support DP altmode. */
 -      desc.svid = USB_TYPEC_DP_SID,
 -      desc.mode = USB_TYPEC_DP_MODE,
 -      desc.vdo = DP_PORT_VDO,
 +      desc.svid = USB_TYPEC_DP_SID;
 +      desc.mode = USB_TYPEC_DP_MODE;
 +      desc.vdo = DP_PORT_VDO;
        amode = typec_port_register_altmode(port->port, &desc);
        if (IS_ERR(amode))
                return PTR_ERR(amode);
         * here for now.
         */
        memset(&desc, 0, sizeof(desc));
 -      desc.svid = USB_TYPEC_TBT_SID,
 -      desc.mode = TYPEC_ANY_MODE,
 +      desc.svid = USB_TYPEC_TBT_SID;
 +      desc.mode = TYPEC_ANY_MODE;
        amode = typec_port_register_altmode(port->port, &desc);
        if (IS_ERR(amode))
                return PTR_ERR(amode);
@@@ -492,6 -492,8 +492,8 @@@ static int cros_typec_enable_dp(struct 
  {
        struct cros_typec_port *port = typec->ports[port_num];
        struct typec_displayport_data dp_data;
+       u32 cable_tbt_vdo;
+       u32 cable_dp_vdo;
        int ret;
  
        if (typec->pd_ctrl_ver < 2) {
        port->state.data = &dp_data;
        port->state.mode = TYPEC_MODAL_STATE(ffs(pd_ctrl->dp_mode));
  
+       /* Get cable VDO for cables with DPSID to check DPAM2.1 is supported */
+       cable_dp_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_DP_SID);
+       /**
+        * Get cable VDO for thunderbolt cables and cables with DPSID but does not
+        * support DPAM2.1.
+        */
+       cable_tbt_vdo = cros_typec_get_cable_vdo(port, USB_TYPEC_TBT_SID);
+       if (cable_dp_vdo & DP_CAP_DPAM_VERSION) {
+               dp_data.conf |= cable_dp_vdo;
+       } else if (cable_tbt_vdo) {
+               dp_data.conf |=  TBT_CABLE_SPEED(cable_tbt_vdo) << DP_CONF_SIGNALLING_SHIFT;
+               /* Cable Type */
+               if (cable_tbt_vdo & TBT_CABLE_OPTICAL)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_OPTICAL << DP_CONF_CABLE_TYPE_SHIFT;
+               else if (cable_tbt_vdo & TBT_CABLE_RETIMER)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_RE_TIMER << DP_CONF_CABLE_TYPE_SHIFT;
+               else if (cable_tbt_vdo & TBT_CABLE_ACTIVE_PASSIVE)
+                       dp_data.conf |= DP_CONF_CABLE_TYPE_RE_DRIVER << DP_CONF_CABLE_TYPE_SHIFT;
+       } else if (PD_IDH_PTYPE(port->c_identity.id_header) == IDH_PTYPE_PCABLE) {
+               dp_data.conf |= VDO_TYPEC_CABLE_SPEED(port->c_identity.vdo[0]) <<
+                               DP_CONF_SIGNALLING_SHIFT;
+       }
        ret = cros_typec_retimer_set(port->retimer, port->state);
        if (!ret)
                ret = typec_mux_set(port->mux, &port->state);
@@@ -237,7 -237,7 +237,7 @@@ static int tps65217_charger_probe(struc
        for (i = 0; i < NUM_CHARGER_IRQS; i++) {
                ret = devm_request_threaded_irq(&pdev->dev, irq[i], NULL,
                                                tps65217_charger_irq,
-                                               IRQF_ONESHOT, "tps65217-charger",
+                                               IRQF_SHARED, "tps65217-charger",
                                                charger);
                if (ret) {
                        dev_err(charger->dev,
        return 0;
  }
  
 -static int tps65217_charger_remove(struct platform_device *pdev)
 +static void tps65217_charger_remove(struct platform_device *pdev)
  {
        struct tps65217_charger *charger = platform_get_drvdata(pdev);
  
        if (charger->poll_task)
                kthread_stop(charger->poll_task);
 -
 -      return 0;
  }
  
  static const struct of_device_id tps65217_charger_match_table[] = {
@@@ -269,7 -271,7 +269,7 @@@ MODULE_DEVICE_TABLE(of, tps65217_charge
  
  static struct platform_driver tps65217_charger_driver = {
        .probe  = tps65217_charger_probe,
 -      .remove = tps65217_charger_remove,
 +      .remove_new = tps65217_charger_remove,
        .driver = {
                .name   = "tps65217-charger",
                .of_match_table = of_match_ptr(tps65217_charger_match_table),
diff --combined drivers/spi/Kconfig
@@@ -616,6 -616,17 +616,17 @@@ config SPI_FSL_ESP
          From MPC8536, 85xx platform uses the controller, and all P10xx,
          P20xx, P30xx,P40xx, P50xx uses this controller.
  
+ config SPI_LJCA
+       tristate "Intel La Jolla Cove Adapter SPI support"
+       depends on USB_LJCA
+       default USB_LJCA
+       help
+         Select this option to enable SPI driver for the Intel
+         La Jolla Cove Adapter (LJCA) board.
+         This driver can also be built as a module. If so, the module
+         will be called spi-ljca.
  config SPI_MESON_SPICC
        tristate "Amlogic Meson SPICC controller"
        depends on COMMON_CLK
@@@ -862,8 -873,7 +873,8 @@@ config SPI_RZV2M_CS
        tristate "Renesas RZ/V2M CSI controller"
        depends on ARCH_RENESAS || COMPILE_TEST
        help
 -        SPI driver for Renesas RZ/V2M Clocked Serial Interface (CSI)
 +        SPI driver for Renesas RZ/V2M Clocked Serial Interface (CSI).
 +        CSI supports both SPI host and SPI target roles.
  
  config SPI_QCOM_QSPI
        tristate "QTI QSPI controller"
diff --combined drivers/thunderbolt/tb.c
  #include "tb_regs.h"
  #include "tunnel.h"
  
- #define TB_TIMEOUT    100     /* ms */
- #define MAX_GROUPS    7       /* max Group_ID is 7 */
+ #define TB_TIMEOUT            100     /* ms */
+ /*
+  * Minimum bandwidth (in Mb/s) that is needed in the single transmitter/receiver
+  * direction. This is 40G - 10% guard band bandwidth.
+  */
+ #define TB_ASYM_MIN           (40000 * 90 / 100)
+ /*
+  * Threshold bandwidth (in Mb/s) that is used to switch the links to
+  * asymmetric and back. This is selected as 45G which means when the
+  * request is higher than this, we switch the link to asymmetric, and
+  * when it is less than this we switch it back. The 45G is selected so
+  * that we still have 27G (of the total 72G) for bulk PCIe traffic when
+  * switching back to symmetric.
+  */
+ #define TB_ASYM_THRESHOLD     45000
+ #define MAX_GROUPS            7       /* max Group_ID is 7 */
+ static unsigned int asym_threshold = TB_ASYM_THRESHOLD;
+ module_param_named(asym_threshold, asym_threshold, uint, 0444);
+ MODULE_PARM_DESC(asym_threshold,
+               "threshold (Mb/s) when to Gen 4 switch link symmetry. 0 disables. (default: "
+               __MODULE_STRING(TB_ASYM_THRESHOLD) ")");
  
  /**
   * struct tb_cm - Simple Thunderbolt connection manager
@@@ -190,7 -213,7 +213,7 @@@ static void tb_add_dp_resources(struct 
                if (!tb_switch_query_dp_resource(sw, port))
                        continue;
  
-               list_add_tail(&port->list, &tcm->dp_resources);
+               list_add(&port->list, &tcm->dp_resources);
                tb_port_dbg(port, "DP IN resource available\n");
        }
  }
@@@ -255,13 -278,13 +278,13 @@@ static int tb_enable_clx(struct tb_swit
         * this in the future to cover the whole topology if it turns
         * out to be beneficial.
         */
-       while (sw && sw->config.depth > 1)
+       while (sw && tb_switch_depth(sw) > 1)
                sw = tb_switch_parent(sw);
  
        if (!sw)
                return 0;
  
-       if (sw->config.depth != 1)
+       if (tb_switch_depth(sw) != 1)
                return 0;
  
        /*
        return ret == -EOPNOTSUPP ? 0 : ret;
  }
  
- /* Disables CL states up to the host router */
- static void tb_disable_clx(struct tb_switch *sw)
+ /**
+  * tb_disable_clx() - Disable CL states up to host router
+  * @sw: Router to start
+  *
+  * Disables CL states from @sw up to the host router. Returns true if
+  * any CL state were disabled. This can be used to figure out whether
+  * the link was setup by us or the boot firmware so we don't
+  * accidentally enable them if they were not enabled during discovery.
+  */
+ static bool tb_disable_clx(struct tb_switch *sw)
  {
+       bool disabled = false;
        do {
-               if (tb_switch_clx_disable(sw) < 0)
+               int ret;
+               ret = tb_switch_clx_disable(sw);
+               if (ret > 0)
+                       disabled = true;
+               else if (ret < 0)
                        tb_sw_warn(sw, "failed to disable CL states\n");
                sw = tb_switch_parent(sw);
        } while (sw);
+       return disabled;
  }
  
  static int tb_increase_switch_tmu_accuracy(struct device *dev, void *data)
@@@ -553,7 -594,7 +594,7 @@@ static struct tb_tunnel *tb_find_first_
        struct tb_switch *sw;
  
        /* Pick the router that is deepest in the topology */
-       if (dst_port->sw->config.depth > src_port->sw->config.depth)
+       if (tb_port_path_direction_downstream(src_port, dst_port))
                sw = dst_port->sw;
        else
                sw = src_port->sw;
        return tb_find_tunnel(tb, TB_TUNNEL_USB3, usb3_down, NULL);
  }
  
- static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
-       struct tb_port *dst_port, int *available_up, int *available_down)
+ /**
+  * tb_consumed_usb3_pcie_bandwidth() - Consumed USB3/PCIe bandwidth over a single link
+  * @tb: Domain structure
+  * @src_port: Source protocol adapter
+  * @dst_port: Destination protocol adapter
+  * @port: USB4 port the consumed bandwidth is calculated
+  * @consumed_up: Consumed upsream bandwidth (Mb/s)
+  * @consumed_down: Consumed downstream bandwidth (Mb/s)
+  *
+  * Calculates consumed USB3 and PCIe bandwidth at @port between path
+  * from @src_port to @dst_port. Does not take tunnel starting from
+  * @src_port and ending from @src_port into account.
+  */
+ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb,
+                                          struct tb_port *src_port,
+                                          struct tb_port *dst_port,
+                                          struct tb_port *port,
+                                          int *consumed_up,
+                                          int *consumed_down)
  {
-       int usb3_consumed_up, usb3_consumed_down, ret;
-       struct tb_cm *tcm = tb_priv(tb);
+       int pci_consumed_up, pci_consumed_down;
        struct tb_tunnel *tunnel;
-       struct tb_port *port;
  
-       tb_dbg(tb, "calculating available bandwidth between %llx:%u <-> %llx:%u\n",
-              tb_route(src_port->sw), src_port->port, tb_route(dst_port->sw),
-              dst_port->port);
+       *consumed_up = *consumed_down = 0;
  
        tunnel = tb_find_first_usb3_tunnel(tb, src_port, dst_port);
        if (tunnel && tunnel->src_port != src_port &&
            tunnel->dst_port != dst_port) {
-               ret = tb_tunnel_consumed_bandwidth(tunnel, &usb3_consumed_up,
-                                                  &usb3_consumed_down);
+               int ret;
+               ret = tb_tunnel_consumed_bandwidth(tunnel, consumed_up,
+                                                  consumed_down);
                if (ret)
                        return ret;
-       } else {
-               usb3_consumed_up = 0;
-               usb3_consumed_down = 0;
        }
  
-       /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
-       *available_up = *available_down = 120000;
+       /*
+        * If there is anything reserved for PCIe bulk traffic take it
+        * into account here too.
+        */
+       if (tb_tunnel_reserved_pci(port, &pci_consumed_up, &pci_consumed_down)) {
+               *consumed_up += pci_consumed_up;
+               *consumed_down += pci_consumed_down;
+       }
  
-       /* Find the minimum available bandwidth over all links */
-       tb_for_each_port_on_path(src_port, dst_port, port) {
-               int link_speed, link_width, up_bw, down_bw;
+       return 0;
+ }
  
-               if (!tb_port_is_null(port))
+ /**
+  * tb_consumed_dp_bandwidth() - Consumed DP bandwidth over a single link
+  * @tb: Domain structure
+  * @src_port: Source protocol adapter
+  * @dst_port: Destination protocol adapter
+  * @port: USB4 port the consumed bandwidth is calculated
+  * @consumed_up: Consumed upsream bandwidth (Mb/s)
+  * @consumed_down: Consumed downstream bandwidth (Mb/s)
+  *
+  * Calculates consumed DP bandwidth at @port between path from @src_port
+  * to @dst_port. Does not take tunnel starting from @src_port and ending
+  * from @src_port into account.
+  */
+ static int tb_consumed_dp_bandwidth(struct tb *tb,
+                                   struct tb_port *src_port,
+                                   struct tb_port *dst_port,
+                                   struct tb_port *port,
+                                   int *consumed_up,
+                                   int *consumed_down)
+ {
+       struct tb_cm *tcm = tb_priv(tb);
+       struct tb_tunnel *tunnel;
+       int ret;
+       *consumed_up = *consumed_down = 0;
+       /*
+        * Find all DP tunnels that cross the port and reduce
+        * their consumed bandwidth from the available.
+        */
+       list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
+               int dp_consumed_up, dp_consumed_down;
+               if (tb_tunnel_is_invalid(tunnel))
                        continue;
  
-               if (tb_is_upstream_port(port)) {
-                       link_speed = port->sw->link_speed;
+               if (!tb_tunnel_is_dp(tunnel))
+                       continue;
+               if (!tb_tunnel_port_on_path(tunnel, port))
+                       continue;
+               /*
+                * Ignore the DP tunnel between src_port and dst_port
+                * because it is the same tunnel and we may be
+                * re-calculating estimated bandwidth.
+                */
+               if (tunnel->src_port == src_port &&
+                   tunnel->dst_port == dst_port)
+                       continue;
+               ret = tb_tunnel_consumed_bandwidth(tunnel, &dp_consumed_up,
+                                                  &dp_consumed_down);
+               if (ret)
+                       return ret;
+               *consumed_up += dp_consumed_up;
+               *consumed_down += dp_consumed_down;
+       }
+       return 0;
+ }
+ static bool tb_asym_supported(struct tb_port *src_port, struct tb_port *dst_port,
+                             struct tb_port *port)
+ {
+       bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       enum tb_link_width width;
+       if (tb_is_upstream_port(port))
+               width = downstream ? TB_LINK_WIDTH_ASYM_RX : TB_LINK_WIDTH_ASYM_TX;
+       else
+               width = downstream ? TB_LINK_WIDTH_ASYM_TX : TB_LINK_WIDTH_ASYM_RX;
+       return tb_port_width_supported(port, width);
+ }
+ /**
+  * tb_maximum_bandwidth() - Maximum bandwidth over a single link
+  * @tb: Domain structure
+  * @src_port: Source protocol adapter
+  * @dst_port: Destination protocol adapter
+  * @port: USB4 port the total bandwidth is calculated
+  * @max_up: Maximum upstream bandwidth (Mb/s)
+  * @max_down: Maximum downstream bandwidth (Mb/s)
+  * @include_asym: Include bandwidth if the link is switched from
+  *              symmetric to asymmetric
+  *
+  * Returns maximum possible bandwidth in @max_up and @max_down over a
+  * single link at @port. If @include_asym is set then includes the
+  * additional banwdith if the links are transitioned into asymmetric to
+  * direction from @src_port to @dst_port.
+  */
+ static int tb_maximum_bandwidth(struct tb *tb, struct tb_port *src_port,
+                               struct tb_port *dst_port, struct tb_port *port,
+                               int *max_up, int *max_down, bool include_asym)
+ {
+       bool downstream = tb_port_path_direction_downstream(src_port, dst_port);
+       int link_speed, link_width, up_bw, down_bw;
+       /*
+        * Can include asymmetric, only if it is actually supported by
+        * the lane adapter.
+        */
+       if (!tb_asym_supported(src_port, dst_port, port))
+               include_asym = false;
+       if (tb_is_upstream_port(port)) {
+               link_speed = port->sw->link_speed;
+               /*
+                * sw->link_width is from upstream perspective so we use
+                * the opposite for downstream of the host router.
+                */
+               if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * 3 * 1000;
+                       down_bw = link_speed * 1 * 1000;
+               } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       up_bw = link_speed * 1 * 1000;
+                       down_bw = link_speed * 3 * 1000;
+               } else if (include_asym) {
                        /*
-                        * sw->link_width is from upstream perspective
-                        * so we use the opposite for downstream of the
-                        * host router.
+                        * The link is symmetric at the moment but we
+                        * can switch it to asymmetric as needed. Report
+                        * this bandwidth as available (even though it
+                        * is not yet enabled).
                         */
-                       if (port->sw->link_width == TB_LINK_WIDTH_ASYM_TX) {
-                               up_bw = link_speed * 3 * 1000;
-                               down_bw = link_speed * 1 * 1000;
-                       } else if (port->sw->link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       if (downstream) {
                                up_bw = link_speed * 1 * 1000;
                                down_bw = link_speed * 3 * 1000;
                        } else {
-                               up_bw = link_speed * port->sw->link_width * 1000;
-                               down_bw = up_bw;
+                               up_bw = link_speed * 3 * 1000;
+                               down_bw = link_speed * 1 * 1000;
                        }
                } else {
-                       link_speed = tb_port_get_link_speed(port);
-                       if (link_speed < 0)
-                               return link_speed;
-                       link_width = tb_port_get_link_width(port);
-                       if (link_width < 0)
-                               return link_width;
-                       if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * port->sw->link_width * 1000;
+                       down_bw = up_bw;
+               }
+       } else {
+               link_speed = tb_port_get_link_speed(port);
+               if (link_speed < 0)
+                       return link_speed;
+               link_width = tb_port_get_link_width(port);
+               if (link_width < 0)
+                       return link_width;
+               if (link_width == TB_LINK_WIDTH_ASYM_TX) {
+                       up_bw = link_speed * 1 * 1000;
+                       down_bw = link_speed * 3 * 1000;
+               } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       up_bw = link_speed * 3 * 1000;
+                       down_bw = link_speed * 1 * 1000;
+               } else if (include_asym) {
+                       /*
+                        * The link is symmetric at the moment but we
+                        * can switch it to asymmetric as needed. Report
+                        * this bandwidth as available (even though it
+                        * is not yet enabled).
+                        */
+                       if (downstream) {
                                up_bw = link_speed * 1 * 1000;
                                down_bw = link_speed * 3 * 1000;
-                       } else if (link_width == TB_LINK_WIDTH_ASYM_RX) {
+                       } else {
                                up_bw = link_speed * 3 * 1000;
                                down_bw = link_speed * 1 * 1000;
-                       } else {
-                               up_bw = link_speed * link_width * 1000;
-                               down_bw = up_bw;
                        }
+               } else {
+                       up_bw = link_speed * link_width * 1000;
+                       down_bw = up_bw;
                }
+       }
  
-               /* Leave 10% guard band */
-               up_bw -= up_bw / 10;
-               down_bw -= down_bw / 10;
-               tb_port_dbg(port, "link total bandwidth %d/%d Mb/s\n", up_bw,
-                           down_bw);
+       /* Leave 10% guard band */
+       *max_up = up_bw - up_bw / 10;
+       *max_down = down_bw - down_bw / 10;
  
-               /*
-                * Find all DP tunnels that cross the port and reduce
-                * their consumed bandwidth from the available.
-                */
-               list_for_each_entry(tunnel, &tcm->tunnel_list, list) {
-                       int dp_consumed_up, dp_consumed_down;
+       tb_port_dbg(port, "link maximum bandwidth %d/%d Mb/s\n", *max_up, *max_down);
+       return 0;
+ }
  
-                       if (tb_tunnel_is_invalid(tunnel))
-                               continue;
+ /**
+  * tb_available_bandwidth() - Available bandwidth for tunneling
+  * @tb: Domain structure
+  * @src_port: Source protocol adapter
+  * @dst_port: Destination protocol adapter
+  * @available_up: Available bandwidth upstream (Mb/s)
+  * @available_down: Available bandwidth downstream (Mb/s)
+  * @include_asym: Include bandwidth if the link is switched from
+  *              symmetric to asymmetric
+  *
+  * Calculates maximum available bandwidth for protocol tunneling between
+  * @src_port and @dst_port at the moment. This is minimum of maximum
+  * link bandwidth across all links reduced by currently consumed
+  * bandwidth on that link.
+  *
+  * If @include_asym is true then includes also bandwidth that can be
+  * added when the links are transitioned into asymmetric (but does not
+  * transition the links).
+  */
+ static int tb_available_bandwidth(struct tb *tb, struct tb_port *src_port,
+                                struct tb_port *dst_port, int *available_up,
+                                int *available_down, bool include_asym)
+ {
+       struct tb_port *port;
+       int ret;
  
-                       if (!tb_tunnel_is_dp(tunnel))
-                               continue;
+       /* Maximum possible bandwidth asymmetric Gen 4 link is 120 Gb/s */
+       *available_up = *available_down = 120000;
  
-                       if (!tb_tunnel_port_on_path(tunnel, port))
-                               continue;
+       /* Find the minimum available bandwidth over all links */
+       tb_for_each_port_on_path(src_port, dst_port, port) {
+               int max_up, max_down, consumed_up, consumed_down;
  
-                       /*
-                        * Ignore the DP tunnel between src_port and
-                        * dst_port because it is the same tunnel and we
-                        * may be re-calculating estimated bandwidth.
-                        */
-                       if (tunnel->src_port == src_port &&
-                           tunnel->dst_port == dst_port)
-                               continue;
+               if (!tb_port_is_null(port))
+                       continue;
  
-                       ret = tb_tunnel_consumed_bandwidth(tunnel,
-                                                          &dp_consumed_up,
-                                                          &dp_consumed_down);
-                       if (ret)
-                               return ret;
+               ret = tb_maximum_bandwidth(tb, src_port, dst_port, port,
+                                          &max_up, &max_down, include_asym);
+               if (ret)
+                       return ret;
  
-                       up_bw -= dp_consumed_up;
-                       down_bw -= dp_consumed_down;
-               }
+               ret = tb_consumed_usb3_pcie_bandwidth(tb, src_port, dst_port,
+                                                     port, &consumed_up,
+                                                     &consumed_down);
+               if (ret)
+                       return ret;
+               max_up -= consumed_up;
+               max_down -= consumed_down;
  
-               /*
-                * If USB3 is tunneled from the host router down to the
-                * branch leading to port we need to take USB3 consumed
-                * bandwidth into account regardless whether it actually
-                * crosses the port.
-                */
-               up_bw -= usb3_consumed_up;
-               down_bw -= usb3_consumed_down;
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, port,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       return ret;
+               max_up -= consumed_up;
+               max_down -= consumed_down;
  
-               if (up_bw < *available_up)
-                       *available_up = up_bw;
-               if (down_bw < *available_down)
-                       *available_down = down_bw;
+               if (max_up < *available_up)
+                       *available_up = max_up;
+               if (max_down < *available_down)
+                       *available_down = max_down;
        }
  
        if (*available_up < 0)
@@@ -729,21 -931,21 +931,21 @@@ static void tb_reclaim_usb3_bandwidth(s
        if (!tunnel)
                return;
  
-       tb_dbg(tb, "reclaiming unused bandwidth for USB3\n");
+       tb_tunnel_dbg(tunnel, "reclaiming unused bandwidth\n");
  
        /*
         * Calculate available bandwidth for the first hop USB3 tunnel.
         * That determines the whole USB3 bandwidth for this branch.
         */
        ret = tb_available_bandwidth(tb, tunnel->src_port, tunnel->dst_port,
-                                    &available_up, &available_down);
+                                    &available_up, &available_down, false);
        if (ret) {
-               tb_warn(tb, "failed to calculate available bandwidth\n");
+               tb_tunnel_warn(tunnel, "failed to calculate available bandwidth\n");
                return;
        }
  
-       tb_dbg(tb, "available bandwidth for USB3 %d/%d Mb/s\n",
-              available_up, available_down);
+       tb_tunnel_dbg(tunnel, "available bandwidth %d/%d Mb/s\n", available_up,
+                     available_down);
  
        tb_tunnel_reclaim_available_bandwidth(tunnel, &available_up, &available_down);
  }
@@@ -794,8 -996,8 +996,8 @@@ static int tb_tunnel_usb3(struct tb *tb
                        return ret;
        }
  
-       ret = tb_available_bandwidth(tb, down, up, &available_up,
-                                    &available_down);
+       ret = tb_available_bandwidth(tb, down, up, &available_up, &available_down,
+                                    false);
        if (ret)
                goto err_reclaim;
  
@@@ -856,6 -1058,225 +1058,225 @@@ static int tb_create_usb3_tunnels(struc
        return 0;
  }
  
+ /**
+  * tb_configure_asym() - Transition links to asymmetric if needed
+  * @tb: Domain structure
+  * @src_port: Source adapter to start the transition
+  * @dst_port: Destination adapter
+  * @requested_up: Additional bandwidth (Mb/s) required upstream
+  * @requested_down: Additional bandwidth (Mb/s) required downstream
+  *
+  * Transition links between @src_port and @dst_port into asymmetric, with
+  * three lanes in the direction from @src_port towards @dst_port and one lane
+  * in the opposite direction, if the bandwidth requirements
+  * (requested + currently consumed) on that link exceed @asym_threshold.
+  *
+  * Must be called with available >= requested over all links.
+  */
+ static int tb_configure_asym(struct tb *tb, struct tb_port *src_port,
+                            struct tb_port *dst_port, int requested_up,
+                            int requested_down)
+ {
+       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)
+               sw = dst_port->sw;
+       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;
+               enum tb_link_width width;
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       break;
+               if (downstream) {
+                       /*
+                        * Downstream so make sure upstream is within the 36G
+                        * (40G - guard band 10%), and the requested is above
+                        * what the threshold is.
+                        */
+                       if (consumed_up + requested_up >= TB_ASYM_MIN) {
+                               ret = -ENOBUFS;
+                               break;
+                       }
+                       /* Does consumed + requested exceed the threshold */
+                       if (consumed_down + requested_down < asym_threshold)
+                               continue;
+                       width = TB_LINK_WIDTH_ASYM_RX;
+               } else {
+                       /* Upstream, the opposite of above */
+                       if (consumed_down + requested_down >= TB_ASYM_MIN) {
+                               ret = -ENOBUFS;
+                               break;
+                       }
+                       if (consumed_up + requested_up < asym_threshold)
+                               continue;
+                       width = TB_LINK_WIDTH_ASYM_TX;
+               }
+               if (up->sw->link_width == width)
+                       continue;
+               if (!tb_port_width_supported(up, width))
+                       continue;
+               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);
+               if (ret) {
+                       tb_sw_warn(up->sw, "failed to set link width\n");
+                       break;
+               }
+       }
+       /* Re-enable CL states if they were previosly enabled */
+       if (clx)
+               tb_enable_clx(sw);
+       return ret;
+ }
+ /**
+  * tb_configure_sym() - Transition links to symmetric if possible
+  * @tb: Domain structure
+  * @src_port: Source adapter to start the transition
+  * @dst_port: Destination adapter
+  * @requested_up: New lower bandwidth request upstream (Mb/s)
+  * @requested_down: New lower bandwidth request downstream (Mb/s)
+  *
+  * Goes over each link from @src_port to @dst_port and tries to
+  * transition the link to symmetric if the currently consumed bandwidth
+  * allows.
+  */
+ static int tb_configure_sym(struct tb *tb, struct tb_port *src_port,
+                           struct tb_port *dst_port, int requested_up,
+                           int requested_down)
+ {
+       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)
+               sw = dst_port->sw;
+       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;
+               /* Already symmetric */
+               if (up->sw->link_width <= TB_LINK_WIDTH_DUAL)
+                       continue;
+               /* Unplugged, no need to switch */
+               if (up->sw->is_unplugged)
+                       continue;
+               ret = tb_consumed_dp_bandwidth(tb, src_port, dst_port, up,
+                                              &consumed_up, &consumed_down);
+               if (ret)
+                       break;
+               if (downstream) {
+                       /*
+                        * Downstream so we want the consumed_down < threshold.
+                        * Upstream traffic should be less than 36G (40G
+                        * guard band 10%) as the link was configured asymmetric
+                        * already.
+                        */
+                       if (consumed_down + requested_down >= asym_threshold)
+                               continue;
+               } else {
+                       if (consumed_up + requested_up >= asym_threshold)
+                               continue;
+               }
+               if (up->sw->link_width == TB_LINK_WIDTH_DUAL)
+                       continue;
+               tb_sw_dbg(up->sw, "configuring symmetric link\n");
+               ret = tb_switch_set_link_width(up->sw, TB_LINK_WIDTH_DUAL);
+               if (ret) {
+                       tb_sw_warn(up->sw, "failed to set link width\n");
+                       break;
+               }
+       }
+       /* Re-enable CL states if they were previosly enabled */
+       if (clx)
+               tb_enable_clx(sw);
+       return ret;
+ }
+ static void tb_configure_link(struct tb_port *down, struct tb_port *up,
+                             struct tb_switch *sw)
+ {
+       struct tb *tb = sw->tb;
+       /* Link the routers using both links if available */
+       down->remote = up;
+       up->remote = down;
+       if (down->dual_link_port && up->dual_link_port) {
+               down->dual_link_port->remote = up->dual_link_port;
+               up->dual_link_port->remote = down->dual_link_port;
+       }
+       /*
+        * Enable lane bonding if the link is currently two single lane
+        * links.
+        */
+       if (sw->link_width < TB_LINK_WIDTH_DUAL)
+               tb_switch_set_link_width(sw, TB_LINK_WIDTH_DUAL);
+       /*
+        * Device router that comes up as symmetric link is
+        * connected deeper in the hierarchy, we transition the links
+        * above into symmetric if bandwidth allows.
+        */
+       if (tb_switch_depth(sw) > 1 &&
+           tb_port_get_link_generation(up) >= 4 &&
+           up->sw->link_width == TB_LINK_WIDTH_DUAL) {
+               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);
+       }
+       /* Set the link configured */
+       tb_switch_configure_link(sw);
+ }
  static void tb_scan_port(struct tb_port *port);
  
  /*
@@@ -964,19 -1385,9 +1385,9 @@@ static void tb_scan_port(struct tb_por
                goto out_rpm_put;
        }
  
-       /* Link the switches using both links if available */
        upstream_port = tb_upstream_port(sw);
-       port->remote = upstream_port;
-       upstream_port->remote = port;
-       if (port->dual_link_port && upstream_port->dual_link_port) {
-               port->dual_link_port->remote = upstream_port->dual_link_port;
-               upstream_port->dual_link_port->remote = port->dual_link_port;
-       }
+       tb_configure_link(port, upstream_port, sw);
  
-       /* Enable lane bonding if supported */
-       tb_switch_lane_bonding_enable(sw);
-       /* Set the link configured */
-       tb_switch_configure_link(sw);
        /*
         * CL0s and CL1 are enabled and supported together.
         * Silently ignore CLx enabling in case CLx is not supported.
@@@ -1040,6 -1451,11 +1451,11 @@@ static void tb_deactivate_and_free_tunn
                 * deallocated properly.
                 */
                tb_switch_dealloc_dp_resource(src_port->sw, src_port);
+               /*
+                * If bandwidth on a link is < asym_threshold
+                * transition the link to symmetric.
+                */
+               tb_configure_sym(tb, src_port, dst_port, 0, 0);
                /* 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);
@@@ -1092,7 -1508,8 +1508,8 @@@ static void tb_free_unplugged_children(
                        tb_retimer_remove_all(port);
                        tb_remove_dp_resources(port->remote->sw);
                        tb_switch_unconfigure_link(port->remote->sw);
-                       tb_switch_lane_bonding_disable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                TB_LINK_WIDTH_SINGLE);
                        tb_switch_remove(port->remote->sw);
                        port->remote = NULL;
                        if (port->dual_link_port)
@@@ -1188,7 -1605,7 +1605,7 @@@ tb_recalc_estimated_bandwidth_for_group
                        ret = tb_release_unused_usb3_bandwidth(tb,
                                first_tunnel->src_port, first_tunnel->dst_port);
                        if (ret) {
-                               tb_port_warn(in,
+                               tb_tunnel_warn(tunnel,
                                        "failed to release unused bandwidth\n");
                                break;
                        }
  
                out = tunnel->dst_port;
                ret = tb_available_bandwidth(tb, in, out, &estimated_up,
-                                            &estimated_down);
+                                            &estimated_down, true);
                if (ret) {
-                       tb_port_warn(in,
+                       tb_tunnel_warn(tunnel,
                                "failed to re-calculate estimated bandwidth\n");
                        break;
                }
                 *  - available bandwidth along the path
                 *  - bandwidth allocated for USB 3.x but not used.
                 */
-               tb_port_dbg(in, "re-calculated estimated bandwidth %u/%u Mb/s\n",
-                           estimated_up, estimated_down);
+               tb_tunnel_dbg(tunnel,
+                             "re-calculated estimated bandwidth %u/%u Mb/s\n",
+                             estimated_up, estimated_down);
  
-               if (in->sw->config.depth < out->sw->config.depth)
+               if (tb_port_path_direction_downstream(in, out))
                        estimated_bw = estimated_down;
                else
                        estimated_bw = estimated_up;
  
                if (usb4_dp_port_set_estimated_bandwidth(in, estimated_bw))
-                       tb_port_warn(in, "failed to update estimated bandwidth\n");
+                       tb_tunnel_warn(tunnel,
+                                      "failed to update estimated bandwidth\n");
        }
  
        if (first_tunnel)
@@@ -1282,18 -1701,14 +1701,14 @@@ static struct tb_port *tb_find_dp_out(s
        return NULL;
  }
  
- static void tb_tunnel_dp(struct tb *tb)
+ static bool tb_tunnel_one_dp(struct tb *tb)
  {
        int available_up, available_down, ret, link_nr;
        struct tb_cm *tcm = tb_priv(tb);
        struct tb_port *port, *in, *out;
+       int consumed_up, consumed_down;
        struct tb_tunnel *tunnel;
  
-       if (!tb_acpi_may_tunnel_dp()) {
-               tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
-               return;
-       }
        /*
         * Find pair of inactive DP IN and DP OUT adapters and then
         * establish a DP tunnel between them.
                        continue;
                }
  
-               tb_port_dbg(port, "DP IN available\n");
+               in = port;
+               tb_port_dbg(in, "DP IN available\n");
  
                out = tb_find_dp_out(tb, port);
-               if (out) {
-                       in = port;
+               if (out)
                        break;
-               }
        }
  
        if (!in) {
                tb_dbg(tb, "no suitable DP IN adapter available, not tunneling\n");
-               return;
+               return false;
        }
        if (!out) {
                tb_dbg(tb, "no suitable DP OUT adapter available, not tunneling\n");
-               return;
+               return false;
        }
  
        /*
                goto err_detach_group;
        }
  
-       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+                                    true);
        if (ret)
                goto err_reclaim_usb;
  
        list_add_tail(&tunnel->list, &tcm->tunnel_list);
        tb_reclaim_usb3_bandwidth(tb, in, out);
  
+       /*
+        * Transition the links to asymmetric if the consumption exceeds
+        * the threshold.
+        */
+       if (!tb_tunnel_consumed_bandwidth(tunnel, &consumed_up, &consumed_down))
+               tb_configure_asym(tb, in, out, consumed_up, consumed_down);
        /* Update the domain with the new bandwidth estimation */
        tb_recalc_estimated_bandwidth(tb);
  
         * TMU mode to HiFi for CL0s to work.
         */
        tb_increase_tmu_accuracy(tunnel);
-       return;
+       return true;
  
  err_free:
        tb_tunnel_free(tunnel);
@@@ -1414,6 -1836,19 +1836,19 @@@ err_rpm_put
        pm_runtime_put_autosuspend(&out->sw->dev);
        pm_runtime_mark_last_busy(&in->sw->dev);
        pm_runtime_put_autosuspend(&in->sw->dev);
+       return false;
+ }
+ static void tb_tunnel_dp(struct tb *tb)
+ {
+       if (!tb_acpi_may_tunnel_dp()) {
+               tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n");
+               return;
+       }
+       while (tb_tunnel_one_dp(tb))
+               ;
  }
  
  static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port)
@@@ -1701,7 -2136,8 +2136,8 @@@ static void tb_handle_hotplug(struct wo
                        tb_remove_dp_resources(port->remote->sw);
                        tb_switch_tmu_disable(port->remote->sw);
                        tb_switch_unconfigure_link(port->remote->sw);
-                       tb_switch_lane_bonding_disable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                TB_LINK_WIDTH_SINGLE);
                        tb_switch_remove(port->remote->sw);
                        port->remote = NULL;
                        if (port->dual_link_port)
@@@ -1781,8 -2217,8 +2217,8 @@@ static int tb_alloc_dp_bandwidth(struc
        in = tunnel->src_port;
        out = tunnel->dst_port;
  
-       tb_port_dbg(in, "bandwidth allocated currently %d/%d Mb/s\n",
-                   allocated_up, allocated_down);
+       tb_tunnel_dbg(tunnel, "bandwidth allocated currently %d/%d Mb/s\n",
+                     allocated_up, allocated_down);
  
        /*
         * If we get rounded up request from graphics side, say HBR2 x 4
        else if (requested_down_corrected < 0)
                requested_down_corrected = 0;
  
-       tb_port_dbg(in, "corrected bandwidth request %d/%d Mb/s\n",
-                   requested_up_corrected, requested_down_corrected);
+       tb_tunnel_dbg(tunnel, "corrected bandwidth request %d/%d Mb/s\n",
+                     requested_up_corrected, requested_down_corrected);
  
        if ((*requested_up >= 0 && requested_up_corrected > max_up_rounded) ||
            (*requested_down >= 0 && requested_down_corrected > max_down_rounded)) {
-               tb_port_dbg(in, "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
-                           requested_up_corrected, requested_down_corrected,
-                           max_up_rounded, max_down_rounded);
+               tb_tunnel_dbg(tunnel,
+                             "bandwidth request too high (%d/%d Mb/s > %d/%d Mb/s)\n",
+                             requested_up_corrected, requested_down_corrected,
+                             max_up_rounded, max_down_rounded);
                return -ENOBUFS;
        }
  
        if ((*requested_up >= 0 && requested_up_corrected <= allocated_up) ||
            (*requested_down >= 0 && requested_down_corrected <= allocated_down)) {
+               /*
+                * If bandwidth on a link is < asym_threshold transition
+                * the link to symmetric.
+                */
+               tb_configure_sym(tb, in, out, *requested_up, *requested_down);
                /*
                 * If requested bandwidth is less or equal than what is
                 * currently allocated to that tunnel we simply change
         * are also in the same group but we use the same function here
         * that we use with the normal bandwidth allocation).
         */
-       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down);
+       ret = tb_available_bandwidth(tb, in, out, &available_up, &available_down,
+                                    true);
        if (ret)
                goto reclaim;
  
-       tb_port_dbg(in, "bandwidth available for allocation %d/%d Mb/s\n",
-                   available_up, available_down);
+       tb_tunnel_dbg(tunnel, "bandwidth available for allocation %d/%d Mb/s\n",
+                     available_up, available_down);
  
        if ((*requested_up >= 0 && available_up >= requested_up_corrected) ||
            (*requested_down >= 0 && available_down >= requested_down_corrected)) {
+               /*
+                * If bandwidth on a link is >= asym_threshold
+                * transition the link to asymmetric.
+                */
+               ret = tb_configure_asym(tb, in, out, *requested_up,
+                                       *requested_down);
+               if (ret) {
+                       tb_configure_sym(tb, in, out, 0, 0);
+                       return ret;
+               }
                ret = tb_tunnel_alloc_bandwidth(tunnel, requested_up,
                                                requested_down);
+               if (ret) {
+                       tb_tunnel_warn(tunnel, "failed to allocate bandwidth\n");
+                       tb_configure_sym(tb, in, out, 0, 0);
+               }
        } else {
                ret = -ENOBUFS;
        }
@@@ -1907,14 -2365,14 +2365,14 @@@ static void tb_handle_dp_bandwidth_requ
        in = &sw->ports[ev->port];
        if (!tb_port_is_dpin(in)) {
                tb_port_warn(in, "bandwidth request to non-DP IN adapter\n");
 -              goto unlock;
 +              goto put_sw;
        }
  
        tb_port_dbg(in, "handling bandwidth allocation request\n");
  
        if (!usb4_dp_port_bandwidth_mode_enabled(in)) {
                tb_port_warn(in, "bandwidth allocation mode not enabled\n");
 -              goto unlock;
 +              goto put_sw;
        }
  
        ret = usb4_dp_port_requested_bandwidth(in);
                        tb_port_dbg(in, "no bandwidth request active\n");
                else
                        tb_port_warn(in, "failed to read requested bandwidth\n");
 -              goto unlock;
 +              goto put_sw;
        }
        requested_bw = ret;
  
        tunnel = tb_find_tunnel(tb, TB_TUNNEL_DP, in, NULL);
        if (!tunnel) {
                tb_port_warn(in, "failed to find tunnel\n");
 -              goto unlock;
 +              goto put_sw;
        }
  
        out = tunnel->dst_port;
  
-       if (in->sw->config.depth < out->sw->config.depth) {
+       if (tb_port_path_direction_downstream(in, out)) {
                requested_up = -1;
                requested_down = requested_bw;
        } else {
        ret = tb_alloc_dp_bandwidth(tunnel, &requested_up, &requested_down);
        if (ret) {
                if (ret == -ENOBUFS)
-                       tb_port_warn(in, "not enough bandwidth available\n");
+                       tb_tunnel_warn(tunnel,
+                                      "not enough bandwidth available\n");
                else
-                       tb_port_warn(in, "failed to change bandwidth allocation\n");
+                       tb_tunnel_warn(tunnel,
+                                      "failed to change bandwidth allocation\n");
        } else {
-               tb_port_dbg(in, "bandwidth allocation changed to %d/%d Mb/s\n",
-                           requested_up, requested_down);
+               tb_tunnel_dbg(tunnel,
+                             "bandwidth allocation changed to %d/%d Mb/s\n",
+                             requested_up, requested_down);
  
                /* Update other clients about the allocation change */
                tb_recalc_estimated_bandwidth(tb);
        }
  
 +put_sw:
 +      tb_switch_put(sw);
  unlock:
        mutex_unlock(&tb->lock);
  
@@@ -2181,7 -2640,8 +2642,8 @@@ static void tb_restore_children(struct 
                        continue;
  
                if (port->remote) {
-                       tb_switch_lane_bonding_enable(port->remote->sw);
+                       tb_switch_set_link_width(port->remote->sw,
+                                                port->remote->sw->link_width);
                        tb_switch_configure_link(port->remote->sw);
  
                        tb_restore_children(port->remote->sw);
  
  #include <linux/usb/gadgetfs.h>
  #include <linux/usb/gadget.h>
+ #include <linux/usb/composite.h> /* for USB_GADGET_DELAYED_STATUS */
+ /* Undef helpers from linux/usb/composite.h as gadgetfs redefines them */
+ #undef DBG
+ #undef ERROR
+ #undef INFO
  
  
  /*
@@@ -1511,7 -1517,16 +1517,16 @@@ delegate
                        event->u.setup = *ctrl;
                        ep0_readable (dev);
                        spin_unlock (&dev->lock);
-                       return 0;
+                       /*
+                        * Return USB_GADGET_DELAYED_STATUS as a workaround to
+                        * stop some UDC drivers (e.g. dwc3) from automatically
+                        * proceeding with the status stage for 0-length
+                        * transfers.
+                        * Should be removed once all UDC drivers are fixed to
+                        * always delay the status stage until a response is
+                        * queued to EP0.
+                        */
+                       return w_length == 0 ? USB_GADGET_DELAYED_STATUS : 0;
                }
        }
  
@@@ -1969,7 -1984,7 +1984,7 @@@ gadgetfs_make_inode (struct super_bloc
                inode->i_mode = mode;
                inode->i_uid = make_kuid(&init_user_ns, default_uid);
                inode->i_gid = make_kgid(&init_user_ns, default_gid);
 -              inode->i_atime = inode->i_mtime = inode_set_ctime_current(inode);
 +              simple_inode_init_ts(inode);
                inode->i_private = data;
                inode->i_fop = fops;
        }
diff --combined drivers/usb/host/xhci.h
@@@ -525,7 -525,7 +525,7 @@@ struct xhci_intr_reg 
   * a work queue (or delayed service routine)?
   */
  #define ERST_EHB              (1 << 3)
- #define ERST_PTR_MASK         (0xf)
+ #define ERST_PTR_MASK         (GENMASK_ULL(63, 4))
  
  /**
   * struct xhci_run_regs
@@@ -558,33 -558,6 +558,6 @@@ struct xhci_doorbell_array 
  #define DB_VALUE(ep, stream)  ((((ep) + 1) & 0xff) | ((stream) << 16))
  #define DB_VALUE_HOST         0x00000000
  
- /**
-  * struct xhci_protocol_caps
-  * @revision:         major revision, minor revision, capability ID,
-  *                    and next capability pointer.
-  * @name_string:      Four ASCII characters to say which spec this xHC
-  *                    follows, typically "USB ".
-  * @port_info:                Port offset, count, and protocol-defined information.
-  */
- struct xhci_protocol_caps {
-       u32     revision;
-       u32     name_string;
-       u32     port_info;
- };
- #define       XHCI_EXT_PORT_MAJOR(x)  (((x) >> 24) & 0xff)
- #define       XHCI_EXT_PORT_MINOR(x)  (((x) >> 16) & 0xff)
- #define       XHCI_EXT_PORT_PSIC(x)   (((x) >> 28) & 0x0f)
- #define       XHCI_EXT_PORT_OFF(x)    ((x) & 0xff)
- #define       XHCI_EXT_PORT_COUNT(x)  (((x) >> 8) & 0xff)
- #define       XHCI_EXT_PORT_PSIV(x)   (((x) >> 0) & 0x0f)
- #define       XHCI_EXT_PORT_PSIE(x)   (((x) >> 4) & 0x03)
- #define       XHCI_EXT_PORT_PLT(x)    (((x) >> 6) & 0x03)
- #define       XHCI_EXT_PORT_PFD(x)    (((x) >> 8) & 0x01)
- #define       XHCI_EXT_PORT_LP(x)     (((x) >> 14) & 0x03)
- #define       XHCI_EXT_PORT_PSIM(x)   (((x) >> 16) & 0xffff)
  #define PLT_MASK        (0x03 << 6)
  #define PLT_SYM         (0x00 << 6)
  #define PLT_ASYM_RX     (0x02 << 6)
@@@ -1545,6 -1518,7 +1518,7 @@@ struct xhci_segment 
        union xhci_trb          *trbs;
        /* private to HCD */
        struct xhci_segment     *next;
+       unsigned int            num;
        dma_addr_t              dma;
        /* Max packet sized bounce buffer for td-fragmant alignment */
        dma_addr_t              bounce_dma;
@@@ -1666,15 -1640,11 +1640,11 @@@ struct xhci_scratchpad 
  struct urb_priv {
        int     num_tds;
        int     num_tds_done;
 -      struct  xhci_td td[];
 +      struct  xhci_td td[] __counted_by(num_tds);
  };
  
- /*
-  * Each segment table entry is 4*32bits long.  1K seems like an ok size:
-  * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table,
-  * meaning 64 ring segments.
-  * Initial allocated size of the ERST, in number of entries */
- #define       ERST_NUM_SEGS   1
+ /* Reasonable limit for number of Event Ring segments (spec allows 32k) */
+ #define       ERST_MAX_SEGS   2
  /* Poll every 60 seconds */
  #define       POLL_TIMEOUT    60
  /* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */
@@@ -2078,13 -2048,8 +2048,8 @@@ struct xhci_ring *xhci_ring_alloc(struc
  void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring);
  int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring,
                unsigned int num_trbs, gfp_t flags);
- int xhci_alloc_erst(struct xhci_hcd *xhci,
-               struct xhci_ring *evt_ring,
-               struct xhci_erst *erst,
-               gfp_t flags);
  void xhci_initialize_ring_info(struct xhci_ring *ring,
                        unsigned int cycle_state);
- void xhci_free_erst(struct xhci_hcd *xhci, struct xhci_erst *erst);
  void xhci_free_endpoint_ring(struct xhci_hcd *xhci,
                struct xhci_virt_device *virt_dev,
                unsigned int ep_index);
@@@ -2119,6 -2084,8 +2084,8 @@@ void xhci_free_container_ctx(struct xhc
  /* xHCI host controller glue */
  typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
  int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us);
+ int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+               u32 mask, u32 done, int usec, unsigned int exit_state);
  void xhci_quiesce(struct xhci_hcd *xhci);
  int xhci_halt(struct xhci_hcd *xhci);
  int xhci_start(struct xhci_hcd *xhci);
@@@ -86,8 -86,11 +86,11 @@@ static int dp_altmode_notify(struct dp_
  
  static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
  {
-       u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
        u8 pin_assign = 0;
+       u32 conf;
+       /* DP Signalling */
+       conf = (dp->data.conf & DP_CONF_SIGNALLING_MASK) >> DP_CONF_SIGNALLING_SHIFT;
  
        switch (con) {
        case DP_STATUS_CON_DISABLED:
@@@ -153,11 -156,11 +156,11 @@@ static int dp_altmode_status_update(str
                        }
                }
        } else {
 -              if (dp->hpd != hpd) {
 -                      drm_connector_oob_hotplug_event(dp->connector_fwnode);
 -                      dp->hpd = hpd;
 -                      sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
 -              }
 +              drm_connector_oob_hotplug_event(dp->connector_fwnode,
 +                                              hpd ? connector_status_connected :
 +                                                    connector_status_disconnected);
 +              dp->hpd = hpd;
 +              sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
        }
  
        return ret;
@@@ -173,8 -176,7 +176,8 @@@ static int dp_altmode_configured(struc
         * configuration is complete to signal HPD.
         */
        if (dp->pending_hpd) {
 -              drm_connector_oob_hotplug_event(dp->connector_fwnode);
 +              drm_connector_oob_hotplug_event(dp->connector_fwnode,
 +                                              connector_status_connected);
                sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
                dp->pending_hpd = false;
        }
@@@ -306,8 -308,7 +309,8 @@@ static int dp_altmode_vdm(struct typec_
                        dp->data.status = 0;
                        dp->data.conf = 0;
                        if (dp->hpd) {
 -                              drm_connector_oob_hotplug_event(dp->connector_fwnode);
 +                              drm_connector_oob_hotplug_event(dp->connector_fwnode,
 +                                                              connector_status_disconnected);
                                dp->hpd = false;
                                sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
                        }
@@@ -625,8 -626,8 +628,8 @@@ void dp_altmode_remove(struct typec_alt
        cancel_work_sync(&dp->work);
  
        if (dp->connector_fwnode) {
 -              if (dp->hpd)
 -                      drm_connector_oob_hotplug_event(dp->connector_fwnode);
 +              drm_connector_oob_hotplug_event(dp->connector_fwnode,
 +                                              connector_status_disconnected);
  
                fwnode_handle_put(dp->connector_fwnode);
        }