diff mbox series

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

Message ID 20231017131851.8299-4-quic_kriskura@quicinc.com
State New
Headers show
Series Enable runtime suspend resume for QCOM devices | expand

Commit Message

Krishna Kurapati PSSNV Oct. 17, 2023, 1:18 p.m. UTC
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@quicinc.com>
---
 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 mbox series

Patch

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)