blob: 262b7f7799769987cd578efefdaa4f9daf5f229f [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)
*/
/**
* @brief EHCI host controller driver
*/
#include <math.h>
#include "../services.h"
#include "ehci.h"
struct usb_hc_data {
struct ehci_host edev;
};
/*****************
**** Helpers ****
*****************/
static inline struct ehci_host *_hcd_to_ehci(usb_host_t *hcd)
{
struct usb_hc_data *hc_data = (struct usb_hc_data *)hcd->pdata;
if (!hc_data) {
ZF_LOGF("Host controller data not found\n");
}
return &hc_data->edev;
}
static void _root_irq(struct ehci_host *edev)
{
uint8_t *portbm;
volatile uint32_t *psc;
int nports;
int port;
int resched;
/* Determine how many ports we should query */
nports = EHCI_HCS_N_PORTS(edev->cap_regs->hcsparams);
if (nports > edev->irq_xact.len * 8) {
nports = edev->irq_xact.len * 8;
}
/* Set the INT data */
if (!edev->irq_xact.vaddr || !edev->irq_cb) {
ZF_LOGF("Root hub IRQ uninitialized\n");
}
psc = _get_portsc(edev, 1);
portbm = xact_get_vaddr(&edev->irq_xact);
memset(portbm, 0, edev->irq_xact.len);
/* Hub itself is at position 0 */
for (port = 1; port <= nports; port++) {
if (psc[port - 1] & EHCI_PORT_CHANGE) {
portbm[port / 8] |= BIT(port & 0x7);
}
if (!(psc[port - 1] & EHCI_PORT_RESET) &&
(edev->bmreset_c & BIT(port))) {
portbm[port / 8] |= BIT(port & 0x7);
}
}
/* Forward the IRQ */
resched = edev->irq_cb(edev->irq_token, XACTSTAT_SUCCESS, 0);
if (resched) {
ZF_LOGF("Root IRQ unhandled\n");
}
}
void ehci_sched_enable_irq(struct ehci_host *edev)
{
uint32_t irq;
irq = edev->op_regs->usbintr;
irq |= EHCIINTR_USBINT;
edev->op_regs->usbintr = irq;
}
void ehci_sched_disable_irq(struct ehci_host *edev)
{
uint32_t irq;
irq = edev->op_regs->usbintr;
irq &= ~EHCIINTR_USBINT;
edev->op_regs->usbintr = irq;
}
int ehci_schedule_xact(usb_host_t *hdev, uint8_t addr, int8_t hub_addr,
uint8_t hub_port, enum usb_speed speed,
struct endpoint *ep, struct xact *xact, int nxact,
usb_cb_t cb, void *t)
{
struct QHn *qhn;
struct TDn *tdn;
struct ehci_host *edev;
int ret;
if (!hdev) {
ZF_LOGF("Invalid USB host\n");
}
edev = _hcd_to_ehci(hdev);
if (hub_addr == -1) {
/* Send off to root handler... No need to create QHn */
if (ep->type == EP_INTERRUPT) {
return ehci_schedule_periodic_root(edev, xact, nxact,
cb, t);
} else {
return hubem_process_xact(edev->hubem, xact, nxact, cb,
t);
}
}
qhn = (struct QHn *)ep->hcpriv;
if (!qhn) {
qhn = qhn_alloc(edev, addr, hub_addr, hub_port, speed, ep);
qhn->lock = ps_mutex_new(edev->sync);
if (!qhn->lock) {
ZF_LOGE("Failed to allocate mutex\n");
}
ep->hcpriv = qhn;
if (ep->type == EP_CONTROL || ep->type == EP_BULK) {
ehci_add_qhn_async(edev, qhn);
} else {
/*
* Polling rate calculation.
* USB spec 5.6.4, 5.7.4 and 9.6.6
*
* In order to maintain the integrity of periodic schedule,
* all polling rates have to be a power of two.
*/
if (speed == USBSPEED_HIGH) {
qhn->rate = (1 << (ep->interval - 1)) / 8;
} else if (speed == USBSPEED_FULL
&& ep->type == EP_ISOCHRONOUS) {
qhn->rate = (1 << (ep->interval - 1));
} else {
qhn->rate = (1 << ilogb(ep->interval));
}
/* Make sure the rate is sane */
if (qhn->rate < 1) {
qhn->rate = 1;
}
if (qhn->rate > 1024) {
qhn->rate = 1024;
}
ehci_add_qhn_periodic(edev, qhn);
}
} else {
/*
* The address and maximum packet size could change after initial
* enumeration, update the queue head accordingly.
*/
qhn_update(qhn, addr, ep);
}
/* Allocate qTD */
tdn = qtd_alloc(edev, speed, ep, xact, nxact, cb, t);
/* Add qTD to the queue head and send off over the bus */
if (ep->type == EP_BULK || ep->type == EP_CONTROL) {
ehci_schedule_async(edev, qhn);
if (cb) {
qtd_enqueue(edev, qhn, tdn);
return 0;
} else {
/* Wait for the existing TD to be processed */
while (qhn->tdns != NULL) ;
ehci_sched_disable_irq(edev);
qtd_enqueue(edev, qhn, tdn);
ret = ehci_wait_for_completion(tdn);
ehci_async_complete(edev);
ehci_sched_enable_irq(edev);
return ret;
}
} else {
qtd_enqueue(edev, qhn, tdn);
return ehci_schedule_periodic(edev);
}
}
void ehci_handle_irq(usb_host_t *hdev)
{
struct ehci_host *edev = _hcd_to_ehci(hdev);
uint32_t sts;
sts = edev->op_regs->usbsts & EHCISTS_MASK;
/* We cannot recover from fatal host error */
if (sts & EHCISTS_HOST_ERR) {
ZF_LOGF("INT - host error\n");
}
if (sts & EHCISTS_USBINT) {
ZF_LOGD("INT - USB\n");
ehci_periodic_complete(edev);
ehci_async_complete(edev);
}
/*
* TODO: To recover from transaction error, we need to scan both the
* periodic queue and the async queue, find the error TD and reschedule
* it.
*/
if (sts & EHCISTS_USBERRINT) {
ZF_LOGD("INT - USB error\n");
}
/*
* We don't handle frame list roll over interrupt, but some controllers
* don't like it always being set to 1, so simply clear it.
*/
if (sts & EHCISTS_FLIST_ROLL) {
ZF_LOGD("INT - Frame list roll over\n");
}
if (sts & EHCISTS_PORTC_DET) {
ZF_LOGD("INT - root hub port change\n");
_root_irq(edev);
}
if (sts & EHCISTS_ASYNC_ADV) {
ZF_LOGD("INT - async list advance\n");
check_doorbell(edev);
}
/* Write to clear */
edev->op_regs->usbsts = sts;
}
int ehci_cancel_xact(usb_host_t *hdev, struct endpoint *ep)
{
struct ehci_host *edev = _hcd_to_ehci(hdev);
if (!ep) {
ZF_LOGF("Invalid endpoint\n");
}
if (ep->hcpriv) {
if (ep->type == EP_BULK || ep->type == EP_CONTROL) {
ehci_del_qhn_async(edev, ep->hcpriv);
} else {
ehci_del_qhn_periodic(edev, ep->hcpriv);
}
}
return 0;
}
/****************************
**** Exported functions ****
****************************/
int
ehci_host_init(usb_host_t *hdev, uintptr_t regs,
void (*board_pwren) (int port, int state))
{
usb_hubem_t hubem;
struct ehci_host *edev;
int pwr_delay_ms;
uint32_t v;
int err;
hdev->pdata = (struct usb_hc_data *)usb_malloc(sizeof(struct usb_hc_data));
if (hdev->pdata == NULL) {
return -1;
}
edev = _hcd_to_ehci(hdev);
edev->devid = hdev->id;
edev->cap_regs = (volatile struct ehci_host_cap *)regs;
edev->op_regs = (volatile struct ehci_host_op *)(regs + edev->cap_regs->caplength);
hdev->schedule_xact = ehci_schedule_xact;
hdev->cancel_xact = ehci_cancel_xact;
hdev->handle_irq = ehci_handle_irq;
edev->board_pwren = board_pwren;
/* Check some params */
hdev->nports = EHCI_HCS_N_PORTS(edev->cap_regs->hcsparams);
if (hdev->nports <=0 || hdev->nports >= 32) {
ZF_LOGF("Invalid HCS register\n");
}
edev->bmreset_c = 0;
/* If the host controller has 64-bit capability, it is compulsory to use
* 64-bit data structure(Section 2.2.4). Clear the most significant address
* for EHCI data structures, since we are running a 32-bit OS */
if (edev->cap_regs->hccparams & EHCI_HCC_64BIT) {
edev->op_regs->ctrldssegment = 0;
}
/* Make sure we are halted before before reset */
edev->op_regs->usbcmd &= ~EHCICMD_RUNSTOP;
while (!(edev->op_regs->usbsts & EHCISTS_HCHALTED)) ;
/* Reset the HC */
edev->op_regs->usbcmd |= EHCICMD_HCRESET;
while (edev->op_regs->usbcmd & EHCICMD_HCRESET) ;
/* Initialise the hub emulation */
pwr_delay_ms = 100; /* Sample value from real hub */
err = usb_hubem_driver_init(edev, hdev->nports, pwr_delay_ms,
&_set_pf, &_clr_pf, &_get_pstat, &hubem);
if (err) {
return -1;
}
edev->hubem = hubem;
edev->dman = hdev->dman;
edev->sync = hdev->sync;
/* Terminate the periodic schedule head */
edev->alist_tail = NULL;
edev->db_pending = NULL;
edev->db_active = NULL;
edev->flist = NULL;
edev->intn_list = NULL;
/* Initialise IRQ */
edev->irq_cb = NULL;
edev->irq_token = NULL;
edev->irq_xact.vaddr = NULL;
edev->irq_xact.len = 0;
/* Initialise the controller. */
v = edev->op_regs->usbcmd;
v &= ~(EHCICMD_LIGHT_RST | EHCICMD_ASYNC_DB |
EHCICMD_PERI_EN | EHCICMD_ASYNC_EN | EHCICMD_HCRESET);
v |= EHCICMD_RUNSTOP;
edev->op_regs->usbcmd = v;
edev->op_regs->configflag |= EHCICFLAG_CFLAG;
dsb();
ps_mdelay(5);
/* Enable Interrupts */
v = edev->op_regs->usbintr;
v |= EHCIINTR_HOST_ERR | EHCIINTR_USBERRINT
| EHCIINTR_USBINT | EHCIINTR_ASYNC_ADV;
edev->op_regs->usbintr = v;
return 0;
}