| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| #include "../services.h" |
| #include "ehci.h" |
| |
| /*************************** |
| *** Hub emulation stubs *** |
| ***************************/ |
| volatile uint32_t *_get_portsc(struct ehci_host *h, int port) |
| { |
| volatile uint32_t *reg; |
| |
| if (port <= 0 || port > EHCI_HCS_N_PORTS(h->cap_regs->hcsparams)) { |
| ZF_LOGF("Invalid port\n"); |
| } |
| reg = &h->op_regs->portsc[port - 1]; |
| return reg; |
| } |
| |
| int _set_pf(void *token, int port, enum port_feature feature) |
| { |
| struct ehci_host *edev = (struct ehci_host *)token; |
| volatile uint32_t *ps_reg = _get_portsc(edev, port); |
| /* Change bits are write-1-to-clear so need to mask then */ |
| uint32_t v = *ps_reg & ~(EHCI_PORT_CHANGE); |
| switch (feature) { |
| case PORT_ENABLE: |
| v |= EHCI_PORT_ENABLE; |
| break; |
| case PORT_POWER: |
| if (edev->board_pwren) { |
| edev->board_pwren(port, 1); |
| } |
| v |= EHCI_PORT_POWER; |
| break; |
| case PORT_RESET: |
| /* HCHALTED bit in USBSTS should be a zero */ |
| if ((edev->op_regs->usbsts & EHCISTS_HCHALTED) != 0) { |
| ZF_LOGF("Failed to rest the host port\n"); |
| } |
| edev->bmreset_c = BIT(port); |
| v &= ~EHCI_PORT_ENABLE; |
| v |= EHCI_PORT_RESET; |
| /* Perform the reset */ |
| *ps_reg = v; |
| /* Sabre will automatically stop the reset and a ENABLE CHANGE |
| * IRQ event fires, but this does not happen on the Odroid! */ |
| /* Wait for reset to complete */ |
| ps_mdelay(10); /* 7.1.7.5 of USB 0.2 10ms delay */ |
| *ps_reg &= ~EHCI_PORT_RESET; |
| while (*ps_reg & EHCI_PORT_RESET) ; |
| |
| return 0; |
| case PORT_SUSPEND: |
| /* Must port owner 0 */ |
| if (*ps_reg & EHCI_PORT_OWNER) { |
| ZF_LOGF("Failed to suspend the port\n"); |
| } |
| /* Perform the Suspend */ |
| v |= EHCI_PORT_SUSPEND; |
| break; |
| default: |
| ZF_LOGD("EHCI: Unknown feature %d for set feature request\n", |
| feature); |
| return -1; |
| } |
| *ps_reg = v; |
| return 0; |
| } |
| |
| int _clr_pf(void *token, int port, enum port_feature feature) |
| { |
| struct ehci_host *edev = (struct ehci_host *)token; |
| volatile uint32_t *ps_reg = _get_portsc(edev, port); |
| /* Change bits are write-1-to-clear so need to mask then */ |
| uint32_t v = *ps_reg & ~(EHCI_PORT_CHANGE); |
| switch (feature) { |
| case PORT_ENABLE: |
| v &= ~EHCI_PORT_ENABLE; |
| break; |
| case PORT_OVER_CURRENT: |
| v &= ~EHCI_PORT_OCURRENT; |
| break; |
| case C_PORT_ENABLE: |
| v |= EHCI_PORT_ENABLE_C; |
| break; |
| case C_PORT_CONNECTION: |
| v |= EHCI_PORT_CONNECT_C; |
| break; |
| case C_PORT_OVER_CURRENT: |
| v |= EHCI_PORT_OCURRENT_C; |
| break; |
| case C_PORT_RESET: |
| edev->bmreset_c &= ~BIT(port); |
| break; |
| case PORT_POWER: |
| v &= ~EHCI_PORT_POWER; |
| if (edev->board_pwren) { |
| edev->board_pwren(port, 0); |
| } |
| break; |
| case PORT_SUSPEND: |
| /* Must be enabled */ |
| if (!(v & EHCI_PORT_ENABLE)) { |
| ZF_LOGF("Port must be enabled\n"); |
| } |
| /* Must be in suspend state */ |
| if (!(v & EHCI_PORT_SUSPEND)) { |
| ZF_LOGF("Port must be suspend\n"); |
| } |
| /* Perform the Suspend */ |
| v |= EHCI_PORT_FORCE_RESUME; |
| break; |
| |
| default: |
| ZF_LOGD("EHCI: Unknown feature %d for clear feature request\n", |
| feature); |
| return -1; |
| } |
| ps_udelay(10); |
| *ps_reg = v; |
| ps_udelay(10); |
| return 0; |
| } |
| |
| int _get_pstat(void *token, int port, struct port_status *_ps) |
| { |
| struct ehci_host *edev = (struct ehci_host *)token; |
| uint32_t v; |
| struct port_status ps; |
| v = *_get_portsc(edev, port); |
| /* Hey EHCI, here's an idea: Why not pull your spec inline with the USB hub spec? */ |
| ps.wPortStatus = |
| ((v & EHCI_PORT_CONNECT) ? BIT(PORT_CONNECTION) : 0) | |
| ((v & EHCI_PORT_ENABLE) ? BIT(PORT_ENABLE) : 0) | |
| ((v & EHCI_PORT_SUSPEND) ? BIT(PORT_SUSPEND) : 0) | |
| ((v & EHCI_PORT_OCURRENT) ? BIT(PORT_OVER_CURRENT) : 0) | |
| ((v & EHCI_PORT_RESET) ? BIT(PORT_RESET) : 0) | |
| ((v & EHCI_PORT_POWER) ? BIT(PORT_POWER) : 0) | 0; |
| ps.wPortChange = |
| ((v & EHCI_PORT_CONNECT_C) ? BIT(PORT_CONNECTION) : 0) | |
| ((v & EHCI_PORT_ENABLE_C) ? BIT(PORT_ENABLE) : 0) | |
| ((v & EHCI_PORT_OCURRENT_C) ? BIT(PORT_OVER_CURRENT) : 0) | 0; |
| /* Set up the speed */ |
| if (v & EHCI_PORT_JSTATE) { |
| /* Full speed */ |
| } else if (v & EHCI_PORT_KSTATE) { |
| ps.wPortStatus |= BIT(PORT_LOW_SPEED); |
| } else { |
| ps.wPortStatus |= BIT(PORT_HIGH_SPEED); |
| } |
| /* Emulate reset complete */ |
| if (!(v & EHCI_PORT_RESET) && (edev->bmreset_c & BIT(port))) { |
| ps.wPortChange |= BIT(PORT_RESET); |
| } |
| *_ps = ps; |
| return 0; |
| } |