01e36532c2
This commit: * Moves all cpu files from cpu/cc26xx to cpu/cc26xx-cc13xx * Bumps the CC26xxware submodule to the latest TI release * Adds CC13xxware as a submodule * Adds support for sub-ghz mode / IEEE 802.15.4g * Splits the driver into multiple files for clarity. We now have the following structure: * A common module that handles access to the RF core, interrupts etc * A module that takes care of BLE functionality * A netstack radio driver for IEEE mode (2.4GHz) * A netstack radio driver for PROP mode (sub-ghz - multiple bands) This commit also adds tick suppression functionality, applicable to all chips of the CC26xx and CC13xx families. Instead waking up on every clock tick simply to increment our software counter, we now only wake up just in time to service the next scheduled etimer. ContikiMAC-triggered wakeups are unaffected. Laslty, this commit also applies a number of minor changes: * Addition of missing includes * Removal of stub functions * Removal of a woraround for a CC26xxware bug that has now been fixed
397 lines
12 KiB
C
397 lines
12 KiB
C
/*
|
|
* Copyright (c) 2014, 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 cc26xx-uart
|
|
* @{
|
|
*
|
|
* \file
|
|
* Implementation of the CC13xx/CC26xx UART driver.
|
|
*/
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "contiki-conf.h"
|
|
#include "cc26xx-uart.h"
|
|
#include "hw_types.h"
|
|
#include "hw_memmap.h"
|
|
#include "sys_ctrl.h"
|
|
#include "prcm.h"
|
|
#include "ioc.h"
|
|
#include "uart.h"
|
|
#include "lpm.h"
|
|
#include "ti-lib.h"
|
|
#include "sys/energest.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Which events to trigger a UART interrupt */
|
|
#define CC26XX_UART_RX_INTERRUPT_TRIGGERS (UART_INT_RX | UART_INT_RT)
|
|
|
|
/* All interrupt masks */
|
|
#define CC26XX_UART_INTERRUPT_ALL (UART_INT_OE | UART_INT_BE | UART_INT_PE | \
|
|
UART_INT_FE | UART_INT_RT | UART_INT_TX | \
|
|
UART_INT_RX | UART_INT_CTS)
|
|
/*---------------------------------------------------------------------------*/
|
|
#define cc26xx_uart_isr UART0IntHandler
|
|
/*---------------------------------------------------------------------------*/
|
|
static int (*input_handler)(unsigned char c);
|
|
/*---------------------------------------------------------------------------*/
|
|
static bool
|
|
usable(void)
|
|
{
|
|
if(BOARD_IOID_UART_RX == IOID_UNUSED ||
|
|
BOARD_IOID_UART_TX == IOID_UNUSED ||
|
|
CC26XX_UART_CONF_ENABLE == 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
power_and_clock(void)
|
|
{
|
|
/* Power on the SERIAL PD */
|
|
ti_lib_prcm_power_domain_on(PRCM_DOMAIN_SERIAL);
|
|
while(ti_lib_prcm_power_domain_status(PRCM_DOMAIN_SERIAL)
|
|
!= PRCM_DOMAIN_POWER_ON);
|
|
|
|
/* Enable UART clock in active mode */
|
|
ti_lib_prcm_peripheral_run_enable(PRCM_PERIPH_UART0);
|
|
ti_lib_prcm_load_set();
|
|
while(!ti_lib_prcm_load_get());
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/*
|
|
* Returns 0 if either the SERIAL PD is off, or the PD is on but the run mode
|
|
* clock is gated. If this function would return 0, accessing UART registers
|
|
* will return a precise bus fault. If this function returns 1, it is safe to
|
|
* access UART registers.
|
|
*
|
|
* This function only checks the 'run mode' clock gate, since it can only ever
|
|
* be called with the MCU in run mode.
|
|
*/
|
|
static bool
|
|
accessible(void)
|
|
{
|
|
/* First, check the PD */
|
|
if(ti_lib_prcm_power_domain_status(PRCM_DOMAIN_SERIAL)
|
|
!= PRCM_DOMAIN_POWER_ON) {
|
|
return false;
|
|
}
|
|
|
|
/* Then check the 'run mode' clock gate */
|
|
if(!(HWREG(PRCM_BASE + PRCM_O_UARTCLKGR) & PRCM_UARTCLKGR_CLK_EN)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
disable_interrupts(void)
|
|
{
|
|
/* Acknowledge UART interrupts */
|
|
ti_lib_int_disable(INT_UART0);
|
|
|
|
/* Disable all UART module interrupts */
|
|
ti_lib_uart_int_disable(UART0_BASE, CC26XX_UART_INTERRUPT_ALL);
|
|
|
|
/* Clear all UART interrupts */
|
|
ti_lib_uart_int_clear(UART0_BASE, CC26XX_UART_INTERRUPT_ALL);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
enable_interrupts(void)
|
|
{
|
|
/* Clear all UART interrupts */
|
|
ti_lib_uart_int_clear(UART0_BASE, CC26XX_UART_INTERRUPT_ALL);
|
|
|
|
/* Enable RX-related interrupts only if we have an input handler */
|
|
if(input_handler) {
|
|
/* Configure which interrupts to generate: FIFO level or after RX timeout */
|
|
ti_lib_uart_int_enable(UART0_BASE, CC26XX_UART_RX_INTERRUPT_TRIGGERS);
|
|
|
|
/* Acknowledge UART interrupts */
|
|
ti_lib_int_enable(INT_UART0);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
configure(void)
|
|
{
|
|
uint32_t ctl_val = UART_CTL_UARTEN | UART_CTL_TXE;
|
|
/*
|
|
* Make sure the TX pin is output / high before assigning it to UART control
|
|
* to avoid falling edge glitches
|
|
*/
|
|
ti_lib_ioc_pin_type_gpio_output(BOARD_IOID_UART_TX);
|
|
ti_lib_gpio_pin_write(BOARD_UART_TX, 1);
|
|
|
|
/*
|
|
* Map UART signals to the correct GPIO pins and configure them as
|
|
* hardware controlled.
|
|
*/
|
|
ti_lib_ioc_pin_type_uart(UART0_BASE, BOARD_IOID_UART_RX, BOARD_IOID_UART_TX,
|
|
BOARD_IOID_UART_CTS, BOARD_IOID_UART_RTS);
|
|
|
|
/* Configure the UART for 115,200, 8-N-1 operation. */
|
|
ti_lib_uart_config_set_exp_clk(UART0_BASE, ti_lib_sys_ctrl_clock_get(),
|
|
CC26XX_UART_CONF_BAUD_RATE,
|
|
(UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE |
|
|
UART_CONFIG_PAR_NONE));
|
|
|
|
/*
|
|
* Generate an RX interrupt at FIFO 1/2 full.
|
|
* We don't really care about the TX interrupt
|
|
*/
|
|
ti_lib_uart_fifo_level_set(UART0_BASE, UART_FIFO_TX7_8, UART_FIFO_RX4_8);
|
|
|
|
/* Enable FIFOs */
|
|
HWREG(UART0_BASE + UART_O_LCRH) |= UART_LCRH_FEN;
|
|
|
|
if(input_handler) {
|
|
ctl_val += UART_CTL_RXE;
|
|
}
|
|
|
|
/* Enable TX, RX (conditionally), and the UART. */
|
|
HWREG(UART0_BASE + UART_O_CTL) = ctl_val;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
lpm_drop_handler(uint8_t mode)
|
|
{
|
|
/*
|
|
* First, wait for any outstanding TX to complete. If we have an input
|
|
* handler, the SERIAL PD will be kept on and the UART module clock will
|
|
* be enabled under sleep as well as deep sleep. In theory, this means that
|
|
* we shouldn't lose any outgoing bytes, but we actually do on occasion.
|
|
* This byte loss may (or may not) be related to the freezing of IO latches
|
|
* between MCU and AON when we drop to deep sleep. This here is essentially a
|
|
* workaround
|
|
*/
|
|
if(accessible() == true) {
|
|
while(ti_lib_uart_busy(UART0_BASE));
|
|
}
|
|
|
|
/*
|
|
* If we have a registered input_handler then we need to retain RX
|
|
* capability. Thus, if this is not a shutdown notification and we have an
|
|
* input handler, we do nothing
|
|
*/
|
|
if((mode != LPM_MODE_SHUTDOWN) && (input_handler != NULL)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If we reach here, we either don't care about staying awake or we have
|
|
* received a shutdown notification
|
|
*
|
|
* Only touch UART registers if the module is powered and clocked
|
|
*/
|
|
if(accessible() == true) {
|
|
/* Disable the module */
|
|
ti_lib_uart_disable(UART0_BASE);
|
|
|
|
/* Disable all UART interrupts and clear all flags */
|
|
disable_interrupts();
|
|
}
|
|
|
|
/*
|
|
* Always stop the clock in run mode. Also stop in Sleep and Deep Sleep if
|
|
* this is a request for full shutdown
|
|
*/
|
|
ti_lib_prcm_peripheral_run_disable(PRCM_PERIPH_UART0);
|
|
if(mode == LPM_MODE_SHUTDOWN) {
|
|
ti_lib_prcm_peripheral_sleep_disable(PRCM_PERIPH_UART0);
|
|
ti_lib_prcm_peripheral_deep_sleep_disable(PRCM_PERIPH_UART0);
|
|
}
|
|
ti_lib_prcm_load_set();
|
|
while(!ti_lib_prcm_load_get());
|
|
|
|
/* Set pins to low leakage configuration in preparation for deep sleep */
|
|
lpm_pin_set_default_state(BOARD_IOID_UART_TX);
|
|
lpm_pin_set_default_state(BOARD_IOID_UART_RX);
|
|
lpm_pin_set_default_state(BOARD_IOID_UART_CTS);
|
|
lpm_pin_set_default_state(BOARD_IOID_UART_RTS);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Declare a data structure to register with LPM. */
|
|
LPM_MODULE(uart_module, NULL, lpm_drop_handler, NULL, LPM_DOMAIN_NONE);
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
enable(void)
|
|
{
|
|
power_and_clock();
|
|
|
|
/* Make sure the peripheral is disabled */
|
|
ti_lib_uart_disable(UART0_BASE);
|
|
|
|
/* Disable all UART interrupts and clear all flags */
|
|
disable_interrupts();
|
|
|
|
/* Setup pins, Baud rate and FIFO levels */
|
|
configure();
|
|
|
|
/* Enable UART interrupts */
|
|
enable_interrupts();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
cc26xx_uart_init()
|
|
{
|
|
bool interrupts_disabled;
|
|
|
|
/* Return early if disabled by user conf or if ports are misconfigured */
|
|
if(usable() == false) {
|
|
return;
|
|
}
|
|
|
|
/* Disable Interrupts */
|
|
interrupts_disabled = ti_lib_int_master_disable();
|
|
|
|
/* Register ourselves with the LPM module */
|
|
lpm_register_module(&uart_module);
|
|
|
|
/* Only TX and EN to start with. RX will be enabled only if needed */
|
|
input_handler = NULL;
|
|
|
|
/*
|
|
* init() won't actually fire up the UART. We turn it on only when (and if)
|
|
* it gets requested, either to enable input or to send out a character
|
|
*
|
|
* Thus, we simply re-enable processor interrupts here
|
|
*/
|
|
if(!interrupts_disabled) {
|
|
ti_lib_int_master_enable();
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
cc26xx_uart_write_byte(uint8_t c)
|
|
{
|
|
/* Return early if disabled by user conf or if ports are misconfigured */
|
|
if(usable() == false) {
|
|
return;
|
|
}
|
|
|
|
if(accessible() == false) {
|
|
enable();
|
|
}
|
|
|
|
ti_lib_uart_char_put(UART0_BASE, c);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
cc26xx_uart_set_input(int (*input)(unsigned char c))
|
|
{
|
|
input_handler = input;
|
|
|
|
/* Return early if disabled by user conf or if ports are misconfigured */
|
|
if(usable() == false) {
|
|
return;
|
|
}
|
|
|
|
if(input == NULL) {
|
|
/* Let the SERIAL PD power down */
|
|
uart_module.domain_lock = LPM_DOMAIN_NONE;
|
|
|
|
/* Disable module clocks under sleep and deep sleep */
|
|
ti_lib_prcm_peripheral_sleep_disable(PRCM_PERIPH_UART0);
|
|
ti_lib_prcm_peripheral_deep_sleep_disable(PRCM_PERIPH_UART0);
|
|
} else {
|
|
/* Request the SERIAL PD to stay on during deep sleep */
|
|
uart_module.domain_lock = LPM_DOMAIN_SERIAL;
|
|
|
|
/* Enable module clocks under sleep and deep sleep */
|
|
ti_lib_prcm_peripheral_sleep_enable(PRCM_PERIPH_UART0);
|
|
ti_lib_prcm_peripheral_deep_sleep_enable(PRCM_PERIPH_UART0);
|
|
}
|
|
|
|
ti_lib_prcm_load_set();
|
|
while(!ti_lib_prcm_load_get());
|
|
|
|
enable();
|
|
|
|
return;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
cc26xx_uart_busy(void)
|
|
{
|
|
/* Return early if disabled by user conf or if ports are misconfigured */
|
|
if(usable() == false) {
|
|
return UART_IDLE;
|
|
}
|
|
|
|
/* If the UART is not accessible, it is not busy */
|
|
if(accessible() == false) {
|
|
return UART_IDLE;
|
|
}
|
|
|
|
return ti_lib_uart_busy(UART0_BASE);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
cc26xx_uart_isr(void)
|
|
{
|
|
char the_char;
|
|
uint32_t flags;
|
|
|
|
ENERGEST_ON(ENERGEST_TYPE_IRQ);
|
|
|
|
power_and_clock();
|
|
|
|
/* Read out the masked interrupt status */
|
|
flags = ti_lib_uart_int_status(UART0_BASE, true);
|
|
|
|
/* Clear all UART interrupt flags */
|
|
ti_lib_uart_int_clear(UART0_BASE, CC26XX_UART_INTERRUPT_ALL);
|
|
|
|
if((flags & CC26XX_UART_RX_INTERRUPT_TRIGGERS) != 0) {
|
|
/*
|
|
* If this was a FIFO RX or an RX timeout, read all bytes available in the
|
|
* RX FIFO.
|
|
*/
|
|
while(ti_lib_uart_chars_avail(UART0_BASE)) {
|
|
the_char = ti_lib_uart_char_get_non_blocking(UART0_BASE);
|
|
|
|
if(input_handler != NULL) {
|
|
input_handler((unsigned char)the_char);
|
|
}
|
|
}
|
|
}
|
|
|
|
ENERGEST_OFF(ENERGEST_TYPE_IRQ);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/** @} */
|