contiki/platform/jn516x/dev/uart-driver.c

613 lines
20 KiB
C

/*
* Copyright (c) 2015 NXP B.V.
* 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 NXP B.V. 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 NXP B.V. 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 NXP B.V. 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.
*
* This file is part of the Contiki operating system.
*
* Author: Lee Mitchell
* Integrated into Contiki by Beshr Al Nahas
*
*/
#include <jendefs.h>
#ifdef DEBUG
#include <dbg.h>
#else
#define DBG_vPrintf(...)
#endif
#include "contiki-conf.h"
#include "uart-driver.h"
#include "sys/rtimer.h"
#include "watchdog.h"
#include <math.h>
#include <AppHardwareApi.h>
#if UART_XONXOFF_FLOW_CTRL
#include "sys/process.h"
#define TX_FIFO_SW_FLOW_LIMIT 8 /* Maximum allowed fill level for tx fifo */
#if TX_FIFO_SW_FLOW_LIMIT > 16
#undef TX_FIFO_SW_FLOW_LIMIT
#define TX_FIFO_SW_FLOW_LIMIT 16
#warning "TX_FIFO_SW_FLOW_LIMIT too big. Forced to 16."
#endif /* TX_FIFO_SW_FLOW_LIMIT > 16 */
#define XON 17
#define XOFF 19
extern volatile unsigned char xonxoff_state;
#endif /* UART_XONXOFF_FLOW_CTRL */
/*** Macro Definitions ***/
#define BUSYWAIT_UNTIL(cond, max_time) \
do { \
rtimer_clock_t t0; \
t0 = RTIMER_NOW(); \
while(!(cond) && RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + (max_time))) ; \
} while(0)
#define DEBUG_UART_BUFFERED FALSE
#define CHAR_DEADLINE (uart_char_delay * 100)
/*** Local Function Prototypes ***/
static void uart_driver_isr(uint32_t device_id, uint32_t item_bitmap);
#if !UART_XONXOFF_FLOW_CTRL
static int16_t uart_driver_get_tx_fifo_available_space(uint8_t uart_dev);
#endif /* !UART_XONXOFF_FLOW_CTRL */
static void uart_driver_set_baudrate(uint8_t uart_dev, uint8_t br);
static void uart_driver_set_high_baudrate(uint8_t uart_dev, uint32_t baud_rate);
/*** Local Variables ***/
#define UART_NUM_UARTS 2
static uint16_t tx_fifo_size[UART_NUM_UARTS] = { 0 };
static uint8_t active_uarts[UART_NUM_UARTS] = { 0 };
/** slip input function pointer */
static int(*uart_input[UART_NUM_UARTS]) (unsigned char) = { 0 };
/* time in uSec for transmitting 1 char */
static uint16_t uart_char_delay = 0;
static volatile int8_t interrupt_enabled[UART_NUM_UARTS] = { 0 };
static volatile int8_t interrupt_enabled_saved[UART_NUM_UARTS] = { 0 };
/****************************************************************************
*
* NAME: uart_driver_init
*
* DESCRIPTION:
* Initializes the specified UART device.
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to initialise, eg, E_AHI_UART_0
* br R Baudrate to use (e.g. UART_RATE_115200)
* if br > UART_RATE_115200
* then uart_driver_set_baud_rate is called
* else vAHI_UartSetClockDivisor
* txbuf_data R Pointer to a memory block to use
* and rxbuf_data as uart tx/rx fifo
* txbuf_size R size of tx fifo (valid range: 16-2047)
* txbuf_size R size of rx fifo (valid range: 16-2047)
* uart_input_function a function pointer to input uart rx bytes
* RETURNS:
* void
*
****************************************************************************/
void
uart_driver_init(uint8_t uart_dev, uint8_t br, uint8_t *txbuf_data,
uint16_t txbuf_size, uint8_t *rxbuf_data, uint16_t rxbuf_size,
int (*uart_input_function)(unsigned char c))
{
#if !UART_HW_FLOW_CTRL
/* Disable RTS/CTS */
vAHI_UartSetRTSCTS(uart_dev, FALSE);
#endif
tx_fifo_size[uart_dev] = txbuf_size;
/* Configure the selected Uart */
uint8_t uart_enabled = bAHI_UartEnable(uart_dev, txbuf_data, txbuf_size,
rxbuf_data, rxbuf_size);
/* fallback to internal buffers */
if(!uart_enabled) {
vAHI_UartEnable(uart_dev);
tx_fifo_size[uart_dev] = 16; /* Fixed size */
}
/* Reset tx/rx fifos */
vAHI_UartReset(uart_dev, TRUE, TRUE);
vAHI_UartReset(uart_dev, FALSE, FALSE);
uart_driver_set_baudrate(uart_dev, br);
/* install interrupt service callback */
if(uart_dev == E_AHI_UART_0) {
vAHI_Uart0RegisterCallback((void *)uart_driver_isr);
} else {
vAHI_Uart1RegisterCallback((void *)uart_driver_isr);
/* Enable RX interrupt */
}
uart_driver_enable_interrupts(uart_dev);
uart_input[uart_dev] = uart_input_function;
active_uarts[uart_dev] = 1;
#if UART_HW_FLOW_CTRL
/* Configure HW flow control */
vAHI_UartSetAutoFlowCtrl(uart_dev, E_AHI_UART_FIFO_ARTS_LEVEL_13, /* uint8 const u8RxFifoLevel,*/
FALSE, /* bool_t const bFlowCtrlPolarity,*/
TRUE, /* bool_t const bAutoRts, */
TRUE /* bool_t const bAutoCts */);
#endif
DBG_vPrintf("UART %d init: using %s buffers %d\n", uart_dev,
uart_enabled ? "external" : "internal", tx_fifo_size[uart_dev]);
}
void
uart_driver_enable_interrupts(uint8_t uart_dev)
{
/* wait while char being tx is done */
while((u8AHI_UartReadLineStatus(uart_dev) & E_AHI_UART_LS_THRE) == 0) ;
vAHI_UartSetInterrupt(uart_dev, FALSE /*bEnableModemStatus*/,
FALSE /*bEnableRxLineStatus == Break condition */,
FALSE /*bEnableTxFifoEmpty*/,
TRUE /* bEnableRxData */, E_AHI_UART_FIFO_LEVEL_14);
interrupt_enabled[uart_dev] = 1;
}
void
uart_driver_disable_interrupts(uint8_t uart_dev)
{
/* wait while char being tx is done */
while((u8AHI_UartReadLineStatus(uart_dev) & E_AHI_UART_LS_THRE) == 0) ;
vAHI_UartSetInterrupt(uart_dev, FALSE /*bEnableModemStatus*/,
FALSE /*bEnableRxLineStatus == Break condition */,
FALSE /*bEnableTxFifoEmpty*/,
FALSE /* bEnableRxData */, E_AHI_UART_FIFO_LEVEL_14);
interrupt_enabled[uart_dev] = 0;
}
void
uart_driver_store_interrupts(uint8_t uart_dev)
{
interrupt_enabled_saved[uart_dev] = interrupt_enabled[uart_dev];
}
void
uart_driver_restore_interrupts(uint8_t uart_dev)
{
if(interrupt_enabled_saved[uart_dev]) {
uart_driver_enable_interrupts(uart_dev);
} else {
uart_driver_disable_interrupts(uart_dev);
}
}
int8_t
uart_driver_interrupt_is_enabled(uint8_t uart_dev)
{
return interrupt_enabled[uart_dev];
}
void
uart_driver_set_input(uint8_t uart_dev, int
(*uart_input_function)(unsigned char c))
{
uart_input[uart_dev] = uart_input_function;
}
/****************************************************************************
*
* NAME: uart_driver_read
*
* DESCRIPTION:
* Reads 1 byte from the RX buffer. If there is no data in the
* buffer, then return FALSE
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to use, eg, E_AHI_UART_0
*
* RETURNS:
* TRUE if a byte has been read from the queue
*
****************************************************************************/
uint8_t
uart_driver_read(uint8_t uart_dev, uint8_t *data)
{
if(data && u16AHI_UartReadRxFifoLevel(uart_dev) > 0) {
*data = u8AHI_UartReadData(uart_dev);
return TRUE;
}
return FALSE;
}
void
uart_driver_write_buffered(uint8_t uart_dev, uint8_t ch)
{
uart_driver_write_with_deadline(uart_dev, ch);
}
/****************************************************************************
*
* NAME: uart_driver_write_with_deadline
*
* DESCRIPTION:
* Writes one byte to the specified uart for transmission
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to use, eg, E_AHI_UART_0
* ch R data to transmit
*
* RETURNS:
* void
*
****************************************************************************/
void
uart_driver_write_with_deadline(uint8_t uart_dev, uint8_t ch)
{
#if UART_XONXOFF_FLOW_CTRL
/* Block until host can receive data */
/* Wait until there are less than N characters in TX FIFO */
while(xonxoff_state != XON
|| u16AHI_UartReadTxFifoLevel(uart_dev) > TX_FIFO_SW_FLOW_LIMIT) {
watchdog_periodic();
}
/* write to TX FIFO and return immediately */
vAHI_UartWriteData(uart_dev, ch);
#else /* UART_XONXOFF_FLOW_CTRL */
volatile int16_t write = 0;
watchdog_periodic();
/* wait until there is space in tx fifo */
BUSYWAIT_UNTIL(write = (uart_driver_get_tx_fifo_available_space(uart_dev) > 0),
CHAR_DEADLINE);
/* write only if there is space so we do not get stuck */
if(write) {
/* write to TX FIFO and return immediately */
vAHI_UartWriteData(uart_dev, ch);
}
#endif /* UART_XONXOFF_FLOW_CTRL */
}
void
uart_driver_write_direct(uint8_t uart_dev, uint8_t ch)
{
/* Write character */
vAHI_UartWriteData(uart_dev, ch);
/* Wait for buffers to empty */
while((u8AHI_UartReadLineStatus(uart_dev) & E_AHI_UART_LS_THRE) == 0) ;
while((u8AHI_UartReadLineStatus(uart_dev) & E_AHI_UART_LS_TEMT) == 0) ;
}
/****************************************************************************
*
* NAME: uart_driver_rx_handler
*
* DESCRIPTION:
* Interrupt service callback for UART data reception. Reads a received
* byte from the UART and writes it to the reception buffer if it is not
* full.
*
* PARAMETERS: Name RW Usage
* uart_dev R Uart to read from
*
* RETURNS:
* void
*
****************************************************************************/
void
uart_driver_rx_handler(uint8_t uart_dev)
{
/* optimization for high throughput: Read upto 32 bytes from RX fifo.
* Disabled because it does not work with current slip_input_byte */
/* Status from uart_input:
* 0 means do not exit power saving mode
* -1 means RX buffer overflow ==> stop reading
* 1 means end of slip packet
*/
#if UART_XONXOFF_FLOW_CTRL
/* save old status */
int xonxoff_state_old = xonxoff_state;
#endif /* UART_XONXOFF_FLOW_CTRL */
int status = 0;
int c = 0;
while(u16AHI_UartReadRxFifoLevel(uart_dev) > 0 && c++ < 32 && status == 0) {
if(uart_input[uart_dev] != NULL) { /* read one char at a time */
/* process received character */
status = (uart_input[uart_dev])(u8AHI_UartReadData(uart_dev));
#if UART_XONXOFF_FLOW_CTRL
/* Process XON-XOFF*/
if(xonxoff_state == XOFF) {
/* XXX do not set break condition as it corrupts one character, instead we block on TX */
/* Instruct uart to stop TX */
/* vAHI_UartSetBreak(uart_dev, TRUE); */
break;
} else if(xonxoff_state_old == XOFF && xonxoff_state == XON) {
/* Instruct uart to resume TX if it was stopped */
/* vAHI_UartSetBreak(uart_dev, FALSE); */
}
#endif /* UART_XONXOFF_FLOW_CTRL */
} else {
/* no input handler, or no bytes to read: Discard byte. */
u8AHI_UartReadData(uart_dev);
}
}
}
/****************************************************************************/
/*** Local Functions ***/
/****************************************************************************/
#if !UART_XONXOFF_FLOW_CTRL
/* Returns the free space in tx fifo, i.e., how many characters we can put */
static int16_t
uart_driver_get_tx_fifo_available_space(uint8_t uart_dev)
{
return tx_fifo_size[uart_dev] - u16AHI_UartReadTxFifoLevel(uart_dev);
}
#endif /* !UART_XONXOFF_FLOW_CTRL */
/* Initializes the specified UART with auto-selection of
baudrate tuning method */
static void
uart_driver_set_baudrate(uint8_t uart_dev, uint8_t br)
{
uint32_t high_br = 0;
uint8_t low_br = 0;
switch(br) {
case UART_RATE_4800:
low_br = E_AHI_UART_RATE_4800;
uart_char_delay = 1667;
break;
case UART_RATE_9600:
low_br = E_AHI_UART_RATE_9600;
uart_char_delay = 834;
break;
case UART_RATE_19200:
low_br = E_AHI_UART_RATE_19200;
uart_char_delay = 417;
break;
case UART_RATE_38400:
low_br = E_AHI_UART_RATE_38400;
uart_char_delay = 209;
break;
case UART_RATE_76800:
low_br = E_AHI_UART_RATE_76800;
uart_char_delay = 105;
break;
case UART_RATE_115200:
low_br = E_AHI_UART_RATE_115200;
uart_char_delay = 69;
break;
case UART_RATE_230400:
high_br = 230400UL;
uart_char_delay = 35;
break;
case UART_RATE_460800:
high_br = 460800UL;
uart_char_delay = 18;
break;
case UART_RATE_500000:
high_br = 500000UL;
uart_char_delay = 16;
break;
case UART_RATE_576000:
high_br = 576000UL;
uart_char_delay = 14;
break;
case UART_RATE_921600:
high_br = 921600UL;
uart_char_delay = 9;
break;
case UART_RATE_1000000:
high_br = 1000000UL;
uart_char_delay = 8;
break;
default:
high_br = 1000000UL;
uart_char_delay = 8;
break;
}
if(high_br == 0) {
vAHI_UartSetClockDivisor(uart_dev, low_br);
} else {
uart_driver_set_high_baudrate(uart_dev, high_br);
}
}
/****************************************************************************
*
* NAME: uart_driver_set_high_baudrate
*
* DESCRIPTION:
* Sets the baud rate for the specified uart
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to initialise, eg, E_AHI_UART_0
* baud_rate R Baudrate to use (bps eg 921600)
*
* RETURNS:
* void
*
****************************************************************************/
static void
uart_driver_set_high_baudrate(uint8_t uart_dev, uint32_t baud_rate)
{
uint16 u16Divisor = 1;
uint32_t u32Remainder;
uint8_t u8ClocksPerBit = 16;
#if (ENABLE_ADVANCED_BAUD_SELECTION)
/* Defining ENABLE_ADVANCED_BAUD_SELECTION in the Makefile
* enables this code which searches for a clocks per bit setting
* that gets closest to the configured rate.
*/
uint32_t u32CalcBaudRate = 0;
int32 i32BaudError = 0x7FFFFFFF;
DBG_vPrintf(DEBUG_UART_BUFFERED, "Config uart=%d, baud=%d\n", uart_dev,
baud_rate);
while(ABS(i32BaudError) > (int32)(baud_rate >> 4)) { /* 6.25% (100/16) error */
if(--u8ClocksPerBit < 3) {
DBG_vPrintf(DEBUG_UART_BUFFERED,
"Could not calculate UART settings for target baud!");
return;
}
#endif /* ENABLE_ADVANCED_BAUD_SELECTION */
/* Calculate Divisor register = 16MHz / (16 x baud rate) */
u16Divisor = (uint16)(16000000UL / ((u8ClocksPerBit + 1) * baud_rate));
/* Correct for rounding errors */
u32Remainder =
(uint32_t)(16000000UL % ((u8ClocksPerBit + 1) * baud_rate));
if(u32Remainder >= (((u8ClocksPerBit + 1) * baud_rate) / 2)) {
u16Divisor += 1;
}
#if (ENABLE_ADVANCED_BAUD_SELECTION)
DBG_vPrintf(DEBUG_UART_BUFFERED, "Divisor=%d, cpb=%d\n", u16Divisor,
u8ClocksPerBit);
u32CalcBaudRate = (16000000UL / ((u8ClocksPerBit + 1) * u16Divisor));
DBG_vPrintf(DEBUG_UART_BUFFERED, "Calculated baud=%d\n", u32CalcBaudRate);
i32BaudError = (int32)u32CalcBaudRate - (int32)baud_rate;
DBG_vPrintf(DEBUG_UART_BUFFERED, "Error baud=%d\n", i32BaudError);
}
DBG_vPrintf(DEBUG_UART_BUFFERED, "Config uart=%d: Divisor=%d, cpb=%d\n",
uart_dev, u16Divisor, u8ClocksPerBit);
/* Set the calculated clocks per bit */
vAHI_UartSetClocksPerBit(uart_dev, u8ClocksPerBit);
#endif /* ENABLE_ADVANCED_BAUD_SELECTION */
/* Set the calculated divisor */
vAHI_UartSetBaudDivisor(uart_dev, u16Divisor);
}
/****************************************************************************
*
* NAME: uart_driver_isr
*
* DESCRIPTION:
* Interrupt service callback for UART's
*
* PARAMETERS: Name RW Usage
* device_id R Device ID of whatever generated the
* interrupt
* item_bitmap R Which part of the device generated
* the interrupt
*
* RETURNS:
* void
*
****************************************************************************/
static void
uart_driver_isr(uint32_t device_id, uint32_t item_bitmap)
{
uint8_t uart_dev;
switch(device_id) {
case E_AHI_DEVICE_UART0:
uart_dev = E_AHI_UART_0;
break;
case E_AHI_DEVICE_UART1:
uart_dev = E_AHI_UART_1;
break;
default:
return;
}
switch(item_bitmap) {
/* byte available since a long time but RX-fifo not full: */
case E_AHI_UART_INT_TIMEOUT:
/* RX-fifo full: */
case E_AHI_UART_INT_RXDATA:
uart_driver_rx_handler(uart_dev);
break;
case E_AHI_UART_INT_TX:
break;
case E_AHI_UART_INT_RXLINE:
/* rx-line interrupt is disabled. Should not get here */
/* An error condition has occurred on the RxD line, such as
a break indication, framing error, parity error or over-run. */
break;
}
}
/****************************************************************************
*
* NAME: uart_driver_tx_in_progress
*
* DESCRIPTION:
* Returns the state of data transmission
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to use, eg, E_AHI_UART_0
*
* RETURNS:
* uint8_t: TRUE if data in buffer is being transmitted
* FALSE if all data in buffer has been transmitted by the UART
*
****************************************************************************/
uint8_t
uart_driver_tx_in_progress(uint8_t uart_dev)
{
if(u16AHI_UartReadTxFifoLevel(uart_dev) == 0) {
if((u8AHI_UartReadLineStatus(uart_dev) & E_AHI_UART_LS_TEMT) != 0) {
return FALSE;
}
}
return TRUE;
}
#ifdef UART_EXTRAS
/****************************************************************************
*
* NAME: uart_driver_flush
*
* DESCRIPTION:
* Flushes the buffers of the specified UART
*
* PARAMETERS: Name RW Usage
* uart_dev R UART to disable, eg, E_AHI_UART_0
* reset_tx R to reset the transmit FIFO
* reset_rx R to reset the receive FIFO
*
* RETURNS:
* void
*
****************************************************************************/
void
uart_driver_flush(uint8_t uart_dev, bool_t reset_tx, bool_t reset_rx)
{
/* Disable TX Fifo empty and Rx data interrupts */
uart_driver_disable_interrupts(uart_dev);
/* flush hardware buffer */
vAHI_UartReset(uart_dev, reset_tx, reset_rx);
vAHI_UartReset(uart_dev, FALSE, FALSE);
/* Re-enable TX Fifo empty and Rx data interrupts */
uart_driver_enable_interrupts(uart_dev);
}
#endif /* UART_EXTRAS */