diff --git a/CODEOWNERS b/CODEOWNERS index 87a3c883b1b..747ba99ef42 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -292,6 +292,7 @@ /drivers/i2c/i2c_dw* @dcpleung /drivers/*/*xec* @franciscomunoz @albertofloyd @scottwcpg /drivers/watchdog/*gecko* @oanerer +/drivers/watchdog/*sifive* @katsuster /drivers/watchdog/wdt_handlers.c @andrewboie /drivers/wifi/ @jukkar @tbursztyka @pfalcon /drivers/wifi/esp/ @mniestroj diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index bbf9540b8e4..df8875d4ecf 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -13,4 +13,5 @@ zephyr_sources_ifdef(CONFIG_WDT_MCUX_WDOG32 wdt_mcux_wdog32.c) zephyr_sources_ifdef(CONFIG_WDT_MCUX_WWDT wdt_mcux_wwdt.c) zephyr_sources_ifdef(CONFIG_WDT_XEC wdt_mchp_xec.c) zephyr_sources_ifdef(CONFIG_WDT_GECKO wdt_gecko.c) +zephyr_sources_ifdef(CONFIG_WDT_SIFIVE wdt_sifive.c) zephyr_sources_ifdef(CONFIG_USERSPACE wdt_handlers.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index dde6f5fccf7..6a35f18e038 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -50,4 +50,6 @@ source "drivers/watchdog/Kconfig.xec" source "drivers/watchdog/Kconfig.gecko" +source "drivers/watchdog/Kconfig.sifive" + endif diff --git a/drivers/watchdog/Kconfig.sifive b/drivers/watchdog/Kconfig.sifive new file mode 100644 index 00000000000..ae582986fab --- /dev/null +++ b/drivers/watchdog/Kconfig.sifive @@ -0,0 +1,11 @@ +# SiFive Freedom WDT configuration + +# Copyright (C) 2020 Katsuhiro Suzuki +# SPDX-License-Identifier: Apache-2.0 + +config WDT_SIFIVE + bool "SiFive Watchdog (WDT) Driver" + depends on SOC_RISCV_SIFIVE_FREEDOM + default y + help + This option enables WDT driver for SiFive Freedom. diff --git a/drivers/watchdog/wdt_sifive.c b/drivers/watchdog/wdt_sifive.c new file mode 100644 index 00000000000..897c6e28142 --- /dev/null +++ b/drivers/watchdog/wdt_sifive.c @@ -0,0 +1,289 @@ +/* + * 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 +#include +#include + +#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL +#include +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_CFG(dev) \ + ((const struct wdt_sifive_device_config *const)(dev)->config) +#define DEV_REG(dev) \ + ((struct wdt_sifive_reg *)(DEV_CFG(dev))->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, +}; + +DEVICE_DT_INST_DECLARE(0); + +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, device_pm_control_nop, + &wdt_sifive_data, &wdt_sifive_cfg, PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sifive_api);