From f9e6ebd306e61a1357ce68f03249fc4d4113a228 Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Mon, 23 Nov 2015 13:04:23 +0100 Subject: [PATCH] Basic Watchdog implementation, not fully tested --- core/system.c | 19 --- core/watchdog.c | 287 +++++++++++++++++++++++++++++++++++ include/core/lpc_regs_12xx.h | 23 +++ include/core/system.h | 5 +- include/core/watchdog.h | 123 +++++++++++++++ 5 files changed, 434 insertions(+), 23 deletions(-) create mode 100644 core/watchdog.c create mode 100644 include/core/watchdog.h diff --git a/core/system.c b/core/system.c index 347295b..1ea1562 100644 --- a/core/system.c +++ b/core/system.c @@ -78,25 +78,6 @@ static void flash_accelerator_config(uint32_t freq_sel) } } -/* Stop the watchdog */ -void stop_watchdog(void) -{ - struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; - struct lpc_watchdog* wdt = LPC_WDT; - - /* Power wadchdog block before changing it's configuration */ - if (! (sys_ctrl->sys_AHB_clk_ctrl & LPC_SYS_ABH_CLK_CTRL_Watchdog)) { - sys_ctrl->sys_AHB_clk_ctrl |= LPC_SYS_ABH_CLK_CTRL_Watchdog; - } - /* Stop watchdog */ - wdt->mode = 0; - wdt->feed_seqence = 0xAA; - wdt->feed_seqence = 0x55; - /* And power it down */ - sys_ctrl->sys_AHB_clk_ctrl &= ~(LPC_SYS_ABH_CLK_CTRL_Watchdog); - sys_ctrl->powerdown_run_cfg |= LPC_POWER_DOWN_WDT_OSC; -} - /* Configure the brown-out detection */ void system_brown_out_detection_config(uint32_t level) { diff --git a/core/watchdog.c b/core/watchdog.c new file mode 100644 index 0000000..431181b --- /dev/null +++ b/core/watchdog.c @@ -0,0 +1,287 @@ +/**************************************************************************** + * core/watchdog.c + * + * Watchdog support + * + * Copyright 2012 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + +/* + * This file implements support of the Windowed Watchdog (WWDT) + */ + +#include + +#include "core/lpc_regs_12xx.h" +#include "core/lpc_core_cm0.h" +#include "core/system.h" +#include "core/watchdog.h" + + + +/***************************************************************************** */ +void watchdog_feed(void) +{ + struct lpc_watchdog* wdt = LPC_WDT; + lpc_disable_irq(); + wdt->feed_seqence = 0xAA; + wdt->feed_seqence = 0x55; + lpc_enable_irq(); +} + +void WDT_Handler(void) +{ + /* Nothing to do, we are only waking up the vehicule by generating an interrupt */ + struct lpc_watchdog* wdt = LPC_WDT; + wdt->mode |= LPC_WDT_INTR_FLAG; +} + +/* Lock the watchdog clock source. Once the clock is locked, the configuration is + * permanent, there's no way to change it. Make all configuration steps before locking + * the watchdog Clock source. +*/ +void watchdog_lock_clk_src(void) +{ + struct lpc_watchdog* wdt = LPC_WDT; + wdt->clk_src_sel |= LPC_WDT_CLK_SRC_LOCK; +} + +/* Lock the watchdog clock source power. + * Once locked, writes to the current watchdog clock power bits in powerdown_*_cfg will + * have no effect. + * It is still possible to switch the watchdog clock source and turn of the clock if the + * watchdog clock source has not been locked yet. + */ +void watchdog_lock_clk_src_power(void) +{ + struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; + struct lpc_watchdog* wdt = LPC_WDT; + + if (wdt->clk_src_sel & LPC_WDT_CLK_WDOSC) { + sys_ctrl->powerdown_sleep_cfg &= ~(LPC_POWER_DOWN_WDT_OSC); + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_WDT_OSC); + } else { + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_IRC); + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_IRC_OUT); + } + wdt->mode |= LPC_WDT_CLK_POWER_LOCK; +} + +/* Lock the watchdog timer value */ +void watchdog_lock_timer_val(void) +{ + struct lpc_watchdog* wdt = LPC_WDT; + wdt->mode |= LPC_WDT_TIMER_VAL_PROTECT; +} + +/* Change the watchdog timer value, if not protected */ +void watchdog_set_timer_val(uint32_t nb_clk) +{ + struct lpc_watchdog* wdt = LPC_WDT; + + if (!(wdt->mode & LPC_WDT_TIMER_VAL_PROTECT)) { + wdt->timer_const = ((nb_clk >> 2) & LPC_WDT_TIMER_MAX); + } +} + +/* Lock the Watchdog enable bit. + * It is still possible to disable the watchdog by setting it's clock to an unpowered + * source if you did not lock the watchdog clock source and clock source power. + */ +void watchdog_lock_enable(void) +{ + struct lpc_watchdog* wdt = LPC_WDT; + wdt->mode |= LPC_WDT_EN_LOCK; +} +/* Lock the watchdog and all related features. + * Calls all the other watchdog_lock_* functions (clk_src, clk_src_power, timer_val, enable). + */ +void watchdog_lock_full(void) +{ + watchdog_lock_enable(); + watchdog_lock_timer_val(); + watchdog_lock_clk_src_power(); + watchdog_lock_clk_src(); +} + +/* Disable deep power down mode entry + * Calls to wfi() will allow entry in sleep and deep-sleep modes, but not deep-power-down mode. + */ +void watchdog_disable_power_down(void) +{ + struct lpc_watchdog* wdt = LPC_WDT; + wdt->mode |= LPC_WDT_POWER_DOWN_DISABLE; +} + +/* + * Configure the watchdog. + * clk_sel is either 0 (IRC) or 1 (WDTCLK). The corresponding clock source will be powered on. + * Note : only WDTCLK is running in deep power down mode + * Note : protecting the clock source power will prevent turning off the IRC for power saving + * if it is selected as main clock source. + */ +void watchdog_config(const struct wdt_config* wd_conf) +{ + struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; + struct lpc_watchdog* wdt = LPC_WDT; + + NVIC_DisableIRQ(WDT_IRQ); + /* Power wadchdog block before changing it's configuration */ + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 1); + /* Configure watchdog timeout for normal operation */ + wdt->timer_const = ((wd_conf->nb_clk >> 2) & LPC_WDT_TIMER_MAX); + /* If intr_mode_only is set, a watchdog timeout will trigger an interrupt instead of a reset */ + if (wd_conf->intr_mode_only == 1) { + wdt->mode = LPC_WDT_EN; + } else { + wdt->mode = LPC_WDT_EN | LPC_WDT_RESET_ON_TIMEOUT; + } + /* Watchdog clock select */ + if (wd_conf->clk_sel == LPC_WDT_CLK_IRC) { + sys_ctrl->powerdown_run_cfg &= ~(LPC_POWER_DOWN_IRC); + sys_ctrl->powerdown_run_cfg &= ~(LPC_POWER_DOWN_IRC_OUT); + wdt->clk_src_sel = LPC_WDT_CLK_IRC; + } else { + sys_ctrl->powerdown_run_cfg &= ~(LPC_POWER_DOWN_WDT_OSC); + wdt->clk_src_sel = LPC_WDT_CLK_WDOSC; + } + /* Use the windows functionnality ? */ + if (wd_conf->nb_clk_win > 0x100) { + wdt->window_compare = (wd_conf->nb_clk_win & LPC_WDT_TIMER_MAX); + } else { + wdt->window_compare = LPC_WDT_TIMER_MAX; + } + /* Warning interrupt ? */ + if (wd_conf->nb_clk_warn != 0) { + if (wd_conf->nb_clk_warn > LPC_WDT_WARNINT_MAXVAL) { + wdt->warning_int_compare = LPC_WDT_WARNINT_MAXVAL; + } else { + wdt->warning_int_compare = wd_conf->nb_clk_warn; + } + } + /* Protect any of the watchdog functions now ? */ + if (wd_conf->locks != 0) { + uint32_t mode = wdt->mode; + if (wd_conf->locks & WDT_CLK_POWER_LOCK) { + mode |= LPC_WDT_CLK_POWER_LOCK; + if (wd_conf->clk_sel == LPC_WDT_CLK_WDOSC) { + sys_ctrl->powerdown_sleep_cfg &= ~(LPC_POWER_DOWN_WDT_OSC); + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_WDT_OSC); + } else { + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_IRC); + sys_ctrl->powerdown_wake_cfg &= ~(LPC_POWER_DOWN_IRC_OUT); + } + } + if (wd_conf->locks & WDT_CLK_SRC_LOCK) { + wdt->clk_src_sel |= LPC_WDT_CLK_SRC_LOCK; + } + if (wd_conf->locks & WDT_EN_LOCK) { + mode |= LPC_WDT_EN_LOCK; + } + if (wd_conf->locks & WDT_TIMER_VAL_LOCK) { + mode |= LPC_WDT_TIMER_VAL_PROTECT; + } + if (wd_conf->locks & WDT_POWER_DOWN_LOCK) { + mode |= LPC_WDT_POWER_DOWN_DISABLE; + } + wdt->mode = mode; + } + /* Feed sequence to validate the configuration */ + watchdog_feed(); + NVIC_EnableIRQ(WDT_IRQ); +} + + +/* + * Stop the watchdog + * This function can be used during system operation to stop the watchdog if it has not + * been locked or protected against clock source modification, for example when entering + * sleep or deep sleep. + * It will also try to power-down the oscilators if not used for main clock. + * Return 0 if a solution has been found to stop the watchdog, or -1 if watchdog is still + * running after this call. + * TODO : Check this function, and implement the missing cases + */ +int stop_watchdog(void) +{ + struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; + struct lpc_watchdog* wdt = LPC_WDT; + int ret = -1; + + NVIC_DisableIRQ(WDT_IRQ); + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 1); + /* Clear enable bit ? */ + if (!(wdt->mode & LPC_WDT_EN_LOCK)) { + wdt->mode &= ~(LPC_WDT_EN); + watchdog_feed(); + ret = 0; + } else if (!(wdt->clk_src_sel & LPC_WDT_CLK_SRC_LOCK)) { + /* If Watchdog enable bit cannot be cleared, try to set watchdog clock to + * an unpowered clock source */ + /* If current clock is WDCLK and power to curent clock is protected, temprarily + * move clk source to IRC */ + if ((wdt->clk_src_sel == LPC_WDT_CLK_WDOSC) && (wdt->mode & LPC_WDT_CLK_POWER_LOCK)) { + wdt->clk_src_sel = LPC_WDT_CLK_IRC; + } + sys_ctrl->powerdown_run_cfg |= LPC_POWER_DOWN_WDT_OSC; + /* Power wadchdog block before changing it's configuration */ + wdt->clk_src_sel = LPC_WDT_CLK_WDOSC; + ret = 0; + } + if (ret != 0) { + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 0); + return -1; + } + if (wdt->mode & LPC_WDT_CLK_POWER_LOCK) { + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 0); + return 0; + } + /* If main clock and clkout not running from IRC (possibly through PLL), turn off IRC */ + if ((sys_ctrl->main_clk_sel != LPC_MAIN_CLK_SRC_IRC_OSC) && + ((sys_ctrl->main_clk_sel & 0x01) && (sys_ctrl->sys_pll_clk_sel != LPC_PLL_CLK_SRC_IRC_OSC)) && + (sys_ctrl->clk_out_src_sel != LPC_CLKOUT_SRC_IRC_OSC)) { + sys_ctrl->powerdown_run_cfg |= LPC_POWER_DOWN_IRC; + sys_ctrl->powerdown_run_cfg |= LPC_POWER_DOWN_IRC_OUT; + } + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 0); + return 0; +} + + + +/* + * Disable the watchdog + * This function can be used upon system startup to disable watchdog operation + */ +void startup_watchdog_disable(void) +{ + struct lpc_sys_control* sys_ctrl = LPC_SYS_CONTROL; + struct lpc_watchdog* wdt = LPC_WDT; + + /* Power wadchdog block before changing it's configuration */ + sys_ctrl->sys_AHB_clk_ctrl |= LPC_SYS_ABH_CLK_CTRL_Watchdog; + /* Stop watchdog */ + wdt->mode = 0; + watchdog_feed(); + /* And power it down */ + subsystem_power(LPC_SYS_ABH_CLK_CTRL_Watchdog, 0); + sys_ctrl->powerdown_run_cfg |= LPC_POWER_DOWN_WDT_OSC; + NVIC_DisableIRQ(WDT_IRQ); +} + + diff --git a/include/core/lpc_regs_12xx.h b/include/core/lpc_regs_12xx.h index 36ef88f..60e1418 100644 --- a/include/core/lpc_regs_12xx.h +++ b/include/core/lpc_regs_12xx.h @@ -212,6 +212,8 @@ struct lpc_sys_control #define LPC_CLKOUT_SRC_WATCHDOG_OSC 0x02 #define LPC_CLKOUT_SRC_MAIN_CLK 0x03 +#define LPC_WDT_DIVSEL(x) (((x) / 2) - 1) +#define LPC_WDT_FREQSEL_600KHz (0x01 << 5) /***************************************************************************** */ /* Flash Control */ @@ -773,6 +775,27 @@ struct lpc_watchdog }; #define LPC_WDT ((struct lpc_watchdog *) LPC_WDT_BASE) +#define LPC_WDT_TIMER_MAX 0xFFFFFF + +/* Mode register */ +#define LPC_WDT_EN (0x01 << 0) +#define LPC_WDT_RESET_ON_TIMEOUT (0x01 << 1) +#define LPC_WDT_TIMEOUT_FLAG (0x01 << 2) +#define LPC_WDT_INTR_FLAG (0x01 << 3) +#define LPC_WDT_TIMER_VAL_PROTECT (0x01 << 4) /* WDPROTECT */ +#define LPC_WDT_CLK_POWER_LOCK (0x01 << 5) /* WDLOCKCLK */ +#define LPC_WDT_POWER_DOWN_DISABLE (0x01 << 6) /* WDLOCKDP */ +#define LPC_WDT_EN_LOCK (0x01 << 7) /* WDLOCKDP */ + +/* Clk source */ +#define LPC_WDT_CLK_IRC (0x00 << 0) +#define LPC_WDT_CLK_WDOSC (0x01 << 0) +#define LPC_WDT_CLK_SRC_LOCK (0x01 << 31) + +/* Warning Interupt */ +#define LPC_WDT_WARNINT_CMPVAL(x) ((x) & 0x3FF) +#define LPC_WDT_WARNINT_MAXVAL 0x3FF + /***************************************************************************** */ /* Analog-to-Digital Converter */ diff --git a/include/core/system.h b/include/core/system.h index 7f17911..98b5fdb 100644 --- a/include/core/system.h +++ b/include/core/system.h @@ -29,6 +29,7 @@ #include "core/lpc_regs_12xx.h" #include "core/lpc_core_cm0.h" +#include "core/watchdog.h" /* Error Values, from glibc errno.h and errno-base.h */ @@ -56,10 +57,6 @@ * interfaces */ void system_set_default_power_state(void); - -/* Stop the watchdog */ -void stop_watchdog(void); - /***************************************************************************** */ /* Power */ /***************************************************************************** */ diff --git a/include/core/watchdog.h b/include/core/watchdog.h new file mode 100644 index 0000000..d261e9f --- /dev/null +++ b/include/core/watchdog.h @@ -0,0 +1,123 @@ +/**************************************************************************** + * core/watchdog.h + * + * Watchdog support + * + * Copyright 2012 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + +/* + * This file implements support of the Windowed Watchdog (WWDT) + */ + + +#ifndef CORE_WATCHDOG_H +#define CORE_WATCHDOG_H + +#include + +#define WDT_CLK_POWER_LOCK (0x01 << 0) +#define WDT_CLK_SRC_LOCK (0x01 << 1) +#define WDT_EN_LOCK (0x01 << 2) +#define WDT_TIMER_VAL_LOCK (0x01 << 3) +#define WDT_POWER_DOWN_LOCK (0x01 << 4) + + +#define WDT_CLK_IRC 0 +#define WDT_CLK_WDTCLK 1 +struct wdt_config { + /* clk_sel is either 0 (IRC) or 1 (WDTCLK). The corresponding clock source will be powered on. */ + int clk_sel; + int intr_mode_only; /* If set to 1, a watchdog timeout will trigger an interrupt instead of a reset */ + uint32_t locks; /* Bitfield from WDT_*_LOCK defined in watchdog.h */ + uint32_t nb_clk; /* Watchdog timer reload value : 0x3FF to 0x03FFFFFF */ + uint32_t nb_clk_win; /* Watchdog window value : 0x100 to 0x00FFFFFF */ + uint16_t nb_clk_warn; /* 0x00 to 0x3FF */ +}; + +/***************************************************************************** */ +void watchdog_feed(void); + + +/* Lock the watchdog clock source. Once the clock is locked, the configuration is + * permanent, there's no way to change it. Make all configuration steps before locking + * the watchdog Clock source. +*/ +void watchdog_lock_clk_src(void); + +/* Lock the watchdog clock source power. + * Once locked, writes to the current watchdog clock power bits in powerdown_*_cfg will + * have no effect. + * It is still possible to switch the watchdog clock source and turn of the clock if the + * watchdog clock source has not been locked yet. + */ +void watchdog_lock_clk_src_power(void); + +/* Lock the watchdog timer value */ +void watchdog_lock_timer_val(void); + +/* Change the watchdog timer value, if not protected */ +void watchdog_set_timer_val(uint32_t nb_clk); + +/* Lock the Watchdog enable bit. + * It is still possible to disable the watchdog by setting it's clock to an unpowered + * source if you did not lock the watchdog clock source and clock source power. + */ +void watchdog_lock_enable(void); + +/* Lock the watchdog and all related features. + * Calls all the other watchdog_lock_* functions (clk_src, clk_src_power, timer_val, enable). + */ +void watchdog_lock_full(void); + +/* Disable deep power down mode entry + * Calls to wfi() will allow entry in sleep and deep-sleep modes, but not deep-power-down mode. + */ +void watchdog_disable_power_down(void); + +/* + * Configure the watchdog. + * clk_sel is either 0 (IRC) or 1 (WDTCLK). The corresponding clock source will be powered on. + * Note : only WDTCLK is running in deep power down mode + * Note : protecting the clock source power will prevent turning off the IRC for power saving + * if it is selected as main clock source. + */ +void watchdog_config(const struct wdt_config* wd_conf); + + +/* + * Stop the watchdog + * This function can be used during system operation to stop the watchdog if it has not + * been locked or protected against clock source modification, for example when entering + * sleep or deep sleep. + * It will also try to power-down the oscilators if not used for main clock. + * Return 0 if a solution has been found to stop the watchdog, or -1 if watchdog is still + * running after this call. + * TODO : Check this function, and implement the missing cases + */ +int stop_watchdog(void); + + +/* + * Disable the watchdog + * This function can be used upon system startup to disable watchdog operation + */ +void startup_watchdog_disable(void); + + +#endif /* CORE_WATCHDOG_H */ -- 2.43.0