aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuenter Roeck <linux@roeck-us.net>2017-12-24 16:04:14 -0500
committerWim Van Sebroeck <wim@iguana.be>2018-01-21 06:56:36 -0500
commit7cd9d5fff792026a908ccf3c229e1ec84668733c (patch)
tree1202acefa73063056292a8cff77cf81ba4aeb15d
parent5bbecc5d3454d1069baf061a2cf0f04d0f0b9e7f (diff)
watchdog: sp5100_tco: Convert to use watchdog subsystem
Convert to watchdog subsystem. As part of that rework, use devm functions where possible, and replace almost all static variables with a dynamically allocated data structure. Cc: Zoltán Böszörményi <zboszor@pr.hu> Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
-rw-r--r--drivers/watchdog/sp5100_tco.c358
1 files changed, 102 insertions, 256 deletions
diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c
index 1123fad38fdf..bb6c4608c1c0 100644
--- a/drivers/watchdog/sp5100_tco.c
+++ b/drivers/watchdog/sp5100_tco.c
@@ -24,37 +24,31 @@
24 24
25#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 25#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
26 26
27#include <linux/init.h>
28#include <linux/io.h>
29#include <linux/ioport.h>
27#include <linux/module.h> 30#include <linux/module.h>
28#include <linux/moduleparam.h> 31#include <linux/moduleparam.h>
29#include <linux/types.h>
30#include <linux/miscdevice.h>
31#include <linux/watchdog.h>
32#include <linux/init.h>
33#include <linux/fs.h>
34#include <linux/pci.h> 32#include <linux/pci.h>
35#include <linux/ioport.h>
36#include <linux/platform_device.h> 33#include <linux/platform_device.h>
37#include <linux/uaccess.h> 34#include <linux/types.h>
38#include <linux/io.h> 35#include <linux/watchdog.h>
39 36
40#include "sp5100_tco.h" 37#include "sp5100_tco.h"
41 38
42/* Module and version information */ 39#define TCO_DRIVER_NAME "sp5100-tco"
43#define TCO_VERSION "0.05"
44#define TCO_MODULE_NAME "SP5100 TCO timer"
45#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
46 40
47/* internal variables */ 41/* internal variables */
48static u32 tcobase_phys; 42
49static u32 tco_wdt_fired; 43struct sp5100_tco {
50static void __iomem *tcobase; 44 struct watchdog_device wdd;
51static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ 45 void __iomem *tcobase;
52static unsigned long timer_alive; 46};
53static char tco_expect_close;
54static struct pci_dev *sp5100_tco_pci;
55 47
56/* the watchdog platform device */ 48/* the watchdog platform device */
57static struct platform_device *sp5100_tco_platform_device; 49static struct platform_device *sp5100_tco_platform_device;
50/* the associated PCI device */
51static struct pci_dev *sp5100_tco_pci;
58 52
59/* module parameters */ 53/* module parameters */
60 54
@@ -79,55 +73,52 @@ static bool tco_has_sp5100_reg_layout(struct pci_dev *dev)
79 dev->revision < 0x40; 73 dev->revision < 0x40;
80} 74}
81 75
82static void tco_timer_start(void) 76static int tco_timer_start(struct watchdog_device *wdd)
83{ 77{
78 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
84 u32 val; 79 u32 val;
85 unsigned long flags;
86 80
87 spin_lock_irqsave(&tco_lock, flags); 81 val = readl(SP5100_WDT_CONTROL(tco->tcobase));
88 val = readl(SP5100_WDT_CONTROL(tcobase));
89 val |= SP5100_WDT_START_STOP_BIT; 82 val |= SP5100_WDT_START_STOP_BIT;
90 writel(val, SP5100_WDT_CONTROL(tcobase)); 83 writel(val, SP5100_WDT_CONTROL(tco->tcobase));
91 spin_unlock_irqrestore(&tco_lock, flags); 84
85 return 0;
92} 86}
93 87
94static void tco_timer_stop(void) 88static int tco_timer_stop(struct watchdog_device *wdd)
95{ 89{
90 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
96 u32 val; 91 u32 val;
97 unsigned long flags;
98 92
99 spin_lock_irqsave(&tco_lock, flags); 93 val = readl(SP5100_WDT_CONTROL(tco->tcobase));
100 val = readl(SP5100_WDT_CONTROL(tcobase));
101 val &= ~SP5100_WDT_START_STOP_BIT; 94 val &= ~SP5100_WDT_START_STOP_BIT;
102 writel(val, SP5100_WDT_CONTROL(tcobase)); 95 writel(val, SP5100_WDT_CONTROL(tco->tcobase));
103 spin_unlock_irqrestore(&tco_lock, flags); 96
97 return 0;
104} 98}
105 99
106static void tco_timer_keepalive(void) 100static int tco_timer_ping(struct watchdog_device *wdd)
107{ 101{
102 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
108 u32 val; 103 u32 val;
109 unsigned long flags;
110 104
111 spin_lock_irqsave(&tco_lock, flags); 105 val = readl(SP5100_WDT_CONTROL(tco->tcobase));
112 val = readl(SP5100_WDT_CONTROL(tcobase));
113 val |= SP5100_WDT_TRIGGER_BIT; 106 val |= SP5100_WDT_TRIGGER_BIT;
114 writel(val, SP5100_WDT_CONTROL(tcobase)); 107 writel(val, SP5100_WDT_CONTROL(tco->tcobase));
115 spin_unlock_irqrestore(&tco_lock, flags); 108
109 return 0;
116} 110}
117 111
118static int tco_timer_set_heartbeat(int t) 112static int tco_timer_set_timeout(struct watchdog_device *wdd,
113 unsigned int t)
119{ 114{
120 unsigned long flags; 115 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
121
122 if (t < 0 || t > 0xffff)
123 return -EINVAL;
124 116
125 /* Write new heartbeat to watchdog */ 117 /* Write new heartbeat to watchdog */
126 spin_lock_irqsave(&tco_lock, flags); 118 writel(t, SP5100_WDT_COUNT(tco->tcobase));
127 writel(t, SP5100_WDT_COUNT(tcobase)); 119
128 spin_unlock_irqrestore(&tco_lock, flags); 120 wdd->timeout = t;
129 121
130 heartbeat = t;
131 return 0; 122 return 0;
132} 123}
133 124
@@ -182,137 +173,7 @@ static void tco_timer_enable(void)
182 } 173 }
183} 174}
184 175
185/* 176static u32 sp5100_tco_read_pm_reg32(u8 index)
186 * /dev/watchdog handling
187 */
188
189static int sp5100_tco_open(struct inode *inode, struct file *file)
190{
191 /* /dev/watchdog can only be opened once */
192 if (test_and_set_bit(0, &timer_alive))
193 return -EBUSY;
194
195 /* Reload and activate timer */
196 tco_timer_start();
197 tco_timer_keepalive();
198 return nonseekable_open(inode, file);
199}
200
201static int sp5100_tco_release(struct inode *inode, struct file *file)
202{
203 /* Shut off the timer. */
204 if (tco_expect_close == 42) {
205 tco_timer_stop();
206 } else {
207 pr_crit("Unexpected close, not stopping watchdog!\n");
208 tco_timer_keepalive();
209 }
210 clear_bit(0, &timer_alive);
211 tco_expect_close = 0;
212 return 0;
213}
214
215static ssize_t sp5100_tco_write(struct file *file, const char __user *data,
216 size_t len, loff_t *ppos)
217{
218 /* See if we got the magic character 'V' and reload the timer */
219 if (len) {
220 if (!nowayout) {
221 size_t i;
222
223 /* note: just in case someone wrote the magic character
224 * five months ago... */
225 tco_expect_close = 0;
226
227 /* scan to see whether or not we got the magic character
228 */
229 for (i = 0; i != len; i++) {
230 char c;
231 if (get_user(c, data + i))
232 return -EFAULT;
233 if (c == 'V')
234 tco_expect_close = 42;
235 }
236 }
237
238 /* someone wrote to us, we should reload the timer */
239 tco_timer_keepalive();
240 }
241 return len;
242}
243
244static long sp5100_tco_ioctl(struct file *file, unsigned int cmd,
245 unsigned long arg)
246{
247 int new_options, retval = -EINVAL;
248 int new_heartbeat;
249 void __user *argp = (void __user *)arg;
250 int __user *p = argp;
251 static const struct watchdog_info ident = {
252 .options = WDIOF_SETTIMEOUT |
253 WDIOF_KEEPALIVEPING |
254 WDIOF_MAGICCLOSE,
255 .firmware_version = 0,
256 .identity = TCO_MODULE_NAME,
257 };
258
259 switch (cmd) {
260 case WDIOC_GETSUPPORT:
261 return copy_to_user(argp, &ident,
262 sizeof(ident)) ? -EFAULT : 0;
263 case WDIOC_GETSTATUS:
264 case WDIOC_GETBOOTSTATUS:
265 return put_user(0, p);
266 case WDIOC_SETOPTIONS:
267 if (get_user(new_options, p))
268 return -EFAULT;
269 if (new_options & WDIOS_DISABLECARD) {
270 tco_timer_stop();
271 retval = 0;
272 }
273 if (new_options & WDIOS_ENABLECARD) {
274 tco_timer_start();
275 tco_timer_keepalive();
276 retval = 0;
277 }
278 return retval;
279 case WDIOC_KEEPALIVE:
280 tco_timer_keepalive();
281 return 0;
282 case WDIOC_SETTIMEOUT:
283 if (get_user(new_heartbeat, p))
284 return -EFAULT;
285 if (tco_timer_set_heartbeat(new_heartbeat))
286 return -EINVAL;
287 tco_timer_keepalive();
288 /* Fall through */
289 case WDIOC_GETTIMEOUT:
290 return put_user(heartbeat, p);
291 default:
292 return -ENOTTY;
293 }
294}
295
296/*
297 * Kernel Interfaces
298 */
299
300static const struct file_operations sp5100_tco_fops = {
301 .owner = THIS_MODULE,
302 .llseek = no_llseek,
303 .write = sp5100_tco_write,
304 .unlocked_ioctl = sp5100_tco_ioctl,
305 .open = sp5100_tco_open,
306 .release = sp5100_tco_release,
307};
308
309static struct miscdevice sp5100_tco_miscdev = {
310 .minor = WATCHDOG_MINOR,
311 .name = "watchdog",
312 .fops = &sp5100_tco_fops,
313};
314
315static u8 sp5100_tco_read_pm_reg32(u8 index)
316{ 177{
317 u32 val = 0; 178 u32 val = 0;
318 int i; 179 int i;
@@ -323,14 +184,13 @@ static u8 sp5100_tco_read_pm_reg32(u8 index)
323 return val; 184 return val;
324} 185}
325 186
326/* 187static int sp5100_tco_setupdevice(struct device *dev,
327 * Init & exit routines 188 struct watchdog_device *wdd)
328 */
329static int sp5100_tco_setupdevice(struct device *dev)
330{ 189{
331 const char *dev_name = NULL; 190 struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
332 u32 val; 191 const char *dev_name;
333 u8 base_addr; 192 u8 base_addr;
193 u32 val;
334 int ret; 194 int ret;
335 195
336 /* 196 /*
@@ -361,8 +221,8 @@ static int sp5100_tco_setupdevice(struct device *dev)
361 dev_dbg(dev, "Got 0x%04x from indirect I/O\n", val); 221 dev_dbg(dev, "Got 0x%04x from indirect I/O\n", val);
362 222
363 /* Check MMIO address conflict */ 223 /* Check MMIO address conflict */
364 if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, 224 if (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE,
365 dev_name)) { 225 dev_name)) {
366 dev_dbg(dev, "MMIO address 0x%04x already in use\n", val); 226 dev_dbg(dev, "MMIO address 0x%04x already in use\n", val);
367 /* 227 /*
368 * Secondly, Find the watchdog timer MMIO address 228 * Secondly, Find the watchdog timer MMIO address
@@ -391,8 +251,8 @@ static int sp5100_tco_setupdevice(struct device *dev)
391 /* Add the Watchdog Timer offset to base address. */ 251 /* Add the Watchdog Timer offset to base address. */
392 val += SB800_PM_WDT_MMIO_OFFSET; 252 val += SB800_PM_WDT_MMIO_OFFSET;
393 /* Check MMIO address conflict */ 253 /* Check MMIO address conflict */
394 if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, 254 if (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE,
395 dev_name)) { 255 dev_name)) {
396 dev_dbg(dev, "MMIO address 0x%04x already in use\n", 256 dev_dbg(dev, "MMIO address 0x%04x already in use\n",
397 val); 257 val);
398 ret = -EBUSY; 258 ret = -EBUSY;
@@ -401,13 +261,11 @@ static int sp5100_tco_setupdevice(struct device *dev)
401 dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val); 261 dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val);
402 } 262 }
403 263
404 tcobase_phys = val; 264 tco->tcobase = devm_ioremap(dev, val, SP5100_WDT_MEM_MAP_SIZE);
405 265 if (!tco->tcobase) {
406 tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE);
407 if (!tcobase) {
408 dev_err(dev, "failed to get tcobase address\n"); 266 dev_err(dev, "failed to get tcobase address\n");
409 ret = -ENOMEM; 267 ret = -ENOMEM;
410 goto unreg_mem_region; 268 goto unreg_region;
411 } 269 }
412 270
413 dev_info(dev, "Using 0x%04x for watchdog MMIO address\n", val); 271 dev_info(dev, "Using 0x%04x for watchdog MMIO address\n", val);
@@ -416,107 +274,95 @@ static int sp5100_tco_setupdevice(struct device *dev)
416 tco_timer_enable(); 274 tco_timer_enable();
417 275
418 /* Check that the watchdog action is set to reset the system */ 276 /* Check that the watchdog action is set to reset the system */
419 val = readl(SP5100_WDT_CONTROL(tcobase)); 277 val = readl(SP5100_WDT_CONTROL(tco->tcobase));
420 /* 278 /*
421 * Save WatchDogFired status, because WatchDogFired flag is 279 * Save WatchDogFired status, because WatchDogFired flag is
422 * cleared here. 280 * cleared here.
423 */ 281 */
424 tco_wdt_fired = val & SP5100_WDT_FIRED; 282 if (val & SP5100_WDT_FIRED)
283 wdd->bootstatus = WDIOF_CARDRESET;
425 val &= ~SP5100_WDT_ACTION_RESET; 284 val &= ~SP5100_WDT_ACTION_RESET;
426 writel(val, SP5100_WDT_CONTROL(tcobase)); 285 writel(val, SP5100_WDT_CONTROL(tco->tcobase));
427 286
428 /* Set a reasonable heartbeat before we stop the timer */ 287 /* Set a reasonable heartbeat before we stop the timer */
429 tco_timer_set_heartbeat(heartbeat); 288 tco_timer_set_timeout(wdd, wdd->timeout);
430 289
431 /* 290 /*
432 * Stop the TCO before we change anything so we don't race with 291 * Stop the TCO before we change anything so we don't race with
433 * a zeroed timer. 292 * a zeroed timer.
434 */ 293 */
435 tco_timer_stop(); 294 tco_timer_stop(wdd);
436 295
437 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); 296 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
438 297
439 return 0; 298 return 0;
440 299
441unreg_mem_region:
442 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
443unreg_region: 300unreg_region:
444 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); 301 release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
445 return ret; 302 return ret;
446} 303}
447 304
305static struct watchdog_info sp5100_tco_wdt_info = {
306 .identity = "SP5100 TCO timer",
307 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
308};
309
310static const struct watchdog_ops sp5100_tco_wdt_ops = {
311 .owner = THIS_MODULE,
312 .start = tco_timer_start,
313 .stop = tco_timer_stop,
314 .ping = tco_timer_ping,
315 .set_timeout = tco_timer_set_timeout,
316};
317
448static int sp5100_tco_probe(struct platform_device *pdev) 318static int sp5100_tco_probe(struct platform_device *pdev)
449{ 319{
450 struct device *dev = &pdev->dev; 320 struct device *dev = &pdev->dev;
321 struct watchdog_device *wdd;
322 struct sp5100_tco *tco;
451 int ret; 323 int ret;
452 324
453 /* 325 tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);
454 * Check whether or not the hardware watchdog is there. If found, then 326 if (!tco)
455 * set it up. 327 return -ENOMEM;
456 */ 328
457 ret = sp5100_tco_setupdevice(dev); 329 wdd = &tco->wdd;
330 wdd->parent = dev;
331 wdd->info = &sp5100_tco_wdt_info;
332 wdd->ops = &sp5100_tco_wdt_ops;
333 wdd->timeout = WATCHDOG_HEARTBEAT;
334 wdd->min_timeout = 1;
335 wdd->max_timeout = 0xffff;
336
337 if (watchdog_init_timeout(wdd, heartbeat, NULL))
338 dev_info(dev, "timeout value invalid, using %d\n",
339 wdd->timeout);
340 watchdog_set_nowayout(wdd, nowayout);
341 watchdog_stop_on_reboot(wdd);
342 watchdog_stop_on_unregister(wdd);
343 watchdog_set_drvdata(wdd, tco);
344
345 ret = sp5100_tco_setupdevice(dev, wdd);
458 if (ret) 346 if (ret)
459 return ret; 347 return ret;
460 348
461 /* Check to see if last reboot was due to watchdog timeout */ 349 ret = devm_watchdog_register_device(dev, wdd);
462 dev_info(dev, "Last reboot was %striggered by watchdog.\n", 350 if (ret) {
463 tco_wdt_fired ? "" : "not "); 351 dev_err(dev, "cannot register watchdog device (err=%d)\n", ret);
464 352 return ret;
465 /*
466 * Check that the heartbeat value is within it's range.
467 * If not, reset to the default.
468 */
469 if (tco_timer_set_heartbeat(heartbeat)) {
470 heartbeat = WATCHDOG_HEARTBEAT;
471 tco_timer_set_heartbeat(heartbeat);
472 }
473
474 ret = misc_register(&sp5100_tco_miscdev);
475 if (ret != 0) {
476 dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n",
477 WATCHDOG_MINOR, ret);
478 goto exit;
479 } 353 }
480 354
481 clear_bit(0, &timer_alive);
482
483 /* Show module parameters */ 355 /* Show module parameters */
484 dev_info(dev, "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", 356 dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
485 tcobase, heartbeat, nowayout); 357 wdd->timeout, nowayout);
486 358
487 return 0; 359 return 0;
488
489exit:
490 iounmap(tcobase);
491 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
492 return ret;
493}
494
495static int sp5100_tco_remove(struct platform_device *pdev)
496{
497 /* Stop the timer before we leave */
498 if (!nowayout)
499 tco_timer_stop();
500
501 /* Deregister */
502 misc_deregister(&sp5100_tco_miscdev);
503 iounmap(tcobase);
504 release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
505
506 return 0;
507}
508
509static void sp5100_tco_shutdown(struct platform_device *pdev)
510{
511 tco_timer_stop();
512} 360}
513 361
514static struct platform_driver sp5100_tco_driver = { 362static struct platform_driver sp5100_tco_driver = {
515 .probe = sp5100_tco_probe, 363 .probe = sp5100_tco_probe,
516 .remove = sp5100_tco_remove,
517 .shutdown = sp5100_tco_shutdown,
518 .driver = { 364 .driver = {
519 .name = TCO_MODULE_NAME, 365 .name = TCO_DRIVER_NAME,
520 }, 366 },
521}; 367};
522 368
@@ -555,14 +401,14 @@ static int __init sp5100_tco_init(void)
555 if (!sp5100_tco_pci) 401 if (!sp5100_tco_pci)
556 return -ENODEV; 402 return -ENODEV;
557 403
558 pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION); 404 pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");
559 405
560 err = platform_driver_register(&sp5100_tco_driver); 406 err = platform_driver_register(&sp5100_tco_driver);
561 if (err) 407 if (err)
562 return err; 408 return err;
563 409
564 sp5100_tco_platform_device = platform_device_register_simple( 410 sp5100_tco_platform_device =
565 TCO_MODULE_NAME, -1, NULL, 0); 411 platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);
566 if (IS_ERR(sp5100_tco_platform_device)) { 412 if (IS_ERR(sp5100_tco_platform_device)) {
567 err = PTR_ERR(sp5100_tco_platform_device); 413 err = PTR_ERR(sp5100_tco_platform_device);
568 goto unreg_platform_driver; 414 goto unreg_platform_driver;