Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: Vitaly Bordug <vitb@kernel.crashing.org>
Signed-off-by: Stefan Roese <sr@denx.de>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

authored by

Vitaly Bordug and committed by
Greg Kroah-Hartman
796bcae7 cd40c4c4

+112 -3
+1 -1
arch/powerpc/boot/dts/sequoia.dts
··· 134 134 }; 135 135 136 136 USB1: usb@e0000400 { 137 - compatible = "ohci-be"; 137 + compatible = "ibm,usb-ohci-440epx", "ohci-be"; 138 138 reg = <0x00000000 0xe0000400 0x00000060>; 139 139 interrupt-parent = <&UIC0>; 140 140 interrupts = <0x15 0x8>;
+8 -1
drivers/usb/host/ehci-hub.c
··· 434 434 port_status &= ~PORT_RWC_BITS; 435 435 ehci_writel(ehci, port_status, status_reg); 436 436 437 - } else 437 + /* ensure 440EPX ohci controller state is operational */ 438 + if (ehci->has_amcc_usb23) 439 + set_ohci_hcfs(ehci, 1); 440 + } else { 438 441 ehci_dbg (ehci, "port %d high speed\n", index + 1); 442 + /* ensure 440EPx ohci controller state is suspended */ 443 + if (ehci->has_amcc_usb23) 444 + set_ohci_hcfs(ehci, 0); 445 + } 439 446 440 447 return port_status; 441 448 }
+44 -1
drivers/usb/host/ehci-ppc-of.c
··· 107 107 { 108 108 struct device_node *dn = op->node; 109 109 struct usb_hcd *hcd; 110 - struct ehci_hcd *ehci; 110 + struct ehci_hcd *ehci = NULL; 111 111 struct resource res; 112 112 int irq; 113 113 int rv; 114 + 115 + struct device_node *np; 114 116 115 117 if (usb_disabled()) 116 118 return -ENODEV; ··· 151 149 } 152 150 153 151 ehci = hcd_to_ehci(hcd); 152 + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); 153 + if (np != NULL) { 154 + /* claim we really affected by usb23 erratum */ 155 + if (!of_address_to_resource(np, 0, &res)) 156 + ehci->ohci_hcctrl_reg = ioremap(res.start + 157 + OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN); 158 + else 159 + pr_debug(__FILE__ ": no ohci offset in fdt\n"); 160 + if (!ehci->ohci_hcctrl_reg) { 161 + pr_debug(__FILE__ ": ioremap for ohci hcctrl failed\n"); 162 + } else { 163 + ehci->has_amcc_usb23 = 1; 164 + } 165 + } 154 166 155 167 if (of_get_property(dn, "big-endian", NULL)) { 156 168 ehci->big_endian_mmio = 1; ··· 197 181 irq_dispose_mapping(irq); 198 182 err_irq: 199 183 release_mem_region(hcd->rsrc_start, hcd->rsrc_len); 184 + 185 + if (ehci->has_amcc_usb23) 186 + iounmap(ehci->ohci_hcctrl_reg); 200 187 err_rmr: 201 188 usb_put_hcd(hcd); 202 189 ··· 210 191 static int ehci_hcd_ppc_of_remove(struct of_device *op) 211 192 { 212 193 struct usb_hcd *hcd = dev_get_drvdata(&op->dev); 194 + struct ehci_hcd *ehci = hcd_to_ehci(hcd); 195 + 196 + struct device_node *np; 197 + struct resource res; 198 + 213 199 dev_set_drvdata(&op->dev, NULL); 214 200 215 201 dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); ··· 225 201 irq_dispose_mapping(hcd->irq); 226 202 release_mem_region(hcd->rsrc_start, hcd->rsrc_len); 227 203 204 + /* use request_mem_region to test if the ohci driver is loaded. if so 205 + * ensure the ohci core is operational. 206 + */ 207 + if (ehci->has_amcc_usb23) { 208 + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); 209 + if (np != NULL) { 210 + if (!of_address_to_resource(np, 0, &res)) 211 + if (!request_mem_region(res.start, 212 + 0x4, hcd_name)) 213 + set_ohci_hcfs(ehci, 1); 214 + else 215 + release_mem_region(res.start, 0x4); 216 + else 217 + pr_debug(__FILE__ ": no ohci offset in fdt\n"); 218 + of_node_put(np); 219 + } 220 + 221 + iounmap(ehci->ohci_hcctrl_reg); 222 + } 228 223 usb_put_hcd(hcd); 229 224 230 225 return 0;
+34
drivers/usb/host/ehci.h
··· 120 120 unsigned has_fsl_port_bug:1; /* FreeScale */ 121 121 unsigned big_endian_mmio:1; 122 122 unsigned big_endian_desc:1; 123 + unsigned has_amcc_usb23:1; 124 + 125 + /* required for usb32 quirk */ 126 + #define OHCI_CTRL_HCFS (3 << 6) 127 + #define OHCI_USB_OPER (2 << 6) 128 + #define OHCI_USB_SUSPEND (3 << 6) 129 + 130 + #define OHCI_HCCTRL_OFFSET 0x4 131 + #define OHCI_HCCTRL_LEN 0x4 132 + __hc32 *ohci_hcctrl_reg; 123 133 124 134 u8 sbrn; /* packed release number */ 125 135 ··· 645 635 writel(val, regs); 646 636 #endif 647 637 } 638 + 639 + /* 640 + * On certain ppc-44x SoC there is a HW issue, that could only worked around with 641 + * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. 642 + * Other common bits are dependant on has_amcc_usb23 quirk flag. 643 + */ 644 + #ifdef CONFIG_44x 645 + static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) 646 + { 647 + u32 hc_control; 648 + 649 + hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS); 650 + if (operational) 651 + hc_control |= OHCI_USB_OPER; 652 + else 653 + hc_control |= OHCI_USB_SUSPEND; 654 + 655 + writel_be(hc_control, ehci->ohci_hcctrl_reg); 656 + (void) readl_be(ehci->ohci_hcctrl_reg); 657 + } 658 + #else 659 + static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) 660 + { } 661 + #endif 648 662 649 663 /*-------------------------------------------------------------------------*/ 650 664
+25
drivers/usb/host/ohci-ppc-of.c
··· 91 91 92 92 int rv; 93 93 int is_bigendian; 94 + struct device_node *np; 94 95 95 96 if (usb_disabled()) 96 97 return -ENODEV; ··· 147 146 rv = usb_add_hcd(hcd, irq, IRQF_DISABLED); 148 147 if (rv == 0) 149 148 return 0; 149 + 150 + /* by now, 440epx is known to show usb_23 erratum */ 151 + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx"); 152 + 153 + /* Work around - At this point ohci_run has executed, the 154 + * controller is running, everything, the root ports, etc., is 155 + * set up. If the ehci driver is loaded, put the ohci core in 156 + * the suspended state. The ehci driver will bring it out of 157 + * suspended state when / if a non-high speed USB device is 158 + * attached to the USB Host port. If the ehci driver is not 159 + * loaded, do nothing. request_mem_region is used to test if 160 + * the ehci driver is loaded. 161 + */ 162 + if (np != NULL) { 163 + if (!of_address_to_resource(np, 0, &res)) { 164 + if (!request_mem_region(res.start, 0x4, hcd_name)) { 165 + writel_be((readl_be(&ohci->regs->control) | 166 + OHCI_USB_SUSPEND), &ohci->regs->control); 167 + (void) readl_be(&ohci->regs->control); 168 + } else 169 + release_mem_region(res.start, 0x4); 170 + } else 171 + pr_debug(__FILE__ ": cannot get ehci offset from fdt\n"); 172 + } 150 173 151 174 iounmap(hcd->regs); 152 175 err_ioremap: