]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - drivers/char/watchdog/omap_wdt.c
2c28307b28bd47c5a8bd83581660f065f568393d
[linux-2.6-omap-h63xx.git] / drivers / char / watchdog / omap_wdt.c
1 /*
2  * linux/drivers/char/omap_wdt.c
3  *
4  * Watchdog driver for the TI OMAP 16xx & 24xx 32KHz (non-secure) watchdog
5  *
6  * Author: MontaVista Software, Inc.
7  *       <gdavis@mvista.com> or <source@mvista.com>
8  *
9  * 2003 (c) MontaVista Software, Inc. This file is licensed under the
10  * terms of the GNU General Public License version 2. This program is
11  * licensed "as is" without any warranty of any kind, whether express
12  * or implied.
13  *
14  * History:
15  *
16  * 20030527: George G. Davis <gdavis@mvista.com>
17  *      Initially based on linux-2.4.19-rmk7-pxa1/drivers/char/sa1100_wdt.c
18  *      (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
19  *      Based on SoftDog driver by Alan Cox <alan@redhat.com>
20  *
21  * Copyright (c) 2004 Texas Instruments.
22  *      1. Modified to support OMAP1610 32-KHz watchdog timer
23  *      2. Ported to 2.6 kernel
24  *
25  * Copyright (c) 2005 David Brownell
26  *      Use the driver model and standard identifiers; handle bigger timeouts.
27  */
28
29 #include <linux/module.h>
30 #include <linux/config.h>
31 #include <linux/types.h>
32 #include <linux/kernel.h>
33 #include <linux/fs.h>
34 #include <linux/mm.h>
35 #include <linux/miscdevice.h>
36 #include <linux/watchdog.h>
37 #include <linux/reboot.h>
38 #include <linux/smp_lock.h>
39 #include <linux/init.h>
40 #include <linux/err.h>
41 #include <linux/platform_device.h>
42 #include <linux/moduleparam.h>
43
44 #include <asm/io.h>
45 #include <asm/uaccess.h>
46 #include <asm/hardware.h>
47 #include <asm/bitops.h>
48 #include <asm/hardware/clock.h>
49
50 #ifdef CONFIG_ARCH_OMAP24XX
51 #include <asm/arch/prcm.h>
52 #endif
53
54 #include "omap_wdt.h"
55
56 static unsigned timer_margin;
57 module_param(timer_margin, uint, 0);
58 MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
59
60 static int omap_wdt_users;
61 static struct clk *armwdt_ck = NULL;
62 static struct clk *mpu_wdt_ick = NULL;
63 static struct clk *mpu_wdt_fck = NULL;
64
65 static unsigned int wdt_trgr_pattern = 0x1234;
66
67 static void omap_wdt_ping(void)
68 {
69         while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) ;        /* wait for posted write to complete */
70         wdt_trgr_pattern = ~wdt_trgr_pattern;
71         omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR));
72         while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) ;        /* wait for posted write to complete */
73         /* reloaded WCRR from WLDR */
74 }
75
76 static void omap_wdt_enable(void)
77 {
78         /* Sequence to enable the watchdog */
79         omap_writel(0xBBBB, OMAP_WATCHDOG_SPR);
80         while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) ;
81         omap_writel(0x4444, OMAP_WATCHDOG_SPR);
82         while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) ;
83 }
84
85 static void omap_wdt_disable(void)
86 {
87         /* sequence required to disable watchdog */
88         omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */
89         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) ;
90         omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */
91         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) ;
92 }
93
94 static void omap_wdt_adjust_timeout(unsigned new_timeout)
95 {
96         if (new_timeout < TIMER_MARGIN_MIN)
97                 new_timeout = TIMER_MARGIN_DEFAULT;
98         if (new_timeout > TIMER_MARGIN_MAX)
99                 new_timeout = TIMER_MARGIN_MAX;
100         timer_margin = new_timeout;
101 }
102
103 static void omap_wdt_set_timeout(void)
104 {
105         u32 pre_margin = GET_WLDR_VAL(timer_margin);
106
107         /* just count up at 32 KHz */
108         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04)
109                 continue;
110         omap_writel(pre_margin, OMAP_WATCHDOG_LDR);
111         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04)
112                 continue;
113 }
114
115 /*
116  *      Allow only one task to hold it open
117  */
118
119 static int omap_wdt_open(struct inode *inode, struct file *file)
120 {
121         if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users))
122                 return -EBUSY;
123
124         if (cpu_is_omap16xx()) {
125                 clk_use(armwdt_ck);     /* Enable the clock */
126         }
127
128         if (cpu_is_omap24xx()) {
129                 clk_use(mpu_wdt_ick);   /* Enable the interface clock */
130                 clk_use(mpu_wdt_fck);   /* Enable the functional clock */
131         }
132
133         /* initialize prescaler */
134         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01)
135                 continue;
136         omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL);
137         while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01)
138                 continue;
139
140         omap_wdt_set_timeout();
141         omap_wdt_enable();
142         return 0;
143 }
144
145 static int omap_wdt_release(struct inode *inode, struct file *file)
146 {
147         /*
148          *      Shut off the timer unless NOWAYOUT is defined.
149          */
150 #ifndef CONFIG_WATCHDOG_NOWAYOUT
151         omap_wdt_disable();
152
153         if (cpu_is_omap16xx()) {
154                 clk_unuse(armwdt_ck);   /* Disable the clock */
155                 clk_put(armwdt_ck);
156                 armwdt_ck = NULL;
157         }
158
159         if (cpu_is_omap24xx()) {
160                 clk_unuse(mpu_wdt_ick); /* Disable the clock */
161                 clk_unuse(mpu_wdt_fck); /* Disable the clock */
162                 clk_put(mpu_wdt_ick);
163                 clk_put(mpu_wdt_fck);
164                 mpu_wdt_ick = NULL;
165                 mpu_wdt_fck = NULL;
166         }
167 #else
168         printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n");
169 #endif
170         omap_wdt_users = 0;
171         return 0;
172 }
173
174 static ssize_t
175 omap_wdt_write(struct file *file, const char __user * data,
176         size_t len, loff_t * ppos)
177 {
178         /* Refresh LOAD_TIME. */
179         if (len)
180                 omap_wdt_ping();
181         return len;
182 }
183
184 static int
185 omap_wdt_ioctl(struct inode *inode, struct file *file,
186         unsigned int cmd, unsigned long arg)
187 {
188         int new_margin;
189         static struct watchdog_info ident = {
190                 .identity = "OMAP Watchdog",
191                 .options = WDIOF_SETTIMEOUT,
192                 .firmware_version = 0,
193         };
194
195         switch (cmd) {
196         default:
197                 return -ENOIOCTLCMD;
198         case WDIOC_GETSUPPORT:
199                 return copy_to_user((struct watchdog_info __user *)arg, &ident,
200         sizeof(ident));
201         case WDIOC_GETSTATUS:
202                 return put_user(0, (int __user *)arg);
203         case WDIOC_GETBOOTSTATUS:
204                 if (cpu_is_omap16xx())
205                         return put_user(omap_readw(ARM_SYSST),
206                                         (int __user *)arg);
207                 if (cpu_is_omap24xx())
208                         return put_user(RM_RSTST_WKUP, (int __user *)arg);
209         case WDIOC_KEEPALIVE:
210                 omap_wdt_ping();
211                 return 0;
212         case WDIOC_SETTIMEOUT:
213                 if (get_user(new_margin, (int __user *)arg))
214                         return -EFAULT;
215                 omap_wdt_adjust_timeout(new_margin);
216
217                 omap_wdt_disable();
218                 omap_wdt_set_timeout();
219                 omap_wdt_enable();
220
221                 omap_wdt_ping();
222                 /* Fall */
223         case WDIOC_GETTIMEOUT:
224                 return put_user(timer_margin, (int __user *)arg);
225         }
226 }
227
228 static struct file_operations omap_wdt_fops = {
229         .owner = THIS_MODULE,
230         .write = omap_wdt_write,
231         .ioctl = omap_wdt_ioctl,
232         .open = omap_wdt_open,
233         .release = omap_wdt_release,
234 };
235
236 static struct miscdevice omap_wdt_miscdev = {
237         .minor = WATCHDOG_MINOR,
238         .name = "watchdog",
239         .fops = &omap_wdt_fops
240 };
241
242 static int __init omap_wdt_probe(struct device *dev)
243 {
244         struct platform_device *pdev = to_platform_device(dev);
245         struct resource *res, *mem;
246         int ret;
247
248         /* reserve static register mappings */
249         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
250         if (!res)
251                 return -ENOENT;
252
253         mem = request_mem_region(res->start, res->end - res->start + 1,
254                                  pdev->name);
255         if (mem == NULL)
256                 return -EBUSY;
257
258         dev_set_drvdata(dev, mem);
259
260         omap_wdt_users = 0;
261
262         if (cpu_is_omap16xx()) {
263                 armwdt_ck = clk_get(dev, "armwdt_ck");
264                 if (IS_ERR(armwdt_ck)) {
265                         ret = PTR_ERR(armwdt_ck);
266                         armwdt_ck = NULL;
267                         goto fail;
268                 }
269         }
270
271         if (cpu_is_omap24xx()) {
272                 mpu_wdt_ick = clk_get(dev, "mpu_wdt_ick");
273                 if (IS_ERR(mpu_wdt_ick)) {
274                         ret = PTR_ERR(mpu_wdt_ick);
275                         mpu_wdt_ick = NULL;
276                         goto fail;
277                 }
278                 mpu_wdt_fck = clk_get(dev, "mpu_wdt_fck");
279                 if (IS_ERR(mpu_wdt_fck)) {
280                         ret = PTR_ERR(mpu_wdt_fck);
281                         mpu_wdt_fck = NULL;
282                         goto fail;
283                 }
284         }
285
286         omap_wdt_disable();
287         omap_wdt_adjust_timeout(timer_margin);
288
289         omap_wdt_miscdev.dev = dev;
290         ret = misc_register(&omap_wdt_miscdev);
291         if (ret)
292                 goto fail;
293
294         pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin);
295
296         /* autogate OCP interface clock */
297         omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG);
298         return 0;
299
300 fail:
301         if (armwdt_ck)
302                 clk_put(armwdt_ck);
303         if (mpu_wdt_ick)
304                 clk_put(mpu_wdt_ick);
305         if (mpu_wdt_fck)
306                 clk_put(mpu_wdt_fck);
307         release_resource(mem);
308         return ret;
309 }
310
311 static void omap_wdt_shutdown(struct device *dev)
312 {
313         omap_wdt_disable();
314 }
315
316 static int __exit omap_wdt_remove(struct device *dev)
317 {
318         struct resource *mem = dev_get_drvdata(dev);
319         misc_deregister(&omap_wdt_miscdev);
320         release_resource(mem);
321         if (armwdt_ck)
322                 clk_put(armwdt_ck);
323         if (mpu_wdt_ick)
324                 clk_put(mpu_wdt_ick);
325         if (mpu_wdt_fck)
326                 clk_put(mpu_wdt_fck);
327         return 0;
328 }
329
330 #ifdef  CONFIG_PM
331
332 /* REVISIT ... not clear this is the best way to handle system suspend; and
333  * it's very inappropriate for selective device suspend (e.g. suspending this
334  * through sysfs rather than by stopping the watchdog daemon).  Also, this
335  * may not play well enough with NOWAYOUT...
336  */
337
338 static int omap_wdt_suspend(struct device *dev, pm_message_t state)
339 {
340         if (omap_wdt_users)
341                 omap_wdt_disable();
342         return 0;
343 }
344
345 static int omap_wdt_resume(struct device *dev)
346 {
347         if (omap_wdt_users) {
348                 omap_wdt_enable();
349                 omap_wdt_ping();
350         }
351         return 0;
352 }
353
354 #else
355 #define omap_wdt_suspend        NULL
356 #define omap_wdt_resume         NULL
357 #endif
358
359 static struct device_driver omap_wdt_driver = {
360         .name = "omap_wdt",
361         .bus = &platform_bus_type,
362         .probe = omap_wdt_probe,
363         .shutdown = omap_wdt_shutdown,
364         .remove = __exit_p(omap_wdt_remove),
365         .suspend = omap_wdt_suspend,
366         .resume = omap_wdt_resume,
367 };
368
369 static int __init omap_wdt_init(void)
370 {
371         return driver_register(&omap_wdt_driver);
372 }
373
374 static void __exit omap_wdt_exit(void)
375 {
376         driver_unregister(&omap_wdt_driver);
377 }
378
379 module_init(omap_wdt_init);
380 module_exit(omap_wdt_exit);
381
382 MODULE_AUTHOR("George G. Davis");
383 MODULE_LICENSE("GPL");
384 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);