xref: /openbmc/linux/drivers/watchdog/ep93xx_wdt.c (revision 4800cd83)
1 /*
2  * Watchdog driver for Cirrus Logic EP93xx family of devices.
3  *
4  * Copyright (c) 2004 Ray Lehtiniemi
5  * Copyright (c) 2006 Tower Technologies
6  * Based on ep93xx driver, bits from alim7101_wdt.c
7  *
8  * Authors: Ray Lehtiniemi <rayl@mail.com>,
9  *	Alessandro Zummo <a.zummo@towertech.it>
10  *
11  * This file is licensed under the terms of the GNU General Public
12  * License version 2. This program is licensed "as is" without any
13  * warranty of any kind, whether express or implied.
14  *
15  * This watchdog fires after 250msec, which is a too short interval
16  * for us to rely on the user space daemon alone. So we ping the
17  * wdt each ~200msec and eventually stop doing it if the user space
18  * daemon dies.
19  *
20  * TODO:
21  *
22  *	- Test last reset from watchdog status
23  *	- Add a few missing ioctls
24  */
25 
26 #include <linux/module.h>
27 #include <linux/fs.h>
28 #include <linux/miscdevice.h>
29 #include <linux/watchdog.h>
30 #include <linux/timer.h>
31 #include <linux/uaccess.h>
32 #include <linux/io.h>
33 #include <mach/hardware.h>
34 
35 #define WDT_VERSION	"0.3"
36 #define PFX		"ep93xx_wdt: "
37 
38 /* default timeout (secs) */
39 #define WDT_TIMEOUT 30
40 
41 static int nowayout = WATCHDOG_NOWAYOUT;
42 static int timeout = WDT_TIMEOUT;
43 
44 static struct timer_list timer;
45 static unsigned long next_heartbeat;
46 static unsigned long wdt_status;
47 static unsigned long boot_status;
48 
49 #define WDT_IN_USE		0
50 #define WDT_OK_TO_CLOSE		1
51 
52 #define EP93XX_WDT_REG(x)	(EP93XX_WATCHDOG_BASE + (x))
53 #define EP93XX_WDT_WATCHDOG	EP93XX_WDT_REG(0x00)
54 #define EP93XX_WDT_WDSTATUS	EP93XX_WDT_REG(0x04)
55 
56 /* reset the wdt every ~200ms */
57 #define WDT_INTERVAL (HZ/5)
58 
59 static void wdt_enable(void)
60 {
61 	__raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG);
62 }
63 
64 static void wdt_disable(void)
65 {
66 	__raw_writew(0xaa55, EP93XX_WDT_WATCHDOG);
67 }
68 
69 static inline void wdt_ping(void)
70 {
71 	__raw_writew(0x5555, EP93XX_WDT_WATCHDOG);
72 }
73 
74 static void wdt_startup(void)
75 {
76 	next_heartbeat = jiffies + (timeout * HZ);
77 
78 	wdt_enable();
79 	mod_timer(&timer, jiffies + WDT_INTERVAL);
80 }
81 
82 static void wdt_shutdown(void)
83 {
84 	del_timer_sync(&timer);
85 	wdt_disable();
86 }
87 
88 static void wdt_keepalive(void)
89 {
90 	/* user land ping */
91 	next_heartbeat = jiffies + (timeout * HZ);
92 }
93 
94 static int ep93xx_wdt_open(struct inode *inode, struct file *file)
95 {
96 	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
97 		return -EBUSY;
98 
99 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
100 
101 	wdt_startup();
102 
103 	return nonseekable_open(inode, file);
104 }
105 
106 static ssize_t
107 ep93xx_wdt_write(struct file *file, const char __user *data, size_t len,
108 		 loff_t *ppos)
109 {
110 	if (len) {
111 		if (!nowayout) {
112 			size_t i;
113 
114 			clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
115 
116 			for (i = 0; i != len; i++) {
117 				char c;
118 
119 				if (get_user(c, data + i))
120 					return -EFAULT;
121 
122 				if (c == 'V')
123 					set_bit(WDT_OK_TO_CLOSE, &wdt_status);
124 				else
125 					clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
126 			}
127 		}
128 		wdt_keepalive();
129 	}
130 
131 	return len;
132 }
133 
134 static const struct watchdog_info ident = {
135 	.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE,
136 	.identity = "EP93xx Watchdog",
137 };
138 
139 static long ep93xx_wdt_ioctl(struct file *file,
140 					unsigned int cmd, unsigned long arg)
141 {
142 	int ret = -ENOTTY;
143 
144 	switch (cmd) {
145 	case WDIOC_GETSUPPORT:
146 		ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
147 				sizeof(ident)) ? -EFAULT : 0;
148 		break;
149 
150 	case WDIOC_GETSTATUS:
151 		ret = put_user(0, (int __user *)arg);
152 		break;
153 
154 	case WDIOC_GETBOOTSTATUS:
155 		ret = put_user(boot_status, (int __user *)arg);
156 		break;
157 
158 	case WDIOC_KEEPALIVE:
159 		wdt_keepalive();
160 		ret = 0;
161 		break;
162 
163 	case WDIOC_GETTIMEOUT:
164 		/* actually, it is 0.250 seconds.... */
165 		ret = put_user(1, (int __user *)arg);
166 		break;
167 	}
168 	return ret;
169 }
170 
171 static int ep93xx_wdt_release(struct inode *inode, struct file *file)
172 {
173 	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
174 		wdt_shutdown();
175 	else
176 		printk(KERN_CRIT PFX
177 			"Device closed unexpectedly - timer will not stop\n");
178 
179 	clear_bit(WDT_IN_USE, &wdt_status);
180 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
181 
182 	return 0;
183 }
184 
185 static const struct file_operations ep93xx_wdt_fops = {
186 	.owner		= THIS_MODULE,
187 	.write		= ep93xx_wdt_write,
188 	.unlocked_ioctl	= ep93xx_wdt_ioctl,
189 	.open		= ep93xx_wdt_open,
190 	.release	= ep93xx_wdt_release,
191 	.llseek		= no_llseek,
192 };
193 
194 static struct miscdevice ep93xx_wdt_miscdev = {
195 	.minor		= WATCHDOG_MINOR,
196 	.name		= "watchdog",
197 	.fops		= &ep93xx_wdt_fops,
198 };
199 
200 static void ep93xx_timer_ping(unsigned long data)
201 {
202 	if (time_before(jiffies, next_heartbeat))
203 		wdt_ping();
204 
205 	/* Re-set the timer interval */
206 	mod_timer(&timer, jiffies + WDT_INTERVAL);
207 }
208 
209 static int __init ep93xx_wdt_init(void)
210 {
211 	int err;
212 
213 	err = misc_register(&ep93xx_wdt_miscdev);
214 
215 	boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0;
216 
217 	printk(KERN_INFO PFX "EP93XX watchdog, driver version "
218 		WDT_VERSION "%s\n",
219 		(__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08)
220 		? " (nCS1 disable detected)" : "");
221 
222 	if (timeout < 1 || timeout > 3600) {
223 		timeout = WDT_TIMEOUT;
224 		printk(KERN_INFO PFX
225 			"timeout value must be 1<=x<=3600, using %d\n",
226 			timeout);
227 	}
228 
229 	setup_timer(&timer, ep93xx_timer_ping, 1);
230 	return err;
231 }
232 
233 static void __exit ep93xx_wdt_exit(void)
234 {
235 	wdt_shutdown();
236 	misc_deregister(&ep93xx_wdt_miscdev);
237 }
238 
239 module_init(ep93xx_wdt_init);
240 module_exit(ep93xx_wdt_exit);
241 
242 module_param(nowayout, int, 0);
243 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
244 
245 module_param(timeout, int, 0);
246 MODULE_PARM_DESC(timeout,
247 	"Watchdog timeout in seconds. (1<=timeout<=3600, default="
248 				__MODULE_STRING(WDT_TIMEOUT) ")");
249 
250 MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>,"
251 		"Alessandro Zummo <a.zummo@towertech.it>");
252 MODULE_DESCRIPTION("EP93xx Watchdog");
253 MODULE_LICENSE("GPL");
254 MODULE_VERSION(WDT_VERSION);
255 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
256