diff --git a/examples/raspberrypi/rp2xxx/README.md b/examples/raspberrypi/rp2xxx/README.md index 3f02f1fa..ae27c8f2 100644 --- a/examples/raspberrypi/rp2xxx/README.md +++ b/examples/raspberrypi/rp2xxx/README.md @@ -19,6 +19,8 @@ examples will eventually be able to run on either chip with no changes due to th Shows an example of a fully custom clock configuration. - [gpio clock output](src/gpio_clock_output.zig) on the [Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) or [Pico2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) boards Routes the SYS clock divided by 1000 out to GPIO25. +- [watchdog timer](src/watchdog_timer.zig) on the [Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) or [Pico2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) boards + Enables a watchdog timer for 1 second, and demonstrates the chip resetting when the watchdog timer elapses ### RP2040 Only diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 5f467d46..e26f3d85 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -47,6 +47,7 @@ pub fn build(b: *std.Build) void { .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, + .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, }; var available_examples = std.ArrayList(Example).init(b.allocator); diff --git a/examples/raspberrypi/rp2xxx/src/watchdog_timer.zig b/examples/raspberrypi/rp2xxx/src/watchdog_timer.zig new file mode 100644 index 00000000..95a63b55 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/watchdog_timer.zig @@ -0,0 +1,50 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const watchdog = rp2xxx.watchdog; +const time = rp2xxx.time; + +const pin_config = rp2xxx.pins.GlobalConfiguration{ + .GPIO25 = .{ + .name = "led", + .direction = .out, + }, +}; +const pins = pin_config.pins(); + +pub fn main() !void { + pin_config.apply(); + + // Set up different blinking behavior if this reset was due to a WD trip + const wd_reset: bool = if (watchdog.caused_reboot()) |_| + true + else + false; + + watchdog.apply(.{ + // Set up the watchdog timer to trip after 1 second + .duration_us = 1000 * 1000, + // Watchdog timer should NOT trip if a debugger is connected + // NOTE: This doesn't appear to work with a JLink, your mileage may vary + .pause_during_debug = true, + }); + + var blink_time: usize = 100; + var fast_counter: usize = 0; + + // Slowly decrease blink frequency until a watchdog reset is triggered from waiting too long between watchdog.update() calls + while (true) { + + // "null" uses whatever the previously configured timeout period for the watchdog was + watchdog.update(null); + pins.led.toggle(); + time.sleep_ms(@as(usize, @intCast(blink_time))); + + // 10 fast blinks to start if the WD caused reset + if (wd_reset and fast_counter < 10) { + fast_counter += 1; + } else { + blink_time += 100; + } + } +} diff --git a/port/raspberrypi/rp2xxx/README.md b/port/raspberrypi/rp2xxx/README.md index 690f52a3..b9aa51f7 100644 --- a/port/raspberrypi/rp2xxx/README.md +++ b/port/raspberrypi/rp2xxx/README.md @@ -24,4 +24,5 @@ The RP2350 is very similar to the RP2040, but has enough differences to require - [ ] spi - [x] time - [ ] uart -- [ ] usb \ No newline at end of file +- [ ] usb +- [x] watchdog \ No newline at end of file diff --git a/port/raspberrypi/rp2xxx/src/hal.zig b/port/raspberrypi/rp2xxx/src/hal.zig index 3d86844b..22655bf7 100644 --- a/port/raspberrypi/rp2xxx/src/hal.zig +++ b/port/raspberrypi/rp2xxx/src/hal.zig @@ -24,6 +24,7 @@ pub const i2c = @import("hal/i2c.zig"); pub const time = @import("hal/time.zig"); pub const uart = @import("hal/uart.zig"); pub const usb = @import("hal/usb.zig"); +pub const watchdog = @import("hal/watchdog.zig"); pub const drivers = @import("hal/drivers.zig"); pub const compatibility = @import("hal/compatibility.zig"); @@ -44,6 +45,9 @@ pub inline fn init() void { /// using the reccomended initialization sequence pub fn init_sequence(comptime clock_cfg: clocks.config.Global) void { + // Disable the watchdog as a soft reset doesn't disable the WD automatically! + watchdog.disable(); + // Reset all peripherals to put system into a known state, - except // for QSPI pads and the XIP IO bank, as this is fatal if running from // flash - and the PLLs, as this is fatal if clock muxing has not been diff --git a/port/raspberrypi/rp2xxx/src/hal/watchdog.zig b/port/raspberrypi/rp2xxx/src/hal/watchdog.zig new file mode 100644 index 00000000..553ce0a7 --- /dev/null +++ b/port/raspberrypi/rp2xxx/src/hal/watchdog.zig @@ -0,0 +1,198 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const WATCHDOG = microzig.chip.peripherals.WATCHDOG; +const PSM = microzig.chip.peripherals.PSM; +const hw = @import("hw.zig"); +const cpu = @import("compatibility.zig").cpu; + +pub const Config = + switch (cpu) { + .RP2040 => struct { + // See errata RP2040-E1, duration ends up getting multiplied by 2 reducing the allowable delay range + duration_us: u23, + pause_during_debug: bool, + }, + .RP2350 => struct { + duration_us: u24, + pause_during_debug: bool, + }, +}; + +var previous_watchdog_delay: ?u24 = null; + +/// Configure and enable the watchdog timer +pub fn apply(config: Config) void { + previous_watchdog_delay = config.duration_us; + + // Disable WD during changing settings + disable(); + + switch (cpu) { + .RP2040 => hw.set_alias(&PSM.WDSEL).write(.{ + .ROSC = 0, + .XOSC = 0, + .CLOCKS = 1, + .RESETS = 1, + .BUSFABRIC = 1, + .ROM = 1, + .SRAM0 = 1, + .SRAM1 = 1, + .SRAM2 = 1, + .SRAM3 = 1, + .SRAM4 = 1, + .SRAM5 = 1, + .XIP = 1, + .VREG_AND_CHIP_RESET = 1, + .SIO = 1, + .PROC0 = 1, + .PROC1 = 1, + .padding = 0, + }), + .RP2350 => hw.set_alias(&PSM.WDSEL).write(.{ + .PROC_COLD = 1, + .OTP = 1, + .ROSC = 0, + .XOSC = 0, + .RESETS = 1, + .CLOCKS = 1, + .PSM_READY = 1, + .BUSFABRIC = 1, + .ROM = 1, + .BOOTRAM = 1, + .SRAM0 = 1, + .SRAM1 = 1, + .SRAM2 = 1, + .SRAM3 = 1, + .SRAM4 = 1, + .SRAM5 = 1, + .SRAM6 = 1, + .SRAM7 = 1, + .SRAM8 = 1, + .SRAM9 = 1, + .XIP = 1, + .SIO = 1, + .ACCESSCTRL = 1, + .PROC0 = 1, + .PROC1 = 1, + .padding = 0, + }), + } + // Tell RESETS hardware to reset everything except ROSC/XOSC on a watchdog reset + + // See errata RP2040-E1, duration needs to be multiplied by 2 for RP2040 + const duration: u24 = if (cpu == .RP2040) @as(u24, config.duration_us) << 1 else config.duration_us; + WATCHDOG.LOAD.write(.{ + .LOAD = duration, + .padding = 0, + }); + hw.set_alias(&WATCHDOG.CTRL).write(.{ + .TIME = 0, + .PAUSE_JTAG = if (config.pause_during_debug) 1 else 0, + .PAUSE_DBG0 = if (config.pause_during_debug) 1 else 0, + .PAUSE_DBG1 = if (config.pause_during_debug) 1 else 0, + .reserved30 = 0, + .ENABLE = 0, + .TRIGGER = 0, + }); + + enable(); +} + +/// Used in a scratch register to determine if the reboot was due to a user enabled +/// watchdog reset, or ROM code using the watchdog +const WATCHDOG_NON_REBOOT_MAGIC: usize = 0x6ab73121; + +/// Enable (resume) the watchdog timer +pub fn enable() void { + // Update scratch[4] to distinguish from both the magic number used for rebooting to a + // specific address, or 0 used to reboot into regular flash path + WATCHDOG.SCRATCH4.write(.{ .SCRATCH4 = WATCHDOG_NON_REBOOT_MAGIC }); + + hw.set_alias(&WATCHDOG.CTRL).write(.{ + .TIME = 0, + .PAUSE_JTAG = 0, + .PAUSE_DBG0 = 0, + .PAUSE_DBG1 = 0, + .reserved30 = 0, + .ENABLE = 1, + .TRIGGER = 0, + }); +} + +/// Disable (pause) the watchdog timer +pub inline fn disable() void { + hw.clear_alias(&WATCHDOG.CTRL).write(.{ + .TIME = 0, + .PAUSE_JTAG = 0, + .PAUSE_DBG0 = 0, + .PAUSE_DBG1 = 0, + .reserved30 = 0, + .ENABLE = 1, + .TRIGGER = 0, + }); +} + +/// Update the watchdog delay (pet the watchdog), this should be called periodically +/// to keep the watchdog from triggering +/// +/// null for new_delay uses the previously configured delay from apply() +pub fn update(delay_us: ?u24) void { + var duration_us: u24 = if (delay_us) |nd| nd else if (previous_watchdog_delay) |pd| pd else std.debug.panic("update() called before watchdog configured with apply()", .{}); + + // See errata RP2040-E1, duration needs to be multiplied by 2 for RP2040 + if (cpu == .RP2040) duration_us = duration_us << 1; + WATCHDOG.LOAD.write(.{ + .LOAD = duration_us, + .padding = 0, + }); +} + +/// Force a watchdog processor reset to occur +/// +/// WARNING: Will reset the MCU! +pub inline fn force() void { + hw.set_alias(&WATCHDOG.CTRL).write(.{ + .TIME = 0, + .PAUSE_JTAG = 0, + .PAUSE_DBG0 = 0, + .PAUSE_DBG1 = 0, + .reserved30 = 0, + .ENABLE = 0, + .TRIGGER = 1, + }); +} + +/// The remaining us until a watchdog triggers +/// +/// NOTE: Due to a silicon bug on the RP2040 this always +/// just returns the configured ticks, NOT the remaining +/// ticks. RP2350 functions as expected. +pub fn remaining_us() u24 { + const rd = WATCHDOG.CTRL.read(); + return switch (cpu) { + .RP2040 => rd.TIME / 2, + .RP2350 => rd.TIME, + }; +} + +const TriggerType = enum { + UserInitiatedTimer, + UserInitiatedForce, + RomInitiatedTimer, + RomInitiatedForce, +}; + +/// Check if the watchdog was the reason for the last reboot +/// +/// TODO: RP2350 SDK masks this with a ROM function: +/// return watchdog_hw->reason && rom_get_last_boot_type() == BOOT_TYPE_NORMAL; +pub fn caused_reboot() ?TriggerType { + const scratch4 = WATCHDOG.SCRATCH4.read(); + const reason = WATCHDOG.REASON.read(); + if (reason.TIMER == 0 and reason.FORCE == 0) return null; + if (scratch4.SCRATCH4 == WATCHDOG_NON_REBOOT_MAGIC) { + return if (reason.TIMER == 1) .UserInitiatedTimer else .UserInitiatedForce; + } else { + return if (reason.TIMER == 1) .RomInitiatedTimer else .RomInitiatedForce; + } +}