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

Commit cecabe5c authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Greg Kroah-Hartman
Browse files

USB: ohci-pxa27x: Add support for external vbus regulators



Override the hub control operation to enable and disable external
regulators for the ports vbus power supply in response to clear/set
USB_PORT_FEAT_POWER requests.

Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 37769939
Loading
Loading
Loading
Loading
+68 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <linux/platform_data/usb-ohci-pxa27x.h>
#include <linux/platform_data/usb-pxa3xx-ulpi.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/signal.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
@@ -120,6 +121,8 @@ static struct hc_driver __read_mostly ohci_pxa27x_hc_driver;
struct pxa27x_ohci {
	struct clk	*clk;
	void __iomem	*mmio_base;
	struct regulator *vbus[3];
	bool		vbus_enabled[3];
};

#define to_pxa27x_ohci(hcd)	(struct pxa27x_ohci *)(hcd_to_ohci(hcd)->priv)
@@ -166,6 +169,52 @@ static int pxa27x_ohci_select_pmm(struct pxa27x_ohci *pxa_ohci, int mode)
	return 0;
}

static int pxa27x_ohci_set_vbus_power(struct pxa27x_ohci *pxa_ohci,
				      unsigned int port, bool enable)
{
	struct regulator *vbus = pxa_ohci->vbus[port];
	int ret = 0;

	if (IS_ERR_OR_NULL(vbus))
		return 0;

	if (enable && !pxa_ohci->vbus_enabled[port])
		ret = regulator_enable(vbus);
	else if (!enable && pxa_ohci->vbus_enabled[port])
		ret = regulator_disable(vbus);

	if (ret < 0)
		return ret;

	pxa_ohci->vbus_enabled[port] = enable;

	return 0;
}

static int pxa27x_ohci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
				   u16 wIndex, char *buf, u16 wLength)
{
	struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd);
	int ret;

	switch (typeReq) {
	case SetPortFeature:
	case ClearPortFeature:
		if (!wIndex || wIndex > 3)
			return -EPIPE;

		if (wValue != USB_PORT_FEAT_POWER)
			break;

		ret = pxa27x_ohci_set_vbus_power(pxa_ohci, wIndex - 1,
						 typeReq == SetPortFeature);
		if (ret)
			return ret;
		break;
	}

	return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
}
/*-------------------------------------------------------------------------*/

static inline void pxa27x_setup_hc(struct pxa27x_ohci *pxa_ohci,
@@ -372,6 +421,7 @@ int usb_hcd_pxa27x_probe (const struct hc_driver *driver, struct platform_device
	struct ohci_hcd *ohci;
	struct resource *r;
	struct clk *usb_clk;
	unsigned int i;

	retval = ohci_pxa_of_init(pdev);
	if (retval)
@@ -417,6 +467,16 @@ int usb_hcd_pxa27x_probe (const struct hc_driver *driver, struct platform_device
	pxa_ohci->clk = usb_clk;
	pxa_ohci->mmio_base = (void __iomem *)hcd->regs;

	for (i = 0; i < 3; ++i) {
		char name[6];

		if (!(inf->flags & (ENABLE_PORT1 << i)))
			continue;

		sprintf(name, "vbus%u", i + 1);
		pxa_ohci->vbus[i] = devm_regulator_get(&pdev->dev, name);
	}

	retval = pxa27x_start_hc(pxa_ohci, &pdev->dev);
	if (retval < 0) {
		pr_debug("pxa27x_start_hc failed");
@@ -462,9 +522,14 @@ int usb_hcd_pxa27x_probe (const struct hc_driver *driver, struct platform_device
void usb_hcd_pxa27x_remove (struct usb_hcd *hcd, struct platform_device *pdev)
{
	struct pxa27x_ohci *pxa_ohci = to_pxa27x_ohci(hcd);
	unsigned int i;

	usb_remove_hcd(hcd);
	pxa27x_stop_hc(pxa_ohci, &pdev->dev);

	for (i = 0; i < 3; ++i)
		pxa27x_ohci_set_vbus_power(pxa_ohci, i, false);

	usb_put_hcd(hcd);
}

@@ -563,7 +628,10 @@ static int __init ohci_pxa27x_init(void)
		return -ENODEV;

	pr_info("%s: " DRIVER_DESC "\n", hcd_name);

	ohci_init_driver(&ohci_pxa27x_hc_driver, &pxa27x_overrides);
	ohci_pxa27x_hc_driver.hub_control = pxa27x_ohci_hub_control;

	return platform_driver_register(&ohci_hcd_pxa27x_driver);
}
module_init(ohci_pxa27x_init);