historical/m0-applesillicon.git/xnu-qemu-arm64-5.1.0/roms/SLOF/lib/libusb/usb-xhci.c

1553 lines
40 KiB
C
Raw Normal View History

2024-01-16 11:20:27 -06:00
/*****************************************************************************
* 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);
}