1 /** 2 * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver 3 * 4 * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com 5 * Author: Roger Quadros <rogerq@ti.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 */ 16 17 #include <linux/extcon-provider.h> 18 #include <linux/gpio.h> 19 #include <linux/gpio/consumer.h> 20 #include <linux/init.h> 21 #include <linux/interrupt.h> 22 #include <linux/irq.h> 23 #include <linux/kernel.h> 24 #include <linux/module.h> 25 #include <linux/of_gpio.h> 26 #include <linux/platform_device.h> 27 #include <linux/slab.h> 28 #include <linux/workqueue.h> 29 #include <linux/pinctrl/consumer.h> 30 31 #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ 32 33 struct usb_extcon_info { 34 struct device *dev; 35 struct extcon_dev *edev; 36 37 struct gpio_desc *id_gpiod; 38 struct gpio_desc *vbus_gpiod; 39 int id_irq; 40 int vbus_irq; 41 42 unsigned long debounce_jiffies; 43 struct delayed_work wq_detcable; 44 }; 45 46 static const unsigned int usb_extcon_cable[] = { 47 EXTCON_USB, 48 EXTCON_USB_HOST, 49 EXTCON_NONE, 50 }; 51 52 /* 53 * "USB" = VBUS and "USB-HOST" = !ID, so we have: 54 * Both "USB" and "USB-HOST" can't be set as active at the 55 * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive 56 * even if VBUS is on. 57 * 58 * State | ID | VBUS 59 * ---------------------------------------- 60 * [1] USB | H | H 61 * [2] none | H | L 62 * [3] USB-HOST | L | H 63 * [4] USB-HOST | L | L 64 * 65 * In case we have only one of these signals: 66 * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. 67 * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. 68 */ 69 static void usb_extcon_detect_cable(struct work_struct *work) 70 { 71 int id, vbus; 72 struct usb_extcon_info *info = container_of(to_delayed_work(work), 73 struct usb_extcon_info, 74 wq_detcable); 75 76 /* check ID and VBUS and update cable state */ 77 id = info->id_gpiod ? 78 gpiod_get_value_cansleep(info->id_gpiod) : 1; 79 vbus = info->vbus_gpiod ? 80 gpiod_get_value_cansleep(info->vbus_gpiod) : id; 81 82 /* at first we clean states which are no longer active */ 83 if (id) 84 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); 85 if (!vbus) 86 extcon_set_state_sync(info->edev, EXTCON_USB, false); 87 88 if (!id) { 89 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); 90 } else { 91 if (vbus) 92 extcon_set_state_sync(info->edev, EXTCON_USB, true); 93 } 94 } 95 96 static irqreturn_t usb_irq_handler(int irq, void *dev_id) 97 { 98 struct usb_extcon_info *info = dev_id; 99 100 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, 101 info->debounce_jiffies); 102 103 return IRQ_HANDLED; 104 } 105 106 static int usb_extcon_probe(struct platform_device *pdev) 107 { 108 struct device *dev = &pdev->dev; 109 struct device_node *np = dev->of_node; 110 struct usb_extcon_info *info; 111 int ret; 112 113 if (!np) 114 return -EINVAL; 115 116 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 117 if (!info) 118 return -ENOMEM; 119 120 info->dev = dev; 121 info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); 122 info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", 123 GPIOD_IN); 124 125 if (!info->id_gpiod && !info->vbus_gpiod) { 126 dev_err(dev, "failed to get gpios\n"); 127 return -ENODEV; 128 } 129 130 if (IS_ERR(info->id_gpiod)) 131 return PTR_ERR(info->id_gpiod); 132 133 if (IS_ERR(info->vbus_gpiod)) 134 return PTR_ERR(info->vbus_gpiod); 135 136 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 137 if (IS_ERR(info->edev)) { 138 dev_err(dev, "failed to allocate extcon device\n"); 139 return -ENOMEM; 140 } 141 142 ret = devm_extcon_dev_register(dev, info->edev); 143 if (ret < 0) { 144 dev_err(dev, "failed to register extcon device\n"); 145 return ret; 146 } 147 148 if (info->id_gpiod) 149 ret = gpiod_set_debounce(info->id_gpiod, 150 USB_GPIO_DEBOUNCE_MS * 1000); 151 if (!ret && info->vbus_gpiod) 152 ret = gpiod_set_debounce(info->vbus_gpiod, 153 USB_GPIO_DEBOUNCE_MS * 1000); 154 155 if (ret < 0) 156 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 157 158 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 159 160 if (info->id_gpiod) { 161 info->id_irq = gpiod_to_irq(info->id_gpiod); 162 if (info->id_irq < 0) { 163 dev_err(dev, "failed to get ID IRQ\n"); 164 return info->id_irq; 165 } 166 167 ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 168 usb_irq_handler, 169 IRQF_TRIGGER_RISING | 170 IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 171 pdev->name, info); 172 if (ret < 0) { 173 dev_err(dev, "failed to request handler for ID IRQ\n"); 174 return ret; 175 } 176 } 177 178 if (info->vbus_gpiod) { 179 info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); 180 if (info->vbus_irq < 0) { 181 dev_err(dev, "failed to get VBUS IRQ\n"); 182 return info->vbus_irq; 183 } 184 185 ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, 186 usb_irq_handler, 187 IRQF_TRIGGER_RISING | 188 IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 189 pdev->name, info); 190 if (ret < 0) { 191 dev_err(dev, "failed to request handler for VBUS IRQ\n"); 192 return ret; 193 } 194 } 195 196 platform_set_drvdata(pdev, info); 197 device_set_wakeup_capable(&pdev->dev, true); 198 199 /* Perform initial detection */ 200 usb_extcon_detect_cable(&info->wq_detcable.work); 201 202 return 0; 203 } 204 205 static int usb_extcon_remove(struct platform_device *pdev) 206 { 207 struct usb_extcon_info *info = platform_get_drvdata(pdev); 208 209 cancel_delayed_work_sync(&info->wq_detcable); 210 device_init_wakeup(&pdev->dev, false); 211 212 return 0; 213 } 214 215 #ifdef CONFIG_PM_SLEEP 216 static int usb_extcon_suspend(struct device *dev) 217 { 218 struct usb_extcon_info *info = dev_get_drvdata(dev); 219 int ret = 0; 220 221 if (device_may_wakeup(dev)) { 222 if (info->id_gpiod) { 223 ret = enable_irq_wake(info->id_irq); 224 if (ret) 225 return ret; 226 } 227 if (info->vbus_gpiod) { 228 ret = enable_irq_wake(info->vbus_irq); 229 if (ret) { 230 if (info->id_gpiod) 231 disable_irq_wake(info->id_irq); 232 233 return ret; 234 } 235 } 236 } 237 238 /* 239 * We don't want to process any IRQs after this point 240 * as GPIOs used behind I2C subsystem might not be 241 * accessible until resume completes. So disable IRQ. 242 */ 243 if (info->id_gpiod) 244 disable_irq(info->id_irq); 245 if (info->vbus_gpiod) 246 disable_irq(info->vbus_irq); 247 248 if (!device_may_wakeup(dev)) 249 pinctrl_pm_select_sleep_state(dev); 250 251 return ret; 252 } 253 254 static int usb_extcon_resume(struct device *dev) 255 { 256 struct usb_extcon_info *info = dev_get_drvdata(dev); 257 int ret = 0; 258 259 if (!device_may_wakeup(dev)) 260 pinctrl_pm_select_default_state(dev); 261 262 if (device_may_wakeup(dev)) { 263 if (info->id_gpiod) { 264 ret = disable_irq_wake(info->id_irq); 265 if (ret) 266 return ret; 267 } 268 if (info->vbus_gpiod) { 269 ret = disable_irq_wake(info->vbus_irq); 270 if (ret) { 271 if (info->id_gpiod) 272 enable_irq_wake(info->id_irq); 273 274 return ret; 275 } 276 } 277 } 278 279 if (info->id_gpiod) 280 enable_irq(info->id_irq); 281 if (info->vbus_gpiod) 282 enable_irq(info->vbus_irq); 283 284 queue_delayed_work(system_power_efficient_wq, 285 &info->wq_detcable, 0); 286 287 return ret; 288 } 289 #endif 290 291 static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 292 usb_extcon_suspend, usb_extcon_resume); 293 294 static const struct of_device_id usb_extcon_dt_match[] = { 295 { .compatible = "linux,extcon-usb-gpio", }, 296 { /* sentinel */ } 297 }; 298 MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 299 300 static const struct platform_device_id usb_extcon_platform_ids[] = { 301 { .name = "extcon-usb-gpio", }, 302 { /* sentinel */ } 303 }; 304 MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); 305 306 static struct platform_driver usb_extcon_driver = { 307 .probe = usb_extcon_probe, 308 .remove = usb_extcon_remove, 309 .driver = { 310 .name = "extcon-usb-gpio", 311 .pm = &usb_extcon_pm_ops, 312 .of_match_table = usb_extcon_dt_match, 313 }, 314 .id_table = usb_extcon_platform_ids, 315 }; 316 317 module_platform_driver(usb_extcon_driver); 318 319 MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 320 MODULE_DESCRIPTION("USB GPIO extcon driver"); 321 MODULE_LICENSE("GPL v2"); 322