aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/extcon
diff options
context:
space:
mode:
authorRoger Quadros <rogerq@ti.com>2015-02-02 05:21:59 -0500
committerChanwoo Choi <cw00.choi@samsung.com>2015-02-23 19:00:53 -0500
commite52817faae359ce95c93c2b6eb88b16d4b430181 (patch)
treeb8eda5c5b308e7ce26eac18638456a40e4f2d648 /drivers/extcon
parent4c883abee08b13d7fac5e672159575bb7a3365a6 (diff)
extcon: usb-gpio: Introduce gpio usb extcon driver
This driver observes the USB ID pin connected over a GPIO and updates the USB cable extcon states accordingly. The existing GPIO extcon driver is not suitable for this purpose as it needs to be taught to understand USB cable states and it can't handle more than one cable per instance. For the USB case we need to handle 2 cable states. 1) USB (attach/detach) 2) USB-HOST (attach/detach) This driver can be easily updated in the future to handle VBUS events in case it happens to be available on GPIO for any platform. Signed-off-by: Roger Quadros <rogerq@ti.com> Reviewed-by: Felipe Balbi <balbi@ti.com> Acked-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Diffstat (limited to 'drivers/extcon')
-rw-r--r--drivers/extcon/Kconfig7
-rw-r--r--drivers/extcon/Makefile1
-rw-r--r--drivers/extcon/extcon-usb-gpio.c237
3 files changed, 245 insertions, 0 deletions
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index 6a1f7de6fa54..e4c01ab046f7 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -93,4 +93,11 @@ config EXTCON_SM5502
93 Silicon Mitus SM5502. The SM5502 is a USB port accessory 93 Silicon Mitus SM5502. The SM5502 is a USB port accessory
94 detector and switch. 94 detector and switch.
95 95
96config EXTCON_USB_GPIO
97 tristate "USB GPIO extcon support"
98 depends on GPIOLIB
99 help
100 Say Y here to enable GPIO based USB cable detection extcon support.
101 Used typically if GPIO is used for USB ID pin detection.
102
96endif # MULTISTATE_SWITCH 103endif # MULTISTATE_SWITCH
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 0370b42e5a27..6a08a98dc069 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -12,3 +12,4 @@ obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
12obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o 12obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o
13obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o 13obj-$(CONFIG_EXTCON_RT8973A) += extcon-rt8973a.o
14obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o 14obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o
15obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o
diff --git a/drivers/extcon/extcon-usb-gpio.c b/drivers/extcon/extcon-usb-gpio.c
new file mode 100644
index 000000000000..3f0bad3ce8aa
--- /dev/null
+++ b/drivers/extcon/extcon-usb-gpio.c
@@ -0,0 +1,237 @@
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/init.h>
19#include <linux/interrupt.h>
20#include <linux/irq.h>
21#include <linux/kernel.h>
22#include <linux/module.h>
23#include <linux/of_gpio.h>
24#include <linux/platform_device.h>
25#include <linux/slab.h>
26#include <linux/workqueue.h>
27
28#define USB_GPIO_DEBOUNCE_MS 20 /* ms */
29
30struct usb_extcon_info {
31 struct device *dev;
32 struct extcon_dev *edev;
33
34 struct gpio_desc *id_gpiod;
35 int id_irq;
36
37 unsigned long debounce_jiffies;
38 struct delayed_work wq_detcable;
39};
40
41/* List of detectable cables */
42enum {
43 EXTCON_CABLE_USB = 0,
44 EXTCON_CABLE_USB_HOST,
45
46 EXTCON_CABLE_END,
47};
48
49static const char *usb_extcon_cable[] = {
50 [EXTCON_CABLE_USB] = "USB",
51 [EXTCON_CABLE_USB_HOST] = "USB-HOST",
52 NULL,
53};
54
55static void usb_extcon_detect_cable(struct work_struct *work)
56{
57 int id;
58 struct usb_extcon_info *info = container_of(to_delayed_work(work),
59 struct usb_extcon_info,
60 wq_detcable);
61
62 /* check ID and update cable state */
63 id = gpiod_get_value_cansleep(info->id_gpiod);
64 if (id) {
65 /*
66 * ID = 1 means USB HOST cable detached.
67 * As we don't have event for USB peripheral cable attached,
68 * we simulate USB peripheral attach here.
69 */
70 extcon_set_cable_state(info->edev,
71 usb_extcon_cable[EXTCON_CABLE_USB_HOST],
72 false);
73 extcon_set_cable_state(info->edev,
74 usb_extcon_cable[EXTCON_CABLE_USB],
75 true);
76 } else {
77 /*
78 * ID = 0 means USB HOST cable attached.
79 * As we don't have event for USB peripheral cable detached,
80 * we simulate USB peripheral detach here.
81 */
82 extcon_set_cable_state(info->edev,
83 usb_extcon_cable[EXTCON_CABLE_USB],
84 false);
85 extcon_set_cable_state(info->edev,
86 usb_extcon_cable[EXTCON_CABLE_USB_HOST],
87 true);
88 }
89}
90
91static irqreturn_t usb_irq_handler(int irq, void *dev_id)
92{
93 struct usb_extcon_info *info = dev_id;
94
95 queue_delayed_work(system_power_efficient_wq, &info->wq_detcable,
96 info->debounce_jiffies);
97
98 return IRQ_HANDLED;
99}
100
101static int usb_extcon_probe(struct platform_device *pdev)
102{
103 struct device *dev = &pdev->dev;
104 struct device_node *np = dev->of_node;
105 struct usb_extcon_info *info;
106 int ret;
107
108 if (!np)
109 return -EINVAL;
110
111 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
112 if (!info)
113 return -ENOMEM;
114
115 info->dev = dev;
116 info->id_gpiod = devm_gpiod_get(&pdev->dev, "id");
117 if (IS_ERR(info->id_gpiod)) {
118 dev_err(dev, "failed to get ID GPIO\n");
119 return PTR_ERR(info->id_gpiod);
120 }
121
122 ret = gpiod_set_debounce(info->id_gpiod,
123 USB_GPIO_DEBOUNCE_MS * 1000);
124 if (ret < 0)
125 info->debounce_jiffies = msecs_to_jiffies(USB_GPIO_DEBOUNCE_MS);
126
127 INIT_DELAYED_WORK(&info->wq_detcable, usb_extcon_detect_cable);
128
129 info->id_irq = gpiod_to_irq(info->id_gpiod);
130 if (info->id_irq < 0) {
131 dev_err(dev, "failed to get ID IRQ\n");
132 return info->id_irq;
133 }
134
135 ret = devm_request_threaded_irq(dev, info->id_irq, NULL,
136 usb_irq_handler,
137 IRQF_TRIGGER_RISING |
138 IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
139 pdev->name, info);
140 if (ret < 0) {
141 dev_err(dev, "failed to request handler for ID IRQ\n");
142 return ret;
143 }
144
145 info->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
146 if (IS_ERR(info->edev)) {
147 dev_err(dev, "failed to allocate extcon device\n");
148 return -ENOMEM;
149 }
150
151 ret = devm_extcon_dev_register(dev, info->edev);
152 if (ret < 0) {
153 dev_err(dev, "failed to register extcon device\n");
154 return ret;
155 }
156
157 platform_set_drvdata(pdev, info);
158 device_init_wakeup(dev, 1);
159
160 /* Perform initial detection */
161 usb_extcon_detect_cable(&info->wq_detcable.work);
162