diff mbox

[4/4] watchdog: Kontron PLD watchdog timer

Message ID 1365441321-21952-4-git-send-email-kevin.strasser@linux.intel.com
State Not Applicable
Headers show

Commit Message

Kevin Strasser April 8, 2013, 5:15 p.m. UTC
From: Michael Brunner <michael.brunner@kontron.com>

Add watchdog timer support for the on-board PLD found on some Kontron
embedded modules.

Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
---
 drivers/watchdog/Kconfig           |   20 +
 drivers/watchdog/Makefile          |    2 +
 drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
 drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
 drivers/watchdog/kempld_wdt.h      |   75 ++++
 5 files changed, 1495 insertions(+)
 create mode 100644 drivers/watchdog/kempld_now1_wdt.c
 create mode 100644 drivers/watchdog/kempld_wdt.c
 create mode 100644 drivers/watchdog/kempld_wdt.h

Comments

Guenter Roeck April 10, 2013, 4:47 p.m. UTC | #1
On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> From: Michael Brunner <michael.brunner@kontron.com>
> 
> Add watchdog timer support for the on-board PLD found on some Kontron
> embedded modules.
> 
> Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>

Personally I would prefer two separate patches for the two drivers,
and to have the drivers converted to the watchdog infrastructure.
Wim's call, of course.

Thanks,
Guenter

> ---
>  drivers/watchdog/Kconfig           |   20 +
>  drivers/watchdog/Makefile          |    2 +
>  drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
>  drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
>  drivers/watchdog/kempld_wdt.h      |   75 ++++
>  5 files changed, 1495 insertions(+)
>  create mode 100644 drivers/watchdog/kempld_now1_wdt.c
>  create mode 100644 drivers/watchdog/kempld_wdt.c
>  create mode 100644 drivers/watchdog/kempld_wdt.h
> 
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 9fcc70c..9ac71ca 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -687,6 +687,26 @@ config HP_WATCHDOG
>  	  To compile this driver as a module, choose M here: the module will be
>  	  called hpwdt.
>  
> +config KEMPLD_WDT
> +	tristate "Kontron COM watchdog"
> +	depends on MFD_KEMPLD
> +	help
> +	  Support for the PLD watchdog on some Kontron ETX and COMexpress
> +	  (ETXexpress) modules
> +
> +	  This driver can also be built as a module. If so, the module will be
> +	  called kempld_wdt.
> +
> +config KEMPLD_NOW1_WDT
> +	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
> +	depends on MFD_KEMPLD
> +	help
> +	  Support for the PLD watchdog on the Kontron COMe-mSP1
> +	  (nanoETXexpress-SP) module.
> +
> +	  This driver can also be built as a module. If so, the module will
> +	  be called kempld_now1_wdt.
> +
>  config HPWDT_NMI_DECODING
>  	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
>  	depends on HP_WATCHDOG
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index a300b94..a029930 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -90,6 +90,8 @@ endif
>  obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
>  obj-$(CONFIG_IT87_WDT) += it87_wdt.o
>  obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> +obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
> +obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
>  obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
>  obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
>  obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
> diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
> new file mode 100644
> index 0000000..19b7272
> --- /dev/null
> +++ b/drivers/watchdog/kempld_now1_wdt.c
> @@ -0,0 +1,602 @@
> +/*
> + *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
> + *
> + *  Copyright (c) 2011-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
> + *        only supports predefined watchdog timeout values.
> + *
> + *        The supported timeouts are:
> + *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 2 as published
> + *  by the Free Software Foundation.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#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/init.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/kempld.h>
> +
> +#include "kempld_wdt.h"
> +
> +#define WATCHDOG_TIMEOUT 30
> +static int timeout = WATCHDOG_TIMEOUT;
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout,
> +		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
> +		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> +		 "Watchdog cannot be stopped once started (default="
> +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +/* nanoETXexpress-SP watchdog register definitions */
> +#define KEMPLD_WDT_NOW1					0xA2
> +#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
> +#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
> +#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
> +#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
> +
> +/* delay in us necessary due to clock domain sync */
> +#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
> +
> +static struct kempld_watchdog_data *kempld_now1_wdt;
> +
> +static int kempld_now1_wdt_read_supported;
> +static int kempld_now1_wdt_reg_cache;
> +
> +static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	if (kempld_now1_wdt_read_supported)
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		/* read out the register again to check if everything worked */
> +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_release_mutex(pld);
> +
> +		/* check if the watchdog was disabled */
> +		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
> +			return -EACCES;
> +	}
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		/* read out the register again to check if everything worked */
> +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_release_mutex(pld);
> +
> +		/* check if the watchdog was disabled */
> +		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> +			return -EACCES;
> +	}
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +		/* write the state again to be sure the trigger register has
> +		 * the right level */
> +		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +	}
> +
> +	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
> +		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
> +	else
> +		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
> +
> +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +
> +static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
> +					int check_only)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +	int ret = 0;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +
> +	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
> +
> +	switch (wdt->timeout) {
> +	case 1:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
> +		break;
> +	case 5:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
> +		break;
> +	case 10:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
> +		break;
> +	case 30:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
> +		break;
> +	case 60:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
> +		break;
> +	case 300:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
> +		break;
> +	case 600:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
> +		break;
> +	case 900:
> +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
> +		break;
> +	default:
> +		ret = -EINVAL;
> +		dev_err(wdt->pld->dev,
> +			"Invalid timeout value given!\n");
> +	}
> +
> +	if (!check_only) {
> +		if (ret == 0) {
> +			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +
> +			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> +		}
> +	}
> +
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +
> +	kempld_release_mutex(pld);
> +
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int wdt_reg;
> +	int timeout;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +		kempld_now1_wdt_reg_cache = wdt_reg;
> +	} else
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +
> +	kempld_release_mutex(pld);
> +
> +	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
> +	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
> +		timeout = 1;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
> +		timeout = 5;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
> +		timeout = 10;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
> +		timeout = 30;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
> +		timeout = 60;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
> +		timeout = 300;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
> +		timeout = 600;
> +		break;
> +	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
> +		timeout = 900;
> +		break;
> +	default:
> +		timeout = -ERANGE;
> +	}
> +
> +	return timeout;
> +}
> +
> +static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
> +					*data, size_t count, loff_t *ppos)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (count) {
> +		kempld_now1_wdt_keepalive(wdt);
> +
> +		if (!nowayout) {
> +			size_t i;
> +
> +			wdt->expect_close = 0;
> +
> +			for (i = 0; i < count; i++) {
> +				char c;
> +				if (get_user(c, data+i))
> +					return -EFAULT;
> +				if (c == 'V')
> +					wdt->expect_close = 42;
> +			}
> +		}
> +	}
> +
> +	return count;
> +}
> +
> +static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
> +				unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +	int options;
> +	int value;
> +	int ret = 0;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			ret = kempld_now1_wdt_stop(wdt);
> +		if (options & WDIOS_ENABLECARD) {
> +			ret = kempld_now1_wdt_start(wdt);
> +			kempld_now1_wdt_keepalive(wdt);
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		kempld_now1_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		wdt->timeout = value;
> +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> +		kempld_now1_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETTIMEOUT:
> +		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		if (timeout < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(timeout, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (wdt->expect_close)
> +		kempld_now1_wdt_stop(wdt);
> +	else {
> +		dev_warn(wdt->pld->dev,
> +			 "Unexpected close, not stopping watchdog!\n");
> +		kempld_now1_wdt_keepalive(wdt);
> +	}
> +
> +	kempld_now1_wdt->expect_close = 0;
> +
> +	clear_bit(0, &wdt->is_open);
> +
> +	return 0;
> +}
> +
> +static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
> +{
> +	int ret;
> +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 wdt_reg;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (test_and_set_bit(0, &wdt->is_open))
> +		return -EBUSY;
> +
> +	if (nowayout)
> +		__module_get(THIS_MODULE);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = kempld_now1_wdt_reg_cache;
> +	}
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(pld);
> +
> +	/* kick the watchdog if it is already enabled, otherwise start it */
> +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
> +		kempld_now1_wdt_keepalive(wdt);
> +	} else {
> +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_now1_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +	}
> +
> +	return nonseekable_open(inode, file);
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> +	wdt->expect_close = 1;
> +	kempld_now1_wdt_release(inode, file);
> +
> +	return ret;
> +}
> +
> +static const struct file_operations kempld_now1_wdt_fops = {
> +	.owner		= THIS_MODULE,
> +	.llseek		= no_llseek,
> +	.write		= kempld_now1_wdt_write,
> +	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
> +	.open		= kempld_now1_wdt_open,
> +	.release	= kempld_now1_wdt_release,
> +};
> +
> +static struct miscdevice kempld_now1_wdt_miscdev = {
> +	.minor	= WATCHDOG_MINOR,
> +	.name	= "watchdog",
> +	.fops	= &kempld_now1_wdt_fops,
> +};
> +
> +static int kempld_now1_wdt_probe(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt;
> +	struct kempld_device_data *pld;
> +	u8 wdt_reg;
> +	int ret;
> +
> +	if (kempld_now1_wdt != NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to support more than one watchdog devices\n");
> +		return -EMFILE;
> +	}
> +
> +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> +	if (wdt == NULL) {
> +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> +					GFP_KERNEL);
> +	if (wdt->timeout_stage == NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to get memory for watchdog stage\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	wdt->stages = 1;
> +	wdt->stage[0] = wdt->timeout_stage;
> +
> +	pld = dev_get_drvdata(pdev->dev.parent);
> +	wdt->pld = pld;
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
> +		sizeof(wdt->ident.identity));
> +
> +	/* set default values for the case we start the watchdog or change
> +	 * the configuration */
> +	wdt->timeout = timeout;
> +
> +	/* use settimeout to check if the timeout parameter is valid */
> +	ret = kempld_now1_wdt_settimeout(wdt, 1);
> +	if (ret)
> +		goto err_check_timeout;
> +	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
> +	if (pld->info.major > 67)
> +		kempld_now1_wdt_read_supported = 1;
> +	else
> +		dev_info(wdt->pld->dev,
> +			 "Watchdog revision does not support read - "
> +			 "unable to get watchdog state!\n");
> +
> +	/* get initial watchdog status */
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> +	if (kempld_now1_wdt_read_supported) {
> +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> +	} else {
> +		wdt_reg = 0x0;
> +	}
> +	kempld_now1_wdt_reg_cache = wdt_reg;
> +	kempld_release_mutex(wdt->pld);
> +
> +	/* check if watchdog is enabled */
> +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> +		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
> +
> +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> +	wdt->ident.options |= WDIOF_SETTIMEOUT;
> +	if (!nowayout)
> +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> +
> +	kempld_now1_wdt = wdt;
> +
> +	ret = misc_register(&kempld_now1_wdt_miscdev);
> +	if (ret)
> +		goto err_misc_register;
> +
> +	dev_info(wdt->pld->dev, "watchdog initialized\n");
> +
> +	return 0;
> +
> +err_misc_register:
> +	kfree(kempld_now1_wdt);
> +	kempld_now1_wdt = NULL;
> +err_check_timeout:
> +err_alloc_dev_data:
> +	return ret;
> +}
> +
> +static int kempld_now1_wdt_remove(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_now1_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	if (wdt != NULL) {
> +		if (!nowayout)
> +			kempld_now1_wdt_stop(wdt);
> +		else
> +			kempld_now1_wdt_keepalive(wdt);
> +	}
> +
> +	misc_deregister(&kempld_now1_wdt_miscdev);
> +
> +	kfree(wdt);
> +	kempld_now1_wdt = NULL;
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver kempld_now1_wdt_driver = {
> +	.driver = {
> +		.name = "kempld_now1-wdt",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = kempld_now1_wdt_probe,
> +	.remove = kempld_now1_wdt_remove,
> +};
> +
> +static int __init kempld_now1_wdt_init(void)
> +{
> +	return platform_driver_register(&kempld_now1_wdt_driver);
> +}
> +
> +static void __exit kempld_now1_wdt_exit(void)
> +{
> +	platform_driver_unregister(&kempld_now1_wdt_driver);
> +}
> +
> +module_init(kempld_now1_wdt_init);
> +module_exit(kempld_now1_wdt_exit);
> +
> +MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
> new file mode 100644
> index 0000000..bc150e5
> --- /dev/null
> +++ b/drivers/watchdog/kempld_wdt.c
> @@ -0,0 +1,796 @@
> +/*
> + *  kempld_wdt.c - Kontron PLD watchdog driver
> + *
> + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  Note: From the PLD watchdog point of view timeout and pretimeout are
> + *        defined differently than in the kernel.
> + *        First the pretimeout stage runs out before the timeout stage gets
> + *        active. This has to be kept in mind.
> + *
> + *  Kernel/API:                     P-----| pretimeout
> + *                |-----------------------T timeout
> + *  Watchdog:     |-----------------P       pretimeout_stage
> + *                                  |-----T timeout_stage
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 2 as published
> + *  by the Free Software Foundation.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#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/init.h>
> +#include <linux/uaccess.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/kempld.h>
> +
> +#include "kempld_wdt.h"
> +
> +#define WATCHDOG_DEFAULT_TIMEOUT 20
> +#define WATCHDOG_DEFAULT_PRETIMEOUT 0
> +static int timeout = -1;
> +static int pretimeout = -1;
> +/* The maximum timeout values have to be probed */
> +module_param(timeout, int, 0);
> +MODULE_PARM_DESC(timeout,
> +		 "Watchdog timeout in seconds. (>0, default="
> +		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
> +module_param(pretimeout, int, 0);
> +MODULE_PARM_DESC(pretimeout,
> +		 "Watchdog pretimeout in seconds. (>=0, default="
> +		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> +		 "Watchdog cannot be stopped once started (default="
> +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static struct kempld_watchdog_data *kempld_wdt;
> +
> +static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	status |= KEMPLD_WDT_CFG_ENABLE;
> +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_release_mutex(pld);
> +
> +	/* check if the watchdog was enabled */
> +	if (!(status & KEMPLD_WDT_CFG_ENABLE))
> +		return -EACCES;
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	status &= ~KEMPLD_WDT_CFG_ENABLE;
> +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_release_mutex(pld);
> +
> +	/* check if the watchdog was disabled */
> +	if (status & KEMPLD_WDT_CFG_ENABLE)
> +		return -EACCES;
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +
> +	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +	int bits;
> +	u64 timeout;
> +	u32 remainder;
> +
> +	if (stage == NULL)
> +		return 0;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
> +
> +	kempld_release_mutex(pld);
> +
> +	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
> +	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
> +	remainder = do_div(timeout, pld->pld_clock);
> +
> +	/* Round up the return value if necessary */
> +	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
> +		timeout++;
> +
> +	return timeout;
> +}
> +
> +static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage,
> +				 int action)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +
> +	if (stage == NULL)
> +		return -EINVAL;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
> +	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
> +	if (action == KEMPLD_WDT_ACTION_RESET)
> +		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
> +	else
> +		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
> +
> +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
> +				 struct kempld_watchdog_stage *stage,
> +				 int timeout)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 stage_cfg;
> +	u8 prescaler;
> +	u64 stage_timeout64;
> +	u32 stage_timeout;
> +	u32 remainder;
> +
> +	if (stage == NULL)
> +		return -EINVAL;
> +
> +	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
> +
> +	stage_timeout64 = ((u64)timeout*pld->pld_clock);
> +	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
> +	if (remainder)
> +		stage_timeout64++;
> +	stage_timeout = stage_timeout64 & stage->timeout_mask;
> +
> +	if (stage_timeout64 != (u64)stage_timeout)
> +		return -EINVAL;
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +
> +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
> +	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
> +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> +	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
> +			stage_timeout);
> +
> +	kempld_release_mutex(pld);
> +
> +	return 0;
> +}
> +
> +static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
> +{
> +	int pretimeout_stage;
> +	int timeout_stage;
> +
> +	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +
> +	if (pretimeout_stage)
> +		wdt->pretimeout = timeout_stage;
> +	else
> +		wdt->pretimeout = 0;
> +
> +	wdt->timeout = pretimeout_stage + timeout_stage;
> +
> +	if (wdt->pretimeout < 0) {
> +		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> +		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
> +			" -> using driver default\n");
> +	}
> +	if (wdt->timeout < 0) {
> +		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> +		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
> +			" -> using driver default\n");
> +	}
> +}
> +
> +static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
> +{
> +	int stage_timeout;
> +	int stage_pretimeout;
> +	int ret;
> +
> +	if ((wdt->timeout <= 0) ||
> +		(wdt->pretimeout < 0) ||
> +		(wdt->pretimeout > wdt->timeout)) {
> +		ret = -EINVAL;
> +		goto err_check_values;
> +	}
> +
> +	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
> +		if (wdt->pretimeout != 0)
> +			dev_warn(wdt->pld->dev,
> +				 "no pretimeout stage available\n"
> +				 " -> only enabling reset\n");
> +		stage_pretimeout = 0;
> +		stage_timeout = wdt->timeout;
> +	} else {
> +		stage_pretimeout = wdt->timeout - wdt->pretimeout;
> +		stage_timeout = wdt->pretimeout;
> +	}
> +
> +	if (stage_pretimeout != 0) {
> +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> +						KEMPLD_WDT_ACTION_NMI);
> +	} else if ((stage_pretimeout == 0)
> +			&& (wdt->pretimeout_stage != NULL)) {
> +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> +						KEMPLD_WDT_ACTION_NONE);
> +	} else
> +		ret = 0;
> +	if (ret)
> +		goto err_setstage;
> +
> +	if (stage_pretimeout != 0) {
> +		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
> +						 stage_pretimeout);
> +		if (ret)
> +			goto err_setstage;
> +	}
> +
> +	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
> +					KEMPLD_WDT_ACTION_RESET);
> +	if (ret)
> +		goto err_setstage;
> +
> +	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
> +					 stage_timeout);
> +	if (ret)
> +		goto err_setstage;
> +
> +	return 0;
> +err_setstage:
> +err_check_values:
> +	return ret;
> +}
> +
> +static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
> +				size_t count, loff_t *ppos)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (count) {
> +		kempld_wdt_keepalive(wdt);
> +
> +		if (!nowayout) {
> +			size_t i;
> +
> +			wdt->expect_close = 0;
> +
> +			for (i = 0; i < count; i++) {
> +				char c;
> +				if (get_user(c, data+i))
> +					return -EFAULT;
> +				if (c == 'V')
> +					wdt->expect_close = 42;
> +			}
> +		}
> +	}
> +
> +	return count;
> +}
> +
> +static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
> +				unsigned long arg)
> +{
> +	void __user *argp = (void __user *)arg;
> +	int __user *p = argp;
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +	int options;
> +	int value;
> +	int ret = 0;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	switch (cmd) {
> +	case WDIOC_GETSUPPORT:
> +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> +			ret = -EFAULT;
> +		break;
> +	case WDIOC_GETSTATUS:
> +	case WDIOC_GETBOOTSTATUS:
> +		ret = put_user(0, p);
> +		break;
> +	case WDIOC_SETOPTIONS:
> +		if (get_user(options, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		if (options & WDIOS_DISABLECARD)
> +			ret = kempld_wdt_stop(wdt);
> +		if (options & WDIOS_ENABLECARD) {
> +			ret = kempld_wdt_start(wdt);
> +			kempld_wdt_keepalive(wdt);
> +		}
> +		break;
> +	case WDIOC_KEEPALIVE:
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_SETTIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		kempld_wdt_update_timeouts(wdt);
> +		wdt->timeout = value;
> +		ret = kempld_wdt_settimeout(wdt);
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETTIMEOUT:
> +		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +		if (value < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(value, p);
> +		break;
> +	case WDIOC_SETPRETIMEOUT:
> +		if (get_user(value, p)) {
> +			ret = -EFAULT;
> +			break;
> +		}
> +		kempld_wdt_update_timeouts(wdt);
> +		wdt->pretimeout = value;
> +		ret = kempld_wdt_settimeout(wdt);
> +		kempld_wdt_keepalive(wdt);
> +		break;
> +	case WDIOC_GETPRETIMEOUT:
> +		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> +		if (value)
> +			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> +		if (value < 0)
> +			ret = ERANGE;
> +		else
> +			ret = put_user(value, p);
> +		break;
> +	default:
> +		ret = -ENOTTY;
> +	}
> +
> +	return ret;
> +}
> +
> +static int kempld_wdt_release(struct inode *inode, struct file *file)
> +{
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (wdt->expect_close)
> +		kempld_wdt_stop(wdt);
> +	else {
> +		dev_warn(wdt->pld->dev,
> +			 "Unexpected close, not stopping watchdog!\n");
> +		kempld_wdt_keepalive(wdt);
> +	}
> +
> +	kempld_wdt->expect_close = 0;
> +
> +	clear_bit(0, &wdt->is_open);
> +
> +	return 0;
> +}
> +
> +static int kempld_wdt_open(struct inode *inode, struct file *file)
> +{
> +	int ret;
> +	struct kempld_watchdog_data *wdt = kempld_wdt;
> +	struct kempld_device_data *pld = wdt->pld;
> +	u8 status;
> +
> +	BUG_ON(wdt == NULL);
> +
> +	if (test_and_set_bit(0, &wdt->is_open))
> +		return -EBUSY;
> +
> +	if (nowayout)
> +		__module_get(THIS_MODULE);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(pld);
> +
> +	/* kick the watchdog if it is already enabled, otherwise start it */
> +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> +		kempld_wdt_keepalive(wdt);
> +	} else {
> +		ret = kempld_wdt_settimeout(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +	}
> +
> +	return nonseekable_open(inode, file);
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> +	wdt->expect_close = 1;
> +	kempld_wdt_release(inode, file);
> +
> +	return ret;
> +}
> +
> +static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
> +{
> +	int stage;
> +
> +	wdt->timeout_stage = NULL;
> +	wdt->pretimeout_stage = NULL;
> +
> +	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
> +		kfree(wdt->stage[stage]);
> +		wdt->stage[stage] = NULL;
> +	}
> +}
> +
> +static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
> +{
> +	struct kempld_device_data *pld = wdt->pld;
> +	int i, ret;
> +	u32 timeout_mask;
> +	struct kempld_watchdog_stage *stage;
> +
> +	wdt->stages = 0;
> +	wdt->timeout_stage = NULL;
> +	wdt->pretimeout_stage = NULL;
> +
> +	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
> +		int j;
> +		u8 index, data, data_orig;
> +
> +		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
> +		timeout_mask = ~0;
> +
> +		kempld_get_mutex_set_index(pld, index);
> +
> +		/* Probe each byte individually according to new spec revision.
> +		 * Register content is restored afterwards. */
> +		for (j = 0; j < 4; j++) {
> +			data_orig = kempld_read8(pld, index);
> +			kempld_write8(pld, index, 0x00);
> +			data = kempld_read8(pld, index);
> +			kempld_write8(pld, index, data_orig);
> +			*(((u8 *)&timeout_mask)+j) &= data;
> +			if (data != 0x0)
> +				break;
> +			index++;
> +		}
> +
> +		kempld_release_mutex(pld);
> +
> +		if ((timeout_mask & 0xff) != 0xff) {
> +			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> +					GFP_KERNEL);
> +			if (stage == NULL) {
> +				ret = -ENOMEM;
> +				goto err_alloc_stages;
> +			}
> +			stage->num = i;
> +			stage->timeout_mask = ~timeout_mask;
> +			wdt->stage[i] = stage;
> +			wdt->stages++;
> +
> +			/* assign available stages to timeout and pretimeout */
> +			if (wdt->timeout_stage == NULL) {
> +				wdt->timeout_stage = stage;
> +			} else if ((wdt->pretimeout_stage == NULL) &&
> +				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
> +				wdt->pretimeout_stage = wdt->timeout_stage;
> +				wdt->timeout_stage = stage;
> +			}
> +		} else
> +			wdt->stage[i] = NULL;
> +	}
> +
> +	return 0;
> +
> +err_alloc_stages:
> +	kempld_wdt_release_stages(wdt);
> +
> +	return ret;
> +}
> +
> +static const struct file_operations kempld_wdt_fops = {
> +	.owner		= THIS_MODULE,
> +	.llseek		= no_llseek,
> +	.write		= kempld_wdt_write,
> +	.unlocked_ioctl	= kempld_wdt_ioctl,
> +	.open		= kempld_wdt_open,
> +	.release	= kempld_wdt_release,
> +};
> +
> +static struct miscdevice kempld_wdt_miscdev = {
> +	.minor	= WATCHDOG_MINOR,
> +	.name	= "watchdog",
> +	.fops	= &kempld_wdt_fops,
> +};
> +
> +static int kempld_wdt_probe(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt;
> +	struct kempld_device_data *pld;
> +	u8 status;
> +	int ret;
> +
> +	if (kempld_wdt != NULL) {
> +		dev_err(&pdev->dev,
> +			"unable to support more than one watchdog devices\n");
> +		return -EMFILE;
> +	}
> +
> +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> +	if (wdt == NULL) {
> +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> +		ret = -ENOMEM;
> +		goto err_alloc_dev_data;
> +	}
> +
> +	pld = dev_get_drvdata(pdev->dev.parent);
> +	wdt->pld = pld;
> +
> +	platform_set_drvdata(pdev, wdt);
> +
> +	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
> +		sizeof(wdt->ident.identity));
> +
> +	/* watchdog firmware version is identical to the CPLD version */
> +	wdt->ident.firmware_version = (pld->info.major<<24)
> +		| (pld->info.minor<<16) | pld->info.buildnr;
> +
> +	/* probe how many usable stages we have */
> +	ret = kempld_wdt_probe_stages(wdt);
> +	if (ret)
> +		goto err_probe_stages;
> +
> +	/* get initial watchdog status */
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(wdt->pld);
> +
> +	/* check if the watchdog is already locked and enable the nowayout
> +	 * option in that case */
> +	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
> +			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
> +		if (!nowayout)
> +			dev_warn(wdt->pld->dev,
> +				 "Forcing nowayout - watchdog lock enabled!\n");
> +		nowayout = 1;
> +	}
> +
> +	/* set default values for the case we start the watchdog or change
> +	 * the configuration */
> +	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> +	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> +
> +	/* check if watchdog is enabled */
> +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> +		/* Get current watchdog settings */
> +		kempld_wdt_update_timeouts(wdt);
> +
> +		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
> +			 "%d s timeout and %d s pretimeout!\n",
> +			 wdt->timeout, wdt->pretimeout);
> +	}
> +
> +	/* update the timeout settings if requested by module parameters */
> +	if (timeout > 0)
> +		wdt->timeout = timeout;
> +	if (pretimeout >= 0)
> +		wdt->pretimeout = pretimeout;
> +
> +	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
> +		 "new settings: %d s timeout and %d s pretimeout\n",
> +		 wdt->timeout, wdt->pretimeout);
> +
> +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> +	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
> +		wdt->ident.options |= WDIOF_SETTIMEOUT;
> +	if (wdt->pretimeout_stage)
> +		wdt->ident.options |= WDIOF_PRETIMEOUT;
> +	if (!nowayout)
> +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> +
> +	kempld_wdt = wdt;
> +
> +	ret = misc_register(&kempld_wdt_miscdev);
> +	if (ret)
> +		goto err_misc_register;
> +
> +	dev_info(wdt->pld->dev,
> +		 "%d stage watchdog initialized, pretimeout %ssupported\n",
> +		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
> +
> +	return 0;
> +
> +err_probe_stages:
> +err_misc_register:
> +	kfree(kempld_wdt);
> +	kempld_wdt = NULL;
> +err_alloc_dev_data:
> +	return ret;
> +}
> +
> +static void kempld_wdt_shutdown(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	if (wdt != NULL) {
> +		if (!nowayout)
> +			kempld_wdt_stop(wdt);
> +		else
> +			kempld_wdt_keepalive(wdt);
> +	}
> +}
> +
> +static int kempld_wdt_remove(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +
> +	BUG_ON(wdt != kempld_wdt);
> +
> +	/* stop or at least keepalive the watchdog before we leave */
> +	kempld_wdt_shutdown(pdev);
> +
> +	misc_deregister(&kempld_wdt_miscdev);
> +
> +	kempld_wdt_release_stages(wdt);
> +
> +	kfree(wdt);
> +	kempld_wdt = NULL;
> +	platform_set_drvdata(pdev, NULL);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int wdt_pm_status_store;
> +
> +/* Disable watchdog if it is active during suspend */
> +static int kempld_wdt_suspend(struct platform_device *pdev,
> +				pm_message_t message)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
> +
> +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> +	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
> +	kempld_release_mutex(pld);
> +
> +	kempld_wdt_update_timeouts(wdt);
> +
> +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
> +		kempld_wdt_shutdown(pdev);
> +
> +	return 0;
> +}
> +
> +/* Enable watchdog and configure it if necessary */
> +static int kempld_wdt_resume(struct platform_device *pdev)
> +{
> +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> +	int ret;
> +
> +	/* if watchdog was stopped before suspend be sure it gets disabled
> +	 * again, for the case BIOS has enabled it during resume */
> +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
> +		ret = kempld_wdt_settimeout(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +		ret = kempld_wdt_start(wdt);
> +		if (ret)
> +			goto err_enable_wdt;
> +
> +		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
> +			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
> +			 wdt->pretimeout);
> +	} else
> +		kempld_wdt_shutdown(pdev);
> +
> +	return 0;
> +
> +err_enable_wdt:
> +	dev_err(wdt->pld->dev,
> +		"Failed to reenable the watchdog timer after resume!\n");
> +
> +	return ret;
> +}
> +#else
> +#define kempld_wdt_suspend	NULL
> +#define kempld_wdt_resume	NULL
> +#endif
> +
> +static struct platform_driver kempld_wdt_driver = {
> +	.driver = {
> +		.name = "kempld-wdt",
> +		.owner = THIS_MODULE,
> +	},
> +	.probe = kempld_wdt_probe,
> +	.remove = kempld_wdt_remove,
> +	.shutdown = kempld_wdt_shutdown,
> +	.suspend = kempld_wdt_suspend,
> +	.resume = kempld_wdt_resume,
> +};
> +
> +static int __init kempld_wdt_init(void)
> +{
> +	return platform_driver_register(&kempld_wdt_driver);
> +}
> +
> +static void __exit kempld_wdt_exit(void)
> +{
> +	platform_driver_unregister(&kempld_wdt_driver);
> +}
> +
> +module_init(kempld_wdt_init);
> +module_exit(kempld_wdt_exit);
> +
> +MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
> +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
> new file mode 100644
> index 0000000..80f68f6
> --- /dev/null
> +++ b/drivers/watchdog/kempld_wdt.h
> @@ -0,0 +1,75 @@
> +/*
> + *  kempld_wdt.h - Kontron PLD watchdog driver definitions
> + *
> + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> + *  Author: Michael Brunner <michael.brunner@kontron.com>
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License 2 as published
> + *  by the Free Software Foundation.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + *
> + *  You should have received a copy of the GNU General Public License
> + *  along with this program; see the file COPYING.  If not, write to
> + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#ifndef _KEMPLD_WDT_H_
> +#define _KEMPLD_WDT_H_
> +
> +/* watchdog register definitions */
> +#define KEMPLD_WDT_KICK			0x16
> +#define KEMPLD_WDT_CFG			0x17
> +#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
> +#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
> +#define		KEMPLD_WDT_CFG_ENABLE			0x10
> +#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
> +#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
> +#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
> +#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
> +#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
> +#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
> +#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
> +#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
> +#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
> +#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
> +#define KEMPLD_WDT_MAX_STAGES		3
> +
> +#define	KEMPLD_WDT_ACTION_NONE		0x0
> +#define	KEMPLD_WDT_ACTION_RESET		0x1
> +#define	KEMPLD_WDT_ACTION_NMI		0x2
> +#define	KEMPLD_WDT_ACTION_SMI		0x3
> +#define	KEMPLD_WDT_ACTION_SCI		0x4
> +#define	KEMPLD_WDT_ACTION_DELAY		0x5
> +
> +#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
> +#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
> +#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
> +
> +const int kempld_prescaler_bits[] = { 21, 17, 12 };
> +#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
> +
> +
> +struct kempld_watchdog_stage {
> +	int	num;
> +	u32	timeout_mask;
> +};
> +
> +struct kempld_watchdog_data {
> +	int				timeout;
> +	int				pretimeout;
> +	unsigned long			is_open;
> +	unsigned long			expect_close;
> +	int				stages;
> +	struct kempld_watchdog_stage	*timeout_stage;
> +	struct kempld_watchdog_stage	*pretimeout_stage;
> +	struct kempld_device_data	*pld;
> +	struct watchdog_info		ident;
> +	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
> +};
> +
> +#endif /* _KEMPLD_WDT_H_ */
> -- 
> 1.7.9.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Kevin Strasser April 10, 2013, 4:57 p.m. UTC | #2
On Wed, Apr 10, 2013 at 09:47:17AM -0700, Guenter Roeck wrote:
> On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> > From: Michael Brunner <michael.brunner@kontron.com>
> > 
> > Add watchdog timer support for the on-board PLD found on some Kontron
> > embedded modules.
> > 
> > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> 
> Personally I would prefer two separate patches for the two drivers,
> and to have the drivers converted to the watchdog infrastructure.
> Wim's call, of course.
> 
Thanks for the feedback. I'm happy to do both if Wim thinks it's
necessary.

-Kevin

> Thanks,
> Guenter
> 
> > ---
> >  drivers/watchdog/Kconfig           |   20 +
> >  drivers/watchdog/Makefile          |    2 +
> >  drivers/watchdog/kempld_now1_wdt.c |  602 +++++++++++++++++++++++++++
> >  drivers/watchdog/kempld_wdt.c      |  796 ++++++++++++++++++++++++++++++++++++
> >  drivers/watchdog/kempld_wdt.h      |   75 ++++
> >  5 files changed, 1495 insertions(+)
> >  create mode 100644 drivers/watchdog/kempld_now1_wdt.c
> >  create mode 100644 drivers/watchdog/kempld_wdt.c
> >  create mode 100644 drivers/watchdog/kempld_wdt.h
> > 
> > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> > index 9fcc70c..9ac71ca 100644
> > --- a/drivers/watchdog/Kconfig
> > +++ b/drivers/watchdog/Kconfig
> > @@ -687,6 +687,26 @@ config HP_WATCHDOG
> >  	  To compile this driver as a module, choose M here: the module will be
> >  	  called hpwdt.
> >  
> > +config KEMPLD_WDT
> > +	tristate "Kontron COM watchdog"
> > +	depends on MFD_KEMPLD
> > +	help
> > +	  Support for the PLD watchdog on some Kontron ETX and COMexpress
> > +	  (ETXexpress) modules
> > +
> > +	  This driver can also be built as a module. If so, the module will be
> > +	  called kempld_wdt.
> > +
> > +config KEMPLD_NOW1_WDT
> > +	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
> > +	depends on MFD_KEMPLD
> > +	help
> > +	  Support for the PLD watchdog on the Kontron COMe-mSP1
> > +	  (nanoETXexpress-SP) module.
> > +
> > +	  This driver can also be built as a module. If so, the module will
> > +	  be called kempld_now1_wdt.
> > +
> >  config HPWDT_NMI_DECODING
> >  	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
> >  	depends on HP_WATCHDOG
> > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> > index a300b94..a029930 100644
> > --- a/drivers/watchdog/Makefile
> > +++ b/drivers/watchdog/Makefile
> > @@ -90,6 +90,8 @@ endif
> >  obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
> >  obj-$(CONFIG_IT87_WDT) += it87_wdt.o
> >  obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
> > +obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
> > +obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
> >  obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
> >  obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
> >  obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
> > diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
> > new file mode 100644
> > index 0000000..19b7272
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_now1_wdt.c
> > @@ -0,0 +1,602 @@
> > +/*
> > + *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
> > + *
> > + *  Copyright (c) 2011-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
> > + *        only supports predefined watchdog timeout values.
> > + *
> > + *        The supported timeouts are:
> > + *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 2 as published
> > + *  by the Free Software Foundation.
> > + *
> > + *  This program is distributed in the hope that it will be useful,
> > + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#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/init.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/slab.h>
> > +#include <linux/delay.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/kempld.h>
> > +
> > +#include "kempld_wdt.h"
> > +
> > +#define WATCHDOG_TIMEOUT 30
> > +static int timeout = WATCHDOG_TIMEOUT;
> > +module_param(timeout, int, 0);
> > +MODULE_PARM_DESC(timeout,
> > +		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
> > +		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
> > +
> > +static bool nowayout = WATCHDOG_NOWAYOUT;
> > +module_param(nowayout, bool, 0);
> > +MODULE_PARM_DESC(nowayout,
> > +		 "Watchdog cannot be stopped once started (default="
> > +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> > +
> > +/* nanoETXexpress-SP watchdog register definitions */
> > +#define KEMPLD_WDT_NOW1					0xA2
> > +#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
> > +#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
> > +#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
> > +#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
> > +
> > +/* delay in us necessary due to clock domain sync */
> > +#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
> > +
> > +static struct kempld_watchdog_data *kempld_now1_wdt;
> > +
> > +static int kempld_now1_wdt_read_supported;
> > +static int kempld_now1_wdt_reg_cache;
> > +
> > +static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	if (kempld_now1_wdt_read_supported)
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		/* read out the register again to check if everything worked */
> > +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_release_mutex(pld);
> > +
> > +		/* check if the watchdog was disabled */
> > +		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
> > +			return -EACCES;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		/* read out the register again to check if everything worked */
> > +		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_release_mutex(pld);
> > +
> > +		/* check if the watchdog was disabled */
> > +		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> > +			return -EACCES;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +		/* write the state again to be sure the trigger register has
> > +		 * the right level */
> > +		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +	}
> > +
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
> > +		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
> > +	else
> > +		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
> > +
> > +	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +
> > +static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
> > +					int check_only)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +	int ret = 0;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +
> > +	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
> > +
> > +	switch (wdt->timeout) {
> > +	case 1:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
> > +		break;
> > +	case 5:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
> > +		break;
> > +	case 10:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
> > +		break;
> > +	case 30:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
> > +		break;
> > +	case 60:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
> > +		break;
> > +	case 300:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
> > +		break;
> > +	case 600:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
> > +		break;
> > +	case 900:
> > +		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
> > +		break;
> > +	default:
> > +		ret = -EINVAL;
> > +		dev_err(wdt->pld->dev,
> > +			"Invalid timeout value given!\n");
> > +	}
> > +
> > +	if (!check_only) {
> > +		if (ret == 0) {
> > +			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +
> > +			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
> > +		}
> > +	}
> > +
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int wdt_reg;
> > +	int timeout;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +		kempld_now1_wdt_reg_cache = wdt_reg;
> > +	} else
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
> > +		timeout = 1;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
> > +		timeout = 5;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
> > +		timeout = 10;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
> > +		timeout = 30;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
> > +		timeout = 60;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
> > +		timeout = 300;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
> > +		timeout = 600;
> > +		break;
> > +	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
> > +		timeout = 900;
> > +		break;
> > +	default:
> > +		timeout = -ERANGE;
> > +	}
> > +
> > +	return timeout;
> > +}
> > +
> > +static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
> > +					*data, size_t count, loff_t *ppos)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (count) {
> > +		kempld_now1_wdt_keepalive(wdt);
> > +
> > +		if (!nowayout) {
> > +			size_t i;
> > +
> > +			wdt->expect_close = 0;
> > +
> > +			for (i = 0; i < count; i++) {
> > +				char c;
> > +				if (get_user(c, data+i))
> > +					return -EFAULT;
> > +				if (c == 'V')
> > +					wdt->expect_close = 42;
> > +			}
> > +		}
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
> > +				unsigned long arg)
> > +{
> > +	void __user *argp = (void __user *)arg;
> > +	int __user *p = argp;
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +	int options;
> > +	int value;
> > +	int ret = 0;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	switch (cmd) {
> > +	case WDIOC_GETSUPPORT:
> > +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> > +			ret = -EFAULT;
> > +		break;
> > +	case WDIOC_GETSTATUS:
> > +	case WDIOC_GETBOOTSTATUS:
> > +		ret = put_user(0, p);
> > +		break;
> > +	case WDIOC_SETOPTIONS:
> > +		if (get_user(options, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		if (options & WDIOS_DISABLECARD)
> > +			ret = kempld_now1_wdt_stop(wdt);
> > +		if (options & WDIOS_ENABLECARD) {
> > +			ret = kempld_now1_wdt_start(wdt);
> > +			kempld_now1_wdt_keepalive(wdt);
> > +		}
> > +		break;
> > +	case WDIOC_KEEPALIVE:
> > +		kempld_now1_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_SETTIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		wdt->timeout = value;
> > +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> > +		kempld_now1_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETTIMEOUT:
> > +		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		if (timeout < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(timeout, p);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (wdt->expect_close)
> > +		kempld_now1_wdt_stop(wdt);
> > +	else {
> > +		dev_warn(wdt->pld->dev,
> > +			 "Unexpected close, not stopping watchdog!\n");
> > +		kempld_now1_wdt_keepalive(wdt);
> > +	}
> > +
> > +	kempld_now1_wdt->expect_close = 0;
> > +
> > +	clear_bit(0, &wdt->is_open);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
> > +{
> > +	int ret;
> > +	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 wdt_reg;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (test_and_set_bit(0, &wdt->is_open))
> > +		return -EBUSY;
> > +
> > +	if (nowayout)
> > +		__module_get(THIS_MODULE);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = kempld_now1_wdt_reg_cache;
> > +	}
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(pld);
> > +
> > +	/* kick the watchdog if it is already enabled, otherwise start it */
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
> > +		kempld_now1_wdt_keepalive(wdt);
> > +	} else {
> > +		ret = kempld_now1_wdt_settimeout(wdt, 0);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_now1_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +	}
> > +
> > +	return nonseekable_open(inode, file);
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> > +	wdt->expect_close = 1;
> > +	kempld_now1_wdt_release(inode, file);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct file_operations kempld_now1_wdt_fops = {
> > +	.owner		= THIS_MODULE,
> > +	.llseek		= no_llseek,
> > +	.write		= kempld_now1_wdt_write,
> > +	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
> > +	.open		= kempld_now1_wdt_open,
> > +	.release	= kempld_now1_wdt_release,
> > +};
> > +
> > +static struct miscdevice kempld_now1_wdt_miscdev = {
> > +	.minor	= WATCHDOG_MINOR,
> > +	.name	= "watchdog",
> > +	.fops	= &kempld_now1_wdt_fops,
> > +};
> > +
> > +static int kempld_now1_wdt_probe(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt;
> > +	struct kempld_device_data *pld;
> > +	u8 wdt_reg;
> > +	int ret;
> > +
> > +	if (kempld_now1_wdt != NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to support more than one watchdog devices\n");
> > +		return -EMFILE;
> > +	}
> > +
> > +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> > +	if (wdt == NULL) {
> > +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> > +					GFP_KERNEL);
> > +	if (wdt->timeout_stage == NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to get memory for watchdog stage\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	wdt->stages = 1;
> > +	wdt->stage[0] = wdt->timeout_stage;
> > +
> > +	pld = dev_get_drvdata(pdev->dev.parent);
> > +	wdt->pld = pld;
> > +
> > +	platform_set_drvdata(pdev, wdt);
> > +
> > +	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
> > +		sizeof(wdt->ident.identity));
> > +
> > +	/* set default values for the case we start the watchdog or change
> > +	 * the configuration */
> > +	wdt->timeout = timeout;
> > +
> > +	/* use settimeout to check if the timeout parameter is valid */
> > +	ret = kempld_now1_wdt_settimeout(wdt, 1);
> > +	if (ret)
> > +		goto err_check_timeout;
> > +	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
> > +	if (pld->info.major > 67)
> > +		kempld_now1_wdt_read_supported = 1;
> > +	else
> > +		dev_info(wdt->pld->dev,
> > +			 "Watchdog revision does not support read - "
> > +			 "unable to get watchdog state!\n");
> > +
> > +	/* get initial watchdog status */
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
> > +	if (kempld_now1_wdt_read_supported) {
> > +		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
> > +		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
> > +	} else {
> > +		wdt_reg = 0x0;
> > +	}
> > +	kempld_now1_wdt_reg_cache = wdt_reg;
> > +	kempld_release_mutex(wdt->pld);
> > +
> > +	/* check if watchdog is enabled */
> > +	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
> > +		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
> > +
> > +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> > +	wdt->ident.options |= WDIOF_SETTIMEOUT;
> > +	if (!nowayout)
> > +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> > +
> > +	kempld_now1_wdt = wdt;
> > +
> > +	ret = misc_register(&kempld_now1_wdt_miscdev);
> > +	if (ret)
> > +		goto err_misc_register;
> > +
> > +	dev_info(wdt->pld->dev, "watchdog initialized\n");
> > +
> > +	return 0;
> > +
> > +err_misc_register:
> > +	kfree(kempld_now1_wdt);
> > +	kempld_now1_wdt = NULL;
> > +err_check_timeout:
> > +err_alloc_dev_data:
> > +	return ret;
> > +}
> > +
> > +static int kempld_now1_wdt_remove(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_now1_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	if (wdt != NULL) {
> > +		if (!nowayout)
> > +			kempld_now1_wdt_stop(wdt);
> > +		else
> > +			kempld_now1_wdt_keepalive(wdt);
> > +	}
> > +
> > +	misc_deregister(&kempld_now1_wdt_miscdev);
> > +
> > +	kfree(wdt);
> > +	kempld_now1_wdt = NULL;
> > +	platform_set_drvdata(pdev, NULL);
> > +
> > +	return 0;
> > +}
> > +
> > +static struct platform_driver kempld_now1_wdt_driver = {
> > +	.driver = {
> > +		.name = "kempld_now1-wdt",
> > +		.owner = THIS_MODULE,
> > +	},
> > +	.probe = kempld_now1_wdt_probe,
> > +	.remove = kempld_now1_wdt_remove,
> > +};
> > +
> > +static int __init kempld_now1_wdt_init(void)
> > +{
> > +	return platform_driver_register(&kempld_now1_wdt_driver);
> > +}
> > +
> > +static void __exit kempld_now1_wdt_exit(void)
> > +{
> > +	platform_driver_unregister(&kempld_now1_wdt_driver);
> > +}
> > +
> > +module_init(kempld_now1_wdt_init);
> > +module_exit(kempld_now1_wdt_exit);
> > +
> > +MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
> > +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> > diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
> > new file mode 100644
> > index 0000000..bc150e5
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_wdt.c
> > @@ -0,0 +1,796 @@
> > +/*
> > + *  kempld_wdt.c - Kontron PLD watchdog driver
> > + *
> > + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  Note: From the PLD watchdog point of view timeout and pretimeout are
> > + *        defined differently than in the kernel.
> > + *        First the pretimeout stage runs out before the timeout stage gets
> > + *        active. This has to be kept in mind.
> > + *
> > + *  Kernel/API:                     P-----| pretimeout
> > + *                |-----------------------T timeout
> > + *  Watchdog:     |-----------------P       pretimeout_stage
> > + *                                  |-----T timeout_stage
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 2 as published
> > + *  by the Free Software Foundation.
> > + *
> > + *  This program is distributed in the hope that it will be useful,
> > + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#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/init.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/slab.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/mfd/kempld.h>
> > +
> > +#include "kempld_wdt.h"
> > +
> > +#define WATCHDOG_DEFAULT_TIMEOUT 20
> > +#define WATCHDOG_DEFAULT_PRETIMEOUT 0
> > +static int timeout = -1;
> > +static int pretimeout = -1;
> > +/* The maximum timeout values have to be probed */
> > +module_param(timeout, int, 0);
> > +MODULE_PARM_DESC(timeout,
> > +		 "Watchdog timeout in seconds. (>0, default="
> > +		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
> > +module_param(pretimeout, int, 0);
> > +MODULE_PARM_DESC(pretimeout,
> > +		 "Watchdog pretimeout in seconds. (>=0, default="
> > +		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
> > +
> > +static bool nowayout = WATCHDOG_NOWAYOUT;
> > +module_param(nowayout, bool, 0);
> > +MODULE_PARM_DESC(nowayout,
> > +		 "Watchdog cannot be stopped once started (default="
> > +		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> > +
> > +static struct kempld_watchdog_data *kempld_wdt;
> > +
> > +static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	status |= KEMPLD_WDT_CFG_ENABLE;
> > +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	/* check if the watchdog was enabled */
> > +	if (!(status & KEMPLD_WDT_CFG_ENABLE))
> > +		return -EACCES;
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	status &= ~KEMPLD_WDT_CFG_ENABLE;
> > +	kempld_write8(pld, KEMPLD_WDT_CFG, status);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	/* check if the watchdog was disabled */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE)
> > +		return -EACCES;
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +	int bits;
> > +	u64 timeout;
> > +	u32 remainder;
> > +
> > +	if (stage == NULL)
> > +		return 0;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
> > +	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
> > +	remainder = do_div(timeout, pld->pld_clock);
> > +
> > +	/* Round up the return value if necessary */
> > +	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
> > +		timeout++;
> > +
> > +	return timeout;
> > +}
> > +
> > +static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage,
> > +				 int action)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +
> > +	if (stage == NULL)
> > +		return -EINVAL;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
> > +	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
> > +	if (action == KEMPLD_WDT_ACTION_RESET)
> > +		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
> > +	else
> > +		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
> > +
> > +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
> > +				 struct kempld_watchdog_stage *stage,
> > +				 int timeout)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 stage_cfg;
> > +	u8 prescaler;
> > +	u64 stage_timeout64;
> > +	u32 stage_timeout;
> > +	u32 remainder;
> > +
> > +	if (stage == NULL)
> > +		return -EINVAL;
> > +
> > +	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
> > +
> > +	stage_timeout64 = ((u64)timeout*pld->pld_clock);
> > +	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
> > +	if (remainder)
> > +		stage_timeout64++;
> > +	stage_timeout = stage_timeout64 & stage->timeout_mask;
> > +
> > +	if (stage_timeout64 != (u64)stage_timeout)
> > +		return -EINVAL;
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +
> > +	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
> > +	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
> > +	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
> > +	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
> > +	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
> > +			stage_timeout);
> > +
> > +	kempld_release_mutex(pld);
> > +
> > +	return 0;
> > +}
> > +
> > +static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
> > +{
> > +	int pretimeout_stage;
> > +	int timeout_stage;
> > +
> > +	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +
> > +	if (pretimeout_stage)
> > +		wdt->pretimeout = timeout_stage;
> > +	else
> > +		wdt->pretimeout = 0;
> > +
> > +	wdt->timeout = pretimeout_stage + timeout_stage;
> > +
> > +	if (wdt->pretimeout < 0) {
> > +		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> > +		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
> > +			" -> using driver default\n");
> > +	}
> > +	if (wdt->timeout < 0) {
> > +		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> > +		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
> > +			" -> using driver default\n");
> > +	}
> > +}
> > +
> > +static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
> > +{
> > +	int stage_timeout;
> > +	int stage_pretimeout;
> > +	int ret;
> > +
> > +	if ((wdt->timeout <= 0) ||
> > +		(wdt->pretimeout < 0) ||
> > +		(wdt->pretimeout > wdt->timeout)) {
> > +		ret = -EINVAL;
> > +		goto err_check_values;
> > +	}
> > +
> > +	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
> > +		if (wdt->pretimeout != 0)
> > +			dev_warn(wdt->pld->dev,
> > +				 "no pretimeout stage available\n"
> > +				 " -> only enabling reset\n");
> > +		stage_pretimeout = 0;
> > +		stage_timeout = wdt->timeout;
> > +	} else {
> > +		stage_pretimeout = wdt->timeout - wdt->pretimeout;
> > +		stage_timeout = wdt->pretimeout;
> > +	}
> > +
> > +	if (stage_pretimeout != 0) {
> > +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> > +						KEMPLD_WDT_ACTION_NMI);
> > +	} else if ((stage_pretimeout == 0)
> > +			&& (wdt->pretimeout_stage != NULL)) {
> > +		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
> > +						KEMPLD_WDT_ACTION_NONE);
> > +	} else
> > +		ret = 0;
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	if (stage_pretimeout != 0) {
> > +		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
> > +						 stage_pretimeout);
> > +		if (ret)
> > +			goto err_setstage;
> > +	}
> > +
> > +	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
> > +					KEMPLD_WDT_ACTION_RESET);
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
> > +					 stage_timeout);
> > +	if (ret)
> > +		goto err_setstage;
> > +
> > +	return 0;
> > +err_setstage:
> > +err_check_values:
> > +	return ret;
> > +}
> > +
> > +static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
> > +				size_t count, loff_t *ppos)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (count) {
> > +		kempld_wdt_keepalive(wdt);
> > +
> > +		if (!nowayout) {
> > +			size_t i;
> > +
> > +			wdt->expect_close = 0;
> > +
> > +			for (i = 0; i < count; i++) {
> > +				char c;
> > +				if (get_user(c, data+i))
> > +					return -EFAULT;
> > +				if (c == 'V')
> > +					wdt->expect_close = 42;
> > +			}
> > +		}
> > +	}
> > +
> > +	return count;
> > +}
> > +
> > +static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
> > +				unsigned long arg)
> > +{
> > +	void __user *argp = (void __user *)arg;
> > +	int __user *p = argp;
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +	int options;
> > +	int value;
> > +	int ret = 0;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	switch (cmd) {
> > +	case WDIOC_GETSUPPORT:
> > +		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
> > +			ret = -EFAULT;
> > +		break;
> > +	case WDIOC_GETSTATUS:
> > +	case WDIOC_GETBOOTSTATUS:
> > +		ret = put_user(0, p);
> > +		break;
> > +	case WDIOC_SETOPTIONS:
> > +		if (get_user(options, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		if (options & WDIOS_DISABLECARD)
> > +			ret = kempld_wdt_stop(wdt);
> > +		if (options & WDIOS_ENABLECARD) {
> > +			ret = kempld_wdt_start(wdt);
> > +			kempld_wdt_keepalive(wdt);
> > +		}
> > +		break;
> > +	case WDIOC_KEEPALIVE:
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_SETTIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		kempld_wdt_update_timeouts(wdt);
> > +		wdt->timeout = value;
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETTIMEOUT:
> > +		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +		if (value < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(value, p);
> > +		break;
> > +	case WDIOC_SETPRETIMEOUT:
> > +		if (get_user(value, p)) {
> > +			ret = -EFAULT;
> > +			break;
> > +		}
> > +		kempld_wdt_update_timeouts(wdt);
> > +		wdt->pretimeout = value;
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		kempld_wdt_keepalive(wdt);
> > +		break;
> > +	case WDIOC_GETPRETIMEOUT:
> > +		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
> > +		if (value)
> > +			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
> > +		if (value < 0)
> > +			ret = ERANGE;
> > +		else
> > +			ret = put_user(value, p);
> > +		break;
> > +	default:
> > +		ret = -ENOTTY;
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int kempld_wdt_release(struct inode *inode, struct file *file)
> > +{
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (wdt->expect_close)
> > +		kempld_wdt_stop(wdt);
> > +	else {
> > +		dev_warn(wdt->pld->dev,
> > +			 "Unexpected close, not stopping watchdog!\n");
> > +		kempld_wdt_keepalive(wdt);
> > +	}
> > +
> > +	kempld_wdt->expect_close = 0;
> > +
> > +	clear_bit(0, &wdt->is_open);
> > +
> > +	return 0;
> > +}
> > +
> > +static int kempld_wdt_open(struct inode *inode, struct file *file)
> > +{
> > +	int ret;
> > +	struct kempld_watchdog_data *wdt = kempld_wdt;
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	u8 status;
> > +
> > +	BUG_ON(wdt == NULL);
> > +
> > +	if (test_and_set_bit(0, &wdt->is_open))
> > +		return -EBUSY;
> > +
> > +	if (nowayout)
> > +		__module_get(THIS_MODULE);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(pld);
> > +
> > +	/* kick the watchdog if it is already enabled, otherwise start it */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> > +		kempld_wdt_keepalive(wdt);
> > +	} else {
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +	}
> > +
> > +	return nonseekable_open(inode, file);
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
> > +	wdt->expect_close = 1;
> > +	kempld_wdt_release(inode, file);
> > +
> > +	return ret;
> > +}
> > +
> > +static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
> > +{
> > +	int stage;
> > +
> > +	wdt->timeout_stage = NULL;
> > +	wdt->pretimeout_stage = NULL;
> > +
> > +	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
> > +		kfree(wdt->stage[stage]);
> > +		wdt->stage[stage] = NULL;
> > +	}
> > +}
> > +
> > +static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
> > +{
> > +	struct kempld_device_data *pld = wdt->pld;
> > +	int i, ret;
> > +	u32 timeout_mask;
> > +	struct kempld_watchdog_stage *stage;
> > +
> > +	wdt->stages = 0;
> > +	wdt->timeout_stage = NULL;
> > +	wdt->pretimeout_stage = NULL;
> > +
> > +	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
> > +		int j;
> > +		u8 index, data, data_orig;
> > +
> > +		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
> > +		timeout_mask = ~0;
> > +
> > +		kempld_get_mutex_set_index(pld, index);
> > +
> > +		/* Probe each byte individually according to new spec revision.
> > +		 * Register content is restored afterwards. */
> > +		for (j = 0; j < 4; j++) {
> > +			data_orig = kempld_read8(pld, index);
> > +			kempld_write8(pld, index, 0x00);
> > +			data = kempld_read8(pld, index);
> > +			kempld_write8(pld, index, data_orig);
> > +			*(((u8 *)&timeout_mask)+j) &= data;
> > +			if (data != 0x0)
> > +				break;
> > +			index++;
> > +		}
> > +
> > +		kempld_release_mutex(pld);
> > +
> > +		if ((timeout_mask & 0xff) != 0xff) {
> > +			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
> > +					GFP_KERNEL);
> > +			if (stage == NULL) {
> > +				ret = -ENOMEM;
> > +				goto err_alloc_stages;
> > +			}
> > +			stage->num = i;
> > +			stage->timeout_mask = ~timeout_mask;
> > +			wdt->stage[i] = stage;
> > +			wdt->stages++;
> > +
> > +			/* assign available stages to timeout and pretimeout */
> > +			if (wdt->timeout_stage == NULL) {
> > +				wdt->timeout_stage = stage;
> > +			} else if ((wdt->pretimeout_stage == NULL) &&
> > +				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
> > +				wdt->pretimeout_stage = wdt->timeout_stage;
> > +				wdt->timeout_stage = stage;
> > +			}
> > +		} else
> > +			wdt->stage[i] = NULL;
> > +	}
> > +
> > +	return 0;
> > +
> > +err_alloc_stages:
> > +	kempld_wdt_release_stages(wdt);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct file_operations kempld_wdt_fops = {
> > +	.owner		= THIS_MODULE,
> > +	.llseek		= no_llseek,
> > +	.write		= kempld_wdt_write,
> > +	.unlocked_ioctl	= kempld_wdt_ioctl,
> > +	.open		= kempld_wdt_open,
> > +	.release	= kempld_wdt_release,
> > +};
> > +
> > +static struct miscdevice kempld_wdt_miscdev = {
> > +	.minor	= WATCHDOG_MINOR,
> > +	.name	= "watchdog",
> > +	.fops	= &kempld_wdt_fops,
> > +};
> > +
> > +static int kempld_wdt_probe(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt;
> > +	struct kempld_device_data *pld;
> > +	u8 status;
> > +	int ret;
> > +
> > +	if (kempld_wdt != NULL) {
> > +		dev_err(&pdev->dev,
> > +			"unable to support more than one watchdog devices\n");
> > +		return -EMFILE;
> > +	}
> > +
> > +	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
> > +	if (wdt == NULL) {
> > +		dev_err(&pdev->dev, "unable to get memory for device data\n");
> > +		ret = -ENOMEM;
> > +		goto err_alloc_dev_data;
> > +	}
> > +
> > +	pld = dev_get_drvdata(pdev->dev.parent);
> > +	wdt->pld = pld;
> > +
> > +	platform_set_drvdata(pdev, wdt);
> > +
> > +	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
> > +		sizeof(wdt->ident.identity));
> > +
> > +	/* watchdog firmware version is identical to the CPLD version */
> > +	wdt->ident.firmware_version = (pld->info.major<<24)
> > +		| (pld->info.minor<<16) | pld->info.buildnr;
> > +
> > +	/* probe how many usable stages we have */
> > +	ret = kempld_wdt_probe_stages(wdt);
> > +	if (ret)
> > +		goto err_probe_stages;
> > +
> > +	/* get initial watchdog status */
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	status = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(wdt->pld);
> > +
> > +	/* check if the watchdog is already locked and enable the nowayout
> > +	 * option in that case */
> > +	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
> > +			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
> > +		if (!nowayout)
> > +			dev_warn(wdt->pld->dev,
> > +				 "Forcing nowayout - watchdog lock enabled!\n");
> > +		nowayout = 1;
> > +	}
> > +
> > +	/* set default values for the case we start the watchdog or change
> > +	 * the configuration */
> > +	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
> > +	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
> > +
> > +	/* check if watchdog is enabled */
> > +	if (status & KEMPLD_WDT_CFG_ENABLE) {
> > +		/* Get current watchdog settings */
> > +		kempld_wdt_update_timeouts(wdt);
> > +
> > +		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
> > +			 "%d s timeout and %d s pretimeout!\n",
> > +			 wdt->timeout, wdt->pretimeout);
> > +	}
> > +
> > +	/* update the timeout settings if requested by module parameters */
> > +	if (timeout > 0)
> > +		wdt->timeout = timeout;
> > +	if (pretimeout >= 0)
> > +		wdt->pretimeout = pretimeout;
> > +
> > +	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
> > +		 "new settings: %d s timeout and %d s pretimeout\n",
> > +		 wdt->timeout, wdt->pretimeout);
> > +
> > +	wdt->ident.options = WDIOF_KEEPALIVEPING;
> > +	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
> > +		wdt->ident.options |= WDIOF_SETTIMEOUT;
> > +	if (wdt->pretimeout_stage)
> > +		wdt->ident.options |= WDIOF_PRETIMEOUT;
> > +	if (!nowayout)
> > +		wdt->ident.options |= WDIOF_MAGICCLOSE;
> > +
> > +	kempld_wdt = wdt;
> > +
> > +	ret = misc_register(&kempld_wdt_miscdev);
> > +	if (ret)
> > +		goto err_misc_register;
> > +
> > +	dev_info(wdt->pld->dev,
> > +		 "%d stage watchdog initialized, pretimeout %ssupported\n",
> > +		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
> > +
> > +	return 0;
> > +
> > +err_probe_stages:
> > +err_misc_register:
> > +	kfree(kempld_wdt);
> > +	kempld_wdt = NULL;
> > +err_alloc_dev_data:
> > +	return ret;
> > +}
> > +
> > +static void kempld_wdt_shutdown(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	if (wdt != NULL) {
> > +		if (!nowayout)
> > +			kempld_wdt_stop(wdt);
> > +		else
> > +			kempld_wdt_keepalive(wdt);
> > +	}
> > +}
> > +
> > +static int kempld_wdt_remove(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +
> > +	BUG_ON(wdt != kempld_wdt);
> > +
> > +	/* stop or at least keepalive the watchdog before we leave */
> > +	kempld_wdt_shutdown(pdev);
> > +
> > +	misc_deregister(&kempld_wdt_miscdev);
> > +
> > +	kempld_wdt_release_stages(wdt);
> > +
> > +	kfree(wdt);
> > +	kempld_wdt = NULL;
> > +	platform_set_drvdata(pdev, NULL);
> > +
> > +	return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM
> > +static int wdt_pm_status_store;
> > +
> > +/* Disable watchdog if it is active during suspend */
> > +static int kempld_wdt_suspend(struct platform_device *pdev,
> > +				pm_message_t message)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
> > +
> > +	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
> > +	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
> > +	kempld_release_mutex(pld);
> > +
> > +	kempld_wdt_update_timeouts(wdt);
> > +
> > +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
> > +		kempld_wdt_shutdown(pdev);
> > +
> > +	return 0;
> > +}
> > +
> > +/* Enable watchdog and configure it if necessary */
> > +static int kempld_wdt_resume(struct platform_device *pdev)
> > +{
> > +	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
> > +	int ret;
> > +
> > +	/* if watchdog was stopped before suspend be sure it gets disabled
> > +	 * again, for the case BIOS has enabled it during resume */
> > +	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
> > +		ret = kempld_wdt_settimeout(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +		ret = kempld_wdt_start(wdt);
> > +		if (ret)
> > +			goto err_enable_wdt;
> > +
> > +		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
> > +			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
> > +			 wdt->pretimeout);
> > +	} else
> > +		kempld_wdt_shutdown(pdev);
> > +
> > +	return 0;
> > +
> > +err_enable_wdt:
> > +	dev_err(wdt->pld->dev,
> > +		"Failed to reenable the watchdog timer after resume!\n");
> > +
> > +	return ret;
> > +}
> > +#else
> > +#define kempld_wdt_suspend	NULL
> > +#define kempld_wdt_resume	NULL
> > +#endif
> > +
> > +static struct platform_driver kempld_wdt_driver = {
> > +	.driver = {
> > +		.name = "kempld-wdt",
> > +		.owner = THIS_MODULE,
> > +	},
> > +	.probe = kempld_wdt_probe,
> > +	.remove = kempld_wdt_remove,
> > +	.shutdown = kempld_wdt_shutdown,
> > +	.suspend = kempld_wdt_suspend,
> > +	.resume = kempld_wdt_resume,
> > +};
> > +
> > +static int __init kempld_wdt_init(void)
> > +{
> > +	return platform_driver_register(&kempld_wdt_driver);
> > +}
> > +
> > +static void __exit kempld_wdt_exit(void)
> > +{
> > +	platform_driver_unregister(&kempld_wdt_driver);
> > +}
> > +
> > +module_init(kempld_wdt_init);
> > +module_exit(kempld_wdt_exit);
> > +
> > +MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
> > +MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
> > diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
> > new file mode 100644
> > index 0000000..80f68f6
> > --- /dev/null
> > +++ b/drivers/watchdog/kempld_wdt.h
> > @@ -0,0 +1,75 @@
> > +/*
> > + *  kempld_wdt.h - Kontron PLD watchdog driver definitions
> > + *
> > + *  Copyright (c) 2010-2012 Kontron Europe GmbH
> > + *  Author: Michael Brunner <michael.brunner@kontron.com>
> > + *
> > + *  This program is free software; you can redistribute it and/or modify
> > + *  it under the terms of the GNU General Public License 2 as published
> > + *  by the Free Software Foundation.
> > + *
> > + *  This program is distributed in the hope that it will be useful,
> > + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + *  GNU General Public License for more details.
> > + *
> > + *  You should have received a copy of the GNU General Public License
> > + *  along with this program; see the file COPYING.  If not, write to
> > + *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
> > + */
> > +
> > +#ifndef _KEMPLD_WDT_H_
> > +#define _KEMPLD_WDT_H_
> > +
> > +/* watchdog register definitions */
> > +#define KEMPLD_WDT_KICK			0x16
> > +#define KEMPLD_WDT_CFG			0x17
> > +#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
> > +#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
> > +#define		KEMPLD_WDT_CFG_ENABLE			0x10
> > +#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
> > +#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
> > +#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
> > +#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
> > +#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
> > +#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
> > +#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
> > +#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
> > +#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
> > +#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
> > +#define KEMPLD_WDT_MAX_STAGES		3
> > +
> > +#define	KEMPLD_WDT_ACTION_NONE		0x0
> > +#define	KEMPLD_WDT_ACTION_RESET		0x1
> > +#define	KEMPLD_WDT_ACTION_NMI		0x2
> > +#define	KEMPLD_WDT_ACTION_SMI		0x3
> > +#define	KEMPLD_WDT_ACTION_SCI		0x4
> > +#define	KEMPLD_WDT_ACTION_DELAY		0x5
> > +
> > +#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
> > +#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
> > +#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
> > +
> > +const int kempld_prescaler_bits[] = { 21, 17, 12 };
> > +#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
> > +
> > +
> > +struct kempld_watchdog_stage {
> > +	int	num;
> > +	u32	timeout_mask;
> > +};
> > +
> > +struct kempld_watchdog_data {
> > +	int				timeout;
> > +	int				pretimeout;
> > +	unsigned long			is_open;
> > +	unsigned long			expect_close;
> > +	int				stages;
> > +	struct kempld_watchdog_stage	*timeout_stage;
> > +	struct kempld_watchdog_stage	*pretimeout_stage;
> > +	struct kempld_device_data	*pld;
> > +	struct watchdog_info		ident;
> > +	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
> > +};
> > +
> > +#endif /* _KEMPLD_WDT_H_ */
> > -- 
> > 1.7.9.5
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 
--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Wim Van Sebroeck May 26, 2013, 2:38 p.m. UTC | #3
Hi Kevin,

> On Wed, Apr 10, 2013 at 09:47:17AM -0700, Guenter Roeck wrote:
> > On Mon, Apr 08, 2013 at 10:15:21AM -0700, Kevin Strasser wrote:
> > > From: Michael Brunner <michael.brunner@kontron.com>
> > > 
> > > Add watchdog timer support for the on-board PLD found on some Kontron
> > > embedded modules.
> > > 
> > > Signed-off-by: Michael Brunner <michael.brunner@kontron.com>
> > > Signed-off-by: Kevin Strasser <kevin.strasser@linux.intel.com>
> > 
> > Personally I would prefer two separate patches for the two drivers,
> > and to have the drivers converted to the watchdog infrastructure.
> > Wim's call, of course.
> > 
> Thanks for the feedback. I'm happy to do both if Wim thinks it's
> necessary.

Yes, 2 patches with conversion to the new watchdog infrastructure please.

Kind regards,
Wim.

--
To unsubscribe from this list: send the line "unsubscribe linux-i2c" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 9fcc70c..9ac71ca 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -687,6 +687,26 @@  config HP_WATCHDOG
 	  To compile this driver as a module, choose M here: the module will be
 	  called hpwdt.
 
+config KEMPLD_WDT
+	tristate "Kontron COM watchdog"
+	depends on MFD_KEMPLD
+	help
+	  Support for the PLD watchdog on some Kontron ETX and COMexpress
+	  (ETXexpress) modules
+
+	  This driver can also be built as a module. If so, the module will be
+	  called kempld_wdt.
+
+config KEMPLD_NOW1_WDT
+	tristate "Kontron COMe-mSP1 (nanoETXexpress-SP) watchdog"
+	depends on MFD_KEMPLD
+	help
+	  Support for the PLD watchdog on the Kontron COMe-mSP1
+	  (nanoETXexpress-SP) module.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called kempld_now1_wdt.
+
 config HPWDT_NMI_DECODING
 	bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
 	depends on HP_WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index a300b94..a029930 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -90,6 +90,8 @@  endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
+obj-$(COnFIG_KEMPLD_NOW1_WDT) += kempld_now1_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
 obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
 obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
diff --git a/drivers/watchdog/kempld_now1_wdt.c b/drivers/watchdog/kempld_now1_wdt.c
new file mode 100644
index 0000000..19b7272
--- /dev/null
+++ b/drivers/watchdog/kempld_now1_wdt.c
@@ -0,0 +1,602 @@ 
+/*
+ *  kempld_now1_wdt.c - Kontron PLD watchdog driver for COMe-mSP1
+ *
+ *  Copyright (c) 2011-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  Note: The watchdog of the COMe-mSP1 (nanoETXexpress-SP) has one stage and
+ *        only supports predefined watchdog timeout values.
+ *
+ *        The supported timeouts are:
+ *              1 s, 5 s, 10 s, 30 s, 1 min, 5 min, 10 min, 15 min
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 2 as published
+ *  by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#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/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#include "kempld_wdt.h"
+
+#define WATCHDOG_TIMEOUT 30
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. (1, 5, 10, 30, 60, 300, 600, "
+		 "900, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/* nanoETXexpress-SP watchdog register definitions */
+#define KEMPLD_WDT_NOW1					0xA2
+#define		KEMPLD_WDT_NOW1_KICK_MASK		0x80
+#define		KEMPLD_WDT_NOW1_ENABLE_MASK		0x40
+#define		KEMPLD_WDT_NOW1_TIMEOUT_MASK		0x1f
+#define			KEMPLD_WDT_NOW1_TIMEOUT_1SEC	0x00
+#define			KEMPLD_WDT_NOW1_TIMEOUT_5SEC	0x01
+#define			KEMPLD_WDT_NOW1_TIMEOUT_10SEC	0x02
+#define			KEMPLD_WDT_NOW1_TIMEOUT_30SEC	0x03
+#define			KEMPLD_WDT_NOW1_TIMEOUT_1MIN	0x10
+#define			KEMPLD_WDT_NOW1_TIMEOUT_5MIN	0x11
+#define			KEMPLD_WDT_NOW1_TIMEOUT_10MIN	0x12
+#define			KEMPLD_WDT_NOW1_TIMEOUT_15MIN	0x13
+
+/* delay in us necessary due to clock domain sync */
+#define		KEMPLD_WDT_NOW1_SYNC_DELAY		31
+
+static struct kempld_watchdog_data *kempld_now1_wdt;
+
+static int kempld_now1_wdt_read_supported;
+static int kempld_now1_wdt_reg_cache;
+
+static int kempld_now1_wdt_start(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	if (kempld_now1_wdt_read_supported)
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	wdt_reg |= KEMPLD_WDT_NOW1_ENABLE_MASK;
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	if (kempld_now1_wdt_read_supported) {
+		/* read out the register again to check if everything worked */
+		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_release_mutex(pld);
+
+		/* check if the watchdog was disabled */
+		if (!(wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK))
+			return -EACCES;
+	}
+
+	return 0;
+}
+
+static int kempld_now1_wdt_stop(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	wdt_reg &= ~KEMPLD_WDT_NOW1_ENABLE_MASK;
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	if (kempld_now1_wdt_read_supported) {
+		/* read out the register again to check if everything worked */
+		kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_release_mutex(pld);
+
+		/* check if the watchdog was disabled */
+		if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
+			return -EACCES;
+	}
+
+	return 0;
+}
+
+static int kempld_now1_wdt_keepalive(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+	if (kempld_now1_wdt_read_supported) {
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = kempld_now1_wdt_reg_cache;
+		/* write the state again to be sure the trigger register has
+		 * the right level */
+		kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+	}
+
+	if (wdt_reg & KEMPLD_WDT_NOW1_KICK_MASK)
+		wdt_reg &= ~KEMPLD_WDT_NOW1_KICK_MASK;
+	else
+		wdt_reg |= KEMPLD_WDT_NOW1_KICK_MASK;
+
+	udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+	kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+
+	kempld_now1_wdt_reg_cache = wdt_reg;
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+
+static int kempld_now1_wdt_settimeout(struct kempld_watchdog_data *wdt,
+					int check_only)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+	int ret = 0;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+
+	wdt_reg &= ~KEMPLD_WDT_NOW1_TIMEOUT_MASK;
+
+	switch (wdt->timeout) {
+	case 1:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1SEC;
+		break;
+	case 5:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5SEC;
+		break;
+	case 10:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10SEC;
+		break;
+	case 30:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_30SEC;
+		break;
+	case 60:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_1MIN;
+		break;
+	case 300:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_5MIN;
+		break;
+	case 600:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_10MIN;
+		break;
+	case 900:
+		wdt_reg |= KEMPLD_WDT_NOW1_TIMEOUT_15MIN;
+		break;
+	default:
+		ret = -EINVAL;
+		dev_err(wdt->pld->dev,
+			"Invalid timeout value given!\n");
+	}
+
+	if (!check_only) {
+		if (ret == 0) {
+			udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+
+			kempld_write8(pld, KEMPLD_WDT_NOW1, wdt_reg);
+		}
+	}
+
+	kempld_now1_wdt_reg_cache = wdt_reg;
+
+	kempld_release_mutex(pld);
+
+	return ret;
+}
+
+static int kempld_now1_wdt_gettimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int wdt_reg;
+	int timeout;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+		kempld_now1_wdt_reg_cache = wdt_reg;
+	} else
+		wdt_reg = kempld_now1_wdt_reg_cache;
+
+	kempld_release_mutex(pld);
+
+	switch (wdt_reg&KEMPLD_WDT_NOW1_TIMEOUT_MASK) {
+	case KEMPLD_WDT_NOW1_TIMEOUT_1SEC:
+		timeout = 1;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_5SEC:
+		timeout = 5;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_10SEC:
+		timeout = 10;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_30SEC:
+		timeout = 30;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_1MIN:
+		timeout = 60;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_5MIN:
+		timeout = 300;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_10MIN:
+		timeout = 600;
+		break;
+	case KEMPLD_WDT_NOW1_TIMEOUT_15MIN:
+		timeout = 900;
+		break;
+	default:
+		timeout = -ERANGE;
+	}
+
+	return timeout;
+}
+
+static ssize_t kempld_now1_wdt_write(struct file *file, const char __user
+					*data, size_t count, loff_t *ppos)
+{
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (count) {
+		kempld_now1_wdt_keepalive(wdt);
+
+		if (!nowayout) {
+			size_t i;
+
+			wdt->expect_close = 0;
+
+			for (i = 0; i < count; i++) {
+				char c;
+				if (get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					wdt->expect_close = 42;
+			}
+		}
+	}
+
+	return count;
+}
+
+static long kempld_now1_wdt_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+	int options;
+	int value;
+	int ret = 0;
+
+	BUG_ON(wdt == NULL);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		if (options & WDIOS_DISABLECARD)
+			ret = kempld_now1_wdt_stop(wdt);
+		if (options & WDIOS_ENABLECARD) {
+			ret = kempld_now1_wdt_start(wdt);
+			kempld_now1_wdt_keepalive(wdt);
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		kempld_now1_wdt_keepalive(wdt);
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		wdt->timeout = value;
+		ret = kempld_now1_wdt_settimeout(wdt, 0);
+		kempld_now1_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETTIMEOUT:
+		value = kempld_now1_wdt_gettimeout(wdt, wdt->timeout_stage);
+		if (timeout < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(timeout, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int kempld_now1_wdt_release(struct inode *inode, struct file *file)
+{
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (wdt->expect_close)
+		kempld_now1_wdt_stop(wdt);
+	else {
+		dev_warn(wdt->pld->dev,
+			 "Unexpected close, not stopping watchdog!\n");
+		kempld_now1_wdt_keepalive(wdt);
+	}
+
+	kempld_now1_wdt->expect_close = 0;
+
+	clear_bit(0, &wdt->is_open);
+
+	return 0;
+}
+
+static int kempld_now1_wdt_open(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct kempld_watchdog_data *wdt = kempld_now1_wdt;
+	struct kempld_device_data *pld = wdt->pld;
+	u8 wdt_reg;
+
+	BUG_ON(wdt == NULL);
+
+	if (test_and_set_bit(0, &wdt->is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = kempld_now1_wdt_reg_cache;
+	}
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(pld);
+
+	/* kick the watchdog if it is already enabled, otherwise start it */
+	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK) {
+		kempld_now1_wdt_keepalive(wdt);
+	} else {
+		ret = kempld_now1_wdt_settimeout(wdt, 0);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_now1_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+	}
+
+	return nonseekable_open(inode, file);
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
+	wdt->expect_close = 1;
+	kempld_now1_wdt_release(inode, file);
+
+	return ret;
+}
+
+static const struct file_operations kempld_now1_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= kempld_now1_wdt_write,
+	.unlocked_ioctl	= kempld_now1_wdt_ioctl,
+	.open		= kempld_now1_wdt_open,
+	.release	= kempld_now1_wdt_release,
+};
+
+static struct miscdevice kempld_now1_wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &kempld_now1_wdt_fops,
+};
+
+static int kempld_now1_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt;
+	struct kempld_device_data *pld;
+	u8 wdt_reg;
+	int ret;
+
+	if (kempld_now1_wdt != NULL) {
+		dev_err(&pdev->dev,
+			"unable to support more than one watchdog devices\n");
+		return -EMFILE;
+	}
+
+	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
+	if (wdt == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	wdt->timeout_stage = kzalloc(sizeof(struct kempld_watchdog_stage),
+					GFP_KERNEL);
+	if (wdt->timeout_stage == NULL) {
+		dev_err(&pdev->dev,
+			"unable to get memory for watchdog stage\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	wdt->stages = 1;
+	wdt->stage[0] = wdt->timeout_stage;
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+	wdt->pld = pld;
+
+	platform_set_drvdata(pdev, wdt);
+
+	strncpy(wdt->ident.identity, "nanoETXexpress-SP Watchdog",
+		sizeof(wdt->ident.identity));
+
+	/* set default values for the case we start the watchdog or change
+	 * the configuration */
+	wdt->timeout = timeout;
+
+	/* use settimeout to check if the timeout parameter is valid */
+	ret = kempld_now1_wdt_settimeout(wdt, 1);
+	if (ret)
+		goto err_check_timeout;
+	wdt->ident.firmware_version = (pld->info.major<<8) + pld->info.minor;
+	if (pld->info.major > 67)
+		kempld_now1_wdt_read_supported = 1;
+	else
+		dev_info(wdt->pld->dev,
+			 "Watchdog revision does not support read - "
+			 "unable to get watchdog state!\n");
+
+	/* get initial watchdog status */
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_NOW1);
+	if (kempld_now1_wdt_read_supported) {
+		udelay(KEMPLD_WDT_NOW1_SYNC_DELAY);
+		wdt_reg = kempld_read8(pld, KEMPLD_WDT_NOW1);
+	} else {
+		wdt_reg = 0x0;
+	}
+	kempld_now1_wdt_reg_cache = wdt_reg;
+	kempld_release_mutex(wdt->pld);
+
+	/* check if watchdog is enabled */
+	if (wdt_reg & KEMPLD_WDT_NOW1_ENABLE_MASK)
+		dev_info(wdt->pld->dev, "Watchdog is already enabled!\n");
+
+	wdt->ident.options = WDIOF_KEEPALIVEPING;
+	wdt->ident.options |= WDIOF_SETTIMEOUT;
+	if (!nowayout)
+		wdt->ident.options |= WDIOF_MAGICCLOSE;
+
+	kempld_now1_wdt = wdt;
+
+	ret = misc_register(&kempld_now1_wdt_miscdev);
+	if (ret)
+		goto err_misc_register;
+
+	dev_info(wdt->pld->dev, "watchdog initialized\n");
+
+	return 0;
+
+err_misc_register:
+	kfree(kempld_now1_wdt);
+	kempld_now1_wdt = NULL;
+err_check_timeout:
+err_alloc_dev_data:
+	return ret;
+}
+
+static int kempld_now1_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_now1_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	if (wdt != NULL) {
+		if (!nowayout)
+			kempld_now1_wdt_stop(wdt);
+		else
+			kempld_now1_wdt_keepalive(wdt);
+	}
+
+	misc_deregister(&kempld_now1_wdt_miscdev);
+
+	kfree(wdt);
+	kempld_now1_wdt = NULL;
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver kempld_now1_wdt_driver = {
+	.driver = {
+		.name = "kempld_now1-wdt",
+		.owner = THIS_MODULE,
+	},
+	.probe = kempld_now1_wdt_probe,
+	.remove = kempld_now1_wdt_remove,
+};
+
+static int __init kempld_now1_wdt_init(void)
+{
+	return platform_driver_register(&kempld_now1_wdt_driver);
+}
+
+static void __exit kempld_now1_wdt_exit(void)
+{
+	platform_driver_unregister(&kempld_now1_wdt_driver);
+}
+
+module_init(kempld_now1_wdt_init);
+module_exit(kempld_now1_wdt_exit);
+
+MODULE_DESCRIPTION("KEM PLD nanoETXexpress-SP Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
new file mode 100644
index 0000000..bc150e5
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.c
@@ -0,0 +1,796 @@ 
+/*
+ *  kempld_wdt.c - Kontron PLD watchdog driver
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  Note: From the PLD watchdog point of view timeout and pretimeout are
+ *        defined differently than in the kernel.
+ *        First the pretimeout stage runs out before the timeout stage gets
+ *        active. This has to be kept in mind.
+ *
+ *  Kernel/API:                     P-----| pretimeout
+ *                |-----------------------T timeout
+ *  Watchdog:     |-----------------P       pretimeout_stage
+ *                                  |-----T timeout_stage
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 2 as published
+ *  by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#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/init.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#include "kempld_wdt.h"
+
+#define WATCHDOG_DEFAULT_TIMEOUT 20
+#define WATCHDOG_DEFAULT_PRETIMEOUT 0
+static int timeout = -1;
+static int pretimeout = -1;
+/* The maximum timeout values have to be probed */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+		 "Watchdog timeout in seconds. (>0, default="
+		__MODULE_STRING(WATCHDOG_DEFAULT_TIMEOUT) ")");
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout,
+		 "Watchdog pretimeout in seconds. (>=0, default="
+		__MODULE_STRING(WATCHDOG_DEFAULT_PRETIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+		 "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static struct kempld_watchdog_data *kempld_wdt;
+
+static int kempld_wdt_start(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status |= KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+
+	kempld_release_mutex(pld);
+
+	/* check if the watchdog was enabled */
+	if (!(status & KEMPLD_WDT_CFG_ENABLE))
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_stop(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	status &= ~KEMPLD_WDT_CFG_ENABLE;
+	kempld_write8(pld, KEMPLD_WDT_CFG, status);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+
+	kempld_release_mutex(pld);
+
+	/* check if the watchdog was disabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE)
+		return -EACCES;
+
+	return 0;
+}
+
+static int kempld_wdt_keepalive(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+
+	kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_gettimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+	int bits;
+	u64 timeout;
+	u32 remainder;
+
+	if (stage == NULL)
+		return 0;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num));
+
+	kempld_release_mutex(pld);
+
+	bits = KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(stage_cfg);
+	timeout = (timeout & stage->timeout_mask) * KEMPLD_PRESCALER(bits);
+	remainder = do_div(timeout, pld->pld_clock);
+
+	/* Round up the return value if necessary */
+	if ((timeout > 0) && (remainder >= (pld->pld_clock/2)))
+		timeout++;
+
+	return timeout;
+}
+
+static int kempld_wdt_setstageaction(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage,
+				 int action)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+
+	if (stage == NULL)
+		return -EINVAL;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ACTION_MASK;
+	stage_cfg |= (action & KEMPLD_WDT_STAGE_CFG_ACTION_MASK);
+	if (action == KEMPLD_WDT_ACTION_RESET)
+		stage_cfg |= KEMPLD_WDT_STAGE_CFG_ASSERT;
+	else
+		stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_ASSERT;
+
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static int kempld_wdt_setstagetimeout(struct kempld_watchdog_data *wdt,
+				 struct kempld_watchdog_stage *stage,
+				 int timeout)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	u8 stage_cfg;
+	u8 prescaler;
+	u64 stage_timeout64;
+	u32 stage_timeout;
+	u32 remainder;
+
+	if (stage == NULL)
+		return -EINVAL;
+
+	prescaler = KEMPLD_WDT_PRESCALER_21BIT;
+
+	stage_timeout64 = ((u64)timeout*pld->pld_clock);
+	remainder = do_div(stage_timeout64, KEMPLD_PRESCALER(prescaler));
+	if (remainder)
+		stage_timeout64++;
+	stage_timeout = stage_timeout64 & stage->timeout_mask;
+
+	if (stage_timeout64 != (u64)stage_timeout)
+		return -EINVAL;
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+
+	stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->num));
+	stage_cfg &= ~KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK;
+	stage_cfg |= KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(prescaler);
+	kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->num), stage_cfg);
+	kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->num),
+			stage_timeout);
+
+	kempld_release_mutex(pld);
+
+	return 0;
+}
+
+static void kempld_wdt_update_timeouts(struct kempld_watchdog_data *wdt)
+{
+	int pretimeout_stage;
+	int timeout_stage;
+
+	pretimeout_stage = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+	timeout_stage = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+
+	if (pretimeout_stage)
+		wdt->pretimeout = timeout_stage;
+	else
+		wdt->pretimeout = 0;
+
+	wdt->timeout = pretimeout_stage + timeout_stage;
+
+	if (wdt->pretimeout < 0) {
+		wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
+		dev_err(wdt->pld->dev, "failed to get valid pretimeout value\n"
+			" -> using driver default\n");
+	}
+	if (wdt->timeout < 0) {
+		wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
+		dev_err(wdt->pld->dev, "failed to get valid timeout value\n"
+			" -> using driver default\n");
+	}
+}
+
+static int kempld_wdt_settimeout(struct kempld_watchdog_data *wdt)
+{
+	int stage_timeout;
+	int stage_pretimeout;
+	int ret;
+
+	if ((wdt->timeout <= 0) ||
+		(wdt->pretimeout < 0) ||
+		(wdt->pretimeout > wdt->timeout)) {
+		ret = -EINVAL;
+		goto err_check_values;
+	}
+
+	if ((wdt->pretimeout == 0) || (wdt->pretimeout_stage == NULL)) {
+		if (wdt->pretimeout != 0)
+			dev_warn(wdt->pld->dev,
+				 "no pretimeout stage available\n"
+				 " -> only enabling reset\n");
+		stage_pretimeout = 0;
+		stage_timeout = wdt->timeout;
+	} else {
+		stage_pretimeout = wdt->timeout - wdt->pretimeout;
+		stage_timeout = wdt->pretimeout;
+	}
+
+	if (stage_pretimeout != 0) {
+		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
+						KEMPLD_WDT_ACTION_NMI);
+	} else if ((stage_pretimeout == 0)
+			&& (wdt->pretimeout_stage != NULL)) {
+		ret = kempld_wdt_setstageaction(wdt, wdt->pretimeout_stage,
+						KEMPLD_WDT_ACTION_NONE);
+	} else
+		ret = 0;
+	if (ret)
+		goto err_setstage;
+
+	if (stage_pretimeout != 0) {
+		ret = kempld_wdt_setstagetimeout(wdt, wdt->pretimeout_stage,
+						 stage_pretimeout);
+		if (ret)
+			goto err_setstage;
+	}
+
+	ret = kempld_wdt_setstageaction(wdt, wdt->timeout_stage,
+					KEMPLD_WDT_ACTION_RESET);
+	if (ret)
+		goto err_setstage;
+
+	ret = kempld_wdt_setstagetimeout(wdt, wdt->timeout_stage,
+					 stage_timeout);
+	if (ret)
+		goto err_setstage;
+
+	return 0;
+err_setstage:
+err_check_values:
+	return ret;
+}
+
+static ssize_t kempld_wdt_write(struct file *file, const char __user *data,
+				size_t count, loff_t *ppos)
+{
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (count) {
+		kempld_wdt_keepalive(wdt);
+
+		if (!nowayout) {
+			size_t i;
+
+			wdt->expect_close = 0;
+
+			for (i = 0; i < count; i++) {
+				char c;
+				if (get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					wdt->expect_close = 42;
+			}
+		}
+	}
+
+	return count;
+}
+
+static long kempld_wdt_ioctl(struct file *file, unsigned int cmd,
+				unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+	int options;
+	int value;
+	int ret = 0;
+
+	BUG_ON(wdt == NULL);
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &wdt->ident, sizeof(wdt->ident)))
+			ret = -EFAULT;
+		break;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		if (options & WDIOS_DISABLECARD)
+			ret = kempld_wdt_stop(wdt);
+		if (options & WDIOS_ENABLECARD) {
+			ret = kempld_wdt_start(wdt);
+			kempld_wdt_keepalive(wdt);
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		kempld_wdt_update_timeouts(wdt);
+		wdt->timeout = value;
+		ret = kempld_wdt_settimeout(wdt);
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETTIMEOUT:
+		value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+		value += kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+		if (value < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(value, p);
+		break;
+	case WDIOC_SETPRETIMEOUT:
+		if (get_user(value, p)) {
+			ret = -EFAULT;
+			break;
+		}
+		kempld_wdt_update_timeouts(wdt);
+		wdt->pretimeout = value;
+		ret = kempld_wdt_settimeout(wdt);
+		kempld_wdt_keepalive(wdt);
+		break;
+	case WDIOC_GETPRETIMEOUT:
+		value = kempld_wdt_gettimeout(wdt, wdt->pretimeout_stage);
+		if (value)
+			value = kempld_wdt_gettimeout(wdt, wdt->timeout_stage);
+		if (value < 0)
+			ret = ERANGE;
+		else
+			ret = put_user(value, p);
+		break;
+	default:
+		ret = -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int kempld_wdt_release(struct inode *inode, struct file *file)
+{
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+
+	BUG_ON(wdt == NULL);
+
+	if (wdt->expect_close)
+		kempld_wdt_stop(wdt);
+	else {
+		dev_warn(wdt->pld->dev,
+			 "Unexpected close, not stopping watchdog!\n");
+		kempld_wdt_keepalive(wdt);
+	}
+
+	kempld_wdt->expect_close = 0;
+
+	clear_bit(0, &wdt->is_open);
+
+	return 0;
+}
+
+static int kempld_wdt_open(struct inode *inode, struct file *file)
+{
+	int ret;
+	struct kempld_watchdog_data *wdt = kempld_wdt;
+	struct kempld_device_data *pld = wdt->pld;
+	u8 status;
+
+	BUG_ON(wdt == NULL);
+
+	if (test_and_set_bit(0, &wdt->is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	/* kick the watchdog if it is already enabled, otherwise start it */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		kempld_wdt_keepalive(wdt);
+	} else {
+		ret = kempld_wdt_settimeout(wdt);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+	}
+
+	return nonseekable_open(inode, file);
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev, "Failed to enable the watchdog timer!\n");
+	wdt->expect_close = 1;
+	kempld_wdt_release(inode, file);
+
+	return ret;
+}
+
+static void kempld_wdt_release_stages(struct kempld_watchdog_data *wdt)
+{
+	int stage;
+
+	wdt->timeout_stage = NULL;
+	wdt->pretimeout_stage = NULL;
+
+	for (stage = 0; stage < KEMPLD_WDT_MAX_STAGES; stage++) {
+		kfree(wdt->stage[stage]);
+		wdt->stage[stage] = NULL;
+	}
+}
+
+static int kempld_wdt_probe_stages(struct kempld_watchdog_data *wdt)
+{
+	struct kempld_device_data *pld = wdt->pld;
+	int i, ret;
+	u32 timeout_mask;
+	struct kempld_watchdog_stage *stage;
+
+	wdt->stages = 0;
+	wdt->timeout_stage = NULL;
+	wdt->pretimeout_stage = NULL;
+
+	for (i = 0; i < KEMPLD_WDT_MAX_STAGES; i++) {
+		int j;
+		u8 index, data, data_orig;
+
+		index = KEMPLD_WDT_STAGE_TIMEOUT(i);
+		timeout_mask = ~0;
+
+		kempld_get_mutex_set_index(pld, index);
+
+		/* Probe each byte individually according to new spec revision.
+		 * Register content is restored afterwards. */
+		for (j = 0; j < 4; j++) {
+			data_orig = kempld_read8(pld, index);
+			kempld_write8(pld, index, 0x00);
+			data = kempld_read8(pld, index);
+			kempld_write8(pld, index, data_orig);
+			*(((u8 *)&timeout_mask)+j) &= data;
+			if (data != 0x0)
+				break;
+			index++;
+		}
+
+		kempld_release_mutex(pld);
+
+		if ((timeout_mask & 0xff) != 0xff) {
+			stage = kzalloc(sizeof(struct kempld_watchdog_stage),
+					GFP_KERNEL);
+			if (stage == NULL) {
+				ret = -ENOMEM;
+				goto err_alloc_stages;
+			}
+			stage->num = i;
+			stage->timeout_mask = ~timeout_mask;
+			wdt->stage[i] = stage;
+			wdt->stages++;
+
+			/* assign available stages to timeout and pretimeout */
+			if (wdt->timeout_stage == NULL) {
+				wdt->timeout_stage = stage;
+			} else if ((wdt->pretimeout_stage == NULL) &&
+				(pld->feature_mask & KEMPLD_FEATURE_BIT_NMI)) {
+				wdt->pretimeout_stage = wdt->timeout_stage;
+				wdt->timeout_stage = stage;
+			}
+		} else
+			wdt->stage[i] = NULL;
+	}
+
+	return 0;
+
+err_alloc_stages:
+	kempld_wdt_release_stages(wdt);
+
+	return ret;
+}
+
+static const struct file_operations kempld_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= kempld_wdt_write,
+	.unlocked_ioctl	= kempld_wdt_ioctl,
+	.open		= kempld_wdt_open,
+	.release	= kempld_wdt_release,
+};
+
+static struct miscdevice kempld_wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &kempld_wdt_fops,
+};
+
+static int kempld_wdt_probe(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt;
+	struct kempld_device_data *pld;
+	u8 status;
+	int ret;
+
+	if (kempld_wdt != NULL) {
+		dev_err(&pdev->dev,
+			"unable to support more than one watchdog devices\n");
+		return -EMFILE;
+	}
+
+	wdt = kzalloc(sizeof(struct kempld_watchdog_data), GFP_KERNEL);
+	if (wdt == NULL) {
+		dev_err(&pdev->dev, "unable to get memory for device data\n");
+		ret = -ENOMEM;
+		goto err_alloc_dev_data;
+	}
+
+	pld = dev_get_drvdata(pdev->dev.parent);
+	wdt->pld = pld;
+
+	platform_set_drvdata(pdev, wdt);
+
+	strncpy(wdt->ident.identity, "KEMPLD Watchdog",
+		sizeof(wdt->ident.identity));
+
+	/* watchdog firmware version is identical to the CPLD version */
+	wdt->ident.firmware_version = (pld->info.major<<24)
+		| (pld->info.minor<<16) | pld->info.buildnr;
+
+	/* probe how many usable stages we have */
+	ret = kempld_wdt_probe_stages(wdt);
+	if (ret)
+		goto err_probe_stages;
+
+	/* get initial watchdog status */
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	status = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(wdt->pld);
+
+	/* check if the watchdog is already locked and enable the nowayout
+	 * option in that case */
+	if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
+			KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
+		if (!nowayout)
+			dev_warn(wdt->pld->dev,
+				 "Forcing nowayout - watchdog lock enabled!\n");
+		nowayout = 1;
+	}
+
+	/* set default values for the case we start the watchdog or change
+	 * the configuration */
+	wdt->timeout = WATCHDOG_DEFAULT_TIMEOUT;
+	wdt->pretimeout = WATCHDOG_DEFAULT_PRETIMEOUT;
+
+	/* check if watchdog is enabled */
+	if (status & KEMPLD_WDT_CFG_ENABLE) {
+		/* Get current watchdog settings */
+		kempld_wdt_update_timeouts(wdt);
+
+		dev_info(wdt->pld->dev, "Watchdog is already enabled:\n"
+			 "%d s timeout and %d s pretimeout!\n",
+			 wdt->timeout, wdt->pretimeout);
+	}
+
+	/* update the timeout settings if requested by module parameters */
+	if (timeout > 0)
+		wdt->timeout = timeout;
+	if (pretimeout >= 0)
+		wdt->pretimeout = pretimeout;
+
+	dev_info(wdt->pld->dev, "watchdog will be set on (re)start\n"
+		 "new settings: %d s timeout and %d s pretimeout\n",
+		 wdt->timeout, wdt->pretimeout);
+
+	wdt->ident.options = WDIOF_KEEPALIVEPING;
+	if ((wdt->timeout_stage) && !(status & KEMPLD_WDT_CFG_GLOBAL_LOCK))
+		wdt->ident.options |= WDIOF_SETTIMEOUT;
+	if (wdt->pretimeout_stage)
+		wdt->ident.options |= WDIOF_PRETIMEOUT;
+	if (!nowayout)
+		wdt->ident.options |= WDIOF_MAGICCLOSE;
+
+	kempld_wdt = wdt;
+
+	ret = misc_register(&kempld_wdt_miscdev);
+	if (ret)
+		goto err_misc_register;
+
+	dev_info(wdt->pld->dev,
+		 "%d stage watchdog initialized, pretimeout %ssupported\n",
+		 wdt->stages, wdt->pretimeout_stage ? "" : "not ");
+
+	return 0;
+
+err_probe_stages:
+err_misc_register:
+	kfree(kempld_wdt);
+	kempld_wdt = NULL;
+err_alloc_dev_data:
+	return ret;
+}
+
+static void kempld_wdt_shutdown(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	if (wdt != NULL) {
+		if (!nowayout)
+			kempld_wdt_stop(wdt);
+		else
+			kempld_wdt_keepalive(wdt);
+	}
+}
+
+static int kempld_wdt_remove(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+
+	BUG_ON(wdt != kempld_wdt);
+
+	/* stop or at least keepalive the watchdog before we leave */
+	kempld_wdt_shutdown(pdev);
+
+	misc_deregister(&kempld_wdt_miscdev);
+
+	kempld_wdt_release_stages(wdt);
+
+	kfree(wdt);
+	kempld_wdt = NULL;
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int wdt_pm_status_store;
+
+/* Disable watchdog if it is active during suspend */
+static int kempld_wdt_suspend(struct platform_device *pdev,
+				pm_message_t message)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+	struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+
+	kempld_get_mutex_set_index(pld, KEMPLD_WDT_CFG);
+	wdt_pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
+	kempld_release_mutex(pld);
+
+	kempld_wdt_update_timeouts(wdt);
+
+	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+		kempld_wdt_shutdown(pdev);
+
+	return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int kempld_wdt_resume(struct platform_device *pdev)
+{
+	struct kempld_watchdog_data *wdt = platform_get_drvdata(pdev);
+	int ret;
+
+	/* if watchdog was stopped before suspend be sure it gets disabled
+	 * again, for the case BIOS has enabled it during resume */
+	if (wdt_pm_status_store & KEMPLD_WDT_CFG_ENABLE) {
+		ret = kempld_wdt_settimeout(wdt);
+		if (ret)
+			goto err_enable_wdt;
+		ret = kempld_wdt_start(wdt);
+		if (ret)
+			goto err_enable_wdt;
+
+		dev_info(wdt->pld->dev, "Resuming watchdog operation:\n"
+			 "%d s timeout and %d s pretimeout\n", wdt->timeout,
+			 wdt->pretimeout);
+	} else
+		kempld_wdt_shutdown(pdev);
+
+	return 0;
+
+err_enable_wdt:
+	dev_err(wdt->pld->dev,
+		"Failed to reenable the watchdog timer after resume!\n");
+
+	return ret;
+}
+#else
+#define kempld_wdt_suspend	NULL
+#define kempld_wdt_resume	NULL
+#endif
+
+static struct platform_driver kempld_wdt_driver = {
+	.driver = {
+		.name = "kempld-wdt",
+		.owner = THIS_MODULE,
+	},
+	.probe = kempld_wdt_probe,
+	.remove = kempld_wdt_remove,
+	.shutdown = kempld_wdt_shutdown,
+	.suspend = kempld_wdt_suspend,
+	.resume = kempld_wdt_resume,
+};
+
+static int __init kempld_wdt_init(void)
+{
+	return platform_driver_register(&kempld_wdt_driver);
+}
+
+static void __exit kempld_wdt_exit(void)
+{
+	platform_driver_unregister(&kempld_wdt_driver);
+}
+
+module_init(kempld_wdt_init);
+module_exit(kempld_wdt_exit);
+
+MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/watchdog/kempld_wdt.h b/drivers/watchdog/kempld_wdt.h
new file mode 100644
index 0000000..80f68f6
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.h
@@ -0,0 +1,75 @@ 
+/*
+ *  kempld_wdt.h - Kontron PLD watchdog driver definitions
+ *
+ *  Copyright (c) 2010-2012 Kontron Europe GmbH
+ *  Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License 2 as published
+ *  by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _KEMPLD_WDT_H_
+#define _KEMPLD_WDT_H_
+
+/* watchdog register definitions */
+#define KEMPLD_WDT_KICK			0x16
+#define KEMPLD_WDT_CFG			0x17
+#define		KEMPLD_WDT_CFG_STAGE_TIMEOUT_OCCURED(x)	(1<<x)
+#define		KEMPLD_WDT_CFG_ENABLE_LOCK		0x8
+#define		KEMPLD_WDT_CFG_ENABLE			0x10
+#define		KEMPLD_WDT_CFG_AUTO_RELOAD		0x40
+#define		KEMPLD_WDT_CFG_GLOBAL_LOCK		0x80
+#define KEMPLD_WDT_STAGE_CFG(x)		(0x18+x)
+#define		KEMPLD_WDT_STAGE_CFG_ACTION_MASK	0x7
+#define		KEMPLD_WDT_STAGE_CFG_GET_ACTION(x)	(x & 0x7)
+#define		KEMPLD_WDT_STAGE_CFG_ASSERT		(1<<3)
+#define		KEMPLD_WDT_STAGE_CFG_PRESCALER_MASK	0x30
+#define		KEMPLD_WDT_STAGE_CFG_GET_PRESCALER(x)	((x & 0x30)>>4)
+#define		KEMPLD_WDT_STAGE_CFG_SET_PRESCALER(x)	((x & 0x30)<<4)
+#define KEMPLD_WDT_STAGE_TIMEOUT(x)	(0x1b+x*4)
+#define KEMPLD_WDT_MAX_STAGES		3
+
+#define	KEMPLD_WDT_ACTION_NONE		0x0
+#define	KEMPLD_WDT_ACTION_RESET		0x1
+#define	KEMPLD_WDT_ACTION_NMI		0x2
+#define	KEMPLD_WDT_ACTION_SMI		0x3
+#define	KEMPLD_WDT_ACTION_SCI		0x4
+#define	KEMPLD_WDT_ACTION_DELAY		0x5
+
+#define	KEMPLD_WDT_PRESCALER_21BIT	0x0
+#define	KEMPLD_WDT_PRESCALER_17BIT	0x1
+#define	KEMPLD_WDT_PRESCALER_12BIT	0x2
+
+const int kempld_prescaler_bits[] = { 21, 17, 12 };
+#define KEMPLD_PRESCALER(x)	(0xffffffff>>(32-kempld_prescaler_bits[x]))
+
+
+struct kempld_watchdog_stage {
+	int	num;
+	u32	timeout_mask;
+};
+
+struct kempld_watchdog_data {
+	int				timeout;
+	int				pretimeout;
+	unsigned long			is_open;
+	unsigned long			expect_close;
+	int				stages;
+	struct kempld_watchdog_stage	*timeout_stage;
+	struct kempld_watchdog_stage	*pretimeout_stage;
+	struct kempld_device_data	*pld;
+	struct watchdog_info		ident;
+	struct kempld_watchdog_stage	*stage[KEMPLD_WDT_MAX_STAGES];
+};
+
+#endif /* _KEMPLD_WDT_H_ */