Signed-off-by: Mathias Nyman <[email protected]>
---
.../testing/sysfs-bus-pci-drivers-xhci_hcd | 2 +-
drivers/usb/host/xhci-dbgcap.c | 60 ++++++++++++++++++-
drivers/usb/host/xhci-dbgcap.h | 3 +
3 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
index 98a8376a83d2..991765d84201 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
+++ b/Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
@@ -22,7 +22,7 @@ Description:
Reading this attribute gives the state of the DbC. It
can be one of the following states: disabled, enabled,
- initialized, connected or configured.
+ initialized, connected, configured or suspended.
What: /sys/bus/pci/drivers/xhci_hcd/.../dbc_idVendor
Date: March 2023
diff --git a/drivers/usb/host/xhci-dbgcap.c b/drivers/usb/host/xhci-dbgcap.c
index 8ff8ac300927..f228c46293ca 100644
--- a/drivers/usb/host/xhci-dbgcap.c
+++ b/drivers/usb/host/xhci-dbgcap.c
@@ -647,6 +647,31 @@ static int xhci_dbc_enable_dce(struct xhci_dbc *dbc, bool
enable)
static void xhci_dbc_set_state(struct xhci_dbc *dbc, enum dbc_state new_state)
{
+ if (dbc->state == new_state)
+ return;
+
+ switch (new_state) {
+ case DS_ENABLED:
+ /*
+ * DbC pm usage is 1 here, both when moved from disconnect or
+ * configured states, or when setting initial DbC enable state.
+ * Just enable pending put
+ */
+ dev_dbg(dbc->dev, "DbC set pending_rpm_put = 1\n");
+ dbc->pending_rpm_put = 1;
+ break;
+ case DS_CONNECTED:
+ if (dbc->pending_rpm_put)
+ /* DbC pm usage still 1, just remove pending put */
+ dbc->pending_rpm_put = 0;
+ else
+ /* DbC pm usage was put to 0, call get */
+ pm_runtime_get(dbc->dev);
+ break;
+ default:
+ break;
+ }
+
dbc->state_timestamp = jiffies;
dbc->state = new_state;
}
@@ -682,7 +707,7 @@ static int xhci_dbc_start(struct xhci_dbc *dbc)
WARN_ON(!dbc);
- pm_runtime_get_sync(dbc->dev); /* note this was self.controller */
+ pm_runtime_get(dbc->dev);
spin_lock_irqsave(&dbc->lock, flags);
ret = xhci_do_dbc_start(dbc);
@@ -707,6 +732,7 @@ static int xhci_dbc_start(struct xhci_dbc *dbc)
static void xhci_dbc_stop(struct xhci_dbc *dbc)
{
unsigned long flags;
+ bool need_rpm_put = false;
WARN_ON(!dbc);
@@ -732,12 +758,20 @@ static void xhci_dbc_stop(struct xhci_dbc *dbc)
spin_lock_irqsave(&dbc->lock, flags);
writel(0, &dbc->regs->control);
+
+ if (dbc->state == DS_CONNECTED || dbc->state == DS_CONFIGURED ||
+ dbc->pending_rpm_put)
+ need_rpm_put = true;
+
+ dbc->pending_rpm_put = 0;
+
xhci_dbc_set_state(dbc, DS_DISABLED);
spin_unlock_irqrestore(&dbc->lock, flags);
xhci_dbc_mem_cleanup(dbc);
- pm_runtime_put(dbc->dev); /* note, was self.controller */
+ if (need_rpm_put)
+ pm_runtime_put(dbc->dev);
}
static void
@@ -911,6 +945,12 @@ static enum evtreturn xhci_dbc_do_handle_events(struct
xhci_dbc *dbc)
dev_info(dbc->dev, "DbC connected\n");
} else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) {
dev_err(dbc->dev, "unexpected DbC disable, xHC
reset?\n");
+ } else if (dbc->pending_rpm_put &&
+ time_is_before_jiffies(dbc->state_timestamp +
+ msecs_to_jiffies(DBC_AUTOSUSPEND_DELAY))) {
+ dbc->pending_rpm_put = 0;
+ dev_dbg(dbc->dev, "DbC Enabled state for 15 seconds, allow
rpm suspend\n");
+ pm_runtime_put(dbc->dev);
}
return EVT_DONE;
@@ -1099,6 +1139,9 @@ static ssize_t dbc_show(struct device *dev,
if (dbc->state >= ARRAY_SIZE(dbc_state_strings))
return sysfs_emit(buf, "unknown\n");
+ if (dbc->resume_required)
+ return sysfs_emit(buf, "suspended\n");
+
return sysfs_emit(buf, "%s\n", dbc_state_strings[dbc->state]);
}
@@ -1113,12 +1156,25 @@ static ssize_t dbc_store(struct device *dev,
dbc = xhci->dbc;
if (sysfs_streq(buf, "enable")) {
+ pm_runtime_get_sync(dbc->dev);
+
mutex_lock(&dbc->enable_mutex);
+ /*
+ * DbC may already be enabled here if xhci was suspended with
+ * dbc->resume_required set, and resumed by
pm_runtime_get_sync()
+ * above. In this case we end up calling xhci_dbc_start() twice,
+ * second time returns an error but is harmless
+ */
xhci_dbc_start(dbc);
+
mutex_unlock(&dbc->enable_mutex);
+ pm_runtime_put(dbc->dev);
} else if (sysfs_streq(buf, "disable")) {
mutex_lock(&dbc->enable_mutex);
+
+ dbc->resume_required = 0;
xhci_dbc_stop(dbc);
+
mutex_unlock(&dbc->enable_mutex);
} else {
return -EINVAL;
diff --git a/drivers/usb/host/xhci-dbgcap.h b/drivers/usb/host/xhci-dbgcap.h
index df7aca8bfe99..5b18efb2c1ea 100644
--- a/drivers/usb/host/xhci-dbgcap.h
+++ b/drivers/usb/host/xhci-dbgcap.h
@@ -114,6 +114,8 @@ struct dbc_ep {
#define DBC_POLL_INTERVAL_MAX 5000 /* milliseconds */
#define DBC_XFER_INACTIVITY_TIMEOUT 10 /* milliseconds */
#define DBC_ENUMERATION_TIMEOUT 2000 /* milliseconds */
+#define DBC_AUTOSUSPEND_DELAY 15000 /* milliseconds */
+
/*
* Private structure for DbC hardware state:
*/
@@ -166,6 +168,7 @@ struct xhci_dbc {
unsigned long xfer_timestamp;
unsigned long state_timestamp;
unsigned resume_required:1;
+ unsigned pending_rpm_put:1;
struct dbc_ep eps[2];
const struct dbc_driver *driver;