Selaa lähdekoodia

esp32 firmware image with support for OTA updates of both FPGA and ESP32

Beginnings of an integrated esp32 firmware image; so far still built
separately as it uses the Arduino framework; still need to integrate.

Currently a firmware image needs to be updated with:

 curl --data-binary @fpga/output/v2.fw http://<ip>/firmware-upgrade

... a wrapper web form to come later.
H. Peter Anvin 2 vuotta sitten
vanhempi
commit
f1e04bf5c7
71 muutettua tiedostoa jossa 4017 lisäystä ja 202 poistoa
  1. 32 0
      esp32/max80/asprintf.c
  2. 61 0
      esp32/max80/common.h
  3. 210 0
      esp32/max80/fpga.c
  4. 8 0
      esp32/max80/fpga.h
  5. 42 0
      esp32/max80/fw.h
  6. 309 0
      esp32/max80/fwupdate.c
  7. 121 0
      esp32/max80/httpd.c
  8. 14 0
      esp32/max80/httpd.h
  9. 83 0
      esp32/max80/jtag.h
  10. 754 0
      esp32/max80/jtag_esp32.c
  11. 64 0
      esp32/max80/max80.ino
  12. BIN
      esp32/max80/max80.ino.esp32s2usb.bin
  13. 82 0
      esp32/max80/ota.c
  14. 5 0
      esp32/max80/ota.h
  15. 28 0
      esp32/max80/reboot.c
  16. 419 0
      esp32/max80/spiflash.c
  17. 72 0
      esp32/max80/spiflash.h
  18. 35 0
      esp32/max80/spz.h
  19. 130 0
      esp32/max80/storage.cpp
  20. 15 0
      esp32/max80/storage.h
  21. 112 0
      esp32/max80/tap.c
  22. 51 0
      esp32/max80/taproute.c
  23. 190 0
      esp32/max80/wifi.cpp
  24. 5 0
      esp32/max80/wifi.h
  25. 42 24
      fpga/Makefile
  26. 57 0
      fpga/bypass.pins
  27. 164 0
      fpga/bypass.qsf
  28. 18 0
      fpga/bypass.sdc
  29. 71 0
      fpga/bypass.sv
  30. 1 0
      fpga/bypass_description.txt
  31. 3 2
      fpga/max80.qpf
  32. 24 44
      fpga/max80.sv
  33. BIN
      fpga/output/bypass.jic
  34. 327 0
      fpga/output/bypass.pin
  35. BIN
      fpga/output/bypass.rbf.gz
  36. BIN
      fpga/output/bypass.rpd.gz
  37. BIN
      fpga/output/bypass.sof
  38. BIN
      fpga/output/bypass.svf.gz
  39. BIN
      fpga/output/bypass.xsvf.gz
  40. BIN
      fpga/output/jtagupd/v1.rbf.gz
  41. BIN
      fpga/output/jtagupd/v1.sof
  42. BIN
      fpga/output/jtagupd/v1.svf.gz
  43. BIN
      fpga/output/jtagupd/v2.rbf.gz
  44. BIN
      fpga/output/jtagupd/v2.sof
  45. BIN
      fpga/output/jtagupd/v2.svf.gz
  46. BIN
      fpga/output/v1.fw
  47. BIN
      fpga/output/v1.jic
  48. BIN
      fpga/output/v1.rbf.gz
  49. BIN
      fpga/output/v1.rpd.gz
  50. BIN
      fpga/output/v1.sof
  51. BIN
      fpga/output/v1.svf.gz
  52. BIN
      fpga/output/v1.update.svf.gz
  53. BIN
      fpga/output/v1.update.xsvf.gz
  54. BIN
      fpga/output/v1.xsvf.gz
  55. BIN
      fpga/output/v2.fw
  56. BIN
      fpga/output/v2.jic
  57. BIN
      fpga/output/v2.rbf.gz
  58. BIN
      fpga/output/v2.rpd.gz
  59. BIN
      fpga/output/v2.sof
  60. BIN
      fpga/output/v2.svf.gz
  61. BIN
      fpga/output/v2.update.svf.gz
  62. BIN
      fpga/output/v2.update.xsvf.gz
  63. BIN
      fpga/output/v2.xsvf.gz
  64. 26 23
      fpga/serial.sv
  65. 193 0
      fpga/v2.qsf
  66. 1 1
      rv32/checksum.h
  67. 15 4
      rv32/compiler.h
  68. 40 24
      rv32/memcpy.S
  69. 160 0
      tools/mkfwimage.pl
  70. 33 14
      tools/svf2xsvf.py
  71. 0 66
      tools/wrapflash.pl

+ 32 - 0
esp32/max80/asprintf.c

@@ -0,0 +1,32 @@
+#include "common.h"
+
+int vasprintf(char **bufp, const char *fmt, va_list va)
+{
+    va_list va2;
+    int len;
+    char *buf;
+
+    va_copy(va2, va);
+    len = vsnprintf(NULL, 0, fmt, va2);
+    va_end(va2);
+
+    buf = malloc(len+1);
+    if (buf)
+	len = vsnprintf(buf, len+1, fmt, va);
+
+    *bufp = buf;
+
+    return len;
+}
+
+int asprintf(char **bufp, const char *fmt, ...)
+{
+    va_list va;
+    int len;
+
+    va_start(va, fmt);
+    len = vasprintf(bufp, fmt, va);
+    va_end(va);
+
+    return len;
+}

+ 61 - 0
esp32/max80/common.h

@@ -0,0 +1,61 @@
+#pragma once
+
+/* Standard C headers */
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Arduino headers */
+#include <Arduino.h>
+#include <FreeRTOS.h>
+
+/* ESP-IDF headers */
+#include <sdkconfig.h>
+#include <esp_err.h>
+#include <esp_event.h>
+#include <esp_log.h>
+
+#ifdef __cplusplus
+# define extern_c extern "C"
+# define EXTERN_C(...) extern "C" { __VA_ARGS__ }
+#else
+# define extern_c extern
+# define EXTERN_C(...) __VA_ARGS__
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+#ifndef MODULE
+# define MODULE ""
+#endif
+
+#if DEBUG
+# define CMSG(...) printf(__VA_ARGS__)
+#else
+# define CMSG(...) ((void)0)
+#endif
+#define MSG(...)  CMSG(MODULE ": " __VA_ARGS__)
+
+/*
+ * Common types for callbacks
+ */
+typedef void *token_t;
+typedef int (*read_func_t)(token_t token, void *buf, size_t len);
+typedef int (*write_func_t)(token_t token, const void *buf, size_t len);
+
+/*
+ * Sleep thread...
+ */
+static inline void suspend(void)
+{
+    vTaskSuspend(NULL);
+}
+
+/*
+ * Reboot system
+ */
+extern_c void reboot_now(void);
+extern_c int reboot_delayed(void);

+ 210 - 0
esp32/max80/fpga.c

@@ -0,0 +1,210 @@
+#define MODULE "fpga"
+#define DEBUG 1
+
+#include "common.h"
+#include "jtag.h"
+#include "fpga.h"
+#include "spz.h"
+
+/*
+ * See:
+ * https://github.com/RichardPlunkett/jrunner-beaglebone/blob/master/jb_jtag.c
+ * and the Cyclone III (!) handbook, volume 1, table 9-20, page 9-63
+ */
+enum JTAG_IR {
+    JI_EXTEST           = 0x000,
+    JI_PULSE_NCONFIG    = 0x001,
+    JI_PROGRAM          = 0x002,
+    JI_STARTUP          = 0x003,
+    JI_CHECK_STATUS     = 0x004,
+    JI_SAMPLE           = 0x005,
+    JI_IDCODE           = 0x006,
+    JI_USERCODE         = 0x007,
+    JI_CONFIG_IO        = 0x00d,
+    JI_CLAMP            = 0x00a,
+    JI_HIGHZ            = 0x00b,
+    JI_EXTEST2          = 0x00f, /* Stratix II, Cyclone II */
+    JI_KEY_CLR_VREG     = 0x029,
+    JI_KEY_PROG_VOL     = 0x1ad,
+    JI_EN_ACTIVE_CLK    = 0x1ee,
+    JI_FACTORY          = 0x281,
+    JI_ACTIVE_ENGAGE    = 0x2b0,
+    JI_ACTIVE_DISENGAGE = 0x2d0,
+    JI_DIS_ACTIVE_CLK   = 0x2ee,
+    JI_BYPASS           = 0x3ff
+};
+#define FPGA_IR_LEN 10
+
+/* Copied from the SVF file */
+#define JTAG_FPGA_LEADIN_BITS		(22*8)
+
+/*
+ * The check status chain seems to match the I/O chain, with in order
+ * {output, control, input}; the chain represents the pads in
+ * *reverse* order with bits [2:0] corresponding to pad 363 (D3) and
+ * [1079:1077] to pad 0; pads 33-36 are the JTAG pins and are not
+ * included in the chain.
+ */
+#define JTAG_FPGA_CHECK_STATUS_BITS	1080
+#define PAD_TO_BIT(p,b)	(((359 - ((p) - 4*((p) > 36)))*3)+(b))
+#define JTAG_FPGA_CONF_DONE_BIT		 PAD_TO_BIT(227, 1)
+
+#define JTAG_FPGA_HZ	6000000
+#define JTAG_FPGA_MS	((JTAG_FPGA_HZ+999)/1000)
+#define JTAG_FPGA_US	((JTAG_FPGA_HZ+999999)/1000000)
+
+static const struct jtag_config jtag_config_fpga = {
+    .hz      = JTAG_FPGA_HZ,
+    .pin_tdi = 16,
+    .pin_tdo = 17,
+    .pin_tms = 14,
+    .pin_tck = 18,
+    .be      = false
+};
+
+static bool test_bit(const uint32_t *buf, unsigned int bit)
+{
+    return (buf[bit >> 5] >> (bit & 31)) & 1;
+}
+
+static void fpga_finish(void)
+{
+    tap_goto_state(TAP_RUN_TEST_IDLE);
+
+    /* Park IR at bypass, wait 1 ms */
+    tap_set_ir(JI_BYPASS, FPGA_IR_LEN);
+    tap_run_test_idle(JTAG_FPGA_MS);
+
+    jtag_disable(NULL);
+}
+
+static uint32_t tap_get_idcode(void)
+{
+    uint32_t idcode;
+
+    tap_set_ir(JI_IDCODE, FPGA_IR_LEN);
+    tap_goto_state(TAP_SHIFT_DR);
+    jtag_io(32, JIO_TMS, NULL, &idcode);
+    tap_goto_state(TAP_RUN_TEST_IDLE);
+
+    return idcode;
+}
+
+/*
+ * See the Cyclone IV handbook, volume 1, table 8-17, page 8-59
+ * for the programming flow.
+ */
+int fpga_program_spz(spz_stream *spz)
+{
+    int err = 0;
+    uint32_t idcode;
+    uint32_t check_status_buf[(JTAG_FPGA_CHECK_STATUS_BITS+31) >> 5];
+
+    /* Configure JTAG to access the FPGA */
+    jtag_enable(&jtag_config_fpga);
+
+    int idcode_loops = 4;
+    while (idcode_loops--) {
+	idcode = tap_get_idcode();
+	if (idcode == spz->header.addr)
+	    break;
+
+	MSG("invalid IDCODE %08X expected %08X, %s\n",
+	    idcode, spz->header.addr,
+	    idcode_loops ? "attempting reset..." : "giving up");
+
+	if (!idcode_loops) {
+	    MSG("check for JTAG cable connected, or power cycle board\n");
+	    err = FWUPDATE_ERR_DEVICE_MISMATCH;
+	    goto fail;
+	}
+
+	tap_reset();
+	jtag_delay(1000);
+	tap_goto_state(TAP_SHIFT_DR);
+	jtag_io(32, JIO_TMS, NULL, &idcode);
+	MSG("IDCODE after reset %08X\n", idcode);
+    }
+
+    MSG("IDCODE %08X is valid\n", idcode);
+
+    /* Disengage programming hardware if active */
+    tap_set_ir(JI_ACTIVE_DISENGAGE, FPGA_IR_LEN);
+    tap_run_test_idle(16);
+
+    tap_set_ir(JI_PROGRAM, FPGA_IR_LEN);
+    tap_run_test_idle(16);
+    jtag_delay(100);
+    tap_run_test_idle(8192);
+
+    /* Leadin: shift in a number of 1s */
+    tap_goto_state(TAP_SHIFT_DR);
+    jtag_io(JTAG_FPGA_LEADIN_BITS, JIO_TDI, NULL, NULL);
+
+    /* The actual data */
+    err = jtag_shift_spz(spz, 0);
+
+    /* 32 bits of 0 terminates the transaction */
+    jtag_io(32, JIO_TMS, NULL, NULL);
+    tap_goto_state(TAP_RUN_TEST_IDLE);
+
+    /* Check status */
+    int check_status_loops = 10;
+    while (1) {
+	tap_set_ir(JI_CHECK_STATUS, FPGA_IR_LEN);
+	tap_run_test_idle(5*JTAG_FPGA_US);
+
+	tap_goto_state(TAP_SHIFT_DR);
+	jtag_io(JTAG_FPGA_CHECK_STATUS_BITS, JIO_TMS, NULL, check_status_buf);
+	tap_goto_state(TAP_RUN_TEST_IDLE);
+
+	if (!test_bit(check_status_buf, JTAG_FPGA_CONF_DONE_BIT)) {
+	    check_status_loops--;
+
+	    MSG("not ready to start... %s\n",
+		check_status_loops ? "waiting" : "giving up");
+
+	    if (!check_status_loops) {
+		err = FWUPDATE_ERR_PROGRAM_FAILED;
+		goto fail;
+	    }
+
+	    jtag_delay(10000);	/* 10 ms */
+	} else {
+	    MSG("ready to start\n");
+	    break;
+	}
+    }
+
+    /* Go to user mode */
+    tap_set_ir(JI_STARTUP, FPGA_IR_LEN);
+    tap_run_test_idle((4096*JTAG_FPGA_MS)/1000+512);
+
+    /* Common finish */
+ fail:
+    fpga_finish();
+
+    return err;
+}
+
+int fpga_reset(void)
+{
+    int err = 0;
+
+    jtag_enable(&jtag_config_fpga);
+
+    tap_run_test_idle(JTAG_FPGA_MS);
+
+    /* Make sure to enable loader (not supposed to be needed...) */
+    tap_set_ir(JI_ACTIVE_ENGAGE, FPGA_IR_LEN);
+    tap_run_test_idle(16);
+
+    /* Pulse nCONFIG via JTAG */
+    tap_set_ir(JI_PULSE_NCONFIG, FPGA_IR_LEN);
+    tap_run_test_idle(JTAG_FPGA_MS);
+
+    /* Common finish */
+    fpga_finish();
+
+    return err;
+}

+ 8 - 0
esp32/max80/fpga.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "common.h"
+#include "jtag.h"
+#include "fw.h"
+
+extern_c int fpga_program_spz(spz_stream *spz);
+extern_c int fpga_reset(void);

+ 42 - 0
esp32/max80/fw.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "common.h"
+
+/*
+ * Firmware chunk header.
+ */
+#define FW_MAGIC		0x7a07fbd6
+
+struct fw_header {
+    uint32_t magic;		/* Magic number */
+    uint16_t type;		/* Content type */
+    uint16_t flags;		/* Content flags */
+    uint32_t len;		/* Content length (excluding header) */
+    uint32_t addr;		/* Address or similar */
+};
+
+enum fw_data_type {
+    FDT_END,			/* End of stream */
+    FDT_DATA,			/* FPGA firmware ata to be flashed */
+    FDT_TARGET,			/* Subsystem string (must match) */
+    FDT_NOTE,			/* Version: XXXXX or similar */
+    FDT_ESP_OTA,		/* ESP32 OTA image */
+    FDT_FPGA_INIT		/* FPGA bitstream for update */
+};
+enum fw_data_flags {
+    FDF_OPTIONAL     = 0x0001	/* Ignore if chunk data type unknown */
+};
+
+/*
+ * Additional error codes
+ */
+#define FWUPDATE_ERR_ERASE_FAILED	(-7)
+#define FWUPDATE_ERR_PROGRAM_FAILED	(-8)
+#define FWUPDATE_ERR_WRITE_PROTECT	(-9)
+#define FWUPDATE_ERR_NOT_READY		(-10)
+#define FWUPDATE_ERR_DETECT		(-11)
+#define FWUPDATE_ERR_DEVICE_MISMATCH	(-12)
+#define FWUPDATE_ERR_IN_PROGRESS	(-13)
+
+extern_c int firmware_update(read_func_t read_func, token_t token);
+extern_c esp_err_t esp_update(read_func_t read_func, token_t token, size_t size);

+ 309 - 0
esp32/max80/fwupdate.c

@@ -0,0 +1,309 @@
+#define MODULE "fwupdate"
+#define DEBUG 1
+
+#include "common.h"
+#include "jtag.h"
+#include "spiflash.h"
+#include "fpga.h"
+#include "ota.h"
+#include "spz.h"
+#include "httpd.h"
+#include "fw.h"
+
+#include <unzipLIB.h>
+#include <zlib.h>
+
+/* Needed for struct inflate_state, due to unziplib hacks */
+#include <zutil.h>
+#include <inftrees.h>
+#include <inflate.h>
+#ifndef local
+# define local static
+#endif
+
+#define BUFFER_SIZE		SPIFLASH_SECTOR_SIZE
+
+/* Normally provided by zlib, but UnzipLIB breaks it */
+static void *z_calloc(void *opaque, unsigned int items, unsigned int size)
+{
+    (void)opaque;
+    return calloc(items, size);
+}
+
+static void z_free(void *opaque, void *ptr)
+{
+    (void)opaque;
+    free(ptr);
+}
+
+int spz_read_data(spz_stream *spz, void *buf, size_t len)
+{
+    uint8_t *p = buf;
+
+    while (len) {
+	unsigned int avail = spz->zs.next_out - spz->optr;
+
+	if (spz->err)
+	    break;
+
+	if (avail) {
+	    if (avail > len)
+		avail = len;
+
+	    memcpy(p, spz->optr, avail);
+	    p += avail;
+	    spz->optr += avail;
+	    len -= avail;
+	} else {
+	    spz->optr = spz->zs.next_out = spz->obuf;
+	    spz->zs.avail_out = BUFFER_SIZE;
+
+	    while (spz->zs.avail_out) {
+		if (!spz->zs.avail_in && !spz->eoi) {
+		    int rlen;
+
+		    spz->zs.next_in = spz->ibuf;
+		    rlen = spz->read_data(spz->token, spz->ibuf, BUFFER_SIZE);
+		    if (rlen < 0) {
+			if (!spz->err)
+			    spz->err = rlen;
+			rlen = 0;
+		    }
+		    spz->eoi = !rlen;
+		    spz->zs.avail_in = rlen;
+		}
+
+		int rv = inflate(&spz->zs, Z_SYNC_FLUSH);
+		if (rv == Z_OK || (rv == Z_BUF_ERROR && !spz->eoi))
+		    continue;
+
+		spz->eoi = true;
+		if (rv != Z_STREAM_END && !spz->err)
+		    spz->err = rv;
+		break;
+	    }
+	}
+    }
+    return p - (uint8_t *)buf;
+}
+
+/*
+ * spz needs to be initialized to zero except the read_data and cookie
+ * fields.
+ */
+static int fwupdate_data_init(spz_stream *spz)
+{
+    spz->zs.zalloc = z_calloc;
+    spz->zs.zfree  = z_free;
+    spz->zs.opaque = spz;	/* Might be useful at some point */
+
+    /* This is necessary due to unziplib damage */
+    spz->zs.state = calloc(1, sizeof(struct inflate_state));
+
+    for (int i = 0; i < SPZ_NBUF; i++) {
+	spz->bufs[i] = malloc(BUFFER_SIZE);
+	if (!spz->bufs[i])
+	    goto err;
+    }
+
+    /* gzip, max window size */
+    int rv = inflateInit2(&spz->zs, 16 + 15);
+    if (rv != Z_OK && rv != Z_STREAM_END) {
+	spz->err = rv;
+	goto err;
+    }
+    spz->cleanup = true;
+
+err:
+    return spz->err;
+}
+
+static int fwupdate_data_cleanup(spz_stream *spz)
+{
+    int err = 0;
+
+    if (!spz)
+	return 0;
+
+    err = spz->err;
+
+    if (spz->cleanup)
+	inflateEnd(&spz->zs);
+
+    /* Don't reload the FPGA on error; it wedges the JTAG bus */
+    if (spz->fpga_updated && !err)
+	fpga_reset();
+
+    for (int i = 0; i < SPZ_NBUF; i++) {
+	if (spz->bufs[i])
+	    free(spz->bufs[i]);
+    }
+    return err;
+}
+
+/*
+ * Blash a full chunk of data as a JTAG SHIFT_DR transaction
+ */
+int jtag_shift_spz(spz_stream *spz, enum jtag_io_flags flags)
+{
+    unsigned int data_left = spz->header.len;
+    int err = 0;
+
+    if (!data_left)
+	return 0;
+
+    while (data_left) {
+	unsigned int bytes = data_left;
+	int rv;
+
+	if (bytes > BUFFER_SIZE)
+	    bytes = BUFFER_SIZE;
+
+	rv = spz_read_data(spz, spz->dbuf, bytes);
+	if (rv < 1) {
+	    err = Z_DATA_ERROR;
+	    break;
+	}
+
+	data_left -= rv;
+	jtag_io(rv << 3, data_left ? 0 : flags, spz->dbuf, NULL);
+    }
+
+    return err;
+}
+
+static void *fwupdate_read_chunk_str(spz_stream *spz)
+{
+    int rv;
+
+    if (spz->header.len >= BUFFER_SIZE) {
+	spz->err = Z_DATA_ERROR;
+	return NULL;
+    }
+
+    rv = spz_read_data(spz, spz->dbuf, spz->header.len);
+    if (spz->err) {
+	return NULL;
+    }
+    if (rv != (int)spz->header.len) {
+	spz->err = Z_DATA_ERROR;
+	return NULL;
+    }
+    spz->dbuf[spz->header.len] = '\0';
+    return spz->dbuf;
+}
+
+/* Skip a data chunk */
+static int fwupdate_skip_chunk(spz_stream *spz)
+{
+    unsigned int skip = spz->header.len;
+
+    while (skip) {
+	unsigned int block = skip;
+	if (block > BUFFER_SIZE)
+	    block = BUFFER_SIZE;
+
+	int rv = spz_read_data(spz, spz->dbuf, block);
+	if (spz->err)
+	    return spz->err;
+	if (rv != (int)block) {
+	    return spz->err = Z_DATA_ERROR;
+	}
+	skip -= block;
+    }
+
+    return 0;
+}
+
+/* Process a data chunk; return a nonzero value if done */
+static int fwupdate_process_chunk(spz_stream *spz)
+{
+    int rv;
+    char *str;
+
+    rv = spz_read_data(spz, &spz->header, sizeof spz->header);
+    if (spz->err)
+	return spz->err;
+    else if (!rv)
+	return Z_STREAM_END;
+    else if (rv != sizeof spz->header)
+	return spz->err = Z_STREAM_ERROR;
+
+    if (spz->header.magic != FW_MAGIC) {
+	MSG("bad chunk header magic 0x%08x\n", spz->header.magic);
+	return spz->err = Z_DATA_ERROR;
+    }
+
+    switch (spz->header.type) {
+    case FDT_END:
+	return Z_STREAM_END;	/* End of data - not an error */
+    case FDT_DATA:
+	MSG("updating FPGA flash\n");
+	return spiflash_write_spz(spz);
+    case FDT_TARGET:
+	str = fwupdate_read_chunk_str(spz);
+#if 0
+	if (!str || strcmp(str, spz->flash->target)) {
+	    MSG("this firmware file targets \"%s\", need \"%s\"\n",
+		str, spz->flash->target);
+	    return spz->err = Z_DATA_ERROR;
+	}
+#else
+	MSG("firmware target: \"%s\"\n", str);
+#endif
+	return Z_OK;
+    case FDT_NOTE:
+	str = fwupdate_read_chunk_str(spz);
+	MSG("%s\n", str);
+	return Z_OK;
+    case FDT_ESP_OTA:
+	MSG("updating ESP32... ");
+	spz->esp_updated = true;
+	rv = esp_update((read_func_t)spz_read_data, (token_t)spz,
+			spz->header.len);
+	CMSG("done.\n");
+	return rv;
+    case FDT_FPGA_INIT:
+	MSG("initializing FPGA for flash programming... ");
+	spz->fpga_updated = true;
+	rv = fpga_program_spz(spz);
+	CMSG("done\n");
+	return rv;
+    default:
+	if (spz->header.flags & FDF_OPTIONAL) {
+	    return fwupdate_skip_chunk(spz);
+	} else {
+	    MSG("unknown chunk type: %u\n", spz->header.type);
+	    return spz->err = Z_DATA_ERROR;
+	}
+    }
+}
+
+int firmware_update(read_func_t read_data, token_t token)
+{
+    struct spz_stream *spz = calloc(1, sizeof *spz);
+    int err = 0;
+
+    if (!spz) {
+	return Z_MEM_ERROR;
+    }
+
+    spz->read_data = read_data;
+    spz->token = token;
+
+    err = fwupdate_data_init(spz);
+    if (err)
+	goto fail;
+
+    while (!fwupdate_process_chunk(spz)) {
+	/* Process data chunks until end */
+    }
+
+    err = fwupdate_data_cleanup(spz);
+    if (err)
+	MSG("failed (err %d)\n", err);
+
+fail:
+    free(spz);
+    return err;
+}

+ 121 - 0
esp32/max80/httpd.c

@@ -0,0 +1,121 @@
+#define MODULE "httpd"
+
+#include "common.h"
+#include "fw.h"
+#include "httpd.h"
+
+static httpd_handle_t httpd;
+
+esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *req)
+{
+    char *response;
+    esp_err_t err;
+    int rv, len;
+
+    printf("[POST] len = %zu uri = \"%s\"\n",
+	   req->content_len, req->uri);
+
+    if (!req->content_len) {
+	return httpd_resp_send_err(req, 411, NULL);
+    }
+
+    rv = firmware_update((read_func_t)httpd_req_recv, (token_t)req);
+
+    if (rv == FWUPDATE_ERR_IN_PROGRESS)
+	return httpd_resp_send_err(req, 409, "Firmware update already in progress");
+    else if (rv)
+	return httpd_resp_send_err(req, 500, "Firmware update failed");
+
+    len = asprintf(&response,
+		   "<!DOCTYPE html>\r\n"
+		   "<html>\r\n"
+		   "<head>\r\n"
+		   "<title>Firmware update completed</title>\r\n"
+		   "</head>\r\n"
+		   "<body>\r\n"
+		   "<h1>Firmware update completed</h1>\r\n"
+		   "<p>Rebooting in %u seconds</p>\r\n"
+		   "</body>\r\n"
+		   "</html>\r\n",
+		   reboot_delayed());
+
+    /* 200 and text/html are the response defaults, no need to set */
+    err = httpd_resp_send(req, response, len);
+    free(response);
+
+    return err;
+}
+
+static esp_err_t httpd_get_handler(httpd_req_t *req)
+{
+    char *response;
+    int len;
+    esp_err_t err;
+
+    len = asprintf(&response,
+		   "<!DOCTYPE html>\n"
+		   "<html>\n"
+		   "<head>\n"
+		   "<title>Hello, World!</title>\n"
+		   "</head>\n"
+		   "<body>\n"
+		   "<p>Hello, World!</p>\n"
+		   "<p>GET uri = <tt>%s</tt></p>\n"
+		   "</pre>\n"
+		   "</body>\n"
+		   "</html>\n",
+		   req->uri);
+
+    /* 200 and text/html are the response defaults, no need to set */
+    err = httpd_resp_send(req, response, len);
+    free(response);
+
+    return err;
+}
+
+static const httpd_uri_t uri_get = {
+    .uri      = "/",
+    .method   = HTTP_GET,
+    .handler  = httpd_get_handler,
+    .user_ctx = NULL
+};
+
+static const httpd_uri_t uri_firmware_upgrade = {
+    .uri      = "/firmware-upgrade",
+    .method   = HTTP_POST,
+    .handler  = httpd_firmware_upgrade_handler,
+    .user_ctx = NULL
+};
+
+void my_httpd_stop(void)
+{
+    if (httpd) {
+	esp_unregister_shutdown_handler(my_httpd_stop);
+	httpd_stop(httpd);
+	httpd = NULL;
+    }
+}
+
+void my_httpd_start(void)
+{
+    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+    httpd_handle_t server;
+
+    if (httpd)
+	return;
+
+    config.task_priority = 2;
+
+    printf("[HTTP] Default stack size: %zu\n", config.stack_size);
+    config.stack_size <<= 2;
+    printf("[HTTP] Requesting stack size: %zu\n", config.stack_size);
+
+    if (httpd_start(&server, &config) != ESP_OK)
+      return;
+
+    httpd = server;
+    httpd_register_uri_handler(httpd, &uri_get);
+    httpd_register_uri_handler(httpd, &uri_firmware_upgrade);
+    esp_register_shutdown_handler(my_httpd_stop);
+    printf("[HTTP] httpd started\n");
+}

+ 14 - 0
esp32/max80/httpd.h

@@ -0,0 +1,14 @@
+#ifndef _HTTPD_H
+
+#include "common.h"
+
+#include <esp_http_server.h>
+
+extern_c void my_httpd_start(void);
+extern_c void my_httpd_stop(void);
+
+extern_c esp_err_t httpd_firmware_upgrade_handler(httpd_req_t *);
+
+typedef int (*read_func_t)(void *pvt, void *buf, size_t buflen);
+
+#endif

+ 83 - 0
esp32/max80/jtag.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#ifndef MAKE_TAPROUTE_C
+# include "common.h"
+#else
+# define extern_c extern
+#endif
+
+enum TAP_STATE
+{
+    TAP_TEST_LOGIC_RESET = 0,
+    TAP_RUN_TEST_IDLE    = 1,
+    TAP_SELECT_DR_SCAN   = 2,
+    TAP_CAPTURE_DR       = 3,
+    TAP_SHIFT_DR         = 4,
+    TAP_EXIT1_DR         = 5,
+    TAP_PAUSE_DR         = 6,
+    TAP_EXIT2_DR         = 7,
+    TAP_UPDATE_DR        = 8,
+    TAP_SELECT_IR_SCAN   = 9,
+    TAP_CAPTURE_IR      = 10,
+    TAP_SHIFT_IR        = 11,
+    TAP_EXIT1_IR        = 12,
+    TAP_PAUSE_IR        = 13,
+    TAP_EXIT2_IR        = 14,
+    TAP_UPDATE_IR       = 15
+};
+
+/*
+ * Short (3-letter) name of states (tap.c)
+ */
+extern_c const char tap_state_names[16][4];
+
+/*
+ * Where to we end up if we do a transition [state][tms]? (tap.c)
+ */
+extern_c const uint8_t tap_state_next[16][2];
+
+/*
+ * Bitmap to get to state [to]: use TMS in bit #from
+ * (repeat until from == to). (taproute.c)
+ */
+extern_c const uint16_t tap_state_route[16];
+
+#ifndef MAKE_TAPROUTE_C
+
+enum jtag_io_flags {
+    JIO_TDI       =  1,	/* TDI high if tdi == NULL */
+    JIO_TMS       =  2,	/* TMS high on last clock (go to EXIT1) */
+    JIO_CS        =  4	/* Raise TMS *after* the last clock (used for SPI) */
+};
+
+struct jtag_config {
+    unsigned long hz;		/* JTAG frequency */
+    uint8_t pin_tdi;		/* TDI/MOSI */
+    uint8_t pin_tdo;		/* TDO/MISO */
+    uint8_t pin_tms;		/* TMS/CS# */
+    uint8_t pin_tck;		/* TCK/SCLK */
+    bool be;			/* Bigendian (for SPI) */
+};
+
+typedef struct spz_stream spz_stream;
+
+/*
+ * Low-level TAP hardware operation interface
+ */
+extern_c int jtag_init(void);
+extern_c int jtag_enable(const struct jtag_config *);
+extern_c int jtag_disable(const struct jtag_config *);
+extern_c int jtag_goto_state(enum TAP_STATE state);
+extern_c int jtag_set_tms(bool tms);
+extern_c void jtag_delay(unsigned int us);
+extern_c uint32_t jtag_pulse(unsigned int bits, uint32_t tdi, uint32_t tms);
+extern_c int jtag_io(unsigned int bits, enum jtag_io_flags flags,
+		     const void *tdi, void *tdo);
+extern_c int jtag_shift_spz(spz_stream *spz, enum jtag_io_flags flags);
+
+extern_c int tap_goto_state(enum TAP_STATE state);
+extern_c int tap_reset(void);
+extern_c void tap_set_ir(uint32_t ir, unsigned int bits);
+extern_c void tap_run_test_idle(unsigned int clocks);
+
+#endif	/* MAKE_TAPROUTE_C */

+ 754 - 0
esp32/max80/jtag_esp32.c

@@ -0,0 +1,754 @@
+/*
+ * Simple JTAG interface using ESP32S2 SPI3
+ *
+ * This implements both JTAG and plain SPI functionality. It uses
+ * programmed I/O (CPU control) using notifications from an interrupt
+ * handler to wake up the active thread; fractional words and
+ * TMS-changing transactions are bit banged. This is not particularly
+ * fast (especially since this code does not currently do double
+ * buffering), but it allows for arbitrary bit shifts and works around
+ * some bugs in ESP32/ESP32-S2 silicon, possibly other versions as well.
+ *
+ * For SPI functionality, the TMS pin represents CS#.
+ *
+ * Note: this uses direct register access for some things that are
+ * normally abstracted in Arduino and ESP-IDF; this greatly reduces
+ * overhead in those cases.
+ */
+
+#define MODULE "jtag-esp32"
+#define DEBUG 0
+
+#include "common.h"
+#include "jtag.h"
+
+#include <esp_intr_alloc.h>
+#include <esp32-hal-spi.h>
+#include <driver/spi_common.h>
+#include <driver/spi_common_internal.h> /* spicommon_*() */
+#include <soc/soc.h>
+#include <soc/spi_struct.h>
+#include <soc/spi_reg.h>
+#include <soc/periph_defs.h>
+#include <soc/gpio_struct.h>
+#include <soc/gpio_sig_map.h>
+
+#define JTAG_SPI_HOST HSPI		/* == SPI3 */
+
+typedef struct spi_struct_t {
+    volatile spi_dev_t *dev;
+    /* Other fields are HAL-specific */
+} spi_t;
+
+/* Return a mask of the <bits> least signficant bits */
+static inline uint32_t LMASK(unsigned int bits)
+{
+    return (UINT32_C(1) << bits) - 1;
+}
+
+static inline void set_gpio_mask(uint32_t mask, bool val)
+{
+    volatile uint32_t *reg = val ? &GPIO.out_w1ts : &GPIO.out_w1tc;
+    *reg = mask;
+}
+
+static inline void set_gpio(unsigned int pin, bool val)
+{
+    set_gpio_mask(1U << pin, val);
+}
+
+static inline uint32_t get_gpio_mask(uint32_t mask)
+{
+    return GPIO.in & mask;
+}
+
+static inline unsigned int get_gpio(unsigned int pin)
+{
+    if (__builtin_constant_p(pin)) {
+	return (GPIO.in >> pin) & 1; /* More efficient on Xtensa */
+    } else {
+	return !!get_gpio_mask(1U << pin);
+    }
+}
+
+static struct jtag_state {
+    spi_t *spi;
+    intr_handle_t intr_handle;
+    TaskHandle_t task;
+
+    struct jtag_config config;
+    uint32_t pin_tdi_mask;
+    uint32_t pin_tdo_mask;
+    uint32_t pin_tms_mask;
+    uint32_t pin_tck_mask;
+
+    unsigned int hz;		/* Actual frequency */
+    unsigned int clkdiv;
+    unsigned int bitbang_loops;	/* Delay loop count in bitbang driver */
+    uint64_t us_to_clocks_multiplier;
+    unsigned int spi_timeout_ticks;
+
+    unsigned int bits;
+    unsigned int tdo_bits_read;
+    const uint32_t *tdi;
+    uint32_t *tdo;
+    uint8_t tdi_bitoffs;
+    uint8_t tdo_bitoffs;
+
+    enum TAP_STATE tap_state;
+    bool configured;
+} jtag;
+
+#define NOTIFY_INDEX 0		/* Seems to be the only available... */
+
+static void ARDUINO_ISR_ATTR jtag_isr(void *);
+
+const unsigned int spi_mosi_src = SPI3_D_OUT_IDX;
+const unsigned int spi_sclk_src = SPI3_CLK_OUT_MUX_IDX;
+const unsigned int gpio_src     = SIG_GPIO_OUT_IDX;
+
+/*
+ * Switch the output lines between SPI (true) or GPIO (false).
+ */
+static inline void jtag_config_tdi(bool attach)
+{
+    GPIO.func_out_sel_cfg[jtag.config.pin_tdi].val =
+	attach ? spi_mosi_src : gpio_src;
+}
+static inline void jtag_config_tck(bool attach)
+{
+    GPIO.func_out_sel_cfg[jtag.config.pin_tck].val
+	= attach ? spi_sclk_src : gpio_src;
+}
+
+/*
+ * If hz != 0 then set to the closest supported frequency and return
+ * the true frequency.
+ *
+ * This precalculates a bunch of parameters dependent on the JTAG
+ * frequency.
+ */
+#define APB_CLK_PER_READ 2	/* Pure guess, but probably conservative */
+
+static unsigned int jtag_set_hz(unsigned int hz)
+{
+    /*
+     * If we waited this long poll to see if we lost an interrupt.
+     * spi_irq_timeout is the number of times the *maximum* chunk
+     * transmission time.
+     */
+    const unsigned int spi_irq_timeout = 4;
+
+    if (hz) {
+	jtag.clkdiv = spiFrequencyToClockDiv(hz);
+	jtag.hz = spiClockDivToFrequency(jtag.clkdiv);
+	jtag.bitbang_loops =
+	    (APB_CLK_FREQ)/(APB_CLK_PER_READ*2*jtag.hz);
+	jtag.us_to_clocks_multiplier = ((uint64_t)jtag.hz << 32)/1000000;
+	jtag.spi_timeout_ticks =
+	    (spi_irq_timeout * (sizeof jtag.spi->dev->data_buf << 3)
+	     * configTICK_RATE_HZ) / (jtag.hz+1) + 1;
+
+	MSG("SPI: JTAG frequency set to %u Hz\n", jtag.hz);
+    }
+    return jtag.hz;
+}
+
+/* Set pins to the default output values and let them stabilize */
+static void jtag_idle_pins(void)
+{
+    set_gpio_mask(jtag.pin_tck_mask, 0);
+    set_gpio_mask(jtag.pin_tdi_mask|jtag.pin_tms_mask, 1);
+
+    jtag_config_tck(false);
+    jtag_config_tdi(false);
+
+    jtag_delay(1000);		/* 1 ms */
+}
+
+int jtag_disable(const struct jtag_config *config)
+{
+    /* Tristate all JTAG pins and stop SPI engine */
+    const char *act = "initialized";
+
+    if (jtag.spi) {
+	jtag.spi->dev->slave.val = 0; /* Disable all interrupt sources */
+
+	jtag_idle_pins();
+	spiDetachMISO(jtag.spi, jtag.config.pin_tdo);
+
+#if 0
+	spiEndTransaction(jtag.spi);
+#endif
+	spiStopBus(jtag.spi);
+	if (jtag.intr_handle)
+	    esp_intr_free(jtag.intr_handle);
+
+	jtag.spi = NULL;
+	act = "disabled";
+    }
+
+    if (config) {
+	jtag.config = *config;
+	jtag.configured = true;
+    }
+
+    /* Tristate all signal pins */
+    if (jtag.configured) {
+	pinMode(jtag.config.pin_tdi, INPUT);
+	pinMode(jtag.config.pin_tdo, INPUT);
+	pinMode(jtag.config.pin_tms, INPUT);
+	pinMode(jtag.config.pin_tck, INPUT);
+    }
+
+    printf("[JTAG] JTAG %s\n", act);
+    return 0;
+}
+
+#if DEBUG > 2
+static void dump_spi_regs(void)
+{
+    const volatile uint32_t *spiregs =
+	(const volatile uint32_t *)jtag.spi->dev;
+
+    for (unsigned int i = 0; i <= 0x100; i += 4) {
+	uint32_t v = *spiregs++;
+	MSG("SPI: reg 0x%03x = 0x%08x %u\n", i, v, v);
+    }
+}
+#else
+#define dump_spi_regs() ((void)0)
+#endif
+
+static inline unsigned int jtag_us_to_clocks(unsigned int us)
+{
+    return (us * jtag.us_to_clocks_multiplier) >> 32;
+}
+
+int jtag_enable(const struct jtag_config *config)
+{
+    if (jtag.spi)
+	return -1;
+
+    jtag.task = xTaskGetCurrentTaskHandle();
+
+    if (config) {
+	jtag.config = *config;
+	jtag.configured = true;
+    }
+
+    if (!jtag.configured)
+	return -1;
+
+    jtag.pin_tdi_mask = 1U << jtag.config.pin_tdi;
+    jtag.pin_tdo_mask = 1U << jtag.config.pin_tdo;
+    jtag.pin_tms_mask = 1U << jtag.config.pin_tms;
+    jtag.pin_tck_mask = 1U << jtag.config.pin_tck;
+
+    pinMode(jtag.config.pin_tdo, INPUT);
+    pinMode(jtag.config.pin_tdi, OUTPUT);
+    pinMode(jtag.config.pin_tms, OUTPUT);
+    pinMode(jtag.config.pin_tck, OUTPUT);
+
+    jtag_set_hz(jtag.config.hz);
+
+    printf("[JTAG] starting bus, clkdiv = 0x%08x (%u Hz)\n",
+	   jtag.clkdiv, jtag.hz);
+    jtag.spi = spiStartBus(JTAG_SPI_HOST, jtag.clkdiv, SPI_MODE0, SPI_LSBFIRST);
+    if (!jtag.spi) {
+	printf("[JTAG] failed to start bus\n");
+	jtag_disable(NULL);
+	return -1;
+    }
+
+    /* spiStartBus resets the bus and sets the frequency */
+
+    spi_dev_t * const hw = jtag.spi->dev;
+
+#if 0
+    MSG("SPI: acquiring lock\n");
+    spiSimpleTransaction(jtag.spi); /* Lock bus */
+#endif
+
+    spiAttachMISO(jtag.spi, jtag.config.pin_tdo);
+
+    jtag_idle_pins();
+
+    /* Initialize all buffers to a canary */
+    for (unsigned int i = 0; i < 18; i++)
+	hw->data_buf[i] = (i+1) * 0x01010101;
+
+    /* Set bit order. Byte order is always littleendian. */
+    uint32_t spi_ctrl_reg = 0;
+    if (!jtag.config.be)
+	spi_ctrl_reg |= SPI_RD_BIT_ORDER_M | SPI_WR_BIT_ORDER_M;
+
+    hw->ctrl.val = spi_ctrl_reg;
+
+    /* Enable buffers 16 & 17 */
+    hw->ctrl1.val = SPI_W16_17_WR_ENA_M;
+
+    /*
+     * Hack: set 8 cycles of CS HOLD at the end of each SPI transaction;
+     * this appears needed to flush out any fractional TDO bits.
+     */
+    /*
+     * Register is called "slave" but interrupt control applies
+     * to master mode too:
+     * Disable all SPI interrupts sources
+     */
+    hw->slave.val = 0;
+
+    /*
+     * Allocate and enable the interrupt.
+     * We want this to be a low priority interrupt (level 1).
+     */
+    const int spi_irq = spicommon_irqsource_for_host(JTAG_SPI_HOST);
+    esp_intr_alloc(spi_irq, ESP_INTR_FLAG_LEVEL1,
+		   jtag_isr, &jtag, &jtag.intr_handle);
+
+    /*
+     * enable trans_done IRQ
+     */
+    hw->slave.val = SPI_INT_TRANS_DONE_EN_M;
+
+#if DEBUG > 1
+    dump_spi_regs();
+#endif
+
+    jtag.tap_state = TAP_RUN_TEST_IDLE; /* Pure guess */
+
+    printf("[JTAG] JTAG enabled\n");
+    return 0;
+}
+
+int jtag_init(void)
+{
+    jtag.configured = false;
+    return 0;
+}
+
+/*
+ * The Arduino HAL API uses busy wait, so here is our own routine:
+ * it can transfer up to 72 bytes at a time at arbitrary bit offsets
+ * but requires that the actual state pointers are 32-bit aligned;
+ * this is handled in jtag_io().
+ *
+ * In bigendian mode, this treats the input as a stream of *bytes*; a
+ * bit offset or length that is not a multiple of 8 will result in
+ * garbage data. The hardware always uses litteendian byte ordering,
+ * but the bit ordering is set as appropriate.
+ *
+ * This is event driven, with the ISR simply unblocking the JTAG thread
+ * via notifications. This isn't particularly fast, but avoids starving
+ * higher-priority processes; it is assumed that the JTAG/SPI transactions
+ * are not particularly performance critical and, being selectively
+ * clocked, can handle significant pauses.
+ *
+ * Using DMA and/or double-buffering would reduce CPU overhead and/or
+ * improve performance, but this is very simple especially with
+ * arbitrary bit offsets and lengths.
+ */
+static int jtag_do_chunk(void)
+{
+    spi_dev_t * const hw = jtag.spi->dev;
+    unsigned int bits;
+    unsigned int tdo_bits_read;
+    unsigned int words;
+    unsigned int i;
+    const uint32_t *tdi = jtag.tdi;
+    uint32_t *tdo = jtag.tdo;
+
+    if (hw->cmd.usr)
+	return -1;		/* Missed interrupt? */
+    xTaskNotifyStateClearIndexed(jtag.task, NOTIFY_INDEX);
+
+#if DEBUG > 1
+    MSG("SPI: in jtag_do_chunk\n");
+#endif
+
+    tdo_bits_read = jtag.tdo_bits_read;
+
+    if (tdo_bits_read) {
+	/* Copy output data from SPI buffers */
+	unsigned int tdo_offs = jtag.tdo_bitoffs;
+
+#if DEBUG > 1
+	MSG("SPI: *** After\n");
+	dump_spi_regs();
+#endif
+	MSG("TDO: %u bits @ %p[%u]", tdo_bits_read, tdo, tdo_offs);
+
+	const unsigned int fullwords = (tdo_bits_read + tdo_offs) >> 5;
+	const unsigned int trailing = (tdo_bits_read + tdo_offs) & 31;
+	const volatile uint32_t *p = hw->data_buf;
+	uint32_t o;
+
+	if (!tdo_offs) {
+	    for (i = 0; i < fullwords; i++)
+		*tdo++ = *p++;
+	    o = 0;
+	} else {
+	    o = *tdo & LMASK(tdo_offs);
+	    for (i = 0; i < fullwords; i++) {
+		uint32_t w = *p++;
+		*tdo++ = (w << tdo_offs) | o;
+		o = w >> tdo_offs;
+	    }
+	}
+
+	/*
+	 * o here has tdo_offs == 0..31 bits of valid data,
+	 * but we might need more than that.
+	 */
+	if (trailing > tdo_offs)
+	    o |= *p << tdo_offs;
+
+	const unsigned int trail_mask = LMASK(trailing);
+	const unsigned int trail_omask = ~LMASK((trailing+7) & ~7);
+	*tdo = (*tdo & trail_omask) | (o & trail_mask);
+
+	MSG(" -> %p[%u]\n", tdo, trailing);
+
+	jtag.tdo_bits_read = 0;
+	jtag.tdo = tdo;
+	jtag.tdo_bitoffs = trailing;
+    }
+
+    /* Now the previous chunk, if any, is complete */
+    bits = jtag.bits;
+    if (!bits)
+	return 0;		/* Done */
+
+    /*
+     * Clamp the SPI transaction size to the size of the buffers;
+     * for a totally dummy transaction just let the hardware run
+     * until it's done.
+     */
+    const unsigned int max_bits = (sizeof hw->data_buf) << 3;
+    if (tdo) {
+	if (bits > max_bits)
+	    bits = max_bits;
+    }
+
+    if (tdi) {
+	/* Copy input data to SPI buffers */
+	unsigned int tdi_offs = jtag.tdi_bitoffs;
+
+	/* Clamp to max_bits-tdi_offs in the hope of future good alignment */
+	if (bits > max_bits - tdi_offs)
+	    bits = max_bits - tdi_offs;
+
+	const unsigned int words  = (bits+31) >> 5;
+	const uint32_t *p = tdi;
+	volatile uint32_t *q = hw->data_buf;
+
+	MSG("TDI: %u bits @ %p[%u]", bits, tdi, tdi_offs);
+
+	if (!tdi_offs) {
+	    for (i = 0; i < words; i++)
+		*q++ = *p++;
+	} else {
+	    const unsigned int inv_offs = 32 - tdi_offs;
+	    uint32_t o;
+
+	    o = *p++;
+	    for (i = 0; i < words; i++) {
+		uint32_t w = *p++;
+		*q++ = (w << inv_offs) | (o >> tdi_offs);
+		o = w;
+	    }
+	}
+
+	tdi     += (bits + tdi_offs) >> 5;
+	tdi_offs = (bits + tdi_offs) & 31;
+
+	CMSG(" -> %p[%u]\n", tdi, tdi_offs);
+
+	jtag.tdi = tdi;
+	jtag.tdi_bitoffs = tdi_offs;
+    }
+
+    /*
+     * No command, address, or dummy phases. Byte order is always
+     * littleendian.
+     */
+    hw->miso_dlen.val = bits-1;
+    hw->mosi_dlen.val = bits-1;	/* Regardless of if there is a tdi or not */
+
+    uint32_t spi_user_reg = SPI_DOUTDIN | SPI_USR_MOSI; /* Per ESP-IDF HAL */
+    if (tdo)
+	spi_user_reg |= SPI_USR_MISO;
+    hw->user.val = spi_user_reg;
+
+#if DEBUG > 1
+    MSG("SPI: *** Before\n");
+    dump_spi_regs();
+#endif
+    jtag.bits -= bits;
+    jtag.tdo_bits_read = tdo ? bits : 0;
+
+    if (jtag.tdo_bits_read) {
+	MSG("SPI: reading %u bits, %u left\n",
+	    jtag.tdo_bits_read, jtag.bits);
+    }
+
+    hw->cmd.val = SPI_USR_M; /* Start! */
+    return bits;
+}
+
+static void ARDUINO_ISR_ATTR jtag_isr(void *jtag_p)
+{
+    struct jtag_state * const jtag = (struct jtag_state *)jtag_p;
+    spi_dev_t * const hw = jtag->spi->dev;
+
+    uint32_t spi_slave_reg = hw->slave.val;
+    if (!(spi_slave_reg & SPI_TRANS_DONE_M))
+	return;			/* Spurious/shared interrupt? */
+
+    hw->slave.val = spi_slave_reg & ~SPI_TRANS_DONE_M;
+
+    BaseType_t do_wakeup = pdFALSE;
+    xTaskNotifyIndexedFromISR(jtag->task, NOTIFY_INDEX, 1,
+			      eSetBits, &do_wakeup);
+    if (do_wakeup)
+	portYIELD_FROM_ISR(do_wakeup);
+}
+
+static inline bool spi_running(void)
+{
+    return jtag.spi->dev->cmd.usr;
+}
+
+#define APB_CLK_PER_READ 2	/* Plain guess */
+
+/*
+ * Set the value of the TMS/CS# line without any clocks.
+ */
+int jtag_set_tms(bool tms)
+{
+    set_gpio_mask(jtag.pin_tms_mask, tms);
+    return 0;
+}
+
+/*
+ * Bitbang up to 32 bits with TDI and TMS support; returns TDO.
+ *
+ * In bigendian mode, the whole value is treated as bigendian, including
+ * byte order, but is still expected to be right-justified if bits < 32;
+ * e.g. if the expected output is the 16-bit value 0x1234
+ * (transmitted as 0001001000110100), tdi should be 0x1234, not
+ * 0x12340000 or 0x3412.
+ */
+static inline void jtag_pulse_sleep(void)
+{
+    unsigned int loops = jtag.bitbang_loops;
+    while (loops--)
+	(void)GPIO.in;
+}
+
+uint32_t jtag_pulse(unsigned int bits, uint32_t tdi, uint32_t tms)
+{
+    unsigned int i;
+    uint32_t gpio_in;
+    uint32_t tdo = 0;
+    uint32_t bit_val;
+
+    MSG("SPI: bitbang %u bits TDI 0x%x TMS 0x%x", bits, tdi, tms);
+
+    jtag_config_tck(false);
+    jtag_config_tdi(false);
+
+    /* PIN_TCK is always 0 between transactions */
+    if (!jtag.config.be) {
+	/* Littleendian */
+
+	bit_val = 1;
+	while (bits--) {
+	    set_gpio_mask(jtag.pin_tms_mask, tms & 1); tms >>= 1;
+	    set_gpio_mask(jtag.pin_tdi_mask, tdi & 1); tdi >>= 1;
+	    jtag_pulse_sleep();
+	    set_gpio_mask(jtag.pin_tck_mask, 1);
+	    if (get_gpio_mask(jtag.pin_tdo_mask))
+		tdo |= bit_val;
+	    bit_val <<= 1;
+	    jtag_pulse_sleep();
+	    set_gpio_mask(jtag.pin_tck_mask, 0);
+	}
+    } else {
+	/* Bigendian */
+
+	bit_val = 1U << (bits-1);
+	while (bits--) {
+	    set_gpio_mask(jtag.pin_tms_mask, tms & bit_val);
+	    set_gpio_mask(jtag.pin_tdi_mask, tdi & bit_val);
+	    bit_val >>= 1;
+	    jtag_pulse_sleep();
+	    set_gpio_mask(jtag.pin_tck_mask, 1);
+	    tdo = (tdo << 1) | !!get_gpio_mask(jtag.pin_tdo_mask);
+	    jtag_pulse_sleep();
+	    set_gpio_mask(jtag.pin_tck_mask, 0);
+	}
+    }
+
+    CMSG(" -> TDO 0x%x\n", tdo);
+    return tdo;
+}
+
+static void jtag_go(unsigned int bits)
+{
+    int chunk;
+
+    MSG("SPI: jtag_go bits %u, tms 0, tdi %p[%u], tdo %p[%u]\n",
+	bits, jtag.tdi, jtag.tdi_bitoffs, jtag.tdo, jtag.tdo_bitoffs);
+
+    jtag.bits = bits;
+    jtag.tdo_bits_read = 0;
+
+    while ((chunk = jtag_do_chunk())) {
+	MSG("SPI: started chunk of %u bits\n", chunk);
+	while (spi_running())
+	    xTaskNotifyWaitIndexed(NOTIFY_INDEX, 0, 1, NULL,
+				   jtag.spi_timeout_ticks);
+    }
+}
+
+void jtag_delay(unsigned int us)
+{
+    jtag_config_tck(false);	/* Don't emit a clock signal */
+    jtag_config_tdi(false);
+
+    jtag.tdi = jtag.tdo = NULL;
+    jtag_go(jtag_us_to_clocks(us));
+}
+
+/*
+ * Transmit an arbitrary number of bits. In bigendian mode,
+ * the bytes will be transferred in the order presented, but
+ * with the most significant bit first; if there is a fractional
+ * byte at the end it should be right-justified.
+ */
+int jtag_io(unsigned int bits, enum jtag_io_flags flags,
+	    const void *tdi, void *tdo)
+{
+    unsigned int s_bits;	/* Bits for SPI engine  */
+    unsigned int b_bits;	/* Bits to bitbang */
+
+#if DEBUG > 1
+    MSG("SPI: jtag_io bits %u, tms %u, tdi %p, tdo %p\n",
+	   bits, tms, tdi, tdo);
+#endif
+
+    if (!bits) {
+	jtag_set_tms(!!(flags & (JIO_TMS|JIO_CS)));
+	jtag_pulse_sleep();
+	return 0;
+    }
+
+    unsigned int tms = !!(flags & JIO_TMS);
+
+    /*
+     * Bitbang fractional bytes and TMS cycles; this works around some
+     * bugs in ESP32-S2 and makes the rest of the logic simpler.
+     */
+    s_bits = (bits - tms) & ~7;
+    b_bits = bits - s_bits;
+
+    MSG("jtag_io s_bits = %u, b_bits = %u, tms = %u\n",
+	s_bits, b_bits, tms);
+
+    if (s_bits) {
+	/* Align pointers and set the bit offset accordingly. */
+	jtag.tdi = (const uint32_t *)((size_t)tdi & ~3);
+	jtag.tdo = (uint32_t *)((size_t)tdo & ~3);
+	jtag.tdi_bitoffs = ((size_t)tdi & 3) << 3;
+	jtag.tdo_bitoffs = ((size_t)tdo & 3) << 3;
+
+	jtag_config_tck(true);
+	jtag_config_tdi(!!tdi);
+	jtag_set_tms(0);
+
+	 /* Default TDI value if !tdi */
+	set_gpio_mask(jtag.pin_tdi_mask, !!(flags & JIO_TDI));
+	jtag_go(s_bits);
+	MSG("jtag_io: tdi = %p[%u], tdo = %p[%u]\n",
+	    jtag.tdi, jtag.tdi_bitoffs, jtag.tdo, jtag.tdo_bitoffs);
+    }
+
+    if (b_bits) {
+	unsigned int s_bytes        = s_bits >> 3;
+	const uint8_t * const tdi_p = (const uint8_t *)tdi;
+	uint8_t * const tdo_p       = (uint8_t *)tdo;
+	uint32_t tdi_d, tdo_d;
+
+	tdi_d = tdi ? tdi_p[s_bytes] : -!!(flags & JIO_TDI);
+	tdo_d = jtag_pulse(b_bits, tdi_d, (uint32_t)tms << (b_bits-1));
+	if (tdo)
+	    tdo_p[s_bytes] = tdo_d;
+    }
+
+    jtag.tap_state += tms;	/* SHIFT_DR/IR -> EXIT1_DR/IR */
+
+    if (flags & JIO_CS) {
+	jtag_set_tms(true);
+	jtag_pulse_sleep();
+    }
+
+    return bits;
+}
+
+int tap_goto_state(enum TAP_STATE to_state)
+{
+    enum TAP_STATE current = jtag.tap_state;
+    uint16_t tms_map = tap_state_route[to_state];
+    int bits = 0;
+    uint32_t tms = 0;
+
+    MSG("~~~ moving to TAP state %s %2u from %s %2u, TMS = ",
+	tap_state_names[to_state], to_state,
+	tap_state_names[current], current);
+    if (current == to_state)
+	return 0;
+
+    while (current != to_state) {
+	uint32_t next_tms = (tms_map >> current) & 1;
+	CMSG("%u", next_tms);
+	tms |= next_tms << bits++;
+	enum TAP_STATE next_state = tap_state_next[current][next_tms];
+	current = next_state;
+    }
+    CMSG("\n");
+
+    jtag_pulse(bits, -1, tms);
+    jtag.tap_state = current;
+    return 0;
+}
+
+/* Force the TAP into the TEST_LOGIC_RESET state */
+int tap_reset(void)
+{
+    /* Per spec, only 5 pulses with TMS = 1 are needed */
+    jtag_pulse(5, -1, -1);
+    jtag.tap_state = TAP_TEST_LOGIC_RESET;
+    return 0;
+}
+
+/*
+ * Set the IR register
+ */
+void tap_set_ir(uint32_t ir, unsigned int bits)
+{
+    tap_goto_state(TAP_SHIFT_IR);
+    jtag_pulse(bits, ir, 1U << (bits-1));
+    jtag.tap_state = TAP_EXIT1_IR;
+    tap_goto_state(TAP_UPDATE_IR);
+}
+
+/*
+ * Issue a certain number of clocks in the RUN_TEST state
+ */
+void tap_run_test_idle(unsigned int clocks)
+{
+    tap_goto_state(TAP_RUN_TEST_IDLE);
+    jtag_io(clocks, JIO_TDI, NULL, NULL);
+}

+ 64 - 0
esp32/max80/max80.ino

@@ -0,0 +1,64 @@
+// #define DEBUG
+
+#define BAUD_RATE 115200
+
+#include "common.h"
+
+#include <freertos/task_snapshot.h>
+#include "fpga.h"
+#include "wifi.h"
+#include "storage.h"
+
+void setup() {
+    printf("[START] MAX80 firmware compiled on " __DATE__ " " __TIME__ "\n");
+    InitStorage();
+    SetupWiFi();
+    //fpga_services_start();
+    printf("[RDY]\n");
+}
+
+static inline char task_state(eTaskState state)
+{
+    switch (state) {
+    case eInvalid:
+	return 'X';
+    case eReady:
+    case eRunning:
+	return 'R';
+    case eBlocked:
+	return 'D';
+    case eSuspended:
+	return 'S';
+    case eDeleted:
+	return 'Z';
+    default:
+	return '?';
+    }
+}
+
+static void dump_tasks(void)
+{
+    TaskHandle_t task = NULL;
+
+    while (1) {
+	task = pxTaskGetNext(task);
+	if (!task)
+	    break;
+
+	printf("%-16s %c %2u\n",
+	       pcTaskGetName(task),
+	       task_state(eTaskGetState(task)),
+	       uxTaskPriorityGet(task));
+    }
+}
+
+void loop() {
+    printf("loop task: %s\n", pcTaskGetName(xTaskGetCurrentTaskHandle()));
+    printf("idle task: %s\n", pcTaskGetName(xTaskGetIdleTaskHandle()));
+
+    dump_tasks();
+
+    putchar('\n');
+
+    vTaskDelay(20 * configTICK_RATE_HZ);
+}

BIN
esp32/max80/max80.ino.esp32s2usb.bin


+ 82 - 0
esp32/max80/ota.c

@@ -0,0 +1,82 @@
+#define MODULE "ota"
+#define DEBUG 1
+
+#include "common.h"
+#include "fw.h"
+
+#include <esp_ota_ops.h>
+
+#define BUFFER_SIZE	4096
+
+esp_err_t esp_update(read_func_t read_func, token_t token, size_t size)
+{
+    const esp_partition_t *partition;
+    esp_err_t err = ESP_ERR_INVALID_ARG;
+    esp_ota_handle_t handle;
+    bool ota_started = false;
+    int len;
+    char *buf = NULL;
+
+    buf = malloc(BUFFER_SIZE);
+    if (!buf) {
+	err = ESP_ERR_NO_MEM;
+	goto fail;
+    }
+
+    partition = esp_ota_get_next_update_partition(NULL);
+    if (!partition) {
+	err = ESP_ERR_NOT_FOUND;
+	goto fail;
+    }
+
+    MSG("starting ESP OTA update, total %zu bytes\n", size);
+
+    err = esp_ota_begin(partition, size, &handle);
+    if (err)
+	goto fail;
+
+    ota_started = true;
+
+    size_t left = size;
+    while (left) {
+	size_t chunk = left;
+	if (chunk > BUFFER_SIZE)
+	    chunk = BUFFER_SIZE;
+
+	len = read_func(token, buf, chunk);
+	if (len <= 0)
+	    break;
+
+	left -= len;
+
+	MSG("writing %d bytes, %zu left\n", len, left);
+	err = esp_ota_write(handle, buf, len);
+	if (err)
+	    goto fail;
+    }
+
+    if (left) {
+	MSG("short input data, aborting update\n");
+	err = ESP_ERR_OTA_VALIDATE_FAILED;
+	goto fail;
+    }
+
+    MSG("finalizing flash update\n");
+    err = esp_ota_end(handle);
+    if (err)
+	goto fail;
+
+    free(buf);
+
+    MSG("setting boot partition\n");
+    return esp_ota_set_boot_partition(partition);
+
+fail:
+    MSG("failure: error 0x%x\n", err);
+
+    if (ota_started)
+	esp_ota_abort(handle);
+    if (buf)
+	free(buf);
+    return err;
+}

+ 5 - 0
esp32/max80/ota.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include "common.h"
+
+esp_err_t esp_ota_update(read_func_t read_func, token_t token, size_t size);

+ 28 - 0
esp32/max80/reboot.c

@@ -0,0 +1,28 @@
+#define MODULE "reboot"
+
+#include "common.h"
+
+void reboot_now(void)
+{
+    while (1) {
+	esp_restart();
+    }
+}
+
+
+int reboot_delayed(void)
+{
+    const int reboot_delay = 10; /* seconds */
+    TimerHandle_t timer;
+
+    timer = xTimerCreate("reboot", configTICK_RATE_HZ*reboot_delay,
+			 pdFALSE, NULL, (TimerCallbackFunction_t)reboot_now);
+
+    if (!timer)
+      return 0;
+
+    if (xTimerStart(timer, configTICK_RATE_HZ) != pdPASS)
+	return 0;
+
+    return reboot_delay;
+}

+ 419 - 0
esp32/max80/spiflash.c

@@ -0,0 +1,419 @@
+#define MODULE "spiflash"
+#define DEBUG 1
+
+#include "common.h"
+#include "spiflash.h"
+#include "spz.h"
+#include "fw.h"
+#include "jtag.h"
+
+/*
+ * SPI flash parameters
+ */
+#define JTAG_SPIFLASH_HZ 10000000	/* Max 26 MHz due to ESP32 */
+
+static const struct jtag_config jtag_config_spiflash = {
+    .hz      = JTAG_SPIFLASH_HZ,
+    .pin_tms = 10,		/* CS# */
+    .pin_tdi = 12,		/* MOSI */
+    .pin_tdo = 13,		/* MISO */
+    .pin_tck = 11,		/* SCLK */
+    .be      = true		/* Bit order within bytes */
+};
+
+/*
+ * Set up a command header with an address according to the SPI
+ * addressing mode. Returns a pointer to the first byte past the
+ * address.
+ */
+static void *spiflash_setup_addrcmd(uint32_t addr,
+				    uint8_t cmd24, uint8_t cmd32,
+				    void *cmdbuf)
+{
+    enum spiflash_addr_mode mode = SPIFLASH_ADDR_DYNAMIC;
+    uint8_t *cmd = cmdbuf;
+
+    if (!mode)
+	mode = addr < (1 << 24) ? SPIFLASH_ADDR_24BIT : SPIFLASH_ADDR_32BIT;
+
+    if (mode == SPIFLASH_ADDR_24BIT) {
+	*cmd++ = cmd24;
+    } else {
+	*cmd++ = cmd32;
+	*cmd++ = addr >> 24;
+    }
+    *cmd++ = addr >> 16;
+    *cmd++ = addr >> 8;
+    *cmd++ = addr;
+
+    return cmd;
+}
+
+# define SHOW_COMMAND()					\
+  do {							\
+      MSG("command: ", cmdbuf, cmdlen);			\
+      for (size_t i = 0; i < cmdlen; i++)		\
+	  CMSG(" %02x", ((const uint8_t *)cmdbuf)[i]);	\
+      CMSG("\n");					\
+  } while(0)
+
+static int spiflash_simple_command(uint32_t cmd)
+{
+    jtag_io(8, JIO_CS, &cmd, NULL);
+    return 0;
+}
+
+static int spiflash_plain_command(const void *cmdbuf, size_t cmdlen)
+{
+#if DEBUG > 1
+    MSG("plain: cmdbuf = %p (%zu), databuf = %p (%zu)\n",
+	cmdbuf, cmdlen);
+    SHOW_COMMAND();
+#endif
+
+    jtag_io(cmdlen << 3, JIO_CS, cmdbuf, NULL);
+    return 0;
+}
+
+static int spiflash_output_command(const void *cmdbuf, size_t cmdlen,
+				    const void *databuf, size_t datalen)
+{
+    if (!datalen)
+	return spiflash_plain_command(cmdbuf, cmdlen);
+
+#if DEBUG > 1
+    MSG("output: cmdbuf = %p (%zu), databuf = %p (%zu)\n",
+	cmdbuf, cmdlen, databuf, datalen);
+    SHOW_COMMAND();
+#endif
+
+    jtag_io(cmdlen << 3, 0, cmdbuf, NULL);
+    jtag_io(datalen << 3, JIO_CS, databuf, NULL);
+    return 0;
+}
+
+static int spiflash_input_command(const void *cmdbuf, size_t cmdlen,
+				  void *databuf, size_t datalen)
+{
+    if (!datalen)
+	return spiflash_plain_command(cmdbuf, cmdlen);
+
+#if DEBUG > 1
+    MSG("input: cmdbuf = %p (%zu), databuf = %p (%zu)\n",
+	cmdbuf, cmdlen, databuf, datalen);
+    SHOW_COMMAND();
+#endif
+
+    jtag_io(cmdlen << 3, 0, cmdbuf, NULL);
+    jtag_io(datalen << 3, JIO_CS, NULL, databuf);
+    return 0;
+}
+
+static int spiflash_read_status(uint32_t reg)
+{
+    uint32_t val = 0;
+
+    jtag_io(8, 0, (const uint8_t *)&reg, NULL);
+    jtag_io(8, JIO_CS, NULL, (uint8_t *)&val);
+    return val;
+}
+
+/* This needs a timeout function */
+static int spiflash_wait_status(uint8_t mask, uint8_t val)
+{
+    unsigned int wait_loops = 100000;
+
+#if DEBUG > 1
+    MSG("waiting for status %02x/%02x... ", mask, val);
+#endif
+
+    while (wait_loops--) {
+	uint8_t sr1 = spiflash_read_status(ROM_READ_SR1);
+
+	if ((sr1 & mask) == val) {
+	    CMSG("ok\n");
+	    return 0;
+	}
+
+	yield();
+    }
+    CMSG("timeout\n");
+    return -1;
+}
+
+static int spiflash_read(uint32_t addr, void *buffer, size_t len)
+{
+    uint32_t cmdbuf[2];
+    uint8_t *cmd = (uint8_t *)cmdbuf;
+    const uint8_t cmd24 = ROM_FAST_READ;
+    const uint8_t cmd32 = ROM_FAST_READ_32BIT;
+    const size_t max_read_len = -1;
+    int rv;
+
+    while (len) {
+	size_t clen = len;
+	if (clen > max_read_len)
+	    clen = max_read_len;
+
+	cmd = spiflash_setup_addrcmd(addr, cmd24, cmd32, cmdbuf);
+	*cmd++ = 0;			/* Dummy cycles */
+
+	rv = spiflash_input_command(cmdbuf, cmd - (uint8_t *)cmdbuf,
+				    buffer, clen);
+	if (rv)
+	    return rv;
+
+	addr += clen;
+	buffer = (uint8_t *)buffer + clen;
+	len -= clen;
+    }
+
+    return 0;
+}
+
+static int spiflash_write_enable(void)
+{
+    uint8_t sr1;
+    int rv;
+
+    rv = spiflash_wait_status(1, 0);
+    if (rv)
+	return rv;
+
+    spiflash_simple_command(ROM_WRITE_ENABLE);
+    return spiflash_wait_status(3, 2);
+}
+
+static int spiflash_program_sector(uint32_t addr, const void *buffer)
+{
+    uint32_t cmdbuf[2];
+    uint8_t *cmd = (uint8_t *)cmdbuf;
+    const uint8_t cmd24 = ROM_PAGE_PROGRAM;
+    const uint8_t cmd32 = ROM_PAGE_PROGRAM_32BIT;
+    int rv;
+    int loops = SPIFLASH_SECTOR_SIZE / SPIFLASH_PAGE_SIZE;
+    const char *p = buffer;
+
+    while (loops--) {
+	rv = spiflash_write_enable();
+	if (rv)
+	    return rv;
+
+	cmd = spiflash_setup_addrcmd(addr, cmd24, cmd32, cmdbuf);
+	spiflash_output_command(cmdbuf, cmd - (uint8_t *)cmdbuf,
+				p, SPIFLASH_PAGE_SIZE);
+
+	rv = spiflash_wait_status(3, 0);
+	if (rv)
+	    return rv;
+
+	addr += SPIFLASH_PAGE_SIZE;
+	p += SPIFLASH_PAGE_SIZE;
+    }
+
+    return 0;
+}
+
+static int spiflash_erase_sector(uint32_t addr)
+{
+    uint32_t cmdbuf[2];
+    uint8_t *cmd = (uint8_t *)cmdbuf;
+    const uint8_t cmd24 = ROM_ERASE_4K;
+    const uint8_t cmd32 = ROM_ERASE_4K_32BIT;
+    int rv;
+
+    rv = spiflash_write_enable();
+    if (rv)
+	return rv;
+
+    cmd = spiflash_setup_addrcmd(addr, cmd24, cmd32, cmdbuf);
+    spiflash_plain_command(cmdbuf, cmd - (uint8_t *)cmdbuf);
+    return spiflash_wait_status(3, 0);
+}
+
+/*
+ * from: current flash contents
+ * to:   desired flash contents
+ *
+ * These are assumed to be aligned full block buffers
+ */
+enum flashmem_status {
+    FMS_DONE,			/* All done, no programming needed */
+    FMS_PROGRAM,		/* Can be programmed */
+    FMS_ERASE,			/* Needs erase before programming */
+    FMS_NOTCHECKED		/* Not checked yet */
+};
+static enum flashmem_status
+spiflash_memcmp(const void *from, const void *to, size_t len)
+{
+    const uint32_t *pf = from;
+    const uint32_t *pt = to;
+    const uint32_t *pfend = (const uint32_t *)((const char *)from + len);
+    uint32_t doprog  = 0;
+    uint32_t doerase = 0;
+
+    while (pf < pfend) {
+	uint32_t f = *pf++;
+	uint32_t t = *pt++;
+
+	doprog  +=  !!(f ^ t);	/* Need programming if any data mismatch */
+	doerase += !!(~f & t);	/* Need erasing if any 0 -> 1 */
+    }
+
+    return doerase ? FMS_ERASE : doprog ? FMS_PROGRAM : FMS_DONE;
+}
+
+static int spiflash_write_sector(spz_stream *spz, unsigned int addr)
+{
+    enum flashmem_status status = FMS_NOTCHECKED;
+
+    MSG("flash sector at 0x%06x: ", addr);
+
+    while (1) {
+	enum flashmem_status oldstatus = status;
+
+	status = spiflash_memcmp(spz->vbuf, spz->dbuf, SPIFLASH_SECTOR_SIZE);
+
+	if (status >= oldstatus) {
+	    CMSG("X [%u>%u]", oldstatus, status);
+	    break;
+	} else if (status == FMS_DONE) {
+	    CMSG("V");
+	    break;
+	} else if (status == FMS_ERASE) {
+	    CMSG("E");
+	    if (spiflash_erase_sector(addr))
+		break;
+	} else if (status == FMS_PROGRAM) {
+	    CMSG("P");
+	    if (spiflash_program_sector(addr, spz->dbuf))
+		break;
+	}
+
+	memset(spz->vbuf, 0xdd, SPIFLASH_SECTOR_SIZE);
+	spiflash_read(addr, spz->vbuf, SPIFLASH_SECTOR_SIZE);
+    }
+
+    int rv;
+
+    if (status == FMS_DONE) {
+	CMSG(" OK\n");
+	rv = 0;
+    } else {
+	CMSG(" FAILED\n");
+	rv = (status == FMS_PROGRAM)
+	    ? FWUPDATE_ERR_PROGRAM_FAILED : FWUPDATE_ERR_ERASE_FAILED;
+    }
+
+    if (!spz->err)
+	spz->err = rv;
+
+    return rv;
+}
+
+static int spiflash_read_jedec_id(void)
+{
+    const uint32_t cmd = ROM_JEDEC_ID;
+    uint32_t jid = 0;
+
+    spiflash_input_command((uint8_t *)&cmd, 1, (uint8_t *)&jid, 3);
+
+    MSG("JEDEC ID: vendor %02x type %02x capacity %02x\n",
+	(uint8_t)jid, (uint8_t)(jid >> 8), (uint8_t)(jid >> 16));
+    return 0;
+}
+
+static void spiflash_show_status(void)
+{
+    MSG("status regs: %02x %02x %02x\n",
+	spiflash_read_status(ROM_READ_SR1),
+	spiflash_read_status(ROM_READ_SR2),
+	spiflash_read_status(ROM_READ_SR3));
+}
+
+#define PIN_FPGA_READY		9
+#define PIN_FPGA_BOARD_ID	1
+
+int spiflash_write_spz(spz_stream *spz)
+{
+    unsigned int data_left = spz->header.len;
+    unsigned int addr      = spz->header.addr;
+    esp_err_t rv;
+
+    if (!data_left || spz->err)
+	return spz->err;
+
+    pinMode(PIN_FPGA_READY,    INPUT);
+    pinMode(PIN_FPGA_BOARD_ID, INPUT);
+
+    MSG("waiting for FPGA bypass to be ready..");
+    while (digitalRead(PIN_FPGA_READY) != LOW) {
+	CMSG(".");
+	yield();
+    }
+    CMSG("\n");
+    MSG("FPGA bypass ready, board version v%c.\n",
+	digitalRead(PIN_FPGA_BOARD_ID) ? '1' : '2');
+
+    jtag_enable(&jtag_config_spiflash);
+
+    spiflash_read_jedec_id();
+    spiflash_show_status();
+
+    while (data_left && !spz->err) {
+	unsigned int pre_padding  = addr & (SPIFLASH_SECTOR_SIZE-1);
+	unsigned int post_padding;
+	unsigned int bytes;
+
+	bytes = SPIFLASH_SECTOR_SIZE - pre_padding;
+	post_padding = 0;
+	if (bytes > data_left) {
+	    post_padding = bytes - data_left;
+	    bytes = data_left;
+	}
+
+	MSG("sector at %06x bytes %u\n", addr, bytes);
+
+	addr -= pre_padding;
+
+	/* Read the current content of this block into vbuf */
+	memset(spz->vbuf, 0xee, SPIFLASH_SECTOR_SIZE);
+	rv = spiflash_read(addr, spz->vbuf, SPIFLASH_SECTOR_SIZE);
+	if (rv)
+	    goto err;
+
+	/* Copy any invariant chunk */
+	if (pre_padding)
+	    memcpy(spz->dbuf, spz->vbuf, pre_padding);
+	if (post_padding)
+	    memcpy(spz->dbuf+SPIFLASH_SECTOR_SIZE-post_padding,
+		   spz->vbuf+SPIFLASH_SECTOR_SIZE-post_padding,
+		   post_padding);
+
+	rv = spz_read_data(spz, spz->dbuf+pre_padding, bytes);
+	if (rv != (int)bytes) {
+	    MSG("needed %u bytes got %d\n", rv);
+	    rv = Z_DATA_ERROR;
+	    goto err;
+	}
+
+	MSG("writing new data...\n");
+	rv = spiflash_write_sector(spz, addr);
+	if (rv) {
+	    spz->err = rv;
+	    goto err;
+	}
+
+	addr += pre_padding + bytes;
+	data_left -= bytes;
+    }
+    rv = 0;
+
+err:
+    if (!spz->err)
+	spz->err = rv;
+
+    jtag_disable(NULL);
+
+    return spz->err;
+}

+ 72 - 0
esp32/max80/spiflash.h

@@ -0,0 +1,72 @@
+#pragma once
+
+#include "common.h"
+
+#define SPIFLASH_SECTOR_SIZE	4096 /* Erasable chunk */
+#define SPIFLASH_PAGE_SIZE	 256 /* Programmable chunk */
+
+struct spz_stream;
+typedef struct spz_stream spz_stream;
+
+extern_c int spiflash_write_spz(spz_stream *spz);
+
+/* SPI flash command opcodes */
+enum romcmd {
+    /* Standard SPI mode commands */
+    ROM_WRITE_ENABLE			= 0x06,
+    ROM_VOLATILE_SR_WRITE_ENABLE	= 0x50,
+    ROM_WRITE_DISABLE			= 0x04,
+    ROM_RELEASE_POWERDOWN_ID		= 0xab,
+    ROM_MANUFACTURER_DEVICE_ID		= 0x90,
+    ROM_JEDEC_ID			= 0x9f,
+    ROM_READ_UNIQUE_ID			= 0x4b,
+    ROM_READ_DATA			= 0x03, /* DO NOT USE */
+    ROM_READ_DATA_32BIT			= 0x13, /* DO NOT USE */
+    ROM_FAST_READ			= 0x0b,
+    ROM_FAST_READ_32BIT			= 0x0c,
+    ROM_PAGE_PROGRAM			= 0x02,
+    ROM_PAGE_PROGRAM_32BIT		= 0x12,
+    ROM_ERASE_4K			= 0x20,
+    ROM_ERASE_4K_32BIT			= 0x21,
+    ROM_ERASE_32K			= 0x52,
+    ROM_ERASE_64K			= 0xd8,
+    ROM_ERASE_64K_32BIT			= 0xdc,
+    ROM_ERASE_ALL			= 0xc7,
+    ROM_READ_SR1			= 0x05,
+    ROM_WRITE_SR1			= 0x01,
+    ROM_READ_SR2			= 0x35,
+    ROM_WRITE_SR2			= 0x31,
+    ROM_READ_SR3			= 0x15,
+    ROM_WRITE_SR3			= 0x11,
+    ROM_READ_EAR			= 0xc8, /* Extended address register */
+    ROM_WRITE_EAR			= 0xc5,
+    ROM_READ_SFDP			= 0x5a,
+    ROM_ERASE_SECURITY			= 0x44,
+    ROM_PROGRAM_SECURITY		= 0x42,
+    ROM_READ_SECURITY			= 0x48,
+    ROM_GLOBAL_BLOCK_LOCK		= 0x7e,
+    ROM_GLOBAL_BLOCK_UNLOCK		= 0x98,
+    ROM_READ_BLOCK_LOCK			= 0x3d,
+    ROM_ONE_BLOCK_LOCK			= 0x36,
+    ROM_ONE_BLOCK_UNLOCK		= 0x39,
+    ROM_ERASE_PROGRAM_SUSPEND		= 0x75,
+    ROM_ERASE_PROGRAM_RESUME		= 0x7a,
+    ROM_POWER_DOWN			= 0xb9,
+    ROM_ENTER_32BIT			= 0xb7,
+    ROM_LEAVE_32BIT			= 0xe9,
+    ROM_ENTER_QPI			= 0x48,
+    ROM_ENABLE_RESET			= 0x66,
+    ROM_RESET				= 0x99,
+
+    /* Dual SPI commands */
+    ROM_FAST_READ_DUAL			= 0x3b,
+    ROM_FAST_READ_DUAL_32BIT		= 0x3c
+};
+
+#define SPIFLASH_SFDP_SIZE	256
+
+enum spiflash_addr_mode {
+    SPIFLASH_ADDR_DYNAMIC,	/* 24-bit for < 16 MB, otherwise 32 bit */
+    SPIFLASH_ADDR_24BIT,	/* 24-bit addressing only */
+    SPIFLASH_ADDR_32BIT		/* 32-bit addressing only */
+};

+ 35 - 0
esp32/max80/spz.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include "common.h"
+#include "fw.h"
+
+#include <zlib.h>
+
+struct spz_stream;
+typedef struct spz_stream spz_stream;
+
+#define SPZ_NBUF 4
+struct spz_stream {
+    z_stream zs;
+    read_func_t read_data;	    /* Read input data */
+    token_t token;		    /* Read input token */
+    uint8_t *optr;		    /* Output data pointer into obuf */
+    /* Note: available output data ends at zs->next_out */
+    union {
+	uint8_t *bufs[SPZ_NBUF];
+	struct {
+	    uint8_t *ibuf;	    /* Input buffer if compressed */
+	    uint8_t *obuf;	    /* Output buffer */
+	    uint8_t *dbuf;	    /* Block data buffer */
+	    uint8_t *vbuf;	    /* Readback/verify buffer */
+	};
+    };
+    struct fw_header header;	    /* Header of currently processed chunk */
+    int err;			    /* Error code to return */
+    bool eoi;			    /* Reached end of input */
+    bool cleanup;		    /* Call inflateEnd() */
+    bool esp_updated;		    /* ESP OTA update performed */
+    bool fpga_updated;		    /* FPGA initialization performed */
+};
+
+int spz_read_data(spz_stream *spz, void *buf, size_t len);

+ 130 - 0
esp32/max80/storage.cpp

@@ -0,0 +1,130 @@
+#include "storage.h"
+#include <EEPROM.h>
+
+#define SSID_LENGTH 64
+#define PASSWORD_LENGTH 64
+#define HOSTNAME_LENGTH 32
+
+struct Config {
+  // WIFI
+  char SSID[SSID_LENGTH];
+  char WifiPassword[PASSWORD_LENGTH];
+  char Hostname[HOSTNAME_LENGTH];
+  char OTAPassword[PASSWORD_LENGTH];
+} currentConfig;
+
+const size_t ConfigLength = sizeof(currentConfig);
+
+void ReadConfig() {
+  char *c = (char *)(&currentConfig);
+  for (int i=0; i<ConfigLength;i++) {
+    c[i] = EEPROM.read(i);
+    if (c[i] == (char)255)
+      c[i] = 0;
+  }
+
+  // Pad all strings to be null terminated
+  currentConfig.SSID[SSID_LENGTH-1] = 0x00;
+  currentConfig.WifiPassword[PASSWORD_LENGTH-1] = 0x00;
+  currentConfig.OTAPassword[PASSWORD_LENGTH-1] = 0x00;
+  currentConfig.Hostname[HOSTNAME_LENGTH-1] = 0x00;
+}
+
+void SaveConfig() {
+  currentConfig.SSID[SSID_LENGTH-1] = 0x00;
+  currentConfig.WifiPassword[PASSWORD_LENGTH-1] = 0x00;
+  currentConfig.OTAPassword[PASSWORD_LENGTH-1] = 0x00;
+  currentConfig.Hostname[HOSTNAME_LENGTH-1] = 0x00;
+
+  char *c = (char *)(&currentConfig);
+  for (int i=0; i<ConfigLength;i++) {
+    EEPROM.write(i, c[i]);
+  }
+  EEPROM.commit();
+}
+
+String GetWifiSSID() {
+  return String(currentConfig.SSID);
+}
+String GetWifiPassword() {
+  return String(currentConfig.WifiPassword);
+}
+String GetOTAPassword() {
+  return String(currentConfig.OTAPassword);
+}
+String GetHostname() {
+  return String(currentConfig.Hostname);
+}
+
+void SaveWifiSSID(String ssid) {
+    int maxLen = SSID_LENGTH-1;
+    if (ssid.length() < maxLen) {
+      maxLen = ssid.length();
+    }
+    for (int i = 0; i < SSID_LENGTH; i++) {
+      if (i < maxLen) {
+        currentConfig.SSID[i] = ssid[i];
+      } else {
+        currentConfig.SSID[i] = 0x00;
+      }
+    }
+    SaveConfig();
+}
+
+void SaveWifiPassword(String pass) {
+    int maxLen = PASSWORD_LENGTH-1;
+    if (pass.length() < maxLen) {
+      maxLen = pass.length();
+    }
+    for (int i = 0; i < PASSWORD_LENGTH; i++) {
+      if (i < maxLen) {
+        currentConfig.WifiPassword[i] = pass[i];
+      } else {
+        currentConfig.WifiPassword[i] = 0x00;
+      }
+    }
+    SaveConfig();
+}
+
+
+void SaveOTAPassword(String pass) {
+    int maxLen = PASSWORD_LENGTH-1;
+    if (pass.length() < maxLen) {
+      maxLen = pass.length();
+    }
+    for (int i = 0; i < PASSWORD_LENGTH; i++) {
+      if (i < maxLen) {
+        currentConfig.OTAPassword[i] = pass[i];
+      } else {
+        currentConfig.OTAPassword[i] = 0x00;
+      }
+    }
+    SaveConfig();
+}
+
+void SaveHostname(String hostname) {
+    int maxLen = HOSTNAME_LENGTH-1;
+    if (hostname.length() < maxLen) {
+      maxLen = hostname.length();
+    }
+    for (int i = 0; i < HOSTNAME_LENGTH; i++) {
+      if (i < maxLen) {
+        currentConfig.Hostname[i] = hostname[i];
+      } else {
+        currentConfig.Hostname[i] = 0x00;
+      }
+    }
+    SaveConfig();
+}
+
+void InitStorage() {
+  EEPROM.begin(ConfigLength);
+#if 1
+  SaveWifiSSID("Hyperion-2");
+  SaveWifiPassword("eUrPp7xtbexWm4TEu7nDtGLRcGP9hvYo");
+  SaveHostname("max80");
+  SaveOTAPassword("max80");
+#endif
+
+  ReadConfig();
+}

+ 15 - 0
esp32/max80/storage.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include "Arduino.h"
+
+String GetWifiSSID();
+String GetWifiPassword();
+String GetHostname();
+String GetOTAPassword();
+
+void SaveWifiSSID(String ssid);
+void SaveWifiPassword(String pass);
+void SaveHostname(String hostname);
+void SaveOTAPassword(String password);
+
+void InitStorage();

+ 112 - 0
esp32/max80/tap.c

@@ -0,0 +1,112 @@
+#include "common.h"
+#include "jtag.h"
+
+const uint8_t tap_state_next[16][2] = {
+    [TAP_TEST_LOGIC_RESET] = { TAP_RUN_TEST_IDLE,  TAP_TEST_LOGIC_RESET },
+    [TAP_RUN_TEST_IDLE   ] = { TAP_RUN_TEST_IDLE,  TAP_SELECT_DR_SCAN   },
+    [TAP_SELECT_DR_SCAN  ] = { TAP_CAPTURE_DR,     TAP_SELECT_IR_SCAN   },
+    [TAP_CAPTURE_DR      ] = { TAP_SHIFT_DR,       TAP_EXIT1_DR         },
+    [TAP_SHIFT_DR        ] = { TAP_SHIFT_DR,       TAP_EXIT1_DR         },
+    [TAP_EXIT1_DR        ] = { TAP_PAUSE_DR,       TAP_UPDATE_DR        },
+    [TAP_PAUSE_DR        ] = { TAP_PAUSE_DR,       TAP_EXIT2_DR         },
+    [TAP_EXIT2_DR        ] = { TAP_SHIFT_DR,       TAP_UPDATE_DR        },
+    [TAP_UPDATE_DR       ] = { TAP_RUN_TEST_IDLE,  TAP_SELECT_DR_SCAN   },
+    [TAP_SELECT_IR_SCAN  ] = { TAP_CAPTURE_IR,     TAP_TEST_LOGIC_RESET },
+    [TAP_CAPTURE_IR      ] = { TAP_SHIFT_IR,       TAP_EXIT1_IR         },
+    [TAP_SHIFT_IR        ] = { TAP_SHIFT_IR,       TAP_EXIT1_IR         },
+    [TAP_EXIT1_IR        ] = { TAP_PAUSE_IR,       TAP_UPDATE_IR        },
+    [TAP_PAUSE_IR        ] = { TAP_PAUSE_IR,       TAP_EXIT2_IR         },
+    [TAP_EXIT2_IR        ] = { TAP_SHIFT_IR,       TAP_UPDATE_IR        },
+    [TAP_UPDATE_IR       ] = { TAP_RUN_TEST_IDLE,  TAP_SELECT_DR_SCAN   }
+};
+
+const char tap_state_names[16][4] =
+{
+    "TLR", "RTI", "SDS", "CDR", "SDR", "EDR", "PDR", "ED2",
+    "UDR", "SIS", "CIR", "SIR", "EIR", "PIR", "EI2", "UIR"
+};
+
+#ifdef MAKE_TAPROUTE_C /* Generate taproute.c */
+
+#include <stdio.h>
+#include <limits.h>
+
+int main(void)
+{
+    int dir[16][16];
+    unsigned int distance[16][16];
+    memset(dir, 0, sizeof dir);
+
+    memset(distance, 0x55, sizeof distance); /* Just don't wrap */
+    for (unsigned int fs = 0; fs < 16; fs++) {
+	distance[fs][fs] = 0;
+	dir[fs][fs] = tap_state_next[fs][0] != fs;
+    }
+
+    bool better;
+    do {
+	better = false;
+	for (unsigned int fs = 0; fs < 16; fs++) {
+	    for (unsigned int ts = 0; ts < 16; ts++) {
+		for (unsigned int d = 0; d < 2; d++) {
+		    unsigned int next = tap_state_next[fs][d];
+		    unsigned int next_cost = distance[next][ts]+1;
+		    if (distance[fs][ts] > next_cost) {
+			distance[fs][ts] = next_cost;
+			dir[fs][ts] = d;
+			better = true;
+		    }
+		}
+	    }
+	}
+    } while (better);
+
+
+    printf("/*\n"
+	   " * This is a generated file!\n"
+	   " *\n"
+	   " * JTAG state routing table: to get from the horizontal\n"
+	   " * axis state to the vertical axis state, go in the given\n"
+	   " * direction. The table in this comment also shows the number\n"
+	   " * of transitions needed.\n"
+	   " *\n");
+
+    printf(" * from:");
+    for (unsigned int fs = 0; fs < 16; fs++)
+	printf(" %3s", tap_state_names[fs]);
+    printf("\n *\n");
+
+    for (unsigned int ts = 0; ts < 16; ts++) {
+	printf(" * %3s: ", tap_state_names[ts]);
+	for (unsigned int fs = 0; fs < 16; fs++) {
+	    printf(" %d/%d", dir[fs][ts], distance[fs][ts]);
+	}
+	printf("\n");
+    }
+
+    printf(" *\n");
+    for (unsigned int d = 0; d < 2; d++) {
+	printf(" *  [%u] ", d);
+	for (unsigned int fs = 0; fs < 16; fs++)
+	    printf(" %3s", tap_state_names[tap_state_next[fs][d]]);
+	printf("\n");
+    }
+
+    printf(" */\n\n");
+
+    printf("#include \"jtag.h\"\n\n");
+    printf("const uint16_t tap_state_route[16] = {\n");
+
+    for (unsigned int ts = 0; ts < 16; ts++) {
+	unsigned int dirmask = 0;
+	for (unsigned int fs = 0; fs < 16; fs++) {
+	    dirmask |= dir[fs][ts] << fs;
+	}
+	printf("\t0x%04x,\n", dirmask);
+    }
+    printf("};\n");
+
+    return 0;
+}
+
+#endif /* MAKE_TAPROUTE_C */

+ 51 - 0
esp32/max80/taproute.c

@@ -0,0 +1,51 @@
+/*
+ * This is a generated file!
+ *
+ * JTAG state routing table: to get from the horizontal
+ * axis state to the vertical axis state, go in the given
+ * direction. The table in this comment also shows the number
+ * of transitions needed.
+ *
+ * from: TLR RTI SDS CDR SDR EDR PDR ED2 UDR SIS CIR SIR EIR PIR EI2 UIR
+ *
+ * TLR:  1/0 1/3 1/2 1/5 1/5 1/4 1/5 1/4 1/3 1/1 1/5 1/5 1/4 1/5 1/4 1/3
+ * RTI:  0/1 0/0 1/3 1/3 1/3 1/2 1/3 1/2 0/1 1/2 1/3 1/3 1/2 1/3 1/2 0/1
+ * SDS:  0/2 1/1 1/0 1/3 1/3 1/2 1/3 1/2 1/1 1/3 1/3 1/3 1/2 1/3 1/2 1/1
+ * CDR:  0/3 1/2 0/1 1/0 1/4 1/3 1/4 1/3 1/2 1/4 1/4 1/4 1/3 1/4 1/3 1/2
+ * SDR:  0/4 1/3 0/2 0/1 0/0 0/3 1/2 0/1 1/3 1/5 1/5 1/5 1/4 1/5 1/4 1/3
+ * EDR:  0/4 1/3 0/2 1/1 1/1 1/0 1/3 0/2 1/3 1/5 1/5 1/5 1/4 1/5 1/4 1/3
+ * PDR:  0/5 1/4 0/3 1/2 1/2 0/1 0/0 0/3 1/4 1/6 1/6 1/6 1/5 1/6 1/5 1/4
+ * ED2:  0/6 1/5 0/4 1/3 1/3 0/2 1/1 1/0 1/5 1/7 1/7 1/7 1/6 1/7 1/6 1/5
+ * UDR:  0/5 1/4 0/3 1/2 1/2 1/1 1/2 1/1 1/0 1/6 1/6 1/6 1/5 1/6 1/5 1/4
+ * SIS:  0/3 1/2 1/1 1/4 1/4 1/3 1/4 1/3 1/2 1/0 1/4 1/4 1/3 1/4 1/3 1/2
+ * CIR:  0/4 1/3 1/2 1/5 1/5 1/4 1/5 1/4 1/3 0/1 1/0 1/5 1/4 1/5 1/4 1/3
+ * SIR:  0/5 1/4 1/3 1/6 1/6 1/5 1/6 1/5 1/4 0/2 0/1 0/0 0/3 1/2 0/1 1/4
+ * EIR:  0/5 1/4 1/3 1/6 1/6 1/5 1/6 1/5 1/4 0/2 1/1 1/1 1/0 1/3 0/2 1/4
+ * PIR:  0/6 1/5 1/4 1/7 1/7 1/6 1/7 1/6 1/5 0/3 1/2 1/2 0/1 0/0 0/3 1/5
+ * EI2:  0/7 1/6 1/5 1/8 1/8 1/7 1/8 1/7 1/6 0/4 1/3 1/3 0/2 1/1 1/0 1/6
+ * UIR:  0/6 1/5 1/4 1/7 1/7 1/6 1/7 1/6 1/5 0/3 1/2 1/2 1/1 1/2 1/1 1/0
+ *
+ *  [0]  RTI RTI CDR SDR SDR PDR PDR SDR RTI CIR SIR SIR PIR PIR SIR RTI
+ *  [1]  TLR SDS SIS EDR EDR UDR ED2 UDR SDS TLR EIR EIR UIR EI2 UIR SDS
+ */
+
+#include "jtag.h"
+
+const uint16_t tap_state_route[16] = {
+	0xffff,
+	0x7efc,
+	0xfffe,
+	0xfffa,
+	0xff42,
+	0xff7a,
+	0xff1a,
+	0xffda,
+	0xfffa,
+	0xfffe,
+	0xfdfe,
+	0xa1fe,
+	0xbdfe,
+	0x8dfe,
+	0xedfe,
+	0xfdfe,
+};

+ 190 - 0
esp32/max80/wifi.cpp

@@ -0,0 +1,190 @@
+#include "Arduino.h"
+#include "ArduinoOTA.h"
+#include "WiFi.h"
+#include "wifi.h"
+#include "storage.h"
+#include "httpd.h"
+
+String ssid        = "";
+String password    = "";
+String otaPassword = "";
+String hostname    = "max80";
+
+bool inOTA = false;
+
+static void start_services(void)
+{
+    my_httpd_start();
+}
+
+static String wifi_local_ip(void)
+{
+    return WiFi.localIP().toString();
+}
+
+static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
+{
+    bool retry = false;
+    bool connected = false;
+
+    switch (event) {
+    case ARDUINO_EVENT_WIFI_READY:
+	printf("[WIFI] Interface ready\n");
+	break;
+    case ARDUINO_EVENT_WIFI_SCAN_DONE:
+	printf("[WIFI] Completed scan for access points\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_START:
+	printf("[WIFI] Client started\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_STOP:
+	printf("[WIFI] Clients stopped\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_CONNECTED:
+	printf("[WIFI] Connected to access point\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
+	printf("[WIFI] Disconnected from WiFi access point\n");
+	retry = true;
+	break;
+    case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
+	printf("[WIFI] Authentication mode of access point has changed\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_GOT_IP:
+	printf("[WIFI] Obtained IP address: %s\n", wifi_local_ip().c_str());
+	connected = true;
+	break;
+    case ARDUINO_EVENT_WIFI_STA_LOST_IP:
+	printf("[WIFI] Lost IP address and IP address is reset to 0\n");
+	retry = true;
+	break;
+    case ARDUINO_EVENT_WPS_ER_SUCCESS:
+	printf("[WIFI] WiFi Protected Setup (WPS): succeeded in enrollee mode\n");
+	break;
+    case ARDUINO_EVENT_WPS_ER_FAILED:
+	printf("[WIFI] WiFi Protected Setup (WPS): failed in enrollee mode\n");
+	break;
+    case ARDUINO_EVENT_WPS_ER_TIMEOUT:
+	printf("[WIFI] WiFi Protected Setup (WPS): timeout in enrollee mode\n");
+	break;
+    case ARDUINO_EVENT_WPS_ER_PIN:
+	printf("[WIFI] WiFi Protected Setup (WPS): pin code in enrollee mode\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_START:
+	printf("[WIFI] Access point started\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_STOP:
+	printf("[WIFI] Access point stopped\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
+	printf("[WIFI] Client connected\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
+	printf("[WIFI] Client disconnected\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
+	printf("[WIFI] Assigned IP address to client\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
+	printf("[WIFI] Received probe request\n");
+	break;
+    case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
+	printf("[WIFI] AP IPv6 is preferred\n");
+	break;
+    case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
+	printf("[WIFI] STA IPv6 is preferred\n");
+	connected = true;
+	break;
+    case ARDUINO_EVENT_ETH_GOT_IP6:
+	printf("[ETH] Ethernet IPv6 is preferred\n");
+	connected = true;
+	break;
+    case ARDUINO_EVENT_ETH_START:
+	printf("[ETH] Ethernet started\n");
+	break;
+    case ARDUINO_EVENT_ETH_STOP:
+	printf("[ETH] Ethernet stopped\n");
+	break;
+    case ARDUINO_EVENT_ETH_CONNECTED:
+	printf("[ETH] Ethernet connected\n");
+	break;
+    case ARDUINO_EVENT_ETH_DISCONNECTED:
+	printf("[ETH] Ethernet disconnected\n");
+	retry = true;
+	break;
+    case ARDUINO_EVENT_ETH_GOT_IP:
+	printf("[ETH] Obtained IP address: %s\n", wifi_local_ip().c_str());
+	connected = true;
+	break;
+    default:
+	break;
+    }
+
+    /* Maybe need to do these in a different thread? */
+
+    if (connected) {
+	start_services();
+    }
+
+    if (retry) {
+	WiFi.disconnect();
+	WiFi.begin();
+    }
+}
+
+void SetupWiFi() {
+    ssid = GetWifiSSID();
+    password = GetWifiPassword();
+    hostname = GetHostname();
+    inOTA = false;
+
+    printf("[INFO] Setting up WiFi\n");
+    printf("[INFO] SSID: %s\n", ssid.c_str());
+
+    WiFi.onEvent(WiFiEvent);
+
+    if (WiFi.getMode() != WIFI_STA) {
+	WiFi.mode(WIFI_STA);
+	delay(10);
+    }
+
+    if (WiFi.SSID() != ssid || WiFi.psk() != password) {
+	printf("[INFO] WiFi config changed.\n");
+	// ... Try to connect to WiFi station.
+	WiFi.begin(ssid.c_str(), password.c_str());
+    } else {
+	WiFi.begin();
+    }
+
+    WiFi.setAutoReconnect(true);
+    WiFi.setHostname(hostname.c_str());
+}
+
+void SetupOTA() {
+    // Start OTA server.
+    ArduinoOTA.setHostname((const char *)hostname.c_str());
+
+    ArduinoOTA.onStart([]() {
+	    inOTA = true;
+	    printf("[INFO] OTA Update Start\n");
+	});
+
+    ArduinoOTA.onEnd([]() {
+	    printf("[INFO] OTA Update End\n");
+	});
+
+    ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
+	    printf("[INFO] Progress: %u%%\n", (unsigned int)((progress*100ULL)/total));
+	});
+
+    ArduinoOTA.onError([](ota_error_t error) {
+	    printf("[ERROR] %u: ", error);
+	    if (error == OTA_AUTH_ERROR) printf("Auth failed\n");
+	    else if (error == OTA_BEGIN_ERROR) printf("Start Failed\n");
+	    else if (error == OTA_CONNECT_ERROR) printf("Connection failed\n");
+	    else if (error == OTA_RECEIVE_ERROR) printf("Receive Error\n");
+	    else if (error == OTA_END_ERROR) printf("End Fail\n");
+	});
+
+    ArduinoOTA.begin();
+}

+ 5 - 0
esp32/max80/wifi.h

@@ -0,0 +1,5 @@
+#pragma once
+
+void SetupWiFi();
+void SetupOTA();
+bool InOTA();

+ 42 - 24
fpga/Makefile

@@ -1,24 +1,24 @@
 MAKEFLAGS      += -R -r
 
 PROJECT		= max80
-REVISIONS	= v1 v2
-VARIANTS        = jtagupd
-QU        	= quartus
+REVISIONS	= v1 v2 bypass
+VARIANTS        =
+QU		= quartus
 
 # Common options for all Quartus tools
-QPRI    	= --lower_priority
+QPRI		= --lower_priority
 QCPF		= $(QU)_cpf $(QPRI)
 QSH		= $(QU)_sh  $(QPRI)
 QSTA		= $(QU)_sta $(QPRI)
-QPGM    	= $(QU)_pgm $(QPRI)
+QPGM		= $(QU)_pgm $(QPRI)
 
 # Common options for Quartus in-flow tools
-QOPT    	= --write_settings_files=off $(QPRI)
-QMAP    	= $(QU)_map $(QOPT)
-QFIT    	= $(QU)_fit $(QOPT)
-QCDB    	= $(QU)_cdb $(QOPT)
-QASM    	= $(QU)_asm $(QOPT)
-QPOW    	= $(QU)_pow $(QOPT)
+QOPT		= --write_settings_files=off $(QPRI)
+QMAP		= $(QU)_map $(QOPT)
+QFIT		= $(QU)_fit $(QOPT)
+QCDB		= $(QU)_cdb $(QOPT)
+QASM		= $(QU)_asm $(QOPT)
+QPOW		= $(QU)_pow $(QOPT)
 
 outdir		= output
 mifdir		= mif
@@ -36,15 +36,22 @@ PREREQFILES = $(mifdir)/sram.mif \
 		$(outdir)/$(rev).$(coffmt).cof))
 
 alltarg := sof pof jic svf svf.gz xsvf xsvf.gz rbf rbf.gz rpf rpf.gz \
-	   rpd rpd.gz fw pow.rpt sta.rpt
+	   rpd rpd.gz pow.rpt sta.rpt
 allout   = $(foreach o,$(alltarg),$(outdir)/$(1).$(o))
-vartarg := sof svf svf.gz rbf rbf.gz
-varout   = $(foreach o,$(vartarg),$(outdir)/$(1).$(o))
-uptarg  := update.svf update.svf.gz update.xsvf update.xsvf.gz
-upout    = $(foreach o,$(uptarg),$(outdir)/$(1).$(o))
+#vartarg := sof svf svf.gz rbf rbf.gz
+#varout   = $(foreach o,$(vartarg),$(outdir)/$(1).$(o))
+#uptarg  := update.svf update.svf.gz update.xsvf update.xsvf.gz
+#upout    = $(foreach o,$(uptarg),$(outdir)/$(1).$(o))
 
 sram_src = ../rv32/
 
+DRAM_ADDR  := 0x100000
+DRAM_IMAGE := ../rv32/dram.bin
+ESP_IMAGE  := ../esp32/max80/max80.ino.esp32s2usb.bin
+
+IDCODE     := 0x020f20dd		# JTAG IDCODE of FPGA
+
+
 .SUFFIXES:
 
 .SECONDARY:
@@ -54,11 +61,15 @@ sram_src = ../rv32/
 all:
 	$(MAKE) prereq
 	$(MAKE) $(REVISIONS:=.targets)
-	$(MAKE) $(VARIANTS)
-	$(MAKE) $(REVISIONS:=.update)
+	$(MAKE) fwimages
+#	$(MAKE) $(VARIANTS)
+#	$(MAKE) $(REVISIONS:=.update)
 
 -include $(REVISIONS:=.deps)
 
+.PHONY: fwimages
+fwimages: $(patsubst %,$(outdir)/%.fw,$(filter-out bypass,$(REVISIONS)))
+
 .PHONY: $(REVISIONS)
 $(REVISIONS):
 	$(MAKE) prereq
@@ -131,8 +142,9 @@ $(outdir)/%.pof: $(outdir)/%.pof.cof $(outdir)/%.sof
 	$(QCPF) -o bitstream_compression=on -c $<
 
 # This produces a transient-load .svf file
+# 6 MHz is the speed of USB-Blaster...
 $(outdir)/%.svf: $(outdir)/%.sof
-	$(QCPF) -c -q 12.0MHz -g 3.3 -n p $< $@
+	$(QCPF) -c -q 6.0MHz -g 3.3 -n p $< $@
 
 # xsvf: compact representation of .svf; more or less a wrapper around
 # the raw binary file.
@@ -155,16 +167,22 @@ $(outdir)/%.rpd: $(outdir)/%.pof
 $(outdir)/%.z.rbf: $(outdir)/%.sof
 	$(QCPF) -c -o bitstream_compression=on $< $@
 
-$(outdir)/%.rpf: $(outdir)/%.rpd $(outdir)/%.z.rbf 
+$(outdir)/%.rpf: $(outdir)/%.rpd $(outdir)/%.z.rbf
 	dd if=$< of=$@ bs=$$(wc -c < $(@:.rpf=.z.rbf)) count=1
 
 $(outdir)/%.gz: $(outdir)/%
 	$(GZIP) -9 < $< > $@
 
-$(outdir)/%.fw: $(outdir)/%.rpf ../rv32/dram.bin ../tools/wrapflash.pl
-	$(PERL) ../tools/wrapflash.pl 'MAX80 $*' \
-		$< 0 ../rv32/dram.bin 0x100000 | \
-	$(GZIP) -9 > $@
+$(outdir)/%.fw: $(outdir)/%.rpf $(outdir)/bypass.rbf \
+		$(DRAM_IMAGE) $(ESP_IMAGE) \
+		../tools/mkfwimage.pl
+	$(PERL) ../tools/mkfwimage.pl -o - \
+		-type target -str 'MAX80 $*' \
+		-type fpgainit -addr $(IDCODE) -optional -file $(outdir)/bypass.rbf \
+		-type data -addr 0 -file $< \
+		-type data -addr $(DRAM_ADDR) -file $(DRAM_IMAGE) \
+		-type espota -optional -file $(ESP_IMAGE) \
+	| $(GZIP) -9 > $@
 
 $(outdir)/%.update.svf: ./scripts/flashsvf.pl \
 	$(outdir)/jtagupd/%.svf $(outdir)/%.map.rpt $(outdir)/%.fw

+ 57 - 0
fpga/bypass.pins

@@ -0,0 +1,57 @@
+#
+# This intentionally only containts the pins that cannot be
+# "reserved as input with weak pullup" or are needed for
+# communications with flash or esp.
+#
+
+# Bank 1
+b1	abc_xm_x
+c2	abc_host_v12
+c1	flash_io[0]
+d2	flash_cs_n
+h1	flash_sck
+h2	flash_io[1]
+
+# Bank 2
+l3	abc_out_n[3]
+k1	abc_a[11]
+l2	abc_inp_n[0]
+l1	abc_a[12]
+k2	abc_out_n[4]
+n2	abc_a[14]
+n1	abc_a[15]
+k5	abc_out_n[2]
+l4	abc_a[10]
+
+# Bank 3
+p6	spi_clk
+m7	spi_miso
+r5	abc_d_oe
+r6	abc_resin_x
+m8	spi_mosi
+n8	spi_cs_esp_n
+p8	esp_int
+n6	spi_cs_flash_n
+
+# Bank 4
+t13	led_2
+t10	abc_host_v1
+k10	board_id
+r13	hdmi_sda
+t14	led_1_v1
+p14	led_1_v2
+r14	led_0
+
+# Bank 5 (2.5 V)
+m15	clock_in
+
+# Bank 6
+e15	rtc_32khz
+
+# Bank 7
+
+# Bank 8
+b4	abc_rdy_x
+a2	abc_int800_x
+a3	abc_nmi_x
+b3	abc_int80_x

+ 164 - 0
fpga/bypass.qsf

@@ -0,0 +1,164 @@
+# -*- tcl -*-
+
+set_global_assignment -name TOP_LEVEL_ENTITY bypass
+set_global_assignment -name SYSTEMVERILOG_FILE bypass.sv
+
+set_global_assignment -name FAMILY "Cyclone IV E"
+set_global_assignment -name DEVICE EP4CE15F17C8
+set_global_assignment -name ORIGINAL_QUARTUS_VERSION 21.1.0
+set_global_assignment -name PROJECT_CREATION_TIME_DATE "16:21:14  DECEMBER 22, 2021"
+set_global_assignment -name LAST_QUARTUS_VERSION "21.1.0 Lite Edition"
+set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output
+set_global_assignment -name MIN_CORE_JUNCTION_TEMP 0
+set_global_assignment -name MAX_CORE_JUNCTION_TEMP 85
+set_global_assignment -name DEVICE_FILTER_PACKAGE EQFP
+set_global_assignment -name DEVICE_FILTER_PIN_COUNT 144
+set_global_assignment -name DEVICE_FILTER_SPEED_GRADE 8
+set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR 256
+set_global_assignment -name EDA_SIMULATION_TOOL "Questa Intel FPGA (SystemVerilog)"
+set_global_assignment -name EDA_TIME_SCALE "1 ps" -section_id eda_simulation
+set_global_assignment -name EDA_OUTPUT_DATA_FORMAT "SYSTEMVERILOG HDL" -section_id eda_simulation
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_timing
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_symbol
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_signal_integrity
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_boundary_scan
+set_global_assignment -name DEVICE_MIGRATION_LIST EP4CE15F17C8
+set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top
+set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top
+set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top
+set_global_assignment -name VCCA_USER_VOLTAGE 2.5V
+set_global_assignment -name POWER_PRESET_COOLING_SOLUTION "NO HEAT SINK WITH STILL AIR"
+set_global_assignment -name POWER_BOARD_THERMAL_MODEL "NONE (CONSERVATIVE)"
+set_global_assignment -name POWER_DEFAULT_INPUT_IO_TOGGLE_RATE "12.5 %"
+set_global_assignment -name VHDL_INPUT_VERSION VHDL_2008
+set_global_assignment -name VHDL_SHOW_LMF_MAPPING_MESSAGES OFF
+set_global_assignment -name VERILOG_INPUT_VERSION SYSTEMVERILOG_2005
+set_global_assignment -name VERILOG_SHOW_LMF_MAPPING_MESSAGES OFF
+set_global_assignment -name REMOVE_REDUNDANT_LOGIC_CELLS ON
+set_global_assignment -name HDL_MESSAGE_LEVEL LEVEL3
+set_global_assignment -name SYNTH_PROTECT_SDC_CONSTRAINT ON
+set_global_assignment -name SYNTH_MESSAGE_LEVEL HIGH
+set_global_assignment -name OPTIMIZE_IOC_REGISTER_PLACEMENT_FOR_TIMING "PACK ALL IO REGISTERS"
+set_global_assignment -name MUX_RESTRUCTURE AUTO
+set_global_assignment -name WEAK_PULL_UP_RESISTOR ON
+set_global_assignment -name ENABLE_OCT_DONE OFF
+set_global_assignment -name ENABLE_CONFIGURATION_PINS OFF
+set_global_assignment -name ENABLE_BOOT_SEL_PIN OFF
+set_global_assignment -name USE_CONFIGURATION_DEVICE ON
+set_global_assignment -name INTERNAL_FLASH_UPDATE_MODE "SINGLE COMP IMAGE"
+set_global_assignment -name STRATIXIII_UPDATE_MODE STANDARD
+set_global_assignment -name CRC_ERROR_OPEN_DRAIN OFF
+set_global_assignment -name GENERATE_JBC_FILE ON
+set_global_assignment -name STRATIX_DEVICE_IO_STANDARD "3.3-V LVTTL"
+set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -rise
+set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -fall
+set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -rise
+set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -fall
+set_instance_assignment -name CURRENT_STRENGTH_NEW "MAXIMUM CURRENT" -to sr_clk
+
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to clock_*
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 6
+set_global_assignment -name IOBANK_VCCIO 2.5V -section_id 5
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 2
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 1
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 8
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 7
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 4
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 3
+set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DATA0_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DATA1_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_FLASH_NCE_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DCLK_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to flash_clk
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to flash_cs_n
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to board_id
+
+set_global_assignment -name CYCLONEIII_CONFIGURATION_DEVICE EPCQ128A
+
+set_global_assignment -name FORCE_CONFIGURATION_VCCIO ON
+set_global_assignment -name CONFIGURATION_VCCIO_LEVEL 3.3V
+
+set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED WITH WEAK PULL-UP"
+set_global_assignment -name PRE_FLOW_SCRIPT_FILE "quartus_sh:scripts/preflow.tcl"
+set_global_assignment -name POST_MODULE_SCRIPT_FILE "quartus_sh:scripts/postmodule.tcl"
+
+set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE PERFORMANCE"
+set_global_assignment -name AUTO_RAM_TO_LCELL_CONVERSION ON
+set_global_assignment -name SYNTH_GATED_CLOCK_CONVERSION ON
+set_global_assignment -name PRE_MAPPING_RESYNTHESIS ON
+set_global_assignment -name ROUTER_CLOCKING_TOPOLOGY_ANALYSIS ON
+set_global_assignment -name QII_AUTO_PACKED_REGISTERS "SPARSE AUTO"
+
+set_global_assignment -name SAVE_DISK_SPACE OFF
+set_global_assignment -name TIMING_ANALYZER_MULTICORNER_ANALYSIS ON
+set_global_assignment -name SMART_RECOMPILE ON
+set_global_assignment -name EDA_TEST_BENCH_ENABLE_STATUS TEST_BENCH_MODE -section_id eda_simulation
+set_global_assignment -name EDA_NATIVELINK_SIMULATION_TEST_BENCH testclk -section_id eda_simulation
+set_global_assignment -name EDA_TEST_BENCH_NAME testclk -section_id eda_simulation
+set_global_assignment -name EDA_DESIGN_INSTANCE_NAME max80 -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_RUN_SIM_FOR "1 ms" -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_MODULE_NAME testclk -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_FILE simulation/testclk.sv -section_id testclk
+set_global_assignment -name EDA_NATIVELINK_PORTABLE_FILE_PATHS ON -section_id eda_simulation
+
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to rtc_32khz
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tdo
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tck
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tdi
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tms
+
+set_global_assignment -name OCP_HW_EVAL DISABLE
+set_global_assignment -name TIMING_ANALYZER_DO_REPORT_TIMING ON
+
+set_global_assignment -name READ_OR_WRITE_IN_BYTE_ADDRESS ON
+set_global_assignment -name POWER_REPORT_POWER_DISSIPATION ON
+set_global_assignment -name POWER_USE_DEVICE_CHARACTERISTICS MAXIMUM
+set_global_assignment -name POWER_USE_TA_VALUE 35
+set_global_assignment -name SOURCE_FILE bypass.pins
+set_global_assignment -name SOURCE_TCL_SCRIPT_FILE scripts/pins.tcl
+
+
+
+
+set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top
+set_global_assignment -name EDA_BOARD_DESIGN_BOUNDARY_SCAN_TOOL "BSDL (Boundary Scan)"
+set_global_assignment -name EDA_NETLIST_WRITER_OUTPUT_DIR /home/hpa/abc80/max80/fw/fpga/bsdl -section_id eda_board_design_boundary_scan
+set_global_assignment -name EDA_BOARD_BOUNDARY_SCAN_OPERATION POST_CONFIG -section_id eda_board_design_boundary_scan
+set_location_assignment PIN_L4 -to abc_a[10]
+set_location_assignment PIN_K1 -to abc_a[11]
+set_location_assignment PIN_L1 -to abc_a[12]
+set_location_assignment PIN_N2 -to abc_a[14]
+set_location_assignment PIN_N1 -to abc_a[15]
+set_location_assignment PIN_R5 -to abc_d_oe
+set_location_assignment PIN_T10 -to abc_host_v1
+set_location_assignment PIN_C2 -to abc_host_v12
+set_location_assignment PIN_L2 -to abc_inp_n[0]
+set_location_assignment PIN_A2 -to abc_int800_x
+set_location_assignment PIN_B3 -to abc_int80_x
+set_location_assignment PIN_A3 -to abc_nmi_x
+set_location_assignment PIN_K5 -to abc_out_n[2]
+set_location_assignment PIN_L3 -to abc_out_n[3]
+set_location_assignment PIN_K2 -to abc_out_n[4]
+set_location_assignment PIN_B4 -to abc_rdy_x
+set_location_assignment PIN_R6 -to abc_resin_x
+set_location_assignment PIN_B1 -to abc_xm_x
+set_location_assignment PIN_K10 -to board_id
+set_location_assignment PIN_M15 -to clock_in
+set_location_assignment PIN_P8 -to esp_int
+set_location_assignment PIN_D2 -to flash_cs_n
+set_location_assignment PIN_C1 -to flash_io[0]
+set_location_assignment PIN_H2 -to flash_io[1]
+set_location_assignment PIN_H1 -to flash_sck
+set_location_assignment PIN_R13 -to hdmi_sda
+set_location_assignment PIN_R14 -to led_0
+set_location_assignment PIN_T14 -to led_1_v1
+set_location_assignment PIN_P14 -to led_1_v2
+set_location_assignment PIN_T13 -to led_2
+set_location_assignment PIN_E15 -to rtc_32khz
+set_location_assignment PIN_P6 -to spi_clk
+set_location_assignment PIN_N8 -to spi_cs_esp_n
+set_location_assignment PIN_N6 -to spi_cs_flash_n
+set_location_assignment PIN_M7 -to spi_miso
+set_location_assignment PIN_M8 -to spi_mosi
+set_global_assignment -name SOURCE_FILE scripts/pins.tcl

+ 18 - 0
fpga/bypass.sdc

@@ -0,0 +1,18 @@
+# -*- tcl -*-
+
+# Clock constraints
+
+# Input master clock for all PLLs
+create_clock -name "clock_48" -period 20.834ns [get_ports {clock_in}]
+#create_clock -name "clock_16" -period 62.500ns [get_ports {clock_16}]
+derive_pll_clocks
+
+# RTC clock; asynchronous with all others
+create_clock -name "rtc_32khz" -period 30517.578ns [get_ports {rtc_32khz}]
+set_clock_groups -asynchronous -group {rtc_32khz}
+
+# Automatically calculate clock uncertainty to jitter and other effects.
+derive_clock_uncertainty
+
+# Don't report signaltap clock problems...
+set_false_path -to [get_registers sld_signaltap:*]

+ 71 - 0
fpga/bypass.sv

@@ -0,0 +1,71 @@
+module bypass (
+	       input	   clock_in,
+	       input	   rtc_32khz,
+	       input	   board_id,
+
+	       input	   spi_clk,
+	       output	   spi_miso,
+	       input	   spi_mosi,
+	       input	   spi_cs_esp_n,
+	       output	   esp_int,
+	       output      spi_cs_flash_n, // Really just a GPIO on ESP32
+
+	       output	   flash_cs_n,
+	       output	   flash_sck,
+	       inout [1:0] flash_io,
+
+	       output	   abc_host_v1,
+	       output	   abc_host_v12,
+	       output	   abc_d_oe,
+
+	       output	   abc_int800_x,
+	       output	   abc_int80_x,
+	       output	   abc_nmi_x,
+	       output	   abc_rdy_x,
+	       output	   abc_resin_x,
+	       output	   abc_xm_x,
+
+	       output	   led_0,
+	       output	   led_1_v1,
+	       output	   led_1_v2,
+	       output	   led_2
+	       );
+
+   wire			   v1 = board_id;  // High = v1
+   wire			   v2 = ~board_id; // Low = v2
+
+   wire [2:0]		   led;
+
+   assign led_0         = led[0];
+   assign led_1_v1      = v1 ? led[1] : 1'bz;
+   assign led_1_v2      = v2 ? led[1] : 1'bz;
+   assign led_2         = led[2];
+
+   assign flash_sck     = spi_clk;
+   assign flash_io[0]   = spi_mosi;
+   assign flash_io[1]   = 1'bz;
+   assign spi_miso      = flash_io[1];
+   assign flash_cs_n    = spi_cs_esp_n;
+
+   assign esp_int        = 1'b0;	// Signal FPGA ready
+   assign spi_cs_flash_n = board_id;
+
+   assign abc_host_v1   = v1 ? 1'b0 : 1'bz;
+   assign abc_host_v12  = 1'b0;
+   assign abc_d_oe      = 1'b0;
+
+   assign abc_int800_x = v1 ? 1'b0 : 1'bz;
+   assign abc_int80_x  = v1 ? 1'b0 : 1'bz;
+   assign abc_nmi_x    = v1 ? 1'b0 : 1'bz;
+   assign abc_rdy_x    = v1 ? 1'b0 : 1'bz;
+   assign abc_resin_x  = v1 ? 1'b0 : 1'bz;
+   assign abc_xm_x     = v1 ? 1'b0 : 1'bz;
+   assign abc_int800_x = v1 ? 1'b0 : 1'bz;
+
+
+   reg [12:0]		   blink;
+   always @(negedge rtc_32khz)
+     blink <= blink + 1'b1;
+
+   assign led = {3{blink[12]}};
+endmodule // bypass

+ 1 - 0
fpga/bypass_description.txt

@@ -0,0 +1 @@
+Bypass FPGA to access FPGA flash from ESP

+ 3 - 2
fpga/max80.qpf

@@ -19,14 +19,15 @@
 #
 # Quartus Prime
 # Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 01:21:21  March 05, 2022
+# Date created = 01:33:58  April 12, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "01:21:21  March 05, 2022"
+DATE = "01:33:58  April 12, 2022"
 
 # Revisions
 
+PROJECT_REVISION = "bypass"
 PROJECT_REVISION = "v2"
 PROJECT_REVISION = "v1"

+ 24 - 44
fpga/max80.sv

@@ -858,53 +858,33 @@ module max80
 	     .periodic ( iodev_irq_sysclock )
 	     );
 
-   // SPI bus to ESP32; using the sdcard IP as a SPI master for now at
-   // least...
-`ifdef REALLY_ESP32
    // ESP32
    assign spi_cs_flash_n = 1'bz;
-   assign esp_io0  = 1'b1;	 // If pulled down on reset, ESP32 will enter
-				 // firmware download mode
 
-   sdcard #(
-	    .with_irq_mask ( 8'b0000_0101 ),
-	    .with_crc7     ( 1'b0 ),
-	    .with_crc16    ( 1'b0 )
-	    )
-   esp (
-	.rst_n    ( rst_n ),
-
-	.clk      ( sys_clk ),
-	.sd_cs_n  ( spi_cs_esp_n ),
-	.sd_di    ( spi_mosi ),
-	.sd_sclk  ( spi_clk ),
-	.sd_do    ( spi_miso ),
-	.sd_cd_n  ( 1'b0 ),
-	.sd_irq_n ( esp_int ),
-
-	.wdata  ( cpu_mem_wdata ),
-	.rdata  ( iodev_rdata_esp ),
-	.valid  ( iodev_valid_esp ),
-	.wstrb  ( cpu_mem_wstrb ),
-	.addr   ( cpu_mem_addr[6:2] ),
-	.wait_n ( iodev_wait_n_esp ),
-	.irq	( iodev_irq_esp )
-	);
-`else // !`ifdef REALLY_ESP32
-   reg [5:-13] esp_ctr;		// 32768 * 2^-13 = 4 Hz
-
-   always @(posedge clk_32kHz)
-     esp_ctr <= esp_ctr + 1'b1;
-
-   assign spi_clk        = esp_ctr[0];
-   assign spi_mosi       = esp_ctr[1];
-   assign spi_miso       = esp_ctr[2];
-   assign spi_cs_flash_n = esp_ctr[3]; // IO01
-   assign spi_cs_esp_n   = esp_ctr[4]; // IO10
-   assign esp_int        = esp_ctr[5]; // IO09
-   assign esp_io0        = 1'b1;
-
-`endif
+   esp esp (
+	    .rst_n    ( rst_n ),
+	    .clk      ( sys_clk ),
+
+	    .cpu_valid ( iodev_valid_esp ),
+	    .cpu_addr  ( cpu_mem_addr[6:2] ),
+	    .cpu_wstrb ( cpu_mem_wstrb ),
+	    .cpu_wdata ( cpu_mem_wdata ),
+	    .cpu_rdata ( iodev_rdata_esp ),
+	    .irq       ( iodev_irq_esp ),
+
+	    .tty_rx   ( ),
+	    .tty_tx   ( ),
+
+	    .esp_en   ( ),
+	    .esp_int  ( esp_int ),
+	    .esp_io0  ( esp_io0 ),
+
+	    .spi_clk  ( spi_clk ),
+	    .spi_miso ( spi_miso ),
+	    .spi_mosi ( spi_mosi ),
+	    .spi_cs_esp_n ( spi_cs_esp_n ),
+	    .spi_cs_flash_n ( spi_cs_flash_n )
+	    );
 
    //
    // I2C bus (RTC and to connector)

BIN
fpga/output/bypass.jic


+ 327 - 0
fpga/output/bypass.pin

@@ -0,0 +1,327 @@
+ -- Copyright (C) 2021  Intel Corporation. All rights reserved.
+ -- Your use of Intel Corporation's design tools, logic functions 
+ -- and other software and tools, and any partner logic 
+ -- functions, and any output files from any of the foregoing 
+ -- (including device programming or simulation files), and any 
+ -- associated documentation or information are expressly subject 
+ -- to the terms and conditions of the Intel Program License 
+ -- Subscription Agreement, the Intel Quartus Prime License Agreement,
+ -- the Intel FPGA IP License Agreement, or other applicable license
+ -- agreement, including, without limitation, that your use is for
+ -- the sole purpose of programming logic devices manufactured by
+ -- Intel and sold by Intel or its authorized distributors.  Please
+ -- refer to the applicable agreement for further details, at
+ -- https://fpgasoftware.intel.com/eula.
+ -- 
+ -- This is a Quartus Prime output file. It is for reporting purposes only, and is
+ -- not intended for use as a Quartus Prime input file. This file cannot be used
+ -- to make Quartus Prime pin assignments - for instructions on how to make pin
+ -- assignments, please see Quartus Prime help.
+ ---------------------------------------------------------------------------------
+
+
+
+ ---------------------------------------------------------------------------------
+ -- NC            : No Connect. This pin has no internal connection to the device.
+ -- DNU           : Do Not Use. This pin MUST NOT be connected.
+ -- VCCINT        : Dedicated power pin, which MUST be connected to VCC  (1.2V).
+ -- VCCIO         : Dedicated power pin, which MUST be connected to VCC
+ --                 of its bank.
+ --                  Bank 1:       3.3V
+ --                  Bank 2:       3.3V
+ --                  Bank 3:       3.3V
+ --                  Bank 4:       3.3V
+ --                  Bank 5:       2.5V
+ --                  Bank 6:       3.3V
+ --                  Bank 7:       3.3V
+ --                  Bank 8:       3.3V
+ -- GND           : Dedicated ground pin. Dedicated GND pins MUST be connected to GND.
+ --                  It can also be used to report unused dedicated pins. The connection
+ --                  on the board for unused dedicated pins depends on whether this will
+ --                  be used in a future design. One example is device migration. When
+ --                  using device migration, refer to the device pin-tables. If it is a
+ --                  GND pin in the pin table or if it will not be used in a future design
+ --                  for another purpose the it MUST be connected to GND. If it is an unused
+ --                  dedicated pin, then it can be connected to a valid signal on the board
+ --                  (low, high, or toggling) if that signal is required for a different
+ --                  revision of the design.
+ -- GND+          : Unused input pin. It can also be used to report unused dual-purpose pins.
+ --                  This pin should be connected to GND. It may also be connected  to a
+ --                  valid signal  on the board  (low, high, or toggling)  if that signal
+ --                  is required for a different revision of the design.
+ -- GND*          : Unused  I/O  pin. Connect each pin marked GND* directly to GND
+ --                  or leave it unconnected.
+ -- RESERVED      : Unused I/O pin, which MUST be left unconnected.
+ -- RESERVED_INPUT    : Pin is tri-stated and should be connected to the board.
+ -- RESERVED_INPUT_WITH_WEAK_PULLUP    : Pin is tri-stated with internal weak pull-up resistor.
+ -- RESERVED_INPUT_WITH_BUS_HOLD       : Pin is tri-stated with bus-hold circuitry.
+ -- RESERVED_OUTPUT_DRIVEN_HIGH        : Pin is output driven high.
+ ---------------------------------------------------------------------------------
+
+
+
+ ---------------------------------------------------------------------------------
+ -- Pin directions (input, output or bidir) are based on device operating in user mode.
+ ---------------------------------------------------------------------------------
+
+Quartus Prime Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
+CHIP  "bypass"  ASSIGNED TO AN: EP4CE15F17C8
+
+Pin Name/Usage               : Location  : Dir.   : I/O Standard      : Voltage : I/O Bank  : User Assignment
+-------------------------------------------------------------------------------------------------------------
+VCCIO8                       : A1        : power  :                   : 3.3V    : 8         :                
+abc_int800_x                 : A2        : output : 3.3-V LVTTL       :         : 8         : Y              
+abc_nmi_x                    : A3        : output : 3.3-V LVTTL       :         : 8         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : A4        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A5        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A6        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A7        :        :                   :         : 8         :                
+GND+                         : A8        :        :                   :         : 8         :                
+GND+                         : A9        :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A10       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A11       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A12       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A13       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A14       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : A15       :        :                   :         : 7         :                
+VCCIO7                       : A16       : power  :                   : 3.3V    : 7         :                
+abc_xm_x                     : B1        : output : 3.3-V LVTTL       :         : 1         : Y              
+GND                          : B2        : gnd    :                   :         :           :                
+abc_int80_x                  : B3        : output : 3.3-V LVTTL       :         : 8         : Y              
+abc_rdy_x                    : B4        : output : 3.3-V LVTTL       :         : 8         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : B5        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B6        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B7        :        :                   :         : 8         :                
+GND+                         : B8        :        :                   :         : 8         :                
+GND+                         : B9        :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B10       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B11       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B12       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B13       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B14       :        :                   :         : 7         :                
+GND                          : B15       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : B16       :        :                   :         : 6         :                
+flash_io[0]                  : C1        : bidir  : 3.3-V LVTTL       :         : 1         : Y              
+abc_host_v12                 : C2        : output : 3.3-V LVTTL       :         : 1         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : C3        :        :                   :         : 8         :                
+VCCIO8                       : C4        : power  :                   : 3.3V    : 8         :                
+GND                          : C5        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C6        :        :                   :         : 8         :                
+VCCIO8                       : C7        : power  :                   : 3.3V    : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C8        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C9        :        :                   :         : 7         :                
+VCCIO7                       : C10       : power  :                   : 3.3V    : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C11       :        :                   :         : 7         :                
+GND                          : C12       : gnd    :                   :         :           :                
+VCCIO7                       : C13       : power  :                   : 3.3V    : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C14       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C15       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : C16       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D1        :        :                   :         : 1         :                
+flash_cs_n                   : D2        : output : 3.3-V LVTTL       :         : 1         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : D3        :        :                   :         : 8         :                
+VCCD_PLL3                    : D4        : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D5        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D6        :        :                   :         : 8         :                
+GND                          : D7        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D8        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D9        :        :                   :         : 7         :                
+GND                          : D10       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D11       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D12       :        :                   :         : 7         :                
+VCCD_PLL2                    : D13       : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D14       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D15       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : D16       :        :                   :         : 6         :                
+GND+                         : E1        :        :                   :         : 1         :                
+GND                          : E2        : gnd    :                   :         :           :                
+VCCIO1                       : E3        : power  :                   : 3.3V    : 1         :                
+GND                          : E4        : gnd    :                   :         :           :                
+GNDA3                        : E5        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E6        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E7        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E8        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E9        :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E10       :        :                   :         : 7         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : E11       :        :                   :         : 7         :                
+GNDA2                        : E12       : gnd    :                   :         :           :                
+GND                          : E13       : gnd    :                   :         :           :                
+VCCIO6                       : E14       : power  :                   : 3.3V    : 6         :                
+rtc_32khz                    : E15       : input  : 3.3-V LVTTL       :         : 6         : Y              
+GND+                         : E16       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F1        :        :                   :         : 1         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F2        :        :                   :         : 1         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F3        :        :                   :         : 1         :                
+nSTATUS                      : F4        :        :                   :         : 1         :                
+VCCA3                        : F5        : power  :                   : 2.5V    :           :                
+GND                          : F6        : gnd    :                   :         :           :                
+VCCINT                       : F7        : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F8        :        :                   :         : 8         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F9        :        :                   :         : 7         :                
+GND                          : F10       : gnd    :                   :         :           :                
+VCCINT                       : F11       : power  :                   : 1.2V    :           :                
+VCCA2                        : F12       : power  :                   : 2.5V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F13       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F14       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F15       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : F16       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G1        :        :                   :         : 1         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G2        :        :                   :         : 1         :                
+VCCIO1                       : G3        : power  :                   : 3.3V    : 1         :                
+GND                          : G4        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G5        :        :                   :         : 1         :                
+VCCINT                       : G6        : power  :                   : 1.2V    :           :                
+VCCINT                       : G7        : power  :                   : 1.2V    :           :                
+VCCINT                       : G8        : power  :                   : 1.2V    :           :                
+VCCINT                       : G9        : power  :                   : 1.2V    :           :                
+VCCINT                       : G10       : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G11       :        :                   :         : 6         :                
+MSEL2                        : G12       :        :                   :         : 6         :                
+GND                          : G13       : gnd    :                   :         :           :                
+VCCIO6                       : G14       : power  :                   : 3.3V    : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G15       :        :                   :         : 6         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : G16       :        :                   :         : 6         :                
+flash_sck                    : H1        : output : 3.3-V LVTTL       :         : 1         : Y              
+flash_io[1]                  : H2        : bidir  : 3.3-V LVTTL       :         : 1         : Y              
+TCK                          : H3        : input  :                   :         : 1         :                
+TDI                          : H4        : input  :                   :         : 1         :                
+nCONFIG                      : H5        :        :                   :         : 1         :                
+VCCINT                       : H6        : power  :                   : 1.2V    :           :                
+GND                          : H7        : gnd    :                   :         :           :                
+GND                          : H8        : gnd    :                   :         :           :                
+GND                          : H9        : gnd    :                   :         :           :                
+GND                          : H10       : gnd    :                   :         :           :                
+VCCINT                       : H11       : power  :                   : 1.2V    :           :                
+MSEL1                        : H12       :        :                   :         : 6         :                
+MSEL0                        : H13       :        :                   :         : 6         :                
+CONF_DONE                    : H14       :        :                   :         : 6         :                
+GND                          : H15       : gnd    :                   :         :           :                
+GND                          : H16       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J1        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J2        :        :                   :         : 2         :                
+nCE                          : J3        :        :                   :         : 1         :                
+TDO                          : J4        : output :                   :         : 1         :                
+TMS                          : J5        : input  :                   :         : 1         :                
+VCCINT                       : J6        : power  :                   : 1.2V    :           :                
+GND                          : J7        : gnd    :                   :         :           :                
+GND                          : J8        : gnd    :                   :         :           :                
+GND                          : J9        : gnd    :                   :         :           :                
+GND                          : J10       : gnd    :                   :         :           :                
+GND                          : J11       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J12       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J13       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J14       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J15       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : J16       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K1        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K2        :        :                   :         : 2         :                
+VCCIO2                       : K3        : power  :                   : 3.3V    : 2         :                
+GND                          : K4        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K5        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K6        :        :                   :         : 2         :                
+VCCINT                       : K7        : power  :                   : 1.2V    :           :                
+GND                          : K8        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K9        :        :                   :         : 4         :                
+board_id                     : K10       : input  : 3.3-V LVTTL       :         : 4         : Y              
+VCCINT                       : K11       : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K12       :        :                   :         : 5         :                
+GND                          : K13       : gnd    :                   :         :           :                
+VCCIO5                       : K14       : power  :                   : 2.5V    : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K15       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : K16       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L1        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L2        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L3        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L4        :        :                   :         : 2         :                
+VCCA1                        : L5        : power  :                   : 2.5V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L6        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L7        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L8        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L9        :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L10       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L11       :        :                   :         : 4         :                
+VCCA4                        : L12       : power  :                   : 2.5V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L13       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L14       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L15       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : L16       :        :                   :         : 5         :                
+GND+                         : M1        :        :                   :         : 2         :                
+GND+                         : M2        :        :                   :         : 2         :                
+VCCIO2                       : M3        : power  :                   : 3.3V    : 2         :                
+GND                          : M4        : gnd    :                   :         :           :                
+GNDA1                        : M5        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : M6        :        :                   :         : 3         :                
+spi_miso                     : M7        : output : 3.3-V LVTTL       :         : 3         : Y              
+spi_mosi                     : M8        : input  : 3.3-V LVTTL       :         : 3         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : M9        :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : M10       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : M11       :        :                   :         : 4         :                
+GNDA4                        : M12       : gnd    :                   :         :           :                
+GND                          : M13       : gnd    :                   :         :           :                
+VCCIO5                       : M14       : power  :                   : 2.5V    : 5         :                
+clock_in                     : M15       : input  : 3.3-V LVTTL       :         : 5         : Y              
+GND+                         : M16       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N1        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N2        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N3        :        :                   :         : 3         :                
+VCCD_PLL1                    : N4        : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N5        :        :                   :         : 3         :                
+spi_cs_flash_n               : N6        : output : 3.3-V LVTTL       :         : 3         : Y              
+GND                          : N7        : gnd    :                   :         :           :                
+spi_cs_esp_n                 : N8        : input  : 3.3-V LVTTL       :         : 3         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : N9        :        :                   :         : 4         :                
+GND                          : N10       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N11       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N12       :        :                   :         : 4         :                
+VCCD_PLL4                    : N13       : power  :                   : 1.2V    :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N14       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N15       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : N16       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : P1        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : P2        :        :                   :         : 2         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : P3        :        :                   :         : 3         :                
+VCCIO3                       : P4        : power  :                   : 3.3V    : 3         :                
+GND                          : P5        : gnd    :                   :         :           :                
+spi_clk                      : P6        : input  : 3.3-V LVTTL       :         : 3         : Y              
+VCCIO3                       : P7        : power  :                   : 3.3V    : 3         :                
+esp_int                      : P8        : output : 3.3-V LVTTL       :         : 3         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : P9        :        :                   :         : 4         :                
+VCCIO4                       : P10       : power  :                   : 3.3V    : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : P11       :        :                   :         : 4         :                
+GND                          : P12       : gnd    :                   :         :           :                
+VCCIO4                       : P13       : power  :                   : 3.3V    : 4         :                
+led_1_v2                     : P14       : output : 3.3-V LVTTL       :         : 4         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : P15       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : P16       :        :                   :         : 5         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R1        :        :                   :         : 2         :                
+GND                          : R2        : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R3        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R4        :        :                   :         : 3         :                
+abc_d_oe                     : R5        : output : 3.3-V LVTTL       :         : 3         : Y              
+abc_resin_x                  : R6        : output : 3.3-V LVTTL       :         : 3         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : R7        :        :                   :         : 3         :                
+GND+                         : R8        :        :                   :         : 3         :                
+GND+                         : R9        :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R10       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R11       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R12       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R13       :        :                   :         : 4         :                
+led_0                        : R14       : output : 3.3-V LVTTL       :         : 4         : Y              
+GND                          : R15       : gnd    :                   :         :           :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : R16       :        :                   :         : 5         :                
+VCCIO3                       : T1        : power  :                   : 3.3V    : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T2        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T3        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T4        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T5        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T6        :        :                   :         : 3         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T7        :        :                   :         : 3         :                
+GND+                         : T8        :        :                   :         : 3         :                
+GND+                         : T9        :        :                   :         : 4         :                
+abc_host_v1                  : T10       : output : 3.3-V LVTTL       :         : 4         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : T11       :        :                   :         : 4         :                
+RESERVED_INPUT_WITH_WEAK_PULLUP : T12       :        :                   :         : 4         :                
+led_2                        : T13       : output : 3.3-V LVTTL       :         : 4         : Y              
+led_1_v1                     : T14       : output : 3.3-V LVTTL       :         : 4         : Y              
+RESERVED_INPUT_WITH_WEAK_PULLUP : T15       :        :                   :         : 4         :                
+VCCIO4                       : T16       : power  :                   : 3.3V    : 4         :                

BIN
fpga/output/bypass.rbf.gz


BIN
fpga/output/bypass.rpd.gz


BIN
fpga/output/bypass.sof


BIN
fpga/output/bypass.svf.gz


BIN
fpga/output/bypass.xsvf.gz


BIN
fpga/output/jtagupd/v1.rbf.gz


BIN
fpga/output/jtagupd/v1.sof


BIN
fpga/output/jtagupd/v1.svf.gz


BIN
fpga/output/jtagupd/v2.rbf.gz


BIN
fpga/output/jtagupd/v2.sof


BIN
fpga/output/jtagupd/v2.svf.gz


BIN
fpga/output/v1.fw


BIN
fpga/output/v1.jic


BIN
fpga/output/v1.rbf.gz


BIN
fpga/output/v1.rpd.gz


BIN
fpga/output/v1.sof


BIN
fpga/output/v1.svf.gz


BIN
fpga/output/v1.update.svf.gz


BIN
fpga/output/v1.update.xsvf.gz


BIN
fpga/output/v1.xsvf.gz


BIN
fpga/output/v2.fw


BIN
fpga/output/v2.jic


BIN
fpga/output/v2.rbf.gz


BIN
fpga/output/v2.rpd.gz


BIN
fpga/output/v2.sof


BIN
fpga/output/v2.svf.gz


BIN
fpga/output/v2.update.svf.gz


BIN
fpga/output/v2.update.xsvf.gz


BIN
fpga/output/v2.xsvf.gz


+ 26 - 23
fpga/serial.sv

@@ -21,28 +21,29 @@ module serial
     parameter        BREAK_BITS        = 16 // Bit times for BREAK detect
     )
    (
-    input tri1		 rst_n,
-    input		 clk,
-
-    output		 tty_tx,
-    input		 tty_rx,
-
-    input		 tx_wstrb,
-    input [7:0]		 tx_data,
-    input		 tx_break, // BREAK is asserted as long as this is true
-    output		 tx_full,
-    output		 tx_empty,
-    input tri0		 tx_flush,
-
-    input		 rx_rstrb,
-    output [7:0]	 rx_data,
-    output		 rx_break,
-    output		 rx_full,
-    output		 rx_empty,
-    input tri0		 rx_flush,
-
-    input [NCO_BITS-1:0] divisor, // If divisor is settable
-    input tri0		 divisor_wstrb
+    input tri1	  rst_n,
+    input	  clk,
+
+    output	  tty_tx,
+    input	  tty_rx,
+
+    input	  tx_wstrb,
+    input [7:0]   tx_data,
+    input	  tx_break, // BREAK is asserted as long as this is true
+    output	  tx_full,
+    output	  tx_empty,
+    input tri0	  tx_flush,
+
+    input	  rx_rstrb,
+    output [7:0]  rx_data,
+    output	  rx_break,
+    output	  rx_full,
+    output	  rx_empty,
+    input tri0	  rx_flush,
+
+    input [31:0]  divisor_wdata, // If divisor is settable
+    input tri0	  divisor_wstrb,
+    output [31:0] divisor
     );
 
    `include "functions.sv"	// For ModelSim
@@ -64,6 +65,8 @@ module serial
    reg  [NCO_BITS-1:0]  nco_q;
    reg			tty_clk_en; // tty clock tick (clock enable)
 
+   assign divisor = divisor_q;
+
    always @(posedge clk)
      { tty_clk_en, nco_q } <= nco_q + divisor_q + 1'b1;
 
@@ -71,7 +74,7 @@ module serial
      if (~rst_n)
        divisor_q <= default_divisor;
      else if ( BAUDRATE_SETTABLE & divisor_wstrb )
-       divisor_q <= divisor;
+       divisor_q <= divisor_wdata[NCO_BITS-1:0];
 
    //
    // **** Transmitter section ****

+ 193 - 0
fpga/v2.qsf

@@ -5,6 +5,199 @@ set_global_assignment -name SOURCE_FILE output/v2.jic.cof
 set_global_assignment -name SYSTEMVERILOG_FILE v2.sv
 
 set_global_assignment -name SOURCE_TCL_SCRIPT_FILE v2_common.qsf
+set_global_assignment -name SOURCE_FILE v2.pins
+set_global_assignment -name VERILOG_FILE ip/pll2_16.v
+set_global_assignment -name QIP_FILE ip/pll2_16.qip
+set_global_assignment -name VERILOG_INCLUDE_FILE v2.vh
+set_global_assignment -name SOURCE_TCL_SCRIPT_FILE max80.qsf
+set_global_assignment -name FAMILY "Cyclone IV E"
+set_global_assignment -name DEVICE EP4CE15F17C8
+set_global_assignment -name ORIGINAL_QUARTUS_VERSION 21.1.0
+set_global_assignment -name PROJECT_CREATION_TIME_DATE "16:21:14  DECEMBER 22, 2021"
+set_global_assignment -name LAST_QUARTUS_VERSION "21.1.0 Lite Edition"
+set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output
+set_global_assignment -name MIN_CORE_JUNCTION_TEMP 0
+set_global_assignment -name MAX_CORE_JUNCTION_TEMP 85
+set_global_assignment -name DEVICE_FILTER_PACKAGE EQFP
+set_global_assignment -name DEVICE_FILTER_PIN_COUNT 144
+set_global_assignment -name DEVICE_FILTER_SPEED_GRADE 8
+set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR 256
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_timing
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_symbol
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_signal_integrity
+set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_boundary_scan
+set_global_assignment -name DEVICE_MIGRATION_LIST EP4CE15F17C8
+set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top
+set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top
+set_global_assignment -name VCCA_USER_VOLTAGE 2.5V
+set_global_assignment -name POWER_PRESET_COOLING_SOLUTION "NO HEAT SINK WITH STILL AIR"
+set_global_assignment -name POWER_BOARD_THERMAL_MODEL "NONE (CONSERVATIVE)"
+set_global_assignment -name POWER_DEFAULT_INPUT_IO_TOGGLE_RATE "12.5 %"
+set_global_assignment -name VHDL_INPUT_VERSION VHDL_2008
+set_global_assignment -name VHDL_SHOW_LMF_MAPPING_MESSAGES OFF
+set_global_assignment -name VERILOG_INPUT_VERSION SYSTEMVERILOG_2005
+set_global_assignment -name VERILOG_SHOW_LMF_MAPPING_MESSAGES OFF
+set_global_assignment -name REMOVE_REDUNDANT_LOGIC_CELLS ON
+set_global_assignment -name HDL_MESSAGE_LEVEL LEVEL3
+set_global_assignment -name SYNTH_PROTECT_SDC_CONSTRAINT ON
+set_global_assignment -name SYNTH_MESSAGE_LEVEL HIGH
+set_global_assignment -name OPTIMIZE_IOC_REGISTER_PLACEMENT_FOR_TIMING "PACK ALL IO REGISTERS"
+set_global_assignment -name MUX_RESTRUCTURE AUTO
+set_global_assignment -name WEAK_PULL_UP_RESISTOR ON
+set_global_assignment -name ENABLE_OCT_DONE OFF
+set_global_assignment -name ENABLE_CONFIGURATION_PINS OFF
+set_global_assignment -name ENABLE_BOOT_SEL_PIN OFF
+set_global_assignment -name USE_CONFIGURATION_DEVICE ON
+set_global_assignment -name INTERNAL_FLASH_UPDATE_MODE "SINGLE COMP IMAGE"
+set_global_assignment -name STRATIXIII_UPDATE_MODE REMOTE
+set_global_assignment -name CRC_ERROR_OPEN_DRAIN OFF
+set_global_assignment -name GENERATE_JBC_FILE ON
+set_global_assignment -name STRATIX_DEVICE_IO_STANDARD "3.3-V LVTTL"
+set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -rise
+set_global_assignment -name OUTPUT_IO_TIMING_NEAR_END_VMEAS "HALF VCCIO" -fall
+set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -rise
+set_global_assignment -name OUTPUT_IO_TIMING_FAR_END_VMEAS "HALF SIGNAL SWING" -fall
+set_instance_assignment -name CURRENT_STRENGTH_NEW "MAXIMUM CURRENT" -to sr_clk
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to clock_*
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 6
+set_global_assignment -name IOBANK_VCCIO 2.5V -section_id 5
+set_instance_assignment -name IO_STANDARD LVDS -to hdmi_d[2]
+set_instance_assignment -name IO_STANDARD LVDS -to hdmi_d[1]
+set_instance_assignment -name IO_STANDARD LVDS -to hdmi_d[0]
+set_instance_assignment -name IO_STANDARD LVDS -to hdmi_clk
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to hdmi_clk
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to hdmi_d[2]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to hdmi_d[1]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to hdmi_d[0]
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 2
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 1
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 8
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 7
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 4
+set_global_assignment -name IOBANK_VCCIO 3.3V -section_id 3
+set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DATA0_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DATA1_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_FLASH_NCE_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_global_assignment -name RESERVE_DCLK_AFTER_CONFIGURATION "USE AS REGULAR IO"
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to flash_clk
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to flash_cs_n
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to board_id
+set_instance_assignment -name CURRENT_STRENGTH_NEW "MAXIMUM CURRENT" -to led[1]
+set_global_assignment -name CYCLONEIII_CONFIGURATION_DEVICE EPCQ128A
+set_global_assignment -name FORCE_CONFIGURATION_VCCIO ON
+set_global_assignment -name CONFIGURATION_VCCIO_LEVEL 3.3V
+set_global_assignment -name RESERVE_ALL_UNUSED_PINS "AS INPUT TRI-STATED WITH WEAK PULL-UP"
+set_global_assignment -name PRE_FLOW_SCRIPT_FILE "quartus_sh:scripts/preflow.tcl"
+set_global_assignment -name POST_MODULE_SCRIPT_FILE "quartus_sh:scripts/postmodule.tcl"
+set_global_assignment -name OPTIMIZATION_MODE "AGGRESSIVE PERFORMANCE"
+set_global_assignment -name AUTO_RAM_TO_LCELL_CONVERSION ON
+set_global_assignment -name SYNTH_GATED_CLOCK_CONVERSION ON
+set_global_assignment -name PRE_MAPPING_RESYNTHESIS ON
+set_global_assignment -name ROUTER_CLOCKING_TOPOLOGY_ANALYSIS ON
+set_global_assignment -name QII_AUTO_PACKED_REGISTERS "SPARSE AUTO"
+set_global_assignment -name SAVE_DISK_SPACE OFF
+set_global_assignment -name TIMING_ANALYZER_MULTICORNER_ANALYSIS ON
+set_global_assignment -name SMART_RECOMPILE ON
+set_global_assignment -name EDA_TEST_BENCH_NAME testclk -section_id eda_simulation
+set_global_assignment -name EDA_DESIGN_INSTANCE_NAME max80 -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_RUN_SIM_FOR "1 ms" -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_MODULE_NAME testclk -section_id testclk
+set_global_assignment -name EDA_TEST_BENCH_FILE simulation/testclk.sv -section_id testclk
+set_global_assignment -name EDA_NATIVELINK_PORTABLE_FILE_PATHS ON -section_id eda_simulation
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to rtc_32khz
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to exth_hc
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to exth_hh
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tdo
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tck
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tdi
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to altera_reserved_tms
+set_global_assignment -name OCP_HW_EVAL DISABLE
+set_global_assignment -name TIMING_ANALYZER_DO_REPORT_TIMING ON
+set_global_assignment -name READ_OR_WRITE_IN_BYTE_ADDRESS ON
+set_global_assignment -name POWER_REPORT_POWER_DISSIPATION ON
+set_global_assignment -name POWER_USE_DEVICE_CHARACTERISTICS MAXIMUM
+set_global_assignment -name POWER_USE_TA_VALUE 35
+set_location_assignment PLL_3 -to "max80:max80|pll3:pll3|altpll:altpll_component|pll3_altpll:auto_generated|pll1"
+set_location_assignment PLL_4 -to "max80:max80|pll4:pll4|altpll:altpll_component|pll4_altpll:auto_generated|pll1"
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[15]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[14]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[13]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[12]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[11]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[10]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[9]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[8]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[7]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[6]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[5]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[4]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[3]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[2]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[1]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to sr_dq[0]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to rngio[0]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to rngio[1]
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to rngio[2]
+set_global_assignment -name VERILOG_FILE usb/usb_desc.v
+set_global_assignment -name SYSTEMVERILOG_FILE usb/usb_serial/src_v/usb_cdc_core.sv
+set_global_assignment -name SYSTEMVERILOG_FILE usb/usb_serial/src_v/usbf_device_core.sv
+set_global_assignment -name SYSTEMVERILOG_FILE rng.sv
+set_global_assignment -name QIP_FILE ip/int_osc/synthesis/int_osc.qip
+set_global_assignment -name VERILOG_FILE ip/pll4.v
+set_global_assignment -name VERILOG_FILE ip/pll3.v
+set_global_assignment -name VERILOG_FILE usb/usb_fs_phy/src_v/usb_fs_phy.v
+set_global_assignment -name VERILOG_FILE usb/usb_serial/src_v/usbf_sie_tx.v
+set_global_assignment -name VERILOG_FILE usb/usb_serial/src_v/usbf_sie_rx.v
+set_global_assignment -name VERILOG_FILE usb/usb_serial/src_v/usbf_defs.v
+set_global_assignment -name VERILOG_FILE usb/usb_serial/src_v/usbf_crc16.v
+set_global_assignment -name SYSTEMVERILOG_FILE usb/usb.sv
+set_global_assignment -name VERILOG_FILE ip/statusram.v
+set_global_assignment -name VERILOG_INCLUDE_FILE iodevs.vh
+set_global_assignment -name SYSTEMVERILOG_FILE serial.sv
+set_global_assignment -name SYSTEMVERILOG_FILE sdcard.sv
+set_global_assignment -name SYSTEMVERILOG_FILE sysclock.sv
+set_global_assignment -name SYSTEMVERILOG_FILE i2c.sv
+set_global_assignment -name SYSTEMVERILOG_FILE abcbus.sv
+set_global_assignment -name VERILOG_FILE ip/abcmapram.v
+set_global_assignment -name SYSTEMVERILOG_FILE fast_mem.sv
+set_global_assignment -name MIF_FILE mif/sram.mif
+set_global_assignment -name VERILOG_FILE picorv32.v
+set_global_assignment -name SYSTEMVERILOG_FILE functions.sv
+set_global_assignment -name SYSTEMVERILOG_FILE spi_master.sv
+set_global_assignment -name SYSTEMVERILOG_FILE sdram.sv
+set_global_assignment -name SYSTEMVERILOG_FILE spirom.sv
+set_global_assignment -name SYSTEMVERILOG_FILE clkbuf.sv
+set_global_assignment -name VERILOG_FILE ip/ddio_out.v
+set_global_assignment -name TCL_SCRIPT_FILE scripts/post_quartus_asm.tcl
+set_global_assignment -name TCL_SCRIPT_FILE scripts/postmodule.tcl
+set_global_assignment -name VERILOG_FILE ip/hdmitx.v
+set_global_assignment -name SYSTEMVERILOG_FILE transpose.sv
+set_global_assignment -name SYSTEMVERILOG_FILE synchro.sv
+set_global_assignment -name SYSTEMVERILOG_FILE tmdsenc.sv
+set_global_assignment -name SYSTEMVERILOG_FILE video.sv
+set_global_assignment -name SDC_FILE max80.sdc
+set_global_assignment -name SYSTEMVERILOG_FILE max80.sv
+set_global_assignment -name SOURCE_TCL_SCRIPT_FILE scripts/pins.tcl
+set_global_assignment -name VERILOG_FILE ip/fifo.v
+set_global_assignment -name VERILOG_FILE ip/ddufifo.v
+set_global_assignment -name VERILOG_FILE ip/cdc_txfifo.v
+set_global_assignment -name VERILOG_FILE ip/cdc_rxfifo.v
+set_global_assignment -name QIP_FILE ip/cdc_txfifo.qip
+set_global_assignment -name QIP_FILE ip/cdc_rxfifo.qip
+set_global_assignment -name SYSTEMVERILOG_FILE vjtag_max80.sv
+set_global_assignment -name VERILOG_FILE ip/vjtag/synthesis/vjtag.v
+set_global_assignment -name QIP_FILE ip/vjtag/synthesis/vjtag.qip
+set_global_assignment -name SYSTEMVERILOG_FILE fpgarst.sv
+set_global_assignment -name VERILOG_FILE ip/altera_remote_update_core.v
+set_instance_assignment -name IO_STANDARD "BUS LVDS" -to usb_rx
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to usb_rx
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to usb_dp
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to usb_dn
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR OFF -to usb_pu
+set_instance_assignment -name TERMINATION OFF -to usb_rx
+set_instance_assignment -name IO_STANDARD "2.5 V" -to sd_cd_n
+set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to sd_cd_n
 
 # Quartus insists on this line...
 set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top

+ 1 - 1
rv32/checksum.h

@@ -1,4 +1,4 @@
 #ifndef CHECKSUM_H
 #define CHECKSUM_H
-#define SDRAM_SUM 0x79caa016
+#define SDRAM_SUM 0x874aa99a
 #endif

+ 15 - 4
rv32/compiler.h

@@ -12,11 +12,22 @@
 /* Use builtin memcpy and memset optimizations */
 #define memset(s,c,n)	__builtin_memset(s,c,n)
 #define memcpy(d,s,n)	__builtin_memcpy(d,s,n)
+#define mempcpy(d,s,n)	__builtin_mempcpy(d,s,n)
 #define memmove(d,s,n)	__builtin_memmove(d,s,n)
-extern void *__memcpy_aligned(void * __restrict,
-			      const void * __restrict, size_t);
-extern void *__memcpy_bytewise(volatile void * __restrict,
-			       const volatile void * __restrict, size_t);
+
+extern volatile void *memcpy_bytewise(volatile void *dst,
+				      const volatile void *src, size_t len);
+
+/*
+ * The odd argument order allows memcpy() and mempcpy() to be implemented
+ * as tail calls
+ */
+extern void *__memxcpy_aligned(void *retval,
+			      const void * restrict src, size_t len,
+			      void * restrict dst);
+extern void *__memxcpy_bytewise(void *retval,
+				const volatile void * restrict src, size_t len,
+				void * restrict dst);
 
 #define likely(x)	__builtin_expect(!!(x), 1)
 #define unlikely(x)	__builtin_expect(!!(x), 0)

+ 40 - 24
rv32/memcpy.S

@@ -1,28 +1,34 @@
 	.section ".text.hot.memcpy","ax"
 
+	.balign 4
+	.globl	mempcpy
+mempcpy:
+	mv	a3, a0
+	add	a0, a0, a2
+	j	__memxcpy
+	.type mempcpy, @function
+	.size mempcpy, . - mempcpy
+
 	.balign 4
 	.globl	memcpy
 memcpy:
-#if 0
-	j	__memcpy_bytewise
-#else
-	or	a5, a0, a1
-	or	a5, a5, a2
-	andi	a5, a5, 3
-	.option	norvc
-	bnez	a5, __memcpy_bytewise
-	.option rvc
-#endif
-
+	mv	a3, a0
 	.type memcpy, @function
 	.size memcpy, . - memcpy
 
+	.globl	__memxcpy
+__memxcpy:
+	or	a5, a3, a1
+	or	a5, a5, a2
+	andi	a5, a5, 3
+	bnez	a5, __memxcpy_bytewise
 
-	.balign 4
-	.globl __memcpy_aligned
-__memcpy_aligned:
-	add	a4, a0, a2
-	mv	a3, a0		// a0 is also return value
+	.type	__memxcpy, @function
+	.size	__memxcpy, . - __memxcpy
+
+	.globl __memxcpy_aligned
+__memxcpy_aligned:
+	add	a4, a3, a2
 
 	andi	a2, a2, 7*4
 	.option	norelax
@@ -67,15 +73,25 @@ __memcpy_aligned:
 .L_empty:
 	ret
 
-	.type	__memcpy_aligned, @function
-	.size	__memcpy_aligned, . - __memcpy_aligned
+	.type	__memxcpy_aligned, @function
+	.size	__memxcpy_aligned, . - __memxcpy_aligned
 
-	// This can be used by I/O devices that need bytewise accesses
+	//
+	// These can be used by I/O devices that need bytewise access
+	//
 	.balign 4
-__memcpy_bytewise:
+memcpy_bytewise:
+	.option	norvc
+	mv	a0, a3
+	.option	rvc
+
+	.type	memcpy_bytewise, @function
+	.size	memcpy_bytewise, . - memcpy_bytewise
+
+	.balign 4
+__memxcpy_bytewise:
 	.option norvc
-	add	a4, a0, a2
-	mv	a3, a0
+	add	a4, a3, a2
 	.option	rvc
 
 	.balign 4
@@ -88,5 +104,5 @@ __memcpy_bytewise:
 
 	ret
 
-	.type	__memcpy_bytewise, @function
-	.size	__memcpy_bytewise, . - __memcpy_bytewise
+	.type	__memxcpy_bytewise, @function
+	.size	__memxcpy_bytewise, . - __memxcpy_bytewise

+ 160 - 0
tools/mkfwimage.pl

@@ -0,0 +1,160 @@
+#!/usr/bin/perl
+
+use strict;
+use integer;
+use Digest::MD5;
+
+my $FW_MAGIC = 0x7a07fbd6;
+
+my %datatypes = (
+    'end'       => 0,		# End of data
+    'data'      => 1,		# FPGA flash data
+    'target'    => 2,		# Firmware target string
+    'note'      => 3,		# Informative string
+    'espota'    => 4,		# ESP32 OTA image
+    'fpgainit'  => 5		# FPGA bypass (transient) image during update
+    );
+
+my $FDF_OPTIONAL = 0x0001;
+
+my $STRING_MAX_LEN = 4095;
+
+sub getint($) {
+    my($s) = @_;
+
+    return undef
+	unless ($s =~ /^(([1-9][0-9]+)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
+
+    my $o = oct($3) + $2;
+    my $p = lc($5);
+
+    if ($p eq 'k') {
+	$o <<= 10;
+    } elsif ($p eq 'm') {
+	$o <<= 20;
+    } elsif ($p eq 'g') {
+	$o <<= 30;
+    } elsif ($p eq 't') {
+	$o <<= 40;
+    } elsif ($p eq 'p') {
+	$o <<= 50;
+    } elsif ($p eq 'e') {
+	$o <<= 60;
+    }
+    return $o;
+}
+sub filelen($) {
+    my($f) = @_;
+    my @s = stat($f);
+
+    return $s[7];
+}
+sub output_chunk($$$) {
+    my($out,$data,$options) = @_;
+
+    print $out pack("VvvVV", $FW_MAGIC,
+		    $options->{'type'}, $options->{'flags'},
+		    length($data), $options->{'addr'});
+    printf STDERR "chunk: type %u flags 0x%x length %u addr 0x%x\n",
+	$options->{'type'}, $options->{'flags'},
+	length($data), $options->{'addr'};
+    print $out $data;
+}
+
+if (!scalar(@ARGV)) {
+    die "Usage: $0 [-o outfile] [options command]...\n".
+	"Options:\n".
+	"\t-type datatype\n".
+	"\t-addr address (or equivalent)\n".
+	"\t-optional\n".
+	"\t-required\n".
+	"Commands:\n".
+	"\t-file data_file\n".
+	"\t-str data_string\n";
+}
+
+our $outfile;
+our $out;
+
+sub delete_out {
+    close($out);
+    unlink($outfile);
+}
+
+if ($ARGV[0] eq '-o') {
+    shift @ARGV;
+    $outfile = shift @ARGV;
+}
+if ($outfile ne '' && $outfile ne '-') {
+    open($out, '>', $outfile) or
+	die "$0: $outfile: $!\n";
+
+    $SIG{'INT'}     = \&delete_out;
+    $SIG{'QUIT'}    = \&delete_out;
+    $SIG{'TERM'}    = \&delete_out;
+    $SIG{'__DIE__'} = \&delete_out;
+} else {
+    $outfile = '-';
+    $out = \*STDOUT;
+}
+
+binmode $out;
+
+my %default_options = {
+    'type'  => $datatypes{'data'},
+    'addr'  => 0,
+    'flags' => 0
+};
+my $err;
+my %options = %default_options;
+while (1) {
+    my $what = shift @ARGV;
+
+    last if (!defined($what));
+
+    if ($what eq '-type') {
+	my $arg = lc(shift @ARGV);
+	$options{'type'} = $datatypes{$arg} || getint($arg);
+	if (!$arg) {
+	    die "$0: invalid data type: $arg";
+	}
+    } elsif ($what eq '-addr') {
+	my $arg = shift @ARGV;
+	$options{'addr'} = getint($arg);
+    } elsif ($what eq '-optional') {
+	$options{'flags'} |= $FDF_OPTIONAL;
+    } elsif ($what eq '-required') {
+	$options{'flags'} &= ~$FDF_OPTIONAL;
+    } elsif ($what eq '-file') {
+	my $infile = shift @ARGV;
+
+	my $in;
+	if (!open($in, '<', $infile)) {
+	    die "$0: $infile: $!\n";
+	}
+	binmode($in);
+	my @is = stat($in);
+	my $data;
+	my $dlen = read($in, $data, $is[7]);
+	close($in);
+
+	output_chunk($out, $data, \%options);
+	undef $data;
+
+	%options = %default_options;
+    } elsif ($what eq '-str') {
+	my $str = shift @ARGV;
+
+	if (length($str) > $STRING_MAX_LEN) {
+	    die "$0: string too long\n";
+	}
+	
+	output_chunk($out, $str, \%options);
+	%options = %default_options;
+    } else {
+	die "$0: unknown argument: $what\n";
+    }
+}
+
+output_chunk($out, '', {'type' => $datatypes{'end'}});
+close($out);

+ 33 - 14
tools/svf2xsvf.py

@@ -64,6 +64,8 @@ file_encoding = 'utf-8'
 
 xrepeat = 0             # argument to XREPEAT, gives retry count for masked compares
 
+# Maximum size of XSDR[BCE] blocks, in bits (Xilinx specifies 56000)
+xsdrxlimit = 2048
 
 #-----< Lexer >---------------------------------------------------------------
 
@@ -386,7 +388,7 @@ trst_mode_allowed = ('ON', 'OFF', 'Z', 'ABSENT')
 enddr_state = IDLE
 endir_state = IDLE
 
-frequency = 	1.00e+006 # HZ;
+frequency =	1.00e+006 # HZ;
 
 # change detection for xsdrsize and xtdomask
 xsdrsize = -1           # the last one sent, send only on change
@@ -486,21 +488,39 @@ try:
                     # previous value, so do not modify xtdomask here.
                     #
                     # The difference between XSDRB and XSDRC seems to imply
-                    # that the user needs to force the shift-DR state first.
+                    # that the user needs to force the shift-DR state first,
+                    # although in practice interpreters seem to treat them
+                    # identically. It is pretty clear, though, that the
+                    # intended use is Begin, Continuation, End, so use
+                    # them that way.
+                    #
+                    # Note that we take the data from the *end* of the TDI
+                    # bit vector; this is because that is the data that
+                    # actually is to be sent first.
                     tdi  = combineBitVectors( tdr.tdi,  sdr.tdi,  hdr.tdi )
 
-                    if xsdrsize != len(tdi):
-                        xsdrsize = len(tdi)
-                        cmdbuf[0] = XSDRSIZE
+                    opcode = XSDRB
+                    tdibits = len(tdi)
+
+                    while tdibits > 0:
+                        xsdrchunk = xsdrxlimit
+                        if tdibits <= xsdrchunk:
+                            xsdrchunk = tdibits
+                            opcode = XSDRE
+                        if xsdrsize != xsdrchunk:
+                            xsdrsize = xsdrchunk
+                            cmdbuf[0] = XSDRSIZE
+                            output.write( cmdbuf )
+                            obuf = bytearray(4)
+                            struct.pack_into( ">i", obuf, 0, xsdrsize )  # big endian 4 byte int to obuf
+                            output.write( obuf )
+
+                        cmdbuf[0] = opcode
                         output.write( cmdbuf )
-                        obuf = bytearray(4)
-                        struct.pack_into( ">i", obuf, 0, xsdrsize )  # big endian 4 byte int to obuf
+                        obuf = makeXSVFbytes( tdi[tdibits-xsdrchunk:tdibits] )
                         output.write( obuf )
-
-                    cmdbuf[0] = XSDRE
-                    output.write( cmdbuf )
-                    obuf = makeXSVFbytes( tdi )
-                    output.write( obuf )
+                        opcode = XSDRC
+                        tdibits -= xsdrchunk
 
                 else:
                     mask = combineBitVectors( tdr.mask, sdr.mask, hdr.mask )
@@ -563,7 +583,7 @@ try:
 
         elif tokVal == 'RUNTEST' or tokVal == 'LDELAY':
             # e.g. from lattice tools:
-            # "RUNTEST	IDLE  	5 TCK	1.00E-003 SEC;"
+            # "RUNTEST	IDLE	5 TCK	1.00E-003 SEC;"
             saveTok = tokVal
             nextTok()
             min_time = 0
@@ -723,4 +743,3 @@ finally:
     cmdbuf[0] = XCOMPLETE
     output.write( cmdbuf )
     output.close()
-

+ 0 - 66
tools/wrapflash.pl

@@ -1,66 +0,0 @@
-#!/usr/bin/perl
-
-use strict;
-use integer;
-
-my $SPIFLASH_MAGIC = 0x7a07fbd6;
-
-my $FDT_END      = 0;
-my $FDT_DATA     = 1;
-my $FDT_TARGET   = 2;
-my $FDT_NOTE     = 3;
-
-my $FDF_OPTIONAL = 0x0001;
-
-sub getint($) {
-    my($s) = @_;
-    return ($s =~ /^0/) ? oct $s : $s+0;
-}
-sub filelen($) {
-    my($f) = @_;
-    my @s = stat($f);
-
-    return $s[7];
-}
-sub output_chunk($$$$) {
-    my($type,$flags,$addr,$data) = @_;
-
-    print pack("VvvVV", $SPIFLASH_MAGIC, $type, $flags, length($data), $addr);
-    print $data;
-}
-
-my $target  = shift @ARGV;
-if (!defined($target)) {
-    die "Usage: $0 target_name [infile addr]...\n";
-}
-
-binmode(STDOUT);
-
-output_chunk($FDT_TARGET, 0, 0, $target);
-
-my $err;
-while (1) {
-    my $infile = shift @ARGV;
-    my $inaddr = getint(shift @ARGV);
-
-    last if (!defined($infile));
-
-    my $in;
-    if (!open($in, '<', $infile)) {
-	$err = "$infile: $!";
-	last;
-    }
-    binmode($in);
-    my @is = stat($in);
-    my $data;
-    my $dlen = read($in, $data, $is[7]);
-    close($in);
-
-    output_chunk($FDT_DATA, 0, $inaddr, $data);
-}
-
-output_chunk($FDT_END, 0, 0, '');
-
-if (defined($err)) {
-    die "$0: $err\n";
-}