|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | #include "sw/device/lib/dif/dif_spi_host.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stdalign.h> | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include "sw/device/lib/base/bitfield.h" | 
|  | #include "sw/device/lib/base/memory.h" | 
|  | #include "sw/device/lib/base/mmio.h" | 
|  |  | 
|  | #include "spi_host_regs.h"  // Generated. | 
|  |  | 
|  | // We create weak symbol aliases for the FIFO write and read functions so the | 
|  | // unit tests can provide mocks.  The mocks provide for separate testing of | 
|  | // the FIFO functions and the overall transaction management functions. | 
|  | OT_WEAK | 
|  | OT_ALIAS("dif_spi_host_fifo_write") | 
|  | dif_result_t spi_host_fifo_write_alias(const dif_spi_host_t *spi_host, | 
|  | const void *src, uint16_t len); | 
|  |  | 
|  | OT_WEAK | 
|  | OT_ALIAS("dif_spi_host_fifo_read") | 
|  | dif_result_t spi_host_fifo_read_alias(const dif_spi_host_t *spi_host, void *dst, | 
|  | uint16_t len); | 
|  |  | 
|  | static void spi_host_reset(const dif_spi_host_t *spi_host) { | 
|  | // Set the software reset request bit. | 
|  | mmio_region_write32( | 
|  | spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET, | 
|  | bitfield_bit32_write(0, SPI_HOST_CONTROL_SW_RST_BIT, true)); | 
|  |  | 
|  | // Wait for the spi host to go inactive. | 
|  | bool active; | 
|  | do { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET); | 
|  | active = bitfield_bit32_read(reg, SPI_HOST_STATUS_ACTIVE_BIT); | 
|  | } while (active); | 
|  |  | 
|  | // Wait for the spi host fifos to drain. | 
|  | uint32_t txqd, rxqd; | 
|  | do { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET); | 
|  | txqd = bitfield_field32_read(reg, SPI_HOST_STATUS_TXQD_FIELD); | 
|  | rxqd = bitfield_field32_read(reg, SPI_HOST_STATUS_RXQD_FIELD); | 
|  | } while (txqd != 0 || rxqd != 0); | 
|  |  | 
|  | // Clear the software reset request bit. | 
|  | mmio_region_write32( | 
|  | spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET, | 
|  | bitfield_bit32_write(0, SPI_HOST_CONTROL_SW_RST_BIT, false)); | 
|  | } | 
|  |  | 
|  | static void spi_host_enable(const dif_spi_host_t *spi_host, bool enable) { | 
|  | mmio_region_write32( | 
|  | spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET, | 
|  | bitfield_bit32_write(0, SPI_HOST_CONTROL_SPIEN_BIT, enable)); | 
|  | } | 
|  |  | 
|  | dif_result_t dif_spi_host_configure(const dif_spi_host_t *spi_host, | 
|  | dif_spi_host_config_t config) { | 
|  | if (spi_host == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  | if (config.peripheral_clock_freq_hz == 0 || config.spi_clock == 0) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t divider = | 
|  | ((config.peripheral_clock_freq_hz / config.spi_clock) / 2) - 1; | 
|  | if (divider & ~SPI_HOST_CONFIGOPTS_CLKDIV_0_MASK) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | spi_host_reset(spi_host); | 
|  | uint32_t reg = 0; | 
|  | reg = | 
|  | bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CLKDIV_0_FIELD, divider); | 
|  | reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNIDLE_0_FIELD, | 
|  | config.chip_select.idle); | 
|  | reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNTRAIL_0_FIELD, | 
|  | config.chip_select.trail); | 
|  | reg = bitfield_field32_write(reg, SPI_HOST_CONFIGOPTS_CSNLEAD_0_FIELD, | 
|  | config.chip_select.lead); | 
|  | reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_FULLCYC_0_BIT, | 
|  | config.full_cycle); | 
|  | reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_CPHA_0_BIT, config.cpha); | 
|  | reg = bitfield_bit32_write(reg, SPI_HOST_CONFIGOPTS_CPOL_0_BIT, config.cpol); | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_CONFIGOPTS_REG_OFFSET, reg); | 
|  | spi_host_enable(spi_host, true); | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_spi_host_output_set_enabled(const dif_spi_host_t *spi_host, | 
|  | bool enabled) { | 
|  | if (spi_host == NULL) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET); | 
|  | mmio_region_write32( | 
|  | spi_host->base_addr, SPI_HOST_CONTROL_REG_OFFSET, | 
|  | bitfield_bit32_write(reg, SPI_HOST_CONTROL_OUTPUT_EN_BIT, enabled)); | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | static void wait_ready(const dif_spi_host_t *spi_host) { | 
|  | bool ready; | 
|  | do { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET); | 
|  | ready = bitfield_bit32_read(reg, SPI_HOST_STATUS_READY_BIT); | 
|  | } while (!ready); | 
|  | } | 
|  |  | 
|  | static void wait_tx_fifo(const dif_spi_host_t *spi_host) { | 
|  | uint32_t txqd; | 
|  | do { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET); | 
|  | txqd = bitfield_field32_read(reg, SPI_HOST_STATUS_TXQD_FIELD); | 
|  | } while (txqd == SPI_HOST_PARAM_TX_DEPTH); | 
|  | } | 
|  |  | 
|  | static void wait_rx_fifo(const dif_spi_host_t *spi_host) { | 
|  | uint32_t rxqd; | 
|  | do { | 
|  | uint32_t reg = | 
|  | mmio_region_read32(spi_host->base_addr, SPI_HOST_STATUS_REG_OFFSET); | 
|  | rxqd = bitfield_field32_read(reg, SPI_HOST_STATUS_RXQD_FIELD); | 
|  | } while (rxqd == 0); | 
|  | } | 
|  |  | 
|  | static inline void tx_fifo_write8(const dif_spi_host_t *spi_host, | 
|  | uintptr_t srcaddr) { | 
|  | uint8_t *src = (uint8_t *)srcaddr; | 
|  | wait_tx_fifo(spi_host); | 
|  | mmio_region_write8(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, *src); | 
|  | } | 
|  |  | 
|  | static inline void tx_fifo_write32(const dif_spi_host_t *spi_host, | 
|  | uintptr_t srcaddr) { | 
|  | wait_tx_fifo(spi_host); | 
|  | uint32_t val = read_32((const void *)srcaddr); | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, val); | 
|  | } | 
|  |  | 
|  | dif_result_t dif_spi_host_fifo_write(const dif_spi_host_t *spi_host, | 
|  | const void *src, uint16_t len) { | 
|  | uintptr_t ptr = (uintptr_t)src; | 
|  | if (spi_host == NULL || (src == NULL && len > 0)) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | // If the pointer starts mis-aligned, write until we are aligned. | 
|  | while (misalignment32_of(ptr) && len > 0) { | 
|  | tx_fifo_write8(spi_host, ptr); | 
|  | ptr += 1; | 
|  | len -= 1; | 
|  | } | 
|  |  | 
|  | // Write complete 32-bit words to the fifo. | 
|  | while (len > 3) { | 
|  | tx_fifo_write32(spi_host, ptr); | 
|  | ptr += 4; | 
|  | len -= 4; | 
|  | } | 
|  |  | 
|  | // Clean up any leftover bytes. | 
|  | while (len > 0) { | 
|  | tx_fifo_write8(spi_host, ptr); | 
|  | ptr += 1; | 
|  | len -= 1; | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | typedef struct queue { | 
|  | int32_t length; | 
|  | uint8_t alignas(uint64_t) data[8]; | 
|  | } queue_t; | 
|  |  | 
|  | static void enqueue_byte(queue_t *queue, uint8_t data) { | 
|  | queue->data[queue->length++] = data; | 
|  | } | 
|  |  | 
|  | static void enqueue_word(queue_t *queue, uint32_t data) { | 
|  | if (queue->length % sizeof(uint32_t) == 0) { | 
|  | write_32(data, queue->data + queue->length); | 
|  | queue->length += 4; | 
|  | } else { | 
|  | for (size_t i = 0; i < sizeof(uint32_t); ++i) { | 
|  | enqueue_byte(queue, (uint8_t)data); | 
|  | data >>= 8; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint8_t dequeue_byte(queue_t *queue) { | 
|  | uint8_t val = queue->data[0]; | 
|  | uint64_t qword = read_64(queue->data); | 
|  | write_64(qword >> 8, queue->data); | 
|  | queue->length -= 1; | 
|  | return val; | 
|  | } | 
|  |  | 
|  | static uint32_t dequeue_word(queue_t *queue) { | 
|  | uint32_t val = read_32(queue->data); | 
|  | write_32(read_32(queue->data + sizeof(uint32_t)), queue->data); | 
|  | queue->length -= 4; | 
|  | return val; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_spi_host_fifo_read(const dif_spi_host_t *spi_host, void *dst, | 
|  | uint16_t len) { | 
|  | if (spi_host == NULL || (dst == NULL && len > 0)) { | 
|  | return kDifBadArg; | 
|  | } | 
|  |  | 
|  | uintptr_t ptr = (uintptr_t)dst; | 
|  | // We always have to read from the RXFIFO as a 32-bit word.  We use a | 
|  | // two-word queue to handle destination and length mis-alignments. | 
|  | queue_t queue = {0}; | 
|  |  | 
|  | // If the buffer is misaligned, write a byte at a time until we reach | 
|  | // alignment. | 
|  | while (misalignment32_of(ptr) && len > 0) { | 
|  | if (queue.length < 1) { | 
|  | wait_rx_fifo(spi_host); | 
|  | enqueue_word(&queue, mmio_region_read32(spi_host->base_addr, | 
|  | SPI_HOST_RXDATA_REG_OFFSET)); | 
|  | } | 
|  | uint8_t *p = (uint8_t *)ptr; | 
|  | *p = dequeue_byte(&queue); | 
|  | ptr += 1; | 
|  | len -= 1; | 
|  | } | 
|  |  | 
|  | // While we can write complete words to memory, operate on 4 bytes at a time. | 
|  | while (len > 3) { | 
|  | if (queue.length < 4) { | 
|  | wait_rx_fifo(spi_host); | 
|  | enqueue_word(&queue, mmio_region_read32(spi_host->base_addr, | 
|  | SPI_HOST_RXDATA_REG_OFFSET)); | 
|  | } | 
|  | write_32(dequeue_word(&queue), (void *)ptr); | 
|  | ptr += 4; | 
|  | len -= 4; | 
|  | } | 
|  |  | 
|  | // Finish up any left over buffer a byte at a time. | 
|  | while (len > 0) { | 
|  | if (queue.length < 1) { | 
|  | wait_rx_fifo(spi_host); | 
|  | enqueue_word(&queue, mmio_region_read32(spi_host->base_addr, | 
|  | SPI_HOST_RXDATA_REG_OFFSET)); | 
|  | } | 
|  | uint8_t *p = (uint8_t *)ptr; | 
|  | *p = dequeue_byte(&queue); | 
|  | ptr += 1; | 
|  | len -= 1; | 
|  | } | 
|  |  | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | static void write_command_reg(const dif_spi_host_t *spi_host, uint16_t length, | 
|  | dif_spi_host_width_t speed, | 
|  | dif_spi_host_direction_t direction, | 
|  | bool last_segment) { | 
|  | uint32_t reg = 0; | 
|  | reg = bitfield_field32_write(reg, SPI_HOST_COMMAND_LEN_FIELD, length - 1); | 
|  | reg = bitfield_field32_write(reg, SPI_HOST_COMMAND_SPEED_FIELD, speed); | 
|  | reg = | 
|  | bitfield_field32_write(reg, SPI_HOST_COMMAND_DIRECTION_FIELD, direction); | 
|  | reg = bitfield_bit32_write(reg, SPI_HOST_COMMAND_CSAAT_BIT, !last_segment); | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_COMMAND_REG_OFFSET, reg); | 
|  | } | 
|  |  | 
|  | static void issue_opcode(const dif_spi_host_t *spi_host, | 
|  | dif_spi_host_segment_t *segment, bool last_segment) { | 
|  | wait_tx_fifo(spi_host); | 
|  | mmio_region_write8(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, | 
|  | segment->opcode); | 
|  | write_command_reg(spi_host, 1, kDifSpiHostWidthStandard, | 
|  | kDifSpiHostDirectionTx, last_segment); | 
|  | } | 
|  |  | 
|  | static void issue_address(const dif_spi_host_t *spi_host, | 
|  | dif_spi_host_segment_t *segment, bool last_segment) { | 
|  | wait_tx_fifo(spi_host); | 
|  | // The address appears on the wire in big-endian order. | 
|  | uint32_t address = bitfield_byteswap32(segment->address.address); | 
|  | uint32_t length; | 
|  | if (segment->address.mode == kDifSpiHostAddrMode4b) { | 
|  | length = 4; | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, | 
|  | address); | 
|  | } else { | 
|  | length = 3; | 
|  | address >>= 8; | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_TXDATA_REG_OFFSET, | 
|  | address); | 
|  | } | 
|  | write_command_reg(spi_host, length, segment->address.width, | 
|  | kDifSpiHostDirectionTx, last_segment); | 
|  | } | 
|  |  | 
|  | static void issue_dummy(const dif_spi_host_t *spi_host, | 
|  | dif_spi_host_segment_t *segment, bool last_segment) { | 
|  | write_command_reg(spi_host, segment->dummy.length, segment->dummy.width, | 
|  | kDifSpiHostDirectionDummy, last_segment); | 
|  | } | 
|  |  | 
|  | static dif_result_t issue_data_phase(const dif_spi_host_t *spi_host, | 
|  | dif_spi_host_segment_t *segment, | 
|  | bool last_segment) { | 
|  | size_t length; | 
|  | dif_spi_host_width_t width; | 
|  | dif_spi_host_direction_t direction; | 
|  |  | 
|  | switch (segment->type) { | 
|  | case kDifSpiHostSegmentTypeTx: | 
|  | width = segment->tx.width; | 
|  | length = segment->tx.length; | 
|  | direction = kDifSpiHostDirectionTx; | 
|  | spi_host_fifo_write_alias(spi_host, segment->tx.buf, segment->tx.length); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeBidirectional: | 
|  | width = segment->bidir.width; | 
|  | length = segment->bidir.length; | 
|  | direction = kDifSpiHostDirectionBidirectional; | 
|  | spi_host_fifo_write_alias(spi_host, segment->bidir.txbuf, | 
|  | segment->bidir.length); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeRx: | 
|  | width = segment->rx.width; | 
|  | length = segment->rx.length; | 
|  | direction = kDifSpiHostDirectionRx; | 
|  | break; | 
|  | default: | 
|  | // Programming error (within this file).  We should never get here. | 
|  | // `issue_data_phase` should only get called for segment types which | 
|  | // represent a data transfer. | 
|  | return kDifBadArg; | 
|  | } | 
|  | write_command_reg(spi_host, length, width, direction, last_segment); | 
|  | return kDifOk; | 
|  | } | 
|  |  | 
|  | dif_result_t dif_spi_host_transaction(const dif_spi_host_t *spi_host, | 
|  | uint32_t csid, | 
|  | dif_spi_host_segment_t *segments, | 
|  | size_t length) { | 
|  | // Write to chip select ID. | 
|  | mmio_region_write32(spi_host->base_addr, SPI_HOST_CSID_REG_OFFSET, csid); | 
|  |  | 
|  | // For each segment, write the segment information to the | 
|  | // COMMAND register and transmit FIFO. | 
|  | for (size_t i = 0; i < length; ++i) { | 
|  | bool last_segment = i == length - 1; | 
|  | wait_ready(spi_host); | 
|  | dif_spi_host_segment_t *segment = &segments[i]; | 
|  | switch (segment->type) { | 
|  | case kDifSpiHostSegmentTypeOpcode: | 
|  | issue_opcode(spi_host, segment, last_segment); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeAddress: | 
|  | issue_address(spi_host, segment, last_segment); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeDummy: | 
|  | issue_dummy(spi_host, segment, last_segment); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeTx: | 
|  | case kDifSpiHostSegmentTypeRx: | 
|  | case kDifSpiHostSegmentTypeBidirectional: { | 
|  | dif_result_t error = issue_data_phase(spi_host, segment, last_segment); | 
|  | if (error != kDifOk) { | 
|  | return error; | 
|  | } | 
|  | break; | 
|  | } | 
|  | default: | 
|  | return kDifBadArg; | 
|  | } | 
|  | } | 
|  |  | 
|  | // For each segment which receives data, read from the receive FIFO. | 
|  | for (size_t i = 0; i < length; ++i) { | 
|  | dif_spi_host_segment_t *segment = &segments[i]; | 
|  | switch (segment->type) { | 
|  | case kDifSpiHostSegmentTypeRx: | 
|  | spi_host_fifo_read_alias(spi_host, segment->rx.buf, segment->rx.length); | 
|  | break; | 
|  | case kDifSpiHostSegmentTypeBidirectional: | 
|  | spi_host_fifo_read_alias(spi_host, segment->bidir.rxbuf, | 
|  | segment->bidir.length); | 
|  | break; | 
|  | default: | 
|  | /* do nothing */; | 
|  | } | 
|  | } | 
|  | return kDifOk; | 
|  | } |