aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Zabel <philipp.zabel@gmail.com>2008-11-24 15:00:01 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2009-01-07 13:00:02 -0500
commit6084f1bf0c51a99cbba612ee90a4607cffb8b042 (patch)
tree88e902277a0741105418ae856748a3ececdc6078
parentb8da8677d4f88db066c1cfe34529d970a060de46 (diff)
USB: otg: gpio_vbus transceiver stub
gpio_vbus provides simple GPIO VBUS sensing for peripheral controllers with an internal transceiver. Optionally, a second GPIO can be used to control D+ pullup. It also interfaces with the regulator framework to limit charging currents when powered via USB. gpio_vbus requests the regulator supplying "vbus_draw" and can enable/disable it or limit its current depending on USB state. [dbrownell@users.sourceforge.net: use drivers/otg, cleanups ] Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/otg/Kconfig13
-rw-r--r--drivers/usb/otg/Makefile5
-rw-r--r--drivers/usb/otg/gpio_vbus.c335
-rw-r--r--include/linux/usb/gpio_vbus.h30
4 files changed, 383 insertions, 0 deletions
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
index 860342902bbb..afe91bfea7f2 100644
--- a/drivers/usb/otg/Kconfig
+++ b/drivers/usb/otg/Kconfig
@@ -14,6 +14,19 @@ config USB_OTG_UTILS
14 Select this to make sure the build includes objects from 14 Select this to make sure the build includes objects from
15 the OTG infrastructure directory. 15 the OTG infrastructure directory.
16 16
17#
18# USB Transceiver Drivers
19#
20config USB_GPIO_VBUS
21 tristate "GPIO based peripheral-only VBUS sensing 'transceiver'"
22 depends on GENERIC_GPIO
23 select USB_OTG_UTILS
24 help
25 Provides simple GPIO VBUS sensing for controllers with an
26 internal transceiver via the otg_transceiver interface, and
27 optionally control of a D+ pullup GPIO as well as a VBUS
28 current limit regulator.
29
17config ISP1301_OMAP 30config ISP1301_OMAP
18 tristate "Philips ISP1301 with OMAP OTG" 31 tristate "Philips ISP1301 with OMAP OTG"
19 depends on I2C && ARCH_OMAP_OTG 32 depends on I2C && ARCH_OMAP_OTG
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
index 483816685d00..6c58b36ca7cf 100644
--- a/drivers/usb/otg/Makefile
+++ b/drivers/usb/otg/Makefile
@@ -1,3 +1,8 @@
1#
2# OTG infrastructure and transceiver drivers
3#
4
5obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o
1obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o 6obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
2 7
3ifeq ($(CONFIG_USB_DEBUG),y) 8ifeq ($(CONFIG_USB_DEBUG),y)
diff --git a/drivers/usb/otg/gpio_vbus.c b/drivers/usb/otg/gpio_vbus.c
new file mode 100644
index 000000000000..63a6036f04be
--- /dev/null
+++ b/drivers/usb/otg/gpio_vbus.c
@@ -0,0 +1,335 @@
1/*
2 * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices
3 *
4 * Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#include <linux/kernel.h>
12#include <linux/platform_device.h>
13#include <linux/gpio.h>
14#include <linux/interrupt.h>
15#include <linux/usb.h>
16
17#include <linux/regulator/consumer.h>
18
19#include <linux/usb/gadget.h>
20#include <linux/usb/gpio_vbus.h>
21#include <linux/usb/otg.h>
22
23
24/*
25 * A simple GPIO VBUS sensing driver for B peripheral only devices
26 * with internal transceivers. It can control a D+ pullup GPIO and
27 * a regulator to limit the current drawn from VBUS.
28 *
29 * Needs to be loaded before the UDC driver that will use it.
30 */
31struct gpio_vbus_data {
32 struct otg_transceiver otg;
33 struct device *dev;
34 struct regulator *vbus_draw;
35 int vbus_draw_enabled;
36 unsigned mA;
37};
38
39
40/*
41 * This driver relies on "both edges" triggering. VBUS has 100 msec to
42 * stabilize, so the peripheral controller driver may need to cope with
43 * some bouncing due to current surges (e.g. charging local capacitance)
44 * and contact chatter.
45 *
46 * REVISIT in desperate straits, toggling between rising and falling
47 * edges might be workable.
48 */
49#define VBUS_IRQ_FLAGS \
50 ( IRQF_SAMPLE_RANDOM | IRQF_SHARED \
51 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING )
52
53
54/* interface to regulator framework */
55static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA)
56{
57 struct regulator *vbus_draw = gpio_vbus->vbus_draw;
58 int enabled;
59
60 if (!vbus_draw)
61 return;
62
63 enabled = gpio_vbus->vbus_draw_enabled;
64 if (mA) {
65 regulator_set_current_limit(vbus_draw, 0, 1000 * mA);
66 if (!enabled) {
67 regulator_enable(vbus_draw);
68 gpio_vbus->vbus_draw_enabled = 1;
69 }
70 } else {
71 if (enabled) {
72 regulator_disable(vbus_draw);
73 gpio_vbus->vbus_draw_enabled = 0;
74 }
75 }
76 gpio_vbus->mA = mA;
77}
78
79/* VBUS change IRQ handler */
80static irqreturn_t gpio_vbus_irq(int irq, void *data)
81{
82 struct platform_device *pdev = data;
83 struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data;
84 struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
85 int gpio, vbus;
86
87 vbus = gpio_get_value(pdata->gpio_vbus);
88 if (pdata->gpio_vbus_inverted)
89 vbus = !vbus;
90
91 dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n",
92 vbus ? "supplied" : "inactive",
93 gpio_vbus->otg.gadget ? gpio_vbus->otg.gadget->name : "none");
94
95 if (!gpio_vbus->otg.gadget)
96 return IRQ_HANDLED;
97
98 /* Peripheral controllers which manage the pullup themselves won't have
99 * gpio_pullup configured here. If it's configured here, we'll do what
100 * isp1301_omap::b_peripheral() does and enable the pullup here... although
101 * that may complicate usb_gadget_{,dis}connect() support.
102 */
103 gpio = pdata->gpio_pullup;
104 if (vbus) {
105 gpio_vbus->otg.state = OTG_STATE_B_PERIPHERAL;
106 usb_gadget_vbus_connect(gpio_vbus->otg.gadget);
107
108 /* drawing a "unit load" is *always* OK, except for OTG */
109 set_vbus_draw(gpio_vbus, 100);
110
111 /* optionally enable D+ pullup */
112 if (gpio_is_valid(gpio))
113 gpio_set_value(gpio, !pdata->gpio_pullup_inverted);
114 } else {
115 /* optionally disable D+ pullup */
116 if (gpio_is_valid(gpio))
117 gpio_set_value(gpio, pdata->gpio_pullup_inverted);
118
119 set_vbus_draw(gpio_vbus, 0);
120
121 usb_gadget_vbus_disconnect(gpio_vbus->otg.gadget);
122 gpio_vbus->otg.state = OTG_STATE_B_IDLE;
123 }
124
125 return IRQ_HANDLED;
126}
127
128/* OTG transceiver interface */
129
130/* bind/unbind the peripheral controller */
131static int gpio_vbus_set_peripheral(struct otg_transceiver *otg,
132 struct usb_gadget *gadget)
133{
134 struct gpio_vbus_data *gpio_vbus;
135 struct gpio_vbus_mach_info *pdata;
136 struct platform_device *pdev;
137 int gpio, irq;
138
139 gpio_vbus = container_of(otg, struct gpio_vbus_data, otg);
140 pdev = to_platform_device(gpio_vbus->dev);
141 pdata = gpio_vbus->dev->platform_data;
142 irq = gpio_to_irq(pdata->gpio_vbus);
143 gpio = pdata->gpio_pullup;
144
145 if (!gadget) {
146 dev_dbg(&pdev->dev, "unregistering gadget '%s'\n",
147 otg->gadget->name);
148
149 /* optionally disable D+ pullup */
150 if (gpio_is_valid(gpio))
151 gpio_set_value(gpio, pdata->gpio_pullup_inverted);
152
153 set_vbus_draw(gpio_vbus, 0);
154
155 usb_gadget_vbus_disconnect(otg->gadget);
156 otg->state = OTG_STATE_UNDEFINED;
157
158 otg->gadget = NULL;
159 return 0;
160 }
161
162 otg->gadget = gadget;
163 dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name);
164
165 /* initialize connection state */
166 gpio_vbus_irq(irq, pdev);
167 return 0;
168}
169
170/* effective for B devices, ignored for A-peripheral */
171static int gpio_vbus_set_power(struct otg_transceiver *otg, unsigned mA)
172{
173 struct gpio_vbus_data *gpio_vbus;
174
175 gpio_vbus = container_of(otg, struct gpio_vbus_data, otg);
176
177 if (otg->state == OTG_STATE_B_PERIPHERAL)
178 set_vbus_draw(gpio_vbus, mA);
179 return 0;
180}
181
182/* for non-OTG B devices: set/clear transceiver suspend mode */
183static int gpio_vbus_set_suspend(struct otg_transceiver *otg, int suspend)
184{
185 struct gpio_vbus_data *gpio_vbus;
186
187 gpio_vbus = container_of(otg, struct gpio_vbus_data, otg);
188
189 /* draw max 0 mA from vbus in suspend mode; or the previously
190 * recorded amount of current if not suspended
191 *
192 * NOTE: high powered configs (mA > 100) may draw up to 2.5 mA
193 * if they're wake-enabled ... we don't handle that yet.
194 */
195 return gpio_vbus_set_power(otg, suspend ? 0 : gpio_vbus->mA);
196}
197
198/* platform driver interface */
199
200static int __init gpio_vbus_probe(struct platform_device *pdev)
201{
202 struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data;
203 struct gpio_vbus_data *gpio_vbus;
204 struct resource *res;
205 int err, gpio, irq;
206
207 if (!pdata || !gpio_is_valid(pdata->gpio_vbus))
208 return -EINVAL;
209 gpio = pdata->gpio_vbus;
210
211 gpio_vbus = kzalloc(sizeof(struct gpio_vbus_data), GFP_KERNEL);
212 if (!gpio_vbus)
213 return -ENOMEM;
214
215 platform_set_drvdata(pdev, gpio_vbus);
216 gpio_vbus->dev = &pdev->dev;
217 gpio_vbus->otg.label = "gpio-vbus";
218 gpio_vbus->otg.state = OTG_STATE_UNDEFINED;
219 gpio_vbus->otg.set_peripheral = gpio_vbus_set_peripheral;
220 gpio_vbus->otg.set_power = gpio_vbus_set_power;
221 gpio_vbus->otg.set_suspend = gpio_vbus_set_suspend;
222
223 err = gpio_request(gpio, "vbus_detect");
224 if (err) {
225 dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n",
226 gpio, err);
227 goto err_gpio;
228 }
229 gpio_direction_input(gpio);
230
231 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
232 if (res) {
233 irq = res->start;
234 res->flags &= IRQF_TRIGGER_MASK;
235 res->flags |= IRQF_SAMPLE_RANDOM | IRQF_SHARED;
236 } else
237 irq = gpio_to_irq(gpio);
238
239 /* if data line pullup is in use, initialize it to "not pulling up" */
240 gpio = pdata->gpio_pullup;
241 if (gpio_is_valid(gpio)) {
242 err = gpio_request(gpio, "udc_pullup");
243 if (err) {
244 dev_err(&pdev->dev,
245 "can't request pullup gpio %d, err: %d\n",
246 gpio, err);
247 gpio_free(pdata->gpio_vbus);
248 goto err_gpio;
249 }
250 gpio_direction_output(gpio, pdata->gpio_pullup_inverted);
251 }
252
253 err = request_irq(irq, gpio_vbus_irq, VBUS_IRQ_FLAGS,
254 "vbus_detect", pdev);
255 if (err) {
256 dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
257 irq, err);
258 goto err_irq;
259 }
260
261 /* only active when a gadget is registered */
262 err = otg_set_transceiver(&gpio_vbus->otg);
263 if (err) {
264 dev_err(&pdev->dev, "can't register transceiver, err: %d\n",
265 err);
266 goto err_otg;
267 }
268
269 gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw");
270 if (IS_ERR(gpio_vbus->vbus_draw)) {
271 dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n",
272 PTR_ERR(gpio_vbus->vbus_draw));
273 gpio_vbus->vbus_draw = NULL;
274 }
275
276 return 0;
277err_otg:
278 free_irq(irq, &pdev->dev);
279err_irq:
280 if (gpio_is_valid(pdata->gpio_pullup))
281 gpio_free(pdata->gpio_pullup);
282 gpio_free(pdata->gpio_vbus);
283err_gpio:
284 platform_set_drvdata(pdev, NULL);
285 kfree(gpio_vbus);
286 return err;
287}
288
289static int __exit gpio_vbus_remove(struct platform_device *pdev)
290{
291 struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev);
292 struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data;
293 int gpio = pdata->gpio_vbus;
294
295 regulator_put(gpio_vbus->vbus_draw);
296
297 otg_set_transceiver(NULL);
298
299 free_irq(gpio_to_irq(gpio), &pdev->dev);
300 if (gpio_is_valid(pdata->gpio_pullup))
301 gpio_free(pdata->gpio_pullup);
302 gpio_free(gpio);
303 platform_set_drvdata(pdev, NULL);
304 kfree(gpio_vbus);
305
306 return 0;
307}
308
309/* NOTE: the gpio-vbus device may *NOT* be hotplugged */
310
311MODULE_ALIAS("platform:gpio-vbus");
312
313static struct platform_driver gpio_vbus_driver = {
314 .driver = {
315 .name = "gpio-vbus",
316 .owner = THIS_MODULE,
317 },
318 .remove = __exit_p(gpio_vbus_remove),
319};
320
321static int __init gpio_vbus_init(void)
322{
323 return platform_driver_probe(&gpio_vbus_driver, gpio_vbus_probe);
324}
325module_init(gpio_vbus_init);
326
327static void __exit gpio_vbus_exit(void)
328{
329 platform_driver_unregister(&gpio_vbus_driver);
330}
331module_exit(gpio_vbus_exit);
332
333MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver");
334MODULE_AUTHOR("Philipp Zabel");
335MODULE_LICENSE("GPL");
diff --git a/include/linux/usb/gpio_vbus.h b/include/linux/usb/gpio_vbus.h
new file mode 100644
index 000000000000..d9f03ccc2d60
--- /dev/null
+++ b/include/linux/usb/gpio_vbus.h
@@ -0,0 +1,30 @@
1/*
2 * A simple GPIO VBUS sensing driver for B peripheral only devices
3 * with internal transceivers.
4 * Optionally D+ pullup can be controlled by a second GPIO.
5 *
6 * Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2 as
10 * published by the Free Software Foundation.
11 *
12 */
13
14/**
15 * struct gpio_vbus_mach_info - configuration for gpio_vbus
16 * @gpio_vbus: VBUS sensing GPIO
17 * @gpio_pullup: optional D+ or D- pullup GPIO (else negative/invalid)
18 * @gpio_vbus_inverted: true if gpio_vbus is active low
19 * @gpio_pullup_inverted: true if gpio_pullup is active low
20 *
21 * The VBUS sensing GPIO should have a pulldown, which will normally be
22 * part of a resistor ladder turning a 4.0V-5.25V level on VBUS into a
23 * value the GPIO detects as active. Some systems will use comparators.
24 */
25struct gpio_vbus_mach_info {
26 int gpio_vbus;
27 int gpio_pullup;
28 bool gpio_vbus_inverted;
29 bool gpio_pullup_inverted;
30};