Kaynağa Gözat

projects/usb_amr: Initial import

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 3 yıl önce
ebeveyn
işleme
931a315f06
37 değiştirilmiş dosya ile 7337 ekleme ve 0 silme
  1. 43 0
      projects/usb_amr/Makefile
  2. 16 0
      projects/usb_amr/README.md
  3. 3 0
      projects/usb_amr/data/clocks.py
  4. 33 0
      projects/usb_amr/data/top-bitsy-v0.pcf
  5. 33 0
      projects/usb_amr/data/top-bitsy-v1.pcf
  6. 48 0
      projects/usb_amr/data/top-icebreaker.pcf
  7. BIN
      projects/usb_amr/doc/ac97_r23.pdf
  8. BIN
      projects/usb_amr/doc/amr.pdf
  9. 45 0
      projects/usb_amr/doc/mem-map.md
  10. BIN
      projects/usb_amr/doc/si3038_si3014_si3024_softmodem_ac97.pdf
  11. 5 0
      projects/usb_amr/fw/.gitignore
  12. 92 0
      projects/usb_amr/fw/Makefile
  13. 680 0
      projects/usb_amr/fw/audio.c
  14. 30 0
      projects/usb_amr/fw/audio.h
  15. 121 0
      projects/usb_amr/fw/cdc-dlm.c
  16. 13 0
      projects/usb_amr/fw/cdc-dlm.h
  17. 31 0
      projects/usb_amr/fw/config.h
  18. 181 0
      projects/usb_amr/fw/fw_app.c
  19. 343 0
      projects/usb_amr/fw/mc97.c
  20. 68 0
      projects/usb_amr/fw/mc97.h
  21. 87 0
      projects/usb_amr/fw/mc97_country.h
  22. 418 0
      projects/usb_amr/fw/usb_desc_app.c
  23. 8 0
      projects/usb_amr/fw/usb_str_app.txt
  24. 162 0
      projects/usb_amr/rtl/dfu_helper.v
  25. 420 0
      projects/usb_amr/rtl/mc97.v
  26. 78 0
      projects/usb_amr/rtl/mc97_fifo.v
  27. 310 0
      projects/usb_amr/rtl/mc97_wb.v
  28. 2979 0
      projects/usb_amr/rtl/picorv32.v
  29. 43 0
      projects/usb_amr/rtl/picorv32_ice40_regs.v
  30. 38 0
      projects/usb_amr/rtl/soc_bram.v
  31. 157 0
      projects/usb_amr/rtl/soc_picorv32_base.v
  32. 186 0
      projects/usb_amr/rtl/soc_picorv32_bridge.v
  33. 43 0
      projects/usb_amr/rtl/soc_spram.v
  34. 157 0
      projects/usb_amr/rtl/soc_usb.v
  35. 80 0
      projects/usb_amr/rtl/sysmgr.v
  36. 274 0
      projects/usb_amr/rtl/top.v
  37. 112 0
      projects/usb_amr/sim/mc97_tb.v

+ 43 - 0
projects/usb_amr/Makefile

@@ -0,0 +1,43 @@
+# Project config
+PROJ = usb_amr
+
+PROJ_DEPS := no2usb no2misc no2ice40
+PROJ_RTL_SRCS := $(addprefix rtl/, \
+	mc97.v \
+	mc97_fifo.v \
+	mc97_wb.v \
+	dfu_helper.v \
+	picorv32.v \
+	picorv32_ice40_regs.v \
+	soc_bram.v \
+	soc_picorv32_base.v \
+	soc_picorv32_bridge.v \
+	soc_spram.v \
+	soc_usb.v \
+	sysmgr.v \
+)
+PROJ_TESTBENCHES := \
+	mc97_tb \
+	$(NULL)
+PROJ_PREREQ = \
+	$(BUILD_TMP)/boot.hex
+PROJ_TOP_SRC := rtl/top.v
+PROJ_TOP_MOD := top
+
+# Target config
+BOARD ?= icebreaker
+DEVICE = up5k
+PACKAGE = sg48
+
+YOSYS_SYNTH_ARGS = -dffe_min_ce_use 4
+NEXTPNR_ARGS = --pre-pack data/clocks.py --seed 2
+
+# Include default rules
+include ../../build/project-rules.mk
+
+# Custom rules
+fw/boot.hex:
+	make -C fw boot.hex
+
+$(BUILD_TMP)/boot.hex: fw/boot.hex
+	cp $< $@

+ 16 - 0
projects/usb_amr/README.md

@@ -0,0 +1,16 @@
+USB AMR interface (using Audio Class)
+=====================================
+
+For the icebreaker, the USB hardware connections are :
+
+  * `P1B4`: USB DP
+  * `P1B3`: USB DN
+  * `P1B2`: Pull up. Resistor of 1.5 kOhm to USB DP 
+
+and the AMR connections are :
+
+  * `P1A1`  : `AC97_RESET#`
+  * `P1A7`  : `AC97_SDATA_OUT`
+  * `P1A8`  : `AC97_SDATA_IN`
+  * `P1A9`  : `AC97_SYNC`
+  * `P1A10` : `AC97_BITCLK`

+ 3 - 0
projects/usb_amr/data/clocks.py

@@ -0,0 +1,3 @@
+ctx.addClock("clk_24m", 24)
+ctx.addClock("clk_48m", 48)
+ctx.addClock("mc97_bitclk", 13)

+ 33 - 0
projects/usb_amr/data/top-bitsy-v0.pcf

@@ -0,0 +1,33 @@
+# SPI
+set_io -nowarn spi_mosi 14
+set_io -nowarn spi_miso 17
+set_io -nowarn spi_clk 15
+set_io -nowarn spi_cs_n 16
+
+# USB
+set_io -nowarn usb_dp 43
+set_io -nowarn usb_dn 42
+set_io -nowarn usb_pu 38
+
+# UART
+set_io -nowarn -pullup yes uart_rx 18
+set_io -nowarn uart_tx 19
+
+# Clock
+set_io -nowarn clk_in 35
+
+# Button
+set_io -nowarn -pullup yes btn 10
+
+# Leds
+set_io -nowarn rgb[0] 39
+set_io -nowarn rgb[1] 40
+set_io -nowarn rgb[2] 41
+
+# AMR
+  # FIXME: pick IOs
+set_io -nowarn mc97_reset_n
+set_io -nowarn mc97_sdata_out
+set_io -nowarn mc97_sdata_in
+set_io -nowarn mc97_sync
+set_io -nowarn mc97_bitclk

+ 33 - 0
projects/usb_amr/data/top-bitsy-v1.pcf

@@ -0,0 +1,33 @@
+# SPI
+set_io -nowarn spi_mosi 14
+set_io -nowarn spi_miso 17
+set_io -nowarn spi_clk 15
+set_io -nowarn spi_cs_n 16
+
+# USB
+set_io -nowarn usb_dp 42
+set_io -nowarn usb_dn 38
+set_io -nowarn usb_pu 37
+
+# UART
+set_io -nowarn -pullup yes uart_rx 47
+set_io -nowarn uart_tx 44
+
+# Clock
+set_io -nowarn clk_in 35
+
+# Button
+set_io -nowarn -pullup yes btn 2
+
+# Leds
+set_io -nowarn rgb[0] 39
+set_io -nowarn rgb[1] 40
+set_io -nowarn rgb[2] 41
+
+# AMR
+  # FIXME: pick IOs
+set_io -nowarn mc97_reset_n
+set_io -nowarn mc97_sdata_out
+set_io -nowarn mc97_sdata_in
+set_io -nowarn mc97_sync
+set_io -nowarn mc97_bitclk

+ 48 - 0
projects/usb_amr/data/top-icebreaker.pcf

@@ -0,0 +1,48 @@
+# SPI
+set_io -nowarn spi_mosi 14
+set_io -nowarn spi_miso 17
+set_io -nowarn spi_clk 15
+set_io -nowarn spi_cs_n 16
+
+# USB
+ # PMOD 1A
+#set_io -nowarn usb_dp 47
+#set_io -nowarn usb_dn 45
+#set_io -nowarn usb_pu  4
+
+ # PMOD 1B
+#set_io -nowarn usb_dp 34
+#set_io -nowarn usb_dn 31
+#set_io -nowarn usb_pu 43
+
+ # PMOD 2
+#set_io -nowarn usb_dp 21
+#set_io -nowarn usb_dn 19
+#set_io -nowarn usb_pu 27
+
+ # tnt's cable
+set_io -nowarn usb_dp 31
+set_io -nowarn usb_dn 34
+set_io -nowarn usb_pu 38
+
+# UART
+set_io -nowarn -pullup yes uart_rx 6
+set_io -nowarn uart_tx 9
+
+# Clock
+set_io -nowarn clk_in 35
+
+# Button
+set_io -nowarn btn 10
+
+# Leds
+set_io -nowarn rgb[0] 39
+set_io -nowarn rgb[1] 40
+set_io -nowarn rgb[2] 41
+
+# AMR
+set_io -nowarn mc97_reset_n 4
+set_io -nowarn mc97_sdata_out 3
+set_io -nowarn mc97_sdata_in 48
+set_io -nowarn mc97_sync 46
+set_io -nowarn mc97_bitclk 44 

BIN
projects/usb_amr/doc/ac97_r23.pdf


BIN
projects/usb_amr/doc/amr.pdf


+ 45 - 0
projects/usb_amr/doc/mem-map.md

@@ -0,0 +1,45 @@
+AC97 controller registers
+=========================
+
+```
+`0x00`	CSR
+		[2]	(RW) GPIO ena
+		[1]	(RW) AC97_reset_n
+		[0]	(RW) Run
+
+`0x01`	Low Level Status
+		[31]	(R)  Codec Ready
+		[28:16] (R)  Slot Request (bit 28=Slot 12, bit 16=Slot 0)
+		[12:0]	(R)  Slot Valid   (bit 12=Slot 12, bit  0=Slot 0)
+		(Write clears)
+
+`0x02`	Codec Register Access
+		[31]	(R)  Busy
+		[30]	(RW) Write Enable
+		[29]    (R)  Read Error flag
+		[21:16] (RW) Register Address
+		[15:0]  (RW) Register Value
+
+`0x04`	Codec GPIO Input
+		[19:0]	(R)  GPIO Input
+
+`0x05`	Codec GPIO Output
+		[19:0]	(RW) GPIO Output
+
+`0x06`	FIFO Data
+		[31]	(R)  Empty flag (for read)
+		[15:0] 	(RW) PCM 16 bits signed
+
+`0x07`	FIFO Control/Status
+		[31]	(RW) FIFO PCM In - Enable
+		[30]	(RW) FIFO PCM In - Flush
+		[29]    (R)  FIFO PCM In - Full
+		[28]    (R)  FIFO PCM In - Empty
+		[27:16]	(R)  FIFO PCM In - Level
+
+		[15]    (RW) FIFO PCM Out - Enable
+		[14]    (RW) FIFO PCM Out - Flush
+		[13]    (R)  FIFO PCM Out - Full
+		[12]    (R)  FIFO PCM Out - Empty
+		[11:0]  (R)  FIFO PCM Out - Level
+```

BIN
projects/usb_amr/doc/si3038_si3014_si3024_softmodem_ac97.pdf


+ 5 - 0
projects/usb_amr/fw/.gitignore

@@ -0,0 +1,5 @@
+*.bin
+*.elf
+*.hex
+*.o
+*.gen.h

+ 92 - 0
projects/usb_amr/fw/Makefile

@@ -0,0 +1,92 @@
+BOARD ?= icebreaker
+CROSS ?= riscv-none-embed-
+CC = $(CROSS)gcc
+OBJCOPY = $(CROSS)objcopy
+ICEPROG = iceprog
+DFU_UTIL = dfu-util
+
+BOARD_DEFINE=BOARD_$(shell echo $(BOARD) | tr a-z\- A-Z_)
+CFLAGS=-Wall -Wextra -Wno-unused-parameter -Os -march=rv32i -mabi=ilp32 -ffreestanding -flto -nostartfiles -fomit-frame-pointer -Wl,--gc-section --specs=nano.specs -D$(BOARD_DEFINE) -I.
+
+
+# Common / Shared
+COMMON_PATH=../../riscv_usb/fw/
+CFLAGS += -I$(COMMON_PATH)
+
+HEADERS_common=$(addprefix $(COMMON_PATH), \
+	console.h \
+	led.h \
+	mini-printf.h \
+	spi.h \
+	utils.h \
+)
+
+SOURCES_common=$(addprefix $(COMMON_PATH), \
+	start.S \
+	console.c \
+	led.c \
+	mini-printf.c  \
+	spi.c \
+	utils.c \
+)
+
+
+# USB
+NO2USB_FW_VERSION=0
+include ../../../cores/no2usb/fw/fw.mk
+CFLAGS += $(INC_no2usb)
+
+SOURCES_common += $(SOURCES_no2usb)
+HEADERS_common += $(HEADERS_no2usb)
+
+
+# Local
+HEADERS_app=\
+	config.h \
+	audio.h \
+	cdc-dlm.h \
+	mc97.h \
+	usb_str_app.gen.h \
+	$(NULL)
+
+SOURCES_app=\
+	audio.c \
+	cdc-dlm.c \
+	mc97.c \
+	fw_app.c \
+	usb_desc_app.c \
+	$(NULL)
+
+
+all: boot.hex fw_app.bin
+
+
+boot.elf: $(COMMON_PATH)/lnk-boot.lds $(COMMON_PATH)/boot.S
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,$(COMMON_PATH)/lnk-boot.lds,--strip-debug -DFLASH_APP_ADDR=0x000a0000 -o $@ $(COMMON_PATH)/boot.S
+
+fw_app.elf: $(COMMON_PATH)/lnk-app.lds $(HEADERS_app) $(SOURCES_app) $(HEADERS_common) $(SOURCES_common)
+	$(CC) $(CFLAGS) -Wl,-Bstatic,-T,$(COMMON_PATH)/lnk-app.lds,--strip-debug -o $@ $(SOURCES_common) $(SOURCES_app)
+
+
+%.hex: %.bin
+	$(COMMON_PATH)/bin2hex.py $< $@
+
+%.bin: %.elf
+	$(OBJCOPY) -O binary $< $@
+
+
+prog: fw_app.bin
+	$(ICEPROG) -o 640k $<
+
+dfuprog: fw_app.bin
+ifeq ($(DFU_SERIAL),)
+	$(DFU_UTIL) -R -a 1 -D $<
+else
+	$(DFU_UTIL) -R -S $(DFU_SERIAL) -a 1 -D $<
+endif
+
+
+clean:
+	rm -f *.bin *.hex *.elf *.o *.gen.h
+
+.PHONY: prog_app clean

+ 680 - 0
projects/usb_amr/fw/audio.c

@@ -0,0 +1,680 @@
+/*
+ * audio.c
+ *
+ * USB Audio class firmware
+ *
+ * Copyright (C) 2020 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "console.h"
+#include "mc97.h"
+
+#include <no2usb/usb.h>
+#include <no2usb/usb_ac_proto.h>
+#include <no2usb/usb_dfu_rt.h>
+#include <no2usb/usb_hw.h>
+#include <no2usb/usb_priv.h>
+
+#include "config.h"
+
+
+#define INTF_AUDIO_CONTROL	1
+#define INTF_AUDIO_DATA_IN	2
+#define INTF_AUDIO_DATA_OUT	3
+#define UNIT_FEAT_PCM_IN	2
+#define UNIT_FEAT_PCM_OUT	5
+
+#define PKT_SIZE_SAMP  60
+#define PKT_SIZE_BYTE 120
+
+
+// PCM Audio
+// ---------------------------------------------------------------------------
+
+static struct {
+	bool    active;
+	uint8_t bdi;
+} g_pcm[2];
+
+
+static void
+pcm_init(void)
+{
+	/* Local state */
+	memset(&g_pcm, 0x00, sizeof(g_pcm));
+
+	/* Init MC97 */
+	mc97_init();
+}
+
+
+// PCM Audio USB helpers
+// ---------------------------------------------------------------------------
+
+static int
+_idx_from_req(struct usb_ctrl_req *req)
+{
+	int unit_id = (req->wIndex >> 8) & 0xff;
+	int chan    = req->wValue & 0xff;
+
+	if (chan != 0)
+		return -1;
+
+	switch (unit_id)
+	{
+	case UNIT_FEAT_PCM_IN:  return 0;
+	case UNIT_FEAT_PCM_OUT: return 1;
+	}
+
+	return -1;
+}
+
+
+// PCM Audio USB data
+// ---------------------------------------------------------------------------
+
+static void
+pcm_usb_configure(const struct usb_conf_desc *conf)
+{
+	const struct usb_intf_desc *intf;
+
+	/* Reset state */
+	g_pcm[0].bdi = 0;
+	g_pcm[1].bdi = 0;
+
+	/* Boot PCM input */
+	intf = usb_desc_find_intf(conf, INTF_AUDIO_DATA_IN, 0, NULL);
+        usb_ep_boot(intf, 0x81, true);
+
+	/* Boot PCM output */
+	intf = usb_desc_find_intf(conf, INTF_AUDIO_DATA_OUT, 0, NULL);
+        usb_ep_boot(intf, 0x01, true);
+        usb_ep_boot(intf, 0x82, false);
+
+	/* MC97 flow reset */
+	mc97_flow_rx_reset();
+	mc97_flow_tx_reset();
+}
+
+static bool
+pcm_usb_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
+{
+	switch (base->bInterfaceNumber)
+	{
+	case INTF_AUDIO_DATA_IN:
+		/* If same state, don't do anything */
+		if (sel->bAlternateSetting == g_pcm[0].active)
+			break;
+
+		g_pcm[0].active = sel->bAlternateSetting;
+
+		/* Reset BDI and reconfigure EPs */
+		g_pcm[0].bdi = 0;
+		usb_ep_reconf(sel, 0x81);
+
+		/* MC97 data flow */
+		if (!g_pcm[0].active)
+			mc97_flow_rx_reset();
+		else
+			mc97_flow_rx_start();
+
+		break;
+
+	case INTF_AUDIO_DATA_OUT:
+		/* If same state, don't do anything */
+		if (sel->bAlternateSetting == g_pcm[1].active)
+			break;
+
+		g_pcm[1].active = sel->bAlternateSetting;
+
+		/* Reset BDI and reconfigure EPs */
+		g_pcm[1].bdi = 0;
+		usb_ep_reconf(sel, 0x01);
+		usb_ep_reconf(sel, 0x82);
+
+		/* MC97 data flow */
+		if (!g_pcm[1].active)
+			mc97_flow_tx_reset();
+
+		/* If active, pre-queue two buffers */
+		if (g_pcm[1].active) {
+			usb_ep_regs[1].out.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(PKT_SIZE_BYTE);
+			usb_ep_regs[1].out.bd[1].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(PKT_SIZE_BYTE);
+		}
+
+		break;
+
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+pcm_usb_get_intf(const struct usb_intf_desc *base, uint8_t *alt)
+{
+	switch (base->bInterfaceNumber)
+	{
+	case INTF_AUDIO_DATA_IN:
+		*alt = g_pcm[0].active;
+		break;
+
+	case INTF_AUDIO_DATA_OUT:
+		*alt = g_pcm[1].active;
+		break;
+
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+
+static void
+pcm_poll_feedback_ep(void)
+{
+	static int rate;
+	uint32_t val;
+
+	/* If not active, reset state */
+	if (!g_pcm[1].active) {
+		rate = 8 * 16384;
+		return;
+	}
+
+	/* Fetch current level and flow active status */
+	int lvl     = mc97_flow_tx_level();
+	bool active = mc97_flow_tx_active();
+
+	/* If flow isn't running, don't run the algo */
+	if (!active)
+		return;
+
+	/* If previous packet isn't sent, don't run the algo */
+	if ((usb_ep_regs[2].in.bd[0].csr & USB_BD_STATE_MSK) == USB_BD_STATE_RDY_DATA)
+		return;
+
+	/* Level alerts */
+	if ((lvl < 32) || (lvl > 224))
+		printf("LEVEL ALERT: %d (%d)\n", lvl, (rate >> 14));
+
+
+	/* Adapt the rate depending on fifo level */
+	rate += ((MC97_FIFO_SIZE / 2) - lvl) << 4;
+
+	if (rate > (9 * 16384))
+		rate = 9 * 16384;
+	else if (rate < (7 * 16384))
+		rate =  7 * 16384;
+
+	/* Set rate */
+	val = rate;
+
+	/* Prepare buffer */
+	usb_data_write(usb_ep_regs[2].in.bd[0].ptr, &val, 4);
+	usb_ep_regs[2].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(3);
+}
+
+
+static void
+pcm_poll_in(void)
+{
+	int16_t buf[PKT_SIZE_SAMP];
+
+	/* Active ? */
+	if (!g_pcm[0].active)
+		return;
+
+	/* Fill as many BDs as we can */
+	while (1) {
+		uint32_t csr = usb_ep_regs[1].in.bd[g_pcm[0].bdi].csr;
+		uint32_t ptr = usb_ep_regs[1].in.bd[g_pcm[0].bdi].ptr;
+
+		/* Is that BD free ? */
+		if ((csr & USB_BD_STATE_MSK) == USB_BD_STATE_RDY_DATA)
+			break;
+
+		/* Read data from MC97 link */
+		int n = mc97_flow_rx_pull(buf, PKT_SIZE_SAMP);
+		if (!n)
+			break;
+
+		/* Submit what we got */
+		usb_data_write(ptr, buf, PKT_SIZE_BYTE);
+		usb_ep_regs[1].in.bd[g_pcm[0].bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(n*2);
+		g_pcm[0].bdi ^= 1;
+
+		/* If packet wasn't full, wait for the next iteration */
+		if (n < PKT_SIZE_SAMP)
+			break;
+	}
+}
+
+static void
+pcm_poll_out(void)
+{
+	int16_t buf[PKT_SIZE_SAMP];
+
+	/* Active ? */
+	if (!g_pcm[1].active)
+		return;
+
+	/* Starting level */
+	int  lvl    = mc97_flow_tx_level();
+	bool active = mc97_flow_tx_active();
+
+	/* Refill process ? */
+	if (!lvl & active)
+		mc97_flow_tx_stop();
+
+	/* Empty as many BDs as we can */
+	while (1) {
+		uint32_t csr = usb_ep_regs[1].out.bd[g_pcm[1].bdi].csr;
+		uint32_t ptr = usb_ep_regs[1].out.bd[g_pcm[1].bdi].ptr;
+
+		/* Is that BD pending ? */
+		if ((csr & USB_BD_STATE_MSK) == USB_BD_STATE_RDY_DATA)
+			break;
+
+		/* Pull valid data */
+		if ((csr & USB_BD_STATE_MSK) == USB_BD_STATE_DONE_OK)
+		{
+			int n = ((csr & USB_BD_LEN_MSK) - 2) / 2; /* Reported length includes CRC */
+
+			/* If it doesn't fit, we're done for now */
+			if ((lvl + n) > MC97_FIFO_SIZE)
+				break;
+
+			lvl += n;
+
+			/* Read and push */
+			if (n) {
+				usb_data_read(buf, ptr, PKT_SIZE_BYTE);
+				mc97_flow_tx_push(buf, n);
+			}
+		}
+
+		/* Reprepare and move on */
+		usb_ep_regs[1].out.bd[g_pcm[1].bdi].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(PKT_SIZE_BYTE);
+		g_pcm[1].bdi ^= 1;
+	}
+
+	/* Delayed enable */
+	if ((lvl > (MC97_FIFO_SIZE/2)) && !active)
+		mc97_flow_tx_start();
+}
+
+static void
+pcm_poll(void)
+{
+	pcm_poll_in();
+	pcm_poll_out();
+	pcm_poll_feedback_ep();
+}
+
+
+// PCM Audio USB control
+// ---------------------------------------------------------------------------
+
+static bool
+pcm_usb_mute_set(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	int idx = _idx_from_req(req);
+
+	switch (idx) {
+	case 0: mc97_set_rx_mute(data[0]); return true;
+	case 1: mc97_set_tx_mute(data[0]); return true;
+	}
+
+	return false;
+}
+
+static bool
+pcm_usb_mute_get(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	int idx = _idx_from_req(req);
+
+	switch (idx) {
+	case 0: data[0] = mc97_get_rx_mute(); return true;
+	case 1: data[0] = mc97_get_tx_mute(); return true;
+	}
+
+	return false;
+}
+
+#define BOUND(x, a, b) (((x)<(a))?(a):(((x)>(b))?(b):(x)))
+
+static bool
+pcm_usb_volume_set(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	int idx = _idx_from_req(req);
+	int16_t vol = *((int16_t*)data);
+
+	switch (idx) {
+	case 0:
+		vol = BOUND(vol, 0, 5760) >> 7;
+		mc97_set_rx_gain(vol);
+		return true;
+
+	case 1:
+		vol = BOUND(-vol, 0, 5760) >> 7;
+		mc97_set_tx_attenuation(vol);
+		return true;
+	}
+
+	return false;
+}
+
+static bool
+pcm_usb_volume_get(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	int idx = _idx_from_req(req);
+
+	switch (idx) {
+	case 0: *((int16_t*)data) =  (mc97_get_rx_gain()        << 7); return true;
+	case 1: *((int16_t*)data) = -(mc97_get_tx_attenuation() << 7); return true;
+	}
+
+	return false;
+}
+
+static bool
+pcm_usb_volume_min(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	const int16_t max[] = { 0 /* 0 dB gain */, -5760 /* 22.5 dB attenuation */ };
+	int idx;
+
+	if ((idx = _idx_from_req(req)) < 0)
+		return false;
+
+	*((int16_t*)data) = max[idx];
+
+	return true;
+}
+
+static bool
+pcm_usb_volume_max(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	const int16_t max[] = { 5760 /* 22.5 dB gain */, 0 /* 0 dB attenuation */ };
+	int idx;
+
+	if ((idx = _idx_from_req(req)) < 0)
+		return false;
+
+	*((int16_t*)data) = max[idx];
+
+	return true;
+}
+
+static bool
+pcm_usb_volume_res(struct usb_ctrl_req *req, uint8_t *data, int *len)
+{
+	const int16_t res[] = { 384, 384 }; /* 1.5 dB resolution */
+	int idx;
+
+	if ((idx = _idx_from_req(req)) < 0)
+		return false;
+
+	*((int16_t*)data) = res[idx];
+
+	return true;
+}
+
+
+// Shared USB driver
+// ---------------------------------------------------------------------------
+
+/* Control handler structs */
+
+typedef bool (*usb_audio_control_fn)(struct usb_ctrl_req *req, uint8_t *data, int *len);
+
+struct usb_audio_control_handler {
+	int len;
+	usb_audio_control_fn set_cur;
+	usb_audio_control_fn get_cur;
+	usb_audio_control_fn get_min;
+	usb_audio_control_fn get_max;
+	usb_audio_control_fn get_res;
+};
+
+struct usb_audio_req_handler {
+	uint8_t rcpt;		/* USB_REQ_RCPT_INTF or USB_REQ_RCPT_EP */
+	uint8_t idx;		/* Interface or EP index */
+	uint8_t entity_id;
+	uint16_t val_match;
+	uint16_t val_mask;
+	const struct usb_audio_control_handler *h;
+};
+
+
+/* Control handlers for this implementation */
+
+static const struct usb_audio_control_handler _uac_mute = {	/* USB_AC_FU_CONTROL_MUTE */
+	.len		= 1,
+	.set_cur	= pcm_usb_mute_set,
+	.get_cur	= pcm_usb_mute_get,
+};
+
+static const struct usb_audio_control_handler _uac_volume = {	/* USB_AC_FU_CONTROL_VOLUME */
+	.len		= 2,
+	.set_cur	= pcm_usb_volume_set,
+	.get_cur	= pcm_usb_volume_get,
+	.get_min	= pcm_usb_volume_min,
+	.get_max	= pcm_usb_volume_max,
+	.get_res	= pcm_usb_volume_res,
+};
+
+static const struct usb_audio_req_handler _uac_handlers[] = {
+	{ USB_REQ_RCPT_INTF, INTF_AUDIO_CONTROL, UNIT_FEAT_PCM_IN,  (USB_AC_FU_CONTROL_MUTE   << 8), 0xff00, &_uac_mute   },
+	{ USB_REQ_RCPT_INTF, INTF_AUDIO_CONTROL, UNIT_FEAT_PCM_IN,  (USB_AC_FU_CONTROL_VOLUME << 8), 0xff00, &_uac_volume },
+	{ USB_REQ_RCPT_INTF, INTF_AUDIO_CONTROL, UNIT_FEAT_PCM_OUT, (USB_AC_FU_CONTROL_MUTE   << 8), 0xff00, &_uac_mute   },
+	{ USB_REQ_RCPT_INTF, INTF_AUDIO_CONTROL, UNIT_FEAT_PCM_OUT, (USB_AC_FU_CONTROL_VOLUME << 8), 0xff00, &_uac_volume },
+	{ 0 }
+};
+
+
+/* USB driver implemntation (including control handler dispatch */
+
+static struct {
+	struct usb_ctrl_req *req;
+	usb_audio_control_fn fn;
+} g_cb_ctx;
+
+static bool
+audio_ctrl_req_cb(struct usb_xfer *xfer)
+{
+	struct usb_ctrl_req *req = g_cb_ctx.req;
+	usb_audio_control_fn fn = g_cb_ctx.fn;
+	return fn(req, xfer->data, &xfer->len);
+}
+
+static enum usb_fnd_resp
+audio_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	const struct usb_audio_req_handler *rh;
+
+	/* Check it's a class request */
+	if (USB_REQ_TYPE(req) != USB_REQ_TYPE_CLASS)
+		return USB_FND_CONTINUE;
+
+	/* Check R/W consitency */
+		/* The control request ID mirrors the read flag in the MSB */
+	if ((req->bmRequestType ^ req->bRequest) & 0x80)
+		return USB_FND_ERROR;
+
+	/* Find a matching handler */
+	for (rh=&_uac_handlers[0]; rh->rcpt; rh++)
+	{
+		usb_audio_control_fn fn = NULL;
+
+		/* Check recipient type and index */
+		if (USB_REQ_RCPT(req) != rh->rcpt)
+			continue;
+
+		if ((req->wIndex & 0xff) != rh->idx)
+			continue;
+
+		/* Check Entity ID */
+		if ((req->wIndex >> 8) != rh->entity_id)
+			continue;
+
+		/* Check control */
+		if ((req->wValue & rh->val_mask) != rh->val_match)
+			continue;
+
+		/* We have a match, first check it's not a NOP and check length */
+		if (!rh->h)
+			return USB_FND_ERROR;
+
+		if ((rh->h->len != -1) && (rh->h->len != req->wLength))
+			return USB_FND_ERROR;
+
+		/* Then grab appropriate function */
+		switch (req->bRequest)
+		{
+		case USB_REQ_AC_SET_CUR:
+			fn = rh->h->set_cur;
+			break;
+
+		case USB_REQ_AC_GET_CUR:
+			fn = rh->h->get_cur;
+			break;
+
+		case USB_REQ_AC_GET_MIN:
+			fn = rh->h->get_min;
+			break;
+
+		case USB_REQ_AC_GET_MAX:
+			fn = rh->h->get_max;
+			break;
+
+		case USB_REQ_AC_GET_RES:
+			fn = rh->h->get_res;
+			break;
+
+		default:
+			fn = NULL;
+		}
+
+		if (!fn)
+			return USB_FND_ERROR;
+
+		/* And try to call it */
+		if (USB_REQ_IS_READ(req)) {
+			/* Request is a read, we can call handler immediately */
+			xfer->len = req->wLength;
+			return fn(req, xfer->data, &xfer->len) ? USB_FND_SUCCESS : USB_FND_ERROR;
+		} else {
+			/* Request is a write, we need to hold off until end of data phase */
+			g_cb_ctx.req = req;
+			g_cb_ctx.fn = fn;
+			xfer->len = req->wLength;
+			xfer->cb_done = audio_ctrl_req_cb;
+			return USB_FND_SUCCESS;
+		}
+	}
+
+	return USB_FND_ERROR;
+}
+
+static enum usb_fnd_resp
+audio_set_conf(const struct usb_conf_desc *conf)
+{
+	/* Default PCM interface is inactive */
+	pcm_usb_configure(conf);
+
+	return USB_FND_SUCCESS;
+}
+
+static enum usb_fnd_resp
+audio_set_intf(const struct usb_intf_desc *base, const struct usb_intf_desc *sel)
+{
+	/* Check it's audio class */
+	if (base->bInterfaceClass != USB_CLS_AUDIO)
+		return USB_FND_CONTINUE;
+
+	/* Sub class */
+	switch (base->bInterfaceSubClass)
+	{
+	case USB_AC_SCLS_AUDIOCONTROL:
+		return USB_FND_SUCCESS;
+
+	case USB_AC_SCLS_AUDIOSTREAMING:
+		return pcm_usb_set_intf(base, sel) ? USB_FND_SUCCESS : USB_FND_ERROR;
+
+	default:
+		return USB_FND_ERROR;
+	}
+}
+
+static enum usb_fnd_resp
+audio_get_intf(const struct usb_intf_desc *base, uint8_t *alt)
+{
+	/* Check it's audio class */
+	if (base->bInterfaceClass != USB_CLS_AUDIO)
+		return USB_FND_CONTINUE;
+
+	/* Sub class */
+	switch (base->bInterfaceSubClass)
+	{
+	case USB_AC_SCLS_AUDIOCONTROL:
+		*alt = 0;
+		return USB_FND_SUCCESS;
+
+	case USB_AC_SCLS_AUDIOSTREAMING:
+		return pcm_usb_get_intf(base, alt) ? USB_FND_SUCCESS : USB_FND_ERROR;
+
+	default:
+		return USB_FND_ERROR;
+	}
+}
+
+static struct usb_fn_drv _audio_drv = {
+	.ctrl_req = audio_ctrl_req,
+	.set_conf = audio_set_conf,
+	.set_intf = audio_set_intf,
+	.get_intf = audio_get_intf,
+};
+
+
+// Exposed API
+// ---------------------------------------------------------------------------
+
+void
+audio_init(void)
+{
+	/* Init hardware */
+	pcm_init();
+
+	/* Register function driver */
+	usb_register_function_driver(&_audio_drv);
+}
+
+void
+audio_poll(void)
+{
+	pcm_poll();
+}

+ 30 - 0
projects/usb_amr/fw/audio.h

@@ -0,0 +1,30 @@
+/*
+ * audio.h
+ *
+ * USB Audio class firmware
+ *
+ * Copyright (C) 2020 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+void audio_init(void);
+void audio_poll(void);
+void audio_debug_print(void);

+ 121 - 0
projects/usb_amr/fw/cdc-dlm.c

@@ -0,0 +1,121 @@
+/*
+ * cdc-dlm.c
+ *
+ * CDC Direct Line Modem control for MC97 modem
+ *
+ * Copyright (C) 2021 Sylvain Munaut
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <no2usb/usb.h>
+#include <no2usb/usb_hw.h>
+#include <no2usb/usb_priv.h>
+#include <no2usb/usb_cdc_proto.h>
+
+#include "cdc-dlm.h"
+#include "mc97.h"
+
+
+#define INTF_CDC_DLM		4
+#define EP_CDC_DLM_NOTIF	0x83
+
+
+static void
+dlm_send_notif_ring_detect(void)
+{
+	const struct usb_ctrl_req notif = {
+		.bmRequestType	= USB_REQ_READ | USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF,
+		.bRequest	= USB_NOTIF_CDC_RING_DETECT,
+		.wValue		= 0,
+		.wIndex		= INTF_CDC_DLM,
+		.wLength	= 0,
+	};
+
+	usb_data_write(usb_ep_regs[3].in.bd[0].ptr, &notif, sizeof(struct usb_ctrl_req));
+	usb_ep_regs[3].in.bd[0].csr = USB_BD_STATE_RDY_DATA | USB_BD_LEN(sizeof(struct usb_ctrl_req));
+}
+
+
+static enum usb_fnd_resp
+dlm_ctrl_req(struct usb_ctrl_req *req, struct usb_xfer *xfer)
+{
+	/* Check it's a class request to an interface */
+	if (USB_REQ_TYPE_RCPT(req) != (USB_REQ_TYPE_CLASS | USB_REQ_RCPT_INTF))
+		return USB_FND_CONTINUE;
+
+	/* Check it's for the DLM interface */
+	if ((req->wIndex & 0xff) != INTF_CDC_DLM)
+		return USB_FND_CONTINUE;
+
+	switch (req->bRequest)
+	{
+	case USB_REQ_CDC_SET_HOOK_STATE:
+		switch (req->wValue) {
+		case 0:  mc97_set_hook(ON_HOOK);   break;
+		case 1:  mc97_set_hook(OFF_HOOK);  break;
+		case 2:  mc97_set_hook(CALLER_ID); break;
+		default: return USB_FND_ERROR;
+		}
+		return USB_FND_SUCCESS;
+
+	case USB_REQ_CDC_SET_AUX_LINE_STATE:
+		/* Control the relay with that */
+		mc97_set_aux_relay(!req->wValue);
+		return USB_FND_SUCCESS;
+
+	case USB_REQ_CDC_RING_AUX_JACK:
+		/* Can't do that ... */
+		return USB_FND_SUCCESS;
+
+	/* Pulse is not supported yet (also disabled in bmCapabilities) */
+	case USB_REQ_CDC_PULSE_SETUP:
+	case USB_REQ_CDC_SEND_PULSE:
+	case USB_REQ_CDC_SET_PULSE_TIME:
+		return USB_FND_ERROR;
+
+	/* TODO: Maybe implement SET_COMM_FEATURE for country selection ? */
+	/* In theory not part of DLM but it's the closest to a standard
+	 * thing to support tweaking the codec params to match local specs
+	 * for a phone line */
+	}
+
+	return USB_FND_ERROR;
+}
+
+static enum usb_fnd_resp
+dlm_set_conf(const struct usb_conf_desc *conf)
+{
+	const struct usb_intf_desc *intf;
+
+	intf = usb_desc_find_intf(conf, INTF_CDC_DLM, 0, NULL);
+        usb_ep_boot(intf, EP_CDC_DLM_NOTIF, false);
+
+	return USB_FND_SUCCESS;
+}
+
+
+static struct usb_fn_drv _dlm_drv = {
+	.ctrl_req = dlm_ctrl_req,
+	.set_conf = dlm_set_conf,
+};
+
+
+void
+cdc_dlm_init(void)
+{
+	/* Register function driver */
+	usb_register_function_driver(&_dlm_drv);
+}
+
+void
+cdc_dlm_poll(void)
+{
+	/* Nothing to do for now */
+
+	/* TODO:
+	 *  - Pulse timing when pulse is implemented
+	 *  - Ring detection (note that it's not simply calling
+	 *    mc97_get_ring_detect(), it must be analyzed to see if the
+	 *    ringing frequency matches something between 10 and 100 Hz)
+	 */
+}

+ 13 - 0
projects/usb_amr/fw/cdc-dlm.h

@@ -0,0 +1,13 @@
+/*
+ * cdc-dlm.h
+ *
+ * CDC Direct Line Modem control for MC97 modem
+ *
+ * Copyright (C) 2021 Sylvain Munaut
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+void cdc_dlm_init(void);
+void cdc_dlm_poll(void);

+ 31 - 0
projects/usb_amr/fw/config.h

@@ -0,0 +1,31 @@
+/*
+ * config.h
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#define UART_BASE	0x81000000
+#define SPI_BASE	0x82000000
+#define LED_BASE	0x83000000
+#define USB_CORE_BASE	0x84000000
+#define USB_DATA_BASE	0x85000000
+#define MC97_BASE	0x86000000

+ 181 - 0
projects/usb_amr/fw/fw_app.c

@@ -0,0 +1,181 @@
+/*
+ * fw_app.c
+ *
+ * Copyright (C) 2019 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <no2usb/usb.h>
+#include <no2usb/usb_dfu_rt.h>
+#include <no2usb/usb_hw.h>
+#include <no2usb/usb_priv.h>
+
+#include "audio.h"
+#include "cdc-dlm.h"
+#include "console.h"
+#include "led.h"
+#include "mc97.h"
+#include "mini-printf.h"
+#include "spi.h"
+#include "utils.h"
+
+#include "config.h"
+
+
+extern const struct usb_stack_descriptors app_stack_desc;
+
+static void
+serial_no_init()
+{
+	uint8_t buf[8];
+	char *id, *desc;
+	int i;
+
+	flash_manuf_id(buf);
+	printf("Flash Manufacturer : %s\n", hexstr(buf, 3, true));
+
+	flash_unique_id(buf);
+	printf("Flash Unique ID    : %s\n", hexstr(buf, 8, true));
+
+	/* Overwrite descriptor string */
+		/* In theory in rodata ... but nothing is ro here */
+	id = hexstr(buf, 8, false);
+	desc = (char*)app_stack_desc.str[1];
+	for (i=0; i<16; i++)
+		desc[2 + (i << 1)] = id[i];
+}
+
+static void
+boot_dfu(void)
+{
+	/* Force re-enumeration */
+	usb_disconnect();
+
+	/* Boot firmware */
+	volatile uint32_t *boot = (void*)0x80000000;
+	*boot = (1 << 2) | (1 << 0);
+}
+
+void
+usb_dfu_rt_cb_reboot(void)
+{
+        boot_dfu();
+}
+
+
+
+
+
+void
+main()
+{
+	int cmd = 0;
+
+	/* Init console IO */
+	console_init();
+	puts("Booting Audio image..\n");
+
+	/* LED */
+	led_init();
+	led_color(48, 96, 5);
+	led_blink(true, 200, 1000);
+	led_breathe(true, 100, 200);
+	led_state(true);
+
+	/* SPI */
+	spi_init();
+
+	/* Enable USB directly */
+	serial_no_init();
+	usb_init(&app_stack_desc);
+	usb_dfu_rt_init();
+
+	/* Init class drivers */
+	audio_init();
+	cdc_dlm_init();
+
+	/* Connect */
+	usb_connect();
+
+	/* Main loop */
+	while (1)
+	{
+		/* Prompt ? */
+		if (cmd >= 0)
+			printf("Command> ");
+
+		/* Poll for command */
+		cmd = getchar_nowait();
+
+		if (cmd >= 0) {
+			if (cmd > 32 && cmd < 127) {
+				putchar(cmd);
+				putchar('\r');
+				putchar('\n');
+			}
+
+			switch (cmd)
+			{
+			case 'i': mc97_init();  break;
+			case 'p': mc97_debug(); break;
+
+			case 'r': mc97_set_aux_relay(false); break;
+			case 'R': mc97_set_aux_relay(true);  break;
+
+			case 'h': mc97_set_hook(ON_HOOK);   break;
+			case 'H': mc97_set_hook(OFF_HOOK);  break;
+			case 'C': mc97_set_hook(CALLER_ID); break;
+
+			case '0': mc97_set_loopback(MC97_LOOPBACK_NONE);            break;
+			case '1': mc97_set_loopback(MC97_LOOPBACK_DIGITAL_ADC);     break;
+			case '2': mc97_set_loopback(MC97_LOOPBACK_ANALOG_LOCAL);    break;
+			case '3': mc97_set_loopback(MC97_LOOPBACK_DIGITAL_DAC);     break;
+			case '4': mc97_set_loopback(MC97_LOOPBACK_ANALOG_REMOTE);   break;
+			case '5': mc97_set_loopback(MC97_LOOPBACK_ISOCAP);          break;
+			case '6': mc97_set_loopback(MC97_LOOPBACK_ANALOG_EXTERNAL); break;
+
+			case 's':
+				for (int i=0; i<128; i+=2)
+					printf("%02x: %04x\n", i, mc97_codec_reg_read(i));
+				break;
+
+			case 'b':
+				boot_dfu();
+				break;
+			case 'c':
+				usb_connect();
+				break;
+			case 'd':
+				usb_disconnect();
+				break;
+			default:
+				break;
+			}
+		}
+
+		/* USB poll */
+		usb_poll();
+		audio_poll();
+		cdc_dlm_poll();
+	}
+}

+ 343 - 0
projects/usb_amr/fw/mc97.c

@@ -0,0 +1,343 @@
+/*
+ * mc97.c
+ *
+ * MC97 controller
+ *
+ * Copyright (C) 2021 Sylvain Munaut
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "console.h"
+#include "mc97.h"
+#include "mc97_country.h"
+
+#include "config.h"
+
+
+struct wb_mc97 {
+	uint32_t csr;
+	uint32_t lls;
+	uint32_t cra;
+	uint32_t _rsvd;
+	uint32_t gpio_in;
+	uint32_t gpio_out;
+	uint32_t fifo_data;
+	uint32_t fifo_csr;
+} __attribute__((packed,aligned(4)));
+
+#define MC97_CSR_GPIO_ENA		(1 <<  2)
+#define MC97_CSR_RESET_N		(1 <<  1)
+#define MC97_CSR_RUN			(1 <<  0)
+
+#define MC97_LLS_CODEC_READY		(1 << 31)
+#define MC97_LLS_SLOT_REQ(n)		(1 << ((n)+16))
+#define MC97_LLS_SLOT_VALID(n)		(1 << ((n)+16))
+
+#define MC97_CRA_BUSY			(1 << 31)
+#define MC97_CRA_WRITE			(1 << 30)
+#define MC97_CRA_READ_ERR		(1 << 29)
+#define MC97_CRA_ADDR(x)		(((x) >> 1) << 16)
+#define MC97_CRA_VAL(x)			(x)
+#define MC97_CRA_GET_VAL(x)		((x) & 0xffff)
+
+#define MC97_FIFO_DATA_EMPTY		(1 << 31)
+
+#define MC97_FIFO_CSR_PCM_IN_ENABLE	(1 << 31)
+#define MC97_FIFO_CSR_PCM_IN_FLUSH	(1 << 30)
+#define MC97_FIFO_CSR_PCM_IN_FULL	(1 << 29)
+#define MC97_FIFO_CSR_PCM_IN_EMPTY	(1 << 28)
+#define MC97_FIFO_CSR_PCM_IN_LEVEL(x)	(((x) >> 16) & 0xfff)
+
+#define MC97_FIFO_CSR_PCM_OUT_ENABLE	(1 << 15)
+#define MC97_FIFO_CSR_PCM_OUT_FLUSH	(1 << 14)
+#define MC97_FIFO_CSR_PCM_OUT_FULL	(1 << 13)
+#define MC97_FIFO_CSR_PCM_OUT_EMPTY	(1 << 12)
+#define MC97_FIFO_CSR_PCM_OUT_LEVEL(x)	((x) & 0xfff)
+
+
+static volatile struct wb_mc97 * const mc97_regs = (void*)(MC97_BASE);
+
+
+static struct {
+	uint16_t rc_46;	/* Cache of reg 0x46 */
+	uint16_t rc_5c; /* Cache of reg 0x5c */
+	uint16_t rc_62; /* Cache of reg 0x62 */
+} g_mc97;
+
+
+
+void
+mc97_codec_reg_write(uint8_t addr, uint16_t val)
+{
+	/* Submit request */
+	mc97_regs->cra =
+		MC97_CRA_WRITE |
+		MC97_CRA_ADDR(addr) |
+		MC97_CRA_VAL(val);
+
+	/* Wait until not busy */
+	while (mc97_regs->cra & MC97_CRA_BUSY);
+}
+
+uint16_t
+mc97_codec_reg_read(uint8_t addr)
+{
+	uint32_t v;
+
+	/* Submit request */
+	mc97_regs->cra = MC97_CRA_ADDR(addr);
+
+	/* Wait until not busy */
+	while ((v = mc97_regs->cra) & MC97_CRA_BUSY);
+
+	/* Check for read errors */
+	if (v & MC97_CRA_READ_ERR)
+		return 0xffff; /* Not much we can do */
+
+	/* Return result */
+	return MC97_CRA_GET_VAL(v);
+}
+
+
+void
+mc97_init(void)
+{
+	/* Initialize controller and reset codec */
+	mc97_regs->csr = MC97_CSR_RUN;
+	mc97_regs->csr = MC97_CSR_RUN | MC97_CSR_RESET_N | MC97_CSR_GPIO_ENA;
+
+	/* Init the codec */
+	mc97_codec_reg_write(0x40, 0x1f40);	/* Line 1 rate 8 kHz */
+	mc97_codec_reg_write(0x3e, 0xf000);	/* Power up */
+	mc97_codec_reg_write(0x46, 0x0000);	/* Mute Off, no gain/attenuation */
+	mc97_codec_reg_write(0x4c, 0x002a);	/* GPIO Direction */
+	mc97_codec_reg_write(0x4e, 0x002a);	/* GPIO polarity/type */
+
+	/* Init cache */
+	g_mc97.rc_46 = 0x0000;
+	g_mc97.rc_5c = 0x0000;
+	g_mc97.rc_62 = 0x0000;
+
+	/* Country default */
+	mc97_select_country(0);
+}
+
+void
+mc97_debug(void)
+{
+	printf("CSR  : %08x\n", mc97_regs->csr);
+	printf("LLS  : %08x\n", mc97_regs->lls);
+	printf("CRA  : %08x\n", mc97_regs->cra);
+	printf("GI   : %08x\n", mc97_regs->gpio_in);
+	printf("GO   : %08x\n", mc97_regs->gpio_out);
+	printf("Fdat : %08x\n", mc97_regs->fifo_data);
+	printf("Fcsr : %08x\n", mc97_regs->fifo_csr);
+}
+
+bool
+mc97_select_country(int cc)
+{
+	for (int i=0; country_data[i].cc >= 0; i++) {
+		/* Match ? */
+		if (country_data[i].cc != cc)
+			continue;
+
+#if 0
+		printf("Configured for %s\n", country_data[i].name);
+#endif
+
+		/* Configure */
+		g_mc97.rc_5c = (g_mc97.rc_5c & 0xff02) | country_data[i].regs[0];
+		g_mc97.rc_62 = (g_mc97.rc_62 & 0xff87) | country_data[i].regs[1];
+
+		mc97_codec_reg_write(0x5c, g_mc97.rc_5c);
+		mc97_codec_reg_write(0x62, g_mc97.rc_62);
+
+		/* Done */
+		return true;
+	}
+
+	return false;
+}
+
+
+void
+mc97_set_aux_relay(bool disconnect)
+{
+	mc97_regs->gpio_out = (mc97_regs->gpio_out & ~(1 << 8)) | (disconnect << 8);
+}
+
+void
+mc97_set_hook(enum mc97_hook_state s)
+{
+	uint32_t gpio_out = mc97_regs->gpio_out & ~((1 << 4) | (1 << 6));
+
+	switch (s) {
+	case ON_HOOK:                         break;
+	case CALLER_ID: gpio_out |= (1 << 6); break;
+	case OFF_HOOK:  gpio_out |= (1 << 4); break;
+	}
+
+	mc97_regs->gpio_out = gpio_out;
+}
+
+bool
+mc97_get_ring_detect(void)
+{
+	return (mc97_regs->gpio_in & (1 << 5)) ? true : false;
+}
+
+void
+mc97_set_loopback(enum mc97_loopback_mode m)
+{
+	mc97_codec_reg_write(0x56, m);
+}
+
+
+uint8_t
+mc97_get_rx_gain(void)
+{
+	return (g_mc97.rc_46 & 0xf) * 3;
+}
+
+void
+mc97_set_rx_gain(uint8_t gain)
+{
+	gain = (gain > 45) ? 0xf : (gain / 3);
+	g_mc97.rc_46 = (g_mc97.rc_46 & 0xff80) | (gain << 8);
+	mc97_codec_reg_write(0x46, g_mc97.rc_46);
+}
+
+bool
+mc97_get_rx_mute(void)
+{
+	return (g_mc97.rc_46 & 0x0080) ? true : false;
+}
+
+void
+mc97_set_rx_mute(bool mute)
+{
+	g_mc97.rc_46 = (g_mc97.rc_46 & 0xff7f) | (mute ? 0x0080 : 0x0000);
+	mc97_codec_reg_write(0x46, g_mc97.rc_46);
+}
+
+uint8_t
+mc97_get_tx_attenuation(void)
+{
+	return ((g_mc97.rc_46 >> 8) & 0xf) * 3;
+}
+
+void
+mc97_set_tx_attenuation(uint8_t attenuation)
+{
+	attenuation = (attenuation > 45) ? 0xf : (attenuation / 3);
+	g_mc97.rc_46 = (g_mc97.rc_46 & 0x80ff) | (attenuation << 8);
+	mc97_codec_reg_write(0x46, g_mc97.rc_46);
+}
+
+bool
+mc97_get_tx_mute(void)
+{
+	return (g_mc97.rc_46 & 0x8000) ? true : false;
+}
+
+void
+mc97_set_tx_mute(bool mute)
+{
+	g_mc97.rc_46 = (g_mc97.rc_46 & 0x7fff) | (mute ? 0x8000 : 0x0000);
+	mc97_codec_reg_write(0x46, g_mc97.rc_46);
+}
+
+
+void
+mc97_flow_rx_reset(void)
+{
+	mc97_regs->fifo_csr = (mc97_regs->fifo_csr & ~MC97_FIFO_CSR_PCM_IN_ENABLE) | MC97_FIFO_CSR_PCM_IN_FLUSH;
+	while (mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_IN_FLUSH);
+}
+
+void
+mc97_flow_rx_start(void)
+{
+	mc97_regs->fifo_csr |= MC97_FIFO_CSR_PCM_IN_ENABLE;
+}
+
+void
+mc97_flow_rx_stop(void)
+{
+	mc97_regs->fifo_csr &= ~MC97_FIFO_CSR_PCM_IN_ENABLE;
+}
+
+int
+mc97_flow_rx_pull(int16_t *data, int n)
+{
+	for (int i=0; i<n; i++) {
+		uint32_t v = mc97_regs->fifo_data;
+		if (v & MC97_FIFO_DATA_EMPTY)
+			return i;
+		data[i] = v & 0xffff;
+	}
+
+	return n;
+}
+
+int
+mc97_flow_rx_level(void)
+{
+	return MC97_FIFO_CSR_PCM_IN_LEVEL(mc97_regs->fifo_csr);
+}
+
+bool
+mc97_flow_rx_active(void)
+{
+	return mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_IN_ENABLE;
+}
+
+
+void
+mc97_flow_tx_reset(void)
+{
+	mc97_regs->fifo_csr = (mc97_regs->fifo_csr & ~MC97_FIFO_CSR_PCM_OUT_ENABLE) | MC97_FIFO_CSR_PCM_OUT_FLUSH;
+	while (mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_OUT_FLUSH);
+}
+
+void
+mc97_flow_tx_start(void)
+{
+	mc97_regs->fifo_csr |= MC97_FIFO_CSR_PCM_OUT_ENABLE;
+}
+
+void
+mc97_flow_tx_stop(void)
+{
+	mc97_regs->fifo_csr &= ~MC97_FIFO_CSR_PCM_OUT_ENABLE;
+}
+
+int
+mc97_flow_tx_push(int16_t *data, int n)
+{
+	int max = MC97_FIFO_SIZE - MC97_FIFO_CSR_PCM_OUT_LEVEL(mc97_regs->fifo_csr);
+
+	if (n > max)
+		n = max;
+
+	for (int i=0; i<n; i++)
+		mc97_regs->fifo_data = data[i];
+
+	return n;
+}
+
+int
+mc97_flow_tx_level(void)
+{
+	return MC97_FIFO_CSR_PCM_OUT_LEVEL(mc97_regs->fifo_csr);
+}
+
+bool
+mc97_flow_tx_active(void)
+{
+	return mc97_regs->fifo_csr & MC97_FIFO_CSR_PCM_OUT_ENABLE;
+}

+ 68 - 0
projects/usb_amr/fw/mc97.h

@@ -0,0 +1,68 @@
+/*
+ * mc97.h
+ *
+ * MC97 controller
+ *
+ * Copyright (C) 2021 Sylvain Munaut
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+
+enum mc97_hook_state {
+	ON_HOOK,
+	CALLER_ID,
+	OFF_HOOK,
+};
+
+enum mc97_loopback_mode {
+	MC97_LOOPBACK_NONE		= 0x0,
+	MC97_LOOPBACK_DIGITAL_ADC	= 0x1,
+	MC97_LOOPBACK_ANALOG_LOCAL	= 0x2,
+	MC97_LOOPBACK_DIGITAL_DAC	= 0x3,
+	MC97_LOOPBACK_ANALOG_REMOTE	= 0x4,
+	MC97_LOOPBACK_ISOCAP		= 0x5,	/* Sil3038 */
+	MC97_LOOPBACK_ANALOG_EXTERNAL	= 0x6,	/* Sil3038 */
+};
+
+#define MC97_FIFO_SIZE 256
+
+
+void     mc97_codec_reg_write(uint8_t addr, uint16_t val);
+uint16_t mc97_codec_reg_read(uint8_t addr);
+
+void     mc97_init(void);
+void     mc97_debug(void);
+bool     mc97_select_country(int cc);
+
+void     mc97_set_aux_relay(bool disconnect);
+void     mc97_set_hook(enum mc97_hook_state s);
+bool     mc97_get_ring_detect(void);
+void     mc97_set_loopback(enum mc97_loopback_mode m);
+
+uint8_t  mc97_get_rx_gain(void);
+void     mc97_set_rx_gain(uint8_t gain);
+bool     mc97_get_rx_mute(void);
+void     mc97_set_rx_mute(bool mute);
+uint8_t  mc97_get_tx_attenuation(void);
+void     mc97_set_tx_attenuation(uint8_t attenuation);
+bool     mc97_get_tx_mute(void);
+void     mc97_set_tx_mute(bool mute);
+
+void     mc97_flow_rx_reset(void);
+void     mc97_flow_rx_start(void);
+void     mc97_flow_rx_stop(void);
+int      mc97_flow_rx_pull(int16_t *data, int n);
+int      mc97_flow_rx_level(void);
+bool     mc97_flow_rx_active(void);
+
+void     mc97_flow_tx_reset(void);
+void     mc97_flow_tx_start(void);
+void     mc97_flow_tx_stop(void);
+int      mc97_flow_tx_push(int16_t *data, int n);
+int      mc97_flow_tx_level(void);
+bool     mc97_flow_tx_active(void);

+ 87 - 0
projects/usb_amr/fw/mc97_country.h

@@ -0,0 +1,87 @@
+
+#pragma once
+
+#define TERM_DATA(ohs,act,dct,rz,rt,lim,vol) { \
+	((ohs) << 7) | (!!(act) << 5) | ((dct) << 3) | ((rz) << 2) | ((rt) << 0), \
+	((lim) << 3) | ((vol) << 5) \
+}
+
+static struct {
+	int cc;
+	char *name;
+	uint8_t regs[2];
+} country_data[] = {
+	{  32, "Argentina",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{  36, "Australia",	TERM_DATA(1, 1, 1, 0, 0, 0, 0) },
+	{  40, "Austria",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{  48, "Bahrain",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{  56, "Belgium",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{  76, "Brazil",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 100, "Bulgaria",	TERM_DATA(0, 1, 3, 0, 0, 3, 0) },
+	{ 124, "Canada",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 152, "Chile",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 156, "China",		TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 170, "Colombia",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 191, "Croatia",	TERM_DATA(0, 1, 3, 0, 0, 3, 0) },
+	{   0, "CTR21",		TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 196, "Cyprus",	TERM_DATA(0, 1, 3, 0, 0, 3, 0) },
+	{ 203, "Czech Republic",TERM_DATA(0, 1, 3, 0, 0, 3, 0) },
+	{ 208, "Denmark",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 218, "Ecuador",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 818, "Egypt",		TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 222, "El Salvador",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 246, "Finland",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 250, "France",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 276, "Germany",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 300, "Greece",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 316, "Guam",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 344, "Hong Kong",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 348, "Hungary",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 352, "Iceland",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 356, "India",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 360, "Indonesia",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 372, "Ireland",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 376, "Israel",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 380, "Italy",		TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 392, "Japan",		TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 400, "Jordan",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 398, "Kazakhstan",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 414, "Kuwait",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 428, "Latvia",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 422, "Lebanon",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 442, "Luxembourg",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 446, "Macao",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 458, "Malaysia",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 470, "Malta",		TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 484, "Mexico",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 504, "Morocco",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 528, "Netherlands",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 554, "New Zealand",	TERM_DATA(0, 1, 2, 0, 0, 0, 0) },
+	{ 566, "Nigeria",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 578, "Norway",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 512, "Oman",		TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 586, "Pakistan",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 604, "Peru",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 608, "Philippines",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 616, "Poland",	TERM_DATA(0, 0, 2, 1, 1, 0, 0) },
+	{ 620, "Portugal",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 642, "Romania",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 643, "Russia",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 682, "Saudi Arabia",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 702, "Singapore",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 703, "Slovakia",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 705, "Slovenia",	TERM_DATA(0, 0, 2, 1, 1, 0, 0) },
+	{ 710, "South Africa",	TERM_DATA(1, 0, 2, 1, 0, 0, 0) },
+	{ 410, "South Korea",	TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 724, "Spain",		TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 752, "Sweden",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 756, "Switzerland",	TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 760, "Syria",		TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 158, "Taiwan",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 764, "Thailand",	TERM_DATA(0, 0, 1, 0, 0, 0, 0) },
+	{ 784, "UAE",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 826, "United Kingdom",TERM_DATA(0, 2, 3, 0, 0, 3, 0) },
+	{ 840, "USA",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ 887, "Yemen",		TERM_DATA(0, 0, 2, 0, 0, 0, 0) },
+	{ -1, (void*)0, { 0, 0 } }
+};

+ 418 - 0
projects/usb_amr/fw/usb_desc_app.c

@@ -0,0 +1,418 @@
+/*
+ * usb_desc_app.c
+ *
+ * Copyright (C) 2020 Sylvain Munaut
+ * All rights reserved.
+ *
+ * LGPL v3+, see LICENSE.lgpl3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <no2usb/usb_proto.h>
+#include <no2usb/usb_ac_proto.h>
+#include <no2usb/usb_cdc_proto.h>
+#include <no2usb/usb_dfu_proto.h>
+#include <no2usb/usb.h>
+
+
+usb_cdc_union_desc_def(1);
+usb_ac_ac_hdr_desc_def(2);
+usb_ac_ac_feature_desc_def(4);
+usb_ac_as_fmt_type1_desc_def(3);
+
+static const struct {
+	/* Configuration */
+	struct usb_conf_desc conf;
+
+	/* DFU Runtime */
+	struct {
+		struct usb_intf_desc intf;
+		struct usb_dfu_func_desc func;
+	} __attribute__ ((packed)) dfu;
+
+	/* Audio Control Interface */
+	struct {
+		struct usb_intf_desc intf;
+		struct usb_ac_ac_hdr_desc__2 hdr;
+		struct usb_ac_ac_input_desc it_phone;
+		struct usb_ac_ac_feature_desc__4 feat_in;
+		struct usb_ac_ac_output_desc ot_usb;
+		struct usb_ac_ac_input_desc it_usb;
+		struct usb_ac_ac_feature_desc__4 feat_out;
+		struct usb_ac_ac_output_desc ot_phone;
+	} __attribute__ ((packed)) audio_ctl;
+
+	/* Audio Inpput Streaming Interface */
+	struct {
+		struct usb_intf_desc intf[2];
+		struct usb_ac_as_general_desc general;
+		struct usb_ac_as_fmt_type1_desc__3 fmt;
+		struct usb_cc_ep_desc ep_data;
+		struct usb_ac_as_ep_general_desc ep_gen;
+	} __attribute__ ((packed)) audio_stream_in;
+
+	/* Audio Output Streaming Interface */
+	struct {
+		struct usb_intf_desc intf[2];
+		struct usb_ac_as_general_desc general;
+		struct usb_ac_as_fmt_type1_desc__3 fmt;
+		struct usb_cc_ep_desc ep_data;
+		struct usb_ac_as_ep_general_desc ep_gen;
+		struct usb_cc_ep_desc ep_sync;
+	} __attribute__ ((packed)) audio_stream_out;
+
+	/* USB CDC DLM */
+	struct {
+		struct usb_intf_desc intf;
+		struct usb_cdc_hdr_desc hdr;
+		struct usb_cdc_dlm_desc dlm;
+		struct usb_cdc_union_desc__1 ud;
+		struct usb_ep_desc ep;
+	} __attribute__ ((packed)) cdc_dlm;
+
+} __attribute__ ((packed)) _app_conf_desc = {
+	.conf = {
+		.bLength                = sizeof(struct usb_conf_desc),
+		.bDescriptorType        = USB_DT_CONF,
+		.wTotalLength           = sizeof(_app_conf_desc),
+		.bNumInterfaces         = 5,
+		.bConfigurationValue    = 1,
+		.iConfiguration         = 4,
+		.bmAttributes           = 0x80,
+		.bMaxPower              = 0x32, /* 100 mA */
+	},
+	.dfu = {
+		.intf = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 0,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 0,
+			.bInterfaceClass	= 0xfe,
+			.bInterfaceSubClass	= 0x01,
+			.bInterfaceProtocol	= 0x01,
+			.iInterface		= 5,
+		},
+		.func = {
+			.bLength		= sizeof(struct usb_dfu_func_desc),
+			.bDescriptorType	= USB_DFU_DT_FUNC,
+			.bmAttributes		= 0x0d,
+			.wDetachTimeOut		= 0,
+			.wTransferSize		= 4096,
+			.bcdDFUVersion		= 0x0101,
+		},
+	},
+	.audio_ctl = {
+		.intf = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 1,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 0,
+			.bInterfaceClass	= USB_CLS_AUDIO,
+			.bInterfaceSubClass	= USB_AC_SCLS_AUDIOCONTROL,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.hdr = {
+			.bLength		= sizeof(struct usb_ac_ac_hdr_desc__2),
+			.bDescriptorType	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_HEADER,
+			.bcdADC			= 0x0100,
+			.wTotalLength		= sizeof(_app_conf_desc.audio_ctl) - sizeof(struct usb_intf_desc),
+			.bInCollection		= 2,
+			.baInterfaceNr		= { 0x02, 0x03 },
+		},
+		.it_phone = {	/* ID 1 */
+			.bLength		= sizeof(struct usb_ac_ac_input_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_INPUT_TERMINAL,
+			.bTerminalID		= 1,
+			.wTerminalType		= 0x0501, /* Phone Line */
+			.bAssocTerminal		= 6,
+			.bNrChannels		= 1,
+			.wChannelConfig		= 0x0000,
+			.iChannelNames		= 0,
+			.iTerminal		= 0,
+		},
+		.feat_in = {	/* ID 2 */
+			.bLength		= sizeof(struct usb_ac_ac_feature_desc__4),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_FEATURE_UNIT,
+			.bUnitID		= 2,
+			.bSourceID		= 1,
+			.bControlSize		= 2,
+			.bmaControls		= {
+				U16_TO_U8_LE(0x0003),	// Mute + Volume
+				U16_TO_U8_LE(0x0000),	// n/a
+			},
+			.iFeature		= 6,
+		},
+		.ot_usb = {	/* ID 3 */
+			.bLength		= sizeof(struct usb_ac_ac_output_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_OUTPUT_TERMINAL,
+			.bTerminalID		= 3,
+			.wTerminalType		= 0x0101, /* USB Streaming */
+			.bAssocTerminal		= 4,
+			.bSourceID		= 2,
+			.iTerminal		= 0,
+		},
+		.it_usb = {	/* ID 4 */
+			.bLength		= sizeof(struct usb_ac_ac_input_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_INPUT_TERMINAL,
+			.bTerminalID		= 4,
+			.wTerminalType		= 0x0101, /* USB Streaming */
+			.bAssocTerminal		= 3,
+			.bNrChannels		= 1,
+			.wChannelConfig		= 0x0000,
+			.iChannelNames		= 0,
+			.iTerminal		= 0,
+		},
+		.feat_out = {	/* ID 5 */
+			.bLength		= sizeof(struct usb_ac_ac_feature_desc__4),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_FEATURE_UNIT,
+			.bUnitID		= 5,
+			.bSourceID		= 4,
+			.bControlSize		= 2,
+			.bmaControls		= {
+				U16_TO_U8_LE(0x0003),	// Mute + Volume
+				U16_TO_U8_LE(0x0000),	// n/a
+			},
+			.iFeature		= 7,
+		},
+		.ot_phone = {	/* ID 6 */
+			.bLength		= sizeof(struct usb_ac_ac_output_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AC_IDST_OUTPUT_TERMINAL,
+			.bTerminalID		= 6,
+			.wTerminalType		= 0x0501, /* Phone Line */
+			.bAssocTerminal		= 1,
+			.bSourceID		= 5,
+			.iTerminal		= 0,
+		},
+	},
+	.audio_stream_in = {
+		.intf[0] = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 2,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 0,
+			.bInterfaceClass	= USB_CLS_AUDIO,
+			.bInterfaceSubClass	= USB_AC_SCLS_AUDIOSTREAMING,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.intf[1] = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 2,
+			.bAlternateSetting	= 1,
+			.bNumEndpoints		= 1,
+			.bInterfaceClass	= USB_CLS_AUDIO,
+			.bInterfaceSubClass	= USB_AC_SCLS_AUDIOSTREAMING,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.general = {
+			.bLength		= sizeof(struct usb_ac_as_general_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AS_IDST_GENERAL,
+			.bTerminalLink		= 3,
+			.bDelay			= 0,
+			.wFormatTag		= 0x0001,	/* PCM */
+		},
+		.fmt = {
+			.bLength		= sizeof(struct usb_ac_as_fmt_type1_desc__3),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AS_IDST_FORMAT_TYPE,
+			.bFormatType		= 1,
+			.bNrChannels		= 1,
+			.bSubframeSize		= 2,
+			.bBitResolution		= 16,
+			.bSamFreqType		= 1,
+			.tSamFreq		= {
+				U24_TO_U8_LE(8000),
+			},
+		},
+		.ep_data = {
+			.bLength		= sizeof(struct usb_cc_ep_desc),
+			.bDescriptorType	= USB_DT_EP,
+			.bEndpointAddress	= 0x81,		/* 1 IN */
+			.bmAttributes		= 0x05,		/* Data, Async, Isoc */
+			.wMaxPacketSize		= 120,
+			.bInterval		= 1,
+			.bRefresh		= 0,
+			.bSynchAddress		= 0,
+		},
+		.ep_gen = {
+			.bLength		= sizeof(struct usb_ac_as_ep_general_desc),
+			.bDescriptortype	= USB_CS_DT_EP,
+			.bDescriptorSubtype	= USB_AC_EDST_GENERAL,
+			.bmAttributes		= 0x00,
+			.bLockDelayUnits	= 0,
+			.wLockDelay		= 0,
+		},
+	},
+	.audio_stream_out = {
+		.intf[0] = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 3,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 0,
+			.bInterfaceClass	= USB_CLS_AUDIO,
+			.bInterfaceSubClass	= USB_AC_SCLS_AUDIOSTREAMING,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.intf[1] = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 3,
+			.bAlternateSetting	= 1,
+			.bNumEndpoints		= 2,
+			.bInterfaceClass	= USB_CLS_AUDIO,
+			.bInterfaceSubClass	= USB_AC_SCLS_AUDIOSTREAMING,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.general = {
+			.bLength		= sizeof(struct usb_ac_as_general_desc),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AS_IDST_GENERAL,
+			.bTerminalLink		= 4,
+			.bDelay			= 0,
+			.wFormatTag		= 0x0001,	/* PCM */
+		},
+		.fmt = {
+			.bLength		= sizeof(struct usb_ac_as_fmt_type1_desc__3),
+			.bDescriptortype	= USB_CS_DT_INTF,
+			.bDescriptorSubtype	= USB_AC_AS_IDST_FORMAT_TYPE,
+			.bFormatType		= 1,
+			.bNrChannels		= 1,
+			.bSubframeSize		= 2,
+			.bBitResolution		= 16,
+			.bSamFreqType		= 1,
+			.tSamFreq		= {
+				U24_TO_U8_LE(8000),
+			},
+		},
+		.ep_data = {
+			.bLength		= sizeof(struct usb_cc_ep_desc),
+			.bDescriptorType	= USB_DT_EP,
+			.bEndpointAddress	= 0x01,		/* 1 OUT */
+			.bmAttributes		= 0x05,		/* Data, Async, Isoc */
+			.wMaxPacketSize		= 120,
+			.bInterval		= 1,
+			.bRefresh		= 0,
+			.bSynchAddress		= 0x82,
+		},
+		.ep_gen = {
+			.bLength		= sizeof(struct usb_ac_as_ep_general_desc),
+			.bDescriptortype	= USB_CS_DT_EP,
+			.bDescriptorSubtype	= USB_AC_EDST_GENERAL,
+			.bmAttributes		= 0x00,
+			.bLockDelayUnits	= 0,
+			.wLockDelay		= 0,
+		},
+		.ep_sync = {
+			.bLength		= sizeof(struct usb_cc_ep_desc),
+			.bDescriptorType	= USB_DT_EP,
+			.bEndpointAddress	= 0x82,		/* 2 IN */
+			.bmAttributes		= 0x11,		/* Feedback, Isoc */
+			.wMaxPacketSize		= 8,
+			.bInterval		= 1,
+			.bRefresh		= 1,
+			.bSynchAddress		= 0,
+		},
+	},
+	.cdc_dlm = {
+		.intf = {
+			.bLength		= sizeof(struct usb_intf_desc),
+			.bDescriptorType	= USB_DT_INTF,
+			.bInterfaceNumber	= 4,
+			.bAlternateSetting	= 0,
+			.bNumEndpoints		= 1,
+			.bInterfaceClass	= USB_CLS_COMMUNICATIONS,
+			.bInterfaceSubClass	= USB_CDC_SCLS_DLCM,
+			.bInterfaceProtocol	= 0x00,
+			.iInterface		= 0,
+		},
+		.hdr = {
+			.bLength		= sizeof(struct usb_cdc_hdr_desc),
+			.bDescriptorType	= USB_CS_DT_INTF,
+			.bDescriptorsubtype	= USB_CDC_DST_HEADER,
+			.bcdCDC			= 0x0110,
+		},
+		.dlm = {
+			.bLength		= sizeof(struct usb_cdc_dlm_desc),
+			.bDescriptorType	= USB_CS_DT_INTF,
+			.bDescriptorsubtype 	= USB_CDC_DST_DLM,
+			.bmCapabilities		= 0x02,
+		},
+		.ud = {
+			.bLength		= sizeof(struct usb_cdc_union_desc) + 1,
+			.bDescriptorType	= USB_CS_DT_INTF,
+			.bDescriptorsubtype	= USB_CDC_DST_UNION,
+			.bMasterInterface	= 4,
+			.bSlaveInterface	= { 1 },
+		},
+		.ep = {
+			.bLength		= sizeof(struct usb_ep_desc),
+			.bDescriptorType	= USB_DT_EP,
+			.bEndpointAddress	= 0x83,		/* 3 IN */
+			.bmAttributes		= 0x03,		/* Interupt */
+			.wMaxPacketSize		= 8,
+			.bInterval		= 32,
+		},
+	},
+};
+
+
+static const struct usb_conf_desc * const _conf_desc_array[] = {
+	&_app_conf_desc.conf,
+};
+
+static const struct usb_dev_desc _dev_desc = {
+	.bLength		= sizeof(struct usb_dev_desc),
+	.bDescriptorType	= USB_DT_DEV,
+	.bcdUSB			= 0x0200,
+	.bDeviceClass		= 0,
+	.bDeviceSubClass	= 0,
+	.bDeviceProtocol	= 0,
+	.bMaxPacketSize0	= 64,
+	.idVendor		= 0x1d50,
+	.idProduct		= 0x6175,
+	.bcdDevice		= 0x0001,	/* v0.1 */
+	.iManufacturer		= 2,
+	.iProduct		= 3,
+	.iSerialNumber		= 1,
+	.bNumConfigurations	= num_elem(_conf_desc_array),
+};
+
+#include "usb_str_app.gen.h"
+
+const struct usb_stack_descriptors app_stack_desc = {
+	.dev = &_dev_desc,
+	.conf = _conf_desc_array,
+	.n_conf = num_elem(_conf_desc_array),
+	.str = _str_desc_array,
+	.n_str = num_elem(_str_desc_array),
+};

+ 8 - 0
projects/usb_amr/fw/usb_str_app.txt

@@ -0,0 +1,8 @@
+0000000000000000
+osmocom
+AMR modem interface
+Main
+DFU runtime
+Line 1 In
+Line 1 Out
+Off-hook

+ 162 - 0
projects/usb_amr/rtl/dfu_helper.v

@@ -0,0 +1,162 @@
+/*
+ * dfu_helper.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module dfu_helper #(
+	parameter integer TIMER_WIDTH = 24,
+	parameter integer BTN_MODE = 3,		// [2] Use btn_tick, [1] Include IO buffer, [0] Invert (active-low)
+	parameter integer DFU_MODE = 0		// 0 = For user app, 1 = For bootloader
+)(
+	// External control
+	input  wire [1:0] boot_sel,
+	input  wire boot_now,
+
+	// Button
+	input  wire btn_pad,
+	input  wire btn_tick,
+
+	// Outputs
+	output wire btn_val,
+	output reg  rst_req,
+
+	// Clock
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Button
+	wire btn_iob;
+	wire btn_v;
+	wire btn_r;
+	wire btn_f;
+
+	// Timer and arming logic
+	reg armed;
+	reg [TIMER_WIDTH-1:0] timer;
+	(* keep="true" *) wire timer_act;
+
+	// Boot logic
+	reg [1:0] wb_sel;
+	reg wb_req;
+	reg wb_now;
+
+
+	// Button logic
+	// ------------
+
+	// IOB
+	generate
+		if (BTN_MODE[1])
+			SB_IO #(
+				.PIN_TYPE(6'b000000),	// Reg input, no output
+				.PULLUP(1'b1),
+				.IO_STANDARD("SB_LVCMOS")
+			) btn_iob_I (
+				.PACKAGE_PIN(btn_pad),
+				.INPUT_CLK  (clk),
+				.D_IN_0     (btn_iob)
+			);
+		else
+			assign btn_iob = btn_pad;
+	endgenerate
+
+	// Deglitch
+	glitch_filter #(
+		.L(BTN_MODE[2] ? 2 : 4),
+		.RST_VAL(BTN_MODE[0]),
+		.WITH_SYNCHRONIZER(1),
+		.WITH_SAMP_COND(BTN_MODE[2])
+	) btn_flt_I (
+		.in       (btn_iob ^ BTN_MODE[0]),
+		.samp_cond(btn_tick),
+		.val      (btn_v),
+		.rise     (btn_r),
+		.fall     (btn_f),
+		.clk      (clk),
+`ifdef SIM
+		.rst      (rst)
+`else
+		// Don't reset so we let the filter settle before
+		// the rest of the logic engages
+		.rst      (1'b0)
+`endif
+	);
+
+	assign btn_val = btn_v;
+
+
+	// Arming & Timer
+	// --------------
+
+	assign timer_act = btn_v ^ armed;
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			armed <= 1'b0;
+		else
+			armed <= armed | timer[TIMER_WIDTH-2];
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			timer <= 0;
+		else
+			timer <= timer_act ? { TIMER_WIDTH{1'b0} } : (timer + { { (TIMER_WIDTH-1){1'b0} }, ~timer[TIMER_WIDTH-1] });
+
+
+	// Boot Logic
+	// ----------
+
+	// Decision
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			wb_sel  <= 2'b00;
+			wb_req  <= 1'b0;
+			rst_req <= 1'b0;
+		end else if (~wb_req) begin
+			if (boot_now) begin
+				// External boot request
+				wb_sel  <= boot_sel;
+				wb_req  <= 1'b1;
+				rst_req <= 1'b0;
+			end else begin
+				if (DFU_MODE == 1) begin
+					// We're in a DFU bootloader, any button press results in
+					// boot to application
+					wb_sel  <= 2'b10;
+					wb_req  <= wb_now | (armed & btn_f);
+					rst_req <= 1'b0;
+				end else begin
+					// We're in user application, short press resets the
+					// logic, long press triggers DFU reboot
+					wb_sel  <= 2'b01;
+					wb_req  <= wb_now  | (armed & btn_f &  timer[TIMER_WIDTH-1]);
+					rst_req <= rst_req | (armed & btn_f & ~timer[TIMER_WIDTH-1]);
+				end
+			end
+		end
+
+	// Ensure select bits are set before the boot pulse
+	always @(posedge clk or posedge rst)
+		if (rst)
+			wb_now <= 1'b0;
+		else
+			wb_now <= wb_req;
+
+	// IP core
+	SB_WARMBOOT warmboot (
+		.BOOT(wb_now),
+		.S0(wb_sel[0]),
+		.S1(wb_sel[1])
+	);
+
+endmodule // dfu_helper

+ 420 - 0
projects/usb_amr/rtl/mc97.v

@@ -0,0 +1,420 @@
+/*
+ * mc97.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2021  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module mc97 (
+	// MC97 link
+	output wire mc97_sdata_out,
+	input  wire mc97_sdata_in,
+	output wire mc97_sync,
+	input  wire mc97_bitclk,
+
+	// User interface - Samples
+	input  wire [15:0] pcm_out_data,
+	output reg         pcm_out_ack,
+
+	output wire [15:0] pcm_in_data,
+	output reg         pcm_in_stb,
+
+	// User interface - GPIO (slot 12)
+	output reg  [19:0] gpio_in,
+	input  wire [19:0] gpio_out,
+	input  wire        gpio_ena,
+
+	// User interface - Registers
+	input  wire [ 5:0] reg_addr,
+	input  wire [15:0] reg_wdata,
+	output wire [15:0] reg_rdata,
+	output reg         reg_rerr,
+	input  wire        reg_valid,
+	input  wire        reg_we,
+	output reg         reg_ack,
+
+	// User interface - Misc
+	input  wire        cfg_run,
+
+	output wire        stat_codec_ready,
+	output reg  [12:0] stat_slot_valid,
+	output reg  [12:0] stat_slot_req,
+	input  wire        stat_clr,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	genvar i;
+
+	// Sequencer
+	reg  [12:0] seq_wr_slot;
+	wire [12:0] seq_rd_slot;
+
+	// MC97 Frame control
+	wire [15:0] fc_tag_out;
+	reg  [15:0] fc_tag_in;		// Captured input slot 0
+	reg  [19:0] fc_status_addr;	// Captured input slot 1
+
+	wire [12:0] fc_slotvalid;	// Mapped "slot valid" value
+	wire [12:0] fc_slotreq;		// Mapped "slot request" value
+
+	// Command FSM
+	localparam [1:0]
+		CS_IDLE    = 0,
+		CS_SUBMIT  = 1,
+		CS_WAIT    = 2,
+		CS_CAPTURE = 3;
+
+	reg   [1:0] cmd_state;
+	reg   [1:0] cmd_state_nxt;
+
+	// PCM samples
+	reg         pcm_out_frame;
+
+	// GPIO
+	reg         gpio_ena_frame;
+
+	// Interface to Shifter Unit
+	reg   [4:0] sui_bitcnt;		// user -> amr
+	reg         sui_out_sync;	// user -> amr
+	reg  [19:0] sui_out_data;	// user -> amr
+	reg  [19:0] sui_in_data;	// amr -> user
+	reg  [ 2:0] sui_flip;		// amr -> user
+	reg         sui_ack;       	// user
+
+	// Shift Unit
+	reg  [3:0] su_rst;
+
+	reg  [5:0] su_bitcnt;
+	reg  [2:0] su_trig;
+	reg [19:0] su_data;
+
+	reg  [3:0] rst_amr_cnt;
+	wire       clk_amr;
+	wire       rst_amr;
+
+	// IOs
+	wire iob_sdata_in;
+	wire iob_sdata_out;
+	reg  iob_sync;
+
+
+	// Sequencer
+	// ---------
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			seq_wr_slot <= 13'h0001;
+		else if (sui_ack)
+			seq_wr_slot <= { seq_wr_slot[11:0], seq_wr_slot[12] };
+
+	assign seq_rd_slot = { seq_wr_slot[1:0], seq_wr_slot[12:2] };
+
+
+	// MC97 Frame control (TAG / SLOTREQ)
+	// ------------------
+
+	// Prepare output TAG value
+	assign fc_tag_out = {
+		cfg_run,                           //    [15] Frame valid,
+		(cmd_state == CS_SUBMIT),          //    [14] Slot 1    - Command Address
+		(cmd_state == CS_SUBMIT) & reg_we, //    [13] Slot 2    - Command Data
+		2'b00,                             // [12:11] Slot 3-4  - (n/a)
+		pcm_out_frame,                     //    [10] Slot 5    - Modem Line 1 PCM
+		6'd0,                              //   [9:4] Slot 6-11 - (n/a)
+		gpio_ena_frame,                    //     [3] Slot 12   - GPIO
+		1'b0,                              //     [2] Reserved
+		2'b00                              //   [1:0] Codec ID (always primary here)
+	};
+
+	// Capture input TAG value
+	always @(posedge clk)
+		if (sui_ack & seq_rd_slot[0])
+			fc_tag_in <= sui_in_data[15:0];
+
+	// Capture input STATUS_ADDR (Slot 1)
+	always @(posedge clk)
+		if (sui_ack & seq_rd_slot[1])
+			fc_status_addr <= sui_in_data[15:0];
+
+	// Map those
+	generate
+		for (i=1; i<13; i=i+1)
+			assign fc_slotvalid[i] = fc_tag_in[15-i];
+	endgenerate
+
+	assign fc_slotvalid[0] = 1'b0; // Slot 0 fixed to 0 (special)
+
+	generate
+		for (i=3; i<13; i=i+1)
+			assign fc_slotreq[i] = fc_status_addr[14-i];
+	endgenerate
+
+	assign fc_slotreq[2:0] = 3'b000; // Slot 0...2 fixed to 0 (special)
+
+	// User Side status
+		// Codec ready flag
+	assign stat_codec_ready = fc_tag_in[15];
+
+		// Slot valid flags
+	always @(posedge clk)
+		if (rst)
+			stat_slot_valid[12:1] <= 1'b0;
+		else
+			stat_slot_valid[12:1] <= (stat_slot_valid[12:1] | fc_slotvalid[12:1]) & {12{~stat_clr}};
+
+	initial
+		stat_slot_valid[0] = 1'b0; // Slot 0 fixed to 0 (special)
+
+		// Slot request flags
+	always @(posedge clk)
+		if (rst)
+			stat_slot_req[12:3] <= 0;
+		else
+			stat_slot_req[12:3] <= (stat_slot_req[12:3] | fc_slotreq[12:3]) & {10{~stat_clr}};
+
+	initial
+		stat_slot_req[2:0] = 3'b000; // Slot 0-2 fixed to 0 (special)
+
+
+	// Command FSM
+	// -----------
+
+	// State register
+	always @(posedge clk)
+		if (rst)
+			cmd_state <= CS_IDLE;
+		else
+			cmd_state <= cmd_state_nxt;
+
+	// Next-State
+	always @(*)
+	begin
+		// Default is no-change
+		cmd_state_nxt = cmd_state;
+
+		// Transistions
+		case (cmd_state)
+			CS_IDLE:
+				// Start new access for frame beginning
+				if (sui_ack & seq_wr_slot[12] & reg_valid)
+					cmd_state_nxt = CS_SUBMIT;
+
+			CS_SUBMIT:
+				// Command has been sent in this frame
+				if (sui_ack & seq_wr_slot[12])
+					// If it was a write, we're done.
+					// For reads, we need to wait for an answer
+					cmd_state_nxt = reg_we ? CS_IDLE : CS_WAIT;
+
+			CS_WAIT:
+				// No matter what, we move onto capture at the next slot
+				// But here we check if the read worked or not (see reg_rerr)
+				if (sui_ack & seq_rd_slot[1])
+					cmd_state_nxt = CS_CAPTURE;
+
+			CS_CAPTURE:
+				if (sui_ack)
+					cmd_state_nxt = CS_IDLE;
+		endcase
+	end
+
+	// Ack
+	always @(posedge clk)
+	begin
+		// Default
+		reg_ack <= 1'b0;
+
+		// Write ack
+		if ((cmd_state == CS_SUBMIT) & sui_ack & seq_wr_slot[12] & reg_we)
+			reg_ack <= 1'b1;
+
+		// Read ack
+		if ((cmd_state == CS_CAPTURE) & sui_ack)
+			reg_ack <= 1'b1;
+	end
+
+	// Read error ?
+	always @(posedge clk)
+		if ((cmd_state == CS_WAIT) & sui_ack & seq_rd_slot[1])
+			reg_rerr <= (sui_in_data[18:13] != reg_addr) | (fc_slotvalid[2:1] != 2'b11);
+
+	// Read data
+	assign reg_rdata = sui_in_data[19:4];
+
+
+	// PCM samples
+	// -----------
+
+	// Output
+	always @(posedge clk)
+		if (sui_ack & seq_wr_slot[12])
+			pcm_out_frame <= ~fc_slotreq[5];
+
+	always @(posedge clk)
+		pcm_out_ack <= sui_ack & seq_wr_slot[5] & pcm_out_frame;
+
+	// Input
+	assign pcm_in_data = sui_in_data[19:4];
+
+	always @(posedge clk)
+		pcm_in_stb <= sui_ack & seq_rd_slot[5] & fc_slotvalid[5];
+
+
+	// GPIO (slot 12)
+	// ----
+
+	// Register enable status for the frame
+	always @(posedge clk)
+		if (sui_ack & seq_wr_slot[12])
+			gpio_ena_frame <= gpio_ena;
+
+	// Capture GPIO input
+	always @(posedge clk)
+		if (sui_ack & seq_rd_slot[12])
+			gpio_in <= sui_in_data;
+
+
+	// Shifter control
+	// ---------------
+
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			sui_bitcnt   <= 5'd0;
+			sui_out_sync <= 1'b0;
+			sui_out_data <= 20'h00000;
+		end else if (sui_ack) begin
+			sui_bitcnt   <= seq_wr_slot[0] ? 5'd14 : 5'd18;
+			sui_out_sync <= seq_wr_slot[0];
+			sui_out_data <= 20'h00000;
+
+			(* parallel_case *)
+			case (1'b1)
+				seq_wr_slot[ 0]: sui_out_data <= { fc_tag_out, 4'h0 };
+				seq_wr_slot[ 1]: sui_out_data <=  (cmd_state == CS_SUBMIT)           ? { ~reg_we, reg_addr, 13'h0000 } : 20'h00000;
+				seq_wr_slot[ 2]: sui_out_data <= ((cmd_state == CS_SUBMIT) & reg_we) ? {         reg_wdata,     4'h0 } : 20'h00000;
+				seq_wr_slot[ 5]: sui_out_data <= pcm_out_frame  ? { pcm_out_data, 4'h0 } : 20'h00000;
+				seq_wr_slot[12]: sui_out_data <= gpio_ena_frame ? gpio_out : 20'h00000;
+			endcase
+		end
+
+
+	// Shifter
+	// -------
+
+	// Clock input
+	SB_GB_IO #(
+		.PIN_TYPE(6'b 0000_01),
+		.PULLUP(1'b0),
+		.IO_STANDARD("SB_LVCMOS")
+	) clk_gb_I (
+		.PACKAGE_PIN          (mc97_bitclk),
+		.GLOBAL_BUFFER_OUTPUT (clk_amr)
+	);
+
+	// Reset
+	always @(posedge clk_amr or posedge rst)
+		if (rst)
+			rst_amr_cnt <= 4'hf;
+		else
+			rst_amr_cnt <= rst_amr_cnt + {4{rst_amr_cnt[3]}};
+
+	SB_GB rst_gbuf_I (
+		.USER_SIGNAL_TO_GLOBAL_BUFFER (rst_amr_cnt[3]),
+		.GLOBAL_BUFFER_OUTPUT         (rst_amr)
+	);
+
+	// Bit Counter
+	always @(posedge clk_amr or posedge rst_amr)
+		if (rst_amr)
+			su_bitcnt <= 6'h3f;
+		else
+			su_bitcnt <= su_bitcnt[5] ? (su_bitcnt + {6{su_bitcnt[5]}}) : { 1'b1, sui_bitcnt };
+
+	always @(*)
+		su_trig[0] = ~su_bitcnt[5];
+
+	always @(posedge clk_amr)
+		su_trig[2:1] <= su_trig[1:0];
+
+	// Sync signal
+	always @(posedge clk_amr)
+		iob_sync <= su_trig[0] ? sui_out_sync : iob_sync;
+
+	// Data shift register
+	always @(posedge clk_amr)
+		su_data <= su_trig[1] ? { sui_out_data } : { su_data[18:0], iob_sdata_in };
+
+	assign iob_sdata_out = su_data[19];
+
+	// Data in capture register
+	always @(posedge clk_amr)
+		if (su_trig[1])
+			sui_in_data[19:1] <= { su_data[17:0], iob_sdata_in };
+
+	always @(posedge clk_amr)
+		if (su_trig[2])
+			sui_in_data[0] <= iob_sdata_in;
+
+	// Flip signal
+	always @(posedge clk_amr)
+		if (rst_amr)
+			sui_flip[0] <= 1'b0;
+		else
+			sui_flip[0] <= sui_flip[0] ^ su_trig[2];
+
+	always @(posedge clk)
+	begin
+		if (rst) begin
+			sui_flip[2:1] <= 2'b00;
+			sui_ack       <= 1'b0;
+		end else begin
+			sui_flip[2:1] <= sui_flip[1:0];
+			sui_ack       <= sui_flip[2] ^ sui_flip[1];
+		end
+	end
+
+
+	// IOBs
+	// ----
+
+	SB_IO #(
+		.PIN_TYPE    (6'b0101_00),
+		.PULLUP      (1'b0),
+		.IO_STANDARD ("SB_LVCMOS")
+	) iob_sdata_out_I (
+		.PACKAGE_PIN (mc97_sdata_out),
+		.OUTPUT_CLK  (clk_amr),
+		.D_OUT_0     (iob_sdata_out)
+	);
+
+	SB_IO #(
+		.PIN_TYPE    (6'b0000_00),
+		.PULLUP      (1'b0),
+		.IO_STANDARD ("SB_LVCMOS")
+	) iob_sdata_in_I (
+		.PACKAGE_PIN (mc97_sdata_in),
+		.INPUT_CLK   (clk_amr),
+		.D_IN_1      (iob_sdata_in)
+	);
+
+	SB_IO #(
+		.PIN_TYPE    (6'b0101_00),
+		.PULLUP      (1'b0),
+		.IO_STANDARD ("SB_LVCMOS")
+	) iob_sync_I (
+		.PACKAGE_PIN (mc97_sync),
+		.OUTPUT_CLK  (clk_amr),
+		.D_OUT_0     (iob_sync)
+	);
+
+endmodule // mc97

+ 78 - 0
projects/usb_amr/rtl/mc97_fifo.v

@@ -0,0 +1,78 @@
+/*
+ * mc97_fifo.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2021  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module mc97_fifo (
+	// Write
+    input  wire [15:0] wr_data,
+    input  wire        wr_ena,
+    output wire        wr_full,
+
+	// Read
+    output wire [15:0] rd_data,
+    input  wire        rd_ena,
+    output wire        rd_empty,
+
+	// Control
+	output reg  [ 8:0] ctl_lvl,
+	input  wire        ctl_flush,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	(* keep *) reg [8:0] mod;
+
+	wire wr_ena_i;
+	wire rd_ena_i;
+
+
+	// FIFO Instance
+	// -------------
+
+	fifo_sync_ram #(
+		.DEPTH(256),
+		.WIDTH(16)
+	) fifo_I (
+		.wr_data  (wr_data),
+		.wr_ena   (wr_ena_i),
+		.wr_full  (wr_full),
+		.rd_data  (rd_data),
+		.rd_ena   (rd_ena_i),
+		.rd_empty (rd_empty),
+		.clk      (clk),
+		.rst      (rst)
+	);
+
+
+	// Control
+	// -------
+
+	assign wr_ena_i = ~wr_full  &  wr_ena ;
+	assign rd_ena_i = ~rd_empty & (rd_ena | ctl_flush);
+
+
+	// Level
+	// -----
+
+	// Level counter
+	always @(posedge clk)
+		if (rst)
+			ctl_lvl <= 0;
+		else
+			ctl_lvl <= ctl_lvl + mod;
+
+	assign mod = { {8{rd_ena_i & ~wr_ena_i}}, rd_ena_i ^ wr_ena_i };
+
+endmodule // mc97_fifo

+ 310 - 0
projects/usb_amr/rtl/mc97_wb.v

@@ -0,0 +1,310 @@
+/*
+ * mc97_wb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2021  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module mc97_wb (
+	// MC97 link
+	output wire mc97_sdata_out,
+	input  wire mc97_sdata_in,
+	output wire mc97_sync,
+	input  wire mc97_bitclk,
+	output reg  mc97_reset_n,
+
+	// Wishbone slave
+	input  wire [ 3:0] wb_addr,
+	output reg  [31:0] wb_rdata,
+	input  wire [31:0] wb_wdata,
+	input  wire        wb_we,
+	input  wire        wb_cyc,
+	output wire        wb_ack,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Wishbone
+	reg  b_ack;
+
+	wire b_wr_rst;
+	wire b_rd_rst;
+
+	reg  b_we_csr;
+	reg  b_we_ll_stat;
+	reg  b_we_ll_reg;
+	reg  b_we_ll_gpio_out;
+	reg  b_we_ll_fifo_data;
+	reg  b_re_ll_fifo_data;
+	reg  b_we_ll_fifo_csr;
+
+	// FIFO PCM Input
+    wire [15:0] fpi_w_data;
+    wire        fpi_w_ena;
+    wire        fpi_w_full;
+
+    wire [15:0] fpi_r_data;
+    wire        fpi_r_ena;
+    wire        fpi_r_empty;
+
+	wire [ 8:0] fpi_lvl;
+	reg         fpi_ena;
+	reg         fpi_flush;
+
+	// FIFO PCM output
+    wire [15:0] fpo_w_data;
+    wire        fpo_w_ena;
+    wire        fpo_w_full;
+
+    wire [15:0] fpo_r_data;
+    wire        fpo_r_ena;
+    wire        fpo_r_empty;
+
+	wire [ 8:0] fpo_lvl;
+	reg         fpo_ena;
+	reg         fpo_flush;
+
+	// LL PCM interface
+	wire [15:0] ll_pcm_out_data;
+	wire        ll_pcm_out_ack;
+	wire [15:0] ll_pcm_in_data;
+	wire        ll_pcm_in_stb;
+
+	// LL GPIO interface
+	wire [19:0] ll_gpio_in;
+	reg  [19:0] ll_gpio_out;
+	reg         ll_gpio_ena;
+
+	// LL Registers interface
+	reg  [ 5:0] ll_reg_addr;
+	reg  [15:0] ll_reg_wdata;
+	wire [15:0] ll_reg_rdata;
+	wire        ll_reg_rerr;
+	reg         ll_reg_valid;
+	reg         ll_reg_we;
+	wire        ll_reg_ack;
+
+	reg  [15:0] ll_reg_rdata_r;
+	wire        ll_reg_rerr_r;
+
+	// LL Misc interface
+	reg         ll_run;
+	wire        ll_stat_codec_ready;
+	wire [12:0] ll_stat_slot_valid;
+	wire [12:0] ll_stat_slot_req;
+	wire        ll_stat_clr;
+
+
+	// Bus Interface
+	// -------------
+
+	// Ack
+	always @(posedge clk)
+		b_ack <= wb_cyc & ~b_ack;
+
+	assign wb_ack = b_ack;
+
+	// Pre-control
+	assign b_wr_rst = ~wb_cyc | b_ack | ~wb_we;
+	assign b_rd_rst = ~wb_cyc | b_ack |  wb_we;
+
+	// Write
+	always @(posedge clk)
+	begin
+		if (b_wr_rst) begin
+			b_we_csr          <= 1'b0;
+			b_we_ll_stat      <= 1'b0;
+			b_we_ll_reg       <= 1'b0;
+			b_we_ll_gpio_out  <= 1'b0;
+			b_we_ll_fifo_data <= 1'b0;
+			b_we_ll_fifo_csr  <= 1'b0;
+		end else begin
+			b_we_csr          <= wb_addr == 4'h0;
+			b_we_ll_stat      <= wb_addr == 4'h1;
+			b_we_ll_reg       <= wb_addr == 4'h2;
+			b_we_ll_gpio_out  <= wb_addr == 4'h5;
+			b_we_ll_fifo_data <= wb_addr == 4'h6;
+			b_we_ll_fifo_csr  <= wb_addr == 4'h7;
+		end
+	end
+
+	// Read mux
+	always @(posedge clk)
+		if (b_rd_rst)
+			wb_rdata <= 32'h00000000;
+		else
+			casez (wb_addr[2:0])
+				3'h0:    wb_rdata <= { 29'h0, ll_gpio_ena, mc97_reset_n, ll_run };
+				3'h1:    wb_rdata <= { ll_stat_codec_ready, 2'h0, ll_stat_slot_req, 3'h0, ll_stat_slot_valid };
+				3'h2:    wb_rdata <= { ll_reg_valid, ll_reg_we, ll_reg_rerr_r, 7'h0, ll_reg_addr, ll_reg_rdata_r };
+				3'h4:    wb_rdata <= { 12'h0, ll_gpio_in  };
+				3'h5:    wb_rdata <= { 12'h0, ll_gpio_out };
+				3'h6:    wb_rdata <= { fpi_r_empty, 15'h0, fpi_r_data };
+				3'h7:    wb_rdata <= {
+								fpi_ena, fpi_flush, fpi_w_full, fpi_r_empty, 3'b000, fpi_lvl,
+								fpo_ena, fpo_flush, fpo_w_full, fpo_r_empty, 3'b000, fpo_lvl
+							};
+				default: wb_rdata <= 32'hxxxxxxxx;
+			endcase
+	
+	always @(posedge clk)
+		if (b_rd_rst)
+			b_re_ll_fifo_data <= 1'b0;
+		else
+			b_re_ll_fifo_data <= wb_addr == 4'h6;
+
+
+	// PCM
+	// ---
+
+	// Bus interface
+	assign fpi_r_ena  = b_re_ll_fifo_data & ~wb_rdata[28];
+	assign fpo_w_data = wb_wdata[15:0];
+	assign fpo_w_ena  = b_we_ll_fifo_data;
+
+	always @(posedge clk)
+		if (rst) begin
+			fpi_ena <= 1'b0;
+			fpo_ena <= 1'b0;
+		end else if (b_we_ll_fifo_csr) begin
+			fpi_ena <= wb_wdata[31];
+			fpo_ena <= wb_wdata[15];
+		end
+	
+	always @(posedge clk)
+		if (rst) begin
+			fpi_flush <= 1'b0;
+			fpo_flush <= 1'b0;
+		end else begin
+			fpi_flush <= (fpi_flush & ~fpi_r_empty) | (b_we_ll_fifo_csr & wb_wdata[30]);
+			fpo_flush <= (fpo_flush & ~fpo_r_empty) | (b_we_ll_fifo_csr & wb_wdata[14]);
+		end
+
+	// FIFO instances
+	mc97_fifo fifo_pcm_in_I (
+		.wr_data   (fpi_w_data),
+		.wr_ena    (fpi_w_ena),
+		.wr_full   (fpi_w_full),
+		.rd_data   (fpi_r_data),
+		.rd_ena    (fpi_r_ena),
+		.rd_empty  (fpi_r_empty),
+		.ctl_lvl   (fpi_lvl),
+		.ctl_flush (fpi_flush),
+		.clk       (clk),
+		.rst       (rst)
+	);
+
+	mc97_fifo fifo_pcm_out_I (
+		.wr_data   (fpo_w_data),
+		.wr_ena    (fpo_w_ena),
+		.wr_full   (fpo_w_full),
+		.rd_data   (fpo_r_data),
+		.rd_ena    (fpo_r_ena),
+		.rd_empty  (fpo_r_empty),
+		.ctl_lvl   (fpo_lvl),
+		.ctl_flush (fpo_flush),
+		.clk       (clk),
+		.rst       (rst)
+	);
+
+	// Low-Level interface
+	assign fpi_w_data = ll_pcm_in_data;
+	assign fpi_w_ena  = ll_pcm_in_stb & fpi_ena;
+
+	assign ll_pcm_out_data = fpo_r_data;
+	assign fpo_r_ena = ll_pcm_out_ack & fpo_ena;
+
+
+	// GPIO
+	// ----
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			ll_gpio_out <= 20'h00000;
+		else if (b_we_ll_gpio_out)
+			ll_gpio_out <= wb_wdata[19:0];
+
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			ll_gpio_ena <= 1'b0;
+		else if (b_we_csr)
+			ll_gpio_ena <= wb_wdata[2];
+
+
+	// Register access
+	// ---------------
+
+	always @(posedge clk)
+		ll_reg_valid <= (ll_reg_valid | b_we_ll_reg) & ~ll_reg_ack;
+
+	always @(posedge clk)
+		if (b_we_ll_reg) begin
+			ll_reg_we    <= wb_wdata[30];
+			ll_reg_addr  <= wb_wdata[21:16];
+			ll_reg_wdata <= wb_wdata[15:0];
+		end
+
+	always @(posedge clk)
+		if (ll_reg_ack & ~ll_reg_we) begin
+			ll_reg_rdata_r <= ll_reg_rdata;
+			ll_reg_rerr_r  <= ll_reg_rerr;
+		end
+
+
+	// Misc
+	// ----
+
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			mc97_reset_n <= 1'b1;
+			ll_run       <= 1'b0;
+		end else if (b_we_csr) begin
+			mc97_reset_n <= wb_wdata[1];
+			ll_run       <= wb_wdata[0];
+		end
+
+	assign ll_stat_clr = b_we_ll_stat;
+
+
+	// Low-level MC97
+	// --------------
+
+	mc97 ll_I (
+		.mc97_sdata_out  (mc97_sdata_out),
+		.mc97_sdata_in   (mc97_sdata_in),
+		.mc97_sync       (mc97_sync),
+		.mc97_bitclk     (mc97_bitclk),
+		.pcm_out_data    (ll_pcm_out_data),
+		.pcm_out_ack     (ll_pcm_out_ack),
+		.pcm_in_data     (ll_pcm_in_data),
+		.pcm_in_stb      (ll_pcm_in_stb),
+		.gpio_in         (ll_gpio_in),
+		.gpio_out        (ll_gpio_out),
+		.gpio_ena        (ll_gpio_ena),
+		.reg_addr        (ll_reg_addr),
+		.reg_wdata       (ll_reg_wdata),
+		.reg_rdata       (ll_reg_rdata),
+		.reg_rerr        (ll_reg_rerr),
+		.reg_valid       (ll_reg_valid),
+		.reg_we          (ll_reg_we),
+		.reg_ack         (ll_reg_ack),
+		.cfg_run         (ll_run),
+		.stat_codec_ready(ll_stat_codec_ready),
+		.stat_slot_valid (ll_stat_slot_valid),
+		.stat_slot_req   (ll_stat_slot_req),
+		.stat_clr        (ll_stat_clr),
+		.clk             (clk),
+		.rst             (rst)
+	);
+
+endmodule // mc97_wb

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2979 - 0
projects/usb_amr/rtl/picorv32.v


+ 43 - 0
projects/usb_amr/rtl/picorv32_ice40_regs.v

@@ -0,0 +1,43 @@
+/*
+ * picorv32_ice40_regs.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Implementation of register file for the PicoRV32 on iCE40
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module picorv32_ice40_regs (
+	input  wire        clk,
+	input  wire        wen,
+	input  wire  [5:0] waddr,
+	input  wire  [5:0] raddr1,
+	input  wire  [5:0] raddr2,
+	input  wire [31:0] wdata,
+	output wire [31:0] rdata1,
+	output wire [31:0] rdata2
+);
+
+	ice40_ebr #(
+		.READ_MODE(0),
+		.WRITE_MODE(0),
+		.MASK_WORKAROUND(0),
+		.NEG_WR_CLK(0),
+		.NEG_RD_CLK(1)
+	) regs[3:0] (
+		.wr_addr ({ 4{2'b00, waddr} }),
+		.wr_data ({ 2{wdata} }),
+		.wr_mask (64'h0000000000000000),
+		.wr_ena  (wen),
+		.wr_clk  (clk),
+		.rd_addr ({2'b00, raddr2, 2'b00, raddr2, 2'b00, raddr1, 2'b00, raddr1}),
+		.rd_data ({rdata2, rdata1}),
+		.rd_ena  (1'b1),
+		.rd_clk  (clk)
+	);
+
+endmodule // picorv32_ice40_regs

+ 38 - 0
projects/usb_amr/rtl/soc_bram.v

@@ -0,0 +1,38 @@
+/*
+ * soc_bram.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module soc_bram #(
+	parameter integer AW = 8,
+	parameter INIT_FILE = ""
+)(
+	input  wire [AW-1:0] addr,
+	output reg    [31:0] rdata,
+	input  wire   [31:0] wdata,
+	input  wire   [ 3:0] wmsk,
+	input  wire          we,
+	input  wire          clk
+);
+
+	reg [31:0] mem [0:(1<<AW)-1];
+
+	initial
+		if (INIT_FILE != "")
+			$readmemh(INIT_FILE, mem);
+
+	always @(posedge clk) begin
+		rdata <= mem[addr];
+		if (we & ~wmsk[0]) mem[addr][ 7: 0] <= wdata[ 7: 0];
+		if (we & ~wmsk[1]) mem[addr][15: 8] <= wdata[15: 8];
+		if (we & ~wmsk[2]) mem[addr][23:16] <= wdata[23:16];
+		if (we & ~wmsk[3]) mem[addr][31:24] <= wdata[31:24];
+	end
+
+endmodule // soc_bram

+ 157 - 0
projects/usb_amr/rtl/soc_picorv32_base.v

@@ -0,0 +1,157 @@
+/*
+ * soc_picorv32_base.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module soc_picorv32_base #(
+	parameter integer WB_N  =  6,
+	parameter integer WB_DW = 32,
+	parameter integer WB_AW = 16,
+	parameter integer SPRAM_AW = 14,	/* 14 => 64k, 15 => 128k */
+
+	/* auto */
+	parameter integer WB_MW = WB_DW / 8,
+	parameter integer WB_RW = WB_DW * WB_N,
+	parameter integer WB_AI = $clog2(WB_MW)
+)(
+	// Wishbone
+	output wire [WB_AW-1:0] wb_addr,
+	input  wire [WB_RW-1:0] wb_rdata,
+	output wire [WB_DW-1:0] wb_wdata,
+	output wire [WB_MW-1:0] wb_wmsk,
+	output wire             wb_we,
+	output wire [WB_N -1:0] wb_cyc,
+	input  wire [WB_N -1:0] wb_ack,
+
+	// Clock / Reset
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Memory bus
+	wire        mem_valid;
+	wire        mem_instr;
+	wire        mem_ready;
+	wire [31:0] mem_addr;
+	wire [31:0] mem_rdata;
+	wire [31:0] mem_wdata;
+	wire [ 3:0] mem_wstrb;
+
+	// RAM
+		// BRAM
+	wire [ 7:0] bram_addr;
+	wire [31:0] bram_rdata;
+	wire [31:0] bram_wdata;
+	wire [ 3:0] bram_wmsk;
+	wire        bram_we;
+
+		// SPRAM
+	wire [14:0] spram_addr;
+	wire [31:0] spram_rdata;
+	wire [31:0] spram_wdata;
+	wire [ 3:0] spram_wmsk;
+	wire        spram_we;
+
+
+	// CPU
+	// ---
+
+	picorv32 #(
+		.PROGADDR_RESET(32'h 0000_0000),
+		.STACKADDR(32'h 0000_0400),
+		.BARREL_SHIFTER(0),
+		.COMPRESSED_ISA(0),
+		.ENABLE_COUNTERS(0),
+		.ENABLE_MUL(0),
+		.ENABLE_DIV(0),
+		.ENABLE_IRQ(0),
+		.ENABLE_IRQ_QREGS(0),
+		.CATCH_MISALIGN(0),
+		.CATCH_ILLINSN(0)
+	) cpu_I (
+		.clk       (clk),
+		.resetn    (~rst),
+		.mem_valid (mem_valid),
+		.mem_instr (mem_instr),
+		.mem_ready (mem_ready),
+		.mem_addr  (mem_addr),
+		.mem_wdata (mem_wdata),
+		.mem_wstrb (mem_wstrb),
+		.mem_rdata (mem_rdata)
+	);
+
+
+	// Bus interface
+	// -------------
+
+	soc_picorv32_bridge #(
+		.WB_N (WB_N),
+		.WB_DW(WB_DW),
+		.WB_AW(WB_AW),
+		.WB_AI(WB_AI)
+	) pb_I (
+		.pb_addr     (mem_addr),
+		.pb_rdata    (mem_rdata),
+		.pb_wdata    (mem_wdata),
+		.pb_wstrb    (mem_wstrb),
+		.pb_valid    (mem_valid),
+		.pb_ready    (mem_ready),
+		.bram_addr   (bram_addr),
+		.bram_rdata  (bram_rdata),
+		.bram_wdata  (bram_wdata),
+		.bram_wmsk   (bram_wmsk),
+		.bram_we     (bram_we),
+		.spram_addr  (spram_addr),
+		.spram_rdata (spram_rdata),
+		.spram_wdata (spram_wdata),
+		.spram_wmsk  (spram_wmsk),
+		.spram_we    (spram_we),
+		.wb_addr     (wb_addr),
+		.wb_wdata    (wb_wdata),
+		.wb_wmsk     (wb_wmsk),
+		.wb_rdata    (wb_rdata),
+		.wb_cyc      (wb_cyc),
+		.wb_we       (wb_we),
+		.wb_ack      (wb_ack),
+		.clk         (clk),
+		.rst         (rst)
+	);
+
+
+	// Local memory
+	// ------------
+
+	// Boot memory
+	soc_bram #(
+		.INIT_FILE("boot.hex")
+	) bram_I (
+		.addr  (bram_addr),
+		.rdata (bram_rdata),
+		.wdata (bram_wdata),
+		.wmsk  (bram_wmsk),
+		.we    (bram_we),
+		.clk   (clk)
+	);
+
+	// Main memory
+	soc_spram #(
+		.AW(SPRAM_AW)
+	) spram_I (
+		.addr  (spram_addr[SPRAM_AW-1:0]),
+		.rdata (spram_rdata),
+		.wdata (spram_wdata),
+		.wmsk  (spram_wmsk),
+		.we    (spram_we),
+		.clk   (clk)
+	);
+
+endmodule // soc_picorv32_base

+ 186 - 0
projects/usb_amr/rtl/soc_picorv32_bridge.v

@@ -0,0 +1,186 @@
+/*
+ * soc_picorv32_bridge.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module soc_picorv32_bridge #(
+	parameter integer WB_N   =  8,
+	parameter integer WB_DW  = 32,
+	parameter integer WB_AW  = 16,
+	parameter integer WB_AI  =  2,
+	parameter integer WB_REG =  0	// [0] = cyc / [1] = addr/wdata/wstrb / [2] = ack/rdata
+)(
+	/* PicoRV32 bus */
+	input  wire [31:0] pb_addr,
+	output wire [31:0] pb_rdata,
+	input  wire [31:0] pb_wdata,
+	input  wire [ 3:0] pb_wstrb,
+	input  wire        pb_valid,
+	output wire        pb_ready,
+
+	/* BRAM */
+	output wire [ 7:0] bram_addr,
+	input  wire [31:0] bram_rdata,
+	output wire [31:0] bram_wdata,
+	output wire [ 3:0] bram_wmsk,
+	output wire        bram_we,
+
+	/* SPRAM */
+	output wire [14:0] spram_addr,
+	input  wire [31:0] spram_rdata,
+	output wire [31:0] spram_wdata,
+	output wire [ 3:0] spram_wmsk,
+	output wire        spram_we,
+
+	/* Wishbone buses */
+	output wire [WB_AW-1:0]        wb_addr,
+	input  wire [(WB_DW*WB_N)-1:0] wb_rdata,
+	output wire [WB_DW-1:0]        wb_wdata,
+	output wire [(WB_DW/8)-1:0]    wb_wmsk,
+	output wire                    wb_we,
+	output wire [WB_N-1:0]         wb_cyc,
+	input  wire [WB_N-1:0]         wb_ack,
+
+	/* Clock / Reset */
+	input  wire clk,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	wire ram_sel;
+	reg  ram_rdy;
+	wire [31:0] ram_rdata;
+
+	(* keep *) wire [WB_N-1:0] wb_match;
+	(* keep *) wire wb_cyc_rst;
+
+	reg  [31:0] wb_rdata_or;
+	wire [31:0] wb_rdata_out;
+	wire wb_rdy;
+
+
+	// RAM access
+	// ----------
+	// BRAM  : 0x00000000 -> 0x000003ff
+	// SPRAM : 0x00020000 -> 0x0003ffff
+
+	assign bram_addr  = pb_addr[ 9:2];
+	assign spram_addr = pb_addr[16:2];
+
+	assign bram_wdata  = pb_wdata;
+	assign spram_wdata = pb_wdata;
+
+	assign bram_wmsk  = ~pb_wstrb;
+	assign spram_wmsk = ~pb_wstrb;
+
+	assign bram_we  = pb_valid & ~pb_addr[31] & |pb_wstrb & ~pb_addr[17];
+	assign spram_we = pb_valid & ~pb_addr[31] & |pb_wstrb &  pb_addr[17];
+
+	assign ram_rdata = ~pb_addr[31] ? (pb_addr[17] ? spram_rdata : bram_rdata) : 32'h00000000;
+
+	assign ram_sel = pb_valid & ~pb_addr[31];
+
+	always @(posedge clk)
+		ram_rdy <= ram_sel && ~ram_rdy;
+
+
+	// Wishbone
+	// --------
+	// wb[x] = 0x8x000000 - 0x8xffffff
+
+	// Access Cycle
+	genvar i;
+	for (i=0; i<WB_N; i=i+1)
+		assign wb_match[i] = (pb_addr[27:24] == i);
+
+	if (WB_REG & 1) begin
+		// Register
+		reg [WB_N-1:0] wb_cyc_reg;
+		always @(posedge clk)
+			if (wb_cyc_rst)
+				wb_cyc_reg <= 0;
+			else
+				wb_cyc_reg <= wb_match & ~wb_ack;
+		assign wb_cyc = wb_cyc_reg;
+	end else begin
+		// Direct connection
+		assign wb_cyc = wb_cyc_rst ? { WB_N{1'b0} } : wb_match;
+	end
+
+	// Addr / Write-Data / Write-Mask / Write-Enable
+	if (WB_REG & 2) begin
+		// Register
+		reg [WB_AW-1:0] wb_addr_reg;
+		reg [WB_DW-1:0] wb_wdata_reg;
+		reg [(WB_DW/8)-1:0] wb_wmsk_reg;
+		reg wb_we_reg;
+
+		always @(posedge clk)
+		begin
+			wb_addr_reg  <= pb_addr[WB_AW+WB_AI-1:WB_AI];
+			wb_wdata_reg <= pb_wdata[WB_DW-1:0];
+			wb_wmsk_reg  <= ~pb_wstrb[(WB_DW/8)-1:0];
+			wb_we_reg    <= |pb_wstrb;
+		end
+
+		assign wb_addr  = wb_addr_reg;
+		assign wb_wdata = wb_wdata_reg;
+		assign wb_wmsk  = wb_wmsk_reg;
+		assign wb_we    = wb_we_reg;
+	end else begin
+		// Direct connection
+		assign wb_addr  = pb_addr[WB_AW+WB_AI-1:WB_AI];
+		assign wb_wdata = pb_wdata[WB_DW-1:0];
+		assign wb_wmsk  = pb_wstrb[(WB_DW/8)-1:0];
+		assign wb_we    = |pb_wstrb;
+	end
+
+	// Ack / Read-Data
+	always @(*)
+	begin : wb_or
+		integer i;
+		wb_rdata_or = 0;
+		for (i=0; i<WB_N; i=i+1)
+			wb_rdata_or[WB_DW-1:0] = wb_rdata_or[WB_DW-1:0] | wb_rdata[WB_DW*i+:WB_DW];
+	end
+
+	if (WB_REG & 4) begin
+		// Register
+		reg wb_rdy_reg;
+		reg [31:0] wb_rdata_reg;
+
+		always @(posedge clk)
+			wb_rdy_reg <= |wb_ack;
+
+		always @(posedge clk)
+			if (wb_cyc_rst)
+				wb_rdata_reg <= 32'h00000000;
+			else
+				wb_rdata_reg <= wb_rdata_or;
+
+		assign wb_cyc_rst = ~pb_valid | ~pb_addr[31] | wb_rdy_reg;
+		assign wb_rdy = wb_rdy_reg;
+		assign wb_rdata_out = wb_rdata_reg;
+	end else begin
+		// Direct connection
+		assign wb_cyc_rst = ~pb_valid | ~pb_addr[31];
+		assign wb_rdy = |wb_ack;
+		assign wb_rdata_out = wb_rdata_or;
+	end
+
+
+	// Final data combining
+	// --------------------
+
+	assign pb_rdata = ram_rdata | wb_rdata_out;
+	assign pb_ready = ram_rdy | wb_rdy;
+
+endmodule // soc_picorv32_bridge

+ 43 - 0
projects/usb_amr/rtl/soc_spram.v

@@ -0,0 +1,43 @@
+/*
+ * soc_spram.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module soc_spram #(
+	parameter integer AW = 14
+)(
+	input  wire [AW-1:0] addr,
+	output wire   [31:0] rdata,
+	input  wire   [31:0] wdata,
+	input  wire   [ 3:0] wmsk,
+	input  wire          we,
+	input  wire          clk
+);
+
+	wire [7:0] msk_nibble = {
+		wmsk[3], wmsk[3],
+		wmsk[2], wmsk[2],
+		wmsk[1], wmsk[1],
+		wmsk[0], wmsk[0]
+	};
+
+	ice40_spram_gen #(
+		.ADDR_WIDTH(AW),
+		.DATA_WIDTH(32)
+	) spram_I (
+		.addr(addr),
+		.rd_data(rdata),
+		.rd_ena(1'b1),
+		.wr_data(wdata),
+		.wr_mask(msk_nibble),
+		.wr_ena(we),
+		.clk(clk)
+	);
+
+endmodule // soc_spram

+ 157 - 0
projects/usb_amr/rtl/soc_usb.v

@@ -0,0 +1,157 @@
+/*
+ * soc_usb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module soc_usb #(
+	parameter integer DW = 32
+)(
+	// USB
+	inout  wire usb_dp,
+	inout  wire usb_dn,
+	output wire usb_pu,
+
+	// Wishbone slave
+	input  wire [  11:0] wb_addr,
+	output wire [DW-1:0] wb_rdata,
+	input  wire [DW-1:0] wb_wdata,
+	input  wire          wb_we,
+	input  wire    [1:0] wb_cyc,
+	output wire    [1:0] wb_ack,
+
+	// Misc
+	output wire usb_sof,
+
+	// Clock / Reset
+	input  wire clk_sys,
+	input  wire clk_48m,
+	input  wire rst
+);
+
+	// Signals
+	// -------
+
+	// Bus OR
+	wire [DW-1:0] wb_rdata_i[0:1];
+
+	// Wishbone in 48 MHz domain
+	wire [11:0] ub_addr;
+	wire [15:0] ub_wdata;
+	wire [15:0] ub_rdata;
+	wire        ub_cyc;
+	wire        ub_we;
+	wire        ub_ack;
+
+	// SoF in 48 MHz domain
+	wire        usb_sof_48m;
+
+	// EP Buffer
+	wire [ 8:0] ep_tx_addr_0;
+	wire [31:0] ep_tx_data_0;
+	wire        ep_tx_we_0;
+
+	wire [ 8:0] ep_rx_addr_0;
+	wire [31:0] ep_rx_data_1;
+	wire        ep_rx_re_0;
+
+	reg ack_ep;
+
+
+	// Cross-clock
+	// -----------
+		// Bring control reg wishbone to 48 MHz domain
+
+	xclk_wb #(
+		.DW(16),
+		.AW(12)
+	)  wb_48m_xclk_I (
+		.s_addr  (wb_addr[11:0]),
+		.s_wdata (wb_wdata[15:0]),
+		.s_rdata (wb_rdata_i[0][15:0]),
+		.s_cyc   (wb_cyc[0]),
+		.s_ack   (wb_ack[0]),
+		.s_we    (wb_we),
+		.s_clk   (clk_sys),
+		.m_addr  (ub_addr),
+		.m_wdata (ub_wdata),
+		.m_rdata (ub_rdata),
+		.m_cyc   (ub_cyc),
+		.m_ack   (ub_ack),
+		.m_we    (ub_we),
+		.m_clk   (clk_48m),
+		.rst     (rst)
+	);
+
+	if (DW != 16)
+		assign wb_rdata_i[0][DW-1:16] = 0;
+
+	xclk_strobe sof_xclk_I (
+		.in_stb  (usb_sof_48m),
+		.in_clk  (clk_48m),
+		.out_stb (usb_sof),
+		.out_clk (clk_sys),
+		.rst     (rst)
+	);
+
+
+	// Core
+	// ----
+
+	usb #(
+		.EPDW(32)
+	) usb_I (
+		.pad_dp       (usb_dp),
+		.pad_dn       (usb_dn),
+		.pad_pu       (usb_pu),
+		.ep_tx_addr_0 (ep_tx_addr_0),
+		.ep_tx_data_0 (ep_tx_data_0),
+		.ep_tx_we_0   (ep_tx_we_0),
+		.ep_rx_addr_0 (ep_rx_addr_0),
+		.ep_rx_data_1 (ep_rx_data_1),
+		.ep_rx_re_0   (ep_rx_re_0),
+		.ep_clk       (clk_sys),
+		.wb_addr      (ub_addr),
+		.wb_rdata     (ub_rdata),
+		.wb_wdata     (ub_wdata),
+		.wb_we        (ub_we),
+		.wb_cyc       (ub_cyc),
+		.wb_ack       (ub_ack),
+		.sof          (usb_sof_48m),
+		.clk          (clk_48m),
+		.rst          (rst)
+	);
+
+
+	// EP data
+	// -------
+
+	assign ep_tx_addr_0 = wb_addr[8:0];
+	assign ep_rx_addr_0 = wb_addr[8:0];
+
+	assign ep_tx_data_0 = wb_wdata;
+	assign wb_rdata_i[1] = ack_ep ? ep_rx_data_1 : 32'h00000000;
+
+	assign ep_tx_we_0 = wb_cyc[1] & wb_we & ~ack_ep;
+	assign ep_rx_re_0 = 1'b1;
+
+	assign wb_ack[1] = ack_ep;
+
+	always @(posedge clk_sys or posedge rst)
+		if (rst)
+			ack_ep <= 1'b0;
+		else
+			ack_ep <= wb_cyc[1] & ~ack_ep;
+
+
+	// Bus read data
+	// -------------
+
+	assign wb_rdata = wb_rdata_i[0] | wb_rdata_i[1];
+
+endmodule // soc_usb

+ 80 - 0
projects/usb_amr/rtl/sysmgr.v

@@ -0,0 +1,80 @@
+/*
+ * sysmgr.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module sysmgr (
+	input  wire clk_in,
+	input  wire rst_in,
+	output wire clk_24m,
+	output wire clk_48m,
+	output wire rst_out
+);
+
+	// Signals
+	wire pll_lock;
+	wire pll_reset_n;
+
+	wire clk_24m_i;
+	wire clk_48m_i;
+	wire rst_i;
+	reg [3:0] rst_cnt;
+
+	// PLL instance
+	SB_PLL40_2F_PAD #(
+		.DIVR(4'b0000),
+		.DIVF(7'b0111111),
+		.DIVQ(3'b100),
+		.FILTER_RANGE(3'b001),
+		.FEEDBACK_PATH("SIMPLE"),
+		.DELAY_ADJUSTMENT_MODE_FEEDBACK("FIXED"),
+		.FDA_FEEDBACK(4'b0000),
+		.SHIFTREG_DIV_MODE(2'b00),
+		.PLLOUT_SELECT_PORTA("GENCLK"),
+		.PLLOUT_SELECT_PORTB("GENCLK_HALF"),
+		.ENABLE_ICEGATE_PORTA(1'b0),
+		.ENABLE_ICEGATE_PORTB(1'b0)
+	) pll_I (
+		.PACKAGEPIN(clk_in),
+		.PLLOUTCOREA(),
+		.PLLOUTGLOBALA(clk_48m_i),
+		.PLLOUTCOREB(),
+		.PLLOUTGLOBALB(clk_24m_i),
+		.EXTFEEDBACK(1'b0),
+		.DYNAMICDELAY(8'h00),
+		.RESETB(pll_reset_n),
+		.BYPASS(1'b0),
+		.LATCHINPUTVALUE(1'b0),
+		.LOCK(pll_lock),
+		.SDI(1'b0),
+		.SDO(),
+		.SCLK(1'b0)
+	);
+
+	assign clk_24m = clk_24m_i;
+	assign clk_48m = clk_48m_i;
+
+	// PLL reset generation
+	assign pll_reset_n = ~rst_in;
+
+	// Logic reset generation
+	always @(posedge clk_24m_i or negedge pll_lock)
+		if (!pll_lock)
+			rst_cnt <= 4'h0;
+		else if (~rst_cnt[3])
+			rst_cnt <= rst_cnt + 1;
+
+	assign rst_i = ~rst_cnt[3];
+
+	SB_GB rst_gbuf_I (
+		.USER_SIGNAL_TO_GLOBAL_BUFFER(rst_i),
+		.GLOBAL_BUFFER_OUTPUT(rst_out)
+	);
+
+endmodule // sysmgr

+ 274 - 0
projects/usb_amr/rtl/top.v

@@ -0,0 +1,274 @@
+/*
+ * top.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2019-2020  Sylvain Munaut <tnt@246tNt.com>
+ * SPDX-License-Identifier: CERN-OHL-P-2.0
+ */
+
+`default_nettype none
+
+module top (
+	// SPI
+	inout  wire spi_mosi,
+	inout  wire spi_miso,
+	inout  wire spi_clk,
+	output wire spi_cs_n,
+
+	// USB
+	inout  wire usb_dp,
+	inout  wire usb_dn,
+	output wire usb_pu,
+
+	// Debug UART
+	input  wire uart_rx,
+	output wire uart_tx,
+
+	// Button
+	input  wire btn,
+
+	// LED
+	output wire [2:0] rgb,
+
+	// AMR interface
+	output wire mc97_sdata_out,
+	inout  wire mc97_sdata_in,
+	output wire mc97_sync,
+	input  wire mc97_bitclk,
+	output wire mc97_reset_n,
+
+	// Clock
+	input  wire clk_in
+);
+
+	localparam integer SPRAM_AW = 14; /* 14 => 64k, 15 => 128k */
+	localparam integer WB_N  =  7;
+
+	localparam integer WB_DW = 32;
+	localparam integer WB_AW = 16;
+	localparam integer WB_RW = WB_DW * WB_N;
+	localparam integer WB_MW = WB_DW / 8;
+
+	genvar i;
+
+
+	// Signals
+	// -------
+
+	// Wishbone
+	wire [WB_AW-1:0] wb_addr;
+	wire [WB_DW-1:0] wb_rdata [0:WB_N-1];
+	wire [WB_RW-1:0] wb_rdata_flat;
+	wire [WB_DW-1:0] wb_wdata;
+	wire [WB_MW-1:0] wb_wmsk;
+	wire [WB_N -1:0] wb_cyc;
+	wire             wb_we;
+	wire [WB_N -1:0] wb_ack;
+
+	// USB
+	wire usb_sof;
+
+	// WarmBoot
+	reg boot_now;
+	reg [1:0] boot_sel;
+
+	// Clock / Reset logic
+	wire clk_24m;
+	wire clk_48m;
+	wire rst;
+
+
+	// SoC
+	// ---
+
+	soc_picorv32_base #(
+		.WB_N    (WB_N),
+		.WB_DW   (WB_DW),
+		.WB_AW   (WB_AW),
+		.SPRAM_AW(SPRAM_AW)
+	) base_I (
+		.wb_addr (wb_addr),
+		.wb_rdata(wb_rdata_flat),
+		.wb_wdata(wb_wdata),
+		.wb_wmsk (wb_wmsk),
+		.wb_we   (wb_we),
+		.wb_cyc  (wb_cyc),
+		.wb_ack  (wb_ack),
+		.clk     (clk_24m),
+		.rst     (rst)
+	);
+
+	for (i=0; i<WB_N; i=i+1)
+		assign wb_rdata_flat[i*WB_DW+:WB_DW] = wb_rdata[i];
+
+
+	// UART [1]
+	// ----
+
+	uart_wb #(
+		.DIV_WIDTH(12),
+		.DW(WB_DW)
+	) uart_I (
+		.uart_tx  (uart_tx),
+		.uart_rx  (uart_rx),
+		.wb_addr  (wb_addr[1:0]),
+		.wb_rdata (wb_rdata[1]),
+		.wb_we    (wb_we),
+		.wb_wdata (wb_wdata),
+		.wb_cyc   (wb_cyc[1]),
+		.wb_ack   (wb_ack[1]),
+		.clk      (clk_24m),
+		.rst      (rst)
+	);
+
+
+	// SPI [2]
+	// ---
+
+	ice40_spi_wb #(
+		.N_CS(1),
+		.WITH_IOB(1),
+		.UNIT(0)
+	) spi_I (
+		.pad_mosi (spi_mosi),
+		.pad_miso (spi_miso),
+		.pad_clk  (spi_clk),
+		.pad_csn  (spi_cs_n),
+		.wb_addr  (wb_addr[3:0]),
+		.wb_rdata (wb_rdata[2]),
+		.wb_wdata (wb_wdata),
+		.wb_we    (wb_we),
+		.wb_cyc   (wb_cyc[2]),
+		.wb_ack   (wb_ack[2]),
+		.clk      (clk_24m),
+		.rst      (rst)
+	);
+
+
+	// RGB LEDs [3]
+	// --------
+
+	ice40_rgb_wb #(
+		.CURRENT_MODE("0b1"),
+		.RGB0_CURRENT("0b000001"),
+		.RGB1_CURRENT("0b000001"),
+		.RGB2_CURRENT("0b000001")
+	) rgb_I (
+		.pad_rgb    (rgb),
+		.wb_addr    (wb_addr[4:0]),
+		.wb_rdata   (wb_rdata[3]),
+		.wb_wdata   (wb_wdata),
+		.wb_we      (wb_we),
+		.wb_cyc     (wb_cyc[3]),
+		.wb_ack     (wb_ack[3]),
+		.clk        (clk_24m),
+		.rst        (rst)
+	);
+
+
+	// USB [4 & 5]
+	// ---
+
+	soc_usb #(
+		.DW(WB_DW)
+	) usb_I (
+		.usb_dp   (usb_dp),
+		.usb_dn   (usb_dn),
+		.usb_pu   (usb_pu),
+		.wb_addr  (wb_addr[11:0]),
+		.wb_rdata (wb_rdata[4]),
+		.wb_wdata (wb_wdata),
+		.wb_we    (wb_we),
+		.wb_cyc   (wb_cyc[5:4]),
+		.wb_ack   (wb_ack[5:4]),
+		.usb_sof  (usb_sof),
+		.clk_sys  (clk_24m),
+		.clk_48m  (clk_48m),
+		.rst      (rst)
+	);
+
+	assign wb_rdata[5] = 0;
+
+
+	// MC97 [6]
+	// ----
+
+	mc97_wb mc97_I (
+		.mc97_sdata_out (mc97_sdata_out),
+		.mc97_sdata_in  (mc97_sdata_in ),
+		.mc97_sync      (mc97_sync),
+		.mc97_bitclk    (mc97_bitclk),
+		.mc97_reset_n   (mc97_reset_n),
+		.wb_addr        (wb_addr[3:0]),
+		.wb_rdata       (wb_rdata[6]),
+		.wb_wdata       (wb_wdata),
+		.wb_we          (wb_we),
+		.wb_cyc         (wb_cyc[6]),
+		.wb_ack         (wb_ack[6]),
+		.clk            (clk_24m),
+		.rst            (rst)
+	);
+
+
+	// Warm Boot
+	// ---------
+
+	// Bus interface
+	always @(posedge clk_24m or posedge rst)
+		if (rst) begin
+			boot_now <= 1'b0;
+			boot_sel <= 2'b00;
+		end else if (wb_cyc[0] & wb_we & (wb_addr[2:0] == 3'b000)) begin
+			boot_now <= wb_wdata[2];
+			boot_sel <= wb_wdata[1:0];
+		end
+
+	assign wb_rdata[0] = 0;
+	assign wb_ack[0] = wb_cyc[0];
+
+	// Helper
+	dfu_helper #(
+		.TIMER_WIDTH(24),
+		.BTN_MODE(3),
+		.DFU_MODE(0)
+	) dfu_helper_I (
+		.boot_now(boot_now),
+		.boot_sel(boot_sel),
+		.btn_pad(btn),
+		.btn_val(),
+		.rst_req(),
+		.clk(clk_24m),
+		.rst(rst)
+	);
+
+
+	// Clock / Reset
+	// -------------
+
+`ifdef SIM
+	reg clk_48m_s = 1'b0;
+	reg clk_24m_s = 1'b0;
+	reg rst_s = 1'b1;
+
+	always #10.42 clk_48m_s <= !clk_48m_s;
+	always #20.84 clk_24m_s <= !clk_24m_s;
+
+	initial begin
+		#200 rst_s = 0;
+	end
+
+	assign clk_48m = clk_48m_s;
+	assign clk_24m = clk_24m_s;
+	assign rst = rst_s;
+`else
+	sysmgr sys_mgr_I (
+		.clk_in(clk_in),
+		.rst_in(1'b0),
+		.clk_48m(clk_48m),
+		.clk_24m(clk_24m),
+		.rst_out(rst)
+	);
+`endif
+
+endmodule // top

+ 112 - 0
projects/usb_amr/sim/mc97_tb.v

@@ -0,0 +1,112 @@
+/*
+ * mc97_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ */
+
+`default_nettype none
+
+module mc97_tb;
+
+	// Signals
+	// -------
+
+	wire mc97_sdata_out;
+	wire mc97_sdata_in;
+	wire mc97_sync;
+	reg  mc97_bitclk = 1'b0;
+
+	wire [15:0] pcm_out_data;
+	wire        pcm_out_ack;
+	wire [15:0] pcm_in_data;
+	wire        pcm_in_stb;
+
+	wire [19:0] gpio_in;
+	wire [19:0] gpio_out;
+	wire        gpio_ena;
+
+	wire [ 5:0] reg_addr;
+	wire [15:0] reg_wdata;
+	wire [15:0] reg_rdata;
+	wire        reg_rerr;
+	wire        reg_valid;
+	wire        reg_we;
+	wire        reg_ack;
+
+	wire        cfg_run;
+
+	wire        stat_codec_ready;
+	wire [12:0] stat_slot_valid;
+	wire [12:0] stat_slot_req;
+	wire        stat_clr;
+
+	reg clk = 1'b0;
+	reg rst = 1'b1;
+
+
+	// Setup recording
+	// ---------------
+
+	initial begin
+		$dumpfile("mc97_tb.vcd");
+		$dumpvars(0,mc97_tb);
+		# 2000000 $finish;
+	end
+
+	always #29.833 clk <= !clk; // 24 MHz
+	always #40.690 mc97_bitclk <= !mc97_bitclk; // 12.288 MHz
+
+	initial begin
+		#200 rst = 0;
+	end
+
+
+	// DUT
+	// ---
+
+
+	mc97 dut_I (
+		.mc97_sdata_out  (mc97_sdata_out),
+		.mc97_sdata_in   (mc97_sdata_in),
+		.mc97_sync       (mc97_sync),
+		.mc97_bitclk     (mc97_bitclk),
+		.pcm_out_data    (pcm_out_data),
+		.pcm_out_ack     (pcm_out_ack),
+		.pcm_in_data     (pcm_in_data),
+		.pcm_in_stb      (pcm_in_stb),
+		.gpio_in         (gpio_in),
+		.gpio_out        (gpio_out),
+		.gpio_ena        (gpio_ena),
+		.reg_addr        (reg_addr),
+		.reg_wdata       (reg_wdata),
+		.reg_rdata       (reg_rdata),
+		.reg_rerr        (reg_rerr),
+		.reg_valid       (reg_valid),
+		.reg_we          (reg_we),
+		.reg_ack         (reg_ack),
+		.cfg_run         (cfg_run),
+		.stat_codec_ready(stat_codec_ready),
+		.stat_slot_valid (stat_slot_valid),
+		.stat_slot_req   (stat_slot_req),
+		.stat_clr        (stat_clr),
+		.clk             (clk),
+		.rst             (rst)
+	);
+
+	assign mc97_sdata_in = mc97_sdata_out;
+
+	assign pcm_out_data = 16'hcafe;
+
+	assign gpio_ena = 1'b1;
+	assign gpio_out = 20'hb00b5;
+
+	assign reg_addr = 5'h1e;
+	assign reg_wdata = 16'hbabe;
+	assign reg_we = 1'b1;
+	assign reg_valid = 1'b1;
+
+	assign cfg_run = 1'b1;
+	assign stat_clr = 1'b0;
+
+endmodule // mc97_tb