-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add clockgen module for clock configuration
- Loading branch information
Showing
3 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
/** | ||
* Copyright (c) 2021, Jeff McBride | ||
* | ||
* This file is part of the modm project. | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
// ---------------------------------------------------------------------------- | ||
|
||
#include "clockgen.hpp" | ||
#include <cmath> | ||
|
||
// CMSIS Core compliance | ||
uint32_t modm_fastdata SystemCoreClock(8000000); | ||
modm_weak void SystemCoreClockUpdate() { /* Nothing to update */ } | ||
|
||
namespace modm::platform | ||
{ | ||
uint16_t modm_fastdata delay_fcpu_MHz(8); | ||
uint16_t modm_fastdata delay_ns_per_loop(3000.0 / 8); | ||
|
||
static void updateSystemClock(uint32_t core_hz) { | ||
SystemCoreClock = core_hz; | ||
delay_fcpu_MHz = core_hz / 1'000'000; | ||
delay_ns_per_loop = std::round(3000.f / (core_hz / 1'000'000)); | ||
} | ||
|
||
static uint16_t divFromMasterClkPrescaler(MasterClkPrescaler pres) { | ||
switch(pres) { | ||
case MasterClkPrescaler::CLK_1: | ||
return 1; | ||
case MasterClkPrescaler::CLK_2: | ||
return 2; | ||
case MasterClkPrescaler::CLK_4: | ||
return 4; | ||
case MasterClkPrescaler::CLK_8: | ||
return 8; | ||
case MasterClkPrescaler::CLK_16: | ||
return 16; | ||
case MasterClkPrescaler::CLK_32: | ||
return 32; | ||
case MasterClkPrescaler::CLK_64: | ||
return 64; | ||
case MasterClkPrescaler::CLK_3: | ||
return 3; | ||
default: | ||
return 1; | ||
} | ||
} | ||
|
||
void ClockGen::enableExternal32Khz(bool crystal_bypass) { | ||
if(crystal_bypass) { | ||
SUPC->SUPC_MR |= SUPC_MR_OSCBYPASS; | ||
} | ||
|
||
SUPC->SUPC_CR = SUPC_CR_KEY_PASSWD | SUPC_CR_XTALSEL; | ||
} | ||
|
||
template<uint32_t multiplier> | ||
void ClockGen::enablePllA(uint32_t wait_cycles) { | ||
static_assert(multiplier > 8 && multiplier < 7502, "Valid PLL MUL range is 9-7501"); | ||
PMC->CKGR_PLLAR = | ||
CKGR_PLLAR_MULA(multiplier-1) | | ||
CKGR_PLLAR_PLLACOUNT(wait_cycles) | | ||
CKGR_PLLAR_PLLAEN(1); | ||
// Wait for lock bit | ||
while(!(PMC->PMC_SR & PMC_SR_LOCKA)) {} | ||
} | ||
|
||
template<uint32_t multiplier> | ||
void ClockGen::enablePllB(uint32_t wait_cycles) { | ||
static_assert(multiplier > 8 && multiplier < 7502, "Valid PLL MUL range is 9-7501"); | ||
PMC->CKGR_PLLBR = | ||
CKGR_PLLBR_MULB(multiplier-1) | | ||
CKGR_PLLBR_PLLBCOUNT(wait_cycles) | | ||
CKGR_PLLBR_PLLBEN(1); | ||
// Wait for lock bit | ||
while(!(PMC->PMC_SR & PMC_SR_LOCKB)) {} | ||
} | ||
|
||
void ClockGen::selectMasterClk(MasterClkSource src, MasterClkPrescaler pres) { | ||
// Datasheet says when selected PLL as source, write PRES first, otherwise | ||
// write CSS first. | ||
if(src == MasterClkSource::PLLA_CLK || src == MasterClkSource::PLLB_CLK) { | ||
PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | PMC_MCKR_PRES((uint32_t)pres); | ||
while(!(PMC->PMC_SR & PMC_SR_MCKRDY)) {} | ||
PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS((uint32_t)src); | ||
while(!(PMC->PMC_SR & PMC_SR_MCKRDY)) {} | ||
} else { | ||
PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_CSS_Msk) | PMC_MCKR_CSS((uint32_t)src); | ||
while(!(PMC->PMC_SR & PMC_SR_MCKRDY)) {} | ||
PMC->PMC_MCKR = (PMC->PMC_MCKR & ~PMC_MCKR_PRES_Msk) | PMC_MCKR_PRES((uint32_t)pres); | ||
while(!(PMC->PMC_SR & PMC_SR_MCKRDY)) {} | ||
} | ||
|
||
updateSystemClock(masterClkFrequency()); | ||
} | ||
|
||
uint32_t ClockGen::masterClkFrequency() { | ||
uint32_t mckr = PMC->PMC_MCKR; | ||
MasterClkSource src = (MasterClkSource)((mckr & ~PMC_MCKR_CSS_Msk) >> PMC_MCKR_CSS_Pos); | ||
MasterClkPrescaler pres = (MasterClkPrescaler)((mckr & ~PMC_MCKR_PRES_Msk) >> PMC_MCKR_PRES_Pos); | ||
uint32_t div = divFromMasterClkPrescaler(pres); | ||
switch(src) { | ||
case MasterClkSource::SLOW_CLK: | ||
return SlowClkFreqHz / div; | ||
case MasterClkSource::MAIN_CLK: | ||
return mainClkFrequency() / div; | ||
case MasterClkSource::PLLA_CLK: | ||
return pllAFrequency() / div; | ||
case MasterClkSource::PLLB_CLK: | ||
return pllBFrequency() / div; | ||
default: | ||
return 0; | ||
} | ||
} | ||
|
||
uint32_t ClockGen::mainClkFrequency() { | ||
// NOTE: This does not support external main clk. To add this, we'll need | ||
// a way for user supplied input frequency. For now, this assumes we're | ||
// using the internal RC for main clock. | ||
uint32_t moscrcf = (PMC->CKGR_MOR & ~CKGR_MOR_MOSCRCF_Msk) >> CKGR_MOR_MOSCRCF_Pos; | ||
if(moscrcf == (uint32_t)MainInternalFreq::Rc8Mhz) { | ||
return 8'000'000; | ||
} else if(moscrcf == (uint32_t)MainInternalFreq::Rc16Mhz) { | ||
return 16'000'000; | ||
} else if(moscrcf == (uint32_t)MainInternalFreq::Rc24Mhz) { | ||
return 24'000'000; | ||
} else { | ||
return 0; | ||
} | ||
} | ||
|
||
uint32_t ClockGen::pllAFrequency() { | ||
uint32_t mul = ((PMC->CKGR_PLLAR & ~CKGR_PLLAR_MULA_Msk) >> CKGR_PLLAR_MULA_Pos) + 1; | ||
uint32_t freq = SlowClkFreqHz * mul; | ||
if(PMC->PMC_MCKR & PMC_MCKR_PLLADIV2) { | ||
freq /= 2; | ||
} | ||
return freq; | ||
} | ||
|
||
uint32_t ClockGen::pllBFrequency() { | ||
uint32_t mul = ((PMC->CKGR_PLLBR & ~CKGR_PLLBR_MULB_Msk) >> CKGR_PLLBR_MULB_Pos) + 1; | ||
uint32_t freq = SlowClkFreqHz * mul; | ||
if(PMC->PMC_MCKR & PMC_MCKR_PLLBDIV2) { | ||
freq /= 2; | ||
} | ||
return freq; | ||
} | ||
|
||
|
||
void ClockGen::setMainInternalFreq(MainInternalFreq freq) { | ||
PMC->CKGR_MOR = | ||
(PMC->CKGR_MOR & ~(CKGR_MOR_MOSCRCF_Msk)) | | ||
CKGR_MOR_MOSCRCF((uint32_t)freq) | | ||
CKGR_MOR_KEY_PASSWD; | ||
} | ||
|
||
} // namespace modm::platform |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/** | ||
* Copyright (c) 2021, Jeff McBride | ||
* | ||
* This file is part of the modm project. | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
// ---------------------------------------------------------------------------- | ||
|
||
#pragma once | ||
|
||
#include <stdint.h> | ||
#include "../device.hpp" | ||
|
||
namespace modm::platform { | ||
|
||
enum class MasterClkSource : uint32_t { | ||
SLOW_CLK = PMC_MCKR_CSS_SLOW_CLK_Val, | ||
MAIN_CLK = PMC_MCKR_CSS_MAIN_CLK_Val, | ||
PLLA_CLK = PMC_MCKR_CSS_PLLA_CLK_Val, | ||
PLLB_CLK = PMC_MCKR_CSS_PLLB_CLK_Val | ||
}; | ||
|
||
enum class MasterClkPrescaler : uint32_t { | ||
CLK_1 = 0, | ||
CLK_2, | ||
CLK_4, | ||
CLK_8, | ||
CLK_16, | ||
CLK_32, | ||
CLK_64, | ||
CLK_3 | ||
}; | ||
|
||
enum class MainInternalFreq : uint32_t { | ||
Rc8Mhz = 0, | ||
Rc16Mhz, | ||
Rc24Mhz | ||
}; | ||
|
||
static const uint32_t SlowClkFreqHz = 32'768; | ||
|
||
/** | ||
* Clock Generator Controller for SAMG5x devices | ||
* | ||
* This class provides control of the clock goneration for the CPU and | ||
* peripherals. | ||
* | ||
* There are two source clocks: a "slow" 32kHz clock which can be sourced from | ||
* an external oscillator/crystal or internal RC oscillator, and a "main" clock which | ||
* can be sourced from the 8/16/24MHz selectable internal RC oscillator, or from | ||
* an external oscillator/crystal. | ||
* | ||
* In addition, there are two PLLs (PLLA, and PLLB), which run off of the slow | ||
* clock. PLLB is used to provide a 48MHz USB clock (external slow crystal is | ||
* required for USB), and PLLA can (optionally) be used to provide a faster | ||
* clock for the CPU and peripherals. | ||
* | ||
* This class does not allow full control of all possible clock configurations. | ||
* | ||
* @author Jeff McBride | ||
* @ingroup modm_platform_clockgen | ||
*/ | ||
class ClockGen { | ||
public: | ||
/** | ||
* Select external source for slow clock | ||
* | ||
* \param crystal_bypass | ||
* Set true to enable use of external oscillator input on XIN, instead | ||
* of crystal. | ||
*/ | ||
static void enableExternal32Khz(bool crystal_bypass = false); | ||
|
||
template<uint32_t multiplier> | ||
static void enablePllA(uint32_t wait_cycles = 50); | ||
|
||
template<uint32_t multiplier> | ||
static void enablePllB(uint32_t wait_cycles = 50); | ||
|
||
static void selectMasterClk(MasterClkSource src, MasterClkPrescaler pres); | ||
|
||
static void setMainInternalFreq(MainInternalFreq freq); | ||
|
||
/** Returns the configured frequency of the main clock */ | ||
static uint32_t mainClkFrequency(); | ||
|
||
/** Returns the configured frequency of the master clock */ | ||
static uint32_t masterClkFrequency(); | ||
|
||
/** Returns the configured frequency of PLL A output */ | ||
static uint32_t pllAFrequency(); | ||
|
||
/** Returns the configured frequency of PLL B output */ | ||
static uint32_t pllBFrequency(); | ||
}; | ||
|
||
} // namespace modm::platform |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
#!/usr/bin/env python3 | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (c) 2021, Jeff McBride | ||
# | ||
# This file is part of the modm project. | ||
# | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
# ----------------------------------------------------------------------------- | ||
|
||
def init(module): | ||
module.name = ":platform:clockgen" | ||
module.description = "Clock Generator (CKGR)" | ||
|
||
def prepare(module, options): | ||
if not options[":target"].has_driver("pmc:samg*"): | ||
return False | ||
|
||
module.depends(":cmsis:device", ":platform:clock") | ||
return True | ||
|
||
def build(env): | ||
device = env[":target"] | ||
|
||
env.outbasepath = "modm/src/modm/platform/clock" | ||
env.copy("clockgen.hpp") | ||
env.copy("clockgen.cpp") |