1 |
/* |
2 |
* PearPC |
3 |
* usb.cc |
4 |
* |
5 |
* Copyright (C) 2003 Sebastian Biallas (sb@biallas.net) |
6 |
* |
7 |
* References: |
8 |
* [1] OpenHCI - Open Host Controller Interface Specification for USB |
9 |
* Revision 1.0a - hcir1_0a.pdf |
10 |
* [2] Linux USB ohci-driver |
11 |
* (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> |
12 |
* (C) Copyright 2000-2001 David Brownell <dbrownell@users.sourceforge.net> |
13 |
* |
14 |
* This program is free software; you can redistribute it and/or modify |
15 |
* it under the terms of the GNU General Public License version 2 as |
16 |
* published by the Free Software Foundation. |
17 |
* |
18 |
* This program is distributed in the hope that it will be useful, |
19 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
21 |
* GNU General Public License for more details. |
22 |
* |
23 |
* You should have received a copy of the GNU General Public License |
24 |
* along with this program; if not, write to the Free Software |
25 |
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
26 |
*/ |
27 |
|
28 |
#include "debug/tracers.h" |
29 |
#include "system/arch/sysendian.h" |
30 |
#include "io/pci/pci.h" |
31 |
#include "usb.h" |
32 |
|
33 |
#include <cstring> |
34 |
|
35 |
#define NUM_INTS 32 /* part of the OHCI standard */ |
36 |
#define MAX_ROOT_PORTS 15 /* maximum OHCI root hub ports */ |
37 |
|
38 |
struct ohci_hcca { |
39 |
uint32 int_table[NUM_INTS]; /* Interrupt ED table */ |
40 |
uint16 frame_no; /* current frame number */ |
41 |
uint16 pad1; /* set to 0 on each frame_no change */ |
42 |
uint32 done_head; /* info returned for an interrupt */ |
43 |
uint8 reserved_for_hc[116]; |
44 |
} PACKED; |
45 |
|
46 |
// [1].122 |
47 |
#define OHCI_REG_REVISION 0x00 // [1].123 |
48 |
#define OHCI_REG_CONTROL 0x04 // [1].123 |
49 |
#define OHCI_REG_CMDSTATUS 0x08 // [1].126 |
50 |
#define OHCI_REG_INTRSTATUS 0x0c // [1].126 |
51 |
#define OHCI_REG_INTRENABLE 0x10 // [1].126 |
52 |
#define OHCI_REG_INTRDISABLE 0x14 // [1].126 |
53 |
#define OHCI_REG_HCCA 0x18 // [1].126 |
54 |
#define OHCI_REG_ED_PERIODCUR 0x1c // [1].126 |
55 |
#define OHCI_REG_ED_CONTROL_HD 0x20 // [1].126 |
56 |
#define OHCI_REG_ED_CONTROL_CUR 0x24 // [1].126 |
57 |
#define OHCI_REG_ED_BULK_HD 0x28 // [1].126 |
58 |
#define OHCI_REG_ED_BULK_CUR 0x2c // [1].126 |
59 |
#define OHCI_REG_DONEHEAD 0x30 // [1].126 |
60 |
#define OHCI_REG_FMINTERVAL 0x34 // [1].126 |
61 |
#define OHCI_REG_FMREMAIN 0x38 // [1].126 |
62 |
#define OHCI_REG_FMNUMBER 0x3c // [1].126 |
63 |
#define OHCI_REG_PERIODICSTART 0x40 // [1].126 |
64 |
#define OHCI_REG_LSTHRESH 0x44 // [1].126 |
65 |
#define OHCI_REG_ROOTHUB_A 0x48 |
66 |
#define OHCI_REG_ROOTHUB_B 0x4c |
67 |
#define OHCI_REG_ROOTHUB_STAT 0x50 |
68 |
#define OHCI_REG_ROOTHUB_PORTS 0x54 |
69 |
|
70 |
/* |
71 |
* bits in ohci_hcregs.control |
72 |
*/ |
73 |
#define OHCI_CTRL_CBSR (3 << 0) /* control/bulk service ratio */ |
74 |
#define OHCI_CTRL_PLE (1 << 2) /* periodic list enable */ |
75 |
#define OHCI_CTRL_IE (1 << 3) /* isochronous enable */ |
76 |
#define OHCI_CTRL_CLE (1 << 4) /* control list enable */ |
77 |
#define OHCI_CTRL_BLE (1 << 5) /* bulk list enable */ |
78 |
#define OHCI_CTRL_HCFS (3 << 6) /* host controller functional state */ |
79 |
#define OHCI_CTRL_IR (1 << 8) /* interrupt routing */ |
80 |
#define OHCI_CTRL_RWC (1 << 9) /* remote wakeup connected */ |
81 |
#define OHCI_CTRL_RWE (1 << 10) /* remote wakeup enable */ |
82 |
|
83 |
/* pre-shifted values for HCFS */ |
84 |
# define OHCI_USB_RESET (0 << 6) |
85 |
# define OHCI_USB_RESUME (1 << 6) |
86 |
# define OHCI_USB_OPER (2 << 6) |
87 |
# define OHCI_USB_SUSPEND (3 << 6) |
88 |
|
89 |
/* |
90 |
* bits in ohci_hcregs.{intrstatus|intrenable|intrdisable} |
91 |
*/ |
92 |
#define OHCI_INTR_SO (1 << 0) /* scheduling overrun */ |
93 |
#define OHCI_INTR_WDH (1 << 1) /* writeback of done_head */ |
94 |
#define OHCI_INTR_SF (1 << 2) /* start frame */ |
95 |
#define OHCI_INTR_RD (1 << 3) /* resume detect */ |
96 |
#define OHCI_INTR_UE (1 << 4) /* unrecoverable error */ |
97 |
#define OHCI_INTR_FNO (1 << 5) /* frame number overflow */ |
98 |
#define OHCI_INTR_RHSC (1 << 6) /* root hub status change */ |
99 |
#define OHCI_INTR_OC (1 << 30) /* ownership change */ |
100 |
#define OHCI_INTR_MIE (1 << 31) /* master interrupt enable */ |
101 |
|
102 |
/* |
103 |
* bits in ohci_hcregs.cmdstatus |
104 |
*/ |
105 |
#define OHCI_HCR (1 << 0) /* host controller reset */ |
106 |
#define OHCI_CLF (1 << 1) /* control list filled */ |
107 |
#define OHCI_BLF (1 << 2) /* bulk list filled */ |
108 |
#define OHCI_OCR (1 << 3) /* ownership change request */ |
109 |
#define OHCI_SOC (3 << 16) /* scheduling overrun count */ |
110 |
|
111 |
/* |
112 |
* bits in ohci_hcregs.roothub.portstatus [1].142 |
113 |
*/ |
114 |
#define OHCI_RH_PS_CCS (1 << 0) /* current connect status */ |
115 |
#define OHCI_RH_PS_PES (1 << 1) /* port enable status */ |
116 |
#define OHCI_RH_PS_PSS (1 << 2) /* port suspend status */ |
117 |
#define OHCI_RH_PS_POCI (1 << 3) /* port overrun current indicator */ |
118 |
#define OHCI_RH_PS_PRS (1 << 4) /* port reset status */ |
119 |
#define OHCI_RH_PS_PPS (1 << 8) /* port power status */ |
120 |
#define OHCI_RH_PS_LSDA (1 << 8) /* low speed device attached */ |
121 |
#define OHCI_RH_PS_CSC (1 << 16) /* connect status change */ |
122 |
#define OHCI_RH_PS_PESC (1 << 17) /* port enable status change */ |
123 |
#define OHCI_RH_PS_PSSC (1 << 18) /* port suspend status change */ |
124 |
#define OHCI_RH_PS_OCIC (1 << 19) /* overrun current indicator change */ |
125 |
#define OHCI_RH_PS_PRSC (1 << 20) /* port reset status change */ |
126 |
|
127 |
struct ohci_hcregs { |
128 |
/* control and status registers */ |
129 |
uint32 control; |
130 |
uint32 cmdstatus; |
131 |
uint32 intrstatus; |
132 |
uint32 intrenable; |
133 |
uint32 intrdisable; |
134 |
/* memory pointers */ |
135 |
uint32 hcca; |
136 |
uint32 ed_periodcurrent; |
137 |
uint32 ed_controlhead; |
138 |
uint32 ed_controlcurrent; |
139 |
uint32 ed_bulkhead; |
140 |
uint32 ed_bulkcurrent; |
141 |
uint32 donehead; |
142 |
/* frame counters */ |
143 |
uint32 fminterval; |
144 |
uint32 fmremaining; |
145 |
uint32 fmnumber; |
146 |
uint32 periodicstart; |
147 |
uint32 lsthresh; |
148 |
/* Root hub ports */ |
149 |
struct ohci_roothub_regs { |
150 |
uint32 a; |
151 |
uint32 b; |
152 |
uint32 status; |
153 |
uint32 portstatus[MAX_ROOT_PORTS]; |
154 |
} roothub; |
155 |
}; |
156 |
|
157 |
static inline const char *hc_regname(uint32 a) |
158 |
{ |
159 |
a >>= 2; |
160 |
if (a > 20) return "unknown"; |
161 |
char *names[] = {"revision","control","cmdstatus","intrstatus","intrenable", |
162 |
"intrdisable","hcca","ed_periodcurrent","ed_controlhead","ed_controlcurrent", |
163 |
"ed_bulkhead","ed_bulkcurrent","donehead","fminterval","fmremaining", |
164 |
"fmnumber","periodicstart", "lsthresh", "roothub.a", "roothub.b", "roothub.status"}; |
165 |
return names[a]; |
166 |
} |
167 |
|
168 |
extern bool gSinglestep; |
169 |
|
170 |
/* |
171 |
* |
172 |
*/ |
173 |
class PCI_USB: public PCI_Device { |
174 |
public: |
175 |
ohci_hcregs hcregs; |
176 |
uint rootport_count; |
177 |
|
178 |
PCI_USB() |
179 |
:PCI_Device("pci-usb", 0x01, 0x06) |
180 |
{ |
181 |
mIORegSize[0] = 0x1000; |
182 |
mIORegType[0] = PCI_ADDRESS_SPACE_MEM; |
183 |
|
184 |
mConfig[0x00] = 0x45; // vendor ID |
185 |
mConfig[0x01] = 0x10; |
186 |
mConfig[0x02] = 0x61; // unit ID |
187 |
mConfig[0x03] = 0xc8; |
188 |
|
189 |
mConfig[0x08] = 0x10; // revision |
190 |
mConfig[0x09] = 0x10; // |
191 |
mConfig[0x0a] = 0x03; // |
192 |
mConfig[0x0b] = 0x0c; // |
193 |
|
194 |
mConfig[0x0e] = 0x00; // header-type |
195 |
|
196 |
assignMemAddress(0, 0x80881000); |
197 |
|
198 |
mConfig[0x3c] = 0x03; |
199 |
mConfig[0x3d] = 0x03; |
200 |
mConfig[0x3e] = 0x03; |
201 |
mConfig[0x3f] = 0x03; |
202 |
|
203 |
rootport_count = 1; |
204 |
reset(); |
205 |
} |
206 |
|
207 |
void reset() |
208 |
{ |
209 |
memset(&hcregs, 0, sizeof hcregs); |
210 |
hcregs.fminterval = 0x2edf; // [1].134 |
211 |
hcregs.lsthresh = 0x628; // [1].137 |
212 |
hcregs.roothub.a = 0 // [1].138 |
213 |
| (1<<12) // No overcurrent protection supported |
214 |
| (0<<10) // always 0 |
215 |
| (1<<9) // Ports are always powered on when the HC is powered on |
216 |
| (0<<8) // all ports are powered at the same time. |
217 |
| rootport_count; // number of rootports |
218 |
// hcregs.roothub.portstatus[0] = ; |
219 |
} |
220 |
|
221 |
virtual bool readDeviceMem(uint r, uint32 address, uint32 &data, uint size) |
222 |
{ |
223 |
if (r != 0) return false; |
224 |
if (size != 4) return false; |
225 |
IO_USB_TRACE("read(r=%d, a=%08x (%s), %d)\n", r, address, hc_regname(address), size); |
226 |
|
227 |
switch (address) { |
228 |
case OHCI_REG_REVISION: |
229 |
// [1].123 |
230 |
data = 0x10; |
231 |
break; |
232 |
case OHCI_REG_CONTROL: |
233 |
data = hcregs.control; |
234 |
break; |
235 |
case OHCI_REG_CMDSTATUS: |
236 |
data = hcregs.cmdstatus; |
237 |
break; |
238 |
case OHCI_REG_INTRSTATUS: |
239 |
data = hcregs.intrstatus; |
240 |
break; |
241 |
case OHCI_REG_INTRENABLE: |
242 |
data = hcregs.intrenable; |
243 |
break; |
244 |
case OHCI_REG_INTRDISABLE: |
245 |
data = hcregs.intrdisable; |
246 |
break; |
247 |
case OHCI_REG_HCCA: |
248 |
data = hcregs.hcca; |
249 |
break; |
250 |
case OHCI_REG_ED_PERIODCUR: |
251 |
data = hcregs.ed_periodcurrent; |
252 |
break; |
253 |
case OHCI_REG_ED_CONTROL_HD: |
254 |
data = hcregs.ed_controlhead; |
255 |
break; |
256 |
case OHCI_REG_ED_CONTROL_CUR: |
257 |
data = hcregs.ed_controlcurrent; |
258 |
break; |
259 |
case OHCI_REG_ED_BULK_HD: |
260 |
data = hcregs.ed_bulkhead; |
261 |
break; |
262 |
case OHCI_REG_ED_BULK_CUR: |
263 |
data = hcregs.ed_bulkcurrent; |
264 |
break; |
265 |
case OHCI_REG_DONEHEAD: |
266 |
data = hcregs.donehead; |
267 |
break; |
268 |
case OHCI_REG_FMINTERVAL: |
269 |
data = hcregs.fminterval; |
270 |
break; |
271 |
case OHCI_REG_FMREMAIN: |
272 |
data = hcregs.fmremaining; |
273 |
break; |
274 |
case OHCI_REG_FMNUMBER: |
275 |
data = hcregs.fmnumber; |
276 |
break; |
277 |
case OHCI_REG_PERIODICSTART: |
278 |
data = hcregs.periodicstart; |
279 |
break; |
280 |
case OHCI_REG_LSTHRESH: |
281 |
data = hcregs.lsthresh; |
282 |
break; |
283 |
case OHCI_REG_ROOTHUB_A: |
284 |
data = hcregs.roothub.a; |
285 |
break; |
286 |
case OHCI_REG_ROOTHUB_B: |
287 |
data = hcregs.roothub.b; |
288 |
break; |
289 |
case OHCI_REG_ROOTHUB_STAT: |
290 |
data = hcregs.roothub.status; |
291 |
break; |
292 |
default: |
293 |
address -= OHCI_REG_ROOTHUB_PORTS; |
294 |
address >>= 2; |
295 |
if (address < rootport_count) { |
296 |
data = hcregs.roothub.portstatus[address]; |
297 |
break; |
298 |
} |
299 |
return false; |
300 |
} |
301 |
|
302 |
// gSinglestep = true; |
303 |
return true; |
304 |
} |
305 |
|
306 |
virtual bool writeDeviceMem(uint r, uint32 address, uint32 data, uint size) |
307 |
{ |
308 |
if (r != 0) return false; |
309 |
if (size != 4) return false; |
310 |
IO_USB_TRACE("write(r=%d, a=%08x (%s), data=%08x, %d)\n", r, address, hc_regname(address), data, size); |
311 |
|
312 |
switch (address) { |
313 |
case OHCI_REG_REVISION: |
314 |
// [1].123 |
315 |
IO_USB_WARN("revision is read only.\n"); |
316 |
return true; |
317 |
case OHCI_REG_CONTROL: |
318 |
hcregs.control = data; |
319 |
break; |
320 |
case OHCI_REG_CMDSTATUS: |
321 |
if (data & OHCI_HCR) { |
322 |
reset(); |
323 |
return true; |
324 |
} |
325 |
hcregs.cmdstatus = data; |
326 |
break; |
327 |
case OHCI_REG_INTRSTATUS: |
328 |
hcregs.intrstatus = data; |
329 |
break; |
330 |
case OHCI_REG_INTRENABLE: |
331 |
hcregs.intrenable = data; |
332 |
break; |
333 |
case OHCI_REG_INTRDISABLE: |
334 |
hcregs.intrdisable = data; |
335 |
break; |
336 |
case OHCI_REG_HCCA: |
337 |
hcregs.hcca = data; |
338 |
break; |
339 |
case OHCI_REG_ED_PERIODCUR: |
340 |
hcregs.ed_periodcurrent = data; |
341 |
break; |
342 |
case OHCI_REG_ED_CONTROL_HD: |
343 |
hcregs.ed_controlhead = data; |
344 |
break; |
345 |
case OHCI_REG_ED_CONTROL_CUR: |
346 |
hcregs.ed_controlcurrent = data; |
347 |
break; |
348 |
case OHCI_REG_ED_BULK_HD: |
349 |
hcregs.ed_bulkhead = data; |
350 |
break; |
351 |
case OHCI_REG_ED_BULK_CUR: |
352 |
hcregs.ed_bulkcurrent = data; |
353 |
break; |
354 |
case OHCI_REG_DONEHEAD: |
355 |
hcregs.donehead = data; |
356 |
break; |
357 |
case OHCI_REG_FMINTERVAL: |
358 |
hcregs.fminterval = data; |
359 |
break; |
360 |
case OHCI_REG_FMREMAIN: |
361 |
hcregs.fmremaining = data; |
362 |
break; |
363 |
case OHCI_REG_FMNUMBER: |
364 |
hcregs.fmnumber = data; |
365 |
break; |
366 |
case OHCI_REG_PERIODICSTART: |
367 |
hcregs.periodicstart = data; |
368 |
break; |
369 |
case OHCI_REG_LSTHRESH: |
370 |
hcregs.lsthresh = data; |
371 |
break; |
372 |
case OHCI_REG_ROOTHUB_A: |
373 |
hcregs.roothub.a = data; |
374 |
break; |
375 |
case OHCI_REG_ROOTHUB_B: |
376 |
hcregs.roothub.b = data; |
377 |
break; |
378 |
case OHCI_REG_ROOTHUB_STAT: |
379 |
hcregs.roothub.status = data; |
380 |
break; |
381 |
default: |
382 |
address -= OHCI_REG_ROOTHUB_PORTS; |
383 |
address >>= 2; |
384 |
if (address < rootport_count) { |
385 |
if (data & OHCI_RH_PS_CCS) { |
386 |
// writing 1 to CCS clears PES |
387 |
hcregs.roothub.portstatus[address] &= ~OHCI_RH_PS_PES; |
388 |
} |
389 |
|
390 |
// writing 1 to these bits clears them |
391 |
hcregs.roothub.portstatus[address] &= ~(data & |
392 |
(OHCI_RH_PS_CSC | OHCI_RH_PS_PESC | OHCI_RH_PS_PSSC |
393 |
| OHCI_RH_PS_OCIC | OHCI_RH_PS_PRSC)); |
394 |
|
395 |
// writing 1 to these bits set them only if CCS is set |
396 |
uint32 p = data & OHCI_RH_PS_PES | OHCI_RH_PS_PSS | OHCI_RH_PS_PRS; |
397 |
if (p) { |
398 |
if (hcregs.roothub.portstatus[address] & OHCI_RH_PS_CCS) { |
399 |
hcregs.roothub.portstatus[address] |= p; |
400 |
} else { |
401 |
// attempt to enable/suspend/reset disconnected port |
402 |
hcregs.roothub.portstatus[address] |= OHCI_RH_PS_CSC; |
403 |
} |
404 |
} |
405 |
|
406 |
// we dont support power switching |
407 |
hcregs.roothub.portstatus[address] |= OHCI_RH_PS_PPS; |
408 |
break; |
409 |
} |
410 |
} |
411 |
|
412 |
// gSinglestep = true; |
413 |
return true; |
414 |
} |
415 |
|
416 |
}; |
417 |
|
418 |
|
419 |
#include "configparser.h" |
420 |
|
421 |
#define USB_KEY_INSTALLED "pci_usb_installed" |
422 |
|
423 |
void usb_init() |
424 |
{ |
425 |
if (gConfig->getConfigInt(USB_KEY_INSTALLED)) { |
426 |
gPCI_Devices->insert(new PCI_USB()); |
427 |
} |
428 |
} |
429 |
|
430 |
void usb_done() |
431 |
{ |
432 |
} |
433 |
|
434 |
void usb_init_config() |
435 |
{ |
436 |
gConfig->acceptConfigEntryIntDef(USB_KEY_INSTALLED, 0); |
437 |
} |