Commit 68b86a25 authored by Linus Torvalds's avatar Linus Torvalds

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

Pull watchdog updates from Wim Van Sebroeck:
 "This contains:
   - fixes and improvements
   - devicetree bindings
   - conversion to watchdog generic framework of the following drivers:
        - booke_wdt
        - bcm47xx_wdt.c
        - at91sam9_wdt
   - Removal of old STMP3xxx driver
   - Addition of following new drivers:
        - new driver for STMP3xxx and i.MX23/28
        - Retu watchdog driver"

* git://www.linux-watchdog.org/linux-watchdog: (30 commits)
  watchdog: sp805_wdt depends on ARM
  watchdog: davinci_wdt: update to devm_* API
  watchdog: davinci_wdt: use devm managed clk get
  watchdog: at91rm9200: add DT support
  watchdog: add timeout-sec property binding
  watchdog: at91sam9_wdt: Convert to use the watchdog framework
  watchdog: omap_wdt: Add option nowayout
  watchdog: core: dt: add support for the timeout-sec dt property
  watchdog: bcm47xx_wdt.c: add hard timer
  watchdog: bcm47xx_wdt.c: rename wdt_time to timeout
  watchdog: bcm47xx_wdt.c: rename ops methods
  watchdog: bcm47xx_wdt.c: use platform device
  watchdog: bcm47xx_wdt.c: convert to watchdog core api
  watchdog: Convert BookE watchdog driver to watchdog infrastructure
  watchdog: s3c2410_wdt: Use devm_* functions
  watchdog: remove old STMP3xxx driver
  watchdog: add new driver for STMP3xxx and i.MX23/28
  rtc: stmp3xxx: add wdt-accessor function
  watchdog: introduce retu_wdt driver
  watchdog: intel_scu_watchdog: fix Kconfig dependency
  ...
parents 527c680f 41e9f3f7
Atmel AT91RM9200 System Timer Watchdog
Required properties:
- compatible: must be "atmel,at91sam9260-wdt".
Example:
watchdog@fffffd00 {
compatible = "atmel,at91rm9200-wdt";
};
...@@ -7,9 +7,13 @@ Required properties: ...@@ -7,9 +7,13 @@ Required properties:
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
Optional properties:
- timeout-sec: contains the watchdog timeout in seconds.
Example: Example:
watchdog@fffffd40 { watchdog@fffffd40 {
compatible = "atmel,at91sam9260-wdt"; compatible = "atmel,at91sam9260-wdt";
reg = <0xfffffd40 0x10>; reg = <0xfffffd40 0x10>;
timeout-sec = <10>;
}; };
...@@ -5,10 +5,15 @@ Required Properties: ...@@ -5,10 +5,15 @@ Required Properties:
- Compatibility : "marvell,orion-wdt" - Compatibility : "marvell,orion-wdt"
- reg : Address of the timer registers - reg : Address of the timer registers
Optional properties:
- timeout-sec : Contains the watchdog timeout in seconds
Example: Example:
wdt@20300 { wdt@20300 {
compatible = "marvell,orion-wdt"; compatible = "marvell,orion-wdt";
reg = <0x20300 0x28>; reg = <0x20300 0x28>;
timeout-sec = <10>;
status = "okay"; status = "okay";
}; };
...@@ -5,9 +5,13 @@ Required properties: ...@@ -5,9 +5,13 @@ Required properties:
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
Optional properties:
- timeout-sec: contains the watchdog timeout in seconds.
Example: Example:
watchdog@4003C000 { watchdog@4003C000 {
compatible = "nxp,pnx4008-wdt"; compatible = "nxp,pnx4008-wdt";
reg = <0x4003C000 0x1000>; reg = <0x4003C000 0x1000>;
timeout-sec = <10>;
}; };
* Qualcomm Atheros AR7130 Watchdog Timer (WDT) Controller
Required properties:
- compatible: must be "qca,ar7130-wdt"
- reg: physical base address of the controller and length of memory mapped
region.
Example:
wdt@18060008 {
compatible = "qca,ar9330-wdt", "qca,ar7130-wdt";
reg = <0x18060008 0x8>;
};
...@@ -9,3 +9,6 @@ Required properties: ...@@ -9,3 +9,6 @@ Required properties:
- reg : base physical address of the controller and length of memory mapped - reg : base physical address of the controller and length of memory mapped
region. region.
- interrupts : interrupt number to the cpu. - interrupts : interrupt number to the cpu.
Optional properties:
- timeout-sec : contains the watchdog timeout in seconds.
The Linux WatchDog Timer Driver Core kernel API. The Linux WatchDog Timer Driver Core kernel API.
=============================================== ===============================================
Last reviewed: 22-May-2012 Last reviewed: 12-Feb-2013
Wim Van Sebroeck <wim@iguana.be> Wim Van Sebroeck <wim@iguana.be>
...@@ -212,3 +212,15 @@ driver specific data to and a pointer to the data itself. ...@@ -212,3 +212,15 @@ driver specific data to and a pointer to the data itself.
The watchdog_get_drvdata function allows you to retrieve driver specific data. The watchdog_get_drvdata function allows you to retrieve driver specific data.
The argument of this function is the watchdog device where you want to retrieve The argument of this function is the watchdog device where you want to retrieve
data from. The function returns the pointer to the driver specific data. data from. The function returns the pointer to the driver specific data.
To initialize the timeout field, the following function can be used:
extern int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm, struct device *dev);
The watchdog_init_timeout function allows you to initialize the timeout field
using the module timeout parameter or by retrieving the timeout-sec property from
the device tree (if the module timeout parameter is invalid). Best practice is
to set the default timeout value as timeout value in the watchdog_device and
then use this function to set the user "preferred" timeout value.
This routine returns zero on success and a negative errno code for failure.
...@@ -102,12 +102,15 @@ void __init ath79_register_uart(void) ...@@ -102,12 +102,15 @@ void __init ath79_register_uart(void)
} }
} }
static struct platform_device ath79_wdt_device = {
.name = "ath79-wdt",
.id = -1,
};
void __init ath79_register_wdt(void) void __init ath79_register_wdt(void)
{ {
platform_device_register(&ath79_wdt_device); struct resource res;
memset(&res, 0, sizeof(res));
res.flags = IORESOURCE_MEM;
res.start = AR71XX_RESET_BASE + AR71XX_RESET_REG_WDOG_CTRL;
res.end = res.start + 0x8 - 1;
platform_device_register_simple("ath79-wdt", -1, &res, 1);
} }
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/stmp_device.h>
#include <linux/stmp3xxx_rtc_wdt.h>
#include <mach/common.h> #include <mach/common.h>
...@@ -36,6 +38,7 @@ ...@@ -36,6 +38,7 @@
#define STMP3XXX_RTC_CTRL_ALARM_IRQ_EN 0x00000001 #define STMP3XXX_RTC_CTRL_ALARM_IRQ_EN 0x00000001
#define STMP3XXX_RTC_CTRL_ONEMSEC_IRQ_EN 0x00000002 #define STMP3XXX_RTC_CTRL_ONEMSEC_IRQ_EN 0x00000002
#define STMP3XXX_RTC_CTRL_ALARM_IRQ 0x00000004 #define STMP3XXX_RTC_CTRL_ALARM_IRQ 0x00000004
#define STMP3XXX_RTC_CTRL_WATCHDOGEN 0x00000010
#define STMP3XXX_RTC_STAT 0x10 #define STMP3XXX_RTC_STAT 0x10
#define STMP3XXX_RTC_STAT_STALE_SHIFT 16 #define STMP3XXX_RTC_STAT_STALE_SHIFT 16
...@@ -45,6 +48,8 @@ ...@@ -45,6 +48,8 @@
#define STMP3XXX_RTC_ALARM 0x40 #define STMP3XXX_RTC_ALARM 0x40
#define STMP3XXX_RTC_WATCHDOG 0x50
#define STMP3XXX_RTC_PERSISTENT0 0x60 #define STMP3XXX_RTC_PERSISTENT0 0x60
#define STMP3XXX_RTC_PERSISTENT0_SET 0x64 #define STMP3XXX_RTC_PERSISTENT0_SET 0x64
#define STMP3XXX_RTC_PERSISTENT0_CLR 0x68 #define STMP3XXX_RTC_PERSISTENT0_CLR 0x68
...@@ -52,12 +57,70 @@ ...@@ -52,12 +57,70 @@
#define STMP3XXX_RTC_PERSISTENT0_ALARM_EN 0x00000004 #define STMP3XXX_RTC_PERSISTENT0_ALARM_EN 0x00000004
#define STMP3XXX_RTC_PERSISTENT0_ALARM_WAKE 0x00000080 #define STMP3XXX_RTC_PERSISTENT0_ALARM_WAKE 0x00000080
#define STMP3XXX_RTC_PERSISTENT1 0x70
/* missing bitmask in headers */
#define STMP3XXX_RTC_PERSISTENT1_FORCE_UPDATER 0x80000000
struct stmp3xxx_rtc_data { struct stmp3xxx_rtc_data {
struct rtc_device *rtc; struct rtc_device *rtc;
void __iomem *io; void __iomem *io;
int irq_alarm; int irq_alarm;
}; };
#if IS_ENABLED(CONFIG_STMP3XXX_RTC_WATCHDOG)
/**
* stmp3xxx_wdt_set_timeout - configure the watchdog inside the STMP3xxx RTC
* @dev: the parent device of the watchdog (= the RTC)
* @timeout: the desired value for the timeout register of the watchdog.
* 0 disables the watchdog
*
* The watchdog needs one register and two bits which are in the RTC domain.
* To handle the resource conflict, the RTC driver will create another
* platform_device for the watchdog driver as a child of the RTC device.
* The watchdog driver is passed the below accessor function via platform_data
* to configure the watchdog. Locking is not needed because accessing SET/CLR
* registers is atomic.
*/
static void stmp3xxx_wdt_set_timeout(struct device *dev, u32 timeout)
{
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev);
if (timeout) {
writel(timeout, rtc_data->io + STMP3XXX_RTC_WATCHDOG);
writel(STMP3XXX_RTC_CTRL_WATCHDOGEN,
rtc_data->io + STMP3XXX_RTC_CTRL + STMP_OFFSET_REG_SET);
writel(STMP3XXX_RTC_PERSISTENT1_FORCE_UPDATER,
rtc_data->io + STMP3XXX_RTC_PERSISTENT1 + STMP_OFFSET_REG_SET);
} else {
writel(STMP3XXX_RTC_CTRL_WATCHDOGEN,
rtc_data->io + STMP3XXX_RTC_CTRL + STMP_OFFSET_REG_CLR);
writel(STMP3XXX_RTC_PERSISTENT1_FORCE_UPDATER,
rtc_data->io + STMP3XXX_RTC_PERSISTENT1 + STMP_OFFSET_REG_CLR);
}
}
static struct stmp3xxx_wdt_pdata wdt_pdata = {
.wdt_set_timeout = stmp3xxx_wdt_set_timeout,
};
static void stmp3xxx_wdt_register(struct platform_device *rtc_pdev)
{
struct platform_device *wdt_pdev =
platform_device_alloc("stmp3xxx_rtc_wdt", rtc_pdev->id);
if (wdt_pdev) {
wdt_pdev->dev.parent = &rtc_pdev->dev;
wdt_pdev->dev.platform_data = &wdt_pdata;
platform_device_add(wdt_pdev);
}
}
#else
static void stmp3xxx_wdt_register(struct platform_device *rtc_pdev)
{
}
#endif /* CONFIG_STMP3XXX_RTC_WATCHDOG */
static void stmp3xxx_wait_time(struct stmp3xxx_rtc_data *rtc_data) static void stmp3xxx_wait_time(struct stmp3xxx_rtc_data *rtc_data)
{ {
/* /*
...@@ -233,6 +296,7 @@ static int stmp3xxx_rtc_probe(struct platform_device *pdev) ...@@ -233,6 +296,7 @@ static int stmp3xxx_rtc_probe(struct platform_device *pdev)
goto out_irq_alarm; goto out_irq_alarm;
} }
stmp3xxx_wdt_register(pdev);
return 0; return 0;
out_irq_alarm: out_irq_alarm:
......
...@@ -79,6 +79,7 @@ config DA9052_WATCHDOG ...@@ -79,6 +79,7 @@ config DA9052_WATCHDOG
config DA9055_WATCHDOG config DA9055_WATCHDOG
tristate "Dialog Semiconductor DA9055 Watchdog" tristate "Dialog Semiconductor DA9055 Watchdog"
depends on MFD_DA9055 depends on MFD_DA9055
select WATCHDOG_CORE
help help
If you say yes here you get support for watchdog on the Dialog If you say yes here you get support for watchdog on the Dialog
Semiconductor DA9055 PMIC. Semiconductor DA9055 PMIC.
...@@ -108,7 +109,7 @@ config WM8350_WATCHDOG ...@@ -108,7 +109,7 @@ config WM8350_WATCHDOG
config ARM_SP805_WATCHDOG config ARM_SP805_WATCHDOG
tristate "ARM SP805 Watchdog" tristate "ARM SP805 Watchdog"
depends on ARM_AMBA depends on ARM && ARM_AMBA
select WATCHDOG_CORE select WATCHDOG_CORE
help help
ARM Primecell SP805 Watchdog timer. This will reboot your system when ARM Primecell SP805 Watchdog timer. This will reboot your system when
...@@ -116,7 +117,7 @@ config ARM_SP805_WATCHDOG ...@@ -116,7 +117,7 @@ config ARM_SP805_WATCHDOG
config AT91RM9200_WATCHDOG config AT91RM9200_WATCHDOG
tristate "AT91RM9200 watchdog" tristate "AT91RM9200 watchdog"
depends on ARCH_AT91RM9200 depends on ARCH_AT91
help help
Watchdog timer embedded into AT91RM9200 chips. This will reboot your Watchdog timer embedded into AT91RM9200 chips. This will reboot your
system when the timeout is reached. system when the timeout is reached.
...@@ -124,6 +125,7 @@ config AT91RM9200_WATCHDOG ...@@ -124,6 +125,7 @@ config AT91RM9200_WATCHDOG
config AT91SAM9X_WATCHDOG config AT91SAM9X_WATCHDOG
tristate "AT91SAM9X / AT91CAP9 watchdog" tristate "AT91SAM9X / AT91CAP9 watchdog"
depends on ARCH_AT91 && !ARCH_AT91RM9200 depends on ARCH_AT91 && !ARCH_AT91RM9200
select WATCHDOG_CORE
help help
Watchdog timer embedded into AT91SAM9X and AT91CAP9 chips. This will Watchdog timer embedded into AT91SAM9X and AT91CAP9 chips. This will
reboot your system when the timeout is reached. reboot your system when the timeout is reached.
...@@ -316,14 +318,15 @@ config TWL4030_WATCHDOG ...@@ -316,14 +318,15 @@ config TWL4030_WATCHDOG
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.
config STMP3XXX_WATCHDOG config STMP3XXX_RTC_WATCHDOG
tristate "Freescale STMP3XXX watchdog" tristate "Freescale STMP3XXX & i.MX23/28 watchdog"
depends on ARCH_STMP3XXX depends on RTC_DRV_STMP
select WATCHDOG_CORE
help help
Say Y here if to include support for the watchdog timer Say Y here to include support for the watchdog timer inside
for the Sigmatel STMP37XX/378X SoC. the RTC for the STMP37XX/378X or i.MX23/28 SoC.
To compile this driver as a module, choose M here: the To compile this driver as a module, choose M here: the
module will be called stmp3xxx_wdt. module will be called stmp3xxx_rtc_wdt.
config NUC900_WATCHDOG config NUC900_WATCHDOG
tristate "Nuvoton NUC900 watchdog" tristate "Nuvoton NUC900 watchdog"
...@@ -376,6 +379,18 @@ config UX500_WATCHDOG ...@@ -376,6 +379,18 @@ config UX500_WATCHDOG
To compile this driver as a module, choose M here: the To compile this driver as a module, choose M here: the
module will be called ux500_wdt. module will be called ux500_wdt.
config RETU_WATCHDOG
tristate "Retu watchdog"
depends on MFD_RETU
select WATCHDOG_CORE
help
Retu watchdog driver for Nokia Internet Tablets (770, N800,
N810). At least on N800 the watchdog cannot be disabled, so
this driver is essential and you should enable it.
To compile this driver as a module, choose M here: the
module will be called retu_wdt.
# AVR32 Architecture # AVR32 Architecture
config AT32AP700X_WDT config AT32AP700X_WDT
...@@ -593,7 +608,7 @@ config IE6XX_WDT ...@@ -593,7 +608,7 @@ config IE6XX_WDT
config INTEL_SCU_WATCHDOG config INTEL_SCU_WATCHDOG
bool "Intel SCU Watchdog for Mobile Platforms" bool "Intel SCU Watchdog for Mobile Platforms"
depends on X86_MRST depends on X86_INTEL_MID
---help--- ---help---
Hardware driver for the watchdog time built into the Intel SCU Hardware driver for the watchdog time built into the Intel SCU
for Intel Mobile Platforms. for Intel Mobile Platforms.
...@@ -983,6 +998,7 @@ config ATH79_WDT ...@@ -983,6 +998,7 @@ config ATH79_WDT
config BCM47XX_WDT config BCM47XX_WDT
tristate "Broadcom BCM47xx Watchdog Timer" tristate "Broadcom BCM47xx Watchdog Timer"
depends on BCM47XX depends on BCM47XX
select WATCHDOG_CORE
help help
Hardware driver for the Broadcom BCM47xx Watchdog Timer. Hardware driver for the Broadcom BCM47xx Watchdog Timer.
...@@ -1131,6 +1147,7 @@ config PIKA_WDT ...@@ -1131,6 +1147,7 @@ config PIKA_WDT
config BOOKE_WDT config BOOKE_WDT
tristate "PowerPC Book-E Watchdog Timer" tristate "PowerPC Book-E Watchdog Timer"
depends on BOOKE || 4xx depends on BOOKE || 4xx
select WATCHDOG_CORE
---help--- ---help---
Watchdog driver for PowerPC Book-E chips, such as the Freescale Watchdog driver for PowerPC Book-E chips, such as the Freescale
MPC85xx SOCs and the IBM PowerPC 440. MPC85xx SOCs and the IBM PowerPC 440.
......
...@@ -48,11 +48,12 @@ obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o ...@@ -48,11 +48,12 @@ obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o
obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o
obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o
obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o
obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o
obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o
obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o
obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o
obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o
# AVR32 Architecture # AVR32 Architecture
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
......
...@@ -24,6 +24,8 @@ ...@@ -24,6 +24,8 @@
#include <linux/types.h> #include <linux/types.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <mach/at91_st.h> #include <mach/at91_st.h>
#define WDT_DEFAULT_TIME 5 /* seconds */ #define WDT_DEFAULT_TIME 5 /* seconds */
...@@ -252,6 +254,12 @@ static int at91wdt_resume(struct platform_device *pdev) ...@@ -252,6 +254,12 @@ static int at91wdt_resume(struct platform_device *pdev)
#define at91wdt_resume NULL #define at91wdt_resume NULL
#endif #endif
static const struct of_device_id at91_wdt_dt_ids[] = {
{ .compatible = "atmel,at91rm9200-wdt" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
static struct platform_driver at91wdt_driver = { static struct platform_driver at91wdt_driver = {
.probe = at91wdt_probe, .probe = at91wdt_probe,
.remove = at91wdt_remove, .remove = at91wdt_remove,
...@@ -261,6 +269,7 @@ static struct platform_driver at91wdt_driver = { ...@@ -261,6 +269,7 @@ static struct platform_driver at91wdt_driver = {
.driver = { .driver = {
.name = "at91_wdt", .name = "at91_wdt",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.of_match_table = of_match_ptr(at91_wdt_dt_ids),
}, },
}; };
......
...@@ -18,11 +18,9 @@ ...@@ -18,11 +18,9 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
...@@ -58,7 +56,7 @@ ...@@ -58,7 +56,7 @@
/* User land timeout */ /* User land timeout */
#define WDT_HEARTBEAT 15 #define WDT_HEARTBEAT 15
static int heartbeat = WDT_HEARTBEAT; static int heartbeat;
module_param(heartbeat, int, 0); module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. " MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
"(default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
...@@ -68,19 +66,17 @@ module_param(nowayout, bool, 0); ...@@ -68,19 +66,17 @@ 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 struct watchdog_device at91_wdt_dev;
static void at91_ping(unsigned long data); static void at91_ping(unsigned long data);
static struct { static struct {
void __iomem *base; void __iomem *base;
unsigned long next_heartbeat; /* the next_heartbeat for the timer */ unsigned long next_heartbeat; /* the next_heartbeat for the timer */
unsigned long open;
char expect_close;
struct timer_list timer; /* The timer that pings the watchdog */ struct timer_list timer; /* The timer that pings the watchdog */
} at91wdt_private; } at91wdt_private;
/* ......................................................................... */ /* ......................................................................... */
/* /*
* Reload the watchdog timer. (ie, pat the watchdog) * Reload the watchdog timer. (ie, pat the watchdog)
*/ */
...@@ -95,39 +91,37 @@ static inline void at91_wdt_reset(void) ...@@ -95,39 +91,37 @@ static inline void at91_wdt_reset(void)
static void at91_ping(unsigned long data) static void at91_ping(unsigned long data)
{ {
if (time_before(jiffies, at91wdt_private.next_heartbeat) || if (time_before(jiffies, at91wdt_private.next_heartbeat) ||
(!nowayout && !at91wdt_private.open)) { (!watchdog_active(&at91_wdt_dev))) {
at91_wdt_reset(); at91_wdt_reset();
mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
} else } else
pr_crit("I will reset your machine !\n"); pr_crit("I will reset your machine !\n");
} }
/* static int at91_wdt_ping(struct watchdog_device *wdd)
* Watchdog device is opened, and watchdog starts running.
*/
static int at91_wdt_open(struct inode *inode, struct file *file)
{ {
if (test_and_set_bit(0, &at91wdt_private.open)) /* calculate when the next userspace timeout will be */
return -EBUSY; at91wdt_private.next_heartbeat = jiffies + wdd->timeout * HZ;
return 0;
}
at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; static int at91_wdt_start(struct watchdog_device *wdd)
{
/* calculate the next userspace timeout and modify the timer */
at91_wdt_ping(wdd);
mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
return 0;
return nonseekable_open(inode, file);
} }
/* static int at91_wdt_stop(struct watchdog_device *wdd)
* Close the watchdog device.
*/
static int at91_wdt_close(struct inode *inode, struct file *file)
{ {
clear_bit(0, &at91wdt_private.open); /* The watchdog timer hardware can not be stopped... */
return 0;
/* stop internal ping */ }
if (!at91wdt_private.expect_close)
del_timer(&at91wdt_private.timer);
at91wdt_private.expect_close = 0; static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
{
wdd->timeout = new_timeout;
return 0; return 0;
} }
...@@ -163,96 +157,28 @@ static int at91_wdt_settimeout(unsigned int timeout) ...@@ -163,96 +157,28 @@ static int at91_wdt_settimeout(unsigned int timeout)
return 0; return 0;
} }
/* ......................................................................... */
static const struct watchdog_info at91_wdt_info = { static const struct watchdog_info at91_wdt_info = {
.identity = DRV_NAME, .identity = DRV_NAME,
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE, WDIOF_MAGICCLOSE,
}; };
/* static const struct watchdog_ops at91_wdt_ops = {
* Handle commands from user-space. .owner = THIS_MODULE,
*/ .start = at91_wdt_start,
static long at91_wdt_ioctl(struct file *file, .stop = at91_wdt_stop,
unsigned int cmd, unsigned long arg) .ping = at91_wdt_ping,
{ .set_timeout = at91_wdt_set_timeout,
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_value;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &at91_wdt_info,
sizeof(at91_wdt_info)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_value, p))
return -EFAULT;
heartbeat = new_value;
at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
return put_user(new_value, p); /* return current value */
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p);
}
return -ENOTTY;
}
/*
* Pat the watchdog whenever device is written to.
*/
static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len,
loff_t *ppos)
{
if (!len)
return 0;
/* Scan for magic character */
if (!nowayout) {
size_t i;
at91wdt_private.expect_close = 0;
for (i = 0; i < len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V') {
at91wdt_private.expect_close = 42;
break;
}
}
}
at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ;
return len;
}
/* ......................................................................... */
static const struct file_operations at91wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = at91_wdt_ioctl,
.open = at91_wdt_open,
.release = at91_wdt_close,
.write = at91_wdt_write,
}; };
static struct miscdevice at91wdt_miscdev = { static struct watchdog_device at91_wdt_dev = {
.minor = WATCHDOG_MINOR, .info = &at91_wdt_info,
.name = "watchdog", .ops = &at91_wdt_ops,
.fops = &at91wdt_fops, .timeout = WDT_HEARTBEAT,
.min_timeout = 1,
.max_timeout = 0xFFFF,
}; };
static int __init at91wdt_probe(struct platform_device *pdev) static int __init at91wdt_probe(struct platform_device *pdev)
...@@ -260,10 +186,6 @@ static int __init at91wdt_probe(struct platform_device *pdev) ...@@ -260,10 +186,6 @@ static int __init at91wdt_probe(struct platform_device *pdev)
struct resource *r; struct resource *r;
int res; int res;
if (at91wdt_miscdev.parent)
return -EBUSY;
at91wdt_miscdev.parent = &pdev->dev;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) if (!r)
return -ENODEV; return -ENODEV;
...@@ -273,38 +195,41 @@ static int __init at91wdt_probe(struct platform_device *pdev) ...@@ -273,38 +195,41 @@ static int __init at91wdt_probe(struct platform_device *pdev)
return -ENOMEM; return -ENOMEM;
} }
at91_wdt_dev.parent = &pdev->dev;
watchdog_init_timeout(&at91_wdt_dev, heartbeat, &pdev->dev);
watchdog_set_nowayout(&at91_wdt_dev, nowayout);
/* Set watchdog */ /* Set watchdog */
res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000));
if (res) if (res)
return res; return res;
res = misc_register(&at91wdt_miscdev); res = watchdog_register_device(&at91_wdt_dev);
if (res) if (res)
return res; return res;
at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; at91wdt_private.next_heartbeat = jiffies + at91_wdt_dev.timeout * HZ;
setup_timer(&at91wdt_private.timer, at91_ping, 0); setup_timer(&at91wdt_private.timer, at91_ping, 0);
mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT);
pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n",
heartbeat, nowayout); at91_wdt_dev.timeout, nowayout);
return 0; return 0;
} }
static int __exit at91wdt_remove(struct platform_device *pdev) static int __exit at91wdt_remove(struct platform_device *pdev)
{ {
int res; watchdog_unregister_device(&at91_wdt_dev);
res = misc_deregister(&at91wdt_miscdev); pr_warn("I quit now, hardware will probably reboot!\n");
if (!res) del_timer(&at91wdt_private.timer);
at91wdt_miscdev.parent = NULL;
return res; return 0;
} }
#if defined(CONFIG_OF) #if defined(CONFIG_OF)
static const struct of_device_id at91_wdt_dt_ids[] __initconst = { static const struct of_device_id at91_wdt_dt_ids[] = {
{ .compatible = "atmel,at91sam9260-wdt" }, { .compatible = "atmel,at91sam9260-wdt" },
{ /* sentinel */ } { /* sentinel */ }
}; };
...@@ -326,4 +251,3 @@ module_platform_driver_probe(at91wdt_driver, at91wdt_probe); ...@@ -326,4 +251,3 @@ module_platform_driver_probe(at91wdt_driver, at91wdt_probe);
MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/miscdevice.h> #include <linux/miscdevice.h>
#include <linux/module.h> #include <linux/module.h>
...@@ -32,14 +33,16 @@ ...@@ -32,14 +33,16 @@
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/of.h>
#include <asm/mach-ath79/ath79.h> #include <linux/of_platform.h>
#include <asm/mach-ath79/ar71xx_regs.h>
#define DRIVER_NAME "ath79-wdt" #define DRIVER_NAME "ath79-wdt"
#define WDT_TIMEOUT 15 /* seconds */ #define WDT_TIMEOUT 15 /* seconds */
#define WDOG_REG_CTRL 0x00
#define WDOG_REG_TIMER 0x04
#define WDOG_CTRL_LAST_RESET BIT(31) #define WDOG_CTRL_LAST_RESET BIT(31)
#define WDOG_CTRL_ACTION_MASK 3 #define WDOG_CTRL_ACTION_MASK 3
#define WDOG_CTRL_ACTION_NONE 0 /* no action */ #define WDOG_CTRL_ACTION_NONE 0 /* no action */
...@@ -66,27 +69,38 @@ static struct clk *wdt_clk; ...@@ -66,27 +69,38 @@ static struct clk *wdt_clk;
static unsigned long wdt_freq; static unsigned long wdt_freq;
static int boot_status; static int boot_status;
static int max_timeout; static int max_timeout;
static void __iomem *wdt_base;
static inline void ath79_wdt_wr(unsigned reg, u32 val)
{
iowrite32(val, wdt_base + reg);
}
static inline u32 ath79_wdt_rr(unsigned reg)
{
return ioread32(wdt_base + reg);
}
static inline void ath79_wdt_keepalive(void) static inline void ath79_wdt_keepalive(void)
{ {
ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout); ath79_wdt_wr(WDOG_REG_TIMER, wdt_freq * timeout);
/* flush write */ /* flush write */
ath79_reset_rr(AR71XX_RESET_REG_WDOG); ath79_wdt_rr(WDOG_REG_TIMER);
} }
static inline void ath79_wdt_enable(void) static inline void ath79_wdt_enable(void)
{ {
ath79_wdt_keepalive(); ath79_wdt_keepalive();
ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR); ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_FCR);
/* flush write */ /* flush write */
ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); ath79_wdt_rr(WDOG_REG_CTRL);
} }
static inline void ath79_wdt_disable(void) static inline void ath79_wdt_disable(void)
{ {
ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE); ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_NONE);
/* flush write */ /* flush write */
ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); ath79_wdt_rr(WDOG_REG_CTRL);
} }
static int ath79_wdt_set_timeout(int val) static int ath79_wdt_set_timeout(int val)
...@@ -226,16 +240,32 @@ static struct miscdevice ath79_wdt_miscdev = { ...@@ -226,16 +240,32 @@ static struct miscdevice ath79_wdt_miscdev = {
static int ath79_wdt_probe(struct platform_device *pdev) static int ath79_wdt_probe(struct platform_device *pdev)
{ {
struct resource *res;
u32 ctrl; u32 ctrl;
int err; int err;
wdt_clk = clk_get(&pdev->dev, "wdt"); if (wdt_base)
return -EBUSY;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resource found\n");
return -EINVAL;
}
wdt_base = devm_request_and_ioremap(&pdev->dev, res);
if (!wdt_base) {
dev_err(&pdev->dev, "unable to remap memory region\n");
return -ENOMEM;
}
wdt_clk = devm_clk_get(&pdev->dev, "wdt");
if (IS_ERR(wdt_clk)) if (IS_ERR(wdt_clk))
return PTR_ERR(wdt_clk); return PTR_ERR(wdt_clk);
err = clk_enable(wdt_clk); err = clk_enable(wdt_clk);
if (err) if (err)
goto err_clk_put; return err;
wdt_freq = clk_get_rate(wdt_clk); wdt_freq = clk_get_rate(wdt_clk);
if (!wdt_freq) { if (!wdt_freq) {
...@@ -251,7 +281,7 @@ static int ath79_wdt_probe(struct platform_device *pdev) ...@@ -251,7 +281,7 @@ static int ath79_wdt_probe(struct platform_device *pdev)
max_timeout, timeout); max_timeout, timeout);
} }
ctrl = ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); ctrl = ath79_wdt_rr(WDOG_REG_CTRL);
boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0; boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0;
err = misc_register(&ath79_wdt_miscdev); err = misc_register(&ath79_wdt_miscdev);
...@@ -265,8 +295,6 @@ static int ath79_wdt_probe(struct platform_device *pdev) ...@@ -265,8 +295,6 @@ static int ath79_wdt_probe(struct platform_device *pdev)
err_clk_disable: err_clk_disable:
clk_disable(wdt_clk); clk_disable(wdt_clk);
err_clk_put:
clk_put(wdt_clk);
return err; return err;
} }
...@@ -274,7 +302,6 @@ static int ath79_wdt_remove(struct platform_device *pdev) ...@@ -274,7 +302,6 @@ static int ath79_wdt_remove(struct platform_device *pdev)
{ {
misc_deregister(&ath79_wdt_miscdev); misc_deregister(&ath79_wdt_miscdev);
clk_disable(wdt_clk); clk_disable(wdt_clk);
clk_put(wdt_clk);
return 0; return 0;
} }
...@@ -283,6 +310,14 @@ static void ath97_wdt_shutdown(struct platform_device *pdev) ...@@ -283,6 +310,14 @@ static void ath97_wdt_shutdown(struct platform_device *pdev)
ath79_wdt_disable(); ath79_wdt_disable();
} }
#ifdef CONFIG_OF
static const struct of_device_id ath79_wdt_match[] = {
{ .compatible = "qca,ar7130-wdt" },
{},
};
MODULE_DEVICE_TABLE(of, ath79_wdt_match);
#endif
static struct platform_driver ath79_wdt_driver = { static struct platform_driver ath79_wdt_driver = {
.probe = ath79_wdt_probe, .probe = ath79_wdt_probe,
.remove = ath79_wdt_remove, .remove = ath79_wdt_remove,
...@@ -290,6 +325,7 @@ static struct platform_driver ath79_wdt_driver = { ...@@ -290,6 +325,7 @@ static struct platform_driver ath79_wdt_driver = {
.driver = { .driver = {
.name = DRIVER_NAME, .name = DRIVER_NAME,
.owner = THIS_MODULE, .owner = THIS_MODULE,
.of_match_table = of_match_ptr(ath79_wdt_match),
}, },
}; };
......
This diff is collapsed.
...@@ -15,12 +15,8 @@ ...@@ -15,12 +15,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h> #include <linux/module.h>
#include <linux/fs.h>
#include <linux/smp.h> #include <linux/smp.h>
#include <linux/miscdevice.h>
#include <linux/notifier.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <linux/uaccess.h>
#include <asm/reg_booke.h> #include <asm/reg_booke.h>
#include <asm/time.h> #include <asm/time.h>
...@@ -45,7 +41,7 @@ u32 booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT; ...@@ -45,7 +41,7 @@ u32 booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT;
#define WDTP_MASK (TCR_WP_MASK) #define WDTP_MASK (TCR_WP_MASK)
#endif #endif
static DEFINE_SPINLOCK(booke_wdt_lock); #ifdef CONFIG_PPC_FSL_BOOK3E
/* For the specified period, determine the number of seconds /* For the specified period, determine the number of seconds
* corresponding to the reset time. There will be a watchdog * corresponding to the reset time. There will be a watchdog
...@@ -86,6 +82,24 @@ static unsigned int sec_to_period(unsigned int secs) ...@@ -86,6 +82,24 @@ static unsigned int sec_to_period(unsigned int secs)
return 0; return 0;
} }
#define MAX_WDT_TIMEOUT period_to_sec(1)
#else /* CONFIG_PPC_FSL_BOOK3E */
static unsigned long long period_to_sec(unsigned int period)
{
return period;
}
static unsigned int sec_to_period(unsigned int secs)
{
return secs;
}
#define MAX_WDT_TIMEOUT 3 /* from Kconfig */
#endif /* !CONFIG_PPC_FSL_BOOK3E */
static void __booke_wdt_set(void *data) static void __booke_wdt_set(void *data)
{ {
u32 val; u32 val;
...@@ -107,9 +121,11 @@ static void __booke_wdt_ping(void *data) ...@@ -107,9 +121,11 @@ static void __booke_wdt_ping(void *data)
mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); mtspr(SPRN_TSR, TSR_ENW|TSR_WIS);
} }
static void booke_wdt_ping(void) static int booke_wdt_ping(struct watchdog_device *wdog)
{ {
on_each_cpu(__booke_wdt_ping, NULL, 0); on_each_cpu(__booke_wdt_ping, NULL, 0);
return 0;
} }
static void __booke_wdt_enable(void *data) static void __booke_wdt_enable(void *data)
...@@ -146,152 +162,81 @@ static void __booke_wdt_disable(void *data) ...@@ -146,152 +162,81 @@ static void __booke_wdt_disable(void *data)
} }
static ssize_t booke_wdt_write(struct file *file, const char __user *buf, static void __booke_wdt_start(struct watchdog_device *wdog)
size_t count, loff_t *ppos)
{ {
booke_wdt_ping(); on_each_cpu(__booke_wdt_enable, NULL, 0);
return count; pr_debug("watchdog enabled (timeout = %u sec)\n", wdog->timeout);
} }
static struct watchdog_info ident = { static int booke_wdt_start(struct watchdog_device *wdog)
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "PowerPC Book-E Watchdog",
};
static long booke_wdt_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{ {
u32 tmp = 0;
u32 __user *p = (u32 __user *)arg;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(p, &ident, sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
return put_user(0, p);
case WDIOC_GETBOOTSTATUS:
/* XXX: something is clearing TSR */
tmp = mfspr(SPRN_TSR) & TSR_WRS(3);
/* returns CARDRESET if last reset was caused by the WDT */
return put_user((tmp ? WDIOF_CARDRESET : 0), p);
case WDIOC_SETOPTIONS:
if (get_user(tmp, p))
return -EFAULT;
if (tmp == WDIOS_ENABLECARD) {
booke_wdt_ping();
break;
} else
return -EINVAL;
return 0;
case WDIOC_KEEPALIVE:
booke_wdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(tmp, p))
return -EFAULT;
#ifdef CONFIG_PPC_FSL_BOOK3E
/* period of 1 gives the largest possible timeout */
if (tmp > period_to_sec(1))
return -EINVAL;
booke_wdt_period = sec_to_period(tmp);
#else
booke_wdt_period = tmp;
#endif
booke_wdt_set();
/* Fall */
case WDIOC_GETTIMEOUT:
#ifdef CONFIG_FSL_BOOKE
return put_user(period_to_sec(booke_wdt_period), p);
#else
return put_user(booke_wdt_period, p);
#endif
default:
return -ENOTTY;
}
return 0;
}
/* wdt_is_active stores whether or not the /dev/watchdog device is opened */
static unsigned long wdt_is_active;
static int booke_wdt_open(struct inode *inode, struct file *file)
{
/* /dev/watchdog can only be opened once */
if (test_and_set_bit(0, &wdt_is_active))
return -EBUSY;
spin_lock(&booke_wdt_lock);
if (booke_wdt_enabled == 0) { if (booke_wdt_enabled == 0) {
booke_wdt_enabled = 1; booke_wdt_enabled = 1;
on_each_cpu(__booke_wdt_enable, NULL, 0); __booke_wdt_start(wdog);
pr_debug("watchdog enabled (timeout = %llu sec)\n",
period_to_sec(booke_wdt_period));
} }
spin_unlock(&booke_wdt_lock); return 0;
return nonseekable_open(inode, file);
} }
static int booke_wdt_release(struct inode *inode, struct file *file) static int booke_wdt_stop(struct watchdog_device *wdog)
{ {
#ifndef CONFIG_WATCHDOG_NOWAYOUT
/* Normally, the watchdog is disabled when /dev/watchdog is closed, but
* if CONFIG_WATCHDOG_NOWAYOUT is defined, then it means that the
* watchdog should remain enabled. So we disable it only if
* CONFIG_WATCHDOG_NOWAYOUT is not defined.
*/
on_each_cpu(__booke_wdt_disable, NULL, 0); on_each_cpu(__booke_wdt_disable, NULL, 0);
booke_wdt_enabled = 0; booke_wdt_enabled = 0;
pr_debug("watchdog disabled\n"); pr_debug("watchdog disabled\n");
#endif
clear_bit(0, &wdt_is_active); return 0;
}
static int booke_wdt_set_timeout(struct watchdog_device *wdt_dev,
unsigned int timeout)
{
if (timeout > MAX_WDT_TIMEOUT)
return -EINVAL;
booke_wdt_period = sec_to_period(timeout);
wdt_dev->timeout = timeout;
booke_wdt_set();
return 0; return 0;
} }
static const struct file_operations booke_wdt_fops = { static struct watchdog_info booke_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "PowerPC Book-E Watchdog",
};
static struct watchdog_ops booke_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.llseek = no_llseek, .start = booke_wdt_start,
.write = booke_wdt_write, .stop = booke_wdt_stop,
.unlocked_ioctl = booke_wdt_ioctl, .ping = booke_wdt_ping,
.open = booke_wdt_open, .set_timeout = booke_wdt_set_timeout,
.release = booke_wdt_release,
}; };
static struct miscdevice booke_wdt_miscdev = { static struct watchdog_device booke_wdt_dev = {
.minor = WATCHDOG_MINOR, .info = &booke_wdt_info,
.name = "watchdog", .ops = &booke_wdt_ops,
.fops = &booke_wdt_fops, .min_timeout = 1,
.max_timeout = 0xFFFF
}; };
static void __exit booke_wdt_exit(void) static void __exit booke_wdt_exit(void)
{ {
misc_deregister(&booke_wdt_miscdev); watchdog_unregister_device(&booke_wdt_dev);
} }
static int __init booke_wdt_init(void) static int __init booke_wdt_init(void)
{ {
int ret = 0; int ret = 0;
bool nowayout = WATCHDOG_NOWAYOUT;
pr_info("powerpc book-e watchdog driver loaded\n"); pr_info("powerpc book-e watchdog driver loaded\n");
ident.firmware_version = cur_cpu_spec->pvr_value; booke_wdt_info.firmware_version = cur_cpu_spec->pvr_value;
booke_wdt_set_timeout(&booke_wdt_dev,
ret = misc_register(&booke_wdt_miscdev); period_to_sec(CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT));
if (ret) { watchdog_set_nowayout(&booke_wdt_dev, nowayout);
pr_err("cannot register device (minor=%u, ret=%i)\n", if (booke_wdt_enabled)
WATCHDOG_MINOR, ret); __booke_wdt_start(&booke_wdt_dev);
return ret;
} ret = watchdog_register_device(&booke_wdt_dev);
spin_lock(&booke_wdt_lock);
if (booke_wdt_enabled == 1) {
pr_info("watchdog enabled (timeout = %llu sec)\n",
period_to_sec(booke_wdt_period));
on_each_cpu(__booke_wdt_enable, NULL, 0);
}
spin_unlock(&booke_wdt_lock);
return ret; return ret;
} }
......
...@@ -69,7 +69,6 @@ static unsigned long wdt_status; ...@@ -69,7 +69,6 @@ static unsigned long wdt_status;
#define WDT_REGION_INITED 2 #define WDT_REGION_INITED 2
#define WDT_DEVICE_INITED 3 #define WDT_DEVICE_INITED 3
static struct resource *wdt_mem;
static void __iomem *wdt_base; static void __iomem *wdt_base;
struct clk *wdt_clk; struct clk *wdt_clk;
...@@ -201,10 +200,11 @@ static struct miscdevice davinci_wdt_miscdev = { ...@@ -201,10 +200,11 @@ static struct miscdevice davinci_wdt_miscdev = {
static int davinci_wdt_probe(struct platform_device *pdev) static int davinci_wdt_probe(struct platform_device *pdev)
{ {
int ret = 0, size; int ret = 0;
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct resource *wdt_mem;
wdt_clk = clk_get(dev, NULL); wdt_clk = devm_clk_get(dev, NULL);
if (WARN_ON(IS_ERR(wdt_clk))) if (WARN_ON(IS_ERR(wdt_clk)))
return PTR_ERR(wdt_clk); return PTR_ERR(wdt_clk);
...@@ -221,43 +221,26 @@ static int davinci_wdt_probe(struct platform_device *pdev) ...@@ -221,43 +221,26 @@ static int davinci_wdt_probe(struct platform_device *pdev)
return -ENOENT; return -ENOENT;
} }
size = resource_size(wdt_mem); wdt_base = devm_request_and_ioremap(dev, wdt_mem);
if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
dev_err(dev, "failed to get memory region\n");
return -ENOENT;
}
wdt_base = ioremap(wdt_mem->start, size);
if (!wdt_base) { if (!wdt_base) {
dev_err(dev, "failed to map memory region\n"); dev_err(dev, "ioremap failed\n");
release_mem_region(wdt_mem->start, size); return -EADDRNOTAVAIL;
wdt_mem = NULL;
return -ENOMEM;
} }
ret = misc_register(&davinci_wdt_miscdev); ret = misc_register(&davinci_wdt_miscdev);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "cannot register misc device\n"); dev_err(dev, "cannot register misc device\n");
release_mem_region(wdt_mem->start, size);
wdt_mem = NULL;
} else { } else {
set_bit(WDT_DEVICE_INITED, &wdt_status); set_bit(WDT_DEVICE_INITED, &wdt_status);
} }
iounmap(wdt_base);
return ret; return ret;
} }
static int davinci_wdt_remove(struct platform_device *pdev) static int davinci_wdt_remove(struct platform_device *pdev)
{ {
misc_deregister(&davinci_wdt_miscdev); misc_deregister(&davinci_wdt_miscdev);
if (wdt_mem) {
release_mem_region(wdt_mem->start, resource_size(wdt_mem));
wdt_mem = NULL;
}
clk_disable_unprepare(wdt_clk); clk_disable_unprepare(wdt_clk);
clk_put(wdt_clk);
return 0; return 0;
} }
......
...@@ -310,6 +310,7 @@ static struct platform_driver gef_wdt_driver = { ...@@ -310,6 +310,7 @@ static struct platform_driver gef_wdt_driver = {
.of_match_table = gef_wdt_ids, .of_match_table = gef_wdt_ids,
}, },
.probe = gef_wdt_probe, .probe = gef_wdt_probe,
.remove = gef_wdt_remove,
}; };
static int __init gef_wdt_init(void) static int __init gef_wdt_init(void)
......
...@@ -45,6 +45,11 @@ ...@@ -45,6 +45,11 @@
#include "omap_wdt.h" #include "omap_wdt.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) ")");
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)");
...@@ -201,7 +206,6 @@ static const struct watchdog_ops omap_wdt_ops = { ...@@ -201,7 +206,6 @@ static const struct watchdog_ops omap_wdt_ops = {
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; struct omap_wd_timer_platform_data *pdata = pdev->dev.platform_data;
bool nowayout = WATCHDOG_NOWAYOUT;
struct watchdog_device *omap_wdt; struct watchdog_device *omap_wdt;
struct resource *res, *mem; struct resource *res, *mem;
struct omap_wdt_dev *wdev; struct omap_wdt_dev *wdev;
......
...@@ -140,6 +140,7 @@ static const struct watchdog_ops orion_wdt_ops = { ...@@ -140,6 +140,7 @@ static const struct watchdog_ops orion_wdt_ops = {
static struct watchdog_device orion_wdt = { static struct watchdog_device orion_wdt = {
.info = &orion_wdt_info, .info = &orion_wdt_info,
.ops = &orion_wdt_ops, .ops = &orion_wdt_ops,
.min_timeout = 1,
}; };
static int orion_wdt_probe(struct platform_device *pdev) static int orion_wdt_probe(struct platform_device *pdev)
...@@ -164,12 +165,9 @@ static int orion_wdt_probe(struct platform_device *pdev) ...@@ -164,12 +165,9 @@ static int orion_wdt_probe(struct platform_device *pdev)
wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk;
if ((heartbeat < 1) || (heartbeat > wdt_max_duration)) orion_wdt.timeout = wdt_max_duration;
heartbeat = wdt_max_duration;
orion_wdt.timeout = heartbeat;
orion_wdt.min_timeout = 1;
orion_wdt.max_timeout = wdt_max_duration; orion_wdt.max_timeout = wdt_max_duration;
watchdog_init_timeout(&orion_wdt, heartbeat, &pdev->dev);
watchdog_set_nowayout(&orion_wdt, nowayout); watchdog_set_nowayout(&orion_wdt, nowayout);
ret = watchdog_register_device(&orion_wdt); ret = watchdog_register_device(&orion_wdt);
...@@ -179,7 +177,7 @@ static int orion_wdt_probe(struct platform_device *pdev) ...@@ -179,7 +177,7 @@ static int orion_wdt_probe(struct platform_device *pdev)
} }
pr_info("Initial timeout %d sec%s\n", pr_info("Initial timeout %d sec%s\n",
heartbeat, nowayout ? ", nowayout" : ""); orion_wdt.timeout, nowayout ? ", nowayout" : "");
return 0; return 0;
} }
...@@ -225,4 +223,5 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" ...@@ -225,4 +223,5 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:orion_wdt");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
...@@ -142,6 +142,7 @@ static const struct watchdog_ops pnx4008_wdt_ops = { ...@@ -142,6 +142,7 @@ static const struct watchdog_ops pnx4008_wdt_ops = {
static struct watchdog_device pnx4008_wdd = { static struct watchdog_device pnx4008_wdd = {
.info = &pnx4008_wdt_ident, .info = &pnx4008_wdt_ident,
.ops = &pnx4008_wdt_ops, .ops = &pnx4008_wdt_ops,
.timeout = DEFAULT_HEARTBEAT,
.min_timeout = 1, .min_timeout = 1,
.max_timeout = MAX_HEARTBEAT, .max_timeout = MAX_HEARTBEAT,
}; };
...@@ -151,8 +152,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev) ...@@ -151,8 +152,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
struct resource *r; struct resource *r;
int ret = 0; int ret = 0;
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) watchdog_init_timeout(&pnx4008_wdd, heartbeat, &pdev->dev);
heartbeat = DEFAULT_HEARTBEAT;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
wdt_base = devm_ioremap_resource(&pdev->dev, r); wdt_base = devm_ioremap_resource(&pdev->dev, r);
...@@ -167,7 +167,6 @@ static int pnx4008_wdt_probe(struct platform_device *pdev) ...@@ -167,7 +167,6 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
if (ret) if (ret)
goto out; goto out;
pnx4008_wdd.timeout = heartbeat;
pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ?
WDIOF_CARDRESET : 0; WDIOF_CARDRESET : 0;
watchdog_set_nowayout(&pnx4008_wdd, nowayout); watchdog_set_nowayout(&pnx4008_wdd, nowayout);
...@@ -181,7 +180,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev) ...@@ -181,7 +180,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
} }
dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n",
heartbeat); pnx4008_wdd.timeout);
return 0; return 0;
......
/*
* Retu watchdog driver
*
* Copyright (C) 2004, 2005 Nokia Corporation
*
* Based on code written by Amit Kucheria and Michael Buesch.
* Rewritten by Aaro Koskinen.
*
* This file is subject to the terms and conditions of the GNU General
* Public License. See the file "COPYING" in the main directory of this
* archive for more details.
*
* 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.
*/
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mfd/retu.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
/* Watchdog timer values in seconds */
#define RETU_WDT_MAX_TIMER 63
struct retu_wdt_dev {
struct retu_dev *rdev;
struct device *dev;
struct delayed_work ping_work;
};
/*
* Since Retu watchdog cannot be disabled in hardware, we must kick it
* with a timer until userspace watchdog software takes over. If
* CONFIG_WATCHDOG_NOWAYOUT is set, we never start the feeding.
*/
static void retu_wdt_ping_enable(struct retu_wdt_dev *wdev)
{
retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER);
schedule_delayed_work(&wdev->ping_work,
round_jiffies_relative(RETU_WDT_MAX_TIMER * HZ / 2));
}
static void retu_wdt_ping_disable(struct retu_wdt_dev *wdev)
{
retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER);
cancel_delayed_work_sync(&wdev->ping_work);
}
static void retu_wdt_ping_work(struct work_struct *work)
{
struct retu_wdt_dev *wdev = container_of(to_delayed_work(work),
struct retu_wdt_dev, ping_work);
retu_wdt_ping_enable(wdev);
}
static int retu_wdt_start(struct watchdog_device *wdog)
{
struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
retu_wdt_ping_disable(wdev);
return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
}
static int retu_wdt_stop(struct watchdog_device *wdog)
{
struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
retu_wdt_ping_enable(wdev);
return 0;
}
static int retu_wdt_ping(struct watchdog_device *wdog)
{
struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
}
static int retu_wdt_set_timeout(struct watchdog_device *wdog,
unsigned int timeout)
{
struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
wdog->timeout = timeout;
return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
}
static const struct watchdog_info retu_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "Retu watchdog",
};
static const struct watchdog_ops retu_wdt_ops = {
.owner = THIS_MODULE,
.start = retu_wdt_start,
.stop = retu_wdt_stop,
.ping = retu_wdt_ping,
.set_timeout = retu_wdt_set_timeout,
};
static int retu_wdt_probe(struct platform_device *pdev)
{
struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
bool nowayout = WATCHDOG_NOWAYOUT;
struct watchdog_device *retu_wdt;
struct retu_wdt_dev *wdev;
int ret;
retu_wdt = devm_kzalloc(&pdev->dev, sizeof(*retu_wdt), GFP_KERNEL);
if (!retu_wdt)
return -ENOMEM;
wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
if (!wdev)
return -ENOMEM;
retu_wdt->info = &retu_wdt_info;
retu_wdt->ops = &retu_wdt_ops;
retu_wdt->timeout = RETU_WDT_MAX_TIMER;
retu_wdt->min_timeout = 0;
retu_wdt->max_timeout = RETU_WDT_MAX_TIMER;
watchdog_set_drvdata(retu_wdt, wdev);
watchdog_set_nowayout(retu_wdt, nowayout);
wdev->rdev = rdev;
wdev->dev = &pdev->dev;
INIT_DELAYED_WORK(&wdev->ping_work, retu_wdt_ping_work);
ret = watchdog_register_device(retu_wdt);
if (ret < 0)
return ret;
if (nowayout)
retu_wdt_ping(retu_wdt);
else
retu_wdt_ping_enable(wdev);
platform_set_drvdata(pdev, retu_wdt);
return 0;
}
static int retu_wdt_remove(struct platform_device *pdev)
{
struct watchdog_device *wdog = platform_get_drvdata(pdev);
struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
watchdog_unregister_device(wdog);
cancel_delayed_work_sync(&wdev->ping_work);
return 0;
}
static struct platform_driver retu_wdt_driver = {
.probe = retu_wdt_probe,
.remove = retu_wdt_remove,
.driver = {
.name = "retu-wdt",
},
};
module_platform_driver(retu_wdt_driver);
MODULE_ALIAS("platform:retu-wdt");
MODULE_DESCRIPTION("Retu watchdog");
MODULE_AUTHOR("Amit Kucheria");
MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
MODULE_LICENSE("GPL");
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
static bool nowayout = WATCHDOG_NOWAYOUT; static bool nowayout = WATCHDOG_NOWAYOUT;
static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; static int tmr_margin;
static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;
static int soft_noboot; static int soft_noboot;
static int debug; static int debug;
...@@ -226,6 +226,7 @@ static struct watchdog_ops s3c2410wdt_ops = { ...@@ -226,6 +226,7 @@ static struct watchdog_ops s3c2410wdt_ops = {
static struct watchdog_device s3c2410_wdd = { static struct watchdog_device s3c2410_wdd = {
.info = &s3c2410_wdt_ident, .info = &s3c2410_wdt_ident,
.ops = &s3c2410wdt_ops, .ops = &s3c2410wdt_ops,
.timeout = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME,
}; };
/* interrupt handler code */ /* interrupt handler code */
...@@ -309,7 +310,6 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -309,7 +310,6 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
unsigned int wtcon; unsigned int wtcon;
int started = 0; int started = 0;
int ret; int ret;
int size;
DBG("%s: probe=%p\n", __func__, pdev); DBG("%s: probe=%p\n", __func__, pdev);
...@@ -330,28 +330,20 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -330,28 +330,20 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
} }
/* get the memory region for the watchdog timer */ /* get the memory region for the watchdog timer */
wdt_base = devm_request_and_ioremap(dev, wdt_mem);
size = resource_size(wdt_mem);
if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
dev_err(dev, "failed to get memory region\n");
ret = -EBUSY;
goto err;
}
wdt_base = ioremap(wdt_mem->start, size);
if (wdt_base == NULL) { if (wdt_base == NULL) {
dev_err(dev, "failed to ioremap() region\n"); dev_err(dev, "failed to devm_request_and_ioremap() region\n");
ret = -EINVAL; ret = -ENOMEM;
goto err_req; goto err;
} }
DBG("probe: mapped wdt_base=%p\n", wdt_base); DBG("probe: mapped wdt_base=%p\n", wdt_base);
wdt_clock = clk_get(&pdev->dev, "watchdog"); wdt_clock = devm_clk_get(dev, "watchdog");
if (IS_ERR(wdt_clock)) { if (IS_ERR(wdt_clock)) {
dev_err(dev, "failed to find watchdog clock source\n"); dev_err(dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock); ret = PTR_ERR(wdt_clock);
goto err_map; goto err;
} }
clk_prepare_enable(wdt_clock); clk_prepare_enable(wdt_clock);
...@@ -365,7 +357,8 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -365,7 +357,8 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
/* see if we can actually set the requested timer margin, and if /* see if we can actually set the requested timer margin, and if
* not, try the default value */ * not, try the default value */
if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, tmr_margin)) { watchdog_init_timeout(&s3c2410_wdd, tmr_margin, &pdev->dev);
if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout)) {
started = s3c2410wdt_set_heartbeat(&s3c2410_wdd, started = s3c2410wdt_set_heartbeat(&s3c2410_wdd,
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
...@@ -378,7 +371,8 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -378,7 +371,8 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
"cannot start\n"); "cannot start\n");
} }
ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0,
pdev->name, pdev);
if (ret != 0) { if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret); dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_cpufreq; goto err_cpufreq;
...@@ -389,7 +383,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -389,7 +383,7 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
ret = watchdog_register_device(&s3c2410_wdd); ret = watchdog_register_device(&s3c2410_wdd);
if (ret) { if (ret) {
dev_err(dev, "cannot register watchdog (%d)\n", ret); dev_err(dev, "cannot register watchdog (%d)\n", ret);
goto err_irq; goto err_cpufreq;
} }
if (tmr_atboot && started == 0) { if (tmr_atboot && started == 0) {
...@@ -414,23 +408,13 @@ static int s3c2410wdt_probe(struct platform_device *pdev) ...@@ -414,23 +408,13 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
return 0; return 0;
err_irq:
free_irq(wdt_irq->start, pdev);
err_cpufreq: err_cpufreq:
s3c2410wdt_cpufreq_deregister(); s3c2410wdt_cpufreq_deregister();
err_clk: err_clk:
clk_disable_unprepare(wdt_clock); clk_disable_unprepare(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL; wdt_clock = NULL;
err_map:
iounmap(wdt_base);
err_req:
release_mem_region(wdt_mem->start, size);
err: err:
wdt_irq = NULL; wdt_irq = NULL;
wdt_mem = NULL; wdt_mem = NULL;
...@@ -441,17 +425,11 @@ static int s3c2410wdt_remove(struct platform_device *dev) ...@@ -441,17 +425,11 @@ static int s3c2410wdt_remove(struct platform_device *dev)
{ {
watchdog_unregister_device(&s3c2410_wdd); watchdog_unregister_device(&s3c2410_wdd);
free_irq(wdt_irq->start, dev);
s3c2410wdt_cpufreq_deregister(); s3c2410wdt_cpufreq_deregister();
clk_disable_unprepare(wdt_clock); clk_disable_unprepare(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL; wdt_clock = NULL;
iounmap(wdt_base);
release_mem_region(wdt_mem->start, resource_size(wdt_mem));
wdt_irq = NULL; wdt_irq = NULL;
wdt_mem = NULL; wdt_mem = NULL;
return 0; return 0;
......
...@@ -361,7 +361,7 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -361,7 +361,7 @@ static unsigned char sp5100_tco_setupdevice(void)
{ {
struct pci_dev *dev = NULL; struct pci_dev *dev = NULL;
const char *dev_name = NULL; const char *dev_name = NULL;
u32 val; u32 val, tmp_val;
u32 index_reg, data_reg, base_addr; u32 index_reg, data_reg, base_addr;
/* Match the PCI device */ /* Match the PCI device */
...@@ -497,30 +497,19 @@ static unsigned char sp5100_tco_setupdevice(void) ...@@ -497,30 +497,19 @@ static unsigned char sp5100_tco_setupdevice(void)
pr_debug("Got 0x%04x from resource tree\n", val); pr_debug("Got 0x%04x from resource tree\n", val);
} }
/* Restore to the low three bits, if chipset is SB8x0(or later) */ /* Restore to the low three bits */
if (sp5100_tco_pci->revision >= 0x40) { outb(base_addr+0, index_reg);
u8 reserved_bit; tmp_val = val | (inb(data_reg) & 0x7);
reserved_bit = inb(base_addr) & 0x7;
val |= (u32)reserved_bit;
}
/* Re-programming the watchdog timer base address */ /* Re-programming the watchdog timer base address */
outb(base_addr+0, index_reg); outb(base_addr+0, index_reg);
/* Low three bits of BASE are reserved */ outb((tmp_val >> 0) & 0xff, data_reg);
outb((val >> 0) & 0xf8, data_reg);
outb(base_addr+1, index_reg); outb(base_addr+1, index_reg);
outb((val >> 8) & 0xff, data_reg); outb((tmp_val >> 8) & 0xff, data_reg);
outb(base_addr+2, index_reg); outb(base_addr+2, index_reg);
outb((val >> 16) & 0xff, data_reg); outb((tmp_val >> 16) & 0xff, data_reg);
outb(base_addr+3, index_reg); outb(base_addr+3, index_reg);
outb((val >> 24) & 0xff, data_reg); outb((tmp_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, if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
dev_name)) { dev_name)) {
......
/*
* Watchdog driver for the RTC based watchdog in STMP3xxx and i.MX23/28
*
* Author: Wolfram Sang <w.sang@pengutronix.de>
*
* Copyright (C) 2011-12 Wolfram Sang, Pengutronix
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
#include <linux/stmp3xxx_rtc_wdt.h>
#define WDOG_TICK_RATE 1000 /* 1 kHz clock */
#define STMP3XXX_DEFAULT_TIMEOUT 19
#define STMP3XXX_MAX_TIMEOUT (UINT_MAX / WDOG_TICK_RATE)
static int heartbeat = STMP3XXX_DEFAULT_TIMEOUT;
module_param(heartbeat, uint, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds from 1 to "
__MODULE_STRING(STMP3XXX_MAX_TIMEOUT) ", default "
__MODULE_STRING(STMP3XXX_DEFAULT_TIMEOUT));
static int wdt_start(struct watchdog_device *wdd)
{
struct device *dev = watchdog_get_drvdata(wdd);
struct stmp3xxx_wdt_pdata *pdata = dev->platform_data;
pdata->wdt_set_timeout(dev->parent, wdd->timeout * WDOG_TICK_RATE);
return 0;
}
static int wdt_stop(struct watchdog_device *wdd)
{
struct device *dev = watchdog_get_drvdata(wdd);
struct stmp3xxx_wdt_pdata *pdata = dev->platform_data;
pdata->wdt_set_timeout(dev->parent, 0);
return 0;
}
static int wdt_set_timeout(struct watchdog_device *wdd, unsigned new_timeout)
{
wdd->timeout = new_timeout;
return wdt_start(wdd);
}
static const struct watchdog_info stmp3xxx_wdt_ident = {
.options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "STMP3XXX RTC Watchdog",
};
static const struct watchdog_ops stmp3xxx_wdt_ops = {
.owner = THIS_MODULE,
.start = wdt_start,
.stop = wdt_stop,
.set_timeout = wdt_set_timeout,
};
static struct watchdog_device stmp3xxx_wdd = {
.info = &stmp3xxx_wdt_ident,
.ops = &stmp3xxx_wdt_ops,
.min_timeout = 1,
.max_timeout = STMP3XXX_MAX_TIMEOUT,
.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
};
static int stmp3xxx_wdt_probe(struct platform_device *pdev)
{
int ret;
watchdog_set_drvdata(&stmp3xxx_wdd, &pdev->dev);
stmp3xxx_wdd.timeout = clamp_t(unsigned, heartbeat, 1, STMP3XXX_MAX_TIMEOUT);
ret = watchdog_register_device(&stmp3xxx_wdd);
if (ret < 0) {
dev_err(&pdev->dev, "cannot register watchdog device\n");
return ret;
}
dev_info(&pdev->dev, "initialized watchdog with heartbeat %ds\n",
stmp3xxx_wdd.timeout);
return 0;
}
static int stmp3xxx_wdt_remove(struct platform_device *pdev)
{
watchdog_unregister_device(&stmp3xxx_wdd);
return 0;
}
static struct platform_driver stmp3xxx_wdt_driver = {
.driver = {
.name = "stmp3xxx_rtc_wdt",
},
.probe = stmp3xxx_wdt_probe,
.remove = stmp3xxx_wdt_remove,
};
module_platform_driver(stmp3xxx_wdt_driver);
MODULE_DESCRIPTION("STMP3XXX RTC Watchdog Driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
/*
* Watchdog driver for Freescale STMP37XX/STMP378X
*
* Author: Vitaly Wool <vital@embeddedalley.com>
*
* Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <mach/platform.h>
#include <mach/regs-rtc.h>
#define DEFAULT_HEARTBEAT 19
#define MAX_HEARTBEAT (0x10000000 >> 6)
/* missing bitmask in headers */
#define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER 0x80000000
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
#define WDOG_COUNTER_RATE 1000 /* 1 kHz clock */
static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock);
static unsigned long wdt_status;
static const bool nowayout = WATCHDOG_NOWAYOUT;
static int heartbeat = DEFAULT_HEARTBEAT;
static unsigned long boot_status;
static void wdt_enable(u32 value)
{
spin_lock(&stmp3xxx_wdt_io_lock);
__raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG);
stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
REGS_RTC_BASE + HW_RTC_PERSISTENT1);
spin_unlock(&stmp3xxx_wdt_io_lock);
}
static void wdt_disable(void)
{
spin_lock(&stmp3xxx_wdt_io_lock);
stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
REGS_RTC_BASE + HW_RTC_PERSISTENT1);
stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
spin_unlock(&stmp3xxx_wdt_io_lock);
}
static void wdt_ping(void)
{
wdt_enable(heartbeat * WDOG_COUNTER_RATE);
}
static int stmp3xxx_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(WDT_IN_USE, &wdt_status))
return -EBUSY;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
wdt_ping();
return nonseekable_open(inode, file);
}
static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
if (len) {
if (!nowayout) {
size_t i;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
set_bit(WDT_OK_TO_CLOSE, &wdt_status);
}
}
wdt_ping();
}
return len;
}
static const struct watchdog_info ident = {
.options = WDIOF_CARDRESET |
WDIOF_MAGICCLOSE |
WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING,
.identity = "STMP3XXX Watchdog",
};
static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_heartbeat, opts;
int ret = -ENOTTY;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, p);
break;
case WDIOC_GETBOOTSTATUS:
ret = put_user(boot_status, p);
break;
case WDIOC_SETOPTIONS:
if (get_user(opts, p)) {
ret = -EFAULT;
break;
}
if (opts & WDIOS_DISABLECARD)
wdt_disable();
else if (opts & WDIOS_ENABLECARD)
wdt_ping();
else {
pr_debug("%s: unknown option 0x%x\n", __func__, opts);
ret = -EINVAL;
break;
}
ret = 0;
break;
case WDIOC_KEEPALIVE:
wdt_ping();
ret = 0;
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_heartbeat, p)) {
ret = -EFAULT;
break;
}
if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) {
ret = -EINVAL;
break;
}
heartbeat = new_heartbeat;
wdt_ping();
/* Fall through */
case WDIOC_GETTIMEOUT:
ret = put_user(heartbeat, p);
break;
}
return ret;
}
static int stmp3xxx_wdt_release(struct inode *inode, struct file *file)
{
int ret = 0;
if (!nowayout) {
if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
wdt_ping();
pr_debug("%s: Device closed unexpectedly\n", __func__);
ret = -EINVAL;
} else {
wdt_disable();
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
}
}
clear_bit(WDT_IN_USE, &wdt_status);
return ret;
}
static const struct file_operations stmp3xxx_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = stmp3xxx_wdt_write,
.unlocked_ioctl = stmp3xxx_wdt_ioctl,
.open = stmp3xxx_wdt_open,
.release = stmp3xxx_wdt_release,
};
static struct miscdevice stmp3xxx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &stmp3xxx_wdt_fops,
};
static int stmp3xxx_wdt_probe(struct platform_device *pdev)
{
int ret = 0;
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
heartbeat = DEFAULT_HEARTBEAT;
boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) &
BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER;
boot_status = !!boot_status;
stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
REGS_RTC_BASE + HW_RTC_PERSISTENT1);
wdt_disable(); /* disable for now */
ret = misc_register(&stmp3xxx_wdt_miscdev);
if (ret < 0) {
dev_err(&pdev->dev, "cannot register misc device\n");
return ret;
}
pr_info("initialized, heartbeat %d sec\n", heartbeat);
return ret;
}
static int stmp3xxx_wdt_remove(struct platform_device *pdev)
{
misc_deregister(&stmp3xxx_wdt_miscdev);
return 0;
}
#ifdef CONFIG_PM
static int wdt_suspended;
static u32 wdt_saved_time;
static int stmp3xxx_wdt_suspend(struct platform_device *pdev,
pm_message_t state)
{
if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) &
BM_RTC_CTRL_WATCHDOGEN) {
wdt_suspended = 1;
wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG);
wdt_disable();
}
return 0;
}
static int stmp3xxx_wdt_resume(struct platform_device *pdev)
{
if (wdt_suspended) {
wdt_enable(wdt_saved_time);
wdt_suspended = 0;
}
return 0;
}
#else
#define stmp3xxx_wdt_suspend NULL
#define stmp3xxx_wdt_resume NULL
#endif
static struct platform_driver platform_wdt_driver = {
.driver = {
.name = "stmp3xxx_wdt",
},
.probe = stmp3xxx_wdt_probe,
.remove = stmp3xxx_wdt_remove,
.suspend = stmp3xxx_wdt_suspend,
.resume = stmp3xxx_wdt_resume,
};
module_platform_driver(platform_wdt_driver);
MODULE_DESCRIPTION("STMP3XXX Watchdog Driver");
MODULE_LICENSE("GPL");
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat,
"Watchdog heartbeat period in seconds from 1 to "
__MODULE_STRING(MAX_HEARTBEAT) ", default "
__MODULE_STRING(DEFAULT_HEARTBEAT));
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
...@@ -36,12 +36,68 @@ ...@@ -36,12 +36,68 @@
#include <linux/init.h> /* For __init/__exit/... */ #include <linux/init.h> /* For __init/__exit/... */
#include <linux/idr.h> /* For ida_* macros */ #include <linux/idr.h> /* For ida_* macros */
#include <linux/err.h> /* For IS_ERR macros */ #include <linux/err.h> /* For IS_ERR macros */
#include <linux/of.h> /* For of_get_timeout_sec */
#include "watchdog_core.h" /* For watchdog_dev_register/... */ #include "watchdog_core.h" /* For watchdog_dev_register/... */
static DEFINE_IDA(watchdog_ida); static DEFINE_IDA(watchdog_ida);
static struct class *watchdog_class; static struct class *watchdog_class;
static void watchdog_check_min_max_timeout(struct watchdog_device *wdd)
{
/*
* Check that we have valid min and max timeout values, if
* not reset them both to 0 (=not used or unknown)
*/
if (wdd->min_timeout > wdd->max_timeout) {
pr_info("Invalid min and max timeout values, resetting to 0!\n");
wdd->min_timeout = 0;
wdd->max_timeout = 0;
}
}
/**
* watchdog_init_timeout() - initialize the timeout field
* @timeout_parm: timeout module parameter
* @dev: Device that stores the timeout-sec property
*
* Initialize the timeout field of the watchdog_device struct with either the
* timeout module parameter (if it is valid value) or the timeout-sec property
* (only if it is a valid value and the timeout_parm is out of bounds).
* If none of them are valid then we keep the old value (which should normally
* be the default timeout value.
*
* A zero is returned on success and -EINVAL for failure.
*/
int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm, struct device *dev)
{
unsigned int t = 0;
int ret = 0;
watchdog_check_min_max_timeout(wdd);
/* try to get the tiemout module parameter first */
if (!watchdog_timeout_invalid(wdd, timeout_parm)) {
wdd->timeout = timeout_parm;
return ret;
}
if (timeout_parm)
ret = -EINVAL;
/* try to get the timeout_sec property */
if (dev == NULL || dev->of_node == NULL)
return ret;
of_property_read_u32(dev->of_node, "timeout-sec", &t);
if (!watchdog_timeout_invalid(wdd, t))
wdd->timeout = t;
else
ret = -EINVAL;
return ret;
}
EXPORT_SYMBOL_GPL(watchdog_init_timeout);
/** /**
* watchdog_register_device() - register a watchdog device * watchdog_register_device() - register a watchdog device
* @wdd: watchdog device * @wdd: watchdog device
...@@ -63,15 +119,7 @@ int watchdog_register_device(struct watchdog_device *wdd) ...@@ -63,15 +119,7 @@ int watchdog_register_device(struct watchdog_device *wdd)
if (wdd->ops->start == NULL || wdd->ops->stop == NULL) if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
return -EINVAL; return -EINVAL;
/* watchdog_check_min_max_timeout(wdd);
* Check that we have valid min and max timeout values, if
* not reset them both to 0 (=not used or unknown)
*/
if (wdd->min_timeout > wdd->max_timeout) {
pr_info("Invalid min and max timeout values, resetting to 0!\n");
wdd->min_timeout = 0;
wdd->max_timeout = 0;
}
/* /*
* Note: now that all watchdog_device data has been verified, we * Note: now that all watchdog_device data has been verified, we
......
...@@ -200,8 +200,7 @@ static int watchdog_set_timeout(struct watchdog_device *wddev, ...@@ -200,8 +200,7 @@ static int watchdog_set_timeout(struct watchdog_device *wddev,
!(wddev->info->options & WDIOF_SETTIMEOUT)) !(wddev->info->options & WDIOF_SETTIMEOUT))
return -EOPNOTSUPP; return -EOPNOTSUPP;
if ((wddev->max_timeout != 0) && if (watchdog_timeout_invalid(wddev, timeout))
(timeout < wddev->min_timeout || timeout > wddev->max_timeout))
return -EINVAL; return -EINVAL;
mutex_lock(&wddev->lock); mutex_lock(&wddev->lock);
......
#ifndef LINUX_BCM47XX_WDT_H_ #ifndef LINUX_BCM47XX_WDT_H_
#define LINUX_BCM47XX_WDT_H_ #define LINUX_BCM47XX_WDT_H_
#include <linux/notifier.h>
#include <linux/timer.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/watchdog.h>
struct bcm47xx_wdt { struct bcm47xx_wdt {
...@@ -10,6 +13,12 @@ struct bcm47xx_wdt { ...@@ -10,6 +13,12 @@ struct bcm47xx_wdt {
u32 max_timer_ms; u32 max_timer_ms;
void *driver_data; void *driver_data;
struct watchdog_device wdd;
struct notifier_block notifier;
struct timer_list soft_timer;
atomic_t soft_ticks;
}; };
static inline void *bcm47xx_wdt_get_drvdata(struct bcm47xx_wdt *wdt) static inline void *bcm47xx_wdt_get_drvdata(struct bcm47xx_wdt *wdt)
......
/*
* stmp3xxx_rtc_wdt.h
*
* Copyright (C) 2011 Wolfram Sang, Pengutronix e.K.
*
* This file is released under the GPLv2.
*/
#ifndef __LINUX_STMP3XXX_RTC_WDT_H
#define __LINUX_STMP3XXX_RTC_WDT_H
struct stmp3xxx_wdt_pdata {
void (*wdt_set_timeout)(struct device *dev, u32 timeout);
};
#endif /* __LINUX_STMP3XXX_RTC_WDT_H */
...@@ -118,6 +118,13 @@ static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool noway ...@@ -118,6 +118,13 @@ static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool noway
set_bit(WDOG_NO_WAY_OUT, &wdd->status); set_bit(WDOG_NO_WAY_OUT, &wdd->status);
} }
/* Use the following function to check if a timeout value is invalid */
static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigned int t)
{
return ((wdd->max_timeout != 0) &&
(t < wdd->min_timeout || t > wdd->max_timeout));
}
/* Use the following functions to manipulate watchdog driver specific data */ /* Use the following functions to manipulate watchdog driver specific data */
static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data) static inline void watchdog_set_drvdata(struct watchdog_device *wdd, void *data)
{ {
...@@ -130,6 +137,8 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd) ...@@ -130,6 +137,8 @@ static inline void *watchdog_get_drvdata(struct watchdog_device *wdd)
} }
/* drivers/watchdog/watchdog_core.c */ /* drivers/watchdog/watchdog_core.c */
extern int watchdog_init_timeout(struct watchdog_device *wdd,
unsigned int timeout_parm, struct device *dev);
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