blob: fdf2fe41ee4136013eb043e16b041b20e35cf474 [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/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