wch-ch32v003
Matt Knight 2 years ago
commit a501e63286

2
.gitignore vendored

@ -0,0 +1,2 @@
zig-cache
zig-out

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "microzig"]
path = microzig
url = git@github.com:ZigEmbeddedGroup/microzig.git

@ -0,0 +1,51 @@
const std = @import("std");
const Builder = std.build.Builder;
const Pkg = std.build.Pkg;
pub const BuildOptions = struct {
packages: ?[]const Pkg = null,
};
pub fn addPiPicoExecutable(
comptime microzig: type,
builder: *Builder,
name: []const u8,
source: []const u8,
options: BuildOptions,
) *std.build.LibExeObjStep {
const rp2040 = microzig.Chip{
.name = "RP2040",
.path = root() ++ "src/rp2040.zig",
.cpu = microzig.cpus.cortex_m0plus,
.memory_regions = &.{
.{ .kind = .flash, .offset = 0x10000100, .length = (2048 * 1024) - 256 },
.{ .kind = .flash, .offset = 0x10000000, .length = 256 },
.{ .kind = .ram, .offset = 0x20000000, .length = 256 * 1024 },
},
};
const raspberry_pi_pico = microzig.Board{
.name = "Raspberry Pi Pico",
.path = root() ++ "src/raspberry_pi_pico.zig",
.chip = rp2040,
};
const ret = microzig.addEmbeddedExecutable(
builder,
name,
source,
.{ .board = raspberry_pi_pico },
.{
.packages = options.packages,
.hal_package_path = .{ .path = root() ++ "src/hal.zig" },
},
) catch @panic("failed to create embedded executable");
ret.setLinkerScriptPath(.{ .path = root() ++ "rp2040.ld" });
return ret;
}
fn root() []const u8 {
return (std.fs.path.dirname(@src().file) orelse unreachable) ++ "/";
}

@ -0,0 +1 @@
Subproject commit ac19b7de8eb1551e603b3ce23a4aab69c71d1216

@ -0,0 +1,58 @@
/*
* This file was auto-generated by microzig
*
* Target CPU: ARM Cortex-M0+
* Target Chip: RP2040
*/
ENTRY(microzig_main);
MEMORY
{
flash0 (rx!w) : ORIGIN = 0x10000000, LENGTH = 0x00200000
ram0 (rw!x) : ORIGIN = 0x20000000, LENGTH = 0x00040000
}
SECTIONS
{
.boot2 : {
__boot2_start__ = .;
KEEP (*(.boot2))
__boot2_end__ = .;
} > flash0
ASSERT(__boot2_end__ - __boot2_start__ == 256,
"ERROR: Pico second stage bootloader must be 256 bytes in size")
.text :
{
KEEP(*(microzig_flash_start))
*(.text*)
*(.rodata*)
} > flash0
.ARM.exidx : {
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} >flash0
.flash1 :
{
*(.flash1)
} > flash1
.data :
{
microzig_data_start = .;
*(.data*)
microzig_data_end = .;
} > ram0 AT> flash0
.bss (NOLOAD) :
{
microzig_bss_start = .;
*(.bss*)
microzig_bss_end = .;
} > ram0
microzig_data_load_start = LOADADDR(.data);
}

@ -0,0 +1,20 @@
const microzig = @import("microzig");
const regs = microzig.chip.regsisters;
pub const gpio = @import("hal/gpio.zig");
pub const clocks = @import("hal/clocks.zig");
pub const default_clock_config = clocks.GlobalConfiguration.init(.{
//.ref = .{ .source = .src_xosc },
.sys = .{
.source = .pll_sys,
.freq = 125_000_000,
},
.usb = .{ .source = .pll_usb },
//.adc = .{ .source = .pll_usb },
//.rtc = .{ .source = .pll_usb },
.peri = .{ .source = .clk_sys },
});
pub fn getCpuId() u32 {
return regs.SIO.CPUID.*;
}

@ -0,0 +1,438 @@
const std = @import("std");
const microzig = @import("microzig");
const pll = @import("pll.zig");
const assert = std.debug.assert;
const regs = microzig.chip.registers;
const xosc_freq = microzig.board.xosc_freq;
// TODO: move to board file
/// this is only nominal, very imprecise and prone to drift over time
const rosc_freq = 6_500_000;
comptime {
assert(xosc_freq <= 15_000_000 and xosc_freq >= 1_000_000); // xosc limits
}
pub const xosc = struct {
const startup_delay_ms = 1;
const startup_delay_value = xosc_freq * startup_delay_ms / 1000 / 256;
pub fn init() void {
regs.XOSC.STARTUP.modify(.{ .DELAY = startup_delay_value });
regs.XOSC.CTRL.modify(.{ .ENABLE = 4011 });
// wait for xosc startup to complete:
while (regs.XOSC.STATUS.read().STABLE == 0) {}
}
pub fn waitCycles(value: u8) void {
assert(is_enabled: {
const status = regs.XOSC.STATUS.read();
break :is_enabled status.STABLE != 0 and status.ENABLED != 0;
});
regs.XOSC.COUNT.modify(value);
while (regs.XOSC.COUNT.read() != 0) {}
}
};
fn formatUppercase(
bytes: []const u8,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = fmt;
_ = options;
for (bytes) |c|
try writer.writeByte(std.ascii.toUpper(c));
}
fn uppercase(bytes: []const u8) std.fmt.Formatter(formatUppercase) {
return .{ .data = bytes };
}
pub const Generator = enum {
gpout0,
gpout1,
gpout2,
gpout3,
ref,
sys,
peri,
usb,
adc,
rtc,
// source directly from register definitions
const Source = enum {
rosc_clksrc_ph,
clksrc_clk_ref_aux,
xosc_clksrc,
clk_ref,
clksrc_clk_sys_aux,
};
// aux sources directly from register definitions
const AuxilarySource = enum {
clksrc_pll_sys,
clksrc_gpin0,
clksrc_gpin1,
clksrc_pll_usb,
rosc_clksrc,
xosc_clksrc,
clk_sys,
clk_usb,
clk_adc,
clk_rtc,
clk_ref,
rosc_clksrc_ph,
};
const source_map = struct {
const ref = [_]Generator.Source{ .rosc_clksrc_ph, .clksrc_clk_ref, .xosc_clksrc };
const sys = [_]Generator.Source{ .clk_ref, .clksrc_clk_sys_aux };
};
const aux_map = struct {};
pub fn hasGlitchlessMux(generator: Generator) bool {
return switch (generator) {
.sys, .ref => true,
else => false,
};
}
pub fn enable(generator: Generator) void {
inline for (std.meta.fields(Generator)) |field| {
if (generator == @field(Generator, field.name)) {
const reg_name = comptime std.fmt.comptimePrint("CLK_{s}_CTRL", .{
uppercase(field.name),
});
if (@hasField(@TypeOf(@field(regs.CLOCKS, reg_name).*).underlying_type, "ENABLE"))
@field(regs.CLOCKS, reg_name).modify(.{ .ENABLE = 1 });
}
}
}
pub fn setDiv(generator: Generator, div: u32) void {
inline for (std.meta.fields(Generator)) |field| {
if (generator == @field(Generator, field.name)) {
const reg_name = comptime std.fmt.comptimePrint("CLK_{s}_DIV", .{
uppercase(field.name),
});
if (@hasDecl(regs.CLOCKS, reg_name))
@field(regs.CLOCKS, reg_name).raw = div
else
assert(false); // doesn't have a divider
}
}
}
pub fn getDiv(generator: Generator) u32 {
return inline for (std.meta.fields(Generator)) |field| {
if (generator == @field(Generator, field.name)) {
const reg_name = comptime std.fmt.comptimePrint("CLK_{s}_DIV", .{
uppercase(field.name),
});
break if (@hasDecl(regs.CLOCKS, reg_name))
@field(regs.CLOCKS, reg_name).raw
else
1;
}
} else unreachable;
}
// The bitfields for the *_SELECTED registers are actually a mask of which
// source is selected. While switching sources it may read as 0, and that's
// what this function is indended for: checking if the new source has
// switched over yet.
//
// Some mention that this is only for the glitchless mux, so if it is non-glitchless then return true
pub fn selected(generator: Generator) bool {
inline for (std.meta.fields(Generator)) |field| {
if (generator == @field(Generator, field.name)) {
return if (@field(Generator, field.name).hasGlitchlessMux()) ret: {
const reg_name = comptime std.fmt.comptimePrint("CLK_{s}_SELECTED", .{
uppercase(field.name),
});
break :ret @field(regs.CLOCKS, reg_name).* != 0;
} else true;
}
} else unreachable;
}
};
pub const Source = enum {
src_rosc,
src_xosc,
src_aux,
pll_sys,
pll_usb,
clk_sys,
};
pub const GlobalConfiguration = struct {
xosc_configured: bool,
sys: ?Configuration,
ref: ?Configuration,
usb: ?Configuration,
adc: ?Configuration,
rtc: ?Configuration,
peri: ?Configuration,
pll_sys: ?pll.Configuration,
pll_usb: ?pll.Configuration,
pub const Option = struct {
source: Source,
freq: ?u32 = null,
};
pub const Options = struct {
sys: ?Option = null,
ref: ?Option = null,
usb: ?Option = null,
adc: ?Option = null,
rtc: ?Option = null,
peri: ?Option = null,
// TODO: allow user to configure PLLs to optimize for low-jitter, low-power, or manually specify
};
/// this function reasons about how to configure the clock system. It will
/// assert if the configuration is invalid
pub fn init(comptime opts: Options) GlobalConfiguration {
var xosc_configured = false;
var pll_sys: ?pll.Configuration = null;
var pll_usb: ?pll.Configuration = null;
return GlobalConfiguration{
// the system clock can either use rosc, xosc, or the sys PLL
.sys = if (opts.sys) |sys_opts| sys_config: {
var output_freq: ?u32 = null;
break :sys_config .{
.generator = .sys,
.input = switch (sys_opts.source) {
.src_rosc => input: {
output_freq = sys_opts.freq orelse rosc_freq;
assert(output_freq.? <= rosc_freq);
break :input .{
.source = .rosc,
.freq = rosc_freq,
};
},
.src_xosc => input: {
xosc_configured = true;
output_freq = sys_opts.freq orelse xosc_freq;
assert(output_freq.? <= xosc_freq);
break :input .{
.source = .xosc,
.freq = xosc_freq,
};
},
.pll_sys => input: {
xosc_configured = true;
output_freq = sys_opts.freq orelse 125_000_000;
assert(output_freq.? <= 125_000_000);
// TODO: proper values for 125MHz
pll_sys = .{
.refdiv = 2,
.vco_freq = 1_440_000_000,
.postdiv1 = 6,
.postdiv2 = 5,
};
break :input .{
.source = .pll_sys,
// TODO: not really sure what frequency to
// drive pll at yet, but this is an okay start
.freq = 125_000_000,
};
},
else => unreachable, // not an available input
},
.output_freq = output_freq.?,
};
} else null,
// to keep things simple for now, we'll make it so that the usb
// generator can only be hooked up to the usb PLL, and only have
// one configuration for the usb PLL
.usb = if (opts.usb) |usb_opts| usb_config: {
assert(pll_usb == null);
assert(usb_opts.source == .pll_usb);
xosc_configured = true;
pll_usb = .{
.refdiv = 1,
.vco_freq = 1_440_000_000,
.postdiv1 = 6,
.postdiv2 = 5,
};
break :usb_config .{
.generator = .usb,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
},
.output_freq = 48_000_000,
};
} else null,
// I THINK that if either pll is configured here, then that means
// that the ref clock generator MUST use xosc to feed the PLLs?
.ref = if (opts.ref) |_|
unreachable // don't explicitly configure for now
else if (pll_sys != null or pll_usb != null) ref_config: {
xosc_configured = true;
break :ref_config .{
.generator = .ref,
.input = .{
.source = .src_xosc,
.freq = xosc_freq,
},
.output_freq = xosc_freq,
};
} else null,
// for the rest of the generators we'll make it so that they can
// either use the ROSC, XOSC, or sys PLL, with whatever dividing
// they need
.adc = if (opts.adc) |_|
unreachable // TODO
else
null,
.rtc = if (opts.rtc) |_|
unreachable // TODO
else
null,
.peri = if (opts.peri) |peri_opts| peri_config: {
if (peri_opts.source == .src_xosc)
xosc_configured = true;
// TODO
break :peri_config .{
.generator = .peri,
.input = .{
.source = peri_opts.source,
.freq = xosc_freq,
},
.output_freq = xosc_freq,
};
} else null,
.xosc_configured = xosc_configured,
.pll_sys = pll_sys,
.pll_usb = pll_usb,
};
}
/// this is explicitly comptime to encourage the user to have separate
/// clock configuration declarations instead of mutating them at runtime
pub fn apply(comptime config: GlobalConfiguration) !void {
// disable resus if it has been turned on elsewhere
regs.CLOCKS.CLK_SYS_RESUS_CTRL.raw = 0;
if (config.xosc_configured) {
regs.WATCHDOG.TICK.modify(.{
.CYCLES = xosc_freq / 1_000_000,
.ENABLE = 1,
});
xosc.init();
}
// switch sys and ref cleanly away from aux sources if they're
// configured to use/be used from PLLs
if (config.sys) |sys| switch (sys.input.source) {
.pll_usb, .pll_sys => {
regs.CLOCKS.CLK_SYS_CTRL.modify(.{ .SRC = 0 });
while (regs.CLOCKS.CLK_SYS_SELECTED.* != 1) {}
},
else => {},
};
if (config.ref) |ref| switch (ref.input.source) {
.pll_usb, .pll_sys => {
regs.CLOCKS.CLK_REF_CTRL.modify(.{ .SRC = 0 });
while (regs.CLOCKS.CLK_REF_SELECTED.* != 1) {}
},
else => {},
};
// initialize PLLs
if (config.pll_sys) |pll_sys_config| pll.sys.apply(pll_sys_config);
if (config.pll_usb) |pll_usb_config| pll.usb.apply(pll_usb_config);
// initialize clock generators
if (config.ref) |ref| try ref.apply();
if (config.usb) |usb| try usb.apply();
if (config.adc) |adc| try adc.apply();
if (config.rtc) |rtc| try rtc.apply();
if (config.peri) |peri| try peri.apply();
}
/// returns frequency of a clock or pll, if unconfigured it returns null
pub fn getFrequency(config: GlobalConfiguration) ?u32 {
_ = config;
return null;
}
};
pub const Configuration = struct {
generator: Generator,
input: struct {
source: Source,
freq: u32,
},
output_freq: u32,
pub fn apply(config: Configuration) !void {
const generator = config.generator;
const input = config.input;
const output_freq = config.output_freq;
// source frequency has to be faster because dividing will always reduce.
assert(input.freq >= output_freq);
if (output_freq < input.freq)
return error.InvalidArgs;
const div = @intCast(u32, (@intCast(u64, input.freq) << 8) / 8);
// check divisor
if (div > generator.getDiv())
generator.setDiv(div);
if (generator.hasGlitchlessMux() and input.source == .src_aux) {
// TODO: clear bits
while (!generator.selected()) {
// TODO: is leaving this empty good enough? pico sdk has `tight_loop_contents()`
}
} else {
// uh stuff
}
// set aux mux first and then glitchless mex if this clock has one
if (generator.hasGlitchlessMux()) {
// write to clock ctrl
while (!generator.selected()) {}
}
generator.enable();
generator.setDiv(div);
// should we store global state on configured clocks?
}
};
//pub fn countFrequencyKhz(source: Source) u32 {}

@ -0,0 +1,153 @@
//! ### Function Select Table
//!
//! GPIO | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9
//! -------|----------|-----------|----------|--------|-----|------|------|---------------|----
//! 0 | SPI0 RX | UART0 TX | I2C0 SDA | PWM0 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 1 | SPI0 CSn | UART0 RX | I2C0 SCL | PWM0 B | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 2 | SPI0 SCK | UART0 CTS | I2C1 SDA | PWM1 A | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 3 | SPI0 TX | UART0 RTS | I2C1 SCL | PWM1 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 4 | SPI0 RX | UART1 TX | I2C0 SDA | PWM2 A | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 5 | SPI0 CSn | UART1 RX | I2C0 SCL | PWM2 B | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 6 | SPI0 SCK | UART1 CTS | I2C1 SDA | PWM3 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 7 | SPI0 TX | UART1 RTS | I2C1 SCL | PWM3 B | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 8 | SPI1 RX | UART1 TX | I2C0 SDA | PWM4 A | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 9 | SPI1 CSn | UART1 RX | I2C0 SCL | PWM4 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 10 | SPI1 SCK | UART1 CTS | I2C1 SDA | PWM5 A | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 11 | SPI1 TX | UART1 RTS | I2C1 SCL | PWM5 B | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 12 | SPI1 RX | UART0 TX | I2C0 SDA | PWM6 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 13 | SPI1 CSn | UART0 RX | I2C0 SCL | PWM6 B | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 14 | SPI1 SCK | UART0 CTS | I2C1 SDA | PWM7 A | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 15 | SPI1 TX | UART0 RTS | I2C1 SCL | PWM7 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 16 | SPI0 RX | UART0 TX | I2C0 SDA | PWM0 A | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 17 | SPI0 CSn | UART0 RX | I2C0 SCL | PWM0 B | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 18 | SPI0 SCK | UART0 CTS | I2C1 SDA | PWM1 A | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 19 | SPI0 TX | UART0 RTS | I2C1 SCL | PWM1 B | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 20 | SPI0 RX | UART1 TX | I2C0 SDA | PWM2 A | SIO | PIO0 | PIO1 | CLOCK GPIN0 | USB VBUS EN
//! 21 | SPI0 CSn | UART1 RX | I2C0 SCL | PWM2 B | SIO | PIO0 | PIO1 | CLOCK GPOUT0 | USB OVCUR DET
//! 22 | SPI0 SCK | UART1 CTS | I2C1 SDA | PWM3 A | SIO | PIO0 | PIO1 | CLOCK GPIN1 | USB VBUS DET
//! 23 | SPI0 TX | UART1 RTS | I2C1 SCL | PWM3 B | SIO | PIO0 | PIO1 | CLOCK GPOUT1 | USB VBUS EN
//! 24 | SPI1 RX | UART1 TX | I2C0 SDA | PWM4 A | SIO | PIO0 | PIO1 | CLOCK GPOUT2 | USB OVCUR DET
//! 25 | SPI1 CSn | UART1 RX | I2C0 SCL | PWM4 B | SIO | PIO0 | PIO1 | CLOCK GPOUT3 | USB VBUS DET
//! 26 | SPI1 SCK | UART1 CTS | I2C1 SDA | PWM5 A | SIO | PIO0 | PIO1 | | USB VBUS EN
//! 27 | SPI1 TX | UART1 RTS | I2C1 SCL | PWM5 B | SIO | PIO0 | PIO1 | | USB OVCUR DET
//! 28 | SPI1 RX | UART0 TX | I2C0 SDA | PWM6 A | SIO | PIO0 | PIO1 | | USB VBUS DET
//! 29 | SPI1 CSn | UART0 RX | I2C0 SCL | PWM6 B | SIO | PIO0 | PIO1 | | USB VBUS EN
const std = @import("std");
const microzig = @import("microzig");
const regs = microzig.chip.registers;
const assert = std.debug.assert;
pub const Function = enum(u5) {
xip,
spi,
uart,
i2c,
pwm,
sio,
pio0,
pio1,
gpck,
usb,
@"null" = 0x1f,
};
pub const Direction = enum(u1) {
in,
out,
};
pub const IrqLevel = enum(u2) {
low,
high,
fall,
rise,
};
pub const IrqCallback = fn (gpio: u32, events: u32) callconv(.C) void;
pub const Override = enum {
normal,
invert,
low,
high,
};
pub const SlewRate = enum {
slow,
fast,
};
pub const DriveStrength = enum {
ma_2,
ma_4,
ma_8,
ma_12,
};
pub const Enabled = enum {
disabled,
enabled,
};
//const gpio_num = gpio_num: {
// // calculate max gpios using comptime parsing
//};
/// Initialize a GPIO, set func to SIO
pub inline fn init(comptime gpio: u32) void {
regs.SIO.GPIO_OE_CLR.raw = 1 << gpio;
regs.SIO.GPIO_OUT_CLR.raw = 1 << gpio;
setFunction(gpio, .sio);
}
/// Reset GPIO back to null function (disables it)
pub inline fn deinit(comptime gpio: u32) void {
setFunction(gpio, .@"null");
}
pub inline fn setDir(comptime gpio: u32, direction: Direction) void {
switch (direction) {
.in => regs.SIO.GPIO_OE_CLR.raw |= (1 << gpio),
.out => regs.SIO.GPIO_OE_SET.raw &= (1 << gpio),
}
}
/// Drive a single GPIO high/low
pub inline fn put(comptime gpio: u32, value: u1) void {
switch (value) {
0 => regs.SIO.GPIO_OUT.raw &= ~@as(u32, 1 << gpio),
1 => regs.SIO.GPIO_OUT.raw |= (1 << gpio),
}
}
pub inline fn setFunction(comptime gpio: u32, function: Function) void {
const reg_name = comptime std.fmt.comptimePrint("GPIO{}_CTRL", .{gpio});
@field(regs.IO_BANK0, reg_name).write(.{
.FUNCSEL = @enumToInt(function),
.OUTOVER = 0,
.INOVER = 0,
.IRQOVER = 0,
.OEOVER = 0,
});
}
// setting both uplls enables a "bus keep" function, a weak pull to whatever
// is current high/low state of GPIO
//pub fn setPulls(gpio: u32, up: bool, down: bool) void {}
//
//pub fn pullUp(gpio: u32) void {}
//
//pub fn pullDown(gpio: u32) void {}
//pub fn disablePulls(gpio: u32) void {}
//pub fn setIrqOver(gpio: u32, value: u32) void {}
//pub fn setOutOver(gpio: u32, value: u32) void {}
//pub fn setInOver(gpio: u32, value: u32) void {}
//pub fn setOeOver(gpio: u32, value: u32) void {}
//pub fn setInputEnabled(gpio: u32, enabled: Enabled) void {}
//pub fn setinputHysteresisEnabled(gpio: u32, enabled: Enabled) void {}
//pub fn setSlewRate(gpio: u32, slew_rate: SlewRate) void {}
//pub fn setDriveStrength(gpio: u32, drive: DriveStrength) void {}
//pub fn setIrqEnabled(gpio: u32, events: IrqEvents) void {}
//pub fn acknowledgeIrq(gpio: u32, events: IrqEvents) void {}

@ -0,0 +1,97 @@
const std = @import("std");
const microzig = @import("microzig");
const assert = std.debug.assert;
const regs = microzig.chip.registers;
const xosc_freq = microzig.board.xosc_freq;
pub const Configuration = struct {
refdiv: u6,
vco_freq: u32,
postdiv1: u3,
postdiv2: u3,
};
pub const sys = @intToPtr(*volatile PLL, regs.PLL_SYS.base_address);
pub const usb = @intToPtr(*volatile PLL, regs.PLL_USB.base_address);
pub const PLL = packed struct {
cs: @TypeOf(regs.PLL_SYS.CS),
pwr: @TypeOf(regs.PLL_SYS.PWR),
fbdiv_int: @TypeOf(regs.PLL_SYS.FBDIV_INT),
prim: @TypeOf(regs.PLL_SYS.PRIM),
comptime {
// bunch of comptime checks in here to validate the layout
assert(0 == @bitOffsetOf(PLL, "cs"));
assert(32 == @bitOffsetOf(PLL, "pwr"));
assert(64 == @bitOffsetOf(PLL, "fbdiv_int"));
assert(96 == @bitOffsetOf(PLL, "prim"));
}
pub fn isLocked(pll: *const volatile PLL) bool {
return pll.cs.read().LOCK == 1;
}
pub fn reset(pll: *const volatile PLL) void {
switch (pll) {
sys => {
regs.RESETS.RESET.modify(.{ .pll_sys = 1 });
regs.RESETS.RESET.modify(.{ .pll_sys = 0 });
while (regs.RESETS.RESET_DONE.read().pll_sys == 1) {}
},
usb => {
regs.RESETS.RESET.modify(.{ .pll_usb = 1 });
regs.RESETS.RESET.modify(.{ .pll_usb = 0 });
while (regs.RESETS.RESET_DONE.read().pll_usb == 1) {}
},
else => unreachable,
}
}
pub fn apply(pll: *volatile PLL, comptime config: Configuration) void {
const ref_freq = xosc_freq / @as(u32, config.refdiv);
const fbdiv = @intCast(u12, config.vco_freq / ref_freq);
assert(fbdiv >= 16 and fbdiv <= 320);
assert(config.postdiv1 >= 1 and config.postdiv1 <= 7);
assert(config.postdiv2 >= 1 and config.postdiv2 <= 7);
assert(config.postdiv2 <= config.postdiv1);
assert(ref_freq <= config.vco_freq / 16);
// 1. program reference clock divider
// 2. program feedback divider
// 3. turn on the main power and vco
// 4. wait for vco to lock
// 5. set up post dividers and turn them on
// do not bother a PLL which is already configured
if (pll.isLocked() and
config.refdiv == pll.cs.read().REFDIV and
fbdiv == pll.fbdiv_int.read() and
config.postdiv1 == pll.prim.read().POSTDIV1 and
config.postdiv2 == pll.prim.read().POSTDIV2)
{
return;
}
pll.reset();
// load vco related dividers
pll.cs.modify(.{ .REFDIV = config.refdiv });
pll.fbdiv_int.modify(fbdiv);
// turn on PLL
pll.pwr.modify(.{ .PD = 0, .VCOPD = 0 });
// wait for PLL to lock
while (!pll.isLocked()) {}
pll.prim.modify(.{
.POSTDIV1 = config.postdiv1,
.POSTDIV2 = config.postdiv2,
});
pll.pwr.modify(.{ .POSTDIVPD = 0 });
}
};

@ -0,0 +1,38 @@
pub const xosc_freq = 12_000_000;
// TODO: improve interface so that user can use a custom implementation and
// automatically checksum it.
pub export const _BOOT2: [256]u8 linksection(".boot2") = [_]u8{
0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60,
0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60,
0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b,
0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61,
0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49,
0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20,
0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42,
0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0,
0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66,
0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0,
0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e,
0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21,
0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60,
0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60,
0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21,
0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21,
0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21,
0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60,
0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28,
0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49,
0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88,
0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20,
0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42,
0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66,
0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e,
0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40,
0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00,
0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00,
0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0,
0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a,
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save