1 /*
2  * RDC321x watchdog driver
3  *
4  * Copyright (C) 2007 Florian Fainelli <florian@openwrt.org>
5  *
6  * This driver is highly inspired from the cpu5_wdt driver
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  *
22  */
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/init.h>
31 #include <linux/ioport.h>
32 #include <linux/timer.h>
33 #include <linux/completion.h>
34 #include <linux/jiffies.h>
35 #include <linux/platform_device.h>
36 #include <linux/watchdog.h>
37 #include <linux/io.h>
38 #include <linux/uaccess.h>
39 
40 #include <asm/mach-rdc321x/rdc321x_defs.h>
41 
42 #define RDC_WDT_MASK	0x80000000 /* Mask */
43 #define RDC_WDT_EN	0x00800000 /* Enable bit */
44 #define RDC_WDT_WTI	0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
45 #define RDC_WDT_RST	0x00100000 /* Reset bit */
46 #define RDC_WDT_WIF	0x00040000 /* WDT IRQ Flag */
47 #define RDC_WDT_IRT	0x00000100 /* IRQ Routing table */
48 #define RDC_WDT_CNT	0x00000001 /* WDT count */
49 
50 #define RDC_CLS_TMR	0x80003844 /* Clear timer */
51 
52 #define RDC_WDT_INTERVAL	(HZ/10+1)
53 
54 static int ticks = 1000;
55 
56 /* some device data */
57 
58 static struct {
59 	struct completion stop;
60 	int running;
61 	struct timer_list timer;
62 	int queue;
63 	int default_ticks;
64 	unsigned long inuse;
65 	spinlock_t lock;
66 } rdc321x_wdt_device;
67 
68 /* generic helper functions */
69 
70 static void rdc321x_wdt_trigger(unsigned long unused)
71 {
72 	unsigned long flags;
73 
74 	if (rdc321x_wdt_device.running)
75 		ticks--;
76 
77 	/* keep watchdog alive */
78 	spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
79 	outl(RDC_WDT_EN | inl(RDC3210_CFGREG_DATA),
80 		RDC3210_CFGREG_DATA);
81 	spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
82 
83 	/* requeue?? */
84 	if (rdc321x_wdt_device.queue && ticks)
85 		mod_timer(&rdc321x_wdt_device.timer,
86 				jiffies + RDC_WDT_INTERVAL);
87 	else {
88 		/* ticks doesn't matter anyway */
89 		complete(&rdc321x_wdt_device.stop);
90 	}
91 
92 }
93 
94 static void rdc321x_wdt_reset(void)
95 {
96 	ticks = rdc321x_wdt_device.default_ticks;
97 }
98 
99 static void rdc321x_wdt_start(void)
100 {
101 	unsigned long flags;
102 
103 	if (!rdc321x_wdt_device.queue) {
104 		rdc321x_wdt_device.queue = 1;
105 
106 		/* Clear the timer */
107 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
108 		outl(RDC_CLS_TMR, RDC3210_CFGREG_ADDR);
109 
110 		/* Enable watchdog and set the timeout to 81.92 us */
111 		outl(RDC_WDT_EN | RDC_WDT_CNT, RDC3210_CFGREG_DATA);
112 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
113 
114 		mod_timer(&rdc321x_wdt_device.timer,
115 				jiffies + RDC_WDT_INTERVAL);
116 	}
117 
118 	/* if process dies, counter is not decremented */
119 	rdc321x_wdt_device.running++;
120 }
121 
122 static int rdc321x_wdt_stop(void)
123 {
124 	if (rdc321x_wdt_device.running)
125 		rdc321x_wdt_device.running = 0;
126 
127 	ticks = rdc321x_wdt_device.default_ticks;
128 
129 	return -EIO;
130 }
131 
132 /* filesystem operations */
133 static int rdc321x_wdt_open(struct inode *inode, struct file *file)
134 {
135 	if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
136 		return -EBUSY;
137 
138 	return nonseekable_open(inode, file);
139 }
140 
141 static int rdc321x_wdt_release(struct inode *inode, struct file *file)
142 {
143 	clear_bit(0, &rdc321x_wdt_device.inuse);
144 	return 0;
145 }
146 
147 static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
148 				unsigned long arg)
149 {
150 	void __user *argp = (void __user *)arg;
151 	unsigned int value;
152 	static struct watchdog_info ident = {
153 		.options = WDIOF_CARDRESET,
154 		.identity = "RDC321x WDT",
155 	};
156 	unsigned long flags;
157 
158 	switch (cmd) {
159 	case WDIOC_KEEPALIVE:
160 		rdc321x_wdt_reset();
161 		break;
162 	case WDIOC_GETSTATUS:
163 		/* Read the value from the DATA register */
164 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
165 		value = inl(RDC3210_CFGREG_DATA);
166 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
167 		if (copy_to_user(argp, &value, sizeof(int)))
168 			return -EFAULT;
169 		break;
170 	case WDIOC_GETSUPPORT:
171 		if (copy_to_user(argp, &ident, sizeof(ident)))
172 			return -EFAULT;
173 		break;
174 	case WDIOC_SETOPTIONS:
175 		if (copy_from_user(&value, argp, sizeof(int)))
176 			return -EFAULT;
177 		switch (value) {
178 		case WDIOS_ENABLECARD:
179 			rdc321x_wdt_start();
180 			break;
181 		case WDIOS_DISABLECARD:
182 			return rdc321x_wdt_stop();
183 		default:
184 			return -EINVAL;
185 		}
186 		break;
187 	default:
188 		return -ENOTTY;
189 	}
190 	return 0;
191 }
192 
193 static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
194 				size_t count, loff_t *ppos)
195 {
196 	if (!count)
197 		return -EIO;
198 
199 	rdc321x_wdt_reset();
200 
201 	return count;
202 }
203 
204 static const struct file_operations rdc321x_wdt_fops = {
205 	.owner		= THIS_MODULE,
206 	.llseek		= no_llseek,
207 	.unlocked_ioctl	= rdc321x_wdt_ioctl,
208 	.open		= rdc321x_wdt_open,
209 	.write		= rdc321x_wdt_write,
210 	.release	= rdc321x_wdt_release,
211 };
212 
213 static struct miscdevice rdc321x_wdt_misc = {
214 	.minor	= WATCHDOG_MINOR,
215 	.name	= "watchdog",
216 	.fops	= &rdc321x_wdt_fops,
217 };
218 
219 static int __devinit rdc321x_wdt_probe(struct platform_device *pdev)
220 {
221 	int err;
222 
223 	err = misc_register(&rdc321x_wdt_misc);
224 	if (err < 0) {
225 		printk(KERN_ERR PFX "watchdog misc_register failed\n");
226 		return err;
227 	}
228 
229 	spin_lock_init(&rdc321x_wdt_device.lock);
230 
231 	/* Reset the watchdog */
232 	outl(RDC_WDT_RST, RDC3210_CFGREG_DATA);
233 
234 	init_completion(&rdc321x_wdt_device.stop);
235 	rdc321x_wdt_device.queue = 0;
236 
237 	clear_bit(0, &rdc321x_wdt_device.inuse);
238 
239 	setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
240 
241 	rdc321x_wdt_device.default_ticks = ticks;
242 
243 	printk(KERN_INFO PFX "watchdog init success\n");
244 
245 	return 0;
246 }
247 
248 static int rdc321x_wdt_remove(struct platform_device *pdev)
249 {
250 	if (rdc321x_wdt_device.queue) {
251 		rdc321x_wdt_device.queue = 0;
252 		wait_for_completion(&rdc321x_wdt_device.stop);
253 	}
254 
255 	misc_deregister(&rdc321x_wdt_misc);
256 
257 	return 0;
258 }
259 
260 static struct platform_driver rdc321x_wdt_driver = {
261 	.probe = rdc321x_wdt_probe,
262 	.remove = rdc321x_wdt_remove,
263 	.driver = {
264 		.owner = THIS_MODULE,
265 		.name = "rdc321x-wdt",
266 	},
267 };
268 
269 static int __init rdc321x_wdt_init(void)
270 {
271 	return platform_driver_register(&rdc321x_wdt_driver);
272 }
273 
274 static void __exit rdc321x_wdt_exit(void)
275 {
276 	platform_driver_unregister(&rdc321x_wdt_driver);
277 }
278 
279 module_init(rdc321x_wdt_init);
280 module_exit(rdc321x_wdt_exit);
281 
282 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
283 MODULE_DESCRIPTION("RDC321x watchdog driver");
284 MODULE_LICENSE("GPL");
285 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
286