Merge branch 'for-linus' of git://neil.brown.name/md
[linux-2.6] / drivers / watchdog / orion5x_wdt.c
1 /*
2  * drivers/watchdog/orion5x_wdt.c
3  *
4  * Watchdog driver for Orion5x processors
5  *
6  * Author: Sylver Bruneau <sylver.bruneau@googlemail.com>
7  *
8  * This file is licensed under  the terms of the GNU General Public
9  * License version 2. This program is licensed "as is" without any
10  * warranty of any kind, whether express or implied.
11  */
12
13 #include <linux/module.h>
14 #include <linux/moduleparam.h>
15 #include <linux/types.h>
16 #include <linux/kernel.h>
17 #include <linux/fs.h>
18 #include <linux/miscdevice.h>
19 #include <linux/platform_device.h>
20 #include <linux/watchdog.h>
21 #include <linux/init.h>
22 #include <linux/uaccess.h>
23 #include <linux/io.h>
24 #include <linux/spinlock.h>
25 #include <plat/orion5x_wdt.h>
26
27 /*
28  * Watchdog timer block registers.
29  */
30 #define TIMER_CTRL              (TIMER_VIRT_BASE + 0x0000)
31 #define  WDT_EN                 0x0010
32 #define WDT_VAL                 (TIMER_VIRT_BASE + 0x0024)
33
34 #define WDT_MAX_CYCLE_COUNT     0xffffffff
35 #define WDT_IN_USE              0
36 #define WDT_OK_TO_CLOSE         1
37
38 static int nowayout = WATCHDOG_NOWAYOUT;
39 static int heartbeat = -1;              /* module parameter (seconds) */
40 static unsigned int wdt_max_duration;   /* (seconds) */
41 static unsigned int wdt_tclk;
42 static unsigned long wdt_status;
43 static spinlock_t wdt_lock;
44
45 static void wdt_enable(void)
46 {
47         u32 reg;
48
49         spin_lock(&wdt_lock);
50
51         /* Set watchdog duration */
52         writel(wdt_tclk * heartbeat, WDT_VAL);
53
54         /* Clear watchdog timer interrupt */
55         reg = readl(BRIDGE_CAUSE);
56         reg &= ~WDT_INT_REQ;
57         writel(reg, BRIDGE_CAUSE);
58
59         /* Enable watchdog timer */
60         reg = readl(TIMER_CTRL);
61         reg |= WDT_EN;
62         writel(reg, TIMER_CTRL);
63
64         /* Enable reset on watchdog */
65         reg = readl(CPU_RESET_MASK);
66         reg |= WDT_RESET;
67         writel(reg, CPU_RESET_MASK);
68
69         spin_unlock(&wdt_lock);
70 }
71
72 static void wdt_disable(void)
73 {
74         u32 reg;
75
76         spin_lock(&wdt_lock);
77
78         /* Disable reset on watchdog */
79         reg = readl(CPU_RESET_MASK);
80         reg &= ~WDT_RESET;
81         writel(reg, CPU_RESET_MASK);
82
83         /* Disable watchdog timer */
84         reg = readl(TIMER_CTRL);
85         reg &= ~WDT_EN;
86         writel(reg, TIMER_CTRL);
87
88         spin_unlock(&wdt_lock);
89 }
90
91 static int orion5x_wdt_get_timeleft(int *time_left)
92 {
93         spin_lock(&wdt_lock);
94         *time_left = readl(WDT_VAL) / wdt_tclk;
95         spin_unlock(&wdt_lock);
96         return 0;
97 }
98
99 static int orion5x_wdt_open(struct inode *inode, struct file *file)
100 {
101         if (test_and_set_bit(WDT_IN_USE, &wdt_status))
102                 return -EBUSY;
103         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
104         wdt_enable();
105         return nonseekable_open(inode, file);
106 }
107
108 static ssize_t orion5x_wdt_write(struct file *file, const char *data,
109                                         size_t len, loff_t *ppos)
110 {
111         if (len) {
112                 if (!nowayout) {
113                         size_t i;
114
115                         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
116                         for (i = 0; i != len; i++) {
117                                 char c;
118
119                                 if (get_user(c, data + i))
120                                         return -EFAULT;
121                                 if (c == 'V')
122                                         set_bit(WDT_OK_TO_CLOSE, &wdt_status);
123                         }
124                 }
125                 wdt_enable();
126         }
127         return len;
128 }
129
130 static struct watchdog_info ident = {
131         .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
132                           WDIOF_KEEPALIVEPING,
133         .identity       = "Orion5x Watchdog",
134 };
135
136
137 static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd,
138                                 unsigned long arg)
139 {
140         int ret = -ENOTTY;
141         int time;
142
143         switch (cmd) {
144         case WDIOC_GETSUPPORT:
145                 ret = copy_to_user((struct watchdog_info *)arg, &ident,
146                                    sizeof(ident)) ? -EFAULT : 0;
147                 break;
148
149         case WDIOC_GETSTATUS:
150         case WDIOC_GETBOOTSTATUS:
151                 ret = put_user(0, (int *)arg);
152                 break;
153
154         case WDIOC_KEEPALIVE:
155                 wdt_enable();
156                 ret = 0;
157                 break;
158
159         case WDIOC_SETTIMEOUT:
160                 ret = get_user(time, (int *)arg);
161                 if (ret)
162                         break;
163
164                 if (time <= 0 || time > wdt_max_duration) {
165                         ret = -EINVAL;
166                         break;
167                 }
168                 heartbeat = time;
169                 wdt_enable();
170                 /* Fall through */
171
172         case WDIOC_GETTIMEOUT:
173                 ret = put_user(heartbeat, (int *)arg);
174                 break;
175
176         case WDIOC_GETTIMELEFT:
177                 if (orion5x_wdt_get_timeleft(&time)) {
178                         ret = -EINVAL;
179                         break;
180                 }
181                 ret = put_user(time, (int *)arg);
182                 break;
183         }
184         return ret;
185 }
186
187 static int orion5x_wdt_release(struct inode *inode, struct file *file)
188 {
189         if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
190                 wdt_disable();
191         else
192                 printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
193                                         "timer will not stop\n");
194         clear_bit(WDT_IN_USE, &wdt_status);
195         clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
196
197         return 0;
198 }
199
200
201 static const struct file_operations orion5x_wdt_fops = {
202         .owner          = THIS_MODULE,
203         .llseek         = no_llseek,
204         .write          = orion5x_wdt_write,
205         .unlocked_ioctl = orion5x_wdt_ioctl,
206         .open           = orion5x_wdt_open,
207         .release        = orion5x_wdt_release,
208 };
209
210 static struct miscdevice orion5x_wdt_miscdev = {
211         .minor          = WATCHDOG_MINOR,
212         .name           = "watchdog",
213         .fops           = &orion5x_wdt_fops,
214 };
215
216 static int __devinit orion5x_wdt_probe(struct platform_device *pdev)
217 {
218         struct orion5x_wdt_platform_data *pdata = pdev->dev.platform_data;
219         int ret;
220
221         if (pdata) {
222                 wdt_tclk = pdata->tclk;
223         } else {
224                 printk(KERN_ERR "Orion5x Watchdog misses platform data\n");
225                 return -ENODEV;
226         }
227
228         if (orion5x_wdt_miscdev.parent)
229                 return -EBUSY;
230         orion5x_wdt_miscdev.parent = &pdev->dev;
231
232         wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk;
233         if (heartbeat <= 0 || heartbeat > wdt_max_duration)
234                 heartbeat = wdt_max_duration;
235
236         ret = misc_register(&orion5x_wdt_miscdev);
237         if (ret)
238                 return ret;
239
240         printk(KERN_INFO "Orion5x Watchdog Timer: Initial timeout %d sec%s\n",
241                                 heartbeat, nowayout ? ", nowayout" : "");
242         return 0;
243 }
244
245 static int __devexit orion5x_wdt_remove(struct platform_device *pdev)
246 {
247         int ret;
248
249         if (test_bit(WDT_IN_USE, &wdt_status)) {
250                 wdt_disable();
251                 clear_bit(WDT_IN_USE, &wdt_status);
252         }
253
254         ret = misc_deregister(&orion5x_wdt_miscdev);
255         if (!ret)
256                 orion5x_wdt_miscdev.parent = NULL;
257
258         return ret;
259 }
260
261 static struct platform_driver orion5x_wdt_driver = {
262         .probe          = orion5x_wdt_probe,
263         .remove         = __devexit_p(orion5x_wdt_remove),
264         .driver         = {
265                 .owner  = THIS_MODULE,
266                 .name   = "orion5x_wdt",
267         },
268 };
269
270 static int __init orion5x_wdt_init(void)
271 {
272         spin_lock_init(&wdt_lock);
273         return platform_driver_register(&orion5x_wdt_driver);
274 }
275
276 static void __exit orion5x_wdt_exit(void)
277 {
278         platform_driver_unregister(&orion5x_wdt_driver);
279 }
280
281 module_init(orion5x_wdt_init);
282 module_exit(orion5x_wdt_exit);
283
284 MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>");
285 MODULE_DESCRIPTION("Orion5x Processor Watchdog");
286
287 module_param(heartbeat, int, 0);
288 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds");
289
290 module_param(nowayout, int, 0);
291 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
292
293 MODULE_LICENSE("GPL");
294 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);