xref: /linux/drivers/extcon/extcon-usb-gpio.c (revision 1802d0beecafe581ad584634ba92f8a471d8a63a)
1*1802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2e52817faSRoger Quadros /**
3e52817faSRoger Quadros  * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver
4e52817faSRoger Quadros  *
5e52817faSRoger Quadros  * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com
6e52817faSRoger Quadros  * Author: Roger Quadros <rogerq@ti.com>
7e52817faSRoger Quadros  */
8e52817faSRoger Quadros 
9176aa360SChanwoo Choi #include <linux/extcon-provider.h>
1092b7cb5dSRoger Quadros #include <linux/gpio.h>
11638f958bSGeert Uytterhoeven #include <linux/gpio/consumer.h>
12e52817faSRoger Quadros #include <linux/init.h>
13e52817faSRoger Quadros #include <linux/interrupt.h>
14e52817faSRoger Quadros #include <linux/irq.h>
15e52817faSRoger Quadros #include <linux/kernel.h>
16e52817faSRoger Quadros #include <linux/module.h>
17e52817faSRoger Quadros #include <linux/of_gpio.h>
18e52817faSRoger Quadros #include <linux/platform_device.h>
19e52817faSRoger Quadros #include <linux/slab.h>
20e52817faSRoger Quadros #include <linux/workqueue.h>
21bcb7440eSPeter Chen #include <linux/pinctrl/consumer.h>
22e52817faSRoger Quadros 
23e52817faSRoger Quadros #define USB_GPIO_DEBOUNCE_MS	20	/* ms */
24e52817faSRoger Quadros 
25e52817faSRoger Quadros struct usb_extcon_info {
26e52817faSRoger Quadros 	struct device *dev;
27e52817faSRoger Quadros 	struct extcon_dev *edev;
28e52817faSRoger Quadros 
29e52817faSRoger Quadros 	struct gpio_desc *id_gpiod;
30541332a1SRoger Quadros 	struct gpio_desc *vbus_gpiod;
31e52817faSRoger Quadros 	int id_irq;
32541332a1SRoger Quadros 	int vbus_irq;
33e52817faSRoger Quadros 
34e52817faSRoger Quadros 	unsigned long debounce_jiffies;
35e52817faSRoger Quadros 	struct delayed_work wq_detcable;
36e52817faSRoger Quadros };
37e52817faSRoger Quadros 
3873b6ecdbSChanwoo Choi static const unsigned int usb_extcon_cable[] = {
392a9de9c0SChanwoo Choi 	EXTCON_USB,
402a9de9c0SChanwoo Choi 	EXTCON_USB_HOST,
412a9de9c0SChanwoo Choi 	EXTCON_NONE,
42e52817faSRoger Quadros };
43e52817faSRoger Quadros 
44541332a1SRoger Quadros /*
45541332a1SRoger Quadros  * "USB" = VBUS and "USB-HOST" = !ID, so we have:
46541332a1SRoger Quadros  * Both "USB" and "USB-HOST" can't be set as active at the
47541332a1SRoger Quadros  * same time so if "USB-HOST" is active (i.e. ID is 0)  we keep "USB" inactive
48541332a1SRoger Quadros  * even if VBUS is on.
49541332a1SRoger Quadros  *
50541332a1SRoger Quadros  *  State              |    ID   |   VBUS
51541332a1SRoger Quadros  * ----------------------------------------
52541332a1SRoger Quadros  *  [1] USB            |    H    |    H
53541332a1SRoger Quadros  *  [2] none           |    H    |    L
54541332a1SRoger Quadros  *  [3] USB-HOST       |    L    |    H
55541332a1SRoger Quadros  *  [4] USB-HOST       |    L    |    L
56541332a1SRoger Quadros  *
57541332a1SRoger Quadros  * In case we have only one of these signals:
58541332a1SRoger Quadros  * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1.
59541332a1SRoger Quadros  * - ID only - we want to distinguish between [1] and [4], so VBUS = ID.
60541332a1SRoger Quadros */
61e52817faSRoger Quadros static void usb_extcon_detect_cable(struct work_struct *work)
62e52817faSRoger Quadros {
63541332a1SRoger Quadros 	int id, vbus;
64e52817faSRoger Quadros 	struct usb_extcon_info *info = container_of(to_delayed_work(work),
65e52817faSRoger Quadros 						    struct usb_extcon_info,
66e52817faSRoger Quadros 						    wq_detcable);
67e52817faSRoger Quadros 
68541332a1SRoger Quadros 	/* check ID and VBUS and update cable state */
69541332a1SRoger Quadros 	id = info->id_gpiod ?
70541332a1SRoger Quadros 		gpiod_get_value_cansleep(info->id_gpiod) : 1;
71541332a1SRoger Quadros 	vbus = info->vbus_gpiod ?
72541332a1SRoger Quadros 		gpiod_get_value_cansleep(info->vbus_gpiod) : id;
73541332a1SRoger Quadros 
74541332a1SRoger Quadros 	/* at first we clean states which are no longer active */
75541332a1SRoger Quadros 	if (id)
768670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
77541332a1SRoger Quadros 	if (!vbus)
788670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB, false);
79541332a1SRoger Quadros 
80541332a1SRoger Quadros 	if (!id) {
818670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
82541332a1SRoger Quadros 	} else {
83541332a1SRoger Quadros 		if (vbus)
84541332a1SRoger Quadros 			extcon_set_state_sync(info->edev, EXTCON_USB, true);
85e52817faSRoger Quadros 	}
86e52817faSRoger Quadros }
87e52817faSRoger Quadros 
88e52817faSRoger Quadros static irqreturn_t usb_irq_handler(int irq, void *dev_id)
89e52817faSRoger Quadros {
90e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_id;
91e52817faSRoger Quadros 
92e52817faSRoger Quadros 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
93e52817faSRoger Quadros 			   info->debounce_jiffies);
94e52817faSRoger Quadros 
95e52817faSRoger Quadros 	return IRQ_HANDLED;
96e52817faSRoger Quadros }
97e52817faSRoger Quadros 
98e52817faSRoger Quadros static int usb_extcon_probe(struct platform_device *pdev)
99e52817faSRoger Quadros {
100e52817faSRoger Quadros 	struct device *dev = &pdev->dev;
101e52817faSRoger Quadros 	struct device_node *np = dev->of_node;
102e52817faSRoger Quadros 	struct usb_extcon_info *info;
103e52817faSRoger Quadros 	int ret;
104e52817faSRoger Quadros 
105366380cdSAndy Shevchenko 	if (!np)
106e52817faSRoger Quadros 		return -EINVAL;
107e52817faSRoger Quadros 
108e52817faSRoger Quadros 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
109e52817faSRoger Quadros 	if (!info)
110e52817faSRoger Quadros 		return -ENOMEM;
111e52817faSRoger Quadros 
112e52817faSRoger Quadros 	info->dev = dev;
113541332a1SRoger Quadros 	info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN);
114541332a1SRoger Quadros 	info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
115541332a1SRoger Quadros 						   GPIOD_IN);
116541332a1SRoger Quadros 
117541332a1SRoger Quadros 	if (!info->id_gpiod && !info->vbus_gpiod) {
118541332a1SRoger Quadros 		dev_err(dev, "failed to get gpios\n");
119541332a1SRoger Quadros 		return -ENODEV;
120e52817faSRoger Quadros 	}
121e52817faSRoger Quadros 
122541332a1SRoger Quadros 	if (IS_ERR(info->id_gpiod))
123541332a1SRoger Quadros 		return PTR_ERR(info->id_gpiod);
124541332a1SRoger Quadros 
125541332a1SRoger Quadros 	if (IS_ERR(info->vbus_gpiod))
126541332a1SRoger Quadros 		return PTR_ERR(info->vbus_gpiod);
127541332a1SRoger Quadros 
128bc1aabadSRobert Baldyga 	info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
129bc1aabadSRobert Baldyga 	if (IS_ERR(info->edev)) {
130bc1aabadSRobert Baldyga 		dev_err(dev, "failed to allocate extcon device\n");
131bc1aabadSRobert Baldyga 		return -ENOMEM;
132bc1aabadSRobert Baldyga 	}
133bc1aabadSRobert Baldyga 
134bc1aabadSRobert Baldyga 	ret = devm_extcon_dev_register(dev, info->edev);
135bc1aabadSRobert Baldyga 	if (ret < 0) {
136bc1aabadSRobert Baldyga 		dev_err(dev, "failed to register extcon device\n");
137bc1aabadSRobert Baldyga 		return ret;
138bc1aabadSRobert Baldyga 	}
139bc1aabadSRobert Baldyga 
140541332a1SRoger Quadros 	if (info->id_gpiod)
141e52817faSRoger Quadros 		ret = gpiod_set_debounce(info->id_gpiod,
142e52817faSRoger Quadros 					 USB_GPIO_DEBOUNCE_MS * 1000);
143541332a1SRoger Quadros 	if (!ret && info->vbus_gpiod)
144541332a1SRoger Quadros 		ret = gpiod_set_debounce(info->vbus_gpiod,
145541332a1SRoger Quadros 					 USB_GPIO_DEBOUNCE_MS * 1000);
146541332a1SRoger Quadros 
147e52817faSRoger Quadros 	if (ret < 0)
148e52817faSRoger Quadros 		info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS);
149e52817faSRoger Quadros 
150e52817faSRoger Quadros 	INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
151e52817faSRoger Quadros 
152541332a1SRoger Quadros 	if (info->id_gpiod) {
153e52817faSRoger Quadros 		info->id_irq = gpiod_to_irq(info->id_gpiod);
154e52817faSRoger Quadros 		if (info->id_irq < 0) {
155e52817faSRoger Quadros 			dev_err(dev, "failed to get ID IRQ\n");
156e52817faSRoger Quadros 			return info->id_irq;
157e52817faSRoger Quadros 		}
158e52817faSRoger Quadros 
159e52817faSRoger Quadros 		ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
160e52817faSRoger Quadros 						usb_irq_handler,
161e52817faSRoger Quadros 						IRQF_TRIGGER_RISING |
162e52817faSRoger Quadros 						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
163e52817faSRoger Quadros 						pdev->name, info);
164e52817faSRoger Quadros 		if (ret < 0) {
165e52817faSRoger Quadros 			dev_err(dev, "failed to request handler for ID IRQ\n");
166e52817faSRoger Quadros 			return ret;
167e52817faSRoger Quadros 		}
168541332a1SRoger Quadros 	}
169541332a1SRoger Quadros 
170541332a1SRoger Quadros 	if (info->vbus_gpiod) {
171541332a1SRoger Quadros 		info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
172541332a1SRoger Quadros 		if (info->vbus_irq < 0) {
173541332a1SRoger Quadros 			dev_err(dev, "failed to get VBUS IRQ\n");
174541332a1SRoger Quadros 			return info->vbus_irq;
175541332a1SRoger Quadros 		}
176541332a1SRoger Quadros 
177541332a1SRoger Quadros 		ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
178541332a1SRoger Quadros 						usb_irq_handler,
179541332a1SRoger Quadros 						IRQF_TRIGGER_RISING |
180541332a1SRoger Quadros 						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
181541332a1SRoger Quadros 						pdev->name, info);
182541332a1SRoger Quadros 		if (ret < 0) {
183541332a1SRoger Quadros 			dev_err(dev, "failed to request handler for VBUS IRQ\n");
184541332a1SRoger Quadros 			return ret;
185541332a1SRoger Quadros 		}
186541332a1SRoger Quadros 	}
187e52817faSRoger Quadros 
188e52817faSRoger Quadros 	platform_set_drvdata(pdev, info);
18998fd0792SPeter Chen 	device_set_wakeup_capable(&pdev->dev, true);
190e52817faSRoger Quadros 
191e52817faSRoger Quadros 	/* Perform initial detection */
192e52817faSRoger Quadros 	usb_extcon_detect_cable(&info->wq_detcable.work);
193e52817faSRoger Quadros 
194e52817faSRoger Quadros 	return 0;
195e52817faSRoger Quadros }
196e52817faSRoger Quadros 
197e52817faSRoger Quadros static int usb_extcon_remove(struct platform_device *pdev)
198e52817faSRoger Quadros {
199e52817faSRoger Quadros 	struct usb_extcon_info *info = platform_get_drvdata(pdev);
200e52817faSRoger Quadros 
201e52817faSRoger Quadros 	cancel_delayed_work_sync(&info->wq_detcable);
20212bd0f32SGrygorii Strashko 	device_init_wakeup(&pdev->dev, false);
20312bd0f32SGrygorii Strashko 
204e52817faSRoger Quadros 	return 0;
205e52817faSRoger Quadros }
206e52817faSRoger Quadros 
207e52817faSRoger Quadros #ifdef CONFIG_PM_SLEEP
208e52817faSRoger Quadros static int usb_extcon_suspend(struct device *dev)
209e52817faSRoger Quadros {
210e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_get_drvdata(dev);
211e52817faSRoger Quadros 	int ret = 0;
212e52817faSRoger Quadros 
213541332a1SRoger Quadros 	if (device_may_wakeup(dev)) {
214541332a1SRoger Quadros 		if (info->id_gpiod) {
215541332a1SRoger Quadros 			ret = enable_irq_wake(info->id_irq);
216541332a1SRoger Quadros 			if (ret)
217541332a1SRoger Quadros 				return ret;
218541332a1SRoger Quadros 		}
219541332a1SRoger Quadros 		if (info->vbus_gpiod) {
220541332a1SRoger Quadros 			ret = enable_irq_wake(info->vbus_irq);
221541332a1SRoger Quadros 			if (ret) {
222541332a1SRoger Quadros 				if (info->id_gpiod)
223541332a1SRoger Quadros 					disable_irq_wake(info->id_irq);
224541332a1SRoger Quadros 
225541332a1SRoger Quadros 				return ret;
226541332a1SRoger Quadros 			}
227541332a1SRoger Quadros 		}
228541332a1SRoger Quadros 	}
229541332a1SRoger Quadros 
230e52817faSRoger Quadros 	/*
231e52817faSRoger Quadros 	 * We don't want to process any IRQs after this point
232e52817faSRoger Quadros 	 * as GPIOs used behind I2C subsystem might not be
233e52817faSRoger Quadros 	 * accessible until resume completes. So disable IRQ.
234e52817faSRoger Quadros 	 */
235541332a1SRoger Quadros 	if (info->id_gpiod)
236e52817faSRoger Quadros 		disable_irq(info->id_irq);
237541332a1SRoger Quadros 	if (info->vbus_gpiod)
238541332a1SRoger Quadros 		disable_irq(info->vbus_irq);
239e52817faSRoger Quadros 
240bcb7440eSPeter Chen 	if (!device_may_wakeup(dev))
241bcb7440eSPeter Chen 		pinctrl_pm_select_sleep_state(dev);
242bcb7440eSPeter Chen 
243e52817faSRoger Quadros 	return ret;
244e52817faSRoger Quadros }
245e52817faSRoger Quadros 
246e52817faSRoger Quadros static int usb_extcon_resume(struct device *dev)
247e52817faSRoger Quadros {
248e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_get_drvdata(dev);
249e52817faSRoger Quadros 	int ret = 0;
250e52817faSRoger Quadros 
251bcb7440eSPeter Chen 	if (!device_may_wakeup(dev))
252bcb7440eSPeter Chen 		pinctrl_pm_select_default_state(dev);
253bcb7440eSPeter Chen 
254541332a1SRoger Quadros 	if (device_may_wakeup(dev)) {
255541332a1SRoger Quadros 		if (info->id_gpiod) {
256541332a1SRoger Quadros 			ret = disable_irq_wake(info->id_irq);
257541332a1SRoger Quadros 			if (ret)
258541332a1SRoger Quadros 				return ret;
259541332a1SRoger Quadros 		}
260541332a1SRoger Quadros 		if (info->vbus_gpiod) {
261541332a1SRoger Quadros 			ret = disable_irq_wake(info->vbus_irq);
262541332a1SRoger Quadros 			if (ret) {
263541332a1SRoger Quadros 				if (info->id_gpiod)
264541332a1SRoger Quadros 					enable_irq_wake(info->id_irq);
265541332a1SRoger Quadros 
266541332a1SRoger Quadros 				return ret;
267541332a1SRoger Quadros 			}
268541332a1SRoger Quadros 		}
269541332a1SRoger Quadros 	}
270541332a1SRoger Quadros 
271541332a1SRoger Quadros 	if (info->id_gpiod)
272e52817faSRoger Quadros 		enable_irq(info->id_irq);
273541332a1SRoger Quadros 	if (info->vbus_gpiod)
274541332a1SRoger Quadros 		enable_irq(info->vbus_irq);
275541332a1SRoger Quadros 
27604c08008SRoger Quadros 	queue_delayed_work(system_power_efficient_wq,
27704c08008SRoger Quadros 			   &info->wq_detcable, 0);
278e52817faSRoger Quadros 
279e52817faSRoger Quadros 	return ret;
280e52817faSRoger Quadros }
281e52817faSRoger Quadros #endif
282e52817faSRoger Quadros 
283e52817faSRoger Quadros static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops,
284e52817faSRoger Quadros 			 usb_extcon_suspend, usb_extcon_resume);
285e52817faSRoger Quadros 
28634825e51SChanwoo Choi static const struct of_device_id usb_extcon_dt_match[] = {
287e52817faSRoger Quadros 	{ .compatible = "linux,extcon-usb-gpio", },
288e52817faSRoger Quadros 	{ /* sentinel */ }
289e52817faSRoger Quadros };
290e52817faSRoger Quadros MODULE_DEVICE_TABLE(of, usb_extcon_dt_match);
291e52817faSRoger Quadros 
292058b6659SLu Baolu static const struct platform_device_id usb_extcon_platform_ids[] = {
293058b6659SLu Baolu 	{ .name = "extcon-usb-gpio", },
294058b6659SLu Baolu 	{ /* sentinel */ }
295058b6659SLu Baolu };
296058b6659SLu Baolu MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids);
297058b6659SLu Baolu 
298e52817faSRoger Quadros static struct platform_driver usb_extcon_driver = {
299e52817faSRoger Quadros 	.probe		= usb_extcon_probe,
300e52817faSRoger Quadros 	.remove		= usb_extcon_remove,
301e52817faSRoger Quadros 	.driver		= {
302e52817faSRoger Quadros 		.name	= "extcon-usb-gpio",
303e52817faSRoger Quadros 		.pm	= &usb_extcon_pm_ops,
304e52817faSRoger Quadros 		.of_match_table = usb_extcon_dt_match,
305e52817faSRoger Quadros 	},
306058b6659SLu Baolu 	.id_table = usb_extcon_platform_ids,
307e52817faSRoger Quadros };
308e52817faSRoger Quadros 
309e52817faSRoger Quadros module_platform_driver(usb_extcon_driver);
310e52817faSRoger Quadros 
311e52817faSRoger Quadros MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
312e52817faSRoger Quadros MODULE_DESCRIPTION("USB GPIO extcon driver");
313e52817faSRoger Quadros MODULE_LICENSE("GPL v2");
314