| /* |
| * Copyright 2023 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Barebones i2s for soundstream. Only supports recording and |
| * without any buffering. The caller must manage the rx fifo |
| * carefully to avoid data loss. |
| */ |
| #include "i2s.h" |
| |
| #include <fail-simulator-on-error.h> |
| #include <futex.h> |
| #include <interrupt.h> |
| #include <locks.h> |
| #include <thread.h> |
| |
| #include <debug.hh> |
| #include <platform/sencha/platform-i2s.hh> |
| |
| #include "hw/top_matcha/sw/autogen/top_matcha.h" |
| #include "i2s_regs.h" |
| |
| /// Expose debugging features unconditionally for this compartment. |
| using Debug = ConditionalDebug<false, "I2S">; |
| |
| #include "compat.h" |
| |
| typedef uint32_t i2s_mmio_t[TOP_MATCHA_I2S0_SIZE_BYTES / sizeof(uint32_t)]; |
| |
| static dif_i2s_t i2s; |
| static void i2s_irq_acknowledge(i2s_irq_t irq_id); |
| |
| #define CHECK_INIT() CHECK(i2s.base_addr.base != 0) |
| |
| static CountingSemaphoreState startup = { 0, 1 }; |
| |
| void i2s_init(void) { |
| CHECK_DIF_OK(dif_i2s_init( |
| mmio_region_from_addr((uintptr_t)MMIO_CAPABILITY(i2s_mmio_t, i2s)), |
| &i2s)); |
| semaphore_put(&startup); |
| } |
| |
| // NB: could be atomic but not needed for our usage. |
| volatile bool rx_watermark_seen = false; |
| |
| void i2s_isr(void) { |
| Timeout t = {0, UnlimitedTimeout}; |
| semaphore_get(&t, &startup); |
| Debug::log("i2s_isr: i2s {} (Thread {})", i2s.base_addr.base, thread_id_get()); |
| |
| const uint32_t *rxWatermarkFutex = interrupt_futex_get( |
| STATIC_SEALED_VALUE(i2sRxWatermarkInterruptCapability)); |
| uint32_t last = *rxWatermarkFutex; |
| for (;;) { |
| Debug::Assert(futex_wait(rxWatermarkFutex, last) == 0, "futex_wait"); |
| last = *rxWatermarkFutex; |
| Debug::log("i2s_isr: i2s {} last {})", i2s.base_addr.base, last); |
| rx_watermark_seen = true; |
| // Acknowledge and disable the interrupt; it will be |
| // re-enabled after the RX FIFO is drained. |
| i2s_irq_acknowledge(kI2sIrqRxWatermark); |
| i2s_irq_set_enabled(kI2sIrqRxWatermark, /*enabled=*/false); |
| CHECK(interrupt_complete( |
| STATIC_SEALED_VALUE(i2sRxWatermarkInterruptCapability)) == 0); |
| } |
| } |
| bool i2s_rx_watermark_seen(void) { |
| // return atomic_swap(&rx_watermark_seen, false); |
| bool was_seen = rx_watermark_seen; |
| if (was_seen) { |
| rx_watermark_seen = false; |
| } |
| return was_seen; |
| } |
| |
| void i2s_wait_for_rx_watermark(void) { |
| const uint32_t *rxWatermarkFutex = interrupt_futex_get( |
| STATIC_SEALED_VALUE(i2sRxWatermarkInterruptCapability)); |
| uint32_t last = *rxWatermarkFutex; |
| while (!i2s_rx_watermark_seen()) { |
| Debug::Assert(futex_wait(rxWatermarkFutex, last) == 0, "futex_wait"); |
| last = *rxWatermarkFutex; |
| } |
| } |
| |
| static void i2s_irq_acknowledge(i2s_irq_t irq_id) { |
| CHECK_INIT(); |
| CHECK_DIF_OK(dif_i2s_irq_acknowledge(&i2s, irq_id)); |
| } |
| |
| void i2s_irq_acknowledge_all() { |
| CHECK_INIT(); |
| CHECK_DIF_OK(dif_i2s_irq_acknowledge_all(&i2s)); |
| } |
| |
| void i2s_irq_set_enabled(i2s_irq_t irq_id, bool enabled) { |
| CHECK_INIT(); |
| CHECK_DIF_OK(dif_i2s_irq_set_enabled( |
| &i2s, irq_id, enabled ? kDifToggleEnabled : kDifToggleDisabled)); |
| } |
| |
| // Clear RX FIFO |
| void i2s_rxfifo_clear(void) { |
| CHECK_INIT(); |
| 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); |
| mmio_region_write32(i2s.base_addr, I2S_FIFO_CTRL_REG_OFFSET, reg_val); |
| } |
| |
| bool i2s_rxfifo_is_empty(void) { |
| CHECK_INIT(); |
| bool empty; |
| CHECK_DIF_OK(dif_i2s_rxfifo_empty(&i2s, &empty)); |
| return empty; |
| } |
| |
| // Configure and enable recording |
| void i2s_record_begin(void) { |
| CHECK_INIT(); |
| uint32_t 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); |
| } |
| |
| uint32_t i2s_get_rdata(void) { |
| CHECK_INIT(); |
| return mmio_region_read32(i2s.base_addr, I2S_RDATA_REG_OFFSET); |
| } |
| |
| // Disable recording |
| void i2s_record_end(void) { |
| CHECK_INIT(); |
| uint32_t 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); |
| } |