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

View file

@ -40,6 +40,9 @@ CONFIG_PWM=y
# ADC Driver
CONFIG_ADC=y
# WDT Driver
CONFIG_WATCHDOG=y
# ESPI Driver
CONFIG_ESPI=y

View file

@ -14,4 +14,5 @@ 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_WDT_NPCX wdt_npcx.c)
zephyr_sources_ifdef(CONFIG_USERSPACE wdt_handlers.c)

View file

@ -52,4 +52,6 @@ source "drivers/watchdog/Kconfig.gecko"
source "drivers/watchdog/Kconfig.sifive"
source "drivers/watchdog/Kconfig.npcx"
endif

View file

@ -0,0 +1,22 @@
# NPCX WDT driver configuration options
# Copyright (c) 2021 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
config WDT_NPCX
bool "Nuvoton NPCX embedded controller (EC) Watchdog Timer driver"
depends on SOC_FAMILY_NPCX
help
This option enables the Watchdog Timer driver for NPCX family of
processors.
Say y if you wish to use watchdog on NPCX MCU.
config WDT_NPCX_DELAY_CYCLES
int "Number of delay cycles before generating watchdog event/signal"
depends on WDT_NPCX
range 1 255
default 10
help
This option defines the window in which a watchdog event must be
handled, in units of 31ms. After this time window, the watchdog reset
triggers immediately.

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);

View file

@ -441,6 +441,13 @@
label = "ADC_0";
};
twd0: watchdog@400d8000 {
compatible = "nuvoton,npcx-watchdog";
reg = <0x400d8000 0x2000>;
t0_out = <&wui_t0out>;
label = "TWD_0";
};
espi0: espi@4000a000 {
compatible = "nuvoton,npcx-espi";
reg = <0x4000a000 0x2000>;

View file

@ -0,0 +1,21 @@
# Copyright (c) 2021 Nuvoton Technology Corporation.
# SPDX-License-Identifier: Apache-2.0
description: Nuvoton, NPCX-TWD node
compatible: "nuvoton,npcx-watchdog"
include: [base.yaml]
properties:
reg:
required: true
label:
required: true
t0_out:
type: phandle
required: true
description: |
Mapping table between Wake-Up Input (WUI) and t0-out timer expired signal.
For example, the WUI mapping on NPCX7 t0-out timer would be
t0_out = <&wui_t0out>;

View file

@ -455,6 +455,51 @@ struct adc_reg {
#define NPCX_THR_DCTL_THRD_EN 15
#define NPCX_THR_DCTL_THR_DVAL FIELD(0, 10)
/*
* Timer Watchdog (TWD) device registers
*/
struct twd_reg {
/* 0x000: Timer and Watchdog Configuration */
volatile uint8_t TWCFG;
volatile uint8_t reserved1;
/* 0x002: Timer and Watchdog Clock Prescaler */
volatile uint8_t TWCP;
volatile uint8_t reserved2;
/* 0x004: TWD Timer 0 */
volatile uint16_t TWDT0;
/* 0x006: TWDT0 Control and Status */
volatile uint8_t T0CSR;
volatile uint8_t reserved3;
/* 0x008: Watchdog Count */
volatile uint8_t WDCNT;
volatile uint8_t reserved4;
/* 0x00A: Watchdog Service Data Match */
volatile uint8_t WDSDM;
volatile uint8_t reserved5;
/* 0x00C: TWD Timer 0 Counter */
volatile uint16_t TWMT0;
/* 0x00E: Watchdog Counter */
volatile uint8_t TWMWD;
volatile uint8_t reserved6;
/* 0x010: Watchdog Clock Prescaler */
volatile uint8_t WDCP;
volatile uint8_t reserved7;
};
/* TWD register fields */
#define NPCX_TWCFG_LTWCFG 0
#define NPCX_TWCFG_LTWCP 1
#define NPCX_TWCFG_LTWDT0 2
#define NPCX_TWCFG_LWDCNT 3
#define NPCX_TWCFG_WDCT0I 4
#define NPCX_TWCFG_WDSDME 5
#define NPCX_T0CSR_RST 0
#define NPCX_T0CSR_TC 1
#define NPCX_T0CSR_WDLTD 3
#define NPCX_T0CSR_WDRST_STS 4
#define NPCX_T0CSR_WD_RUN 5
#define NPCX_T0CSR_TESDIS 7
/*
* Enhanced Serial Peripheral Interface (eSPI) device registers
*/

View file

@ -50,6 +50,17 @@ config ADC_NPCX
inputs can be measured and a internal voltage reference (VREF), 2.816V
(typical) is used for measurement.
config WDT_NPCX
default y
depends on WATCHDOG
help
Enable support for NPCX Watchdog driver. Besides watchdog
functionality, it also provides the protection mechanism over software
execution. After setting the configuration registers, the software can
lock it to provide a higher level of protection against subsequent
erroneous software action. Once a section of the TWD is locked, only
reset or the unlock sequence releases it.
config ESPI_NPCX
default y
depends on ESPI

View file

@ -66,6 +66,12 @@ NPCX_REG_OFFSET_CHECK(adc_reg, THRCTL1, 0x014);
NPCX_REG_OFFSET_CHECK(adc_reg, ADCCNF2, 0x020);
NPCX_REG_OFFSET_CHECK(adc_reg, CHNDAT, 0x040);
/* TWD register structure check */
NPCX_REG_SIZE_CHECK(twd_reg, 0x012);
NPCX_REG_OFFSET_CHECK(twd_reg, T0CSR, 0x006);
NPCX_REG_OFFSET_CHECK(twd_reg, TWMWD, 0x00e);
NPCX_REG_OFFSET_CHECK(twd_reg, WDCP, 0x010);
/* ESPI register structure check */
NPCX_REG_SIZE_CHECK(espi_reg, 0x500);
NPCX_REG_OFFSET_CHECK(espi_reg, FLASHCFG, 0x034);

View file

@ -83,6 +83,8 @@
#define WDT_NODE DT_INST(0, nxp_kinetis_wdog32)
#elif DT_HAS_COMPAT_STATUS_OKAY(microchip_xec_watchdog)
#define WDT_NODE DT_INST(0, microchip_xec_watchdog)
#elif DT_HAS_COMPAT_STATUS_OKAY(nuvoton_npcx_watchdog)
#define WDT_NODE DT_INST(0, nuvoton_npcx_watchdog)
#endif
#ifdef WDT_NODE