[PATCH rtems v2 2/3] bsp/imxrt1166: Support GPIO CS pins in LPSPI

Christian Mauderer christian.mauderer at embedded-brains.de
Tue Nov 21 14:54:34 UTC 2023


With this, it is possible to use GPIOs as CS pins in the LPSPI. To avoid
additional complexity, the GPIOs will have the same limitations as the
native (hardware) CS pins.

The GPIO CS feature adds a number of extra code when starting SPI
transfers on this controller. Therefore it is possible to disable the
additional code by just setting the IMXRT_LPSPI_MAX_CS option to 0. In
that case only native CS pins are supported.

At the moment, this feature is only enabled on i.MXRT1166 by default
because it is not tested on i.MXRT1050. But it should work there too.
---
 bsps/arm/imxrt/spi/imxrt-lpspi.c            | 244 ++++++++++++++++++--
 spec/build/bsps/arm/imxrt/grp.yml           |   2 +
 spec/build/bsps/arm/imxrt/optlpspimaxcs.yml |  21 ++
 3 files changed, 248 insertions(+), 19 deletions(-)
 create mode 100644 spec/build/bsps/arm/imxrt/optlpspimaxcs.yml

diff --git a/bsps/arm/imxrt/spi/imxrt-lpspi.c b/bsps/arm/imxrt/spi/imxrt-lpspi.c
index aed4f07f88..f23df73734 100644
--- a/bsps/arm/imxrt/spi/imxrt-lpspi.c
+++ b/bsps/arm/imxrt/spi/imxrt-lpspi.c
@@ -29,6 +29,7 @@
 #include <bsp/fatal.h>
 #include <bsp/fdt.h>
 #include <bsp/irq.h>
+#include <bsp/imx-gpio.h>
 
 #include <chip.h>
 #include <dev/spi/spi.h>
@@ -36,6 +37,10 @@
 #include <libfdt.h>
 #include <imxrt/lpspi.h>
 
+#if IMXRT_LPSPI_MAX_CS != 0 && IMXRT_LPSPI_MAX_CS < 4
+#error IMXRT_LPSPI_MAX_CS hast to be either 0 or at least 4.
+#endif
+
 struct imxrt_lpspi_bus {
   spi_bus base;
   volatile LPSPI_Type *regs;
@@ -57,6 +62,19 @@ struct imxrt_lpspi_bus {
   const uint8_t *tx_buf;
 
   uint32_t fifo_size;
+
+#if IMXRT_LPSPI_MAX_CS != 0
+  struct {
+    bool is_gpio;
+    struct imx_gpio_pin gpio;
+    uint32_t active;
+  } cs[IMXRT_LPSPI_MAX_CS];
+  /*
+   * dummy_cs is either <0 if no dummy exists or the index of the cs that is
+   * used as dummy.
+   */
+  int dummy_cs;
+#endif
 };
 
 static const uint32_t word_size = 8;
@@ -148,7 +166,15 @@ static void imxrt_lpspi_config(
     tcr |= LPSPI_TCR_LSBF_MASK;
   }
 
+#if IMXRT_LPSPI_MAX_CS > 0
+  if (bus->cs[msg->cs].is_gpio || (msg->mode & SPI_NO_CS) != 0) {
+    tcr |= LPSPI_TCR_PCS(bus->dummy_cs);
+  } else {
+    tcr |= LPSPI_TCR_PCS(msg->cs);
+  }
+#else
   tcr |= LPSPI_TCR_PCS(msg->cs);
+#endif
   tcr |= LPSPI_TCR_CONT_MASK;
   tcr |= LPSPI_TCR_FRAMESZ(word_size-1);
 
@@ -308,14 +334,33 @@ static inline int imxrt_lpspi_settings_ok(
 )
 {
   /* most of this is currently just not implemented */
-  if (msg->cs > 3 ||
-      msg->speed_hz > bus->base.max_speed_hz ||
+  if (msg->speed_hz > bus->base.max_speed_hz ||
       msg->delay_usecs != 0 ||
-      (msg->mode & ~(SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST)) != 0 ||
+      (msg->mode & ~(SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST | SPI_NO_CS)) != 0 ||
       msg->bits_per_word != word_size) {
     return -EINVAL;
   }
 
+#if IMXRT_LPSPI_MAX_CS == 0
+  if (msg->cs > 3 || (msg->mode & SPI_NO_CS) != 0) {
+    return -EINVAL;
+  }
+#else /* IMXRT_LPSPI_MAX_CS != 0 */
+  /*
+   * Chip select is a bit tricky. This depends on whether it's a native or a
+   * GPIO chip select.
+   */
+  if (msg->cs > IMXRT_LPSPI_MAX_CS) {
+    return -EINVAL;
+  }
+  if (!bus->cs[msg->cs].is_gpio && msg->cs > 3) {
+    return -EINVAL;
+  }
+  if ((msg->mode & SPI_NO_CS) != 0 && bus->dummy_cs < 0) {
+    return -EINVAL;
+  }
+#endif
+
   if (prev_msg != NULL && !prev_msg->cs_change) {
     /*
      * A lot of settings have to be the same in this case because the upper 8
@@ -355,6 +400,10 @@ static int imxrt_lpspi_check_messages(
    * Check whether cs_change is set on last message. Can't work without it
    * because the last received data is only put into the FIFO if it is the end
    * of a transfer or if another TX byte is put into the FIFO.
+   *
+   * In theory, a GPIO CS wouldn't need that limitation. But handling it
+   * different for the GPIO CS would add complexity. So keep it as a driver
+   * limitation for now.
    */
   if (!prev_msg->cs_change) {
     return -EINVAL;
@@ -363,6 +412,92 @@ static int imxrt_lpspi_check_messages(
   return 0;
 }
 
+#if IMXRT_LPSPI_MAX_CS > 0
+/*
+ * Check how many of the messages can be processed in one go. At the moment it
+ * is necessary to pause on CS changes when GPIO CS are used.
+ */
+static int imxrt_lpspi_check_howmany(
+  struct imxrt_lpspi_bus *bus,
+  const spi_ioc_transfer *msgs,
+  uint32_t max
+)
+{
+  int i;
+
+  if (max == 0) {
+    return max;
+  }
+
+  for (i = 0; i < max - 1; ++i) {
+    const spi_ioc_transfer *msg = &msgs[i];
+    const spi_ioc_transfer *next_msg = &msgs[i+1];
+
+    bool cs_is_gpio = bus->cs[msg->cs].is_gpio;
+    bool no_cs = msg->mode & SPI_NO_CS;
+    bool no_cs_next = next_msg->mode & SPI_NO_CS;
+
+    if (cs_is_gpio && msg->cs_change) {
+      break;
+    }
+
+    if (no_cs != no_cs_next) {
+      break;
+    }
+
+    if (cs_is_gpio && (msg->cs != next_msg->cs)) {
+      break;
+    }
+  }
+
+  return i+1;
+}
+#endif
+
+/*
+ * Transfer some messages. CS must not change between messages if GPIO CS are
+ * used.
+ */
+static void imxrt_lpspi_transfer_some(
+  struct imxrt_lpspi_bus *bus,
+  const spi_ioc_transfer *msgs,
+  uint32_t n
+)
+{
+#if IMXRT_LPSPI_MAX_CS > 0
+  /*
+   * Software chip select. Due to the checks in the
+   * imxrt_lpspi_check_messages, the CS can't change in the middle of a
+   * transfer. So we can just use the one from the first message.
+   */
+  if ((msgs[0].mode & SPI_NO_CS) == 0 && bus->cs[msgs[0].cs].is_gpio) {
+    imx_gpio_set_output(&bus->cs[msgs[0].cs].gpio, bus->cs[msgs[0].cs].active);
+  }
+#endif
+
+  bus->tx_msg_todo = n;
+  bus->tx_msg = &msgs[0];
+  bus->rx_msg_todo = n;
+  bus->rx_msg = &msgs[0];
+  bus->cs_change_on_last_msg = true;
+
+  imxrt_lpspi_next_rx_msg(bus, bus->regs);
+  imxrt_lpspi_next_tx_msg(bus, bus->regs);
+  /*
+   * Enable the transmit FIFO empty interrupt which will cause an interrupt
+   * instantly because there is no data in the transmit FIFO. The interrupt
+   * will then fill the FIFO. So nothing else to do here.
+   */
+  bus->regs->IER = LPSPI_IER_TDIE_MASK;
+  rtems_binary_semaphore_wait(&bus->sem);
+
+#if IMXRT_LPSPI_MAX_CS > 0
+  if ((msgs[0].mode & SPI_NO_CS) == 0 && bus->cs[msgs[0].cs].is_gpio) {
+    imx_gpio_set_output(&bus->cs[msgs[0].cs].gpio, ~bus->cs[msgs[0].cs].active);
+  }
+#endif
+}
+
 static int imxrt_lpspi_transfer(
   spi_bus *base,
   const spi_ioc_transfer *msgs,
@@ -377,22 +512,19 @@ static int imxrt_lpspi_transfer(
   rv = imxrt_lpspi_check_messages(bus, msgs, n);
 
   if (rv == 0) {
-    bus->tx_msg_todo = n;
-    bus->tx_msg = &msgs[0];
-    bus->rx_msg_todo = n;
-    bus->rx_msg = &msgs[0];
-    bus->cs_change_on_last_msg = true;
-
-    imxrt_lpspi_next_rx_msg(bus, bus->regs);
-    imxrt_lpspi_next_tx_msg(bus, bus->regs);
-    /*
-     * Enable the transmit FIFO empty interrupt which will cause an interrupt
-     * instantly because there is no data in the transmit FIFO. The interrupt
-     * will then fill the FIFO. So nothing else to do here.
-     */
-    bus->regs->IER = LPSPI_IER_TDIE_MASK;
-    rtems_binary_semaphore_wait(&bus->sem);
-  }
+#if IMXRT_LPSPI_MAX_CS > 0
+    while (n > 0) {
+      uint32_t howmany;
+
+      howmany = imxrt_lpspi_check_howmany(bus, msgs, n);
+      imxrt_lpspi_transfer_some(bus, msgs, howmany);
+      n -= howmany;
+      msgs += howmany;
+    };
+#else
+    imxrt_lpspi_transfer_some(bus, msgs, n);
+#endif
+  };
 
   return rv;
 }
@@ -570,6 +702,9 @@ void imxrt_lpspi_init(void)
       struct imxrt_lpspi_bus *bus;
       int eno;
       const char *bus_path;
+#if IMXRT_LPSPI_MAX_CS != 0
+      const uint32_t *val;
+#endif
 
       bus = (struct imxrt_lpspi_bus*) spi_bus_alloc_and_init(sizeof(*bus));
       if (bus == NULL) {
@@ -593,6 +728,77 @@ void imxrt_lpspi_init(void)
         bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
       }
 
+#if IMXRT_LPSPI_MAX_CS != 0
+      bus->dummy_cs = -1;
+      val = fdt_getprop(fdt, node, "num-cs", NULL);
+      /* If num-cs is not set: Just assume we only have hardware CS pins */
+      if (val != NULL) {
+        uint32_t num_cs;
+        size_t i;
+        int len;
+        const uint32_t *val_end;
+
+        num_cs = fdt32_to_cpu(val[0]);
+        if (num_cs > IMXRT_LPSPI_MAX_CS) {
+          bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
+        }
+
+        val = fdt_getprop(fdt, node, "cs-gpios", &len);
+        if (val == NULL) {
+          bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
+        }
+        val_end = val + len;
+
+        for (i = 0; i < num_cs; ++i) {
+          if (val >= val_end) {
+            /* Already reached the end. But still pins to process. */
+            bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
+          }
+          if (fdt32_to_cpu(val[0]) == 0) {
+            /* phandle == 0; this is a native CS */
+            bus->cs[i].is_gpio = false;
+            ++val;
+          } else {
+            /*
+             * phandle is something. Assume an imx_gpio. Other GPIO controllers
+             * are not supported.
+             */
+            rtems_status_code sc;
+
+            if (bus->dummy_cs < 0) {
+              bus->dummy_cs = i;
+            }
+            bus->cs[i].is_gpio = true;
+            /*
+             * According to Linux device tree documentation, the bit 0 of the
+             * flag bitfield in the last cell is 0 for an active high and 1 for
+             * an active low pin. Usually the defines GPIO_ACTIVE_HIGH and
+             * GPIO_ACTIVE_LOW would be used for that. But we don't have them.
+             */
+            bus->cs[i].active = (~fdt32_to_cpu(val[2])) & 0x1;
+            sc = imx_gpio_init_from_fdt_property_pointer(&bus->cs[i].gpio, val,
+                IMX_GPIO_MODE_OUTPUT, &val);
+            if (sc != RTEMS_SUCCESSFUL || val > val_end) {
+              bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
+            }
+
+            /* Set to idle state */
+            imx_gpio_set_output(&bus->cs[i].gpio, ~bus->cs[i].active);
+          }
+        }
+
+        /*
+         * All pins are processed. Check dummy_cs. If it is still <0, no GPIO is
+         * used. That's OK. But if it is set, at least one GPIO CS is set and in
+         * this case one of the native CS pins has to be reserved for the
+         * dummy_cs.
+         */
+        if (bus->dummy_cs > 3) {
+          bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT);
+        }
+      }
+#endif
+
       bus->clock_ip = imxrt_lpspi_clock_ip(bus->regs);
       bus->src_clock_hz = imxrt_lpspi_get_src_freq(bus->clock_ip);
       /* Absolut maximum is 30MHz according to electrical characteristics */
diff --git a/spec/build/bsps/arm/imxrt/grp.yml b/spec/build/bsps/arm/imxrt/grp.yml
index 6191823899..12e50c5376 100644
--- a/spec/build/bsps/arm/imxrt/grp.yml
+++ b/spec/build/bsps/arm/imxrt/grp.yml
@@ -24,6 +24,8 @@ links:
   uid: optfsledmaemlm
 - role: build-dependency
   uid: optlinkcmds
+- role: build-dependency
+  uid: optlpspimaxcs
 - role: build-dependency
   uid: optmemdtcmsz
 - role: build-dependency
diff --git a/spec/build/bsps/arm/imxrt/optlpspimaxcs.yml b/spec/build/bsps/arm/imxrt/optlpspimaxcs.yml
new file mode 100644
index 0000000000..d7cc0ff644
--- /dev/null
+++ b/spec/build/bsps/arm/imxrt/optlpspimaxcs.yml
@@ -0,0 +1,21 @@
+SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause
+actions:
+- get-integer: null
+- define: null
+build-type: option
+copyrights:
+- Copyright (C) 2023 embedded brains GmbH & Co. KG
+default:
+- enabled-by: arm/imxrt1166-cm7-saltshaker
+  value: 8
+- enabled-by: true
+  value: 0
+description: |
+  Maximum number of (combined) native and GPIO chip selects per LPSPI.  If only
+  native chip selects are used, this can be set to 0 to save some processing
+  cycles on SPI transfers.  Otherwise you have to set it to at least 4.
+enabled-by: true
+format: '{}'
+links: []
+name: IMXRT_LPSPI_MAX_CS
+type: build
-- 
2.35.3



More information about the devel mailing list