Skip to content

Commit a1b0d60

Browse files
committed
Add USB UAC2 Audio IF to Zephyr Firmware
1 parent 2f6d8a5 commit a1b0d60

18 files changed

Lines changed: 1761 additions & 21 deletions

File tree

‎FPGA/boards/altera/cyc2/cyc2.qsf‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ set_location_assignment PIN_39 -to spictrl_cs
6161
set_location_assignment PIN_37 -to spictrl_irq
6262
set_location_assignment PIN_43 -to spictrl_miso
6363
set_location_assignment PIN_40 -to spictrl_mosi
64+
set_location_assignment PIN_198 -to esp_bclk
65+
set_location_assignment PIN_199 -to esp_fsclk
66+
set_location_assignment PIN_201 -to esp_din
67+
set_location_assignment PIN_203 -to esp_dout
6468
set_location_assignment PIN_89 -to c2_resetn
6569
set_location_assignment PIN_35 -to mdc
6670
set_location_assignment PIN_34 -to mdio

‎FPGA/boards/altera/cyc2/cyc2top.vhd‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use work.audioclks_pkg.all;
88
use work.system_cfg_pkg.all;
99
ENTITY cyc2top IS
1010
generic (
11-
syscfg: t_global_system_cfg := global_system_cfg_lo
11+
syscfg: t_global_system_cfg := global_system_cfg_rn2io
1212
);
1313
PORT
1414
(
@@ -17,6 +17,10 @@ ENTITY cyc2top IS
1717

1818
fs_adc: OUT STD_LOGIC; -- lrclk
1919
fs_dac: OUT STD_LOGIC; -- lrclk
20+
esp_fsclk : OUT STD_LOGIC;
21+
esp_bclk : OUT STD_LOGIC;
22+
esp_din : IN STD_LOGIC;
23+
esp_dout : OUT std_logic;
2024
fs_ext : OUT STD_LOGIC;
2125
tdm_out: OUT STD_LOGIC_VECTOR(3 downto 0); -- tdm out
2226
bclk_adc: OUT STD_LOGIC; -- bclk
@@ -76,14 +80,18 @@ signal mclk_reg : std_logic;
7680
signal dbg_mac_tx_clk_o : std_logic;
7781

7882
begin
79-
tdm_in_reg <= tdm_in(syscfg.AUDIO_CONFIG.TX_AD_CFG.TDM_PINS - 1 downto 0);
83+
--tdm_in_reg <= tdm_in(syscfg.AUDIO_CONFIG.TX_AD_CFG.TDM_PINS - 1 downto 0);
8084
INIT_DONE <= '1';
8185
tx_err_o <= (others => '0');
82-
tdm_out(syscfg.AUDIO_CONFIG.RX_DA_CFG.TDM_PINS - 1 downto 0) <= tdm_out_reg;
86+
--tdm_out(syscfg.AUDIO_CONFIG.RX_DA_CFG.TDM_PINS - 1 downto 0) <= tdm_out_reg;
87+
esp_dout <= tdm_out_reg(0);
88+
tdm_in_reg(0) <= esp_din;
8389
mclk <= mclk_reg;
8490
phy_rstn_o <= '1';
8591
bclk_adc <= not bclk;
8692
bclk_dac <= not bclk;
93+
esp_bclk <= not bclk;
94+
esp_fsclk <= fs;
8795
txen(0) <= phy_txen;
8896
phy0tx <= phy_tx;
8997
phy1tx <= phy_tx;

‎FPGA/packages/audioclks_pkg.vhd‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ package audioclks_pkg is
6161
fs_high_is_channel_2 => true,
6262
tdm_channels => 2
6363
);
64+
constant i2s_esp_config : t_audio_clock_io_cfg := (
65+
data_is_valid_on_rising_bclk_edge => true,
66+
bits_are_right_shifted_to_fs => true,
67+
fs_is_one_bclk_high => false,
68+
fs_high_is_channel_2 => true,
69+
tdm_channels => 2
70+
);
6471
constant i2s_lj_dac_config : t_audio_clock_io_cfg := (
6572
data_is_valid_on_rising_bclk_edge => true,
6673
bits_are_right_shifted_to_fs => false,

‎FPGA/packages/system_cfg_pkg.vhd‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ package system_cfg_pkg is
113113
TDM_PINS => 1,
114114
ADDA_CFG => i2s_adc_config
115115
);
116+
constant two_i2s_inputs_bclkinv : t_audio_cfg := (
117+
MAX_STREAMS => 2,
118+
BUFFER_DEPTH => 64,
119+
CHANNELS => 2,
120+
TDM_PINS => 1,
121+
ADDA_CFG => i2s_esp_config
122+
);
116123
constant four_i2s_outputs : t_audio_cfg := (
117124
MAX_STREAMS => 8,
118125
BUFFER_DEPTH => 256,
@@ -143,6 +150,14 @@ package system_cfg_pkg is
143150
RX_DA_CFG => four_i2s_outputs,
144151
TX_AD_CFG => disable_audio_path
145152
);
153+
constant audio_config_2io : t_global_audio_cfg := (
154+
MCLK_SPEED => audio_clock_24_57,
155+
BCLK_SPEED => audio_clock_03_07,
156+
USE_PARALLEL_INTERFACE => false,
157+
PARALLEL_BYTE_DEPTH => 3,
158+
RX_DA_CFG => two_i2s_inputs_bclkinv,
159+
TX_AD_CFG => two_i2s_inputs_bclkinv
160+
);
146161
constant audio_config_mi : t_global_audio_cfg := (
147162
MCLK_SPEED => audio_clock_24_57,
148163
BCLK_SPEED => audio_clock_03_07,
@@ -182,6 +197,17 @@ package system_cfg_pkg is
182197
ENABLE_METERING => false,
183198
PTP_MOVING_AVERAGE_DEPTH => 4
184199
);
200+
constant global_system_cfg_rn2io : t_global_system_cfg := (
201+
CLK_IN_SPEED => 25,
202+
SOC_TYPE => LITEX_SPIBONE,
203+
PLATFORM => ALTERA,
204+
PHY_CONFIG => std_lxt_cfg,
205+
AUDIO_CONFIG => audio_config_2io,
206+
STATIC_PTP_CONFIG => true,
207+
PTP_IN_SOFTWARE => true,
208+
ENABLE_METERING => false,
209+
PTP_MOVING_AVERAGE_DEPTH => 4
210+
);
185211
constant global_system_cfg_cyc : t_global_system_cfg := (
186212
CLK_IN_SPEED => 12,
187213
SOC_TYPE => LITEX_VEXRISCV_SDRAM,

‎soc_firmware/app/CMakeLists.txt‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,20 @@ elseif(CONFIG_FPGA_HAL_LITEX OR CONFIG_FPGA_HAL_SPI OR CONFIG_FPGA_HAL_MOCK)
239239
target_sources(app PRIVATE src/nmos/nmos_json.c)
240240
endif()
241241
target_sources_ifdef(CONFIG_SD_CONFIG app PRIVATE src/sd_config.c)
242+
# USB Audio Class 2 <-> FPGA I2S bridge (ESP32-S3 external MCU only).
243+
# usb_audio_i2s.c drives I2S0 + GDMA through the ESP HAL directly (the
244+
# Zephyr I2S driver cannot stream gaplessly); the I2S HAL sources are
245+
# only compiled by the hal_espressif module when CONFIG_I2S_ESP32 is
246+
# set, so pull them in here.
247+
if(CONFIG_AES67_USB_AUDIO)
248+
target_sources(app PRIVATE src/usb_audio.c src/usb_audio_i2s.c)
249+
# register dump in `usbaudio dwc2` (bring-up diagnostics)
250+
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/drivers/usb/common)
251+
target_sources(app PRIVATE
252+
${ZEPHYR_HAL_ESPRESSIF_MODULE_DIR}/components/esp_hal_i2s/i2s_hal.c
253+
${ZEPHYR_HAL_ESPRESSIF_MODULE_DIR}/components/hal/hal_utils.c
254+
)
255+
endif()
242256
target_sources_ifdef(CONFIG_FLASH_CONFIG app PRIVATE src/flash_config.c)
243257
if(CONFIG_THREAD_RUNTIME_STATS AND CONFIG_SHELL)
244258
target_sources(app PRIVATE src/perf_stats.c)

‎soc_firmware/app/Kconfig‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,75 @@ config APP_SPI_BRINGUP
5353
(HTTP / RTSP / mDNS / PTP BMC) is the default for every HAL
5454
backend that has networking, including SPI.
5555

56+
menu "USB audio interface (UAC2 <-> FPGA I2S)"
57+
58+
config AES67_USB_AUDIO
59+
bool "Expose the FPGA audio port as a USB Audio Class 2 device"
60+
default n
61+
depends on FPGA_HAL_SPI
62+
depends on USB_DEVICE_STACK_NEXT && USBD_AUDIO2_CLASS
63+
depends on SOC_SERIES_ESP32S3
64+
help
65+
Experimental "AES67 USB audio interface": the ESP32-S3's USB-OTG
66+
core enumerates as a UAC2 device (2 in / 2 out, 48 kHz, 24 bit)
67+
and the samples are shovelled between the USB isochronous
68+
endpoints and an I2S port on which the ESP32 is the *target*
69+
(slave) of the FPGA's PTP-disciplined media clock. Implicit
70+
feedback keeps the host's rate locked to the FPGA clock. See
71+
soc_firmware/docs/usb-audio-plan.md and src/usb_audio.c.
72+
73+
Needs the zephyr,uac2 node + i2s0 + usb_otg from the board overlay
74+
and moves the console off the USB Serial/JTAG port (shared PHY).
75+
76+
if AES67_USB_AUDIO
77+
78+
config AES67_USB_AUDIO_VID
79+
hex "USB vendor ID"
80+
default 0x1209
81+
help
82+
0x1209 is pid.codes; PIDs 0x0001-0x0010 there are reserved for
83+
testing/private use, which is what this experiment is. Replace
84+
before shipping anything.
85+
86+
config AES67_USB_AUDIO_PID
87+
hex "USB product ID"
88+
default 0x0001
89+
90+
config AES67_USB_AUDIO_MANUFACTURER
91+
string "USB manufacturer string"
92+
default "AES67 project"
93+
94+
config AES67_USB_AUDIO_PRODUCT
95+
string "USB product string suffix"
96+
default "AES67USB"
97+
help
98+
The USB product string is "<hostname> <suffix>", the hostname being
99+
the same one the device uses on the network (aes67_config). The
100+
suffix alone is used when no hostname is configured. Note that
101+
desktops resolve the model name from the USB ID database first;
102+
with a pid.codes test PID they display "pid.codes Test PID"
103+
regardless of this string.
104+
105+
config AES67_USB_AUDIO_MAX_POWER
106+
int "bMaxPower (2 mA units)"
107+
default 250
108+
help
109+
Bus power the configuration descriptor asks for. 250 = 500 mA:
110+
the DevKitC + FPGA board are powered from this port in the
111+
bench setup.
112+
113+
config AES67_USB_AUDIO_AUTOSTART
114+
bool "Enable the USB device at boot"
115+
default y
116+
help
117+
Bring the USB device up at the end of main(). Off = the I2S/USB
118+
path stays idle until 'usbaudio start' is issued from the shell
119+
(bring-up aid).
120+
121+
endif # AES67_USB_AUDIO
122+
123+
endmenu
124+
56125
menu "RAVENNA RTSP Server/Client"
57126

58127
config RTSP

‎soc_firmware/app/boards/esp32s3_devkitc.overlay‎

Lines changed: 134 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,20 @@
1919
* ADDA nRST = GPIO14 (active low — low holds the card in reset;
2020
* shared with the display controller)
2121
*
22-
* Console/shell: built-in USB Serial/JTAG (usb_serial), not uart0.
22+
* Console/shell: uart0 (GPIO43 TX / GPIO44 RX, the DevKitC's "UART"
23+
* USB connector). The built-in USB Serial/JTAG controller shares the
24+
* chip's single internal USB PHY with the USB-OTG core; enabling the USB
25+
* audio device (usb_otg) hands the PHY to OTG, so the "USB" connector is
26+
* the audio interface and can no longer carry the console.
27+
*
28+
* USB audio (UAC2) <-> FPGA I2S, ESP32 is I2S *target* (slave) of the
29+
* FPGA's media clock (BCLK = 64 fs, WS = fs, 24-bit in 32-bit slots):
30+
* BCLK = GPIO40 (in, FPGA bclk_dac / bclk_adc)
31+
* WS = GPIO41 (in, FPGA fs_dac / fs_adc)
32+
* DIN = GPIO42 (in, FPGA tdm_out[0] -> USB record)
33+
* DOUT = GPIO39 (out, FPGA tdm_in[0] <- USB playback)
34+
* GPIO39..42 are the chip's own JTAG pads (unused: FPGA JTAG is bit-banged
35+
* on GPIO4..7, the ESP itself is debugged over USB-JTAG/UART).
2336
*
2437
* Display controller (LEDs/buttons/7-segment, 115200 8N1):
2538
* TX = GPIO17, RX = GPIO18 (board default uart1 pinmux)
@@ -33,6 +46,8 @@
3346
*/
3447

3548
#include <zephyr/dt-bindings/gpio/gpio.h>
49+
#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>
50+
#include <zephyr/dt-bindings/usb/audio.h>
3651

3752
&psram0 {
3853
size = <DT_SIZE_M(8)>;
@@ -55,6 +70,47 @@
5570
};
5671
};
5772

73+
/* I2S0 as full-duplex target: BCLK and WS are inputs only, so they
74+
* are muxed with explicit ESP32_PINMUX(pin, <in-signal>, ESP_NOSIG)
75+
* instead of the stock I2S0_O_BCK_GPIOnn macros, which also connect
76+
* the peripheral's output signal and enable the pad driver — that
77+
* would fight the FPGA's clock outputs. The TX (O_) and RX (I_)
78+
* halves both get the same pads (the GPIO matrix fans one pad out
79+
* to several input signals); the driver additionally sets
80+
* sig_loopback so RX follows TX's clock inputs. */
81+
i2s0_fpga: i2s0_fpga {
82+
group1 {
83+
pinmux = <ESP32_PINMUX(40, ESP_I2S0O_BCK_IN, ESP_NOSIG)>,
84+
<ESP32_PINMUX(40, ESP_I2S0I_BCK_IN, ESP_NOSIG)>,
85+
<ESP32_PINMUX(41, ESP_I2S0O_WS_IN, ESP_NOSIG)>,
86+
<ESP32_PINMUX(41, ESP_I2S0I_WS_IN, ESP_NOSIG)>,
87+
<I2S0_I_SD_GPIO42>;
88+
};
89+
group2 {
90+
pinmux = <I2S0_O_SD_GPIO39>;
91+
output-low;
92+
};
93+
};
94+
};
95+
96+
/* I2S0 <-> FPGA audio port. usb_audio_i2s.c programs the controller and
97+
* GDMA channel 0 through the ESP HAL (Zephyr's I2S driver restarts the DMA
98+
* per block and drops samples in the gap); it only takes reg, clocks and
99+
* the pinctrl from this node. The node stays disabled so the Zephyr driver
100+
* is never instantiated, and `dma` stays disabled as in the board DTS. */
101+
&i2s0 {
102+
pinctrl-0 = <&i2s0_fpga>;
103+
pinctrl-names = "default";
104+
};
105+
106+
/* USB-OTG device controller (already labelled zephyr_udc0 / okay in the
107+
* board DTS) and the USB Audio Class 2 function on top of it. */
108+
&usb_otg {
109+
status = "okay";
110+
};
111+
112+
&usb_serial {
113+
status = "disabled";
58114
};
59115

60116
&spi2 {
@@ -101,19 +157,9 @@ i2c2: &i2c0 {
101157
pinctrl-names = "default";
102158
};
103159

104-
/* Console + shell on the ESP32-S3's built-in USB Serial/JTAG controller
105-
* (the "USB" port of the DevKitC, CDC-ACM towards the host) instead of
106-
* uart0 / the UART-bridge port. Zephyr derives ESP_CONSOLE_USB_SERIAL_JTAG
107-
* and the ROM console device number from this chosen node, so no extra
108-
* Kconfig is needed. uart0 stays enabled as a plain peripheral. */
109-
&usb_serial {
110-
status = "okay";
111-
};
112-
113160
/ {
161+
/* Console + shell stay on the board default, uart0 (see header). */
114162
chosen {
115-
zephyr,console = &usb_serial;
116-
zephyr,shell-uart = &usb_serial;
117163
zephyr,fpga-spi = &fpga_spi;
118164
};
119165

@@ -145,3 +191,79 @@ i2c2: &i2c0 {
145191
gpios = <&gpio0 14 GPIO_ACTIVE_LOW>;
146192
};
147193
};
194+
195+
/* USB Audio Class 2 function: 2 in / 2 out, 48 kHz, 24 bit in 3-byte
196+
* subslots (294 B per Full-Speed packet; 4-byte subslots would need a
197+
* 392 B RxFIFO, more than the ESP32-S3's DWC2 can spare next to the IN
198+
* FIFO, see CONFIG_UDC_DWC2_RXFIFO_MAX_PERCENTAGE). The clock is the FPGA's
199+
* PTP-disciplined media clock: external, free-running, not SOF-locked ->
200+
* asynchronous endpoints. Rate matching uses implicit feedback: the IN
201+
* stream carries exactly as many frames per SOF as I2S delivered and the
202+
* host derives the OUT packet size from that; both directions share the
203+
* clock, so the playback queue stays level. Entity IDs are the child
204+
* index + 1 (UAC2_ENTITY_ID()), the order below is therefore significant. */
205+
/ {
206+
uac2_fpga: usb_audio2 {
207+
compatible = "zephyr,uac2";
208+
status = "okay";
209+
full-speed;
210+
audio-function = <AUDIO_FUNCTION_IO_BOX>;
211+
212+
uac_aclk: aclk {
213+
compatible = "zephyr,uac2-clock-source";
214+
clock-type = "external";
215+
frequency-control = "read-only";
216+
sampling-frequencies = <48000>;
217+
};
218+
219+
/* Host -> device (USB playback) ... */
220+
usb_out_terminal: usb_out_terminal {
221+
compatible = "zephyr,uac2-input-terminal";
222+
clock-source = <&uac_aclk>;
223+
terminal-type = <USB_TERMINAL_STREAMING>;
224+
front-left;
225+
front-right;
226+
};
227+
228+
/* ... leaves through the I2S port into the FPGA (AES67 TX). */
229+
fpga_tx_terminal: fpga_tx {
230+
compatible = "zephyr,uac2-output-terminal";
231+
data-source = <&usb_out_terminal>;
232+
clock-source = <&uac_aclk>;
233+
terminal-type = <EXTERNAL_TERMINAL_DIGITAL_AUDIO_INTERFACE>;
234+
};
235+
236+
/* FPGA (AES67 RX) enters through the I2S port ... */
237+
fpga_rx_terminal: fpga_rx {
238+
compatible = "zephyr,uac2-input-terminal";
239+
clock-source = <&uac_aclk>;
240+
terminal-type = <EXTERNAL_TERMINAL_DIGITAL_AUDIO_INTERFACE>;
241+
front-left;
242+
front-right;
243+
};
244+
245+
/* ... and goes to the host (USB record). */
246+
usb_in_terminal: usb_in_terminal {
247+
compatible = "zephyr,uac2-output-terminal";
248+
data-source = <&fpga_rx_terminal>;
249+
clock-source = <&uac_aclk>;
250+
terminal-type = <USB_TERMINAL_STREAMING>;
251+
};
252+
253+
as_iso_out: out_interface {
254+
compatible = "zephyr,uac2-audio-streaming";
255+
linked-terminal = <&usb_out_terminal>;
256+
implicit-feedback;
257+
subslot-size = <3>;
258+
bit-resolution = <24>;
259+
};
260+
261+
as_iso_in: in_interface {
262+
compatible = "zephyr,uac2-audio-streaming";
263+
linked-terminal = <&usb_in_terminal>;
264+
implicit-feedback;
265+
subslot-size = <3>;
266+
bit-resolution = <24>;
267+
};
268+
};
269+
};

0 commit comments

Comments
 (0)