blob: c5c66c3f48930b435d374fe0924cc2c922c220d7 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <assert.h>
#include <stdbool.h>
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_pinmux.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/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
OTTF_DEFINE_TEST_CONFIG();
static dif_pinmux_t pinmux;
static dif_spi_device_handle_t spi_device;
static dif_spi_host_t spi_host0;
static dif_spi_host_t spi_host1;
typedef struct pinmux_pad_attributes {
dif_pinmux_index_t pad;
dif_pinmux_pad_kind_t kind;
dif_pinmux_pad_attr_flags_t flags;
} pinmux_pad_attributes_t;
// Enable pull-ups for spi_host data pins to avoid floating inputs.
static const pinmux_pad_attributes_t pinmux_pad_config[] = {
{
.pad = kTopEarlgreyMuxedPadsIob1,
.kind = kDifPinmuxPadKindMio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopEarlgreyMuxedPadsIob3,
.kind = kDifPinmuxPadKindMio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopEarlgreyDirectPadsSpiHost0Sd0,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopEarlgreyDirectPadsSpiHost0Sd1,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopEarlgreyDirectPadsSpiHost0Sd2,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
{
.pad = kTopEarlgreyDirectPadsSpiHost0Sd3,
.kind = kDifPinmuxPadKindDio,
.flags = kDifPinmuxPadAttrPullResistorEnable |
kDifPinmuxPadAttrPullResistorUp,
},
};
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 = kTopEarlgreyPinmuxMioOutIob0,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Csb,
},
{
.pad = kTopEarlgreyPinmuxMioOutIob2,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sck,
},
{
.pad = kTopEarlgreyPinmuxMioOutIob1,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd0,
},
{
.pad = kTopEarlgreyPinmuxMioOutIob3,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd1,
},
// These peripheral I/Os are not assigned for tests.
// {
// .pad = ???,
// .peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd2,
// },
// {
// .pad = ???,
// .peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd3,
// },
};
static const pinmux_select_t pinmux_in_config[] = {
{
.pad = kTopEarlgreyPinmuxInselIob1,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd0,
},
{
.pad = kTopEarlgreyPinmuxInselIob3,
.peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd1,
},
// These peripheral I/Os are not assigned for tests.
// {
// .pad = ???,
// .peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd2,
// },
// {
// .pad = ???,
// .peripheral = kTopEarlgreyPinmuxOutselSpiHost1Sd3,
// },
};
/**
* A set of typical opcodes for the named flash commands.
*/
enum spi_flash_opcode {
kSpiFlashOpReadJedec = 0x9f,
kSpiFlashOpReadSfdp = 0x5a,
kSpiFlashOpReadNormal = 0x03,
kSpiFlashOpReadFast = 0x0b,
kSpiFlashOpReadDual = 0x3b,
kSpiFlashOpReadQuad = 0x6b,
kSpiFlashOpWriteEnable = 0x06,
kSpiFlashOpWriteDisable = 0x04,
kSpiFlashOpReadStatus1 = 0x05,
kSpiFlashOpReadStatus2 = 0x35,
kSpiFlashOpReadStatus3 = 0x15,
kSpiFlashOpWriteStatus1 = 0x01,
kSpiFlashOpWriteStatus2 = 0x31,
kSpiFlashOpWriteStatus3 = 0x11,
kSpiFlashOpChipErase = 0xc7,
kSpiFlashOpSectorErase = 0x20,
kSpiFlashOpPageProgram = 0x02,
kSpiFlashOpEnter4bAddr = 0xb7,
kSpiFlashOpExit4bAddr = 0xe9,
};
enum spi_device_command_slot {
kSpiDeviceReadCommandSlotBase = 0,
kSpiDeviceWriteCommandSlotBase = 11,
};
/**
* 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));
}
/**
* Initialize the SPI device to be in passthrough mode and allow the following
* commands to pass through:
* - ReadJedec
* - ReadSfdp
* - ReadNormal
* - ReadFast
* - ReadDual
* - ReadQuad
* - WriteEnable
* - WriteDisable
* - ReadStatus1
* - ReadStatus2
* - ReadStatus3
* - WriteStatus1
* - WriteStatus2
* - WriteStatus3
* - ChipErase
* - SectorErase
* - PageProgram
* - Enter4bAddr
* - Exit4bAddr
*
*/
void init_spi_device(void) {
dif_spi_device_config_t spi_device_config = {
.clock_polarity = kDifSpiDeviceEdgePositive,
.data_phase = kDifSpiDeviceEdgeNegative,
.tx_order = kDifSpiDeviceBitOrderMsbToLsb,
.rx_order = kDifSpiDeviceBitOrderMsbToLsb,
.device_mode = kDifSpiDeviceModePassthrough,
};
CHECK_DIF_OK(dif_spi_device_configure(&spi_device, spi_device_config));
dif_spi_device_passthrough_intercept_config_t intercept_config = {
.status = false,
.jedec_id = false,
.sfdp = false,
.mailbox = false,
};
CHECK_DIF_OK(dif_spi_device_set_passthrough_intercept_config(
&spi_device, intercept_config));
// Set up passthrough filter to allow all commands.
CHECK_DIF_OK(dif_spi_device_set_all_passthrough_command_filters(
&spi_device, kDifToggleDisabled));
dif_spi_device_flash_command_t read_commands[] = {
{
// Slot 0: ReadStatus1
.opcode = kSpiFlashOpReadStatus1,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.dummy_cycles = 0,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 1: ReadStatus2
.opcode = kSpiFlashOpReadStatus2,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.dummy_cycles = 0,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 2: ReadStatus3
.opcode = kSpiFlashOpReadStatus3,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.dummy_cycles = 0,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 3: ReadJedecID
.opcode = kSpiFlashOpReadJedec,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.dummy_cycles = 0,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 4: ReadSfdp
.opcode = kSpiFlashOpReadSfdp,
.address_type = kDifSpiDeviceFlashAddr3Byte,
.dummy_cycles = 8,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 5: ReadNormal
.opcode = kSpiFlashOpReadNormal,
.address_type = kDifSpiDeviceFlashAddrCfg,
.passthrough_swap_address = true,
.dummy_cycles = 0,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 6: ReadFast
.opcode = kSpiFlashOpReadFast,
.address_type = kDifSpiDeviceFlashAddrCfg,
.passthrough_swap_address = true,
.dummy_cycles = 8,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = true,
},
{
// Slot 7: ReadDual
.opcode = kSpiFlashOpReadDual,
.address_type = kDifSpiDeviceFlashAddrCfg,
.passthrough_swap_address = true,
.dummy_cycles = 8,
.payload_io_type = kDifSpiDevicePayloadIoDual,
.payload_dir_to_host = true,
},
{
// Slot 8: ReadQuad
.opcode = kSpiFlashOpReadQuad,
.address_type = kDifSpiDeviceFlashAddrCfg,
.passthrough_swap_address = true,
.dummy_cycles = 8,
.payload_io_type = kDifSpiDevicePayloadIoQuad,
.payload_dir_to_host = true,
},
};
for (int i = 0; i < ARRAYSIZE(read_commands); ++i) {
uint8_t slot = i + kSpiDeviceReadCommandSlotBase;
CHECK_DIF_OK(dif_spi_device_set_flash_command_slot(
&spi_device, slot, kDifToggleEnabled, read_commands[i]));
}
dif_spi_device_flash_command_t write_commands[] = {
{
// Slot 11: WriteStatus1
.opcode = kSpiFlashOpWriteStatus1,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = false,
},
{
// Slot 12: WriteStatus2
.opcode = kSpiFlashOpWriteStatus2,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = false,
},
{
// Slot 13: WriteStatus3
.opcode = kSpiFlashOpWriteStatus3,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = false,
},
{
// Slot 14: ChipErase
.opcode = kSpiFlashOpChipErase,
.address_type = kDifSpiDeviceFlashAddrDisabled,
.payload_io_type = kDifSpiDevicePayloadIoNone,
},
{
// Slot 15: SectorErase
.opcode = kSpiFlashOpSectorErase,
.address_type = kDifSpiDeviceFlashAddrCfg,
.payload_io_type = kDifSpiDevicePayloadIoNone,
},
{
// Slot 16: PageProgram
.opcode = kSpiFlashOpPageProgram,
.address_type = kDifSpiDeviceFlashAddrCfg,
.payload_io_type = kDifSpiDevicePayloadIoSingle,
.payload_dir_to_host = false,
},
};
for (int i = 0; i < ARRAYSIZE(write_commands); ++i) {
uint8_t slot = i + kSpiDeviceWriteCommandSlotBase;
CHECK_DIF_OK(dif_spi_device_set_flash_command_slot(
&spi_device, slot, kDifToggleEnabled, write_commands[i]));
}
CHECK_DIF_OK(dif_spi_device_configure_flash_wren_command(
&spi_device, kDifToggleEnabled, kSpiFlashOpWriteEnable));
CHECK_DIF_OK(dif_spi_device_configure_flash_wrdi_command(
&spi_device, kDifToggleEnabled, kSpiFlashOpWriteDisable));
CHECK_DIF_OK(dif_spi_device_configure_flash_en4b_command(
&spi_device, kDifToggleEnabled, kSpiFlashOpEnter4bAddr));
CHECK_DIF_OK(dif_spi_device_configure_flash_ex4b_command(
&spi_device, kDifToggleEnabled, kSpiFlashOpExit4bAddr));
}
bool test_main(void) {
// Initialize the pinmux.
CHECK_DIF_OK(dif_pinmux_init(
mmio_region_from_addr(TOP_EARLGREY_PINMUX_AON_BASE_ADDR), &pinmux));
pinmux_testutils_init(&pinmux);
for (int i = 0; i < ARRAYSIZE(pinmux_pad_config); ++i) {
dif_pinmux_pad_attr_t attr, attr_check;
pinmux_pad_attributes_t config = pinmux_pad_config[i];
CHECK_DIF_OK(
dif_pinmux_pad_get_attrs(&pinmux, config.pad, config.kind, &attr));
attr.flags = config.flags;
CHECK_DIF_OK(dif_pinmux_pad_write_attrs(&pinmux, config.pad, config.kind,
attr, &attr_check));
// Check that attributes were accepted?
}
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 spi_host devices.
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_EARLGREY_SPI_HOST0_BASE_ADDR), &spi_host0));
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_EARLGREY_SPI_HOST0_BASE_ADDR), &spi_host1));
init_spi_host(&spi_host0, (uint32_t)kClockFreqHiSpeedPeripheralHz);
init_spi_host(&spi_host1, (uint32_t)kClockFreqPeripheralHz);
// Initialize spi_device.
mmio_region_t spi_device_base_addr =
mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR);
CHECK_DIF_OK(dif_spi_device_init_handle(spi_device_base_addr, &spi_device));
init_spi_device();
// 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.");
// Enable all spi_device and spi_host interrupts, and check that they do not
// trigger.
dif_spi_device_irq_t all_spi_device_irqs[] = {
kDifSpiDeviceIrqGenericRxFull,
kDifSpiDeviceIrqGenericRxWatermark,
kDifSpiDeviceIrqGenericTxWatermark,
kDifSpiDeviceIrqGenericRxError,
kDifSpiDeviceIrqGenericRxOverflow,
kDifSpiDeviceIrqGenericTxUnderflow,
kDifSpiDeviceIrqUploadCmdfifoNotEmpty,
kDifSpiDeviceIrqUploadPayloadNotEmpty,
kDifSpiDeviceIrqUploadPayloadOverflow,
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));
irq_external_ctrl(/*en=*/true);
wait_for_interrupt();
// No interrupts are expected, so fail if one occurred. Note that the DV
// environment is the only one that can signal that the test has passed.
LOG_INFO("Received unexpected interrupt, ending wfi");
return false;
}