blob: 01dc91473b1373e83bf9c9e0e005089e44ce18cb [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/usbdev.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#include "usbdev_regs.h" // Generated.
#define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR
#define EXTRACT(n, f) ((n >> USBDEV_##f##_OFFSET) & USBDEV_##f##_MASK)
#define SETBIT(val, bit) (val | 1 << bit)
#define CLRBIT(val, bit) (val & ~(1 << bit))
#define REG32(add) *((volatile uint32_t *)(add))
// Free buffer pool is held on a simple stack
// Initalize to all buffer IDs are free
static void buf_init(usbdev_ctx_t *ctx) {
for (int i = 0; i < NUM_BUFS; i++) {
ctx->freebuf[i] = i;
}
ctx->nfree = NUM_BUFS;
}
// Allocating a buffer just pops next ID from the stack
usbbufid_t usbdev_buf_allocate_byid(usbdev_ctx_t *ctx) {
if (ctx->nfree <= 0) {
return -1;
}
return ctx->freebuf[--ctx->nfree];
}
// Freeing a buffer just pushes the ID back on the stack
int usbdev_buf_free_byid(usbdev_ctx_t *ctx, usbbufid_t buf) {
if ((ctx->nfree >= NUM_BUFS) || (buf >= NUM_BUFS)) {
return -1;
}
ctx->freebuf[ctx->nfree++] = buf;
return 0;
}
uint32_t *usbdev_buf_idtoaddr(usbdev_ctx_t *ctx, usbbufid_t buf) {
return (uint32_t *)(USBDEV_BASE_ADDR + USBDEV_BUFFER_REG_OFFSET +
(buf * BUF_LENGTH));
}
void usbdev_buf_copyto_byid(usbdev_ctx_t *ctx, usbbufid_t buf, const void *from,
size_t len_bytes) {
int32_t *from_word = (int32_t *)from;
int len_words;
volatile uint32_t *bp = usbdev_buf_idtoaddr(ctx, buf);
if (len_bytes > BUF_LENGTH) {
len_bytes = BUF_LENGTH;
}
// This will round up if len_bytes is not on a multiple of int32_t
// Always ok to fill the extra bytes since the buffers are aligned
len_words = (len_bytes + sizeof(int32_t) - 1) / sizeof(int32_t);
for (int i = 0; i < len_words; i++) {
bp[i] = from_word[i];
}
}
// Supply as many buffers to the receive available fifo as possible
inline static void fill_av_fifo(usbdev_ctx_t *ctx) {
while (!(REG32(USBDEV_BASE_ADDR + USBDEV_USBSTAT_REG_OFFSET) &
(1 << USBDEV_USBSTAT_AV_FULL_BIT))) {
usbbufid_t buf = usbdev_buf_allocate_byid(ctx);
if (buf < 0) {
// no more free buffers, can't fill AV FIFO
break;
}
REG32(USBDEV_BASE_ADDR + USBDEV_AVBUFFER_REG_OFFSET) = buf;
}
}
void usbdev_sendbuf_byid(usbdev_ctx_t *ctx, usbbufid_t buf, size_t size,
int endpoint) {
uint32_t configin =
USBDEV_BASE_ADDR + USBDEV_CONFIGIN_0_REG_OFFSET + (4 * endpoint);
if ((endpoint >= NUM_ENDPOINTS) || (buf >= NUM_BUFS)) {
return;
}
if (size > BUF_LENGTH) {
size = BUF_LENGTH;
}
REG32(configin) = ((buf << USBDEV_CONFIGIN_0_BUFFER_0_OFFSET) |
(size << USBDEV_CONFIGIN_0_SIZE_0_OFFSET) |
(1 << USBDEV_CONFIGIN_0_RDY_0_BIT));
}
void usbdev_poll(usbdev_ctx_t *ctx) {
uint32_t istate = REG32(USBDEV_BASE_ADDR + USBDEV_INTR_STATE_REG_OFFSET);
// Do this first to keep things going
fill_av_fifo(ctx);
// Process IN completions first so we get the fact that send completed
// before processing a response
if (istate & (1 << USBDEV_INTR_STATE_PKT_SENT_BIT)) {
uint32_t sentep = REG32(USBDEV_BASE_ADDR + USBDEV_IN_SENT_REG_OFFSET);
uint32_t configin = USBDEV_BASE_ADDR + USBDEV_CONFIGIN_0_REG_OFFSET;
TRC_C('a' + sentep);
for (int ep = 0; ep < NUM_ENDPOINTS; ep++) {
if (sentep & (1 << ep)) {
// Free up the buffer and optionally callback
int32_t cfgin = REG32(configin + (4 * ep));
usbdev_buf_free_byid(ctx, EXTRACT(cfgin, CONFIGIN_0_BUFFER_0));
if (ctx->tx_done_callback[ep]) {
ctx->tx_done_callback[ep](ctx->ep_ctx[ep]);
}
}
}
// Write one to clear all the ones we handled
REG32(USBDEV_BASE_ADDR + USBDEV_IN_SENT_REG_OFFSET) = sentep;
// Clear the interupt
REG32(USBDEV_BASE_ADDR + USBDEV_INTR_STATE_REG_OFFSET) =
(1 << USBDEV_INTR_STATE_PKT_SENT_BIT);
}
if (istate & (1 << USBDEV_INTR_STATE_PKT_RECEIVED_BIT)) {
while (!(REG32(USBDEV_BASE_ADDR + USBDEV_USBSTAT_REG_OFFSET) &
(1 << USBDEV_USBSTAT_RX_EMPTY_BIT))) {
uint32_t rxinfo = REG32(USBDEV_BASE_ADDR + USBDEV_RXFIFO_REG_OFFSET);
usbbufid_t buf = EXTRACT(rxinfo, RXFIFO_BUFFER);
int size = EXTRACT(rxinfo, RXFIFO_SIZE);
int endpoint = EXTRACT(rxinfo, RXFIFO_EP);
int setup = (rxinfo >> USBDEV_RXFIFO_SETUP_BIT) & 1;
if (ctx->rx_callback[endpoint]) {
ctx->rx_callback[endpoint](ctx->ep_ctx[endpoint], buf, size, setup);
} else {
TRC_S("USB: unexpected RX ");
TRC_I(rxinfo, 24);
}
usbdev_buf_free_byid(ctx, buf);
}
// Clear the interupt
REG32(USBDEV_BASE_ADDR + USBDEV_INTR_STATE_REG_OFFSET) =
(1 << USBDEV_INTR_STATE_PKT_RECEIVED_BIT);
}
if (istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED_BIT) |
(1 << USBDEV_INTR_STATE_PKT_SENT_BIT))) {
TRC_C('I');
TRC_I(istate, 12);
TRC_C(' ');
REG32(USBDEV_BASE_ADDR + USBDEV_INTR_STATE_REG_OFFSET) =
istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED_BIT) |
(1 << USBDEV_INTR_STATE_PKT_SENT_BIT));
if (istate & (1 << USBDEV_INTR_ENABLE_LINK_RESET_BIT)) {
// Link reset
for (int ep = 0; ep < NUM_ENDPOINTS; ep++) {
if (ctx->reset[ep]) {
ctx->reset[ep](ctx->ep_ctx[ep]);
}
}
// Clear the interupt
REG32(USBDEV_BASE_ADDR + USBDEV_INTR_STATE_REG_OFFSET) =
(1 << USBDEV_INTR_ENABLE_LINK_RESET_BIT);
}
}
// TODO - clean this up
// Frame ticks every 1ms, use to flush data every 16ms
// (faster in DPI but this seems to work ok)
// At reset frame count is 0, compare to 1 so no calls before SOF received
uint32_t usbframe = EXTRACT(
REG32(USBDEV_BASE_ADDR + USBDEV_USBSTAT_REG_OFFSET), USBSTAT_FRAME);
if ((usbframe & 0xf) == 1) {
if (ctx->flushed == 0) {
for (int i = 0; i < NUM_ENDPOINTS; i++) {
if (ctx->flush[i]) {
ctx->flush[i](ctx->ep_ctx[i]);
}
}
ctx->flushed = 1;
}
} else {
ctx->flushed = 0;
}
// TODO Errors? What Errors?
}
unsigned int usbdev_get_status(usbdev_ctx_t *ctx) {
unsigned int status = REG32(USBDEV_BASE_ADDR + USBDEV_USBSTAT_REG_OFFSET);
return status;
}
unsigned int usbdev_get_link_state(usbdev_ctx_t *ctx) {
unsigned int link_state = EXTRACT(
REG32(USBDEV_BASE_ADDR + USBDEV_USBSTAT_REG_OFFSET), USBSTAT_LINK_STATE);
return link_state;
}
unsigned int usbdev_get_address(usbdev_ctx_t *ctx) {
unsigned int addr =
EXTRACT(REG32(USBDEV_BASE_ADDR + USBDEV_USBCTRL_REG_OFFSET),
USBCTRL_DEVICE_ADDRESS);
return addr;
}
void usbdev_set_deviceid(usbdev_ctx_t *ctx, int deviceid) {
REG32(USBDEV_BASE_ADDR + USBDEV_USBCTRL_REG_OFFSET) =
(1 << USBDEV_USBCTRL_ENABLE_BIT) |
(deviceid << USBDEV_USBCTRL_DEVICE_ADDRESS_OFFSET);
}
void usbdev_halt(usbdev_ctx_t *ctx, int endpoint, int enable) {
uint32_t epbit = 1 << endpoint;
uint32_t stall = REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET);
if (enable) {
stall |= epbit;
} else {
stall &= ~epbit;
}
REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET) = stall;
ctx->halted = stall;
// TODO future addition would be to callback the endpoint driver
// for now it just sees its traffic has stopped
}
void usbdev_set_iso(usbdev_ctx_t *ctx, int endpoint, int enable) {
if (enable) {
REG32(USBDEV_BASE_ADDR + USBDEV_ISO_REG_OFFSET) =
SETBIT(REG32(USBDEV_BASE_ADDR + USBDEV_ISO_REG_OFFSET), endpoint);
} else {
REG32(USBDEV_BASE_ADDR + USBDEV_ISO_REG_OFFSET) =
CLRBIT(REG32(USBDEV_BASE_ADDR + USBDEV_ISO_REG_OFFSET), endpoint);
}
}
void usbdev_clear_data_toggle(usbdev_ctx_t *ctx, int endpoint) {
REG32(USBDEV_BASE_ADDR + USBDEV_DATA_TOGGLE_CLEAR_REG_OFFSET) =
(1 << endpoint);
}
void usbdev_set_ep0_stall(usbdev_ctx_t *ctx, int stall) {
if (stall) {
REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET) =
REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET) | 1;
} else {
REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET) =
REG32(USBDEV_BASE_ADDR + USBDEV_STALL_REG_OFFSET) & ~(1);
}
}
// TODO got hang with this inline
int usbdev_can_rem_wake(usbdev_ctx_t *ctx) { return ctx->can_wake; }
void usbdev_endpoint_setup(usbdev_ctx_t *ctx, int ep, int enableout,
void *ep_ctx, void (*tx_done)(void *),
void (*rx)(void *, usbbufid_t, int, int),
void (*flush)(void *), void (*reset)(void *)) {
ctx->ep_ctx[ep] = ep_ctx;
ctx->tx_done_callback[ep] = tx_done;
ctx->rx_callback[ep] = rx;
ctx->flush[ep] = flush;
ctx->reset[ep] = reset;
if (enableout) {
uint32_t rxen = REG32(USBDEV_BASE_ADDR + USBDEV_RXENABLE_OUT_REG_OFFSET);
rxen |= (1 << (ep + USBDEV_RXENABLE_OUT_OUT_0_BIT));
REG32(USBDEV_BASE_ADDR + USBDEV_RXENABLE_OUT_REG_OFFSET) = rxen;
}
}
void usbdev_init(usbdev_ctx_t *ctx, bool pinflip, bool diff_rx, bool diff_tx) {
// setup context
for (int i = 0; i < NUM_ENDPOINTS; i++) {
usbdev_endpoint_setup(ctx, i, 0, NULL, NULL, NULL, NULL, NULL);
}
ctx->halted = 0;
ctx->can_wake = 0;
buf_init(ctx);
// All about polling...
REG32(USBDEV_BASE_ADDR + USBDEV_INTR_ENABLE_REG_OFFSET) = 0;
// Provide buffers for any reception
fill_av_fifo(ctx);
REG32(USBDEV_BASE_ADDR + USBDEV_RXENABLE_SETUP_REG_OFFSET) =
(1 << USBDEV_RXENABLE_SETUP_SETUP_0_BIT);
REG32(USBDEV_BASE_ADDR + USBDEV_RXENABLE_OUT_REG_OFFSET) =
(1 << USBDEV_RXENABLE_OUT_OUT_0_BIT);
uint32_t phy_config =
(pinflip << USBDEV_PHY_CONFIG_PINFLIP_BIT) |
(diff_rx << USBDEV_PHY_CONFIG_RX_DIFFERENTIAL_MODE_BIT) |
(diff_tx << USBDEV_PHY_CONFIG_TX_DIFFERENTIAL_MODE_BIT) |
(1 << USBDEV_PHY_CONFIG_EOP_SINGLE_BIT_BIT);
REG32(USBDEV_BASE_ADDR + USBDEV_PHY_CONFIG_REG_OFFSET) = phy_config;
REG32(USBDEV_BASE_ADDR + USBDEV_USBCTRL_REG_OFFSET) =
(1 << USBDEV_USBCTRL_ENABLE_BIT);
}
void usbdev_force_dx_pullup(line_sel_t line, bool set) {
// Force usb to pretend it is in suspend
uint32_t reg_val = REG32(USBDEV_BASE_ADDR + USBDEV_PHY_PINS_DRIVE_REG_OFFSET);
uint32_t mask;
mask = line == kDpSel ? USBDEV_PHY_PINS_DRIVE_DP_PULLUP_EN_O_BIT
: USBDEV_PHY_PINS_DRIVE_DN_PULLUP_EN_O_BIT;
if (set) {
reg_val = SETBIT(reg_val, mask);
} else {
reg_val = CLRBIT(reg_val, mask);
}
reg_val = SETBIT(reg_val, USBDEV_PHY_PINS_DRIVE_EN_BIT);
REG32(USBDEV_BASE_ADDR + USBDEV_PHY_PINS_DRIVE_REG_OFFSET) = reg_val;
}
void usbdev_force_suspend() {
// Force usb to pretend it is in suspend
REG32(USBDEV_BASE_ADDR + USBDEV_PHY_PINS_DRIVE_REG_OFFSET) |=
1 << USBDEV_PHY_PINS_DRIVE_SUSPEND_O_BIT |
1 << USBDEV_PHY_PINS_DRIVE_EN_BIT;
}
void usbdev_wake(bool set) {
uint32_t reg_val = REG32(USBDEV_BASE_ADDR + USBDEV_WAKE_CONFIG_REG_OFFSET);
if (set) {
reg_val = SETBIT(reg_val, USBDEV_WAKE_CONFIG_WAKE_EN_BIT);
} else {
reg_val = CLRBIT(reg_val, USBDEV_WAKE_CONFIG_WAKE_EN_BIT);
}
REG32(USBDEV_BASE_ADDR + USBDEV_WAKE_CONFIG_REG_OFFSET) = reg_val;
}