|  | /* | 
|  | * Copyright (C) 2020 Katsuhiro Suzuki | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * @brief Watchdog (WDT) Driver for SiFive Freedom | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT sifive_wdt | 
|  |  | 
|  | #include <zephyr/kernel.h> | 
|  | #include <soc.h> | 
|  | #include <zephyr/drivers/watchdog.h> | 
|  |  | 
|  | #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL | 
|  | #include <zephyr/logging/log.h> | 
|  | #include <zephyr/irq.h> | 
|  | LOG_MODULE_REGISTER(wdt_sifive); | 
|  |  | 
|  | #define WDOGCFG_SCALE_MAX     0xf | 
|  | #define WDOGCFG_SCALE_SHIFT   0 | 
|  | #define WDOGCFG_SCALE_MASK    (WDOGCFG_SCALE_MAX << WDOGCFG_SCALE_SHIFT) | 
|  | #define WDOGCFG_RSTEN         BIT(8) | 
|  | #define WDOGCFG_ZEROCMP       BIT(9) | 
|  | #define WDOGCFG_ENALWAYS      BIT(12) | 
|  | #define WDOGCFG_COREAWAKE     BIT(13) | 
|  | #define WDOGCFG_IP0           BIT(28) | 
|  |  | 
|  | #define WDOGCMP_MAX        0xffff | 
|  |  | 
|  | #define WDOG_KEY           0x51f15e | 
|  | #define WDOG_FEED          0xd09f00d | 
|  |  | 
|  | #define WDOG_CLK           32768 | 
|  |  | 
|  | struct wdt_sifive_reg { | 
|  | /* offset: 0x000 */ | 
|  | uint32_t wdogcfg; | 
|  | uint32_t dummy0; | 
|  | uint32_t wdogcount; | 
|  | uint32_t dummy1; | 
|  | /* offset: 0x010 */ | 
|  | uint32_t wdogs; | 
|  | uint32_t dummy2; | 
|  | uint32_t wdogfeed; | 
|  | uint32_t wdogkey; | 
|  | /* offset: 0x020 */ | 
|  | uint32_t wdogcmp0; | 
|  | }; | 
|  |  | 
|  | struct wdt_sifive_device_config { | 
|  | uintptr_t regs; | 
|  | }; | 
|  |  | 
|  | struct wdt_sifive_dev_data { | 
|  | wdt_callback_t cb; | 
|  | bool enable_cb; | 
|  | bool timeout_valid; | 
|  | }; | 
|  |  | 
|  | #define DEV_REG(dev) \ | 
|  | ((struct wdt_sifive_reg *) \ | 
|  | ((const struct wdt_sifive_device_config *const)(dev)->config)->regs) | 
|  |  | 
|  | /** | 
|  | * @brief Set maximum length of timeout to watchdog | 
|  | * | 
|  | * @param dev Watchdog device struct | 
|  | */ | 
|  | static void wdt_sifive_set_max_timeout(const struct device *dev) | 
|  | { | 
|  | volatile struct wdt_sifive_reg *wdt = DEV_REG(dev); | 
|  | uint32_t t; | 
|  |  | 
|  | t = wdt->wdogcfg; | 
|  | t |= WDOGCFG_SCALE_MASK; | 
|  |  | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcfg = t; | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcmp0 = WDOGCMP_MAX; | 
|  | } | 
|  |  | 
|  | static void wdt_sifive_isr(const struct device *dev) | 
|  | { | 
|  | volatile struct wdt_sifive_reg *wdt = DEV_REG(dev); | 
|  | struct wdt_sifive_dev_data *data = dev->data; | 
|  | uint32_t t; | 
|  |  | 
|  | wdt_sifive_set_max_timeout(dev); | 
|  |  | 
|  | t = wdt->wdogcfg; | 
|  | t &= ~WDOGCFG_IP0; | 
|  |  | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcfg = t; | 
|  |  | 
|  | if (data->enable_cb && data->cb) { | 
|  | data->enable_cb = false; | 
|  | data->cb(dev, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int wdt_sifive_disable(const struct device *dev) | 
|  | { | 
|  | struct wdt_sifive_dev_data *data = dev->data; | 
|  |  | 
|  | wdt_sifive_set_max_timeout(dev); | 
|  |  | 
|  | data->enable_cb = false; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int wdt_sifive_setup(const struct device *dev, uint8_t options) | 
|  | { | 
|  | volatile struct wdt_sifive_reg *wdt = DEV_REG(dev); | 
|  | struct wdt_sifive_dev_data *data = dev->data; | 
|  | uint32_t t, mode; | 
|  |  | 
|  | if (!data->timeout_valid) { | 
|  | LOG_ERR("No valid timeouts installed"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | mode = WDOGCFG_ENALWAYS; | 
|  | if ((options & WDT_OPT_PAUSE_IN_SLEEP) == | 
|  | WDT_OPT_PAUSE_IN_SLEEP) { | 
|  | mode = WDOGCFG_COREAWAKE; | 
|  | } | 
|  | if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) == | 
|  | WDT_OPT_PAUSE_HALTED_BY_DBG) { | 
|  | mode = WDOGCFG_COREAWAKE; | 
|  | } | 
|  |  | 
|  | t = wdt->wdogcfg; | 
|  | t &= ~(WDOGCFG_ENALWAYS | WDOGCFG_COREAWAKE); | 
|  | t |= mode; | 
|  |  | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcfg = t; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Calculates the watchdog counter value (wdogcmp0) and | 
|  | *        scaler (wdogscale) to be installed in the watchdog timer | 
|  | * | 
|  | * @param timeout Timeout value in milliseconds. | 
|  | * @param clk     Clock of watchdog in Hz. | 
|  | * @param scaler  Pointer to return scaler power of 2 | 
|  | * | 
|  | * @return Watchdog counter value | 
|  | */ | 
|  | static int wdt_sifive_convtime(uint32_t timeout, int clk, int *scaler) | 
|  | { | 
|  | uint64_t cnt; | 
|  | int i; | 
|  |  | 
|  | cnt = (uint64_t)timeout * clk / 1000; | 
|  | for (i = 0; i < 16; i++) { | 
|  | if (cnt <= WDOGCMP_MAX) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | cnt >>= 1; | 
|  | } | 
|  |  | 
|  | if (i == 16) { | 
|  | /* Maximum counter and scaler */ | 
|  | LOG_ERR("Invalid timeout value allowed range"); | 
|  |  | 
|  | *scaler = WDOGCFG_SCALE_MAX; | 
|  | return WDOGCMP_MAX; | 
|  | } | 
|  |  | 
|  | *scaler = i; | 
|  |  | 
|  | return cnt; | 
|  | } | 
|  |  | 
|  | static int wdt_sifive_install_timeout(const struct device *dev, | 
|  | const struct wdt_timeout_cfg *cfg) | 
|  | { | 
|  | volatile struct wdt_sifive_reg *wdt = DEV_REG(dev); | 
|  | struct wdt_sifive_dev_data *data = dev->data; | 
|  | uint32_t mode = 0, t; | 
|  | int cmp, scaler; | 
|  |  | 
|  | if (data->timeout_valid) { | 
|  | LOG_ERR("No more timeouts can be installed"); | 
|  | return -ENOMEM; | 
|  | } | 
|  | if (cfg->window.min != 0U || cfg->window.max == 0U) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Freedom watchdog does not support window timeout config. | 
|  | * So use max field of window. | 
|  | */ | 
|  | cmp = wdt_sifive_convtime(cfg->window.max, WDOG_CLK, &scaler); | 
|  | if (cmp < 0 || WDOGCMP_MAX < cmp) { | 
|  | LOG_ERR("Unsupported watchdog timeout\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | switch (cfg->flags) { | 
|  | case WDT_FLAG_RESET_SOC: | 
|  | /* WDT supports global SoC reset but cannot callback. */ | 
|  | mode = WDOGCFG_RSTEN | WDOGCFG_ZEROCMP; | 
|  | break; | 
|  | case WDT_FLAG_RESET_NONE: | 
|  | /* No reset */ | 
|  | mode = WDOGCFG_ZEROCMP; | 
|  | break; | 
|  | case WDT_FLAG_RESET_CPU_CORE: | 
|  | default: | 
|  | LOG_ERR("Unsupported watchdog config flags\n"); | 
|  |  | 
|  | wdt_sifive_disable(dev); | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | t = wdt->wdogcfg; | 
|  | t &= ~(WDOGCFG_RSTEN | WDOGCFG_ZEROCMP | WDOGCFG_SCALE_MASK); | 
|  | t |= mode | scaler; | 
|  |  | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcfg = t; | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogcmp0 = cmp; | 
|  |  | 
|  | data->cb = cfg->callback; | 
|  | data->enable_cb = true; | 
|  | data->timeout_valid = true; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int wdt_sifive_feed(const struct device *dev, int channel_id) | 
|  | { | 
|  | volatile struct wdt_sifive_reg *wdt = DEV_REG(dev); | 
|  |  | 
|  | wdt->wdogkey = WDOG_KEY; | 
|  | wdt->wdogfeed = WDOG_FEED; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static DEVICE_API(wdt, wdt_sifive_api) = { | 
|  | .setup = wdt_sifive_setup, | 
|  | .disable = wdt_sifive_disable, | 
|  | .install_timeout = wdt_sifive_install_timeout, | 
|  | .feed = wdt_sifive_feed, | 
|  | }; | 
|  |  | 
|  | static void wdt_sifive_irq_config(void) | 
|  | { | 
|  | IRQ_CONNECT(DT_INST_IRQN(0), | 
|  | DT_INST_IRQ(0, priority), wdt_sifive_isr, | 
|  | DEVICE_DT_INST_GET(0), 0); | 
|  | irq_enable(DT_INST_IRQN(0)); | 
|  | } | 
|  |  | 
|  | static int wdt_sifive_init(const struct device *dev) | 
|  | { | 
|  | #ifdef CONFIG_WDT_DISABLE_AT_BOOT | 
|  | wdt_sifive_disable(dev); | 
|  | #endif | 
|  | wdt_sifive_irq_config(); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct wdt_sifive_dev_data wdt_sifive_data; | 
|  |  | 
|  | static const struct wdt_sifive_device_config wdt_sifive_cfg = { | 
|  | .regs = DT_INST_REG_ADDR(0), | 
|  | }; | 
|  |  | 
|  | DEVICE_DT_INST_DEFINE(0, wdt_sifive_init, NULL, | 
|  | &wdt_sifive_data, &wdt_sifive_cfg, PRE_KERNEL_1, | 
|  | CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sifive_api); |