Commit 4fe19a13 authored by Linus Torvalds's avatar Linus Torvalds

Merge git://www.linux-watchdog.org/linux-watchdog

Pull watchdog updates from Wim Van Sebroeck:
 "This includes some fixes and code improvements (like
  clk_prepare_enable and clk_disable_unprepare), conversion from the
  omap_wdt and twl4030_wdt drivers to the watchdog framework, addition
  of the SB8x0 chipset support and the DA9055 Watchdog driver and some
  OF support for the davinci_wdt driver."

* git://www.linux-watchdog.org/linux-watchdog: (22 commits)
  watchdog: mei: avoid oops in watchdog unregister code path
  watchdog: Orion: Fix possible null-deference in orion_wdt_probe
  watchdog: sp5100_tco: Add SB8x0 chipset support
  watchdog: davinci_wdt: add OF support
  watchdog: da9052: Fix invalid free of devm_ allocated data
  watchdog: twl4030_wdt: Change TWL4030_MODULE_PM_RECEIVER to TWL_MODULE_PM_RECEIVER
  watchdog: remove depends on CONFIG_EXPERIMENTAL
  watchdog: Convert dev_printk(KERN_<LEVEL> to dev_<level>(
  watchdog: DA9055 Watchdog driver
  watchdog: omap_wdt: eliminate goto
  watchdog: omap_wdt: delete redundant platform_set_drvdata() calls
  watchdog: omap_wdt: convert to devm_ functions
  watchdog: omap_wdt: convert to new watchdog core
  watchdog: WatchDog Timer Driver Core: fix comment
  watchdog: s3c2410_wdt: use clk_prepare_enable and clk_disable_unprepare
  watchdog: imx2_wdt: Select the driver via ARCH_MXC
  watchdog: cpu5wdt.c: add missing del_timer call
  watchdog: hpwdt.c: Increase version string
  watchdog: Convert twl4030_wdt to watchdog core
  davinci_wdt: preparation for switch to common clock framework
  ...
parents 769cb858 d6921700
DaVinci Watchdog Timer (WDT) Controller
Required properties:
- compatible : Should be "ti,davinci-wdt"
- reg : Should contain WDT registers location and length
Examples:
wdt: wdt@2320000 {
compatible = "ti,davinci-wdt";
reg = <0x02320000 0x80>;
};
...@@ -370,7 +370,7 @@ void mei_watchdog_register(struct mei_device *dev) ...@@ -370,7 +370,7 @@ void mei_watchdog_register(struct mei_device *dev)
void mei_watchdog_unregister(struct mei_device *dev) void mei_watchdog_unregister(struct mei_device *dev)
{ {
if (test_bit(WDOG_UNREGISTERED, &amt_wd_dev.status)) if (watchdog_get_drvdata(&amt_wd_dev) == NULL)
return; return;
watchdog_set_drvdata(&amt_wd_dev, NULL); watchdog_set_drvdata(&amt_wd_dev, NULL);
......
...@@ -76,6 +76,16 @@ config DA9052_WATCHDOG ...@@ -76,6 +76,16 @@ config DA9052_WATCHDOG
Alternatively say M to compile the driver as a module, Alternatively say M to compile the driver as a module,
which will be called da9052_wdt. which will be called da9052_wdt.
config DA9055_WATCHDOG
tristate "Dialog Semiconductor DA9055 Watchdog"
depends on MFD_DA9055
help
If you say yes here you get support for watchdog on the Dialog
Semiconductor DA9055 PMIC.
This driver can also be built as a module. If so, the module
will be called da9055_wdt.
config WM831X_WATCHDOG config WM831X_WATCHDOG
tristate "WM831x watchdog" tristate "WM831x watchdog"
depends on MFD_WM831X depends on MFD_WM831X
...@@ -232,6 +242,7 @@ config EP93XX_WATCHDOG ...@@ -232,6 +242,7 @@ config EP93XX_WATCHDOG
config OMAP_WATCHDOG config OMAP_WATCHDOG
tristate "OMAP Watchdog" tristate "OMAP Watchdog"
depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS
select WATCHDOG_CORE
help help
Support for TI OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog. Say 'Y' Support for TI OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog. Say 'Y'
here to enable the OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog timer. here to enable the OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog timer.
...@@ -300,6 +311,7 @@ config COH901327_WATCHDOG ...@@ -300,6 +311,7 @@ config COH901327_WATCHDOG
config TWL4030_WATCHDOG config TWL4030_WATCHDOG
tristate "TWL4030 Watchdog" tristate "TWL4030 Watchdog"
depends on TWL4030_CORE depends on TWL4030_CORE
select WATCHDOG_CORE
help help
Support for TI TWL4030 watchdog. Say 'Y' here to enable the Support for TI TWL4030 watchdog. Say 'Y' here to enable the
watchdog timer support for TWL4030 chips. watchdog timer support for TWL4030 chips.
...@@ -342,7 +354,7 @@ config MAX63XX_WATCHDOG ...@@ -342,7 +354,7 @@ config MAX63XX_WATCHDOG
config IMX2_WDT config IMX2_WDT
tristate "IMX2+ Watchdog" tristate "IMX2+ Watchdog"
depends on IMX_HAVE_PLATFORM_IMX2_WDT depends on ARCH_MXC
help help
This is the driver for the hardware watchdog This is the driver for the hardware watchdog
on the Freescale IMX2 and later processors. on the Freescale IMX2 and later processors.
...@@ -431,7 +443,7 @@ config ALIM7101_WDT ...@@ -431,7 +443,7 @@ config ALIM7101_WDT
config F71808E_WDT config F71808E_WDT
tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog"
depends on X86 && EXPERIMENTAL depends on X86
help help
This is the driver for the hardware watchdog on the Fintek This is the driver for the hardware watchdog on the Fintek
F71808E, F71862FG, F71869, F71882FG and F71889FG Super I/O controllers. F71808E, F71862FG, F71869, F71882FG and F71889FG Super I/O controllers.
...@@ -622,7 +634,7 @@ config IT8712F_WDT ...@@ -622,7 +634,7 @@ config IT8712F_WDT
config IT87_WDT config IT87_WDT
tristate "IT87 Watchdog Timer" tristate "IT87 Watchdog Timer"
depends on X86 && EXPERIMENTAL depends on X86
---help--- ---help---
This is the driver for the hardware watchdog on the ITE IT8702, This is the driver for the hardware watchdog on the ITE IT8702,
IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 and IT8728 IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 and IT8728
......
...@@ -164,6 +164,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o ...@@ -164,6 +164,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
# Architecture Independent # Architecture Independent
obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
......
...@@ -284,6 +284,7 @@ static void ath97_wdt_shutdown(struct platform_device *pdev) ...@@ -284,6 +284,7 @@ static void ath97_wdt_shutdown(struct platform_device *pdev)
} }
static struct platform_driver ath79_wdt_driver = { static struct platform_driver ath79_wdt_driver = {
.probe = ath79_wdt_probe,
.remove = ath79_wdt_remove, .remove = ath79_wdt_remove,
.shutdown = ath97_wdt_shutdown, .shutdown = ath97_wdt_shutdown,
.driver = { .driver = {
...@@ -292,17 +293,7 @@ static struct platform_driver ath79_wdt_driver = { ...@@ -292,17 +293,7 @@ static struct platform_driver ath79_wdt_driver = {
}, },
}; };
static int __init ath79_wdt_init(void) module_platform_driver(ath79_wdt_driver);
{
return platform_driver_probe(&ath79_wdt_driver, ath79_wdt_probe);
}
module_init(ath79_wdt_init);
static void __exit ath79_wdt_exit(void)
{
platform_driver_unregister(&ath79_wdt_driver);
}
module_exit(ath79_wdt_exit);
MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver"); MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver");
MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org"); MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
......
...@@ -266,6 +266,7 @@ static void cpu5wdt_exit(void) ...@@ -266,6 +266,7 @@ static void cpu5wdt_exit(void)
if (cpu5wdt_device.queue) { if (cpu5wdt_device.queue) {
cpu5wdt_device.queue = 0; cpu5wdt_device.queue = 0;
wait_for_completion(&cpu5wdt_device.stop); wait_for_completion(&cpu5wdt_device.stop);
del_timer(&cpu5wdt_device.timer);
} }
misc_deregister(&cpu5wdt_misc); misc_deregister(&cpu5wdt_misc);
......
...@@ -53,10 +53,6 @@ static const struct { ...@@ -53,10 +53,6 @@ static const struct {
static void da9052_wdt_release_resources(struct kref *r) static void da9052_wdt_release_resources(struct kref *r)
{ {
struct da9052_wdt_data *driver_data =
container_of(r, struct da9052_wdt_data, kref);
kfree(driver_data);
} }
static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev, static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev,
......
/*
* System monitoring driver for DA9055 PMICs.
*
* Copyright(c) 2012 Dialog Semiconductor Ltd.
*
* Author: David Dajun Chen <dchen@diasemi.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/watchdog.h>
#include <linux/delay.h>
#include <linux/mfd/da9055/core.h>
#include <linux/mfd/da9055/reg.h>
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) ")");
#define DA9055_DEF_TIMEOUT 4
#define DA9055_TWDMIN 256
struct da9055_wdt_data {
struct watchdog_device wdt;
struct da9055 *da9055;
struct kref kref;
};
static const struct {
u8 reg_val;
int user_time; /* In seconds */
} da9055_wdt_maps[] = {
{ 0, 0 },
{ 1, 2 },
{ 2, 4 },
{ 3, 8 },
{ 4, 16 },
{ 5, 32 },
{ 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */
{ 6, 65 },
{ 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */
{ 7, 131 },
};
static int da9055_wdt_set_timeout(struct watchdog_device *wdt_dev,
unsigned int timeout)
{
struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
struct da9055 *da9055 = driver_data->da9055;
int ret, i;
for (i = 0; i < ARRAY_SIZE(da9055_wdt_maps); i++)
if (da9055_wdt_maps[i].user_time == timeout)
break;
if (i == ARRAY_SIZE(da9055_wdt_maps))
ret = -EINVAL;
else
ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_B,
DA9055_TWDSCALE_MASK,
da9055_wdt_maps[i].reg_val <<
DA9055_TWDSCALE_SHIFT);
if (ret < 0)
dev_err(da9055->dev,
"Failed to update timescale bit, %d\n", ret);
wdt_dev->timeout = timeout;
return ret;
}
static int da9055_wdt_ping(struct watchdog_device *wdt_dev)
{
struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
struct da9055 *da9055 = driver_data->da9055;
int ret;
/*
* We have a minimum time for watchdog window called TWDMIN. A write
* to the watchdog before this elapsed time will cause an error.
*/
mdelay(DA9055_TWDMIN);
/* Reset the watchdog timer */
ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_E,
DA9055_WATCHDOG_MASK, 1);
return ret;
}
static void da9055_wdt_release_resources(struct kref *r)
{
struct da9055_wdt_data *driver_data =
container_of(r, struct da9055_wdt_data, kref);
kfree(driver_data);
}
static void da9055_wdt_ref(struct watchdog_device *wdt_dev)
{
struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
kref_get(&driver_data->kref);
}
static void da9055_wdt_unref(struct watchdog_device *wdt_dev)
{
struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
kref_put(&driver_data->kref, da9055_wdt_release_resources);
}
static int da9055_wdt_start(struct watchdog_device *wdt_dev)
{
return da9055_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
}
static int da9055_wdt_stop(struct watchdog_device *wdt_dev)
{
return da9055_wdt_set_timeout(wdt_dev, 0);
}
static struct watchdog_info da9055_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "DA9055 Watchdog",
};
static const struct watchdog_ops da9055_wdt_ops = {
.owner = THIS_MODULE,
.start = da9055_wdt_start,
.stop = da9055_wdt_stop,
.ping = da9055_wdt_ping,
.set_timeout = da9055_wdt_set_timeout,
.ref = da9055_wdt_ref,
.unref = da9055_wdt_unref,
};
static int da9055_wdt_probe(struct platform_device *pdev)
{
struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent);
struct da9055_wdt_data *driver_data;
struct watchdog_device *da9055_wdt;
int ret;
driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data),
GFP_KERNEL);
if (!driver_data) {
dev_err(da9055->dev, "Failed to allocate watchdog device\n");
return -ENOMEM;
}
driver_data->da9055 = da9055;
da9055_wdt = &driver_data->wdt;
da9055_wdt->timeout = DA9055_DEF_TIMEOUT;
da9055_wdt->info = &da9055_wdt_info;
da9055_wdt->ops = &da9055_wdt_ops;
watchdog_set_nowayout(da9055_wdt, nowayout);
watchdog_set_drvdata(da9055_wdt, driver_data);
kref_init(&driver_data->kref);
ret = da9055_wdt_stop(da9055_wdt);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to stop watchdog, %d\n", ret);
goto err;
}
dev_set_drvdata(&pdev->dev, driver_data);
ret = watchdog_register_device(&driver_data->wdt);
if (ret != 0)
dev_err(da9055->dev, "watchdog_register_device() failed: %d\n",
ret);
err:
return ret;
}
static int da9055_wdt_remove(struct platform_device *pdev)
{
struct da9055_wdt_data *driver_data = dev_get_drvdata(&pdev->dev);
watchdog_unregister_device(&driver_data->wdt);
kref_put(&driver_data->kref, da9055_wdt_release_resources);
return 0;
}
static struct platform_driver da9055_wdt_driver = {
.probe = da9055_wdt_probe,
.remove = da9055_wdt_remove,
.driver = {
.name = "da9055-watchdog",
},
};
module_platform_driver(da9055_wdt_driver);
MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
MODULE_DESCRIPTION("DA9055 watchdog");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da9055-watchdog");
...@@ -208,7 +208,7 @@ static int davinci_wdt_probe(struct platform_device *pdev) ...@@ -208,7 +208,7 @@ static int davinci_wdt_probe(struct platform_device *pdev)
if (WARN_ON(IS_ERR(wdt_clk))) if (WARN_ON(IS_ERR(wdt_clk)))
return PTR_ERR(wdt_clk); return PTR_ERR(wdt_clk);
clk_enable(wdt_clk); clk_prepare_enable(wdt_clk);
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
heartbeat = DEFAULT_HEARTBEAT; heartbeat = DEFAULT_HEARTBEAT;
...@@ -256,16 +256,23 @@ static int davinci_wdt_remove(struct platform_device *pdev) ...@@ -256,16 +256,23 @@ static int davinci_wdt_remove(struct platform_device *pdev)
wdt_mem = NULL; wdt_mem = NULL;
} }
clk_disable(wdt_clk); clk_disable_unprepare(wdt_clk);
clk_put(wdt_clk); clk_put(wdt_clk);
return 0; return 0;
} }
static const struct of_device_id davinci_wdt_of_match[] = {
{ .compatible = "ti,davinci-wdt", },
{},
};
MODULE_DEVICE_TABLE(of, davinci_wdt_of_match);
static struct platform_driver platform_wdt_driver = { static struct platform_driver platform_wdt_driver = {
.driver = { .driver = {
.name = "watchdog", .name = "watchdog",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.of_match_table = davinci_wdt_of_match,
}, },
.probe = davinci_wdt_probe, .probe = davinci_wdt_probe,
.remove = davinci_wdt_remove, .remove = davinci_wdt_remove,
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
#endif /* CONFIG_HPWDT_NMI_DECODING */ #endif /* CONFIG_HPWDT_NMI_DECODING */
#include <asm/nmi.h> #include <asm/nmi.h>
#define HPWDT_VERSION "1.3.0" #define HPWDT_VERSION "1.3.1"
#define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) #define SECS_TO_TICKS(secs) ((secs) * 1000 / 128)
#define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) #define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000)
#define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) #define HPWDT_MAX_TIMER TICKS_TO_SECS(65535)
......
...@@ -80,8 +80,7 @@ static irqreturn_t mpcore_wdt_fire(int irq, void *arg) ...@@ -80,8 +80,7 @@ static irqreturn_t mpcore_wdt_fire(int irq, void *arg)
/* Check it really was our interrupt */ /* Check it really was our interrupt */
if (readl(wdt->base + TWD_WDOG_INTSTAT)) { if (readl(wdt->base + TWD_WDOG_INTSTAT)) {
dev_printk(KERN_CRIT, wdt->dev, dev_crit(wdt->dev, "Triggered - Reboot ignored\n");
"Triggered - Reboot ignored.\n");
/* Clear the interrupt on the watchdog */ /* Clear the interrupt on the watchdog */
writel(1, wdt->base + TWD_WDOG_INTSTAT); writel(1, wdt->base + TWD_WDOG_INTSTAT);
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -123,7 +122,7 @@ static void mpcore_wdt_stop(struct mpcore_wdt *wdt) ...@@ -123,7 +122,7 @@ static void mpcore_wdt_stop(struct mpcore_wdt *wdt)
static void mpcore_wdt_start(struct mpcore_wdt *wdt) static void mpcore_wdt_start(struct mpcore_wdt *wdt)
{ {
dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n"); dev_info(wdt->dev, "enabling watchdog\n");
/* This loads the count register but does NOT start the count yet */ /* This loads the count register but does NOT start the count yet */
mpcore_wdt_keepalive(wdt); mpcore_wdt_keepalive(wdt);
...@@ -180,7 +179,7 @@ static int mpcore_wdt_release(struct inode *inode, struct file *file) ...@@ -180,7 +179,7 @@ static int mpcore_wdt_release(struct inode *inode, struct file *file)
if (wdt->expect_close == 42) if (wdt->expect_close == 42)
mpcore_wdt_stop(wdt); mpcore_wdt_stop(wdt);
else { else {
dev_printk(KERN_CRIT, wdt->dev, dev_crit(wdt->dev,
"unexpected close, not stopping watchdog!\n"); "unexpected close, not stopping watchdog!\n");
mpcore_wdt_keepalive(wdt); mpcore_wdt_keepalive(wdt);
} }
...@@ -351,7 +350,7 @@ static int mpcore_wdt_probe(struct platform_device *pdev) ...@@ -351,7 +350,7 @@ static int mpcore_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(wdt->dev, wdt->irq, mpcore_wdt_fire, 0, ret = devm_request_irq(wdt->dev, wdt->irq, mpcore_wdt_fire, 0,
"mpcore_wdt", wdt); "mpcore_wdt", wdt);
if (ret) { if (ret) {
dev_printk(KERN_ERR, wdt->dev, dev_err(wdt->dev,
"cannot register IRQ%d for watchdog\n", "cannot register IRQ%d for watchdog\n",
wdt->irq); wdt->irq);
return ret; return ret;
...@@ -365,7 +364,7 @@ static int mpcore_wdt_probe(struct platform_device *pdev) ...@@ -365,7 +364,7 @@ static int mpcore_wdt_probe(struct platform_device *pdev)
mpcore_wdt_miscdev.parent = &pdev->dev; mpcore_wdt_miscdev.parent = &pdev->dev;
ret = misc_register(&mpcore_wdt_miscdev); ret = misc_register(&mpcore_wdt_miscdev);
if (ret) { if (ret) {
dev_printk(KERN_ERR, wdt->dev, dev_err(wdt->dev,
"cannot register miscdev on minor=%d (err=%d)\n", "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret); WATCHDOG_MINOR, ret);
return ret; return ret;
......
...@@ -31,42 +31,34 @@ ...@@ -31,42 +31,34 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <linux/reboot.h> #include <linux/reboot.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/bitops.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/platform_data/omap-wd-timer.h> #include <linux/platform_data/omap-wd-timer.h>
#include "omap_wdt.h" #include "omap_wdt.h"
static struct platform_device *omap_wdt_dev;
static unsigned timer_margin; static unsigned timer_margin;
module_param(timer_margin, uint, 0); module_param(timer_margin, uint, 0);
MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
static unsigned int wdt_trgr_pattern = 0x1234;
static DEFINE_SPINLOCK(wdt_lock);
struct omap_wdt_dev { struct omap_wdt_dev {
void __iomem *base; /* physical */ void __iomem *base; /* physical */
struct device *dev; struct device *dev;
int omap_wdt_users; bool omap_wdt_users;
struct resource *mem; struct resource *mem;
struct miscdevice omap_wdt_miscdev; int wdt_trgr_pattern;
struct mutex lock; /* to avoid races with PM */
}; };
static void omap_wdt_ping(struct omap_wdt_dev *wdev) static void omap_wdt_reload(struct omap_wdt_dev *wdev)
{ {
void __iomem *base = wdev->base; void __iomem *base = wdev->base;
...@@ -74,8 +66,8 @@ static void omap_wdt_ping(struct omap_wdt_dev *wdev) ...@@ -74,8 +66,8 @@ static void omap_wdt_ping(struct omap_wdt_dev *wdev)
while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08)
cpu_relax(); cpu_relax();
wdt_trgr_pattern = ~wdt_trgr_pattern; wdev->wdt_trgr_pattern = ~wdev->wdt_trgr_pattern;
__raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); __raw_writel(wdev->wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR));
/* wait for posted write to complete */ /* wait for posted write to complete */
while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08)
...@@ -111,18 +103,10 @@ static void omap_wdt_disable(struct omap_wdt_dev *wdev) ...@@ -111,18 +103,10 @@ static void omap_wdt_disable(struct omap_wdt_dev *wdev)
cpu_relax(); cpu_relax();
} }
static void omap_wdt_adjust_timeout(unsigned new_timeout) static void omap_wdt_set_timer(struct omap_wdt_dev *wdev,
{ unsigned int timeout)
if (new_timeout < TIMER_MARGIN_MIN)
new_timeout = TIMER_MARGIN_DEFAULT;
if (new_timeout > TIMER_MARGIN_MAX)
new_timeout = TIMER_MARGIN_MAX;
timer_margin = new_timeout;
}
static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev)
{ {
u32 pre_margin = GET_WLDR_VAL(timer_margin); u32 pre_margin = GET_WLDR_VAL(timeout);
void __iomem *base = wdev->base; void __iomem *base = wdev->base;
/* just count up at 32 KHz */ /* just count up at 32 KHz */
...@@ -134,16 +118,14 @@ static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) ...@@ -134,16 +118,14 @@ static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev)
cpu_relax(); cpu_relax();
} }
/* static int omap_wdt_start(struct watchdog_device *wdog)
* Allow only one task to hold it open
*/
static int omap_wdt_open(struct inode *inode, struct file *file)
{ {
struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
void __iomem *base = wdev->base; void __iomem *base = wdev->base;
if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) mutex_lock(&wdev->lock);
return -EBUSY;
wdev->omap_wdt_users = true;
pm_runtime_get_sync(wdev->dev); pm_runtime_get_sync(wdev->dev);
...@@ -155,223 +137,169 @@ static int omap_wdt_open(struct inode *inode, struct file *file) ...@@ -155,223 +137,169 @@ static int omap_wdt_open(struct inode *inode, struct file *file)
while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01)
cpu_relax(); cpu_relax();
file->private_data = (void *) wdev; omap_wdt_set_timer(wdev, wdog->timeout);
omap_wdt_reload(wdev); /* trigger loading of new timeout value */
omap_wdt_set_timeout(wdev);
omap_wdt_ping(wdev); /* trigger loading of new timeout value */
omap_wdt_enable(wdev); omap_wdt_enable(wdev);
return nonseekable_open(inode, file); mutex_unlock(&wdev->lock);
return 0;
} }
static int omap_wdt_release(struct inode *inode, struct file *file) static int omap_wdt_stop(struct watchdog_device *wdog)
{ {
struct omap_wdt_dev *wdev = file->private_data; struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
/* mutex_lock(&wdev->lock);
* Shut off the timer unless NOWAYOUT is defined.
*/
#ifndef CONFIG_WATCHDOG_NOWAYOUT
omap_wdt_disable(wdev); omap_wdt_disable(wdev);
pm_runtime_put_sync(wdev->dev); pm_runtime_put_sync(wdev->dev);
#else wdev->omap_wdt_users = false;
pr_crit("Unexpected close, not stopping!\n"); mutex_unlock(&wdev->lock);
#endif
wdev->omap_wdt_users = 0;
return 0; return 0;
} }
static ssize_t omap_wdt_write(struct file *file, const char __user *data, static int omap_wdt_ping(struct watchdog_device *wdog)
size_t len, loff_t *ppos)
{ {
struct omap_wdt_dev *wdev = file->private_data; struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
/* Refresh LOAD_TIME. */ mutex_lock(&wdev->lock);
if (len) { omap_wdt_reload(wdev);
spin_lock(&wdt_lock); mutex_unlock(&wdev->lock);
omap_wdt_ping(wdev);
spin_unlock(&wdt_lock); return 0;
}
return len;
} }
static long omap_wdt_ioctl(struct file *file, unsigned int cmd, static int omap_wdt_set_timeout(struct watchdog_device *wdog,
unsigned long arg) unsigned int timeout)
{ {
struct omap_wd_timer_platform_data *pdata; struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
struct omap_wdt_dev *wdev;
u32 rs;
int new_margin, bs;
static const struct watchdog_info ident = {
.identity = "OMAP Watchdog",
.options = WDIOF_SETTIMEOUT,
.firmware_version = 0,
};
wdev = file->private_data;
pdata = wdev->dev->platform_data;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user((struct watchdog_info __user *)arg, &ident,
sizeof(ident));
case WDIOC_GETSTATUS:
return put_user(0, (int __user *)arg);
case WDIOC_GETBOOTSTATUS:
if (!pdata || !pdata->read_reset_sources)
return put_user(0, (int __user *)arg);
rs = pdata->read_reset_sources();
bs = (rs & (1 << OMAP_MPU_WD_RST_SRC_ID_SHIFT)) ?
WDIOF_CARDRESET : 0;
return put_user(bs, (int __user *)arg);
case WDIOC_KEEPALIVE:
spin_lock(&wdt_lock);
omap_wdt_ping(wdev);
spin_unlock(&wdt_lock);
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, (int __user *)arg))
return -EFAULT;
omap_wdt_adjust_timeout(new_margin);
spin_lock(&wdt_lock); mutex_lock(&wdev->lock);
omap_wdt_disable(wdev); omap_wdt_disable(wdev);
omap_wdt_set_timeout(wdev); omap_wdt_set_timer(wdev, timeout);
omap_wdt_enable(wdev); omap_wdt_enable(wdev);
omap_wdt_reload(wdev);
wdog->timeout = timeout;
mutex_unlock(&wdev->lock);
omap_wdt_ping(wdev); return 0;
spin_unlock(&wdt_lock);
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(timer_margin, (int __user *)arg);
default:
return -ENOTTY;
}
} }
static const struct file_operations omap_wdt_fops = { static const struct watchdog_info omap_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "OMAP Watchdog",
};
static const struct watchdog_ops omap_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.write = omap_wdt_write, .start = omap_wdt_start,
.unlocked_ioctl = omap_wdt_ioctl, .stop = omap_wdt_stop,
.open = omap_wdt_open, .ping = omap_wdt_ping,
.release = omap_wdt_release, .set_timeout = omap_wdt_set_timeout,
.llseek = no_llseek,
}; };
static int omap_wdt_probe(struct platform_device *pdev) static int omap_wdt_probe(struct platform_device *pdev)
{ {
struct omap_wd_timer_platform_data *pdata = pdev->dev.platform_data;
bool nowayout = WATCHDOG_NOWAYOUT;
struct watchdog_device *omap_wdt;
struct resource *res, *mem; struct resource *res, *mem;
struct omap_wdt_dev *wdev; struct omap_wdt_dev *wdev;
u32 rs;
int ret; int ret;
omap_wdt = devm_kzalloc(&pdev->dev, sizeof(*omap_wdt), GFP_KERNEL);
if (!omap_wdt)
return -ENOMEM;
/* reserve static register mappings */ /* reserve static register mappings */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) { if (!res)
ret = -ENOENT; return -ENOENT;
goto err_get_resource;
}
if (omap_wdt_dev) {
ret = -EBUSY;
goto err_busy;
}
mem = request_mem_region(res->start, resource_size(res), pdev->name); mem = devm_request_mem_region(&pdev->dev, res->start,
if (!mem) { resource_size(res), pdev->name);
ret = -EBUSY; if (!mem)
goto err_busy; return -EBUSY;
}
wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
if (!wdev) { if (!wdev)
ret = -ENOMEM; return -ENOMEM;
goto err_kzalloc;
}
wdev->omap_wdt_users = 0; wdev->omap_wdt_users = false;
wdev->mem = mem; wdev->mem = mem;
wdev->dev = &pdev->dev; wdev->dev = &pdev->dev;
wdev->wdt_trgr_pattern = 0x1234;
mutex_init(&wdev->lock);
wdev->base = ioremap(res->start, resource_size(res)); wdev->base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!wdev->base) { if (!wdev->base)
ret = -ENOMEM; return -ENOMEM;
goto err_ioremap;
} omap_wdt->info = &omap_wdt_info;
omap_wdt->ops = &omap_wdt_ops;
omap_wdt->min_timeout = TIMER_MARGIN_MIN;
omap_wdt->max_timeout = TIMER_MARGIN_MAX;
if (timer_margin >= TIMER_MARGIN_MIN &&
timer_margin <= TIMER_MARGIN_MAX)
omap_wdt->timeout = timer_margin;
else
omap_wdt->timeout = TIMER_MARGIN_DEFAULT;
watchdog_set_drvdata(omap_wdt, wdev);
watchdog_set_nowayout(omap_wdt, nowayout);
platform_set_drvdata(pdev, wdev); platform_set_drvdata(pdev, omap_wdt);
pm_runtime_enable(wdev->dev); pm_runtime_enable(wdev->dev);
pm_runtime_get_sync(wdev->dev); pm_runtime_get_sync(wdev->dev);
omap_wdt_disable(wdev); if (pdata && pdata->read_reset_sources)
omap_wdt_adjust_timeout(timer_margin); rs = pdata->read_reset_sources();
else
rs = 0;
omap_wdt->bootstatus = (rs & (1 << OMAP_MPU_WD_RST_SRC_ID_SHIFT)) ?
WDIOF_CARDRESET : 0;
wdev->omap_wdt_miscdev.parent = &pdev->dev; omap_wdt_disable(wdev);
wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR;
wdev->omap_wdt_miscdev.name = "watchdog";
wdev->omap_wdt_miscdev.fops = &omap_wdt_fops;
ret = misc_register(&(wdev->omap_wdt_miscdev)); ret = watchdog_register_device(omap_wdt);
if (ret) if (ret) {
goto err_misc; pm_runtime_disable(wdev->dev);
return ret;
}
pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n",
__raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, __raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF,
timer_margin); omap_wdt->timeout);
pm_runtime_put_sync(wdev->dev); pm_runtime_put_sync(wdev->dev);
omap_wdt_dev = pdev;
return 0; return 0;
err_misc:
pm_runtime_disable(wdev->dev);
platform_set_drvdata(pdev, NULL);
iounmap(wdev->base);
err_ioremap:
wdev->base = NULL;
kfree(wdev);
err_kzalloc:
release_mem_region(res->start, resource_size(res));
err_busy:
err_get_resource:
return ret;
} }
static void omap_wdt_shutdown(struct platform_device *pdev) static void omap_wdt_shutdown(struct platform_device *pdev)
{ {
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
mutex_lock(&wdev->lock);
if (wdev->omap_wdt_users) { if (wdev->omap_wdt_users) {
omap_wdt_disable(wdev); omap_wdt_disable(wdev);
pm_runtime_put_sync(wdev->dev); pm_runtime_put_sync(wdev->dev);
} }
mutex_unlock(&wdev->lock);
} }
static int omap_wdt_remove(struct platform_device *pdev) static int omap_wdt_remove(struct platform_device *pdev)
{ {
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
pm_runtime_disable(wdev->dev); pm_runtime_disable(wdev->dev);
if (!res) watchdog_unregister_device(wdog);
return -ENOENT;
misc_deregister(&(wdev->omap_wdt_miscdev));
release_mem_region(res->start, resource_size(res));
platform_set_drvdata(pdev, NULL);
iounmap(wdev->base);
kfree(wdev);
omap_wdt_dev = NULL;
return 0; return 0;
} }
...@@ -386,25 +314,31 @@ static int omap_wdt_remove(struct platform_device *pdev) ...@@ -386,25 +314,31 @@ static int omap_wdt_remove(struct platform_device *pdev)
static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state)
{ {
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
mutex_lock(&wdev->lock);
if (wdev->omap_wdt_users) { if (wdev->omap_wdt_users) {
omap_wdt_disable(wdev); omap_wdt_disable(wdev);
pm_runtime_put_sync(wdev->dev); pm_runtime_put_sync(wdev->dev);
} }
mutex_unlock(&wdev->lock);
return 0; return 0;
} }
static int omap_wdt_resume(struct platform_device *pdev) static int omap_wdt_resume(struct platform_device *pdev)
{ {
struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog);
mutex_lock(&wdev->lock);
if (wdev->omap_wdt_users) { if (wdev->omap_wdt_users) {
pm_runtime_get_sync(wdev->dev); pm_runtime_get_sync(wdev->dev);
omap_wdt_enable(wdev); omap_wdt_enable(wdev);
omap_wdt_ping(wdev); omap_wdt_reload(wdev);
} }
mutex_unlock(&wdev->lock);
return 0; return 0;
} }
...@@ -437,5 +371,4 @@ module_platform_driver(omap_wdt_driver); ...@@ -437,5 +371,4 @@ module_platform_driver(omap_wdt_driver);
MODULE_AUTHOR("George G. Davis"); MODULE_AUTHOR("George G. Davis");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_ALIAS("platform:omap_wdt"); MODULE_ALIAS("platform:omap_wdt");
...@@ -156,6 +156,8 @@ static int orion_wdt_probe(struct platform_device *pdev) ...@@ -156,6 +156,8 @@ static int orion_wdt_probe(struct platform_device *pdev)
wdt_tclk = clk_get_rate(clk); wdt_tclk = clk_get_rate(clk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
wdt_reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); wdt_reg = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!wdt_reg) if (!wdt_reg)
return -ENOMEM; return -ENOMEM;
......
...@@ -354,7 +354,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -354,7 +354,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
goto err_map; goto err_map;
} }
clk_enable(wdt_clock); clk_prepare_enable(wdt_clock);
ret = s3c2410wdt_cpufreq_register(); ret = s3c2410wdt_cpufreq_register();
if (ret < 0) { if (ret < 0) {
...@@ -421,7 +421,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -421,7 +421,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
s3c2410wdt_cpufreq_deregister(); s3c2410wdt_cpufreq_deregister();
err_clk: err_clk:
clk_disable(wdt_clock); clk_disable_unprepare(wdt_clock);
clk_put(wdt_clock); clk_put(wdt_clock);
wdt_clock = NULL; wdt_clock = NULL;
...@@ -445,7 +445,7 @@ static int s3c2410wdt_remove(struct platform_device *dev) ...@@ -445,7 +445,7 @@ static int s3c2410wdt_remove(struct platform_device *dev)
s3c2410wdt_cpufreq_deregister(); s3c2410wdt_cpufreq_deregister();
clk_disable(wdt_clock); clk_disable_unprepare(wdt_clock);
clk_put(wdt_clock); clk_put(wdt_clock);
wdt_clock = NULL; wdt_clock = NULL;
......
...@@ -13,7 +13,9 @@ ...@@ -13,7 +13,9 @@
* as published by the Free Software Foundation; either version * as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version. * 2 of the License, or (at your option) any later version.
* *
* See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide" * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
* AMD Publication 45482 "AMD SB800-Series Southbridges Register
* Reference Guide"
*/ */
/* /*
...@@ -38,18 +40,24 @@ ...@@ -38,18 +40,24 @@
#include "sp5100_tco.h" #include "sp5100_tco.h"
/* Module and version information */ /* Module and version information */
#define TCO_VERSION "0.01" #define TCO_VERSION "0.03"
#define TCO_MODULE_NAME "SP5100 TCO timer" #define TCO_MODULE_NAME "SP5100 TCO timer"
#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION #define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
/* internal variables */ /* internal variables */
static u32 tcobase_phys; static u32 tcobase_phys;
static u32 resbase_phys;
static u32 tco_wdt_fired;
static void __iomem *tcobase; static void __iomem *tcobase;
static unsigned int pm_iobase; static unsigned int pm_iobase;
static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
static unsigned long timer_alive; static unsigned long timer_alive;
static char tco_expect_close; static char tco_expect_close;
static struct pci_dev *sp5100_tco_pci; static struct pci_dev *sp5100_tco_pci;
static struct resource wdt_res = {
.name = "Watchdog Timer",
.flags = IORESOURCE_MEM,
};
/* the watchdog platform device */ /* the watchdog platform device */
static struct platform_device *sp5100_tco_platform_device; static struct platform_device *sp5100_tco_platform_device;
...@@ -64,9 +72,15 @@ MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default=" ...@@ -64,9 +72,15 @@ MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
static bool nowayout = WATCHDOG_NOWAYOUT; static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0); module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started" MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."
" (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); " (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static unsigned int force_addr;
module_param(force_addr, uint, 0);
MODULE_PARM_DESC(force_addr, "Force the use of specified MMIO address."
" ONLY USE THIS PARAMETER IF YOU REALLY KNOW"
" WHAT YOU ARE DOING (default=none)");
/* /*
* Some TCO specific functions * Some TCO specific functions
*/ */
...@@ -122,6 +136,79 @@ static int tco_timer_set_heartbeat(int t) ...@@ -122,6 +136,79 @@ static int tco_timer_set_heartbeat(int t)
return 0; return 0;
} }
static void tco_timer_enable(void)
{
int val;
if (sp5100_tco_pci->revision >= 0x40) {
/* For SB800 or later */
/* Set the Watchdog timer resolution to 1 sec */
outb(SB800_PM_WATCHDOG_CONFIG, SB800_IO_PM_INDEX_REG);
val = inb(SB800_IO_PM_DATA_REG);
val |= SB800_PM_WATCHDOG_SECOND_RES;
outb(val, SB800_IO_PM_DATA_REG);
/* Enable watchdog decode bit and watchdog timer */
outb(SB800_PM_WATCHDOG_CONTROL, SB800_IO_PM_INDEX_REG);
val = inb(SB800_IO_PM_DATA_REG);
val |= SB800_PCI_WATCHDOG_DECODE_EN;
val &= ~SB800_PM_WATCHDOG_DISABLE;
outb(val, SB800_IO_PM_DATA_REG);
} else {
/* For SP5100 or SB7x0 */
/* Enable watchdog decode bit */
pci_read_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
&val);
val |= SP5100_PCI_WATCHDOG_DECODE_EN;
pci_write_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
val);
/* Enable Watchdog timer and set the resolution to 1 sec */
outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG);
val = inb(SP5100_IO_PM_DATA_REG);
val |= SP5100_PM_WATCHDOG_SECOND_RES;
val &= ~SP5100_PM_WATCHDOG_DISABLE;
outb(val, SP5100_IO_PM_DATA_REG);
}
}
static void tco_timer_disable(void)
{
int val;
if (sp5100_tco_pci->revision >= 0x40) {
/* For SB800 or later */
/* Enable watchdog decode bit and Disable watchdog timer */
outb(SB800_PM_WATCHDOG_CONTROL, SB800_IO_PM_INDEX_REG);
val = inb(SB800_IO_PM_DATA_REG);
val |= SB800_PCI_WATCHDOG_DECODE_EN;
val |= SB800_PM_WATCHDOG_DISABLE;
outb(val, SB800_IO_PM_DATA_REG);
} else {
/* For SP5100 or SB7x0 */
/* Enable watchdog decode bit */
pci_read_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
&val);
val |= SP5100_PCI_WATCHDOG_DECODE_EN;
pci_write_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
val);
/* Disable Watchdog timer */
outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG);
val = inb(SP5100_IO_PM_DATA_REG);
val |= SP5100_PM_WATCHDOG_DISABLE;
outb(val, SP5100_IO_PM_DATA_REG);
}
}
/* /*
* /dev/watchdog handling * /dev/watchdog handling
*/ */
...@@ -270,11 +357,12 @@ MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); ...@@ -270,11 +357,12 @@ MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
/* /*
* Init & exit routines * Init & exit routines
*/ */
static unsigned char sp5100_tco_setupdevice(void) static unsigned char sp5100_tco_setupdevice(void)
{ {
struct pci_dev *dev = NULL; struct pci_dev *dev = NULL;
const char *dev_name = NULL;
u32 val; u32 val;
u32 index_reg, data_reg, base_addr;
/* Match the PCI device */ /* Match the PCI device */
for_each_pci_dev(dev) { for_each_pci_dev(dev) {
...@@ -287,29 +375,160 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -287,29 +375,160 @@ static unsigned char sp5100_tco_setupdevice(void)
if (!sp5100_tco_pci) if (!sp5100_tco_pci)
return 0; return 0;
pr_info("PCI Revision ID: 0x%x\n", sp5100_tco_pci->revision);
/*
* Determine type of southbridge chipset.
*/
if (sp5100_tco_pci->revision >= 0x40) {
dev_name = SB800_DEVNAME;
index_reg = SB800_IO_PM_INDEX_REG;
data_reg = SB800_IO_PM_DATA_REG;
base_addr = SB800_PM_WATCHDOG_BASE;
} else {
dev_name = SP5100_DEVNAME;
index_reg = SP5100_IO_PM_INDEX_REG;
data_reg = SP5100_IO_PM_DATA_REG;
base_addr = SP5100_PM_WATCHDOG_BASE;
}
/* Request the IO ports used by this driver */ /* Request the IO ports used by this driver */
pm_iobase = SP5100_IO_PM_INDEX_REG; pm_iobase = SP5100_IO_PM_INDEX_REG;
if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) { if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, dev_name)) {
pr_err("I/O address 0x%04x already in use\n", pm_iobase); pr_err("I/O address 0x%04x already in use\n", pm_iobase);
goto exit; goto exit;
} }
/* Find the watchdog base address. */ /*
outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG); * First, Find the watchdog timer MMIO address from indirect I/O.
val = inb(SP5100_IO_PM_DATA_REG); */
outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG); outb(base_addr+3, index_reg);
val = val << 8 | inb(SP5100_IO_PM_DATA_REG); val = inb(data_reg);
outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG); outb(base_addr+2, index_reg);
val = val << 8 | inb(SP5100_IO_PM_DATA_REG); val = val << 8 | inb(data_reg);
outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG); outb(base_addr+1, index_reg);
/* Low three bits of BASE0 are reserved. */ val = val << 8 | inb(data_reg);
val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8); outb(base_addr+0, index_reg);
/* Low three bits of BASE are reserved */
val = val << 8 | (inb(data_reg) & 0xf8);
pr_debug("Got 0x%04x from indirect I/O\n", val);
/* Check MMIO address conflict */
if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
dev_name))
goto setup_wdt;
else
pr_debug("MMIO address 0x%04x already in use\n", val);
if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, /*
"SP5100 TCO")) { * Secondly, Find the watchdog timer MMIO address
pr_err("mmio address 0x%04x already in use\n", val); * from SBResource_MMIO register.
*/
if (sp5100_tco_pci->revision >= 0x40) {
/* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
outb(SB800_PM_ACPI_MMIO_EN+3, SB800_IO_PM_INDEX_REG);
val = inb(SB800_IO_PM_DATA_REG);
outb(SB800_PM_ACPI_MMIO_EN+2, SB800_IO_PM_INDEX_REG);
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
outb(SB800_PM_ACPI_MMIO_EN+1, SB800_IO_PM_INDEX_REG);
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
outb(SB800_PM_ACPI_MMIO_EN+0, SB800_IO_PM_INDEX_REG);
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
} else {
/* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
pci_read_config_dword(sp5100_tco_pci,
SP5100_SB_RESOURCE_MMIO_BASE, &val);
}
/* The SBResource_MMIO is enabled and mapped memory space? */
if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) ==
SB800_ACPI_MMIO_DECODE_EN) {
/* Clear unnecessary the low twelve bits */
val &= ~0xFFF;
/* Add the Watchdog Timer offset to base address. */
val += SB800_PM_WDT_MMIO_OFFSET;
/* Check MMIO address conflict */
if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
dev_name)) {
pr_debug("Got 0x%04x from SBResource_MMIO register\n",
val);
goto setup_wdt;
} else
pr_debug("MMIO address 0x%04x already in use\n", val);
} else
pr_debug("SBResource_MMIO is disabled(0x%04x)\n", val);
/*
* Lastly re-programming the watchdog timer MMIO address,
* This method is a last resort...
*
* Before re-programming, to ensure that the watchdog timer
* is disabled, disable the watchdog timer.
*/
tco_timer_disable();
if (force_addr) {
/*
* Force the use of watchdog timer MMIO address, and aligned to
* 8byte boundary.
*/
force_addr &= ~0x7;
val = force_addr;
pr_info("Force the use of 0x%04x as MMIO address\n", val);
} else {
/*
* Get empty slot into the resource tree for watchdog timer.
*/
if (allocate_resource(&iomem_resource,
&wdt_res,
SP5100_WDT_MEM_MAP_SIZE,
0xf0000000,
0xfffffff8,
0x8,
NULL,
NULL)) {
pr_err("MMIO allocation failed\n");
goto unreg_region; goto unreg_region;
} }
val = resbase_phys = wdt_res.start;
pr_debug("Got 0x%04x from resource tree\n", val);
}
/* Restore to the low three bits, if chipset is SB8x0(or later) */
if (sp5100_tco_pci->revision >= 0x40) {
u8 reserved_bit;
reserved_bit = inb(base_addr) & 0x7;
val |= (u32)reserved_bit;
}
/* Re-programming the watchdog timer base address */
outb(base_addr+0, index_reg);
/* Low three bits of BASE are reserved */
outb((val >> 0) & 0xf8, data_reg);
outb(base_addr+1, index_reg);
outb((val >> 8) & 0xff, data_reg);
outb(base_addr+2, index_reg);
outb((val >> 16) & 0xff, data_reg);
outb(base_addr+3, index_reg);
outb((val >> 24) & 0xff, data_reg);
/*
* Clear unnecessary the low three bits,
* if chipset is SB8x0(or later)
*/
if (sp5100_tco_pci->revision >= 0x40)
val &= ~0x7;
if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
dev_name)) {
pr_err("MMIO address 0x%04x already in use\n", val);
goto unreg_resource;
}
setup_wdt:
tcobase_phys = val; tcobase_phys = val;
tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE);
...@@ -318,26 +537,18 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -318,26 +537,18 @@ static unsigned char sp5100_tco_setupdevice(void)
goto unreg_mem_region; goto unreg_mem_region;
} }
/* Enable watchdog decode bit */ pr_info("Using 0x%04x for watchdog MMIO address\n", val);
pci_read_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
&val);
val |= SP5100_PCI_WATCHDOG_DECODE_EN;
pci_write_config_dword(sp5100_tco_pci,
SP5100_PCI_WATCHDOG_MISC_REG,
val);
/* Enable Watchdog timer and set the resolution to 1 sec. */ /* Setup the watchdog timer */
outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); tco_timer_enable();
val = inb(SP5100_IO_PM_DATA_REG);
val |= SP5100_PM_WATCHDOG_SECOND_RES;
val &= ~SP5100_PM_WATCHDOG_DISABLE;
outb(val, SP5100_IO_PM_DATA_REG);
/* Check that the watchdog action is set to reset the system. */ /* Check that the watchdog action is set to reset the system */
val = readl(SP5100_WDT_CONTROL(tcobase)); val = readl(SP5100_WDT_CONTROL(tcobase));
/*
* Save WatchDogFired status, because WatchDogFired flag is
* cleared here.
*/
tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED;
val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; val &= ~SP5100_PM_WATCHDOG_ACTION_RESET;
writel(val, SP5100_WDT_CONTROL(tcobase)); writel(val, SP5100_WDT_CONTROL(tcobase));
...@@ -355,6 +566,9 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -355,6 +566,9 @@ static unsigned char sp5100_tco_setupdevice(void)
unreg_mem_region: unreg_mem_region:
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
unreg_resource:
if (resbase_phys)
release_resource(&wdt_res);
unreg_region: unreg_region:
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
exit: exit:
...@@ -364,23 +578,18 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -364,23 +578,18 @@ static unsigned char sp5100_tco_setupdevice(void)
static int sp5100_tco_init(struct platform_device *dev) static int sp5100_tco_init(struct platform_device *dev)
{ {
int ret; int ret;
u32 val; char addr_str[16];
/* Check whether or not the hardware watchdog is there. If found, then /*
* Check whether or not the hardware watchdog is there. If found, then
* set it up. * set it up.
*/ */
if (!sp5100_tco_setupdevice()) if (!sp5100_tco_setupdevice())
return -ENODEV; return -ENODEV;
/* Check to see if last reboot was due to watchdog timeout */ /* Check to see if last reboot was due to watchdog timeout */
pr_info("Watchdog reboot %sdetected\n", pr_info("Last reboot was %striggered by watchdog.\n",
readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ? tco_wdt_fired ? "" : "not ");
"" : "not ");
/* Clear out the old status */
val = readl(SP5100_WDT_CONTROL(tcobase));
val &= ~SP5100_PM_WATCHDOG_FIRED;
writel(val, SP5100_WDT_CONTROL(tcobase));
/* /*
* Check that the heartbeat value is within it's range. * Check that the heartbeat value is within it's range.
...@@ -400,14 +609,24 @@ static int sp5100_tco_init(struct platform_device *dev) ...@@ -400,14 +609,24 @@ static int sp5100_tco_init(struct platform_device *dev)
clear_bit(0, &timer_alive); clear_bit(0, &timer_alive);
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", /* Show module parameters */
tcobase, heartbeat, nowayout); if (force_addr == tcobase_phys)
/* The force_addr is vaild */
sprintf(addr_str, "0x%04x", force_addr);
else
strcpy(addr_str, "none");
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d, "
"force_addr=%s)\n",
tcobase, heartbeat, nowayout, addr_str);
return 0; return 0;
exit: exit:
iounmap(tcobase); iounmap(tcobase);
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
if (resbase_phys)
release_resource(&wdt_res);
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
return ret; return ret;
} }
...@@ -422,6 +641,8 @@ static void sp5100_tco_cleanup(void) ...@@ -422,6 +641,8 @@ static void sp5100_tco_cleanup(void)
misc_deregister(&sp5100_tco_miscdev); misc_deregister(&sp5100_tco_miscdev);
iounmap(tcobase); iounmap(tcobase);
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
if (resbase_phys)
release_resource(&wdt_res);
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
} }
...@@ -451,7 +672,7 @@ static int __init sp5100_tco_init_module(void) ...@@ -451,7 +672,7 @@ static int __init sp5100_tco_init_module(void)
{ {
int err; int err;
pr_info("SP5100 TCO WatchDog Timer Driver v%s\n", TCO_VERSION); pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
err = platform_driver_register(&sp5100_tco_driver); err = platform_driver_register(&sp5100_tco_driver);
if (err) if (err)
...@@ -475,13 +696,13 @@ static void __exit sp5100_tco_cleanup_module(void) ...@@ -475,13 +696,13 @@ static void __exit sp5100_tco_cleanup_module(void)
{ {
platform_device_unregister(sp5100_tco_platform_device); platform_device_unregister(sp5100_tco_platform_device);
platform_driver_unregister(&sp5100_tco_driver); platform_driver_unregister(&sp5100_tco_driver);
pr_info("SP5100 TCO Watchdog Module Unloaded\n"); pr_info("SP5100/SB800 TCO Watchdog Module Unloaded\n");
} }
module_init(sp5100_tco_init_module); module_init(sp5100_tco_init_module);
module_exit(sp5100_tco_cleanup_module); module_exit(sp5100_tco_cleanup_module);
MODULE_AUTHOR("Priyanka Gupta"); MODULE_AUTHOR("Priyanka Gupta");
MODULE_DESCRIPTION("TCO timer driver for SP5100 chipset"); MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
...@@ -9,33 +9,57 @@ ...@@ -9,33 +9,57 @@
/* /*
* Some address definitions for the Watchdog * Some address definitions for the Watchdog
*/ */
#define SP5100_WDT_MEM_MAP_SIZE 0x08 #define SP5100_WDT_MEM_MAP_SIZE 0x08
#define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */ #define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */
#define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ #define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */
#define SP5100_WDT_START_STOP_BIT 1 #define SP5100_WDT_START_STOP_BIT (1 << 0)
#define SP5100_WDT_TRIGGER_BIT (1 << 7) #define SP5100_WDT_TRIGGER_BIT (1 << 7)
#define SP5100_PCI_WATCHDOG_MISC_REG 0x41
#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3)
#define SP5100_PM_IOPORTS_SIZE 0x02 #define SP5100_PM_IOPORTS_SIZE 0x02
/* These two IO registers are hardcoded and there doesn't seem to be a way to /*
* These two IO registers are hardcoded and there doesn't seem to be a way to
* read them from a register. * read them from a register.
*/ */
/* For SP5100/SB7x0 chipset */
#define SP5100_IO_PM_INDEX_REG 0xCD6 #define SP5100_IO_PM_INDEX_REG 0xCD6
#define SP5100_IO_PM_DATA_REG 0xCD7 #define SP5100_IO_PM_DATA_REG 0xCD7
#define SP5100_SB_RESOURCE_MMIO_BASE 0x9C
#define SP5100_PM_WATCHDOG_CONTROL 0x69 #define SP5100_PM_WATCHDOG_CONTROL 0x69
#define SP5100_PM_WATCHDOG_BASE0 0x6C #define SP5100_PM_WATCHDOG_BASE 0x6C
#define SP5100_PM_WATCHDOG_BASE1 0x6D
#define SP5100_PM_WATCHDOG_BASE2 0x6E
#define SP5100_PM_WATCHDOG_BASE3 0x6F
#define SP5100_PM_WATCHDOG_FIRED (1 << 1) #define SP5100_PM_WATCHDOG_FIRED (1 << 1)
#define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) #define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2)
#define SP5100_PM_WATCHDOG_DISABLE 1 #define SP5100_PCI_WATCHDOG_MISC_REG 0x41
#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3)
#define SP5100_PM_WATCHDOG_DISABLE (1 << 0)
#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) #define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1)
#define SP5100_DEVNAME "SP5100 TCO"
/* For SB8x0(or later) chipset */
#define SB800_IO_PM_INDEX_REG 0xCD6
#define SB800_IO_PM_DATA_REG 0xCD7
#define SB800_PM_ACPI_MMIO_EN 0x24
#define SB800_PM_WATCHDOG_CONTROL 0x48
#define SB800_PM_WATCHDOG_BASE 0x48
#define SB800_PM_WATCHDOG_CONFIG 0x4C
#define SB800_PCI_WATCHDOG_DECODE_EN (1 << 0)
#define SB800_PM_WATCHDOG_DISABLE (1 << 2)
#define SB800_PM_WATCHDOG_SECOND_RES (3 << 0)
#define SB800_ACPI_MMIO_DECODE_EN (1 << 0)
#define SB800_ACPI_MMIO_SEL (1 << 2)
#define SB800_PM_WDT_MMIO_OFFSET 0xB00
#define SB800_DEVNAME "SB800 TCO"
...@@ -130,16 +130,10 @@ static int wdt_config(struct watchdog_device *wdd, bool ping) ...@@ -130,16 +130,10 @@ static int wdt_config(struct watchdog_device *wdd, bool ping)
int ret; int ret;
if (!ping) { if (!ping) {
ret = clk_prepare(wdt->clk);
if (ret) {
dev_err(&wdt->adev->dev, "clock prepare fail");
return ret;
}
ret = clk_enable(wdt->clk); ret = clk_prepare_enable(wdt->clk);
if (ret) { if (ret) {
dev_err(&wdt->adev->dev, "clock enable fail"); dev_err(&wdt->adev->dev, "clock enable fail");
clk_unprepare(wdt->clk);
return ret; return ret;
} }
} }
...@@ -190,8 +184,7 @@ static int wdt_disable(struct watchdog_device *wdd) ...@@ -190,8 +184,7 @@ static int wdt_disable(struct watchdog_device *wdd)
readl_relaxed(wdt->base + WDTLOCK); readl_relaxed(wdt->base + WDTLOCK);
spin_unlock(&wdt->lock); spin_unlock(&wdt->lock);
clk_disable(wdt->clk); clk_disable_unprepare(wdt->clk);
clk_unprepare(wdt->clk);
return 0; return 0;
} }
......
...@@ -22,26 +22,12 @@ ...@@ -22,26 +22,12 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/i2c/twl.h> #include <linux/i2c/twl.h>
#define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3 #define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3
#define TWL4030_WDT_STATE_OPEN 0x1
#define TWL4030_WDT_STATE_ACTIVE 0x8
static struct platform_device *twl4030_wdt_dev;
struct twl4030_wdt {
struct miscdevice miscdev;
int timer_margin;
unsigned long state;
};
static bool nowayout = WATCHDOG_NOWAYOUT; static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0); module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
...@@ -49,175 +35,75 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " ...@@ -49,175 +35,75 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
static int twl4030_wdt_write(unsigned char val) static int twl4030_wdt_write(unsigned char val)
{ {
return twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val, return twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, val,
TWL4030_WATCHDOG_CFG_REG_OFFS); TWL4030_WATCHDOG_CFG_REG_OFFS);
} }
static int twl4030_wdt_enable(struct twl4030_wdt *wdt) static int twl4030_wdt_start(struct watchdog_device *wdt)
{ {
return twl4030_wdt_write(wdt->timer_margin + 1); return twl4030_wdt_write(wdt->timeout + 1);
} }
static int twl4030_wdt_disable(struct twl4030_wdt *wdt) static int twl4030_wdt_stop(struct watchdog_device *wdt)
{ {
return twl4030_wdt_write(0); return twl4030_wdt_write(0);
} }
static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout) static int twl4030_wdt_set_timeout(struct watchdog_device *wdt,
{ unsigned int timeout)
if (timeout < 0 || timeout > 30) {
dev_warn(wdt->miscdev.parent,
"Timeout can only be in the range [0-30] seconds");
return -EINVAL;
}
wdt->timer_margin = timeout;
return twl4030_wdt_enable(wdt);
}
static ssize_t twl4030_wdt_write_fop(struct file *file,
const char __user *data, size_t len, loff_t *ppos)
{
struct twl4030_wdt *wdt = file->private_data;
if (len)
twl4030_wdt_enable(wdt);
return len;
}
static long twl4030_wdt_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{ {
void __user *argp = (void __user *)arg; wdt->timeout = timeout;
int __user *p = argp;
int new_margin;
struct twl4030_wdt *wdt = file->private_data;
static const struct watchdog_info twl4030_wd_ident = {
.identity = "TWL4030 Watchdog",
.options = WDIOF_SETTIMEOUT,
.firmware_version = 0,
};
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &twl4030_wd_ident,
sizeof(twl4030_wd_ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
twl4030_wdt_enable(wdt);
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, p))
return -EFAULT;
if (twl4030_wdt_set_timeout(wdt, new_margin))
return -EINVAL;
return put_user(wdt->timer_margin, p);
case WDIOC_GETTIMEOUT:
return put_user(wdt->timer_margin, p);
default:
return -ENOTTY;
}
return 0; return 0;
} }
static int twl4030_wdt_open(struct inode *inode, struct file *file) static const struct watchdog_info twl4030_wdt_info = {
{ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev); .identity = "TWL4030 Watchdog",
};
/* /dev/watchdog can only be opened once */
if (test_and_set_bit(0, &wdt->state))
return -EBUSY;
wdt->state |= TWL4030_WDT_STATE_ACTIVE;
file->private_data = (void *) wdt;
twl4030_wdt_enable(wdt);
return nonseekable_open(inode, file);
}
static int twl4030_wdt_release(struct inode *inode, struct file *file)
{
struct twl4030_wdt *wdt = file->private_data;
if (nowayout) {
dev_alert(wdt->miscdev.parent,
"Unexpected close, watchdog still running!\n");
twl4030_wdt_enable(wdt);
} else {
if (twl4030_wdt_disable(wdt))
return -EFAULT;
wdt->state &= ~TWL4030_WDT_STATE_ACTIVE;
}
clear_bit(0, &wdt->state);
return 0;
}
static const struct file_operations twl4030_wdt_fops = { static const struct watchdog_ops twl4030_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.llseek = no_llseek, .start = twl4030_wdt_start,
.open = twl4030_wdt_open, .stop = twl4030_wdt_stop,
.release = twl4030_wdt_release, .set_timeout = twl4030_wdt_set_timeout,
.unlocked_ioctl = twl4030_wdt_ioctl,
.write = twl4030_wdt_write_fop,
}; };
static int twl4030_wdt_probe(struct platform_device *pdev) static int twl4030_wdt_probe(struct platform_device *pdev)
{ {
int ret = 0; int ret = 0;
struct twl4030_wdt *wdt; struct watchdog_device *wdt;
wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL); wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
if (!wdt) if (!wdt)
return -ENOMEM; return -ENOMEM;
wdt->state = 0; wdt->info = &twl4030_wdt_info;
wdt->timer_margin = 30; wdt->ops = &twl4030_wdt_ops;
wdt->miscdev.parent = &pdev->dev; wdt->status = 0;
wdt->miscdev.fops = &twl4030_wdt_fops; wdt->timeout = 30;
wdt->miscdev.minor = WATCHDOG_MINOR; wdt->min_timeout = 1;
wdt->miscdev.name = "watchdog"; wdt->max_timeout = 30;
watchdog_set_nowayout(wdt, nowayout);
platform_set_drvdata(pdev, wdt); platform_set_drvdata(pdev, wdt);
twl4030_wdt_dev = pdev; twl4030_wdt_stop(wdt);
twl4030_wdt_disable(wdt);
ret = misc_register(&wdt->miscdev); ret = watchdog_register_device(wdt);
if (ret) { if (ret) {
dev_err(wdt->miscdev.parent,
"Failed to register misc device\n");
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(wdt);
twl4030_wdt_dev = NULL;
return ret; return ret;
} }
return 0; return 0;
} }
static int twl4030_wdt_remove(struct platform_device *pdev) static int twl4030_wdt_remove(struct platform_device *pdev)
{ {
struct twl4030_wdt *wdt = platform_get_drvdata(pdev); struct watchdog_device *wdt = platform_get_drvdata(pdev);
if (wdt->state & TWL4030_WDT_STATE_ACTIVE)
if (twl4030_wdt_disable(wdt))
return -EFAULT;
wdt->state &= ~TWL4030_WDT_STATE_ACTIVE;
misc_deregister(&wdt->miscdev);
watchdog_unregister_device(wdt);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(wdt);
twl4030_wdt_dev = NULL;
return 0; return 0;
} }
...@@ -225,18 +111,18 @@ static int twl4030_wdt_remove(struct platform_device *pdev) ...@@ -225,18 +111,18 @@ static int twl4030_wdt_remove(struct platform_device *pdev)
#ifdef CONFIG_PM #ifdef CONFIG_PM
static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state) static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state)
{ {
struct twl4030_wdt *wdt = platform_get_drvdata(pdev); struct watchdog_device *wdt = platform_get_drvdata(pdev);
if (wdt->state & TWL4030_WDT_STATE_ACTIVE) if (watchdog_active(wdt))
return twl4030_wdt_disable(wdt); return twl4030_wdt_stop(wdt);
return 0; return 0;
} }
static int twl4030_wdt_resume(struct platform_device *pdev) static int twl4030_wdt_resume(struct platform_device *pdev)
{ {
struct twl4030_wdt *wdt = platform_get_drvdata(pdev); struct watchdog_device *wdt = platform_get_drvdata(pdev);
if (wdt->state & TWL4030_WDT_STATE_ACTIVE) if (watchdog_active(wdt))
return twl4030_wdt_enable(wdt); return twl4030_wdt_start(wdt);
return 0; return 0;
} }
...@@ -260,6 +146,5 @@ module_platform_driver(twl4030_wdt_driver); ...@@ -260,6 +146,5 @@ module_platform_driver(twl4030_wdt_driver);
MODULE_AUTHOR("Nokia Corporation"); MODULE_AUTHOR("Nokia Corporation");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_ALIAS("platform:twl4030_wdt"); MODULE_ALIAS("platform:twl4030_wdt");
...@@ -129,7 +129,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd) ...@@ -129,7 +129,7 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
return wdd->driver_data; return wdd->driver_data;
} }
/* drivers/watchdog/core/watchdog_core.c */ /* drivers/watchdog/watchdog_core.c */
extern int watchdog_register_device(struct watchdog_device *); extern int watchdog_register_device(struct watchdog_device *);
extern void watchdog_unregister_device(struct watchdog_device *); extern void watchdog_unregister_device(struct watchdog_device *);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment