Forráskód Böngészése

time: synchronize all clocks: RTC, SNTP, ESP, FPGA/ABC

Use a special esplink protocol variant to pass time information
between all the devices.
H. Peter Anvin 2 éve
szülő
commit
9ac4e30722

+ 7 - 0
esp32/max80/common.h

@@ -67,3 +67,10 @@ extern_c int reboot_delayed(void);
  */
 extern_c uint8_t max80_board_version;
 
+/*
+ * Time sync status
+ */
+extern_c volatile bool time_net_sync_status;
+struct timeval;
+extern_c void time_net_sync(const struct timeval *tv);
+extern_c void print_time(const char *msg, const struct timeval *tv);

+ 15 - 15
esp32/max80/esplink.c

@@ -65,7 +65,7 @@ void esplink_init(void)
     elink.desc = xmalloc_dma(EL_RB_COUNT * sizeof *elink.desc);
     elink.rb.u = xmalloc_dma(EL_RB_COUNT * sizeof *elink.rb.u);
     elink.rb.d = xmalloc_dma(EL_RB_COUNT * sizeof *elink.rb.d);
-    
+
     struct esplink_sem *s = &elink.sem[0][0];
     for (size_t i = 0; i < EL_RB_COUNT*2; i++) {
 	s->lock = null_check(xSemaphoreCreateBinary());
@@ -73,7 +73,7 @@ void esplink_init(void)
     }
 
     xSemaphoreGive(elink.mutex);
-}    
+}
 
 /*
  * This needs to be called from the FPGA service thread once before
@@ -82,7 +82,7 @@ void esplink_init(void)
  * shutdown and/or reinitialization of the link.
  *
  * Call this function with head == NULL to either shut the link down
- * or to initialize the data structures (see above) 
+ * or to initialize the data structures (see above)
  */
 void esplink_start(const struct esplink_head *head)
 {
@@ -97,9 +97,9 @@ void esplink_start(const struct esplink_head *head)
 
     if (!head)
 	goto shutdown;
-    
+
     /* At this point elink.mutex and all the ->lock mutexes are held */
-    
+
     elink.head       = head->rb;
     elink.head.count = Min(head->rb.count, EL_RB_COUNT);
     size_t desc_size = sizeof(*elink.desc) * elink.head.count;
@@ -110,7 +110,7 @@ void esplink_start(const struct esplink_head *head)
 	elink.need[i][0] = RINGBUF_UNUSABLE + 1;
 	elink.need[i][1] = 1;
     }
-    
+
     iov[0].cmd   = FPGA_CMD_RD;
     iov[0].addr  = elink.head.desc;
     iov[0].rdata = (void *)&elink.desc;
@@ -171,7 +171,7 @@ void esplink_poll(void)
 
 	EventBits_t wakeup = 0;
 	EventBits_t tbit = 1;
-	
+
 	for (size_t i = 0; i < count; i++) {
 	    size_t need;
 
@@ -227,7 +227,7 @@ size_t esplink_write(enum esplink_ringbuf_user ring, const void *data,
 	return tx;
 
     mintx = Min(mintx, len);
-    
+
     const char *p = data;
     struct esplink_sem *sem = &elink.sem[ring][0];
 
@@ -252,19 +252,19 @@ size_t esplink_write(enum esplink_ringbuf_user ring, const void *data,
     atomic(elink.need[ring][0]) = need; /* Minimum wakeup */
 
     char * const start = desc->start;
-    
+
     while (elink.ready == ready_gen) {
 	xEventGroupClearBits(esplink_filled, ELQUEUE_DL(ring));
 
 	const size_t tail = *tptr;
 	size_t space      = (tail-head) & (size-1);
-	
+
 	if (!len) {
 	    if (space >= need)
 		xEventGroupSetBits(esplink_filled, ELQUEUE_DL(ring));
 	    break;
 	}
-	
+
 	if (space < need) {
 	    if (tx >= mintx)
 		break;
@@ -304,7 +304,7 @@ size_t esplink_write(enum esplink_ringbuf_user ring, const void *data,
 	iv->rdata = (void *)tptr;
 	iv->len   = sizeof *tptr;
 	iv++;
-	
+
 	fpga_iov(iov, iv - iov);
     }
 
@@ -347,7 +347,7 @@ size_t esplink_read(enum esplink_ringbuf_user ring, void *data,
     atomic(elink.need[ring][1]) = need; /* Minimum wakeup */
 
     char * const start  = desc->start;
-    
+
     while (elink.ready == ready_gen) {
 	xEventGroupClearBits(esplink_filled, ELQUEUE_UL(ring));
 
@@ -359,7 +359,7 @@ size_t esplink_read(enum esplink_ringbuf_user ring, void *data,
 		xEventGroupSetBits(esplink_filled, ELQUEUE_UL(ring));
 	    break;
 	}
-	
+
 	if (avail < need) {
 	    if (rx >= minrx)
 		break;
@@ -399,7 +399,7 @@ size_t esplink_read(enum esplink_ringbuf_user ring, void *data,
 	iv->rdata = (void *)hptr;
 	iv->len   = sizeof *hptr;
 	iv++;
-	
+
 	fpga_iov(iov, iv - iov);
     }
 

+ 27 - 2
esp32/max80/esplink.h

@@ -38,7 +38,28 @@ struct esplink_ptrs_dstr {
     size_t head;
 };
 
+
+struct esplink_timesync {
+    struct esplink_timesync_buf {
+	uint16_t update;
+	uint16_t tick;
+	union {
+	    struct {
+		unsigned int sec2     : 5;
+		unsigned int min      : 6;
+		unsigned int hour     : 5;
+		unsigned int mday     : 5;
+		unsigned int mon      : 4;
+		unsigned int year     : 7;
+	    } tm;
+	    uint32_t td;
+	};
+    } get, set;
+};
+
 #define ESPLINK_HEAD_MAGIC	0x3648dec4
+#define MAX_SIGNATURE_LEN	64
+
 struct esplink_head {
     volatile uint32_t magic;
     uint32_t          hlen;
@@ -53,8 +74,8 @@ struct esplink_head {
 	    };
 	};
     } board;
-    const char *signature;
-    uint32_t    signature_len;
+
+    volatile struct esplink_timesync *tsync;
 
     struct esplink_ringbuf_head {
 	uint32_t count;
@@ -62,15 +83,19 @@ struct esplink_head {
 	struct esplink_ptrs_dstr *dstr; /* Downstream (FPGA) side */
 	struct esplink_ptrs_ustr *ustr; /* Upstream (ESP32) side */
     } rb;
+
+    char signature[MAX_SIGNATURE_LEN]; /* Human-readable signature string */
 };
 
 #define EL_DIRQ_UNDERRUN	0	/* Local interrupt/status bit */
 #define EL_DIRQ_HELLO		1
 #define EL_DIRQ_RINGBUF		2
+#define EL_DIRQ_TIME		3
 
 #define EL_UIRQ_WREN		0	/* Remote write enable bit, not IRQ */
 #define EL_UIRQ_READY		1
 #define EL_UIRQ_RINGBUF		2
+#define EL_UIRQ_TIME		3
 
 /*
  * Well known ring buffer indicies; must match for both sides.

+ 0 - 1
esp32/max80/fpga.h

@@ -57,4 +57,3 @@ static inline EventBits_t esplink_wait_for(EventBits_t queues, bool online)
 			       queues | (online ? ELWAIT_ONLINE : 0),
 			       0, pdTRUE, portMAX_DELAY) ^ ELWAIT_ONLINE;
 }
-

+ 1 - 1
esp32/max80/fpgajtag.c

@@ -75,7 +75,7 @@ static void fpga_finish(void)
     tap_run_test_idle(JTAG_FPGA_MS);
 
     /* Reset?! */
-    
+
     jtag_disable(NULL);
 }
 

+ 173 - 31
esp32/max80/fpgasvc.c

@@ -16,7 +16,7 @@
 
 #define FPGA_SPI_HOST	FSPI	/* SPI2 */
 
-#define FPGA_PRIORITY	3
+#define FPGA_PRIORITY	10
 #define FPGA_SVC_STACK	4096
 
 static spi_bus_config_t spi_bus_config = {
@@ -53,14 +53,17 @@ static const spi_device_interface_config_t spi_device_interface_config = {
 
 static spi_device_handle_t spi_handle;
 static TaskHandle_t fpga_task;
+static TimerHandle_t fpga_timesync_timer;
 static SemaphoreHandle_t spi_mutex;
 static EventGroupHandle_t spi_done_evgroup;
 static volatile bool spi_abort_all;
+static const volatile struct esplink_timesync *tsync_addr;
 
 #define NOTIFY_INDEX	0
 #define NOTIFY_FPGA	(1 << 0)
 #define NOTIFY_ENABLE	(1 << 1)
 #define NOTIFY_DISABLE	(1 << 2)
+#define NOTIFY_TIMESYNC	(1 << 3)
 #if 0
 #define NOTIFY_SPI	(1 << 3)
 #define NOTIFY_RINGBUF	(1 << 4)
@@ -89,7 +92,7 @@ static uint32_t notify_wait_for(uint32_t flags)
 static void ARDUINO_ISR_ATTR fpga_notify_from_isr(uint32_t flags)
 {
     BaseType_t wakeup = pdFALSE;
-    
+
     if (xTaskNotifyIndexedFromISR(fpga_task, NOTIFY_INDEX, flags, eSetBits,
 				  &wakeup) != pdFAIL)
 	portYIELD_FROM_ISR(wakeup);
@@ -105,6 +108,12 @@ static void ARDUINO_ISR_ATTR fpga_interrupt(void)
     fpga_notify_from_isr(NOTIFY_FPGA);
 }
 
+static void fpga_timesync_trigger(TimerHandle_t t)
+{
+    (void)t;
+    fpga_notify_from_task(NOTIFY_TIMESYNC);
+}
+
 static void ARDUINO_ISR_ATTR spi_callback(spi_transaction_t *t)
 {
     size_t flags = (size_t)t->user;
@@ -145,6 +154,10 @@ esp_err_t fpga_service_init(void)
 		    FPGA_PRIORITY, &fpga_task) != pdPASS)
 	return ESP_FAIL;
 
+    fpga_timesync_timer =
+      null_check(xTimerCreate("rtc_sync", 17*configTICK_RATE_HZ,
+			      pdTRUE, NULL, fpga_timesync_trigger));
+
     esplink_init();
 
     xEventGroupSetBits(fpga_service_evgroup, NOTIFY_DISABLE);
@@ -154,7 +167,7 @@ esp_err_t fpga_service_init(void)
 static bool fpga_link_enable(void)
 {
     esp_err_t err;
-    
+
     if (spi_handle)
 	return true;		/* Already started */
 
@@ -175,7 +188,7 @@ static bool fpga_link_enable(void)
 	goto release_bus_fail;
 
     xEventGroupClearBits(spi_done_evgroup, EVENT_ALL_BITS);
-    
+
     pinMode(PIN_FPGA_INT, INPUT);
     attachInterrupt(PIN_FPGA_INT, fpga_interrupt, FALLING);
 
@@ -193,7 +206,7 @@ free_bus_fail:
 
 init_fail:
     xEventGroupSetBits(fpga_service_evgroup, NOTIFY_DISABLE);
-    
+
 done:
     return !err;
 }
@@ -204,9 +217,9 @@ static void fpga_link_disable(void)
 	return;			/* Already stopped */
 
     xEventGroupClearBits(fpga_service_evgroup, NOTIFY_ENABLE);
-    
+
     xSemaphoreTakeRecursive(spi_mutex, portMAX_DELAY);
-    
+
     detachInterrupt(PIN_FPGA_INT);
 
     spi_device_release_bus(spi_handle);
@@ -217,6 +230,8 @@ static void fpga_link_disable(void)
     xEventGroupSetBits(fpga_service_evgroup, NOTIFY_DISABLE);
 }
 
+static void fpga_poll_set_time(void);
+
 static bool fpga_online(void)
 {
     struct esplink_head head;
@@ -239,26 +254,26 @@ static bool fpga_online(void)
 	   head.board.major, head.board.minor,
 	   head.board.fixes, head.board.fpga);
 
-    if (((size_t)head.signature_len - 1) >= 127)
-	return false;
-
-    char signature_string[head.signature_len+1];
-    fpga_io_read(0, head.signature,
-		 signature_string, head.signature_len);
-    signature_string[head.signature_len] = '\0';
-    fpga_io_write(0, (char *)head.signature + 9, "GUBBAR", 6);
+    tsync_addr = head.tsync;
 
-    printf("[FPGA] online, signature \"%s\"\n", signature_string);
+    printf("[FPGA] online, signature \"%.*s\"\n",
+	   (int)(sizeof head.signature - 1), head.signature);
     esplink_start(&head);
 
     setenv_bool("status.max80.fpga", true);
     xSemaphoreGiveRecursive(spi_mutex);
+
+    xTimerStart(fpga_timesync_timer, portMAX_DELAY);
+    fpga_poll_set_time();
+
     return true;
 }
 
 static void fpga_offline(void)
 {
+    tsync_addr = NULL;
     xSemaphoreTakeRecursive(spi_mutex, portMAX_DELAY);
+    xTimerStop(fpga_timesync_timer, portMAX_DELAY);
     setenv_bool("status.max80.fpga", false);
     esplink_start(NULL);	/* Stop esplink */
 }
@@ -273,13 +288,13 @@ esp_err_t fpga_iov(const struct fpga_iov *iov, size_t niov)
 
     for (size_t i = 0; i < niov; i++) {
 	const struct fpga_iov *iv = &iov[i];
-	
+
 	if (!iv->len && !(iv->cmd & FPGA_CMD_NULL))
 	    continue;
 
 	spi_transaction_ext_t *t = &trans[ntrans];
 	memset(t, 0, sizeof *t);
-	
+
 	t->base.flags =
 	    SPI_TRANS_MODE_DIO |
 	    SPI_TRANS_VARIABLE_DUMMY |
@@ -310,7 +325,7 @@ esp_err_t fpga_iov(const struct fpga_iov *iov, size_t niov)
 
     if (!ntrans)
 	return ESP_OK;
-    
+
     esp_err_t err = ESP_OK;
     xSemaphoreTakeRecursive(spi_mutex, portMAX_DELAY);
     if (!spi_handle) {
@@ -346,7 +361,7 @@ fail:
     xSemaphoreGiveRecursive(spi_mutex);
     return err;
 }
-    
+
 esp_err_t fpga_io_write(unsigned int cmd, const void *addr,
 			const void *data, size_t len)
 {
@@ -373,6 +388,123 @@ esp_err_t fpga_io_read(unsigned int cmd, const void *addr,
     return fpga_iov(&iov, 1);
 }
 
+/*
+ * This should be executed after getting an EL_UIRQ_TIME notification;
+ * do this in polling mode for best latency.
+ */
+static void fpga_get_time(void)
+{
+    esp_err_t err;
+    struct tm tm;
+    struct timeval tv;
+    struct tsbuf {
+	/* These two words are the status word normally considered "dummy" */
+	uint16_t status;
+	uint16_t tick;
+
+	struct esplink_timesync_buf get;
+    } tsbuf;
+
+    if (!tsync_addr) {
+	fpga_io_status(FPGA_CMD_ACK(EL_UIRQ_TIME));
+	return;
+    }
+
+    spi_transaction_ext_t trans;
+    memset(&trans, 0, sizeof trans);
+
+    trans.base.flags =
+	SPI_TRANS_MODE_DIO |
+	SPI_TRANS_VARIABLE_DUMMY |
+	SPI_TRANS_MULTILINE_CMD |
+	SPI_TRANS_MULTILINE_ADDR;
+    trans.base.rxlength = sizeof tsbuf << 3;
+    trans.base.rx_buffer = &tsbuf;
+
+    trans.base.addr = (size_t)&tsync_addr->get;
+    trans.base.cmd  = FPGA_CMD_RD | FPGA_CMD_ACK(EL_UIRQ_TIME);
+
+    xSemaphoreTakeRecursive(spi_mutex, portMAX_DELAY);
+    err = spi_device_polling_transmit(spi_handle, &trans.base);
+    xSemaphoreGiveRecursive(spi_mutex);
+    if (err)
+	return;
+
+    if (time_net_sync_status)
+	return;		 /* Ignore time from RTC if SNTP active now */
+
+    tm.tm_sec   = tsbuf.get.tm.sec2 << 1;
+    tm.tm_min   = tsbuf.get.tm.min;
+    tm.tm_hour  = tsbuf.get.tm.hour;
+    tm.tm_mday  = tsbuf.get.tm.mday;
+    tm.tm_mon   = tsbuf.get.tm.mon - 1;
+    tm.tm_year  = tsbuf.get.tm.year + 80;
+    tm.tm_isdst = -1;		/* Unknown */
+
+    /* The third term handles wraparounds due to delay in transit */
+    tv.tv_sec = mktime(&tm) + (tsbuf.tick >> 15) +
+	((tsbuf.get.tick >= tsbuf.tick) << 1);
+    tv.tv_usec = (((uint32_t)tsbuf.tick << 17) * UINT64_C(1000000)) >> 32;
+
+    settimeofday(&tv, NULL);
+
+    print_time("[FPGA] Time set from RTC: ", &tv);
+}
+
+static void fpga_poll_set_time(void)
+{
+    if (!tsync_addr)
+	return;
+
+    if (!time_net_sync_status) {
+	/* Poll for current time; will call fpga_get_time() later */
+	fpga_io_status(FPGA_CMD_IRQ(EL_DIRQ_TIME));
+	return;
+    }
+
+    /* Otherwise transmit time to set the RTC */
+    esp_err_t err;
+    struct timeval tv;
+    struct esplink_timesync_buf tset;
+
+    spi_transaction_t trans;
+    memset(&trans, 0, sizeof trans);
+
+    tset.update = 1;
+
+    xSemaphoreTakeRecursive(spi_mutex, portMAX_DELAY);
+
+    gettimeofday(&tv, NULL);
+    const struct tm *tm = localtime(&tv.tv_sec);
+
+    tset.tick = ((tv.tv_usec * ((1ULL << (32+15))/1000000+1)) >> 32)
+      + ((tv.tv_sec & 1) << 15);
+    tset.tm.sec2 = tm->tm_sec >> 1;
+    tset.tm.min  = tm->tm_min;
+    tset.tm.hour = tm->tm_hour;
+    tset.tm.mday = tm->tm_mday;
+    tset.tm.mon  = tm->tm_mon + 1;
+    tset.tm.year = tm->tm_year - 80;
+
+    trans.flags =
+	SPI_TRANS_MODE_DIO |
+	SPI_TRANS_MULTILINE_CMD |
+	SPI_TRANS_MULTILINE_ADDR;
+    trans.length = sizeof tset << 3;
+    trans.tx_buffer = &tset;
+
+    trans.addr = (size_t)&tsync_addr->set;
+    trans.cmd  = FPGA_CMD_WR | FPGA_CMD_IRQ(EL_DIRQ_TIME) |
+	FPGA_CMD_ACK(EL_UIRQ_TIME);
+
+    err = spi_device_polling_transmit(spi_handle, &trans);
+    xSemaphoreGiveRecursive(spi_mutex);
+    if (err)
+	return;
+
+    print_time("[FPGA] RTC update: ", &tv);
+}
+
 /*
  * Get status in polling mode (small transaction, < 256 CPU cycles).
  * cmd typically would be IRQ/ACK bits.
@@ -412,7 +544,7 @@ static void fpga_service_task(void *dummy)
     } fpga_state = FPGA_DISABLED;
 
     fputs("[FPGA] Starting FPGA services task\n", stdout);
-    
+
     while (1) {
 	uint32_t notifiers, status;
 
@@ -431,16 +563,23 @@ static void fpga_service_task(void *dummy)
 				  FPGA_CMD_IRQ(EL_DIRQ_HELLO));
 	  printf("[FPGA] FPGA status flags = 0x%08x\n", status);
 
-	  if ((status & 0xf031) == 0x9030) {
-	      if (fpga_online())
-		  fpga_state = FPGA_ONLINE;
-	  } else if (digitalRead(PIN_FPGA_INT)) {
+	  if (!digitalRead(PIN_FPGA_INT)) {
+	      if (status & 0x40)
+		  fpga_io_status(FPGA_CMD_ACK(2));
+	      if (status & 0x80)
+		  fpga_io_status(FPGA_CMD_ACK(3));
+	      if ((status & 0xf031) == 0x9030) {
+		  if (fpga_online())
+		      fpga_state = FPGA_ONLINE;
+	      }
+	  } else {
 	      notifiers = notify_wait_for(NOTIFY_FPGA|NOTIFY_DISABLE);
 	  }
 	  break;
 
 	case FPGA_ONLINE:
-	    notifiers = notify_wait_for(NOTIFY_FPGA|NOTIFY_DISABLE);
+	    notifiers = notify_wait_for(NOTIFY_FPGA|NOTIFY_DISABLE|
+					NOTIFY_TIMESYNC);
 
 	    if (notifiers & NOTIFY_DISABLE) {
 		fpga_offline();
@@ -454,17 +593,20 @@ static void fpga_service_task(void *dummy)
 		    fpga_offline();
 		    printf("[FPGA] FPGA offline, status = 0x%08x\n", status);
 		    fpga_state = FPGA_OFFLINE;
+		    notifiers = 0;
 		    break;
 		}
 
+		if (status & 0x80)
+		    fpga_get_time();
+
 		if (status & 0x40)
 		    esplink_poll();
-
-		if (status & 0x80) {
-		    fputs("[FPGA] invalid upstream interrupt 3\n", stdout);
-		    fpga_io_status(FPGA_CMD_ACK(3));
-		}	    
 	    }
+
+	    if (notifiers & NOTIFY_TIMESYNC)
+		fpga_poll_set_time();
+
 	    break;
 	}
 

+ 5 - 5
esp32/max80/httpd.c

@@ -180,7 +180,7 @@ static esp_err_t httpd_send_plain(httpd_req_t *req,
     const char *now = http_now();
 
     MSG("http_send_plain %u \"%.*s\"\n", rcode, (int)blen, body);
-    
+
     if (rcode > 499)
 	flags |= HSP_CLOSE;
 
@@ -440,7 +440,7 @@ static esp_err_t httpd_sys_handler(httpd_req_t *req)
 
     if (STRING_MATCHES(file, filelen, "getstatus"))
 	return httpd_get_status(req);
-    
+
     if (STRING_MATCHES(file, filelen, "getconfig"))
 	return httpd_get_config_status(req, false);
 
@@ -617,7 +617,7 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	filename,
 	mime_type->ext ? mime_type->ext : "(none)",
 	mime_type->mime ? mime_type->mime : "(none)");
-    
+
     unz_file_info fileinfo;
     memset(&fileinfo, 0, sizeof fileinfo);
     unzGetCurrentFileInfo(unz, &fileinfo, NULL, 0, NULL, 0, NULL, 0);
@@ -644,11 +644,11 @@ static esp_err_t httpd_static_handler(httpd_req_t *req)
 	}
 
 	MSG("redirect: %.*s\n", (int)len, buffer);
-	
+
 	err = httpd_send_plain(req, 302, buffer, len, 0, 0);
 	goto out;
     }
-    
+
     /*
      * Hopefully the combination of date and CRC
      * is strong enough to quality for a "strong" ETag

+ 0 - 1
esp32/max80/led.c

@@ -49,4 +49,3 @@ void led_init(void)
     for (int led = 0; led < 3; led++)
 	led_set(led, LED_OFF);
 }
-

+ 0 - 1
esp32/max80/led.h

@@ -18,4 +18,3 @@ enum led_mode {
 
 extern_c void led_init(void);
 extern_c void led_set(int led, enum led_mode mode);
-

+ 30 - 0
esp32/max80/time.c

@@ -0,0 +1,30 @@
+#include "common.h"
+#include "config.h"
+
+void print_time(const char *msg, const struct timeval *tv)
+{
+    const struct tm *tm = localtime(&tv->tv_sec);
+    char tb1[48], tb2[16];
+    size_t len;
+
+    strftime(tb1, sizeof tb1, "%a %Y-%m-%d %H:%M:%S", tm);
+    strftime(tb2, sizeof tb2, "%z (%Z)", tm);
+
+    printf("%s%s.%03lu %s\n", msg, tb1, tv->tv_usec/1000UL, tb2);
+}
+
+volatile bool time_net_sync_status;
+void time_net_sync(const struct timeval *tv)
+{
+    bool synced = !!tv;
+
+    if (synced != time_net_sync_status) {
+	time_net_sync_status = synced;
+	setenv_bool("status.net.sntp.sync", synced);
+	if (!synced) {
+	    printf("[SNTP] Time synchronization lost\n");
+	} else {
+	    print_time("[SNTP] Time synchronized: ", tv);
+	}
+    }
+}

+ 6 - 15
esp32/max80/wifi.cpp

@@ -48,20 +48,11 @@ static void sntp_sync_cb(struct timeval *tv)
     switch (sync_status) {
     case SNTP_SYNC_STATUS_RESET:
 	sntp_set_sync_mode(SNTP_SYNC_MODE_IMMED); // Until first sync
-	if (prev_sync_status != sync_status) {
-	    printf("[SNTP] time synchronization lost\n");
-	    setenv_bool("status.net.sntp.sync", false);
-	}
+	time_net_sync(NULL);
 	break;
     case SNTP_SYNC_STATUS_COMPLETED:
 	sntp_set_sync_mode(SNTP_SYNC_MODE_SMOOTH); // After successful sync
-	if (prev_sync_status != sync_status) {
-	    char timebuf[64];
-	    const struct tm *tm = localtime(&tv->tv_sec);
-	    strftime(timebuf, sizeof timebuf, "%a %Y-%m-%d %H:%M:%S %z", tm);
-	    printf("[SNTP] Time synchronized: %s\n", timebuf);
-	    setenv_bool("status.net.sntp.sync", true);
-	}
+	time_net_sync(tv);
 	break;
     default:
 	break;
@@ -176,7 +167,7 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
     int prev_ap_clients = ap_clients;
     IPAddress wifi_local_ip = WiFi.localIP();
     const char *local_ip = ip_str(wifi_local_ip);
-    
+
     switch (event) {
     case ARDUINO_EVENT_WIFI_READY:
 	printf("[WIFI] Interface ready\n");
@@ -327,7 +318,7 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 	    led_set(LED_GREEN, connected & CON_AP ? LED_FLASH_FAST : LED_ON);
 	}
     }
-    
+
     if (is_connect) {
 	start_services();
     }
@@ -344,7 +335,7 @@ static void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info)
 static void setenv_mac(const char *var, const uint8_t mac[6])
 {
     char mac_str[3*6];
-    
+
     snprintf(mac_str, sizeof mac_str, "%02x:%02x:%02x:%02x:%02x:%02x",
 	     mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
     setenv(var, mac_str, 1);
@@ -365,7 +356,7 @@ static void wifi_config_ap(void)
     setenv_mac("status.net.ap.mac", mac);
     /* The last two bytes of the MAC */
     snprintf(ap_ssid, sizeof ap_ssid, "MAX80_%02X%02X", mac[4], mac[5]);
-    
+
     printf("[WIFI] AP SSID %s IP %s netmask %s channel %u\n",
 	       ap_ssid, AP_IP.toString(), AP_Netmask.toString(), channel+1);
     setenv("status.net.ap.ssid", ap_ssid, 1);

+ 0 - 4
esp32/max80/xmalloc.c

@@ -54,7 +54,3 @@ void *xnrealloc(void *ptr, size_t nmemb, size_t size)
 {
     return xrealloc(ptr, nsize(nmemb, size));
 }
-
-
-
-

BIN
esp32/output/max80.ino.bin


+ 1 - 1
fpga/abcbus.sv

@@ -429,7 +429,7 @@ module abcbus (
    // Memory read latency counter
    reg [7:0] memrd_latency_ctr = 'b0;
    reg [7:0] memrd_latency_max = 'b0;
-   reg 	     memrd_latency_err = 1'b0;
+   reg	     memrd_latency_err = 1'b0;
 
    wire [7:0] memrd_latency_ctr_next = memrd_latency_ctr + 1'b1;
 

+ 30 - 28
fpga/esp.sv

@@ -19,7 +19,7 @@
 // A 32-bit address follows; for a read, the following 16 cycles
 // contains dummy/status data:
 //
-// Bit [31:16] = adjusted memory address
+// Bit [31:16] = 16 LSB of 32 kHz RTC counter
 // Bit [15:12] = 4'b1001
 // Bit [11: 8] = 0 reserved
 // Bit [ 7: 5] = upstream interrupts pending
@@ -38,36 +38,38 @@ module esp #(
 	     parameter        dram_bits = 25,
 	     parameter [31:0] dram_base = 32'h40000000
 	     ) (
-		input 	      rst_n,
-		input 	      sys_clk,
-		input 	      sdram_clk,
+		input	      rst_n,
+		input	      sys_clk,
+		input	      sdram_clk,
 
-		input 	      cpu_valid,
+		input	      cpu_valid,
 		input [4:0]   cpu_addr,
 		input [3:0]   cpu_wstrb,
 		input [31:0]  cpu_wdata,
 		output [31:0] cpu_rdata,
 		output reg    irq,
-			      
+
 		dram_bus.dstr dram,
- 
+
 		output reg    esp_int,
-		input 	      spi_clk,
+		input	      spi_clk,
 		inout [1:0]   spi_io,
-		input 	      spi_cs_n
+		input	      spi_cs_n,
+
+		input [15:0]  rtc_ctr
 		);
 
-   reg  [31:0] 		      mem_addr = 'b0;
-   wire [31:0] 		      mem_addr_mask = (1'b1 << dram_bits) - 3'd4;
-   wire [31:0] 		      mem_addr_out = (mem_addr & mem_addr_mask)
+   reg  [31:0]		      mem_addr = 'b0;
+   wire [31:0]		      mem_addr_mask = (1'b1 << dram_bits) - 3'd4;
+   wire [31:0]		      mem_addr_out = (mem_addr & mem_addr_mask)
 			      | dram_base;
 
-   reg 			      mem_valid;
-   reg [31:0] 		      mem_wdata;
-   wire 		      mem_write;
-   reg [ 3:0] 		      mem_wstrb;
-   wire 		      mem_ready;
-   wire [31:0] 		      mem_rdata;
+   reg			      mem_valid;
+   reg [31:0]		      mem_wdata;
+   wire			      mem_write;
+   reg [ 3:0]		      mem_wstrb;
+   wire			      mem_ready;
+   wire [31:0]		      mem_rdata;
 
    dram_port #(32) mem
      (
@@ -80,10 +82,10 @@ module esp #(
       .ready ( mem_ready ),
       .rd    ( mem_rdata )
       );
-   
+
    reg [1:0]		  spi_clk_q;
-   reg 			  spi_cs_n_q;
-   reg [1:0] 		  spi_io_q;
+   reg			  spi_cs_n_q;
+   reg [1:0]		  spi_io_q;
 
    always @(posedge sdram_clk)
      begin
@@ -110,11 +112,11 @@ module esp #(
    reg [ 3:1] spi_irq;
    reg [ 3:1] latched_spi_irq;	// SPI IRQ as of transition start
    reg [ 1:0] spi_out;
-   reg 	      spi_oe;
+   reg	      spi_oe;
    reg [ 2:0] spi_wbe;		// Partial word write byte enables
    reg [23:0] spi_wdata;	// Partial word write data
-   reg 	      spi_wr_en;	// SPI writes enabled by CPU
-   reg 	      spi_mem_en;	// Read, or write enabled at start of trans
+   reg	      spi_wr_en;	// SPI writes enabled by CPU
+   reg	      spi_mem_en;	// Read, or write enabled at start of trans
 
    assign spi_io = spi_oe ? spi_out : 2'bzz;
 
@@ -122,7 +124,7 @@ module esp #(
 
    wire [31:0] spi_indata = { spi_shr[29:0], spi_io_q };
 
-   reg 	       cpu_valid_q;
+   reg	       cpu_valid_q;
 
    always @(negedge rst_n or posedge sdram_clk)
      if (~rst_n)
@@ -202,8 +204,8 @@ module esp #(
 			    spi_shr[31:28]  <= { latched_spi_irq, spi_wr_en };
 			    spi_shr[27:24]  <= cpu_irq;
 			    spi_shr[23:16]  <= 8'b1001_0000;
-			    spi_shr[15: 8]  <= mem_addr_out[23:16];
-			    spi_shr[ 7: 0]  <= mem_addr_out[31:24];
+			    spi_shr[15: 8]  <= rtc_ctr[ 7: 0];
+			    spi_shr[ 7: 0]  <= rtc_ctr[15: 8];
 			 end // else: !if(spi_state == st_io)
 
 		       if (mem_valid && spi_state != st_cmd)
@@ -306,7 +308,7 @@ module esp #(
 
    always @(posedge sys_clk)
      irq <= |cpu_irq;
-   
+
    always @(*)
      casez (cpu_addr[1:0])
        2'b0?:

+ 12 - 12
fpga/fast_mem.sv

@@ -9,22 +9,22 @@ module fast_mem
     parameter data_file
     )
    (
-    input 		  rst_n,
-    input 		  clk,
+    input		  rst_n,
+    input		  clk,
 
-    input 		  write0,
-    input 		  read0,
-    input [3:0] 	  wstrb0,
+    input		  write0,
+    input		  read0,
+    input [3:0]		  wstrb0,
     input [words_lg2-1:0] addr0,
-    input [31:0] 	  wdata0,
-    output [31:0] 	  rdata0,
+    input [31:0]	  wdata0,
+    output [31:0]	  rdata0,
 
-    input 		  write1,
-    input 		  read1,
-    input [3:0] 	  wstrb1,
+    input		  write1,
+    input		  read1,
+    input [3:0]		  wstrb1,
     input [words_lg2-1:0] addr1,
-    input [31:0] 	  wdata1,
-    output [31:0] 	  rdata1
+    input [31:0]	  wdata1,
+    output [31:0]	  rdata1
     );
 
    altsyncram ip (

+ 3 - 3
fpga/max80.qpf

@@ -19,15 +19,15 @@
 #
 # Quartus Prime
 # Version 21.1.0 Build 842 10/21/2021 SJ Lite Edition
-# Date created = 03:00:56  May 14, 2022
+# Date created = 00:20:57  May 17, 2022
 #
 # -------------------------------------------------------------------------- #
 
 QUARTUS_VERSION = "21.1"
-DATE = "03:00:56  May 14, 2022"
+DATE = "00:20:57  May 17, 2022"
 
 # Revisions
 
-PROJECT_REVISION = "v1"
 PROJECT_REVISION = "v2"
+PROJECT_REVISION = "v1"
 PROJECT_REVISION = "bypass"

+ 77 - 72
fpga/max80.sv

@@ -13,123 +13,123 @@ module max80
     parameter logic [7:0] fpga_ver)
    (
     // Clock oscillator
-    input 	  master_clk, // 336 MHz from PLL2
-    input 	  slow_clk, // ~12 MHz clock from PLL2
-    input 	  master_pll_locked, // PLL2 is locked, master_clk is good
-    output 	  reset_plls, // Reset all PLLs including PLL2
+    input	  master_clk, // 336 MHz from PLL2
+    input	  slow_clk, // ~12 MHz clock from PLL2
+    input	  master_pll_locked, // PLL2 is locked, master_clk is good
+    output	  reset_plls, // Reset all PLLs including PLL2
 
-    input 	  board_id, // This better match the firmware
+    input	  board_id, // This better match the firmware
 
     // ABC-bus
-    inout 	  abc_clk, // ABC-bus 3 MHz clock
+    inout	  abc_clk, // ABC-bus 3 MHz clock
     inout [15:0]  abc_a, // ABC address bus
     inout [7:0]   abc_d, // ABC data bus
-    output 	  abc_d_oe, // Data bus output enable
-    inout 	  abc_rst_n, // ABC bus reset strobe
-    inout 	  abc_cs_n, // ABC card select strobe
+    output	  abc_d_oe, // Data bus output enable
+    inout	  abc_rst_n, // ABC bus reset strobe
+    inout	  abc_cs_n, // ABC card select strobe
     inout [4:0]   abc_out_n, // OUT, C1-C4 strobe
     inout [1:0]   abc_inp_n, // INP, STATUS strobe
-    inout 	  abc_xmemfl_n, // Memory read strobe
-    inout 	  abc_xmemw800_n, // Memory write strobe (ABC800)
-    inout 	  abc_xmemw80_n, // Memory write strobe (ABC80)
-    inout 	  abc_xinpstb_n, // I/O read strobe (ABC800)
-    inout 	  abc_xoutpstb_n, // I/O write strobe (ABC80)
+    inout	  abc_xmemfl_n, // Memory read strobe
+    inout	  abc_xmemw800_n, // Memory write strobe (ABC800)
+    inout	  abc_xmemw80_n, // Memory write strobe (ABC80)
+    inout	  abc_xinpstb_n, // I/O read strobe (ABC800)
+    inout	  abc_xoutpstb_n, // I/O write strobe (ABC80)
     // The following are inverted versus the bus IF
     // the corresponding MOSFETs are installed
-    inout 	  abc_rdy_x, // RDY = WAIT#
-    inout 	  abc_resin_x, // System reset request
-    inout 	  abc_int80_x, // System INT request (ABC80)
-    inout 	  abc_int800_x, // System INT request (ABC800)
-    inout 	  abc_nmi_x, // System NMI request (ABC800)
-    inout 	  abc_xm_x, // System memory override (ABC800)
+    inout	  abc_rdy_x, // RDY = WAIT#
+    inout	  abc_resin_x, // System reset request
+    inout	  abc_int80_x, // System INT request (ABC80)
+    inout	  abc_int800_x, // System INT request (ABC800)
+    inout	  abc_nmi_x, // System NMI request (ABC800)
+    inout	  abc_xm_x, // System memory override (ABC800)
     // Host/device control
-    output 	  abc_host, // 1 = host, 0 = target
+    output	  abc_host, // 1 = host, 0 = target
 
     // ABC-bus extension header
     // (Note: cannot use an array here because HC and HH are
     // input only.)
-    inout 	  exth_ha,
-    inout 	  exth_hb,
-    input 	  exth_hc,
-    inout 	  exth_hd,
-    inout 	  exth_he,
-    inout 	  exth_hf,
-    inout 	  exth_hg,
-    input 	  exth_hh,
+    inout	  exth_ha,
+    inout	  exth_hb,
+    input	  exth_hc,
+    inout	  exth_hd,
+    inout	  exth_he,
+    inout	  exth_hf,
+    inout	  exth_hg,
+    input	  exth_hh,
 
     // SDRAM bus
-    output 	  sr_clk,
+    output	  sr_clk,
     output [1:0]  sr_ba, // Bank address
     output [12:0] sr_a, // Address within bank
     inout [15:0]  sr_dq, // Also known as D or IO
     output [1:0]  sr_dqm, // DQML and DQMH
-    output 	  sr_cs_n,
-    output 	  sr_we_n,
-    output 	  sr_cas_n,
-    output 	  sr_ras_n,
+    output	  sr_cs_n,
+    output	  sr_we_n,
+    output	  sr_cas_n,
+    output	  sr_ras_n,
 
     // SD card
-    input 	  sd_cd_n,
-    output 	  sd_cs_n,
-    output 	  sd_clk,
-    output 	  sd_di,
-    input 	  sd_do,
+    input	  sd_cd_n,
+    output	  sd_cs_n,
+    output	  sd_clk,
+    output	  sd_di,
+    input	  sd_do,
 
     // Serial console (naming is FPGA as DCE)
-    input 	  tty_txd,
-    output 	  tty_rxd,
-    input 	  tty_rts,
-    output 	  tty_cts,
-    input 	  tty_dtr,
+    input	  tty_txd,
+    output	  tty_rxd,
+    input	  tty_rts,
+    output	  tty_cts,
+    input	  tty_dtr,
 
     // SPI flash memory (also configuration)
-    output 	  flash_cs_n,
-    output 	  flash_sck,
+    output	  flash_cs_n,
+    output	  flash_sck,
     inout [1:0]   flash_io,
 
     // SPI bus (connected to ESP32 so can be bidirectional)
-    inout 	  spi_clk,	  // ESP32 IO12
+    inout	  spi_clk,	  // ESP32 IO12
     inout [1:0]   spi_io,	  // ESP32 IO13,IO11
-    inout 	  spi_cs_esp_n,   // ESP32 IO10
-    inout 	  spi_cs_flash_n, // ESP32 IO01
+    inout	  spi_cs_esp_n,   // ESP32 IO10
+    inout	  spi_cs_flash_n, // ESP32 IO01
 
     // Other ESP32 connections
-    inout 	  esp_io0,        // ESP32 IO00
-    inout 	  esp_int,        // ESP32 IO09
+    inout	  esp_io0,        // ESP32 IO00
+    inout	  esp_int,        // ESP32 IO09
 
     // I2C bus (RTC and external)
-    inout 	  i2c_scl,
-    inout 	  i2c_sda,
-    input 	  rtc_32khz,
-    input 	  rtc_int_n,
+    inout	  i2c_scl,
+    inout	  i2c_sda,
+    input	  rtc_32khz,
+    input	  rtc_int_n,
 
     // LEDs
     output [2:0]  led,
 
     // USB
-    inout 	  usb_dp,
-    inout 	  usb_dn,
-    output 	  usb_pu,
-    input 	  usb_rx,
-    input 	  usb_rx_ok,
+    inout	  usb_dp,
+    inout	  usb_dn,
+    output	  usb_pu,
+    input	  usb_rx,
+    input	  usb_rx_ok,
 
     // HDMI
     output [2:0]  hdmi_d,
-    output 	  hdmi_clk,
-    inout 	  hdmi_scl,
-    inout 	  hdmi_sda,
-    inout 	  hdmi_hpd,
+    output	  hdmi_clk,
+    inout	  hdmi_scl,
+    inout	  hdmi_sda,
+    inout	  hdmi_hpd,
 
     // Unconnected pins with pullups, used for randomness
     inout [2:0]   rngio,
 
     // Various clocks available to the top level as well as internally
-    output 	  sdram_clk, // 168 MHz SDRAM clock
-    output 	  sys_clk, //  84 MHz System clock
-    output 	  flash_clk, // 134 MHz Serial flash ROM clock
-    output 	  usb_clk, //  48 MHz USB clock
-    output 	  vid_clk, //  56 MHz Video pixel clock
-    output 	  vid_hdmiclk	// 280 MHz HDMI serializer clock = vid_clk x 5
+    output	  sdram_clk, // 168 MHz SDRAM clock
+    output	  sys_clk, //  84 MHz System clock
+    output	  flash_clk, // 134 MHz Serial flash ROM clock
+    output	  usb_clk, //  48 MHz USB clock
+    output	  vid_clk, //  56 MHz Video pixel clock
+    output	  vid_hdmiclk	// 280 MHz HDMI serializer clock = vid_clk x 5
     );
 
    // -----------------------------------------------------------------------
@@ -841,6 +841,8 @@ module max80
    //
    wire clk_32kHz = ~rtc_32khz;	// Inverted
 
+   wire [15:0] rtc_ctr;
+
    sysclock #(.PERIODIC_HZ_LG2 ( TIMER_SHIFT ))
    sysclock (
 	     .rst_n     ( rst_n ),
@@ -854,7 +856,8 @@ module max80
 	     .wstrb   ( cpu_mem_wstrb ),
 	     .addr    ( cpu_mem_addr[2] ),
 
-	     .periodic ( iodev_irq_sysclock )
+	     .periodic ( iodev_irq_sysclock ),
+	     .rtc_ctr  ( rtc_ctr )
 	     );
 
    // ESP32
@@ -877,7 +880,9 @@ module max80
 	    .spi_io     ( spi_io ),
 	    .spi_cs_n   ( spi_cs_esp_n ),
 
-	    .dram       ( sr_bus[2].dstr )
+	    .dram       ( sr_bus[2].dstr ),
+
+	    .rtc_ctr    ( rtc_ctr )
 	    );
 
    //

BIN
fpga/output/bypass.jic


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.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.xsvf.gz


+ 6 - 2
fpga/sysclock.sv

@@ -23,7 +23,9 @@ module sysclock (
 		 input [31:0]	   wdata,
 		 input [3:0]	   wstrb,
 
-		 output		   periodic
+		 output		   periodic,
+
+		 output [15:0]	   rtc_ctr
 		);
 
    parameter PERIODIC_HZ_LG2 = 5;
@@ -95,9 +97,11 @@ module sysclock (
    wire wrap_mday = wrap_hour & (tm_mday >= maxday(tm_mon, tm_year));
    wire wrap_mon  = wrap_mday & (tm_mon  >= 4'd12);
 
-   // Yes, it may jump if the counter is written...
+   // Yes, it may jump if the counter is written, that is intentional
    assign periodic = tm_tick[14 - PERIODIC_HZ_LG2];
 
+   assign rtc_ctr  = tm_tick;
+
    always @(posedge sys_clk)
      begin
 	tm_tick      <= tm_tick + rtc_clk_stb;

+ 57 - 57
fpga/v1.sv

@@ -8,93 +8,93 @@
 module v1
    (
     // Clock oscillator
-    input 	  clock_48, // 48 MHz
-    input 	  board_id, // This better match the firmware
+    input	  clock_48, // 48 MHz
+    input	  board_id, // This better match the firmware
 
     // ABC-bus
-    input 	  abc_clk, // ABC-bus 3 MHz clock
+    input	  abc_clk, // ABC-bus 3 MHz clock
     input [15:0]  abc_a, // ABC address bus
     inout [7:0]   abc_d, // ABC data bus
-    output 	  abc_d_oe, // Data bus output enable
-    input 	  abc_rst_n, // ABC bus reset strobe
-    input 	  abc_cs_n, // ABC card select strobe
+    output	  abc_d_oe, // Data bus output enable
+    input	  abc_rst_n, // ABC bus reset strobe
+    input	  abc_cs_n, // ABC card select strobe
     input [4:0]   abc_out_n, // OUT, C1-C4 strobe
     input [1:0]   abc_inp_n, // INP, STATUS strobe
-    input 	  abc_xmemfl_n, // Memory read strobe
-    input 	  abc_xmemw800_n, // Memory write strobe (ABC800)
-    input 	  abc_xmemw80_n, // Memory write strobe (ABC80)
-    input 	  abc_xinpstb_n, // I/O read strobe (ABC800)
-    input 	  abc_xoutpstb_n, // I/O write strobe (ABC80)
+    input	  abc_xmemfl_n, // Memory read strobe
+    input	  abc_xmemw800_n, // Memory write strobe (ABC800)
+    input	  abc_xmemw80_n, // Memory write strobe (ABC80)
+    input	  abc_xinpstb_n, // I/O read strobe (ABC800)
+    input	  abc_xoutpstb_n, // I/O write strobe (ABC80)
     // The following are inverted versus the bus IF
     // the corresponding MOSFETs are installed
-    output 	  abc_rdy_x, // RDY = WAIT#
-    output 	  abc_resin_x, // System reset request
-    output 	  abc_int80_x, // System INT request (ABC80)
-    output 	  abc_int800_x, // System INT request (ABC800)
-    output 	  abc_nmi_x, // System NMI request (ABC800)
-    output 	  abc_xm_x, // System memory override (ABC800)
+    output	  abc_rdy_x, // RDY = WAIT#
+    output	  abc_resin_x, // System reset request
+    output	  abc_int80_x, // System INT request (ABC80)
+    output	  abc_int800_x, // System INT request (ABC800)
+    output	  abc_nmi_x, // System NMI request (ABC800)
+    output	  abc_xm_x, // System memory override (ABC800)
     // Host/device control
-    output 	  abc_host, // 1 = host, 0 = target
-    output 	  abc_a_oe,
+    output	  abc_host, // 1 = host, 0 = target
+    output	  abc_a_oe,
     // Bus isolation
-    output 	  abc_d_ce_n,
+    output	  abc_d_ce_n,
 
     // ABC-bus extension header
     // (Note: cannot use an array here because HC and HH are
     // input only.)
-    inout 	  exth_ha,
-    inout 	  exth_hb,
-    input 	  exth_hc,
-    inout 	  exth_hd,
-    inout 	  exth_he,
-    inout 	  exth_hf,
-    inout 	  exth_hg,
-    input 	  exth_hh,
+    inout	  exth_ha,
+    inout	  exth_hb,
+    input	  exth_hc,
+    inout	  exth_hd,
+    inout	  exth_he,
+    inout	  exth_hf,
+    inout	  exth_hg,
+    input	  exth_hh,
 
     // SDRAM bus
-    output 	  sr_clk,
-    output 	  sr_cke,
+    output	  sr_clk,
+    output	  sr_cke,
     output [1:0]  sr_ba, // Bank address
     output [12:0] sr_a, // Address within bank
     inout [15:0]  sr_dq, // Also known as D or IO
     output [1:0]  sr_dqm, // DQML and DQMH
-    output 	  sr_cs_n,
-    output 	  sr_we_n,
-    output 	  sr_cas_n,
-    output 	  sr_ras_n,
+    output	  sr_cs_n,
+    output	  sr_we_n,
+    output	  sr_cas_n,
+    output	  sr_ras_n,
 
     // SD card
-    output 	  sd_clk,
-    output 	  sd_cmd,
+    output	  sd_clk,
+    output	  sd_cmd,
     inout [3:0]   sd_dat,
 
     // Serial console (naming is FPGA as DCE)
-    input 	  tty_txd,
-    output 	  tty_rxd,
-    input 	  tty_rts,
-    output 	  tty_cts,
-    input 	  tty_dtr,
+    input	  tty_txd,
+    output	  tty_rxd,
+    input	  tty_rts,
+    output	  tty_cts,
+    input	  tty_dtr,
 
     // SPI flash memory (also configuration)
-    output 	  flash_cs_n,
-    output 	  flash_sck,
+    output	  flash_cs_n,
+    output	  flash_sck,
     inout [1:0]   flash_io,
 
     // SPI bus (connected to ESP32 so can be bidirectional)
-    inout 	  spi_clk,	  // ESP32 IO12
+    inout	  spi_clk,	  // ESP32 IO12
     inout [1:0]   spi_io,	  // ESP32 IO13,IO11
-    inout 	  spi_cs_esp_n,   // ESP32 IO10
-    inout 	  spi_cs_flash_n, // ESP32 IO01
+    inout	  spi_cs_esp_n,   // ESP32 IO10
+    inout	  spi_cs_flash_n, // ESP32 IO01
 
     // Other ESP32 connections
-    inout 	  esp_io0,        // ESP32 IO00
-    inout 	  esp_int,        // ESP32 IO09
+    inout	  esp_io0,        // ESP32 IO00
+    inout	  esp_int,        // ESP32 IO09
 
     // I2C bus (RTC and external)
-    inout 	  i2c_scl,
-    inout 	  i2c_sda,
-    input 	  rtc_32khz,
-    input 	  rtc_int_n,
+    inout	  i2c_scl,
+    inout	  i2c_sda,
+    input	  rtc_32khz,
+    input	  rtc_int_n,
 
     // LED (2 = D23/G, 1 = D22/R, 0 = D17/B)
     output [2:0]  led,
@@ -104,10 +104,10 @@ module v1
 
     // HDMI
     output [2:0]  hdmi_d,
-    output 	  hdmi_clk,
-    inout 	  hdmi_scl,
-    inout 	  hdmi_sda,
-    inout 	  hdmi_hpd,
+    output	  hdmi_clk,
+    inout	  hdmi_scl,
+    inout	  hdmi_sda,
+    inout	  hdmi_hpd,
 
     // Unconnected pins with pullups, used for randomness
     inout [2:0]   rngio
@@ -132,7 +132,7 @@ module v1
    wire		  reset_plls;
    wire		  master_pll_locked;
    wire		  master_clk;	// 336 MHz
-   wire 	  slow_clk;	//  12 MHz
+   wire		  slow_clk;	//  12 MHz
 
    pll2_48 pll2 (
 		 .areset ( reset_plls ),

+ 5 - 5
fpga/vjtag_max80.sv

@@ -179,7 +179,7 @@ module vjtag_max80
    reg	       mem_error;	// Memory underrun
    reg	       advance_mem_addr;
    reg	       mem_header_done;
-   reg 	       mem_do_write;
+   reg	       mem_do_write;
 
    reg	       tck_q;
    reg	       tck_stb;
@@ -196,7 +196,7 @@ module vjtag_max80
    reg [4:0] sdr_ctr;
 
    // Main data shift register.
-   reg 	       v_st_sdr_q;
+   reg	       v_st_sdr_q;
    reg  [31:0] jtag_shr;
    wire [31:0] jtag_shr_in = ir_cmd[3]
 	       ? { tdi_s, jtag_shr[31:1] } :
@@ -253,7 +253,7 @@ module vjtag_max80
 	     // be reading the fetched data.
 
 	     mem_do_write <= 1'b0;
-	     
+
 	     if ( ir_cmd[3:1] == cmd_memwr )
 	       begin
 		  if ( v_st_cdr )
@@ -302,7 +302,7 @@ module vjtag_max80
 				 // to reading the new word
 				 if ( ~ir_ro )
 				   jtag_memaddr     <= mem_addr;
-				 
+
 				 advance_mem_addr   <= mem_header_done;
 			      end
 			      5'd3:
@@ -324,7 +324,7 @@ module vjtag_max80
 			 end // else: !if( ~mem_write )
 		    end // if ( st_sdr_s )
 	       end // if ( ir_cmd[3:1] == cmd_memwr )
-	     
+
 	     if ( mem_do_write )
 	       begin
 		  mem_wdata          <= jtag_shr_in;

+ 1 - 1
rv32/checksum.h

@@ -1,4 +1,4 @@
 #ifndef CHECKSUM_H
 #define CHECKSUM_H
-#define SDRAM_SUM 0xfc05d140
+#define SDRAM_SUM 0x9b2d9673
 #endif

+ 1 - 1
rv32/common.h

@@ -61,7 +61,7 @@ extern void mount_abcdrives(void);
 
 extern void read_rtc(void);
 extern void write_rtc(void);
-extern bool do_write_rtc;
+extern volatile bool do_write_rtc;
 extern void rtc_abc_init(void);
 extern void rtc_abc_io_poll(void);
 extern void disk_cache_init(void);

+ 17 - 2
rv32/esp.c

@@ -5,6 +5,7 @@
 #include "esplink.h"
 
 struct esplink_head __esplink_head esplink_head;
+static volatile struct esplink_timesync tsync;
 
 uint32_t __esplink esplink[(65536 - sizeof(struct esplink_head)) >> 2];
 
@@ -16,6 +17,19 @@ IRQHANDLER(esp,0)
     if (irqstatus & (1 << EL_DIRQ_UNDERRUN)) {
 	con_printf("[ESP] ESP link memory underrun!!\n");
 	ESP_SPI_IRQ = (1 << EL_UIRQ_READY); /* Block writes, reinitialize! */
+	return;
+    }
+    if (irqstatus & (1 << EL_DIRQ_TIME)) {
+	if (tsync.set.update) {
+	    SYSCLOCK_TICK_HOLD = tsync.set.tick;
+	    SYSCLOCK_DATETIME  = tsync.set.td;
+	    tsync.set.update = 0;
+	    do_write_rtc = true;
+	} else {
+	    tsync.get.td   = SYSCLOCK_DATETIME;
+	    tsync.get.tick = SYSCLOCK_TICK_HOLD;
+	    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_TIME;
+	}
     }
     if (irqstatus & (1 << EL_DIRQ_HELLO)) {
 	con_printf("[ESP] Got hello, sending ready...\n");
@@ -35,8 +49,9 @@ void esp_init(void)
     
     esplink_head.hlen          = sizeof esplink_head;
     esplink_head.board.cfg     = SYS_BOARDCFG;
-    esplink_head.signature     = esp_signature;
-    esplink_head.signature_len = sizeof esp_signature - 1;
+    memcpy(esplink_head.signature, esp_signature, sizeof esp_signature);
+
+    esplink_head.tsync         = &tsync;
 
     esplink_head.magic         = ESPLINK_HEAD_MAGIC;
     ESP_SPI_IRQ                = (1 << EL_UIRQ_READY);

+ 12 - 5
rv32/rtc.c

@@ -85,6 +85,13 @@ static int i2c_start_rtc(void)
     return 0;
 }
 
+static void con_time(const char *what, struct tms tms)
+{
+    con_printf("RTC time %s: %04u-%02u-%02u %02u:%02u:%02u\n",
+	       what, tms.tm_year + 1980, tms.tm_mon, tms.tm_mday,
+	       tms.tm_hour, tms.tm_min, tms_sec(tms));
+}
+
 void read_rtc(void)
 {
     uint8_t rtc_regs[RTC_TIME_REGS];
@@ -137,12 +144,10 @@ void read_rtc(void)
 	con_printf(" %02x", rtc_regs[i]);
 #endif
 
-    con_printf("RTC time: %04u-%02u-%02u %02u:%02u:%02u\n",
-	       tms.tm_year + 1980, tms.tm_mon, tms.tm_mday,
-	       tms.tm_hour, tms.tm_min, tms_sec(tms));
+    con_time("read", tms);
 }
 
-bool do_write_rtc;
+volatile bool do_write_rtc;
 
 void write_rtc(void)
 {
@@ -151,7 +156,6 @@ void write_rtc(void)
     struct tms tms;
     int sda_retry_count = 16;
 
-    do_write_rtc = false;
     tms = get_systime();
 
     rtc_regs[0] = tobcd(tms_sec(tms));
@@ -171,4 +175,7 @@ void write_rtc(void)
 	i2c_send(rtc_regs[i], 0);
 
     i2c_send(rtc_regs[i], I2C_P);
+
+    con_time("set", tms);
+    do_write_rtc = false;
 }

+ 5 - 0
rv32/systime.h

@@ -9,6 +9,7 @@
 #define SYSTIME_H
 
 #include <stdint.h>
+#include "irq.h"
 
 struct tms_tick_reg {
     union {
@@ -40,16 +41,20 @@ static inline struct tms get_systime(void)
 {
     struct tms tms;
 
+    irqmask_t mask = mask_irq(ESP_IRQ);
     tms.words[0] = SYSCLOCK_DATETIME;
     tms.words[1] = SYSCLOCK_TICK;
+    restore_irqs(mask);
 
     return tms;
 }
 
 static inline void set_systime(struct tms tms)
 {
+    irqmask_t mask = mask_irq(ESP_IRQ);
     SYSCLOCK_TICK_HOLD = tms.hold.ticks;
     SYSCLOCK_DATETIME  = tms.words[0];
+    restore_irqs(mask);
 }
 
 static inline unsigned int tms_sec(struct tms tms)