/* * This file is part of the libpayload project. * * Copyright (C) 2010 coresystems GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ //#define USB_DEBUG #include <libpayload.h> #include <kconfig.h> #include "ehci.h" #include "ehci_private.h" typedef struct { int n_ports; /* typical C, n_ports is the number * of ports, while ports[] spans [0,n_ports-1], * even though the spec counts from 1. */ volatile portsc_t *ports; int *devices; } rh_inst_t; #define RH_INST(dev) ((rh_inst_t*)(dev)->data) static void ehci_rh_destroy (usbdev_t *dev) { int port; /* Tear down all devices below the root hub (in bottom-up order). */ for (port = 0; port < RH_INST(dev)->n_ports; port++) { if (RH_INST(dev)->devices[port] != -1) { usb_detach_device(dev->controller, RH_INST(dev)->devices[port]); RH_INST(dev)->devices[port] = -1; } } free (RH_INST(dev)->devices); free (RH_INST(dev)); } static void ehci_rh_hand_over_port (usbdev_t *dev, int port) { usb_debug("giving up port %x, it's USB1\n", port+1); /* Clear ConnectStatusChange before evaluation */ /* RW/C register, so clear it by writing 1 */ RH_INST(dev)->ports[port] |= P_CONN_STATUS_CHANGE; /* Lowspeed device. Hand over to companion */ RH_INST(dev)->ports[port] |= P_PORT_OWNER; /* TOTEST: how long to wait? trying 100ms for now */ int timeout = 10; /* timeout after 10 * 10ms == 100ms */ while (!(RH_INST(dev)->ports[port] & P_CONN_STATUS_CHANGE) && timeout--) mdelay(10); if (!(RH_INST(dev)->ports[port] & P_CONN_STATUS_CHANGE)) { usb_debug("Warning: Handing port over to companion timed out.\n"); } /* RW/C register, so clear it by writing 1 */ RH_INST(dev)->ports[port] |= P_CONN_STATUS_CHANGE; return; } static void ehci_rh_scanport (usbdev_t *dev, int port) { usb_speed port_speed; if (RH_INST(dev)->devices[port]!=-1) { usb_debug("Unregister device at port %x\n", port+1); usb_detach_device(dev->controller, RH_INST(dev)->devices[port]); RH_INST(dev)->devices[port]=-1; } /* device connected, handle */ if (RH_INST(dev)->ports[port] & P_CURR_CONN_STATUS) { mdelay(100); // usb20 spec 9.1.2 if (!IS_ENABLED(CONFIG_LP_USB_EHCI_HOSTPC_ROOT_HUB_TT) && (RH_INST(dev)->ports[port] & P_LINE_STATUS) == P_LINE_STATUS_LOWSPEED) { ehci_rh_hand_over_port(dev, port); return; } /* Deassert enable, assert reset. These must change * atomically. */ RH_INST(dev)->ports[port] = (RH_INST(dev)->ports[port] & ~P_PORT_ENABLE) | P_PORT_RESET; /* Wait a bit while reset is active (+1 to avoid Tegra race). */ mdelay(50 + 1); // usb20 spec 7.1.7.5 (TDRSTR) /* Deassert reset. */ RH_INST(dev)->ports[port] &= ~P_PORT_RESET; /* Wait max. 2ms (ehci spec 2.3.9) for flag change to finish. */ int timeout = 20; /* time out after 20 * 100us == 2ms */ while ((RH_INST(dev)->ports[port] & P_PORT_RESET) && timeout--) udelay(100); if (RH_INST(dev)->ports[port] & P_PORT_RESET) { usb_debug("Error: ehci_rh: port reset timed out.\n"); return; } mdelay(10); /* TRSTRCY (USB 2.0 spec 7.1.7.5) */ /* If the host controller enabled the port, it's a high-speed * device, otherwise it's full-speed. */ if (!(RH_INST(dev)->ports[port] & P_PORT_ENABLE)) { ehci_rh_hand_over_port(dev, port); return; } if (IS_ENABLED(CONFIG_LP_USB_EHCI_HOSTPC_ROOT_HUB_TT)) { port_speed = (usb_speed) ((EHCI_INST(dev->controller)->operation->hostpc >> 25) & 0x03); } else { usb_debug("port %x hosts a USB2 device\n", port+1); port_speed = HIGH_SPEED; } RH_INST(dev)->devices[port] = usb_attach_device(dev->controller , dev->address, port, port_speed); } /* RW/C register, so clear it by writing 1 */ RH_INST(dev)->ports[port] |= P_CONN_STATUS_CHANGE; } static int ehci_rh_report_port_changes (usbdev_t *dev) { int i; for (i=0; i<RH_INST(dev)->n_ports; i++) { if (RH_INST(dev)->ports[i] & P_CONN_STATUS_CHANGE) return i; } return -1; } static void ehci_rh_poll (usbdev_t *dev) { int port; while ((port = ehci_rh_report_port_changes (dev)) != -1) ehci_rh_scanport (dev, port); } void ehci_rh_init (usbdev_t *dev) { int i; dev->destroy = ehci_rh_destroy; dev->poll = ehci_rh_poll; dev->data = xmalloc(sizeof(rh_inst_t)); RH_INST(dev)->n_ports = EHCI_INST(dev->controller)->capabilities->hcsparams & HCS_NPORTS_MASK; RH_INST(dev)->ports = EHCI_INST(dev->controller)->operation->portsc; RH_INST(dev)->devices = xmalloc(RH_INST(dev)->n_ports * sizeof(int)); usb_debug("root hub has %x ports\n", RH_INST(dev)->n_ports); /* If the host controller has port power control, enable power on * all ports and wait 20ms. */ if (EHCI_INST(dev->controller)->capabilities->hcsparams & HCS_PORT_POWER_CONTROL) { usb_debug("host controller has port power control, " "giving power to all ports.\n"); for (i=0; i < RH_INST(dev)->n_ports; i++) RH_INST(dev)->ports[i] |= P_PP; } mdelay(20); // ehci spec 2.3.9 dev->speed = HIGH_SPEED; dev->address = 0; dev->hub = -1; dev->port = -1; for (i=0; i < RH_INST(dev)->n_ports; i++) { RH_INST(dev)->devices[i] = -1; ehci_rh_scanport(dev, i); } }