Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 1 | // Code for handling EHCI USB controllers. |
| 2 | // |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 3 | // Copyright (C) 2010-2013 Kevin O'Connor <kevin@koconnor.net> |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 4 | // |
| 5 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
| 6 | |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 7 | #include "biosvar.h" // GET_LOWFLAT |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 8 | #include "config.h" // CONFIG_* |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 9 | #include "output.h" // dprintf |
| 10 | #include "malloc.h" // free |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 11 | #include "memmap.h" // PAGE_SIZE |
Kevin O'Connor | 4d8510c | 2016-02-03 01:28:20 -0500 | [diff] [blame] | 12 | #include "pcidevice.h" // foreachpci |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 13 | #include "pci_ids.h" // PCI_CLASS_SERIAL_USB_UHCI |
| 14 | #include "pci_regs.h" // PCI_BASE_ADDRESS_0 |
Kevin O'Connor | fa9c66a | 2013-09-14 19:10:40 -0400 | [diff] [blame] | 15 | #include "string.h" // memset |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 16 | #include "usb.h" // struct usb_s |
| 17 | #include "usb-ehci.h" // struct ehci_qh |
Kevin O'Connor | 2d2fa31 | 2013-09-14 21:55:26 -0400 | [diff] [blame] | 18 | #include "util.h" // msleep |
Kevin O'Connor | b9c6a96 | 2013-09-14 13:01:30 -0400 | [diff] [blame] | 19 | #include "x86.h" // readl |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 20 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 21 | struct usb_ehci_s { |
| 22 | struct usb_s usb; |
| 23 | struct ehci_caps *caps; |
| 24 | struct ehci_regs *regs; |
| 25 | struct ehci_qh *async_qh; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 26 | int checkports; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 27 | }; |
| 28 | |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 29 | struct ehci_pipe { |
| 30 | struct ehci_qh qh; |
| 31 | struct ehci_qtd *next_td, *tds; |
| 32 | void *data; |
| 33 | struct usb_pipe pipe; |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 34 | }; |
| 35 | |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 36 | static int PendingEHCI; |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 37 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 38 | |
| 39 | /**************************************************************** |
| 40 | * Root hub |
| 41 | ****************************************************************/ |
| 42 | |
| 43 | #define EHCI_TIME_POSTPOWER 20 |
| 44 | #define EHCI_TIME_POSTRESET 2 |
| 45 | |
Kevin O'Connor | d28b0fe | 2010-03-28 15:11:19 -0400 | [diff] [blame] | 46 | // Check if device attached to port |
| 47 | static int |
| 48 | ehci_hub_detect(struct usbhub_s *hub, u32 port) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 49 | { |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 50 | struct usb_ehci_s *cntl = container_of(hub->cntl, struct usb_ehci_s, usb); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 51 | u32 *portreg = &cntl->regs->portsc[port]; |
| 52 | u32 portsc = readl(portreg); |
| 53 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 54 | if (!(portsc & PORT_CONNECT)) |
| 55 | // No device present |
Kevin O'Connor | 0f68130 | 2014-09-10 11:33:01 -0400 | [diff] [blame] | 56 | return 0; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 57 | |
| 58 | if ((portsc & PORT_LINESTATUS_MASK) == PORT_LINESTATUS_KSTATE) { |
| 59 | // low speed device |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 60 | writel(portreg, portsc | PORT_OWNER); |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 61 | return -1; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | // XXX - if just powered up, need to wait for USB_TIME_ATTDB? |
| 65 | |
Kevin O'Connor | d28b0fe | 2010-03-28 15:11:19 -0400 | [diff] [blame] | 66 | // Begin reset on port |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 67 | portsc = (portsc & ~PORT_PE) | PORT_RESET; |
| 68 | writel(portreg, portsc); |
| 69 | msleep(USB_TIME_DRSTR); |
Kevin O'Connor | 0f68130 | 2014-09-10 11:33:01 -0400 | [diff] [blame] | 70 | return 1; |
Kevin O'Connor | d28b0fe | 2010-03-28 15:11:19 -0400 | [diff] [blame] | 71 | } |
| 72 | |
| 73 | // Reset device on port |
| 74 | static int |
| 75 | ehci_hub_reset(struct usbhub_s *hub, u32 port) |
| 76 | { |
| 77 | struct usb_ehci_s *cntl = container_of(hub->cntl, struct usb_ehci_s, usb); |
| 78 | u32 *portreg = &cntl->regs->portsc[port]; |
| 79 | u32 portsc = readl(portreg); |
| 80 | |
| 81 | // Finish reset on port |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 82 | portsc &= ~PORT_RESET; |
| 83 | writel(portreg, portsc); |
| 84 | msleep(EHCI_TIME_POSTRESET); |
| 85 | |
| 86 | portsc = readl(portreg); |
| 87 | if (!(portsc & PORT_CONNECT)) |
| 88 | // No longer connected |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 89 | return -1; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 90 | if (!(portsc & PORT_PE)) { |
| 91 | // full speed device |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 92 | writel(portreg, portsc | PORT_OWNER); |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 93 | return -1; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 94 | } |
Kevin O'Connor | 87ab2fb | 2010-03-20 23:25:11 -0400 | [diff] [blame] | 95 | |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 96 | return USB_HIGHSPEED; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 97 | } |
| 98 | |
Kevin O'Connor | d28b0fe | 2010-03-28 15:11:19 -0400 | [diff] [blame] | 99 | // Disable port |
| 100 | static void |
| 101 | ehci_hub_disconnect(struct usbhub_s *hub, u32 port) |
| 102 | { |
| 103 | struct usb_ehci_s *cntl = container_of(hub->cntl, struct usb_ehci_s, usb); |
| 104 | u32 *portreg = &cntl->regs->portsc[port]; |
| 105 | u32 portsc = readl(portreg); |
| 106 | writel(portreg, portsc & ~PORT_PE); |
| 107 | } |
| 108 | |
| 109 | static struct usbhub_op_s ehci_HubOp = { |
| 110 | .detect = ehci_hub_detect, |
| 111 | .reset = ehci_hub_reset, |
| 112 | .disconnect = ehci_hub_disconnect, |
| 113 | }; |
| 114 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 115 | // Find any devices connected to the root hub. |
| 116 | static int |
| 117 | check_ehci_ports(struct usb_ehci_s *cntl) |
| 118 | { |
Kevin O'Connor | 5ac31b8 | 2014-09-10 10:02:20 -0400 | [diff] [blame] | 119 | // Power up ports. |
| 120 | int i; |
| 121 | for (i=0; i<cntl->checkports; i++) { |
| 122 | u32 *portreg = &cntl->regs->portsc[i]; |
| 123 | u32 portsc = readl(portreg); |
| 124 | if (!(portsc & PORT_POWER)) { |
| 125 | portsc |= PORT_POWER; |
| 126 | writel(portreg, portsc); |
| 127 | } |
| 128 | } |
| 129 | msleep(EHCI_TIME_POSTPOWER); |
| 130 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 131 | struct usbhub_s hub; |
| 132 | memset(&hub, 0, sizeof(hub)); |
| 133 | hub.cntl = &cntl->usb; |
Kevin O'Connor | d28b0fe | 2010-03-28 15:11:19 -0400 | [diff] [blame] | 134 | hub.portcount = cntl->checkports; |
| 135 | hub.op = &ehci_HubOp; |
| 136 | usb_enumerate(&hub); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 137 | return hub.devcount; |
| 138 | } |
| 139 | |
| 140 | |
| 141 | /**************************************************************** |
| 142 | * Setup |
| 143 | ****************************************************************/ |
| 144 | |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 145 | // Wait for next USB async frame to start - for ensuring safe memory release. |
| 146 | static void |
| 147 | ehci_waittick(struct usb_ehci_s *cntl) |
| 148 | { |
| 149 | if (MODE16) { |
| 150 | msleep(10); |
| 151 | return; |
| 152 | } |
| 153 | // Wait for access to "doorbell" |
| 154 | barrier(); |
| 155 | u32 cmd, sts; |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 156 | u32 end = timer_calc(100); |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 157 | for (;;) { |
| 158 | sts = readl(&cntl->regs->usbsts); |
| 159 | if (!(sts & STS_IAA)) { |
| 160 | cmd = readl(&cntl->regs->usbcmd); |
| 161 | if (!(cmd & CMD_IAAD)) |
| 162 | break; |
| 163 | } |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 164 | if (timer_check(end)) { |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 165 | warn_timeout(); |
| 166 | return; |
| 167 | } |
| 168 | yield(); |
| 169 | } |
| 170 | // Ring "doorbell" |
| 171 | writel(&cntl->regs->usbcmd, cmd | CMD_IAAD); |
| 172 | // Wait for completion |
| 173 | for (;;) { |
| 174 | sts = readl(&cntl->regs->usbsts); |
| 175 | if (sts & STS_IAA) |
| 176 | break; |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 177 | if (timer_check(end)) { |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 178 | warn_timeout(); |
| 179 | return; |
| 180 | } |
| 181 | yield(); |
| 182 | } |
| 183 | // Ack completion |
| 184 | writel(&cntl->regs->usbsts, STS_IAA); |
| 185 | } |
| 186 | |
| 187 | static void |
| 188 | ehci_free_pipes(struct usb_ehci_s *cntl) |
| 189 | { |
| 190 | dprintf(7, "ehci_free_pipes %p\n", cntl); |
| 191 | |
| 192 | struct ehci_qh *start = cntl->async_qh; |
| 193 | struct ehci_qh *pos = start; |
| 194 | for (;;) { |
| 195 | struct ehci_qh *next = (void*)(pos->next & ~EHCI_PTR_BITS); |
| 196 | if (next == start) |
| 197 | break; |
| 198 | struct ehci_pipe *pipe = container_of(next, struct ehci_pipe, qh); |
Kevin O'Connor | 2bfd170 | 2014-10-16 12:31:42 -0400 | [diff] [blame] | 199 | if (usb_is_freelist(&cntl->usb, &pipe->pipe)) |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 200 | pos->next = next->next; |
| 201 | else |
| 202 | pos = next; |
| 203 | } |
| 204 | ehci_waittick(cntl); |
| 205 | for (;;) { |
| 206 | struct usb_pipe *usbpipe = cntl->usb.freelist; |
| 207 | if (!usbpipe) |
| 208 | break; |
| 209 | cntl->usb.freelist = usbpipe->freenext; |
| 210 | struct ehci_pipe *pipe = container_of(usbpipe, struct ehci_pipe, pipe); |
| 211 | free(pipe); |
| 212 | } |
| 213 | } |
| 214 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 215 | static void |
| 216 | configure_ehci(void *data) |
| 217 | { |
| 218 | struct usb_ehci_s *cntl = data; |
| 219 | |
| 220 | // Allocate ram for schedule storage |
| 221 | struct ehci_framelist *fl = memalign_high(sizeof(*fl), sizeof(*fl)); |
| 222 | struct ehci_qh *intr_qh = memalign_high(EHCI_QH_ALIGN, sizeof(*intr_qh)); |
| 223 | struct ehci_qh *async_qh = memalign_high(EHCI_QH_ALIGN, sizeof(*async_qh)); |
| 224 | if (!fl || !intr_qh || !async_qh) { |
| 225 | warn_noalloc(); |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 226 | PendingEHCI--; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 227 | goto fail; |
| 228 | } |
| 229 | |
| 230 | // XXX - check for halted? |
| 231 | |
| 232 | // Reset the HC |
| 233 | u32 cmd = readl(&cntl->regs->usbcmd); |
| 234 | writel(&cntl->regs->usbcmd, (cmd & ~(CMD_ASE | CMD_PSE)) | CMD_HCRESET); |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 235 | u32 end = timer_calc(250); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 236 | for (;;) { |
| 237 | cmd = readl(&cntl->regs->usbcmd); |
| 238 | if (!(cmd & CMD_HCRESET)) |
| 239 | break; |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 240 | if (timer_check(end)) { |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 241 | warn_timeout(); |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 242 | PendingEHCI--; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 243 | goto fail; |
| 244 | } |
Kevin O'Connor | 698d3f9 | 2010-04-17 16:59:12 -0400 | [diff] [blame] | 245 | yield(); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 246 | } |
| 247 | |
| 248 | // Disable interrupts (just to be safe). |
| 249 | writel(&cntl->regs->usbintr, 0); |
| 250 | |
| 251 | // Set schedule to point to primary intr queue head |
| 252 | memset(intr_qh, 0, sizeof(*intr_qh)); |
| 253 | intr_qh->next = EHCI_PTR_TERM; |
| 254 | intr_qh->info2 = (0x01 << QH_SMASK_SHIFT); |
| 255 | intr_qh->token = QTD_STS_HALT; |
| 256 | intr_qh->qtd_next = intr_qh->alt_next = EHCI_PTR_TERM; |
| 257 | int i; |
| 258 | for (i=0; i<ARRAY_SIZE(fl->links); i++) |
| 259 | fl->links[i] = (u32)intr_qh | EHCI_PTR_QH; |
| 260 | writel(&cntl->regs->periodiclistbase, (u32)fl); |
| 261 | |
| 262 | // Set async list to point to primary async queue head |
| 263 | memset(async_qh, 0, sizeof(*async_qh)); |
| 264 | async_qh->next = (u32)async_qh | EHCI_PTR_QH; |
| 265 | async_qh->info1 = QH_HEAD; |
| 266 | async_qh->token = QTD_STS_HALT; |
| 267 | async_qh->qtd_next = async_qh->alt_next = EHCI_PTR_TERM; |
| 268 | cntl->async_qh = async_qh; |
| 269 | writel(&cntl->regs->asynclistbase, (u32)async_qh); |
| 270 | |
| 271 | // Enable queues |
| 272 | writel(&cntl->regs->usbcmd, cmd | CMD_ASE | CMD_PSE | CMD_RUN); |
| 273 | |
| 274 | // Set default of high speed for root hub. |
| 275 | writel(&cntl->regs->configflag, 1); |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 276 | PendingEHCI--; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 277 | |
| 278 | // Find devices |
| 279 | int count = check_ehci_ports(cntl); |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 280 | ehci_free_pipes(cntl); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 281 | if (count) |
| 282 | // Success |
| 283 | return; |
| 284 | |
| 285 | // No devices found - shutdown and free controller. |
| 286 | writel(&cntl->regs->usbcmd, cmd & ~CMD_RUN); |
| 287 | msleep(4); // 2ms to stop reading memory - XXX |
| 288 | fail: |
| 289 | free(fl); |
| 290 | free(intr_qh); |
| 291 | free(async_qh); |
| 292 | free(cntl); |
| 293 | } |
| 294 | |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 295 | static void |
| 296 | ehci_controller_setup(struct pci_device *pci) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 297 | { |
Kevin O'Connor | 706eb89 | 2016-02-02 22:28:06 -0500 | [diff] [blame] | 298 | struct ehci_caps *caps = pci_enable_membar(pci, PCI_BASE_ADDRESS_0); |
| 299 | if (!caps) |
| 300 | return; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 301 | u32 hcc_params = readl(&caps->hccparams); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 302 | |
| 303 | struct usb_ehci_s *cntl = malloc_tmphigh(sizeof(*cntl)); |
Kevin O'Connor | ffdcd3a | 2011-07-10 15:48:00 -0400 | [diff] [blame] | 304 | if (!cntl) { |
| 305 | warn_noalloc(); |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 306 | return; |
Kevin O'Connor | ffdcd3a | 2011-07-10 15:48:00 -0400 | [diff] [blame] | 307 | } |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 308 | memset(cntl, 0, sizeof(*cntl)); |
Kevin O'Connor | 8ff8e01 | 2011-07-09 14:11:21 -0400 | [diff] [blame] | 309 | cntl->usb.pci = pci; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 310 | cntl->usb.type = USB_TYPE_EHCI; |
| 311 | cntl->caps = caps; |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 312 | cntl->checkports = readl(&cntl->caps->hcsparams) & HCS_N_PORTS_MASK; |
Avik Sil | 7cac600 | 2013-02-14 10:54:57 +0530 | [diff] [blame] | 313 | cntl->regs = (void*)caps + readb(&caps->caplength); |
Sven Schnelle | a3fa66c | 2012-06-12 09:20:23 +0200 | [diff] [blame] | 314 | if (hcc_params & HCC_64BIT_ADDR) |
| 315 | cntl->regs->ctrldssegment = 0; |
Kevin O'Connor | e2d6fdd | 2014-09-10 11:04:26 -0400 | [diff] [blame] | 316 | PendingEHCI++; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 317 | |
Kevin O'Connor | 7b67300 | 2016-02-03 03:03:15 -0500 | [diff] [blame] | 318 | dprintf(1, "EHCI init on dev %pP (regs=%p)\n", pci, cntl->regs); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 319 | |
Kevin O'Connor | 706eb89 | 2016-02-02 22:28:06 -0500 | [diff] [blame] | 320 | pci_enable_busmaster(pci); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 321 | |
| 322 | // XXX - check for and disable SMM control? |
| 323 | |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 324 | run_thread(configure_ehci, cntl); |
| 325 | } |
| 326 | |
| 327 | void |
| 328 | ehci_setup(void) |
| 329 | { |
| 330 | if (! CONFIG_USB_EHCI) |
| 331 | return; |
| 332 | struct pci_device *pci; |
| 333 | foreachpci(pci) { |
| 334 | if (pci_classprog(pci) == PCI_CLASS_SERIAL_USB_EHCI) |
| 335 | ehci_controller_setup(pci); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 336 | } |
Kevin O'Connor | 32a2b0e | 2016-02-02 14:28:13 -0500 | [diff] [blame] | 337 | } |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 338 | |
Kevin O'Connor | 32a2b0e | 2016-02-02 14:28:13 -0500 | [diff] [blame] | 339 | // Wait for all EHCI controllers to initialize. This forces OHCI/UHCI |
| 340 | // setup to always be after any EHCI ports are routed to EHCI. |
| 341 | void |
| 342 | ehci_wait_controllers(void) |
| 343 | { |
| 344 | while (CONFIG_USB_EHCI && CONFIG_THREADS && PendingEHCI) |
Kevin O'Connor | ec443ff | 2013-12-05 18:43:20 -0500 | [diff] [blame] | 345 | yield(); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 346 | } |
| 347 | |
| 348 | |
| 349 | /**************************************************************** |
| 350 | * End point communication |
| 351 | ****************************************************************/ |
| 352 | |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 353 | // Setup fields in qh |
| 354 | static void |
| 355 | ehci_desc2pipe(struct ehci_pipe *pipe, struct usbdevice_s *usbdev |
| 356 | , struct usb_endpoint_descriptor *epdesc) |
| 357 | { |
| 358 | usb_desc2pipe(&pipe->pipe, usbdev, epdesc); |
| 359 | |
Kevin O'Connor | 32d68ab | 2012-03-10 21:07:26 -0500 | [diff] [blame] | 360 | pipe->qh.info1 = ((pipe->pipe.maxpacket << QH_MAXPACKET_SHIFT) |
| 361 | | (pipe->pipe.speed << QH_SPEED_SHIFT) |
| 362 | | (pipe->pipe.ep << QH_EP_SHIFT) |
| 363 | | (pipe->pipe.devaddr << QH_DEVADDR_SHIFT)); |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 364 | |
| 365 | pipe->qh.info2 = (1 << QH_MULT_SHIFT); |
| 366 | struct usbdevice_s *hubdev = usbdev->hub->usbdev; |
| 367 | if (hubdev) { |
| 368 | struct ehci_pipe *hpipe = container_of( |
| 369 | hubdev->defpipe, struct ehci_pipe, pipe); |
| 370 | if (hpipe->pipe.speed == USB_HIGHSPEED) |
Kevin O'Connor | 67d1fbe | 2014-09-12 11:59:23 -0400 | [diff] [blame] | 371 | pipe->qh.info2 |= (((usbdev->port+1) << QH_HUBPORT_SHIFT) |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 372 | | (hpipe->pipe.devaddr << QH_HUBADDR_SHIFT)); |
| 373 | else |
| 374 | pipe->qh.info2 = hpipe->qh.info2; |
| 375 | } |
| 376 | |
| 377 | u8 eptype = epdesc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; |
| 378 | if (eptype == USB_ENDPOINT_XFER_CONTROL) |
| 379 | pipe->qh.info1 |= ((pipe->pipe.speed != USB_HIGHSPEED ? QH_CONTROL : 0) |
| 380 | | QH_TOGGLECONTROL); |
| 381 | else if (eptype == USB_ENDPOINT_XFER_INT) |
| 382 | pipe->qh.info2 |= (0x01 << QH_SMASK_SHIFT) | (0x1c << QH_CMASK_SHIFT); |
| 383 | } |
| 384 | |
Kevin O'Connor | c3d96c2 | 2012-03-10 09:03:25 -0500 | [diff] [blame] | 385 | static struct usb_pipe * |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 386 | ehci_alloc_intr_pipe(struct usbdevice_s *usbdev |
| 387 | , struct usb_endpoint_descriptor *epdesc) |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 388 | { |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 389 | struct usb_ehci_s *cntl = container_of( |
| 390 | usbdev->hub->cntl, struct usb_ehci_s, usb); |
Kevin O'Connor | 98cdad3 | 2014-10-16 12:05:09 -0400 | [diff] [blame] | 391 | int frameexp = usb_get_period(usbdev, epdesc); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 392 | dprintf(7, "ehci_alloc_intr_pipe %p %d\n", &cntl->usb, frameexp); |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 393 | |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 394 | if (frameexp > 10) |
| 395 | frameexp = 10; |
| 396 | int maxpacket = epdesc->wMaxPacketSize; |
| 397 | // Determine number of entries needed for 2 timer ticks. |
| 398 | int ms = 1<<frameexp; |
Kevin O'Connor | 6901337 | 2013-07-20 12:08:48 -0400 | [diff] [blame] | 399 | int count = DIV_ROUND_UP(ticks_to_ms(2), ms); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 400 | struct ehci_pipe *pipe = memalign_low(EHCI_QH_ALIGN, sizeof(*pipe)); |
| 401 | struct ehci_qtd *tds = memalign_low(EHCI_QTD_ALIGN, sizeof(*tds) * count); |
| 402 | void *data = malloc_low(maxpacket * count); |
| 403 | if (!pipe || !tds || !data) { |
| 404 | warn_noalloc(); |
| 405 | goto fail; |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 406 | } |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 407 | memset(pipe, 0, sizeof(*pipe)); |
Kevin O'Connor | 7d2fd49 | 2014-01-17 18:43:10 -0500 | [diff] [blame] | 408 | memset(tds, 0, sizeof(*tds) * count); |
| 409 | memset(data, 0, maxpacket * count); |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 410 | ehci_desc2pipe(pipe, usbdev, epdesc); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 411 | pipe->next_td = pipe->tds = tds; |
| 412 | pipe->data = data; |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 413 | pipe->qh.qtd_next = (u32)tds; |
| 414 | |
| 415 | int i; |
| 416 | for (i=0; i<count; i++) { |
| 417 | struct ehci_qtd *td = &tds[i]; |
| 418 | td->qtd_next = (i==count-1 ? (u32)tds : (u32)&td[1]); |
| 419 | td->alt_next = EHCI_PTR_TERM; |
| 420 | td->token = (ehci_explen(maxpacket) | QTD_STS_ACTIVE |
| 421 | | QTD_PID_IN | ehci_maxerr(3)); |
| 422 | td->buf[0] = (u32)data + maxpacket * i; |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 423 | } |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 424 | |
| 425 | // Add to interrupt schedule. |
| 426 | struct ehci_framelist *fl = (void*)readl(&cntl->regs->periodiclistbase); |
| 427 | if (frameexp == 0) { |
| 428 | // Add to existing interrupt entry. |
| 429 | struct ehci_qh *intr_qh = (void*)(fl->links[0] & ~EHCI_PTR_BITS); |
| 430 | pipe->qh.next = intr_qh->next; |
| 431 | barrier(); |
| 432 | intr_qh->next = (u32)&pipe->qh | EHCI_PTR_QH; |
| 433 | } else { |
| 434 | int startpos = 1<<(frameexp-1); |
| 435 | pipe->qh.next = fl->links[startpos]; |
| 436 | barrier(); |
| 437 | for (i=startpos; i<ARRAY_SIZE(fl->links); i+=ms) |
| 438 | fl->links[i] = (u32)&pipe->qh | EHCI_PTR_QH; |
| 439 | } |
| 440 | |
| 441 | return &pipe->pipe; |
| 442 | fail: |
| 443 | free(pipe); |
| 444 | free(tds); |
| 445 | free(data); |
| 446 | return NULL; |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 447 | } |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 448 | |
Kevin O'Connor | 01de9bd | 2012-03-05 22:41:21 -0500 | [diff] [blame] | 449 | struct usb_pipe * |
Kevin O'Connor | 277ea6f | 2014-10-16 13:40:42 -0400 | [diff] [blame] | 450 | ehci_realloc_pipe(struct usbdevice_s *usbdev, struct usb_pipe *upipe |
| 451 | , struct usb_endpoint_descriptor *epdesc) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 452 | { |
| 453 | if (! CONFIG_USB_EHCI) |
| 454 | return NULL; |
Kevin O'Connor | 277ea6f | 2014-10-16 13:40:42 -0400 | [diff] [blame] | 455 | usb_add_freelist(upipe); |
| 456 | if (!epdesc) |
| 457 | return NULL; |
Kevin O'Connor | c3d96c2 | 2012-03-10 09:03:25 -0500 | [diff] [blame] | 458 | u8 eptype = epdesc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; |
| 459 | if (eptype == USB_ENDPOINT_XFER_INT) |
| 460 | return ehci_alloc_intr_pipe(usbdev, epdesc); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 461 | struct usb_ehci_s *cntl = container_of( |
Kevin O'Connor | 54bf207 | 2012-03-09 07:52:33 -0500 | [diff] [blame] | 462 | usbdev->hub->cntl, struct usb_ehci_s, usb); |
Kevin O'Connor | 54bf207 | 2012-03-09 07:52:33 -0500 | [diff] [blame] | 463 | dprintf(7, "ehci_alloc_async_pipe %p %d\n", &cntl->usb, eptype); |
| 464 | |
Kevin O'Connor | 2bfd170 | 2014-10-16 12:31:42 -0400 | [diff] [blame] | 465 | struct usb_pipe *usbpipe = usb_get_freelist(&cntl->usb, eptype); |
Kevin O'Connor | 54bf207 | 2012-03-09 07:52:33 -0500 | [diff] [blame] | 466 | if (usbpipe) { |
| 467 | // Use previously allocated pipe. |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 468 | struct ehci_pipe *pipe = container_of(usbpipe, struct ehci_pipe, pipe); |
| 469 | ehci_desc2pipe(pipe, usbdev, epdesc); |
Kevin O'Connor | 54bf207 | 2012-03-09 07:52:33 -0500 | [diff] [blame] | 470 | return usbpipe; |
| 471 | } |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 472 | |
| 473 | // Allocate a new queue head. |
| 474 | struct ehci_pipe *pipe; |
Kevin O'Connor | 54bf207 | 2012-03-09 07:52:33 -0500 | [diff] [blame] | 475 | if (eptype == USB_ENDPOINT_XFER_CONTROL) |
Kevin O'Connor | 402f9a0 | 2012-02-20 12:54:49 -0500 | [diff] [blame] | 476 | pipe = memalign_tmphigh(EHCI_QH_ALIGN, sizeof(*pipe)); |
| 477 | else |
| 478 | pipe = memalign_low(EHCI_QH_ALIGN, sizeof(*pipe)); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 479 | if (!pipe) { |
| 480 | warn_noalloc(); |
| 481 | return NULL; |
| 482 | } |
| 483 | memset(pipe, 0, sizeof(*pipe)); |
Kevin O'Connor | 8915ed2 | 2012-03-10 10:09:56 -0500 | [diff] [blame] | 484 | ehci_desc2pipe(pipe, usbdev, epdesc); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 485 | pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 486 | |
| 487 | // Add queue head to controller list. |
| 488 | struct ehci_qh *async_qh = cntl->async_qh; |
| 489 | pipe->qh.next = async_qh->next; |
| 490 | barrier(); |
| 491 | async_qh->next = (u32)&pipe->qh | EHCI_PTR_QH; |
| 492 | return &pipe->pipe; |
| 493 | } |
| 494 | |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 495 | static void |
| 496 | ehci_reset_pipe(struct ehci_pipe *pipe) |
| 497 | { |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 498 | SET_LOWFLAT(pipe->qh.qtd_next, EHCI_PTR_TERM); |
| 499 | SET_LOWFLAT(pipe->qh.alt_next, EHCI_PTR_TERM); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 500 | barrier(); |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 501 | SET_LOWFLAT(pipe->qh.token, GET_LOWFLAT(pipe->qh.token) & QTD_TOGGLE); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 502 | } |
| 503 | |
| 504 | static int |
Kevin O'Connor | ab9d771 | 2014-06-14 12:48:35 -0400 | [diff] [blame] | 505 | ehci_wait_td(struct ehci_pipe *pipe, struct ehci_qtd *td, u32 end) |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 506 | { |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 507 | u32 status; |
| 508 | for (;;) { |
| 509 | status = td->token; |
| 510 | if (!(status & QTD_STS_ACTIVE)) |
| 511 | break; |
Kevin O'Connor | 018bdd7 | 2013-07-20 18:22:57 -0400 | [diff] [blame] | 512 | if (timer_check(end)) { |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 513 | u32 cur = GET_LOWFLAT(pipe->qh.current); |
| 514 | u32 tok = GET_LOWFLAT(pipe->qh.token); |
| 515 | u32 next = GET_LOWFLAT(pipe->qh.qtd_next); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 516 | warn_timeout(); |
| 517 | dprintf(1, "ehci pipe=%p cur=%08x tok=%08x next=%x td=%p status=%x\n" |
| 518 | , pipe, cur, tok, next, td, status); |
| 519 | ehci_reset_pipe(pipe); |
| 520 | struct usb_ehci_s *cntl = container_of( |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 521 | GET_LOWFLAT(pipe->pipe.cntl), struct usb_ehci_s, usb); |
Kevin O'Connor | 6152c32 | 2012-03-10 08:53:58 -0500 | [diff] [blame] | 522 | ehci_waittick(cntl); |
| 523 | return -1; |
| 524 | } |
| 525 | yield(); |
| 526 | } |
| 527 | if (status & QTD_STS_HALT) { |
| 528 | dprintf(1, "ehci_wait_td error - status=%x\n", status); |
| 529 | ehci_reset_pipe(pipe); |
| 530 | return -2; |
| 531 | } |
| 532 | return 0; |
| 533 | } |
| 534 | |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 535 | static void |
| 536 | ehci_fill_tdbuf(struct ehci_qtd *td, u32 dest, int transfer) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 537 | { |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 538 | u32 *pos = td->buf, end = dest + transfer; |
| 539 | for (; dest < end; dest = ALIGN_DOWN(dest + PAGE_SIZE, PAGE_SIZE)) |
| 540 | *pos++ = dest; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 541 | } |
| 542 | |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 543 | #define STACKQTDS 6 |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 544 | |
| 545 | int |
Kevin O'Connor | 2891a83 | 2014-12-31 17:41:14 -0500 | [diff] [blame] | 546 | ehci_send_pipe(struct usb_pipe *p, int dir, const void *cmd |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 547 | , void *data, int datasize) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 548 | { |
| 549 | if (! CONFIG_USB_EHCI) |
| 550 | return -1; |
| 551 | struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 552 | dprintf(7, "ehci_send_pipe qh=%p dir=%d data=%p size=%d\n" |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 553 | , &pipe->qh, dir, data, datasize); |
| 554 | |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 555 | // Allocate tds on stack (with required alignment) |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 556 | u8 tdsbuf[sizeof(struct ehci_qtd) * STACKQTDS + EHCI_QTD_ALIGN - 1]; |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 557 | struct ehci_qtd *tds = (void*)ALIGN((u32)tdsbuf, EHCI_QTD_ALIGN), *td = tds; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 558 | memset(tds, 0, sizeof(*tds) * STACKQTDS); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 559 | |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 560 | // Setup transfer descriptors |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 561 | u16 maxpacket = GET_LOWFLAT(pipe->pipe.maxpacket); |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 562 | u32 toggle = 0; |
| 563 | if (cmd) { |
| 564 | // Send setup pid on control transfers |
| 565 | td->qtd_next = (u32)MAKE_FLATPTR(GET_SEG(SS), td+1); |
| 566 | td->alt_next = EHCI_PTR_TERM; |
Kevin O'Connor | 2891a83 | 2014-12-31 17:41:14 -0500 | [diff] [blame] | 567 | td->token = (ehci_explen(USB_CONTROL_SETUP_SIZE) | QTD_STS_ACTIVE |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 568 | | QTD_PID_SETUP | ehci_maxerr(3)); |
Kevin O'Connor | 2891a83 | 2014-12-31 17:41:14 -0500 | [diff] [blame] | 569 | ehci_fill_tdbuf(td, (u32)cmd, USB_CONTROL_SETUP_SIZE); |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 570 | td++; |
| 571 | toggle = QTD_TOGGLE; |
| 572 | } |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 573 | u32 dest = (u32)data, dataend = dest + datasize; |
| 574 | while (dest < dataend) { |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 575 | // Send data pids |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 576 | if (td >= &tds[STACKQTDS]) { |
| 577 | warn_noalloc(); |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 578 | return -1; |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 579 | } |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 580 | int maxtransfer = 5*PAGE_SIZE - (dest & (PAGE_SIZE-1)); |
| 581 | int transfer = dataend - dest; |
| 582 | if (transfer > maxtransfer) |
| 583 | transfer = ALIGN_DOWN(maxtransfer, maxpacket); |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 584 | td->qtd_next = (u32)MAKE_FLATPTR(GET_SEG(SS), td+1); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 585 | td->alt_next = EHCI_PTR_TERM; |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 586 | td->token = (ehci_explen(transfer) | toggle | QTD_STS_ACTIVE |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 587 | | (dir ? QTD_PID_IN : QTD_PID_OUT) | ehci_maxerr(3)); |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 588 | ehci_fill_tdbuf(td, dest, transfer); |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 589 | td++; |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 590 | dest += transfer; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 591 | } |
Kevin O'Connor | abd6742 | 2014-12-30 21:16:04 -0500 | [diff] [blame] | 592 | if (cmd) { |
| 593 | // Send status pid on control transfers |
| 594 | if (td >= &tds[STACKQTDS]) { |
| 595 | warn_noalloc(); |
| 596 | return -1; |
| 597 | } |
| 598 | td->qtd_next = EHCI_PTR_TERM; |
| 599 | td->alt_next = EHCI_PTR_TERM; |
| 600 | td->token = (QTD_TOGGLE | QTD_STS_ACTIVE |
| 601 | | (dir ? QTD_PID_OUT : QTD_PID_IN) | ehci_maxerr(3)); |
| 602 | td++; |
| 603 | } |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 604 | |
| 605 | // Transfer data |
| 606 | (td-1)->qtd_next = EHCI_PTR_TERM; |
| 607 | barrier(); |
| 608 | SET_LOWFLAT(pipe->qh.qtd_next, (u32)MAKE_FLATPTR(GET_SEG(SS), tds)); |
Kevin O'Connor | 1bd976b | 2015-01-01 12:51:04 -0500 | [diff] [blame] | 609 | u32 end = timer_calc(usb_xfer_time(p, datasize)); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 610 | int i; |
Kevin O'Connor | 7f66e7b | 2014-12-30 20:50:44 -0500 | [diff] [blame] | 611 | for (i=0, td=tds; i<STACKQTDS; i++, td++) { |
Kevin O'Connor | ab9d771 | 2014-06-14 12:48:35 -0400 | [diff] [blame] | 612 | int ret = ehci_wait_td(pipe, td, end); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 613 | if (ret) |
Kevin O'Connor | 49ecf69 | 2011-11-19 14:21:51 -0500 | [diff] [blame] | 614 | return -1; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 615 | } |
| 616 | |
| 617 | return 0; |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 618 | } |
| 619 | |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 620 | int |
| 621 | ehci_poll_intr(struct usb_pipe *p, void *data) |
| 622 | { |
| 623 | ASSERT16(); |
| 624 | if (! CONFIG_USB_EHCI) |
| 625 | return -1; |
| 626 | struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe); |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 627 | struct ehci_qtd *td = GET_LOWFLAT(pipe->next_td); |
| 628 | u32 token = GET_LOWFLAT(td->token); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 629 | if (token & QTD_STS_ACTIVE) |
| 630 | // No intrs found. |
| 631 | return -1; |
| 632 | // XXX - check for errors. |
| 633 | |
| 634 | // Copy data. |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 635 | int maxpacket = GET_LOWFLAT(pipe->pipe.maxpacket); |
| 636 | int pos = td - GET_LOWFLAT(pipe->tds); |
| 637 | void *tddata = GET_LOWFLAT(pipe->data) + maxpacket * pos; |
| 638 | memcpy_far(GET_SEG(SS), data, SEG_LOW, LOWFLAT2LOW(tddata), maxpacket); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 639 | |
| 640 | // Reenable this td. |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 641 | struct ehci_qtd *next = (void*)(GET_LOWFLAT(td->qtd_next) & ~EHCI_PTR_BITS); |
| 642 | SET_LOWFLAT(pipe->next_td, next); |
| 643 | SET_LOWFLAT(td->buf[0], (u32)tddata); |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 644 | barrier(); |
Kevin O'Connor | 4f6563e | 2012-05-19 22:42:51 -0400 | [diff] [blame] | 645 | SET_LOWFLAT(td->token, (ehci_explen(maxpacket) | QTD_STS_ACTIVE |
Kevin O'Connor | 190cc62 | 2010-03-09 19:43:52 -0500 | [diff] [blame] | 646 | | QTD_PID_IN | ehci_maxerr(3))); |
| 647 | |
| 648 | return 0; |
| 649 | } |