diff --git a/cpu/kinetis/Kconfig b/cpu/kinetis/Kconfig index 7b9a4443cf..6cd7ddef75 100644 --- a/cpu/kinetis/Kconfig +++ b/cpu/kinetis/Kconfig @@ -27,7 +27,7 @@ config CPU_FAM_K config CPU_FAM_L bool select CPU_COMMON_KINETIS - select HAS_PERIPH_MCG + select HAS_PERIPH_MCG_LITE config CPU_FAM_W bool @@ -126,6 +126,11 @@ config HAS_PERIPH_MCG help Indicates that the cpu uses the Kinetis Multipurpose Clock Generator. +config HAS_PERIPH_MCG_LITE + bool + help + Indicates that the cpu uses the lite version of the Kinetis Multipurpose Clock Generator. + config HAS_PERIPH_ICS bool help diff --git a/cpu/kinetis/Makefile.dep b/cpu/kinetis/Makefile.dep index 45e3557e38..4b4eba3a72 100644 --- a/cpu/kinetis/Makefile.dep +++ b/cpu/kinetis/Makefile.dep @@ -10,11 +10,14 @@ endif # and check FEATURES_USED instead. FEATURES_OPTIONAL += periph_ics FEATURES_OPTIONAL += periph_mcg +FEATURES_OPTIONAL += periph_mcg_lite ifneq (,$(filter periph_ics,$(FEATURES_USED))) USEMODULE += periph_ics else ifneq (,$(filter periph_mcg,$(FEATURES_USED))) USEMODULE += periph_mcg +else ifneq (,$(filter periph_mcg_lite,$(FEATURES_USED))) + USEMODULE += periph_mcg_lite endif USEMODULE += periph_wdog diff --git a/cpu/kinetis/Makefile.features b/cpu/kinetis/Makefile.features index 093bdc8dbb..8be2f95809 100644 --- a/cpu/kinetis/Makefile.features +++ b/cpu/kinetis/Makefile.features @@ -26,6 +26,8 @@ endif ifeq (ea,$(CPU_FAM)) FEATURES_PROVIDED += periph_ics +else ifeq (l,$(CPU_FAM)) + FEATURES_PROVIDED += periph_mcg_lite else FEATURES_PROVIDED += periph_mcg endif diff --git a/cpu/kinetis/include/mcg.h b/cpu/kinetis/include/mcg.h index b7089f60ac..3f0412f226 100644 --- a/cpu/kinetis/include/mcg.h +++ b/cpu/kinetis/include/mcg.h @@ -7,7 +7,8 @@ * details. */ -#ifdef MODULE_PERIPH_MCG /* please doxygen by hiding dangling references */ +/* please doxygen by hiding dangling references */ +#if defined(MODULE_PERIPH_MCG) || defined(MODULE_PERIPH_MCG_LITE) /** * @defgroup cpu_kinetis_mcg Kinetis MCG * @ingroup cpu_kinetis diff --git a/cpu/kinetis/include/periph_cpu.h b/cpu/kinetis/include/periph_cpu.h index 9debb1b078..200ce22d2c 100644 --- a/cpu/kinetis/include/periph_cpu.h +++ b/cpu/kinetis/include/periph_cpu.h @@ -532,8 +532,8 @@ typedef struct { uart_type_t type; /**< Hardware module type (KINETIS_UART or KINETIS_LPUART)*/ } uart_conf_t; -#if !defined(KINETIS_HAVE_PLL) -#if defined(MCG_C6_PLLS_MASK) || DOXYGEN +#if !defined(KINETIS_HAVE_PLL) && defined(MODULE_PERIPH_MCG) \ + && defined(MCG_C6_PLLS_MASK) || DOXYGEN /** * @brief Defined to 1 if the MCG in this Kinetis CPU has a PLL */ @@ -541,7 +541,19 @@ typedef struct { #else #define KINETIS_HAVE_PLL 0 #endif -#endif /* !defined(KINETIS_HAVE_PLL) */ + +#ifdef MODULE_PERIPH_MCG_LITE +/** + * @brief Kinetis possible MCG modes + */ +typedef enum kinetis_mcg_mode { + KINETIS_MCG_MODE_LIRC8M = 0, /**< LIRC 8 MHz mode*/ + KINETIS_MCG_MODE_HIRC = 1, /**< HIRC 48 MHz mode */ + KINETIS_MCG_MODE_EXT = 2, /**< External clocking mode */ + KINETIS_MCG_MODE_LIRC2M = 3, /**< LIRC 2 MHz mode */ + KINETIS_MCG_MODE_NUMOF, /**< Number of possible modes */ +} kinetis_mcg_mode_t; +#endif /* MODULE_PERIPH_MCG_LITE */ #ifdef MODULE_PERIPH_MCG /** @@ -583,6 +595,9 @@ typedef enum { KINETIS_MCG_FLL_FACTOR_2929 = (MCG_C4_DRST_DRS(3) | MCG_C4_DMX32_MASK), } kinetis_mcg_fll_t; +#endif /* MODULE_PERIPH_MCG */ +#if defined(MODULE_PERIPH_MCG) || defined(MODULE_PERIPH_MCG_LITE) + /** * @brief Kinetis FLL external reference clock range settings */ @@ -625,6 +640,8 @@ typedef enum { * @note This flag affects the clock frequency of the CPU when using the MCG * in FBI, or BLPI clocking modes. * + * @note This flag is ignored on MCG_Lite parts + * * - If this flag is set, the fast internal reference clock (up to 4 MHz, * depends on settings) will be routed to the MCGIRCLK internal clock signal. * - If not set, the slow internal reference clock (32 kHz) will be routed to @@ -652,6 +669,17 @@ typedef enum { * CPU STOP modes. */ KINETIS_CLOCK_MCGIRCLK_STOP_EN = (1 << 4), + /** + * @brief Enable MCGPCLK (HIRC) internal clock signal + * + * This flag corresponds to the HIRCEN bit in the MCG_MC register. + * + * This clock source is only available on MCG_Lite parts + * + * - If this flag is set, the MCG will provide MCGPCLK for use by other + * peripherals. + */ + KINETIS_CLOCK_MCGPCLK_EN = (1 << 5), } kinetis_clock_flags_t; /** @@ -723,6 +751,7 @@ typedef struct { * @see CPU reference manual, OSC_CR[SCxP] */ uint8_t osc_clc; +#ifdef MODULE_PERIPH_MCG /** * @brief MCG external reference oscillator selection * @@ -733,9 +762,12 @@ typedef struct { * @see CPU reference manual, MCG_C7[OSCSEL] */ uint8_t oscsel; +#endif /* MODULE_PERIPH_MCG */ /** * @brief Fast internal reference clock divider * + * This field is also known as LIRC_DIV1 on MCG_Lite parts. + * * The bits will be passed directly to the MCG_SC register without any * transformation, use the MCG_SC_FCRDIV() macro to ensure the proper bit * shift for the chosen setting. @@ -743,6 +775,20 @@ typedef struct { * @see CPU reference manual, MCG_SC[FCRDIV] */ uint8_t fcrdiv; +#ifdef MODULE_PERIPH_MCG_LITE + /** + * @brief LIRC second clock divider + * + * The bits will be passed directly to the MCG_MC register without any + * transformation, use the MCG_MC_LIRC_DIV2() macro to ensure the proper bit + * shift for the chosen setting. + * This divider only affects the MCGIRCLK output, it does not affect the + * core frequency when running the MCU in a LIRC clocking mode. + * + * @see CPU reference manual, MCG_MC[LIRC_DIV2] + */ + uint8_t lirc_div2; +#else /** * @brief FLL ERC divider setting * @@ -789,8 +835,9 @@ typedef struct { */ uint8_t pll_vdiv; #endif /* KINETIS_HAVE_PLL */ -} clock_config_t; #endif /* MODULE_PERIPH_MCG */ +} clock_config_t; +#endif /* MODULE_PERIPH_MCG || MODULE_PERIPH_MCG_LITE */ /** * @brief CPU internal function for initializing PORTs * diff --git a/cpu/kinetis/periph/mcg.c b/cpu/kinetis/periph/mcg.c index e8c66f4343..599f8def0d 100644 --- a/cpu/kinetis/periph/mcg.c +++ b/cpu/kinetis/periph/mcg.c @@ -283,7 +283,6 @@ static void kinetis_mcg_set_fbe(void) current_mode = KINETIS_MCG_MODE_FBE; } - /** * @brief Initialize the FLL Bypassed Low Power Internal Mode. * diff --git a/cpu/kinetis/periph/mcg_lite.c b/cpu/kinetis/periph/mcg_lite.c new file mode 100644 index 0000000000..53ff3b9fb0 --- /dev/null +++ b/cpu/kinetis/periph/mcg_lite.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2015 PHYTEC Messtechnik GmbH + * Copyright (C) 2017 Eistec AB + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_kinetis + * @ingroup cpu_kinetis_mcg + * @{ + * + * @file + * @brief Implementation of the Kinetis Multipurpose Clock Generator (Lite version) + * + * @author Johann Fischer + * @author Joakim NohlgÄrd + * + */ + +#include +#include "periph_conf.h" +#include "mcg.h" +#include "bit.h" + +/* The CPU is in LIRC8M mode after hardware reset */ +static kinetis_mcg_mode_t current_mode = KINETIS_MCG_MODE_LIRC8M; + +/** + * @brief Enable Oscillator module + */ +static void kinetis_mcg_enable_osc(void) +{ + /* Configure ERC range for the DCO input. */ + MCG->C2 = (MCG->C2 & ~MCG_C2_RANGE0_MASK) | clock_config.erc_range; + +#if defined(OSC0) + /* Kinetis CPU with OSC module */ + /* Enable Oscillator */ + if (clock_config.clock_flags & KINETIS_CLOCK_OSC0_EN) { + /* Configure oscillator */ + OSC0->CR = OSC_CR_ERCLKEN_MASK | OSC_CR_EREFSTEN_MASK | clock_config.osc_clc; + bit_set8(&MCG->C2, MCG_C2_EREFS0_SHIFT); + + /* wait for OSC initialization */ + while ((MCG->S & MCG_S_OSCINIT0_MASK) == 0) {} + } + else { + bit_clear8(&MCG->C2, MCG_C2_EREFS0_SHIFT); + } +#elif defined(RSIM) + /* Kinetis CPU with a radio system integration module which can provide an + * oscillator output. */ + + /* The CPUs with RSIM (currently only KW41Z, KW31Z, KW21Z) ignore the EREFS0 + * bit in MCG_C2 because they have no OSC module. These CPUs need to use the + * RF oscillator inside the RSIM module if an oscillator is needed. */ + /* The external reference clock line on these CPUs is permanently connected + * to the RSIM clock output, thus the RSIM, instead of the MCG, controls the + * external clock source selection. */ + + if (clock_config.clock_flags & KINETIS_CLOCK_OSC0_EN) { + /* Disable RF oscillator bypass, if it was enabled before */ + bit_clear32(&RSIM->RF_OSC_CTRL, RSIM_RF_OSC_CTRL_RF_OSC_BYPASS_EN_SHIFT); + } + else { + /* Enable RF oscillator bypass, to use the EXTAL pin as external clock + * source without the oscillator circuit */ + bit_set32(&RSIM->RF_OSC_CTRL, RSIM_RF_OSC_CTRL_RF_OSC_BYPASS_EN_SHIFT); + } + + /* Enable RF oscillator circuit */ + /* Current setting is that the OSC only runs in RUN and WAIT modes, see ref.man. */ + RSIM->CONTROL = (RSIM->CONTROL & ~RSIM_CONTROL_RF_OSC_EN_MASK) | RSIM_CONTROL_RF_OSC_EN(1); + + /* Wait for oscillator ready signal */ + while((RSIM->CONTROL & RSIM_CONTROL_RF_OSC_READY_MASK) == 0) {} +#endif /* defined OSC0/RSIM */ +} + +/** + * @brief Initialize the 32 kHz reference clock (ERCLK32K) + * + * This will enable the RTC oscillator if enabled in the configuration. + */ +static void kinetis_mcg_init_erclk32k(void) +{ + /* Enable RTC oscillator if selected */ + if (clock_config.clock_flags & KINETIS_CLOCK_RTCOSC_EN) { + RTC_CLKEN(); + if (!(RTC->CR & RTC_CR_OSCE_MASK)) { + /* Only touch if it was previously not running. The RTC is not reset + * by software resets, only by power on reset */ + RTC->CR = RTC_CR_OSCE_MASK | RTC_CR_SUP_MASK | clock_config.rtc_clc; + } + } + /* Select ERCLK32K source */ + SIM->SOPT1 = (SIM->SOPT1 & ~SIM_SOPT1_OSC32KSEL_MASK) | clock_config.osc32ksel; +} + +/** + * @brief Initialize the MCG internal reference clock (MCGIRCLK) + * + * This clock signal can be used for directly clocking certain peripherals, and + * can be chosen as the MCG output clock (MCGOUTCLK). + */ +static void kinetis_mcg_init_mcgirclk(void) +{ + /* Configure internal reference clock */ + /* On MCG_Lite, LIRC is divided first by LIRC_DIV1, controlled by + * MCG_SC[FCRDIV], then by LIRC_DIV2, controlled by MCG_MC[LIRC_DIV2] */ + MCG->SC = (MCG->SC & ~MCG_SC_FCRDIV_MASK) | clock_config.fcrdiv; + MCG->MC = (MCG->MC & ~MCG_MC_LIRC_DIV2_MASK) | clock_config.lirc_div2; + /* on MCG_Lite, we control the IRCS flag via the mode selection (LIRC2M vs LIRC8M) */ + + /* Enable/disable MCGIRCLK */ + /* MCGIRCLK can be used as an alternate clock source for certain modules */ + if (clock_config.clock_flags & KINETIS_CLOCK_MCGIRCLK_EN) { + bit_set8(&MCG->C1, MCG_C1_IRCLKEN_SHIFT); + } + else { + bit_clear8(&MCG->C1, MCG_C1_IRCLKEN_SHIFT); + } + + if (clock_config.clock_flags & KINETIS_CLOCK_MCGIRCLK_STOP_EN) { + /* Enable MCGIRCLK during STOP (but only when also IRCLKEN is set) */ + bit_set8(&MCG->C1, MCG_C1_IREFSTEN_SHIFT); + } + else { + bit_clear8(&MCG->C1, MCG_C1_IREFSTEN_SHIFT); + } +} + +/** + * @brief Initialize the MCG high speed peripheral clock (MCGPCLK) + * + * This clock signal can be used for directly clocking certain peripherals + */ +static void kinetis_mcg_init_mcgpclk(void) +{ + if (clock_config.clock_flags & KINETIS_CLOCK_MCGPCLK_EN) { + bit_set8(&MCG->MC, MCG_MC_HIRCEN_SHIFT); + } + else { + bit_clear8(&MCG->MC, MCG_MC_HIRCEN_SHIFT); + } +} + +/** + * @brief Initialize HIRC (48 MHz) mode. + */ +static void kinetis_mcg_set_hirc(void) +{ + /* select HIRC mode */ + MCG->C1 = (MCG->C1 & ~MCG_C1_CLKS_MASK) | (MCG_C1_CLKS(0)); + + /* Wait until HIRC is selected */ + while ((MCG->S & (MCG_S_CLKST_MASK)) != 0) {} + + current_mode = KINETIS_MCG_MODE_HIRC; +} + +/** + * @brief Initialize EXT (external clock) mode. + */ +static void kinetis_mcg_set_ext(void) +{ + kinetis_mcg_enable_osc(); + + /* select EXT mode */ + MCG->C1 = (MCG->C1 & ~MCG_C1_CLKS_MASK) | (MCG_C1_CLKS(0)); + + /* Wait until HIRC is selected */ + while ((MCG->S & (MCG_S_CLKST_MASK)) != 0) {} + + current_mode = KINETIS_MCG_MODE_HIRC; +} + +/** + * @brief Initialize LIRC mode. + * + * Use @p ircs to select between LIRC8M (8 MHz) and LIRC2M (2 MHz). + * + * @param[in] lirc8m set to 1 -> LIRC8M, 0 -> LIRC2M + */ +static void kinetis_mcg_set_lirc(unsigned lirc8m) +{ + uint32_t clkdiv = SIM->CLKDIV1; + if ((lirc8m && (current_mode == KINETIS_MCG_MODE_LIRC8M)) || + (!lirc8m && (current_mode == KINETIS_MCG_MODE_LIRC2M))) { + /* We can not switch directly between LIRC2M <-> LIRC8M, go via HIRC */ + /* Set safe clock dividers so we don't run out of specs while switching */ + SIM->CLKDIV1 = SIM_CLKDIV1_OUTDIV1(3); /* divide clock by 4 => 12 MHz */ + kinetis_mcg_set_hirc(); + } + + if (lirc8m) { + /* Select 8 MHz mode */ + bit_set8(&MCG->C2, MCG_C2_IRCS_SHIFT); + current_mode = KINETIS_MCG_MODE_LIRC8M; + } + else { + /* Select 2 MHz mode */ + bit_clear8(&MCG->C2, MCG_C2_IRCS_SHIFT); + current_mode = KINETIS_MCG_MODE_LIRC2M; + } + + /* select LIRC as clock source */ + MCG->C1 = (MCG->C1 & ~MCG_C1_CLKS_MASK) | (MCG_C1_CLKS(1)); + + /* Wait until LIRC is selected */ + while ((MCG->S & (MCG_S_CLKST_MASK)) != MCG_S_CLKST(1)) {} + + /* Restore clock divider settings */ + SIM->CLKDIV1 = clkdiv; +} + +int kinetis_mcg_set_mode(kinetis_mcg_mode_t mode) +{ + switch(mode) { + case KINETIS_MCG_MODE_LIRC8M: + kinetis_mcg_set_lirc(1); + break; + case KINETIS_MCG_MODE_HIRC: + kinetis_mcg_set_hirc(); + break; + case KINETIS_MCG_MODE_EXT: + kinetis_mcg_set_ext(); + break; + case KINETIS_MCG_MODE_LIRC2M: + kinetis_mcg_set_lirc(0); + break; + default: + return -1; + } + + return 0; +} + +void kinetis_mcg_init(void) +{ + unsigned mask = irq_disable(); + + /* Set module clock dividers */ + SIM->CLKDIV1 = clock_config.clkdiv1; + + kinetis_mcg_init_mcgpclk(); + + kinetis_mcg_init_mcgirclk(); + + kinetis_mcg_init_erclk32k(); + + /* Switch to the selected MCG mode */ + kinetis_mcg_set_mode(clock_config.default_mode); + irq_restore(mask); +} + +/** @} */ diff --git a/cpu/kinetis/vectors.c b/cpu/kinetis/vectors.c index 7b9609393b..12a3775b71 100644 --- a/cpu/kinetis/vectors.c +++ b/cpu/kinetis/vectors.c @@ -319,8 +319,11 @@ ISR_VECTOR(1) const isr_t vector_cpu[CPU_IRQ_NUMOF] = { [DAC1_IRQn ] = isr_dac1, /* DAC1 interrupt */ #endif #ifdef MCG +#ifndef MCG_MC_LIRC_DIV2_MASK + /* Only on full MCG, not MCG_Lite */ [MCG_IRQn ] = isr_mcg, /* MCG Interrupt */ -#endif +#endif /* MCG_MC_LIRC_DIV2_MASK */ +#endif /* MCG */ #ifdef LPTMR0 [LPTMR0_IRQn ] = isr_lptmr0, /* LPTimer interrupt */ #endif