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