| // 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); |
| } |