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

296 lines
9.8 KiB
C

/*
* 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)
{
if ((data_bits & UART_INVALID_MODE) || (parity & UART_INVALID_MODE)) {
return UART_NOMODE;
}
/* 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();
}