summaryrefslogtreecommitdiff
path: root/bsp
diff options
context:
space:
mode:
authorrcitach <[email protected]>2026-06-11 14:19:26 +0800
committerGitHub <[email protected]>2026-06-11 14:19:26 +0800
commitbb5c90489a3fa6982fbf67444ddde5bff103fc81 (patch)
treeff127eae5c445734200271d90c6940cfd6b5ba9a /bsp
parentdd84cc0334a9b08ca7c80cbe4f807dc62885539c (diff)
[BSP][NS800RT7P65] Add WDG driver for NS800RT7xxx#11451
Diffstat (limited to 'bsp')
-rw-r--r--bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript5
-rw-r--r--bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.c472
-rw-r--r--bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.h32
-rw-r--r--bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.c273
-rw-r--r--bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.h30
-rw-r--r--bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml9
-rw-r--r--bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig18
7 files changed, 838 insertions, 1 deletions
diff --git a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript
index f37b3c74ae..5662cb9b62 100644
--- a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript
+++ b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/SConscript
@@ -30,6 +30,11 @@ if GetDepend('BSP_USING_SPI'):
if GetDepend('BSP_USING_QSPI'):
src += ['drv_qspi.c']
+if GetDepend('BSP_USING_WDT'):
+ src += ['drv_iwdg.c']
+ if GetDepend('BSP_USING_WWDG'):
+ src += ['drv_wwdg.c']
+
group = DefineGroup('HAL_Drivers', src, depend = [''], CPPPATH = path)
Return('group')
diff --git a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.c b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.c
new file mode 100644
index 0000000000..69c18a25d0
--- /dev/null
+++ b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.c
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-13 Jeffery Yuan first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include "board.h"
+#include "drv_iwdg.h"
+
+#ifdef BSP_USING_IWDG1
+#include "iwdg1.h"
+#endif
+
+#ifdef BSP_USING_IWDG2
+#include "iwdg2.h"
+#endif
+
+#if defined(RT_USING_WDT) && (defined(BSP_USING_IWDG1) || defined(BSP_USING_IWDG2))
+
+#define DBG_TAG "drv.iwdg"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define NS800_USEC_PER_SEC 1000000ULL
+#define NS800_MSEC_PER_SEC 1000ULL
+#define NS800_IWDG1_CLK_HZ 10000000ULL
+#define NS800_IWDG2_CLK_HZ 128000ULL
+#define NS800_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+enum ns800_iwdg_id
+{
+ NS800_IWDG1 = 0,
+ NS800_IWDG2,
+};
+
+struct ns800_iwdg
+{
+ rt_watchdog_t watchdog;
+ enum ns800_iwdg_id id;
+ const char *name;
+ void *base;
+ rt_bool_t started;
+ rt_uint64_t timeout_us;
+ union
+ {
+#ifdef BSP_USING_IWDG1
+ struct
+ {
+ IWDG1_Prescaler prescaler;
+ IWDG1_Timeout timeout;
+ } iwdg1;
+#endif
+#ifdef BSP_USING_IWDG2
+ struct
+ {
+ IWDG2_Prescaler prescaler;
+ rt_uint16_t reload;
+ } iwdg2;
+#endif
+ } hw;
+};
+
+static rt_uint64_t ns800_abs_diff_u64(rt_uint64_t a, rt_uint64_t b)
+{
+ return (a > b) ? (a - b) : (b - a);
+}
+
+static rt_uint32_t ns800_timeout_to_seconds(rt_uint64_t timeout_us)
+{
+ return (rt_uint32_t)((timeout_us + NS800_USEC_PER_SEC - 1ULL) / NS800_USEC_PER_SEC);
+}
+
+static rt_uint32_t ns800_timeout_to_ms(rt_uint64_t timeout_us)
+{
+ return (rt_uint32_t)((timeout_us + NS800_MSEC_PER_SEC - 1ULL) / NS800_MSEC_PER_SEC);
+}
+
+#ifdef BSP_USING_IWDG1
+
+static struct ns800_iwdg iwdg1_dev;
+
+static const rt_uint8_t iwdg1_prescaler_code[] =
+{
+ IWDG1_PRESCALE_DIV1,
+ IWDG1_PRESCALE_DIV2,
+ IWDG1_PRESCALE_DIV4,
+ IWDG1_PRESCALE_DIV8,
+ IWDG1_PRESCALE_DIV16,
+ IWDG1_PRESCALE_DIV32,
+ IWDG1_PRESCALE_DIV128,
+ IWDG1_PRESCALE_DIV256,
+ IWDG1_PRESCALE_DIV512,
+ IWDG1_PRESCALE_DIV1024,
+ IWDG1_PRESCALE_DIV2048,
+ IWDG1_PRESCALE_DIV4096,
+ IWDG1_PRESCALE_DIV8192,
+ IWDG1_PRESCALE_DIV64,
+};
+
+static const rt_uint32_t iwdg1_prescaler_div[] =
+{
+ 1U, 2U, 4U, 8U, 16U, 32U, 128U, 256U, 512U, 1024U, 2048U, 4096U, 8192U, 64U,
+};
+
+static const rt_uint16_t iwdg1_timeout_count[] =
+{
+ 1024U, 4096U, 8192U, 16384U,
+};
+
+static rt_uint64_t iwdg1_calc_timeout_us(rt_uint32_t div, rt_uint32_t count)
+{
+ return ((rt_uint64_t)count * div * NS800_USEC_PER_SEC + NS800_IWDG1_CLK_HZ / 2ULL) / NS800_IWDG1_CLK_HZ;
+}
+
+static void iwdg1_select_config(rt_uint32_t timeout_s, struct ns800_iwdg *dev)
+{
+ rt_uint64_t target_us = (rt_uint64_t)timeout_s * NS800_USEC_PER_SEC;
+ rt_uint64_t best_diff = (rt_uint64_t)-1;
+ rt_uint32_t best_prescaler = 0;
+ rt_uint32_t best_timeout = 0;
+ rt_uint32_t psc;
+ rt_uint32_t top;
+
+ if (target_us == 0)
+ {
+ target_us = iwdg1_calc_timeout_us(iwdg1_prescaler_div[0], iwdg1_timeout_count[0]);
+ }
+
+ for (psc = 0; psc < NS800_ARRAY_SIZE(iwdg1_prescaler_code); psc++)
+ {
+ for (top = 0; top < NS800_ARRAY_SIZE(iwdg1_timeout_count); top++)
+ {
+ rt_uint64_t actual_us = iwdg1_calc_timeout_us(iwdg1_prescaler_div[psc], iwdg1_timeout_count[top]);
+ rt_uint64_t diff = ns800_abs_diff_u64(actual_us, target_us);
+
+ if (diff < best_diff)
+ {
+ best_diff = diff;
+ best_prescaler = psc;
+ best_timeout = top;
+ dev->timeout_us = actual_us;
+ }
+ }
+ }
+
+ dev->hw.iwdg1.prescaler = (IWDG1_Prescaler)iwdg1_prescaler_code[best_prescaler];
+ dev->hw.iwdg1.timeout = (IWDG1_Timeout)best_timeout;
+}
+
+static void iwdg1_apply_config(struct ns800_iwdg *dev)
+{
+ IWDG1_configModule((IWDG1_TypeDef *)dev->base,
+ RESET_EN,
+ LPRUN,
+ dev->hw.iwdg1.prescaler,
+ HEAD_PERCENT100,
+ END_PERCENT0,
+ dev->hw.iwdg1.timeout);
+ IWDG1_clearErrorStatus((IWDG1_TypeDef *)dev->base);
+ IWDG1_clearIntStatus((IWDG1_TypeDef *)dev->base);
+}
+
+void IWDG1_Handler(void)
+{
+ rt_interrupt_enter();
+ IWDG1_clearErrorStatus((IWDG1_TypeDef *)iwdg1_dev.base);
+ IWDG1_clearIntStatus((IWDG1_TypeDef *)iwdg1_dev.base);
+ while (IWDG1_getIntStatus((IWDG1_TypeDef *)iwdg1_dev.base))
+ {
+ ;
+ }
+ IWDG1_enableModule((IWDG1_TypeDef *)iwdg1_dev.base);
+ rt_interrupt_leave();
+}
+
+#endif /* BSP_USING_IWDG1 */
+
+#ifdef BSP_USING_IWDG2
+
+static struct ns800_iwdg iwdg2_dev;
+
+static const rt_uint8_t iwdg2_prescaler_code[] =
+{
+ IWDG2_PRESCALE_DIV128,
+ IWDG2_PRESCALE_DIV2048,
+ IWDG2_PRESCALE_DIV1024,
+ IWDG2_PRESCALE_DIV512,
+ IWDG2_PRESCALE_DIV256,
+ IWDG2_PRESCALE_DIV64,
+ IWDG2_PRESCALE_DIV32,
+ IWDG2_PRESCALE_DIV16,
+ IWDG2_PRESCALE_DIV8,
+ IWDG2_PRESCALE_DIV4,
+};
+
+static const rt_uint32_t iwdg2_prescaler_div[] =
+{
+ 128U, 2048U, 1024U, 512U, 256U, 64U, 32U, 16U, 8U, 4U,
+};
+
+static rt_uint64_t iwdg2_calc_timeout_us(rt_uint32_t div, rt_uint32_t reload)
+{
+ return ((rt_uint64_t)(reload + 1U) * div * NS800_USEC_PER_SEC + NS800_IWDG2_CLK_HZ / 2ULL) / NS800_IWDG2_CLK_HZ;
+}
+
+static void iwdg2_select_config(rt_uint32_t timeout_s, struct ns800_iwdg *dev)
+{
+ rt_uint64_t target_us = (rt_uint64_t)timeout_s * NS800_USEC_PER_SEC;
+ rt_uint64_t best_diff = (rt_uint64_t)-1;
+ rt_uint32_t best_prescaler = 0;
+ rt_uint32_t best_reload = 0;
+ rt_uint32_t psc;
+
+ if (target_us == 0)
+ {
+ target_us = iwdg2_calc_timeout_us(iwdg2_prescaler_div[NS800_ARRAY_SIZE(iwdg2_prescaler_div) - 1], 0U);
+ }
+
+ for (psc = 0; psc < NS800_ARRAY_SIZE(iwdg2_prescaler_code); psc++)
+ {
+ rt_uint64_t ticks;
+ rt_uint64_t actual_us;
+ rt_uint64_t diff;
+ rt_uint64_t tick_us = ((rt_uint64_t)iwdg2_prescaler_div[psc] * NS800_USEC_PER_SEC +
+ NS800_IWDG2_CLK_HZ / 2ULL) / NS800_IWDG2_CLK_HZ;
+
+ if (tick_us == 0)
+ {
+ tick_us = 1;
+ }
+
+ ticks = (target_us + tick_us / 2ULL) / tick_us;
+ if (ticks == 0)
+ {
+ ticks = 1;
+ }
+
+ if (ticks > 4096ULL)
+ {
+ ticks = 4096ULL;
+ }
+
+ actual_us = iwdg2_calc_timeout_us(iwdg2_prescaler_div[psc], (rt_uint32_t)(ticks - 1ULL));
+ diff = ns800_abs_diff_u64(actual_us, target_us);
+ if (diff < best_diff)
+ {
+ best_diff = diff;
+ best_prescaler = psc;
+ best_reload = (rt_uint32_t)(ticks - 1ULL);
+ dev->timeout_us = actual_us;
+ }
+ }
+
+ dev->hw.iwdg2.prescaler = (IWDG2_Prescaler)iwdg2_prescaler_code[best_prescaler];
+ dev->hw.iwdg2.reload = (rt_uint16_t)best_reload;
+}
+
+static rt_err_t iwdg2_apply_config(struct ns800_iwdg *dev)
+{
+ if (!IWDG2_configModule((IWDG2_TypeDef *)dev->base,
+ RESET_SEL,
+ dev->hw.iwdg2.prescaler,
+ dev->hw.iwdg2.reload))
+ {
+ return -RT_ETIMEOUT;
+ }
+
+ return RT_EOK;
+}
+
+void IWDG2_Handler(void)
+{
+ rt_interrupt_enter();
+ if (IWDG2_getIntStatus((IWDG2_TypeDef *)iwdg2_dev.base))
+ {
+ IWDG2_clearIntStatus((IWDG2_TypeDef *)iwdg2_dev.base);
+ while (IWDG2_getIntStatus((IWDG2_TypeDef *)iwdg2_dev.base))
+ {
+ ;
+ }
+ }
+ rt_interrupt_leave();
+}
+
+#endif /* BSP_USING_IWDG2 */
+
+static rt_err_t ns800_iwdg_init(rt_watchdog_t *wdt)
+{
+ RT_UNUSED(wdt);
+ return RT_EOK;
+}
+
+static rt_err_t ns800_iwdg_control(rt_watchdog_t *wdt, int cmd, void *arg)
+{
+ struct ns800_iwdg *dev;
+
+ RT_ASSERT(wdt != RT_NULL);
+ dev = (struct ns800_iwdg *)wdt->parent.user_data;
+ RT_ASSERT(dev != RT_NULL);
+
+ switch (cmd)
+ {
+ case RT_DEVICE_CTRL_WDT_KEEPALIVE:
+#ifdef BSP_USING_IWDG1
+ if (dev->id == NS800_IWDG1)
+ {
+ IWDG1_refreshModule((IWDG1_TypeDef *)dev->base);
+ }
+#endif
+#ifdef BSP_USING_IWDG2
+ if (dev->id == NS800_IWDG2)
+ {
+ IWDG2_refreshModule((IWDG2_TypeDef *)dev->base);
+ }
+#endif
+ break;
+
+ case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
+ if (arg == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+#ifdef BSP_USING_IWDG1
+ if (dev->id == NS800_IWDG1)
+ {
+ if (dev->started)
+ {
+ LOG_W("%s timeout cannot be reconfigured after start", dev->name);
+ return -RT_EBUSY;
+ }
+ iwdg1_select_config(*(rt_uint32_t *)arg, dev);
+ LOG_I("%s timeout request=%u s actual=%u ms",
+ dev->name, *(rt_uint32_t *)arg, ns800_timeout_to_ms(dev->timeout_us));
+ }
+#endif
+#ifdef BSP_USING_IWDG2
+ if (dev->id == NS800_IWDG2)
+ {
+ rt_err_t ret;
+
+ iwdg2_select_config(*(rt_uint32_t *)arg, dev);
+ ret = iwdg2_apply_config(dev);
+ if (ret != RT_EOK)
+ {
+ return ret;
+ }
+
+ if (dev->started)
+ {
+ IWDG2_refreshModule((IWDG2_TypeDef *)dev->base);
+ }
+ LOG_I("%s timeout request=%u s actual=%u ms",
+ dev->name, *(rt_uint32_t *)arg, ns800_timeout_to_ms(dev->timeout_us));
+ }
+#endif
+ break;
+
+ case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
+ if (arg == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+ *(rt_uint32_t *)arg = ns800_timeout_to_seconds(dev->timeout_us);
+ break;
+
+ case RT_DEVICE_CTRL_WDT_GET_TIMELEFT:
+ return -RT_ENOSYS;
+
+ case RT_DEVICE_CTRL_WDT_START:
+ if (!dev->started)
+ {
+#ifdef BSP_USING_IWDG1
+ if (dev->id == NS800_IWDG1)
+ {
+ iwdg1_apply_config(dev);
+ IWDG1_enableModule((IWDG1_TypeDef *)dev->base);
+ }
+#endif
+#ifdef BSP_USING_IWDG2
+ if (dev->id == NS800_IWDG2)
+ {
+ rt_err_t ret = iwdg2_apply_config(dev);
+ if (ret != RT_EOK)
+ {
+ return ret;
+ }
+ IWDG2_enableModule((IWDG2_TypeDef *)dev->base);
+ IWDG2_refreshModule((IWDG2_TypeDef *)dev->base);
+ }
+#endif
+ dev->started = RT_TRUE;
+ LOG_I("%s started, timeout=%u ms", dev->name, ns800_timeout_to_ms(dev->timeout_us));
+ }
+ break;
+
+ case RT_DEVICE_CTRL_WDT_STOP:
+ return -RT_ENOSYS;
+
+ default:
+ return -RT_EINVAL;
+ }
+
+ return RT_EOK;
+}
+
+static const struct rt_watchdog_ops ns800_iwdg_ops =
+{
+ .init = ns800_iwdg_init,
+ .control = ns800_iwdg_control,
+};
+
+#ifdef BSP_USING_IWDG1
+int rt_hw_iwdg1_init(void)
+{
+ iwdg1_dev.id = NS800_IWDG1;
+ iwdg1_dev.name = IWDG1_DEVICE_NAME;
+ iwdg1_dev.base = IWDG1;
+ iwdg1_dev.started = RT_FALSE;
+ iwdg1_select_config(10U, &iwdg1_dev);
+ iwdg1_dev.watchdog.ops = &ns800_iwdg_ops;
+
+ if (rt_hw_watchdog_register(&iwdg1_dev.watchdog,
+ iwdg1_dev.name,
+ RT_DEVICE_FLAG_DEACTIVATE,
+ &iwdg1_dev) != RT_EOK)
+ {
+ LOG_E("%s register failed", iwdg1_dev.name);
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+}
+INIT_BOARD_EXPORT(rt_hw_iwdg1_init);
+#endif /* BSP_USING_IWDG1 */
+
+#ifdef BSP_USING_IWDG2
+int rt_hw_iwdg2_init(void)
+{
+ RCC_unlockRccRegister();
+ RCC_enableLircOscillator();
+ RCC_lockRccRegister();
+
+ iwdg2_dev.id = NS800_IWDG2;
+ iwdg2_dev.name = IWDG2_DEVICE_NAME;
+ iwdg2_dev.base = IWDG2;
+ iwdg2_dev.started = RT_FALSE;
+ iwdg2_select_config(10U, &iwdg2_dev);
+ iwdg2_dev.watchdog.ops = &ns800_iwdg_ops;
+
+ if (rt_hw_watchdog_register(&iwdg2_dev.watchdog,
+ iwdg2_dev.name,
+ RT_DEVICE_FLAG_DEACTIVATE,
+ &iwdg2_dev) != RT_EOK)
+ {
+ LOG_E("%s register failed", iwdg2_dev.name);
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+}
+INIT_BOARD_EXPORT(rt_hw_iwdg2_init);
+#endif /* BSP_USING_IWDG2 */
+
+#endif /* RT_USING_WDT && (BSP_USING_IWDG1 || BSP_USING_IWDG2) */
+
diff --git a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.h b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.h
new file mode 100644
index 0000000000..2b17471be0
--- /dev/null
+++ b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_iwdg.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-13 Jeffery Yuan first version
+ */
+
+#ifndef __DRV_IWDG_H__
+#define __DRV_IWDG_H__
+
+#include <rtdevice.h>
+#include <board.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define IWDG1_DEVICE_NAME "iwdg1"
+#define IWDG2_DEVICE_NAME "iwdg2"
+
+int rt_hw_iwdg1_init(void);
+int rt_hw_iwdg2_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DRV_IWDG_H__ */
+
diff --git a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.c b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.c
new file mode 100644
index 0000000000..41e109c1a8
--- /dev/null
+++ b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.c
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-13 Jeffery Yuan first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include "board.h"
+#include "drv_wwdg.h"
+#include "rcc.h"
+#include "wwdg.h"
+
+#ifdef RT_USING_WDT
+#ifdef BSP_USING_WWDG
+
+#define DBG_TAG "drv.wwdg"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define NS800_WWDG_COUNTER_MIN 0x40U
+#define NS800_WWDG_COUNTER_MAX 0x7FU
+#define NS800_WWDG_TICK_BASE 4096ULL /* Fixed WWDG counter clock divider from the reference manual. */
+#define NS800_USEC_PER_SEC 1000000ULL
+#define NS800_MSEC_PER_SEC 1000ULL
+#define NS800_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+struct ns800_wwdg
+{
+ rt_watchdog_t watchdog;
+ WWDG_TypeDef *base;
+ WWDG_Prescaler prescaler;
+ rt_uint8_t reload;
+ rt_uint8_t window;
+ rt_uint64_t timeout_us;
+ rt_bool_t started;
+};
+
+static struct ns800_wwdg wwdg_dev;
+
+static const rt_uint8_t wwdg_prescaler_div[] =
+{
+ 1U, 2U, 4U, 8U, 16U, 32U, 64U, 128U,
+};
+
+static rt_uint32_t wwdg_get_clock_hz(void)
+{
+ rt_uint32_t clock_hz = RCC_getPclk5Frequency();
+
+ if (clock_hz == 0U || clock_hz == 0xFFFFFFFFU)
+ {
+ LOG_E("%s clock query failed", WWDG_DEVICE_NAME);
+ return 0U;
+ }
+
+ return clock_hz;
+}
+
+static rt_uint64_t wwdg_calc_timeout_us(rt_uint32_t div, rt_uint32_t reload, rt_uint32_t clock_hz)
+{
+ rt_uint32_t ticks = reload - (NS800_WWDG_COUNTER_MIN - 1U);
+
+ return (rt_uint64_t)ticks * NS800_WWDG_TICK_BASE * div * NS800_USEC_PER_SEC / clock_hz;
+}
+
+static rt_uint64_t wwdg_calc_max_timeout_us(rt_uint32_t clock_hz)
+{
+ return wwdg_calc_timeout_us(wwdg_prescaler_div[NS800_ARRAY_SIZE(wwdg_prescaler_div) - 1],
+ NS800_WWDG_COUNTER_MAX,
+ clock_hz);
+}
+
+static rt_uint32_t wwdg_timeout_to_ms(rt_uint64_t timeout_us)
+{
+ return (rt_uint32_t)(timeout_us / NS800_MSEC_PER_SEC);
+}
+
+static void wwdg_select_max_config(struct ns800_wwdg *dev)
+{
+ rt_uint32_t clock_hz = wwdg_get_clock_hz();
+ rt_uint32_t max_prescaler = NS800_ARRAY_SIZE(wwdg_prescaler_div) - 1U;
+
+ dev->prescaler = (WWDG_Prescaler)max_prescaler;
+ dev->reload = NS800_WWDG_COUNTER_MAX;
+ dev->window = NS800_WWDG_COUNTER_MAX;
+ dev->timeout_us = clock_hz ? wwdg_calc_max_timeout_us(clock_hz) : 0U;
+}
+
+static rt_err_t wwdg_select_config(rt_uint32_t timeout_s, struct ns800_wwdg *dev)
+{
+ rt_uint64_t target_us = (rt_uint64_t)timeout_s * NS800_USEC_PER_SEC;
+ rt_uint64_t best_timeout_us = (rt_uint64_t)-1;
+ rt_uint32_t best_prescaler = 0;
+ rt_uint32_t best_reload = NS800_WWDG_COUNTER_MAX;
+ rt_uint32_t clock_hz = wwdg_get_clock_hz();
+ rt_uint32_t psc;
+
+ if (timeout_s == 0U || clock_hz == 0U)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (target_us > wwdg_calc_max_timeout_us(clock_hz))
+ {
+ return -RT_EINVAL;
+ }
+
+ for (psc = 0; psc < NS800_ARRAY_SIZE(wwdg_prescaler_div); psc++)
+ {
+ rt_uint64_t tick_unit = (rt_uint64_t)NS800_WWDG_TICK_BASE * wwdg_prescaler_div[psc] * NS800_USEC_PER_SEC;
+ rt_uint64_t ticks;
+ rt_uint32_t reload;
+ rt_uint64_t actual_us;
+
+ ticks = (target_us * clock_hz + tick_unit - 1ULL) / tick_unit;
+ if (ticks == 0)
+ {
+ ticks = 1;
+ }
+
+ if (ticks > 64ULL)
+ {
+ continue;
+ }
+
+ reload = (rt_uint32_t)(ticks + (NS800_WWDG_COUNTER_MIN - 1U));
+ actual_us = wwdg_calc_timeout_us(wwdg_prescaler_div[psc], reload, clock_hz);
+ if (actual_us >= target_us && actual_us < best_timeout_us)
+ {
+ best_prescaler = psc;
+ best_reload = reload;
+ best_timeout_us = actual_us;
+ }
+ }
+
+ if (best_timeout_us == (rt_uint64_t)-1)
+ {
+ return -RT_EINVAL;
+ }
+
+ dev->prescaler = (WWDG_Prescaler)best_prescaler;
+ dev->reload = (rt_uint8_t)best_reload;
+ dev->window = (rt_uint8_t)best_reload;
+ dev->timeout_us = best_timeout_us;
+
+ return RT_EOK;
+}
+
+static void wwdg_apply_config(struct ns800_wwdg *dev)
+{
+ WWDG_configModule(dev->base,
+ WWDG_DIRECT_RESET,
+ dev->prescaler,
+ dev->window);
+}
+
+static rt_err_t wwdg_init(rt_watchdog_t *wdt)
+{
+ RT_UNUSED(wdt);
+ return RT_EOK;
+}
+
+static rt_err_t wwdg_control(rt_watchdog_t *wdt, int cmd, void *arg)
+{
+ struct ns800_wwdg *dev;
+ rt_err_t ret;
+
+ RT_ASSERT(wdt != RT_NULL);
+ dev = (struct ns800_wwdg *)wdt->parent.user_data;
+ RT_ASSERT(dev != RT_NULL);
+
+ switch (cmd)
+ {
+ case RT_DEVICE_CTRL_WDT_KEEPALIVE:
+ WWDG_refreshModule(dev->base, dev->reload);
+ break;
+
+ case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
+ if (arg == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+ ret = wwdg_select_config(*(rt_uint32_t *)arg, dev);
+ if (ret != RT_EOK)
+ {
+ rt_uint32_t clock_hz = wwdg_get_clock_hz();
+
+ LOG_W("%s timeout request=%u s exceeds max=%u ms",
+ WWDG_DEVICE_NAME,
+ *(rt_uint32_t *)arg,
+ clock_hz ? wwdg_timeout_to_ms(wwdg_calc_max_timeout_us(clock_hz)) : 0U);
+ return ret;
+ }
+ if (dev->started)
+ {
+ wwdg_apply_config(dev);
+ WWDG_refreshModule(dev->base, dev->reload);
+ }
+ LOG_W("%s timeout request=%u s actual=%u ms",
+ WWDG_DEVICE_NAME,
+ *(rt_uint32_t *)arg,
+ wwdg_timeout_to_ms(dev->timeout_us));
+ break;
+
+ case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
+ if (arg == RT_NULL)
+ {
+ return -RT_EINVAL;
+ }
+ *(rt_uint32_t *)arg = (rt_uint32_t)(dev->timeout_us / NS800_USEC_PER_SEC);
+ break;
+
+ case RT_DEVICE_CTRL_WDT_GET_TIMELEFT:
+ return -RT_ENOSYS;
+
+ case RT_DEVICE_CTRL_WDT_START:
+ if (!dev->started)
+ {
+ wwdg_apply_config(dev);
+ WWDG_clearIntStatus(dev->base);
+ WWDG_enableModule(dev->base, dev->reload);
+ dev->started = RT_TRUE;
+ LOG_I("%s started, timeout=%u ms", WWDG_DEVICE_NAME, wwdg_timeout_to_ms(dev->timeout_us));
+ }
+ break;
+
+ case RT_DEVICE_CTRL_WDT_STOP:
+ return -RT_ENOSYS;
+
+ default:
+ return -RT_EINVAL;
+ }
+
+ return RT_EOK;
+}
+
+static const struct rt_watchdog_ops wwdg_ops =
+{
+ .init = wwdg_init,
+ .control = wwdg_control,
+};
+
+int rt_hw_wwdg_init(void)
+{
+ wwdg_dev.base = WWDG;
+ wwdg_dev.started = RT_FALSE;
+ if (wwdg_select_config(1U, &wwdg_dev) != RT_EOK)
+ {
+ wwdg_select_max_config(&wwdg_dev);
+ }
+ wwdg_dev.watchdog.ops = &wwdg_ops;
+
+ if (rt_hw_watchdog_register(&wwdg_dev.watchdog,
+ WWDG_DEVICE_NAME,
+ RT_DEVICE_FLAG_DEACTIVATE,
+ &wwdg_dev) != RT_EOK)
+ {
+ LOG_E("%s register failed", WWDG_DEVICE_NAME);
+ return -RT_ERROR;
+ }
+
+ return RT_EOK;
+}
+INIT_BOARD_EXPORT(rt_hw_wwdg_init);
+
+#endif /* BSP_USING_WWDG */
+#endif /* RT_USING_WDT */
+
diff --git a/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.h b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.h
new file mode 100644
index 0000000000..32b90820ce
--- /dev/null
+++ b/bsp/novosns/ns800/libraries/HAL_Drivers/drivers/drv_wwdg.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2006-2026, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2026-05-13 Jeffery Yuan first version
+ */
+
+#ifndef DRV_WWDG_H__
+#define DRV_WWDG_H__
+
+#include <rtdevice.h>
+#include <board.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WWDG_DEVICE_NAME "wwdg"
+
+int rt_hw_wwdg_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DRV_WWDG_H__ */
+
diff --git a/bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml b/bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml
index 796e33c9df..c1aa4777e4 100644
--- a/bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml
+++ b/bsp/novosns/ns800/ns800rt7p65-nssinepad/.ci/attachconfig/ci.attachconfig.yml
@@ -15,4 +15,11 @@ devices.spi:
kconfig:
- CONFIG_BSP_USING_SPI=y
- CONFIG_BSP_USING_SPI1=y
- - CONFIG_BSP_USING_QSPI=y \ No newline at end of file
+ - CONFIG_BSP_USING_QSPI=y
+devices.wdt:
+ <<: *scons
+ kconfig:
+ - CONFIG_BSP_USING_WDT=y
+ - CONFIG_BSP_USING_IWDG1=y
+ - CONFIG_BSP_USING_IWDG2=y
+ - CONFIG_BSP_USING_WWDG=y \ No newline at end of file
diff --git a/bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig b/bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig
index 1c899b2331..b23da33d13 100644
--- a/bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig
+++ b/bsp/novosns/ns800/ns800rt7p65-nssinepad/board/Kconfig
@@ -211,4 +211,22 @@ menu "On-chip Peripheral Drivers"
select RT_USING_QSPI
default n
+ menuconfig BSP_USING_WDT
+ bool "Enable Watchdog"
+ select RT_USING_WDT
+ default n
+ if BSP_USING_WDT
+ config BSP_USING_IWDG1
+ bool "Enable IWDG1"
+ default n
+
+ config BSP_USING_IWDG2
+ bool "Enable IWDG2"
+ default n
+
+ config BSP_USING_WWDG
+ bool "Enable WWDG"
+ default n
+ endif
+
endmenu