xref: /linux/drivers/extcon/extcon-usb-gpio.c (revision afeea2758b4f1210361ce2a91d8fa3e7df606ad2)
11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
238b1a3c6SRandy Dunlap /*
3e52817faSRoger Quadros  * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver
4e52817faSRoger Quadros  *
5f6dfb3c9SAlexander A. Klimov  * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com
6e52817faSRoger Quadros  * Author: Roger Quadros <rogerq@ti.com>
7e52817faSRoger Quadros  */
8e52817faSRoger Quadros 
9176aa360SChanwoo Choi #include <linux/extcon-provider.h>
10638f958bSGeert Uytterhoeven #include <linux/gpio/consumer.h>
11e52817faSRoger Quadros #include <linux/init.h>
12e52817faSRoger Quadros #include <linux/interrupt.h>
13e52817faSRoger Quadros #include <linux/irq.h>
14e52817faSRoger Quadros #include <linux/kernel.h>
15e52817faSRoger Quadros #include <linux/module.h>
16e52817faSRoger Quadros #include <linux/platform_device.h>
17e52817faSRoger Quadros #include <linux/slab.h>
18e52817faSRoger Quadros #include <linux/workqueue.h>
19bcb7440eSPeter Chen #include <linux/pinctrl/consumer.h>
2057869c11SLinus Walleij #include <linux/mod_devicetable.h>
21e52817faSRoger Quadros 
22e52817faSRoger Quadros #define USB_GPIO_DEBOUNCE_MS	20	/* ms */
23e52817faSRoger Quadros 
24e52817faSRoger Quadros struct usb_extcon_info {
25e52817faSRoger Quadros 	struct device *dev;
26e52817faSRoger Quadros 	struct extcon_dev *edev;
27e52817faSRoger Quadros 
28e52817faSRoger Quadros 	struct gpio_desc *id_gpiod;
29541332a1SRoger Quadros 	struct gpio_desc *vbus_gpiod;
30e52817faSRoger Quadros 	int id_irq;
31541332a1SRoger Quadros 	int vbus_irq;
32e52817faSRoger Quadros 
33e52817faSRoger Quadros 	unsigned long debounce_jiffies;
34e52817faSRoger Quadros 	struct delayed_work wq_detcable;
35e52817faSRoger Quadros };
36e52817faSRoger Quadros 
3773b6ecdbSChanwoo Choi static const unsigned int usb_extcon_cable[] = {
382a9de9c0SChanwoo Choi 	EXTCON_USB,
392a9de9c0SChanwoo Choi 	EXTCON_USB_HOST,
402a9de9c0SChanwoo Choi 	EXTCON_NONE,
41e52817faSRoger Quadros };
42e52817faSRoger Quadros 
43541332a1SRoger Quadros /*
44541332a1SRoger Quadros  * "USB" = VBUS and "USB-HOST" = !ID, so we have:
45541332a1SRoger Quadros  * Both "USB" and "USB-HOST" can't be set as active at the
46541332a1SRoger Quadros  * same time so if "USB-HOST" is active (i.e. ID is 0)  we keep "USB" inactive
47541332a1SRoger Quadros  * even if VBUS is on.
48541332a1SRoger Quadros  *
49541332a1SRoger Quadros  *  State              |    ID   |   VBUS
50541332a1SRoger Quadros  * ----------------------------------------
51541332a1SRoger Quadros  *  [1] USB            |    H    |    H
52541332a1SRoger Quadros  *  [2] none           |    H    |    L
53541332a1SRoger Quadros  *  [3] USB-HOST       |    L    |    H
54541332a1SRoger Quadros  *  [4] USB-HOST       |    L    |    L
55541332a1SRoger Quadros  *
56541332a1SRoger Quadros  * In case we have only one of these signals:
57541332a1SRoger Quadros  * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1.
58541332a1SRoger Quadros  * - ID only - we want to distinguish between [1] and [4], so VBUS = ID.
59541332a1SRoger Quadros */
60e52817faSRoger Quadros static void usb_extcon_detect_cable(struct work_struct *work)
61e52817faSRoger Quadros {
62541332a1SRoger Quadros 	int id, vbus;
63e52817faSRoger Quadros 	struct usb_extcon_info *info = container_of(to_delayed_work(work),
64e52817faSRoger Quadros 						    struct usb_extcon_info,
65e52817faSRoger Quadros 						    wq_detcable);
66e52817faSRoger Quadros 
67541332a1SRoger Quadros 	/* check ID and VBUS and update cable state */
68541332a1SRoger Quadros 	id = info->id_gpiod ?
69541332a1SRoger Quadros 		gpiod_get_value_cansleep(info->id_gpiod) : 1;
70541332a1SRoger Quadros 	vbus = info->vbus_gpiod ?
71541332a1SRoger Quadros 		gpiod_get_value_cansleep(info->vbus_gpiod) : id;
72541332a1SRoger Quadros 
73541332a1SRoger Quadros 	/* at first we clean states which are no longer active */
74541332a1SRoger Quadros 	if (id)
758670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
76541332a1SRoger Quadros 	if (!vbus)
778670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB, false);
78541332a1SRoger Quadros 
79541332a1SRoger Quadros 	if (!id) {
808670b459SChanwoo Choi 		extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
81541332a1SRoger Quadros 	} else {
82541332a1SRoger Quadros 		if (vbus)
83541332a1SRoger Quadros 			extcon_set_state_sync(info->edev, EXTCON_USB, true);
84e52817faSRoger Quadros 	}
85e52817faSRoger Quadros }
86e52817faSRoger Quadros 
87e52817faSRoger Quadros static irqreturn_t usb_irq_handler(int irq, void *dev_id)
88e52817faSRoger Quadros {
89e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_id;
90e52817faSRoger Quadros 
91e52817faSRoger Quadros 	queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
92e52817faSRoger Quadros 			   info->debounce_jiffies);
93e52817faSRoger Quadros 
94e52817faSRoger Quadros 	return IRQ_HANDLED;
95e52817faSRoger Quadros }
96e52817faSRoger Quadros 
97e52817faSRoger Quadros static int usb_extcon_probe(struct platform_device *pdev)
98e52817faSRoger Quadros {
99e52817faSRoger Quadros 	struct device *dev = &pdev->dev;
100e52817faSRoger Quadros 	struct device_node *np = dev->of_node;
101e52817faSRoger Quadros 	struct usb_extcon_info *info;
102e52817faSRoger Quadros 	int ret;
103e52817faSRoger Quadros 
104366380cdSAndy Shevchenko 	if (!np)
105e52817faSRoger Quadros 		return -EINVAL;
106e52817faSRoger Quadros 
107e52817faSRoger Quadros 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
108e52817faSRoger Quadros 	if (!info)
109e52817faSRoger Quadros 		return -ENOMEM;
110e52817faSRoger Quadros 
111e52817faSRoger Quadros 	info->dev = dev;
112541332a1SRoger Quadros 	info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN);
113541332a1SRoger Quadros 	info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
114541332a1SRoger Quadros 						   GPIOD_IN);
115541332a1SRoger Quadros 
116541332a1SRoger Quadros 	if (!info->id_gpiod && !info->vbus_gpiod) {
117541332a1SRoger Quadros 		dev_err(dev, "failed to get gpios\n");
118541332a1SRoger Quadros 		return -ENODEV;
119e52817faSRoger Quadros 	}
120e52817faSRoger Quadros 
121541332a1SRoger Quadros 	if (IS_ERR(info->id_gpiod))
122541332a1SRoger Quadros 		return PTR_ERR(info->id_gpiod);
123541332a1SRoger Quadros 
124541332a1SRoger Quadros 	if (IS_ERR(info->vbus_gpiod))
125541332a1SRoger Quadros 		return PTR_ERR(info->vbus_gpiod);
126541332a1SRoger Quadros 
127bc1aabadSRobert Baldyga 	info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
128bc1aabadSRobert Baldyga 	if (IS_ERR(info->edev)) {
129bc1aabadSRobert Baldyga 		dev_err(dev, "failed to allocate extcon device\n");
130bc1aabadSRobert Baldyga 		return -ENOMEM;
131bc1aabadSRobert Baldyga 	}
132bc1aabadSRobert Baldyga 
133bc1aabadSRobert Baldyga 	ret = devm_extcon_dev_register(dev, info->edev);
134bc1aabadSRobert Baldyga 	if (ret < 0) {
135bc1aabadSRobert Baldyga 		dev_err(dev, "failed to register extcon device\n");
136bc1aabadSRobert Baldyga 		return ret;
137bc1aabadSRobert Baldyga 	}
138bc1aabadSRobert Baldyga 
139541332a1SRoger Quadros 	if (info->id_gpiod)
140e52817faSRoger Quadros 		ret = gpiod_set_debounce(info->id_gpiod,
141e52817faSRoger Quadros 					 USB_GPIO_DEBOUNCE_MS * 1000);
142541332a1SRoger Quadros 	if (!ret && info->vbus_gpiod)
143541332a1SRoger Quadros 		ret = gpiod_set_debounce(info->vbus_gpiod,
144541332a1SRoger Quadros 					 USB_GPIO_DEBOUNCE_MS * 1000);
145541332a1SRoger Quadros 
146e52817faSRoger Quadros 	if (ret < 0)
147e52817faSRoger Quadros 		info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS);
148e52817faSRoger Quadros 
149e52817faSRoger Quadros 	INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
150e52817faSRoger Quadros 
151541332a1SRoger Quadros 	if (info->id_gpiod) {
152e52817faSRoger Quadros 		info->id_irq = gpiod_to_irq(info->id_gpiod);
153e52817faSRoger Quadros 		if (info->id_irq < 0) {
154e52817faSRoger Quadros 			dev_err(dev, "failed to get ID IRQ\n");
155e52817faSRoger Quadros 			return info->id_irq;
156e52817faSRoger Quadros 		}
157e52817faSRoger Quadros 
158e52817faSRoger Quadros 		ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
159e52817faSRoger Quadros 						usb_irq_handler,
160e52817faSRoger Quadros 						IRQF_TRIGGER_RISING |
161e52817faSRoger Quadros 						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
162e52817faSRoger Quadros 						pdev->name, info);
163e52817faSRoger Quadros 		if (ret < 0) {
164e52817faSRoger Quadros 			dev_err(dev, "failed to request handler for ID IRQ\n");
165e52817faSRoger Quadros 			return ret;
166e52817faSRoger Quadros 		}
167541332a1SRoger Quadros 	}
168541332a1SRoger Quadros 
169541332a1SRoger Quadros 	if (info->vbus_gpiod) {
170541332a1SRoger Quadros 		info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
171541332a1SRoger Quadros 		if (info->vbus_irq < 0) {
172541332a1SRoger Quadros 			dev_err(dev, "failed to get VBUS IRQ\n");
173541332a1SRoger Quadros 			return info->vbus_irq;
174541332a1SRoger Quadros 		}
175541332a1SRoger Quadros 
176541332a1SRoger Quadros 		ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
177541332a1SRoger Quadros 						usb_irq_handler,
178541332a1SRoger Quadros 						IRQF_TRIGGER_RISING |
179541332a1SRoger Quadros 						IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
180541332a1SRoger Quadros 						pdev->name, info);
181541332a1SRoger Quadros 		if (ret < 0) {
182541332a1SRoger Quadros 			dev_err(dev, "failed to request handler for VBUS IRQ\n");
183541332a1SRoger Quadros 			return ret;
184541332a1SRoger Quadros 		}
185541332a1SRoger Quadros 	}
186e52817faSRoger Quadros 
187e52817faSRoger Quadros 	platform_set_drvdata(pdev, info);
18898fd0792SPeter Chen 	device_set_wakeup_capable(&pdev->dev, true);
189e52817faSRoger Quadros 
190e52817faSRoger Quadros 	/* Perform initial detection */
191e52817faSRoger Quadros 	usb_extcon_detect_cable(&info->wq_detcable.work);
192e52817faSRoger Quadros 
193e52817faSRoger Quadros 	return 0;
194e52817faSRoger Quadros }
195e52817faSRoger Quadros 
196*5be3dfe6SUwe Kleine-König static void usb_extcon_remove(struct platform_device *pdev)
197e52817faSRoger Quadros {
198e52817faSRoger Quadros 	struct usb_extcon_info *info = platform_get_drvdata(pdev);
199e52817faSRoger Quadros 
200e52817faSRoger Quadros 	cancel_delayed_work_sync(&info->wq_detcable);
20112bd0f32SGrygorii Strashko 	device_init_wakeup(&pdev->dev, false);
202e52817faSRoger Quadros }
203e52817faSRoger Quadros 
204e52817faSRoger Quadros #ifdef CONFIG_PM_SLEEP
205e52817faSRoger Quadros static int usb_extcon_suspend(struct device *dev)
206e52817faSRoger Quadros {
207e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_get_drvdata(dev);
208e52817faSRoger Quadros 	int ret = 0;
209e52817faSRoger Quadros 
210541332a1SRoger Quadros 	if (device_may_wakeup(dev)) {
211541332a1SRoger Quadros 		if (info->id_gpiod) {
212541332a1SRoger Quadros 			ret = enable_irq_wake(info->id_irq);
213541332a1SRoger Quadros 			if (ret)
214541332a1SRoger Quadros 				return ret;
215541332a1SRoger Quadros 		}
216541332a1SRoger Quadros 		if (info->vbus_gpiod) {
217541332a1SRoger Quadros 			ret = enable_irq_wake(info->vbus_irq);
218541332a1SRoger Quadros 			if (ret) {
219541332a1SRoger Quadros 				if (info->id_gpiod)
220541332a1SRoger Quadros 					disable_irq_wake(info->id_irq);
221541332a1SRoger Quadros 
222541332a1SRoger Quadros 				return ret;
223541332a1SRoger Quadros 			}
224541332a1SRoger Quadros 		}
225541332a1SRoger Quadros 	}
226541332a1SRoger Quadros 
227bcb7440eSPeter Chen 	if (!device_may_wakeup(dev))
228bcb7440eSPeter Chen 		pinctrl_pm_select_sleep_state(dev);
229bcb7440eSPeter Chen 
230e52817faSRoger Quadros 	return ret;
231e52817faSRoger Quadros }
232e52817faSRoger Quadros 
233e52817faSRoger Quadros static int usb_extcon_resume(struct device *dev)
234e52817faSRoger Quadros {
235e52817faSRoger Quadros 	struct usb_extcon_info *info = dev_get_drvdata(dev);
236e52817faSRoger Quadros 	int ret = 0;
237e52817faSRoger Quadros 
238bcb7440eSPeter Chen 	if (!device_may_wakeup(dev))
239bcb7440eSPeter Chen 		pinctrl_pm_select_default_state(dev);
240bcb7440eSPeter Chen 
241541332a1SRoger Quadros 	if (device_may_wakeup(dev)) {
242541332a1SRoger Quadros 		if (info->id_gpiod) {
243541332a1SRoger Quadros 			ret = disable_irq_wake(info->id_irq);
244541332a1SRoger Quadros 			if (ret)
245541332a1SRoger Quadros 				return ret;
246541332a1SRoger Quadros 		}
247541332a1SRoger Quadros 		if (info->vbus_gpiod) {
248541332a1SRoger Quadros 			ret = disable_irq_wake(info->vbus_irq);
249541332a1SRoger Quadros 			if (ret) {
250541332a1SRoger Quadros 				if (info->id_gpiod)
251541332a1SRoger Quadros 					enable_irq_wake(info->id_irq);
252541332a1SRoger Quadros 
253541332a1SRoger Quadros 				return ret;
254541332a1SRoger Quadros 			}
255541332a1SRoger Quadros 		}
256541332a1SRoger Quadros 	}
257541332a1SRoger Quadros 
25804c08008SRoger Quadros 	queue_delayed_work(system_power_efficient_wq,
25904c08008SRoger Quadros 			   &info->wq_detcable, 0);
260e52817faSRoger Quadros 
261e52817faSRoger Quadros 	return ret;
262e52817faSRoger Quadros }
263e52817faSRoger Quadros #endif
264e52817faSRoger Quadros 
265e52817faSRoger Quadros static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops,
266e52817faSRoger Quadros 			 usb_extcon_suspend, usb_extcon_resume);
267e52817faSRoger Quadros 
26834825e51SChanwoo Choi static const struct of_device_id usb_extcon_dt_match[] = {
269e52817faSRoger Quadros 	{ .compatible = "linux,extcon-usb-gpio", },
270e52817faSRoger Quadros 	{ /* sentinel */ }
271e52817faSRoger Quadros };
272e52817faSRoger Quadros MODULE_DEVICE_TABLE(of, usb_extcon_dt_match);
273e52817faSRoger Quadros 
274058b6659SLu Baolu static const struct platform_device_id usb_extcon_platform_ids[] = {
275058b6659SLu Baolu 	{ .name = "extcon-usb-gpio", },
276058b6659SLu Baolu 	{ /* sentinel */ }
277058b6659SLu Baolu };
278058b6659SLu Baolu MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids);
279058b6659SLu Baolu 
280e52817faSRoger Quadros static struct platform_driver usb_extcon_driver = {
281e52817faSRoger Quadros 	.probe		= usb_extcon_probe,
282*5be3dfe6SUwe Kleine-König 	.remove_new	= usb_extcon_remove,
283e52817faSRoger Quadros 	.driver		= {
284e52817faSRoger Quadros 		.name	= "extcon-usb-gpio",
285e52817faSRoger Quadros 		.pm	= &usb_extcon_pm_ops,
286e52817faSRoger Quadros 		.of_match_table = usb_extcon_dt_match,
287e52817faSRoger Quadros 	},
288058b6659SLu Baolu 	.id_table = usb_extcon_platform_ids,
289e52817faSRoger Quadros };
290e52817faSRoger Quadros 
291e52817faSRoger Quadros module_platform_driver(usb_extcon_driver);
292e52817faSRoger Quadros 
293e52817faSRoger Quadros MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
294e52817faSRoger Quadros MODULE_DESCRIPTION("USB GPIO extcon driver");
295e52817faSRoger Quadros MODULE_LICENSE("GPL v2");
296