1 // SPDX-License-Identifier: GPL-2.0-only
3 * drivers/extcon/extcon-usb-gpio.c - USB GPIO extcon driver
5 * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com
6 * Author: Roger Quadros <rogerq@ti.com>
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 #include <linux/mod_devicetable.h>
22 #define USB_GPIO_DEBOUNCE_MS 20 /* ms */
24 struct usb_extcon_info
{
26 struct extcon_dev
*edev
;
28 struct gpio_desc
*id_gpiod
;
29 struct gpio_desc
*vbus_gpiod
;
33 unsigned long debounce_jiffies
;
34 struct delayed_work wq_detcable
;
37 static const unsigned int usb_extcon_cable
[] = {
44 * "USB" = VBUS and "USB-HOST" = !ID, so we have:
45 * Both "USB" and "USB-HOST" can't be set as active at the
46 * same time so if "USB-HOST" is active (i.e. ID is 0) we keep "USB" inactive
50 * ----------------------------------------
53 * [3] USB-HOST | L | H
54 * [4] USB-HOST | L | L
56 * In case we have only one of these signals:
57 * - VBUS only - we want to distinguish between [1] and [2], so ID is always 1.
58 * - ID only - we want to distinguish between [1] and [4], so VBUS = ID.
60 static void usb_extcon_detect_cable(struct work_struct
*work
)
63 struct usb_extcon_info
*info
= container_of(to_delayed_work(work
),
64 struct usb_extcon_info
,
67 /* check ID and VBUS and update cable state */
69 gpiod_get_value_cansleep(info
->id_gpiod
) : 1;
70 vbus
= info
->vbus_gpiod
?
71 gpiod_get_value_cansleep(info
->vbus_gpiod
) : id
;
73 /* at first we clean states which are no longer active */
75 extcon_set_state_sync(info
->edev
, EXTCON_USB_HOST
, false);
77 extcon_set_state_sync(info
->edev
, EXTCON_USB
, false);
80 extcon_set_state_sync(info
->edev
, EXTCON_USB_HOST
, true);
83 extcon_set_state_sync(info
->edev
, EXTCON_USB
, true);
87 static irqreturn_t
usb_irq_handler(int irq
, void *dev_id
)
89 struct usb_extcon_info
*info
= dev_id
;
91 queue_delayed_work(system_power_efficient_wq
, &info
->wq_detcable
,
92 info
->debounce_jiffies
);
97 static int usb_extcon_probe(struct platform_device
*pdev
)
99 struct device
*dev
= &pdev
->dev
;
100 struct device_node
*np
= dev
->of_node
;
101 struct usb_extcon_info
*info
;
107 info
= devm_kzalloc(&pdev
->dev
, sizeof(*info
), GFP_KERNEL
);
112 info
->id_gpiod
= devm_gpiod_get_optional(&pdev
->dev
, "id", GPIOD_IN
);
113 info
->vbus_gpiod
= devm_gpiod_get_optional(&pdev
->dev
, "vbus",
116 if (!info
->id_gpiod
&& !info
->vbus_gpiod
) {
117 dev_err(dev
, "failed to get gpios\n");
121 if (IS_ERR(info
->id_gpiod
))
122 return PTR_ERR(info
->id_gpiod
);
124 if (IS_ERR(info
->vbus_gpiod
))
125 return PTR_ERR(info
->vbus_gpiod
);
127 info
->edev
= devm_extcon_dev_allocate(dev
, usb_extcon_cable
);
128 if (IS_ERR(info
->edev
)) {
129 dev_err(dev
, "failed to allocate extcon device\n");
133 ret
= devm_extcon_dev_register(dev
, info
->edev
);
135 dev_err(dev
, "failed to register extcon device\n");
140 ret
= gpiod_set_debounce(info
->id_gpiod
,
141 USB_GPIO_DEBOUNCE_MS
* 1000);
142 if (!ret
&& info
->vbus_gpiod
)
143 ret
= gpiod_set_debounce(info
->vbus_gpiod
,
144 USB_GPIO_DEBOUNCE_MS
* 1000);
147 info
->debounce_jiffies
= msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS
);
149 INIT_DELAYED_WORK(&info
->wq_detcable
, usb_extcon_detect_cable
);
151 if (info
->id_gpiod
) {
152 info
->id_irq
= gpiod_to_irq(info
->id_gpiod
);
153 if (info
->id_irq
< 0) {
154 dev_err(dev
, "failed to get ID IRQ\n");
158 ret
= devm_request_threaded_irq(dev
, info
->id_irq
, NULL
,
160 IRQF_TRIGGER_RISING
|
161 IRQF_TRIGGER_FALLING
| IRQF_ONESHOT
,
164 dev_err(dev
, "failed to request handler for ID IRQ\n");
169 if (info
->vbus_gpiod
) {
170 info
->vbus_irq
= gpiod_to_irq(info
->vbus_gpiod
);
171 if (info
->vbus_irq
< 0) {
172 dev_err(dev
, "failed to get VBUS IRQ\n");
173 return info
->vbus_irq
;
176 ret
= devm_request_threaded_irq(dev
, info
->vbus_irq
, NULL
,
178 IRQF_TRIGGER_RISING
|
179 IRQF_TRIGGER_FALLING
| IRQF_ONESHOT
,
182 dev_err(dev
, "failed to request handler for VBUS IRQ\n");
187 platform_set_drvdata(pdev
, info
);
188 device_set_wakeup_capable(&pdev
->dev
, true);
190 /* Perform initial detection */
191 usb_extcon_detect_cable(&info
->wq_detcable
.work
);
196 static void usb_extcon_remove(struct platform_device
*pdev
)
198 struct usb_extcon_info
*info
= platform_get_drvdata(pdev
);
200 cancel_delayed_work_sync(&info
->wq_detcable
);
201 device_init_wakeup(&pdev
->dev
, false);
204 #ifdef CONFIG_PM_SLEEP
205 static int usb_extcon_suspend(struct device
*dev
)
207 struct usb_extcon_info
*info
= dev_get_drvdata(dev
);
210 if (device_may_wakeup(dev
)) {
211 if (info
->id_gpiod
) {
212 ret
= enable_irq_wake(info
->id_irq
);
216 if (info
->vbus_gpiod
) {
217 ret
= enable_irq_wake(info
->vbus_irq
);
220 disable_irq_wake(info
->id_irq
);
227 if (!device_may_wakeup(dev
))
228 pinctrl_pm_select_sleep_state(dev
);
233 static int usb_extcon_resume(struct device
*dev
)
235 struct usb_extcon_info
*info
= dev_get_drvdata(dev
);
238 if (!device_may_wakeup(dev
))
239 pinctrl_pm_select_default_state(dev
);
241 if (device_may_wakeup(dev
)) {
242 if (info
->id_gpiod
) {
243 ret
= disable_irq_wake(info
->id_irq
);
247 if (info
->vbus_gpiod
) {
248 ret
= disable_irq_wake(info
->vbus_irq
);
251 enable_irq_wake(info
->id_irq
);
258 queue_delayed_work(system_power_efficient_wq
,
259 &info
->wq_detcable
, 0);
265 static SIMPLE_DEV_PM_OPS(usb_extcon_pm_ops
,
266 usb_extcon_suspend
, usb_extcon_resume
);
268 static const struct of_device_id usb_extcon_dt_match
[] = {
269 { .compatible
= "linux,extcon-usb-gpio", },
272 MODULE_DEVICE_TABLE(of
, usb_extcon_dt_match
);
274 static const struct platform_device_id usb_extcon_platform_ids
[] = {
275 { .name
= "extcon-usb-gpio", },
278 MODULE_DEVICE_TABLE(platform
, usb_extcon_platform_ids
);
280 static struct platform_driver usb_extcon_driver
= {
281 .probe
= usb_extcon_probe
,
282 .remove_new
= usb_extcon_remove
,
284 .name
= "extcon-usb-gpio",
285 .pm
= &usb_extcon_pm_ops
,
286 .of_match_table
= usb_extcon_dt_match
,
288 .id_table
= usb_extcon_platform_ids
,
291 module_platform_driver(usb_extcon_driver
);
293 MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
294 MODULE_DESCRIPTION("USB GPIO extcon driver");
295 MODULE_LICENSE("GPL v2");