firmware/ice40-riscv: Import common parts to all iCE40/RISC-V firmwares
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
diff --git a/firmware/ice40-riscv/common/bin2hex.py b/firmware/ice40-riscv/common/bin2hex.py
new file mode 100755
index 0000000..43ba263
--- /dev/null
+++ b/firmware/ice40-riscv/common/bin2hex.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+#
+# Converts binary into something that can be used by `readmemh`
+#
+# Copyright (C) 2020 Sylvain Munaut <tnt@246tNt.com>
+# SPDX-License-Identifier: MIT
+#
+
+import struct
+import sys
+
+
+def main(argv0, in_name, out_name):
+ with open(in_name, 'rb') as in_fh, open(out_name, 'w') as out_fh:
+ while True:
+ b = in_fh.read(4)
+ if len(b) < 4:
+ break
+ out_fh.write('%08x\n' % struct.unpack('<I', b))
+
+if __name__ == '__main__':
+ main(*sys.argv)
diff --git a/firmware/ice40-riscv/common/console.c b/firmware/ice40-riscv/common/console.c
new file mode 100644
index 0000000..c5a9136
--- /dev/null
+++ b/firmware/ice40-riscv/common/console.c
@@ -0,0 +1,76 @@
+/*
+ * console.c
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <stdint.h>
+
+#include "config.h"
+#include "mini-printf.h"
+
+
+struct wb_uart {
+ uint32_t data;
+ uint32_t clkdiv;
+} __attribute__((packed,aligned(4)));
+
+static volatile struct wb_uart * const uart_regs = (void*)(UART_BASE);
+
+
+static char _printf_buf[128];
+
+void console_init(void)
+{
+#ifdef BOARD_E1_TRACER
+ uart_regs->clkdiv = 22; /* ~1 Mbaud with clk=24MHz */
+#else
+ uart_regs->clkdiv = 29; /* ~1 Mbaud with clk=30.72MHz */
+#endif
+}
+
+char getchar(void)
+{
+ int32_t c;
+ do {
+ c = uart_regs->data;
+ } while (c & 0x80000000);
+ return c;
+}
+
+int getchar_nowait(void)
+{
+ int32_t c;
+ c = uart_regs->data;
+ return c & 0x80000000 ? -1 : (c & 0xff);
+}
+
+void putchar(char c)
+{
+ uart_regs->data = c;
+}
+
+void puts(const char *p)
+{
+ char c;
+ while ((c = *(p++)) != 0x00) {
+ if (c == '\n')
+ uart_regs->data = '\r';
+ uart_regs->data = c;
+ }
+}
+
+int printf(const char *fmt, ...)
+{
+ va_list va;
+ int l;
+
+ va_start(va, fmt);
+ l = mini_vsnprintf(_printf_buf, 128, fmt, va);
+ va_end(va);
+
+ puts(_printf_buf);
+
+ return l;
+}
diff --git a/firmware/ice40-riscv/common/console.h b/firmware/ice40-riscv/common/console.h
new file mode 100644
index 0000000..2645927
--- /dev/null
+++ b/firmware/ice40-riscv/common/console.h
@@ -0,0 +1,16 @@
+/*
+ * console.h
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+void console_init(void);
+
+char getchar(void);
+int getchar_nowait(void);
+void putchar(char c);
+void puts(const char *p);
+int printf(const char *fmt, ...);
diff --git a/firmware/ice40-riscv/common/dma.c b/firmware/ice40-riscv/common/dma.c
new file mode 100644
index 0000000..a2c81cc
--- /dev/null
+++ b/firmware/ice40-riscv/common/dma.c
@@ -0,0 +1,69 @@
+/*
+ * dma.c
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "config.h"
+#include "dma.h"
+
+
+struct dma {
+ uint32_t csr;
+ uint32_t _rsvd;
+ uint32_t addr_e1;
+ uint32_t addr_usb;
+} __attribute__((packed,aligned(4)));
+
+#define DMA_CSR_GO (1 << 15)
+#define DMA_CSR_BUSY (1 << 15)
+#define DMA_DIR_E1_TO_USB (0 << 14)
+#define DMA_DIR_USB_TO_E1 (1 << 14)
+#define DMA_CSR_LEN(x) (((x)-2) & 0x1fff)
+
+static volatile struct dma * const dma_regs = (void*)(DMA_BASE);
+
+static struct {
+ bool pending;
+ dma_cb cb_fn;
+ void *cb_data;
+} g_dma;
+
+
+bool
+dma_ready(void)
+{
+ return !(dma_regs->csr & DMA_CSR_BUSY);
+}
+
+void
+dma_exec(unsigned addr_e1, unsigned addr_usb, unsigned len, bool dir,
+ dma_cb cb_fn, void *cb_data)
+{
+ dma_regs->addr_e1 = addr_e1;
+ dma_regs->addr_usb = addr_usb;
+ dma_regs->csr =
+ DMA_CSR_GO |
+ (dir ? DMA_DIR_USB_TO_E1 : DMA_DIR_E1_TO_USB) |
+ DMA_CSR_LEN(len);
+
+ g_dma.pending = true;
+ g_dma.cb_fn = cb_fn;
+ g_dma.cb_data = cb_data;
+}
+
+bool
+dma_poll(void)
+{
+ if (g_dma.pending && dma_ready()) {
+ g_dma.pending = false;
+ if (g_dma.cb_fn)
+ g_dma.cb_fn(g_dma.cb_data);
+ }
+
+ return g_dma.pending;
+}
diff --git a/firmware/ice40-riscv/common/dma.h b/firmware/ice40-riscv/common/dma.h
new file mode 100644
index 0000000..1793750
--- /dev/null
+++ b/firmware/ice40-riscv/common/dma.h
@@ -0,0 +1,23 @@
+/*
+ * dma.h
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+typedef void (*dma_cb)(void *);
+
+/* Direction
+ * 0 is E1 to USB
+ * 1 is USB to E1
+ */
+
+bool dma_ready(void);
+void dma_exec(unsigned addr_e1, unsigned addr_usb, unsigned len, bool dir,
+ dma_cb cb_fn, void *cb_data);
+
+bool dma_poll(void);
diff --git a/firmware/ice40-riscv/common/led.c b/firmware/ice40-riscv/common/led.c
new file mode 100644
index 0000000..3ae298a
--- /dev/null
+++ b/firmware/ice40-riscv/common/led.c
@@ -0,0 +1,167 @@
+/*
+ * led.c
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "config.h"
+#include "led.h"
+
+
+struct ledda_ip {
+ uint32_t _rsvd0;
+ uint32_t pwrr; /* 0001 LEDDPWRR - Pulse Width Register Red */
+ uint32_t pwrg; /* 0010 LEDDPWRG - Pulse Width Register Green */
+ uint32_t pwrb; /* 0011 LEDDPWRB - Pulse Width Register Blue */
+ uint32_t _rsvd1;
+ uint32_t bcrr; /* 0101 LEDDBCRR - Breathe Control Rise Register */
+ uint32_t bcfr; /* 0101 LEDDBCFR - Breathe Control Fall Register */
+ uint32_t _rsvd2;
+ uint32_t cr0; /* 1000 LEDDCR0 - Control Register 0 */
+ uint32_t br; /* 1001 LEDDBR - Pre-scale Register */
+ uint32_t onr; /* 1010 LEDONR - ON Time Register */
+ uint32_t ofr; /* 1011 LEDOFR - OFF Time Register */
+} __attribute__((packed,aligned(4)));
+
+#define LEDDA_IP_CR0_LEDDEN (1 << 7)
+#define LEDDA_IP_CR0_FR250 (1 << 6)
+#define LEDDA_IP_CR0_OUTPOL (1 << 5)
+#define LEDDA_IP_CR0_OUTSKEW (1 << 4)
+#define LEDDA_IP_CR0_QUICK_STOP (1 << 3)
+#define LEDDA_IP_CR0_PWM_LINEAR (0 << 2)
+#define LEDDA_IP_CR0_PWM_LFSR (1 << 2)
+#define LEDDA_IP_CR0_SCALE_MSB(x) (((x) >> 8) & 3)
+
+#define LEDDA_IP_BR_SCALE_LSB(x) ((x) & 0xff)
+
+#define LEDDA_IP_ONOFF_TIME_MS(x) (((x) >> 5) & 0xff) /* 32ms interval up to 8s */
+
+#define LEDDA_IP_BREATHE_ENABLE (1 << 7)
+#define LEDDA_IP_BREATHE_MODULATE (1 << 5)
+#define LEDDA_IP_BREATHE_TIME_MS(x) (((x) >> 7) & 0x0f) /* 128ms interval up to 2s */
+
+
+struct led {
+ uint32_t csr;
+ uint32_t _rsvd[15];
+ struct ledda_ip ip;
+} __attribute__((packed,aligned(4)));
+
+#define LED_CSR_LEDDEXE (1 << 1)
+#define LED_CSR_RGBLEDEN (1 << 2)
+#define LED_CSR_CURREN (1 << 3)
+
+
+static volatile struct led * const led_regs = (void*)(LED_BASE);
+
+static const uint32_t led_cr0_base =
+ LEDDA_IP_CR0_FR250 |
+ LEDDA_IP_CR0_OUTSKEW |
+ LEDDA_IP_CR0_QUICK_STOP |
+ LEDDA_IP_CR0_PWM_LFSR |
+ LEDDA_IP_CR0_SCALE_MSB(480);
+
+
+void
+led_init(void)
+{
+ led_regs->ip.pwrr = 0;
+ led_regs->ip.pwrg = 0;
+ led_regs->ip.pwrb = 0;
+
+ led_regs->ip.bcrr = 0;
+ led_regs->ip.bcfr = 0;
+
+ led_regs->ip.onr = 0;
+ led_regs->ip.ofr = 0;
+
+ led_regs->ip.br = LEDDA_IP_BR_SCALE_LSB(480);
+ led_regs->ip.cr0 = led_cr0_base;
+
+ led_regs->csr = LED_CSR_LEDDEXE | LED_CSR_RGBLEDEN | LED_CSR_CURREN;
+}
+
+void
+led_color(uint8_t r, uint8_t g, uint8_t b)
+{
+#if defined(BOARD_ICE1USB)
+ // icE1usb
+ led_regs->ip.pwrr = b;
+ led_regs->ip.pwrg = g;
+ led_regs->ip.pwrb = r;
+#elif defined(BOARD_ICE1USB_PROTO_ICEBREAKER)
+ // iCEBreaker v1.0b tnt
+ led_regs->ip.pwrr = r;
+ led_regs->ip.pwrg = b;
+ led_regs->ip.pwrb = g;
+/*
+ // iCEBreaker v1.0c+
+ led_regs->ip.pwrr = b;
+ led_regs->ip.pwrg = g;
+ led_regs->ip.pwrb = r;
+ */
+#elif defined(BOARD_ICE1USB_PROTO_BITSY)
+ // iCEBreaker bitsy v0 (RGB led 'hacked on')
+ led_regs->ip.pwrr = g;
+ led_regs->ip.pwrg = r;
+ led_regs->ip.pwrb = b;
+#elif defined(BOARD_E1_TRACER)
+ // E1 tracer
+ led_regs->ip.pwrr = b;
+ led_regs->ip.pwrg = g;
+ led_regs->ip.pwrb = r;
+#else
+ // Default / Unknown
+ led_regs->ip.pwrr = r;
+ led_regs->ip.pwrg = g;
+ led_regs->ip.pwrb = b;
+#endif
+}
+
+void
+led_state(bool on)
+{
+ if (on)
+ led_regs->ip.cr0 = led_cr0_base | LEDDA_IP_CR0_LEDDEN;
+ else
+ led_regs->ip.cr0 = led_cr0_base;
+}
+
+void
+led_blink(bool enabled, int on_time_ms, int off_time_ms)
+{
+ /* Disable EXE before doing any change */
+ led_regs->csr = LED_CSR_RGBLEDEN | LED_CSR_CURREN;
+
+ /* Load new config */
+ if (enabled) {
+ led_regs->ip.onr = LEDDA_IP_ONOFF_TIME_MS(on_time_ms);
+ led_regs->ip.ofr = LEDDA_IP_ONOFF_TIME_MS(off_time_ms);
+ } else {
+ led_regs->ip.onr = 0;
+ led_regs->ip.ofr = 0;
+ }
+
+ /* Re-enable execution */
+ led_regs->csr = LED_CSR_LEDDEXE | LED_CSR_RGBLEDEN | LED_CSR_CURREN;
+}
+
+void
+led_breathe(bool enabled, int rise_time_ms, int fall_time_ms)
+{
+ if (enabled) {
+ led_regs->ip.bcrr = LEDDA_IP_BREATHE_ENABLE |
+ LEDDA_IP_BREATHE_MODULATE |
+ LEDDA_IP_BREATHE_TIME_MS(rise_time_ms);
+ led_regs->ip.bcfr = LEDDA_IP_BREATHE_ENABLE |
+ LEDDA_IP_BREATHE_MODULATE |
+ LEDDA_IP_BREATHE_TIME_MS(fall_time_ms);
+ } else {
+ led_regs->ip.bcrr = 0;
+ led_regs->ip.bcfr = 0;
+ }
+}
diff --git a/firmware/ice40-riscv/common/led.h b/firmware/ice40-riscv/common/led.h
new file mode 100644
index 0000000..a48c8d2
--- /dev/null
+++ b/firmware/ice40-riscv/common/led.h
@@ -0,0 +1,16 @@
+/*
+ * led.h
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+void led_init(void);
+void led_color(uint8_t r, uint8_t g, uint8_t b);
+void led_state(bool on);
+void led_blink(bool enabled, int on_time_ms, int off_time_ms);
+void led_breathe(bool enabled, int rise_time_ms, int fall_time_ms);
diff --git a/firmware/ice40-riscv/common/lnk-app.lds b/firmware/ice40-riscv/common/lnk-app.lds
new file mode 100644
index 0000000..27e51fa
--- /dev/null
+++ b/firmware/ice40-riscv/common/lnk-app.lds
@@ -0,0 +1,52 @@
+MEMORY
+{
+ SPRAM (xrw) : ORIGIN = 0x00020000, LENGTH = 0x10000
+ BRAM (xrw) : ORIGIN = 0x00000010, LENGTH = 0x03f0
+}
+ENTRY(_start)
+SECTIONS {
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text.start)
+ *(.text)
+ *(.text*)
+ *(.rodata)
+ *(.rodata*)
+ *(.srodata)
+ *(.srodata*)
+ . = ALIGN(4);
+ _etext = .;
+ _sidata = _etext;
+ } >SPRAM
+ .data : AT ( _sidata )
+ {
+ . = ALIGN(4);
+ _sdata = .;
+ _ram_start = .;
+ . = ALIGN(4);
+ *(.data)
+ *(.data*)
+ *(.sdata)
+ *(.sdata*)
+ . = ALIGN(4);
+ _edata = .;
+ } >SPRAM
+ .bss :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ *(.bss)
+ *(.bss*)
+ *(.sbss)
+ *(.sbss*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ } >SPRAM
+ .heap :
+ {
+ . = ALIGN(4);
+ _heap_start = .;
+ } >SPRAM
+}
diff --git a/firmware/ice40-riscv/common/mini-printf.c b/firmware/ice40-riscv/common/mini-printf.c
new file mode 100644
index 0000000..53cfe99
--- /dev/null
+++ b/firmware/ice40-riscv/common/mini-printf.c
@@ -0,0 +1,208 @@
+/*
+ * The Minimal snprintf() implementation
+ *
+ * Copyright (c) 2013,2014 Michal Ludvig <michal@logix.cz>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the auhor nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----
+ *
+ * This is a minimal snprintf() implementation optimised
+ * for embedded systems with a very limited program memory.
+ * mini_snprintf() doesn't support _all_ the formatting
+ * the glibc does but on the other hand is a lot smaller.
+ * Here are some numbers from my STM32 project (.bin file size):
+ * no snprintf(): 10768 bytes
+ * mini snprintf(): 11420 bytes (+ 652 bytes)
+ * glibc snprintf(): 34860 bytes (+24092 bytes)
+ * Wasting nearly 24kB of memory just for snprintf() on
+ * a chip with 32kB flash is crazy. Use mini_snprintf() instead.
+ *
+ */
+
+#include "mini-printf.h"
+
+static unsigned int
+mini_strlen(const char *s)
+{
+ unsigned int len = 0;
+ while (s[len] != '\0') len++;
+ return len;
+}
+
+static unsigned int
+mini_itoa(int value, unsigned int radix, unsigned int uppercase, unsigned int unsig,
+ char *buffer, unsigned int zero_pad)
+{
+ char *pbuffer = buffer;
+ int negative = 0;
+ unsigned int i, len;
+
+ /* No support for unusual radixes. */
+ if (radix > 16)
+ return 0;
+
+ if (value < 0 && !unsig) {
+ negative = 1;
+ value = -value;
+ }
+
+ /* This builds the string back to front ... */
+ do {
+ int digit = value % radix;
+ *(pbuffer++) = (digit < 10 ? '0' + digit : (uppercase ? 'A' : 'a') + digit - 10);
+ value /= radix;
+ } while (value > 0);
+
+ for (i = (pbuffer - buffer); i < zero_pad; i++)
+ *(pbuffer++) = '0';
+
+ if (negative)
+ *(pbuffer++) = '-';
+
+ *(pbuffer) = '\0';
+
+ /* ... now we reverse it (could do it recursively but will
+ * conserve the stack space) */
+ len = (pbuffer - buffer);
+ for (i = 0; i < len / 2; i++) {
+ char j = buffer[i];
+ buffer[i] = buffer[len-i-1];
+ buffer[len-i-1] = j;
+ }
+
+ return len;
+}
+
+struct mini_buff {
+ char *buffer, *pbuffer;
+ unsigned int buffer_len;
+};
+
+static int
+_putc(int ch, struct mini_buff *b)
+{
+ if ((unsigned int)((b->pbuffer - b->buffer) + 1) >= b->buffer_len)
+ return 0;
+ *(b->pbuffer++) = ch;
+ *(b->pbuffer) = '\0';
+ return 1;
+}
+
+static int
+_puts(char *s, unsigned int len, struct mini_buff *b)
+{
+ unsigned int i;
+
+ if (b->buffer_len - (b->pbuffer - b->buffer) - 1 < len)
+ len = b->buffer_len - (b->pbuffer - b->buffer) - 1;
+
+ /* Copy to buffer */
+ for (i = 0; i < len; i++)
+ *(b->pbuffer++) = s[i];
+ *(b->pbuffer) = '\0';
+
+ return len;
+}
+
+int
+mini_vsnprintf(char *buffer, unsigned int buffer_len, const char *fmt, va_list va)
+{
+ struct mini_buff b;
+ char bf[24];
+ char ch;
+
+ b.buffer = buffer;
+ b.pbuffer = buffer;
+ b.buffer_len = buffer_len;
+
+ while ((ch=*(fmt++))) {
+ if ((unsigned int)((b.pbuffer - b.buffer) + 1) >= b.buffer_len)
+ break;
+ if (ch!='%')
+ _putc(ch, &b);
+ else {
+ char zero_pad = 0;
+ char *ptr;
+ unsigned int len;
+
+ ch=*(fmt++);
+
+ /* Zero padding requested */
+ if (ch=='0') {
+ ch=*(fmt++);
+ if (ch == '\0')
+ goto end;
+ if (ch >= '0' && ch <= '9')
+ zero_pad = ch - '0';
+ ch=*(fmt++);
+ }
+
+ switch (ch) {
+ case 0:
+ goto end;
+
+ case 'u':
+ case 'd':
+ len = mini_itoa(va_arg(va, unsigned int), 10, 0, (ch=='u'), bf, zero_pad);
+ _puts(bf, len, &b);
+ break;
+
+ case 'x':
+ case 'X':
+ len = mini_itoa(va_arg(va, unsigned int), 16, (ch=='X'), 1, bf, zero_pad);
+ _puts(bf, len, &b);
+ break;
+
+ case 'c' :
+ _putc((char)(va_arg(va, int)), &b);
+ break;
+
+ case 's' :
+ ptr = va_arg(va, char*);
+ _puts(ptr, mini_strlen(ptr), &b);
+ break;
+
+ default:
+ _putc(ch, &b);
+ break;
+ }
+ }
+ }
+end:
+ return b.pbuffer - b.buffer;
+}
+
+
+int
+mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...)
+{
+ int ret;
+ va_list va;
+ va_start(va, fmt);
+ ret = mini_vsnprintf(buffer, buffer_len, fmt, va);
+ va_end(va);
+
+ return ret;
+}
diff --git a/firmware/ice40-riscv/common/mini-printf.h b/firmware/ice40-riscv/common/mini-printf.h
new file mode 100644
index 0000000..99a9519
--- /dev/null
+++ b/firmware/ice40-riscv/common/mini-printf.h
@@ -0,0 +1,50 @@
+/*
+ * The Minimal snprintf() implementation
+ *
+ * Copyright (c) 2013 Michal Ludvig <michal@logix.cz>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the auhor nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#ifndef __MINI_PRINTF__
+#define __MINI_PRINTF__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+int mini_vsnprintf(char* buffer, unsigned int buffer_len, const char *fmt, va_list va);
+int mini_snprintf(char* buffer, unsigned int buffer_len, const char *fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define vsnprintf mini_vsnprintf
+#define snprintf mini_snprintf
+
+#endif
diff --git a/firmware/ice40-riscv/common/spi.c b/firmware/ice40-riscv/common/spi.c
new file mode 100644
index 0000000..9072d27
--- /dev/null
+++ b/firmware/ice40-riscv/common/spi.c
@@ -0,0 +1,246 @@
+/*
+ * spi.c
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "config.h"
+#include "spi.h"
+
+
+struct spi {
+ uint32_t _rsvd0[6];
+ uint32_t irq; /* 0110 - SPIIRQ - Interrupt Status Register */
+ uint32_t irqen; /* 0111 - SPIIRQEN - Interrupt Control Register */
+ uint32_t cr0; /* 1000 - CR0 - Control Register 0 */
+ uint32_t cr1; /* 1001 - CR1 - Control Register 1 */
+ uint32_t cr2; /* 1010 - CR2 - Control Register 2 */
+ uint32_t br; /* 1011 - BR - Baud Rate Register */
+ uint32_t sr; /* 1100 - SR - Status Register */
+ uint32_t txdr; /* 1101 - TXDR - Transmit Data Register */
+ uint32_t rxdr; /* 1110 - RXDR - Receive Data Register */
+ uint32_t csr; /* 1111 - CSR - Chip Select Register */
+} __attribute__((packed,aligned(4)));
+
+#define SPI_CR0_TIDLE(xcnt) (((xcnt) & 3) << 6)
+#define SPI_CR0_TTRAIL(xcnt) (((xcnt) & 7) << 3)
+#define SPI_CR0_TLEAD(xcnt) (((xcnt) & 7) << 0)
+
+#define SPI_CR1_ENABLE (1 << 7)
+#define SPI_CR1_WKUPEN_USER (1 << 6)
+#define SPI_CR1_TXEDGE (1 << 4)
+
+#define SPI_CR2_MASTER (1 << 7)
+#define SPI_CR2_MCSH (1 << 6)
+#define SPI_CR2_SDBRE (1 << 5)
+#define SPI_CR2_CPOL (1 << 2)
+#define SPI_CR2_CPHA (1 << 1)
+#define SPI_CR2_LSBF (1 << 0)
+
+#define SPI_SR_TIP (1 << 7)
+#define SPI_SR_BUSY (1 << 6)
+#define SPI_SR_TRDY (1 << 4)
+#define SPI_SR_RRDY (1 << 3)
+#define SPI_SR_TOE (1 << 2)
+#define SPI_SR_ROE (1 << 1)
+#define SPI_SR_MDF (1 << 0)
+
+
+static volatile struct spi * const spi_regs[] = {
+ (void*)(SPI_FLASH_BASE),
+#ifdef BOARD_E1_TRACER
+ (void*)(SPI_LIU_BASE),
+#endif
+};
+
+
+void
+spi_init(void)
+{
+ /* Channel 0: Flash */
+ spi_regs[0]->cr0 = SPI_CR0_TIDLE(3) |
+ SPI_CR0_TTRAIL(7) |
+ SPI_CR0_TLEAD(7);
+
+ spi_regs[0]->cr1 = SPI_CR1_ENABLE;
+ spi_regs[0]->cr2 = SPI_CR2_MASTER | SPI_CR2_MCSH;
+ spi_regs[0]->br = 3;
+ spi_regs[0]->csr = 0xf;
+
+#ifdef BOARD_E1_TRACER
+ /* Channel 1: LIU */
+ spi_regs[1]->cr0 = SPI_CR0_TIDLE(3) |
+ SPI_CR0_TTRAIL(7) |
+ SPI_CR0_TLEAD(7);
+ spi_regs[1]->cr1 = SPI_CR1_ENABLE | SPI_CR1_TXEDGE;
+ spi_regs[1]->cr2 = SPI_CR2_MASTER | SPI_CR2_LSBF | SPI_CR2_MCSH | SPI_CR2_CPHA;
+ spi_regs[1]->br = 3;
+ spi_regs[1]->csr = 0xf;
+#endif
+}
+
+void
+spi_xfer(unsigned cs, const struct spi_xfer_chunk *xfer, unsigned n)
+{
+ unsigned chan = (cs >> 2);
+ cs &= 3;
+
+ /* Setup CS */
+ //spi_regs[chan]->cr2 |= SPI_CR2_MCSH;
+ spi_regs[chan]->csr = 0xf ^ (1 << cs);
+
+ /* Run the chunks */
+ while (n--) {
+ for (int i=0; i<xfer->len; i++)
+ {
+ spi_regs[chan]->txdr = xfer->write ? xfer->data[i] : 0x00;
+ while (!(spi_regs[chan]->sr & SPI_SR_RRDY));
+ if (xfer->read)
+ xfer->data[i] = spi_regs[chan]->rxdr;
+ }
+ xfer++;
+ }
+
+ /* Clear CS */
+ //spi_regs[chan]->cr2 &= ~SPI_CR2_MCSH;
+ spi_regs[chan]->csr = 0xf;
+}
+
+
+#define FLASH_CMD_DEEP_POWER_DOWN 0xb9
+#define FLASH_CMD_WAKE_UP 0xab
+#define FLASH_CMD_WRITE_ENABLE 0x06
+#define FLASH_CMD_WRITE_ENABLE_VOLATILE 0x50
+#define FLASH_CMD_WRITE_DISABLE 0x04
+
+#define FLASH_CMD_READ_MANUF_ID 0x9f
+#define FLASH_CMD_READ_UNIQUE_ID 0x4b
+
+#define FLASH_CMD_READ_SR1 0x05
+#define FLASH_CMD_WRITE_SR1 0x01
+
+#define FLASH_CMD_READ_DATA 0x03
+#define FLASH_CMD_PAGE_PROGRAM 0x02
+#define FLASH_CMD_CHIP_ERASE 0x60
+#define FLASH_CMD_SECTOR_ERASE 0x20
+
+void
+flash_cmd(uint8_t cmd)
+{
+ struct spi_xfer_chunk xfer[1] = {
+ { .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 1);
+}
+
+void
+flash_deep_power_down(void)
+{
+ flash_cmd(FLASH_CMD_DEEP_POWER_DOWN);
+}
+
+void
+flash_wake_up(void)
+{
+ flash_cmd(FLASH_CMD_WAKE_UP);
+}
+
+void
+flash_write_enable(void)
+{
+ flash_cmd(FLASH_CMD_WRITE_ENABLE);
+}
+
+void
+flash_write_enable_volatile(void)
+{
+ flash_cmd(FLASH_CMD_WRITE_ENABLE_VOLATILE);
+}
+
+void
+flash_write_disable(void)
+{
+ flash_cmd(FLASH_CMD_WRITE_DISABLE);
+}
+
+void
+flash_manuf_id(void *manuf)
+{
+ uint8_t cmd = FLASH_CMD_READ_MANUF_ID;
+ struct spi_xfer_chunk xfer[2] = {
+ { .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
+ { .data = (void*)manuf, .len = 3, .read = true, .write = false, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 2);
+}
+
+void
+flash_unique_id(void *id)
+{
+ uint8_t cmd = FLASH_CMD_READ_UNIQUE_ID;
+ struct spi_xfer_chunk xfer[3] = {
+ { .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
+ { .data = (void*)0, .len = 4, .read = false, .write = false, },
+ { .data = (void*)id, .len = 8, .read = true, .write = false, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 3);
+}
+
+uint8_t
+flash_read_sr(void)
+{
+ uint8_t cmd = FLASH_CMD_READ_SR1;
+ uint8_t rv;
+ struct spi_xfer_chunk xfer[2] = {
+ { .data = (void*)&cmd, .len = 1, .read = false, .write = true, },
+ { .data = (void*)&rv, .len = 1, .read = true, .write = false, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 2);
+ return rv;
+}
+
+void
+flash_write_sr(uint8_t sr)
+{
+ uint8_t cmd[2] = { FLASH_CMD_WRITE_SR1, sr };
+ struct spi_xfer_chunk xfer[1] = {
+ { .data = (void*)cmd, .len = 2, .read = false, .write = true, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 1);
+}
+
+void
+flash_read(void *dst, uint32_t addr, unsigned len)
+{
+ uint8_t cmd[4] = { FLASH_CMD_READ_DATA, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
+ struct spi_xfer_chunk xfer[2] = {
+ { .data = (void*)cmd, .len = 4, .read = false, .write = true, },
+ { .data = (void*)dst, .len = len, .read = true, .write = false, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 2);
+}
+
+void
+flash_page_program(void *src, uint32_t addr, unsigned len)
+{
+ uint8_t cmd[4] = { FLASH_CMD_PAGE_PROGRAM, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
+ struct spi_xfer_chunk xfer[2] = {
+ { .data = (void*)cmd, .len = 4, .read = false, .write = true, },
+ { .data = (void*)src, .len = len, .read = false, .write = true, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 2);
+}
+
+void
+flash_sector_erase(uint32_t addr)
+{
+ uint8_t cmd[4] = { FLASH_CMD_SECTOR_ERASE, ((addr >> 16) & 0xff), ((addr >> 8) & 0xff), (addr & 0xff) };
+ struct spi_xfer_chunk xfer[1] = {
+ { .data = (void*)cmd, .len = 4, .read = false, .write = true, },
+ };
+ spi_xfer(SPI_CS_FLASH, xfer, 1);
+}
diff --git a/firmware/ice40-riscv/common/spi.h b/firmware/ice40-riscv/common/spi.h
new file mode 100644
index 0000000..3a95358
--- /dev/null
+++ b/firmware/ice40-riscv/common/spi.h
@@ -0,0 +1,36 @@
+/*
+ * spi.h
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+struct spi_xfer_chunk {
+ uint8_t *data;
+ unsigned len;
+ bool write;
+ bool read;
+};
+
+#define SPI_CS_FLASH 0
+#define SPI_CS_LIU(n) (4+(n))
+
+void spi_init(void);
+void spi_xfer(unsigned cs, const struct spi_xfer_chunk *xfer, unsigned n);
+
+void flash_cmd(uint8_t cmd);
+void flash_deep_power_down(void);
+void flash_wake_up(void);
+void flash_write_enable(void);
+void flash_write_disable(void);
+void flash_manuf_id(void *manuf);
+void flash_unique_id(void *id);
+uint8_t flash_read_sr(void);
+void flash_write_sr(uint8_t sr);
+void flash_read(void *dst, uint32_t addr, unsigned len);
+void flash_page_program(void *src, uint32_t addr, unsigned len);
+void flash_sector_erase(uint32_t addr);
diff --git a/firmware/ice40-riscv/common/start.S b/firmware/ice40-riscv/common/start.S
new file mode 100644
index 0000000..cc7abaf
--- /dev/null
+++ b/firmware/ice40-riscv/common/start.S
@@ -0,0 +1,110 @@
+/*
+ * start.S
+ *
+ * Startup code taken from picosoc/picorv32 and adapted for use here
+ *
+ * Copyright (C) 2017 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+ .section .text.start
+ .global _start
+_start:
+
+ // zero-initialize register file
+ addi x1, zero, 0
+ // x2 (sp) is initialized by reset
+ addi x3, zero, 0
+ addi x4, zero, 0
+ addi x5, zero, 0
+ addi x6, zero, 0
+ addi x7, zero, 0
+ addi x8, zero, 0
+ addi x9, zero, 0
+ addi x10, zero, 0
+ addi x11, zero, 0
+ addi x12, zero, 0
+ addi x13, zero, 0
+ addi x14, zero, 0
+ addi x15, zero, 0
+ addi x16, zero, 0
+ addi x17, zero, 0
+ addi x18, zero, 0
+ addi x19, zero, 0
+ addi x20, zero, 0
+ addi x21, zero, 0
+ addi x22, zero, 0
+ addi x23, zero, 0
+ addi x24, zero, 0
+ addi x25, zero, 0
+ addi x26, zero, 0
+ addi x27, zero, 0
+ addi x28, zero, 0
+ addi x29, zero, 0
+ addi x30, zero, 0
+ addi x31, zero, 0
+
+#ifdef BOOT_DEBUG
+ // Set UART divisor
+ li a0, 0x81000000
+ li a1, 28
+ sw a1, 4(a0)
+
+ // Output '1'
+ li a1, 49
+ sw a1, 0(a0)
+#endif
+
+ // copy data section
+ la a0, _sidata
+ la a1, _sdata
+ la a2, _edata
+ bge a1, a2, end_init_data
+loop_init_data:
+ lw a3, 0(a0)
+ sw a3, 0(a1)
+ addi a0, a0, 4
+ addi a1, a1, 4
+ blt a1, a2, loop_init_data
+end_init_data:
+
+#ifdef BOOT_DEBUG
+ // Output '2'
+ li a0, 0x81000000
+ li a1, 50
+ sw a1, 0(a0)
+#endif
+
+ // zero-init bss section
+ la a0, _sbss
+ la a1, _ebss
+ bge a0, a1, end_init_bss
+loop_init_bss:
+ sw zero, 0(a0)
+ addi a0, a0, 4
+ blt a0, a1, loop_init_bss
+end_init_bss:
+
+#ifdef BOOT_DEBUG
+ // Output '3'
+ li a0, 0x81000000
+ li a1, 51
+ sw a1, 0(a0)
+#endif
+
+ // call main
+ call main
+loop:
+ j loop
diff --git a/firmware/ice40-riscv/common/utils.c b/firmware/ice40-riscv/common/utils.c
new file mode 100644
index 0000000..67b292f
--- /dev/null
+++ b/firmware/ice40-riscv/common/utils.c
@@ -0,0 +1,31 @@
+/*
+ * utils.c
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+char *
+hexstr(void *d, int n, bool space)
+{
+ static const char * const hex = "0123456789abcdef";
+ static char buf[96];
+ uint8_t *p = d;
+ char *s = buf;
+ char c;
+
+ while (n--) {
+ c = *p++;
+ *s++ = hex[c >> 4];
+ *s++ = hex[c & 0xf];
+ if (space)
+ *s++ = ' ';
+ }
+
+ s[space?-1:0] = '\0';
+
+ return buf;
+}
diff --git a/firmware/ice40-riscv/common/utils.h b/firmware/ice40-riscv/common/utils.h
new file mode 100644
index 0000000..fc898f8
--- /dev/null
+++ b/firmware/ice40-riscv/common/utils.h
@@ -0,0 +1,12 @@
+/*
+ * utils.h
+ *
+ * Copyright (C) 2019-2020 Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+char *hexstr(void *d, int n, bool space);