sam0_common: add Watchdog implementation
This commit is contained in:
parent
04b92683e4
commit
c9f8ff1cf1
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
234
cpu/sam0_common/periph/wdt.c
Normal file
234
cpu/sam0_common/periph/wdt.c
Normal file
@ -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 <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#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 */
|
||||
Loading…
x
Reference in New Issue
Block a user