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