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