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.
902 lines
25 KiB
902 lines
25 KiB
/* |
|
* Copyright (c) 2016, Freescale Semiconductor, Inc. |
|
* Copyright (c) 2017,2019, 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> |
|
#endif |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/sys_clock.h> |
|
#include <zephyr/irq.h> |
|
#include <zephyr/drivers/reset.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 |
|
|
|
struct spi_mcux_config { |
|
SPI_Type *base; |
|
const struct device *clock_dev; |
|
clock_control_subsys_t clock_subsys; |
|
void (*irq_config_func)(const struct device *dev); |
|
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[2]; |
|
}; |
|
#endif |
|
|
|
struct spi_mcux_data { |
|
const struct device *dev; |
|
spi_master_handle_t handle; |
|
struct spi_context ctx; |
|
size_t transfer_len; |
|
#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 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); |
|
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"); |
|
} |
|
} |
|
|
|
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, 1, data->transfer_len); |
|
spi_context_update_rx(&data->ctx, 1, data->transfer_len); |
|
|
|
spi_mcux_transfer_next_packet(data->dev); |
|
} |
|
|
|
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; |
|
uint32_t word_size; |
|
|
|
if (spi_context_configured(&data->ctx, spi_cfg)) { |
|
/* This configuration is already in use */ |
|
return 0; |
|
} |
|
|
|
if (spi_cfg->operation & SPI_HALF_DUPLEX) { |
|
LOG_ERR("Half-duplex not supported"); |
|
return -ENOTSUP; |
|
} |
|
|
|
word_size = 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; |
|
} |
|
|
|
/* |
|
* 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 = word_size - 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); |
|
|
|
SPI_SetDummyData(base, (uint8_t)config->def_char); |
|
|
|
SPI_MasterTransferCreateHandle(base, &data->handle, |
|
spi_mcux_transfer_callback, data); |
|
|
|
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 = word_size - 1; |
|
|
|
SPI_SlaveInit(base, &slave_config); |
|
|
|
SPI_SetDummyData(base, (uint8_t)config->def_char); |
|
|
|
SPI_SlaveTransferCreateHandle(base, &data->handle, |
|
spi_mcux_transfer_callback, data); |
|
|
|
data->ctx.config = spi_cfg; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_SPI_MCUX_FLEXCOMM_DMA |
|
/* Dummy buffer used as a sink when rc buf is null */ |
|
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) { |
|
/* this part of the transfer ends */ |
|
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_RX_DONE_FLAG; |
|
} else { |
|
LOG_ERR("DMA callback channel %d is not valid.", |
|
channel); |
|
data->status_flags |= SPI_MCUX_FLEXCOMM_DMA_ERROR_FLAG; |
|
} |
|
} |
|
|
|
spi_context_complete(&data->ctx, spi_dev, 0); |
|
} |
|
|
|
|
|
static void spi_mcux_prepare_txlastword(uint32_t *txLastWord, |
|
const uint8_t *buf, const struct spi_config *spi_cfg, |
|
size_t len) |
|
{ |
|
uint32_t word_size; |
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); |
|
|
|
if (word_size > 8) { |
|
*txLastWord = (((uint32_t)buf[len - 1U] << 8U) | |
|
(buf[len - 2U])); |
|
} else { |
|
*txLastWord = buf[len - 1U]; |
|
} |
|
|
|
*txLastWord |= (uint32_t)SPI_FIFOWR_EOT_MASK; |
|
|
|
*txLastWord |= ((uint32_t)SPI_DEASSERT_ALL & |
|
(~(uint32_t)SPI_DEASSERTNUM_SSEL((uint32_t)spi_cfg->slave))); |
|
|
|
/* set width of data - range asserted at entry */ |
|
*txLastWord |= SPI_FIFOWR_LEN(word_size - 1); |
|
} |
|
|
|
static void spi_mcux_prepare_txdummy(uint32_t *dummy, bool last_packet, |
|
const struct spi_config *spi_cfg) |
|
{ |
|
uint32_t word_size; |
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); |
|
|
|
if (last_packet) { |
|
*dummy |= (uint32_t)SPI_FIFOWR_EOT_MASK; |
|
} |
|
|
|
*dummy |= ((uint32_t)SPI_DEASSERT_ALL & |
|
(~(uint32_t)SPI_DEASSERTNUM_SSEL((uint32_t)spi_cfg->slave))); |
|
|
|
/* set width of data - range asserted at entry */ |
|
*dummy |= SPI_FIFOWR_LEN(word_size - 1); |
|
} |
|
|
|
static int spi_mcux_dma_tx_load(const struct device *dev, const uint8_t *buf, |
|
const struct spi_config *spi_cfg, size_t len, bool last_packet) |
|
{ |
|
const struct spi_mcux_config *cfg = dev->config; |
|
struct spi_mcux_data *data = dev->data; |
|
struct dma_block_config *blk_cfg; |
|
int ret; |
|
SPI_Type *base = cfg->base; |
|
uint32_t word_size; |
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); |
|
|
|
/* remember active TX DMA channel (used in callback) */ |
|
struct stream *stream = &data->dma_tx; |
|
|
|
blk_cfg = &stream->dma_blk_cfg[0]; |
|
|
|
/* prepare the block for this TX DMA channel */ |
|
memset(blk_cfg, 0, sizeof(struct dma_block_config)); |
|
|
|
/* tx direction has memory as source and periph as dest. */ |
|
if (buf == NULL) { |
|
data->dummy_tx_buffer = 0; |
|
data->last_word = 0; |
|
spi_mcux_prepare_txdummy(&data->dummy_tx_buffer, last_packet, spi_cfg); |
|
|
|
if (last_packet && |
|
((word_size > 8) ? (len > 2U) : (len > 1U))) { |
|
spi_mcux_prepare_txdummy(&data->last_word, last_packet, spi_cfg); |
|
blk_cfg->source_address = (uint32_t)&data->dummy_tx_buffer; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = (word_size > 8) ? |
|
(len - 2U) : (len - 1U); |
|
blk_cfg->next_block = &stream->dma_blk_cfg[1]; |
|
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
|
|
blk_cfg = &stream->dma_blk_cfg[1]; |
|
|
|
/* prepare the block for this TX DMA channel */ |
|
memset(blk_cfg, 0, sizeof(struct dma_block_config)); |
|
blk_cfg->source_address = (uint32_t)&data->last_word; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = sizeof(uint32_t); |
|
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
} else { |
|
blk_cfg->source_address = (uint32_t)&data->dummy_tx_buffer; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = len; |
|
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
} |
|
} else { |
|
if (last_packet) { |
|
spi_mcux_prepare_txlastword(&data->last_word, buf, spi_cfg, len); |
|
} |
|
/* If last packet and data transfer frame is bigger then 1, |
|
* use dma descriptor to send the last data. |
|
*/ |
|
if (last_packet && |
|
((word_size > 8) ? (len > 2U) : (len > 1U))) { |
|
blk_cfg->source_address = (uint32_t)buf; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = (word_size > 8) ? |
|
(len - 2U) : (len - 1U); |
|
blk_cfg->next_block = &stream->dma_blk_cfg[1]; |
|
|
|
blk_cfg = &stream->dma_blk_cfg[1]; |
|
|
|
/* prepare the block for this TX DMA channel */ |
|
memset(blk_cfg, 0, sizeof(struct dma_block_config)); |
|
blk_cfg->source_address = (uint32_t)&data->last_word; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = sizeof(uint32_t); |
|
blk_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
blk_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; |
|
} else { |
|
blk_cfg->source_address = (uint32_t)buf; |
|
blk_cfg->dest_address = (uint32_t)&base->FIFOWR; |
|
blk_cfg->block_size = len; |
|
} |
|
} |
|
|
|
/* Enables the DMA request from SPI txFIFO */ |
|
base->FIFOCFG |= SPI_FIFOCFG_DMATX_MASK; |
|
|
|
/* 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; |
|
} |
|
|
|
uint32_t tmpData = 0U; |
|
|
|
spi_mcux_prepare_txdummy(&tmpData, last_packet, spi_cfg); |
|
|
|
/* 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. |
|
*/ |
|
if ((last_packet) && |
|
((word_size > 8) ? (len == 2U) : (len == 1U))) { |
|
*((uint16_t *)((uint32_t)&base->FIFOWR) + 1) = (uint16_t)(tmpData >> 16U); |
|
} else { |
|
/* Clear the SPI_FIFOWR_EOT_MASK bit when data is not the last */ |
|
tmpData &= (~(uint32_t)SPI_FIFOWR_EOT_MASK); |
|
*((uint16_t *)((uint32_t)&base->FIFOWR) + 1) = (uint16_t)(tmpData >> 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) |
|
{ |
|
const struct spi_mcux_config *cfg = dev->config; |
|
struct spi_mcux_data *data = dev->data; |
|
struct dma_block_config *blk_cfg; |
|
int ret; |
|
SPI_Type *base = cfg->base; |
|
|
|
/* retrieve active RX DMA channel (used in callback) */ |
|
struct stream *stream = &data->dma_rx; |
|
|
|
blk_cfg = &stream->dma_blk_cfg[0]; |
|
|
|
/* prepare the block for this RX DMA channel */ |
|
memset(blk_cfg, 0, sizeof(struct dma_block_config)); |
|
blk_cfg->block_size = len; |
|
|
|
/* rx direction has periph as source and mem as dest. */ |
|
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->source_address = (uint32_t)&base->FIFORD; |
|
|
|
/* direction is given by the DT */ |
|
stream->dma_cfg.head_block = blk_cfg; |
|
stream->dma_cfg.user_data = (struct device *)dev; |
|
|
|
/* Enables the DMA request from SPI rxFIFO */ |
|
base->FIFOCFG |= SPI_FIFOCFG_DMARX_MASK; |
|
|
|
/* 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_move_buffers(const struct device *dev, size_t len, |
|
const struct spi_config *spi_cfg, bool last_packet) |
|
{ |
|
struct spi_mcux_data *data = dev->data; |
|
int ret; |
|
|
|
ret = spi_mcux_dma_rx_load(dev, data->ctx.rx_buf, len); |
|
|
|
if (ret != 0) { |
|
return ret; |
|
} |
|
|
|
ret = spi_mcux_dma_tx_load(dev, data->ctx.tx_buf, spi_cfg, |
|
len, last_packet); |
|
|
|
return ret; |
|
} |
|
|
|
static int wait_dma_rx_tx_done(const struct device *dev) |
|
{ |
|
struct spi_mcux_data *data = dev->data; |
|
int ret = -1; |
|
|
|
while (1) { |
|
ret = spi_context_wait_for_completion(&data->ctx); |
|
if (data->status_flags & SPI_MCUX_FLEXCOMM_DMA_ERROR_FLAG) { |
|
return -EIO; |
|
} |
|
|
|
if ((data->status_flags & SPI_MCUX_FLEXCOMM_DMA_DONE_FLAG) == |
|
SPI_MCUX_FLEXCOMM_DMA_DONE_FLAG) { |
|
return 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; |
|
uint32_t word_size; |
|
uint16_t data_size; |
|
|
|
spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg); |
|
|
|
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); |
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); |
|
|
|
data_size = (word_size > 8) ? (sizeof(uint16_t)) : (sizeof(uint8_t)); |
|
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; |
|
|
|
while (data->ctx.rx_len > 0 || data->ctx.tx_len > 0) { |
|
size_t dma_len; |
|
|
|
/* last is used to deassert chip select if this |
|
* is the last transfer in the set. |
|
*/ |
|
bool last = false; |
|
|
|
if (data->ctx.rx_len == 0) { |
|
dma_len = data->ctx.tx_len; |
|
last = true; |
|
} else if (data->ctx.tx_len == 0) { |
|
dma_len = data->ctx.rx_len; |
|
last = true; |
|
} else if (data->ctx.tx_len == data->ctx.rx_len) { |
|
dma_len = data->ctx.rx_len; |
|
last = true; |
|
} else { |
|
dma_len = MIN(data->ctx.tx_len, data->ctx.rx_len); |
|
last = false; |
|
} |
|
|
|
/* at this point, last just means whether or not |
|
* this transfer will completely cover |
|
* the current tx/rx buffer in data->ctx |
|
* or require additional transfers because |
|
* the two buffers are not the same size. |
|
* |
|
* if it covers the current ctx tx/rx buffers, then |
|
* we'll move to the next pair of buffers (if any) |
|
* after the transfer, but if there are |
|
* no more buffer pairs, then this is the last |
|
* transfer in the set and we need to deassert CS. |
|
*/ |
|
if (last) { |
|
/* this dma transfer should cover |
|
* the entire current data->ctx set |
|
* of buffers. if there are more |
|
* buffers in the set, then we don't |
|
* want to deassert CS. |
|
*/ |
|
if ((data->ctx.tx_count > 1) || |
|
(data->ctx.rx_count > 1)) { |
|
/* more buffers to transfer so |
|
* this isn't last |
|
*/ |
|
last = false; |
|
} |
|
} |
|
|
|
data->status_flags = 0; |
|
|
|
ret = spi_mcux_dma_move_buffers(dev, dma_len, spi_cfg, last); |
|
if (ret != 0) { |
|
break; |
|
} |
|
|
|
ret = wait_dma_rx_tx_done(dev); |
|
if (ret != 0) { |
|
break; |
|
} |
|
|
|
/* wait until TX FIFO is really empty */ |
|
while (0U == (base->FIFOSTAT & SPI_FIFOSTAT_TXEMPTY_MASK)) { |
|
} |
|
|
|
spi_context_update_tx(&data->ctx, 1, dma_len); |
|
spi_context_update_rx(&data->ctx, 1, dma_len); |
|
} |
|
|
|
base->FIFOCFG &= ~SPI_FIFOCFG_DMATX_MASK; |
|
base->FIFOCFG &= ~SPI_FIFOCFG_DMARX_MASK; |
|
|
|
spi_context_cs_control(&data->ctx, false); |
|
|
|
out: |
|
spi_context_release(&data->ctx, ret); |
|
|
|
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; |
|
|
|
spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg); |
|
|
|
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); |
|
|
|
ret = spi_context_wait_for_completion(&data->ctx); |
|
out: |
|
spi_context_release(&data->ctx, ret); |
|
|
|
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(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; |
|
} |
|
|
|
config->irq_config_func(dev); |
|
|
|
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 const struct spi_driver_api 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, |
|
}; |
|
|
|
#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)); \ |
|
} |
|
|
|
#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 = MEMORY_TO_PERIPHERAL, \ |
|
.dma_callback = spi_mcux_dma_callback, \ |
|
.complete_callback_en = true, \ |
|
.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) \ |
|
}; \ |
|
DEVICE_DT_INST_DEFINE(id, \ |
|
spi_mcux_init, \ |
|
NULL, \ |
|
&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)
|
|
|