drivers: wdt: add watchdog driver support for NPCX7 series.

In npcx7 series, the Timer and Watchdog module (TWD) generates the
clocks and interrupts used for timing periodic functions in the system.
It also provides watchdog reset signal generation in response to a
failure detection.

The CL also includes:
    — Add npcx watchdog device tree declarations.
    — Zephyr watchdog api implementation.
    — Add Watchdog definitions for npcx7 series in
      tests/drivers/watchdog/wdt_basic_api/src/test_wdt.c for
      supporting test suites.

Signed-off-by: Mulin Chao <MLChao@nuvoton.com>
This commit is contained in:
Mulin Chao 2020-08-17 11:15:35 +08:00 committed by Anas Nashif
commit 1c21ca829b
11 changed files with 459 additions and 0 deletions

339
drivers/watchdog/wdt_npcx.c Normal file
View file

@ -0,0 +1,339 @@
/*
* 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 <assert.h>
#include <drivers/gpio.h>
#include <drivers/clock_control.h>
#include <drivers/watchdog.h>
#include <soc.h>
#include "soc_miwu.h"
#include <logging/log.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. Since the watchdog counter is 8-bits, maximum
* time supported by npcx watchdog is 256 * (32 * 32) / 32768 = 8 sec.
*/
#define NPCX_WDT_MAX_WND_TIME 8000UL
/*
* 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
/* 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_dev_callback miwu_cb;
/* Driver convenience defines */
#define DRV_CONFIG(dev) ((const struct wdt_npcx_config *)(dev)->config)
#define DRV_DATA(dev) ((struct wdt_npcx_data *)(dev)->data)
#define HAL_INSTANCE(dev) (struct twd_reg *)(DRV_CONFIG(dev)->base)
/* WDT local inline functions */
static inline void wdt_t0out_reload(const struct device *dev)
{
struct twd_reg *const inst = HAL_INSTANCE(dev);
unsigned int key;
key = irq_lock();
/* Reload and restart T0 timer */
inst->T0CSR |= BIT(NPCX_T0CSR_RST);
/* Wait for timer is loaded and restart */
while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST))
;
irq_unlock(key);
}
static inline void wdt_wait_stopped(const struct device *dev)
{
struct twd_reg *const inst = HAL_INSTANCE(dev);
unsigned int key;
key = irq_lock();
/* If watchdog is still running? */
while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN))
;
irq_unlock(key);
}
/* WDT local functions */
static void wdt_t0out_isr(const struct device *dev, struct npcx_wui *wui)
{
struct wdt_npcx_data *const data = DRV_DATA(dev);
ARG_UNUSED(wui);
/* Handle watchdog event here. */
if (data->cb) {
data->cb(dev, 0);
}
LOG_DBG("WDT issued! WUI(%d %d %d)", wui->table, wui->group, wui->bit);
/* Wait for watchdog event and reset occurred! */
while (1)
;
}
static void wdt_config_t0out_interrupt(const struct device *dev)
{
const struct wdt_npcx_config *const config = DRV_CONFIG(dev);
/* 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_dev_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 = DRV_DATA(dev);
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 = DRV_CONFIG(dev);
struct wdt_npcx_data *const data = DRV_DATA(dev);
/* 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;
}
if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0) {
LOG_ERR("WDT_OPT_PAUSE_HALTED_BY_DBG is not supported");
return -ENOTSUP;
}
/*
* 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(ceiling_fraction(data->timeout * NPCX_WDT_CLK,
32 * 1000) - 1, 1);
/* Configure 8-bit watchdog counter */
inst->WDCNT = MIN(ceiling_fraction(data->timeout, 32) +
CONFIG_WDT_NPCX_DELAY_CYCLES, 0xff);
LOG_DBG("WDT setup: TWDT0, WDCNT are %d, %d", inst->TWDT0, inst->WDCNT);
/* Reload and restart T0 timer */
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 0;
}
static int wdt_npcx_disable(const struct device *dev)
{
const struct wdt_npcx_config *const config = DRV_CONFIG(dev);
struct wdt_npcx_data *const data = DRV_DATA(dev);
struct twd_reg *const inst = HAL_INSTANCE(dev);
/*
* Ensure we have waited at least 3 watchdog ticks before
* stopping watchdog
*/
while (k_uptime_get() - data->last_watchdog_touch <
NPCX_WDT_MIN_WND_TIME)
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 for watchdof is stopped. */
wdt_wait_stopped(dev);
return 0;
}
static int wdt_npcx_feed(const struct device *dev, int channel_id)
{
ARG_UNUSED(channel_id);
struct wdt_npcx_data *const data = DRV_DATA(dev);
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 */
wdt_t0out_reload(dev);
return 0;
}
/* 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 |= 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, device_pm_control_nop,
&wdt_npcx_data_0, &wdt_npcx_cfg_0,
PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&wdt_npcx_driver_api);