blob: 97ccfce0dabba48039b19172f6de0874e5417a03 [file] [log] [blame]
matthieu castet90074dc2009-06-05 18:57:18 +02001/*
2 * Watchdog driver for Broadcom BCM47XX
3 *
4 * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>
5 * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +01006 * Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de>
matthieu castet90074dc2009-06-05 18:57:18 +02007 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version
11 * 2 of the License, or (at your option) any later version.
12 */
13
Joe Perches27c766a2012-02-15 15:06:19 -080014#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
15
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010016#include <linux/bcm47xx_wdt.h>
matthieu castet90074dc2009-06-05 18:57:18 +020017#include <linux/bitops.h>
18#include <linux/errno.h>
matthieu castet90074dc2009-06-05 18:57:18 +020019#include <linux/init.h>
20#include <linux/kernel.h>
matthieu castet90074dc2009-06-05 18:57:18 +020021#include <linux/module.h>
22#include <linux/moduleparam.h>
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010023#include <linux/platform_device.h>
matthieu castet90074dc2009-06-05 18:57:18 +020024#include <linux/reboot.h>
25#include <linux/types.h>
matthieu castet90074dc2009-06-05 18:57:18 +020026#include <linux/watchdog.h>
27#include <linux/timer.h>
28#include <linux/jiffies.h>
matthieu castet90074dc2009-06-05 18:57:18 +020029
30#define DRV_NAME "bcm47xx_wdt"
31
32#define WDT_DEFAULT_TIME 30 /* seconds */
33#define WDT_MAX_TIME 255 /* seconds */
34
35static int wdt_time = WDT_DEFAULT_TIME;
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010036static bool nowayout = WATCHDOG_NOWAYOUT;
matthieu castet90074dc2009-06-05 18:57:18 +020037
38module_param(wdt_time, int, 0);
39MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="
40 __MODULE_STRING(WDT_DEFAULT_TIME) ")");
41
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010042module_param(nowayout, bool, 0);
matthieu castet90074dc2009-06-05 18:57:18 +020043MODULE_PARM_DESC(nowayout,
44 "Watchdog cannot be stopped once started (default="
45 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
matthieu castet90074dc2009-06-05 18:57:18 +020046
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010047static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)
matthieu castet90074dc2009-06-05 18:57:18 +020048{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010049 return container_of(wdd, struct bcm47xx_wdt, wdd);
matthieu castet90074dc2009-06-05 18:57:18 +020050}
51
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010052static void bcm47xx_timer_tick(unsigned long data)
matthieu castet90074dc2009-06-05 18:57:18 +020053{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010054 struct bcm47xx_wdt *wdt = (struct bcm47xx_wdt *)data;
55 u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms);
matthieu castet90074dc2009-06-05 18:57:18 +020056
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010057 if (!atomic_dec_and_test(&wdt->soft_ticks)) {
58 wdt->timer_set_ms(wdt, next_tick);
59 mod_timer(&wdt->soft_timer, jiffies + HZ);
matthieu castet90074dc2009-06-05 18:57:18 +020060 } else {
Joe Perches27c766a2012-02-15 15:06:19 -080061 pr_crit("Watchdog will fire soon!!!\n");
matthieu castet90074dc2009-06-05 18:57:18 +020062 }
63}
64
Hauke Mehrtens5434a042013-01-12 18:14:07 +010065static int bcm47xx_wdt_keepalive(struct watchdog_device *wdd)
matthieu castet90074dc2009-06-05 18:57:18 +020066{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010067 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
68
69 atomic_set(&wdt->soft_ticks, wdd->timeout);
Hauke Mehrtens5434a042013-01-12 18:14:07 +010070
71 return 0;
matthieu castet90074dc2009-06-05 18:57:18 +020072}
73
Hauke Mehrtens5434a042013-01-12 18:14:07 +010074static int bcm47xx_wdt_start(struct watchdog_device *wdd)
matthieu castet90074dc2009-06-05 18:57:18 +020075{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010076 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
77
78 bcm47xx_wdt_keepalive(wdd);
79 bcm47xx_timer_tick((unsigned long)wdt);
Hauke Mehrtens5434a042013-01-12 18:14:07 +010080
81 return 0;
matthieu castet90074dc2009-06-05 18:57:18 +020082}
83
Hauke Mehrtens5434a042013-01-12 18:14:07 +010084static int bcm47xx_wdt_stop(struct watchdog_device *wdd)
matthieu castet90074dc2009-06-05 18:57:18 +020085{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010086 struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
87
88 del_timer_sync(&wdt->soft_timer);
89 wdt->timer_set(wdt, 0);
Hauke Mehrtens5434a042013-01-12 18:14:07 +010090
91 return 0;
matthieu castet90074dc2009-06-05 18:57:18 +020092}
93
Hauke Mehrtens5434a042013-01-12 18:14:07 +010094static int bcm47xx_wdt_set_timeout(struct watchdog_device *wdd,
95 unsigned int new_time)
matthieu castet90074dc2009-06-05 18:57:18 +020096{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +010097 if (new_time < 1 || new_time > WDT_MAX_TIME) {
98 pr_warn("timeout value must be 1<=x<=%d, using %d\n",
99 WDT_MAX_TIME, new_time);
matthieu castet90074dc2009-06-05 18:57:18 +0200100 return -EINVAL;
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100101 }
matthieu castet90074dc2009-06-05 18:57:18 +0200102
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100103 wdd->timeout = new_time;
matthieu castet90074dc2009-06-05 18:57:18 +0200104 return 0;
105}
106
Wim Van Sebroeck42747d72009-12-26 18:55:22 +0000107static const struct watchdog_info bcm47xx_wdt_info = {
Wim Van Sebroeck5f3b2752011-02-23 20:04:38 +0000108 .identity = DRV_NAME,
109 .options = WDIOF_SETTIMEOUT |
matthieu castet90074dc2009-06-05 18:57:18 +0200110 WDIOF_KEEPALIVEPING |
111 WDIOF_MAGICCLOSE,
112};
113
matthieu castet90074dc2009-06-05 18:57:18 +0200114static int bcm47xx_wdt_notify_sys(struct notifier_block *this,
Hauke Mehrtens5434a042013-01-12 18:14:07 +0100115 unsigned long code, void *unused)
matthieu castet90074dc2009-06-05 18:57:18 +0200116{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100117 struct bcm47xx_wdt *wdt;
118
119 wdt = container_of(this, struct bcm47xx_wdt, notifier);
matthieu castet90074dc2009-06-05 18:57:18 +0200120 if (code == SYS_DOWN || code == SYS_HALT)
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100121 wdt->wdd.ops->stop(&wdt->wdd);
matthieu castet90074dc2009-06-05 18:57:18 +0200122 return NOTIFY_DONE;
123}
124
Hauke Mehrtens5434a042013-01-12 18:14:07 +0100125static struct watchdog_ops bcm47xx_wdt_ops = {
matthieu castet90074dc2009-06-05 18:57:18 +0200126 .owner = THIS_MODULE,
Hauke Mehrtens5434a042013-01-12 18:14:07 +0100127 .start = bcm47xx_wdt_start,
128 .stop = bcm47xx_wdt_stop,
129 .ping = bcm47xx_wdt_keepalive,
130 .set_timeout = bcm47xx_wdt_set_timeout,
matthieu castet90074dc2009-06-05 18:57:18 +0200131};
132
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100133static int bcm47xx_wdt_probe(struct platform_device *pdev)
matthieu castet90074dc2009-06-05 18:57:18 +0200134{
135 int ret;
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100136 struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
matthieu castet90074dc2009-06-05 18:57:18 +0200137
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100138 if (!wdt)
139 return -ENXIO;
matthieu castet90074dc2009-06-05 18:57:18 +0200140
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100141 setup_timer(&wdt->soft_timer, bcm47xx_timer_tick,
142 (long unsigned int)wdt);
matthieu castet90074dc2009-06-05 18:57:18 +0200143
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100144 wdt->wdd.ops = &bcm47xx_wdt_ops;
145 wdt->wdd.info = &bcm47xx_wdt_info;
146 wdt->wdd.timeout = WDT_DEFAULT_TIME;
147 ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout);
matthieu castet90074dc2009-06-05 18:57:18 +0200148 if (ret)
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100149 goto err_timer;
150 watchdog_set_nowayout(&wdt->wdd, nowayout);
matthieu castet90074dc2009-06-05 18:57:18 +0200151
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100152 wdt->notifier.notifier_call = &bcm47xx_wdt_notify_sys;
153
154 ret = register_reboot_notifier(&wdt->notifier);
155 if (ret)
156 goto err_timer;
157
158 ret = watchdog_register_device(&wdt->wdd);
159 if (ret)
160 goto err_notifier;
matthieu castet90074dc2009-06-05 18:57:18 +0200161
Joe Perches27c766a2012-02-15 15:06:19 -0800162 pr_info("BCM47xx Watchdog Timer enabled (%d seconds%s)\n",
163 wdt_time, nowayout ? ", nowayout" : "");
matthieu castet90074dc2009-06-05 18:57:18 +0200164 return 0;
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100165
166err_notifier:
167 unregister_reboot_notifier(&wdt->notifier);
168err_timer:
169 del_timer_sync(&wdt->soft_timer);
170
171 return ret;
matthieu castet90074dc2009-06-05 18:57:18 +0200172}
173
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100174static int bcm47xx_wdt_remove(struct platform_device *pdev)
matthieu castet90074dc2009-06-05 18:57:18 +0200175{
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100176 struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
matthieu castet90074dc2009-06-05 18:57:18 +0200177
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100178 if (!wdt)
179 return -ENXIO;
180
181 watchdog_unregister_device(&wdt->wdd);
182 unregister_reboot_notifier(&wdt->notifier);
183
184 return 0;
matthieu castet90074dc2009-06-05 18:57:18 +0200185}
186
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100187static struct platform_driver bcm47xx_wdt_driver = {
188 .driver = {
189 .owner = THIS_MODULE,
190 .name = "bcm47xx-wdt",
191 },
192 .probe = bcm47xx_wdt_probe,
193 .remove = bcm47xx_wdt_remove,
194};
195
196module_platform_driver(bcm47xx_wdt_driver);
matthieu castet90074dc2009-06-05 18:57:18 +0200197
198MODULE_AUTHOR("Aleksandar Radovanovic");
Hauke Mehrtensf82dedf2013-01-24 18:13:34 +0100199MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
matthieu castet90074dc2009-06-05 18:57:18 +0200200MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
201MODULE_LICENSE("GPL");