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