19fd7a3551
OR-ing an offset to a base address instead of adding it is dangerous because it can only work if the base address is aligned enough for the offset. Moreover, if the base address or the offset has a value unknown at compile time, then the assembly instructions dedicated to 'base + offset' addressing on most CPUs can't be emitted by the compiler because this would require the alignment of the base address against the offset to be known in order to optimize 'base | offset' into 'base + offset'. In that case, the compiler has to emit more instructions in order to compute 'base | offset' on most CPUs, e.g. on ARM, which means larger binary size and slower execution. Hence, replace all occurrences of 'base | offset' with 'base + offset'. This must become a coding rule. Here are the results for the cc2538-demo example: - Compilation of uart_init(): * before: REG(regs->base | UART_CC) = 0; 200b78: f446 637c orr.w r3, r6, #4032 ; 0xfc0 200b7c: f043 0308 orr.w r3, r3, #8 200b80: 2200 movs r2, #0 200b82: 601a str r2, [r3, #0] * now: REG(regs->base + UART_CC) = 0; 200b7a: 2300 movs r3, #0 200b7c: f8c4 3fc8 str.w r3, [r4, #4040] ; 0xfc8 - Size of the .text section: * before: 0x4c7c * now: 0x4c28 * saved: 84 bytes Signed-off-by: Benoît Thébaudeau <benoit.thebaudeau.dev@gmail.com>
400 lines
13 KiB
C
400 lines
13 KiB
C
/*
|
|
* Copyright (c) 2012, Texas Instruments Incorporated - http://www.ti.com/
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/**
|
|
* \addtogroup cc2538-uart
|
|
* @{
|
|
*
|
|
* \file
|
|
* Implementation of the cc2538 UART driver
|
|
*/
|
|
#include "contiki.h"
|
|
#include "sys/energest.h"
|
|
#include "dev/sys-ctrl.h"
|
|
#include "dev/ioc.h"
|
|
#include "dev/gpio.h"
|
|
#include "dev/uart.h"
|
|
#include "lpm.h"
|
|
#include "reg.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#ifndef UART0_RX_PORT
|
|
#define UART0_RX_PORT (-1)
|
|
#endif
|
|
#ifndef UART0_RX_PIN
|
|
#define UART0_RX_PIN (-1)
|
|
#endif
|
|
#if UART0_RX_PORT >= 0 && UART0_RX_PIN < 0 || \
|
|
UART0_RX_PORT < 0 && UART0_RX_PIN >= 0
|
|
#error Both UART0_RX_PORT and UART0_RX_PIN must be valid or invalid
|
|
#endif
|
|
|
|
#ifndef UART0_TX_PORT
|
|
#define UART0_TX_PORT (-1)
|
|
#endif
|
|
#ifndef UART0_TX_PIN
|
|
#define UART0_TX_PIN (-1)
|
|
#endif
|
|
#if UART0_TX_PORT >= 0 && UART0_TX_PIN < 0 || \
|
|
UART0_TX_PORT < 0 && UART0_TX_PIN >= 0
|
|
#error Both UART0_TX_PORT and UART0_TX_PIN must be valid or invalid
|
|
#endif
|
|
|
|
#if UART0_RX_PORT >= 0 && UART0_TX_PORT < 0 || \
|
|
UART0_RX_PORT < 0 && UART0_TX_PORT >= 0
|
|
#error Both UART0_RX and UART0_TX pads must be valid or invalid
|
|
#endif
|
|
|
|
#if UART_IN_USE(0) && UART0_RX_PORT < 0
|
|
#error Contiki is configured to use UART0, but its pads are not valid
|
|
#endif
|
|
|
|
#ifndef UART1_RX_PORT
|
|
#define UART1_RX_PORT (-1)
|
|
#endif
|
|
#ifndef UART1_RX_PIN
|
|
#define UART1_RX_PIN (-1)
|
|
#endif
|
|
#if UART1_RX_PORT >= 0 && UART1_RX_PIN < 0 || \
|
|
UART1_RX_PORT < 0 && UART1_RX_PIN >= 0
|
|
#error Both UART1_RX_PORT and UART1_RX_PIN must be valid or invalid
|
|
#endif
|
|
|
|
#ifndef UART1_TX_PORT
|
|
#define UART1_TX_PORT (-1)
|
|
#endif
|
|
#ifndef UART1_TX_PIN
|
|
#define UART1_TX_PIN (-1)
|
|
#endif
|
|
#if UART1_TX_PORT >= 0 && UART1_TX_PIN < 0 || \
|
|
UART1_TX_PORT < 0 && UART1_TX_PIN >= 0
|
|
#error Both UART1_TX_PORT and UART1_TX_PIN must be valid or invalid
|
|
#endif
|
|
|
|
#if UART1_RX_PORT >= 0 && UART1_TX_PORT < 0 || \
|
|
UART1_RX_PORT < 0 && UART1_TX_PORT >= 0
|
|
#error Both UART1_RX and UART1_TX pads must be valid or invalid
|
|
#endif
|
|
|
|
#if UART_IN_USE(1) && UART1_RX_PORT < 0
|
|
#error Contiki is configured to use UART1, but its pads are not valid
|
|
#endif
|
|
|
|
#ifndef UART1_CTS_PORT
|
|
#define UART1_CTS_PORT (-1)
|
|
#endif
|
|
#ifndef UART1_CTS_PIN
|
|
#define UART1_CTS_PIN (-1)
|
|
#endif
|
|
#if UART1_CTS_PORT >= 0 && UART1_CTS_PIN < 0 || \
|
|
UART1_CTS_PORT < 0 && UART1_CTS_PIN >= 0
|
|
#error Both UART1_CTS_PORT and UART1_CTS_PIN must be valid or invalid
|
|
#endif
|
|
|
|
#ifndef UART1_RTS_PORT
|
|
#define UART1_RTS_PORT (-1)
|
|
#endif
|
|
#ifndef UART1_RTS_PIN
|
|
#define UART1_RTS_PIN (-1)
|
|
#endif
|
|
#if UART1_RTS_PORT >= 0 && UART1_RTS_PIN < 0 || \
|
|
UART1_RTS_PORT < 0 && UART1_RTS_PIN >= 0
|
|
#error Both UART1_RTS_PORT and UART1_RTS_PIN must be valid or invalid
|
|
#endif
|
|
/*---------------------------------------------------------------------------*/
|
|
/*
|
|
* Baud rate defines used in uart_init() to set the values of UART_IBRD and
|
|
* UART_FBRD in order to achieve the configured baud rates.
|
|
*/
|
|
#define UART_CLOCK_RATE 16000000 /* 16 MHz */
|
|
#define UART_CTL_HSE_VALUE 0
|
|
#define UART_CTL_VALUE (UART_CTL_RXE | UART_CTL_TXE | (UART_CTL_HSE_VALUE << 5))
|
|
|
|
/* DIV_ROUND() divides integers while avoiding a rounding error: */
|
|
#define DIV_ROUND(num, denom) (((num) + (denom) / 2) / (denom))
|
|
|
|
#define BAUD2BRD(baud) DIV_ROUND(UART_CLOCK_RATE << (UART_CTL_HSE_VALUE + 2), (baud))
|
|
#define BAUD2IBRD(baud) (BAUD2BRD(baud) >> 6)
|
|
#define BAUD2FBRD(baud) (BAUD2BRD(baud) & 0x3f)
|
|
/*---------------------------------------------------------------------------*/
|
|
typedef struct {
|
|
int8_t port;
|
|
int8_t pin;
|
|
} uart_pad_t;
|
|
typedef struct {
|
|
uint32_t sys_ctrl_rcgcuart_uart;
|
|
uint32_t sys_ctrl_scgcuart_uart;
|
|
uint32_t sys_ctrl_dcgcuart_uart;
|
|
uint32_t base;
|
|
uint32_t ioc_uartrxd_uart;
|
|
uint32_t ioc_pxx_sel_uart_txd;
|
|
uint32_t ibrd;
|
|
uint32_t fbrd;
|
|
uart_pad_t rx;
|
|
uart_pad_t tx;
|
|
uart_pad_t cts;
|
|
uart_pad_t rts;
|
|
uint8_t nvic_int;
|
|
} uart_regs_t;
|
|
/*---------------------------------------------------------------------------*/
|
|
static const uart_regs_t uart_regs[UART_INSTANCE_COUNT] = {
|
|
{
|
|
.sys_ctrl_rcgcuart_uart = SYS_CTRL_RCGCUART_UART0,
|
|
.sys_ctrl_scgcuart_uart = SYS_CTRL_SCGCUART_UART0,
|
|
.sys_ctrl_dcgcuart_uart = SYS_CTRL_DCGCUART_UART0,
|
|
.base = UART_0_BASE,
|
|
.ioc_uartrxd_uart = IOC_UARTRXD_UART0,
|
|
.ioc_pxx_sel_uart_txd = IOC_PXX_SEL_UART0_TXD,
|
|
.ibrd = BAUD2IBRD(UART0_CONF_BAUD_RATE),
|
|
.fbrd = BAUD2FBRD(UART0_CONF_BAUD_RATE),
|
|
.rx = {UART0_RX_PORT, UART0_RX_PIN},
|
|
.tx = {UART0_TX_PORT, UART0_TX_PIN},
|
|
.cts = {-1, -1},
|
|
.rts = {-1, -1},
|
|
.nvic_int = NVIC_INT_UART0
|
|
}, {
|
|
.sys_ctrl_rcgcuart_uart = SYS_CTRL_RCGCUART_UART1,
|
|
.sys_ctrl_scgcuart_uart = SYS_CTRL_SCGCUART_UART1,
|
|
.sys_ctrl_dcgcuart_uart = SYS_CTRL_DCGCUART_UART1,
|
|
.base = UART_1_BASE,
|
|
.ioc_uartrxd_uart = IOC_UARTRXD_UART1,
|
|
.ioc_pxx_sel_uart_txd = IOC_PXX_SEL_UART1_TXD,
|
|
.ibrd = BAUD2IBRD(UART1_CONF_BAUD_RATE),
|
|
.fbrd = BAUD2FBRD(UART1_CONF_BAUD_RATE),
|
|
.rx = {UART1_RX_PORT, UART1_RX_PIN},
|
|
.tx = {UART1_TX_PORT, UART1_TX_PIN},
|
|
.cts = {UART1_CTS_PORT, UART1_CTS_PIN},
|
|
.rts = {UART1_RTS_PORT, UART1_RTS_PIN},
|
|
.nvic_int = NVIC_INT_UART1
|
|
}
|
|
};
|
|
static int (* input_handler[UART_INSTANCE_COUNT])(unsigned char c);
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
reset(uint32_t uart_base)
|
|
{
|
|
uint32_t lchr;
|
|
|
|
/* Make sure the UART is disabled before trying to configure it */
|
|
REG(uart_base + UART_CTL) = UART_CTL_VALUE;
|
|
|
|
/* Clear error status */
|
|
REG(uart_base + UART_ECR) = 0xFF;
|
|
|
|
/* Store LCHR configuration */
|
|
lchr = REG(uart_base + UART_LCRH);
|
|
|
|
/* Flush FIFOs by clearing LCHR.FEN */
|
|
REG(uart_base + UART_LCRH) = 0;
|
|
|
|
/* Restore LCHR configuration */
|
|
REG(uart_base + UART_LCRH) = lchr;
|
|
|
|
/* UART Enable */
|
|
REG(uart_base + UART_CTL) |= UART_CTL_UARTEN;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static bool
|
|
permit_pm1(void)
|
|
{
|
|
const uart_regs_t *regs;
|
|
|
|
for(regs = &uart_regs[0]; regs < &uart_regs[UART_INSTANCE_COUNT]; regs++) {
|
|
/* Note: UART_FR.TXFE reads 0 if the UART clock is gated. */
|
|
if((REG(SYS_CTRL_RCGCUART) & regs->sys_ctrl_rcgcuart_uart) != 0 &&
|
|
(REG(regs->base + UART_FR) & UART_FR_TXFE) == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
uart_init(uint8_t uart)
|
|
{
|
|
const uart_regs_t *regs;
|
|
|
|
if(uart >= UART_INSTANCE_COUNT) {
|
|
return;
|
|
}
|
|
regs = &uart_regs[uart];
|
|
if(regs->rx.port < 0 || regs->tx.port < 0) {
|
|
return;
|
|
}
|
|
|
|
lpm_register_peripheral(permit_pm1);
|
|
|
|
/* Enable clock for the UART while Running, in Sleep and Deep Sleep */
|
|
REG(SYS_CTRL_RCGCUART) |= regs->sys_ctrl_rcgcuart_uart;
|
|
REG(SYS_CTRL_SCGCUART) |= regs->sys_ctrl_scgcuart_uart;
|
|
REG(SYS_CTRL_DCGCUART) |= regs->sys_ctrl_dcgcuart_uart;
|
|
|
|
/* Run on SYS_DIV */
|
|
REG(regs->base + UART_CC) = 0;
|
|
|
|
/*
|
|
* Select the UARTx RX pin by writing to the IOC_UARTRXD_UARTn register
|
|
*
|
|
* The value to be written will be on of the IOC_INPUT_SEL_Pxn defines from
|
|
* ioc.h. The value can also be calculated as:
|
|
*
|
|
* (port << 3) + pin
|
|
*/
|
|
REG(regs->ioc_uartrxd_uart) = (regs->rx.port << 3) + regs->rx.pin;
|
|
|
|
/*
|
|
* Pad Control for the TX pin:
|
|
* - Set function to UARTn TX
|
|
* - Output Enable
|
|
*/
|
|
ioc_set_sel(regs->tx.port, regs->tx.pin, regs->ioc_pxx_sel_uart_txd);
|
|
ioc_set_over(regs->tx.port, regs->tx.pin, IOC_OVERRIDE_OE);
|
|
|
|
/* Set RX and TX pins to peripheral mode */
|
|
GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(regs->tx.port),
|
|
GPIO_PIN_MASK(regs->tx.pin));
|
|
GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(regs->rx.port),
|
|
GPIO_PIN_MASK(regs->rx.pin));
|
|
|
|
/*
|
|
* UART Interrupt Masks:
|
|
* Acknowledge RX and RX Timeout
|
|
* Acknowledge Framing, Overrun and Break Errors
|
|
*/
|
|
REG(regs->base + UART_IM) = UART_IM_RXIM | UART_IM_RTIM;
|
|
REG(regs->base + UART_IM) |= UART_IM_OEIM | UART_IM_BEIM | UART_IM_FEIM;
|
|
|
|
REG(regs->base + UART_IFLS) =
|
|
UART_IFLS_RXIFLSEL_1_8 | UART_IFLS_TXIFLSEL_1_2;
|
|
|
|
/* Make sure the UART is disabled before trying to configure it */
|
|
REG(regs->base + UART_CTL) = UART_CTL_VALUE;
|
|
|
|
/* Baud Rate Generation */
|
|
REG(regs->base + UART_IBRD) = regs->ibrd;
|
|
REG(regs->base + UART_FBRD) = regs->fbrd;
|
|
|
|
/* UART Control: 8N1 with FIFOs */
|
|
REG(regs->base + UART_LCRH) = UART_LCRH_WLEN_8 | UART_LCRH_FEN;
|
|
|
|
/*
|
|
* Enable hardware flow control (RTS/CTS) if requested.
|
|
* Note that hardware flow control is available only on UART1.
|
|
*/
|
|
if(regs->cts.port >= 0) {
|
|
REG(IOC_UARTCTS_UART1) = ioc_input_sel(regs->cts.port, regs->cts.pin);
|
|
GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(regs->cts.port), GPIO_PIN_MASK(regs->cts.pin));
|
|
ioc_set_over(regs->cts.port, regs->cts.pin, IOC_OVERRIDE_DIS);
|
|
REG(UART_1_BASE + UART_CTL) |= UART_CTL_CTSEN;
|
|
}
|
|
|
|
if(regs->rts.port >= 0) {
|
|
ioc_set_sel(regs->rts.port, regs->rts.pin, IOC_PXX_SEL_UART1_RTS);
|
|
GPIO_PERIPHERAL_CONTROL(GPIO_PORT_TO_BASE(regs->rts.port), GPIO_PIN_MASK(regs->rts.pin));
|
|
ioc_set_over(regs->rts.port, regs->rts.pin, IOC_OVERRIDE_OE);
|
|
REG(UART_1_BASE + UART_CTL) |= UART_CTL_RTSEN;
|
|
}
|
|
|
|
/* UART Enable */
|
|
REG(regs->base + UART_CTL) |= UART_CTL_UARTEN;
|
|
|
|
/* Enable UART0 Interrupts */
|
|
nvic_interrupt_enable(regs->nvic_int);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
uart_set_input(uint8_t uart, int (* input)(unsigned char c))
|
|
{
|
|
if(uart >= UART_INSTANCE_COUNT) {
|
|
return;
|
|
}
|
|
|
|
input_handler[uart] = input;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
uart_write_byte(uint8_t uart, uint8_t b)
|
|
{
|
|
uint32_t uart_base;
|
|
|
|
if(uart >= UART_INSTANCE_COUNT) {
|
|
return;
|
|
}
|
|
uart_base = uart_regs[uart].base;
|
|
|
|
/* Block if the TX FIFO is full */
|
|
while(REG(uart_base + UART_FR) & UART_FR_TXFF);
|
|
|
|
REG(uart_base + UART_DR) = b;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
uart_isr(uint8_t uart)
|
|
{
|
|
uint32_t uart_base;
|
|
uint16_t mis;
|
|
|
|
ENERGEST_ON(ENERGEST_TYPE_IRQ);
|
|
|
|
uart_base = uart_regs[uart].base;
|
|
|
|
/* Store the current MIS and clear all flags early, except the RTM flag.
|
|
* This will clear itself when we read out the entire FIFO contents */
|
|
mis = REG(uart_base + UART_MIS) & 0x0000FFFF;
|
|
|
|
REG(uart_base + UART_ICR) = 0x0000FFBF;
|
|
|
|
if(mis & (UART_MIS_RXMIS | UART_MIS_RTMIS)) {
|
|
while(!(REG(uart_base + UART_FR) & UART_FR_RXFE)) {
|
|
if(input_handler[uart] != NULL) {
|
|
input_handler[uart]((unsigned char)(REG(uart_base + UART_DR) & 0xFF));
|
|
} else {
|
|
/* To prevent an Overrun Error, we need to flush the FIFO even if we
|
|
* don't have an input_handler. Use mis as a data trash can */
|
|
mis = REG(uart_base + UART_DR);
|
|
}
|
|
}
|
|
} else if(mis & (UART_MIS_OEMIS | UART_MIS_BEMIS | UART_MIS_FEMIS)) {
|
|
/* ISR triggered due to some error condition */
|
|
reset(uart_base);
|
|
}
|
|
|
|
ENERGEST_OFF(ENERGEST_TYPE_IRQ);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#define UART_ISR(u) void uart##u##_isr(void) { uart_isr(u); }
|
|
UART_ISR(0)
|
|
UART_ISR(1)
|
|
|
|
/** @} */
|