summaryrefslogtreecommitdiffstats
path: root/bsps/arm
diff options
context:
space:
mode:
authorChristian Mauderer <christian.mauderer@embedded-brains.de>2023-11-21 15:54:34 +0100
committerChristian Mauderer <christian.mauderer@embedded-brains.de>2023-11-28 13:36:41 +0100
commit02f2316be7aff38f657cdae1a332ff161a6d7292 (patch)
tree3f67c1b5115a73dc9cb50d20b5c6b61df05b6a4c /bsps/arm
parentbsps/imx*: imx_gpio from pointer to fdt property (diff)
downloadrtems-02f2316be7aff38f657cdae1a332ff161a6d7292.tar.bz2
bsp/imxrt1166: Support GPIO CS pins in LPSPI
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.
Diffstat (limited to 'bsps/arm')
-rw-r--r--bsps/arm/imxrt/spi/imxrt-lpspi.c244
1 files changed, 225 insertions, 19 deletions
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 */