diff options
-rw-r--r-- | drivers/usb/core/hcd.c | 14 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 29 | ||||
-rw-r--r-- | drivers/usb/core/phy.c | 120 | ||||
-rw-r--r-- | drivers/usb/core/phy.h | 3 |
4 files changed, 162 insertions, 4 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index d56597dc7d42..14fc28c4cf48 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2792,10 +2792,16 @@ int usb_add_hcd(struct usb_hcd *hcd, struct usb_device *rhdev; struct usb_hcd *shared_hcd; - if (!hcd->skip_phy_initialization && usb_hcd_is_primary_hcd(hcd)) { - hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); - if (IS_ERR(hcd->phy_roothub)) - return PTR_ERR(hcd->phy_roothub); + if (!hcd->skip_phy_initialization) { + if (usb_hcd_is_primary_hcd(hcd)) { + hcd->phy_roothub = usb_phy_roothub_alloc(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } else { + hcd->phy_roothub = usb_phy_roothub_alloc_usb3_phy(hcd->self.sysdev); + if (IS_ERR(hcd->phy_roothub)) + return PTR_ERR(hcd->phy_roothub); + } retval = usb_phy_roothub_init(hcd->phy_roothub); if (retval) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ffd7c99e24a3..6f99557fa4ba 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -37,6 +37,7 @@ #include <asm/byteorder.h> #include "hub.h" +#include "phy.h" #include "otg_productlist.h" #define USB_VENDOR_GENESYS_LOGIC 0x05e3 @@ -634,6 +635,34 @@ static int hub_ext_port_status(struct usb_hub *hub, int port1, int type, ret = 0; } mutex_unlock(&hub->status_mutex); + + /* + * There is no need to lock status_mutex here, because status_mutex + * protects hub->status, and the phy driver only checks the port + * status without changing the status. + */ + if (!ret) { + struct usb_device *hdev = hub->hdev; + + /* + * Only roothub will be notified of connection changes, + * since the USB PHY only cares about changes at the next + * level. + */ + if (is_root_hub(hdev)) { + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + bool connect; + bool connect_change; + + connect_change = *change & USB_PORT_STAT_C_CONNECTION; + connect = *status & USB_PORT_STAT_CONNECTION; + if (connect_change && connect) + usb_phy_roothub_notify_connect(hcd->phy_roothub, port1 - 1); + else if (connect_change) + usb_phy_roothub_notify_disconnect(hcd->phy_roothub, port1 - 1); + } + } + return ret; } diff --git a/drivers/usb/core/phy.c b/drivers/usb/core/phy.c index fb1588e7c282..faa20054ad5a 100644 --- a/drivers/usb/core/phy.c +++ b/drivers/usb/core/phy.c @@ -19,6 +19,30 @@ struct usb_phy_roothub { struct list_head list; }; +/* Allocate the roothub_entry by specific name of phy */ +static int usb_phy_roothub_add_phy_by_name(struct device *dev, const char *name, + struct list_head *list) +{ + struct usb_phy_roothub *roothub_entry; + struct phy *phy; + + phy = devm_of_phy_get(dev, dev->of_node, name); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + roothub_entry = devm_kzalloc(dev, sizeof(*roothub_entry), GFP_KERNEL); + if (!roothub_entry) + return -ENOMEM; + + INIT_LIST_HEAD(&roothub_entry->list); + + roothub_entry->phy = phy; + + list_add_tail(&roothub_entry->list, list); + + return 0; +} + static int usb_phy_roothub_add_phy(struct device *dev, int index, struct list_head *list) { @@ -65,6 +89,9 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) INIT_LIST_HEAD(&phy_roothub->list); + if (!usb_phy_roothub_add_phy_by_name(dev, "usb2-phy", &phy_roothub->list)) + return phy_roothub; + for (i = 0; i < num_phys; i++) { err = usb_phy_roothub_add_phy(dev, i, &phy_roothub->list); if (err) @@ -75,6 +102,41 @@ struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev) } EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc); +/** + * usb_phy_roothub_alloc_usb3_phy - alloc the roothub + * @dev: the device of the host controller + * + * Allocate the usb phy roothub if the host use a generic usb3-phy. + * + * Return: On success, a pointer to the usb_phy_roothub. Otherwise, + * %NULL if no use usb3 phy or %-ENOMEM if out of memory. + */ +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev) +{ + struct usb_phy_roothub *phy_roothub; + int num_phys; + + if (!IS_ENABLED(CONFIG_GENERIC_PHY)) + return NULL; + + num_phys = of_count_phandle_with_args(dev->of_node, "phys", + "#phy-cells"); + if (num_phys <= 0) + return NULL; + + phy_roothub = devm_kzalloc(dev, sizeof(*phy_roothub), GFP_KERNEL); + if (!phy_roothub) + return ERR_PTR(-ENOMEM); + + INIT_LIST_HEAD(&phy_roothub->list); + + if (!usb_phy_roothub_add_phy_by_name(dev, "usb3-phy", &phy_roothub->list)) + return phy_roothub; + + return NULL; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_alloc_usb3_phy); + int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; @@ -172,6 +234,64 @@ int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub) } EXPORT_SYMBOL_GPL(usb_phy_roothub_calibrate); +/** + * usb_phy_roothub_notify_connect() - connect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for connect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_connect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_connect); + +/** + * usb_phy_roothub_notify_disconnect() - disconnect notification + * @phy_roothub: the phy of roothub, if the host use a generic phy. + * @port: the port index for disconnect + * + * If the phy needs to get connection status, the callback can be used. + * Returns: %0 if successful, a negative error code otherwise + */ +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port) +{ + struct usb_phy_roothub *roothub_entry; + struct list_head *head; + int err; + + if (!phy_roothub) + return 0; + + head = &phy_roothub->list; + + list_for_each_entry(roothub_entry, head, list) { + err = phy_notify_disconnect(roothub_entry->phy, port); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(usb_phy_roothub_notify_disconnect); + int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub) { struct usb_phy_roothub *roothub_entry; diff --git a/drivers/usb/core/phy.h b/drivers/usb/core/phy.h index 20a267cd986b..88b49c0ea6b5 100644 --- a/drivers/usb/core/phy.h +++ b/drivers/usb/core/phy.h @@ -12,6 +12,7 @@ struct device; struct usb_phy_roothub; struct usb_phy_roothub *usb_phy_roothub_alloc(struct device *dev); +struct usb_phy_roothub *usb_phy_roothub_alloc_usb3_phy(struct device *dev); int usb_phy_roothub_init(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); @@ -19,6 +20,8 @@ int usb_phy_roothub_exit(struct usb_phy_roothub *phy_roothub); int usb_phy_roothub_set_mode(struct usb_phy_roothub *phy_roothub, enum phy_mode mode); int usb_phy_roothub_calibrate(struct usb_phy_roothub *phy_roothub); +int usb_phy_roothub_notify_connect(struct usb_phy_roothub *phy_roothub, int port); +int usb_phy_roothub_notify_disconnect(struct usb_phy_roothub *phy_roothub, int port); int usb_phy_roothub_power_on(struct usb_phy_roothub *phy_roothub); void usb_phy_roothub_power_off(struct usb_phy_roothub *phy_roothub); |