Skip to content

NXP LPSPI: Add support for Peripheral (slave) mode #87144

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
26 changes: 26 additions & 0 deletions boards/nxp/frdm_mcxa156/dts/lpspi1_header.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2025 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/

&lpspi1 {
status = "okay";
pinctrl-0 = <&pinmux_lpspi1>;
pinctrl-names = "default";
};

&pinctrl {
/* J2 pins 12, 10, 8, 6 -> CLK, MISO, MOSI, PCS */
pinmux_lpspi1: pinmux_lpspi1 {
group0 {
pinmux = <LPSPI1_SDO_P2_13>,
<LPSPI1_SCK_P2_12>,
<LPSPI1_SDI_P2_16>,
<LPSPI1_PCS1_P2_6>;
slew-rate = "fast";
drive-strength = "low";
input-enable;
};
};
};
154 changes: 113 additions & 41 deletions drivers/spi/spi_nxp_lpspi/spi_nxp_lpspi.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ LOG_MODULE_REGISTER(spi_mcux_lpspi, CONFIG_SPI_LOG_LEVEL);
struct lpspi_driver_data {
size_t fill_len;
uint8_t word_size_bytes;
uint8_t lpspi_op_mode;
};

static inline uint8_t rx_fifo_cur_len(LPSPI_Type *base)
Expand Down Expand Up @@ -97,68 +98,105 @@ static inline void lpspi_handle_rx_irq(const struct device *dev)
}
}

static inline uint32_t lpspi_next_tx_word(const struct device *dev, int offset)
/* constructs the next word from the buffer */
static inline uint32_t lpspi_next_tx_word(const struct device *dev, const uint8_t *buf,
int offset, size_t max_bytes)
{
struct spi_mcux_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;
const uint8_t *byte = ctx->tx_buf + offset;
uint32_t num_bytes = MIN(lpspi_data->word_size_bytes, ctx->tx_len);
const uint8_t *byte = buf + offset;
uint32_t next_word = 0;

for (uint8_t i = 0; i < num_bytes; i++) {
for (uint8_t i = 0; i < max_bytes; i++) {
next_word |= byte[i] << (BITS_PER_BYTE * i);
}

return next_word;
}

static inline void lpspi_fill_tx_fifo(const struct device *dev)
/* fills the TX fifo with specified amount of data from the specified buffer */
static inline void lpspi_fill_tx_fifo(const struct device *dev, const uint8_t *buf,
size_t buf_len, size_t fill_len)
{
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
struct spi_mcux_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
size_t bytes_in_xfer = lpspi_data->fill_len * lpspi_data->word_size_bytes;
size_t offset;

for (offset = 0; offset < bytes_in_xfer; offset += lpspi_data->word_size_bytes) {
LPSPI_WriteData(base, lpspi_next_tx_word(dev, offset));
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 = lpspi_next_tx_word(dev, buf, offset, next_word_bytes);
LPSPI_WriteData(base, next_word);
offset += word_size;
buf_remaining_bytes -= word_size;
}

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

static void lpspi_fill_tx_fifo_nop(const struct device *dev)
/* just fills TX fifo with the specified amount of NOPS */
static void lpspi_fill_tx_fifo_nop(const struct device *dev, size_t fill_len)
{
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
struct spi_mcux_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;

for (int i = 0; i < lpspi_data->fill_len; i++) {
for (int i = 0; i < fill_len; i++) {
LPSPI_WriteData(base, 0);
}

LOG_DBG("Filled TX fifo with %d NOPs", lpspi_data->fill_len);
LOG_DBG("Filled TX fifo with %d NOPs", fill_len);
}

/* handles refilling the TX fifo from empty */
static void lpspi_next_tx_fill(const struct device *dev)
{
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_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 max_chunk;
size_t fill_len;
size_t actual_filled = 0;

/* Convert bytes to words for this xfer */
max_chunk = DIV_ROUND_UP(ctx->tx_len, lpspi_data->word_size_bytes);
max_chunk = MIN(max_chunk, config->tx_fifo_size);
lpspi_data->fill_len = max_chunk;

if (spi_context_tx_buf_on(ctx)) {
lpspi_fill_tx_fifo(dev);
} else {
lpspi_fill_tx_fifo_nop(dev);
fill_len = DIV_ROUND_UP(spi_context_tx_len_left(ctx), lpspi_data->word_size_bytes);
fill_len = MIN(fill_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;
size_t bufs_left = ctx->tx_count;

while (fill_len > 0) {
size_t next_buf_fill = MIN(cur_buf_len_left, fill_len);

if (cur_buf_pos == NULL) {
lpspi_fill_tx_fifo_nop(dev, next_buf_fill);
} else {
lpspi_fill_tx_fifo(dev, cur_buf_pos,
current_buf->len, next_buf_fill);
}

fill_len -= next_buf_fill;
cur_buf_pos += next_buf_fill;

/* in the case where we just filled as much as we could from the current buffer,
* this logic while wrong should have no effect, since fill_len will be 0,
* so I choose not to make the code extra complex
*/
bufs_left--;
if (bufs_left > 0) {
current_buf += 1;
cur_buf_len_left = current_buf->len;
cur_buf_pos = current_buf->buf;
} else {
fill_len = 0;
}

actual_filled += next_buf_fill;
}

lpspi_data->fill_len = actual_filled;
}

static inline void lpspi_handle_tx_irq(const struct device *dev)
Expand All @@ -168,7 +206,12 @@ static inline void lpspi_handle_tx_irq(const struct device *dev)
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;

spi_context_update_tx(ctx, lpspi_data->word_size_bytes, lpspi_data->fill_len);
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_ClearStatusFlags(base, kLPSPI_TxDataRequestFlag);

Expand All @@ -180,6 +223,25 @@ static inline void lpspi_handle_tx_irq(const struct device *dev)
lpspi_next_tx_fill(data->dev);
}

static inline void lpspi_handle_err051588(LPSPI_Type *base, uint32_t status_flags, uint8_t op_mode)
{
if (op_mode != SPI_OP_MODE_SLAVE) {
/* this errata is only for slave mode */
return;
}

if (!(status_flags & LPSPI_SR_TEF_MASK)) {
/* the errata happens when transmit error (underrun) occurs */
return;
}

/* clear the w1c error flag as usual */
base->SR = LPSPI_SR_TEF_MASK;

/* workaround is to reset the transmit fifo before writing any new data */
base->CR |= LPSPI_CR_RTF_MASK;
}

static void lpspi_isr(const struct device *dev)
{
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
Expand All @@ -188,31 +250,32 @@ static void lpspi_isr(const struct device *dev)
struct spi_mcux_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;
uint8_t op_mode = lpspi_data->lpspi_op_mode;

if (status_flags & kLPSPI_RxDataReadyFlag) {
lpspi_handle_rx_irq(dev);
}

if (status_flags & kLPSPI_TxDataRequestFlag) {
lpspi_handle_err051588(base, status_flags, op_mode);
lpspi_handle_tx_irq(dev);
}

if (spi_context_tx_on(ctx)) {
return;
}

if (spi_context_rx_len_left(ctx) == 1) {
if (op_mode == SPI_OP_MODE_MASTER && spi_context_rx_len_left(ctx) == 1) {
base->TCR &= ~LPSPI_TCR_CONT_MASK;
} else if (spi_context_rx_on(ctx)) {
size_t rx_fifo_len = rx_fifo_cur_len(base);
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);

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

lpspi_fill_tx_fifo_nop(dev);
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;
} else {
spi_context_complete(ctx, dev, 0);
NVIC_ClearPendingIRQ(config->irqn);
Expand All @@ -228,9 +291,11 @@ static int transceive(const struct device *dev, const struct spi_config *spi_cfg
bool asynchronous, spi_callback_t cb, void *userdata)
{
LPSPI_Type *base = (LPSPI_Type *)DEVICE_MMIO_NAMED_GET(dev, reg_base);
const struct spi_mcux_config *config = dev->config;
struct spi_mcux_data *data = dev->data;
struct lpspi_driver_data *lpspi_data = (struct lpspi_driver_data *)data->driver_data;
struct spi_context *ctx = &data->ctx;
uint8_t op_mode = SPI_OP_MODE_GET(spi_cfg->operation);
int ret = 0;

spi_context_lock(&data->ctx, asynchronous, cb, userdata, spi_cfg);
Expand All @@ -243,26 +308,34 @@ 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);
lpspi_data->lpspi_op_mode = op_mode;

ret = spi_mcux_configure(dev, spi_cfg);
if (ret) {
goto error;
}

LPSPI_FlushFifo(base, true, true);
LPSPI_FlushFifo(base, false, true);
LPSPI_ClearStatusFlags(base, (uint32_t)kLPSPI_AllStatusFlag);
LPSPI_DisableInterrupts(base, (uint32_t)kLPSPI_AllInterruptEnable);

LOG_DBG("Starting LPSPI transfer");
spi_context_cs_control(ctx, true);

LPSPI_SetFifoWatermarks(base, 0, 0);
if (op_mode == SPI_OP_MODE_MASTER) {
LPSPI_SetFifoWatermarks(base, 0, 0);
} else {
LPSPI_SetFifoWatermarks(base, config->tx_fifo_size - 2, 0);
}

LPSPI_Enable(base, true);

/* keep the chip select asserted until the end of the zephyr xfer */
base->TCR |= LPSPI_TCR_CONT_MASK;
/* tcr is written to tx fifo */
lpspi_wait_tx_fifo_empty(dev);
if (op_mode == SPI_OP_MODE_MASTER) {
/* keep the chip select active until the end of the zephyr xfer */
base->TCR |= LPSPI_TCR_CONT_MASK;
/* tcr is written to tx fifo */
lpspi_wait_tx_fifo_empty(dev);
}

/* start the transfer sequence which are handled by irqs */
lpspi_next_tx_fill(dev);
Expand All @@ -271,7 +344,6 @@ static int transceive(const struct device *dev, const struct spi_config *spi_cfg
(uint32_t)kLPSPI_RxInterruptEnable);

return spi_context_wait_for_completion(ctx);

error:
spi_context_release(ctx, ret);
return ret;
Expand Down
Loading
Loading