1 /*
2  * Copyright (C) 2023 SLB
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT infineon_xmc4xxx_watchdog
8 
9 #include <errno.h>
10 #include <stdint.h>
11 
12 #include <soc.h>
13 #include <zephyr/drivers/watchdog.h>
14 #include <zephyr/irq.h>
15 
16 #include <xmc_scu.h>
17 #include <xmc_wdt.h>
18 
19 struct wdt_xmc4xxx_dev_data {
20 	wdt_callback_t cb;
21 	uint8_t mode;
22 	bool timeout_valid;
23 	bool is_serviced;
24 };
25 
26 /* When the watchdog counter rolls over, the SCU will generate a */
27 /* pre-warning event which gets routed to the ISR below. If the */
28 /* watchdog is not serviced, the SCU will only reset the MCU */
29 /* after the second time the counter rolls over. */
30 /* Hence the reset will only happen after 2*cfg->window.max have elapsed. */
31 /* We could potentially manually reset the MCU, but this way the context */
32 /* information (i.e. that reset happened because of a watchdog) is lost. */
33 
wdt_xmc4xxx_isr(const struct device * dev)34 static void wdt_xmc4xxx_isr(const struct device *dev)
35 {
36 	struct wdt_xmc4xxx_dev_data *data = dev->data;
37 	uint32_t event = XMC_SCU_INTERUPT_GetEventStatus();
38 
39 	/* todo add interrupt controller? */
40 	if ((event & XMC_SCU_INTERRUPT_EVENT_WDT_WARN) == 0) {
41 		return;
42 	}
43 
44 	/* this is a level triggered interrupt. the event must be cleared */
45 	XMC_SCU_INTERRUPT_ClearEventStatus(XMC_SCU_INTERRUPT_EVENT_WDT_WARN);
46 
47 	data->is_serviced = false;
48 
49 	if (data->cb) {
50 		data->cb(dev, 0);
51 	}
52 
53 	/* Ensure that watchdog is serviced if RESET_NONE mode is used */
54 	if (data->mode == WDT_FLAG_RESET_NONE && !data->is_serviced) {
55 		XMC_WDT_Service();
56 	}
57 
58 	XMC_WDT_ClearAlarm();
59 }
60 
wdt_xmc4xxx_disable(const struct device * dev)61 static int wdt_xmc4xxx_disable(const struct device *dev)
62 {
63 	struct wdt_xmc4xxx_dev_data *data = dev->data;
64 
65 	XMC_WDT_Stop();
66 	XMC_WDT_Disable();
67 
68 	data->timeout_valid = false;
69 
70 	return 0;
71 }
72 
wdt_xmc4xxx_setup(const struct device * dev,uint8_t options)73 static int wdt_xmc4xxx_setup(const struct device *dev, uint8_t options)
74 {
75 	struct wdt_xmc4xxx_dev_data *data = dev->data;
76 
77 	if (!data->timeout_valid) {
78 		return -EINVAL;
79 	}
80 
81 	if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) {
82 		SCU_CLK->SLEEPCR &= ~XMC_SCU_CLOCK_SLEEP_MODE_CONFIG_ENABLE_WDT;
83 	} else {
84 		SCU_CLK->SLEEPCR |= XMC_SCU_CLOCK_SLEEP_MODE_CONFIG_ENABLE_WDT;
85 	}
86 
87 	if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0) {
88 		XMC_WDT_SetDebugMode(XMC_WDT_DEBUG_MODE_STOP);
89 	} else {
90 		XMC_WDT_SetDebugMode(XMC_WDT_DEBUG_MODE_RUN);
91 	}
92 
93 	XMC_WDT_Start();
94 
95 	return 0;
96 }
97 
wdt_xmc4xxx_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)98 static int wdt_xmc4xxx_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
99 {
100 	XMC_WDT_CONFIG_t wdt_config = {0};
101 	struct wdt_xmc4xxx_dev_data *data = dev->data;
102 	uint32_t wdt_clock;
103 
104 	/* disable the watchdog if timeout was already installed */
105 	if (data->timeout_valid) {
106 		wdt_xmc4xxx_disable(dev);
107 		data->timeout_valid = false;
108 	}
109 
110 	if (cfg->window.min != 0 || cfg->window.max == 0) {
111 		return -EINVAL;
112 	}
113 
114 	wdt_clock = XMC_SCU_CLOCK_GetWdtClockFrequency();
115 
116 	if ((uint64_t)cfg->window.max * wdt_clock / 1000 > UINT32_MAX) {
117 		return -EINVAL;
118 	}
119 
120 	wdt_config.window_upper_bound = (uint64_t)cfg->window.max * wdt_clock / 1000;
121 
122 	XMC_WDT_Init(&wdt_config);
123 	XMC_WDT_SetDebugMode(XMC_WDT_MODE_PREWARNING);
124 	XMC_SCU_INTERRUPT_EnableEvent(XMC_SCU_INTERRUPT_EVENT_WDT_WARN);
125 
126 	if (cfg->flags == WDT_FLAG_RESET_NONE && cfg->callback == NULL) {
127 		return -EINVAL;
128 	}
129 
130 	data->cb = cfg->callback;
131 	data->mode = cfg->flags;
132 	data->timeout_valid = true;
133 
134 	return 0;
135 }
136 
wdt_xmc4xxx_feed(const struct device * dev,int channel_id)137 static int wdt_xmc4xxx_feed(const struct device *dev, int channel_id)
138 {
139 	ARG_UNUSED(channel_id);
140 	struct wdt_xmc4xxx_dev_data *data = dev->data;
141 
142 	XMC_WDT_Service();
143 	data->is_serviced = true;
144 
145 	return 0;
146 }
147 
148 static DEVICE_API(wdt, wdt_xmc4xxx_api) = {
149 	.setup = wdt_xmc4xxx_setup,
150 	.disable = wdt_xmc4xxx_disable,
151 	.install_timeout = wdt_xmc4xxx_install_timeout,
152 	.feed = wdt_xmc4xxx_feed,
153 };
154 
155 static struct wdt_xmc4xxx_dev_data wdt_xmc4xxx_data;
156 
wdt_xmc4xxx_irq_config(void)157 static void wdt_xmc4xxx_irq_config(void)
158 {
159 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), wdt_xmc4xxx_isr,
160 		    DEVICE_DT_INST_GET(0), 0);
161 	irq_enable(DT_INST_IRQN(0));
162 }
163 
wdt_xmc4xxx_init(const struct device * dev)164 static int wdt_xmc4xxx_init(const struct device *dev)
165 {
166 	wdt_xmc4xxx_irq_config();
167 
168 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
169 	return 0;
170 #else
171 	int ret;
172 	const struct wdt_timeout_cfg cfg = {.window.max = CONFIG_WDT_XMC4XXX_DEFAULT_TIMEOUT_MAX_MS,
173 					    .flags = WDT_FLAG_RESET_SOC};
174 
175 	ret = wdt_xmc4xxx_install_timeout(dev, &cfg);
176 	if (ret < 0) {
177 		return ret;
178 	}
179 
180 	return wdt_xmc4xxx_setup(dev, WDT_OPT_PAUSE_HALTED_BY_DBG);
181 #endif
182 }
183 DEVICE_DT_INST_DEFINE(0, wdt_xmc4xxx_init, NULL, &wdt_xmc4xxx_data, NULL, PRE_KERNEL_1,
184 		      CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xmc4xxx_api);
185