11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 238b1a3c6SRandy Dunlap /* 3e52817faSRoger Quadros * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver 4e52817faSRoger Quadros * 5f6dfb3c9SAlexander A. Klimov * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com 6e52817faSRoger Quadros * Author: Roger Quadros <rogerq@ti.com> 7e52817faSRoger Quadros */ 8e52817faSRoger Quadros 9176aa360SChanwoo Choi #include <linux/extcon-provider.h> 10638f958bSGeert Uytterhoeven #include <linux/gpio/consumer.h> 11e52817faSRoger Quadros #include <linux/init.h> 12e52817faSRoger Quadros #include <linux/interrupt.h> 13e52817faSRoger Quadros #include <linux/irq.h> 14e52817faSRoger Quadros #include <linux/kernel.h> 15e52817faSRoger Quadros #include <linux/module.h> 16e52817faSRoger Quadros #include <linux/platform_device.h> 17e52817faSRoger Quadros #include <linux/slab.h> 18e52817faSRoger Quadros #include <linux/workqueue.h> 19bcb7440eSPeter Chen #include <linux/pinctrl/consumer.h> 2057869c11SLinus Walleij #include <linux/mod_devicetable.h> 21e52817faSRoger Quadros 22e52817faSRoger Quadros #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ 23e52817faSRoger Quadros 24e52817faSRoger Quadros struct usb_extcon_info { 25e52817faSRoger Quadros struct device *dev; 26e52817faSRoger Quadros struct extcon_dev *edev; 27e52817faSRoger Quadros 28e52817faSRoger Quadros struct gpio_desc *id_gpiod; 29541332a1SRoger Quadros struct gpio_desc *vbus_gpiod; 30e52817faSRoger Quadros int id_irq; 31541332a1SRoger Quadros int vbus_irq; 32e52817faSRoger Quadros 33e52817faSRoger Quadros unsigned long debounce_jiffies; 34e52817faSRoger Quadros struct delayed_work wq_detcable; 35e52817faSRoger Quadros }; 36e52817faSRoger Quadros 3773b6ecdbSChanwoo Choi static const unsigned int usb_extcon_cable[] = { 382a9de9c0SChanwoo Choi EXTCON_USB, 392a9de9c0SChanwoo Choi EXTCON_USB_HOST, 402a9de9c0SChanwoo Choi EXTCON_NONE, 41e52817faSRoger Quadros }; 42e52817faSRoger Quadros 43541332a1SRoger Quadros /* 44541332a1SRoger Quadros * "USB" = VBUS and "USB-HOST" = !ID, so we have: 45541332a1SRoger Quadros * Both "USB" and "USB-HOST" can't be set as active at the 46541332a1SRoger Quadros * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive 47541332a1SRoger Quadros * even if VBUS is on. 48541332a1SRoger Quadros * 49541332a1SRoger Quadros * State | ID | VBUS 50541332a1SRoger Quadros * ---------------------------------------- 51541332a1SRoger Quadros * [1] USB | H | H 52541332a1SRoger Quadros * [2] none | H | L 53541332a1SRoger Quadros * [3] USB-HOST | L | H 54541332a1SRoger Quadros * [4] USB-HOST | L | L 55541332a1SRoger Quadros * 56541332a1SRoger Quadros * In case we have only one of these signals: 57541332a1SRoger Quadros * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1. 58541332a1SRoger Quadros * - ID only - we want to distinguish between [1] and [4], so VBUS = ID. 59541332a1SRoger Quadros */ 60e52817faSRoger Quadros static void usb_extcon_detect_cable(struct work_struct *work) 61e52817faSRoger Quadros { 62541332a1SRoger Quadros int id, vbus; 63e52817faSRoger Quadros struct usb_extcon_info *info = container_of(to_delayed_work(work), 64e52817faSRoger Quadros struct usb_extcon_info, 65e52817faSRoger Quadros wq_detcable); 66e52817faSRoger Quadros 67541332a1SRoger Quadros /* check ID and VBUS and update cable state */ 68541332a1SRoger Quadros id = info->id_gpiod ? 69541332a1SRoger Quadros gpiod_get_value_cansleep(info->id_gpiod) : 1; 70541332a1SRoger Quadros vbus = info->vbus_gpiod ? 71541332a1SRoger Quadros gpiod_get_value_cansleep(info->vbus_gpiod) : id; 72541332a1SRoger Quadros 73541332a1SRoger Quadros /* at first we clean states which are no longer active */ 74541332a1SRoger Quadros if (id) 758670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false); 76541332a1SRoger Quadros if (!vbus) 778670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB, false); 78541332a1SRoger Quadros 79541332a1SRoger Quadros if (!id) { 808670b459SChanwoo Choi extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true); 81541332a1SRoger Quadros } else { 82541332a1SRoger Quadros if (vbus) 83541332a1SRoger Quadros extcon_set_state_sync(info->edev, EXTCON_USB, true); 84e52817faSRoger Quadros } 85e52817faSRoger Quadros } 86e52817faSRoger Quadros 87e52817faSRoger Quadros static irqreturn_t usb_irq_handler(int irq, void *dev_id) 88e52817faSRoger Quadros { 89e52817faSRoger Quadros struct usb_extcon_info *info = dev_id; 90e52817faSRoger Quadros 91e52817faSRoger Quadros queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, 92e52817faSRoger Quadros info->debounce_jiffies); 93e52817faSRoger Quadros 94e52817faSRoger Quadros return IRQ_HANDLED; 95e52817faSRoger Quadros } 96e52817faSRoger Quadros 97e52817faSRoger Quadros static int usb_extcon_probe(struct platform_device *pdev) 98e52817faSRoger Quadros { 99e52817faSRoger Quadros struct device *dev = &pdev->dev; 100e52817faSRoger Quadros struct device_node *np = dev->of_node; 101e52817faSRoger Quadros struct usb_extcon_info *info; 102e52817faSRoger Quadros int ret; 103e52817faSRoger Quadros 104366380cdSAndy Shevchenko if (!np) 105e52817faSRoger Quadros return -EINVAL; 106e52817faSRoger Quadros 107e52817faSRoger Quadros info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 108e52817faSRoger Quadros if (!info) 109e52817faSRoger Quadros return -ENOMEM; 110e52817faSRoger Quadros 111e52817faSRoger Quadros info->dev = dev; 112541332a1SRoger Quadros info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN); 113541332a1SRoger Quadros info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus", 114541332a1SRoger Quadros GPIOD_IN); 115541332a1SRoger Quadros 116541332a1SRoger Quadros if (!info->id_gpiod && !info->vbus_gpiod) { 117541332a1SRoger Quadros dev_err(dev, "failed to get gpios\n"); 118541332a1SRoger Quadros return -ENODEV; 119e52817faSRoger Quadros } 120e52817faSRoger Quadros 121541332a1SRoger Quadros if (IS_ERR(info->id_gpiod)) 122541332a1SRoger Quadros return PTR_ERR(info->id_gpiod); 123541332a1SRoger Quadros 124541332a1SRoger Quadros if (IS_ERR(info->vbus_gpiod)) 125541332a1SRoger Quadros return PTR_ERR(info->vbus_gpiod); 126541332a1SRoger Quadros 127bc1aabadSRobert Baldyga info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 128bc1aabadSRobert Baldyga if (IS_ERR(info->edev)) { 129bc1aabadSRobert Baldyga dev_err(dev, "failed to allocate extcon device\n"); 130bc1aabadSRobert Baldyga return -ENOMEM; 131bc1aabadSRobert Baldyga } 132bc1aabadSRobert Baldyga 133bc1aabadSRobert Baldyga ret = devm_extcon_dev_register(dev, info->edev); 134bc1aabadSRobert Baldyga if (ret < 0) { 135bc1aabadSRobert Baldyga dev_err(dev, "failed to register extcon device\n"); 136bc1aabadSRobert Baldyga return ret; 137bc1aabadSRobert Baldyga } 138bc1aabadSRobert Baldyga 139541332a1SRoger Quadros if (info->id_gpiod) 140e52817faSRoger Quadros ret = gpiod_set_debounce(info->id_gpiod, 141e52817faSRoger Quadros USB_GPIO_DEBOUNCE_MS * 1000); 142541332a1SRoger Quadros if (!ret && info->vbus_gpiod) 143541332a1SRoger Quadros ret = gpiod_set_debounce(info->vbus_gpiod, 144541332a1SRoger Quadros USB_GPIO_DEBOUNCE_MS * 1000); 145541332a1SRoger Quadros 146e52817faSRoger Quadros if (ret < 0) 147e52817faSRoger Quadros info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 148e52817faSRoger Quadros 149e52817faSRoger Quadros INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 150e52817faSRoger Quadros 151541332a1SRoger Quadros if (info->id_gpiod) { 152e52817faSRoger Quadros info->id_irq = gpiod_to_irq(info->id_gpiod); 153e52817faSRoger Quadros if (info->id_irq < 0) { 154e52817faSRoger Quadros dev_err(dev, "failed to get ID IRQ\n"); 155e52817faSRoger Quadros return info->id_irq; 156e52817faSRoger Quadros } 157e52817faSRoger Quadros 158e52817faSRoger Quadros ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 159e52817faSRoger Quadros usb_irq_handler, 160e52817faSRoger Quadros IRQF_TRIGGER_RISING | 161e52817faSRoger Quadros IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 162e52817faSRoger Quadros pdev->name, info); 163e52817faSRoger Quadros if (ret < 0) { 164e52817faSRoger Quadros dev_err(dev, "failed to request handler for ID IRQ\n"); 165e52817faSRoger Quadros return ret; 166e52817faSRoger Quadros } 167541332a1SRoger Quadros } 168541332a1SRoger Quadros 169541332a1SRoger Quadros if (info->vbus_gpiod) { 170541332a1SRoger Quadros info->vbus_irq = gpiod_to_irq(info->vbus_gpiod); 171541332a1SRoger Quadros if (info->vbus_irq < 0) { 172541332a1SRoger Quadros dev_err(dev, "failed to get VBUS IRQ\n"); 173541332a1SRoger Quadros return info->vbus_irq; 174541332a1SRoger Quadros } 175541332a1SRoger Quadros 176541332a1SRoger Quadros ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL, 177541332a1SRoger Quadros usb_irq_handler, 178541332a1SRoger Quadros IRQF_TRIGGER_RISING | 179541332a1SRoger Quadros IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 180541332a1SRoger Quadros pdev->name, info); 181541332a1SRoger Quadros if (ret < 0) { 182541332a1SRoger Quadros dev_err(dev, "failed to request handler for VBUS IRQ\n"); 183541332a1SRoger Quadros return ret; 184541332a1SRoger Quadros } 185541332a1SRoger Quadros } 186e52817faSRoger Quadros 187e52817faSRoger Quadros platform_set_drvdata(pdev, info); 18898fd0792SPeter Chen device_set_wakeup_capable(&pdev->dev, true); 189e52817faSRoger Quadros 190e52817faSRoger Quadros /* Perform initial detection */ 191e52817faSRoger Quadros usb_extcon_detect_cable(&info->wq_detcable.work); 192e52817faSRoger Quadros 193e52817faSRoger Quadros return 0; 194e52817faSRoger Quadros } 195e52817faSRoger Quadros 196*5be3dfe6SUwe Kleine-König static void usb_extcon_remove(struct platform_device *pdev) 197e52817faSRoger Quadros { 198e52817faSRoger Quadros struct usb_extcon_info *info = platform_get_drvdata(pdev); 199e52817faSRoger Quadros 200e52817faSRoger Quadros cancel_delayed_work_sync(&info->wq_detcable); 20112bd0f32SGrygorii Strashko device_init_wakeup(&pdev->dev, false); 202e52817faSRoger Quadros } 203e52817faSRoger Quadros 204e52817faSRoger Quadros #ifdef CONFIG_PM_SLEEP 205e52817faSRoger Quadros static int usb_extcon_suspend(struct device *dev) 206e52817faSRoger Quadros { 207e52817faSRoger Quadros struct usb_extcon_info *info = dev_get_drvdata(dev); 208e52817faSRoger Quadros int ret = 0; 209e52817faSRoger Quadros 210541332a1SRoger Quadros if (device_may_wakeup(dev)) { 211541332a1SRoger Quadros if (info->id_gpiod) { 212541332a1SRoger Quadros ret = enable_irq_wake(info->id_irq); 213541332a1SRoger Quadros if (ret) 214541332a1SRoger Quadros return ret; 215541332a1SRoger Quadros } 216541332a1SRoger Quadros if (info->vbus_gpiod) { 217541332a1SRoger Quadros ret = enable_irq_wake(info->vbus_irq); 218541332a1SRoger Quadros if (ret) { 219541332a1SRoger Quadros if (info->id_gpiod) 220541332a1SRoger Quadros disable_irq_wake(info->id_irq); 221541332a1SRoger Quadros 222541332a1SRoger Quadros return ret; 223541332a1SRoger Quadros } 224541332a1SRoger Quadros } 225541332a1SRoger Quadros } 226541332a1SRoger Quadros 227bcb7440eSPeter Chen if (!device_may_wakeup(dev)) 228bcb7440eSPeter Chen pinctrl_pm_select_sleep_state(dev); 229bcb7440eSPeter Chen 230e52817faSRoger Quadros return ret; 231e52817faSRoger Quadros } 232e52817faSRoger Quadros 233e52817faSRoger Quadros static int usb_extcon_resume(struct device *dev) 234e52817faSRoger Quadros { 235e52817faSRoger Quadros struct usb_extcon_info *info = dev_get_drvdata(dev); 236e52817faSRoger Quadros int ret = 0; 237e52817faSRoger Quadros 238bcb7440eSPeter Chen if (!device_may_wakeup(dev)) 239bcb7440eSPeter Chen pinctrl_pm_select_default_state(dev); 240bcb7440eSPeter Chen 241541332a1SRoger Quadros if (device_may_wakeup(dev)) { 242541332a1SRoger Quadros if (info->id_gpiod) { 243541332a1SRoger Quadros ret = disable_irq_wake(info->id_irq); 244541332a1SRoger Quadros if (ret) 245541332a1SRoger Quadros return ret; 246541332a1SRoger Quadros } 247541332a1SRoger Quadros if (info->vbus_gpiod) { 248541332a1SRoger Quadros ret = disable_irq_wake(info->vbus_irq); 249541332a1SRoger Quadros if (ret) { 250541332a1SRoger Quadros if (info->id_gpiod) 251541332a1SRoger Quadros enable_irq_wake(info->id_irq); 252541332a1SRoger Quadros 253541332a1SRoger Quadros return ret; 254541332a1SRoger Quadros } 255541332a1SRoger Quadros } 256541332a1SRoger Quadros } 257541332a1SRoger Quadros 25804c08008SRoger Quadros queue_delayed_work(system_power_efficient_wq, 25904c08008SRoger Quadros &info->wq_detcable, 0); 260e52817faSRoger Quadros 261e52817faSRoger Quadros return ret; 262e52817faSRoger Quadros } 263e52817faSRoger Quadros #endif 264e52817faSRoger Quadros 265e52817faSRoger Quadros static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 266e52817faSRoger Quadros usb_extcon_suspend, usb_extcon_resume); 267e52817faSRoger Quadros 26834825e51SChanwoo Choi static const struct of_device_id usb_extcon_dt_match[] = { 269e52817faSRoger Quadros { .compatible = "linux,extcon-usb-gpio", }, 270e52817faSRoger Quadros { /* sentinel */ } 271e52817faSRoger Quadros }; 272e52817faSRoger Quadros MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 273e52817faSRoger Quadros 274058b6659SLu Baolu static const struct platform_device_id usb_extcon_platform_ids[] = { 275058b6659SLu Baolu { .name = "extcon-usb-gpio", }, 276058b6659SLu Baolu { /* sentinel */ } 277058b6659SLu Baolu }; 278058b6659SLu Baolu MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); 279058b6659SLu Baolu 280e52817faSRoger Quadros static struct platform_driver usb_extcon_driver = { 281e52817faSRoger Quadros .probe = usb_extcon_probe, 282*5be3dfe6SUwe Kleine-König .remove_new = usb_extcon_remove, 283e52817faSRoger Quadros .driver = { 284e52817faSRoger Quadros .name = "extcon-usb-gpio", 285e52817faSRoger Quadros .pm = &usb_extcon_pm_ops, 286e52817faSRoger Quadros .of_match_table = usb_extcon_dt_match, 287e52817faSRoger Quadros }, 288058b6659SLu Baolu .id_table = usb_extcon_platform_ids, 289e52817faSRoger Quadros }; 290e52817faSRoger Quadros 291e52817faSRoger Quadros module_platform_driver(usb_extcon_driver); 292e52817faSRoger Quadros 293e52817faSRoger Quadros MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 294e52817faSRoger Quadros MODULE_DESCRIPTION("USB GPIO extcon driver"); 295e52817faSRoger Quadros MODULE_LICENSE("GPL v2"); 296