lkml.org 
[lkml]   [2013]   [Apr]   [8]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 4/4] watchdog: Kontron PLD watchdog timer
    Date
    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

    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


    \
     
     \ /
      Last update: 2013-04-08 21:48    [W:3.629 / U:0.008 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site