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.
---
 bsps/arm/imxrt/spi/imxrt-lpspi.c            | 146 +++++++++++++++++++-
 spec/build/bsps/arm/imxrt/grp.yml           |   2 +
 spec/build/bsps/arm/imxrt/optlpspimaxcs.yml |  19 +++
 3 files changed, 165 insertions(+), 2 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..9d1124e58f 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) {
+    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,18 +334,40 @@ 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->bits_per_word != word_size) {
     return -EINVAL;
   }
 
+#if IMXRT_LPSPI_MAX_CS == 0
+  if (msg->cs > 3) {
+    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;
+  }
+#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
      * bit of TCR can't be changed if it is a continuous transfer.
+     *
+     * This also makes sure that there is only one GPIO CS used during the 
whole
+     * message queue. Without that, we maybe would have to wait for the FIFOs 
to
+     * be empty and then switch the CS. In theory that would be possible but it
+     * would add a lot of complexity. So stick with it as a driver limitation
+     * for now.
      */
     if (prev_msg->cs != msg->cs ||
         prev_msg->speed_hz != msg->speed_hz ||
@@ -355,6 +403,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;
@@ -377,6 +429,18 @@ static int imxrt_lpspi_transfer(
   rv = imxrt_lpspi_check_messages(bus, msgs, n);
 
   if (rv == 0) {
+#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 (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;
@@ -392,6 +456,13 @@ static int imxrt_lpspi_transfer(
      */
     bus->regs->IER = LPSPI_IER_TDIE_MASK;
     rtems_binary_semaphore_wait(&bus->sem);
+
+#if IMXRT_LPSPI_MAX_CS > 0
+    if (bus->cs[msgs[0].cs].is_gpio) {
+      imx_gpio_set_output(&bus->cs[msgs[0].cs].gpio,
+          ~bus->cs[msgs[0].cs].active);
+    }
+#endif
   }
 
   return rv;
@@ -570,6 +641,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 +667,74 @@ 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);
+            }
+          }
+        }
+
+        /*
+         * 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..405c4d4505
--- /dev/null
+++ b/spec/build/bsps/arm/imxrt/optlpspimaxcs.yml
@@ -0,0 +1,19 @@
+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: true
+  value: 8
+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

_______________________________________________
devel mailing list
devel@rtems.org
http://lists.rtems.org/mailman/listinfo/devel

Reply via email to