Commit | Line | Data |
---|---|---|
c60923dd MS |
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_MIN_TIMEOUT 1 | |
25 | #define MLXREG_WDT_OPTIONS_BASE (WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | \ | |
26 | WDIOF_SETTIMEOUT) | |
27 | ||
28 | /** | |
29 | * struct mlxreg_wdt - wd private data: | |
30 | * | |
31 | * @wdd: watchdog device; | |
32 | * @device: basic device; | |
33 | * @pdata: data received from platform driver; | |
34 | * @regmap: register map of parent device; | |
35 | * @timeout: defined timeout in sec.; | |
36 | * @action_idx: index for direct access to action register; | |
37 | * @timeout_idx:index for direct access to TO register; | |
38 | * @tleft_idx: index for direct access to time left register; | |
39 | * @ping_idx: index for direct access to ping register; | |
40 | * @reset_idx: index for direct access to reset cause register; | |
41 | * @wd_type: watchdog HW type; | |
42 | */ | |
43 | struct mlxreg_wdt { | |
44 | struct watchdog_device wdd; | |
45 | struct mlxreg_core_platform_data *pdata; | |
46 | void *regmap; | |
47 | int action_idx; | |
48 | int timeout_idx; | |
49 | int tleft_idx; | |
50 | int ping_idx; | |
51 | int reset_idx; | |
52 | enum mlxreg_wdt_type wdt_type; | |
53 | }; | |
54 | ||
55 | static void mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt) | |
56 | { | |
57 | struct mlxreg_core_data *reg_data; | |
58 | u32 regval; | |
59 | int rc; | |
60 | ||
61 | if (wdt->reset_idx == -EINVAL) | |
62 | return; | |
63 | ||
64 | if (!(wdt->wdd.info->options & WDIOF_CARDRESET)) | |
65 | return; | |
66 | ||
67 | reg_data = &wdt->pdata->data[wdt->reset_idx]; | |
68 | rc = regmap_read(wdt->regmap, reg_data->reg, ®val); | |
69 | if (!rc) { | |
70 | if (regval & ~reg_data->mask) { | |
71 | wdt->wdd.bootstatus = WDIOF_CARDRESET; | |
72 | dev_info(wdt->wdd.parent, | |
73 | "watchdog previously reset the CPU\n"); | |
74 | } | |
75 | } | |
76 | } | |
77 | ||
78 | static int mlxreg_wdt_start(struct watchdog_device *wdd) | |
79 | { | |
80 | struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); | |
81 | struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx]; | |
82 | ||
83 | return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask, | |
84 | BIT(reg_data->bit)); | |
85 | } | |
86 | ||
87 | static int mlxreg_wdt_stop(struct watchdog_device *wdd) | |
88 | { | |
89 | struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); | |
90 | struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx]; | |
91 | ||
92 | return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask, | |
93 | ~BIT(reg_data->bit)); | |
94 | } | |
95 | ||
96 | static int mlxreg_wdt_ping(struct watchdog_device *wdd) | |
97 | { | |
98 | struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); | |
99 | struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx]; | |
100 | ||
101 | return regmap_update_bits_base(wdt->regmap, reg_data->reg, | |
102 | ~reg_data->mask, BIT(reg_data->bit), | |
103 | NULL, false, true); | |
104 | } | |
105 | ||
106 | static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd, | |
107 | unsigned int timeout) | |
108 | { | |
109 | struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); | |
110 | struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx]; | |
111 | u32 regval, set_time, hw_timeout; | |
112 | int rc; | |
113 | ||
114 | if (wdt->wdt_type == MLX_WDT_TYPE1) { | |
115 | rc = regmap_read(wdt->regmap, reg_data->reg, ®val); | |
116 | if (rc) | |
117 | return rc; | |
118 | ||
119 | hw_timeout = order_base_2(timeout * MLXREG_WDT_CLOCK_SCALE); | |
120 | regval = (regval & reg_data->mask) | hw_timeout; | |
121 | /* Rowndown to actual closest number of sec. */ | |
122 | set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE; | |
123 | } else { | |
124 | set_time = timeout; | |
125 | regval = timeout; | |
126 | } | |
127 | ||
128 | wdd->timeout = set_time; | |
129 | rc = regmap_write(wdt->regmap, reg_data->reg, regval); | |
130 | ||
131 | if (!rc) { | |
132 | /* | |
133 | * Restart watchdog with new timeout period | |
134 | * if watchdog is already started. | |
135 | */ | |
136 | if (watchdog_active(wdd)) { | |
137 | rc = mlxreg_wdt_stop(wdd); | |
138 | if (!rc) | |
139 | rc = mlxreg_wdt_start(wdd); | |
140 | } | |
141 | } | |
142 | ||
143 | return rc; | |
144 | } | |
145 | ||
146 | static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd) | |
147 | { | |
148 | struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); | |
149 | struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx]; | |
150 | u32 regval; | |
151 | int rc; | |
152 | ||
153 | rc = regmap_read(wdt->regmap, reg_data->reg, ®val); | |
154 | /* Return 0 timeleft in case of failure register read. */ | |
155 | return rc == 0 ? regval : 0; | |
156 | } | |
157 | ||
158 | static const struct watchdog_ops mlxreg_wdt_ops_type1 = { | |
159 | .start = mlxreg_wdt_start, | |
160 | .stop = mlxreg_wdt_stop, | |
161 | .ping = mlxreg_wdt_ping, | |
162 | .set_timeout = mlxreg_wdt_set_timeout, | |
163 | .owner = THIS_MODULE, | |
164 | }; | |
165 | ||
166 | static const struct watchdog_ops mlxreg_wdt_ops_type2 = { | |
167 | .start = mlxreg_wdt_start, | |
168 | .stop = mlxreg_wdt_stop, | |
169 | .ping = mlxreg_wdt_ping, | |
170 | .set_timeout = mlxreg_wdt_set_timeout, | |
171 | .get_timeleft = mlxreg_wdt_get_timeleft, | |
172 | .owner = THIS_MODULE, | |
173 | }; | |
174 | ||
175 | static const struct watchdog_info mlxreg_wdt_main_info = { | |
176 | .options = MLXREG_WDT_OPTIONS_BASE | |
177 | | WDIOF_CARDRESET, | |
178 | .identity = "mlx-wdt-main", | |
179 | }; | |
180 | ||
181 | static const struct watchdog_info mlxreg_wdt_aux_info = { | |
182 | .options = MLXREG_WDT_OPTIONS_BASE | |
183 | | WDIOF_ALARMONLY, | |
184 | .identity = "mlx-wdt-aux", | |
185 | }; | |
186 | ||
187 | static void mlxreg_wdt_config(struct mlxreg_wdt *wdt, | |
188 | struct mlxreg_core_platform_data *pdata) | |
189 | { | |
190 | struct mlxreg_core_data *data = pdata->data; | |
191 | int i; | |
192 | ||
193 | wdt->reset_idx = -EINVAL; | |
194 | for (i = 0; i < pdata->counter; i++, data++) { | |
195 | if (strnstr(data->label, "action", sizeof(data->label))) | |
196 | wdt->action_idx = i; | |
197 | else if (strnstr(data->label, "timeout", sizeof(data->label))) | |
198 | wdt->timeout_idx = i; | |
199 | else if (strnstr(data->label, "timeleft", sizeof(data->label))) | |
200 | wdt->tleft_idx = i; | |
201 | else if (strnstr(data->label, "ping", sizeof(data->label))) | |
202 | wdt->ping_idx = i; | |
203 | else if (strnstr(data->label, "reset", sizeof(data->label))) | |
204 | wdt->reset_idx = i; | |
205 | } | |
206 | ||
207 | wdt->pdata = pdata; | |
208 | if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity, | |
209 | sizeof(mlxreg_wdt_main_info.identity))) | |
210 | wdt->wdd.info = &mlxreg_wdt_main_info; | |
211 | else | |
212 | wdt->wdd.info = &mlxreg_wdt_aux_info; | |
213 | ||
214 | wdt->wdt_type = pdata->version; | |
215 | if (wdt->wdt_type == MLX_WDT_TYPE2) { | |
216 | wdt->wdd.ops = &mlxreg_wdt_ops_type2; | |
217 | wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2; | |
218 | } else { | |
219 | wdt->wdd.ops = &mlxreg_wdt_ops_type1; | |
220 | wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1; | |
221 | } | |
222 | wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT; | |
223 | } | |
224 | ||
225 | static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt, | |
226 | struct mlxreg_core_platform_data *pdata) | |
227 | { | |
228 | u32 timeout; | |
229 | ||
230 | timeout = pdata->data[wdt->timeout_idx].health_cntr; | |
231 | return mlxreg_wdt_set_timeout(&wdt->wdd, timeout); | |
232 | } | |
233 | ||
234 | static int mlxreg_wdt_probe(struct platform_device *pdev) | |
235 | { | |
099e3039 | 236 | struct device *dev = &pdev->dev; |
c60923dd MS |
237 | struct mlxreg_core_platform_data *pdata; |
238 | struct mlxreg_wdt *wdt; | |
239 | int rc; | |
240 | ||
099e3039 | 241 | pdata = dev_get_platdata(dev); |
c60923dd | 242 | if (!pdata) { |
099e3039 | 243 | dev_err(dev, "Failed to get platform data.\n"); |
c60923dd MS |
244 | return -EINVAL; |
245 | } | |
099e3039 | 246 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); |
c60923dd MS |
247 | if (!wdt) |
248 | return -ENOMEM; | |
249 | ||
099e3039 | 250 | wdt->wdd.parent = dev; |
c60923dd MS |
251 | wdt->regmap = pdata->regmap; |
252 | mlxreg_wdt_config(wdt, pdata); | |
253 | ||
254 | if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT)) | |
255 | watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); | |
256 | watchdog_stop_on_reboot(&wdt->wdd); | |
257 | watchdog_stop_on_unregister(&wdt->wdd); | |
258 | watchdog_set_drvdata(&wdt->wdd, wdt); | |
259 | rc = mlxreg_wdt_init_timeout(wdt, pdata); | |
260 | if (rc) | |
261 | goto register_error; | |
262 | ||
263 | if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT)) { | |
264 | rc = mlxreg_wdt_start(&wdt->wdd); | |
265 | if (rc) | |
266 | goto register_error; | |
267 | set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); | |
268 | } | |
269 | mlxreg_wdt_check_card_reset(wdt); | |
099e3039 | 270 | rc = devm_watchdog_register_device(dev, &wdt->wdd); |
c60923dd MS |
271 | |
272 | register_error: | |
273 | if (rc) | |
099e3039 | 274 | dev_err(dev, "Cannot register watchdog device (err=%d)\n", rc); |
c60923dd MS |
275 | return rc; |
276 | } | |
277 | ||
278 | static struct platform_driver mlxreg_wdt_driver = { | |
279 | .probe = mlxreg_wdt_probe, | |
280 | .driver = { | |
281 | .name = "mlx-wdt", | |
282 | }, | |
283 | }; | |
284 | ||
285 | module_platform_driver(mlxreg_wdt_driver); | |
286 | ||
287 | MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>"); | |
288 | MODULE_DESCRIPTION("Mellanox watchdog driver"); | |
289 | MODULE_LICENSE("GPL"); | |
290 | MODULE_ALIAS("platform:mlx-wdt"); |