From a9955d3e177cdb251eb12adeac856798745515dc Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 15 Jun 2023 16:01:11 +0800 Subject: [PATCH] drivers: watchdog: Add Andestech ATCWDT200 driver. Support the Andes atcwdt200 watchdog driver. Signed-off-by: Kevin Wang --- .../riscv/adp_xc7k_ae350/adp_xc7k_ae350.dts | 6 +- .../riscv/adp_xc7k_ae350/adp_xc7k_ae350.yaml | 1 + drivers/watchdog/CMakeLists.txt | 1 + drivers/watchdog/Kconfig | 2 + drivers/watchdog/Kconfig.andes_atcwdt200 | 14 + drivers/watchdog/wdt_andes_atcwdt200.c | 366 ++++++++++++++++++ .../watchdog/andestech,atcwdt200.yaml | 18 + dts/riscv/andes/andes_v5_ae350.dtsi | 10 +- 8 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 drivers/watchdog/Kconfig.andes_atcwdt200 create mode 100644 drivers/watchdog/wdt_andes_atcwdt200.c create mode 100644 dts/bindings/watchdog/andestech,atcwdt200.yaml diff --git a/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.dts b/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.dts index 8ff809f022c..011e8e5597c 100644 --- a/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.dts +++ b/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.dts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Andes Technology Corporation + * Copyright (c) 2023 Andes Technology Corporation * * SPDX-License-Identifier: Apache-2.0 */ @@ -180,3 +180,7 @@ status = "okay"; cs-gpios = <&gpio0 7 GPIO_ACTIVE_LOW>; }; + +&wdt { + status = "okay"; +}; diff --git a/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.yaml b/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.yaml index 8a2a1031993..37e25bc5d8e 100644 --- a/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.yaml +++ b/boards/riscv/adp_xc7k_ae350/adp_xc7k_ae350.yaml @@ -12,6 +12,7 @@ supported: - i2c - spi - eeprom + - watchdog testing: ignore_tags: - bluetooth diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 12317539ac7..e06eb3d407c 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -40,5 +40,6 @@ 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) +zephyr_library_sources_ifdef(CONFIG_WDT_ANDES_ATCWDT200 wdt_andes_atcwdt200.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE wdt_handlers.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index cae4b526143..950c8c4e7c2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -110,4 +110,6 @@ source "drivers/watchdog/Kconfig.ifx_cat1" source "drivers/watchdog/Kconfig.opentitan" +source "drivers/watchdog/Kconfig.andes_atcwdt200" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.andes_atcwdt200 b/drivers/watchdog/Kconfig.andes_atcwdt200 new file mode 100644 index 00000000000..b4160c7bf24 --- /dev/null +++ b/drivers/watchdog/Kconfig.andes_atcwdt200 @@ -0,0 +1,14 @@ +# Kconfig Andes Watchdog configuration options +# +# Copyright (c) 2023 Andes Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +config WDT_ANDES_ATCWDT200 + bool "Andes Watchdog driver" + default y + depends on DT_HAS_ANDESTECH_ATCWDT200_ENABLED + select COUNTER + help + Enable driver for the Andes Watchdog driver. diff --git a/drivers/watchdog/wdt_andes_atcwdt200.c b/drivers/watchdog/wdt_andes_atcwdt200.c new file mode 100644 index 00000000000..6c314488568 --- /dev/null +++ b/drivers/watchdog/wdt_andes_atcwdt200.c @@ -0,0 +1,366 @@ +/* + * 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); diff --git a/dts/bindings/watchdog/andestech,atcwdt200.yaml b/dts/bindings/watchdog/andestech,atcwdt200.yaml new file mode 100644 index 00000000000..07d260dda2f --- /dev/null +++ b/dts/bindings/watchdog/andestech,atcwdt200.yaml @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023, Andes Technology Corporation. +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: Andes Watchdog driver + +compatible: "andestech,atcwdt200" + +include: base.yaml + +properties: + reg: + required: true + + interrupts: + required: true diff --git a/dts/riscv/andes/andes_v5_ae350.dtsi b/dts/riscv/andes/andes_v5_ae350.dtsi index fc4dab3cfff..ad4900eb5c4 100644 --- a/dts/riscv/andes/andes_v5_ae350.dtsi +++ b/dts/riscv/andes/andes_v5_ae350.dtsi @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Andes Technology Corporation + * Copyright (c) 2023 Andes Technology Corporation * * SPDX-License-Identifier: Apache-2.0 */ @@ -326,6 +326,14 @@ status = "disabled"; }; + wdt: wdt@f0500000 { + compatible = "andestech,atcwdt200"; + reg = <0xf0500000 0x1000>; + interrupts = <20 1>; + interrupt-parent = <&plic0>; + status = "disabled"; + }; + smc0: smc@e0400000 { compatible = "andestech,atfsmc020"; reg = <0xe0400000 0x1000>;