1*1802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2e52817faSRoger Quadros /** 3e52817faSRoger Quadros * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver 4e52817faSRoger Quadros * 5e52817faSRoger Quadros * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com 6e52817faSRoger Quadros * Author: Roger Quadros <rogerq@ti.com> 7e52817faSRoger Quadros */ 8e52817faSRoger Quadros 9176aa360SChanwoo Choi #include <linux/extcon-provider.h> 1092b7cb5dSRoger Quadros #include <linux/gpio.h> 11638f958bSGeert Uytterhoeven #include <linux/gpio/consumer.h> 12e52817faSRoger Quadros #include <linux/init.h> 13e52817faSRoger Quadros #include <linux/interrupt.h> 14e52817faSRoger Quadros #include <linux/irq.h> 15e52817faSRoger Quadros #include <linux/kernel.h> 16e52817faSRoger Quadros #include <linux/module.h> 17e52817faSRoger Quadros #include <linux/of_gpio.h> 18e52817faSRoger Quadros #include <linux/platform_device.h> 19e52817faSRoger Quadros #include <linux/slab.h> 20e52817faSRoger Quadros #include <linux/workqueue.h> 21bcb7440eSPeter Chen #include <linux/pinctrl/consumer.h> 22e52817faSRoger Quadros 23e52817faSRoger Quadros #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ 24e52817faSRoger Quadros 25e52817faSRoger Quadros struct usb_extcon_info { 26e52817faSRoger Quadros struct device *dev; 27e52817faSRoger Quadros struct extcon_dev *edev; 28e52817faSRoger Quadros 29e52817faSRoger Quadros struct gpio_desc *id_gpiod; 30541332a1SRoger Quadros struct gpio_desc *vbus_gpiod; 31e52817faSRoger Quadros int id_irq; 32541332a1SRoger Quadros int vbus_irq; 33e52817faSRoger Quadros 34e52817faSRoger Quadros unsigned long debounce_jiffies; 35e52817faSRoger Quadros struct delayed_work wq_detcable; 36e52817faSRoger Quadros }; 37e52817faSRoger Quadros 3873b6ecdbSChanwoo Choi static const unsigned int usb_extcon_cable[] = { 392a9de9c0SChanwoo Choi EXTCON_USB, 402a9de9c0SChanwoo Choi EXTCON_USB_HOST, 412a9de9c0SChanwoo Choi EXTCON_NONE, 42e52817faSRoger Quadros }; 43e52817faSRoger Quadros 44541332a1SRoger Quadros /* 45541332a1SRoger Quadros * "USB" = VBUS and "USB-HOST" = !ID, so we have: 46541332a1SRoger Quadros * Both "USB" and "USB-HOST" can't be set as active at the 47541332a1SRoger Quadros * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive 48541332a1SRoger Quadros * even if VBUS is on. 49541332a1SRoger Quadros * 50541332a1SRoger Quadros * State | ID | VBUS 51541332a1SRoger Quadros * ---------------------------------------- 52541332a1SRoger Quadros * [1] USB | H | H 53541332a1SRoger Quadros * [2] none | H | L 54541332a1SRoger Quadros * [3] USB-HOST | L | H 55541332a1SRoger Quadros * [4] USB-HOST | L | L 56541332a1SRoger Quadros * 57541332a1SRoger Quadros * In case we have only one of these signals: 58541332a1SRoger Quadros * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. 59541332a1SRoger Quadros * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. 60541332a1SRoger Quadros */ 61e52817faSRoger Quadros static void usb_extcon_detect_cable(struct work_struct *work) 62e52817faSRoger Quadros { 63541332a1SRoger Quadros int id, vbus; 64e52817faSRoger Quadros struct usb_extcon_info *info = container_of(to_delayed_work(work), 65e52817faSRoger Quadros struct usb_extcon_info, 66e52817faSRoger Quadros wq_detcable); 67e52817faSRoger Quadros 68541332a1SRoger Quadros /* check ID and VBUS and update cable state */ 69541332a1SRoger Quadros id = info->id_gpiod ? 70541332a1SRoger Quadros gpiod_get_value_cansleep(info->id_gpiod) : 1; 71541332a1SRoger Quadros vbus = info->vbus_gpiod ? 72541332a1SRoger Quadros gpiod_get_value_cansleep(info->vbus_gpiod) : id; 73541332a1SRoger Quadros 74541332a1SRoger Quadros /* at first we clean states which are no longer active */ 75541332a1SRoger Quadros if (id) 768670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); 77541332a1SRoger Quadros if (!vbus) 788670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB, false); 79541332a1SRoger Quadros 80541332a1SRoger Quadros if (!id) { 818670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); 82541332a1SRoger Quadros } else { 83541332a1SRoger Quadros if (vbus) 84541332a1SRoger Quadros extcon_set_state_sync(info->edev, EXTCON_USB, true); 85e52817faSRoger Quadros } 86e52817faSRoger Quadros } 87e52817faSRoger Quadros 88e52817faSRoger Quadros static irqreturn_t usb_irq_handler(int irq, void *dev_id) 89e52817faSRoger Quadros { 90e52817faSRoger Quadros struct usb_extcon_info *info = dev_id; 91e52817faSRoger Quadros 92e52817faSRoger Quadros queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, 93e52817faSRoger Quadros info->debounce_jiffies); 94e52817faSRoger Quadros 95e52817faSRoger Quadros return IRQ_HANDLED; 96e52817faSRoger Quadros } 97e52817faSRoger Quadros 98e52817faSRoger Quadros static int usb_extcon_probe(struct platform_device *pdev) 99e52817faSRoger Quadros { 100e52817faSRoger Quadros struct device *dev = &pdev->dev; 101e52817faSRoger Quadros struct device_node *np = dev->of_node; 102e52817faSRoger Quadros struct usb_extcon_info *info; 103e52817faSRoger Quadros int ret; 104e52817faSRoger Quadros 105366380cdSAndy Shevchenko if (!np) 106e52817faSRoger Quadros return -EINVAL; 107e52817faSRoger Quadros 108e52817faSRoger Quadros info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 109e52817faSRoger Quadros if (!info) 110e52817faSRoger Quadros return -ENOMEM; 111e52817faSRoger Quadros 112e52817faSRoger Quadros info->dev = dev; 113541332a1SRoger Quadros info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); 114541332a1SRoger Quadros info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", 115541332a1SRoger Quadros GPIOD_IN); 116541332a1SRoger Quadros 117541332a1SRoger Quadros if (!info->id_gpiod && !info->vbus_gpiod) { 118541332a1SRoger Quadros dev_err(dev, "failed to get gpios\n"); 119541332a1SRoger Quadros return -ENODEV; 120e52817faSRoger Quadros } 121e52817faSRoger Quadros 122541332a1SRoger Quadros if (IS_ERR(info->id_gpiod)) 123541332a1SRoger Quadros return PTR_ERR(info->id_gpiod); 124541332a1SRoger Quadros 125541332a1SRoger Quadros if (IS_ERR(info->vbus_gpiod)) 126541332a1SRoger Quadros return PTR_ERR(info->vbus_gpiod); 127541332a1SRoger Quadros 128bc1aabadSRobert Baldyga info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 129bc1aabadSRobert Baldyga if (IS_ERR(info->edev)) { 130bc1aabadSRobert Baldyga dev_err(dev, "failed to allocate extcon device\n"); 131bc1aabadSRobert Baldyga return -ENOMEM; 132bc1aabadSRobert Baldyga } 133bc1aabadSRobert Baldyga 134bc1aabadSRobert Baldyga ret = devm_extcon_dev_register(dev, info->edev); 135bc1aabadSRobert Baldyga if (ret < 0) { 136bc1aabadSRobert Baldyga dev_err(dev, "failed to register extcon device\n"); 137bc1aabadSRobert Baldyga return ret; 138bc1aabadSRobert Baldyga } 139bc1aabadSRobert Baldyga 140541332a1SRoger Quadros if (info->id_gpiod) 141e52817faSRoger Quadros ret = gpiod_set_debounce(info->id_gpiod, 142e52817faSRoger Quadros USB_GPIO_DEBOUNCE_MS * 1000); 143541332a1SRoger Quadros if (!ret && info->vbus_gpiod) 144541332a1SRoger Quadros ret = gpiod_set_debounce(info->vbus_gpiod, 145541332a1SRoger Quadros USB_GPIO_DEBOUNCE_MS * 1000); 146541332a1SRoger Quadros 147e52817faSRoger Quadros if (ret < 0) 148e52817faSRoger Quadros info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 149e52817faSRoger Quadros 150e52817faSRoger Quadros INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 151e52817faSRoger Quadros 152541332a1SRoger Quadros if (info->id_gpiod) { 153e52817faSRoger Quadros info->id_irq = gpiod_to_irq(info->id_gpiod); 154e52817faSRoger Quadros if (info->id_irq < 0) { 155e52817faSRoger Quadros dev_err(dev, "failed to get ID IRQ\n"); 156e52817faSRoger Quadros return info->id_irq; 157e52817faSRoger Quadros } 158e52817faSRoger Quadros 159e52817faSRoger Quadros ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 160e52817faSRoger Quadros usb_irq_handler, 161e52817faSRoger Quadros IRQF_TRIGGER_RISING | 162e52817faSRoger Quadros IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 163e52817faSRoger Quadros pdev->name, info); 164e52817faSRoger Quadros if (ret < 0) { 165e52817faSRoger Quadros dev_err(dev, "failed to request handler for ID IRQ\n"); 166e52817faSRoger Quadros return ret; 167e52817faSRoger Quadros } 168541332a1SRoger Quadros } 169541332a1SRoger Quadros 170541332a1SRoger Quadros if (info->vbus_gpiod) { 171541332a1SRoger Quadros info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); 172541332a1SRoger Quadros if (info->vbus_irq < 0) { 173541332a1SRoger Quadros dev_err(dev, "failed to get VBUS IRQ\n"); 174541332a1SRoger Quadros return info->vbus_irq; 175541332a1SRoger Quadros } 176541332a1SRoger Quadros 177541332a1SRoger Quadros ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, 178541332a1SRoger Quadros usb_irq_handler, 179541332a1SRoger Quadros IRQF_TRIGGER_RISING | 180541332a1SRoger Quadros IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 181541332a1SRoger Quadros pdev->name, info); 182541332a1SRoger Quadros if (ret < 0) { 183541332a1SRoger Quadros dev_err(dev, "failed to request handler for VBUS IRQ\n"); 184541332a1SRoger Quadros return ret; 185541332a1SRoger Quadros } 186541332a1SRoger Quadros } 187e52817faSRoger Quadros 188e52817faSRoger Quadros platform_set_drvdata(pdev, info); 18998fd0792SPeter Chen device_set_wakeup_capable(&pdev->dev, true); 190e52817faSRoger Quadros 191e52817faSRoger Quadros /* Perform initial detection */ 192e52817faSRoger Quadros usb_extcon_detect_cable(&info->wq_detcable.work); 193e52817faSRoger Quadros 194e52817faSRoger Quadros return 0; 195e52817faSRoger Quadros } 196e52817faSRoger Quadros 197e52817faSRoger Quadros static int usb_extcon_remove(struct platform_device *pdev) 198e52817faSRoger Quadros { 199e52817faSRoger Quadros struct usb_extcon_info *info = platform_get_drvdata(pdev); 200e52817faSRoger Quadros 201e52817faSRoger Quadros cancel_delayed_work_sync(&info->wq_detcable); 20212bd0f32SGrygorii Strashko device_init_wakeup(&pdev->dev, false); 20312bd0f32SGrygorii Strashko 204e52817faSRoger Quadros return 0; 205e52817faSRoger Quadros } 206e52817faSRoger Quadros 207e52817faSRoger Quadros #ifdef CONFIG_PM_SLEEP 208e52817faSRoger Quadros static int usb_extcon_suspend(struct device *dev) 209e52817faSRoger Quadros { 210e52817faSRoger Quadros struct usb_extcon_info *info = dev_get_drvdata(dev); 211e52817faSRoger Quadros int ret = 0; 212e52817faSRoger Quadros 213541332a1SRoger Quadros if (device_may_wakeup(dev)) { 214541332a1SRoger Quadros if (info->id_gpiod) { 215541332a1SRoger Quadros ret = enable_irq_wake(info->id_irq); 216541332a1SRoger Quadros if (ret) 217541332a1SRoger Quadros return ret; 218541332a1SRoger Quadros } 219541332a1SRoger Quadros if (info->vbus_gpiod) { 220541332a1SRoger Quadros ret = enable_irq_wake(info->vbus_irq); 221541332a1SRoger Quadros if (ret) { 222541332a1SRoger Quadros if (info->id_gpiod) 223541332a1SRoger Quadros disable_irq_wake(info->id_irq); 224541332a1SRoger Quadros 225541332a1SRoger Quadros return ret; 226541332a1SRoger Quadros } 227541332a1SRoger Quadros } 228541332a1SRoger Quadros } 229541332a1SRoger Quadros 230e52817faSRoger Quadros /* 231e52817faSRoger Quadros * We don't want to process any IRQs after this point 232e52817faSRoger Quadros * as GPIOs used behind I2C subsystem might not be 233e52817faSRoger Quadros * accessible until resume completes. So disable IRQ. 234e52817faSRoger Quadros */ 235541332a1SRoger Quadros if (info->id_gpiod) 236e52817faSRoger Quadros disable_irq(info->id_irq); 237541332a1SRoger Quadros if (info->vbus_gpiod) 238541332a1SRoger Quadros disable_irq(info->vbus_irq); 239e52817faSRoger Quadros 240bcb7440eSPeter Chen if (!device_may_wakeup(dev)) 241bcb7440eSPeter Chen pinctrl_pm_select_sleep_state(dev); 242bcb7440eSPeter Chen 243e52817faSRoger Quadros return ret; 244e52817faSRoger Quadros } 245e52817faSRoger Quadros 246e52817faSRoger Quadros static int usb_extcon_resume(struct device *dev) 247e52817faSRoger Quadros { 248e52817faSRoger Quadros struct usb_extcon_info *info = dev_get_drvdata(dev); 249e52817faSRoger Quadros int ret = 0; 250e52817faSRoger Quadros 251bcb7440eSPeter Chen if (!device_may_wakeup(dev)) 252bcb7440eSPeter Chen pinctrl_pm_select_default_state(dev); 253bcb7440eSPeter Chen 254541332a1SRoger Quadros if (device_may_wakeup(dev)) { 255541332a1SRoger Quadros if (info->id_gpiod) { 256541332a1SRoger Quadros ret = disable_irq_wake(info->id_irq); 257541332a1SRoger Quadros if (ret) 258541332a1SRoger Quadros return ret; 259541332a1SRoger Quadros } 260541332a1SRoger Quadros if (info->vbus_gpiod) { 261541332a1SRoger Quadros ret = disable_irq_wake(info->vbus_irq); 262541332a1SRoger Quadros if (ret) { 263541332a1SRoger Quadros if (info->id_gpiod) 264541332a1SRoger Quadros enable_irq_wake(info->id_irq); 265541332a1SRoger Quadros 266541332a1SRoger Quadros return ret; 267541332a1SRoger Quadros } 268541332a1SRoger Quadros } 269541332a1SRoger Quadros } 270541332a1SRoger Quadros 271541332a1SRoger Quadros if (info->id_gpiod) 272e52817faSRoger Quadros enable_irq(info->id_irq); 273541332a1SRoger Quadros if (info->vbus_gpiod) 274541332a1SRoger Quadros enable_irq(info->vbus_irq); 275541332a1SRoger Quadros 27604c08008SRoger Quadros queue_delayed_work(system_power_efficient_wq, 27704c08008SRoger Quadros &info->wq_detcable, 0); 278e52817faSRoger Quadros 279e52817faSRoger Quadros return ret; 280e52817faSRoger Quadros } 281e52817faSRoger Quadros #endif 282e52817faSRoger Quadros 283e52817faSRoger Quadros static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 284e52817faSRoger Quadros usb_extcon_suspend, usb_extcon_resume); 285e52817faSRoger Quadros 28634825e51SChanwoo Choi static const struct of_device_id usb_extcon_dt_match[] = { 287e52817faSRoger Quadros { .compatible = "linux,extcon-usb-gpio", }, 288e52817faSRoger Quadros { /* sentinel */ } 289e52817faSRoger Quadros }; 290e52817faSRoger Quadros MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 291e52817faSRoger Quadros 292058b6659SLu Baolu static const struct platform_device_id usb_extcon_platform_ids[] = { 293058b6659SLu Baolu { .name = "extcon-usb-gpio", }, 294058b6659SLu Baolu { /* sentinel */ } 295058b6659SLu Baolu }; 296058b6659SLu Baolu MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); 297058b6659SLu Baolu 298e52817faSRoger Quadros static struct platform_driver usb_extcon_driver = { 299e52817faSRoger Quadros .probe = usb_extcon_probe, 300e52817faSRoger Quadros .remove = usb_extcon_remove, 301e52817faSRoger Quadros .driver = { 302e52817faSRoger Quadros .name = "extcon-usb-gpio", 303e52817faSRoger Quadros .pm = &usb_extcon_pm_ops, 304e52817faSRoger Quadros .of_match_table = usb_extcon_dt_match, 305e52817faSRoger Quadros }, 306058b6659SLu Baolu .id_table = usb_extcon_platform_ids, 307e52817faSRoger Quadros }; 308e52817faSRoger Quadros 309e52817faSRoger Quadros module_platform_driver(usb_extcon_driver); 310e52817faSRoger Quadros 311e52817faSRoger Quadros MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 312e52817faSRoger Quadros MODULE_DESCRIPTION("USB GPIO extcon driver"); 313e52817faSRoger Quadros MODULE_LICENSE("GPL v2"); 314