diff options
Diffstat (limited to 'drivers/watchdog/ts72xx_wdt.c')
| -rw-r--r-- | drivers/watchdog/ts72xx_wdt.c | 520 | 
1 files changed, 520 insertions, 0 deletions
| diff --git a/drivers/watchdog/ts72xx_wdt.c b/drivers/watchdog/ts72xx_wdt.c new file mode 100644 index 000000000000..565a2c3321e5 --- /dev/null +++ b/drivers/watchdog/ts72xx_wdt.c @@ -0,0 +1,520 @@ +/* + * Watchdog driver for Technologic Systems TS-72xx based SBCs + * (TS-7200, TS-7250 and TS-7260). These boards have external + * glue logic CPLD chip, which includes programmable watchdog + * timer. + * + * Copyright (c) 2009 Mika Westerberg <mika.westerberg@iki.fi> + * + * This driver is based on ep93xx_wdt and wm831x_wdt drivers. + * + * 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. + */ + +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> + +#define TS72XX_WDT_FEED_VAL		0x05 +#define TS72XX_WDT_DEFAULT_TIMEOUT	8 + +static int timeout = TS72XX_WDT_DEFAULT_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. " +			  "(1 <= timeout <= 8, default=" +			  __MODULE_STRING(TS72XX_WDT_DEFAULT_TIMEOUT) +			  ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +/** + * struct ts72xx_wdt - watchdog control structure + * @lock: lock that protects this structure + * @regval: watchdog timeout value suitable for control register + * @flags: flags controlling watchdog device state + * @control_reg: watchdog control register + * @feed_reg: watchdog feed register + * @pdev: back pointer to platform dev + */ +struct ts72xx_wdt { +	struct mutex	lock; +	int		regval; + +#define TS72XX_WDT_BUSY_FLAG		1 +#define TS72XX_WDT_EXPECT_CLOSE_FLAG	2 +	int		flags; + +	void __iomem	*control_reg; +	void __iomem	*feed_reg; + +	struct platform_device *pdev; +}; + +struct platform_device *ts72xx_wdt_pdev; + +/* + * TS-72xx Watchdog supports following timeouts (value written + * to control register): + *	value	description + *	------------------------- + * 	0x00	watchdog disabled + *	0x01	250ms + *	0x02	500ms + *	0x03	1s + *	0x04	reserved + *	0x05	2s + *	0x06	4s + *	0x07	8s + * + * Timeouts below 1s are not very usable so we don't + * allow them at all. + * + * We provide two functions that convert between these: + * timeout_to_regval() and regval_to_timeout(). + */ +static const struct { +	int	timeout; +	int	regval; +} ts72xx_wdt_map[] = { +	{ 1, 3 }, +	{ 2, 5 }, +	{ 4, 6 }, +	{ 8, 7 }, +}; + +/** + * timeout_to_regval() - converts given timeout to control register value + * @new_timeout: timeout in seconds to be converted + * + * Function converts given @new_timeout into valid value that can + * be programmed into watchdog control register. When conversion is + * not possible, function returns %-EINVAL. + */ +static int timeout_to_regval(int new_timeout) +{ +	int i; + +	/* first limit it to 1 - 8 seconds */ +	new_timeout = clamp_val(new_timeout, 1, 8); + +	for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { +		if (ts72xx_wdt_map[i].timeout >= new_timeout) +			return ts72xx_wdt_map[i].regval; +	} + +	return -EINVAL; +} + +/** + * regval_to_timeout() - converts control register value to timeout + * @regval: control register value to be converted + * + * Function converts given @regval to timeout in seconds (1, 2, 4 or 8). + * If @regval cannot be converted, function returns %-EINVAL. + */ +static int regval_to_timeout(int regval) +{ +	int i; + +	for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { +		if (ts72xx_wdt_map[i].regval == regval) +			return ts72xx_wdt_map[i].timeout; +	} + +	return -EINVAL; +} + +/** + * ts72xx_wdt_kick() - kick the watchdog + * @wdt: watchdog to be kicked + * + * Called with @wdt->lock held. + */ +static inline void ts72xx_wdt_kick(struct ts72xx_wdt *wdt) +{ +	__raw_writeb(TS72XX_WDT_FEED_VAL, wdt->feed_reg); +} + +/** + * ts72xx_wdt_start() - starts the watchdog timer + * @wdt: watchdog to be started + * + * This function programs timeout to watchdog timer + * and starts it. + * + * Called with @wdt->lock held. + */ +static void ts72xx_wdt_start(struct ts72xx_wdt *wdt) +{ +	/* +	 * To program the wdt, it first must be "fed" and +	 * only after that (within 30 usecs) the configuration +	 * can be changed. +	 */ +	ts72xx_wdt_kick(wdt); +	__raw_writeb((u8)wdt->regval, wdt->control_reg); +} + +/** + * ts72xx_wdt_stop() - stops the watchdog timer + * @wdt: watchdog to be stopped + * + * Called with @wdt->lock held. + */ +static void ts72xx_wdt_stop(struct ts72xx_wdt *wdt) +{ +	ts72xx_wdt_kick(wdt); +	__raw_writeb(0, wdt->control_reg); +} + +static int ts72xx_wdt_open(struct inode *inode, struct file *file) +{ +	struct ts72xx_wdt *wdt = platform_get_drvdata(ts72xx_wdt_pdev); +	int regval; + +	/* +	 * Try to convert default timeout to valid register +	 * value first. +	 */ +	regval = timeout_to_regval(timeout); +	if (regval < 0) { +		dev_err(&wdt->pdev->dev, +			"failed to convert timeout (%d) to register value\n", +			timeout); +		return -EINVAL; +	} + +	if (mutex_lock_interruptible(&wdt->lock)) +		return -ERESTARTSYS; + +	if ((wdt->flags & TS72XX_WDT_BUSY_FLAG) != 0) { +		mutex_unlock(&wdt->lock); +		return -EBUSY; +	} + +	wdt->flags = TS72XX_WDT_BUSY_FLAG; +	wdt->regval = regval; +	file->private_data = wdt; + +	ts72xx_wdt_start(wdt); + +	mutex_unlock(&wdt->lock); +	return nonseekable_open(inode, file); +} + +static int ts72xx_wdt_release(struct inode *inode, struct file *file) +{ +	struct ts72xx_wdt *wdt = file->private_data; + +	if (mutex_lock_interruptible(&wdt->lock)) +		return -ERESTARTSYS; + +	if ((wdt->flags & TS72XX_WDT_EXPECT_CLOSE_FLAG) != 0) { +		ts72xx_wdt_stop(wdt); +	} else { +		dev_warn(&wdt->pdev->dev, +			 "TS-72XX WDT device closed unexpectly. " +			 "Watchdog timer will not stop!\n"); +		/* +		 * Kick it one more time, to give userland some time +		 * to recover (for example, respawning the kicker +		 * daemon). +		 */ +		ts72xx_wdt_kick(wdt); +	} + +	wdt->flags = 0; + +	mutex_unlock(&wdt->lock); +	return 0; +} + +static ssize_t ts72xx_wdt_write(struct file *file, +				const char __user *data, +				size_t len, +				loff_t *ppos) +{ +	struct ts72xx_wdt *wdt = file->private_data; + +	if (!len) +		return 0; + +	if (mutex_lock_interruptible(&wdt->lock)) +		return -ERESTARTSYS; + +	ts72xx_wdt_kick(wdt); + +	/* +	 * Support for magic character closing. User process +	 * writes 'V' into the device, just before it is closed. +	 * This means that we know that the wdt timer can be +	 * stopped after user closes the device. +	 */ +	if (!nowayout) { +		int i; + +		for (i = 0; i < len; i++) { +			char c; + +			/* In case it was set long ago */ +			wdt->flags &= ~TS72XX_WDT_EXPECT_CLOSE_FLAG; + +			if (get_user(c, data + i)) { +				mutex_unlock(&wdt->lock); +				return -EFAULT; +			} +			if (c == 'V') { +				wdt->flags |= TS72XX_WDT_EXPECT_CLOSE_FLAG; +				break; +			} +		} +	} + +	mutex_unlock(&wdt->lock); +	return len; +} + +static const struct watchdog_info winfo = { +	.options		= WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | +				  WDIOF_MAGICCLOSE, +	.firmware_version	= 1, +	.identity		= "TS-72XX WDT", +}; + +static long ts72xx_wdt_ioctl(struct file *file, unsigned int cmd, +			     unsigned long arg) +{ +	struct ts72xx_wdt *wdt = file->private_data; +	void __user *argp = (void __user *)arg; +	int __user *p = (int __user *)argp; +	int error = 0; + +	if (mutex_lock_interruptible(&wdt->lock)) +		return -ERESTARTSYS; + +	switch (cmd) { +	case WDIOC_GETSUPPORT: +		error = copy_to_user(argp, &winfo, sizeof(winfo)); +		break; + +	case WDIOC_GETSTATUS: +	case WDIOC_GETBOOTSTATUS: +		return put_user(0, p); + +	case WDIOC_KEEPALIVE: +		ts72xx_wdt_kick(wdt); +		break; + +	case WDIOC_SETOPTIONS: { +		int options; + +		if (get_user(options, p)) { +			error = -EFAULT; +			break; +		} + +		error = -EINVAL; + +		if ((options & WDIOS_DISABLECARD) != 0) { +			ts72xx_wdt_stop(wdt); +			error = 0; +		} +		if ((options & WDIOS_ENABLECARD) != 0) { +			ts72xx_wdt_start(wdt); +			error = 0; +		} + +		break; +	} + +	case WDIOC_SETTIMEOUT: { +		int new_timeout; + +		if (get_user(new_timeout, p)) { +			error = -EFAULT; +		} else { +			int regval; + +			regval = timeout_to_regval(new_timeout); +			if (regval < 0) { +				error = -EINVAL; +			} else { +				ts72xx_wdt_stop(wdt); +				wdt->regval = regval; +				ts72xx_wdt_start(wdt); +			} +		} +		if (error) +			break; + +		/*FALLTHROUGH*/ +	} + +	case WDIOC_GETTIMEOUT: +		if (put_user(regval_to_timeout(wdt->regval), p)) +			error = -EFAULT; +		break; + +	default: +		error = -ENOTTY; +		break; +	} + +	mutex_unlock(&wdt->lock); +	return error; +} + +static const struct file_operations ts72xx_wdt_fops = { +	.owner		= THIS_MODULE, +	.llseek		= no_llseek, +	.open		= ts72xx_wdt_open, +	.release	= ts72xx_wdt_release, +	.write		= ts72xx_wdt_write, +	.unlocked_ioctl	= ts72xx_wdt_ioctl, +}; + +static struct miscdevice ts72xx_wdt_miscdev = { +	.minor		= WATCHDOG_MINOR, +	.name		= "watchdog", +	.fops		= &ts72xx_wdt_fops, +}; + +static __devinit int ts72xx_wdt_probe(struct platform_device *pdev) +{ +	struct ts72xx_wdt *wdt; +	struct resource *r1, *r2; +	int error = 0; + +	wdt = kzalloc(sizeof(struct ts72xx_wdt), GFP_KERNEL); +	if (!wdt) { +		dev_err(&pdev->dev, "failed to allocate memory\n"); +		return -ENOMEM; +	} + +	r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!r1) { +		dev_err(&pdev->dev, "failed to get memory resource\n"); +		error = -ENODEV; +		goto fail; +	} + +	r1 = request_mem_region(r1->start, resource_size(r1), pdev->name); +	if (!r1) { +		dev_err(&pdev->dev, "cannot request memory region\n"); +		error = -EBUSY; +		goto fail; +	} + +	wdt->control_reg = ioremap(r1->start, resource_size(r1)); +	if (!wdt->control_reg) { +		dev_err(&pdev->dev, "failed to map memory\n"); +		error = -ENODEV; +		goto fail_free_control; +	} + +	r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); +	if (!r2) { +		dev_err(&pdev->dev, "failed to get memory resource\n"); +		error = -ENODEV; +		goto fail_unmap_control; +	} + +	r2 = request_mem_region(r2->start, resource_size(r2), pdev->name); +	if (!r2) { +		dev_err(&pdev->dev, "cannot request memory region\n"); +		error = -EBUSY; +		goto fail_unmap_control; +	} + +	wdt->feed_reg = ioremap(r2->start, resource_size(r2)); +	if (!wdt->feed_reg) { +		dev_err(&pdev->dev, "failed to map memory\n"); +		error = -ENODEV; +		goto fail_free_feed; +	} + +	platform_set_drvdata(pdev, wdt); +	ts72xx_wdt_pdev = pdev; +	wdt->pdev = pdev; +	mutex_init(&wdt->lock); + +	error = misc_register(&ts72xx_wdt_miscdev); +	if (error) { +		dev_err(&pdev->dev, "failed to register miscdev\n"); +		goto fail_unmap_feed; +	} + +	dev_info(&pdev->dev, "TS-72xx Watchdog driver\n"); + +	return 0; + +fail_unmap_feed: +	platform_set_drvdata(pdev, NULL); +	iounmap(wdt->feed_reg); +fail_free_feed: +	release_mem_region(r2->start, resource_size(r2)); +fail_unmap_control: +	iounmap(wdt->control_reg); +fail_free_control: +	release_mem_region(r1->start, resource_size(r1)); +fail: +	kfree(wdt); +	return error; +} + +static __devexit int ts72xx_wdt_remove(struct platform_device *pdev) +{ +	struct ts72xx_wdt *wdt = platform_get_drvdata(pdev); +	struct resource *res; +	int error; + +	error = misc_deregister(&ts72xx_wdt_miscdev); +	platform_set_drvdata(pdev, NULL); + +	iounmap(wdt->feed_reg); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 1); +	release_mem_region(res->start, resource_size(res)); + +	iounmap(wdt->control_reg); +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	release_mem_region(res->start, resource_size(res)); + +	kfree(wdt); +	return error; +} + +static struct platform_driver ts72xx_wdt_driver = { +	.probe		= ts72xx_wdt_probe, +	.remove		= __devexit_p(ts72xx_wdt_remove), +	.driver		= { +		.name	= "ts72xx-wdt", +		.owner	= THIS_MODULE, +	}, +}; + +static __init int ts72xx_wdt_init(void) +{ +	return platform_driver_register(&ts72xx_wdt_driver); +} +module_init(ts72xx_wdt_init); + +static __exit void ts72xx_wdt_exit(void) +{ +	platform_driver_unregister(&ts72xx_wdt_driver); +} +module_exit(ts72xx_wdt_exit); + +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); +MODULE_DESCRIPTION("TS-72xx SBC Watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ts72xx-wdt"); | 
