diff --git a/src/hal.zig b/src/hal.zig index 3db0677..92539f3 100644 --- a/src/hal.zig +++ b/src/hal.zig @@ -1,25 +1,25 @@ const microzig = @import("microzig"); -const regs = microzig.chip.regsisters; +const regs = microzig.chip.registers; + pub const gpio = @import("hal/gpio.zig"); pub const clocks = @import("hal/clocks.zig"); pub const multicore = @import("hal/multicore.zig"); pub const time = @import("hal/time.zig"); +pub const uart = @import("hal/uart.zig"); 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 }, - //.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 { + // TODO: resets need to be reviewed here clock_config.apply(); + gpio.reset(); } pub fn getCpuId() u32 { diff --git a/src/hal/clocks.zig b/src/hal/clocks.zig index fef6e61..e81f1ea 100644 --- a/src/hal/clocks.zig +++ b/src/hal/clocks.zig @@ -83,6 +83,10 @@ pub const Generator = enum { regs.CLOCKS.base_address, ); + fn getRegs(generator: Generator) *volatile GeneratorRegs { + return &generators[@enumToInt(generator)]; + } + pub fn hasGlitchlessMux(generator: Generator) bool { return switch (generator) { .sys, .ref => true, @@ -93,7 +97,7 @@ pub const Generator = enum { pub fn enable(generator: Generator) void { switch (generator) { .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) return; - generators[@enumToInt(generator)].div = div; + generator.getRegs().div = div; } pub fn getDiv(generator: Generator) u32 { if (generator == .peri) return 1; - return generators[@enumToInt(generator)].div; + return generator.getRegs().div; } // 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 pub fn selected(generator: Generator) bool { - return (0 != generators[@enumToInt(generator)].selected); + return (0 != generator.getRegs().selected); } 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 { switch (generator) { .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 { + const gen_regs = generator.getRegs(); const mask = ~@as(u32, 0x3); - const ctrl_value = generators[@enumToInt(generator)].ctrl; - generators[@enumToInt(generator)].ctrl = (ctrl_value & mask) | src; + const ctrl_value = gen_regs.ctrl; + gen_regs.ctrl = (ctrl_value & mask) | src; } pub fn setAuxSource(generator: Generator, auxsrc: u32) void { + const gen_regs = generator.getRegs(); const mask = ~@as(u32, 0x1e0); - const ctrl_value = generators[@enumToInt(generator)].ctrl; - generators[@enumToInt(generator)].ctrl = (ctrl_value & mask) | (auxsrc << 5); + const ctrl_value = gen_regs.ctrl; + gen_regs.ctrl = (ctrl_value & mask) | (auxsrc << 5); } }; @@ -264,16 +270,20 @@ fn auxSrcValue(generator: Generator, source: Source) u32 { } 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, + xosc_configured: bool = false, + sys: ?Configuration = null, + ref: ?Configuration = null, + usb: ?Configuration = null, + adc: ?Configuration = null, + rtc: ?Configuration = null, + peri: ?Configuration = null, + gpout0: ?Configuration = null, + gpout1: ?Configuration = null, + gpout2: ?Configuration = null, + gpout3: ?Configuration = null, + + pll_sys: ?pll.Configuration = null, + pll_usb: ?pll.Configuration = null, pub const Option = struct { source: Source, @@ -287,173 +297,220 @@ pub const GlobalConfiguration = struct { adc: ?Option = null, rtc: ?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 }; + 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 /// 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 = .src_rosc, - .freq = rosc_freq, - .src_value = srcValue(.sys, .src_rosc), - .auxsrc_value = auxSrcValue(.sys, .src_rosc), - }; - }, - .src_xosc => input: { - xosc_configured = true; - output_freq = sys_opts.freq orelse xosc_freq; - assert(output_freq.? <= xosc_freq); - break :input .{ - .source = .src_xosc, - .freq = xosc_freq, - .src_value = srcValue(.sys, .src_xosc), - .auxsrc_value = auxSrcValue(.sys, .src_xosc), - }; - }, - .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, - .src_value = srcValue(.sys, .pll_sys), - .auxsrc_value = auxSrcValue(.sys, .pll_sys), - }; - }, - - else => unreachable, // not an available input + var config = GlobalConfiguration{}; + + // I THINK that if either pll is configured here, then that means + // that the ref clock generator MUST use xosc to feed the PLLs? + config.ref = if (opts.ref) |ref_opts| ref_config: { + assert(ref_opts.source == .src_xosc); + break :ref_config .{ + .generator = .ref, + .input = .{ + .source = ref_opts.source, + .freq = config.getFrequency(ref_opts.source).?, + .src_value = srcValue(.ref, ref_opts.source), + .auxsrc_value = auxSrcValue(.ref, ref_opts.source), + }, + .output_freq = config.getFrequency(ref_opts.source).?, + }; + } else if (config.pll_sys != null or config.pll_usb != null) ref_config: { + config.xosc_configured = true; + break :ref_config .{ + .generator = .ref, + .input = .{ + .source = .src_xosc, + .freq = xosc_freq, + .src_value = srcValue(.ref, .src_xosc), + .auxsrc_value = auxSrcValue(.ref, .src_xosc), + }, + .output_freq = xosc_freq, + }; + } else null; + + // the system clock can either use rosc, xosc, or the sys PLL + config.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 = .src_rosc, + .freq = rosc_freq, + .src_value = srcValue(.sys, .src_rosc), + .auxsrc_value = auxSrcValue(.sys, .src_rosc), + }; }, - .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, - .src_value = srcValue(.usb, .pll_usb), - .auxsrc_value = auxSrcValue(.usb, .pll_usb), + .src_xosc => input: { + config.xosc_configured = true; + output_freq = sys_opts.freq orelse xosc_freq; + assert(output_freq.? <= xosc_freq); + break :input .{ + .source = .src_xosc, + .freq = xosc_freq, + .src_value = srcValue(.sys, .src_xosc), + .auxsrc_value = auxSrcValue(.sys, .src_xosc), + }; }, - .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, - .src_value = srcValue(.ref, .src_xosc), - .auxsrc_value = auxSrcValue(.ref, .src_xosc), + .pll_sys => input: { + config.xosc_configured = true; + output_freq = sys_opts.freq orelse 125_000_000; + assert(output_freq.? == 125_000_000); // if using pll use 125MHz for now + + // TODO: proper values for 125MHz + config.pll_sys = .{ + .refdiv = 1, + .fbdiv = 125, + .postdiv1 = 6, + .postdiv2 = 2, + }; + + 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), + }; }, - .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 - // the usb PLL - .adc = if (opts.adc) |adc_opts| adc_config: { - assert(adc_opts.source == .pll_usb); - xosc_configured = true; + 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 + 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 - pll_usb = .{ + 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, + }; + } 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, - .vco_freq = 1_440_000_000, - .postdiv1 = 6, - .postdiv2 = 5, - }; - - 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, + .fbdiv = 40, + .postdiv1 = 5, + .postdiv2 = 2, }; - } else null, - - .rtc = if (opts.rtc) |_| - unreachable // TODO + } + + 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; + + 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 - 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, + config.getFrequency(gpout0_opts.source).?, + } else null; - .xosc_configured = xosc_configured, - .pll_sys = pll_sys, - .pll_usb = pll_usb, - }; + return config; } /// 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) { .pll_usb, .pll_sys => { regs.CLOCKS.CLK_SYS_CTRL.modify(.{ .SRC = 0 }); - while (regs.CLOCKS.CLK_SYS_SELECTED.* == 0) {} + while (!Generator.sys.selected()) {} }, else => {}, }; @@ -484,7 +541,7 @@ pub const GlobalConfiguration = struct { 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.* == 0) {} + while (!Generator.ref.selected()) {} }, else => {}, }; @@ -495,10 +552,15 @@ pub const GlobalConfiguration = struct { //// initialize clock generators 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.adc) |adc| adc.apply(config.sys); if (config.rtc) |rtc| rtc.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. 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 if (div > generator.getDiv()) generator.setDiv(div); - // TODO what _is_ an aux source? if (generator.hasGlitchlessMux() and input.src_value == 1) { generator.clearSource(); while (!generator.selected()) {} @@ -557,17 +618,18 @@ pub const Configuration = struct { } }; +// NOTE: untested pub fn countFrequencyKhz(source: Source, comptime clock_config: GlobalConfiguration) u32 { const ref_freq = clock_config.ref.?.output_freq; // wait for counter to be done while (CLOCKS.FC0_STATUS.read().RUNNING == 1) {} - CLOCKS.FC0_REF_KHZ.* = ref_freq / 1000; - CLOCKS.FC0_INTERVAL.* = 10; - CLOCKS.FC0_MIN_KHZ.* = 0; - CLOCKS.FC0_MAX_KHZ.* = std.math.maxInt(u32); - CLOCKS.FC0_SRC.* = @enumToInt(source); + CLOCKS.FC0_REF_KHZ.raw = ref_freq / 1000; + CLOCKS.FC0_INTERVAL.raw = 10; + CLOCKS.FC0_MIN_KHZ.raw = 0; + CLOCKS.FC0_MAX_KHZ.raw = std.math.maxInt(u32); + CLOCKS.FC0_SRC.raw = @enumToInt(source); while (CLOCKS.FC0_STATUS.read().DONE != 1) {} diff --git a/src/hal/gpio.zig b/src/hal/gpio.zig index 416b155..1aa0e6b 100644 --- a/src/hal/gpio.zig +++ b/src/hal/gpio.zig @@ -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 { const pad_bank_reg = comptime std.fmt.comptimePrint("GPIO{}", .{gpio}); @field(regs.PADS_BANK0, pad_bank_reg).modify(.{ diff --git a/src/hal/pll.zig b/src/hal/pll.zig index eb16a63..4481abd 100644 --- a/src/hal/pll.zig +++ b/src/hal/pll.zig @@ -7,9 +7,13 @@ const xosc_freq = microzig.board.xosc_freq; pub const Configuration = struct { refdiv: u6, - vco_freq: u32, + fbdiv: u32, postdiv1: 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 { @@ -41,15 +45,15 @@ pub const PLL = enum { } pub fn apply(pll: PLL, comptime config: Configuration) void { - const pll_regs = pll.getRegs(); - 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.fbdiv >= 16 and config.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); + + 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 // 2. program feedback divider @@ -60,7 +64,7 @@ pub const PLL = enum { // do not bother a PLL which is already configured if (pll.isLocked() 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.postdiv2 == pll_regs.prim.read().POSTDIV2) { @@ -71,7 +75,7 @@ pub const PLL = enum { // load vco related dividers pll_regs.cs.modify(.{ .REFDIV = config.refdiv }); - pll_regs.fbdiv_int.modify(fbdiv); + pll_regs.fbdiv_int.modify(config.fbdiv); // turn on PLL pll_regs.pwr.modify(.{ .PD = 0, .VCOPD = 0 }); diff --git a/src/hal/uart.zig b/src/hal/uart.zig new file mode 100644 index 0000000..1816399 --- /dev/null +++ b/src/hal/uart.zig @@ -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(.{}); + } +}; diff --git a/src/rp2040.zig b/src/rp2040.zig index e96b901..571b648 100644 --- a/src/rp2040.zig +++ b/src/rp2040.zig @@ -44,14 +44,12 @@ pub const VectorTable = extern struct { }; pub const registers = struct { - /// System Control Space pub const SCS = struct { pub const base_address = 0xe000e000; /// System Tick Timer pub const SysTick = struct { - /// address: 0xe000e010 /// SysTick Control and Status Register pub const CTRL = @intToPtr(*volatile Mmio(32, packed struct { @@ -134,7 +132,6 @@ pub const registers = struct { /// Nested Vectored Interrupt Controller pub const NVIC = struct { - /// address: 0xe000e100 /// Interrupt Set Enable Register pub const ISER = @intToPtr(*volatile u32, base_address + 0x100); @@ -158,7 +155,6 @@ pub const registers = struct { /// System Control Block pub const SCB = struct { - /// address: 0xe000ed00 pub const CPUID = @intToPtr(*volatile Mmio(32, packed struct { REVISION: u4, @@ -343,7 +339,6 @@ pub const registers = struct { /// Memory Protection Unit pub const MPU = struct { - /// address: 0xe000ed90 /// MPU Type Register pub const TYPE = @intToPtr(*volatile Mmio(32, packed struct {