1/** 2 * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver 3 * 4 * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com 5 * Author: Roger Quadros <rogerq@ti.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 */ 16 17#include <linux/extcon.h> 18#include <linux/gpio.h> 19#include <linux/gpio/consumer.h> 20#include <linux/init.h> 21#include <linux/interrupt.h> 22#include <linux/irq.h> 23#include <linux/kernel.h> 24#include <linux/module.h> 25#include <linux/of_gpio.h> 26#include <linux/platform_device.h> 27#include <linux/slab.h> 28#include <linux/workqueue.h> 29 30#define USB_GPIO_DEBOUNCE_MS 20 /* ms */ 31 32struct usb_extcon_info { 33 struct device *dev; 34 struct extcon_dev *edev; 35 36 struct gpio_desc *id_gpiod; 37 int id_irq; 38 39 unsigned long debounce_jiffies; 40 struct delayed_work wq_detcable; 41}; 42 43static const unsigned int usb_extcon_cable[] = { 44 EXTCON_USB, 45 EXTCON_USB_HOST, 46 EXTCON_NONE, 47}; 48 49static void usb_extcon_detect_cable(struct work_struct *work) 50{ 51 int id; 52 struct usb_extcon_info *info = container_of(to_delayed_work(work), 53 struct usb_extcon_info, 54 wq_detcable); 55 56 /* check ID and update cable state */ 57 id = gpiod_get_value_cansleep(info->id_gpiod); 58 if (id) { 59 /* 60 * ID = 1 means USB HOST cable detached. 61 * As we don't have event for USB peripheral cable attached, 62 * we simulate USB peripheral attach here. 63 */ 64 extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, false); 65 extcon_set_cable_state_(info->edev, EXTCON_USB, true); 66 } else { 67 /* 68 * ID = 0 means USB HOST cable attached. 69 * As we don't have event for USB peripheral cable detached, 70 * we simulate USB peripheral detach here. 71 */ 72 extcon_set_cable_state_(info->edev, EXTCON_USB, false); 73 extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, true); 74 } 75} 76 77static irqreturn_t usb_irq_handler(int irq, void *dev_id) 78{ 79 struct usb_extcon_info *info = dev_id; 80 81 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, 82 info->debounce_jiffies); 83 84 return IRQ_HANDLED; 85} 86 87static int usb_extcon_probe(struct platform_device *pdev) 88{ 89 struct device *dev = &pdev->dev; 90 struct device_node *np = dev->of_node; 91 struct usb_extcon_info *info; 92 int ret; 93 94 if (!np) 95 return -EINVAL; 96 97 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 98 if (!info) 99 return -ENOMEM; 100 101 info->dev = dev; 102 info->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); 103 if (IS_ERR(info->id_gpiod)) { 104 dev_err(dev, "failed to get ID GPIO\n"); 105 return PTR_ERR(info->id_gpiod); 106 } 107 108 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 109 if (IS_ERR(info->edev)) { 110 dev_err(dev, "failed to allocate extcon device\n"); 111 return -ENOMEM; 112 } 113 114 ret = devm_extcon_dev_register(dev, info->edev); 115 if (ret < 0) { 116 dev_err(dev, "failed to register extcon device\n"); 117 return ret; 118 } 119 120 ret = gpiod_set_debounce(info->id_gpiod, 121 USB_GPIO_DEBOUNCE_MS * 1000); 122 if (ret < 0) 123 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 124 125 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 126 127 info->id_irq = gpiod_to_irq(info->id_gpiod); 128 if (info->id_irq < 0) { 129 dev_err(dev, "failed to get ID IRQ\n"); 130 return info->id_irq; 131 } 132 133 ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 134 usb_irq_handler, 135 IRQF_TRIGGER_RISING | 136 IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 137 pdev->name, info); 138 if (ret < 0) { 139 dev_err(dev, "failed to request handler for ID IRQ\n"); 140 return ret; 141 } 142 143 platform_set_drvdata(pdev, info); 144 device_init_wakeup(dev, 1); 145 146 /* Perform initial detection */ 147 usb_extcon_detect_cable(&info->wq_detcable.work); 148 149 return 0; 150} 151 152static int usb_extcon_remove(struct platform_device *pdev) 153{ 154 struct usb_extcon_info *info = platform_get_drvdata(pdev); 155 156 cancel_delayed_work_sync(&info->wq_detcable); 157 158 return 0; 159} 160 161#ifdef CONFIG_PM_SLEEP 162static int usb_extcon_suspend(struct device *dev) 163{ 164 struct usb_extcon_info *info = dev_get_drvdata(dev); 165 int ret = 0; 166 167 if (device_may_wakeup(dev)) { 168 ret = enable_irq_wake(info->id_irq); 169 if (ret) 170 return ret; 171 } 172 173 /* 174 * We don't want to process any IRQs after this point 175 * as GPIOs used behind I2C subsystem might not be 176 * accessible until resume completes. So disable IRQ. 177 */ 178 disable_irq(info->id_irq); 179 180 return ret; 181} 182 183static int usb_extcon_resume(struct device *dev) 184{ 185 struct usb_extcon_info *info = dev_get_drvdata(dev); 186 int ret = 0; 187 188 if (device_may_wakeup(dev)) { 189 ret = disable_irq_wake(info->id_irq); 190 if (ret) 191 return ret; 192 } 193 194 enable_irq(info->id_irq); 195 196 return ret; 197} 198#endif 199 200static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 201 usb_extcon_suspend, usb_extcon_resume); 202 203static const struct of_device_id usb_extcon_dt_match[] = { 204 { .compatible = "linux,extcon-usb-gpio", }, 205 { /* sentinel */ } 206}; 207MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 208 209static struct platform_driver usb_extcon_driver = { 210 .probe = usb_extcon_probe, 211 .remove = usb_extcon_remove, 212 .driver = { 213 .name = "extcon-usb-gpio", 214 .pm = &usb_extcon_pm_ops, 215 .of_match_table = usb_extcon_dt_match, 216 }, 217}; 218 219module_platform_driver(usb_extcon_driver); 220 221MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 222MODULE_DESCRIPTION("USB GPIO extcon driver"); 223MODULE_LICENSE("GPL v2"); 224