Skip to content

Commit

Permalink
- Added watchdog timer API for both RP2040 and RP2350 (#338)
Browse files Browse the repository at this point in the history
  • Loading branch information
haydenridd authored Dec 30, 2024
1 parent cd3180c commit 776a655
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 1 deletion.
2 changes: 2 additions & 0 deletions examples/raspberrypi/rp2xxx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions examples/raspberrypi/rp2xxx/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
50 changes: 50 additions & 0 deletions examples/raspberrypi/rp2xxx/src/watchdog_timer.zig
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
3 changes: 2 additions & 1 deletion port/raspberrypi/rp2xxx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ The RP2350 is very similar to the RP2040, but has enough differences to require
- [ ] spi
- [x] time
- [ ] uart
- [ ] usb
- [ ] usb
- [x] watchdog
4 changes: 4 additions & 0 deletions port/raspberrypi/rp2xxx/src/hal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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
Expand Down
198 changes: 198 additions & 0 deletions port/raspberrypi/rp2xxx/src/hal/watchdog.zig
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 776a655

Please sign in to comment.