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.
461 lines
14 KiB
461 lines
14 KiB
/* |
|
* Copyright (c) 2024, STRIM, ALC |
|
* Copyright 2024-2025 NXP |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT nxp_flexio_spi |
|
|
|
#include <errno.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/drivers/spi/rtio.h> |
|
#include <zephyr/drivers/clock_control.h> |
|
#include <fsl_flexio_spi.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/drivers/pinctrl.h> |
|
#include <zephyr/drivers/misc/nxp_flexio/nxp_flexio.h> |
|
|
|
LOG_MODULE_REGISTER(spi_mcux_flexio_spi, CONFIG_SPI_LOG_LEVEL); |
|
|
|
#include "spi_context.h" |
|
|
|
|
|
struct spi_mcux_flexio_config { |
|
FLEXIO_SPI_Type *flexio_spi; |
|
const struct device *flexio_dev; |
|
const struct pinctrl_dev_config *pincfg; |
|
const struct nxp_flexio_child *child; |
|
}; |
|
|
|
struct spi_mcux_flexio_data { |
|
const struct device *dev; |
|
flexio_spi_master_handle_t handle; |
|
struct spi_context ctx; |
|
size_t transfer_len; |
|
uint8_t transfer_flags; |
|
}; |
|
|
|
|
|
static void spi_mcux_transfer_next_packet(const struct device *dev) |
|
{ |
|
const struct spi_mcux_flexio_config *config = dev->config; |
|
struct spi_mcux_flexio_data *data = dev->data; |
|
struct spi_context *ctx = &data->ctx; |
|
flexio_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.flags = kFLEXIO_SPI_csContinuous | data->transfer_flags; |
|
|
|
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; |
|
} |
|
|
|
data->transfer_len = transfer.dataSize; |
|
|
|
status = FLEXIO_SPI_MasterTransferNonBlocking(config->flexio_spi, &data->handle, |
|
&transfer); |
|
if (status != kStatus_Success) { |
|
LOG_ERR("Transfer could not start"); |
|
} |
|
} |
|
|
|
static int spi_mcux_flexio_isr(void *user_data) |
|
{ |
|
const struct device *dev = (const struct device *)user_data; |
|
const struct spi_mcux_flexio_config *config = dev->config; |
|
struct spi_mcux_flexio_data *data = dev->data; |
|
|
|
FLEXIO_SPI_MasterTransferHandleIRQ(config->flexio_spi, &data->handle); |
|
|
|
return 0; |
|
} |
|
|
|
static void spi_mcux_master_transfer_callback(FLEXIO_SPI_Type *flexio_spi, |
|
flexio_spi_master_handle_t *handle, status_t status, void *userData) |
|
{ |
|
struct spi_mcux_flexio_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 void spi_flexio_master_init(FLEXIO_SPI_Type *base, flexio_spi_master_config_t *masterConfig, |
|
uint8_t pol, uint32_t srcClock_Hz) |
|
{ |
|
assert(base != NULL); |
|
assert(masterConfig != NULL); |
|
|
|
flexio_shifter_config_t shifterConfig; |
|
flexio_timer_config_t timerConfig; |
|
uint32_t ctrlReg = 0; |
|
uint16_t timerDiv = 0; |
|
uint16_t timerCmp = 0; |
|
|
|
/* Clear the shifterConfig & timerConfig struct. */ |
|
(void)memset(&shifterConfig, 0, sizeof(shifterConfig)); |
|
(void)memset(&timerConfig, 0, sizeof(timerConfig)); |
|
|
|
/* Configure FLEXIO SPI Master */ |
|
ctrlReg = base->flexioBase->CTRL; |
|
ctrlReg &= ~(FLEXIO_CTRL_DOZEN_MASK | FLEXIO_CTRL_DBGE_MASK | |
|
FLEXIO_CTRL_FASTACC_MASK | FLEXIO_CTRL_FLEXEN_MASK); |
|
ctrlReg |= (FLEXIO_CTRL_DBGE(masterConfig->enableInDebug) | |
|
FLEXIO_CTRL_FASTACC(masterConfig->enableFastAccess) | |
|
FLEXIO_CTRL_FLEXEN(masterConfig->enableMaster)); |
|
if (!masterConfig->enableInDoze) { |
|
ctrlReg |= FLEXIO_CTRL_DOZEN_MASK; |
|
} |
|
|
|
base->flexioBase->CTRL = ctrlReg; |
|
|
|
/* Do hardware configuration. */ |
|
/* 1. Configure the shifter 0 for tx. */ |
|
shifterConfig.timerSelect = base->timerIndex[0]; |
|
shifterConfig.pinConfig = kFLEXIO_PinConfigOutput; |
|
shifterConfig.pinSelect = base->SDOPinIndex; |
|
shifterConfig.pinPolarity = kFLEXIO_PinActiveHigh; |
|
shifterConfig.shifterMode = kFLEXIO_ShifterModeTransmit; |
|
shifterConfig.inputSource = kFLEXIO_ShifterInputFromPin; |
|
if (masterConfig->phase == kFLEXIO_SPI_ClockPhaseFirstEdge) { |
|
shifterConfig.timerPolarity = kFLEXIO_ShifterTimerPolarityOnNegitive; |
|
shifterConfig.shifterStop = kFLEXIO_ShifterStopBitDisable; |
|
shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable; |
|
} else { |
|
shifterConfig.timerPolarity = kFLEXIO_ShifterTimerPolarityOnPositive; |
|
shifterConfig.shifterStop = kFLEXIO_ShifterStopBitLow; |
|
shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnShift; |
|
} |
|
|
|
FLEXIO_SetShifterConfig(base->flexioBase, base->shifterIndex[0], &shifterConfig); |
|
|
|
/* 2. Configure the shifter 1 for rx. */ |
|
shifterConfig.timerSelect = base->timerIndex[0]; |
|
shifterConfig.pinConfig = kFLEXIO_PinConfigOutputDisabled; |
|
shifterConfig.pinSelect = base->SDIPinIndex; |
|
shifterConfig.pinPolarity = kFLEXIO_PinActiveHigh; |
|
shifterConfig.shifterMode = kFLEXIO_ShifterModeReceive; |
|
shifterConfig.inputSource = kFLEXIO_ShifterInputFromPin; |
|
shifterConfig.shifterStop = kFLEXIO_ShifterStopBitDisable; |
|
shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable; |
|
if (masterConfig->phase == kFLEXIO_SPI_ClockPhaseFirstEdge) { |
|
shifterConfig.timerPolarity = kFLEXIO_ShifterTimerPolarityOnPositive; |
|
} else { |
|
shifterConfig.timerPolarity = kFLEXIO_ShifterTimerPolarityOnNegitive; |
|
} |
|
|
|
FLEXIO_SetShifterConfig(base->flexioBase, base->shifterIndex[1], &shifterConfig); |
|
|
|
/*3. Configure the timer 0 for SCK. */ |
|
timerConfig.triggerSelect = FLEXIO_TIMER_TRIGGER_SEL_SHIFTnSTAT(base->shifterIndex[0]); |
|
timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow; |
|
timerConfig.triggerSource = kFLEXIO_TimerTriggerSourceInternal; |
|
timerConfig.pinConfig = kFLEXIO_PinConfigOutput; |
|
timerConfig.pinSelect = base->SCKPinIndex; |
|
timerConfig.pinPolarity = pol ? kFLEXIO_PinActiveLow : kFLEXIO_PinActiveHigh; |
|
timerConfig.timerMode = kFLEXIO_TimerModeDual8BitBaudBit; |
|
timerConfig.timerOutput = kFLEXIO_TimerOutputZeroNotAffectedByReset; |
|
timerConfig.timerDecrement = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput; |
|
timerConfig.timerReset = kFLEXIO_TimerResetNever; |
|
timerConfig.timerDisable = kFLEXIO_TimerDisableOnTimerCompare; |
|
timerConfig.timerEnable = kFLEXIO_TimerEnableOnTriggerHigh; |
|
timerConfig.timerStop = kFLEXIO_TimerStopBitEnableOnTimerDisable; |
|
timerConfig.timerStart = kFLEXIO_TimerStartBitEnabled; |
|
/* Low 8-bits are used to configure baud rate. */ |
|
timerDiv = (uint16_t)(srcClock_Hz / masterConfig->baudRate_Bps); |
|
|
|
/* Add protection if the required baud rate overflows. |
|
* FLEXIO input freq can't meet required baud rate. Max baud rate can |
|
* not exceed 1/4 of input freq. You can raise input freq or lower |
|
* baud rate required to remove this warning. |
|
*/ |
|
if (timerDiv < 4) { |
|
timerDiv = 4; |
|
} |
|
/* If timeDiv is odd, get it to even. */ |
|
timerDiv += timerDiv & 1UL; |
|
|
|
if (masterConfig->baudRate_Bps != (srcClock_Hz / timerDiv)) { |
|
LOG_WRN("baud rate req:%uKbps, got:%uKbps", |
|
(uint32_t)(masterConfig->baudRate_Bps / 1000), |
|
(uint32_t)(srcClock_Hz / (timerDiv*1000))); |
|
} |
|
|
|
timerDiv = timerDiv / 2U - 1U; |
|
/* High 8-bits are used to configure shift clock edges(transfer width). */ |
|
timerCmp = ((uint16_t)masterConfig->dataMode * 2U - 1U) << 8U; |
|
timerCmp |= timerDiv; |
|
|
|
timerConfig.timerCompare = timerCmp; |
|
|
|
FLEXIO_SetTimerConfig(base->flexioBase, base->timerIndex[0], &timerConfig); |
|
} |
|
|
|
static int spi_mcux_flexio_configure(const struct device *dev, |
|
const struct spi_config *spi_cfg) |
|
{ |
|
const struct spi_mcux_flexio_config *config = dev->config; |
|
struct spi_mcux_flexio_data *data = dev->data; |
|
|
|
flexio_spi_master_config_t master_config; |
|
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; |
|
} |
|
|
|
if (SPI_OP_MODE_GET(spi_cfg->operation) != SPI_OP_MODE_MASTER) { |
|
LOG_ERR("Mode Slave not supported"); |
|
return -ENOTSUP; |
|
} |
|
|
|
FLEXIO_SPI_MasterGetDefaultConfig(&master_config); |
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); |
|
if ((word_size != 8) && (word_size != 16) && (word_size != 32)) { |
|
LOG_ERR("Word size %d must be 8, 16 or 32", word_size); |
|
return -EINVAL; |
|
} |
|
master_config.dataMode = word_size; |
|
|
|
if (spi_cfg->operation & SPI_TRANSFER_LSB) { |
|
if (word_size == 8) { |
|
data->transfer_flags = kFLEXIO_SPI_8bitLsb; |
|
} else if (word_size == 16) { |
|
data->transfer_flags = kFLEXIO_SPI_16bitLsb; |
|
} else { |
|
data->transfer_flags = kFLEXIO_SPI_32bitLsb; |
|
} |
|
} else { |
|
if (word_size == 8) { |
|
data->transfer_flags = kFLEXIO_SPI_8bitMsb; |
|
} else if (word_size == 16) { |
|
data->transfer_flags = kFLEXIO_SPI_16bitMsb; |
|
} else { |
|
data->transfer_flags = kFLEXIO_SPI_32bitMsb; |
|
} |
|
} |
|
|
|
if (nxp_flexio_get_rate(config->flexio_dev, &clock_freq)) { |
|
return -EINVAL; |
|
} |
|
|
|
master_config.phase = |
|
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) |
|
? kFLEXIO_SPI_ClockPhaseSecondEdge |
|
: kFLEXIO_SPI_ClockPhaseFirstEdge; |
|
|
|
master_config.baudRate_Bps = spi_cfg->frequency; |
|
spi_flexio_master_init(config->flexio_spi, &master_config, |
|
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL), clock_freq); |
|
|
|
FLEXIO_SPI_MasterTransferCreateHandle(config->flexio_spi, &data->handle, |
|
spi_mcux_master_transfer_callback, |
|
data); |
|
/* No SetDummyData() for FlexIO_SPI */ |
|
|
|
data->ctx.config = spi_cfg; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
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) |
|
{ |
|
const struct spi_mcux_flexio_config *config = dev->config; |
|
struct spi_mcux_flexio_data *data = dev->data; |
|
int ret; |
|
|
|
spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg); |
|
|
|
nxp_flexio_lock(config->flexio_dev); |
|
ret = spi_mcux_flexio_configure(dev, spi_cfg); |
|
nxp_flexio_unlock(config->flexio_dev); |
|
if (ret) { |
|
goto out; |
|
} |
|
|
|
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); |
|
|
|
spi_context_cs_control(&data->ctx, true); |
|
|
|
nxp_flexio_lock(config->flexio_dev); |
|
nxp_flexio_irq_disable(config->flexio_dev); |
|
|
|
spi_mcux_transfer_next_packet(dev); |
|
|
|
nxp_flexio_irq_enable(config->flexio_dev); |
|
nxp_flexio_unlock(config->flexio_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) |
|
{ |
|
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) |
|
{ |
|
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_flexio_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_flexio_config *config = dev->config; |
|
struct spi_mcux_flexio_data *data = dev->data; |
|
int err; |
|
|
|
err = nxp_flexio_child_attach(config->flexio_dev, config->child); |
|
if (err < 0) { |
|
return err; |
|
} |
|
|
|
err = spi_context_cs_configure_all(&data->ctx); |
|
if (err < 0) { |
|
return err; |
|
} |
|
|
|
spi_context_unlock_unconditionally(&data->ctx); |
|
|
|
data->dev = dev; |
|
|
|
/* TODO: DMA */ |
|
|
|
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); |
|
if (err) { |
|
return err; |
|
} |
|
|
|
spi_context_unlock_unconditionally(&data->ctx); |
|
|
|
return 0; |
|
} |
|
|
|
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, |
|
}; |
|
|
|
#define SPI_MCUX_FLEXIO_SPI_INIT(n) \ |
|
PINCTRL_DT_INST_DEFINE(n); \ |
|
\ |
|
static FLEXIO_SPI_Type flexio_spi_##n = { \ |
|
.flexioBase = (FLEXIO_Type *)DT_REG_ADDR(DT_INST_PARENT(n)), \ |
|
.SDOPinIndex = DT_INST_PROP(n, sdo_pin), \ |
|
.SDIPinIndex = DT_INST_PROP(n, sdi_pin), \ |
|
.SCKPinIndex = DT_INST_PROP(n, sck_pin), \ |
|
}; \ |
|
\ |
|
static const struct nxp_flexio_child nxp_flexio_spi_child_##n = { \ |
|
.isr = spi_mcux_flexio_isr, \ |
|
.user_data = (void *)DEVICE_DT_INST_GET(n), \ |
|
.res = { \ |
|
.shifter_index = flexio_spi_##n.shifterIndex, \ |
|
.shifter_count = ARRAY_SIZE(flexio_spi_##n.shifterIndex), \ |
|
.timer_index = flexio_spi_##n.timerIndex, \ |
|
.timer_count = ARRAY_SIZE(flexio_spi_##n.timerIndex) \ |
|
} \ |
|
}; \ |
|
\ |
|
static const struct spi_mcux_flexio_config spi_mcux_flexio_config_##n = { \ |
|
.flexio_spi = &flexio_spi_##n, \ |
|
.flexio_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ |
|
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
|
.child = &nxp_flexio_spi_child_##n, \ |
|
}; \ |
|
\ |
|
static struct spi_mcux_flexio_data spi_mcux_flexio_data_##n = { \ |
|
SPI_CONTEXT_INIT_LOCK(spi_mcux_flexio_data_##n, ctx), \ |
|
SPI_CONTEXT_INIT_SYNC(spi_mcux_flexio_data_##n, ctx), \ |
|
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx) \ |
|
}; \ |
|
\ |
|
SPI_DEVICE_DT_INST_DEFINE(n, spi_mcux_init, NULL, \ |
|
&spi_mcux_flexio_data_##n, \ |
|
&spi_mcux_flexio_config_##n, POST_KERNEL, \ |
|
CONFIG_SPI_INIT_PRIORITY, \ |
|
&spi_mcux_driver_api); \ |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SPI_MCUX_FLEXIO_SPI_INIT)
|
|
|