Merge tag '3.15-fixes' of git://neil.brown.name/md
[firefly-linux-kernel-4.4.55.git] / drivers / watchdog / cpu5wdt.c
1 /*
2  * sma cpu5 watchdog driver
3  *
4  * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  */
21
22 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
23
24 #include <linux/module.h>
25 #include <linux/moduleparam.h>
26 #include <linux/types.h>
27 #include <linux/errno.h>
28 #include <linux/miscdevice.h>
29 #include <linux/fs.h>
30 #include <linux/ioport.h>
31 #include <linux/timer.h>
32 #include <linux/completion.h>
33 #include <linux/jiffies.h>
34 #include <linux/io.h>
35 #include <linux/uaccess.h>
36 #include <linux/watchdog.h>
37
38 /* adjustable parameters */
39
40 static int verbose;
41 static int port = 0x91;
42 static int ticks = 10000;
43 static DEFINE_SPINLOCK(cpu5wdt_lock);
44
45 #define PFX                     "cpu5wdt: "
46
47 #define CPU5WDT_EXTENT          0x0A
48
49 #define CPU5WDT_STATUS_REG      0x00
50 #define CPU5WDT_TIME_A_REG      0x02
51 #define CPU5WDT_TIME_B_REG      0x03
52 #define CPU5WDT_MODE_REG        0x04
53 #define CPU5WDT_TRIGGER_REG     0x07
54 #define CPU5WDT_ENABLE_REG      0x08
55 #define CPU5WDT_RESET_REG       0x09
56
57 #define CPU5WDT_INTERVAL        (HZ/10+1)
58
59 /* some device data */
60
61 static struct {
62         struct completion stop;
63         int running;
64         struct timer_list timer;
65         int queue;
66         int default_ticks;
67         unsigned long inuse;
68 } cpu5wdt_device;
69
70 /* generic helper functions */
71
72 static void cpu5wdt_trigger(unsigned long unused)
73 {
74         if (verbose > 2)
75                 pr_debug("trigger at %i ticks\n", ticks);
76
77         if (cpu5wdt_device.running)
78                 ticks--;
79
80         spin_lock(&cpu5wdt_lock);
81         /* keep watchdog alive */
82         outb(1, port + CPU5WDT_TRIGGER_REG);
83
84         /* requeue?? */
85         if (cpu5wdt_device.queue && ticks)
86                 mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);
87         else {
88                 /* ticks doesn't matter anyway */
89                 complete(&cpu5wdt_device.stop);
90         }
91         spin_unlock(&cpu5wdt_lock);
92
93 }
94
95 static void cpu5wdt_reset(void)
96 {
97         ticks = cpu5wdt_device.default_ticks;
98
99         if (verbose)
100                 pr_debug("reset (%i ticks)\n", (int) ticks);
101
102 }
103
104 static void cpu5wdt_start(void)
105 {
106         unsigned long flags;
107
108         spin_lock_irqsave(&cpu5wdt_lock, flags);
109         if (!cpu5wdt_device.queue) {
110                 cpu5wdt_device.queue = 1;
111                 outb(0, port + CPU5WDT_TIME_A_REG);
112                 outb(0, port + CPU5WDT_TIME_B_REG);
113                 outb(1, port + CPU5WDT_MODE_REG);
114                 outb(0, port + CPU5WDT_RESET_REG);
115                 outb(0, port + CPU5WDT_ENABLE_REG);
116                 mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);
117         }
118         /* if process dies, counter is not decremented */
119         cpu5wdt_device.running++;
120         spin_unlock_irqrestore(&cpu5wdt_lock, flags);
121 }
122
123 static int cpu5wdt_stop(void)
124 {
125         unsigned long flags;
126
127         spin_lock_irqsave(&cpu5wdt_lock, flags);
128         if (cpu5wdt_device.running)
129                 cpu5wdt_device.running = 0;
130         ticks = cpu5wdt_device.default_ticks;
131         spin_unlock_irqrestore(&cpu5wdt_lock, flags);
132         if (verbose)
133                 pr_crit("stop not possible\n");
134         return -EIO;
135 }
136
137 /* filesystem operations */
138
139 static int cpu5wdt_open(struct inode *inode, struct file *file)
140 {
141         if (test_and_set_bit(0, &cpu5wdt_device.inuse))
142                 return -EBUSY;
143         return nonseekable_open(inode, file);
144 }
145
146 static int cpu5wdt_release(struct inode *inode, struct file *file)
147 {
148         clear_bit(0, &cpu5wdt_device.inuse);
149         return 0;
150 }
151
152 static long cpu5wdt_ioctl(struct file *file, unsigned int cmd,
153                                                 unsigned long arg)
154 {
155         void __user *argp = (void __user *)arg;
156         int __user *p = argp;
157         unsigned int value;
158         static const struct watchdog_info ident = {
159                 .options = WDIOF_CARDRESET,
160                 .identity = "CPU5 WDT",
161         };
162
163         switch (cmd) {
164         case WDIOC_GETSUPPORT:
165                 if (copy_to_user(argp, &ident, sizeof(ident)))
166                         return -EFAULT;
167                 break;
168         case WDIOC_GETSTATUS:
169                 value = inb(port + CPU5WDT_STATUS_REG);
170                 value = (value >> 2) & 1;
171                 return put_user(value, p);
172         case WDIOC_GETBOOTSTATUS:
173                 return put_user(0, p);
174         case WDIOC_SETOPTIONS:
175                 if (get_user(value, p))
176                         return -EFAULT;
177                 if (value & WDIOS_ENABLECARD)
178                         cpu5wdt_start();
179                 if (value & WDIOS_DISABLECARD)
180                         cpu5wdt_stop();
181                 break;
182         case WDIOC_KEEPALIVE:
183                 cpu5wdt_reset();
184                 break;
185         default:
186                 return -ENOTTY;
187         }
188         return 0;
189 }
190
191 static ssize_t cpu5wdt_write(struct file *file, const char __user *buf,
192                                                 size_t count, loff_t *ppos)
193 {
194         if (!count)
195                 return -EIO;
196         cpu5wdt_reset();
197         return count;
198 }
199
200 static const struct file_operations cpu5wdt_fops = {
201         .owner          = THIS_MODULE,
202         .llseek         = no_llseek,
203         .unlocked_ioctl = cpu5wdt_ioctl,
204         .open           = cpu5wdt_open,
205         .write          = cpu5wdt_write,
206         .release        = cpu5wdt_release,
207 };
208
209 static struct miscdevice cpu5wdt_misc = {
210         .minor  = WATCHDOG_MINOR,
211         .name   = "watchdog",
212         .fops   = &cpu5wdt_fops,
213 };
214
215 /* init/exit function */
216
217 static int cpu5wdt_init(void)
218 {
219         unsigned int val;
220         int err;
221
222         if (verbose)
223                 pr_debug("port=0x%x, verbose=%i\n", port, verbose);
224
225         init_completion(&cpu5wdt_device.stop);
226         cpu5wdt_device.queue = 0;
227         setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0);
228         cpu5wdt_device.default_ticks = ticks;
229
230         if (!request_region(port, CPU5WDT_EXTENT, PFX)) {
231                 pr_err("request_region failed\n");
232                 err = -EBUSY;
233                 goto no_port;
234         }
235
236         /* watchdog reboot? */
237         val = inb(port + CPU5WDT_STATUS_REG);
238         val = (val >> 2) & 1;
239         if (!val)
240                 pr_info("sorry, was my fault\n");
241
242         err = misc_register(&cpu5wdt_misc);
243         if (err < 0) {
244                 pr_err("misc_register failed\n");
245                 goto no_misc;
246         }
247
248
249         pr_info("init success\n");
250         return 0;
251
252 no_misc:
253         release_region(port, CPU5WDT_EXTENT);
254 no_port:
255         return err;
256 }
257
258 static int cpu5wdt_init_module(void)
259 {
260         return cpu5wdt_init();
261 }
262
263 static void cpu5wdt_exit(void)
264 {
265         if (cpu5wdt_device.queue) {
266                 cpu5wdt_device.queue = 0;
267                 wait_for_completion(&cpu5wdt_device.stop);
268                 del_timer(&cpu5wdt_device.timer);
269         }
270
271         misc_deregister(&cpu5wdt_misc);
272
273         release_region(port, CPU5WDT_EXTENT);
274
275 }
276
277 static void cpu5wdt_exit_module(void)
278 {
279         cpu5wdt_exit();
280 }
281
282 /* module entry points */
283
284 module_init(cpu5wdt_init_module);
285 module_exit(cpu5wdt_exit_module);
286
287 MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>");
288 MODULE_DESCRIPTION("sma cpu5 watchdog driver");
289 MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog");
290 MODULE_LICENSE("GPL");
291
292 module_param(port, int, 0);
293 MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91");
294
295 module_param(verbose, int, 0);
296 MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
297
298 module_param(ticks, int, 0);
299 MODULE_PARM_DESC(ticks, "count down ticks, default is 10000");