xref: /linux/drivers/extcon/extcon-usb-gpio.c (revision e52817faae359ce95c93c2b6eb88b16d4b430181)
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