1 /*
2 * Copyright (c) 2023 Analog Devices, Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT adi_max32_watchdog
8
9 #include <zephyr/drivers/watchdog.h>
10 #include <zephyr/irq.h>
11 #include <zephyr/drivers/clock_control/adi_max32_clock_control.h>
12 #include <soc.h>
13 #include <errno.h>
14
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(wdt_max32, CONFIG_WDT_LOG_LEVEL);
17
18 #include <wrap_max32_wdt.h>
19
20 struct max32_wdt_config {
21 mxc_wdt_regs_t *regs;
22 const struct device *clock;
23 struct max32_perclk perclk;
24 void (*irq_func)(void);
25 };
26
27 struct max32_wdt_data {
28 struct wdt_window timeout;
29 wdt_callback_t callback;
30 };
31
wdt_max32_calculate_timeout(uint32_t timeout,uint32_t clock_src)32 static int wdt_max32_calculate_timeout(uint32_t timeout, uint32_t clock_src)
33 {
34 int i;
35 uint32_t clk_frequency = 0;
36 uint32_t number_of_tick = 0;
37
38 clk_frequency = ADI_MAX32_GET_PRPH_CLK_FREQ(clock_src);
39 if (clk_frequency == 0) {
40 LOG_ERR("Unsupported clock source.");
41 return -ENOTSUP;
42 }
43
44 number_of_tick = ((uint64_t)timeout * (uint64_t)clk_frequency) / 1000;
45
46 i = LOG2CEIL(number_of_tick); /* Find closest bigger 2^i value than number_of_tick. */
47 i = CLAMP(i, 16, 31); /* Limit i between 16 and 31. */
48
49 /* It returns 31 - i because period thresholds are inverse ordered in register. */
50 return (31 - i);
51 }
52
wdt_max32_disable(const struct device * dev)53 static int wdt_max32_disable(const struct device *dev)
54 {
55 const struct max32_wdt_config *cfg = dev->config;
56
57 if (!(cfg->regs->ctrl & WRAP_MXC_F_WDT_CTRL_EN)) {
58 return -EFAULT;
59 }
60
61 MXC_WDT_Disable(cfg->regs);
62 return 0;
63 }
64
wdt_max32_feed(const struct device * dev,int channel_id)65 static int wdt_max32_feed(const struct device *dev, int channel_id)
66 {
67 ARG_UNUSED(channel_id);
68 const struct max32_wdt_config *cfg = dev->config;
69
70 MXC_WDT_ResetTimer(cfg->regs);
71 return 0;
72 }
73
wdt_max32_setup(const struct device * dev,uint8_t options)74 static int wdt_max32_setup(const struct device *dev, uint8_t options)
75 {
76 const struct max32_wdt_config *cfg = dev->config;
77
78 if (cfg->regs->ctrl & WRAP_MXC_F_WDT_CTRL_EN) {
79 return -EBUSY;
80 }
81
82 if (options & WDT_OPT_PAUSE_IN_SLEEP) {
83 return -ENOTSUP;
84 }
85
86 MXC_WDT_ResetTimer(cfg->regs);
87 MXC_WDT_Enable(cfg->regs);
88 return 0;
89 }
90
wdt_max32_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)91 static int wdt_max32_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
92 {
93 int ret = 0;
94 const struct max32_wdt_config *dev_cfg = dev->config;
95 struct max32_wdt_data *data = dev->data;
96 mxc_wdt_regs_t *regs = dev_cfg->regs;
97 wrap_mxc_wdt_cfg_t wdt_cfg;
98
99 if (cfg->window.max == 0U) {
100 return -EINVAL;
101 }
102
103 if (regs->ctrl & WRAP_MXC_F_WDT_CTRL_EN) {
104 return -EBUSY;
105 }
106
107 data->timeout = cfg->window;
108 data->callback = cfg->callback;
109
110 /* Default values to eliminate warnings */
111 wdt_cfg.mode = MXC_WDT_COMPATIBILITY;
112 wdt_cfg.upperResetPeriod = 0;
113 wdt_cfg.lowerResetPeriod = 0;
114 wdt_cfg.upperIntPeriod = 0;
115 wdt_cfg.lowerIntPeriod = 0;
116
117 if (data->timeout.min > 0) {
118 wdt_cfg.mode = MXC_WDT_WINDOWED;
119
120 ret = Wrap_MXC_WDT_Init(regs, &wdt_cfg);
121 if (ret != E_NO_ERROR) {
122 LOG_DBG("%s does not support windowed mode.", CONFIG_BOARD);
123 return -EINVAL;
124 }
125
126 int lower_timeout_period =
127 wdt_max32_calculate_timeout(data->timeout.min, dev_cfg->perclk.clk_src);
128 if (lower_timeout_period == -ENOTSUP) {
129 return -EINVAL;
130 }
131
132 if (data->callback == NULL) {
133 wdt_cfg.lowerResetPeriod = lower_timeout_period;
134 wdt_cfg.lowerIntPeriod = lower_timeout_period; /* Not used */
135 } else {
136 switch (lower_timeout_period) {
137 case MXC_WDT_PERIOD_2_16: /* Min timeout */
138 wdt_cfg.lowerResetPeriod = MXC_WDT_PERIOD_2_17;
139 wdt_cfg.lowerIntPeriod = MXC_WDT_PERIOD_2_16;
140 break;
141 default:
142 /* Generate interrupt just before reset */
143 wdt_cfg.lowerResetPeriod = lower_timeout_period;
144 /* +1 means one steps before */
145 wdt_cfg.lowerIntPeriod = lower_timeout_period + 1;
146 break;
147 }
148 }
149 }
150
151 int upper_timeout_period =
152 wdt_max32_calculate_timeout(data->timeout.max, dev_cfg->perclk.clk_src);
153 if (upper_timeout_period == -ENOTSUP) {
154 return -EINVAL;
155 }
156
157 if (data->callback == NULL) {
158 wdt_cfg.upperResetPeriod = upper_timeout_period;
159 wdt_cfg.upperIntPeriod = upper_timeout_period; /* Not used */
160 } else {
161 switch (upper_timeout_period) {
162 case MXC_WDT_PERIOD_2_16: /* Min timeout */
163 wdt_cfg.upperResetPeriod = MXC_WDT_PERIOD_2_17;
164 wdt_cfg.upperIntPeriod = MXC_WDT_PERIOD_2_16;
165 break;
166 default:
167 /* Generate interrupt just before reset */
168 wdt_cfg.upperResetPeriod = upper_timeout_period;
169 /* +1 means one steps before */
170 wdt_cfg.upperIntPeriod = upper_timeout_period + 1;
171 break;
172 }
173 }
174
175 Wrap_MXC_WDT_SetResetPeriod(regs, &wdt_cfg);
176
177 switch (cfg->flags) {
178 case WDT_FLAG_RESET_SOC:
179 MXC_WDT_EnableReset(regs);
180 LOG_DBG("Configuring reset SOC mode.");
181 break;
182
183 case WDT_FLAG_RESET_NONE:
184 MXC_WDT_DisableReset(regs);
185 LOG_DBG("Configuring non-reset mode.");
186 break;
187
188 default:
189 LOG_ERR("Unsupported watchdog config flag.");
190 return -ENOTSUP;
191 }
192
193 /* If callback is not null, enable interrupt. */
194 if (data->callback) {
195 Wrap_MXC_WDT_SetIntPeriod(regs, &wdt_cfg);
196 MXC_WDT_EnableInt(regs);
197 }
198
199 return ret;
200 }
201
wdt_max32_isr(const void * param)202 static void wdt_max32_isr(const void *param)
203 {
204 const struct device *dev = (const struct device *)param;
205 const struct max32_wdt_config *cfg = dev->config;
206 struct max32_wdt_data *data = dev->data;
207
208 if (data->callback) {
209 data->callback(dev, 0);
210 }
211
212 MXC_WDT_ClearIntFlag(cfg->regs);
213 }
214
wdt_max32_init(const struct device * dev)215 static int wdt_max32_init(const struct device *dev)
216 {
217 int ret = 0;
218 const struct max32_wdt_config *cfg = dev->config;
219 mxc_wdt_regs_t *regs = cfg->regs;
220
221 /* Enable clock */
222 ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->perclk);
223 if (ret) {
224 return ret;
225 }
226
227 ret = Wrap_MXC_WDT_SelectClockSource(regs, cfg->perclk.clk_src);
228 if (ret != E_NO_ERROR) {
229 LOG_ERR("WDT instance does not support given clock source.");
230 return -ENOTSUP;
231 }
232
233 /* Disable all actions */
234 MXC_WDT_Disable(regs);
235 MXC_WDT_DisableReset(regs);
236 MXC_WDT_DisableInt(regs);
237 MXC_WDT_ClearResetFlag(regs);
238 MXC_WDT_ClearIntFlag(regs);
239
240 cfg->irq_func(); /* WDT IRQ enable*/
241
242 return 0;
243 }
244
245 static DEVICE_API(wdt, max32_wdt_api) = {
246 .setup = wdt_max32_setup,
247 .disable = wdt_max32_disable,
248 .install_timeout = wdt_max32_install_timeout,
249 .feed = wdt_max32_feed,
250 };
251
252 #define MAX32_WDT_INIT(_num) \
253 static void wdt_max32_irq_init_##_num(void) \
254 { \
255 IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), wdt_max32_isr, \
256 DEVICE_DT_INST_GET(_num), 0); \
257 irq_enable(DT_INST_IRQN(_num)); \
258 } \
259 static struct max32_wdt_data max32_wdt_data##_num; \
260 static const struct max32_wdt_config max32_wdt_config##_num = { \
261 .regs = (mxc_wdt_regs_t *)DT_INST_REG_ADDR(_num), \
262 .clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(_num)), \
263 .perclk.clk_src = \
264 DT_INST_PROP_OR(_num, clock_source, ADI_MAX32_PRPH_CLK_SRC_PCLK), \
265 .perclk.bus = DT_INST_CLOCKS_CELL(_num, offset), \
266 .perclk.bit = DT_INST_CLOCKS_CELL(_num, bit), \
267 .irq_func = &wdt_max32_irq_init_##_num, \
268 }; \
269 DEVICE_DT_INST_DEFINE(_num, wdt_max32_init, NULL, &max32_wdt_data##_num, \
270 &max32_wdt_config##_num, POST_KERNEL, \
271 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &max32_wdt_api);
272
273 DT_INST_FOREACH_STATUS_OKAY(MAX32_WDT_INIT)
274