blob: 5d82bd59e079f3f50dea08959ed7906c5268ff16 [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{
Julius Werner7234d602014-04-08 12:54:25 -070086 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 Georgi6615ef32010-08-13 09:18:58 +000091 OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ClearPortEnable; // disable port
Nico Huberafe86c02012-05-21 14:46:26 +020092 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 Georgi6615ef32010-08-13 09:18:58 +000098}
99
100static void
101ohci_rh_scanport (usbdev_t *dev, int port)
102{
103 if (port >= RH_INST(dev)->numports) {
Gabe Black93ded592012-11-01 15:44:10 -0700104 usb_debug("Invalid port %d\n", port);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000105 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 Black93ded592012-11-01 15:44:10 -0700125 usb_debug ("port enable failed\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000126 return;
127 }
128
Julius Wernere00ba212013-09-24 20:03:54 -0700129 usb_speed speed = (OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & LowSpeedDeviceAttached) != 0;
Patrick Georgi6615ef32010-08-13 09:18:58 +0000130 RH_INST (dev)->port[port] = usb_attach_device(dev->controller, dev->address, port, speed);
131}
132
133static int
134ohci_rh_report_port_changes (usbdev_t *dev)
135{
Nico Huberb9917c22012-11-22 15:45:48 +0100136 ohci_t *const ohcic = OHCI_INST (dev->controller);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000137
Nico Huberb9917c22012-11-22 15:45:48 +0100138 int i;
Patrick Georgi6615ef32010-08-13 09:18:58 +0000139
140 for (i = 0; i < RH_INST(dev)->numports; i++) {
141 // maybe detach+attach happened between two scans?
Nico Huberb9917c22012-11-22 15:45:48 +0100142 if (ohcic->opreg->HcRhPortStatus[i] & ConnectStatusChange) {
143 ohcic->opreg->HcRhPortStatus[i] = ConnectStatusChange;
Gabe Black93ded592012-11-01 15:44:10 -0700144 usb_debug("attachment change on port %d\n", i);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000145 return i;
146 }
147 }
148
149 // no change
150 return -1;
151}
152
153static void
154ohci_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
162static void
163ohci_rh_poll (usbdev_t *dev)
164{
Nico Huberb9917c22012-11-22 15:45:48 +0100165 ohci_t *const ohcic = OHCI_INST (dev->controller);
166
Patrick Georgi6615ef32010-08-13 09:18:58 +0000167 int port;
Nico Huberb9917c22012-11-22 15:45:48 +0100168
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 Georgi6615ef32010-08-13 09:18:58 +0000176 while ((port = ohci_rh_report_port_changes (dev)) != -1)
177 ohci_rh_scanport (dev, port);
178}
179
180void
181ohci_rh_init (usbdev_t *dev)
182{
183 int i;
184
185 dev->destroy = ohci_rh_destroy;
186 dev->poll = ohci_rh_poll;
187
Julius Werner7234d602014-04-08 12:54:25 -0700188 dev->data = xmalloc (sizeof (rh_inst_t));
Patrick Georgi6615ef32010-08-13 09:18:58 +0000189 RH_INST (dev)->numports = OHCI_INST (dev->controller)->opreg->HcRhDescriptorA & NumberDownstreamPortsMask;
Julius Werner7234d602014-04-08 12:54:25 -0700190 RH_INST (dev)->port = xmalloc(sizeof(int) * RH_INST (dev)->numports);
Gabe Black93ded592012-11-01 15:44:10 -0700191 usb_debug("%d ports registered\n", RH_INST (dev)->numports);
Patrick Georgi6615ef32010-08-13 09:18:58 +0000192
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 Black93ded592012-11-01 15:44:10 -0700204 usb_debug("rh init done\n");
Patrick Georgi6615ef32010-08-13 09:18:58 +0000205}