On Tue, Oct 16, 2012 at 09:36:46AM +0800, Peter Chen wrote: Hi Felipe & Alex,
Do you have any comments for this patch? > 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 > -- Best Regards, Peter Chen -- 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