Skip to content

Commit e57bab5

Browse files
robhancocksedcarlescufi
authored andcommitted
drivers: watchdog: add Xilinx AXI Timebase WDT driver
Add a driver for the Xilinx AXI Timebase WDT logic core. This can be instantiated on various Xilinx FPGA-based platforms such as the Digilent Arty, although it is not part of the default image used with the Zephyr board configuration. The driver can also optionally implement the HWINFO API to allow determining whether the last system reset was initiated by the WDT. Since this is a standalone IP core which could be used a variety of configurations, this support is optional in case the system/SoC it is used with already implements this support. Signed-off-by: Robert Hancock <[email protected]>
1 parent f271a82 commit e57bab5

File tree

4 files changed

+315
-0
lines changed

4 files changed

+315
-0
lines changed

drivers/watchdog/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_COUNTER wdt_counter.c)
3232
zephyr_library_sources_ifdef(CONFIG_WDT_NXP_S32 wdt_nxp_s32.c)
3333
zephyr_library_sources_ifdef(CONFIG_WDT_SMARTBOND wdt_smartbond.c)
3434
zephyr_library_sources_ifdef(CONFIG_WDT_TI_TPS382X wdt_ti_tps382x.c)
35+
zephyr_library_sources_ifdef(CONFIG_WDT_XILINX_AXI wdt_xilinx_axi.c)
3536

3637
zephyr_library_sources_ifdef(CONFIG_WDT_DW wdt_dw.c wdt_dw_common.c)
3738
zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c)

drivers/watchdog/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,6 @@ source "drivers/watchdog/Kconfig.ti_tps382x"
104104

105105
source "drivers/watchdog/Kconfig.tco"
106106

107+
source "drivers/watchdog/Kconfig.xlnx"
108+
107109
endif # WATCHDOG

drivers/watchdog/Kconfig.xlnx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Xilinx watchdog configuration
2+
3+
# Copyright (c) 2023, Calian
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
config WDT_XILINX_AXI
7+
bool "Xilinx AXI Timebase WDT driver"
8+
default y
9+
depends on DT_HAS_XLNX_XPS_TIMEBASE_WDT_1_00_A_ENABLED
10+
help
11+
Enable the Xilinx AXI Timebase WDT driver.
12+
13+
if WDT_XILINX_AXI
14+
15+
config WDT_XILINX_AXI_HWINFO_API
16+
bool "Expose HWINFO API in Xilinx AXI Timebase WDT driver"
17+
default y
18+
select HWINFO
19+
help
20+
Controls whether the Xilinx AXI Timebase WDT driver exposes a HWINFO
21+
API which allows determining whether the WDT initiated the last
22+
system reset. This may need to be disabled if using a device or SoC
23+
which already implements this API.
24+
25+
endif # WDT_XILINX_AXI

drivers/watchdog/wdt_xilinx_axi.c

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
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+
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+
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+
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+
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+
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+
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+
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+
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 const struct wdt_driver_api 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)

0 commit comments

Comments
 (0)