Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

extcon: usb-gpio: Add VBUS detection support

Driver can now work with both ID and VBUS pins or either one of
them.

There can be the following 3 cases

1) Both ID and VBUS GPIOs are available:

ID = LOW -> USB_HOST active, USB inactive
ID = HIGH -> USB_HOST inactive, USB state is same as VBUS.

2) Only ID GPIO is available:

ID = LOW -> USB_HOST active, USB inactive
ID = HIGH -> USB_HOST inactive, USB active

3) Only VBUS GPIO is available:

VBUS = LOW -> USB_HOST inactive, USB inactive
VBUS = HIGH -> USB_HOST inactive, USB active

Signed-off-by: Roger Quadros <rogerq@ti.com>
Reviewed-by: Peter Chen <peter.chen@nxp.com>
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>

authored by

Roger Quadros and committed by
Chanwoo Choi
541332a1 a25f0944

+132 -40
+3
Documentation/devicetree/bindings/extcon/extcon-usb-gpio.txt
··· 5 5 6 6 Required properties: 7 7 - compatible: Should be "linux,extcon-usb-gpio" 8 + 9 + Either one of id-gpio or vbus-gpio must be present. Both can be present as well. 8 10 - id-gpio: gpio for USB ID pin. See gpio binding. 11 + - vbus-gpio: gpio for USB VBUS pin. 9 12 10 13 Example: Examples of extcon-usb-gpio node in dra7-evm.dts as listed below: 11 14 extcon_usb1 {
+129 -40
drivers/extcon/extcon-usb-gpio.c
··· 24 24 #include <linux/module.h> 25 25 #include <linux/of_gpio.h> 26 26 #include <linux/platform_device.h> 27 - #include <linux/pm_wakeirq.h> 28 27 #include <linux/slab.h> 29 28 #include <linux/workqueue.h> 30 29 #include <linux/acpi.h> ··· 35 36 struct extcon_dev *edev; 36 37 37 38 struct gpio_desc *id_gpiod; 39 + struct gpio_desc *vbus_gpiod; 38 40 int id_irq; 41 + int vbus_irq; 39 42 40 43 unsigned long debounce_jiffies; 41 44 struct delayed_work wq_detcable; ··· 49 48 EXTCON_NONE, 50 49 }; 51 50 51 + /* 52 + * "USB" = VBUS and "USB-HOST" = !ID, so we have: 53 + * Both "USB" and "USB-HOST" can't be set as active at the 54 + * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive 55 + * even if VBUS is on. 56 + * 57 + * State | ID | VBUS 58 + * ---------------------------------------- 59 + * [1] USB | H | H 60 + * [2] none | H | L 61 + * [3] USB-HOST | L | H 62 + * [4] USB-HOST | L | L 63 + * 64 + * In case we have only one of these signals: 65 + * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. 66 + * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. 67 + */ 52 68 static void usb_extcon_detect_cable(struct work_struct *work) 53 69 { 54 - int id; 70 + int id, vbus; 55 71 struct usb_extcon_info *info = container_of(to_delayed_work(work), 56 72 struct usb_extcon_info, 57 73 wq_detcable); 58 74 59 - /* check ID and update cable state */ 60 - id = gpiod_get_value_cansleep(info->id_gpiod); 61 - if (id) { 62 - /* 63 - * ID = 1 means USB HOST cable detached. 64 - * As we don't have event for USB peripheral cable attached, 65 - * we simulate USB peripheral attach here. 66 - */ 75 + /* check ID and VBUS and update cable state */ 76 + id = info->id_gpiod ? 77 + gpiod_get_value_cansleep(info->id_gpiod) : 1; 78 + vbus = info->vbus_gpiod ? 79 + gpiod_get_value_cansleep(info->vbus_gpiod) : id; 80 + 81 + /* at first we clean states which are no longer active */ 82 + if (id) 67 83 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); 68 - extcon_set_state_sync(info->edev, EXTCON_USB, true); 69 - } else { 70 - /* 71 - * ID = 0 means USB HOST cable attached. 72 - * As we don't have event for USB peripheral cable detached, 73 - * we simulate USB peripheral detach here. 74 - */ 84 + if (!vbus) 75 85 extcon_set_state_sync(info->edev, EXTCON_USB, false); 86 + 87 + if (!id) { 76 88 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); 89 + } else { 90 + if (vbus) 91 + extcon_set_state_sync(info->edev, EXTCON_USB, true); 77 92 } 78 93 } 79 94 ··· 118 101 return -ENOMEM; 119 102 120 103 info->dev = dev; 121 - info->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); 122 - if (IS_ERR(info->id_gpiod)) { 123 - dev_err(dev, "failed to get ID GPIO\n"); 124 - return PTR_ERR(info->id_gpiod); 104 + info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); 105 + info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", 106 + GPIOD_IN); 107 + 108 + if (!info->id_gpiod && !info->vbus_gpiod) { 109 + dev_err(dev, "failed to get gpios\n"); 110 + return -ENODEV; 125 111 } 112 + 113 + if (IS_ERR(info->id_gpiod)) 114 + return PTR_ERR(info->id_gpiod); 115 + 116 + if (IS_ERR(info->vbus_gpiod)) 117 + return PTR_ERR(info->vbus_gpiod); 126 118 127 119 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 128 120 if (IS_ERR(info->edev)) { ··· 145 119 return ret; 146 120 } 147 121 148 - ret = gpiod_set_debounce(info->id_gpiod, 149 - USB_GPIO_DEBOUNCE_MS * 1000); 122 + if (info->id_gpiod) 123 + ret = gpiod_set_debounce(info->id_gpiod, 124 + USB_GPIO_DEBOUNCE_MS * 1000); 125 + if (!ret && info->vbus_gpiod) 126 + ret = gpiod_set_debounce(info->vbus_gpiod, 127 + USB_GPIO_DEBOUNCE_MS * 1000); 128 + 150 129 if (ret < 0) 151 130 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 152 131 153 132 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 154 133 155 - info->id_irq = gpiod_to_irq(info->id_gpiod); 156 - if (info->id_irq < 0) { 157 - dev_err(dev, "failed to get ID IRQ\n"); 158 - return info->id_irq; 134 + if (info->id_gpiod) { 135 + info->id_irq = gpiod_to_irq(info->id_gpiod); 136 + if (info->id_irq < 0) { 137 + dev_err(dev, "failed to get ID IRQ\n"); 138 + return info->id_irq; 139 + } 140 + 141 + ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 142 + usb_irq_handler, 143 + IRQF_TRIGGER_RISING | 144 + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 145 + pdev->name, info); 146 + if (ret < 0) { 147 + dev_err(dev, "failed to request handler for ID IRQ\n"); 148 + return ret; 149 + } 159 150 } 160 151 161 - ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 162 - usb_irq_handler, 163 - IRQF_TRIGGER_RISING | 164 - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 165 - pdev->name, info); 166 - if (ret < 0) { 167 - dev_err(dev, "failed to request handler for ID IRQ\n"); 168 - return ret; 152 + if (info->vbus_gpiod) { 153 + info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); 154 + if (info->vbus_irq < 0) { 155 + dev_err(dev, "failed to get VBUS IRQ\n"); 156 + return info->vbus_irq; 157 + } 158 + 159 + ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, 160 + usb_irq_handler, 161 + IRQF_TRIGGER_RISING | 162 + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 163 + pdev->name, info); 164 + if (ret < 0) { 165 + dev_err(dev, "failed to request handler for VBUS IRQ\n"); 166 + return ret; 167 + } 169 168 } 170 169 171 170 platform_set_drvdata(pdev, info); 172 171 device_init_wakeup(dev, true); 173 - dev_pm_set_wake_irq(dev, info->id_irq); 174 172 175 173 /* Perform initial detection */ 176 174 usb_extcon_detect_cable(&info->wq_detcable.work); ··· 207 157 struct usb_extcon_info *info = platform_get_drvdata(pdev); 208 158 209 159 cancel_delayed_work_sync(&info->wq_detcable); 210 - 211 - dev_pm_clear_wake_irq(&pdev->dev); 212 160 device_init_wakeup(&pdev->dev, false); 213 161 214 162 return 0; ··· 218 170 struct usb_extcon_info *info = dev_get_drvdata(dev); 219 171 int ret = 0; 220 172 173 + if (device_may_wakeup(dev)) { 174 + if (info->id_gpiod) { 175 + ret = enable_irq_wake(info->id_irq); 176 + if (ret) 177 + return ret; 178 + } 179 + if (info->vbus_gpiod) { 180 + ret = enable_irq_wake(info->vbus_irq); 181 + if (ret) { 182 + if (info->id_gpiod) 183 + disable_irq_wake(info->id_irq); 184 + 185 + return ret; 186 + } 187 + } 188 + } 189 + 221 190 /* 222 191 * We don't want to process any IRQs after this point 223 192 * as GPIOs used behind I2C subsystem might not be 224 193 * accessible until resume completes. So disable IRQ. 225 194 */ 226 - disable_irq(info->id_irq); 195 + if (info->id_gpiod) 196 + disable_irq(info->id_irq); 197 + if (info->vbus_gpiod) 198 + disable_irq(info->vbus_irq); 227 199 228 200 return ret; 229 201 } ··· 253 185 struct usb_extcon_info *info = dev_get_drvdata(dev); 254 186 int ret = 0; 255 187 256 - enable_irq(info->id_irq); 188 + if (device_may_wakeup(dev)) { 189 + if (info->id_gpiod) { 190 + ret = disable_irq_wake(info->id_irq); 191 + if (ret) 192 + return ret; 193 + } 194 + if (info->vbus_gpiod) { 195 + ret = disable_irq_wake(info->vbus_irq); 196 + if (ret) { 197 + if (info->id_gpiod) 198 + enable_irq_wake(info->id_irq); 199 + 200 + return ret; 201 + } 202 + } 203 + } 204 + 205 + if (info->id_gpiod) 206 + enable_irq(info->id_irq); 207 + if (info->vbus_gpiod) 208 + enable_irq(info->vbus_irq); 209 + 257 210 if (!device_may_wakeup(dev)) 258 211 queue_delayed_work(system_power_efficient_wq, 259 212 &info->wq_detcable, 0);