This is the result of a couple weeks of twiddling and testing to create a clock interface with the high level goals of:
Having an "easy mode" interface for the vast majority of people that just want the default Rpi Pico board clock configuration
The ability to create an entirely custom clock configuration by hand (hard mode) for the sickos
LOTS of validity checks at comptime
Clear comments/code structure that communicates what our current clock procedure does and does not do
Open up the door for adding in RP2350 clock support via compile time interfaces (what kicked off this whole thing)
I'll highlight and explain a couple of the fundamental changes below.
Compile Time Interface for Generator
The RP2350 and RP2040 are very similar in their clock configs, but have just enough differences to warrant some sort of compile time interface. clocks/common.zig defines various functions and methods that are common to both chips. clocks/rp2040.zig defines behavior that is specific to the RP2040, and soon there will be a clocks/rp2350.zig to match. This is fairly self-explanatory when you look at the code, as the two different chips have slightly different options for clock sources, tick counters, etc.
Separating "Easy Mode" and "Hard Mode"
The configuration API has been reworked in order to provide a clear separation between the path of least resistance to getting a working clock configuration, and the "expert" focused custom clock configuration path.
clocks.config.preset provides helper functions for generating the full config.Global without having to get into the weeds. default() provides the same behavior as what currently gets configured, system() allows you to divide down the SYS and REF clock frequencies to lower levels, and output_to_gpio() lets you easily MUX + divide down a specified clock to a GPIO. The HAL now calls clocks.config.preset.default() to establish the global clock_config constant that users will usually pass to other HAL APIs so that they can determine what the current SYS clock frequency is.
On the other hand, there is nothing to stop a user from hand rolling their own config.Global, and overriding init() to provide their own clock initialization sequence.
I've added some examples showcasing how someone would go about fiddling with clocks in:
changing_system_clocks.zig
custom_clock_config.zig
gpio_clock_output.zig (this already existed, but modified it to use the new clocks.config.preset API)
ALL the comptime Checks
I've added a TON of comptime checks to the various configuration methods to ensure that:
You can't set an incompatible source for a clock generator
You can't write a bogus divisor to a clock generator
You can't try to configure a clock with a frequency that can't integer divide from the source (the old API didn't support fractional division, and I agree that it isn't a high priority to add so didn't add it in mine)
You can't try to set the SRC register on a generator that doesn't have a glitchless mux
This does force clock configurations to be comptime known, however at the point where you need true runtime dynamic modification of your system clocks, you are doing something way, way more complicated than this HAL can support. In theory there's nothing to stop us from also supporting non-comptime known configuration, it would just require a ton more scaffolding code (think apply() vs apply_runtime()) that I just don't think is worth it. The existing API must be comptime known anyways.
This is the result of a couple weeks of twiddling and testing to create a clock interface with the high level goals of:
comptime
I'll highlight and explain a couple of the fundamental changes below.
Compile Time Interface for
Generator
The RP2350 and RP2040 are very similar in their clock configs, but have just enough differences to warrant some sort of compile time interface.
clocks/common.zig
defines various functions and methods that are common to both chips.clocks/rp2040.zig
defines behavior that is specific to the RP2040, and soon there will be aclocks/rp2350.zig
to match. This is fairly self-explanatory when you look at the code, as the two different chips have slightly different options for clock sources, tick counters, etc.Separating "Easy Mode" and "Hard Mode"
The configuration API has been reworked in order to provide a clear separation between the path of least resistance to getting a working clock configuration, and the "expert" focused custom clock configuration path.
clocks.config.preset
provides helper functions for generating the fullconfig.Global
without having to get into the weeds.default()
provides the same behavior as what currently gets configured,system()
allows you to divide down the SYS and REF clock frequencies to lower levels, andoutput_to_gpio()
lets you easily MUX + divide down a specified clock to a GPIO. The HAL now callsclocks.config.preset.default()
to establish the globalclock_config
constant that users will usually pass to other HAL APIs so that they can determine what the current SYS clock frequency is.On the other hand, there is nothing to stop a user from hand rolling their own
config.Global
, and overridinginit()
to provide their own clock initialization sequence.I've added some examples showcasing how someone would go about fiddling with clocks in:
changing_system_clocks.zig
custom_clock_config.zig
gpio_clock_output.zig
(this already existed, but modified it to use the newclocks.config.preset
API)ALL the
comptime
ChecksI've added a TON of
comptime
checks to the various configuration methods to ensure that:SRC
register on a generator that doesn't have a glitchless muxThis does force clock configurations to be
comptime
known, however at the point where you need true runtime dynamic modification of your system clocks, you are doing something way, way more complicated than this HAL can support. In theory there's nothing to stop us from also supporting non-comptime known configuration, it would just require a ton more scaffolding code (thinkapply()
vsapply_runtime()
) that I just don't think is worth it. The existing API must becomptime
known anyways.