blob: fcb16db59fb1ada6d4e8cca306990afe4d9f422f [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.
*/
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include "hw/top_matcha/sw/autogen/top_matcha.h"
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_pinmux.h"
#include "sw/device/lib/dif/dif_rv_plic.h"
#include "sw/device/lib/dif/dif_spi_device.h"
#include "sw/device/lib/dif/dif_spi_host.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/testing/pinmux_testutils.h"
#include "sw/device/lib/testing/spi_device_testutils.h"
#include "sw/device/lib/testing/spi_flash_testutils.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
OTTF_DEFINE_TEST_CONFIG();
// Bit map of command slots to be filtered. This is supplied by the DV
// environment.
const volatile uint32_t kFilteredCommands;
// Whether to upload write commands and have software relay them.
const volatile uint8_t kUploadWriteCommands;
static dif_pinmux_t pinmux;
static dif_rv_plic_t rv_plic;
static dif_spi_device_handle_t spi_device;
static dif_spi_host_t spi_host0;
static dif_spi_host_t spi_host1;
// Enable pull-ups for spi_host data pins to avoid floating inputs.
static const pinmux_pad_attributes_t pinmux_pad_config[] = {
{
.pad = kTopMatchaMuxedPadsIob1,
.kind = kDifPinmuxPadKindMio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopMatchaMuxedPadsIob3,
.kind = kDifPinmuxPadKindMio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopMatchaDirectPadsSpiHost0Sd0,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopMatchaDirectPadsSpiHost0Sd1,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopMatchaDirectPadsSpiHost0Sd2,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopMatchaDirectPadsSpiHost0Sd3,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
};
/**
* A convenience struct to associate a mux selection that connects a pad and
* peripheral. This can be used for an input mux or an output mux.
*/
typedef struct pinmux_select {
dif_pinmux_index_t pad;
dif_pinmux_index_t peripheral;
} pinmux_select_t;
static const pinmux_select_t pinmux_out_config[] = {
{
.pad = kTopMatchaPinmuxMioOutIob0,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Csb,
},
{
.pad = kTopMatchaPinmuxMioOutIob2,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Sck,
},
{
.pad = kTopMatchaPinmuxMioOutIob1,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0,
},
{
.pad = kTopMatchaPinmuxMioOutIob3,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1,
},
// These peripheral I/Os are not assigned for tests.
// {
// .pad = ???,
// .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2,
// },
// {
// .pad = ???,
// .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3,
// },
};
static const pinmux_select_t pinmux_in_config[] = {
{
.pad = kTopMatchaPinmuxInselIob1,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Sd0,
},
{
.pad = kTopMatchaPinmuxInselIob3,
.peripheral = kTopMatchaPinmuxOutselSpiHost1Sd1,
},
// These peripheral I/Os are not assigned for tests.
// {
// .pad = ???,
// .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd2,
// },
// {
// .pad = ???,
// .peripheral = kTopMatchaPinmuxOutselSpiHost1Sd3,
// },
};
/**
* Initialize the provided SPI host. For the most part, the values provided are
* filler, as spi_host0 will be in passthrough mode and spi_host1 will remain
* unused throughout the test.
*/
void init_spi_host(dif_spi_host_t *spi_host,
uint32_t peripheral_clock_freq_hz) {
dif_spi_host_config_t config = {
.spi_clock = peripheral_clock_freq_hz / 2,
.peripheral_clock_freq_hz = peripheral_clock_freq_hz,
.chip_select =
{
.idle = 2,
.trail = 2,
.lead = 2,
},
};
CHECK_DIF_OK(dif_spi_host_configure(spi_host, config));
CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true));
}
/**
* Handle an incoming Write Status command.
*
* Modifies the internal status register and relays the command out to the
* downstream SPI flash.
*
* @param status The aggregated value of all three flash status registers prior
* to this command's execution.
* @param offset The bit offset for the byte to be modified by the payload.
* @param opcode The opcode corresponding to the incoming command.
*/
void handle_write_status(uint32_t status, uint8_t offset, uint8_t opcode) {
uint8_t payload;
uint16_t occupancy;
uint32_t start_offset;
CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy(
&spi_device, &occupancy, &start_offset));
CHECK(occupancy == 1);
CHECK_DIF_OK(dif_spi_device_read_flash_buffer(
&spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset, occupancy,
&payload));
status &= (0xffu << offset);
status |= (payload << offset);
CHECK_DIF_OK(dif_spi_device_set_flash_status_registers(&spi_device, status));
spi_flash_testutils_issue_write_enable(&spi_host0);
dif_spi_host_segment_t transaction[] = {
{
.type = kDifSpiHostSegmentTypeOpcode,
.opcode = opcode,
},
{
.type = kDifSpiHostSegmentTypeTx,
.tx =
{
.width = kDifSpiHostWidthStandard,
.buf = &payload,
.length = 1,
},
},
};
CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction,
ARRAYSIZE(transaction)));
spi_flash_testutils_wait_until_not_busy(&spi_host0);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
/**
* Handle an incoming Chip Erase command.
*
* Relays the command out to the downstream SPI flash.
*/
void handle_chip_erase(void) {
spi_flash_testutils_erase_chip(&spi_host0);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
/**
* Handle an incoming Sector Erase command.
*
* Relays the command out to the downstream SPI flash.
*/
void handle_sector_erase(void) {
uint8_t occupancy;
CHECK_DIF_OK(
dif_spi_device_get_flash_address_fifo_occupancy(&spi_device, &occupancy));
CHECK(occupancy == 1);
uint32_t address;
CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
dif_toggle_t addr4b_enabled;
CHECK_DIF_OK(
dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
spi_flash_testutils_erase_sector(&spi_host0, address, addr_is_4b);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
/**
* Handle an incoming Page Program command.
*
* Relays the command out to the downstream SPI flash.
*/
void handle_page_program(void) {
uint8_t address_occupancy;
CHECK_DIF_OK(dif_spi_device_get_flash_address_fifo_occupancy(
&spi_device, &address_occupancy));
CHECK(address_occupancy == 1);
uint32_t address;
CHECK_DIF_OK(dif_spi_device_pop_flash_address_fifo(&spi_device, &address));
uint8_t payload[256];
uint16_t payload_occupancy;
uint32_t start_offset;
CHECK_DIF_OK(dif_spi_device_get_flash_payload_fifo_occupancy(
&spi_device, &payload_occupancy, &start_offset));
CHECK(start_offset == 0);
CHECK(payload_occupancy <= sizeof(payload));
CHECK_DIF_OK(dif_spi_device_read_flash_buffer(
&spi_device, kDifSpiDeviceFlashBufferTypePayload, start_offset,
payload_occupancy, payload));
dif_toggle_t addr4b_enabled;
CHECK_DIF_OK(
dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled));
bool addr_is_4b = dif_toggle_to_bool(addr4b_enabled);
spi_flash_testutils_program_page(&spi_host0, payload, payload_occupancy,
address, addr_is_4b);
CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device));
}
// This function assumes only one command will ever be uploaded to the FIFO at a
// time. The IP does not currently make any such guarantee.
void spi_device_process_upload_fifo(void) {
dif_irq_type_t irq_type;
CHECK_DIF_OK(dif_spi_device_irq_get_type(
&spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &irq_type));
if (irq_type == kDifIrqTypeEvent) {
CHECK_DIF_OK(dif_spi_device_irq_acknowledge(
&spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty));
}
// Uploaded commands should all set the busy bit, and WREN should have been
// submitted before the uploaded command.
uint32_t status;
CHECK_DIF_OK(dif_spi_device_get_flash_status_registers(&spi_device, &status));
CHECK(status & kSpiFlashStatusBitWip);
CHECK(status & kSpiFlashStatusBitWel);
uint8_t command;
CHECK_DIF_OK(dif_spi_device_pop_flash_command_fifo(&spi_device, &command));
// Check command against the ones we expect.
// Call command-specific handlers, probably, which validate the commands. Then
// execute.
if (command == kSpiDeviceFlashOpWriteStatus1) {
handle_write_status(status, /*offset=*/0, command);
} else if (command == kSpiDeviceFlashOpWriteStatus2) {
handle_write_status(status, /*offset=*/8, command);
} else if (command == kSpiDeviceFlashOpWriteStatus3) {
handle_write_status(status, /*offset=*/16, command);
} else if (command == kSpiDeviceFlashOpChipErase) {
handle_chip_erase();
} else if (command == kSpiDeviceFlashOpSectorErase) {
handle_sector_erase();
} else if (command == kSpiDeviceFlashOpPageProgram) {
handle_page_program();
} else {
CHECK(false, "Received unexpected command 0x%x", command);
}
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
kDifToggleEnabled));
}
/**
* Check that the command FIFO is not empty, and mask the interrupt for deferred
* processing.
*
* Runs in interrupt context.
*/
void spi_device_isr(void) {
bool cmdfifo_not_empty;
CHECK_DIF_OK(dif_spi_device_irq_is_pending(
&spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
&cmdfifo_not_empty));
CHECK(cmdfifo_not_empty);
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(
&spi_device.dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
kDifToggleDisabled));
}
/**
* Handle SPI device interrupts.
*
* Runs in interrupt context.
*/
void ottf_external_isr(void) {
const uint32_t kPlicTarget = kTopMatchaPlicTargetIbex0;
dif_rv_plic_irq_id_t plic_irq_id;
CHECK_DIF_OK(dif_rv_plic_irq_claim(&rv_plic, kPlicTarget, &plic_irq_id));
top_matcha_plic_peripheral_t peripheral = (top_matcha_plic_peripheral_t)
top_matcha_plic_interrupt_for_peripheral[plic_irq_id];
switch (peripheral) {
case kTopMatchaPlicPeripheralSpiDevice:
// Only the UploadCmdfifoNotEmpty interrupt is expected.
CHECK(plic_irq_id == kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty);
spi_device_isr();
break;
default:
break;
}
// Complete the IRQ at PLIC.
CHECK_DIF_OK(dif_rv_plic_irq_complete(&rv_plic, kPlicTarget, plic_irq_id));
}
bool test_main(void) {
// Initialize the pinmux.
CHECK_DIF_OK(dif_pinmux_init(
mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR), &pinmux));
pinmux_testutils_init(&pinmux);
pinmux_testutils_configure_pads(&pinmux, pinmux_pad_config,
ARRAYSIZE(pinmux_pad_config));
for (int i = 0; i < ARRAYSIZE(pinmux_in_config); ++i) {
pinmux_select_t setting = pinmux_in_config[i];
CHECK_DIF_OK(
dif_pinmux_input_select(&pinmux, setting.peripheral, setting.pad));
}
for (int i = 0; i < ARRAYSIZE(pinmux_out_config); ++i) {
pinmux_select_t setting = pinmux_out_config[i];
CHECK_DIF_OK(
dif_pinmux_output_select(&pinmux, setting.pad, setting.peripheral));
}
// Initialize the PLIC.
CHECK_DIF_OK(dif_rv_plic_init(
mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic));
// Initialize the spi_host devices.
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0));
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host1));
init_spi_host(&spi_host0, (uint32_t)kClockFreqPeripheralHz * 4);
init_spi_host(&spi_host1, (uint32_t)kClockFreqPeripheralHz);
// Initialize spi_device.
mmio_region_t spi_device_base_addr =
mmio_region_from_addr(TOP_MATCHA_SPI_DEVICE_BASE_ADDR);
CHECK_DIF_OK(dif_spi_device_init_handle(spi_device_base_addr, &spi_device));
bool upload_write_commands = (kUploadWriteCommands != 0);
spi_device_testutils_configure_passthrough(&spi_device, kFilteredCommands,
upload_write_commands);
// Enable all spi_device and spi_host interrupts, and check that they do not
// trigger unless command upload is enabled.
dif_spi_device_irq_t all_spi_device_irqs[] = {
kDifSpiDeviceIrqGenericRxFull, kDifSpiDeviceIrqGenericRxWatermark,
kDifSpiDeviceIrqGenericTxWatermark, kDifSpiDeviceIrqGenericRxError,
kDifSpiDeviceIrqGenericRxOverflow, kDifSpiDeviceIrqGenericTxUnderflow,
kDifSpiDeviceIrqUploadCmdfifoNotEmpty, kDifSpiDeviceIrqReadbufWatermark,
kDifSpiDeviceIrqReadbufFlip, kDifSpiDeviceIrqTpmHeaderNotEmpty,
};
for (int i = 0; i < ARRAYSIZE(all_spi_device_irqs); ++i) {
dif_spi_device_irq_t irq = all_spi_device_irqs[i];
CHECK_DIF_OK(dif_spi_device_irq_set_enabled(&spi_device.dev, irq,
kDifToggleEnabled));
}
CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqError,
kDifToggleEnabled));
CHECK_DIF_OK(dif_spi_host_irq_set_enabled(&spi_host0, kDifSpiHostIrqSpiEvent,
kDifToggleEnabled));
dif_rv_plic_irq_id_t spi_irqs[] = {
kTopMatchaPlicIrqIdSpiDeviceGenericRxFull,
kTopMatchaPlicIrqIdSpiDeviceGenericRxWatermark,
kTopMatchaPlicIrqIdSpiDeviceGenericTxWatermark,
kTopMatchaPlicIrqIdSpiDeviceGenericRxError,
kTopMatchaPlicIrqIdSpiDeviceGenericRxOverflow,
kTopMatchaPlicIrqIdSpiDeviceGenericTxUnderflow,
kTopMatchaPlicIrqIdSpiDeviceUploadCmdfifoNotEmpty,
kTopMatchaPlicIrqIdSpiDeviceReadbufWatermark,
kTopMatchaPlicIrqIdSpiDeviceReadbufFlip,
kTopMatchaPlicIrqIdSpiHost0Error,
kTopMatchaPlicIrqIdSpiHost0SpiEvent,
};
for (int i = 0; i < ARRAYSIZE(spi_irqs); ++i) {
dif_rv_plic_irq_id_t irq = spi_irqs[i];
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
&rv_plic, irq, kTopMatchaPlicTargetIbex0, kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(&rv_plic, irq, 0x1));
}
irq_external_ctrl(/*en=*/true);
// Send the DV environment a specific message for synchronization. The
// sequencer can pick this up and know it can begin sending SPI flash
// transactions.
LOG_INFO("Test setup complete.");
while (true) {
uint8_t spi_device_cmdfifo_occupancy;
irq_global_ctrl(/*en=*/false);
CHECK_DIF_OK(dif_spi_device_get_flash_command_fifo_occupancy(
&spi_device, &spi_device_cmdfifo_occupancy));
if (spi_device_cmdfifo_occupancy == 0) {
wait_for_interrupt();
}
irq_global_ctrl(/*en=*/true);
spi_device_process_upload_fifo();
}
return true;
}