blob: 7b5e18323f3f530a67133334f1355dfd5b24056a [file] [log] [blame]
Thomas Gleixner2874c5f2019-05-27 08:55:01 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Linus Torvalds1da177e2005-04-16 15:20:36 -07002/* drivers/char/watchdog/scx200_wdt.c
3
4 National Semiconductor SCx200 Watchdog support
5
6 Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
7
8 Some code taken from:
9 National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
10 (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
11
Linus Torvalds1da177e2005-04-16 15:20:36 -070012
13 The author(s) of this software shall not be held liable for damages
14 of any nature resulting due to the use of this software. This
15 software is provided AS-IS with no warranties. */
16
Joe Perches27c766a2012-02-15 15:06:19 -080017#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
Linus Torvalds1da177e2005-04-16 15:20:36 -070019#include <linux/module.h>
20#include <linux/moduleparam.h>
21#include <linux/init.h>
22#include <linux/miscdevice.h>
23#include <linux/watchdog.h>
24#include <linux/notifier.h>
25#include <linux/reboot.h>
26#include <linux/fs.h>
Jean Delvare6473d162007-03-06 02:45:12 -080027#include <linux/ioport.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070028#include <linux/scx200.h>
Alan Cox9b748ed2008-05-19 14:08:49 +010029#include <linux/uaccess.h>
30#include <linux/io.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070031
Joe Perches27c766a2012-02-15 15:06:19 -080032#define DEBUG
Linus Torvalds1da177e2005-04-16 15:20:36 -070033
34MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
35MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
36MODULE_LICENSE("GPL");
Linus Torvalds1da177e2005-04-16 15:20:36 -070037
Linus Torvalds1da177e2005-04-16 15:20:36 -070038static int margin = 60; /* in seconds */
39module_param(margin, int, 0);
40MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
41
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010042static bool nowayout = WATCHDOG_NOWAYOUT;
43module_param(nowayout, bool, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -070044MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
45
46static u16 wdto_restart;
Linus Torvalds1da177e2005-04-16 15:20:36 -070047static char expect_close;
Alan Cox9b748ed2008-05-19 14:08:49 +010048static unsigned long open_lock;
49static DEFINE_SPINLOCK(scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070050
51/* Bits of the WDCNFG register */
52#define W_ENABLE 0x00fa /* Enable watchdog */
53#define W_DISABLE 0x0000 /* Disable watchdog */
54
55/* The scaling factor for the timer, this depends on the value of W_ENABLE */
56#define W_SCALE (32768/1024)
57
58static void scx200_wdt_ping(void)
59{
Alan Cox9b748ed2008-05-19 14:08:49 +010060 spin_lock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070061 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
Alan Cox9b748ed2008-05-19 14:08:49 +010062 spin_unlock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070063}
64
65static void scx200_wdt_update_margin(void)
66{
Joe Perches27c766a2012-02-15 15:06:19 -080067 pr_info("timer margin %d seconds\n", margin);
Linus Torvalds1da177e2005-04-16 15:20:36 -070068 wdto_restart = margin * W_SCALE;
69}
70
71static void scx200_wdt_enable(void)
72{
Joe Perches27c766a2012-02-15 15:06:19 -080073 pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
Linus Torvalds1da177e2005-04-16 15:20:36 -070074
Alan Cox9b748ed2008-05-19 14:08:49 +010075 spin_lock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070076 outw(0, scx200_cb_base + SCx200_WDT_WDTO);
77 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
78 outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
Alan Cox9b748ed2008-05-19 14:08:49 +010079 spin_unlock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070080
81 scx200_wdt_ping();
82}
83
84static void scx200_wdt_disable(void)
85{
Joe Perches27c766a2012-02-15 15:06:19 -080086 pr_debug("disabling watchdog timer\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -070087
Alan Cox9b748ed2008-05-19 14:08:49 +010088 spin_lock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070089 outw(0, scx200_cb_base + SCx200_WDT_WDTO);
90 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
91 outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
Alan Cox9b748ed2008-05-19 14:08:49 +010092 spin_unlock(&scx_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070093}
94
95static int scx200_wdt_open(struct inode *inode, struct file *file)
96{
97 /* only allow one at a time */
Alan Cox9b748ed2008-05-19 14:08:49 +010098 if (test_and_set_bit(0, &open_lock))
Linus Torvalds1da177e2005-04-16 15:20:36 -070099 return -EBUSY;
100 scx200_wdt_enable();
101
Kirill Smelkovc5bf68f2019-03-26 23:51:19 +0300102 return stream_open(inode, file);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700103}
104
105static int scx200_wdt_release(struct inode *inode, struct file *file)
106{
Alan Cox9b748ed2008-05-19 14:08:49 +0100107 if (expect_close != 42)
Joe Perches27c766a2012-02-15 15:06:19 -0800108 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
Alan Cox9b748ed2008-05-19 14:08:49 +0100109 else if (!nowayout)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700110 scx200_wdt_disable();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700111 expect_close = 0;
Alan Cox9b748ed2008-05-19 14:08:49 +0100112 clear_bit(0, &open_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700113
114 return 0;
115}
116
117static int scx200_wdt_notify_sys(struct notifier_block *this,
118 unsigned long code, void *unused)
119{
120 if (code == SYS_HALT || code == SYS_POWER_OFF)
121 if (!nowayout)
122 scx200_wdt_disable();
123
124 return NOTIFY_DONE;
125}
126
Alan Cox9b748ed2008-05-19 14:08:49 +0100127static struct notifier_block scx200_wdt_notifier = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700128 .notifier_call = scx200_wdt_notify_sys,
129};
130
131static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
132 size_t len, loff_t *ppos)
133{
134 /* check for a magic close character */
Alan Cox9b748ed2008-05-19 14:08:49 +0100135 if (len) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700136 size_t i;
137
138 scx200_wdt_ping();
139
140 expect_close = 0;
141 for (i = 0; i < len; ++i) {
142 char c;
Wim Van Sebroeck7944d3a2008-08-06 20:19:41 +0000143 if (get_user(c, data + i))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700144 return -EFAULT;
145 if (c == 'V')
146 expect_close = 42;
147 }
148
149 return len;
150 }
151
152 return 0;
153}
154
Alan Cox9b748ed2008-05-19 14:08:49 +0100155static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
156 unsigned long arg)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700157{
158 void __user *argp = (void __user *)arg;
159 int __user *p = argp;
Alan Cox9b748ed2008-05-19 14:08:49 +0100160 static const struct watchdog_info ident = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700161 .identity = "NatSemi SCx200 Watchdog",
162 .firmware_version = 1,
Wim Van Sebroecke73a7802009-05-11 18:33:00 +0000163 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
164 WDIOF_MAGICCLOSE,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700165 };
166 int new_margin;
167
168 switch (cmd) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169 case WDIOC_GETSUPPORT:
Alan Cox9b748ed2008-05-19 14:08:49 +0100170 if (copy_to_user(argp, &ident, sizeof(ident)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700171 return -EFAULT;
172 return 0;
173 case WDIOC_GETSTATUS:
174 case WDIOC_GETBOOTSTATUS:
175 if (put_user(0, p))
176 return -EFAULT;
177 return 0;
178 case WDIOC_KEEPALIVE:
179 scx200_wdt_ping();
180 return 0;
181 case WDIOC_SETTIMEOUT:
182 if (get_user(new_margin, p))
183 return -EFAULT;
184 if (new_margin < 1)
185 return -EINVAL;
186 margin = new_margin;
187 scx200_wdt_update_margin();
188 scx200_wdt_ping();
Gustavo A. R. Silva55a1b872020-07-17 11:40:59 -0500189 fallthrough;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190 case WDIOC_GETTIMEOUT:
191 if (put_user(margin, p))
192 return -EFAULT;
193 return 0;
Wim Van Sebroeck0c060902008-07-18 11:41:17 +0000194 default:
195 return -ENOTTY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700196 }
197}
198
Arjan van de Ven62322d22006-07-03 00:24:21 -0700199static const struct file_operations scx200_wdt_fops = {
Alan Cox9b748ed2008-05-19 14:08:49 +0100200 .owner = THIS_MODULE,
201 .llseek = no_llseek,
202 .write = scx200_wdt_write,
203 .unlocked_ioctl = scx200_wdt_ioctl,
Arnd Bergmannb6dfb242019-06-03 14:23:09 +0200204 .compat_ioctl = compat_ptr_ioctl,
Alan Cox9b748ed2008-05-19 14:08:49 +0100205 .open = scx200_wdt_open,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206 .release = scx200_wdt_release,
207};
208
209static struct miscdevice scx200_wdt_miscdev = {
210 .minor = WATCHDOG_MINOR,
Alan Cox9b748ed2008-05-19 14:08:49 +0100211 .name = "watchdog",
212 .fops = &scx200_wdt_fops,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700213};
214
215static int __init scx200_wdt_init(void)
216{
217 int r;
218
Joe Perches27c766a2012-02-15 15:06:19 -0800219 pr_debug("NatSemi SCx200 Watchdog Driver\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700220
221 /* check that we have found the configuration block */
222 if (!scx200_cb_present())
223 return -ENODEV;
224
225 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
226 SCx200_WDT_SIZE,
227 "NatSemi SCx200 Watchdog")) {
Joe Perches27c766a2012-02-15 15:06:19 -0800228 pr_warn("watchdog I/O region busy\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 return -EBUSY;
230 }
231
232 scx200_wdt_update_margin();
233 scx200_wdt_disable();
234
Wim Van Sebroeckc6cb13a2007-12-26 20:32:51 +0000235 r = register_reboot_notifier(&scx200_wdt_notifier);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700236 if (r) {
Joe Perches27c766a2012-02-15 15:06:19 -0800237 pr_err("unable to register reboot notifier\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
239 SCx200_WDT_SIZE);
240 return r;
241 }
242
Wim Van Sebroeckc6cb13a2007-12-26 20:32:51 +0000243 r = misc_register(&scx200_wdt_miscdev);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244 if (r) {
Wim Van Sebroeckc6cb13a2007-12-26 20:32:51 +0000245 unregister_reboot_notifier(&scx200_wdt_notifier);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
247 SCx200_WDT_SIZE);
248 return r;
249 }
250
251 return 0;
252}
253
254static void __exit scx200_wdt_cleanup(void)
255{
Linus Torvalds1da177e2005-04-16 15:20:36 -0700256 misc_deregister(&scx200_wdt_miscdev);
Wim Van Sebroeckc6cb13a2007-12-26 20:32:51 +0000257 unregister_reboot_notifier(&scx200_wdt_notifier);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
259 SCx200_WDT_SIZE);
260}
261
262module_init(scx200_wdt_init);
263module_exit(scx200_wdt_cleanup);