546 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Kontron PLD watchdog driver4 *5 * Copyright (c) 2010-2013 Kontron Europe GmbH6 * Author: Michael Brunner <michael.brunner@kontron.com>7 *8 * Note: From the PLD watchdog point of view timeout and pretimeout are9 * defined differently than in the kernel.10 * First the pretimeout stage runs out before the timeout stage gets11 * active.12 *13 * Kernel/API: P-----| pretimeout14 * |-----------------------T timeout15 * Watchdog: |-----------------P pretimeout_stage16 * |-----T timeout_stage17 */18 19#include <linux/module.h>20#include <linux/moduleparam.h>21#include <linux/uaccess.h>22#include <linux/watchdog.h>23#include <linux/platform_device.h>24#include <linux/mfd/kempld.h>25 26#define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)27#define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))28#define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)29#define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)30#define STAGE_CFG_PRESCALER_MASK 0x3031#define STAGE_CFG_ACTION_MASK 0x732#define STAGE_CFG_ASSERT (1 << 3)33 34#define KEMPLD_WDT_MAX_STAGES 235#define KEMPLD_WDT_KICK 0x1636#define KEMPLD_WDT_CFG 0x1737#define KEMPLD_WDT_CFG_ENABLE 0x1038#define KEMPLD_WDT_CFG_ENABLE_LOCK 0x839#define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x8040 41enum {42 ACTION_NONE = 0,43 ACTION_RESET,44 ACTION_NMI,45 ACTION_SMI,46 ACTION_SCI,47 ACTION_DELAY,48};49 50enum {51 STAGE_TIMEOUT = 0,52 STAGE_PRETIMEOUT,53};54 55enum {56 PRESCALER_21 = 0,57 PRESCALER_17,58 PRESCALER_12,59};60 61static const u32 kempld_prescaler[] = {62 [PRESCALER_21] = (1 << 21) - 1,63 [PRESCALER_17] = (1 << 17) - 1,64 [PRESCALER_12] = (1 << 12) - 1,65 0,66};67 68struct kempld_wdt_stage {69 unsigned int id;70 u32 mask;71};72 73struct kempld_wdt_data {74 struct kempld_device_data *pld;75 struct watchdog_device wdd;76 unsigned int pretimeout;77 struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES];78 u8 pm_status_store;79};80 81#define DEFAULT_TIMEOUT 30 /* seconds */82#define DEFAULT_PRETIMEOUT 083 84static unsigned int timeout = DEFAULT_TIMEOUT;85module_param(timeout, uint, 0);86MODULE_PARM_DESC(timeout,87 "Watchdog timeout in seconds. (>=0, default="88 __MODULE_STRING(DEFAULT_TIMEOUT) ")");89 90static unsigned int pretimeout = DEFAULT_PRETIMEOUT;91module_param(pretimeout, uint, 0);92MODULE_PARM_DESC(pretimeout,93 "Watchdog pretimeout in seconds. (>=0, default="94 __MODULE_STRING(DEFAULT_PRETIMEOUT) ")");95 96static bool nowayout = WATCHDOG_NOWAYOUT;97module_param(nowayout, bool, 0);98MODULE_PARM_DESC(nowayout,99 "Watchdog cannot be stopped once started (default="100 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");101 102static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,103 struct kempld_wdt_stage *stage,104 u8 action)105{106 struct kempld_device_data *pld = wdt_data->pld;107 u8 stage_cfg;108 109 if (!stage || !stage->mask)110 return -EINVAL;111 112 kempld_get_mutex(pld);113 stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));114 stage_cfg &= ~STAGE_CFG_ACTION_MASK;115 stage_cfg |= (action & STAGE_CFG_ACTION_MASK);116 117 if (action == ACTION_RESET)118 stage_cfg |= STAGE_CFG_ASSERT;119 else120 stage_cfg &= ~STAGE_CFG_ASSERT;121 122 kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);123 kempld_release_mutex(pld);124 125 return 0;126}127 128static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,129 struct kempld_wdt_stage *stage,130 unsigned int timeout)131{132 struct kempld_device_data *pld = wdt_data->pld;133 u32 prescaler;134 u64 stage_timeout64;135 u32 stage_timeout;136 u32 remainder;137 u8 stage_cfg;138 139 prescaler = kempld_prescaler[PRESCALER_21];140 141 if (!stage)142 return -EINVAL;143 144 stage_timeout64 = (u64)timeout * pld->pld_clock;145 remainder = do_div(stage_timeout64, prescaler);146 if (remainder)147 stage_timeout64++;148 149 if (stage_timeout64 > stage->mask)150 return -EINVAL;151 152 stage_timeout = stage_timeout64 & stage->mask;153 154 kempld_get_mutex(pld);155 stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));156 stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;157 stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21);158 kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);159 kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),160 stage_timeout);161 kempld_release_mutex(pld);162 163 return 0;164}165 166/*167 * kempld_get_mutex must be called prior to calling this function.168 */169static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,170 struct kempld_wdt_stage *stage)171{172 struct kempld_device_data *pld = wdt_data->pld;173 unsigned int timeout;174 u64 stage_timeout;175 u32 prescaler;176 u32 remainder;177 u8 stage_cfg;178 179 if (!stage->mask)180 return 0;181 182 stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));183 stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));184 prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];185 186 stage_timeout = (stage_timeout & stage->mask) * prescaler;187 remainder = do_div(stage_timeout, pld->pld_clock);188 if (remainder)189 stage_timeout++;190 191 timeout = stage_timeout;192 WARN_ON_ONCE(timeout != stage_timeout);193 194 return timeout;195}196 197static int kempld_wdt_set_timeout(struct watchdog_device *wdd,198 unsigned int timeout)199{200 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);201 struct kempld_wdt_stage *pretimeout_stage;202 struct kempld_wdt_stage *timeout_stage;203 int ret;204 205 timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];206 pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];207 208 if (pretimeout_stage->mask && wdt_data->pretimeout > 0)209 timeout = wdt_data->pretimeout;210 211 ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,212 ACTION_RESET);213 if (ret)214 return ret;215 ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,216 timeout);217 if (ret)218 return ret;219 220 wdd->timeout = timeout;221 return 0;222}223 224static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,225 unsigned int pretimeout)226{227 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);228 struct kempld_wdt_stage *pretimeout_stage;229 u8 action = ACTION_NONE;230 int ret;231 232 pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];233 234 if (!pretimeout_stage->mask)235 return -ENXIO;236 237 if (pretimeout > wdd->timeout)238 return -EINVAL;239 240 if (pretimeout > 0)241 action = ACTION_NMI;242 243 ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,244 action);245 if (ret)246 return ret;247 ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,248 wdd->timeout - pretimeout);249 if (ret)250 return ret;251 252 wdt_data->pretimeout = pretimeout;253 return 0;254}255 256static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)257{258 struct kempld_device_data *pld = wdt_data->pld;259 struct kempld_wdt_stage *pretimeout_stage;260 struct kempld_wdt_stage *timeout_stage;261 unsigned int pretimeout, timeout;262 263 pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];264 timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];265 266 kempld_get_mutex(pld);267 pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);268 timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);269 kempld_release_mutex(pld);270 271 if (pretimeout)272 wdt_data->pretimeout = timeout;273 else274 wdt_data->pretimeout = 0;275 276 wdt_data->wdd.timeout = pretimeout + timeout;277}278 279static int kempld_wdt_start(struct watchdog_device *wdd)280{281 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);282 struct kempld_device_data *pld = wdt_data->pld;283 u8 status;284 int ret;285 286 ret = kempld_wdt_set_timeout(wdd, wdd->timeout);287 if (ret)288 return ret;289 290 kempld_get_mutex(pld);291 status = kempld_read8(pld, KEMPLD_WDT_CFG);292 status |= KEMPLD_WDT_CFG_ENABLE;293 kempld_write8(pld, KEMPLD_WDT_CFG, status);294 status = kempld_read8(pld, KEMPLD_WDT_CFG);295 kempld_release_mutex(pld);296 297 /* Check if the watchdog was enabled */298 if (!(status & KEMPLD_WDT_CFG_ENABLE))299 return -EACCES;300 301 return 0;302}303 304static int kempld_wdt_stop(struct watchdog_device *wdd)305{306 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);307 struct kempld_device_data *pld = wdt_data->pld;308 u8 status;309 310 kempld_get_mutex(pld);311 status = kempld_read8(pld, KEMPLD_WDT_CFG);312 status &= ~KEMPLD_WDT_CFG_ENABLE;313 kempld_write8(pld, KEMPLD_WDT_CFG, status);314 status = kempld_read8(pld, KEMPLD_WDT_CFG);315 kempld_release_mutex(pld);316 317 /* Check if the watchdog was disabled */318 if (status & KEMPLD_WDT_CFG_ENABLE)319 return -EACCES;320 321 return 0;322}323 324static int kempld_wdt_keepalive(struct watchdog_device *wdd)325{326 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);327 struct kempld_device_data *pld = wdt_data->pld;328 329 kempld_get_mutex(pld);330 kempld_write8(pld, KEMPLD_WDT_KICK, 'K');331 kempld_release_mutex(pld);332 333 return 0;334}335 336static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,337 unsigned long arg)338{339 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);340 void __user *argp = (void __user *)arg;341 int ret = -ENOIOCTLCMD;342 int __user *p = argp;343 int new_value;344 345 switch (cmd) {346 case WDIOC_SETPRETIMEOUT:347 if (get_user(new_value, p))348 return -EFAULT;349 ret = kempld_wdt_set_pretimeout(wdd, new_value);350 if (ret)351 return ret;352 ret = kempld_wdt_keepalive(wdd);353 break;354 case WDIOC_GETPRETIMEOUT:355 ret = put_user(wdt_data->pretimeout, (int __user *)arg);356 break;357 }358 359 return ret;360}361 362static int kempld_wdt_probe_stages(struct watchdog_device *wdd)363{364 struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);365 struct kempld_device_data *pld = wdt_data->pld;366 struct kempld_wdt_stage *pretimeout_stage;367 struct kempld_wdt_stage *timeout_stage;368 u8 index, data, data_orig;369 u32 mask;370 int i, j;371 372 pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];373 timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];374 375 pretimeout_stage->mask = 0;376 timeout_stage->mask = 0;377 378 for (i = 0; i < 3; i++) {379 index = KEMPLD_WDT_STAGE_TIMEOUT(i);380 mask = 0;381 382 kempld_get_mutex(pld);383 /* Probe each byte individually. */384 for (j = 0; j < 4; j++) {385 data_orig = kempld_read8(pld, index + j);386 kempld_write8(pld, index + j, 0x00);387 data = kempld_read8(pld, index + j);388 /* A failed write means this byte is reserved */389 if (data != 0x00)390 break;391 kempld_write8(pld, index + j, data_orig);392 mask |= 0xff << (j * 8);393 }394 kempld_release_mutex(pld);395 396 /* Assign available stages to timeout and pretimeout */397 if (!timeout_stage->mask) {398 timeout_stage->mask = mask;399 timeout_stage->id = i;400 } else {401 if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {402 pretimeout_stage->mask = timeout_stage->mask;403 timeout_stage->mask = mask;404 pretimeout_stage->id = timeout_stage->id;405 timeout_stage->id = i;406 }407 break;408 }409 }410 411 if (!timeout_stage->mask)412 return -ENODEV;413 414 return 0;415}416 417static const struct watchdog_info kempld_wdt_info = {418 .identity = "KEMPLD Watchdog",419 .options = WDIOF_SETTIMEOUT |420 WDIOF_KEEPALIVEPING |421 WDIOF_MAGICCLOSE |422 WDIOF_PRETIMEOUT423};424 425static const struct watchdog_ops kempld_wdt_ops = {426 .owner = THIS_MODULE,427 .start = kempld_wdt_start,428 .stop = kempld_wdt_stop,429 .ping = kempld_wdt_keepalive,430 .set_timeout = kempld_wdt_set_timeout,431 .ioctl = kempld_wdt_ioctl,432};433 434static int kempld_wdt_probe(struct platform_device *pdev)435{436 struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);437 struct kempld_wdt_data *wdt_data;438 struct device *dev = &pdev->dev;439 struct watchdog_device *wdd;440 u8 status;441 int ret = 0;442 443 wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);444 if (!wdt_data)445 return -ENOMEM;446 447 wdt_data->pld = pld;448 wdd = &wdt_data->wdd;449 wdd->parent = dev;450 451 kempld_get_mutex(pld);452 status = kempld_read8(pld, KEMPLD_WDT_CFG);453 kempld_release_mutex(pld);454 455 /* Enable nowayout if watchdog is already locked */456 if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |457 KEMPLD_WDT_CFG_GLOBAL_LOCK)) {458 if (!nowayout)459 dev_warn(dev,460 "Forcing nowayout - watchdog lock enabled!\n");461 nowayout = true;462 }463 464 wdd->info = &kempld_wdt_info;465 wdd->ops = &kempld_wdt_ops;466 467 watchdog_set_drvdata(wdd, wdt_data);468 watchdog_set_nowayout(wdd, nowayout);469 470 ret = kempld_wdt_probe_stages(wdd);471 if (ret)472 return ret;473 474 kempld_wdt_set_timeout(wdd, timeout);475 kempld_wdt_set_pretimeout(wdd, pretimeout);476 477 /* Check if watchdog is already enabled */478 if (status & KEMPLD_WDT_CFG_ENABLE) {479 /* Get current watchdog settings */480 kempld_wdt_update_timeouts(wdt_data);481 dev_info(dev, "Watchdog was already enabled\n");482 }483 484 platform_set_drvdata(pdev, wdt_data);485 watchdog_stop_on_reboot(wdd);486 watchdog_stop_on_unregister(wdd);487 ret = devm_watchdog_register_device(dev, wdd);488 if (ret)489 return ret;490 491 dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);492 493 return 0;494}495 496/* Disable watchdog if it is active during suspend */497static int kempld_wdt_suspend(struct platform_device *pdev,498 pm_message_t message)499{500 struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);501 struct kempld_device_data *pld = wdt_data->pld;502 struct watchdog_device *wdd = &wdt_data->wdd;503 504 kempld_get_mutex(pld);505 wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);506 kempld_release_mutex(pld);507 508 kempld_wdt_update_timeouts(wdt_data);509 510 if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)511 return kempld_wdt_stop(wdd);512 513 return 0;514}515 516/* Enable watchdog and configure it if necessary */517static int kempld_wdt_resume(struct platform_device *pdev)518{519 struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);520 struct watchdog_device *wdd = &wdt_data->wdd;521 522 /*523 * If watchdog was stopped before suspend be sure it gets disabled524 * again, for the case BIOS has enabled it during resume525 */526 if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)527 return kempld_wdt_start(wdd);528 else529 return kempld_wdt_stop(wdd);530}531 532static struct platform_driver kempld_wdt_driver = {533 .driver = {534 .name = "kempld-wdt",535 },536 .probe = kempld_wdt_probe,537 .suspend = pm_ptr(kempld_wdt_suspend),538 .resume = pm_ptr(kempld_wdt_resume),539};540 541module_platform_driver(kempld_wdt_driver);542 543MODULE_DESCRIPTION("KEM PLD Watchdog Driver");544MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");545MODULE_LICENSE("GPL");546