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/pm_wakeirq.h> 28 #include <linux/slab.h> 29 #include <linux/workqueue.h> 30 #include <linux/acpi.h> 31 32 #define USB_GPIO_DEBOUNCE_MS 20 /* ms */ 33 34 struct usb_extcon_info { 35 struct device *dev; 36 struct extcon_dev *edev; 37 38 struct gpio_desc *id_gpiod; 39 int id_irq; 40 41 unsigned long debounce_jiffies; 42 struct delayed_work wq_detcable; 43 }; 44 45 static const unsigned int usb_extcon_cable[] = { 46 EXTCON_USB, 47 EXTCON_USB_HOST, 48 EXTCON_NONE, 49 }; 50 51 static void usb_extcon_detect_cable(struct work_struct *work) 52 { 53 int id; 54 struct usb_extcon_info *info = container_of(to_delayed_work(work), 55 struct usb_extcon_info, 56 wq_detcable); 57 58 /* check ID and update cable state */ 59 id = gpiod_get_value_cansleep(info->id_gpiod); 60 if (id) { 61 /* 62 * ID = 1 means USB HOST cable detached. 63 * As we don't have event for USB peripheral cable attached, 64 * we simulate USB peripheral attach here. 65 */ 66 extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, false); 67 extcon_set_cable_state_(info->edev, EXTCON_USB, true); 68 } else { 69 /* 70 * ID = 0 means USB HOST cable attached. 71 * As we don't have event for USB peripheral cable detached, 72 * we simulate USB peripheral detach here. 73 */ 74 extcon_set_cable_state_(info->edev, EXTCON_USB, false); 75 extcon_set_cable_state_(info->edev, EXTCON_USB_HOST, true); 76 } 77 } 78 79 static irqreturn_t usb_irq_handler(int irq, void *dev_id) 80 { 81 struct usb_extcon_info *info = dev_id; 82 83 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, 84 info->debounce_jiffies); 85 86 return IRQ_HANDLED; 87 } 88 89 static int usb_extcon_probe(struct platform_device *pdev) 90 { 91 struct device *dev = &pdev->dev; 92 struct device_node *np = dev->of_node; 93 struct usb_extcon_info *info; 94 int ret; 95 96 if (!np && !ACPI_HANDLE(dev)) 97 return -EINVAL; 98 99 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); 100 if (!info) 101 return -ENOMEM; 102 103 info->dev = dev; 104 info->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); 105 if (IS_ERR(info->id_gpiod)) { 106 dev_err(dev, "failed to get ID GPIO\n"); 107 return PTR_ERR(info->id_gpiod); 108 } 109 110 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); 111 if (IS_ERR(info->edev)) { 112 dev_err(dev, "failed to allocate extcon device\n"); 113 return -ENOMEM; 114 } 115 116 ret = devm_extcon_dev_register(dev, info->edev); 117 if (ret < 0) { 118 dev_err(dev, "failed to register extcon device\n"); 119 return ret; 120 } 121 122 ret = gpiod_set_debounce(info->id_gpiod, 123 USB_GPIO_DEBOUNCE_MS * 1000); 124 if (ret < 0) 125 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS); 126 127 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable); 128 129 info->id_irq = gpiod_to_irq(info->id_gpiod); 130 if (info->id_irq < 0) { 131 dev_err(dev, "failed to get ID IRQ\n"); 132 return info->id_irq; 133 } 134 135 ret = devm_request_threaded_irq(dev, info->id_irq, NULL, 136 usb_irq_handler, 137 IRQF_TRIGGER_RISING | 138 IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 139 pdev->name, info); 140 if (ret < 0) { 141 dev_err(dev, "failed to request handler for ID IRQ\n"); 142 return ret; 143 } 144 145 platform_set_drvdata(pdev, info); 146 device_init_wakeup(dev, true); 147 dev_pm_set_wake_irq(dev, info->id_irq); 148 149 /* Perform initial detection */ 150 usb_extcon_detect_cable(&info->wq_detcable.work); 151 152 return 0; 153 } 154 155 static int usb_extcon_remove(struct platform_device *pdev) 156 { 157 struct usb_extcon_info *info = platform_get_drvdata(pdev); 158 159 cancel_delayed_work_sync(&info->wq_detcable); 160 161 dev_pm_clear_wake_irq(&pdev->dev); 162 device_init_wakeup(&pdev->dev, false); 163 164 return 0; 165 } 166 167 #ifdef CONFIG_PM_SLEEP 168 static int usb_extcon_suspend(struct device *dev) 169 { 170 struct usb_extcon_info *info = dev_get_drvdata(dev); 171 int ret = 0; 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 183 static int usb_extcon_resume(struct device *dev) 184 { 185 struct usb_extcon_info *info = dev_get_drvdata(dev); 186 int ret = 0; 187 188 enable_irq(info->id_irq); 189 if (!device_may_wakeup(dev)) 190 queue_delayed_work(system_power_efficient_wq, 191 &info->wq_detcable, 0); 192 193 return ret; 194 } 195 #endif 196 197 static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops, 198 usb_extcon_suspend, usb_extcon_resume); 199 200 static const struct of_device_id usb_extcon_dt_match[] = { 201 { .compatible = "linux,extcon-usb-gpio", }, 202 { /* sentinel */ } 203 }; 204 MODULE_DEVICE_TABLE(of, usb_extcon_dt_match); 205 206 static const struct platform_device_id usb_extcon_platform_ids[] = { 207 { .name = "extcon-usb-gpio", }, 208 { /* sentinel */ } 209 }; 210 MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids); 211 212 static struct platform_driver usb_extcon_driver = { 213 .probe = usb_extcon_probe, 214 .remove = usb_extcon_remove, 215 .driver = { 216 .name = "extcon-usb-gpio", 217 .pm = &usb_extcon_pm_ops, 218 .of_match_table = usb_extcon_dt_match, 219 }, 220 .id_table = usb_extcon_platform_ids, 221 }; 222 223 module_platform_driver(usb_extcon_driver); 224 225 MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>"); 226 MODULE_DESCRIPTION("USB GPIO extcon driver"); 227 MODULE_LICENSE("GPL v2"); 228