/***************************************************************************//** * @file * @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART) * Peripheral API ******************************************************************************* * # License * Copyright 2018 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * SPDX-License-Identifier: Zlib * * The licensor of this software is Silicon Laboratories Inc. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * 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. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 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. * ******************************************************************************/ #include "em_leuart.h" #if defined(LEUART_COUNT) && (LEUART_COUNT > 0) #include "em_cmu.h" #include "sl_assert.h" /***************************************************************************//** * @addtogroup leuart LEUART - Low Energy UART * @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART) * Peripheral API * @details * This module contains functions to control the LEUART peripheral of Silicon * Labs 32-bit MCUs and SoCs. The LEUART module provides the full UART communication using * a low frequency 32.768 kHz clock and has special features for communication * without the CPU intervention. * @{ ******************************************************************************/ /******************************************************************************* ******************************* DEFINES *********************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /** A validation of the LEUART register block pointer reference * for assert statements. */ #if (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 /** @endcond */ /******************************************************************************* ************************** LOCAL FUNCTIONS ******************************** ******************************************************************************/ /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ /***************************************************************************//** * @brief * Wait for ongoing sync of register(s) to the low-frequency domain to complete. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] mask * A bitmask corresponding to SYNCBUSY register defined bits, indicating * registers that must complete any ongoing synchronization. ******************************************************************************/ __STATIC_INLINE void LEUART_Sync(LEUART_TypeDef *leuart, uint32_t mask) { /* Avoid deadlock if modifying the same register twice when freeze mode is */ /* activated. */ if (leuart->FREEZE & LEUART_FREEZE_REGFREEZE) { return; } /* Wait for any pending previous write operation to have been completed */ /* in the low-frequency domai. */ while ((leuart->SYNCBUSY & mask) != 0U) { } } /** @endcond */ /******************************************************************************* ************************** GLOBAL FUNCTIONS ******************************* ******************************************************************************/ /***************************************************************************//** * @brief * Calculate the baudrate for the LEUART given reference frequency and clock division. * * @details * This function returns the baudrate that a LEUART module will use if * configured with the given frequency and clock divisor. Notice that * this function will not use the hardware configuration. It can be used * to determine if a given configuration is sufficiently accurate for the * application. * * @param[in] refFreq * The LEUART peripheral frequency used. * * @param[in] clkdiv * The clock division factor to be used. * * @return * A baudrate with given settings. ******************************************************************************/ uint32_t LEUART_BaudrateCalc(uint32_t refFreq, uint32_t clkdiv) { uint32_t divisor; uint32_t remainder; uint32_t quotient; uint32_t br; /* Mask out unused bits. */ clkdiv &= _LEUART_CLKDIV_MASK; /* Use integer division to avoid forcing in float division */ /* utils, and yet keep rounding effect errors to a minimum. */ /* * Baudrate is given by: * * br = fLEUARTn/(1 + (CLKDIV / 256)) * * which can be rewritten to * * br = (256 * fLEUARTn)/(256 + CLKDIV) * * Normally, with fLEUARTn appr 32768 Hz, there is no problem with overflow * if using 32 bit arithmetic. However, since fLEUARTn may be derived from * HFCORECLK, consider the overflow when using integer arithmetic. */ /* * The basic problem with integer division in the above formula is that * the dividend (256 * fLEUARTn) may become higher than the maximum 32 bit * integer. Yet we want to evaluate the dividend first before dividing * to get as small rounding effects as possible. * Also, harsh restrictions should be avoided on the maximum fLEUARTn value. * * For division a/b: * * a = qb + r * * where q is the quotient and r is the remainder, both integers. * * The orignal baudrate formula can be rewritten as: * * br = 256a / b = 256(qb + r)/b = 256q + 256r/b * * where a is 'refFreq' and b is 'divisor', referring to variable names. */ divisor = 256 + clkdiv; quotient = refFreq / divisor; remainder = refFreq % divisor; /* Since the divisor >= 256, the below cannot exceed the maximum 32 bit value. */ br = 256 * quotient; /* * A remainder < (256 + clkdiv), which means the dividend (256 * remainder) worst case is * 256*(256 + 0x7ff8) = 0x80F800. */ br += (256 * remainder) / divisor; return br; } /***************************************************************************//** * @brief * Get the current baudrate for LEUART. * * @details * This function returns the actual baudrate (not considering the oscillator * inaccuracies) used by the LEUART peripheral. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @return * The current baudrate. ******************************************************************************/ uint32_t LEUART_BaudrateGet(LEUART_TypeDef *leuart) { uint32_t freq; CMU_Clock_TypeDef clock; /* Get the current frequency. */ if (leuart == LEUART0) { clock = cmuClock_LEUART0; } #if (LEUART_COUNT > 1) else if (leuart == LEUART1) { clock = cmuClock_LEUART1; } #endif else { EFM_ASSERT(0); return 0; } freq = CMU_ClockFreqGet(clock); return LEUART_BaudrateCalc(freq, leuart->CLKDIV); } /***************************************************************************//** * @brief * Configure the baudrate (or as close as possible to a specified baudrate). * * @note * The baudrate setting requires synchronization into the * low-frequency domain. If the same register is modified before a previous * update has completed, this function will stall until the previous * synchronization has completed. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] refFreq * The LEUART reference clock frequency in Hz that will be used. If set to 0, * the currently configured reference clock is assumed. * * @param[in] baudrate * A baudrate to try to achieve for LEUART. ******************************************************************************/ void LEUART_BaudrateSet(LEUART_TypeDef *leuart, uint32_t refFreq, uint32_t baudrate) { uint32_t clkdiv; CMU_Clock_TypeDef clock; /* Prevent dividing by 0. */ EFM_ASSERT(baudrate); /* * Use integer division to avoid forcing in float division * utils, and yet keep rounding effect errors to a minimum. * * CLKDIV in asynchronous mode is given by: * * CLKDIV = 256*(fLEUARTn/br - 1) = ((256*fLEUARTn)/br) - 256 * * Normally, with fLEUARTn appr 32768 Hz, there is no problem with overflow * if using 32 bit arithmetic. However, since fLEUARTn may be derived from * HFCORECLK, consider the overflow when using integer arithmetic. * * The basic problem with integer division in the above formula is that * the dividend (256 * fLEUARTn) may become higher than the maximum 32 bit * integer. Yet, the dividend should be evaluated first before dividing * to get as small rounding effects as possible. * Also, harsh restrictions on the maximum fLEUARTn value should not be made. * * Since the last 3 bits of CLKDIV are don't care, base the * integer arithmetic on the below formula: * * CLKDIV/8 = ((32*fLEUARTn)/br) - 32 * * and calculate 1/8 of CLKDIV first. This allows for fLEUARTn * up to 128 MHz without overflowing a 32 bit value. */ /* Get the current frequency. */ if (!refFreq) { if (leuart == LEUART0) { clock = cmuClock_LEUART0; } #if (LEUART_COUNT > 1) else if (leuart == LEUART1) { clock = cmuClock_LEUART1; } #endif else { EFM_ASSERT(0); return; } refFreq = CMU_ClockFreqGet(clock); } /* Calculate and set the CLKDIV with fractional bits. */ clkdiv = (32 * refFreq) / baudrate; clkdiv -= 32; clkdiv *= 8; /* Verify that the resulting clock divider is within limits. */ EFM_ASSERT(clkdiv <= _LEUART_CLKDIV_MASK); /* If the EFM_ASSERT is not enabled, make sure not to write to reserved bits. */ clkdiv &= _LEUART_CLKDIV_MASK; /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_CLKDIV); leuart->CLKDIV = clkdiv; } /***************************************************************************//** * @brief * Enable/disable the LEUART receiver and/or transmitter. * * @details * Notice that this function does not do any configuration. Enabling should * normally be done after the initialization is done (if not enabled as part * of initialization). * * @note * Enabling/disabling requires synchronization into the low-frequency domain. * If the same register is modified before a previous update has completed, * this function will stall until the previous synchronization has completed. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] enable * Select status for receiver/transmitter. ******************************************************************************/ void LEUART_Enable(LEUART_TypeDef *leuart, LEUART_Enable_TypeDef enable) { uint32_t tmp; /* Make sure that the module exists on the selected chip. */ EFM_ASSERT(LEUART_REF_VALID(leuart)); /* Disable as specified. */ tmp = ~((uint32_t)(enable)); tmp &= (_LEUART_CMD_RXEN_MASK | _LEUART_CMD_TXEN_MASK); tmp <<= 1; /* Enable as specified. */ tmp |= (uint32_t)(enable); /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD); leuart->CMD = tmp; } /***************************************************************************//** * @brief * LEUART register synchronization freeze control. * * @details * Some LEUART registers require synchronization into the low-frequency (LF) * domain. The freeze feature allows for several such registers to be * modified before passing them to the LF domain simultaneously (which * takes place when the freeze mode is disabled). * * @note * When enabling freeze mode, this function will wait for all current * ongoing LEUART synchronization to the LF domain to complete (Normally * synchronization will not be in progress.) However, for this reason, when * using freeze mode, modifications of registers requiring LF synchronization * should be done within one freeze enable/disable block to avoid unnecessary * stalling. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] enable * @li True - enable freeze, modified registers are not propagated to the * LF domain * @li False - disables freeze, modified registers are propagated to the LF * domain ******************************************************************************/ void LEUART_FreezeEnable(LEUART_TypeDef *leuart, bool enable) { if (enable) { /* * Wait for any ongoing LF synchronization to complete to * protect against the rare case when a user * - modifies a register requiring LF sync * - then enables freeze before LF sync completed * - then modifies the same register again * since modifying a register while it is in sync progress should be * avoided. */ while (leuart->SYNCBUSY != 0U) { } leuart->FREEZE = LEUART_FREEZE_REGFREEZE; } else { leuart->FREEZE = 0; } } /***************************************************************************//** * @brief * Initialize LEUART. * * @details * This function will configure basic settings to operate in normal * asynchronous mode. Consider using LEUART_Reset() prior to this function if * the state of configuration is not known, since only configuration settings * specified by @p init are set. * * Special control setup not covered by this function may be done either * before or after using this function (but normally before enabling) * by direct modification of the CTRL register. * * Notice that pins used by the LEUART module must be properly configured * by the user explicitly for the LEUART to work as intended. * (When configuring pins consider the sequence of * configuration to avoid unintended pulses/glitches on output * pins.) * * @note * Initializing requires synchronization into the low-frequency domain. * If the same register is modified before a previous update has completed, * this function will stall until the previous synchronization has completed. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] init * A pointer to the initialization structure used to configure basic async setup. ******************************************************************************/ void LEUART_Init(LEUART_TypeDef *leuart, LEUART_Init_TypeDef const *init) { /* Make sure the module exists on the selected chip. */ EFM_ASSERT(LEUART_REF_VALID(leuart)); /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD); /* Ensure disabled while configuring. */ leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS; /* Freeze registers to avoid stalling for the LF synchronization. */ LEUART_FreezeEnable(leuart, true); /* Configure databits and stopbits. */ leuart->CTRL = (leuart->CTRL & ~(_LEUART_CTRL_PARITY_MASK | _LEUART_CTRL_STOPBITS_MASK)) | (uint32_t)(init->databits) | (uint32_t)(init->parity) | (uint32_t)(init->stopbits); /* Configure the baudrate. */ LEUART_BaudrateSet(leuart, init->refFreq, init->baudrate); /* Finally enable (as specified). */ leuart->CMD = (uint32_t)init->enable; /* Unfreeze registers and pass new settings on to LEUART. */ LEUART_FreezeEnable(leuart, false); } /***************************************************************************//** * @brief * Reset LEUART to the same state that it was in after a hardware reset. * * @param[in] leuart * A pointer to the LEUART peripheral register block. ******************************************************************************/ void LEUART_Reset(LEUART_TypeDef *leuart) { /* Make sure the module exists on the selected chip. */ EFM_ASSERT(LEUART_REF_VALID(leuart)); /* Freeze registers to avoid stalling for LF synchronization. */ LEUART_FreezeEnable(leuart, true); /* Make sure disabled first, before resetting other registers. */ leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS | LEUART_CMD_RXBLOCKDIS | LEUART_CMD_CLEARTX | LEUART_CMD_CLEARRX; leuart->CTRL = _LEUART_CTRL_RESETVALUE; leuart->CLKDIV = _LEUART_CLKDIV_RESETVALUE; leuart->STARTFRAME = _LEUART_STARTFRAME_RESETVALUE; leuart->SIGFRAME = _LEUART_SIGFRAME_RESETVALUE; leuart->IEN = _LEUART_IEN_RESETVALUE; leuart->IFC = _LEUART_IFC_MASK; leuart->PULSECTRL = _LEUART_PULSECTRL_RESETVALUE; #if defined(_LEUART_ROUTEPEN_MASK) leuart->ROUTEPEN = _LEUART_ROUTEPEN_RESETVALUE; leuart->ROUTELOC0 = _LEUART_ROUTELOC0_RESETVALUE; #else leuart->ROUTE = _LEUART_ROUTE_RESETVALUE; #endif /* Unfreeze registers and pass new settings on to LEUART. */ LEUART_FreezeEnable(leuart, false); } /***************************************************************************//** * @brief * Receive one 8 bit frame, (or part of 9 bit frame). * * @details * This function is normally used to receive one frame when operating with * frame length 8 bits. See LEUART_RxExt() for reception of * 9 bit frames. * * Notice that possible parity/stop bits are not considered a part of the specified * frame bit length. * * @note * This function will stall if the buffer is empty until data is received. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @return * Data received. ******************************************************************************/ uint8_t LEUART_Rx(LEUART_TypeDef *leuart) { while (!(leuart->STATUS & LEUART_STATUS_RXDATAV)) { } return (uint8_t)leuart->RXDATA; } /***************************************************************************//** * @brief * Receive one 8-9 bit frame with extended information. * * @details * This function is normally used to receive one frame and additional RX * status information is required. * * @note * This function will stall if buffer is empty until data is received. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @return * Data received. ******************************************************************************/ uint16_t LEUART_RxExt(LEUART_TypeDef *leuart) { while (!(leuart->STATUS & LEUART_STATUS_RXDATAV)) { } return (uint16_t)leuart->RXDATAX; } /***************************************************************************//** * @brief * Transmit one frame. * * @details * Depending on the frame length configuration, 8 (least significant) bits from * @p data are transmitted. If the frame length is 9, 8 bits are transmitted from * @p data and one bit as specified by the CTRL register, BIT8DV field. * See LEUART_TxExt() for transmitting 9 bit frame with full control of * all 9 bits. * * Notice that possible parity/stop bits in asynchronous mode are not * considered a part of the specified frame bit length. * * @note * This function will stall if buffer is full until the buffer becomes available. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] data * Data to transmit. See details above for more info. ******************************************************************************/ void LEUART_Tx(LEUART_TypeDef *leuart, uint8_t data) { /* Check that transmit buffer is empty. */ while (!(leuart->STATUS & LEUART_STATUS_TXBL)) { } /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATA); leuart->TXDATA = (uint32_t)data; } /***************************************************************************//** * @brief * Transmit one 8-9 bit frame with extended control. * * @details * Notice that possible parity/stop bits in asynchronous mode are not * considered a part of the specified frame bit length. * * @note * This function will stall if the buffer is full until the buffer becomes available. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] data * Data to transmit with extended control. Least significant bit contains * frame bits and additional control bits are available as documented in * the reference manual (set to 0 if not used). ******************************************************************************/ void LEUART_TxExt(LEUART_TypeDef *leuart, uint16_t data) { /* Check that transmit buffer is empty. */ while (!(leuart->STATUS & LEUART_STATUS_TXBL)) { } /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATAX); leuart->TXDATAX = (uint32_t)data; } /***************************************************************************//** * @brief * Enables handling of LEUART TX by DMA in EM2. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] enable * True - enables functionality * False - disables functionality * ******************************************************************************/ void LEUART_TxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable) { /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL | LEUART_SYNCBUSY_CMD); #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) /* LEUART_E201: Changing the value of TXDMAWU while TXEN=1 could potentially * cause unpredictable behavior. */ bool txEnabled = (leuart->STATUS & _LEUART_STATUS_TXENS_MASK) != 0U; if (txEnabled) { /* Wait for potential transmit to complete. */ while ((leuart->STATUS & LEUART_STATUS_TXIDLE) == 0U) { } leuart->CMD = LEUART_CMD_TXDIS; LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD); } if (enable) { leuart->CTRL |= LEUART_CTRL_TXDMAWU; } else { leuart->CTRL &= ~LEUART_CTRL_TXDMAWU; } if (txEnabled) { leuart->CMD = LEUART_CMD_TXEN; } #else if (enable) { leuart->CTRL |= LEUART_CTRL_TXDMAWU; } else { leuart->CTRL &= ~LEUART_CTRL_TXDMAWU; } #endif } /***************************************************************************//** * @brief * Enables handling of LEUART RX by DMA in EM2. * * @param[in] leuart * A pointer to the LEUART peripheral register block. * * @param[in] enable * True - enables functionality * False - disables functionality * ******************************************************************************/ void LEUART_RxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable) { /* LF register about to be modified requires sync; busy check. */ LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL | LEUART_SYNCBUSY_CMD); #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) /* LEUART_E201: Changing the value of RXDMAWU while RXEN=1 could potentially * cause unpredictable behavior. */ bool rxEnabled = (leuart->STATUS & _LEUART_STATUS_RXENS_MASK) != 0U; if (rxEnabled) { leuart->CMD = LEUART_CMD_RXDIS; LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD); } if (enable) { leuart->CTRL |= LEUART_CTRL_RXDMAWU; } else { leuart->CTRL &= ~LEUART_CTRL_RXDMAWU; } if (rxEnabled) { leuart->CMD = LEUART_CMD_RXEN; } #else if (enable) { leuart->CTRL |= LEUART_CTRL_RXDMAWU; } else { leuart->CTRL &= ~LEUART_CTRL_RXDMAWU; } #endif } /** @} (end addtogroup leuart) */ #endif /* defined(LEUART_COUNT) && (LEUART_COUNT > 0) */