From: Michael Grzeschik <m.grzeschik@xxxxxxxxxxxxxx> 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.grzeschik@xxxxxxxxxxxxxx> Signed-off-by: Peter Chen <peter.chen@xxxxxxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html