xref: /openbmc/linux/drivers/watchdog/mlx_wdt.c (revision bef7a78d)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Mellanox watchdog driver
4  *
5  * Copyright (C) 2019 Mellanox Technologies
6  * Copyright (C) 2019 Michael Shych <mshych@mellanox.com>
7  */
8 
9 #include <linux/bitops.h>
10 #include <linux/device.h>
11 #include <linux/errno.h>
12 #include <linux/log2.h>
13 #include <linux/module.h>
14 #include <linux/platform_data/mlxreg.h>
15 #include <linux/platform_device.h>
16 #include <linux/regmap.h>
17 #include <linux/spinlock.h>
18 #include <linux/types.h>
19 #include <linux/watchdog.h>
20 
21 #define MLXREG_WDT_CLOCK_SCALE		1000
22 #define MLXREG_WDT_MAX_TIMEOUT_TYPE1	32
23 #define MLXREG_WDT_MAX_TIMEOUT_TYPE2	255
24 #define MLXREG_WDT_MAX_TIMEOUT_TYPE3	65535
25 #define MLXREG_WDT_MIN_TIMEOUT		1
26 #define MLXREG_WDT_OPTIONS_BASE (WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | \
27 				 WDIOF_SETTIMEOUT)
28 
29 /**
30  * struct mlxreg_wdt - wd private data:
31  *
32  * @wdd:	watchdog device;
33  * @device:	basic device;
34  * @pdata:	data received from platform driver;
35  * @regmap:	register map of parent device;
36  * @timeout:	defined timeout in sec.;
37  * @action_idx:	index for direct access to action register;
38  * @timeout_idx:index for direct access to TO register;
39  * @tleft_idx:	index for direct access to time left register;
40  * @ping_idx:	index for direct access to ping register;
41  * @reset_idx:	index for direct access to reset cause register;
42  * @wd_type:	watchdog HW type;
43  */
44 struct mlxreg_wdt {
45 	struct watchdog_device wdd;
46 	struct mlxreg_core_platform_data *pdata;
47 	void *regmap;
48 	int action_idx;
49 	int timeout_idx;
50 	int tleft_idx;
51 	int ping_idx;
52 	int reset_idx;
53 	int regmap_val_sz;
54 	enum mlxreg_wdt_type wdt_type;
55 };
56 
57 static void mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt)
58 {
59 	struct mlxreg_core_data *reg_data;
60 	u32 regval;
61 	int rc;
62 
63 	if (wdt->reset_idx == -EINVAL)
64 		return;
65 
66 	if (!(wdt->wdd.info->options & WDIOF_CARDRESET))
67 		return;
68 
69 	reg_data = &wdt->pdata->data[wdt->reset_idx];
70 	rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
71 	if (!rc) {
72 		if (regval & ~reg_data->mask) {
73 			wdt->wdd.bootstatus = WDIOF_CARDRESET;
74 			dev_info(wdt->wdd.parent,
75 				 "watchdog previously reset the CPU\n");
76 		}
77 	}
78 }
79 
80 static int mlxreg_wdt_start(struct watchdog_device *wdd)
81 {
82 	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
83 	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
84 
85 	return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask,
86 				  BIT(reg_data->bit));
87 }
88 
89 static int mlxreg_wdt_stop(struct watchdog_device *wdd)
90 {
91 	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
92 	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
93 
94 	return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask,
95 				  ~BIT(reg_data->bit));
96 }
97 
98 static int mlxreg_wdt_ping(struct watchdog_device *wdd)
99 {
100 	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
101 	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx];
102 
103 	return regmap_update_bits_base(wdt->regmap, reg_data->reg,
104 				       ~reg_data->mask, BIT(reg_data->bit),
105 				       NULL, false, true);
106 }
107 
108 static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd,
109 				  unsigned int timeout)
110 {
111 	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
112 	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
113 	u32 regval, set_time, hw_timeout;
114 	int rc;
115 
116 	switch (wdt->wdt_type) {
117 	case MLX_WDT_TYPE1:
118 		rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
119 		if (rc)
120 			return rc;
121 
122 		hw_timeout = order_base_2(timeout * MLXREG_WDT_CLOCK_SCALE);
123 		regval = (regval & reg_data->mask) | hw_timeout;
124 		/* Rowndown to actual closest number of sec. */
125 		set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE;
126 		rc = regmap_write(wdt->regmap, reg_data->reg, regval);
127 		break;
128 	case MLX_WDT_TYPE2:
129 		set_time = timeout;
130 		rc = regmap_write(wdt->regmap, reg_data->reg, timeout);
131 		break;
132 	case MLX_WDT_TYPE3:
133 		/* WD_TYPE3 has 2B set time register */
134 		set_time = timeout;
135 		if (wdt->regmap_val_sz == 1) {
136 			regval = timeout & 0xff;
137 			rc = regmap_write(wdt->regmap, reg_data->reg, regval);
138 			if (!rc) {
139 				regval = (timeout & 0xff00) >> 8;
140 				rc = regmap_write(wdt->regmap,
141 						reg_data->reg + 1, regval);
142 			}
143 		} else {
144 			rc = regmap_write(wdt->regmap, reg_data->reg, timeout);
145 		}
146 		break;
147 	default:
148 		return -EINVAL;
149 	}
150 
151 	wdd->timeout = set_time;
152 	if (!rc) {
153 		/*
154 		 * Restart watchdog with new timeout period
155 		 * if watchdog is already started.
156 		 */
157 		if (watchdog_active(wdd)) {
158 			rc = mlxreg_wdt_stop(wdd);
159 			if (!rc)
160 				rc = mlxreg_wdt_start(wdd);
161 		}
162 	}
163 
164 	return rc;
165 }
166 
167 static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd)
168 {
169 	struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
170 	struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx];
171 	u32 regval, msb, lsb;
172 	int rc;
173 
174 	if (wdt->wdt_type == MLX_WDT_TYPE2) {
175 		rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
176 	} else {
177 		/* WD_TYPE3 has 2 byte timeleft register */
178 		if (wdt->regmap_val_sz == 1) {
179 			rc = regmap_read(wdt->regmap, reg_data->reg, &lsb);
180 			if (!rc) {
181 				rc = regmap_read(wdt->regmap,
182 						reg_data->reg + 1, &msb);
183 				regval = (msb & 0xff) << 8 | (lsb & 0xff);
184 			}
185 		} else {
186 			rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
187 		}
188 	}
189 
190 	/* Return 0 timeleft in case of failure register read. */
191 	return rc == 0 ? regval : 0;
192 }
193 
194 static const struct watchdog_ops mlxreg_wdt_ops_type1 = {
195 	.start		= mlxreg_wdt_start,
196 	.stop		= mlxreg_wdt_stop,
197 	.ping		= mlxreg_wdt_ping,
198 	.set_timeout	= mlxreg_wdt_set_timeout,
199 	.owner		= THIS_MODULE,
200 };
201 
202 static const struct watchdog_ops mlxreg_wdt_ops_type2 = {
203 	.start		= mlxreg_wdt_start,
204 	.stop		= mlxreg_wdt_stop,
205 	.ping		= mlxreg_wdt_ping,
206 	.set_timeout	= mlxreg_wdt_set_timeout,
207 	.get_timeleft	= mlxreg_wdt_get_timeleft,
208 	.owner		= THIS_MODULE,
209 };
210 
211 static const struct watchdog_info mlxreg_wdt_main_info = {
212 	.options	= MLXREG_WDT_OPTIONS_BASE
213 			| WDIOF_CARDRESET,
214 	.identity	= "mlx-wdt-main",
215 };
216 
217 static const struct watchdog_info mlxreg_wdt_aux_info = {
218 	.options	= MLXREG_WDT_OPTIONS_BASE
219 			| WDIOF_ALARMONLY,
220 	.identity	= "mlx-wdt-aux",
221 };
222 
223 static void mlxreg_wdt_config(struct mlxreg_wdt *wdt,
224 			      struct mlxreg_core_platform_data *pdata)
225 {
226 	struct mlxreg_core_data *data = pdata->data;
227 	int i;
228 
229 	wdt->reset_idx = -EINVAL;
230 	for (i = 0; i < pdata->counter; i++, data++) {
231 		if (strnstr(data->label, "action", sizeof(data->label)))
232 			wdt->action_idx = i;
233 		else if (strnstr(data->label, "timeout", sizeof(data->label)))
234 			wdt->timeout_idx = i;
235 		else if (strnstr(data->label, "timeleft", sizeof(data->label)))
236 			wdt->tleft_idx = i;
237 		else if (strnstr(data->label, "ping", sizeof(data->label)))
238 			wdt->ping_idx = i;
239 		else if (strnstr(data->label, "reset", sizeof(data->label)))
240 			wdt->reset_idx = i;
241 	}
242 
243 	wdt->pdata = pdata;
244 	if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity,
245 		    sizeof(mlxreg_wdt_main_info.identity)))
246 		wdt->wdd.info = &mlxreg_wdt_main_info;
247 	else
248 		wdt->wdd.info = &mlxreg_wdt_aux_info;
249 
250 	wdt->wdt_type = pdata->version;
251 	switch (wdt->wdt_type) {
252 	case MLX_WDT_TYPE1:
253 		wdt->wdd.ops = &mlxreg_wdt_ops_type1;
254 		wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1;
255 		break;
256 	case MLX_WDT_TYPE2:
257 		wdt->wdd.ops = &mlxreg_wdt_ops_type2;
258 		wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2;
259 		break;
260 	case MLX_WDT_TYPE3:
261 		wdt->wdd.ops = &mlxreg_wdt_ops_type2;
262 		wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE3;
263 		break;
264 	default:
265 		break;
266 	}
267 
268 	wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT;
269 }
270 
271 static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt,
272 				   struct mlxreg_core_platform_data *pdata)
273 {
274 	u32 timeout;
275 
276 	timeout = pdata->data[wdt->timeout_idx].health_cntr;
277 	return mlxreg_wdt_set_timeout(&wdt->wdd, timeout);
278 }
279 
280 static int mlxreg_wdt_probe(struct platform_device *pdev)
281 {
282 	struct device *dev = &pdev->dev;
283 	struct mlxreg_core_platform_data *pdata;
284 	struct mlxreg_wdt *wdt;
285 	int rc;
286 
287 	pdata = dev_get_platdata(dev);
288 	if (!pdata) {
289 		dev_err(dev, "Failed to get platform data.\n");
290 		return -EINVAL;
291 	}
292 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
293 	if (!wdt)
294 		return -ENOMEM;
295 
296 	wdt->wdd.parent = dev;
297 	wdt->regmap = pdata->regmap;
298 	rc = regmap_get_val_bytes(wdt->regmap);
299 	if (rc < 0)
300 		return -EINVAL;
301 
302 	wdt->regmap_val_sz = rc;
303 	mlxreg_wdt_config(wdt, pdata);
304 
305 	if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT))
306 		watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
307 	watchdog_stop_on_reboot(&wdt->wdd);
308 	watchdog_stop_on_unregister(&wdt->wdd);
309 	watchdog_set_drvdata(&wdt->wdd, wdt);
310 	rc = mlxreg_wdt_init_timeout(wdt, pdata);
311 	if (rc)
312 		goto register_error;
313 
314 	if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT)) {
315 		rc = mlxreg_wdt_start(&wdt->wdd);
316 		if (rc)
317 			goto register_error;
318 		set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
319 	}
320 	mlxreg_wdt_check_card_reset(wdt);
321 	rc = devm_watchdog_register_device(dev, &wdt->wdd);
322 
323 register_error:
324 	if (rc)
325 		dev_err(dev, "Cannot register watchdog device (err=%d)\n", rc);
326 	return rc;
327 }
328 
329 static struct platform_driver mlxreg_wdt_driver = {
330 	.probe	= mlxreg_wdt_probe,
331 	.driver	= {
332 			.name = "mlx-wdt",
333 	},
334 };
335 
336 module_platform_driver(mlxreg_wdt_driver);
337 
338 MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>");
339 MODULE_DESCRIPTION("Mellanox watchdog driver");
340 MODULE_LICENSE("GPL");
341 MODULE_ALIAS("platform:mlx-wdt");
342