diff options
author | Tomoya MORINAGA <tomoya.rohm@gmail.com> | 2012-02-03 02:14:18 -0500 |
---|---|---|
committer | Felipe Balbi <balbi@ti.com> | 2012-02-09 02:58:48 -0500 |
commit | 637b78eb31e0b167ed913f1750bb645dfeda38f0 (patch) | |
tree | 9496e1f2489bedef09c523bd0f0f0f960e553014 /drivers/usb/gadget | |
parent | dd63180b758d5972fc90621af0741d5bfae1a684 (diff) |
usb: gadget: pch_udc: Detecting VBUS through GPIO with interrupt
Problem:
pch_udc continues operation even if VBUS becomes Low.
pch_udc performs D+ pulling up before VBUS becomes High.
USB device should be controlled according to VBUS state.
Root cause:
The current pch_udc is not always monitoring VBUS.
Solution:
The change of VBUS is detected using an interrupt of GPIO.
If VBUS became Low, pch_udc handles 'disconnect'.
After VBUS became High, a pull improves D+, and pch_udc
handles 'connect'.
[ balbi@ti.com : make it actually compile ]
Signed-off-by: Tomoya MORINAGA <tomoya.rohm@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r-- | drivers/usb/gadget/pch_udc.c | 87 |
1 files changed, 83 insertions, 4 deletions
diff --git a/drivers/usb/gadget/pch_udc.c b/drivers/usb/gadget/pch_udc.c index 9c5ae2c10147..a992084d3890 100644 --- a/drivers/usb/gadget/pch_udc.c +++ b/drivers/usb/gadget/pch_udc.c | |||
@@ -306,11 +306,15 @@ struct pch_udc_ep { | |||
306 | * struct pch_vbus_gpio_data - Structure holding GPIO informaton | 306 | * struct pch_vbus_gpio_data - Structure holding GPIO informaton |
307 | * for detecting VBUS | 307 | * for detecting VBUS |
308 | * @port: gpio port number | 308 | * @port: gpio port number |
309 | * @intr: gpio interrupt number | ||
309 | * @irq_work_fall Structure for WorkQueue | 310 | * @irq_work_fall Structure for WorkQueue |
311 | * @irq_work_rise Structure for WorkQueue | ||
310 | */ | 312 | */ |
311 | struct pch_vbus_gpio_data { | 313 | struct pch_vbus_gpio_data { |
312 | int port; | 314 | int port; |
315 | int intr; | ||
313 | struct work_struct irq_work_fall; | 316 | struct work_struct irq_work_fall; |
317 | struct work_struct irq_work_rise; | ||
314 | }; | 318 | }; |
315 | 319 | ||
316 | /** | 320 | /** |
@@ -1296,8 +1300,10 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) | |||
1296 | dev->driver->disconnect( | 1300 | dev->driver->disconnect( |
1297 | &dev->gadget); | 1301 | &dev->gadget); |
1298 | } | 1302 | } |
1299 | pch_udc_reconnect(dev); | 1303 | if (dev->vbus_gpio.intr) |
1300 | dev_dbg(&dev->pdev->dev, "VBUS fell"); | 1304 | pch_udc_init(dev); |
1305 | else | ||
1306 | pch_udc_reconnect(dev); | ||
1301 | return; | 1307 | return; |
1302 | } | 1308 | } |
1303 | vbus_saved = vbus; | 1309 | vbus_saved = vbus; |
@@ -1306,6 +1312,57 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) | |||
1306 | } | 1312 | } |
1307 | 1313 | ||
1308 | /** | 1314 | /** |
1315 | * pch_vbus_gpio_work_rise() - This API checks VBUS is High. | ||
1316 | * If VBUS is High, connect is processed | ||
1317 | * @irq_work: Structure for WorkQueue | ||
1318 | * | ||
1319 | */ | ||
1320 | static void pch_vbus_gpio_work_rise(struct work_struct *irq_work) | ||
1321 | { | ||
1322 | struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, | ||
1323 | struct pch_vbus_gpio_data, irq_work_rise); | ||
1324 | struct pch_udc_dev *dev = | ||
1325 | container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); | ||
1326 | int vbus; | ||
1327 | |||
1328 | if (!dev->vbus_gpio.port) | ||
1329 | return; | ||
1330 | |||
1331 | mdelay(PCH_VBUS_INTERVAL); | ||
1332 | vbus = pch_vbus_gpio_get_value(dev); | ||
1333 | |||
1334 | if (vbus == 1) { | ||
1335 | dev_dbg(&dev->pdev->dev, "VBUS rose"); | ||
1336 | pch_udc_reconnect(dev); | ||
1337 | return; | ||
1338 | } | ||
1339 | } | ||
1340 | |||
1341 | /** | ||
1342 | * pch_vbus_gpio_irq() - IRQ handler for GPIO intrerrupt for changing VBUS | ||
1343 | * @irq: Interrupt request number | ||
1344 | * @dev: Reference to the device structure | ||
1345 | * | ||
1346 | * Return codes: | ||
1347 | * 0: Success | ||
1348 | * -EINVAL: GPIO port is invalid or can't be initialized. | ||
1349 | */ | ||
1350 | static irqreturn_t pch_vbus_gpio_irq(int irq, void *data) | ||
1351 | { | ||
1352 | struct pch_udc_dev *dev = (struct pch_udc_dev *)data; | ||
1353 | |||
1354 | if (!dev->vbus_gpio.port || !dev->vbus_gpio.intr) | ||
1355 | return IRQ_NONE; | ||
1356 | |||
1357 | if (pch_vbus_gpio_get_value(dev)) | ||
1358 | schedule_work(&dev->vbus_gpio.irq_work_rise); | ||
1359 | else | ||
1360 | schedule_work(&dev->vbus_gpio.irq_work_fall); | ||
1361 | |||
1362 | return IRQ_HANDLED; | ||
1363 | } | ||
1364 | |||
1365 | /** | ||
1309 | * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. | 1366 | * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS. |
1310 | * @dev: Reference to the driver structure | 1367 | * @dev: Reference to the driver structure |
1311 | * @vbus_gpio Number of GPIO port to detect gpio | 1368 | * @vbus_gpio Number of GPIO port to detect gpio |
@@ -1317,8 +1374,10 @@ static void pch_vbus_gpio_work_fall(struct work_struct *irq_work) | |||
1317 | static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) | 1374 | static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) |
1318 | { | 1375 | { |
1319 | int err; | 1376 | int err; |
1377 | int irq_num = 0; | ||
1320 | 1378 | ||
1321 | dev->vbus_gpio.port = 0; | 1379 | dev->vbus_gpio.port = 0; |
1380 | dev->vbus_gpio.intr = 0; | ||
1322 | 1381 | ||
1323 | if (vbus_gpio_port <= -1) | 1382 | if (vbus_gpio_port <= -1) |
1324 | return -EINVAL; | 1383 | return -EINVAL; |
@@ -1341,6 +1400,21 @@ static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) | |||
1341 | gpio_direction_input(vbus_gpio_port); | 1400 | gpio_direction_input(vbus_gpio_port); |
1342 | INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); | 1401 | INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); |
1343 | 1402 | ||
1403 | irq_num = gpio_to_irq(vbus_gpio_port); | ||
1404 | if (irq_num > 0) { | ||
1405 | irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_BOTH); | ||
1406 | err = request_irq(irq_num, pch_vbus_gpio_irq, 0, | ||
1407 | "vbus_detect", dev); | ||
1408 | if (!err) { | ||
1409 | dev->vbus_gpio.intr = irq_num; | ||
1410 | INIT_WORK(&dev->vbus_gpio.irq_work_rise, | ||
1411 | pch_vbus_gpio_work_rise); | ||
1412 | } else { | ||
1413 | pr_err("%s: can't request irq %d, err: %d\n", | ||
1414 | __func__, irq_num, err); | ||
1415 | } | ||
1416 | } | ||
1417 | |||
1344 | return 0; | 1418 | return 0; |
1345 | } | 1419 | } |
1346 | 1420 | ||
@@ -1350,6 +1424,9 @@ static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) | |||
1350 | */ | 1424 | */ |
1351 | static void pch_vbus_gpio_free(struct pch_udc_dev *dev) | 1425 | static void pch_vbus_gpio_free(struct pch_udc_dev *dev) |
1352 | { | 1426 | { |
1427 | if (dev->vbus_gpio.intr) | ||
1428 | free_irq(dev->vbus_gpio.intr, dev); | ||
1429 | |||
1353 | if (dev->vbus_gpio.port) | 1430 | if (dev->vbus_gpio.port) |
1354 | gpio_free(dev->vbus_gpio.port); | 1431 | gpio_free(dev->vbus_gpio.port); |
1355 | } | 1432 | } |
@@ -2676,7 +2753,8 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr) | |||
2676 | } | 2753 | } |
2677 | pch_udc_reconnect(dev); | 2754 | pch_udc_reconnect(dev); |
2678 | } else if ((dev->vbus_session == 0) | 2755 | } else if ((dev->vbus_session == 0) |
2679 | && (vbus == 1)) | 2756 | && (vbus == 1) |
2757 | && !dev->vbus_gpio.intr) | ||
2680 | schedule_work(&dev->vbus_gpio.irq_work_fall); | 2758 | schedule_work(&dev->vbus_gpio.irq_work_fall); |
2681 | 2759 | ||
2682 | dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); | 2760 | dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); |
@@ -2941,7 +3019,8 @@ static int pch_udc_start(struct usb_gadget_driver *driver, | |||
2941 | pch_udc_setup_ep0(dev); | 3019 | pch_udc_setup_ep0(dev); |
2942 | 3020 | ||
2943 | /* clear SD */ | 3021 | /* clear SD */ |
2944 | pch_udc_clear_disconnect(dev); | 3022 | if ((pch_vbus_gpio_get_value(dev) != 0) || !dev->vbus_gpio.intr) |
3023 | pch_udc_clear_disconnect(dev); | ||
2945 | 3024 | ||
2946 | dev->connected = 1; | 3025 | dev->connected = 1; |
2947 | return 0; | 3026 | return 0; |