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