Hi Roger, On 04/01/2015 01:28 PM, Roger Quadros wrote: > Robert, > > On 01/04/15 10:23, Robert Baldyga wrote: >> This patch adds VBUS pin detection support to extcon-usb-gpio driver. >> It allows to use this driver with boards which have both VBUS and ID >> pins, or only one of them. >> >> Following table of states presents relationship between this signals >> and detected cable type: >> >> State | ID | VBUS >> ---------------------------------------- >> [1] USB | H | H >> [2] none | H | L >> [3] USB & USB-HOST | L | H >> [4] USB-HOST | L | L >> >> In case we have only one of these signals: >> - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. >> - ID only - we want to distinguish between [1] and [4], so VBUS = ID. >> >> Signed-off-by: Robert Baldyga <r.baldyga@xxxxxxxxxxx> >> --- >> drivers/extcon/extcon-usb-gpio.c | 171 +++++++++++++++++++++++++++------------ >> 1 file changed, 119 insertions(+), 52 deletions(-) >> >> diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c >> index f6aa99d..c842715 100644 >> --- a/drivers/extcon/extcon-usb-gpio.c >> +++ b/drivers/extcon/extcon-usb-gpio.c >> @@ -32,7 +32,9 @@ struct usb_extcon_info { >> struct extcon_dev *edev; >> >> struct gpio_desc *id_gpiod; >> + struct gpio_desc *vbus_gpiod; >> int id_irq; >> + int vbus_irq; >> >> unsigned long debounce_jiffies; >> struct delayed_work wq_detcable; >> @@ -52,40 +54,49 @@ static const char *usb_extcon_cable[] = { >> NULL, >> }; >> >> +/* >> + * "USB" = VBUS and "USB-HOST" = !ID, so we have: >> + * >> + * State | ID | VBUS >> + * ---------------------------------------- >> + * [1] USB | H | H >> + * [2] none | H | L >> + * [3] USB & USB-HOST | L | H >> + * [4] USB-HOST | L | L >> + * >> + * In case we have only one of these signals: >> + * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. >> + * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. >> + */ >> + >> static void usb_extcon_detect_cable(struct work_struct *work) >> { >> int id; >> + int vbus; >> struct usb_extcon_info *info = container_of(to_delayed_work(work), >> struct usb_extcon_info, >> wq_detcable); >> >> - /* check ID and update cable state */ >> - id = gpiod_get_value_cansleep(info->id_gpiod); >> - if (id) { >> - /* >> - * ID = 1 means USB HOST cable detached. >> - * As we don't have event for USB peripheral cable attached, >> - * we simulate USB peripheral attach here. >> - */ >> - extcon_set_cable_state(info->edev, >> - usb_extcon_cable[EXTCON_CABLE_USB_HOST], >> - false); >> - extcon_set_cable_state(info->edev, >> - usb_extcon_cable[EXTCON_CABLE_USB], >> - true); >> - } else { >> - /* >> - * ID = 0 means USB HOST cable attached. >> - * As we don't have event for USB peripheral cable detached, >> - * we simulate USB peripheral detach here. >> - */ >> + /* check ID and VBUS and update cable state */ >> + >> + id = info->id_gpiod ? >> + gpiod_get_value_cansleep(info->id_gpiod) : 1; >> + >> + vbus = info->vbus_gpiod ? >> + gpiod_get_value_cansleep(info->vbus_gpiod) : id; >> + >> + /* at first we clean states which are no longer active */ >> + if (id) >> extcon_set_cable_state(info->edev, >> - usb_extcon_cable[EXTCON_CABLE_USB], >> - false); >> + usb_extcon_cable[EXTCON_CABLE_USB_HOST], false); >> + if (!vbus) >> extcon_set_cable_state(info->edev, >> - usb_extcon_cable[EXTCON_CABLE_USB_HOST], >> - true); >> - } >> + usb_extcon_cable[EXTCON_CABLE_USB], false); >> + >> + extcon_set_cable_state(info->edev, >> + usb_extcon_cable[EXTCON_CABLE_USB_HOST], !id); >> + extcon_set_cable_state(info->edev, >> + usb_extcon_cable[EXTCON_CABLE_USB], vbus); > > This approach has the following problems: > 1) If ID is 1 then extcon_set_cable_state(USB_HOST, false) gets called twice. > 2) If VBUS is 0 then extcon_set_cable_state(USB, false) gets called twice. > 3) When both ID and VBUS are available, even if either one changes state then we unnecessarily > update the other pins cable state as well. > > This is probably not an issue as extcon core might be ignoring duplicate set_cable_states, > but I think we should avoid them if we can. I wouldn't mind (3) as we unnecessarily need to > keep track of previous states, but (1) and (2) should be fixed. > Extcon core is responsible for storing current cable state to let us do not worry about that. However if we really do want to get rid of redundant usb_extcon_cable() calls, we can create separate interrupt handler for VBUS. Br, Robert Baldyga > >> } >> >> static irqreturn_t usb_irq_handler(int irq, void *dev_id) >> @@ -113,10 +124,12 @@ static int usb_extcon_probe(struct platform_device *pdev) >> return -ENOMEM; >> >> info->dev = dev; >> - info->id_gpiod = devm_gpiod_get(&pdev->dev, "id"); >> - if (IS_ERR(info->id_gpiod)) { >> - dev_err(dev, "failed to get ID GPIO\n"); >> - return PTR_ERR(info->id_gpiod); >> + info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id"); >> + info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus"); >> + >> + if (!info->id_gpiod && !info->vbus_gpiod) { >> + dev_err(dev, "failed to get gpios\n"); >> + return -ENODEV; >> } >> >> info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); >> @@ -131,27 +144,51 @@ static int usb_extcon_probe(struct platform_device *pdev) >> return ret; >> } >> >> - ret = gpiod_set_debounce(info->id_gpiod, >> - USB_GPIO_DEBOUNCE_MS * 1000); >> + if (info->id_gpiod) >> + ret = gpiod_set_debounce(info->id_gpiod, >> + USB_GPIO_DEBOUNCE_MS * 1000); >> + if (!ret && info->vbus_gpiod) { >> + ret = gpiod_set_debounce(info->vbus_gpiod, >> + USB_GPIO_DEBOUNCE_MS * 1000); >> + if (ret < 0 && info->id_gpiod) >> + gpiod_set_debounce(info->vbus_gpiod, 0); >> + } >> if (ret < 0) >> info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); >> >> INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); >> >> - info->id_irq = gpiod_to_irq(info->id_gpiod); >> - if (info->id_irq < 0) { >> - dev_err(dev, "failed to get ID IRQ\n"); >> - return info->id_irq; >> + if (info->id_gpiod) { >> + info->id_irq = gpiod_to_irq(info->id_gpiod); >> + if (info->id_irq < 0) { >> + dev_err(dev, "failed to get ID IRQ\n"); >> + return info->id_irq; >> + } >> + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, >> + usb_irq_handler, >> + IRQF_TRIGGER_RISING | >> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, >> + pdev->name, info); >> + if (ret < 0) { >> + dev_err(dev, "failed to request handler for ID IRQ\n"); >> + return ret; >> + } >> } >> - >> - ret = devm_request_threaded_irq(dev, info->id_irq, NULL, >> - usb_irq_handler, >> - IRQF_TRIGGER_RISING | >> - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, >> - pdev->name, info); >> - if (ret < 0) { >> - dev_err(dev, "failed to request handler for ID IRQ\n"); >> - return ret; >> + if (info->vbus_gpiod) { >> + info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); >> + if (info->vbus_irq < 0) { >> + dev_err(dev, "failed to get VBUS IRQ\n"); >> + return info->vbus_irq; >> + } >> + ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, >> + usb_irq_handler, >> + IRQF_TRIGGER_RISING | >> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, >> + pdev->name, info); >> + if (ret < 0) { >> + dev_err(dev, "failed to request handler for VBUS IRQ\n"); >> + return ret; >> + } >> } >> >> platform_set_drvdata(pdev, info); >> @@ -179,9 +216,16 @@ static int usb_extcon_suspend(struct device *dev) >> int ret = 0; >> >> if (device_may_wakeup(dev)) { >> - ret = enable_irq_wake(info->id_irq); >> - if (ret) >> - return ret; >> + if (info->id_gpiod) { >> + ret = enable_irq_wake(info->id_irq); >> + if (ret) >> + return ret; >> + } >> + if (info->vbus_gpiod) { >> + ret = enable_irq_wake(info->vbus_irq); >> + if (ret) >> + goto err; >> + } >> } >> >> /* >> @@ -189,8 +233,16 @@ static int usb_extcon_suspend(struct device *dev) >> * as GPIOs used behind I2C subsystem might not be >> * accessible until resume completes. So disable IRQ. >> */ >> - disable_irq(info->id_irq); >> + if (info->id_gpiod) >> + disable_irq(info->id_irq); >> + if (info->vbus_gpiod) >> + disable_irq(info->vbus_irq); >> + >> + return ret; >> >> +err: >> + if (info->id_gpiod) >> + disable_irq_wake(info->id_irq); >> return ret; >> } >> >> @@ -200,13 +252,28 @@ static int usb_extcon_resume(struct device *dev) >> int ret = 0; >> >> if (device_may_wakeup(dev)) { >> - ret = disable_irq_wake(info->id_irq); >> - if (ret) >> - return ret; >> + if (info->id_gpiod) { >> + ret = disable_irq_wake(info->id_irq); >> + if (ret) >> + return ret; >> + } >> + if (info->vbus_gpiod) { >> + ret = disable_irq_wake(info->vbus_irq); >> + if (ret) >> + goto err; >> + } >> } >> >> - enable_irq(info->id_irq); >> + if (info->id_gpiod) >> + enable_irq(info->id_irq); >> + if (info->vbus_gpiod) >> + enable_irq(info->vbus_irq); >> + >> + return ret; >> >> +err: >> + if (info->id_gpiod) >> + enable_irq_wake(info->id_irq); >> return ret; >> } >> #endif >> > > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html