1 /*
2  * Driver for Xilinx AXI Timebase WDT core, as described in
3  * Xilinx document PG128.
4  *
5  * Note that the window mode of operation of the core is
6  * currently not supported. Also, only a full SoC reset is
7  * supported as a watchdog expiry action.
8  *
9  * Copyright © 2023 Calian Ltd.  All rights reserved.
10  * SPDX-License-Identifier: Apache-2.0
11  */
12 
13 #define DT_DRV_COMPAT xlnx_xps_timebase_wdt_1_00_a
14 
15 #include <errno.h>
16 #include <zephyr/kernel.h>
17 #include <zephyr/device.h>
18 #include <zephyr/logging/log.h>
19 #include <zephyr/drivers/watchdog.h>
20 #include <zephyr/drivers/hwinfo.h>
21 
22 enum xilinx_wdt_axi_register {
23 	REG_TWCSR0 = 0x00, /* Control/Status Register 0 */
24 	REG_TWCSR1 = 0x04, /* Control/Status Register 1 */
25 	REG_TBR = 0x08,	   /* Timebase Register */
26 	REG_MWR = 0x0C,	   /* Master Write Control Register */
27 };
28 
29 enum xilinx_wdt_csr0_bits {
30 	CSR0_WRS = BIT(3),
31 	CSR0_WDS = BIT(2),
32 	CSR0_EWDT1 = BIT(1),
33 	CSR0_EWDT2 = BIT(0),
34 };
35 
36 enum xilinx_wdt_csr1_bits {
37 	CSR1_EWDT2 = BIT(0),
38 };
39 
40 enum {
41 	TIMER_WIDTH_MIN = 8,
42 };
43 
44 LOG_MODULE_REGISTER(wdt_xilinx_axi, CONFIG_WDT_LOG_LEVEL);
45 
46 struct xilinx_wdt_axi_config {
47 	mem_addr_t base;
48 	uint32_t clock_rate;
49 	uint8_t timer_width_max;
50 	bool enable_once;
51 };
52 
53 struct xilinx_wdt_axi_data {
54 	struct k_spinlock lock;
55 	bool timeout_active;
56 	bool wdt_started;
57 };
58 
59 static const struct device *first_wdt_dev;
60 
wdt_xilinx_axi_setup(const struct device * dev,uint8_t options)61 static int wdt_xilinx_axi_setup(const struct device *dev, uint8_t options)
62 {
63 	const struct xilinx_wdt_axi_config *config = dev->config;
64 	struct xilinx_wdt_axi_data *data = dev->data;
65 	k_spinlock_key_t key = k_spin_lock(&data->lock);
66 	int ret;
67 
68 	if (!data->timeout_active) {
69 		ret = -EINVAL;
70 		goto out;
71 	}
72 
73 	if (data->wdt_started) {
74 		ret = -EBUSY;
75 		goto out;
76 	}
77 
78 	/* We don't actually know or control at the driver level whether
79 	 * the WDT pauses in CPU sleep or when halted by the debugger,
80 	 * so we don't check anything with the options.
81 	 */
82 	sys_write32(CSR0_EWDT1 | CSR0_WDS, config->base + REG_TWCSR0);
83 	sys_write32(CSR1_EWDT2, config->base + REG_TWCSR1);
84 	data->wdt_started = true;
85 	ret = 0;
86 
87 out:
88 	k_spin_unlock(&data->lock, key);
89 	return ret;
90 }
91 
wdt_xilinx_axi_disable(const struct device * dev)92 static int wdt_xilinx_axi_disable(const struct device *dev)
93 {
94 	const struct xilinx_wdt_axi_config *config = dev->config;
95 	struct xilinx_wdt_axi_data *data = dev->data;
96 	k_spinlock_key_t key = k_spin_lock(&data->lock);
97 	int ret;
98 
99 	if (config->enable_once) {
100 		ret = -EPERM;
101 		goto out;
102 	}
103 
104 	if (!data->wdt_started) {
105 		ret = -EFAULT;
106 		goto out;
107 	}
108 
109 	sys_write32(CSR0_WDS, config->base + REG_TWCSR0);
110 	sys_write32(0, config->base + REG_TWCSR1);
111 	data->wdt_started = false;
112 	ret = 0;
113 
114 out:
115 	k_spin_unlock(&data->lock, key);
116 	return ret;
117 }
118 
wdt_xilinx_axi_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)119 static int wdt_xilinx_axi_install_timeout(const struct device *dev,
120 					  const struct wdt_timeout_cfg *cfg)
121 {
122 	const struct xilinx_wdt_axi_config *config = dev->config;
123 	struct xilinx_wdt_axi_data *data = dev->data;
124 	k_spinlock_key_t key = k_spin_lock(&data->lock);
125 	uint32_t timer_width;
126 	bool good_timer_width = false;
127 	int ret;
128 
129 	if (data->timeout_active) {
130 		ret = -ENOMEM;
131 		goto out;
132 	}
133 
134 	if (!(cfg->flags & WDT_FLAG_RESET_SOC)) {
135 		ret = -ENOTSUP;
136 		goto out;
137 	}
138 
139 	if (cfg->window.min != 0) {
140 		ret = -EINVAL;
141 		goto out;
142 	}
143 
144 	for (timer_width = TIMER_WIDTH_MIN; timer_width <= config->timer_width_max; timer_width++) {
145 		/* Note: WDT expiry happens after 2 wraps of the timer (first raises an interrupt
146 		 * which is not used, second triggers a reset) so add 1 to timer_width.
147 		 */
148 		const uint64_t expiry_cycles = ((uint64_t)1) << (timer_width + 1);
149 		const uint64_t expiry_msec = expiry_cycles * 1000 / config->clock_rate;
150 
151 		if (expiry_msec >= cfg->window.max) {
152 			LOG_INF("Set timer width to %u, actual expiry %u msec", timer_width,
153 				(unsigned int)expiry_msec);
154 			good_timer_width = true;
155 			break;
156 		}
157 	}
158 
159 	if (!good_timer_width) {
160 		LOG_ERR("Cannot support timeout value of %u msec", cfg->window.max);
161 		ret = -EINVAL;
162 		goto out;
163 	}
164 
165 	sys_write32(timer_width, config->base + REG_MWR);
166 	data->timeout_active = true;
167 	ret = 0;
168 
169 out:
170 	k_spin_unlock(&data->lock, key);
171 	return ret;
172 }
173 
wdt_xilinx_axi_feed(const struct device * dev,int channel_id)174 static int wdt_xilinx_axi_feed(const struct device *dev, int channel_id)
175 {
176 	const struct xilinx_wdt_axi_config *config = dev->config;
177 	struct xilinx_wdt_axi_data *data = dev->data;
178 	k_spinlock_key_t key = k_spin_lock(&data->lock);
179 	uint32_t twcsr0 = sys_read32(config->base + REG_TWCSR0);
180 	int ret;
181 
182 	if (channel_id != 0 || !data->timeout_active) {
183 		ret = -EINVAL;
184 		goto out;
185 	}
186 
187 	twcsr0 |= CSR0_WDS;
188 	if (data->wdt_started) {
189 		twcsr0 |= CSR0_EWDT1;
190 	}
191 
192 	sys_write32(twcsr0, config->base + REG_TWCSR0);
193 	ret = 0;
194 
195 out:
196 	k_spin_unlock(&data->lock, key);
197 	return ret;
198 }
199 
wdt_xilinx_axi_init(const struct device * dev)200 static int wdt_xilinx_axi_init(const struct device *dev)
201 {
202 	if (IS_ENABLED(CONFIG_WDT_XILINX_AXI_HWINFO_API)) {
203 		if (first_wdt_dev) {
204 			LOG_WRN("Multiple WDT instances, only first will implement HWINFO");
205 		} else {
206 			first_wdt_dev = dev;
207 		}
208 	}
209 
210 	return 0;
211 }
212 
213 #ifdef CONFIG_WDT_XILINX_AXI_HWINFO_API
214 
z_impl_hwinfo_get_reset_cause(uint32_t * cause)215 int z_impl_hwinfo_get_reset_cause(uint32_t *cause)
216 {
217 	if (!first_wdt_dev) {
218 		return -ENOSYS;
219 	}
220 
221 	const struct xilinx_wdt_axi_config *config = first_wdt_dev->config;
222 
223 	if ((sys_read32(config->base + REG_TWCSR0) & CSR0_WRS) != 0) {
224 		*cause = RESET_WATCHDOG;
225 	} else {
226 		*cause = 0;
227 	}
228 
229 	return 0;
230 }
231 
z_impl_hwinfo_clear_reset_cause(void)232 int z_impl_hwinfo_clear_reset_cause(void)
233 {
234 	if (!first_wdt_dev) {
235 		return -ENOSYS;
236 	}
237 
238 	const struct xilinx_wdt_axi_config *config = first_wdt_dev->config;
239 	struct xilinx_wdt_axi_data *data = first_wdt_dev->data;
240 
241 	k_spinlock_key_t key = k_spin_lock(&data->lock);
242 	uint32_t twcsr0 = sys_read32(config->base + REG_TWCSR0);
243 
244 	if ((twcsr0 & CSR0_WRS) != 0) {
245 		twcsr0 |= CSR0_WRS;
246 		sys_write32(twcsr0, config->base + REG_TWCSR0);
247 	}
248 
249 	k_spin_unlock(&data->lock, key);
250 
251 	return 0;
252 }
253 
z_impl_hwinfo_get_supported_reset_cause(uint32_t * supported)254 int z_impl_hwinfo_get_supported_reset_cause(uint32_t *supported)
255 {
256 	if (!first_wdt_dev) {
257 		return -ENOSYS;
258 	}
259 
260 	*supported = RESET_WATCHDOG;
261 	return 0;
262 }
263 
264 #endif
265 
266 static DEVICE_API(wdt, wdt_xilinx_api) = {
267 	.setup = wdt_xilinx_axi_setup,
268 	.disable = wdt_xilinx_axi_disable,
269 	.install_timeout = wdt_xilinx_axi_install_timeout,
270 	.feed = wdt_xilinx_axi_feed,
271 };
272 
273 #define WDT_XILINX_AXI_INIT(inst)                                                                  \
274 	static struct xilinx_wdt_axi_data wdt_xilinx_axi_##inst##_dev_data;                        \
275                                                                                                    \
276 	static const struct xilinx_wdt_axi_config wdt_xilinx_axi_##inst##_cfg = {                  \
277 		.base = DT_INST_REG_ADDR(inst),                                                    \
278 		.clock_rate = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency),              \
279 		.timer_width_max = DT_INST_PROP(inst, xlnx_wdt_interval),                          \
280 		.enable_once = DT_INST_PROP(inst, xlnx_wdt_enable_once),                           \
281 	};                                                                                         \
282                                                                                                    \
283 	DEVICE_DT_INST_DEFINE(inst, &wdt_xilinx_axi_init, NULL, &wdt_xilinx_axi_##inst##_dev_data, \
284 			      &wdt_xilinx_axi_##inst##_cfg, PRE_KERNEL_1,                          \
285 			      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xilinx_api);
286 
287 DT_INST_FOREACH_STATUS_OKAY(WDT_XILINX_AXI_INIT)
288