Merge git://git.infradead.org/mtd-2.6
[linux-2.6] / drivers / watchdog / stmp3xxx_wdt.c
1 /*
2  * Watchdog driver for Freescale STMP37XX/STMP378X
3  *
4  * Author: Vitaly Wool <vital@embeddedalley.com>
5  *
6  * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
7  * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
8  */
9 #include <linux/init.h>
10 #include <linux/kernel.h>
11 #include <linux/fs.h>
12 #include <linux/miscdevice.h>
13 #include <linux/watchdog.h>
14 #include <linux/platform_device.h>
15 #include <linux/spinlock.h>
16 #include <linux/uaccess.h>
17
18 #include <mach/platform.h>
19 #include <mach/regs-rtc.h>
20
21 #define DEFAULT_HEARTBEAT       19
22 #define MAX_HEARTBEAT           (0x10000000 >> 6)
23
24 /* missing bitmask in headers */
25 #define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER     0x80000000
26
27 #define WDT_IN_USE              0
28 #define WDT_OK_TO_CLOSE         1
29
30 #define WDOG_COUNTER_RATE       1000 /* 1 kHz clock */
31
32 static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock);
33 static unsigned long wdt_status;
34 static const int nowayout = WATCHDOG_NOWAYOUT;
35 static int heartbeat = DEFAULT_HEARTBEAT;
36 static unsigned long boot_status;
37
38 static void wdt_enable(u32 value)
39 {
40         spin_lock(&stmp3xxx_wdt_io_lock);
41         __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG);
42         stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
43         stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
44                         REGS_RTC_BASE + HW_RTC_PERSISTENT1);
45         spin_unlock(&stmp3xxx_wdt_io_lock);
46 }
47
48 static void wdt_disable(void)
49 {
50         spin_lock(&stmp3xxx_wdt_io_lock);
51         stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
52                         REGS_RTC_BASE + HW_RTC_PERSISTENT1);
53         stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
54         spin_unlock(&stmp3xxx_wdt_io_lock);
55 }
56
57 static void wdt_ping(void)
58 {
59         wdt_enable(heartbeat * WDOG_COUNTER_RATE);
60 }
61
62 static int stmp3xxx_wdt_open(struct inode *inode, struct file *file)
63 {
64         if (test_and_set_bit(WDT_IN_USE, &wdt_status))
65                 return -EBUSY;
66
67         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
68         wdt_ping();
69
70         return nonseekable_open(inode, file);
71 }
72
73 static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data,
74         size_t len, loff_t *ppos)
75 {
76         if (len) {
77                 if (!nowayout) {
78                         size_t i;
79
80                         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
81
82                         for (i = 0; i != len; i++) {
83                                 char c;
84
85                                 if (get_user(c, data + i))
86                                         return -EFAULT;
87                                 if (c == 'V')
88                                         set_bit(WDT_OK_TO_CLOSE, &wdt_status);
89                         }
90                 }
91                 wdt_ping();
92         }
93
94         return len;
95 }
96
97 static struct watchdog_info ident = {
98         .options        = WDIOF_CARDRESET |
99                           WDIOF_MAGICCLOSE |
100                           WDIOF_SETTIMEOUT |
101                           WDIOF_KEEPALIVEPING,
102         .identity       = "STMP3XXX Watchdog",
103 };
104
105 static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd,
106         unsigned long arg)
107 {
108         void __user *argp = (void __user *)arg;
109         int __user *p = argp;
110         int new_heartbeat, opts;
111         int ret = -ENOTTY;
112
113         switch (cmd) {
114         case WDIOC_GETSUPPORT:
115                 ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
116                 break;
117
118         case WDIOC_GETSTATUS:
119                 ret = put_user(0, p);
120                 break;
121
122         case WDIOC_GETBOOTSTATUS:
123                 ret = put_user(boot_status, p);
124                 break;
125
126         case WDIOC_SETOPTIONS:
127                 if (get_user(opts, p)) {
128                         ret = -EFAULT;
129                         break;
130                 }
131                 if (opts & WDIOS_DISABLECARD)
132                         wdt_disable();
133                 else if (opts & WDIOS_ENABLECARD)
134                         wdt_ping();
135                 else {
136                         pr_debug("%s: unknown option 0x%x\n", __func__, opts);
137                         ret = -EINVAL;
138                         break;
139                 }
140                 ret = 0;
141                 break;
142
143         case WDIOC_KEEPALIVE:
144                 wdt_ping();
145                 ret = 0;
146                 break;
147
148         case WDIOC_SETTIMEOUT:
149                 if (get_user(new_heartbeat, p)) {
150                         ret = -EFAULT;
151                         break;
152                 }
153                 if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) {
154                         ret = -EINVAL;
155                         break;
156                 }
157
158                 heartbeat = new_heartbeat;
159                 wdt_ping();
160                 /* Fall through */
161
162         case WDIOC_GETTIMEOUT:
163                 ret = put_user(heartbeat, p);
164                 break;
165         }
166         return ret;
167 }
168
169 static int stmp3xxx_wdt_release(struct inode *inode, struct file *file)
170 {
171         int ret = 0;
172
173         if (!nowayout) {
174                 if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
175                         wdt_ping();
176                         pr_debug("%s: Device closed unexpectdly\n", __func__);
177                         ret = -EINVAL;
178                 } else {
179                         wdt_disable();
180                         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
181                 }
182         }
183         clear_bit(WDT_IN_USE, &wdt_status);
184
185         return ret;
186 }
187
188 static const struct file_operations stmp3xxx_wdt_fops = {
189         .owner = THIS_MODULE,
190         .llseek = no_llseek,
191         .write = stmp3xxx_wdt_write,
192         .unlocked_ioctl = stmp3xxx_wdt_ioctl,
193         .open = stmp3xxx_wdt_open,
194         .release = stmp3xxx_wdt_release,
195 };
196
197 static struct miscdevice stmp3xxx_wdt_miscdev = {
198         .minor = WATCHDOG_MINOR,
199         .name = "watchdog",
200         .fops = &stmp3xxx_wdt_fops,
201 };
202
203 static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev)
204 {
205         int ret = 0;
206
207         if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
208                 heartbeat = DEFAULT_HEARTBEAT;
209
210         boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) &
211                         BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER;
212         boot_status = !!boot_status;
213         stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
214                         REGS_RTC_BASE + HW_RTC_PERSISTENT1);
215         wdt_disable();          /* disable for now */
216
217         ret = misc_register(&stmp3xxx_wdt_miscdev);
218         if (ret < 0) {
219                 dev_err(&pdev->dev, "cannot register misc device\n");
220                 return ret;
221         }
222
223         printk(KERN_INFO "stmp3xxx watchdog: initialized, heartbeat %d sec\n",
224                 heartbeat);
225
226         return ret;
227 }
228
229 static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev)
230 {
231         misc_deregister(&stmp3xxx_wdt_miscdev);
232         return 0;
233 }
234
235 #ifdef CONFIG_PM
236 static int wdt_suspended;
237 static u32 wdt_saved_time;
238
239 static int stmp3xxx_wdt_suspend(struct platform_device *pdev,
240                                 pm_message_t state)
241 {
242         if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) &
243                 BM_RTC_CTRL_WATCHDOGEN) {
244                 wdt_suspended = 1;
245                 wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG);
246                 wdt_disable();
247         }
248         return 0;
249 }
250
251 static int stmp3xxx_wdt_resume(struct platform_device *pdev)
252 {
253         if (wdt_suspended) {
254                 wdt_enable(wdt_saved_time);
255                 wdt_suspended = 0;
256         }
257         return 0;
258 }
259 #else
260 #define stmp3xxx_wdt_suspend    NULL
261 #define stmp3xxx_wdt_resume     NULL
262 #endif
263
264 static struct platform_driver platform_wdt_driver = {
265         .driver = {
266                 .name = "stmp3xxx_wdt",
267         },
268         .probe = stmp3xxx_wdt_probe,
269         .remove = __devexit_p(stmp3xxx_wdt_remove),
270         .suspend = stmp3xxx_wdt_suspend,
271         .resume = stmp3xxx_wdt_resume,
272 };
273
274 static int __init stmp3xxx_wdt_init(void)
275 {
276         return platform_driver_register(&platform_wdt_driver);
277 }
278
279 static void __exit stmp3xxx_wdt_exit(void)
280 {
281         return platform_driver_unregister(&platform_wdt_driver);
282 }
283
284 module_init(stmp3xxx_wdt_init);
285 module_exit(stmp3xxx_wdt_exit);
286
287 MODULE_DESCRIPTION("STMP3XXX Watchdog Driver");
288 MODULE_LICENSE("GPL");
289
290 module_param(heartbeat, int, 0);
291 MODULE_PARM_DESC(heartbeat,
292                  "Watchdog heartbeat period in seconds from 1 to "
293                  __MODULE_STRING(MAX_HEARTBEAT) ", default "
294                  __MODULE_STRING(DEFAULT_HEARTBEAT));
295
296 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);