Works atleast for the case that controller is not OS owned nor BIOS owned, I couldn...
[AROS.git] / rom / usb / pciusbhc / xhci / pcixhci_controller.c
blobc4558ca4f83ad2cf43b38538e5073e434d6755f6
1 /*
2 Copyright © 2014, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: PCI XHCI USB host controller
6 Lang: English
7 */
9 #ifdef DEBUG
10 #undef DEBUG
11 #endif
12 #define DEBUG 1
14 #include <aros/io.h>
15 #include <aros/debug.h>
16 #include <aros/macros.h>
17 #include <aros/asmcall.h>
18 #include <aros/symbolsets.h>
20 #include <proto/oop.h>
21 #include <proto/exec.h>
22 #include <proto/stdc.h>
23 #include <proto/arossupport.h>
25 #include <devices/usb.h>
26 #include <devices/usb_hub.h>
27 #include <devices/newstyle.h>
28 #include <devices/usbhardware.h>
29 #include <devices/timer.h>
31 #include <asm/io.h>
32 #include <inttypes.h>
34 #include <hidd/pci.h>
35 #include <hidd/hidd.h>
37 #include "pcixhci_intern.h"
39 #include "pcixhci_controller.h"
41 #include LC_LIBDEFS_FILE
43 #define PCIXHCIBase unit->pcixhcibase
45 static AROS_INTH1(PCIXHCI_IntCode, struct PCIXHCIUnit *, unit) {
46 AROS_INTFUNC_INIT
48 ULONG usbsts, portsc;
50 usbsts = operational_readl(XHCI_USBSTS);
52 if(usbsts & XHCF_STS_PCD) {
53 operational_writel(XHCI_USBSTS, XHCF_STS_PCD);
54 mybug_unit(-1, ("cleared usbsts = %08x\n", operational_readl(XHCI_USBSTS)));
55 struct PCIXHCIPort *port = NULL;
57 ForeachNode(&unit->roothub.port_list, port) {
58 portsc = operational_readl(XHCI_PORTSC(port->number));
60 if(portsc & XHCF_PS_CSC) {
61 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_CSC));
62 mybug_unit(-1,("port %d XHCF_PS_CSC\n", port->number));
65 if(portsc & XHCF_PS_PEC) {
66 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_PEC));
67 mybug_unit(-1,("port %d XHCF_PS_PEC\n", port->number));
70 if(portsc & XHCF_PS_OCC) {
71 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_OCC));
72 mybug_unit(-1,("port %d XHCF_PS_OCC\n", port->number));
75 if(portsc & XHCF_PS_WRC) {
76 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_WRC));
77 mybug_unit(-1,("port %d XHCF_PS_WRC\n", port->number));
80 if(portsc & XHCF_PS_PRC) {
81 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_PRC));
82 mybug_unit(-1,("port %d XHCF_PS_PRC\n", port->number));
85 if(portsc & XHCF_PS_PLC) {
86 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_PLC));
87 mybug_unit(-1,("port %d XHCF_PS_PLC\n", port->number));
90 if(portsc & XHCF_PS_CEC) {
91 operational_writel(XHCI_PORTSC(port->number), (portsc | ~XHCF_PS_CEC));
92 mybug_unit(-1,("port %d XHCF_PS_CEC\n", port->number));
96 return 0;
97 AROS_INTFUNC_EXIT
101 We get called only once (per controller) when the driver inits
102 We own the controller until our driver expunges so we assume that nobody messes with our stuff...
103 Driver NEVER expunges, it messes Poseidon.
105 BOOL PCIXHCI_HCInit(struct PCIXHCIUnit *unit) {
106 //mybug(0, ("[PCIXHCI] PCIXHCI_HCInit: Entering function\n"));
108 /* Our unit is in suspended state until it is reset */
109 unit->state = UHSF_SUSPENDED;
111 /* Init the port list but do not fill it */
112 NEWLIST(&unit->roothub.port_list);
114 snprintf(unit->name, 255, "PCIXHCI[%02x:%02x.%01x]", (UBYTE)unit->hc.bus, (UBYTE)unit->hc.dev, (UBYTE)unit->hc.sub);
115 unit->node.ln_Name = (STRPTR)&unit->name;
117 if(!PCIXHCI_CreateTimer(unit)) {
118 return FALSE;
121 /* capability base(=base0) has been stored in the enumerator */
123 /* Store operational base */
124 unit->hc.operational_base = (APTR) ((IPTR) (unit->hc.capability_base) + capability_readb(XHCI_CAPLENGTH));
126 /* Store doorbell base */
127 unit->hc.doorbell_base = (APTR) ((IPTR) (unit->hc.capability_base) + XHCV_DBOFF(capability_readl(XHCI_DBOFF)));
129 /* Store runtime base */
130 unit->hc.runtime_base = (APTR) ((IPTR) (unit->hc.capability_base) + XHCV_RTSOFF(capability_readl(XHCI_RTSOFF)));
132 mybug_unit(-1, ("unit node name %s\n", unit->node.ln_Name));
133 mybug_unit(-1, ("pcidevice = %p\n", unit->hc.pcidevice));
134 mybug_unit(-1, ("pcidriver = %p\n", unit->hc.pcidriver));
135 mybug_unit(-1, ("bus = %x\n", unit->hc.bus));
136 mybug_unit(-1, ("dev = %x\n", unit->hc.dev));
137 mybug_unit(-1, ("sub = %x\n", unit->hc.sub));
138 mybug_unit(-1, ("intline = %d\n", unit->hc.intline));
140 mybug_unit(-1, ("capability = %p\n", unit->hc.capability_base));
141 mybug_unit(-1, ("operational = %p\n", unit->hc.operational_base));
142 mybug_unit(-1, ("doorbell = %p\n", unit->hc.doorbell_base));
143 mybug_unit(-1, ("runtime = %p\n", unit->hc.runtime_base));
146 PCIXHCI[03:00.0] PCIXHCI_HCInit: capability = f3dfe000
147 PCIXHCI[03:00.0] PCIXHCI_HCInit: operational = f3dfe020
148 PCIXHCI[03:00.0] PCIXHCI_HCInit: doorbell = f3dfe800 up to 256 32 bit doorbell registers (4*256 = 0x400)
149 PCIXHCI[03:00.0] PCIXHCI_HCInit: runtime = f3dfe600
151 PCIXHCI[04:00.0] PCIXHCI_HCInit: capability = f3efe000
152 PCIXHCI[04:00.0] PCIXHCI_HCInit: operational = f3efe020
153 PCIXHCI[04:00.0] PCIXHCI_HCInit: doorbell = f3efe800 up to 256 32 bit doorbell registers (4*256 = 0x400)
154 PCIXHCI[04:00.0] PCIXHCI_HCInit: runtime = f3efe600
157 /* We use pointers and stuff... */
158 struct TagItem pciActivateMemAndBusmaster[] = {
159 { aHidd_PCIDevice_isIO, FALSE },
160 { aHidd_PCIDevice_isMEM, TRUE },
161 { aHidd_PCIDevice_isMaster, TRUE },
162 { TAG_DONE, 0UL },
165 OOP_SetAttrs(unit->hc.pcidevice, (struct TagItem *)pciActivateMemAndBusmaster);
167 /* Get the host controller from BIOS if possible */
168 IPTR cap_legacy;
169 ULONG usblegsup, timeout;
171 cap_legacy = PCIXHCI_SearchExtendedCap(unit, XHCI_EXT_CAPS_LEGACY, (IPTR) NULL);
172 if(cap_legacy) {
173 usblegsup = READREG32(cap_legacy, XHCI_USBLEGSUP);
174 mybug_unit(-1, ("usblegsup1 = %08x\n", usblegsup));
176 /* Check if not OS owned */
177 if( ((!(usblegsup & XHCF_OSOWNED)) || (usblegsup & XHCF_BIOSOWNED)) ){
178 WRITEMEM32(cap_legacy, (usblegsup|XHCF_OSOWNED));
180 usblegsup = READREG32(cap_legacy, XHCI_USBLEGSUP);
181 mybug_unit(-1, ("usblegsup2 = %08x\n", usblegsup));
183 /* Spec says "no more than a second", we give it a little more */
184 timeout = 250;
186 while(1) {
187 usblegsup = READREG32(cap_legacy, XHCI_USBLEGSUP);
188 mybug_unit(-1, ("usblegsup3 = %08x\n", usblegsup));
189 if( (usblegsup & XHCF_OSOWNED) && (!(usblegsup & XHCF_BIOSOWNED)) ){
190 break;
193 /* Wait 10ms and check again */
194 PCIXHCI_Delay(unit, 10);
196 if(--timeout) {
197 mybug_unit(-1, ("BIOS didn't release XHCI. Forcing and praying...\n"));
198 WRITEMEM32(cap_legacy, ((usblegsup|XHCF_OSOWNED)&~XHCF_BIOSOWNED));
199 break;
202 } else {
203 mybug_unit(-1, ("Controller is already owned by the OS\n"));
207 mybug(-1,("XHCV_MaxIntrs = %d\n",XHCV_MaxIntrs(capability_readl(XHCI_HCSPARAMS1))));
209 /* Add interrupt handler */
210 snprintf(unit->hc.intname, 255, "%s interrupt handler", unit->node.ln_Name);
211 unit->hc.inthandler.is_Node.ln_Name = (STRPTR)&unit->hc.intname;
212 unit->hc.inthandler.is_Node.ln_Pri = 15;
213 unit->hc.inthandler.is_Node.ln_Type = NT_INTERRUPT;
214 unit->hc.inthandler.is_Code = (VOID_FUNC)PCIXHCI_IntCode;
215 unit->hc.inthandler.is_Data = unit;
216 if(!HIDD_PCIDevice_AddInterrupt(unit->hc.pcidevice, &unit->hc.inthandler)) {
217 mybug_unit(-1, ("Failed setting up interrupt handler!\n"));
218 return FALSE;
221 return TRUE;
225 We get called everytime the driver is trying to get online
226 We make use of it by also filling the port list of our unit,
227 that way toggling unit offline and online may give different results for ports after reset
229 BOOL PCIXHCI_HCReset(struct PCIXHCIUnit *unit) {
230 mybug_unit(0, ("Entering function\n"));
232 ULONG timeout;
234 if(!PCIXHCI_HCHalt(unit)) {
235 return FALSE;
238 /* our unit is in reset state until the higher level usb reset is called */
239 unit->state = UHSF_RESET;
241 /* Reset controller by setting HCRST-bit */
242 operational_writel(XHCI_USBCMD, (operational_readl(XHCI_USBCMD) | XHCF_CMD_HCRST));
245 Controller clears HCRST bit when reset is done, wait for it and the CNR-bit to be cleared
247 timeout = 250; //FIXME: arbitrary value of 2500ms
248 while ((operational_readl(XHCI_USBCMD) & XHCF_CMD_HCRST) && (timeout-- != 0)) {
249 if(!timeout) {
250 mybug_unit(-1, ("Time is up for XHCF_CMD_HCRST bit!\n"));
251 return FALSE;
253 /* Wait 10ms and check again */
254 PCIXHCI_Delay(unit, 10);
257 timeout = 250; //FIXME: arbitrary value of 2500ms
258 while ((operational_readl(XHCI_USBSTS) & XHCF_STS_CNR) && (timeout-- != 0)) {
259 if(!timeout) {
260 mybug_unit(-1, ("Time is up for XHCF_STS_CNR bit!\n"));
261 return FALSE;
263 /* Wait 10ms and check again */
264 PCIXHCI_Delay(unit, 10);
267 if(!PCIXHCI_FindPorts(unit)) {
268 return FALSE;
271 struct PCIXHCIPort *port = NULL;
273 mybug_unit(-1, ("Unit %d at %p %s\n", unit->number, unit, unit->name));
274 ForeachNode(&unit->roothub.port_list, port) {
275 mybug_unit(-1, (" port %d at %p %s\n", port->number, port, port->name));
277 mybug(-1,("\n"));
279 /* Enable host controller to issue interrupts */
280 mybug_unit(-1, ("usbcmd = %08x\n", operational_readl(XHCI_USBCMD)));
281 mybug_unit(-1, ("usbsts = %08x\n", operational_readl(XHCI_USBSTS)));
282 operational_writel(XHCI_USBCMD, (operational_readl(XHCI_USBCMD) | (XHCF_CMD_RS | XHCF_CMD_INTE | XHCF_CMD_HSEE) ));
283 mybug_unit(-1, ("usbcmd = %08x\n", operational_readl(XHCI_USBCMD)));
284 mybug_unit(-1, ("usbsts = %08x\n", operational_readl(XHCI_USBSTS)));
285 return TRUE;
288 BOOL PCIXHCI_HCHalt(struct PCIXHCIUnit *unit) {
289 mybug_unit(-1, ("Entering function\n"));
291 ULONG timeout, temp;
293 /* Halt the controller by clearing Run/Stop bit */
294 temp = operational_readl(XHCI_USBCMD);
295 operational_writel(XHCI_USBCMD, (temp & ~XHCF_CMD_RS));
297 /* Our unit advertises that it is in suspended state */
298 unit->state = UHSF_SUSPENDED;
301 The xHC shall halt within 16 ms. after software clears the Run/Stop bit if certain conditions have been met.
302 The HCHalted (HCH) bit in the USBSTS register indicates when the xHC has finished its
303 pending pipelined transactions and has entered the stopped state.
305 timeout = 250; //FIXME: arbitrary value of 2500ms
306 do {
307 temp = operational_readl(XHCI_USBSTS);
308 if( (temp & XHCF_STS_HCH) ) {
309 mybug_unit(-1, ("controller halted!\n"));
310 return TRUE;
312 /* Wait 10ms and check again */
313 PCIXHCI_Delay(unit, 10);
314 } while(--timeout);
316 mybug_unit(-1, ("halt failed!\n"));
317 return FALSE;
320 IPTR PCIXHCI_SearchExtendedCap(struct PCIXHCIUnit *unit, ULONG id, IPTR extcapoff) {
322 IPTR extcap = (IPTR) NULL;
324 mybug_unit(-1,("searching for extended capability id(%ld)\n", id));
326 if(extcapoff) {
327 /* Last known good */
328 if(XHCV_EXT_CAPS_NEXT(READMEM32(extcapoff))) {
329 extcap = extcapoff + XHCV_EXT_CAPS_NEXT(READMEM32(extcapoff));
330 } else {
331 extcap = (IPTR) NULL;
333 } else {
334 extcap = XHCV_xECP(capability_readl(XHCI_HCCPARAMS1)) + (IPTR) unit->hc.capability_base;
335 mybug_unit(-1, ("searching from beginning %p\n", extcap));
338 /* Either the first (if exist) or the next from last known (if exist) else (IPTR) NULL */
339 while(extcap != (IPTR) NULL) {
340 if((XHCV_EXT_CAPS_ID(READMEM32(extcap)) == id)) {
341 mybug_unit(-1, ("found matching extended capability id at %lx\n", extcap));
342 break;
343 } else {
344 if(XHCV_EXT_CAPS_NEXT(READMEM32(extcap))) {
345 mybug_unit(-1, ("skipping extended capability id at %lx\n", extcap));
346 extcap += XHCV_EXT_CAPS_NEXT(READMEM32(extcap));
347 } else {
348 extcap = (IPTR) NULL;
349 break;
354 return (IPTR) extcap;
357 BOOL PCIXHCI_FindPorts(struct PCIXHCIUnit *unit) {
359 struct PCIXHCIPort *port = NULL;
361 IPTR cap_protocol = (IPTR) NULL;
363 ULONG portnum = 0, portcount = 0, temp, major, minor, po, pc;
365 /* (Re)build the port list of our unit */
366 ForeachNode(&unit->roothub.port_list, port) {
367 mybug_unit(-1, ("Deleting port %d named %s at %p\n", port->number, port->name, port));
368 REMOVE(port);
369 FreeVec(port);
372 portcount = XHCV_MaxPorts(capability_readl(XHCI_HCSPARAMS1));
373 mybug_unit(-1, ("Controller advertises port count to be %d\n", portcount));
375 do {
376 port = AllocVec(sizeof(struct PCIXHCIPort), MEMF_ANY|MEMF_CLEAR);
377 if(port == NULL) {
378 mybug_unit(-1, ("Failed to create new port structure\n"));
379 ForeachNode(&unit->roothub.port_list, port) {
380 mybug_unit(-1, ("Deleting port %d named %s at %p\n", port->number, port->name, port));
381 REMOVE(port);
382 FreeVec(port);
384 return FALSE;
385 } else {
386 port->node.ln_Type = NT_USER;
387 port->number = ++portnum;
388 AddTail(&unit->roothub.port_list, (struct Node *)port);
390 mybug_unit(-1, ("Created new port %d at %p\n", port->number, port));
392 } while(--portcount);
396 We may get more than one capability protocol header or just one (Which is case OnMyHardware(TM))
398 while((cap_protocol = PCIXHCI_SearchExtendedCap(unit, XHCI_EXT_CAPS_PROTOCOL, cap_protocol))) {
399 temp = READREG32(cap_protocol, XHCI_SPFD);
400 major = XHCV_SPFD_RMAJOR(temp);
401 minor = XHCV_SPFD_RMINOR(temp);
403 temp = READREG32(cap_protocol, XHCI_SPPORT);
404 po = XHCV_SPPORT_CPO(temp);
405 pc = XHCV_SPPORT_CPCNT(temp);
407 mybug_unit(-1, ("Version %ld.%ld port offset %d port count %d\n", major, minor, po, pc));
409 /* Iterate through port list and place the name for one that fits inside the offset and count */
410 ForeachNode(&unit->roothub.port_list, port) {
411 if( (port->number>=po) && (port->number<(po+pc)) ){
412 snprintf(port->name, 255, "%s USB %d.%d port %d", unit->node.ln_Name, major, minor, port->number);
413 port->node.ln_Name = (STRPTR)&port->name;
418 /* Check if any port is left unnamed */
419 ForeachNode(&unit->roothub.port_list, port) {
420 if(port->node.ln_Name == NULL){
421 snprintf(port->name, 255, "%s USB 2.0 port %d (guessed)", unit->node.ln_Name, port->number);
422 port->node.ln_Name = (STRPTR)&port->name;
426 return TRUE;
429 BOOL PCIXHCI_PortPower(struct PCIXHCIUnit *unit, ULONG portnum, BOOL poweron) {
431 Check for port power control
433 if(capability_readl(XHCI_HCCPARAMS1) & XHCF_PPC) {
434 mybug_unit(-1, ("Port has power switch\n"));
435 } else {
436 mybug_unit(-1, ("Port does not have power switch\n"));
439 /* We have power on by default, skip this for now */
441 ULONG portsc;
443 portsc = operational_readl(XHCI_PORTSC(portnum));
445 mybug_unit(-1, ("portsc = %08x\n", portsc));
446 portsc = portsc & 0x7F00FF08;
447 mybug_unit(-1, ("portsc = %08x\n", portsc));
449 if(poweron) {
450 portsc = (portsc | XHCF_PS_PP);
451 mybug_unit(-1, ("Port powering up\n"));
452 } else {
453 mybug_unit(-1, ("Port powering down\n"));
454 portsc = (portsc & ~XHCF_PS_PP);
457 operational_writel(XHCI_PORTSC(portnum), portsc);
459 portsc = operational_readl(XHCI_PORTSC(portnum));
460 mybug_unit(-1, ("portsc = %08x\n", portsc));
462 return TRUE;