|  | // 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 "spi_device_regs.h"  // Generated. | 
|  |  | 
|  | const uint16_t kDifSpiDeviceBufferLen = SPI_DEVICE_BUFFER_SIZE_BYTES; | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_init(dif_spi_device_params_t params, | 
|  | dif_spi_device_t *spi) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | // This ensures all other fields are zeroed. | 
|  | *spi = (dif_spi_device_t){.params = params}; | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Computes the required value of the control register from a given | 
|  | * configuration. | 
|  | */ | 
|  | static uint32_t build_control_word(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); | 
|  | val = bitfield_field32_write(val, SPI_DEVICE_CFG_TIMER_V_FIELD, | 
|  | config.rx_fifo_timeout); | 
|  |  | 
|  | return val; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_configure( | 
|  | dif_spi_device_t *spi, dif_spi_device_config_t config) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | // NOTE: we do not write to any registers until performing all | 
|  | // function argument checks, to avoid a halfway-configured SPI. | 
|  |  | 
|  | uint32_t device_config = build_control_word(config); | 
|  |  | 
|  | uint16_t rx_fifo_start = 0x0; | 
|  | uint16_t rx_fifo_end = config.rx_fifo_len - 1; | 
|  | uint16_t tx_fifo_start = rx_fifo_end + 1; | 
|  | uint16_t tx_fifo_end = tx_fifo_start + config.tx_fifo_len - 1; | 
|  | if (tx_fifo_end >= kDifSpiDeviceBufferLen) { | 
|  | // We've overflown the SRAM region... | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | spi->rx_fifo_len = config.rx_fifo_len; | 
|  | spi->tx_fifo_len = config.tx_fifo_len; | 
|  |  | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_CFG_REG_OFFSET, | 
|  | device_config); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_RXF_ADDR_REG_OFFSET, | 
|  | rx_fifo_bounds); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_TXF_ADDR_REG_OFFSET, | 
|  | tx_fifo_bounds); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_abort(const dif_spi_device_t *spi) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | // Set the `abort` bit, and then spin until `abort_done` is asserted. | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi->params.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET); | 
|  | reg = bitfield_bit32_write(reg, SPI_DEVICE_CONTROL_ABORT_BIT, true); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_CONTROL_REG_OFFSET, | 
|  | reg); | 
|  |  | 
|  | while (true) { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi->params.base_addr, SPI_DEVICE_STATUS_REG_OFFSET); | 
|  | if (bitfield_bit32_read(reg, SPI_DEVICE_STATUS_ABORT_DONE_BIT)) { | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | DIF_WARN_UNUSED_RESULT | 
|  | static bool irq_index(dif_spi_device_irq_t irq, bitfield_bit32_index_t *index) { | 
|  | switch (irq) { | 
|  | case kDifSpiDeviceIrqRxFull: | 
|  | *index = SPI_DEVICE_INTR_COMMON_RXF_BIT; | 
|  | break; | 
|  | case kDifSpiDeviceIrqRxAboveLevel: | 
|  | *index = SPI_DEVICE_INTR_COMMON_RXLVL_BIT; | 
|  | break; | 
|  | case kDifSpiDeviceIrqTxBelowLevel: | 
|  | *index = SPI_DEVICE_INTR_COMMON_TXLVL_BIT; | 
|  | break; | 
|  | case kDifSpiDeviceIrqRxError: | 
|  | *index = SPI_DEVICE_INTR_COMMON_RXERR_BIT; | 
|  | break; | 
|  | case kDifSpiDeviceIrqRxOverflow: | 
|  | *index = SPI_DEVICE_INTR_COMMON_RXOVERFLOW_BIT; | 
|  | break; | 
|  | case kDifSpiDeviceIrqTxUnderflow: | 
|  | *index = SPI_DEVICE_INTR_COMMON_TXUNDERFLOW_BIT; | 
|  | break; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_is_pending( | 
|  | const dif_spi_device_t *spi, dif_spi_device_irq_t irq, bool *is_pending) { | 
|  | if (spi == NULL || is_pending == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t index; | 
|  | if (!irq_index(irq, &index)) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(spi->params.base_addr, | 
|  | SPI_DEVICE_INTR_STATE_REG_OFFSET); | 
|  | *is_pending = bitfield_bit32_read(reg, index); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_acknowledge( | 
|  | const dif_spi_device_t *spi, dif_spi_device_irq_t irq) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t index; | 
|  | if (!irq_index(irq, &index)) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = bitfield_bit32_write(0, index, true); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_INTR_STATE_REG_OFFSET, | 
|  | reg); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_get_enabled( | 
|  | const dif_spi_device_t *spi, dif_spi_device_irq_t irq, | 
|  | dif_spi_device_toggle_t *state) { | 
|  | if (spi == NULL || state == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t index; | 
|  | if (!irq_index(irq, &index)) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(spi->params.base_addr, | 
|  | SPI_DEVICE_INTR_ENABLE_REG_OFFSET); | 
|  | *state = bitfield_bit32_read(reg, index) ? kDifSpiDeviceToggleEnabled | 
|  | : kDifSpiDeviceToggleDisabled; | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_set_enabled( | 
|  | const dif_spi_device_t *spi, dif_spi_device_irq_t irq, | 
|  | dif_spi_device_toggle_t state) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t index; | 
|  | if (!irq_index(irq, &index)) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bool flag; | 
|  | switch (state) { | 
|  | case kDifSpiDeviceToggleEnabled: | 
|  | flag = true; | 
|  | break; | 
|  | case kDifSpiDeviceToggleDisabled: | 
|  | flag = false; | 
|  | break; | 
|  | default: | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = mmio_region_read32(spi->params.base_addr, | 
|  | SPI_DEVICE_INTR_ENABLE_REG_OFFSET); | 
|  | reg = bitfield_bit32_write(reg, index, flag); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_INTR_ENABLE_REG_OFFSET, | 
|  | reg); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_force(const dif_spi_device_t *spi, | 
|  | dif_spi_device_irq_t irq) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | bitfield_bit32_index_t index; | 
|  | if (!irq_index(irq, &index)) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = bitfield_bit32_write(0, index, true); | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_INTR_TEST_REG_OFFSET, | 
|  | reg); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_disable_all( | 
|  | const dif_spi_device_t *spi, dif_spi_device_irq_snapshot_t *snapshot) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | if (snapshot != NULL) { | 
|  | *snapshot = mmio_region_read32(spi->params.base_addr, | 
|  | SPI_DEVICE_INTR_ENABLE_REG_OFFSET); | 
|  | } | 
|  |  | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_INTR_ENABLE_REG_OFFSET, | 
|  | 0); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_irq_restore_all( | 
|  | const dif_spi_device_t *spi, | 
|  | const dif_spi_device_irq_snapshot_t *snapshot) { | 
|  | if (spi == NULL || snapshot == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | mmio_region_write32(spi->params.base_addr, SPI_DEVICE_INTR_ENABLE_REG_OFFSET, | 
|  | *snapshot); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_set_irq_levels( | 
|  | const dif_spi_device_t *spi, uint16_t rx_level, uint16_t tx_level) { | 
|  | if (spi == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | 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->params.base_addr, SPI_DEVICE_FIFO_LEVEL_REG_OFFSET, | 
|  | compressed_limit); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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 << 11); | 
|  | static const uint16_t kFifoOffsetMask = (1 << 11) - 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 poitner 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_t *spi, | 
|  | fifo_ptr_params_t params) { | 
|  | uint32_t ptr = mmio_region_read32(spi->params.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(const dif_spi_device_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->params.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_spi_device_result_t dif_spi_device_rx_pending(const dif_spi_device_t *spi, | 
|  | size_t *bytes_pending) { | 
|  | if (spi == NULL || bytes_pending == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | fifo_ptrs_t ptrs = decompress_ptrs(spi, kRxFifoParams); | 
|  | *bytes_pending = fifo_bytes_in_use(ptrs, spi->rx_fifo_len); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_tx_pending(const dif_spi_device_t *spi, | 
|  | size_t *bytes_pending) { | 
|  | if (spi == NULL || bytes_pending == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | fifo_ptrs_t ptrs = decompress_ptrs(spi, kTxFifoParams); | 
|  | *bytes_pending = fifo_bytes_in_use(ptrs, spi->tx_fifo_len); | 
|  |  | 
|  | return kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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(const dif_spi_device_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->params.base_addr, mmio_offset, | 
|  | byte_buf, bytes_to_copy); | 
|  | } else { | 
|  | // `byte_buf` -> SPI device buffer | 
|  | mmio_region_memcpy_to_mmio32(spi->params.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_spi_device_result_t dif_spi_device_recv(const dif_spi_device_t *spi, | 
|  | void *buf, size_t buf_len, | 
|  | size_t *bytes_received) { | 
|  | if (spi == NULL || buf == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | uint16_t fifo_base = 0; | 
|  | uint16_t fifo_len = spi->rx_fifo_len; | 
|  | fifo_ptrs_t fifo = decompress_ptrs(spi, kRxFifoParams); | 
|  |  | 
|  | size_t bytes = spi_memcpy(spi, &fifo, fifo_base, 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 kDifSpiDeviceOk; | 
|  | } | 
|  |  | 
|  | dif_spi_device_result_t dif_spi_device_send(const dif_spi_device_t *spi, | 
|  | const void *buf, size_t buf_len, | 
|  | size_t *bytes_sent) { | 
|  | if (spi == NULL || buf == NULL) { | 
|  | return kDifSpiDeviceBadArg; | 
|  | } | 
|  |  | 
|  | // Start of the TX FIFO is the end of the RX FIFO. | 
|  | uint16_t fifo_base = spi->rx_fifo_len; | 
|  | uint16_t fifo_len = spi->tx_fifo_len; | 
|  | fifo_ptrs_t fifo = decompress_ptrs(spi, kTxFifoParams); | 
|  |  | 
|  | size_t bytes = spi_memcpy(spi, &fifo, fifo_base, 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 kDifSpiDeviceOk; | 
|  | } |