* refined clock configuration, uart works with clk_peri at xosc frequency

* fix pll_sys configuration
wch-ch32v003
Matt Knight 2 years ago committed by GitHub
parent f75a019aa5
commit f0e51f8302
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,25 +1,25 @@
const microzig = @import("microzig"); const microzig = @import("microzig");
const regs = microzig.chip.regsisters; const regs = microzig.chip.registers;
pub const gpio = @import("hal/gpio.zig"); pub const gpio = @import("hal/gpio.zig");
pub const clocks = @import("hal/clocks.zig"); pub const clocks = @import("hal/clocks.zig");
pub const multicore = @import("hal/multicore.zig"); pub const multicore = @import("hal/multicore.zig");
pub const time = @import("hal/time.zig"); pub const time = @import("hal/time.zig");
pub const uart = @import("hal/uart.zig");
pub const clock_config = clocks.GlobalConfiguration.init(.{ pub const clock_config = clocks.GlobalConfiguration.init(.{
.sys = .{ .source = .src_xosc }, .sys = .{
.source = .pll_sys,
.freq = 125_000_000,
},
.ref = .{ .source = .src_xosc },
.peri = .{ .source = .clk_sys }, .peri = .{ .source = .clk_sys },
//.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 init() void { pub fn init() void {
// TODO: resets need to be reviewed here
clock_config.apply(); clock_config.apply();
gpio.reset();
} }
pub fn getCpuId() u32 { pub fn getCpuId() u32 {

@ -83,6 +83,10 @@ pub const Generator = enum {
regs.CLOCKS.base_address, regs.CLOCKS.base_address,
); );
fn getRegs(generator: Generator) *volatile GeneratorRegs {
return &generators[@enumToInt(generator)];
}
pub fn hasGlitchlessMux(generator: Generator) bool { pub fn hasGlitchlessMux(generator: Generator) bool {
return switch (generator) { return switch (generator) {
.sys, .ref => true, .sys, .ref => true,
@ -93,7 +97,7 @@ pub const Generator = enum {
pub fn enable(generator: Generator) void { pub fn enable(generator: Generator) void {
switch (generator) { switch (generator) {
.ref, .sys => {}, .ref, .sys => {},
else => generators[@enumToInt(generator)].ctrl |= (1 << 11), else => generator.getRegs().ctrl |= (1 << 11),
} }
} }
@ -101,14 +105,14 @@ pub const Generator = enum {
if (generator == .peri) if (generator == .peri)
return; return;
generators[@enumToInt(generator)].div = div; generator.getRegs().div = div;
} }
pub fn getDiv(generator: Generator) u32 { pub fn getDiv(generator: Generator) u32 {
if (generator == .peri) if (generator == .peri)
return 1; return 1;
return generators[@enumToInt(generator)].div; return generator.getRegs().div;
} }
// The bitfields for the *_SELECTED registers are actually a mask of which // The bitfields for the *_SELECTED registers are actually a mask of which
@ -118,17 +122,17 @@ pub const Generator = enum {
// //
// Some mention that this is only for the glitchless mux, so if it is non-glitchless then return true // 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 { pub fn selected(generator: Generator) bool {
return (0 != generators[@enumToInt(generator)].selected); return (0 != generator.getRegs().selected);
} }
pub fn clearSource(generator: Generator) void { pub fn clearSource(generator: Generator) void {
generators[@enumToInt(generator)].ctrl &= ~@as(u32, 0x3); generator.getRegs().ctrl &= ~@as(u32, 0x3);
} }
pub fn disable(generator: Generator) void { pub fn disable(generator: Generator) void {
switch (generator) { switch (generator) {
.sys, .ref => {}, .sys, .ref => {},
else => generators[@enumToInt(generator)].ctrl &= ~@as(u32, 1 << 11), else => generator.getRegs().ctrl &= ~@as(u32, 1 << 11),
} }
} }
@ -147,15 +151,17 @@ pub const Generator = enum {
} }
pub fn setSource(generator: Generator, src: u32) void { pub fn setSource(generator: Generator, src: u32) void {
const gen_regs = generator.getRegs();
const mask = ~@as(u32, 0x3); const mask = ~@as(u32, 0x3);
const ctrl_value = generators[@enumToInt(generator)].ctrl; const ctrl_value = gen_regs.ctrl;
generators[@enumToInt(generator)].ctrl = (ctrl_value & mask) | src; gen_regs.ctrl = (ctrl_value & mask) | src;
} }
pub fn setAuxSource(generator: Generator, auxsrc: u32) void { pub fn setAuxSource(generator: Generator, auxsrc: u32) void {
const gen_regs = generator.getRegs();
const mask = ~@as(u32, 0x1e0); const mask = ~@as(u32, 0x1e0);
const ctrl_value = generators[@enumToInt(generator)].ctrl; const ctrl_value = gen_regs.ctrl;
generators[@enumToInt(generator)].ctrl = (ctrl_value & mask) | (auxsrc << 5); gen_regs.ctrl = (ctrl_value & mask) | (auxsrc << 5);
} }
}; };
@ -264,16 +270,20 @@ fn auxSrcValue(generator: Generator, source: Source) u32 {
} }
pub const GlobalConfiguration = struct { pub const GlobalConfiguration = struct {
xosc_configured: bool, xosc_configured: bool = false,
sys: ?Configuration, sys: ?Configuration = null,
ref: ?Configuration, ref: ?Configuration = null,
usb: ?Configuration, usb: ?Configuration = null,
adc: ?Configuration, adc: ?Configuration = null,
rtc: ?Configuration, rtc: ?Configuration = null,
peri: ?Configuration, peri: ?Configuration = null,
gpout0: ?Configuration = null,
pll_sys: ?pll.Configuration, gpout1: ?Configuration = null,
pll_usb: ?pll.Configuration, gpout2: ?Configuration = null,
gpout3: ?Configuration = null,
pll_sys: ?pll.Configuration = null,
pll_usb: ?pll.Configuration = null,
pub const Option = struct { pub const Option = struct {
source: Source, source: Source,
@ -287,173 +297,220 @@ pub const GlobalConfiguration = struct {
adc: ?Option = null, adc: ?Option = null,
rtc: ?Option = null, rtc: ?Option = null,
peri: ?Option = null, peri: ?Option = null,
gpout0: ?Option = null,
gpout1: ?Option = null,
gpout2: ?Option = null,
gpout3: ?Option = null,
// TODO: allow user to configure PLLs to optimize for low-jitter, low-power, or manually specify // TODO: allow user to configure PLLs to optimize for low-jitter, low-power, or manually specify
}; };
pub fn getFrequency(config: GlobalConfiguration, source: Source) ?u32 {
return switch (source) {
.src_xosc => xosc_freq,
.src_rosc => rosc_freq,
.clk_sys => if (config.sys) |sys_config| sys_config.output_freq else null,
.clk_usb => if (config.usb) |usb_config| usb_config.output_freq else null,
.clk_ref => if (config.ref) |ref_config| ref_config.output_freq else null,
.pll_sys => if (config.pll_sys) |pll_sys_config| pll_sys_config.frequency() else null,
.pll_usb => if (config.pll_usb) |pll_usb_config| pll_usb_config.frequency() else null,
else => null,
};
}
/// this function reasons about how to configure the clock system. It will /// this function reasons about how to configure the clock system. It will
/// assert if the configuration is invalid /// assert if the configuration is invalid
pub fn init(comptime opts: Options) GlobalConfiguration { pub fn init(comptime opts: Options) GlobalConfiguration {
var xosc_configured = false; var config = GlobalConfiguration{};
var pll_sys: ?pll.Configuration = null;
var pll_usb: ?pll.Configuration = 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?
return GlobalConfiguration{ config.ref = if (opts.ref) |ref_opts| ref_config: {
// the system clock can either use rosc, xosc, or the sys PLL assert(ref_opts.source == .src_xosc);
.sys = if (opts.sys) |sys_opts| sys_config: { break :ref_config .{
var output_freq: ?u32 = null; .generator = .ref,
break :sys_config .{ .input = .{
.generator = .sys, .source = ref_opts.source,
.input = switch (sys_opts.source) { .freq = config.getFrequency(ref_opts.source).?,
.src_rosc => input: { .src_value = srcValue(.ref, ref_opts.source),
output_freq = sys_opts.freq orelse rosc_freq; .auxsrc_value = auxSrcValue(.ref, ref_opts.source),
assert(output_freq.? <= rosc_freq); },
break :input .{ .output_freq = config.getFrequency(ref_opts.source).?,
.source = .src_rosc, };
.freq = rosc_freq, } else if (config.pll_sys != null or config.pll_usb != null) ref_config: {
.src_value = srcValue(.sys, .src_rosc), config.xosc_configured = true;
.auxsrc_value = auxSrcValue(.sys, .src_rosc), break :ref_config .{
}; .generator = .ref,
}, .input = .{
.src_xosc => input: { .source = .src_xosc,
xosc_configured = true; .freq = xosc_freq,
output_freq = sys_opts.freq orelse xosc_freq; .src_value = srcValue(.ref, .src_xosc),
assert(output_freq.? <= xosc_freq); .auxsrc_value = auxSrcValue(.ref, .src_xosc),
break :input .{ },
.source = .src_xosc, .output_freq = xosc_freq,
.freq = xosc_freq, };
.src_value = srcValue(.sys, .src_xosc), } else null;
.auxsrc_value = auxSrcValue(.sys, .src_xosc),
}; // the system clock can either use rosc, xosc, or the sys PLL
}, config.sys = if (opts.sys) |sys_opts| sys_config: {
.pll_sys => input: { var output_freq: ?u32 = null;
xosc_configured = true; break :sys_config .{
output_freq = sys_opts.freq orelse 125_000_000; .generator = .sys,
assert(output_freq.? <= 125_000_000); .input = switch (sys_opts.source) {
.src_rosc => input: {
// TODO: proper values for 125MHz output_freq = sys_opts.freq orelse rosc_freq;
pll_sys = .{ assert(output_freq.? <= rosc_freq);
.refdiv = 2, break :input .{
.vco_freq = 1_440_000_000, .source = .src_rosc,
.postdiv1 = 6, .freq = rosc_freq,
.postdiv2 = 5, .src_value = srcValue(.sys, .src_rosc),
}; .auxsrc_value = auxSrcValue(.sys, .src_rosc),
};
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,
.src_value = srcValue(.sys, .pll_sys),
.auxsrc_value = auxSrcValue(.sys, .pll_sys),
};
},
else => unreachable, // not an available input
}, },
.output_freq = output_freq.?, .src_xosc => input: {
}; config.xosc_configured = true;
} else null, output_freq = sys_opts.freq orelse xosc_freq;
assert(output_freq.? <= xosc_freq);
// to keep things simple for now, we'll make it so that the usb break :input .{
// generator can only be hooked up to the usb PLL, and only have .source = .src_xosc,
// one configuration for the usb PLL .freq = xosc_freq,
.usb = if (opts.usb) |usb_opts| usb_config: { .src_value = srcValue(.sys, .src_xosc),
assert(pll_usb == null); .auxsrc_value = auxSrcValue(.sys, .src_xosc),
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,
.src_value = srcValue(.usb, .pll_usb),
.auxsrc_value = auxSrcValue(.usb, .pll_usb),
}, },
.output_freq = 48_000_000, .pll_sys => input: {
}; config.xosc_configured = true;
} else null, output_freq = sys_opts.freq orelse 125_000_000;
assert(output_freq.? == 125_000_000); // if using pll use 125MHz for now
// I THINK that if either pll is configured here, then that means
// that the ref clock generator MUST use xosc to feed the PLLs? // TODO: proper values for 125MHz
.ref = if (opts.ref) |_| config.pll_sys = .{
unreachable // don't explicitly configure for now .refdiv = 1,
else if (pll_sys != null or pll_usb != null) ref_config: { .fbdiv = 125,
xosc_configured = true; .postdiv1 = 6,
break :ref_config .{ .postdiv2 = 2,
.generator = .ref, };
.input = .{
.source = .src_xosc, break :input .{
.freq = xosc_freq, .source = .pll_sys,
.src_value = srcValue(.ref, .src_xosc), // TODO: not really sure what frequency to
.auxsrc_value = auxSrcValue(.ref, .src_xosc), // drive pll at yet, but this is an okay start
.freq = 125_000_000,
.src_value = srcValue(.sys, .pll_sys),
.auxsrc_value = auxSrcValue(.sys, .pll_sys),
};
}, },
.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 requires a 48MHz clock, so only ever let it get hooked up to else => unreachable, // not an available input
// the usb PLL },
.adc = if (opts.adc) |adc_opts| adc_config: { .output_freq = output_freq.?,
assert(adc_opts.source == .pll_usb); };
xosc_configured = true; } 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
config.usb = if (opts.usb) |usb_opts| usb_config: {
assert(config.pll_usb == null);
assert(usb_opts.source == .pll_usb);
config.xosc_configured = true;
config.pll_usb = .{
.refdiv = 1,
.fbdiv = 40,
.postdiv1 = 5,
.postdiv2 = 2,
};
// TODO: some safety checks for overwriting this break :usb_config .{
pll_usb = .{ .generator = .usb,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = srcValue(.usb, .pll_usb),
.auxsrc_value = auxSrcValue(.usb, .pll_usb),
},
.output_freq = 48_000_000,
};
} 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 requires a 48MHz clock, so only ever let it get hooked up to
// the usb PLL
config.adc = if (opts.adc) |adc_opts| adc_config: {
assert(adc_opts.source == .pll_usb);
config.xosc_configured = true;
// TODO: some safety checks for overwriting this
if (config.pll_usb) |pll_usb| {
assert(pll_usb.refdiv == 1);
assert(pll_usb.fbdiv == 40);
assert(pll_usb.postdiv1 == 5);
assert(pll_usb.postdiv2 == 2);
} else {
config.pll_usb = .{
.refdiv = 1, .refdiv = 1,
.vco_freq = 1_440_000_000, .fbdiv = 40,
.postdiv1 = 6, .postdiv1 = 5,
.postdiv2 = 5, .postdiv2 = 2,
};
break :adc_config .{
.generator = .usb,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = srcValue(.adc, .pll_usb),
.auxsrc_value = auxSrcValue(.adc, .pll_usb),
},
.output_freq = 48_000_000,
}; };
} else null, }
.rtc = if (opts.rtc) |_| break :adc_config .{
unreachable // TODO .generator = .usb,
.input = .{
.source = .pll_usb,
.freq = 48_000_000,
.src_value = srcValue(.adc, .pll_usb),
.auxsrc_value = auxSrcValue(.adc, .pll_usb),
},
.output_freq = 48_000_000,
};
} else null;
config.rtc = if (opts.rtc) |_|
unreachable // TODO
else
null;
config.peri = if (opts.peri) |peri_opts| peri_config: {
if (peri_opts.source == .src_xosc)
config.xosc_configured = true;
break :peri_config .{
.generator = .peri,
.input = .{
.source = peri_opts.source,
.freq = config.getFrequency(peri_opts.source) orelse
@compileError("you need to configure the source: " ++ @tagName(peri_opts.source)),
.src_value = srcValue(.peri, peri_opts.source),
.auxsrc_value = auxSrcValue(.peri, peri_opts.source),
},
.output_freq = if (peri_opts.freq) |output_freq|
output_freq
else
config.getFrequency(peri_opts.source).?,
};
} else null;
config.gpout0 = if (opts.gpout0) |gpout0_opts| .{
.generator = .gpout0,
.input = .{
.source = gpout0_opts.source,
.freq = config.getFrequency(gpout0_opts.source) orelse
@compileError("you need to configure the source: " ++ @tagName(gpout0_opts.source)),
.src_value = srcValue(.gpout0, gpout0_opts.source),
.auxsrc_value = auxSrcValue(.gpout0, gpout0_opts.source),
},
.output_freq = if (gpout0_opts.freq) |output_freq|
output_freq
else else
null, config.getFrequency(gpout0_opts.source).?,
} else null;
.peri = if (opts.peri) |peri_opts| peri_config: {
if (peri_opts.source == .src_xosc)
xosc_configured = true;
break :peri_config .{
.generator = .peri,
.input = .{
.source = peri_opts.source,
.freq = xosc_freq,
.src_value = srcValue(.peri, peri_opts.source),
.auxsrc_value = auxSrcValue(.peri, peri_opts.source),
},
.output_freq = xosc_freq,
};
} else null,
.xosc_configured = xosc_configured, return config;
.pll_sys = pll_sys,
.pll_usb = pll_usb,
};
} }
/// this is explicitly comptime to encourage the user to have separate /// this is explicitly comptime to encourage the user to have separate
@ -476,7 +533,7 @@ pub const GlobalConfiguration = struct {
if (config.sys) |sys| switch (sys.input.source) { if (config.sys) |sys| switch (sys.input.source) {
.pll_usb, .pll_sys => { .pll_usb, .pll_sys => {
regs.CLOCKS.CLK_SYS_CTRL.modify(.{ .SRC = 0 }); regs.CLOCKS.CLK_SYS_CTRL.modify(.{ .SRC = 0 });
while (regs.CLOCKS.CLK_SYS_SELECTED.* == 0) {} while (!Generator.sys.selected()) {}
}, },
else => {}, else => {},
}; };
@ -484,7 +541,7 @@ pub const GlobalConfiguration = struct {
if (config.ref) |ref| switch (ref.input.source) { if (config.ref) |ref| switch (ref.input.source) {
.pll_usb, .pll_sys => { .pll_usb, .pll_sys => {
regs.CLOCKS.CLK_REF_CTRL.modify(.{ .SRC = 0 }); regs.CLOCKS.CLK_REF_CTRL.modify(.{ .SRC = 0 });
while (regs.CLOCKS.CLK_REF_SELECTED.* == 0) {} while (!Generator.ref.selected()) {}
}, },
else => {}, else => {},
}; };
@ -495,10 +552,15 @@ pub const GlobalConfiguration = struct {
//// initialize clock generators //// initialize clock generators
if (config.ref) |ref| ref.apply(config.sys); if (config.ref) |ref| ref.apply(config.sys);
if (config.sys) |sys| sys.apply(config.sys);
if (config.usb) |usb| usb.apply(config.sys); if (config.usb) |usb| usb.apply(config.sys);
if (config.adc) |adc| adc.apply(config.sys); if (config.adc) |adc| adc.apply(config.sys);
if (config.rtc) |rtc| rtc.apply(config.sys); if (config.rtc) |rtc| rtc.apply(config.sys);
if (config.peri) |peri| peri.apply(config.sys); if (config.peri) |peri| peri.apply(config.sys);
if (config.gpout0) |gpout0| gpout0.apply(config.sys);
if (config.gpout1) |gpout1| gpout1.apply(config.sys);
if (config.gpout2) |gpout2| gpout2.apply(config.sys);
if (config.gpout3) |gpout3| gpout3.apply(config.sys);
} }
}; };
@ -522,13 +584,12 @@ pub const Configuration = struct {
// source frequency has to be faster because dividing will always reduce. // source frequency has to be faster because dividing will always reduce.
assert(input.freq >= output_freq); assert(input.freq >= output_freq);
const div = @intCast(u32, (@intCast(u64, input.freq) << 8) / 8); const div = @intCast(u32, (@intCast(u64, input.freq) << 8) / output_freq);
// check divisor // check divisor
if (div > generator.getDiv()) if (div > generator.getDiv())
generator.setDiv(div); generator.setDiv(div);
// TODO what _is_ an aux source?
if (generator.hasGlitchlessMux() and input.src_value == 1) { if (generator.hasGlitchlessMux() and input.src_value == 1) {
generator.clearSource(); generator.clearSource();
while (!generator.selected()) {} while (!generator.selected()) {}
@ -557,17 +618,18 @@ pub const Configuration = struct {
} }
}; };
// NOTE: untested
pub fn countFrequencyKhz(source: Source, comptime clock_config: GlobalConfiguration) u32 { pub fn countFrequencyKhz(source: Source, comptime clock_config: GlobalConfiguration) u32 {
const ref_freq = clock_config.ref.?.output_freq; const ref_freq = clock_config.ref.?.output_freq;
// wait for counter to be done // wait for counter to be done
while (CLOCKS.FC0_STATUS.read().RUNNING == 1) {} while (CLOCKS.FC0_STATUS.read().RUNNING == 1) {}
CLOCKS.FC0_REF_KHZ.* = ref_freq / 1000; CLOCKS.FC0_REF_KHZ.raw = ref_freq / 1000;
CLOCKS.FC0_INTERVAL.* = 10; CLOCKS.FC0_INTERVAL.raw = 10;
CLOCKS.FC0_MIN_KHZ.* = 0; CLOCKS.FC0_MIN_KHZ.raw = 0;
CLOCKS.FC0_MAX_KHZ.* = std.math.maxInt(u32); CLOCKS.FC0_MAX_KHZ.raw = std.math.maxInt(u32);
CLOCKS.FC0_SRC.* = @enumToInt(source); CLOCKS.FC0_SRC.raw = @enumToInt(source);
while (CLOCKS.FC0_STATUS.read().DONE != 1) {} while (CLOCKS.FC0_STATUS.read().DONE != 1) {}

@ -131,6 +131,10 @@ pub inline fn put(comptime gpio: u32, value: u1) void {
} }
} }
pub inline fn toggle(comptime gpio: u32) void {
regs.SIO.GPIO_OUT_XOR.raw = (1 << gpio);
}
pub inline fn setFunction(comptime gpio: u32, function: Function) void { pub inline fn setFunction(comptime gpio: u32, function: Function) void {
const pad_bank_reg = comptime std.fmt.comptimePrint("GPIO{}", .{gpio}); const pad_bank_reg = comptime std.fmt.comptimePrint("GPIO{}", .{gpio});
@field(regs.PADS_BANK0, pad_bank_reg).modify(.{ @field(regs.PADS_BANK0, pad_bank_reg).modify(.{

@ -7,9 +7,13 @@ const xosc_freq = microzig.board.xosc_freq;
pub const Configuration = struct { pub const Configuration = struct {
refdiv: u6, refdiv: u6,
vco_freq: u32, fbdiv: u32,
postdiv1: u3, postdiv1: u3,
postdiv2: u3, postdiv2: u3,
pub fn frequency(config: Configuration) u32 {
return @as(u32, xosc_freq) / config.refdiv * config.fbdiv / config.postdiv1 / config.postdiv2;
}
}; };
pub const PLL = enum { pub const PLL = enum {
@ -41,15 +45,15 @@ pub const PLL = enum {
} }
pub fn apply(pll: PLL, comptime config: Configuration) void { pub fn apply(pll: PLL, comptime config: Configuration) void {
const pll_regs = pll.getRegs(); assert(config.fbdiv >= 16 and config.fbdiv <= 320);
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.postdiv1 >= 1 and config.postdiv1 <= 7);
assert(config.postdiv2 >= 1 and config.postdiv2 <= 7); assert(config.postdiv2 >= 1 and config.postdiv2 <= 7);
assert(config.postdiv2 <= config.postdiv1); assert(config.postdiv2 <= config.postdiv1);
assert(ref_freq <= config.vco_freq / 16);
const pll_regs = pll.getRegs();
const ref_freq = xosc_freq / @as(u32, config.refdiv);
const vco_freq = ref_freq * config.fbdiv;
assert(ref_freq <= vco_freq / 16);
// 1. program reference clock divider // 1. program reference clock divider
// 2. program feedback divider // 2. program feedback divider
@ -60,7 +64,7 @@ pub const PLL = enum {
// do not bother a PLL which is already configured // do not bother a PLL which is already configured
if (pll.isLocked() and if (pll.isLocked() and
config.refdiv == pll_regs.cs.read().REFDIV and config.refdiv == pll_regs.cs.read().REFDIV and
fbdiv == pll_regs.fbdiv_int.read() and config.fbdiv == pll_regs.fbdiv_int.read() and
config.postdiv1 == pll_regs.prim.read().POSTDIV1 and config.postdiv1 == pll_regs.prim.read().POSTDIV1 and
config.postdiv2 == pll_regs.prim.read().POSTDIV2) config.postdiv2 == pll_regs.prim.read().POSTDIV2)
{ {
@ -71,7 +75,7 @@ pub const PLL = enum {
// load vco related dividers // load vco related dividers
pll_regs.cs.modify(.{ .REFDIV = config.refdiv }); pll_regs.cs.modify(.{ .REFDIV = config.refdiv });
pll_regs.fbdiv_int.modify(fbdiv); pll_regs.fbdiv_int.modify(config.fbdiv);
// turn on PLL // turn on PLL
pll_regs.pwr.modify(.{ .PD = 0, .VCOPD = 0 }); pll_regs.pwr.modify(.{ .PD = 0, .VCOPD = 0 });

@ -0,0 +1,212 @@
const std = @import("std");
const microzig = @import("microzig");
const gpio = @import("gpio.zig");
const clocks = @import("clocks.zig");
const assert = std.debug.assert;
const regs = microzig.chip.registers;
pub const WordBits = enum {
five,
six,
seven,
eight,
};
pub const StopBits = enum {
one,
two,
};
pub const Parity = enum {
none,
even,
odd,
};
pub const Config = struct {
clock_config: clocks.GlobalConfiguration,
tx_pin: u32,
rx_pin: u32,
baud_rate: u32,
word_bits: WordBits = .eight,
stop_bits: StopBits = .one,
parity: Parity = .none,
};
pub const UartRegs = extern struct {
dr: u32,
rsr: u32,
reserved0: [4]u32,
fr: @typeInfo(@TypeOf(regs.UART0.UARTFR)).Pointer.child,
resertev1: [1]u32,
ilpr: u32,
ibrd: u32,
fbrd: u32,
lcr_h: @typeInfo(@TypeOf(regs.UART0.UARTLCR_H)).Pointer.child,
cr: @typeInfo(@TypeOf(regs.UART0.UARTCR)).Pointer.child,
ifls: u32,
imsc: u32,
ris: u32,
mis: u32,
icr: u32,
dmacr: @typeInfo(@TypeOf(regs.UART0.UARTDMACR)).Pointer.child,
periphid0: u32,
periphid1: u32,
periphid2: u32,
periphid3: u32,
cellid0: u32,
cellid1: u32,
cellid2: u32,
cellid3: u32,
padding: [4069]u32,
};
const uarts = @intToPtr(*volatile [2]UartRegs, regs.UART0.base_address);
comptime {
assert(@sizeOf(UartRegs) == (regs.UART1.base_address - regs.UART0.base_address));
}
pub const UART = enum {
uart0,
uart1,
const WriteError = error{};
pub const Writer = std.io.Writer(UART, WriteError, write);
pub fn writer(uart: UART) Writer {
return .{ .context = uart };
}
fn getRegs(uart: UART) *volatile UartRegs {
return &uarts[@enumToInt(uart)];
}
pub fn init(comptime id: u32, comptime config: Config) UART {
const uart: UART = switch (id) {
0 => .uart0,
1 => .uart1,
else => @compileError("there is only uart0 and uart1"),
};
assert(config.baud_rate > 0);
uart.reset();
const uart_regs = uart.getRegs();
const peri_freq = config.clock_config.peri.?.output_freq;
uart.setBaudRate(config.baud_rate, peri_freq);
uart.setFormat(config.word_bits, config.stop_bits, config.parity);
uart_regs.cr.modify(.{
.UARTEN = 1,
.TXE = 1,
.RXE = 1,
});
uart_regs.lcr_h.modify(.{ .FEN = 1 });
// - always enable DREQ signals -- no harm if dma isn't listening
uart_regs.dmacr.modify(.{
.TXDMAE = 1,
.RXDMAE = 1,
});
// TODO comptime assertions
gpio.setFunction(config.tx_pin, .uart);
gpio.setFunction(config.rx_pin, .uart);
return uart;
}
pub fn isReadable(uart: UART) bool {
return (0 == uart.getRegs().fr.read().RXFE);
}
pub fn isWritable(uart: UART) bool {
return (0 == uart.getRegs().fr.read().TXFF);
}
// TODO: implement tx fifo
pub fn write(uart: UART, payload: []const u8) WriteError!usize {
const uart_regs = uart.getRegs();
for (payload) |byte| {
while (!uart.isWritable()) {}
uart_regs.dr = byte;
}
return payload.len;
}
pub fn readWord(uart: UART) u8 {
const uart_regs = uart.getRegs();
while (!uart.isReadable()) {}
return @truncate(u8, uart_regs.dr);
}
pub fn reset(uart: UART) void {
switch (uart) {
.uart0 => {
regs.RESETS.RESET.modify(.{ .uart0 = 1 });
regs.RESETS.RESET.modify(.{ .uart0 = 0 });
while (regs.RESETS.RESET_DONE.read().uart0 != 1) {}
},
.uart1 => {
regs.RESETS.RESET.modify(.{ .uart1 = 1 });
regs.RESETS.RESET.modify(.{ .uart1 = 0 });
while (regs.RESETS.RESET_DONE.read().uart1 != 1) {}
},
}
}
pub fn setFormat(
uart: UART,
word_bits: WordBits,
stop_bits: StopBits,
parity: Parity,
) void {
const uart_regs = uart.getRegs();
uart_regs.lcr_h.modify(.{
.WLEN = switch (word_bits) {
.eight => @as(u2, 0b11),
.seven => @as(u2, 0b10),
.six => @as(u2, 0b01),
.five => @as(u2, 0b00),
},
.STP2 = switch (stop_bits) {
.one => @as(u1, 0),
.two => @as(u1, 1),
},
.PEN = if (parity != .none) @as(u1, 1) else @as(u1, 0),
.EPS = switch (parity) {
.even => @as(u1, 1),
.odd => @as(u1, 0),
else => @as(u1, 0),
},
});
}
fn setBaudRate(uart: UART, baud_rate: u32, peri_freq: u32) void {
assert(baud_rate > 0);
const uart_regs = uart.getRegs();
const baud_rate_div = (8 * peri_freq / baud_rate);
var baud_ibrd = baud_rate_div >> 7;
const baud_fbrd = if (baud_ibrd == 0) baud_fbrd: {
baud_ibrd = 1;
break :baud_fbrd 0;
} else if (baud_ibrd >= 65535) baud_fbrd: {
baud_ibrd = 65535;
break :baud_fbrd 0;
} else ((baud_rate_div & 0x7f) + 1) / 2;
uart_regs.ibrd = baud_ibrd;
uart_regs.fbrd = baud_fbrd;
// just want a write, don't want to change these values
uart_regs.lcr_h.modify(.{});
}
};

@ -44,14 +44,12 @@ pub const VectorTable = extern struct {
}; };
pub const registers = struct { pub const registers = struct {
/// System Control Space /// System Control Space
pub const SCS = struct { pub const SCS = struct {
pub const base_address = 0xe000e000; pub const base_address = 0xe000e000;
/// System Tick Timer /// System Tick Timer
pub const SysTick = struct { pub const SysTick = struct {
/// address: 0xe000e010 /// address: 0xe000e010
/// SysTick Control and Status Register /// SysTick Control and Status Register
pub const CTRL = @intToPtr(*volatile Mmio(32, packed struct { pub const CTRL = @intToPtr(*volatile Mmio(32, packed struct {
@ -134,7 +132,6 @@ pub const registers = struct {
/// Nested Vectored Interrupt Controller /// Nested Vectored Interrupt Controller
pub const NVIC = struct { pub const NVIC = struct {
/// address: 0xe000e100 /// address: 0xe000e100
/// Interrupt Set Enable Register /// Interrupt Set Enable Register
pub const ISER = @intToPtr(*volatile u32, base_address + 0x100); pub const ISER = @intToPtr(*volatile u32, base_address + 0x100);
@ -158,7 +155,6 @@ pub const registers = struct {
/// System Control Block /// System Control Block
pub const SCB = struct { pub const SCB = struct {
/// address: 0xe000ed00 /// address: 0xe000ed00
pub const CPUID = @intToPtr(*volatile Mmio(32, packed struct { pub const CPUID = @intToPtr(*volatile Mmio(32, packed struct {
REVISION: u4, REVISION: u4,
@ -343,7 +339,6 @@ pub const registers = struct {
/// Memory Protection Unit /// Memory Protection Unit
pub const MPU = struct { pub const MPU = struct {
/// address: 0xe000ed90 /// address: 0xe000ed90
/// MPU Type Register /// MPU Type Register
pub const TYPE = @intToPtr(*volatile Mmio(32, packed struct { pub const TYPE = @intToPtr(*volatile Mmio(32, packed struct {

Loading…
Cancel
Save