| /* |
| * Copyright 2023 Google LLC |
| * |
| * 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. |
| */ |
| |
| |
| // Configures the SPI host peripheral as a passthrough to the SPI device |
| // peripheral. Allows for programming of on-board flash memories. |
| // Heavily based on sw/device/tests/sim_dv/spi_passthrough_test.c from OpenTitan |
| // upstream. |
| |
| #include "hw/top_matcha/sw/autogen/top_matcha.h" |
| #include "sw/device/lib/arch/device.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/dif/dif_uart.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/test_util.h" |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| static dif_rv_plic_t rv_plic; |
| static dif_spi_device_handle_t spi_device; |
| static dif_spi_host_t spi_host0; |
| static dif_uart_t uart; |
| |
| /** |
| * A set of typical opcodes for the named flash commands. |
| */ |
| enum spi_flash_opcode { |
| kSpiFlashOpReadJedec = 0x9f, |
| kSpiFlashOpReadSfdp = 0x5a, |
| kSpiFlashOpReadNormal = 0x13, |
| 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, |
| kSpiFlashOpBlockErase3b = 0xd8, |
| kSpiFlashOpBlockErase4b = 0xdc, |
| kSpiFlashOpPageProgram = 0x02, |
| kSpiFlashOpPageProgram4b = 0x12, |
| kSpiFlashOpEnter4bAddr = 0xb7, |
| kSpiFlashOpExit4bAddr = 0xe9, |
| }; |
| |
| enum spi_flash_status_bit { |
| kSpiFlashStatusBitWip = 0x1, |
| kSpiFlashStatusBitWel = 0x2, |
| }; |
| |
| /** |
| * The index where read commands and write commands begin in the command slots. |
| */ |
| enum spi_device_command_slot { |
| kSpiDeviceReadCommandSlotBase = 0, |
| kSpiDeviceWriteCommandSlotBase = 11, |
| }; |
| |
| #define FULL_PAGE_SIZE 512 |
| static const int kHalfPageSize = 256; |
| static const int kFullPageSize = FULL_PAGE_SIZE; |
| enum spi_device_page_part { |
| kSpiDevicePagePartUnknown = 0, |
| kSpiDevicePagePartTop = 1, |
| kSpiDevicePagePartBottom = 2, |
| }; |
| |
| void init_spi_host(dif_spi_host_t *spi_host, |
| uint32_t peripheral_clock_freq_hz) { |
| dif_spi_host_config_t config = { |
| .spi_clock = kClockFreqSpiPassthroughHz, |
| .peripheral_clock_freq_hz = kClockFreqCpuHz, |
| }; |
| CHECK_DIF_OK(dif_spi_host_configure(spi_host, config)); |
| CHECK_DIF_OK(dif_spi_host_output_set_enabled(spi_host, /*enabled=*/true)); |
| } |
| |
| 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)); |
| } |
| |
| 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)); |
| } |
| |
| /** |
| * Spin wait until a Read Status command shows the downstream SPI flash is no |
| * longer busy. |
| */ |
| void wait_until_not_busy(void) { |
| uint8_t status; |
| |
| do { |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = kSpiFlashOpReadStatus1, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeRx, |
| .rx = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .buf = &status, |
| .length = 1, |
| }, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| } while (status & kSpiFlashStatusBitWip); |
| } |
| |
| /** |
| * Issue the Write Enable command to the downstream SPI flash. |
| */ |
| void issue_write_enable(void) { |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = kSpiFlashOpWriteEnable, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| } |
| |
| /** |
| * 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)); |
| |
| issue_write_enable(); |
| |
| 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))); |
| wait_until_not_busy(); |
| 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) { |
| issue_write_enable(); |
| |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = kSpiFlashOpChipErase, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| wait_until_not_busy(); |
| 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)); |
| |
| issue_write_enable(); |
| |
| dif_spi_host_addr_mode_t addr_mode = dif_toggle_to_bool(addr4b_enabled) |
| ? kDifSpiHostAddrMode4b |
| : kDifSpiHostAddrMode3b; |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = kSpiFlashOpSectorErase, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeAddress, |
| .address = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .mode = addr_mode, |
| .address = address, |
| }, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| |
| wait_until_not_busy(); |
| CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); |
| } |
| |
| void handle_block_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)); |
| |
| issue_write_enable(); |
| |
| dif_spi_host_addr_mode_t addr_mode = dif_toggle_to_bool(addr4b_enabled) |
| ? kDifSpiHostAddrMode4b |
| : kDifSpiHostAddrMode3b; |
| enum spi_flash_opcode opcode = dif_toggle_to_bool(addr4b_enabled) |
| ? kSpiFlashOpBlockErase4b |
| : kSpiFlashOpBlockErase3b; |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = opcode, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeAddress, |
| .address = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .mode = addr_mode, |
| .address = address, |
| }, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| |
| wait_until_not_busy(); |
| CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); |
| } |
| |
| // The flash memory on Nexus has 512-byte write sizes, but the RX |
| // FIFO of the SPI host is only 256-bytes. `handle_page_program` combines the |
| // data from two 256-byte writes into a single 512-byte write. |
| static uint8_t full_page[FULL_PAGE_SIZE] __attribute__((aligned(32))); |
| 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)); |
| |
| // Check the address to determine which half of the full page we're receiving. |
| enum spi_device_page_part page_part = kSpiDevicePagePartUnknown; |
| if (address % kFullPageSize == 0) { |
| page_part = kSpiDevicePagePartTop; |
| } else if (address % kFullPageSize == kHalfPageSize) { |
| page_part = kSpiDevicePagePartBottom; |
| } else { |
| CHECK(false); |
| } |
| |
| // If receiving the top half of a page, clear the whole buffer first. |
| if (page_part == kSpiDevicePagePartTop) { |
| memset(full_page, 0, kFullPageSize); |
| } |
| |
| // Receive the half-page payload. |
| uint8_t payload[kHalfPageSize]; |
| 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)); |
| |
| // If we are operating on the top half, simply copy data out of the FIFO, |
| // mark the transaction as complete, and exit. |
| // If we receive the bottom half, we continue on to program the actual flash |
| // memory. |
| if (page_part == kSpiDevicePagePartTop) { |
| memcpy(full_page, payload, kHalfPageSize); |
| CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); |
| return; |
| } else if (page_part == kSpiDevicePagePartBottom) { |
| memcpy(full_page + kHalfPageSize, payload, kHalfPageSize); |
| } |
| |
| dif_toggle_t addr4b_enabled; |
| CHECK_DIF_OK( |
| dif_spi_device_get_4b_address_mode(&spi_device, &addr4b_enabled)); |
| issue_write_enable(); |
| |
| dif_spi_host_addr_mode_t addr_mode = dif_toggle_to_bool(addr4b_enabled) |
| ? kDifSpiHostAddrMode4b |
| : kDifSpiHostAddrMode3b; |
| enum spi_flash_opcode opcode = dif_toggle_to_bool(addr4b_enabled) |
| ? kSpiFlashOpPageProgram4b |
| : kSpiFlashOpPageProgram; |
| |
| // Configure the transaction to program the page. |
| // We have to split the data portion into two segments, |
| // due to FIFO size constraints. |
| dif_spi_host_segment_t transaction[] = { |
| { |
| .type = kDifSpiHostSegmentTypeOpcode, |
| .opcode = opcode, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeAddress, |
| .address = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .mode = addr_mode, |
| .address = address - kHalfPageSize, |
| }, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeTx, |
| .tx = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .buf = full_page, |
| .length = kHalfPageSize, |
| }, |
| }, |
| { |
| .type = kDifSpiHostSegmentTypeTx, |
| .tx = |
| { |
| .width = kDifSpiHostWidthStandard, |
| .buf = full_page + kHalfPageSize, |
| .length = kHalfPageSize, |
| }, |
| }, |
| }; |
| CHECK_DIF_OK(dif_spi_host_transaction(&spi_host0, /*csid=*/0, transaction, |
| ARRAYSIZE(transaction))); |
| |
| wait_until_not_busy(); |
| CHECK_DIF_OK(dif_spi_device_clear_flash_busy_bit(&spi_device)); |
| } |
| |
| 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 == kSpiFlashOpWriteStatus1) { |
| handle_write_status(status, /*offset=*/0, command); |
| } else if (command == kSpiFlashOpWriteStatus2) { |
| handle_write_status(status, /*offset=*/8, command); |
| } else if (command == kSpiFlashOpWriteStatus3) { |
| handle_write_status(status, /*offset=*/16, command); |
| } else if (command == kSpiFlashOpChipErase) { |
| handle_chip_erase(); |
| } else if (command == kSpiFlashOpSectorErase) { |
| handle_sector_erase(); |
| } else if (command == kSpiFlashOpBlockErase3b || |
| command == kSpiFlashOpBlockErase4b) { |
| handle_block_erase(); |
| } else if (command == kSpiFlashOpPageProgram) { |
| 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)); |
| } |
| |
| /** |
| * Initialize the SPI device to be in passthrough mode and allow the following |
| * commands to pass through: |
| * - ReadJedec |
| * - ReadSfdp |
| * - ReadNormal |
| * - ReadFast |
| * - WriteEnable |
| * - WriteDisable |
| * - ReadStatus1 |
| * - ReadStatus2 |
| * - ReadStatus3 |
| * - WriteStatus1 |
| * - WriteStatus2 |
| * - WriteStatus3 |
| * - ChipErase |
| * - SectorErase |
| * - PageProgram |
| * - Enter4bAddr |
| * - Exit4bAddr |
| * |
| */ |
| void init_spi_device(bool upload_write_commands) { |
| 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)); |
| |
| // Zero-init the payload memory to avoid overzealous X-triggered assertions. |
| uint8_t zeroes[256]; |
| memset(zeroes, 0, sizeof(zeroes)); |
| CHECK_DIF_OK(dif_spi_device_write_flash_buffer( |
| &spi_device, kDifSpiDeviceFlashBufferTypePayload, 0, sizeof(zeroes), |
| zeroes)); |
| |
| dif_spi_device_passthrough_intercept_config_t intercept_config = { |
| .status = upload_write_commands, |
| .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, initially. |
| CHECK_DIF_OK(dif_spi_device_set_all_passthrough_command_filters( |
| &spi_device, kDifToggleDisabled)); |
| |
| uint32_t filters = 0; |
| 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, |
| }, |
| }; |
| for (int i = 0; i < ARRAYSIZE(read_commands); ++i) { |
| uint8_t slot = i + kSpiDeviceReadCommandSlotBase; |
| if (bitfield_bit32_read(filters, slot)) { |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, read_commands[i].opcode, kDifToggleEnabled)); |
| } |
| 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 9: Erase2 3b |
| .opcode = kSpiFlashOpBlockErase3b, |
| .address_type = kDifSpiDeviceFlashAddrCfg, |
| .payload_io_type = kDifSpiDevicePayloadIoNone, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 10: Erase2 4b |
| .opcode = kSpiFlashOpBlockErase4b, |
| .address_type = kDifSpiDeviceFlashAddrCfg, |
| .payload_io_type = kDifSpiDevicePayloadIoNone, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 11: WriteStatus1 |
| .opcode = kSpiFlashOpWriteStatus1, |
| .address_type = kDifSpiDeviceFlashAddrDisabled, |
| .payload_io_type = kDifSpiDevicePayloadIoSingle, |
| .payload_dir_to_host = false, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 12: WriteStatus2 |
| .opcode = kSpiFlashOpWriteStatus2, |
| .address_type = kDifSpiDeviceFlashAddrDisabled, |
| .payload_io_type = kDifSpiDevicePayloadIoSingle, |
| .payload_dir_to_host = false, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 13: WriteStatus3 |
| .opcode = kSpiFlashOpWriteStatus3, |
| .address_type = kDifSpiDeviceFlashAddrDisabled, |
| .payload_io_type = kDifSpiDevicePayloadIoSingle, |
| .payload_dir_to_host = false, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 14: ChipErase |
| .opcode = kSpiFlashOpChipErase, |
| .address_type = kDifSpiDeviceFlashAddrDisabled, |
| .payload_io_type = kDifSpiDevicePayloadIoNone, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 15: SectorErase |
| .opcode = kSpiFlashOpSectorErase, |
| .address_type = kDifSpiDeviceFlashAddrCfg, |
| .payload_io_type = kDifSpiDevicePayloadIoNone, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| { |
| // Slot 16: PageProgram |
| .opcode = kSpiFlashOpPageProgram, |
| .address_type = kDifSpiDeviceFlashAddrCfg, |
| .payload_io_type = kDifSpiDevicePayloadIoSingle, |
| .payload_dir_to_host = false, |
| .upload = upload_write_commands, |
| .set_busy_status = upload_write_commands, |
| }, |
| }; |
| for (int i = 0; i < ARRAYSIZE(write_commands); ++i) { |
| uint8_t slot = i + kSpiDeviceWriteCommandSlotBase; |
| if (bitfield_bit32_read(filters, slot) || upload_write_commands) { |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, write_commands[i].opcode, kDifToggleEnabled)); |
| } |
| CHECK_DIF_OK(dif_spi_device_set_flash_command_slot( |
| &spi_device, slot, kDifToggleEnabled, write_commands[i])); |
| } |
| // This configuration for these commands does not guard against misbehaved |
| // hosts. The timing of any of these commands relative to an uploaded command |
| // cannot be determined. |
| 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)); |
| |
| if (upload_write_commands) { |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, kSpiFlashOpReadStatus1, kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, kSpiFlashOpReadStatus2, kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, kSpiFlashOpReadStatus3, kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, kSpiFlashOpWriteEnable, kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_spi_device_set_passthrough_command_filter( |
| &spi_device, kSpiFlashOpWriteDisable, kDifToggleEnabled)); |
| } |
| } |
| |
| void _ottf_main(void) { |
| init_uart(TOP_MATCHA_UART0_BASE_ADDR, &uart); |
| CHECK_DIF_OK(dif_rv_plic_init( |
| mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR), &rv_plic)); |
| |
| CHECK_DIF_OK(dif_spi_host_init( |
| mmio_region_from_addr(TOP_MATCHA_SPI_HOST0_BASE_ADDR), &spi_host0)); |
| init_spi_host(&spi_host0, (uint32_t)kClockFreqPeripheralHz); |
| wait_until_not_busy(); |
| |
| CHECK_DIF_OK(dif_spi_device_init_handle( |
| mmio_region_from_addr(TOP_MATCHA_SPI_DEVICE_BASE_ADDR), &spi_device)); |
| init_spi_device(true); |
| // 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)); |
| } |
| 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); |
| |
| LOG_INFO("SPI passthrough ready."); |
| |
| 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(); |
| } |
| |
| asm volatile("wfi"); |
| } |