ASoC: cros_ec_codec: refactor I2S RX
[linux-2.6-block.git] / drivers / watchdog / scx200_wdt.c
CommitLineData
2874c5fd 1// SPDX-License-Identifier: GPL-2.0-or-later
1da177e4
LT
2/* 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
1da177e4
LT
12
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
27c766aa
JP
17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
1da177e4
LT
19#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>
6473d160 27#include <linux/ioport.h>
1da177e4 28#include <linux/scx200.h>
9b748ed0
AC
29#include <linux/uaccess.h>
30#include <linux/io.h>
1da177e4 31
27c766aa 32#define DEBUG
1da177e4
LT
33
34MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
35MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
36MODULE_LICENSE("GPL");
1da177e4 37
1da177e4
LT
38static int margin = 60; /* in seconds */
39module_param(margin, int, 0);
40MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
41
86a1e189
WVS
42static bool nowayout = WATCHDOG_NOWAYOUT;
43module_param(nowayout, bool, 0);
1da177e4
LT
44MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
45
46static u16 wdto_restart;
1da177e4 47static char expect_close;
9b748ed0
AC
48static unsigned long open_lock;
49static DEFINE_SPINLOCK(scx_lock);
1da177e4
LT
50
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{
9b748ed0 60 spin_lock(&scx_lock);
1da177e4 61 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
9b748ed0 62 spin_unlock(&scx_lock);
1da177e4
LT
63}
64
65static void scx200_wdt_update_margin(void)
66{
27c766aa 67 pr_info("timer margin %d seconds\n", margin);
1da177e4
LT
68 wdto_restart = margin * W_SCALE;
69}
70
71static void scx200_wdt_enable(void)
72{
27c766aa 73 pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
1da177e4 74
9b748ed0 75 spin_lock(&scx_lock);
1da177e4
LT
76 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);
9b748ed0 79 spin_unlock(&scx_lock);
1da177e4
LT
80
81 scx200_wdt_ping();
82}
83
84static void scx200_wdt_disable(void)
85{
27c766aa 86 pr_debug("disabling watchdog timer\n");
1da177e4 87
9b748ed0 88 spin_lock(&scx_lock);
1da177e4
LT
89 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);
9b748ed0 92 spin_unlock(&scx_lock);
1da177e4
LT
93}
94
95static int scx200_wdt_open(struct inode *inode, struct file *file)
96{
97 /* only allow one at a time */
9b748ed0 98 if (test_and_set_bit(0, &open_lock))
1da177e4
LT
99 return -EBUSY;
100 scx200_wdt_enable();
101
c5bf68fe 102 return stream_open(inode, file);
1da177e4
LT
103}
104
105static int scx200_wdt_release(struct inode *inode, struct file *file)
106{
9b748ed0 107 if (expect_close != 42)
27c766aa 108 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
9b748ed0 109 else if (!nowayout)
1da177e4 110 scx200_wdt_disable();
1da177e4 111 expect_close = 0;
9b748ed0 112 clear_bit(0, &open_lock);
1da177e4
LT
113
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
9b748ed0 127static struct notifier_block scx200_wdt_notifier = {
1da177e4
LT
128 .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 */
9b748ed0 135 if (len) {
1da177e4
LT
136 size_t i;
137
138 scx200_wdt_ping();
139
140 expect_close = 0;
141 for (i = 0; i < len; ++i) {
142 char c;
7944d3a5 143 if (get_user(c, data + i))
1da177e4
LT
144 return -EFAULT;
145 if (c == 'V')
146 expect_close = 42;
147 }
148
149 return len;
150 }
151
152 return 0;
153}
154
9b748ed0
AC
155static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
156 unsigned long arg)
1da177e4
LT
157{
158 void __user *argp = (void __user *)arg;
159 int __user *p = argp;
9b748ed0 160 static const struct watchdog_info ident = {
1da177e4
LT
161 .identity = "NatSemi SCx200 Watchdog",
162 .firmware_version = 1,
e73a7802
WVS
163 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
164 WDIOF_MAGICCLOSE,
1da177e4
LT
165 };
166 int new_margin;
167
168 switch (cmd) {
1da177e4 169 case WDIOC_GETSUPPORT:
9b748ed0 170 if (copy_to_user(argp, &ident, sizeof(ident)))
1da177e4
LT
171 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();
24f21618 189 /* Fall through */
1da177e4
LT
190 case WDIOC_GETTIMEOUT:
191 if (put_user(margin, p))
192 return -EFAULT;
193 return 0;
0c06090c
WVS
194 default:
195 return -ENOTTY;
1da177e4
LT
196 }
197}
198
62322d25 199static const struct file_operations scx200_wdt_fops = {
9b748ed0
AC
200 .owner = THIS_MODULE,
201 .llseek = no_llseek,
202 .write = scx200_wdt_write,
203 .unlocked_ioctl = scx200_wdt_ioctl,
204 .open = scx200_wdt_open,
1da177e4
LT
205 .release = scx200_wdt_release,
206};
207
208static struct miscdevice scx200_wdt_miscdev = {
209 .minor = WATCHDOG_MINOR,
9b748ed0
AC
210 .name = "watchdog",
211 .fops = &scx200_wdt_fops,
1da177e4
LT
212};
213
214static int __init scx200_wdt_init(void)
215{
216 int r;
217
27c766aa 218 pr_debug("NatSemi SCx200 Watchdog Driver\n");
1da177e4
LT
219
220 /* check that we have found the configuration block */
221 if (!scx200_cb_present())
222 return -ENODEV;
223
224 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
225 SCx200_WDT_SIZE,
226 "NatSemi SCx200 Watchdog")) {
27c766aa 227 pr_warn("watchdog I/O region busy\n");
1da177e4
LT
228 return -EBUSY;
229 }
230
231 scx200_wdt_update_margin();
232 scx200_wdt_disable();
233
c6cb13ae 234 r = register_reboot_notifier(&scx200_wdt_notifier);
1da177e4 235 if (r) {
27c766aa 236 pr_err("unable to register reboot notifier\n");
1da177e4
LT
237 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
238 SCx200_WDT_SIZE);
239 return r;
240 }
241
c6cb13ae 242 r = misc_register(&scx200_wdt_miscdev);
1da177e4 243 if (r) {
c6cb13ae 244 unregister_reboot_notifier(&scx200_wdt_notifier);
1da177e4
LT
245 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
246 SCx200_WDT_SIZE);
247 return r;
248 }
249
250 return 0;
251}
252
253static void __exit scx200_wdt_cleanup(void)
254{
1da177e4 255 misc_deregister(&scx200_wdt_miscdev);
c6cb13ae 256 unregister_reboot_notifier(&scx200_wdt_notifier);
1da177e4
LT
257 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
258 SCx200_WDT_SIZE);
259}
260
261module_init(scx200_wdt_init);
262module_exit(scx200_wdt_cleanup);