summaryrefslogtreecommitdiffstats
path: root/bsps/arm/imxrt/spi/imxrt-lpspi.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/arm/imxrt/spi/imxrt-lpspi.c')
-rw-r--r--bsps/arm/imxrt/spi/imxrt-lpspi.c433
1 files changed, 352 insertions, 81 deletions
diff --git a/bsps/arm/imxrt/spi/imxrt-lpspi.c b/bsps/arm/imxrt/spi/imxrt-lpspi.c
index 06d8f715d9..f23df73734 100644
--- a/bsps/arm/imxrt/spi/imxrt-lpspi.c
+++ b/bsps/arm/imxrt/spi/imxrt-lpspi.c
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
- * Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
+ * Copyright (C) 2020 embedded brains GmbH & Co. KG
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -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;
@@ -43,17 +48,33 @@ struct imxrt_lpspi_bus {
uint32_t src_clock_hz;
clock_ip_name_t clock_ip;
- uint32_t msg_todo;
- const spi_ioc_transfer *msg;
rtems_binary_semaphore sem;
- uint32_t tcr;
+ bool cs_change_on_last_msg;
+ uint32_t rx_msg_todo;
+ const spi_ioc_transfer *rx_msg;
size_t remaining_rx_size;
uint8_t *rx_buf;
+ uint32_t tx_msg_todo;
+ const spi_ioc_transfer *tx_msg;
size_t remaining_tx_size;
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;
@@ -145,12 +166,16 @@ static void imxrt_lpspi_config(
tcr |= LPSPI_TCR_LSBF_MASK;
}
- tcr |= LPSPI_TCR_PCS(msg->cs);
-
- if (!msg->cs_change) {
- tcr |= LPSPI_TCR_CONT_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);
if (ccr_orig != ccr) {
@@ -159,9 +184,13 @@ static void imxrt_lpspi_config(
regs->CR |= LPSPI_CR_MEN_MASK;
}
- /* No CONTC on first write. Otherwise upper 8 bits are not written. */
- regs->TCR = tcr;
- regs->TCR = tcr | LPSPI_TCR_CONTC_MASK | LPSPI_TCR_CONT_MASK;
+ if (bus->cs_change_on_last_msg) {
+ /* No CONTC on first write. Otherwise upper 8 bits are not written. */
+ regs->TCR = tcr;
+ }
+ regs->TCR = tcr | LPSPI_TCR_CONTC_MASK;
+
+ bus->cs_change_on_last_msg = msg->cs_change;
}
static inline bool imxrt_lpspi_rx_fifo_not_empty(
@@ -184,48 +213,72 @@ static inline bool imxrt_lpspi_tx_fifo_not_full(
bus->fifo_size - 2;
}
+static void imxrt_lpspi_next_tx_msg(
+ struct imxrt_lpspi_bus *bus,
+ volatile LPSPI_Type *regs
+)
+{
+ if (bus->tx_msg_todo > 0) {
+ const spi_ioc_transfer *msg;
+
+ msg = bus->tx_msg;
+
+ imxrt_lpspi_config(bus, regs, msg);
+ bus->remaining_tx_size = msg->len;
+ bus->tx_buf = msg->tx_buf;
+ }
+}
+
static void imxrt_lpspi_fill_tx_fifo(
struct imxrt_lpspi_bus *bus,
volatile LPSPI_Type *regs
)
{
while(imxrt_lpspi_tx_fifo_not_full(bus, regs)
- && bus->remaining_tx_size > 0) {
- if (bus->remaining_tx_size == 1) {
- regs->TCR &= ~(LPSPI_TCR_CONT_MASK);
- }
+ && (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0)) {
+ if (bus->remaining_tx_size > 0) {
+ if (bus->remaining_tx_size == 1 && bus->tx_msg->cs_change) {
+ /*
+ * Necessary for getting the last data out of the Rx FIFO. See "i.MX
+ * RT1050 Processor Reference Manual Rev. 4" Chapter 47.3.2.2 "Receive
+ * FIFO and Data Match":
+ *
+ * "During a continuous transfer, if the transmit FIFO is empty, then
+ * the receive data is only written to the receive FIFO after the
+ * transmit FIFO is written or after the Transmit Command Register (TCR)
+ * is written to end the frame."
+ */
+ regs->TCR &= ~(LPSPI_TCR_CONT_MASK);
+ }
- if (bus->tx_buf != NULL) {
- regs->TDR = bus->tx_buf[0];
- ++bus->tx_buf;
- } else {
- regs->TDR = 0;
+ if (bus->tx_buf != NULL) {
+ regs->TDR = bus->tx_buf[0];
+ ++bus->tx_buf;
+ } else {
+ regs->TDR = 0;
+ }
+ --bus->remaining_tx_size;
+ }
+ if (bus->remaining_tx_size == 0) {
+ --bus->tx_msg_todo;
+ ++bus->tx_msg;
+ imxrt_lpspi_next_tx_msg(bus, regs);
}
- --bus->remaining_tx_size;
}
}
-static void imxrt_lpspi_next_msg(
+static void imxrt_lpspi_next_rx_msg(
struct imxrt_lpspi_bus *bus,
volatile LPSPI_Type *regs
)
{
- if (bus->msg_todo > 0) {
+ if (bus->rx_msg_todo > 0) {
const spi_ioc_transfer *msg;
- msg = bus->msg;
+ msg = bus->rx_msg;
- imxrt_lpspi_config(bus, regs, msg);
- bus->remaining_tx_size = msg->len;
bus->remaining_rx_size = msg->len;
bus->rx_buf = msg->rx_buf;
- bus->tx_buf = msg->tx_buf;
-
- imxrt_lpspi_fill_tx_fifo(bus, regs);
- regs->IER = LPSPI_IER_TDIE_MASK;
- } else {
- regs->IER = 0;
- rtems_binary_semaphore_post(&bus->sem);
}
}
@@ -234,15 +287,22 @@ static void imxrt_lpspi_pull_data_from_rx_fifo(
volatile LPSPI_Type *regs
)
{
- while (imxrt_lpspi_rx_fifo_not_empty(regs) && bus->remaining_rx_size > 0) {
- uint32_t data;
-
- data = regs->RDR;
- if (bus->rx_buf != NULL) {
- *bus->rx_buf = data;
- ++bus->rx_buf;
+ uint32_t data;
+ while (imxrt_lpspi_rx_fifo_not_empty(regs)
+ && (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0)) {
+ if (bus->remaining_rx_size > 0) {
+ data = regs->RDR;
+ if (bus->rx_buf != NULL) {
+ *bus->rx_buf = data;
+ ++bus->rx_buf;
+ }
+ --bus->remaining_rx_size;
+ }
+ if (bus->remaining_rx_size == 0) {
+ --bus->rx_msg_todo;
+ ++bus->rx_msg;
+ imxrt_lpspi_next_rx_msg(bus, regs);
}
- --bus->remaining_rx_size;
}
}
@@ -257,48 +317,62 @@ static void imxrt_lpspi_interrupt(void *arg)
imxrt_lpspi_pull_data_from_rx_fifo(bus, regs);
imxrt_lpspi_fill_tx_fifo(bus, regs);
- if (bus->remaining_tx_size == 0) {
- if (bus->remaining_rx_size > 0) {
- regs->IER = LPSPI_IER_RDIE_MASK;
- } else {
- --bus->msg_todo;
- ++bus->msg;
- imxrt_lpspi_next_msg(bus, regs);
- }
+ if (bus->tx_msg_todo > 0 || bus->remaining_tx_size > 0) {
+ regs->IER = LPSPI_IER_TDIE_MASK;
+ } else if (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0) {
+ regs->IER = LPSPI_IER_RDIE_MASK;
+ } else {
+ regs->IER = 0;
+ rtems_binary_semaphore_post(&bus->sem);
}
}
static inline int imxrt_lpspi_settings_ok(
struct imxrt_lpspi_bus *bus,
- const spi_ioc_transfer *msg
+ const spi_ioc_transfer *msg,
+ const spi_ioc_transfer *prev_msg
)
{
- if (msg->cs_change == 0) {
- /*
- * This one most likely would need a bigger workaround if it is necessary.
- * See "i.MX RT1050 Processor Reference Manual Rev. 4" Chapter 47.3.2.2
- * "Receive FIFO and Data Match":
- *
- * "During a continuous transfer, if the transmit FIFO is empty, then the
- * receive data is only written to the receive FIFO after the transmit FIFO
- * is written or after the Transmit Command Register (TCR) is written to end
- * the frame."
- *
- * It might is possible to extend the driver so that it can work with an
- * empty read buffer.
- */
- return -EINVAL;
- }
-
/* 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
+ * bit of TCR can't be changed if it is a continuous transfer.
+ */
+ if (prev_msg->cs != msg->cs ||
+ prev_msg->speed_hz != msg->speed_hz ||
+ prev_msg->mode != msg->mode) {
+ return -EINVAL;
+ }
+ }
+
return 0;
}
@@ -308,20 +382,122 @@ static int imxrt_lpspi_check_messages(
uint32_t size
)
{
+ const spi_ioc_transfer *prev_msg = NULL;
+
while(size > 0) {
int rv;
- rv = imxrt_lpspi_settings_ok(bus, msg);
+ rv = imxrt_lpspi_settings_ok(bus, msg, prev_msg);
if (rv != 0) {
return rv;
}
+ prev_msg = msg;
++msg;
--size;
}
+ /*
+ * 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;
+ }
+
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,
@@ -336,12 +512,19 @@ static int imxrt_lpspi_transfer(
rv = imxrt_lpspi_check_messages(bus, msgs, n);
if (rv == 0) {
- bus->msg_todo = n;
- bus->msg = &msgs[0];
-
- imxrt_lpspi_next_msg(bus, bus->regs);
- 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;
}
@@ -416,7 +599,7 @@ static int imxrt_lpspi_setup(spi_bus *base)
bus = (struct imxrt_lpspi_bus *) base;
- rv = imxrt_lpspi_settings_ok(bus, &msg);
+ rv = imxrt_lpspi_settings_ok(bus, &msg, NULL);
/*
* Nothing to do besides checking.
@@ -426,12 +609,15 @@ static int imxrt_lpspi_setup(spi_bus *base)
return rv;
}
-static uint32_t imxrt_lpspi_get_src_freq(void)
+static uint32_t imxrt_lpspi_get_src_freq(clock_ip_name_t clock_ip)
{
uint32_t freq;
+#if IMXRT_IS_MIMXRT10xx
uint32_t mux;
uint32_t divider;
+ (void) clock_ip; /* Not necessary for i.MXRT1050 */
+
mux = CLOCK_GetMux(kCLOCK_LpspiMux);
switch (mux) {
@@ -453,6 +639,17 @@ static uint32_t imxrt_lpspi_get_src_freq(void)
divider = CLOCK_GetDiv(kCLOCK_LpspiDiv) + 1;
freq /= divider;
+#elif IMXRT_IS_MIMXRT11xx
+ /*
+ * FIXME: A future version of the mcux_sdk might provide a better method to
+ * get the clock instead of this hack.
+ */
+ clock_root_t clock_root = clock_ip + kCLOCK_Root_Lpspi1 - kCLOCK_Lpspi1;
+
+ freq = CLOCK_GetRootClockFreq(clock_root);
+#else
+ #error Getting SPI frequency is not implemented for this chip.
+#endif
return freq;
}
@@ -505,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) {
@@ -528,8 +728,79 @@ 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->src_clock_hz = imxrt_lpspi_get_src_freq(bus->clock_ip);
/* Absolut maximum is 30MHz according to electrical characteristics */
bus->base.max_speed_hz = MIN(bus->src_clock_hz / 2, 30000000);
bus->base.delay_usecs = 0;