Hi,
We are using a NRF54L15 device as an SPI slave device communicating through an other SoC as SPI master. This SPI master device emits periodic SPI messages at indeterminate intervals which means we can't have a spi_read call pending forever unless in a specific thread. The real issue is that we may need to send data to the master using a custom reversed logic based on a GPIO interrupt in which the master device will then read from the NRF54L15. This unusual scenario is at this time not possible to modify.
From what I've experimented through the zephyr SPI API, it's impossible to have a pending spi_read nor spi_read_signal call and at the same time have a spi_write or spi_write_signal function, in that case it will spinlock until the previous transaction completed.
Therefore I was thinking of controling the CS line manually so that a GPIO trigger on the CS line will then enable the NRF54L15 the possibility to call the synchronous spi_read without blocking as the data is supposed to be available immediately. Unfortunately, this seems not possible because the delay between the interrupt handler and the spi_read is too long that the spi_read is actually blocking until the next transaction arise. I've tried to disable the CS line management at all in the spi_cfg structure but then the process ends in this part of the driver code:
spi_nrfx_spis.c:
if (spi_cs_is_gpio(spi_cfg)) { LOG_ERR("CS control via GPIO is not supported"); return -EINVAL; }
For now, I'm thinking what can be done to handle the requirements:
- being able to read only when needed
- being able to write at any time from the slave to the master
For what it worth, that was the current implementation based on a specific thread notified with a zephyr message queue either from the CS GPIO interrupt handler or the main loop posting a message at periodic interval.
#include <stdint.h> #include <stddef.h> #include <time.h> #include <zephyr/device.h> #include <zephyr/devicetree.h> #include <zephyr/drivers/gpio.h> #include <zephyr/drivers/spi.h> #include <zephyr/kernel.h> #include <zephyr/logging/log.h> LOG_MODULE_REGISTER(nrf); #define NRF_SPI_MSG_MAX 130 #define NRF_SPI_CS_NODE DT_ALIAS(sw0) //static struct gpio_dt_spec nrf_spi_cs_pin = GPIO_DT_SPEC_GET_OR(NRF_SPI_CS_NODE, gpios, {0}); static struct spi_config nrf_spi_cfg = { .operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_OP_MODE_SLAVE, .frequency = 4000000, .slave = 0, .cs = { .gpio = GPIO_DT_SPEC_GET_OR(NRF_SPI_CS_NODE, gpios, {0}) } }; static const struct device *nrf_spi_dev; static struct gpio_callback nrf_spi_data; typedef enum { NRF_SPI_EV_READ, NRF_SPI_EV_WRITE } nrf_spi_ev_type; typedef struct { nrf_spi_ev_type type; } nrf_spi_ev_read; typedef struct { nrf_spi_ev_type type; uint8_t data[NRF_SPI_MSG_MAX]; size_t length; } nrf_spi_ev_write; // union for notifying the thread about read/write on the SPI thread typedef union { // common layout nrf_spi_ev_type type; // per-event data. nrf_spi_ev_read read; nrf_spi_ev_write write; } nrf_spi_ev; K_MSGQ_DEFINE(nrf_spi_queue, sizeof (nrf_spi_ev), 8, 1); void nrf_spi_cs_int(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { const nrf_spi_ev ev = { .type = NRF_SPI_EV_READ }; int rv; // TODO: K_FOREVER maybe not. LOG_INF("triggered!"); rv = k_msgq_put(&nrf_spi_queue, &ev, K_FOREVER); if (rv != 0) { LOG_WRN("unable to signal SPI service: %d", rv); } } static int nrf_spi_init(void) { int rv; nrf_spi_dev = DEVICE_DT_GET(DT_NODELABEL(my_spi_slave)); if (!device_is_ready(nrf_spi_dev)) { LOG_INF("SPI slave device not available"); return -1; } if (!gpio_is_ready_dt(&nrf_spi_cfg.cs.gpio)) { LOG_INF("SPI CS pin not available"); return -1; } rv = gpio_pin_configure_dt(&nrf_spi_cfg.cs.gpio, GPIO_INPUT); if (rv != 0) { LOG_INF("failed to configure SPI CS: %d", rv); return -1; } rv = gpio_pin_interrupt_configure_dt(&nrf_spi_cfg.cs.gpio, GPIO_INT_EDGE_FALLING); if (rv != 0) { LOG_INF("failed to configure SPI CS interrupt handler: %d", rv); return -1; } gpio_init_callback(&nrf_spi_data, nrf_spi_cs_int, BIT(nrf_spi_cfg.cs.gpio.pin)); gpio_add_callback(nrf_spi_cfg.cs.gpio.port, &nrf_spi_data); return 0; } #include <zephyr/timing/timing.h> static void nrf_spi_read(void) { uint8_t buffer[NRF_SPI_MSG_MAX] = {0}; int rv; struct spi_buf s_buf = { .buf = buffer, .len = sizeof (buffer), }; struct spi_buf_set s_set = { .buffers = &s_buf, .count = 1 }; LOG_INF("reading from SPI now..."); rv = spi_read(nrf_spi_dev, &nrf_spi_cfg, &s_set); LOG_INF("reading okay in %u ns, %u cycles", (unsigned)total_ns, (unsigned)total_cycles); if (rv < 0) { LOG_WRN("SPI read error: %d", rv); return; } // TODO: handle message LOG_INF("message received!"); } static void nrf_spi_write(nrf_spi_ev_write *ev) { // TODO: try to send. // TODO: handle GPIO lines. int rv; const struct spi_buf s_buf = { .buf = ev->data, .len = ev->length }; const struct spi_buf_set s_set = { .buffers = &s_buf, .count = 1 }; LOG_INF("writing from SPI now..."); rv = spi_write(nrf_spi_dev, &nrf_spi_cfg, &s_set); LOG_INF("writing okay"); if (rv != 0) { LOG_WRN("SPI write error: %d", rv); return; } LOG_INF("message sent!"); } static void nrf_spi_entry(void *, void *, void *) { for (;;) { nrf_spi_ev ev = {0}; int rv; rv = k_msgq_get(&nrf_spi_queue, &ev, K_FOREVER); if (rv != 0) { LOG_INF("SPI message queue status: %d", rv); continue; } LOG_INF("number of messages in queue: %u", (unsigned)k_msgq_num_used_get(&nrf_spi_queue)); switch (ev.type) { case NRF_SPI_EV_READ: LOG_INF("SPI event read"); nrf_spi_read(); break; case NRF_SPI_EV_WRITE: LOG_INF("SPI event write"); nrf_spi_write(&ev.write); break; default: break; } } } K_THREAD_DEFINE(nrf_spi_thread, 1024, nrf_spi_entry, NULL, NULL, NULL, 1, K_USER, 0); void nrf_tell(const char *msg) { nrf_spi_ev ev = {0}; ev.write.type = NRF_SPI_EV_WRITE; ev.write.length = strlen(msg); memcpy(ev.write.data, msg, ev.write.length); // CAUTION LOG_INF("posting a new message..."); k_msgq_put(&nrf_spi_queue, &ev, K_FOREVER); } int main(void) { if (nrf_spi_init() < 0) return 1; for (;;) { k_msleep(3000); nrf_tell("test"); } }