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