/****************************************************************************** * @file system_gd32vf103.c * @brief NMSIS Nuclei Core Device Peripheral Access Layer Source File for * Device gd32vf103 * @version V1.00 * @date 22. Nov 2019 ******************************************************************************/ /* * Copyright (c) 2009-2018 Arm Limited. All rights reserved. * Copyright (c) 2019 Nuclei Limited. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the License); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file system_gd32vf103.c * @brief add from nuclei SDK * https://github.com/Nuclei-Software/nuclei-sdk * @version 1.1 * @author AIIT XUOS Lab * @date 2021-12-03 */ #include #include #include "nuclei_sdk_hal.h" /*---------------------------------------------------------------------------- Define clocks *----------------------------------------------------------------------------*/ /* ToDo: add here your necessary defines for device initialization following is an example for different system frequencies */ #ifndef SYSTEM_CLOCK #define SYSTEM_CLOCK __SYSTEM_CLOCK_108M_PLL_HXTAL #endif /** * \defgroup NMSIS_Core_SystemAndClock System and Clock Configuration * \brief Functions for system and clock setup available in system_.c. * \details * Nuclei provides a template file **system_Device.c** that must be adapted by * the silicon vendor to match their actual device. As a minimum requirement, * this file must provide: * - A device-specific system configuration function, \ref SystemInit(). * - A global variable that contains the system frequency, \ref SystemCoreClock. * * The file configures the device and, typically, initializes the oscillator (PLL) that is part * of the microcontroller device. This file might export other functions or variables that provide * a more flexible configuration of the microcontroller system. * * \note Please pay special attention to the static variable \c SystemCoreClock. This variable might be * used throughout the whole system initialization and runtime to calculate frequency/time related values. * Thus one must assure that the variable always reflects the actual system clock speed. * * \attention * Be aware that a value stored to \c SystemCoreClock during low level initialization (i.e. \c SystemInit()) might get * overwritten by C libray startup code and/or .bss section initialization. * Thus its highly recommended to call \ref SystemCoreClockUpdate at the beginning of the user \c main() routine. * * @{ */ /*---------------------------------------------------------------------------- System Core Clock Variable *----------------------------------------------------------------------------*/ /* ToDo: initialize SystemCoreClock with the system core clock frequency value achieved after system intitialization. This means system core clock frequency after call to SystemInit() */ /** * \brief Variable to hold the system core clock value * \details * Holds the system core clock, which is the system clock frequency supplied to the SysTick * timer and the processor core clock. This variable can be used by debuggers to query the * frequency of the debug timer or to configure the trace clock speed. * * \attention * Compilers must be configured to avoid removing this variable in case the application * program is not using it. Debugging systems require the variable to be physically * present in memory so that it can be examined to configure the debugger. */ uint32_t SystemCoreClock = __SYSTEM_CLOCK_108M_PLL_HXTAL; /* System Clock Frequency (Core Clock) */ /*---------------------------------------------------------------------------- Clock functions *----------------------------------------------------------------------------*/ /*! \brief configure the system clock to 108M by PLL which selects HXTAL(MD/HD/XD:8M; CL:25M) as its clock source \param[in] none \param[out] none \retval none */ static void system_clock_108m_hxtal(void) { uint32_t timeout = 0U; uint32_t stab_flag = 0U; /* enable HXTAL */ RCU_CTL |= RCU_CTL_HXTALEN; /* wait until HXTAL is stable or the startup time is longer than * HXTAL_STARTUP_TIMEOUT */ do { timeout++; stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB); } while ((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout)); /* if fail */ if (0U == (RCU_CTL & RCU_CTL_HXTALSTB)) { while (1) { } } /* HXTAL is stable */ /* AHB = SYSCLK */ RCU_CFG0 |= RCU_AHB_CKSYS_DIV1; /* APB2 = AHB/1 */ RCU_CFG0 |= RCU_APB2_CKAHB_DIV1; /* APB1 = AHB/2 */ RCU_CFG0 |= RCU_APB1_CKAHB_DIV2; /* CK_PLL = (CK_PREDIV0) * 27 = 108 MHz */ RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4); RCU_CFG0 |= (RCU_PLLSRC_HXTAL | RCU_PLL_MUL27); if (HXTAL_VALUE == 25000000) { /* CK_PREDIV0 = (CK_HXTAL)/5 *8 /10 = 4 MHz */ RCU_CFG1 &= ~(RCU_CFG1_PREDV0SEL | RCU_CFG1_PREDV1 | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV0); RCU_CFG1 |= (RCU_PREDV0SRC_CKPLL1 | RCU_PREDV1_DIV5 | RCU_PLL1_MUL8 | RCU_PREDV0_DIV10); /* enable PLL1 */ RCU_CTL |= RCU_CTL_PLL1EN; /* wait till PLL1 is ready */ while (0U == (RCU_CTL & RCU_CTL_PLL1STB)) { } /* enable PLL1 */ RCU_CTL |= RCU_CTL_PLL2EN; /* wait till PLL1 is ready */ while (0U == (RCU_CTL & RCU_CTL_PLL2STB)) { } } else if (HXTAL_VALUE == 8000000) { RCU_CFG1 &= ~(RCU_CFG1_PREDV0SEL | RCU_CFG1_PREDV1 | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV0); RCU_CFG1 |= (RCU_PREDV0SRC_HXTAL | RCU_PREDV0_DIV2 | RCU_PREDV1_DIV2 | RCU_PLL1_MUL20 | RCU_PLL2_MUL20); /* enable PLL1 */ RCU_CTL |= RCU_CTL_PLL1EN; /* wait till PLL1 is ready */ while (0U == (RCU_CTL & RCU_CTL_PLL1STB)) { } /* enable PLL2 */ RCU_CTL |= RCU_CTL_PLL2EN; /* wait till PLL1 is ready */ while (0U == (RCU_CTL & RCU_CTL_PLL2STB)) { } } /* enable PLL */ RCU_CTL |= RCU_CTL_PLLEN; /* wait until PLL is stable */ while (0U == (RCU_CTL & RCU_CTL_PLLSTB)) { } /* select PLL as system clock */ RCU_CFG0 &= ~RCU_CFG0_SCS; RCU_CFG0 |= RCU_CKSYSSRC_PLL; /* wait until PLL is selected as system clock */ while (0U == (RCU_CFG0 & RCU_SCSS_PLL)) { } } /*! \brief configure the system clock \param[in] none \param[out] none \retval none */ static void system_clock_config(void) { system_clock_108m_hxtal(); } /** * \brief Function to update the variable \ref SystemCoreClock * \details * Updates the variable \ref SystemCoreClock and must be called whenever the core clock is changed * during program execution. The function evaluates the clock register settings and calculates * the current core clock. */ void SystemCoreClockUpdate(void) /* Get Core Clock Frequency */ { /* ToDo: add code to calculate the system frequency based upon the current * register settings. * Note: This function can be used to retrieve the system core clock * frequeny after user changed register settings. */ uint32_t scss; uint32_t pllsel, predv0sel, pllmf, ck_src; uint32_t predv0, predv1, pll1mf; scss = GET_BITS(RCU_CFG0, 2, 3); switch (scss) { /* IRC8M is selected as CK_SYS */ case SEL_IRC8M: SystemCoreClock = IRC8M_VALUE; break; /* HXTAL is selected as CK_SYS */ case SEL_HXTAL: SystemCoreClock = HXTAL_VALUE; break; /* PLL is selected as CK_SYS */ case SEL_PLL: /* PLL clock source selection, HXTAL or IRC8M/2 */ pllsel = (RCU_CFG0 & RCU_CFG0_PLLSEL); if (RCU_PLLSRC_IRC8M_DIV2 == pllsel) { /* PLL clock source is IRC8M/2 */ ck_src = IRC8M_VALUE / 2U; } else { /* PLL clock source is HXTAL */ ck_src = HXTAL_VALUE; predv0sel = (RCU_CFG1 & RCU_CFG1_PREDV0SEL); /* source clock use PLL1 */ if (RCU_PREDV0SRC_CKPLL1 == predv0sel) { predv1 = ((RCU_CFG1 & RCU_CFG1_PREDV1) >> 4) + 1U; pll1mf = ((RCU_CFG1 & RCU_CFG1_PLL1MF) >> 8) + 2U; if (17U == pll1mf) { pll1mf = 20U; } ck_src = (ck_src / predv1) * pll1mf; } predv0 = (RCU_CFG1 & RCU_CFG1_PREDV0) + 1U; ck_src /= predv0; } /* PLL multiplication factor */ pllmf = GET_BITS(RCU_CFG0, 18, 21); if ((RCU_CFG0 & RCU_CFG0_PLLMF_4)) { pllmf |= 0x10U; } if (pllmf >= 15U) { pllmf += 1U; } else { pllmf += 2U; } SystemCoreClock = ck_src * pllmf; if (15U == pllmf) { /* PLL source clock multiply by 6.5 */ SystemCoreClock = ck_src * 6U + ck_src / 2U; } break; /* IRC8M is selected as CK_SYS */ default: SystemCoreClock = IRC8M_VALUE; break; } } /** * \brief Function to Initialize the system. * \details * Initializes the microcontroller system. Typically, this function configures the * oscillator (PLL) that is part of the microcontroller device. For systems * with a variable clock speed, it updates the variable \ref SystemCoreClock. * SystemInit is called from the file startup_device. */ void SystemInit(void) { /* ToDo: add code to initialize the system * Warn: do not use global variables because this function is called before * reaching pre-main. RW section maybe overwritten afterwards. */ /* reset the RCC clock configuration to the default reset state */ /* enable IRC8M */ RCU_CTL |= RCU_CTL_IRC8MEN; /* reset SCS, AHBPSC, APB1PSC, APB2PSC, ADCPSC, CKOUT0SEL bits */ RCU_CFG0 &= ~(RCU_CFG0_SCS | RCU_CFG0_AHBPSC | RCU_CFG0_APB1PSC | RCU_CFG0_APB2PSC | RCU_CFG0_ADCPSC | RCU_CFG0_ADCPSC_2 | RCU_CFG0_CKOUT0SEL); /* reset HXTALEN, CKMEN, PLLEN bits */ RCU_CTL &= ~(RCU_CTL_HXTALEN | RCU_CTL_CKMEN | RCU_CTL_PLLEN); /* Reset HXTALBPS bit */ RCU_CTL &= ~(RCU_CTL_HXTALBPS); /* reset PLLSEL, PREDV0_LSB, PLLMF, USBFSPSC bits */ RCU_CFG0 &= ~(RCU_CFG0_PLLSEL | RCU_CFG0_PREDV0_LSB | RCU_CFG0_PLLMF | RCU_CFG0_USBFSPSC | RCU_CFG0_PLLMF_4); RCU_CFG1 = 0x00000000U; /* Reset HXTALEN, CKMEN, PLLEN, PLL1EN and PLL2EN bits */ RCU_CTL &= ~(RCU_CTL_PLLEN | RCU_CTL_PLL1EN | RCU_CTL_PLL2EN | RCU_CTL_CKMEN | RCU_CTL_HXTALEN); /* disable all interrupts */ RCU_INT = 0x00FF0000U; /* Configure the System clock source, PLL Multiplier, AHB/APBx prescalers and Flash settings */ system_clock_config(); } /** * \defgroup NMSIS_Core_IntExcNMI_Handling Interrupt and Exception and NMI Handling * \brief Functions for interrupt, exception and nmi handle available in system_.c. * \details * Nuclei provide a template for interrupt, exception and NMI handling. Silicon Vendor could adapat according * to their requirement. Silicon vendor could implement interface for different exception code and * replace current implementation. * * @{ */ /** \brief Max exception handler number, don't include the NMI(0xFFF) one */ #define MAX_SYSTEM_EXCEPTION_NUM 12 /** * \brief Store the exception handlers for each exception ID * \note * - This SystemExceptionHandlers are used to store all the handlers for all * the exception codes Nuclei N/NX core provided. * - Exception code 0 - 11, totally 12 exceptions are mapped to SystemExceptionHandlers[0:11] * - Exception for NMI is also re-routed to exception handling(exception code 0xFFF) in startup code configuration, the handler itself is mapped to SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM] */ static unsigned long SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM + 1]; /** * \brief Exception Handler Function Typedef * \note * This typedef is only used internal in this system_gd32vf103.c file. * It is used to do type conversion for registered exception handler before calling it. */ typedef void (*EXC_HANDLER)(unsigned long mcause, unsigned long sp); /** * \brief System Default Exception Handler * \details * This function provided a default exception and NMI handling code for all exception ids. * By default, It will just print some information for debug, Vendor can customize it according to its requirements. */ static void system_default_exception_handler(unsigned long mcause, unsigned long sp) { /* TODO: Uncomment this if you have implement printf function */ printf("MCAUSE: 0x%lx\r\n", mcause); printf("MEPC : 0x%lx\r\n", __RV_CSR_READ(CSR_MEPC)); printf("MTVAL : 0x%lx\r\n", __RV_CSR_READ(CSR_MBADADDR)); Exception_DumpFrame(sp); while (1); } /** * \brief Initialize all the default core exception handlers * \details * The core exception handler for each exception id will be initialized to \ref system_default_exception_handler. * \note * Called in \ref _init function, used to initialize default exception handlers for all exception IDs */ static void Exception_Init(void) { for (int i = 0; i < MAX_SYSTEM_EXCEPTION_NUM + 1; i++) { SystemExceptionHandlers[i] = (unsigned long)system_default_exception_handler; } } /** * \brief Register an exception handler for exception code EXCn * \details * * For EXCn < \ref MAX_SYSTEM_EXCEPTION_NUM, it will be registered into SystemExceptionHandlers[EXCn-1]. * * For EXCn == NMI_EXCn, it will be registered into SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM]. * \param EXCn See \ref EXCn_Type * \param exc_handler The exception handler for this exception code EXCn */ void Exception_Register_EXC(uint32_t EXCn, unsigned long exc_handler) { if ((EXCn < MAX_SYSTEM_EXCEPTION_NUM) && (EXCn >= 0)) { SystemExceptionHandlers[EXCn] = exc_handler; } else if (EXCn == NMI_EXCn) { SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM] = exc_handler; } } /** * \brief Get current exception handler for exception code EXCn * \details * * For EXCn < \ref MAX_SYSTEM_EXCEPTION_NUM, it will return SystemExceptionHandlers[EXCn-1]. * * For EXCn == NMI_EXCn, it will return SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM]. * \param EXCn See \ref EXCn_Type * \return Current exception handler for exception code EXCn, if not found, return 0. */ unsigned long Exception_Get_EXC(uint32_t EXCn) { if ((EXCn < MAX_SYSTEM_EXCEPTION_NUM) && (EXCn >= 0)) { return SystemExceptionHandlers[EXCn]; } else if (EXCn == NMI_EXCn) { return SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM]; } else { return 0; } } /** * \brief Common NMI and Exception handler entry * \details * This function provided a command entry for NMI and exception. Silicon Vendor could modify * this template implementation according to requirement. * \remarks * - RISCV provided common entry for all types of exception. This is proposed code template * for exception entry function, Silicon Vendor could modify the implementation. * - For the core_exception_handler template, we provided exception register function \ref Exception_Register_EXC * which can help developer to register your exception handler for specific exception number. */ uint32_t core_exception_handler(unsigned long mcause, unsigned long sp) { uint32_t EXCn = (uint32_t)(mcause & 0X00000fff); EXC_HANDLER exc_handler; if ((EXCn < MAX_SYSTEM_EXCEPTION_NUM) && (EXCn >= 0)) { exc_handler = (EXC_HANDLER)SystemExceptionHandlers[EXCn]; } else if (EXCn == NMI_EXCn) { exc_handler = (EXC_HANDLER)SystemExceptionHandlers[MAX_SYSTEM_EXCEPTION_NUM]; } else { exc_handler = (EXC_HANDLER)system_default_exception_handler; } if (exc_handler != NULL) { exc_handler(mcause, sp); } return 0; } /** @} */ /* End of Doxygen Group NMSIS_Core_ExceptionAndNMI */ void SystemBannerPrint(void) { #if defined(NUCLEI_BANNER) && (NUCLEI_BANNER == 1) printf("Nuclei SDK Build Time: %s, %s\r\n", __DATE__, __TIME__); #ifdef DOWNLOAD_MODE_STRING printf("Download Mode: %s\r\n", DOWNLOAD_MODE_STRING); #endif printf("CPU Frequency %d Hz\r\n", SystemCoreClock); #endif } /** * \brief initialize eclic config * \details * Eclic need initialize after boot up, Vendor could also change the initialization * configuration. */ void ECLIC_Init(void) { /* TODO: Add your own initialization code here. This function will be called by main */ ECLIC_SetMth(0); ECLIC_SetCfgNlbits(__ECLIC_INTCTLBITS); } /** * \brief Dump Exception Frame * \details * This function provided feature to dump exception frame stored in stack. */ void Exception_DumpFrame(unsigned long sp) { EXC_Frame_Type *exc_frame = (EXC_Frame_Type *)sp; #ifndef __riscv_32e printf("ra: 0x%x, tp: 0x%x, t0: 0x%x, t1: 0x%x, t2: 0x%x, t3: 0x%x, t4: 0x%x, t5: 0x%x, t6: 0x%x\n" \ "a0: 0x%x, a1: 0x%x, a2: 0x%x, a3: 0x%x, a4: 0x%x, a5: 0x%x, a6: 0x%x, a7: 0x%x\n" \ "mcause: 0x%x, mepc: 0x%x, msubm: 0x%x\n", exc_frame->ra, exc_frame->tp, exc_frame->t0, \ exc_frame->t1, exc_frame->t2, exc_frame->t3, exc_frame->t4, exc_frame->t5, exc_frame->t6, \ exc_frame->a0, exc_frame->a1, exc_frame->a2, exc_frame->a3, exc_frame->a4, exc_frame->a5, \ exc_frame->a6, exc_frame->a7, exc_frame->mcause, exc_frame->mepc, exc_frame->msubm); #else printf("ra: 0x%x, tp: 0x%x, t0: 0x%x, t1: 0x%x, t2: 0x%x\n" \ "a0: 0x%x, a1: 0x%x, a2: 0x%x, a3: 0x%x, a4: 0x%x, a5: 0x%x\n" \ "mcause: 0x%x, mepc: 0x%x, msubm: 0x%x\n", exc_frame->ra, exc_frame->tp, exc_frame->t0, \ exc_frame->t1, exc_frame->t2, exc_frame->a0, exc_frame->a1, exc_frame->a2, exc_frame->a3, \ exc_frame->a4, exc_frame->a5, exc_frame->mcause, exc_frame->mepc, exc_frame->msubm); #endif } /** * \brief Initialize a specific IRQ and register the handler * \details * This function set vector mode, trigger mode and polarity, interrupt level and priority, * assign handler for specific IRQn. * \param [in] IRQn NMI interrupt handler address * \param [in] shv \ref ECLIC_NON_VECTOR_INTERRUPT means non-vector mode, and \ref ECLIC_VECTOR_INTERRUPT is vector mode * \param [in] trig_mode see \ref ECLIC_TRIGGER_Type * \param [in] lvl interupt level * \param [in] priority interrupt priority * \param [in] handler interrupt handler, if NULL, handler will not be installed * \return -1 means invalid input parameter. 0 means successful. * \remarks * - This function use to configure specific eclic interrupt and register its interrupt handler and enable its interrupt. * - If the vector table is placed in read-only section(FLASHXIP mode), handler could not be installed */ int32_t ECLIC_Register_IRQ(IRQn_Type IRQn, uint8_t shv, ECLIC_TRIGGER_Type trig_mode, uint8_t lvl, uint8_t priority, void* handler) { if ((IRQn > SOC_INT_MAX) || (shv > ECLIC_VECTOR_INTERRUPT) \ || (trig_mode > ECLIC_NEGTIVE_EDGE_TRIGGER)) { return -1; } /* set interrupt vector mode */ ECLIC_SetShvIRQ(IRQn, shv); /* set interrupt trigger mode and polarity */ ECLIC_SetTrigIRQ(IRQn, trig_mode); /* set interrupt level */ ECLIC_SetLevelIRQ(IRQn, lvl); /* set interrupt priority */ ECLIC_SetPriorityIRQ(IRQn, priority); if (handler != NULL) { /* set interrupt handler entry to vector table */ ECLIC_SetVector(IRQn, (rv_csr_t)handler); } /* enable interrupt */ ECLIC_EnableIRQ(IRQn); return 0; } /** @} */ /* End of Doxygen Group NMSIS_Core_ExceptionAndNMI */ /** * \brief early init function before main * \details * This function is executed right before main function. * For RISC-V gnu toolchain, _init function might not be called * by __libc_init_array function, so we defined a new function * to do initialization */ void _premain_init(void) { /* TODO: Add your own initialization code here, called before main */ SystemCoreClock = get_cpu_freq(); /* configure USART */ gd_com_init(SOC_DEBUG_UART); /* Display banner after UART initialized */ // SystemBannerPrint(); /* Initialize exception default handlers */ Exception_Init(); /* ECLIC initialization, mainly MTH and NLBIT */ ECLIC_Init(); } /** * \brief finish function after main * \param [in] status status code return from main * \details * This function is executed right after main function. * For RISC-V gnu toolchain, _fini function might not be called * by __libc_fini_array function, so we defined a new function * to do initialization */ void _postmain_fini(int status) { /* TODO: Add your own finishing code here, called after main */ } /** * \brief _init function called in __libc_init_array() * \details * This `__libc_init_array()` function is called during startup code, * user need to implement this function, otherwise when link it will * error init.c:(.text.__libc_init_array+0x26): undefined reference to `_init' * \note * Please use \ref _premain_init function now */ void _init(void) { /* Don't put any code here, please use _premain_init now */ } /** * \brief _fini function called in __libc_fini_array() * \details * This `__libc_fini_array()` function is called when exit main. * user need to implement this function, otherwise when link it will * error fini.c:(.text.__libc_fini_array+0x28): undefined reference to `_fini' * \note * Please use \ref _postmain_fini function now */ void _fini(void) { /* Don't put any code here, please use _postmain_fini now */ } /** @} */ /* End of Doxygen Group NMSIS_Core_SystemAndClock */