Commit 796bcae7 authored by Vitaly Bordug's avatar Vitaly Bordug Committed by Greg Kroah-Hartman

USB: powerpc: Workaround for the PPC440EPX USBH_23 errata [take 3]

A published errata for ppc440epx states, that when running Linux with
both EHCI and OHCI modules loaded, the EHCI module experiences a fatal
error when a high-speed device is connected to the USB2.0, and
functions normally if OHCI module is not loaded.

There used to be recommendation to use only hi-speed or full-speed
devices with specific conditions, when respective module was unloaded.
Later, it was observed that ohci suspend is enough to keep things
going, and it was turned into workaround, as explained below.

Quote from original descriprion:

The 440EPx USB 2.0 Host controller is an EHCI compliant controller.  In
USB 2.0 Host controllers, each EHCI controller has one or more companion
controllers, which may be OHCI or UHCI.  An USB 2.0 Host controller will
contain one or more ports.  For each port, only one of the controllers
is connected at any one time. In the 440EPx, there is only one OHCI
companion controller, and only one USB 2.0 Host port.
All ports on an USB 2.0 controller default to the companion
controller.  If you load only an ohci driver, it will have control of
the ports and any deviceplugged in will operate, although high speed
devices will be forced to operate at full speed.  When an ehci driver
is loaded, it explicitly takes control of the ports.  If there is a
device connected, and / or every time there is a new device connected,
the ehci driver determines if the device is high speed or not.  If it
is high speed, the driver retains control of the port.  If it is not,
the driver explicitly gives the companion controller control of the
port.

The is a software workaround that uses
Initial version of the software workaround was posted to
linux-usb-devel:

http://www.mail-archive.com/linux-usb-devel@lists.sourceforge.net/msg54019.html

and later available from amcc.com:
http://www.amcc.com/Embedded/Downloads/download.html?cat=1&family=15&ins=2

The patch below is generally based on the latter, but reworked to
powerpc/of_device USB drivers, and uses a few devicetree inquiries to
get rid of (some) hardcoded defines.
Signed-off-by: default avatarVitaly Bordug <vitb@kernel.crashing.org>
Signed-off-by: default avatarStefan Roese <sr@denx.de>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent cd40c4c4
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
}; };
USB1: usb@e0000400 { USB1: usb@e0000400 {
compatible = "ohci-be"; compatible = "ibm,usb-ohci-440epx", "ohci-be";
reg = <0x00000000 0xe0000400 0x00000060>; reg = <0x00000000 0xe0000400 0x00000060>;
interrupt-parent = <&UIC0>; interrupt-parent = <&UIC0>;
interrupts = <0x15 0x8>; interrupts = <0x15 0x8>;
......
...@@ -434,8 +434,15 @@ static int check_reset_complete ( ...@@ -434,8 +434,15 @@ static int check_reset_complete (
port_status &= ~PORT_RWC_BITS; port_status &= ~PORT_RWC_BITS;
ehci_writel(ehci, port_status, status_reg); ehci_writel(ehci, port_status, status_reg);
} else /* ensure 440EPX ohci controller state is operational */
if (ehci->has_amcc_usb23)
set_ohci_hcfs(ehci, 1);
} else {
ehci_dbg (ehci, "port %d high speed\n", index + 1); ehci_dbg (ehci, "port %d high speed\n", index + 1);
/* ensure 440EPx ohci controller state is suspended */
if (ehci->has_amcc_usb23)
set_ohci_hcfs(ehci, 0);
}
return port_status; return port_status;
} }
......
...@@ -107,11 +107,13 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) ...@@ -107,11 +107,13 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match)
{ {
struct device_node *dn = op->node; struct device_node *dn = op->node;
struct usb_hcd *hcd; struct usb_hcd *hcd;
struct ehci_hcd *ehci; struct ehci_hcd *ehci = NULL;
struct resource res; struct resource res;
int irq; int irq;
int rv; int rv;
struct device_node *np;
if (usb_disabled()) if (usb_disabled())
return -ENODEV; return -ENODEV;
...@@ -149,6 +151,20 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) ...@@ -149,6 +151,20 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match)
} }
ehci = hcd_to_ehci(hcd); ehci = hcd_to_ehci(hcd);
np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx");
if (np != NULL) {
/* claim we really affected by usb23 erratum */
if (!of_address_to_resource(np, 0, &res))
ehci->ohci_hcctrl_reg = ioremap(res.start +
OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN);
else
pr_debug(__FILE__ ": no ohci offset in fdt\n");
if (!ehci->ohci_hcctrl_reg) {
pr_debug(__FILE__ ": ioremap for ohci hcctrl failed\n");
} else {
ehci->has_amcc_usb23 = 1;
}
}
if (of_get_property(dn, "big-endian", NULL)) { if (of_get_property(dn, "big-endian", NULL)) {
ehci->big_endian_mmio = 1; ehci->big_endian_mmio = 1;
...@@ -181,6 +197,9 @@ err_ioremap: ...@@ -181,6 +197,9 @@ err_ioremap:
irq_dispose_mapping(irq); irq_dispose_mapping(irq);
err_irq: err_irq:
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
if (ehci->has_amcc_usb23)
iounmap(ehci->ohci_hcctrl_reg);
err_rmr: err_rmr:
usb_put_hcd(hcd); usb_put_hcd(hcd);
...@@ -191,6 +210,11 @@ err_rmr: ...@@ -191,6 +210,11 @@ err_rmr:
static int ehci_hcd_ppc_of_remove(struct of_device *op) static int ehci_hcd_ppc_of_remove(struct of_device *op)
{ {
struct usb_hcd *hcd = dev_get_drvdata(&op->dev); struct usb_hcd *hcd = dev_get_drvdata(&op->dev);
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct device_node *np;
struct resource res;
dev_set_drvdata(&op->dev, NULL); dev_set_drvdata(&op->dev, NULL);
dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n");
...@@ -201,6 +225,25 @@ static int ehci_hcd_ppc_of_remove(struct of_device *op) ...@@ -201,6 +225,25 @@ static int ehci_hcd_ppc_of_remove(struct of_device *op)
irq_dispose_mapping(hcd->irq); irq_dispose_mapping(hcd->irq);
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
/* use request_mem_region to test if the ohci driver is loaded. if so
* ensure the ohci core is operational.
*/
if (ehci->has_amcc_usb23) {
np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx");
if (np != NULL) {
if (!of_address_to_resource(np, 0, &res))
if (!request_mem_region(res.start,
0x4, hcd_name))
set_ohci_hcfs(ehci, 1);
else
release_mem_region(res.start, 0x4);
else
pr_debug(__FILE__ ": no ohci offset in fdt\n");
of_node_put(np);
}
iounmap(ehci->ohci_hcctrl_reg);
}
usb_put_hcd(hcd); usb_put_hcd(hcd);
return 0; return 0;
......
...@@ -120,6 +120,16 @@ struct ehci_hcd { /* one per controller */ ...@@ -120,6 +120,16 @@ struct ehci_hcd { /* one per controller */
unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned has_fsl_port_bug:1; /* FreeScale */
unsigned big_endian_mmio:1; unsigned big_endian_mmio:1;
unsigned big_endian_desc:1; unsigned big_endian_desc:1;
unsigned has_amcc_usb23:1;
/* required for usb32 quirk */
#define OHCI_CTRL_HCFS (3 << 6)
#define OHCI_USB_OPER (2 << 6)
#define OHCI_USB_SUSPEND (3 << 6)
#define OHCI_HCCTRL_OFFSET 0x4
#define OHCI_HCCTRL_LEN 0x4
__hc32 *ohci_hcctrl_reg;
u8 sbrn; /* packed release number */ u8 sbrn; /* packed release number */
...@@ -636,6 +646,30 @@ static inline void ehci_writel(const struct ehci_hcd *ehci, ...@@ -636,6 +646,30 @@ static inline void ehci_writel(const struct ehci_hcd *ehci,
#endif #endif
} }
/*
* On certain ppc-44x SoC there is a HW issue, that could only worked around with
* explicit suspend/operate of OHCI. This function hereby makes sense only on that arch.
* Other common bits are dependant on has_amcc_usb23 quirk flag.
*/
#ifdef CONFIG_44x
static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational)
{
u32 hc_control;
hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS);
if (operational)
hc_control |= OHCI_USB_OPER;
else
hc_control |= OHCI_USB_SUSPEND;
writel_be(hc_control, ehci->ohci_hcctrl_reg);
(void) readl_be(ehci->ohci_hcctrl_reg);
}
#else
static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational)
{ }
#endif
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* /*
......
...@@ -91,6 +91,7 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) ...@@ -91,6 +91,7 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match)
int rv; int rv;
int is_bigendian; int is_bigendian;
struct device_node *np;
if (usb_disabled()) if (usb_disabled())
return -ENODEV; return -ENODEV;
...@@ -147,6 +148,30 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) ...@@ -147,6 +148,30 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match)
if (rv == 0) if (rv == 0)
return 0; return 0;
/* by now, 440epx is known to show usb_23 erratum */
np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx");
/* Work around - At this point ohci_run has executed, the
* controller is running, everything, the root ports, etc., is
* set up. If the ehci driver is loaded, put the ohci core in
* the suspended state. The ehci driver will bring it out of
* suspended state when / if a non-high speed USB device is
* attached to the USB Host port. If the ehci driver is not
* loaded, do nothing. request_mem_region is used to test if
* the ehci driver is loaded.
*/
if (np != NULL) {
if (!of_address_to_resource(np, 0, &res)) {
if (!request_mem_region(res.start, 0x4, hcd_name)) {
writel_be((readl_be(&ohci->regs->control) |
OHCI_USB_SUSPEND), &ohci->regs->control);
(void) readl_be(&ohci->regs->control);
} else
release_mem_region(res.start, 0x4);
} else
pr_debug(__FILE__ ": cannot get ehci offset from fdt\n");
}
iounmap(hcd->regs); iounmap(hcd->regs);
err_ioremap: err_ioremap:
irq_dispose_mapping(irq); irq_dispose_mapping(irq);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment