Skip to content

Commit 0cf7fd1

Browse files
nandojvekartben
authored andcommitted
drivers: watchdog: atmel: Introduce sam4l wdt
Introduce sam4l watchdog configuration. This entry is necessary to select proper watchdog configuration at board init due to #83429. Signed-off-by: Gerson Fernando Budke <nandojve@gmail.com>
1 parent abee6d5 commit 0cf7fd1

File tree

6 files changed

+365
-37
lines changed

6 files changed

+365
-37
lines changed

drivers/watchdog/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_NPM6001 wdt_npm6001.c)
3131
zephyr_library_sources_ifdef(CONFIG_WDT_NRFX wdt_nrfx.c)
3232
zephyr_library_sources_ifdef(CONFIG_WDT_RPI_PICO wdt_rpi_pico.c)
3333
zephyr_library_sources_ifdef(CONFIG_WDT_SAM wdt_sam.c)
34+
zephyr_library_sources_ifdef(CONFIG_WDT_SAM4L wdt_sam4l.c)
3435
zephyr_library_sources_ifdef(CONFIG_WDT_SAM0 wdt_sam0.c)
3536
zephyr_library_sources_ifdef(CONFIG_WDT_SIFIVE wdt_sifive.c)
3637
zephyr_library_sources_ifdef(CONFIG_WDT_TCO wdt_tco.c)

drivers/watchdog/Kconfig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ source "drivers/watchdog/Kconfig.stm32"
6969

7070
source "drivers/watchdog/Kconfig.cmsdk_apb"
7171

72+
source "drivers/watchdog/Kconfig.esp32"
73+
7274
source "drivers/watchdog/Kconfig.sam"
7375

74-
source "drivers/watchdog/Kconfig.esp32"
76+
source "drivers/watchdog/Kconfig.sam4l"
7577

7678
source "drivers/watchdog/Kconfig.sam0"
7779

drivers/watchdog/Kconfig.sam4l

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright (C) 2024-2025, Gerson Fernando Budke <nandojve@gmail.com>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config WDT_SAM4L
5+
bool "Atmel SAM4L MCU Family Watchdog (WDT) Driver"
6+
default y
7+
depends on DT_HAS_ATMEL_SAM4L_WATCHDOG_ENABLED
8+
select HAS_WDT_DISABLE_AT_BOOT
9+
help
10+
Enable WDT driver for Atmel SAM4L MCUs.

drivers/watchdog/wdt_sam4l.c

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/*
2+
* Copyright (C) 2024-2025, Gerson Fernando Budke <nandojve@gmail.com>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT atmel_sam4l_watchdog
8+
9+
/**
10+
* @brief Watchdog (WDT) Driver for Atmel SAM4L MCUs
11+
*
12+
* Note:
13+
* - SAM4L watchdog has a fuse bit to automatically enable the watchdog at boot.
14+
* It should be enabled to keep compatibility with SAM family.
15+
* - Since the MCU boots with WDT enabled, the CONFIG_WDT_DISABLE_AT_BOOT
16+
* is set default at boot and watchdog module is disabled in the MCU for
17+
* systems that don't need watchdog functionality.
18+
* - If the application needs to use the watchdog in the system, then
19+
* CONFIG_WDT_DISABLE_AT_BOOT must be unset in the app's config file
20+
*/
21+
22+
#include <zephyr/drivers/watchdog.h>
23+
#include <zephyr/drivers/clock_control/atmel_sam_pmc.h>
24+
#include <zephyr/irq.h>
25+
#include <soc.h>
26+
27+
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
28+
#include <zephyr/logging/log.h>
29+
LOG_MODULE_REGISTER(wdt_sam4l);
30+
31+
#define WDT_FIRST_KEY 0x55ul
32+
#define WDT_SECOND_KEY 0xAAul
33+
34+
enum wdt_sam4l_clock_src {
35+
WDT_SAM4L_CLK_SRC_RCSYS = 0,
36+
WDT_SAM4L_CLK_SRC_OSC32K = 1,
37+
};
38+
39+
struct wdt_sam4l_dev_cfg {
40+
Wdt *regs;
41+
void (*irq_cfg_func)(const struct device *dev);
42+
const struct atmel_sam_pmc_config clock_cfg;
43+
enum wdt_sam4l_clock_src clock_src;
44+
bool lock;
45+
};
46+
47+
struct wdt_sam4l_dev_data {
48+
wdt_callback_t cb;
49+
uint32_t flags;
50+
};
51+
52+
/**
53+
* @brief Sets the Watchdog Timer Control register to the @a ctrl value.
54+
*
55+
* @param ctrl Value to set the WatchDog Timer Control register to.
56+
*/
57+
static void wdt_sam4l_set_ctrl(const struct wdt_sam4l_dev_cfg *cfg,
58+
const uint32_t ctrl)
59+
{
60+
Wdt *const wdt = cfg->regs;
61+
volatile uint32_t delay;
62+
63+
/* Calculate delay for internal synchronization, see 45.1.3 WDT errata */
64+
if (cfg->clock_src == WDT_SAM4L_CLK_SRC_OSC32K) {
65+
delay = DIV_ROUND_UP(SOC_ATMEL_SAM_MCK_FREQ_HZ * 2, SOC_ATMEL_SAM_RC32K_NOMINAL_HZ);
66+
} else {
67+
delay = DIV_ROUND_UP(SOC_ATMEL_SAM_MCK_FREQ_HZ * 2, SOC_ATMEL_SAM_RCSYS_NOMINAL_HZ);
68+
}
69+
/* ~8 cycles for one while loop */
70+
delay >>= 3;
71+
while (delay--) {
72+
}
73+
wdt->CTRL = WDT_CTRL_KEY(WDT_FIRST_KEY)
74+
| ctrl;
75+
wdt->CTRL = WDT_CTRL_KEY(WDT_SECOND_KEY)
76+
| ctrl;
77+
}
78+
79+
/**
80+
* @brief Calculate timeout scale factor based on a input in milliseconds
81+
*
82+
* timeout(ms) = (2pow(scale + 1) * 1000) / wdt_clk
83+
*
84+
* @param cfg Configuration
85+
* @param time Timeout value in milliseconds
86+
*/
87+
static int wdt_sam4l_calc_timeout(const struct wdt_sam4l_dev_cfg *cfg,
88+
uint32_t time)
89+
{
90+
uint32_t wdt_clk = cfg->clock_src == WDT_SAM4L_CLK_SRC_OSC32K
91+
? SOC_ATMEL_SAM_RC32K_NOMINAL_HZ
92+
: SOC_ATMEL_SAM_RCSYS_NOMINAL_HZ;
93+
94+
for (int scale = 7; scale <= 31; ++scale) {
95+
uint32_t timeout = (BIT64(scale + 1) * 1000ull) / wdt_clk;
96+
97+
if (time <= timeout) {
98+
return scale;
99+
}
100+
}
101+
102+
return -EINVAL;
103+
}
104+
105+
/**
106+
* @brief Calculate both the Banned and Prescale Select timeout to be installed
107+
* in the watchdog timer.
108+
*
109+
* The config->min value will define the banned timeout. The prescaler timeout
110+
* then should be defined by the interval of (config->max - config-min).
111+
*
112+
* @param config Timeout Window configuration value in milliseconds.
113+
* @param tban Pointer to the banned timeout
114+
* @param psel Pointer to the timeout perscaller selection
115+
*/
116+
static int wdt_sam4l_convert_timeout(const struct wdt_sam4l_dev_cfg *cfg,
117+
const struct wdt_timeout_cfg *config,
118+
uint32_t *tban, uint32_t *psel)
119+
{
120+
int scale;
121+
122+
if (config->window.max < config->window.min || config->window.max == 0) {
123+
LOG_ERR("The watchdog window should have a valid period");
124+
return -EINVAL;
125+
}
126+
127+
scale = wdt_sam4l_calc_timeout(cfg, config->window.min);
128+
if (scale < 0) {
129+
LOG_ERR("Window minimal value is too big");
130+
return -EINVAL;
131+
}
132+
*tban = scale;
133+
134+
scale = wdt_sam4l_calc_timeout(cfg, config->window.max - config->window.min);
135+
if (scale < 0) {
136+
LOG_ERR("Window maximal value is too big");
137+
return -EINVAL;
138+
}
139+
*psel = scale;
140+
141+
return 0;
142+
}
143+
144+
static int wdt_sam4l_setup(const struct device *dev, uint8_t options)
145+
{
146+
const struct wdt_sam4l_dev_cfg *cfg = dev->config;
147+
Wdt *const wdt = cfg->regs;
148+
volatile uint32_t reg;
149+
150+
if (options & WDT_OPT_PAUSE_IN_SLEEP) {
151+
LOG_ERR("Pause in Sleep is an invalid option");
152+
return -ENOTSUP;
153+
}
154+
155+
if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) {
156+
LOG_ERR("Pause on CPU halted by debug is an invalid option");
157+
return -ENOTSUP;
158+
}
159+
160+
reg = wdt->CTRL;
161+
162+
if (reg & WDT_CTRL_EN) {
163+
LOG_ERR("Watchdog is running and can not be changed");
164+
return -EPERM;
165+
}
166+
167+
if (reg & WDT_CTRL_SFV) {
168+
LOG_ERR("Watchdog is locked and can not be changed");
169+
return -EPERM;
170+
}
171+
172+
if (!(reg & WDT_CTRL_PSEL_Msk)) {
173+
LOG_ERR("No valid timeouts installed");
174+
return -EINVAL;
175+
}
176+
177+
reg = wdt->CTRL
178+
| (cfg->lock ? WDT_CTRL_SFV : 0)
179+
| WDT_CTRL_EN;
180+
181+
wdt_sam4l_set_ctrl(cfg, reg);
182+
while ((wdt->CTRL & WDT_CTRL_EN) != WDT_CTRL_EN) {
183+
}
184+
185+
return 0;
186+
}
187+
188+
static int wdt_sam4l_disable(const struct device *dev)
189+
{
190+
const struct wdt_sam4l_dev_cfg *cfg = dev->config;
191+
Wdt *const wdt = cfg->regs;
192+
193+
if (wdt->CTRL & WDT_CTRL_SFV) {
194+
LOG_ERR("Watchdog is already locked");
195+
return -EPERM;
196+
}
197+
198+
wdt_sam4l_set_ctrl(cfg, wdt->CTRL & ~WDT_CTRL_EN);
199+
while (wdt->CTRL & WDT_CTRL_EN) {
200+
}
201+
202+
wdt_sam4l_set_ctrl(cfg, wdt->CTRL & ~WDT_CTRL_CEN);
203+
while (wdt->CTRL & WDT_CTRL_CEN) {
204+
}
205+
206+
return 0;
207+
}
208+
209+
static int wdt_sam4l_install_timeout(const struct device *dev,
210+
const struct wdt_timeout_cfg *config)
211+
{
212+
const struct wdt_sam4l_dev_cfg *cfg = dev->config;
213+
struct wdt_sam4l_dev_data *const data = dev->data;
214+
Wdt *const wdt = cfg->regs;
215+
volatile uint32_t reg;
216+
uint32_t tban, psel;
217+
218+
if (wdt->CTRL & WDT_CTRL_MODE) {
219+
LOG_ERR("No more timeouts can be installed");
220+
return -ENOMEM;
221+
}
222+
223+
if (config->flags & WDT_FLAG_RESET_CPU_CORE) {
224+
LOG_ERR("The SAM4L watchdog does not support reset CPU core");
225+
return -ENOTSUP;
226+
}
227+
228+
if (wdt_sam4l_convert_timeout(cfg, config, &tban, &psel)) {
229+
return -EINVAL;
230+
}
231+
232+
reg = wdt->CTRL;
233+
234+
if (reg & WDT_CTRL_EN) {
235+
LOG_ERR("Watchdog is running and can not be changed");
236+
return -EPERM;
237+
}
238+
239+
if (reg & WDT_CTRL_SFV) {
240+
LOG_ERR("Watchdog is locked and can not be changed");
241+
return -EPERM;
242+
}
243+
244+
data->cb = config->callback;
245+
data->flags = config->flags;
246+
reg = (cfg->clock_src == WDT_SAM4L_CLK_SRC_OSC32K ? WDT_CTRL_CSSEL : 0)
247+
| (config->callback ? WDT_CTRL_IM : 0)
248+
| (config->window.min ? WDT_CTRL_MODE : 0)
249+
| (config->window.min ? WDT_CTRL_TBAN(tban) : 0)
250+
| WDT_CTRL_PSEL(psel);
251+
wdt_sam4l_set_ctrl(cfg, reg);
252+
253+
wdt_sam4l_set_ctrl(cfg, wdt->CTRL | WDT_CTRL_CEN);
254+
while (!(wdt->CTRL & WDT_CTRL_CEN)) {
255+
}
256+
257+
return 0;
258+
}
259+
260+
static int wdt_sam4l_feed(const struct device *dev, int channel_id)
261+
{
262+
const struct wdt_sam4l_dev_cfg *cfg = dev->config;
263+
Wdt *const wdt = cfg->regs;
264+
265+
ARG_UNUSED(channel_id);
266+
267+
while ((wdt->SR & WDT_SR_CLEARED) != WDT_SR_CLEARED) {
268+
}
269+
wdt->CLR = WDT_CLR_WDTCLR
270+
| WDT_CLR_KEY(WDT_FIRST_KEY);
271+
wdt->CLR = WDT_CLR_WDTCLR
272+
| WDT_CLR_KEY(WDT_SECOND_KEY);
273+
274+
return 0;
275+
}
276+
277+
/* If the callback blocks or ISR do not clear flags the system will trigger a
278+
* CPU reset on the next watchdog timeout, see 20.5.3 Interrupt Mode
279+
*/
280+
static void wdt_sam4l_isr(const struct device *dev)
281+
{
282+
const struct wdt_sam4l_dev_cfg *cfg = dev->config;
283+
struct wdt_sam4l_dev_data *const data = dev->data;
284+
Wdt *const wdt = cfg->regs;
285+
286+
wdt->ICR = WDT_ICR_WINT;
287+
data->cb(dev, 0);
288+
}
289+
290+
static DEVICE_API(wdt, wdt_sam4l_driver_api) = {
291+
.setup = wdt_sam4l_setup,
292+
.disable = wdt_sam4l_disable,
293+
.install_timeout = wdt_sam4l_install_timeout,
294+
.feed = wdt_sam4l_feed,
295+
};
296+
297+
static int wdt_sam4l_init(const struct device *dev)
298+
{
299+
const struct wdt_sam4l_dev_cfg *const cfg = dev->config;
300+
Wdt *const wdt = cfg->regs;
301+
302+
/* Enable WDT clock in PMC */
303+
(void)clock_control_on(SAM_DT_PMC_CONTROLLER,
304+
(clock_control_subsys_t)&cfg->clock_cfg);
305+
306+
if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
307+
wdt_sam4l_disable(dev);
308+
return 0;
309+
}
310+
311+
wdt->IDR = WDT_IDR_MASK;
312+
cfg->irq_cfg_func(dev);
313+
wdt->IER = WDT_IER_WINT;
314+
315+
return 0;
316+
}
317+
318+
#define WDT_SAM4L_INIT(n) \
319+
static void wdt##n##_sam4l_irq_cfg(const struct device *dev) \
320+
{ \
321+
IRQ_CONNECT(DT_INST_IRQN(n), \
322+
DT_INST_IRQ(n, priority), \
323+
wdt_sam4l_isr, \
324+
DEVICE_DT_INST_GET(n), 0); \
325+
irq_enable(DT_INST_IRQN(n)); \
326+
} \
327+
\
328+
static const struct wdt_sam4l_dev_cfg wdt##n##_sam4l_cfg = { \
329+
.regs = (Wdt *)DT_INST_REG_ADDR(n), \
330+
.irq_cfg_func = wdt##n##_sam4l_irq_cfg, \
331+
.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(n), \
332+
.clock_src = DT_INST_ENUM_IDX(n, clk_source), \
333+
.lock = DT_INST_PROP(n, lock_mode), \
334+
}; \
335+
\
336+
static struct wdt_sam4l_dev_data wdt##n##_sam4l_data; \
337+
\
338+
DEVICE_DT_INST_DEFINE(n, wdt_sam4l_init, \
339+
NULL, \
340+
&wdt##n##_sam4l_data, \
341+
&wdt##n##_sam4l_cfg, \
342+
PRE_KERNEL_1, \
343+
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
344+
&wdt_sam4l_driver_api);
345+
346+
DT_INST_FOREACH_STATUS_OKAY(WDT_SAM4L_INIT)

0 commit comments

Comments
 (0)