Commit 8128f648 authored by popcornmix's avatar popcornmix
Browse files

bcm2708 watchdog driver



Signed-off-by: default avatarpopcornmix <popcornmix@gmail.com>
parent 16468d7b
Loading
Loading
Loading
Loading
+6 −0
Original line number Original line Diff line number Diff line
@@ -402,6 +402,12 @@ config RETU_WATCHDOG
	  To compile this driver as a module, choose M here: the
	  To compile this driver as a module, choose M here: the
	  module will be called retu_wdt.
	  module will be called retu_wdt.


config BCM2708_WDT
	tristate "BCM2708 Watchdog"
	depends on ARCH_BCM2708
	help
	  Enables BCM2708 watchdog support.

config MOXART_WDT
config MOXART_WDT
	tristate "MOXART watchdog"
	tristate "MOXART watchdog"
	depends on ARCH_MOXART
	depends on ARCH_MOXART
+1 −0
Original line number Original line Diff line number Diff line
@@ -54,6 +54,7 @@ obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o
obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o
obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o
obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o
obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o
obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o
obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o
obj-$(CONFIG_BCM2708_WDT) += bcm2708_wdog.o
obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o
obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o
obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o
obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o
obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o
obj-$(CONFIG_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o
+384 −0
Original line number Original line Diff line number Diff line
/*
 *	Broadcom BCM2708 watchdog driver.
 *
 *	(c) Copyright 2010 Broadcom Europe Ltd
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 *
 *      BCM2708 watchdog driver. Loosely based on wdt driver.
 */

#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <mach/platform.h>

#include <asm/system.h>

#define SECS_TO_WDOG_TICKS(x) ((x) << 16)
#define WDOG_TICKS_TO_SECS(x) ((x) >> 16)

static unsigned long wdog_is_open;
static uint32_t wdog_ticks;	       /* Ticks to load into wdog timer */
static char expect_close;

/*
 *	Module parameters
 */

#define WD_TIMO 10		       /* Default heartbeat = 60 seconds */
static int heartbeat = WD_TIMO;	       /* Heartbeat in seconds */

module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat,
	"Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default="
				__MODULE_STRING(WD_TIMO) ")");

static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout,
	"Watchdog cannot be stopped once started (default="
				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

static DEFINE_SPINLOCK(wdog_lock);

/**
 *	Start the watchdog driver.
 */

static int wdog_start(unsigned long timeout)
{
	uint32_t cur;
	unsigned long flags;
	spin_lock_irqsave(&wdog_lock, flags);

	/* enable the watchdog */
	iowrite32(PM_PASSWORD | (timeout & PM_WDOG_TIME_SET),
		  __io_address(PM_WDOG));
	cur = ioread32(__io_address(PM_RSTC));
	iowrite32(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) |
		  PM_RSTC_WRCFG_FULL_RESET, __io_address(PM_RSTC));

	spin_unlock_irqrestore(&wdog_lock, flags);
	return 0;
}

/**
 *	Stop the watchdog driver.
 */

static int wdog_stop(void)
{
	iowrite32(PM_PASSWORD | PM_RSTC_RESET, __io_address(PM_RSTC));
	printk(KERN_INFO "watchdog stopped\n");
	return 0;
}

/**
 *	Reload counter one with the watchdog heartbeat. We don't bother
 *	reloading the cascade counter.
 */

static void wdog_ping(void)
{
	wdog_start(wdog_ticks);
}

/**
 *	@t:		the new heartbeat value that needs to be set.
 *
 *	Set a new heartbeat value for the watchdog device. If the heartbeat
 *	value is incorrect we keep the old value and return -EINVAL. If
 *	successful we return 0.
 */

static int wdog_set_heartbeat(int t)
{
	if (t < 1 || t > WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET))
		return -EINVAL;

	heartbeat = t;
	wdog_ticks = SECS_TO_WDOG_TICKS(t);
	return 0;
}

/**
 *	@file: file handle to the watchdog
 *	@buf: buffer to write (unused as data does not matter here
 *	@count: count of bytes
 *	@ppos: pointer to the position to write. No seeks allowed
 *
 *	A write to a watchdog device is defined as a keepalive signal.
 *
 *      if 'nowayout' is set then normally a close() is ignored. But
 *      if you write 'V' first then the close() will stop the timer.
 */

static ssize_t wdog_write(struct file *file, const char __user *buf,
						size_t count, loff_t *ppos)
{
	if (count) {
		if (!nowayout) {
			size_t i;

			/* In case it was set long ago */
			expect_close = 0;

			for (i = 0; i != count; i++) {
				char c;
				if (get_user(c, buf + i))
					return -EFAULT;
				if (c == 'V')
					expect_close = 42;
			}
		}
		wdog_ping();
	}
	return count;
}

static int wdog_get_status(void)
{
	unsigned long flags;
	int status = 0;
	spin_lock_irqsave(&wdog_lock, flags);
	/* FIXME: readback reset reason */
	spin_unlock_irqrestore(&wdog_lock, flags);
	return status;
}

static uint32_t wdog_get_remaining(void)
{
	uint32_t ret = ioread32(__io_address(PM_WDOG));
	return ret & PM_WDOG_TIME_SET;
}

/**
 *	@file: file handle to the device
 *	@cmd: watchdog command
 *	@arg: argument pointer
 *
 *	The watchdog API defines a common set of functions for all watchdogs
 *	according to their available features. We only actually usefully support
 *	querying capabilities and current status.
 */

static long wdog_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
	int new_heartbeat;
	int status;
	int options;
	uint32_t remaining;

	struct watchdog_info ident = {
		.options =		WDIOF_SETTIMEOUT|
					WDIOF_MAGICCLOSE|
					WDIOF_KEEPALIVEPING,
		.firmware_version =	1,
		.identity =		"BCM2708",
	};

	switch (cmd) {
	case WDIOC_GETSUPPORT:
		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
	case WDIOC_GETSTATUS:
		status = wdog_get_status();
		return put_user(status, p);
	case WDIOC_GETBOOTSTATUS:
		return put_user(0, p);
	case WDIOC_KEEPALIVE:
		wdog_ping();
		return 0;
	case WDIOC_SETTIMEOUT:
		if (get_user(new_heartbeat, p))
			return -EFAULT;
		if (wdog_set_heartbeat(new_heartbeat))
			return -EINVAL;
		wdog_ping();
		/* Fall */
	case WDIOC_GETTIMEOUT:
		return put_user(heartbeat, p);
	case WDIOC_GETTIMELEFT:
		remaining = WDOG_TICKS_TO_SECS(wdog_get_remaining());
		return put_user(remaining, p);
	case WDIOC_SETOPTIONS:
		if (get_user(options, p))
			return -EFAULT;
		if (options & WDIOS_DISABLECARD)
			wdog_stop();
		if (options & WDIOS_ENABLECARD)
			wdog_start(wdog_ticks);
		return 0;
	default:
		return -ENOTTY;
	}
}

/**
 *	@inode: inode of device
 *	@file: file handle to device
 *
 *	The watchdog device has been opened. The watchdog device is single
 *	open and on opening we load the counters.
 */

static int wdog_open(struct inode *inode, struct file *file)
{
	if (test_and_set_bit(0, &wdog_is_open))
		return -EBUSY;
	/*
	 *	Activate
	 */
	wdog_start(wdog_ticks);
	return nonseekable_open(inode, file);
}

/**
 *	@inode: inode to board
 *	@file: file handle to board
 *
 *	The watchdog has a configurable API. There is a religious dispute
 *	between people who want their watchdog to be able to shut down and
 *	those who want to be sure if the watchdog manager dies the machine
 *	reboots. In the former case we disable the counters, in the latter
 *	case you have to open it again very soon.
 */

static int wdog_release(struct inode *inode, struct file *file)
{
	if (expect_close == 42) {
		wdog_stop();
	} else {
		printk(KERN_CRIT
		 "wdt: WDT device closed unexpectedly.  WDT will not stop!\n");
		wdog_ping();
	}
	clear_bit(0, &wdog_is_open);
	expect_close = 0;
	return 0;
}

/**
 *	@this: our notifier block
 *	@code: the event being reported
 *	@unused: unused
 *
 *	Our notifier is called on system shutdowns. Turn the watchdog
 *	off so that it does not fire during the next reboot.
 */

static int wdog_notify_sys(struct notifier_block *this, unsigned long code,
	void *unused)
{
	if (code == SYS_DOWN || code == SYS_HALT)
		wdog_stop();
	return NOTIFY_DONE;
}

/*
 *	Kernel Interfaces
 */


static const struct file_operations wdog_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= wdog_write,
	.unlocked_ioctl	= wdog_ioctl,
	.open		= wdog_open,
	.release	= wdog_release,
};

static struct miscdevice wdog_miscdev = {
	.minor	= WATCHDOG_MINOR,
	.name	= "watchdog",
	.fops	= &wdog_fops,
};

/*
 *	The WDT card needs to learn about soft shutdowns in order to
 *	turn the timebomb registers off.
 */

static struct notifier_block wdog_notifier = {
	.notifier_call = wdog_notify_sys,
};

/**
 *	cleanup_module:
 *
 *	Unload the watchdog. You cannot do this with any file handles open.
 *	If your watchdog is set to continue ticking on close and you unload
 *	it, well it keeps ticking. We won't get the interrupt but the board
 *	will not touch PC memory so all is fine. You just have to load a new
 *	module in 60 seconds or reboot.
 */

static void __exit wdog_exit(void)
{
	misc_deregister(&wdog_miscdev);
	unregister_reboot_notifier(&wdog_notifier);
}

static int __init wdog_init(void)
{
	int ret;

	/* Check that the heartbeat value is within it's range;
	   if not reset to the default */
	if (wdog_set_heartbeat(heartbeat)) {
		wdog_set_heartbeat(WD_TIMO);
		printk(KERN_INFO "bcm2708_wdog: heartbeat value must be "
			"0 < heartbeat < %d, using %d\n",
				WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
				WD_TIMO);
	}

	ret = register_reboot_notifier(&wdog_notifier);
	if (ret) {
		printk(KERN_ERR
		      "wdt: cannot register reboot notifier (err=%d)\n", ret);
		goto out_reboot;
	}

	ret = misc_register(&wdog_miscdev);
	if (ret) {
		printk(KERN_ERR
			"wdt: cannot register miscdev on minor=%d (err=%d)\n",
							WATCHDOG_MINOR, ret);
		goto out_misc;
	}

	printk(KERN_INFO "bcm2708 watchdog, heartbeat=%d sec (nowayout=%d)\n",
		heartbeat, nowayout);
	return 0;

out_misc:
	unregister_reboot_notifier(&wdog_notifier);
out_reboot:
	return ret;
}

module_init(wdog_init);
module_exit(wdog_exit);

MODULE_AUTHOR("Luke Diamand");
MODULE_DESCRIPTION("Driver for BCM2708 watchdog");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_ALIAS_MISCDEV(TEMP_MINOR);
MODULE_LICENSE("GPL");