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