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