diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index f7a00aae20d..cd40a5a7984 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_SMARTBOND wdt_smartbond.c) zephyr_library_sources_ifdef(CONFIG_WDT_TI_TPS382X wdt_ti_tps382x.c) zephyr_library_sources_ifdef(CONFIG_WDT_XILINX_AXI wdt_xilinx_axi.c) zephyr_library_sources_ifdef(CONFIG_WDT_INFINEON_CAT1 wdt_ifx_cat1.c) +zephyr_library_sources_ifdef(CONFIG_WDT_OPENTITAN wdt_opentitan.c) zephyr_library_sources_ifdef(CONFIG_WDT_DW wdt_dw.c wdt_dw_common.c) zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 96f3248cd56..cae4b526143 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -108,4 +108,6 @@ source "drivers/watchdog/Kconfig.xlnx" source "drivers/watchdog/Kconfig.ifx_cat1" +source "drivers/watchdog/Kconfig.opentitan" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.opentitan b/drivers/watchdog/Kconfig.opentitan new file mode 100644 index 00000000000..7a9c10a6987 --- /dev/null +++ b/drivers/watchdog/Kconfig.opentitan @@ -0,0 +1,13 @@ +# OpenTitan Always-On Timer support + +# Copyright (c) 2023, Rivos Inc. +# SPDX-License-Identifier: Apache-2.0 + +config WDT_OPENTITAN + bool "OpenTitan Always-On (AON) Timer" + depends on DT_HAS_LOWRISC_OPENTITAN_AONTIMER_ENABLED + default y + select HAS_WDT_MULTISTAGE + help + This option enables support for the watchdog portion of the OpenTitan AON + timer. diff --git a/drivers/watchdog/wdt_opentitan.c b/drivers/watchdog/wdt_opentitan.c new file mode 100644 index 00000000000..7f6fde21e41 --- /dev/null +++ b/drivers/watchdog/wdt_opentitan.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2023 by Rivos Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT lowrisc_opentitan_aontimer + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(wdt_opentitan, CONFIG_WDT_LOG_LEVEL); + +#define OT_REG_WDOG_REGWEN_OFFSET 0x10 +#define OT_REG_WDOG_CTRL_OFFSET 0x14 +#define OT_REG_WDOG_BARK_THOLD_OFFSET 0x18 +#define OT_REG_WDOG_BITE_THOLD_OFFSET 0x1C +#define OT_REG_WDOG_COUNT_OFFSET 0x20 +#define OT_REG_INTR_STATE_OFFSET 0x24 + +struct wdt_ot_aontimer_cfg { + uintptr_t regs; + uint32_t clk_freq; + bool wdog_lock; +}; + +struct wdt_ot_aontimer_data { + wdt_callback_t bark; +}; + +static int ot_aontimer_setup(const struct device *dev, uint8_t options) +{ + const struct wdt_ot_aontimer_cfg *const cfg = dev->config; + volatile uintptr_t regs = cfg->regs; + + sys_write32(0, regs + OT_REG_WDOG_COUNT_OFFSET); + sys_write32(1, regs + OT_REG_WDOG_CTRL_OFFSET); + if (cfg->wdog_lock) { + /* Force a read to ensure the timer was enabled. */ + (void) sys_read32(regs + OT_REG_WDOG_CTRL_OFFSET); + sys_write32(0, regs + OT_REG_WDOG_REGWEN_OFFSET); + } + return 0; +} + +static int ot_aontimer_disable(const struct device *dev) +{ + const struct wdt_ot_aontimer_cfg *const cfg = dev->config; + volatile uintptr_t regs = cfg->regs; + + if (!sys_read32(regs + OT_REG_WDOG_REGWEN_OFFSET)) { + LOG_ERR("Cannot disable - watchdog settings locked."); + return -EPERM; + } + + uint32_t ctrl_val = sys_read32(regs + OT_REG_WDOG_CTRL_OFFSET); + + if (!(ctrl_val & BIT(0))) { + return -EFAULT; + } + sys_write32(ctrl_val & ~BIT(0), regs + OT_REG_WDOG_CTRL_OFFSET); + + return 0; +} + +/* + * The OpenTitan AON Timer includes a multi-level watchdog timer. + * While the first stage supports an interrupt callback, the second does not. + * The second stage is mandatory to adjust the "bite" time window. + * + * Some boundaries are enforced to prevent behavior that is technically correct + * but is not likely intended. + * Bark: + * Minimum must be 0. Maximum must be > min. + * The bark interrupt occurs at max (or if the timeout is too long to be + * supported, the value x s.t. min < x < max and x is the largest valid timeout) + * Bite: + * Minimum must be >= bark.min, and maximum >= bark.max. If the timeout is too + * long to fit, it tries to find the value x s.t. min < x < max where x is the + * largest valid timeout. + * The bite action occurs max. + */ +static int ot_aontimer_install_timeout(const struct device *dev, + const struct wdt_timeout_cfg *cfg) +{ + struct wdt_ot_aontimer_data *data = dev->data; + const struct wdt_ot_aontimer_cfg *const dev_cfg = dev->config; + volatile uintptr_t reg_base = dev_cfg->regs; + const uint64_t max_window = (uint64_t) UINT32_MAX * 1000 / dev_cfg->clk_freq; + uint64_t bite_thold; +#ifdef CONFIG_WDT_MULTISTAGE + /* When multistage is selected, add an intermediate bark stage */ + uint64_t bark_thold; + struct wdt_timeout_cfg *bite = cfg->next; + + if (bite == NULL || bite->window.max < cfg->window.max || + (uint64_t) bite->window.min > max_window) { + return -EINVAL; + } + /* + * Flag must be clear for stage 1, and reset SOC for stage 2. + * CPU not supported + */ + if (cfg->flags != WDT_FLAG_RESET_NONE || bite->flags != WDT_FLAG_RESET_SOC) { + return -ENOTSUP; + } +#endif + + if (cfg->window.min > cfg->window.max || (uint64_t) cfg->window.min > max_window) { + return -EINVAL; + } + + if (!sys_read32(reg_base + OT_REG_WDOG_REGWEN_OFFSET)) { + LOG_ERR("Cannot install timeout - watchdog settings locked."); + return -ENOMEM; + } + + /* Watchdog is already enabled! */ + if (sys_read32(reg_base + OT_REG_WDOG_CTRL_OFFSET) & BIT(0)) { + return -EBUSY; + } + +#ifdef CONFIG_WDT_MULTISTAGE + /* Force 64-bit ops to ensure thresholds fits in the timer reg. */ + bark_thold = ((uint64_t) cfg->window.max * dev_cfg->clk_freq / 1000); + bite_thold = ((uint64_t) bite->window.max * dev_cfg->clk_freq / 1000); + /* Saturate these config values; min is verified to be < max_window */ + if (bark_thold > UINT32_MAX) { + bark_thold = UINT32_MAX; + } + if (bite_thold > UINT32_MAX) { + bite_thold = UINT32_MAX; + } + data->bark = cfg->callback; + sys_write32((uint32_t) bark_thold, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET); + sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET); +#else + bite_thold = ((uint64_t) cfg->window.max * dev_cfg->clk_freq / 1000); + /* Saturate this config value; min is verified to be < max_window */ + if (bite_thold > UINT32_MAX) { + bite_thold = UINT32_MAX; + } + if (cfg->flags == WDT_FLAG_RESET_NONE) { + /* Set bite -> bark, so we generate an interrupt instead of resetting */ + sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET); + /* Disable bite by writing it to max. Edge case is the bark = max. */ + sys_write32(UINT32_MAX, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET); + data->bark = cfg->callback; + } else { + data->bark = NULL; + /* Effectively disable bark by setting it to max */ + sys_write32(UINT32_MAX, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET); + sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET); + } +#endif + + return 0; +} + +static int ot_aontimer_feed(const struct device *dev, int channel_id) +{ + ARG_UNUSED(channel_id); + const struct wdt_ot_aontimer_cfg *const cfg = dev->config; + volatile uintptr_t regs = cfg->regs; + + sys_write32(0, regs + OT_REG_WDOG_COUNT_OFFSET); + + /* Deassert the interrupt line */ + sys_write32(BIT(1), regs + OT_REG_INTR_STATE_OFFSET); + return 0; +} + +static void wdt_ot_isr(const struct device *dev) +{ + const struct wdt_ot_aontimer_cfg *const cfg = dev->config; + struct wdt_ot_aontimer_data *data = dev->data; + volatile uintptr_t regs = cfg->regs; + + if (data->bark != NULL) { + data->bark(dev, 0); + } + + /* Deassert the interrupt line */ + sys_write32(BIT(1), regs + OT_REG_INTR_STATE_OFFSET); +} + +static int ot_aontimer_init(const struct device *dev) +{ + IRQ_CONNECT( + DT_INST_IRQ_BY_IDX(0, 0, irq), + DT_INST_IRQ_BY_IDX(0, 0, priority), wdt_ot_isr, + DEVICE_DT_INST_GET(0), 0); + irq_enable(DT_INST_IRQ_BY_IDX(0, 0, irq)); + + return 0; +} + +static struct wdt_ot_aontimer_data ot_aontimer_data; + +static struct wdt_ot_aontimer_cfg ot_aontimer_cfg = { + .regs = (volatile uintptr_t) DT_INST_REG_ADDR(0), + .clk_freq = DT_INST_PROP(0, clock_frequency), + .wdog_lock = DT_INST_PROP(0, wdog_lock), +}; + +static const struct wdt_driver_api ot_aontimer_api = { + .setup = ot_aontimer_setup, + .disable = ot_aontimer_disable, + .install_timeout = ot_aontimer_install_timeout, + .feed = ot_aontimer_feed, +}; + +DEVICE_DT_INST_DEFINE(0, ot_aontimer_init, NULL, + &ot_aontimer_data, &ot_aontimer_cfg, PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &ot_aontimer_api);