diff --git a/efm32/src/app.h b/efm32/src/app.h index 2824af0..528e882 100644 --- a/efm32/src/app.h +++ b/efm32/src/app.h @@ -8,6 +8,8 @@ #ifndef SRC_APP_H_ #define SRC_APP_H_ + + #define PRINTING_USE_VCOM #define USING_DEV_BOARD diff --git a/efm32/src/crypto.c b/efm32/src/crypto.c index 8bab5f0..3d1eb7e 100644 --- a/efm32/src/crypto.c +++ b/efm32/src/crypto.c @@ -311,13 +311,13 @@ void crypto_ecdsa_sign(uint8_t * data, int len, uint8_t * sig, int MBEDTLS_ECP_I if ( uECC_sign(_signing_key, data, len, sig, curve) == 0) { - printf("error, uECC failed\n"); + printf2(TAG_ERR,"error, uECC failed\n"); exit(1); } return; fail: - printf("error, invalid key length\n"); + printf2(TAG_ERR,"error, invalid key length: %d\n", _key_len); exit(1); } diff --git a/efm32/src/device.c b/efm32/src/device.c index d1c0268..603b44b 100644 --- a/efm32/src/device.c +++ b/efm32/src/device.c @@ -303,11 +303,52 @@ int authenticator_is_backup_initialized() uint8_t adc_rng(void); +void bootloader_init(void) +{ + /* Chip errata */ + + + + // status LEDS + GPIO_PinModeSet(gpioPortF, + 4, + gpioModePushPull, + 0); + + GPIO_PinModeSet(gpioPortF, + 5, + gpioModePushPull, + 1); + + // EFM8 RDY/BUSY + GPIO_PinModeSet(RDY_PIN, gpioModeInput, 0); + + // EFM8 MSG Available + GPIO_PinModeSet(MSG_AVAIL_PIN, gpioModeInput, 0); + + // SPI R/w Indicator + GPIO_PinModeSet(RW_PIN, gpioModePushPull, 1); + + // USB message rdy ext int + // GPIO_ExtIntConfig(gpioPortC, 9, 9, 1, 0,1); + // NVIC_EnableIRQ(GPIO_ODD_IRQn); + + + printing_init(); + + + MSC_Init(); + + + +} + void device_init(void) { /* Chip errata */ + CHIP_Init(); enter_DefaultMode_from_RESET(); @@ -341,6 +382,7 @@ void device_init(void) init_adc(); MSC_Init(); + init_atomic_counter(); if (sizeof(AuthenticatorState) > PAGE_SIZE) { @@ -360,4 +402,6 @@ void device_init(void) buf[i] = adc_rng(); } dump_hex(buf,sizeof(buf)); + + } diff --git a/efm32boot/.cproject b/efm32boot/.cproject new file mode 100644 index 0000000..41587a3 --- /dev/null +++ b/efm32boot/.cproject @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/efm32boot/.project b/efm32boot/.project new file mode 100644 index 0000000..6753d30 --- /dev/null +++ b/efm32boot/.project @@ -0,0 +1,37 @@ + + + efm32boot + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + + org.eclipse.cdt.core.cnature + com.silabs.ss.framework.ide.project.sls.core.SLSProjectNature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + + + + crypto + 2 + $%7BPARENT-1-PROJECT_LOC%7D/crypto + + + efm32 + 2 + $%7BPARENT-1-PROJECT_LOC%7D/efm32 + + + fido2 + 2 + $%7BPARENT-1-PROJECT_LOC%7D/fido2 + + + diff --git a/efm32boot/.settings/com.silabs.ss.framework.ide.project.sls.core.prefs b/efm32boot/.settings/com.silabs.ss.framework.ide.project.sls.core.prefs new file mode 100644 index 0000000..b4554b4 --- /dev/null +++ b/efm32boot/.settings/com.silabs.ss.framework.ide.project.sls.core.prefs @@ -0,0 +1,2 @@ +copiedFilesOriginState={} +eclipse.preferences.version=1 diff --git a/efm32boot/CMSIS/EFM32PG1B/startup_gcc_efm32pg1b.s b/efm32boot/CMSIS/EFM32PG1B/startup_gcc_efm32pg1b.s new file mode 100644 index 0000000..2a683c7 --- /dev/null +++ b/efm32boot/CMSIS/EFM32PG1B/startup_gcc_efm32pg1b.s @@ -0,0 +1,317 @@ +/* @file startup_efm32pg1b.S + * @brief startup file for Silicon Labs EFM32PG1B devices. + * For use with GCC for ARM Embedded Processors + * @version 5.2.2 + * Date: 12 June 2014 + * + */ +/* Copyright (c) 2011 - 2014 ARM LIMITED + + All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - 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. + - Neither the name of ARM 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 COPYRIGHT HOLDERS AND 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. + ---------------------------------------------------------------------------*/ + + .syntax unified + .arch armv7-m + .section .stack + .align 3 +#ifdef __STACK_SIZE + .equ Stack_Size, __STACK_SIZE +#else + .equ Stack_Size, 0x00000400 +#endif + .globl __StackTop + .globl __StackLimit +__StackLimit: + .space Stack_Size + .size __StackLimit, . - __StackLimit +__StackTop: + .size __StackTop, . - __StackTop + + .section .heap + .align 3 +#ifdef __HEAP_SIZE + .equ Heap_Size, __HEAP_SIZE +#else + .equ Heap_Size, 0x00000C00 +#endif + .globl __HeapBase + .globl __HeapLimit +__HeapBase: + .if Heap_Size + .space Heap_Size + .endif + .size __HeapBase, . - __HeapBase +__HeapLimit: + .size __HeapLimit, . - __HeapLimit + + .section .vectors + .align 2 + .globl __Vectors +__Vectors: + .long __StackTop /* Top of Stack */ + .long Reset_Handler /* Reset Handler */ + .long NMI_Handler /* NMI Handler */ + .long HardFault_Handler /* Hard Fault Handler */ + .long MemManage_Handler /* MPU Fault Handler */ + .long BusFault_Handler /* Bus Fault Handler */ + .long UsageFault_Handler /* Usage Fault Handler */ + .long Default_Handler /* Reserved */ + .long Default_Handler /* Reserved */ + .long Default_Handler /* Reserved */ + .long Default_Handler /* Reserved */ + .long SVC_Handler /* SVCall Handler */ + .long DebugMon_Handler /* Debug Monitor Handler */ + .long Default_Handler /* Reserved */ + .long PendSV_Handler /* PendSV Handler */ + .long SysTick_Handler /* SysTick Handler */ + + /* External interrupts */ + .long EMU_IRQHandler /* 0 - EMU */ + .long Default_Handler /* 1 - Reserved */ + .long WDOG0_IRQHandler /* 2 - WDOG0 */ + .long Default_Handler /* 3 - Reserved */ + .long Default_Handler /* 4 - Reserved */ + .long Default_Handler /* 5 - Reserved */ + .long Default_Handler /* 6 - Reserved */ + .long Default_Handler /* 7 - Reserved */ + .long LDMA_IRQHandler /* 8 - LDMA */ + .long GPIO_EVEN_IRQHandler /* 9 - GPIO_EVEN */ + .long TIMER0_IRQHandler /* 10 - TIMER0 */ + .long USART0_RX_IRQHandler /* 11 - USART0_RX */ + .long USART0_TX_IRQHandler /* 12 - USART0_TX */ + .long ACMP0_IRQHandler /* 13 - ACMP0 */ + .long ADC0_IRQHandler /* 14 - ADC0 */ + .long IDAC0_IRQHandler /* 15 - IDAC0 */ + .long I2C0_IRQHandler /* 16 - I2C0 */ + .long GPIO_ODD_IRQHandler /* 17 - GPIO_ODD */ + .long TIMER1_IRQHandler /* 18 - TIMER1 */ + .long USART1_RX_IRQHandler /* 19 - USART1_RX */ + .long USART1_TX_IRQHandler /* 20 - USART1_TX */ + .long LEUART0_IRQHandler /* 21 - LEUART0 */ + .long PCNT0_IRQHandler /* 22 - PCNT0 */ + .long CMU_IRQHandler /* 23 - CMU */ + .long MSC_IRQHandler /* 24 - MSC */ + .long CRYPTO_IRQHandler /* 25 - CRYPTO */ + .long LETIMER0_IRQHandler /* 26 - LETIMER0 */ + .long Default_Handler /* 27 - Reserved */ + .long Default_Handler /* 28 - Reserved */ + .long RTCC_IRQHandler /* 29 - RTCC */ + .long Default_Handler /* 30 - Reserved */ + .long CRYOTIMER_IRQHandler /* 31 - CRYOTIMER */ + .long Default_Handler /* 32 - Reserved */ + .long FPUEH_IRQHandler /* 33 - FPUEH */ + + + .size __Vectors, . - __Vectors + + .text + .thumb + .thumb_func + .align 2 + .globl Reset_Handler + .type Reset_Handler, %function +Reset_Handler: +#ifndef __NO_SYSTEM_INIT + ldr r0, =SystemInit + blx r0 +#endif + +/* Firstly it copies data from read only memory to RAM. There are two schemes + * to copy. One can copy more than one sections. Another can only copy + * one section. The former scheme needs more instructions and read-only + * data to implement than the latter. + * Macro __STARTUP_COPY_MULTIPLE is used to choose between two schemes. */ + +#ifdef __STARTUP_COPY_MULTIPLE +/* Multiple sections scheme. + * + * Between symbol address __copy_table_start__ and __copy_table_end__, + * there are array of triplets, each of which specify: + * offset 0: LMA of start of a section to copy from + * offset 4: VMA of start of a section to copy to + * offset 8: size of the section to copy. Must be multiply of 4 + * + * All addresses must be aligned to 4 bytes boundary. + */ + ldr r4, =__copy_table_start__ + ldr r5, =__copy_table_end__ + +.L_loop0: + cmp r4, r5 + bge .L_loop0_done + ldr r1, [r4] + ldr r2, [r4, #4] + ldr r3, [r4, #8] + +.L_loop0_0: + subs r3, #4 + ittt ge + ldrge r0, [r1, r3] + strge r0, [r2, r3] + bge .L_loop0_0 + + adds r4, #12 + b .L_loop0 + +.L_loop0_done: +#else +/* Single section scheme. + * + * The ranges of copy from/to are specified by following symbols + * __etext: LMA of start of the section to copy from. Usually end of text + * __data_start__: VMA of start of the section to copy to + * __data_end__: VMA of end of the section to copy to + * + * All addresses must be aligned to 4 bytes boundary. + */ + ldr r1, =__etext + ldr r2, =__data_start__ + ldr r3, =__data_end__ + +.L_loop1: + cmp r2, r3 + ittt lt + ldrlt r0, [r1], #4 + strlt r0, [r2], #4 + blt .L_loop1 +#endif /*__STARTUP_COPY_MULTIPLE */ + +/* This part of work usually is done in C library startup code. Otherwise, + * define this macro to enable it in this startup. + * + * There are two schemes too. One can clear multiple BSS sections. Another + * can only clear one section. The former is more size expensive than the + * latter. + * + * Define macro __STARTUP_CLEAR_BSS_MULTIPLE to choose the former. + * Otherwise efine macro __STARTUP_CLEAR_BSS to choose the later. + */ +#ifdef __STARTUP_CLEAR_BSS_MULTIPLE +/* Multiple sections scheme. + * + * Between symbol address __zero_table_start__ and __zero_table_end__, + * there are array of tuples specifying: + * offset 0: Start of a BSS section + * offset 4: Size of this BSS section. Must be multiply of 4 + */ + ldr r3, =__zero_table_start__ + ldr r4, =__zero_table_end__ + +.L_loop2: + cmp r3, r4 + bge .L_loop2_done + ldr r1, [r3] + ldr r2, [r3, #4] + movs r0, 0 + +.L_loop2_0: + subs r2, #4 + itt ge + strge r0, [r1, r2] + bge .L_loop2_0 + adds r3, #8 + b .L_loop2 +.L_loop2_done: +#elif defined (__STARTUP_CLEAR_BSS) +/* Single BSS section scheme. + * + * The BSS section is specified by following symbols + * __bss_start__: start of the BSS section. + * __bss_end__: end of the BSS section. + * + * Both addresses must be aligned to 4 bytes boundary. + */ + ldr r1, =__bss_start__ + ldr r2, =__bss_end__ + + movs r0, 0 +.L_loop3: + cmp r1, r2 + itt lt + strlt r0, [r1], #4 + blt .L_loop3 +#endif /* __STARTUP_CLEAR_BSS_MULTIPLE || __STARTUP_CLEAR_BSS */ + +#ifndef __START +#define __START _start +#endif + bl __START + + .pool + .size Reset_Handler, . - Reset_Handler + + .align 1 + .thumb_func + .weak Default_Handler + .type Default_Handler, %function +Default_Handler: + b . + .size Default_Handler, . - Default_Handler + +/* Macro to define default handlers. Default handler + * will be weak symbol and just dead loops. They can be + * overwritten by other handlers */ + .macro def_irq_handler handler_name + .weak \handler_name + .set \handler_name, Default_Handler + .endm + + def_irq_handler NMI_Handler + def_irq_handler HardFault_Handler + def_irq_handler MemManage_Handler + def_irq_handler BusFault_Handler + def_irq_handler UsageFault_Handler + def_irq_handler SVC_Handler + def_irq_handler DebugMon_Handler + def_irq_handler PendSV_Handler + def_irq_handler SysTick_Handler + + + def_irq_handler EMU_IRQHandler + def_irq_handler WDOG0_IRQHandler + def_irq_handler LDMA_IRQHandler + def_irq_handler GPIO_EVEN_IRQHandler + def_irq_handler TIMER0_IRQHandler + def_irq_handler USART0_RX_IRQHandler + def_irq_handler USART0_TX_IRQHandler + def_irq_handler ACMP0_IRQHandler + def_irq_handler ADC0_IRQHandler + def_irq_handler IDAC0_IRQHandler + def_irq_handler I2C0_IRQHandler + def_irq_handler GPIO_ODD_IRQHandler + def_irq_handler TIMER1_IRQHandler + def_irq_handler USART1_RX_IRQHandler + def_irq_handler USART1_TX_IRQHandler + def_irq_handler LEUART0_IRQHandler + def_irq_handler PCNT0_IRQHandler + def_irq_handler CMU_IRQHandler + def_irq_handler MSC_IRQHandler + def_irq_handler CRYPTO_IRQHandler + def_irq_handler LETIMER0_IRQHandler + def_irq_handler RTCC_IRQHandler + def_irq_handler CRYOTIMER_IRQHandler + def_irq_handler FPUEH_IRQHandler + + .end diff --git a/efm32boot/CMSIS/EFM32PG1B/system_efm32pg1b.c b/efm32boot/CMSIS/EFM32PG1B/system_efm32pg1b.c new file mode 100644 index 0000000..c52e3e1 --- /dev/null +++ b/efm32boot/CMSIS/EFM32PG1B/system_efm32pg1b.c @@ -0,0 +1,389 @@ +/***************************************************************************//** + * @file system_efm32pg1b.c + * @brief CMSIS Cortex-M3/M4 System Layer for EFM32 devices. + * @version 5.2.2 + ****************************************************************************** + * # License + * Copyright 2017 Silicon Laboratories, Inc. http://www.silabs.com + ****************************************************************************** + * + * 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.@n + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software.@n + * 3. This notice may not be removed or altered from any source distribution. + * + * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Laboratories, Inc. + * has no obligation to support this Software. Silicon Laboratories, Inc. 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 Laboratories, Inc. 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 +#include "em_device.h" + +/******************************************************************************* + ****************************** DEFINES ************************************ + ******************************************************************************/ + +/** LFRCO frequency, tuned to below frequency during manufacturing. */ +#define EFM32_LFRCO_FREQ (32768UL) +/** ULFRCO frequency */ +#define EFM32_ULFRCO_FREQ (1000UL) + +/******************************************************************************* + ************************** LOCAL VARIABLES ******************************** + ******************************************************************************/ + +/* System oscillator frequencies. These frequencies are normally constant */ +/* for a target, but they are made configurable in order to allow run-time */ +/* handling of different boards. The crystal oscillator clocks can be set */ +/* compile time to a non-default value by defining respective EFM_nFXO_FREQ */ +/* values according to board design. By defining the EFM_nFXO_FREQ to 0, */ +/* one indicates that the oscillator is not present, in order to save some */ +/* SW footprint. */ + +#ifndef EFM32_HFRCO_MAX_FREQ +/** Maximum HFRCO frequency */ +#define EFM32_HFRCO_MAX_FREQ (38000000UL) +#endif + +#ifndef EFM32_HFXO_FREQ +/** HFXO frequency */ +#define EFM32_HFXO_FREQ (40000000UL) +#endif + +#ifndef EFM32_HFRCO_STARTUP_FREQ +/** HFRCO startup frequency */ +#define EFM32_HFRCO_STARTUP_FREQ (19000000UL) +#endif + + +/* Do not define variable if HF crystal oscillator not present */ +#if (EFM32_HFXO_FREQ > 0UL) +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ +/** System HFXO clock. */ +static uint32_t SystemHFXOClock = EFM32_HFXO_FREQ; +/** @endcond (DO_NOT_INCLUDE_WITH_DOXYGEN) */ +#endif + +#ifndef EFM32_LFXO_FREQ +/** LFXO frequency */ +#define EFM32_LFXO_FREQ (EFM32_LFRCO_FREQ) +#endif +/* Do not define variable if LF crystal oscillator not present */ +#if (EFM32_LFXO_FREQ > 0UL) +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ +/** System LFXO clock. */ +static uint32_t SystemLFXOClock = 32768UL; +/** @endcond (DO_NOT_INCLUDE_WITH_DOXYGEN) */ +#endif + + +/******************************************************************************* + ************************** GLOBAL VARIABLES ******************************* + ******************************************************************************/ + +/** + * @brief + * System System Clock Frequency (Core Clock). + * + * @details + * Required CMSIS global variable that must be kept up-to-date. + */ +uint32_t SystemCoreClock = EFM32_HFRCO_STARTUP_FREQ; + + +/** + * @brief + * System HFRCO frequency + * + * @note + * This is an EFM32 proprietary variable, not part of the CMSIS definition. + * + * @details + * Frequency of the system HFRCO oscillator + */ +uint32_t SystemHfrcoFreq = EFM32_HFRCO_STARTUP_FREQ; + + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Get the current core clock frequency. + * + * @details + * Calculate and get the current core clock frequency based on the current + * configuration. Assuming that the SystemCoreClock global variable is + * maintained, the core clock frequency is stored in that variable as well. + * This function will however calculate the core clock based on actual HW + * configuration. It will also update the SystemCoreClock global variable. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * The current core clock frequency in Hz. + ******************************************************************************/ +uint32_t SystemCoreClockGet(void) +{ + uint32_t ret; + uint32_t presc; + + ret = SystemHFClockGet(); + presc = (CMU->HFCOREPRESC & _CMU_HFCOREPRESC_PRESC_MASK) >> + _CMU_HFCOREPRESC_PRESC_SHIFT; + ret /= (presc + 1); + + /* Keep CMSIS system clock variable up-to-date */ + SystemCoreClock = ret; + + return ret; +} + + +/***************************************************************************//** + * @brief + * Get the maximum core clock frequency. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * The maximum core clock frequency in Hz. + ******************************************************************************/ +uint32_t SystemMaxCoreClockGet(void) +{ + return (EFM32_HFRCO_MAX_FREQ > EFM32_HFXO_FREQ ? \ + EFM32_HFRCO_MAX_FREQ : EFM32_HFXO_FREQ); +} + + +/***************************************************************************//** + * @brief + * Get the current HFCLK frequency. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * The current HFCLK frequency in Hz. + ******************************************************************************/ +uint32_t SystemHFClockGet(void) +{ + uint32_t ret; + + switch (CMU->HFCLKSTATUS & _CMU_HFCLKSTATUS_SELECTED_MASK) + { + case CMU_HFCLKSTATUS_SELECTED_LFXO: +#if (EFM32_LFXO_FREQ > 0) + ret = SystemLFXOClock; +#else + /* We should not get here, since core should not be clocked. May */ + /* be caused by a misconfiguration though. */ + ret = 0; +#endif + break; + + case CMU_HFCLKSTATUS_SELECTED_LFRCO: + ret = EFM32_LFRCO_FREQ; + break; + + case CMU_HFCLKSTATUS_SELECTED_HFXO: +#if (EFM32_HFXO_FREQ > 0) + ret = SystemHFXOClock; +#else + /* We should not get here, since core should not be clocked. May */ + /* be caused by a misconfiguration though. */ + ret = 0; +#endif + break; + + default: /* CMU_HFCLKSTATUS_SELECTED_HFRCO */ + ret = SystemHfrcoFreq; + break; + } + + return ret / (1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK) + >> _CMU_HFPRESC_PRESC_SHIFT)); +} + + +/**************************************************************************//** + * @brief + * Get high frequency crystal oscillator clock frequency for target system. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * HFXO frequency in Hz. + *****************************************************************************/ +uint32_t SystemHFXOClockGet(void) +{ + /* External crystal oscillator present? */ +#if (EFM32_HFXO_FREQ > 0) + return SystemHFXOClock; +#else + return 0; +#endif +} + + +/**************************************************************************//** + * @brief + * Set high frequency crystal oscillator clock frequency for target system. + * + * @note + * This function is mainly provided for being able to handle target systems + * with different HF crystal oscillator frequencies run-time. If used, it + * should probably only be used once during system startup. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @param[in] freq + * HFXO frequency in Hz used for target. + *****************************************************************************/ +void SystemHFXOClockSet(uint32_t freq) +{ + /* External crystal oscillator present? */ +#if (EFM32_HFXO_FREQ > 0) + SystemHFXOClock = freq; + + /* Update core clock frequency if HFXO is used to clock core */ + if ((CMU->HFCLKSTATUS & _CMU_HFCLKSTATUS_SELECTED_MASK) == CMU_HFCLKSTATUS_SELECTED_HFXO) + { + /* The function will update the global variable */ + SystemCoreClockGet(); + } +#else + (void)freq; /* Unused parameter */ +#endif +} + + +/**************************************************************************//** + * @brief + * Initialize the system. + * + * @details + * Do required generic HW system init. + * + * @note + * This function is invoked during system init, before the main() routine + * and any data has been initialized. For this reason, it cannot do any + * initialization of variables etc. + *****************************************************************************/ +void SystemInit(void) +{ +#if (__FPU_PRESENT == 1) && (__FPU_USED == 1) + /* Set floating point coprosessor access mode. */ + SCB->CPACR |= ((3UL << 10*2) | /* set CP10 Full Access */ + (3UL << 11*2) ); /* set CP11 Full Access */ +#endif +} + + +/**************************************************************************//** + * @brief + * Get low frequency RC oscillator clock frequency for target system. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * LFRCO frequency in Hz. + *****************************************************************************/ +uint32_t SystemLFRCOClockGet(void) +{ + /* Currently we assume that this frequency is properly tuned during */ + /* manufacturing and is not changed after reset. If future requirements */ + /* for re-tuning by user, we can add support for that. */ + return EFM32_LFRCO_FREQ; +} + + +/**************************************************************************//** + * @brief + * Get ultra low frequency RC oscillator clock frequency for target system. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * ULFRCO frequency in Hz. + *****************************************************************************/ +uint32_t SystemULFRCOClockGet(void) +{ + /* The ULFRCO frequency is not tuned, and can be very inaccurate */ + return EFM32_ULFRCO_FREQ; +} + + +/**************************************************************************//** + * @brief + * Get low frequency crystal oscillator clock frequency for target system. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @return + * LFXO frequency in Hz. + *****************************************************************************/ +uint32_t SystemLFXOClockGet(void) +{ + /* External crystal oscillator present? */ +#if (EFM32_LFXO_FREQ > 0) + return SystemLFXOClock; +#else + return 0; +#endif +} + + +/**************************************************************************//** + * @brief + * Set low frequency crystal oscillator clock frequency for target system. + * + * @note + * This function is mainly provided for being able to handle target systems + * with different HF crystal oscillator frequencies run-time. If used, it + * should probably only be used once during system startup. + * + * @note + * This is an EFM32 proprietary function, not part of the CMSIS definition. + * + * @param[in] freq + * LFXO frequency in Hz used for target. + *****************************************************************************/ +void SystemLFXOClockSet(uint32_t freq) +{ + /* External crystal oscillator present? */ +#if (EFM32_LFXO_FREQ > 0) + SystemLFXOClock = freq; + + /* Update core clock frequency if LFXO is used to clock core */ + if ((CMU->HFCLKSTATUS & _CMU_HFCLKSTATUS_SELECTED_MASK) == CMU_HFCLKSTATUS_SELECTED_LFXO) + { + /* The function will update the global variable */ + SystemCoreClockGet(); + } +#else + (void)freq; /* Unused parameter */ +#endif +} diff --git a/efm32boot/efm32boot.hwconf b/efm32boot/efm32boot.hwconf new file mode 100644 index 0000000..ec25abf --- /dev/null +++ b/efm32boot/efm32boot.hwconf @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/efm32boot/emlib/em_assert.c b/efm32boot/emlib/em_assert.c new file mode 100644 index 0000000..71225e0 --- /dev/null +++ b/efm32boot/emlib/em_assert.c @@ -0,0 +1,81 @@ +/***************************************************************************//** + * @file em_assert.c + * @brief Assert API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 "em_assert.h" +#include + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup ASSERT + * @{ + ******************************************************************************/ + +#if defined(DEBUG_EFM) +/***************************************************************************//** + * @brief + * EFM internal assert handling. + * + * This function is invoked through EFM_ASSERT() macro usage only, it should + * not be used explicitly. + * + * This implementation simply enters an indefinite loop, allowing + * the use of a debugger to determine cause of failure. By defining + * DEBUG_EFM_USER to the preprocessor for all files, a user defined version + * of this function must be defined and will be invoked instead, possibly + * providing output of assertion location. + * + * @note + * This function is not used unless @ref DEBUG_EFM is defined + * during preprocessing of EFM_ASSERT() usage. + * + * @param[in] file + * Name of source file where assertion failed. + * + * @param[in] line + * Line number in source file where assertion failed. + ******************************************************************************/ +void assertEFM(const char *file, int line) +{ + (void)file; /* Unused parameter */ + (void)line; /* Unused parameter */ + + while (true) { + } +} +#endif /* DEBUG_EFM */ + +/** @} (end addtogroup ASSERT) */ +/** @} (end addtogroup emlib) */ diff --git a/efm32boot/emlib/em_cmu.c b/efm32boot/emlib/em_cmu.c new file mode 100644 index 0000000..96316ff --- /dev/null +++ b/efm32boot/emlib/em_cmu.c @@ -0,0 +1,5310 @@ +/***************************************************************************//** + * @file em_cmu.c + * @brief Clock management unit (CMU) Peripheral API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 "em_cmu.h" +#if defined(CMU_PRESENT) + +#include +#include +#include "em_assert.h" +#include "em_bus.h" +#include "em_emu.h" +#include "em_cmu.h" +#include "em_system.h" +#include "em_common.h" + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup CMU + * @brief Clock management unit (CMU) Peripheral API + * @details + * This module contains functions to control the CMU peripheral of Silicon + * Labs 32-bit MCUs and SoCs. The CMU controls oscillators and clocks. + * @{ + ******************************************************************************/ + +/******************************************************************************* + ****************************** DEFINES ************************************ + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +#if defined(_SILICON_LABS_32B_SERIES_1) +/** Maximum allowed core frequency when using 0 wait-states on flash access. */ +#define CMU_MAX_FREQ_0WS 26000000 +/** Maximum allowed core frequency when using 1 wait-states on flash access */ +#define CMU_MAX_FREQ_1WS 40000000 +/** Maximum allowed core frequency when using 2 wait-states on flash access */ +#define CMU_MAX_FREQ_2WS 54000000 +/** Maximum allowed core frequency when using 3 wait-states on flash access */ +#define CMU_MAX_FREQ_3WS 72000000 +#elif defined(_SILICON_LABS_32B_SERIES_0) +/** Maximum allowed core frequency when using 0 wait-states on flash access. */ +#define CMU_MAX_FREQ_0WS 16000000 +/** Maximum allowed core frequency when using 1 wait-states on flash access */ +#define CMU_MAX_FREQ_1WS 32000000 +#else +#error "Max Flash wait-state frequencies are not defined for this platform." +#endif + +/** Maximum frequency for HFLE interface */ +#if defined(CMU_CTRL_HFLE) +/** Maximum HFLE frequency for series 0 EFM32 and EZR32 Wonder Gecko. */ +#if defined(_SILICON_LABS_32B_SERIES_0) \ + && (defined(_EFM32_WONDER_FAMILY) \ + || defined(_EZR32_WONDER_FAMILY)) +#define CMU_MAX_FREQ_HFLE 24000000 +/** Maximum HFLE frequency for other series 0 parts with maximum core clock + higher than 32MHz. */ +#elif defined(_SILICON_LABS_32B_SERIES_0) \ + && (defined(_EFM32_GIANT_FAMILY) \ + || defined(_EZR32_LEOPARD_FAMILY)) +#define CMU_MAX_FREQ_HFLE maxFreqHfle() +#endif +#elif defined(CMU_CTRL_WSHFLE) +/** Maximum HFLE frequency for series 1 parts */ +#define CMU_MAX_FREQ_HFLE 32000000 +#endif + +#if defined(CMU_STATUS_HFXOSHUNTOPTRDY) +#define HFXO_TUNING_READY_FLAGS (CMU_STATUS_HFXOPEAKDETRDY | CMU_STATUS_HFXOSHUNTOPTRDY) +#define HFXO_TUNING_MODE_AUTO (_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_AUTOCMD) +#define HFXO_TUNING_MODE_CMD (_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_CMD) +#elif defined(CMU_STATUS_HFXOPEAKDETRDY) +#define HFXO_TUNING_READY_FLAGS (CMU_STATUS_HFXOPEAKDETRDY) +#define HFXO_TUNING_MODE_AUTO (_CMU_HFXOCTRL_PEAKDETMODE_AUTOCMD) +#define HFXO_TUNING_MODE_CMD (_CMU_HFXOCTRL_PEAKDETMODE_CMD) +#endif + +#if defined(CMU_HFXOCTRL_MODE_EXTCLK) +/** HFXO external clock mode is renamed from EXTCLK to DIGEXTCLK. */ +#define CMU_HFXOCTRL_MODE_DIGEXTCLK CMU_HFXOCTRL_MODE_EXTCLK +#endif + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +#define VSCALE_DEFAULT (EMU_VScaleGet()) +#else +#define VSCALE_DEFAULT 0 +#endif + +/******************************************************************************* + ************************** LOCAL VARIABLES ******************************** + ******************************************************************************/ + +#if defined(_CMU_AUXHFRCOCTRL_FREQRANGE_MASK) +static CMU_AUXHFRCOFreq_TypeDef auxHfrcoFreq = cmuAUXHFRCOFreq_19M0Hz; +#endif +#if defined(_CMU_STATUS_HFXOSHUNTOPTRDY_MASK) +#define HFXO_INVALID_TRIM (~_CMU_HFXOTRIMSTATUS_MASK) +#endif + +#if defined(CMU_OSCENCMD_DPLLEN) +/** Table of HFRCOCTRL values and their associated min/max frequencies and + optional band enumerator. */ +static const struct hfrcoCtrlTableElement{ + uint32_t minFreq; + uint32_t maxFreq; + uint32_t value; + CMU_HFRCOFreq_TypeDef band; +} hfrcoCtrlTable[] = +{ + // minFreq maxFreq HFRCOCTRL value band + { 860000, 1050000, 0xBC601F00, cmuHFRCOFreq_1M0Hz }, + { 1050000, 1280000, 0xBC611F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 1280000, 1480000, 0xBCA21F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 1480000, 1800000, 0xAD231F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 1800000, 2110000, 0xBA601F00, cmuHFRCOFreq_2M0Hz }, + { 2110000, 2560000, 0xBA611F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 2560000, 2970000, 0xBAA21F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 2970000, 3600000, 0xAB231F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 3600000, 4220000, 0xB8601F00, cmuHFRCOFreq_4M0Hz }, + { 4220000, 5120000, 0xB8611F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 5120000, 5930000, 0xB8A21F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 5930000, 7520000, 0xA9231F00, cmuHFRCOFreq_7M0Hz }, + { 7520000, 9520000, 0x99241F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 9520000, 11800000, 0x99251F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 11800000, 14400000, 0x99261F00, cmuHFRCOFreq_13M0Hz }, + { 14400000, 17200000, 0x99271F00, cmuHFRCOFreq_16M0Hz }, + { 17200000, 19700000, 0x99481F00, cmuHFRCOFreq_19M0Hz }, + { 19700000, 23800000, 0x99491F35, (CMU_HFRCOFreq_TypeDef)0 }, + { 23800000, 28700000, 0x994A1F00, cmuHFRCOFreq_26M0Hz }, + { 28700000, 34800000, 0x996B1F00, cmuHFRCOFreq_32M0Hz }, +#if defined(_DEVINFO_HFRCOCAL16_MASK) + { 34800000, 42800000, 0x996C1F00, cmuHFRCOFreq_38M0Hz }, + { 42800000, 51600000, 0x996D1F00, cmuHFRCOFreq_48M0Hz }, + { 51600000, 60500000, 0x998E1F00, cmuHFRCOFreq_56M0Hz }, + { 60500000, 72000000, 0xA98F1F00, cmuHFRCOFreq_64M0Hz } +#else + { 34800000, 40000000, 0x996C1F00, cmuHFRCOFreq_38M0Hz } +#endif +}; + +#define HFRCOCTRLTABLE_ENTRIES (sizeof(hfrcoCtrlTable) \ + / sizeof(struct hfrcoCtrlTableElement)) +#endif // CMU_OSCENCMD_DPLLEN + +#if defined(_SILICON_LABS_32B_SERIES_1) && defined(_EMU_STATUS_VSCALE_MASK) +/* Devices with Voltage Scaling needs extra handling of wait states. */ +static const struct flashWsTableElement{ + uint32_t maxFreq; + uint8_t vscale; + uint8_t ws; +} flashWsTable[] = +{ +#if (_SILICON_LABS_GECKO_INTERNAL_SDID == 100) + { 18000000, 0, 0 }, /* 0 wait states at max frequency 18 MHz and 1.2V */ + { 36000000, 0, 1 }, /* 1 wait states at max frequency 36 MHz and 1.2V */ + { 54000000, 0, 2 }, /* 2 wait states at max frequency 54 MHz and 1.2V */ + { 72000000, 0, 3 }, /* 3 wait states at max frequency 72 MHz and 1.2V */ + { 7000000, 2, 0 }, /* 0 wait states at max frequency 7 MHz and 1.0V */ + { 14000000, 2, 1 }, /* 1 wait states at max frequency 14 MHz and 1.0V */ + { 21000000, 2, 2 }, /* 2 wait states at max frequency 21 MHz and 1.0V */ +#else + { 25000000, 0, 0 }, /* 0 wait states at max frequency 25 MHz and 1.2V */ + { 40000000, 0, 1 }, /* 1 wait states at max frequency 40 MHz and 1.2V */ + { 7000000, 2, 0 }, /* 0 wait states at max frequency 7 MHz and 1.0V */ + { 14000000, 2, 1 }, /* 1 wait states at max frequency 14 MHz and 1.0V */ + { 21000000, 2, 2 }, /* 2 wait states at max frequency 21 MHz and 1.0V */ +#endif +}; + +#define FLASH_WS_TABLE_ENTRIES (sizeof(flashWsTable) / sizeof(flashWsTable[0])) +#endif + +#if defined(_CMU_USHFRCOCTRL_FREQRANGE_MASK) \ + || defined(_CMU_USHFRCOTUNE_MASK) +#ifndef EFM32_USHFRCO_STARTUP_FREQ +#define EFM32_USHFRCO_STARTUP_FREQ (48000000UL) +#endif + +static uint32_t ushfrcoFreq = EFM32_USHFRCO_STARTUP_FREQ; +#endif + +/******************************************************************************* + ************************** LOCAL PROTOTYPES ******************************* + ******************************************************************************/ +#if defined(_CMU_HFRCOCTRL_FREQRANGE_MASK) +static uint32_t CMU_HFRCODevinfoGet(CMU_HFRCOFreq_TypeDef freq); +#endif + +#if defined(_CMU_USHFRCOCTRL_FREQRANGE_MASK) +static uint32_t CMU_USHFRCODevinfoGet(CMU_USHFRCOFreq_TypeDef freq); +#endif + +/** @endcond */ + +/******************************************************************************* + ************************** LOCAL FUNCTIONS ******************************** + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +#if defined(_SILICON_LABS_32B_SERIES_0) \ + && (defined(_EFM32_GIANT_FAMILY) \ + || defined(_EZR32_LEOPARD_FAMILY)) +/***************************************************************************//** + * @brief + * Return max allowed frequency for low energy peripherals. + ******************************************************************************/ +static uint32_t maxFreqHfle(void) +{ + uint16_t majorMinorRev; + + switch (SYSTEM_GetFamily()) { + case systemPartFamilyEfm32Leopard: + case systemPartFamilyEzr32Leopard: + /* CHIP MAJOR bit [5:0] */ + majorMinorRev = (((ROMTABLE->PID0 & _ROMTABLE_PID0_REVMAJOR_MASK) + >> _ROMTABLE_PID0_REVMAJOR_SHIFT) << 8); + /* CHIP MINOR bit [7:4] */ + majorMinorRev |= (((ROMTABLE->PID2 & _ROMTABLE_PID2_REVMINORMSB_MASK) + >> _ROMTABLE_PID2_REVMINORMSB_SHIFT) << 4); + /* CHIP MINOR bit [3:0] */ + majorMinorRev |= ((ROMTABLE->PID3 & _ROMTABLE_PID3_REVMINORLSB_MASK) + >> _ROMTABLE_PID3_REVMINORLSB_SHIFT); + + if (majorMinorRev >= 0x0204) { + return 24000000; + } else { + return 32000000; + } + + case systemPartFamilyEfm32Giant: + return 32000000; + + default: + /* Invalid device family. */ + EFM_ASSERT(false); + return 0; + } +} +#endif + +#if defined(CMU_MAX_FREQ_HFLE) + +/* Unified definitions for HFLE wait-state and prescaler fields. */ +#if defined(CMU_CTRL_HFLE) +#define _GENERIC_HFLE_WS_MASK _CMU_CTRL_HFLE_MASK +#define _GENERIC_HFLE_WS_SHIFT _CMU_CTRL_HFLE_SHIFT +#define GENERIC_HFLE_PRESC_REG CMU->HFCORECLKDIV +#define _GENERIC_HFLE_PRESC_MASK _CMU_HFCORECLKDIV_HFCORECLKLEDIV_MASK +#define _GENERIC_HFLE_PRESC_SHIFT _CMU_HFCORECLKDIV_HFCORECLKLEDIV_SHIFT +#elif defined(CMU_CTRL_WSHFLE) +#define _GENERIC_HFLE_WS_MASK _CMU_CTRL_WSHFLE_MASK +#define _GENERIC_HFLE_WS_SHIFT _CMU_CTRL_WSHFLE_SHIFT +#define GENERIC_HFLE_PRESC_REG CMU->HFPRESC +#define _GENERIC_HFLE_PRESC_MASK _CMU_HFPRESC_HFCLKLEPRESC_MASK +#define _GENERIC_HFLE_PRESC_SHIFT _CMU_HFPRESC_HFCLKLEPRESC_SHIFT +#endif + +/***************************************************************************//** + * @brief + * Set HFLE wait-states and HFCLKLE prescaler. + * + * @param[in] maxLeFreq + * Max LE frequency + ******************************************************************************/ +static void setHfLeConfig(uint32_t hfFreq) +{ + unsigned int hfleWs; + uint32_t hflePresc; + + /* Check for 1 bit fields. BUS_RegBitWrite() below are going to fail if the + fields are changed to more than 1 bit. */ + EFM_ASSERT((_GENERIC_HFLE_WS_MASK >> _GENERIC_HFLE_WS_SHIFT) == 0x1); + + /* - Enable HFLE wait-state if to allow access to LE peripherals when HFBUSCLK is + above maxLeFreq. + - Set HFLE prescaler. Allowed HFLE clock frequency is maxLeFreq. */ + + hfleWs = 1; + if (hfFreq <= CMU_MAX_FREQ_HFLE) { + hfleWs = 0; + hflePresc = 0; + } else if (hfFreq <= (2 * CMU_MAX_FREQ_HFLE)) { + hflePresc = 1; + } else { + hflePresc = 2; + } + BUS_RegBitWrite(&CMU->CTRL, _GENERIC_HFLE_WS_SHIFT, hfleWs); + GENERIC_HFLE_PRESC_REG = (GENERIC_HFLE_PRESC_REG & ~_GENERIC_HFLE_PRESC_MASK) + | (hflePresc << _GENERIC_HFLE_PRESC_SHIFT); +} + +#if defined(_CMU_CTRL_HFLE_MASK) +/***************************************************************************//** + * @brief + * Get HFLE wait-state configuration. + * + * @return + * Current wait-state configuration. + ******************************************************************************/ +static uint32_t getHfLeConfig(void) +{ + uint32_t ws = BUS_RegBitRead(&CMU->CTRL, _GENERIC_HFLE_WS_SHIFT); + return ws; +} +#endif +#endif + +/***************************************************************************//** + * @brief + * Get the AUX clock frequency. Used by MSC flash programming and LESENSE, + * by default also as debug clock. + * + * @return + * AUX Frequency in Hz + ******************************************************************************/ +static uint32_t auxClkGet(void) +{ + uint32_t ret; + +#if defined(_CMU_AUXHFRCOCTRL_FREQRANGE_MASK) + ret = auxHfrcoFreq; + +#elif defined(_CMU_AUXHFRCOCTRL_BAND_MASK) + /* All series 0 families except EFM32G */ + switch (CMU->AUXHFRCOCTRL & _CMU_AUXHFRCOCTRL_BAND_MASK) { + case CMU_AUXHFRCOCTRL_BAND_1MHZ: + if ( SYSTEM_GetProdRev() >= 19 ) { + ret = 1200000; + } else { + ret = 1000000; + } + break; + + case CMU_AUXHFRCOCTRL_BAND_7MHZ: + if ( SYSTEM_GetProdRev() >= 19 ) { + ret = 6600000; + } else { + ret = 7000000; + } + break; + + case CMU_AUXHFRCOCTRL_BAND_11MHZ: + ret = 11000000; + break; + + case CMU_AUXHFRCOCTRL_BAND_14MHZ: + ret = 14000000; + break; + + case CMU_AUXHFRCOCTRL_BAND_21MHZ: + ret = 21000000; + break; + +#if defined(_CMU_AUXHFRCOCTRL_BAND_28MHZ) + case CMU_AUXHFRCOCTRL_BAND_28MHZ: + ret = 28000000; + break; +#endif + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + +#else + /* Gecko has a fixed 14Mhz AUXHFRCO clock */ + ret = 14000000; + +#endif + + return ret; +} + +#if defined (_CMU_ADCCTRL_ADC0CLKSEL_HFSRCCLK) \ + || defined (_CMU_ADCCTRL_ADC1CLKSEL_HFSRCCLK) +/***************************************************************************//** + * @brief + * Get the HFSRCCLK frequency. + * + * @return + * HFSRCCLK Frequency in Hz + ******************************************************************************/ +static uint32_t hfSrcClkGet(void) +{ + uint32_t ret; + + ret = SystemHFClockGet(); + return ret * (1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK) + >> _CMU_HFPRESC_PRESC_SHIFT)); +} +#endif + +/***************************************************************************//** + * @brief + * Get the Debug Trace clock frequency + * + * @return + * Debug Trace frequency in Hz + ******************************************************************************/ +static uint32_t dbgClkGet(void) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + /* Get selected clock source */ + clk = CMU_ClockSelectGet(cmuClock_DBG); + + switch (clk) { + case cmuSelect_HFCLK: + ret = SystemHFClockGet(); + break; + + case cmuSelect_AUXHFRCO: + ret = auxClkGet(); + break; + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + return ret; +} + +#if defined(_CMU_ADCCTRL_MASK) +/***************************************************************************//** + * @brief + * Get the ADC n asynchronous clock frequency + * + * @return + * ADC n asynchronous frequency in Hz + ******************************************************************************/ +static uint32_t adcAsyncClkGet(uint32_t adc) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + /* Get selected clock source */ + switch (adc) { + case 0: + clk = CMU_ClockSelectGet(cmuClock_ADC0ASYNC); + break; + +#if defined(_CMU_ADCCTRL_ADC1CLKSEL_MASK) + case 1: + clk = CMU_ClockSelectGet(cmuClock_ADC1ASYNC); + break; +#endif + + default: + EFM_ASSERT(0); + return 0; + } + + switch (clk) { + case cmuSelect_Disabled: + ret = 0; + break; + + case cmuSelect_AUXHFRCO: + ret = auxClkGet(); + break; + + case cmuSelect_HFXO: + ret = SystemHFXOClockGet(); + break; + + case cmuSelect_HFSRCCLK: + ret = hfSrcClkGet(); + break; + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + return ret; +} +#endif + +#if defined(_CMU_SDIOCTRL_MASK) +/***************************************************************************//** + * @brief + * Get the SDIO reference clock frequency + * + * @return + * SDIO reference clock frequency in Hz + ******************************************************************************/ +static uint32_t sdioRefClkGet(void) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + /* Get selected clock source */ + clk = CMU_ClockSelectGet(cmuClock_SDIOREF); + + switch (clk) { + case cmuSelect_HFRCO: + ret = SystemHfrcoFreq; + break; + + case cmuSelect_HFXO: + ret = SystemHFXOClockGet(); + break; + + case cmuSelect_AUXHFRCO: + ret = auxClkGet(); + break; + + case cmuSelect_USHFRCO: + ret = ushfrcoFreq; + break; + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + return ret; +} +#endif + +#if defined(_CMU_QSPICTRL_MASK) +/***************************************************************************//** + * @brief + * Get the QSPI n reference clock frequency + * + * @return + * QSPI n reference clock frequency in Hz + ******************************************************************************/ +static uint32_t qspiRefClkGet(uint32_t qspi) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + /* Get selected clock source */ + switch (qspi) { + case 0: + clk = CMU_ClockSelectGet(cmuClock_QSPI0REF); + break; + + default: + EFM_ASSERT(0); + return 0; + } + + switch (clk) { + case cmuSelect_HFRCO: + ret = SystemHfrcoFreq; + break; + + case cmuSelect_HFXO: + ret = SystemHFXOClockGet(); + break; + + case cmuSelect_AUXHFRCO: + ret = auxClkGet(); + break; + + case cmuSelect_USHFRCO: + ret = ushfrcoFreq; + break; + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + return ret; +} +#endif + +#if defined(USBR_CLOCK_PRESENT) +/***************************************************************************//** + * @brief + * Get the USB rate clock frequency + * + * @return + * USB rate clock frequency in Hz + ******************************************************************************/ +static uint32_t usbRateClkGet(void) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + clk = CMU_ClockSelectGet(cmuClock_USBR); + + switch (clk) { + case cmuSelect_USHFRCO: + ret = ushfrcoFreq; + break; + + case cmuSelect_HFXO: + ret = SystemHFXOClockGet(); + break; + + case cmuSelect_HFXOX2: + ret = 2u * SystemHFXOClockGet(); + break; + + case cmuSelect_HFRCO: + ret = SystemHfrcoFreq; + break; + + case cmuSelect_LFXO: + ret = SystemLFXOClockGet(); + break; + + case cmuSelect_LFRCO: + ret = SystemLFRCOClockGet(); + break; + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + return ret; +} +#endif + +/***************************************************************************//** + * @brief + * Configure flash access wait states in order to support given core clock + * frequency. + * + * @param[in] coreFreq + * Core clock frequency to configure flash wait-states for + * + * @param[in] vscale + * Voltage Scale level. Supported levels are 0 and 2 where 0 is the default. + ******************************************************************************/ +static void flashWaitStateControl(uint32_t coreFreq, int vscale) +{ + uint32_t mode; + bool mscLocked; +#if defined(MSC_READCTRL_MODE_WS0SCBTP) + bool scbtpEn; /* Suppressed Conditional Branch Target Prefetch setting. */ +#endif + (void) vscale; /* vscale parameter is only used on some devices */ + + /* Make sure the MSC is unlocked */ + mscLocked = MSC->LOCK; + MSC->LOCK = MSC_UNLOCK_CODE; + + /* Get mode and SCBTP enable */ + mode = MSC->READCTRL & _MSC_READCTRL_MODE_MASK; +#if defined(MSC_READCTRL_MODE_WS0SCBTP) + /* Devices with MODE and SCBTP in same register field */ + switch (mode) { + case MSC_READCTRL_MODE_WS0: + case MSC_READCTRL_MODE_WS1: +#if defined(MSC_READCTRL_MODE_WS2) + case MSC_READCTRL_MODE_WS2: +#endif + scbtpEn = false; + break; + + default: /* WSxSCBTP */ + scbtpEn = true; + break; + } + + /* Set mode based on the core clock frequency and SCBTP enable */ + if (false) { + } +#if defined(MSC_READCTRL_MODE_WS2) + else if (coreFreq > CMU_MAX_FREQ_1WS) { + mode = (scbtpEn ? MSC_READCTRL_MODE_WS2SCBTP : MSC_READCTRL_MODE_WS2); + } +#endif + else if ((coreFreq <= CMU_MAX_FREQ_1WS) && (coreFreq > CMU_MAX_FREQ_0WS)) { + mode = (scbtpEn ? MSC_READCTRL_MODE_WS1SCBTP : MSC_READCTRL_MODE_WS1); + } else { + mode = (scbtpEn ? MSC_READCTRL_MODE_WS0SCBTP : MSC_READCTRL_MODE_WS0); + } + +#elif defined(_SILICON_LABS_32B_SERIES_1) && defined(_EMU_STATUS_VSCALE_MASK) + + /* These devices have specific requirements on the supported flash wait state + * depending on frequency and voltage scale level. */ + uint32_t i; + for (i = 0; i < FLASH_WS_TABLE_ENTRIES; i++) { + if ((flashWsTable[i].vscale == vscale) + && (coreFreq <= flashWsTable[i].maxFreq)) { + break; // found matching entry + } + } + + if (i == FLASH_WS_TABLE_ENTRIES) { + EFM_ASSERT(false); + mode = 3; // worst case flash wait state for unsupported cases + } else { + mode = flashWsTable[i].ws; + } + mode = mode << _MSC_READCTRL_MODE_SHIFT; + +#else + /* Devices where MODE and SCBTP are in separate fields and where the device + * either does not support voltage scale or where the voltage scale does + * not impact flash wait state configuration. */ + if (coreFreq <= CMU_MAX_FREQ_0WS) { + mode = 0; + } else if (coreFreq <= CMU_MAX_FREQ_1WS) { + mode = 1; + } +#if defined(MSC_READCTRL_MODE_WS2) + else if (coreFreq <= CMU_MAX_FREQ_2WS) { + mode = 2; + } +#endif +#if defined(MSC_READCTRL_MODE_WS3) + else if (coreFreq <= CMU_MAX_FREQ_3WS) { + mode = 3; + } +#endif + mode = mode << _MSC_READCTRL_MODE_SHIFT; + +#endif + + /* BUS_RegMaskedWrite cannot be used here as it would temporarily set the + mode field to WS0 */ + MSC->READCTRL = (MSC->READCTRL & ~_MSC_READCTRL_MODE_MASK) | mode; + + if (mscLocked) { + MSC->LOCK = 0; + } +} + +/***************************************************************************//** + * @brief + * Configure flash access wait states to most conservative setting for + * this target. Retain SCBTP (Suppressed Conditional Branch Target Prefetch) + * setting. + ******************************************************************************/ +static void flashWaitStateMax(void) +{ + flashWaitStateControl(SystemMaxCoreClockGet(), 0); +} + +#if defined(_MSC_RAMCTRL_RAMWSEN_MASK) +/***************************************************************************//** + * @brief + * Configure RAM access wait states in order to support given core clock + * frequency. + * + * @param[in] coreFreq + * Core clock frequency to configure RAM wait-states for + * + * @param[in] vscale + * Voltage Scale level. Supported levels are 0 and 2 where 0 is the default. + ******************************************************************************/ +static void setRamWaitState(uint32_t coreFreq, int vscale) +{ + uint32_t limit = 38000000; + if (vscale == 2) { + limit = 16000000; + } + + if (coreFreq > limit) { + BUS_RegMaskedSet(&MSC->RAMCTRL, (MSC_RAMCTRL_RAMWSEN + | MSC_RAMCTRL_RAM1WSEN + | MSC_RAMCTRL_RAM2WSEN)); + } else { + BUS_RegMaskedClear(&MSC->RAMCTRL, (MSC_RAMCTRL_RAMWSEN + | MSC_RAMCTRL_RAM1WSEN + | MSC_RAMCTRL_RAM2WSEN)); + } +} +#endif + +#if defined(_MSC_CTRL_WAITMODE_MASK) +/***************************************************************************//** + * @brief + * Configure wait state for peripheral accesses over the bus to support + * given bus clock frequency. + * + * @param[in] busFreq + * peripheral bus clock frequency to configure wait-states for + * + * @param[in] vscale + * The voltage scale to configure wait-states for. Expected values are + * 0 or 2. + * + * @li 0 = 1.2 V (VSCALE2) + * @li 2 = 1.0 V (VSCALE0) + * ******************************************************************************/ +static void setBusWaitState(uint32_t busFreq, int vscale) +{ + if ((busFreq > 50000000) && (vscale == 0)) { + BUS_RegMaskedSet(&MSC->CTRL, MSC_CTRL_WAITMODE_WS1); + } else { + BUS_RegMaskedClear(&MSC->CTRL, MSC_CTRL_WAITMODE_WS1); + } +} +#endif + +/***************************************************************************//** + * @brief + * Configure various wait states necessary to switch to a certain frequency + * and a certain voltage scale. + * + * @details + * This function will setup the necessary flash, bus and RAM wait states. + * Updating the wait state configuration must be done before + * increasing the clock frequency, and it must be done after decreasing the + * clock frequency. Updating the wait state configuration must be done before + * core voltage is decreased, and it must be done after a core voltage is + * increased. + * + * @param[in] coreFreq + * Core clock frequency to configure wait-states for. + * + * @param[in] vscale + * The voltage scale to configure wait-states for. Expected values are + * 0 or 2, higher number is lower voltage. + * + * @li 0 = 1.2 V (VSCALE2) + * @li 2 = 1.0 V (VSCALE0) + * + ******************************************************************************/ +void CMU_UpdateWaitStates(uint32_t freq, int vscale) +{ + flashWaitStateControl(freq, vscale); +#if defined(_MSC_RAMCTRL_RAMWSEN_MASK) + setRamWaitState(freq, vscale); +#endif +#if defined(_MSC_CTRL_WAITMODE_MASK) + setBusWaitState(freq, vscale); +#endif +} + +#if defined(_CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_MASK) +/***************************************************************************//** + * @brief + * Return upper value for CMU_HFXOSTEADYSTATECTRL_REGISH + ******************************************************************************/ +static uint32_t getRegIshUpperVal(uint32_t steadyStateRegIsh) +{ + uint32_t regIshUpper; + const uint32_t upperMax = _CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_MASK + >> _CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_SHIFT; + /* Add 3 as specified in register description for CMU_HFXOSTEADYSTATECTRL_REGISHUPPER. */ + regIshUpper = SL_MIN(steadyStateRegIsh + 3, upperMax); + regIshUpper <<= _CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_SHIFT; + return regIshUpper; +} +#endif + +#if defined(_CMU_HFXOCTRL_MASK) +/***************************************************************************//** + * @brief + * Get the HFXO tuning mode + * + * @return + * The current HFXO tuning mode from the HFXOCTRL register. + ******************************************************************************/ +__STATIC_INLINE uint32_t getHfxoTuningMode(void) +{ +#if defined(_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) + return (CMU->HFXOCTRL & _CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK); +#else + return (CMU->HFXOCTRL & _CMU_HFXOCTRL_PEAKDETMODE_MASK); +#endif +} + +/***************************************************************************//** + * @brief + * Set the HFXO tuning mode + * + * @param[in] mode + * the new HFXO tuning mode, this can be HFXO_TUNING_MODE_AUTO or + * HFXO_TUNING_MODE_CMD. + ******************************************************************************/ +__STATIC_INLINE void setHfxoTuningMode(uint32_t mode) +{ +#if defined(_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) + CMU->HFXOCTRL = (CMU->HFXOCTRL & ~_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) | mode; +#else + CMU->HFXOCTRL = (CMU->HFXOCTRL & ~_CMU_HFXOCTRL_PEAKDETMODE_MASK) | mode; +#endif +} +#endif + +/***************************************************************************//** + * @brief + * Get the LFnCLK frequency based on current configuration. + * + * @param[in] lfClkBranch + * Selected LF branch + * + * @return + * The LFnCLK frequency in Hz. If no LFnCLK is selected (disabled), 0 is + * returned. + ******************************************************************************/ +static uint32_t lfClkGet(CMU_Clock_TypeDef lfClkBranch) +{ + uint32_t sel; + uint32_t ret = 0; + + switch (lfClkBranch) { + case cmuClock_LFA: + case cmuClock_LFB: +#if defined(_CMU_LFCCLKEN0_MASK) + case cmuClock_LFC: +#endif +#if defined(_CMU_LFECLKSEL_MASK) + case cmuClock_LFE: +#endif + break; + + default: + EFM_ASSERT(0); + break; + } + + sel = CMU_ClockSelectGet(lfClkBranch); + + /* Get clock select field */ + switch (lfClkBranch) { + case cmuClock_LFA: +#if defined(_CMU_LFCLKSEL_MASK) + sel = (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFA_MASK) >> _CMU_LFCLKSEL_LFA_SHIFT; +#elif defined(_CMU_LFACLKSEL_MASK) + sel = (CMU->LFACLKSEL & _CMU_LFACLKSEL_LFA_MASK) >> _CMU_LFACLKSEL_LFA_SHIFT; +#else + EFM_ASSERT(0); +#endif + break; + + case cmuClock_LFB: +#if defined(_CMU_LFCLKSEL_MASK) + sel = (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFB_MASK) >> _CMU_LFCLKSEL_LFB_SHIFT; +#elif defined(_CMU_LFBCLKSEL_MASK) + sel = (CMU->LFBCLKSEL & _CMU_LFBCLKSEL_LFB_MASK) >> _CMU_LFBCLKSEL_LFB_SHIFT; +#else + EFM_ASSERT(0); +#endif + break; + +#if defined(_CMU_LFCCLKEN0_MASK) + case cmuClock_LFC: +#if defined(_CMU_LFCLKSEL_LFC_MASK) + sel = (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFC_MASK) >> _CMU_LFCLKSEL_LFC_SHIFT; +#elif defined(_CMU_LFCCLKSEL_LFC_MASK) + sel = (CMU->LFCCLKSEL & _CMU_LFCCLKSEL_LFC_MASK) >> _CMU_LFCCLKSEL_LFC_SHIFT; +#else + EFM_ASSERT(0); +#endif + break; +#endif + +#if defined(_CMU_LFECLKSEL_MASK) + case cmuClock_LFE: + sel = (CMU->LFECLKSEL & _CMU_LFECLKSEL_LFE_MASK) >> _CMU_LFECLKSEL_LFE_SHIFT; + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + + /* Get clock frequency */ +#if defined(_CMU_LFCLKSEL_MASK) + switch (sel) { + case _CMU_LFCLKSEL_LFA_LFRCO: + ret = SystemLFRCOClockGet(); + break; + + case _CMU_LFCLKSEL_LFA_LFXO: + ret = SystemLFXOClockGet(); + break; + +#if defined(_CMU_LFCLKSEL_LFA_HFCORECLKLEDIV2) + case _CMU_LFCLKSEL_LFA_HFCORECLKLEDIV2: +#if defined(CMU_MAX_FREQ_HFLE) + /* HFLE bit is or'ed by hardware with HFCORECLKLEDIV to reduce the + * frequency of CMU_HFCORECLKLEDIV2. */ + ret = SystemCoreClockGet() / (1U << (getHfLeConfig() + 1)); +#else + ret = SystemCoreClockGet() / 2U; +#endif + break; +#endif + + case _CMU_LFCLKSEL_LFA_DISABLED: + ret = 0; +#if defined(CMU_LFCLKSEL_LFAE) + /* Check LF Extended bit setting for LFA or LFB ULFRCO clock */ + if ((lfClkBranch == cmuClock_LFA) || (lfClkBranch == cmuClock_LFB)) { + if (CMU->LFCLKSEL >> (lfClkBranch == cmuClock_LFA + ? _CMU_LFCLKSEL_LFAE_SHIFT + : _CMU_LFCLKSEL_LFBE_SHIFT)) { + ret = SystemULFRCOClockGet(); + } + } +#endif + break; + + default: + EFM_ASSERT(0); + ret = 0U; + break; + } +#endif /* _CMU_LFCLKSEL_MASK */ + +#if defined(_CMU_LFACLKSEL_MASK) + switch (sel) { + case _CMU_LFACLKSEL_LFA_LFRCO: + ret = SystemLFRCOClockGet(); + break; + + case _CMU_LFACLKSEL_LFA_LFXO: + ret = SystemLFXOClockGet(); + break; + + case _CMU_LFACLKSEL_LFA_ULFRCO: + ret = SystemULFRCOClockGet(); + break; + +#if defined(CMU_LFACLKSEL_LFA_PLFRCO) + case _CMU_LFACLKSEL_LFA_PLFRCO: + ret = SystemLFRCOClockGet(); + break; +#endif + +#if defined(_CMU_LFACLKSEL_LFA_HFCLKLE) + case _CMU_LFACLKSEL_LFA_HFCLKLE: + ret = SystemCoreClockGet() + / CMU_Log2ToDiv(((CMU->HFPRESC & _CMU_HFPRESC_HFCLKLEPRESC_MASK) + >> _CMU_HFPRESC_HFCLKLEPRESC_SHIFT) + 1); + break; +#elif defined(_CMU_LFBCLKSEL_LFB_HFCLKLE) + case _CMU_LFBCLKSEL_LFB_HFCLKLE: + ret = SystemCoreClockGet() + / CMU_Log2ToDiv(((CMU->HFPRESC & _CMU_HFPRESC_HFCLKLEPRESC_MASK) + >> _CMU_HFPRESC_HFCLKLEPRESC_SHIFT) + 1); + break; +#endif + + case _CMU_LFACLKSEL_LFA_DISABLED: + ret = 0; + break; + } +#endif + + return ret; +} + +/***************************************************************************//** + * @brief + * Wait for ongoing sync of register(s) to low frequency domain to complete. + * + * @param[in] mask + * Bitmask corresponding to SYNCBUSY register defined bits, indicating + * registers that must complete any ongoing synchronization. + ******************************************************************************/ +__STATIC_INLINE void syncReg(uint32_t mask) +{ + /* Avoid deadlock if modifying the same register twice when freeze mode is */ + /* activated. */ + if (CMU->FREEZE & CMU_FREEZE_REGFREEZE) { + return; + } + + /* Wait for any pending previous write operation to have been completed */ + /* in low frequency domain */ + while (CMU->SYNCBUSY & mask) { + } +} + +#if defined(USBC_CLOCK_PRESENT) +/***************************************************************************//** + * @brief + * Get the USBC frequency + * + * @return + * USBC frequency in Hz + ******************************************************************************/ +static uint32_t usbCClkGet(void) +{ + uint32_t ret; + CMU_Select_TypeDef clk; + + /* Get selected clock source */ + clk = CMU_ClockSelectGet(cmuClock_USBC); + + switch (clk) { + case cmuSelect_LFXO: + ret = SystemLFXOClockGet(); + break; + case cmuSelect_LFRCO: + ret = SystemLFRCOClockGet(); + break; +#if defined (_CMU_USHFRCOCTRL_MASK) + case cmuSelect_USHFRCO: + ret = ushfrcoFreq; + break; +#endif + case cmuSelect_HFCLK: + ret = SystemHFClockGet(); + break; + default: + /* Clock is not enabled */ + ret = 0; + break; + } + return ret; +} +#endif + +/** @endcond */ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +#if defined(_CMU_AUXHFRCOCTRL_BAND_MASK) +/***************************************************************************//** + * @brief + * Get AUXHFRCO band in use. + * + * @return + * AUXHFRCO band in use. + ******************************************************************************/ +CMU_AUXHFRCOBand_TypeDef CMU_AUXHFRCOBandGet(void) +{ + return (CMU_AUXHFRCOBand_TypeDef)((CMU->AUXHFRCOCTRL + & _CMU_AUXHFRCOCTRL_BAND_MASK) + >> _CMU_AUXHFRCOCTRL_BAND_SHIFT); +} +#endif /* _CMU_AUXHFRCOCTRL_BAND_MASK */ + +#if defined(_CMU_AUXHFRCOCTRL_BAND_MASK) +/***************************************************************************//** + * @brief + * Set AUXHFRCO band and the tuning value based on the value in the + * calibration table made during production. + * + * @param[in] band + * AUXHFRCO band to activate. + ******************************************************************************/ +void CMU_AUXHFRCOBandSet(CMU_AUXHFRCOBand_TypeDef band) +{ + uint32_t tuning; + + /* Read tuning value from calibration table */ + switch (band) { + case cmuAUXHFRCOBand_1MHz: + tuning = (DEVINFO->AUXHFRCOCAL0 & _DEVINFO_AUXHFRCOCAL0_BAND1_MASK) + >> _DEVINFO_AUXHFRCOCAL0_BAND1_SHIFT; + break; + + case cmuAUXHFRCOBand_7MHz: + tuning = (DEVINFO->AUXHFRCOCAL0 & _DEVINFO_AUXHFRCOCAL0_BAND7_MASK) + >> _DEVINFO_AUXHFRCOCAL0_BAND7_SHIFT; + break; + + case cmuAUXHFRCOBand_11MHz: + tuning = (DEVINFO->AUXHFRCOCAL0 & _DEVINFO_AUXHFRCOCAL0_BAND11_MASK) + >> _DEVINFO_AUXHFRCOCAL0_BAND11_SHIFT; + break; + + case cmuAUXHFRCOBand_14MHz: + tuning = (DEVINFO->AUXHFRCOCAL0 & _DEVINFO_AUXHFRCOCAL0_BAND14_MASK) + >> _DEVINFO_AUXHFRCOCAL0_BAND14_SHIFT; + break; + + case cmuAUXHFRCOBand_21MHz: + tuning = (DEVINFO->AUXHFRCOCAL1 & _DEVINFO_AUXHFRCOCAL1_BAND21_MASK) + >> _DEVINFO_AUXHFRCOCAL1_BAND21_SHIFT; + break; + +#if defined(_CMU_AUXHFRCOCTRL_BAND_28MHZ) + case cmuAUXHFRCOBand_28MHz: + tuning = (DEVINFO->AUXHFRCOCAL1 & _DEVINFO_AUXHFRCOCAL1_BAND28_MASK) + >> _DEVINFO_AUXHFRCOCAL1_BAND28_SHIFT; + break; +#endif + + default: + EFM_ASSERT(0); + return; + } + + /* Set band/tuning */ + CMU->AUXHFRCOCTRL = (CMU->AUXHFRCOCTRL + & ~(_CMU_AUXHFRCOCTRL_BAND_MASK + | _CMU_AUXHFRCOCTRL_TUNING_MASK)) + | (band << _CMU_AUXHFRCOCTRL_BAND_SHIFT) + | (tuning << _CMU_AUXHFRCOCTRL_TUNING_SHIFT); +} +#endif /* _CMU_AUXHFRCOCTRL_BAND_MASK */ + +#if defined(_CMU_AUXHFRCOCTRL_FREQRANGE_MASK) +/**************************************************************************//** + * @brief + * Get the AUXHFRCO frequency calibration word in DEVINFO + * + * @param[in] freq + * Frequency in Hz + * + * @return + * AUXHFRCO calibration word for a given frequency + *****************************************************************************/ +static uint32_t CMU_AUXHFRCODevinfoGet(CMU_AUXHFRCOFreq_TypeDef freq) +{ + switch (freq) { + /* 1, 2 and 4MHz share the same calibration word */ + case cmuAUXHFRCOFreq_1M0Hz: + case cmuAUXHFRCOFreq_2M0Hz: + case cmuAUXHFRCOFreq_4M0Hz: + return DEVINFO->AUXHFRCOCAL0; + + case cmuAUXHFRCOFreq_7M0Hz: + return DEVINFO->AUXHFRCOCAL3; + + case cmuAUXHFRCOFreq_13M0Hz: + return DEVINFO->AUXHFRCOCAL6; + + case cmuAUXHFRCOFreq_16M0Hz: + return DEVINFO->AUXHFRCOCAL7; + + case cmuAUXHFRCOFreq_19M0Hz: + return DEVINFO->AUXHFRCOCAL8; + + case cmuAUXHFRCOFreq_26M0Hz: + return DEVINFO->AUXHFRCOCAL10; + + case cmuAUXHFRCOFreq_32M0Hz: + return DEVINFO->AUXHFRCOCAL11; + + case cmuAUXHFRCOFreq_38M0Hz: + return DEVINFO->AUXHFRCOCAL12; + +#if defined(DEVINFO_AUXHFRCOCAL14) + case cmuAUXHFRCOFreq_48M0Hz: + return DEVINFO->AUXHFRCOCAL13; + + case cmuAUXHFRCOFreq_50M0Hz: + return DEVINFO->AUXHFRCOCAL14; +#endif + + default: /* cmuAUXHFRCOFreq_UserDefined */ + return 0; + } +} +#endif /* _CMU_AUXHFRCOCTRL_FREQRANGE_MASK */ + +#if defined(_CMU_AUXHFRCOCTRL_FREQRANGE_MASK) +/***************************************************************************//** + * @brief + * Get current AUXHFRCO frequency. + * + * @return + * AUXHFRCO frequency + ******************************************************************************/ +CMU_AUXHFRCOFreq_TypeDef CMU_AUXHFRCOBandGet(void) +{ + return auxHfrcoFreq; +} +#endif /* _CMU_AUXHFRCOCTRL_FREQRANGE_MASK */ + +#if defined(_CMU_AUXHFRCOCTRL_FREQRANGE_MASK) +/***************************************************************************//** + * @brief + * Set AUXHFRCO calibration for the selected target frequency. + * + * @param[in] setFreq + * AUXHFRCO frequency to set + ******************************************************************************/ +void CMU_AUXHFRCOBandSet(CMU_AUXHFRCOFreq_TypeDef setFreq) +{ + uint32_t freqCal; + + /* Get DEVINFO index, set global auxHfrcoFreq */ + freqCal = CMU_AUXHFRCODevinfoGet(setFreq); + EFM_ASSERT((freqCal != 0) && (freqCal != UINT_MAX)); + auxHfrcoFreq = setFreq; + + /* Wait for any previous sync to complete, and then set calibration data + for the selected frequency. */ + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_AUXHFRCOBSY_SHIFT)) ; + + /* Set divider in AUXHFRCOCTRL for 1, 2 and 4MHz */ + switch (setFreq) { + case cmuAUXHFRCOFreq_1M0Hz: + freqCal = (freqCal & ~_CMU_AUXHFRCOCTRL_CLKDIV_MASK) + | CMU_AUXHFRCOCTRL_CLKDIV_DIV4; + break; + + case cmuAUXHFRCOFreq_2M0Hz: + freqCal = (freqCal & ~_CMU_AUXHFRCOCTRL_CLKDIV_MASK) + | CMU_AUXHFRCOCTRL_CLKDIV_DIV2; + break; + + case cmuAUXHFRCOFreq_4M0Hz: + freqCal = (freqCal & ~_CMU_AUXHFRCOCTRL_CLKDIV_MASK) + | CMU_AUXHFRCOCTRL_CLKDIV_DIV1; + break; + + default: + break; + } + CMU->AUXHFRCOCTRL = freqCal; +} +#endif /* _CMU_AUXHFRCOCTRL_FREQRANGE_MASK */ + +/***************************************************************************//** + * @brief + * Calibrate clock. + * + * @details + * Run a calibration for HFCLK against a selectable reference clock. Please + * refer to the reference manual, CMU chapter, for further details. + * + * @note + * This function will not return until calibration measurement is completed. + * + * @param[in] HFCycles + * The number of HFCLK cycles to run calibration. Increasing this number + * increases precision, but the calibration will take more time. + * + * @param[in] ref + * The reference clock used to compare HFCLK with. + * + * @return + * The number of ticks the reference clock after HFCycles ticks on the HF + * clock. + ******************************************************************************/ +uint32_t CMU_Calibrate(uint32_t HFCycles, CMU_Osc_TypeDef ref) +{ + EFM_ASSERT(HFCycles <= (_CMU_CALCNT_CALCNT_MASK >> _CMU_CALCNT_CALCNT_SHIFT)); + + /* Set reference clock source */ + switch (ref) { + case cmuOsc_LFXO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_LFXO; + break; + + case cmuOsc_LFRCO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_LFRCO; + break; + + case cmuOsc_HFXO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_HFXO; + break; + + case cmuOsc_HFRCO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_HFRCO; + break; + + case cmuOsc_AUXHFRCO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_AUXHFRCO; + break; + +#if defined (_CMU_USHFRCOCTRL_MASK) + case cmuOsc_USHFRCO: + CMU->CALCTRL = CMU_CALCTRL_UPSEL_USHFRCO; + break; +#endif + + default: + EFM_ASSERT(0); + return 0; + } + + /* Set top value */ + CMU->CALCNT = HFCycles; + + /* Start calibration */ + CMU->CMD = CMU_CMD_CALSTART; + +#if defined(CMU_STATUS_CALRDY) + /* Wait until calibration completes */ + while (!BUS_RegBitRead(&CMU->STATUS, _CMU_STATUS_CALRDY_SHIFT)) { + } +#else + /* Wait until calibration completes */ + while (BUS_RegBitRead(&CMU->STATUS, _CMU_STATUS_CALBSY_SHIFT)) { + } +#endif + + return CMU->CALCNT; +} + +#if defined(_CMU_CALCTRL_UPSEL_MASK) && defined(_CMU_CALCTRL_DOWNSEL_MASK) +/***************************************************************************//** + * @brief + * Configure clock calibration + * + * @details + * Configure a calibration for a selectable clock source against another + * selectable reference clock. + * Refer to the reference manual, CMU chapter, for further details. + * + * @note + * After configuration, a call to CMU_CalibrateStart() is required, and + * the resulting calibration value can be read out with the + * CMU_CalibrateCountGet() function call. + * + * @param[in] downCycles + * The number of downSel clock cycles to run calibration. Increasing this + * number increases precision, but the calibration will take more time. + * + * @param[in] downSel + * The clock which will be counted down downCycles + * + * @param[in] upSel + * The reference clock, the number of cycles generated by this clock will + * be counted and added up, the result can be given with the + * CMU_CalibrateCountGet() function call. + ******************************************************************************/ +void CMU_CalibrateConfig(uint32_t downCycles, CMU_Osc_TypeDef downSel, + CMU_Osc_TypeDef upSel) +{ + /* Keep untouched configuration settings */ + uint32_t calCtrl = CMU->CALCTRL + & ~(_CMU_CALCTRL_UPSEL_MASK | _CMU_CALCTRL_DOWNSEL_MASK); + + /* 20 bits of precision to calibration count register */ + EFM_ASSERT(downCycles <= (_CMU_CALCNT_CALCNT_MASK >> _CMU_CALCNT_CALCNT_SHIFT)); + + /* Set down counting clock source - down counter */ + switch (downSel) { + case cmuOsc_LFXO: + calCtrl |= CMU_CALCTRL_DOWNSEL_LFXO; + break; + + case cmuOsc_LFRCO: + calCtrl |= CMU_CALCTRL_DOWNSEL_LFRCO; + break; + + case cmuOsc_HFXO: + calCtrl |= CMU_CALCTRL_DOWNSEL_HFXO; + break; + + case cmuOsc_HFRCO: + calCtrl |= CMU_CALCTRL_DOWNSEL_HFRCO; + break; + + case cmuOsc_AUXHFRCO: + calCtrl |= CMU_CALCTRL_DOWNSEL_AUXHFRCO; + break; + +#if defined (_CMU_USHFRCOCTRL_MASK) + case cmuOsc_USHFRCO: + calCtrl |= CMU_CALCTRL_DOWNSEL_USHFRCO; + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + + /* Set top value to be counted down by the downSel clock */ + CMU->CALCNT = downCycles; + + /* Set reference clock source - up counter */ + switch (upSel) { + case cmuOsc_LFXO: + calCtrl |= CMU_CALCTRL_UPSEL_LFXO; + break; + + case cmuOsc_LFRCO: + calCtrl |= CMU_CALCTRL_UPSEL_LFRCO; + break; + + case cmuOsc_HFXO: + calCtrl |= CMU_CALCTRL_UPSEL_HFXO; + break; + + case cmuOsc_HFRCO: + calCtrl |= CMU_CALCTRL_UPSEL_HFRCO; + break; + + case cmuOsc_AUXHFRCO: + calCtrl |= CMU_CALCTRL_UPSEL_AUXHFRCO; + break; + +#if defined (_CMU_USHFRCOCTRL_MASK) + case cmuOsc_USHFRCO: + calCtrl |= CMU_CALCTRL_UPSEL_USHFRCO; + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + + CMU->CALCTRL = calCtrl; +} +#endif + +/***************************************************************************//** + * @brief + * Get calibration count register + * @note + * If continuous calibrartion mode is active, calibration busy will almost + * always be off, and we just need to read the value, where the normal case + * would be that this function call has been triggered by the CALRDY + * interrupt flag. + * @return + * Calibration count, the number of UPSEL clocks (see CMU_CalibrateConfig) + * in the period of DOWNSEL oscillator clock cycles configured by a previous + * write operation to CMU->CALCNT + ******************************************************************************/ +uint32_t CMU_CalibrateCountGet(void) +{ + /* Wait until calibration completes, UNLESS continuous calibration mode is */ + /* active */ +#if defined(CMU_CALCTRL_CONT) + if (!BUS_RegBitRead(&CMU->CALCTRL, _CMU_CALCTRL_CONT_SHIFT)) { +#if defined(CMU_STATUS_CALRDY) + /* Wait until calibration completes */ + while (!BUS_RegBitRead(&CMU->STATUS, _CMU_STATUS_CALRDY_SHIFT)) { + } +#else + /* Wait until calibration completes */ + while (BUS_RegBitRead(&CMU->STATUS, _CMU_STATUS_CALBSY_SHIFT)) { + } +#endif + } +#else + while (BUS_RegBitRead(&CMU->STATUS, _CMU_STATUS_CALBSY_SHIFT)) { + } +#endif + return CMU->CALCNT; +} + +/***************************************************************************//** + * @brief + * Get clock divisor/prescaler. + * + * @param[in] clock + * Clock point to get divisor/prescaler for. Notice that not all clock points + * have a divisor/prescaler. Please refer to CMU overview in reference manual. + * + * @return + * The current clock point divisor/prescaler. 1 is returned + * if @p clock specifies a clock point without a divisor/prescaler. + ******************************************************************************/ +CMU_ClkDiv_TypeDef CMU_ClockDivGet(CMU_Clock_TypeDef clock) +{ +#if defined(_SILICON_LABS_32B_SERIES_1) + return 1 + (uint32_t)CMU_ClockPrescGet(clock); + +#elif defined(_SILICON_LABS_32B_SERIES_0) + uint32_t divReg; + CMU_ClkDiv_TypeDef ret; + + /* Get divisor reg id */ + divReg = (clock >> CMU_DIV_REG_POS) & CMU_DIV_REG_MASK; + + switch (divReg) { +#if defined(_CMU_CTRL_HFCLKDIV_MASK) + case CMU_HFCLKDIV_REG: + ret = 1 + ((CMU->CTRL & _CMU_CTRL_HFCLKDIV_MASK) + >> _CMU_CTRL_HFCLKDIV_SHIFT); + break; +#endif + + case CMU_HFPERCLKDIV_REG: + ret = (CMU_ClkDiv_TypeDef)((CMU->HFPERCLKDIV + & _CMU_HFPERCLKDIV_HFPERCLKDIV_MASK) + >> _CMU_HFPERCLKDIV_HFPERCLKDIV_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; + + case CMU_HFCORECLKDIV_REG: + ret = (CMU_ClkDiv_TypeDef)((CMU->HFCORECLKDIV + & _CMU_HFCORECLKDIV_HFCORECLKDIV_MASK) + >> _CMU_HFCORECLKDIV_HFCORECLKDIV_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; + + case CMU_LFAPRESC0_REG: + switch (clock) { + case cmuClock_RTC: + ret = (CMU_ClkDiv_TypeDef)((CMU->LFAPRESC0 & _CMU_LFAPRESC0_RTC_MASK) + >> _CMU_LFAPRESC0_RTC_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; + +#if defined(_CMU_LFAPRESC0_LETIMER0_MASK) + case cmuClock_LETIMER0: + ret = (CMU_ClkDiv_TypeDef)((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LETIMER0_MASK) + >> _CMU_LFAPRESC0_LETIMER0_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; +#endif + +#if defined(_CMU_LFAPRESC0_LCD_MASK) + case cmuClock_LCDpre: + ret = (CMU_ClkDiv_TypeDef)(((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LCD_MASK) + >> _CMU_LFAPRESC0_LCD_SHIFT) + + CMU_DivToLog2(cmuClkDiv_16)); + ret = CMU_Log2ToDiv(ret); + break; +#endif + +#if defined(_CMU_LFAPRESC0_LESENSE_MASK) + case cmuClock_LESENSE: + ret = (CMU_ClkDiv_TypeDef)((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LESENSE_MASK) + >> _CMU_LFAPRESC0_LESENSE_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; +#endif + + default: + EFM_ASSERT(0); + ret = cmuClkDiv_1; + break; + } + break; + + case CMU_LFBPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFBPRESC0_LEUART0_MASK) + case cmuClock_LEUART0: + ret = (CMU_ClkDiv_TypeDef)((CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART0_MASK) + >> _CMU_LFBPRESC0_LEUART0_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; +#endif + +#if defined(_CMU_LFBPRESC0_LEUART1_MASK) + case cmuClock_LEUART1: + ret = (CMU_ClkDiv_TypeDef)((CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART1_MASK) + >> _CMU_LFBPRESC0_LEUART1_SHIFT); + ret = CMU_Log2ToDiv(ret); + break; +#endif + + default: + EFM_ASSERT(0); + ret = cmuClkDiv_1; + break; + } + break; + + default: + EFM_ASSERT(0); + ret = cmuClkDiv_1; + break; + } + + return ret; +#endif +} + +/***************************************************************************//** + * @brief + * Set clock divisor/prescaler. + * + * @note + * If setting a LF clock prescaler, synchronization into the low frequency + * domain is required. If the same register is modified before a previous + * update has completed, this function will stall until the previous + * synchronization has completed. Please refer to CMU_FreezeEnable() for + * a suggestion on how to reduce stalling time in some use cases. + * + * @param[in] clock + * Clock point to set divisor/prescaler for. Notice that not all clock points + * have a divisor/prescaler, please refer to CMU overview in the reference + * manual. + * + * @param[in] div + * The clock divisor to use (<= cmuClkDiv_512). + ******************************************************************************/ +void CMU_ClockDivSet(CMU_Clock_TypeDef clock, CMU_ClkDiv_TypeDef div) +{ +#if defined(_SILICON_LABS_32B_SERIES_1) + CMU_ClockPrescSet(clock, (CMU_ClkPresc_TypeDef)(div - 1)); + +#elif defined(_SILICON_LABS_32B_SERIES_0) + uint32_t freq; + uint32_t divReg; + + /* Get divisor reg id */ + divReg = (clock >> CMU_DIV_REG_POS) & CMU_DIV_REG_MASK; + + switch (divReg) { +#if defined(_CMU_CTRL_HFCLKDIV_MASK) + case CMU_HFCLKDIV_REG: + EFM_ASSERT((div >= cmuClkDiv_1) && (div <= cmuClkDiv_8)); + + /* Configure worst case wait states for flash access before setting divisor */ + flashWaitStateMax(); + + /* Set divider */ + CMU->CTRL = (CMU->CTRL & ~_CMU_CTRL_HFCLKDIV_MASK) + | ((div - 1) << _CMU_CTRL_HFCLKDIV_SHIFT); + + /* Update CMSIS core clock variable */ + /* (The function will update the global variable) */ + freq = SystemCoreClockGet(); + + /* Optimize flash access wait state setting for current core clk */ + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + break; +#endif + + case CMU_HFPERCLKDIV_REG: + EFM_ASSERT((div >= cmuClkDiv_1) && (div <= cmuClkDiv_512)); + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + CMU->HFPERCLKDIV = (CMU->HFPERCLKDIV & ~_CMU_HFPERCLKDIV_HFPERCLKDIV_MASK) + | (div << _CMU_HFPERCLKDIV_HFPERCLKDIV_SHIFT); + break; + + case CMU_HFCORECLKDIV_REG: + EFM_ASSERT((div >= cmuClkDiv_1) && (div <= cmuClkDiv_512)); + + /* Configure worst case wait states for flash access before setting divisor */ + flashWaitStateMax(); + +#if defined(CMU_MAX_FREQ_HFLE) + setHfLeConfig(SystemHFClockGet() / div); +#endif + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->HFCORECLKDIV = (CMU->HFCORECLKDIV + & ~_CMU_HFCORECLKDIV_HFCORECLKDIV_MASK) + | (div << _CMU_HFCORECLKDIV_HFCORECLKDIV_SHIFT); + + /* Update CMSIS core clock variable */ + /* (The function will update the global variable) */ + freq = SystemCoreClockGet(); + + /* Optimize wait state setting for current core clk */ + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + break; + + case CMU_LFAPRESC0_REG: + switch (clock) { + case cmuClock_RTC: + EFM_ASSERT(div <= cmuClkDiv_32768); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_RTC_MASK) + | (div << _CMU_LFAPRESC0_RTC_SHIFT); + break; + +#if defined(_CMU_LFAPRESC0_LETIMER0_MASK) + case cmuClock_LETIMER0: + EFM_ASSERT(div <= cmuClkDiv_32768); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LETIMER0_MASK) + | (div << _CMU_LFAPRESC0_LETIMER0_SHIFT); + break; +#endif + +#if defined(LCD_PRESENT) + case cmuClock_LCDpre: + EFM_ASSERT((div >= cmuClkDiv_16) && (div <= cmuClkDiv_128)); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LCD_MASK) + | ((div - CMU_DivToLog2(cmuClkDiv_16)) + << _CMU_LFAPRESC0_LCD_SHIFT); + break; +#endif /* defined(LCD_PRESENT) */ + +#if defined(LESENSE_PRESENT) + case cmuClock_LESENSE: + EFM_ASSERT(div <= cmuClkDiv_8); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LESENSE_MASK) + | (div << _CMU_LFAPRESC0_LESENSE_SHIFT); + break; +#endif /* defined(LESENSE_PRESENT) */ + + default: + EFM_ASSERT(0); + break; + } + break; + + case CMU_LFBPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFBPRESC0_LEUART0_MASK) + case cmuClock_LEUART0: + EFM_ASSERT(div <= cmuClkDiv_8); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFBPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFBPRESC0 = (CMU->LFBPRESC0 & ~_CMU_LFBPRESC0_LEUART0_MASK) + | (((uint32_t)div) << _CMU_LFBPRESC0_LEUART0_SHIFT); + break; +#endif + +#if defined(_CMU_LFBPRESC0_LEUART1_MASK) + case cmuClock_LEUART1: + EFM_ASSERT(div <= cmuClkDiv_8); + + /* LF register about to be modified require sync. busy check */ + syncReg(CMU_SYNCBUSY_LFBPRESC0); + + /* Convert to correct scale */ + div = CMU_DivToLog2(div); + + CMU->LFBPRESC0 = (CMU->LFBPRESC0 & ~_CMU_LFBPRESC0_LEUART1_MASK) + | (((uint32_t)div) << _CMU_LFBPRESC0_LEUART1_SHIFT); + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + break; + + default: + EFM_ASSERT(0); + break; + } +#endif +} + +/***************************************************************************//** + * @brief + * Enable/disable a clock. + * + * @details + * In general, module clocking is disabled after a reset. If a module + * clock is disabled, the registers of that module are not accessible and + * reading from such registers may return undefined values. Writing to + * registers of clock disabled modules have no effect. One should normally + * avoid accessing module registers of a module with a disabled clock. + * + * @note + * If enabling/disabling a LF clock, synchronization into the low frequency + * domain is required. If the same register is modified before a previous + * update has completed, this function will stall until the previous + * synchronization has completed. Please refer to CMU_FreezeEnable() for + * a suggestion on how to reduce stalling time in some use cases. + * + * @param[in] clock + * The clock to enable/disable. Notice that not all defined clock + * points have separate enable/disable control, please refer to CMU overview + * in reference manual. + * + * @param[in] enable + * @li true - enable specified clock. + * @li false - disable specified clock. + ******************************************************************************/ +void CMU_ClockEnable(CMU_Clock_TypeDef clock, bool enable) +{ + volatile uint32_t *reg; + uint32_t bit; + uint32_t sync = 0; + + /* Identify enable register */ + switch ((clock >> CMU_EN_REG_POS) & CMU_EN_REG_MASK) { +#if defined(_CMU_CTRL_HFPERCLKEN_MASK) + case CMU_CTRL_EN_REG: + reg = &CMU->CTRL; + break; +#endif + +#if defined(_CMU_HFCORECLKEN0_MASK) + case CMU_HFCORECLKEN0_EN_REG: + reg = &CMU->HFCORECLKEN0; +#if defined(CMU_MAX_FREQ_HFLE) + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); +#endif + break; +#endif + +#if defined(_CMU_HFBUSCLKEN0_MASK) + case CMU_HFBUSCLKEN0_EN_REG: + reg = &CMU->HFBUSCLKEN0; + break; +#endif + +#if defined(_CMU_HFPERCLKDIV_MASK) + case CMU_HFPERCLKDIV_EN_REG: + reg = &CMU->HFPERCLKDIV; + break; +#endif + + case CMU_HFPERCLKEN0_EN_REG: + reg = &CMU->HFPERCLKEN0; + break; + +#if defined(_CMU_HFPERCLKEN1_MASK) + case CMU_HFPERCLKEN1_EN_REG: + reg = &CMU->HFPERCLKEN1; + break; +#endif + + case CMU_LFACLKEN0_EN_REG: + reg = &CMU->LFACLKEN0; + sync = CMU_SYNCBUSY_LFACLKEN0; + break; + + case CMU_LFBCLKEN0_EN_REG: + reg = &CMU->LFBCLKEN0; + sync = CMU_SYNCBUSY_LFBCLKEN0; + break; + +#if defined(_CMU_LFCCLKEN0_MASK) + case CMU_LFCCLKEN0_EN_REG: + reg = &CMU->LFCCLKEN0; + sync = CMU_SYNCBUSY_LFCCLKEN0; + break; +#endif + +#if defined(_CMU_LFECLKEN0_MASK) + case CMU_LFECLKEN0_EN_REG: + reg = &CMU->LFECLKEN0; + sync = CMU_SYNCBUSY_LFECLKEN0; + break; +#endif + +#if defined(_CMU_SDIOCTRL_MASK) + case CMU_SDIOREF_EN_REG: + reg = &CMU->SDIOCTRL; + enable = !enable; + break; +#endif + +#if defined(_CMU_QSPICTRL_MASK) + case CMU_QSPI0REF_EN_REG: + reg = &CMU->QSPICTRL; + enable = !enable; + break; +#endif +#if defined(_CMU_USBCTRL_MASK) + case CMU_USBRCLK_EN_REG: + reg = &CMU->USBCTRL; + break; +#endif + + case CMU_PCNT_EN_REG: + reg = &CMU->PCNTCTRL; + break; + + default: /* Cannot enable/disable clock point */ + EFM_ASSERT(0); + return; + } + + /* Get bit position used to enable/disable */ + bit = (clock >> CMU_EN_BIT_POS) & CMU_EN_BIT_MASK; + + /* LF synchronization required? */ + if (sync) { + syncReg(sync); + } + + /* Set/clear bit as requested */ + BUS_RegBitWrite(reg, bit, enable); +} + +/***************************************************************************//** + * @brief + * Get clock frequency for a clock point. + * + * @param[in] clock + * Clock point to fetch frequency for. + * + * @return + * The current frequency in Hz. + ******************************************************************************/ +uint32_t CMU_ClockFreqGet(CMU_Clock_TypeDef clock) +{ + uint32_t ret; + + switch (clock & (CMU_CLK_BRANCH_MASK << CMU_CLK_BRANCH_POS)) { + case (CMU_HF_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = SystemHFClockGet(); + break; + + case (CMU_HFPER_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = SystemHFClockGet(); + /* Calculate frequency after HFPER divider. */ +#if defined(_CMU_HFPERCLKDIV_HFPERCLKDIV_MASK) + ret >>= (CMU->HFPERCLKDIV & _CMU_HFPERCLKDIV_HFPERCLKDIV_MASK) + >> _CMU_HFPERCLKDIV_HFPERCLKDIV_SHIFT; +#endif +#if defined(_CMU_HFPERPRESC_PRESC_MASK) + ret /= 1U + ((CMU->HFPERPRESC & _CMU_HFPERPRESC_PRESC_MASK) + >> _CMU_HFPERPRESC_PRESC_SHIFT); +#endif + break; + +#if defined(_SILICON_LABS_32B_SERIES_1) +#if defined(CRYPTO_PRESENT) \ + || defined(LDMA_PRESENT) \ + || defined(GPCRC_PRESENT) \ + || defined(PRS_PRESENT) \ + || defined(GPIO_PRESENT) + case (CMU_HFBUS_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = SystemHFClockGet(); + break; +#endif + + case (CMU_HFCORE_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = SystemHFClockGet(); + ret /= 1U + ((CMU->HFCOREPRESC & _CMU_HFCOREPRESC_PRESC_MASK) + >> _CMU_HFCOREPRESC_PRESC_SHIFT); + break; + + case (CMU_HFEXP_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = SystemHFClockGet(); + ret /= 1U + ((CMU->HFEXPPRESC & _CMU_HFEXPPRESC_PRESC_MASK) + >> _CMU_HFEXPPRESC_PRESC_SHIFT); + break; +#endif + +#if defined(_SILICON_LABS_32B_SERIES_0) +#if defined(AES_PRESENT) \ + || defined(DMA_PRESENT) \ + || defined(EBI_PRESENT) \ + || defined(USB_PRESENT) + case (CMU_HFCORE_CLK_BRANCH << CMU_CLK_BRANCH_POS): + { + ret = SystemCoreClockGet(); + } break; +#endif +#endif + + case (CMU_LFA_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); + break; + +#if defined(_CMU_LFACLKEN0_RTC_MASK) + case (CMU_RTC_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); + ret >>= (CMU->LFAPRESC0 & _CMU_LFAPRESC0_RTC_MASK) + >> _CMU_LFAPRESC0_RTC_SHIFT; + break; +#endif + +#if defined(_CMU_LFECLKEN0_RTCC_MASK) + case (CMU_RTCC_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFE); + break; +#endif + +#if defined(_CMU_LFACLKEN0_LETIMER0_MASK) + case (CMU_LETIMER0_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); +#if defined(_SILICON_LABS_32B_SERIES_0) + ret >>= (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LETIMER0_MASK) + >> _CMU_LFAPRESC0_LETIMER0_SHIFT; +#else + ret /= CMU_Log2ToDiv((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LETIMER0_MASK) + >> _CMU_LFAPRESC0_LETIMER0_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_LFACLKEN0_LCD_MASK) + case (CMU_LCDPRE_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); +#if defined(_SILICON_LABS_32B_SERIES_0) + ret >>= ((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LCD_MASK) + >> _CMU_LFAPRESC0_LCD_SHIFT) + + CMU_DivToLog2(cmuClkDiv_16); +#else + ret /= CMU_Log2ToDiv((CMU->LFAPRESC0 & _CMU_LFAPRESC0_LCD_MASK) + >> _CMU_LFAPRESC0_LCD_SHIFT); +#endif + break; + +#if defined(_CMU_LCDCTRL_MASK) + case (CMU_LCD_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); + ret >>= (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LCD_MASK) + >> _CMU_LFAPRESC0_LCD_SHIFT; + ret /= 1U + ((CMU->LCDCTRL & _CMU_LCDCTRL_FDIV_MASK) + >> _CMU_LCDCTRL_FDIV_SHIFT); + break; +#endif +#endif + +#if defined(_CMU_LFACLKEN0_LESENSE_MASK) + case (CMU_LESENSE_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFA); + ret >>= (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LESENSE_MASK) + >> _CMU_LFAPRESC0_LESENSE_SHIFT; + break; +#endif + + case (CMU_LFB_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFB); + break; + +#if defined(_CMU_LFBCLKEN0_LEUART0_MASK) + case (CMU_LEUART0_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFB); +#if defined(_SILICON_LABS_32B_SERIES_0) + ret >>= (CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART0_MASK) + >> _CMU_LFBPRESC0_LEUART0_SHIFT; +#else + ret /= CMU_Log2ToDiv((CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART0_MASK) + >> _CMU_LFBPRESC0_LEUART0_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_LFBCLKEN0_LEUART1_MASK) + case (CMU_LEUART1_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFB); +#if defined(_SILICON_LABS_32B_SERIES_0) + ret >>= (CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART1_MASK) + >> _CMU_LFBPRESC0_LEUART1_SHIFT; +#else + ret /= CMU_Log2ToDiv((CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART1_MASK) + >> _CMU_LFBPRESC0_LEUART1_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_LFBCLKEN0_CSEN_MASK) + case (CMU_CSEN_LF_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFB); + ret /= CMU_Log2ToDiv(((CMU->LFBPRESC0 & _CMU_LFBPRESC0_CSEN_MASK) + >> _CMU_LFBPRESC0_CSEN_SHIFT) + 4); + break; +#endif + +#if defined(_SILICON_LABS_32B_SERIES_1) + case (CMU_LFE_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = lfClkGet(cmuClock_LFE); + break; +#endif + + case (CMU_DBG_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = dbgClkGet(); + break; + + case (CMU_AUX_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = auxClkGet(); + break; + +#if defined(USBC_CLOCK_PRESENT) + case (CMU_USBC_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = usbCClkGet(); + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC0CLKSEL_MASK) + case (CMU_ADC0ASYNC_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = adcAsyncClkGet(0); +#if defined(_CMU_ADCCTRL_ADC0CLKDIV_MASK) + ret /= 1U + ((CMU->ADCCTRL & _CMU_ADCCTRL_ADC0CLKDIV_MASK) + >> _CMU_ADCCTRL_ADC0CLKDIV_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC1CLKSEL_MASK) + case (CMU_ADC1ASYNC_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = adcAsyncClkGet(1); +#if defined(_CMU_ADCCTRL_ADC1CLKDIV_MASK) + ret /= 1U + ((CMU->ADCCTRL & _CMU_ADCCTRL_ADC1CLKDIV_MASK) + >> _CMU_ADCCTRL_ADC1CLKDIV_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_SDIOCTRL_SDIOCLKSEL_MASK) + case (CMU_SDIOREF_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = sdioRefClkGet(); + break; +#endif + +#if defined(_CMU_QSPICTRL_QSPI0CLKSEL_MASK) + case (CMU_QSPI0REF_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = qspiRefClkGet(0); + break; +#endif + +#if defined(USBR_CLOCK_PRESENT) + case (CMU_USBR_CLK_BRANCH << CMU_CLK_BRANCH_POS): + ret = usbRateClkGet(); + break; +#endif + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + + return ret; +} + +#if defined(_SILICON_LABS_32B_SERIES_1) +/***************************************************************************//** + * @brief + * Get clock prescaler. + * + * @param[in] clock + * Clock point to get the prescaler for. Notice that not all clock points + * have a prescaler. Please refer to CMU overview in reference manual. + * + * @return + * The prescaler value of the current clock point. 0 is returned + * if @p clock specifies a clock point without a prescaler. + ******************************************************************************/ +uint32_t CMU_ClockPrescGet(CMU_Clock_TypeDef clock) +{ + uint32_t prescReg; + uint32_t ret; + + /* Get prescaler register id. */ + prescReg = (clock >> CMU_PRESC_REG_POS) & CMU_PRESC_REG_MASK; + + switch (prescReg) { + case CMU_HFPRESC_REG: + ret = (CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK) + >> _CMU_HFPRESC_PRESC_SHIFT; + break; + + case CMU_HFEXPPRESC_REG: + ret = (CMU->HFEXPPRESC & _CMU_HFEXPPRESC_PRESC_MASK) + >> _CMU_HFEXPPRESC_PRESC_SHIFT; + break; + + case CMU_HFCLKLEPRESC_REG: + ret = (CMU->HFPRESC & _CMU_HFPRESC_HFCLKLEPRESC_MASK) + >> _CMU_HFPRESC_HFCLKLEPRESC_SHIFT; + break; + + case CMU_HFPERPRESC_REG: + ret = (CMU->HFPERPRESC & _CMU_HFPERPRESC_PRESC_MASK) + >> _CMU_HFPERPRESC_PRESC_SHIFT; + break; + + case CMU_HFCOREPRESC_REG: + ret = (CMU->HFCOREPRESC & _CMU_HFCOREPRESC_PRESC_MASK) + >> _CMU_HFCOREPRESC_PRESC_SHIFT; + break; + + case CMU_LFAPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFAPRESC0_LETIMER0_MASK) + case cmuClock_LETIMER0: + ret = (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LETIMER0_MASK) + >> _CMU_LFAPRESC0_LETIMER0_SHIFT; + /* Convert the exponent to prescaler value. */ + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFAPRESC0_LESENSE_MASK) + case cmuClock_LESENSE: + ret = (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LESENSE_MASK) + >> _CMU_LFAPRESC0_LESENSE_SHIFT; + /* Convert the exponent to prescaler value. */ + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFAPRESC0_LETIMER1_MASK) + case cmuClock_LETIMER1: + ret = (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LETIMER1_MASK) + >> _CMU_LFAPRESC0_LETIMER1_SHIFT; + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFAPRESC0_LCD_MASK) + case cmuClock_LCD: + case cmuClock_LCDpre: + ret = (CMU->LFAPRESC0 & _CMU_LFAPRESC0_LCD_MASK) + >> _CMU_LFAPRESC0_LCD_SHIFT; + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFAPRESC0_RTC_MASK) + case cmuClock_RTC: + ret = (CMU->LFAPRESC0 & _CMU_LFAPRESC0_RTC_MASK) + >> _CMU_LFAPRESC0_RTC_SHIFT; + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + + default: + EFM_ASSERT(0); + ret = 0U; + break; + } + break; + + case CMU_LFBPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFBPRESC0_LEUART0_MASK) + case cmuClock_LEUART0: + ret = (CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART0_MASK) + >> _CMU_LFBPRESC0_LEUART0_SHIFT; + /* Convert the exponent to prescaler value. */ + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFBPRESC0_LEUART1_MASK) + case cmuClock_LEUART1: + ret = (CMU->LFBPRESC0 & _CMU_LFBPRESC0_LEUART1_MASK) + >> _CMU_LFBPRESC0_LEUART1_SHIFT; + /* Convert the exponent to prescaler value. */ + ret = CMU_Log2ToDiv(ret) - 1U; + break; +#endif + +#if defined(_CMU_LFBPRESC0_CSEN_MASK) + case cmuClock_CSEN_LF: + ret = (CMU->LFBPRESC0 & _CMU_LFBPRESC0_CSEN_MASK) + >> _CMU_LFBPRESC0_CSEN_SHIFT; + /* Convert the exponent to prescaler value. */ + ret = CMU_Log2ToDiv(ret + 4) - 1U; + break; +#endif + + default: + EFM_ASSERT(0); + ret = 0U; + break; + } + break; + + case CMU_LFEPRESC0_REG: + switch (clock) { +#if defined(RTCC_PRESENT) + case cmuClock_RTCC: + /* No need to compute with LFEPRESC0_RTCC - DIV1 is the only */ + /* allowed value. Convert the exponent to prescaler value. */ + ret = _CMU_LFEPRESC0_RTCC_DIV1; + break; + + default: + EFM_ASSERT(0); + ret = 0U; + break; +#endif + } + break; + + case CMU_ADCASYNCDIV_REG: + switch (clock) { +#if defined(_CMU_ADCCTRL_ADC0CLKDIV_MASK) + case cmuClock_ADC0ASYNC: + ret = (CMU->ADCCTRL & _CMU_ADCCTRL_ADC0CLKDIV_MASK) + >> _CMU_ADCCTRL_ADC0CLKDIV_SHIFT; + break; +#endif +#if defined(_CMU_ADCCTRL_ADC1CLKDIV_MASK) + case cmuClock_ADC1ASYNC: + ret = (CMU->ADCCTRL & _CMU_ADCCTRL_ADC1CLKDIV_MASK) + >> _CMU_ADCCTRL_ADC1CLKDIV_SHIFT; + break; +#endif + default: + EFM_ASSERT(0); + ret = 0U; + break; + } + break; + + default: + EFM_ASSERT(0); + ret = 0U; + break; + } + + return ret; +} +#endif + +#if defined(_SILICON_LABS_32B_SERIES_1) +/***************************************************************************//** + * @brief + * Set clock prescaler. + * + * @note + * If setting a LF clock prescaler, synchronization into the low frequency + * domain is required. If the same register is modified before a previous + * update has completed, this function will stall until the previous + * synchronization has completed. Please refer to CMU_FreezeEnable() for + * a suggestion on how to reduce stalling time in some use cases. + * + * @param[in] clock + * Clock point to set prescaler for. Notice that not all clock points + * have a prescaler, please refer to CMU overview in the reference manual. + * + * @param[in] presc + * The clock prescaler to use. + ******************************************************************************/ +void CMU_ClockPrescSet(CMU_Clock_TypeDef clock, CMU_ClkPresc_TypeDef presc) +{ + uint32_t freq; + uint32_t prescReg; + + /* Get divisor reg id */ + prescReg = (clock >> CMU_PRESC_REG_POS) & CMU_PRESC_REG_MASK; + + switch (prescReg) { + case CMU_HFPRESC_REG: + EFM_ASSERT(presc < 32U); + + /* Configure worst case wait-states for flash and HFLE. */ + flashWaitStateMax(); + setHfLeConfig(CMU_MAX_FREQ_HFLE + 1); + + CMU->HFPRESC = (CMU->HFPRESC & ~_CMU_HFPRESC_PRESC_MASK) + | (presc << _CMU_HFPRESC_PRESC_SHIFT); + + /* Update CMSIS core clock variable (this function updates the global variable). + Optimize flash and HFLE wait states. */ + freq = SystemCoreClockGet(); + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); + break; + + case CMU_HFEXPPRESC_REG: + EFM_ASSERT(presc < 32U); + + CMU->HFEXPPRESC = (CMU->HFEXPPRESC & ~_CMU_HFEXPPRESC_PRESC_MASK) + | (presc << _CMU_HFEXPPRESC_PRESC_SHIFT); + break; + + case CMU_HFCLKLEPRESC_REG: +#if defined (CMU_HFPRESC_HFCLKLEPRESC_DIV8) + EFM_ASSERT(presc < 3U); +#else + EFM_ASSERT(presc < 2U); +#endif + + /* Specifies the clock divider for HFCLKLE. This clock divider must be set + * high enough for the divided clock frequency to be at or below the max + * frequency allowed for the HFCLKLE clock. */ + CMU->HFPRESC = (CMU->HFPRESC & ~_CMU_HFPRESC_HFCLKLEPRESC_MASK) + | (presc << _CMU_HFPRESC_HFCLKLEPRESC_SHIFT); + break; + + case CMU_HFPERPRESC_REG: + EFM_ASSERT(presc < 512U); + + CMU->HFPERPRESC = (CMU->HFPERPRESC & ~_CMU_HFPERPRESC_PRESC_MASK) + | (presc << _CMU_HFPERPRESC_PRESC_SHIFT); + break; + + case CMU_HFCOREPRESC_REG: + EFM_ASSERT(presc < 512U); + + /* Configure worst case wait-states for flash and HFLE. */ + flashWaitStateMax(); + setHfLeConfig(CMU_MAX_FREQ_HFLE + 1); + + CMU->HFCOREPRESC = (CMU->HFCOREPRESC & ~_CMU_HFCOREPRESC_PRESC_MASK) + | (presc << _CMU_HFCOREPRESC_PRESC_SHIFT); + + /* Update CMSIS core clock variable (this function updates the global variable). + Optimize flash and HFLE wait states. */ + freq = SystemCoreClockGet(); + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); + break; + + case CMU_LFAPRESC0_REG: + switch (clock) { +#if defined(RTC_PRESENT) + case cmuClock_RTC: + EFM_ASSERT(presc <= 32768U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_RTC_MASK) + | (presc << _CMU_LFAPRESC0_RTC_SHIFT); + break; +#endif + +#if defined(RTCC_PRESENT) + case cmuClock_RTCC: +#if defined(_CMU_LFEPRESC0_RTCC_MASK) + /* DIV1 is the only accepted value. */ + EFM_ASSERT(presc <= 0U); + + /* LF register about to be modified require sync. Busy check.. */ + syncReg(CMU_SYNCBUSY_LFEPRESC0); + + CMU->LFEPRESC0 = (CMU->LFEPRESC0 & ~_CMU_LFEPRESC0_RTCC_MASK) + | (presc << _CMU_LFEPRESC0_RTCC_SHIFT); +#else + EFM_ASSERT(presc <= 32768U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_RTCC_MASK) + | (presc << _CMU_LFAPRESC0_RTCC_SHIFT); +#endif + break; +#endif + +#if defined(_CMU_LFAPRESC0_LETIMER0_MASK) + case cmuClock_LETIMER0: + EFM_ASSERT(presc <= 32768U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LETIMER0_MASK) + | (presc << _CMU_LFAPRESC0_LETIMER0_SHIFT); + break; +#endif + +#if defined(_CMU_LFAPRESC0_LESENSE_MASK) + case cmuClock_LESENSE: + EFM_ASSERT(presc <= 8); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LESENSE_MASK) + | (presc << _CMU_LFAPRESC0_LESENSE_SHIFT); + break; +#endif + +#if defined(_CMU_LFAPRESC0_LCD_MASK) + case cmuClock_LCDpre: + case cmuClock_LCD: + { + EFM_ASSERT(presc <= 32768U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFAPRESC0); + + CMU->LFAPRESC0 = (CMU->LFAPRESC0 & ~_CMU_LFAPRESC0_LCD_MASK) + | (presc << _CMU_LFAPRESC0_LCD_SHIFT); + } break; +#endif + + default: + EFM_ASSERT(0); + break; + } + break; + + case CMU_LFBPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFBPRESC0_LEUART0_MASK) + case cmuClock_LEUART0: + EFM_ASSERT(presc <= 8U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFBPRESC0); + + CMU->LFBPRESC0 = (CMU->LFBPRESC0 & ~_CMU_LFBPRESC0_LEUART0_MASK) + | (presc << _CMU_LFBPRESC0_LEUART0_SHIFT); + break; +#endif + +#if defined(_CMU_LFBPRESC0_LEUART1_MASK) + case cmuClock_LEUART1: + EFM_ASSERT(presc <= 8U); + + /* Convert prescaler value to DIV exponent scale. */ + presc = CMU_PrescToLog2(presc); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFBPRESC0); + + CMU->LFBPRESC0 = (CMU->LFBPRESC0 & ~_CMU_LFBPRESC0_LEUART1_MASK) + | (presc << _CMU_LFBPRESC0_LEUART1_SHIFT); + break; +#endif + +#if defined(_CMU_LFBPRESC0_CSEN_MASK) + case cmuClock_CSEN_LF: + EFM_ASSERT((presc <= 127U) && (presc >= 15U)); + + /* Convert prescaler value to DIV exponent scale. + * DIV16 is the lowest supported prescaler. */ + presc = CMU_PrescToLog2(presc) - 4; + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFBPRESC0); + + CMU->LFBPRESC0 = (CMU->LFBPRESC0 & ~_CMU_LFBPRESC0_CSEN_MASK) + | (presc << _CMU_LFBPRESC0_CSEN_SHIFT); + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + break; + + case CMU_LFEPRESC0_REG: + switch (clock) { +#if defined(_CMU_LFEPRESC0_RTCC_MASK) + case cmuClock_RTCC: + EFM_ASSERT(presc <= 0U); + + /* LF register about to be modified require sync. Busy check. */ + syncReg(CMU_SYNCBUSY_LFEPRESC0); + + CMU->LFEPRESC0 = (CMU->LFEPRESC0 & ~_CMU_LFEPRESC0_RTCC_MASK) + | (presc << _CMU_LFEPRESC0_RTCC_SHIFT); + break; +#endif + + default: + EFM_ASSERT(0); + break; + } + break; + + case CMU_ADCASYNCDIV_REG: + switch (clock) { +#if defined(_CMU_ADCCTRL_ADC0CLKDIV_MASK) + case cmuClock_ADC0ASYNC: + EFM_ASSERT(presc <= 3); + CMU->ADCCTRL = (CMU->ADCCTRL & ~_CMU_ADCCTRL_ADC0CLKDIV_MASK) + | (presc << _CMU_ADCCTRL_ADC0CLKDIV_SHIFT); + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC1CLKDIV_MASK) + case cmuClock_ADC1ASYNC: + EFM_ASSERT(presc <= 3); + CMU->ADCCTRL = (CMU->ADCCTRL & ~_CMU_ADCCTRL_ADC1CLKDIV_MASK) + | (presc << _CMU_ADCCTRL_ADC1CLKDIV_SHIFT); + break; +#endif + default: + EFM_ASSERT(0); + break; + } + break; + + default: + EFM_ASSERT(0); + break; + } +} +#endif + +/***************************************************************************//** + * @brief + * Get currently selected reference clock used for a clock branch. + * + * @param[in] clock + * Clock branch to fetch selected ref. clock for. One of: + * @li #cmuClock_HF + * @li #cmuClock_LFA + * @li #cmuClock_LFB @if _CMU_LFCLKSEL_LFAE_ULFRCO + * @li #cmuClock_LFC + * @endif @if _SILICON_LABS_32B_SERIES_1 + * @li #cmuClock_LFE + * @endif + * @li #cmuClock_DBG @if DOXYDOC_USB_PRESENT + * @li #cmuClock_USBC + * @endif + * + * @return + * Reference clock used for clocking selected branch, #cmuSelect_Error if + * invalid @p clock provided. + ******************************************************************************/ +CMU_Select_TypeDef CMU_ClockSelectGet(CMU_Clock_TypeDef clock) +{ + CMU_Select_TypeDef ret = cmuSelect_Disabled; + uint32_t selReg; + + selReg = (clock >> CMU_SEL_REG_POS) & CMU_SEL_REG_MASK; + + switch (selReg) { + case CMU_HFCLKSEL_REG: +#if defined(_CMU_HFCLKSTATUS_MASK) + switch (CMU->HFCLKSTATUS & _CMU_HFCLKSTATUS_SELECTED_MASK) { + case CMU_HFCLKSTATUS_SELECTED_LFXO: + ret = cmuSelect_LFXO; + break; + + case CMU_HFCLKSTATUS_SELECTED_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_HFCLKSTATUS_SELECTED_HFXO: + ret = cmuSelect_HFXO; + break; + + default: + ret = cmuSelect_HFRCO; + break; + } +#else + switch (CMU->STATUS + & (CMU_STATUS_HFRCOSEL + | CMU_STATUS_HFXOSEL + | CMU_STATUS_LFRCOSEL +#if defined(CMU_STATUS_USHFRCODIV2SEL) + | CMU_STATUS_USHFRCODIV2SEL +#endif + | CMU_STATUS_LFXOSEL)) { + case CMU_STATUS_LFXOSEL: + ret = cmuSelect_LFXO; + break; + + case CMU_STATUS_LFRCOSEL: + ret = cmuSelect_LFRCO; + break; + + case CMU_STATUS_HFXOSEL: + ret = cmuSelect_HFXO; + break; + +#if defined(CMU_STATUS_USHFRCODIV2SEL) + case CMU_STATUS_USHFRCODIV2SEL: + ret = cmuSelect_USHFRCODIV2; + break; +#endif + + default: + ret = cmuSelect_HFRCO; + break; + } +#endif + break; + +#if defined(_CMU_LFCLKSEL_MASK) || defined(_CMU_LFACLKSEL_MASK) + case CMU_LFACLKSEL_REG: +#if defined(_CMU_LFCLKSEL_MASK) + switch (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFA_MASK) { + case CMU_LFCLKSEL_LFA_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFCLKSEL_LFA_LFXO: + ret = cmuSelect_LFXO; + break; + +#if defined(CMU_LFCLKSEL_LFA_HFCORECLKLEDIV2) + case CMU_LFCLKSEL_LFA_HFCORECLKLEDIV2: + ret = cmuSelect_HFCLKLE; + break; +#endif + + default: +#if defined(CMU_LFCLKSEL_LFAE) + if (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFAE_MASK) { + ret = cmuSelect_ULFRCO; + break; + } +#else + ret = cmuSelect_Disabled; +#endif + break; + } + +#elif defined(_CMU_LFACLKSEL_MASK) + switch (CMU->LFACLKSEL & _CMU_LFACLKSEL_LFA_MASK) { + case CMU_LFACLKSEL_LFA_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFACLKSEL_LFA_LFXO: + ret = cmuSelect_LFXO; + break; + + case CMU_LFACLKSEL_LFA_ULFRCO: + ret = cmuSelect_ULFRCO; + break; + +#if defined(_CMU_LFACLKSEL_LFA_HFCLKLE) + case CMU_LFACLKSEL_LFA_HFCLKLE: + ret = cmuSelect_HFCLKLE; + break; +#endif + +#if defined(CMU_LFACLKSEL_LFA_PLFRCO) + case CMU_LFACLKSEL_LFA_PLFRCO: + ret = cmuSelect_PLFRCO; + break; +#endif + + default: + ret = cmuSelect_Disabled; + break; + } +#endif + break; +#endif /* _CMU_LFCLKSEL_MASK || _CMU_LFACLKSEL_MASK */ + +#if defined(_CMU_LFCLKSEL_MASK) || defined(_CMU_LFBCLKSEL_MASK) + case CMU_LFBCLKSEL_REG: +#if defined(_CMU_LFCLKSEL_MASK) + switch (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFB_MASK) { + case CMU_LFCLKSEL_LFB_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFCLKSEL_LFB_LFXO: + ret = cmuSelect_LFXO; + break; + +#if defined(CMU_LFCLKSEL_LFB_HFCORECLKLEDIV2) + case CMU_LFCLKSEL_LFB_HFCORECLKLEDIV2: + ret = cmuSelect_HFCLKLE; + break; +#endif + +#if defined(CMU_LFCLKSEL_LFB_HFCLKLE) + case CMU_LFCLKSEL_LFB_HFCLKLE: + ret = cmuSelect_HFCLKLE; + break; +#endif + + default: +#if defined(CMU_LFCLKSEL_LFBE) + if (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFBE_MASK) { + ret = cmuSelect_ULFRCO; + break; + } +#else + ret = cmuSelect_Disabled; +#endif + break; + } + +#elif defined(_CMU_LFBCLKSEL_MASK) + switch (CMU->LFBCLKSEL & _CMU_LFBCLKSEL_LFB_MASK) { + case CMU_LFBCLKSEL_LFB_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFBCLKSEL_LFB_LFXO: + ret = cmuSelect_LFXO; + break; + + case CMU_LFBCLKSEL_LFB_ULFRCO: + ret = cmuSelect_ULFRCO; + break; + + case CMU_LFBCLKSEL_LFB_HFCLKLE: + ret = cmuSelect_HFCLKLE; + break; + +#if defined(CMU_LFBCLKSEL_LFB_PLFRCO) + case CMU_LFBCLKSEL_LFB_PLFRCO: + ret = cmuSelect_PLFRCO; + break; +#endif + + default: + ret = cmuSelect_Disabled; + break; + } +#endif + break; +#endif /* _CMU_LFCLKSEL_MASK || _CMU_LFBCLKSEL_MASK */ + +#if defined(_CMU_LFCLKSEL_LFC_MASK) + case CMU_LFCCLKSEL_REG: + switch (CMU->LFCLKSEL & _CMU_LFCLKSEL_LFC_MASK) { + case CMU_LFCLKSEL_LFC_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFCLKSEL_LFC_LFXO: + ret = cmuSelect_LFXO; + break; + + default: + ret = cmuSelect_Disabled; + break; + } + break; +#endif + +#if defined(_CMU_LFECLKSEL_LFE_MASK) + case CMU_LFECLKSEL_REG: + switch (CMU->LFECLKSEL & _CMU_LFECLKSEL_LFE_MASK) { + case CMU_LFECLKSEL_LFE_LFRCO: + ret = cmuSelect_LFRCO; + break; + + case CMU_LFECLKSEL_LFE_LFXO: + ret = cmuSelect_LFXO; + break; + + case CMU_LFECLKSEL_LFE_ULFRCO: + ret = cmuSelect_ULFRCO; + break; + +#if defined (_CMU_LFECLKSEL_LFE_HFCLKLE) + case CMU_LFECLKSEL_LFE_HFCLKLE: + ret = cmuSelect_HFCLKLE; + break; +#endif + +#if defined(CMU_LFECLKSEL_LFE_PLFRCO) + case CMU_LFECLKSEL_LFE_PLFRCO: + ret = cmuSelect_PLFRCO; + break; +#endif + + default: + ret = cmuSelect_Disabled; + break; + } + break; +#endif /* CMU_LFECLKSEL_REG */ + + case CMU_DBGCLKSEL_REG: +#if defined(_CMU_DBGCLKSEL_DBG_MASK) + switch (CMU->DBGCLKSEL & _CMU_DBGCLKSEL_DBG_MASK) { + case CMU_DBGCLKSEL_DBG_HFCLK: + ret = cmuSelect_HFCLK; + break; + + case CMU_DBGCLKSEL_DBG_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + } + +#elif defined(_CMU_CTRL_DBGCLK_MASK) + switch (CMU->CTRL & _CMU_CTRL_DBGCLK_MASK) { + case CMU_CTRL_DBGCLK_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + + case CMU_CTRL_DBGCLK_HFCLK: + ret = cmuSelect_HFCLK; + break; + } +#else + ret = cmuSelect_AUXHFRCO; +#endif + break; + +#if defined(USBC_CLOCK_PRESENT) + case CMU_USBCCLKSEL_REG: + switch (CMU->STATUS + & (CMU_STATUS_USBCLFXOSEL +#if defined(_CMU_STATUS_USBCHFCLKSEL_MASK) + | CMU_STATUS_USBCHFCLKSEL +#endif +#if defined(_CMU_STATUS_USBCUSHFRCOSEL_MASK) + | CMU_STATUS_USBCUSHFRCOSEL +#endif + | CMU_STATUS_USBCLFRCOSEL)) { +#if defined(_CMU_STATUS_USBCHFCLKSEL_MASK) + case CMU_STATUS_USBCHFCLKSEL: + ret = cmuSelect_HFCLK; + break; +#endif + +#if defined(_CMU_STATUS_USBCUSHFRCOSEL_MASK) + case CMU_STATUS_USBCUSHFRCOSEL: + ret = cmuSelect_USHFRCO; + break; +#endif + + case CMU_STATUS_USBCLFXOSEL: + ret = cmuSelect_LFXO; + break; + + case CMU_STATUS_USBCLFRCOSEL: + ret = cmuSelect_LFRCO; + break; + + default: + ret = cmuSelect_Disabled; + break; + } + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC0CLKSEL_MASK) + case CMU_ADC0ASYNCSEL_REG: + switch (CMU->ADCCTRL & _CMU_ADCCTRL_ADC0CLKSEL_MASK) { + case CMU_ADCCTRL_ADC0CLKSEL_DISABLED: + ret = cmuSelect_Disabled; + break; + + case CMU_ADCCTRL_ADC0CLKSEL_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + + case CMU_ADCCTRL_ADC0CLKSEL_HFXO: + ret = cmuSelect_HFXO; + break; + + case CMU_ADCCTRL_ADC0CLKSEL_HFSRCCLK: + ret = cmuSelect_HFSRCCLK; + break; + } + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC1CLKSEL_MASK) + case CMU_ADC1ASYNCSEL_REG: + switch (CMU->ADCCTRL & _CMU_ADCCTRL_ADC1CLKSEL_MASK) { + case CMU_ADCCTRL_ADC1CLKSEL_DISABLED: + ret = cmuSelect_Disabled; + break; + + case CMU_ADCCTRL_ADC1CLKSEL_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + + case CMU_ADCCTRL_ADC1CLKSEL_HFXO: + ret = cmuSelect_HFXO; + break; + + case CMU_ADCCTRL_ADC1CLKSEL_HFSRCCLK: + ret = cmuSelect_HFSRCCLK; + break; + } + break; +#endif + +#if defined(_CMU_SDIOCTRL_SDIOCLKSEL_MASK) + case CMU_SDIOREFSEL_REG: + switch (CMU->SDIOCTRL & _CMU_SDIOCTRL_SDIOCLKSEL_MASK) { + case CMU_SDIOCTRL_SDIOCLKSEL_HFRCO: + ret = cmuSelect_HFRCO; + break; + + case CMU_SDIOCTRL_SDIOCLKSEL_HFXO: + ret = cmuSelect_HFXO; + break; + + case CMU_SDIOCTRL_SDIOCLKSEL_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + + case CMU_SDIOCTRL_SDIOCLKSEL_USHFRCO: + ret = cmuSelect_USHFRCO; + break; + } + break; +#endif + +#if defined(_CMU_QSPICTRL_QSPI0CLKSEL_MASK) + case CMU_QSPI0REFSEL_REG: + switch (CMU->QSPICTRL & _CMU_QSPICTRL_QSPI0CLKSEL_MASK) { + case CMU_QSPICTRL_QSPI0CLKSEL_HFRCO: + ret = cmuSelect_HFRCO; + break; + + case CMU_QSPICTRL_QSPI0CLKSEL_HFXO: + ret = cmuSelect_HFXO; + break; + + case CMU_QSPICTRL_QSPI0CLKSEL_AUXHFRCO: + ret = cmuSelect_AUXHFRCO; + break; + + case CMU_QSPICTRL_QSPI0CLKSEL_USHFRCO: + ret = cmuSelect_USHFRCO; + break; + } + break; +#endif + +#if defined(_CMU_USBCTRL_USBCLKSEL_MASK) + case CMU_USBRCLKSEL_REG: + switch (CMU->USBCTRL & _CMU_USBCTRL_USBCLKSEL_MASK) { + case CMU_USBCTRL_USBCLKSEL_USHFRCO: + ret = cmuSelect_USHFRCO; + break; + + case CMU_USBCTRL_USBCLKSEL_HFXO: + ret = cmuSelect_HFXO; + break; + + case CMU_USBCTRL_USBCLKSEL_HFXOX2: + ret = cmuSelect_HFXOX2; + break; + + case CMU_USBCTRL_USBCLKSEL_HFRCO: + ret = cmuSelect_HFRCO; + break; + + case CMU_USBCTRL_USBCLKSEL_LFXO: + ret = cmuSelect_LFXO; + break; + + case CMU_USBCTRL_USBCLKSEL_LFRCO: + ret = cmuSelect_LFRCO; + break; + } + break; +#endif + + default: + EFM_ASSERT(0); + ret = cmuSelect_Error; + break; + } + + return ret; +} + +/***************************************************************************//** + * @brief + * Select reference clock/oscillator used for a clock branch. + * + * @details + * Notice that if a selected reference is not enabled prior to selecting its + * use, it will be enabled, and this function will wait for the selected + * oscillator to be stable. It will however NOT be disabled if another + * reference clock is selected later. + * + * This feature is particularly important if selecting a new reference + * clock for the clock branch clocking the core, otherwise the system + * may halt. + * + * @param[in] clock + * Clock branch to select reference clock for. One of: + * @li #cmuClock_HF + * @li #cmuClock_LFA + * @li #cmuClock_LFB + * @if _CMU_LFCCLKEN0_MASK + * @li #cmuClock_LFC + * @endif + * @if _CMU_LFECLKEN0_MASK + * @li #cmuClock_LFE + * @endif + * @li #cmuClock_DBG + * @if _CMU_CMD_USBCLKSEL_MASK + * @li #cmuClock_USBC + * @endif + * @if _CMU_USBCTRL_MASK + * @li #cmuClock_USBR + * @endif + * + * @param[in] ref + * Reference selected for clocking, please refer to reference manual for + * for details on which reference is available for a specific clock branch. + * @li #cmuSelect_HFRCO + * @li #cmuSelect_LFRCO + * @li #cmuSelect_HFXO + * @if _CMU_HFXOCTRL_HFXOX2EN_MASK + * @li #cmuSelect_HFXOX2 + * @endif + * @li #cmuSelect_LFXO + * @li #cmuSelect_HFCLKLE + * @li #cmuSelect_AUXHFRCO + * @if _CMU_USHFRCOCTRL_MASK + * @li #cmuSelect_USHFRCO + * @endif + * @li #cmuSelect_HFCLK + * @ifnot DOXYDOC_EFM32_GECKO_FAMILY + * @li #cmuSelect_ULFRCO + * @li #cmuSelect_PLFRCO + * @endif + ******************************************************************************/ +void CMU_ClockSelectSet(CMU_Clock_TypeDef clock, CMU_Select_TypeDef ref) +{ + uint32_t select = cmuOsc_HFRCO; + CMU_Osc_TypeDef osc = cmuOsc_HFRCO; + uint32_t freq; + uint32_t tmp; + uint32_t selRegId; +#if defined(_SILICON_LABS_32B_SERIES_1) + volatile uint32_t *selReg = NULL; +#endif +#if defined(CMU_LFCLKSEL_LFAE_ULFRCO) + uint32_t lfExtended = 0; +#endif + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + uint32_t vScaleFrequency = 0; /* Use default */ + + /* Start voltage upscaling before clock is set. */ + if (clock == cmuClock_HF) { + if (ref == cmuSelect_HFXO) { + vScaleFrequency = SystemHFXOClockGet(); + } else if ((ref == cmuSelect_HFRCO) + && (CMU_HFRCOBandGet() > CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX)) { + vScaleFrequency = CMU_HFRCOBandGet(); + } + if (vScaleFrequency != 0) { + EMU_VScaleEM01ByClock(vScaleFrequency, false); + } + } +#endif + + selRegId = (clock >> CMU_SEL_REG_POS) & CMU_SEL_REG_MASK; + + switch (selRegId) { + case CMU_HFCLKSEL_REG: + switch (ref) { + case cmuSelect_LFXO: +#if defined(_SILICON_LABS_32B_SERIES_1) + select = CMU_HFCLKSEL_HF_LFXO; +#elif defined(_SILICON_LABS_32B_SERIES_0) + select = CMU_CMD_HFCLKSEL_LFXO; +#endif + osc = cmuOsc_LFXO; + break; + + case cmuSelect_LFRCO: +#if defined(_SILICON_LABS_32B_SERIES_1) + select = CMU_HFCLKSEL_HF_LFRCO; +#elif defined(_SILICON_LABS_32B_SERIES_0) + select = CMU_CMD_HFCLKSEL_LFRCO; +#endif + osc = cmuOsc_LFRCO; + break; + + case cmuSelect_HFXO: +#if defined(CMU_HFCLKSEL_HF_HFXO) + select = CMU_HFCLKSEL_HF_HFXO; +#elif defined(CMU_CMD_HFCLKSEL_HFXO) + select = CMU_CMD_HFCLKSEL_HFXO; +#endif + osc = cmuOsc_HFXO; +#if defined(CMU_MAX_FREQ_HFLE) + /* Set 1 HFLE wait-state until the new HFCLKLE frequency is known. + This is known after 'select' is written below. */ + setHfLeConfig(CMU_MAX_FREQ_HFLE + 1); +#endif +#if defined(CMU_CTRL_HFXOBUFCUR_BOOSTABOVE32MHZ) + /* Adjust HFXO buffer current for frequencies above 32MHz */ + if (SystemHFXOClockGet() > 32000000) { + CMU->CTRL = (CMU->CTRL & ~_CMU_CTRL_HFXOBUFCUR_MASK) + | CMU_CTRL_HFXOBUFCUR_BOOSTABOVE32MHZ; + } else { + CMU->CTRL = (CMU->CTRL & ~_CMU_CTRL_HFXOBUFCUR_MASK) + | CMU_CTRL_HFXOBUFCUR_BOOSTUPTO32MHZ; + } +#endif + break; + + case cmuSelect_HFRCO: +#if defined(_SILICON_LABS_32B_SERIES_1) + select = CMU_HFCLKSEL_HF_HFRCO; +#elif defined(_SILICON_LABS_32B_SERIES_0) + select = CMU_CMD_HFCLKSEL_HFRCO; +#endif + osc = cmuOsc_HFRCO; +#if defined(CMU_MAX_FREQ_HFLE) + /* Set 1 HFLE wait-state until the new HFCLKLE frequency is known. + This is known after 'select' is written below. */ + setHfLeConfig(CMU_MAX_FREQ_HFLE + 1); +#endif + break; + +#if defined(CMU_CMD_HFCLKSEL_USHFRCODIV2) + case cmuSelect_USHFRCODIV2: + select = CMU_CMD_HFCLKSEL_USHFRCODIV2; + osc = cmuOsc_USHFRCO; + break; +#endif + +#if defined(CMU_LFCLKSEL_LFAE_ULFRCO) || defined(CMU_LFACLKSEL_LFA_ULFRCO) + case cmuSelect_ULFRCO: + /* ULFRCO cannot be used as HFCLK */ + EFM_ASSERT(0); + return; +#endif + + default: + EFM_ASSERT(0); + return; + } + + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(osc, true, true); + + /* Configure worst case wait states for flash access before selecting */ + flashWaitStateMax(); + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* Wait for voltage upscaling to complete before clock is set. */ + if (vScaleFrequency != 0) { + EMU_VScaleWait(); + } +#endif + + /* Switch to selected oscillator */ +#if defined(_CMU_HFCLKSEL_MASK) + CMU->HFCLKSEL = select; +#else + CMU->CMD = select; +#endif +#if defined(CMU_MAX_FREQ_HFLE) + /* Update HFLE configuration after 'select' is set. + Note that the HFCLKLE clock is connected differently on planform 1 and 2 */ + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); +#endif + + /* Update CMSIS core clock variable */ + /* (The function will update the global variable) */ + freq = SystemCoreClockGet(); + + /* Optimize flash access wait state setting for currently selected core clk */ + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* Keep EMU module informed on source HF clock frequency. This will apply voltage + downscaling after clock is set if downscaling is configured. */ + if (vScaleFrequency == 0) { + EMU_VScaleEM01ByClock(0, true); + } +#endif + break; + +#if defined(_SILICON_LABS_32B_SERIES_1) + case CMU_LFACLKSEL_REG: + selReg = (selReg == NULL) ? &CMU->LFACLKSEL : selReg; +#if !defined(_CMU_LFACLKSEL_LFA_HFCLKLE) + /* HFCLKCLE can not be used as LFACLK */ + EFM_ASSERT(ref != cmuSelect_HFCLKLE); +#endif + /* Fall through and select clock source */ + +#if defined(_CMU_LFCCLKSEL_MASK) + case CMU_LFCCLKSEL_REG: + selReg = (selReg == NULL) ? &CMU->LFCCLKSEL : selReg; +#if !defined(_CMU_LFCCLKSEL_LFC_HFCLKLE) + /* HFCLKCLE can not be used as LFCCLK */ + EFM_ASSERT(ref != cmuSelect_HFCLKLE); +#endif +#endif + /* Fall through and select clock source */ + + case CMU_LFECLKSEL_REG: + selReg = (selReg == NULL) ? &CMU->LFECLKSEL : selReg; +#if !defined(_CMU_LFECLKSEL_LFE_HFCLKLE) + /* HFCLKCLE can not be used as LFECLK */ + EFM_ASSERT(ref != cmuSelect_HFCLKLE); +#endif + /* Fall through and select clock source */ + + case CMU_LFBCLKSEL_REG: + selReg = (selReg == NULL) ? &CMU->LFBCLKSEL : selReg; + switch (ref) { + case cmuSelect_Disabled: + tmp = _CMU_LFACLKSEL_LFA_DISABLED; + break; + + case cmuSelect_LFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFXO, true, true); + tmp = _CMU_LFACLKSEL_LFA_LFXO; + break; + + case cmuSelect_LFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); + tmp = _CMU_LFACLKSEL_LFA_LFRCO; + break; + + case cmuSelect_HFCLKLE: + /* Ensure correct HFLE wait-states and enable HFCLK to LE */ + setHfLeConfig(SystemCoreClockGet()); + BUS_RegBitWrite(&CMU->HFBUSCLKEN0, _CMU_HFBUSCLKEN0_LE_SHIFT, 1); + tmp = _CMU_LFBCLKSEL_LFB_HFCLKLE; + break; + + case cmuSelect_ULFRCO: + /* ULFRCO is always on, there is no need to enable it. */ + tmp = _CMU_LFACLKSEL_LFA_ULFRCO; + break; + +#if defined(_CMU_STATUS_PLFRCOENS_MASK) + case cmuSelect_PLFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_PLFRCO, true, true); + tmp = _CMU_LFACLKSEL_LFA_PLFRCO; + break; +#endif + + default: + EFM_ASSERT(0); + return; + } + *selReg = tmp; + break; + +#elif defined(_SILICON_LABS_32B_SERIES_0) + case CMU_LFACLKSEL_REG: + case CMU_LFBCLKSEL_REG: + switch (ref) { + case cmuSelect_Disabled: + tmp = _CMU_LFCLKSEL_LFA_DISABLED; + break; + + case cmuSelect_LFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFXO, true, true); + tmp = _CMU_LFCLKSEL_LFA_LFXO; + break; + + case cmuSelect_LFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); + tmp = _CMU_LFCLKSEL_LFA_LFRCO; + break; + + case cmuSelect_HFCLKLE: +#if defined(CMU_MAX_FREQ_HFLE) + /* Set HFLE wait-state and divider */ + freq = SystemCoreClockGet(); + setHfLeConfig(freq); +#endif + /* Ensure HFCORE to LE clocking is enabled */ + BUS_RegBitWrite(&CMU->HFCORECLKEN0, _CMU_HFCORECLKEN0_LE_SHIFT, 1); + tmp = _CMU_LFCLKSEL_LFA_HFCORECLKLEDIV2; + break; + +#if defined(CMU_LFCLKSEL_LFAE_ULFRCO) + case cmuSelect_ULFRCO: + /* ULFRCO is always enabled */ + tmp = _CMU_LFCLKSEL_LFA_DISABLED; + lfExtended = 1; + break; +#endif + + default: + /* Illegal clock source for LFA/LFB selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + if (selRegId == CMU_LFACLKSEL_REG) { +#if defined(_CMU_LFCLKSEL_LFAE_MASK) + CMU->LFCLKSEL = (CMU->LFCLKSEL + & ~(_CMU_LFCLKSEL_LFA_MASK | _CMU_LFCLKSEL_LFAE_MASK)) + | (tmp << _CMU_LFCLKSEL_LFA_SHIFT) + | (lfExtended << _CMU_LFCLKSEL_LFAE_SHIFT); +#else + CMU->LFCLKSEL = (CMU->LFCLKSEL & ~_CMU_LFCLKSEL_LFA_MASK) + | (tmp << _CMU_LFCLKSEL_LFA_SHIFT); +#endif + } else { +#if defined(_CMU_LFCLKSEL_LFBE_MASK) + CMU->LFCLKSEL = (CMU->LFCLKSEL + & ~(_CMU_LFCLKSEL_LFB_MASK | _CMU_LFCLKSEL_LFBE_MASK)) + | (tmp << _CMU_LFCLKSEL_LFB_SHIFT) + | (lfExtended << _CMU_LFCLKSEL_LFBE_SHIFT); +#else + CMU->LFCLKSEL = (CMU->LFCLKSEL & ~_CMU_LFCLKSEL_LFB_MASK) + | (tmp << _CMU_LFCLKSEL_LFB_SHIFT); +#endif + } + break; + +#if defined(_CMU_LFCLKSEL_LFC_MASK) + case CMU_LFCCLKSEL_REG: + switch (ref) { + case cmuSelect_Disabled: + tmp = _CMU_LFCLKSEL_LFA_DISABLED; + break; + + case cmuSelect_LFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFXO, true, true); + tmp = _CMU_LFCLKSEL_LFC_LFXO; + break; + + case cmuSelect_LFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); + tmp = _CMU_LFCLKSEL_LFC_LFRCO; + break; + + default: + /* Illegal clock source for LFC selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->LFCLKSEL = (CMU->LFCLKSEL & ~_CMU_LFCLKSEL_LFC_MASK) + | (tmp << _CMU_LFCLKSEL_LFC_SHIFT); + break; +#endif +#endif + +#if defined(_CMU_DBGCLKSEL_DBG_MASK) || defined(CMU_CTRL_DBGCLK) + case CMU_DBGCLKSEL_REG: + switch (ref) { +#if defined(_CMU_DBGCLKSEL_DBG_MASK) + case cmuSelect_AUXHFRCO: + /* Select AUXHFRCO as debug clock */ + CMU->DBGCLKSEL = CMU_DBGCLKSEL_DBG_AUXHFRCO; + break; + + case cmuSelect_HFCLK: + /* Select divided HFCLK as debug clock */ + CMU->DBGCLKSEL = CMU_DBGCLKSEL_DBG_HFCLK; + break; +#endif + +#if defined(CMU_CTRL_DBGCLK) + case cmuSelect_AUXHFRCO: + /* Select AUXHFRCO as debug clock */ + CMU->CTRL = (CMU->CTRL & ~(_CMU_CTRL_DBGCLK_MASK)) + | CMU_CTRL_DBGCLK_AUXHFRCO; + break; + + case cmuSelect_HFCLK: + /* Select divided HFCLK as debug clock */ + CMU->CTRL = (CMU->CTRL & ~(_CMU_CTRL_DBGCLK_MASK)) + | CMU_CTRL_DBGCLK_HFCLK; + break; +#endif + + default: + /* Illegal clock source for debug selected */ + EFM_ASSERT(0); + return; + } + break; +#endif + +#if defined(USBC_CLOCK_PRESENT) + case CMU_USBCCLKSEL_REG: + switch (ref) { + case cmuSelect_LFXO: + /* Select LFXO as clock source for USB, can only be used in sleep mode */ + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFXO, true, true); + + /* Switch oscillator */ + CMU->CMD = CMU_CMD_USBCCLKSEL_LFXO; + + /* Wait until clock is activated */ + while ((CMU->STATUS & CMU_STATUS_USBCLFXOSEL) == 0) { + } + break; + + case cmuSelect_LFRCO: + /* Select LFRCO as clock source for USB, can only be used in sleep mode */ + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); + + /* Switch oscillator */ + CMU->CMD = CMU_CMD_USBCCLKSEL_LFRCO; + + /* Wait until clock is activated */ + while ((CMU->STATUS & CMU_STATUS_USBCLFRCOSEL) == 0) { + } + break; + +#if defined(CMU_STATUS_USBCHFCLKSEL) + case cmuSelect_HFCLK: + /* Select undivided HFCLK as clock source for USB */ + /* Oscillator must already be enabled to avoid a core lockup */ + CMU->CMD = CMU_CMD_USBCCLKSEL_HFCLKNODIV; + /* Wait until clock is activated */ + while ((CMU->STATUS & CMU_STATUS_USBCHFCLKSEL) == 0) { + } + break; +#endif + +#if defined(CMU_CMD_USBCCLKSEL_USHFRCO) + case cmuSelect_USHFRCO: + /* Select USHFRCO as clock source for USB */ + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_USHFRCO, true, true); + + /* Switch oscillator */ + CMU->CMD = CMU_CMD_USBCCLKSEL_USHFRCO; + + /* Wait until clock is activated */ + while ((CMU->STATUS & CMU_STATUS_USBCUSHFRCOSEL) == 0) { + } + break; +#endif + + default: + /* Illegal clock source for USB */ + EFM_ASSERT(0); + return; + } + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC0CLKSEL_MASK) + case CMU_ADC0ASYNCSEL_REG: + switch (ref) { + case cmuSelect_Disabled: + tmp = _CMU_ADCCTRL_ADC0CLKSEL_DISABLED; + break; + + case cmuSelect_AUXHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_AUXHFRCO, true, true); + tmp = _CMU_ADCCTRL_ADC0CLKSEL_AUXHFRCO; + break; + + case cmuSelect_HFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + tmp = _CMU_ADCCTRL_ADC0CLKSEL_HFXO; + break; + + case cmuSelect_HFSRCCLK: + tmp = _CMU_ADCCTRL_ADC0CLKSEL_HFSRCCLK; + break; + + default: + /* Illegal clock source for ADC0ASYNC selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->ADCCTRL = (CMU->ADCCTRL & ~_CMU_ADCCTRL_ADC0CLKSEL_MASK) + | (tmp << _CMU_ADCCTRL_ADC0CLKSEL_SHIFT); + break; +#endif + +#if defined(_CMU_ADCCTRL_ADC1CLKSEL_MASK) + case CMU_ADC1ASYNCSEL_REG: + switch (ref) { + case cmuSelect_Disabled: + tmp = _CMU_ADCCTRL_ADC1CLKSEL_DISABLED; + break; + + case cmuSelect_AUXHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_AUXHFRCO, true, true); + tmp = _CMU_ADCCTRL_ADC1CLKSEL_AUXHFRCO; + break; + + case cmuSelect_HFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + tmp = _CMU_ADCCTRL_ADC1CLKSEL_HFXO; + break; + + case cmuSelect_HFSRCCLK: + tmp = _CMU_ADCCTRL_ADC1CLKSEL_HFSRCCLK; + break; + + default: + /* Illegal clock source for ADC1ASYNC selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->ADCCTRL = (CMU->ADCCTRL & ~_CMU_ADCCTRL_ADC1CLKSEL_MASK) + | (tmp << _CMU_ADCCTRL_ADC1CLKSEL_SHIFT); + break; +#endif + +#if defined(_CMU_SDIOCTRL_SDIOCLKSEL_MASK) + case CMU_SDIOREFSEL_REG: + switch (ref) { + case cmuSelect_HFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFRCO, true, true); + tmp = _CMU_SDIOCTRL_SDIOCLKSEL_HFRCO; + break; + + case cmuSelect_HFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + tmp = _CMU_SDIOCTRL_SDIOCLKSEL_HFXO; + break; + + case cmuSelect_AUXHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_AUXHFRCO, true, true); + tmp = _CMU_SDIOCTRL_SDIOCLKSEL_AUXHFRCO; + break; + + case cmuSelect_USHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_USHFRCO, true, true); + tmp = _CMU_SDIOCTRL_SDIOCLKSEL_USHFRCO; + break; + + default: + /* Illegal clock source for SDIOREF selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->SDIOCTRL = (CMU->SDIOCTRL & ~_CMU_SDIOCTRL_SDIOCLKSEL_MASK) + | (tmp << _CMU_SDIOCTRL_SDIOCLKSEL_SHIFT); + break; +#endif + +#if defined(_CMU_QSPICTRL_QSPI0CLKSEL_MASK) + case CMU_QSPI0REFSEL_REG: + switch (ref) { + case cmuSelect_HFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFRCO, true, true); + tmp = _CMU_QSPICTRL_QSPI0CLKSEL_HFRCO; + break; + + case cmuSelect_HFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + tmp = _CMU_QSPICTRL_QSPI0CLKSEL_HFXO; + break; + + case cmuSelect_AUXHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_AUXHFRCO, true, true); + tmp = _CMU_QSPICTRL_QSPI0CLKSEL_AUXHFRCO; + break; + + case cmuSelect_USHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_USHFRCO, true, true); + tmp = _CMU_QSPICTRL_QSPI0CLKSEL_USHFRCO; + break; + + default: + /* Illegal clock source for QSPI0REF selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->QSPICTRL = (CMU->QSPICTRL & ~_CMU_QSPICTRL_QSPI0CLKSEL_MASK) + | (tmp << _CMU_QSPICTRL_QSPI0CLKSEL_SHIFT); + break; +#endif + +#if defined(_CMU_USBCTRL_USBCLKSEL_MASK) + case CMU_USBRCLKSEL_REG: + switch (ref) { + case cmuSelect_USHFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_USHFRCO, true, true); + tmp = _CMU_USBCTRL_USBCLKSEL_USHFRCO; + break; + + case cmuSelect_HFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + tmp = _CMU_USBCTRL_USBCLKSEL_HFXO; + break; + + case cmuSelect_HFXOX2: + /* Only allowed for HFXO frequencies up to 25 MHz */ + EFM_ASSERT(SystemHFXOClockGet() <= 25000000u); + + /* Enable HFXO X2 */ + CMU->HFXOCTRL |= CMU_HFXOCTRL_HFXOX2EN; + + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFXO, true, true); + + tmp = _CMU_USBCTRL_USBCLKSEL_HFXOX2; + break; + + case cmuSelect_HFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_HFRCO, true, true); + tmp = _CMU_USBCTRL_USBCLKSEL_HFRCO; + break; + + case cmuSelect_LFXO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFXO, true, true); + tmp = _CMU_USBCTRL_USBCLKSEL_LFXO; + break; + + case cmuSelect_LFRCO: + /* Ensure selected oscillator is enabled, waiting for it to stabilize */ + CMU_OscillatorEnable(cmuOsc_LFRCO, true, true); + tmp = _CMU_USBCTRL_USBCLKSEL_LFRCO; + break; + + default: + /* Illegal clock source for USBR selected */ + EFM_ASSERT(0); + return; + } + + /* Apply select */ + CMU->USBCTRL = (CMU->USBCTRL & ~_CMU_USBCTRL_USBCLKSEL_MASK) + | (tmp << _CMU_USBCTRL_USBCLKSEL_SHIFT); + break; +#endif + + default: + EFM_ASSERT(0); + break; + } +} + +#if defined(CMU_OSCENCMD_DPLLEN) +/**************************************************************************//** + * @brief + * Lock the DPLL to a given frequency. + * + * The frequency is given by: Fout = Fref * (N+1) / (M+1). + * + * @note + * This function does not check if the given N & M values will actually + * produce the desired target frequency. + * Any peripheral running off HFRCO should be switched to HFRCODIV2 prior to + * calling this function to avoid over-clocking. + * + * @param[in] init + * DPLL setup parameters. + * + * @return + * Returns false on invalid target frequency or DPLL locking error. + *****************************************************************************/ +bool CMU_DPLLLock(CMU_DPLLInit_TypeDef *init) +{ + int index = 0; + unsigned int i; + bool hfrcoDiv2 = false; + uint32_t hfrcoCtrlVal, lockStatus, sysFreq; + + EFM_ASSERT(init->frequency >= hfrcoCtrlTable[0].minFreq); + EFM_ASSERT(init->frequency + <= hfrcoCtrlTable[HFRCOCTRLTABLE_ENTRIES - 1].maxFreq); + EFM_ASSERT(init->n >= 32); + EFM_ASSERT(init->n <= (_CMU_DPLLCTRL1_N_MASK >> _CMU_DPLLCTRL1_N_SHIFT)); + EFM_ASSERT(init->m <= (_CMU_DPLLCTRL1_M_MASK >> _CMU_DPLLCTRL1_M_SHIFT)); + EFM_ASSERT(init->ssInterval <= (_CMU_HFRCOSS_SSINV_MASK + >> _CMU_HFRCOSS_SSINV_SHIFT)); + EFM_ASSERT(init->ssAmplitude <= (_CMU_HFRCOSS_SSAMP_MASK + >> _CMU_HFRCOSS_SSAMP_SHIFT)); + +#if defined(_EMU_STATUS_VSCALE_MASK) + if ((EMU_VScaleGet() == emuVScaleEM01_LowPower) + && (init->frequency > CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX)) { + EFM_ASSERT(false); + return false; + } +#endif + + // Find correct HFRCO band, and retrieve a HFRCOCTRL value. + for (i = 0; i < HFRCOCTRLTABLE_ENTRIES; i++) { + if ((init->frequency >= hfrcoCtrlTable[i].minFreq) + && (init->frequency <= hfrcoCtrlTable[i].maxFreq)) { + index = i; // Correct band found + break; + } + } + if (index == HFRCOCTRLTABLE_ENTRIES) { + EFM_ASSERT(false); + return false; // Target frequency out of spec. + } + hfrcoCtrlVal = hfrcoCtrlTable[index].value; + + // Check if we have a calibrated HFRCOCTRL.TUNING value in device DI page. + if (hfrcoCtrlTable[index].band != (CMU_HFRCOFreq_TypeDef)0) { + uint32_t tuning; + + tuning = (CMU_HFRCODevinfoGet(hfrcoCtrlTable[index].band) + & _CMU_HFRCOCTRL_TUNING_MASK) + >> _CMU_HFRCOCTRL_TUNING_SHIFT; + + // When HFRCOCTRL.FINETUNINGEN is enabled, the center frequency + // of the band shifts down by 5.8%. We subtract 9 to compensate. + if (tuning > 9) { + tuning -= 9; + } else { + tuning = 0; + } + + hfrcoCtrlVal |= tuning << _CMU_HFRCOCTRL_TUNING_SHIFT; + } + + // Update CMSIS frequency SystemHfrcoFreq value. + SystemHfrcoFreq = init->frequency; + + // Set max wait-states while changing core clock. + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + flashWaitStateMax(); + } + + // Update HFLE configuration before updating HFRCO, use new DPLL frequency. + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + setHfLeConfig(init->frequency); + + // Switch to HFRCO/2 before setting DPLL to avoid over-clocking. + hfrcoDiv2 = (CMU->HFCLKSTATUS & _CMU_HFCLKSTATUS_SELECTED_MASK) + == CMU_HFCLKSTATUS_SELECTED_HFRCODIV2; + CMU->HFCLKSEL = CMU_HFCLKSEL_HF_HFRCODIV2; + } + + CMU->OSCENCMD = CMU_OSCENCMD_DPLLDIS; + while ((CMU->STATUS & (CMU_STATUS_DPLLENS | CMU_STATUS_DPLLRDY)) != 0) ; + CMU->IFC = CMU_IFC_DPLLRDY | CMU_IFC_DPLLLOCKFAILLOW + | CMU_IFC_DPLLLOCKFAILHIGH; + CMU->DPLLCTRL1 = (init->n << _CMU_DPLLCTRL1_N_SHIFT) + | (init->m << _CMU_DPLLCTRL1_M_SHIFT); + CMU->HFRCOCTRL = hfrcoCtrlVal; + CMU->DPLLCTRL = (init->refClk << _CMU_DPLLCTRL_REFSEL_SHIFT) + | (init->autoRecover << _CMU_DPLLCTRL_AUTORECOVER_SHIFT) + | (init->edgeSel << _CMU_DPLLCTRL_EDGESEL_SHIFT) + | (init->lockMode << _CMU_DPLLCTRL_MODE_SHIFT); + CMU->OSCENCMD = CMU_OSCENCMD_DPLLEN; + while ((lockStatus = (CMU->IF & (CMU_IF_DPLLRDY + | CMU_IF_DPLLLOCKFAILLOW + | CMU_IF_DPLLLOCKFAILHIGH))) == 0) ; + + if ((CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) + && (hfrcoDiv2 == false)) { + CMU->HFCLKSEL = CMU_HFCLKSEL_HF_HFRCO; + } + + // If HFRCO is selected as HF clock, optimize flash access wait-state + // configuration for this frequency and update CMSIS core clock variable. + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + // Call SystemCoreClockGet() to update CMSIS core clock variable. + sysFreq = SystemCoreClockGet(); + EFM_ASSERT(sysFreq <= init->frequency); + EFM_ASSERT(sysFreq <= SystemHfrcoFreq); + EFM_ASSERT(init->frequency == SystemHfrcoFreq); + CMU_UpdateWaitStates(sysFreq, VSCALE_DEFAULT); + } + + // Reduce HFLE frequency if possible. + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + // Update voltage scaling. + EMU_VScaleEM01ByClock(0, true); +#endif + + if (lockStatus == CMU_IF_DPLLRDY) { + return true; + } + return false; +} +#endif // CMU_OSCENCMD_DPLLEN + +/**************************************************************************//** + * @brief + * CMU low frequency register synchronization freeze control. + * + * @details + * Some CMU registers requires 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). + * + * Another usage scenario of this feature, is when using an API (such + * as the CMU API) for modifying several bit fields consecutively in the + * same register. If freeze mode is enabled during this sequence, stalling + * can be avoided. + * + * @note + * When enabling freeze mode, this function will wait for all current + * ongoing CMU synchronization to 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 unecessary + * stalling. + * + * @param[in] enable + * @li true - enable freeze, modified registers are not propagated to the + * LF domain + * @li false - disable freeze, modified registers are propagated to LF + * domain + *****************************************************************************/ +void CMU_FreezeEnable(bool enable) +{ + if (enable) { + /* Wait for any ongoing LF synchronization to complete. This is just 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 (CMU->SYNCBUSY) { + } + + CMU->FREEZE = CMU_FREEZE_REGFREEZE; + } else { + CMU->FREEZE = 0; + } +} + +#if defined(_CMU_HFRCOCTRL_BAND_MASK) +/***************************************************************************//** + * @brief + * Get HFRCO band in use. + * + * @return + * HFRCO band in use. + ******************************************************************************/ +CMU_HFRCOBand_TypeDef CMU_HFRCOBandGet(void) +{ + return (CMU_HFRCOBand_TypeDef)((CMU->HFRCOCTRL & _CMU_HFRCOCTRL_BAND_MASK) + >> _CMU_HFRCOCTRL_BAND_SHIFT); +} +#endif /* _CMU_HFRCOCTRL_BAND_MASK */ + +#if defined(_CMU_HFRCOCTRL_BAND_MASK) +/***************************************************************************//** + * @brief + * Set HFRCO band and the tuning value based on the value in the calibration + * table made during production. + * + * @param[in] band + * HFRCO band to activate. + ******************************************************************************/ +void CMU_HFRCOBandSet(CMU_HFRCOBand_TypeDef band) +{ + uint32_t tuning; + uint32_t freq; + CMU_Select_TypeDef osc; + + /* Read tuning value from calibration table */ + switch (band) { + case cmuHFRCOBand_1MHz: + tuning = (DEVINFO->HFRCOCAL0 & _DEVINFO_HFRCOCAL0_BAND1_MASK) + >> _DEVINFO_HFRCOCAL0_BAND1_SHIFT; + break; + + case cmuHFRCOBand_7MHz: + tuning = (DEVINFO->HFRCOCAL0 & _DEVINFO_HFRCOCAL0_BAND7_MASK) + >> _DEVINFO_HFRCOCAL0_BAND7_SHIFT; + break; + + case cmuHFRCOBand_11MHz: + tuning = (DEVINFO->HFRCOCAL0 & _DEVINFO_HFRCOCAL0_BAND11_MASK) + >> _DEVINFO_HFRCOCAL0_BAND11_SHIFT; + break; + + case cmuHFRCOBand_14MHz: + tuning = (DEVINFO->HFRCOCAL0 & _DEVINFO_HFRCOCAL0_BAND14_MASK) + >> _DEVINFO_HFRCOCAL0_BAND14_SHIFT; + break; + + case cmuHFRCOBand_21MHz: + tuning = (DEVINFO->HFRCOCAL1 & _DEVINFO_HFRCOCAL1_BAND21_MASK) + >> _DEVINFO_HFRCOCAL1_BAND21_SHIFT; + break; + +#if defined(_CMU_HFRCOCTRL_BAND_28MHZ) + case cmuHFRCOBand_28MHz: + tuning = (DEVINFO->HFRCOCAL1 & _DEVINFO_HFRCOCAL1_BAND28_MASK) + >> _DEVINFO_HFRCOCAL1_BAND28_SHIFT; + break; +#endif + + default: + EFM_ASSERT(0); + return; + } + + /* If HFRCO is used for core clock, we have to consider flash access WS. */ + osc = CMU_ClockSelectGet(cmuClock_HF); + if (osc == cmuSelect_HFRCO) { + /* Configure worst case wait states for flash access before setting divider */ + flashWaitStateMax(); + } + + /* Set band/tuning */ + CMU->HFRCOCTRL = (CMU->HFRCOCTRL + & ~(_CMU_HFRCOCTRL_BAND_MASK | _CMU_HFRCOCTRL_TUNING_MASK)) + | (band << _CMU_HFRCOCTRL_BAND_SHIFT) + | (tuning << _CMU_HFRCOCTRL_TUNING_SHIFT); + + /* If HFRCO is used for core clock, optimize flash WS */ + if (osc == cmuSelect_HFRCO) { + /* Call SystemCoreClockGet() to update CMSIS core clock variable. */ + freq = SystemCoreClockGet(); + CMU_UpdateWaitStates(freq, VSCALE_DEFAULT); + } + +#if defined(CMU_MAX_FREQ_HFLE) + /* Reduce HFLE frequency if possible. */ + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); +#endif +} +#endif /* _CMU_HFRCOCTRL_BAND_MASK */ + +#if defined(_CMU_HFRCOCTRL_FREQRANGE_MASK) +/**************************************************************************//** + * @brief + * Get the HFRCO frequency calibration word in DEVINFO + * + * @param[in] freq + * Frequency in Hz + * + * @return + * HFRCO calibration word for a given frequency + *****************************************************************************/ +static uint32_t CMU_HFRCODevinfoGet(CMU_HFRCOFreq_TypeDef freq) +{ + switch (freq) { + /* 1, 2 and 4MHz share the same calibration word */ + case cmuHFRCOFreq_1M0Hz: + case cmuHFRCOFreq_2M0Hz: + case cmuHFRCOFreq_4M0Hz: + return DEVINFO->HFRCOCAL0; + + case cmuHFRCOFreq_7M0Hz: + return DEVINFO->HFRCOCAL3; + + case cmuHFRCOFreq_13M0Hz: + return DEVINFO->HFRCOCAL6; + + case cmuHFRCOFreq_16M0Hz: + return DEVINFO->HFRCOCAL7; + + case cmuHFRCOFreq_19M0Hz: + return DEVINFO->HFRCOCAL8; + + case cmuHFRCOFreq_26M0Hz: + return DEVINFO->HFRCOCAL10; + + case cmuHFRCOFreq_32M0Hz: + return DEVINFO->HFRCOCAL11; + + case cmuHFRCOFreq_38M0Hz: + return DEVINFO->HFRCOCAL12; + +#if defined(_DEVINFO_HFRCOCAL16_MASK) + case cmuHFRCOFreq_48M0Hz: + return DEVINFO->HFRCOCAL13; + + case cmuHFRCOFreq_56M0Hz: + return DEVINFO->HFRCOCAL14; + + case cmuHFRCOFreq_64M0Hz: + return DEVINFO->HFRCOCAL15; + + case cmuHFRCOFreq_72M0Hz: + return DEVINFO->HFRCOCAL16; +#endif + + default: /* cmuHFRCOFreq_UserDefined */ + return 0; + } +} + +/***************************************************************************//** + * @brief + * Get current HFRCO frequency. + * + * @return + * HFRCO frequency + ******************************************************************************/ +CMU_HFRCOFreq_TypeDef CMU_HFRCOBandGet(void) +{ + return (CMU_HFRCOFreq_TypeDef)SystemHfrcoFreq; +} + +/***************************************************************************//** + * @brief + * Set HFRCO calibration for the selected target frequency. + * + * @param[in] setFreq + * HFRCO frequency to set + ******************************************************************************/ +void CMU_HFRCOBandSet(CMU_HFRCOFreq_TypeDef setFreq) +{ + uint32_t freqCal; + uint32_t sysFreq; + uint32_t prevFreq; + + /* Get DEVINFO index, set CMSIS frequency SystemHfrcoFreq */ + freqCal = CMU_HFRCODevinfoGet(setFreq); + EFM_ASSERT((freqCal != 0) && (freqCal != UINT_MAX)); + prevFreq = SystemHfrcoFreq; + SystemHfrcoFreq = (uint32_t)setFreq; + + /* Set max wait-states while changing core clock */ + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + flashWaitStateMax(); + } + + /* Wait for any previous sync to complete, and then set calibration data + for the selected frequency. */ + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_HFRCOBSY_SHIFT)) ; + + /* Check for valid calibration data */ + EFM_ASSERT(freqCal != UINT_MAX); + + /* Set divider in HFRCOCTRL for 1, 2 and 4MHz */ + switch (setFreq) { + case cmuHFRCOFreq_1M0Hz: + freqCal = (freqCal & ~_CMU_HFRCOCTRL_CLKDIV_MASK) + | CMU_HFRCOCTRL_CLKDIV_DIV4; + break; + + case cmuHFRCOFreq_2M0Hz: + freqCal = (freqCal & ~_CMU_HFRCOCTRL_CLKDIV_MASK) + | CMU_HFRCOCTRL_CLKDIV_DIV2; + break; + + case cmuHFRCOFreq_4M0Hz: + freqCal = (freqCal & ~_CMU_HFRCOCTRL_CLKDIV_MASK) + | CMU_HFRCOCTRL_CLKDIV_DIV1; + break; + + default: + break; + } + + /* Update HFLE configuration before updating HFRCO. + Use the new set frequency. */ + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + /* setFreq is worst-case as dividers may reduce the HFLE frequency. */ + setHfLeConfig(setFreq); + } + + if (setFreq > prevFreq) { +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* When increasing frequency we need to voltage scale before the change */ + EMU_VScaleEM01ByClock(setFreq, true); +#endif + } + + CMU->HFRCOCTRL = freqCal; + + /* If HFRCO is selected as HF clock, optimize flash access wait-state configuration + for this frequency and update CMSIS core clock variable. */ + if (CMU_ClockSelectGet(cmuClock_HF) == cmuSelect_HFRCO) { + /* Call SystemCoreClockGet() to update CMSIS core clock variable. */ + sysFreq = SystemCoreClockGet(); + EFM_ASSERT(sysFreq <= (uint32_t)setFreq); + EFM_ASSERT(sysFreq <= SystemHfrcoFreq); + EFM_ASSERT(setFreq == SystemHfrcoFreq); + CMU_UpdateWaitStates(sysFreq, VSCALE_DEFAULT); + } + + /* Reduce HFLE frequency if possible. */ + setHfLeConfig(CMU_ClockFreqGet(cmuClock_HFLE)); + + if (setFreq <= prevFreq) { +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* When decreasing frequency we need to voltage scale after the change */ + EMU_VScaleEM01ByClock(0, true); +#endif + } +} +#endif /* _CMU_HFRCOCTRL_FREQRANGE_MASK */ + +#if defined(_CMU_HFRCOCTRL_SUDELAY_MASK) +/***************************************************************************//** + * @brief + * Get the HFRCO startup delay. + * + * @details + * Please refer to the reference manual for further details. + * + * @return + * The startup delay in use. + ******************************************************************************/ +uint32_t CMU_HFRCOStartupDelayGet(void) +{ + return (CMU->HFRCOCTRL & _CMU_HFRCOCTRL_SUDELAY_MASK) + >> _CMU_HFRCOCTRL_SUDELAY_SHIFT; +} + +/***************************************************************************//** + * @brief + * Set the HFRCO startup delay. + * + * @details + * Please refer to the reference manual for further details. + * + * @param[in] delay + * The startup delay to set (<= 31). + ******************************************************************************/ +void CMU_HFRCOStartupDelaySet(uint32_t delay) +{ + EFM_ASSERT(delay <= 31); + + delay &= _CMU_HFRCOCTRL_SUDELAY_MASK >> _CMU_HFRCOCTRL_SUDELAY_SHIFT; + CMU->HFRCOCTRL = (CMU->HFRCOCTRL & ~(_CMU_HFRCOCTRL_SUDELAY_MASK)) + | (delay << _CMU_HFRCOCTRL_SUDELAY_SHIFT); +} +#endif + +#if defined(_CMU_USHFRCOCTRL_FREQRANGE_MASK) +/**************************************************************************//** + * @brief + * Get the USHFRCO frequency calibration word in DEVINFO + * + * @param[in] freq + * Frequency in Hz + * + * @return + * USHFRCO calibration word for a given frequency + *****************************************************************************/ +static uint32_t CMU_USHFRCODevinfoGet(CMU_USHFRCOFreq_TypeDef freq) +{ + switch (freq) { + case cmuUSHFRCOFreq_16M0Hz: + return DEVINFO->USHFRCOCAL7; + + case cmuUSHFRCOFreq_32M0Hz: + return DEVINFO->USHFRCOCAL11; + + case cmuUSHFRCOFreq_48M0Hz: + return DEVINFO->USHFRCOCAL13; + + case cmuUSHFRCOFreq_50M0Hz: + return DEVINFO->USHFRCOCAL14; + + default: /* cmuUSHFRCOFreq_UserDefined */ + return 0; + } +} + +/***************************************************************************//** + * @brief + * Get current USHFRCO frequency. + * + * @return + * HFRCO frequency + ******************************************************************************/ +CMU_USHFRCOFreq_TypeDef CMU_USHFRCOBandGet(void) +{ + return (CMU_USHFRCOFreq_TypeDef) ushfrcoFreq; +} + +/***************************************************************************//** + * @brief + * Set USHFRCO calibration for the selected target frequency. + * + * @param[in] setFreq + * USHFRCO frequency to set + ******************************************************************************/ +void CMU_USHFRCOBandSet(CMU_USHFRCOFreq_TypeDef setFreq) +{ + uint32_t freqCal; + + /* Get DEVINFO calibration values */ + freqCal = CMU_USHFRCODevinfoGet(setFreq); + EFM_ASSERT((freqCal != 0) && (freqCal != UINT_MAX)); + ushfrcoFreq = (uint32_t)setFreq; + + /* Wait for any previous sync to complete, and then set calibration data + for the selected frequency. */ + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_USHFRCOBSY_SHIFT)) ; + + CMU->USHFRCOCTRL = freqCal; +} +#endif /* _CMU_USHFRCOCTRL_FREQRANGE_MASK */ + +#if defined(_CMU_HFXOCTRL_AUTOSTARTEM0EM1_MASK) +/***************************************************************************//** + * @brief + * Enable or disable HFXO autostart + * + * @param[in] userSel + * Additional user specified enable bit. + * + * @param[in] enEM0EM1Start + * If true, HFXO is automatically started upon entering EM0/EM1 entry from + * EM2/EM3. HFXO selection has to be handled by the user. + * If false, HFXO is not started automatically when entering EM0/EM1. + * + * @param[in] enEM0EM1StartSel + * If true, HFXO is automatically started and immediately selected upon + * entering EM0/EM1 entry from EM2/EM3. Note that this option stalls the use of + * HFSRCCLK until HFXO becomes ready. + * If false, HFXO is not started or selected automatically when entering + * EM0/EM1. + ******************************************************************************/ +void CMU_HFXOAutostartEnable(uint32_t userSel, + bool enEM0EM1Start, + bool enEM0EM1StartSel) +{ + uint32_t hfxoFreq; + uint32_t hfxoCtrl; + + /* Mask supported enable bits. */ +#if defined(_CMU_HFXOCTRL_AUTOSTARTRDYSELRAC_MASK) + userSel &= _CMU_HFXOCTRL_AUTOSTARTRDYSELRAC_MASK; +#else + userSel = 0; +#endif + + hfxoCtrl = CMU->HFXOCTRL & ~(userSel + | _CMU_HFXOCTRL_AUTOSTARTEM0EM1_MASK + | _CMU_HFXOCTRL_AUTOSTARTSELEM0EM1_MASK); + + hfxoCtrl |= userSel + | (enEM0EM1Start ? CMU_HFXOCTRL_AUTOSTARTEM0EM1 : 0) + | (enEM0EM1StartSel ? CMU_HFXOCTRL_AUTOSTARTSELEM0EM1 : 0); + + /* Set wait-states for HFXO if automatic start and select is configured. */ + if (userSel || enEM0EM1StartSel) { + hfxoFreq = SystemHFXOClockGet(); + CMU_UpdateWaitStates(hfxoFreq, VSCALE_DEFAULT); + setHfLeConfig(hfxoFreq); + } + + /* Update HFXOCTRL after wait-states are updated as HF may automatically switch + to HFXO when automatic select is enabled . */ + CMU->HFXOCTRL = hfxoCtrl; +} +#endif + +/**************************************************************************//** + * @brief + * Set HFXO control registers + * + * @note + * HFXO configuration should be obtained from a configuration tool, + * app note or xtal datasheet. This function disables the HFXO to ensure + * a valid state before update. + * + * @param[in] hfxoInit + * HFXO setup parameters + *****************************************************************************/ +void CMU_HFXOInit(const CMU_HFXOInit_TypeDef *hfxoInit) +{ + /* Do not disable HFXO if it is currently selected as HF/Core clock */ + EFM_ASSERT(CMU_ClockSelectGet(cmuClock_HF) != cmuSelect_HFXO); + + /* HFXO must be disabled before reconfiguration */ + CMU_OscillatorEnable(cmuOsc_HFXO, false, true); + +#if defined(_SILICON_LABS_32B_SERIES_1) && (_SILICON_LABS_GECKO_INTERNAL_SDID >= 100) + uint32_t tmp = CMU_HFXOCTRL_MODE_XTAL; + + switch (hfxoInit->mode) { + case cmuOscMode_Crystal: + tmp = CMU_HFXOCTRL_MODE_XTAL; + break; + case cmuOscMode_External: + tmp = CMU_HFXOCTRL_MODE_DIGEXTCLK; + break; + case cmuOscMode_AcCoupled: + tmp = CMU_HFXOCTRL_MODE_ACBUFEXTCLK; + break; + default: + EFM_ASSERT(false); /* Unsupported configuration */ + } + + /* HFXO Doubler can only be enabled on crystals up to max 25 MHz */ + if (SystemHFXOClockGet() <= 25000000) { + tmp |= CMU_HFXOCTRL_HFXOX2EN; + } + + CMU->HFXOCTRL = (CMU->HFXOCTRL & ~(_CMU_HFXOCTRL_MODE_MASK + | _CMU_HFXOCTRL_HFXOX2EN_MASK)) + | tmp; + + /* Set tuning for startup and steady state */ + CMU->HFXOSTARTUPCTRL = (hfxoInit->ctuneStartup << _CMU_HFXOSTARTUPCTRL_CTUNE_SHIFT) + | (hfxoInit->xoCoreBiasTrimStartup << _CMU_HFXOSTARTUPCTRL_IBTRIMXOCORE_SHIFT); + + CMU->HFXOSTEADYSTATECTRL = (CMU->HFXOSTEADYSTATECTRL & ~(_CMU_HFXOSTEADYSTATECTRL_CTUNE_MASK + | _CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_MASK)) + | (hfxoInit->ctuneSteadyState << _CMU_HFXOSTEADYSTATECTRL_CTUNE_SHIFT) + | (hfxoInit->xoCoreBiasTrimSteadyState << _CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_SHIFT); + + /* Set timeouts */ + CMU->HFXOTIMEOUTCTRL = (hfxoInit->timeoutPeakDetect << _CMU_HFXOTIMEOUTCTRL_PEAKDETTIMEOUT_SHIFT) + | (hfxoInit->timeoutSteady << _CMU_HFXOTIMEOUTCTRL_STEADYTIMEOUT_SHIFT) + | (hfxoInit->timeoutStartup << _CMU_HFXOTIMEOUTCTRL_STARTUPTIMEOUT_SHIFT); + +#elif defined(_CMU_HFXOCTRL_MASK) + /* Verify that the deprecated autostart fields are not used, + * @ref CMU_HFXOAutostartEnable must be used instead. */ + EFM_ASSERT(!(hfxoInit->autoStartEm01 + || hfxoInit->autoSelEm01 + || hfxoInit->autoStartSelOnRacWakeup)); + + uint32_t tmp = CMU_HFXOCTRL_MODE_XTAL; + + /* AC coupled external clock not supported */ + EFM_ASSERT(hfxoInit->mode != cmuOscMode_AcCoupled); + if (hfxoInit->mode == cmuOscMode_External) { + tmp = CMU_HFXOCTRL_MODE_DIGEXTCLK; + } + + /* Apply control settings */ + CMU->HFXOCTRL = (CMU->HFXOCTRL & ~_CMU_HFXOCTRL_MODE_MASK) + | tmp; + BUS_RegBitWrite(&CMU->HFXOCTRL, _CMU_HFXOCTRL_LOWPOWER_SHIFT, hfxoInit->lowPowerMode); + + /* Set XTAL tuning parameters */ + +#if defined(_CMU_HFXOCTRL1_PEAKDETTHR_MASK) + /* Set peak detection threshold */ + CMU->HFXOCTRL1 = (CMU->HFXOCTRL1 & ~_CMU_HFXOCTRL1_PEAKDETTHR_MASK) + | (hfxoInit->thresholdPeakDetect << _CMU_HFXOCTRL1_PEAKDETTHR_SHIFT); +#endif + /* Set tuning for startup and steady state */ + CMU->HFXOSTARTUPCTRL = (hfxoInit->ctuneStartup << _CMU_HFXOSTARTUPCTRL_CTUNE_SHIFT) + | (hfxoInit->xoCoreBiasTrimStartup << _CMU_HFXOSTARTUPCTRL_IBTRIMXOCORE_SHIFT); + + CMU->HFXOSTEADYSTATECTRL = (CMU->HFXOSTEADYSTATECTRL & ~(_CMU_HFXOSTEADYSTATECTRL_CTUNE_MASK + | _CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_MASK + | _CMU_HFXOSTEADYSTATECTRL_REGISH_MASK + | _CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_MASK)) + | (hfxoInit->ctuneSteadyState << _CMU_HFXOSTEADYSTATECTRL_CTUNE_SHIFT) + | (hfxoInit->xoCoreBiasTrimSteadyState << _CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_SHIFT) + | (hfxoInit->regIshSteadyState << _CMU_HFXOSTEADYSTATECTRL_REGISH_SHIFT) + | getRegIshUpperVal(hfxoInit->regIshSteadyState); + + /* Set timeouts */ + CMU->HFXOTIMEOUTCTRL = (hfxoInit->timeoutPeakDetect << _CMU_HFXOTIMEOUTCTRL_PEAKDETTIMEOUT_SHIFT) + | (hfxoInit->timeoutSteady << _CMU_HFXOTIMEOUTCTRL_STEADYTIMEOUT_SHIFT) + | (hfxoInit->timeoutStartup << _CMU_HFXOTIMEOUTCTRL_STARTUPTIMEOUT_SHIFT) + | (hfxoInit->timeoutShuntOptimization << _CMU_HFXOTIMEOUTCTRL_SHUNTOPTTIMEOUT_SHIFT); + +#else + CMU->CTRL = (CMU->CTRL & ~(_CMU_CTRL_HFXOTIMEOUT_MASK + | _CMU_CTRL_HFXOBOOST_MASK + | _CMU_CTRL_HFXOMODE_MASK + | _CMU_CTRL_HFXOGLITCHDETEN_MASK)) + | (hfxoInit->timeout << _CMU_CTRL_HFXOTIMEOUT_SHIFT) + | (hfxoInit->boost << _CMU_CTRL_HFXOBOOST_SHIFT) + | (hfxoInit->mode << _CMU_CTRL_HFXOMODE_SHIFT) + | (hfxoInit->glitchDetector ? CMU_CTRL_HFXOGLITCHDETEN : 0); +#endif +} + +/***************************************************************************//** + * @brief + * Get the LCD framerate divisor (FDIV) setting. + * + * @return + * The LCD framerate divisor. + ******************************************************************************/ +uint32_t CMU_LCDClkFDIVGet(void) +{ +#if defined(LCD_PRESENT) && defined(_CMU_LCDCTRL_MASK) + return (CMU->LCDCTRL & _CMU_LCDCTRL_FDIV_MASK) >> _CMU_LCDCTRL_FDIV_SHIFT; +#else + return 0; +#endif /* defined(LCD_PRESENT) */ +} + +/***************************************************************************//** + * @brief + * Set the LCD framerate divisor (FDIV) setting. + * + * @note + * The FDIV field (CMU LCDCTRL register) should only be modified while the + * LCD module is clock disabled (CMU LFACLKEN0.LCD bit is 0). This function + * will NOT modify FDIV if the LCD module clock is enabled. Please refer to + * CMU_ClockEnable() for disabling/enabling LCD clock. + * + * @param[in] div + * The FDIV setting to use. + ******************************************************************************/ +void CMU_LCDClkFDIVSet(uint32_t div) +{ +#if defined(LCD_PRESENT) && defined(_CMU_LCDCTRL_MASK) + EFM_ASSERT(div <= cmuClkDiv_128); + + /* Do not allow modification if LCD clock enabled */ + if (CMU->LFACLKEN0 & CMU_LFACLKEN0_LCD) { + return; + } + + div <<= _CMU_LCDCTRL_FDIV_SHIFT; + div &= _CMU_LCDCTRL_FDIV_MASK; + CMU->LCDCTRL = (CMU->LCDCTRL & ~_CMU_LCDCTRL_FDIV_MASK) | div; +#else + (void)div; /* Unused parameter */ +#endif /* defined(LCD_PRESENT) */ +} + +/**************************************************************************//** + * @brief + * Set LFXO control registers + * + * @note + * LFXO configuration should be obtained from a configuration tool, + * app note or xtal datasheet. This function disables the LFXO to ensure + * a valid state before update. + * + * @param[in] lfxoInit + * LFXO setup parameters + *****************************************************************************/ +void CMU_LFXOInit(const CMU_LFXOInit_TypeDef *lfxoInit) +{ + /* Do not disable LFXO if it is currently selected as HF/Core clock */ + EFM_ASSERT(CMU_ClockSelectGet(cmuClock_HF) != cmuSelect_LFXO); + + /* LFXO must be disabled before reconfiguration */ + CMU_OscillatorEnable(cmuOsc_LFXO, false, false); + +#if defined(_CMU_LFXOCTRL_MASK) + BUS_RegMaskedWrite(&CMU->LFXOCTRL, + _CMU_LFXOCTRL_TUNING_MASK + | _CMU_LFXOCTRL_GAIN_MASK + | _CMU_LFXOCTRL_TIMEOUT_MASK + | _CMU_LFXOCTRL_MODE_MASK, + (lfxoInit->ctune << _CMU_LFXOCTRL_TUNING_SHIFT) + | (lfxoInit->gain << _CMU_LFXOCTRL_GAIN_SHIFT) + | (lfxoInit->timeout << _CMU_LFXOCTRL_TIMEOUT_SHIFT) + | (lfxoInit->mode << _CMU_LFXOCTRL_MODE_SHIFT)); +#else + bool cmuBoost = (lfxoInit->boost & 0x2); + BUS_RegMaskedWrite(&CMU->CTRL, + _CMU_CTRL_LFXOTIMEOUT_MASK + | _CMU_CTRL_LFXOBOOST_MASK + | _CMU_CTRL_LFXOMODE_MASK, + (lfxoInit->timeout << _CMU_CTRL_LFXOTIMEOUT_SHIFT) + | ((cmuBoost ? 1 : 0) << _CMU_CTRL_LFXOBOOST_SHIFT) + | (lfxoInit->mode << _CMU_CTRL_LFXOMODE_SHIFT)); +#endif + +#if defined(_EMU_AUXCTRL_REDLFXOBOOST_MASK) + bool emuReduce = (lfxoInit->boost & 0x1); + BUS_RegBitWrite(&EMU->AUXCTRL, _EMU_AUXCTRL_REDLFXOBOOST_SHIFT, emuReduce ? 1 : 0); +#endif +} + +/***************************************************************************//** + * @brief + * Enable/disable oscillator. + * + * @note + * WARNING: When this function is called to disable either cmuOsc_LFXO or + * cmuOsc_HFXO the LFXOMODE or HFXOMODE fields of the CMU_CTRL register + * are reset to the reset value. I.e. if external clock sources are selected + * in either LFXOMODE or HFXOMODE fields, the configuration will be cleared + * and needs to be reconfigured if needed later. + * + * @param[in] osc + * The oscillator to enable/disable. + * + * @param[in] enable + * @li true - enable specified oscillator. + * @li false - disable specified oscillator. + * + * @param[in] wait + * Only used if @p enable is true. + * @li true - wait for oscillator start-up time to timeout before returning. + * @li false - do not wait for oscillator start-up time to timeout before + * returning. + ******************************************************************************/ +void CMU_OscillatorEnable(CMU_Osc_TypeDef osc, bool enable, bool wait) +{ + uint32_t rdyBitPos; +#if defined(_SILICON_LABS_32B_SERIES_1) + uint32_t ensBitPos; +#endif +#if defined(_CMU_STATUS_HFXOPEAKDETRDY_MASK) + uint32_t hfxoTrimStatus; +#endif + + uint32_t enBit; + uint32_t disBit; + + switch (osc) { + case cmuOsc_HFRCO: + enBit = CMU_OSCENCMD_HFRCOEN; + disBit = CMU_OSCENCMD_HFRCODIS; + rdyBitPos = _CMU_STATUS_HFRCORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_HFRCOENS_SHIFT; +#endif + break; + + case cmuOsc_HFXO: + enBit = CMU_OSCENCMD_HFXOEN; + disBit = CMU_OSCENCMD_HFXODIS; + rdyBitPos = _CMU_STATUS_HFXORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_HFXOENS_SHIFT; +#endif + break; + + case cmuOsc_AUXHFRCO: + enBit = CMU_OSCENCMD_AUXHFRCOEN; + disBit = CMU_OSCENCMD_AUXHFRCODIS; + rdyBitPos = _CMU_STATUS_AUXHFRCORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_AUXHFRCOENS_SHIFT; +#endif + break; + + case cmuOsc_LFRCO: + enBit = CMU_OSCENCMD_LFRCOEN; + disBit = CMU_OSCENCMD_LFRCODIS; + rdyBitPos = _CMU_STATUS_LFRCORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_LFRCOENS_SHIFT; +#endif + break; + + case cmuOsc_LFXO: + enBit = CMU_OSCENCMD_LFXOEN; + disBit = CMU_OSCENCMD_LFXODIS; + rdyBitPos = _CMU_STATUS_LFXORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_LFXOENS_SHIFT; +#endif + break; + +#if defined(_CMU_STATUS_USHFRCOENS_MASK) + case cmuOsc_USHFRCO: + enBit = CMU_OSCENCMD_USHFRCOEN; + disBit = CMU_OSCENCMD_USHFRCODIS; + rdyBitPos = _CMU_STATUS_USHFRCORDY_SHIFT; +#if defined(_SILICON_LABS_32B_SERIES_1) + ensBitPos = _CMU_STATUS_USHFRCOENS_SHIFT; +#endif + break; +#endif + +#if defined(_CMU_STATUS_PLFRCOENS_MASK) + case cmuOsc_PLFRCO: + enBit = CMU_OSCENCMD_PLFRCOEN; + disBit = CMU_OSCENCMD_PLFRCODIS; + rdyBitPos = _CMU_STATUS_PLFRCORDY_SHIFT; + ensBitPos = _CMU_STATUS_PLFRCOENS_SHIFT; + break; +#endif + + default: + /* Undefined clock source or cmuOsc_ULFRCO. ULFRCO is always enabled, + and cannot be disabled. Ie. the definition of cmuOsc_ULFRCO is primarely + intended for information: the ULFRCO is always on. */ + EFM_ASSERT(0); + return; + } + + if (enable) { + #if defined(_CMU_HFXOCTRL_MASK) + bool firstHfxoEnable = false; + + /* Enabling the HFXO for the first time requires special handling. We use the + * PEAKDETSHUTOPTMODE field of the HFXOCTRL register to see if this is the + * first time the HFXO is enabled. */ + if ((osc == cmuOsc_HFXO) && (getHfxoTuningMode() == HFXO_TUNING_MODE_AUTO)) { + /* REGPWRSEL must be set to DVDD before the HFXO can be enabled. */ +#if defined(_EMU_PWRCTRL_REGPWRSEL_MASK) + EFM_ASSERT(EMU->PWRCTRL & EMU_PWRCTRL_REGPWRSEL_DVDD); +#endif + + firstHfxoEnable = true; + /* First time we enable an external clock we should switch to CMD mode to make sure that + * we only do SCO and not PDA tuning. */ + if ((CMU->HFXOCTRL & (_CMU_HFXOCTRL_MODE_MASK)) == CMU_HFXOCTRL_MODE_DIGEXTCLK) { + setHfxoTuningMode(HFXO_TUNING_MODE_CMD); + } + } +#endif + CMU->OSCENCMD = enBit; + +#if defined(_SILICON_LABS_32B_SERIES_1) + /* Always wait for ENS to go high */ + while (!BUS_RegBitRead(&CMU->STATUS, ensBitPos)) { + } +#endif + + /* Wait for clock to become ready after enable */ + if (wait) { + while (!BUS_RegBitRead(&CMU->STATUS, rdyBitPos)) ; +#if defined(_SILICON_LABS_32B_SERIES_1) + if ((osc == cmuOsc_HFXO) && firstHfxoEnable) { + if ((CMU->HFXOCTRL & _CMU_HFXOCTRL_MODE_MASK) == CMU_HFXOCTRL_MODE_DIGEXTCLK) { +#if defined(CMU_CMD_HFXOSHUNTOPTSTART) + /* External clock mode should only do shunt current optimization. */ + CMU_OscillatorTuningOptimize(cmuOsc_HFXO, cmuHFXOTuningMode_ShuntCommand, true); +#endif + } else { + /* Wait for peak detection and shunt current optimization to complete. */ + CMU_OscillatorTuningWait(cmuOsc_HFXO, cmuHFXOTuningMode_Auto); + } + + /* Disable the HFXO again to apply the trims. Apply trim from HFXOTRIMSTATUS + when disabled. */ + hfxoTrimStatus = CMU_OscillatorTuningGet(cmuOsc_HFXO); + CMU_OscillatorEnable(cmuOsc_HFXO, false, true); + CMU_OscillatorTuningSet(cmuOsc_HFXO, hfxoTrimStatus); + + /* Restart in CMD mode. */ + CMU->OSCENCMD = enBit; + while (!BUS_RegBitRead(&CMU->STATUS, rdyBitPos)) ; + } +#endif + } + } else { + CMU->OSCENCMD = disBit; + +#if defined(_SILICON_LABS_32B_SERIES_1) + /* Always wait for ENS to go low */ + while (BUS_RegBitRead(&CMU->STATUS, ensBitPos)) { + } +#endif + } +} + +/***************************************************************************//** + * @brief + * Get oscillator frequency tuning setting. + * + * @param[in] osc + * Oscillator to get tuning value for, one of: + * @li #cmuOsc_LFRCO + * @li #cmuOsc_HFRCO @if _CMU_USHFRCOCTRL_TUNING_MASK + * @li #cmuOsc_USHFRCO + * @endif + * @li #cmuOsc_AUXHFRCO + * @li #cmuOsc_HFXO if CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE is defined + * + * @return + * The oscillator frequency tuning setting in use. + ******************************************************************************/ +uint32_t CMU_OscillatorTuningGet(CMU_Osc_TypeDef osc) +{ + uint32_t ret; + + switch (osc) { + case cmuOsc_LFRCO: + ret = (CMU->LFRCOCTRL & _CMU_LFRCOCTRL_TUNING_MASK) + >> _CMU_LFRCOCTRL_TUNING_SHIFT; + break; + + case cmuOsc_HFRCO: + ret = (CMU->HFRCOCTRL & _CMU_HFRCOCTRL_TUNING_MASK) + >> _CMU_HFRCOCTRL_TUNING_SHIFT; + break; + +#if defined (_CMU_USHFRCOCTRL_TUNING_MASK) + case cmuOsc_USHFRCO: + ret = (CMU->USHFRCOCTRL & _CMU_USHFRCOCTRL_TUNING_MASK) + >> _CMU_USHFRCOCTRL_TUNING_SHIFT; + break; +#endif + + case cmuOsc_AUXHFRCO: + ret = (CMU->AUXHFRCOCTRL & _CMU_AUXHFRCOCTRL_TUNING_MASK) + >> _CMU_AUXHFRCOCTRL_TUNING_SHIFT; + break; + +#if defined(_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) + case cmuOsc_HFXO: + ret = CMU->HFXOTRIMSTATUS & (_CMU_HFXOTRIMSTATUS_IBTRIMXOCORE_MASK +#if defined(_CMU_HFXOTRIMSTATUS_REGISH_MASK) + | _CMU_HFXOTRIMSTATUS_REGISH_MASK +#endif + ); + break; +#endif + + default: + EFM_ASSERT(0); + ret = 0; + break; + } + + return ret; +} + +/***************************************************************************//** + * @brief + * Set the oscillator frequency tuning control. + * + * @note + * Oscillator tuning is done during production, and the tuning value is + * automatically loaded after a reset. Changing the tuning value from the + * calibrated value is for more advanced use. Certain oscillators also have + * build-in tuning optimization. + * + * @param[in] osc + * Oscillator to set tuning value for, one of: + * @li #cmuOsc_LFRCO + * @li #cmuOsc_HFRCO @if _CMU_USHFRCOCTRL_TUNING_MASK + * @li #cmuOsc_USHFRCO + * @endif + * @li #cmuOsc_AUXHFRCO + * @li #cmuOsc_HFXO if PEAKDETSHUNTOPTMODE is available. Note that CMD mode is set. + * + * @param[in] val + * The oscillator frequency tuning setting to use. + ******************************************************************************/ +void CMU_OscillatorTuningSet(CMU_Osc_TypeDef osc, uint32_t val) +{ +#if defined(_CMU_HFXOSTEADYSTATECTRL_REGISH_MASK) + uint32_t regIshUpper; +#endif + + switch (osc) { + case cmuOsc_LFRCO: + EFM_ASSERT(val <= (_CMU_LFRCOCTRL_TUNING_MASK + >> _CMU_LFRCOCTRL_TUNING_SHIFT)); + val &= (_CMU_LFRCOCTRL_TUNING_MASK >> _CMU_LFRCOCTRL_TUNING_SHIFT); +#if defined(_SILICON_LABS_32B_SERIES_1) + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_LFRCOBSY_SHIFT)) ; +#endif + CMU->LFRCOCTRL = (CMU->LFRCOCTRL & ~(_CMU_LFRCOCTRL_TUNING_MASK)) + | (val << _CMU_LFRCOCTRL_TUNING_SHIFT); + break; + + case cmuOsc_HFRCO: + EFM_ASSERT(val <= (_CMU_HFRCOCTRL_TUNING_MASK + >> _CMU_HFRCOCTRL_TUNING_SHIFT)); + val &= (_CMU_HFRCOCTRL_TUNING_MASK >> _CMU_HFRCOCTRL_TUNING_SHIFT); +#if defined(_SILICON_LABS_32B_SERIES_1) + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_HFRCOBSY_SHIFT)) { + } +#endif + CMU->HFRCOCTRL = (CMU->HFRCOCTRL & ~(_CMU_HFRCOCTRL_TUNING_MASK)) + | (val << _CMU_HFRCOCTRL_TUNING_SHIFT); + break; + +#if defined (_CMU_USHFRCOCTRL_TUNING_MASK) + case cmuOsc_USHFRCO: + EFM_ASSERT(val <= (_CMU_USHFRCOCTRL_TUNING_MASK + >> _CMU_USHFRCOCTRL_TUNING_SHIFT)); + val &= (_CMU_USHFRCOCTRL_TUNING_MASK >> _CMU_USHFRCOCTRL_TUNING_SHIFT); +#if defined(_SILICON_LABS_32B_SERIES_1) + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_USHFRCOBSY_SHIFT)) { + } +#endif + CMU->USHFRCOCTRL = (CMU->USHFRCOCTRL & ~(_CMU_USHFRCOCTRL_TUNING_MASK)) + | (val << _CMU_USHFRCOCTRL_TUNING_SHIFT); + break; +#endif + + case cmuOsc_AUXHFRCO: + EFM_ASSERT(val <= (_CMU_AUXHFRCOCTRL_TUNING_MASK + >> _CMU_AUXHFRCOCTRL_TUNING_SHIFT)); + val &= (_CMU_AUXHFRCOCTRL_TUNING_MASK >> _CMU_AUXHFRCOCTRL_TUNING_SHIFT); +#if defined(_SILICON_LABS_32B_SERIES_1) + while (BUS_RegBitRead(&CMU->SYNCBUSY, _CMU_SYNCBUSY_AUXHFRCOBSY_SHIFT)) { + } +#endif + CMU->AUXHFRCOCTRL = (CMU->AUXHFRCOCTRL & ~(_CMU_AUXHFRCOCTRL_TUNING_MASK)) + | (val << _CMU_AUXHFRCOCTRL_TUNING_SHIFT); + break; + +#if defined(_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) + case cmuOsc_HFXO: + + /* Do set PEAKDETSHUNTOPTMODE or HFXOSTEADYSTATECTRL if HFXO is enabled */ + EFM_ASSERT(!(CMU->STATUS & CMU_STATUS_HFXOENS)); + + /* Switch to command mode. Automatic SCO and PDA calibration is not done + at the next enable. Set user REGISH, REGISHUPPER and IBTRIMXOCORE. */ + CMU->HFXOCTRL = (CMU->HFXOCTRL & ~_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) + | CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_CMD; + +#if defined(_CMU_HFXOSTEADYSTATECTRL_REGISH_MASK) + regIshUpper = getRegIshUpperVal((val & _CMU_HFXOSTEADYSTATECTRL_REGISH_MASK) + >> _CMU_HFXOSTEADYSTATECTRL_REGISH_SHIFT); + CMU->HFXOSTEADYSTATECTRL = (CMU->HFXOSTEADYSTATECTRL + & ~(_CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_MASK + | _CMU_HFXOSTEADYSTATECTRL_REGISH_MASK + | _CMU_HFXOSTEADYSTATECTRL_REGISHUPPER_MASK)) + | val + | regIshUpper; +#else + CMU->HFXOSTEADYSTATECTRL = (CMU->HFXOSTEADYSTATECTRL + & ~_CMU_HFXOSTEADYSTATECTRL_IBTRIMXOCORE_MASK) + | val; +#endif + + break; +#endif + + default: + EFM_ASSERT(0); + break; + } +} + +#if defined(_CMU_HFXOCTRL_PEAKDETSHUNTOPTMODE_MASK) || defined(_CMU_HFXOCTRL_PEAKDETMODE_MASK) +/***************************************************************************//** + * @brief + * Wait for oscillator tuning optimization. + * + * @param[in] osc + * Oscillator to set tuning value for, one of: + * @li #cmuOsc_HFXO + * + * @param[in] mode + * Tuning optimization mode. + * + * @return + * Returns false on invalid parameters or oscillator error status. + ******************************************************************************/ +bool CMU_OscillatorTuningWait(CMU_Osc_TypeDef osc, + CMU_HFXOTuningMode_TypeDef mode) +{ + uint32_t waitFlags; + EFM_ASSERT(osc == cmuOsc_HFXO); + + /* Currently implemented for HFXO with PEAKDETSHUNTOPTMODE only */ + (void)osc; + + if (getHfxoTuningMode() == HFXO_TUNING_MODE_AUTO) { + waitFlags = HFXO_TUNING_READY_FLAGS; + } else { + /* Set wait flags for each command and wait */ + switch (mode) { +#if defined(_CMU_STATUS_HFXOSHUNTOPTRDY_MASK) + case cmuHFXOTuningMode_ShuntCommand: + waitFlags = CMU_STATUS_HFXOSHUNTOPTRDY; + break; +#endif + case cmuHFXOTuningMode_Auto: + waitFlags = HFXO_TUNING_READY_FLAGS; + break; + +#if defined(CMU_CMD_HFXOSHUNTOPTSTART) + case cmuHFXOTuningMode_PeakShuntCommand: + waitFlags = HFXO_TUNING_READY_FLAGS; + break; +#endif + + default: + waitFlags = _CMU_STATUS_MASK; + EFM_ASSERT(false); + } + } + while ((CMU->STATUS & waitFlags) != waitFlags) ; + +#if defined(CMU_IF_HFXOPEAKDETERR) + /* Check error flags */ + if (waitFlags & CMU_STATUS_HFXOPEAKDETRDY) { + return (CMU->IF & CMU_IF_HFXOPEAKDETERR ? true : false); + } +#endif + return true; +} + +/***************************************************************************//** + * @brief + * Start and optionally wait for oscillator tuning optimization. + * + * @param[in] osc + * Oscillator to set tuning value for, one of: + * @li #cmuOsc_HFXO + * + * @param[in] mode + * Tuning optimization mode. + * + * @param[in] wait + * Wait for tuning optimization to complete. + * true - wait for tuning optimization to complete. + * false - return without waiting. + * + * @return + * Returns false on invalid parameters or oscillator error status. + ******************************************************************************/ +bool CMU_OscillatorTuningOptimize(CMU_Osc_TypeDef osc, + CMU_HFXOTuningMode_TypeDef mode, + bool wait) +{ + switch (osc) { + case cmuOsc_HFXO: + if (mode) { +#if defined(CMU_IF_HFXOPEAKDETERR) + /* Clear error flag before command write */ + CMU->IFC = CMU_IFC_HFXOPEAKDETERR; +#endif + CMU->CMD = mode; + } + if (wait) { + return CMU_OscillatorTuningWait(osc, mode); + } + break; + + default: + EFM_ASSERT(false); + } + return true; +} +#endif + +/**************************************************************************//** + * @brief + * Determine if currently selected PCNTn clock used is external or LFBCLK. + * + * @param[in] instance + * PCNT instance number to get currently selected clock source for. + * + * @return + * @li true - selected clock is external clock. + * @li false - selected clock is LFBCLK. + *****************************************************************************/ +bool CMU_PCNTClockExternalGet(unsigned int instance) +{ + uint32_t setting; + + switch (instance) { +#if defined(_CMU_PCNTCTRL_PCNT0CLKEN_MASK) + case 0: + setting = CMU->PCNTCTRL & CMU_PCNTCTRL_PCNT0CLKSEL_PCNT0S0; + break; + +#if defined(_CMU_PCNTCTRL_PCNT1CLKEN_MASK) + case 1: + setting = CMU->PCNTCTRL & CMU_PCNTCTRL_PCNT1CLKSEL_PCNT1S0; + break; + +#if defined(_CMU_PCNTCTRL_PCNT2CLKEN_MASK) + case 2: + setting = CMU->PCNTCTRL & CMU_PCNTCTRL_PCNT2CLKSEL_PCNT2S0; + break; +#endif +#endif +#endif + + default: + setting = 0; + break; + } + return (setting ? true : false); +} + +/**************************************************************************//** + * @brief + * Select PCNTn clock. + * + * @param[in] instance + * PCNT instance number to set selected clock source for. + * + * @param[in] external + * Set to true to select external clock, false to select LFBCLK. + *****************************************************************************/ +void CMU_PCNTClockExternalSet(unsigned int instance, bool external) +{ +#if defined(PCNT_PRESENT) + uint32_t setting = 0; + + EFM_ASSERT(instance < PCNT_COUNT); + + if (external) { + setting = 1; + } + + BUS_RegBitWrite(&(CMU->PCNTCTRL), (instance * 2) + 1, setting); + +#else + (void)instance; /* Unused parameter */ + (void)external; /* Unused parameter */ +#endif +} + +#if defined(_CMU_USHFRCOCONF_BAND_MASK) +/***************************************************************************//** + * @brief + * Get USHFRCO band in use. + * + * @return + * USHFRCO band in use. + ******************************************************************************/ +CMU_USHFRCOBand_TypeDef CMU_USHFRCOBandGet(void) +{ + return (CMU_USHFRCOBand_TypeDef)((CMU->USHFRCOCONF + & _CMU_USHFRCOCONF_BAND_MASK) + >> _CMU_USHFRCOCONF_BAND_SHIFT); +} +#endif + +#if defined(_CMU_USHFRCOCONF_BAND_MASK) +/***************************************************************************//** + * @brief + * Set USHFRCO band to use. + * + * @param[in] band + * USHFRCO band to activate. + ******************************************************************************/ +void CMU_USHFRCOBandSet(CMU_USHFRCOBand_TypeDef band) +{ + uint32_t tuning; + uint32_t fineTuning; + CMU_Select_TypeDef osc; + + /* Cannot switch band if USHFRCO is already selected as HF clock. */ + osc = CMU_ClockSelectGet(cmuClock_HF); + EFM_ASSERT((CMU_USHFRCOBandGet() != band) && (osc != cmuSelect_USHFRCO)); + + /* Read tuning value from calibration table */ + switch (band) { + case cmuUSHFRCOBand_24MHz: + tuning = (DEVINFO->USHFRCOCAL0 & _DEVINFO_USHFRCOCAL0_BAND24_TUNING_MASK) + >> _DEVINFO_USHFRCOCAL0_BAND24_TUNING_SHIFT; + fineTuning = (DEVINFO->USHFRCOCAL0 + & _DEVINFO_USHFRCOCAL0_BAND24_FINETUNING_MASK) + >> _DEVINFO_USHFRCOCAL0_BAND24_FINETUNING_SHIFT; + ushfrcoFreq = 24000000UL; + break; + + case cmuUSHFRCOBand_48MHz: + tuning = (DEVINFO->USHFRCOCAL0 & _DEVINFO_USHFRCOCAL0_BAND48_TUNING_MASK) + >> _DEVINFO_USHFRCOCAL0_BAND48_TUNING_SHIFT; + fineTuning = (DEVINFO->USHFRCOCAL0 + & _DEVINFO_USHFRCOCAL0_BAND48_FINETUNING_MASK) + >> _DEVINFO_USHFRCOCAL0_BAND48_FINETUNING_SHIFT; + /* Enable the clock divider before switching the band from 24 to 48MHz */ + BUS_RegBitWrite(&CMU->USHFRCOCONF, _CMU_USHFRCOCONF_USHFRCODIV2DIS_SHIFT, 0); + ushfrcoFreq = 48000000UL; + break; + + default: + EFM_ASSERT(0); + return; + } + + /* Set band and tuning */ + CMU->USHFRCOCONF = (CMU->USHFRCOCONF & ~_CMU_USHFRCOCONF_BAND_MASK) + | (band << _CMU_USHFRCOCONF_BAND_SHIFT); + CMU->USHFRCOCTRL = (CMU->USHFRCOCTRL & ~_CMU_USHFRCOCTRL_TUNING_MASK) + | (tuning << _CMU_USHFRCOCTRL_TUNING_SHIFT); + CMU->USHFRCOTUNE = (CMU->USHFRCOTUNE & ~_CMU_USHFRCOTUNE_FINETUNING_MASK) + | (fineTuning << _CMU_USHFRCOTUNE_FINETUNING_SHIFT); + + /* Disable the clock divider after switching the band from 48 to 24MHz */ + if (band == cmuUSHFRCOBand_24MHz) { + BUS_RegBitWrite(&CMU->USHFRCOCONF, _CMU_USHFRCOCONF_USHFRCODIV2DIS_SHIFT, 1); + } +} +#endif + +/** @} (end addtogroup CMU) */ +/** @} (end addtogroup emlib) */ +#endif /* __EM_CMU_H */ diff --git a/efm32boot/emlib/em_cryotimer.c b/efm32boot/emlib/em_cryotimer.c new file mode 100644 index 0000000..66f4ac5 --- /dev/null +++ b/efm32boot/emlib/em_cryotimer.c @@ -0,0 +1,61 @@ +/***************************************************************************//** + * @file em_cryotimer.c + * @brief Ultra Low Energy Timer/Counter (CRYOTIMER) peripheral API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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.@n + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software.@n + * 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 "em_cryotimer.h" +#include "em_bus.h" + +#if defined(CRYOTIMER_PRESENT) && (CRYOTIMER_COUNT == 1) + +/***************************************************************************//** + * @brief + * Initialize the CRYOTIMER. + * + * @details + * Use this function to initialize the CRYOTIMER. + * Select prescaler setting and select low frequency oscillator. + * Refer to the configuration structure @ref CRYOTIMER_Init_TypeDef for more + * details. + * + * @param[in] init + * Pointer to initialization structure. + ******************************************************************************/ +void CRYOTIMER_Init(const CRYOTIMER_Init_TypeDef *init) +{ + CRYOTIMER->PERIODSEL = (uint32_t)init->period & _CRYOTIMER_PERIODSEL_MASK; + CRYOTIMER->CTRL = ((uint32_t)init->enable << _CRYOTIMER_CTRL_EN_SHIFT) + | ((uint32_t)init->debugRun << _CRYOTIMER_CTRL_DEBUGRUN_SHIFT) + | ((uint32_t)init->osc << _CRYOTIMER_CTRL_OSCSEL_SHIFT) + | ((uint32_t)init->presc << _CRYOTIMER_CTRL_PRESC_SHIFT); + CRYOTIMER_EM4WakeupEnable(init->em4Wakeup); +} + +#endif /* defined(CRYOTIMER_PRESENT) && (CRYOTIMER_COUNT > 0) */ diff --git a/efm32boot/emlib/em_emu.c b/efm32boot/emlib/em_emu.c new file mode 100644 index 0000000..1ba8a46 --- /dev/null +++ b/efm32boot/emlib/em_emu.c @@ -0,0 +1,2587 @@ +/***************************************************************************//** + * @file em_emu.c + * @brief Energy Management Unit (EMU) Peripheral API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 + +#include "em_emu.h" +#if defined(EMU_PRESENT) && (EMU_COUNT > 0) + +#include "em_cmu.h" +#include "em_system.h" +#include "em_common.h" +#include "em_assert.h" + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup EMU + * @brief Energy Management Unit (EMU) Peripheral API + * @details + * This module contains functions to control the EMU peripheral of Silicon + * Labs 32-bit MCUs and SoCs. The EMU handles the different low energy modes + * in Silicon Labs microcontrollers. + * @{ + ******************************************************************************/ + +/* Consistency check, since restoring assumes similar bitpositions in */ +/* CMU OSCENCMD and STATUS regs */ +#if (CMU_STATUS_AUXHFRCOENS != CMU_OSCENCMD_AUXHFRCOEN) +#error Conflict in AUXHFRCOENS and AUXHFRCOEN bitpositions +#endif +#if (CMU_STATUS_HFXOENS != CMU_OSCENCMD_HFXOEN) +#error Conflict in HFXOENS and HFXOEN bitpositions +#endif +#if (CMU_STATUS_LFRCOENS != CMU_OSCENCMD_LFRCOEN) +#error Conflict in LFRCOENS and LFRCOEN bitpositions +#endif +#if (CMU_STATUS_LFXOENS != CMU_OSCENCMD_LFXOEN) +#error Conflict in LFXOENS and LFXOEN bitpositions +#endif + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ +#if defined(_SILICON_LABS_32B_SERIES_0) +/* Fix for errata EMU_E107 - non-WIC interrupt masks. + * Zero Gecko and future families are not affected by errata EMU_E107 */ +#if defined(_EFM32_GECKO_FAMILY) +#define ERRATA_FIX_EMU_E107_EN +#define NON_WIC_INT_MASK_0 (~(0x0dfc0323U)) +#define NON_WIC_INT_MASK_1 (~(0x0U)) + +#elif defined(_EFM32_TINY_FAMILY) +#define ERRATA_FIX_EMU_E107_EN +#define NON_WIC_INT_MASK_0 (~(0x001be323U)) +#define NON_WIC_INT_MASK_1 (~(0x0U)) + +#elif defined(_EFM32_GIANT_FAMILY) +#define ERRATA_FIX_EMU_E107_EN +#define NON_WIC_INT_MASK_0 (~(0xff020e63U)) +#define NON_WIC_INT_MASK_1 (~(0x00000046U)) + +#elif defined(_EFM32_WONDER_FAMILY) +#define ERRATA_FIX_EMU_E107_EN +#define NON_WIC_INT_MASK_0 (~(0xff020e63U)) +#define NON_WIC_INT_MASK_1 (~(0x00000046U)) + +#endif +#endif + +/* Fix for errata EMU_E108 - High Current Consumption on EM4 Entry. */ +#if defined(_SILICON_LABS_32B_SERIES_0) && defined(_EFM32_HAPPY_FAMILY) +#define ERRATA_FIX_EMU_E108_EN +#endif + +/* Fix for errata EMU_E208 - Occasional Full Reset After Exiting EM4H */ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) +#define ERRATA_FIX_EMU_E208_EN +#endif + +/* Enable FETCNT tuning errata fix */ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) +#define ERRATA_FIX_DCDC_FETCNT_SET_EN +#endif + +/* Enable LN handshake errata fix */ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) +#define ERRATA_FIX_DCDC_LNHS_BLOCK_EN +typedef enum { + errataFixDcdcHsInit, + errataFixDcdcHsTrimSet, + errataFixDcdcHsBypassLn, + errataFixDcdcHsLnWaitDone +} errataFixDcdcHs_TypeDef; +static errataFixDcdcHs_TypeDef errataFixDcdcHsState = errataFixDcdcHsInit; +#endif + +/* Used to figure out if a memory address is inside or outside of a RAM block. + * A memory address is inside a RAM block if the address is greater than the + * RAM block address. */ +#define ADDRESS_NOT_IN_BLOCK(addr, block) ((addr) <= (block)) + +/* RAM Block layout for various device families. Note that some devices + * have special layout in RAM0 and some devices have a special RAM block + * at the end of their block layout. */ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_84) +#define RAM1_BLOCKS 2 +#define RAM1_BLOCK_SIZE 0x10000 // 64 kB blocks +#define RAM2_BLOCKS 1 +#define RAM2_BLOCK_SIZE 0x800 // 2 kB block +#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_89) +#define RAM0_BLOCKS 2 +#define RAM0_BLOCK_SIZE 0x4000 +#define RAM1_BLOCKS 2 +#define RAM1_BLOCK_SIZE 0x4000 // 16 kB blocks +#define RAM2_BLOCKS 1 +#define RAM2_BLOCK_SIZE 0x800 // 2 kB block +#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_95) +#define RAM0_BLOCKS 1 +#define RAM0_BLOCK_SIZE 0x4000 // 16 kB block +#define RAM1_BLOCKS 1 +#define RAM1_BLOCK_SIZE 0x4000 // 16 kB block +#define RAM2_BLOCKS 1 +#define RAM2_BLOCK_SIZE 0x800 // 2 kB block +#elif defined(_SILICON_LABS_32B_SERIES_0) && defined(_EFM32_GIANT_FAMILY) +#define RAM0_BLOCKS 4 +#define RAM0_BLOCK_SIZE 0x8000 // 32 kB blocks +#elif defined(_SILICON_LABS_32B_SERIES_0) && defined(_EFM32_GECKO_FAMILY) +#define RAM0_BLOCKS 4 +#define RAM0_BLOCK_SIZE 0x1000 // 4 kB blocks +#elif defined(_SILICON_LABS_32B_SERIES_1) && defined(_EFM32_GIANT_FAMILY) +#define RAM0_BLOCKS 8 +#define RAM0_BLOCK_SIZE 0x4000 // 16 kB blocks +#define RAM1_BLOCKS 8 +#define RAM1_BLOCK_SIZE 0x4000 // 16 kB blocks +#define RAM2_BLOCKS 4 +#define RAM2_BLOCK_SIZE 0x10000 // 64 kB blocks +#endif + +#if defined(_SILICON_LABS_32B_SERIES_0) +/* RAM_MEM_END on Gecko devices have a value larger than the SRAM_SIZE */ +#define RAM0_END (SRAM_BASE + SRAM_SIZE - 1) +#else +#define RAM0_END RAM_MEM_END +#endif + +#if defined(CMU_STATUS_HFXOSHUNTOPTRDY) +#define HFXO_STATUS_READY_FLAGS (CMU_STATUS_HFXOPEAKDETRDY | CMU_STATUS_HFXOSHUNTOPTRDY) +#elif defined(CMU_STATUS_HFXOPEAKDETRDY) +#define HFXO_STATUS_READY_FLAGS (CMU_STATUS_HFXOPEAKDETRDY) +#endif + +/** @endcond */ + +#if defined(_EMU_DCDCCTRL_MASK) +/* DCDCTODVDD output range min/max */ +#if !defined(PWRCFG_DCDCTODVDD_VMIN) +#define PWRCFG_DCDCTODVDD_VMIN 1800 +#endif +#if !defined(PWRCFG_DCDCTODVDD_VMAX) +#define PWRCFG_DCDCTODVDD_VMAX 3000 +#endif +#endif + +/******************************************************************************* + *************************** LOCAL VARIABLES ******************************** + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +/* Static user configuration */ +#if defined(_EMU_DCDCCTRL_MASK) +static uint16_t dcdcMaxCurrent_mA; +static uint16_t dcdcEm01LoadCurrent_mA; +static EMU_DcdcLnReverseCurrentControl_TypeDef dcdcReverseCurrentControl; +#endif +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +static EMU_EM01Init_TypeDef vScaleEM01Config = { false }; +#endif +/** @endcond */ + +/******************************************************************************* + ************************** LOCAL FUNCTIONS ******************************** + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +/* Convert from level to EM0 and 1 command bit */ +__STATIC_INLINE uint32_t vScaleEM01Cmd(EMU_VScaleEM01_TypeDef level) +{ + return EMU_CMD_EM01VSCALE0 << (_EMU_STATUS_VSCALE_VSCALE0 - (uint32_t)level); +} +#endif + +/***************************************************************************//** + * @brief + * Save/restore/update oscillator, core clock and voltage scaling configuration on + * EM2 or EM3 entry/exit. + * + * @details + * Hardware may automatically change oscillator and voltage scaling configuration + * when going into or out of an energy mode. Static data in this function keeps track of + * such configuration bits and is used to restore state if needed. + * + ******************************************************************************/ +typedef enum { + emState_Save, /* Save EMU and CMU state */ + emState_Restore, /* Restore and unlock */ +} emState_TypeDef; + +static void emState(emState_TypeDef action) +{ + uint32_t oscEnCmd; + uint32_t cmuLocked; + static uint32_t cmuStatus; + static CMU_Select_TypeDef hfClock; +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + static uint8_t vScaleStatus; +#endif + + /* Save or update state */ + if (action == emState_Save) { + /* Save configuration. */ + cmuStatus = CMU->STATUS; + hfClock = CMU_ClockSelectGet(cmuClock_HF); +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* Save vscale */ + EMU_VScaleWait(); + vScaleStatus = (uint8_t)((EMU->STATUS & _EMU_STATUS_VSCALE_MASK) + >> _EMU_STATUS_VSCALE_SHIFT); +#endif + } else if (action == emState_Restore) { /* Restore state */ + /* Apply saved configuration. */ +#if defined(_EMU_CMD_EM01VSCALE0_MASK) + /* Restore EM0 and 1 voltage scaling level. EMU_VScaleWait() is called later, + just before HF clock select is set. */ + EMU->CMD = vScaleEM01Cmd((EMU_VScaleEM01_TypeDef)vScaleStatus); +#endif + + /* CMU registers may be locked */ + cmuLocked = CMU->LOCK & CMU_LOCK_LOCKKEY_LOCKED; + CMU_Unlock(); + + /* AUXHFRCO are automatically disabled (except if using debugger). */ + /* HFRCO, USHFRCO and HFXO are automatically disabled. */ + /* LFRCO/LFXO may be disabled by SW in EM3. */ + /* Restore according to status prior to entering energy mode. */ + oscEnCmd = 0; + oscEnCmd |= ((cmuStatus & CMU_STATUS_HFRCOENS) ? CMU_OSCENCMD_HFRCOEN : 0); + oscEnCmd |= ((cmuStatus & CMU_STATUS_AUXHFRCOENS) ? CMU_OSCENCMD_AUXHFRCOEN : 0); + oscEnCmd |= ((cmuStatus & CMU_STATUS_LFRCOENS) ? CMU_OSCENCMD_LFRCOEN : 0); + oscEnCmd |= ((cmuStatus & CMU_STATUS_HFXOENS) ? CMU_OSCENCMD_HFXOEN : 0); + oscEnCmd |= ((cmuStatus & CMU_STATUS_LFXOENS) ? CMU_OSCENCMD_LFXOEN : 0); +#if defined(_CMU_STATUS_USHFRCOENS_MASK) + oscEnCmd |= ((cmuStatus & CMU_STATUS_USHFRCOENS) ? CMU_OSCENCMD_USHFRCOEN : 0); +#endif + CMU->OSCENCMD = oscEnCmd; + +#if defined(_EMU_STATUS_VSCALE_MASK) + /* Wait for upscale to complete and then restore selected clock */ + EMU_VScaleWait(); +#endif + + if (hfClock != cmuSelect_HFRCO) { + CMU_ClockSelectSet(cmuClock_HF, hfClock); + } + + /* If HFRCO was disabled before entering Energy Mode, turn it off again */ + /* as it is automatically enabled by wake up */ + if ( !(cmuStatus & CMU_STATUS_HFRCOENS) ) { + CMU->OSCENCMD = CMU_OSCENCMD_HFRCODIS; + } + + /* Restore CMU register locking */ + if (cmuLocked) { + CMU_Lock(); + } + } +} + +#if defined(ERRATA_FIX_EMU_E107_EN) +/* Get enable conditions for errata EMU_E107 fix. */ +__STATIC_INLINE bool getErrataFixEmuE107En(void) +{ + /* SYSTEM_ChipRevisionGet could have been used here, but we would like a + * faster implementation in this case. + */ + uint16_t majorMinorRev; + + /* CHIP MAJOR bit [3:0] */ + majorMinorRev = ((ROMTABLE->PID0 & _ROMTABLE_PID0_REVMAJOR_MASK) + >> _ROMTABLE_PID0_REVMAJOR_SHIFT) + << 8; + /* CHIP MINOR bit [7:4] */ + majorMinorRev |= ((ROMTABLE->PID2 & _ROMTABLE_PID2_REVMINORMSB_MASK) + >> _ROMTABLE_PID2_REVMINORMSB_SHIFT) + << 4; + /* CHIP MINOR bit [3:0] */ + majorMinorRev |= (ROMTABLE->PID3 & _ROMTABLE_PID3_REVMINORLSB_MASK) + >> _ROMTABLE_PID3_REVMINORLSB_SHIFT; + +#if defined(_EFM32_GECKO_FAMILY) + return (majorMinorRev <= 0x0103); +#elif defined(_EFM32_TINY_FAMILY) + return (majorMinorRev <= 0x0102); +#elif defined(_EFM32_GIANT_FAMILY) + return (majorMinorRev <= 0x0103) || (majorMinorRev == 0x0204); +#elif defined(_EFM32_WONDER_FAMILY) + return (majorMinorRev == 0x0100); +#else + /* Zero Gecko and future families are not affected by errata EMU_E107 */ + return false; +#endif +} +#endif + +/* LP prepare / LN restore P/NFET count */ +#define DCDC_LP_PFET_CNT 7 +#define DCDC_LP_NFET_CNT 7 +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) +static void currentLimitersUpdate(void); +static void dcdcFetCntSet(bool lpModeSet) +{ + uint32_t tmp; + static uint32_t emuDcdcMiscCtrlReg; + + if (lpModeSet) { + emuDcdcMiscCtrlReg = EMU->DCDCMISCCTRL; + tmp = EMU->DCDCMISCCTRL + & ~(_EMU_DCDCMISCCTRL_PFETCNT_MASK | _EMU_DCDCMISCCTRL_NFETCNT_MASK); + tmp |= (DCDC_LP_PFET_CNT << _EMU_DCDCMISCCTRL_PFETCNT_SHIFT) + | (DCDC_LP_NFET_CNT << _EMU_DCDCMISCCTRL_NFETCNT_SHIFT); + EMU->DCDCMISCCTRL = tmp; + currentLimitersUpdate(); + } else { + EMU->DCDCMISCCTRL = emuDcdcMiscCtrlReg; + currentLimitersUpdate(); + } +} +#endif + +#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN) +static void dcdcHsFixLnBlock(void) +{ +#define EMU_DCDCSTATUS (*(volatile uint32_t *)(EMU_BASE + 0x7C)) + if ((errataFixDcdcHsState == errataFixDcdcHsTrimSet) + || (errataFixDcdcHsState == errataFixDcdcHsBypassLn)) { + /* Wait for LNRUNNING */ + if ((EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK) == EMU_DCDCCTRL_DCDCMODE_LOWNOISE) { + while (!(EMU_DCDCSTATUS & (0x1 << 16))) ; + } + errataFixDcdcHsState = errataFixDcdcHsLnWaitDone; + } +} +#endif + +#if defined(_EMU_CTRL_EM23VSCALE_MASK) +/* Configure EMU and CMU for EM2 and 3 voltage downscale */ +static void vScaleDownEM23Setup(void) +{ + uint32_t hfSrcClockFrequency; + + EMU_VScaleEM23_TypeDef scaleEM23Voltage = + (EMU_VScaleEM23_TypeDef)((EMU->CTRL & _EMU_CTRL_EM23VSCALE_MASK) + >> _EMU_CTRL_EM23VSCALE_SHIFT); + + EMU_VScaleEM01_TypeDef currentEM01Voltage = + (EMU_VScaleEM01_TypeDef)((EMU->STATUS & _EMU_STATUS_VSCALE_MASK) + >> _EMU_STATUS_VSCALE_SHIFT); + + /* Wait until previous scaling is done. */ + EMU_VScaleWait(); + + /* Inverse coding. */ + if ((uint32_t)scaleEM23Voltage > (uint32_t)currentEM01Voltage) { + /* Set safe clock and wait-states. */ + if (scaleEM23Voltage == emuVScaleEM23_LowPower) { + hfSrcClockFrequency = CMU_ClockDivGet(cmuClock_HF) * CMU_ClockFreqGet(cmuClock_HF); + /* Set default low power voltage HFRCO band as HF clock. */ + if (hfSrcClockFrequency > CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX) { + CMU_HFRCOBandSet(cmuHFRCOFreq_19M0Hz); + } + CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFRCO); + } else { + /* Other voltage scaling levels are not currently supported. */ + EFM_ASSERT(false); + } + } else { + /* Same voltage or hardware will scale to min(EMU_CTRL_EM23VSCALE, EMU_STATUS_VSCALE) */ + } +} +#endif +/** @endcond */ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Enter energy mode 2 (EM2). + * + * @details + * When entering EM2, the high frequency clocks are disabled, ie HFXO, HFRCO + * and AUXHFRCO (for AUXHFRCO, see exception note below). When re-entering + * EM0, HFRCO is re-enabled and the core will be clocked by the configured + * HFRCO band. This ensures a quick wakeup from EM2. + * + * However, prior to entering EM2, the core may have been using another + * oscillator than HFRCO. The @p restore parameter gives the user the option + * to restore all HF oscillators according to state prior to entering EM2, + * as well as the clock used to clock the core. This restore procedure is + * handled by SW. However, since handled by SW, it will not be restored + * before completing the interrupt function(s) waking up the core! + * + * @note + * If restoring core clock to use the HFXO oscillator, which has been + * disabled during EM2 mode, this function will stall until the oscillator + * has stabilized. Stalling time can be reduced by adding interrupt + * support detecting stable oscillator, and an asynchronous switch to the + * original oscillator. See CMU documentation. Such a feature is however + * outside the scope of the implementation in this function. + * @par + * If HFXO is re-enabled by this function, and NOT used to clock the core, + * this function will not wait for HFXO to stabilize. This must be considered + * by the application if trying to use features relying on that oscillator + * upon return. + * @par + * If a debugger is attached, the AUXHFRCO will not be disabled if enabled + * upon entering EM2. It will thus remain enabled when returning to EM0 + * regardless of the @p restore parameter. + * @par + * If HFXO autostart and select is enabled by using CMU_HFXOAutostartEnable(), + * the starting and selecting of the core clocks will be identical to the user + * independently of the value of the @p restore parameter when waking up on + * the wakeup sources corresponding to the autostart and select setting. + * @par + * If voltage scaling is supported, the restore parameter is true and the EM0 + * voltage scaling level is set higher than the EM2 level, then the EM0 level is + * also restored. + * + * @param[in] restore + * @li true - save and restore oscillators, clocks and voltage scaling, see + * function details. + * @li false - do not save and restore oscillators and clocks, see function + * details. + * @par + * The @p restore option should only be used if all clock control is done + * via the CMU API. + ******************************************************************************/ +void EMU_EnterEM2(bool restore) +{ +#if defined(ERRATA_FIX_EMU_E107_EN) + bool errataFixEmuE107En; + uint32_t nonWicIntEn[2]; +#endif + + /* Only save EMU and CMU state if restored on wake-up. */ + if (restore) { + emState(emState_Save); + } + +#if defined(_EMU_CTRL_EM23VSCALE_MASK) + vScaleDownEM23Setup(); +#endif + + /* Enter Cortex deep sleep mode */ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + + /* Fix for errata EMU_E107 - store non-WIC interrupt enable flags. + Disable the enabled non-WIC interrupts. */ +#if defined(ERRATA_FIX_EMU_E107_EN) + errataFixEmuE107En = getErrataFixEmuE107En(); + if (errataFixEmuE107En) { + nonWicIntEn[0] = NVIC->ISER[0] & NON_WIC_INT_MASK_0; + NVIC->ICER[0] = nonWicIntEn[0]; +#if (NON_WIC_INT_MASK_1 != (~(0x0U))) + nonWicIntEn[1] = NVIC->ISER[1] & NON_WIC_INT_MASK_1; + NVIC->ICER[1] = nonWicIntEn[1]; +#endif + } +#endif + +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) + dcdcFetCntSet(true); +#endif +#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN) + dcdcHsFixLnBlock(); +#endif + + __WFI(); + +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) + dcdcFetCntSet(false); +#endif + + /* Fix for errata EMU_E107 - restore state of non-WIC interrupt enable flags. */ +#if defined(ERRATA_FIX_EMU_E107_EN) + if (errataFixEmuE107En) { + NVIC->ISER[0] = nonWicIntEn[0]; +#if (NON_WIC_INT_MASK_1 != (~(0x0U))) + NVIC->ISER[1] = nonWicIntEn[1]; +#endif + } +#endif + + /* Restore oscillators/clocks and voltage scaling if supported. */ + if (restore) { + emState(emState_Restore); + } else { + /* If not restoring, and original clock was not HFRCO, we have to */ + /* update CMSIS core clock variable since HF clock has changed */ + /* to HFRCO. */ + SystemCoreClockUpdate(); + } +} + +/***************************************************************************//** + * @brief + * Enter energy mode 3 (EM3). + * + * @details + * When entering EM3, the high frequency clocks are disabled by HW, ie HFXO, + * HFRCO and AUXHFRCO (for AUXHFRCO, see exception note below). In addition, + * the low frequency clocks, ie LFXO and LFRCO are disabled by SW. When + * re-entering EM0, HFRCO is re-enabled and the core will be clocked by the + * configured HFRCO band. This ensures a quick wakeup from EM3. + * + * However, prior to entering EM3, the core may have been using another + * oscillator than HFRCO. The @p restore parameter gives the user the option + * to restore all HF/LF oscillators according to state prior to entering EM3, + * as well as the clock used to clock the core. This restore procedure is + * handled by SW. However, since handled by SW, it will not be restored + * before completing the interrupt function(s) waking up the core! + * + * @note + * If restoring core clock to use an oscillator other than HFRCO, this + * function will stall until the oscillator has stabilized. Stalling time + * can be reduced by adding interrupt support detecting stable oscillator, + * and an asynchronous switch to the original oscillator. See CMU + * documentation. Such a feature is however outside the scope of the + * implementation in this function. + * @par + * If HFXO/LFXO/LFRCO are re-enabled by this function, and NOT used to clock + * the core, this function will not wait for those oscillators to stabilize. + * This must be considered by the application if trying to use features + * relying on those oscillators upon return. + * @par + * If a debugger is attached, the AUXHFRCO will not be disabled if enabled + * upon entering EM3. It will thus remain enabled when returning to EM0 + * regardless of the @p restore parameter. + * @par + * If voltage scaling is supported, the restore parameter is true and the EM0 + * voltage scaling level is set higher than the EM3 level, then the EM0 level is + * also restored. + * + * @param[in] restore + * @li true - save and restore oscillators, clocks and voltage scaling, see + * function details. + * @li false - do not save and restore oscillators and clocks, see function + * details. + * @par + * The @p restore option should only be used if all clock control is done + * via the CMU API. + ******************************************************************************/ +void EMU_EnterEM3(bool restore) +{ + uint32_t cmuLocked; + +#if defined(ERRATA_FIX_EMU_E107_EN) + bool errataFixEmuE107En; + uint32_t nonWicIntEn[2]; +#endif + + /* Only save EMU and CMU state if restored on wake-up. */ + if (restore) { + emState(emState_Save); + } + +#if defined(_EMU_CTRL_EM23VSCALE_MASK) + vScaleDownEM23Setup(); +#endif + + /* CMU registers may be locked */ + cmuLocked = CMU->LOCK & CMU_LOCK_LOCKKEY_LOCKED; + CMU_Unlock(); + + /* Disable LF oscillators */ + CMU->OSCENCMD = CMU_OSCENCMD_LFXODIS | CMU_OSCENCMD_LFRCODIS; + + /* Restore CMU register locking */ + if (cmuLocked) { + CMU_Lock(); + } + + /* Enter Cortex deep sleep mode */ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + + /* Fix for errata EMU_E107 - store non-WIC interrupt enable flags. + Disable the enabled non-WIC interrupts. */ +#if defined(ERRATA_FIX_EMU_E107_EN) + errataFixEmuE107En = getErrataFixEmuE107En(); + if (errataFixEmuE107En) { + nonWicIntEn[0] = NVIC->ISER[0] & NON_WIC_INT_MASK_0; + NVIC->ICER[0] = nonWicIntEn[0]; +#if (NON_WIC_INT_MASK_1 != (~(0x0U))) + nonWicIntEn[1] = NVIC->ISER[1] & NON_WIC_INT_MASK_1; + NVIC->ICER[1] = nonWicIntEn[1]; +#endif + } +#endif + +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) + dcdcFetCntSet(true); +#endif +#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN) + dcdcHsFixLnBlock(); +#endif + + __WFI(); + +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) + dcdcFetCntSet(false); +#endif + + /* Fix for errata EMU_E107 - restore state of non-WIC interrupt enable flags. */ +#if defined(ERRATA_FIX_EMU_E107_EN) + if (errataFixEmuE107En) { + NVIC->ISER[0] = nonWicIntEn[0]; +#if (NON_WIC_INT_MASK_1 != (~(0x0U))) + NVIC->ISER[1] = nonWicIntEn[1]; +#endif + } +#endif + + /* Restore oscillators/clocks and voltage scaling if supported. */ + if (restore) { + emState(emState_Restore); + } else { + /* If not restoring, and original clock was not HFRCO, we have to */ + /* update CMSIS core clock variable since HF clock has changed */ + /* to HFRCO. */ + SystemCoreClockUpdate(); + } +} + +/***************************************************************************//** + * @brief + * Save CMU HF clock select state, oscillator enable and voltage scaling + * (if available) before @ref EMU_EnterEM2() or @ref EMU_EnterEM3() are called + * with the restore parameter set to false. Calling this function is + * equivalent to calling @ref EMU_EnterEM2() or @ref EMU_EnterEM3() with the + * restore parameter set to true, but it allows the state to be saved without + * going to sleep. The state can be restored manually by calling + * @ref EMU_Restore(). + ******************************************************************************/ +void EMU_Save(void) +{ + emState(emState_Save); +} + +/***************************************************************************//** + * @brief + * Restore CMU HF clock select state, oscillator enable and voltage scaling + * (if available) after @ref EMU_EnterEM2() or @ref EMU_EnterEM3() are called + * with the restore parameter set to false. Calling this function is + * equivalent to calling @ref EMU_EnterEM2() or @ref EMU_EnterEM3() with the + * restore parameter set to true, but it allows the application to evaluate the + * wakeup reason before restoring state. + ******************************************************************************/ +void EMU_Restore(void) +{ + emState(emState_Restore); +} + +/***************************************************************************//** + * @brief + * Enter energy mode 4 (EM4). + * + * @note + * Only a power on reset or external reset pin can wake the device from EM4. + ******************************************************************************/ +void EMU_EnterEM4(void) +{ + int i; + +#if defined(_EMU_EM4CTRL_EM4ENTRY_SHIFT) + uint32_t em4seq2 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK) + | (2 << _EMU_EM4CTRL_EM4ENTRY_SHIFT); + uint32_t em4seq3 = (EMU->EM4CTRL & ~_EMU_EM4CTRL_EM4ENTRY_MASK) + | (3 << _EMU_EM4CTRL_EM4ENTRY_SHIFT); +#else + uint32_t em4seq2 = (EMU->CTRL & ~_EMU_CTRL_EM4CTRL_MASK) + | (2 << _EMU_CTRL_EM4CTRL_SHIFT); + uint32_t em4seq3 = (EMU->CTRL & ~_EMU_CTRL_EM4CTRL_MASK) + | (3 << _EMU_CTRL_EM4CTRL_SHIFT); +#endif + + /* Make sure register write lock is disabled */ + EMU_Unlock(); + +#if defined(_EMU_EM4CTRL_MASK) + if ((EMU->EM4CTRL & _EMU_EM4CTRL_EM4STATE_MASK) == EMU_EM4CTRL_EM4STATE_EM4S) { + uint32_t dcdcMode = EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK; + if (dcdcMode == EMU_DCDCCTRL_DCDCMODE_LOWNOISE + || dcdcMode == EMU_DCDCCTRL_DCDCMODE_LOWPOWER) { + /* DCDC is not supported in EM4S so we switch DCDC to bypass mode before + * entering EM4S */ + EMU_DCDCModeSet(emuDcdcMode_Bypass); + } + } +#endif + +#if defined(_EMU_EM4CTRL_MASK) && defined(ERRATA_FIX_EMU_E208_EN) + if (EMU->EM4CTRL & EMU_EM4CTRL_EM4STATE_EM4H) { + /* Fix for errata EMU_E208 - Occasional Full Reset After Exiting EM4H. + * Full description of errata fix can be found in the errata document. */ + __disable_irq(); + *(volatile uint32_t *)(EMU_BASE + 0x190) = 0x0000ADE8UL; + *(volatile uint32_t *)(EMU_BASE + 0x198) |= (0x1UL << 7); + *(volatile uint32_t *)(EMU_BASE + 0x88) |= (0x1UL << 8); + } +#endif + +#if defined(ERRATA_FIX_EMU_E108_EN) + /* Fix for errata EMU_E108 - High Current Consumption on EM4 Entry. */ + __disable_irq(); + *(volatile uint32_t *)0x400C80E4 = 0; +#endif + +#if defined(ERRATA_FIX_DCDC_FETCNT_SET_EN) + dcdcFetCntSet(true); +#endif +#if defined(ERRATA_FIX_DCDC_LNHS_BLOCK_EN) + dcdcHsFixLnBlock(); +#endif + + for (i = 0; i < 4; i++) { +#if defined(_EMU_EM4CTRL_EM4ENTRY_SHIFT) + EMU->EM4CTRL = em4seq2; + EMU->EM4CTRL = em4seq3; + } + EMU->EM4CTRL = em4seq2; +#else + EMU->CTRL = em4seq2; + EMU->CTRL = em4seq3; + } + EMU->CTRL = em4seq2; +#endif +} + +#if defined(_EMU_EM4CTRL_MASK) +/***************************************************************************//** + * @brief + * Enter energy mode 4 hibernate (EM4H). + * + * @note + * Retention of clocks and GPIO in EM4 can be configured using + * @ref EMU_EM4Init before calling this function. + ******************************************************************************/ +void EMU_EnterEM4H(void) +{ + BUS_RegBitWrite(&EMU->EM4CTRL, _EMU_EM4CTRL_EM4STATE_SHIFT, 1); + EMU_EnterEM4(); +} + +/***************************************************************************//** + * @brief + * Enter energy mode 4 shutoff (EM4S). + * + * @note + * Retention of clocks and GPIO in EM4 can be configured using + * @ref EMU_EM4Init before calling this function. + ******************************************************************************/ +void EMU_EnterEM4S(void) +{ + BUS_RegBitWrite(&EMU->EM4CTRL, _EMU_EM4CTRL_EM4STATE_SHIFT, 0); + EMU_EnterEM4(); +} +#endif + +/***************************************************************************//** + * @brief + * Power down memory block. + * + * @param[in] blocks + * Specifies a logical OR of bits indicating memory blocks to power down. + * Bit 0 selects block 1, bit 1 selects block 2, etc. Memory block 0 cannot + * be disabled. Please refer to the reference manual for available + * memory blocks for a device. + * + * @note + * Only a POR reset can power up the specified memory block(s) after powerdown. + * + * @deprecated + * This function is deprecated, use @ref EMU_RamPowerDown() instead which + * maps a user provided memory range into RAM blocks to power down. + ******************************************************************************/ +void EMU_MemPwrDown(uint32_t blocks) +{ +#if defined(_EMU_MEMCTRL_MASK) + EMU->MEMCTRL = blocks & _EMU_MEMCTRL_MASK; +#elif defined(_EMU_RAM0CTRL_MASK) + EMU->RAM0CTRL = blocks & _EMU_RAM0CTRL_MASK; +#else + (void)blocks; +#endif +} + +/***************************************************************************//** + * @brief + * Power down RAM memory blocks. + * + * @details + * This function will power down all the RAM blocks that are within a given + * range. The RAM block layout is different between device families, so this + * function can be used in a generic way to power down a RAM memory region + * which is known to be unused. + * + * This function will only power down blocks which are completely enclosed + * by the memory range given by [start, end). + * + * Here is an example of how to power down all RAM blocks except the first + * one. The first RAM block is special in that it cannot be powered down + * by the hardware. The size of this first RAM block is device specific + * see the reference manual to find the RAM block sizes. + * + * @code + * EMU_RamPowerDown(SRAM_BASE, SRAM_BASE + SRAM_SIZE); + * @endcode + * + * @note + * Only a POR reset can power up the specified memory block(s) after powerdown. + * + * @param[in] start + * The start address of the RAM region to power down. This address is + * inclusive. + * + * @param[in] end + * The end address of the RAM region to power down. This address is + * exclusive. If this parameter is 0, then all RAM blocks contained in the + * region from start to the upper RAM address will be powered down. + ******************************************************************************/ +void EMU_RamPowerDown(uint32_t start, uint32_t end) +{ + uint32_t mask = 0; + + if (end == 0) { + end = SRAM_BASE + SRAM_SIZE; + } + + // Check to see if something in RAM0 can be powered down + if (end > RAM0_END) { +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_84) // EFM32xG12 and EFR32xG12 + // Block 0 is 16 kB and cannot be powered off + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20004000) << 0; // Block 1, 16 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20008000) << 1; // Block 2, 16 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x2000C000) << 2; // Block 3, 16 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20010000) << 3; // Block 4, 64 kB +#elif defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) // EFM32xG1 and EFR32xG1 + // Block 0 is 4 kB and cannot be powered off + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20001000) << 0; // Block 1, 4 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20002000) << 1; // Block 2, 8 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20004000) << 2; // Block 3, 8 kB + mask |= ADDRESS_NOT_IN_BLOCK(start, 0x20006000) << 3; // Block 4, 7 kB +#elif defined(RAM0_BLOCKS) + // These platforms have equally sized RAM blocks + for (int i = 1; i < RAM0_BLOCKS; i++) { + mask |= ADDRESS_NOT_IN_BLOCK(start, RAM_MEM_BASE + (i * RAM0_BLOCK_SIZE)) << (i - 1); + } +#endif + } + + // Power down the selected blocks +#if defined(_EMU_MEMCTRL_MASK) + EMU->MEMCTRL = EMU->MEMCTRL | mask; +#elif defined(_EMU_RAM0CTRL_MASK) + EMU->RAM0CTRL = EMU->RAM0CTRL | mask; +#else + // These devices are unable to power down RAM blocks + (void) mask; + (void) start; +#endif + +#if defined(RAM1_MEM_END) + mask = 0; + if (end > RAM1_MEM_END) { + for (int i = 0; i < RAM1_BLOCKS; i++) { + mask |= ADDRESS_NOT_IN_BLOCK(start, RAM1_MEM_BASE + (i * RAM1_BLOCK_SIZE)) << i; + } + } + EMU->RAM1CTRL |= mask; +#endif + +#if defined(RAM2_MEM_END) + mask = 0; + if (end > RAM2_MEM_END) { + for (int i = 0; i < RAM2_BLOCKS; i++) { + mask |= ADDRESS_NOT_IN_BLOCK(start, RAM2_MEM_BASE + (i * RAM2_BLOCK_SIZE)) << i; + } + } + EMU->RAM2CTRL |= mask; +#endif +} + +#if defined(_EMU_EM23PERNORETAINCTRL_MASK) +/***************************************************************************//** + * @brief + * Set EM2 3 peripheral retention control. + * + * @param[in] periMask + * Peripheral select mask. Use | operator to select multiple peripheral, for example + * @ref emuPeripheralRetention_LEUART0 | @ref emuPeripheralRetention_VDAC0. + * @param[in] enable + * Peripheral retention enable (true) or disable (false). + * + * + * @note + * Only peripheral retention disable is currently supported. Peripherals are + * enabled by default, and can only be disabled. + ******************************************************************************/ +void EMU_PeripheralRetention(EMU_PeripheralRetention_TypeDef periMask, bool enable) +{ + EFM_ASSERT(!enable); + EMU->EM23PERNORETAINCTRL = periMask & emuPeripheralRetention_ALL; +} +#endif + +/***************************************************************************//** + * @brief + * Update EMU module with CMU oscillator selection/enable status. + * + * @deprecated + * Oscillator status is saved in @ref EMU_EnterEM2() and @ref EMU_EnterEM3(). + ******************************************************************************/ +void EMU_UpdateOscConfig(void) +{ + emState(emState_Save); +} + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +/***************************************************************************//** + * @brief + * Voltage scale in EM0 and 1 by clock frequency. + * + * @param[in] clockFrequency + * Use CMSIS HF clock if 0, or override to custom clock. Providing a + * custom clock frequency is required if using a non-standard HFXO + * frequency. + * @param[in] wait + * Wait for scaling to complete. + * + * @note + * This function is primarily needed by the @ref CMU module. + ******************************************************************************/ +void EMU_VScaleEM01ByClock(uint32_t clockFrequency, bool wait) +{ + uint32_t hfSrcClockFrequency; + uint32_t hfPresc = 1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK) + >> _CMU_HFPRESC_PRESC_SHIFT); + + /* VSCALE frequency is HFSRCCLK */ + if (clockFrequency == 0) { + hfSrcClockFrequency = SystemHFClockGet() * hfPresc; + } else { + hfSrcClockFrequency = clockFrequency; + } + + /* Apply EM0 and 1 voltage scaling command. */ + if (vScaleEM01Config.vScaleEM01LowPowerVoltageEnable + && (hfSrcClockFrequency < CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX)) { + EMU_VScaleEM01(emuVScaleEM01_LowPower, wait); + } else { + EMU_VScaleEM01(emuVScaleEM01_HighPerformance, wait); + } +} +#endif + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +/***************************************************************************//** + * @brief + * Force voltage scaling in EM0 and 1 to a specific voltage level. + * + * @param[in] voltage + * Target VSCALE voltage level. + * @param[in] wait + * Wait for scaling to complate. + * + * @note + * This function is useful for upscaling before programming Flash from @ref MSC, + * and downscaling after programming is done. Flash programming is only supported + * at @ref emuVScaleEM01_HighPerformance. + * + * @note + * This function ignores @ref vScaleEM01LowPowerVoltageEnable set from @ref + * EMU_EM01Init(). + ******************************************************************************/ +void EMU_VScaleEM01(EMU_VScaleEM01_TypeDef voltage, bool wait) +{ + uint32_t hfSrcClockFrequency; + uint32_t hfPresc = 1U + ((CMU->HFPRESC & _CMU_HFPRESC_PRESC_MASK) + >> _CMU_HFPRESC_PRESC_SHIFT); + uint32_t hfFreq = SystemHFClockGet(); + EMU_VScaleEM01_TypeDef current = EMU_VScaleGet(); + + if (current == voltage) { + /* Voltage is already at correct level. */ + return; + } + + hfSrcClockFrequency = hfFreq * hfPresc; + + if (voltage == emuVScaleEM01_LowPower) { + EFM_ASSERT(hfSrcClockFrequency <= CMU_VSCALEEM01_LOWPOWER_VOLTAGE_CLOCK_MAX); + /* Update wait states before scaling down voltage */ + CMU_UpdateWaitStates(hfFreq, emuVScaleEM01_LowPower); + } + + EMU->CMD = vScaleEM01Cmd(voltage); + + if (voltage == emuVScaleEM01_HighPerformance) { + /* Update wait states after scaling up voltage */ + CMU_UpdateWaitStates(hfFreq, emuVScaleEM01_HighPerformance); + } + + if (wait) { + EMU_VScaleWait(); + } +} +#endif + +#if defined(_EMU_CMD_EM01VSCALE0_MASK) +/***************************************************************************//** + * @brief + * Update EMU module with Energy Mode 0 and 1 configuration + * + * @param[in] em01Init + * Energy Mode 0 and 1 configuration structure + ******************************************************************************/ +void EMU_EM01Init(const EMU_EM01Init_TypeDef *em01Init) +{ + vScaleEM01Config.vScaleEM01LowPowerVoltageEnable = + em01Init->vScaleEM01LowPowerVoltageEnable; + EMU_VScaleEM01ByClock(0, true); +} +#endif + +/***************************************************************************//** + * @brief + * Update EMU module with Energy Mode 2 and 3 configuration + * + * @param[in] em23Init + * Energy Mode 2 and 3 configuration structure + ******************************************************************************/ +void EMU_EM23Init(const EMU_EM23Init_TypeDef *em23Init) +{ +#if defined(_EMU_CTRL_EMVREG_MASK) + EMU->CTRL = em23Init->em23VregFullEn ? (EMU->CTRL | EMU_CTRL_EMVREG) + : (EMU->CTRL & ~EMU_CTRL_EMVREG); +#elif defined(_EMU_CTRL_EM23VREG_MASK) + EMU->CTRL = em23Init->em23VregFullEn ? (EMU->CTRL | EMU_CTRL_EM23VREG) + : (EMU->CTRL & ~EMU_CTRL_EM23VREG); +#else + (void)em23Init; +#endif + +#if defined(_EMU_CTRL_EM23VSCALE_MASK) + EMU->CTRL = (EMU->CTRL & ~_EMU_CTRL_EM23VSCALE_MASK) + | (em23Init->vScaleEM23Voltage << _EMU_CTRL_EM23VSCALE_SHIFT); +#endif +} + +#if defined(_EMU_EM4CONF_MASK) || defined(_EMU_EM4CTRL_MASK) +/***************************************************************************//** + * @brief + * Update EMU module with Energy Mode 4 configuration + * + * @param[in] em4Init + * Energy Mode 4 configuration structure + ******************************************************************************/ +void EMU_EM4Init(const EMU_EM4Init_TypeDef *em4Init) +{ +#if defined(_EMU_EM4CONF_MASK) + /* Init for platforms with EMU->EM4CONF register */ + uint32_t em4conf = EMU->EM4CONF; + + /* Clear fields that will be reconfigured */ + em4conf &= ~(_EMU_EM4CONF_LOCKCONF_MASK + | _EMU_EM4CONF_OSC_MASK + | _EMU_EM4CONF_BURTCWU_MASK + | _EMU_EM4CONF_VREGEN_MASK); + + /* Configure new settings */ + em4conf |= (em4Init->lockConfig << _EMU_EM4CONF_LOCKCONF_SHIFT) + | (em4Init->osc) + | (em4Init->buRtcWakeup << _EMU_EM4CONF_BURTCWU_SHIFT) + | (em4Init->vreg << _EMU_EM4CONF_VREGEN_SHIFT); + + /* Apply configuration. Note that lock can be set after this stage. */ + EMU->EM4CONF = em4conf; + +#elif defined(_EMU_EM4CTRL_MASK) + /* Init for platforms with EMU->EM4CTRL register */ + + uint32_t em4ctrl = EMU->EM4CTRL; + + em4ctrl &= ~(_EMU_EM4CTRL_RETAINLFXO_MASK + | _EMU_EM4CTRL_RETAINLFRCO_MASK + | _EMU_EM4CTRL_RETAINULFRCO_MASK + | _EMU_EM4CTRL_EM4STATE_MASK + | _EMU_EM4CTRL_EM4IORETMODE_MASK); + + em4ctrl |= (em4Init->retainLfxo ? EMU_EM4CTRL_RETAINLFXO : 0) + | (em4Init->retainLfrco ? EMU_EM4CTRL_RETAINLFRCO : 0) + | (em4Init->retainUlfrco ? EMU_EM4CTRL_RETAINULFRCO : 0) + | (em4Init->em4State ? EMU_EM4CTRL_EM4STATE_EM4H : 0) + | (em4Init->pinRetentionMode); + + EMU->EM4CTRL = em4ctrl; +#endif + +#if defined(_EMU_CTRL_EM4HVSCALE_MASK) + EMU->CTRL = (EMU->CTRL & ~_EMU_CTRL_EM4HVSCALE_MASK) + | (em4Init->vScaleEM4HVoltage << _EMU_CTRL_EM4HVSCALE_SHIFT); +#endif +} +#endif + +#if defined(BU_PRESENT) +/***************************************************************************//** + * @brief + * Configure Backup Power Domain settings + * + * @param[in] bupdInit + * Backup power domain initialization structure + ******************************************************************************/ +void EMU_BUPDInit(const EMU_BUPDInit_TypeDef *bupdInit) +{ + uint32_t reg; + + /* Set power connection configuration */ + reg = EMU->PWRCONF & ~(_EMU_PWRCONF_PWRRES_MASK + | _EMU_PWRCONF_VOUTSTRONG_MASK + | _EMU_PWRCONF_VOUTMED_MASK + | _EMU_PWRCONF_VOUTWEAK_MASK); + + reg |= bupdInit->resistor + | (bupdInit->voutStrong << _EMU_PWRCONF_VOUTSTRONG_SHIFT) + | (bupdInit->voutMed << _EMU_PWRCONF_VOUTMED_SHIFT) + | (bupdInit->voutWeak << _EMU_PWRCONF_VOUTWEAK_SHIFT); + + EMU->PWRCONF = reg; + + /* Set backup domain inactive mode configuration */ + reg = EMU->BUINACT & ~(_EMU_BUINACT_PWRCON_MASK); + reg |= (bupdInit->inactivePower); + EMU->BUINACT = reg; + + /* Set backup domain active mode configuration */ + reg = EMU->BUACT & ~(_EMU_BUACT_PWRCON_MASK); + reg |= (bupdInit->activePower); + EMU->BUACT = reg; + + /* Set power control configuration */ + reg = EMU->BUCTRL & ~(_EMU_BUCTRL_PROBE_MASK + | _EMU_BUCTRL_BODCAL_MASK + | _EMU_BUCTRL_STATEN_MASK + | _EMU_BUCTRL_EN_MASK); + + /* Note use of ->enable to both enable BUPD, use BU_VIN pin input and + release reset */ + reg |= bupdInit->probe + | (bupdInit->bodCal << _EMU_BUCTRL_BODCAL_SHIFT) + | (bupdInit->statusPinEnable << _EMU_BUCTRL_STATEN_SHIFT) + | (bupdInit->enable << _EMU_BUCTRL_EN_SHIFT); + + /* Enable configuration */ + EMU->BUCTRL = reg; + + /* If enable is true, enable BU_VIN input power pin, if not disable it */ + EMU_BUPinEnable(bupdInit->enable); + + /* If enable is true, release BU reset, if not keep reset asserted */ + BUS_RegBitWrite(&(RMU->CTRL), _RMU_CTRL_BURSTEN_SHIFT, !bupdInit->enable); +} + +/***************************************************************************//** + * @brief + * Configure Backup Power Domain BOD Threshold value + * @note + * These values are precalibrated + * @param[in] mode Active or Inactive mode + * @param[in] value + ******************************************************************************/ +void EMU_BUThresholdSet(EMU_BODMode_TypeDef mode, uint32_t value) +{ + EFM_ASSERT(value < 8); + EFM_ASSERT(value <= (_EMU_BUACT_BUEXTHRES_MASK >> _EMU_BUACT_BUEXTHRES_SHIFT)); + + switch (mode) { + case emuBODMode_Active: + EMU->BUACT = (EMU->BUACT & ~_EMU_BUACT_BUEXTHRES_MASK) + | (value << _EMU_BUACT_BUEXTHRES_SHIFT); + break; + case emuBODMode_Inactive: + EMU->BUINACT = (EMU->BUINACT & ~_EMU_BUINACT_BUENTHRES_MASK) + | (value << _EMU_BUINACT_BUENTHRES_SHIFT); + break; + } +} + +/***************************************************************************//** + * @brief + * Configure Backup Power Domain BOD Threshold Range + * @note + * These values are precalibrated + * @param[in] mode Active or Inactive mode + * @param[in] value + ******************************************************************************/ +void EMU_BUThresRangeSet(EMU_BODMode_TypeDef mode, uint32_t value) +{ + EFM_ASSERT(value < 4); + EFM_ASSERT(value <= (_EMU_BUACT_BUEXRANGE_MASK >> _EMU_BUACT_BUEXRANGE_SHIFT)); + + switch (mode) { + case emuBODMode_Active: + EMU->BUACT = (EMU->BUACT & ~_EMU_BUACT_BUEXRANGE_MASK) + | (value << _EMU_BUACT_BUEXRANGE_SHIFT); + break; + case emuBODMode_Inactive: + EMU->BUINACT = (EMU->BUINACT & ~_EMU_BUINACT_BUENRANGE_MASK) + | (value << _EMU_BUINACT_BUENRANGE_SHIFT); + break; + } +} +#endif + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ +#if defined(_EMU_DCDCCTRL_MASK) +/* Translate fields with different names across platform generations to common names. */ +#if defined(_EMU_DCDCMISCCTRL_LPCMPBIAS_MASK) +#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK _EMU_DCDCMISCCTRL_LPCMPBIAS_MASK +#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT _EMU_DCDCMISCCTRL_LPCMPBIAS_SHIFT +#elif defined(_EMU_DCDCMISCCTRL_LPCMPBIASEM234H_MASK) +#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK _EMU_DCDCMISCCTRL_LPCMPBIASEM234H_MASK +#define _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT _EMU_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT +#endif +#if defined(_EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK) +#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK _EMU_DCDCLPCTRL_LPCMPHYSSEL_MASK +#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT _EMU_DCDCLPCTRL_LPCMPHYSSEL_SHIFT +#elif defined(_EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK) +#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK _EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK +#define _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT _EMU_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT +#endif + +/* Internal DCDC trim modes. */ +typedef enum { + dcdcTrimMode_EM234H_LP = 0, +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + dcdcTrimMode_EM01_LP, +#endif + dcdcTrimMode_LN, +} dcdcTrimMode_TypeDef; + +/***************************************************************************//** + * @brief + * Load DCDC calibration constants from DI page. Const means calibration + * data that does not change depending on other configuration parameters. + * + * @return + * False if calibration registers are locked + ******************************************************************************/ +static bool dcdcConstCalibrationLoad(void) +{ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) + uint32_t val; + volatile uint32_t *reg; + + /* DI calib data in flash */ + volatile uint32_t* const diCal_EMU_DCDCLNFREQCTRL = (volatile uint32_t *)(0x0FE08038); + volatile uint32_t* const diCal_EMU_DCDCLNVCTRL = (volatile uint32_t *)(0x0FE08040); + volatile uint32_t* const diCal_EMU_DCDCLPCTRL = (volatile uint32_t *)(0x0FE08048); + volatile uint32_t* const diCal_EMU_DCDCLPVCTRL = (volatile uint32_t *)(0x0FE08050); + volatile uint32_t* const diCal_EMU_DCDCTRIM0 = (volatile uint32_t *)(0x0FE08058); + volatile uint32_t* const diCal_EMU_DCDCTRIM1 = (volatile uint32_t *)(0x0FE08060); + + if (DEVINFO->DCDCLPVCTRL0 != UINT_MAX) { + val = *(diCal_EMU_DCDCLNFREQCTRL + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCLNFREQCTRL; + *reg = val; + + val = *(diCal_EMU_DCDCLNVCTRL + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCLNVCTRL; + *reg = val; + + val = *(diCal_EMU_DCDCLPCTRL + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCLPCTRL; + *reg = val; + + val = *(diCal_EMU_DCDCLPVCTRL + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCLPVCTRL; + *reg = val; + + val = *(diCal_EMU_DCDCTRIM0 + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCTRIM0; + *reg = val; + + val = *(diCal_EMU_DCDCTRIM1 + 1); + reg = (volatile uint32_t *)*diCal_EMU_DCDCTRIM1; + *reg = val; + + return true; + } + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + +#else + return true; +#endif +} + +/***************************************************************************//** + * @brief + * Set recommended and validated current optimization and timing settings + * + ******************************************************************************/ +static void dcdcValidatedConfigSet(void) +{ +/* Disable LP mode hysterysis in the state machine control */ +#define EMU_DCDCMISCCTRL_LPCMPHYSDIS (0x1UL << 1) +/* Comparator threshold on the high side */ +#define EMU_DCDCMISCCTRL_LPCMPHYSHI (0x1UL << 2) +#define EMU_DCDCSMCTRL (*(volatile uint32_t *)(EMU_BASE + 0x44)) + + uint32_t lnForceCcm; + +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) + uint32_t dcdcTiming; + SYSTEM_ChipRevision_TypeDef rev; +#endif + + /* Enable duty cycling of the bias */ + EMU->DCDCLPCTRL |= EMU_DCDCLPCTRL_LPVREFDUTYEN; + + /* Set low-noise RCO for LNFORCECCM configuration + * LNFORCECCM is default 1 for EFR32 + * LNFORCECCM is default 0 for EFM32 + */ + lnForceCcm = BUS_RegBitRead(&EMU->DCDCMISCCTRL, _EMU_DCDCMISCCTRL_LNFORCECCM_SHIFT); + if (lnForceCcm) { + /* 7MHz is recommended for LNFORCECCM = 1 */ + EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_7MHz); + } else { + /* 3MHz is recommended for LNFORCECCM = 0 */ + EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_3MHz); + } + +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) + EMU->DCDCTIMING &= ~_EMU_DCDCTIMING_DUTYSCALE_MASK; + EMU->DCDCMISCCTRL |= EMU_DCDCMISCCTRL_LPCMPHYSDIS + | EMU_DCDCMISCCTRL_LPCMPHYSHI; + + SYSTEM_ChipRevisionGet(&rev); + if ((rev.major == 1) + && (rev.minor < 3) + && (errataFixDcdcHsState == errataFixDcdcHsInit)) { + /* LPCMPWAITDIS = 1 */ + EMU_DCDCSMCTRL |= 1; + + dcdcTiming = EMU->DCDCTIMING; + dcdcTiming &= ~(_EMU_DCDCTIMING_LPINITWAIT_MASK + | _EMU_DCDCTIMING_LNWAIT_MASK + | _EMU_DCDCTIMING_BYPWAIT_MASK); + + dcdcTiming |= ((180 << _EMU_DCDCTIMING_LPINITWAIT_SHIFT) + | (12 << _EMU_DCDCTIMING_LNWAIT_SHIFT) + | (180 << _EMU_DCDCTIMING_BYPWAIT_SHIFT)); + EMU->DCDCTIMING = dcdcTiming; + + errataFixDcdcHsState = errataFixDcdcHsTrimSet; + } +#endif +} + +/***************************************************************************//** + * @brief + * Compute current limiters: + * LNCLIMILIMSEL: LN current limiter threshold + * LPCLIMILIMSEL: LP current limiter threshold + * DCDCZDETCTRL: zero detector limiter threshold + ******************************************************************************/ +static void currentLimitersUpdate(void) +{ + uint32_t lncLimSel; + uint32_t zdetLimSel; + uint32_t pFetCnt; + uint16_t maxReverseCurrent_mA; + + /* 80mA as recommended peak in Application Note AN0948. + The peak current is the average current plus 50% of the current ripple. + Hence, a 14mA average current is recommended in LP mode. Since LP PFETCNT is also + a constant, we get lpcLimImSel = 1. The following calculation is provided + for documentation only. */ + const uint32_t lpcLim = (((14 + 40) + ((14 + 40) / 2)) + / (5 * (DCDC_LP_PFET_CNT + 1))) + - 1; + const uint32_t lpcLimSel = lpcLim << _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_SHIFT; + + /* Get enabled PFETs */ + pFetCnt = (EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_PFETCNT_MASK) + >> _EMU_DCDCMISCCTRL_PFETCNT_SHIFT; + + /* Compute LN current limiter threshold from nominal user input current and + LN PFETCNT as described in the register description for + EMU_DCDCMISCCTRL_LNCLIMILIMSEL. */ + lncLimSel = (((dcdcMaxCurrent_mA + 40) + ((dcdcMaxCurrent_mA + 40) / 2)) + / (5 * (pFetCnt + 1))) + - 1; + + /* Saturate the register field value */ + lncLimSel = SL_MIN(lncLimSel, + _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK + >> _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_SHIFT); + + lncLimSel <<= _EMU_DCDCMISCCTRL_LNCLIMILIMSEL_SHIFT; + + /* Check for overflow */ + EFM_ASSERT((lncLimSel & ~_EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK) == 0x0); + EFM_ASSERT((lpcLimSel & ~_EMU_DCDCMISCCTRL_LPCLIMILIMSEL_MASK) == 0x0); + + EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_LNCLIMILIMSEL_MASK + | _EMU_DCDCMISCCTRL_LPCLIMILIMSEL_MASK)) + | (lncLimSel | lpcLimSel); + + /* Compute reverse current limit threshold for the zero detector from user input + maximum reverse current and LN PFETCNT as described in the register description + for EMU_DCDCZDETCTRL_ZDETILIMSEL. */ + if (dcdcReverseCurrentControl >= 0) { + /* If dcdcReverseCurrentControl < 0, then EMU_DCDCZDETCTRL_ZDETILIMSEL is "don't care" */ + maxReverseCurrent_mA = (uint16_t)dcdcReverseCurrentControl; + + zdetLimSel = ( ((maxReverseCurrent_mA + 40) + ((maxReverseCurrent_mA + 40) / 2)) + / ((2 * (pFetCnt + 1)) + ((pFetCnt + 1) / 2)) ); + /* Saturate the register field value */ + zdetLimSel = SL_MIN(zdetLimSel, + _EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK + >> _EMU_DCDCZDETCTRL_ZDETILIMSEL_SHIFT); + + zdetLimSel <<= _EMU_DCDCZDETCTRL_ZDETILIMSEL_SHIFT; + + /* Check for overflow */ + EFM_ASSERT((zdetLimSel & ~_EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK) == 0x0); + + EMU->DCDCZDETCTRL = (EMU->DCDCZDETCTRL & ~_EMU_DCDCZDETCTRL_ZDETILIMSEL_MASK) + | zdetLimSel; + } +} + +/***************************************************************************//** + * @brief + * Set static variables that hold the user set maximum peak current + * and reverse current. Update limiters. + * + * @param[in] maxCurrent_mA + * Set the maximum peak current that the DCDC can draw from the power source. + * @param[in] reverseCurrentControl + * Reverse current control as defined by + * @ref EMU_DcdcLnReverseCurrentControl_TypeDef. Positive values have unit mA. + ******************************************************************************/ +static void userCurrentLimitsSet(uint32_t maxCurrent_mA, + EMU_DcdcLnReverseCurrentControl_TypeDef reverseCurrentControl) +{ + dcdcMaxCurrent_mA = maxCurrent_mA; + dcdcReverseCurrentControl = reverseCurrentControl; +} + +/***************************************************************************//** + * @brief + * Set DCDC low noise compensator control register + * + * @param[in] comp + * Low-noise mode compensator trim setpoint + ******************************************************************************/ +static void compCtrlSet(EMU_DcdcLnCompCtrl_TypeDef comp) +{ + switch (comp) { + case emuDcdcLnCompCtrl_1u0F: + EMU->DCDCLNCOMPCTRL = 0x57204077UL; + break; + + case emuDcdcLnCompCtrl_4u7F: + EMU->DCDCLNCOMPCTRL = 0xB7102137UL; + break; + + default: + EFM_ASSERT(false); + break; + } +} + +/***************************************************************************//** + * @brief + * Load EMU_DCDCLPCTRL_LPCMPHYSSEL depending on LP bias, LP feedback + * attenuation and DEVINFOREV. + * + * @param[in] lpAttenuation + * LP feedback attenuation. + * @param[in] lpCmpBias + * lpCmpBias selection. + * @param[in] trimMode + * DCDC trim mode. + ******************************************************************************/ +static bool lpCmpHystCalibrationLoad(bool lpAttenuation, + uint8_t lpCmpBias, + dcdcTrimMode_TypeDef trimMode) +{ + uint32_t lpcmpHystSel; + + /* Get calib data revision */ +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) + uint8_t devinfoRev = SYSTEM_GetDevinfoRev(); + + /* Load LPATT indexed calibration data */ + if (devinfoRev < 4) +#else + /* Format change not present of newer families. */ + if (false) +#endif + { + lpcmpHystSel = DEVINFO->DCDCLPCMPHYSSEL0; + + if (lpAttenuation) { + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT1_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT1_SHIFT; + } else { + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT0_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL0_LPCMPHYSSELLPATT0_SHIFT; + } + } else { + /* devinfoRev >= 4: load LPCMPBIAS indexed calibration data */ + lpcmpHystSel = DEVINFO->DCDCLPCMPHYSSEL1; + switch (lpCmpBias) { + case 0: + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS0_SHIFT; + break; + + case 1: + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS1_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS1_SHIFT; + break; + + case 2: + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS2_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS2_SHIFT; + break; + + case 3: + lpcmpHystSel = (lpcmpHystSel & _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS3_MASK) + >> _DEVINFO_DCDCLPCMPHYSSEL1_LPCMPHYSSELLPCMPBIAS3_SHIFT; + break; + + default: + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + } + + /* Set trims */ + if (trimMode == dcdcTrimMode_EM234H_LP) { + /* Make sure the sel value is within the field range. */ + lpcmpHystSel <<= _GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_SHIFT; + if (lpcmpHystSel & ~_GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + EMU->DCDCLPCTRL = (EMU->DCDCLPCTRL & ~_GENERIC_DCDCLPCTRL_LPCMPHYSSELEM234H_MASK) | lpcmpHystSel; + } + +#if defined(_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK) + if (trimMode == dcdcTrimMode_EM01_LP) { + /* Make sure the sel value is within the field range. */ + lpcmpHystSel <<= _EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_SHIFT; + if (lpcmpHystSel & ~_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + EMU->DCDCLPEM01CFG = (EMU->DCDCLPEM01CFG & ~_EMU_DCDCLPEM01CFG_LPCMPHYSSELEM01_MASK) | lpcmpHystSel; + } +#endif + + return true; +} + +/***************************************************************************//** + * @brief + * Load LPVREF low and high from DEVINFO. + * + * @param[out] vrefL + * LPVREF low from DEVINFO. + * @param[out] vrefH + * LPVREF high from DEVINFO. + * @param[in] lpAttenuation + * LP feedback attenuation. + * @param[in] lpcmpBias + * lpcmpBias to lookup in DEVINFO. + ******************************************************************************/ +static void lpGetDevinfoVrefLowHigh(uint32_t *vrefL, + uint32_t *vrefH, + bool lpAttenuation, + uint8_t lpcmpBias) +{ + uint32_t vrefLow = 0; + uint32_t vrefHigh = 0; + + /* Find VREF high and low in DEVINFO indexed by LPCMPBIAS (lpcmpBias) + and LPATT (lpAttenuation) */ + uint32_t switchVal = (lpcmpBias << 8) | (lpAttenuation ? 1 : 0); + switch (switchVal) { + case ((0 << 8) | 1): + vrefLow = DEVINFO->DCDCLPVCTRL2; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS0_MASK) + >> _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS0_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS0_MASK) + >> _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS0_SHIFT; + break; + + case ((1 << 8) | 1): + vrefLow = DEVINFO->DCDCLPVCTRL2; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS1_MASK) + >> _DEVINFO_DCDCLPVCTRL2_3V0LPATT1LPCMPBIAS1_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS1_MASK) + >> _DEVINFO_DCDCLPVCTRL2_1V8LPATT1LPCMPBIAS1_SHIFT; + break; + + case ((2 << 8) | 1): + vrefLow = DEVINFO->DCDCLPVCTRL3; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS2_MASK) + >> _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS2_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS2_MASK) + >> _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS2_SHIFT; + break; + + case ((3 << 8) | 1): + vrefLow = DEVINFO->DCDCLPVCTRL3; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS3_MASK) + >> _DEVINFO_DCDCLPVCTRL3_3V0LPATT1LPCMPBIAS3_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS3_MASK) + >> _DEVINFO_DCDCLPVCTRL3_1V8LPATT1LPCMPBIAS3_SHIFT; + break; + + case ((0 << 8) | 0): + vrefLow = DEVINFO->DCDCLPVCTRL0; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS0_MASK) + >> _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS0_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS0_MASK) + >> _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS0_SHIFT; + break; + + case ((1 << 8) | 0): + vrefLow = DEVINFO->DCDCLPVCTRL0; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS1_MASK) + >> _DEVINFO_DCDCLPVCTRL0_1V8LPATT0LPCMPBIAS1_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS1_MASK) + >> _DEVINFO_DCDCLPVCTRL0_1V2LPATT0LPCMPBIAS1_SHIFT; + break; + + case ((2 << 8) | 0): + vrefLow = DEVINFO->DCDCLPVCTRL1; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS2_MASK) + >> _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS2_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS2_MASK) + >> _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS2_SHIFT; + break; + + case ((3 << 8) | 0): + vrefLow = DEVINFO->DCDCLPVCTRL1; + vrefHigh = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS3_MASK) + >> _DEVINFO_DCDCLPVCTRL1_1V8LPATT0LPCMPBIAS3_SHIFT; + vrefLow = (vrefLow & _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS3_MASK) + >> _DEVINFO_DCDCLPVCTRL1_1V2LPATT0LPCMPBIAS3_SHIFT; + break; + + default: + EFM_ASSERT(false); + break; + } + *vrefL = vrefLow; + *vrefH = vrefHigh; +} + +/** @endcond */ + +/***************************************************************************//** + * @brief + * Set DCDC regulator operating mode + * + * @param[in] dcdcMode + * DCDC mode + ******************************************************************************/ +void EMU_DCDCModeSet(EMU_DcdcMode_TypeDef dcdcMode) +{ + uint32_t currentDcdcMode; + + /* Wait for any previous write sync to complete and read DCDC mode. */ + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + currentDcdcMode = (EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK); + + /* Enable bypass current limiter when not in bypass mode to prevent + excessive current between VREGVDD and DVDD supplies when reentering bypass mode. */ + if (currentDcdcMode != EMU_DCDCCTRL_DCDCMODE_BYPASS) { + BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, 1); + } + + if ((EMU_DcdcMode_TypeDef)currentDcdcMode == dcdcMode) { + /* Mode already set. If already in bypass, make sure bypass current limiter + is disabled. */ + if (dcdcMode == emuDcdcMode_Bypass) { + BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, 0); + } + return; + } + +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) + + /* Fix for errata DCDC_E203 */ + if ((currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_BYPASS) + && (dcdcMode == emuDcdcMode_LowNoise)) { + errataFixDcdcHsState = errataFixDcdcHsBypassLn; + } + +#else + + /* Fix for errata DCDC_E204 */ + if (((currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_OFF) || (currentDcdcMode == EMU_DCDCCTRL_DCDCMODE_BYPASS)) + && ((dcdcMode == emuDcdcMode_LowPower) || (dcdcMode == emuDcdcMode_LowNoise))) { + /* Always start in LOWNOISE mode and then switch to LOWPOWER mode once LOWNOISE startup is complete. */ + EMU_IntClear(EMU_IFC_DCDCLNRUNNING); + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + EMU->DCDCCTRL = (EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) | EMU_DCDCCTRL_DCDCMODE_LOWNOISE; + while (!(EMU_IntGet() & EMU_IF_DCDCLNRUNNING)) ; + } +#endif + + /* Set user requested mode. */ + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + EMU->DCDCCTRL = (EMU->DCDCCTRL & ~_EMU_DCDCCTRL_DCDCMODE_MASK) | dcdcMode; + + /* Disable bypass current limiter after bypass mode is entered. + Enable the limiter if any other mode is entered. */ + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + BUS_RegBitWrite(&EMU->DCDCCLIMCTRL, _EMU_DCDCCLIMCTRL_BYPLIMEN_SHIFT, dcdcMode == emuDcdcMode_Bypass ? 0 : 1); +} + +/***************************************************************************//** + * @brief + * Set DCDC LN regulator conduction mode + * + * @param[in] conductionMode + * DCDC LN conduction mode. + * @param[in] rcoDefaultSet + * The default DCDC RCO band for the conductionMode will be used if true. + * Otherwise the current RCO configuration is used. + ******************************************************************************/ +void EMU_DCDCConductionModeSet(EMU_DcdcConductionMode_TypeDef conductionMode, bool rcoDefaultSet) +{ + EMU_DcdcMode_TypeDef currentDcdcMode + = (EMU_DcdcMode_TypeDef)(EMU->DCDCCTRL & _EMU_DCDCCTRL_DCDCMODE_MASK); + EMU_DcdcLnRcoBand_TypeDef rcoBand + = (EMU_DcdcLnRcoBand_TypeDef)((EMU->DCDCLNFREQCTRL & _EMU_DCDCLNFREQCTRL_RCOBAND_MASK) + >> _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT); + + /* Set bypass mode and wait for bypass mode to settle before + EMU_DCDCMISCCTRL_LNFORCECCM is set. Restore current DCDC mode. */ + EMU_IntClear(EMU_IFC_DCDCINBYPASS); + EMU_DCDCModeSet(emuDcdcMode_Bypass); + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + while (!(EMU_IntGet() & EMU_IF_DCDCINBYPASS)) ; + if (conductionMode == emuDcdcConductionMode_DiscontinuousLN) { + EMU->DCDCMISCCTRL &= ~EMU_DCDCMISCCTRL_LNFORCECCM; + if (rcoDefaultSet) { + EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_3MHz); + } else { + /* emuDcdcConductionMode_DiscontinuousLN supports up to 4MHz LN RCO. */ + EFM_ASSERT(rcoBand <= emuDcdcLnRcoBand_4MHz); + } + } else { + EMU->DCDCMISCCTRL |= EMU_DCDCMISCCTRL_LNFORCECCM; + if (rcoDefaultSet) { + EMU_DCDCLnRcoBandSet(emuDcdcLnRcoBand_7MHz); + } + } + EMU_DCDCModeSet(currentDcdcMode); + /* Update slice configuration as it depends on conduction mode and RCO band. */ + EMU_DCDCOptimizeSlice(dcdcEm01LoadCurrent_mA); +} + +/***************************************************************************//** + * @brief + * Configure DCDC regulator + * + * @note + * If the power circuit is configured for NODCDC as described in Section + * 11.3.4.3 of the Reference Manual, do not call this function. Instead call + * EMU_DCDCPowerOff(). + * + * @param[in] dcdcInit + * DCDC initialization structure + * + * @return + * True if initialization parameters are valid + ******************************************************************************/ +bool EMU_DCDCInit(const EMU_DCDCInit_TypeDef *dcdcInit) +{ + uint32_t lpCmpBiasSelEM234H; + +#if defined(_EMU_PWRCFG_MASK) + /* Set external power configuration. This enables writing to the other + DCDC registers. */ + EMU->PWRCFG = EMU_PWRCFG_PWRCFG_DCDCTODVDD; + + /* EMU->PWRCFG is write-once and POR reset only. Check that + we could set the desired power configuration. */ + if ((EMU->PWRCFG & _EMU_PWRCFG_PWRCFG_MASK) != EMU_PWRCFG_PWRCFG_DCDCTODVDD) { + /* If this assert triggers unexpectedly, please power cycle the + kit to reset the power configuration. */ + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } +#endif + + /* Load DCDC calibration data from the DI page */ + dcdcConstCalibrationLoad(); + + /* Check current parameters */ + EFM_ASSERT(dcdcInit->maxCurrent_mA <= 200); + EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= dcdcInit->maxCurrent_mA); + EFM_ASSERT(dcdcInit->reverseCurrentControl <= 200); + + if (dcdcInit->dcdcMode == emuDcdcMode_LowNoise) { + /* DCDC low-noise supports max 200mA */ + EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= 200); + } +#if (_SILICON_LABS_GECKO_INTERNAL_SDID != 80) + else if (dcdcInit->dcdcMode == emuDcdcMode_LowPower) { + /* Up to 10mA is supported for EM01-LP mode. */ + EFM_ASSERT(dcdcInit->em01LoadCurrent_mA <= 10); + } +#endif + + /* EM2/3/4 current above 10mA is not supported */ + EFM_ASSERT(dcdcInit->em234LoadCurrent_uA <= 10000); + + if (dcdcInit->em234LoadCurrent_uA < 75) { + lpCmpBiasSelEM234H = 0; + } else if (dcdcInit->em234LoadCurrent_uA < 500) { + lpCmpBiasSelEM234H = 1 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT; + } else if (dcdcInit->em234LoadCurrent_uA < 2500) { + lpCmpBiasSelEM234H = 2 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT; + } else { + lpCmpBiasSelEM234H = 3 << _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT; + } + + /* ==== THESE NEXT STEPS ARE STRONGLY ORDER DEPENDENT ==== */ + + /* Set DCDC low-power mode comparator bias selection */ + + /* 1. Set DCDC low-power mode comparator bias selection and forced CCM + => Updates DCDCMISCCTRL_LNFORCECCM */ + EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK + | _EMU_DCDCMISCCTRL_LNFORCECCM_MASK)) + | ((uint32_t)lpCmpBiasSelEM234H + | (dcdcInit->reverseCurrentControl >= 0 + ? EMU_DCDCMISCCTRL_LNFORCECCM : 0)); +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + /* Only 10mA EM01-LP current is supported */ + EMU->DCDCLPEM01CFG = (EMU->DCDCLPEM01CFG & ~_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + | EMU_DCDCLPEM01CFG_LPCMPBIASEM01_BIAS3; +#endif + + /* 2. Set recommended and validated current optimization settings + <= Depends on LNFORCECCM + => Updates DCDCLNFREQCTRL_RCOBAND */ + dcdcValidatedConfigSet(); + + /* 3. Updated static currents and limits user data. + Limiters are updated in EMU_DCDCOptimizeSlice() */ + userCurrentLimitsSet(dcdcInit->maxCurrent_mA, + dcdcInit->reverseCurrentControl); + dcdcEm01LoadCurrent_mA = dcdcInit->em01LoadCurrent_mA; + + /* 4. Optimize LN slice based on given user input load current + <= Depends on DCDCMISCCTRL_LNFORCECCM and DCDCLNFREQCTRL_RCOBAND + <= Depends on dcdcInit->maxCurrent_mA and dcdcInit->reverseCurrentControl + => Updates DCDCMISCCTRL_P/NFETCNT + => Updates DCDCMISCCTRL_LNCLIMILIMSEL and DCDCMISCCTRL_LPCLIMILIMSEL + => Updates DCDCZDETCTRL_ZDETILIMSEL */ + EMU_DCDCOptimizeSlice(dcdcInit->em01LoadCurrent_mA); + + /* ======================================================= */ + + /* Set DCDC low noise mode compensator control register. */ + compCtrlSet(dcdcInit->dcdcLnCompCtrl); + + /* Set DCDC output voltage */ + if (!EMU_DCDCOutputVoltageSet(dcdcInit->mVout, true, true)) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + +#if (_SILICON_LABS_GECKO_INTERNAL_SDID == 80) + /* Select analog peripheral power supply. This must be done before + DCDC mode is set for all EFM32xG1 and EFR32xG1 devices. */ + BUS_RegBitWrite(&EMU->PWRCTRL, + _EMU_PWRCTRL_ANASW_SHIFT, + dcdcInit->anaPeripheralPower ? 1 : 0); +#endif + +#if defined(_EMU_PWRCTRL_REGPWRSEL_MASK) + /* Select DVDD as input to the digital regulator. The switch to DVDD will take + effect once the DCDC output is stable. */ + EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD; +#endif + + /* Set EM0 DCDC operating mode. Output voltage set in + EMU_DCDCOutputVoltageSet() above takes effect if mode + is changed from bypass/off mode. */ + EMU_DCDCModeSet(dcdcInit->dcdcMode); + +#if (_SILICON_LABS_GECKO_INTERNAL_SDID != 80) + /* Select analog peripheral power supply. This must be done after + DCDC mode is set for all devices other than EFM32xG1 and EFR32xG1. */ + BUS_RegBitWrite(&EMU->PWRCTRL, + _EMU_PWRCTRL_ANASW_SHIFT, + dcdcInit->anaPeripheralPower ? 1 : 0); +#endif + + return true; +} + +/***************************************************************************//** + * @brief + * Set DCDC output voltage + * + * @param[in] mV + * Target DCDC output voltage in mV + * + * @return + * True if the mV parameter is valid + ******************************************************************************/ +bool EMU_DCDCOutputVoltageSet(uint32_t mV, + bool setLpVoltage, + bool setLnVoltage) +{ +#if defined(_DEVINFO_DCDCLNVCTRL0_3V0LNATT1_MASK) + +#define DCDC_TRIM_MODES ((uint8_t)dcdcTrimMode_LN + 1) + bool validOutVoltage; + bool attenuationSet; + uint32_t mVlow = 0; + uint32_t mVhigh = 0; + uint32_t mVdiff; + uint32_t vrefVal[DCDC_TRIM_MODES] = { 0 }; + uint32_t vrefLow[DCDC_TRIM_MODES] = { 0 }; + uint32_t vrefHigh[DCDC_TRIM_MODES] = { 0 }; + uint8_t lpcmpBias[DCDC_TRIM_MODES] = { 0 }; + + /* Check that the set voltage is within valid range. + Voltages are obtained from the datasheet. */ + validOutVoltage = ((mV >= PWRCFG_DCDCTODVDD_VMIN) + && (mV <= PWRCFG_DCDCTODVDD_VMAX)); + + if (!validOutVoltage) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + + /* Set attenuation to use and low/high range. */ + attenuationSet = (mV > 1800); + if (attenuationSet) { + mVlow = 1800; + mVhigh = 3000; + mVdiff = mVhigh - mVlow; + } else { + mVlow = 1200; + mVhigh = 1800; + mVdiff = mVhigh - mVlow; + } + + /* Get 2-point calib data from DEVINFO */ + + /* LN mode */ + if (attenuationSet) { + vrefLow[dcdcTrimMode_LN] = DEVINFO->DCDCLNVCTRL0; + vrefHigh[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_MASK) + >> _DEVINFO_DCDCLNVCTRL0_3V0LNATT1_SHIFT; + vrefLow[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_MASK) + >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT1_SHIFT; + } else { + vrefLow[dcdcTrimMode_LN] = DEVINFO->DCDCLNVCTRL0; + vrefHigh[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_MASK) + >> _DEVINFO_DCDCLNVCTRL0_1V8LNATT0_SHIFT; + vrefLow[dcdcTrimMode_LN] = (vrefLow[dcdcTrimMode_LN] & _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_MASK) + >> _DEVINFO_DCDCLNVCTRL0_1V2LNATT0_SHIFT; + } + + /* LP EM234H mode */ + lpcmpBias[dcdcTrimMode_EM234H_LP] = (EMU->DCDCMISCCTRL & _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_MASK) + >> _GENERIC_DCDCMISCCTRL_LPCMPBIASEM234H_SHIFT; + lpGetDevinfoVrefLowHigh(&vrefLow[dcdcTrimMode_EM234H_LP], + &vrefHigh[dcdcTrimMode_EM234H_LP], + attenuationSet, + lpcmpBias[dcdcTrimMode_EM234H_LP]); + +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + /* LP EM01 mode */ + lpcmpBias[dcdcTrimMode_EM01_LP] = (EMU->DCDCLPEM01CFG & _EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + >> _EMU_DCDCLPEM01CFG_LPCMPBIASEM01_SHIFT; + lpGetDevinfoVrefLowHigh(&vrefLow[dcdcTrimMode_EM01_LP], + &vrefHigh[dcdcTrimMode_EM01_LP], + attenuationSet, + lpcmpBias[dcdcTrimMode_EM01_LP]); +#endif + + /* Calculate output voltage trims */ + vrefVal[dcdcTrimMode_LN] = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_LN] - vrefLow[dcdcTrimMode_LN])) + / mVdiff; + vrefVal[dcdcTrimMode_LN] += vrefLow[dcdcTrimMode_LN]; + + vrefVal[dcdcTrimMode_EM234H_LP] = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_EM234H_LP] - vrefLow[dcdcTrimMode_EM234H_LP])) + / mVdiff; + vrefVal[dcdcTrimMode_EM234H_LP] += vrefLow[dcdcTrimMode_EM234H_LP]; + +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + vrefVal[dcdcTrimMode_EM01_LP] = ((mV - mVlow) * (vrefHigh[dcdcTrimMode_EM01_LP] - vrefLow[dcdcTrimMode_EM01_LP])) + / mVdiff; + vrefVal[dcdcTrimMode_EM01_LP] += vrefLow[dcdcTrimMode_EM01_LP]; +#endif + + /* Range checks */ + if ((vrefVal[dcdcTrimMode_LN] > vrefHigh[dcdcTrimMode_LN]) + || (vrefVal[dcdcTrimMode_LN] < vrefLow[dcdcTrimMode_LN]) +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + || (vrefVal[dcdcTrimMode_EM01_LP] > vrefHigh[dcdcTrimMode_EM01_LP]) + || (vrefVal[dcdcTrimMode_EM01_LP] < vrefLow[dcdcTrimMode_EM01_LP]) +#endif + || (vrefVal[dcdcTrimMode_EM234H_LP] > vrefHigh[dcdcTrimMode_EM234H_LP]) + || (vrefVal[dcdcTrimMode_EM234H_LP] < vrefLow[dcdcTrimMode_EM234H_LP])) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + + /* Update output voltage tuning for LN and LP modes. */ + if (setLnVoltage) { + EMU->DCDCLNVCTRL = (EMU->DCDCLNVCTRL & ~(_EMU_DCDCLNVCTRL_LNVREF_MASK | _EMU_DCDCLNVCTRL_LNATT_MASK)) + | (vrefVal[dcdcTrimMode_LN] << _EMU_DCDCLNVCTRL_LNVREF_SHIFT) + | (attenuationSet ? EMU_DCDCLNVCTRL_LNATT : 0); + } + + if (setLpVoltage) { + /* Load LP EM234H comparator hysteresis calibration */ + if (!(lpCmpHystCalibrationLoad(attenuationSet, lpcmpBias[dcdcTrimMode_EM234H_LP], dcdcTrimMode_EM234H_LP))) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + +#if defined(_EMU_DCDCLPEM01CFG_LPCMPBIASEM01_MASK) + /* Load LP EM234H comparator hysteresis calibration */ + if (!(lpCmpHystCalibrationLoad(attenuationSet, lpcmpBias[dcdcTrimMode_EM01_LP], dcdcTrimMode_EM01_LP))) { + EFM_ASSERT(false); + /* Return when assertions are disabled */ + return false; + } + + /* LP VREF is that max of trims for EM01 and EM234H. */ + vrefVal[dcdcTrimMode_EM234H_LP] = SL_MAX(vrefVal[dcdcTrimMode_EM234H_LP], vrefVal[dcdcTrimMode_EM01_LP]); +#endif + + /* Don't exceed max available code as specified in the reference manual for EMU_DCDCLPVCTRL. */ + vrefVal[dcdcTrimMode_EM234H_LP] = SL_MIN(vrefVal[dcdcTrimMode_EM234H_LP], 0xE7U); + EMU->DCDCLPVCTRL = (EMU->DCDCLPVCTRL & ~(_EMU_DCDCLPVCTRL_LPVREF_MASK | _EMU_DCDCLPVCTRL_LPATT_MASK)) + | (vrefVal[dcdcTrimMode_EM234H_LP] << _EMU_DCDCLPVCTRL_LPVREF_SHIFT) + | (attenuationSet ? EMU_DCDCLPVCTRL_LPATT : 0); + } +#endif + return true; +} + +/***************************************************************************//** + * @brief + * Optimize DCDC slice count based on the estimated average load current + * in EM0 + * + * @param[in] em0LoadCurrent_mA + * Estimated average EM0 load current in mA. + ******************************************************************************/ +void EMU_DCDCOptimizeSlice(uint32_t em0LoadCurrent_mA) +{ + uint32_t sliceCount = 0; + uint32_t rcoBand = (EMU->DCDCLNFREQCTRL & _EMU_DCDCLNFREQCTRL_RCOBAND_MASK) + >> _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT; + + /* Set recommended slice count */ + if ((EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK) && (rcoBand >= emuDcdcLnRcoBand_5MHz)) { + if (em0LoadCurrent_mA < 20) { + sliceCount = 4; + } else if ((em0LoadCurrent_mA >= 20) && (em0LoadCurrent_mA < 40)) { + sliceCount = 8; + } else { + sliceCount = 16; + } + } else if ((!(EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK)) && (rcoBand <= emuDcdcLnRcoBand_4MHz)) { + if (em0LoadCurrent_mA < 10) { + sliceCount = 4; + } else if ((em0LoadCurrent_mA >= 10) && (em0LoadCurrent_mA < 20)) { + sliceCount = 8; + } else { + sliceCount = 16; + } + } else if ((EMU->DCDCMISCCTRL & _EMU_DCDCMISCCTRL_LNFORCECCM_MASK) && (rcoBand <= emuDcdcLnRcoBand_4MHz)) { + if (em0LoadCurrent_mA < 40) { + sliceCount = 8; + } else { + sliceCount = 16; + } + } else { + /* This configuration is not recommended. EMU_DCDCInit() applies a recommended + configuration. */ + EFM_ASSERT(false); + } + + /* The selected slices are PSLICESEL + 1 */ + sliceCount--; + + /* Apply slice count to both N and P slice */ + sliceCount = (sliceCount << _EMU_DCDCMISCCTRL_PFETCNT_SHIFT + | sliceCount << _EMU_DCDCMISCCTRL_NFETCNT_SHIFT); + EMU->DCDCMISCCTRL = (EMU->DCDCMISCCTRL & ~(_EMU_DCDCMISCCTRL_PFETCNT_MASK + | _EMU_DCDCMISCCTRL_NFETCNT_MASK)) + | sliceCount; + + /* Update current limiters */ + currentLimitersUpdate(); +} + +/***************************************************************************//** + * @brief + * Set DCDC Low-noise RCO band. + * + * @param[in] band + * RCO band to set. + ******************************************************************************/ +void EMU_DCDCLnRcoBandSet(EMU_DcdcLnRcoBand_TypeDef band) +{ + uint32_t forcedCcm; + forcedCcm = BUS_RegBitRead(&EMU->DCDCMISCCTRL, _EMU_DCDCMISCCTRL_LNFORCECCM_SHIFT); + + /* DCM mode supports up to 4MHz LN RCO. */ + EFM_ASSERT((!forcedCcm && band <= emuDcdcLnRcoBand_4MHz) || forcedCcm); + + EMU->DCDCLNFREQCTRL = (EMU->DCDCLNFREQCTRL & ~_EMU_DCDCLNFREQCTRL_RCOBAND_MASK) + | (band << _EMU_DCDCLNFREQCTRL_RCOBAND_SHIFT); + + /* Update slice configuration as this depends on the RCO band. */ + EMU_DCDCOptimizeSlice(dcdcEm01LoadCurrent_mA); +} + +/***************************************************************************//** + * @brief + * Power off the DCDC regulator. + * + * @details + * This function powers off the DCDC controller. This function should only be + * used if the external power circuit is wired for no DCDC. If the external power + * circuit is wired for DCDC usage, then use EMU_DCDCInit() and set the + * DCDC in bypass mode to disable DCDC. + * + * @return + * Return false if the DCDC could not be disabled. + ******************************************************************************/ +bool EMU_DCDCPowerOff(void) +{ + bool dcdcModeSet; + +#if defined(_EMU_PWRCFG_MASK) + /* Set DCDCTODVDD only to enable write access to EMU->DCDCCTRL */ + EMU->PWRCFG = EMU_PWRCFG_PWRCFG_DCDCTODVDD; +#endif + + /* Select DVDD as input to the digital regulator */ +#if defined(EMU_PWRCTRL_IMMEDIATEPWRSWITCH) + EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD | EMU_PWRCTRL_IMMEDIATEPWRSWITCH; +#elif defined(EMU_PWRCTRL_REGPWRSEL_DVDD) + EMU->PWRCTRL |= EMU_PWRCTRL_REGPWRSEL_DVDD; +#endif + + /* Set DCDC to OFF and disable LP in EM2/3/4. Verify that the required + mode could be set. */ + while (EMU->DCDCSYNC & EMU_DCDCSYNC_DCDCCTRLBUSY) ; + EMU->DCDCCTRL = EMU_DCDCCTRL_DCDCMODE_OFF; + + dcdcModeSet = (EMU->DCDCCTRL == EMU_DCDCCTRL_DCDCMODE_OFF); + EFM_ASSERT(dcdcModeSet); + + return dcdcModeSet; +} +#endif + +#if defined(EMU_STATUS_VMONRDY) +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +/***************************************************************************//** + * @brief + * Get calibrated threshold value. + * + * @details + * All VMON channels have two calibration fields in the DI page that + * describes the threshold at 1.86V and 2.98V. This function will convert + * the uncalibrated input voltage threshold in millivolts into a calibrated + * threshold. + * + * @param[in] channel + * VMON channel + * + * @param[in] threshold + * Desired threshold in millivolts. + * + * @return + * Calibrated threshold value to use. First digit of return value is placed + * in the "fine" register fields while the next digits are placed in the + * "coarse" register fields. + ******************************************************************************/ +static uint32_t vmonCalibratedThreshold(EMU_VmonChannel_TypeDef channel, + int threshold) +{ + uint32_t tLow; + uint32_t tHigh; + uint32_t calReg; + + /* Get calibration values for 1.86V and 2.98V */ + switch (channel) { + case emuVmonChannel_AVDD: + calReg = DEVINFO->VMONCAL0; + tLow = (10 * ((calReg & _DEVINFO_VMONCAL0_AVDD1V86THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL0_AVDD1V86THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL0_AVDD1V86THRESFINE_MASK) + >> _DEVINFO_VMONCAL0_AVDD1V86THRESFINE_SHIFT); + tHigh = (10 * ((calReg & _DEVINFO_VMONCAL0_AVDD2V98THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL0_AVDD2V98THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL0_AVDD2V98THRESFINE_MASK) + >> _DEVINFO_VMONCAL0_AVDD2V98THRESFINE_SHIFT); + break; + case emuVmonChannel_ALTAVDD: + calReg = DEVINFO->VMONCAL0; + tLow = (10 * ((calReg & _DEVINFO_VMONCAL0_ALTAVDD1V86THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL0_ALTAVDD1V86THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL0_ALTAVDD1V86THRESFINE_MASK) + >> _DEVINFO_VMONCAL0_ALTAVDD1V86THRESFINE_SHIFT); + tHigh = (10 * ((calReg & _DEVINFO_VMONCAL0_ALTAVDD2V98THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL0_ALTAVDD2V98THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL0_ALTAVDD2V98THRESFINE_MASK) + >> _DEVINFO_VMONCAL0_ALTAVDD2V98THRESFINE_SHIFT); + break; + case emuVmonChannel_DVDD: + calReg = DEVINFO->VMONCAL1; + tLow = (10 * ((calReg & _DEVINFO_VMONCAL1_DVDD1V86THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL1_DVDD1V86THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL1_DVDD1V86THRESFINE_MASK) + >> _DEVINFO_VMONCAL1_DVDD1V86THRESFINE_SHIFT); + tHigh = (10 * ((calReg & _DEVINFO_VMONCAL1_DVDD2V98THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL1_DVDD2V98THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL1_DVDD2V98THRESFINE_MASK) + >> _DEVINFO_VMONCAL1_DVDD2V98THRESFINE_SHIFT); + break; + case emuVmonChannel_IOVDD0: + calReg = DEVINFO->VMONCAL1; + tLow = (10 * ((calReg & _DEVINFO_VMONCAL1_IO01V86THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL1_IO01V86THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL1_IO01V86THRESFINE_MASK) + >> _DEVINFO_VMONCAL1_IO01V86THRESFINE_SHIFT); + tHigh = (10 * ((calReg & _DEVINFO_VMONCAL1_IO02V98THRESCOARSE_MASK) + >> _DEVINFO_VMONCAL1_IO02V98THRESCOARSE_SHIFT)) + + ((calReg & _DEVINFO_VMONCAL1_IO02V98THRESFINE_MASK) + >> _DEVINFO_VMONCAL1_IO02V98THRESFINE_SHIFT); + break; + default: + EFM_ASSERT(false); + return threshold; + } + + if (tHigh <= tLow) { + /* Uncalibrated device guard */ + return threshold; + } + + /* Calculate threshold. + * + * Note that volt is used in the reference manual, however we are interested + * in millivolt results. We also increase the precision of Va and Vb in the + * calculation instead of using floating points. + */ + uint32_t va = (1120 * 100) / (tHigh - tLow); + uint32_t vb = (1860 * 100) - (va * tLow); + /* Round threshold to nearest integer value. */ + return ((threshold * 100) - vb + (va / 2)) / va; +} + +/** @endcond */ + +/***************************************************************************//** + * @brief + * Initialize VMON channel. + * + * @details + * Initialize a VMON channel without hysteresis. If the channel supports + * separate rise and fall triggers, both thresholds will be set to the same + * value. The threshold will be converted to a register field value based + * on calibration values from the DI page. + * + * @param[in] vmonInit + * VMON initialization struct + ******************************************************************************/ +void EMU_VmonInit(const EMU_VmonInit_TypeDef *vmonInit) +{ + uint32_t thresholdCoarse, thresholdFine; + uint32_t threshold; + + EFM_ASSERT((vmonInit->threshold >= 1620) && (vmonInit->threshold <= 3400)); + + threshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->threshold); + thresholdFine = threshold % 10; + thresholdCoarse = threshold / 10; + + /* Saturate threshold to max values. */ + if (thresholdCoarse > 0xF) { + thresholdCoarse = 0xF; + thresholdFine = 9; + } + + switch (vmonInit->channel) { + case emuVmonChannel_AVDD: + EMU->VMONAVDDCTRL = (thresholdCoarse << _EMU_VMONAVDDCTRL_RISETHRESCOARSE_SHIFT) + | (thresholdFine << _EMU_VMONAVDDCTRL_RISETHRESFINE_SHIFT) + | (thresholdCoarse << _EMU_VMONAVDDCTRL_FALLTHRESCOARSE_SHIFT) + | (thresholdFine << _EMU_VMONAVDDCTRL_FALLTHRESFINE_SHIFT) + | (vmonInit->riseWakeup ? EMU_VMONAVDDCTRL_RISEWU : 0) + | (vmonInit->fallWakeup ? EMU_VMONAVDDCTRL_FALLWU : 0) + | (vmonInit->enable ? EMU_VMONAVDDCTRL_EN : 0); + break; + case emuVmonChannel_ALTAVDD: + EMU->VMONALTAVDDCTRL = (thresholdCoarse << _EMU_VMONALTAVDDCTRL_THRESCOARSE_SHIFT) + | (thresholdFine << _EMU_VMONALTAVDDCTRL_THRESFINE_SHIFT) + | (vmonInit->riseWakeup ? EMU_VMONALTAVDDCTRL_RISEWU : 0) + | (vmonInit->fallWakeup ? EMU_VMONALTAVDDCTRL_FALLWU : 0) + | (vmonInit->enable ? EMU_VMONALTAVDDCTRL_EN : 0); + break; + case emuVmonChannel_DVDD: + EMU->VMONDVDDCTRL = (thresholdCoarse << _EMU_VMONDVDDCTRL_THRESCOARSE_SHIFT) + | (thresholdFine << _EMU_VMONDVDDCTRL_THRESFINE_SHIFT) + | (vmonInit->riseWakeup ? EMU_VMONDVDDCTRL_RISEWU : 0) + | (vmonInit->fallWakeup ? EMU_VMONDVDDCTRL_FALLWU : 0) + | (vmonInit->enable ? EMU_VMONDVDDCTRL_EN : 0); + break; + case emuVmonChannel_IOVDD0: + EMU->VMONIO0CTRL = (thresholdCoarse << _EMU_VMONIO0CTRL_THRESCOARSE_SHIFT) + | (thresholdFine << _EMU_VMONIO0CTRL_THRESFINE_SHIFT) + | (vmonInit->retDisable ? EMU_VMONIO0CTRL_RETDIS : 0) + | (vmonInit->riseWakeup ? EMU_VMONIO0CTRL_RISEWU : 0) + | (vmonInit->fallWakeup ? EMU_VMONIO0CTRL_FALLWU : 0) + | (vmonInit->enable ? EMU_VMONIO0CTRL_EN : 0); + break; + default: + EFM_ASSERT(false); + return; + } +} + +/***************************************************************************//** + * @brief + * Initialize VMON channel with hysteresis (separate rise and fall triggers). + * + * @details + * Initialize a VMON channel which supports hysteresis. The AVDD channel is + * the only channel to support separate rise and fall triggers. The rise and + * fall thresholds will be converted to a register field value based on + * calibration values from the DI page. + * + * @param[in] vmonInit + * VMON Hysteresis initialization struct + ******************************************************************************/ +void EMU_VmonHystInit(const EMU_VmonHystInit_TypeDef *vmonInit) +{ + uint32_t riseThreshold; + uint32_t fallThreshold; + + /* VMON supports voltages between 1620 mV and 3400 mV (inclusive) */ + EFM_ASSERT((vmonInit->riseThreshold >= 1620) && (vmonInit->riseThreshold <= 3400)); + EFM_ASSERT((vmonInit->fallThreshold >= 1620) && (vmonInit->fallThreshold <= 3400)); + /* Fall threshold has to be lower than rise threshold */ + EFM_ASSERT(vmonInit->fallThreshold <= vmonInit->riseThreshold); + + riseThreshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->riseThreshold); + fallThreshold = vmonCalibratedThreshold(vmonInit->channel, vmonInit->fallThreshold); + + switch (vmonInit->channel) { + case emuVmonChannel_AVDD: + EMU->VMONAVDDCTRL = ((riseThreshold / 10) << _EMU_VMONAVDDCTRL_RISETHRESCOARSE_SHIFT) + | ((riseThreshold % 10) << _EMU_VMONAVDDCTRL_RISETHRESFINE_SHIFT) + | ((fallThreshold / 10) << _EMU_VMONAVDDCTRL_FALLTHRESCOARSE_SHIFT) + | ((fallThreshold % 10) << _EMU_VMONAVDDCTRL_FALLTHRESFINE_SHIFT) + | (vmonInit->riseWakeup ? EMU_VMONAVDDCTRL_RISEWU : 0) + | (vmonInit->fallWakeup ? EMU_VMONAVDDCTRL_FALLWU : 0) + | (vmonInit->enable ? EMU_VMONAVDDCTRL_EN : 0); + break; + default: + EFM_ASSERT(false); + return; + } +} + +/***************************************************************************//** + * @brief + * Enable or disable a VMON channel + * + * @param[in] channel + * VMON channel to enable/disable + * + * @param[in] enable + * Whether to enable or disable + ******************************************************************************/ +void EMU_VmonEnable(EMU_VmonChannel_TypeDef channel, bool enable) +{ + uint32_t volatile * reg; + uint32_t bit; + + switch (channel) { + case emuVmonChannel_AVDD: + reg = &(EMU->VMONAVDDCTRL); + bit = _EMU_VMONAVDDCTRL_EN_SHIFT; + break; + case emuVmonChannel_ALTAVDD: + reg = &(EMU->VMONALTAVDDCTRL); + bit = _EMU_VMONALTAVDDCTRL_EN_SHIFT; + break; + case emuVmonChannel_DVDD: + reg = &(EMU->VMONDVDDCTRL); + bit = _EMU_VMONDVDDCTRL_EN_SHIFT; + break; + case emuVmonChannel_IOVDD0: + reg = &(EMU->VMONIO0CTRL); + bit = _EMU_VMONIO0CTRL_EN_SHIFT; + break; + default: + EFM_ASSERT(false); + return; + } + + BUS_RegBitWrite(reg, bit, enable); +} + +/***************************************************************************//** + * @brief + * Get the status of a voltage monitor channel. + * + * @param[in] channel + * VMON channel to get status for + * + * @return + * Status of the selected VMON channel. True if channel is triggered. + ******************************************************************************/ +bool EMU_VmonChannelStatusGet(EMU_VmonChannel_TypeDef channel) +{ + uint32_t bit; + switch (channel) { + case emuVmonChannel_AVDD: + bit = _EMU_STATUS_VMONAVDD_SHIFT; + break; + case emuVmonChannel_ALTAVDD: + bit = _EMU_STATUS_VMONALTAVDD_SHIFT; + break; + case emuVmonChannel_DVDD: + bit = _EMU_STATUS_VMONDVDD_SHIFT; + break; + case emuVmonChannel_IOVDD0: + bit = _EMU_STATUS_VMONIO0_SHIFT; + break; + default: + EFM_ASSERT(false); + bit = 0; + } + + return BUS_RegBitRead(&EMU->STATUS, bit); +} +#endif /* EMU_STATUS_VMONRDY */ + +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) +/***************************************************************************//** + * @brief + * Adjust the bias refresh rate + * + * @details + * This function is only meant to be used under high-temperature operation on + * EFR32xG1 and EFM32xG1 devices. Adjusting the bias mode will + * increase the typical current consumption. See application note 1027 + * and errata documents for further details. + * + * @param [in] mode + * The new bias refresh rate + ******************************************************************************/ +void EMU_SetBiasMode(EMU_BiasMode_TypeDef mode) +{ +#define EMU_TESTLOCK (*(volatile uint32_t *) (EMU_BASE + 0x190)) +#define EMU_BIASCONF (*(volatile uint32_t *) (EMU_BASE + 0x164)) +#define EMU_BIASTESTCTRL (*(volatile uint32_t *) (EMU_BASE + 0x19C)) +#define CMU_ULFRCOCTRL (*(volatile uint32_t *) (CMU_BASE + 0x03C)) + + uint32_t freq = 0x2u; + bool emuTestLocked = false; + + if (mode == emuBiasMode_1KHz) { + freq = 0x0u; + } + + if (EMU_TESTLOCK == 0x1u) { + emuTestLocked = true; + EMU_TESTLOCK = 0xADE8u; + } + + if (mode == emuBiasMode_Continuous) { + EMU_BIASCONF &= ~0x74u; + } else { + EMU_BIASCONF |= 0x74u; + } + + EMU_BIASTESTCTRL |= 0x8u; + CMU_ULFRCOCTRL = (CMU_ULFRCOCTRL & ~0xC00u) + | ((freq & 0x3u) << 10u); + EMU_BIASTESTCTRL &= ~0x8u; + + if (emuTestLocked) { + EMU_TESTLOCK = 0u; + } +} +#endif + +/** @} (end addtogroup EMU) */ +/** @} (end addtogroup emlib) */ +#endif /* __EM_EMU_H */ diff --git a/efm32boot/emlib/em_gpio.c b/efm32boot/emlib/em_gpio.c new file mode 100644 index 0000000..3b1d146 --- /dev/null +++ b/efm32boot/emlib/em_gpio.c @@ -0,0 +1,367 @@ +/***************************************************************************//** + * @file em_gpio.c + * @brief General Purpose IO (GPIO) peripheral API + * devices. + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 "em_gpio.h" + +#if defined(GPIO_COUNT) && (GPIO_COUNT > 0) + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup GPIO + * @brief General Purpose Input/Output (GPIO) API + * @details + * This module contains functions to control the GPIO peripheral of Silicon + * Labs 32-bit MCUs and SoCs. The GPIO peripheral is used for pin configuration + * and direct pin manipulation and sensing as well as routing for peripheral + * pin connections. + * @{ + ******************************************************************************/ + +/******************************************************************************* + ******************************* DEFINES *********************************** + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +/** Validation of pin typically usable in assert statements. */ +#define GPIO_DRIVEMODE_VALID(mode) ((mode) <= 3) +#define GPIO_STRENGHT_VALID(strenght) (!((strenght) \ + & ~(_GPIO_P_CTRL_DRIVESTRENGTH_MASK \ + | _GPIO_P_CTRL_DRIVESTRENGTHALT_MASK))) +/** @endcond */ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Sets the pin location of the debug pins (Serial Wire interface). + * + * @note + * Changing the pins used for debugging uncontrolled, may result in a lockout. + * + * @param[in] location + * The debug pin location to use (0-3). + ******************************************************************************/ +void GPIO_DbgLocationSet(unsigned int location) +{ +#if defined (_GPIO_ROUTE_SWLOCATION_MASK) + EFM_ASSERT(location < AFCHANLOC_MAX); + + GPIO->ROUTE = (GPIO->ROUTE & ~_GPIO_ROUTE_SWLOCATION_MASK) + | (location << _GPIO_ROUTE_SWLOCATION_SHIFT); +#else + (void)location; +#endif +} + +#if defined (_GPIO_P_CTRL_DRIVEMODE_MASK) +/***************************************************************************//** + * @brief + * Sets the drive mode for a GPIO port. + * + * @param[in] port + * The GPIO port to access. + * + * @param[in] mode + * Drive mode to use for port. + ******************************************************************************/ +void GPIO_DriveModeSet(GPIO_Port_TypeDef port, GPIO_DriveMode_TypeDef mode) +{ + EFM_ASSERT(GPIO_PORT_VALID(port) && GPIO_DRIVEMODE_VALID(mode)); + + GPIO->P[port].CTRL = (GPIO->P[port].CTRL & ~(_GPIO_P_CTRL_DRIVEMODE_MASK)) + | (mode << _GPIO_P_CTRL_DRIVEMODE_SHIFT); +} +#endif + +#if defined (_GPIO_P_CTRL_DRIVESTRENGTH_MASK) +/***************************************************************************//** + * @brief + * Sets the drive strength for a GPIO port. + * + * @param[in] port + * The GPIO port to access. + * + * @param[in] strength + * Drive strength to use for port. + ******************************************************************************/ +void GPIO_DriveStrengthSet(GPIO_Port_TypeDef port, + GPIO_DriveStrength_TypeDef strength) +{ + EFM_ASSERT(GPIO_PORT_VALID(port) && GPIO_STRENGHT_VALID(strength)); + BUS_RegMaskedWrite(&GPIO->P[port].CTRL, + _GPIO_P_CTRL_DRIVESTRENGTH_MASK | _GPIO_P_CTRL_DRIVESTRENGTHALT_MASK, + strength); +} +#endif + +/***************************************************************************//** + * @brief + * Configure GPIO external pin interrupt. + * + * @details + * If reconfiguring a GPIO interrupt that is already enabled, it is generally + * recommended to disable it first, see GPIO_Disable(). + * + * The actual GPIO interrupt handler must be in place before enabling the + * interrupt. + * + * Notice that any pending interrupt for the selected interrupt is cleared + * by this function. + * + * @note + * On series 0 devices the pin number parameter is not used. The + * pin number used on these devices is hardwired to the interrupt with the + * same number. @n + * On series 1 devices, pin number can be selected freely within a group. + * Interrupt numbers are divided into 4 groups (intNo / 4) and valid pin + * number within the interrupt groups are: + * 0: pins 0-3 + * 1: pins 4-7 + * 2: pins 8-11 + * 3: pins 12-15 + * + * @param[in] port + * The port to associate with @p pin. + * + * @param[in] pin + * The pin number on the port. + * + * @param[in] intNo + * The interrupt number to trigger. + * + * @param[in] risingEdge + * Set to true if interrupts shall be enabled on rising edge, otherwise false. + * + * @param[in] fallingEdge + * Set to true if interrupts shall be enabled on falling edge, otherwise false. + * + * @param[in] enable + * Set to true if interrupt shall be enabled after configuration completed, + * false to leave disabled. See GPIO_IntDisable() and GPIO_IntEnable(). + ******************************************************************************/ +void GPIO_ExtIntConfig(GPIO_Port_TypeDef port, + unsigned int pin, + unsigned int intNo, + bool risingEdge, + bool fallingEdge, + bool enable) +{ + uint32_t tmp = 0; +#if !defined(_GPIO_EXTIPINSELL_MASK) + (void)pin; +#endif + + EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin)); +#if defined(_GPIO_EXTIPINSELL_MASK) + EFM_ASSERT(GPIO_INTNO_PIN_VALID(intNo, pin)); +#endif + + /* There are two registers controlling the interrupt configuration: + * The EXTIPSELL register controls pins 0-7 and EXTIPSELH controls + * pins 8-15. */ + if (intNo < 8) { + BUS_RegMaskedWrite(&GPIO->EXTIPSELL, + _GPIO_EXTIPSELL_EXTIPSEL0_MASK + << (_GPIO_EXTIPSELL_EXTIPSEL1_SHIFT * intNo), + port << (_GPIO_EXTIPSELL_EXTIPSEL1_SHIFT * intNo)); + } else { + tmp = intNo - 8; + BUS_RegMaskedWrite(&GPIO->EXTIPSELH, + _GPIO_EXTIPSELH_EXTIPSEL8_MASK + << (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp), + port << (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp)); + } + +#if defined(_GPIO_EXTIPINSELL_MASK) + /* There are two registers controlling the interrupt/pin number mapping: + * The EXTIPINSELL register controls interrupt 0-7 and EXTIPINSELH controls + * interrupt 8-15. */ + if (intNo < 8) { + BUS_RegMaskedWrite(&GPIO->EXTIPINSELL, + _GPIO_EXTIPINSELL_EXTIPINSEL0_MASK + << (_GPIO_EXTIPINSELL_EXTIPINSEL1_SHIFT * intNo), + ((pin % 4) & _GPIO_EXTIPINSELL_EXTIPINSEL0_MASK) + << (_GPIO_EXTIPINSELL_EXTIPINSEL1_SHIFT * intNo)); + } else { + BUS_RegMaskedWrite(&GPIO->EXTIPINSELH, + _GPIO_EXTIPINSELH_EXTIPINSEL8_MASK + << (_GPIO_EXTIPINSELH_EXTIPINSEL9_SHIFT * tmp), + ((pin % 4) & _GPIO_EXTIPINSELH_EXTIPINSEL8_MASK) + << (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp)); + } +#endif + + /* Enable/disable rising edge */ + BUS_RegBitWrite(&(GPIO->EXTIRISE), intNo, risingEdge); + + /* Enable/disable falling edge */ + BUS_RegBitWrite(&(GPIO->EXTIFALL), intNo, fallingEdge); + + /* Clear any pending interrupt */ + GPIO->IFC = 1 << intNo; + + /* Finally enable/disable interrupt */ + BUS_RegBitWrite(&(GPIO->IEN), intNo, enable); +} + +/***************************************************************************//** + * @brief + * Set the mode for a GPIO pin. + * + * @param[in] port + * The GPIO port to access. + * + * @param[in] pin + * The pin number in the port. + * + * @param[in] mode + * The desired pin mode. + * + * @param[in] out + * Value to set for pin in DOUT register. The DOUT setting is important for + * even some input mode configurations, determining pull-up/down direction. + ******************************************************************************/ +void GPIO_PinModeSet(GPIO_Port_TypeDef port, + unsigned int pin, + GPIO_Mode_TypeDef mode, + unsigned int out) +{ + EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin)); + + /* If disabling pin, do not modify DOUT in order to reduce chance for */ + /* glitch/spike (may not be sufficient precaution in all use cases) */ + if (mode != gpioModeDisabled) { + if (out) { + GPIO_PinOutSet(port, pin); + } else { + GPIO_PinOutClear(port, pin); + } + } + + /* There are two registers controlling the pins for each port. The MODEL + * register controls pins 0-7 and MODEH controls pins 8-15. */ + if (pin < 8) { + GPIO->P[port].MODEL = (GPIO->P[port].MODEL & ~(0xFu << (pin * 4))) + | (mode << (pin * 4)); + } else { + GPIO->P[port].MODEH = (GPIO->P[port].MODEH & ~(0xFu << ((pin - 8) * 4))) + | (mode << ((pin - 8) * 4)); + } + + if (mode == gpioModeDisabled) { + if (out) { + GPIO_PinOutSet(port, pin); + } else { + GPIO_PinOutClear(port, pin); + } + } +} + +/***************************************************************************//** + * @brief + * Get the mode for a GPIO pin. + * + * @param[in] port + * The GPIO port to access. + * + * @param[in] pin + * The pin number in the port. + * + * @return + * The pin mode. + ******************************************************************************/ +GPIO_Mode_TypeDef GPIO_PinModeGet(GPIO_Port_TypeDef port, + unsigned int pin) +{ + EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin)); + + if (pin < 8) { + return (GPIO_Mode_TypeDef) ((GPIO->P[port].MODEL >> (pin * 4)) & 0xF); + } else { + return (GPIO_Mode_TypeDef) ((GPIO->P[port].MODEH >> ((pin - 8) * 4)) & 0xF); + } +} + +#if defined(_GPIO_EM4WUEN_MASK) +/**************************************************************************//** + * @brief + * Enable GPIO pin wake-up from EM4. When the function exits, + * EM4 mode can be safely entered. + * + * @note + * It is assumed that the GPIO pin modes are set correctly. + * Valid modes are @ref gpioModeInput and @ref gpioModeInputPull. + * + * @param[in] pinmask + * Bitmask containing the bitwise logic OR of which GPIO pin(s) to enable. + * Refer to Reference Manuals for pinmask to GPIO port/pin mapping. + * @param[in] polaritymask + * Bitmask containing the bitwise logic OR of GPIO pin(s) wake-up polarity. + * Refer to Reference Manuals for pinmask to GPIO port/pin mapping. + *****************************************************************************/ +void GPIO_EM4EnablePinWakeup(uint32_t pinmask, uint32_t polaritymask) +{ + EFM_ASSERT((pinmask & ~_GPIO_EM4WUEN_MASK) == 0); + +#if defined(_GPIO_EM4WUPOL_MASK) + EFM_ASSERT((polaritymask & ~_GPIO_EM4WUPOL_MASK) == 0); + GPIO->EM4WUPOL &= ~pinmask; /* Set wakeup polarity */ + GPIO->EM4WUPOL |= pinmask & polaritymask; +#elif defined(_GPIO_EXTILEVEL_MASK) + EFM_ASSERT((polaritymask & ~_GPIO_EXTILEVEL_MASK) == 0); + GPIO->EXTILEVEL &= ~pinmask; + GPIO->EXTILEVEL |= pinmask & polaritymask; +#endif + GPIO->EM4WUEN |= pinmask; /* Enable wakeup */ + + GPIO_EM4SetPinRetention(true); /* Enable pin retention */ + +#if defined(_GPIO_CMD_EM4WUCLR_MASK) + GPIO->CMD = GPIO_CMD_EM4WUCLR; /* Clear wake-up logic */ +#elif defined(_GPIO_IFC_EM4WU_MASK) + GPIO_IntClear(pinmask); +#endif +} +#endif + +/** @} (end addtogroup GPIO) */ +/** @} (end addtogroup emlib) */ + +#endif /* defined(GPIO_COUNT) && (GPIO_COUNT > 0) */ diff --git a/efm32boot/emlib/em_msc.c b/efm32boot/emlib/em_msc.c new file mode 100644 index 0000000..2391d2a --- /dev/null +++ b/efm32boot/emlib/em_msc.c @@ -0,0 +1,1163 @@ +/***************************************************************************//** + * @file em_msc.c + * @brief Flash controller (MSC) Peripheral API + * @version 5.5.0 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. www.silabs.com + ******************************************************************************* + * + * 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 "em_msc.h" +#if defined(MSC_COUNT) && (MSC_COUNT > 0) + +#include "em_system.h" +#if defined(_MSC_TIMEBASE_MASK) +#include "em_cmu.h" +#endif +#include "em_assert.h" +#if defined(_MSC_ECCCTRL_MASK) +#include "em_cmu.h" +#include "em_core.h" +#endif + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +#if defined(__ICCARM__) +/* Suppress warnings originating from use of EFM_ASSERT() with IAR: + EFM_ASSERT() is implemented as a local ramfunc */ +#pragma diag_suppress=Ta022 +#endif + +#if defined(EM_MSC_RUN_FROM_FLASH) && defined(_EFM32_GECKO_FAMILY) +#error "Running Flash write/erase operations from Flash is not supported on EFM32G." +#endif + +/******************************************************************************* + ****************************** DEFINES ****************************** + ******************************************************************************/ +#if defined(MSC_WRITECTRL_WDOUBLE) +#define WORDS_PER_DATA_PHASE (FLASH_SIZE < (512 * 1024) ? 1 : 2) +#else +#define WORDS_PER_DATA_PHASE (1) +#endif + +#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) +/* Fix for errata FLASH_E201 - Potential program failure after Power On */ +#define ERRATA_FIX_FLASH_E201_EN +#endif + +#if defined(_MSC_ECCCTRL_MASK) +#if defined(_SILICON_LABS_32B_SERIES_1_CONFIG_1) +/* On Series 1 Config 1, EFM32GG11, ECC is supported for RAM0 and RAM1 + banks (not RAM2). It is necessary to figure out which is biggest to + calculate the number of DMA descriptors needed. */ +#define ECC_RAM_SIZE_MAX (SL_MAX(RAM0_MEM_SIZE, RAM1_MEM_SIZE)) + +#define ECC_RAM0_MEM_BASE (RAM0_MEM_BASE) +#define ECC_RAM0_MEM_SIZE (RAM0_MEM_SIZE) + +#define ECC_RAM1_MEM_BASE (RAM1_MEM_BASE) +#define ECC_RAM1_MEM_SIZE (RAM1_MEM_SIZE) + +#define ECC_CTRL_REG_ADDR (&MSC->ECCCTRL) +#define ECC_RAM0_WRITE_EN (_MSC_ECCCTRL_RAMECCEWEN_SHIFT) +#define ECC_RAM0_CHECK_EN (_MSC_ECCCTRL_RAMECCCHKEN_SHIFT) +#define ECC_RAM1_WRITE_EN (_MSC_ECCCTRL_RAM1ECCEWEN_SHIFT) +#define ECC_RAM1_CHECK_EN (_MSC_ECCCTRL_RAM1ECCCHKEN_SHIFT) + +#define ECC_IFC_REG_ADDR (&MSC->IFC) +#define ECC_IFC_MASK (MSC_IFC_RAMERR1B | MSC_IFC_RAMERR2B \ + | MSC_IFC_RAM1ERR1B | MSC_IFC_RAM1ERR2B) +#else +#error Unknown device. +#endif + +#define ECC_DMA_MAX_XFERCNT (_LDMA_CH_CTRL_XFERCNT_MASK \ + >> _LDMA_CH_CTRL_XFERCNT_SHIFT) +#define ECC_DMA_DESC_SIZE ((ECC_DMA_MAX_XFERCNT + 1) * 4) /* 4 bytes units */ + +#define ECC_DMA_DESCS (ECC_RAM_SIZE_MAX / ECC_DMA_DESC_SIZE) + +#endif + +/******************************************************************************* + ****************************** TYPEDEFS ****************************** + ******************************************************************************/ +typedef enum { + mscWriteIntSafe, + mscWriteFast, +} MSC_WriteStrategy_Typedef; + +#if defined(_MSC_ECCCTRL_MASK) +typedef struct { + volatile uint32_t *ctrlReg; + uint32_t writeEnBit; + uint32_t checkEnBit; + volatile uint32_t *ifClearReg; + uint32_t ifClearMask; + uint32_t base; + uint32_t size; +} MSC_EccBank_Typedef; +#endif + +/******************************************************************************* + ****************************** LOCALS ******************************* + ******************************************************************************/ +#if defined(_MSC_ECCCTRL_MASK) +static const MSC_EccBank_Typedef eccBank[MSC_ECC_BANKS] = +{ + { ECC_CTRL_REG_ADDR, ECC_RAM0_WRITE_EN, ECC_RAM0_CHECK_EN, + ECC_IFC_REG_ADDR, ECC_IFC_MASK, + ECC_RAM0_MEM_BASE, ECC_RAM0_MEM_SIZE }, +#if MSC_ECC_BANKS > 1 + { ECC_CTRL_REG_ADDR, ECC_RAM1_WRITE_EN, ECC_RAM1_CHECK_EN, + ECC_IFC_REG_ADDR, ECC_IFC_MASK, + ECC_RAM1_MEM_BASE, ECC_RAM1_MEM_SIZE }, +#endif +}; +#endif + +/******************************************************************************* + ****************************** FUNCTIONS ****************************** + ******************************************************************************/ +MSC_RAMFUNC_DECLARATOR MSC_Status_TypeDef +MSC_WriteWordI(uint32_t *address, + void const *data, + uint32_t numBytes, + MSC_WriteStrategy_Typedef writeStrategy); + +MSC_RAMFUNC_DECLARATOR MSC_Status_TypeDef +MSC_LoadWriteData(uint32_t* data, + uint32_t numWords, + MSC_WriteStrategy_Typedef writeStrategy); + +MSC_RAMFUNC_DECLARATOR MSC_Status_TypeDef +MSC_LoadVerifyAddress(uint32_t* address); + +#if !defined(EM_MSC_RUN_FROM_FLASH) + +MSC_RAMFUNC_DECLARATOR void mscRfAssertEFM(const char *file, int line); + +/***************************************************************************//** + * @brief + * Local ramfunc assertEFM. + * + * A local ramfunc version of assertEFM is needed because certain MSC functions + * are allocated to RAM. The Flash may get erased and code normally located in + * Flash must therefore have a RAM copy. + * + * This function is invoked through EFM_ASSERT() macro usage only and should + * not be used explicitly. + * + * @param[in] file + * The source file where assertion failed. + * + * @param[in] line + * A line number in the source file where assertion failed. + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +void mscRfAssertEFM(const char *file, int line) +{ + (void)file; /* Unused parameter */ + (void)line; /* Unused parameter */ + + while (true) { + } +} +MSC_RAMFUNC_DEFINITION_END + +/* Undef the define from em_assert.h and redirect to a local ramfunc version. */ +#undef EFM_ASSERT +#if defined(DEBUG_EFM) || defined(DEBUG_EFM_USER) +#define EFM_ASSERT(expr) ((expr) ? ((void)0) : mscRfAssertEFM(__FILE__, __LINE__)) +#else +#define EFM_ASSERT(expr) ((void)(expr)) +#endif /* defined(DEBUG_EFM) || defined(DEBUG_EFM_USER) */ + +#endif /* !EM_MSC_RUN_FROM_FLASH */ + +/** @endcond */ + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup MSC + * @{ + ******************************************************************************/ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Enables the flash controller for writing. + * @note + * This function must be called before flash operations when + * AUXHFRCO clock has been changed from a default band. + ******************************************************************************/ +void MSC_Init(void) +{ +#if defined(_MSC_TIMEBASE_MASK) + uint32_t freq, cycles; +#endif + +#if defined(_EMU_STATUS_VSCALE_MASK) + /* VSCALE must be done. Flash erase and write requires VSCALE2. */ + EFM_ASSERT(!(EMU->STATUS & _EMU_STATUS_VSCALEBUSY_MASK)); + EFM_ASSERT((EMU->STATUS & _EMU_STATUS_VSCALE_MASK) == EMU_STATUS_VSCALE_VSCALE2); +#endif + + /* Unlock the MSC module. */ + MSC->LOCK = MSC_UNLOCK_CODE; + /* Disable writing to the Flash. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + +#if defined(_MSC_TIMEBASE_MASK) + /* Configure MSC->TIMEBASE according to a selected frequency. */ + freq = CMU_ClockFreqGet(cmuClock_AUX); + + /* Timebase 5us is used for the 1/1.2 MHz band only. Note that the 1 MHz band + is tuned to 1.2 MHz on newer revisions. */ + if (freq > 1200000) { + /* Calculate a number of clock cycles for 1 us as a base period. */ + freq = (freq * 11) / 10; + cycles = (freq / 1000000) + 1; + + /* Configure clock cycles for flash timing. */ + MSC->TIMEBASE = (MSC->TIMEBASE & ~(_MSC_TIMEBASE_BASE_MASK + | _MSC_TIMEBASE_PERIOD_MASK)) + | MSC_TIMEBASE_PERIOD_1US + | (cycles << _MSC_TIMEBASE_BASE_SHIFT); + } else { + /* Calculate a number of clock cycles for 5 us as a base period. */ + freq = (freq * 5 * 11) / 10; + cycles = (freq / 1000000) + 1; + + /* Configure clock cycles for flash timing */ + MSC->TIMEBASE = (MSC->TIMEBASE & ~(_MSC_TIMEBASE_BASE_MASK + | _MSC_TIMEBASE_PERIOD_MASK)) + | MSC_TIMEBASE_PERIOD_5US + | (cycles << _MSC_TIMEBASE_BASE_SHIFT); + } +#endif +} + +/***************************************************************************//** + * @brief + * Disables the flash controller for writing. + ******************************************************************************/ +void MSC_Deinit(void) +{ + /* Disable writing to the Flash. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + /* Lock the MSC module.*/ + MSC->LOCK = 0; +} + +/***************************************************************************//** + * @brief + * Set the MSC code execution configuration. + * + * @param[in] execConfig + * The code execution configuration. + ******************************************************************************/ +void MSC_ExecConfigSet(MSC_ExecConfig_TypeDef *execConfig) +{ + uint32_t mscReadCtrl; + +#if defined(MSC_READCTRL_MODE_WS0SCBTP) + mscReadCtrl = MSC->READCTRL & _MSC_READCTRL_MODE_MASK; + if ((mscReadCtrl == MSC_READCTRL_MODE_WS0) && (execConfig->scbtEn)) { + mscReadCtrl |= MSC_READCTRL_MODE_WS0SCBTP; + } else if ((mscReadCtrl == MSC_READCTRL_MODE_WS1) && (execConfig->scbtEn)) { + mscReadCtrl |= MSC_READCTRL_MODE_WS1SCBTP; + } else if ((mscReadCtrl == MSC_READCTRL_MODE_WS0SCBTP) && (!execConfig->scbtEn)) { + mscReadCtrl |= MSC_READCTRL_MODE_WS0; + } else if ((mscReadCtrl == MSC_READCTRL_MODE_WS1SCBTP) && (!execConfig->scbtEn)) { + mscReadCtrl |= MSC_READCTRL_MODE_WS1; + } else { + /* No change needed. */ + } +#endif + + mscReadCtrl = MSC->READCTRL & ~(0 +#if defined(MSC_READCTRL_SCBTP) + | MSC_READCTRL_SCBTP +#endif +#if defined(MSC_READCTRL_USEHPROT) + | MSC_READCTRL_USEHPROT +#endif +#if defined(MSC_READCTRL_PREFETCH) + | MSC_READCTRL_PREFETCH +#endif +#if defined(MSC_READCTRL_ICCDIS) + | MSC_READCTRL_ICCDIS +#endif +#if defined(MSC_READCTRL_AIDIS) + | MSC_READCTRL_AIDIS +#endif +#if defined(MSC_READCTRL_IFCDIS) + | MSC_READCTRL_IFCDIS +#endif + ); + mscReadCtrl |= (0 +#if defined(MSC_READCTRL_SCBTP) + | (execConfig->scbtEn ? MSC_READCTRL_SCBTP : 0) +#endif +#if defined(MSC_READCTRL_USEHPROT) + | (execConfig->useHprot ? MSC_READCTRL_USEHPROT : 0) +#endif +#if defined(MSC_READCTRL_PREFETCH) + | (execConfig->prefetchEn ? MSC_READCTRL_PREFETCH : 0) +#endif +#if defined(MSC_READCTRL_ICCDIS) + | (execConfig->iccDis ? MSC_READCTRL_ICCDIS : 0) +#endif +#if defined(MSC_READCTRL_AIDIS) + | (execConfig->aiDis ? MSC_READCTRL_AIDIS : 0) +#endif +#if defined(MSC_READCTRL_IFCDIS) + | (execConfig->ifcDis ? MSC_READCTRL_IFCDIS : 0) +#endif + ); + + MSC->READCTRL = mscReadCtrl; +} + +#if defined(_MSC_ECCCTRL_MASK) + +/***************************************************************************//** + * @brief + * DMA read and write existing values (for ECC initialization). + * + * @details + * This function uses DMA to read and write the existing data values in + * the RAM region specified by start and size. The function will use the + * 2 DMA channels specified by the channels[2] array. + * + * @param[in] start + * A start address of the address range in RAM to read/write. + * + * @param[in] size + * A size of the address range in RAM to read/write. + * + * @param[in] channels[2] + * An array of 2 DMA channels to use. + ******************************************************************************/ +static void mscEccReadWriteExistingDma(uint32_t start, + uint32_t size, + uint32_t channels[2]) +{ + uint32_t descCnt = 0; + uint32_t dmaDesc[ECC_DMA_DESCS][4]; + uint32_t chMask = (1 << channels[0]) | (1 << channels[1]); + /* Assert that the 2 DMA channel numbers are different. */ + EFM_ASSERT(channels[0] != channels[1]); + + /* Make sure that the ECC_RAM_SIZE_MAX is a multiple of ECC_DMA_DESC_SIZE + to match the total xfer size of the descriptor chain with the largest + ECC RAM bank. */ + EFM_ASSERT((ECC_RAM_SIZE_MAX % ECC_DMA_DESC_SIZE) == 0); + + /* Initialize the LDMA descriptor chain. */ + do { + dmaDesc[descCnt][0] = /* DMA desc CTRL word */ + LDMA_CH_CTRL_STRUCTTYPE_TRANSFER + | LDMA_CH_CTRL_STRUCTREQ + | _LDMA_CH_CTRL_XFERCNT_MASK + | LDMA_CH_CTRL_BLOCKSIZE_ALL + | LDMA_CH_CTRL_REQMODE_ALL + | LDMA_CH_CTRL_SRCINC_ONE + | LDMA_CH_CTRL_SIZE_WORD + | LDMA_CH_CTRL_DSTINC_ONE; + + /* A source and destination address. */ + dmaDesc[descCnt][1] = start; + dmaDesc[descCnt][2] = start; + /* A link to the next descriptor. */ + dmaDesc[descCnt][3] = LDMA_CH_LINK_LINK + | (((uint32_t) &dmaDesc[descCnt + 1][0]) + & _LDMA_CH_LINK_LINKADDR_MASK); + + start += ECC_DMA_DESC_SIZE; + size -= ECC_DMA_DESC_SIZE; + descCnt++; + } while (size); + + /* Divide the descriptor list in two parts, one for each channel, + by setting the link bit and address 0 of the descriptor in the middle + to 0. */ + dmaDesc[(descCnt / 2) - 1][3] = 0; + + /* Set the last descriptor link bit and address to 0. */ + dmaDesc[descCnt - 1][3] = 0; + + /* Start the LDMA clock. */ + CMU_ClockEnable(cmuClock_LDMA, true); + + /* Round robin scheduling for all channels (0 = no fixed priority channels). + */ + LDMA->CTRL = 0 << _LDMA_CTRL_NUMFIXED_SHIFT; + LDMA->CHEN = 0; + LDMA->DBGHALT = 0; + LDMA->REQDIS = 0; + + /* Disable LDMA interrupts and clear interrupt status. */ + LDMA->IEN = 0; + LDMA->IFC = 0xFFFFFFFF; + + /* Disable looping. */ + LDMA->CH[channels[0]].LOOP = 0; + LDMA->CH[channels[1]].LOOP = 0; + + /* Set the descriptor address for the first channel. */ + LDMA->CH[channels[0]].LINK = ((uint32_t)&dmaDesc[0][0]) + & _LDMA_CH_LINK_LINKADDR_MASK; + /* Set the descriptor address for the second channel. */ + LDMA->CH[channels[1]].LINK = ((uint32_t)&dmaDesc[descCnt / 2][0]) + & _LDMA_CH_LINK_LINKADDR_MASK; + /* Clear the channel done flags. */ + BUS_RegMaskedClear(&LDMA->CHDONE, chMask); + + /* Start transfer by loading descriptors. */ + LDMA->LINKLOAD = chMask; + + /* Wait until finished. */ + while (!(((LDMA->CHEN & chMask) == 0) + && ((LDMA->CHDONE & chMask) == chMask))) { + } + + /* Stop the LDMA clock. */ + CMU_ClockEnable(cmuClock_LDMA, false); +} + +/***************************************************************************//** + * @brief + * Initialize ECC for a given memory bank. + * + * @brief + * This function initializes ECC for a given memory bank which is specified + * with the MSC_EccBank_Typedef structure input parameter. + * + * @param[in] eccBank + * The ECC memory bank device structure. + * + * @param[in] dmaChannels + * An array of 2 DMA channels that may be used during ECC initialization. + * + ******************************************************************************/ +static void mscEccBankInit(const MSC_EccBank_Typedef *eccBank, + uint32_t dmaChannels[2]) +{ + uint32_t ctrlReg; + + CORE_DECLARE_IRQ_STATE; + + CORE_ENTER_CRITICAL(); + + /* Enable the ECC write. Keep ECC checking disabled during initialization. */ + ctrlReg = *eccBank->ctrlReg; + ctrlReg |= 1 << eccBank->writeEnBit; + *eccBank->ctrlReg = ctrlReg; + + /* Initialize ECC syndromes by using DMA to read and write the existing + data values in RAM. */ + mscEccReadWriteExistingDma(eccBank->base, eccBank->size, dmaChannels); + + /* Clear any ECC errors that may have been reported before or during + initialization. */ + *eccBank->ifClearReg = eccBank->ifClearMask; + + /* Enable the ECC decoder to detect and report ECC errors. */ + ctrlReg |= 1 << eccBank->checkEnBit; + *eccBank->ctrlReg = ctrlReg; + + CORE_EXIT_CRITICAL(); +} + +/***************************************************************************//** + * @brief + * Disable ECC for a given memory bank. + * + * @brief + * This function disables ECC for a given memory bank which is specified + * with the MSC_EccBank_Typedef structure input parameter. + * + * @param[in] eccBank + * ECC memory bank device structure. + * + ******************************************************************************/ +static void mscEccBankDisable(const MSC_EccBank_Typedef *eccBank) +{ + /* Disable ECC write (encoder) and checking (decoder). */ + *eccBank->ctrlReg &= ~((1 << eccBank->writeEnBit) | (1 << eccBank->checkEnBit)); +} + +/***************************************************************************//** + * @brief + * Configure Error Correcting Code (ECC) + * + * @details + * This function configures ECC support according to the configuration + * input parameter. If the user requests enabling ECC for a given RAM bank, + * this function will initialize ECC memory (syndromes) for the bank by + * reading and writing the existing values in memory, i.e., all data is + * preserved. The initialization process runs in a critical section + * disallowing interrupts and thread scheduling and will consume a + * considerable amount of clock cycles. Therefore, carefully + * assess where to call this function. Consider increasing + * the clock frequency to reduce the execution time. + * This function makes use of 2 DMA channels to move data to/from RAM in an + * efficient way. The user can select which 2 DMA channels to use + * to avoid conflicts with the application. However, make sure + * that no other DMA operations take place while this function is executing. + * If the application is using the DMA controller prior to calling this + * function, the application will need to reinitialize DMA registers after + * this function has completed. + * + * @note + * This function protects the ECC initialization procedure from interrupts + * and other threads by using a critical section (defined by em_core.h) + * When running on an RTOS, the user may need to override CORE_EnterCritical + * CORE_ExitCritical which are declared as 'SL_WEAK' in em_core.c. + * + * @param[in] eccConfig + * ECC configuration + ******************************************************************************/ +void MSC_EccConfigSet(MSC_EccConfig_TypeDef *eccConfig) +{ + unsigned int cnt; + +#if defined(_SILICON_LABS_32B_SERIES_1_CONFIG_1) + /* On Series 1 Config 1, EFM32GG11, disable the ECC fault enable. */ + MSC->CTRL &= ~MSC_CTRL_RAMECCERRFAULTEN; +#endif + + /* Loop through the ECC banks array and enable or disable according to + the eccConfig->enableEccBank array. */ + for (cnt = 0; cnt < MSC_ECC_BANKS; cnt++) { + if (eccConfig->enableEccBank[cnt]) { + mscEccBankInit(&eccBank[cnt], eccConfig->dmaChannels); + } else { + mscEccBankDisable(&eccBank[cnt]); + } + } +} + +#endif /* #if defined(_MSC_ECCCTRL_MASK) */ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +/***************************************************************************//** + * @brief + * Perform the address phase of the flash write cycle. + * @details + * This function performs the address phase of a flash write operation by + * writing the given flash address to the ADDRB register and issuing the + * LADDRIM command to load the address. + * @param[in] address + * An address in flash memory. Must be aligned at a 4 byte boundary. + * @return + * Returns the status of the address load operation, #MSC_Status_TypeDef + * @verbatim + * mscReturnOk - The operation completed successfully. + * mscReturnInvalidAddr - The operation tried to erase a non-flash area. + * mscReturnLocked - The operation tried to erase a locked area of the Flash. + * @endverbatim + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_LoadVerifyAddress(uint32_t* address) +{ + uint32_t status; + uint32_t timeOut; + + /* Wait for the MSC to become ready. */ + timeOut = MSC_PROGRAM_TIMEOUT; + while ((MSC->STATUS & MSC_STATUS_BUSY) && (timeOut != 0)) { + timeOut--; + } + + /* Check for timeout. */ + if (timeOut == 0) { + return mscReturnTimeOut; + } + /* Load the address. */ + MSC->ADDRB = (uint32_t)address; + MSC->WRITECMD = MSC_WRITECMD_LADDRIM; + + status = MSC->STATUS; + if (status & (MSC_STATUS_INVADDR | MSC_STATUS_LOCKED)) { + /* Check for an invalid address. */ + if (status & MSC_STATUS_INVADDR) { + return mscReturnInvalidAddr; + } + /* Check for the write protected page. */ + if (status & MSC_STATUS_LOCKED) { + return mscReturnLocked; + } + } + return mscReturnOk; +} +MSC_RAMFUNC_DEFINITION_END + +/***************************************************************************//** + * @brief + * Perform a flash data write phase. + * @details + * This function performs the data phase of a flash write operation by loading + * the given number of 32-bit words to the WDATA register. + * @param[in] data + * A pointer to the first data word to load. + * @param[in] numWords + * A number of data words (32-bit) to load. + * @param[in] writeStrategy + * A write strategy to apply. + * @return + * Returns the status of the data load operation. + * @verbatim + * mscReturnOk - An operation completed successfully. + * mscReturnTimeOut - An operation timed out waiting for the flash operation + * to complete. + * @endverbatim + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_LoadWriteData(uint32_t* data, + uint32_t numWords, + MSC_WriteStrategy_Typedef writeStrategy) +{ + uint32_t timeOut; + uint32_t wordIndex; + bool useWDouble = false; + MSC_Status_TypeDef retval = mscReturnOk; +#if !defined(_EFM32_GECKO_FAMILY) + uint32_t irqState; +#endif + +#if defined(_MSC_WRITECTRL_LPWRITE_MASK) && defined(_MSC_WRITECTRL_WDOUBLE_MASK) + /* If the LPWRITE (Low Power Write) is NOT enabled, set WDOUBLE (Write Double word). */ + if (!(MSC->WRITECTRL & MSC_WRITECTRL_LPWRITE)) { +#if defined(_SILICON_LABS_32B_SERIES_0) + /* If the number of words to be written is odd, align by writing + a single word first, before setting the WDOUBLE bit. */ + if (numWords & 0x1) { + /* Wait for the MSC to become ready for the next word. */ + timeOut = MSC_PROGRAM_TIMEOUT; + while ((!(MSC->STATUS & MSC_STATUS_WDATAREADY)) && (timeOut != 0)) { + timeOut--; + } + /* Check for timeout. */ + if (timeOut == 0) { + return mscReturnTimeOut; + } + /* Clear the double word option to write the initial single word. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WDOUBLE; + /* Write first data word. */ + MSC->WDATA = *data++; + MSC->WRITECMD = MSC_WRITECMD_WRITEONCE; + + /* Wait for the operation to finish. It may be required to change the WDOUBLE + configuration after the initial write. It should not be changed while BUSY. */ + timeOut = MSC_PROGRAM_TIMEOUT; + while ((MSC->STATUS & MSC_STATUS_BUSY) && (timeOut != 0)) { + timeOut--; + } + /* Check for timeout. */ + if (timeOut == 0) { + return mscReturnTimeOut; + } + /* Subtract this initial odd word for the write loop below. */ + numWords -= 1; + retval = mscReturnOk; + } + /* Set the double word option to write two words per + data phase. */ +#endif + MSC->WRITECTRL |= MSC_WRITECTRL_WDOUBLE; + useWDouble = true; + } +#endif /* defined( _MSC_WRITECTRL_LPWRITE_MASK ) && defined( _MSC_WRITECTRL_WDOUBLE_MASK ) */ + + /* Write the rest as a double word write if wordsPerDataPhase == 2 */ + if (numWords > 0) { + /**** Write strategy: mscWriteIntSafe ****/ + if (writeStrategy == mscWriteIntSafe) { + /* Requires a system core clock at 1MHz or higher */ + EFM_ASSERT(SystemCoreClock >= 1000000); + wordIndex = 0; + while (wordIndex < numWords) { + if (!useWDouble) { + MSC->WDATA = *data++; + wordIndex++; + MSC->WRITECMD = MSC_WRITECMD_WRITEONCE; + } else { // useWDouble == true + /* Trigger a double write according to flash properties. */ +#if defined(_SILICON_LABS_32B_SERIES_0) && defined(_MSC_WRITECTRL_WDOUBLE_MASK) + MSC->WDATA = *data++; + while (!(MSC->STATUS & MSC_STATUS_WDATAREADY)) ; + MSC->WDATA = *data++; + wordIndex += 2; + MSC->WRITECMD = MSC_WRITECMD_WRITEONCE; + +#elif defined(_SILICON_LABS_32B_SERIES_1) && defined(_MSC_WRITECTRL_WDOUBLE_MASK) + while (!(MSC->STATUS & MSC_STATUS_WDATAREADY)) ; + do { + MSC->WDATA = *data++; + wordIndex++; + } while ((MSC->STATUS & MSC_STATUS_WDATAREADY) + && (wordIndex < numWords)); + MSC->WRITECMD = MSC_WRITECMD_WRITETRIG; +#endif + } + + /* Wait for the transaction to finish. */ + timeOut = MSC_PROGRAM_TIMEOUT; + while ((MSC->STATUS & MSC_STATUS_BUSY) && (timeOut != 0)) { + timeOut--; + } + /* Check for a timeout. */ + if (timeOut == 0) { + retval = mscReturnTimeOut; + break; + } +#if defined(_EFM32_GECKO_FAMILY) + MSC->ADDRB += 4; + MSC->WRITECMD = MSC_WRITECMD_LADDRIM; +#endif + } + } + /**** Write strategy: mscWriteFast ****/ + else { +#if defined(_EFM32_GECKO_FAMILY) + /* Gecko does not have auto-increment of ADDR. */ + EFM_ASSERT(false); +#else + /* Requires a system core clock at 14 MHz or higher. */ + EFM_ASSERT(SystemCoreClock >= 14000000); + + /* + * Protect from interrupts to be sure to satisfy the us timing + * needs of the MSC flash programming state machine. + */ + irqState = __get_PRIMASK(); + __disable_irq(); + + wordIndex = 0; + while (wordIndex < numWords) { + /* Wait for the MSC to be ready for the next word. */ + while (!(MSC->STATUS & MSC_STATUS_WDATAREADY)) { + /* If the write to MSC->WDATA below missed the 30 us timeout and the + following MSC_WRITECMD_WRITETRIG command arrived while + MSC_STATUS_BUSY is 1, the MSC_WRITECMD_WRITETRIG could be ignored by + the MSC. In this case, MSC_STATUS_WORDTIMEOUT is set to 1 + and MSC_STATUS_BUSY is 0. A new trigger is therefore needed to + complete write of data in MSC->WDATA. + If WDATAREADY became high since entering the loop, exit and continue + to the next WDATA write. + */ + if ((MSC->STATUS & (MSC_STATUS_WORDTIMEOUT + | MSC_STATUS_BUSY + | MSC_STATUS_WDATAREADY)) + == MSC_STATUS_WORDTIMEOUT) { + MSC->WRITECMD = MSC_WRITECMD_WRITETRIG; + } + } + + if (!useWDouble) { + MSC->WDATA = *data; + MSC->WRITECMD = MSC_WRITECMD_WRITETRIG; + data++; + wordIndex++; + } else { // useWDouble == true + /* Trigger double write according to flash properties. */ +#if defined(_SILICON_LABS_32B_SERIES_0) + MSC->WDATA = *data; + if (wordIndex & 0x1) { + MSC->WRITECMD = MSC_WRITECMD_WRITETRIG; + } + data++; + wordIndex++; + +#elif (_SILICON_LABS_32B_SERIES_1_CONFIG >= 2) + do { + MSC->WDATA = *data++; + wordIndex++; + } while ((MSC->STATUS & MSC_STATUS_WDATAREADY) + && (wordIndex < numWords)); + MSC->WRITECMD = MSC_WRITECMD_WRITETRIG; +#endif + } + } + + if (irqState == 0) { + /* Restore the previous interrupt state. */ + __enable_irq(); + } + + /* Wait for the transaction to finish. */ + timeOut = MSC_PROGRAM_TIMEOUT; + while ((MSC->STATUS & MSC_STATUS_BUSY) && (timeOut != 0)) { + timeOut--; + } + /* Check for a timeout. */ + if (timeOut == 0) { + retval = mscReturnTimeOut; + } +#endif + } /* writeStrategy */ + } + +#if defined(_MSC_WRITECTRL_WDOUBLE_MASK) + /* Clear a double word option, which should not be left on when returning. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WDOUBLE; +#endif + + return retval; +} +MSC_RAMFUNC_DEFINITION_END + +/***************************************************************************//** + * @brief + * An internal flash write function with the select write strategy parameter. + * @param[in] address + * A write address. + * @param[in] data + * A pointer to the first data word to load. + * @param[in] numBytes + * A nsumber of data bytes to load, which must be a multiple of 4 bytes. + * @param[in] writeStrategy + * A wWrite strategy to apply. + * @return + * Returns the status of the data load operation. + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_WriteWordI(uint32_t *address, + void const *data, + uint32_t numBytes, + MSC_WriteStrategy_Typedef writeStrategy) +{ + uint32_t wordCount; + uint32_t numWords; + uint32_t pageWords; + uint32_t* pData; + MSC_Status_TypeDef retval = mscReturnOk; + + /* Check alignment (must be aligned to words). */ + EFM_ASSERT(((uint32_t) address & 0x3) == 0); + + /* Check a number of bytes. Must be divisible by four. */ + EFM_ASSERT((numBytes & 0x3) == 0); + +#if defined(_EMU_STATUS_VSCALE_MASK) + /* VSCALE must be done and flash write requires VSCALE2. */ + EFM_ASSERT(!(EMU->STATUS & _EMU_STATUS_VSCALEBUSY_MASK)); + EFM_ASSERT((EMU->STATUS & _EMU_STATUS_VSCALE_MASK) == EMU_STATUS_VSCALE_VSCALE2); +#endif + + /* Enable writing to the MSC module. */ + MSC->WRITECTRL |= MSC_WRITECTRL_WREN; + + /* Convert bytes to words. */ + numWords = numBytes >> 2; + EFM_ASSERT(numWords > 0); + + /* The following loop splits the data into chunks corresponding to flash pages. + The address is loaded only once per page because the hardware automatically + increments the address internally for each data load inside a page. */ + for (wordCount = 0, pData = (uint32_t *)data; wordCount < numWords; ) { + /* First, the address is loaded. The address is auto-incremented within a page. + Therefore, the address phase is only needed once for each page. */ + retval = MSC_LoadVerifyAddress(address + wordCount); + if (mscReturnOk != retval) { + return retval; + } + /* Compute the number of words to write to the current page. */ + pageWords = + (FLASH_PAGE_SIZE + - (((uint32_t) (address + wordCount)) & (FLASH_PAGE_SIZE - 1))) + / sizeof(uint32_t); + if (pageWords > numWords - wordCount) { + pageWords = numWords - wordCount; + } + /* Write the data in the current page. */ + retval = MSC_LoadWriteData(pData, pageWords, writeStrategy); + if (mscReturnOk != retval) { + break; + } + wordCount += pageWords; + pData += pageWords; + } + +#if defined(ERRATA_FIX_FLASH_E201_EN) + /* Fix for errata FLASH_E201 - Potential program failure after Power On. + * + * Check if the first word was programmed correctly. If a failure is detected, + * retry programming of the first word. + * + * A full description of the errata is in the errata document. */ + pData = (uint32_t *) data; + if (*address != *pData) { + retval = MSC_LoadVerifyAddress(address); + if (mscReturnOk == retval) { + retval = MSC_LoadWriteData(pData, 1, writeStrategy); + } + } +#endif + + /* Disable writing to the MSC module. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + +#if defined(_MSC_WRITECTRL_WDOUBLE_MASK) +#if (WORDS_PER_DATA_PHASE == 2) + /* Turn off the double word write cycle support. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WDOUBLE; +#endif +#endif + + return retval; +} +MSC_RAMFUNC_DEFINITION_END + +/** @endcond */ + +/***************************************************************************//** + * @brief + * Erases a page in flash memory. + * @note + * It is recommended to run this code from RAM. On the Gecko family, it is required + * to run this function from RAM. + * + * For IAR IDE, Rowley IDE, SimplicityStudio IDE, Atollic IDE, and ARM GCC IDE, this is + * achieved automatically by using attributes in the function proctype. For Keil + * uVision IDE, define a section called "ram_code" and place this manually in + * the project's scatter file. + * + * @param[in] startAddress + * A pointer to the flash page to erase. Must be aligned to the beginning of the page + * boundary. + * @return + * Returns the status of erase operation, #MSC_Status_TypeDef + * @verbatim + * mscReturnOk - The operation completed successfully. + * mscReturnInvalidAddr - The operation tried to erase a non-flash area. + * mscReturnLocked - The operation tried to erase a locked area of the flash. + * mscReturnTimeOut - The operation timed out waiting for the flash operation + * to complete. + * @endverbatim + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_ErasePage(uint32_t *startAddress) +{ + uint32_t timeOut = MSC_PROGRAM_TIMEOUT; + + /* An address must be aligned to pages. */ + EFM_ASSERT((((uint32_t) startAddress) & (FLASH_PAGE_SIZE - 1)) == 0); +#if defined(_EMU_STATUS_VSCALE_MASK) + /* VSCALE must be done and flash erase requires VSCALE2. */ + EFM_ASSERT(!(EMU->STATUS & _EMU_STATUS_VSCALEBUSY_MASK)); + EFM_ASSERT((EMU->STATUS & _EMU_STATUS_VSCALE_MASK) == EMU_STATUS_VSCALE_VSCALE2); +#endif + + /* Enable writing to the MSC module. */ + MSC->WRITECTRL |= MSC_WRITECTRL_WREN; + + /* Load an address. */ + MSC->ADDRB = (uint32_t)startAddress; + MSC->WRITECMD = MSC_WRITECMD_LADDRIM; + + /* Check for an invalid address. */ + if (MSC->STATUS & MSC_STATUS_INVADDR) { + /* Disable writing to the MSC */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + return mscReturnInvalidAddr; + } + /* Check for write protected page. */ + if (MSC->STATUS & MSC_STATUS_LOCKED) { + /* Disable writing to the MSC module. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + return mscReturnLocked; + } + /* Send erase page command. */ + MSC->WRITECMD = MSC_WRITECMD_ERASEPAGE; + + /* Wait for the erase to complete. */ + while ((MSC->STATUS & MSC_STATUS_BUSY) && (timeOut != 0)) { + timeOut--; + } + if (timeOut == 0) { + /* Disable writing to the MSC module. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + return mscReturnTimeOut; + } + /* Disable writing to the MSC module. */ + MSC->WRITECTRL &= ~MSC_WRITECTRL_WREN; + return mscReturnOk; +} +MSC_RAMFUNC_DEFINITION_END + +/***************************************************************************//** + * @brief + * Writes data to flash memory. This function is interrupt-safe, but slower than + * MSC_WriteWordFast(), which writes to flash with interrupts disabled. + * Write data must be aligned to words and contain a number of bytes that is + * divisible by four. + * @note + * It is recommended to erase the flash page before performing a write. + * + * It is recommended to run this code from RAM. On the Gecko family, it is required + * to run this function from RAM. + * + * For IAR IDE, Rowley IDE, SimplicityStudio IDE, Atollic IDE, and ARM GCC IDE, + * this is done automatically by using attributes in the function proctype. + * For Keil uVision IDE, define a section called "ram_code" and place it + * manually in the project's scatter file. + * + * This function requires a system core clock at 1 MHz or higher. + * + * @param[in] address + * A pointer to the flash word to write to. Must be aligned to words. + * @param[in] data + * Data to write to flash. + * @param[in] numBytes + * A number of bytes to write from flash. NB: Must be divisible by four. + * @return + * Returns the status of the write operation. + * @verbatim + * flashReturnOk - The operation completed successfully. + * flashReturnInvalidAddr - The operation tried to erase a non-flash area. + * flashReturnLocked - The operation tried to erase a locked area of the Flash. + * flashReturnTimeOut - The operation timed out waiting for the flash operation + * to complete, or the MSC module timed out waiting for the software to write + * the next word into the DWORD register. + * @endverbatim + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_WriteWord(uint32_t *address, + void const *data, + uint32_t numBytes) +{ + return MSC_WriteWordI(address, data, numBytes, mscWriteIntSafe); +} +MSC_RAMFUNC_DEFINITION_END + +#if !defined(_EFM32_GECKO_FAMILY) +/***************************************************************************//** + * @brief + * Writes data to flash memory. This function is faster than MSC_WriteWord(), + * but it disables interrupts. Write data must be aligned to words and contain + * a number of bytes that is divisible by four. + * @note + * It is recommended to erase the flash page before performing a write. + * It is required to run this function from RAM on parts that include a + * flash write buffer. + * + * For IAR IDE, Rowley IDE, SimplicityStudio IDE, Atollic IDE, and ARM GCC IDE, + * this is done automatically by using attributes in the function proctype. + * For Keil uVision IDE, define a section called "ram_code" and place this manually + * in the project's scatter file. + * + * @param[in] address + * A pointer to the flash word to write to. Must be aligned to words. + * @param[in] data + * Data to write to flash. + * @param[in] numBytes + * A number of bytes to write from the Flash. NB: Must be divisible by four. + * @return + * Returns the status of the write operation. + * @verbatim + * flashReturnOk - The operation completed successfully. + * flashReturnInvalidAddr - The operation tried to erase a non-flash area. + * flashReturnLocked - The operation tried to erase a locked area of the flash. + * flashReturnTimeOut - The operation timed out waiting for flash operation + * to complete. Or the MSC timed out waiting for the software to write + * the next word into the DWORD register. + * @endverbatim + ******************************************************************************/ +#if !defined (EM_MSC_RUN_FROM_FLASH) || (_SILICON_LABS_GECKO_INTERNAL_SDID < 84) +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_WriteWordFast(uint32_t *address, + void const *data, + uint32_t numBytes) +{ + return MSC_WriteWordI(address, data, numBytes, mscWriteFast); +} +MSC_RAMFUNC_DEFINITION_END + +#endif +#endif + +#if defined(_MSC_MASSLOCK_MASK) +/***************************************************************************//** + * @brief + * Erase the entire Flash in one operation. + * + * @note + * This command will erase the entire contents of the device. + * Use with care, both a debug session and all contents of the flash will be + * lost. The lock bit, MLW will prevent this operation from executing and + * might prevent a successful mass erase. + ******************************************************************************/ +MSC_RAMFUNC_DEFINITION_BEGIN +MSC_Status_TypeDef MSC_MassErase(void) +{ + /* Enable writing to the MSC module. */ + MSC->WRITECTRL |= MSC_WRITECTRL_WREN; + + /* Unlock the device mass erase. */ + MSC->MASSLOCK = MSC_MASSLOCK_LOCKKEY_UNLOCK; + + /* Erase the first 512 K block. */ + MSC->WRITECMD = MSC_WRITECMD_ERASEMAIN0; + + /* Waiting for erase to complete. */ + while ((MSC->STATUS & MSC_STATUS_BUSY) != 0U) { + } + +#if ((FLASH_SIZE >= (512 * 1024)) && defined(_MSC_WRITECMD_ERASEMAIN1_MASK)) + /* Erase the second 512 K block. */ + MSC->WRITECMD = MSC_WRITECMD_ERASEMAIN1; + + /* Waiting for erase to complete. */ + while ((MSC->STATUS & MSC_STATUS_BUSY) != 0U) { + } +#endif + + /* Restore the mass erase lock. */ + MSC->MASSLOCK = MSC_MASSLOCK_LOCKKEY_LOCK; + + /* This will only successfully return if calling function is also in SRAM. */ + return mscReturnOk; +} +MSC_RAMFUNC_DEFINITION_END + +#endif + +/** @} (end addtogroup MSC) */ +/** @} (end addtogroup emlib) */ +#endif /* defined(MSC_COUNT) && (MSC_COUNT > 0) */ diff --git a/efm32boot/emlib/em_system.c b/efm32boot/emlib/em_system.c new file mode 100644 index 0000000..6877ac4 --- /dev/null +++ b/efm32boot/emlib/em_system.c @@ -0,0 +1,114 @@ +/***************************************************************************//** + * @file em_system.c + * @brief System Peripheral API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 "em_system.h" +#include "em_assert.h" +#include + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup SYSTEM + * @{ + ******************************************************************************/ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Get chip major/minor revision. + * + * @param[out] rev + * Location to place chip revision info. + ******************************************************************************/ +void SYSTEM_ChipRevisionGet(SYSTEM_ChipRevision_TypeDef *rev) +{ + uint8_t tmp; + + EFM_ASSERT(rev); + + /* CHIP FAMILY bit [5:2] */ + tmp = (((ROMTABLE->PID1 & _ROMTABLE_PID1_FAMILYMSB_MASK) >> _ROMTABLE_PID1_FAMILYMSB_SHIFT) << 2); + /* CHIP FAMILY bit [1:0] */ + tmp |= ((ROMTABLE->PID0 & _ROMTABLE_PID0_FAMILYLSB_MASK) >> _ROMTABLE_PID0_FAMILYLSB_SHIFT); + rev->family = tmp; + + /* CHIP MAJOR bit [3:0] */ + rev->major = (ROMTABLE->PID0 & _ROMTABLE_PID0_REVMAJOR_MASK) >> _ROMTABLE_PID0_REVMAJOR_SHIFT; + + /* CHIP MINOR bit [7:4] */ + tmp = (((ROMTABLE->PID2 & _ROMTABLE_PID2_REVMINORMSB_MASK) >> _ROMTABLE_PID2_REVMINORMSB_SHIFT) << 4); + /* CHIP MINOR bit [3:0] */ + tmp |= ((ROMTABLE->PID3 & _ROMTABLE_PID3_REVMINORLSB_MASK) >> _ROMTABLE_PID3_REVMINORLSB_SHIFT); + rev->minor = tmp; +} + +/***************************************************************************//** + * @brief + * Get factory calibration value for a given peripheral register. + * + * @param[in] regAddress + * Peripheral calibration register address to get calibration value for. If + * a calibration value is found then this register is updated with the + * calibration value. + * + * @return + * True if a calibration value exists, false otherwise. + ******************************************************************************/ +bool SYSTEM_GetCalibrationValue(volatile uint32_t *regAddress) +{ + SYSTEM_CalAddrVal_TypeDef * p, * end; + + p = (SYSTEM_CalAddrVal_TypeDef *)(DEVINFO_BASE & 0xFFFFF000); + end = (SYSTEM_CalAddrVal_TypeDef *)DEVINFO_BASE; + + for (; p < end; p++) { + if (p->address == 0xFFFFFFFF) { + /* Found table terminator */ + return false; + } + if (p->address == (uint32_t)regAddress) { + *regAddress = p->calValue; + return true; + } + } + /* Nothing found for regAddress */ + return false; +} + +/** @} (end addtogroup SYSTEM) */ +/** @} (end addtogroup emlib) */ diff --git a/efm32boot/emlib/em_usart.c b/efm32boot/emlib/em_usart.c new file mode 100644 index 0000000..3a5e65c --- /dev/null +++ b/efm32boot/emlib/em_usart.c @@ -0,0 +1,1161 @@ +/***************************************************************************//** + * @file em_usart.c + * @brief Universal synchronous/asynchronous receiver/transmitter (USART/UART) + * Peripheral API + * @version 5.2.2 + ******************************************************************************* + * # License + * Copyright 2016 Silicon Laboratories, Inc. http://www.silabs.com + ******************************************************************************* + * + * 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 "em_usart.h" +#if defined(USART_COUNT) && (USART_COUNT > 0) + +#include "em_cmu.h" +#include "em_bus.h" +#include "em_assert.h" + +/***************************************************************************//** + * @addtogroup emlib + * @{ + ******************************************************************************/ + +/***************************************************************************//** + * @addtogroup USART + * @{ + ******************************************************************************/ + +/******************************************************************************* + ******************************* DEFINES *********************************** + ******************************************************************************/ + +/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */ + +/** Validation of USART register block pointer reference for assert statements. */ +#if (USART_COUNT == 1) && defined(USART0) +#define USART_REF_VALID(ref) ((ref) == USART0) + +#elif (USART_COUNT == 1) && defined(USART1) +#define USART_REF_VALID(ref) ((ref) == USART1) + +#elif (USART_COUNT == 2) && defined(USART2) +#define USART_REF_VALID(ref) (((ref) == USART1) || ((ref) == USART2)) + +#elif (USART_COUNT == 2) +#define USART_REF_VALID(ref) (((ref) == USART0) || ((ref) == USART1)) + +#elif (USART_COUNT == 3) +#define USART_REF_VALID(ref) (((ref) == USART0) || ((ref) == USART1) \ + || ((ref) == USART2)) +#elif (USART_COUNT == 4) +#define USART_REF_VALID(ref) (((ref) == USART0) || ((ref) == USART1) \ + || ((ref) == USART2) || ((ref) == USART3)) +#elif (USART_COUNT == 5) +#define USART_REF_VALID(ref) (((ref) == USART0) || ((ref) == USART1) \ + || ((ref) == USART2) || ((ref) == USART3) \ + || ((ref) == USART4)) +#elif (USART_COUNT == 6) +#define USART_REF_VALID(ref) (((ref) == USART0) || ((ref) == USART1) \ + || ((ref) == USART2) || ((ref) == USART3) \ + || ((ref) == USART4) || ((ref) == USART5)) +#else +#error "Undefined number of USARTs." +#endif + +#if defined(USARTRF_COUNT) && (USARTRF_COUNT > 0) +#if (USARTRF_COUNT == 1) && defined(USARTRF0) +#define USARTRF_REF_VALID(ref) ((ref) == USARTRF0) +#elif (USARTRF_COUNT == 1) && defined(USARTRF1) +#define USARTRF_REF_VALID(ref) ((ref) == USARTRF1) +#else +#define USARTRF_REF_VALID(ref) (0) +#endif +#else +#define USARTRF_REF_VALID(ref) (0) +#endif + +#if defined(_EZR32_HAPPY_FAMILY) +#define USART_IRDA_VALID(ref) ((ref) == USART0) +#elif defined(_EFM32_HAPPY_FAMILY) +#define USART_IRDA_VALID(ref) (((ref) == USART0) || ((ref) == USART1)) +#elif defined(USART0) +#define USART_IRDA_VALID(ref) ((ref) == USART0) +#elif (USART_COUNT == 1) && defined(USART1) +#define USART_IRDA_VALID(ref) ((ref) == USART1) +#elif defined(USARTRF0) +#define USART_IRDA_VALID(ref) ((ref) == USARTRF0) +#else +#define USART_IRDA_VALID(ref) (0) +#endif + +#if defined(_SILICON_LABS_32B_SERIES_1) + #if defined(USART3) + #define USART_I2S_VALID(ref) (((ref) == USART1) || ((ref) == USART3)) + #else + #define USART_I2S_VALID(ref) ((ref) == USART1) + #endif +#elif defined(_SILICON_LABS_32B_SERIES_0) + #if defined(_EZR32_HAPPY_FAMILY) + #define USART_I2S_VALID(ref) ((ref) == USART0) + #elif defined(_EFM32_HAPPY_FAMILY) + #define USART_I2S_VALID(ref) (((ref) == USART0) || ((ref) == USART1)) + #elif defined(_EFM32_TINY_FAMILY) || defined(_EFM32_ZERO_FAMILY) + #define USART_I2S_VALID(ref) ((ref) == USART1) + #elif defined(_EFM32_GIANT_FAMILY) || defined(_EFM32_WONDER_FAMILY) + #define USART_I2S_VALID(ref) (((ref) == USART1) || ((ref) == USART2)) +#endif +#endif + +#if (UART_COUNT == 1) +#define UART_REF_VALID(ref) ((ref) == UART0) +#elif (UART_COUNT == 2) +#define UART_REF_VALID(ref) (((ref) == UART0) || ((ref) == UART1)) +#else +#define UART_REF_VALID(ref) (0) +#endif + +#if defined(_USART_CLKDIV_DIVEXT_MASK) +#define CLKDIV_MASK (_USART_CLKDIV_DIV_MASK | _USART_CLKDIV_DIVEXT_MASK) +#else +#define CLKDIV_MASK _USART_CLKDIV_DIV_MASK +#endif + +/** @endcond */ + +/******************************************************************************* + ************************** GLOBAL FUNCTIONS ******************************* + ******************************************************************************/ + +/***************************************************************************//** + * @brief + * Configure USART/UART operating in asynchronous mode to use a given + * baudrate (or as close as possible to specified baudrate). + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] refFreq + * USART/UART reference clock frequency in Hz that will be used. If set to 0, + * the currently configured reference clock is assumed. + * + * @param[in] baudrate + * Baudrate to try to achieve for USART/UART. + * + * @param[in] ovs + * Oversampling to be used. Normal is 16x oversampling, but lower oversampling + * may be used to achieve higher rates or better baudrate accuracy in some + * cases. Notice that lower oversampling frequency makes channel more + * vulnerable to bit faults during reception due to clock inaccuracies + * compared to link partner. + ******************************************************************************/ +void USART_BaudrateAsyncSet(USART_TypeDef *usart, + uint32_t refFreq, + uint32_t baudrate, + USART_OVS_TypeDef ovs) +{ + uint32_t clkdiv; + uint32_t oversample; + + /* Inhibit divide by 0 */ + EFM_ASSERT(baudrate); + + /* + * We want to 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 * (fHFPERCLK/(oversample * br) - 1) + * or + * CLKDIV = (256 * fHFPERCLK)/(oversample * br) - 256 + * + * The basic problem with integer division in the above formula is that + * the dividend (256 * fHFPERCLK) may become higher than max 32 bit + * integer. Yet, we want to evaluate dividend first before dividing in + * order to get as small rounding effects as possible. We do not want + * to make too harsh restrictions on max fHFPERCLK value either. + * + * One can possibly factorize 256 and oversample/br. However, + * since the last 6 or 3 bits of CLKDIV are don't care, we can base our + * integer arithmetic on the below formula + * + * CLKDIV / 64 = (4 * fHFPERCLK)/(oversample * br) - 4 (3 bits dont care) + * or + * CLKDIV / 8 = (32 * fHFPERCLK)/(oversample * br) - 32 (6 bits dont care) + * + * and calculate 1/64 of CLKDIV first. This allows for fHFPERCLK + * up to 1GHz without overflowing a 32 bit value! + */ + + /* HFPERCLK used to clock all USART/UART peripheral modules */ + if (!refFreq) { + refFreq = CMU_ClockFreqGet(cmuClock_HFPER); + } + + /* Map oversampling */ + switch (ovs) { + case usartOVS16: + EFM_ASSERT(baudrate <= (refFreq / 16)); + oversample = 16; + break; + + case usartOVS8: + EFM_ASSERT(baudrate <= (refFreq / 8)); + oversample = 8; + break; + + case usartOVS6: + EFM_ASSERT(baudrate <= (refFreq / 6)); + oversample = 6; + break; + + case usartOVS4: + EFM_ASSERT(baudrate <= (refFreq / 4)); + oversample = 4; + break; + + default: + /* Invalid input */ + EFM_ASSERT(0); + return; + } + + /* Calculate and set CLKDIV with fractional bits. + * The added (oversample*baudrate)/2 in the first line is to round the + * divisor to the nearest fractional divisor. */ +#if defined(_SILICON_LABS_32B_SERIES_0) && !defined(_EFM32_HAPPY_FAMILY) + /* Devices with 2 fractional bits. CLKDIV[7:6] */ + clkdiv = 4 * refFreq + (oversample * baudrate) / 2; + clkdiv /= (oversample * baudrate); + clkdiv -= 4; + clkdiv *= 64; +#else + /* Devices with 5 fractional bits. CLKDIV[7:3] */ + clkdiv = 32 * refFreq + (oversample * baudrate) / 2; + clkdiv /= (oversample * baudrate); + clkdiv -= 32; + clkdiv *= 8; +#endif + + /* Verify that resulting clock divider is within limits */ + EFM_ASSERT(clkdiv <= CLKDIV_MASK); + + /* Make sure we don't write to reserved bits */ + clkdiv &= CLKDIV_MASK; + + usart->CTRL &= ~_USART_CTRL_OVS_MASK; + usart->CTRL |= ovs; + usart->CLKDIV = clkdiv; +} + +/***************************************************************************//** + * @brief + * Calculate baudrate for USART/UART given reference frequency, clock division + * and oversampling rate (if async mode). + * + * @details + * This function returns the baudrate that a USART/UART module will use if + * configured with the given frequency, clock divisor and mode. Notice that + * this function will not use actual HW configuration. It can be used + * to determinate if a given configuration is sufficiently accurate for the + * application. + * + * @param[in] refFreq + * USART/UART HF peripheral frequency used. + * + * @param[in] clkdiv + * Clock division factor to be used. + * + * @param[in] syncmode + * @li true - synchronous mode operation. + * @li false - asynchronous mode operation. + * + * @param[in] ovs + * Oversampling used if asynchronous mode. Not used if @p syncmode is true. + * + * @return + * Baudrate with given settings. + ******************************************************************************/ +uint32_t USART_BaudrateCalc(uint32_t refFreq, + uint32_t clkdiv, + bool syncmode, + USART_OVS_TypeDef ovs) +{ + uint32_t oversample; + uint64_t divisor; + uint64_t factor; + uint64_t remainder; + uint64_t quotient; + uint32_t br; + + /* Out of bound clkdiv ? */ + EFM_ASSERT(clkdiv <= CLKDIV_MASK); + + /* Mask out unused bits */ + clkdiv &= CLKDIV_MASK; + + /* We want to use integer division to avoid forcing in float division */ + /* utils, and yet keep rounding effect errors to a minimum. */ + + /* Baudrate calculation depends on if synchronous or asynchronous mode */ + if (syncmode) { + /* + * Baudrate is given by: + * + * br = fHFPERCLK/(2 * (1 + (CLKDIV / 256))) + * + * which can be rewritten to + * + * br = (128 * fHFPERCLK)/(256 + CLKDIV) + */ + oversample = 1; /* Not used in sync mode, ie 1 */ + factor = 128; + } else { + /* + * Baudrate in asynchronous mode is given by: + * + * br = fHFPERCLK/(oversample * (1 + (CLKDIV / 256))) + * + * which can be rewritten to + * + * br = (256 * fHFPERCLK)/(oversample * (256 + CLKDIV)) + * + * First of all we can reduce the 256 factor of the dividend with + * (part of) oversample part of the divisor. + */ + + switch (ovs) { + case usartOVS16: + oversample = 1; + factor = 256 / 16; + break; + + case usartOVS8: + oversample = 1; + factor = 256 / 8; + break; + + case usartOVS6: + oversample = 3; + factor = 256 / 2; + break; + + default: + oversample = 1; + factor = 256 / 4; + break; + } + } + + /* + * The basic problem with integer division in the above formula is that + * the dividend (factor * fHFPERCLK) may become larger than a 32 bit + * integer. Yet we want to evaluate dividend first before dividing in + * order to get as small rounding effects as possible. We do not want + * to make too harsh restrictions on max fHFPERCLK value either. + * + * For division a/b, we can write + * + * a = qb + r + * + * where q is the quotient and r is the remainder, both integers. + * + * The orignal baudrate formula can be rewritten as + * + * br = xa / b = x(qb + r)/b = xq + xr/b + * + * where x is 'factor', a is 'refFreq' and b is 'divisor', referring to + * variable names. + */ + + /* + * Divisor will never exceed max 32 bit value since + * clkdiv <= _USART_CLKDIV_DIV_MASK (currently 0x1FFFC0 or 0x7FFFF8) + * and 'oversample' has been reduced to <= 3. + */ + divisor = oversample * (256 + clkdiv); + + quotient = refFreq / divisor; + remainder = refFreq % divisor; + + /* factor <= 128 and since divisor >= 256, the below cannot exceed max */ + /* 32 bit value. However, factor * remainder can become larger than 32-bit */ + /* because of the size of _USART_CLKDIV_DIV_MASK on some families. */ + br = (uint32_t)(factor * quotient); + + /* + * factor <= 128 and remainder < (oversample*(256 + clkdiv)), which + * means dividend (factor * remainder) worst case is + * 128 * (3 * (256 + _USART_CLKDIV_DIV_MASK)) = 0x1_8001_7400. + */ + br += (uint32_t)((factor * remainder) / divisor); + + return br; +} + +/***************************************************************************//** + * @brief + * Get current baudrate for USART/UART. + * + * @details + * This function returns the actual baudrate (not considering oscillator + * inaccuracies) used by a USART/UART peripheral. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @return + * Current baudrate. + ******************************************************************************/ +uint32_t USART_BaudrateGet(USART_TypeDef *usart) +{ + uint32_t freq; + USART_OVS_TypeDef ovs; + bool syncmode; + + if (usart->CTRL & USART_CTRL_SYNC) { + syncmode = true; + } else { + syncmode = false; + } + + /* HFPERCLK used to clock all USART/UART peripheral modules */ + freq = CMU_ClockFreqGet(cmuClock_HFPER); + ovs = (USART_OVS_TypeDef)(usart->CTRL & _USART_CTRL_OVS_MASK); + return USART_BaudrateCalc(freq, usart->CLKDIV, syncmode, ovs); +} + +/***************************************************************************//** + * @brief + * Configure USART operating in synchronous mode to use a given baudrate + * (or as close as possible to specified baudrate). + * + * @details + * The configuration will be set to use a baudrate <= the specified baudrate + * in order to ensure that the baudrate does not exceed the specified value. + * + * Fractional clock division is suppressed, although the HW design allows it. + * It could cause half clock cycles to exceed specified limit, and thus + * potentially violate specifications for the slave device. In some special + * situations fractional clock division may be useful even in synchronous + * mode, but in those cases it must be directly adjusted, possibly assisted + * by USART_BaudrateCalc(): + * + * @param[in] usart + * Pointer to USART peripheral register block. (Cannot be used on UART + * modules.) + * + * @param[in] refFreq + * USART reference clock frequency in Hz that will be used. If set to 0, + * the currently configured reference clock is assumed. + * + * @param[in] baudrate + * Baudrate to try to achieve for USART. + ******************************************************************************/ +void USART_BaudrateSyncSet(USART_TypeDef *usart, uint32_t refFreq, uint32_t baudrate) +{ + uint32_t clkdiv; + + /* Inhibit divide by 0 */ + EFM_ASSERT(baudrate); + + /* + * CLKDIV in synchronous mode is given by: + * + * CLKDIV = 256 * (fHFPERCLK/(2 * br) - 1) + */ + + /* HFPERCLK used to clock all USART/UART peripheral modules */ + if (!refFreq) { + refFreq = CMU_ClockFreqGet(cmuClock_HFPER); + } + + clkdiv = (refFreq - 1) / (2 * baudrate); + clkdiv = clkdiv << 8; + + /* Verify that resulting clock divider is within limits */ + EFM_ASSERT(!(clkdiv & ~CLKDIV_MASK)); + + usart->CLKDIV = clkdiv; +} + +/***************************************************************************//** + * @brief + * Enable/disable USART/UART receiver and/or transmitter. + * + * @details + * Notice that this function does not do any configuration. Enabling should + * normally be done after initialization is done (if not enabled as part + * of init). + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] enable + * Select status for receiver/transmitter. + ******************************************************************************/ +void USART_Enable(USART_TypeDef *usart, USART_Enable_TypeDef enable) +{ + uint32_t tmp; + + /* Make sure the module exists on the selected chip */ + EFM_ASSERT(USART_REF_VALID(usart) + || USARTRF_REF_VALID(usart) + || UART_REF_VALID(usart) ); + + /* Disable as specified */ + tmp = ~((uint32_t) (enable)); + tmp &= _USART_CMD_RXEN_MASK | _USART_CMD_TXEN_MASK; + usart->CMD = tmp << 1; + + /* Enable as specified */ + usart->CMD = (uint32_t) (enable); +} + +/***************************************************************************//** + * @brief + * Init USART/UART for normal asynchronous mode. + * + * @details + * This function will configure basic settings in order to operate in normal + * asynchronous mode. + * + * Special control setup not covered by this function must be done after + * using this function by direct modification of the CTRL register. + * + * Notice that pins used by the USART/UART module must be properly configured + * by the user explicitly, in order for the USART/UART to work as intended. + * (When configuring pins, one should remember to consider the sequence of + * configuration, in order to avoid unintended pulses/glitches on output + * pins.) + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] init + * Pointer to initialization structure used to configure basic async setup. + ******************************************************************************/ +void USART_InitAsync(USART_TypeDef *usart, const USART_InitAsync_TypeDef *init) +{ + /* Make sure the module exists on the selected chip */ + EFM_ASSERT(USART_REF_VALID(usart) + || USARTRF_REF_VALID(usart) + || UART_REF_VALID(usart) ); + + /* Init USART registers to HW reset state. */ + USART_Reset(usart); + +#if defined(USART_INPUT_RXPRS) && defined(USART_CTRL_MVDIS) + /* Disable majority vote if specified. */ + if (init->mvdis) { + usart->CTRL |= USART_CTRL_MVDIS; + } + + /* Configure PRS input mode. */ + if (init->prsRxEnable) { + usart->INPUT = (uint32_t) init->prsRxCh | USART_INPUT_RXPRS; + } +#endif + + /* Configure databits, stopbits and parity */ + usart->FRAME = (uint32_t)init->databits + | (uint32_t)init->stopbits + | (uint32_t)init->parity; + + /* Configure baudrate */ + USART_BaudrateAsyncSet(usart, init->refFreq, init->baudrate, init->oversampling); + +#if defined(_USART_TIMING_CSHOLD_MASK) + usart->TIMING = ((init->autoCsHold << _USART_TIMING_CSHOLD_SHIFT) + & _USART_TIMING_CSHOLD_MASK) + | ((init->autoCsSetup << _USART_TIMING_CSSETUP_SHIFT) + & _USART_TIMING_CSSETUP_MASK); + if (init->autoCsEnable) { + usart->CTRL |= USART_CTRL_AUTOCS; + } +#endif + /* Finally enable (as specified) */ + usart->CMD = (uint32_t)init->enable; +} + +/***************************************************************************//** + * @brief + * Init USART for synchronous mode. + * + * @details + * This function will configure basic settings in order to operate in + * synchronous mode. + * + * Special control setup not covered by this function must be done after + * using this function by direct modification of the CTRL register. + * + * Notice that pins used by the USART module must be properly configured + * by the user explicitly, in order for the USART to work as intended. + * (When configuring pins, one should remember to consider the sequence of + * configuration, in order to avoid unintended pulses/glitches on output + * pins.) + * + * @param[in] usart + * Pointer to USART peripheral register block. (UART does not support this + * mode.) + * + * @param[in] init + * Pointer to initialization structure used to configure basic async setup. + ******************************************************************************/ +void USART_InitSync(USART_TypeDef *usart, const USART_InitSync_TypeDef *init) +{ + /* Make sure the module exists on the selected chip */ + EFM_ASSERT(USART_REF_VALID(usart) || USARTRF_REF_VALID(usart) ); + + /* Init USART registers to HW reset state. */ + USART_Reset(usart); + + /* Set bits for synchronous mode */ + usart->CTRL |= (USART_CTRL_SYNC) + | (uint32_t)init->clockMode + | (init->msbf ? USART_CTRL_MSBF : 0); + +#if defined(_USART_CTRL_AUTOTX_MASK) + usart->CTRL |= init->autoTx ? USART_CTRL_AUTOTX : 0; +#endif + +#if defined(_USART_INPUT_RXPRS_MASK) + /* Configure PRS input mode. */ + if (init->prsRxEnable) { + usart->INPUT = (uint32_t)init->prsRxCh | USART_INPUT_RXPRS; + } +#endif + + /* Configure databits, leave stopbits and parity at reset default (not used) */ + usart->FRAME = (uint32_t)init->databits + | USART_FRAME_STOPBITS_DEFAULT + | USART_FRAME_PARITY_DEFAULT; + + /* Configure baudrate */ + USART_BaudrateSyncSet(usart, init->refFreq, init->baudrate); + + /* Finally enable (as specified) */ + if (init->master) { + usart->CMD = USART_CMD_MASTEREN; + } + +#if defined(_USART_TIMING_CSHOLD_MASK) + usart->TIMING = ((init->autoCsHold << _USART_TIMING_CSHOLD_SHIFT) + & _USART_TIMING_CSHOLD_MASK) + | ((init->autoCsSetup << _USART_TIMING_CSSETUP_SHIFT) + & _USART_TIMING_CSSETUP_MASK); + if (init->autoCsEnable) { + usart->CTRL |= USART_CTRL_AUTOCS; + } +#endif + + usart->CMD = (uint32_t)init->enable; +} + +/***************************************************************************//** + * @brief + * Init USART for asynchronous IrDA mode. + * + * @details + * This function will configure basic settings in order to operate in + * asynchronous IrDA mode. + * + * Special control setup not covered by this function must be done after + * using this function by direct modification of the CTRL and IRCTRL + * registers. + * + * Notice that pins used by the USART/UART module must be properly configured + * by the user explicitly, in order for the USART/UART to work as intended. + * (When configuring pins, one should remember to consider the sequence of + * configuration, in order to avoid unintended pulses/glitches on output + * pins.) + * + * @param[in] usart + * Pointer to USART peripheral register block. + * + * @param[in] init + * Pointer to initialization structure used to configure async IrDA setup. + * + * @note + * Not all USART instances support IrDA. See the datasheet for your device. + * + ******************************************************************************/ +void USARTn_InitIrDA(USART_TypeDef *usart, const USART_InitIrDA_TypeDef *init) +{ + EFM_ASSERT(USART_IRDA_VALID(usart)); + + /* Init USART as async device */ + USART_InitAsync(usart, &(init->async)); + + /* Set IrDA modulation to RZI (return-to-zero-inverted) */ + usart->CTRL |= USART_CTRL_TXINV; + + /* Invert Rx signal before demodulator if enabled */ + if (init->irRxInv) { + usart->CTRL |= USART_CTRL_RXINV; + } + + /* Configure IrDA */ + usart->IRCTRL |= (uint32_t)init->irPw + | (uint32_t)init->irPrsSel + | ((uint32_t)init->irFilt << _USART_IRCTRL_IRFILT_SHIFT) + | ((uint32_t)init->irPrsEn << _USART_IRCTRL_IRPRSEN_SHIFT); + + /* Enable IrDA */ + usart->IRCTRL |= USART_IRCTRL_IREN; +} + +#if defined(_USART_I2SCTRL_MASK) +/***************************************************************************//** + * @brief + * Init USART for I2S mode. + * + * @details + * This function will configure basic settings in order to operate in I2S + * mode. + * + * Special control setup not covered by this function must be done after + * using this function by direct modification of the CTRL and I2SCTRL + * registers. + * + * Notice that pins used by the USART module must be properly configured + * by the user explicitly, in order for the USART to work as intended. + * (When configuring pins, one should remember to consider the sequence of + * configuration, in order to avoid unintended pulses/glitches on output + * pins.) + * + * @param[in] usart + * Pointer to USART peripheral register block. (UART does not support this + * mode.) + * + * @param[in] init + * Pointer to initialization structure used to configure basic I2S setup. + * + * @note + * This function does not apply to all USART's. Refer to chip manuals. + * + ******************************************************************************/ +void USART_InitI2s(USART_TypeDef *usart, USART_InitI2s_TypeDef *init) +{ + USART_Enable_TypeDef enable; + + /* Make sure the module exists on the selected chip */ + EFM_ASSERT(USART_I2S_VALID(usart)); + + /* Override the enable setting. */ + enable = init->sync.enable; + init->sync.enable = usartDisable; + + /* Init USART as a sync device. */ + USART_InitSync(usart, &init->sync); + + /* Configure and enable I2CCTRL register acording to selected mode. */ + usart->I2SCTRL = (uint32_t)init->format + | (uint32_t)init->justify + | (init->delay ? USART_I2SCTRL_DELAY : 0) + | (init->dmaSplit ? USART_I2SCTRL_DMASPLIT : 0) + | (init->mono ? USART_I2SCTRL_MONO : 0) + | USART_I2SCTRL_EN; + + if (enable != usartDisable) { + USART_Enable(usart, enable); + } +} +#endif + +/***************************************************************************//** + * @brief + * Initialize automatic transmissions using PRS channel as trigger + * @note + * Initialize USART with USART_Init() before setting up PRS configuration + * + * @param[in] usart Pointer to USART to configure + * @param[in] init Pointer to initialization structure + ******************************************************************************/ +void USART_InitPrsTrigger(USART_TypeDef *usart, const USART_PrsTriggerInit_TypeDef *init) +{ + uint32_t trigctrl; + + /* Clear values that will be reconfigured */ + trigctrl = usart->TRIGCTRL & ~(_USART_TRIGCTRL_RXTEN_MASK + | _USART_TRIGCTRL_TXTEN_MASK +#if defined(USART_TRIGCTRL_AUTOTXTEN) + | _USART_TRIGCTRL_AUTOTXTEN_MASK +#endif + | _USART_TRIGCTRL_TSEL_MASK); + +#if defined(USART_TRIGCTRL_AUTOTXTEN) + if (init->autoTxTriggerEnable) { + trigctrl |= USART_TRIGCTRL_AUTOTXTEN; + } +#endif + if (init->txTriggerEnable) { + trigctrl |= USART_TRIGCTRL_TXTEN; + } + if (init->rxTriggerEnable) { + trigctrl |= USART_TRIGCTRL_RXTEN; + } + trigctrl |= init->prsTriggerChannel; + + /* Enable new configuration */ + usart->TRIGCTRL = trigctrl; +} + +/***************************************************************************//** + * @brief + * Reset USART/UART to same state as after a HW reset. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + ******************************************************************************/ +void USART_Reset(USART_TypeDef *usart) +{ + /* Make sure the module exists on the selected chip */ + EFM_ASSERT(USART_REF_VALID(usart) + || USARTRF_REF_VALID(usart) + || UART_REF_VALID(usart) ); + + /* Make sure disabled first, before resetting other registers */ + usart->CMD = USART_CMD_RXDIS | USART_CMD_TXDIS | USART_CMD_MASTERDIS + | USART_CMD_RXBLOCKDIS | USART_CMD_TXTRIDIS | USART_CMD_CLEARTX + | USART_CMD_CLEARRX; + usart->CTRL = _USART_CTRL_RESETVALUE; + usart->FRAME = _USART_FRAME_RESETVALUE; + usart->TRIGCTRL = _USART_TRIGCTRL_RESETVALUE; + usart->CLKDIV = _USART_CLKDIV_RESETVALUE; + usart->IEN = _USART_IEN_RESETVALUE; + usart->IFC = _USART_IFC_MASK; +#if defined(_USART_ROUTEPEN_MASK) || defined(_UART_ROUTEPEN_MASK) + usart->ROUTEPEN = _USART_ROUTEPEN_RESETVALUE; + usart->ROUTELOC0 = _USART_ROUTELOC0_RESETVALUE; + usart->ROUTELOC1 = _USART_ROUTELOC1_RESETVALUE; +#else + usart->ROUTE = _USART_ROUTE_RESETVALUE; +#endif + + if (USART_IRDA_VALID(usart)) { + usart->IRCTRL = _USART_IRCTRL_RESETVALUE; + } + +#if defined(_USART_INPUT_RESETVALUE) + usart->INPUT = _USART_INPUT_RESETVALUE; +#endif + +#if defined(_USART_I2SCTRL_RESETVALUE) + if (USART_I2S_VALID(usart)) { + usart->I2SCTRL = _USART_I2SCTRL_RESETVALUE; + } +#endif +} + +/***************************************************************************//** + * @brief + * Receive one 4-8 bit frame, (or part of 10-16 bit frame). + * + * @details + * This function is normally used to receive one frame when operating with + * frame length 4-8 bits. Please refer to @ref USART_RxExt() for reception of + * 9 bit frames. + * + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if the buffer is empty, until data is received. + * Alternatively the user can explicitly check whether data is available, and + * if data is avaliable, call @ref USART_RxDataGet() to read the RXDATA + * register directly. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @return + * Data received. + ******************************************************************************/ +uint8_t USART_Rx(USART_TypeDef *usart) +{ + while (!(usart->STATUS & USART_STATUS_RXDATAV)) + ; + + return (uint8_t)usart->RXDATA; +} + +/***************************************************************************//** + * @brief + * Receive two 4-8 bit frames, or one 10-16 bit frame. + * + * @details + * This function is normally used to receive one frame when operating with + * frame length 10-16 bits. Please refer to @ref USART_RxDoubleExt() for + * reception of two 9 bit frames. + * + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is empty, until data is received. + * Alternatively the user can explicitly check whether data is available, and + * if data is avaliable, call @ref USART_RxDoubleGet() to read the RXDOUBLE + * register directly. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @return + * Data received. + ******************************************************************************/ +uint16_t USART_RxDouble(USART_TypeDef *usart) +{ + while (!(usart->STATUS & USART_STATUS_RXFULL)) + ; + + return (uint16_t)usart->RXDOUBLE; +} + +/***************************************************************************//** + * @brief + * Receive two 4-9 bit frames, or one 10-16 bit frame with extended + * information. + * + * @details + * This function is normally used to receive one frame when operating with + * frame length 10-16 bits and additional RX status information is required. + * + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is empty, until data is received. + * Alternatively the user can explicitly check whether data is available, and + * if data is avaliable, call @ref USART_RxDoubleXGet() to read the RXDOUBLEX + * register directly. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @return + * Data received. + ******************************************************************************/ +uint32_t USART_RxDoubleExt(USART_TypeDef *usart) +{ + while (!(usart->STATUS & USART_STATUS_RXFULL)) + ; + + return usart->RXDOUBLEX; +} + +/***************************************************************************//** + * @brief + * Receive one 4-9 bit frame, (or part of 10-16 bit frame) with extended + * information. + * + * @details + * This function is normally used to receive one frame when operating with + * frame length 4-9 bits and additional RX status information is required. + * + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is empty, until data is received. + * Alternatively the user can explicitly check whether data is available, and + * if data is avaliable, call @ref USART_RxDataXGet() to read the RXDATAX + * register directly. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @return + * Data received. + ******************************************************************************/ +uint16_t USART_RxExt(USART_TypeDef *usart) +{ + while (!(usart->STATUS & USART_STATUS_RXDATAV)) + ; + + return (uint16_t)usart->RXDATAX; +} + +/***************************************************************************//** + * @brief + * Perform one 8 bit frame SPI transfer. + * + * @note + * This function will stall if the transmit buffer is full. When a transmit + * buffer becomes available, data is written and the function will wait until + * the data is fully transmitted. The SPI return value is then read out and + * returned. + * + * @param[in] usart + * Pointer to USART peripheral register block. + * + * @param[in] data + * Data to transmit. + * + * @return + * Data received. + ******************************************************************************/ +uint8_t USART_SpiTransfer(USART_TypeDef *usart, uint8_t data) +{ + while (!(usart->STATUS & USART_STATUS_TXBL)) + ; + usart->TXDATA = (uint32_t)data; + while (!(usart->STATUS & USART_STATUS_TXC)) + ; + return (uint8_t)usart->RXDATA; +} + +/***************************************************************************//** + * @brief + * Transmit one 4-9 bit frame. + * + * @details + * Depending on frame length configuration, 4-8 (least significant) bits from + * @p data are transmitted. If frame length is 9, 8 bits are transmitted from + * @p data and one bit as specified by CTRL register, BIT8DV field. Please + * refer to USART_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 part of specified frame bit length. + * + * @note + * This function will stall if buffer is full, until buffer becomes available. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] data + * Data to transmit. See details above for further info. + ******************************************************************************/ +void USART_Tx(USART_TypeDef *usart, uint8_t data) +{ + /* Check that transmit buffer is empty */ + while (!(usart->STATUS & USART_STATUS_TXBL)) + ; + usart->TXDATA = (uint32_t)data; +} + +/***************************************************************************//** + * @brief + * Transmit two 4-9 bit frames, or one 10-16 bit frame. + * + * @details + * Depending on frame length configuration, 4-8 (least significant) bits from + * each byte in @p data are transmitted. If frame length is 9, 8 bits are + * transmitted from each byte in @p data adding one bit as specified by CTRL + * register, BIT8DV field, to each byte. Please refer to USART_TxDoubleExt() + * for transmitting two 9 bit frames with full control of all 9 bits. + * + * If frame length is 10-16, 10-16 (least significant) bits from @p data + * are transmitted. + * + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is full, until buffer becomes available. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] data + * Data to transmit, the least significant byte holds the frame transmitted + * first. See details above for further info. + ******************************************************************************/ +void USART_TxDouble(USART_TypeDef *usart, uint16_t data) +{ + /* Check that transmit buffer is empty */ + while (!(usart->STATUS & USART_STATUS_TXBL)) + ; + usart->TXDOUBLE = (uint32_t)data; +} + +/***************************************************************************//** + * @brief + * Transmit two 4-9 bit frames, or one 10-16 bit frame with extended control. + * + * @details + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is full, until buffer becomes available. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] data + * Data to transmit with extended control. Contains two 16 bit words + * concatenated. Least significant word holds frame transitted first. If frame + * length is 4-9, two frames with 4-9 least significant bits from each 16 bit + * word are transmitted. + * @par + * If frame length is 10-16 bits, 8 data bits are taken from the least + * significant 16 bit word, and the remaining bits from the other 16 bit word. + * @par + * Additional control bits are available as documented in the reference + * manual (set to 0 if not used). For 10-16 bit frame length, these control + * bits are taken from the most significant 16 bit word. + ******************************************************************************/ +void USART_TxDoubleExt(USART_TypeDef *usart, uint32_t data) +{ + /* Check that transmit buffer is empty */ + while (!(usart->STATUS & USART_STATUS_TXBL)) + ; + usart->TXDOUBLEX = data; +} + +/***************************************************************************//** + * @brief + * Transmit one 4-9 bit frame with extended control. + * + * @details + * Notice that possible parity/stop bits in asynchronous mode are not + * considered part of specified frame bit length. + * + * @note + * This function will stall if buffer is full, until buffer becomes available. + * + * @param[in] usart + * Pointer to USART/UART peripheral register block. + * + * @param[in] data + * Data to transmit with extended control. Least significant bits contains + * frame bits, and additional control bits are available as documented in + * the reference manual (set to 0 if not used). + ******************************************************************************/ +void USART_TxExt(USART_TypeDef *usart, uint16_t data) +{ + /* Check that transmit buffer is empty */ + while (!(usart->STATUS & USART_STATUS_TXBL)) + ; + usart->TXDATAX = (uint32_t)data; +} + +/** @} (end addtogroup USART) */ +/** @} (end addtogroup emlib) */ +#endif /* defined(USART_COUNT) && (USART_COUNT > 0) */ diff --git a/efm32boot/inc/app.h b/efm32boot/inc/app.h new file mode 100644 index 0000000..a01d6f5 --- /dev/null +++ b/efm32boot/inc/app.h @@ -0,0 +1,22 @@ +/* + * app.h + * + * Created on: Jun 26, 2018 + * Author: conor + */ + +#ifndef SRC_APP_H_ +#define SRC_APP_H_ + +#define IS_BOOTLOADER + +#define PRINTING_USE_VCOM + +#define USING_DEV_BOARD + +#define BRIDGE_TO_WALLET + +void printing_init(); + + +#endif /* SRC_APP_H_ */ diff --git a/efm32boot/src/crypto.c b/efm32boot/src/crypto.c new file mode 100644 index 0000000..b1efb8e --- /dev/null +++ b/efm32boot/src/crypto.c @@ -0,0 +1,345 @@ +/* + * Wrapper for crypto implementation on device + * + * */ +#include +#include +#include + + + +#include "util.h" +#include "crypto.h" + +#ifdef USE_SOFTWARE_IMPLEMENTATION + +#include "sha256.h" +#include "uECC.h" +#include "aes.h" +#include "ctap.h" +#include "device.h" +#include "app.h" + +#if defined(USING_PC) || defined(IS_BOOTLOADER) +typedef enum +{ + MBEDTLS_ECP_DP_NONE = 0, + MBEDTLS_ECP_DP_SECP192R1, /*!< 192-bits NIST curve */ + MBEDTLS_ECP_DP_SECP224R1, /*!< 224-bits NIST curve */ + MBEDTLS_ECP_DP_SECP256R1, /*!< 256-bits NIST curve */ + MBEDTLS_ECP_DP_SECP384R1, /*!< 384-bits NIST curve */ + MBEDTLS_ECP_DP_SECP521R1, /*!< 521-bits NIST curve */ + MBEDTLS_ECP_DP_BP256R1, /*!< 256-bits Brainpool curve */ + MBEDTLS_ECP_DP_BP384R1, /*!< 384-bits Brainpool curve */ + MBEDTLS_ECP_DP_BP512R1, /*!< 512-bits Brainpool curve */ + MBEDTLS_ECP_DP_CURVE25519, /*!< Curve25519 */ + MBEDTLS_ECP_DP_SECP192K1, /*!< 192-bits "Koblitz" curve */ + MBEDTLS_ECP_DP_SECP224K1, /*!< 224-bits "Koblitz" curve */ + MBEDTLS_ECP_DP_SECP256K1, /*!< 256-bits "Koblitz" curve */ +} mbedtls_ecp_group_id; +#endif + + +const uint8_t attestation_cert_der[]; +const uint16_t attestation_cert_der_size; +const uint8_t attestation_key[]; +const uint16_t attestation_key_size; + + + +static SHA256_CTX sha256_ctx; +static const struct uECC_Curve_t * _es256_curve = NULL; +static const uint8_t * _signing_key = NULL; +static int _key_len = 0; + +// Secrets for testing only +static uint8_t master_secret[32] = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff" + "\xff\xee\xdd\xcc\xbb\xaa\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00"; + +static uint8_t transport_secret[32] = "\x10\x01\x22\x33\x44\x55\x66\x77\x87\x90\x0a\xbb\x3c\xd8\xee\xff" + "\xff\xee\x8d\x1c\x3b\xfa\x99\x88\x77\x86\x55\x44\xd3\xff\x33\x00"; + + + +void crypto_sha256_init() +{ + sha256_init(&sha256_ctx); +} + +void crypto_reset_master_secret() +{ + ctap_generate_rng(master_secret, 32); +} + + +void crypto_sha256_update(uint8_t * data, size_t len) +{ + sha256_update(&sha256_ctx, data, len); +} + +void crypto_sha256_update_secret() +{ + sha256_update(&sha256_ctx, master_secret, 32); +} + +void crypto_sha256_final(uint8_t * hash) +{ + sha256_final(&sha256_ctx, hash); +} + +void crypto_sha256_hmac_init(uint8_t * key, uint32_t klen, uint8_t * hmac) +{ + uint8_t buf[64]; + int i; + memset(buf, 0, sizeof(buf)); + + if (key == CRYPTO_MASTER_KEY) + { + key = master_secret; + klen = sizeof(master_secret); + } + + if(klen > 64) + { + printf("Error, key size must be <= 64\n"); + exit(1); + } + + memmove(buf, key, klen); + + for (i = 0; i < sizeof(buf); i++) + { + buf[i] = buf[i] ^ 0x36; + } + + crypto_sha256_init(); + crypto_sha256_update(buf, 64); +} + +void crypto_sha256_hmac_final(uint8_t * key, uint32_t klen, uint8_t * hmac) +{ + uint8_t buf[64]; + int i; + crypto_sha256_final(hmac); + memset(buf, 0, sizeof(buf)); + if (key == CRYPTO_MASTER_KEY) + { + key = master_secret; + klen = sizeof(master_secret); + } + + + if(klen > 64) + { + printf("Error, key size must be <= 64\n"); + exit(1); + } + memmove(buf, key, klen); + + for (i = 0; i < sizeof(buf); i++) + { + buf[i] = buf[i] ^ 0x5c; + } + + crypto_sha256_init(); + crypto_sha256_update(buf, 64); + crypto_sha256_update(hmac, 32); + crypto_sha256_final(hmac); +} + + +void crypto_ecc256_init() +{ + uECC_set_rng((uECC_RNG_Function)ctap_generate_rng); + _es256_curve = uECC_secp256r1(); +} + + +void crypto_ecc256_load_attestation_key() +{ + _signing_key = attestation_key; + _key_len = 32; +} + +void crypto_ecc256_sign(uint8_t * data, int len, uint8_t * sig) +{ + if ( uECC_sign(_signing_key, data, len, sig, _es256_curve) == 0) + { + printf("error, uECC failed\n"); + exit(1); + } +} + +void crypto_ecc256_load_key(uint8_t * data, int len, uint8_t * data2, int len2) +{ + static uint8_t privkey[32]; + generate_private_key(data,len,data2,len2,privkey); + _signing_key = privkey; + _key_len = 32; +} + +void crypto_ecdsa_sign(uint8_t * data, int len, uint8_t * sig, int MBEDTLS_ECP_ID) +{ + + const struct uECC_Curve_t * curve = NULL; + + switch(MBEDTLS_ECP_ID) + { + case MBEDTLS_ECP_DP_SECP256R1: + curve = uECC_secp256r1(); + if (_key_len != 32) goto fail; + break; + default: + printf("error, invalid ECDSA alg specifier\n"); + exit(1); + } + + if ( uECC_sign(_signing_key, data, len, sig, curve) == 0) + { + printf("error, uECC failed\n"); + exit(1); + } + return; + +fail: + printf("error, invalid key length\n"); + exit(1); + +} + +void generate_private_key(uint8_t * data, int len, uint8_t * data2, int len2, uint8_t * privkey) +{ + crypto_sha256_hmac_init(CRYPTO_MASTER_KEY, 0, privkey); + crypto_sha256_update(data, len); + crypto_sha256_update(data2, len2); + crypto_sha256_update(master_secret, 32); + crypto_sha256_hmac_final(CRYPTO_MASTER_KEY, 0, privkey); +} + + +/*int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve);*/ +void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8_t * y) +{ + uint8_t privkey[32]; + uint8_t pubkey[64]; + + generate_private_key(data,len,NULL,0,privkey); + + memset(pubkey,0,sizeof(pubkey)); + uECC_compute_public_key(privkey, pubkey, _es256_curve); + memmove(x,pubkey,32); + memmove(y,pubkey+32,32); +} + +void crypto_load_external_key(uint8_t * key, int len) +{ + _signing_key = key; + _key_len = len; +} + + +void crypto_ecc256_make_key_pair(uint8_t * pubkey, uint8_t * privkey) +{ + if (uECC_make_key(pubkey, privkey, _es256_curve) != 1) + { + printf("Error, uECC_make_key failed\n"); + exit(1); + } +} + +void crypto_ecc256_shared_secret(const uint8_t * pubkey, const uint8_t * privkey, uint8_t * shared_secret) +{ + if (uECC_shared_secret(pubkey, privkey, shared_secret, _es256_curve) != 1) + { + printf("Error, uECC_shared_secret failed\n"); + exit(1); + } + +} + +struct AES_ctx aes_ctx; +void crypto_aes256_init(uint8_t * key, uint8_t * nonce) +{ + if (key == CRYPTO_TRANSPORT_KEY) + { + AES_init_ctx(&aes_ctx, transport_secret); + } + else + { + AES_init_ctx(&aes_ctx, key); + } + if (nonce == NULL) + { + memset(aes_ctx.Iv, 0, 16); + } + else + { + memmove(aes_ctx.Iv, nonce, 16); + } +} + +// prevent round key recomputation +void crypto_aes256_reset_iv(uint8_t * nonce) +{ + if (nonce == NULL) + { + memset(aes_ctx.Iv, 0, 16); + } + else + { + memmove(aes_ctx.Iv, nonce, 16); + } +} + +void crypto_aes256_decrypt(uint8_t * buf, int length) +{ + AES_CBC_decrypt_buffer(&aes_ctx, buf, length); +} + +void crypto_aes256_encrypt(uint8_t * buf, int length) +{ + AES_CBC_encrypt_buffer(&aes_ctx, buf, length); +} + + +const uint8_t attestation_cert_der[] = +"\x30\x82\x01\xfb\x30\x82\x01\xa1\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0a\x06\x08" +"\x2a\x86\x48\xce\x3d\x04\x03\x02\x30\x2c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13" +"\x02\x55\x53\x31\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x10\x30\x0e" +"\x06\x03\x55\x04\x0a\x0c\x07\x54\x45\x53\x54\x20\x43\x41\x30\x20\x17\x0d\x31\x38" +"\x30\x35\x31\x30\x30\x33\x30\x36\x32\x30\x5a\x18\x0f\x32\x30\x36\x38\x30\x34\x32" +"\x37\x30\x33\x30\x36\x32\x30\x5a\x30\x7c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13" +"\x02\x55\x53\x31\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x0f\x30\x0d" +"\x06\x03\x55\x04\x07\x0c\x06\x4c\x61\x75\x72\x65\x6c\x31\x15\x30\x13\x06\x03\x55" +"\x04\x0a\x0c\x0c\x54\x45\x53\x54\x20\x43\x4f\x4d\x50\x41\x4e\x59\x31\x22\x30\x20" +"\x06\x03\x55\x04\x0b\x0c\x19\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x6f\x72" +"\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6f\x6e\x31\x14\x30\x12\x06\x03\x55\x04" +"\x03\x0c\x0b\x63\x6f\x6e\x6f\x72\x70\x70\x2e\x63\x6f\x6d\x30\x59\x30\x13\x06\x07" +"\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00" +"\x04\x45\xa9\x02\xc1\x2e\x9c\x0a\x33\xfa\x3e\x84\x50\x4a\xb8\x02\xdc\x4d\xb9\xaf" +"\x15\xb1\xb6\x3a\xea\x8d\x3f\x03\x03\x55\x65\x7d\x70\x3f\xb4\x02\xa4\x97\xf4\x83" +"\xb8\xa6\xf9\x3c\xd0\x18\xad\x92\x0c\xb7\x8a\x5a\x3e\x14\x48\x92\xef\x08\xf8\xca" +"\xea\xfb\x32\xab\x20\xa3\x62\x30\x60\x30\x46\x06\x03\x55\x1d\x23\x04\x3f\x30\x3d" +"\xa1\x30\xa4\x2e\x30\x2c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31" +"\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x10\x30\x0e\x06\x03\x55\x04" +"\x0a\x0c\x07\x54\x45\x53\x54\x20\x43\x41\x82\x09\x00\xf7\xc9\xec\x89\xf2\x63\x94" +"\xd9\x30\x09\x06\x03\x55\x1d\x13\x04\x02\x30\x00\x30\x0b\x06\x03\x55\x1d\x0f\x04" +"\x04\x03\x02\x04\xf0\x30\x0a\x06\x08\x2a\x86\x48\xce\x3d\x04\x03\x02\x03\x48\x00" +"\x30\x45\x02\x20\x18\x38\xb0\x45\x03\x69\xaa\xa7\xb7\x38\x62\x01\xaf\x24\x97\x5e" +"\x7e\x74\x64\x1b\xa3\x7b\xf7\xe6\xd3\xaf\x79\x28\xdb\xdc\xa5\x88\x02\x21\x00\xcd" +"\x06\xf1\xe3\xab\x16\x21\x8e\xd8\xc0\x14\xaf\x09\x4f\x5b\x73\xef\x5e\x9e\x4b\xe7" +"\x35\xeb\xdd\x9b\x6d\x8f\x7d\xf3\xc4\x3a\xd7"; + + +const uint16_t attestation_cert_der_size = sizeof(attestation_cert_der)-1; + + +const uint8_t attestation_key[] = "\xcd\x67\xaa\x31\x0d\x09\x1e\xd1\x6e\x7e\x98\x92\xaa\x07\x0e\x19\x94\xfc\xd7\x14\xae\x7c\x40\x8f\xb9\x46\xb7\x2e\x5f\xe7\x5d\x30"; +const uint16_t attestation_key_size = sizeof(attestation_key)-1; + + +#else +#error "No crypto implementation defined" +#endif + + diff --git a/efm32boot/src/main.c b/efm32boot/src/main.c new file mode 100644 index 0000000..d826c73 --- /dev/null +++ b/efm32boot/src/main.c @@ -0,0 +1,28 @@ +#include "em_device.h" +#include "em_chip.h" +#include "device.h" +#include "app.h" +#include "InitDevice.h" + +void bootloader_init(void); + +int main(void) +{ + /* Chip errata */ + CHIP_Init(); + + EMU_enter_DefaultMode_from_RESET(); + CMU_enter_DefaultMode_from_RESET(); +// ADC0_enter_DefaultMode_from_RESET(); + USART0_enter_DefaultMode_from_RESET(); + USART1_enter_DefaultMode_from_RESET(); + LDMA_enter_DefaultMode_from_RESET(); + CRYOTIMER_enter_DefaultMode_from_RESET(); + PORTIO_enter_DefaultMode_from_RESET(); + + bootloader_init(); + + /* Infinite loop */ + while (1) { + } +} diff --git a/fido2/ctap.c b/fido2/ctap.c index e7b5b02..a3dc1b6 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -1404,7 +1404,7 @@ static uint16_t key_addr_offset(int index) uint16_t ctap_key_len(uint8_t index) { int i = ctap_keys_stored(); - if (i >= MAX_KEYS || index >= MAX_KEYS) + if (index >= i || index >= MAX_KEYS) { return 0; } @@ -1449,7 +1449,7 @@ int8_t ctap_load_key(uint8_t index, uint8_t * key) int i = ctap_keys_stored(); uint16_t offset; uint16_t len; - if (i >= MAX_KEYS || index >= MAX_KEYS) + if (index >= i || index >= MAX_KEYS) { return ERR_NO_KEY_SPACE; } diff --git a/fido2/main.c b/fido2/main.c index f7d801c..b0fb0a3 100644 --- a/fido2/main.c +++ b/fido2/main.c @@ -24,10 +24,10 @@ int main(int argc, char * argv[]) set_logging_mask( /*0*/ -// TAG_GEN| + TAG_GEN| /*TAG_MC |*/ /*TAG_GA |*/ - /*TAG_WALLET |*/ + TAG_WALLET | TAG_STOR | /*TAG_CP |*/ // TAG_CTAP| diff --git a/fido2/wallet.c b/fido2/wallet.c index d3084bf..2383aeb 100644 --- a/fido2/wallet.c +++ b/fido2/wallet.c @@ -15,7 +15,7 @@ #include "storage.h" #include "device.h" -#ifdef USING_PC +#if defined(USING_PC) || defined(IS_BOOTLOADER) typedef enum { MBEDTLS_ECP_DP_NONE = 0,