1 /*
2 * Copyright (C) 2017 Intel Deutschland GmbH
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT atmel_sam_watchdog
8
9 /**
10 * @brief Watchdog (WDT) Driver for Atmel SAM MCUs
11 *
12 * Note:
13 * - Once the watchdog disable bit is set, it cannot be cleared till next
14 * power reset, i.e, the watchdog cannot be started once stopped.
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/irq.h>
24 #include <soc.h>
25
26 #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
27 #include <zephyr/logging/log.h>
28 LOG_MODULE_REGISTER(wdt_sam);
29
30 #define SAM_PRESCALAR 128
31 #define WDT_MAX_VALUE 4095
32
33 /* Device constant configuration parameters */
34 struct wdt_sam_dev_cfg {
35 Wdt *regs;
36 };
37
38 struct wdt_sam_dev_data {
39 wdt_callback_t cb;
40 uint32_t mode;
41 bool timeout_valid;
42 bool mode_set;
43 };
44
45 static struct wdt_sam_dev_data wdt_sam_data = { 0 };
46
wdt_sam_isr(const struct device * dev)47 static void wdt_sam_isr(const struct device *dev)
48 {
49 const struct wdt_sam_dev_cfg *config = dev->config;
50 uint32_t wdt_sr;
51 Wdt * const wdt = config->regs;
52 struct wdt_sam_dev_data *data = dev->data;
53
54 /* Clear status bit to acknowledge interrupt by dummy read. */
55 wdt_sr = wdt->WDT_SR;
56
57 data->cb(dev, 0);
58 }
59
60 /**
61 * @brief Calculates the watchdog counter value (WDV)
62 * to be installed in the watchdog timer
63 *
64 * @param timeout Timeout value in milliseconds.
65 * @param slow clock on board in Hz.
66 */
wdt_sam_convert_timeout(uint32_t timeout,uint32_t sclk)67 int wdt_sam_convert_timeout(uint32_t timeout, uint32_t sclk)
68 {
69 uint32_t max, min;
70
71 timeout = timeout * 1000U;
72 min = (SAM_PRESCALAR * 1000000) / sclk;
73 max = min * WDT_MAX_VALUE;
74 if ((timeout < min) || (timeout > max)) {
75 LOG_ERR("Invalid timeout value allowed range:"
76 "%d ms to %d ms", min / 1000U, max / 1000U);
77 return -EINVAL;
78 }
79
80 return WDT_MR_WDV(timeout / min);
81 }
82
wdt_sam_disable(const struct device * dev)83 static int wdt_sam_disable(const struct device *dev)
84 {
85 const struct wdt_sam_dev_cfg *config = dev->config;
86
87 Wdt * const wdt = config->regs;
88 struct wdt_sam_dev_data *data = dev->data;
89
90 /* since Watchdog mode register is 'write-once', we can't disable if
91 * someone has already set the mode register
92 */
93 if (data->mode_set) {
94 return -EPERM;
95 }
96
97 /* do we handle -EFAULT here */
98
99 /* Watchdog Mode register is 'write-once' only register.
100 * Once disabled, it cannot be enabled until the device is reset
101 */
102 wdt->WDT_MR |= WDT_MR_WDDIS;
103 data->mode_set = true;
104
105 return 0;
106 }
107
wdt_sam_setup(const struct device * dev,uint8_t options)108 static int wdt_sam_setup(const struct device *dev, uint8_t options)
109 {
110 const struct wdt_sam_dev_cfg *config = dev->config;
111
112 Wdt * const wdt = config->regs;
113 struct wdt_sam_dev_data *data = dev->data;
114
115 if (!data->timeout_valid) {
116 LOG_ERR("No valid timeouts installed");
117 return -EINVAL;
118 }
119
120 /* since Watchdog mode register is 'write-once', we can't set if
121 * someone has already set the mode register
122 */
123 if (data->mode_set) {
124 return -EPERM;
125 }
126
127 if ((options & WDT_OPT_PAUSE_IN_SLEEP) == WDT_OPT_PAUSE_IN_SLEEP) {
128 data->mode |= WDT_MR_WDIDLEHLT;
129 }
130
131 if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) ==
132 WDT_OPT_PAUSE_HALTED_BY_DBG) {
133 data->mode |= WDT_MR_WDDBGHLT;
134 }
135
136 wdt->WDT_MR = data->mode;
137 data->mode_set = true;
138
139 return 0;
140 }
141
wdt_sam_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)142 static int wdt_sam_install_timeout(const struct device *dev,
143 const struct wdt_timeout_cfg *cfg)
144 {
145 uint32_t wdt_mode = 0U;
146 int timeout_value;
147
148 struct wdt_sam_dev_data *data = dev->data;
149
150 if (data->timeout_valid) {
151 LOG_ERR("No more timeouts can be installed");
152 return -ENOMEM;
153 }
154
155 if (cfg->window.min != 0U) {
156 return -EINVAL;
157 }
158
159 /*
160 * Convert time to cycles. SAM3X SoC doesn't supports window
161 * timeout config. So the api expects the timeout to be filled
162 * in the max field of the timeout config.
163 */
164 timeout_value = wdt_sam_convert_timeout(cfg->window.max,
165 (uint32_t) CHIP_FREQ_XTAL_32K);
166
167 if (timeout_value < 0) {
168 return -EINVAL;
169 }
170
171 switch (cfg->flags) {
172 case WDT_FLAG_RESET_SOC:
173 /*A Watchdog fault (underflow or error) activates all resets */
174 wdt_mode = WDT_MR_WDRSTEN; /* WDT reset enable */
175 break;
176
177 case WDT_FLAG_RESET_NONE:
178 /* A Watchdog fault (underflow or error) asserts interrupt. */
179 if (cfg->callback) {
180 wdt_mode = WDT_MR_WDFIEN; /* WDT fault interrupt. */
181 data->cb = cfg->callback;
182 } else {
183 LOG_ERR("Invalid(NULL) ISR callback passed\n");
184 return -EINVAL;
185 }
186 break;
187
188 /* Processor only reset mode not available in same70 series */
189 #ifdef WDT_MR_WDRPROC
190 case WDT_FLAG_RESET_CPU_CORE:
191 /*A Watchdog fault activates the processor reset*/
192 LOG_DBG("Configuring reset CPU only mode\n");
193 wdt_mode = WDT_MR_WDRSTEN | /* WDT reset enable */
194 WDT_MR_WDRPROC; /* WDT reset processor only*/
195 break;
196 #endif
197 default:
198 LOG_ERR("Unsupported watchdog config Flag\n");
199 return -ENOTSUP;
200 }
201
202 data->mode = wdt_mode |
203 WDT_MR_WDV(timeout_value) |
204 WDT_MR_WDD(timeout_value);
205
206 data->timeout_valid = true;
207
208 return 0;
209 }
210
wdt_sam_feed(const struct device * dev,int channel_id)211 static int wdt_sam_feed(const struct device *dev, int channel_id)
212 {
213 const struct wdt_sam_dev_cfg *config = dev->config;
214
215 /*
216 * On watchdog restart the Watchdog counter is immediately
217 * reloaded/fed with the 12-bit watchdog counter
218 * value from WDT_MR and restarted
219 */
220 Wdt * const wdt = config->regs;
221
222 wdt->WDT_CR |= WDT_CR_KEY_PASSWD | WDT_CR_WDRSTT;
223
224 return 0;
225 }
226
227 static DEVICE_API(wdt, wdt_sam_api) = {
228 .setup = wdt_sam_setup,
229 .disable = wdt_sam_disable,
230 .install_timeout = wdt_sam_install_timeout,
231 .feed = wdt_sam_feed,
232 };
233
234 static const struct wdt_sam_dev_cfg wdt_sam_cfg = {
235 .regs = (Wdt *)DT_INST_REG_ADDR(0),
236 };
237
wdt_sam_irq_config(void)238 static void wdt_sam_irq_config(void)
239 {
240 IRQ_CONNECT(DT_INST_IRQN(0),
241 DT_INST_IRQ(0, priority), wdt_sam_isr,
242 DEVICE_DT_INST_GET(0), 0);
243 irq_enable(DT_INST_IRQN(0));
244 }
245
wdt_sam_init(const struct device * dev)246 static int wdt_sam_init(const struct device *dev)
247 {
248 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
249 wdt_sam_disable(dev);
250 #endif
251
252 wdt_sam_irq_config();
253 return 0;
254 }
255
256 DEVICE_DT_INST_DEFINE(0, wdt_sam_init, NULL,
257 &wdt_sam_data, &wdt_sam_cfg, PRE_KERNEL_1,
258 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sam_api);
259