/* * Copyright (c) 2022, Jamie McCrae * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT raspberrypi_pico_watchdog #include #include #include #include LOG_MODULE_REGISTER(wdt_rpi_pico, CONFIG_WDT_LOG_LEVEL); /* Maximum watchdog time is halved due to errata RP2040-E1 */ #define RPI_PICO_MAX_WDT_TIME (0xffffff / 2) #define RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR 2 /* Watchdog requires a 1MHz clock source, divided from the crystal oscillator */ #define RPI_PICO_XTAL_FREQ_WDT_TICK_DIVISOR 1000000 struct wdt_rpi_pico_data { uint8_t reset_type; uint32_t load; bool enabled; }; struct wdt_rpi_pico_config { uint32_t xtal_frequency; }; static int wdt_rpi_pico_setup(const struct device *dev, uint8_t options) { const struct wdt_rpi_pico_config *config = dev->config; struct wdt_rpi_pico_data *data = dev->data; if ((options & WDT_OPT_PAUSE_IN_SLEEP) == 1) { return -ENOTSUP; } hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS); psm_hw->wdsel = 0; /* TODO: Handle individual core reset when SMP support for RP2040 is added */ if (data->reset_type == WDT_FLAG_RESET_SOC) { hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_BITS); } else if (data->reset_type == WDT_FLAG_RESET_CPU_CORE) { hw_set_bits(&psm_hw->wdsel, PSM_WDSEL_PROC0_BITS); } if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0) { hw_clear_bits(&watchdog_hw->ctrl, (WATCHDOG_CTRL_PAUSE_JTAG_BITS | WATCHDOG_CTRL_PAUSE_DBG0_BITS | WATCHDOG_CTRL_PAUSE_DBG1_BITS)); } else { hw_set_bits(&watchdog_hw->ctrl, (WATCHDOG_CTRL_PAUSE_JTAG_BITS | WATCHDOG_CTRL_PAUSE_DBG0_BITS | WATCHDOG_CTRL_PAUSE_DBG1_BITS)); } watchdog_hw->load = data->load; /* Zero out the scratch registers so that the module reboots at the * default program counter */ watchdog_hw->scratch[4] = 0; watchdog_hw->scratch[5] = 0; watchdog_hw->scratch[6] = 0; watchdog_hw->scratch[7] = 0; hw_set_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS); data->enabled = true; watchdog_hw->tick = (config->xtal_frequency / RPI_PICO_XTAL_FREQ_WDT_TICK_DIVISOR) | WATCHDOG_TICK_ENABLE_BITS; return 0; } static int wdt_rpi_pico_disable(const struct device *dev) { struct wdt_rpi_pico_data *data = dev->data; if (data->enabled == false) { return -EFAULT; } hw_clear_bits(&watchdog_hw->ctrl, WATCHDOG_CTRL_ENABLE_BITS); data->enabled = false; return 0; } static int wdt_rpi_pico_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg) { struct wdt_rpi_pico_data *data = dev->data; if (cfg->window.min != 0U || cfg->window.max == 0U) { return -EINVAL; } else if (cfg->window.max > RPI_PICO_MAX_WDT_TIME) { return -EINVAL; } else if (cfg->callback != NULL) { return -ENOTSUP; } else if ((cfg->flags & WDT_FLAG_RESET_MASK) == WDT_FLAG_RESET_NONE) { /* The RP2040 does technically support this mode, but requires * a program counter and stack pointer value to be set, * therefore do not allow configuring in this mode */ return -EINVAL; } data->load = (cfg->window.max * RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR); data->reset_type = (cfg->flags & WDT_FLAG_RESET_MASK); return 0; } static int wdt_rpi_pico_feed(const struct device *dev, int channel_id) { struct wdt_rpi_pico_data *data = dev->data; if (channel_id != 0) { /* There is only one input to the watchdog */ return -EINVAL; } if (data->enabled == false) { /* Watchdog is not running so does not need to be fed */ return -EINVAL; } watchdog_hw->load = data->load; return 0; } static int wdt_rpi_pico_init(const struct device *dev) { #ifndef CONFIG_WDT_DISABLE_AT_BOOT return wdt_rpi_pico_setup(dev, WDT_OPT_PAUSE_HALTED_BY_DBG); #endif return 0; } static const struct wdt_driver_api wdt_rpi_pico_driver_api = { .setup = wdt_rpi_pico_setup, .disable = wdt_rpi_pico_disable, .install_timeout = wdt_rpi_pico_install_timeout, .feed = wdt_rpi_pico_feed, }; #define WDT_RPI_PICO_WDT_DEVICE(idx) \ static const struct wdt_rpi_pico_config wdt_##idx##_config = { \ .xtal_frequency = DT_INST_PROP_BY_PHANDLE(idx, clocks, clock_frequency) \ }; \ static struct wdt_rpi_pico_data wdt_##idx##_data = { \ .reset_type = WDT_FLAG_RESET_SOC, \ .load = (CONFIG_WDT_RPI_PICO_INITIAL_TIMEOUT * \ RPI_PICO_WDT_TIME_MULTIPLICATION_FACTOR), \ .enabled = false \ }; \ DEVICE_DT_DEFINE(DT_NODELABEL(wdt##idx), wdt_rpi_pico_init, NULL, &wdt_##idx##_data, \ &wdt_##idx##_config, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &wdt_rpi_pico_driver_api) DT_INST_FOREACH_STATUS_OKAY(WDT_RPI_PICO_WDT_DEVICE);