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