Root/
1 | /* |
2 | * omap_wdt.c |
3 | * |
4 | * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 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@lxorguk.ukuu.org.uk> |
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 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
30 | |
31 | #include <linux/module.h> |
32 | #include <linux/types.h> |
33 | #include <linux/kernel.h> |
34 | #include <linux/fs.h> |
35 | #include <linux/mm.h> |
36 | #include <linux/miscdevice.h> |
37 | #include <linux/watchdog.h> |
38 | #include <linux/reboot.h> |
39 | #include <linux/init.h> |
40 | #include <linux/err.h> |
41 | #include <linux/platform_device.h> |
42 | #include <linux/moduleparam.h> |
43 | #include <linux/bitops.h> |
44 | #include <linux/io.h> |
45 | #include <linux/uaccess.h> |
46 | #include <linux/slab.h> |
47 | #include <linux/pm_runtime.h> |
48 | #include <mach/hardware.h> |
49 | #include <plat/prcm.h> |
50 | |
51 | #include "omap_wdt.h" |
52 | |
53 | static struct platform_device *omap_wdt_dev; |
54 | |
55 | static unsigned timer_margin; |
56 | module_param(timer_margin, uint, 0); |
57 | MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); |
58 | |
59 | static unsigned int wdt_trgr_pattern = 0x1234; |
60 | static DEFINE_SPINLOCK(wdt_lock); |
61 | |
62 | struct omap_wdt_dev { |
63 | void __iomem *base; /* physical */ |
64 | struct device *dev; |
65 | int omap_wdt_users; |
66 | struct resource *mem; |
67 | struct miscdevice omap_wdt_miscdev; |
68 | }; |
69 | |
70 | static void omap_wdt_ping(struct omap_wdt_dev *wdev) |
71 | { |
72 | void __iomem *base = wdev->base; |
73 | |
74 | /* wait for posted write to complete */ |
75 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
76 | cpu_relax(); |
77 | |
78 | wdt_trgr_pattern = ~wdt_trgr_pattern; |
79 | __raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); |
80 | |
81 | /* wait for posted write to complete */ |
82 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
83 | cpu_relax(); |
84 | /* reloaded WCRR from WLDR */ |
85 | } |
86 | |
87 | static void omap_wdt_enable(struct omap_wdt_dev *wdev) |
88 | { |
89 | void __iomem *base = wdev->base; |
90 | |
91 | /* Sequence to enable the watchdog */ |
92 | __raw_writel(0xBBBB, base + OMAP_WATCHDOG_SPR); |
93 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) |
94 | cpu_relax(); |
95 | |
96 | __raw_writel(0x4444, base + OMAP_WATCHDOG_SPR); |
97 | while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) |
98 | cpu_relax(); |
99 | } |
100 | |
101 | static void omap_wdt_disable(struct omap_wdt_dev *wdev) |
102 | { |
103 | void __iomem *base = wdev->base; |
104 | |
105 | /* sequence required to disable watchdog */ |
106 | __raw_writel(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ |
107 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) |
108 | cpu_relax(); |
109 | |
110 | __raw_writel(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ |
111 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) |
112 | cpu_relax(); |
113 | } |
114 | |
115 | static void omap_wdt_adjust_timeout(unsigned new_timeout) |
116 | { |
117 | if (new_timeout < TIMER_MARGIN_MIN) |
118 | new_timeout = TIMER_MARGIN_DEFAULT; |
119 | if (new_timeout > TIMER_MARGIN_MAX) |
120 | new_timeout = TIMER_MARGIN_MAX; |
121 | timer_margin = new_timeout; |
122 | } |
123 | |
124 | static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) |
125 | { |
126 | u32 pre_margin = GET_WLDR_VAL(timer_margin); |
127 | void __iomem *base = wdev->base; |
128 | |
129 | /* just count up at 32 KHz */ |
130 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) |
131 | cpu_relax(); |
132 | |
133 | __raw_writel(pre_margin, base + OMAP_WATCHDOG_LDR); |
134 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) |
135 | cpu_relax(); |
136 | } |
137 | |
138 | /* |
139 | * Allow only one task to hold it open |
140 | */ |
141 | static int omap_wdt_open(struct inode *inode, struct file *file) |
142 | { |
143 | struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); |
144 | void __iomem *base = wdev->base; |
145 | |
146 | if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) |
147 | return -EBUSY; |
148 | |
149 | pm_runtime_get_sync(wdev->dev); |
150 | |
151 | /* initialize prescaler */ |
152 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) |
153 | cpu_relax(); |
154 | |
155 | __raw_writel((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); |
156 | while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) |
157 | cpu_relax(); |
158 | |
159 | file->private_data = (void *) wdev; |
160 | |
161 | omap_wdt_set_timeout(wdev); |
162 | omap_wdt_ping(wdev); /* trigger loading of new timeout value */ |
163 | omap_wdt_enable(wdev); |
164 | |
165 | return nonseekable_open(inode, file); |
166 | } |
167 | |
168 | static int omap_wdt_release(struct inode *inode, struct file *file) |
169 | { |
170 | struct omap_wdt_dev *wdev = file->private_data; |
171 | |
172 | /* |
173 | * Shut off the timer unless NOWAYOUT is defined. |
174 | */ |
175 | #ifndef CONFIG_WATCHDOG_NOWAYOUT |
176 | omap_wdt_disable(wdev); |
177 | |
178 | pm_runtime_put_sync(wdev->dev); |
179 | #else |
180 | pr_crit("Unexpected close, not stopping!\n"); |
181 | #endif |
182 | wdev->omap_wdt_users = 0; |
183 | |
184 | return 0; |
185 | } |
186 | |
187 | static ssize_t omap_wdt_write(struct file *file, const char __user *data, |
188 | size_t len, loff_t *ppos) |
189 | { |
190 | struct omap_wdt_dev *wdev = file->private_data; |
191 | |
192 | /* Refresh LOAD_TIME. */ |
193 | if (len) { |
194 | spin_lock(&wdt_lock); |
195 | omap_wdt_ping(wdev); |
196 | spin_unlock(&wdt_lock); |
197 | } |
198 | return len; |
199 | } |
200 | |
201 | static long omap_wdt_ioctl(struct file *file, unsigned int cmd, |
202 | unsigned long arg) |
203 | { |
204 | struct omap_wdt_dev *wdev; |
205 | int new_margin; |
206 | static const struct watchdog_info ident = { |
207 | .identity = "OMAP Watchdog", |
208 | .options = WDIOF_SETTIMEOUT, |
209 | .firmware_version = 0, |
210 | }; |
211 | |
212 | wdev = file->private_data; |
213 | |
214 | switch (cmd) { |
215 | case WDIOC_GETSUPPORT: |
216 | return copy_to_user((struct watchdog_info __user *)arg, &ident, |
217 | sizeof(ident)); |
218 | case WDIOC_GETSTATUS: |
219 | return put_user(0, (int __user *)arg); |
220 | case WDIOC_GETBOOTSTATUS: |
221 | if (cpu_is_omap16xx()) |
222 | return put_user(__raw_readw(ARM_SYSST), |
223 | (int __user *)arg); |
224 | if (cpu_is_omap24xx()) |
225 | return put_user(omap_prcm_get_reset_sources(), |
226 | (int __user *)arg); |
227 | return put_user(0, (int __user *)arg); |
228 | case WDIOC_KEEPALIVE: |
229 | spin_lock(&wdt_lock); |
230 | omap_wdt_ping(wdev); |
231 | spin_unlock(&wdt_lock); |
232 | return 0; |
233 | case WDIOC_SETTIMEOUT: |
234 | if (get_user(new_margin, (int __user *)arg)) |
235 | return -EFAULT; |
236 | omap_wdt_adjust_timeout(new_margin); |
237 | |
238 | spin_lock(&wdt_lock); |
239 | omap_wdt_disable(wdev); |
240 | omap_wdt_set_timeout(wdev); |
241 | omap_wdt_enable(wdev); |
242 | |
243 | omap_wdt_ping(wdev); |
244 | spin_unlock(&wdt_lock); |
245 | /* Fall */ |
246 | case WDIOC_GETTIMEOUT: |
247 | return put_user(timer_margin, (int __user *)arg); |
248 | default: |
249 | return -ENOTTY; |
250 | } |
251 | } |
252 | |
253 | static const struct file_operations omap_wdt_fops = { |
254 | .owner = THIS_MODULE, |
255 | .write = omap_wdt_write, |
256 | .unlocked_ioctl = omap_wdt_ioctl, |
257 | .open = omap_wdt_open, |
258 | .release = omap_wdt_release, |
259 | .llseek = no_llseek, |
260 | }; |
261 | |
262 | static int __devinit omap_wdt_probe(struct platform_device *pdev) |
263 | { |
264 | struct resource *res, *mem; |
265 | struct omap_wdt_dev *wdev; |
266 | int ret; |
267 | |
268 | /* reserve static register mappings */ |
269 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
270 | if (!res) { |
271 | ret = -ENOENT; |
272 | goto err_get_resource; |
273 | } |
274 | |
275 | if (omap_wdt_dev) { |
276 | ret = -EBUSY; |
277 | goto err_busy; |
278 | } |
279 | |
280 | mem = request_mem_region(res->start, resource_size(res), pdev->name); |
281 | if (!mem) { |
282 | ret = -EBUSY; |
283 | goto err_busy; |
284 | } |
285 | |
286 | wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); |
287 | if (!wdev) { |
288 | ret = -ENOMEM; |
289 | goto err_kzalloc; |
290 | } |
291 | |
292 | wdev->omap_wdt_users = 0; |
293 | wdev->mem = mem; |
294 | wdev->dev = &pdev->dev; |
295 | |
296 | wdev->base = ioremap(res->start, resource_size(res)); |
297 | if (!wdev->base) { |
298 | ret = -ENOMEM; |
299 | goto err_ioremap; |
300 | } |
301 | |
302 | platform_set_drvdata(pdev, wdev); |
303 | |
304 | pm_runtime_enable(wdev->dev); |
305 | pm_runtime_get_sync(wdev->dev); |
306 | |
307 | omap_wdt_disable(wdev); |
308 | omap_wdt_adjust_timeout(timer_margin); |
309 | |
310 | wdev->omap_wdt_miscdev.parent = &pdev->dev; |
311 | wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR; |
312 | wdev->omap_wdt_miscdev.name = "watchdog"; |
313 | wdev->omap_wdt_miscdev.fops = &omap_wdt_fops; |
314 | |
315 | ret = misc_register(&(wdev->omap_wdt_miscdev)); |
316 | if (ret) |
317 | goto err_misc; |
318 | |
319 | pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", |
320 | __raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, |
321 | timer_margin); |
322 | |
323 | pm_runtime_put_sync(wdev->dev); |
324 | |
325 | omap_wdt_dev = pdev; |
326 | |
327 | return 0; |
328 | |
329 | err_misc: |
330 | pm_runtime_disable(wdev->dev); |
331 | platform_set_drvdata(pdev, NULL); |
332 | iounmap(wdev->base); |
333 | |
334 | err_ioremap: |
335 | wdev->base = NULL; |
336 | kfree(wdev); |
337 | |
338 | err_kzalloc: |
339 | release_mem_region(res->start, resource_size(res)); |
340 | |
341 | err_busy: |
342 | err_get_resource: |
343 | |
344 | return ret; |
345 | } |
346 | |
347 | static void omap_wdt_shutdown(struct platform_device *pdev) |
348 | { |
349 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
350 | |
351 | if (wdev->omap_wdt_users) { |
352 | omap_wdt_disable(wdev); |
353 | pm_runtime_put_sync(wdev->dev); |
354 | } |
355 | } |
356 | |
357 | static int __devexit omap_wdt_remove(struct platform_device *pdev) |
358 | { |
359 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
360 | struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
361 | |
362 | pm_runtime_disable(wdev->dev); |
363 | if (!res) |
364 | return -ENOENT; |
365 | |
366 | misc_deregister(&(wdev->omap_wdt_miscdev)); |
367 | release_mem_region(res->start, resource_size(res)); |
368 | platform_set_drvdata(pdev, NULL); |
369 | |
370 | iounmap(wdev->base); |
371 | |
372 | kfree(wdev); |
373 | omap_wdt_dev = NULL; |
374 | |
375 | return 0; |
376 | } |
377 | |
378 | #ifdef CONFIG_PM |
379 | |
380 | /* REVISIT ... not clear this is the best way to handle system suspend; and |
381 | * it's very inappropriate for selective device suspend (e.g. suspending this |
382 | * through sysfs rather than by stopping the watchdog daemon). Also, this |
383 | * may not play well enough with NOWAYOUT... |
384 | */ |
385 | |
386 | static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) |
387 | { |
388 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
389 | |
390 | if (wdev->omap_wdt_users) { |
391 | omap_wdt_disable(wdev); |
392 | pm_runtime_put_sync(wdev->dev); |
393 | } |
394 | |
395 | return 0; |
396 | } |
397 | |
398 | static int omap_wdt_resume(struct platform_device *pdev) |
399 | { |
400 | struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
401 | |
402 | if (wdev->omap_wdt_users) { |
403 | pm_runtime_get_sync(wdev->dev); |
404 | omap_wdt_enable(wdev); |
405 | omap_wdt_ping(wdev); |
406 | } |
407 | |
408 | return 0; |
409 | } |
410 | |
411 | #else |
412 | #define omap_wdt_suspend NULL |
413 | #define omap_wdt_resume NULL |
414 | #endif |
415 | |
416 | static const struct of_device_id omap_wdt_of_match[] = { |
417 | { .compatible = "ti,omap3-wdt", }, |
418 | {}, |
419 | }; |
420 | MODULE_DEVICE_TABLE(of, omap_wdt_of_match); |
421 | |
422 | static struct platform_driver omap_wdt_driver = { |
423 | .probe = omap_wdt_probe, |
424 | .remove = __devexit_p(omap_wdt_remove), |
425 | .shutdown = omap_wdt_shutdown, |
426 | .suspend = omap_wdt_suspend, |
427 | .resume = omap_wdt_resume, |
428 | .driver = { |
429 | .owner = THIS_MODULE, |
430 | .name = "omap_wdt", |
431 | .of_match_table = omap_wdt_of_match, |
432 | }, |
433 | }; |
434 | |
435 | module_platform_driver(omap_wdt_driver); |
436 | |
437 | MODULE_AUTHOR("George G. Davis"); |
438 | MODULE_LICENSE("GPL"); |
439 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
440 | MODULE_ALIAS("platform:omap_wdt"); |
441 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9