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