drivers: spi_nrfx_spi[m]: Handle transaction timeouts properly

Add code that acts accordingly when a transaction does not complete
in the expected time. It makes sure that the transaction is aborted
so that no unexpected interrupt occurs afterwards and it also cleans
up after that abort so that the driver can handle further requests.

Signed-off-by: Andrzej Głąbek <andrzej.glabek@nordicsemi.no>
diff --git a/drivers/spi/spi_nrfx_spi.c b/drivers/spi/spi_nrfx_spi.c
index cdd2487..3c9f52b 100644
--- a/drivers/spi/spi_nrfx_spi.c
+++ b/drivers/spi/spi_nrfx_spi.c
@@ -152,6 +152,19 @@
 	return 0;
 }
 
+static void finish_transaction(const struct device *dev, int error)
+{
+	struct spi_nrfx_data *dev_data = dev->data;
+	struct spi_context *ctx = &dev_data->ctx;
+
+	spi_context_cs_control(ctx, false);
+
+	LOG_DBG("Transaction finished with status %d", error);
+
+	spi_context_complete(ctx, dev, error);
+	dev_data->busy = false;
+}
+
 static void transfer_next_chunk(const struct device *dev)
 {
 	const struct spi_nrfx_config *dev_config = dev->config;
@@ -179,12 +192,7 @@
 		error = -EIO;
 	}
 
-	spi_context_cs_control(ctx, false);
-
-	LOG_DBG("Transaction finished with status %d", error);
-
-	spi_context_complete(ctx, dev, error);
-	dev_data->busy = false;
+	finish_transaction(dev, error);
 }
 
 static void event_handler(const nrfx_spi_evt_t *p_event, void *p_context)
@@ -192,6 +200,14 @@
 	struct spi_nrfx_data *dev_data = p_context;
 
 	if (p_event->type == NRFX_SPI_EVENT_DONE) {
+		/* Chunk length is set to 0 when a transaction is aborted
+		 * due to a timeout.
+		 */
+		if (dev_data->chunk_len == 0) {
+			finish_transaction(dev_data->dev, -ETIMEDOUT);
+			return;
+		}
+
 		spi_context_update_tx(&dev_data->ctx, 1, dev_data->chunk_len);
 		spi_context_update_rx(&dev_data->ctx, 1, dev_data->chunk_len);
 
@@ -208,6 +224,7 @@
 		      void *userdata)
 {
 	struct spi_nrfx_data *dev_data = dev->data;
+	const struct spi_nrfx_config *dev_config = dev->config;
 	int error;
 
 	spi_context_lock(&dev_data->ctx, asynchronous, cb, userdata, spi_cfg);
@@ -222,6 +239,27 @@
 		transfer_next_chunk(dev);
 
 		error = spi_context_wait_for_completion(&dev_data->ctx);
+		if (error == -ETIMEDOUT) {
+			/* Set the chunk length to 0 so that event_handler()
+			 * knows that the transaction timed out and is to be
+			 * aborted.
+			 */
+			dev_data->chunk_len = 0;
+			/* Abort the current transfer by deinitializing
+			 * the nrfx driver.
+			 */
+			nrfx_spi_uninit(&dev_config->spi);
+			dev_data->initialized = false;
+
+			/* Make sure the transaction is finished (it may be
+			 * already finished if it actually did complete before
+			 * the nrfx driver was deinitialized).
+			 */
+			finish_transaction(dev, -ETIMEDOUT);
+
+			/* Clean up the driver state. */
+			k_sem_reset(&dev_data->ctx.sync);
+		}
 	}
 
 	spi_context_release(&dev_data->ctx, error);
diff --git a/drivers/spi/spi_nrfx_spim.c b/drivers/spi/spi_nrfx_spim.c
index e8e9768..cd9da80 100644
--- a/drivers/spi/spi_nrfx_spim.c
+++ b/drivers/spi/spi_nrfx_spim.c
@@ -285,6 +285,19 @@
 }
 #endif
 
+static void finish_transaction(const struct device *dev, int error)
+{
+	struct spi_nrfx_data *dev_data = dev->data;
+	struct spi_context *ctx = &dev_data->ctx;
+
+	spi_context_cs_control(ctx, false);
+
+	LOG_DBG("Transaction finished with status %d", error);
+
+	spi_context_complete(ctx, dev, error);
+	dev_data->busy = false;
+}
+
 static void transfer_next_chunk(const struct device *dev)
 {
 	struct spi_nrfx_data *dev_data = dev->data;
@@ -342,12 +355,7 @@
 		}
 	}
 
-	spi_context_cs_control(ctx, false);
-
-	LOG_DBG("Transaction finished with status %d", error);
-
-	spi_context_complete(ctx, dev, error);
-	dev_data->busy = false;
+	finish_transaction(dev, error);
 }
 
 static void event_handler(const nrfx_spim_evt_t *p_event, void *p_context)
@@ -355,6 +363,14 @@
 	struct spi_nrfx_data *dev_data = p_context;
 
 	if (p_event->type == NRFX_SPIM_EVENT_DONE) {
+		/* Chunk length is set to 0 when a transaction is aborted
+		 * due to a timeout.
+		 */
+		if (dev_data->chunk_len == 0) {
+			finish_transaction(dev_data->dev, -ETIMEDOUT);
+			return;
+		}
+
 #ifdef CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58
 		anomaly_58_workaround_clear(dev_data);
 #endif
@@ -374,6 +390,7 @@
 		      void *userdata)
 {
 	struct spi_nrfx_data *dev_data = dev->data;
+	const struct spi_nrfx_config *dev_config = dev->config;
 	int error;
 
 	spi_context_lock(&dev_data->ctx, asynchronous, cb, userdata, spi_cfg);
@@ -388,6 +405,30 @@
 		transfer_next_chunk(dev);
 
 		error = spi_context_wait_for_completion(&dev_data->ctx);
+		if (error == -ETIMEDOUT) {
+			/* Set the chunk length to 0 so that event_handler()
+			 * knows that the transaction timed out and is to be
+			 * aborted.
+			 */
+			dev_data->chunk_len = 0;
+			/* Abort the current transfer by deinitializing
+			 * the nrfx driver.
+			 */
+			nrfx_spim_uninit(&dev_config->spim);
+			dev_data->initialized = false;
+
+			/* Make sure the transaction is finished (it may be
+			 * already finished if it actually did complete before
+			 * the nrfx driver was deinitialized).
+			 */
+			finish_transaction(dev, -ETIMEDOUT);
+
+			/* Clean up the driver state. */
+			k_sem_reset(&dev_data->ctx.sync);
+#ifdef CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58
+			anomaly_58_workaround_clear(dev_data);
+#endif
+		}
 	}
 
 	spi_context_release(&dev_data->ctx, error);