historical/m0-applesillicon.git/xnu-qemu-arm64-5.1.0/roms/SLOF/lib/libusb/usb-xhci.c
2024-01-16 11:20:27 -06:00

1552 lines
40 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*****************************************************************************
* Copyright (c) 2013 IBM Corporation
* All rights reserved.
* This program and the accompanying materials
* are made available under the terms of the BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/bsd-license.php
*
* Contributors:
* IBM Corporation - initial implementation
*****************************************************************************/
#include <string.h>
#include "usb.h"
#include "usb-core.h"
#include "usb-xhci.h"
#include "tools.h"
#include "paflof.h"
#undef XHCI_DEBUG
//#define XHCI_DEBUG
#ifdef XHCI_DEBUG
#define dprintf(_x ...) do { printf("%s: ", __func__); printf(_x); } while (0)
#else
#define dprintf(_x ...)
#endif
struct port_state ps_array_usb2[] = {
{1, 0, 0, 0, PORTSC_PLS_U0, "ERROR"}
};
struct port_state ps_array_usb3[] = {
{0, 0, 0, 0, PORTSC_PLS_DISABLED, "Powered-OFF"},
{1, 0, 0, 0, PORTSC_PLS_POLLING, "Polling"},
{1, 0, 0, 0, PORTSC_PLS_U0, "Polling"},
{1, 0, 0, 0, PORTSC_PLS_RXDETECT, "*** Disconnected ***"},
{1, 0, 0, 0, PORTSC_PLS_DISABLED, "Disabled"},
{1, 0, 0, 0, PORTSC_PLS_INACTIVE, "Error"},
{1, 0, 0, 0, PORTSC_PLS_TEST_MODE,"Loopback"},
{1, 0, 0, 0, PORTSC_PLS_COMP_MODE,"Compliancek"},
{1, 1, 0, 1, PORTSC_PLS_U0, "****** Reset ******"},
{1, 1, 1, 0, PORTSC_PLS_U0, "****** Enabled ******"},
};
static void dump_xhci_regs(struct xhci_hcd *xhcd)
{
#ifdef XHCI_DEBUG
struct xhci_cap_regs *cap;
struct xhci_op_regs *op;
struct xhci_run_regs *run;
cap = xhcd->cap_regs;
op = xhcd->op_regs;
run = xhcd->run_regs;
dprintf("\n");
dprintf(" - CAPLENGTH %02X\n", read_reg8 (&cap->caplength));
dprintf(" - HCIVERSION %04X\n", read_reg16(&cap->hciversion));
dprintf(" - HCSPARAMS1 %08X\n", read_reg32(&cap->hcsparams1));
dprintf(" - HCSPARAMS2 %08X\n", read_reg32(&cap->hcsparams2));
dprintf(" - HCSPARAMS3 %08X\n", read_reg32(&cap->hcsparams3));
dprintf(" - HCCPARAMS %08X\n", read_reg32(&cap->hccparams));
dprintf(" - DBOFF %08X\n", read_reg32(&cap->dboff));
dprintf(" - RTSOFF %08X\n", read_reg32(&cap->rtsoff));
dprintf("\n");
dprintf(" - USBCMD %08X\n", read_reg32(&op->usbcmd));
dprintf(" - USBSTS %08X\n", read_reg32(&op->usbsts));
dprintf(" - PAGESIZE %08X\n", read_reg32(&op->pagesize));
dprintf(" - DNCTRL %08X\n", read_reg32(&op->dnctrl));
dprintf(" - CRCR %016llX\n", read_reg64(&op->crcr));
dprintf(" - DCBAAP %016llX\n", read_reg64(&op->dcbaap));
dprintf(" - CONFIG %08X\n", read_reg32(&op->config));
dprintf("\n");
dprintf(" - MFINDEX %08X\n", read_reg32(&run->mfindex));
dprintf("\n");
#endif
}
static void print_port_status(struct xhci_port_regs *prs)
{
#ifdef XHCI_DEBUG
uint32_t portsc;
uint32_t CCS, PED, PP, PLS, i, PR = 0;
portsc = read_reg32(&prs->portsc);
dprintf("portsc %08x portpmsc %08x portli %08x\n",
portsc,
read_reg32(&prs->portpmsc),
read_reg32(&prs->portli));
if (portsc & PORTSC_CCS) {
printf("CCS ");
CCS = 1;
}
if (portsc & PORTSC_PED) {
printf("PED ");
PED = 1;
}
if (portsc & PORTSC_OCA)
printf("OCA ");
if (portsc & PORTSC_PR)
printf("OCA ");
PLS = (portsc & PORTSC_PLS_MASK) >> 5;
printf("PLS:%d ", PLS);
if (portsc & PORTSC_PP) {
printf("PP ");
PP = 1;
}
printf("PS:%d ", (portsc & PORTSC_PS_MASK) >> 10);
printf("PIC:%d ", (portsc & PORTSC_PIC_MASK) >> 14);
if (portsc & PORTSC_LWS)
printf("LWS ");
if (portsc & PORTSC_CSC)
printf("CSC ");
if (portsc & PORTSC_PEC)
printf("PEC ");
if (portsc & PORTSC_WRC)
printf("WRC ");
if (portsc & PORTSC_OCC)
printf("OCC ");
if (portsc & PORTSC_PRC)
printf("PRC ");
if (portsc & PORTSC_PLC)
printf("PLC ");
if (portsc & PORTSC_CEC)
printf("CEC ");
if (portsc & PORTSC_CAS)
printf("CAS ");
if (portsc & PORTSC_WCE)
printf("WCE ");
if (portsc & PORTSC_WDE)
printf("WDE ");
if (portsc & PORTSC_WOE)
printf("WOE ");
if (portsc & PORTSC_DR)
printf("DR ");
if (portsc & PORTSC_WPR)
printf("WPR ");
printf("\n");
for (i = 0 ; i < (sizeof(ps_array_usb3)/sizeof(struct port_state)); i++) {
if (PP == ps_array_usb3[i].PP) {
if (CCS == ps_array_usb3[i].CCS) {
if (PED == ps_array_usb3[i].PED) {
if (PR == ps_array_usb3[i].PR) {
dprintf("%s - PLS %d\n", ps_array_usb3[i].state, PLS);
break;
}
}
}
}
}
#endif
}
static inline bool xhci_is_hc_ready(uint32_t *usbsts)
{
return !(read_reg32(usbsts) & XHCI_USBSTS_CNR);
}
static inline bool xhci_wait_for_cnr(uint32_t *usbsts)
{
/* Standard:
* Note: The xHC should halt within 16 ms. of software clearing the
* R/S bit to 0.
* Give some more time... 32ms
*/
int count = 320;
dprintf("Waiting for Controller ready ..");
while (!xhci_is_hc_ready(usbsts)) {
dprintf(".");
count--;
if (!count) {
dprintf(" failed %08X\n", read_reg32(usbsts));
return false;
}
SLOF_usleep(100);
}
dprintf(" done\n");
return true;
}
static bool xhci_hcd_set_runstop(struct xhci_op_regs *op, bool run_req)
{
uint32_t reg;
dprintf("Request %s\n", run_req ? "RUN" : "STOP");
if (!xhci_is_hc_ready(&op->usbsts)) {
dprintf("Controller not ready\n");
return false;
}
reg = read_reg32(&op->usbcmd);
if (run_req)
reg |= run_req;
else
reg &= (uint32_t)~1;
dprintf("writing %08X\n", reg);
write_reg32(&op->usbcmd, reg);
mb();
xhci_wait_for_cnr(&op->usbsts);
return true;
}
static bool xhci_hcd_reset(struct xhci_op_regs *op)
{
uint32_t reg;
/* Check if the controller is halted, else halt it */
if (!(read_reg32(&op->usbsts) & XHCI_USBSTS_HCH)) {
dprintf("HCHalted not set\n");
if (!xhci_hcd_set_runstop(op, false))
return false;
}
if (read_reg32(&op->usbsts) & XHCI_USBSTS_CNR) {
dprintf("Controller not ready\n");
return false;
}
reg = read_reg32(&op->usbcmd) | XHCI_USBCMD_HCRST;
/* Ready to Reset the controller now */
write_reg32(&op->usbcmd, reg);
xhci_wait_for_cnr(&op->usbsts);
return true;
}
static void xhci_handle_cmd_completion(struct xhci_hcd *xhcd,
struct xhci_event_trb *event)
{
uint32_t flags, slot_id, status;
status = le32_to_cpu(event->status);
flags = le32_to_cpu(event->flags);
slot_id = TRB_SLOT_ID(flags);
if (TRB_STATUS(status) == COMP_SUCCESS)
xhcd->slot_id = slot_id;
else
xhcd->slot_id = 0;
}
static uint64_t xhci_poll_event(struct xhci_hcd *xhcd,
uint32_t event_type)
{
struct xhci_event_trb *event;
uint64_t val, retval = 0;
uint32_t flags, time;
int index;
mb();
event = (struct xhci_event_trb *)xhcd->ering.deq;
flags = le32_to_cpu(event->flags);
dprintf("Reading from event ptr %p %08x\n", event, flags);
time = SLOF_GetTimer() + ((event_type == XHCI_POLL_NO_WAIT)? 0: USB_TIMEOUT);
while ((flags & TRB_CYCLE_STATE) != xhcd->ering.cycle_state) {
mb();
flags = le32_to_cpu(event->flags);
if (time < SLOF_GetTimer())
return 0;
}
mb();
flags = le32_to_cpu(event->flags);
switch(TRB_TYPE(flags))
{
case TRB_CMD_COMPLETION:
dprintf("CMD Completion\n");
xhci_handle_cmd_completion(xhcd, event);
break;
case TRB_PORT_STATUS:
dprintf("Port status event\n");
break;
case TRB_TRANSFER_EVENT:
dprintf("XFER event addr %16lx, status %08x, flags %08x\n",
le64_to_cpu(event->addr),
le32_to_cpu(event->status),
le32_to_cpu(event->flags));
break;
default:
printf("TRB_TYPE %d\n", TRB_TYPE(flags));
dprintf("Event addr %16lx, status %08x, flags %08x state %d\n",
le64_to_cpu(event->addr),
le32_to_cpu(event->status),
flags, xhcd->ering.cycle_state);
break;
}
xhcd->ering.deq = (uint64_t) (event + 1);
retval = le64_to_cpu(event->addr);
event->addr = 0;
event->status = 0;
event->flags = cpu_to_le32(xhcd->ering.cycle_state);
index = xhcd->ering.deq - (uint64_t)xhcd->ering.trbs;
val = xhcd->ering.trbs_dma;
val += (index % XHCI_EVENT_TRBS_SIZE);
if (!(index % XHCI_EVENT_TRBS_SIZE)) {
xhcd->ering.deq = (uint64_t)xhcd->ering.trbs;
xhcd->ering.cycle_state = xhcd->ering.cycle_state ? 0 : 1;
dprintf("Rounding %d\n", xhcd->ering.cycle_state);
}
dprintf("Update start %x deq %x index %d\n",
xhcd->ering.trbs_dma, val, index/sizeof(*event));
write_reg64(&xhcd->run_regs->irs[0].erdp, val);
if (retval == 0)
return (uint64_t)event;
else
return retval;
}
static void xhci_send_cmd(struct xhci_hcd *xhcd, uint32_t field1,
uint32_t field2, uint32_t field3, uint32_t field4)
{
struct xhci_db_regs *dbr;
struct xhci_command_trb *cmd;
uint32_t val, cycle_state;
dbr = xhcd->db_regs;
cmd = (struct xhci_command_trb *)xhcd->crseg.enq;
cmd->field[0] = cpu_to_le32(field1);
cmd->field[1] = cpu_to_le32(field2);
cmd->field[2] = cpu_to_le32(field3);
val = le32_to_cpu(cmd->field[3]);
cycle_state = (val & 0x1) ? 0 : 1;
val = field4 | cycle_state;
cmd->field[3] = cpu_to_le32(val);
dprintf("CMD %016lx val %08x cycle_state %d field1 %08x, field2 %08x, field3 %08x field4 %08x\n",
cmd, val, cycle_state,
le32_to_cpu(cmd->field[0]),
le32_to_cpu(cmd->field[1]),
le32_to_cpu(cmd->field[2]),
le32_to_cpu(cmd->field[3])
);
/* Ring the doorbell */
write_reg32(&dbr->db[0], 0);
xhci_poll_event(xhcd, 0);
cmd++;
xhcd->crseg.enq = (uint64_t)cmd;
return;
}
static void xhci_send_enable_slot(struct xhci_hcd *xhcd, uint32_t port)
{
uint32_t field1, field2, field3, field4;
field1 = 0;
field2 = 0;
field3 = 0;
field4 = TRB_CMD_TYPE(TRB_ENABLE_SLOT);
xhci_send_cmd(xhcd, field1, field2, field3, field4);
}
static void xhci_send_addr_device(struct xhci_hcd *xhcd, uint32_t slot_id,
uint64_t dma_in_ctx)
{
uint32_t field1, field2, field3, field4;
dprintf("Address device %lx, low %x, high %x\n", dma_in_ctx,
TRB_ADDR_LOW(dma_in_ctx),
TRB_ADDR_HIGH(dma_in_ctx));
field1 = TRB_ADDR_LOW(dma_in_ctx) & ~0xF;
field2 = TRB_ADDR_HIGH(dma_in_ctx);
field3 = 0;
field4 = TRB_CMD_TYPE(TRB_ADDRESS_DEV) | TRB_CMD_SLOT_ID(slot_id);
xhci_send_cmd(xhcd, field1, field2, field3, field4);
}
static uint32_t xhci_get_epno(struct usb_pipe *pipe)
{
uint32_t x_epno;
x_epno = pipe->dir | 2 * pipe->epno;
dprintf("EPno %d:%d DIR %d\n", pipe->epno, x_epno, pipe->dir);
return x_epno;
}
static void xhci_configure_ep(struct xhci_hcd *xhcd, uint32_t slot_id,
uint64_t dma_in_ctx)
{
uint32_t field1, field2, field3, field4;
dprintf("Configure EP %lx, low %x, high %x\n", dma_in_ctx,
TRB_ADDR_LOW(dma_in_ctx),
TRB_ADDR_HIGH(dma_in_ctx));
field1 = TRB_ADDR_LOW(dma_in_ctx) & ~0xF;
field2 = TRB_ADDR_HIGH(dma_in_ctx);
field3 = 0;
field4 = TRB_CMD_TYPE(TRB_CONFIG_EP) | TRB_CMD_SLOT_ID(slot_id);
xhci_send_cmd(xhcd, field1, field2, field3, field4);
}
static void xhci_init_seg(struct xhci_seg *seg, uint32_t size, uint32_t type)
{
struct xhci_link_trb *link;
seg->size = size / XHCI_TRB_SIZE;
seg->next = NULL;
seg->type = type;
seg->cycle_state = 1;
seg->enq = (uint64_t)seg->trbs;
seg->deq = (uint64_t)seg->trbs;
memset((void *)seg->trbs, 0, size);
if (type != TYPE_EVENT) {
link =(struct xhci_link_trb *) (seg->trbs + seg->size - 1);
link->addr = cpu_to_le64(seg->trbs_dma);
link->field2 = 0;
link->field3 = cpu_to_le32(0x1 | TRB_CMD_TYPE(TRB_LINK));
}
return;
}
static bool xhci_alloc_seg(struct xhci_seg *seg, uint32_t size, uint32_t type)
{
seg->trbs = (union xhci_trb *)SLOF_dma_alloc(size);
if (!seg->trbs) {
dprintf("Alloc failed\n");
return false;
}
xhci_init_seg(seg, size, type);
seg->trbs_dma = SLOF_dma_map_in((void *)seg->trbs, size, false);
dprintf(" TRBs %016lX TRBS-DMA %016lX\n", seg->trbs, seg->trbs_dma);
return true;
}
static void xhci_free_seg(struct xhci_seg *seg, uint32_t size)
{
if (seg->trbs) {
dprintf(" TRBs %016lX TRBS-DMA %016lX size %x\n", seg->trbs, seg->trbs_dma, size);
SLOF_dma_map_out(seg->trbs_dma, (void *)seg->trbs, size);
SLOF_dma_free((void *)seg->trbs, size);
}
memset(seg, 0, sizeof(*seg));
}
#define CTX_SIZE(x) ( (x) ? 64 : 32 )
static bool xhci_alloc_ctx(struct xhci_ctx *ctx, uint32_t size, uint32_t type)
{
ctx->addr = (uint8_t *)SLOF_dma_alloc(size);
if (!ctx->addr) {
dprintf("Alloc failed\n");
return false;
}
ctx->size = size;
ctx->type = type;
memset((void *)ctx->addr, 0, size);
ctx->dma_addr = SLOF_dma_map_in((void *)ctx->addr, size, false);
dprintf("ctx %llx, ctx_dma %llx\n", ctx->addr, ctx->dma_addr);
return true;
}
static struct xhci_control_ctx *xhci_get_control_ctx(struct xhci_ctx *ctx)
{
if (ctx->type == XHCI_CTX_TYPE_INPUT)
return (struct xhci_control_ctx *) ctx->addr;
return NULL;
}
static struct xhci_slot_ctx *xhci_get_slot_ctx(struct xhci_ctx *ctx, uint32_t ctx_size)
{
uint32_t offset = 0;
if (ctx->type == XHCI_CTX_TYPE_INPUT)
offset += ctx_size;
return (struct xhci_slot_ctx *)(ctx->addr + offset);
}
static struct xhci_ep_ctx *xhci_get_ep0_ctx(struct xhci_ctx *ctx, uint32_t ctx_size)
{
uint32_t offset = 0;
offset = ctx_size;
if (ctx->type == XHCI_CTX_TYPE_INPUT)
offset += ctx_size;
return (struct xhci_ep_ctx *)(ctx->addr + offset);
}
static struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_ctx *ctx, uint32_t ctx_size,
uint32_t epno)
{
uint32_t offset = 0;
offset = ctx_size * epno;
if (ctx->type == XHCI_CTX_TYPE_INPUT)
offset += ctx_size;
return (struct xhci_ep_ctx *)(ctx->addr + offset);
}
static void xhci_free_ctx(struct xhci_ctx *ctx, uint32_t size)
{
SLOF_dma_map_out(ctx->dma_addr, (void *)ctx->addr, size);
SLOF_dma_free((void *)ctx->addr, size);
}
static uint32_t usb_control_max_packet(uint32_t speed)
{
uint32_t max_packet = 0;
switch(speed)
{
case USB_LOW_SPEED:
max_packet = 8;
break;
case USB_FULL_SPEED:
max_packet = 8;
break;
case USB_HIGH_SPEED:
max_packet = 64;
break;
case USB_SUPER_SPEED:
max_packet = 512;
break;
default:
/* should not reach here */
dprintf("Unknown speed\n");
}
return max_packet;
}
static bool xhci_alloc_dev(struct xhci_hcd *xhcd, struct usb_dev *hub,
uint32_t slot_id, uint32_t port, uint32_t slotspeed)
{
struct usb_dev *dev;
struct xhci_dev *xdev;
struct xhci_slot_ctx *slot;
struct xhci_control_ctx *ctrl;
struct xhci_ep_ctx *ep0;
uint32_t ctx_size, val;
uint16_t max_packet;
uint32_t newport, rootport;
if (slot_id > XHCI_CONFIG_MAX_SLOT) {
printf("USB3 slot ID %d is too high (max is %d)\n", slot_id,
XHCI_CONFIG_MAX_SLOT);
return false;
}
ctx_size = CTX_SIZE(xhcd->hcc_csz_64);
xdev = &xhcd->xdevs[slot_id];
xdev->slot_id = slot_id;
xdev->ctx_size = ctx_size;
/* 4.3.3 Device Slot initialization */
/* Step 1 */
if (!xhci_alloc_ctx(&xdev->in_ctx, XHCI_CTX_BUF_SIZE, XHCI_CTX_TYPE_INPUT)) {
dprintf("Failed allocating in_ctx\n");
return false;
}
/* Step 2 */
ctrl = xhci_get_control_ctx(&xdev->in_ctx);
ctrl->a_flags = cpu_to_le32(0x3); /* A0, A1 */
ctrl->d_flags = 0;
/* Step 3 */
slot = xhci_get_slot_ctx(&xdev->in_ctx, ctx_size);
newport = rootport = port + 1;
val = newport & 0xf;
for (dev = hub; dev != NULL; dev = dev->hub) {
val = (val << 4) | (dev->port & 0xf); /* Build route string */
rootport = dev->port;
}
val >>= 4; /* Remove root hub ID from the string */
val |= LAST_CONTEXT(1) | slotspeed;
slot->field1 = cpu_to_le32(val);
slot->field2 = cpu_to_le32(ROOT_HUB_PORT(rootport));
/* Step 4 */
if (!xhci_alloc_seg(&xdev->control, XHCI_CONTROL_TRBS_SIZE, TYPE_CTRL)) {
dprintf("Failed allocating control\n");
goto fail_in_ctx;
}
/* Step 5 */
ep0 = xhci_get_ep0_ctx(&xdev->in_ctx, ctx_size);
val = 0;
max_packet = usb_control_max_packet(USB_SUPER_SPEED);
max_packet = 64;
val = EP_TYPE(EP_CTRL) | MAX_BURST(0) | ERROR_COUNT(3) |
MAX_PACKET_SIZE(max_packet);
ep0->field2 = cpu_to_le32(val);;
ep0->deq_addr = cpu_to_le64(xdev->control.trbs_dma | xdev->control.cycle_state);
ep0->field4 = cpu_to_le32(8);
/* Step 6 */
if (!xhci_alloc_ctx(&xdev->out_ctx, XHCI_CTX_BUF_SIZE, XHCI_CTX_TYPE_DEVICE)) {
dprintf("Failed allocating out_ctx\n");
goto fail_control_seg;
}
/* Step 7 */
xhcd->dcbaap[slot_id] = cpu_to_le64(xdev->out_ctx.dma_addr);
/* Step 8 */
slot = xhci_get_slot_ctx(&xdev->out_ctx, ctx_size);
ep0 = xhci_get_ep0_ctx(&xdev->out_ctx, ctx_size);
dprintf("Slot State %x \n", SLOT_STATE(le32_to_cpu(slot->field4)));
xhci_send_addr_device(xhcd, slot_id, xdev->in_ctx.dma_addr);
mb();
dprintf("Slot State %x \n", SLOT_STATE(le32_to_cpu(slot->field4)));
dprintf("EP0 f0 %08X f1 %08X %016lX %08X\n",
le32_to_cpu(ep0->field1),
le32_to_cpu(ep0->field2),
le64_to_cpu(ep0->deq_addr),
le32_to_cpu(ep0->field4));
/* Step 9 - configure ep */
ctrl->a_flags = cpu_to_le32(0x1); /* A0 */
ctrl->d_flags = 0;
xhci_configure_ep(xhcd, slot_id, xdev->in_ctx.dma_addr);
mb();
dprintf("Slot State %x \n", SLOT_STATE(le32_to_cpu(slot->field4)));
dprintf("USB Device address %d \n", USB_DEV_ADDRESS(le32_to_cpu(slot->field4)));
dprintf("EP0 f0 %08X f1 %08X %016lX %08X\n",
le32_to_cpu(ep0->field1),
le32_to_cpu(ep0->field2),
le64_to_cpu(ep0->deq_addr),
le32_to_cpu(ep0->field4));
dev = usb_devpool_get();
dprintf("allocated device %p\n", dev);
dev->hcidev = xhcd->hcidev;
dev->speed = USB_SUPER_SPEED;
dev->addr = USB_DEV_ADDRESS(slot->field4);
dev->port = newport;
dev->hub = hub;
dev->priv = xdev;
xdev->dev = dev;
if (usb_setup_new_device(dev, newport)) {
usb_slof_populate_new_device(dev);
return true;
}
xhci_free_ctx(&xdev->out_ctx, XHCI_CTX_BUF_SIZE);
fail_control_seg:
xhci_free_seg(&xdev->control, XHCI_CONTROL_TRBS_SIZE);
fail_in_ctx:
xhci_free_ctx(&xdev->in_ctx, XHCI_CTX_BUF_SIZE);
return false;
}
static void xhci_free_dev(struct xhci_dev *xdev)
{
xhci_free_seg(&xdev->bulk_in, XHCI_DATA_TRBS_SIZE);
xhci_free_seg(&xdev->bulk_out, XHCI_DATA_TRBS_SIZE);
xhci_free_seg(&xdev->intr, XHCI_INTR_TRBS_SIZE);
xhci_free_seg(&xdev->control, XHCI_CONTROL_TRBS_SIZE);
xhci_free_ctx(&xdev->in_ctx, XHCI_CTX_BUF_SIZE);
xhci_free_ctx(&xdev->out_ctx, XHCI_CTX_BUF_SIZE);
}
bool usb3_dev_init(struct xhci_hcd *xhcd, struct usb_dev *hub, uint32_t port,
uint32_t slotspeed)
{
/* Device enable slot */
xhci_send_enable_slot(xhcd, port);
if (!xhcd->slot_id) {
dprintf("Unable to get slot id\n");
return false;
}
dprintf("SLOT ID: %d\n", xhcd->slot_id);
if (!xhci_alloc_dev(xhcd, hub, xhcd->slot_id, port, slotspeed)) {
dprintf("Unable to allocate device\n");
return false;
}
return true;
}
static int xhci_device_present(uint32_t portsc, uint32_t usb_ver)
{
if (usb_ver == USB_XHCI) {
/* Device present and enabled state */
if ((portsc & PORTSC_CCS) &&
(portsc & PORTSC_PP) &&
(portsc & PORTSC_PED)) {
return true;
}
} else if (usb_ver == USB_EHCI) {
/* Device present and in disabled state */
if ((portsc & PORTSC_CCS) && (portsc & PORTSC_CSC))
return true;
}
return false;
}
static int xhci_port_scan(struct xhci_hcd *xhcd,
uint32_t usb_ver)
{
uint32_t num_ports, portsc, i;
struct xhci_op_regs *op;
struct xhci_port_regs *prs;
struct xhci_cap_regs *cap;
uint32_t xecp_off;
uint32_t *xecp_addr, *base;
uint32_t port_off = 0, port_cnt;
dprintf("enter\n");
op = xhcd->op_regs;
cap = xhcd->cap_regs;
port_cnt = num_ports = read_reg32(&cap->hcsparams1) >> 24;
/* Read the xHCI extented capability to find usb3 ports and offset*/
xecp_off = XHCI_HCCPARAMS_XECP(read_reg32(&cap->hccparams));
base = (uint32_t *)cap;
while (xecp_off > 0) {
xecp_addr = base + xecp_off;
dprintf("xecp_off %d %p %p \n", xecp_off, base, xecp_addr);
if (XHCI_XECP_CAP_ID(read_reg32(xecp_addr)) == XHCI_XECP_CAP_SP &&
XHCI_XECP_CAP_SP_MJ(read_reg32(xecp_addr)) == usb_ver &&
XHCI_XECP_CAP_SP_MN(read_reg32(xecp_addr)) == 0) {
port_cnt = XHCI_XECP_CAP_SP_PC(read_reg32(xecp_addr + 2));
port_off = XHCI_XECP_CAP_SP_PO(read_reg32(xecp_addr + 2));
dprintf("PortCount %d Portoffset %d\n", port_cnt, port_off);
}
base = xecp_addr;
xecp_off = XHCI_XECP_NEXT_PTR(read_reg32(xecp_addr));
}
if (port_off == 0) /* port_off should always start from 1 */
return false;
for (i = (port_off - 1); i < (port_off + port_cnt - 1); i++) {
prs = &op->prs[i];
portsc = read_reg32(&prs->portsc);
if (xhci_device_present(portsc, usb_ver)) {
/* Device present */
dprintf("Device present on port %d\n", i);
/* Reset the port */
portsc = read_reg32(&prs->portsc);
portsc = portsc | PORTSC_PR;
write_reg32(&prs->portsc, portsc);
/* FIXME poll for port event */
SLOF_msleep(20);
xhci_poll_event(xhcd, 0);
portsc = read_reg32(&prs->portsc);
if (portsc & ~PORTSC_PRC) {
dprintf("Port reset complete %d\n", i);
}
print_port_status(prs);
if (!usb3_dev_init(xhcd, NULL, i - (port_off - 1),
((portsc >> 10) & 0xf) << 20)) {
dprintf("USB device initialization failed\n");
}
}
}
dprintf("exit\n");
return true;
}
static int xhci_hub_check_ports(struct xhci_hcd *xhcd)
{
return xhci_port_scan(xhcd, USB_XHCI) | xhci_port_scan(xhcd, USB_EHCI);
}
static bool xhci_hcd_init(struct xhci_hcd *xhcd)
{
struct xhci_op_regs *op;
struct xhci_int_regs *irs;
uint64_t val;
uint32_t reg;
if (!xhcd) {
dprintf("NULL pointer\n");
goto fail;
}
op = xhcd->op_regs;
irs = &xhcd->run_regs->irs[0];
if (!xhci_hcd_reset(op)) {
dprintf("Reset failed\n");
goto fail;
}
write_reg32(&op->config, XHCI_CONFIG_MAX_SLOT);
reg = read_reg32(&xhcd->cap_regs->hccparams);
/* 64byte context !! */
xhcd->hcc_csz_64 = (reg & XHCI_HCCPARAMS_CSZ) ? 1 : 0;
if (xhcd->hcc_csz_64) {
printf("usb-xhci: 64 Byte context not supported\n");
goto fail;
}
/*
* 6.1 Device Context Base Address Array
*
* Allocate memory and initialize
*/
xhcd->dcbaap = (uint64_t *)SLOF_dma_alloc(XHCI_DCBAAP_MAX_SIZE);
if (!xhcd->dcbaap) {
dprintf("Alloc failed\n");
goto fail;
}
memset((void *)xhcd->dcbaap, 0, XHCI_DCBAAP_MAX_SIZE);
xhcd->dcbaap_dma = SLOF_dma_map_in((void *)xhcd->dcbaap,
XHCI_DCBAAP_MAX_SIZE, false);
dprintf("dcbaap %llx, dcbaap_phys %llx\n", xhcd->dcbaap, xhcd->dcbaap_dma);
write_reg64(&op->dcbaap, xhcd->dcbaap_dma);
/*
* Command Ring Control - TRB
* FIXME - better way to allocate it...
*/
if (!xhci_alloc_seg(&xhcd->crseg, XHCI_CRCR_CRP_SIZE, TYPE_COMMAND))
goto fail_dcbaap;
val = read_reg64(&op->crcr) & ~XHCI_CRCR_CRP_MASK;
val = val | (xhcd->crseg.trbs_dma & XHCI_CRCR_CRP_MASK);
write_reg64(&op->crcr, val);
/*
* Event Ring Control - TRB
* Allocate event TRBS
*/
if (!xhci_alloc_seg(&xhcd->ering, XHCI_EVENT_TRBS_SIZE, TYPE_EVENT))
goto fail_crseg;
/*
* Populate event ring segment table.
* Note: only using one segment.
*/
xhcd->erst.entries = SLOF_dma_alloc(XHCI_EVENT_TRBS_SIZE);
if (!xhcd->erst.entries)
goto fail_ering;
xhcd->erst.dma = SLOF_dma_map_in((void *)xhcd->erst.entries,
XHCI_EVENT_TRBS_SIZE, false);
xhcd->erst.num_segs = XHCI_ERST_NUM_SEGS;
/* populate entries[0] */
write_reg64(&xhcd->erst.entries->addr, xhcd->ering.trbs_dma);
write_reg32(&xhcd->erst.entries->size, xhcd->ering.size);
write_reg32(&xhcd->erst.entries->reserved, 0);
/* populate erdp */
val = read_reg64(&irs->erdp) & ~XHCI_ERDP_MASK;
val = val | (xhcd->ering.trbs_dma & XHCI_ERDP_MASK);
write_reg64(&irs->erdp, val);
/* populate erstsz */
val = read_reg32(&irs->erstsz) & ~XHCI_ERST_SIZE_MASK;
val = val | xhcd->erst.num_segs;
write_reg32(&irs->erstsz, val);
/* Now write the erstba */
val = read_reg64(&irs->erstba) & ~XHCI_ERST_ADDR_MASK;
val = val | (xhcd->erst.dma & XHCI_ERST_ADDR_MASK);
write_reg64(&irs->erstba, val);
dprintf("ERDP %llx TRB-DMA %llx\n", read_reg64(&irs->erdp),
xhcd->ering.trbs_dma);
dprintf("ERST %llx, ERST DMA %llx, size %d\n",
(uint64_t)xhcd->erst.entries, xhcd->erst.dma,
xhcd->erst.num_segs);
mb();
if (!xhci_hcd_set_runstop(op, true))
goto fail_erst_entries;
if (!xhci_hub_check_ports(xhcd))
goto fail_erst_entries;
return true;
fail_erst_entries:
write_reg32(&irs->erstsz, 0);
write_reg64(&irs->erstba, 0);
mb();
SLOF_dma_map_out(xhcd->erst.dma, (void *)xhcd->erst.entries, XHCI_EVENT_TRBS_SIZE);
SLOF_dma_free((void *)xhcd->erst.entries, XHCI_EVENT_TRBS_SIZE);
fail_ering:
xhci_free_seg(&xhcd->ering, XHCI_EVENT_TRBS_SIZE);
fail_crseg:
val = read_reg64(&op->crcr) & ~XHCI_CRCR_CRP_MASK;
write_reg64(&op->crcr, val);
mb();
xhci_free_seg(&xhcd->crseg, XHCI_CRCR_CRP_SIZE);
fail_dcbaap:
write_reg64(&op->dcbaap, 0);
mb();
SLOF_dma_map_out(xhcd->dcbaap_dma, (void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE);
SLOF_dma_free((void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE);
fail:
return false;
}
static bool xhci_hcd_exit(struct xhci_hcd *xhcd)
{
struct xhci_op_regs *op;
struct xhci_int_regs *irs;
uint64_t val;
int i;
if (!xhcd) {
dprintf("NULL pointer\n");
return false;
}
op = xhcd->op_regs;
if (!xhci_hcd_set_runstop(op, false)) {
dprintf("NULL pointer\n");
}
for (i = 1; i < XHCI_CONFIG_MAX_SLOT; i++) {
if (xhcd->xdevs[i].dev)
xhci_free_dev(&xhcd->xdevs[i]);
}
irs = &xhcd->run_regs->irs[0];
write_reg32(&irs->erstsz, 0);
write_reg64(&irs->erstba, 0);
mb();
if (xhcd->erst.entries) {
SLOF_dma_map_out(xhcd->erst.dma, xhcd->erst.entries, XHCI_EVENT_TRBS_SIZE);
SLOF_dma_free(xhcd->erst.entries, XHCI_EVENT_TRBS_SIZE);
}
xhci_free_seg(&xhcd->ering, XHCI_EVENT_TRBS_SIZE);
val = read_reg64(&op->crcr) & ~XHCI_CRCR_CRP_MASK;
write_reg64(&op->crcr, val);
xhci_free_seg(&xhcd->crseg, XHCI_CRCR_CRP_SIZE);
write_reg64(&op->dcbaap, 0);
if (xhcd->dcbaap) {
SLOF_dma_map_out(xhcd->dcbaap_dma, (void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE);
SLOF_dma_free((void *)xhcd->dcbaap, XHCI_DCBAAP_MAX_SIZE);
}
/*
* QEMU implementation of XHCI doesn't implement halt
* properly. It basically says that it's halted immediately
* but doesn't actually terminate ongoing activities and
* DMAs. This needs to be fixed in QEMU.
*
* For now, wait for 50ms grace time till qemu stops using
* this device.
*/
SLOF_msleep(50);
return true;
}
static void xhci_init(struct usb_hcd_dev *hcidev)
{
struct xhci_hcd *xhcd;
printf(" XHCI: Initializing\n");
dprintf("device base address %p\n", hcidev->base);
hcidev->base = (void *)((uint64_t)hcidev->base & ~7);
xhcd = SLOF_alloc_mem(sizeof(*xhcd));
if (!xhcd) {
printf("usb-xhci: Unable to allocate memory\n");
return;
}
memset(xhcd, 0, sizeof(*xhcd));
hcidev->nextaddr = 1;
hcidev->priv = xhcd;
xhcd->hcidev = hcidev;
xhcd->cap_regs = (struct xhci_cap_regs *)(hcidev->base);
xhcd->op_regs = (struct xhci_op_regs *)(hcidev->base +
read_reg8(&xhcd->cap_regs->caplength));
xhcd->run_regs = (struct xhci_run_regs *)(hcidev->base +
read_reg32(&xhcd->cap_regs->rtsoff));
xhcd->db_regs = (struct xhci_db_regs *)(hcidev->base +
read_reg32(&xhcd->cap_regs->dboff));
dump_xhci_regs(xhcd);
if (!xhci_hcd_init(xhcd))
printf("usb-xhci: failed to initialize XHCI controller.\n");
dump_xhci_regs(xhcd);
}
static void xhci_exit(struct usb_hcd_dev *hcidev)
{
struct xhci_hcd *xhcd;
dprintf("%s: enter \n", __func__);
if (!hcidev && !hcidev->priv) {
return;
}
xhcd = hcidev->priv;
xhci_hcd_exit(xhcd);
SLOF_free_mem(xhcd, sizeof(*xhcd));
hcidev->priv = NULL;
}
static void fill_trb_buff(struct xhci_command_trb *cmd, uint32_t field1,
uint32_t field2, uint32_t field3, uint32_t field4)
{
uint32_t val, cycle_state;
cmd->field[0] = cpu_to_le32(field1);
cmd->field[1] = cpu_to_le32(field2);
cmd->field[2] = cpu_to_le32(field3);
val = le32_to_cpu(cmd->field[3]);
cycle_state = (val & 0x1) ? 0 : 1;
val = cycle_state | (field4 & ~0x1);
cmd->field[3] = cpu_to_le32(val);
mb();
dprintf("CMD %016lx val %08x cycle_state %d field1 %08x, field2 %08x, field3 %08x field4 %08x\n",
cmd, val, cycle_state,
le32_to_cpu(cmd->field[0]),
le32_to_cpu(cmd->field[1]),
le32_to_cpu(cmd->field[2]),
le32_to_cpu(cmd->field[3])
);
return;
}
static void fill_setup_trb(struct xhci_command_trb *cmd, struct usb_dev_req *req,
uint32_t size)
{
uint32_t field1, field2, field3, field4 = 0;
uint64_t req_raw;
uint32_t datalen = 0, pid = 0;
req_raw = *((uint64_t *)req);
dprintf("%lx %lx \n", *((uint64_t *)req), req_raw);
/* req_raw is already in right byte order... */
field1 = cpu_to_le32(TRB_ADDR_HIGH(req_raw));
field2 = cpu_to_le32(TRB_ADDR_LOW(req_raw));
field3 = 8; /* ALWAYS 8 */
datalen = cpu_to_le16(req->wLength);
if (datalen) {
pid = (req->bmRequestType & REQT_DIR_IN) ? 3 : 2;
field4 = TRB_TRT(pid);
}
field4 |= TRB_CMD_TYPE(TRB_SETUP_STAGE) | TRB_IDT;
fill_trb_buff(cmd, field1, field2, field3, field4);
}
static void fill_setup_data(struct xhci_command_trb *cmd, void *data,
uint32_t size, uint32_t dir)
{
uint32_t field1, field2, field3, field4;
field1 = TRB_ADDR_LOW(data);
field2 = TRB_ADDR_HIGH(data);
field3 = size;
field4 = TRB_CMD_TYPE(TRB_DATA_STAGE);
if (dir)
field4 |= TRB_DIR_IN;
fill_trb_buff(cmd, field1, field2, field3, field4);
}
static void fill_status_trb(struct xhci_command_trb *cmd, uint32_t dir)
{
uint32_t field1, field2, field3, field4;
field1 = 0;
field2 = 0;
field3 = 0;
field4 = TRB_CMD_TYPE(TRB_STATUS_STAGE) | TRB_IOC;
if (dir)
field4 |= TRB_DIR_IN;
fill_trb_buff(cmd, field1, field2, field3, field4);
}
static void fill_normal_trb(struct xhci_transfer_trb *trb, void *data,
uint32_t size)
{
uint32_t field1, field2, field3, field4;
field1 = TRB_ADDR_LOW(data);
field2 = TRB_ADDR_HIGH(data);
field3 = size;
field4 = TRB_CMD_TYPE(TRB_NORMAL) | TRB_IOC;
fill_trb_buff((struct xhci_command_trb *)trb, field1, field2, field3, field4);
}
static int xhci_send_ctrl(struct usb_pipe *pipe, struct usb_dev_req *req, void *data)
{
struct xhci_dev *xdev;
struct xhci_seg *ctrl;
struct xhci_hcd *xhcd;
struct xhci_command_trb *cmd;
struct xhci_db_regs *dbr;
long req_phys = 0, data_phys = 0;
int ret = true;
uint32_t slot_id, pid = 0, datalen = 0;
if (!pipe->dev || !pipe->dev->hcidev) {
dprintf(" NULL pointer\n");
return false;
}
xdev = pipe->dev->priv;
slot_id = xdev->slot_id;
ctrl = &xdev->control;
xhcd = (struct xhci_hcd *)pipe->dev->hcidev->priv;
dbr = xhcd->db_regs;
if (!ctrl || !xdev || !xhcd) {
dprintf(" NULL pointer\n");
return false;
}
cmd = (struct xhci_command_trb *)ctrl->enq;
req_phys = SLOF_dma_map_in(req, sizeof(struct usb_dev_req), true);
fill_setup_trb(cmd, req, sizeof(*req));
cmd++;
datalen = cpu_to_le16(req->wLength);
if (datalen)
pid = 1;
if (datalen) {
data_phys = SLOF_dma_map_in(data, datalen, true);
fill_setup_data(cmd, (void *) data_phys, datalen, pid);
cmd++;
}
fill_status_trb(cmd, pid);
cmd++;
/* Ring the doorbell - ep0 */
write_reg32(&dbr->db[slot_id], 1);
if (!xhci_poll_event(xhcd, 0)) {
dprintf("Command failed\n");
ret = false;
}
ctrl->enq = (uint64_t) cmd;
SLOF_dma_map_out(req_phys, req, sizeof(struct usb_dev_req));
if (datalen)
SLOF_dma_map_out(data_phys, data, datalen);
return ret;
}
static inline struct xhci_pipe *xhci_pipe_get_xpipe(struct usb_pipe *pipe)
{
struct xhci_pipe *xpipe;
xpipe = container_of(pipe, struct xhci_pipe, pipe);
dprintf("%s: xpipe is %p\n", __func__, xpipe);
return xpipe;
}
static inline struct xhci_seg *xhci_pipe_get_seg(struct usb_pipe *pipe)
{
struct xhci_pipe *xpipe;
xpipe = xhci_pipe_get_xpipe(pipe);
return xpipe->seg;
}
static inline void *xhci_get_trb(struct xhci_seg *seg)
{
uint64_t val, enq;
int index;
struct xhci_link_trb *link;
enq = val = seg->enq;
val = val + XHCI_TRB_SIZE;
index = (enq - (uint64_t)seg->trbs) / XHCI_TRB_SIZE + 1;
dprintf("%s: enq %llx, val %llx %x\n", __func__, enq, val, index);
/* TRBs being a cyclic buffer, here we cycle back to beginning. */
if (index == (seg->size - 1)) {
dprintf("%s: rounding \n", __func__);
seg->enq = (uint64_t)seg->trbs;
seg->cycle_state ^= seg->cycle_state;
link = (struct xhci_link_trb *) (seg->trbs + seg->size - 1);
link->addr = cpu_to_le64(seg->trbs_dma);
link->field2 = 0;
link->field3 = cpu_to_le32(0x1 | TRB_CMD_TYPE(TRB_LINK));
mb();
}
else {
seg->enq = seg->enq + XHCI_TRB_SIZE;
}
return (void *)enq;
}
static inline void *xhci_get_trb_deq(struct xhci_seg *seg)
{
uint64_t deq_next, deq;
int index;
deq = seg->deq;
deq_next = deq + XHCI_TRB_SIZE;
index = (deq - (uint64_t)seg->trbs) / XHCI_TRB_SIZE + 1;
dprintf("%s: deq %llx, deq_next %llx index %x\n", __func__, deq, deq_next, index);
/* TRBs being a cyclic buffer, here we cycle back to beginning. */
if (index == (seg->size - 1)) {
dprintf("%s: rounding \n", __func__);
seg->deq = (uint64_t)seg->trbs;
}
else {
seg->deq = deq_next;
}
return (void *)deq;
}
static uint64_t xhci_get_trb_phys(struct xhci_seg *seg, uint64_t trb)
{
return seg->trbs_dma + (trb - (uint64_t)seg->trbs);
}
static uint32_t xhci_trb_get_index(struct xhci_seg *seg, struct xhci_transfer_trb *trb)
{
return trb - (struct xhci_transfer_trb *)seg->trbs;
}
static int usb_kb = false;
static int xhci_transfer_bulk(struct usb_pipe *pipe, void *td, void *td_phys,
void *data, int datalen)
{
struct xhci_dev *xdev;
struct xhci_seg *seg;
struct xhci_hcd *xhcd;
struct xhci_transfer_trb *trb;
struct xhci_db_regs *dbr;
int ret = true;
uint32_t slot_id, epno, time;
uint64_t trb_phys, event_phys;
if (!pipe->dev || !pipe->dev->hcidev) {
dprintf(" NULL pointer\n");
dprintf(" pipe dev %p hcidev %p\n", pipe->dev, pipe->dev->hcidev);
return false;
}
xdev = pipe->dev->priv;
slot_id = xdev->slot_id;
seg = xhci_pipe_get_seg(pipe);
xhcd = (struct xhci_hcd *)pipe->dev->hcidev->priv;
dbr = xhcd->db_regs;
if (!seg || !xdev || !xhcd) {
dprintf(" NULL pointer\n");
dprintf(" seg %p xdev %p xhcd %p\n", seg, xdev, xhcd);
return false;
}
if (datalen > XHCI_MAX_BULK_SIZE) {
printf("usb-xhci: bulk transfer size too big\n");
return false;
}
trb = xhci_get_trb(seg);
trb_phys = xhci_get_trb_phys(seg, (uint64_t)trb);
fill_normal_trb(trb, (void *)data, datalen);
epno = xhci_get_epno(pipe);
write_reg32(&dbr->db[slot_id], epno);
time = SLOF_GetTimer() + USB_TIMEOUT;
while (1) {
event_phys = xhci_poll_event(xhcd, 0);
if (event_phys == trb_phys) {
break;
} else if (event_phys == 0) { /* polling timed out */
ret = false;
break;
} else
usb_kb = true;
/* transfer timed out */
if (time < SLOF_GetTimer())
return false;
}
trb->addr = 0;
trb->len = 0;
trb->flags = 0;
mb();
return ret;
}
static int xhci_alloc_pipe_pool(struct xhci_hcd *xhcd)
{
struct xhci_pipe *xpipe, *curr, *prev;
unsigned int i, count;
long xpipe_phys = 0;
count = XHCI_PIPE_POOL_SIZE/sizeof(*xpipe);
xhcd->pool = xpipe = SLOF_dma_alloc(XHCI_PIPE_POOL_SIZE);
if (!xpipe)
return -1;
xhcd->pool_phys = xpipe_phys = SLOF_dma_map_in(xpipe, XHCI_PIPE_POOL_SIZE, true);
dprintf("%s: xpipe %p, xpipe_phys %lx\n", __func__, xpipe, xpipe_phys);
/* Although an array, link them */
for (i = 0, curr = xpipe, prev = NULL; i < count; i++, curr++) {
if (prev)
prev->pipe.next = &curr->pipe;
curr->pipe.next = NULL;
prev = curr;
}
if (!xhcd->freelist)
xhcd->freelist = &xpipe->pipe;
else
xhcd->end->next = &xpipe->pipe;
xhcd->end = &prev->pipe;
return 0;
}
static void xhci_init_bulk_ep(struct usb_dev *dev, struct usb_pipe *pipe)
{
struct xhci_hcd *xhcd;
struct xhci_dev *xdev;
struct xhci_seg *seg;
struct xhci_pipe *xpipe;
struct xhci_control_ctx *ctrl;
struct xhci_ep_ctx *ep;
uint32_t x_epno, val, type;
if (!pipe || !dev || !dev->priv)
return;
xdev = dev->priv;
xhcd = dev->hcidev->priv;
dprintf("dir %d\n", pipe->dir);
seg = xhci_pipe_get_seg(pipe);
xpipe = xhci_pipe_get_xpipe(pipe);
if (pipe->dir) {
type = EP_BULK_IN;
seg = &xdev->bulk_in;
}
else {
type = EP_BULK_OUT;
seg = &xdev->bulk_out;
}
if (!seg->trbs) {
if (!xhci_alloc_seg(seg, XHCI_DATA_TRBS_SIZE, TYPE_BULK)) {
printf("usb-xhci: allocation failed for bulk endpoint\n");
return;
}
} else {
xhci_init_seg(seg, XHCI_DATA_TRBS_SIZE, TYPE_BULK);
}
pipe->mps = XHCI_MAX_BULK_SIZE;
ctrl = xhci_get_control_ctx(&xdev->in_ctx);
x_epno = xhci_get_epno(pipe);
ep = xhci_get_ep_ctx(&xdev->in_ctx, xdev->ctx_size, x_epno);
val = EP_TYPE(type) | MAX_BURST(0) | ERROR_COUNT(3) |
MAX_PACKET_SIZE(pipe->mps);
ep->field2 = cpu_to_le32(val);;
ep->deq_addr = cpu_to_le64(seg->trbs_dma | seg->cycle_state);
ep->field4 = cpu_to_le32(8);
ctrl->a_flags = cpu_to_le32(BIT(x_epno) | 0x1);
ctrl->d_flags = 0;
xhci_configure_ep(xhcd, xdev->slot_id, xdev->in_ctx.dma_addr);
xpipe->seg = seg;
}
static int xhci_get_pipe_intr(struct usb_pipe *pipe,
struct xhci_hcd *xhcd,
char *buf, size_t len)
{
struct xhci_dev *xdev;
struct xhci_seg *seg;
struct xhci_pipe *xpipe;
struct xhci_control_ctx *ctrl;
struct xhci_ep_ctx *ep;
uint32_t x_epno, val, type;
struct usb_dev *dev;
struct xhci_transfer_trb *trb;
dev = pipe->dev;
if (dev->class != DEV_HID_KEYB)
return false;
xdev = dev->priv;
pipe->mps = 8;
seg = xhci_pipe_get_seg(pipe);
xpipe = xhci_pipe_get_xpipe(pipe);
type = EP_INT_IN;
seg = &xdev->intr;
if (!seg->trbs) {
if (!xhci_alloc_seg(seg, XHCI_INTR_TRBS_SIZE, TYPE_BULK)) {
printf("usb-xhci: allocation failed for interrupt endpoint\n");
return false;
}
} else {
xhci_init_seg(seg, XHCI_EVENT_TRBS_SIZE, TYPE_BULK);
}
xpipe->buflen = pipe->mps * XHCI_INTR_TRBS_SIZE/(sizeof(struct xhci_transfer_trb));
xpipe->buf = SLOF_dma_alloc(xpipe->buflen);
xpipe->buf_phys = SLOF_dma_map_in(xpipe->buf, xpipe->buflen, false);
ctrl = xhci_get_control_ctx(&xdev->in_ctx);
x_epno = xhci_get_epno(pipe);
ep = xhci_get_ep_ctx(&xdev->in_ctx, xdev->ctx_size, x_epno);
val = EP_TYPE(type) | MAX_BURST(0) | ERROR_COUNT(3) |
MAX_PACKET_SIZE(pipe->mps);
ep->field2 = cpu_to_le32(val);
ep->deq_addr = cpu_to_le64(seg->trbs_dma | seg->cycle_state);
ep->field4 = cpu_to_le32(8);
ctrl->a_flags = cpu_to_le32(BIT(x_epno) | 0x1);
ctrl->d_flags = 0;
xhci_configure_ep(xhcd, xdev->slot_id, xdev->in_ctx.dma_addr);
xpipe->seg = seg;
trb = xhci_get_trb(seg);
buf = (char *)(xpipe->buf_phys + xhci_trb_get_index(seg, trb) * pipe->mps);
fill_normal_trb(trb, (void *)buf, pipe->mps);
return true;
}
static struct usb_pipe* xhci_get_pipe(struct usb_dev *dev, struct usb_ep_descr *ep, char *buf, size_t len)
{
struct xhci_hcd *xhcd;
struct usb_pipe *new = NULL;
if (!dev)
return NULL;
xhcd = (struct xhci_hcd *)dev->hcidev->priv;
if (!xhcd->freelist) {
dprintf("usb-xhci: %s allocating pool\n", __func__);
if (xhci_alloc_pipe_pool(xhcd))
return NULL;
}
new = xhcd->freelist;
xhcd->freelist = xhcd->freelist->next;
if (!xhcd->freelist)
xhcd->end = NULL;
memset(new, 0, sizeof(*new));
new->dev = dev;
new->next = NULL;
new->type = ep->bmAttributes & USB_EP_TYPE_MASK;
new->speed = dev->speed;
new->mps = ep->wMaxPacketSize;
new->dir = (ep->bEndpointAddress & 0x80) >> 7;
new->epno = ep->bEndpointAddress & 0x0f;
if (new->type == USB_EP_TYPE_INTR) {
if (!xhci_get_pipe_intr(new, xhcd, buf, len)) {
printf("usb-xhci: %s alloc_intr failed %p\n",
__func__, new);
}
}
if (new->type == USB_EP_TYPE_BULK)
xhci_init_bulk_ep(dev, new);
return new;
}
static void xhci_put_pipe(struct usb_pipe *pipe)
{
struct xhci_hcd *xhcd;
struct xhci_pipe *xpipe;
dprintf("usb-xhci: %s enter - %p\n", __func__, pipe);
if (!pipe || !pipe->dev)
return;
xhcd = pipe->dev->hcidev->priv;
dprintf("dir %d\n", pipe->dir);
if (pipe->type == USB_EP_TYPE_BULK) {
xpipe = xhci_pipe_get_xpipe(pipe);
xpipe->seg = NULL;
} else if (pipe->type == USB_EP_TYPE_INTR) {
xpipe = xhci_pipe_get_xpipe(pipe);
SLOF_dma_map_out(xpipe->buf_phys, xpipe->buf, xpipe->buflen);
SLOF_dma_free(xpipe->buf, xpipe->buflen);
xpipe->seg = NULL;
}
if (xhcd->end)
xhcd->end->next = pipe;
else
xhcd->freelist = pipe;
xhcd->end = pipe;
pipe->next = NULL;
pipe->dev = NULL;
memset(pipe, 0, sizeof(*pipe));
dprintf("usb-xhci: %s exit\n", __func__);
}
static int xhci_poll_intr(struct usb_pipe *pipe, uint8_t *data)
{
struct xhci_transfer_trb *trb;
struct xhci_seg *seg;
struct xhci_pipe *xpipe;
struct xhci_dev *xdev;
struct xhci_hcd *xhcd;
struct xhci_db_regs *dbr;
uint32_t x_epno;
uint8_t *buf, ret = 1;
if (!pipe || !pipe->dev || !pipe->dev->hcidev)
return 0;
xdev = pipe->dev->priv;
xhcd = (struct xhci_hcd *)pipe->dev->hcidev->priv;
x_epno = xhci_get_epno(pipe);
seg = xhci_pipe_get_seg(pipe);
xpipe = xhci_pipe_get_xpipe(pipe);
if (usb_kb == true) {
/* This event was consumed by bulk transfer */
usb_kb = false;
xhci_get_trb_deq(seg);
goto skip_poll;
}
/* Ring the doorbell - x_epno */
dbr = xhcd->db_regs;
write_reg32(&dbr->db[xdev->slot_id], x_epno);
if (!xhci_poll_event(xhcd, XHCI_POLL_NO_WAIT)) {
return 0;
}
mb();
trb = xhci_get_trb_deq(seg);
buf = xpipe->buf + xhci_trb_get_index(seg, trb) * pipe->mps;
memcpy(data, buf, 8);
memset(buf, 0, 8);
skip_poll:
trb = xhci_get_trb(seg);
buf = (uint8_t *)(xpipe->buf_phys + xhci_trb_get_index(seg, trb) * pipe->mps);
fill_normal_trb(trb, (void *)buf, pipe->mps);
return ret;
}
struct usb_hcd_ops xhci_ops = {
.name = "xhci-hcd",
.init = xhci_init,
.exit = xhci_exit,
.usb_type = USB_XHCI,
.get_pipe = xhci_get_pipe,
.put_pipe = xhci_put_pipe,
.poll_intr = xhci_poll_intr,
.send_ctrl = xhci_send_ctrl,
.transfer_bulk = xhci_transfer_bulk,
.next = NULL,
};
void usb_xhci_register(void)
{
usb_hcd_register(&xhci_ops);
}