xref: /aosp_15_r20/external/coreboot/payloads/libpayload/drivers/usb/ohci_rh.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /*
2  *
3  * Copyright (C) 2010 Patrick Georgi
4  *
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.
15  *
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.
27  */
28 
29 //#define USB_DEBUG
30 
31 #include <libpayload.h>
32 #include "ohci_private.h"
33 #include "ohci.h"
34 
35 typedef struct {
36 	int numports;
37 	int *port;
38 } rh_inst_t;
39 
40 #define RH_INST(dev) ((rh_inst_t*)(dev)->data)
41 
42 static void
ohci_rh_enable_port(usbdev_t * dev,int port)43 ohci_rh_enable_port(usbdev_t *dev, int port)
44 {
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).
49 	 */
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;
55 
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--;
64 		}
65 		if (OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port]
66 				& PortResetStatus) {
67 			usb_debug("Warning: root-hub port reset timed out.\n");
68 			break;
69 		}
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);
78 	}
79 }
80 
81 /* disable root hub */
82 static void
ohci_rh_disable_port(usbdev_t * dev,int port)83 ohci_rh_disable_port(usbdev_t *dev, int port)
84 {
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;
88 	}
89 
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);
96 	}
97 }
98 
99 static void
ohci_rh_scanport(usbdev_t * dev,int port)100 ohci_rh_scanport(usbdev_t *dev, int port)
101 {
102 	if (port >= RH_INST(dev)->numports) {
103 		usb_debug("Invalid port %d\n", port);
104 		return;
105 	}
106 
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;
111 	}
112 
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;
117 
118 	OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] = ConnectStatusChange; // clear port state change
119 	ohci_rh_enable_port(dev, port);
120 
121 	mdelay(100); // wait for signal to stabilize
122 
123 	if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) {
124 		usb_debug("port enable failed\n");
125 		return;
126 	}
127 
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);
130 }
131 
132 static int
ohci_rh_report_port_changes(usbdev_t * dev)133 ohci_rh_report_port_changes(usbdev_t *dev)
134 {
135 	ohci_t *const ohcic = OHCI_INST(dev->controller);
136 
137 	int i;
138 
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;
145 		}
146 	}
147 
148 	// no change
149 	return -1;
150 }
151 
152 static void
ohci_rh_destroy(usbdev_t * dev)153 ohci_rh_destroy(usbdev_t *dev)
154 {
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));
159 }
160 
161 static void
ohci_rh_poll(usbdev_t * dev)162 ohci_rh_poll(usbdev_t *dev)
163 {
164 	ohci_t *const ohcic = OHCI_INST(dev->controller);
165 
166 	int port;
167 
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");
173 
174 	/* Scan ports with changed connection status. */
175 	while ((port = ohci_rh_report_port_changes(dev)) != -1)
176 		ohci_rh_scanport(dev, port);
177 }
178 
179 void
ohci_rh_init(usbdev_t * dev)180 ohci_rh_init(usbdev_t *dev)
181 {
182 	int i;
183 
184 	dev->destroy = ohci_rh_destroy;
185 	dev->poll = ohci_rh_poll;
186 
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);
191 
192 	for (i = 0; i < RH_INST(dev)->numports; i++) {
193 		ohci_rh_enable_port(dev, i);
194 		RH_INST(dev)->port[i] = -1;
195 	}
196 
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;
202 
203 	usb_debug("rh init done\n");
204 }
205