usb: ehci: add ehci_port_power interface
authorMichael Grzeschik <m.grzeschik@pengutronix.de>
Mon, 13 Oct 2014 01:53:03 +0000 (09:53 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 3 Nov 2014 23:34:00 +0000 (15:34 -0800)
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 just toggled by the PORT_POWER
bit, and the gpio-regulator is needed to be toggled too.

This patch defines a port power control interface ehci_port_power for
ehci core use, it toggles PORT_POWER bit as well as calls platform
defined .port_power if it is defined.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Peter Chen <peter.chen@freescale.com>
Acked-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-hub.c
drivers/usb/host/ehci.h
include/linux/usb/hcd.h

index 15feaf924b71aea658fa8529bcea1f3c3d53256f..df75b8e7d1576b6868c03ce62d2647906c11596d 100644 (file)
@@ -311,6 +311,7 @@ static void unlink_empty_async_suspended(struct ehci_hcd *ehci);
 static void ehci_work(struct ehci_hcd *ehci);
 static void start_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh);
 static void end_unlink_intr(struct ehci_hcd *ehci, struct ehci_qh *qh);
+static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable);
 
 #include "ehci-timer.c"
 #include "ehci-hub.c"
@@ -329,9 +330,13 @@ static void ehci_turn_off_all_ports(struct ehci_hcd *ehci)
 {
        int     port = HCS_N_PORTS(ehci->hcs_params);
 
-       while (port--)
+       while (port--) {
                ehci_writel(ehci, PORT_RWC_BITS,
                                &ehci->regs->port_status[port]);
+               spin_unlock_irq(&ehci->lock);
+               ehci_port_power(ehci, port, false);
+               spin_lock_irq(&ehci->lock);
+       }
 }
 
 /*
@@ -1233,6 +1238,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);
index 5728829cf6ef0e631df75ba69d77ae04c5c26fbc..118edb7bdca288f2f2adf9f7783f3b54949e870d 100644 (file)
@@ -69,10 +69,8 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
                if (test_bit(port, &ehci->owned_ports)) {
                        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);
-                       }
+                       if (!(status & PORT_POWER))
+                               ehci_port_power(ehci, port, true);
                }
        }
 
@@ -952,9 +950,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);
+                               ehci_port_power(ehci, 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 +1004,9 @@ 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);
+                               spin_unlock_irqrestore(&ehci->lock, flags);
+                               ehci_port_power(ehci, wIndex, false);
+                               spin_lock_irqsave(&ehci->lock, flags);
                                temp = ehci_readl(ehci, status_reg);
                        }
                }
@@ -1187,9 +1187,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);
+                               ehci_port_power(ehci, wIndex, true);
+                               spin_lock_irqsave(&ehci->lock, flags);
+                       }
                        break;
                case USB_PORT_FEAT_RESET:
                        if (temp & (PORT_SUSPEND|PORT_RESUME))
@@ -1297,3 +1299,20 @@ 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;
 }
+
+static int ehci_port_power(struct ehci_hcd *ehci, int portnum, bool enable)
+{
+       struct usb_hcd *hcd = ehci_to_hcd(ehci);
+       u32 __iomem *status_reg = &ehci->regs->port_status[portnum];
+       u32 temp = ehci_readl(ehci, status_reg) & ~PORT_RWC_BITS;
+
+       if (enable)
+               ehci_writel(ehci, temp | PORT_POWER, status_reg);
+       else
+               ehci_writel(ehci, temp & ~PORT_POWER, status_reg);
+
+       if (hcd->driver->port_power)
+               hcd->driver->port_power(hcd, portnum, enable);
+
+       return 0;
+}
index eee228a26a0e71795b472218feefa7c5380e62d0..6f0577b0a5ae37de4acb99ec661553a78128a1b9 100644 (file)
@@ -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,
index cd96a2bc3388973f59b221f13187a30c4177b2d3..9cf7e3594609b20f4768daf661594fc3e6d146e2 100644 (file)
@@ -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 if necessary */
+       int     (*port_power)(struct usb_hcd *hcd, int portnum, bool enable);
+
 };
 
 static inline int hcd_giveback_urb_in_bh(struct usb_hcd *hcd)