0001
0002
0003
0004
0005
0006
0007
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
0028 #define WDT_SOFTTIMER_MAX 255
0029 #define WDT_SOFTTIMER_THRESHOLD 60
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");