| // 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/dif/dif_spi_host.h" |
| |
| #include "gtest/gtest.h" |
| #include "sw/device/lib/base/global_mock.h" |
| #include "sw/device/lib/base/macros.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/base/mock_mmio.h" |
| #include "sw/device/lib/dif/dif_test_base.h" |
| |
| #include "spi_host_regs.h" // Generated. |
| |
| namespace dif_spi_host_unittest { |
| namespace { |
| |
| // Mock out the spi_host_fifo functions. |
| namespace internal { |
| class MockFifo : public ::global_mock::GlobalMock<MockFifo> { |
| public: |
| MOCK_METHOD(void, write, (const dif_spi_host_t *, const void *, uint16_t)); |
| MOCK_METHOD(void, read, (const dif_spi_host_t *, void *, uint16_t)); |
| }; |
| } // namespace internal |
| using MockFifo = testing::StrictMock<internal::MockFifo>; |
| extern "C" { |
| dif_result_t spi_host_fifo_write_alias(const dif_spi_host_t *spi_host, |
| const void *src, uint16_t len) { |
| MockFifo::Instance().write(spi_host, src, len); |
| return kDifOk; |
| } |
| dif_result_t spi_host_fifo_read_alias(const dif_spi_host_t *spi_host, void *dst, |
| uint16_t len) { |
| MockFifo::Instance().read(spi_host, dst, len); |
| return kDifOk; |
| } |
| } |
| |
| using mock_mmio::MmioTest; |
| using mock_mmio::MockDevice; |
| using testing::ElementsAre; |
| using testing::Test; |
| |
| // Helper macros to make expectations easier to read. |
| #define EXPECT_COMMAND_REG(length, width, direction, last_segment) \ |
| EXPECT_WRITE32(SPI_HOST_COMMAND_REG_OFFSET, \ |
| { \ |
| {SPI_HOST_COMMAND_LEN_OFFSET, (length)-1}, \ |
| {SPI_HOST_COMMAND_SPEED_OFFSET, width}, \ |
| {SPI_HOST_COMMAND_DIRECTION_OFFSET, direction}, \ |
| {SPI_HOST_COMMAND_CSAAT_BIT, !(last_segment)}, \ |
| }) |
| |
| #define EXPECT_READY(ready) \ |
| EXPECT_READ32(SPI_HOST_STATUS_REG_OFFSET, \ |
| {{SPI_HOST_STATUS_READY_BIT, ready}}) |
| |
| #define EXPECT_TXQD(txqd) \ |
| EXPECT_READ32(SPI_HOST_STATUS_REG_OFFSET, \ |
| {{SPI_HOST_STATUS_TXQD_OFFSET, txqd}}) |
| |
| #define EXPECT_RXQD(rxqd) \ |
| EXPECT_READ32(SPI_HOST_STATUS_REG_OFFSET, \ |
| {{SPI_HOST_STATUS_RXQD_OFFSET, rxqd}}) |
| |
| class SpiHostTest : public Test, public MmioTest { |
| protected: |
| void ExpectDeviceReset() { |
| // Place IP into reset. |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SW_RST_BIT, true}, |
| }); |
| // Active bit should be clear. |
| EXPECT_READ32(SPI_HOST_STATUS_REG_OFFSET, 0); |
| // TXQD and RXQD should be zeros. |
| EXPECT_READ32(SPI_HOST_STATUS_REG_OFFSET, 0); |
| // Release IP from reset. |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SW_RST_BIT, false}, |
| }); |
| } |
| |
| dif_spi_host_t spi_host_ = { |
| .base_addr = dev().region(), |
| }; |
| |
| dif_spi_host_config config_ = { |
| .spi_clock = 500000, |
| .peripheral_clock_freq_hz = 1000000, |
| .chip_select = |
| { |
| .idle = 0, |
| .trail = 0, |
| .lead = 0, |
| }, |
| .full_cycle = false, |
| .cpha = false, |
| .cpol = false, |
| }; |
| }; |
| |
| class ConfigTest : public SpiHostTest {}; |
| |
| TEST_F(ConfigTest, NullArgs) { |
| EXPECT_DIF_BADARG(dif_spi_host_configure(nullptr, config_)); |
| } |
| |
| TEST_F(ConfigTest, BadDivider) { |
| // A spi_clock faster than the peripheral clock is invalid. |
| config_.spi_clock = 1000001; |
| EXPECT_DIF_BADARG(dif_spi_host_configure(&spi_host_, config_)); |
| |
| // A spi_clock of zero is invalid. |
| config_.spi_clock = 0; |
| EXPECT_DIF_BADARG(dif_spi_host_configure(&spi_host_, config_)); |
| } |
| |
| // Checks the default configuration. |
| TEST_F(ConfigTest, Default) { |
| ExpectDeviceReset(); |
| EXPECT_WRITE32(SPI_HOST_CONFIGOPTS_REG_OFFSET, |
| { |
| {SPI_HOST_CONFIGOPTS_CLKDIV_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNIDLE_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNTRAIL_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNLEAD_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_FULLCYC_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPHA_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPOL_0_BIT, false}, |
| }); |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SPIEN_BIT, true}, |
| }); |
| |
| EXPECT_DIF_OK(dif_spi_host_configure(&spi_host_, config_)); |
| } |
| |
| // Checks the arguments to the output-enablement DIF are validated. |
| TEST_F(ConfigTest, OutputSetEnabledNullHandle) { |
| EXPECT_DIF_BADARG(dif_spi_host_output_set_enabled(nullptr, true)); |
| } |
| |
| // Checks manipulation of the output enable bit. |
| TEST_F(ConfigTest, OutputEnable) { |
| EXPECT_READ32(SPI_HOST_CONTROL_REG_OFFSET, 0); |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_OUTPUT_EN_BIT, true}, |
| }); |
| EXPECT_DIF_OK(dif_spi_host_output_set_enabled(&spi_host_, true)); |
| } |
| |
| // Checks that the clock divider gets calculated correctly. |
| TEST_F(ConfigTest, ClockRate) { |
| config_.spi_clock = 500000; |
| |
| ExpectDeviceReset(); |
| EXPECT_WRITE32(SPI_HOST_CONFIGOPTS_REG_OFFSET, |
| { |
| {SPI_HOST_CONFIGOPTS_CLKDIV_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNIDLE_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNTRAIL_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNLEAD_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_FULLCYC_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPHA_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPOL_0_BIT, false}, |
| }); |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SPIEN_BIT, true}, |
| }); |
| |
| EXPECT_DIF_OK(dif_spi_host_configure(&spi_host_, config_)); |
| } |
| |
| // Checks that the chip select options get written to the appropriate fields in |
| // the config register. |
| TEST_F(ConfigTest, ChipSelectOptions) { |
| config_.chip_select.idle = 1; |
| config_.chip_select.trail = 2; |
| config_.chip_select.lead = 3; |
| |
| ExpectDeviceReset(); |
| EXPECT_WRITE32(SPI_HOST_CONFIGOPTS_REG_OFFSET, |
| { |
| {SPI_HOST_CONFIGOPTS_CLKDIV_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNIDLE_0_OFFSET, 1}, |
| {SPI_HOST_CONFIGOPTS_CSNTRAIL_0_OFFSET, 2}, |
| {SPI_HOST_CONFIGOPTS_CSNLEAD_0_OFFSET, 3}, |
| {SPI_HOST_CONFIGOPTS_FULLCYC_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPHA_0_BIT, false}, |
| {SPI_HOST_CONFIGOPTS_CPOL_0_BIT, false}, |
| }); |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SPIEN_BIT, true}, |
| }); |
| |
| EXPECT_DIF_OK(dif_spi_host_configure(&spi_host_, config_)); |
| } |
| |
| // Checks that the SPI cycle, polarity and phase options get written to the |
| // appropriate fields in the config register. |
| TEST_F(ConfigTest, SpiOptions) { |
| config_.full_cycle = true; |
| config_.cpol = true; |
| config_.cpha = true; |
| |
| ExpectDeviceReset(); |
| EXPECT_WRITE32(SPI_HOST_CONFIGOPTS_REG_OFFSET, |
| { |
| {SPI_HOST_CONFIGOPTS_CLKDIV_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNIDLE_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNTRAIL_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_CSNLEAD_0_OFFSET, 0}, |
| {SPI_HOST_CONFIGOPTS_FULLCYC_0_BIT, true}, |
| {SPI_HOST_CONFIGOPTS_CPHA_0_BIT, true}, |
| {SPI_HOST_CONFIGOPTS_CPOL_0_BIT, true}, |
| }); |
| EXPECT_WRITE32(SPI_HOST_CONTROL_REG_OFFSET, |
| { |
| {SPI_HOST_CONTROL_SPIEN_BIT, true}, |
| }); |
| |
| EXPECT_DIF_OK(dif_spi_host_configure(&spi_host_, config_)); |
| } |
| |
| class TransactionTest : public SpiHostTest { |
| protected: |
| MockFifo fifo_; |
| }; |
| |
| // Checks that an opcode segment is sent correctly. |
| TEST_F(TransactionTest, IssueOpcode) { |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeOpcode; |
| segment.opcode = 0x5a; |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_TXQD(0); |
| // Opcodes are written directly to the FIFO register. |
| EXPECT_WRITE8(SPI_HOST_TXDATA_REG_OFFSET, 0x5a); |
| EXPECT_COMMAND_REG(/*length=*/1, /*width=*/kDifSpiHostWidthStandard, |
| /*direction=*/kDifSpiHostDirectionTx, /*last=*/true); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that an address segment is sent correctly in 3-byte mode. |
| TEST_F(TransactionTest, IssueAddressMode3b) { |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeAddress; |
| segment.address.width = kDifSpiHostWidthStandard; |
| segment.address.mode = kDifSpiHostAddrMode3b; |
| segment.address.address = 0x112233; |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_TXQD(0); |
| // SPI addresses are written directly to the FIFO register. |
| EXPECT_WRITE32(SPI_HOST_TXDATA_REG_OFFSET, 0x332211); |
| EXPECT_COMMAND_REG(/*length=*/3, /*width=*/kDifSpiHostWidthStandard, |
| /*direction=*/kDifSpiHostDirectionTx, /*last=*/true); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that an address segment is sent correctly in 4-byte mode. |
| TEST_F(TransactionTest, IssueAddressMode4b) { |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeAddress; |
| segment.address.width = kDifSpiHostWidthStandard; |
| segment.address.mode = kDifSpiHostAddrMode4b; |
| segment.address.address = 0x11223344; |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_TXQD(0); |
| // SPI addresses are written directly to the FIFO register. |
| EXPECT_WRITE32(SPI_HOST_TXDATA_REG_OFFSET, 0x44332211); |
| EXPECT_COMMAND_REG(/*length=*/4, /*width=*/kDifSpiHostWidthStandard, |
| /*direction=*/kDifSpiHostDirectionTx, /*last=*/true); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that a dummy segment is sent correctly. |
| TEST_F(TransactionTest, IssueDummy) { |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeDummy; |
| segment.dummy.width = kDifSpiHostWidthStandard; |
| segment.dummy.length = 8; |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_COMMAND_REG(/*length=*/8, /*width=*/kDifSpiHostWidthStandard, |
| /*direction=*/kDifSpiHostDirectionDummy, /*last=*/true); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that a transmit segment is sent correctly. |
| TEST_F(TransactionTest, TransmitDual) { |
| uint8_t buf[32]; |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeTx; |
| segment.tx.width = kDifSpiHostWidthDual; |
| segment.tx.buf = buf; |
| segment.tx.length = sizeof(buf); |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_CALL(fifo_, write(&spi_host_, buf, sizeof(buf))); |
| EXPECT_COMMAND_REG(/*length=*/sizeof(buf), /*width=*/kDifSpiHostWidthDual, |
| /*direction=*/kDifSpiHostDirectionTx, /*last=*/true); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that a receive segment is sent correctly. |
| TEST_F(TransactionTest, ReceiveQuad) { |
| uint8_t buf[32]; |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeRx; |
| segment.rx.width = kDifSpiHostWidthQuad; |
| segment.rx.buf = buf; |
| segment.rx.length = sizeof(buf); |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_COMMAND_REG(/*length=*/sizeof(buf), /*width=*/kDifSpiHostWidthQuad, |
| /*direction=*/kDifSpiHostDirectionRx, /*last=*/true); |
| EXPECT_CALL(fifo_, read(&spi_host_, buf, sizeof(buf))); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that a tranceive segment is sent correctly. |
| TEST_F(TransactionTest, Transceive) { |
| uint8_t txbuf[32]; |
| uint8_t rxbuf[32]; |
| dif_spi_host_segment segment; |
| segment.type = kDifSpiHostSegmentTypeBidirectional; |
| segment.bidir.width = kDifSpiHostWidthStandard; |
| segment.bidir.txbuf = txbuf; |
| segment.bidir.rxbuf = rxbuf; |
| segment.bidir.length = sizeof(txbuf); |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_CALL(fifo_, write(&spi_host_, txbuf, sizeof(txbuf))); |
| EXPECT_COMMAND_REG( |
| /*length=*/sizeof(txbuf), /*width=*/kDifSpiHostWidthStandard, |
| /*direction=*/kDifSpiHostDirectionBidirectional, /*last=*/true); |
| EXPECT_CALL(fifo_, read(&spi_host_, rxbuf, sizeof(rxbuf))); |
| |
| EXPECT_DIF_OK(dif_spi_host_transaction(&spi_host_, 0, &segment, 1)); |
| } |
| |
| // Checks that multiple segments are sent correctly. |
| TEST_F(TransactionTest, MultiSegmentTxRx) { |
| uint8_t txbuf[32]; |
| uint8_t rxbuf[64]; |
| dif_spi_host_segment segment[2]; |
| |
| segment[0].type = kDifSpiHostSegmentTypeTx; |
| segment[0].rx.width = kDifSpiHostWidthDual; |
| segment[0].rx.buf = txbuf; |
| segment[0].rx.length = sizeof(txbuf); |
| segment[1].type = kDifSpiHostSegmentTypeRx; |
| segment[1].rx.width = kDifSpiHostWidthDual; |
| segment[1].rx.buf = rxbuf; |
| segment[1].rx.length = sizeof(rxbuf); |
| |
| EXPECT_WRITE32(SPI_HOST_CSID_REG_OFFSET, 0); |
| EXPECT_READY(true); |
| EXPECT_CALL(fifo_, write(&spi_host_, txbuf, sizeof(txbuf))); |
| EXPECT_COMMAND_REG(/*length=*/sizeof(txbuf), /*width=*/kDifSpiHostWidthDual, |
| /*direction=*/kDifSpiHostDirectionTx, /*last=*/false); |
| EXPECT_READY(true); |
| EXPECT_COMMAND_REG(/*length=*/sizeof(rxbuf), /*width=*/kDifSpiHostWidthDual, |
| /*direction=*/kDifSpiHostDirectionRx, /*last=*/true); |
| EXPECT_CALL(fifo_, read(&spi_host_, rxbuf, sizeof(rxbuf))); |
| |
| EXPECT_DIF_OK( |
| dif_spi_host_transaction(&spi_host_, 0, segment, ARRAYSIZE(segment))); |
| } |
| |
| class FifoTest : public SpiHostTest {}; |
| |
| // Checks that arguments are validated. |
| TEST_F(FifoTest, NullArgs) { |
| uint32_t buffer[2] = {1, 2}; |
| |
| EXPECT_DIF_BADARG(dif_spi_host_fifo_write(nullptr, buffer, sizeof(buffer))); |
| EXPECT_DIF_BADARG( |
| dif_spi_host_fifo_write(&spi_host_, nullptr, sizeof(buffer))); |
| |
| EXPECT_DIF_BADARG(dif_spi_host_fifo_read(nullptr, buffer, sizeof(buffer))); |
| EXPECT_DIF_BADARG( |
| dif_spi_host_fifo_read(&spi_host_, nullptr, sizeof(buffer))); |
| } |
| |
| // Checks that an aligned source buffer is written as 32-bit words into the |
| // transmit FIFO. |
| TEST_F(FifoTest, AlignedWrite) { |
| uint32_t buffer[] = {1, 2}; |
| |
| EXPECT_TXQD(0); |
| EXPECT_WRITE32(SPI_HOST_TXDATA_REG_OFFSET, 1); |
| EXPECT_TXQD(0); |
| EXPECT_WRITE32(SPI_HOST_TXDATA_REG_OFFSET, 2); |
| |
| EXPECT_DIF_OK(dif_spi_host_fifo_write(&spi_host_, buffer, sizeof(buffer))); |
| } |
| |
| template <size_t count, size_t align> |
| struct Aligned { |
| alignas(align) uint8_t value[count]; |
| uint8_t *get() { return &value[0]; } |
| }; |
| |
| // Checks that a misaligned source buffer is written as bytes into the |
| // transmit FIFO until alignment is reached and then written as 32-bit words. |
| TEST_F(FifoTest, MisalignedWrite) { |
| // We'll intentionally mis-align the buffer by 1 when calling |
| // dif_spi_host_fifo_write. |
| Aligned<9, 4> buffer = {0, 1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Because of the misalignment, expect three byte writes. |
| EXPECT_TXQD(0); |
| EXPECT_WRITE8(SPI_HOST_TXDATA_REG_OFFSET, 1); |
| EXPECT_TXQD(0); |
| EXPECT_WRITE8(SPI_HOST_TXDATA_REG_OFFSET, 2); |
| EXPECT_TXQD(0); |
| EXPECT_WRITE8(SPI_HOST_TXDATA_REG_OFFSET, 3); |
| |
| // Then a word write when we reach alignment. |
| EXPECT_TXQD(0); |
| EXPECT_WRITE32(SPI_HOST_TXDATA_REG_OFFSET, 0x07060504); |
| |
| // Then a byte write to finish the buffer. |
| EXPECT_TXQD(0); |
| EXPECT_WRITE8(SPI_HOST_TXDATA_REG_OFFSET, 8); |
| |
| EXPECT_DIF_OK(dif_spi_host_fifo_write(&spi_host_, buffer.get() + 1, 8)); |
| } |
| |
| // Checks that an aligned destination buffer receives the contents of the |
| // recieve FIFO. |
| TEST_F(FifoTest, AlignedRead) { |
| uint32_t buffer[2]; |
| |
| EXPECT_RXQD(2); |
| EXPECT_READ32(SPI_HOST_RXDATA_REG_OFFSET, 1); |
| EXPECT_RXQD(1); |
| EXPECT_READ32(SPI_HOST_RXDATA_REG_OFFSET, 2); |
| |
| EXPECT_DIF_OK(dif_spi_host_fifo_read(&spi_host_, buffer, sizeof(buffer))); |
| EXPECT_THAT(buffer, ElementsAre(1, 2)); |
| } |
| |
| // Checks that a misaligned destination buffer receives the contents of the |
| // recieve FIFO. |
| TEST_F(FifoTest, MisalignedRead) { |
| // We'll intentionally mis-align the buffer by 1 when calling |
| // dif_spi_host_fifo_read. |
| Aligned<9, 4> buffer{}; |
| |
| EXPECT_RXQD(2); |
| EXPECT_READ32(SPI_HOST_RXDATA_REG_OFFSET, 0x04030201); |
| EXPECT_RXQD(1); |
| EXPECT_READ32(SPI_HOST_RXDATA_REG_OFFSET, 0x08070605); |
| |
| EXPECT_DIF_OK(dif_spi_host_fifo_read(&spi_host_, buffer.get() + 1, 8)); |
| EXPECT_THAT(buffer.value, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8)); |
| } |
| |
| } // namespace |
| } // namespace dif_spi_host_unittest |