The patch includes both API change and caller change. The main changes like below:
- add notify_suspend/notify_resume callback This let usb phy driver has the chance to change hw settings during the controller suspend/resume procedure. Besides, old parameter "port" is useless for phy notify, as one usb phy is only for one usb port. New parameter "speed" stands for the device's speed which is on the port. - implement notify_suspend/notify_resume callback for mxs phy driver These notify will be called during the bus suspend/resume procedure. - Add phy notify at suspend/resume procedure for chipidea host driver - refine phy notify operation during connection and disconnection The history of this problem like below: At some i.mx SoCs, when controller works at host mode, the PHY register needs to be changed at device connect, disconnect, bus suspend and resume due to the SoC limitations. The phy notification should be added according to below rules: 1. Only set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during high speed host mode. 2. Do not set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during the reset and speed negotiation period. 3. Do not set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during host suspend/resume sequence. Please refer: i.mx23RM(page 413) for detail. http://www.freescale.com/files/dsp/doc/ref_manual/IMX23RM.pdf Freescale i.MX SoC, i.mx23, i.mx28 and i.mx6(i.mx6SL does not need to follow the 3rd rule) need to follow above rules. The correct notification setting method should be: 1. Set connect notify after the second bus reset. 2. Set disconnect notify after disconnection. 3. Set suspend nofity after bus goes to suspend (portsc.suspendM=1). 4. Set resume notify after resume (portsc.fpr=0). Signed-off-by: Peter Chen <peter.c...@freescale.com> Tested-by: Mike Thompson <mpthomp...@gmail.com> --- Changes for v4: - Delete useless lines which forgot to delete at last patch. Changes for v3: - In order to fix git bitsec error, merge .h and .c to one commit - The phy notify only needs to be done when udev has existed at hub.c Changes for v2: - Add Tested-by: Mike Thompson <mpthomp...@gmail.com> --- drivers/usb/chipidea/host.c | 75 +++++++++++++++++++++++++++++++++++++++++- drivers/usb/core/hub.c | 16 ++++---- drivers/usb/otg/mxs-phy.c | 56 +++++++++++++++++++++++++------- include/linux/usb/phy.h | 44 +++++++++++++++++++++---- 4 files changed, 162 insertions(+), 29 deletions(-) diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index ebff9f4..74a6d57 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -23,6 +23,7 @@ #include <linux/usb.h> #include <linux/usb/hcd.h> #include <linux/usb/chipidea.h> +#include <linux/usb/otg.h> #define CHIPIDEA_EHCI #include "../host/ehci-hcd.c" @@ -46,6 +47,76 @@ static int ci_ehci_setup(struct usb_hcd *hcd) return ret; } +static enum usb_device_speed ci_get_device_speed(struct ehci_hcd *ehci, + u32 portsc) +{ + if (ehci_is_TDI(ehci)) { + switch ((portsc >> (ehci->has_hostpc ? 25 : 26)) & 3) { + case 0: + return USB_SPEED_FULL; + case 1: + return USB_SPEED_LOW; + case 2: + return USB_SPEED_HIGH; + default: + return USB_SPEED_UNKNOWN; + } + } else { + return USB_SPEED_HIGH; + } +} + +static int ci_bus_suspend(struct usb_hcd *hcd) +{ + int ret; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int port; + + ret = ehci_bus_suspend(hcd); + if (ret) + return ret; + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 portsc = ehci_readl(ehci, reg); + + if (portsc & PORT_CONNECT) { + enum usb_device_speed speed; + speed = ci_get_device_speed(ehci, portsc); + /* notify the USB PHY */ + if (hcd->phy) + usb_phy_notify_suspend(hcd->phy, speed); + } + } + + return ret; +} + +static int ci_bus_resume(struct usb_hcd *hcd) +{ + int ret; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int port; + + ret = ehci_bus_resume(hcd); + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 portsc = ehci_readl(ehci, reg); + + if (portsc & PORT_CONNECT) { + enum usb_device_speed speed; + speed = ci_get_device_speed(ehci, portsc); + /* notify the USB PHY */ + if (hcd->phy) + usb_phy_notify_resume(hcd->phy, speed); + } + } + + return ret; +} static const struct hc_driver ci_ehci_hc_driver = { .description = "ehci_hcd", @@ -84,8 +155,8 @@ static const struct hc_driver ci_ehci_hc_driver = { */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, - .bus_suspend = ehci_bus_suspend, - .bus_resume = ehci_bus_resume, + .bus_suspend = ci_bus_suspend, + .bus_resume = ci_bus_resume, .relinquish_port = ehci_relinquish_port, .port_handed_over = ehci_port_handed_over, diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 6dc41c6..7b4062d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3994,6 +3994,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, if (retval) goto fail; + if (hcd->phy && !hdev->parent) + usb_phy_notify_connect(hcd->phy, udev->speed); + /* * Some superspeed devices have finished the link training process * and attached to a superspeed hub port, but the device descriptor @@ -4188,8 +4191,12 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, } /* Disconnect any existing devices under this port */ - if (udev) + if (udev) { + if (hcd->phy && !hdev->parent && + !(portstatus & USB_PORT_STAT_CONNECTION)) + usb_phy_notify_disconnect(hcd->phy, udev->speed); usb_disconnect(&hub->ports[port1 - 1]->child); + } clear_bit(port1, hub->change_bits); /* We can forget about a "removed" device when there's a physical @@ -4212,13 +4219,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, } } - if (hcd->phy && !hdev->parent) { - if (portstatus & USB_PORT_STAT_CONNECTION) - usb_phy_notify_connect(hcd->phy, port1); - else - usb_phy_notify_disconnect(hcd->phy, port1); - } - /* Return now if debouncing failed or nothing is connected or * the device was "removed". */ diff --git a/drivers/usb/otg/mxs-phy.c b/drivers/usb/otg/mxs-phy.c index 88db976..41e0543 100644 --- a/drivers/usb/otg/mxs-phy.c +++ b/drivers/usb/otg/mxs-phy.c @@ -96,39 +96,69 @@ static void mxs_phy_enhostdiscondetect_delay(struct work_struct *ws) mxs_phy->phy.io_priv + HW_USBPHY_CTRL_SET); } -static int mxs_phy_on_connect(struct usb_phy *phy, int port) +static int mxs_phy_on_connect(struct usb_phy *phy, + enum usb_device_speed speed) { struct mxs_phy *mxs_phy = to_mxs_phy(phy); - dev_dbg(phy->dev, "Connect on port %d\n", port); - - mxs_phy_hw_init(mxs_phy); + dev_dbg(phy->dev, "%s speed device has connected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); /* * Delay enabling ENHOSTDISCONDETECT so that connection and * reset processing can be completed for the root hub. */ - dev_dbg(phy->dev, "Delaying setting ENHOSTDISCONDETECT\n"); - PREPARE_DELAYED_WORK(&mxs_phy->enhostdiscondetect_work, + if (speed == USB_SPEED_HIGH) { + PREPARE_DELAYED_WORK(&mxs_phy->enhostdiscondetect_work, mxs_phy_enhostdiscondetect_delay); - schedule_delayed_work(&mxs_phy->enhostdiscondetect_work, + schedule_delayed_work(&mxs_phy->enhostdiscondetect_work, msecs_to_jiffies(MXY_PHY_ENHOSTDISCONDETECT_DELAY)); + } return 0; } -static int mxs_phy_on_disconnect(struct usb_phy *phy, int port) +static int mxs_phy_on_disconnect(struct usb_phy *phy, + enum usb_device_speed speed) { - dev_dbg(phy->dev, "Disconnect on port %d\n", port); + dev_dbg(phy->dev, "%s speed device has disconnected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); - /* No need to delay before clearing ENHOSTDISCONDETECT. */ - dev_dbg(phy->dev, "Clearing ENHOSTDISCONDETECT\n"); - writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + if (speed == USB_SPEED_HIGH) { + /* No need to delay before clearing ENHOSTDISCONDETECT. */ + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); + } + + return 0; +} + +static int mxs_phy_on_suspend(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "At suspend, %s speed device on the port\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, phy->io_priv + HW_USBPHY_CTRL_CLR); return 0; } +static int mxs_phy_on_resume(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "after resume, %s speed device on the port\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); + + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_SET); + + return 0; +} + static int mxs_phy_probe(struct platform_device *pdev) { struct resource *res; @@ -166,6 +196,8 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.shutdown = mxs_phy_shutdown; mxs_phy->phy.notify_connect = mxs_phy_on_connect; mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect; + mxs_phy->phy.notify_suspend = mxs_phy_on_suspend; + mxs_phy->phy.notify_resume = mxs_phy_on_resume; ATOMIC_INIT_NOTIFIER_HEAD(&mxs_phy->phy.notifier); diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 06b5bae..45e2235 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -10,6 +10,7 @@ #define __LINUX_USB_PHY_H #include <linux/notifier.h> +#include <linux/usb.h> enum usb_phy_events { USB_EVENT_NONE, /* no events or cable disconnected */ @@ -98,9 +99,20 @@ struct usb_phy { int (*set_suspend)(struct usb_phy *x, int suspend); - /* notify phy connect status change */ - int (*notify_connect)(struct usb_phy *x, int port); - int (*notify_disconnect)(struct usb_phy *x, int port); + /* + * Notify phy that + * - The controller's connect status change. + * - The controller's suspend/resume occurs, and the device + * is on the port. + */ + int (*notify_connect)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_disconnect)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_suspend)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_resume)(struct usb_phy *x, + enum usb_device_speed speed); }; @@ -189,19 +201,37 @@ usb_phy_set_suspend(struct usb_phy *x, int suspend) } static inline int -usb_phy_notify_connect(struct usb_phy *x, int port) +usb_phy_notify_connect(struct usb_phy *x, enum usb_device_speed speed) { if (x->notify_connect) - return x->notify_connect(x, port); + return x->notify_connect(x, speed); else return 0; } static inline int -usb_phy_notify_disconnect(struct usb_phy *x, int port) +usb_phy_notify_disconnect(struct usb_phy *x, enum usb_device_speed speed) { if (x->notify_disconnect) - return x->notify_disconnect(x, port); + return x->notify_disconnect(x, speed); + else + return 0; +} + +static inline int +usb_phy_notify_suspend(struct usb_phy *x, enum usb_device_speed speed) +{ + if (x->notify_suspend) + return x->notify_suspend(x, speed); + else + return 0; +} + +static inline int +usb_phy_notify_resume(struct usb_phy *x, enum usb_device_speed speed) +{ + if (x->notify_resume) + return x->notify_resume(x, speed); else return 0; } -- 1.7.0.4 -- 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