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