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