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