Basic Watchdog implementation, not fully tested
authorNathael Pajani <nathael.pajani@ed3l.fr>
Mon, 23 Nov 2015 12:04:23 +0000 (13:04 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:04 +0000 (17:03 +0100)
core/system.c
core/watchdog.c [new file with mode: 0644]
include/core/lpc_regs_12xx.h
include/core/system.h
include/core/watchdog.h [new file with mode: 0644]

index 347295b..1ea1562 100644 (file)
@@ -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 (file)
index 0000000..431181b
--- /dev/null
@@ -0,0 +1,287 @@
+/****************************************************************************
+ *   core/watchdog.c
+ *
+ * Watchdog support
+ *
+ * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+/*
+ * This file implements support of the Windowed Watchdog (WWDT)
+ */
+
+#include <stdint.h>
+
+#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);
+}
+
+
index 36ef88f..60e1418 100644 (file)
@@ -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                             */
index 7f17911..98b5fdb 100644 (file)
@@ -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 */
  * 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 (file)
index 0000000..d261e9f
--- /dev/null
@@ -0,0 +1,123 @@
+/****************************************************************************
+ *   core/watchdog.h
+ *
+ * Watchdog support
+ *
+ * Copyright 2012 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+/*
+ * This file implements support of the Windowed Watchdog (WWDT)
+ */
+
+
+#ifndef CORE_WATCHDOG_H
+#define CORE_WATCHDOG_H
+
+#include <stdint.h>
+
+#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 */