blob: e20358f791f6afe2663748abf27339f0fbcaf8d1 [file] [log] [blame]
/*
* 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);
}