[RFC 3/8] usb: dwc3: qcom: Enable autosuspend for host mode

From: Krishna Kurapati
Date: Tue Oct 17 2023 - 09:19:37 EST


When in host mode, enable autosuspend for xhci and root hubs.

a) Register a vendor call back to get information of successful role
change by core. When xhci is enumerated successfully, configure it to
use_autosuspend. The decision of whether or not to do runtime_allow for
xhci node is left to userspace:
(echo auto > */xhci-auto/power/control).

b) Register to usb-core notifications in set_mode vendor callback to
identify when root hubs are being created. Configure them to
use_autosuspend accordingly.

c) Configure any connected device to use_autosuspend. In general for
mobile use cases, autosuspend is enabled and wakeup is enabled only
for hubs and audio devices. So let userspace choose to configure
autosuspend_delay and wakeup capability of connected devices.

Signed-off-by: Krishna Kurapati <quic_kriskura@xxxxxxxxxxx>
---
drivers/usb/dwc3/core.c | 3 +++
drivers/usb/dwc3/core.h | 9 ++++++++
drivers/usb/dwc3/dwc3-qcom.c | 42 ++++++++++++++++++++++++++++++++++++
3 files changed, 54 insertions(+)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 53a8d92ad663..b4d1d1c98dd5 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -233,6 +233,9 @@ static void __dwc3_set_mode(struct work_struct *work)
break;
}

+ if (!ret)
+ dwc3_notify_mode_changed(dwc, dwc->current_dr_role);
+
out:
pm_runtime_mark_last_busy(dwc->dev);
pm_runtime_put_autosuspend(dwc->dev);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index aefcb0d388b7..5ed7fd5eb776 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -974,10 +974,12 @@ struct dwc3_scratchpad_array {
* @notify_cable_disconnect: Notify glue of cable removal
* irrespective of host or device mode.
* @set_mode: Notify glue before mode change is about to happen.
+ * @mode_changed: Notify glue that mode change was done successfully
*/
struct dwc3_glue_ops {
void (*notify_cable_disconnect)(void *glue_data);
void (*set_mode)(void *glue_data, u32 desired_dr_role);
+ void (*mode_changed)(void *glue_data, u32 current_dr_role);
};

struct dwc3_glue_data {
@@ -1600,6 +1602,13 @@ static inline void dwc3_notify_set_mode(struct dwc3 *dwc,
dwc->glue_ops->set_mode(dwc->glue_data, desired_dr_role);
}

+static inline void dwc3_notify_mode_changed(struct dwc3 *dwc,
+ u32 current_dr_role)
+{
+ if (dwc->glue_ops && dwc->glue_ops->mode_changed)
+ dwc->glue_ops->mode_changed(dwc->glue_data, current_dr_role);
+}
+
#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c
index 4013a5e6c6c0..9c7b23888f11 100644
--- a/drivers/usb/dwc3/dwc3-qcom.c
+++ b/drivers/usb/dwc3/dwc3-qcom.c
@@ -91,6 +91,7 @@ struct dwc3_qcom {

bool enable_rt;
enum usb_role current_role;
+ struct notifier_block xhci_nb;
};

static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val)
@@ -676,6 +677,27 @@ static const struct software_node dwc3_qcom_swnode = {
.properties = dwc3_qcom_acpi_properties,
};

+static int dwc3_xhci_event_notifier(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct usb_device *udev = ptr;
+
+ if (event != USB_DEVICE_ADD)
+ return NOTIFY_DONE;
+
+ /*
+ * If this is a roothub corresponding to this controller, enable autosuspend
+ */
+ if (!udev->parent) {
+ pm_runtime_use_autosuspend(&udev->dev);
+ pm_runtime_set_autosuspend_delay(&udev->dev, 1000);
+ }
+
+ usb_mark_last_busy(udev);
+
+ return NOTIFY_DONE;
+}
+
static void dwc3_qcom_handle_cable_disconnect(void *data)
{
struct dwc3_qcom *qcom = (struct dwc3_qcom *)data;
@@ -688,6 +710,8 @@ static void dwc3_qcom_handle_cable_disconnect(void *data)
pm_runtime_get_sync(qcom->dev);
dwc3_qcom_vbus_override_enable(qcom, false);
pm_runtime_put_autosuspend(qcom->dev);
+ } else if (qcom->current_role == USB_ROLE_HOST) {
+ usb_unregister_notify(&qcom->xhci_nb);
}

pm_runtime_mark_last_busy(qcom->dev);
@@ -711,15 +735,33 @@ static void dwc3_qcom_handle_set_mode(void *data, u32 desired_dr_role)
qcom->current_role = USB_ROLE_DEVICE;
} else if ((desired_dr_role == DWC3_GCTL_PRTCAP_HOST) &&
(qcom->current_role != USB_ROLE_HOST)) {
+ qcom->xhci_nb.notifier_call = dwc3_xhci_event_notifier;
+ usb_register_notify(&qcom->xhci_nb);
qcom->current_role = USB_ROLE_HOST;
}

pm_runtime_mark_last_busy(qcom->dev);
}

+static void dwc3_qcom_handle_mode_changed(void *data, u32 current_dr_role)
+{
+ struct dwc3_qcom *qcom = (struct dwc3_qcom *)data;
+
+ /*
+ * XHCI platform device is allocated upon host init.
+ * So ensure we are in host mode before enabling autosuspend.
+ */
+ if ((current_dr_role == DWC3_GCTL_PRTCAP_HOST) &&
+ (qcom->current_role == USB_ROLE_HOST)) {
+ pm_runtime_use_autosuspend(&qcom->dwc->xhci->dev);
+ pm_runtime_set_autosuspend_delay(&qcom->dwc->xhci->dev, 0);
+ }
+}
+
struct dwc3_glue_ops dwc3_qcom_glue_hooks = {
.notify_cable_disconnect = dwc3_qcom_handle_cable_disconnect,
.set_mode = dwc3_qcom_handle_set_mode,
+ .mode_changed = dwc3_qcom_handle_mode_changed,
};

static int dwc3_qcom_probe_core(struct platform_device *pdev, struct dwc3_qcom *qcom)
--
2.42.0