Merge git://www.linux-watchdog.org/linux-watchdog
Pull watchdog updates from Wim Van Sebroeck: - new drivers for: NI 903x/913x watchdog driver, WinSystems EBC-C384 watchdog timer and ARM SBSA watchdog driver - Support for NCT6102D devices - Improvements of the generic watchdog framework (improve restart handler, make set_timeout optional, introduce infrastructure triggered keepalives, ... - improvements on the pnx4008 watchdog driver - several smaller fixes and improvements * git://www.linux-watchdog.org/linux-watchdog: (28 commits) watchdog: Ensure that wdd is not dereferenced if NULL watchdog: imx2: Convert to use infrastructure triggered keepalives watchdog: dw_wdt: Convert to use watchdog infrastructure watchdog: Add support for minimum time between heartbeats watchdog: Make stop function optional watchdog: Introduce WDOG_HW_RUNNING flag watchdog: Introduce hardware maximum heartbeat in watchdog core watchdog: Make set_timeout function optional arm: lpc32xx: remove restart handler arm: lpc32xx: phy3250 remove restart hook watchdog: pnx4008: restart: support "cmd" from userspace watchdog: pnx4008: add support for soft reset watchdog: pnx4008: add restart handler watchdog: pnx4008: update logging during power-on watchdog: tangox_wdt: test clock rate to avoid division by 0 watchdog: atlas7_wdt: test clock rate to avoid division by 0 watchdog: s3c2410_wdt: Add max and min timeout values Watchdog: introduce ARM SBSA watchdog driver Documentation: add sbsa-gwdt driver documentation watchdog: Add watchdog timer support for the WinSystems EBC-C384 ...
This commit is contained in:
commit
1e75a9f34a
31
Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt
Normal file
31
Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt
Normal file
@ -0,0 +1,31 @@
|
||||
* SBSA (Server Base System Architecture) Generic Watchdog
|
||||
|
||||
The SBSA Generic Watchdog Timer is used to force a reset of the system
|
||||
after two stages of timeout have elapsed. A detailed definition of the
|
||||
watchdog timer can be found in the ARM document: ARM-DEN-0029 - Server
|
||||
Base System Architecture (SBSA)
|
||||
|
||||
Required properties:
|
||||
- compatible: Should at least contain "arm,sbsa-gwdt".
|
||||
|
||||
- reg: Each entry specifies the base physical address of a register frame
|
||||
and the length of that frame; currently, two frames must be defined,
|
||||
in this order:
|
||||
1: Watchdog control frame;
|
||||
2: Refresh frame.
|
||||
|
||||
- interrupts: Should contain the Watchdog Signal 0 (WS0) SPI (Shared
|
||||
Peripheral Interrupt) number of SBSA Generic Watchdog.
|
||||
|
||||
Optional properties
|
||||
- timeout-sec: Watchdog timeout values (in seconds).
|
||||
|
||||
Example for FVP Foundation Model v8:
|
||||
|
||||
watchdog@2a440000 {
|
||||
compatible = "arm,sbsa-gwdt";
|
||||
reg = <0x0 0x2a440000 0 0x1000>,
|
||||
<0x0 0x2a450000 0 0x1000>;
|
||||
interrupts = <0 27 4>;
|
||||
timeout-sec = <30>;
|
||||
};
|
@ -52,6 +52,8 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
unsigned int min_hw_heartbeat_ms;
|
||||
unsigned int max_hw_heartbeat_ms;
|
||||
struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
@ -73,8 +75,21 @@ It contains following fields:
|
||||
additional information about the watchdog timer itself. (Like it's unique name)
|
||||
* ops: a pointer to the list of watchdog operations that the watchdog supports.
|
||||
* timeout: the watchdog timer's timeout value (in seconds).
|
||||
This is the time after which the system will reboot if user space does
|
||||
not send a heartbeat request if WDOG_ACTIVE is set.
|
||||
* min_timeout: the watchdog timer's minimum timeout value (in seconds).
|
||||
* max_timeout: the watchdog timer's maximum timeout value (in seconds).
|
||||
If set, the minimum configurable value for 'timeout'.
|
||||
* max_timeout: the watchdog timer's maximum timeout value (in seconds),
|
||||
as seen from userspace. If set, the maximum configurable value for
|
||||
'timeout'. Not used if max_hw_heartbeat_ms is non-zero.
|
||||
* min_hw_heartbeat_ms: Minimum time between heartbeats sent to the chip,
|
||||
in milli-seconds.
|
||||
* max_hw_heartbeat_ms: Maximum hardware heartbeat, in milli-seconds.
|
||||
If set, the infrastructure will send heartbeats to the watchdog driver
|
||||
if 'timeout' is larger than max_hw_heartbeat_ms, unless WDOG_ACTIVE
|
||||
is set and userspace failed to send a heartbeat for at least 'timeout'
|
||||
seconds. max_hw_heartbeat_ms must be set if a driver does not implement
|
||||
the stop function.
|
||||
* reboot_nb: notifier block that is registered for reboot notifications, for
|
||||
internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core
|
||||
will stop the watchdog on such notifications.
|
||||
@ -123,17 +138,20 @@ are:
|
||||
device.
|
||||
The routine needs a pointer to the watchdog timer device structure as a
|
||||
parameter. It returns zero on success or a negative errno code for failure.
|
||||
* stop: with this routine the watchdog timer device is being stopped.
|
||||
The routine needs a pointer to the watchdog timer device structure as a
|
||||
parameter. It returns zero on success or a negative errno code for failure.
|
||||
Some watchdog timer hardware can only be started and not be stopped. The
|
||||
driver supporting this hardware needs to make sure that a start and stop
|
||||
routine is being provided. This can be done by using a timer in the driver
|
||||
that regularly sends a keepalive ping to the watchdog timer hardware.
|
||||
|
||||
Not all watchdog timer hardware supports the same functionality. That's why
|
||||
all other routines/operations are optional. They only need to be provided if
|
||||
they are supported. These optional routines/operations are:
|
||||
* stop: with this routine the watchdog timer device is being stopped.
|
||||
The routine needs a pointer to the watchdog timer device structure as a
|
||||
parameter. It returns zero on success or a negative errno code for failure.
|
||||
Some watchdog timer hardware can only be started and not be stopped. A
|
||||
driver supporting such hardware does not have to implement the stop routine.
|
||||
If a driver has no stop function, the watchdog core will set WDOG_HW_RUNNING
|
||||
and start calling the driver's keepalive pings function after the watchdog
|
||||
device is closed.
|
||||
If a watchdog driver does not implement the stop function, it must set
|
||||
max_hw_heartbeat_ms.
|
||||
* ping: this is the routine that sends a keepalive ping to the watchdog timer
|
||||
hardware.
|
||||
The routine needs a pointer to the watchdog timer device structure as a
|
||||
@ -153,9 +171,18 @@ they are supported. These optional routines/operations are:
|
||||
and -EIO for "could not write value to the watchdog". On success this
|
||||
routine should set the timeout value of the watchdog_device to the
|
||||
achieved timeout value (which may be different from the requested one
|
||||
because the watchdog does not necessarily has a 1 second resolution).
|
||||
because the watchdog does not necessarily have a 1 second resolution).
|
||||
Drivers implementing max_hw_heartbeat_ms set the hardware watchdog heartbeat
|
||||
to the minimum of timeout and max_hw_heartbeat_ms. Those drivers set the
|
||||
timeout value of the watchdog_device either to the requested timeout value
|
||||
(if it is larger than max_hw_heartbeat_ms), or to the achieved timeout value.
|
||||
(Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the
|
||||
watchdog's info structure).
|
||||
If the watchdog driver does not have to perform any action but setting the
|
||||
watchdog_device.timeout, this callback can be omitted.
|
||||
If set_timeout is not provided but, WDIOF_SETTIMEOUT is set, the watchdog
|
||||
infrastructure updates the timeout value of the watchdog_device internally
|
||||
to the requested value.
|
||||
* get_timeleft: this routines returns the time that's left before a reset.
|
||||
* restart: this routine restarts the machine. It returns 0 on success or a
|
||||
negative errno code for failure.
|
||||
@ -169,11 +196,19 @@ The 'ref' and 'unref' operations are no longer used and deprecated.
|
||||
The status bits should (preferably) be set with the set_bit and clear_bit alike
|
||||
bit-operations. The status bits that are defined are:
|
||||
* WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device
|
||||
is active or not. When the watchdog is active after booting, then you should
|
||||
set this status bit (Note: when you register the watchdog timer device with
|
||||
this bit set, then opening /dev/watchdog will skip the start operation)
|
||||
is active or not from user perspective. User space is expected to send
|
||||
heartbeat requests to the driver while this flag is set.
|
||||
* WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog.
|
||||
If this bit is set then the watchdog timer will not be able to stop.
|
||||
* WDOG_HW_RUNNING: Set by the watchdog driver if the hardware watchdog is
|
||||
running. The bit must be set if the watchdog timer hardware can not be
|
||||
stopped. The bit may also be set if the watchdog timer is running after
|
||||
booting, before the watchdog device is opened. If set, the watchdog
|
||||
infrastructure will send keepalives to the watchdog hardware while
|
||||
WDOG_ACTIVE is not set.
|
||||
Note: when you register the watchdog timer device with this bit set,
|
||||
then opening /dev/watchdog will skip the start operation but send a keepalive
|
||||
request instead.
|
||||
|
||||
To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog
|
||||
timer device) you can either:
|
||||
|
@ -200,6 +200,11 @@ mv64x60_wdt:
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
ni903x_wdt:
|
||||
timeout: Initial watchdog timeout in seconds (0<timeout<516, default=60)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
nuc900_wdt:
|
||||
heartbeat: Watchdog heartbeats in seconds.
|
||||
(default = 15)
|
||||
@ -284,6 +289,13 @@ sbc_fitpc2_wdt:
|
||||
margin: Watchdog margin in seconds (default 60s)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
-------------------------------------------------
|
||||
sbsa_gwdt:
|
||||
timeout: Watchdog timeout in seconds. (default 10s)
|
||||
action: Watchdog action at the first stage timeout,
|
||||
set to 0 to ignore, 1 to panic. (default=0)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
sc1200wdt:
|
||||
isapnp: When set to 0 driver ISA PnP support will be disabled (default=1)
|
||||
io: io port
|
||||
|
@ -11965,6 +11965,12 @@ M: David Härdeman <david@hardeman.nu>
|
||||
S: Maintained
|
||||
F: drivers/media/rc/winbond-cir.c
|
||||
|
||||
WINSYSTEMS EBC-C384 WATCHDOG DRIVER
|
||||
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||||
L: linux-watchdog@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/watchdog/ebc-c384_wdt.c
|
||||
|
||||
WINSYSTEMS WS16C48 GPIO DRIVER
|
||||
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||||
L: linux-gpio@vger.kernel.org
|
||||
|
@ -194,21 +194,6 @@ void __init lpc32xx_map_io(void)
|
||||
iotable_init(lpc32xx_io_desc, ARRAY_SIZE(lpc32xx_io_desc));
|
||||
}
|
||||
|
||||
void lpc23xx_restart(enum reboot_mode mode, const char *cmd)
|
||||
{
|
||||
/* Make sure WDT clocks are enabled */
|
||||
__raw_writel(LPC32XX_CLKPWR_PWMCLK_WDOG_EN,
|
||||
LPC32XX_CLKPWR_TIMER_CLK_CTRL);
|
||||
|
||||
/* Instant assert of RESETOUT_N with pulse length 1mS */
|
||||
__raw_writel(13000, io_p2v(LPC32XX_WDTIM_BASE + 0x18));
|
||||
__raw_writel(0x70, io_p2v(LPC32XX_WDTIM_BASE + 0xC));
|
||||
|
||||
/* Wait for watchdog to reset system */
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
|
||||
static int __init lpc32xx_check_uid(void)
|
||||
{
|
||||
u32 uid[4];
|
||||
|
@ -30,7 +30,6 @@ extern void lpc32xx_timer_init(void);
|
||||
extern void __init lpc32xx_init_irq(void);
|
||||
extern void __init lpc32xx_map_io(void);
|
||||
extern void __init lpc32xx_serial_init(void);
|
||||
extern void lpc23xx_restart(enum reboot_mode, const char *);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -262,5 +262,4 @@ DT_MACHINE_START(LPC32XX_DT, "LPC32XX SoC (Flattened Device Tree)")
|
||||
.init_time = lpc32xx_timer_init,
|
||||
.init_machine = lpc3250_machine_init,
|
||||
.dt_compat = lpc32xx_dt_compat,
|
||||
.restart = lpc23xx_restart,
|
||||
MACHINE_END
|
||||
|
@ -202,6 +202,26 @@ config ARM_SP805_WATCHDOG
|
||||
ARM Primecell SP805 Watchdog timer. This will reboot your system when
|
||||
the timeout is reached.
|
||||
|
||||
config ARM_SBSA_WATCHDOG
|
||||
tristate "ARM SBSA Generic Watchdog"
|
||||
depends on ARM64
|
||||
depends on ARM_ARCH_TIMER
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
ARM SBSA Generic Watchdog has two stage timeouts:
|
||||
the first signal (WS0) is for alerting the system by interrupt,
|
||||
the second one (WS1) is a real hardware reset.
|
||||
More details: ARM DEN0029B - Server Base System Architecture (SBSA)
|
||||
|
||||
This driver can operate ARM SBSA Generic Watchdog as a single stage
|
||||
or a two stages watchdog, it depends on the module parameter "action".
|
||||
|
||||
Note: the maximum timeout in the two stages mode is half of that in
|
||||
the single stage mode.
|
||||
|
||||
To compile this driver as module, choose M here: The module
|
||||
will be called sbsa_gwdt.
|
||||
|
||||
config ASM9260_WATCHDOG
|
||||
tristate "Alphascale ASM9260 watchdog"
|
||||
depends on MACH_ASM9260
|
||||
@ -330,6 +350,7 @@ config SA1100_WATCHDOG
|
||||
config DW_WATCHDOG
|
||||
tristate "Synopsys DesignWare watchdog"
|
||||
depends on HAS_IOMEM
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Say Y here if to include support for the Synopsys DesignWare
|
||||
watchdog timer found in many chips.
|
||||
@ -399,6 +420,7 @@ config DAVINCI_WATCHDOG
|
||||
config ORION_WATCHDOG
|
||||
tristate "Orion watchdog"
|
||||
depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU
|
||||
depends on ARM
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Say Y here if to include support for the watchdog timer
|
||||
@ -468,6 +490,7 @@ config NUC900_WATCHDOG
|
||||
config TS4800_WATCHDOG
|
||||
tristate "TS-4800 Watchdog"
|
||||
depends on HAS_IOMEM && OF
|
||||
depends on SOC_IMX51 || COMPILE_TEST
|
||||
select WATCHDOG_CORE
|
||||
select MFD_SYSCON
|
||||
help
|
||||
@ -713,6 +736,15 @@ config ALIM7101_WDT
|
||||
|
||||
Most people will say N.
|
||||
|
||||
config EBC_C384_WDT
|
||||
tristate "WinSystems EBC-C384 Watchdog Timer"
|
||||
depends on X86
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Enables watchdog timer support for the watchdog timer on the
|
||||
WinSystems EBC-C384 motherboard. The timeout may be configured via
|
||||
the timeout module parameter.
|
||||
|
||||
config F71808E_WDT
|
||||
tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog"
|
||||
depends on X86
|
||||
@ -1142,6 +1174,7 @@ config W83627HF_WDT
|
||||
NCT6779
|
||||
NCT6791
|
||||
NCT6792
|
||||
NCT6102D/04D/06D
|
||||
|
||||
This watchdog simply watches your kernel to make sure it doesn't
|
||||
freeze, and if it does, it reboots your computer after a certain
|
||||
@ -1229,6 +1262,17 @@ config INTEL_MEI_WDT
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called mei_wdt.
|
||||
|
||||
config NI903X_WDT
|
||||
tristate "NI 903x/913x Watchdog"
|
||||
depends on X86 && ACPI
|
||||
select WATCHDOG_CORE
|
||||
---help---
|
||||
This is the driver for the watchdog timer on the National Instruments
|
||||
903x/913x real-time controllers.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will be
|
||||
called ni903x_wdt.
|
||||
|
||||
# M32R Architecture
|
||||
|
||||
# M68K Architecture
|
||||
@ -1392,10 +1436,12 @@ config BCM7038_WDT
|
||||
tristate "BCM7038 Watchdog"
|
||||
select WATCHDOG_CORE
|
||||
depends on HAS_IOMEM
|
||||
depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
|
||||
help
|
||||
Watchdog driver for the built-in hardware in Broadcom 7038 SoCs.
|
||||
|
||||
Say 'Y or 'M' here to enable the driver.
|
||||
Watchdog driver for the built-in hardware in Broadcom 7038 and
|
||||
later SoCs used in set-top boxes. BCM7038 was made public
|
||||
during the 2004 CES, and since then, many Broadcom chips use this
|
||||
watchdog block, including some cable modem chips.
|
||||
|
||||
config IMGPDC_WDT
|
||||
tristate "Imagination Technologies PDC Watchdog Timer"
|
||||
|
@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
|
||||
|
||||
# ARM Architecture
|
||||
obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o
|
||||
obj-$(CONFIG_ARM_SBSA_WATCHDOG) += sbsa_gwdt.o
|
||||
obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o
|
||||
obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
|
||||
obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
|
||||
@ -88,6 +89,7 @@ obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
|
||||
obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
|
||||
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
||||
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
|
||||
obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
|
||||
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
|
||||
obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o
|
||||
obj-$(CONFIG_GEODE_WDT) += geodewdt.o
|
||||
@ -127,6 +129,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
|
||||
obj-$(CONFIG_INTEL_SCU_WATCHDOG) += intel_scu_watchdog.o
|
||||
obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
|
||||
obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
|
||||
obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
|
||||
|
||||
# M32R Architecture
|
||||
|
||||
|
@ -154,6 +154,11 @@ static int atlas7_wdt_probe(struct platform_device *pdev)
|
||||
writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL);
|
||||
|
||||
wdt->tick_rate = clk_get_rate(clk);
|
||||
if (!wdt->tick_rate) {
|
||||
ret = -EINVAL;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
wdt->clk = clk;
|
||||
atlas7_wdd.min_timeout = 1;
|
||||
atlas7_wdd.max_timeout = UINT_MAX / wdt->tick_rate;
|
||||
|
@ -87,7 +87,8 @@ static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm47xx_wdt_restart(struct watchdog_device *wdd)
|
||||
static int bcm47xx_wdt_restart(struct watchdog_device *wdd,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
|
||||
|
||||
|
@ -119,7 +119,8 @@ static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da9063_wdt_restart(struct watchdog_device *wdd)
|
||||
static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
|
||||
int ret;
|
||||
|
@ -48,7 +48,8 @@ static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks)
|
||||
spin_unlock_irqrestore(&wdt->lock, flags);
|
||||
}
|
||||
|
||||
static int dc_wdt_restart(struct watchdog_device *wdog)
|
||||
static int dc_wdt_restart(struct watchdog_device *wdog, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
|
||||
|
||||
|
@ -12,9 +12,8 @@
|
||||
* and these are a function of the input clock frequency.
|
||||
*
|
||||
* The DesignWare watchdog cannot be stopped once it has been started so we
|
||||
* use a software timer to implement a ping that will keep the watchdog alive.
|
||||
* If we receive an expected close for the watchdog then we keep the timer
|
||||
* running, otherwise the timer is stopped and the watchdog will expire.
|
||||
* do not implement a stop function. The watchdog core will continue to send
|
||||
* heartbeat requests after the watchdog device has been closed.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
@ -22,12 +21,9 @@
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/notifier.h>
|
||||
@ -35,8 +31,6 @@
|
||||
#include <linux/pm.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define WDOG_CONTROL_REG_OFFSET 0x00
|
||||
@ -57,53 +51,50 @@ module_param(nowayout, bool, 0);
|
||||
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
|
||||
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
#define WDT_TIMEOUT (HZ / 2)
|
||||
|
||||
static struct {
|
||||
struct dw_wdt {
|
||||
void __iomem *regs;
|
||||
struct clk *clk;
|
||||
unsigned long in_use;
|
||||
unsigned long next_heartbeat;
|
||||
struct timer_list timer;
|
||||
int expect_close;
|
||||
struct notifier_block restart_handler;
|
||||
} dw_wdt;
|
||||
struct watchdog_device wdd;
|
||||
};
|
||||
|
||||
static inline int dw_wdt_is_enabled(void)
|
||||
#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd)
|
||||
|
||||
static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
|
||||
{
|
||||
return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) &
|
||||
return readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) &
|
||||
WDOG_CONTROL_REG_WDT_EN_MASK;
|
||||
}
|
||||
|
||||
static inline int dw_wdt_top_in_seconds(unsigned top)
|
||||
static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top)
|
||||
{
|
||||
/*
|
||||
* There are 16 possible timeout values in 0..15 where the number of
|
||||
* cycles is 2 ^ (16 + i) and the watchdog counts down.
|
||||
*/
|
||||
return (1U << (16 + top)) / clk_get_rate(dw_wdt.clk);
|
||||
return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk);
|
||||
}
|
||||
|
||||
static int dw_wdt_get_top(void)
|
||||
static int dw_wdt_get_top(struct dw_wdt *dw_wdt)
|
||||
{
|
||||
int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
|
||||
int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
|
||||
|
||||
return dw_wdt_top_in_seconds(top);
|
||||
return dw_wdt_top_in_seconds(dw_wdt, top);
|
||||
}
|
||||
|
||||
static inline void dw_wdt_set_next_heartbeat(void)
|
||||
static int dw_wdt_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ;
|
||||
}
|
||||
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||
|
||||
static void dw_wdt_keepalive(void)
|
||||
{
|
||||
writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
|
||||
writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs +
|
||||
WDOG_COUNTER_RESTART_REG_OFFSET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_wdt_set_top(unsigned top_s)
|
||||
static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
|
||||
{
|
||||
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||
int i, top_val = DW_WDT_MAX_TOP;
|
||||
|
||||
/*
|
||||
@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s)
|
||||
* always look for >=.
|
||||
*/
|
||||
for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
|
||||
if (dw_wdt_top_in_seconds(i) >= top_s) {
|
||||
if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) {
|
||||
top_val = i;
|
||||
break;
|
||||
}
|
||||
@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s)
|
||||
* effectively get a pat of the watchdog right here.
|
||||
*/
|
||||
writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
|
||||
dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
|
||||
dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
|
||||
|
||||
/*
|
||||
* Add an explicit pat to handle versions of the watchdog that
|
||||
* don't have TOPINIT. This won't hurt on versions that have
|
||||
* it.
|
||||
*/
|
||||
dw_wdt_keepalive();
|
||||
wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);
|
||||
|
||||
dw_wdt_set_next_heartbeat();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dw_wdt_top_in_seconds(top_val);
|
||||
static int dw_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||
|
||||
dw_wdt_set_timeout(wdd, wdd->timeout);
|
||||
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
|
||||
writel(WDOG_CONTROL_REG_WDT_EN_MASK,
|
||||
dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_wdt_restart_handle(struct notifier_block *this,
|
||||
unsigned long mode, void *cmd)
|
||||
unsigned long mode, void *cmd)
|
||||
{
|
||||
struct dw_wdt *dw_wdt;
|
||||
u32 val;
|
||||
|
||||
writel(0, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
|
||||
val = readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
|
||||
dw_wdt = container_of(this, struct dw_wdt, restart_handler);
|
||||
|
||||
writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
|
||||
val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
|
||||
if (val & WDOG_CONTROL_REG_WDT_EN_MASK)
|
||||
writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
|
||||
WDOG_COUNTER_RESTART_REG_OFFSET);
|
||||
writel(WDOG_COUNTER_RESTART_KICK_VALUE,
|
||||
dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
|
||||
else
|
||||
writel(WDOG_CONTROL_REG_WDT_EN_MASK,
|
||||
dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
|
||||
dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
|
||||
|
||||
/* wait for reset to assert... */
|
||||
mdelay(500);
|
||||
@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this,
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static void dw_wdt_ping(unsigned long data)
|
||||
static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
if (time_before(jiffies, dw_wdt.next_heartbeat) ||
|
||||
(!nowayout && !dw_wdt.in_use)) {
|
||||
dw_wdt_keepalive();
|
||||
mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
|
||||
} else
|
||||
pr_crit("keepalive missed, machine will reset\n");
|
||||
}
|
||||
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||
|
||||
static int dw_wdt_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
if (test_and_set_bit(0, &dw_wdt.in_use))
|
||||
return -EBUSY;
|
||||
|
||||
/* Make sure we don't get unloaded. */
|
||||
__module_get(THIS_MODULE);
|
||||
|
||||
if (!dw_wdt_is_enabled()) {
|
||||
/*
|
||||
* The watchdog is not currently enabled. Set the timeout to
|
||||
* something reasonable and then start it.
|
||||
*/
|
||||
dw_wdt_set_top(DW_WDT_DEFAULT_SECONDS);
|
||||
writel(WDOG_CONTROL_REG_WDT_EN_MASK,
|
||||
dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
|
||||
}
|
||||
|
||||
dw_wdt_set_next_heartbeat();
|
||||
|
||||
return nonseekable_open(inode, filp);
|
||||
}
|
||||
|
||||
static ssize_t dw_wdt_write(struct file *filp, const char __user *buf,
|
||||
size_t len, loff_t *offset)
|
||||
{
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
dw_wdt.expect_close = 0;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
char c;
|
||||
|
||||
if (get_user(c, buf + i))
|
||||
return -EFAULT;
|
||||
|
||||
if (c == 'V') {
|
||||
dw_wdt.expect_close = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dw_wdt_set_next_heartbeat();
|
||||
dw_wdt_keepalive();
|
||||
mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static u32 dw_wdt_time_left(void)
|
||||
{
|
||||
return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
|
||||
clk_get_rate(dw_wdt.clk);
|
||||
return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
|
||||
clk_get_rate(dw_wdt->clk);
|
||||
}
|
||||
|
||||
static const struct watchdog_info dw_wdt_ident = {
|
||||
@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = {
|
||||
.identity = "Synopsys DesignWare Watchdog",
|
||||
};
|
||||
|
||||
static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
unsigned long val;
|
||||
int timeout;
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
return copy_to_user((void __user *)arg, &dw_wdt_ident,
|
||||
sizeof(dw_wdt_ident)) ? -EFAULT : 0;
|
||||
|
||||
case WDIOC_GETSTATUS:
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
return put_user(0, (int __user *)arg);
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
dw_wdt_set_next_heartbeat();
|
||||
return 0;
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
if (get_user(val, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
timeout = dw_wdt_set_top(val);
|
||||
return put_user(timeout , (int __user *)arg);
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(dw_wdt_get_top(), (int __user *)arg);
|
||||
|
||||
case WDIOC_GETTIMELEFT:
|
||||
/* Get the time left until expiry. */
|
||||
if (get_user(val, (int __user *)arg))
|
||||
return -EFAULT;
|
||||
return put_user(dw_wdt_time_left(), (int __user *)arg);
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static int dw_wdt_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
clear_bit(0, &dw_wdt.in_use);
|
||||
|
||||
if (!dw_wdt.expect_close) {
|
||||
del_timer(&dw_wdt.timer);
|
||||
|
||||
if (!nowayout)
|
||||
pr_crit("unexpected close, system will reboot soon\n");
|
||||
else
|
||||
pr_crit("watchdog cannot be disabled, system will reboot soon\n");
|
||||
}
|
||||
|
||||
dw_wdt.expect_close = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
static const struct watchdog_ops dw_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = dw_wdt_start,
|
||||
.ping = dw_wdt_ping,
|
||||
.set_timeout = dw_wdt_set_timeout,
|
||||
.get_timeleft = dw_wdt_get_timeleft,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dw_wdt_suspend(struct device *dev)
|
||||
{
|
||||
clk_disable_unprepare(dw_wdt.clk);
|
||||
struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
|
||||
|
||||
clk_disable_unprepare(dw_wdt->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_wdt_resume(struct device *dev)
|
||||
{
|
||||
int err = clk_prepare_enable(dw_wdt.clk);
|
||||
struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
|
||||
int err = clk_prepare_enable(dw_wdt->clk);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dw_wdt_keepalive();
|
||||
dw_wdt_ping(&dw_wdt->wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -312,67 +206,82 @@ static int dw_wdt_resume(struct device *dev)
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume);
|
||||
|
||||
static const struct file_operations wdt_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.open = dw_wdt_open,
|
||||
.write = dw_wdt_write,
|
||||
.unlocked_ioctl = dw_wdt_ioctl,
|
||||
.release = dw_wdt_release
|
||||
};
|
||||
|
||||
static struct miscdevice dw_wdt_miscdev = {
|
||||
.fops = &wdt_fops,
|
||||
.name = "watchdog",
|
||||
.minor = WATCHDOG_MINOR,
|
||||
};
|
||||
|
||||
static int dw_wdt_drv_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct watchdog_device *wdd;
|
||||
struct dw_wdt *dw_wdt;
|
||||
struct resource *mem;
|
||||
int ret;
|
||||
struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
||||
dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem);
|
||||
if (IS_ERR(dw_wdt.regs))
|
||||
return PTR_ERR(dw_wdt.regs);
|
||||
dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
|
||||
if (!dw_wdt)
|
||||
return -ENOMEM;
|
||||
|
||||
dw_wdt.clk = devm_clk_get(&pdev->dev, NULL);
|
||||
if (IS_ERR(dw_wdt.clk))
|
||||
return PTR_ERR(dw_wdt.clk);
|
||||
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
dw_wdt->regs = devm_ioremap_resource(dev, mem);
|
||||
if (IS_ERR(dw_wdt->regs))
|
||||
return PTR_ERR(dw_wdt->regs);
|
||||
|
||||
ret = clk_prepare_enable(dw_wdt.clk);
|
||||
dw_wdt->clk = devm_clk_get(dev, NULL);
|
||||
if (IS_ERR(dw_wdt->clk))
|
||||
return PTR_ERR(dw_wdt->clk);
|
||||
|
||||
ret = clk_prepare_enable(dw_wdt->clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = misc_register(&dw_wdt_miscdev);
|
||||
wdd = &dw_wdt->wdd;
|
||||
wdd->info = &dw_wdt_ident;
|
||||
wdd->ops = &dw_wdt_ops;
|
||||
wdd->min_timeout = 1;
|
||||
wdd->max_hw_heartbeat_ms =
|
||||
dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000;
|
||||
wdd->parent = dev;
|
||||
|
||||
watchdog_set_drvdata(wdd, dw_wdt);
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
watchdog_init_timeout(wdd, 0, dev);
|
||||
|
||||
/*
|
||||
* If the watchdog is already running, use its already configured
|
||||
* timeout. Otherwise use the default or the value provided through
|
||||
* devicetree.
|
||||
*/
|
||||
if (dw_wdt_is_enabled(dw_wdt)) {
|
||||
wdd->timeout = dw_wdt_get_top(dw_wdt);
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
} else {
|
||||
wdd->timeout = DW_WDT_DEFAULT_SECONDS;
|
||||
watchdog_init_timeout(wdd, 0, dev);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dw_wdt);
|
||||
|
||||
ret = watchdog_register_device(wdd);
|
||||
if (ret)
|
||||
goto out_disable_clk;
|
||||
|
||||
dw_wdt.restart_handler.notifier_call = dw_wdt_restart_handle;
|
||||
dw_wdt.restart_handler.priority = 128;
|
||||
ret = register_restart_handler(&dw_wdt.restart_handler);
|
||||
dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle;
|
||||
dw_wdt->restart_handler.priority = 128;
|
||||
ret = register_restart_handler(&dw_wdt->restart_handler);
|
||||
if (ret)
|
||||
pr_warn("cannot register restart handler\n");
|
||||
|
||||
dw_wdt_set_next_heartbeat();
|
||||
setup_timer(&dw_wdt.timer, dw_wdt_ping, 0);
|
||||
mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
|
||||
|
||||
return 0;
|
||||
|
||||
out_disable_clk:
|
||||
clk_disable_unprepare(dw_wdt.clk);
|
||||
|
||||
clk_disable_unprepare(dw_wdt->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dw_wdt_drv_remove(struct platform_device *pdev)
|
||||
{
|
||||
unregister_restart_handler(&dw_wdt.restart_handler);
|
||||
struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
|
||||
|
||||
misc_deregister(&dw_wdt_miscdev);
|
||||
|
||||
clk_disable_unprepare(dw_wdt.clk);
|
||||
unregister_restart_handler(&dw_wdt->restart_handler);
|
||||
watchdog_unregister_device(&dw_wdt->wdd);
|
||||
clk_disable_unprepare(dw_wdt->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
188
drivers/watchdog/ebc-c384_wdt.c
Normal file
188
drivers/watchdog/ebc-c384_wdt.c
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Watchdog timer driver for the WinSystems EBC-C384
|
||||
* Copyright (C) 2016 William Breathitt Gray
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/device.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define MODULE_NAME "ebc-c384_wdt"
|
||||
#define WATCHDOG_TIMEOUT 60
|
||||
/*
|
||||
* The timeout value in minutes must fit in a single byte when sent to the
|
||||
* watchdog timer; the maximum timeout possible is 15300 (255 * 60) seconds.
|
||||
*/
|
||||
#define WATCHDOG_MAX_TIMEOUT 15300
|
||||
#define BASE_ADDR 0x564
|
||||
#define ADDR_EXTENT 5
|
||||
#define CFG_ADDR (BASE_ADDR + 1)
|
||||
#define PET_ADDR (BASE_ADDR + 2)
|
||||
|
||||
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 timeout;
|
||||
module_param(timeout, uint, 0);
|
||||
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
|
||||
__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
|
||||
|
||||
static int ebc_c384_wdt_start(struct watchdog_device *wdev)
|
||||
{
|
||||
unsigned t = wdev->timeout;
|
||||
|
||||
/* resolution is in minutes for timeouts greater than 255 seconds */
|
||||
if (t > 255)
|
||||
t = DIV_ROUND_UP(t, 60);
|
||||
|
||||
outb(t, PET_ADDR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_c384_wdt_stop(struct watchdog_device *wdev)
|
||||
{
|
||||
outb(0x00, PET_ADDR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ebc_c384_wdt_set_timeout(struct watchdog_device *wdev, unsigned t)
|
||||
{
|
||||
/* resolution is in minutes for timeouts greater than 255 seconds */
|
||||
if (t > 255) {
|
||||
/* round second resolution up to minute granularity */
|
||||
wdev->timeout = roundup(t, 60);
|
||||
|
||||
/* set watchdog timer for minutes */
|
||||
outb(0x00, CFG_ADDR);
|
||||
} else {
|
||||
wdev->timeout = t;
|
||||
|
||||
/* set watchdog timer for seconds */
|
||||
outb(0x80, CFG_ADDR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops ebc_c384_wdt_ops = {
|
||||
.start = ebc_c384_wdt_start,
|
||||
.stop = ebc_c384_wdt_stop,
|
||||
.set_timeout = ebc_c384_wdt_set_timeout
|
||||
};
|
||||
|
||||
static const struct watchdog_info ebc_c384_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT,
|
||||
.identity = MODULE_NAME
|
||||
};
|
||||
|
||||
static int __init ebc_c384_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct watchdog_device *wdd;
|
||||
|
||||
if (!devm_request_region(dev, BASE_ADDR, ADDR_EXTENT, dev_name(dev))) {
|
||||
dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
|
||||
BASE_ADDR, BASE_ADDR + ADDR_EXTENT);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
|
||||
if (!wdd)
|
||||
return -ENOMEM;
|
||||
|
||||
wdd->info = &ebc_c384_wdt_info;
|
||||
wdd->ops = &ebc_c384_wdt_ops;
|
||||
wdd->timeout = WATCHDOG_TIMEOUT;
|
||||
wdd->min_timeout = 1;
|
||||
wdd->max_timeout = WATCHDOG_MAX_TIMEOUT;
|
||||
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
|
||||
if (watchdog_init_timeout(wdd, timeout, dev))
|
||||
dev_warn(dev, "Invalid timeout (%u seconds), using default (%u seconds)\n",
|
||||
timeout, WATCHDOG_TIMEOUT);
|
||||
|
||||
platform_set_drvdata(pdev, wdd);
|
||||
|
||||
return watchdog_register_device(wdd);
|
||||
}
|
||||
|
||||
static int ebc_c384_wdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct watchdog_device *wdd = platform_get_drvdata(pdev);
|
||||
|
||||
watchdog_unregister_device(wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ebc_c384_wdt_driver = {
|
||||
.driver = {
|
||||
.name = MODULE_NAME
|
||||
},
|
||||
.remove = ebc_c384_wdt_remove
|
||||
};
|
||||
|
||||
static struct platform_device *ebc_c384_wdt_device;
|
||||
|
||||
static int __init ebc_c384_wdt_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!dmi_match(DMI_BOARD_NAME, "EBC-C384 SBC"))
|
||||
return -ENODEV;
|
||||
|
||||
ebc_c384_wdt_device = platform_device_alloc(MODULE_NAME, -1);
|
||||
if (!ebc_c384_wdt_device)
|
||||
return -ENOMEM;
|
||||
|
||||
err = platform_device_add(ebc_c384_wdt_device);
|
||||
if (err)
|
||||
goto err_platform_device;
|
||||
|
||||
err = platform_driver_probe(&ebc_c384_wdt_driver, ebc_c384_wdt_probe);
|
||||
if (err)
|
||||
goto err_platform_driver;
|
||||
|
||||
return 0;
|
||||
|
||||
err_platform_driver:
|
||||
platform_device_del(ebc_c384_wdt_device);
|
||||
err_platform_device:
|
||||
platform_device_put(ebc_c384_wdt_device);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit ebc_c384_wdt_exit(void)
|
||||
{
|
||||
platform_device_unregister(ebc_c384_wdt_device);
|
||||
platform_driver_unregister(&ebc_c384_wdt_driver);
|
||||
}
|
||||
|
||||
module_init(ebc_c384_wdt_init);
|
||||
module_exit(ebc_c384_wdt_exit);
|
||||
|
||||
MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
|
||||
MODULE_DESCRIPTION("WinSystems EBC-C384 watchdog timer driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" MODULE_NAME);
|
@ -150,7 +150,8 @@ static int pdc_wdt_start(struct watchdog_device *wdt_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pdc_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int pdc_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
|
||||
|
||||
|
@ -25,14 +25,12 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define DRIVER_NAME "imx2-wdt"
|
||||
@ -60,7 +58,6 @@
|
||||
struct imx2_wdt_device {
|
||||
struct clk *clk;
|
||||
struct regmap *regmap;
|
||||
struct timer_list timer; /* Pings the watchdog when closed */
|
||||
struct watchdog_device wdog;
|
||||
};
|
||||
|
||||
@ -80,7 +77,8 @@ static const struct watchdog_info imx2_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
|
||||
};
|
||||
|
||||
static int imx2_wdt_restart(struct watchdog_device *wdog)
|
||||
static int imx2_wdt_restart(struct watchdog_device *wdog, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||
unsigned int wcr_enable = IMX2_WDT_WCR_WDE;
|
||||
@ -146,16 +144,6 @@ static int imx2_wdt_ping(struct watchdog_device *wdog)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void imx2_wdt_timer_ping(unsigned long arg)
|
||||
{
|
||||
struct watchdog_device *wdog = (struct watchdog_device *)arg;
|
||||
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||
|
||||
/* ping it every wdog->timeout / 2 seconds to prevent reboot */
|
||||
imx2_wdt_ping(wdog);
|
||||
mod_timer(&wdev->timer, jiffies + wdog->timeout * HZ / 2);
|
||||
}
|
||||
|
||||
static int imx2_wdt_set_timeout(struct watchdog_device *wdog,
|
||||
unsigned int new_timeout)
|
||||
{
|
||||
@ -172,40 +160,19 @@ static int imx2_wdt_start(struct watchdog_device *wdog)
|
||||
{
|
||||
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
/* delete the timer that pings the watchdog after close */
|
||||
del_timer_sync(&wdev->timer);
|
||||
if (imx2_wdt_is_running(wdev))
|
||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||
} else
|
||||
else
|
||||
imx2_wdt_setup(wdog);
|
||||
|
||||
set_bit(WDOG_HW_RUNNING, &wdog->status);
|
||||
|
||||
return imx2_wdt_ping(wdog);
|
||||
}
|
||||
|
||||
static int imx2_wdt_stop(struct watchdog_device *wdog)
|
||||
{
|
||||
/*
|
||||
* We don't need a clk_disable, it cannot be disabled once started.
|
||||
* We use a timer to ping the watchdog while /dev/watchdog is closed
|
||||
*/
|
||||
imx2_wdt_timer_ping((unsigned long)wdog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void imx2_wdt_ping_if_active(struct watchdog_device *wdog)
|
||||
{
|
||||
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||
imx2_wdt_timer_ping((unsigned long)wdog);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct watchdog_ops imx2_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = imx2_wdt_start,
|
||||
.stop = imx2_wdt_stop,
|
||||
.ping = imx2_wdt_ping,
|
||||
.set_timeout = imx2_wdt_set_timeout,
|
||||
.restart = imx2_wdt_restart,
|
||||
@ -253,7 +220,7 @@ static int __init imx2_wdt_probe(struct platform_device *pdev)
|
||||
wdog->info = &imx2_wdt_info;
|
||||
wdog->ops = &imx2_wdt_ops;
|
||||
wdog->min_timeout = 1;
|
||||
wdog->max_timeout = IMX2_WDT_MAX_TIME;
|
||||
wdog->max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000;
|
||||
wdog->parent = &pdev->dev;
|
||||
|
||||
ret = clk_prepare_enable(wdev->clk);
|
||||
@ -274,9 +241,10 @@ static int __init imx2_wdt_probe(struct platform_device *pdev)
|
||||
watchdog_set_restart_priority(wdog, 128);
|
||||
watchdog_init_timeout(wdog, timeout, &pdev->dev);
|
||||
|
||||
setup_timer(&wdev->timer, imx2_wdt_timer_ping, (unsigned long)wdog);
|
||||
|
||||
imx2_wdt_ping_if_active(wdog);
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||
set_bit(WDOG_HW_RUNNING, &wdog->status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable the watchdog power down counter at boot. Otherwise the power
|
||||
@ -309,7 +277,6 @@ static int __exit imx2_wdt_remove(struct platform_device *pdev)
|
||||
watchdog_unregister_device(wdog);
|
||||
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
del_timer_sync(&wdev->timer);
|
||||
imx2_wdt_ping(wdog);
|
||||
dev_crit(&pdev->dev, "Device removed: Expect reboot!\n");
|
||||
}
|
||||
@ -323,10 +290,9 @@ static void imx2_wdt_shutdown(struct platform_device *pdev)
|
||||
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
/*
|
||||
* We are running, we need to delete the timer but will
|
||||
* give max timeout before reboot will take place
|
||||
* We are running, configure max timeout before reboot
|
||||
* will take place.
|
||||
*/
|
||||
del_timer_sync(&wdev->timer);
|
||||
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||
imx2_wdt_ping(wdog);
|
||||
dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n");
|
||||
@ -344,10 +310,6 @@ static int imx2_wdt_suspend(struct device *dev)
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||
imx2_wdt_ping(wdog);
|
||||
|
||||
/* The watchdog is not active */
|
||||
if (!watchdog_active(wdog))
|
||||
del_timer_sync(&wdev->timer);
|
||||
}
|
||||
|
||||
clk_disable_unprepare(wdev->clk);
|
||||
@ -373,19 +335,10 @@ static int imx2_wdt_resume(struct device *dev)
|
||||
* watchdog again.
|
||||
*/
|
||||
imx2_wdt_setup(wdog);
|
||||
}
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||
imx2_wdt_ping(wdog);
|
||||
} else if (imx2_wdt_is_running(wdev)) {
|
||||
/* Resuming from non-deep sleep state. */
|
||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||
imx2_wdt_ping(wdog);
|
||||
/*
|
||||
* But the watchdog is not active, then start
|
||||
* the timer again.
|
||||
*/
|
||||
if (!watchdog_active(wdog))
|
||||
mod_timer(&wdev->timer,
|
||||
jiffies + wdog->timeout * HZ / 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -153,7 +153,8 @@ static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
|
||||
unsigned long flags;
|
||||
|
@ -62,7 +62,8 @@ struct meson_wdt_dev {
|
||||
const struct meson_wdt_data *data;
|
||||
};
|
||||
|
||||
static int meson_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int meson_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
|
||||
u32 tc_reboot = MESON_WDT_DC_RESET;
|
||||
|
@ -31,7 +31,8 @@ struct moxart_wdt_dev {
|
||||
|
||||
static int heartbeat;
|
||||
|
||||
static int moxart_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int moxart_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev);
|
||||
|
||||
|
@ -64,7 +64,8 @@ struct mtk_wdt_dev {
|
||||
void __iomem *wdt_base;
|
||||
};
|
||||
|
||||
static int mtk_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int mtk_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
|
||||
void __iomem *wdt_base;
|
||||
|
270
drivers/watchdog/ni903x_wdt.c
Normal file
270
drivers/watchdog/ni903x_wdt.c
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (C) 2016 National Instruments Corp.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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/acpi.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define NIWD_CONTROL 0x01
|
||||
#define NIWD_COUNTER2 0x02
|
||||
#define NIWD_COUNTER1 0x03
|
||||
#define NIWD_COUNTER0 0x04
|
||||
#define NIWD_SEED2 0x05
|
||||
#define NIWD_SEED1 0x06
|
||||
#define NIWD_SEED0 0x07
|
||||
|
||||
#define NIWD_IO_SIZE 0x08
|
||||
|
||||
#define NIWD_CONTROL_MODE 0x80
|
||||
#define NIWD_CONTROL_PROC_RESET 0x20
|
||||
#define NIWD_CONTROL_PET 0x10
|
||||
#define NIWD_CONTROL_RUNNING 0x08
|
||||
#define NIWD_CONTROL_CAPTURECOUNTER 0x04
|
||||
#define NIWD_CONTROL_RESET 0x02
|
||||
#define NIWD_CONTROL_ALARM 0x01
|
||||
|
||||
#define NIWD_PERIOD_NS 30720
|
||||
#define NIWD_MIN_TIMEOUT 1
|
||||
#define NIWD_MAX_TIMEOUT 515
|
||||
#define NIWD_DEFAULT_TIMEOUT 60
|
||||
|
||||
#define NIWD_NAME "ni903x_wdt"
|
||||
|
||||
struct ni903x_wdt {
|
||||
struct device *dev;
|
||||
u16 io_base;
|
||||
struct watchdog_device wdd;
|
||||
};
|
||||
|
||||
static unsigned int timeout;
|
||||
module_param(timeout, uint, 0);
|
||||
MODULE_PARM_DESC(timeout,
|
||||
"Watchdog timeout in seconds. (default="
|
||||
__MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")");
|
||||
|
||||
static int nowayout = WATCHDOG_NOWAYOUT;
|
||||
module_param(nowayout, int, S_IRUGO);
|
||||
MODULE_PARM_DESC(nowayout,
|
||||
"Watchdog cannot be stopped once started (default="
|
||||
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
static void ni903x_start(struct ni903x_wdt *wdt)
|
||||
{
|
||||
u8 control = inb(wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
|
||||
outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
|
||||
}
|
||||
|
||||
static int ni903x_wdd_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS);
|
||||
|
||||
outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2);
|
||||
outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1);
|
||||
outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0);
|
||||
|
||||
wdd->timeout = timeout;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
u8 control, counter0, counter1, counter2;
|
||||
u32 counter;
|
||||
|
||||
control = inb(wdt->io_base + NIWD_CONTROL);
|
||||
control |= NIWD_CONTROL_CAPTURECOUNTER;
|
||||
outb(control, wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
counter2 = inb(wdt->io_base + NIWD_COUNTER2);
|
||||
counter1 = inb(wdt->io_base + NIWD_COUNTER1);
|
||||
counter0 = inb(wdt->io_base + NIWD_COUNTER0);
|
||||
|
||||
counter = (counter2 << 16) | (counter1 << 8) | counter0;
|
||||
|
||||
return counter / (1000000000 / NIWD_PERIOD_NS);
|
||||
}
|
||||
|
||||
static int ni903x_wdd_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
u8 control;
|
||||
|
||||
control = inb(wdt->io_base + NIWD_CONTROL);
|
||||
outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ni903x_wdd_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET,
|
||||
wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
ni903x_wdd_set_timeout(wdd, wdd->timeout);
|
||||
ni903x_start(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ni903x_wdd_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static acpi_status ni903x_resources(struct acpi_resource *res, void *data)
|
||||
{
|
||||
struct ni903x_wdt *wdt = data;
|
||||
u16 io_size;
|
||||
|
||||
switch (res->type) {
|
||||
case ACPI_RESOURCE_TYPE_IO:
|
||||
if (wdt->io_base != 0) {
|
||||
dev_err(wdt->dev, "too many IO resources\n");
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
wdt->io_base = res->data.io.minimum;
|
||||
io_size = res->data.io.address_length;
|
||||
|
||||
if (io_size < NIWD_IO_SIZE) {
|
||||
dev_err(wdt->dev, "memory region too small\n");
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
if (!devm_request_region(wdt->dev, wdt->io_base, io_size,
|
||||
NIWD_NAME)) {
|
||||
dev_err(wdt->dev, "failed to get memory region\n");
|
||||
return AE_ERROR;
|
||||
}
|
||||
|
||||
return AE_OK;
|
||||
|
||||
case ACPI_RESOURCE_TYPE_END_TAG:
|
||||
default:
|
||||
/* Ignore unsupported resources, e.g. IRQ */
|
||||
return AE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct watchdog_info ni903x_wdd_info = {
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||
.identity = "NI Watchdog",
|
||||
};
|
||||
|
||||
static const struct watchdog_ops ni903x_wdd_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = ni903x_wdd_start,
|
||||
.stop = ni903x_wdd_stop,
|
||||
.ping = ni903x_wdd_ping,
|
||||
.set_timeout = ni903x_wdd_set_timeout,
|
||||
.get_timeleft = ni903x_wdd_get_timeleft,
|
||||
};
|
||||
|
||||
static int ni903x_acpi_add(struct acpi_device *device)
|
||||
{
|
||||
struct device *dev = &device->dev;
|
||||
struct watchdog_device *wdd;
|
||||
struct ni903x_wdt *wdt;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
|
||||
if (!wdt)
|
||||
return -ENOMEM;
|
||||
|
||||
device->driver_data = wdt;
|
||||
wdt->dev = dev;
|
||||
|
||||
status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
|
||||
ni903x_resources, wdt);
|
||||
if (ACPI_FAILURE(status) || wdt->io_base == 0) {
|
||||
dev_err(dev, "failed to get resources\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
wdd = &wdt->wdd;
|
||||
wdd->info = &ni903x_wdd_info;
|
||||
wdd->ops = &ni903x_wdd_ops;
|
||||
wdd->min_timeout = NIWD_MIN_TIMEOUT;
|
||||
wdd->max_timeout = NIWD_MAX_TIMEOUT;
|
||||
wdd->timeout = NIWD_DEFAULT_TIMEOUT;
|
||||
wdd->parent = dev;
|
||||
watchdog_set_drvdata(wdd, wdt);
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
ret = watchdog_init_timeout(wdd, timeout, dev);
|
||||
if (ret)
|
||||
dev_err(dev, "unable to set timeout value, using default\n");
|
||||
|
||||
ret = watchdog_register_device(wdd);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register watchdog\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Switch from boot mode to user mode */
|
||||
outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE,
|
||||
wdt->io_base + NIWD_CONTROL);
|
||||
|
||||
dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
|
||||
wdt->io_base, timeout, nowayout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ni903x_acpi_remove(struct acpi_device *device)
|
||||
{
|
||||
struct ni903x_wdt *wdt = acpi_driver_data(device);
|
||||
|
||||
ni903x_wdd_stop(&wdt->wdd);
|
||||
watchdog_unregister_device(&wdt->wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id ni903x_device_ids[] = {
|
||||
{"NIC775C", 0},
|
||||
{"", 0},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, ni903x_device_ids);
|
||||
|
||||
static struct acpi_driver ni903x_acpi_driver = {
|
||||
.name = NIWD_NAME,
|
||||
.ids = ni903x_device_ids,
|
||||
.ops = {
|
||||
.add = ni903x_acpi_add,
|
||||
.remove = ni903x_acpi_remove,
|
||||
},
|
||||
};
|
||||
|
||||
module_acpi_driver(ni903x_acpi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("NI 903x Watchdog");
|
||||
MODULE_AUTHOR("Jeff Westfahl <jeff.westfahl@ni.com>");
|
||||
MODULE_AUTHOR("Kyle Roeschley <kyle.roeschley@ni.com>");
|
||||
MODULE_LICENSE("GPL");
|
@ -31,6 +31,8 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
/* WatchDog Timer - Chapter 23 Page 207 */
|
||||
@ -124,6 +126,41 @@ static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pnx4008_restart_handler(struct watchdog_device *wdd,
|
||||
unsigned long mode, void *cmd)
|
||||
{
|
||||
const char *boot_cmd = cmd;
|
||||
|
||||
/*
|
||||
* Verify if a "cmd" passed from the userspace program rebooting
|
||||
* the system; if available, handle it.
|
||||
* - For details, see the 'reboot' syscall in kernel/reboot.c
|
||||
* - If the received "cmd" is not supported, use the default mode.
|
||||
*/
|
||||
if (boot_cmd) {
|
||||
if (boot_cmd[0] == 'h')
|
||||
mode = REBOOT_HARD;
|
||||
else if (boot_cmd[0] == 's')
|
||||
mode = REBOOT_SOFT;
|
||||
}
|
||||
|
||||
if (mode == REBOOT_SOFT) {
|
||||
/* Force match output active */
|
||||
writel(EXT_MATCH0, WDTIM_EMR(wdt_base));
|
||||
/* Internal reset on match output (RESOUT_N not asserted) */
|
||||
writel(M_RES1, WDTIM_MCTRL(wdt_base));
|
||||
} else {
|
||||
/* Instant assert of RESETOUT_N with pulse length 1mS */
|
||||
writel(13000, WDTIM_PULSE(wdt_base));
|
||||
writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base));
|
||||
}
|
||||
|
||||
/* Wait for watchdog to reset system */
|
||||
mdelay(1000);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static const struct watchdog_info pnx4008_wdt_ident = {
|
||||
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
|
||||
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
|
||||
@ -135,6 +172,7 @@ static const struct watchdog_ops pnx4008_wdt_ops = {
|
||||
.start = pnx4008_wdt_start,
|
||||
.stop = pnx4008_wdt_stop,
|
||||
.set_timeout = pnx4008_wdt_set_timeout,
|
||||
.restart = pnx4008_restart_handler,
|
||||
};
|
||||
|
||||
static struct watchdog_device pnx4008_wdd = {
|
||||
@ -169,6 +207,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
|
||||
WDIOF_CARDRESET : 0;
|
||||
pnx4008_wdd.parent = &pdev->dev;
|
||||
watchdog_set_nowayout(&pnx4008_wdd, nowayout);
|
||||
watchdog_set_restart_priority(&pnx4008_wdd, 128);
|
||||
|
||||
pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */
|
||||
|
||||
@ -178,8 +217,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
|
||||
goto disable_clk;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n",
|
||||
pnx4008_wdd.timeout);
|
||||
dev_info(&pdev->dev, "heartbeat %d sec\n", pnx4008_wdd.timeout);
|
||||
|
||||
return 0;
|
||||
|
||||
|
@ -70,7 +70,8 @@ static int qcom_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
return qcom_wdt_start(wdd);
|
||||
}
|
||||
|
||||
static int qcom_wdt_restart(struct watchdog_device *wdd)
|
||||
static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||
u32 timeout;
|
||||
|
@ -237,7 +237,7 @@ static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
|
||||
return -EINVAL;
|
||||
/* Fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return copy_to_user(argp, &timeout, sizeof(int));
|
||||
return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
@ -47,6 +47,8 @@
|
||||
#define S3C2410_WTDAT 0x04
|
||||
#define S3C2410_WTCNT 0x08
|
||||
|
||||
#define S3C2410_WTCNT_MAXCNT 0xffff
|
||||
|
||||
#define S3C2410_WTCON_RSTEN (1 << 0)
|
||||
#define S3C2410_WTCON_INTEN (1 << 2)
|
||||
#define S3C2410_WTCON_ENABLE (1 << 5)
|
||||
@ -56,8 +58,11 @@
|
||||
#define S3C2410_WTCON_DIV64 (2 << 3)
|
||||
#define S3C2410_WTCON_DIV128 (3 << 3)
|
||||
|
||||
#define S3C2410_WTCON_MAXDIV 0x80
|
||||
|
||||
#define S3C2410_WTCON_PRESCALE(x) ((x) << 8)
|
||||
#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8)
|
||||
#define S3C2410_WTCON_PRESCALE_MAX 0xff
|
||||
|
||||
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
|
||||
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
|
||||
@ -198,6 +203,14 @@ do { \
|
||||
|
||||
/* functions */
|
||||
|
||||
static inline unsigned int s3c2410wdt_max_timeout(struct clk *clock)
|
||||
{
|
||||
unsigned long freq = clk_get_rate(clock);
|
||||
|
||||
return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1)
|
||||
/ S3C2410_WTCON_MAXDIV);
|
||||
}
|
||||
|
||||
static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
|
||||
{
|
||||
return container_of(nb, struct s3c2410_wdt, freq_transition);
|
||||
@ -349,7 +362,8 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2410wdt_restart(struct watchdog_device *wdd)
|
||||
static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||
void __iomem *wdt_base = wdt->reg_base;
|
||||
@ -567,6 +581,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
wdt->wdt_device.min_timeout = 1;
|
||||
wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt->clock);
|
||||
|
||||
ret = s3c2410wdt_cpufreq_register(wdt);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register cpufreq\n");
|
||||
|
408
drivers/watchdog/sbsa_gwdt.c
Normal file
408
drivers/watchdog/sbsa_gwdt.c
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* SBSA(Server Base System Architecture) Generic Watchdog driver
|
||||
*
|
||||
* Copyright (c) 2015, Linaro Ltd.
|
||||
* Author: Fu Wei <fu.wei@linaro.org>
|
||||
* Suravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>
|
||||
* Al Stone <al.stone@linaro.org>
|
||||
* Timur Tabi <timur@codeaurora.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License 2 as published
|
||||
* by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* ARM SBSA Generic Watchdog has two stage timeouts:
|
||||
* the first signal (WS0) is for alerting the system by interrupt,
|
||||
* the second one (WS1) is a real hardware reset.
|
||||
* More details about the hardware specification of this device:
|
||||
* ARM DEN0029B - Server Base System Architecture (SBSA)
|
||||
*
|
||||
* This driver can operate ARM SBSA Generic Watchdog as a single stage watchdog
|
||||
* or a two stages watchdog, it's set up by the module parameter "action".
|
||||
* In the single stage mode, when the timeout is reached, your system
|
||||
* will be reset by WS1. The first signal (WS0) is ignored.
|
||||
* In the two stages mode, when the timeout is reached, the first signal (WS0)
|
||||
* will trigger panic. If the system is getting into trouble and cannot be reset
|
||||
* by panic or restart properly by the kdump kernel(if supported), then the
|
||||
* second stage (as long as the first stage) will be reached, system will be
|
||||
* reset by WS1. This function can help administrator to backup the system
|
||||
* context info by panic console output or kdump.
|
||||
*
|
||||
* SBSA GWDT:
|
||||
* if action is 1 (the two stages mode):
|
||||
* |--------WOR-------WS0--------WOR-------WS1
|
||||
* |----timeout-----(panic)----timeout-----reset
|
||||
*
|
||||
* if action is 0 (the single stage mode):
|
||||
* |------WOR-----WS0(ignored)-----WOR------WS1
|
||||
* |--------------timeout-------------------reset
|
||||
*
|
||||
* Note: Since this watchdog timer has two stages, and each stage is determined
|
||||
* by WOR, in the single stage mode, the timeout is (WOR * 2); in the two
|
||||
* stages mode, the timeout is WOR. The maximum timeout in the two stages mode
|
||||
* is half of that in the single stage mode.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <asm/arch_timer.h>
|
||||
|
||||
#define DRV_NAME "sbsa-gwdt"
|
||||
#define WATCHDOG_NAME "SBSA Generic Watchdog"
|
||||
|
||||
/* SBSA Generic Watchdog register definitions */
|
||||
/* refresh frame */
|
||||
#define SBSA_GWDT_WRR 0x000
|
||||
|
||||
/* control frame */
|
||||
#define SBSA_GWDT_WCS 0x000
|
||||
#define SBSA_GWDT_WOR 0x008
|
||||
#define SBSA_GWDT_WCV 0x010
|
||||
|
||||
/* refresh/control frame */
|
||||
#define SBSA_GWDT_W_IIDR 0xfcc
|
||||
#define SBSA_GWDT_IDR 0xfd0
|
||||
|
||||
/* Watchdog Control and Status Register */
|
||||
#define SBSA_GWDT_WCS_EN BIT(0)
|
||||
#define SBSA_GWDT_WCS_WS0 BIT(1)
|
||||
#define SBSA_GWDT_WCS_WS1 BIT(2)
|
||||
|
||||
/**
|
||||
* struct sbsa_gwdt - Internal representation of the SBSA GWDT
|
||||
* @wdd: kernel watchdog_device structure
|
||||
* @clk: store the System Counter clock frequency, in Hz.
|
||||
* @refresh_base: Virtual address of the watchdog refresh frame
|
||||
* @control_base: Virtual address of the watchdog control frame
|
||||
*/
|
||||
struct sbsa_gwdt {
|
||||
struct watchdog_device wdd;
|
||||
u32 clk;
|
||||
void __iomem *refresh_base;
|
||||
void __iomem *control_base;
|
||||
};
|
||||
|
||||
#define DEFAULT_TIMEOUT 10 /* seconds */
|
||||
|
||||
static unsigned int timeout;
|
||||
module_param(timeout, uint, 0);
|
||||
MODULE_PARM_DESC(timeout,
|
||||
"Watchdog timeout in seconds. (>=0, default="
|
||||
__MODULE_STRING(DEFAULT_TIMEOUT) ")");
|
||||
|
||||
/*
|
||||
* action refers to action taken when watchdog gets WS0
|
||||
* 0 = skip
|
||||
* 1 = panic
|
||||
* defaults to skip (0)
|
||||
*/
|
||||
static int action;
|
||||
module_param(action, int, 0);
|
||||
MODULE_PARM_DESC(action, "after watchdog gets WS0 interrupt, do: "
|
||||
"0 = skip(*) 1 = panic");
|
||||
|
||||
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||
module_param(nowayout, bool, S_IRUGO);
|
||||
MODULE_PARM_DESC(nowayout,
|
||||
"Watchdog cannot be stopped once started (default="
|
||||
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
/*
|
||||
* watchdog operation functions
|
||||
*/
|
||||
static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
wdd->timeout = timeout;
|
||||
|
||||
if (action)
|
||||
writel(gwdt->clk * timeout,
|
||||
gwdt->control_base + SBSA_GWDT_WOR);
|
||||
else
|
||||
/*
|
||||
* In the single stage mode, The first signal (WS0) is ignored,
|
||||
* the timeout is (WOR * 2), so the WOR should be configured
|
||||
* to half value of timeout.
|
||||
*/
|
||||
writel(gwdt->clk / 2 * timeout,
|
||||
gwdt->control_base + SBSA_GWDT_WOR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int sbsa_gwdt_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
u64 timeleft = 0;
|
||||
|
||||
/*
|
||||
* In the single stage mode, if WS0 is deasserted
|
||||
* (watchdog is in the first stage),
|
||||
* timeleft = WOR + (WCV - system counter)
|
||||
*/
|
||||
if (!action &&
|
||||
!(readl(gwdt->control_base + SBSA_GWDT_WCS) & SBSA_GWDT_WCS_WS0))
|
||||
timeleft += readl(gwdt->control_base + SBSA_GWDT_WOR);
|
||||
|
||||
timeleft += readq(gwdt->control_base + SBSA_GWDT_WCV) -
|
||||
arch_counter_get_cntvct();
|
||||
|
||||
do_div(timeleft, gwdt->clk);
|
||||
|
||||
return timeleft;
|
||||
}
|
||||
|
||||
static int sbsa_gwdt_keepalive(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
/*
|
||||
* Writing WRR for an explicit watchdog refresh.
|
||||
* You can write anyting (like 0).
|
||||
*/
|
||||
writel(0, gwdt->refresh_base + SBSA_GWDT_WRR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int sbsa_gwdt_status(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
u32 status = readl(gwdt->control_base + SBSA_GWDT_WCS);
|
||||
|
||||
/* is the watchdog timer running? */
|
||||
return (status & SBSA_GWDT_WCS_EN) << WDOG_ACTIVE;
|
||||
}
|
||||
|
||||
static int sbsa_gwdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
/* writing WCS will cause an explicit watchdog refresh */
|
||||
writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sbsa_gwdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
|
||||
|
||||
/* Simply write 0 to WCS to clean WCS_EN bit */
|
||||
writel(0, gwdt->control_base + SBSA_GWDT_WCS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t sbsa_gwdt_interrupt(int irq, void *dev_id)
|
||||
{
|
||||
panic(WATCHDOG_NAME " timeout");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct watchdog_info sbsa_gwdt_info = {
|
||||
.identity = WATCHDOG_NAME,
|
||||
.options = WDIOF_SETTIMEOUT |
|
||||
WDIOF_KEEPALIVEPING |
|
||||
WDIOF_MAGICCLOSE |
|
||||
WDIOF_CARDRESET,
|
||||
};
|
||||
|
||||
static struct watchdog_ops sbsa_gwdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = sbsa_gwdt_start,
|
||||
.stop = sbsa_gwdt_stop,
|
||||
.status = sbsa_gwdt_status,
|
||||
.ping = sbsa_gwdt_keepalive,
|
||||
.set_timeout = sbsa_gwdt_set_timeout,
|
||||
.get_timeleft = sbsa_gwdt_get_timeleft,
|
||||
};
|
||||
|
||||
static int sbsa_gwdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
void __iomem *rf_base, *cf_base;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct watchdog_device *wdd;
|
||||
struct sbsa_gwdt *gwdt;
|
||||
struct resource *res;
|
||||
int ret, irq;
|
||||
u32 status;
|
||||
|
||||
gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
|
||||
if (!gwdt)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, gwdt);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
cf_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(cf_base))
|
||||
return PTR_ERR(cf_base);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
||||
rf_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(rf_base))
|
||||
return PTR_ERR(rf_base);
|
||||
|
||||
/*
|
||||
* Get the frequency of system counter from the cp15 interface of ARM
|
||||
* Generic timer. We don't need to check it, because if it returns "0",
|
||||
* system would panic in very early stage.
|
||||
*/
|
||||
gwdt->clk = arch_timer_get_cntfrq();
|
||||
gwdt->refresh_base = rf_base;
|
||||
gwdt->control_base = cf_base;
|
||||
|
||||
wdd = &gwdt->wdd;
|
||||
wdd->parent = dev;
|
||||
wdd->info = &sbsa_gwdt_info;
|
||||
wdd->ops = &sbsa_gwdt_ops;
|
||||
wdd->min_timeout = 1;
|
||||
wdd->max_timeout = U32_MAX / gwdt->clk;
|
||||
wdd->timeout = DEFAULT_TIMEOUT;
|
||||
watchdog_set_drvdata(wdd, gwdt);
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
|
||||
status = readl(cf_base + SBSA_GWDT_WCS);
|
||||
if (status & SBSA_GWDT_WCS_WS1) {
|
||||
dev_warn(dev, "System reset by WDT.\n");
|
||||
wdd->bootstatus |= WDIOF_CARDRESET;
|
||||
}
|
||||
|
||||
if (action) {
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
action = 0;
|
||||
dev_warn(dev, "unable to get ws0 interrupt.\n");
|
||||
} else {
|
||||
/*
|
||||
* In case there is a pending ws0 interrupt, just ping
|
||||
* the watchdog before registering the interrupt routine
|
||||
*/
|
||||
writel(0, rf_base + SBSA_GWDT_WRR);
|
||||
if (devm_request_irq(dev, irq, sbsa_gwdt_interrupt, 0,
|
||||
pdev->name, gwdt)) {
|
||||
action = 0;
|
||||
dev_warn(dev, "unable to request IRQ %d.\n",
|
||||
irq);
|
||||
}
|
||||
}
|
||||
if (!action)
|
||||
dev_warn(dev, "falling back to single stage mode.\n");
|
||||
}
|
||||
/*
|
||||
* In the single stage mode, The first signal (WS0) is ignored,
|
||||
* the timeout is (WOR * 2), so the maximum timeout should be doubled.
|
||||
*/
|
||||
if (!action)
|
||||
wdd->max_timeout *= 2;
|
||||
|
||||
watchdog_init_timeout(wdd, timeout, dev);
|
||||
/*
|
||||
* Update timeout to WOR.
|
||||
* Because of the explicit watchdog refresh mechanism,
|
||||
* it's also a ping, if watchdog is enabled.
|
||||
*/
|
||||
sbsa_gwdt_set_timeout(wdd, wdd->timeout);
|
||||
|
||||
ret = watchdog_register_device(wdd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev_info(dev, "Initialized with %ds timeout @ %u Hz, action=%d.%s\n",
|
||||
wdd->timeout, gwdt->clk, action,
|
||||
status & SBSA_GWDT_WCS_EN ? " [enabled]" : "");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sbsa_gwdt_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = platform_get_drvdata(pdev);
|
||||
|
||||
sbsa_gwdt_stop(&gwdt->wdd);
|
||||
}
|
||||
|
||||
static int sbsa_gwdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = platform_get_drvdata(pdev);
|
||||
|
||||
watchdog_unregister_device(&gwdt->wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Disable watchdog if it is active during suspend */
|
||||
static int __maybe_unused sbsa_gwdt_suspend(struct device *dev)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);
|
||||
|
||||
if (watchdog_active(&gwdt->wdd))
|
||||
sbsa_gwdt_stop(&gwdt->wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Enable watchdog if necessary */
|
||||
static int __maybe_unused sbsa_gwdt_resume(struct device *dev)
|
||||
{
|
||||
struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);
|
||||
|
||||
if (watchdog_active(&gwdt->wdd))
|
||||
sbsa_gwdt_start(&gwdt->wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops sbsa_gwdt_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(sbsa_gwdt_suspend, sbsa_gwdt_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id sbsa_gwdt_of_match[] = {
|
||||
{ .compatible = "arm,sbsa-gwdt", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sbsa_gwdt_of_match);
|
||||
|
||||
static const struct platform_device_id sbsa_gwdt_pdev_match[] = {
|
||||
{ .name = DRV_NAME, },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, sbsa_gwdt_pdev_match);
|
||||
|
||||
static struct platform_driver sbsa_gwdt_driver = {
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.pm = &sbsa_gwdt_pm_ops,
|
||||
.of_match_table = sbsa_gwdt_of_match,
|
||||
},
|
||||
.probe = sbsa_gwdt_probe,
|
||||
.remove = sbsa_gwdt_remove,
|
||||
.shutdown = sbsa_gwdt_shutdown,
|
||||
.id_table = sbsa_gwdt_pdev_match,
|
||||
};
|
||||
|
||||
module_platform_driver(sbsa_gwdt_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SBSA Generic Watchdog Driver");
|
||||
MODULE_AUTHOR("Fu Wei <fu.wei@linaro.org>");
|
||||
MODULE_AUTHOR("Suravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>");
|
||||
MODULE_AUTHOR("Al Stone <al.stone@linaro.org>");
|
||||
MODULE_AUTHOR("Timur Tabi <timur@codeaurora.org>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:" DRV_NAME);
|
@ -83,7 +83,8 @@ static const int wdt_timeout_map[] = {
|
||||
};
|
||||
|
||||
|
||||
static int sunxi_wdt_restart(struct watchdog_device *wdt_dev)
|
||||
static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
|
||||
void __iomem *wdt_base = sunxi_wdt->wdt_base;
|
||||
|
@ -139,6 +139,10 @@ static int tangox_wdt_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
|
||||
dev->clk_rate = clk_get_rate(dev->clk);
|
||||
if (!dev->clk_rate) {
|
||||
err = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev->wdt.parent = &pdev->dev;
|
||||
dev->wdt.info = &tangox_wdt_info;
|
||||
@ -171,10 +175,8 @@ static int tangox_wdt_probe(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
err = watchdog_register_device(&dev->wdt);
|
||||
if (err) {
|
||||
clk_disable_unprepare(dev->clk);
|
||||
return err;
|
||||
}
|
||||
if (err)
|
||||
goto err;
|
||||
|
||||
platform_set_drvdata(pdev, dev);
|
||||
|
||||
@ -187,6 +189,10 @@ static int tangox_wdt_probe(struct platform_device *pdev)
|
||||
dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
clk_disable_unprepare(dev->clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tangox_wdt_remove(struct platform_device *pdev)
|
||||
|
@ -45,10 +45,11 @@
|
||||
static int wdt_io;
|
||||
static int cr_wdt_timeout; /* WDT timeout register */
|
||||
static int cr_wdt_control; /* WDT control register */
|
||||
static int cr_wdt_csr; /* WDT control & status register */
|
||||
|
||||
enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf,
|
||||
w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p,
|
||||
w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792 };
|
||||
w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792, nct6102 };
|
||||
|
||||
static int timeout; /* in seconds */
|
||||
module_param(timeout, int, 0);
|
||||
@ -92,15 +93,21 @@ MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)");
|
||||
#define W83667HG_B_ID 0xb3
|
||||
#define NCT6775_ID 0xb4
|
||||
#define NCT6776_ID 0xc3
|
||||
#define NCT6102_ID 0xc4
|
||||
#define NCT6779_ID 0xc5
|
||||
#define NCT6791_ID 0xc8
|
||||
#define NCT6792_ID 0xc9
|
||||
|
||||
#define W83627HF_WDT_TIMEOUT 0xf6
|
||||
#define W83697HF_WDT_TIMEOUT 0xf4
|
||||
#define NCT6102D_WDT_TIMEOUT 0xf1
|
||||
|
||||
#define W83627HF_WDT_CONTROL 0xf5
|
||||
#define W83697HF_WDT_CONTROL 0xf3
|
||||
#define NCT6102D_WDT_CONTROL 0xf0
|
||||
|
||||
#define W836X7HF_WDT_CSR 0xf7
|
||||
#define NCT6102D_WDT_CSR 0xf2
|
||||
|
||||
static void superio_outb(int reg, int val)
|
||||
{
|
||||
@ -197,6 +204,7 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip)
|
||||
case nct6779:
|
||||
case nct6791:
|
||||
case nct6792:
|
||||
case nct6102:
|
||||
/*
|
||||
* These chips have a fixed WDTO# output pin (W83627UHG),
|
||||
* or support more than one WDTO# output pin.
|
||||
@ -229,8 +237,8 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip)
|
||||
superio_outb(cr_wdt_control, t);
|
||||
|
||||
/* reset trigger, disable keyboard & mouse turning off watchdog */
|
||||
t = superio_inb(0xF7) & ~0xD0;
|
||||
superio_outb(0xF7, t);
|
||||
t = superio_inb(cr_wdt_csr) & ~0xD0;
|
||||
superio_outb(cr_wdt_csr, t);
|
||||
|
||||
superio_exit();
|
||||
|
||||
@ -322,6 +330,7 @@ static int wdt_find(int addr)
|
||||
|
||||
cr_wdt_timeout = W83627HF_WDT_TIMEOUT;
|
||||
cr_wdt_control = W83627HF_WDT_CONTROL;
|
||||
cr_wdt_csr = W836X7HF_WDT_CSR;
|
||||
|
||||
ret = superio_enter();
|
||||
if (ret)
|
||||
@ -387,6 +396,12 @@ static int wdt_find(int addr)
|
||||
case NCT6792_ID:
|
||||
ret = nct6792;
|
||||
break;
|
||||
case NCT6102_ID:
|
||||
ret = nct6102;
|
||||
cr_wdt_timeout = NCT6102D_WDT_TIMEOUT;
|
||||
cr_wdt_control = NCT6102D_WDT_CONTROL;
|
||||
cr_wdt_csr = NCT6102D_WDT_CSR;
|
||||
break;
|
||||
case 0xff:
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
@ -422,6 +437,7 @@ static int __init wdt_init(void)
|
||||
"NCT6779",
|
||||
"NCT6791",
|
||||
"NCT6792",
|
||||
"NCT6102",
|
||||
};
|
||||
|
||||
wdt_io = 0x2e;
|
||||
|
@ -164,7 +164,7 @@ static int watchdog_restart_notifier(struct notifier_block *nb,
|
||||
|
||||
int ret;
|
||||
|
||||
ret = wdd->ops->restart(wdd);
|
||||
ret = wdd->ops->restart(wdd, action, data);
|
||||
if (ret)
|
||||
return NOTIFY_BAD;
|
||||
|
||||
@ -199,7 +199,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd)
|
||||
return -EINVAL;
|
||||
|
||||
/* Mandatory operations need to be supported */
|
||||
if (wdd->ops->start == NULL || wdd->ops->stop == NULL)
|
||||
if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms))
|
||||
return -EINVAL;
|
||||
|
||||
watchdog_check_min_max_timeout(wdd);
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||
#include <linux/fs.h> /* For file operations */
|
||||
#include <linux/init.h> /* For __init/__exit/... */
|
||||
#include <linux/jiffies.h> /* For timeout functions */
|
||||
#include <linux/kernel.h> /* For printk/panic/... */
|
||||
#include <linux/kref.h> /* For data references */
|
||||
#include <linux/miscdevice.h> /* For handling misc devices */
|
||||
@ -44,6 +45,7 @@
|
||||
#include <linux/slab.h> /* For memory functions */
|
||||
#include <linux/types.h> /* For standard types (like size_t) */
|
||||
#include <linux/watchdog.h> /* For watchdog specific items */
|
||||
#include <linux/workqueue.h> /* For workqueue */
|
||||
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
|
||||
|
||||
#include "watchdog_core.h"
|
||||
@ -61,6 +63,9 @@ struct watchdog_core_data {
|
||||
struct cdev cdev;
|
||||
struct watchdog_device *wdd;
|
||||
struct mutex lock;
|
||||
unsigned long last_keepalive;
|
||||
unsigned long last_hw_keepalive;
|
||||
struct delayed_work work;
|
||||
unsigned long status; /* Internal status bits */
|
||||
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
||||
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
||||
@ -71,6 +76,91 @@ static dev_t watchdog_devt;
|
||||
/* Reference to watchdog device behind /dev/watchdog */
|
||||
static struct watchdog_core_data *old_wd_data;
|
||||
|
||||
static struct workqueue_struct *watchdog_wq;
|
||||
|
||||
static inline bool watchdog_need_worker(struct watchdog_device *wdd)
|
||||
{
|
||||
/* All variables in milli-seconds */
|
||||
unsigned int hm = wdd->max_hw_heartbeat_ms;
|
||||
unsigned int t = wdd->timeout * 1000;
|
||||
|
||||
/*
|
||||
* A worker to generate heartbeat requests is needed if all of the
|
||||
* following conditions are true.
|
||||
* - Userspace activated the watchdog.
|
||||
* - The driver provided a value for the maximum hardware timeout, and
|
||||
* thus is aware that the framework supports generating heartbeat
|
||||
* requests.
|
||||
* - Userspace requests a longer timeout than the hardware can handle.
|
||||
*/
|
||||
return hm && ((watchdog_active(wdd) && t > hm) ||
|
||||
(t && !watchdog_active(wdd) && watchdog_hw_running(wdd)));
|
||||
}
|
||||
|
||||
static long watchdog_next_keepalive(struct watchdog_device *wdd)
|
||||
{
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
unsigned int timeout_ms = wdd->timeout * 1000;
|
||||
unsigned long keepalive_interval;
|
||||
unsigned long last_heartbeat;
|
||||
unsigned long virt_timeout;
|
||||
unsigned int hw_heartbeat_ms;
|
||||
|
||||
virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms);
|
||||
hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms);
|
||||
keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2);
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
return keepalive_interval;
|
||||
|
||||
/*
|
||||
* To ensure that the watchdog times out wdd->timeout seconds
|
||||
* after the most recent ping from userspace, the last
|
||||
* worker ping has to come in hw_heartbeat_ms before this timeout.
|
||||
*/
|
||||
last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms);
|
||||
return min_t(long, last_heartbeat - jiffies, keepalive_interval);
|
||||
}
|
||||
|
||||
static inline void watchdog_update_worker(struct watchdog_device *wdd)
|
||||
{
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
|
||||
if (watchdog_need_worker(wdd)) {
|
||||
long t = watchdog_next_keepalive(wdd);
|
||||
|
||||
if (t > 0)
|
||||
mod_delayed_work(watchdog_wq, &wd_data->work, t);
|
||||
} else {
|
||||
cancel_delayed_work(&wd_data->work);
|
||||
}
|
||||
}
|
||||
|
||||
static int __watchdog_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
unsigned long earliest_keepalive = wd_data->last_hw_keepalive +
|
||||
msecs_to_jiffies(wdd->min_hw_heartbeat_ms);
|
||||
int err;
|
||||
|
||||
if (time_is_after_jiffies(earliest_keepalive)) {
|
||||
mod_delayed_work(watchdog_wq, &wd_data->work,
|
||||
earliest_keepalive - jiffies);
|
||||
return 0;
|
||||
}
|
||||
|
||||
wd_data->last_hw_keepalive = jiffies;
|
||||
|
||||
if (wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
||||
else
|
||||
err = wdd->ops->start(wdd); /* restart watchdog */
|
||||
|
||||
watchdog_update_worker(wdd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* watchdog_ping: ping the watchdog.
|
||||
* @wdd: the watchdog device to ping
|
||||
@ -85,17 +175,28 @@ static struct watchdog_core_data *old_wd_data;
|
||||
|
||||
static int watchdog_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
int err;
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
if (!watchdog_active(wdd) && !watchdog_hw_running(wdd))
|
||||
return 0;
|
||||
|
||||
if (wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
||||
else
|
||||
err = wdd->ops->start(wdd); /* restart watchdog */
|
||||
wd_data->last_keepalive = jiffies;
|
||||
return __watchdog_ping(wdd);
|
||||
}
|
||||
|
||||
return err;
|
||||
static void watchdog_ping_work(struct work_struct *work)
|
||||
{
|
||||
struct watchdog_core_data *wd_data;
|
||||
struct watchdog_device *wdd;
|
||||
|
||||
wd_data = container_of(to_delayed_work(work), struct watchdog_core_data,
|
||||
work);
|
||||
|
||||
mutex_lock(&wd_data->lock);
|
||||
wdd = wd_data->wdd;
|
||||
if (wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd)))
|
||||
__watchdog_ping(wdd);
|
||||
mutex_unlock(&wd_data->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -111,14 +212,23 @@ static int watchdog_ping(struct watchdog_device *wdd)
|
||||
|
||||
static int watchdog_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
unsigned long started_at;
|
||||
int err;
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
return 0;
|
||||
|
||||
err = wdd->ops->start(wdd);
|
||||
if (err == 0)
|
||||
started_at = jiffies;
|
||||
if (watchdog_hw_running(wdd) && wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd);
|
||||
else
|
||||
err = wdd->ops->start(wdd);
|
||||
if (err == 0) {
|
||||
set_bit(WDOG_ACTIVE, &wdd->status);
|
||||
wd_data->last_keepalive = started_at;
|
||||
watchdog_update_worker(wdd);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -137,7 +247,7 @@ static int watchdog_start(struct watchdog_device *wdd)
|
||||
|
||||
static int watchdog_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
int err;
|
||||
int err = 0;
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
return 0;
|
||||
@ -148,9 +258,15 @@ static int watchdog_stop(struct watchdog_device *wdd)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
err = wdd->ops->stop(wdd);
|
||||
if (err == 0)
|
||||
if (wdd->ops->stop)
|
||||
err = wdd->ops->stop(wdd);
|
||||
else
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
|
||||
if (err == 0) {
|
||||
clear_bit(WDOG_ACTIVE, &wdd->status);
|
||||
watchdog_update_worker(wdd);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -183,13 +299,22 @@ static unsigned int watchdog_get_status(struct watchdog_device *wdd)
|
||||
static int watchdog_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT))
|
||||
int err = 0;
|
||||
|
||||
if (!(wdd->info->options & WDIOF_SETTIMEOUT))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (watchdog_timeout_invalid(wdd, timeout))
|
||||
return -EINVAL;
|
||||
|
||||
return wdd->ops->set_timeout(wdd, timeout);
|
||||
if (wdd->ops->set_timeout)
|
||||
err = wdd->ops->set_timeout(wdd, timeout);
|
||||
else
|
||||
wdd->timeout = timeout;
|
||||
|
||||
watchdog_update_worker(wdd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -538,7 +663,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||
* If the /dev/watchdog device is open, we don't want the module
|
||||
* to be unloaded.
|
||||
*/
|
||||
if (!try_module_get(wdd->ops->owner)) {
|
||||
if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) {
|
||||
err = -EBUSY;
|
||||
goto out_clear;
|
||||
}
|
||||
@ -549,7 +674,8 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||
|
||||
file->private_data = wd_data;
|
||||
|
||||
kref_get(&wd_data->kref);
|
||||
if (!watchdog_hw_running(wdd))
|
||||
kref_get(&wd_data->kref);
|
||||
|
||||
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
|
||||
return nonseekable_open(inode, file);
|
||||
@ -585,6 +711,7 @@ static int watchdog_release(struct inode *inode, struct file *file)
|
||||
struct watchdog_core_data *wd_data = file->private_data;
|
||||
struct watchdog_device *wdd;
|
||||
int err = -EBUSY;
|
||||
bool running;
|
||||
|
||||
mutex_lock(&wd_data->lock);
|
||||
|
||||
@ -609,14 +736,24 @@ static int watchdog_release(struct inode *inode, struct file *file)
|
||||
watchdog_ping(wdd);
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&wd_data->work);
|
||||
watchdog_update_worker(wdd);
|
||||
|
||||
/* make sure that /dev/watchdog can be re-opened */
|
||||
clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
|
||||
|
||||
done:
|
||||
running = wdd && watchdog_hw_running(wdd);
|
||||
mutex_unlock(&wd_data->lock);
|
||||
/* Allow the owner module to be unloaded again */
|
||||
module_put(wd_data->cdev.owner);
|
||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
/*
|
||||
* Allow the owner module to be unloaded again unless the watchdog
|
||||
* is still running. If the watchdog is still running, it can not
|
||||
* be stopped, and its driver must not be unloaded.
|
||||
*/
|
||||
if (!running) {
|
||||
module_put(wd_data->cdev.owner);
|
||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -658,6 +795,11 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
wd_data->wdd = wdd;
|
||||
wdd->wd_data = wd_data;
|
||||
|
||||
if (!watchdog_wq)
|
||||
return -ENODEV;
|
||||
|
||||
INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work);
|
||||
|
||||
if (wdd->id == 0) {
|
||||
old_wd_data = wd_data;
|
||||
watchdog_miscdev.parent = wdd->parent;
|
||||
@ -688,8 +830,23 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
old_wd_data = NULL;
|
||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
|
||||
/* Record time of most recent heartbeat as 'just before now'. */
|
||||
wd_data->last_hw_keepalive = jiffies - 1;
|
||||
|
||||
/*
|
||||
* If the watchdog is running, prevent its driver from being unloaded,
|
||||
* and schedule an immediate ping.
|
||||
*/
|
||||
if (watchdog_hw_running(wdd)) {
|
||||
__module_get(wdd->ops->owner);
|
||||
kref_get(&wd_data->kref);
|
||||
queue_delayed_work(watchdog_wq, &wd_data->work, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -715,6 +872,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||
wdd->wd_data = NULL;
|
||||
mutex_unlock(&wd_data->lock);
|
||||
|
||||
cancel_delayed_work_sync(&wd_data->work);
|
||||
|
||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
|
||||
@ -780,6 +939,13 @@ int __init watchdog_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
watchdog_wq = alloc_workqueue("watchdogd",
|
||||
WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
|
||||
if (!watchdog_wq) {
|
||||
pr_err("Failed to create watchdog workqueue\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = class_register(&watchdog_class);
|
||||
if (err < 0) {
|
||||
pr_err("couldn't register class\n");
|
||||
@ -806,4 +972,5 @@ void __exit watchdog_dev_exit(void)
|
||||
{
|
||||
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
|
||||
class_unregister(&watchdog_class);
|
||||
destroy_workqueue(watchdog_wq);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
#define ZIIRAVE_STATE_OFF 0x1
|
||||
#define ZIIRAVE_STATE_ON 0x2
|
||||
|
||||
static char *ziirave_reasons[] = {"power cycle", "triggered", NULL, NULL,
|
||||
static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL,
|
||||
"host request", NULL, "illegal configuration",
|
||||
"illegal instruction", "illegal trap",
|
||||
"unknown"};
|
||||
|
@ -10,8 +10,9 @@
|
||||
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <uapi/linux/watchdog.h>
|
||||
|
||||
@ -46,7 +47,7 @@ struct watchdog_ops {
|
||||
unsigned int (*status)(struct watchdog_device *);
|
||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
||||
int (*restart)(struct watchdog_device *);
|
||||
int (*restart)(struct watchdog_device *, unsigned long, void *);
|
||||
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||
};
|
||||
|
||||
@ -61,14 +62,21 @@ struct watchdog_ops {
|
||||
* @bootstatus: Status of the watchdog device at boot.
|
||||
* @timeout: The watchdog devices timeout value (in seconds).
|
||||
* @min_timeout:The watchdog devices minimum timeout value (in seconds).
|
||||
* @max_timeout:The watchdog devices maximum timeout value (in seconds).
|
||||
* @max_timeout:The watchdog devices maximum timeout value (in seconds)
|
||||
* as configurable from user space. Only relevant if
|
||||
* max_hw_heartbeat_ms is not provided.
|
||||
* @min_hw_heartbeat_ms:
|
||||
* Minimum time between heartbeats, in milli-seconds.
|
||||
* @max_hw_heartbeat_ms:
|
||||
* Hardware limit for maximum timeout, in milli-seconds.
|
||||
* Replaces max_timeout if specified.
|
||||
* @reboot_nb: The notifier block to stop watchdog on reboot.
|
||||
* @restart_nb: The notifier block to register a restart function.
|
||||
* @driver_data:Pointer to the drivers private data.
|
||||
* @wd_data: Pointer to watchdog core internal data.
|
||||
* @status: Field that contains the devices internal status bits.
|
||||
* @deferred: entry in wtd_deferred_reg_list which is used to
|
||||
* register early initialized watchdogs.
|
||||
* @deferred: Entry in wtd_deferred_reg_list which is used to
|
||||
* register early initialized watchdogs.
|
||||
*
|
||||
* The watchdog_device structure contains all information about a
|
||||
* watchdog timer device.
|
||||
@ -89,6 +97,8 @@ struct watchdog_device {
|
||||
unsigned int timeout;
|
||||
unsigned int min_timeout;
|
||||
unsigned int max_timeout;
|
||||
unsigned int min_hw_heartbeat_ms;
|
||||
unsigned int max_hw_heartbeat_ms;
|
||||
struct notifier_block reboot_nb;
|
||||
struct notifier_block restart_nb;
|
||||
void *driver_data;
|
||||
@ -98,6 +108,7 @@ struct watchdog_device {
|
||||
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
|
||||
#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */
|
||||
#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */
|
||||
#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */
|
||||
struct list_head deferred;
|
||||
};
|
||||
|
||||
@ -110,6 +121,15 @@ static inline bool watchdog_active(struct watchdog_device *wdd)
|
||||
return test_bit(WDOG_ACTIVE, &wdd->status);
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the following function to check whether or not the hardware watchdog
|
||||
* is running
|
||||
*/
|
||||
static inline bool watchdog_hw_running(struct watchdog_device *wdd)
|
||||
{
|
||||
return test_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
}
|
||||
|
||||
/* Use the following function to set the nowayout feature */
|
||||
static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
|
||||
{
|
||||
@ -128,13 +148,18 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne
|
||||
{
|
||||
/*
|
||||
* The timeout is invalid if
|
||||
* - the requested value is larger than UINT_MAX / 1000
|
||||
* (since internal calculations are done in milli-seconds),
|
||||
* or
|
||||
* - the requested value is smaller than the configured minimum timeout,
|
||||
* or
|
||||
* - a maximum timeout is configured, and the requested value is larger
|
||||
* than the maximum timeout.
|
||||
* - a maximum hardware timeout is not configured, a maximum timeout
|
||||
* is configured, and the requested value is larger than the
|
||||
* configured maximum timeout.
|
||||
*/
|
||||
return t < wdd->min_timeout ||
|
||||
(wdd->max_timeout && t > wdd->max_timeout);
|
||||
return t > UINT_MAX / 1000 || t < wdd->min_timeout ||
|
||||
(!wdd->max_hw_heartbeat_ms && wdd->max_timeout &&
|
||||
t > wdd->max_timeout);
|
||||
}
|
||||
|
||||
/* Use the following functions to manipulate watchdog driver specific data */
|
||||
|
Loading…
Reference in New Issue
Block a user