blob: 9de2b981f81fad2dab88e104ff913ea3dc1fcddf [file] [log] [blame]
Patrick Georgi6615ef32010-08-13 09:18:58 +00001/*
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
36typedef struct {
37 int numports;
38 int *port;
39} rh_inst_t;
40
41#define RH_INST(dev) ((rh_inst_t*)(dev)->data)
42
43static void
44ohci_rh_enable_port (usbdev_t *dev, int port)
45{
Nico Huberafe86c02012-05-21 14:46:26 +020046 /* 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 Hubere8a71d32012-11-23 11:52:18 +010051 int total_delay = 100; /* 100 * 500us == 50ms */
52 while (total_delay > 0) {
Nico Huberafe86c02012-05-21 14:46:26 +020053 if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port]
54 & CurrentConnectStatus))
55 return;
Patrick Georgi6615ef32010-08-13 09:18:58 +000056
Nico Huberafe86c02012-05-21 14:46:26 +020057 /* 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 Hubere8a71d32012-11-23 11:52:18 +010064 udelay(500); total_delay--;
Nico Huberafe86c02012-05-21 14:46:26 +020065 }
66 if (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port]
67 & PortResetStatus) {
Gabe Black93ded592012-11-01 15:44:10 -070068 usb_debug("Warning: root-hub port reset timed out.\n");
Nico Huberafe86c02012-05-21 14:46:26 +020069 break;
70 }
71 if ((200-timeout) < 20)
Gabe Black93ded592012-11-01 15:44:10 -070072 usb_debug("Warning: port reset too short: %dms; "
Nico Huberafe86c02012-05-21 14:46:26 +020073 "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 Black93ded592012-11-01 15:44:10 -070078 usb_debug ("rh port reset finished after %dms.\n", (200-timeout)/2);
Nico Huberafe86c02012-05-21 14:46:26 +020079 }
Patrick Georgi6615ef32010-08-13 09:18:58 +000080}
81
82/* disable root hub */
83static void
84ohci_rh_disable_port (usbdev_t *dev, int port)
85{
86 OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ClearPortEnable; // disable port
Nico Huberafe86c02012-05-21 14:46:26 +020087 int timeout = 50; /* timeout after 50 * 100us == 5ms */
88 while ((OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port]
89 & PortEnableStatus)
90 && timeout--) {
91 udelay(100);
92 }
Patrick Georgi6615ef32010-08-13 09:18:58 +000093}
94
95static void
96ohci_rh_scanport (usbdev_t *dev, int port)
97{
98 if (port >= RH_INST(dev)->numports) {
Gabe Black93ded592012-11-01 15:44:10 -070099 usb_debug("Invalid port %d\n", port);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000100 return;
101 }
102
103 /* device registered, and device change logged, so something must have happened */
104 if (RH_INST (dev)->port[port] != -1) {
105 usb_detach_device(dev->controller, RH_INST (dev)->port[port]);
106 RH_INST (dev)->port[port] = -1;
107 }
108
109 /* no device attached
110 previously registered devices are detached, nothing left to do */
111 if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & CurrentConnectStatus))
112 return;
113
114 OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ConnectStatusChange; // clear port state change
115 ohci_rh_enable_port (dev, port);
116
117 mdelay(100); // wait for signal to stabilize
118
119 if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) {
Gabe Black93ded592012-11-01 15:44:10 -0700120 usb_debug ("port enable failed\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000121 return;
122 }
123
124 int speed = (OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & LowSpeedDeviceAttached) != 0;
125 RH_INST (dev)->port[port] = usb_attach_device(dev->controller, dev->address, port, speed);
126}
127
128static int
129ohci_rh_report_port_changes (usbdev_t *dev)
130{
131 int i;
132
133 if (!(OHCI_INST (dev->controller)->opreg->HcInterruptStatus & RootHubStatusChange)) return -1;
134 OHCI_INST (dev->controller)->opreg->HcInterruptStatus = RootHubStatusChange;
Gabe Black93ded592012-11-01 15:44:10 -0700135 usb_debug("port change\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000136
137 for (i = 0; i < RH_INST(dev)->numports; i++) {
138 // maybe detach+attach happened between two scans?
139 if (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[i] & ConnectStatusChange) {
Gabe Black93ded592012-11-01 15:44:10 -0700140 usb_debug("attachment change on port %d\n", i);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000141 return i;
142 }
143 }
144
145 // no change
146 return -1;
147}
148
149static void
150ohci_rh_destroy (usbdev_t *dev)
151{
152 int i;
153 for (i = 0; i < RH_INST (dev)->numports; i++)
154 ohci_rh_disable_port (dev, i);
155 free (RH_INST (dev));
156}
157
158static void
159ohci_rh_poll (usbdev_t *dev)
160{
161 int port;
162 while ((port = ohci_rh_report_port_changes (dev)) != -1)
163 ohci_rh_scanport (dev, port);
164}
165
166void
167ohci_rh_init (usbdev_t *dev)
168{
169 int i;
170
171 dev->destroy = ohci_rh_destroy;
172 dev->poll = ohci_rh_poll;
173
174 dev->data = malloc (sizeof (rh_inst_t));
175 if (!dev->data)
Patrick Georgi2e768e72011-11-04 11:50:03 +0100176 fatal("Not enough memory for OHCI RH.\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000177
178 RH_INST (dev)->numports = OHCI_INST (dev->controller)->opreg->HcRhDescriptorA & NumberDownstreamPortsMask;
179 RH_INST (dev)->port = malloc(sizeof(int) * RH_INST (dev)->numports);
Gabe Black93ded592012-11-01 15:44:10 -0700180 usb_debug("%d ports registered\n", RH_INST (dev)->numports);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000181
182 for (i = 0; i < RH_INST (dev)->numports; i++) {
183 ohci_rh_enable_port (dev, i);
184 RH_INST (dev)->port[i] = -1;
185 }
186
187 /* we can set them here because a root hub _really_ shouldn't
188 appear elsewhere */
189 dev->address = 0;
190 dev->hub = -1;
191 dev->port = -1;
192
Gabe Black93ded592012-11-01 15:44:10 -0700193 usb_debug("rh init done\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000194}