Blame view
drivers/watchdog/omap_wdt.c
10.5 KB
7768a13c2
|
1 |
/* |
2817142f3
|
2 |
* omap_wdt.c |
7768a13c2
|
3 |
* |
2817142f3
|
4 |
* Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog |
7768a13c2
|
5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
* * Author: MontaVista Software, Inc. * <gdavis@mvista.com> or <source@mvista.com> * * 2003 (c) MontaVista Software, Inc. This file is licensed under the * terms of the GNU General Public License version 2. This program is * licensed "as is" without any warranty of any kind, whether express * or implied. * * History: * * 20030527: George G. Davis <gdavis@mvista.com> * Initially based on linux-2.4.19-rmk7-pxa1/drivers/char/sa1100_wdt.c * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> |
29fa0586d
|
19 |
* Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk> |
7768a13c2
|
20 21 22 23 24 25 26 27 28 29 |
* * Copyright (c) 2004 Texas Instruments. * 1. Modified to support OMAP1610 32-KHz watchdog timer * 2. Ported to 2.6 kernel * * Copyright (c) 2005 David Brownell * Use the driver model and standard identifiers; handle bigger timeouts. */ #include <linux/module.h> |
7768a13c2
|
30 31 32 33 34 35 36 |
#include <linux/types.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/reboot.h> |
7768a13c2
|
37 38 39 40 |
#include <linux/init.h> #include <linux/err.h> #include <linux/platform_device.h> #include <linux/moduleparam.h> |
1977f0327
|
41 |
#include <linux/bitops.h> |
089ab0791
|
42 |
#include <linux/io.h> |
12b9df7d2
|
43 |
#include <linux/uaccess.h> |
5a0e3ad6a
|
44 |
#include <linux/slab.h> |
7ec5ad0f3
|
45 |
#include <linux/pm_runtime.h> |
a09e64fbc
|
46 |
#include <mach/hardware.h> |
ce491cf85
|
47 |
#include <plat/prcm.h> |
7768a13c2
|
48 49 |
#include "omap_wdt.h" |
2817142f3
|
50 |
static struct platform_device *omap_wdt_dev; |
7768a13c2
|
51 52 53 |
static unsigned timer_margin; module_param(timer_margin, uint, 0); MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); |
7768a13c2
|
54 |
static unsigned int wdt_trgr_pattern = 0x1234; |
1334f3293
|
55 |
static DEFINE_SPINLOCK(wdt_lock); |
7768a13c2
|
56 |
|
2817142f3
|
57 58 59 60 |
struct omap_wdt_dev { void __iomem *base; /* physical */ struct device *dev; int omap_wdt_users; |
2817142f3
|
61 62 63 64 65 |
struct resource *mem; struct miscdevice omap_wdt_miscdev; }; static void omap_wdt_ping(struct omap_wdt_dev *wdev) |
7768a13c2
|
66 |
{ |
2817142f3
|
67 |
void __iomem *base = wdev->base; |
b3112180f
|
68 |
|
7768a13c2
|
69 |
/* wait for posted write to complete */ |
9f69e3b0c
|
70 |
while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
7768a13c2
|
71 |
cpu_relax(); |
b3112180f
|
72 |
|
7768a13c2
|
73 |
wdt_trgr_pattern = ~wdt_trgr_pattern; |
9f69e3b0c
|
74 |
__raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); |
b3112180f
|
75 |
|
7768a13c2
|
76 |
/* wait for posted write to complete */ |
9f69e3b0c
|
77 |
while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) |
7768a13c2
|
78 79 80 |
cpu_relax(); /* reloaded WCRR from WLDR */ } |
2817142f3
|
81 |
static void omap_wdt_enable(struct omap_wdt_dev *wdev) |
7768a13c2
|
82 |
{ |
b3112180f
|
83 |
void __iomem *base = wdev->base; |
7768a13c2
|
84 |
/* Sequence to enable the watchdog */ |
9f69e3b0c
|
85 86 |
__raw_writel(0xBBBB, base + OMAP_WATCHDOG_SPR); while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) |
7768a13c2
|
87 |
cpu_relax(); |
b3112180f
|
88 |
|
9f69e3b0c
|
89 90 |
__raw_writel(0x4444, base + OMAP_WATCHDOG_SPR); while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) |
7768a13c2
|
91 92 |
cpu_relax(); } |
2817142f3
|
93 |
static void omap_wdt_disable(struct omap_wdt_dev *wdev) |
7768a13c2
|
94 |
{ |
b3112180f
|
95 |
void __iomem *base = wdev->base; |
7768a13c2
|
96 |
/* sequence required to disable watchdog */ |
9f69e3b0c
|
97 98 |
__raw_writel(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) |
7768a13c2
|
99 |
cpu_relax(); |
b3112180f
|
100 |
|
9f69e3b0c
|
101 102 |
__raw_writel(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) |
7768a13c2
|
103 104 105 106 107 108 109 110 111 112 113 |
cpu_relax(); } static void omap_wdt_adjust_timeout(unsigned new_timeout) { if (new_timeout < TIMER_MARGIN_MIN) new_timeout = TIMER_MARGIN_DEFAULT; if (new_timeout > TIMER_MARGIN_MAX) new_timeout = TIMER_MARGIN_MAX; timer_margin = new_timeout; } |
2817142f3
|
114 |
static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) |
7768a13c2
|
115 116 |
{ u32 pre_margin = GET_WLDR_VAL(timer_margin); |
b3112180f
|
117 |
void __iomem *base = wdev->base; |
7768a13c2
|
118 |
|
0503add9d
|
119 |
pm_runtime_get_sync(wdev->dev); |
7768a13c2
|
120 |
/* just count up at 32 KHz */ |
9f69e3b0c
|
121 |
while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) |
7768a13c2
|
122 |
cpu_relax(); |
b3112180f
|
123 |
|
9f69e3b0c
|
124 125 |
__raw_writel(pre_margin, base + OMAP_WATCHDOG_LDR); while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) |
7768a13c2
|
126 |
cpu_relax(); |
0503add9d
|
127 128 |
pm_runtime_put_sync(wdev->dev); |
7768a13c2
|
129 130 131 132 133 |
} /* * Allow only one task to hold it open */ |
7768a13c2
|
134 135 |
static int omap_wdt_open(struct inode *inode, struct file *file) { |
b3112180f
|
136 137 |
struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); void __iomem *base = wdev->base; |
2817142f3
|
138 |
if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) |
7768a13c2
|
139 |
return -EBUSY; |
7ec5ad0f3
|
140 |
pm_runtime_get_sync(wdev->dev); |
7768a13c2
|
141 142 |
/* initialize prescaler */ |
9f69e3b0c
|
143 |
while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) |
7768a13c2
|
144 |
cpu_relax(); |
b3112180f
|
145 |
|
9f69e3b0c
|
146 147 |
__raw_writel((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) |
7768a13c2
|
148 |
cpu_relax(); |
2817142f3
|
149 150 151 |
file->private_data = (void *) wdev; omap_wdt_set_timeout(wdev); |
789cd4702
|
152 |
omap_wdt_ping(wdev); /* trigger loading of new timeout value */ |
2817142f3
|
153 |
omap_wdt_enable(wdev); |
b3112180f
|
154 |
|
0503add9d
|
155 |
pm_runtime_put_sync(wdev->dev); |
ec9505a7e
|
156 |
return nonseekable_open(inode, file); |
7768a13c2
|
157 158 159 160 |
} static int omap_wdt_release(struct inode *inode, struct file *file) { |
b3112180f
|
161 |
struct omap_wdt_dev *wdev = file->private_data; |
7768a13c2
|
162 163 164 165 |
/* * Shut off the timer unless NOWAYOUT is defined. */ #ifndef CONFIG_WATCHDOG_NOWAYOUT |
0503add9d
|
166 |
pm_runtime_get_sync(wdev->dev); |
7768a13c2
|
167 |
|
2817142f3
|
168 |
omap_wdt_disable(wdev); |
7768a13c2
|
169 |
|
7ec5ad0f3
|
170 |
pm_runtime_put_sync(wdev->dev); |
7768a13c2
|
171 172 173 174 |
#else printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping! "); #endif |
2817142f3
|
175 |
wdev->omap_wdt_users = 0; |
b3112180f
|
176 |
|
7768a13c2
|
177 178 |
return 0; } |
12b9df7d2
|
179 |
static ssize_t omap_wdt_write(struct file *file, const char __user *data, |
7768a13c2
|
180 181 |
size_t len, loff_t *ppos) { |
b3112180f
|
182 |
struct omap_wdt_dev *wdev = file->private_data; |
7768a13c2
|
183 |
/* Refresh LOAD_TIME. */ |
12b9df7d2
|
184 |
if (len) { |
0503add9d
|
185 |
pm_runtime_get_sync(wdev->dev); |
12b9df7d2
|
186 |
spin_lock(&wdt_lock); |
2817142f3
|
187 |
omap_wdt_ping(wdev); |
12b9df7d2
|
188 |
spin_unlock(&wdt_lock); |
0503add9d
|
189 |
pm_runtime_put_sync(wdev->dev); |
12b9df7d2
|
190 |
} |
7768a13c2
|
191 192 |
return len; } |
12b9df7d2
|
193 194 |
static long omap_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
7768a13c2
|
195 |
{ |
2817142f3
|
196 |
struct omap_wdt_dev *wdev; |
7768a13c2
|
197 |
int new_margin; |
12b9df7d2
|
198 |
static const struct watchdog_info ident = { |
7768a13c2
|
199 200 201 202 |
.identity = "OMAP Watchdog", .options = WDIOF_SETTIMEOUT, .firmware_version = 0, }; |
b3112180f
|
203 |
|
2817142f3
|
204 |
wdev = file->private_data; |
7768a13c2
|
205 206 |
switch (cmd) { |
7768a13c2
|
207 208 209 210 211 212 213 |
case WDIOC_GETSUPPORT: return copy_to_user((struct watchdog_info __user *)arg, &ident, sizeof(ident)); case WDIOC_GETSTATUS: return put_user(0, (int __user *)arg); case WDIOC_GETBOOTSTATUS: if (cpu_is_omap16xx()) |
9f69e3b0c
|
214 |
return put_user(__raw_readw(ARM_SYSST), |
7768a13c2
|
215 216 217 218 |
(int __user *)arg); if (cpu_is_omap24xx()) return put_user(omap_prcm_get_reset_sources(), (int __user *)arg); |
e2bf7c4c2
|
219 |
return put_user(0, (int __user *)arg); |
7768a13c2
|
220 |
case WDIOC_KEEPALIVE: |
0503add9d
|
221 |
pm_runtime_get_sync(wdev->dev); |
12b9df7d2
|
222 |
spin_lock(&wdt_lock); |
2817142f3
|
223 |
omap_wdt_ping(wdev); |
12b9df7d2
|
224 |
spin_unlock(&wdt_lock); |
0503add9d
|
225 |
pm_runtime_put_sync(wdev->dev); |
7768a13c2
|
226 227 228 229 230 |
return 0; case WDIOC_SETTIMEOUT: if (get_user(new_margin, (int __user *)arg)) return -EFAULT; omap_wdt_adjust_timeout(new_margin); |
0503add9d
|
231 |
pm_runtime_get_sync(wdev->dev); |
12b9df7d2
|
232 |
spin_lock(&wdt_lock); |
2817142f3
|
233 234 235 |
omap_wdt_disable(wdev); omap_wdt_set_timeout(wdev); omap_wdt_enable(wdev); |
7768a13c2
|
236 |
|
2817142f3
|
237 |
omap_wdt_ping(wdev); |
12b9df7d2
|
238 |
spin_unlock(&wdt_lock); |
0503add9d
|
239 |
pm_runtime_put_sync(wdev->dev); |
7768a13c2
|
240 241 242 |
/* Fall */ case WDIOC_GETTIMEOUT: return put_user(timer_margin, (int __user *)arg); |
0c06090c9
|
243 244 |
default: return -ENOTTY; |
7768a13c2
|
245 246 |
} } |
2b8693c06
|
247 |
static const struct file_operations omap_wdt_fops = { |
7768a13c2
|
248 249 |
.owner = THIS_MODULE, .write = omap_wdt_write, |
12b9df7d2
|
250 |
.unlocked_ioctl = omap_wdt_ioctl, |
7768a13c2
|
251 252 |
.open = omap_wdt_open, .release = omap_wdt_release, |
6038f373a
|
253 |
.llseek = no_llseek, |
7768a13c2
|
254 |
}; |
0e3912c75
|
255 |
static int __devinit omap_wdt_probe(struct platform_device *pdev) |
7768a13c2
|
256 257 |
{ struct resource *res, *mem; |
2817142f3
|
258 |
struct omap_wdt_dev *wdev; |
b3112180f
|
259 |
int ret; |
7768a13c2
|
260 261 262 |
/* reserve static register mappings */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
b3112180f
|
263 264 265 266 |
if (!res) { ret = -ENOENT; goto err_get_resource; } |
7768a13c2
|
267 |
|
b3112180f
|
268 269 270 271 |
if (omap_wdt_dev) { ret = -EBUSY; goto err_busy; } |
2817142f3
|
272 |
|
b782a5637
|
273 |
mem = request_mem_region(res->start, resource_size(res), pdev->name); |
b3112180f
|
274 275 276 277 |
if (!mem) { ret = -EBUSY; goto err_busy; } |
7768a13c2
|
278 |
|
2817142f3
|
279 280 281 |
wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); if (!wdev) { ret = -ENOMEM; |
b3112180f
|
282 |
goto err_kzalloc; |
2817142f3
|
283 |
} |
b3112180f
|
284 |
|
2817142f3
|
285 286 |
wdev->omap_wdt_users = 0; wdev->mem = mem; |
7ec5ad0f3
|
287 |
wdev->dev = &pdev->dev; |
2817142f3
|
288 |
|
b782a5637
|
289 |
wdev->base = ioremap(res->start, resource_size(res)); |
9f69e3b0c
|
290 291 |
if (!wdev->base) { ret = -ENOMEM; |
b3112180f
|
292 |
goto err_ioremap; |
9f69e3b0c
|
293 |
} |
2817142f3
|
294 |
platform_set_drvdata(pdev, wdev); |
7768a13c2
|
295 |
|
7ec5ad0f3
|
296 297 |
pm_runtime_enable(wdev->dev); pm_runtime_get_sync(wdev->dev); |
789cd4702
|
298 |
|
2817142f3
|
299 |
omap_wdt_disable(wdev); |
7768a13c2
|
300 |
omap_wdt_adjust_timeout(timer_margin); |
2817142f3
|
301 302 303 304 305 306 |
wdev->omap_wdt_miscdev.parent = &pdev->dev; wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR; wdev->omap_wdt_miscdev.name = "watchdog"; wdev->omap_wdt_miscdev.fops = &omap_wdt_fops; ret = misc_register(&(wdev->omap_wdt_miscdev)); |
7768a13c2
|
307 |
if (ret) |
b3112180f
|
308 |
goto err_misc; |
7768a13c2
|
309 |
|
2817142f3
|
310 311 |
pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec ", |
9f69e3b0c
|
312 |
__raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, |
2817142f3
|
313 |
timer_margin); |
7768a13c2
|
314 |
|
7ec5ad0f3
|
315 |
pm_runtime_put_sync(wdev->dev); |
789cd4702
|
316 |
|
2817142f3
|
317 |
omap_wdt_dev = pdev; |
7768a13c2
|
318 |
return 0; |
b3112180f
|
319 320 321 322 323 324 |
err_misc: platform_set_drvdata(pdev, NULL); iounmap(wdev->base); err_ioremap: wdev->base = NULL; |
b3112180f
|
325 326 327 |
kfree(wdev); err_kzalloc: |
b782a5637
|
328 |
release_mem_region(res->start, resource_size(res)); |
b3112180f
|
329 330 331 |
err_busy: err_get_resource: |
7768a13c2
|
332 333 334 335 336 |
return ret; } static void omap_wdt_shutdown(struct platform_device *pdev) { |
b3112180f
|
337 |
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
2817142f3
|
338 |
|
0503add9d
|
339 340 |
if (wdev->omap_wdt_users) { pm_runtime_get_sync(wdev->dev); |
2817142f3
|
341 |
omap_wdt_disable(wdev); |
0503add9d
|
342 343 |
pm_runtime_put_sync(wdev->dev); } |
7768a13c2
|
344 |
} |
0e3912c75
|
345 |
static int __devexit omap_wdt_remove(struct platform_device *pdev) |
7768a13c2
|
346 |
{ |
b3112180f
|
347 |
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
2817142f3
|
348 349 350 351 352 353 |
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -ENOENT; misc_deregister(&(wdev->omap_wdt_miscdev)); |
b782a5637
|
354 |
release_mem_region(res->start, resource_size(res)); |
2817142f3
|
355 |
platform_set_drvdata(pdev, NULL); |
b3112180f
|
356 |
|
9f69e3b0c
|
357 |
iounmap(wdev->base); |
2817142f3
|
358 359 |
kfree(wdev); omap_wdt_dev = NULL; |
b3112180f
|
360 |
|
7768a13c2
|
361 362 363 364 365 366 367 368 369 370 371 372 373 |
return 0; } #ifdef CONFIG_PM /* REVISIT ... not clear this is the best way to handle system suspend; and * it's very inappropriate for selective device suspend (e.g. suspending this * through sysfs rather than by stopping the watchdog daemon). Also, this * may not play well enough with NOWAYOUT... */ static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) { |
b3112180f
|
374 |
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
0503add9d
|
375 376 |
if (wdev->omap_wdt_users) { pm_runtime_get_sync(wdev->dev); |
2817142f3
|
377 |
omap_wdt_disable(wdev); |
0503add9d
|
378 379 |
pm_runtime_put_sync(wdev->dev); } |
b3112180f
|
380 |
|
7768a13c2
|
381 382 383 384 385 |
return 0; } static int omap_wdt_resume(struct platform_device *pdev) { |
b3112180f
|
386 |
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); |
2817142f3
|
387 |
if (wdev->omap_wdt_users) { |
0503add9d
|
388 |
pm_runtime_get_sync(wdev->dev); |
2817142f3
|
389 390 |
omap_wdt_enable(wdev); omap_wdt_ping(wdev); |
0503add9d
|
391 |
pm_runtime_put_sync(wdev->dev); |
7768a13c2
|
392 |
} |
b3112180f
|
393 |
|
7768a13c2
|
394 395 396 397 398 399 400 401 402 403 |
return 0; } #else #define omap_wdt_suspend NULL #define omap_wdt_resume NULL #endif static struct platform_driver omap_wdt_driver = { .probe = omap_wdt_probe, |
0e3912c75
|
404 |
.remove = __devexit_p(omap_wdt_remove), |
7768a13c2
|
405 406 407 408 409 410 411 412 |
.shutdown = omap_wdt_shutdown, .suspend = omap_wdt_suspend, .resume = omap_wdt_resume, .driver = { .owner = THIS_MODULE, .name = "omap_wdt", }, }; |
b8ec61189
|
413 |
module_platform_driver(omap_wdt_driver); |
7768a13c2
|
414 415 416 417 |
MODULE_AUTHOR("George G. Davis"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |
f37d193c7
|
418 |
MODULE_ALIAS("platform:omap_wdt"); |