/* * Copyright (c) 2023 Andes Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT andestech_atcwdt200 #include #include #include #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL #include #include #include #include LOG_MODULE_REGISTER(wdt_andes); /* Watchdog register */ #define REG_IDR 0x00 #define REG_CTRL 0x10 #define REG_RESTAR 0x14 #define REG_WREN 0x18 #define REG_STATUS 0x1c #define WDT_CTRL(addr) (addr + REG_CTRL) #define WDT_RESTAR(addr) (addr + REG_RESTAR) #define WDT_WREN(addr) (addr + REG_WREN) #define WDT_STATUS(addr) (addr + REG_STATUS) /* Atcwdt200 magic number */ /* 0x10 Control Register */ #define WDT_CTRL_RSTTIME_POW_2_7 0x000 #define WDT_CTRL_RSTTIME_POW_2_8 0x100 #define WDT_CTRL_RSTTIME_POW_2_9 0x200 #define WDT_CTRL_RSTTIME_POW_2_10 0x300 #define WDT_CTRL_RSTTIME_POW_2_11 0x400 #define WDT_CTRL_RSTTIME_POW_2_12 0x500 #define WDT_CTRL_RSTTIME_POW_2_13 0x600 #define WDT_CTRL_RSTTIME_POW_2_14 0x700 #define WDT_CTRL_INTTIME_POW_2_6 0x000 #define WDT_CTRL_INTTIME_POW_2_8 0x010 #define WDT_CTRL_INTTIME_POW_2_10 0x020 #define WDT_CTRL_INTTIME_POW_2_11 0x030 #define WDT_CTRL_INTTIME_POW_2_12 0x040 #define WDT_CTRL_INTTIME_POW_2_13 0x050 #define WDT_CTRL_INTTIME_POW_2_14 0x060 #define WDT_CTRL_INTTIME_POW_2_15 0x070 #define WDT_CTRL_INTTIME_POW_2_17 0x080 #define WDT_CTRL_INTTIME_POW_2_19 0x090 #define WDT_CTRL_INTTIME_POW_2_21 0x0A0 #define WDT_CTRL_INTTIME_POW_2_23 0x0B0 #define WDT_CTRL_INTTIME_POW_2_25 0x0C0 #define WDT_CTRL_INTTIME_POW_2_27 0x0D0 #define WDT_CTRL_INTTIME_POW_2_29 0x0E0 #define WDT_CTRL_INTTIME_POW_2_31 0x0F0 #define WDT_CTRL_RSTEN 0x8 #define WDT_CTRL_INTEN 0x4 #define WDT_CTRL_APBCLK 0x2 #define WDT_CTRL_EXTCLK 0x0 #define WDT_CTRL_EN 0x1 /* Magic Number for Restart Register */ #define WDT_RESTART_NUM 0xcafe /* Magic Number for Write Enable Register */ #define WDT_WREN_NUM 0x5aa5 /* 0x1C Status Register */ #define WDT_ST_INTEXPIRED 0x1 #define WDT_ST_INTEXPIRED_CLR 0x1 /* * SMU(System Management Unit) Registers for hwinfo driver */ /* Register offset*/ #define SMU_RESET_WRSR 0x10 #define SMU_RESET_REGHI 0x60 #define SMU_RESET_REGLO 0x50 #define SMU_CMD 0x14 #define SMU_RESET_CMD 0x3c #define WDOGCFG_PERIOD_MIN BIT(7) #define WDOGCFG_PERIOD_MAX BIT(14) #define EXT_CLOCK_FREQ BIT(15) static const struct device *const syscon_dev = DEVICE_DT_GET(DT_NODELABEL(syscon)); static const struct device *const counter_dev = DEVICE_DT_GET(DT_NODELABEL(pit0)); struct counter_alarm_cfg alarm_cfg; struct wdt_atcwdt200_config { uintptr_t base; }; struct wdt_atcwdt200_dev_data { bool timeout_valid; counter_alarm_callback_t counter_callback; struct k_spinlock lock; }; static int wdt_atcwdt200_disable(const struct device *dev); static void wdt_counter_cb(const struct device *counter_dev, uint8_t chan_id, uint32_t counter, void *user_data) { const struct device *dev = DEVICE_DT_INST_GET(0); struct wdt_atcwdt200_dev_data *wdt_data = dev->data; uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; k_spinlock_key_t key; key = k_spin_lock(&wdt_data->lock); sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(WDT_RESTART_NUM, WDT_RESTAR(wdt_addr)); counter_set_channel_alarm(counter_dev, 2, &alarm_cfg); k_spin_unlock(&wdt_data->lock, key); } /** * @brief Set maximum length of timeout to watchdog * * @param dev Watchdog device struct */ static void wdt_atcwdt200_set_max_timeout(const struct device *dev) { struct wdt_atcwdt200_dev_data *data = dev->data; k_spinlock_key_t key; uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; uint32_t reg, counter_freq; key = k_spin_lock(&data->lock); counter_freq = counter_get_frequency(counter_dev); alarm_cfg.flags = 0; alarm_cfg.callback = wdt_counter_cb; alarm_cfg.user_data = &alarm_cfg; alarm_cfg.ticks = ((WDOGCFG_PERIOD_MAX * counter_freq) / EXT_CLOCK_FREQ) >> 1; reg = WDT_CTRL_RSTTIME_POW_2_14; sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(reg, WDT_CTRL(wdt_addr)); data->timeout_valid = true; k_spin_unlock(&data->lock, key); } static int wdt_atcwdt200_disable(const struct device *dev) { struct wdt_atcwdt200_dev_data *data = dev->data; uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; k_spinlock_key_t key; uint32_t reg; key = k_spin_lock(&data->lock); reg = sys_read32(WDT_CTRL(wdt_addr)); reg &= ~(WDT_CTRL_RSTEN | WDT_CTRL_EN); sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(reg, WDT_CTRL(wdt_addr)); k_spin_unlock(&data->lock, key); wdt_atcwdt200_set_max_timeout(dev); counter_cancel_channel_alarm(counter_dev, 2); return 0; } static int wdt_atcwdt200_setup(const struct device *dev, uint8_t options) { struct wdt_atcwdt200_dev_data *data = dev->data; uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; k_spinlock_key_t key; uint32_t reg; uint32_t ret = 0; if (!data->timeout_valid) { LOG_ERR("No valid timeouts installed"); return -EINVAL; } key = k_spin_lock(&data->lock); reg = sys_read32(WDT_CTRL(wdt_addr)); reg |= (WDT_CTRL_RSTEN | WDT_CTRL_EN); if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) == WDT_OPT_PAUSE_HALTED_BY_DBG) { counter_cancel_channel_alarm(counter_dev, 2); sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(reg, WDT_CTRL(wdt_addr)); goto out; } else { ret = counter_set_channel_alarm(counter_dev, 2, &alarm_cfg); if (ret != 0) { ret = -EINVAL; goto out; } sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(reg, WDT_CTRL(wdt_addr)); } out: k_spin_unlock(&data->lock, key); return ret; } /** * @brief Calculates the watchdog counter value (wdogcmp0) and * scaler (wdogscale) to be installed in the watchdog timer * * @param timeout Timeout value in milliseconds. * @param scaler Pointer to return scaler power of 2 * * @return Watchdog counter value */ static uint32_t wdt_atcwdt200_convtime(uint32_t timeout, uint32_t *scaler) { int i; uint32_t rst_period, cnt; cnt = (uint32_t)((timeout * EXT_CLOCK_FREQ) / 1000); rst_period = cnt; for (i = 0; i < 14 && cnt > 0; i++) { cnt >>= 1; } *scaler = i; return rst_period; } static int wdt_atcwdt200_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg) { struct wdt_atcwdt200_dev_data *data = dev->data; uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; k_spinlock_key_t key; uint32_t rst_period, reg, counter_freq, scaler; if (cfg->window.min != 0U || cfg->window.max == 0U) { return -EINVAL; } counter_freq = counter_get_frequency(counter_dev); rst_period = wdt_atcwdt200_convtime(cfg->window.max, &scaler); if (rst_period < 0 || WDOGCFG_PERIOD_MAX < rst_period) { LOG_ERR("Unsupported watchdog timeout\n"); return -EINVAL; } wdt_atcwdt200_disable(dev); key = k_spin_lock(&data->lock); switch (cfg->flags) { case WDT_FLAG_RESET_SOC: if (scaler < 7) { reg = WDT_CTRL_RSTTIME_POW_2_7; } else { scaler = scaler - 7; reg = scaler << 8; } alarm_cfg.flags = 0; alarm_cfg.callback = wdt_counter_cb; alarm_cfg.user_data = &alarm_cfg; alarm_cfg.ticks = (((cfg->window.max * counter_freq) / 1000) >> 1); break; case WDT_FLAG_RESET_NONE: case WDT_FLAG_RESET_CPU_CORE: default: LOG_ERR("Unsupported watchdog config flags\n"); k_spin_unlock(&data->lock, key); return -ENOTSUP; } sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(reg, WDT_CTRL(wdt_addr)); k_spin_unlock(&data->lock, key); return 0; } static int wdt_atcwdt200_feed(const struct device *dev, int channel_id) { uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base; ARG_UNUSED(channel_id); sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr)); sys_write32(WDT_RESTART_NUM, WDT_RESTAR(wdt_addr)); return 0; } static const struct wdt_driver_api wdt_atcwdt200_api = { .setup = wdt_atcwdt200_setup, .disable = wdt_atcwdt200_disable, .install_timeout = wdt_atcwdt200_install_timeout, .feed = wdt_atcwdt200_feed, }; static int wdt_atcwdt200_init(const struct device *dev) { struct wdt_atcwdt200_dev_data *data = dev->data; data->timeout_valid = false; data->counter_callback = wdt_counter_cb; uint32_t ret; counter_start(counter_dev); ret = syscon_write_reg(syscon_dev, SMU_RESET_REGLO, ((uint32_t)((unsigned long) Z_MEM_PHYS_ADDR(CONFIG_KERNEL_ENTRY)))); if (ret < 0) { return -EINVAL; } ret = syscon_write_reg(syscon_dev, SMU_RESET_REGHI, ((uint32_t)((uint64_t)((unsigned long) Z_MEM_PHYS_ADDR(CONFIG_KERNEL_ENTRY)) >> 32))); if (ret < 0) { return -EINVAL; } #ifdef CONFIG_WDT_DISABLE_AT_BOOT wdt_atcwdt200_disable(dev); #else data->timeout_valid = true; wdt_atcwdt200_set_max_timeout(dev); wdt_atcwdt200_setup(dev, 0x0); #endif return 0; } static struct wdt_atcwdt200_dev_data wdt_atcwdt200_data; static const struct wdt_atcwdt200_config wdt_atcwdt200_cfg = { .base = DT_INST_REG_ADDR(0), }; DEVICE_DT_INST_DEFINE(0, wdt_atcwdt200_init, NULL, &wdt_atcwdt200_data, &wdt_atcwdt200_cfg, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_atcwdt200_api);