From: Michael Grzeschik <m.grzesc...@pengutronix.de>

The current EHCI implementation is prepared to toggle the
PORT_POWER bit to enable or disable a USB-Port. In some
cases this port power can not be toggled by the PORT_POWER
bit but instead i.e. by an external GPIO.
This patch adds a override callback for port power control,
the platform code can use it for its specific usage.

Signed-off-by: Michael Grzeschik <m.grzesc...@pengutronix.de>
Signed-off-by: Peter Chen <peter.c...@freescale.com>
---
 drivers/usb/host/ehci-hcd.c |   10 +++++++++-
 drivers/usb/host/ehci-hub.c |   41 +++++++++++++++++++++++++++++++----------
 drivers/usb/host/ehci.h     |    3 +++
 include/linux/usb/hcd.h     |    3 +++
 4 files changed, 46 insertions(+), 11 deletions(-)

diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 15feaf9..b59c8fa 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -328,10 +328,15 @@ static void end_unlink_intr(struct ehci_hcd *ehci, struct 
ehci_qh *qh);
 static void ehci_turn_off_all_ports(struct ehci_hcd *ehci)
 {
        int     port = HCS_N_PORTS(ehci->hcs_params);
+       struct usb_hcd *hcd = ehci_to_hcd(ehci);
 
-       while (port--)
+       while (port--) {
                ehci_writel(ehci, PORT_RWC_BITS,
                                &ehci->regs->port_status[port]);
+               spin_unlock_irq(&ehci->lock);
+               hcd->driver->port_power(hcd, port, false);
+               spin_lock_irq(&ehci->lock);
+       }
 }
 
 /*
@@ -1216,6 +1221,7 @@ static const struct hc_driver ehci_hc_driver = {
        .bus_resume =           ehci_bus_resume,
        .relinquish_port =      ehci_relinquish_port,
        .port_handed_over =     ehci_port_handed_over,
+       .port_power     =       ehci_port_power,
 
        /*
         * device support
@@ -1233,6 +1239,8 @@ void ehci_init_driver(struct hc_driver *drv,
                drv->hcd_priv_size += over->extra_priv_size;
                if (over->reset)
                        drv->reset = over->reset;
+               if (over->port_power)
+                       drv->port_power = over->port_power;
        }
 }
 EXPORT_SYMBOL_GPL(ehci_init_driver);
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 5728829..bfb7b75 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -70,8 +70,8 @@ static void ehci_handover_companion_ports(struct ehci_hcd 
*ehci)
                        reg = &ehci->regs->port_status[port];
                        status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
                        if (!(status & PORT_POWER)) {
-                               status |= PORT_POWER;
                                ehci_writel(ehci, status, reg);
+                               hcd->driver->port_power(hcd, port, true);
                        }
                }
        }
@@ -952,9 +952,11 @@ int ehci_hub_control(
                        clear_bit(wIndex, &ehci->port_c_suspend);
                        break;
                case USB_PORT_FEAT_POWER:
-                       if (HCS_PPC (ehci->hcs_params))
-                               ehci_writel(ehci, temp & ~PORT_POWER,
-                                               status_reg);
+                       if (HCS_PPC(ehci->hcs_params)) {
+                               spin_unlock_irqrestore(&ehci->lock, flags);
+                               hcd->driver->port_power(hcd, wIndex, false);
+                               spin_lock_irqsave(&ehci->lock, flags);
+                       }
                        break;
                case USB_PORT_FEAT_C_CONNECTION:
                        ehci_writel(ehci, temp | PORT_CSC, status_reg);
@@ -1004,9 +1006,11 @@ int ehci_hub_control(
                         */
                        if (((temp & PORT_OC) || (ehci->need_oc_pp_cycle))
                                        && HCS_PPC(ehci->hcs_params)) {
-                               ehci_writel(ehci,
-                                       temp & ~(PORT_RWC_BITS | PORT_POWER),
-                                       status_reg);
+                               temp &= ~PORT_RWC_BITS;
+                               ehci_writel(ehci, temp, status_reg);
+                               spin_unlock_irqrestore(&ehci->lock, flags);
+                               hcd->driver->port_power(hcd, wIndex, false);
+                               spin_lock_irqsave(&ehci->lock, flags);
                                temp = ehci_readl(ehci, status_reg);
                        }
                }
@@ -1187,9 +1191,11 @@ int ehci_hub_control(
                        set_bit(wIndex, &ehci->suspended_ports);
                        break;
                case USB_PORT_FEAT_POWER:
-                       if (HCS_PPC (ehci->hcs_params))
-                               ehci_writel(ehci, temp | PORT_POWER,
-                                               status_reg);
+                       if (HCS_PPC(ehci->hcs_params)) {
+                               spin_unlock_irqrestore(&ehci->lock, flags);
+                               hcd->driver->port_power(hcd, wIndex, true);
+                               spin_lock_irqsave(&ehci->lock, flags);
+                       }
                        break;
                case USB_PORT_FEAT_RESET:
                        if (temp & (PORT_SUSPEND|PORT_RESUME))
@@ -1297,3 +1303,18 @@ static int ehci_port_handed_over(struct usb_hcd *hcd, 
int portnum)
        reg = &ehci->regs->port_status[portnum - 1];
        return ehci_readl(ehci, reg) & PORT_OWNER;
 }
+
+int ehci_port_power(struct usb_hcd *hcd, int portnum, bool enable)
+{
+       struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+       u32 __iomem *status_reg = &ehci->regs->port_status[portnum];
+       u32 status = ehci_readl(ehci, status_reg);
+
+       if (enable)
+               ehci_writel(ehci, status | PORT_POWER, status_reg);
+       else
+               ehci_writel(ehci, status & ~PORT_POWER, status_reg);
+
+       return 0;
+}
+EXPORT_SYMBOL(ehci_port_power);
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index eee228a..a7dc2c1 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -859,6 +859,8 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd 
*ehci, const __hc32 *x)
 struct ehci_driver_overrides {
        size_t          extra_priv_size;
        int             (*reset)(struct usb_hcd *hcd);
+       int             (*port_power)(struct usb_hcd *hcd,
+                               int portnum, bool enable);
 };
 
 extern void    ehci_init_driver(struct hc_driver *drv,
@@ -874,5 +876,6 @@ extern int  ehci_resume(struct usb_hcd *hcd, bool 
hibernated);
 
 extern int     ehci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                                 u16 wIndex, char *buf, u16 wLength);
+extern int     ehci_port_power(struct usb_hcd *hcd, int portnum, bool enable);
 
 #endif /* __LINUX_EHCI_HCD_H */
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index cd96a2b..39b5898 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -379,6 +379,9 @@ struct hc_driver {
        int     (*disable_usb3_lpm_timeout)(struct usb_hcd *,
                        struct usb_device *, enum usb3_link_state state);
        int     (*find_raw_port_number)(struct usb_hcd *, int);
+       /* Call for power on/off the port */
+       int     (*port_power)(struct usb_hcd *hcd, int portnum, bool enable);
+
 };
 
 static inline int hcd_giveback_urb_in_bh(struct usb_hcd *hcd)
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to