From bd929a3746879d3cf05e0ec7b90947344bf1e166 Mon Sep 17 00:00:00 2001 From: iosabi Date: Sat, 11 Apr 2020 22:13:44 +0000 Subject: [PATCH] cpu/qn908x: Add support for UART. The QN908x has four FLEXCOMM interfaces that support a subset of UART, SPI or I2C each one. This patch adds generic support for dealing with the FLEXCOMM initialization and interrupts and adds a driver for RX/TX support in UART. With this patch is now possible to use a shell on the device over UART. --- boards/qn9080dk/Kconfig | 2 + boards/qn9080dk/Makefile.features | 1 + boards/qn9080dk/include/periph_conf.h | 16 +- cpu/qn908x/Makefile.dep | 7 +- cpu/qn908x/doc.txt | 26 +++ cpu/qn908x/include/flexcomm.h | 67 ++++++ cpu/qn908x/include/periph_cpu.h | 55 +++++ cpu/qn908x/periph/flexcomm.c | 142 +++++++++++++ cpu/qn908x/periph/uart.c | 292 ++++++++++++++++++++++++++ 9 files changed, 603 insertions(+), 5 deletions(-) create mode 100644 cpu/qn908x/include/flexcomm.h create mode 100644 cpu/qn908x/periph/flexcomm.c create mode 100644 cpu/qn908x/periph/uart.c diff --git a/boards/qn9080dk/Kconfig b/boards/qn9080dk/Kconfig index 2d175d08ad..1f44df993d 100644 --- a/boards/qn9080dk/Kconfig +++ b/boards/qn9080dk/Kconfig @@ -17,5 +17,7 @@ config BOARD_QN9080DK # Put defined MCU peripherals here (in alphabetical order) select BOARD_HAS_XTAL32K select BOARD_HAS_XTAL_32M + select HAS_PERIPH_UART + select HAS_PERIPH_UART_MODECFG source "$(RIOTBOARD)/common/qn908x/Kconfig" diff --git a/boards/qn9080dk/Makefile.features b/boards/qn9080dk/Makefile.features index e79116aff7..f4cfcb4ccc 100644 --- a/boards/qn9080dk/Makefile.features +++ b/boards/qn9080dk/Makefile.features @@ -3,6 +3,7 @@ CPU_MODEL = qn9080xhn # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_uart periph_uart_modecfg # Include the common qn908x board features. include $(RIOTBOARD)/common/qn908x/Makefile.features diff --git a/boards/qn9080dk/include/periph_conf.h b/boards/qn9080dk/include/periph_conf.h index a028149990..abfaee80fb 100644 --- a/boards/qn9080dk/include/periph_conf.h +++ b/boards/qn9080dk/include/periph_conf.h @@ -28,10 +28,24 @@ extern "C" { #endif +/** + * @name UART configuration + * @{ + */ +static const uart_conf_t uart_config[] = { + { + .dev = USART0, + .rx_pin = GPIO_PIN(PORT_A, 17), + .tx_pin = GPIO_PIN(PORT_A, 16), + }, +}; + +#define UART_NUMOF ARRAY_SIZE(uart_config) +/** @} */ + /* put here the board peripherals definitions: - Available clocks - Timers - - UARTs - PWMs - SPIs - I2C diff --git a/cpu/qn908x/Makefile.dep b/cpu/qn908x/Makefile.dep index eb318d253f..6b18dfe7e6 100644 --- a/cpu/qn908x/Makefile.dep +++ b/cpu/qn908x/Makefile.dep @@ -10,9 +10,8 @@ USEMODULE += vendor_fsl_clock # All peripherals use gpio_mux.h USEMODULE += periph_gpio_mux -# This cpu modules doesn't support UART peripherals yet, so we need to include -# stdio_null. -# TODO: Remove stdio_null once periph_uart is implemented in this module. -USEMODULE += stdio_null +ifneq (,$(filter periph_uart,$(USEMODULE))) + USEMODULE += periph_flexcomm +endif include $(RIOTCPU)/cortexm_common/Makefile.dep diff --git a/cpu/qn908x/doc.txt b/cpu/qn908x/doc.txt index 9035815773..e77ea1896c 100644 --- a/cpu/qn908x/doc.txt +++ b/cpu/qn908x/doc.txt @@ -31,6 +31,32 @@ The GPIO driver uses the @ref GPIO_PIN(port, pin) macro to declare pins. No configuration is necessary. +@defgroup cpu_qn908x_uart NXP QN908x UART +@ingroup cpu_qn908x +@brief NXP QN908x UART driver + +There are several FLEXCOMM interfaces in this chip, but only two of these +support UART (FLEXCOMM0 and FLEXCOMM1). The default UART mode is 8n1 and can +be changed with the uart_mode() function. If only RX or only TX is desired, the +other pin can be set to GPIO_UNDEF. + +### UART configuration example (for periph_conf.h) ### + + static const uart_conf_t uart_config[] = { + { + .dev = USART0, + .rx_pin = GPIO_PIN(PORT_A, 17), /* or 5 */ + .tx_pin = GPIO_PIN(PORT_A, 16), /* or 4 */ + }, + { + .dev = USART1, + .rx_pin = GPIO_PIN(PORT_A, 9), /* or 13 */ + .tx_pin = GPIO_PIN(PORT_A, 8), /* or 12 */ + }, + }; + #define UART_NUMOF ARRAY_SIZE(uart_config) + + @defgroup cpu_qn908x_wdt NXP QN908x Watchdog timer (WDT) @ingroup cpu_qn908x @brief NXP QN908x Watchdog timer (WDT) diff --git a/cpu/qn908x/include/flexcomm.h b/cpu/qn908x/include/flexcomm.h new file mode 100644 index 0000000000..1589c34e6e --- /dev/null +++ b/cpu/qn908x/include/flexcomm.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * + * @{ + * + * @file + * @brief Flexcomm interface functions. + * + * The FLEXCOMM blocks can operate in different modes such as UART, SPI and I2C, + * but not all modules support all modes. These functions allow to initialize + * and configure the FLEXCOMM, as well as route back the ISRs to the + * corresponding module. + * + * @author iosabi + */ + +#ifndef FLEXCOMM_H +#define FLEXCOMM_H + +#include +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Flexcomm PSELID values + * + * This value identifies the current function of a FLEXCOMM module. + */ +typedef enum { + FLEXCOMM_ID_UART = 1, /**< UART mode. */ + FLEXCOMM_ID_SPI = 2, /**< SPI mode. */ + FLEXCOMM_ID_I2C = 3, /**< I2C mode. */ +} flexcom_pselid_t; + +/** + * @brief Initialize a flexcomm module to operate as the selected mode. + * + * @returns -1 in case of error, otherwise returns the number of flexcomm + * instance initialized, such as 2 for FLEXCOMM2. + */ +int flexcomm_init(FLEXCOMM_Type *dev, flexcom_pselid_t mode); + +/** + * @brief Obtain the flexcomm block number (0-based) from the address. + * + * For example, the flexcomm block number of FLEXCOMM2, the pointer to the + * FLEXCOMM_Type block is 2. If an invalid address is passed returns -1. + */ +int flexcomm_instance_from_addr(FLEXCOMM_Type *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* FLEXCOMM_H */ +/** @} */ diff --git a/cpu/qn908x/include/periph_cpu.h b/cpu/qn908x/include/periph_cpu.h index 9d94eece99..6d87741dc2 100644 --- a/cpu/qn908x/include/periph_cpu.h +++ b/cpu/qn908x/include/periph_cpu.h @@ -140,6 +140,61 @@ enum { GPIO_PORTS_NUMOF /**< overall number of available ports */ }; +/** + * @brief UART module configuration options + * + * QN908x doesn't have any UART standalone blocks, but it has two FLEXCOMM + * blocks that can be put in UART mode. The USART_Type* address is one of the + * FLEXCOMM_Type* addresses as well. + */ +typedef struct { + USART_Type *dev; /**< Pointer to module hardware registers */ + gpio_t rx_pin; /**< RX pin, GPIO_UNDEF disables RX. */ + gpio_t tx_pin; /**< TX pin, GPIO_UNDEF disables TX. */ +} uart_conf_t; + +/** + * @brief Definition of possible parity modes + * + * These are defined to match the values of the USART->CFG : PARITYSEL bit + * field. + * @{ + */ +typedef enum { + UART_PARITY_NONE = 0, /**< no parity */ + UART_PARITY_EVEN = 2, /**< even parity */ + UART_PARITY_ODD = 3, /**< odd parity */ +} uart_parity_t; +#define HAVE_UART_PARITY_T +/** @} */ + +/** + * @brief Definition of possible data bits lengths in a UART frame + * + * These are defined to match the values of the USART->CFG : DATALEN bit field. + * @{ + */ +typedef enum { + UART_DATA_BITS_7 = 0, /**< 7 data bits */ + UART_DATA_BITS_8 = 1, /**< 8 data bits */ + /* Note: There's a UART_DATA_BITS_9 possible in this hardware. */ +} uart_data_bits_t; +#define HAVE_UART_DATA_BITS_T +/** @} */ + +/** + * @brief Definition of possible stop bits lengths + * + * These are defined to match the values of the USART->CFG : STOPLEN bit field. + * @{ + */ +typedef enum { + UART_STOP_BITS_1 = 0, /**< 1 stop bit */ + UART_STOP_BITS_2 = 1, /**< 2 stop bits */ +} uart_stop_bits_t; +#define HAVE_UART_STOP_BITS_T +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/qn908x/periph/flexcomm.c b/cpu/qn908x/periph/flexcomm.c new file mode 100644 index 0000000000..80caefaddd --- /dev/null +++ b/cpu/qn908x/periph/flexcomm.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * + * @{ + * + * @file + * @brief Flexcomm interrupt dispatch. + * + * @author iosabi + * + * @} + */ + +#include +#include "cpu.h" +#include "periph_conf.h" +#include "vectors_qn908x.h" +#include "flexcomm.h" + +#include "vendor/drivers/fsl_clock.h" + +#include "debug.h" + +int flexcomm_init(FLEXCOMM_Type *dev, flexcom_pselid_t mode) +{ + static const clock_ip_name_t flexcomm_clocks[] = FLEXCOMM_CLOCKS; + int flexcomm_num = flexcomm_instance_from_addr(dev); + + if (flexcomm_num < 0 || flexcomm_num >= (int)ARRAY_SIZE(flexcomm_clocks)) { + DEBUG("Invalid flexcomm_num: %d\n", flexcomm_num); + return -1; + } + CLOCK_EnableClock(flexcomm_clocks[flexcomm_num]); + /* Reset the flexcomm. */ + SYSCON->RST_SW_SET = 1u << flexcomm_num; + SYSCON->RST_SW_CLR = 1u << flexcomm_num; + + /* Check that the mode is present in the FLEXCOMM. + * Bits 4, 5 and 6 tell whether the UART, SPI and I2C respectively are + * present. */ + if ((dev->PSELID & (1u << (mode + 3))) == 0) { + DEBUG("Mode %d not present in FLEXCOMM%d\n", (int)mode, flexcomm_num); + return -1; + } + + /* This also locks the peripheral so it can't be changed until device or + * peripheral is reset. */ + dev->PSELID = (dev->PSELID & ~FLEXCOMM_PSELID_PERSEL_MASK) | + FLEXCOMM_PSELID_LOCK_MASK | + FLEXCOMM_PSELID_PERSEL(mode); + return flexcomm_num; +} + +int flexcomm_instance_from_addr(FLEXCOMM_Type *dev) +{ + static const FLEXCOMM_Type *flexcomm_addrs[] = FLEXCOMM_BASE_PTRS; + + for (uint8_t i = 0; i < ARRAY_SIZE(flexcomm_addrs); i++) { + if (flexcomm_addrs[i] == dev) { + return i; + } + } + DEBUG("Invalid FLEXCOMM address.\n"); + return -1; +} + +#ifdef MODULE_PERIPH_UART +extern void isr_flexcomm_uart(USART_Type *dev, uint32_t flexcomm_num); +#endif /* MODULE_PERIPH_UART */ + +#ifdef MODULE_PERIPH_SPI +extern void isr_flexcomm_spi(SPI_Type *dev, uint32_t flexcomm_num); +#endif /* MODULE_PERIPH_SPI */ + +#ifdef MODULE_PERIPH_I2C +extern void isr_flexcomm_i2c(I2C_Type *dev, uint32_t flexcomm_num); +#endif /* MODULE_PERIPH_I2C */ + +/** + * @brief General Flexcomm interrupt handler dispatch. + * + * The driver that should get an interrupt from the flexcomm depends on the + * currently configured one, which can be obtained from the PSELID. + */ +static void isr_flexcomm(void *flexcomm, uint32_t flexcomm_num) +{ + switch (((FLEXCOMM_Type *)flexcomm)->PSELID & FLEXCOMM_PSELID_PERSEL_MASK) { +#ifdef MODULE_PERIPH_UART + case FLEXCOMM_ID_UART: + isr_flexcomm_uart((USART_Type *)(flexcomm), flexcomm_num); + return; +#endif /* MODULE_PERIPH_UART */ +#ifdef MODULE_PERIPH_SPI + case FLEXCOMM_ID_SPI: + isr_flexcomm_spi((SPI_Type *)(flexcomm), flexcomm_num); + return; +#endif /* MODULE_PERIPH_SPI */ +#ifdef MODULE_PERIPH_I2C + case FLEXCOMM_ID_I2C: + isr_flexcomm_i2c((I2C_Type *)(flexcomm), flexcomm_num); + return; +#endif /* MODULE_PERIPH_I2C */ + default: + cortexm_isr_end(); + } +} + +#ifdef FLEXCOMM0 +void isr_flexcomm0(void) +{ + isr_flexcomm(FLEXCOMM0, 0); +} +#endif /* FLEXCOMM0 */ + +#ifdef FLEXCOMM1 +void isr_flexcomm1(void) +{ + isr_flexcomm(FLEXCOMM1, 1); +} +#endif /* FLEXCOMM1 */ + +#ifdef FLEXCOMM2 +void isr_flexcomm2(void) +{ + isr_flexcomm(FLEXCOMM2, 2); +} +#endif /* FLEXCOMM2 */ + +#ifdef FLEXCOMM3 +void isr_flexcomm3(void) +{ + isr_flexcomm(FLEXCOMM3, 3); +} +#endif /* FLEXCOMM3 */ diff --git a/cpu/qn908x/periph/uart.c b/cpu/qn908x/periph/uart.c new file mode 100644 index 0000000000..9844fd03ad --- /dev/null +++ b/cpu/qn908x/periph/uart.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * @ingroup drivers_periph_uart + * + * @{ + * + * @file + * @brief Low-level UART driver implementation + * + * This implementation only supports blocking writing using busy-wait. + * + * @author iosabi + * + * @} + */ + +#include "cpu.h" +#include "periph_conf.h" +#include "periph/gpio.h" +#include "periph/uart.h" +#include "gpio_mux.h" +#include "flexcomm.h" + +#include + +#include "vendor/drivers/fsl_clock.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief Runtime UART configuration with user callback function for RX. + */ +static uart_isr_ctx_t config[UART_NUMOF]; + +/** + * @brief The device number (in the UART_NUMOF range) registered for each + * flexcomm port. + */ +static uart_t uart_dev_from_flexcomm[FSL_FEATURE_SOC_FLEXCOMM_COUNT] = { + [0 ... FSL_FEATURE_SOC_FLEXCOMM_COUNT - 1] = UART_NUMOF +}; + +/** + * @brief Limit values for the USART OSRVAL, BRGVAL and MULTx values. + * @{ + */ +#define UART_OSRVAL_MAX 15u +#define UART_BRGVAL_MAX 0xffffu +#define UART_MULTX_MAX 255u +/** @} */ + +/** + * @brief The maximum UART frequency divisor possible. + * + * This will limit the lower end of the baudrate allowed. + */ +#define UART_MAX_DIVISOR ((UART_OSRVAL_MAX + 1) * (UART_BRGVAL_MAX + 1) * \ + (UART_MULTX_MAX + 256)) + +static int _uart_set_baudrate(USART_Type *dev, uint8_t flexcomm_num, + uint32_t baudrate) +{ + assert(flexcomm_num < 2); + /* The FLEXCOMM clock for the FLEXCOMM0 and FLEXCOMM1 is based on the + * kCLOCK_BusClk clock frequency with an optional divisor, using the + * following formula: + * flexcomm freq := bus freq / (1 + (MULTx / 256)) + * where MULTx is a value between 0 and 255 and is set with + * CLOCK_SetClkDiv(). Only the FLEXCOMM0 and FLEXCOMM1 support USART + * function, so the MULTx is always available. + * The UART baudrate is then: + * uart baudrate := flexcomm freq / ((OSRVAL + 1) * (BRGVAL + 1)) + * where OSRVAL (oversample selection value) is a number between 4 and 15 + * (the larger the better) and BRGVAL is between 0 and 0xffff. + * Combining and expanding the previous expression: + * bus freq * 256 / baudrate = (256 + MULTx) * (OSRVAL + 1) * (BRGVAL + 1) + * so we need to find those the values that minimize the error and maximize + * OSRVAL. + */ + if (baudrate == 0) { + return UART_NOBAUD; + } + uint32_t bus_freq = CLOCK_GetFreq(kCLOCK_BusClk); + uint32_t target; + { + /* The remainder of this division is unavoidable frequency error at the + * current clock frequency so we can discard it now. We add 127 to round + * up or down to the nearest target value. */ + const uint64_t target64 = (((uint64_t)(bus_freq) << 8ull) + 127u) / + baudrate; + if (target64 > UART_MAX_DIVISOR) { + return UART_NOBAUD; + } + /* At this point we know the target value fits in 32-bit since + * UART_MAX_DIVISOR fits in 32-bit. */ + target = target64; + } + + uint32_t best_osrval = 0; + uint32_t best_multx = 0; + uint32_t best_brgval = 0; + uint32_t best_error = UINT_MAX; + + /* To simplify the math, let's assume we need to pick 3 values A, B and C + * such that A * B * C is as close as a possible to a given target T. In + * other words, we need to minimize the error |T - A * B * C|. + * To do that, we scan over all possible values of A and B (about 2000 + * possibilities) and compute the error value taking C as the following: + * C := floor((T + A * B / 2) / (A * B)) + * To compute the error we can avoid some multiplications if we consider + * that we can decompose a number N as "floor(N / M) * M + remainder(N, M)" + * taking N = T + A * B / 2 and M = A * B we get the error: + * |T - A * B * C| = |T + A * B / 2 - A * B * C - A * B / 2| + * = |remainder(T + A * B / 2, A * B) - A * B / 2| + */ + for (uint8_t osrval_p1 = UART_OSRVAL_MAX + 1; osrval_p1 > 8; osrval_p1--) { + /* Initial value of (OSRVAL + 1) * (256 + MULTx) */ + uint32_t m = osrval_p1 * 256; + for (uint32_t multx_p256 = 256; + multx_p256 < 256 + UART_MULTX_MAX && best_error != 0 && + m / 2 <= target; + multx_p256++, m += osrval_p1) { + uint32_t error = (target + m / 2) % m; + error = abs((int32_t)error - (int32_t)(m / 2)); + if (error < best_error) { + /* Only in this case we need to do the division as well. */ + uint32_t brgval_p1 = (target + m / 2) / m; + if (brgval_p1 > (UART_BRGVAL_MAX + 1)) { + continue; + } + best_osrval = osrval_p1 - 1; + best_multx = multx_p256 - 256; + best_brgval = brgval_p1 - 1; + best_error = error; + } + } + if (best_error == 0) { + break; + } + } + + if (best_osrval == 0) { + return UART_NOBAUD; + } + + CLOCK_SetClkDiv(flexcomm_num ? kCLOCK_DivFrg1 : kCLOCK_DivFrg0, best_multx); + dev->BRG = best_brgval; + dev->OSR = best_osrval; + return UART_OK; +} + +int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) +{ + assert(uart < UART_NUMOF); + + const uart_conf_t *uart_conf = uart_config + uart; + USART_Type *const dev = uart_conf->dev; + + int flexcomm_num = flexcomm_init((FLEXCOMM_Type *)dev, FLEXCOMM_ID_UART); + if (flexcomm_num < 0) { + return UART_INTERR; + } + uart_dev_from_flexcomm[flexcomm_num] = uart; + + int ret = _uart_set_baudrate(dev, flexcomm_num, baudrate); + if (ret != UART_OK) { + return ret; + } + + /* remember callback addresses */ + config[uart].rx_cb = rx_cb; + config[uart].arg = arg; + + /* Interrupt trigger and level for RX and TX disabled by default. */ + dev->FIFOTRIG = 0; + + /* Enable RX side. */ + if (rx_cb != NULL && gpio_is_valid(uart_conf->rx_pin)) { + /* Trigger RX interrupt when there is at least 1 byte (RXLVL = 0). */ + dev->FIFOTRIG |= USART_FIFOTRIG_RXLVLENA_MASK | USART_FIFOTRIG_RXLVL(0); + + /* Enable RX interrupt. */ + dev->FIFOCFG |= USART_FIFOCFG_EMPTYRX_MASK | + USART_FIFOCFG_ENABLERX_MASK; + dev->FIFOINTENSET = USART_FIFOINTENSET_RXLVL_MASK; + /* flexcomm_num is the same as the USART instance number in the + * USART_IRQS array. */ + const uint8_t usart_irqn[] = USART_IRQS; + NVIC_EnableIRQ(usart_irqn[flexcomm_num]); + } + else { + dev->FIFOCFG &= ~USART_FIFOCFG_ENABLERX_MASK; + uart_conf->dev->FIFOINTENCLR = USART_FIFOINTENSET_RXLVL_MASK; + } + + /* Enable TX side. */ + if (gpio_is_valid(uart_conf->tx_pin)) { + dev->FIFOCFG |= USART_FIFOCFG_EMPTYTX_MASK | + USART_FIFOCFG_ENABLETX_MASK; + } + else { + dev->FIFOCFG &= ~USART_FIFOCFG_ENABLETX_MASK; + } + + /* Configure RX and TX pins. RX/TX pins are always in function 4. + * GPIO_UNDEF are ignored. */ + gpio_init_mux(uart_conf->rx_pin, 4); + gpio_init_mux(uart_conf->tx_pin, 4); + /* This call also enables the UART. */ + return uart_mode(uart, UART_DATA_BITS_8, UART_PARITY_NONE, + UART_STOP_BITS_1); +} + +int uart_mode(uart_t uart, uart_data_bits_t data_bits, uart_parity_t parity, + uart_stop_bits_t stop_bits) +{ + /* Setup mode and enable USART. The values of the uart_data_bits_t, + * uart_parity_t and uart_stop_bits_t enums were selected to match the + * fields in this registers so there's no need to do any conversion. */ + uart_config[uart].dev->CFG = USART_CFG_PARITYSEL(parity) + | USART_CFG_STOPLEN(stop_bits) + | USART_CFG_DATALEN(data_bits) + | USART_CFG_LOOP(0) | USART_CFG_ENABLE_MASK; + return UART_OK; +} + +void uart_write(uart_t uart, const uint8_t *data, size_t len) +{ + USART_Type *dev = uart_config[uart].dev; + + /* If the TX side or the whole uart mode was not enabled during init or at + * all yet we can only ignore this transmission. This allows DEBUG messages + * to be ignored without hanging here before the uart is initialized. */ + if (!(dev->FIFOCFG & USART_FIFOCFG_ENABLETX_MASK) || + !(dev->CFG & USART_CFG_ENABLE_MASK)) { + return; + } + + for (; len; len--) { + while (!(dev->FIFOSTAT & USART_FIFOSTAT_TXNOTFULL_MASK)) {} + dev->FIFOWR = *(data++); + } + /* Wait until we flush out all the bytes. */ + while (!(dev->STAT & USART_STAT_TXIDLE_MASK)) {} +} + +void uart_poweron(uart_t uart) +{ + USART_Type *dev = uart_config[uart].dev; + + dev->CFG |= USART_CFG_ENABLE_MASK; +} + +void uart_poweroff(uart_t uart) +{ + USART_Type *dev = uart_config[uart].dev; + + while (!(dev->STAT & USART_STAT_TXIDLE_MASK)) {} + dev->CFG &= ~USART_CFG_ENABLE_MASK; +} + +void isr_flexcomm_uart(USART_Type *dev, uint32_t flexcomm_num) +{ + uart_t uart = uart_dev_from_flexcomm[flexcomm_num]; + + while (dev->FIFOSTAT & USART_FIFOSTAT_RXNOTEMPTY_MASK) { + /* Reading from FIFORD may clear the FIFOSTAT RXNOTEMPTY if we read all + * the bytes. */ + uint8_t data = dev->FIFORD; + if (uart < UART_NUMOF && config[uart].rx_cb != NULL) { + config[uart].rx_cb(config[uart].arg, data); + } + } + + if (dev->FIFOSTAT & USART_FIFOSTAT_RXERR_MASK) { + /* This is a USART FIFO RX overrun. + * Note: writing a 1 to the FIFOSTAT flag clears it. */ + dev->FIFOSTAT = USART_FIFOSTAT_RXERR_MASK; + /* TODO: Signal an error to the application. */ + } + + cortexm_isr_end(); +}