1 /*
2  * Copyright (2) 2019 Vestas Wind Systems A/S
3  *
4  * Based on wdt_mcux_wdog.c, which is:
5  * Copyright (c) 2018, NXP
6  *
7  * SPDX-License-Identifier: Apache-2.0
8  */
9 
10 #define DT_DRV_COMPAT nxp_kinetis_wdog32
11 
12 #include <zephyr/drivers/watchdog.h>
13 #include <zephyr/drivers/clock_control.h>
14 #include <fsl_wdog32.h>
15 
16 #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
17 #include <zephyr/logging/log.h>
18 #include <zephyr/irq.h>
19 LOG_MODULE_REGISTER(wdt_mcux_wdog32);
20 
21 #define MIN_TIMEOUT 1
22 
23 struct mcux_wdog32_config {
24 	WDOG_Type *base;
25 #if DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency)
26 	uint32_t clock_frequency;
27 #else /* !DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
28 	const struct device *clock_dev;
29 	clock_control_subsys_t clock_subsys;
30 #endif /* !DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
31 	wdog32_clock_source_t clk_source;
32 	wdog32_clock_prescaler_t clk_divider;
33 	void (*irq_config_func)(const struct device *dev);
34 };
35 
36 struct mcux_wdog32_data {
37 	wdt_callback_t callback;
38 	wdog32_config_t wdog_config;
39 	bool timeout_valid;
40 };
41 
mcux_wdog32_setup(const struct device * dev,uint8_t options)42 static int mcux_wdog32_setup(const struct device *dev, uint8_t options)
43 {
44 	const struct mcux_wdog32_config *config = dev->config;
45 	struct mcux_wdog32_data *data = dev->data;
46 	WDOG_Type *base = config->base;
47 
48 	if (!data->timeout_valid) {
49 		LOG_ERR("No valid timeouts installed");
50 		return -EINVAL;
51 	}
52 
53 	data->wdog_config.workMode.enableStop =
54 		(options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
55 
56 	data->wdog_config.workMode.enableDebug =
57 		(options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U;
58 
59 	WDOG32_Init(base, &data->wdog_config);
60 	LOG_DBG("Setup the watchdog");
61 
62 	return 0;
63 }
64 
mcux_wdog32_disable(const struct device * dev)65 static int mcux_wdog32_disable(const struct device *dev)
66 {
67 	const struct mcux_wdog32_config *config = dev->config;
68 	struct mcux_wdog32_data *data = dev->data;
69 	WDOG_Type *base = config->base;
70 
71 	WDOG32_Deinit(base);
72 	data->timeout_valid = false;
73 	LOG_DBG("Disabled the watchdog");
74 
75 	return 0;
76 }
77 
78 #define MSEC_TO_WDOG32_TICKS(clock_freq, divider, msec) \
79 	((uint32_t)(clock_freq * msec / 1000U / divider))
80 
mcux_wdog32_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)81 static int mcux_wdog32_install_timeout(const struct device *dev,
82 				       const struct wdt_timeout_cfg *cfg)
83 {
84 	const struct mcux_wdog32_config *config = dev->config;
85 	struct mcux_wdog32_data *data = dev->data;
86 	uint32_t clock_freq;
87 	int div;
88 
89 	if (data->timeout_valid) {
90 		LOG_ERR("No more timeouts can be installed");
91 		return -ENOMEM;
92 	}
93 
94 #if DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency)
95 	clock_freq = config->clock_frequency;
96 #else /* !DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
97 	if (!device_is_ready(config->clock_dev)) {
98 		LOG_ERR("clock control device not ready");
99 		return -ENODEV;
100 	}
101 
102 	if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
103 				   &clock_freq)) {
104 		return -EINVAL;
105 	}
106 #endif /* !DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
107 
108 	div = config->clk_divider == kWDOG32_ClockPrescalerDivide1 ? 1U : 256U;
109 
110 	WDOG32_GetDefaultConfig(&data->wdog_config);
111 
112 	data->wdog_config.timeoutValue =
113 		MSEC_TO_WDOG32_TICKS(clock_freq, div, cfg->window.max);
114 
115 	if (cfg->window.min) {
116 		data->wdog_config.enableWindowMode = true;
117 		data->wdog_config.windowValue =
118 			MSEC_TO_WDOG32_TICKS(clock_freq, div, cfg->window.min);
119 	} else {
120 		data->wdog_config.enableWindowMode = false;
121 		data->wdog_config.windowValue = 0;
122 	}
123 
124 	if ((data->wdog_config.timeoutValue < MIN_TIMEOUT) ||
125 	    (data->wdog_config.timeoutValue < data->wdog_config.windowValue)) {
126 		LOG_ERR("Invalid timeout");
127 		return -EINVAL;
128 	}
129 
130 	data->wdog_config.prescaler = config->clk_divider;
131 	data->wdog_config.clockSource = config->clk_source;
132 	data->wdog_config.enableInterrupt = cfg->callback != NULL;
133 	data->callback = cfg->callback;
134 	data->timeout_valid = true;
135 	LOG_DBG("Installed timeout (timeoutValue = %d)",
136 		data->wdog_config.timeoutValue);
137 
138 	return 0;
139 }
140 
mcux_wdog32_feed(const struct device * dev,int channel_id)141 static int mcux_wdog32_feed(const struct device *dev, int channel_id)
142 {
143 	const struct mcux_wdog32_config *config = dev->config;
144 	WDOG_Type *base = config->base;
145 
146 	if (channel_id != 0) {
147 		LOG_ERR("Invalid channel id");
148 		return -EINVAL;
149 	}
150 
151 	WDOG32_Refresh(base);
152 	LOG_DBG("Fed the watchdog");
153 
154 	return 0;
155 }
156 
mcux_wdog32_isr(const struct device * dev)157 static void mcux_wdog32_isr(const struct device *dev)
158 {
159 	const struct mcux_wdog32_config *config = dev->config;
160 	struct mcux_wdog32_data *data = dev->data;
161 	WDOG_Type *base = config->base;
162 	uint32_t flags;
163 
164 	flags = WDOG32_GetStatusFlags(base);
165 	WDOG32_ClearStatusFlags(base, flags);
166 
167 	if (data->callback) {
168 		data->callback(dev, 0);
169 	}
170 }
171 
mcux_wdog32_init(const struct device * dev)172 static int mcux_wdog32_init(const struct device *dev)
173 {
174 	const struct mcux_wdog32_config *config = dev->config;
175 
176 	config->irq_config_func(dev);
177 
178 	return 0;
179 }
180 
181 static const struct wdt_driver_api mcux_wdog32_api = {
182 	.setup = mcux_wdog32_setup,
183 	.disable = mcux_wdog32_disable,
184 	.install_timeout = mcux_wdog32_install_timeout,
185 	.feed = mcux_wdog32_feed,
186 };
187 
188 #define TO_WDOG32_CLK_SRC(val) _DO_CONCAT(kWDOG32_ClockSource, val)
189 #define TO_WDOG32_CLK_DIV(val) _DO_CONCAT(kWDOG32_ClockPrescalerDivide, val)
190 
191 static void mcux_wdog32_config_func_0(const struct device *dev);
192 
193 static const struct mcux_wdog32_config mcux_wdog32_config_0 = {
194 	.base = (WDOG_Type *) DT_INST_REG_ADDR(0),
195 #if DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency)
196 	.clock_frequency = DT_INST_PROP_BY_PHANDLE(0, clocks, clock_frequency),
197 #else /* !DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
198 	.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(0)),
199 	.clock_subsys = (clock_control_subsys_t)
200 		DT_INST_CLOCKS_CELL(0, name),
201 #endif /* DT_NODE_HAS_PROP(DT_INST_PHANDLE(0, clocks), clock_frequency) */
202 	.clk_source =
203 		TO_WDOG32_CLK_SRC(DT_INST_PROP(0, clk_source)),
204 	.clk_divider =
205 		TO_WDOG32_CLK_DIV(DT_INST_PROP(0, clk_divider)),
206 	.irq_config_func = mcux_wdog32_config_func_0,
207 };
208 
209 static struct mcux_wdog32_data mcux_wdog32_data_0;
210 
211 DEVICE_DT_INST_DEFINE(0, &mcux_wdog32_init,
212 		    NULL, &mcux_wdog32_data_0,
213 		    &mcux_wdog32_config_0, POST_KERNEL,
214 		    CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
215 		    &mcux_wdog32_api);
216 
mcux_wdog32_config_func_0(const struct device * dev)217 static void mcux_wdog32_config_func_0(const struct device *dev)
218 {
219 	IRQ_CONNECT(DT_INST_IRQN(0),
220 		    DT_INST_IRQ(0, priority),
221 		    mcux_wdog32_isr, DEVICE_DT_INST_GET(0), 0);
222 
223 	irq_enable(DT_INST_IRQN(0));
224 }
225