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