diff options
Diffstat (limited to 'bsps/arm/imxrt/spi/imxrt-lpspi.c')
-rw-r--r-- | bsps/arm/imxrt/spi/imxrt-lpspi.c | 433 |
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; |