Bug in nRF SPIM RTIO: incorrect chip-select handling at transaction completion

Environment :

- nRF Connect SDK v3.4.0.
- CONFIG_SPI_NRFX_SPIM_RTIO=y.
- SPI_HOLD_ON_CS is not enabled.

The same completion ordering is present in upstream Zephyr main : https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/spi/spi_nrfx_spim_rtio.c

Bug

In iodev_end_txn(), the driver calls spi_rtio_complete() before it clears chip
select. It also skips the CS clear when another transaction is already queued.

This creates two problems:

1. A completion callback can submit and start another transaction. The previous
   completion path then clears CS and disables SPIM underneath that new
   transaction.
2. If another transaction is already queued, spi_rtio_complete() returns true.
   The driver starts that transaction without clearing CS between transactions.

Independent transactions require a CS boundary unless SPI_HOLD_ON_CS requests
otherwise.

Proposed fix

Move the conditional CS clear before spi_rtio_complete(). The resulting function
is:

static void iodev_end_txn(const struct device *dev, int result)
{
    const struct driver_config *dev_config = dev->config;
    struct spi_rtio *spi_rtio_ctx = dev_config->spi_rtio_ctx;
    struct rtio_sqe *sqe = &spi_rtio_ctx->txn_head->sqe;
    struct spi_dt_spec *spi_spec = sqe->iodev->data;
    struct spi_config *spi_cfg = &spi_spec->config;

    if ((spi_cfg->operation & SPI_HOLD_ON_CS) == 0) {
        spi_nrfx_spim_common_cs_clear(dev, spi_cfg);
    }

    if (spi_rtio_complete(spi_rtio_ctx, result) == false) {
        pm_device_runtime_put(dev);
        return;
    }

    iodev_start_curr(dev);
}


This ends the previous SPI transaction before completion callbacks can start
another transaction. It also restores CS boundaries between queued transactions.

Validation

We noticed this problem during migration of our custom accelerometer driver
from synchronous sample fetch to RTIO sensor streaming on nRF5340 hardware.
The synchronous path worked correctly before the migration.

With the native RTIO SPIM backend, the stream delivered 29 samples, then small
batches of seven samples, followed by -ENOMEM. The immediate repeated reads
exhausted the stream buffer pool before the consumer could return the buffers.

Moving the CS clear before spi_rtio_complete() fixed the failure without changes
to the buffer pool size. All three hardware tests passed, including stop and
restart, with zero stream errors across requested rates from 12.5 to 400 Hz.

Our SDK checkout also includes the upstream RTIO CQE callback backport.
The failing test used semaphore-based CQE consumption through
rtio_cqe_consume_block(), not CQE callback notification. The callbacks involved
in the SPI completion path are standard chained RTIO callbacks (RTIO_OP_CALLBACK).

Hardware validation used our local SDK checkout. We did not separately test a
checkout without the CQE callback backport. Upstream confirmation was by source
inspection.

Can Nordic review this fix and include it in the SDK and upstream Zephyr?

Related