diff --git a/firmware/ice40-riscv/icE1usb/.gitignore b/firmware/ice40-riscv/icE1usb/.gitignore new file mode 100644 index 0000000..5d5607c --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/.gitignore @@ -0,0 +1,5 @@ +*.elf +*.hex +*.bin +*.o +*.gen.h diff --git a/firmware/ice40-riscv/icE1usb/Makefile b/firmware/ice40-riscv/icE1usb/Makefile new file mode 100644 index 0000000..67d0a5d --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/Makefile @@ -0,0 +1,82 @@ +#ice1usb +#ice1usb-proto-icebreaker +#ice1usb-proto-bitsty +#e1-tracer +BOARD ?= ice1usb-proto-icebreaker +CROSS ?= riscv-none-embed- +CC = $(CROSS)gcc +OBJCOPY = $(CROSS)objcopy +ICEPROG = iceprog +DFU_UTIL = dfu-util + +BOARD_DEFINE=BOARD_$(shell echo $(BOARD) | tr a-z\- A-Z_) +CFLAGS=-Wall -Os -march=rv32i -mabi=ilp32 -ffreestanding -flto -nostartfiles -fomit-frame-pointer -Wl,--gc-section --specs=nano.specs -D$(BOARD_DEFINE) -I. -I../common + +NO2USB_FW_VERSION=0 +include ../../../gateware/cores/no2usb/fw/fw.mk +CFLAGS += $(INC_no2usb) + +LNK=../common/lnk-app.lds + +HEADERS_common := $(addprefix ../common/, \ + console.h \ + dma.h \ + led.h \ + mini-printf.h \ + spi.h \ + utils.h \ +) + +SOURCES_common := $(addprefix ../common/, \ + ../common/start.S \ + console.c \ + dma.c \ + led.c \ + mini-printf.c \ + spi.c \ + utils.c \ +) + +HEADERS_common += $(HEADERS_no2usb) +SOURCES_common += $(SOURCES_no2usb) + +HEADERS_app=\ + config.h \ + e1.h \ + misc.h \ + usb_str_app.gen.h \ + $(NULL) + +SOURCES_app=\ + e1.c \ + fw_app.c \ + misc.c \ + usb_desc_app.c \ + usb_e1.c \ + $(NULL) + + +all: fw_app.bin + + +fw_app.elf: $(LNK) $(HEADERS_app) $(SOURCES_app) $(HEADERS_common) $(SOURCES_common) + $(CC) $(CFLAGS) -Wl,-Bstatic,-T,$(LNK),--strip-debug -o $@ $(SOURCES_common) $(SOURCES_app) + + +%.hex: %.bin + ../common/bin2hex.py $< $@ + +%.bin: %.elf + $(OBJCOPY) -O binary $< $@ + +prog: fw_app.bin + $(ICEPROG) -o 640k $< + +dfuprog: fw_app.bin + $(DFU_UTIL) -R -a 1 -D $< + + +clean: + rm -f *.bin *.hex *.elf *.o *.gen.h + +.PHONY: prog dfuprog clean diff --git a/firmware/ice40-riscv/icE1usb/config.h b/firmware/ice40-riscv/icE1usb/config.h new file mode 100644 index 0000000..e10a120 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/config.h @@ -0,0 +1,18 @@ +/* + * config.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + +#pragma once + +#define SPI_FLASH_BASE 0x80000000 +#define UART_BASE 0x81000000 +#define LED_BASE 0x82000000 +#define USB_CORE_BASE 0x83000000 +#define USB_DATA_BASE 0x84000000 +#define E1_DATA_BASE 0x85000000 +#define DMA_BASE 0x86000000 +#define E1_CORE_BASE 0x87000000 +#define MISC_BASE 0x88000000 diff --git a/firmware/ice40-riscv/icE1usb/e1.c b/firmware/ice40-riscv/icE1usb/e1.c new file mode 100644 index 0000000..0e14b6f --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/e1.c @@ -0,0 +1,523 @@ +/* + * e1.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include "config.h" +#include "console.h" +#include "e1.h" + +#include "dma.h" +#include "led.h" // FIXME + + +// Hardware +// -------- + +struct e1_chan { + uint32_t csr; + uint32_t bd; +} __attribute__((packed,aligned(4))); + +struct e1_core { + struct e1_chan rx; + struct e1_chan tx; +} __attribute__((packed,aligned(4))); + +#define E1_RX_CR_ENABLE (1 << 0) +#define E1_RX_CR_MODE_TRSP (0 << 1) +#define E1_RX_CR_MODE_BYTE (1 << 1) +#define E1_RX_CR_MODE_BFA (2 << 1) +#define E1_RX_CR_MODE_MFA (3 << 1) +#define E1_RX_CR_OVFL_CLR (1 << 12) +#define E1_RX_SR_ENABLED (1 << 0) +#define E1_RX_SR_ALIGNED (1 << 1) +#define E1_RX_SR_BD_IN_EMPTY (1 << 8) +#define E1_RX_SR_BD_IN_FULL (1 << 9) +#define E1_RX_SR_BD_OUT_EMPTY (1 << 10) +#define E1_RX_SR_BD_OUT_FULL (1 << 11) +#define E1_RX_SR_OVFL (1 << 12) + +#define E1_TX_CR_ENABLE (1 << 0) +#define E1_TX_CR_MODE_TRSP (0 << 1) +#define E1_TX_CR_MODE_TS0 (1 << 1) +#define E1_TX_CR_MODE_TS0_CRC (2 << 1) +#define E1_TX_CR_MODE_TS0_CRC_E (3 << 1) +#define E1_TX_CR_TICK_LOCAL (0 << 3) +#define E1_TX_CR_TICK_REMOTE (1 << 3) +#define E1_TX_CR_ALARM (1 << 4) +#define E1_TX_CR_LOOPBACK (1 << 5) +#define E1_TX_CR_UNFL_CLR (1 << 12) +#define E1_TX_SR_ENABLED (1 << 0) +#define E1_TX_SR_BD_IN_EMPTY (1 << 8) +#define E1_TX_SR_BD_IN_FULL (1 << 9) +#define E1_TX_SR_BD_OUT_EMPTY (1 << 10) +#define E1_TX_SR_BD_OUT_FULL (1 << 11) +#define E1_TX_SR_UNFL (1 << 12) + +#define E1_BD_VALID (1 << 15) +#define E1_BD_CRC1 (1 << 14) +#define E1_BD_CRC0 (1 << 13) +#define E1_BD_ADDR(x) ((x) & 0x7f) +#define E1_BD_ADDR_MSK 0x7f +#define E1_BD_ADDR_SHFT 0 + + +static volatile struct e1_core * const e1_regs = (void *)(E1_CORE_BASE); +static volatile uint8_t * const e1_data = (void *)(E1_DATA_BASE); + + +volatile uint8_t * +e1_data_ptr(int mf, int frame, int ts) +{ + return &e1_data[(mf << 9) | (frame << 5) | ts]; +} + +unsigned int +e1_data_ofs(int mf, int frame, int ts) +{ + return (mf << 9) | (frame << 5) | ts; +} + + +// FIFOs +// ----- +/* Note: FIFO works at 'frame' level (i.e. 32 bytes) */ + +struct e1_fifo { + /* Buffer zone associated with the FIFO */ + unsigned int base; + unsigned int mask; + + /* Pointers / Levels */ + unsigned int wptr[2]; /* 0=committed 1=allocated */ + unsigned int rptr[2]; /* 0=discared 1=peeked */ +}; + + /* Utils */ +static void +e1f_reset(struct e1_fifo *fifo, unsigned int base, unsigned int len) +{ + memset(fifo, 0x00, sizeof(struct e1_fifo)); + fifo->base = base; + fifo->mask = len - 1; +} + +static unsigned int +e1f_allocd_frames(struct e1_fifo *fifo) +{ + /* Number of frames that are allocated (i.e. where we can't write to) */ + return (fifo->wptr[1] - fifo->rptr[0]) & fifo->mask; +} + +static unsigned int +e1f_valid_frames(struct e1_fifo *fifo) +{ + /* Number of valid frames */ + return (fifo->wptr[0] - fifo->rptr[0]) & fifo->mask; +} + +static unsigned int +e1f_unseen_frames(struct e1_fifo *fifo) +{ + /* Number of valid frames that haven't been peeked yet */ + return (fifo->wptr[0] - fifo->rptr[1]) & fifo->mask; +} + +static unsigned int +e1f_free_frames(struct e1_fifo *fifo) +{ + /* Number of frames that aren't allocated */ + return (fifo->rptr[0] - fifo->wptr[1] - 1) & fifo->mask; +} + +static unsigned int +e1f_ofs_to_dma(unsigned int ofs) +{ + /* DMA address are 32-bits word address. Offsets are 32 byte address */ + return (ofs << 3); +} + +static unsigned int +e1f_ofs_to_mf(unsigned int ofs) +{ + /* E1 Buffer Descriptors are always multiframe aligned */ + return (ofs >> 4); +} + + + /* Debug */ +static void +e1f_debug(struct e1_fifo *fifo, const char *name) +{ + unsigned int la, lv, lu, lf; + + la = e1f_allocd_frames(fifo); + lv = e1f_valid_frames(fifo); + lu = e1f_unseen_frames(fifo); + lf = e1f_free_frames(fifo); + + printf("%s: R: %u / %u | W: %u / %u | A:%u V:%u U:%u F:%u\n", + name, + fifo->rptr[0], fifo->rptr[1], fifo->wptr[0], fifo->wptr[1], + la, lv, lu, lf + ); +} + + /* Frame level read/write */ +static unsigned int +e1f_frame_write(struct e1_fifo *fifo, unsigned int *ofs, unsigned int max_frames) +{ + unsigned int lf, le; + + lf = e1f_free_frames(fifo); + le = fifo->mask - fifo->wptr[0] + 1; + + if (max_frames > le) + max_frames = le; + if (max_frames > lf) + max_frames = lf; + + *ofs = fifo->base + fifo->wptr[0]; + fifo->wptr[1] = fifo->wptr[0] = (fifo->wptr[0] + max_frames) & fifo->mask; + + return max_frames; +} + +static unsigned int +e1f_frame_read(struct e1_fifo *fifo, unsigned int *ofs, int max_frames) +{ + unsigned int lu, le; + + lu = e1f_unseen_frames(fifo); + le = fifo->mask - fifo->rptr[1] + 1; + + if (max_frames > le) + max_frames = le; + if (max_frames > lu) + max_frames = lu; + + *ofs = fifo->base + fifo->rptr[1]; + fifo->rptr[0] = fifo->rptr[1] = (fifo->rptr[1] + max_frames) & fifo->mask; + + return max_frames; +} + + + /* MultiFrame level split read/write */ +static bool +e1f_multiframe_write_prepare(struct e1_fifo *fifo, unsigned int *ofs) +{ + unsigned int lf; + + lf = e1f_free_frames(fifo); + if (lf < 16) + return false; + + *ofs = fifo->base + fifo->wptr[1]; + fifo->wptr[1] = (fifo->wptr[1] + 16) & fifo->mask; + + return true; +} + +static void +e1f_multiframe_write_commit(struct e1_fifo *fifo) +{ + fifo->wptr[0] = (fifo->wptr[0] + 16) & fifo->mask; +} + +static bool +e1f_multiframe_read_peek(struct e1_fifo *fifo, unsigned int *ofs) +{ + unsigned int lu; + + lu = e1f_unseen_frames(fifo); + if (lu < 16) + return false; + + *ofs = fifo->base + fifo->rptr[1]; + fifo->rptr[1] = (fifo->rptr[1] + 16) & fifo->mask; + + return true; +} + +static void +e1f_multiframe_read_discard(struct e1_fifo *fifo) +{ + fifo->rptr[0] = (fifo->rptr[0] + 16) & fifo->mask; +} + +static void +e1f_multiframe_empty(struct e1_fifo *fifo) +{ + fifo->rptr[0] = fifo->rptr[1] = (fifo->wptr[0] & ~15); +} + + + +// Main logic +// ---------- + +enum e1_pipe_state { + IDLE = 0, + BOOT = 1, + RUN = 2, + RECOVER = 3, +}; + +static struct { + struct { + uint32_t cr; + struct e1_fifo fifo; + int in_flight; + enum e1_pipe_state state; + } rx; + + struct { + uint32_t cr; + struct e1_fifo fifo; + int in_flight; + enum e1_pipe_state state; + } tx; +} g_e1; + + + + +void +e1_init(bool clk_mode) +{ + /* Global state init */ + memset(&g_e1, 0x00, sizeof(g_e1)); + + /* Reset FIFOs */ + e1f_reset(&g_e1.rx.fifo, 0, 128); + e1f_reset(&g_e1.tx.fifo, 128, 128); + + /* Enable Rx */ + g_e1.rx.cr = E1_RX_CR_OVFL_CLR | + E1_RX_CR_MODE_MFA | + E1_RX_CR_ENABLE; + e1_regs->rx.csr = g_e1.rx.cr; + + /* Enable Tx */ + g_e1.tx.cr = E1_TX_CR_UNFL_CLR | + (clk_mode ? E1_TX_CR_TICK_REMOTE : E1_TX_CR_TICK_LOCAL) | + E1_TX_CR_MODE_TS0_CRC_E | + E1_TX_CR_ENABLE; + e1_regs->tx.csr = g_e1.tx.cr; + + /* State */ + g_e1.rx.state = BOOT; + g_e1.tx.state = BOOT; +} + + +#include "dma.h" + +unsigned int +e1_rx_need_data(unsigned int usb_addr, unsigned int max_frames) +{ + unsigned int ofs; + int tot_frames = 0; + int n_frames; + + while (max_frames) { + /* Get some data from the FIFO */ + n_frames = e1f_frame_read(&g_e1.rx.fifo, &ofs, max_frames); + if (!n_frames) + break; + + /* Copy from FIFO to USB */ + dma_exec(e1f_ofs_to_dma(ofs), usb_addr, n_frames * (32 / 4), false, NULL, NULL); + + /* Prepare Next */ + usb_addr += n_frames * (32 / 4); + max_frames -= n_frames; + tot_frames += n_frames; + + /* Wait for DMA completion */ + while (dma_poll()); + } + + return tot_frames; +} + +unsigned int +e1_tx_feed_data(unsigned int usb_addr, unsigned int frames) +{ + unsigned int ofs; + int n_frames; + + while (frames) { + /* Get some space in FIFO */ + n_frames = e1f_frame_write(&g_e1.tx.fifo, &ofs, frames); + if (!n_frames) { + printf("[!] TX FIFO Overflow %d %d\n", frames, n_frames); + break; + } + + /* Copy from USB to FIFO */ + dma_exec(e1f_ofs_to_dma(ofs), usb_addr, n_frames * (32 / 4), true, NULL, NULL); + + /* Prepare next */ + usb_addr += n_frames * (32 / 4); + frames -= n_frames; + + /* Wait for DMA completion */ + while (dma_poll()); + } + + return frames; +} + +unsigned int +e1_tx_level(void) +{ + return e1f_valid_frames(&g_e1.tx.fifo); +} + +unsigned int +e1_rx_level(void) +{ + return e1f_valid_frames(&g_e1.rx.fifo); +} + +void +e1_poll(void) +{ + uint32_t bd; + unsigned int ofs; + + /* Active ? */ + if ((g_e1.rx.state == IDLE) && (g_e1.tx.state == IDLE)) + return; + + /* HACK: LED link status */ + if (e1_regs->rx.csr & 2) + led_color(0, 48, 0); + else + led_color(48, 0, 0); + + /* Recover any done TX BD */ + while ( (bd = e1_regs->tx.bd) & E1_BD_VALID ) { + e1f_multiframe_read_discard(&g_e1.tx.fifo); + g_e1.tx.in_flight--; + } + + /* Recover any done RX BD */ + while ( (bd = e1_regs->rx.bd) & E1_BD_VALID ) { + /* FIXME: CRC status ? */ + e1f_multiframe_write_commit(&g_e1.rx.fifo); + if ((bd & (E1_BD_CRC0 | E1_BD_CRC1)) != (E1_BD_CRC0 | E1_BD_CRC1)) + printf("b: %03x\n", bd); + g_e1.rx.in_flight--; + } + + /* Boot procedure */ + if (g_e1.tx.state == BOOT) { + if (e1f_unseen_frames(&g_e1.tx.fifo) < (16 * 5)) + return; + /* HACK: LED flow status */ + led_blink(true, 200, 1000); + led_breathe(true, 100, 200); + } + + /* Handle RX */ + /* Misalign ? */ + if (g_e1.rx.state == RUN) { + if (!(e1_regs->rx.csr & E1_RX_SR_ALIGNED)) { + printf("[!] E1 rx misalign\n"); + g_e1.rx.state = RECOVER; + } + } + + /* Overflow ? */ + if (g_e1.rx.state == RUN) { + if (e1_regs->rx.csr & E1_RX_SR_OVFL) { + printf("[!] E1 overflow %d\n", g_e1.rx.in_flight); + g_e1.rx.state = RECOVER; + } + } + + /* Recover ready ? */ + if (g_e1.rx.state == RECOVER) { + if (g_e1.rx.in_flight != 0) + goto done_rx; + e1f_multiframe_empty(&g_e1.rx.fifo); + } + + /* Fill new RX BD */ + while (g_e1.rx.in_flight < 4) { + if (!e1f_multiframe_write_prepare(&g_e1.rx.fifo, &ofs)) + break; + e1_regs->rx.bd = e1f_ofs_to_mf(ofs); + g_e1.rx.in_flight++; + } + + /* Clear overflow if needed */ + if (g_e1.rx.state != RUN) { + e1_regs->rx.csr = g_e1.rx.cr | E1_RX_CR_OVFL_CLR; + g_e1.rx.state = RUN; + } +done_rx: + + /* Handle TX */ + /* Underflow ? */ + if (g_e1.tx.state == RUN) { + if (e1_regs->tx.csr & E1_TX_SR_UNFL) { + printf("[!] E1 underflow %d\n", g_e1.tx.in_flight); + g_e1.tx.state = RECOVER; + } + } + + /* Recover ready ? */ + if (g_e1.tx.state == RECOVER) { + if (e1f_unseen_frames(&g_e1.tx.fifo) < (16 * 5)) + return; + } + + /* Fill new TX BD */ + while (g_e1.tx.in_flight < 4) { + if (!e1f_multiframe_read_peek(&g_e1.tx.fifo, &ofs)) + break; + e1_regs->tx.bd = e1f_ofs_to_mf(ofs); + g_e1.tx.in_flight++; + } + + /* Clear underflow if needed */ + if (g_e1.tx.state != RUN) { + e1_regs->tx.csr = g_e1.tx.cr | E1_TX_CR_UNFL_CLR; + g_e1.tx.state = RUN; + } +} + +void +e1_debug_print(bool data) +{ + volatile uint8_t *p; + + puts("E1\n"); + printf("CSR: Rx %04x / Tx %04x\n", e1_regs->rx.csr, e1_regs->tx.csr); + printf("InF: Rx %d / Tx %d\n", g_e1.rx.in_flight, g_e1.tx.in_flight); + printf("Sta: Rx %d / Tx %d\n", g_e1.rx.state, g_e1.tx.state); + + e1f_debug(&g_e1.rx.fifo, "Rx FIFO"); + e1f_debug(&g_e1.tx.fifo, "Tx FIFO"); + + if (data) { + puts("\nE1 Data\n"); + for (int f=0; f<16; f++) { + p = e1_data_ptr(0, f, 0); + for (int ts=0; ts<32; ts++) + printf(" %02x", p[ts]); + printf("\n"); + } + } +} diff --git a/firmware/ice40-riscv/icE1usb/e1.h b/firmware/ice40-riscv/icE1usb/e1.h new file mode 100644 index 0000000..e68514f --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/e1.h @@ -0,0 +1,15 @@ +/* + * e1.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +void e1_init(bool clk_mode); +void e1_poll(void); +void e1_debug_print(bool data); + +volatile uint8_t *e1_data_ptr(int mf, int frame, int ts); +unsigned int e1_data_ofs(int mf, int frame, int ts); diff --git a/firmware/ice40-riscv/icE1usb/fw_app.c b/firmware/ice40-riscv/icE1usb/fw_app.c new file mode 100644 index 0000000..fe1dd01 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/fw_app.c @@ -0,0 +1,169 @@ +/* + * fw_app.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include +#include + +#include "config.h" + +#include "console.h" +#include "e1.h" +#include "led.h" +#include "misc.h" +#include "mini-printf.h" +#include "spi.h" +#include "utils.h" + + +extern const struct usb_stack_descriptors app_stack_desc; + +static void +serial_no_init() +{ + uint8_t buf[8]; + char *id, *desc; + int i; + + flash_manuf_id(buf); + printf("Flash Manufacturer : %s\n", hexstr(buf, 3, true)); + + flash_unique_id(buf); + printf("Flash Unique ID : %s\n", hexstr(buf, 8, true)); + + /* Overwrite descriptor string */ + /* In theory in rodata ... but nothing is ro here */ + id = hexstr(buf, 8, false); + desc = (char*)app_stack_desc.str[1]; + for (i=0; i<16; i++) + desc[2 + (i << 1)] = id[i]; +} + +static void +boot_dfu(void) +{ + /* Force re-enumeration */ + usb_disconnect(); + + /* Boot firmware */ + volatile uint32_t *boot = (void*)0x80000000; + *boot = (1 << 2) | (1 << 0); +} + +void +usb_dfu_rt_cb_reboot(void) +{ + boot_dfu(); +} + + +static volatile uint32_t * const misc_regs = (void*)(MISC_BASE); + +void main() +{ + bool e1_active = false; + int cmd = 0; + + /* Init console IO */ + console_init(); + puts("Booting App image..\n"); + + /* LED */ + led_init(); + + /* SPI */ + spi_init(); + + /* Setup E1 Vref */ + int d = 25; + pdm_set(PDM_E1_CT, true, 128, false); + pdm_set(PDM_E1_P, true, 128 - d, false); + pdm_set(PDM_E1_N, true, 128 + d, false); + + /* Setup clock tuning */ + pdm_set(PDM_CLK_HI, true, 2048, false); + pdm_set(PDM_CLK_LO, false, 0, false); + + /* Enable USB directly */ + serial_no_init(); + usb_init(&app_stack_desc); + usb_dfu_rt_init(); + usb_e1_init(); + + /* Start */ + e1_init(false); // local tick + e1_active = true; + led_state(true); + usb_connect(); + + /* Main loop */ + while (1) + { + /* Prompt ? */ + if (cmd >= 0) + printf("Command> "); + + /* Poll for command */ + cmd = getchar_nowait(); + + if (cmd >= 0) { + if (cmd > 32 && cmd < 127) { + putchar(cmd); + putchar('\r'); + putchar('\n'); + } + + switch (cmd) + { + case 'p': + usb_debug_print(); + break; + case 'b': + boot_dfu(); + break; + case 'o': + e1_debug_print(false); + break; + case 'O': + e1_debug_print(true); + break; + case 't': + printf("%08x\n", misc_regs[0]); + case 'e': + e1_init(true); + e1_active = true; + led_state(true); + break; + case 'E': + e1_init(false); + e1_active = true; + led_state(true); + break; + case 'c': + usb_connect(); + break; + case 'd': + usb_disconnect(); + break; + default: + break; + } + } + + /* USB poll */ + usb_poll(); + + /* E1 poll */ + if (e1_active) { + e1_poll(); + usb_e1_run(); + } + } +} diff --git a/firmware/ice40-riscv/icE1usb/misc.c b/firmware/ice40-riscv/icE1usb/misc.c new file mode 100644 index 0000000..9ceb8c6 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/misc.c @@ -0,0 +1,50 @@ +/* + * misc.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "config.h" +#include "misc.h" + + +struct misc { + uint32_t warmboot; + uint32_t gpio; + uint32_t e1_led; + uint32_t _rsvd; + struct { + uint16_t tx; + uint16_t rx; + } e1_tick[2]; + uint32_t gps; + uint32_t time; + uint32_t pdm[8]; +} __attribute__((packed,aligned(4))); + +static volatile struct misc * const misc_regs = (void*)(MISC_BASE); + + +static const int pdm_bits[5] = { 12, 12, 8, 8, 8 }; + + +void +pdm_set(int chan, bool enable, unsigned value, bool normalize) +{ + if (normalize) + value >>= (16 - pdm_bits[chan]); + if (enable) + value |= 0x80000000; + misc_regs->pdm[chan] = value; +} + + +uint16_t +e1_tick_read(void) +{ + return misc_regs->e1_tick[0].tx; +} diff --git a/firmware/ice40-riscv/icE1usb/misc.h b/firmware/ice40-riscv/icE1usb/misc.h new file mode 100644 index 0000000..36126da --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/misc.h @@ -0,0 +1,23 @@ +/* + * misc.h + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +enum pdm_chan { + PDM_CLK_LO = 0, + PDM_CLK_HI = 1, + PDM_E1_N = 2, + PDM_E1_P = 3, + PDM_E1_CT = 4, +}; + +void pdm_set(int chan, bool enable, unsigned value, bool normalize); + +uint16_t e1_tick_read(void); diff --git a/firmware/ice40-riscv/icE1usb/usb_desc_app.c b/firmware/ice40-riscv/icE1usb/usb_desc_app.c new file mode 100644 index 0000000..98f2830 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/usb_desc_app.c @@ -0,0 +1,271 @@ +/* + * usb_desc_app.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#define NULL ((void*)0) +#define num_elem(a) (sizeof(a) / sizeof(a[0])) + + +static const struct { + /* Configuration */ + struct usb_conf_desc conf; + + /* E1 */ + struct { + struct { + struct usb_intf_desc intf; + struct usb_ep_desc ep_data_in; + struct usb_ep_desc ep_data_out; + struct usb_ep_desc ep_fb; + } __attribute__ ((packed)) off; + struct { + struct usb_intf_desc intf; + struct usb_ep_desc ep_data_in; + struct usb_ep_desc ep_data_out; + struct usb_ep_desc ep_fb; + } __attribute__ ((packed)) on; + } __attribute__ ((packed)) e1; + + /* CDC */ +#if 0 + struct { + struct usb_intf_desc intf_ctl; + struct usb_cs_intf_hdr_desc cs_intf_hdr; + struct usb_cs_intf_acm_desc cs_intf_acm; + struct usb_cs_intf_union_desc cs_intf_union; + uint8_t cs_intf_union_slave; + struct usb_ep_desc ep_ctl; + struct usb_intf_desc intf_data; + struct usb_ep_desc ep_data_out; + struct usb_ep_desc ep_data_in; + } __attribute__ ((packed)) cdc; +#endif + + /* DFU Runtime */ + struct { + struct usb_intf_desc intf; + struct usb_dfu_desc func; + } __attribute__ ((packed)) dfu; +} __attribute__ ((packed)) _app_conf_desc = { + .conf = { + .bLength = sizeof(struct usb_conf_desc), + .bDescriptorType = USB_DT_CONF, + .wTotalLength = sizeof(_app_conf_desc), +#if 0 + .bNumInterfaces = 4, +#else + .bNumInterfaces = 2, +#endif + .bConfigurationValue = 1, + .iConfiguration = 4, + .bmAttributes = 0x80, + .bMaxPower = 0x32, /* 100 mA */ + }, + .e1 = { + .off = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 3, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xe1, + .bInterfaceProtocol = 0x00, + .iInterface = 5, + }, + .ep_data_in = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x82, + .bmAttributes = 0x05, + .wMaxPacketSize = 0, + .bInterval = 1, + }, + .ep_data_out = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x01, + .bmAttributes = 0x05, + .wMaxPacketSize = 0, + .bInterval = 1, + }, + .ep_fb = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x81, + .bmAttributes = 0x11, + .wMaxPacketSize = 0, + .bInterval = 3, + }, + }, + .on = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 0, + .bAlternateSetting = 1, + .bNumEndpoints = 3, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xe1, + .bInterfaceProtocol = 0x00, + .iInterface = 5, + }, + .ep_data_in = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x82, + .bmAttributes = 0x05, + .wMaxPacketSize = 388, + .bInterval = 1, + }, + .ep_data_out = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x01, + .bmAttributes = 0x05, + .wMaxPacketSize = 388, + .bInterval = 1, + }, + .ep_fb = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x81, + .bmAttributes = 0x11, + .wMaxPacketSize = 8, + .bInterval = 3, + }, + }, + }, +#if 0 + .cdc = { + .intf_ctl = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = 0x02, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 6, + }, + .cs_intf_hdr = { + .bLength = sizeof(struct usb_cs_intf_hdr_desc), + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x00, + .bcdCDC = 0x0110, + }, + .cs_intf_acm = { + .bLength = sizeof(struct usb_cs_intf_acm_desc), + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x02, + .bmCapabilities = 0x02, + }, + .cs_intf_union = { + .bLength = sizeof(struct usb_cs_intf_union_desc) + 1, + .bDescriptorType = USB_DT_CS_INTF, + .bDescriptorsubtype = 0x06, + .bMasterInterface = 1, + }, + .cs_intf_union_slave = 2, + .ep_ctl = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x84, + .bmAttributes = 0x03, + .wMaxPacketSize = 64, + .bInterval = 0x40, + }, + .intf_data = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, + .bInterfaceNumber = 2, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0x0a, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 7, + }, + .ep_data_out = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x05, + .bmAttributes = 0x02, + .wMaxPacketSize = 64, + .bInterval = 0x00, + }, + .ep_data_in = { + .bLength = sizeof(struct usb_ep_desc), + .bDescriptorType = USB_DT_EP, + .bEndpointAddress = 0x85, + .bmAttributes = 0x02, + .wMaxPacketSize = 64, + .bInterval = 0x00, + }, + }, +#endif + .dfu = { + .intf = { + .bLength = sizeof(struct usb_intf_desc), + .bDescriptorType = USB_DT_INTF, +#if 0 + .bInterfaceNumber = 3, +#else + .bInterfaceNumber = 1, +#endif + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = 0xfe, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x01, + .iInterface = 8, + }, + .func = { + .bLength = sizeof(struct usb_dfu_desc), + .bDescriptorType = USB_DT_DFU, + .bmAttributes = 0x0d, + .wDetachTimeOut = 1000, + .wTransferSize = 4096, + .bcdDFUVersion = 0x0101, + }, + }, +}; + +static const struct usb_conf_desc * const _conf_desc_array[] = { + &_app_conf_desc.conf, +}; + +static const struct usb_dev_desc _dev_desc = { + .bLength = sizeof(struct usb_dev_desc), + .bDescriptorType = USB_DT_DEV, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x1d50, + .idProduct = 0x6145, + .bcdDevice = 0x0003, /* v0.3 */ + .iManufacturer = 2, + .iProduct = 3, + .iSerialNumber = 1, + .bNumConfigurations = num_elem(_conf_desc_array), +}; + +#include "usb_str_app.gen.h" + +const struct usb_stack_descriptors app_stack_desc = { + .dev = &_dev_desc, + .conf = _conf_desc_array, + .n_conf = num_elem(_conf_desc_array), + .str = _str_desc_array, + .n_str = num_elem(_str_desc_array), +}; diff --git a/firmware/ice40-riscv/icE1usb/usb_e1.c b/firmware/ice40-riscv/icE1usb/usb_e1.c new file mode 100644 index 0000000..52fb53b --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/usb_e1.c @@ -0,0 +1,264 @@ +/* + * usb_e1.c + * + * Copyright (C) 2019-2020 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include + +#include +#include + +#include "console.h" +#include "misc.h" + +struct { + bool running; + int out_bdi; + int in_bdi; +} g_usb_e1; + + +/* Hack */ +unsigned int e1_rx_need_data(unsigned int usb_addr, unsigned int max_len); +unsigned int e1_tx_feed_data(unsigned int usb_addr, unsigned int len); +unsigned int e1_tx_level(void); +unsigned int e1_rx_level(void); +/* ---- */ + +bool +usb_ep_boot(const struct usb_intf_desc *intf, uint8_t ep_addr, bool dual_bd); + +static void +_usb_fill_feedback_ep(void) +{ + static uint16_t ticks_prev = 0; + uint16_t ticks; + uint32_t val = 8192; + unsigned int level; + + /* Compute real E1 tick count (with safety agains bad values) */ + ticks = e1_tick_read(); + val = (ticks - ticks_prev) & 0xffff; + ticks_prev = ticks; + if ((val < 7168) | (val > 9216)) + val = 8192; + + /* Bias depending on TX fifo level */ + level = e1_tx_level(); + if (level < (3 * 16)) + val += 256; + else if (level > (8 * 16)) + val -= 256; + + /* Prepare buffer */ + usb_data_write(64, &val, 4); + usb_ep_regs[1].in.bd[0].ptr = 64; + usb_ep_regs[1].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(3); +} + + +void +usb_e1_run(void) +{ + int bdi; + + if (!g_usb_e1.running) + return; + + /* EP2 IN */ + bdi = g_usb_e1.in_bdi; + + while ((usb_ep_regs[2].in.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) + { + uint32_t ptr = usb_ep_regs[2].in.bd[bdi].ptr; + uint32_t hdr; + + /* Error check */ + if ((usb_ep_regs[2].in.bd[bdi].csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR) + puts("Err EP2 IN\n"); + + /* Get some data from E1 */ + int n = e1_rx_level(); + + if (n > 64) + n = 12; + else if (n > 32) + n = 10; + else if (n > 8) + n = 8; + else if (!n) + break; + + n = e1_rx_need_data((ptr >> 2) + 1, n); + + /* Write header */ + hdr = 0x616b00b5; + usb_data_write(ptr, &hdr, 4); + + /* Resubmit */ + usb_ep_regs[2].in.bd[bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN((n * 32) + 4); + + /* Next BDI */ + bdi ^= 1; + g_usb_e1.in_bdi = bdi; + } + + /* EP1 OUT */ + bdi = g_usb_e1.out_bdi; + + while ((usb_ep_regs[1].out.bd[bdi].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) + { + uint32_t ptr = usb_ep_regs[1].out.bd[bdi].ptr; + uint32_t csr = usb_ep_regs[1].out.bd[bdi].csr; + uint32_t hdr; + + /* Error check */ + if ((csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_ERR) { + puts("Err EP1 OUT\n"); + goto refill; + } + + /* Grab header */ + usb_data_read(&hdr, ptr, 4); + + /* Empty data into the FIFO */ + int n = ((csr & USB_BD_LEN_MSK) - 4) / 32; + n = e1_tx_feed_data((ptr >> 2) + 1, n); + +refill: + /* Refill it */ + usb_ep_regs[1].out.bd[bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); + + /* Next BDI */ + bdi ^= 1; + g_usb_e1.out_bdi = bdi; + + static int x = 0; + if ((x++ & 0xff) == 0xff) + puts("."); + } + + /* EP1 IN */ + if ((usb_ep_regs[1].in.bd[0].csr & USB_BD_STATE_MSK) != USB_BD_STATE_RDY_DATA) + { + _usb_fill_feedback_ep(); + } +} + +static const struct usb_intf_desc * +_find_intf(const struct usb_conf_desc *conf, uint8_t idx) +{ + const struct usb_intf_desc *intf = NULL; + const void *sod, *eod; + + if (!conf) + return NULL; + + sod = conf; + eod = sod + conf->wTotalLength; + + while (1) { + sod = usb_desc_find(sod, eod, USB_DT_INTF); + if (!sod) + break; + + intf = (void*)sod; + if (intf->bInterfaceNumber == idx) + return intf; + + sod = usb_desc_next(sod); + } + + return NULL; +} +enum usb_fnd_resp +_e1_set_conf(const struct usb_conf_desc *conf) +{ + const struct usb_intf_desc *intf; + + printf("e1 set_conf %08x\n", conf); + if (!conf) + return USB_FND_SUCCESS; + + intf = _find_intf(conf, 0); + if (!intf) + return USB_FND_ERROR; + + printf("e1 set_conf %08x\n", intf); + + usb_ep_boot(intf, 0x01, true); + usb_ep_boot(intf, 0x81, true); + usb_ep_boot(intf, 0x82, true); + + return USB_FND_SUCCESS; +} + +enum usb_fnd_resp +_e1_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel) +{ + if (base->bInterfaceNumber != 0) + return USB_FND_CONTINUE; + + if (sel->bAlternateSetting != 1) + return USB_FND_SUCCESS; + + /* Hack to avoid re-setting while running ... avoid BD desync */ + if (g_usb_e1.running) + return USB_FND_SUCCESS; + + g_usb_e1.running = true; + + /* Configure EP1 OUT / EP2 IN */ + usb_ep_regs[1].out.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL; /* Type=Isochronous, dual buffered */ + usb_ep_regs[2].in.status = USB_EP_TYPE_ISOC | USB_EP_BD_DUAL; /* Type=Isochronous, dual buffered */ + + /* Configure EP1 IN (feedback) */ + usb_ep_regs[1].in.status = USB_EP_TYPE_ISOC; /* Type=Isochronous, single buffered */ + + /* EP2 IN: Prepare two buffers */ + usb_ep_regs[2].in.bd[0].ptr = 1024; + usb_ep_regs[2].in.bd[0].csr = 0; + + usb_ep_regs[2].in.bd[1].ptr = 1536; + usb_ep_regs[2].in.bd[1].csr = 0; + + /* EP1 OUT: Queue two buffers */ + usb_ep_regs[1].out.bd[0].ptr = 1024; + usb_ep_regs[1].out.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); + + usb_ep_regs[1].out.bd[1].ptr = 1536; + usb_ep_regs[1].out.bd[1].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(388); + + /* EP1 IN: Queue buffer */ + _usb_fill_feedback_ep(); + + return USB_FND_SUCCESS; +} + +enum usb_fnd_resp +_e1_get_intf(const struct usb_intf_desc *base, uint8_t *alt) +{ + if (base->bInterfaceNumber != 0) + return USB_FND_CONTINUE; + + *alt = g_usb_e1.running ? 1 : 0; + + return USB_FND_SUCCESS; +} + +static struct usb_fn_drv _e1_drv = { + .set_conf = _e1_set_conf, + .set_intf = _e1_set_intf, + .get_intf = _e1_get_intf, +}; + +void +usb_e1_init(void) +{ + memset(&g_usb_e1, 0x00, sizeof(g_usb_e1)); + usb_register_function_driver(&_e1_drv); +} diff --git a/firmware/ice40-riscv/icE1usb/usb_str_app.txt b/firmware/ice40-riscv/icE1usb/usb_str_app.txt new file mode 100644 index 0000000..10887d3 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/usb_str_app.txt @@ -0,0 +1,8 @@ +0000000000000000 +osmocom +icE1usb +Main +E1 +Console (control) +Console (data) +DFU runtime