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