diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index abf8c6cdea9e..ef1959363fb1 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -44,6 +44,7 @@ config USB_ARCH_HAS_HCD
config USB
tristate "Support for Host-side USB"
depends on USB_ARCH_HAS_HCD
+ depends on POWER_SEQUENCING if POWER_SEQUENCING
select GENERIC_ALLOCATOR
select USB_COMMON
select NLS # for UTF-8 strings
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b7991eb53197..a231cc076ebd 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -33,6 +33,7 @@
#include <linux/mutex.h>
#include <linux/random.h>
#include <linux/pm_qos.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/kobject.h>
#include <linux/bitfield.h>
@@ -875,6 +876,16 @@ static void hub_tt_work(struct work_struct *work)
spin_unlock_irqrestore(&hub->tt.lock, flags);
}
+static int usb_hub_set_port_pwrseq(struct usb_port *port, bool on)
+{
+ if (!IS_ENABLED(CONFIG_POWER_SEQUENCING))
+ return 0;
+
+ if (on)
+ return pwrseq_enable(port->pwrseq);
+ return pwrseq_disable(port->pwrseq);
+}
+
/**
* usb_hub_set_port_power - control hub port's power state
* @hdev: USB device belonging to the usb hub@@ -890,15 +901,22 @@ static void hub_tt_work(struct work_struct *work)
int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub,
int port1, bool set)
{
+ struct usb_port *pwrseq_port = hub->ports[port1 - 1];
int ret;
+ ret = usb_hub_set_port_pwrseq(pwrseq_port, set);
+ if (ret)
+ return ret;
+
if (set)
ret = set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
else
ret = usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
- if (ret)
+ if (ret) {
+ usb_hub_set_port_pwrseq(pwrseq_port, !set);
return ret;
+ }
assign_bit(port1, hub->power_bits, set);
return 0;diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h
index de524c6da9fc..3f403a56e5f7 100644
--- a/drivers/usb/core/hub.h
+++ b/drivers/usb/core/hub.h
@@ -103,7 +103,15 @@ static inline bool hub_is_port_power_switchable(struct usb_hub *hub)
if (!hub)
return false;
hcs = hub->descriptor->wHubCharacteristics;
- return (le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM;
+ if ((le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM)
+ return true;
+ /* check for controllable external power sequencers */
+ for (unsigned int i = 0; i < hub->hdev->maxchild; i++) {
+ if (hub->ports[i] && hub->ports[i]->pwrseq)
+ return true;
+ }
+
+ return false;
}
static inline int hub_is_superspeed(struct usb_device *hdev)diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c
index 8d686d43e996..8e95a6c09615 100644
--- a/drivers/usb/core/port.c
+++ b/drivers/usb/core/port.c
@@ -8,11 +8,14 @@
*/
#include <linux/acpi.h>
+#include <linux/cleanup.h>
#include <linux/kstrtox.h>
#include <linux/slab.h>
#include <linux/string_choices.h>
#include <linux/sysfs.h>
#include <linux/pm_qos.h>
+#include <linux/property.h>
+#include <linux/pwrseq/consumer.h>
#include <linux/component.h>
#include <linux/usb/of.h>
@@ -26,6 +29,7 @@ static const struct attribute_group *port_dev_group[];
int usb_port_is_power_on(struct usb_port *port, unsigned int portstatus)
{
int ret = 0;
+ int pwrseq_state;
if (port->is_superspeed) {
if (portstatus & USB_SS_PORT_STAT_POWER)@@ -35,7 +39,13 @@ int usb_port_is_power_on(struct usb_port *port, unsigned int portstatus)
ret = 1;
}
- return ret;
+ /* stub function returns error */
+ pwrseq_state = pwrseq_get_state(port->pwrseq);
+ /* fall back to port status if pwrseq is in unknown state */
+ if (pwrseq_state < 0 || pwrseq_state == PWRSEQ_STATE_UNKNOWN)
+ return ret;
+
+ return ret && pwrseq_state == PWRSEQ_STATE_ON;
}
static bool usb_port_allow_power_off(struct usb_device *hdev,
@@ -45,6 +55,9 @@ static bool usb_port_allow_power_off(struct usb_device *hdev,
if (hub_is_port_power_switchable(hub))
return true;
+ if (port_dev->pwrseq)
+ return true;
+
if (!IS_ENABLED(CONFIG_ACPI))
return false;
@@ -380,6 +393,8 @@ static void usb_port_device_release(struct device *dev)
* device_platform_notify_remove() in device_del().
*/
fwnode_handle_put(dev_fwnode(dev));
+ /* usb_hub_create_port_device() could leave an error value */
+ pwrseq_put(port_dev->pwrseq);
kfree(port_dev->req);
kfree(port_dev);
}
@@ -772,11 +787,46 @@ static const struct component_ops connector_ops = {
.unbind = connector_unbind,
};
+static bool port_pwrseq_is_supported(struct usb_port *port_dev)
+{
+ struct device *dev = &port_dev->dev;
+ struct fwnode_handle *port = dev_fwnode(dev);
+
+ struct fwnode_handle *ep __free(fwnode_handle) =
+ fwnode_graph_get_next_port_endpoint(port, NULL);
+ if (!ep)
+ return false;
+
+ struct fwnode_handle *remote __free(fwnode_handle) =
+ fwnode_graph_get_remote_port_parent(ep);
+ if (!remote)
+ return false;
+
+ if (!fwnode_device_is_compatible(remote, "pcie-m2-e-connector")) {
+ dev_dbg(dev, "remote endpoint %pfw is not a supported connector", remote);
+ return false;
+ }
+
+ return true;
+}
+
+static struct pwrseq_desc *usb_hub_port_pwrseq_get(struct usb_port *port_dev)
+{
+ if (!IS_ENABLED(CONFIG_POWER_SEQUENCING))
+ return NULL;
+
+ if (!port_pwrseq_is_supported(port_dev))
+ return NULL;
+
+ return pwrseq_get(&port_dev->dev, "usb");
+}
+
int usb_hub_create_port_device(struct usb_hub *hub, int port1)
{
struct usb_port *port_dev;
struct usb_device *hdev = hub->hdev;
struct fwnode_handle *fwnode = dev_fwnode(&hdev->dev);
+ struct pwrseq_desc *pwrseq;
int retval;
port_dev = kzalloc_obj(*port_dev);@@ -827,6 +877,7 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
retval = device_register(&port_dev->dev);
if (retval) {
put_device(&port_dev->dev);
+ hub->ports[port1 - 1] = NULL;
return retval;
}
@@ -844,6 +895,14 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1)
goto err_put_kn;
}
+ pwrseq = usb_hub_port_pwrseq_get(port_dev);
+ if (IS_ERR(pwrseq)) {
+ retval = dev_err_probe(&port_dev->dev, PTR_ERR(pwrseq),
+ "failed to get power sequencing descriptor\n");
+ goto err_put_kn;
+ }
+ port_dev->pwrseq = pwrseq;
+
retval = component_add(&port_dev->dev, &connector_ops);
if (retval) {
dev_warn(&port_dev->dev, "failed to add component\n");@@ -901,6 +960,7 @@ void usb_hub_remove_port_device(struct usb_hub *hub, int port1)
peer = port_dev->peer;
if (peer)
unlink_peers(port_dev, peer);
+ pwrseq_disable(port_dev->pwrseq);
component_del(&port_dev->dev, &connector_ops);
sysfs_put(port_dev->state_kn);
device_unregister(&port_dev->dev);
diff --git a/drivers/usb/core/port.h b/drivers/usb/core/port.h
index 2f4349b3ce6b..088a182332d4 100644
--- a/drivers/usb/core/port.h
+++ b/drivers/usb/core/port.h
@@ -25,6 +25,7 @@
* @port_owner: port's owner
* @peer: related usb2 and usb3 ports (share the same connector)
* @connector: USB Type-C connector
+ * @pwrseq: power sequencing descriptor for the port
* @req: default pm qos request for hubs without port power control
* @connect_type: port's connect type
* @state: device state of the usb device attached to the port
@@ -44,6 +45,7 @@ struct usb_port {
struct usb_dev_state *port_owner;
struct usb_port *peer;
struct typec_connector *connector;
+ struct pwrseq_desc *pwrseq;
struct dev_pm_qos_request *req;
enum usb_port_connect_type connect_type;
enum usb_device_state state;--
2.55.0.979.g7e5102b832-goog