1 /* SPDX-License-Identifier: Apache-2.0 */
2 /*
3 * Copyright (c) 2023 Intel Corporation
4 *
5 * Author: Adrian Warecki <adrian.warecki@intel.com>
6 */
7
8 #define DT_DRV_COMPAT snps_designware_watchdog
9
10 #include <zephyr/drivers/watchdog.h>
11 #include <zephyr/logging/log.h>
12 #include <zephyr/sys_clock.h>
13 #include <zephyr/math/ilog2.h>
14 #include <zephyr/drivers/reset.h>
15 #include <zephyr/drivers/clock_control.h>
16 #include <zephyr/irq.h>
17
18 #include "wdt_dw.h"
19 #include "wdt_dw_common.h"
20
21 LOG_MODULE_REGISTER(wdt_dw, CONFIG_WDT_LOG_LEVEL);
22
23 /* Device run time data */
24 struct dw_wdt_dev_data {
25 /* MMIO mapping information for watchdog register base address */
26 DEVICE_MMIO_RAM;
27 /* Clock frequency */
28 uint32_t clk_freq;
29 uint32_t config;
30 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
31 wdt_callback_t callback;
32 #endif
33 };
34
35 /* Device constant configuration parameters */
36 struct dw_wdt_dev_cfg {
37 DEVICE_MMIO_ROM;
38
39 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks)
40 /* Clock controller dev instance */
41 const struct device *clk_dev;
42 /* Identifier for timer to get clock freq from clk manager */
43 clock_control_subsys_t clkid;
44 #endif
45 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
46 void (*irq_config)(void);
47 #endif
48 uint8_t reset_pulse_length;
49 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
50 /* Reset controller device configurations */
51 struct reset_dt_spec reset_spec;
52 #endif
53 };
54
dw_wdt_setup(const struct device * dev,uint8_t options)55 static int dw_wdt_setup(const struct device *dev, uint8_t options)
56 {
57 struct dw_wdt_dev_data *const dev_data = dev->data;
58 uintptr_t reg_base = DEVICE_MMIO_GET(dev);
59 int ret;
60
61 ret = dw_wdt_check_options(options);
62 if (ret != 0) {
63 return ret;
64 }
65
66 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
67 /* Configure response mode */
68 dw_wdt_response_mode_set((uint32_t)reg_base, !!dev_data->callback);
69 #endif
70
71 return dw_wdt_configure((uint32_t)reg_base, dev_data->config);
72 }
73
dw_wdt_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)74 static int dw_wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *config)
75 {
76 __maybe_unused const struct dw_wdt_dev_cfg *const dev_config = dev->config;
77 struct dw_wdt_dev_data *const dev_data = dev->data;
78 uintptr_t reg_base = DEVICE_MMIO_GET(dev);
79
80 if (config == NULL) {
81 LOG_ERR("watchdog timeout configuration missing");
82 return -ENODATA;
83 }
84
85 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
86 if (config->callback && !dev_config->irq_config) {
87 #else
88 if (config->callback) {
89 #endif
90 LOG_ERR("Interrupt is not configured, can't set a callback.");
91 return -ENOTSUP;
92 }
93
94 if (config->flags) {
95 LOG_WRN("Watchdog behavior is not configurable.");
96 }
97
98 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
99 dev_data->callback = config->callback;
100 #endif
101
102 return dw_wdt_calc_period((uint32_t)reg_base, dev_data->clk_freq, config,
103 &dev_data->config);
104 }
105
106 static int dw_wdt_feed(const struct device *dev, int channel_id)
107 {
108 uintptr_t reg_base = DEVICE_MMIO_GET(dev);
109
110 /* Only channel 0 is supported */
111 if (channel_id) {
112 return -EINVAL;
113 }
114
115 dw_wdt_counter_restart((uint32_t)reg_base);
116
117 return 0;
118 }
119
120 int dw_wdt_disable(const struct device *dev)
121 {
122 int ret = -ENOTSUP;
123 /*
124 * Once watchdog is enabled by setting WDT_EN bit watchdog cannot be disabled by clearing
125 * WDT_EN bit and to disable/clear WDT_EN bit watchdog IP should undergo reset
126 */
127 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
128 const struct dw_wdt_dev_cfg *const dev_config = dev->config;
129
130 /*
131 * Assert and de-assert reset only if the reset prop is defined in the device
132 * tree node for this dev instance
133 */
134 if (dev_config->reset_spec.dev != NULL) {
135 if (!device_is_ready(dev_config->reset_spec.dev)) {
136 LOG_ERR("reset controller device not ready");
137 return -ENODEV;
138 }
139
140 /* Assert and de-assert reset watchdog */
141 ret = reset_line_toggle(dev_config->reset_spec.dev, dev_config->reset_spec.id);
142 if (ret != 0) {
143 LOG_ERR("watchdog disable/reset failed");
144 return ret;
145 }
146 }
147 #endif
148 return ret;
149 }
150
151 static DEVICE_API(wdt, dw_wdt_api) = {
152 .setup = dw_wdt_setup,
153 .disable = dw_wdt_disable,
154 .install_timeout = dw_wdt_install_timeout,
155 .feed = dw_wdt_feed,
156 };
157
158 static int dw_wdt_init(const struct device *dev)
159 {
160 DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
161 const struct dw_wdt_dev_cfg *const dev_config = dev->config;
162 int ret;
163 uintptr_t reg_base = DEVICE_MMIO_GET(dev);
164
165 /* Reset watchdog controller if reset controller is supported */
166 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
167 ret = dw_wdt_disable(dev);
168 if (ret != 0) {
169 return ret;
170 }
171 #endif
172
173 /* Get clock frequency from the clock manager if supported */
174 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks)
175 struct dw_wdt_dev_data *const dev_data = dev->data;
176
177 if (!device_is_ready(dev_config->clk_dev)) {
178 LOG_ERR("Clock controller device not ready");
179 return -ENODEV;
180 }
181
182 ret = clock_control_get_rate(dev_config->clk_dev, dev_config->clkid,
183 &dev_data->clk_freq);
184 if (ret != 0) {
185 LOG_ERR("Failed to get watchdog clock rate");
186 return ret;
187 }
188 #endif
189 ret = dw_wdt_probe((uint32_t)reg_base, dev_config->reset_pulse_length);
190 if (ret) {
191 return ret;
192 }
193
194 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
195 if (dev_config->irq_config) {
196 dev_config->irq_config();
197 }
198 #endif
199
200 /*
201 * Enable watchdog if it needs to be enabled at boot.
202 * watchdog timer will be started with maximum timeout
203 * that is the default value.
204 */
205 if (!IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
206 dw_wdt_enable((uint32_t)reg_base);
207 }
208
209 return 0;
210 }
211
212 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
213 static void dw_wdt_isr(const struct device *dev)
214 {
215 struct dw_wdt_dev_data *const dev_data = dev->data;
216 uintptr_t reg_base = DEVICE_MMIO_GET(dev);
217
218 if (dw_wdt_interrupt_status_register_get((uint32_t)reg_base)) {
219
220 /*
221 * Clearing interrupt here will not assert system reset, so interrupt
222 * will not be cleared here.
223 */
224 if (dev_data->callback) {
225 dev_data->callback(dev, 0);
226 }
227 }
228 }
229 #endif
230
231 #define CHECK_CLOCK(inst) \
232 !(DT_INST_NODE_HAS_PROP(inst, clock_frequency) || DT_INST_NODE_HAS_PROP(inst, clocks)) ||
233
234 #if DT_INST_FOREACH_STATUS_OKAY(CHECK_CLOCK) 0
235 #error Clock frequency not configured!
236 #endif
237
238 /* Bindings to the platform */
239 #define DW_WDT_IRQ_FLAGS(inst) \
240 COND_CODE_1(DT_INST_IRQ_HAS_CELL(inst, sense), (DT_INST_IRQ(inst, sense)), (0))
241
242 #define DW_WDT_RESET_SPEC_INIT(inst) \
243 .reset_spec = RESET_DT_SPEC_INST_GET(inst),
244
245 #define IRQ_CONFIG(inst) \
246 static void dw_wdt##inst##_irq_config(void) \
247 { \
248 IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), dw_wdt_isr, \
249 DEVICE_DT_INST_GET(inst), DW_WDT_IRQ_FLAGS(inst)); \
250 irq_enable(DT_INST_IRQN(inst)); \
251 }
252
253 #define DW_WDT_INIT(inst) \
254 IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(inst), interrupts), (IRQ_CONFIG(inst))) \
255 \
256 static const struct dw_wdt_dev_cfg wdt_dw##inst##_config = { \
257 DEVICE_MMIO_ROM_INIT(DT_DRV_INST(inst)), \
258 .reset_pulse_length = ilog2(DT_INST_PROP_OR(inst, reset_pulse_length, 2)) - 1, \
259 IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, resets), \
260 (DW_WDT_RESET_SPEC_INIT(inst))) \
261 IF_ENABLED(DT_PHA_HAS_CELL(DT_DRV_INST(inst), clocks, clkid), \
262 ( \
263 .clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
264 .clkid = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, clkid), \
265 )) \
266 IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(inst), interrupts), \
267 (.irq_config = dw_wdt##inst##_irq_config,) \
268 ) \
269 }; \
270 \
271 static struct dw_wdt_dev_data wdt_dw##inst##_data = { \
272 COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, clock_frequency), \
273 (.clk_freq = DT_INST_PROP(inst, clock_frequency)), \
274 (COND_CODE_1(DT_PHA_HAS_CELL(DT_DRV_INST(inst), clocks, clkid), \
275 (.clk_freq = 0), \
276 (.clk_freq = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency))))), \
277 }; \
278 \
279 DEVICE_DT_INST_DEFINE(inst, &dw_wdt_init, NULL, &wdt_dw##inst##_data, \
280 &wdt_dw##inst##_config, POST_KERNEL, \
281 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &dw_wdt_api);
282
283 DT_INST_FOREACH_STATUS_OKAY(DW_WDT_INIT)
284