diff options
Diffstat (limited to 'bsps/arm/imxrt/spi/imxrt-lpspi.c')
-rw-r--r-- | bsps/arm/imxrt/spi/imxrt-lpspi.c | 264 |
1 files changed, 242 insertions, 22 deletions
diff --git a/bsps/arm/imxrt/spi/imxrt-lpspi.c b/bsps/arm/imxrt/spi/imxrt-lpspi.c index 80b47f9663..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; @@ -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; } @@ -477,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) { @@ -504,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; } @@ -556,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) { @@ -579,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; |