cc41ec5b9de4b886943d484cc941d4797ccc18fe
[librecmc/librecmc.git] / target / linux / brcm63xx / files / drivers / watchdog / bcm63xx_wdt.c
1 /*
2  *  Broadcom BCM63xx SoC watchdog driver
3  *
4  *  Copyright (C) 2007, Miguel Gaio <miguel.gaio@efixo.com>
5  *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
6  *
7  *  This program is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU General Public License
9  *  as published by the Free Software Foundation; either version
10  *  2 of the License, or (at your option) any later version.
11  */
12
13 #include <linux/bitops.h>
14 #include <linux/errno.h>
15 #include <linux/fs.h>
16 #include <linux/init.h>
17 #include <linux/kernel.h>
18 #include <linux/miscdevice.h>
19 #include <linux/module.h>
20 #include <linux/moduleparam.h>
21 #include <linux/reboot.h>
22 #include <linux/types.h>
23 #include <linux/uaccess.h>
24 #include <linux/watchdog.h>
25 #include <linux/timer.h>
26 #include <linux/jiffies.h>
27 #include <linux/resource.h>
28 #include <linux/platform_device.h>
29
30 #include <bcm63xx_cpu.h>
31 #include <bcm63xx_io.h>
32 #include <bcm63xx_regs.h>
33
34 #define PFX KBUILD_MODNAME
35
36 #define WDT_HZ          50000000 /* Fclk */
37 #define WDT_DEFAULT_TIME        30      /* seconds */
38 #define WDT_MAX_TIME            256     /* seconds */
39
40 static struct {
41         void __iomem *regs;
42         struct timer_list timer;
43         int default_ticks;
44         unsigned long inuse;
45         atomic_t ticks;
46 } bcm63xx_wdt_device;
47
48 static int expect_close;
49 static int timeout;
50
51 static int wdt_time = WDT_DEFAULT_TIME;
52 static int nowayout = WATCHDOG_NOWAYOUT;
53 module_param(nowayout, int, 0);
54 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
55         __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
56
57 /* HW functions */
58 static void bcm63xx_wdt_hw_start(void)
59 {
60         bcm_writel(0xffffffff, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG);
61         bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
62         bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
63 }
64
65 static void bcm63xx_wdt_hw_stop(void)
66 {
67         bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG);
68         bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG);
69 }
70
71 static void bcm63xx_timer_tick(unsigned long unused)
72 {
73         if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) {
74                 bcm63xx_wdt_hw_start();
75                 mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ);
76         } else
77                 printk(KERN_CRIT PFX "watchdog will restart system\n");
78 }
79
80 static void bcm63xx_wdt_pet(void)
81 {
82         atomic_set(&bcm63xx_wdt_device.ticks, wdt_time);
83 }
84
85 static void bcm63xx_wdt_start(void)
86 {
87         bcm63xx_wdt_pet();
88         bcm63xx_timer_tick(0);
89 }
90
91 static void bcm63xx_wdt_pause(void)
92 {
93         del_timer_sync(&bcm63xx_wdt_device.timer);
94         bcm63xx_wdt_hw_stop();
95 }
96
97 static int bcm63xx_wdt_settimeout(int new_time)
98 {
99         if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
100                 return -EINVAL;
101
102         wdt_time = new_time;
103
104         return 0;
105 }
106
107 static int bcm63xx_wdt_open(struct inode *inode, struct file *file)
108 {
109         if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse))
110                 return -EBUSY;
111         
112         bcm63xx_wdt_start();
113         return nonseekable_open(inode, file);
114 }
115
116 static int bcm63xx_wdt_release(struct inode *inode, struct file *file)
117 {
118         if (expect_close == 42)
119                 bcm63xx_wdt_pause();
120         else {
121                 printk(KERN_CRIT PFX
122                         ": Unexpected close, not stopping watchdog!\n");
123                 bcm63xx_wdt_start();
124         }
125         clear_bit(0, &bcm63xx_wdt_device.inuse);
126         expect_close = 0;
127         return 0;
128 }
129
130 static ssize_t bcm63xx_wdt_write(struct file *file, const char *data,
131                                 size_t len, loff_t *ppos)
132 {
133         if (len) {
134                 if (!nowayout) {
135                         size_t i;
136
137                         /* In case it was set long ago */
138                         expect_close = 0;
139
140                         for (i = 0; i != len; i++) {
141                                 char c;
142                                 if (get_user(c, data + i))
143                                         return -EFAULT;
144                                 if (c == 'V')
145                                         expect_close = 42;
146                         }
147                 }
148                 bcm63xx_wdt_pet();
149         }
150         return len;
151 }
152
153 static struct watchdog_info bcm63xx_wdt_info = {
154         .identity       = PFX,
155         .options        = WDIOF_SETTIMEOUT |
156                                 WDIOF_KEEPALIVEPING |
157                                 WDIOF_MAGICCLOSE,
158 };
159
160
161 static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd,
162                                 unsigned long arg)
163 {
164         void __user *argp = (void __user *)arg;
165         int __user *p = argp;
166         int new_value, retval = -EINVAL;
167
168         switch (cmd) {
169         case WDIOC_GETSUPPORT:
170                 return copy_to_user(argp, &bcm63xx_wdt_info,
171                         sizeof(bcm63xx_wdt_info)) ? -EFAULT : 0;
172
173         case WDIOC_GETSTATUS:
174         case WDIOC_GETBOOTSTATUS:
175                 return put_user(0, p);
176
177         case WDIOC_SETOPTIONS:
178                 if (get_user(new_value, p))
179                         return -EFAULT;
180
181                 if (new_value & WDIOS_DISABLECARD) {
182                         bcm63xx_wdt_pause();
183                         retval = 0;
184                 }
185                 if (new_value & WDIOS_ENABLECARD) {
186                         bcm63xx_wdt_start();
187                         retval = 0;
188                 }
189
190                 return retval;
191         
192         case WDIOC_KEEPALIVE:
193                 bcm63xx_wdt_pet();
194                 return 0;
195                 
196         case WDIOC_SETTIMEOUT:
197                 if (get_user(new_value, p))
198                         return -EFAULT;
199
200                 if (bcm63xx_wdt_settimeout(new_value))
201                         return -EINVAL;
202
203                 bcm63xx_wdt_pet();
204
205         case WDIOC_GETTIMEOUT:
206                 return put_user(wdt_time, p);
207
208         default:
209                 return -ENOTTY;
210
211         }
212 }
213
214 static int bcm63xx_wdt_notify_sys(struct notifier_block *this,
215            unsigned long code, void *unused)
216 {
217         if (code == SYS_DOWN || code == SYS_HALT)
218                 bcm63xx_wdt_pause();
219         return NOTIFY_DONE;
220 }
221
222 static struct file_operations bcm63xx_wdt_fops = {
223         .owner          = THIS_MODULE,
224         .llseek         = no_llseek,
225         .write          = bcm63xx_wdt_write,
226         .unlocked_ioctl = bcm63xx_wdt_ioctl,
227         .open           = bcm63xx_wdt_open,
228         .release        = bcm63xx_wdt_release,
229 };
230
231 static struct miscdevice bcm63xx_wdt_miscdev = {
232         .minor  = WATCHDOG_MINOR,
233         .name   = "watchdog",
234         .fops   = &bcm63xx_wdt_fops,
235 };
236
237 static struct notifier_block bcm63xx_wdt_notifier = {
238         .notifier_call = bcm63xx_wdt_notify_sys,
239 };
240
241
242 static int bcm63xx_wdt_probe(struct platform_device *pdev)
243 {
244         int ret;
245         struct resource *r;
246         
247         setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L);
248
249         r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
250         if (!r) {
251                 printk(KERN_ERR PFX 
252                         "failed to retrieve resources\n");
253                 return -ENODEV;
254         }
255
256         bcm63xx_wdt_device.regs = ioremap_nocache(r->start, r->end - r->start);
257         if (!bcm63xx_wdt_device.regs) {
258                 printk(KERN_ERR PFX
259                         "failed to remap I/O resources\n");
260                 return -ENXIO;
261         }
262
263         if (bcm63xx_wdt_settimeout(wdt_time)) {
264                 bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME);
265                 printk(KERN_INFO PFX
266                         ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n",
267                         wdt_time);
268         }
269
270         ret = register_reboot_notifier(&bcm63xx_wdt_notifier);
271         if (ret) {
272                 printk(KERN_ERR PFX
273                         "failed to register reboot_notifier\n");
274                 return ret;
275         }
276
277         ret = misc_register(&bcm63xx_wdt_miscdev);
278         if (ret < 0) {
279                 printk(KERN_ERR PFX
280                         "failed to register watchdog device\n");
281                 goto unmap;
282         }
283
284         printk(KERN_INFO PFX " started, timer margin: %d sec\n", WDT_DEFAULT_TIME);
285
286         return 0;
287
288 unmap:
289         unregister_reboot_notifier(&bcm63xx_wdt_notifier);
290         iounmap(bcm63xx_wdt_device.regs);
291         return ret;
292 }
293
294 static int bcm63xx_wdt_remove(struct platform_device *pdev)
295 {
296         if (!nowayout)
297                 bcm63xx_wdt_pause();
298
299         misc_deregister(&bcm63xx_wdt_miscdev);
300
301         iounmap(bcm63xx_wdt_device.regs);
302
303         unregister_reboot_notifier(&bcm63xx_wdt_notifier);
304
305         return 0;
306 }
307
308 static struct platform_driver bcm63xx_wdt = {
309         .probe  = bcm63xx_wdt_probe,
310         .remove = bcm63xx_wdt_remove,
311         .driver = {
312                 .name = "bcm63xx-wdt",
313         }
314 };
315
316 static int __init bcm63xx_wdt_init(void)
317 {
318         return platform_driver_register(&bcm63xx_wdt);
319 }
320
321 static void __exit bcm63xx_wdt_exit(void)
322 {
323         platform_driver_unregister(&bcm63xx_wdt);
324 }
325
326 module_init(bcm63xx_wdt_init);
327 module_exit(bcm63xx_wdt_exit);
328
329 MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>");
330 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
331 MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog");
332 MODULE_LICENSE("GPL");
333 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
334 MODULE_ALIAS("platform:bcm63xx-wdt");