| // 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); | 
 | } |