blob: 305ee0a818aacec13b8b66164c48843f8651c31a [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 "sw/device/lib/testing/json/spi_passthru.h"
#include <stdbool.h>
#include <stdint.h>
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/base/status.h"
#include "sw/device/lib/dif/dif_spi_device.h"
#include "sw/device/lib/dif/dif_spi_host.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/testing/json/command.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_flow_control.h"
#include "sw/device/lib/testing/test_framework/ottf_main.h"
#include "sw/device/lib/testing/test_framework/ujson_ottf.h"
#include "sw/device/lib/ujson/ujson.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
OTTF_DEFINE_TEST_CONFIG(.enable_uart_flow_control = true);
static dif_spi_device_handle_t spid;
static dif_spi_host_t spih;
static status_t configure_jedec_id(ujson_t *uj, dif_spi_device_handle_t *spid) {
config_jedec_id_t config;
TRY(ujson_deserialize_config_jedec_id_t(uj, &config));
dif_spi_device_flash_id_t id = {
.device_id = config.device_id,
.manufacturer_id = config.manufacturer_id,
.continuation_code = config.continuation_code,
.num_continuation_code = config.continuation_len,
};
TRY(dif_spi_device_set_flash_id(spid, id));
return RESP_OK_STATUS(uj);
}
static status_t write_status_register(ujson_t *uj,
dif_spi_device_handle_t *spid) {
status_register_t sr;
TRY(ujson_deserialize_status_register_t(uj, &sr));
TRY(dif_spi_device_set_flash_status_registers(spid, sr.status));
return RESP_OK_STATUS(uj);
}
static status_t read_status_register(ujson_t *uj,
dif_spi_device_handle_t *spid) {
status_register_t sr;
dif_toggle_t addr_4b;
TRY(dif_spi_device_get_flash_status_registers(spid, &sr.status));
TRY(dif_spi_device_get_4b_address_mode(spid, &addr_4b));
sr.addr_4b = addr_4b;
RESP_OK(ujson_serialize_status_register_t, uj, &sr);
return OK_STATUS();
}
static status_t write_sfdp_data(ujson_t *uj, dif_spi_device_handle_t *spid) {
sfdp_data_t sfdp;
TRY(ujson_deserialize_sfdp_data_t(uj, &sfdp));
TRY(dif_spi_device_write_flash_buffer(spid, kDifSpiDeviceFlashBufferTypeSfdp,
0, sizeof(sfdp.data), sfdp.data));
return RESP_OK_STATUS(uj);
}
static status_t wait_for_upload(ujson_t *uj, dif_spi_device_handle_t *spid) {
// Wait for a SPI transaction cause an upload.
bool upload_pending;
do {
// The UploadCmdfifoNotEmpty interrupt status is updated after the SPI
// transaction completes.
TRY(dif_spi_device_irq_is_pending(
&spid->dev, kDifSpiDeviceIrqUploadCmdfifoNotEmpty, &upload_pending));
} while (!upload_pending);
upload_info_t info = {0};
uint8_t occupancy;
// Get the SPI opcode.
TRY(dif_spi_device_get_flash_command_fifo_occupancy(spid, &occupancy));
if (occupancy != 1) {
// Cannot have an uploaded command without an opcode.
return INTERNAL();
}
TRY(dif_spi_device_pop_flash_command_fifo(spid, &info.opcode));
// Get the flash_status register.
TRY(dif_spi_device_get_flash_status_registers(spid, &info.flash_status));
// Get the SPI address (if available).
TRY(dif_spi_device_get_flash_address_fifo_occupancy(spid, &occupancy));
if (occupancy) {
dif_toggle_t addr_4b;
TRY(dif_spi_device_get_4b_address_mode(spid, &addr_4b));
info.addr_4b = addr_4b;
TRY(dif_spi_device_pop_flash_address_fifo(spid, &info.address));
info.has_address = true;
}
// Get the SPI data payload (if available).
uint32_t start;
TRY(dif_spi_device_get_flash_payload_fifo_occupancy(spid, &info.data_len,
&start));
if (info.data_len) {
if (info.data_len > sizeof(info.data)) {
// We aren't expecting more than 256 bytes of data.
return INVALID_ARGUMENT();
}
TRY(dif_spi_device_read_flash_buffer(spid,
kDifSpiDeviceFlashBufferTypePayload,
start, info.data_len, info.data));
}
// Finished: ack the IRQ and clear the busy bit (and all other bits)
// in flash_status.
TRY(dif_spi_device_irq_acknowledge(&spid->dev,
kDifSpiDeviceIrqUploadCmdfifoNotEmpty));
TRY(dif_spi_device_set_flash_status_registers(spid, 0));
RESP_OK(ujson_serialize_upload_info_t, uj, &info);
return OK_STATUS();
}
status_t spi_flash_read_id(ujson_t *uj, dif_spi_host_t *spih,
dif_spi_device_handle_t *spid) {
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleDisabled));
spi_flash_testutils_jedec_id_t jedec_id;
spi_flash_testutils_read_id(spih, &jedec_id);
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleEnabled));
spi_flash_read_id_t uj_id = {
.device_id = jedec_id.device_id,
.manufacturer_id = jedec_id.manufacturer_id,
.continuation_len = jedec_id.continuation_len,
};
return RESP_OK(ujson_serialize_spi_flash_read_id_t, uj, &uj_id);
}
status_t spi_flash_read_sfdp(ujson_t *uj, dif_spi_host_t *spih,
dif_spi_device_handle_t *spid) {
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleDisabled));
spi_flash_read_sfdp_t op;
TRY(ujson_deserialize_spi_flash_read_sfdp_t(uj, &op));
sfdp_data_t sfdp;
CHECK(op.length <= sizeof(sfdp.data));
spi_flash_testutils_read_sfdp(spih, op.address, sfdp.data, op.length);
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleEnabled));
return RESP_OK(ujson_serialize_sfdp_data_t, uj, &sfdp);
}
status_t spi_flash_erase_sector(ujson_t *uj, dif_spi_host_t *spih,
dif_spi_device_handle_t *spid) {
spi_flash_erase_sector_t op;
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleDisabled));
TRY(ujson_deserialize_spi_flash_erase_sector_t(uj, &op));
spi_flash_testutils_erase_sector(spih, op.address, op.addr4b);
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleEnabled));
return RESP_OK_STATUS(uj);
}
status_t spi_flash_write(ujson_t *uj, dif_spi_host_t *spih,
dif_spi_device_handle_t *spid) {
spi_flash_write_t op;
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleDisabled));
TRY(ujson_deserialize_spi_flash_write_t(uj, &op));
if (op.length > sizeof(op.data)) {
LOG_ERROR("Flash write length larger than buffer: %u", op.length);
return INVALID_ARGUMENT();
}
spi_flash_testutils_program_page(spih, op.data, op.length, op.address,
op.addr4b);
TRY(dif_spi_device_set_passthrough_mode(spid, kDifToggleEnabled));
return RESP_OK_STATUS(uj);
}
status_t command_processor(ujson_t *uj) {
while (true) {
test_command_t command;
TRY(ujson_deserialize_test_command_t(uj, &command));
switch (command) {
case kTestCommandSpiConfigureJedecId:
RESP_ERR(uj, configure_jedec_id(uj, &spid));
break;
case kTestCommandSpiReadStatus:
RESP_ERR(uj, read_status_register(uj, &spid));
break;
case kTestCommandSpiWriteStatus:
RESP_ERR(uj, write_status_register(uj, &spid));
break;
case kTestCommandSpiWriteSfdp:
RESP_ERR(uj, write_sfdp_data(uj, &spid));
break;
case kTestCommandSpiWaitForUpload:
RESP_ERR(uj, wait_for_upload(uj, &spid));
break;
case kTestCommandSpiFlashReadId:
RESP_ERR(uj, spi_flash_read_id(uj, &spih, &spid));
break;
case kTestCommandSpiFlashReadSfdp:
RESP_ERR(uj, spi_flash_read_sfdp(uj, &spih, &spid));
break;
case kTestCommandSpiFlashEraseSector:
RESP_ERR(uj, spi_flash_erase_sector(uj, &spih, &spid));
break;
case kTestCommandSpiFlashWrite:
RESP_ERR(uj, spi_flash_write(uj, &spih, &spid));
break;
default:
LOG_ERROR("Unrecognized command: %d", command);
RESP_ERR(uj, INVALID_ARGUMENT());
}
}
// We should never reach here.
return INTERNAL();
}
bool test_main(void) {
const uint32_t spi_host_clock_freq_hz =
(uint32_t)kClockFreqHiSpeedPeripheralHz;
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_EARLGREY_SPI_HOST0_BASE_ADDR), &spih));
dif_spi_host_config_t config = {
.spi_clock = spi_host_clock_freq_hz / 4,
.peripheral_clock_freq_hz = spi_host_clock_freq_hz,
.chip_select =
{
.idle = 2,
.trail = 2,
.lead = 2,
},
};
CHECK_DIF_OK(dif_spi_host_configure(&spih, config));
CHECK_DIF_OK(dif_spi_host_output_set_enabled(&spih, /*enabled=*/true));
CHECK_DIF_OK(dif_spi_device_init_handle(
mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR), &spid));
// We want to block passthru of the first 5 read commands, corresponding to
// ReadStatus{1,2,3}, ReadJedecID and ReadSfdp.
// We also block all write commands.
spi_device_testutils_configure_passthrough(&spid,
/*filters=*/0x1F,
/*upload_write_commands=*/true);
dif_spi_device_passthrough_intercept_config_t passthru_cfg = {
.status = true,
.jedec_id = true,
.sfdp = true,
.mailbox = false,
};
CHECK_DIF_OK(
dif_spi_device_set_passthrough_intercept_config(&spid, passthru_cfg));
ujson_t uj = ujson_ottf_console();
status_t s = command_processor(&uj);
LOG_INFO("status = %r", s);
return status_ok(s);
}