1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver
4 *
5 * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com
6 * Author: Roger Quadros <rogerq@ti.com>
7 */
8
9 #include <linux/extcon-provider.h>
10 #include <linux/gpio/consumer.h>
11 #include <linux/init.h>
12 #include <linux/interrupt.h>
13 #include <linux/irq.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/slab.h>
18 #include <linux/workqueue.h>
19 #include <linux/pinctrl/consumer.h>
20
21 #define USB_GPIO_DEBOUNCE_MS 20 /* ms */
22
23 struct usb_extcon_info {
24 struct device *dev;
25 struct extcon_dev *edev;
26
27 struct gpio_desc *id_gpiod;
28 struct gpio_desc *vbus_gpiod;
29 int id_irq;
30 int vbus_irq;
31
32 unsigned long debounce_jiffies;
33 struct delayed_work wq_detcable;
34 };
35
36 static const unsigned int usb_extcon_cable[] = {
37 EXTCON_USB,
38 EXTCON_USB_HOST,
39 EXTCON_NONE,
40 };
41
42 /*
43 * "USB" = VBUS and "USB-HOST" = !ID, so we have:
44 * Both "USB" and "USB-HOST" can't be set as active at the
45 * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive
46 * even if VBUS is on.
47 *
48 * State | ID | VBUS
49 * ----------------------------------------
50 * [1] USB | H | H
51 * [2] none | H | L
52 * [3] USB-HOST | L | H
53 * [4] USB-HOST | L | L
54 *
55 * In case we have only one of these signals:
56 * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1.
57 * - ID only - we want to distinguish between [1] and [4], so VBUS = ID.
58 */
usb_extcon_detect_cable(struct work_struct * work)59 static void usb_extcon_detect_cable(struct work_struct *work)
60 {
61 int id, vbus;
62 struct usb_extcon_info *info = container_of(to_delayed_work(work),
63 struct usb_extcon_info,
64 wq_detcable);
65
66 /* check ID and VBUS and update cable state */
67 id = info->id_gpiod ?
68 gpiod_get_value_cansleep(info->id_gpiod) : 1;
69 vbus = info->vbus_gpiod ?
70 gpiod_get_value_cansleep(info->vbus_gpiod) : id;
71
72 /* at first we clean states which are no longer active */
73 if (id)
74 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, false);
75 if (!vbus)
76 extcon_set_state_sync(info->edev, EXTCON_USB, false);
77
78 if (!id) {
79 extcon_set_state_sync(info->edev, EXTCON_USB_HOST, true);
80 } else {
81 if (vbus)
82 extcon_set_state_sync(info->edev, EXTCON_USB, true);
83 }
84 }
85
usb_irq_handler(int irq,void * dev_id)86 static irqreturn_t usb_irq_handler(int irq, void *dev_id)
87 {
88 struct usb_extcon_info *info = dev_id;
89
90 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
91 info->debounce_jiffies);
92
93 return IRQ_HANDLED;
94 }
95
usb_extcon_probe(struct platform_device * pdev)96 static int usb_extcon_probe(struct platform_device *pdev)
97 {
98 struct device *dev = &pdev->dev;
99 struct device_node *np = dev->of_node;
100 struct usb_extcon_info *info;
101 int ret;
102
103 if (!np)
104 return -EINVAL;
105
106 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
107 if (!info)
108 return -ENOMEM;
109
110 info->dev = dev;
111 info->id_gpiod = devm_gpiod_get_optional(&pdev->dev, "id", GPIOD_IN);
112 info->vbus_gpiod = devm_gpiod_get_optional(&pdev->dev, "vbus",
113 GPIOD_IN);
114
115 if (!info->id_gpiod && !info->vbus_gpiod) {
116 dev_err(dev, "failed to get gpios\n");
117 return -ENODEV;
118 }
119
120 if (IS_ERR(info->id_gpiod))
121 return PTR_ERR(info->id_gpiod);
122
123 if (IS_ERR(info->vbus_gpiod))
124 return PTR_ERR(info->vbus_gpiod);
125
126 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
127 if (IS_ERR(info->edev)) {
128 dev_err(dev, "failed to allocate extcon device\n");
129 return -ENOMEM;
130 }
131
132 ret = devm_extcon_dev_register(dev, info->edev);
133 if (ret < 0) {
134 dev_err(dev, "failed to register extcon device\n");
135 return ret;
136 }
137
138 if (info->id_gpiod)
139 ret = gpiod_set_debounce(info->id_gpiod,
140 USB_GPIO_DEBOUNCE_MS * 1000);
141 if (!ret && info->vbus_gpiod)
142 ret = gpiod_set_debounce(info->vbus_gpiod,
143 USB_GPIO_DEBOUNCE_MS * 1000);
144
145 if (ret < 0)
146 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS);
147
148 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
149
150 if (info->id_gpiod) {
151 info->id_irq = gpiod_to_irq(info->id_gpiod);
152 if (info->id_irq < 0) {
153 dev_err(dev, "failed to get ID IRQ\n");
154 return info->id_irq;
155 }
156
157 ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
158 usb_irq_handler,
159 IRQF_TRIGGER_RISING |
160 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
161 pdev->name, info);
162 if (ret < 0) {
163 dev_err(dev, "failed to request handler for ID IRQ\n");
164 return ret;
165 }
166 }
167
168 if (info->vbus_gpiod) {
169 info->vbus_irq = gpiod_to_irq(info->vbus_gpiod);
170 if (info->vbus_irq < 0) {
171 dev_err(dev, "failed to get VBUS IRQ\n");
172 return info->vbus_irq;
173 }
174
175 ret = devm_request_threaded_irq(dev, info->vbus_irq, NULL,
176 usb_irq_handler,
177 IRQF_TRIGGER_RISING |
178 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
179 pdev->name, info);
180 if (ret < 0) {
181 dev_err(dev, "failed to request handler for VBUS IRQ\n");
182 return ret;
183 }
184 }
185
186 platform_set_drvdata(pdev, info);
187 device_set_wakeup_capable(&pdev->dev, true);
188
189 /* Perform initial detection */
190 usb_extcon_detect_cable(&info->wq_detcable.work);
191
192 return 0;
193 }
194
usb_extcon_remove(struct platform_device * pdev)195 static void usb_extcon_remove(struct platform_device *pdev)
196 {
197 struct usb_extcon_info *info = platform_get_drvdata(pdev);
198
199 cancel_delayed_work_sync(&info->wq_detcable);
200 device_init_wakeup(&pdev->dev, false);
201 }
202
203 #ifdef CONFIG_PM_SLEEP
usb_extcon_suspend(struct device * dev)204 static int usb_extcon_suspend(struct device *dev)
205 {
206 struct usb_extcon_info *info = dev_get_drvdata(dev);
207 int ret = 0;
208
209 if (device_may_wakeup(dev)) {
210 if (info->id_gpiod) {
211 ret = enable_irq_wake(info->id_irq);
212 if (ret)
213 return ret;
214 }
215 if (info->vbus_gpiod) {
216 ret = enable_irq_wake(info->vbus_irq);
217 if (ret) {
218 if (info->id_gpiod)
219 disable_irq_wake(info->id_irq);
220
221 return ret;
222 }
223 }
224 }
225
226 if (!device_may_wakeup(dev))
227 pinctrl_pm_select_sleep_state(dev);
228
229 return ret;
230 }
231
usb_extcon_resume(struct device * dev)232 static int usb_extcon_resume(struct device *dev)
233 {
234 struct usb_extcon_info *info = dev_get_drvdata(dev);
235 int ret = 0;
236
237 if (!device_may_wakeup(dev))
238 pinctrl_pm_select_default_state(dev);
239
240 if (device_may_wakeup(dev)) {
241 if (info->id_gpiod) {
242 ret = disable_irq_wake(info->id_irq);
243 if (ret)
244 return ret;
245 }
246 if (info->vbus_gpiod) {
247 ret = disable_irq_wake(info->vbus_irq);
248 if (ret) {
249 if (info->id_gpiod)
250 enable_irq_wake(info->id_irq);
251
252 return ret;
253 }
254 }
255 }
256
257 queue_delayed_work(system_power_efficient_wq,
258 &info->wq_detcable, 0);
259
260 return ret;
261 }
262 #endif
263
264 static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops,
265 usb_extcon_suspend, usb_extcon_resume);
266
267 static const struct of_device_id usb_extcon_dt_match[] = {
268 { .compatible = "linux,extcon-usb-gpio", },
269 { /* sentinel */ }
270 };
271 MODULE_DEVICE_TABLE(of, usb_extcon_dt_match);
272
273 static const struct platform_device_id usb_extcon_platform_ids[] = {
274 { .name = "extcon-usb-gpio", },
275 { /* sentinel */ }
276 };
277 MODULE_DEVICE_TABLE(platform, usb_extcon_platform_ids);
278
279 static struct platform_driver usb_extcon_driver = {
280 .probe = usb_extcon_probe,
281 .remove = usb_extcon_remove,
282 .driver = {
283 .name = "extcon-usb-gpio",
284 .pm = &usb_extcon_pm_ops,
285 .of_match_table = usb_extcon_dt_match,
286 },
287 .id_table = usb_extcon_platform_ids,
288 };
289
290 module_platform_driver(usb_extcon_driver);
291
292 MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
293 MODULE_DESCRIPTION("USB GPIO extcon driver");
294 MODULE_LICENSE("GPL v2");
295