mpc83xx_wdt.c 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
/*
 * mpc83xx_wdt.c - MPC83xx watchdog userspace interface
 *
 * Authors: Dave Updegraff <dave@cray.org>
 * 	    Kumar Gala <galak@kernel.crashing.org>
 * 		Attribution: from 83xx_wst: Florian Schirmer <jolt@tuxbox.org>
 * 				..and from sc520_wdt
 *
 * Note: it appears that you can only actually ENABLE or DISABLE the thing
 * once after POR. Once enabled, you cannot disable, and vice versa.
 *
 * 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.
 */

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/watchdog.h>
#include <asm/io.h>
#include <asm/uaccess.h>

struct mpc83xx_wdt {
	__be32 res0;
	__be32 swcrr; /* System watchdog control register */
#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */
#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
	__be32 swcnr; /* System watchdog count register */
	u8 res1[2];
	__be16 swsrr; /* System watchdog service register */
	u8 res2[0xF0];
};

static struct mpc83xx_wdt __iomem *wd_base;

static u16 timeout = 0xffff;
module_param(timeout, ushort, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in ticks. (0<timeout<65536, default=65535");

static int reset = 1;
module_param(reset, bool, 0);
MODULE_PARM_DESC(reset, "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset");

/*
 * We always prescale, but if someone really doesn't want to they can set this
 * to 0
 */
static int prescale = 1;
static unsigned int timeout_sec;

static unsigned long wdt_is_open;
static spinlock_t wdt_spinlock;

static void mpc83xx_wdt_keepalive(void)
{
	/* Ping the WDT */
	spin_lock(&wdt_spinlock);
	out_be16(&wd_base->swsrr, 0x556c);
	out_be16(&wd_base->swsrr, 0xaa39);
	spin_unlock(&wdt_spinlock);
}

static ssize_t mpc83xx_wdt_write(struct file *file, const char __user *buf,
				 size_t count, loff_t *ppos)
{
	if (count)
		mpc83xx_wdt_keepalive();
	return count;
}

static int mpc83xx_wdt_open(struct inode *inode, struct file *file)
{
	u32 tmp = SWCRR_SWEN;
	if (test_and_set_bit(0, &wdt_is_open))
		return -EBUSY;

	/* Once we start the watchdog we can't stop it */
	__module_get(THIS_MODULE);

	/* Good, fire up the show */
	if (prescale)
		tmp |= SWCRR_SWPR;
	if (reset)
		tmp |= SWCRR_SWRI;

	tmp |= timeout << 16;

	out_be32(&wd_base->swcrr, tmp);

	return nonseekable_open(inode, file);
}

static int mpc83xx_wdt_release(struct inode *inode, struct file *file)
{
	printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n");
	mpc83xx_wdt_keepalive();
	clear_bit(0, &wdt_is_open);
	return 0;
}

static int mpc83xx_wdt_ioctl(struct inode *inode, struct file *file,
				unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
	static struct watchdog_info ident = {
		.options = WDIOF_KEEPALIVEPING,
		.firmware_version = 1,
		.identity = "MPC83xx",
	};

	switch (cmd) {
	case WDIOC_GETSUPPORT:
		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
	case WDIOC_KEEPALIVE:
		mpc83xx_wdt_keepalive();
		return 0;
	case WDIOC_GETTIMEOUT:
		return put_user(timeout_sec, p);
	default:
128
		return -ENOTTY;
129 130 131
	}
}

132
static const struct file_operations mpc83xx_wdt_fops = {
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.write		= mpc83xx_wdt_write,
	.ioctl		= mpc83xx_wdt_ioctl,
	.open		= mpc83xx_wdt_open,
	.release	= mpc83xx_wdt_release,
};

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

static int __devinit mpc83xx_wdt_probe(struct platform_device *dev)
{
	struct resource *r;
	int ret;
	unsigned int *freq = dev->dev.platform_data;

	/* get a pointer to the register memory */
	r = platform_get_resource(dev, IORESOURCE_MEM, 0);

	if (!r) {
		ret = -ENODEV;
		goto err_out;
	}

	wd_base = ioremap(r->start, sizeof (struct mpc83xx_wdt));

	if (wd_base == NULL) {
		ret = -ENOMEM;
		goto err_out;
	}

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

	/* Calculate the timeout in seconds */
	if (prescale)
		timeout_sec = (timeout * 0x10000) / (*freq);
	else
		timeout_sec = timeout / (*freq);

	printk(KERN_INFO "WDT driver for MPC83xx initialized. "
		"mode:%s timeout=%d (%d seconds)\n",
		reset ? "reset":"interrupt", timeout, timeout_sec);

	spin_lock_init(&wdt_spinlock);

	return 0;

err_unmap:
	iounmap(wd_base);
err_out:
	return ret;
}

static int __devexit mpc83xx_wdt_remove(struct platform_device *dev)
{
	misc_deregister(&mpc83xx_wdt_miscdev);
	iounmap(wd_base);

	return 0;
}

static struct platform_driver mpc83xx_wdt_driver = {
	.probe		= mpc83xx_wdt_probe,
	.remove		= __devexit_p(mpc83xx_wdt_remove),
	.driver		= {
		.name	= "mpc83xx_wdt",
	},
};

static int __init mpc83xx_wdt_init(void)
{
	return platform_driver_register(&mpc83xx_wdt_driver);
}

static void __exit mpc83xx_wdt_exit(void)
{
	platform_driver_unregister(&mpc83xx_wdt_driver);
}

module_init(mpc83xx_wdt_init);
module_exit(mpc83xx_wdt_exit);

MODULE_AUTHOR("Dave Updegraff, Kumar Gala");
MODULE_DESCRIPTION("Driver for watchdog timer in MPC83xx uProcessor");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);