extcon-usb-gpio.c (7295B)
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 24struct 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 37static 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*/ 60static 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 87static 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 97static 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 196static 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 207static 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 if (!device_may_wakeup(dev)) 230 pinctrl_pm_select_sleep_state(dev); 231 232 return ret; 233} 234 235static int usb_extcon_resume(struct device *dev) 236{ 237 struct usb_extcon_info *info = dev_get_drvdata(dev); 238 int ret = 0; 239 240 if (!device_may_wakeup(dev)) 241 pinctrl_pm_select_default_state(dev); 242 243 if (device_may_wakeup(dev)) { 244 if (info->id_gpiod) { 245 ret = disable_irq_wake(info->id_irq); 246 if (ret) 247 return ret; 248 } 249 if (info->vbus_gpiod) { 250 ret = disable_irq_wake(info->vbus_irq); 251 if (ret) { 252 if (info->id_gpiod) 253 enable_irq_wake(info->id_irq); 254 255 return ret; 256 } 257 } 258 } 259 260 queue_delayed_work(system_power_efficient_wq, 261 &info->wq_detcable, 0); 262 263 return ret; 264} 265#endif 266 267static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 268 usb_extcon_suspend, usb_extcon_resume); 269 270static const struct of_device_id usb_extcon_dt_match[] = { 271 { .compatible = "linux,extcon-usb-gpio", }, 272 { /* sentinel */ } 273}; 274MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 275 276static const struct platform_device_id usb_extcon_platform_ids[] = { 277 { .name = "extcon-usb-gpio", }, 278 { /* sentinel */ } 279}; 280MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); 281 282static struct platform_driver usb_extcon_driver = { 283 .probe = usb_extcon_probe, 284 .remove = usb_extcon_remove, 285 .driver = { 286 .name = "extcon-usb-gpio", 287 .pm = &usb_extcon_pm_ops, 288 .of_match_table = usb_extcon_dt_match, 289 }, 290 .id_table = usb_extcon_platform_ids, 291}; 292 293module_platform_driver(usb_extcon_driver); 294 295MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 296MODULE_DESCRIPTION("USB GPIO extcon driver"); 297MODULE_LICENSE("GPL v2");