diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index 3ac29c6eaa68d..e22d0daa26034 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -25,6 +25,7 @@ #include #include +#include #include "phy-qcom-qmp-common.h" @@ -2275,6 +2276,7 @@ struct qmp_combo { struct phy *usb_phy; enum phy_mode phy_mode; + enum qcom_usb_ss_state ss_state; unsigned int usb_init_count; struct phy *dp_phy; @@ -3929,6 +3931,16 @@ static int qmp_combo_usb_set_mode(struct phy *phy, enum phy_mode mode, int submo qmp->phy_mode = mode; + switch (submode) { + case QCOM_USB_SS_PRESENT: + case QCOM_USB_SS_ABSENT: + qmp->ss_state = submode; + break; + default: + qmp->ss_state = QCOM_USB_SS_UNKNOWN; + break; + } + return 0; } @@ -3957,11 +3969,20 @@ static void qmp_combo_enable_autonomous_mode(struct qmp_combo *qmp) void __iomem *pcs_aon = qmp->pcs_aon; u32 intr_mask; - if (qmp->phy_mode == PHY_MODE_USB_HOST_SS || - qmp->phy_mode == PHY_MODE_USB_DEVICE_SS) - intr_mask = ARCVR_DTCT_EN | ALFPS_DTCT_EN; - else - intr_mask = ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL; + /* + * Use LFPS detection only when a SuperSpeed device is known to be + * present. For absent or unknown state, use receiver detection. + */ + switch (qmp->ss_state) { + case QCOM_USB_SS_PRESENT: + intr_mask = ARCVR_DTCT_EN | ALFPS_DTCT_EN; + break; + case QCOM_USB_SS_ABSENT: + case QCOM_USB_SS_UNKNOWN: + default: + intr_mask = ARCVR_DTCT_EN | ARCVR_DTCT_EVENT_SEL; + break; + } /* Clear any pending interrupts status */ qphy_setbits(pcs_usb, cfg->regs[QPHY_PCS_LFPS_RXTERM_IRQ_CLEAR], IRQ_CLEAR); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c index 5d131a33ae5a4..9eaf5b3f2d7ca 100644 --- a/drivers/usb/dwc3/dwc3-qcom.c +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "core.h" #include "glue.h" @@ -67,6 +68,7 @@ struct dwc3_qcom_port { int dm_hs_phy_irq; int ss_phy_irq; enum usb_device_speed usb2_speed; + enum qcom_usb_ss_state ss_state; }; struct dwc3_qcom { @@ -252,6 +254,32 @@ static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, i return udev->speed; } +static enum qcom_usb_ss_state dwc3_qcom_read_ss_state(struct dwc3_qcom *qcom, int port_index) +{ + struct usb_device *udev; + struct usb_hcd __maybe_unused *hcd, *usb3_hcd; + struct dwc3 *dwc = &qcom->dwc; + + /* FIXME: Fix this layering violation. */ + hcd = platform_get_drvdata(dwc->xhci); + if (!hcd) + return QCOM_USB_SS_ABSENT; + +#ifdef CONFIG_USB + usb3_hcd = hcd->shared_hcd ? hcd->shared_hcd : hcd; + if (!usb3_hcd->self.root_hub) + return QCOM_USB_SS_ABSENT; + + udev = usb_hub_find_child(usb3_hcd->self.root_hub, port_index + 1); +#else + udev = NULL; +#endif + if (!udev) + return QCOM_USB_SS_ABSENT; + + return udev->speed >= USB_SPEED_SUPER ? QCOM_USB_SS_PRESENT : QCOM_USB_SS_ABSENT; +} + static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity) { if (!irq) @@ -360,8 +388,13 @@ static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup) * freezable workqueue. */ if (dwc3_qcom_is_host(qcom) && wakeup) { - for (i = 0; i < qcom->num_ports; i++) + for (i = 0; i < qcom->num_ports; i++){ qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i); + qcom->ports[i].ss_state = dwc3_qcom_read_ss_state(qcom, i); + phy_set_mode_ext(qcom->dwc.usb3_generic_phy[i], PHY_MODE_USB_HOST, + qcom->ports[i].ss_state); + + } dwc3_qcom_enable_interrupts(qcom); } else { dev_pm_genpd_synced_poweroff(qcom->dev); diff --git a/include/linux/phy/phy-qcom-qmp-usb-ss.h b/include/linux/phy/phy-qcom-qmp-usb-ss.h new file mode 100644 index 0000000000000..4da4d71e977d7 --- /dev/null +++ b/include/linux/phy/phy-qcom-qmp-usb-ss.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Shared submode contract between dwc3-qcom glue and the Qualcomm QMP + * USB3/combo PHY driver for communicating live SuperSpeed link presence + * at suspend-entry, via phy_set_mode_ext()'s submode argument. + */ + +#ifndef _PHY_QCOM_QMP_USB_SS_H_ +#define _PHY_QCOM_QMP_USB_SS_H_ + +enum qcom_usb_ss_state { + QCOM_USB_SS_UNKNOWN = 0, + QCOM_USB_SS_PRESENT, + QCOM_USB_SS_ABSENT, +}; + +#endif /* _PHY_QCOM_QMP_USB_SS_H_ */