blob: 08187b6acd404446607204f3accbdf1e7255f3c9 [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_device.h"
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/memory.h"
#include "sw/device/lib/dif/dif_base.h"
#include "spi_device_regs.h" // Generated.
#define DIF_SPI_DEVICE_TPM_FIFO_DEPTH 16
const uint16_t kDifSpiDeviceBufferLen = SPI_DEVICE_BUFFER_SIZE_BYTES;
enum { kDifSpiDeviceFlashStatusWelBit = 1 };
enum { kDifSpiDeviceEFlashLen = 2048 };
enum { kDifSpiDeviceMailboxLen = 1024 };
enum { kDifSpiDeviceSfdpLen = 256 };
enum { kDifSpiDevicePayloadLen = 256 };
enum {
kDifSpiDeviceEFlashOffset = 0,
kDifSpiDeviceMailboxOffset = 2048,
kDifSpiDeviceSfdpOffset = 3072,
kDifSpiDevicePayloadOffset = 3328,
};
/**
* Computes the required value of the control register from a given
* configuration.
*/
static inline uint32_t build_control_word(
const dif_spi_device_config_t config) {
uint32_t val = 0;
val =
bitfield_bit32_write(val, SPI_DEVICE_CFG_CPOL_BIT,
config.clock_polarity == kDifSpiDeviceEdgeNegative);
val = bitfield_bit32_write(val, SPI_DEVICE_CFG_CPHA_BIT,
config.data_phase == kDifSpiDeviceEdgePositive);
val = bitfield_bit32_write(val, SPI_DEVICE_CFG_TX_ORDER_BIT,
config.tx_order == kDifSpiDeviceBitOrderLsbToMsb);
val = bitfield_bit32_write(val, SPI_DEVICE_CFG_RX_ORDER_BIT,
config.rx_order == kDifSpiDeviceBitOrderLsbToMsb);
if (config.device_mode == kDifSpiDeviceModeGeneric) {
val = bitfield_field32_write(val, SPI_DEVICE_CFG_TIMER_V_FIELD,
config.mode_cfg.generic.rx_fifo_commit_wait);
}
return val;
}
dif_result_t dif_spi_device_init_handle(mmio_region_t base_addr,
dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
return dif_spi_device_init(base_addr, &spi->dev);
}
dif_result_t dif_spi_device_configure(dif_spi_device_handle_t *spi,
dif_spi_device_config_t config) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t device_mode;
switch (config.device_mode) {
case kDifSpiDeviceModeGeneric:
device_mode = SPI_DEVICE_CONTROL_MODE_VALUE_FWMODE;
break;
case kDifSpiDeviceModeFlashEmulation:
device_mode = SPI_DEVICE_CONTROL_MODE_VALUE_FLASHMODE;
break;
case kDifSpiDeviceModePassthrough:
device_mode = SPI_DEVICE_CONTROL_MODE_VALUE_PASSTHROUGH;
break;
default:
return kDifBadArg;
}
// NOTE: we do not write to any registers until performing all
// function argument checks, to avoid a halfway-configured SPI.
if (config.device_mode == kDifSpiDeviceModeGeneric) {
uint16_t rx_fifo_start = 0x0;
uint16_t rx_fifo_end = config.mode_cfg.generic.rx_fifo_len - 1;
uint16_t tx_fifo_start = rx_fifo_end + 1;
uint16_t tx_fifo_end =
tx_fifo_start + config.mode_cfg.generic.tx_fifo_len - 1;
if (tx_fifo_end >= kDifSpiDeviceBufferLen) {
// We've overflown the SRAM region...
return kDifBadArg;
}
uint32_t rx_fifo_bounds = 0;
rx_fifo_bounds = bitfield_field32_write(
rx_fifo_bounds, SPI_DEVICE_RXF_ADDR_BASE_FIELD, rx_fifo_start);
rx_fifo_bounds = bitfield_field32_write(
rx_fifo_bounds, SPI_DEVICE_RXF_ADDR_LIMIT_FIELD, rx_fifo_end);
uint32_t tx_fifo_bounds = 0;
tx_fifo_bounds = bitfield_field32_write(
tx_fifo_bounds, SPI_DEVICE_TXF_ADDR_BASE_FIELD, tx_fifo_start);
tx_fifo_bounds = bitfield_field32_write(
tx_fifo_bounds, SPI_DEVICE_TXF_ADDR_LIMIT_FIELD, tx_fifo_end);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_RXF_ADDR_REG_OFFSET,
rx_fifo_bounds);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_TXF_ADDR_REG_OFFSET,
tx_fifo_bounds);
}
uint32_t device_config = build_control_word(config);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET,
device_config);
// Turn off SRAM clock to change modes.
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_SRAM_CLK_EN_BIT, false);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
// Change mode.
control = bitfield_field32_write(control, SPI_DEVICE_CONTROL_MODE_FIELD,
device_mode);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
// Re-enable SRAM clock.
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_SRAM_CLK_EN_BIT, true);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
spi->config = config;
return kDifOk;
}
dif_result_t dif_spi_device_set_passthrough_mode(dif_spi_device_handle_t *spi,
dif_toggle_t enable) {
if (spi == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
uint32_t mode = bitfield_field32_read(control, SPI_DEVICE_CONTROL_MODE_FIELD);
if (mode != SPI_DEVICE_CONTROL_MODE_VALUE_FLASHMODE &&
mode != SPI_DEVICE_CONTROL_MODE_VALUE_PASSTHROUGH) {
return kDifBadArg;
}
if (dif_toggle_to_bool(enable)) {
control = bitfield_field32_write(control, SPI_DEVICE_CONTROL_MODE_FIELD,
SPI_DEVICE_CONTROL_MODE_VALUE_PASSTHROUGH);
} else {
control = bitfield_field32_write(control, SPI_DEVICE_CONTROL_MODE_FIELD,
SPI_DEVICE_CONTROL_MODE_VALUE_FLASHMODE);
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
return kDifOk;
}
dif_result_t dif_spi_device_reset_generic_tx_fifo(
dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_RST_TXFIFO_BIT, true);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_RST_TXFIFO_BIT, false);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
return kDifOk;
}
dif_result_t dif_spi_device_reset_generic_rx_fifo(
dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_RST_RXFIFO_BIT, true);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
control =
bitfield_bit32_write(control, SPI_DEVICE_CONTROL_RST_RXFIFO_BIT, false);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
return kDifOk;
}
dif_result_t dif_spi_device_set_sram_clock_enable(dif_spi_device_handle_t *spi,
dif_toggle_t enable) {
if (spi == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
bool clk_enable = dif_toggle_to_bool(enable);
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
control = bitfield_bit32_write(control, SPI_DEVICE_CONTROL_SRAM_CLK_EN_BIT,
clk_enable);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET,
control);
return kDifOk;
}
dif_result_t dif_spi_device_get_sram_clock_enable(dif_spi_device_handle_t *spi,
dif_toggle_t *enabled) {
if (spi == NULL || enabled == NULL) {
return kDifBadArg;
}
uint32_t control =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
*enabled = dif_bool_to_toggle(
bitfield_bit32_read(control, SPI_DEVICE_CONTROL_SRAM_CLK_EN_BIT));
return kDifOk;
}
dif_result_t dif_spi_device_abort(dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
// Set the `abort` bit, and then spin until `abort_done` is asserted.
uint32_t reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET);
reg = bitfield_bit32_write(reg, SPI_DEVICE_CONTROL_ABORT_BIT, true);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET, reg);
while (true) {
uint32_t reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_STATUS_REG_OFFSET);
if (bitfield_bit32_read(reg, SPI_DEVICE_STATUS_ABORT_DONE_BIT)) {
return kDifOk;
}
}
}
dif_result_t dif_spi_device_set_irq_levels(dif_spi_device_handle_t *spi,
uint16_t rx_level,
uint16_t tx_level) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t compressed_limit = 0;
compressed_limit = bitfield_field32_write(
compressed_limit, SPI_DEVICE_FIFO_LEVEL_RXLVL_FIELD, rx_level);
compressed_limit = bitfield_field32_write(
compressed_limit, SPI_DEVICE_FIFO_LEVEL_TXLVL_FIELD, tx_level);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_FIFO_LEVEL_REG_OFFSET,
compressed_limit);
return kDifOk;
}
/**
* Parameters for compressing and decompressing a FIFO pointer register.
*/
typedef struct fifo_ptr_params {
ptrdiff_t reg_offset;
ptrdiff_t write_offset;
ptrdiff_t read_offset;
uint32_t write_mask;
uint32_t read_mask;
} fifo_ptr_params_t;
/**
* Parameters for the transmission FIFO.
*/
static const fifo_ptr_params_t kTxFifoParams = {
.reg_offset = SPI_DEVICE_TXF_PTR_REG_OFFSET,
.write_offset = SPI_DEVICE_TXF_PTR_WPTR_OFFSET,
.write_mask = SPI_DEVICE_TXF_PTR_WPTR_MASK,
.read_offset = SPI_DEVICE_TXF_PTR_RPTR_OFFSET,
.read_mask = SPI_DEVICE_TXF_PTR_RPTR_MASK,
};
/**
* Parameters for the receipt FIFO.
*/
static const fifo_ptr_params_t kRxFifoParams = {
.reg_offset = SPI_DEVICE_RXF_PTR_REG_OFFSET,
.write_offset = SPI_DEVICE_RXF_PTR_WPTR_OFFSET,
.write_mask = SPI_DEVICE_RXF_PTR_WPTR_MASK,
.read_offset = SPI_DEVICE_RXF_PTR_RPTR_OFFSET,
.read_mask = SPI_DEVICE_RXF_PTR_RPTR_MASK,
};
/**
* An exploded FIFO pointer value, consisting of a `uint11_t`
* offset part (an offset into a FIFO), and a `uint1_t` phase
* (which indicates whether this pointer has wrapped around or not).
*
* See also `fifo_ptrs_t`.
*/
typedef struct fifo_ptr {
uint16_t offset;
bool phase;
} fifo_ptr_t;
// Masks for extracting the phase and offset parts from a
// compressed FIFO pointer.
static const uint16_t kFifoPhaseMask = (1 << 12);
static const uint16_t kFifoOffsetMask = (1 << 12) - 1;
/**
* Modifies a `fifo_ptr_t` into a FIFO of length `fifo_len` by
* incrementing it by `increment`, making sure to correctly flip the
* phase bit on overflow.
*
* @param ptr the pointer to increment.
* @param increment the amount to increment by.
* @param fifo_len the length of the FIFO the pointer points into.
*/
static void fifo_ptr_increment(fifo_ptr_t *ptr, uint16_t increment,
uint16_t fifo_len) {
uint32_t inc_with_overflow = ptr->offset + increment;
// If we would overflow, wrap and flip the overflow bit.
if (inc_with_overflow >= fifo_len) {
inc_with_overflow -= fifo_len;
ptr->phase = !ptr->phase;
}
ptr->offset = inc_with_overflow & kFifoOffsetMask;
}
/**
* A decompressed FIFO pointer register, consisting of a read offset
* and a write offset within the FIFO region.
*
* The offsets themselves are only `uint11_t`, with an additional
* 12th "phase" bit used for detecting the wrap around behavior of
* the ring buffer FIFOs.
*/
typedef struct fifo_ptrs {
fifo_ptr_t write_ptr;
fifo_ptr_t read_ptr;
} fifo_ptrs_t;
/**
* Expands read and write FIFO pointers out of `spi`, using the given FIFO
* parameters.
*
* @param spi the SPI device.
* @param params bitfield parameters for the FIFO.
* @return expanded pointers read out of `spi`.
*/
static fifo_ptrs_t decompress_ptrs(const dif_spi_device_handle_t *spi,
fifo_ptr_params_t params) {
uint32_t ptr = mmio_region_read32(spi->dev.base_addr, params.reg_offset);
uint16_t write_val =
(uint16_t)((ptr >> params.write_offset) & params.write_mask);
uint16_t read_val =
(uint16_t)((ptr >> params.read_offset) & params.read_mask);
return (fifo_ptrs_t){
.write_ptr =
{
.offset = write_val & kFifoOffsetMask,
.phase = (write_val & kFifoPhaseMask) != 0,
},
.read_ptr =
{
.offset = read_val & kFifoOffsetMask,
.phase = (read_val & kFifoPhaseMask) != 0,
},
};
}
/**
* Writes back read and write FIFO pointers into `spi`, using the given FIFO
* parameters.
*
* @param spi the SPI device.
* @param params bitfield parameters for the FIFO.
* @param ptrs the new pointer values.
*/
static void compress_ptrs(dif_spi_device_handle_t *spi,
fifo_ptr_params_t params, fifo_ptrs_t ptrs) {
uint16_t write_val = ptrs.write_ptr.offset;
if (ptrs.write_ptr.phase) {
write_val |= kFifoPhaseMask;
}
uint16_t read_val = ptrs.read_ptr.offset;
if (ptrs.read_ptr.phase) {
read_val |= kFifoPhaseMask;
}
uint32_t ptr = 0;
ptr = bitfield_field32_write(ptr,
(bitfield_field32_t){
.mask = params.write_mask,
.index = params.write_offset,
},
write_val);
ptr = bitfield_field32_write(ptr,
(bitfield_field32_t){
.mask = params.read_mask,
.index = params.read_offset,
},
read_val);
mmio_region_write32(spi->dev.base_addr, params.reg_offset, ptr);
}
/**
* Counts the number of bytes from the read pointer to the write pointer in
* `ptrs`, in a FIFO of length `fifo_len`.
*
* @param ptrs a set of FIFO pointers.
* @param fifo_len the length of the fifo, in bytes.
* @return the number of bytes "in use".
*/
static uint16_t fifo_bytes_in_use(fifo_ptrs_t ptrs, uint16_t fifo_len) {
// This represents the case where the valid data of the fifo is "inclusive",
// i.e., the buffer looks like (where a / represents valid data):
// [ ///// ]
// ^ ^
// r w
//
// In particular, when r == w, the fifo is empty.
if (ptrs.write_ptr.phase == ptrs.read_ptr.phase) {
return ptrs.write_ptr.offset - ptrs.read_ptr.offset;
}
// This represents the case where the valid data of the fifo is "exclusive",
// i.e., the buffer looks like (where a / represents valid data):
// [/ //////]
// ^ ^
// w r
//
// In particular, when r == w, the fifo is full.
return fifo_len - (ptrs.read_ptr.offset - ptrs.write_ptr.offset);
}
dif_result_t dif_spi_device_rx_pending(const dif_spi_device_handle_t *spi,
size_t *bytes_pending) {
if (spi == NULL || bytes_pending == NULL) {
return kDifBadArg;
}
fifo_ptrs_t ptrs = decompress_ptrs(spi, kRxFifoParams);
*bytes_pending =
fifo_bytes_in_use(ptrs, spi->config.mode_cfg.generic.rx_fifo_len);
return kDifOk;
}
dif_result_t dif_spi_device_tx_pending(const dif_spi_device_handle_t *spi,
size_t *bytes_pending) {
if (spi == NULL || bytes_pending == NULL) {
return kDifBadArg;
}
fifo_ptrs_t ptrs = decompress_ptrs(spi, kTxFifoParams);
*bytes_pending =
fifo_bytes_in_use(ptrs, spi->config.mode_cfg.generic.tx_fifo_len);
return kDifOk;
}
dif_result_t dif_spi_device_get_async_fifo_levels(dif_spi_device_handle_t *spi,
uint16_t *rx_fifo_level,
uint16_t *tx_fifo_level) {
if (spi == NULL || rx_fifo_level == NULL || tx_fifo_level == NULL) {
return kDifBadArg;
}
uint32_t async_fifo_level = mmio_region_read32(
spi->dev.base_addr, SPI_DEVICE_ASYNC_FIFO_LEVEL_REG_OFFSET);
*rx_fifo_level = bitfield_field32_read(
async_fifo_level, SPI_DEVICE_ASYNC_FIFO_LEVEL_RXLVL_FIELD);
*tx_fifo_level = bitfield_field32_read(
async_fifo_level, SPI_DEVICE_ASYNC_FIFO_LEVEL_TXLVL_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_get_generic_fifo_status(
dif_spi_device_handle_t *spi,
dif_spi_device_generic_fifo_status_t *status) {
if (spi == NULL || status == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_STATUS_REG_OFFSET);
status->rx_full =
bitfield_bit32_read(reg_val, SPI_DEVICE_STATUS_RXF_FULL_BIT);
status->rx_empty =
bitfield_bit32_read(reg_val, SPI_DEVICE_STATUS_RXF_EMPTY_BIT);
status->tx_full =
bitfield_bit32_read(reg_val, SPI_DEVICE_STATUS_TXF_FULL_BIT);
status->tx_empty =
bitfield_bit32_read(reg_val, SPI_DEVICE_STATUS_TXF_EMPTY_BIT);
return kDifOk;
}
dif_result_t dif_spi_device_get_csb_status(dif_spi_device_handle_t *spi,
bool *csb) {
if (spi == NULL || csb == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_STATUS_REG_OFFSET);
*csb = bitfield_bit32_read(reg_val, SPI_DEVICE_STATUS_CSB_BIT);
return kDifOk;
}
/**
* Performs a "memcpy" of sorts between a main memory buffer and SPI SRAM,
* which does not support non-word I/O.
*
* If `is_recv` is set, then the copy direction is `spi -> buf`. If it is
* unset, the copy direction is `buf -> spi`.
*
* @param spi a SPI device.
* @param fifo a decompressed FIFO pointer pair.
* @param fifo_base the offset from start of SRAM for the FIFO to copy to/from.
* @param fifo_len the length of the FIFO, in bytes.
* @param byte_buf a main memory buffer for copying from/to.
* @param buf_len the length of the main memory buffer.
* @param is_recv whether this is a SPI reciept or SPI transmit transaction.
* @return the number of bytes copied.
*/
static size_t spi_memcpy(dif_spi_device_handle_t *spi, fifo_ptrs_t *fifo,
uint16_t fifo_base, uint16_t fifo_len,
uint8_t *byte_buf, size_t buf_len, bool is_recv) {
uint16_t bytes_left = fifo_bytes_in_use(*fifo, fifo_len);
// When sending, the bytes left are the empty space still available.
if (!is_recv) {
bytes_left = fifo_len - bytes_left;
}
if (bytes_left > buf_len) {
bytes_left = buf_len;
}
if (bytes_left == 0) {
return 0;
}
const uint16_t total_bytes = bytes_left;
// For receipt, we advance the read pointer, which indicates how far ahead
// we've read so far. For sending, we advance the write pointer, which
// indicates how far ahead we've written.
fifo_ptr_t *ptr;
if (is_recv) {
ptr = &fifo->read_ptr;
} else {
ptr = &fifo->write_ptr;
}
// `mmio_region_memcpy_*_mmio32` functions assume sequential memory access
// while the SPI device uses a circular buffer. Therefore, we split the copy
// operation into chunks that access the device buffer sequentially.
while (bytes_left > 0) {
const uint32_t mmio_offset =
SPI_DEVICE_BUFFER_REG_OFFSET + fifo_base + ptr->offset;
const uint32_t bytes_until_wrap = fifo_len - ptr->offset;
uint16_t bytes_to_copy = bytes_left;
if (bytes_to_copy > bytes_until_wrap) {
bytes_to_copy = bytes_until_wrap;
}
if (is_recv) {
// SPI device buffer -> `byte_buf`
mmio_region_memcpy_from_mmio32(spi->dev.base_addr, mmio_offset, byte_buf,
bytes_to_copy);
} else {
// `byte_buf` -> SPI device buffer
mmio_region_memcpy_to_mmio32(spi->dev.base_addr, mmio_offset, byte_buf,
bytes_to_copy);
}
fifo_ptr_increment(ptr, bytes_to_copy, fifo_len);
byte_buf += bytes_to_copy;
bytes_left -= bytes_to_copy;
}
return total_bytes;
}
dif_result_t dif_spi_device_recv(dif_spi_device_handle_t *spi, void *buf,
size_t buf_len, size_t *bytes_received) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
uint16_t fifo_base = 0;
fifo_ptrs_t fifo = decompress_ptrs(spi, kRxFifoParams);
size_t bytes = spi_memcpy(spi, &fifo, fifo_base,
spi->config.mode_cfg.generic.rx_fifo_len,
(uint8_t *)buf, buf_len, /*is_recv=*/true);
if (bytes_received != NULL) {
*bytes_received = bytes;
}
if (bytes > 0) {
// Commit the new RX FIFO pointers.
compress_ptrs(spi, kRxFifoParams, fifo);
}
return kDifOk;
}
dif_result_t dif_spi_device_send(dif_spi_device_handle_t *spi, const void *buf,
size_t buf_len, size_t *bytes_sent) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
// Start of the TX FIFO is the end of the RX FIFO.
fifo_ptrs_t fifo = decompress_ptrs(spi, kTxFifoParams);
size_t bytes =
spi_memcpy(spi, &fifo, spi->config.mode_cfg.generic.rx_fifo_len,
spi->config.mode_cfg.generic.tx_fifo_len, (uint8_t *)buf,
buf_len, /*is_recv=*/false);
if (bytes_sent != NULL) {
*bytes_sent = bytes;
}
if (bytes > 0) {
// Commit the new TX FIFO pointers.
compress_ptrs(spi, kTxFifoParams, fifo);
}
return kDifOk;
}
dif_result_t dif_spi_device_enable_mailbox(dif_spi_device_handle_t *spi,
uint32_t address) {
if (spi == NULL) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_MAILBOX_ADDR_REG_OFFSET,
address);
uint32_t cfg_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET);
cfg_reg = bitfield_bit32_write(cfg_reg, SPI_DEVICE_CFG_MAILBOX_EN_BIT, 1);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET, cfg_reg);
return kDifOk;
}
dif_result_t dif_spi_device_disable_mailbox(dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t cfg_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET);
cfg_reg = bitfield_bit32_write(cfg_reg, SPI_DEVICE_CFG_MAILBOX_EN_BIT, 0);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET, cfg_reg);
return kDifOk;
}
dif_result_t dif_spi_device_get_mailbox_configuration(
dif_spi_device_handle_t *spi, dif_toggle_t *is_enabled, uint32_t *address) {
if (spi == NULL || is_enabled == NULL || address == NULL) {
return kDifBadArg;
}
uint32_t cfg_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET);
bool mailbox_enabled =
bitfield_bit32_read(cfg_reg, SPI_DEVICE_CFG_MAILBOX_EN_BIT);
*is_enabled = dif_bool_to_toggle(mailbox_enabled);
*address = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_MAILBOX_ADDR_REG_OFFSET);
return kDifOk;
}
dif_result_t dif_spi_device_set_4b_address_mode(dif_spi_device_handle_t *spi,
dif_toggle_t addr_4b) {
if (spi == NULL || !dif_is_valid_toggle(addr_4b)) {
return kDifBadArg;
}
uint32_t cfg_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET);
if (addr_4b == kDifToggleEnabled) {
cfg_reg =
bitfield_bit32_write(cfg_reg, SPI_DEVICE_CFG_ADDR_4B_EN_BIT, true);
} else {
cfg_reg =
bitfield_bit32_write(cfg_reg, SPI_DEVICE_CFG_ADDR_4B_EN_BIT, false);
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET, cfg_reg);
return kDifOk;
}
dif_result_t dif_spi_device_get_4b_address_mode(dif_spi_device_handle_t *spi,
dif_toggle_t *addr_4b) {
if (spi == NULL || addr_4b == NULL) {
return kDifBadArg;
}
uint32_t cfg_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_CFG_REG_OFFSET);
if (bitfield_bit32_read(cfg_reg, SPI_DEVICE_CFG_ADDR_4B_EN_BIT)) {
*addr_4b = kDifToggleEnabled;
} else {
*addr_4b = kDifToggleDisabled;
}
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_id(dif_spi_device_handle_t *spi,
dif_spi_device_flash_id_t *id) {
if (spi == NULL || id == NULL) {
return kDifBadArg;
}
uint32_t cc_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_JEDEC_CC_REG_OFFSET);
uint32_t id_reg =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_JEDEC_ID_REG_OFFSET);
id->num_continuation_code =
bitfield_field32_read(cc_reg, SPI_DEVICE_JEDEC_CC_NUM_CC_FIELD);
id->continuation_code =
bitfield_field32_read(cc_reg, SPI_DEVICE_JEDEC_CC_CC_FIELD);
id->manufacturer_id =
bitfield_field32_read(id_reg, SPI_DEVICE_JEDEC_ID_MF_FIELD);
id->device_id = bitfield_field32_read(id_reg, SPI_DEVICE_JEDEC_ID_ID_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_set_flash_id(dif_spi_device_handle_t *spi,
dif_spi_device_flash_id_t id) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t cc_reg = bitfield_field32_write(0, SPI_DEVICE_JEDEC_CC_NUM_CC_FIELD,
id.num_continuation_code);
cc_reg = bitfield_field32_write(cc_reg, SPI_DEVICE_JEDEC_CC_CC_FIELD,
id.continuation_code);
uint32_t id_reg = bitfield_field32_write(0, SPI_DEVICE_JEDEC_ID_MF_FIELD,
id.manufacturer_id);
id_reg = bitfield_field32_write(id_reg, SPI_DEVICE_JEDEC_ID_ID_FIELD,
id.device_id);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_JEDEC_CC_REG_OFFSET,
cc_reg);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_JEDEC_ID_REG_OFFSET,
id_reg);
return kDifOk;
}
dif_result_t dif_spi_device_set_passthrough_intercept_config(
dif_spi_device_handle_t *spi,
dif_spi_device_passthrough_intercept_config_t config) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t reg_val = bitfield_bit32_write(0, SPI_DEVICE_INTERCEPT_EN_STATUS_BIT,
config.status);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_INTERCEPT_EN_JEDEC_BIT,
config.jedec_id);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_INTERCEPT_EN_SFDP_BIT,
config.sfdp);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_INTERCEPT_EN_MBX_BIT,
config.mailbox);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_INTERCEPT_EN_REG_OFFSET,
reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_get_last_read_address(dif_spi_device_handle_t *spi,
uint32_t *address) {
if (spi == NULL || address == NULL) {
return kDifBadArg;
}
*address = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_LAST_READ_ADDR_REG_OFFSET);
return kDifOk;
}
dif_result_t dif_spi_device_set_eflash_read_threshold(
dif_spi_device_handle_t *spi, uint32_t address) {
if (spi == NULL || address > SPI_DEVICE_READ_THRESHOLD_THRESHOLD_MASK) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_READ_THRESHOLD_REG_OFFSET,
address);
return kDifOk;
}
dif_result_t dif_spi_device_set_flash_command_slot(
dif_spi_device_handle_t *spi, uint8_t slot, dif_toggle_t enable,
dif_spi_device_flash_command_t command_info) {
if (spi == NULL || slot >= SPI_DEVICE_PARAM_NUM_CMD_INFO ||
!dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
ptrdiff_t reg_offset =
SPI_DEVICE_CMD_INFO_0_REG_OFFSET + slot * sizeof(uint32_t);
uint32_t reg_val = 0;
if (enable == kDifToggleDisabled) {
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_VALID_0_BIT, false);
} else {
// Validate command info parameters.
uint32_t address_mode;
switch (command_info.address_type) {
case kDifSpiDeviceFlashAddrDisabled:
address_mode = SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDRDISABLED;
break;
case kDifSpiDeviceFlashAddrCfg:
address_mode = SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDRCFG;
break;
case kDifSpiDeviceFlashAddr3Byte:
address_mode = SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDR3B;
break;
case kDifSpiDeviceFlashAddr4Byte:
address_mode = SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDR4B;
break;
default:
return kDifBadArg;
}
if (command_info.dummy_cycles >
(1u + SPI_DEVICE_CMD_INFO_0_DUMMY_SIZE_0_MASK)) {
return kDifBadArg;
}
uint32_t payload_en;
switch (command_info.payload_io_type) {
case kDifSpiDevicePayloadIoNone:
payload_en = 0x0;
break;
case kDifSpiDevicePayloadIoSingle:
if (command_info.payload_dir_to_host) {
payload_en = 0x2;
} else {
payload_en = 0x1;
}
break;
case kDifSpiDevicePayloadIoDual:
payload_en = 0x3;
break;
case kDifSpiDevicePayloadIoQuad:
payload_en = 0xf;
break;
default:
return kDifBadArg;
}
// Check for invalid argument combinations.
if (command_info.payload_swap_enable &&
(command_info.payload_dir_to_host ||
command_info.payload_io_type != kDifSpiDevicePayloadIoSingle)) {
return kDifBadArg;
}
if (command_info.passthrough_swap_address &&
command_info.address_type == kDifSpiDeviceFlashAddrDisabled) {
return kDifBadArg;
}
// Write the command info values.
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_CMD_INFO_0_OPCODE_0_FIELD, command_info.opcode);
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_FIELD, address_mode);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_ADDR_SWAP_EN_0_BIT,
command_info.passthrough_swap_address);
if (command_info.dummy_cycles > 0) {
reg_val = bitfield_field32_write(reg_val,
SPI_DEVICE_CMD_INFO_0_DUMMY_SIZE_0_FIELD,
command_info.dummy_cycles - 1);
reg_val = bitfield_bit32_write(
reg_val, SPI_DEVICE_CMD_INFO_0_DUMMY_EN_0_BIT, true);
}
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_EN_0_FIELD, payload_en);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_DIR_0_BIT,
command_info.payload_dir_to_host);
reg_val = bitfield_bit32_write(reg_val,
SPI_DEVICE_CMD_INFO_0_PAYLOAD_SWAP_EN_0_BIT,
command_info.payload_swap_enable);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_UPLOAD_0_BIT,
command_info.upload);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_BUSY_0_BIT,
command_info.set_busy_status);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_0_VALID_0_BIT, true);
}
mmio_region_write32(spi->dev.base_addr, reg_offset, reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_command_slot(
dif_spi_device_handle_t *spi, uint8_t slot, dif_toggle_t *enabled,
dif_spi_device_flash_command_t *command_info) {
if (spi == NULL || enabled == NULL || command_info == NULL ||
slot >= SPI_DEVICE_PARAM_NUM_CMD_INFO) {
return kDifBadArg;
}
ptrdiff_t reg_offset =
SPI_DEVICE_CMD_INFO_0_REG_OFFSET + slot * sizeof(uint32_t);
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr, reg_offset);
dif_spi_device_flash_address_type_t address_type;
uint32_t reg_val_address_mode =
bitfield_field32_read(reg_val, SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_FIELD);
switch (reg_val_address_mode) {
case SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDRDISABLED:
address_type = kDifSpiDeviceFlashAddrDisabled;
break;
case SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDRCFG:
address_type = kDifSpiDeviceFlashAddrCfg;
break;
case SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDR3B:
address_type = kDifSpiDeviceFlashAddr3Byte;
break;
case SPI_DEVICE_CMD_INFO_0_ADDR_MODE_0_VALUE_ADDR4B:
address_type = kDifSpiDeviceFlashAddr4Byte;
break;
default:
address_type = kDifSpiDeviceFlashAddrCount;
break;
}
uint32_t dummy_cycles;
if (bitfield_bit32_read(reg_val, SPI_DEVICE_CMD_INFO_0_DUMMY_EN_0_BIT)) {
dummy_cycles = 1 + bitfield_field32_read(
reg_val, SPI_DEVICE_CMD_INFO_0_DUMMY_SIZE_0_FIELD);
} else {
dummy_cycles = 0;
}
uint32_t payload_en =
bitfield_field32_read(reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_EN_0_FIELD);
bool payload_dir_to_host =
bitfield_bit32_read(reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_DIR_0_BIT);
dif_spi_device_payload_io_t payload_io_type;
switch (payload_en) {
case 0x0:
payload_io_type = kDifSpiDevicePayloadIoNone;
break;
case 0x1:
if (payload_dir_to_host) {
payload_io_type = kDifSpiDevicePayloadIoInvalid;
} else {
payload_io_type = kDifSpiDevicePayloadIoSingle;
}
break;
case 0x2:
if (!payload_dir_to_host) {
payload_io_type = kDifSpiDevicePayloadIoInvalid;
} else {
payload_io_type = kDifSpiDevicePayloadIoSingle;
}
break;
case 0x3:
payload_io_type = kDifSpiDevicePayloadIoDual;
break;
case 0xf:
payload_io_type = kDifSpiDevicePayloadIoQuad;
break;
default:
payload_io_type = kDifSpiDevicePayloadIoInvalid;
break;
}
dif_spi_device_flash_command_t cmd = {
.opcode =
bitfield_field32_read(reg_val, SPI_DEVICE_CMD_INFO_0_OPCODE_0_FIELD),
.address_type = address_type,
.dummy_cycles = dummy_cycles,
.payload_io_type = payload_io_type,
.passthrough_swap_address = bitfield_bit32_read(
reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_SWAP_EN_0_BIT),
.payload_dir_to_host = payload_dir_to_host,
.payload_swap_enable = bitfield_bit32_read(
reg_val, SPI_DEVICE_CMD_INFO_0_PAYLOAD_SWAP_EN_0_BIT),
.upload =
bitfield_bit32_read(reg_val, SPI_DEVICE_CMD_INFO_0_UPLOAD_0_BIT),
.set_busy_status =
bitfield_bit32_read(reg_val, SPI_DEVICE_CMD_INFO_0_BUSY_0_BIT),
};
*command_info = cmd;
if (bitfield_bit32_read(reg_val, SPI_DEVICE_CMD_INFO_0_VALID_0_BIT)) {
*enabled = kDifToggleEnabled;
} else {
*enabled = kDifToggleDisabled;
}
return kDifOk;
}
/**
* Write cmd_info register that is a separate CSR for a specific opcode (not
* attached to a numbered slot).
*
* @param spi A handle to a spi_device.
* @param enable Whether to enable the function.
* @param opcode Which opcode activates the function.
* @param reg_offset The register offset for the function's cmd_info CSR.
* @return The result of the operation.
*/
static dif_result_t write_special_cmd_info(dif_spi_device_handle_t *spi,
dif_toggle_t enable, uint8_t opcode,
ptrdiff_t reg_offset) {
if (spi == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
bool valid = dif_toggle_to_bool(enable);
uint32_t reg_val =
bitfield_field32_write(0, SPI_DEVICE_CMD_INFO_EN4B_OPCODE_FIELD, opcode);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_CMD_INFO_EN4B_VALID_BIT, valid);
mmio_region_write32(spi->dev.base_addr, reg_offset, reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_configure_flash_en4b_command(
dif_spi_device_handle_t *spi, dif_toggle_t enable, uint8_t opcode) {
return write_special_cmd_info(spi, enable, opcode,
SPI_DEVICE_CMD_INFO_EN4B_REG_OFFSET);
}
dif_result_t dif_spi_device_configure_flash_ex4b_command(
dif_spi_device_handle_t *spi, dif_toggle_t enable, uint8_t opcode) {
return write_special_cmd_info(spi, enable, opcode,
SPI_DEVICE_CMD_INFO_EX4B_REG_OFFSET);
}
dif_result_t dif_spi_device_configure_flash_wren_command(
dif_spi_device_handle_t *spi, dif_toggle_t enable, uint8_t opcode) {
return write_special_cmd_info(spi, enable, opcode,
SPI_DEVICE_CMD_INFO_WREN_REG_OFFSET);
}
dif_result_t dif_spi_device_configure_flash_wrdi_command(
dif_spi_device_handle_t *spi, dif_toggle_t enable, uint8_t opcode) {
return write_special_cmd_info(spi, enable, opcode,
SPI_DEVICE_CMD_INFO_WRDI_REG_OFFSET);
}
dif_result_t dif_spi_device_set_flash_address_swap(dif_spi_device_handle_t *spi,
uint32_t mask,
uint32_t replacement) {
if (spi == NULL) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_ADDR_SWAP_MASK_REG_OFFSET,
mask);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_ADDR_SWAP_DATA_REG_OFFSET,
replacement);
return kDifOk;
}
dif_result_t dif_spi_device_set_flash_payload_swap(dif_spi_device_handle_t *spi,
uint32_t mask,
uint32_t replacement) {
if (spi == NULL) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr,
SPI_DEVICE_PAYLOAD_SWAP_MASK_REG_OFFSET, mask);
mmio_region_write32(spi->dev.base_addr,
SPI_DEVICE_PAYLOAD_SWAP_DATA_REG_OFFSET, replacement);
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_command_fifo_occupancy(
dif_spi_device_handle_t *spi, uint8_t *occupancy) {
if (spi == NULL || occupancy == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_UPLOAD_STATUS_REG_OFFSET);
*occupancy = bitfield_field32_read(
reg_val, SPI_DEVICE_UPLOAD_STATUS_CMDFIFO_DEPTH_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_address_fifo_occupancy(
dif_spi_device_handle_t *spi, uint8_t *occupancy) {
if (spi == NULL || occupancy == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_UPLOAD_STATUS_REG_OFFSET);
*occupancy = bitfield_field32_read(
reg_val, SPI_DEVICE_UPLOAD_STATUS_ADDRFIFO_DEPTH_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_payload_fifo_occupancy(
dif_spi_device_handle_t *spi, uint16_t *occupancy, uint32_t *start_offset) {
if (spi == NULL || occupancy == NULL || start_offset == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_UPLOAD_STATUS2_REG_OFFSET);
*occupancy = bitfield_field32_read(
reg_val, SPI_DEVICE_UPLOAD_STATUS2_PAYLOAD_DEPTH_FIELD);
*start_offset = bitfield_field32_read(
reg_val, SPI_DEVICE_UPLOAD_STATUS2_PAYLOAD_START_IDX_FIELD);
return kDifOk;
}
// TODO: Does the IP handle overrun / underrun correctly?
dif_result_t dif_spi_device_pop_flash_command_fifo(dif_spi_device_handle_t *spi,
uint8_t *command) {
if (spi == NULL || command == NULL) {
return kDifBadArg;
}
uint32_t upload_status = mmio_region_read32(
spi->dev.base_addr, SPI_DEVICE_UPLOAD_STATUS_REG_OFFSET);
if (!bitfield_bit32_read(upload_status,
SPI_DEVICE_UPLOAD_STATUS_CMDFIFO_NOTEMPTY_BIT)) {
return kDifUnavailable;
}
uint32_t cmd_item = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_UPLOAD_CMDFIFO_REG_OFFSET);
*command =
bitfield_field32_read(cmd_item, SPI_DEVICE_UPLOAD_CMDFIFO_DATA_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_pop_flash_address_fifo(dif_spi_device_handle_t *spi,
uint32_t *address) {
if (spi == NULL || address == NULL) {
return kDifBadArg;
}
uint32_t upload_status = mmio_region_read32(
spi->dev.base_addr, SPI_DEVICE_UPLOAD_STATUS_REG_OFFSET);
if (!bitfield_bit32_read(upload_status,
SPI_DEVICE_UPLOAD_STATUS_ADDRFIFO_NOTEMPTY_BIT)) {
return kDifUnavailable;
}
*address = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_UPLOAD_ADDRFIFO_REG_OFFSET);
return kDifOk;
}
typedef struct dif_spi_device_flash_buffer_info {
size_t buffer_len;
ptrdiff_t buffer_offset;
} dif_spi_device_flash_buffer_info_t;
static dif_result_t dif_spi_device_get_flash_buffer_info(
dif_spi_device_flash_buffer_type_t buffer_type,
dif_spi_device_flash_buffer_info_t *info) {
switch (buffer_type) {
case kDifSpiDeviceFlashBufferTypeEFlash:
info->buffer_len = kDifSpiDeviceEFlashLen;
info->buffer_offset = kDifSpiDeviceEFlashOffset;
break;
case kDifSpiDeviceFlashBufferTypeMailbox:
info->buffer_len = kDifSpiDeviceMailboxLen;
info->buffer_offset = kDifSpiDeviceMailboxOffset;
break;
case kDifSpiDeviceFlashBufferTypeSfdp:
info->buffer_len = kDifSpiDeviceSfdpLen;
info->buffer_offset = kDifSpiDeviceSfdpOffset;
break;
case kDifSpiDeviceFlashBufferTypePayload:
info->buffer_len = kDifSpiDevicePayloadLen;
info->buffer_offset = kDifSpiDevicePayloadOffset;
break;
default:
return kDifBadArg;
}
return kDifOk;
}
dif_result_t dif_spi_device_read_flash_buffer(
dif_spi_device_handle_t *spi,
dif_spi_device_flash_buffer_type_t buffer_type, uint32_t offset,
size_t length, uint8_t *buf) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
dif_spi_device_flash_buffer_info_t info;
dif_result_t status =
dif_spi_device_get_flash_buffer_info(buffer_type, &info);
if (status != kDifOk) {
return status;
}
if (offset >= (info.buffer_offset + info.buffer_len) ||
length > (info.buffer_offset + info.buffer_len - offset)) {
return kDifBadArg;
}
ptrdiff_t offset_from_base =
SPI_DEVICE_BUFFER_REG_OFFSET + info.buffer_offset + offset;
mmio_region_memcpy_from_mmio32(spi->dev.base_addr, offset_from_base, buf,
length);
return kDifOk;
}
dif_result_t dif_spi_device_write_flash_buffer(
dif_spi_device_handle_t *spi,
dif_spi_device_flash_buffer_type_t buffer_type, uint32_t offset,
size_t length, const uint8_t *buf) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
dif_spi_device_flash_buffer_info_t info;
dif_result_t status =
dif_spi_device_get_flash_buffer_info(buffer_type, &info);
if (status != kDifOk) {
return status;
}
if (offset >= (info.buffer_offset + info.buffer_len) ||
length > (info.buffer_offset + info.buffer_len - offset)) {
return kDifBadArg;
}
ptrdiff_t offset_from_base =
SPI_DEVICE_BUFFER_REG_OFFSET + info.buffer_offset + offset;
mmio_region_memcpy_to_mmio32(spi->dev.base_addr, offset_from_base, buf,
length);
return kDifOk;
}
dif_result_t dif_spi_device_get_passthrough_command_filter(
dif_spi_device_handle_t *spi, uint8_t command, dif_toggle_t *enabled) {
if (spi == NULL || enabled == NULL) {
return kDifBadArg;
}
ptrdiff_t reg_offset =
SPI_DEVICE_CMD_FILTER_0_REG_OFFSET + (command >> 5) * sizeof(uint32_t);
uint32_t command_index = command & 0x1fu;
uint32_t reg_value = mmio_region_read32(spi->dev.base_addr, reg_offset);
bool filtered = bitfield_bit32_read(reg_value, command_index);
if (filtered) {
*enabled = kDifToggleEnabled;
} else {
*enabled = kDifToggleDisabled;
}
return kDifOk;
}
dif_result_t dif_spi_device_set_passthrough_command_filter(
dif_spi_device_handle_t *spi, uint8_t command, dif_toggle_t enabled) {
if (spi == NULL || !dif_is_valid_toggle(enabled)) {
return kDifBadArg;
}
bool enable_filter = dif_toggle_to_bool(enabled);
ptrdiff_t reg_offset =
SPI_DEVICE_CMD_FILTER_0_REG_OFFSET + (command >> 5) * sizeof(uint32_t);
uint32_t command_index = command & 0x1fu;
uint32_t reg_value = mmio_region_read32(spi->dev.base_addr, reg_offset);
reg_value = bitfield_bit32_write(reg_value, command_index, enable_filter);
mmio_region_write32(spi->dev.base_addr, reg_offset, reg_value);
return kDifOk;
}
dif_result_t dif_spi_device_set_all_passthrough_command_filters(
dif_spi_device_handle_t *spi, dif_toggle_t enable) {
if (spi == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
uint32_t reg_value = dif_toggle_to_bool(enable) ? UINT32_MAX : 0;
for (int i = 0; i < SPI_DEVICE_CMD_FILTER_MULTIREG_COUNT; i++) {
uint32_t reg_offset =
SPI_DEVICE_CMD_FILTER_0_REG_OFFSET + i * sizeof(uint32_t);
mmio_region_write32(spi->dev.base_addr, reg_offset, reg_value);
}
return kDifOk;
}
dif_result_t dif_spi_device_clear_flash_busy_bit(dif_spi_device_handle_t *spi) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_FLASH_STATUS_REG_OFFSET);
reg_val =
bitfield_bit32_write(reg_val, kDifSpiDeviceFlashStatusWelBit, false);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_FLASH_STATUS_BUSY_BIT, false);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_FLASH_STATUS_REG_OFFSET,
reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_set_flash_status_registers(
dif_spi_device_handle_t *spi, uint32_t value) {
if (spi == NULL) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_FLASH_STATUS_REG_OFFSET,
value);
return kDifOk;
}
dif_result_t dif_spi_device_get_flash_status_registers(
dif_spi_device_handle_t *spi, uint32_t *value) {
if (spi == NULL || value == NULL) {
return kDifBadArg;
}
*value = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_FLASH_STATUS_REG_OFFSET);
return kDifOk;
}
dif_result_t dif_spi_device_get_tpm_capabilities(
dif_spi_device_handle_t *spi, dif_spi_device_tpm_caps_t *caps) {
if (spi == NULL || caps == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_TPM_CAP_REG_OFFSET);
caps->revision = bitfield_field32_read(reg_val, SPI_DEVICE_TPM_CAP_REV_FIELD);
caps->multi_locality =
bitfield_bit32_read(reg_val, SPI_DEVICE_TPM_CAP_LOCALITY_BIT);
caps->max_write_size =
bitfield_field32_read(reg_val, SPI_DEVICE_TPM_CAP_MAX_WR_SIZE_FIELD);
caps->max_read_size =
bitfield_field32_read(reg_val, SPI_DEVICE_TPM_CAP_MAX_RD_SIZE_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_configure(dif_spi_device_handle_t *spi,
dif_toggle_t enable,
dif_spi_device_tpm_config_t config) {
if (spi == NULL || !dif_is_valid_toggle(enable)) {
return kDifBadArg;
}
bool tpm_en = dif_toggle_to_bool(enable);
uint32_t reg_val = bitfield_bit32_write(0, SPI_DEVICE_TPM_CFG_EN_BIT, tpm_en);
if (tpm_en) {
bool use_crb;
switch (config.interface) {
case kDifSpiDeviceTpmInterfaceFifo:
use_crb = false;
break;
case kDifSpiDeviceTpmInterfaceCrb:
use_crb = true;
break;
default:
return kDifBadArg;
}
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_TPM_CFG_TPM_MODE_BIT, use_crb);
reg_val = bitfield_bit32_write(reg_val, SPI_DEVICE_TPM_CFG_HW_REG_DIS_BIT,
config.disable_return_by_hardware);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_TPM_CFG_TPM_REG_CHK_DIS_BIT,
config.disable_address_prefix_check);
reg_val =
bitfield_bit32_write(reg_val, SPI_DEVICE_TPM_CFG_INVALID_LOCALITY_BIT,
config.disable_locality_check);
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_TPM_CFG_REG_OFFSET,
reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_get_data_status(
dif_spi_device_handle_t *spi, dif_spi_device_tpm_data_status_t *status) {
if (spi == NULL || status == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_TPM_STATUS_REG_OFFSET);
status->cmd_addr_valid =
bitfield_bit32_read(reg_val, SPI_DEVICE_TPM_STATUS_CMDADDR_NOTEMPTY_BIT);
status->write_fifo_occupancy =
bitfield_field32_read(reg_val, SPI_DEVICE_TPM_STATUS_WRFIFO_DEPTH_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_set_access_reg(dif_spi_device_handle_t *spi,
uint8_t locality,
uint8_t value) {
if (spi == NULL || locality >= SPI_DEVICE_PARAM_NUM_LOCALITY) {
return kDifBadArg;
}
// There is one 8-bit TPM_ACCESS register per locality, but bus accesses are
// 32 bits.
ptrdiff_t reg_offset = SPI_DEVICE_TPM_ACCESS_0_REG_OFFSET + (locality & 0xfc);
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr, reg_offset);
switch (locality & 0x03) {
case 0:
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_TPM_ACCESS_0_ACCESS_0_FIELD, value);
break;
case 1:
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_TPM_ACCESS_0_ACCESS_1_FIELD, value);
break;
case 2:
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_TPM_ACCESS_0_ACCESS_2_FIELD, value);
break;
case 3:
reg_val = bitfield_field32_write(
reg_val, SPI_DEVICE_TPM_ACCESS_0_ACCESS_3_FIELD, value);
break;
default:
break;
}
mmio_region_write32(spi->dev.base_addr, reg_offset, reg_val);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_get_access_reg(dif_spi_device_handle_t *spi,
uint8_t locality,
uint8_t *value) {
if (spi == NULL || locality >= SPI_DEVICE_PARAM_NUM_LOCALITY ||
value == NULL) {
return kDifBadArg;
}
// There is one 8-bit TPM_ACCESS register per locality, but bus accesses are
// 32 bits.
ptrdiff_t reg_offset = SPI_DEVICE_TPM_ACCESS_0_REG_OFFSET + (locality & 0xfc);
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr, reg_offset);
switch (locality & 0x03) {
case 0:
*value = bitfield_field32_read(reg_val,
SPI_DEVICE_TPM_ACCESS_0_ACCESS_0_FIELD);
break;
case 1:
*value = bitfield_field32_read(reg_val,
SPI_DEVICE_TPM_ACCESS_0_ACCESS_1_FIELD);
break;
case 2:
*value = bitfield_field32_read(reg_val,
SPI_DEVICE_TPM_ACCESS_0_ACCESS_2_FIELD);
break;
case 3:
*value = bitfield_field32_read(reg_val,
SPI_DEVICE_TPM_ACCESS_0_ACCESS_3_FIELD);
break;
default:
break;
}
return kDifOk;
}
/**
* Write a TPM register used with the return-by-hardware logic.
*
* @param spi A handle to a spi device.
* @param value The value to write.
* @param reg_offset The offset of the related CSR from the spi device's base.
* @return The result of the operation.
*/
static dif_result_t tpm_reg_write32(dif_spi_device_handle_t *spi,
uint32_t value, ptrdiff_t reg_offset) {
if (spi == NULL) {
return kDifBadArg;
}
mmio_region_write32(spi->dev.base_addr, reg_offset, value);
return kDifOk;
}
/**
* Read from a TPM register used with the return-by-hardware logic.
*
* @param spi A handle to a spi device.
* @param value The value read.
* @param reg_offset The offset of the related CSR from the spi device's base.
* @return The result of the operation.
*/
static dif_result_t tpm_reg_read32(dif_spi_device_handle_t *spi,
uint32_t *value, ptrdiff_t reg_offset) {
if (spi == NULL || value == NULL) {
return kDifBadArg;
}
*value = mmio_region_read32(spi->dev.base_addr, reg_offset);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_set_sts_reg(dif_spi_device_handle_t *spi,
uint32_t value) {
return tpm_reg_write32(spi, value, SPI_DEVICE_TPM_STS_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_get_sts_reg(dif_spi_device_handle_t *spi,
uint32_t *value) {
return tpm_reg_read32(spi, value, SPI_DEVICE_TPM_STS_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_set_intf_capability_reg(
dif_spi_device_handle_t *spi, uint32_t value) {
return tpm_reg_write32(spi, value, SPI_DEVICE_TPM_INTF_CAPABILITY_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_get_intf_capability_reg(
dif_spi_device_handle_t *spi, uint32_t *value) {
return tpm_reg_read32(spi, value, SPI_DEVICE_TPM_INTF_CAPABILITY_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_set_int_enable_reg(dif_spi_device_handle_t *spi,
uint32_t value) {
return tpm_reg_write32(spi, value, SPI_DEVICE_TPM_INT_ENABLE_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_get_int_enable_reg(dif_spi_device_handle_t *spi,
uint32_t *value) {
return tpm_reg_read32(spi, value, SPI_DEVICE_TPM_INT_ENABLE_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_set_int_vector_reg(dif_spi_device_handle_t *spi,
uint32_t value) {
return tpm_reg_write32(spi, value, SPI_DEVICE_TPM_INT_VECTOR_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_get_int_vector_reg(dif_spi_device_handle_t *spi,
uint32_t *value) {
return tpm_reg_read32(spi, value, SPI_DEVICE_TPM_INT_VECTOR_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_set_int_status_reg(dif_spi_device_handle_t *spi,
uint32_t value) {
return tpm_reg_write32(spi, value, SPI_DEVICE_TPM_INT_STATUS_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_get_int_status_reg(dif_spi_device_handle_t *spi,
uint32_t *value) {
return tpm_reg_read32(spi, value, SPI_DEVICE_TPM_INT_STATUS_REG_OFFSET);
}
dif_result_t dif_spi_device_tpm_set_id(dif_spi_device_handle_t *spi,
dif_spi_device_tpm_id_t id) {
if (spi == NULL) {
return kDifBadArg;
}
uint32_t reg_val;
reg_val =
bitfield_field32_write(0, SPI_DEVICE_TPM_DID_VID_VID_FIELD, id.vendor_id);
reg_val = bitfield_field32_write(reg_val, SPI_DEVICE_TPM_DID_VID_DID_FIELD,
id.device_id);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_TPM_DID_VID_REG_OFFSET,
reg_val);
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_TPM_RID_REG_OFFSET,
id.revision);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_get_id(dif_spi_device_handle_t *spi,
dif_spi_device_tpm_id_t *value) {
if (spi == NULL || value == NULL) {
return kDifBadArg;
}
uint32_t did_vid =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_TPM_DID_VID_REG_OFFSET);
uint32_t rid =
mmio_region_read32(spi->dev.base_addr, SPI_DEVICE_TPM_RID_REG_OFFSET);
value->vendor_id =
bitfield_field32_read(did_vid, SPI_DEVICE_TPM_DID_VID_VID_FIELD);
value->device_id =
bitfield_field32_read(did_vid, SPI_DEVICE_TPM_DID_VID_DID_FIELD);
value->revision = bitfield_field32_read(rid, SPI_DEVICE_TPM_RID_RID_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_get_command(dif_spi_device_handle_t *spi,
uint8_t *command,
uint32_t *address) {
if (spi == NULL || command == NULL || address == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(spi->dev.base_addr,
SPI_DEVICE_TPM_CMD_ADDR_REG_OFFSET);
*command = bitfield_field32_read(reg_val, SPI_DEVICE_TPM_CMD_ADDR_CMD_FIELD);
*address = bitfield_field32_read(reg_val, SPI_DEVICE_TPM_CMD_ADDR_ADDR_FIELD);
return kDifOk;
}
dif_result_t dif_spi_device_tpm_write_data(dif_spi_device_handle_t *spi,
size_t length, uint8_t *buf) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
dif_spi_device_tpm_data_status_t status;
dif_result_t result = dif_spi_device_tpm_get_data_status(spi, &status);
uint8_t offset = length & 0x3; // lower two bits of length
uint32_t rdfifo_wdata;
if (result != kDifOk) {
return result;
}
// TODO: Ensure the received length is greater than FIFO SIZE
if (DIF_SPI_DEVICE_TPM_FIFO_DEPTH * sizeof(uint32_t) < length) {
return kDifOutOfRange;
}
for (int i = 0; i < length; i += 4) {
if (i + 4 > length) {
// Send partial
rdfifo_wdata = 0;
for (int j = 0; j <= offset; j++) {
rdfifo_wdata |= buf[i + j] << (8 * j);
}
} else {
// Type casting to uint32_t then fetch
rdfifo_wdata = *((uint32_t *)buf + (i >> 2));
}
mmio_region_write32(spi->dev.base_addr, SPI_DEVICE_TPM_READ_FIFO_REG_OFFSET,
rdfifo_wdata);
}
return kDifOk;
}
dif_result_t dif_spi_device_tpm_read_data(dif_spi_device_handle_t *spi,
size_t length, uint8_t *buf) {
if (spi == NULL || buf == NULL) {
return kDifBadArg;
}
dif_spi_device_tpm_data_status_t status;
dif_result_t result = dif_spi_device_tpm_get_data_status(spi, &status);
if (result != kDifOk) {
return result;
}
if (status.write_fifo_occupancy < length) {
return kDifOutOfRange;
}
for (int i = 0; i < length; i++) {
uint32_t fifo_val = mmio_region_read32(
spi->dev.base_addr, SPI_DEVICE_TPM_WRITE_FIFO_REG_OFFSET);
buf[i] =
bitfield_field32_read(fifo_val, SPI_DEVICE_TPM_WRITE_FIFO_VALUE_FIELD);
}
return kDifOk;
}