1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/kinetis/periph/uart.c
2020-10-23 00:46:26 +02:00

459 lines
12 KiB
C

/*
* Copyright (C) 2017 Eistec AB
* Copyright (C) 2014 PHYTEC Messtechnik GmbH
* Copyright (C) 2014 Freie Universität Berlin
*
* 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_kinetis
* @ingroup drivers_periph_uart
*
* @{
*
* @file
* @brief Low-level UART driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Johann Fischer <j.fischer@phytec.de>
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/
#include <assert.h>
#include "cpu.h"
#include "bit.h"
#include "periph_conf.h"
#include "periph/uart.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#ifndef KINETIS_HAVE_LPUART
#ifdef LPUART0
#define KINETIS_HAVE_LPUART 1
#else
#define KINETIS_HAVE_LPUART 0
#endif
#endif /* KINETIS_HAVE_LPUART */
#ifndef KINETIS_HAVE_UART
#ifdef UART0
#define KINETIS_HAVE_UART 1
#else
#define KINETIS_HAVE_UART 0
#endif
#endif /* KINETIS_HAVE_LPUART */
#ifndef KINETIS_UART_ADVANCED
/**
* Attempts to determine the type of the UART,
* using the BRFA field in the UART C4 register.
*/
#ifdef UART_C4_BRFA
#define KINETIS_UART_ADVANCED 1
#endif
#endif
#ifndef LPUART_OVERSAMPLING_RATE_MIN
/* Use 10x oversampling at minimum, this will be iterated to achieve a better
* baud rate match than otherwise possible with a fixed oversampling rate */
/* Hardware reset default value is an oversampling rate of 16 */
#define LPUART_OVERSAMPLING_RATE_MIN (10)
#endif
#ifndef LPUART_OVERSAMPLING_RATE_MAX
#define LPUART_OVERSAMPLING_RATE_MAX (32)
#endif
/* Default LPUART clock setting to avoid compilation failures, define this in
* periph_conf.h to set board specific configuration if using the LPUART. */
#ifndef LPUART_0_SRC
#define LPUART_0_SRC 0
#endif
#ifndef LPUART_1_SRC
#define LPUART_1_SRC 0
#endif
/**
* @brief Runtime configuration space, holds pointers to callback functions for RX
*/
static uart_isr_ctx_t config[UART_NUMOF];
static inline void uart_init_pins(uart_t uart);
#if KINETIS_HAVE_UART
static inline void uart_init_uart(uart_t uart, uint32_t baudrate);
#endif
#if KINETIS_HAVE_LPUART
static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate);
#endif
/* Only use the dispatch function for uart_write if both UART and LPUART are
* available at the same time */
#if KINETIS_HAVE_UART && KINETIS_HAVE_LPUART
#define KINETIS_UART_WRITE_INLINE static inline
KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, size_t len);
KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *data, size_t len);
#else
#define KINETIS_UART_WRITE_INLINE
#if KINETIS_HAVE_UART
#define uart_write_uart uart_write
#elif KINETIS_HAVE_LPUART
#define uart_write_lpuart uart_write
#endif
#endif
int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg)
{
assert(uart < UART_NUMOF);
/* remember callback addresses */
config[uart].rx_cb = rx_cb;
config[uart].arg = arg;
uart_init_pins(uart);
/* Turn on module clock gate */
bit_set32(uart_config[uart].scgc_addr, uart_config[uart].scgc_bit);
switch (uart_config[uart].type) {
#if KINETIS_HAVE_UART
case KINETIS_UART:
uart_init_uart(uart, baudrate);
break;
#endif
#if KINETIS_HAVE_LPUART
case KINETIS_LPUART:
uart_init_lpuart(uart, baudrate);
break;
#endif
default:
return UART_NODEV;
}
return UART_OK;
}
void uart_poweron(uart_t uart)
{
(void)uart;
/* not implemented (yet) */
}
void uart_poweroff(uart_t uart)
{
(void)uart;
/* not implemented (yet) */
}
#if KINETIS_HAVE_UART && KINETIS_HAVE_LPUART
/* Dispatch function to pass to the proper write function depending on UART type
* This function is only used when the CPU supports both UART and LPUART. */
void uart_write(uart_t uart, const uint8_t *data, size_t len)
{
switch (uart_config[uart].type) {
case KINETIS_UART:
uart_write_uart(uart, data, len);
break;
case KINETIS_LPUART:
uart_write_lpuart(uart, data, len);
break;
default:
return;
}
}
#endif
static inline void uart_init_pins(uart_t uart)
{
/* initialize pins */
if (uart_config[uart].pin_rx != GPIO_UNDEF) {
gpio_init_port(uart_config[uart].pin_rx, uart_config[uart].pcr_rx);
}
if (uart_config[uart].pin_tx != GPIO_UNDEF) {
gpio_init_port(uart_config[uart].pin_tx, uart_config[uart].pcr_tx);
}
}
#if KINETIS_HAVE_UART
static inline void uart_init_uart(uart_t uart, uint32_t baudrate)
{
/* do basic initialization */
UART_Type *dev = uart_config[uart].dev;
uint32_t clk;
uint16_t ubd;
clk = uart_config[uart].freq;
/* disable transmitter and receiver */
dev->C2 &= ~(UART_C2_TE_MASK | UART_C2_RE_MASK);
/* Select mode */
dev->C1 = uart_config[uart].mode;
/* calculate baudrate */
ubd = (uint16_t)(clk / (baudrate * 16));
/* set baudrate */
dev->BDH = (uint8_t)UART_BDH_SBR(ubd >> 8);
dev->BDL = (uint8_t)UART_BDL_SBR(ubd);
#if KINETIS_UART_ADVANCED
/* set baudrate fine adjust (brfa) */
uint8_t brfa = ((((4 * clk) / baudrate) + 1) / 2) % 32;
dev->C4 = UART_C4_BRFA(brfa);
/* Enable FIFO buffers */
dev->PFIFO |= UART_PFIFO_RXFE_MASK | UART_PFIFO_TXFE_MASK;
/* Set level to trigger TDRE flag whenever there is space in the TXFIFO */
/* FIFO size is 2^(PFIFO_TXFIFOSIZE + 1) (4, 8, 16 ...) for values != 0.
* TXFIFOSIZE == 0 means size = 1 (i.e. only one byte, no hardware FIFO) */
if ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) != 0) {
uint8_t txfifo_size =
(2 << ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) >>
UART_PFIFO_TXFIFOSIZE_SHIFT));
dev->TWFIFO = UART_TWFIFO_TXWATER(txfifo_size - 1);
}
else {
/* Missing hardware support */
dev->TWFIFO = 0;
}
/* Trigger RX interrupt when there is 1 byte or more in the RXFIFO */
dev->RWFIFO = 1;
/* Clear all hardware buffers now, this must be done whenever the FIFO
* enable flags are modified. */
dev->CFIFO = UART_CFIFO_RXFLUSH_MASK | UART_CFIFO_TXFLUSH_MASK;
#endif /* KINETIS_UART_ADVANCED */
/* enable transmitter and receiver + RX interrupt */
dev->C2 |= UART_C2_TE_MASK | UART_C2_RE_MASK | UART_C2_RIE_MASK;
/* enable receive interrupt */
NVIC_EnableIRQ(uart_config[uart].irqn);
}
#endif /* KINETIS_HAVE_UART */
#if KINETIS_HAVE_UART
KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, size_t len)
{
UART_Type *dev = uart_config[uart].dev;
for (size_t i = 0; i < len; i++) {
while (!(dev->S1 & UART_S1_TDRE_MASK)) {}
dev->D = data[i];
}
}
#if defined(UART_0_ISR) || defined(UART_1_ISR) || defined(UART_2_ISR) || \
defined(UART_3_ISR) || defined(UART_4_ISR)
static inline void irq_handler_uart(uart_t uart)
{
UART_Type *dev = uart_config[uart].dev;
/*
* On Cortex-M0, it happens that S1 is read with LDR
* instruction instead of LDRB. This will read the data register
* at the same time and arrived byte will be lost. Maybe it's a GCC bug.
*
* Observed with: arm-none-eabi-gcc (4.8.3-8+..)
* It does not happen with: arm-none-eabi-gcc (4.8.3-9+11)
*/
if (dev->S1 & UART_S1_RDRF_MASK) {
/* RDRF flag will be cleared when dev-D was read */
uint8_t data = dev->D;
if (config[uart].rx_cb != NULL) {
config[uart].rx_cb(config[uart].arg, data);
}
}
#if (KINETIS_UART_ADVANCED == 0)
/* clear overrun flag */
if (dev->S1 & UART_S1_OR_MASK) {
dev->S1 = UART_S1_OR_MASK;
}
#endif
cortexm_isr_end();
}
#endif
#ifdef UART_0_ISR
void UART_0_ISR(void)
{
irq_handler_uart(UART_DEV(0));
}
#endif
#ifdef UART_1_ISR
void UART_1_ISR(void)
{
irq_handler_uart(UART_DEV(1));
}
#endif
#ifdef UART_2_ISR
void UART_2_ISR(void)
{
irq_handler_uart(UART_DEV(2));
}
#endif
#ifdef UART_3_ISR
void UART_3_ISR(void)
{
irq_handler_uart(UART_DEV(3));
}
#endif
#ifdef UART_4_ISR
void UART_4_ISR(void)
{
irq_handler_uart(UART_DEV(4));
}
#endif
#endif /* KINETIS_HAVE_UART */
#if KINETIS_HAVE_LPUART
static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate)
{
LPUART_Type *dev = uart_config[uart].dev;
/* Set LPUART clock source */
#ifdef SIM_SOPT2_LPUART0SRC
if (dev == LPUART0) {
SIM->SOPT2 = (SIM->SOPT2 & ~SIM_SOPT2_LPUART0SRC_MASK) |
SIM_SOPT2_LPUART0SRC(LPUART_0_SRC);
}
#endif
#ifdef SIM_SOPT2_LPUART1SRC
if (dev == LPUART1) {
SIM->SOPT2 = (SIM->SOPT2 & ~SIM_SOPT2_LPUART1SRC_MASK) |
SIM_SOPT2_LPUART1SRC(LPUART_1_SRC);
}
#endif
/* Select mode */
/* transmitter and receiver disabled */
dev->CTRL = uart_config[uart].mode;
/* calculate baud rate divisor */
uint32_t clk = uart_config[uart].freq;
uint32_t best_err = baudrate;
uint32_t best_osr = LPUART_OVERSAMPLING_RATE_MIN;
/* Use the oversampling rate as a baud rate fine adjust tool */
for (uint32_t osr = LPUART_OVERSAMPLING_RATE_MIN; osr <= LPUART_OVERSAMPLING_RATE_MAX; ++osr) {
uint32_t div = clk / (osr * baudrate);
uint32_t actual_baud = clk / (osr * div);
uint32_t err = ((actual_baud > baudrate) ? actual_baud - baudrate : baudrate - actual_baud);
if (err < best_err) {
best_err = err;
best_osr = osr;
}
}
uint32_t sbr = clk / (best_osr * baudrate);
/* set baud rate */
dev->BAUD = LPUART_BAUD_OSR(best_osr - 1) | LPUART_BAUD_SBR(sbr);
/* enable transmitter and receiver + RX interrupt */
dev->CTRL |= LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK | LPUART_CTRL_RIE_MASK;
/* enable receive interrupt */
NVIC_EnableIRQ(uart_config[uart].irqn);
}
KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *data, size_t len)
{
LPUART_Type *dev = uart_config[uart].dev;
for (size_t i = 0; i < len; i++) {
while ((dev->STAT & LPUART_STAT_TDRE_MASK) == 0) {}
dev->DATA = data[i];
}
}
#if defined(LPUART_0_ISR) || defined(LPUART_1_ISR) || defined(LPUART_2_ISR) || \
defined(LPUART_3_ISR) || defined(LPUART_4_ISR)
static inline void irq_handler_lpuart(uart_t uart)
{
LPUART_Type *dev = uart_config[uart].dev;
uint32_t stat = dev->STAT;
/* Clear all IRQ flags */
dev->STAT = stat;
if (stat & LPUART_STAT_RDRF_MASK) {
/* RDRF flag will be cleared when LPUART_DATA is read */
uint8_t data = dev->DATA;
if (stat & (LPUART_STAT_FE_MASK | LPUART_STAT_PF_MASK)) {
if (stat & LPUART_STAT_FE_MASK) {
DEBUG("LPUART framing error %08" PRIx32 "\n", stat);
}
if (stat & LPUART_STAT_PF_MASK) {
DEBUG("LPUART parity error %08" PRIx32 "\n", stat);
}
/* FE is set whenever the next character to be read from LPUART_DATA
* was received with logic 0 detected where a stop bit was expected. */
/* PF is set whenever the next character to be read from LPUART_DATA
* was received when parity is enabled (PE = 1) and the parity bit in
* the received character does not agree with the expected parity value. */
}
/* Only run callback if no error occurred */
else if (config[uart].rx_cb != NULL) {
config[uart].rx_cb(config[uart].arg, data);
}
}
if (stat & LPUART_STAT_OR_MASK) {
/* Input buffer overflow, means that the software was too slow to
* receive the data */
DEBUG("LPUART overrun %08" PRIx32 "\n", stat);
}
cortexm_isr_end();
}
#endif
#ifdef LPUART_0_ISR
void LPUART_0_ISR(void)
{
irq_handler_lpuart(UART_DEV(0));
}
#endif
#ifdef LPUART_1_ISR
void LPUART_1_ISR(void)
{
irq_handler_lpuart(UART_DEV(1));
}
#endif
#ifdef LPUART_2_ISR
void LPUART_2_ISR(void)
{
irq_handler_lpuart(UART_DEV(2));
}
#endif
#ifdef LPUART_3_ISR
void LPUART_3_ISR(void)
{
irq_handler_lpuart(UART_DEV(3));
}
#endif
#ifdef LPUART_4_ISR
void LPUART_4_ISR(void)
{
irq_handler_lpuart(UART_DEV(4));
}
#endif
#endif /* KINETIS_HAVE_LPUART */