blob: 9edafe02096c7d62ea2f82b587e1410970e53094 [file] [log] [blame]
/*
* Copyright (c) 2016, Freescale Semiconductor, Inc.
* Copyright 2017, 2019, 2025, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_lpc_spi
#include <errno.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/spi/rtio.h>
#include <zephyr/drivers/clock_control.h>
#include <fsl_spi.h>
#include <zephyr/logging/log.h>
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/dma/dma_mcux_lpc.h>
#endif
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/sys_clock.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/policy.h>
LOG_MODULE_REGISTER(spi_mcux_flexcomm, CONFIG_SPI_LOG_LEVEL);
#include "spi_context.h"
#define SPI_CHIP_SELECT_COUNT 4
#define SPI_MAX_DATA_WIDTH 16
#define SPI_MIN_DATA_WIDTH 4
struct spi_mcux_config {
SPI_Type *base;
const struct device *clock_dev;
clock_control_subsys_t clock_subsys;
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
void (*irq_config_func)(const struct device *dev);
#endif
uint32_t pre_delay;
uint32_t post_delay;
uint32_t frame_delay;
uint32_t transfer_delay;
uint32_t def_char;
const struct pinctrl_dev_config *pincfg;
const struct reset_dt_spec reset;
};
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
#define SPI_MCUX_FLEXCOMM_DMA_ERROR_FLAG 0x01
#define SPI_MCUX_FLEXCOMM_DMA_RX_DONE_FLAG 0x02
#define SPI_MCUX_FLEXCOMM_DMA_TX_DONE_FLAG 0x04
#define SPI_MCUX_FLEXCOMM_DMA_DONE_FLAG \
(SPI_MCUX_FLEXCOMM_DMA_RX_DONE_FLAG | SPI_MCUX_FLEXCOMM_DMA_TX_DONE_FLAG)
struct stream {
const struct device *dma_dev;
uint32_t channel; /* stores the channel for dma */
struct dma_config dma_cfg;
struct dma_block_config dma_blk_cfg[CONFIG_SPI_MCUX_FLEXCOMM_DMA_MAX_BLOCKS];
};
#endif
struct spi_mcux_data {
const struct device *dev;
spi_master_handle_t handle;
struct spi_context ctx;
size_t transfer_len;
uint16_t word_size_bytes;
uint16_t word_size_bits;
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
volatile uint32_t status_flags;
struct stream dma_rx;
struct stream dma_tx;
/* dummy value used for transferring NOP when tx buf is null */
uint32_t dummy_tx_buffer;
/* Used to send the last word */
uint32_t last_word;
#endif
};
static bool force_reconfig;
static void spi_mcux_transfer_next_packet(const struct device *dev)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
struct spi_context *ctx = &data->ctx;
spi_transfer_t transfer;
status_t status;
if ((ctx->tx_len == 0) && (ctx->rx_len == 0)) {
/* nothing left to rx or tx, we're done! */
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, dev, 0);
spi_context_release(&data->ctx, 0);
pm_policy_device_power_lock_put(dev);
return;
}
transfer.configFlags = 0;
if (ctx->tx_len == 0) {
/* rx only, nothing to tx */
transfer.txData = NULL;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->rx_len;
} else if (ctx->rx_len == 0) {
/* tx only, nothing to rx */
transfer.txData = (uint8_t *) ctx->tx_buf;
transfer.rxData = NULL;
transfer.dataSize = ctx->tx_len;
} else if (ctx->tx_len == ctx->rx_len) {
/* rx and tx are the same length */
transfer.txData = (uint8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->tx_len;
} else if (ctx->tx_len > ctx->rx_len) {
/* Break up the tx into multiple transfers so we don't have to
* rx into a longer intermediate buffer. Leave chip select
* active between transfers.
*/
transfer.txData = (uint8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->rx_len;
} else {
/* Break up the rx into multiple transfers so we don't have to
* tx from a longer intermediate buffer. Leave chip select
* active between transfers.
*/
transfer.txData = (uint8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->tx_len;
}
if (ctx->tx_count <= 1 && ctx->rx_count <= 1) {
transfer.configFlags = kSPI_FrameAssert;
}
data->transfer_len = transfer.dataSize;
status = SPI_MasterTransferNonBlocking(base, &data->handle, &transfer);
if (status != kStatus_Success) {
LOG_ERR("Transfer could not start");
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, dev, -EIO);
pm_policy_device_power_lock_put(dev);
}
}
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
static void spi_mcux_isr(const struct device *dev)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
SPI_MasterTransferHandleIRQ(base, &data->handle);
}
static void spi_mcux_transfer_callback(SPI_Type *base,
spi_master_handle_t *handle, status_t status, void *userData)
{
struct spi_mcux_data *data = userData;
spi_context_update_tx(&data->ctx, data->word_size_bytes, data->transfer_len);
spi_context_update_rx(&data->ctx, data->word_size_bytes, data->transfer_len);
spi_mcux_transfer_next_packet(data->dev);
}
#endif /* CONFIG_SPI_MCUX_FLEXCOMM_DMA */
static uint8_t spi_clock_cycles(uint32_t delay_ns, uint32_t sck_frequency_hz)
{
/* Convert delay_ns to an integer number of clock cycles of frequency
* sck_frequency_hz. The maximum delay is 15 clock cycles.
*/
uint8_t delay_cycles = (uint64_t)delay_ns * sck_frequency_hz / NSEC_PER_SEC;
delay_cycles = MIN(delay_cycles, 15);
return delay_cycles;
}
static int spi_mcux_configure(const struct device *dev,
const struct spi_config *spi_cfg)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
uint32_t clock_freq;
if ((spi_context_configured(&data->ctx, spi_cfg)) && (!force_reconfig)) {
/* This configuration is already in use */
return 0;
}
force_reconfig = false;
if (spi_cfg->operation & SPI_HALF_DUPLEX) {
LOG_ERR("Half-duplex not supported");
return -ENOTSUP;
}
/*
* Do master or slave initialisation, depending on the
* mode requested.
*/
if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_MASTER) {
spi_master_config_t master_config;
SPI_MasterGetDefaultConfig(&master_config);
if (!device_is_ready(config->clock_dev)) {
LOG_ERR("clock control device not ready");
return -ENODEV;
}
/* Get the clock frequency */
if (clock_control_get_rate(config->clock_dev,
config->clock_subsys, &clock_freq)) {
return -EINVAL;
}
if (spi_cfg->slave > SPI_CHIP_SELECT_COUNT) {
LOG_ERR("Slave %d is greater than %d",
spi_cfg->slave, SPI_CHIP_SELECT_COUNT);
return -EINVAL;
}
master_config.sselNum = spi_cfg->slave;
master_config.sselPol = kSPI_SpolActiveAllLow;
master_config.dataWidth = data->word_size_bits - 1;
master_config.polarity =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL)
? kSPI_ClockPolarityActiveLow
: kSPI_ClockPolarityActiveHigh;
master_config.phase =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA)
? kSPI_ClockPhaseSecondEdge
: kSPI_ClockPhaseFirstEdge;
master_config.direction =
(spi_cfg->operation & SPI_TRANSFER_LSB)
? kSPI_LsbFirst
: kSPI_MsbFirst;
master_config.baudRate_Bps = spi_cfg->frequency;
spi_delay_config_t *delayConfig = &master_config.delayConfig;
delayConfig->preDelay = spi_clock_cycles(config->pre_delay,
spi_cfg->frequency);
delayConfig->postDelay = spi_clock_cycles(config->post_delay,
spi_cfg->frequency);
delayConfig->frameDelay = spi_clock_cycles(config->frame_delay,
spi_cfg->frequency);
delayConfig->transferDelay = spi_clock_cycles(config->transfer_delay,
spi_cfg->frequency);
SPI_MasterInit(base, &master_config, clock_freq);
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
SPI_SetDummyData(base, (uint8_t)config->def_char);
SPI_MasterTransferCreateHandle(base, &data->handle,
spi_mcux_transfer_callback, data);
#endif
data->ctx.config = spi_cfg;
} else {
spi_slave_config_t slave_config;
SPI_SlaveGetDefaultConfig(&slave_config);
slave_config.polarity =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL)
? kSPI_ClockPolarityActiveLow
: kSPI_ClockPolarityActiveHigh;
slave_config.phase =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA)
? kSPI_ClockPhaseSecondEdge
: kSPI_ClockPhaseFirstEdge;
slave_config.direction =
(spi_cfg->operation & SPI_TRANSFER_LSB)
? kSPI_LsbFirst
: kSPI_MsbFirst;
/* SS pin active low */
slave_config.sselPol = kSPI_SpolActiveAllLow;
slave_config.dataWidth = data->word_size_bits - 1;
SPI_SlaveInit(base, &slave_config);
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
SPI_SetDummyData(base, (uint8_t)config->def_char);
SPI_SlaveTransferCreateHandle(base, &data->handle,
spi_mcux_transfer_callback, data);
#endif
data->ctx.config = spi_cfg;
}
return 0;
}
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
/* Dummy buffer used as a sink when rc buf is null */
static uint32_t dummy_rx_buffer;
/* This function is executed in the interrupt context */
static void spi_mcux_dma_callback(const struct device *dev, void *arg,
uint32_t channel, int status)
{
/* arg directly holds the spi device */
const struct device *spi_dev = arg;
struct spi_mcux_data *data = spi_dev->data;
if (status < 0) {
LOG_ERR("DMA callback error with channel %d.", channel);
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_ERROR_FLAG;
} else {
/* identify the origin of this callback */
if (channel == data->dma_tx.channel) {
/* this part of the transfer ends */
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_TX_DONE_FLAG;
} else if (channel == data->dma_rx.channel) {
/* The RX DMA interrupt will trigger once all of the data is transferred, so
* we use that to call context complete.
*/
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_RX_DONE_FLAG;
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, spi_dev, 0);
pm_policy_device_power_lock_put(dev);
} else {
LOG_ERR("DMA callback channel %d is not valid.",
channel);
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_ERROR_FLAG;
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, spi_dev, -EIO);
pm_policy_device_power_lock_put(dev);
}
}
}
static uint32_t spi_mcux_get_tx_word(uint32_t value, const struct spi_config *spi_cfg,
uint8_t word_size)
{
value |= ((uint32_t)SPI_DEASSERT_ALL &
(~(uint32_t)SPI_DEASSERTNUM_SSEL((uint32_t)spi_cfg->slave)));
/* set width of data - range asserted at entry */
value |= SPI_FIFOWR_LEN(word_size - 1);
return value;
}
static uint32_t spi_mcux_get_last_tx_word(const struct spi_config *spi_cfg, const uint8_t *buf,
size_t len, uint32_t def_char, uint8_t word_size)
{
uint32_t value = def_char;
/* Buffer will be null if TX is sending dummy data. In this case, we don't
* need to copy any data from the buffer into the dummy word.
*/
if (buf != NULL) {
if (word_size > 8) {
assert(len > 1);
value = (((uint32_t)buf[len - 1U] << 8U) | (buf[len - 2U]));
} else {
value = buf[len - 1U];
}
}
value |= (uint32_t)SPI_FIFOWR_EOT_MASK;
return spi_mcux_get_tx_word(value, spi_cfg, word_size);
}
static int spi_mcux_dma_tx_load(const struct device *dev, const struct spi_config *spi_cfg,
const uint8_t *buf, size_t len, size_t dma_block_num,
bool last_packet)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
struct dma_block_config *blk_cfg;
/* remember active TX DMA channel (used in callback) */
struct stream *stream = &data->dma_tx;
/* prepare the block for this TX DMA channel */
blk_cfg = &stream->dma_blk_cfg[dma_block_num];
/* tx direction has memory as source and periph as dest. */
blk_cfg->dest_address = (uint32_t)&base->FIFOWR;
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
blk_cfg->dest_scatter_en = 0;
blk_cfg->source_gather_en = 0;
if (last_packet) {
data->last_word = spi_mcux_get_last_tx_word(spi_cfg, buf, len, config->def_char,
data->word_size_bits);
blk_cfg->source_address = (uint32_t)&data->last_word;
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
blk_cfg->block_size = sizeof(uint32_t);
blk_cfg->next_block = NULL;
} else {
blk_cfg->block_size = len;
blk_cfg->next_block = blk_cfg + 1;
if (buf) {
blk_cfg->source_address = (uint32_t)buf;
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_INCREMENT;
} else {
data->dummy_tx_buffer = 0;
blk_cfg->source_address = (uint32_t)&data->dummy_tx_buffer;
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
}
}
return EXIT_SUCCESS;
}
static int spi_mcux_dma_tx_start(const struct device *dev, const struct spi_config *spi_cfg)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
struct stream *stream = &data->dma_tx;
int ret;
/* direction is given by the DT */
stream->dma_cfg.head_block = &stream->dma_blk_cfg[0];
/* give the client dev as arg, as the callback comes from the dma */
stream->dma_cfg.user_data = (struct device *)dev;
/* pass our client origin to the dma: data->dma_tx.dma_channel */
ret = dma_config(data->dma_tx.dma_dev, data->dma_tx.channel,
&stream->dma_cfg);
/* the channel is the actual stream from 0 */
if (ret != 0) {
return ret;
}
/* Setup the control info.
* Halfword writes to just the control bits (offset 0xE22) doesn't push
* anything into the FIFO. And the data access type of control bits must
* be uint16_t, byte writes or halfword writes to FIFOWR will push the
* data and the current control bits into the FIFO.
*/
uint32_t tmp_data = spi_mcux_get_tx_word(config->def_char, spi_cfg, data->word_size_bits);
*((uint16_t *)((uint32_t)&base->FIFOWR) + 1) = (uint16_t)(tmp_data >> 16U);
/* gives the request ID */
return dma_start(data->dma_tx.dma_dev, data->dma_tx.channel);
}
static int spi_mcux_dma_rx_load(const struct device *dev, uint8_t *buf, size_t len,
size_t dma_block_num, bool last_packet)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
struct dma_block_config *blk_cfg;
/* retrieve active RX DMA channel (used in callback) */
struct stream *stream = &data->dma_rx;
/* prepare the block for this RX DMA channel */
blk_cfg = &stream->dma_blk_cfg[dma_block_num];
blk_cfg->block_size = len;
blk_cfg->dest_scatter_en = 0;
if (last_packet) {
blk_cfg->next_block = NULL;
} else {
blk_cfg->next_block = blk_cfg + 1;
}
/* rx direction has periph as source and mem as dest. */
blk_cfg->source_address = (uint32_t)&base->FIFORD;
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
blk_cfg->source_gather_en = 0;
if (buf == NULL) {
/* if rx buff is null, then write data to dummy address. */
blk_cfg->dest_address = (uint32_t)&dummy_rx_buffer;
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE;
} else {
blk_cfg->dest_address = (uint32_t)buf;
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_INCREMENT;
}
return EXIT_SUCCESS;
}
static int spi_mcux_dma_rx_start(const struct device *dev)
{
struct spi_mcux_data *data = dev->data;
struct stream *stream = &data->dma_rx;
int ret;
/* direction is given by the DT */
stream->dma_cfg.head_block = &stream->dma_blk_cfg[0];
stream->dma_cfg.user_data = (struct device *)dev;
/* pass our client origin to the dma: data->dma_rx.channel */
ret = dma_config(data->dma_rx.dma_dev, data->dma_rx.channel,
&stream->dma_cfg);
/* the channel is the actual stream from 0 */
if (ret != 0) {
return ret;
}
/* gives the request ID */
return dma_start(data->dma_rx.dma_dev, data->dma_rx.channel);
}
static int spi_mcux_dma_transfer(const struct device *dev, const struct spi_config *spi_cfg)
{
struct spi_mcux_data *data = dev->data;
const size_t data_size = data->word_size_bytes;
size_t dma_block = 0;
size_t block_length;
int ret;
bool last_packet = false;
/* Setup DMA tx/rx data size based on spi word size.*/
data->dma_rx.dma_cfg.source_data_size = data_size;
data->dma_rx.dma_cfg.dest_data_size = data_size;
data->dma_tx.dma_cfg.source_data_size = data_size;
data->dma_tx.dma_cfg.dest_data_size = data_size;
data->status_flags = 0;
/* Parse all data to be sent into chained DMA blocks. */
while (1) {
block_length = spi_context_max_continuous_chunk(&data->ctx);
assert(block_length >= data_size);
if (data->ctx.tx_count <= 1 && data->ctx.rx_count <= 1 &&
block_length == spi_context_longest_current_buf(&data->ctx)) {
/* On the last buffer. First send all but the last word, then when only one
* word is remaining, load the last buffer with that word so it can be set
* to release the CS line.
*/
if (block_length > data_size) {
block_length -= data_size;
} else {
/* There is only one transfer left. */
last_packet = true;
}
}
ret = spi_mcux_dma_rx_load(dev, data->ctx.rx_buf, block_length, dma_block,
last_packet);
if (ret) {
LOG_ERR("could not load rx data to dma: %d", ret);
return ret;
}
ret = spi_mcux_dma_tx_load(dev, spi_cfg, data->ctx.tx_buf, block_length, dma_block,
last_packet);
if (ret) {
LOG_ERR("could not load tx data to dma: %d", ret);
return ret;
}
spi_context_update_rx(&data->ctx, data_size, block_length);
spi_context_update_tx(&data->ctx, data_size, block_length);
/* Increment block count before exit so the last block is counted for dma_cfg. */
dma_block++;
if (last_packet) {
assert(spi_context_total_rx_len(&data->ctx) == 0);
assert(spi_context_total_tx_len(&data->ctx) == 0);
break;
}
if (dma_block == CONFIG_SPI_MCUX_FLEXCOMM_DMA_MAX_BLOCKS) {
LOG_ERR("spi xfer exceeds dma block allocation");
return -ENOMEM;
}
}
/* Update dma_cfg with actual blocks used. */
data->dma_rx.dma_cfg.block_count = dma_block;
data->dma_tx.dma_cfg.block_count = dma_block;
ret = spi_mcux_dma_rx_start(dev);
if (ret) {
LOG_ERR("could not start dma rx: %d", ret);
return -EIO;
}
ret = spi_mcux_dma_tx_start(dev, spi_cfg);
if (ret) {
LOG_ERR("could not start dma tx: %d", ret);
return -EIO;
}
return EXIT_SUCCESS;
}
static void spi_mcux_transfer_one_word(const struct device *dev, const struct spi_config *spi_cfg)
{
/* don't use the SDK, which is overkill for a one word transfer */
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
SPI_Type *base = config->base;
uint32_t tmp32;
tmp32 = spi_mcux_get_last_tx_word(spi_cfg, ctx->tx_buf, 1, config->def_char,
data->word_size_bits);
/* wait for TXFIFO to not be full */
while ((base->FIFOSTAT & SPI_FIFOSTAT_TXNOTFULL_MASK) == 0) {
}
/* txFIFO is not full */
base->FIFOWR = tmp32;
/* wait for RXFIFO to not be empty */
while ((base->FIFOSTAT & SPI_FIFOSTAT_RXNOTEMPTY_MASK) == 0) {
}
/* rxFIFO is not empty */
tmp32 = base->FIFORD;
/* copy to user buffer if given one */
if (ctx->rx_len) {
assert(ctx->rx_buf);
ctx->rx_buf[0] = (uint8_t)tmp32;
if (data->word_size_bits > 8) {
ctx->rx_buf[1] = (uint8_t)(tmp32 >> 8);
}
}
/* wait if TX FIFO of previous transfer is not empty */
while ((base->FIFOSTAT & SPI_FIFOSTAT_TXEMPTY_MASK) == 0) {
}
}
static int transceive_dma(const struct device *dev,
const struct spi_config *spi_cfg,
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_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
SPI_Type *base = config->base;
int ret;
uint8_t word_size = (uint8_t)SPI_WORD_SIZE_GET(spi_cfg->operation);
if (word_size > SPI_MAX_DATA_WIDTH) {
LOG_ERR("Word size %d is greater than %d", word_size, SPI_MAX_DATA_WIDTH);
return -EINVAL;
}
pm_policy_device_power_lock_get(dev);
spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg);
data->word_size_bits = word_size;
data->word_size_bytes = (word_size > 8) ? (sizeof(uint16_t)) : (sizeof(uint8_t));
ret = spi_mcux_configure(dev, spi_cfg);
if (ret) {
goto out;
}
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
spi_context_cs_control(&data->ctx, true);
/* Clear FIFOs and any previous errors before transfer */
base->FIFOCFG |= SPI_FIFOCFG_EMPTYTX_MASK | SPI_FIFOCFG_EMPTYRX_MASK;
base->FIFOSTAT |= SPI_FIFOSTAT_TXERR_MASK | SPI_FIFOSTAT_RXERR_MASK;
/* Check if this transaction is only sending one word of data. If this is the case, it is
* less work to load that word into the FIFO instead of setting up the DMA to write one
* word. This also avoids the edge case where the FIFOWR bits cannot be set by a chained
* DMA descriptor, because there is only one DMA descriptor.
*/
if (data->ctx.tx_count <= 1 && data->ctx.rx_count <= 1 &&
spi_context_longest_current_buf(&data->ctx) == data->word_size_bytes) {
/* Disable DMATX/RX */
base->FIFOCFG &= ~(SPI_FIFOCFG_DMARX_MASK | SPI_FIFOCFG_DMATX_MASK);
spi_mcux_transfer_one_word(dev, spi_cfg);
spi_context_update_tx(&data->ctx, data->word_size_bytes, data->transfer_len);
spi_context_update_rx(&data->ctx, data->word_size_bytes, data->transfer_len);
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, dev, 0);
/* If asynchronous was true, set to false since transfer is done and we
* want to release the power lock below.
*/
asynchronous = false;
} else {
/* Enable DMATX/RX. */
base->FIFOCFG |= SPI_FIFOCFG_DMARX_MASK | SPI_FIFOCFG_DMATX_MASK;
ret = spi_mcux_dma_transfer(dev, spi_cfg);
if (ret) {
spi_context_cs_control(&data->ctx, false);
goto out;
}
}
/* if asynchronous is true, spi_context_wait_for_completion() just returns 0
* and spi_context_release() is a noop
*/
ret = spi_context_wait_for_completion(&data->ctx);
out:
spi_context_release(&data->ctx, ret);
if (!asynchronous || (ret != 0)) {
/* Only release the power lock if synchronous, or if an error occurred.
* For asynchronous case, the power_lock is released in
* when the transfer is done in the dma completion callback
*/
pm_policy_device_power_lock_put(dev);
}
return ret;
}
#endif
static int transceive(const struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
bool asynchronous,
spi_callback_t cb,
void *userdata)
{
struct spi_mcux_data *data = dev->data;
int ret;
uint8_t word_size = (uint8_t)SPI_WORD_SIZE_GET(spi_cfg->operation);
if (word_size > SPI_MAX_DATA_WIDTH) {
LOG_ERR("Word size %d is greater than %d", word_size, SPI_MAX_DATA_WIDTH);
return -EINVAL;
}
if (word_size < SPI_MIN_DATA_WIDTH) {
LOG_ERR("Word size %d is less than %d", word_size, SPI_MIN_DATA_WIDTH);
return -EINVAL;
}
pm_policy_device_power_lock_get(dev);
spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg);
data->word_size_bits = word_size;
data->word_size_bytes = (word_size > 8) ? (sizeof(uint16_t)) : (sizeof(uint8_t));
ret = spi_mcux_configure(dev, spi_cfg);
if (ret) {
goto out;
}
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
spi_context_cs_control(&data->ctx, true);
spi_mcux_transfer_next_packet(dev);
return spi_context_wait_for_completion(&data->ctx);
out:
spi_context_release(&data->ctx, ret);
pm_policy_device_power_lock_put(dev);
return ret;
}
static int spi_mcux_transceive(const struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
return transceive_dma(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL);
#endif
return transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL);
}
#ifdef CONFIG_SPI_ASYNC
static int spi_mcux_transceive_async(const struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
spi_callback_t cb,
void *userdata)
{
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
return transceive_dma(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata);
#endif
return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata);
}
#endif /* CONFIG_SPI_ASYNC */
static int spi_mcux_release(const struct device *dev,
const struct spi_config *spi_cfg)
{
struct spi_mcux_data *data = dev->data;
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
static int spi_mcux_init_common(const struct device *dev)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
int err = 0;
if (!device_is_ready(config->reset.dev)) {
LOG_ERR("Reset device not ready");
return -ENODEV;
}
err = reset_line_toggle(config->reset.dev, config->reset.id);
if (err) {
return err;
}
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
config->irq_config_func(dev);
#endif
data->dev = dev;
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
if (err) {
return err;
}
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA
if (!device_is_ready(data->dma_tx.dma_dev)) {
LOG_ERR("%s device is not ready", data->dma_tx.dma_dev->name);
return -ENODEV;
}
if (!device_is_ready(data->dma_rx.dma_dev)) {
LOG_ERR("%s device is not ready", data->dma_rx.dma_dev->name);
return -ENODEV;
}
#endif /* CONFIG_SPI_MCUX_FLEXCOMM_DMA */
err = spi_context_cs_configure_all(&data->ctx);
if (err < 0) {
return err;
}
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
static int spi_mcux_flexcomm_pm_action(const struct device *dev, enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
break;
case PM_DEVICE_ACTION_SUSPEND:
break;
case PM_DEVICE_ACTION_TURN_OFF:
/*This flag is used to prevent configuration optimiztions
* after exiting PM3 on spi_mcux_configure()
*/
force_reconfig = true;
break;
case PM_DEVICE_ACTION_TURN_ON:
spi_mcux_init_common(dev);
break;
default:
return -ENOTSUP;
}
return 0;
}
static int spi_mcux_init(const struct device *dev)
{
/* Rest of the init is done from the PM_DEVICE_TURN_ON action
* which is invoked by pm_device_driver_init().
*/
return pm_device_driver_init(dev, spi_mcux_flexcomm_pm_action);
}
static DEVICE_API(spi, spi_mcux_driver_api) = {
.transceive = spi_mcux_transceive,
#ifdef CONFIG_SPI_ASYNC
.transceive_async = spi_mcux_transceive_async,
#endif
#ifdef CONFIG_SPI_RTIO
.iodev_submit = spi_rtio_iodev_default_submit,
#endif
.release = spi_mcux_release,
};
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER_DECL(id) \
static void spi_mcux_config_func_##id(const struct device *dev)
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER_FUNC(id) \
.irq_config_func = spi_mcux_config_func_##id,
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER(id) \
static void spi_mcux_config_func_##id(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(id), DT_INST_IRQ(id, priority), spi_mcux_isr, \
DEVICE_DT_INST_GET(id), 0); \
irq_enable(DT_INST_IRQN(id)); \
}
#else
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER_DECL(id)
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER_FUNC(id)
#define SPI_MCUX_FLEXCOMM_IRQ_HANDLER(id)
#endif
#ifndef CONFIG_SPI_MCUX_FLEXCOMM_DMA
#define SPI_DMA_CHANNELS(id)
#else
#define SPI_DMA_CHANNELS(id) \
.dma_tx = { \
.dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(id, tx)), \
.channel = \
DT_INST_DMAS_CELL_BY_NAME(id, tx, channel), \
.dma_cfg = { \
.channel_direction = LPC_DMA_SPI_MCUX_FLEXCOMM_TX, \
.dma_callback = spi_mcux_dma_callback, \
.block_count = 2, \
} \
}, \
.dma_rx = { \
.dma_dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(id, rx)), \
.channel = \
DT_INST_DMAS_CELL_BY_NAME(id, rx, channel), \
.dma_cfg = { \
.channel_direction = PERIPHERAL_TO_MEMORY, \
.dma_callback = spi_mcux_dma_callback, \
.block_count = 1, \
} \
}
#endif
#define SPI_MCUX_FLEXCOMM_DEVICE(id) \
SPI_MCUX_FLEXCOMM_IRQ_HANDLER_DECL(id); \
PINCTRL_DT_INST_DEFINE(id); \
static const struct spi_mcux_config spi_mcux_config_##id = { \
.base = \
(SPI_Type *)DT_INST_REG_ADDR(id), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(id)), \
.clock_subsys = \
(clock_control_subsys_t)DT_INST_CLOCKS_CELL(id, name),\
SPI_MCUX_FLEXCOMM_IRQ_HANDLER_FUNC(id) \
.pre_delay = DT_INST_PROP_OR(id, pre_delay, 0), \
.post_delay = DT_INST_PROP_OR(id, post_delay, 0), \
.frame_delay = DT_INST_PROP_OR(id, frame_delay, 0), \
.transfer_delay = DT_INST_PROP_OR(id, transfer_delay, 0), \
.def_char = DT_INST_PROP_OR(id, def_char, 0), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id), \
.reset = RESET_DT_SPEC_INST_GET(id), \
}; \
static struct spi_mcux_data spi_mcux_data_##id = { \
SPI_CONTEXT_INIT_LOCK(spi_mcux_data_##id, ctx), \
SPI_CONTEXT_INIT_SYNC(spi_mcux_data_##id, ctx), \
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(id), ctx) \
SPI_DMA_CHANNELS(id) \
}; \
PM_DEVICE_DT_INST_DEFINE(id, spi_mcux_flexcomm_pm_action); \
SPI_DEVICE_DT_INST_DEFINE(id, \
spi_mcux_init, \
PM_DEVICE_DT_INST_GET(id), \
&spi_mcux_data_##id, \
&spi_mcux_config_##id, \
POST_KERNEL, \
CONFIG_SPI_INIT_PRIORITY, \
&spi_mcux_driver_api); \
\
SPI_MCUX_FLEXCOMM_IRQ_HANDLER(id)
DT_INST_FOREACH_STATUS_OKAY(SPI_MCUX_FLEXCOMM_DEVICE)