blob: a1d0b012902bc40ea658f0e4db73c4e1d5f726a3 [file] [log] [blame]
/*
* 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;
}