HOWTO: update misspelling and word incorrected
[sfrench/cifs-2.6.git] / drivers / watchdog / at32ap700x_wdt.c
1 /*
2  * Watchdog driver for Atmel AT32AP700X devices
3  *
4  * Copyright (C) 2005-2006 Atmel Corporation
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/init.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/moduleparam.h>
15 #include <linux/miscdevice.h>
16 #include <linux/fs.h>
17 #include <linux/platform_device.h>
18 #include <linux/watchdog.h>
19 #include <linux/uaccess.h>
20 #include <linux/io.h>
21 #include <linux/spinlock.h>
22
23 #define TIMEOUT_MIN             1
24 #define TIMEOUT_MAX             2
25 #define TIMEOUT_DEFAULT         TIMEOUT_MAX
26
27 /* module parameters */
28 static int timeout =  TIMEOUT_DEFAULT;
29 module_param(timeout, int, 0);
30 MODULE_PARM_DESC(timeout,
31                 "Timeout value. Limited to be 1 or 2 seconds. (default="
32                 __MODULE_STRING(TIMEOUT_DEFAULT) ")");
33
34 static int nowayout = WATCHDOG_NOWAYOUT;
35 module_param(nowayout, int, 0);
36 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
37                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
38
39 /* Watchdog registers and write/read macro */
40 #define WDT_CTRL                0x00
41 #define WDT_CTRL_EN                0
42 #define WDT_CTRL_PSEL              8
43 #define WDT_CTRL_KEY              24
44
45 #define WDT_CLR                 0x04
46
47 #define WDT_BIT(name)           (1 << WDT_##name)
48 #define WDT_BF(name, value)     ((value) << WDT_##name)
49
50 #define wdt_readl(dev, reg)                             \
51         __raw_readl((dev)->regs + WDT_##reg)
52 #define wdt_writel(dev, reg, value)                     \
53         __raw_writel((value), (dev)->regs + WDT_##reg)
54
55 struct wdt_at32ap700x {
56         void __iomem            *regs;
57         spinlock_t              io_lock;
58         int                     timeout;
59         unsigned long           users;
60         struct miscdevice       miscdev;
61 };
62
63 static struct wdt_at32ap700x *wdt;
64 static char expect_release;
65
66 /*
67  * Disable the watchdog.
68  */
69 static inline void at32_wdt_stop(void)
70 {
71         unsigned long psel;
72
73         spin_lock(&wdt->io_lock);
74         psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f);
75         wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55));
76         wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa));
77         spin_unlock(&wdt->io_lock);
78 }
79
80 /*
81  * Enable and reset the watchdog.
82  */
83 static inline void at32_wdt_start(void)
84 {
85         /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */
86         unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe;
87
88         spin_lock(&wdt->io_lock);
89         wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
90                         | WDT_BF(CTRL_PSEL, psel)
91                         | WDT_BF(CTRL_KEY, 0x55));
92         wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
93                         | WDT_BF(CTRL_PSEL, psel)
94                         | WDT_BF(CTRL_KEY, 0xaa));
95         spin_unlock(&wdt->io_lock);
96 }
97
98 /*
99  * Pat the watchdog timer.
100  */
101 static inline void at32_wdt_pat(void)
102 {
103         spin_lock(&wdt->io_lock);
104         wdt_writel(wdt, CLR, 0x42);
105         spin_unlock(&wdt->io_lock);
106 }
107
108 /*
109  * Watchdog device is opened, and watchdog starts running.
110  */
111 static int at32_wdt_open(struct inode *inode, struct file *file)
112 {
113         if (test_and_set_bit(1, &wdt->users))
114                 return -EBUSY;
115
116         at32_wdt_start();
117         return nonseekable_open(inode, file);
118 }
119
120 /*
121  * Close the watchdog device.
122  */
123 static int at32_wdt_close(struct inode *inode, struct file *file)
124 {
125         if (expect_release == 42) {
126                 at32_wdt_stop();
127         } else {
128                 dev_dbg(wdt->miscdev.parent,
129                         "Unexpected close, not stopping watchdog!\n");
130                 at32_wdt_pat();
131         }
132         clear_bit(1, &wdt->users);
133         expect_release = 0;
134         return 0;
135 }
136
137 /*
138  * Change the watchdog time interval.
139  */
140 static int at32_wdt_settimeout(int time)
141 {
142         /*
143          * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is
144          * 2 ^ 16 allowing up to 2 seconds timeout.
145          */
146         if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX))
147                 return -EINVAL;
148
149         /*
150          * Set new watchdog time. It will be used when at32_wdt_start() is
151          * called.
152          */
153         wdt->timeout = time;
154         return 0;
155 }
156
157 static struct watchdog_info at32_wdt_info = {
158         .identity       = "at32ap700x watchdog",
159         .options        = WDIOF_SETTIMEOUT |
160                           WDIOF_KEEPALIVEPING |
161                           WDIOF_MAGICCLOSE,
162 };
163
164 /*
165  * Handle commands from user-space.
166  */
167 static int at32_wdt_ioctl(struct inode *inode, struct file *file,
168                 unsigned int cmd, unsigned long arg)
169 {
170         int ret = -ENOTTY;
171         int time;
172         void __user *argp = (void __user *)arg;
173         int __user *p = argp;
174
175         switch (cmd) {
176         case WDIOC_KEEPALIVE:
177                 at32_wdt_pat();
178                 ret = 0;
179                 break;
180         case WDIOC_GETSUPPORT:
181                 ret = copy_to_user(argp, &at32_wdt_info,
182                                 sizeof(at32_wdt_info)) ? -EFAULT : 0;
183                 break;
184         case WDIOC_SETTIMEOUT:
185                 ret = get_user(time, p);
186                 if (ret)
187                         break;
188                 ret = at32_wdt_settimeout(time);
189                 if (ret)
190                         break;
191                 /* Enable new time value */
192                 at32_wdt_start();
193                 /* fall through */
194         case WDIOC_GETTIMEOUT:
195                 ret = put_user(wdt->timeout, p);
196                 break;
197         case WDIOC_GETSTATUS: /* fall through */
198         case WDIOC_GETBOOTSTATUS:
199                 ret = put_user(0, p);
200                 break;
201         case WDIOC_SETOPTIONS:
202                 ret = get_user(time, p);
203                 if (ret)
204                         break;
205                 if (time & WDIOS_DISABLECARD)
206                         at32_wdt_stop();
207                 if (time & WDIOS_ENABLECARD)
208                         at32_wdt_start();
209                 ret = 0;
210                 break;
211         }
212
213         return ret;
214 }
215
216 static ssize_t at32_wdt_write(struct file *file, const char __user *data,
217                                 size_t len, loff_t *ppos)
218 {
219         /* See if we got the magic character 'V' and reload the timer */
220         if (len) {
221                 if (!nowayout) {
222                         size_t i;
223
224                         /*
225                          * note: just in case someone wrote the magic
226                          * character five months ago...
227                          */
228                         expect_release = 0;
229
230                         /*
231                          * scan to see whether or not we got the magic
232                          * character
233                          */
234                         for (i = 0; i != len; i++) {
235                                 char c;
236                                 if (get_user(c, data+i))
237                                         return -EFAULT;
238                                 if (c == 'V')
239                                         expect_release = 42;
240                         }
241                 }
242                 /* someone wrote to us, we should pat the watchdog */
243                 at32_wdt_pat();
244         }
245         return len;
246 }
247
248 static const struct file_operations at32_wdt_fops = {
249         .owner          = THIS_MODULE,
250         .llseek         = no_llseek,
251         .ioctl          = at32_wdt_ioctl,
252         .open           = at32_wdt_open,
253         .release        = at32_wdt_close,
254         .write          = at32_wdt_write,
255 };
256
257 static int __init at32_wdt_probe(struct platform_device *pdev)
258 {
259         struct resource *regs;
260         int ret;
261
262         if (wdt) {
263                 dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n");
264                 return -EBUSY;
265         }
266
267         regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
268         if (!regs) {
269                 dev_dbg(&pdev->dev, "missing mmio resource\n");
270                 return -ENXIO;
271         }
272
273         wdt = kzalloc(sizeof(struct wdt_at32ap700x), GFP_KERNEL);
274         if (!wdt) {
275                 dev_dbg(&pdev->dev, "no memory for wdt structure\n");
276                 return -ENOMEM;
277         }
278
279         wdt->regs = ioremap(regs->start, regs->end - regs->start + 1);
280         if (!wdt->regs) {
281                 ret = -ENOMEM;
282                 dev_dbg(&pdev->dev, "could not map I/O memory\n");
283                 goto err_free;
284         }
285         spin_lock_init(&wdt->io_lock);
286         wdt->users = 0;
287         wdt->miscdev.minor = WATCHDOG_MINOR;
288         wdt->miscdev.name = "watchdog";
289         wdt->miscdev.fops = &at32_wdt_fops;
290
291         if (at32_wdt_settimeout(timeout)) {
292                 at32_wdt_settimeout(TIMEOUT_DEFAULT);
293                 dev_dbg(&pdev->dev,
294                         "default timeout invalid, set to %d sec.\n",
295                         TIMEOUT_DEFAULT);
296         }
297
298         ret = misc_register(&wdt->miscdev);
299         if (ret) {
300                 dev_dbg(&pdev->dev, "failed to register wdt miscdev\n");
301                 goto err_iounmap;
302         }
303
304         platform_set_drvdata(pdev, wdt);
305         wdt->miscdev.parent = &pdev->dev;
306         dev_info(&pdev->dev,
307                 "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n",
308                 wdt->regs, wdt->timeout, nowayout);
309
310         return 0;
311
312 err_iounmap:
313         iounmap(wdt->regs);
314 err_free:
315         kfree(wdt);
316         wdt = NULL;
317         return ret;
318 }
319
320 static int __exit at32_wdt_remove(struct platform_device *pdev)
321 {
322         if (wdt && platform_get_drvdata(pdev) == wdt) {
323                 /* Stop the timer before we leave */
324                 if (!nowayout)
325                         at32_wdt_stop();
326
327                 misc_deregister(&wdt->miscdev);
328                 iounmap(wdt->regs);
329                 kfree(wdt);
330                 wdt = NULL;
331                 platform_set_drvdata(pdev, NULL);
332         }
333
334         return 0;
335 }
336
337 static void at32_wdt_shutdown(struct platform_device *pdev)
338 {
339         at32_wdt_stop();
340 }
341
342 #ifdef CONFIG_PM
343 static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message)
344 {
345         at32_wdt_stop();
346         return 0;
347 }
348
349 static int at32_wdt_resume(struct platform_device *pdev)
350 {
351         if (wdt->users)
352                 at32_wdt_start();
353         return 0;
354 }
355 #else
356 #define at32_wdt_suspend NULL
357 #define at32_wdt_resume NULL
358 #endif
359
360 static struct platform_driver at32_wdt_driver = {
361         .remove         = __exit_p(at32_wdt_remove),
362         .suspend        = at32_wdt_suspend,
363         .resume         = at32_wdt_resume,
364         .driver         = {
365                 .name   = "at32_wdt",
366                 .owner  = THIS_MODULE,
367         },
368         .shutdown       = at32_wdt_shutdown,
369 };
370
371 static int __init at32_wdt_init(void)
372 {
373         return platform_driver_probe(&at32_wdt_driver, at32_wdt_probe);
374 }
375 module_init(at32_wdt_init);
376
377 static void __exit at32_wdt_exit(void)
378 {
379         platform_driver_unregister(&at32_wdt_driver);
380 }
381 module_exit(at32_wdt_exit);
382
383 MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
384 MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X");
385 MODULE_LICENSE("GPL");
386 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);