| /* |
| * 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> |
| 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 const struct wdt_driver_api 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); |