added prescaler for 16 bit pwm in LPC1347 target
Fork of mbed-dev by
Diff: targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/serial_api.c
- Revision:
- 0:9b334a45a8ff
- Child:
- 22:9c52de9bc1d7
diff -r 000000000000 -r 9b334a45a8ff targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/serial_api.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/hal/TARGET_Silicon_Labs/TARGET_EFM32/serial_api.c Thu Oct 01 15:25:22 2015 +0300 @@ -0,0 +1,1829 @@ +/***************************************************************************//** + * @file serial_api.c + ******************************************************************************* + * @section License + * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b> + ******************************************************************************* + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no + * obligation to support this Software. Silicon Labs is providing the + * Software "AS IS", with no express or implied warranties of any kind, + * including, but not limited to, any implied warranties of merchantability + * or fitness for any particular purpose or warranties against infringement + * of any proprietary rights of a third party. + * + * Silicon Labs will not be liable for any consequential, incidental, or + * special damages, or any other relief, or for any claim by any third party, + * arising from your use of this Software. + * + ******************************************************************************/ + +#include "device.h" +#include "clocking.h" +#if DEVICE_SERIAL + +#include "mbed_assert.h" +#include "serial_api.h" +#include <string.h> +#include <stdbool.h> + +#include "pinmap.h" +#include "pinmap_function.h" +#include "PeripheralPins.h" +#include "PeripheralNames.h" + +#include "em_usart.h" +#include "em_leuart.h" +#include "em_cmu.h" +#include "em_dma.h" +#include "dma_api_HAL.h" +#include "dma_api.h" +#include "sleep_api.h" +#include "buffer.h" +#include "sleepmodes.h" + +#define SERIAL_LEAST_ACTIVE_SLEEPMODE EM1 +#define SERIAL_LEAST_ACTIVE_SLEEPMODE_LEUART EM2 + +/** Validation of LEUART register block pointer reference + * for assert statements. */ +#if !defined(LEUART_COUNT) +#define LEUART_REF_VALID(ref) (0) +#elif (LEUART_COUNT == 1) +#define LEUART_REF_VALID(ref) ((ref) == LEUART0) +#elif (LEUART_COUNT == 2) +#define LEUART_REF_VALID(ref) (((ref) == LEUART0) || ((ref) == LEUART1)) +#else +#error Undefined number of low energy UARTs (LEUART). +#endif + +/* Store IRQ id for each UART */ +static uint32_t serial_irq_ids[SERIAL_NUM_UARTS] = { 0 }; +/* Interrupt handler from mbed common */ +static uart_irq_handler irq_handler; +/* Keep track of incoming DMA IRQ's */ +static bool serial_dma_irq_fired[DMACTRL_CH_CNT] = { false }; + +/* Serial interface on USBTX/USBRX retargets stdio */ +int stdio_uart_inited = 0; +serial_t stdio_uart; + +static void uart_irq(UARTName, int, SerialIrq); +uint8_t serial_get_index(serial_t *obj); +void serial_enable(serial_t *obj, uint8_t enable); +void serial_enable_pins(serial_t *obj, uint8_t enable); +IRQn_Type serial_get_rx_irq_index(serial_t *obj); +IRQn_Type serial_get_tx_irq_index(serial_t *obj); +CMU_Clock_TypeDef serial_get_clock(serial_t *obj); + +/* ISRs for RX and TX events */ +#ifdef UART0 +static void uart0_rx_irq() { uart_irq(UART_0, 0, RxIrq); } +static void uart0_tx_irq() { uart_irq(UART_0, 0, TxIrq); USART_IntClear((USART_TypeDef*)UART_0, USART_IFC_TXC);} +#endif +#ifdef UART1 +static void uart1_rx_irq() { uart_irq(UART_1, 1, RxIrq); } +static void uart1_tx_irq() { uart_irq(UART_1, 1, TxIrq); USART_IntClear((USART_TypeDef*)UART_1, USART_IFC_TXC);} +#endif +#ifdef USART0 +static void usart0_rx_irq() { uart_irq(USART_0, 2, RxIrq); } +static void usart0_tx_irq() { uart_irq(USART_0, 2, TxIrq); USART_IntClear((USART_TypeDef*)USART_0, USART_IFC_TXC);} +#endif +#ifdef USART1 +static void usart1_rx_irq() { uart_irq(USART_1, 3, RxIrq); } +static void usart1_tx_irq() { uart_irq(USART_1, 3, TxIrq); USART_IntClear((USART_TypeDef*)USART_1, USART_IFC_TXC);} +#endif +#ifdef USART2 +static void usart2_rx_irq() { uart_irq(USART_2, 4, RxIrq); } +static void usart2_tx_irq() { uart_irq(USART_2, 4, TxIrq); USART_IntClear((USART_TypeDef*)USART_2, USART_IFC_TXC);} +#endif +#ifdef LEUART0 +static void leuart0_irq() +{ + if(LEUART_IntGetEnabled(LEUART0) && (LEUART_IF_RXDATAV | LEUART_IF_FERR | LEUART_IFC_PERR | LEUART_IF_RXOF)) { + uart_irq(LEUART_0, 5, RxIrq); + } else { + uart_irq(LEUART_0, 5, TxIrq); + } +} +#endif +#ifdef LEUART1 +static void leuart1_irq() +{ + if(LEUART_IntGetEnabled(LEUART1) && (LEUART_IF_RXDATAV | LEUART_IF_FERR | LEUART_IFC_PERR | LEUART_IF_RXOF)) { + uart_irq(LEUART_1, 6, RxIrq); + } else { + uart_irq(LEUART_1, 6, TxIrq); + } +} +#endif + +/** + * Initialize the UART using default settings, overridden by settings from serial object + * + * @param obj pointer to serial object + */ +static void uart_init(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_Init_TypeDef init = LEUART_INIT_DEFAULT; + + init.enable = leuartDisable; + init.baudrate = 9600; + init.databits = leuartDatabits8; + init.parity = leuartNoParity; + init.stopbits = leuartStopbits1; +#ifdef LEUART_USING_LFXO + init.refFreq = LEUART_LF_REF_FREQ; +#else + init.refFreq = LEUART_REF_FREQ; +#endif + LEUART_Init(obj->serial.periph.leuart, &init); + } else { + USART_InitAsync_TypeDef init = USART_INITASYNC_DEFAULT; + + init.enable = usartDisable; + init.baudrate = 9600; + init.oversampling = usartOVS16; + init.databits = usartDatabits8; + init.parity = usartNoParity; + init.stopbits = usartStopbits1; + + /* Determine the reference clock, because the correct clock is not set up at init time */ + init.refFreq = REFERENCE_FREQUENCY; + + USART_InitAsync(obj->serial.periph.uart, &init); + } +} + +/** +* Get index of serial object, relating it to the physical peripheral. +* +* @param obj pointer to serial object +* @return internal index of U(S)ART peripheral +*/ +inline uint8_t serial_get_index(serial_t *obj) +{ + switch ((uint32_t)obj->serial.periph.uart) { +#ifdef UART0 + case UART_0: + return 0; +#endif +#ifdef UART1 + case UART_1: + return 1; +#endif +#ifdef USART0 + case USART_0: + return 2; +#endif +#ifdef USART1 + case USART_1: + return 3; +#endif +#ifdef USART2 + case USART_2: + return 4; +#endif +#ifdef LEUART0 + case LEUART_0: + return 5; +#endif +#ifdef LEUART1 + case LEUART_1: + return 6; +#endif + } + return 0; +} + +/** +* Get index of serial object RX IRQ, relating it to the physical peripheral. +* +* @param obj pointer to serial object +* @return internal NVIC RX IRQ index of U(S)ART peripheral +*/ +inline IRQn_Type serial_get_rx_irq_index(serial_t *obj) +{ + switch ((uint32_t)obj->serial.periph.uart) { +#ifdef UART0 + case UART_0: + return UART0_RX_IRQn; +#endif +#ifdef UART1 + case UART_1: + return UART1_RX_IRQn; +#endif +#ifdef USART0 + case USART_0: + return USART0_RX_IRQn; +#endif +#ifdef USART1 + case USART_1: + return USART1_RX_IRQn; +#endif +#ifdef USART2 + case USART_2: + return USART2_RX_IRQn; +#endif +#ifdef LEUART0 + case LEUART_0: + return LEUART0_IRQn; +#endif +#ifdef LEUART1 + case LEUART_1: + return LEUART1_IRQn; +#endif + default: + MBED_ASSERT(0); + } + return (IRQn_Type)0; +} + +/** +* Get index of serial object TX IRQ, relating it to the physical peripheral. +* +* @param obj pointer to serial object +* @return internal NVIC TX IRQ index of U(S)ART peripheral +*/ +inline IRQn_Type serial_get_tx_irq_index(serial_t *obj) +{ + switch ((uint32_t)obj->serial.periph.uart) { +#ifdef UART0 + case UART_0: + return UART0_TX_IRQn; +#endif +#ifdef UART1 + case UART_1: + return UART1_TX_IRQn; +#endif +#ifdef USART0 + case USART_0: + return USART0_TX_IRQn; +#endif +#ifdef USART1 + case USART_1: + return USART1_TX_IRQn; +#endif +#ifdef USART2 + case USART_2: + return USART2_TX_IRQn; +#endif +#ifdef LEUART0 + case LEUART_0: + return LEUART0_IRQn; +#endif +#ifdef LEUART1 + case LEUART_1: + return LEUART1_IRQn; +#endif + default: + MBED_ASSERT(0); + } + return (IRQn_Type)0; +} + +/** +* Get clock tree for serial peripheral pointed to by obj. +* +* @param obj pointer to serial object +* @return CMU_Clock_TypeDef for U(S)ART +*/ +inline CMU_Clock_TypeDef serial_get_clock(serial_t *obj) +{ + switch ((uint32_t)obj->serial.periph.uart) { +#ifdef UART0 + case UART_0: + return cmuClock_UART0; +#endif +#ifdef UART1 + case UART_1: + return cmuClock_UART1; +#endif +#ifdef USART0 + case USART_0: + return cmuClock_USART0; +#endif +#ifdef USART1 + case USART_1: + return cmuClock_USART1; +#endif +#ifdef USART2 + case USART_2: + return cmuClock_USART2; +#endif +#ifdef LEUART0 + case LEUART_0: + return cmuClock_LEUART0; +#endif +#ifdef LEUART1 + case LEUART_1: + return cmuClock_LEUART1; +#endif + default: + return cmuClock_HFPER; + } +} + +void serial_preinit(serial_t *obj, PinName tx, PinName rx) +{ + /* Get UART object connected to the given pins */ + UARTName uart_tx = (UARTName) pinmap_peripheral(tx, PinMap_UART_TX); + UARTName uart_rx = (UARTName) pinmap_peripheral(rx, PinMap_UART_RX); + /* Check that pins are connected to same UART */ + UARTName uart = (UARTName) pinmap_merge(uart_tx, uart_rx); + MBED_ASSERT((int) uart != NC); + + obj->serial.periph.uart = (USART_TypeDef *) uart; + + /* Get location */ + uint32_t uart_tx_loc = pin_location(tx, PinMap_UART_TX); + uint32_t uart_rx_loc = pin_location(rx, PinMap_UART_RX); + /* Check that pins are used by same location for the given UART */ + obj->serial.location = pinmap_merge(uart_tx_loc, uart_rx_loc); + MBED_ASSERT(obj->serial.location != (uint32_t)NC); + + /* Store pins in object for easy disabling in serial_free() */ + obj->serial.rx_pin = rx; + obj->serial.tx_pin = tx; + + /* Select interrupt */ + switch ((uint32_t)obj->serial.periph.uart) { +#ifdef UART0 + case UART_0: + NVIC_SetVector(UART0_RX_IRQn, (uint32_t) &uart0_rx_irq); + NVIC_SetVector(UART0_TX_IRQn, (uint32_t) &uart0_tx_irq); + NVIC_SetPriority(UART0_TX_IRQn, 1); + break; +#endif +#ifdef UART1 + case UART_1: + NVIC_SetVector(UART1_RX_IRQn, (uint32_t) &uart1_rx_irq); + NVIC_SetVector(UART1_TX_IRQn, (uint32_t) &uart1_tx_irq); + NVIC_SetPriority(UART1_TX_IRQn, 1); + break; +#endif +#ifdef USART0 + case USART_0: + NVIC_SetVector(USART0_RX_IRQn, (uint32_t) &usart0_rx_irq); + NVIC_SetVector(USART0_TX_IRQn, (uint32_t) &usart0_tx_irq); + NVIC_SetPriority(USART0_TX_IRQn, 1); + break; +#endif +#ifdef USART1 + case USART_1: + NVIC_SetVector(USART1_RX_IRQn, (uint32_t) &usart1_rx_irq); + NVIC_SetVector(USART1_TX_IRQn, (uint32_t) &usart1_tx_irq); + NVIC_SetPriority(USART1_TX_IRQn, 1); + break; +#endif +#ifdef USART2 + case USART_2: + NVIC_SetVector(USART2_RX_IRQn, (uint32_t) &usart2_rx_irq); + NVIC_SetVector(USART2_TX_IRQn, (uint32_t) &usart2_tx_irq); + NVIC_SetPriority(USART2_TX_IRQn, 1); + break; +#endif +#ifdef LEUART0 + case LEUART_0: + NVIC_SetVector(LEUART0_IRQn, (uint32_t) &leuart0_irq); + break; +#endif +#ifdef LEUART1 + case LEUART_1: + NVIC_SetVector(LEUART1_IRQn, (uint32_t) &leuart1_irq); + break; +#endif + } +} + +void serial_enable_pins(serial_t *obj, uint8_t enable) +{ + if (enable) { + /* Configure GPIO pins*/ + pin_mode(obj->serial.rx_pin, Input); + /* 0x10 sets DOUT. Prevents false start. */ + pin_mode(obj->serial.tx_pin, PushPull | 0x10); + } else { + pin_mode(obj->serial.rx_pin, Disabled); + pin_mode(obj->serial.tx_pin, Disabled); + } +} + +void serial_init(serial_t *obj, PinName tx, PinName rx) +{ + serial_preinit(obj, tx, rx); + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + // Set up LEUART clock tree +#ifdef LEUART_USING_LFXO + //set to use LFXO + CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_LFXO); + CMU_ClockEnable(cmuClock_CORELE, true); +#else + //set to use high-speed clock + CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_CORELEDIV2); +#endif + } + + CMU_ClockEnable(serial_get_clock(obj), true); + + /* Configure UART for async operation */ + uart_init(obj); + + /* Limitations of board controller: CDC port only supports 115kbaud */ + if((tx == STDIO_UART_TX) && (rx == STDIO_UART_RX) && (obj->serial.periph.uart == (USART_TypeDef*)STDIO_UART )) { + serial_baud(obj, 115200); + } + + /* Enable pins for UART at correct location */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + obj->serial.periph.leuart->ROUTE = LEUART_ROUTE_RXPEN | LEUART_ROUTE_TXPEN | (obj->serial.location << _LEUART_ROUTE_LOCATION_SHIFT); + obj->serial.periph.leuart->IFC = LEUART_IFC_TXC; + obj->serial.periph.leuart->CTRL |= LEUART_CTRL_RXDMAWU | LEUART_CTRL_TXDMAWU; + } else { + obj->serial.periph.uart->ROUTE = USART_ROUTE_RXPEN | USART_ROUTE_TXPEN | (obj->serial.location << _USART_ROUTE_LOCATION_SHIFT); + obj->serial.periph.uart->IFC = USART_IFC_TXC; + } + + /* If this is the UART to be used for stdio, copy it to the stdio_uart struct */ + if (obj->serial.periph.uart == (USART_TypeDef*)STDIO_UART ) { + stdio_uart_inited = 1; + memcpy(&stdio_uart, obj, sizeof(serial_t)); + } + + serial_enable_pins(obj, true); + serial_enable(obj, true); + + + obj->serial.dmaOptionsTX.dmaChannel = -1; + obj->serial.dmaOptionsTX.dmaUsageState = DMA_USAGE_OPPORTUNISTIC; + + obj->serial.dmaOptionsRX.dmaChannel = -1; + obj->serial.dmaOptionsRX.dmaUsageState = DMA_USAGE_OPPORTUNISTIC; + +} + +void serial_enable(serial_t *obj, uint8_t enable) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + if (enable) { + LEUART_Enable(obj->serial.periph.leuart, leuartEnable); + } else { + LEUART_Enable(obj->serial.periph.leuart, leuartDisable); + } + } else { + if (enable) { + USART_Enable(obj->serial.periph.uart, usartEnable); + } else { + USART_Enable(obj->serial.periph.uart, usartDisable); + } + } + serial_irq_ids[serial_get_index(obj)] = 0; +} + +/** + * Set UART baud rate + */ +void serial_baud(serial_t *obj, int baudrate) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { +#ifdef LEUART_USING_LFXO + + /* check if baudrate is within allowed range */ + MBED_ASSERT(baudrate >= (LEUART_LF_REF_FREQ >> 7)); + + if(baudrate > (LEUART_LF_REF_FREQ >> 1)){ + /* check if baudrate is within allowed range */ + MBED_ASSERT((baudrate <= (LEUART_HF_REF_FREQ >> 1)) && (baudrate > (LEUART_HF_REF_FREQ >> 10))); + + CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_CORELEDIV2); + uint8_t divisor = 1; + + if(baudrate > (LEUART_HF_REF_FREQ >> 7)){ + divisor = 1; + }else if(baudrate > (LEUART_HF_REF_FREQ >> 8)){ + divisor = 2; + }else if(baudrate > (LEUART_HF_REF_FREQ >> 9)){ + divisor = 4; + }else{ + divisor = 8; + } + CMU_ClockDivSet(serial_get_clock(obj), divisor); + LEUART_BaudrateSet(obj->serial.periph.leuart, LEUART_HF_REF_FREQ/divisor, (uint32_t)baudrate); + }else{ + CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_LFXO); + CMU_ClockDivSet(serial_get_clock(obj), 1); + LEUART_BaudrateSet(obj->serial.periph.leuart, LEUART_LF_REF_FREQ, (uint32_t)baudrate); + } +#else + /* check if baudrate is within allowed range */ + MBED_ASSERT((baudrate > (LEUART_REF_FREQ >> 10)) && (baudrate <= (LEUART_REF_FREQ >> 1))); + uint8_t divisor = 1; + if(baudrate > (LEUART_REF_FREQ >> 7)){ + divisor = 1; + }else if(baudrate > (LEUART_REF_FREQ >> 8)){ + divisor = 2; + }else if(baudrate > (LEUART_REF_FREQ >> 9)){ + divisor = 4; + }else{ + divisor = 8; + } + CMU_ClockDivSet(serial_get_clock(obj), divisor); + LEUART_BaudrateSet(obj->serial.periph.leuart, LEUART_REF_FREQ/divisor, (uint32_t)baudrate); +#endif + } else { + USART_BaudrateAsyncSet(obj->serial.periph.uart, REFERENCE_FREQUENCY, (uint32_t)baudrate, usartOVS16); + } +} + +/** + * Set UART format by re-initializing the peripheral. + */ +void serial_format(serial_t *obj, int data_bits, SerialParity parity, int stop_bits) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + /* Save the serial state */ + uint8_t was_enabled = LEUART_StatusGet(obj->serial.periph.leuart) & (LEUART_STATUS_TXENS | LEUART_STATUS_RXENS); + uint32_t enabled_interrupts = obj->serial.periph.leuart->IEN; + + LEUART_Init_TypeDef init = LEUART_INIT_DEFAULT; + + /* We support 8 data bits ONLY on LEUART*/ + MBED_ASSERT(data_bits == 8); + + /* Re-init the UART */ + init.enable = (was_enabled == 0 ? leuartDisable : leuartEnable); + init.baudrate = LEUART_BaudrateGet(obj->serial.periph.leuart); + if (stop_bits == 2) { + init.stopbits = leuartStopbits2; + } else { + init.stopbits = leuartStopbits1; + } + switch (parity) { + case ParityOdd: + case ParityForced0: + init.parity = leuartOddParity; + break; + case ParityEven: + case ParityForced1: + init.parity = leuartEvenParity; + break; + default: /* ParityNone */ + init.parity = leuartNoParity; + break; + } + + LEUART_Init(obj->serial.periph.leuart, &init); + + /* Re-enable pins for UART at correct location */ + obj->serial.periph.leuart->ROUTE = LEUART_ROUTE_RXPEN | LEUART_ROUTE_TXPEN | (obj->serial.location << _LEUART_ROUTE_LOCATION_SHIFT); + + /* Re-enable interrupts */ + if(was_enabled != 0) { + obj->serial.periph.leuart->IFC = LEUART_IFC_TXC; + obj->serial.periph.leuart->IEN = enabled_interrupts; + } + } else { + /* Save the serial state */ + uint8_t was_enabled = USART_StatusGet(obj->serial.periph.uart) & (USART_STATUS_TXENS | USART_STATUS_RXENS); + uint32_t enabled_interrupts = obj->serial.periph.uart->IEN; + + + USART_InitAsync_TypeDef init = USART_INITASYNC_DEFAULT; + + /* We support 4 to 8 data bits */ + MBED_ASSERT(data_bits >= 4 && data_bits <= 8); + + /* Re-init the UART */ + init.enable = (was_enabled == 0 ? usartDisable : usartEnable); + init.baudrate = USART_BaudrateGet(obj->serial.periph.uart); + init.oversampling = usartOVS16; + init.databits = (USART_Databits_TypeDef)((data_bits - 3) << _USART_FRAME_DATABITS_SHIFT); + if (stop_bits == 2) { + init.stopbits = usartStopbits2; + } else { + init.stopbits = usartStopbits1; + } + switch (parity) { + case ParityOdd: + case ParityForced0: + init.parity = usartOddParity; + break; + case ParityEven: + case ParityForced1: + init.parity = usartEvenParity; + break; + default: /* ParityNone */ + init.parity = usartNoParity; + break; + } + + USART_InitAsync(obj->serial.periph.uart, &init); + + /* Re-enable pins for UART at correct location */ + obj->serial.periph.uart->ROUTE = USART_ROUTE_RXPEN | USART_ROUTE_TXPEN | (obj->serial.location << _USART_ROUTE_LOCATION_SHIFT); + + /* Re-enable interrupts */ + if(was_enabled != 0) { + obj->serial.periph.uart->IFC = USART_IFC_TXC; + obj->serial.periph.uart->IEN = enabled_interrupts; + } + } +} + +/****************************************************************************** + * INTERRUPTS * + ******************************************************************************/ +uint8_t serial_tx_ready(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->STATUS & LEUART_STATUS_TXBL) ? true : false; + } else { + return (obj->serial.periph.uart->STATUS & USART_STATUS_TXBL) ? true : false; + } +} + +uint8_t serial_rx_ready(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->STATUS & LEUART_STATUS_RXDATAV) ? true : false; + } else { + return (obj->serial.periph.uart->STATUS & USART_STATUS_RXDATAV) ? true : false; + } +} + +void serial_write_asynch(serial_t *obj, int data) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + obj->serial.periph.leuart->TXDATA = (uint32_t)data; + } else { + obj->serial.periph.uart->TXDATA = (uint32_t)data; + } +} + +int serial_read_asynch(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (int)obj->serial.periph.leuart->RXDATA; + } else { + return (int)obj->serial.periph.uart->RXDATA; + } +} + +uint8_t serial_tx_int_flag(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->IF & LEUART_IF_TXBL) ? true : false; + } else { + return (obj->serial.periph.uart->IF & USART_IF_TXBL) ? true : false; + } +} + +uint8_t serial_rx_int_flag(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->IF & LEUART_IF_RXDATAV) ? true : false; + } else { + return (obj->serial.periph.uart->IF & USART_IF_RXDATAV) ? true : false; + } +} + +void serial_read_asynch_complete(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + obj->serial.periph.leuart->IFC |= LEUART_IFC_RXOF; // in case it got full + } else { + obj->serial.periph.uart->IFC |= USART_IFC_RXFULL; // in case it got full + } +} + +void serial_write_asynch_complete(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + obj->serial.periph.leuart->IFC |= LEUART_IFC_TXC; + } else { + obj->serial.periph.uart->IFC |= USART_IFC_TXC; + } +} + +/** Enable and set the interrupt handler for write (TX) + * + * @param obj The serial object + * @param address The address of TX handler + * @param enable Set to non-zero to enable or zero to disable + */ +void serial_write_enable_interrupt(serial_t *obj, uint32_t address, uint8_t enable) +{ + NVIC_SetVector(serial_get_tx_irq_index(obj), address); + serial_irq_set(obj, (SerialIrq)1, enable); +} + +/** Enable and set the interrupt handler for read (RX) + * + * @param obj The serial object + * @param address The address of RX handler + * @param enable Set to non-zero to enable or zero to disable + */ +void serial_read_enable_interrupt(serial_t *obj, uint32_t address, uint8_t enable) +{ + NVIC_SetVector(serial_get_rx_irq_index(obj), address); + serial_irq_set(obj, (SerialIrq)0, enable); +} + +uint8_t serial_interrupt_enabled(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->IEN & (LEUART_IEN_RXDATAV | LEUART_IEN_TXBL)) ? true : false; + } else { + return (obj->serial.periph.uart->IEN & (USART_IEN_RXDATAV | USART_IEN_TXBL)) ? true : false; + } +} + +/** + * Set handler for all serial interrupts (is probably SerialBase::_handler()) + * and store IRQ ID to be returned to the handler upon interrupt. ID is + * probably a pointer to the calling Serial object. + */ +void serial_irq_handler(serial_t *obj, uart_irq_handler handler, uint32_t id) +{ + irq_handler = handler; + serial_irq_ids[serial_get_index(obj)] = id; +} + +/** + * Generic ISR for all UARTs, both TX and RX + */ +static void uart_irq(UARTName name, int index, SerialIrq irq) +{ + if (serial_irq_ids[index] != 0) { + /* Pass interrupt on to mbed common handler */ + irq_handler(serial_irq_ids[index], irq); + /* Clearing interrupt not necessary */ + } +} + +/** + * Set ISR for a given UART and interrupt event (TX or RX) + */ +void serial_irq_set(serial_t *obj, SerialIrq irq, uint32_t enable) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + /* Enable or disable interrupt */ + if (enable) { + if (irq == RxIrq) { /* RX */ + obj->serial.periph.leuart->IEN |= LEUART_IEN_RXDATAV; + NVIC_ClearPendingIRQ(serial_get_rx_irq_index(obj)); + NVIC_EnableIRQ(serial_get_rx_irq_index(obj)); + } else { /* TX */ + obj->serial.periph.leuart->IEN |= LEUART_IEN_TXC; + NVIC_ClearPendingIRQ(serial_get_tx_irq_index(obj)); + NVIC_SetPriority(serial_get_tx_irq_index(obj), 1); + NVIC_EnableIRQ(serial_get_tx_irq_index(obj)); + } + } else { + if (irq == RxIrq) { /* RX */ + obj->serial.periph.leuart->IEN &= ~LEUART_IEN_RXDATAV; + NVIC_DisableIRQ(serial_get_rx_irq_index(obj)); + } else { /* TX */ + obj->serial.periph.leuart->IEN &= ~LEUART_IEN_TXC; + NVIC_DisableIRQ(serial_get_tx_irq_index(obj)); + } + } + } else { + /* Enable or disable interrupt */ + if (enable) { + if (irq == RxIrq) { /* RX */ + obj->serial.periph.uart->IEN |= USART_IEN_RXDATAV; + NVIC_ClearPendingIRQ(serial_get_rx_irq_index(obj)); + NVIC_EnableIRQ(serial_get_rx_irq_index(obj)); + } else { /* TX */ + obj->serial.periph.uart->IEN |= USART_IEN_TXC; + NVIC_ClearPendingIRQ(serial_get_tx_irq_index(obj)); + NVIC_SetPriority(serial_get_tx_irq_index(obj), 1); + NVIC_EnableIRQ(serial_get_tx_irq_index(obj)); + } + } else { + if (irq == RxIrq) { /* RX */ + obj->serial.periph.uart->IEN &= ~USART_IEN_RXDATAV; + NVIC_DisableIRQ(serial_get_rx_irq_index(obj)); + } else { /* TX */ + obj->serial.periph.uart->IEN &= ~USART_IEN_TXC; + NVIC_DisableIRQ(serial_get_tx_irq_index(obj)); + } + } + } +} + +/****************************************************************************** + * READ/WRITE * + ******************************************************************************/ + +/** + * Get one char from serial link + */ +int serial_getc(serial_t *obj) +{ + /* Emlib USART_Rx blocks until data is available, so we don't need to use + * serial_readable(). Use USART_RxDataGet() to read register directly. */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return LEUART_Rx(obj->serial.periph.leuart); + } else { + return USART_Rx(obj->serial.periph.uart); + } +} + +/* + * Send one char over serial link + */ +void serial_putc(serial_t *obj, int c) +{ + /* Emlib USART_Tx blocks until buffer is writable (non-full), so we don't + * need to use serial_writable(). */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_Tx(obj->serial.periph.leuart, (uint8_t)(c)); + } else { + USART_Tx(obj->serial.periph.uart, (uint8_t)(c)); + } +} + +/** + * Check if data is available in RX data vector + */ +int serial_readable(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return obj->serial.periph.leuart->STATUS & LEUART_STATUS_RXDATAV; + } else { + return obj->serial.periph.uart->STATUS & USART_STATUS_RXDATAV; + } +} + +/** + * Check if TX buffer is empty + */ +int serial_writable(serial_t *obj) +{ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return obj->serial.periph.leuart->STATUS & LEUART_STATUS_TXBL; + } else { + return obj->serial.periph.uart->STATUS & USART_STATUS_TXBL; + } +} + +/** + * Clear UART interrupts + */ +void serial_clear(serial_t *obj) +{ + /* Interrupts automatically clear when condition is not met anymore */ +} + +void serial_break_set(serial_t *obj) +{ + /* Send transmission break */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + obj->serial.periph.leuart->TXDATAX = LEUART_TXDATAX_TXBREAK; + } else { + obj->serial.periph.uart->TXDATAX = USART_TXDATAX_TXBREAK; + } +} + +void serial_break_clear(serial_t *obj) +{ + /* No need to clear break, it is automatically cleared after one frame. + * From the reference manual: + * + * By setting TXBREAK, the output will be held low during the stop-bit + * period to generate a framing error. A receiver that supports break + * detection detects this state, allowing it to be used e.g. for framing + * of larger data packets. The line is driven high before the next frame + * is transmitted so the next start condition can be identified correctly + * by the recipient. Continuous breaks lasting longer than a USART frame + * are thus not supported by the USART. GPIO can be used for this. + */ +} + +void serial_pinout_tx(PinName tx) +{ + /* 0x10 sets DOUT high. Prevents false start. */ + pin_mode(tx, PushPull | 0x10); +} + +/************************************************************************************ + * DMA helper functions * + ************************************************************************************/ +/****************************************** +* static void serial_dmaTransferComplete(uint channel, bool primary, void* user) +* +* Callback function which gets called upon DMA transfer completion +* the user-defined pointer is pointing to the CPP-land thunk +******************************************/ +static void serial_dmaTransferComplete(unsigned int channel, bool primary, void *user) +{ + /* Store information about which channel triggered because CPP doesn't take arguments */ + serial_dma_irq_fired[channel] = true; + + /* User pointer should be a thunk to CPP land */ + if (user != NULL) { + ((DMACallback)user)(); + } +} + +/****************************************** +* static void serial_setupDmaChannel(serial_t *obj, bool tx_nrx) +* +* Sets up the DMA configuration block for the assigned channel +* tx_nrx: true if configuring TX, false if configuring RX. +******************************************/ +static void serial_dmaSetupChannel(serial_t *obj, bool tx_nrx) +{ + DMA_CfgChannel_TypeDef channelConfig; + + if(tx_nrx) { + //setup TX channel + channelConfig.highPri = false; + channelConfig.enableInt = true; + channelConfig.cb = &(obj->serial.dmaOptionsTX.dmaCallback); + + switch((uint32_t)(obj->serial.periph.uart)) { +#ifdef UART0 + case UART_0: + channelConfig.select = DMAREQ_UART0_TXBL; + break; +#endif +#ifdef UART1 + case UART_1: + channelConfig.select = DMAREQ_UART1_TXBL; + break; +#endif +#ifdef USART0 + case USART_0: + channelConfig.select = DMAREQ_USART0_TXBL; + break; +#endif +#ifdef USART1 + case USART_1: + channelConfig.select = DMAREQ_USART1_TXBL; + break; +#endif +#ifdef USART2 + case USART_2: + channelConfig.select = DMAREQ_USART2_TXBL; + break; +#endif +#ifdef LEUART0 + case LEUART_0: + channelConfig.select = DMAREQ_LEUART0_TXBL; + break; +#endif +#ifdef LEUART1 + case LEUART_1: + channelConfig.select = DMAREQ_LEUART1_TXBL; + break; +#endif + } + + DMA_CfgChannel(obj->serial.dmaOptionsTX.dmaChannel, &channelConfig); + } else { + //setup RX channel + channelConfig.highPri = true; + channelConfig.enableInt = true; + channelConfig.cb = &(obj->serial.dmaOptionsRX.dmaCallback); + + switch((uint32_t)(obj->serial.periph.uart)) { +#ifdef UART0 + case UART_0: + channelConfig.select = DMAREQ_UART0_RXDATAV; + break; +#endif +#ifdef UART1 + case UART_1: + channelConfig.select = DMAREQ_UART1_RXDATAV; + break; +#endif +#ifdef USART0 + case USART_0: + channelConfig.select = DMAREQ_USART0_RXDATAV; + break; +#endif +#ifdef USART1 + case USART_1: + channelConfig.select = DMAREQ_USART1_RXDATAV; + break; +#endif +#ifdef USART2 + case USART_2: + channelConfig.select = DMAREQ_USART2_RXDATAV; + break; +#endif +#ifdef LEUART0 + case LEUART_0: + channelConfig.select = DMAREQ_LEUART0_RXDATAV; + break; +#endif +#ifdef LEUART1 + case LEUART_1: + channelConfig.select = DMAREQ_LEUART1_RXDATAV; + break; +#endif + } + + DMA_CfgChannel(obj->serial.dmaOptionsRX.dmaChannel, &channelConfig); + } + + +} + +/****************************************** +* static void serial_dmaTrySetState(DMA_OPTIONS_t *obj, DMAUsage requestedState) +* +* Tries to set the passed DMA state to the requested state. +* +* requested state possibilities: +* * NEVER: +* if the previous state was always, will deallocate the channel +* * OPPORTUNISTIC: +* If the previous state was always, will reuse that channel but free upon next completion. +* If not, will try to acquire a channel. +* When allocated, state changes to DMA_USAGE_TEMPORARY_ALLOCATED. +* * ALWAYS: +* Will try to allocate a channel and keep it. +* If succesfully allocated, state changes to DMA_USAGE_ALLOCATED. +******************************************/ +static void serial_dmaTrySetState(DMA_OPTIONS_t *obj, DMAUsage requestedState, serial_t *serialPtr, bool tx_nrx) +{ + DMAUsage currentState = obj->dmaUsageState; + int tempDMAChannel = -1; + + if ((requestedState == DMA_USAGE_ALWAYS) && (currentState != DMA_USAGE_ALLOCATED)) { + /* Try to allocate channel */ + tempDMAChannel = dma_channel_allocate(DMA_CAP_NONE); + if(tempDMAChannel >= 0) { + obj->dmaChannel = tempDMAChannel; + obj->dmaUsageState = DMA_USAGE_ALLOCATED; + dma_init(); + serial_dmaSetupChannel(serialPtr, tx_nrx); + } + } else if (requestedState == DMA_USAGE_OPPORTUNISTIC) { + if (currentState == DMA_USAGE_ALLOCATED) { + /* Channels have already been allocated previously by an ALWAYS state, so after this transfer, we will release them */ + obj->dmaUsageState = DMA_USAGE_TEMPORARY_ALLOCATED; + } else { + /* Try to allocate channel */ + tempDMAChannel = dma_channel_allocate(DMA_CAP_NONE); + if(tempDMAChannel >= 0) { + obj->dmaChannel = tempDMAChannel; + obj->dmaUsageState = DMA_USAGE_TEMPORARY_ALLOCATED; + dma_init(); + serial_dmaSetupChannel(serialPtr, tx_nrx); + } + } + } else if (requestedState == DMA_USAGE_NEVER) { + /* If channel is allocated, get rid of it */ + dma_channel_free(obj->dmaChannel); + obj->dmaChannel = -1; + obj->dmaUsageState = DMA_USAGE_NEVER; + } +} + +static void serial_dmaActivate(serial_t *obj, void* cb, void* buffer, int length, bool tx_nrx) +{ + DMA_CfgDescr_TypeDef channelConfig; + + if(tx_nrx) { + // Set DMA callback + obj->serial.dmaOptionsTX.dmaCallback.cbFunc = serial_dmaTransferComplete; + obj->serial.dmaOptionsTX.dmaCallback.userPtr = cb; + + // Set up configuration structure + channelConfig.dstInc = dmaDataIncNone; + channelConfig.srcInc = dmaDataInc1; + channelConfig.size = dmaDataSize1; + channelConfig.arbRate = dmaArbitrate1; + channelConfig.hprot = 0; + + DMA_CfgDescr(obj->serial.dmaOptionsTX.dmaChannel, true, &channelConfig); + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + // Activate TX + obj->serial.periph.leuart->CMD = LEUART_CMD_TXEN; + while(obj->serial.periph.leuart->SYNCBUSY & LEUART_SYNCBUSY_CMD); + + // Kick off TX DMA + DMA_ActivateBasic(obj->serial.dmaOptionsTX.dmaChannel, true, false, (void*) &(obj->serial.periph.leuart->TXDATA), buffer, length - 1); + } else { + // Activate TX amd clear TX buffer + obj->serial.periph.uart->CMD = USART_CMD_TXEN | USART_CMD_CLEARTX; + + // Kick off TX DMA + DMA_ActivateBasic(obj->serial.dmaOptionsTX.dmaChannel, true, false, (void*) &(obj->serial.periph.uart->TXDATA), buffer, length - 1); + } + } else { + // Set DMA callback + obj->serial.dmaOptionsRX.dmaCallback.cbFunc = serial_dmaTransferComplete; + obj->serial.dmaOptionsRX.dmaCallback.userPtr = cb; + + // Set up configuration structure + channelConfig.dstInc = dmaDataInc1; + channelConfig.srcInc = dmaDataIncNone; + channelConfig.size = dmaDataSize1; + channelConfig.arbRate = dmaArbitrate1; + channelConfig.hprot = 0; + + DMA_CfgDescr(obj->serial.dmaOptionsRX.dmaChannel, true, &channelConfig); + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + // Activate RX and clear RX buffer + obj->serial.periph.leuart->CMD = LEUART_CMD_RXEN | LEUART_CMD_CLEARRX; + while(obj->serial.periph.leuart->SYNCBUSY & LEUART_SYNCBUSY_CMD); + + // Kick off RX DMA + DMA_ActivateBasic(obj->serial.dmaOptionsRX.dmaChannel, true, false, buffer, (void*) &(obj->serial.periph.leuart->RXDATA), length - 1); + } else { + // Activate RX and clear RX buffer + obj->serial.periph.uart->CMD = USART_CMD_RXEN | USART_CMD_CLEARRX; + + // Kick off RX DMA + DMA_ActivateBasic(obj->serial.dmaOptionsRX.dmaChannel, true, false, buffer, (void*) &(obj->serial.periph.uart->RXDATA), length - 1); + } + } +} + +/************************************************************************************ + * ASYNCHRONOUS HAL * + ************************************************************************************/ + +#if DEVICE_SERIAL_ASYNCH + +/************************************ + * HELPER FUNCTIONS * + ***********************************/ + +/** Configure TX events + * + * @param obj The serial object + * @param event The logical OR of the TX events to configure + * @param enable Set to non-zero to enable events, or zero to disable them + */ +void serial_tx_enable_event(serial_t *obj, int event, uint8_t enable) +{ + // Shouldn't have to enable TX interrupt here, just need to keep track of the requested events. + if(enable) obj->serial.events |= event; + else obj->serial.events &= ~event; +} + +/** + * @param obj The serial object. + * @param event The logical OR of the RX events to configure + * @param enable Set to non-zero to enable events, or zero to disable them + */ +void serial_rx_enable_event(serial_t *obj, int event, uint8_t enable) +{ + if(enable) { + obj->serial.events |= event; + } else { + obj->serial.events &= ~event; + } + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + if(event & SERIAL_EVENT_RX_FRAMING_ERROR) { + //FERR interrupt source + if(enable) obj->serial.periph.leuart->IEN |= LEUART_IEN_FERR; + else obj->serial.periph.leuart->IEN &= ~LEUART_IEN_FERR; + } + if(event & SERIAL_EVENT_RX_PARITY_ERROR) { + //PERR interrupt source + if(enable) obj->serial.periph.leuart->IEN |= LEUART_IEN_PERR; + else obj->serial.periph.leuart->IEN &= ~LEUART_IEN_PERR; + } + if(event & SERIAL_EVENT_RX_OVERFLOW) { + //RXOF interrupt source + if(enable) obj->serial.periph.leuart->IEN |= LEUART_IEN_RXOF; + else obj->serial.periph.leuart->IEN &= ~LEUART_IEN_RXOF; + } + } else { + if(event & SERIAL_EVENT_RX_FRAMING_ERROR) { + //FERR interrupt source + if(enable) obj->serial.periph.uart->IEN |= USART_IEN_FERR; + else obj->serial.periph.uart->IEN &= ~USART_IEN_FERR; + } + if(event & SERIAL_EVENT_RX_PARITY_ERROR) { + //PERR interrupt source + if(enable) obj->serial.periph.uart->IEN |= USART_IEN_PERR; + else obj->serial.periph.uart->IEN &= ~USART_IEN_PERR; + } + if(event & SERIAL_EVENT_RX_OVERFLOW) { + //RXOF interrupt source + if(enable) obj->serial.periph.uart->IEN |= USART_IEN_RXOF; + else obj->serial.periph.uart->IEN &= ~USART_IEN_RXOF; + } + } +} + +/** Configure the TX buffer for an asynchronous write serial transaction + * + * @param obj The serial object. + * @param tx The buffer for sending. + * @param tx_length The number of words to transmit. + */ +void serial_tx_buffer_set(serial_t *obj, void *tx, int tx_length, uint8_t width) +{ + // We only support byte buffers for now + MBED_ASSERT(width == 8); + + if(serial_tx_active(obj)) return; + + obj->tx_buff.buffer = tx; + obj->tx_buff.length = tx_length; + obj->tx_buff.pos = 0; + + return; +} + +/** Configure the TX buffer for an asynchronous read serial transaction + * + * @param obj The serial object. + * @param rx The buffer for receiving. + * @param rx_length The number of words to read. + */ +void serial_rx_buffer_set(serial_t *obj, void *rx, int rx_length, uint8_t width) +{ + // We only support byte buffers for now + MBED_ASSERT(width == 8); + + if(serial_rx_active(obj)) return; + + obj->rx_buff.buffer = rx; + obj->rx_buff.length = rx_length; + obj->rx_buff.pos = 0; + + return; +} + +/************************************ + * TRANSFER FUNCTIONS * + ***********************************/ + +/** Begin asynchronous TX transfer. The used buffer is specified in the serial object, + * tx_buff + * + * @param obj The serial object + * @param cb The function to call when an event occurs + * @param hint A suggestion for how to use DMA with this transfer + * @return Returns number of data transfered, or 0 otherwise + */ +int serial_tx_asynch(serial_t *obj, const void *tx, size_t tx_length, uint8_t tx_width, uint32_t handler, uint32_t event, DMAUsage hint) +{ + // Check that a buffer has indeed been set up + MBED_ASSERT(tx != (void*)0); + if(tx_length == 0) return 0; + + // Set up buffer + serial_tx_buffer_set(obj, (void *)tx, tx_length, tx_width); + + // Set up events + serial_tx_enable_event(obj, SERIAL_EVENT_TX_ALL, false); + serial_tx_enable_event(obj, event, true); + + // Set up sleepmode +#ifdef LEUART_USING_LFXO + if(LEUART_REF_VALID(obj->serial.periph.leuart) && (LEUART_BaudrateGet(obj->serial.periph.leuart) <= (LEUART_LF_REF_FREQ/2))){ + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE_LEUART); + }else{ + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); + } +#else + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); +#endif + + // Determine DMA strategy + serial_dmaTrySetState(&(obj->serial.dmaOptionsTX), hint, obj, true); + + // If DMA, kick off DMA transfer + if(obj->serial.dmaOptionsTX.dmaChannel >= 0) { + serial_dmaActivate(obj, (void*)handler, obj->tx_buff.buffer, obj->tx_buff.length, true); + } + // Else, activate interrupt. TXBL will take care of buffer filling through ISR. + else { + // Store callback + NVIC_ClearPendingIRQ(serial_get_tx_irq_index(obj)); + NVIC_DisableIRQ(serial_get_tx_irq_index(obj)); + NVIC_SetPriority(serial_get_tx_irq_index(obj), 1); + NVIC_SetVector(serial_get_tx_irq_index(obj), (uint32_t)handler); + NVIC_EnableIRQ(serial_get_tx_irq_index(obj)); + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + // Activate TX and clear TX buffer + obj->serial.periph.leuart->CMD = LEUART_CMD_TXEN | LEUART_CMD_CLEARTX; + while(obj->serial.periph.leuart->SYNCBUSY & LEUART_SYNCBUSY_CMD); + + // Enable interrupt + LEUART_IntEnable(obj->serial.periph.leuart, LEUART_IEN_TXBL); + } else { + // Activate TX and clear TX buffer + obj->serial.periph.uart->CMD = USART_CMD_TXEN | USART_CMD_CLEARTX; + + // Enable interrupt + USART_IntEnable(obj->serial.periph.uart, USART_IEN_TXBL); + } + } + + return 0; +} + +/** Begin asynchronous RX transfer (enable interrupt for data collecting) + * The used buffer is specified in the serial object - rx_buff + * + * @param obj The serial object + * @param cb The function to call when an event occurs + * @param hint A suggestion for how to use DMA with this transfer + */ +void serial_rx_asynch(serial_t *obj, void *rx, size_t rx_length, uint8_t rx_width, uint32_t handler, uint32_t event, uint8_t char_match, DMAUsage hint) +{ + // Check that a buffer has indeed been set up + MBED_ASSERT(rx != (void*)0); + if(rx_length == 0) return; + + // Set up buffer + serial_rx_buffer_set(obj,(void*) rx, rx_length, rx_width); + + //disable character match if no character is specified + if(char_match == SERIAL_RESERVED_CHAR_MATCH){ + event &= ~SERIAL_EVENT_RX_CHARACTER_MATCH; + } + + /*clear all set interrupts*/ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_PERR | LEUART_IFC_FERR | LEUART_IFC_RXOF); + }else{ + USART_IntClear(obj->serial.periph.uart, USART_IFC_PERR | USART_IFC_FERR | USART_IFC_RXOF); + } + + // Set up events + serial_rx_enable_event(obj, SERIAL_EVENT_RX_ALL, false); + serial_rx_enable_event(obj, event, true); + obj->char_match = char_match; + + // Set up sleepmode +#ifdef LEUART_USING_LFXO + if(LEUART_REF_VALID(obj->serial.periph.leuart) && (LEUART_BaudrateGet(obj->serial.periph.leuart) <= (LEUART_LF_REF_FREQ/2))){ + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE_LEUART); + }else{ + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); + } +#else + blockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); +#endif + + // Determine DMA strategy + // If character match is enabled, we can't use DMA, sadly. We could when using LEUART though, but that support is not in here yet. + // TODO: add DMA support for character matching with leuart + if(!(event & SERIAL_EVENT_RX_CHARACTER_MATCH)) { + serial_dmaTrySetState(&(obj->serial.dmaOptionsRX), hint, obj, false); + }else{ + serial_dmaTrySetState(&(obj->serial.dmaOptionsRX), DMA_USAGE_NEVER, obj, false); + } + + // If DMA, kick off DMA + if(obj->serial.dmaOptionsRX.dmaChannel >= 0) { + serial_dmaActivate(obj, (void*)handler, obj->rx_buff.buffer, obj->rx_buff.length, false); + } + // Else, activate interrupt. RXDATAV is responsible for incoming data notification. + else { + // Store callback + NVIC_ClearPendingIRQ(serial_get_rx_irq_index(obj)); + NVIC_SetVector(serial_get_rx_irq_index(obj), (uint32_t)handler); + NVIC_EnableIRQ(serial_get_rx_irq_index(obj)); + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + // Activate RX and clear RX buffer + obj->serial.periph.leuart->CMD = LEUART_CMD_RXEN | LEUART_CMD_CLEARRX; + while(obj->serial.periph.leuart->SYNCBUSY & LEUART_SYNCBUSY_CMD); + + // Enable interrupt + LEUART_IntEnable(obj->serial.periph.leuart, LEUART_IEN_RXDATAV); + } else { + // Activate RX and clear RX buffer + obj->serial.periph.uart->CMD = USART_CMD_RXEN | USART_CMD_CLEARRX; + + // Clear RXFULL + USART_IntClear(obj->serial.periph.uart, USART_IFC_RXFULL); + + // Enable interrupt + USART_IntEnable(obj->serial.periph.uart, USART_IEN_RXDATAV); + } + } + + return; +} + +/** Attempts to determine if the serial peripheral is already in use for TX + * + * @param obj The serial object + * @return Non-zero if the TX transaction is ongoing, 0 otherwise + */ +uint8_t serial_tx_active(serial_t *obj) +{ + switch(obj->serial.dmaOptionsTX.dmaUsageState) { + case DMA_USAGE_TEMPORARY_ALLOCATED: + /* Temporary allocation always means its active, as this state gets cleared afterwards */ + return 1; + case DMA_USAGE_ALLOCATED: + /* Check whether the allocated DMA channel is active by checking the DMA transfer */ + return(DMA_ChannelEnabled(obj->serial.dmaOptionsTX.dmaChannel)); + default: + /* Check whether interrupt for serial TX is enabled */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->IEN & (LEUART_IEN_TXBL)) ? true : false; + } else { + return (obj->serial.periph.uart->IEN & (USART_IEN_TXBL)) ? true : false; + } + } +} + +/** Attempts to determine if the serial peripheral is already in use for RX + * + * @param obj The serial object + * @return Non-zero if the RX transaction is ongoing, 0 otherwise + */ +uint8_t serial_rx_active(serial_t *obj) +{ + switch(obj->serial.dmaOptionsRX.dmaUsageState) { + case DMA_USAGE_TEMPORARY_ALLOCATED: + /* Temporary allocation always means its active, as this state gets cleared afterwards */ + return 1; + case DMA_USAGE_ALLOCATED: + /* Check whether the allocated DMA channel is active by checking the DMA transfer */ + return(DMA_ChannelEnabled(obj->serial.dmaOptionsRX.dmaChannel)); + default: + /* Check whether interrupt for serial TX is enabled */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + return (obj->serial.periph.leuart->IEN & (LEUART_IEN_RXDATAV)) ? true : false; + } else { + return (obj->serial.periph.uart->IEN & (USART_IEN_RXDATAV)) ? true : false; + } + } +} + +/** The asynchronous TX handler. Writes to the TX FIFO and checks for events. + * If any TX event has occured, the TX abort function is called. + * + * @param obj The serial object + * @return Returns event flags if a TX transfer termination condition was met or 0 otherwise + */ +int serial_tx_irq_handler_asynch(serial_t *obj) +{ + /* This interrupt handler is called from USART irq */ + uint8_t *buf = obj->tx_buff.buffer; + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + if(obj->serial.periph.leuart->IEN & LEUART_IEN_TXBL){ + /* There is still data to send */ + while((LEUART_StatusGet(obj->serial.periph.leuart) & LEUART_STATUS_TXBL) && (obj->tx_buff.pos <= (obj->tx_buff.length - 1))) { + while (obj->serial.periph.leuart->SYNCBUSY); + LEUART_Tx(obj->serial.periph.leuart, buf[obj->tx_buff.pos]); + obj->tx_buff.pos++; + } + if(obj->tx_buff.pos >= obj->tx_buff.length){ + /* Last byte has been put in TX, set up TXC interrupt */ + LEUART_IntDisable(obj->serial.periph.leuart, LEUART_IEN_TXBL); + LEUART_IntEnable(obj->serial.periph.leuart, LEUART_IEN_TXC); + } + }else if (obj->serial.periph.leuart->IF & LEUART_IF_TXC){ + /* Last byte has been successfully transmitted. Stop the procedure */ + serial_tx_abort_asynch(obj); + return SERIAL_EVENT_TX_COMPLETE & obj->serial.events; + } + } else { + if(obj->serial.periph.uart->IEN & USART_IEN_TXBL){ + /* There is still data to send */ + while((USART_StatusGet(obj->serial.periph.uart) & USART_STATUS_TXBL) && (obj->tx_buff.pos <= (obj->tx_buff.length - 1))) { + USART_Tx(obj->serial.periph.uart, buf[obj->tx_buff.pos]); + obj->tx_buff.pos++; + } + if(obj->tx_buff.pos >= obj->tx_buff.length){ + /* Last byte has been put in TX, set up TXC interrupt */ + USART_IntDisable(obj->serial.periph.uart, USART_IEN_TXBL); + USART_IntEnable(obj->serial.periph.uart, USART_IEN_TXC); + } + } else if (obj->serial.periph.uart->IF & USART_IF_TXC) { + /* Last byte has been successfully transmitted. Stop the procedure */ + serial_tx_abort_asynch(obj); + return SERIAL_EVENT_TX_COMPLETE & obj->serial.events; + } + } + return 0; +} + +/** The asynchronous RX handler. Reads from the RX FIFOF and checks for events. + * If any RX event has occured, the RX abort function is called. + * + * @param obj The serial object + * @return Returns event flags if a RX transfer termination condition was met or 0 otherwise + */ +int serial_rx_irq_handler_asynch(serial_t *obj) +{ + int event = 0; + + /* This interrupt handler is called from USART irq */ + uint8_t *buf = (uint8_t*)obj->rx_buff.buffer; + + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + /* Determine the source of the interrupt */ + if(LEUART_IntGetEnabled(obj->serial.periph.leuart) & LEUART_IF_PERR) { + /* Parity error has occurred, and we are notifying. */ + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_PERR); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_PARITY_ERROR; + } + + if(LEUART_IntGetEnabled(obj->serial.periph.leuart) & LEUART_IF_FERR) { + /* Framing error has occurred, and we are notifying */ + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_FERR); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_FRAMING_ERROR; + } + + if(LEUART_IntGetEnabled(obj->serial.periph.leuart) & LEUART_IF_RXOF) { + /* RX buffer overflow has occurred, and we are notifying */ + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_RXOF); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_OVERFLOW; + } + + if((LEUART_IntGetEnabled(obj->serial.periph.leuart) & LEUART_IF_RXDATAV) || (LEUART_StatusGet(obj->serial.periph.leuart) & LEUART_STATUS_RXDATAV)) { + /* Valid data in buffer. Determine course of action: continue receiving or interrupt */ + if(obj->rx_buff.pos >= (obj->rx_buff.length - 1)) { + /* Last char, transfer complete. Switch off interrupt and return event. */ + buf[obj->rx_buff.pos] = LEUART_RxDataGet(obj->serial.periph.leuart); + + event |= SERIAL_EVENT_RX_COMPLETE; + + if((buf[obj->rx_buff.pos] == obj->char_match) && (obj->serial.events & SERIAL_EVENT_RX_CHARACTER_MATCH)) event |= SERIAL_EVENT_RX_CHARACTER_MATCH; + + serial_rx_abort_asynch(obj); + return event & obj->serial.events; + } else { + /* There's still space in the receive buffer */ + while((LEUART_StatusGet(obj->serial.periph.leuart) & LEUART_STATUS_RXDATAV) && (obj->rx_buff.pos <= (obj->rx_buff.length - 1))) { + bool aborting = false; + buf[obj->rx_buff.pos] = LEUART_RxDataGet(obj->serial.periph.leuart); + obj->rx_buff.pos++; + + /* Check for character match event */ + if((buf[obj->rx_buff.pos - 1] == obj->char_match) && (obj->serial.events & SERIAL_EVENT_RX_CHARACTER_MATCH)) { + aborting = true; + event |= SERIAL_EVENT_RX_CHARACTER_MATCH; + } + + /* Check for final char event */ + if(obj->rx_buff.pos >= (obj->rx_buff.length)) { + aborting = true; + event |= SERIAL_EVENT_RX_COMPLETE & obj->serial.events; + } + + if(aborting) { + serial_rx_abort_asynch(obj); + return event & obj->serial.events; + } + } + } + } + } else { + /* Determine the source of the interrupt */ + if(USART_IntGetEnabled(obj->serial.periph.uart) & USART_IF_PERR) { + /* Parity error has occurred, and we are notifying. */ + USART_IntClear(obj->serial.periph.uart, USART_IFC_PERR); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_PARITY_ERROR; + } + + if(USART_IntGetEnabled(obj->serial.periph.uart) & USART_IF_FERR) { + /* Framing error has occurred, and we are notifying */ + USART_IntClear(obj->serial.periph.uart, USART_IFC_FERR); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_FRAMING_ERROR; + } + + if(USART_IntGetEnabled(obj->serial.periph.uart) & USART_IF_RXOF) { + /* RX buffer overflow has occurred, and we are notifying */ + USART_IntClear(obj->serial.periph.uart, USART_IFC_RXOF); + serial_rx_abort_asynch(obj); + return SERIAL_EVENT_RX_OVERFLOW; + } + + if((USART_IntGetEnabled(obj->serial.periph.uart) & USART_IF_RXDATAV) || (USART_StatusGet(obj->serial.periph.uart) & USART_STATUS_RXFULL)) { + /* Valid data in buffer. Determine course of action: continue receiving or interrupt */ + if(obj->rx_buff.pos >= (obj->rx_buff.length - 1)) { + /* Last char, transfer complete. Switch off interrupt and return event. */ + buf[obj->rx_buff.pos] = USART_RxDataGet(obj->serial.periph.uart); + + event |= SERIAL_EVENT_RX_COMPLETE; + + if((buf[obj->rx_buff.pos] == obj->char_match) && (obj->serial.events & SERIAL_EVENT_RX_CHARACTER_MATCH)) event |= SERIAL_EVENT_RX_CHARACTER_MATCH; + + serial_rx_abort_asynch(obj); + return event & obj->serial.events; + } else { + /* There's still space in the receive buffer */ + while(((USART_StatusGet(obj->serial.periph.uart) & USART_STATUS_RXDATAV) || (USART_StatusGet(obj->serial.periph.uart) & USART_IF_RXFULL)) && (obj->rx_buff.pos <= (obj->rx_buff.length - 1))) { + bool aborting = false; + buf[obj->rx_buff.pos] = USART_RxDataGet(obj->serial.periph.uart); + obj->rx_buff.pos++; + + /* Check for character match event */ + if((buf[obj->rx_buff.pos - 1] == obj->char_match) && (obj->serial.events & SERIAL_EVENT_RX_CHARACTER_MATCH)) { + aborting = true; + event |= SERIAL_EVENT_RX_CHARACTER_MATCH; + } + + /* Check for final char event */ + if(obj->rx_buff.pos >= (obj->rx_buff.length)) { + aborting = true; + event |= SERIAL_EVENT_RX_COMPLETE & obj->serial.events; + } + + if(aborting) { + serial_rx_abort_asynch(obj); + return event & obj->serial.events; + } + } + } + } + } + + /* All events should have generated a return, if no return has happened, no event has been caught */ + return 0; +} + +/** Unified IRQ handler. Determines the appropriate handler to execute and returns the flags. + * + * WARNING: this code should be stateless, as re-entrancy is very possible in interrupt-based mode. + */ +int serial_irq_handler_asynch(serial_t *obj) +{ + /* First, check if we're running in DMA mode */ + if(serial_dma_irq_fired[obj->serial.dmaOptionsRX.dmaChannel]) { + /* Clean up */ + serial_dma_irq_fired[obj->serial.dmaOptionsRX.dmaChannel] = false; + serial_rx_abort_asynch(obj); + + /* Notify CPP land of RX completion */ + return SERIAL_EVENT_RX_COMPLETE & obj->serial.events; + } else if (serial_dma_irq_fired[obj->serial.dmaOptionsTX.dmaChannel]) { + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + if(obj->serial.periph.leuart->IEN & LEUART_IEN_TXC){ + LEUART_IntDisable(obj->serial.periph.leuart,LEUART_IEN_TXC); + /* Clean up */ + serial_dma_irq_fired[obj->serial.dmaOptionsTX.dmaChannel] = false; + serial_tx_abort_asynch(obj); + /* Notify CPP land of completion */ + return SERIAL_EVENT_TX_COMPLETE & obj->serial.events; + }else{ + LEUART_IntEnable(obj->serial.periph.leuart,LEUART_IEN_TXC); + } + }else{ + if(obj->serial.periph.uart->IEN & USART_IEN_TXC){ + USART_IntDisable(obj->serial.periph.leuart,USART_IEN_TXC); + /* Clean up */ + serial_dma_irq_fired[obj->serial.dmaOptionsTX.dmaChannel] = false; + serial_tx_abort_asynch(obj); + /* Notify CPP land of completion */ + return SERIAL_EVENT_TX_COMPLETE & obj->serial.events; + }else{ + USART_IntEnable(obj->serial.periph.leuart,USART_IEN_TXC); + } + } + } else { + /* Check the NVIC to see which interrupt we're running from + * Also make sure to prioritize RX */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + //Different method of checking tx vs rx for LEUART + if(LEUART_IntGetEnabled(obj->serial.periph.leuart) & (LEUART_IF_RXDATAV | LEUART_IF_FERR | LEUART_IF_PERR | LEUART_IF_RXOF)) { + return serial_rx_irq_handler_asynch(obj); + } else if(LEUART_StatusGet(obj->serial.periph.leuart) & (LEUART_STATUS_TXBL | LEUART_STATUS_TXC)) { + return serial_tx_irq_handler_asynch(obj); + } + } else { + if(USART_IntGetEnabled(obj->serial.periph.uart) & (USART_IF_RXDATAV | USART_IF_RXOF | USART_IF_PERR | USART_IF_FERR)) { + return serial_rx_irq_handler_asynch(obj); + } else if(USART_StatusGet(obj->serial.periph.uart) & (USART_STATUS_TXBL | USART_STATUS_TXC)){ + return serial_tx_irq_handler_asynch(obj); + } + } + } + + // All should be done now + return 0; +} + +/** Abort the ongoing TX transaction. It disables the enabled interupt for TX and + * flush TX hardware buffer if TX FIFO is used + * + * @param obj The serial object + */ +void serial_tx_abort_asynch(serial_t *obj) +{ + /* Stop transmitter */ + //obj->serial.periph.uart->CMD |= USART_CMD_TXDIS; + + /* Clean up */ + switch(obj->serial.dmaOptionsTX.dmaUsageState) { + case DMA_USAGE_ALLOCATED: + /* stop DMA transfer */ + DMA_ChannelEnable(obj->serial.dmaOptionsTX.dmaChannel, false); + break; + case DMA_USAGE_TEMPORARY_ALLOCATED: + /* stop DMA transfer and release channel */ + DMA_ChannelEnable(obj->serial.dmaOptionsTX.dmaChannel, false); + dma_channel_free(obj->serial.dmaOptionsTX.dmaChannel); + obj->serial.dmaOptionsTX.dmaChannel = -1; + obj->serial.dmaOptionsTX.dmaUsageState = DMA_USAGE_OPPORTUNISTIC; + break; + default: + /* stop interrupting */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_IntDisable(obj->serial.periph.leuart, LEUART_IEN_TXC); + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_TXC); + } else { + USART_IntDisable(obj->serial.periph.uart, USART_IEN_TXC); + USART_IntClear(obj->serial.periph.uart, USART_IFC_TXC); + } + break; + } + + /* Say that we can stop using this emode */ +#ifdef LEUART_USING_LFXO + if(LEUART_REF_VALID(obj->serial.periph.leuart) && (LEUART_BaudrateGet(obj->serial.periph.leuart) <= (LEUART_LF_REF_FREQ/2))){ + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE_LEUART); + }else{ + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); + } +#else + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); +#endif +} + +/** Abort the ongoing RX transaction It disables the enabled interrupt for RX and + * flush RX hardware buffer if RX FIFO is used + * + * @param obj The serial object + */ +void serial_rx_abort_asynch(serial_t *obj) +{ + /* Stop receiver */ + obj->serial.periph.uart->CMD |= USART_CMD_RXDIS; + + /* Clean up */ + switch(obj->serial.dmaOptionsRX.dmaUsageState) { + case DMA_USAGE_ALLOCATED: + /* stop DMA transfer */ + DMA_ChannelEnable(obj->serial.dmaOptionsRX.dmaChannel, false); + break; + case DMA_USAGE_TEMPORARY_ALLOCATED: + /* stop DMA transfer and release channel */ + DMA_ChannelEnable(obj->serial.dmaOptionsRX.dmaChannel, false); + dma_channel_free(obj->serial.dmaOptionsRX.dmaChannel); + obj->serial.dmaOptionsRX.dmaChannel = -1; + obj->serial.dmaOptionsRX.dmaUsageState = DMA_USAGE_OPPORTUNISTIC; + break; + default: + /* stop interrupting */ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_IntDisable(obj->serial.periph.leuart, LEUART_IEN_RXDATAV | LEUART_IEN_PERR | LEUART_IEN_FERR | LEUART_IEN_RXOF); + } else { + USART_IntDisable(obj->serial.periph.uart, USART_IEN_RXDATAV | USART_IEN_PERR | USART_IEN_FERR | USART_IEN_RXOF); + } + break; + } + + /*clear all set interrupts*/ + if(LEUART_REF_VALID(obj->serial.periph.leuart)) { + LEUART_IntClear(obj->serial.periph.leuart, LEUART_IFC_PERR | LEUART_IFC_FERR | LEUART_IFC_RXOF); + }else{ + USART_IntClear(obj->serial.periph.uart, USART_IFC_PERR | USART_IFC_FERR | USART_IFC_RXOF); + } + + /* Say that we can stop using this emode */ +#ifdef LEUART_USING_LFXO + if(LEUART_REF_VALID(obj->serial.periph.leuart) && (LEUART_BaudrateGet(obj->serial.periph.leuart) <= (LEUART_LF_REF_FREQ/2))){ + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE_LEUART); + }else{ + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); + } +#else + unblockSleepMode(SERIAL_LEAST_ACTIVE_SLEEPMODE); +#endif +} + +#endif //DEVICE_SERIAL_ASYNCH +#endif //DEVICE_SERIAL