From c9f8ff1cf13c273eafd4baabf434c0aedc255fb5 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 10 May 2019 15:28:44 +0200 Subject: [PATCH 1/2] sam0_common: add Watchdog implementation --- cpu/sam0_common/Makefile.features | 1 + cpu/sam0_common/include/periph_cpu_common.h | 20 ++ cpu/sam0_common/periph/wdt.c | 234 ++++++++++++++++++++ 3 files changed, 255 insertions(+) create mode 100644 cpu/sam0_common/periph/wdt.c diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index f5cf9a55e3cc..63eb3f31ae16 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -4,5 +4,6 @@ FEATURES_PROVIDED += periph_flashpage_raw FEATURES_PROVIDED += periph_flashpage_rwee FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_uart_modecfg +FEATURES_PROVIDED += periph_wdt periph_wdt_cb -include $(RIOTCPU)/cortexm_common/Makefile.features diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index 2056dbf94216..fc43467598c7 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -496,6 +496,26 @@ typedef struct { } sam0_common_usb_config_t; #endif /* USB_INST_NUM */ +/** + * @name WDT upper and lower bound times in ms + * @{ + */ +/* Limits are in clock cycles according to data sheet. + As the WDT is clocked by a 1024 Hz clock, 1 cycle ≈ 1 ms */ +#define NWDT_TIME_LOWER_LIMIT (8U) +#define NWDT_TIME_UPPER_LIMIT (16384U) +/** @} */ + + +/** + * @brief Watchdog can be stopped. + */ +#define WDT_HAS_STOP (1) +/** + * @brief Watchdog has to be initialized. + */ +#define WDT_HAS_INIT (1) + #ifdef __cplusplus } #endif diff --git a/cpu/sam0_common/periph/wdt.c b/cpu/sam0_common/periph/wdt.c new file mode 100644 index 000000000000..cdfecbff05d9 --- /dev/null +++ b/cpu/sam0_common/periph/wdt.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2019 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_sam0_common + * @ingroup drivers_periph_wdt + * @{ + * + * @file wdt.c + * @brief Low-level WDT driver implementation + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include "periph/wdt.h" +#include "pm_layered.h" +#include "board.h" + +#include "debug.h" + +#ifndef WDT_CLOCK_HZ +#define WDT_CLOCK_HZ 1024 +#endif + +/* work around inconsistency in header files */ +#ifndef WDT_CONFIG_PER_8_Val +#define WDT_CONFIG_PER_8_Val WDT_CONFIG_PER_CYC8_Val +#endif +#ifndef WDT_CONFIG_PER_8K_Val +#define WDT_CONFIG_PER_8K_Val WDT_CONFIG_PER_CYC8192_Val +#endif +#ifndef WDT_CONFIG_PER_16K_Val +#define WDT_CONFIG_PER_16K_Val WDT_CONFIG_PER_CYC16384_Val +#endif + + +static inline void _set_enable(bool on) +{ +/* work around strange watchdog behaviour if IDLE2 is used on samd21 */ +#ifdef CPU_FAM_SAMD21 + if (on) { + pm_block(1); + } +#endif + +#ifdef WDT_CTRLA_ENABLE + WDT->CTRLA.bit.ENABLE = on; +#else + WDT->CTRL.bit.ENABLE = on; +#endif +} + +static inline void _wait_syncbusy(void) +{ +#ifdef WDT_STATUS_SYNCBUSY + while (WDT->STATUS.bit.SYNCBUSY) {} +#else + while (WDT->SYNCBUSY.reg) {} +#endif +} + +static uint32_t ms_to_per(uint32_t ms) +{ + const uint32_t cycles = (ms * WDT_CLOCK_HZ) / 1024; + + /* Minimum WDT period is 8 clock cycles (register value 0) */ + if (cycles <= 8) { + return 0; + } + + /* Round up to next pow2 and calculate the register value */ + return 29 - __builtin_clz(cycles - 1); +} + +#ifdef CPU_SAMD21 +static void _wdt_clock_setup(void) +{ +/* RTC / RTT will alredy set up GCLK2 as needed */ +#if !defined(MODULE_PERIPH_RTC) && !defined(MODULE_PERIPH_RTT) + /* Setup clock GCLK2 with OSCULP32K divided by 32 */ + GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_DIVSEL; + + while (GCLK->STATUS.bit.SYNCBUSY) {} +#endif + + /* Connect to GCLK2 (~1.024 kHz) */ + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT + | GCLK_CLKCTRL_GEN_GCLK2 + | GCLK_CLKCTRL_CLKEN; +} +#else +static void _wdt_clock_setup(void) +{ + /* nothing to do here */ +} +#endif + +void wdt_init(void) +{ + _wdt_clock_setup(); +#ifdef MCLK + MCLK->APBAMASK.bit.WDT_ = 1; +#else + PM->APBAMASK.bit.WDT_ = 1; +#endif + + _set_enable(0); + NVIC_EnableIRQ(WDT_IRQn); +} + +void wdt_setup_reboot(uint32_t min_time, uint32_t max_time) +{ + uint32_t per, win; + + if (max_time == 0) { + DEBUG("invalid period: max_time = %"PRIu32"\n", max_time); + return; + } + + per = ms_to_per(max_time); + + if (per > WDT_CONFIG_PER_16K_Val) { + DEBUG("invalid period: max_time = %"PRIu32"\n", max_time); + return; + } + + if (min_time) { + win = ms_to_per(min_time); + + if (win > WDT_CONFIG_PER_8K_Val) { + DEBUG("invalid period: min_time = %"PRIu32"\n", min_time); + return; + } + + if (per < win) { + per = win + 1; + } + +#ifdef WDT_CTRLA_WEN + WDT->CTRLA.bit.WEN = 1; +#else + WDT->CTRL.bit.WEN = 1; +#endif + } else { + win = 0; +#ifdef WDT_CTRLA_WEN + WDT->CTRLA.bit.WEN = 0; +#else + WDT->CTRL.bit.WEN = 0; +#endif + } + + WDT->INTFLAG.reg = WDT_INTFLAG_EW; + + DEBUG("watchdog window: %"PRIu32", period: %"PRIu32"\n", win, per); + + WDT->CONFIG.reg = WDT_CONFIG_WINDOW(win) | WDT_CONFIG_PER(per); + _wait_syncbusy(); +} + +void wdt_stop(void) +{ + _set_enable(0); + _wait_syncbusy(); +} + +void wdt_start(void) +{ + _set_enable(1); + _wait_syncbusy(); +} + +void wdt_kick(void) +{ + WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY_Val; +} + +#ifdef MODULE_PERIPH_WDT_CB +static wdt_cb_t cb; +static void* cb_arg; + +void wdt_setup_reboot_with_callback(uint32_t min_time, uint32_t max_time, + wdt_cb_t wdt_cb, void *arg) +{ + uint32_t per = ms_to_per(max_time); + + if (per == WDT_CONFIG_PER_8_Val && wdt_cb) { + DEBUG("period too short for early warning\n"); + return; + } + + cb = wdt_cb; + cb_arg = arg; + + if (cb != NULL) { + uint32_t warning_offset = ms_to_per(WDT_WARNING_PERIOD); + + if (warning_offset == 0) { + warning_offset = 1; + } + + if (warning_offset >= per) { + warning_offset = per - 1; + } + + WDT->INTENSET.reg = WDT_INTENSET_EW; + WDT->EWCTRL.bit.EWOFFSET = per - warning_offset; + } else { + WDT->INTENCLR.reg = WDT_INTENCLR_EW; + } + + wdt_setup_reboot(min_time, max_time); +} + +void isr_wdt(void) +{ + WDT->INTFLAG.reg = WDT_INTFLAG_EW; + + if (cb != NULL) { + cb(cb_arg); + } + + cortexm_isr_end(); +} +#endif /* MODULE_PERIPH_WDT_CB */ From a4baf45da19e9096068f550f9c9b5cf2f26babd7 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Fri, 13 Sep 2019 13:20:35 +0200 Subject: [PATCH 2/2] tests/periph_wdt: only test powers of two Some watchdog implementations do not support arbitrary precision for timeouts. Using powers of two seems like a good common denominator. --- tests/periph_wdt/tests/01-run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/periph_wdt/tests/01-run.py b/tests/periph_wdt/tests/01-run.py index 403cff7c9882..388f75c7f4d2 100755 --- a/tests/periph_wdt/tests/01-run.py +++ b/tests/periph_wdt/tests/01-run.py @@ -13,7 +13,7 @@ # We test only up to 10ms, with smaller times mcu doesn't have time to # print system time before resetting -reset_times_ms = [1e2, 5e2, 1e3, 5e3] +reset_times_ms = [128, 512, 1024, 8192] # We don't check for accuracy, only order of magnitude. Some MCU use an # an internal un-calibrated clock as reference which can deviate in