1 /* Watchdog timer for the Geode GX/LX
2  *
3  * Copyright (C) 2006, Advanced Micro Devices, Inc.
4  * Backported to 2.4 by Willy Tarreau <w@1wt.eu>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  */
11 
12 
13 #include <linux/module.h>
14 #include <linux/types.h>
15 #include <linux/miscdevice.h>
16 #include <linux/watchdog.h>
17 #include <linux/fs.h>
18 #include <linux/notifier.h>
19 #include <linux/reboot.h>
20 
21 #include <asm/uaccess.h>
22 #include <asm/geode-mfgpt.h>
23 
24 #define GEODEWDT_HZ 500
25 #define GEODEWDT_SCALE 6
26 #define GEODEWDT_MAX_SECONDS 131
27 
28 #define WDT_FLAGS_OPEN 1
29 #define WDT_FLAGS_ORPHAN 2
30 
31 /* The defaults for the other timers are 60, so we'll use that too */
32 
33 static int cur_interval = 60;
34 MODULE_PARM(cur_interval, "i");
35 MODULE_PARM_DESC(cur_interval, "Watchdog interval in seconds. 1<= interval <=131, default=60.");
36 
37 static int wdt_timer;
38 static unsigned long wdt_flags;
39 static int safe_close;
40 
geodewdt_ping(void)41 static void geodewdt_ping(void)
42 {
43 	//printk("PING\n");
44 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
45 	geode_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
46 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
47 }
48 
geodewdt_stop(void)49 static void geodewdt_stop(void)
50 {
51 	//printk("STOP\n");
52 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
53 	geode_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
54 }
55 
geodewdt_set_heartbeat(int val)56 static int geodewdt_set_heartbeat(int val)
57 {
58 	if (val < 1 || val > GEODEWDT_MAX_SECONDS)
59 		return -EINVAL;
60 
61 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
62 	geode_mfgpt_write(wdt_timer, MFGPT_REG_CMP2, val * GEODEWDT_HZ);
63 	geode_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
64 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
65 
66 	//printk("HEARTBEAT %d\n", val);
67 	cur_interval = val;
68 	return 0;
69 }
70 
71 static int
geodewdt_open(struct inode * inode,struct file * file)72 geodewdt_open(struct inode *inode, struct file *file)
73 {
74 	if (MINOR(inode->i_rdev) != WATCHDOG_MINOR)
75 		return -ENODEV;
76 
77         if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags))
78                 return -EBUSY;
79 
80         if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags))
81                 MOD_INC_USE_COUNT;
82 
83 	geodewdt_ping();
84         return 0;
85 }
86 
87 static int
geodewdt_release(struct inode * inode,struct file * file)88 geodewdt_release(struct inode *inode, struct file *file)
89 {
90 	if (MINOR(inode->i_rdev) != WATCHDOG_MINOR)
91 		return 0;
92 
93 	if (safe_close) {
94 		geodewdt_stop();
95 		MOD_DEC_USE_COUNT;
96 	}
97 	else {
98 		printk(KERN_CRIT "Unexpected close - watchdog is not stopping.\n");
99 		geodewdt_ping();
100 
101 		set_bit(WDT_FLAGS_ORPHAN, &wdt_flags);
102 	}
103 
104 	clear_bit(WDT_FLAGS_OPEN, &wdt_flags);
105 	safe_close = 0;
106 	return 0;
107 }
108 
109 static ssize_t
geodewdt_write(struct file * file,const char __user * data,size_t len,loff_t * ppos)110 geodewdt_write(struct file *file, const char __user *data, size_t len,
111 	       loff_t *ppos)
112 {
113         if(len) {
114 		size_t i;
115                 safe_close = 0;
116 
117                 for (i = 0; i != len; i++) {
118 			char c;
119 
120 			if (get_user(c, data + i))
121 				return -EFAULT;
122 
123 			if (c == 'V')
124 				safe_close = 1;
125 		}
126 	}
127 
128 	geodewdt_ping();
129 	return len;
130 }
131 
132 static int
geodewdt_ioctl(struct inode * inode,struct file * file,unsigned int cmd,unsigned long arg)133 geodewdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
134 	       unsigned long arg)
135 {
136 	void __user *argp = (void __user *)arg;
137 	int __user *p = argp;
138 	int interval;
139 
140 	static struct watchdog_info ident = {
141 		.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
142 		| WDIOF_MAGICCLOSE,
143 		.firmware_version =     0,
144 		.identity =             "Geode Watchdog",
145         };
146 
147 	switch(cmd) {
148 	case WDIOC_GETSUPPORT:
149 		return copy_to_user(argp, &ident,
150 				    sizeof(ident)) ? -EFAULT : 0;
151 		break;
152 
153 	case WDIOC_GETSTATUS:
154 	case WDIOC_GETBOOTSTATUS:
155 		return put_user(0, p);
156 
157 	case WDIOC_KEEPALIVE:
158 		geodewdt_ping();
159 		return 0;
160 
161 	case WDIOC_SETTIMEOUT:
162 		if (get_user(interval, p))
163 			return -EFAULT;
164 
165 		if (geodewdt_set_heartbeat(interval))
166 			return -EINVAL;
167 
168 /* Fall through */
169 
170 	case WDIOC_GETTIMEOUT:
171 		return put_user(cur_interval, p);
172 	}
173 
174 	return -ENOTTY;
175 }
176 
geodewdt_notify_sys(struct notifier_block * this,unsigned long code,void * unused)177 static int geodewdt_notify_sys(struct notifier_block *this,
178 			       unsigned long code, void *unused)
179 {
180         if(code==SYS_DOWN || code==SYS_HALT)
181 		geodewdt_stop();
182 
183         return NOTIFY_DONE;
184 }
185 
186 static struct file_operations geodewdt_fops = {
187         .owner          = THIS_MODULE,
188         .llseek         = no_llseek,
189         .write          = geodewdt_write,
190         .ioctl          = geodewdt_ioctl,
191         .open           = geodewdt_open,
192         .release        = geodewdt_release,
193 };
194 
195 static struct miscdevice geodewdt_miscdev = {
196 	.minor = WATCHDOG_MINOR,
197 	.name = "watchdog",
198 	.fops = &geodewdt_fops
199 };
200 
201 static struct notifier_block geodewdt_notifier = {
202 	.notifier_call = geodewdt_notify_sys
203 };
204 
geodewdt_init(void)205 static int __init geodewdt_init(void)
206 {
207 	int ret, timer;
208 
209 	timer = geode_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_ANY);
210 
211 	if (timer == -1) {
212 		printk(KERN_ERR "geodewdt:  No timers were available\n");
213 		return -ENODEV;
214 	}
215 
216 	wdt_timer = timer;
217 
218 	/* Set up the timer */
219 
220 	geode_mfgpt_write(wdt_timer, MFGPT_REG_SETUP,
221 			  GEODEWDT_SCALE | (3 << 8));
222 
223 	/* Set up comparator 2 to reset when the event fires */
224 	geode_mfgpt_set_event(wdt_timer, MFGPT_CMP2, MFGPT_EVENT_RESET);
225 
226 	/* Set up the initial timeout */
227 
228 	geode_mfgpt_write(wdt_timer, MFGPT_REG_CMP2,
229 		cur_interval * GEODEWDT_HZ);
230 
231 	ret = misc_register(&geodewdt_miscdev);
232 	if (ret)
233 		return ret;
234 
235 	ret = register_reboot_notifier(&geodewdt_notifier);
236 
237 	if (ret)
238 		misc_deregister(&geodewdt_miscdev);
239 
240 	return ret;
241 }
242 
243 static void __exit
geodewdt_exit(void)244 geodewdt_exit(void)
245 {
246 	misc_deregister(&geodewdt_miscdev);
247 	unregister_reboot_notifier(&geodewdt_notifier);
248 }
249 
250 module_init(geodewdt_init);
251 module_exit(geodewdt_exit);
252 
253 MODULE_AUTHOR("Advanced Micro Devices, Inc");
254 MODULE_DESCRIPTION("Geode GX/LX Watchdog Driver");
255 MODULE_LICENSE("GPL");
256