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