// SPDX-License-Identifier: GPL-2.0-or-later /* * AMD Elan SC520 processor Watchdog Timer driver * * Based on acquirewdt.c by Alan Cox, * and sbc60xxwdt.c by Jakob Oestergaard <[email protected]> * * The authors do NOT admit liability nor provide warranty for * any of this software. This material is provided "AS-IS" in * the hope that it may be useful for others. * * (c) Copyright 2001 Scott Jennings <[email protected]> * 9/27 - 2001 [Initial release] * * Additional fixes Alan Cox * - Fixed formatting * - Removed debug printks * - Fixed SMP built kernel deadlock * - Switched to private locks not lock_kernel * - Used ioremap/writew/readw * - Added NOWAYOUT support * 4/12 - 2002 Changes by Rob Radez <[email protected]> * - Change comments * - Eliminate fop_llseek * - Change CONFIG_WATCHDOG_NOWAYOUT semantics * - Add KERN_* tags to printks * - fix possible wdt_is_open race * - Report proper capabilities in watchdog_info * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, * GETTIMEOUT, SETOPTIONS} ioctls * 09/8 - 2003 Changes by Wim Van Sebroeck <[email protected]> * - cleanup of trailing spaces * - added extra printk's for startup problems * - use module_param * - made timeout (the emulated heartbeat) a module_param * - made the keepalive ping an internal subroutine * 3/27 - 2004 Changes by Sean Young <[email protected]> * - set MMCR_BASE to 0xfffef000 * - CBAR does not need to be read * - removed debugging printks * * This WDT driver is different from most other Linux WDT * drivers in that the driver will ping the watchdog by itself, * because this particular WDT has a very short timeout (1.6 * seconds) and it would be insane to count on any userspace * daemon always getting scheduled within that time frame. * * This driver uses memory mapped IO, and spinlock. */ #define pr_fmt(fmt) … #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> #include <linux/timer.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> #include <linux/jiffies.h> #include <linux/io.h> #include <linux/uaccess.h> /* * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) * * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s * * We will program the SC520 watchdog for a timeout of 2.01s. * If we reset the watchdog every ~250ms we should be safe. */ #define WDT_INTERVAL … /* * We must not require too good response from the userspace daemon. * Here we require the userspace daemon to send us a heartbeat * char to /dev/watchdog every 30 seconds. */ #define WATCHDOG_TIMEOUT … /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ static int timeout = …; module_param(timeout, int, 0); MODULE_PARM_DESC(…) …; static bool nowayout = … WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(…) …; /* * AMD Elan SC520 - Watchdog Timer Registers */ #define MMCR_BASE … #define OFFS_WDTMRCTL … /* WDT Control Register bit definitions */ #define WDT_EXP_SEL_01 … #define WDT_EXP_SEL_02 … #define WDT_EXP_SEL_03 … #define WDT_EXP_SEL_04 … #define WDT_EXP_SEL_05 … #define WDT_EXP_SEL_06 … #define WDT_EXP_SEL_07 … #define WDT_EXP_SEL_08 … #define WDT_IRQ_FLG … #define WDT_WRST_ENB … #define WDT_ENB … static __u16 __iomem *wdtmrctl; static void wdt_timer_ping(struct timer_list *); static DEFINE_TIMER(timer, wdt_timer_ping); static unsigned long next_heartbeat; static unsigned long wdt_is_open; static char wdt_expect_close; static DEFINE_SPINLOCK(wdt_spinlock); /* * Whack the dog */ static void wdt_timer_ping(struct timer_list *unused) { … } /* * Utility routines */ static void wdt_config(int writeval) { … } static int wdt_startup(void) { … } static int wdt_turnoff(void) { … } static int wdt_keepalive(void) { … } static int wdt_set_heartbeat(int t) { … } /* * /dev/watchdog handling */ static ssize_t fop_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { … } static int fop_open(struct inode *inode, struct file *file) { … } static int fop_close(struct inode *inode, struct file *file) { … } static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { … } static const struct file_operations wdt_fops = …; static struct miscdevice wdt_miscdev = …; /* * Notifier for system down */ static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { … } /* * The WDT needs to learn about soft shutdowns in order to * turn the timebomb registers off. */ static struct notifier_block wdt_notifier = …; static void __exit sc520_wdt_unload(void) { … } static int __init sc520_wdt_init(void) { … } module_init(…) …; module_exit(sc520_wdt_unload); MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …;