blob: 638bf9e8b4e6f3dec88268ce185767d2c8064061 [file] [log] [blame]
/*
* Copyright (c) 2020, The OpenThread Authors.
* 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.
*/
/**
* @file
* This file implements the OpenThread platform abstraction for UART communication.
*
*/
#include <stddef.h>
#include <string.h>
#include "openthread-system.h"
#include <openthread-core-config.h>
#include "utils/code_utils.h"
#include "utils/uart.h"
#include "ecode.h"
#include "em_core.h"
#include "sl_sleeptimer.h"
#include "sl_status.h"
#include "uartdrv.h"
#include "hal-config.h"
#include "platform-efr32.h"
#include "sl_uartdrv_usart_vcom_config.h"
#define HELPER1(x) USART##x##_RX_IRQn
#define HELPER2(x) HELPER1(x)
#define USART_IRQ HELPER2(SL_UARTDRV_USART_VCOM_PERIPHERAL_NO)
#define HELPER3(x) USART##x##_RX_IRQHandler
#define HELPER4(x) HELPER3(x)
#define USART_IRQHandler HELPER4(SL_UARTDRV_USART_VCOM_PERIPHERAL_NO)
enum
{
kReceiveFifoSize = 128,
};
DEFINE_BUF_QUEUE(EMDRV_UARTDRV_MAX_CONCURRENT_RX_BUFS, sUartRxQueue);
DEFINE_BUF_QUEUE(EMDRV_UARTDRV_MAX_CONCURRENT_TX_BUFS, sUartTxQueue);
static const UARTDRV_InitUart_t USART_INIT = {
.port = USART0, /* USART port */
.baudRate = HAL_SERIAL_APP_BAUD_RATE, /* Baud rate */
#if defined(_USART_ROUTELOC0_MASK)
.portLocationTx = BSP_SERIAL_APP_TX_LOC, /* USART Tx pin location number */
.portLocationRx = BSP_SERIAL_APP_RX_LOC, /* USART Rx pin location number */
#elif defined(_USART_ROUTE_MASK)
#error This configuration is not supported
// .portLocation = NULL;
#elif defined(_GPIO_USART_ROUTEEN_MASK)
.txPort = BSP_SERIAL_APP_TX_PORT, /* USART Tx port number */
.rxPort = BSP_SERIAL_APP_RX_PORT, /* USART Rx port number */
.txPin = BSP_SERIAL_APP_TX_PIN, /* USART Tx pin number */
.rxPin = BSP_SERIAL_APP_RX_PIN, /* USART Rx pin number */
.uartNum = 0, /* UART instance number */
#endif
.stopBits = (USART_Stopbits_TypeDef)USART_FRAME_STOPBITS_ONE, /* Stop bits */
.parity = (USART_Parity_TypeDef)USART_FRAME_PARITY_NONE, /* Parity */
.oversampling = (USART_OVS_TypeDef)USART_CTRL_OVS_X16, /* Oversampling mode*/
#if defined(USART_CTRL_MVDIS)
.mvdis = false, /* Majority vote disable */
#endif // USART_CTRL_MVDIS
.fcType = HAL_SERIAL_APP_FLOW_CONTROL, /* Flow control */
.ctsPort = BSP_SERIAL_APP_CTS_PORT, /* CTS port number */
.ctsPin = BSP_SERIAL_APP_CTS_PIN, /* CTS pin number */
.rtsPort = BSP_SERIAL_APP_RTS_PORT, /* RTS port number */
.rtsPin = BSP_SERIAL_APP_RTS_PIN, /* RTS pin number */
.rxQueue = (UARTDRV_Buffer_FifoQueue_t *)&sUartRxQueue, /* RX operation queue */
.txQueue = (UARTDRV_Buffer_FifoQueue_t *)&sUartTxQueue, /* TX operation queue */
#if defined(_USART_ROUTELOC1_MASK)
.portLocationCts = BSP_SERIAL_APP_CTS_LOC, /* CTS location */
.portLocationRts = BSP_SERIAL_APP_RTS_LOC /* RTS location */
#endif // _USART_ROUTELOC1_MASK
};
static UARTDRV_HandleData_t sUartHandleData;
static UARTDRV_Handle_t sUartHandle = &sUartHandleData;
// In order to reduce the probability of data loss due to disabled interrupts, we use
// two duplicate receive buffers so we can always have one "active" receive request.
#define RECEIVE_BUFFER_SIZE 128
static uint8_t sReceiveBuffer1[RECEIVE_BUFFER_SIZE];
static uint8_t sReceiveBuffer2[RECEIVE_BUFFER_SIZE];
static uint8_t lastCount = 0;
static volatile bool sCheckForTxComplete = false;
typedef struct ReceiveFifo_t
{
// The data buffer
uint8_t mBuffer[kReceiveFifoSize];
// The offset of the first item written to the list.
volatile uint16_t mHead;
// The offset of the next item to be written to the list.
volatile uint16_t mTail;
} ReceiveFifo_t;
static ReceiveFifo_t sReceiveFifo;
static void processReceive(void);
static void processTransmit(void);
static void receiveDone(UARTDRV_Handle_t aHandle, Ecode_t aStatus, uint8_t *aData, UARTDRV_Count_t aCount)
{
OT_UNUSED_VARIABLE(aStatus);
// We can only write if incrementing mTail doesn't equal mHead
if (sReceiveFifo.mHead != (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize)
{
memcpy(sReceiveFifo.mBuffer + sReceiveFifo.mTail, aData + lastCount, aCount - lastCount);
sReceiveFifo.mTail = (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize;
lastCount = 0;
}
UARTDRV_Receive(aHandle, aData, aCount, receiveDone);
otSysEventSignalPending();
}
static void transmitDone(UARTDRV_Handle_t aHandle, Ecode_t aStatus, uint8_t *aData, UARTDRV_Count_t aCount)
{
OT_UNUSED_VARIABLE(aHandle);
OT_UNUSED_VARIABLE(aStatus);
OT_UNUSED_VARIABLE(aData);
OT_UNUSED_VARIABLE(aCount);
// This value will be used later in processTransmit() to call otPlatUartSendDone()
sCheckForTxComplete = true;
otSysEventSignalPending();
}
static void processReceive(void)
{
uint8_t * aData;
UARTDRV_Count_t aCount, remaining;
CORE_ATOMIC_SECTION(UARTDRV_GetReceiveStatus(sUartHandle, &aData, &aCount, &remaining);
if (aCount > lastCount) {
memcpy(sReceiveFifo.mBuffer + sReceiveFifo.mTail, aData + lastCount, aCount - lastCount);
sReceiveFifo.mTail = (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize;
lastCount = aCount;
})
// Copy tail to prevent multiple reads
uint16_t tail = sReceiveFifo.mTail;
// If the data wraps around, process the first part
if (sReceiveFifo.mHead > tail)
{
otPlatUartReceived(sReceiveFifo.mBuffer + sReceiveFifo.mHead, kReceiveFifoSize - sReceiveFifo.mHead);
// Reset the buffer mHead back to zero.
sReceiveFifo.mHead = 0;
}
// For any data remaining, process it
if (sReceiveFifo.mHead != tail)
{
otPlatUartReceived(sReceiveFifo.mBuffer + sReceiveFifo.mHead, tail - sReceiveFifo.mHead);
// Set mHead to the local tail we have cached
sReceiveFifo.mHead = tail;
}
}
static void flushTimeoutAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData)
{
OT_UNUSED_VARIABLE(aHandle);
OT_UNUSED_VARIABLE(aData);
bool *flushTimedOut = (bool *)aData;
*flushTimedOut = true;
}
otError otPlatUartFlush(void)
{
otError error = OT_ERROR_NONE;
sl_status_t status = SL_STATUS_OK;
volatile bool flushTimedOut = false;
sl_sleeptimer_timer_handle_t flushTimer;
// Start flush timeout timer
status = sl_sleeptimer_start_timer_ms(&flushTimer, OPENTHREAD_CONFIG_EFR32_UART_TX_FLUSH_TIMEOUT_MS,
flushTimeoutAlarmCallback, NULL, 0,
SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
otEXPECT_ACTION(status == SL_STATUS_OK, error = OT_ERROR_FAILED);
// Block until DMA has finished transmitting every buffer in sUartTxQueue and becomes idle
uint8_t transmitQueueDepth = 0;
bool uartFullyFlushed = false;
bool uartIdle = false;
do
{
// Check both peripheral status and queue depth
transmitQueueDepth = UARTDRV_GetTransmitDepth(sUartHandle);
uartIdle = UARTDRV_GetPeripheralStatus(sUartHandle) & (UARTDRV_STATUS_TXIDLE | UARTDRV_STATUS_TXC);
uartFullyFlushed = uartIdle && (transmitQueueDepth == 0);
} while (!uartFullyFlushed && !flushTimedOut);
sl_sleeptimer_stop_timer(&flushTimer);
if (flushTimedOut)
{
// Abort all transmits
UARTDRV_Abort(sUartHandle, uartdrvAbortTransmit);
}
exit:
return error;
}
static void processTransmit(void)
{
// NOTE: This check needs to be done in here and cannot be done in transmitDone because the transmit may not be
// fully complete when the transmitDone callback is called.
if (!sCheckForTxComplete)
{
return;
}
bool queueEmpty = UARTDRV_GetPeripheralStatus(sUartHandle) & (UARTDRV_STATUS_TXIDLE | UARTDRV_STATUS_TXC);
uint8_t transmitQueueDepth = UARTDRV_GetTransmitDepth(sUartHandle);
if (transmitQueueDepth == 0 && queueEmpty)
{
sCheckForTxComplete = false;
otPlatUartSendDone();
}
}
void USART_IRQHandler(void)
{
otSysEventSignalPending();
}
otError otPlatUartEnable(void)
{
UARTDRV_InitUart_t uartInit = USART_INIT;
sReceiveFifo.mHead = 0;
sReceiveFifo.mTail = 0;
UARTDRV_Init(sUartHandle, &uartInit);
// When one receive request is completed, the other buffer is used for a separate receive request, issued
// immediately.
UARTDRV_Receive(sUartHandle, sReceiveBuffer1, RECEIVE_BUFFER_SIZE, receiveDone);
UARTDRV_Receive(sUartHandle, sReceiveBuffer2, RECEIVE_BUFFER_SIZE, receiveDone);
// Enable USART0 interrupt to wake OT task when data arrives
NVIC_ClearPendingIRQ(USART_IRQ);
NVIC_EnableIRQ(USART_IRQ);
USART_IntEnable(SL_UARTDRV_USART_VCOM_PERIPHERAL, USART_IF_RXDATAV);
return OT_ERROR_NONE;
}
otError otPlatUartDisable(void)
{
return OT_ERROR_NOT_IMPLEMENTED;
}
otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
{
otError error = OT_ERROR_NONE;
Ecode_t status = ECODE_EMDRV_UARTDRV_OK;
// Ensure that no ongoing transmits have started finishing.
// This prevents queued buffers from being modified before they are transmitted
if (sCheckForTxComplete)
{
otPlatUartFlush();
}
status = UARTDRV_Transmit(sUartHandle, (uint8_t *)aBuf, aBufLength, transmitDone);
otEXPECT_ACTION(status == ECODE_EMDRV_UARTDRV_OK, error = OT_ERROR_FAILED);
exit:
return error;
}
void efr32UartProcess(void)
{
processReceive();
processTransmit();
}