Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the libpayload project. |
| 3 | * |
| 4 | * Copyright (C) 2010 Patrick Georgi |
| 5 | * |
| 6 | * Redistribution and use in source and binary forms, with or without |
| 7 | * modification, are permitted provided that the following conditions |
| 8 | * are met: |
| 9 | * 1. Redistributions of source code must retain the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer. |
| 11 | * 2. Redistributions in binary form must reproduce the above copyright |
| 12 | * notice, this list of conditions and the following disclaimer in the |
| 13 | * documentation and/or other materials provided with the distribution. |
| 14 | * 3. The name of the author may not be used to endorse or promote products |
| 15 | * derived from this software without specific prior written permission. |
| 16 | * |
| 17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 27 | * SUCH DAMAGE. |
| 28 | */ |
| 29 | |
| 30 | //#define USB_DEBUG |
| 31 | |
| 32 | #include <libpayload.h> |
| 33 | #include "ohci_private.h" |
| 34 | #include "ohci.h" |
| 35 | |
| 36 | typedef struct { |
| 37 | int numports; |
| 38 | int *port; |
| 39 | } rh_inst_t; |
| 40 | |
| 41 | #define RH_INST(dev) ((rh_inst_t*)(dev)->data) |
| 42 | |
| 43 | static void |
| 44 | ohci_rh_enable_port (usbdev_t *dev, int port) |
| 45 | { |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 46 | /* Reset RH port should hold 50ms with pulses of at least 10ms and |
| 47 | * gaps of at most 3ms (usb20 spec 7.1.7.5). |
| 48 | * After reset, the port will be enabled automatically (ohci spec |
| 49 | * 7.4.4). |
| 50 | */ |
Nico Huber | e8a71d3 | 2012-11-23 11:52:18 +0100 | [diff] [blame] | 51 | int total_delay = 100; /* 100 * 500us == 50ms */ |
| 52 | while (total_delay > 0) { |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 53 | if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] |
| 54 | & CurrentConnectStatus)) |
| 55 | return; |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 56 | |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 57 | /* start reset */ |
| 58 | OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = |
| 59 | SetPortReset; |
| 60 | int timeout = 200; /* timeout after 200 * 500us == 100ms */ |
| 61 | while ((OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] |
| 62 | & PortResetStatus) |
| 63 | && timeout--) { |
Nico Huber | e8a71d3 | 2012-11-23 11:52:18 +0100 | [diff] [blame] | 64 | udelay(500); total_delay--; |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 65 | } |
| 66 | if (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] |
| 67 | & PortResetStatus) { |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 68 | usb_debug("Warning: root-hub port reset timed out.\n"); |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 69 | break; |
| 70 | } |
| 71 | if ((200-timeout) < 20) |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 72 | usb_debug("Warning: port reset too short: %dms; " |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 73 | "should be at least 10ms.\n", |
| 74 | (200-timeout)/2); |
| 75 | /* clear reset status change */ |
| 76 | OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = |
| 77 | PortResetStatusChange; |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 78 | usb_debug ("rh port reset finished after %dms.\n", (200-timeout)/2); |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 79 | } |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 80 | } |
| 81 | |
| 82 | /* disable root hub */ |
| 83 | static void |
| 84 | ohci_rh_disable_port (usbdev_t *dev, int port) |
| 85 | { |
Julius Werner | 7234d60 | 2014-04-08 12:54:25 -0700 | [diff] [blame^] | 86 | if (RH_INST (dev)->port[port] != -1) { |
| 87 | usb_detach_device(dev->controller, RH_INST (dev)->port[port]); |
| 88 | RH_INST (dev)->port[port] = -1; |
| 89 | } |
| 90 | |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 91 | OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ClearPortEnable; // disable port |
Nico Huber | afe86c0 | 2012-05-21 14:46:26 +0200 | [diff] [blame] | 92 | int timeout = 50; /* timeout after 50 * 100us == 5ms */ |
| 93 | while ((OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] |
| 94 | & PortEnableStatus) |
| 95 | && timeout--) { |
| 96 | udelay(100); |
| 97 | } |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | static void |
| 101 | ohci_rh_scanport (usbdev_t *dev, int port) |
| 102 | { |
| 103 | if (port >= RH_INST(dev)->numports) { |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 104 | usb_debug("Invalid port %d\n", port); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 105 | return; |
| 106 | } |
| 107 | |
| 108 | /* device registered, and device change logged, so something must have happened */ |
| 109 | if (RH_INST (dev)->port[port] != -1) { |
| 110 | usb_detach_device(dev->controller, RH_INST (dev)->port[port]); |
| 111 | RH_INST (dev)->port[port] = -1; |
| 112 | } |
| 113 | |
| 114 | /* no device attached |
| 115 | previously registered devices are detached, nothing left to do */ |
| 116 | if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & CurrentConnectStatus)) |
| 117 | return; |
| 118 | |
| 119 | OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ConnectStatusChange; // clear port state change |
| 120 | ohci_rh_enable_port (dev, port); |
| 121 | |
| 122 | mdelay(100); // wait for signal to stabilize |
| 123 | |
| 124 | if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) { |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 125 | usb_debug ("port enable failed\n"); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 126 | return; |
| 127 | } |
| 128 | |
Julius Werner | e00ba21 | 2013-09-24 20:03:54 -0700 | [diff] [blame] | 129 | usb_speed speed = (OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & LowSpeedDeviceAttached) != 0; |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 130 | RH_INST (dev)->port[port] = usb_attach_device(dev->controller, dev->address, port, speed); |
| 131 | } |
| 132 | |
| 133 | static int |
| 134 | ohci_rh_report_port_changes (usbdev_t *dev) |
| 135 | { |
Nico Huber | b9917c2 | 2012-11-22 15:45:48 +0100 | [diff] [blame] | 136 | ohci_t *const ohcic = OHCI_INST (dev->controller); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 137 | |
Nico Huber | b9917c2 | 2012-11-22 15:45:48 +0100 | [diff] [blame] | 138 | int i; |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 139 | |
| 140 | for (i = 0; i < RH_INST(dev)->numports; i++) { |
| 141 | // maybe detach+attach happened between two scans? |
Nico Huber | b9917c2 | 2012-11-22 15:45:48 +0100 | [diff] [blame] | 142 | if (ohcic->opreg->HcRhPortStatus[i] & ConnectStatusChange) { |
| 143 | ohcic->opreg->HcRhPortStatus[i] = ConnectStatusChange; |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 144 | usb_debug("attachment change on port %d\n", i); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 145 | return i; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | // no change |
| 150 | return -1; |
| 151 | } |
| 152 | |
| 153 | static void |
| 154 | ohci_rh_destroy (usbdev_t *dev) |
| 155 | { |
| 156 | int i; |
| 157 | for (i = 0; i < RH_INST (dev)->numports; i++) |
| 158 | ohci_rh_disable_port (dev, i); |
| 159 | free (RH_INST (dev)); |
| 160 | } |
| 161 | |
| 162 | static void |
| 163 | ohci_rh_poll (usbdev_t *dev) |
| 164 | { |
Nico Huber | b9917c2 | 2012-11-22 15:45:48 +0100 | [diff] [blame] | 165 | ohci_t *const ohcic = OHCI_INST (dev->controller); |
| 166 | |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 167 | int port; |
Nico Huber | b9917c2 | 2012-11-22 15:45:48 +0100 | [diff] [blame] | 168 | |
| 169 | /* Check if anything changed. */ |
| 170 | if (!(ohcic->opreg->HcInterruptStatus & RootHubStatusChange)) |
| 171 | return; |
| 172 | ohcic->opreg->HcInterruptStatus = RootHubStatusChange; |
| 173 | usb_debug("root hub status change\n"); |
| 174 | |
| 175 | /* Scan ports with changed connection status. */ |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 176 | while ((port = ohci_rh_report_port_changes (dev)) != -1) |
| 177 | ohci_rh_scanport (dev, port); |
| 178 | } |
| 179 | |
| 180 | void |
| 181 | ohci_rh_init (usbdev_t *dev) |
| 182 | { |
| 183 | int i; |
| 184 | |
| 185 | dev->destroy = ohci_rh_destroy; |
| 186 | dev->poll = ohci_rh_poll; |
| 187 | |
Julius Werner | 7234d60 | 2014-04-08 12:54:25 -0700 | [diff] [blame^] | 188 | dev->data = xmalloc (sizeof (rh_inst_t)); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 189 | RH_INST (dev)->numports = OHCI_INST (dev->controller)->opreg->HcRhDescriptorA & NumberDownstreamPortsMask; |
Julius Werner | 7234d60 | 2014-04-08 12:54:25 -0700 | [diff] [blame^] | 190 | RH_INST (dev)->port = xmalloc(sizeof(int) * RH_INST (dev)->numports); |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 191 | usb_debug("%d ports registered\n", RH_INST (dev)->numports); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 192 | |
| 193 | for (i = 0; i < RH_INST (dev)->numports; i++) { |
| 194 | ohci_rh_enable_port (dev, i); |
| 195 | RH_INST (dev)->port[i] = -1; |
| 196 | } |
| 197 | |
| 198 | /* we can set them here because a root hub _really_ shouldn't |
| 199 | appear elsewhere */ |
| 200 | dev->address = 0; |
| 201 | dev->hub = -1; |
| 202 | dev->port = -1; |
| 203 | |
Gabe Black | 93ded59 | 2012-11-01 15:44:10 -0700 | [diff] [blame] | 204 | usb_debug("rh init done\n"); |
Patrick Georgi | 6615ef3 | 2010-08-13 09:18:58 +0000 | [diff] [blame] | 205 | } |