Skip to content

Commit

Permalink
Add clockgen module for clock configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
mcbridejc committed Sep 7, 2021
1 parent f2b2a42 commit 02725a9
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 0 deletions.
162 changes: 162 additions & 0 deletions src/modm/platform/clock/samg/clockgen.cpp
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
100 changes: 100 additions & 0 deletions src/modm/platform/clock/samg/clockgen.hpp
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
29 changes: 29 additions & 0 deletions src/modm/platform/clock/samg/module.lb
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")

0 comments on commit 02725a9

Please sign in to comment.