188 lines
5.5 KiB
C
188 lines
5.5 KiB
C
/*
|
|
* Copyright (c) 2022, Jamie McCrae
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT raspberrypi_pico_watchdog
|
|
|
|
#include <hardware/watchdog.h>
|
|
#include <hardware/structs/psm.h>
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/sys_clock.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
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_CLK_REF_FREQ_WDT_TICK_DIVISOR 1000000
|
|
|
|
struct wdt_rpi_pico_data {
|
|
uint8_t reset_type;
|
|
uint32_t load;
|
|
bool enabled;
|
|
};
|
|
|
|
struct wdt_rpi_pico_config {
|
|
const struct device *clk_dev;
|
|
clock_control_subsys_t clk_id;
|
|
};
|
|
|
|
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;
|
|
uint32_t ref_clk;
|
|
int err;
|
|
|
|
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;
|
|
|
|
err = clock_control_on(config->clk_dev, config->clk_id);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
err = clock_control_get_rate(config->clk_dev, config->clk_id, &ref_clk);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
|
|
watchdog_hw->tick = (ref_clk / RPI_PICO_CLK_REF_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 * USEC_PER_MSEC > 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 * USEC_PER_MSEC * 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 = { \
|
|
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(idx)), \
|
|
.clk_id = (clock_control_subsys_t)DT_INST_PHA_BY_IDX(idx, clocks, 0, clk_id), \
|
|
}; \
|
|
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);
|