blob: 8814766a23941f71e62ae2b5c7800ecb23c966fc [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// Get NULL from here
#include "usbdev.h"
#include <stddef.h>
#include "common.h"
#define USBDEV_BASE_ADDR 0x40020000
#include "usbdev_regs.h"
#define EXTRACT(n, f) ((n >> USBDEV_##f##_OFFSET) & USBDEV_##f##_MASK)
// 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_BUFFER() + (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_USBSTAT()) & (1 << USBDEV_USBSTAT_AV_FULL))) {
usbbufid_t buf = usbdev_buf_allocate_byid(ctx);
if (buf < 0) {
// no more free buffers, can't fill AV FIFO
break;
}
REG32(USBDEV_AVBUFFER()) = buf;
}
}
void usbdev_sendbuf_byid(usbdev_ctx_t *ctx, usbbufid_t buf, size_t size,
int endpoint) {
uint32_t configin = USBDEV_CONFIGIN0() + (4 * endpoint);
if ((endpoint >= NUM_ENDPOINTS) || (buf >= NUM_BUFS)) {
return;
}
if (size > BUF_LENGTH) {
size = BUF_LENGTH;
}
REG32(configin) =
((buf << USBDEV_CONFIGIN0_BUFFER0_OFFSET) |
(size << USBDEV_CONFIGIN0_SIZE0_OFFSET) | (1 << USBDEV_CONFIGIN0_RDY0));
}
void usbdev_poll(usbdev_ctx_t *ctx) {
uint32_t istate = REG32(USBDEV_INTR_STATE());
// 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)) {
uint32_t sentep = REG32(USBDEV_IN_SENT());
uint32_t configin = USBDEV_CONFIGIN0();
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, CONFIGIN0_BUFFER0));
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_IN_SENT()) = sentep;
// Clear the interupt
REG32(USBDEV_INTR_STATE()) = (1 << USBDEV_INTR_STATE_PKT_SENT);
}
if (istate & (1 << USBDEV_INTR_STATE_PKT_RECEIVED)) {
while (!(REG32(USBDEV_USBSTAT()) & (1 << USBDEV_USBSTAT_RX_EMPTY))) {
uint32_t rxinfo = REG32(USBDEV_RXFIFO());
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) & 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_INTR_STATE()) = (1 << USBDEV_INTR_STATE_PKT_RECEIVED);
}
if (istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED) |
(1 << USBDEV_INTR_STATE_PKT_SENT))) {
TRC_C('I');
TRC_I(istate, 12);
TRC_C(' ');
REG32(USBDEV_INTR_STATE()) =
istate & ~((1 << USBDEV_INTR_STATE_PKT_RECEIVED) |
(1 << USBDEV_INTR_STATE_PKT_SENT));
}
// TODO(mdhayter) - 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_USBSTAT()), 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(mdhater) Errors? What Errors?
}
void usbdev_set_deviceid(usbdev_ctx_t *ctx, int deviceid) {
REG32(USBDEV_USBCTRL()) = (1 << USBDEV_USBCTRL_ENABLE) |
(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_STALL());
if (enable) {
stall |= epbit;
} else {
stall &= ~epbit;
}
REG32(USBDEV_STALL()) = stall;
ctx->halted = stall;
// TODO future addition would be to callback the endpoint driver
// for now it just sees its traffic has stopped
}
// 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 *)) {
ctx->ep_ctx[ep] = ep_ctx;
ctx->tx_done_callback[ep] = tx_done;
ctx->rx_callback[ep] = rx;
ctx->flush[ep] = flush;
if (enableout) {
uint32_t rxen = REG32(USBDEV_RXENABLE());
rxen |= (1 << (ep + USBDEV_RXENABLE_OUT0));
REG32(USBDEV_RXENABLE()) = rxen;
}
}
void usbdev_init(usbdev_ctx_t *ctx) {
// setup context
for (int i = 0; i < NUM_ENDPOINTS; i++) {
usbdev_endpoint_setup(ctx, i, 0, NULL, NULL, NULL, NULL);
}
ctx->halted = 0;
ctx->can_wake = 0;
buf_init(ctx);
// All about polling...
REG32(USBDEV_INTR_ENABLE()) = 0;
// Provide buffers for any reception
fill_av_fifo(ctx);
REG32(USBDEV_RXENABLE()) =
((1 << USBDEV_RXENABLE_SETUP0) | (1 << USBDEV_RXENABLE_OUT0));
REG32(USBDEV_USBCTRL()) = (1 << USBDEV_USBCTRL_ENABLE);
}