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