Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  *  Watchdog driver for Broadcom BCM47XX
0004  *
0005  *  Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>
0006  *  Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>
0007  *  Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de>
0008  *
0009  */
0010 
0011 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0012 
0013 #include <linux/bcm47xx_wdt.h>
0014 #include <linux/bitops.h>
0015 #include <linux/errno.h>
0016 #include <linux/kernel.h>
0017 #include <linux/module.h>
0018 #include <linux/moduleparam.h>
0019 #include <linux/platform_device.h>
0020 #include <linux/types.h>
0021 #include <linux/watchdog.h>
0022 #include <linux/timer.h>
0023 #include <linux/jiffies.h>
0024 
0025 #define DRV_NAME        "bcm47xx_wdt"
0026 
0027 #define WDT_DEFAULT_TIME    30  /* seconds */
0028 #define WDT_SOFTTIMER_MAX   255 /* seconds */
0029 #define WDT_SOFTTIMER_THRESHOLD 60  /* seconds */
0030 
0031 static int timeout = WDT_DEFAULT_TIME;
0032 static bool nowayout = WATCHDOG_NOWAYOUT;
0033 
0034 module_param(timeout, int, 0);
0035 MODULE_PARM_DESC(timeout, "Watchdog time in seconds. (default="
0036                 __MODULE_STRING(WDT_DEFAULT_TIME) ")");
0037 
0038 module_param(nowayout, bool, 0);
0039 MODULE_PARM_DESC(nowayout,
0040         "Watchdog cannot be stopped once started (default="
0041                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0042 
0043 static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)
0044 {
0045     return container_of(wdd, struct bcm47xx_wdt, wdd);
0046 }
0047 
0048 static int bcm47xx_wdt_hard_keepalive(struct watchdog_device *wdd)
0049 {
0050     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0051 
0052     wdt->timer_set_ms(wdt, wdd->timeout * 1000);
0053 
0054     return 0;
0055 }
0056 
0057 static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd)
0058 {
0059     return 0;
0060 }
0061 
0062 static int bcm47xx_wdt_hard_stop(struct watchdog_device *wdd)
0063 {
0064     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0065 
0066     wdt->timer_set(wdt, 0);
0067 
0068     return 0;
0069 }
0070 
0071 static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd,
0072                     unsigned int new_time)
0073 {
0074     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0075     u32 max_timer = wdt->max_timer_ms;
0076 
0077     if (new_time < 1 || new_time > max_timer / 1000) {
0078         pr_warn("timeout value must be 1<=x<=%d, using %d\n",
0079             max_timer / 1000, new_time);
0080         return -EINVAL;
0081     }
0082 
0083     wdd->timeout = new_time;
0084     return 0;
0085 }
0086 
0087 static int bcm47xx_wdt_restart(struct watchdog_device *wdd,
0088                    unsigned long action, void *data)
0089 {
0090     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0091 
0092     wdt->timer_set(wdt, 1);
0093 
0094     return 0;
0095 }
0096 
0097 static const struct watchdog_ops bcm47xx_wdt_hard_ops = {
0098     .owner      = THIS_MODULE,
0099     .start      = bcm47xx_wdt_hard_start,
0100     .stop       = bcm47xx_wdt_hard_stop,
0101     .ping       = bcm47xx_wdt_hard_keepalive,
0102     .set_timeout    = bcm47xx_wdt_hard_set_timeout,
0103     .restart        = bcm47xx_wdt_restart,
0104 };
0105 
0106 static void bcm47xx_wdt_soft_timer_tick(struct timer_list *t)
0107 {
0108     struct bcm47xx_wdt *wdt = from_timer(wdt, t, soft_timer);
0109     u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms);
0110 
0111     if (!atomic_dec_and_test(&wdt->soft_ticks)) {
0112         wdt->timer_set_ms(wdt, next_tick);
0113         mod_timer(&wdt->soft_timer, jiffies + HZ);
0114     } else {
0115         pr_crit("Watchdog will fire soon!!!\n");
0116     }
0117 }
0118 
0119 static int bcm47xx_wdt_soft_keepalive(struct watchdog_device *wdd)
0120 {
0121     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0122 
0123     atomic_set(&wdt->soft_ticks, wdd->timeout);
0124 
0125     return 0;
0126 }
0127 
0128 static int bcm47xx_wdt_soft_start(struct watchdog_device *wdd)
0129 {
0130     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0131 
0132     bcm47xx_wdt_soft_keepalive(wdd);
0133     bcm47xx_wdt_soft_timer_tick(&wdt->soft_timer);
0134 
0135     return 0;
0136 }
0137 
0138 static int bcm47xx_wdt_soft_stop(struct watchdog_device *wdd)
0139 {
0140     struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
0141 
0142     del_timer_sync(&wdt->soft_timer);
0143     wdt->timer_set(wdt, 0);
0144 
0145     return 0;
0146 }
0147 
0148 static int bcm47xx_wdt_soft_set_timeout(struct watchdog_device *wdd,
0149                     unsigned int new_time)
0150 {
0151     if (new_time < 1 || new_time > WDT_SOFTTIMER_MAX) {
0152         pr_warn("timeout value must be 1<=x<=%d, using %d\n",
0153             WDT_SOFTTIMER_MAX, new_time);
0154         return -EINVAL;
0155     }
0156 
0157     wdd->timeout = new_time;
0158     return 0;
0159 }
0160 
0161 static const struct watchdog_info bcm47xx_wdt_info = {
0162     .identity   = DRV_NAME,
0163     .options    = WDIOF_SETTIMEOUT |
0164                 WDIOF_KEEPALIVEPING |
0165                 WDIOF_MAGICCLOSE,
0166 };
0167 
0168 static const struct watchdog_ops bcm47xx_wdt_soft_ops = {
0169     .owner      = THIS_MODULE,
0170     .start      = bcm47xx_wdt_soft_start,
0171     .stop       = bcm47xx_wdt_soft_stop,
0172     .ping       = bcm47xx_wdt_soft_keepalive,
0173     .set_timeout    = bcm47xx_wdt_soft_set_timeout,
0174     .restart        = bcm47xx_wdt_restart,
0175 };
0176 
0177 static int bcm47xx_wdt_probe(struct platform_device *pdev)
0178 {
0179     int ret;
0180     bool soft;
0181     struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
0182 
0183     if (!wdt)
0184         return -ENXIO;
0185 
0186     soft = wdt->max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000;
0187 
0188     if (soft) {
0189         wdt->wdd.ops = &bcm47xx_wdt_soft_ops;
0190         timer_setup(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, 0);
0191     } else {
0192         wdt->wdd.ops = &bcm47xx_wdt_hard_ops;
0193     }
0194 
0195     wdt->wdd.info = &bcm47xx_wdt_info;
0196     wdt->wdd.timeout = WDT_DEFAULT_TIME;
0197     wdt->wdd.parent = &pdev->dev;
0198     ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout);
0199     if (ret)
0200         goto err_timer;
0201     watchdog_set_nowayout(&wdt->wdd, nowayout);
0202     watchdog_set_restart_priority(&wdt->wdd, 64);
0203     watchdog_stop_on_reboot(&wdt->wdd);
0204 
0205     ret = watchdog_register_device(&wdt->wdd);
0206     if (ret)
0207         goto err_timer;
0208 
0209     dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n",
0210         timeout, nowayout ? ", nowayout" : "",
0211         soft ? ", Software Timer" : "");
0212     return 0;
0213 
0214 err_timer:
0215     if (soft)
0216         del_timer_sync(&wdt->soft_timer);
0217 
0218     return ret;
0219 }
0220 
0221 static int bcm47xx_wdt_remove(struct platform_device *pdev)
0222 {
0223     struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
0224 
0225     watchdog_unregister_device(&wdt->wdd);
0226 
0227     return 0;
0228 }
0229 
0230 static struct platform_driver bcm47xx_wdt_driver = {
0231     .driver     = {
0232         .name   = "bcm47xx-wdt",
0233     },
0234     .probe      = bcm47xx_wdt_probe,
0235     .remove     = bcm47xx_wdt_remove,
0236 };
0237 
0238 module_platform_driver(bcm47xx_wdt_driver);
0239 
0240 MODULE_AUTHOR("Aleksandar Radovanovic");
0241 MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
0242 MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
0243 MODULE_LICENSE("GPL");