Skip to content

Commit e3d3333

Browse files
andryblacksalkinium
authored andcommitted
[rp] Add clocks module
1 parent 04dc881 commit e3d3333

File tree

3 files changed

+422
-0
lines changed

3 files changed

+422
-0
lines changed

src/modm/platform/clock/rp/clocks.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2022, Andrey Kunitsyn
3+
*
4+
* This file is part of the modm project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
// ----------------------------------------------------------------------------
11+
12+
#include "clocks.hpp"
13+
14+
#include <hardware/structs/xosc.h>
15+
16+
#include "../device.hpp"
17+
18+
#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER
19+
#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 1
20+
#endif
21+
22+
// CMSIS Core compliance
23+
constinit uint32_t modm_fastdata SystemCoreClock(modm::platform::ClockControl::BootFrequency);
24+
25+
namespace modm::platform
26+
{
27+
constinit uint16_t modm_fastdata delay_fcpu_MHz(computeDelayMhz(ClockControl::BootFrequency));
28+
constinit uint16_t modm_fastdata delay_ns_per_loop(computeDelayNsPerLoop(ClockControl::BootFrequency));
29+
30+
static uint32_t configured_freq[CLK_COUNT];
31+
32+
void
33+
ClockControl::enableExternalCrystal(uint32_t freq)
34+
{
35+
// Assumes 1-15 MHz input, checked above.
36+
xosc_hw->ctrl = XOSC_CTRL_FREQ_RANGE_VALUE_1_15MHZ;
37+
38+
uint32_t delay = ((((freq / 1000) + 128) / 256) * PICO_XOSC_STARTUP_DELAY_MULTIPLIER);
39+
40+
// Set xosc startup delay
41+
xosc_hw->startup = delay;
42+
43+
// Set the enable bit now that we have set freq range and startup delay
44+
hw_set_bits(&xosc_hw->ctrl, XOSC_CTRL_ENABLE_VALUE_ENABLE << XOSC_CTRL_ENABLE_LSB);
45+
46+
// Wait for XOSC to be stable
47+
while (!(xosc_hw->status & XOSC_STATUS_STABLE_BITS)) __NOP();
48+
}
49+
50+
void
51+
ClockControl::configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq)
52+
{
53+
clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)];
54+
// If increasing divisor, set divisor before source. Otherwise set source
55+
// before divisor. This avoids a momentary overspeed when e.g. switching
56+
// to a faster source and increasing divisor to compensate.
57+
if (div > clock->div) clock->div = div;
58+
// If switching a glitchless slice to an aux source, switch
59+
// away from aux *first* to avoid passing glitches when changing aux mux.
60+
// Assume (!!!) glitchless source 0 is no faster than the aux source.
61+
if (src == CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX)
62+
{
63+
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_REF_CTRL_SRC_BITS);
64+
while (!(clock->selected & 1u)) __NOP();
65+
}
66+
// If no glitchless mux, cleanly stop the clock to avoid glitches
67+
// propagating when changing aux mux. Note it would be a really bad idea
68+
// to do this on one of the glitchless clocks.
69+
else
70+
{
71+
// Disable clock. On clk_ref and clk_sys this does nothing,
72+
// all other clocks have the ENABLE bit in the same position.
73+
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
74+
if (configured_freq[uint32_t(clk)] > 0)
75+
{
76+
// Delay for 3 cycles of the target clock, for ENABLE propagation.
77+
// Note XOSC_COUNT is not helpful here because XOSC is not
78+
// necessarily running, nor is timer... so, 3 cycles per loop:
79+
uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1;
80+
asm volatile(
81+
".syntax unified \n\t"
82+
"1: \n\t"
83+
"subs %0, #1 \n\t"
84+
"bne 1b"
85+
: "+r"(delay_cyc));
86+
}
87+
}
88+
89+
// Set aux mux first, and then glitchless mux
90+
hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
91+
CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS);
92+
93+
hw_write_masked(&clock->ctrl, src << CLOCKS_CLK_REF_CTRL_SRC_LSB,
94+
CLOCKS_CLK_REF_CTRL_SRC_BITS);
95+
while (!(clock->selected & (1u << src))) __NOP();
96+
97+
// Enable clock. On clk_ref and clk_sys this does nothing,
98+
// all other clocks have the ENABLE bit in the same position.
99+
hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
100+
101+
// Now that the source is configured, we can trust that the user-supplied
102+
// divisor is a safe value.
103+
clock->div = div;
104+
// Store the configured frequency
105+
configured_freq[uint32_t(clk)] = freq;
106+
}
107+
108+
void
109+
ClockControl::configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq)
110+
{
111+
112+
clock_hw_t *clock = &clocks_hw->clk[uint32_t(clk)];
113+
// If increasing divisor, set divisor before source. Otherwise set source
114+
// before divisor. This avoids a momentary overspeed when e.g. switching
115+
// to a faster source and increasing divisor to compensate.
116+
if (div > clock->div) clock->div = div;
117+
// Disable clock. On clk_ref and clk_sys this does nothing,
118+
// all other clocks have the ENABLE bit in the same position.
119+
hw_clear_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
120+
if (configured_freq[uint32_t(clk)] > 0)
121+
{
122+
// Delay for 3 cycles of the target clock, for ENABLE propagation.
123+
// Note XOSC_COUNT is not helpful here because XOSC is not
124+
// necessarily running, nor is timer... so, 3 cycles per loop:
125+
uint32_t delay_cyc = configured_freq[clk_sys] / configured_freq[uint32_t(clk)] + 1;
126+
asm volatile(
127+
".syntax unified \n\t"
128+
"1: \n\t"
129+
"subs %0, #1 \n\t"
130+
"bne 1b"
131+
: "+r"(delay_cyc));
132+
}
133+
134+
// Set aux mux
135+
hw_write_masked(&clock->ctrl, (auxsrc << CLOCKS_CLK_SYS_CTRL_AUXSRC_LSB),
136+
CLOCKS_CLK_SYS_CTRL_AUXSRC_BITS);
137+
138+
// Enable clock. On clk_ref and clk_sys this does nothing,
139+
// all other clocks have the ENABLE bit in the same position.
140+
hw_set_bits(&clock->ctrl, CLOCKS_CLK_GPOUT0_CTRL_ENABLE_BITS);
141+
142+
// Now that the source is configured, we can trust that the user-supplied
143+
// divisor is a safe value.
144+
clock->div = div;
145+
// Store the configured frequency
146+
configured_freq[uint32_t(clk)] = freq;
147+
}
148+
} // namespace modm::platform
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright (c) 2022, Andrey Kunitsyn
3+
*
4+
* This file is part of the modm project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
// ----------------------------------------------------------------------------
11+
12+
#pragma once
13+
14+
#include <hardware/structs/clocks.h>
15+
#include <hardware/structs/pll.h>
16+
#include <stdint.h>
17+
18+
#include <modm/architecture/interface/delay.hpp>
19+
#include <modm/platform/core/resets.hpp>
20+
#include <modm/platform/device.hpp>
21+
22+
namespace modm::platform
23+
{
24+
25+
/// @ingroup modm_platform_clockgen
26+
class ClockControl
27+
{
28+
public:
29+
enum class Clock
30+
{
31+
%% for clk in clocks
32+
{{ clk['name'] | capitalize }} = {{ clk['idx'] | int }},
33+
%% endfor
34+
};
35+
enum class ClockSrc
36+
{
37+
%% for src in sources
38+
{{ src }},
39+
%% endfor
40+
};
41+
42+
enum class Pll
43+
{
44+
Sys,
45+
Usb,
46+
};
47+
48+
static constexpr uint32_t BootFrequency = 125'000'000; // after bootloader
49+
50+
protected:
51+
/// @cond
52+
struct InvalidConfig
53+
{
54+
static constexpr uint32_t src = 0xffffffff;
55+
static constexpr uint32_t aux_src = 0xffffffff;
56+
};
57+
58+
template<Clock clk, ClockSrc src>
59+
struct SrcConfig : InvalidConfig
60+
{};
61+
62+
static void
63+
configureImpl(Clock clk, uint32_t src, uint32_t auxsrc, uint32_t div, uint32_t freq);
64+
static void
65+
configureImpl(Clock clk, uint32_t auxsrc, uint32_t div, uint32_t freq);
66+
67+
static constexpr bool
68+
isGlitchless(Clock clk)
69+
{
70+
%% for clk in clocks if clk['glitchless'] == "true"
71+
if (clk == Clock::{{ clk['name'] | capitalize }}) return true;
72+
%% endfor
73+
return false;
74+
}
75+
/// @endcond
76+
77+
public:
78+
static inline void
79+
disableResus()
80+
{
81+
clocks_hw->resus.ctrl = 0;
82+
}
83+
84+
static void
85+
enableExternalCrystal(uint32_t freq);
86+
87+
template<Clock clk>
88+
static void
89+
disableAux()
90+
{
91+
static_assert(isGlitchless(clk),"This clock has only aux sources");
92+
%% for clk in clocks if clk['glitchless'] == "true"
93+
{% if not loop.first %}else {% endif %}if constexpr (clk == Clock::{{ clk['name'] | capitalize }})
94+
{
95+
hw_clear_bits(&clocks_hw->clk[uint32_t(clk)].ctrl, CLOCKS_CLK_{{ clk['name'] | upper }}_CTRL_SRC_BITS);
96+
}
97+
%% endfor
98+
while (clocks_hw->clk[uint32_t(clk)].selected != 0x1) __NOP();
99+
}
100+
101+
template<Pll pll_name, uint32_t refdiv, uint32_t pll_mul, uint32_t post_div1,
102+
uint32_t post_div2>
103+
static void
104+
initPll()
105+
{
106+
pll_hw_t* pll = pll_name == Pll::Sys ? pll_sys_hw : pll_usb_hw;
107+
static_assert(pll_mul >= 16 and pll_mul <= 320, "invalid pll_mul");
108+
// Check divider ranges
109+
static_assert((post_div1 >= 1 and post_div1 <= 7) and (post_div2 >= 1 and post_div2 <= 7),
110+
"invalid post div");
111+
// post_div1 should be >= post_div2
112+
// from appnote page 11
113+
// postdiv1 is designed to operate with a higher input frequency
114+
// than postdiv2
115+
static_assert(post_div2 <= post_div1, "post_div2 vs post_div1");
116+
uint32_t pdiv = (post_div1 << PLL_PRIM_POSTDIV1_LSB) | (post_div2 << PLL_PRIM_POSTDIV2_LSB);
117+
if ((pll->cs & PLL_CS_LOCK_BITS) and (refdiv == (pll->cs & PLL_CS_REFDIV_BITS)) and
118+
(pll_mul == (pll->fbdiv_int & PLL_FBDIV_INT_BITS)) and
119+
(pdiv == (pll->prim & (PLL_PRIM_POSTDIV1_BITS & PLL_PRIM_POSTDIV2_BITS))))
120+
{
121+
// do not disrupt PLL that is already correctly configured and operating
122+
return;
123+
}
124+
uint32_t pll_reset = (Pll::Usb == pll_name) ? RESETS_RESET_PLL_USB_BITS : RESETS_RESET_PLL_SYS_BITS;
125+
Resets::reset(pll_reset);
126+
Resets::unresetWait(pll_reset);
127+
128+
// Load VCO-related dividers before starting VCO
129+
pll->cs = refdiv;
130+
pll->fbdiv_int = pll_mul;
131+
132+
// Turn on PLL
133+
uint32_t power = PLL_PWR_PD_BITS | // Main power
134+
PLL_PWR_VCOPD_BITS; // VCO Power
135+
136+
hw_clear_bits(&pll->pwr, power);
137+
138+
// Wait for PLL to lock
139+
while (!(pll->cs & PLL_CS_LOCK_BITS)) __NOP();
140+
141+
// Set up post dividers
142+
pll->prim = pdiv;
143+
144+
// Turn on post divider
145+
hw_clear_bits(&pll->pwr, PLL_PWR_POSTDIVPD_BITS);
146+
}
147+
148+
/**
149+
* Valid connections are:
150+
%% for clk in clocks
151+
%% for src in clk.source
152+
* - Clock::{{ clk.name | capitalize }} -> ClockSrc::{{ src.cname }}
153+
%% endfor
154+
%% endfor
155+
*/
156+
template<Clock clk, ClockSrc src, uint32_t SrcFreq, uint32_t TargetFreq>
157+
static void
158+
configureClock()
159+
{
160+
using Config = SrcConfig<clk, src>;
161+
constexpr uint32_t div = (uint64_t(SrcFreq) << 8) / TargetFreq;
162+
static_assert(((uint64_t(SrcFreq) << 8) / div) == TargetFreq, "invalid freq");
163+
164+
if constexpr (isGlitchless(clk))
165+
{
166+
static_assert(Config::src != InvalidConfig::src and
167+
Config::aux_src != InvalidConfig::aux_src,
168+
"Invalid clock config");
169+
configureImpl(clk, Config::src, Config::aux_src, div, TargetFreq);
170+
}
171+
else {
172+
static_assert(Config::aux_src != InvalidConfig::aux_src, "Invalid clock config");
173+
configureImpl(clk, Config::aux_src, div, TargetFreq);
174+
}
175+
}
176+
177+
template<uint32_t Core_Hz>
178+
static void
179+
updateCoreFrequency()
180+
{
181+
SystemCoreClock = Core_Hz;
182+
delay_fcpu_MHz = computeDelayMhz(Core_Hz);
183+
delay_ns_per_loop = computeDelayNsPerLoop(Core_Hz);
184+
}
185+
};
186+
187+
/// @cond
188+
// clocks connections matrix
189+
%% for clk in clocks
190+
// {{ clk.name | capitalize }}
191+
%% for src in clk.source
192+
template<>
193+
struct ClockControl::SrcConfig<ClockControl::Clock::{{ clk.name | capitalize }}, ClockControl::ClockSrc::{{ src.cname }}>
194+
{
195+
%% if clk.glitchless == 'true'
196+
static constexpr uint32_t src = {{ src.src | int }};
197+
%% endif
198+
static constexpr uint32_t aux_src = {{ src.aux | int }};
199+
};
200+
%% endfor
201+
%% endfor
202+
/// @endcond
203+
204+
} // namespace modm::platform

0 commit comments

Comments
 (0)