aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/watchdog/tegra_wdt.c
diff options
context:
space:
mode:
authorJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
committerJonathan Herman <hermanjl@cs.unc.edu>2013-01-22 10:38:37 -0500
commitfcc9d2e5a6c89d22b8b773a64fb4ad21ac318446 (patch)
treea57612d1888735a2ec7972891b68c1ac5ec8faea /drivers/watchdog/tegra_wdt.c
parent8dea78da5cee153b8af9c07a2745f6c55057fe12 (diff)
Added missing tegra files.HEADmaster
Diffstat (limited to 'drivers/watchdog/tegra_wdt.c')
-rw-r--r--drivers/watchdog/tegra_wdt.c491
1 files changed, 491 insertions, 0 deletions
diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
new file mode 100644
index 00000000000..4fa31b25a36
--- /dev/null
+++ b/drivers/watchdog/tegra_wdt.c
@@ -0,0 +1,491 @@
1/*
2 * drivers/watchdog/tegra_wdt.c
3 *
4 * watchdog driver for NVIDIA tegra internal watchdog
5 *
6 * Copyright (c) 2011, NVIDIA Corporation.
7 *
8 * based on drivers/watchdog/softdog.c and drivers/watchdog/omap_wdt.c
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * more details.
19 *
20 * You should have received a copy of the GNU General Public License along
21 * with this program; if not, write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25#include <linux/fs.h>
26#include <linux/interrupt.h>
27#include <linux/io.h>
28#include <linux/kernel.h>
29#include <linux/miscdevice.h>
30#include <linux/notifier.h>
31#include <linux/platform_device.h>
32#include <linux/reboot.h>
33#include <linux/slab.h>
34#include <linux/spinlock.h>
35#include <linux/uaccess.h>
36#include <linux/watchdog.h>
37
38/* minimum and maximum watchdog trigger periods, in seconds */
39#define MIN_WDT_PERIOD 5
40#define MAX_WDT_PERIOD 1000
41
42enum tegra_wdt_status {
43 WDT_DISABLED = 1 << 0,
44 WDT_ENABLED = 1 << 1,
45 WDT_IOCTL_ENABBLED_AT_PROBE = 1 << 2,
46};
47
48struct tegra_wdt {
49 struct miscdevice miscdev;
50 struct notifier_block notifier;
51 struct resource *res_src;
52 struct resource *res_wdt;
53 unsigned long users;
54 void __iomem *wdt_source;
55 void __iomem *wdt_timer;
56 int irq;
57 int timeout;
58 int status;
59};
60
61static struct platform_device *tegra_wdt_dev;
62/*
63 * For spinlock lockup detection to work, the heartbeat should be 2*lockup
64 * for cases where the spinlock disabled irqs.
65 */
66static int heartbeat = 120; /* must be greater than MIN_WDT_PERIOD and lower than MAX_WDT_PERIOD */
67
68#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
69
70#define TIMER_PTV 0x0
71 #define TIMER_EN (1 << 31)
72 #define TIMER_PERIODIC (1 << 30)
73#define TIMER_PCR 0x4
74 #define TIMER_PCR_INTR (1 << 30)
75#define WDT_EN (1 << 5)
76#define WDT_SEL_TMR1 (0 << 4)
77#define WDT_SYS_RST (1 << 2)
78
79static void tegra_wdt_enable(struct tegra_wdt *wdt)
80{
81 u32 val;
82
83 /* since the watchdog reset occurs when a second interrupt
84 * is asserted before the first is processed, program the
85 * timer period to one-half of the watchdog period */
86 val = wdt->timeout * 1000000ul / 2;
87 val |= (TIMER_EN | TIMER_PERIODIC);
88 writel(val, wdt->wdt_timer + TIMER_PTV);
89
90 val = WDT_EN | WDT_SEL_TMR1 | WDT_SYS_RST;
91 writel(val, wdt->wdt_source);
92}
93
94static void tegra_wdt_disable(struct tegra_wdt *wdt)
95{
96 writel(0, wdt->wdt_source);
97 writel(0, wdt->wdt_timer + TIMER_PTV);
98}
99
100static inline void tegra_wdt_ping(struct tegra_wdt *wdt)
101{
102 return;
103}
104
105static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id)
106{
107 struct tegra_wdt *wdt = dev_id;
108
109 writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR);
110 return IRQ_HANDLED;
111}
112#elif defined(CONFIG_ARCH_TEGRA_3x_SOC)
113
114#define TIMER_PTV 0
115 #define TIMER_EN (1 << 31)
116 #define TIMER_PERIODIC (1 << 30)
117#define TIMER_PCR 0x4
118 #define TIMER_PCR_INTR (1 << 30)
119#define WDT_CFG (0)
120 #define WDT_CFG_TMR_SRC (0 << 0) /* for TMR10. */
121 #define WDT_CFG_PERIOD (1 << 4)
122 #define WDT_CFG_INT_EN (1 << 12)
123 #define WDT_CFG_SYS_RST_EN (1 << 14)
124 #define WDT_CFG_PMC2CAR_RST_EN (1 << 15)
125#define WDT_CMD (8)
126 #define WDT_CMD_START_COUNTER (1 << 0)
127 #define WDT_CMD_DISABLE_COUNTER (1 << 1)
128#define WDT_UNLOCK (0xC)
129 #define WDT_UNLOCK_PATTERN (0xC45A << 0)
130
131static void tegra_wdt_set_timeout(struct tegra_wdt *wdt, int sec)
132{
133 u32 ptv;
134
135 ptv = readl(wdt->wdt_timer + TIMER_PTV);
136
137 wdt->timeout = clamp(sec, MIN_WDT_PERIOD, MAX_WDT_PERIOD);
138 if (ptv & TIMER_EN) {
139 /* since the watchdog reset occurs when a fourth interrupt
140 * is asserted before the first is processed, program the
141 * timer period to one-fourth of the watchdog period */
142 ptv = (wdt->timeout * 1000000ul) / 4;
143 ptv |= (TIMER_EN | TIMER_PERIODIC);
144 writel(ptv, wdt->wdt_timer + TIMER_PTV);
145 }
146}
147
148static inline void tegra_wdt_ping(struct tegra_wdt *wdt)
149{
150 writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD);
151}
152
153static void tegra_wdt_enable(struct tegra_wdt *wdt)
154{
155 u32 val;
156
157 writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR);
158 val = (wdt->timeout * 1000000ul) / 4;
159 val |= (TIMER_EN | TIMER_PERIODIC);
160 writel(val, wdt->wdt_timer + TIMER_PTV);
161
162 val = WDT_CFG_TMR_SRC | WDT_CFG_PERIOD | /*WDT_CFG_INT_EN |*/
163 /*WDT_CFG_SYS_RST_EN |*/ WDT_CFG_PMC2CAR_RST_EN;
164 writel(val, wdt->wdt_source + WDT_CFG);
165 writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD);
166}
167
168static void tegra_wdt_disable(struct tegra_wdt *wdt)
169{
170 writel(WDT_UNLOCK_PATTERN, wdt->wdt_source + WDT_UNLOCK);
171 writel(WDT_CMD_DISABLE_COUNTER, wdt->wdt_source + WDT_CMD);
172
173 writel(0, wdt->wdt_timer + TIMER_PTV);
174}
175
176static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id)
177{
178 struct tegra_wdt *wdt = dev_id;
179
180 tegra_wdt_ping(wdt);
181 return IRQ_HANDLED;
182}
183#endif
184
185static int tegra_wdt_notify(struct notifier_block *this,
186 unsigned long code, void *dev)
187{
188 struct tegra_wdt *wdt = container_of(this, struct tegra_wdt, notifier);
189
190 if (code == SYS_DOWN || code == SYS_HALT)
191 tegra_wdt_disable(wdt);
192 return NOTIFY_DONE;
193}
194
195static int tegra_wdt_open(struct inode *inode, struct file *file)
196{
197 struct tegra_wdt *wdt = platform_get_drvdata(tegra_wdt_dev);
198
199 if (test_and_set_bit(1, &wdt->users))
200 return -EBUSY;
201
202 wdt->status |= WDT_ENABLED;
203 wdt->timeout = heartbeat;
204 tegra_wdt_enable(wdt);
205 file->private_data = wdt;
206 return nonseekable_open(inode, file);
207}
208
209static int tegra_wdt_release(struct inode *inode, struct file *file)
210{
211 struct tegra_wdt *wdt = file->private_data;
212
213 if (wdt->status == WDT_ENABLED) {
214#ifndef CONFIG_WATCHDOG_NOWAYOUT
215 tegra_wdt_disable(wdt);
216 wdt->status = WDT_DISABLED;
217#endif
218 }
219 wdt->users = 0;
220 return 0;
221}
222
223static long tegra_wdt_ioctl(struct file *file, unsigned int cmd,
224 unsigned long arg)
225{
226 struct tegra_wdt *wdt = file->private_data;
227 static DEFINE_SPINLOCK(lock);
228 int new_timeout;
229 int option;
230 static const struct watchdog_info ident = {
231 .identity = "Tegra Watchdog",
232 .options = WDIOF_SETTIMEOUT,
233 .firmware_version = 0,
234 };
235
236 switch (cmd) {
237 case WDIOC_GETSUPPORT:
238 return copy_to_user((struct watchdog_info __user *)arg, &ident,
239 sizeof(ident));
240 case WDIOC_GETSTATUS:
241 case WDIOC_GETBOOTSTATUS:
242 return put_user(0, (int __user *)arg);
243
244 case WDIOC_KEEPALIVE:
245 spin_lock(&lock);
246 tegra_wdt_ping(wdt);
247 spin_unlock(&lock);
248 return 0;
249
250 case WDIOC_SETTIMEOUT:
251 if (get_user(new_timeout, (int __user *)arg))
252 return -EFAULT;
253 spin_lock(&lock);
254 tegra_wdt_disable(wdt);
255 wdt->timeout = clamp(new_timeout, MIN_WDT_PERIOD, MAX_WDT_PERIOD);
256 tegra_wdt_enable(wdt);
257 spin_unlock(&lock);
258 case WDIOC_GETTIMEOUT:
259 return put_user(wdt->timeout, (int __user *)arg);
260
261 case WDIOC_SETOPTIONS:
262#ifndef CONFIG_WATCHDOG_NOWAYOUT
263 if (get_user(option, (int __user *)arg))
264 return -EFAULT;
265 spin_lock(&lock);
266 if (option & WDIOS_DISABLECARD) {
267 wdt->status &= ~WDT_ENABLED;
268 wdt->status |= WDT_DISABLED;
269 tegra_wdt_disable(wdt);
270 } else if (option & WDIOS_ENABLECARD) {
271 tegra_wdt_enable(wdt);
272 wdt->status |= WDT_ENABLED;
273 wdt->status &= ~WDT_DISABLED;
274 } else {
275 spin_unlock(&lock);
276 return -EINVAL;
277 }
278 spin_unlock(&lock);
279 return 0;
280#else
281 return -EINVAL;
282#endif
283 }
284 return -ENOTTY;
285}
286
287static ssize_t tegra_wdt_write(struct file *file, const char __user *data,
288 size_t len, loff_t *ppos)
289{
290 return len;
291}
292
293static const struct file_operations tegra_wdt_fops = {
294 .owner = THIS_MODULE,
295 .llseek = no_llseek,
296 .write = tegra_wdt_write,
297 .unlocked_ioctl = tegra_wdt_ioctl,
298 .open = tegra_wdt_open,
299 .release = tegra_wdt_release,
300};
301
302static int tegra_wdt_probe(struct platform_device *pdev)
303{
304 struct resource *res_src, *res_wdt, *res_irq;
305 struct tegra_wdt *wdt;
306 u32 src;
307 int ret = 0;
308
309 if (pdev->id != -1) {
310 dev_err(&pdev->dev, "only id -1 supported\n");
311 return -ENODEV;
312 }
313
314 if (tegra_wdt_dev != NULL) {
315 dev_err(&pdev->dev, "watchdog already registered\n");
316 return -EIO;
317 }
318
319 res_src = platform_get_resource(pdev, IORESOURCE_MEM, 0);
320 res_wdt = platform_get_resource(pdev, IORESOURCE_MEM, 1);
321 res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
322
323 if (!res_src || !res_wdt || !res_irq) {
324 dev_err(&pdev->dev, "incorrect resources\n");
325 return -ENOENT;
326 }
327
328 wdt = kzalloc(sizeof(*wdt), GFP_KERNEL);
329 if (!wdt) {
330 dev_err(&pdev->dev, "out of memory\n");
331 return -ENOMEM;
332 }
333
334 wdt->irq = -1;
335 wdt->miscdev.parent = &pdev->dev;
336 wdt->miscdev.minor = WATCHDOG_MINOR;
337 wdt->miscdev.name = "watchdog";
338 wdt->miscdev.fops = &tegra_wdt_fops;
339
340 wdt->notifier.notifier_call = tegra_wdt_notify;
341
342 res_src = request_mem_region(res_src->start, resource_size(res_src),
343 pdev->name);
344 res_wdt = request_mem_region(res_wdt->start, resource_size(res_wdt),
345 pdev->name);
346
347 if (!res_src || !res_wdt) {
348 dev_err(&pdev->dev, "unable to request memory resources\n");
349 ret = -EBUSY;
350 goto fail;
351 }
352
353 wdt->wdt_source = ioremap(res_src->start, resource_size(res_src));
354 wdt->wdt_timer = ioremap(res_wdt->start, resource_size(res_wdt));
355 if (!wdt->wdt_source || !wdt->wdt_timer) {
356 dev_err(&pdev->dev, "unable to map registers\n");
357 ret = -ENOMEM;
358 goto fail;
359 }
360
361 src = readl(wdt->wdt_source);
362 if (src & BIT(12))
363 dev_info(&pdev->dev, "last reset due to watchdog timeout\n");
364
365 tegra_wdt_disable(wdt);
366 writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR);
367
368 ret = request_irq(res_irq->start, tegra_wdt_interrupt, IRQF_DISABLED,
369 dev_name(&pdev->dev), wdt);
370 if (ret) {
371 dev_err(&pdev->dev, "unable to configure IRQ\n");
372 goto fail;
373 }
374
375 wdt->irq = res_irq->start;
376 wdt->res_src = res_src;
377 wdt->res_wdt = res_wdt;
378
379 ret = register_reboot_notifier(&wdt->notifier);
380 if (ret) {
381 dev_err(&pdev->dev, "cannot register reboot notifier\n");
382 goto fail;
383 }
384
385 ret = misc_register(&wdt->miscdev);
386 if (ret) {
387 dev_err(&pdev->dev, "failed to register misc device\n");
388 unregister_reboot_notifier(&wdt->notifier);
389 goto fail;
390 }
391
392 platform_set_drvdata(pdev, wdt);
393 tegra_wdt_dev = pdev;
394#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_ON_PROBE
395 wdt->status = WDT_ENABLED | WDT_ENABLED_AT_PROBE;
396 wdt->timeout = heartbeat;
397 tegra_wdt_enable(wdt);
398#else
399 wdt->status = WDT_DISABLED;
400#endif
401 return 0;
402fail:
403 if (wdt->irq != -1)
404 free_irq(wdt->irq, wdt);
405 if (wdt->wdt_source)
406 iounmap(wdt->wdt_source);
407 if (wdt->wdt_timer)
408 iounmap(wdt->wdt_timer);
409 if (res_src)
410 release_mem_region(res_src->start, resource_size(res_src));
411 if (res_wdt)
412 release_mem_region(res_wdt->start, resource_size(res_wdt));
413 kfree(wdt);
414 return ret;
415}
416
417static int tegra_wdt_remove(struct platform_device *pdev)
418{
419 struct tegra_wdt *wdt = platform_get_drvdata(pdev);
420
421 tegra_wdt_disable(wdt);
422
423 unregister_reboot_notifier(&wdt->notifier);
424 misc_deregister(&wdt->miscdev);
425 free_irq(wdt->irq, wdt);
426 iounmap(wdt->wdt_source);
427 iounmap(wdt->wdt_timer);
428 release_mem_region(wdt->res_src->start, resource_size(wdt->res_src));
429 release_mem_region(wdt->res_wdt->start, resource_size(wdt->res_wdt));
430 kfree(wdt);
431 platform_set_drvdata(pdev, NULL);
432 return 0;
433}
434
435#ifdef CONFIG_PM
436static int tegra_wdt_suspend(struct platform_device *pdev, pm_message_t state)
437{
438 struct tegra_wdt *wdt = platform_get_drvdata(pdev);
439
440 tegra_wdt_disable(wdt);
441 return 0;
442}
443
444static int tegra_wdt_resume(struct platform_device *pdev)
445{
446 struct tegra_wdt *wdt = platform_get_drvdata(pdev);
447
448 if (wdt->status & WDT_ENABLED)
449 tegra_wdt_enable(wdt);
450
451 return 0;
452}
453#endif
454
455static struct platform_driver tegra_wdt_driver = {
456 .probe = tegra_wdt_probe,
457 .remove = __devexit_p(tegra_wdt_remove),
458#ifdef CONFIG_PM
459 .suspend = tegra_wdt_suspend,
460 .resume = tegra_wdt_resume,
461#endif
462 .driver = {
463 .owner = THIS_MODULE,
464 .name = "tegra_wdt",
465 },
466};
467
468static int __init tegra_wdt_init(void)
469{
470 return platform_driver_register(&tegra_wdt_driver);
471}
472
473static void __exit tegra_wdt_exit(void)
474{
475 platform_driver_unregister(&tegra_wdt_driver);
476}
477
478module_init(tegra_wdt_init);
479module_exit(tegra_wdt_exit);
480
481MODULE_AUTHOR("NVIDIA Corporation");
482MODULE_DESCRIPTION("Tegra Watchdog Driver");
483
484module_param(heartbeat, int, 0);
485MODULE_PARM_DESC(heartbeat,
486 "Watchdog heartbeat period in seconds");
487
488MODULE_LICENSE("GPL");
489MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
490MODULE_ALIAS("platform:tegra_wdt");
491