1 /*
2  * Copyright (c) 2021 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/watchdog.h>
8 #include <zephyr/drivers/counter.h>
9 #include <zephyr/logging/log_ctrl.h>
10 
11 #define WDT_CHANNEL_COUNT DT_PROP(DT_WDT_COUNTER, num_channels)
12 #define DT_WDT_COUNTER DT_COMPAT_GET_ANY_STATUS_OKAY(zephyr_counter_watchdog)
13 
14 #define WDT_SUPPORTED_CFG_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC)
15 
16 extern void sys_arch_reboot(int type);
17 
18 struct wdt_counter_data {
19 	wdt_callback_t callback[CONFIG_WDT_COUNTER_CH_COUNT];
20 	uint32_t timeout[CONFIG_WDT_COUNTER_CH_COUNT];
21 	uint8_t flags[CONFIG_WDT_COUNTER_CH_COUNT];
22 	uint8_t alloc_cnt;
23 };
24 
25 static struct wdt_counter_data wdt_data;
26 
27 struct wdt_counter_config {
28 	const struct device *counter;
29 };
30 
wdt_counter_setup(const struct device * dev,uint8_t options)31 static int wdt_counter_setup(const struct device *dev, uint8_t options)
32 {
33 	const struct wdt_counter_config *config = dev->config;
34 	const struct device *counter = config->counter;
35 
36 	if ((options & WDT_OPT_PAUSE_IN_SLEEP) || (options & WDT_OPT_PAUSE_HALTED_BY_DBG)) {
37 		return -ENOTSUP;
38 	}
39 
40 	return counter_start(counter);
41 }
42 
wdt_counter_disable(const struct device * dev)43 static int wdt_counter_disable(const struct device *dev)
44 {
45 	const struct wdt_counter_config *config = dev->config;
46 	const struct device *counter = config->counter;
47 
48 	return counter_stop(counter);
49 }
50 
counter_alarm_callback(const struct device * dev,uint8_t chan_id,uint32_t ticks,void * user_data)51 static void counter_alarm_callback(const struct device *dev,
52 				   uint8_t chan_id, uint32_t ticks,
53 				   void *user_data)
54 {
55 	const struct device *wdt_dev = user_data;
56 	struct wdt_counter_data *data = wdt_dev->data;
57 
58 	counter_stop(dev);
59 	if (data->callback[chan_id]) {
60 		data->callback[chan_id](wdt_dev, chan_id);
61 	}
62 
63 	if (data->flags[chan_id] & WDT_FLAG_RESET_SOC) {
64 		LOG_PANIC();
65 		sys_arch_reboot(0);
66 	}
67 }
68 
timeout_set(const struct device * dev,int chan_id,bool cancel)69 static int timeout_set(const struct device *dev, int chan_id, bool cancel)
70 {
71 	const struct wdt_counter_config *config = dev->config;
72 	struct wdt_counter_data *data = dev->data;
73 	const struct device *counter = config->counter;
74 	struct counter_alarm_cfg alarm_cfg = {
75 		.callback = counter_alarm_callback,
76 		.ticks = data->timeout[chan_id],
77 		.user_data = (void *)dev,
78 		.flags = 0
79 	};
80 
81 	if (cancel) {
82 		int err = counter_cancel_channel_alarm(counter, chan_id);
83 
84 		if (err < 0) {
85 			return err;
86 		}
87 	}
88 
89 	return counter_set_channel_alarm(counter, chan_id, &alarm_cfg);
90 }
91 
wdt_counter_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)92 static int wdt_counter_install_timeout(const struct device *dev,
93 				   const struct wdt_timeout_cfg *cfg)
94 {
95 	struct wdt_counter_data *data = dev->data;
96 	const struct wdt_counter_config *config = dev->config;
97 	const struct device *counter = config->counter;
98 	int chan_id;
99 
100 	if (!device_is_ready(counter)) {
101 		return -EIO;
102 	}
103 
104 	uint32_t max_timeout = counter_get_top_value(counter) -
105 				counter_get_guard_period(counter,
106 				COUNTER_GUARD_PERIOD_LATE_TO_SET);
107 	uint32_t timeout_ticks = counter_us_to_ticks(counter, cfg->window.max * 1000);
108 
109 	if (cfg->flags & ~WDT_SUPPORTED_CFG_FLAGS) {
110 		return -ENOTSUP;
111 	}
112 
113 	if (cfg->window.min != 0U) {
114 		return -EINVAL;
115 	}
116 
117 	if (timeout_ticks > max_timeout || timeout_ticks == 0) {
118 		return -EINVAL;
119 	}
120 
121 	if (data->alloc_cnt == 0) {
122 		return -ENOMEM;
123 	}
124 
125 	data->alloc_cnt--;
126 	chan_id = data->alloc_cnt;
127 	data->timeout[chan_id] = timeout_ticks;
128 	data->callback[chan_id] = cfg->callback;
129 	data->flags[chan_id] = cfg->flags;
130 
131 	int err = timeout_set(dev, chan_id, false);
132 
133 	if (err < 0) {
134 		return err;
135 	}
136 
137 	return chan_id;
138 }
139 
wdt_counter_feed(const struct device * dev,int chan_id)140 static int wdt_counter_feed(const struct device *dev, int chan_id)
141 {
142 	const struct wdt_counter_config *config = dev->config;
143 
144 	if (chan_id > counter_get_num_of_channels(config->counter)) {
145 		return -EINVAL;
146 	}
147 
148 	/* Move alarm further in time. */
149 	return timeout_set(dev, chan_id, true);
150 }
151 
152 static const struct wdt_driver_api wdt_counter_driver_api = {
153 	.setup = wdt_counter_setup,
154 	.disable = wdt_counter_disable,
155 	.install_timeout = wdt_counter_install_timeout,
156 	.feed = wdt_counter_feed,
157 };
158 
159 static const struct wdt_counter_config wdt_counter_config = {
160 	.counter = DEVICE_DT_GET(DT_PHANDLE(DT_WDT_COUNTER, counter)),
161 };
162 
163 
wdt_counter_init(const struct device * dev)164 static int wdt_counter_init(const struct device *dev)
165 {
166 	const struct wdt_counter_config *config = dev->config;
167 	struct wdt_counter_data *data = dev->data;
168 	uint8_t ch_cnt = counter_get_num_of_channels(config->counter);
169 
170 	data->alloc_cnt = MIN(ch_cnt, CONFIG_WDT_COUNTER_CH_COUNT);
171 
172 	return 0;
173 }
174 
175 DEVICE_DT_DEFINE(DT_WDT_COUNTER, wdt_counter_init, NULL,
176 		 &wdt_data, &wdt_counter_config,
177 		 POST_KERNEL,
178 		 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
179 		 &wdt_counter_driver_api);
180