xref: /OK3568_Linux_fs/kernel/drivers/watchdog/cpu5wdt.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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