diff options
Diffstat (limited to 'drivers/usb/common/usb-conn-gpio.c')
| -rw-r--r-- | drivers/usb/common/usb-conn-gpio.c | 83 |
1 files changed, 53 insertions, 30 deletions
diff --git a/drivers/usb/common/usb-conn-gpio.c b/drivers/usb/common/usb-conn-gpio.c index 0158148cb054..421c3af38e06 100644 --- a/drivers/usb/common/usb-conn-gpio.c +++ b/drivers/usb/common/usb-conn-gpio.c @@ -19,7 +19,11 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/regulator/consumer.h> +#include <linux/string_choices.h> #include <linux/usb/role.h> +#include <linux/idr.h> + +static DEFINE_IDA(usb_conn_ida); #define USB_GPIO_DEB_MS 20 /* ms */ #define USB_GPIO_DEB_US ((USB_GPIO_DEB_MS) * 1000) /* us */ @@ -29,6 +33,7 @@ struct usb_conn_info { struct device *dev; + int conn_id; /* store the IDA-allocated ID */ struct usb_role_switch *role_sw; enum usb_role last_role; struct regulator *vbus; @@ -42,6 +47,7 @@ struct usb_conn_info { struct power_supply_desc desc; struct power_supply *charger; + bool initial_detection; }; /* @@ -86,11 +92,13 @@ static void usb_conn_detect_cable(struct work_struct *work) dev_dbg(info->dev, "role %s -> %s, gpios: id %d, vbus %d\n", usb_role_string(info->last_role), usb_role_string(role), id, vbus); - if (info->last_role == role) { + if (!info->initial_detection && info->last_role == role) { dev_warn(info->dev, "repeated role: %s\n", usb_role_string(role)); return; } + info->initial_detection = false; + if (info->last_role == USB_ROLE_HOST && info->vbus) regulator_disable(info->vbus); @@ -108,7 +116,7 @@ static void usb_conn_detect_cable(struct work_struct *work) if (info->vbus) dev_dbg(info->dev, "vbus regulator is %s\n", - regulator_is_enabled(info->vbus) ? "enabled" : "disabled"); + str_enabled_disabled(regulator_is_enabled(info->vbus))); power_supply_changed(info->charger); } @@ -154,10 +162,20 @@ static int usb_conn_psy_register(struct usb_conn_info *info) struct device *dev = info->dev; struct power_supply_desc *desc = &info->desc; struct power_supply_config cfg = { - .of_node = dev->of_node, + .fwnode = dev_fwnode(dev), }; - desc->name = "usb-charger"; + info->conn_id = ida_alloc(&usb_conn_ida, GFP_KERNEL); + if (info->conn_id < 0) + return info->conn_id; + + desc->name = devm_kasprintf(dev, GFP_KERNEL, "usb-charger-%d", + info->conn_id); + if (!desc->name) { + ida_free(&usb_conn_ida, info->conn_id); + return -ENOMEM; + } + desc->properties = usb_charger_properties; desc->num_properties = ARRAY_SIZE(usb_charger_properties); desc->get_property = usb_charger_get_property; @@ -165,8 +183,10 @@ static int usb_conn_psy_register(struct usb_conn_info *info) cfg.drv_data = info; info->charger = devm_power_supply_register(dev, desc, &cfg); - if (IS_ERR(info->charger)) - dev_err(dev, "Unable to register charger\n"); + if (IS_ERR(info->charger)) { + dev_err(dev, "Unable to register charger %d\n", info->conn_id); + ida_free(&usb_conn_ida, info->conn_id); + } return PTR_ERR_OR_ZERO(info->charger); } @@ -175,7 +195,6 @@ static int usb_conn_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct usb_conn_info *info; - bool need_vbus = true; int ret = 0; info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); @@ -205,27 +224,12 @@ static int usb_conn_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&info->dw_det, usb_conn_detect_cable); - /* - * If the USB connector is a child of a USB port and that port already provides the VBUS - * supply, there's no need for the USB connector to provide it again. - */ - if (dev->parent && dev->parent->of_node) { - if (of_find_property(dev->parent->of_node, "vbus-supply", NULL)) - need_vbus = false; - } - - if (!need_vbus) { - info->vbus = devm_regulator_get_optional(dev, "vbus"); - if (PTR_ERR(info->vbus) == -ENODEV) - info->vbus = NULL; - } else { - info->vbus = devm_regulator_get(dev, "vbus"); - } + info->vbus = devm_regulator_get_optional(dev, "vbus"); + if (PTR_ERR(info->vbus) == -ENODEV) + info->vbus = NULL; - if (IS_ERR(info->vbus)) { - ret = PTR_ERR(info->vbus); - return dev_err_probe(dev, ret, "failed to get vbus :%d\n", ret); - } + if (IS_ERR(info->vbus)) + return dev_err_probe(dev, PTR_ERR(info->vbus), "failed to get vbus\n"); info->role_sw = usb_role_switch_get(dev); if (IS_ERR(info->role_sw)) @@ -271,8 +275,10 @@ static int usb_conn_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, info); + device_set_wakeup_capable(&pdev->dev, true); /* Perform initial detection */ + info->initial_detection = true; usb_conn_queue_dwork(info, 0); return 0; @@ -282,24 +288,33 @@ put_role_sw: return ret; } -static int usb_conn_remove(struct platform_device *pdev) +static void usb_conn_remove(struct platform_device *pdev) { struct usb_conn_info *info = platform_get_drvdata(pdev); cancel_delayed_work_sync(&info->dw_det); + if (info->charger) + ida_free(&usb_conn_ida, info->conn_id); + if (info->last_role == USB_ROLE_HOST && info->vbus) regulator_disable(info->vbus); usb_role_switch_put(info->role_sw); - - return 0; } static int __maybe_unused usb_conn_suspend(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + enable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + enable_irq_wake(info->vbus_irq); + return 0; + } + if (info->id_gpiod) disable_irq(info->id_irq); if (info->vbus_gpiod) @@ -314,6 +329,14 @@ static int __maybe_unused usb_conn_resume(struct device *dev) { struct usb_conn_info *info = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + if (info->id_gpiod) + disable_irq_wake(info->id_irq); + if (info->vbus_gpiod) + disable_irq_wake(info->vbus_irq); + return 0; + } + pinctrl_pm_select_default_state(dev); if (info->id_gpiod) |
