Example to record and play back audio - This example starts recording via I2S when the top-left switch on Nexus is pressed, and continues recording until either the buffer is full (5 seconds), or the user releases the button -- whichever comes first. Afterwards, it will transmit the recorded audio out via I2S TX. - When recording, a moving average of the two channels are calculated separately, and used to cancel out any DC offset in the input. - Before transmitting, the amplitude of the audio is scaled to take about 75% of the dynamic range. - For the live demo, https://www.adafruit.com/product/3421 was used as the microphone input, and https://www.adafruit.com/product/3006 and a generic speaker were used for output. Change-Id: Ie4413d5578f3f07bbcc37e2640b5433528a22c61
diff --git a/sw/device/examples/i2s_record_playback/BUILD b/sw/device/examples/i2s_record_playback/BUILD new file mode 100644 index 0000000..4457850 --- /dev/null +++ b/sw/device/examples/i2s_record_playback/BUILD
@@ -0,0 +1,54 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +load("//rules:matcha.bzl", "bin_to_c_file", "sec_flash_binary", "smc_flash_binary", "NEXUS_CORE_TARGETS") + +sec_flash_binary( + name = "i2s_record_playback", + srcs = [ + "i2s_record_playback_sec.c", + "i2s_record_playback_smc_bin.h", + "i2s_record_playback.h", + ], + per_device_deps = { + "fpga_nexus": [NEXUS_CORE_TARGETS.get("secure_core")], + }, + copts = [ + "-nostdlib", + "-ffreestanding", + ], + deps = [ + "//sw/device/tests:test_lib", + "//sw/device/lib/dif:smc_ctrl", + "//sw/device/lib/dif:tlul_mailbox", + "@lowrisc_opentitan//sw/device/lib/dif:rv_plic", + ], +) + +smc_flash_binary( + name = "i2s_record_playback_smc", + srcs = [ + "i2s_record_playback_smc.c", + "i2s_record_playback.h", + ], + per_device_deps = { + "fpga_nexus": [NEXUS_CORE_TARGETS.get("smc")], + }, + copts = [ + "-nostdlib", + "-ffreestanding", + ], + deps = [ + "//sw/device/tests:test_lib_smc", + "//sw/device/lib/dif:i2s", + "//sw/device/lib/dif:tlul_mailbox", + "@lowrisc_opentitan//sw/device/lib/dif:rv_plic", + ], +) + +bin_to_c_file( + name = "i2s_record_playback_smc_bin", + srcs = ["i2s_record_playback_smc_fpga_nexus_bin"], + var_name = "smc_bin", +)
diff --git a/sw/device/examples/i2s_record_playback/i2s_record_playback.h b/sw/device/examples/i2s_record_playback/i2s_record_playback.h new file mode 100644 index 0000000..d150ac9 --- /dev/null +++ b/sw/device/examples/i2s_record_playback/i2s_record_playback.h
@@ -0,0 +1,11 @@ +#ifndef SW_DEVICE_EXAMPLES_I2S_RECORD_PLAYBACK_I2S_RECORD_PLAYBACK_H_ +#define SW_DEVICE_EXAMPLES_I2S_RECORD_PLAYBACK_I2S_RECORD_PLAYBACK_H_ + +#include <stdint.h> + +const uint32_t kMessageGpioPressed = 0xdeadbeef; +const uint32_t kMessageGpioReleased = 0x21524110; +const uint32_t kMessageEnableLed = 0xcafeb0ba; +const uint32_t kMessageDisableLed = 0x35014f45; + +#endif // SW_DEVICE_EXAMPLES_I2S_RECORD_PLAYBACK_I2S_RECORD_PLAYBACK_H_
diff --git a/sw/device/examples/i2s_record_playback/i2s_record_playback_sec.c b/sw/device/examples/i2s_record_playback/i2s_record_playback_sec.c new file mode 100644 index 0000000..c406e81 --- /dev/null +++ b/sw/device/examples/i2s_record_playback/i2s_record_playback_sec.c
@@ -0,0 +1,119 @@ +// Copyright 2023 Google LLC. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "sw/device/examples/i2s_record_playback/i2s_record_playback.h" +#include "sw/device/examples/i2s_record_playback/i2s_record_playback_smc_bin.h" // Generated. +#include "sw/device/lib/arch/device.h" +#include "sw/device/lib/dif/dif_gpio.h" +#include "sw/device/lib/dif/dif_pinmux.h" +#include "sw/device/lib/dif/dif_rv_plic.h" +#include "sw/device/lib/dif/dif_smc_ctrl.h" +#include "sw/device/lib/dif/dif_tlul_mailbox.h" +#include "sw/device/lib/dif/dif_uart.h" +#include "sw/device/lib/runtime/hart.h" +#include "sw/device/lib/runtime/irq.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_test_config.h" +#include "sw/device/lib/testing/test_framework/test_util.h" + +static dif_gpio_t gpio; +static dif_pinmux_t pinmux; +static dif_rv_plic_t plic; +static dif_tlul_mailbox_t mailbox; +static dif_smc_ctrl_t smc_ctrl; +static dif_uart_t uart; + +OTTF_DEFINE_TEST_CONFIG(); + +void ottf_external_isr(void) { + dif_rv_plic_irq_id_t interrupt_id; + CHECK_DIF_OK( + dif_rv_plic_irq_claim(&plic, kTopMatchaPlicTargetIbex0, &interrupt_id)); + + top_matcha_plic_peripheral_t peripheral_id = + top_matcha_plic_interrupt_for_peripheral[interrupt_id]; + CHECK(peripheral_id == kTopMatchaPlicPeripheralGpio || + peripheral_id == kTopMatchaPlicPeripheralTlulMailboxSec, + "ISR got an interrupt from a non-GPIO"); + + if (peripheral_id == kTopMatchaPlicPeripheralGpio) { + dif_gpio_irq_t gpio_num = interrupt_id - kTopMatchaPlicIrqIdGpioGpio0; + CHECK(gpio_num == 0); + CHECK_DIF_OK(dif_gpio_irq_acknowledge(&gpio, gpio_num)); + bool gpio_state; + CHECK_DIF_OK(dif_gpio_read(&gpio, gpio_num, &gpio_state)); + // The GPIO will be high when the button is not pressed. + uint32_t msg = gpio_state ? kMessageGpioReleased : kMessageGpioPressed; + CHECK_DIF_OK(dif_tlul_mailbox_send_message(&mailbox, &msg)); + } + if (peripheral_id == kTopMatchaPlicPeripheralTlulMailboxSec) { + CHECK_DIF_OK( + dif_tlul_mailbox_irq_acknowledge(&mailbox, kDifTlulMailboxIrqRtirq)); + uint32_t msg; + CHECK_DIF_OK(dif_tlul_mailbox_read_message(&mailbox, &msg)); + if (msg == kMessageEnableLed) { + CHECK_DIF_OK(dif_gpio_write(&gpio, 1, true)); + } + if (msg == kMessageDisableLed) { + CHECK_DIF_OK(dif_gpio_write(&gpio, 1, false)); + } + } + CHECK_DIF_OK( + dif_rv_plic_irq_complete(&plic, kTopMatchaPlicTargetIbex0, interrupt_id)); +} + +void _ottf_main(void) { + // Initialize the UART to enable logging for non-DV simulation platforms. + if (kDeviceType != kDeviceSimDV) { + init_uart(TOP_MATCHA_UART0_BASE_ADDR, &uart); + } + CHECK_DIF_OK( + dif_gpio_init(mmio_region_from_addr(TOP_MATCHA_GPIO_BASE_ADDR), &gpio)); + CHECK_DIF_OK(dif_pinmux_init( + mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux)); + CHECK_DIF_OK(dif_smc_ctrl_init( + mmio_region_from_addr(TOP_MATCHA_SMC_CTRL_BASE_ADDR), &smc_ctrl)); + CHECK_DIF_OK(dif_tlul_mailbox_init( + mmio_region_from_addr(TOP_MATCHA_TLUL_MAILBOX_SEC_BASE_ADDR), &mailbox)); + + if (kDeviceType == kDeviceFpgaNexus) { + memcpy((void*)TOP_MATCHA_RAM_SMC_BASE_ADDR, smc_bin, smc_bin_len); + } + CHECK_DIF_OK(dif_smc_ctrl_set_en(&smc_ctrl)); + + // Bind switch on IOR5 to GPIO0 and configure interrupts. + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &plic)); + CHECK_DIF_OK(dif_pinmux_output_select(&pinmux, kTopMatchaPinmuxMioOutIoa8, + kTopMatchaPinmuxOutselGpioGpio1)); + CHECK_DIF_OK(dif_gpio_output_set_enabled(&gpio, 1, kDifToggleEnabled)); + CHECK_DIF_OK(dif_pinmux_input_select(&pinmux, + kTopMatchaPinmuxPeripheralInGpioGpio0, + kTopMatchaPinmuxInselIor5)); + CHECK_DIF_OK(dif_gpio_output_set_enabled(&gpio, 0, kDifToggleDisabled)); + CHECK_DIF_OK(dif_gpio_irq_set_trigger(&gpio, (1 << 0) /* GPIO 0*/, + kDifGpioIrqTriggerEdgeRisingFalling)); + CHECK_DIF_OK( + dif_gpio_irq_set_enabled(&gpio, kDifGpioIrqGpio0, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&plic, kTopMatchaPlicIrqIdGpioGpio0, + kDifRvPlicMaxPriority)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(&plic, kTopMatchaPlicIrqIdGpioGpio0, + kTopMatchaPlicTargetIbex0, + kDifToggleEnabled)); + CHECK_DIF_OK(dif_tlul_mailbox_irq_set_enabled( + &mailbox, kDifTlulMailboxIrqRtirq, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &plic, kTopMatchaPlicIrqIdTlulMailboxSecRtirq, kTopMatchaPlicTargetIbex0, + kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority( + &plic, kTopMatchaPlicIrqIdTlulMailboxSecRtirq, kDifRvPlicMaxPriority)); + irq_global_ctrl(true); + irq_external_ctrl(true); + + while (true) { + wait_for_interrupt(); + } + __builtin_unreachable(); +}
diff --git a/sw/device/examples/i2s_record_playback/i2s_record_playback_smc.c b/sw/device/examples/i2s_record_playback/i2s_record_playback_smc.c new file mode 100644 index 0000000..a236e84 --- /dev/null +++ b/sw/device/examples/i2s_record_playback/i2s_record_playback_smc.c
@@ -0,0 +1,308 @@ +// Copyright 2022 Google LLC. +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include <limits.h> + +#include "hw/top_matcha/ip/i2s/data/i2s_regs.h" +#include "hw/top_matcha/sw/autogen/top_matcha.h" +#include "sw/device/examples/i2s_record_playback/i2s_record_playback.h" +#include "sw/device/lib/dif/dif_i2s.h" +#include "sw/device/lib/dif/dif_rv_plic.h" +#include "sw/device/lib/dif/dif_tlul_mailbox.h" +#include "sw/device/lib/runtime/hart.h" +#include "sw/device/lib/runtime/irq.h" +#include "sw/device/lib/runtime/log.h" +#include "sw/device/lib/runtime/print.h" +#include "sw/device/lib/testing/test_framework/check.h" +#include "sw/device/lib/testing/test_framework/ottf_test_config.h" +#include "sw/device/lib/testing/test_framework/status.h" +#include "sw/device/lib/testing/test_framework/test_util.h" + +OTTF_DEFINE_TEST_CONFIG(); + +static dif_i2s_t i2s; +static dif_rv_plic_t plic_smc; +static dif_uart_t smc_uart; +static dif_tlul_mailbox_t mailbox; + +volatile bool i2s_rx_watermark_seen = false; +volatile bool i2s_tx_watermark_seen = false; +volatile bool i2s_tx_empty_seen = false; +volatile bool record = false; + +#define ABS(x) (x > 0 ? x : -x) + +#define kFilterSamples (256) +int16_t samples_left[kFilterSamples] = {0}; +int16_t samples_right[kFilterSamples] = {0}; +size_t index_left = 0; +size_t index_right = 0; +int32_t total_left = 0; +int32_t total_right = 0; + +void ottf_external_isr(void) { + dif_rv_plic_irq_id_t interrupt_id; + CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc, + &interrupt_id)); + + top_matcha_plic_peripheral_smc_t peripheral_id = + top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id]; + switch (peripheral_id) { + case kTopMatchaPlicPeripheralTlulMailboxSmc: { + uint32_t message; + CHECK_DIF_OK( + dif_tlul_mailbox_irq_acknowledge(&mailbox, kDifTlulMailboxIrqRtirq)); + CHECK_DIF_OK(dif_tlul_mailbox_read_message(&mailbox, &message)); + if (message == kMessageGpioPressed) { + record = true; + } else if (message == kMessageGpioReleased) { + record = false; + } else { + CHECK(false, "Unknown message"); + } + break; + } + case kTopMatchaPlicPeripheralI2s0: { + switch (interrupt_id) { + case kTopMatchaPlicIrqIdI2s0RxWatermark: + // Acknowledge and disable the interrupt. + // It will be re-enabled after the RX FIFO is drained. + CHECK_DIF_OK(dif_i2s_irq_acknowledge(&i2s, kDifI2sIrqRxWatermark)); + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqRxWatermark, + kDifToggleDisabled)); + i2s_rx_watermark_seen = true; + break; + case kTopMatchaPlicIrqIdI2s0TxWatermark: + // Acknowledge and disable the interrupt. + // It will be re-enabled after the TX FIFO is reloaded. + CHECK_DIF_OK(dif_i2s_irq_acknowledge(&i2s, kDifI2sIrqTxWatermark)); + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxWatermark, + kDifToggleDisabled)); + i2s_tx_watermark_seen = true; + break; + case kTopMatchaPlicIrqIdI2s0TxEmpty: + CHECK_DIF_OK(dif_i2s_irq_acknowledge(&i2s, kDifI2sIrqTxEmpty)); + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxEmpty, + kDifToggleDisabled)); + i2s_tx_empty_seen = true; + break; + default: + CHECK(false, "Unhandled I2S interrupt"); + } + break; + } + default: + CHECK(false, "Unhandled peripheral!"); + } + + CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc, + interrupt_id)); +} + +#define kSamples (5 * 16000) +uint32_t samples[kSamples]; + +void _ottf_main(void) { + // Initialize the SMC UART to enable logging for non-DV simulation platforms. + if (kDeviceType != kDeviceSimDV) { + init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart); + } + CHECK_DIF_OK(dif_rv_plic_init( + mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc)); + CHECK_DIF_OK(dif_tlul_mailbox_init( + mmio_region_from_addr(TOP_MATCHA_TLUL_MAILBOX_SMC_BASE_ADDR), &mailbox)); + + CHECK_DIF_OK(dif_tlul_mailbox_irq_set_enabled( + &mailbox, kDifTlulMailboxIrqRtirq, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &plic_smc, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq, + kTopMatchaPlicTargetIbex0Smc, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority( + &plic_smc, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq, + kDifRvPlicMaxPriority)); + + CHECK_DIF_OK(dif_rv_plic_irq_set_priority( + &plic_smc, kTopMatchaPlicIrqIdI2s0RxWatermark, kDifRvPlicMaxPriority)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &plic_smc, kTopMatchaPlicIrqIdI2s0RxWatermark, + kTopMatchaPlicTargetIbex0Smc, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority( + &plic_smc, kTopMatchaPlicIrqIdI2s0TxWatermark, kDifRvPlicMaxPriority)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &plic_smc, kTopMatchaPlicIrqIdI2s0TxWatermark, + kTopMatchaPlicTargetIbex0Smc, kDifToggleEnabled)); + CHECK_DIF_OK(dif_rv_plic_irq_set_priority( + &plic_smc, kTopMatchaPlicIrqIdI2s0TxEmpty, kDifRvPlicMaxPriority)); + CHECK_DIF_OK(dif_rv_plic_irq_set_enabled( + &plic_smc, kTopMatchaPlicIrqIdI2s0TxEmpty, kTopMatchaPlicTargetIbex0Smc, + kDifToggleEnabled)); + irq_global_ctrl(true); + irq_external_ctrl(true); + + memset(samples, 0xa5, sizeof(samples)); + CHECK_DIF_OK( + dif_i2s_init(mmio_region_from_addr(TOP_MATCHA_I2S0_BASE_ADDR), &i2s)); + + // Clear RX FIFO + uint32_t reg_val; + reg_val = mmio_region_read32(i2s.base_addr, I2S_FIFO_CTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, I2S_FIFO_CTRL_RXRST_BIT, 1); + reg_val = bitfield_field32_write(reg_val, I2S_FIFO_CTRL_TXILVL_FIELD, 3); + mmio_region_write32(i2s.base_addr, I2S_FIFO_CTRL_REG_OFFSET, reg_val); + + CHECK_DIF_OK(dif_i2s_irq_acknowledge_all(&i2s)); + CHECK_DIF_OK( + dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqRxWatermark, kDifToggleEnabled)); + + while (true) { + // Wait until the mailbox tells us to start + while (true) { + if (!record) { + wait_for_interrupt(); + } + if (record) { + break; + } + } + + uint32_t message_enable_led = kMessageEnableLed; + CHECK_DIF_OK(dif_tlul_mailbox_send_message(&mailbox, &message_enable_led)); + LOG_INFO("Start recording..."); + // Configure and enable recording + reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_RX_BIT, 1); + reg_val = bitfield_field32_write(reg_val, I2S_CTRL_NCO_RX_FIELD, + 24); // divide by 24 + mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val); + + // Record until our buffer is full, or the mailbox told us to stop. + int sample = 0; + while (sample < ARRAYSIZE(samples) && record) { + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqRxWatermark, + kDifToggleEnabled)); + while (!i2s_rx_watermark_seen) { + wait_for_interrupt(); + } + i2s_rx_watermark_seen = false; + + bool empty; + CHECK_DIF_OK(dif_i2s_rxfifo_empty(&i2s, &empty)); + while (!empty) { + reg_val = mmio_region_read32(i2s.base_addr, I2S_RDATA_REG_OFFSET); + // For each sample, split into left and right channel values, + // and update the moving average. + int16_t left = reg_val >> 16; + int16_t right = reg_val & 0xFFFF; + total_left -= samples_left[index_left]; + total_right -= samples_right[index_right]; + total_left += left; + samples_left[index_left] = left; + total_right += right; + samples_right[index_right] = right; + index_left = (index_left + 1) % ARRAYSIZE(samples_left); + index_right = (index_right + 1) % ARRAYSIZE(samples_right); + int16_t mean_left = total_left / ARRAYSIZE(samples_left); + int16_t mean_right = total_right / ARRAYSIZE(samples_right); + + // Subtract the moving average from each channel, and repack into the + // sample buffer. + uint32_t offset_reg_val = (((left - mean_left) & 0xFFFF) << 16) | + ((right - mean_right) & 0xFFFF); + samples[sample++] = offset_reg_val; + CHECK_DIF_OK(dif_i2s_rxfifo_empty(&i2s, &empty)); + if (sample == ARRAYSIZE(samples) || !record) { + break; + } + } + } + + // Disable recording + reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_RX_BIT, 0); + mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val); + uint32_t message_disable_led = kMessageDisableLed; + CHECK_DIF_OK(dif_tlul_mailbox_send_message(&mailbox, &message_disable_led)); + + LOG_INFO("Done recording %d samples", sample); + int samples_captured = sample; + + // Calculate the min/max of the audio, after correcting DC offsets + int32_t max = INT16_MIN; + int32_t min = INT16_MAX; + for (int i = 1; i < (samples_captured * 2); i += 2) { + int16_t* samples_s16 = (int16_t*)samples; + int32_t sample = samples_s16[i]; + if (sample < min) { + min = sample; + } + if (sample > max) { + max = sample; + } + } + + // Calculate a scaling factor, and use this to scale the waveform + // to a peak of 75% amplitude. + int32_t scale_max = ((int32_t)max * 100) / ((int32_t)INT16_MAX); + int32_t scale_min = ABS(((int32_t)min * 100) / ((int32_t)INT16_MIN)); + int32_t scale = scale_max > scale_min ? scale_max : scale_min; + for (int i = 1; i < (samples_captured * 2); i += 2) { + int16_t* samples_s16 = (int16_t*)samples; + int16_t sample = samples_s16[i]; + int16_t scaled_sample = (int16_t)(((int32_t)sample * 100) / scale); + scaled_sample = (((int32_t)scaled_sample * 75) / 100); + samples_s16[i] = scaled_sample; + } + + LOG_INFO("Playing back recorded samples..."); + // Enable TX interrupts, disable RX interrupts + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqRxWatermark, + kDifToggleDisabled)); + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxWatermark, + kDifToggleEnabled)); + CHECK_DIF_OK( + dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxEmpty, kDifToggleEnabled)); + + reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_TX_BIT, 1); + reg_val = bitfield_field32_write(reg_val, I2S_CTRL_NCO_TX_FIELD, + 48); // divide by 96 + mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val); + uint32_t samples_to_tx = sample; + uint32_t tx_audio_count = 0; + while (tx_audio_count < samples_to_tx) { + while (!i2s_tx_watermark_seen) { + wait_for_interrupt(); + } + i2s_tx_watermark_seen = false; + i2s_tx_empty_seen = false; + + // Push as many bytes into the FIFO as we can manage. If this + // goes until we write the all, so much the better. + bool full; + do { + CHECK_DIF_OK(dif_i2s_txfifo_full(&i2s, &full)); + if (full) { + break; + } + uint32_t next_tx_val = samples[tx_audio_count++]; + mmio_region_write32(i2s.base_addr, I2S_WDATA_REG_OFFSET, next_tx_val); + } while (tx_audio_count < samples_to_tx && !full); + CHECK_DIF_OK(dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxWatermark, + kDifToggleEnabled)); + CHECK_DIF_OK( + dif_i2s_irq_set_enabled(&i2s, kDifI2sIrqTxEmpty, kDifToggleEnabled)); + } + + // Wait for the FIFO to drain, we should receive the empty interrupt. + while (!i2s_tx_empty_seen) { + wait_for_interrupt(); + } + reg_val = mmio_region_read32(i2s.base_addr, I2S_CTRL_REG_OFFSET); + reg_val = bitfield_bit32_write(reg_val, I2S_CTRL_TX_BIT, 0); + mmio_region_write32(i2s.base_addr, I2S_CTRL_REG_OFFSET, reg_val); + LOG_INFO("Done with playback."); + } + __builtin_unreachable(); +}