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