diff --git a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig index 020ad5b5d0c..2a1bdc07d82 100644 --- a/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig +++ b/boards/arm/npcx7m6fb_evb/npcx7m6fb_evb_defconfig @@ -40,6 +40,9 @@ CONFIG_PWM=y # ADC Driver CONFIG_ADC=y +# WDT Driver +CONFIG_WATCHDOG=y + # ESPI Driver CONFIG_ESPI=y diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index df8875d4ecf..33c1cc49b5d 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -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) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 6a35f18e038..0e803dab3b9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -52,4 +52,6 @@ source "drivers/watchdog/Kconfig.gecko" source "drivers/watchdog/Kconfig.sifive" +source "drivers/watchdog/Kconfig.npcx" + endif diff --git a/drivers/watchdog/Kconfig.npcx b/drivers/watchdog/Kconfig.npcx new file mode 100644 index 00000000000..ef59801507d --- /dev/null +++ b/drivers/watchdog/Kconfig.npcx @@ -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. diff --git a/drivers/watchdog/wdt_npcx.c b/drivers/watchdog/wdt_npcx.c new file mode 100644 index 00000000000..4944e8140c5 --- /dev/null +++ b/drivers/watchdog/wdt_npcx.c @@ -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 +#include +#include +#include +#include + +#include "soc_miwu.h" +#include +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); diff --git a/dts/arm/nuvoton/npcx7m6fb.dtsi b/dts/arm/nuvoton/npcx7m6fb.dtsi index 45724f10897..6d44a1ba53b 100644 --- a/dts/arm/nuvoton/npcx7m6fb.dtsi +++ b/dts/arm/nuvoton/npcx7m6fb.dtsi @@ -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>; diff --git a/dts/bindings/watchdog/nuvoton,npcx-watchdog.yaml b/dts/bindings/watchdog/nuvoton,npcx-watchdog.yaml new file mode 100644 index 00000000000..3d07a918823 --- /dev/null +++ b/dts/bindings/watchdog/nuvoton,npcx-watchdog.yaml @@ -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>; diff --git a/soc/arm/nuvoton_npcx/common/reg/reg_def.h b/soc/arm/nuvoton_npcx/common/reg/reg_def.h index 97f122dbfc4..70bf5e8bf2d 100644 --- a/soc/arm/nuvoton_npcx/common/reg/reg_def.h +++ b/soc/arm/nuvoton_npcx/common/reg/reg_def.h @@ -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 */ diff --git a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series index add19fa39e5..df527f90ad0 100644 --- a/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series +++ b/soc/arm/nuvoton_npcx/npcx7/Kconfig.defconfig.series @@ -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 diff --git a/soc/arm/nuvoton_npcx/npcx7/soc.c b/soc/arm/nuvoton_npcx/npcx7/soc.c index b4f516ad5d8..0c5cd241191 100644 --- a/soc/arm/nuvoton_npcx/npcx7/soc.c +++ b/soc/arm/nuvoton_npcx/npcx7/soc.c @@ -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); diff --git a/tests/drivers/watchdog/wdt_basic_api/src/test_wdt.c b/tests/drivers/watchdog/wdt_basic_api/src/test_wdt.c index 60e616331aa..1900335c072 100644 --- a/tests/drivers/watchdog/wdt_basic_api/src/test_wdt.c +++ b/tests/drivers/watchdog/wdt_basic_api/src/test_wdt.c @@ -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