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