drivers: mcux: flexio: Added MCUX FlexIO SPI driver

Added SPI driver using FlexIO.

Signed-off-by: Mikhail Siomin <victorovich.01@mail.ru>
diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt
index cd46998..d5abf88 100644
--- a/drivers/spi/CMakeLists.txt
+++ b/drivers/spi/CMakeLists.txt
@@ -11,6 +11,7 @@
 zephyr_library_sources_ifdef(CONFIG_SPI_STM32		spi_ll_stm32.c)
 zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_DSPI	spi_mcux_dspi.c)
 zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXCOMM	spi_mcux_flexcomm.c)
+zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXIO	spi_mcux_flexio.c)
 zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI	spi_mcux_lpspi.c)
 zephyr_library_sources_ifdef(CONFIG_SPI_SAM		spi_sam.c)
 zephyr_library_sources_ifdef(CONFIG_SPI_SAM0		spi_sam0.c)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2cb7c83..acb37b3 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -75,6 +75,8 @@
 
 source "drivers/spi/Kconfig.mcux_flexcomm"
 
+source "drivers/spi/Kconfig.mcux_flexio"
+
 source "drivers/spi/Kconfig.mcux_lpspi"
 
 source "drivers/spi/Kconfig.rv32m1_lpspi"
diff --git a/drivers/spi/Kconfig.mcux_flexio b/drivers/spi/Kconfig.mcux_flexio
new file mode 100644
index 0000000..82e04a5
--- /dev/null
+++ b/drivers/spi/Kconfig.mcux_flexio
@@ -0,0 +1,11 @@
+# Copyright (c) 2024, STRIM, ALC
+# SPDX-License-Identifier: Apache-2.0
+
+config SPI_MCUX_FLEXIO
+	bool "MCUX FlexIO SPI driver"
+	default y
+	depends on DT_HAS_NXP_FLEXIO_SPI_ENABLED
+	depends on CLOCK_CONTROL
+	select MCUX_FLEXIO
+	help
+	  Enable support for MCUX FlexIO SPI driver.
diff --git a/drivers/spi/spi_mcux_flexio.c b/drivers/spi/spi_mcux_flexio.c
new file mode 100644
index 0000000..69bb996
--- /dev/null
+++ b/drivers/spi/spi_mcux_flexio.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2024, STRIM, ALC
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT nxp_flexio_spi
+
+#include <errno.h>
+#include <zephyr/drivers/spi.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 baudrate. */
+	timerDiv = (uint16_t)(srcClock_Hz / masterConfig->baudRate_Bps);
+	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 const struct spi_driver_api spi_mcux_driver_api = {
+	.transceive = spi_mcux_transceive,
+#ifdef CONFIG_SPI_ASYNC
+	.transceive_async = spi_mcux_transceive_async,
+#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)	\
+	};								\
+									\
+	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)
diff --git a/dts/bindings/spi/nxp,flexio-spi.yaml b/dts/bindings/spi/nxp,flexio-spi.yaml
new file mode 100644
index 0000000..3c64d76
--- /dev/null
+++ b/dts/bindings/spi/nxp,flexio-spi.yaml
@@ -0,0 +1,27 @@
+# Copyright (c) 2024, STRIM, ALC
+# SPDX-License-Identifier: Apache-2.0
+
+description: NXP FlexIO SPI controller
+
+compatible: "nxp,flexio-spi"
+
+include: [spi-controller.yaml, "pinctrl-device.yaml"]
+
+properties:
+  sdo-pin:
+    type: int
+    required: true
+    description: |
+      Pin select for data output.
+
+  sdi-pin:
+    type: int
+    required: true
+    description: |
+      Pin select for data input.
+
+  sck-pin:
+    type: int
+    required: true
+    description: |
+      Pin select for clock.