1 /*
2 * Copyright (c) 2021 Nuvoton Technology Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nuvoton_npcx_watchdog
8
9 /**
10 * @file
11 * @brief Nuvoton NPCX watchdog modules driver
12 *
13 * This file contains the drivers of NPCX Watchdog module that generates the
14 * clocks and interrupts (T0 Timer) used for its callback functions in the
15 * system. It also provides watchdog reset signal generation in response to a
16 * failure detection. Please refer the block diagram for more detail.
17 *
18 * +---------------------+ +-----------------+
19 * LFCLK --->| T0 Prescale Counter |-+->| 16-Bit T0 Timer |--------> T0 Timer
20 * (32kHz) | (TWCP 1:32) | | | (TWDT0) | Event
21 * +---------------------+ | +-----------------+
22 * +---------------------------------+
23 * |
24 * | +-------------------+ +-----------------+
25 * +--->| Watchdog Prescale |--->| 8-Bit Watchdog |-----> Watchdog Event/Reset
26 * | (WDCP 1:32) | | Counter (WDCNT) | after n clocks
27 * +-------------------+ +-----------------+
28 *
29 */
30
31 #include "soc_miwu.h"
32
33 #include <assert.h>
34
35 #include <zephyr/drivers/clock_control.h>
36 #include <zephyr/drivers/gpio.h>
37 #include <zephyr/drivers/watchdog.h>
38 #include <zephyr/kernel.h>
39 #include <zephyr/logging/log.h>
40
41 #include <soc.h>
42 #include "soc_dbg.h"
43 LOG_MODULE_REGISTER(wdt_npcx, CONFIG_WDT_LOG_LEVEL);
44
45 /* Watchdog operating frequency is fixed to LFCLK (32.768) kHz */
46 #define NPCX_WDT_CLK LFCLK
47
48 /*
49 * Maximum watchdog window time. Keep the timer and watchdog clock prescaler
50 * (TWCP) to 0x5. Since the watchdog counter is 8-bits, maximum time supported
51 * by npcx watchdog is 256 * (32 * 32768) / 32768 = 8192 sec.
52 * The maximum time supported of T0OUT is 65536 * 32 / 32768 = 64 sec.
53 * Thus, the maximum time of watchdog set here is 64 sec.
54 */
55 #define NPCX_WDT_MAX_WND_TIME 64000UL
56
57 /*
58 * Minimum watchdog window time. Ensure we have waited at least 3 watchdog
59 * clocks since touching WD timer. 3 / (32768 / 1024) HZ = 93.75ms
60 */
61 #define NPCX_WDT_MIN_WND_TIME 100UL
62
63 /* Timeout for reloading and restarting Timer 0. (Unit:ms) */
64 #define NPCX_T0CSR_RST_TIMEOUT 2
65
66 /* Timeout for stopping watchdog. (Unit:ms) */
67 #define NPCX_WATCHDOG_STOP_TIMEOUT 1
68
69 /* Device config */
70 struct wdt_npcx_config {
71 /* wdt controller base address */
72 uintptr_t base;
73 /* t0 timer wake-up input source configuration */
74 const struct npcx_wui t0out;
75 };
76
77 /* Driver data */
78 struct wdt_npcx_data {
79 /* Timestamp of touching watchdog last time */
80 int64_t last_watchdog_touch;
81 /* Timeout callback used to handle watchdog event */
82 wdt_callback_t cb;
83 /* Watchdog feed timeout in milliseconds */
84 uint32_t timeout;
85 /* Indicate whether a watchdog timeout is installed */
86 bool timeout_installed;
87 };
88
89 struct miwu_callback miwu_cb;
90
91 /* Driver convenience defines */
92 #define HAL_INSTANCE(dev) ((struct twd_reg *)((const struct wdt_npcx_config *)(dev)->config)->base)
93
94 /* WDT local inline functions */
wdt_t0out_reload(const struct device * dev)95 static inline int wdt_t0out_reload(const struct device *dev)
96 {
97 struct twd_reg *const inst = HAL_INSTANCE(dev);
98 uint64_t st;
99
100 /* Reload and restart T0 timer */
101 inst->T0CSR = (inst->T0CSR & ~BIT(NPCX_T0CSR_WDRST_STS)) |
102 BIT(NPCX_T0CSR_RST);
103 /* Wait for timer is loaded and restart */
104 st = k_uptime_get();
105 while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST)) {
106 if (k_uptime_get() - st > NPCX_T0CSR_RST_TIMEOUT) {
107 /* RST bit is still set? */
108 if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_RST)) {
109 LOG_ERR("Timeout: reload T0 timer!");
110 return -ETIMEDOUT;
111 }
112 }
113 }
114
115 return 0;
116 }
117
wdt_wait_stopped(const struct device * dev)118 static inline int wdt_wait_stopped(const struct device *dev)
119 {
120 struct twd_reg *const inst = HAL_INSTANCE(dev);
121 uint64_t st;
122
123 st = k_uptime_get();
124 /* If watchdog is still running? */
125 while (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) {
126 if (k_uptime_get() - st > NPCX_WATCHDOG_STOP_TIMEOUT) {
127 /* WD_RUN bit is still set? */
128 if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) {
129 LOG_ERR("Timeout: stop watchdog timer!");
130 return -ETIMEDOUT;
131 }
132 }
133 }
134
135 return 0;
136 }
137
138 /* WDT local functions */
wdt_t0out_isr(const struct device * dev,struct npcx_wui * wui)139 static void wdt_t0out_isr(const struct device *dev, struct npcx_wui *wui)
140 {
141 struct wdt_npcx_data *const data = dev->data;
142 ARG_UNUSED(wui);
143
144 LOG_DBG("WDT reset will issue after %d delay cycle! WUI(%d %d %d)",
145 CONFIG_WDT_NPCX_WARNING_LEADING_TIME_MS, wui->table, wui->group, wui->bit);
146
147 /* Handle watchdog event here. */
148 if (data->cb) {
149 data->cb(dev, 0);
150 }
151 }
152
wdt_config_t0out_interrupt(const struct device * dev)153 static void wdt_config_t0out_interrupt(const struct device *dev)
154 {
155 const struct wdt_npcx_config *const config = dev->config;
156
157 /* Initialize a miwu device input and its callback function */
158 npcx_miwu_init_dev_callback(&miwu_cb, &config->t0out, wdt_t0out_isr,
159 dev);
160 npcx_miwu_manage_callback(&miwu_cb, true);
161
162 /*
163 * Configure the T0 wake-up event triggered from a rising edge
164 * on T0OUT signal.
165 */
166 npcx_miwu_interrupt_configure(&config->t0out,
167 NPCX_MIWU_MODE_EDGE, NPCX_MIWU_TRIG_HIGH);
168 }
169
170 /* WDT api functions */
wdt_npcx_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)171 static int wdt_npcx_install_timeout(const struct device *dev,
172 const struct wdt_timeout_cfg *cfg)
173 {
174 struct wdt_npcx_data *const data = dev->data;
175 struct twd_reg *const inst = HAL_INSTANCE(dev);
176
177 /* If watchdog is already running */
178 if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) {
179 return -EBUSY;
180 }
181
182 /* No window watchdog support */
183 if (cfg->window.min != 0) {
184 data->timeout_installed = false;
185 return -EINVAL;
186 }
187
188 /*
189 * Since the watchdog counter in npcx series is 8-bits, maximum time
190 * supported by it is 256 * (32 * 32) / 32768 = 8 sec. This makes the
191 * allowed range of 1-8000 in milliseconds. Check if the provided value
192 * is within this range.
193 */
194 if (cfg->window.max > NPCX_WDT_MAX_WND_TIME || cfg->window.max == 0) {
195 data->timeout_installed = false;
196 return -EINVAL;
197 }
198
199 /* Save watchdog timeout */
200 data->timeout = cfg->window.max;
201
202 /* Install user timeout isr */
203 data->cb = cfg->callback;
204 data->timeout_installed = true;
205
206 return 0;
207 }
208
wdt_npcx_setup(const struct device * dev,uint8_t options)209 static int wdt_npcx_setup(const struct device *dev, uint8_t options)
210 {
211 struct twd_reg *const inst = HAL_INSTANCE(dev);
212 const struct wdt_npcx_config *const config = dev->config;
213 struct wdt_npcx_data *const data = dev->data;
214 uint32_t wd_cnt, pre_scal;
215 uint8_t wdcp;
216
217 int rv;
218
219 /* Disable irq of t0-out expired event first */
220 npcx_miwu_irq_disable(&config->t0out);
221
222 if (!data->timeout_installed) {
223 LOG_ERR("No valid WDT timeout installed");
224 return -EINVAL;
225 }
226
227 if (IS_BIT_SET(inst->T0CSR, NPCX_T0CSR_WD_RUN)) {
228 LOG_ERR("WDT timer is busy");
229 return -EBUSY;
230 }
231
232 if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) {
233 LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP is not supported");
234 return -ENOTSUP;
235 }
236
237 /* Stall the WDT counter when halted by debugger */
238 if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0) {
239 npcx_dbg_freeze_enable(true);
240 } else {
241 npcx_dbg_freeze_enable(false);
242 }
243
244 /*
245 * One clock period of T0 timer is 32/32.768 KHz = 0.976 ms.
246 * Then the counter value is timeout/0.976 - 1.
247 */
248 inst->TWDT0 = MAX(DIV_ROUND_UP(data->timeout * NPCX_WDT_CLK,
249 32 * 1000) - 1, 1);
250
251 /* Configure 8-bit watchdog counter
252 * Change the prescaler of watchdog clock for larger timeout
253 */
254 wd_cnt = DIV_ROUND_UP((data->timeout + CONFIG_WDT_NPCX_WARNING_LEADING_TIME_MS) *
255 NPCX_WDT_CLK,
256 32 * 1000);
257
258 pre_scal = DIV_ROUND_UP(wd_cnt, 255);
259
260 /*
261 * Find the smallest power of 2 greater than or equal to the
262 * prescaler
263 */
264 wdcp = LOG2(pre_scal - 1) + 1;
265 pre_scal = 1 << wdcp;
266
267 inst->WDCP = wdcp;
268 inst->WDCNT = wd_cnt / pre_scal;
269
270 LOG_DBG("WDT setup: TWDT0, WDCNT are %d, %d", inst->TWDT0, inst->WDCNT);
271
272 /* Reload and restart T0 timer */
273 rv = wdt_t0out_reload(dev);
274
275 /* Configure t0 timer interrupt and its isr. */
276 wdt_config_t0out_interrupt(dev);
277
278 /* Enable irq of t0-out expired event */
279 npcx_miwu_irq_enable(&config->t0out);
280
281 return rv;
282 }
283
wdt_npcx_disable(const struct device * dev)284 static int wdt_npcx_disable(const struct device *dev)
285 {
286 const struct wdt_npcx_config *const config = dev->config;
287 struct wdt_npcx_data *const data = dev->data;
288 struct twd_reg *const inst = HAL_INSTANCE(dev);
289 uint16_t min_wnd_t;
290
291 /*
292 * Ensure we have waited at least 3 watchdog ticks before
293 * stopping watchdog
294 */
295 min_wnd_t = DIV_ROUND_UP(3 * NPCX_WDT_CLK, 32 * (1 << inst->WDCP));
296 while (k_uptime_get() - data->last_watchdog_touch < min_wnd_t) {
297 continue;
298 }
299
300 /*
301 * Stop and unlock watchdog by writing 87h, 61h and 63h
302 * sequence bytes to WDSDM register
303 */
304 inst->WDSDM = 0x87;
305 inst->WDSDM = 0x61;
306 inst->WDSDM = 0x63;
307
308 /* Disable irq of t0-out expired event and mark it uninstalled */
309 npcx_miwu_irq_disable(&config->t0out);
310 data->timeout_installed = false;
311
312 /* Wait until watchdog is stopped. */
313 return wdt_wait_stopped(dev);
314 }
315
wdt_npcx_feed(const struct device * dev,int channel_id)316 static int wdt_npcx_feed(const struct device *dev, int channel_id)
317 {
318 ARG_UNUSED(channel_id);
319 struct wdt_npcx_data *const data = dev->data;
320 struct twd_reg *const inst = HAL_INSTANCE(dev);
321
322 /* Feed watchdog by writing 5Ch to WDSDM */
323 inst->WDSDM = 0x5C;
324 data->last_watchdog_touch = k_uptime_get();
325
326 /* Reload and restart T0 timer */
327 return wdt_t0out_reload(dev);
328 }
329
330 /* WDT driver registration */
331 static const struct wdt_driver_api wdt_npcx_driver_api = {
332 .setup = wdt_npcx_setup,
333 .disable = wdt_npcx_disable,
334 .install_timeout = wdt_npcx_install_timeout,
335 .feed = wdt_npcx_feed,
336 };
337
wdt_npcx_init(const struct device * dev)338 static int wdt_npcx_init(const struct device *dev)
339 {
340 struct twd_reg *const inst = HAL_INSTANCE(dev);
341
342 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
343 wdt_npcx_disable(dev);
344 #endif
345
346 /*
347 * TWCFG (Timer Watchdog Configuration) setting
348 * [7:6]- Reserved = 0
349 * [5] - WDSDME = 1: Feed watchdog by writing 5Ch to WDSDM
350 * [4] - WDCT0I = 1: Select T0IN as watchdog prescaler clock
351 * [3] - LWDCNT = 0: Don't lock WDCNT register
352 * [2] - LTWDT0 = 0: Don't lock TWDT0 register
353 * [1] - LTWCP = 0: Don't lock TWCP register
354 * [0] - LTWCFG = 0: Don't lock TWCFG register
355 */
356 inst->TWCFG = BIT(NPCX_TWCFG_WDSDME) | BIT(NPCX_TWCFG_WDCT0I);
357
358 /* Disable early touch functionality */
359 inst->T0CSR = (inst->T0CSR & ~BIT(NPCX_T0CSR_WDRST_STS)) |
360 BIT(NPCX_T0CSR_TESDIS);
361 /*
362 * Plan clock frequency of T0 timer and watchdog timer as below:
363 * - T0 Timer freq is LFCLK/32 Hz
364 * - Watchdog freq is T0CLK/32 Hz (ie. LFCLK/1024 Hz)
365 */
366 inst->WDCP = 0x05; /* Prescaler is 32 in Watchdog Timer */
367 inst->TWCP = 0x05; /* Prescaler is 32 in T0 Timer */
368
369 return 0;
370 }
371
372 static const struct wdt_npcx_config wdt_npcx_cfg_0 = {
373 .base = DT_INST_REG_ADDR(0),
374 .t0out = NPCX_DT_WUI_ITEM_BY_NAME(0, t0_out)
375 };
376
377 static struct wdt_npcx_data wdt_npcx_data_0;
378
379 DEVICE_DT_INST_DEFINE(0, wdt_npcx_init, NULL,
380 &wdt_npcx_data_0, &wdt_npcx_cfg_0,
381 PRE_KERNEL_1,
382 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
383 &wdt_npcx_driver_api);
384