1 /*
2  * Copyright (c) 2022 Florin Stancu <niflostancu@gmail.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_cc13xx_cc26xx_watchdog
8 
9 #include <zephyr/drivers/watchdog.h>
10 #include <zephyr/irq.h>
11 #include <soc.h>
12 #include <errno.h>
13 
14 #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(wdt_cc13xx_cc26xx);
17 
18 /* Driverlib includes */
19 #include <driverlib/watchdog.h>
20 
21 
22 /*
23  * TI CC13xx/CC26xx watchdog is a 32-bit timer that runs on the MCU clock
24  * with a fixed 32 divider.
25  *
26  * For the default MCU frequency of 48MHz:
27  * 1ms = (48e6 / 32 / 1000) = 1500 ticks
28  * Max. value = 2^32 / 1500 ~= 2863311 ms
29  *
30  * The watchdog will issue reset only on second in turn time-out (if the timer
31  * or the interrupt aren't reset after the first time-out). By default, regular
32  * interrupt is generated but platform supports also NMI (can be enabled by
33  * setting the `interrupt-nmi` boolean DT property).
34  */
35 
36 #define CPU_FREQ DT_PROP(DT_PATH(cpus, cpu_0), clock_frequency)
37 #define WATCHDOG_DIV_RATIO	 32
38 #define WATCHDOG_MS_RATIO	 (CPU_FREQ / WATCHDOG_DIV_RATIO / 1000)
39 #define WATCHDOG_MAX_RELOAD_MS	 (0xFFFFFFFFu / WATCHDOG_MS_RATIO)
40 #define WATCHDOG_MS_TO_TICKS(_ms) ((_ms) * WATCHDOG_MS_RATIO)
41 
42 struct wdt_cc13xx_cc26xx_data {
43 	uint8_t enabled;
44 	uint32_t reload;
45 	wdt_callback_t cb;
46 	uint8_t flags;
47 };
48 
49 struct wdt_cc13xx_cc26xx_cfg {
50 	uint32_t reg;
51 	uint8_t irq_nmi;
52 	void (*irq_cfg_func)(void);
53 };
54 
wdt_cc13xx_cc26xx_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)55 static int wdt_cc13xx_cc26xx_install_timeout(const struct device *dev,
56 				      const struct wdt_timeout_cfg *cfg)
57 {
58 	struct wdt_cc13xx_cc26xx_data *data = dev->data;
59 
60 	/* window watchdog not supported */
61 	if (cfg->window.min != 0U || cfg->window.max == 0U) {
62 		return -EINVAL;
63 	}
64 	/*
65 	 * Note: since this SoC doesn't define CONFIG_WDT_MULTISTAGE, we don't need to
66 	 * specifically check for it and return ENOTSUP
67 	 */
68 
69 	if (cfg->window.max > WATCHDOG_MAX_RELOAD_MS) {
70 		return -EINVAL;
71 	}
72 	data->reload = WATCHDOG_MS_TO_TICKS(cfg->window.max);
73 	data->cb = cfg->callback;
74 	data->flags = cfg->flags;
75 	LOG_DBG("raw reload value: %d", data->reload);
76 	return 0;
77 }
78 
wdt_cc13xx_cc26xx_setup(const struct device * dev,uint8_t options)79 static int wdt_cc13xx_cc26xx_setup(const struct device *dev, uint8_t options)
80 {
81 	const struct wdt_cc13xx_cc26xx_cfg *config = dev->config;
82 	struct wdt_cc13xx_cc26xx_data *data = dev->data;
83 
84 	/*
85 	 * Note: don't check if watchdog is already enabled, an application might
86 	 * want to dynamically re-configure its options (e.g., decrease the reload
87 	 * value for critical sections).
88 	 */
89 
90 	WatchdogUnlock();
91 
92 	/* clear any previous interrupt flags */
93 	WatchdogIntClear();
94 
95 	/* Stall the WDT counter when halted by debugger */
96 	if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) {
97 		WatchdogStallEnable();
98 	} else {
99 		WatchdogStallDisable();
100 	}
101 	/*
102 	 * According to TI's datasheets, the WDT is paused in STANDBY mode,
103 	 * so we simply continue with the setup => don't do this check:
104 	 * > if (options & WDT_OPT_PAUSE_IN_SLEEP) {
105 	 * >	return -ENOTSUP;
106 	 * > }
107 	 */
108 
109 	/* raw reload value was computed by `_install_timeout()` */
110 	WatchdogReloadSet(data->reload);
111 
112 	/* use the Device Tree-configured interrupt type */
113 	if (config->irq_nmi) {
114 		LOG_DBG("NMI enabled");
115 		WatchdogIntTypeSet(WATCHDOG_INT_TYPE_NMI);
116 	} else {
117 		WatchdogIntTypeSet(WATCHDOG_INT_TYPE_INT);
118 	}
119 
120 	switch ((data->flags & WDT_FLAG_RESET_MASK)) {
121 	case WDT_FLAG_RESET_NONE:
122 		LOG_DBG("reset disabled");
123 		WatchdogResetDisable();
124 		break;
125 	case WDT_FLAG_RESET_SOC:
126 		LOG_DBG("reset enabled");
127 		WatchdogResetEnable();
128 		break;
129 	default:
130 		WatchdogLock();
131 		return -ENOTSUP;
132 	}
133 
134 	data->enabled = 1;
135 	WatchdogEnable();
136 	WatchdogLock();
137 
138 	LOG_DBG("done");
139 	return 0;
140 }
141 
wdt_cc13xx_cc26xx_disable(const struct device * dev)142 static int wdt_cc13xx_cc26xx_disable(const struct device *dev)
143 {
144 	struct wdt_cc13xx_cc26xx_data *data = dev->data;
145 
146 	if (!WatchdogRunning()) {
147 		return -EFAULT;
148 	}
149 
150 	/*
151 	 * Node: once started, the watchdog timer cannot be stopped!
152 	 * All we can do is disable the timeout reset, but the interrupt
153 	 * will be triggered if it was enabled (though it won't trigger the
154 	 * user callback due to `enabled` being unsed)!
155 	 */
156 	data->enabled = 0;
157 	WatchdogUnlock();
158 	WatchdogResetDisable();
159 	WatchdogLock();
160 
161 	return 0;
162 }
163 
wdt_cc13xx_cc26xx_feed(const struct device * dev,int channel_id)164 static int wdt_cc13xx_cc26xx_feed(const struct device *dev, int channel_id)
165 {
166 	struct wdt_cc13xx_cc26xx_data *data = dev->data;
167 
168 	WatchdogUnlock();
169 	WatchdogIntClear();
170 	WatchdogReloadSet(data->reload);
171 	WatchdogLock();
172 	LOG_DBG("feed %i", data->reload);
173 	return 0;
174 }
175 
wdt_cc13xx_cc26xx_isr(const struct device * dev)176 static void wdt_cc13xx_cc26xx_isr(const struct device *dev)
177 {
178 	struct wdt_cc13xx_cc26xx_data *data = dev->data;
179 
180 	/* Simulate the watchdog being disabled: don't call the handler. */
181 	if (!data->enabled) {
182 		return;
183 	}
184 
185 	/*
186 	 * Note: don't clear the interrupt here, leave it for the callback
187 	 * to decide (by calling `_feed()`)
188 	 */
189 
190 	LOG_DBG("ISR");
191 	if (data->cb) {
192 		data->cb(dev, 0);
193 	}
194 }
195 
wdt_cc13xx_cc26xx_init(const struct device * dev)196 static int wdt_cc13xx_cc26xx_init(const struct device *dev)
197 {
198 	const struct wdt_cc13xx_cc26xx_cfg *config = dev->config;
199 	uint8_t options = 0;
200 
201 	LOG_DBG("init");
202 	config->irq_cfg_func();
203 
204 	if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
205 		return 0;
206 	}
207 
208 #ifdef CONFIG_DEBUG
209 	/* when CONFIG_DEBUG is enabled, pause the WDT during debugging */
210 	options = WDT_OPT_PAUSE_HALTED_BY_DBG;
211 #endif /* CONFIG_DEBUG */
212 
213 	return wdt_cc13xx_cc26xx_setup(dev, options);
214 }
215 
216 static DEVICE_API(wdt, wdt_cc13xx_cc26xx_api) = {
217 	.setup = wdt_cc13xx_cc26xx_setup,
218 	.disable = wdt_cc13xx_cc26xx_disable,
219 	.install_timeout = wdt_cc13xx_cc26xx_install_timeout,
220 	.feed = wdt_cc13xx_cc26xx_feed,
221 };
222 
223 #define CC13XX_CC26XX_WDT_INIT(index)						 \
224 	static void wdt_cc13xx_cc26xx_irq_cfg_##index(void)			 \
225 	{									 \
226 		if (DT_INST_PROP(index, interrupt_nmi)) {			 \
227 			return; /* NMI interrupt is used */			 \
228 		}								 \
229 		IRQ_CONNECT(DT_INST_IRQN(index),				 \
230 			DT_INST_IRQ(index, priority),				 \
231 			wdt_cc13xx_cc26xx_isr, DEVICE_DT_INST_GET(index), 0);	 \
232 		irq_enable(DT_INST_IRQN(index));				 \
233 	}									 \
234 	static struct wdt_cc13xx_cc26xx_data wdt_cc13xx_cc26xx_data_##index = {	 \
235 		.reload = WATCHDOG_MS_TO_TICKS(					 \
236 			CONFIG_WDT_CC13XX_CC26XX_INITIAL_TIMEOUT),		 \
237 		.cb = NULL,							 \
238 		.flags = 0,							 \
239 	};									 \
240 	static struct wdt_cc13xx_cc26xx_cfg wdt_cc13xx_cc26xx_cfg_##index = {	 \
241 		.reg = DT_INST_REG_ADDR(index),					 \
242 		.irq_nmi = DT_INST_PROP(index, interrupt_nmi),			 \
243 		.irq_cfg_func = wdt_cc13xx_cc26xx_irq_cfg_##index,		 \
244 	};									 \
245 	DEVICE_DT_INST_DEFINE(index,						 \
246 		wdt_cc13xx_cc26xx_init, NULL,					 \
247 		&wdt_cc13xx_cc26xx_data_##index,				 \
248 		&wdt_cc13xx_cc26xx_cfg_##index,					 \
249 		POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,		 \
250 		&wdt_cc13xx_cc26xx_api);
251 
252 DT_INST_FOREACH_STATUS_OKAY(CC13XX_CC26XX_WDT_INIT)
253