Skip to content

LPSPI optimizations 5/19/25 #90182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/releases/migration-guide-4.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,9 @@ OpenThread
SPI
===

* Renamed ``CONFIG_SPI_MCUX_LPSPI`` to :kconfig:option:`CONFIG_SPI_NXP_LPSPI`,
and similar for any child configs for that driver, including
:kconfig:option:`CONFIG_SPI_NXP_LPSPI_DMA` and :kconfig:option:`CONFIG_SPI_NXP_LPSPI_CPU`.
* Renamed the device tree property ``port_sel`` to ``port-sel``.
* Renamed the device tree property ``chip_select`` to ``chip-select``.
* The binding file for :dtcompatible:`andestech,atcspi200` has been renamed to have a name
Expand Down
4 changes: 2 additions & 2 deletions drivers/clock_control/clock_control_mcux_ccm.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(clock_control);

#ifdef CONFIG_SPI_MCUX_LPSPI
#ifdef CONFIG_SPI_NXP_LPSPI
static const clock_name_t lpspi_clocks[] = {
kCLOCK_Usb1PllPfd1Clk,
kCLOCK_Usb1PllPfd0Clk,
Expand Down Expand Up @@ -207,7 +207,7 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev,
break;
#endif

#ifdef CONFIG_SPI_MCUX_LPSPI
#ifdef CONFIG_SPI_NXP_LPSPI
case IMX_CCM_LPSPI_CLK:
{
uint32_t lpspi_mux = CLOCK_GetMux(kCLOCK_LpspiMux);
Expand Down
4 changes: 2 additions & 2 deletions drivers/clock_control/clock_control_mcux_ccm_rev2.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev,
break;
#endif

#ifdef CONFIG_SPI_MCUX_LPSPI
#ifdef CONFIG_SPI_NXP_LPSPI
#if defined(CONFIG_SOC_SERIES_IMXRT118X)
case IMX_CCM_LPSPI0102_CLK:
clock_root = kCLOCK_Root_Lpspi0102 + instance;
Expand All @@ -91,7 +91,7 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev,
clock_root = kCLOCK_Root_Lpspi1 + instance;
break;
#endif /* CONFIG_SOC_SERIES_IMXRT118X */
#endif /* CONFIG_SPI_MCUX_LPSPI */
#endif /* CONFIG_SPI_NXP_LPSPI */

#ifdef CONFIG_UART_MCUX_LPUART
#if defined(CONFIG_SOC_SERIES_IMXRT118X)
Expand Down
4 changes: 2 additions & 2 deletions drivers/clock_control/clock_control_mcux_syscon.c
Original file line number Diff line number Diff line change
Expand Up @@ -555,14 +555,14 @@ static int mcux_lpc_syscon_clock_control_get_subsys_rate(const struct device *de
break;
#endif /* defined(CONFIG_DT_HAS_NXP_XSPI_ENABLED) */

#if (defined(CONFIG_SPI_MCUX_LPSPI) && CONFIG_SOC_SERIES_MCXA)
#if (defined(CONFIG_SPI_NXP_LPSPI) && CONFIG_SOC_SERIES_MCXA)
case MCUX_LPSPI0_CLK:
*rate = CLOCK_GetLpspiClkFreq(0);
break;
case MCUX_LPSPI1_CLK:
*rate = CLOCK_GetLpspiClkFreq(1);
break;
#endif /* defined(CONFIG_SPI_MCUX_LPSPI) */
#endif /* defined(CONFIG_SPI_NXP_LPSPI) */
}

return 0;
Expand Down
6 changes: 3 additions & 3 deletions drivers/spi/spi_nxp_lpspi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright 2024 NXP

zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI spi_nxp_lpspi_common.c)
zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI_CPU spi_nxp_lpspi.c)
zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI_DMA spi_nxp_lpspi_dma.c)
zephyr_library_sources_ifdef(CONFIG_SPI_NXP_LPSPI spi_nxp_lpspi_common.c)
zephyr_library_sources_ifdef(CONFIG_SPI_NXP_LPSPI_CPU spi_nxp_lpspi.c)
zephyr_library_sources_ifdef(CONFIG_SPI_NXP_LPSPI_DMA spi_nxp_lpspi_dma.c)
25 changes: 19 additions & 6 deletions drivers/spi/spi_nxp_lpspi/Kconfig
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2018, 2024-2025 NXP
# SPDX-License-Identifier: Apache-2.0

config SPI_MCUX_LPSPI
config SPI_NXP_LPSPI
bool "NXP LPSPI peripheral"
default y
depends on DT_HAS_NXP_LPSPI_ENABLED
Expand All @@ -10,9 +10,9 @@ config SPI_MCUX_LPSPI
help
Enable driver support for NXP LPSPI.

if SPI_MCUX_LPSPI
if SPI_NXP_LPSPI

config SPI_MCUX_LPSPI_DMA
config SPI_NXP_LPSPI_DMA
bool "NXP LPSPI DMA-based Driver"
default y
select DMA
Expand All @@ -27,13 +27,26 @@ config SPI_MCUX_LPSPI_DMA
immediately with CPU, so there could be more latency between
the point of requesting a transfer and when it actually starts.

config SPI_MCUX_LPSPI_CPU
config SPI_NXP_LPSPI_CPU
bool "NXP LPSPI CPU-based driver"
default y
depends on $(dt_compat_any_not_has_prop,$(DT_COMPAT_NXP_LPSPI),dmas) || !SPI_MCUX_LPSPI_DMA
depends on $(dt_compat_any_not_has_prop,$(DT_COMPAT_NXP_LPSPI),dmas) || !SPI_NXP_LPSPI_DMA
help
Enable "normal" CPU based SPI driver for LPSPI.
This has lower latency than DMA-based driver but over the
longer transfers will likely have less bandwidth and use more CPU time.

endif # SPI_MCUX_LPSPI
config SPI_NXP_LPSPI_TXFIFO_WAIT_CYCLES
int "Number of CPU cycles to wait on TX fifo empty"
default 0 if DEBUG
default 10000
Comment on lines +41 to +42

This comment was marked as resolved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@decsny now zephyr project enables copilot review, but copilot is not good at Kconfig staff.

help
This option most likely does not need changed.
The drivers tend to need to wait on confirming the transmit command
is consumed by the hardware by checking of the TX fifo is emptied.
This option gives a maximum number of CPU cycles to wait on that check.
The special value of 0 means infinite, which can be useful for debugging
for if there is some programming error that causes TX fifo not to empty.
The default of 10000 is arbitrary.

endif # SPI_NXP_LPSPI
87 changes: 57 additions & 30 deletions drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ LOG_MODULE_DECLARE(spi_lpspi, CONFIG_SPI_LOG_LEVEL);
#include "spi_nxp_lpspi_priv.h"

struct lpspi_driver_data {
size_t fill_len;
uint8_t word_size_bytes;
};

Expand Down Expand Up @@ -113,20 +112,19 @@ static inline void lpspi_fill_tx_fifo(const struct device *dev, const uint8_t *b
struct lpspi_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
uint8_t word_size = lpspi_data->word_size_bytes;
size_t buf_remaining_bytes = buf_len * word_size;
size_t offset = 0;
uint32_t next_word;
uint32_t next_word_bytes;

for (int word_count = 0; word_count < fill_len; word_count++) {
next_word_bytes = MIN(word_size, buf_remaining_bytes);
next_word_bytes = MIN(word_size, buf_len);
next_word = lpspi_next_tx_word(dev, buf, offset, next_word_bytes);
base->TDR = next_word;
offset += word_size;
buf_remaining_bytes -= word_size;
buf_len -= word_size;
}

LOG_DBG("Filled TX FIFO to %d words (%d bytes)", lpspi_data->fill_len, offset);
LOG_DBG("Filled TX FIFO to %d words (%d bytes)", fill_len, offset);
}

/* just fills TX fifo with the specified amount of NOPS */
Expand All @@ -145,14 +143,14 @@ static void lpspi_fill_tx_fifo_nop(const struct device *dev, size_t fill_len)
static void lpspi_next_tx_fill(const struct device *dev)
{
const struct lpspi_config *config = dev->config;
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
struct lpspi_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;
size_t fill_len;
uint8_t left_in_fifo = tx_fifo_cur_len(base);
size_t fill_len = MIN(ctx->tx_len, config->tx_fifo_size - left_in_fifo);
size_t actual_filled = 0;

fill_len = MIN(ctx->tx_len, config->tx_fifo_size);

const struct spi_buf *current_buf = ctx->current_tx;
const uint8_t *cur_buf_pos = ctx->tx_buf;
size_t cur_buf_len_left = ctx->tx_len;
Expand Down Expand Up @@ -187,35 +185,27 @@ static void lpspi_next_tx_fill(const struct device *dev)
actual_filled += next_buf_fill;
}

lpspi_data->fill_len = actual_filled;
spi_context_update_tx(ctx, lpspi_data->word_size_bytes, actual_filled);
}

static inline void lpspi_handle_tx_irq(const struct device *dev)
{
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
struct lpspi_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;

base->SR = LPSPI_SR_TDF_MASK;

/* If we receive a TX interrupt but no more data is available,
* we can be sure that all data has been written to the bus.
* we can be sure that all data has been written to the fifo.
* Disable the interrupt to signal that we are done.
*/
if (!spi_context_tx_on(ctx)) {
base->IER &= ~LPSPI_IER_TDIE_MASK;
return;
}

while (spi_context_tx_on(ctx) && lpspi_data->fill_len > 0) {
size_t this_buf_words_sent = MIN(lpspi_data->fill_len, ctx->tx_len);

spi_context_update_tx(ctx, lpspi_data->word_size_bytes, this_buf_words_sent);
lpspi_data->fill_len -= this_buf_words_sent;
}

lpspi_next_tx_fill(data->dev);
lpspi_next_tx_fill(dev);
}

static inline void lpspi_end_xfer(const struct device *dev)
Expand Down Expand Up @@ -262,21 +252,55 @@ static void lpspi_isr(const struct device *dev)
return;
}

/* the lpspi v1 has an errata where it doesn't clock the last bit
* in continuous mode until you write the TCR
*/
bool likely_stalling_v1 = data->major_version < 2 &&
(DIV_ROUND_UP(spi_context_rx_len_left(ctx, word_size_bytes), word_size_bytes) == 1);

if (spi_context_rx_on(ctx)) {
/* capture these values because they could change during this code block */
size_t rx_fifo_len = rx_fifo_cur_len(base);
size_t tx_fifo_len = tx_fifo_cur_len(base);

/*
* Goal here is to provide the number of TX NOPS to match the amount of RX
* we have left to receive, so that we provide the correct number of
* clocks to the bus, since the clocks only happen when TX data is being sent.
*
* The expected RX left is essentially what is left in the spi transfer
* minus what we have just got in the fifo, but prevent underflow of this
* subtraction since it is unsigned.
*/
size_t expected_rx_left = rx_fifo_len < ctx->rx_len ? ctx->rx_len - rx_fifo_len : 0;
size_t max_fill = MIN(expected_rx_left, config->rx_fifo_size);
size_t tx_current_fifo_len = tx_fifo_cur_len(base);

size_t fill_len = tx_current_fifo_len < ctx->rx_len ?
max_fill - tx_current_fifo_len : 0;

lpspi_fill_tx_fifo_nop(dev, fill_len);
lpspi_data->fill_len = fill_len;
/*
* We know the expected amount of RX we have left but only fill up to the
* max of the RX fifo size so that we don't have some overflow of the FIFO later.
* Similarly, we shouldn't overfill the TX fifo with too many NOPs.
*/
size_t tx_fifo_space_left = config->tx_fifo_size - tx_fifo_len;
size_t rx_fifo_space_left = config->rx_fifo_size - rx_fifo_len;
size_t max_fifo_fill = MIN(tx_fifo_space_left, rx_fifo_space_left);
size_t max_fill = MIN(max_fifo_fill, expected_rx_left);

if (likely_stalling_v1 && max_fill > 0) {
max_fill -= 1;
}

/* If we already have some words in the tx fifo, we should count those */
if (max_fill > tx_fifo_len) {
max_fill -= tx_fifo_len;
} else {
max_fill = 0;
}

/* So now we want to fill the fifo with the max amount of NOPs */
lpspi_fill_tx_fifo_nop(dev, max_fill);
}

if ((DIV_ROUND_UP(spi_context_rx_len_left(ctx, word_size_bytes), word_size_bytes) == 1) &&
(LPSPI_VERID_MAJOR(base->VERID) < 2)) {
if (likely_stalling_v1) {
/* Due to stalling behavior on older LPSPI,
* need to end xfer in order to get last bit clocked out on bus.
*/
Expand Down Expand Up @@ -311,7 +335,7 @@ static int transceive(const struct device *dev, const struct spi_config *spi_cfg

spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, lpspi_data->word_size_bytes);

ret = spi_mcux_configure(dev, spi_cfg);
ret = lpspi_configure(dev, spi_cfg);
if (ret) {
goto error;
}
Expand All @@ -337,7 +361,10 @@ static int transceive(const struct device *dev, const struct spi_config *spi_cfg
base->TCR |= LPSPI_TCR_CONT_MASK;
}
/* tcr is written to tx fifo */
lpspi_wait_tx_fifo_empty(dev);
ret = lpspi_wait_tx_fifo_empty(dev);
if (ret) {
return ret;
}

/* start the transfer sequence which are handled by irqs */
lpspi_next_tx_fill(dev);
Expand Down Expand Up @@ -424,7 +451,7 @@ static int lpspi_init(const struct device *dev)
#define SPI_LPSPI_INIT_IF_DMA(n) IF_DISABLED(SPI_NXP_LPSPI_HAS_DMAS(n), (LPSPI_INIT(n)))

#define SPI_LPSPI_INIT(n) \
COND_CODE_1(CONFIG_SPI_MCUX_LPSPI_DMA, \
COND_CODE_1(CONFIG_SPI_NXP_LPSPI_DMA, \
(SPI_LPSPI_INIT_IF_DMA(n)), (LPSPI_INIT(n)))

DT_INST_FOREACH_STATUS_OKAY(SPI_LPSPI_INIT)
Loading
Loading