mlx_wdt.c (9175B)
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 */ 44struct 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 57static 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, ®val); 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 80static 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 89static 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 98static 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_write_bits(wdt->regmap, reg_data->reg, ~reg_data->mask, 104 BIT(reg_data->bit)); 105} 106 107static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd, 108 unsigned int timeout) 109{ 110 struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); 111 struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx]; 112 u32 regval, set_time, hw_timeout; 113 int rc; 114 115 switch (wdt->wdt_type) { 116 case MLX_WDT_TYPE1: 117 rc = regmap_read(wdt->regmap, reg_data->reg, ®val); 118 if (rc) 119 return rc; 120 121 hw_timeout = order_base_2(timeout * MLXREG_WDT_CLOCK_SCALE); 122 regval = (regval & reg_data->mask) | hw_timeout; 123 /* Rowndown to actual closest number of sec. */ 124 set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE; 125 rc = regmap_write(wdt->regmap, reg_data->reg, regval); 126 break; 127 case MLX_WDT_TYPE2: 128 set_time = timeout; 129 rc = regmap_write(wdt->regmap, reg_data->reg, timeout); 130 break; 131 case MLX_WDT_TYPE3: 132 /* WD_TYPE3 has 2B set time register */ 133 set_time = timeout; 134 if (wdt->regmap_val_sz == 1) { 135 regval = timeout & 0xff; 136 rc = regmap_write(wdt->regmap, reg_data->reg, regval); 137 if (!rc) { 138 regval = (timeout & 0xff00) >> 8; 139 rc = regmap_write(wdt->regmap, 140 reg_data->reg + 1, regval); 141 } 142 } else { 143 rc = regmap_write(wdt->regmap, reg_data->reg, timeout); 144 } 145 break; 146 default: 147 return -EINVAL; 148 } 149 150 wdd->timeout = set_time; 151 if (!rc) { 152 /* 153 * Restart watchdog with new timeout period 154 * if watchdog is already started. 155 */ 156 if (watchdog_active(wdd)) { 157 rc = mlxreg_wdt_stop(wdd); 158 if (!rc) 159 rc = mlxreg_wdt_start(wdd); 160 } 161 } 162 163 return rc; 164} 165 166static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd) 167{ 168 struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd); 169 struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx]; 170 u32 regval, msb, lsb; 171 int rc; 172 173 if (wdt->wdt_type == MLX_WDT_TYPE2) { 174 rc = regmap_read(wdt->regmap, reg_data->reg, ®val); 175 } else { 176 /* WD_TYPE3 has 2 byte timeleft register */ 177 if (wdt->regmap_val_sz == 1) { 178 rc = regmap_read(wdt->regmap, reg_data->reg, &lsb); 179 if (!rc) { 180 rc = regmap_read(wdt->regmap, 181 reg_data->reg + 1, &msb); 182 regval = (msb & 0xff) << 8 | (lsb & 0xff); 183 } 184 } else { 185 rc = regmap_read(wdt->regmap, reg_data->reg, ®val); 186 } 187 } 188 189 /* Return 0 timeleft in case of failure register read. */ 190 return rc == 0 ? regval : 0; 191} 192 193static const struct watchdog_ops mlxreg_wdt_ops_type1 = { 194 .start = mlxreg_wdt_start, 195 .stop = mlxreg_wdt_stop, 196 .ping = mlxreg_wdt_ping, 197 .set_timeout = mlxreg_wdt_set_timeout, 198 .owner = THIS_MODULE, 199}; 200 201static const struct watchdog_ops mlxreg_wdt_ops_type2 = { 202 .start = mlxreg_wdt_start, 203 .stop = mlxreg_wdt_stop, 204 .ping = mlxreg_wdt_ping, 205 .set_timeout = mlxreg_wdt_set_timeout, 206 .get_timeleft = mlxreg_wdt_get_timeleft, 207 .owner = THIS_MODULE, 208}; 209 210static const struct watchdog_info mlxreg_wdt_main_info = { 211 .options = MLXREG_WDT_OPTIONS_BASE 212 | WDIOF_CARDRESET, 213 .identity = "mlx-wdt-main", 214}; 215 216static const struct watchdog_info mlxreg_wdt_aux_info = { 217 .options = MLXREG_WDT_OPTIONS_BASE 218 | WDIOF_ALARMONLY, 219 .identity = "mlx-wdt-aux", 220}; 221 222static void mlxreg_wdt_config(struct mlxreg_wdt *wdt, 223 struct mlxreg_core_platform_data *pdata) 224{ 225 struct mlxreg_core_data *data = pdata->data; 226 int i; 227 228 wdt->reset_idx = -EINVAL; 229 for (i = 0; i < pdata->counter; i++, data++) { 230 if (strnstr(data->label, "action", sizeof(data->label))) 231 wdt->action_idx = i; 232 else if (strnstr(data->label, "timeout", sizeof(data->label))) 233 wdt->timeout_idx = i; 234 else if (strnstr(data->label, "timeleft", sizeof(data->label))) 235 wdt->tleft_idx = i; 236 else if (strnstr(data->label, "ping", sizeof(data->label))) 237 wdt->ping_idx = i; 238 else if (strnstr(data->label, "reset", sizeof(data->label))) 239 wdt->reset_idx = i; 240 } 241 242 wdt->pdata = pdata; 243 if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity, 244 sizeof(mlxreg_wdt_main_info.identity))) 245 wdt->wdd.info = &mlxreg_wdt_main_info; 246 else 247 wdt->wdd.info = &mlxreg_wdt_aux_info; 248 249 wdt->wdt_type = pdata->version; 250 switch (wdt->wdt_type) { 251 case MLX_WDT_TYPE1: 252 wdt->wdd.ops = &mlxreg_wdt_ops_type1; 253 wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1; 254 break; 255 case MLX_WDT_TYPE2: 256 wdt->wdd.ops = &mlxreg_wdt_ops_type2; 257 wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2; 258 break; 259 case MLX_WDT_TYPE3: 260 wdt->wdd.ops = &mlxreg_wdt_ops_type2; 261 wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE3; 262 break; 263 default: 264 break; 265 } 266 267 wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT; 268} 269 270static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt, 271 struct mlxreg_core_platform_data *pdata) 272{ 273 u32 timeout; 274 275 timeout = pdata->data[wdt->timeout_idx].health_cntr; 276 return mlxreg_wdt_set_timeout(&wdt->wdd, timeout); 277} 278 279static int mlxreg_wdt_probe(struct platform_device *pdev) 280{ 281 struct device *dev = &pdev->dev; 282 struct mlxreg_core_platform_data *pdata; 283 struct mlxreg_wdt *wdt; 284 int rc; 285 286 pdata = dev_get_platdata(dev); 287 if (!pdata) { 288 dev_err(dev, "Failed to get platform data.\n"); 289 return -EINVAL; 290 } 291 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 292 if (!wdt) 293 return -ENOMEM; 294 295 wdt->wdd.parent = dev; 296 wdt->regmap = pdata->regmap; 297 rc = regmap_get_val_bytes(wdt->regmap); 298 if (rc < 0) 299 return -EINVAL; 300 301 wdt->regmap_val_sz = rc; 302 mlxreg_wdt_config(wdt, pdata); 303 304 if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT)) 305 watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); 306 watchdog_stop_on_reboot(&wdt->wdd); 307 watchdog_stop_on_unregister(&wdt->wdd); 308 watchdog_set_drvdata(&wdt->wdd, wdt); 309 rc = mlxreg_wdt_init_timeout(wdt, pdata); 310 if (rc) 311 goto register_error; 312 313 if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT)) { 314 rc = mlxreg_wdt_start(&wdt->wdd); 315 if (rc) 316 goto register_error; 317 set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); 318 } 319 mlxreg_wdt_check_card_reset(wdt); 320 rc = devm_watchdog_register_device(dev, &wdt->wdd); 321 322register_error: 323 if (rc) 324 dev_err(dev, "Cannot register watchdog device (err=%d)\n", rc); 325 return rc; 326} 327 328static struct platform_driver mlxreg_wdt_driver = { 329 .probe = mlxreg_wdt_probe, 330 .driver = { 331 .name = "mlx-wdt", 332 }, 333}; 334 335module_platform_driver(mlxreg_wdt_driver); 336 337MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>"); 338MODULE_DESCRIPTION("Mellanox watchdog driver"); 339MODULE_LICENSE("GPL"); 340MODULE_ALIAS("platform:mlx-wdt");