12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b7e04f8cSWim Van Sebroeck /* drivers/char/watchdog/scx200_wdt.c
3b7e04f8cSWim Van Sebroeck
4b7e04f8cSWim Van Sebroeck National Semiconductor SCx200 Watchdog support
5b7e04f8cSWim Van Sebroeck
6b7e04f8cSWim Van Sebroeck Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
7b7e04f8cSWim Van Sebroeck
8b7e04f8cSWim Van Sebroeck Some code taken from:
9b7e04f8cSWim Van Sebroeck National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
10b7e04f8cSWim Van Sebroeck (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
11b7e04f8cSWim Van Sebroeck
12b7e04f8cSWim Van Sebroeck
13b7e04f8cSWim Van Sebroeck The author(s) of this software shall not be held liable for damages
14b7e04f8cSWim Van Sebroeck of any nature resulting due to the use of this software. This
15b7e04f8cSWim Van Sebroeck software is provided AS-IS with no warranties. */
16b7e04f8cSWim Van Sebroeck
1727c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1827c766aaSJoe Perches
19b7e04f8cSWim Van Sebroeck #include <linux/module.h>
20b7e04f8cSWim Van Sebroeck #include <linux/moduleparam.h>
21b7e04f8cSWim Van Sebroeck #include <linux/init.h>
22b7e04f8cSWim Van Sebroeck #include <linux/miscdevice.h>
23b7e04f8cSWim Van Sebroeck #include <linux/watchdog.h>
24b7e04f8cSWim Van Sebroeck #include <linux/notifier.h>
25b7e04f8cSWim Van Sebroeck #include <linux/reboot.h>
26b7e04f8cSWim Van Sebroeck #include <linux/fs.h>
27b7e04f8cSWim Van Sebroeck #include <linux/ioport.h>
28b7e04f8cSWim Van Sebroeck #include <linux/scx200.h>
299b748ed0SAlan Cox #include <linux/uaccess.h>
309b748ed0SAlan Cox #include <linux/io.h>
31b7e04f8cSWim Van Sebroeck
3227c766aaSJoe Perches #define DEBUG
33b7e04f8cSWim Van Sebroeck
34b7e04f8cSWim Van Sebroeck MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
35b7e04f8cSWim Van Sebroeck MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
36b7e04f8cSWim Van Sebroeck MODULE_LICENSE("GPL");
37b7e04f8cSWim Van Sebroeck
38b7e04f8cSWim Van Sebroeck static int margin = 60; /* in seconds */
39b7e04f8cSWim Van Sebroeck module_param(margin, int, 0);
40b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
41b7e04f8cSWim Van Sebroeck
4286a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
4386a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
44b7e04f8cSWim Van Sebroeck MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
45b7e04f8cSWim Van Sebroeck
46b7e04f8cSWim Van Sebroeck static u16 wdto_restart;
47b7e04f8cSWim Van Sebroeck static char expect_close;
489b748ed0SAlan Cox static unsigned long open_lock;
499b748ed0SAlan Cox static DEFINE_SPINLOCK(scx_lock);
50b7e04f8cSWim Van Sebroeck
51b7e04f8cSWim Van Sebroeck /* Bits of the WDCNFG register */
52b7e04f8cSWim Van Sebroeck #define W_ENABLE 0x00fa /* Enable watchdog */
53b7e04f8cSWim Van Sebroeck #define W_DISABLE 0x0000 /* Disable watchdog */
54b7e04f8cSWim Van Sebroeck
55b7e04f8cSWim Van Sebroeck /* The scaling factor for the timer, this depends on the value of W_ENABLE */
56b7e04f8cSWim Van Sebroeck #define W_SCALE (32768/1024)
57b7e04f8cSWim Van Sebroeck
scx200_wdt_ping(void)58b7e04f8cSWim Van Sebroeck static void scx200_wdt_ping(void)
59b7e04f8cSWim Van Sebroeck {
609b748ed0SAlan Cox spin_lock(&scx_lock);
61b7e04f8cSWim Van Sebroeck outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
629b748ed0SAlan Cox spin_unlock(&scx_lock);
63b7e04f8cSWim Van Sebroeck }
64b7e04f8cSWim Van Sebroeck
scx200_wdt_update_margin(void)65b7e04f8cSWim Van Sebroeck static void scx200_wdt_update_margin(void)
66b7e04f8cSWim Van Sebroeck {
6727c766aaSJoe Perches pr_info("timer margin %d seconds\n", margin);
68b7e04f8cSWim Van Sebroeck wdto_restart = margin * W_SCALE;
69b7e04f8cSWim Van Sebroeck }
70b7e04f8cSWim Van Sebroeck
scx200_wdt_enable(void)71b7e04f8cSWim Van Sebroeck static void scx200_wdt_enable(void)
72b7e04f8cSWim Van Sebroeck {
7327c766aaSJoe Perches pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
74b7e04f8cSWim Van Sebroeck
759b748ed0SAlan Cox spin_lock(&scx_lock);
76b7e04f8cSWim Van Sebroeck outw(0, scx200_cb_base + SCx200_WDT_WDTO);
77b7e04f8cSWim Van Sebroeck outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
78b7e04f8cSWim Van Sebroeck outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
799b748ed0SAlan Cox spin_unlock(&scx_lock);
80b7e04f8cSWim Van Sebroeck
81b7e04f8cSWim Van Sebroeck scx200_wdt_ping();
82b7e04f8cSWim Van Sebroeck }
83b7e04f8cSWim Van Sebroeck
scx200_wdt_disable(void)84b7e04f8cSWim Van Sebroeck static void scx200_wdt_disable(void)
85b7e04f8cSWim Van Sebroeck {
8627c766aaSJoe Perches pr_debug("disabling watchdog timer\n");
87b7e04f8cSWim Van Sebroeck
889b748ed0SAlan Cox spin_lock(&scx_lock);
89b7e04f8cSWim Van Sebroeck outw(0, scx200_cb_base + SCx200_WDT_WDTO);
90b7e04f8cSWim Van Sebroeck outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
91b7e04f8cSWim Van Sebroeck outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
929b748ed0SAlan Cox spin_unlock(&scx_lock);
93b7e04f8cSWim Van Sebroeck }
94b7e04f8cSWim Van Sebroeck
scx200_wdt_open(struct inode * inode,struct file * file)95b7e04f8cSWim Van Sebroeck static int scx200_wdt_open(struct inode *inode, struct file *file)
96b7e04f8cSWim Van Sebroeck {
97b7e04f8cSWim Van Sebroeck /* only allow one at a time */
989b748ed0SAlan Cox if (test_and_set_bit(0, &open_lock))
99b7e04f8cSWim Van Sebroeck return -EBUSY;
100b7e04f8cSWim Van Sebroeck scx200_wdt_enable();
101b7e04f8cSWim Van Sebroeck
102c5bf68feSKirill Smelkov return stream_open(inode, file);
103b7e04f8cSWim Van Sebroeck }
104b7e04f8cSWim Van Sebroeck
scx200_wdt_release(struct inode * inode,struct file * file)105b7e04f8cSWim Van Sebroeck static int scx200_wdt_release(struct inode *inode, struct file *file)
106b7e04f8cSWim Van Sebroeck {
1079b748ed0SAlan Cox if (expect_close != 42)
10827c766aaSJoe Perches pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
1099b748ed0SAlan Cox else if (!nowayout)
110b7e04f8cSWim Van Sebroeck scx200_wdt_disable();
111b7e04f8cSWim Van Sebroeck expect_close = 0;
1129b748ed0SAlan Cox clear_bit(0, &open_lock);
113b7e04f8cSWim Van Sebroeck
114b7e04f8cSWim Van Sebroeck return 0;
115b7e04f8cSWim Van Sebroeck }
116b7e04f8cSWim Van Sebroeck
scx200_wdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)117b7e04f8cSWim Van Sebroeck static int scx200_wdt_notify_sys(struct notifier_block *this,
118b7e04f8cSWim Van Sebroeck unsigned long code, void *unused)
119b7e04f8cSWim Van Sebroeck {
120b7e04f8cSWim Van Sebroeck if (code == SYS_HALT || code == SYS_POWER_OFF)
121b7e04f8cSWim Van Sebroeck if (!nowayout)
122b7e04f8cSWim Van Sebroeck scx200_wdt_disable();
123b7e04f8cSWim Van Sebroeck
124b7e04f8cSWim Van Sebroeck return NOTIFY_DONE;
125b7e04f8cSWim Van Sebroeck }
126b7e04f8cSWim Van Sebroeck
1279b748ed0SAlan Cox static struct notifier_block scx200_wdt_notifier = {
128b7e04f8cSWim Van Sebroeck .notifier_call = scx200_wdt_notify_sys,
129b7e04f8cSWim Van Sebroeck };
130b7e04f8cSWim Van Sebroeck
scx200_wdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)131b7e04f8cSWim Van Sebroeck static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
132b7e04f8cSWim Van Sebroeck size_t len, loff_t *ppos)
133b7e04f8cSWim Van Sebroeck {
134b7e04f8cSWim Van Sebroeck /* check for a magic close character */
1359b748ed0SAlan Cox if (len) {
136b7e04f8cSWim Van Sebroeck size_t i;
137b7e04f8cSWim Van Sebroeck
138b7e04f8cSWim Van Sebroeck scx200_wdt_ping();
139b7e04f8cSWim Van Sebroeck
140b7e04f8cSWim Van Sebroeck expect_close = 0;
141b7e04f8cSWim Van Sebroeck for (i = 0; i < len; ++i) {
142b7e04f8cSWim Van Sebroeck char c;
143b7e04f8cSWim Van Sebroeck if (get_user(c, data + i))
144b7e04f8cSWim Van Sebroeck return -EFAULT;
145b7e04f8cSWim Van Sebroeck if (c == 'V')
146b7e04f8cSWim Van Sebroeck expect_close = 42;
147b7e04f8cSWim Van Sebroeck }
148b7e04f8cSWim Van Sebroeck
149b7e04f8cSWim Van Sebroeck return len;
150b7e04f8cSWim Van Sebroeck }
151b7e04f8cSWim Van Sebroeck
152b7e04f8cSWim Van Sebroeck return 0;
153b7e04f8cSWim Van Sebroeck }
154b7e04f8cSWim Van Sebroeck
scx200_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)1559b748ed0SAlan Cox static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
1569b748ed0SAlan Cox unsigned long arg)
157b7e04f8cSWim Van Sebroeck {
158b7e04f8cSWim Van Sebroeck void __user *argp = (void __user *)arg;
159b7e04f8cSWim Van Sebroeck int __user *p = argp;
1609b748ed0SAlan Cox static const struct watchdog_info ident = {
161b7e04f8cSWim Van Sebroeck .identity = "NatSemi SCx200 Watchdog",
162b7e04f8cSWim Van Sebroeck .firmware_version = 1,
163e73a7802SWim Van Sebroeck .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
164e73a7802SWim Van Sebroeck WDIOF_MAGICCLOSE,
165b7e04f8cSWim Van Sebroeck };
166b7e04f8cSWim Van Sebroeck int new_margin;
167b7e04f8cSWim Van Sebroeck
168b7e04f8cSWim Van Sebroeck switch (cmd) {
169b7e04f8cSWim Van Sebroeck case WDIOC_GETSUPPORT:
170b7e04f8cSWim Van Sebroeck if (copy_to_user(argp, &ident, sizeof(ident)))
171b7e04f8cSWim Van Sebroeck return -EFAULT;
172b7e04f8cSWim Van Sebroeck return 0;
173b7e04f8cSWim Van Sebroeck case WDIOC_GETSTATUS:
174b7e04f8cSWim Van Sebroeck case WDIOC_GETBOOTSTATUS:
175b7e04f8cSWim Van Sebroeck if (put_user(0, p))
176b7e04f8cSWim Van Sebroeck return -EFAULT;
177b7e04f8cSWim Van Sebroeck return 0;
178b7e04f8cSWim Van Sebroeck case WDIOC_KEEPALIVE:
179b7e04f8cSWim Van Sebroeck scx200_wdt_ping();
180b7e04f8cSWim Van Sebroeck return 0;
181b7e04f8cSWim Van Sebroeck case WDIOC_SETTIMEOUT:
182b7e04f8cSWim Van Sebroeck if (get_user(new_margin, p))
183b7e04f8cSWim Van Sebroeck return -EFAULT;
184b7e04f8cSWim Van Sebroeck if (new_margin < 1)
185b7e04f8cSWim Van Sebroeck return -EINVAL;
186b7e04f8cSWim Van Sebroeck margin = new_margin;
187b7e04f8cSWim Van Sebroeck scx200_wdt_update_margin();
188b7e04f8cSWim Van Sebroeck scx200_wdt_ping();
189*55a1b87eSGustavo A. R. Silva fallthrough;
190b7e04f8cSWim Van Sebroeck case WDIOC_GETTIMEOUT:
191b7e04f8cSWim Van Sebroeck if (put_user(margin, p))
192b7e04f8cSWim Van Sebroeck return -EFAULT;
193b7e04f8cSWim Van Sebroeck return 0;
1940c06090cSWim Van Sebroeck default:
1950c06090cSWim Van Sebroeck return -ENOTTY;
196b7e04f8cSWim Van Sebroeck }
197b7e04f8cSWim Van Sebroeck }
198b7e04f8cSWim Van Sebroeck
199b7e04f8cSWim Van Sebroeck static const struct file_operations scx200_wdt_fops = {
200b7e04f8cSWim Van Sebroeck .owner = THIS_MODULE,
201b7e04f8cSWim Van Sebroeck .llseek = no_llseek,
202b7e04f8cSWim Van Sebroeck .write = scx200_wdt_write,
2039b748ed0SAlan Cox .unlocked_ioctl = scx200_wdt_ioctl,
204b6dfb247SArnd Bergmann .compat_ioctl = compat_ptr_ioctl,
205b7e04f8cSWim Van Sebroeck .open = scx200_wdt_open,
206b7e04f8cSWim Van Sebroeck .release = scx200_wdt_release,
207b7e04f8cSWim Van Sebroeck };
208b7e04f8cSWim Van Sebroeck
209b7e04f8cSWim Van Sebroeck static struct miscdevice scx200_wdt_miscdev = {
210b7e04f8cSWim Van Sebroeck .minor = WATCHDOG_MINOR,
211b7e04f8cSWim Van Sebroeck .name = "watchdog",
212b7e04f8cSWim Van Sebroeck .fops = &scx200_wdt_fops,
213b7e04f8cSWim Van Sebroeck };
214b7e04f8cSWim Van Sebroeck
scx200_wdt_init(void)215b7e04f8cSWim Van Sebroeck static int __init scx200_wdt_init(void)
216b7e04f8cSWim Van Sebroeck {
217b7e04f8cSWim Van Sebroeck int r;
218b7e04f8cSWim Van Sebroeck
21927c766aaSJoe Perches pr_debug("NatSemi SCx200 Watchdog Driver\n");
220b7e04f8cSWim Van Sebroeck
221b7e04f8cSWim Van Sebroeck /* check that we have found the configuration block */
222b7e04f8cSWim Van Sebroeck if (!scx200_cb_present())
223b7e04f8cSWim Van Sebroeck return -ENODEV;
224b7e04f8cSWim Van Sebroeck
225b7e04f8cSWim Van Sebroeck if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
226b7e04f8cSWim Van Sebroeck SCx200_WDT_SIZE,
227b7e04f8cSWim Van Sebroeck "NatSemi SCx200 Watchdog")) {
22827c766aaSJoe Perches pr_warn("watchdog I/O region busy\n");
229b7e04f8cSWim Van Sebroeck return -EBUSY;
230b7e04f8cSWim Van Sebroeck }
231b7e04f8cSWim Van Sebroeck
232b7e04f8cSWim Van Sebroeck scx200_wdt_update_margin();
233b7e04f8cSWim Van Sebroeck scx200_wdt_disable();
234b7e04f8cSWim Van Sebroeck
235c6cb13aeSWim Van Sebroeck r = register_reboot_notifier(&scx200_wdt_notifier);
236b7e04f8cSWim Van Sebroeck if (r) {
23727c766aaSJoe Perches pr_err("unable to register reboot notifier\n");
238b7e04f8cSWim Van Sebroeck release_region(scx200_cb_base + SCx200_WDT_OFFSET,
239b7e04f8cSWim Van Sebroeck SCx200_WDT_SIZE);
240b7e04f8cSWim Van Sebroeck return r;
241b7e04f8cSWim Van Sebroeck }
242b7e04f8cSWim Van Sebroeck
243c6cb13aeSWim Van Sebroeck r = misc_register(&scx200_wdt_miscdev);
244b7e04f8cSWim Van Sebroeck if (r) {
245c6cb13aeSWim Van Sebroeck unregister_reboot_notifier(&scx200_wdt_notifier);
246b7e04f8cSWim Van Sebroeck release_region(scx200_cb_base + SCx200_WDT_OFFSET,
247b7e04f8cSWim Van Sebroeck SCx200_WDT_SIZE);
248b7e04f8cSWim Van Sebroeck return r;
249b7e04f8cSWim Van Sebroeck }
250b7e04f8cSWim Van Sebroeck
251b7e04f8cSWim Van Sebroeck return 0;
252b7e04f8cSWim Van Sebroeck }
253b7e04f8cSWim Van Sebroeck
scx200_wdt_cleanup(void)254b7e04f8cSWim Van Sebroeck static void __exit scx200_wdt_cleanup(void)
255b7e04f8cSWim Van Sebroeck {
256b7e04f8cSWim Van Sebroeck misc_deregister(&scx200_wdt_miscdev);
257c6cb13aeSWim Van Sebroeck unregister_reboot_notifier(&scx200_wdt_notifier);
258b7e04f8cSWim Van Sebroeck release_region(scx200_cb_base + SCx200_WDT_OFFSET,
259b7e04f8cSWim Van Sebroeck SCx200_WDT_SIZE);
260b7e04f8cSWim Van Sebroeck }
261b7e04f8cSWim Van Sebroeck
262b7e04f8cSWim Van Sebroeck module_init(scx200_wdt_init);
263b7e04f8cSWim Van Sebroeck module_exit(scx200_wdt_cleanup);
264