Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Philipp Zabel and committed by
Greg Kroah-Hartman
6084f1bf b8da8677

+383
+13
drivers/usb/otg/Kconfig
··· 14 14 Select this to make sure the build includes objects from 15 15 the OTG infrastructure directory. 16 16 17 + # 18 + # USB Transceiver Drivers 19 + # 20 + config 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 + 17 30 config ISP1301_OMAP 18 31 tristate "Philips ISP1301 with OMAP OTG" 19 32 depends on I2C && ARCH_OMAP_OTG
+5
drivers/usb/otg/Makefile
··· 1 + # 2 + # OTG infrastructure and transceiver drivers 3 + # 4 + 5 + obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o 1 6 obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o 2 7 3 8 ifeq ($(CONFIG_USB_DEBUG),y)
+335
drivers/usb/otg/gpio_vbus.c
··· 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 + */ 31 + struct 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 */ 55 + static 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 */ 80 + static 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 */ 131 + static 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 */ 171 + static 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 */ 183 + static 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 + 200 + static 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; 277 + err_otg: 278 + free_irq(irq, &pdev->dev); 279 + err_irq: 280 + if (gpio_is_valid(pdata->gpio_pullup)) 281 + gpio_free(pdata->gpio_pullup); 282 + gpio_free(pdata->gpio_vbus); 283 + err_gpio: 284 + platform_set_drvdata(pdev, NULL); 285 + kfree(gpio_vbus); 286 + return err; 287 + } 288 + 289 + static 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 + 311 + MODULE_ALIAS("platform:gpio-vbus"); 312 + 313 + static 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 + 321 + static int __init gpio_vbus_init(void) 322 + { 323 + return platform_driver_probe(&gpio_vbus_driver, gpio_vbus_probe); 324 + } 325 + module_init(gpio_vbus_init); 326 + 327 + static void __exit gpio_vbus_exit(void) 328 + { 329 + platform_driver_unregister(&gpio_vbus_driver); 330 + } 331 + module_exit(gpio_vbus_exit); 332 + 333 + MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver"); 334 + MODULE_AUTHOR("Philipp Zabel"); 335 + MODULE_LICENSE("GPL");
+30
include/linux/usb/gpio_vbus.h
··· 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 + */ 25 + struct gpio_vbus_mach_info { 26 + int gpio_vbus; 27 + int gpio_pullup; 28 + bool gpio_vbus_inverted; 29 + bool gpio_pullup_inverted; 30 + };