Merge commit 'v2.6.28-rc4' into x86/apic
[linux-2.6] / drivers / watchdog / ks8695_wdt.c
1 /*
2  * Watchdog driver for Kendin/Micrel KS8695.
3  *
4  * (C) 2007 Andrew Victor
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 version 2 as
8  * published by the Free Software Foundation.
9  */
10
11 #include <linux/bitops.h>
12 #include <linux/errno.h>
13 #include <linux/fs.h>
14 #include <linux/init.h>
15 #include <linux/kernel.h>
16 #include <linux/miscdevice.h>
17 #include <linux/module.h>
18 #include <linux/moduleparam.h>
19 #include <linux/platform_device.h>
20 #include <linux/types.h>
21 #include <linux/watchdog.h>
22 #include <linux/io.h>
23 #include <linux/uaccess.h>
24 #include <mach/regs-timer.h>
25
26 #define WDT_DEFAULT_TIME        5       /* seconds */
27 #define WDT_MAX_TIME            171     /* seconds */
28
29 static int wdt_time = WDT_DEFAULT_TIME;
30 static int nowayout = WATCHDOG_NOWAYOUT;
31
32 module_param(wdt_time, int, 0);
33 MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="
34                                         __MODULE_STRING(WDT_DEFAULT_TIME) ")");
35
36 #ifdef CONFIG_WATCHDOG_NOWAYOUT
37 module_param(nowayout, int, 0);
38 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
39                                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
40 #endif
41
42
43 static unsigned long ks8695wdt_busy;
44 static spinlock_t ks8695_lock;
45
46 /* ......................................................................... */
47
48 /*
49  * Disable the watchdog.
50  */
51 static inline void ks8695_wdt_stop(void)
52 {
53         unsigned long tmcon;
54
55         spin_lock(&ks8695_lock);
56         /* disable timer0 */
57         tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON);
58         __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
59         spin_unlock(&ks8695_lock);
60 }
61
62 /*
63  * Enable and reset the watchdog.
64  */
65 static inline void ks8695_wdt_start(void)
66 {
67         unsigned long tmcon;
68         unsigned long tval = wdt_time * CLOCK_TICK_RATE;
69
70         spin_lock(&ks8695_lock);
71         /* disable timer0 */
72         tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON);
73         __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
74
75         /* program timer0 */
76         __raw_writel(tval | T0TC_WATCHDOG, KS8695_TMR_VA + KS8695_T0TC);
77
78         /* re-enable timer0 */
79         tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON);
80         __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
81         spin_unlock(&ks8695_lock);
82 }
83
84 /*
85  * Reload the watchdog timer.  (ie, pat the watchdog)
86  */
87 static inline void ks8695_wdt_reload(void)
88 {
89         unsigned long tmcon;
90
91         spin_lock(&ks8695_lock);
92         /* disable, then re-enable timer0 */
93         tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON);
94         __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
95         __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
96         spin_unlock(&ks8695_lock);
97 }
98
99 /*
100  * Change the watchdog time interval.
101  */
102 static int ks8695_wdt_settimeout(int new_time)
103 {
104         /*
105          * All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz
106          *
107          * Since WDV is a 16-bit counter, the maximum period is
108          * 65536 / 0.256 = 256 seconds.
109          */
110         if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
111                 return -EINVAL;
112
113         /* Set new watchdog time. It will be used when
114            ks8695_wdt_start() is called. */
115         wdt_time = new_time;
116         return 0;
117 }
118
119 /* ......................................................................... */
120
121 /*
122  * Watchdog device is opened, and watchdog starts running.
123  */
124 static int ks8695_wdt_open(struct inode *inode, struct file *file)
125 {
126         if (test_and_set_bit(0, &ks8695wdt_busy))
127                 return -EBUSY;
128
129         ks8695_wdt_start();
130         return nonseekable_open(inode, file);
131 }
132
133 /*
134  * Close the watchdog device.
135  * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also
136  *  disabled.
137  */
138 static int ks8695_wdt_close(struct inode *inode, struct file *file)
139 {
140         /* Disable the watchdog when file is closed */
141         if (!nowayout)
142                 ks8695_wdt_stop();
143         clear_bit(0, &ks8695wdt_busy);
144         return 0;
145 }
146
147 static struct watchdog_info ks8695_wdt_info = {
148         .identity       = "ks8695 watchdog",
149         .options        = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
150 };
151
152 /*
153  * Handle commands from user-space.
154  */
155 static long ks8695_wdt_ioctl(struct file *file, unsigned int cmd,
156                                                         unsigned long arg)
157 {
158         void __user *argp = (void __user *)arg;
159         int __user *p = argp;
160         int new_value;
161
162         switch (cmd) {
163         case WDIOC_GETSUPPORT:
164                 return copy_to_user(argp, &ks8695_wdt_info,
165                                         sizeof(ks8695_wdt_info)) ? -EFAULT : 0;
166         case WDIOC_GETSTATUS:
167         case WDIOC_GETBOOTSTATUS:
168                 return put_user(0, p);
169         case WDIOC_SETOPTIONS:
170                 if (get_user(new_value, p))
171                         return -EFAULT;
172                 if (new_value & WDIOS_DISABLECARD)
173                         ks8695_wdt_stop();
174                 if (new_value & WDIOS_ENABLECARD)
175                         ks8695_wdt_start();
176                 return 0;
177         case WDIOC_KEEPALIVE:
178                 ks8695_wdt_reload();    /* pat the watchdog */
179                 return 0;
180         case WDIOC_SETTIMEOUT:
181                 if (get_user(new_value, p))
182                         return -EFAULT;
183                 if (ks8695_wdt_settimeout(new_value))
184                         return -EINVAL;
185                 /* Enable new time value */
186                 ks8695_wdt_start();
187                 /* Return current value */
188                 return put_user(wdt_time, p);
189         case WDIOC_GETTIMEOUT:
190                 return put_user(wdt_time, p);
191         default:
192                 return -ENOTTY;
193         }
194 }
195
196 /*
197  * Pat the watchdog whenever device is written to.
198  */
199 static ssize_t ks8695_wdt_write(struct file *file, const char *data,
200                                                 size_t len, loff_t *ppos)
201 {
202         ks8695_wdt_reload();            /* pat the watchdog */
203         return len;
204 }
205
206 /* ......................................................................... */
207
208 static const struct file_operations ks8695wdt_fops = {
209         .owner          = THIS_MODULE,
210         .llseek         = no_llseek,
211         .unlocked_ioctl = ks8695_wdt_ioctl,
212         .open           = ks8695_wdt_open,
213         .release        = ks8695_wdt_close,
214         .write          = ks8695_wdt_write,
215 };
216
217 static struct miscdevice ks8695wdt_miscdev = {
218         .minor          = WATCHDOG_MINOR,
219         .name           = "watchdog",
220         .fops           = &ks8695wdt_fops,
221 };
222
223 static int __init ks8695wdt_probe(struct platform_device *pdev)
224 {
225         int res;
226
227         if (ks8695wdt_miscdev.parent)
228                 return -EBUSY;
229         ks8695wdt_miscdev.parent = &pdev->dev;
230
231         res = misc_register(&ks8695wdt_miscdev);
232         if (res)
233                 return res;
234
235         printk(KERN_INFO "KS8695 Watchdog Timer enabled (%d seconds%s)\n",
236                                 wdt_time, nowayout ? ", nowayout" : "");
237         return 0;
238 }
239
240 static int __exit ks8695wdt_remove(struct platform_device *pdev)
241 {
242         int res;
243
244         res = misc_deregister(&ks8695wdt_miscdev);
245         if (!res)
246                 ks8695wdt_miscdev.parent = NULL;
247
248         return res;
249 }
250
251 static void ks8695wdt_shutdown(struct platform_device *pdev)
252 {
253         ks8695_wdt_stop();
254 }
255
256 #ifdef CONFIG_PM
257
258 static int ks8695wdt_suspend(struct platform_device *pdev, pm_message_t message)
259 {
260         ks8695_wdt_stop();
261         return 0;
262 }
263
264 static int ks8695wdt_resume(struct platform_device *pdev)
265 {
266         if (ks8695wdt_busy)
267                 ks8695_wdt_start();
268         return 0;
269 }
270
271 #else
272 #define ks8695wdt_suspend NULL
273 #define ks8695wdt_resume        NULL
274 #endif
275
276 static struct platform_driver ks8695wdt_driver = {
277         .probe          = ks8695wdt_probe,
278         .remove         = __exit_p(ks8695wdt_remove),
279         .shutdown       = ks8695wdt_shutdown,
280         .suspend        = ks8695wdt_suspend,
281         .resume         = ks8695wdt_resume,
282         .driver         = {
283                 .name   = "ks8695_wdt",
284                 .owner  = THIS_MODULE,
285         },
286 };
287
288 static int __init ks8695_wdt_init(void)
289 {
290         spin_lock_init(&ks8695_lock);
291         /* Check that the heartbeat value is within range;
292            if not reset to the default */
293         if (ks8695_wdt_settimeout(wdt_time)) {
294                 ks8695_wdt_settimeout(WDT_DEFAULT_TIME);
295                 pr_info("ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i, using %d\n",
296                                                         wdt_time, WDT_MAX_TIME);
297         }
298         return platform_driver_register(&ks8695wdt_driver);
299 }
300
301 static void __exit ks8695_wdt_exit(void)
302 {
303         platform_driver_unregister(&ks8695wdt_driver);
304 }
305
306 module_init(ks8695_wdt_init);
307 module_exit(ks8695_wdt_exit);
308
309 MODULE_AUTHOR("Andrew Victor");
310 MODULE_DESCRIPTION("Watchdog driver for KS8695");
311 MODULE_LICENSE("GPL");
312 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
313 MODULE_ALIAS("platform:ks8695_wdt");