blob: 4b212e1d7960cdf025b4b8cef69b7d16e2b936fe [file] [log] [blame]
/*
* Copyright 2023 Google LLC
* Copyright lowRISC contributors
*
* 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.
*/
// Simple SMC program that prints over UART and confirms OTTF testing
// capabilities.
#include "hw/top_matcha/ip/i2s/data/i2s_regs.h"
#include "hw/top_matcha/sw/autogen/top_matcha.h"
#include "sw/device/lib/dif/dif_i2s.h"
#include "sw/device/lib/dif/dif_rv_plic.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"
#include "sw/device/tests/testdata/i2s_440hz_audio_tx.h"
OTTF_DEFINE_TEST_CONFIG();
static dif_i2s_t i2s;
static dif_rv_plic_t plic_smc;
static dif_uart_t smc_uart;
volatile bool i2s_tx_watermark_seen = false;
volatile bool i2s_tx_empty_seen = false;
const int32_t* i2s_testdata_s32 = (const int32_t*)i2s_440hz_audio_tx;
static uint32_t tx_audio_count = 0;
uint32_t tx_next(void) {
CHECK(tx_audio_count < (i2s_440hz_audio_tx_len / sizeof(uint32_t)));
return (uint32_t)i2s_testdata_s32[tx_audio_count++];
}
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];
CHECK(peripheral_id == kTopMatchaPlicPeripheralI2s0,
"ISR interrupted peripheral is not I2S!");
switch (peripheral_id) {
case kTopMatchaPlicPeripheralI2s0: {
switch (interrupt_id) {
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:
LOG_FATAL("Interrupt is not an I2S interrupt");
}
break;
}
default:
LOG_FATAL("Peripheral is not implemented!");
}
// Complete the IRQ by writing the IRQ source to the Ibex specific CC
// register.
CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
interrupt_id));
}
void _ottf_main(void) {
uint32_t reg_val;
test_status_set(kTestStatusInTest);
LOG_INFO("Start i2s tx test");
// 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_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);
CHECK_DIF_OK(
dif_i2s_init(mmio_region_from_addr(TOP_MATCHA_I2S0_BASE_ADDR), &i2s));
// Clear TX / RX fifos
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_bit32_write(reg_val, I2S_FIFO_CTRL_TXRST_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, 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 = kDeviceType == kDeviceSimVerilator || kDeviceType == kDeviceSimDV
? 6
: (i2s_440hz_audio_tx_len / sizeof(uint32_t));
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 = tx_next();
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);
// Note: passing a test in the SMC ends the simulation, even if the secure
// core is in the middle of a test.
test_status_set(kTestStatusPassed);
}