| /* |
| * Copyright (c) 2021 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_npcx_watchdog |
| |
| /** |
| * @file |
| * @brief Nuvoton NPCX watchdog modules driver |
| * |
| * This file contains the drivers of NPCX Watchdog module that generates the |
| * clocks and interrupts (T0 Timer) used for its callback functions in the |
| * system. It also provides watchdog reset signal generation in response to a |
| * failure detection. Please refer the block diagram for more detail. |
| * |
| * +---------------------+ +-----------------+ |
| * LFCLK --->| T0 Prescale Counter |-+->| 16-Bit T0 Timer |--------> T0 Timer |
| * (32kHz) | (TWCP 1:32) | | | (TWDT0) | Event |
| * +---------------------+ | +-----------------+ |
| * +---------------------------------+ |
| * | |
| * | +-------------------+ +-----------------+ |
| * +--->| Watchdog Prescale |--->| 8-Bit Watchdog |-----> Watchdog Event/Reset |
| * | (WDCP 1:32) | | Counter (WDCNT) | after n clocks |
| * +-------------------+ +-----------------+ |
| * |
| */ |
| |
| #include "soc_miwu.h" |
| |
| #include <assert.h> |
| |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/watchdog.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| |
| #include <soc.h> |
| #include "soc_dbg.h" |
| LOG_MODULE_REGISTER(wdt_npcx, CONFIG_WDT_LOG_LEVEL); |
| |
| /* Watchdog operating frequency is fixed to LFCLK (32.768) kHz */ |
| #define NPCX_WDT_CLK LFCLK |
| |
| /* |
| * Maximum watchdog window time. Keep the timer and watchdog clock prescaler |
| * (TWCP) to 0x5. Since the watchdog counter is 8-bits, maximum time supported |
| * by npcx watchdog is 256 * (32 * 32768) / 32768 = 8192 sec. |
| * The maximum time supported of T0OUT is 65536 * 32 / 32768 = 64 sec. |
| * Thus, the maximum time of watchdog set here is 64 sec. |
| */ |
| #define NPCX_WDT_MAX_WND_TIME 64000UL |
| |
| /* |
| * Minimum watchdog window time. Ensure we have waited at least 3 watchdog |
| * clocks since touching WD timer. 3 / (32768 / 1024) HZ = 93.75ms |
| */ |
| #define NPCX_WDT_MIN_WND_TIME 100UL |
| |
| /* Timeout for reloading and restarting Timer 0. (Unit:ms) */ |
| #define NPCX_T0CSR_RST_TIMEOUT 2 |
| |
| /* Timeout for stopping watchdog. (Unit:ms) */ |
| #define NPCX_WATCHDOG_STOP_TIMEOUT 1 |
| |
| /* Device config */ |
| struct wdt_npcx_config { |
| /* wdt controller base address */ |
| uintptr_t base; |
| /* t0 timer wake-up input source configuration */ |
| const struct npcx_wui t0out; |
| }; |
| |
| /* Driver data */ |
| struct wdt_npcx_data { |
| /* Timestamp of touching watchdog last time */ |
| int64_t last_watchdog_touch; |
| /* Timeout callback used to handle watchdog event */ |
| wdt_callback_t cb; |
| /* Watchdog feed timeout in milliseconds */ |
| uint32_t timeout; |
| /* Indicate whether a watchdog timeout is installed */ |
| bool timeout_installed; |
| }; |
| |
| struct miwu_callback miwu_cb; |
| |
| /* Driver convenience defines */ |
| #define HAL_INSTANCE(dev) ((struct twd_reg *)((const struct wdt_npcx_config *)(dev)->config)->base) |
| |
| /* WDT local inline functions */ |
| static inline int wdt_t0out_reload(const struct device *dev) |
| { |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| uint64_t st; |
| |
| /* Reload and restart T0 timer */ |
| inst->T0CSR = (inst->T0CSR & ~BIT(NPCX_T0CSR_WDRST_STS)) | |
| BIT(NPCX_T0CSR_RST); |
| /* Wait for timer is loaded and restart */ |
| st = k_uptime_get(); |
| while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST)) { |
| if (k_uptime_get() - st > NPCX_T0CSR_RST_TIMEOUT) { |
| /* RST bit is still set? */ |
| if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST)) { |
| LOG_ERR("Timeout: reload T0 timer!"); |
| return -ETIMEDOUT; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static inline int wdt_wait_stopped(const struct device *dev) |
| { |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| uint64_t st; |
| |
| st = k_uptime_get(); |
| /* If watchdog is still running? */ |
| while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) { |
| if (k_uptime_get() - st > NPCX_WATCHDOG_STOP_TIMEOUT) { |
| /* WD_RUN bit is still set? */ |
| if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) { |
| LOG_ERR("Timeout: stop watchdog timer!"); |
| return -ETIMEDOUT; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* WDT local functions */ |
| static void wdt_t0out_isr(const struct device *dev, struct npcx_wui *wui) |
| { |
| struct wdt_npcx_data *const data = dev->data; |
| ARG_UNUSED(wui); |
| |
| LOG_DBG("WDT reset will issue after %d delay cycle! WUI(%d %d %d)", |
| CONFIG_WDT_NPCX_WARNING_LEADING_TIME_MS, wui->table, wui->group, wui->bit); |
| |
| /* Handle watchdog event here. */ |
| if (data->cb) { |
| data->cb(dev, 0); |
| } |
| } |
| |
| static void wdt_config_t0out_interrupt(const struct device *dev) |
| { |
| const struct wdt_npcx_config *const config = dev->config; |
| |
| /* Initialize a miwu device input and its callback function */ |
| npcx_miwu_init_dev_callback(&miwu_cb, &config->t0out, wdt_t0out_isr, |
| dev); |
| npcx_miwu_manage_callback(&miwu_cb, true); |
| |
| /* |
| * Configure the T0 wake-up event triggered from a rising edge |
| * on T0OUT signal. |
| */ |
| npcx_miwu_interrupt_configure(&config->t0out, |
| NPCX_MIWU_MODE_EDGE, NPCX_MIWU_TRIG_HIGH); |
| } |
| |
| /* WDT api functions */ |
| static int wdt_npcx_install_timeout(const struct device *dev, |
| const struct wdt_timeout_cfg *cfg) |
| { |
| struct wdt_npcx_data *const data = dev->data; |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| |
| /* If watchdog is already running */ |
| if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) { |
| return -EBUSY; |
| } |
| |
| /* No window watchdog support */ |
| if (cfg->window.min != 0) { |
| data->timeout_installed = false; |
| return -EINVAL; |
| } |
| |
| /* |
| * Since the watchdog counter in npcx series is 8-bits, maximum time |
| * supported by it is 256 * (32 * 32) / 32768 = 8 sec. This makes the |
| * allowed range of 1-8000 in milliseconds. Check if the provided value |
| * is within this range. |
| */ |
| if (cfg->window.max > NPCX_WDT_MAX_WND_TIME || cfg->window.max == 0) { |
| data->timeout_installed = false; |
| return -EINVAL; |
| } |
| |
| /* Save watchdog timeout */ |
| data->timeout = cfg->window.max; |
| |
| /* Install user timeout isr */ |
| data->cb = cfg->callback; |
| data->timeout_installed = true; |
| |
| return 0; |
| } |
| |
| static int wdt_npcx_setup(const struct device *dev, uint8_t options) |
| { |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| const struct wdt_npcx_config *const config = dev->config; |
| struct wdt_npcx_data *const data = dev->data; |
| uint32_t wd_cnt, pre_scal; |
| uint8_t wdcp; |
| |
| int rv; |
| |
| /* Disable irq of t0-out expired event first */ |
| npcx_miwu_irq_disable(&config->t0out); |
| |
| if (!data->timeout_installed) { |
| LOG_ERR("No valid WDT timeout installed"); |
| return -EINVAL; |
| } |
| |
| if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) { |
| LOG_ERR("WDT timer is busy"); |
| return -EBUSY; |
| } |
| |
| if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) { |
| LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP is not supported"); |
| return -ENOTSUP; |
| } |
| |
| /* Stall the WDT counter when halted by debugger */ |
| if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0) { |
| npcx_dbg_freeze_enable(true); |
| } else { |
| npcx_dbg_freeze_enable(false); |
| } |
| |
| /* |
| * One clock period of T0 timer is 32/32.768 KHz = 0.976 ms. |
| * Then the counter value is timeout/0.976 - 1. |
| */ |
| inst->TWDT0 = MAX(DIV_ROUND_UP(data->timeout * NPCX_WDT_CLK, |
| 32 * 1000) - 1, 1); |
| |
| /* Configure 8-bit watchdog counter |
| * Change the prescaler of watchdog clock for larger timeout |
| */ |
| wd_cnt = DIV_ROUND_UP((data->timeout + CONFIG_WDT_NPCX_WARNING_LEADING_TIME_MS) * |
| NPCX_WDT_CLK, |
| 32 * 1000); |
| |
| pre_scal = DIV_ROUND_UP(wd_cnt, 255); |
| |
| /* |
| * Find the smallest power of 2 greater than or equal to the |
| * prescaler |
| */ |
| wdcp = LOG2(pre_scal - 1) + 1; |
| pre_scal = 1 << wdcp; |
| |
| inst->WDCP = wdcp; |
| inst->WDCNT = wd_cnt / pre_scal; |
| |
| LOG_DBG("WDT setup: TWDT0, WDCNT are %d, %d", inst->TWDT0, inst->WDCNT); |
| |
| /* Reload and restart T0 timer */ |
| rv = wdt_t0out_reload(dev); |
| |
| /* Configure t0 timer interrupt and its isr. */ |
| wdt_config_t0out_interrupt(dev); |
| |
| /* Enable irq of t0-out expired event */ |
| npcx_miwu_irq_enable(&config->t0out); |
| |
| return rv; |
| } |
| |
| static int wdt_npcx_disable(const struct device *dev) |
| { |
| const struct wdt_npcx_config *const config = dev->config; |
| struct wdt_npcx_data *const data = dev->data; |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| uint16_t min_wnd_t; |
| |
| /* |
| * Ensure we have waited at least 3 watchdog ticks before |
| * stopping watchdog |
| */ |
| min_wnd_t = DIV_ROUND_UP(3 * NPCX_WDT_CLK, 32 * (1 << inst->WDCP)); |
| while (k_uptime_get() - data->last_watchdog_touch < min_wnd_t) { |
| continue; |
| } |
| |
| /* |
| * Stop and unlock watchdog by writing 87h, 61h and 63h |
| * sequence bytes to WDSDM register |
| */ |
| inst->WDSDM = 0x87; |
| inst->WDSDM = 0x61; |
| inst->WDSDM = 0x63; |
| |
| /* Disable irq of t0-out expired event and mark it uninstalled */ |
| npcx_miwu_irq_disable(&config->t0out); |
| data->timeout_installed = false; |
| |
| /* Wait until watchdog is stopped. */ |
| return wdt_wait_stopped(dev); |
| } |
| |
| static int wdt_npcx_feed(const struct device *dev, int channel_id) |
| { |
| ARG_UNUSED(channel_id); |
| struct wdt_npcx_data *const data = dev->data; |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| |
| /* Feed watchdog by writing 5Ch to WDSDM */ |
| inst->WDSDM = 0x5C; |
| data->last_watchdog_touch = k_uptime_get(); |
| |
| /* Reload and restart T0 timer */ |
| return wdt_t0out_reload(dev); |
| } |
| |
| /* WDT driver registration */ |
| static const struct wdt_driver_api wdt_npcx_driver_api = { |
| .setup = wdt_npcx_setup, |
| .disable = wdt_npcx_disable, |
| .install_timeout = wdt_npcx_install_timeout, |
| .feed = wdt_npcx_feed, |
| }; |
| |
| static int wdt_npcx_init(const struct device *dev) |
| { |
| struct twd_reg *const inst = HAL_INSTANCE(dev); |
| |
| #ifdef CONFIG_WDT_DISABLE_AT_BOOT |
| wdt_npcx_disable(dev); |
| #endif |
| |
| /* |
| * TWCFG (Timer Watchdog Configuration) setting |
| * [7:6]- Reserved = 0 |
| * [5] - WDSDME = 1: Feed watchdog by writing 5Ch to WDSDM |
| * [4] - WDCT0I = 1: Select T0IN as watchdog prescaler clock |
| * [3] - LWDCNT = 0: Don't lock WDCNT register |
| * [2] - LTWDT0 = 0: Don't lock TWDT0 register |
| * [1] - LTWCP = 0: Don't lock TWCP register |
| * [0] - LTWCFG = 0: Don't lock TWCFG register |
| */ |
| inst->TWCFG = BIT(NPCX_TWCFG_WDSDME) | BIT(NPCX_TWCFG_WDCT0I); |
| |
| /* Disable early touch functionality */ |
| inst->T0CSR = (inst->T0CSR & ~BIT(NPCX_T0CSR_WDRST_STS)) | |
| BIT(NPCX_T0CSR_TESDIS); |
| /* |
| * Plan clock frequency of T0 timer and watchdog timer as below: |
| * - T0 Timer freq is LFCLK/32 Hz |
| * - Watchdog freq is T0CLK/32 Hz (ie. LFCLK/1024 Hz) |
| */ |
| inst->WDCP = 0x05; /* Prescaler is 32 in Watchdog Timer */ |
| inst->TWCP = 0x05; /* Prescaler is 32 in T0 Timer */ |
| |
| return 0; |
| } |
| |
| static const struct wdt_npcx_config wdt_npcx_cfg_0 = { |
| .base = DT_INST_REG_ADDR(0), |
| .t0out = NPCX_DT_WUI_ITEM_BY_NAME(0, t0_out) |
| }; |
| |
| static struct wdt_npcx_data wdt_npcx_data_0; |
| |
| DEVICE_DT_INST_DEFINE(0, wdt_npcx_init, NULL, |
| &wdt_npcx_data_0, &wdt_npcx_cfg_0, |
| PRE_KERNEL_1, |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, |
| &wdt_npcx_driver_api); |