Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

836 lines
25 KiB

/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_cadence, CONFIG_SPI_LOG_LEVEL);
#include "spi_context.h"
/*******************************************************************************
* Macro Definition
******************************************************************************/
/* Register offset address */
#define SPI_CONF 0x00
#define SPI_INT_STATUS 0x04
#define SPI_INT_ENABLE 0x08
#define SPI_INT_DISABLE 0x0c
#define SPI_INT_MASK 0x10
#define SPI_SPI_ENABLE 0x14
#define SPI_DELAY 0x18
#define SPI_TX_DATA 0x1c
#define SPI_RX_DATA 0x20
#define SPI_SLAVE_IDLE_COUNT 0x24
#define SPI_TX_THRESHOLD 0x28
#define SPI_RX_THRESHOLD 0x2c
/* Configuration register bit offset */
#define SPI_CONF_PCSL_OFFSET 10
#define SPI_CONF_MRCS_OFFSET 8
#define SPI_CONF_TWS_OFFSET 6
#define SPI_CONF_MBRD_OFFSET 3
/* Configuration register bit mask */
#define SPI_CONF_TXCLR BIT(20)
#define SPI_CONF_RXCLR BIT(19)
#define SPI_CONF_SPSE BIT(18)
#define SPI_CONF_MFGE BIT(17)
#define SPI_CONF_MSC BIT(16)
#define SPI_CONF_MSE BIT(15)
#define SPI_CONF_MCSE BIT(14)
#define SPI_CONF_PCSL_MASK GENMASK(13, 10)
#define SPI_CONF_PSD BIT(9)
#define SPI_CONF_MRCS BIT(8)
#define SPI_CONF_TWS_MASK GENMASK(7, 6)
#define SPI_CONF_MBRD_MASK GENMASK(5, 3)
#define SPI_CONF_CPHA BIT(2)
#define SPI_CONF_CPOL BIT(1)
#define SPI_CONF_MSEL BIT(0)
#define SPI_CONF_INITIAL_VAL (SPI_CONF_PCSL_MASK | SPI_CONF_MCSE | SPI_CONF_MRCS | SPI_CONF_MSEL)
/* Interrupt register bit mask */
#define SPI_INT_TUF BIT(6)
#define SPI_INT_RF BIT(5)
#define SPI_INT_RNE BIT(4)
#define SPI_INT_TF BIT(3)
#define SPI_INT_TNF BIT(2)
#define SPI_INT_MF BIT(1)
#define SPI_INT_ROF BIT(0)
#define SPI_INT_DEFAULT (SPI_INT_RNE | SPI_INT_TNF | SPI_INT_ROF | SPI_INT_TUF)
/* SPI enable register bit offset */
#define SPI_SPI_ENABLE_SPIE BIT(0)
/* Maximum baud rate divisor */
#define SPI_MBRD_MIN 0
#define SPI_MBRD_MAX 7
#define SPI_FREQ_LIST_MAX ((SPI_MBRD_MAX + 1) * 2 + 1)
#define SPI_CFG(dev) ((struct spi_cdns_cfg *)(dev->config))
#define SPI_REG(dev, offset) ((mem_addr_t)(SPI_CFG(dev)->base + (offset)))
/*******************************************************************************
* Types Definition
******************************************************************************/
typedef void (*irq_config_func_t)(void);
/**
* @brief SPI Driver config information.
*
* This parameter isn't updated after initialization.
*
* @param base SPI register base address.
* @param clock_frequency Peripheral bus clock
* @param ext_clock External clock frequency.
* @param cs_setup_us Array of durations from CS assert to
* SCLK in us for slaves.
* @param cs_hold_us Array of durations from CS assert to
* SCLK in us for slaves.
* @param irq_config Interrupt configuration function.
* @param leave_enabled_during_config Conditionally enable or
* disable the SPI bus during
* configuration
* @param freq_list Selectable clock
* frequency list.
*/
struct spi_cdns_cfg {
uint32_t base;
uint32_t clock_frequency;
uint32_t ext_clock;
irq_config_func_t irq_config;
uint8_t fifo_width;
uint16_t rx_fifo_depth;
uint16_t tx_fifo_depth;
};
/**
* @brief SPI Driver private data.
*
* @param ctx Transceive context information.
* @param config Current SPI controller configuration structure
* @param freq Actual transfer frequency.
* @param tx_remain_entry Remain entries to Tx-FIFO.
* @param fifo_diff Difference between Tx-FIFO entry and Rx-FIFO entry.
*/
struct spi_cdns_data {
struct spi_context ctx;
struct spi_config config;
uint32_t freq;
uint32_t tx_remain_entry;
int32_t fifo_diff;
};
/*******************************************************************************
* Private Functions Code
******************************************************************************/
/**
* @brief Mask-write 32-bit value to register.
*
* @param addr register address.
* @param mask 32-bit Mask value.
* @param value 32-bit value to be written.
* @return None.
*/
static inline void sys_set_mask32(mem_addr_t addr, uint32_t mask, uint32_t value)
{
uint32_t temp = sys_read32(addr);
temp &= ~(mask);
temp |= value;
sys_write32(temp, addr);
}
/**
* @brief Check whether to update context configuration.
*
* @param dev Device structure (In memory) per driver instance
* @param config SPI controller configuration structure
* @retval true Configuration is same.
* @retval false Configuration is differ.
*/
static inline bool spi_cdns_context_configured(const struct device *dev,
const struct spi_config *config)
{
struct spi_cdns_data *data = dev->data;
if (spi_context_configured(&data->ctx, config) &&
(data->config.frequency == config->frequency) &&
(data->config.operation == config->operation) &&
(data->config.slave == config->slave)) {
return true;
}
return false;
}
/**
* @brief Enable/Disable SPI controller
*
* @param dev Device structure (In memory) per driver instance
* @param on SPI enable flag (True: Enable, False: Disable)
* @return None.
*/
static inline void spi_cdns_spi_enable(const struct device *dev, bool on)
{
if (on) {
sys_set_bits(SPI_REG(dev, SPI_SPI_ENABLE), SPI_SPI_ENABLE_SPIE);
} else {
sys_clear_bits(SPI_REG(dev, SPI_SPI_ENABLE), SPI_SPI_ENABLE_SPIE);
}
}
/**
* @brief Assert/Deassert chip select line
*
* @param dev Device structure (In memory) per driver instance
* @param on Assert chip select (True: Assert, False: Deassert)
* @return None.
*/
static inline void spi_cdns_cs_control(const struct device *dev, bool on)
{
struct spi_cdns_data *data = dev->data;
if (IS_ENABLED(CONFIG_SPI_SLAVE) && spi_context_is_slave(&data->ctx)) {
/* Skip slave select assert/de-assert in slave mode */
return;
}
if (on) {
uint32_t val = SPI_CONF_PCSL_MASK &
~(1 << (SPI_CONF_PCSL_OFFSET + data->ctx.config->slave));
sys_set_mask32(SPI_REG(dev, SPI_CONF), SPI_CONF_PCSL_MASK, val);
k_busy_wait(data->ctx.config->cs.delay);
} else if (!(data->ctx.config->operation & SPI_HOLD_ON_CS)) {
k_busy_wait(data->ctx.config->cs.delay);
sys_set_mask32(SPI_REG(dev, SPI_CONF), SPI_CONF_PCSL_MASK, SPI_CONF_PCSL_MASK);
}
}
/**
* @brief Send 1-entry to Tx-FIFO
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static void spi_cdns_send(const struct device *dev)
{
const struct spi_cdns_cfg *config = dev->config;
struct spi_cdns_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
uint8_t dfs = SPI_WORD_SIZE_GET(ctx->config->operation) / 8;
uint32_t val = 0;
int i, loop;
loop = (config->fifo_width / 8) / dfs;
for (i = 0; i < loop; i++) {
if (spi_context_tx_buf_on(ctx)) {
switch (dfs) {
case 1:
if (config->fifo_width == 8) {
val |= UNALIGNED_GET((uint8_t *)ctx->tx_buf);
} else if (config->fifo_width == 16) {
val |= UNALIGNED_GET((uint8_t *)ctx->tx_buf) << 8 * (1 - i);
} else if (config->fifo_width == 32) {
val |= UNALIGNED_GET((uint8_t *)ctx->tx_buf) << 8 * (3 - i);
}
break;
case 2:
if (config->fifo_width == 16) {
val |= UNALIGNED_GET((uint16_t *)ctx->tx_buf);
} else if (config->fifo_width == 32) {
val |= UNALIGNED_GET((uint16_t *)ctx->tx_buf)
<< 16 * (1 - i);
}
break;
case 4:
if (config->fifo_width == 32) {
val |= UNALIGNED_GET((uint32_t *)ctx->tx_buf);
}
break;
}
}
if ((spi_context_tx_buf_on(ctx) || spi_context_rx_buf_on(ctx))) {
if (data->tx_remain_entry > 0) {
data->tx_remain_entry--;
data->fifo_diff++;
}
}
spi_context_update_tx(&data->ctx, dfs, 1);
}
sys_write32(val, SPI_REG(dev, SPI_TX_DATA));
}
/**
* @brief Receive 1-entry from Rx-FIFO
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static void spi_cdns_recv(const struct device *dev)
{
const struct spi_cdns_cfg *config = dev->config;
struct spi_cdns_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
uint8_t dfs = SPI_WORD_SIZE_GET(ctx->config->operation) / 8;
uint32_t val;
int i, loop;
val = sys_read32(SPI_REG(dev, SPI_RX_DATA));
loop = (config->fifo_width / 8) / dfs;
for (i = 0; i < loop; i++) {
if (spi_context_rx_buf_on(ctx)) {
switch (dfs) {
case 1:
if (config->fifo_width == 8) {
UNALIGNED_PUT(val & 0xFF, (uint8_t *)ctx->rx_buf);
} else if (config->fifo_width == 16) {
UNALIGNED_PUT((val >> 8 * (1 - i)) & 0xFF,
(uint8_t *)ctx->rx_buf);
} else if (config->fifo_width == 32) {
UNALIGNED_PUT((val >> 8 * (3 - i)) & 0xFF,
(uint8_t *)ctx->rx_buf);
}
break;
case 2:
if (config->fifo_width == 16) {
UNALIGNED_PUT(val & 0xFFFF, (uint16_t *)ctx->rx_buf);
} else if (config->fifo_width == 32) {
UNALIGNED_PUT((val >> 16 * (1 - i)) & 0xFFFF,
(uint16_t *)ctx->rx_buf);
}
break;
case 4:
if (config->fifo_width == 32) {
UNALIGNED_PUT(val, (uint32_t *)ctx->rx_buf);
}
break;
}
}
if (data->fifo_diff > 0) {
data->fifo_diff--;
}
spi_context_update_rx(ctx, dfs, 1);
}
}
/**
* @brief Push to Tx-FIFO
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static void spi_cdns_push_data(const struct device *dev)
{
const struct spi_cdns_cfg *config = dev->config;
struct spi_cdns_data *data = dev->data;
uint32_t tx_entry;
if (spi_context_is_slave(&data->ctx)) {
/*
* while tx fifo is not full and there is data to transmit, as we are a target fill
* it up until we are full
*/
while ((!(sys_read32(SPI_REG(dev, SPI_INT_STATUS)) & SPI_INT_TF)) &&
(data->tx_remain_entry > 0)) {
spi_cdns_send(dev);
}
} else {
/*
* We can't fill until we are full as we could chase our tail with waiting until we
* are full, while at the same time data is being sent out faster than we can check
* if we are full
*/
tx_entry = MIN(config->tx_fifo_depth - data->fifo_diff, data->tx_remain_entry);
while (tx_entry > 0) {
spi_cdns_send(dev);
tx_entry--;
}
}
}
/**
* @brief Pull from Rx-FIFO
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static void spi_cdns_pull_data(const struct device *dev)
{
const struct spi_cdns_cfg *config = dev->config;
struct spi_cdns_data *data = dev->data;
uint32_t rx_threshold_tmp;
uint32_t rx_remain_entry;
/*
* As there is no rx fifo empty status bit, Write the rx threshold
* to so the rne status bit will report when there is less than 1
* item in the fifo
*/
rx_threshold_tmp = sys_read32(SPI_REG(dev, SPI_RX_THRESHOLD));
sys_write32(1, SPI_REG(dev, SPI_RX_THRESHOLD));
while (sys_read32(SPI_REG(dev, SPI_INT_STATUS)) & SPI_INT_RNE) {
spi_cdns_recv(dev);
}
/*
* The threshold is designed to trigger by FIFO I/O.
* Therefore, it is necessary to set rx threshold before pulling.
*/
rx_remain_entry = DIV_ROUND_UP(data->fifo_diff, (config->fifo_width / 8));
if ((rx_remain_entry != 0) && (rx_remain_entry < rx_threshold_tmp)) {
sys_write32(rx_remain_entry, SPI_REG(dev, SPI_RX_THRESHOLD));
} else {
sys_write32(rx_threshold_tmp, SPI_REG(dev, SPI_RX_THRESHOLD));
}
}
/**
* @brief Configure SPI controller
*
* @param dev Device structure (In memory) per driver instance
* @param config SPI controller configuration structure
* @retval 0 No errors.
* @retval -EINVAL Invalid argument error.
* @retval -ENOTSUP Unsupported value error.
*/
static int spi_cdns_configure(const struct device *dev, const struct spi_config *config)
{
const struct spi_cdns_cfg *dev_config = dev->config;
struct spi_cdns_data *data = dev->data;
uint32_t word_size, conf_val, clock_freq, ext_clock_freq;
uint8_t baud_rate_div, ext_baud_rate_div;
if (spi_cdns_context_configured(dev, config)) {
/* Nothing to do */
return 0;
}
if (config->operation & (SPI_MODE_LOOP | SPI_TRANSFER_LSB | SPI_LINES_DUAL |
SPI_LINES_QUAD | SPI_LINES_OCTAL)) {
return -ENOTSUP;
}
/* Active High CS is not supported with hardware CS */
if (!spi_cs_is_gpio(config) && (config->operation & SPI_CS_ACTIVE_HIGH)) {
return -ENOTSUP;
}
if ((config->operation & SPI_OP_MODE_SLAVE) && !IS_ENABLED(CONFIG_SPI_SLAVE)) {
LOG_ERR("Kconfig for enable SPI in slave mode is not enabled");
return -ENOTSUP;
}
/* Word Sizes are only compatible with certain fifo widths */
word_size = SPI_WORD_SIZE_GET(config->operation);
if (((word_size != 8) && (word_size != 16) && (word_size != 32)) ||
(word_size > dev_config->fifo_width) ||
((dev_config->fifo_width == 24) && (word_size == 16)) ||
((dev_config->fifo_width == 32) && (word_size == 24))) {
return -ENOTSUP;
}
data->ctx.config = config;
data->config = *config;
conf_val = SPI_CONF_PCSL_MASK | SPI_CONF_MCSE;
/* Configure for Master or Slave */
if (config->operation & SPI_OP_MODE_SLAVE) {
conf_val &= ~(SPI_CONF_MSEL);
} else {
conf_val |= SPI_CONF_MSEL;
}
/* Set the polarity */
if (config->operation & SPI_MODE_CPHA) {
conf_val |= SPI_CONF_CPHA;
} else {
conf_val &= ~(SPI_CONF_CPHA);
}
/* Set the phase */
if (config->operation & SPI_MODE_CPOL) {
conf_val |= SPI_CONF_CPOL;
} else {
conf_val &= ~(SPI_CONF_CPOL);
}
/*
* Set clock frequency.
* SPI clock is generated based on pclk or ext_clk, and the frequency closest
* to the value obtained by dividing the two base clocks is selected.
*/
clock_freq = dev_config->clock_frequency;
baud_rate_div = SPI_MBRD_MIN;
while ((baud_rate_div < SPI_MBRD_MAX) &&
((clock_freq / (2 << baud_rate_div)) > config->frequency)) {
baud_rate_div++;
}
if (dev_config->ext_clock) {
/* check if there is a closer frequency with ext_clock */
ext_clock_freq = dev_config->ext_clock;
ext_baud_rate_div = SPI_MBRD_MIN;
while ((ext_baud_rate_div < SPI_MBRD_MAX) &&
((ext_clock_freq / (2 << ext_baud_rate_div)) > config->frequency)) {
ext_baud_rate_div++;
}
if (config->frequency - (clock_freq / (2 << baud_rate_div)) >
config->frequency - (ext_clock_freq / (2 << ext_baud_rate_div))) {
/* ext_clock is closer, so use it instead */
baud_rate_div = ext_baud_rate_div;
clock_freq = ext_clock_freq;
conf_val |= SPI_CONF_MRCS;
} else {
conf_val &= ~SPI_CONF_MRCS;
}
} else {
conf_val &= ~SPI_CONF_MRCS;
}
conf_val &= ~SPI_CONF_MBRD_MASK;
conf_val |= baud_rate_div << SPI_CONF_MBRD_OFFSET;
LOG_DBG("%s: spi baud rate %uHz", dev->name, clock_freq / (2 << baud_rate_div));
/* Set transfer word size */
conf_val &= ~(SPI_CONF_TWS_MASK);
conf_val |= ((word_size / 8) - 1) << SPI_CONF_TWS_OFFSET;
sys_write32(conf_val, SPI_REG(dev, SPI_CONF));
return 0;
}
/**
* @brief Interrupt handler
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static void spi_cdns_isr(const struct device *dev)
{
struct spi_cdns_data *data = dev->data;
int32_t int_status;
int error = 0;
int_status = sys_read32(SPI_REG(dev, SPI_INT_STATUS));
sys_write32(int_status, SPI_REG(dev, SPI_INT_STATUS));
if ((int_status & SPI_INT_ROF) && spi_context_rx_buf_on(&data->ctx)) {
LOG_ERR("%s: rx fifo overflow", dev->name);
error = -EIO;
goto complete;
}
if ((int_status & SPI_INT_TUF) && spi_context_tx_buf_on(&data->ctx)) {
LOG_ERR("%s: tx fifo underflow", dev->name);
error = -EIO;
goto complete;
}
if (int_status & SPI_INT_RNE) {
spi_cdns_pull_data(dev);
}
if (int_status & SPI_INT_TNF) {
spi_cdns_push_data(dev);
}
if (!spi_context_tx_buf_on(&data->ctx)) {
/* Disable Tx-FIFO interrupt for no transfer data */
sys_write32(SPI_INT_TNF, SPI_REG(dev, SPI_INT_DISABLE));
}
if (spi_context_tx_buf_on(&data->ctx) || spi_context_rx_buf_on(&data->ctx)) {
return;
}
if (data->fifo_diff != 0) {
return;
}
complete:
sys_write32(SPI_INT_DEFAULT, SPI_REG(dev, SPI_INT_DISABLE));
#if CONFIG_SPI_ASYNC
if (data->ctx.asynchronous) {
if (spi_cs_is_gpio(data->ctx.config)) {
spi_context_cs_control(&data->ctx, false);
} else {
spi_cdns_cs_control(dev, false);
}
pm_device_busy_clear(dev);
}
#endif
spi_context_complete(&data->ctx, dev, error);
}
/**
* @brief Initialize SPI driver
*
* @param dev Device structure (In memory) per driver instance
* @return None.
*/
static int spi_cdns_init(const struct device *dev)
{
const struct spi_cdns_cfg *cfg = dev->config;
struct spi_cdns_data *data = dev->data;
cfg->irq_config();
sys_write32(SPI_CONF_INITIAL_VAL, SPI_REG(dev, SPI_CONF));
/* Disable interrupt */
sys_write32(SPI_INT_DEFAULT, SPI_REG(dev, SPI_INT_DISABLE));
/* Clear Pending Interrupts */
(void)sys_read32(SPI_REG(dev, SPI_INT_STATUS));
/* TxFIFO and RxFIFO clear */
sys_set_mask32(SPI_REG(dev, SPI_CONF), SPI_CONF_TXCLR | SPI_CONF_RXCLR,
SPI_CONF_TXCLR | SPI_CONF_RXCLR);
spi_cdns_spi_enable(dev, true);
/* Make sure the context is unlocked */
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
/*******************************************************************************
* Public Functions Code
******************************************************************************/
/**
* @brief Internal read/write the specified amount of data.
*
* @param dev Pointer to the device structure for the driver instance.
* @param config Pointer to a valid spi_config structure instance.
* @param tx_bufs Buffer array where data to be sent originates from, or NULL if none.
* @param rx_bufs Buffer array where data to be read will be written to, or NULL if none.
* @param asynchronous Asynchronous flag
* @param signal A pointer to a valid and ready to be signaled struct k_poll_signal.
*
* @retval 0 Success.
* @retval -EINVAL Invalid argument error.
* @retval -ENOTSUP Unsupported value error.
* @retval -EBUSY Waiting period timed out.
* @retval -EIO Rx FIFO overflow.
*/
static int spi_cdns_transceive(const struct device *dev, const struct spi_config *config,
const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs,
bool asynchronous, spi_callback_t cb, void *userdata)
{
const struct spi_cdns_cfg *dev_config = dev->config;
struct spi_cdns_data *data = dev->data;
uint32_t dfs;
int ret;
spi_context_lock(&data->ctx, asynchronous, cb, userdata, config);
pm_device_busy_set(dev);
spi_cdns_spi_enable(dev, false);
ret = spi_cdns_configure(dev, config);
if (ret < 0) {
spi_cdns_spi_enable(dev, true);
goto out;
}
/* Disable interrupt */
sys_write32(SPI_INT_DEFAULT, SPI_REG(dev, SPI_INT_DISABLE));
/* Clear Pending Interrupts */
(void)sys_read32(SPI_REG(dev, SPI_INT_STATUS));
/* Reset semaphore for waiting for completion */
k_sem_reset(&data->ctx.sync);
/* TxFIFO and RxFIFO clear */
sys_set_mask32(SPI_REG(dev, SPI_CONF), SPI_CONF_TXCLR | SPI_CONF_RXCLR,
SPI_CONF_TXCLR | SPI_CONF_RXCLR);
spi_cdns_spi_enable(dev, true);
data->fifo_diff = 0;
dfs = SPI_WORD_SIZE_GET(data->ctx.config->operation) / 8;
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, dfs);
data->tx_remain_entry =
MAX(spi_context_total_rx_len(&data->ctx), spi_context_total_tx_len(&data->ctx));
/* 0 byte transfer */
if ((spi_context_total_rx_len(&data->ctx) == 0) &&
(spi_context_total_tx_len(&data->ctx) == 0)) {
if (asynchronous) {
spi_context_complete(&data->ctx, dev, 0);
}
goto out;
}
/* Set fifo thresholds */
if (spi_context_is_slave(&data->ctx)) {
sys_write32(1, SPI_REG(dev, SPI_RX_THRESHOLD));
sys_write32(dev_config->tx_fifo_depth - 1, SPI_REG(dev, SPI_TX_THRESHOLD));
} else {
uint32_t fifo_words = MIN(DIV_ROUND_UP(spi_context_total_rx_len(&data->ctx),
(dev_config->fifo_width / 8)),
dev_config->rx_fifo_depth * 5 / 8);
sys_write32(fifo_words, SPI_REG(dev, SPI_RX_THRESHOLD));
sys_write32(dev_config->tx_fifo_depth / 2, SPI_REG(dev, SPI_TX_THRESHOLD));
}
if (spi_cs_is_gpio(data->ctx.config)) {
spi_context_cs_control(&data->ctx, true);
} else {
spi_cdns_cs_control(dev, true);
}
sys_write32(SPI_INT_DEFAULT, SPI_REG(dev, SPI_INT_ENABLE));
ret = spi_context_wait_for_completion(&data->ctx);
if (!asynchronous) {
if (spi_cs_is_gpio(data->ctx.config)) {
spi_context_cs_control(&data->ctx, false);
} else {
spi_cdns_cs_control(dev, false);
}
pm_device_busy_clear(dev);
}
#ifdef CONFIG_SPI_SLAVE
if (spi_context_is_slave(&data->ctx) && !ret) {
ret = data->ctx.recv_frames;
}
#endif /* CONFIG_SPI_SLAVE */
out:
spi_context_release(&data->ctx, ret);
return ret;
}
/**
* @brief Read/write the specified amount of data from the SPI driver.
*
* Note: This function is synchronous.
*
* @param dev Pointer to the device structure for the driver instance.
* @param config Pointer to a valid spi_config structure instance.
* @param tx_bufs Buffer array where data to be sent originates from, or NULL if none.
* @param rx_bufs Buffer array where data to be read will be written to, or NULL if none.
*
* @retval 0 Success.
* @retval -EINVAL Invalid argument error.
* @retval -ENOTSUP Unsupported value error.
* @retval -EBUSY Waiting period timed out.
* @retval -EIO Rx FIFO overflow.
*/
static int spi_cdns_transceive_sync(const struct device *dev, const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
return spi_cdns_transceive(dev, config, tx_bufs, rx_bufs, false, NULL, NULL);
}
#ifdef CONFIG_SPI_ASYNC
/**
* @brief Read/write the specified amount of data from the SPI driver.
*
* Note: This function is asynchronous.
*
* @param dev Pointer to the device structure for the driver instance.
* @param config Pointer to a valid spi_config structure instance.
* @param tx_bufs Buffer array where data to be sent originates from, or NULL if none.
* @param rx_bufs Buffer array where data to be read will be written to, or NULL if none.
* @param async A pointer to a valid and ready to be signaled struct k_poll_signal.
*
* @retval 0 Success.
* @retval -EINVAL Invalid argument error.
* @retval -ENOTSUP Unsupported value error.
*/
static int spi_cdns_transceive_async(const struct device *dev, const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs, spi_callback_t cb,
void *userdata)
{
return spi_cdns_transceive(dev, config, tx_bufs, rx_bufs, true, cb, userdata);
}
#endif /* CONFIG_SPI_ASYNC */
/**
* @brief Release the SPI device locked on by the current config
*
* @param dev Pointer to the device structure for the driver instance
* @param config Pointer to a valid spi_config structure instance.
*/
static int spi_cdns_release(const struct device *dev, const struct spi_config *config)
{
struct spi_cdns_data *data = dev->data;
if (spi_cs_is_gpio(data->ctx.config)) {
spi_context_cs_control(&data->ctx, false);
} else {
spi_cdns_cs_control(dev, false);
}
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
/**
* SPI driver API registered in Zephyr spi framework
*/
static DEVICE_API(spi, spi_cdns_api) = {
.transceive = spi_cdns_transceive_sync,
#ifdef CONFIG_SPI_ASYNC
.transceive_async = spi_cdns_transceive_async,
#endif /* CONFIG_SPI_ASYNC */
.release = spi_cdns_release,
#ifdef CONFIG_SPI_RTIO
.iodev_submit = spi_rtio_iodev_default_submit,
#endif /* CONFIG_SPI_RTIO */
};
#define SPI_CDNS_INIT(n) \
static void spi_cdns_irq_config_##n(void); \
static struct spi_cdns_data spi_cdns_data_##n = { \
SPI_CONTEXT_INIT_LOCK(spi_cdns_data_##n, ctx), \
SPI_CONTEXT_INIT_SYNC(spi_cdns_data_##n, ctx), \
}; \
static struct spi_cdns_cfg spi_cdns_cfg_##n = { \
.base = DT_INST_REG_ADDR(n), \
.irq_config = spi_cdns_irq_config_##n, \
.clock_frequency = DT_INST_PROP(n, clock_frequency), \
.ext_clock = DT_INST_PROP_OR(n, clock_frequency_ext, 0), \
.fifo_width = DT_INST_PROP(n, fifo_width), \
.tx_fifo_depth = DT_INST_PROP(n, tx_fifo_depth), \
.rx_fifo_depth = DT_INST_PROP(n, rx_fifo_depth), \
}; \
SPI_DEVICE_DT_INST_DEFINE(n, spi_cdns_init, NULL, &spi_cdns_data_##n, &spi_cdns_cfg_##n, \
POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, &spi_cdns_api); \
static void spi_cdns_irq_config_##n(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), spi_cdns_isr, \
DEVICE_DT_INST_GET(n), 0); \
irq_enable(DT_INST_IRQN(n)); \
}
#define DT_DRV_COMPAT cdns_spi
DT_INST_FOREACH_STATUS_OKAY(SPI_CDNS_INIT)