diff options
| author | Tomoya MORINAGA <tomoya.rohm@gmail.com> | 2012-02-03 02:35:26 -0500 |
|---|---|---|
| committer | Felipe Balbi <balbi@ti.com> | 2012-02-09 02:56:53 -0500 |
| commit | dd63180b758d5972fc90621af0741d5bfae1a684 (patch) | |
| tree | 0340c73c18cfac563b8b445cc79c74c2ab7d3ba7 | |
| parent | 20edfbb6a17f3007c1905e9849d8d306e318883b (diff) | |
usb: gadget: pch_udc: Detecting VBUS through GPIO
Problem:
In USB Suspend, pch_udc handles 'disconnect'.
Root cause:
The current pch_udc is not monitoring VBUS.
When USB cable is disconnected, USB Device Controller generates
an interrupt of USB Suspend.
pch_udc cannot distinguish it is USB Suspend or disconnect.
Therefore, pch_udc handles 'disconnect' after an interrupt of
USB Suspend happend.
Solution:
VBUS is detected through GPIO.
After an interrupt produced USB Suspend, if VBUS is Low,
pch_udc handles 'disconnect'.
If VBUS is High, pch_udc handles 'suspend'.
Signed-off-by: Tomoya MORINAGA <tomoya.rohm@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
| -rw-r--r-- | drivers/usb/gadget/pch_udc.c | 145 |
1 files changed, 142 insertions, 3 deletions
diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 82416aae816..9c5ae2c1014 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c | |||
| @@ -15,6 +15,13 @@ | |||
| 15 | #include <linux/interrupt.h> | 15 | #include <linux/interrupt.h> |
| 16 | #include <linux/usb/ch9.h> | 16 | #include <linux/usb/ch9.h> |
| 17 | #include <linux/usb/gadget.h> | 17 | #include <linux/usb/gadget.h> |
| 18 | #include <linux/gpio.h> | ||
| 19 | |||
| 20 | /* GPIO port for VBUS detecting */ | ||
| 21 | static int vbus_gpio_port = -1; /* GPIO port number (-1:Not used) */ | ||
| 22 | |||
| 23 | #define PCH_VBUS_PERIOD 3000 /* VBUS polling period (msec) */ | ||
| 24 | #define PCH_VBUS_INTERVAL 10 /* VBUS polling interval (msec) */ | ||
| 18 | 25 | ||
| 19 | /* Address offset of Registers */ | 26 | /* Address offset of Registers */ |
| 20 | #define UDC_EP_REG_SHIFT 0x20 /* Offset to next EP */ | 27 | #define UDC_EP_REG_SHIFT 0x20 /* Offset to next EP */ |
| @@ -296,6 +303,17 @@ struct pch_udc_ep { | |||
| 296 | }; | 303 | }; |
| 297 | 304 | ||
| 298 | /** | 305 | /** |
| 306 | * struct pch_vbus_gpio_data - Structure holding GPIO informaton | ||
| 307 | * for detecting VBUS | ||
| 308 | * @port: gpio port number | ||
| 309 | * @irq_work_fall Structure for WorkQueue | ||
| 310 | */ | ||
| 311 | struct pch_vbus_gpio_data { | ||
| 312 | int port; | ||
| 313 | struct work_struct irq_work_fall; | ||
| 314 | }; | ||
| 315 | |||
| 316 | /** | ||
| 299 | * struct pch_udc_dev - Structure holding complete information | 317 | * struct pch_udc_dev - Structure holding complete information |
| 300 | * of the PCH USB device | 318 | * of the PCH USB device |
| 301 | * @gadget: gadget driver data | 319 | * @gadget: gadget driver data |
| @@ -323,6 +341,7 @@ struct pch_udc_ep { | |||
| 323 | * @base_addr: for mapped device memory | 341 | * @base_addr: for mapped device memory |
| 324 | * @irq: IRQ line for the device | 342 | * @irq: IRQ line for the device |
| 325 | * @cfg_data: current cfg, intf, and alt in use | 343 | * @cfg_data: current cfg, intf, and alt in use |
| 344 | * @vbus_gpio: GPIO informaton for detecting VBUS | ||
| 326 | */ | 345 | */ |
| 327 | struct pch_udc_dev { | 346 | struct pch_udc_dev { |
| 328 | struct usb_gadget gadget; | 347 | struct usb_gadget gadget; |
| @@ -349,7 +368,8 @@ struct pch_udc_dev { | |||
| 349 | unsigned long phys_addr; | 368 | unsigned long phys_addr; |
| 350 | void __iomem *base_addr; | 369 | void __iomem *base_addr; |
| 351 | unsigned irq; | 370 | unsigned irq; |
| 352 | struct pch_udc_cfg_data cfg_data; | 371 | struct pch_udc_cfg_data cfg_data; |
| 372 | struct pch_vbus_gpio_data vbus_gpio; | ||
| 353 | }; | 373 | }; |
| 354 | 374 | ||
| 355 | #define PCH_UDC_PCI_BAR 1 | 375 | #define PCH_UDC_PCI_BAR 1 |
| @@ -1226,6 +1246,115 @@ static const struct usb_gadget_ops pch_udc_ops = { | |||
| 1226 | }; | 1246 | }; |
| 1227 | 1247 | ||
| 1228 | /** | 1248 | /** |
| 1249 | * pch_vbus_gpio_get_value() - This API gets value of GPIO port as VBUS status. | ||
| 1250 | * @dev: Reference to the driver structure | ||
| 1251 | * | ||
| 1252 | * Return value: | ||
| 1253 | * 1: VBUS is high | ||
| 1254 | * 0: VBUS is low | ||
| 1255 | * -1: It is not enable to detect VBUS using GPIO | ||
| 1256 | */ | ||
| 1257 | static int pch_vbus_gpio_get_value(struct pch_udc_dev *dev) | ||
| 1258 | { | ||
| 1259 | int vbus = 0; | ||
| 1260 | |||
| 1261 | if (dev->vbus_gpio.port) | ||
| 1262 | vbus = gpio_get_value(dev->vbus_gpio.port) ? 1 : 0; | ||
| 1263 | else | ||
| 1264 | vbus = -1; | ||
| 1265 | |||
| 1266 | return vbus; | ||
| 1267 | } | ||
| 1268 | |||
| 1269 | /** | ||
| 1270 | * pch_vbus_gpio_work_fall() - This API keeps watch on VBUS becoming Low. | ||
| 1271 | * If VBUS is Low, disconnect is processed | ||
| 1272 | * @irq_work: Structure for WorkQueue | ||
| 1273 | * | ||
| 1274 | */ | ||
| 1275 | static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) | ||
| 1276 | { | ||
| 1277 | struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, | ||
| 1278 | struct pch_vbus_gpio_data, irq_work_fall); | ||
| 1279 | struct pch_udc_dev *dev = | ||
| 1280 | container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); | ||
| 1281 | int vbus_saved = -1; | ||
| 1282 | int vbus; | ||
| 1283 | int count; | ||
| 1284 | |||
| 1285 | if (!dev->vbus_gpio.port) | ||
| 1286 | return; | ||
| 1287 | |||
| 1288 | for (count = 0; count < (PCH_VBUS_PERIOD / PCH_VBUS_INTERVAL); | ||
| 1289 | count++) { | ||
| 1290 | vbus = pch_vbus_gpio_get_value(dev); | ||
| 1291 | |||
| 1292 | if ((vbus_saved == vbus) && (vbus == 0)) { | ||
| 1293 | dev_dbg(&dev->pdev->dev, "VBUS fell"); | ||
| 1294 | if (dev->driver | ||
| 1295 | && dev->driver->disconnect) { | ||
| 1296 | dev->driver->disconnect( | ||
| 1297 | &dev->gadget); | ||
| 1298 | } | ||
| 1299 | pch_udc_reconnect(dev); | ||
| 1300 | dev_dbg(&dev->pdev->dev, "VBUS fell"); | ||
| 1301 | return; | ||
| 1302 | } | ||
| 1303 | vbus_saved = vbus; | ||
| 1304 | mdelay(PCH_VBUS_INTERVAL); | ||
| 1305 | } | ||
| 1306 | } | ||
| 1307 | |||
| 1308 | /** | ||
| 1309 | * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. | ||
| 1310 | * @dev: Reference to the driver structure | ||
| 1311 | * @vbus_gpio Number of GPIO port to detect gpio | ||
| 1312 | * | ||
| 1313 | * Return codes: | ||
| 1314 | * 0: Success | ||
| 1315 | * -EINVAL: GPIO port is invalid or can't be initialized. | ||
| 1316 | */ | ||
| 1317 | static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) | ||
| 1318 | { | ||
| 1319 | int err; | ||
| 1320 | |||
| 1321 | dev->vbus_gpio.port = 0; | ||
| 1322 | |||
| 1323 | if (vbus_gpio_port <= -1) | ||
| 1324 | return -EINVAL; | ||
| 1325 | |||
| 1326 | err = gpio_is_valid(vbus_gpio_port); | ||
| 1327 | if (!err) { | ||
| 1328 | pr_err("%s: gpio port %d is invalid\n", | ||
| 1329 | __func__, vbus_gpio_port); | ||
| 1330 | return -EINVAL; | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | err = gpio_request(vbus_gpio_port, "pch_vbus"); | ||
| 1334 | if (err) { | ||
| 1335 | pr_err("%s: can't request gpio port %d, err: %d\n", | ||
| 1336 | __func__, vbus_gpio_port, err); | ||
| 1337 | return -EINVAL; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | dev->vbus_gpio.port = vbus_gpio_port; | ||
| 1341 | gpio_direction_input(vbus_gpio_port); | ||
| 1342 | INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); | ||
| 1343 | |||
| 1344 | return 0; | ||
| 1345 | } | ||
| 1346 | |||
| 1347 | /** | ||
| 1348 | * pch_vbus_gpio_free() - This API frees resources of GPIO port | ||
| 1349 | * @dev: Reference to the driver structure | ||
| 1350 | */ | ||
| 1351 | static void pch_vbus_gpio_free(struct pch_udc_dev *dev) | ||
| 1352 | { | ||
| 1353 | if (dev->vbus_gpio.port) | ||
| 1354 | gpio_free(dev->vbus_gpio.port); | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | /** | ||
| 1229 | * complete_req() - This API is invoked from the driver when processing | 1358 | * complete_req() - This API is invoked from the driver when processing |
| 1230 | * of a request is complete | 1359 | * of a request is complete |
| 1231 | * @ep: Reference to the endpoint structure | 1360 | * @ep: Reference to the endpoint structure |
| @@ -2510,6 +2639,8 @@ static void pch_udc_svc_cfg_interrupt(struct pch_udc_dev *dev) | |||
| 2510 | */ | 2639 | */ |
| 2511 | static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) | 2640 | static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) |
| 2512 | { | 2641 | { |
| 2642 | int vbus; | ||
| 2643 | |||
| 2513 | /* USB Reset Interrupt */ | 2644 | /* USB Reset Interrupt */ |
| 2514 | if (dev_intr & UDC_DEVINT_UR) { | 2645 | if (dev_intr & UDC_DEVINT_UR) { |
| 2515 | pch_udc_svc_ur_interrupt(dev); | 2646 | pch_udc_svc_ur_interrupt(dev); |
| @@ -2535,14 +2666,19 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) | |||
| 2535 | spin_lock(&dev->lock); | 2666 | spin_lock(&dev->lock); |
| 2536 | } | 2667 | } |
| 2537 | 2668 | ||
| 2538 | if (dev->vbus_session == 0) { | 2669 | vbus = pch_vbus_gpio_get_value(dev); |
| 2670 | if ((dev->vbus_session == 0) | ||
| 2671 | && (vbus != 1)) { | ||
| 2539 | if (dev->driver && dev->driver->disconnect) { | 2672 | if (dev->driver && dev->driver->disconnect) { |
| 2540 | spin_unlock(&dev->lock); | 2673 | spin_unlock(&dev->lock); |
| 2541 | dev->driver->disconnect(&dev->gadget); | 2674 | dev->driver->disconnect(&dev->gadget); |
| 2542 | spin_lock(&dev->lock); | 2675 | spin_lock(&dev->lock); |
| 2543 | } | 2676 | } |
| 2544 | pch_udc_reconnect(dev); | 2677 | |
