1 /*
2  * Copyright (c) 2024 Nuvoton Technology Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT nuvoton_numaker_wwdt
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/reset.h>
11 #include <zephyr/drivers/clock_control.h>
12 #include <zephyr/drivers/clock_control/clock_control_numaker.h>
13 #include <zephyr/drivers/watchdog.h>
14 #include <zephyr/logging/log.h>
15 #include <soc.h>
16 #include <NuMicro.h>
17 
18 LOG_MODULE_REGISTER(wwdt_numaker, CONFIG_WDT_LOG_LEVEL);
19 
20 #define NUMAKER_PRESCALER_MAX 15U
21 #define NUMAKER_COUNTER_MAX   0x3eU
22 #define NUMAKER_COUNTER_MIN   0x01U
23 
24 /* Device config */
25 struct wwdt_numaker_config {
26 	/* wdt base address */
27 	WWDT_T *wwdt_base;
28 	uint32_t clk_modidx;
29 	uint32_t clk_src;
30 	uint32_t clk_div;
31 	const struct device *clk_dev;
32 };
33 
34 struct wwdt_numaker_data {
35 	wdt_callback_t cb;
36 	bool timeout_valid;
37 	/* watchdog timeout in milliseconds */
38 	uint32_t timeout;
39 	uint32_t prescaler;
40 	uint32_t counter;
41 };
42 
m_wwdt_numaker_clk_get_rate(const struct wwdt_numaker_config * cfg,uint32_t * rate)43 static int m_wwdt_numaker_clk_get_rate(const struct wwdt_numaker_config *cfg, uint32_t *rate)
44 {
45 
46 	if (cfg->clk_src == CLK_CLKSEL1_WWDTSEL_LIRC) {
47 		*rate = __LIRC / (cfg->clk_div + 1);
48 	} else {
49 		/* clock source is from HCLK, CLK_CLKSEL1_WWDTSEL_HCLK_DIV2048 */
50 		SystemCoreClockUpdate();
51 		*rate = CLK_GetHCLKFreq() / 2048 / (cfg->clk_div + 1);
52 	}
53 
54 	return 0;
55 }
56 
57 
58 /* Convert watchdog clock to nearest ms (rounded up) */
m_wwdt_numaker_calc_ms(const struct device * dev,uint32_t pow2)59 static uint32_t m_wwdt_numaker_calc_ms(const struct device *dev, uint32_t pow2)
60 {
61 	const struct wwdt_numaker_config *cfg = dev->config;
62 	uint32_t clk_freq;
63 	uint32_t prescale_clks;
64 	uint32_t period_ms;
65 
66 	m_wwdt_numaker_clk_get_rate(cfg, &clk_freq);
67 	prescale_clks = (1 << pow2) * 64;
68 	period_ms = DIV_ROUND_UP(prescale_clks * MSEC_PER_SEC, clk_freq);
69 
70 	return period_ms;
71 }
72 
m_wwdt_numaker_calc_window(const struct device * dev,const struct wdt_window * win,uint32_t * timeout,uint32_t * prescaler,uint32_t * counter)73 static int m_wwdt_numaker_calc_window(const struct device *dev,
74 				      const struct wdt_window *win,
75 				      uint32_t *timeout,
76 				      uint32_t *prescaler,
77 				      uint32_t *counter)
78 {
79 	uint32_t pow2;
80 	uint32_t gap;
81 
82 	/* Find nearest period value (rounded up) */
83 	for (pow2 = 0U; pow2 <= NUMAKER_PRESCALER_MAX; pow2++) {
84 		*timeout = m_wwdt_numaker_calc_ms(dev, pow2);
85 
86 		if (*timeout >= win->max) {
87 			*prescaler = pow2 << WWDT_CTL_PSCSEL_Pos;
88 			if (win->min == 0U) {
89 				*counter = NUMAKER_COUNTER_MAX;
90 			} else {
91 				gap = DIV_ROUND_UP(win->min
92 						   * NUMAKER_COUNTER_MAX,
93 						   *timeout);
94 				*counter = NUMAKER_COUNTER_MAX - gap;
95 				if (*counter < NUMAKER_COUNTER_MIN) {
96 					*counter = NUMAKER_COUNTER_MIN;
97 				}
98 			}
99 
100 			return 0;
101 		}
102 	}
103 
104 	return -EINVAL;
105 }
106 
wwdt_numaker_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)107 static int wwdt_numaker_install_timeout(const struct device *dev,
108 					const struct wdt_timeout_cfg *cfg)
109 {
110 	struct wwdt_numaker_data *data = dev->data;
111 	const struct wwdt_numaker_config *config = dev->config;
112 	uint32_t timeout;
113 	uint32_t prescaler;
114 	uint32_t counter;
115 
116 	LOG_DBG("");
117 	/* Validate watchdog already running */
118 	if (config->wwdt_base->CTL & WWDT_CTL_WWDTEN_Msk) {
119 		LOG_ERR("watchdog is busy");
120 		return -EBUSY;
121 	}
122 
123 	if (cfg->window.max == 0U) {
124 		LOG_ERR("window.max should be non-zero");
125 		return -EINVAL;
126 	}
127 
128 	if (m_wwdt_numaker_calc_window(dev, &cfg->window, &timeout, &prescaler, &counter) != 0) {
129 		LOG_ERR("window.max is out of range");
130 		return -EINVAL;
131 	}
132 
133 	LOG_DBG("counter=%d", counter);
134 	data->timeout = timeout;
135 	data->prescaler = prescaler;
136 	data->counter = counter;
137 	data->cb = cfg->callback;
138 	data->timeout_valid = true;
139 
140 	return 0;
141 }
142 
wwdt_numaker_disable(const struct device * dev)143 static int wwdt_numaker_disable(const struct device *dev)
144 {
145 	struct wwdt_numaker_data *data = dev->data;
146 	const struct wwdt_numaker_config *cfg = dev->config;
147 	WWDT_T *wwdt_base = cfg->wwdt_base;
148 
149 	LOG_DBG("");
150 	/* stop counting */
151 	wwdt_base->CTL &= ~WWDT_CTL_WWDTEN_Msk;
152 
153 	/* disable interrupt enable bit */
154 	wwdt_base->CTL &= ~WWDT_CTL_INTEN_Msk;
155 
156 	/* disable interrupt */
157 	irq_disable(DT_INST_IRQN(0));
158 
159 	data->timeout_valid = false;
160 
161 	return 0;
162 }
163 
wwdt_numaker_setup(const struct device * dev,uint8_t options)164 static int wwdt_numaker_setup(const struct device *dev, uint8_t options)
165 {
166 	struct wwdt_numaker_data *data = dev->data;
167 	const struct wwdt_numaker_config *cfg = dev->config;
168 	WWDT_T *wwdt_base = cfg->wwdt_base;
169 	uint32_t dbg_mask = 0U;
170 
171 	LOG_DBG("");
172 	irq_disable(DT_INST_IRQN(0));
173 
174 	/* Validate watchdog already running */
175 	if (wwdt_base->CTL & WWDT_CTL_WWDTEN_Msk) {
176 		LOG_ERR("watchdog is busy");
177 		return -EBUSY;
178 	}
179 
180 	if (!data->timeout_valid) {
181 		LOG_ERR("No valid timeout installed");
182 		return -EINVAL;
183 	}
184 
185 	if (options & WDT_OPT_PAUSE_IN_SLEEP) {
186 		LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP is not supported");
187 		return -ENOTSUP;
188 	}
189 
190 	if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) {
191 		dbg_mask = WWDT_CTL_ICEDEBUG_Msk;
192 	}
193 
194 	/* Clear  WWDT Reset & Compared Match Interrupt System Flag */
195 	wwdt_base->STATUS = WWDT_STATUS_WWDTRF_Msk |
196 			    WWDT_STATUS_WWDTIF_Msk;
197 
198 	/* Open WWDT and start counting */
199 	wwdt_base->CTL = data->prescaler |
200 			 (data->counter << WWDT_CTL_CMPDAT_Pos) |
201 			 WWDT_CTL_INTEN_Msk |
202 			 WWDT_CTL_WWDTEN_Msk |
203 			 dbg_mask;
204 
205 	irq_enable(DT_INST_IRQN(0));
206 
207 	return 0;
208 }
209 
wwdt_numaker_feed(const struct device * dev,int channel_id)210 static int wwdt_numaker_feed(const struct device *dev, int channel_id)
211 {
212 	const struct wwdt_numaker_config *cfg = dev->config;
213 	WWDT_T *wwdt_base = cfg->wwdt_base;
214 
215 	LOG_DBG("CNT=%d, CTL=0x%x", wwdt_base->CNT, wwdt_base->CTL);
216 	ARG_UNUSED(channel_id);
217 
218 	/* Reload WWDT Counter */
219 	wwdt_base->RLDCNT = WWDT_RELOAD_WORD;
220 
221 	return 0;
222 }
223 
wwdt_numaker_isr(const struct device * dev)224 static void wwdt_numaker_isr(const struct device *dev)
225 {
226 	struct wwdt_numaker_data *data = dev->data;
227 	const struct wwdt_numaker_config *cfg = dev->config;
228 	WWDT_T *wwdt_base = cfg->wwdt_base;
229 
230 	LOG_DBG("CNT=%d", wwdt_base->CNT);
231 	if (wwdt_base->STATUS & WWDT_STATUS_WWDTIF_Msk) {
232 		/* Clear WWDT Compared Match Interrupt Flag */
233 		wwdt_base->STATUS = WWDT_STATUS_WWDTIF_Msk;
234 
235 		if (data->cb != NULL) {
236 			data->cb(dev, 0);
237 		}
238 	}
239 }
240 
241 static DEVICE_API(wdt, wwdt_numaker_api) = {
242 	.setup = wwdt_numaker_setup,
243 	.disable = wwdt_numaker_disable,
244 	.install_timeout = wwdt_numaker_install_timeout,
245 	.feed = wwdt_numaker_feed,
246 };
247 
wwdt_numaker_init(const struct device * dev)248 static int wwdt_numaker_init(const struct device *dev)
249 {
250 	const struct wwdt_numaker_config *cfg = dev->config;
251 	struct numaker_scc_subsys scc_subsys;
252 	int err;
253 
254 	SYS_UnlockReg();
255 
256 	irq_disable(DT_INST_IRQN(0));
257 	/* CLK controller */
258 	memset(&scc_subsys, 0x00, sizeof(scc_subsys));
259 	scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
260 	scc_subsys.pcc.clk_modidx = cfg->clk_modidx;
261 	scc_subsys.pcc.clk_src = cfg->clk_src;
262 	scc_subsys.pcc.clk_div = cfg->clk_div;
263 
264 	/* Equivalent to CLK_EnableModuleClock() */
265 	err = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys);
266 	if (err != 0) {
267 		goto done;
268 	}
269 
270 	/* Equivalent to CLK_SetModuleClock() */
271 	err = clock_control_configure(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL);
272 	if (err != 0) {
273 		goto done;
274 	}
275 
276 	/* Enable NVIC */
277 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
278 		    wwdt_numaker_isr, DEVICE_DT_INST_GET(0), 0);
279 	irq_enable(DT_INST_IRQN(0));
280 
281 done:
282 	SYS_LockReg();
283 	return err;
284 
285 }
286 
287 /* Set config based on DTS */
288 static struct wwdt_numaker_config wwdt_numaker_cfg_inst = {
289 	.wwdt_base = (WWDT_T *)DT_INST_REG_ADDR(0),
290 	.clk_modidx = DT_INST_CLOCKS_CELL(0, clock_module_index),
291 	.clk_src = DT_INST_CLOCKS_CELL(0, clock_source),
292 	.clk_div = DT_INST_CLOCKS_CELL(0, clock_divider),
293 	.clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(0))),
294 };
295 
296 static struct wwdt_numaker_data wwdt_numaker_data_inst;
297 
298 DEVICE_DT_INST_DEFINE(0, wwdt_numaker_init, NULL,
299 		      &wwdt_numaker_data_inst, &wwdt_numaker_cfg_inst,
300 		      POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
301 		      &wwdt_numaker_api);
302