linux/drivers/watchdog/bcm47xx_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 *  Watchdog driver for Broadcom BCM47XX
   4 *
   5 *  Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>
   6 *  Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>
   7 *  Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de>
   8 *
   9 */
  10
  11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  12
  13#include <linux/bcm47xx_wdt.h>
  14#include <linux/bitops.h>
  15#include <linux/errno.h>
  16#include <linux/kernel.h>
  17#include <linux/module.h>
  18#include <linux/moduleparam.h>
  19#include <linux/platform_device.h>
  20#include <linux/types.h>
  21#include <linux/watchdog.h>
  22#include <linux/timer.h>
  23#include <linux/jiffies.h>
  24
  25#define DRV_NAME                "bcm47xx_wdt"
  26
  27#define WDT_DEFAULT_TIME        30      /* seconds */
  28#define WDT_SOFTTIMER_MAX       255     /* seconds */
  29#define WDT_SOFTTIMER_THRESHOLD 60      /* seconds */
  30
  31static int timeout = WDT_DEFAULT_TIME;
  32static bool nowayout = WATCHDOG_NOWAYOUT;
  33
  34module_param(timeout, int, 0);
  35MODULE_PARM_DESC(timeout, "Watchdog time in seconds. (default="
  36                                __MODULE_STRING(WDT_DEFAULT_TIME) ")");
  37
  38module_param(nowayout, bool, 0);
  39MODULE_PARM_DESC(nowayout,
  40                "Watchdog cannot be stopped once started (default="
  41                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  42
  43static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)
  44{
  45        return container_of(wdd, struct bcm47xx_wdt, wdd);
  46}
  47
  48static int bcm47xx_wdt_hard_keepalive(struct watchdog_device *wdd)
  49{
  50        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
  51
  52        wdt->timer_set_ms(wdt, wdd->timeout * 1000);
  53
  54        return 0;
  55}
  56
  57static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd)
  58{
  59        return 0;
  60}
  61
  62static int bcm47xx_wdt_hard_stop(struct watchdog_device *wdd)
  63{
  64        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
  65
  66        wdt->timer_set(wdt, 0);
  67
  68        return 0;
  69}
  70
  71static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd,
  72                                        unsigned int new_time)
  73{
  74        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
  75        u32 max_timer = wdt->max_timer_ms;
  76
  77        if (new_time < 1 || new_time > max_timer / 1000) {
  78                pr_warn("timeout value must be 1<=x<=%d, using %d\n",
  79                        max_timer / 1000, new_time);
  80                return -EINVAL;
  81        }
  82
  83        wdd->timeout = new_time;
  84        return 0;
  85}
  86
  87static int bcm47xx_wdt_restart(struct watchdog_device *wdd,
  88                               unsigned long action, void *data)
  89{
  90        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
  91
  92        wdt->timer_set(wdt, 1);
  93
  94        return 0;
  95}
  96
  97static const struct watchdog_ops bcm47xx_wdt_hard_ops = {
  98        .owner          = THIS_MODULE,
  99        .start          = bcm47xx_wdt_hard_start,
 100        .stop           = bcm47xx_wdt_hard_stop,
 101        .ping           = bcm47xx_wdt_hard_keepalive,
 102        .set_timeout    = bcm47xx_wdt_hard_set_timeout,
 103        .restart        = bcm47xx_wdt_restart,
 104};
 105
 106static void bcm47xx_wdt_soft_timer_tick(struct timer_list *t)
 107{
 108        struct bcm47xx_wdt *wdt = from_timer(wdt, t, soft_timer);
 109        u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms);
 110
 111        if (!atomic_dec_and_test(&wdt->soft_ticks)) {
 112                wdt->timer_set_ms(wdt, next_tick);
 113                mod_timer(&wdt->soft_timer, jiffies + HZ);
 114        } else {
 115                pr_crit("Watchdog will fire soon!!!\n");
 116        }
 117}
 118
 119static int bcm47xx_wdt_soft_keepalive(struct watchdog_device *wdd)
 120{
 121        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
 122
 123        atomic_set(&wdt->soft_ticks, wdd->timeout);
 124
 125        return 0;
 126}
 127
 128static int bcm47xx_wdt_soft_start(struct watchdog_device *wdd)
 129{
 130        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
 131
 132        bcm47xx_wdt_soft_keepalive(wdd);
 133        bcm47xx_wdt_soft_timer_tick(&wdt->soft_timer);
 134
 135        return 0;
 136}
 137
 138static int bcm47xx_wdt_soft_stop(struct watchdog_device *wdd)
 139{
 140        struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
 141
 142        del_timer_sync(&wdt->soft_timer);
 143        wdt->timer_set(wdt, 0);
 144
 145        return 0;
 146}
 147
 148static int bcm47xx_wdt_soft_set_timeout(struct watchdog_device *wdd,
 149                                        unsigned int new_time)
 150{
 151        if (new_time < 1 || new_time > WDT_SOFTTIMER_MAX) {
 152                pr_warn("timeout value must be 1<=x<=%d, using %d\n",
 153                        WDT_SOFTTIMER_MAX, new_time);
 154                return -EINVAL;
 155        }
 156
 157        wdd->timeout = new_time;
 158        return 0;
 159}
 160
 161static const struct watchdog_info bcm47xx_wdt_info = {
 162        .identity       = DRV_NAME,
 163        .options        = WDIOF_SETTIMEOUT |
 164                                WDIOF_KEEPALIVEPING |
 165                                WDIOF_MAGICCLOSE,
 166};
 167
 168static const struct watchdog_ops bcm47xx_wdt_soft_ops = {
 169        .owner          = THIS_MODULE,
 170        .start          = bcm47xx_wdt_soft_start,
 171        .stop           = bcm47xx_wdt_soft_stop,
 172        .ping           = bcm47xx_wdt_soft_keepalive,
 173        .set_timeout    = bcm47xx_wdt_soft_set_timeout,
 174        .restart        = bcm47xx_wdt_restart,
 175};
 176
 177static int bcm47xx_wdt_probe(struct platform_device *pdev)
 178{
 179        int ret;
 180        bool soft;
 181        struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
 182
 183        if (!wdt)
 184                return -ENXIO;
 185
 186        soft = wdt->max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000;
 187
 188        if (soft) {
 189                wdt->wdd.ops = &bcm47xx_wdt_soft_ops;
 190                timer_setup(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, 0);
 191        } else {
 192                wdt->wdd.ops = &bcm47xx_wdt_hard_ops;
 193        }
 194
 195        wdt->wdd.info = &bcm47xx_wdt_info;
 196        wdt->wdd.timeout = WDT_DEFAULT_TIME;
 197        wdt->wdd.parent = &pdev->dev;
 198        ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout);
 199        if (ret)
 200                goto err_timer;
 201        watchdog_set_nowayout(&wdt->wdd, nowayout);
 202        watchdog_set_restart_priority(&wdt->wdd, 64);
 203        watchdog_stop_on_reboot(&wdt->wdd);
 204
 205        ret = watchdog_register_device(&wdt->wdd);
 206        if (ret)
 207                goto err_timer;
 208
 209        dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n",
 210                timeout, nowayout ? ", nowayout" : "",
 211                soft ? ", Software Timer" : "");
 212        return 0;
 213
 214err_timer:
 215        if (soft)
 216                del_timer_sync(&wdt->soft_timer);
 217
 218        return ret;
 219}
 220
 221static int bcm47xx_wdt_remove(struct platform_device *pdev)
 222{
 223        struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
 224
 225        watchdog_unregister_device(&wdt->wdd);
 226
 227        return 0;
 228}
 229
 230static struct platform_driver bcm47xx_wdt_driver = {
 231        .driver         = {
 232                .name   = "bcm47xx-wdt",
 233        },
 234        .probe          = bcm47xx_wdt_probe,
 235        .remove         = bcm47xx_wdt_remove,
 236};
 237
 238module_platform_driver(bcm47xx_wdt_driver);
 239
 240MODULE_AUTHOR("Aleksandar Radovanovic");
 241MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
 242MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
 243MODULE_LICENSE("GPL");
 244