9e8b0372d2a4e7ac9df92130835fdf976e67c6b7
[openwrt/svn-archive/archive.git] / target / linux / rdc / files-2.6.30 / drivers / watchdog / rdc321x_wdt.c
1 /*
2 * RDC321x watchdog driver
3 *
4 * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 *
20 */
21
22 #include <linux/miscdevice.h>
23 #include <linux/platform_device.h>
24 #include <linux/fs.h>
25 #include <linux/init.h>
26 #include <linux/ioport.h>
27 #include <linux/timer.h>
28 #include <linux/watchdog.h>
29 #include <linux/uaccess.h>
30 #include <linux/pci.h>
31
32 #include <asm/rdc321x_defs.h>
33
34 extern int rdc321x_pci_write(int reg, u32 val);
35 extern int rdc321x_pci_read(int reg, u32 *val);
36
37 #define RDC321X_WDT_REG 0x00000044
38
39 #define RDC_WDT_EN 0x00800000 /* Enable bit */
40 #define RDC_WDT_WDTIRQ 0x00400000 /* Create WDT IRQ before CPU reset */
41 #define RDC_WDT_NMIIRQ 0x00200000 /* Create NMI IRQ before CPU reset */
42 #define RDC_WDT_RST 0x00100000 /* Reset wdt */
43 #define RDC_WDT_NIF 0x00080000 /* NMI interrupt occured */
44 #define RDC_WDT_WIF 0x00040000 /* WDT interrupt occured */
45 #define RDC_WDT_IRT 0x00000700 /* IRQ Routing table */
46 #define RDC_WDT_CNT 0x0000007F /* WDT count */
47
48 /* default counter value (2.34 s) */
49 #define RDC_WDT_DFLT_CNT 0x00000040
50
51 #define RDC_WDT_SETUP (RDC_WDT_EN | RDC_WDT_NMIIRQ | RDC_WDT_RST | RDC_WDT_DFLT_CNT)
52
53 /* some device data */
54 static struct {
55 struct timer_list timer;
56 int seconds_left;
57 int total_seconds;
58 bool inuse;
59 bool running;
60 bool close_expected;
61 } rdc321x_wdt_dev;
62
63 static struct watchdog_info ident = {
64 .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
65 .identity = "RDC321x WDT",
66 };
67
68 /* generic helper functions */
69 static void rdc321x_wdt_timer(unsigned long unused)
70 {
71 if (!rdc321x_wdt_dev.running) {
72 rdc321x_pci_write(RDC321X_WDT_REG, 0);
73 return;
74 }
75
76 rdc321x_wdt_dev.seconds_left--;
77
78 if (rdc321x_wdt_dev.seconds_left < 1)
79 return;
80
81 rdc321x_pci_write(RDC321X_WDT_REG, RDC_WDT_SETUP);
82
83 mod_timer(&rdc321x_wdt_dev.timer, HZ * 2 + jiffies);
84 }
85
86 static void rdc321x_wdt_reset(void)
87 {
88 rdc321x_wdt_dev.seconds_left = rdc321x_wdt_dev.total_seconds;
89 }
90
91 static void rdc321x_wdt_start(void)
92 {
93 if (rdc321x_wdt_dev.running)
94 return;
95
96 rdc321x_wdt_dev.seconds_left = rdc321x_wdt_dev.total_seconds;
97
98 rdc321x_wdt_dev.running = true;
99
100 rdc321x_wdt_timer(0);
101
102 return;
103 }
104
105 static int rdc321x_wdt_stop(void)
106 {
107 if (WATCHDOG_NOWAYOUT)
108 return -ENOSYS;
109
110 rdc321x_wdt_dev.running = false;
111
112 return 0;
113 }
114
115 /* filesystem operations */
116 static int rdc321x_wdt_open(struct inode *inode, struct file *file)
117 {
118 if (xchg(&rdc321x_wdt_dev.inuse, true))
119 return -EBUSY;
120
121 return nonseekable_open(inode, file);
122 }
123
124 static int rdc321x_wdt_release(struct inode *inode, struct file *file)
125 {
126 if (rdc321x_wdt_dev.close_expected)
127 rdc321x_wdt_stop();
128
129 rdc321x_wdt_dev.inuse = false;
130
131 return 0;
132 }
133
134 static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
135 unsigned long arg)
136 {
137 void __user *argp = (void __user *)arg;
138 int value;
139
140 switch (cmd) {
141 case WDIOC_KEEPALIVE:
142 rdc321x_wdt_reset();
143 break;
144 case WDIOC_GETSUPPORT:
145 if (copy_to_user(argp, &ident, sizeof(ident)))
146 return -EFAULT;
147 break;
148 case WDIOC_SETTIMEOUT:
149 if (copy_from_user(&rdc321x_wdt_dev.total_seconds, argp, sizeof(int)))
150 return -EFAULT;
151 rdc321x_wdt_dev.seconds_left = rdc321x_wdt_dev.total_seconds;
152 break;
153 case WDIOC_GETTIMEOUT:
154 if (copy_to_user(argp, &rdc321x_wdt_dev.total_seconds, sizeof(int)))
155 return -EFAULT;
156 break;
157 case WDIOC_GETTIMELEFT:
158 if (copy_to_user(argp, &rdc321x_wdt_dev.seconds_left, sizeof(int)))
159 return -EFAULT;
160 break;
161 case WDIOC_SETOPTIONS:
162 if (copy_from_user(&value, argp, sizeof(int)))
163 return -EFAULT;
164 switch (value) {
165 case WDIOS_ENABLECARD:
166 rdc321x_wdt_start();
167 break;
168 case WDIOS_DISABLECARD:
169 return rdc321x_wdt_stop();
170 default:
171 return -EINVAL;
172 }
173 break;
174 default:
175 return -EINVAL;
176 }
177 return 0;
178 }
179
180 static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
181 size_t count, loff_t *ppos)
182 {
183 size_t i;
184
185 if (!count)
186 return -EIO;
187
188 rdc321x_wdt_dev.close_expected = false;
189
190 for (i = 0; i != count; i++) {
191 char c;
192
193 if (get_user(c, buf + i))
194 return -EFAULT;
195
196 if (c == 'V') {
197 rdc321x_wdt_dev.close_expected = true;
198 break;
199 }
200 }
201
202 rdc321x_wdt_reset();
203
204 return count;
205 }
206
207 static const struct file_operations rdc321x_wdt_fops = {
208 .llseek = no_llseek,
209 .unlocked_ioctl = rdc321x_wdt_ioctl,
210 .open = rdc321x_wdt_open,
211 .write = rdc321x_wdt_write,
212 .release = rdc321x_wdt_release,
213 };
214
215 static struct miscdevice rdc321x_wdt_misc = {
216 .minor = WATCHDOG_MINOR,
217 .name = "watchdog",
218 .fops = &rdc321x_wdt_fops,
219 };
220
221 static int __init rdc321x_wdt_probe(struct platform_device *pdev)
222 {
223 int err;
224
225 err = rdc321x_pci_write(RDC321X_WDT_REG, 0);
226 if (err)
227 return err;
228
229 rdc321x_wdt_dev.running = false;
230 rdc321x_wdt_dev.close_expected = false;
231 rdc321x_wdt_dev.inuse = 0;
232 setup_timer(&rdc321x_wdt_dev.timer, rdc321x_wdt_timer, 0);
233 rdc321x_wdt_dev.total_seconds = 100;
234
235 err = misc_register(&rdc321x_wdt_misc);
236 if (err < 0) {
237 printk(KERN_ERR PFX "watchdog: misc_register failed\n");
238 return err;
239 }
240
241 panic_on_unrecovered_nmi = 1;
242 dev_info(&pdev->dev, "watchdog inig success\n");
243
244 return 0;
245 }
246
247 static int __devexit rdc321x_wdt_remove(struct platform_device *pdev)
248 {
249 if (rdc321x_wdt_dev.inuse)
250 rdc321x_wdt_dev.inuse = 0;
251 misc_deregister(&rdc321x_wdt_misc);
252 return 0;
253 }
254
255 static struct platform_driver rdc321x_wdt_driver = {
256 .driver.name = "rdc321x-wdt",
257 .driver.owner = THIS_MODULE,
258 .probe = rdc321x_wdt_probe,
259 .remove = __devexit_p(rdc321x_wdt_remove),
260 };
261
262 static int __init rdc321x_wdt_init(void)
263 {
264 return platform_driver_register(&rdc321x_wdt_driver);
265 }
266
267 static void __exit rdc321x_wdt_exit(void)
268 {
269 platform_driver_unregister(&rdc321x_wdt_driver);
270 }
271
272 module_init(rdc321x_wdt_init);
273 module_exit(rdc321x_wdt_exit);
274
275 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
276 MODULE_DESCRIPTION("RDC321x Watchdog driver");
277 MODULE_LICENSE("GPL");
278 MODULE_ALIAS("platform:rdc321x-wdt");