USB: ohci-pxa27x: Add support for external vbus regulators
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Wed, 16 Apr 2014 16:00:11 +0000 (18:00 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 24 Apr 2014 20:05:32 +0000 (13:05 -0700)
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: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ohci-pxa27x.c

index d21d5fefa76c7bce41c49ad71027f7a084cb503d..e68f3d02cd1a3cb14a6dbd05abdf59be1a365a4e 100644 (file)
@@ -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);