Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd63180b authored by Tomoya MORINAGA's avatar Tomoya MORINAGA Committed by Felipe Balbi
Browse files

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: default avatarTomoya MORINAGA <tomoya.rohm@gmail.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 20edfbb6
Loading
Loading
Loading
Loading
+142 −3
Original line number Diff line number Diff line
@@ -15,6 +15,13 @@
#include <linux/interrupt.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include <linux/gpio.h>

/* GPIO port for VBUS detecting */
static int vbus_gpio_port = -1;		/* GPIO port number (-1:Not used) */

#define PCH_VBUS_PERIOD		3000	/* VBUS polling period (msec) */
#define PCH_VBUS_INTERVAL	10	/* VBUS polling interval (msec) */

/* Address offset of Registers */
#define UDC_EP_REG_SHIFT	0x20	/* Offset to next EP */
@@ -295,6 +302,17 @@ struct pch_udc_ep {
	unsigned long			epsts;
};

/**
 * struct pch_vbus_gpio_data - Structure holding GPIO informaton
 *					for detecting VBUS
 * @port:		gpio port number
 * @irq_work_fall	Structure for WorkQueue
 */
struct pch_vbus_gpio_data {
	int			port;
	struct work_struct	irq_work_fall;
};

/**
 * struct pch_udc_dev - Structure holding complete information
 *			of the PCH USB device
@@ -323,6 +341,7 @@ struct pch_udc_ep {
 * @base_addr:		for mapped device memory
 * @irq:		IRQ line for the device
 * @cfg_data:		current cfg, intf, and alt in use
 * @vbus_gpio:		GPIO informaton for detecting VBUS
 */
struct pch_udc_dev {
	struct usb_gadget		gadget;
@@ -350,6 +369,7 @@ struct pch_udc_dev {
	void __iomem			*base_addr;
	unsigned			irq;
	struct pch_udc_cfg_data		cfg_data;
	struct pch_vbus_gpio_data	vbus_gpio;
};

#define PCH_UDC_PCI_BAR			1
@@ -1225,6 +1245,115 @@ static const struct usb_gadget_ops pch_udc_ops = {
	.stop	= pch_udc_stop,
};

/**
 * pch_vbus_gpio_get_value() - This API gets value of GPIO port as VBUS status.
 * @dev:	Reference to the driver structure
 *
 * Return value:
 *	1: VBUS is high
 *	0: VBUS is low
 *     -1: It is not enable to detect VBUS using GPIO
 */
static int pch_vbus_gpio_get_value(struct pch_udc_dev *dev)
{
	int vbus = 0;

	if (dev->vbus_gpio.port)
		vbus = gpio_get_value(dev->vbus_gpio.port) ? 1 : 0;
	else
		vbus = -1;

	return vbus;
}

/**
 * pch_vbus_gpio_work_fall() - This API keeps watch on VBUS becoming Low.
 *                             If VBUS is Low, disconnect is processed
 * @irq_work:	Structure for WorkQueue
 *
 */
static void pch_vbus_gpio_work_fall(struct work_struct *irq_work)
{
	struct pch_vbus_gpio_data *vbus_gpio = container_of(irq_work,
		struct pch_vbus_gpio_data, irq_work_fall);
	struct pch_udc_dev *dev =
		container_of(vbus_gpio, struct pch_udc_dev, vbus_gpio);
	int vbus_saved = -1;
	int vbus;
	int count;

	if (!dev->vbus_gpio.port)
		return;

	for (count = 0; count < (PCH_VBUS_PERIOD / PCH_VBUS_INTERVAL);
		count++) {
		vbus = pch_vbus_gpio_get_value(dev);

		if ((vbus_saved == vbus) && (vbus == 0)) {
			dev_dbg(&dev->pdev->dev, "VBUS fell");
			if (dev->driver
				&& dev->driver->disconnect) {
				dev->driver->disconnect(
					&dev->gadget);
			}
			pch_udc_reconnect(dev);
			dev_dbg(&dev->pdev->dev, "VBUS fell");
			return;
		}
		vbus_saved = vbus;
		mdelay(PCH_VBUS_INTERVAL);
	}
}

/**
 * pch_vbus_gpio_init() - This API initializes GPIO port detecting VBUS.
 * @dev:	Reference to the driver structure
 * @vbus_gpio	Number of GPIO port to detect gpio
 *
 * Return codes:
 *	0: Success
 *	-EINVAL: GPIO port is invalid or can't be initialized.
 */
static int pch_vbus_gpio_init(struct pch_udc_dev *dev, int vbus_gpio_port)
{
	int err;

	dev->vbus_gpio.port = 0;

	if (vbus_gpio_port <= -1)
		return -EINVAL;

	err = gpio_is_valid(vbus_gpio_port);
	if (!err) {
		pr_err("%s: gpio port %d is invalid\n",
			__func__, vbus_gpio_port);
		return -EINVAL;
	}

	err = gpio_request(vbus_gpio_port, "pch_vbus");
	if (err) {
		pr_err("%s: can't request gpio port %d, err: %d\n",
			__func__, vbus_gpio_port, err);
		return -EINVAL;
	}

	dev->vbus_gpio.port = vbus_gpio_port;
	gpio_direction_input(vbus_gpio_port);
	INIT_WORK(&dev->vbus_gpio.irq_work_fall, pch_vbus_gpio_work_fall);

	return 0;
}

/**
 * pch_vbus_gpio_free() - This API frees resources of GPIO port
 * @dev:	Reference to the driver structure
 */
static void pch_vbus_gpio_free(struct pch_udc_dev *dev)
{
	if (dev->vbus_gpio.port)
		gpio_free(dev->vbus_gpio.port);
}

/**
 * complete_req() - This API is invoked from the driver when processing
 *			of a request is complete
@@ -2510,6 +2639,8 @@ static void pch_udc_svc_cfg_interrupt(struct pch_udc_dev *dev)
 */
static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr)
{
	int vbus;

	/* USB Reset Interrupt */
	if (dev_intr & UDC_DEVINT_UR) {
		pch_udc_svc_ur_interrupt(dev);
@@ -2535,14 +2666,19 @@ static void pch_udc_dev_isr(struct pch_udc_dev *dev, u32 dev_intr)
			spin_lock(&dev->lock);
		}

		if (dev->vbus_session == 0) {
		vbus = pch_vbus_gpio_get_value(dev);
		if ((dev->vbus_session == 0)
			&& (vbus != 1)) {
			if (dev->driver && dev->driver->disconnect) {
				spin_unlock(&dev->lock);
				dev->driver->disconnect(&dev->gadget);
				spin_lock(&dev->lock);
			}
			pch_udc_reconnect(dev);
		}
		} else if ((dev->vbus_session == 0)
			&& (vbus == 1))
			schedule_work(&dev->vbus_gpio.irq_work_fall);

		dev_dbg(&dev->pdev->dev, "USB_SUSPEND\n");
	}
	/* Clear the SOF interrupt, if enabled */
@@ -2704,6 +2840,7 @@ static int pch_udc_pcd_init(struct pch_udc_dev *dev)
{
	pch_udc_init(dev);
	pch_udc_pcd_reinit(dev);
	pch_vbus_gpio_init(dev, vbus_gpio_port);
	return 0;
}

@@ -2882,6 +3019,8 @@ static void pch_udc_remove(struct pci_dev *pdev)
				 UDC_EP0OUT_BUFF_SIZE * 4, DMA_FROM_DEVICE);
	kfree(dev->ep0out_buf);

	pch_vbus_gpio_free(dev);

	pch_udc_exit(dev);

	if (dev->irq_registered)