mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-03 09:31:56 +00:00
The correct procedure to reload the T0 timer is: 1. Load TWDT0 register with the new value or write 1 to RST bit in T0CSR register to load the old value. 2. Wait until RST bit in T0CSR register becomes 1. 3. Wait until RST bit in T0CSR register becomes 0. The current watchdog driver misses step 2. Fix the issue in this commit. Signed-off-by: Jun Lin <CHLin56@nuvoton.com>
397 lines
11 KiB
C
397 lines
11 KiB
C
/*
|
|
* 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_CLEAR_TIMEOUT 2
|
|
#define NPCX_T0CSR_RST_SET_TIMEOUT 1
|
|
|
|
/* 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 the T0CSR_RST bit to change from 0 to 1. This transition is expected to occur
|
|
* over multiple LFCLK cycles. If a timeout occurs, we can assume that the bit has changed
|
|
* from 0 -> 1 -> 0, but the polling thread missed it because it was preempted by
|
|
* higher-priority threads. In this case, we can simply return.
|
|
*/
|
|
st = k_uptime_get();
|
|
while (!IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST)) {
|
|
if (k_uptime_get() - st > NPCX_T0CSR_RST_SET_TIMEOUT) {
|
|
return 0;
|
|
}
|
|
}
|
|
/* 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_CLEAR_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 DEVICE_API(wdt, 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);
|