mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-25 02:48:21 +00:00
487722cf2d
I just can't find any value in MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) and MODULE_ALIAS_MISCDEV(TEMP_MINOR) statements. Either the device is enumerated and the driver already has a module alias (e.g. PCI, USB etc.) that will get the right driver loaded automatically. Or the device is not enumerated and loading its driver will lead to more or less intrusive hardware poking. Such hardware poking should be limited to a bare minimum, so the user should really decide which drivers should be tried and in what order. Trying them all in arbitrary order can't do any good. On top of that, loading that many drivers at once bloats the kernel log. Also many drivers will stay loaded afterward, bloating the output of "lsmod" and wasting memory. Some modules (cs5535_mfgpt which gets loaded as a dependency) can't even be unloaded! If defining char-major-10-130 is needed then it should happen in user-space. Signed-off-by: Jean Delvare <jdelvare@suse.de> Acked-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be> Cc: Stephen Warren <swarren@wwwdotorg.org> Cc: Mike Frysinger <vapier.adi@gmail.com> Cc: Wan ZongShun <mcuos.com@gmail.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Zwane Mwaikambo <zwane@arm.linux.org.uk> Cc: Jim Cromie <jim.cromie@gmail.com>
581 lines
14 KiB
C
581 lines
14 KiB
C
/*
|
|
* Kontron PLD watchdog driver
|
|
*
|
|
* Copyright (c) 2010-2013 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* Kernel/API: P-----| pretimeout
|
|
* |-----------------------T timeout
|
|
* Watchdog: |-----------------P pretimeout_stage
|
|
* |-----T timeout_stage
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mfd/kempld.h>
|
|
|
|
#define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)
|
|
#define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))
|
|
#define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)
|
|
#define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)
|
|
#define STAGE_CFG_PRESCALER_MASK 0x30
|
|
#define STAGE_CFG_ACTION_MASK 0x7
|
|
#define STAGE_CFG_ASSERT (1 << 3)
|
|
|
|
#define KEMPLD_WDT_MAX_STAGES 2
|
|
#define KEMPLD_WDT_KICK 0x16
|
|
#define KEMPLD_WDT_CFG 0x17
|
|
#define KEMPLD_WDT_CFG_ENABLE 0x10
|
|
#define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8
|
|
#define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80
|
|
|
|
enum {
|
|
ACTION_NONE = 0,
|
|
ACTION_RESET,
|
|
ACTION_NMI,
|
|
ACTION_SMI,
|
|
ACTION_SCI,
|
|
ACTION_DELAY,
|
|
};
|
|
|
|
enum {
|
|
STAGE_TIMEOUT = 0,
|
|
STAGE_PRETIMEOUT,
|
|
};
|
|
|
|
enum {
|
|
PRESCALER_21 = 0,
|
|
PRESCALER_17,
|
|
PRESCALER_12,
|
|
};
|
|
|
|
static const u32 kempld_prescaler[] = {
|
|
[PRESCALER_21] = (1 << 21) - 1,
|
|
[PRESCALER_17] = (1 << 17) - 1,
|
|
[PRESCALER_12] = (1 << 12) - 1,
|
|
0,
|
|
};
|
|
|
|
struct kempld_wdt_stage {
|
|
unsigned int id;
|
|
u32 mask;
|
|
};
|
|
|
|
struct kempld_wdt_data {
|
|
struct kempld_device_data *pld;
|
|
struct watchdog_device wdd;
|
|
unsigned int pretimeout;
|
|
struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES];
|
|
#ifdef CONFIG_PM
|
|
u8 pm_status_store;
|
|
#endif
|
|
};
|
|
|
|
#define DEFAULT_TIMEOUT 30 /* seconds */
|
|
#define DEFAULT_PRETIMEOUT 0
|
|
|
|
static unsigned int timeout = DEFAULT_TIMEOUT;
|
|
module_param(timeout, uint, 0);
|
|
MODULE_PARM_DESC(timeout,
|
|
"Watchdog timeout in seconds. (>=0, default="
|
|
__MODULE_STRING(DEFAULT_TIMEOUT) ")");
|
|
|
|
static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
|
|
module_param(pretimeout, uint, 0);
|
|
MODULE_PARM_DESC(pretimeout,
|
|
"Watchdog pretimeout in seconds. (>=0, default="
|
|
__MODULE_STRING(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 int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
|
|
struct kempld_wdt_stage *stage,
|
|
u8 action)
|
|
{
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
u8 stage_cfg;
|
|
|
|
if (!stage || !stage->mask)
|
|
return -EINVAL;
|
|
|
|
kempld_get_mutex(pld);
|
|
stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
|
|
stage_cfg &= ~STAGE_CFG_ACTION_MASK;
|
|
stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
|
|
|
|
if (action == ACTION_RESET)
|
|
stage_cfg |= STAGE_CFG_ASSERT;
|
|
else
|
|
stage_cfg &= ~STAGE_CFG_ASSERT;
|
|
|
|
kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
|
|
kempld_release_mutex(pld);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
|
|
struct kempld_wdt_stage *stage,
|
|
unsigned int timeout)
|
|
{
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
u32 prescaler = kempld_prescaler[PRESCALER_21];
|
|
u64 stage_timeout64;
|
|
u32 stage_timeout;
|
|
u32 remainder;
|
|
u8 stage_cfg;
|
|
|
|
if (!stage)
|
|
return -EINVAL;
|
|
|
|
stage_timeout64 = (u64)timeout * pld->pld_clock;
|
|
remainder = do_div(stage_timeout64, prescaler);
|
|
if (remainder)
|
|
stage_timeout64++;
|
|
|
|
if (stage_timeout64 > stage->mask)
|
|
return -EINVAL;
|
|
|
|
stage_timeout = stage_timeout64 & stage->mask;
|
|
|
|
kempld_get_mutex(pld);
|
|
stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
|
|
stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
|
|
stage_cfg |= STAGE_CFG_SET_PRESCALER(prescaler);
|
|
kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
|
|
kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
|
|
stage_timeout);
|
|
kempld_release_mutex(pld);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* kempld_get_mutex must be called prior to calling this function.
|
|
*/
|
|
static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
|
|
struct kempld_wdt_stage *stage)
|
|
{
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
unsigned int timeout;
|
|
u64 stage_timeout;
|
|
u32 prescaler;
|
|
u32 remainder;
|
|
u8 stage_cfg;
|
|
|
|
if (!stage->mask)
|
|
return 0;
|
|
|
|
stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
|
|
stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
|
|
prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
|
|
|
|
stage_timeout = (stage_timeout & stage->mask) * prescaler;
|
|
remainder = do_div(stage_timeout, pld->pld_clock);
|
|
if (remainder)
|
|
stage_timeout++;
|
|
|
|
timeout = stage_timeout;
|
|
WARN_ON_ONCE(timeout != stage_timeout);
|
|
|
|
return timeout;
|
|
}
|
|
|
|
static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
|
|
unsigned int timeout)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_wdt_stage *pretimeout_stage;
|
|
struct kempld_wdt_stage *timeout_stage;
|
|
int ret;
|
|
|
|
timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
|
|
pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
|
|
|
|
if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
|
|
timeout = wdt_data->pretimeout;
|
|
|
|
ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
|
|
ACTION_RESET);
|
|
if (ret)
|
|
return ret;
|
|
ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
|
|
timeout);
|
|
if (ret)
|
|
return ret;
|
|
|
|
wdd->timeout = timeout;
|
|
return 0;
|
|
}
|
|
|
|
static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
|
|
unsigned int pretimeout)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_wdt_stage *pretimeout_stage;
|
|
u8 action = ACTION_NONE;
|
|
int ret;
|
|
|
|
pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
|
|
|
|
if (!pretimeout_stage->mask)
|
|
return -ENXIO;
|
|
|
|
if (pretimeout > wdd->timeout)
|
|
return -EINVAL;
|
|
|
|
if (pretimeout > 0)
|
|
action = ACTION_NMI;
|
|
|
|
ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
|
|
action);
|
|
if (ret)
|
|
return ret;
|
|
ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
|
|
wdd->timeout - pretimeout);
|
|
if (ret)
|
|
return ret;
|
|
|
|
wdt_data->pretimeout = pretimeout;
|
|
return 0;
|
|
}
|
|
|
|
static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
|
|
{
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
struct kempld_wdt_stage *pretimeout_stage;
|
|
struct kempld_wdt_stage *timeout_stage;
|
|
unsigned int pretimeout, timeout;
|
|
|
|
pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
|
|
timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
|
|
|
|
kempld_get_mutex(pld);
|
|
pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
|
|
timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
|
|
kempld_release_mutex(pld);
|
|
|
|
if (pretimeout)
|
|
wdt_data->pretimeout = timeout;
|
|
else
|
|
wdt_data->pretimeout = 0;
|
|
|
|
wdt_data->wdd.timeout = pretimeout + timeout;
|
|
}
|
|
|
|
static int kempld_wdt_start(struct watchdog_device *wdd)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
u8 status;
|
|
int ret;
|
|
|
|
ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
|
|
if (ret)
|
|
return ret;
|
|
|
|
kempld_get_mutex(pld);
|
|
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 watchdog_device *wdd)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
u8 status;
|
|
|
|
kempld_get_mutex(pld);
|
|
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 watchdog_device *wdd)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
|
|
kempld_get_mutex(pld);
|
|
kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
|
|
kempld_release_mutex(pld);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
void __user *argp = (void __user *)arg;
|
|
int ret = -ENOIOCTLCMD;
|
|
int __user *p = argp;
|
|
int new_value;
|
|
|
|
switch (cmd) {
|
|
case WDIOC_SETPRETIMEOUT:
|
|
if (get_user(new_value, p))
|
|
return -EFAULT;
|
|
ret = kempld_wdt_set_pretimeout(wdd, new_value);
|
|
if (ret)
|
|
return ret;
|
|
ret = kempld_wdt_keepalive(wdd);
|
|
break;
|
|
case WDIOC_GETPRETIMEOUT:
|
|
ret = put_user(wdt_data->pretimeout, (int __user *)arg);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
struct kempld_wdt_stage *pretimeout_stage;
|
|
struct kempld_wdt_stage *timeout_stage;
|
|
u8 index, data, data_orig;
|
|
u32 mask;
|
|
int i, j;
|
|
|
|
pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
|
|
timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
|
|
|
|
pretimeout_stage->mask = 0;
|
|
timeout_stage->mask = 0;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
index = KEMPLD_WDT_STAGE_TIMEOUT(i);
|
|
mask = 0;
|
|
|
|
kempld_get_mutex(pld);
|
|
/* Probe each byte individually. */
|
|
for (j = 0; j < 4; j++) {
|
|
data_orig = kempld_read8(pld, index + j);
|
|
kempld_write8(pld, index + j, 0x00);
|
|
data = kempld_read8(pld, index + j);
|
|
/* A failed write means this byte is reserved */
|
|
if (data != 0x00)
|
|
break;
|
|
kempld_write8(pld, index + j, data_orig);
|
|
mask |= 0xff << (j * 8);
|
|
}
|
|
kempld_release_mutex(pld);
|
|
|
|
/* Assign available stages to timeout and pretimeout */
|
|
if (!timeout_stage->mask) {
|
|
timeout_stage->mask = mask;
|
|
timeout_stage->id = i;
|
|
} else {
|
|
if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
|
|
pretimeout_stage->mask = timeout_stage->mask;
|
|
timeout_stage->mask = mask;
|
|
pretimeout_stage->id = timeout_stage->id;
|
|
timeout_stage->id = i;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!timeout_stage->mask)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct watchdog_info kempld_wdt_info = {
|
|
.identity = "KEMPLD Watchdog",
|
|
.options = WDIOF_SETTIMEOUT |
|
|
WDIOF_KEEPALIVEPING |
|
|
WDIOF_MAGICCLOSE |
|
|
WDIOF_PRETIMEOUT
|
|
};
|
|
|
|
static struct watchdog_ops kempld_wdt_ops = {
|
|
.owner = THIS_MODULE,
|
|
.start = kempld_wdt_start,
|
|
.stop = kempld_wdt_stop,
|
|
.ping = kempld_wdt_keepalive,
|
|
.set_timeout = kempld_wdt_set_timeout,
|
|
.ioctl = kempld_wdt_ioctl,
|
|
};
|
|
|
|
static int kempld_wdt_probe(struct platform_device *pdev)
|
|
{
|
|
struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
|
|
struct kempld_wdt_data *wdt_data;
|
|
struct device *dev = &pdev->dev;
|
|
struct watchdog_device *wdd;
|
|
u8 status;
|
|
int ret = 0;
|
|
|
|
wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
|
|
if (!wdt_data)
|
|
return -ENOMEM;
|
|
|
|
wdt_data->pld = pld;
|
|
wdd = &wdt_data->wdd;
|
|
wdd->parent = dev;
|
|
|
|
kempld_get_mutex(pld);
|
|
status = kempld_read8(pld, KEMPLD_WDT_CFG);
|
|
kempld_release_mutex(pld);
|
|
|
|
/* Enable nowayout if watchdog is already locked */
|
|
if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
|
|
KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
|
|
if (!nowayout)
|
|
dev_warn(dev,
|
|
"Forcing nowayout - watchdog lock enabled!\n");
|
|
nowayout = true;
|
|
}
|
|
|
|
wdd->info = &kempld_wdt_info;
|
|
wdd->ops = &kempld_wdt_ops;
|
|
|
|
watchdog_set_drvdata(wdd, wdt_data);
|
|
watchdog_set_nowayout(wdd, nowayout);
|
|
|
|
ret = kempld_wdt_probe_stages(wdd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
kempld_wdt_set_timeout(wdd, timeout);
|
|
kempld_wdt_set_pretimeout(wdd, pretimeout);
|
|
|
|
/* Check if watchdog is already enabled */
|
|
if (status & KEMPLD_WDT_CFG_ENABLE) {
|
|
/* Get current watchdog settings */
|
|
kempld_wdt_update_timeouts(wdt_data);
|
|
dev_info(dev, "Watchdog was already enabled\n");
|
|
}
|
|
|
|
platform_set_drvdata(pdev, wdt_data);
|
|
ret = watchdog_register_device(wdd);
|
|
if (ret)
|
|
return ret;
|
|
|
|
dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void kempld_wdt_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
|
|
|
|
kempld_wdt_stop(&wdt_data->wdd);
|
|
}
|
|
|
|
static int kempld_wdt_remove(struct platform_device *pdev)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
|
|
struct watchdog_device *wdd = &wdt_data->wdd;
|
|
int ret = 0;
|
|
|
|
if (!nowayout)
|
|
ret = kempld_wdt_stop(wdd);
|
|
watchdog_unregister_device(wdd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
/* Disable watchdog if it is active during suspend */
|
|
static int kempld_wdt_suspend(struct platform_device *pdev,
|
|
pm_message_t message)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
|
|
struct kempld_device_data *pld = wdt_data->pld;
|
|
struct watchdog_device *wdd = &wdt_data->wdd;
|
|
|
|
kempld_get_mutex(pld);
|
|
wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
|
|
kempld_release_mutex(pld);
|
|
|
|
kempld_wdt_update_timeouts(wdt_data);
|
|
|
|
if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
|
|
return kempld_wdt_stop(wdd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enable watchdog and configure it if necessary */
|
|
static int kempld_wdt_resume(struct platform_device *pdev)
|
|
{
|
|
struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
|
|
struct watchdog_device *wdd = &wdt_data->wdd;
|
|
|
|
/*
|
|
* If watchdog was stopped before suspend be sure it gets disabled
|
|
* again, for the case BIOS has enabled it during resume
|
|
*/
|
|
if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
|
|
return kempld_wdt_start(wdd);
|
|
else
|
|
return kempld_wdt_stop(wdd);
|
|
}
|
|
#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,
|
|
};
|
|
|
|
module_platform_driver(kempld_wdt_driver);
|
|
|
|
MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
|
|
MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
|
|
MODULE_LICENSE("GPL");
|