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

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>

authored by

Tomoya MORINAGA and committed by
Felipe Balbi
637b78eb dd63180b

+83 -4
+83 -4
drivers/usb/gadget/pch_udc.c
··· 306 306 * struct pch_vbus_gpio_data - Structure holding GPIO informaton 307 307 * for detecting VBUS 308 308 * @port: gpio port number 309 + * @intr: gpio interrupt number 309 310 * @irq_work_fall Structure for WorkQueue 311 + * @irq_work_rise Structure for WorkQueue 310 312 */ 311 313 struct pch_vbus_gpio_data { 312 314 int port; 315 + int intr; 313 316 struct work_struct irq_work_fall; 317 + struct work_struct irq_work_rise; 314 318 }; 315 319 316 320 /** ··· 1300 1296 dev->driver->disconnect( 1301 1297 &dev->gadget); 1302 1298 } 1303 - pch_udc_reconnect(dev); 1304 - dev_dbg(&dev->pdev->dev, "VBUS fell"); 1299 + if (dev->vbus_gpio.intr) 1300 + pch_udc_init(dev); 1301 + else 1302 + pch_udc_reconnect(dev); 1305 1303 return; 1306 1304 } 1307 1305 vbus_saved = vbus; 1308 1306 mdelay(PCH_VBUS_INTERVAL); 1309 1307 } 1308 + } 1309 + 1310 + /** 1311 + * pch_vbus_gpio_work_rise() - This API checks VBUS is High. 1312 + * If VBUS is High, connect is processed 1313 + * @irq_work: Structure for WorkQueue 1314 + * 1315 + */ 1316 + static void pch_vbus_gpio_work_rise(struct work_struct *irq_work) 1317 + { 1318 + struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work, 1319 + struct pch_vbus_gpio_data, irq_work_rise); 1320 + struct pch_udc_dev *dev = 1321 + container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio); 1322 + int vbus; 1323 + 1324 + if (!dev->vbus_gpio.port) 1325 + return; 1326 + 1327 + mdelay(PCH_VBUS_INTERVAL); 1328 + vbus = pch_vbus_gpio_get_value(dev); 1329 + 1330 + if (vbus == 1) { 1331 + dev_dbg(&dev->pdev->dev, "VBUS rose"); 1332 + pch_udc_reconnect(dev); 1333 + return; 1334 + } 1335 + } 1336 + 1337 + /** 1338 + * pch_vbus_gpio_irq() - IRQ handler for GPIO intrerrupt for changing VBUS 1339 + * @irq: Interrupt request number 1340 + * @dev: Reference to the device structure 1341 + * 1342 + * Return codes: 1343 + * 0: Success 1344 + * -EINVAL: GPIO port is invalid or can't be initialized. 1345 + */ 1346 + static irqreturn_t pch_vbus_gpio_irq(int irq, void *data) 1347 + { 1348 + struct pch_udc_dev *dev = (struct pch_udc_dev *)data; 1349 + 1350 + if (!dev->vbus_gpio.port || !dev->vbus_gpio.intr) 1351 + return IRQ_NONE; 1352 + 1353 + if (pch_vbus_gpio_get_value(dev)) 1354 + schedule_work(&dev->vbus_gpio.irq_work_rise); 1355 + else 1356 + schedule_work(&dev->vbus_gpio.irq_work_fall); 1357 + 1358 + return IRQ_HANDLED; 1310 1359 } 1311 1360 1312 1361 /** ··· 1374 1317 static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port) 1375 1318 { 1376 1319 int err; 1320 + int irq_num = 0; 1377 1321 1378 1322 dev->vbus_gpio.port = 0; 1323 + dev->vbus_gpio.intr = 0; 1379 1324 1380 1325 if (vbus_gpio_port <= -1) 1381 1326 return -EINVAL; ··· 1400 1341 gpio_direction_input(vbus_gpio_port); 1401 1342 INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall); 1402 1343 1344 + irq_num = gpio_to_irq(vbus_gpio_port); 1345 + if (irq_num > 0) { 1346 + irq_set_irq_type(irq_num, IRQ_TYPE_EDGE_BOTH); 1347 + err = request_irq(irq_num, pch_vbus_gpio_irq, 0, 1348 + "vbus_detect", dev); 1349 + if (!err) { 1350 + dev->vbus_gpio.intr = irq_num; 1351 + INIT_WORK(&dev->vbus_gpio.irq_work_rise, 1352 + pch_vbus_gpio_work_rise); 1353 + } else { 1354 + pr_err("%s: can't request irq %d, err: %d\n", 1355 + __func__, irq_num, err); 1356 + } 1357 + } 1358 + 1403 1359 return 0; 1404 1360 } 1405 1361 ··· 1424 1350 */ 1425 1351 static void pch_vbus_gpio_free(struct pch_udc_dev *dev) 1426 1352 { 1353 + if (dev->vbus_gpio.intr) 1354 + free_irq(dev->vbus_gpio.intr, dev); 1355 + 1427 1356 if (dev->vbus_gpio.port) 1428 1357 gpio_free(dev->vbus_gpio.port); 1429 1358 } ··· 2753 2676 } 2754 2677 pch_udc_reconnect(dev); 2755 2678 } else if ((dev->vbus_session == 0) 2756 - && (vbus == 1)) 2679 + && (vbus == 1) 2680 + && !dev->vbus_gpio.intr) 2757 2681 schedule_work(&dev->vbus_gpio.irq_work_fall); 2758 2682 2759 2683 dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n"); ··· 3019 2941 pch_udc_setup_ep0(dev); 3020 2942 3021 2943 /* clear SD */ 3022 - pch_udc_clear_disconnect(dev); 2944 + if ((pch_vbus_gpio_get_value(dev) != 0) || !dev->vbus_gpio.intr) 2945 + pch_udc_clear_disconnect(dev); 3023 2946 3024 2947 dev->connected = 1; 3025 2948 return 0;