Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.17.
  1/*
  2 *	Watchdog driver for the mpcore watchdog timer
  3 *
  4 *	(c) Copyright 2004 ARM Limited
  5 *
  6 *	Based on the SoftDog driver:
  7 *	(c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  8 *						All Rights Reserved.
  9 *
 10 *	This program is free software; you can redistribute it and/or
 11 *	modify it under the terms of the GNU General Public License
 12 *	as published by the Free Software Foundation; either version
 13 *	2 of the License, or (at your option) any later version.
 14 *
 15 *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
 16 *	warranty for any of this software. This material is provided
 17 *	"AS-IS" and at no charge.
 18 *
 19 *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
 20 *
 21 */
 22#include <linux/module.h>
 23#include <linux/moduleparam.h>
 24#include <linux/types.h>
 25#include <linux/miscdevice.h>
 26#include <linux/watchdog.h>
 27#include <linux/fs.h>
 28#include <linux/reboot.h>
 29#include <linux/init.h>
 30#include <linux/interrupt.h>
 31#include <linux/platform_device.h>
 32#include <linux/uaccess.h>
 33#include <linux/slab.h>
 34#include <linux/io.h>
 35
 36#include <asm/smp_twd.h>
 37
 38struct mpcore_wdt {
 39	unsigned long	timer_alive;
 40	struct device	*dev;
 41	void __iomem	*base;
 42	int		irq;
 43	unsigned int	perturb;
 44	char		expect_close;
 45};
 46
 47static struct platform_device *mpcore_wdt_dev;
 48static DEFINE_SPINLOCK(wdt_lock);
 49
 50#define TIMER_MARGIN	60
 51static int mpcore_margin = TIMER_MARGIN;
 52module_param(mpcore_margin, int, 0);
 53MODULE_PARM_DESC(mpcore_margin,
 54	"MPcore timer margin in seconds. (0 < mpcore_margin < 65536, default="
 55				__MODULE_STRING(TIMER_MARGIN) ")");
 56
 57static int nowayout = WATCHDOG_NOWAYOUT;
 58module_param(nowayout, int, 0);
 59MODULE_PARM_DESC(nowayout,
 60	"Watchdog cannot be stopped once started (default="
 61				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 62
 63#define ONLY_TESTING	0
 64static int mpcore_noboot = ONLY_TESTING;
 65module_param(mpcore_noboot, int, 0);
 66MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, "
 67	"set to 1 to ignore reboots, 0 to reboot (default="
 68					__MODULE_STRING(ONLY_TESTING) ")");
 69
 70/*
 71 *	This is the interrupt handler.  Note that we only use this
 72 *	in testing mode, so don't actually do a reboot here.
 73 */
 74static irqreturn_t mpcore_wdt_fire(int irq, void *arg)
 75{
 76	struct mpcore_wdt *wdt = arg;
 77
 78	/* Check it really was our interrupt */
 79	if (readl(wdt->base + TWD_WDOG_INTSTAT)) {
 80		dev_printk(KERN_CRIT, wdt->dev,
 81					"Triggered - Reboot ignored.\n");
 82		/* Clear the interrupt on the watchdog */
 83		writel(1, wdt->base + TWD_WDOG_INTSTAT);
 84		return IRQ_HANDLED;
 85	}
 86	return IRQ_NONE;
 87}
 88
 89/*
 90 *	mpcore_wdt_keepalive - reload the timer
 91 *
 92 *	Note that the spec says a DIFFERENT value must be written to the reload
 93 *	register each time.  The "perturb" variable deals with this by adding 1
 94 *	to the count every other time the function is called.
 95 */
 96static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt)
 97{
 98	unsigned long count;
 99
100	spin_lock(&wdt_lock);
101	/* Assume prescale is set to 256 */
102	count =  __raw_readl(wdt->base + TWD_WDOG_COUNTER);
103	count = (0xFFFFFFFFU - count) * (HZ / 5);
104	count = (count / 256) * mpcore_margin;
105
106	/* Reload the counter */
107	writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD);
108	wdt->perturb = wdt->perturb ? 0 : 1;
109	spin_unlock(&wdt_lock);
110}
111
112static void mpcore_wdt_stop(struct mpcore_wdt *wdt)
113{
114	spin_lock(&wdt_lock);
115	writel(0x12345678, wdt->base + TWD_WDOG_DISABLE);
116	writel(0x87654321, wdt->base + TWD_WDOG_DISABLE);
117	writel(0x0, wdt->base + TWD_WDOG_CONTROL);
118	spin_unlock(&wdt_lock);
119}
120
121static void mpcore_wdt_start(struct mpcore_wdt *wdt)
122{
123	dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n");
124
125	/* This loads the count register but does NOT start the count yet */
126	mpcore_wdt_keepalive(wdt);
127
128	if (mpcore_noboot) {
129		/* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */
130		writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL);
131	} else {
132		/* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */
133		writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL);
134	}
135}
136
137static int mpcore_wdt_set_heartbeat(int t)
138{
139	if (t < 0x0001 || t > 0xFFFF)
140		return -EINVAL;
141
142	mpcore_margin = t;
143	return 0;
144}
145
146/*
147 *	/dev/watchdog handling
148 */
149static int mpcore_wdt_open(struct inode *inode, struct file *file)
150{
151	struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev);
152
153	if (test_and_set_bit(0, &wdt->timer_alive))
154		return -EBUSY;
155
156	if (nowayout)
157		__module_get(THIS_MODULE);
158
159	file->private_data = wdt;
160
161	/*
162	 *	Activate timer
163	 */
164	mpcore_wdt_start(wdt);
165
166	return nonseekable_open(inode, file);
167}
168
169static int mpcore_wdt_release(struct inode *inode, struct file *file)
170{
171	struct mpcore_wdt *wdt = file->private_data;
172
173	/*
174	 *	Shut off the timer.
175	 *	Lock it in if it's a module and we set nowayout
176	 */
177	if (wdt->expect_close == 42)
178		mpcore_wdt_stop(wdt);
179	else {
180		dev_printk(KERN_CRIT, wdt->dev,
181				"unexpected close, not stopping watchdog!\n");
182		mpcore_wdt_keepalive(wdt);
183	}
184	clear_bit(0, &wdt->timer_alive);
185	wdt->expect_close = 0;
186	return 0;
187}
188
189static ssize_t mpcore_wdt_write(struct file *file, const char *data,
190						size_t len, loff_t *ppos)
191{
192	struct mpcore_wdt *wdt = file->private_data;
193
194	/*
195	 *	Refresh the timer.
196	 */
197	if (len) {
198		if (!nowayout) {
199			size_t i;
200
201			/* In case it was set long ago */
202			wdt->expect_close = 0;
203
204			for (i = 0; i != len; i++) {
205				char c;
206
207				if (get_user(c, data + i))
208					return -EFAULT;
209				if (c == 'V')
210					wdt->expect_close = 42;
211			}
212		}
213		mpcore_wdt_keepalive(wdt);
214	}
215	return len;
216}
217
218static const struct watchdog_info ident = {
219	.options		= WDIOF_SETTIMEOUT |
220				  WDIOF_KEEPALIVEPING |
221				  WDIOF_MAGICCLOSE,
222	.identity		= "MPcore Watchdog",
223};
224
225static long mpcore_wdt_ioctl(struct file *file, unsigned int cmd,
226							unsigned long arg)
227{
228	struct mpcore_wdt *wdt = file->private_data;
229	int ret;
230	union {
231		struct watchdog_info ident;
232		int i;
233	} uarg;
234
235	if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg))
236		return -ENOTTY;
237
238	if (_IOC_DIR(cmd) & _IOC_WRITE) {
239		ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd));
240		if (ret)
241			return -EFAULT;
242	}
243
244	switch (cmd) {
245	case WDIOC_GETSUPPORT:
246		uarg.ident = ident;
247		ret = 0;
248		break;
249
250	case WDIOC_GETSTATUS:
251	case WDIOC_GETBOOTSTATUS:
252		uarg.i = 0;
253		ret = 0;
254		break;
255
256	case WDIOC_SETOPTIONS:
257		ret = -EINVAL;
258		if (uarg.i & WDIOS_DISABLECARD) {
259			mpcore_wdt_stop(wdt);
260			ret = 0;
261		}
262		if (uarg.i & WDIOS_ENABLECARD) {
263			mpcore_wdt_start(wdt);
264			ret = 0;
265		}
266		break;
267
268	case WDIOC_KEEPALIVE:
269		mpcore_wdt_keepalive(wdt);
270		ret = 0;
271		break;
272
273	case WDIOC_SETTIMEOUT:
274		ret = mpcore_wdt_set_heartbeat(uarg.i);
275		if (ret)
276			break;
277
278		mpcore_wdt_keepalive(wdt);
279		/* Fall */
280	case WDIOC_GETTIMEOUT:
281		uarg.i = mpcore_margin;
282		ret = 0;
283		break;
284
285	default:
286		return -ENOTTY;
287	}
288
289	if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
290		ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd));
291		if (ret)
292			ret = -EFAULT;
293	}
294	return ret;
295}
296
297/*
298 *	System shutdown handler.  Turn off the watchdog if we're
299 *	restarting or halting the system.
300 */
301static void mpcore_wdt_shutdown(struct platform_device *dev)
302{
303	struct mpcore_wdt *wdt = platform_get_drvdata(dev);
304
305	if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT)
306		mpcore_wdt_stop(wdt);
307}
308
309/*
310 *	Kernel Interfaces
311 */
312static const struct file_operations mpcore_wdt_fops = {
313	.owner		= THIS_MODULE,
314	.llseek		= no_llseek,
315	.write		= mpcore_wdt_write,
316	.unlocked_ioctl	= mpcore_wdt_ioctl,
317	.open		= mpcore_wdt_open,
318	.release	= mpcore_wdt_release,
319};
320
321static struct miscdevice mpcore_wdt_miscdev = {
322	.minor		= WATCHDOG_MINOR,
323	.name		= "watchdog",
324	.fops		= &mpcore_wdt_fops,
325};
326
327static int __devinit mpcore_wdt_probe(struct platform_device *dev)
328{
329	struct mpcore_wdt *wdt;
330	struct resource *res;
331	int ret;
332
333	/* We only accept one device, and it must have an id of -1 */
334	if (dev->id != -1)
335		return -ENODEV;
336
337	res = platform_get_resource(dev, IORESOURCE_MEM, 0);
338	if (!res) {
339		ret = -ENODEV;
340		goto err_out;
341	}
342
343	wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL);
344	if (!wdt) {
345		ret = -ENOMEM;
346		goto err_out;
347	}
348
349	wdt->dev = &dev->dev;
350	wdt->irq = platform_get_irq(dev, 0);
351	if (wdt->irq < 0) {
352		ret = -ENXIO;
353		goto err_free;
354	}
355	wdt->base = ioremap(res->start, resource_size(res));
356	if (!wdt->base) {
357		ret = -ENOMEM;
358		goto err_free;
359	}
360
361	mpcore_wdt_miscdev.parent = &dev->dev;
362	ret = misc_register(&mpcore_wdt_miscdev);
363	if (ret) {
364		dev_printk(KERN_ERR, wdt->dev,
365			"cannot register miscdev on minor=%d (err=%d)\n",
366							WATCHDOG_MINOR, ret);
367		goto err_misc;
368	}
369
370	ret = request_irq(wdt->irq, mpcore_wdt_fire, IRQF_DISABLED,
371							"mpcore_wdt", wdt);
372	if (ret) {
373		dev_printk(KERN_ERR, wdt->dev,
374			"cannot register IRQ%d for watchdog\n", wdt->irq);
375		goto err_irq;
376	}
377
378	mpcore_wdt_stop(wdt);
379	platform_set_drvdata(dev, wdt);
380	mpcore_wdt_dev = dev;
381
382	return 0;
383
384err_irq:
385	misc_deregister(&mpcore_wdt_miscdev);
386err_misc:
387	iounmap(wdt->base);
388err_free:
389	kfree(wdt);
390err_out:
391	return ret;
392}
393
394static int __devexit mpcore_wdt_remove(struct platform_device *dev)
395{
396	struct mpcore_wdt *wdt = platform_get_drvdata(dev);
397
398	platform_set_drvdata(dev, NULL);
399
400	misc_deregister(&mpcore_wdt_miscdev);
401
402	mpcore_wdt_dev = NULL;
403
404	free_irq(wdt->irq, wdt);
405	iounmap(wdt->base);
406	kfree(wdt);
407	return 0;
408}
409
410#ifdef CONFIG_PM
411static int mpcore_wdt_suspend(struct platform_device *dev, pm_message_t msg)
412{
413	struct mpcore_wdt *wdt = platform_get_drvdata(dev);
414	mpcore_wdt_stop(wdt);		/* Turn the WDT off */
415	return 0;
416}
417
418static int mpcore_wdt_resume(struct platform_device *dev)
419{
420	struct mpcore_wdt *wdt = platform_get_drvdata(dev);
421	/* re-activate timer */
422	if (test_bit(0, &wdt->timer_alive))
423		mpcore_wdt_start(wdt);
424	return 0;
425}
426#else
427#define mpcore_wdt_suspend	NULL
428#define mpcore_wdt_resume	NULL
429#endif
430
431/* work with hotplug and coldplug */
432MODULE_ALIAS("platform:mpcore_wdt");
433
434static struct platform_driver mpcore_wdt_driver = {
435	.probe		= mpcore_wdt_probe,
436	.remove		= __devexit_p(mpcore_wdt_remove),
437	.suspend	= mpcore_wdt_suspend,
438	.resume		= mpcore_wdt_resume,
439	.shutdown	= mpcore_wdt_shutdown,
440	.driver		= {
441		.owner	= THIS_MODULE,
442		.name	= "mpcore_wdt",
443	},
444};
445
446static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. "
447		"mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n";
448
449static int __init mpcore_wdt_init(void)
450{
451	/*
452	 * Check that the margin value is within it's range;
453	 * if not reset to the default
454	 */
455	if (mpcore_wdt_set_heartbeat(mpcore_margin)) {
456		mpcore_wdt_set_heartbeat(TIMER_MARGIN);
457		printk(KERN_INFO "mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n",
458			TIMER_MARGIN);
459	}
460
461	printk(banner, mpcore_noboot, mpcore_margin, nowayout);
462
463	return platform_driver_register(&mpcore_wdt_driver);
464}
465
466static void __exit mpcore_wdt_exit(void)
467{
468	platform_driver_unregister(&mpcore_wdt_driver);
469}
470
471module_init(mpcore_wdt_init);
472module_exit(mpcore_wdt_exit);
473
474MODULE_AUTHOR("ARM Limited");
475MODULE_DESCRIPTION("MPcore Watchdog Device Driver");
476MODULE_LICENSE("GPL");
477MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);