162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for onboard USB hubs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2022, Google LLC 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/export.h> 1062306a36Sopenharmony_ci#include <linux/gpio/consumer.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <linux/list.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/mutex.h> 1662306a36Sopenharmony_ci#include <linux/of.h> 1762306a36Sopenharmony_ci#include <linux/of_platform.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <linux/regulator/consumer.h> 2062306a36Sopenharmony_ci#include <linux/slab.h> 2162306a36Sopenharmony_ci#include <linux/suspend.h> 2262306a36Sopenharmony_ci#include <linux/sysfs.h> 2362306a36Sopenharmony_ci#include <linux/usb.h> 2462306a36Sopenharmony_ci#include <linux/usb/hcd.h> 2562306a36Sopenharmony_ci#include <linux/usb/onboard_hub.h> 2662306a36Sopenharmony_ci#include <linux/workqueue.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "onboard_usb_hub.h" 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* 3162306a36Sopenharmony_ci * Use generic names, as the actual names might differ between hubs. If a new 3262306a36Sopenharmony_ci * hub requires more than the currently supported supplies, add a new one here. 3362306a36Sopenharmony_ci */ 3462306a36Sopenharmony_cistatic const char * const supply_names[] = { 3562306a36Sopenharmony_ci "vdd", 3662306a36Sopenharmony_ci "vdd2", 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define MAX_SUPPLIES ARRAY_SIZE(supply_names) 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic void onboard_hub_attach_usb_driver(struct work_struct *work); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic struct usb_device_driver onboard_hub_usbdev_driver; 4462306a36Sopenharmony_cistatic DECLARE_WORK(attach_usb_driver_work, onboard_hub_attach_usb_driver); 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/************************** Platform driver **************************/ 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct usbdev_node { 4962306a36Sopenharmony_ci struct usb_device *udev; 5062306a36Sopenharmony_ci struct list_head list; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistruct onboard_hub { 5462306a36Sopenharmony_ci struct regulator_bulk_data supplies[MAX_SUPPLIES]; 5562306a36Sopenharmony_ci struct device *dev; 5662306a36Sopenharmony_ci const struct onboard_hub_pdata *pdata; 5762306a36Sopenharmony_ci struct gpio_desc *reset_gpio; 5862306a36Sopenharmony_ci bool always_powered_in_suspend; 5962306a36Sopenharmony_ci bool is_powered_on; 6062306a36Sopenharmony_ci bool going_away; 6162306a36Sopenharmony_ci struct list_head udev_list; 6262306a36Sopenharmony_ci struct mutex lock; 6362306a36Sopenharmony_ci}; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic int onboard_hub_power_on(struct onboard_hub *hub) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci int err; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci err = regulator_bulk_enable(hub->pdata->num_supplies, hub->supplies); 7062306a36Sopenharmony_ci if (err) { 7162306a36Sopenharmony_ci dev_err(hub->dev, "failed to enable supplies: %d\n", err); 7262306a36Sopenharmony_ci return err; 7362306a36Sopenharmony_ci } 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci fsleep(hub->pdata->reset_us); 7662306a36Sopenharmony_ci gpiod_set_value_cansleep(hub->reset_gpio, 0); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci hub->is_powered_on = true; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return 0; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int onboard_hub_power_off(struct onboard_hub *hub) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci int err; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci gpiod_set_value_cansleep(hub->reset_gpio, 1); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci err = regulator_bulk_disable(hub->pdata->num_supplies, hub->supplies); 9062306a36Sopenharmony_ci if (err) { 9162306a36Sopenharmony_ci dev_err(hub->dev, "failed to disable supplies: %d\n", err); 9262306a36Sopenharmony_ci return err; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci hub->is_powered_on = false; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci return 0; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int __maybe_unused onboard_hub_suspend(struct device *dev) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci struct onboard_hub *hub = dev_get_drvdata(dev); 10362306a36Sopenharmony_ci struct usbdev_node *node; 10462306a36Sopenharmony_ci bool power_off = true; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (hub->always_powered_in_suspend) 10762306a36Sopenharmony_ci return 0; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci mutex_lock(&hub->lock); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci list_for_each_entry(node, &hub->udev_list, list) { 11262306a36Sopenharmony_ci if (!device_may_wakeup(node->udev->bus->controller)) 11362306a36Sopenharmony_ci continue; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (usb_wakeup_enabled_descendants(node->udev)) { 11662306a36Sopenharmony_ci power_off = false; 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci mutex_unlock(&hub->lock); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (!power_off) 12462306a36Sopenharmony_ci return 0; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci return onboard_hub_power_off(hub); 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int __maybe_unused onboard_hub_resume(struct device *dev) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct onboard_hub *hub = dev_get_drvdata(dev); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (hub->is_powered_on) 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci return onboard_hub_power_on(hub); 13762306a36Sopenharmony_ci} 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) 14062306a36Sopenharmony_ci{ 14162306a36Sopenharmony_ci snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci struct usbdev_node *node; 14762306a36Sopenharmony_ci char link_name[64]; 14862306a36Sopenharmony_ci int err; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci mutex_lock(&hub->lock); 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci if (hub->going_away) { 15362306a36Sopenharmony_ci err = -EINVAL; 15462306a36Sopenharmony_ci goto error; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci node = kzalloc(sizeof(*node), GFP_KERNEL); 15862306a36Sopenharmony_ci if (!node) { 15962306a36Sopenharmony_ci err = -ENOMEM; 16062306a36Sopenharmony_ci goto error; 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci node->udev = udev; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci list_add(&node->list, &hub->udev_list); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci mutex_unlock(&hub->lock); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci get_udev_link_name(udev, link_name, sizeof(link_name)); 17062306a36Sopenharmony_ci WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci return 0; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_cierror: 17562306a36Sopenharmony_ci mutex_unlock(&hub->lock); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return err; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci struct usbdev_node *node; 18362306a36Sopenharmony_ci char link_name[64]; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci get_udev_link_name(udev, link_name, sizeof(link_name)); 18662306a36Sopenharmony_ci sysfs_remove_link(&hub->dev->kobj, link_name); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci mutex_lock(&hub->lock); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci list_for_each_entry(node, &hub->udev_list, list) { 19162306a36Sopenharmony_ci if (node->udev == udev) { 19262306a36Sopenharmony_ci list_del(&node->list); 19362306a36Sopenharmony_ci kfree(node); 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci } 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci mutex_unlock(&hub->lock); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, 20262306a36Sopenharmony_ci char *buf) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci const struct onboard_hub *hub = dev_get_drvdata(dev); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, 21062306a36Sopenharmony_ci const char *buf, size_t count) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci struct onboard_hub *hub = dev_get_drvdata(dev); 21362306a36Sopenharmony_ci bool val; 21462306a36Sopenharmony_ci int ret; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci ret = kstrtobool(buf, &val); 21762306a36Sopenharmony_ci if (ret < 0) 21862306a36Sopenharmony_ci return ret; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci hub->always_powered_in_suspend = val; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci return count; 22362306a36Sopenharmony_ci} 22462306a36Sopenharmony_cistatic DEVICE_ATTR_RW(always_powered_in_suspend); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic struct attribute *onboard_hub_attrs[] = { 22762306a36Sopenharmony_ci &dev_attr_always_powered_in_suspend.attr, 22862306a36Sopenharmony_ci NULL, 22962306a36Sopenharmony_ci}; 23062306a36Sopenharmony_ciATTRIBUTE_GROUPS(onboard_hub); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic void onboard_hub_attach_usb_driver(struct work_struct *work) 23362306a36Sopenharmony_ci{ 23462306a36Sopenharmony_ci int err; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); 23762306a36Sopenharmony_ci if (err) 23862306a36Sopenharmony_ci pr_err("Failed to attach USB driver: %d\n", err); 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic int onboard_hub_probe(struct platform_device *pdev) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci const struct of_device_id *of_id; 24462306a36Sopenharmony_ci struct device *dev = &pdev->dev; 24562306a36Sopenharmony_ci struct onboard_hub *hub; 24662306a36Sopenharmony_ci unsigned int i; 24762306a36Sopenharmony_ci int err; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); 25062306a36Sopenharmony_ci if (!hub) 25162306a36Sopenharmony_ci return -ENOMEM; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci of_id = of_match_device(onboard_hub_match, &pdev->dev); 25462306a36Sopenharmony_ci if (!of_id) 25562306a36Sopenharmony_ci return -ENODEV; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci hub->pdata = of_id->data; 25862306a36Sopenharmony_ci if (!hub->pdata) 25962306a36Sopenharmony_ci return -EINVAL; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci if (hub->pdata->num_supplies > MAX_SUPPLIES) 26262306a36Sopenharmony_ci return dev_err_probe(dev, -EINVAL, "max %zu supplies supported!\n", 26362306a36Sopenharmony_ci MAX_SUPPLIES); 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci for (i = 0; i < hub->pdata->num_supplies; i++) 26662306a36Sopenharmony_ci hub->supplies[i].supply = supply_names[i]; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci err = devm_regulator_bulk_get(dev, hub->pdata->num_supplies, hub->supplies); 26962306a36Sopenharmony_ci if (err) { 27062306a36Sopenharmony_ci dev_err(dev, "Failed to get regulator supplies: %d\n", err); 27162306a36Sopenharmony_ci return err; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", 27562306a36Sopenharmony_ci GPIOD_OUT_HIGH); 27662306a36Sopenharmony_ci if (IS_ERR(hub->reset_gpio)) 27762306a36Sopenharmony_ci return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci hub->dev = dev; 28062306a36Sopenharmony_ci mutex_init(&hub->lock); 28162306a36Sopenharmony_ci INIT_LIST_HEAD(&hub->udev_list); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci dev_set_drvdata(dev, hub); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci err = onboard_hub_power_on(hub); 28662306a36Sopenharmony_ci if (err) 28762306a36Sopenharmony_ci return err; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci /* 29062306a36Sopenharmony_ci * The USB driver might have been detached from the USB devices by 29162306a36Sopenharmony_ci * onboard_hub_remove() (e.g. through an 'unbind' by userspace), 29262306a36Sopenharmony_ci * make sure to re-attach it if needed. 29362306a36Sopenharmony_ci * 29462306a36Sopenharmony_ci * This needs to be done deferred to avoid self-deadlocks on systems 29562306a36Sopenharmony_ci * with nested onboard hubs. 29662306a36Sopenharmony_ci */ 29762306a36Sopenharmony_ci schedule_work(&attach_usb_driver_work); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci return 0; 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic void onboard_hub_remove(struct platform_device *pdev) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); 30562306a36Sopenharmony_ci struct usbdev_node *node; 30662306a36Sopenharmony_ci struct usb_device *udev; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci hub->going_away = true; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci mutex_lock(&hub->lock); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* unbind the USB devices to avoid dangling references to this device */ 31362306a36Sopenharmony_ci while (!list_empty(&hub->udev_list)) { 31462306a36Sopenharmony_ci node = list_first_entry(&hub->udev_list, struct usbdev_node, list); 31562306a36Sopenharmony_ci udev = node->udev; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci /* 31862306a36Sopenharmony_ci * Unbinding the driver will call onboard_hub_remove_usbdev(), 31962306a36Sopenharmony_ci * which acquires hub->lock. We must release the lock first. 32062306a36Sopenharmony_ci */ 32162306a36Sopenharmony_ci get_device(&udev->dev); 32262306a36Sopenharmony_ci mutex_unlock(&hub->lock); 32362306a36Sopenharmony_ci device_release_driver(&udev->dev); 32462306a36Sopenharmony_ci put_device(&udev->dev); 32562306a36Sopenharmony_ci mutex_lock(&hub->lock); 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci mutex_unlock(&hub->lock); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci onboard_hub_power_off(hub); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, onboard_hub_match); 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_cistatic const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { 33662306a36Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) 33762306a36Sopenharmony_ci}; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_cistatic struct platform_driver onboard_hub_driver = { 34062306a36Sopenharmony_ci .probe = onboard_hub_probe, 34162306a36Sopenharmony_ci .remove_new = onboard_hub_remove, 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci .driver = { 34462306a36Sopenharmony_ci .name = "onboard-usb-hub", 34562306a36Sopenharmony_ci .of_match_table = onboard_hub_match, 34662306a36Sopenharmony_ci .pm = pm_ptr(&onboard_hub_pm_ops), 34762306a36Sopenharmony_ci .dev_groups = onboard_hub_groups, 34862306a36Sopenharmony_ci }, 34962306a36Sopenharmony_ci}; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci/************************** USB driver **************************/ 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci#define VENDOR_ID_CYPRESS 0x04b4 35462306a36Sopenharmony_ci#define VENDOR_ID_GENESYS 0x05e3 35562306a36Sopenharmony_ci#define VENDOR_ID_MICROCHIP 0x0424 35662306a36Sopenharmony_ci#define VENDOR_ID_REALTEK 0x0bda 35762306a36Sopenharmony_ci#define VENDOR_ID_TI 0x0451 35862306a36Sopenharmony_ci#define VENDOR_ID_VIA 0x2109 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci/* 36162306a36Sopenharmony_ci * Returns the onboard_hub platform device that is associated with the USB 36262306a36Sopenharmony_ci * device passed as parameter. 36362306a36Sopenharmony_ci */ 36462306a36Sopenharmony_cistatic struct onboard_hub *_find_onboard_hub(struct device *dev) 36562306a36Sopenharmony_ci{ 36662306a36Sopenharmony_ci struct platform_device *pdev; 36762306a36Sopenharmony_ci struct device_node *np; 36862306a36Sopenharmony_ci struct onboard_hub *hub; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci pdev = of_find_device_by_node(dev->of_node); 37162306a36Sopenharmony_ci if (!pdev) { 37262306a36Sopenharmony_ci np = of_parse_phandle(dev->of_node, "peer-hub", 0); 37362306a36Sopenharmony_ci if (!np) { 37462306a36Sopenharmony_ci dev_err(dev, "failed to find device node for peer hub\n"); 37562306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci pdev = of_find_device_by_node(np); 37962306a36Sopenharmony_ci of_node_put(np); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (!pdev) 38262306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci hub = dev_get_drvdata(&pdev->dev); 38662306a36Sopenharmony_ci put_device(&pdev->dev); 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci /* 38962306a36Sopenharmony_ci * The presence of drvdata ('hub') indicates that the platform driver 39062306a36Sopenharmony_ci * finished probing. This handles the case where (conceivably) we could 39162306a36Sopenharmony_ci * be running at the exact same time as the platform driver's probe. If 39262306a36Sopenharmony_ci * we detect the race we request probe deferral and we'll come back and 39362306a36Sopenharmony_ci * try again. 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_ci if (!hub) 39662306a36Sopenharmony_ci return ERR_PTR(-EPROBE_DEFER); 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci return hub; 39962306a36Sopenharmony_ci} 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic int onboard_hub_usbdev_probe(struct usb_device *udev) 40262306a36Sopenharmony_ci{ 40362306a36Sopenharmony_ci struct device *dev = &udev->dev; 40462306a36Sopenharmony_ci struct onboard_hub *hub; 40562306a36Sopenharmony_ci int err; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci /* ignore supported hubs without device tree node */ 40862306a36Sopenharmony_ci if (!dev->of_node) 40962306a36Sopenharmony_ci return -ENODEV; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci hub = _find_onboard_hub(dev); 41262306a36Sopenharmony_ci if (IS_ERR(hub)) 41362306a36Sopenharmony_ci return PTR_ERR(hub); 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci dev_set_drvdata(dev, hub); 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci err = onboard_hub_add_usbdev(hub, udev); 41862306a36Sopenharmony_ci if (err) 41962306a36Sopenharmony_ci return err; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci return 0; 42262306a36Sopenharmony_ci} 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_cistatic void onboard_hub_usbdev_disconnect(struct usb_device *udev) 42562306a36Sopenharmony_ci{ 42662306a36Sopenharmony_ci struct onboard_hub *hub = dev_get_drvdata(&udev->dev); 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci onboard_hub_remove_usbdev(hub, udev); 42962306a36Sopenharmony_ci} 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_cistatic const struct usb_device_id onboard_hub_id_table[] = { 43262306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB33{0,1,2}x/CYUSB230x 3.0 */ 43362306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB33{0,1,2}x/CYUSB230x 2.0 */ 43462306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */ 43562306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */ 43662306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 */ 43762306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 */ 43862306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ 43962306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 */ 44062306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 */ 44162306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 */ 44262306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ 44362306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ 44462306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ 44562306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ 44662306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ 44762306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ 44862306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ 44962306a36Sopenharmony_ci { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 */ 45062306a36Sopenharmony_ci {} 45162306a36Sopenharmony_ci}; 45262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, onboard_hub_id_table); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_cistatic struct usb_device_driver onboard_hub_usbdev_driver = { 45562306a36Sopenharmony_ci .name = "onboard-usb-hub", 45662306a36Sopenharmony_ci .probe = onboard_hub_usbdev_probe, 45762306a36Sopenharmony_ci .disconnect = onboard_hub_usbdev_disconnect, 45862306a36Sopenharmony_ci .generic_subclass = 1, 45962306a36Sopenharmony_ci .supports_autosuspend = 1, 46062306a36Sopenharmony_ci .id_table = onboard_hub_id_table, 46162306a36Sopenharmony_ci}; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_cistatic int __init onboard_hub_init(void) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci int ret; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); 46862306a36Sopenharmony_ci if (ret) 46962306a36Sopenharmony_ci return ret; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci ret = platform_driver_register(&onboard_hub_driver); 47262306a36Sopenharmony_ci if (ret) 47362306a36Sopenharmony_ci usb_deregister_device_driver(&onboard_hub_usbdev_driver); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci return ret; 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_cimodule_init(onboard_hub_init); 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic void __exit onboard_hub_exit(void) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci usb_deregister_device_driver(&onboard_hub_usbdev_driver); 48262306a36Sopenharmony_ci platform_driver_unregister(&onboard_hub_driver); 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci cancel_work_sync(&attach_usb_driver_work); 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_cimodule_exit(onboard_hub_exit); 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ciMODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); 48962306a36Sopenharmony_ciMODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); 49062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 491