1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

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.
This commit is contained in:
iosabi 2020-04-11 22:13:44 +00:00
parent 0427e28f17
commit bd929a3746
9 changed files with 603 additions and 5 deletions

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 <iosabi@protonmail.com>
*/
#ifndef FLEXCOMM_H
#define FLEXCOMM_H
#include <stdint.h>
#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 */
/** @} */

View File

@ -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

View File

@ -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 <iosabi@protonmail.com>
*
* @}
*/
#include <stdint.h>
#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 */

292
cpu/qn908x/periph/uart.c Normal file
View File

@ -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 <iosabi@protonmail.com>
*
* @}
*/
#include "cpu.h"
#include "periph_conf.h"
#include "periph/gpio.h"
#include "periph/uart.h"
#include "gpio_mux.h"
#include "flexcomm.h"
#include <stdlib.h>
#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();
}