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