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
|
@ -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 timeout;
|
||||||
unsigned int min_timeout;
|
unsigned int min_timeout;
|
||||||
unsigned int max_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 reboot_nb;
|
||||||
struct notifier_block restart_nb;
|
struct notifier_block restart_nb;
|
||||||
void *driver_data;
|
void *driver_data;
|
||||||
|
@ -73,8 +75,21 @@ It contains following fields:
|
||||||
additional information about the watchdog timer itself. (Like it's unique name)
|
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.
|
* ops: a pointer to the list of watchdog operations that the watchdog supports.
|
||||||
* timeout: the watchdog timer's timeout value (in seconds).
|
* 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).
|
* 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
|
* reboot_nb: notifier block that is registered for reboot notifications, for
|
||||||
internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core
|
internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core
|
||||||
will stop the watchdog on such notifications.
|
will stop the watchdog on such notifications.
|
||||||
|
@ -123,17 +138,20 @@ are:
|
||||||
device.
|
device.
|
||||||
The routine needs a pointer to the watchdog timer device structure as a
|
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.
|
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
|
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
|
all other routines/operations are optional. They only need to be provided if
|
||||||
they are supported. These optional routines/operations are:
|
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
|
* ping: this is the routine that sends a keepalive ping to the watchdog timer
|
||||||
hardware.
|
hardware.
|
||||||
The routine needs a pointer to the watchdog timer device structure as a
|
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
|
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
|
routine should set the timeout value of the watchdog_device to the
|
||||||
achieved timeout value (which may be different from the requested one
|
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
|
(Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the
|
||||||
watchdog's info structure).
|
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.
|
* 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
|
* restart: this routine restarts the machine. It returns 0 on success or a
|
||||||
negative errno code for failure.
|
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
|
The status bits should (preferably) be set with the set_bit and clear_bit alike
|
||||||
bit-operations. The status bits that are defined are:
|
bit-operations. The status bits that are defined are:
|
||||||
* WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device
|
* 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
|
is active or not from user perspective. User space is expected to send
|
||||||
set this status bit (Note: when you register the watchdog timer device with
|
heartbeat requests to the driver while this flag is set.
|
||||||
this bit set, then opening /dev/watchdog will skip the start operation)
|
|
||||||
* WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog.
|
* 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.
|
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
|
To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog
|
||||||
timer device) you can either:
|
timer device) you can either:
|
||||||
|
|
|
@ -200,6 +200,11 @@ mv64x60_wdt:
|
||||||
nowayout: Watchdog cannot be stopped once started
|
nowayout: Watchdog cannot be stopped once started
|
||||||
(default=kernel config parameter)
|
(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:
|
nuc900_wdt:
|
||||||
heartbeat: Watchdog heartbeats in seconds.
|
heartbeat: Watchdog heartbeats in seconds.
|
||||||
(default = 15)
|
(default = 15)
|
||||||
|
@ -284,6 +289,13 @@ sbc_fitpc2_wdt:
|
||||||
margin: Watchdog margin in seconds (default 60s)
|
margin: Watchdog margin in seconds (default 60s)
|
||||||
nowayout: Watchdog cannot be stopped once started
|
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:
|
sc1200wdt:
|
||||||
isapnp: When set to 0 driver ISA PnP support will be disabled (default=1)
|
isapnp: When set to 0 driver ISA PnP support will be disabled (default=1)
|
||||||
io: io port
|
io: io port
|
||||||
|
|
|
@ -11965,6 +11965,12 @@ M: David Härdeman <david@hardeman.nu>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: drivers/media/rc/winbond-cir.c
|
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
|
WINSYSTEMS WS16C48 GPIO DRIVER
|
||||||
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
M: William Breathitt Gray <vilhelm.gray@gmail.com>
|
||||||
L: linux-gpio@vger.kernel.org
|
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));
|
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)
|
static int __init lpc32xx_check_uid(void)
|
||||||
{
|
{
|
||||||
u32 uid[4];
|
u32 uid[4];
|
||||||
|
|
|
@ -30,7 +30,6 @@ extern void lpc32xx_timer_init(void);
|
||||||
extern void __init lpc32xx_init_irq(void);
|
extern void __init lpc32xx_init_irq(void);
|
||||||
extern void __init lpc32xx_map_io(void);
|
extern void __init lpc32xx_map_io(void);
|
||||||
extern void __init lpc32xx_serial_init(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_time = lpc32xx_timer_init,
|
||||||
.init_machine = lpc3250_machine_init,
|
.init_machine = lpc3250_machine_init,
|
||||||
.dt_compat = lpc32xx_dt_compat,
|
.dt_compat = lpc32xx_dt_compat,
|
||||||
.restart = lpc23xx_restart,
|
|
||||||
MACHINE_END
|
MACHINE_END
|
||||||
|
|
|
@ -202,6 +202,26 @@ config ARM_SP805_WATCHDOG
|
||||||
ARM Primecell SP805 Watchdog timer. This will reboot your system when
|
ARM Primecell SP805 Watchdog timer. This will reboot your system when
|
||||||
the timeout is reached.
|
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
|
config ASM9260_WATCHDOG
|
||||||
tristate "Alphascale ASM9260 watchdog"
|
tristate "Alphascale ASM9260 watchdog"
|
||||||
depends on MACH_ASM9260
|
depends on MACH_ASM9260
|
||||||
|
@ -330,6 +350,7 @@ config SA1100_WATCHDOG
|
||||||
config DW_WATCHDOG
|
config DW_WATCHDOG
|
||||||
tristate "Synopsys DesignWare watchdog"
|
tristate "Synopsys DesignWare watchdog"
|
||||||
depends on HAS_IOMEM
|
depends on HAS_IOMEM
|
||||||
|
select WATCHDOG_CORE
|
||||||
help
|
help
|
||||||
Say Y here if to include support for the Synopsys DesignWare
|
Say Y here if to include support for the Synopsys DesignWare
|
||||||
watchdog timer found in many chips.
|
watchdog timer found in many chips.
|
||||||
|
@ -399,6 +420,7 @@ config DAVINCI_WATCHDOG
|
||||||
config ORION_WATCHDOG
|
config ORION_WATCHDOG
|
||||||
tristate "Orion watchdog"
|
tristate "Orion watchdog"
|
||||||
depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU
|
depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU
|
||||||
|
depends on ARM
|
||||||
select WATCHDOG_CORE
|
select WATCHDOG_CORE
|
||||||
help
|
help
|
||||||
Say Y here if to include support for the watchdog timer
|
Say Y here if to include support for the watchdog timer
|
||||||
|
@ -468,6 +490,7 @@ config NUC900_WATCHDOG
|
||||||
config TS4800_WATCHDOG
|
config TS4800_WATCHDOG
|
||||||
tristate "TS-4800 Watchdog"
|
tristate "TS-4800 Watchdog"
|
||||||
depends on HAS_IOMEM && OF
|
depends on HAS_IOMEM && OF
|
||||||
|
depends on SOC_IMX51 || COMPILE_TEST
|
||||||
select WATCHDOG_CORE
|
select WATCHDOG_CORE
|
||||||
select MFD_SYSCON
|
select MFD_SYSCON
|
||||||
help
|
help
|
||||||
|
@ -713,6 +736,15 @@ config ALIM7101_WDT
|
||||||
|
|
||||||
Most people will say N.
|
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
|
config F71808E_WDT
|
||||||
tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog"
|
tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog"
|
||||||
depends on X86
|
depends on X86
|
||||||
|
@ -1142,6 +1174,7 @@ config W83627HF_WDT
|
||||||
NCT6779
|
NCT6779
|
||||||
NCT6791
|
NCT6791
|
||||||
NCT6792
|
NCT6792
|
||||||
|
NCT6102D/04D/06D
|
||||||
|
|
||||||
This watchdog simply watches your kernel to make sure it doesn't
|
This watchdog simply watches your kernel to make sure it doesn't
|
||||||
freeze, and if it does, it reboots your computer after a certain
|
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:
|
To compile this driver as a module, choose M here:
|
||||||
the module will be called mei_wdt.
|
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
|
# M32R Architecture
|
||||||
|
|
||||||
# M68K Architecture
|
# M68K Architecture
|
||||||
|
@ -1392,10 +1436,12 @@ config BCM7038_WDT
|
||||||
tristate "BCM7038 Watchdog"
|
tristate "BCM7038 Watchdog"
|
||||||
select WATCHDOG_CORE
|
select WATCHDOG_CORE
|
||||||
depends on HAS_IOMEM
|
depends on HAS_IOMEM
|
||||||
|
depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
|
||||||
help
|
help
|
||||||
Watchdog driver for the built-in hardware in Broadcom 7038 SoCs.
|
Watchdog driver for the built-in hardware in Broadcom 7038 and
|
||||||
|
later SoCs used in set-top boxes. BCM7038 was made public
|
||||||
Say 'Y or 'M' here to enable the driver.
|
during the 2004 CES, and since then, many Broadcom chips use this
|
||||||
|
watchdog block, including some cable modem chips.
|
||||||
|
|
||||||
config IMGPDC_WDT
|
config IMGPDC_WDT
|
||||||
tristate "Imagination Technologies PDC Watchdog Timer"
|
tristate "Imagination Technologies PDC Watchdog Timer"
|
||||||
|
|
|
@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
|
||||||
|
|
||||||
# ARM Architecture
|
# ARM Architecture
|
||||||
obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o
|
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_ASM9260_WATCHDOG) += asm9260_wdt.o
|
||||||
obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
|
obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
|
||||||
obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_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_ADVANTECH_WDT) += advantechwdt.o
|
||||||
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
|
||||||
obj-$(CONFIG_ALIM7101_WDT) += alim7101_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_F71808E_WDT) += f71808e_wdt.o
|
||||||
obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o
|
obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o
|
||||||
obj-$(CONFIG_GEODE_WDT) += geodewdt.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_SCU_WATCHDOG) += intel_scu_watchdog.o
|
||||||
obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
|
obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o
|
||||||
obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
|
obj-$(CONFIG_INTEL_MEI_WDT) += mei_wdt.o
|
||||||
|
obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
|
||||||
|
|
||||||
# M32R Architecture
|
# M32R Architecture
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,11 @@ static int atlas7_wdt_probe(struct platform_device *pdev)
|
||||||
writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL);
|
writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL);
|
||||||
|
|
||||||
wdt->tick_rate = clk_get_rate(clk);
|
wdt->tick_rate = clk_get_rate(clk);
|
||||||
|
if (!wdt->tick_rate) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err1;
|
||||||
|
}
|
||||||
|
|
||||||
wdt->clk = clk;
|
wdt->clk = clk;
|
||||||
atlas7_wdd.min_timeout = 1;
|
atlas7_wdd.min_timeout = 1;
|
||||||
atlas7_wdd.max_timeout = UINT_MAX / wdt->tick_rate;
|
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;
|
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);
|
struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,8 @@ static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
|
||||||
return ret;
|
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);
|
struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
|
@ -48,7 +48,8 @@ static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks)
|
||||||
spin_unlock_irqrestore(&wdt->lock, flags);
|
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);
|
struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,8 @@
|
||||||
* and these are a function of the input clock frequency.
|
* and these are a function of the input clock frequency.
|
||||||
*
|
*
|
||||||
* The DesignWare watchdog cannot be stopped once it has been started so we
|
* 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.
|
* do not implement a stop function. The watchdog core will continue to send
|
||||||
* If we receive an expected close for the watchdog then we keep the timer
|
* heartbeat requests after the watchdog device has been closed.
|
||||||
* running, otherwise the timer is stopped and the watchdog will expire.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
@ -22,12 +21,9 @@
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/device.h>
|
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/fs.h>
|
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/miscdevice.h>
|
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/moduleparam.h>
|
#include <linux/moduleparam.h>
|
||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
|
@ -35,8 +31,6 @@
|
||||||
#include <linux/pm.h>
|
#include <linux/pm.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
#include <linux/timer.h>
|
|
||||||
#include <linux/uaccess.h>
|
|
||||||
#include <linux/watchdog.h>
|
#include <linux/watchdog.h>
|
||||||
|
|
||||||
#define WDOG_CONTROL_REG_OFFSET 0x00
|
#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 "
|
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
|
||||||
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||||
|
|
||||||
#define WDT_TIMEOUT (HZ / 2)
|
struct dw_wdt {
|
||||||
|
|
||||||
static struct {
|
|
||||||
void __iomem *regs;
|
void __iomem *regs;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
unsigned long in_use;
|
|
||||||
unsigned long next_heartbeat;
|
|
||||||
struct timer_list timer;
|
|
||||||
int expect_close;
|
|
||||||
struct notifier_block restart_handler;
|
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;
|
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
|
* There are 16 possible timeout values in 0..15 where the number of
|
||||||
* cycles is 2 ^ (16 + i) and the watchdog counts down.
|
* 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);
|
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;
|
int i, top_val = DW_WDT_MAX_TOP;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s)
|
||||||
* always look for >=.
|
* always look for >=.
|
||||||
*/
|
*/
|
||||||
for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
|
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;
|
top_val = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s)
|
||||||
* effectively get a pat of the watchdog right here.
|
* effectively get a pat of the watchdog right here.
|
||||||
*/
|
*/
|
||||||
writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
|
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);
|
||||||
|
|
||||||
/*
|
wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);
|
||||||
* 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();
|
|
||||||
|
|
||||||
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,
|
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;
|
u32 val;
|
||||||
|
|
||||||
writel(0, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
|
dw_wdt = container_of(this, struct dw_wdt, restart_handler);
|
||||||
val = readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
|
|
||||||
|
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)
|
if (val & WDOG_CONTROL_REG_WDT_EN_MASK)
|
||||||
writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
|
writel(WDOG_COUNTER_RESTART_KICK_VALUE,
|
||||||
WDOG_COUNTER_RESTART_REG_OFFSET);
|
dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
|
||||||
else
|
else
|
||||||
writel(WDOG_CONTROL_REG_WDT_EN_MASK,
|
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... */
|
/* wait for reset to assert... */
|
||||||
mdelay(500);
|
mdelay(500);
|
||||||
|
@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this,
|
||||||
return NOTIFY_DONE;
|
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) ||
|
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||||
(!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");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int dw_wdt_open(struct inode *inode, struct file *filp)
|
return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
|
||||||
{
|
clk_get_rate(dw_wdt->clk);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct watchdog_info dw_wdt_ident = {
|
static const struct watchdog_info dw_wdt_ident = {
|
||||||
|
@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = {
|
||||||
.identity = "Synopsys DesignWare Watchdog",
|
.identity = "Synopsys DesignWare Watchdog",
|
||||||
};
|
};
|
||||||
|
|
||||||
static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
static const struct watchdog_ops dw_wdt_ops = {
|
||||||
{
|
.owner = THIS_MODULE,
|
||||||
unsigned long val;
|
.start = dw_wdt_start,
|
||||||
int timeout;
|
.ping = dw_wdt_ping,
|
||||||
|
.set_timeout = dw_wdt_set_timeout,
|
||||||
switch (cmd) {
|
.get_timeleft = dw_wdt_get_timeleft,
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int dw_wdt_suspend(struct device *dev)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dw_wdt_resume(struct device *dev)
|
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)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
dw_wdt_keepalive();
|
dw_wdt_ping(&dw_wdt->wdd);
|
||||||
|
|
||||||
return 0;
|
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 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)
|
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;
|
int ret;
|
||||||
struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
|
|
||||||
dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem);
|
dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
|
||||||
if (IS_ERR(dw_wdt.regs))
|
if (!dw_wdt)
|
||||||
return PTR_ERR(dw_wdt.regs);
|
return -ENOMEM;
|
||||||
|
|
||||||
dw_wdt.clk = devm_clk_get(&pdev->dev, NULL);
|
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
if (IS_ERR(dw_wdt.clk))
|
dw_wdt->regs = devm_ioremap_resource(dev, mem);
|
||||||
return PTR_ERR(dw_wdt.clk);
|
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)
|
if (ret)
|
||||||
return 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)
|
if (ret)
|
||||||
goto out_disable_clk;
|
goto out_disable_clk;
|
||||||
|
|
||||||
dw_wdt.restart_handler.notifier_call = dw_wdt_restart_handle;
|
dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle;
|
||||||
dw_wdt.restart_handler.priority = 128;
|
dw_wdt->restart_handler.priority = 128;
|
||||||
ret = register_restart_handler(&dw_wdt.restart_handler);
|
ret = register_restart_handler(&dw_wdt->restart_handler);
|
||||||
if (ret)
|
if (ret)
|
||||||
pr_warn("cannot register restart handler\n");
|
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;
|
return 0;
|
||||||
|
|
||||||
out_disable_clk:
|
out_disable_clk:
|
||||||
clk_disable_unprepare(dw_wdt.clk);
|
clk_disable_unprepare(dw_wdt->clk);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int dw_wdt_drv_remove(struct platform_device *pdev)
|
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);
|
unregister_restart_handler(&dw_wdt->restart_handler);
|
||||||
|
watchdog_unregister_device(&dw_wdt->wdd);
|
||||||
clk_disable_unprepare(dw_wdt.clk);
|
clk_disable_unprepare(dw_wdt->clk);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
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);
|
struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,12 @@
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
#include <linux/jiffies.h>
|
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/moduleparam.h>
|
#include <linux/moduleparam.h>
|
||||||
#include <linux/of_address.h>
|
#include <linux/of_address.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/regmap.h>
|
#include <linux/regmap.h>
|
||||||
#include <linux/timer.h>
|
|
||||||
#include <linux/watchdog.h>
|
#include <linux/watchdog.h>
|
||||||
|
|
||||||
#define DRIVER_NAME "imx2-wdt"
|
#define DRIVER_NAME "imx2-wdt"
|
||||||
|
@ -60,7 +58,6 @@
|
||||||
struct imx2_wdt_device {
|
struct imx2_wdt_device {
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct timer_list timer; /* Pings the watchdog when closed */
|
|
||||||
struct watchdog_device wdog;
|
struct watchdog_device wdog;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +77,8 @@ static const struct watchdog_info imx2_wdt_info = {
|
||||||
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
|
.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);
|
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||||
unsigned int wcr_enable = IMX2_WDT_WCR_WDE;
|
unsigned int wcr_enable = IMX2_WDT_WCR_WDE;
|
||||||
|
@ -146,16 +144,6 @@ static int imx2_wdt_ping(struct watchdog_device *wdog)
|
||||||
return 0;
|
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,
|
static int imx2_wdt_set_timeout(struct watchdog_device *wdog,
|
||||||
unsigned int new_timeout)
|
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);
|
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||||
|
|
||||||
if (imx2_wdt_is_running(wdev)) {
|
if (imx2_wdt_is_running(wdev))
|
||||||
/* delete the timer that pings the watchdog after close */
|
|
||||||
del_timer_sync(&wdev->timer);
|
|
||||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||||
} else
|
else
|
||||||
imx2_wdt_setup(wdog);
|
imx2_wdt_setup(wdog);
|
||||||
|
|
||||||
|
set_bit(WDOG_HW_RUNNING, &wdog->status);
|
||||||
|
|
||||||
return imx2_wdt_ping(wdog);
|
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 = {
|
static const struct watchdog_ops imx2_wdt_ops = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.start = imx2_wdt_start,
|
.start = imx2_wdt_start,
|
||||||
.stop = imx2_wdt_stop,
|
|
||||||
.ping = imx2_wdt_ping,
|
.ping = imx2_wdt_ping,
|
||||||
.set_timeout = imx2_wdt_set_timeout,
|
.set_timeout = imx2_wdt_set_timeout,
|
||||||
.restart = imx2_wdt_restart,
|
.restart = imx2_wdt_restart,
|
||||||
|
@ -253,7 +220,7 @@ static int __init imx2_wdt_probe(struct platform_device *pdev)
|
||||||
wdog->info = &imx2_wdt_info;
|
wdog->info = &imx2_wdt_info;
|
||||||
wdog->ops = &imx2_wdt_ops;
|
wdog->ops = &imx2_wdt_ops;
|
||||||
wdog->min_timeout = 1;
|
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;
|
wdog->parent = &pdev->dev;
|
||||||
|
|
||||||
ret = clk_prepare_enable(wdev->clk);
|
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_set_restart_priority(wdog, 128);
|
||||||
watchdog_init_timeout(wdog, timeout, &pdev->dev);
|
watchdog_init_timeout(wdog, timeout, &pdev->dev);
|
||||||
|
|
||||||
setup_timer(&wdev->timer, imx2_wdt_timer_ping, (unsigned long)wdog);
|
if (imx2_wdt_is_running(wdev)) {
|
||||||
|
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||||
imx2_wdt_ping_if_active(wdog);
|
set_bit(WDOG_HW_RUNNING, &wdog->status);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Disable the watchdog power down counter at boot. Otherwise the power
|
* 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);
|
watchdog_unregister_device(wdog);
|
||||||
|
|
||||||
if (imx2_wdt_is_running(wdev)) {
|
if (imx2_wdt_is_running(wdev)) {
|
||||||
del_timer_sync(&wdev->timer);
|
|
||||||
imx2_wdt_ping(wdog);
|
imx2_wdt_ping(wdog);
|
||||||
dev_crit(&pdev->dev, "Device removed: Expect reboot!\n");
|
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)) {
|
if (imx2_wdt_is_running(wdev)) {
|
||||||
/*
|
/*
|
||||||
* We are running, we need to delete the timer but will
|
* We are running, configure max timeout before reboot
|
||||||
* give max timeout before reboot will take place
|
* will take place.
|
||||||
*/
|
*/
|
||||||
del_timer_sync(&wdev->timer);
|
|
||||||
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||||
imx2_wdt_ping(wdog);
|
imx2_wdt_ping(wdog);
|
||||||
dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n");
|
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)) {
|
if (imx2_wdt_is_running(wdev)) {
|
||||||
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||||
imx2_wdt_ping(wdog);
|
imx2_wdt_ping(wdog);
|
||||||
|
|
||||||
/* The watchdog is not active */
|
|
||||||
if (!watchdog_active(wdog))
|
|
||||||
del_timer_sync(&wdev->timer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clk_disable_unprepare(wdev->clk);
|
clk_disable_unprepare(wdev->clk);
|
||||||
|
@ -373,19 +335,10 @@ static int imx2_wdt_resume(struct device *dev)
|
||||||
* watchdog again.
|
* watchdog again.
|
||||||
*/
|
*/
|
||||||
imx2_wdt_setup(wdog);
|
imx2_wdt_setup(wdog);
|
||||||
|
}
|
||||||
|
if (imx2_wdt_is_running(wdev)) {
|
||||||
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
imx2_wdt_set_timeout(wdog, wdog->timeout);
|
||||||
imx2_wdt_ping(wdog);
|
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;
|
return 0;
|
||||||
|
|
|
@ -153,7 +153,8 @@ static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev)
|
||||||
return 0;
|
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);
|
struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
|
@ -62,7 +62,8 @@ struct meson_wdt_dev {
|
||||||
const struct meson_wdt_data *data;
|
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);
|
struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
u32 tc_reboot = MESON_WDT_DC_RESET;
|
u32 tc_reboot = MESON_WDT_DC_RESET;
|
||||||
|
|
|
@ -31,7 +31,8 @@ struct moxart_wdt_dev {
|
||||||
|
|
||||||
static int heartbeat;
|
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);
|
struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,8 @@ struct mtk_wdt_dev {
|
||||||
void __iomem *wdt_base;
|
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);
|
struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
void __iomem *wdt_base;
|
void __iomem *wdt_base;
|
||||||
|
|
|
@ -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/slab.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/reboot.h>
|
||||||
#include <mach/hardware.h>
|
#include <mach/hardware.h>
|
||||||
|
|
||||||
/* WatchDog Timer - Chapter 23 Page 207 */
|
/* WatchDog Timer - Chapter 23 Page 207 */
|
||||||
|
@ -124,6 +126,41 @@ static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd,
|
||||||
return 0;
|
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 = {
|
static const struct watchdog_info pnx4008_wdt_ident = {
|
||||||
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
|
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
|
||||||
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
|
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
|
||||||
|
@ -135,6 +172,7 @@ static const struct watchdog_ops pnx4008_wdt_ops = {
|
||||||
.start = pnx4008_wdt_start,
|
.start = pnx4008_wdt_start,
|
||||||
.stop = pnx4008_wdt_stop,
|
.stop = pnx4008_wdt_stop,
|
||||||
.set_timeout = pnx4008_wdt_set_timeout,
|
.set_timeout = pnx4008_wdt_set_timeout,
|
||||||
|
.restart = pnx4008_restart_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct watchdog_device pnx4008_wdd = {
|
static struct watchdog_device pnx4008_wdd = {
|
||||||
|
@ -169,6 +207,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
|
||||||
WDIOF_CARDRESET : 0;
|
WDIOF_CARDRESET : 0;
|
||||||
pnx4008_wdd.parent = &pdev->dev;
|
pnx4008_wdd.parent = &pdev->dev;
|
||||||
watchdog_set_nowayout(&pnx4008_wdd, nowayout);
|
watchdog_set_nowayout(&pnx4008_wdd, nowayout);
|
||||||
|
watchdog_set_restart_priority(&pnx4008_wdd, 128);
|
||||||
|
|
||||||
pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */
|
pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */
|
||||||
|
|
||||||
|
@ -178,8 +217,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev)
|
||||||
goto disable_clk;
|
goto disable_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n",
|
dev_info(&pdev->dev, "heartbeat %d sec\n", pnx4008_wdd.timeout);
|
||||||
pnx4008_wdd.timeout);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,8 @@ static int qcom_wdt_set_timeout(struct watchdog_device *wdd,
|
||||||
return qcom_wdt_start(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);
|
struct qcom_wdt *wdt = to_qcom_wdt(wdd);
|
||||||
u32 timeout;
|
u32 timeout;
|
||||||
|
|
|
@ -237,7 +237,7 @@ static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
/* Fall through */
|
/* Fall through */
|
||||||
case WDIOC_GETTIMEOUT:
|
case WDIOC_GETTIMEOUT:
|
||||||
return copy_to_user(argp, &timeout, sizeof(int));
|
return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
|
||||||
default:
|
default:
|
||||||
return -ENOTTY;
|
return -ENOTTY;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@
|
||||||
#define S3C2410_WTDAT 0x04
|
#define S3C2410_WTDAT 0x04
|
||||||
#define S3C2410_WTCNT 0x08
|
#define S3C2410_WTCNT 0x08
|
||||||
|
|
||||||
|
#define S3C2410_WTCNT_MAXCNT 0xffff
|
||||||
|
|
||||||
#define S3C2410_WTCON_RSTEN (1 << 0)
|
#define S3C2410_WTCON_RSTEN (1 << 0)
|
||||||
#define S3C2410_WTCON_INTEN (1 << 2)
|
#define S3C2410_WTCON_INTEN (1 << 2)
|
||||||
#define S3C2410_WTCON_ENABLE (1 << 5)
|
#define S3C2410_WTCON_ENABLE (1 << 5)
|
||||||
|
@ -56,8 +58,11 @@
|
||||||
#define S3C2410_WTCON_DIV64 (2 << 3)
|
#define S3C2410_WTCON_DIV64 (2 << 3)
|
||||||
#define S3C2410_WTCON_DIV128 (3 << 3)
|
#define S3C2410_WTCON_DIV128 (3 << 3)
|
||||||
|
|
||||||
|
#define S3C2410_WTCON_MAXDIV 0x80
|
||||||
|
|
||||||
#define S3C2410_WTCON_PRESCALE(x) ((x) << 8)
|
#define S3C2410_WTCON_PRESCALE(x) ((x) << 8)
|
||||||
#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8)
|
#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8)
|
||||||
|
#define S3C2410_WTCON_PRESCALE_MAX 0xff
|
||||||
|
|
||||||
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
|
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
|
||||||
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
|
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
|
||||||
|
@ -198,6 +203,14 @@ do { \
|
||||||
|
|
||||||
/* functions */
|
/* 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)
|
static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
|
||||||
{
|
{
|
||||||
return container_of(nb, struct s3c2410_wdt, freq_transition);
|
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;
|
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);
|
struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
|
||||||
void __iomem *wdt_base = wdt->reg_base;
|
void __iomem *wdt_base = wdt->reg_base;
|
||||||
|
@ -567,6 +581,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wdt->wdt_device.min_timeout = 1;
|
||||||
|
wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt->clock);
|
||||||
|
|
||||||
ret = s3c2410wdt_cpufreq_register(wdt);
|
ret = s3c2410wdt_cpufreq_register(wdt);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
dev_err(dev, "failed to register cpufreq\n");
|
dev_err(dev, "failed to register cpufreq\n");
|
||||||
|
|
|
@ -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);
|
struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
|
||||||
void __iomem *wdt_base = sunxi_wdt->wdt_base;
|
void __iomem *wdt_base = sunxi_wdt->wdt_base;
|
||||||
|
|
|
@ -139,6 +139,10 @@ static int tangox_wdt_probe(struct platform_device *pdev)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
dev->clk_rate = clk_get_rate(dev->clk);
|
dev->clk_rate = clk_get_rate(dev->clk);
|
||||||
|
if (!dev->clk_rate) {
|
||||||
|
err = -EINVAL;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
dev->wdt.parent = &pdev->dev;
|
dev->wdt.parent = &pdev->dev;
|
||||||
dev->wdt.info = &tangox_wdt_info;
|
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);
|
err = watchdog_register_device(&dev->wdt);
|
||||||
if (err) {
|
if (err)
|
||||||
clk_disable_unprepare(dev->clk);
|
goto err;
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
platform_set_drvdata(pdev, dev);
|
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");
|
dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
clk_disable_unprepare(dev->clk);
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tangox_wdt_remove(struct platform_device *pdev)
|
static int tangox_wdt_remove(struct platform_device *pdev)
|
||||||
|
|
|
@ -45,10 +45,11 @@
|
||||||
static int wdt_io;
|
static int wdt_io;
|
||||||
static int cr_wdt_timeout; /* WDT timeout register */
|
static int cr_wdt_timeout; /* WDT timeout register */
|
||||||
static int cr_wdt_control; /* WDT control 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,
|
enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf,
|
||||||
w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p,
|
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 */
|
static int timeout; /* in seconds */
|
||||||
module_param(timeout, int, 0);
|
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 W83667HG_B_ID 0xb3
|
||||||
#define NCT6775_ID 0xb4
|
#define NCT6775_ID 0xb4
|
||||||
#define NCT6776_ID 0xc3
|
#define NCT6776_ID 0xc3
|
||||||
|
#define NCT6102_ID 0xc4
|
||||||
#define NCT6779_ID 0xc5
|
#define NCT6779_ID 0xc5
|
||||||
#define NCT6791_ID 0xc8
|
#define NCT6791_ID 0xc8
|
||||||
#define NCT6792_ID 0xc9
|
#define NCT6792_ID 0xc9
|
||||||
|
|
||||||
#define W83627HF_WDT_TIMEOUT 0xf6
|
#define W83627HF_WDT_TIMEOUT 0xf6
|
||||||
#define W83697HF_WDT_TIMEOUT 0xf4
|
#define W83697HF_WDT_TIMEOUT 0xf4
|
||||||
|
#define NCT6102D_WDT_TIMEOUT 0xf1
|
||||||
|
|
||||||
#define W83627HF_WDT_CONTROL 0xf5
|
#define W83627HF_WDT_CONTROL 0xf5
|
||||||
#define W83697HF_WDT_CONTROL 0xf3
|
#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)
|
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 nct6779:
|
||||||
case nct6791:
|
case nct6791:
|
||||||
case nct6792:
|
case nct6792:
|
||||||
|
case nct6102:
|
||||||
/*
|
/*
|
||||||
* These chips have a fixed WDTO# output pin (W83627UHG),
|
* These chips have a fixed WDTO# output pin (W83627UHG),
|
||||||
* or support more than one WDTO# output pin.
|
* 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);
|
superio_outb(cr_wdt_control, t);
|
||||||
|
|
||||||
/* reset trigger, disable keyboard & mouse turning off watchdog */
|
/* reset trigger, disable keyboard & mouse turning off watchdog */
|
||||||
t = superio_inb(0xF7) & ~0xD0;
|
t = superio_inb(cr_wdt_csr) & ~0xD0;
|
||||||
superio_outb(0xF7, t);
|
superio_outb(cr_wdt_csr, t);
|
||||||
|
|
||||||
superio_exit();
|
superio_exit();
|
||||||
|
|
||||||
|
@ -322,6 +330,7 @@ static int wdt_find(int addr)
|
||||||
|
|
||||||
cr_wdt_timeout = W83627HF_WDT_TIMEOUT;
|
cr_wdt_timeout = W83627HF_WDT_TIMEOUT;
|
||||||
cr_wdt_control = W83627HF_WDT_CONTROL;
|
cr_wdt_control = W83627HF_WDT_CONTROL;
|
||||||
|
cr_wdt_csr = W836X7HF_WDT_CSR;
|
||||||
|
|
||||||
ret = superio_enter();
|
ret = superio_enter();
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -387,6 +396,12 @@ static int wdt_find(int addr)
|
||||||
case NCT6792_ID:
|
case NCT6792_ID:
|
||||||
ret = nct6792;
|
ret = nct6792;
|
||||||
break;
|
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:
|
case 0xff:
|
||||||
ret = -ENODEV;
|
ret = -ENODEV;
|
||||||
break;
|
break;
|
||||||
|
@ -422,6 +437,7 @@ static int __init wdt_init(void)
|
||||||
"NCT6779",
|
"NCT6779",
|
||||||
"NCT6791",
|
"NCT6791",
|
||||||
"NCT6792",
|
"NCT6792",
|
||||||
|
"NCT6102",
|
||||||
};
|
};
|
||||||
|
|
||||||
wdt_io = 0x2e;
|
wdt_io = 0x2e;
|
||||||
|
|
|
@ -164,7 +164,7 @@ static int watchdog_restart_notifier(struct notifier_block *nb,
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = wdd->ops->restart(wdd);
|
ret = wdd->ops->restart(wdd, action, data);
|
||||||
if (ret)
|
if (ret)
|
||||||
return NOTIFY_BAD;
|
return NOTIFY_BAD;
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
/* Mandatory operations need to be supported */
|
/* 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;
|
return -EINVAL;
|
||||||
|
|
||||||
watchdog_check_min_max_timeout(wdd);
|
watchdog_check_min_max_timeout(wdd);
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||||
#include <linux/fs.h> /* For file operations */
|
#include <linux/fs.h> /* For file operations */
|
||||||
#include <linux/init.h> /* For __init/__exit/... */
|
#include <linux/init.h> /* For __init/__exit/... */
|
||||||
|
#include <linux/jiffies.h> /* For timeout functions */
|
||||||
#include <linux/kernel.h> /* For printk/panic/... */
|
#include <linux/kernel.h> /* For printk/panic/... */
|
||||||
#include <linux/kref.h> /* For data references */
|
#include <linux/kref.h> /* For data references */
|
||||||
#include <linux/miscdevice.h> /* For handling misc devices */
|
#include <linux/miscdevice.h> /* For handling misc devices */
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
#include <linux/slab.h> /* For memory functions */
|
#include <linux/slab.h> /* For memory functions */
|
||||||
#include <linux/types.h> /* For standard types (like size_t) */
|
#include <linux/types.h> /* For standard types (like size_t) */
|
||||||
#include <linux/watchdog.h> /* For watchdog specific items */
|
#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 <linux/uaccess.h> /* For copy_to_user/put_user/... */
|
||||||
|
|
||||||
#include "watchdog_core.h"
|
#include "watchdog_core.h"
|
||||||
|
@ -61,6 +63,9 @@ struct watchdog_core_data {
|
||||||
struct cdev cdev;
|
struct cdev cdev;
|
||||||
struct watchdog_device *wdd;
|
struct watchdog_device *wdd;
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
|
unsigned long last_keepalive;
|
||||||
|
unsigned long last_hw_keepalive;
|
||||||
|
struct delayed_work work;
|
||||||
unsigned long status; /* Internal status bits */
|
unsigned long status; /* Internal status bits */
|
||||||
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
||||||
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
#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 */
|
/* Reference to watchdog device behind /dev/watchdog */
|
||||||
static struct watchdog_core_data *old_wd_data;
|
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.
|
* watchdog_ping: ping the watchdog.
|
||||||
* @wdd: the watchdog device to ping
|
* @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)
|
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;
|
return 0;
|
||||||
|
|
||||||
if (wdd->ops->ping)
|
wd_data->last_keepalive = jiffies;
|
||||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
return __watchdog_ping(wdd);
|
||||||
else
|
}
|
||||||
err = wdd->ops->start(wdd); /* restart watchdog */
|
|
||||||
|
|
||||||
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)
|
static int watchdog_start(struct watchdog_device *wdd)
|
||||||
{
|
{
|
||||||
|
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||||
|
unsigned long started_at;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (watchdog_active(wdd))
|
if (watchdog_active(wdd))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
started_at = jiffies;
|
||||||
|
if (watchdog_hw_running(wdd) && wdd->ops->ping)
|
||||||
|
err = wdd->ops->ping(wdd);
|
||||||
|
else
|
||||||
err = wdd->ops->start(wdd);
|
err = wdd->ops->start(wdd);
|
||||||
if (err == 0)
|
if (err == 0) {
|
||||||
set_bit(WDOG_ACTIVE, &wdd->status);
|
set_bit(WDOG_ACTIVE, &wdd->status);
|
||||||
|
wd_data->last_keepalive = started_at;
|
||||||
|
watchdog_update_worker(wdd);
|
||||||
|
}
|
||||||
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -137,7 +247,7 @@ static int watchdog_start(struct watchdog_device *wdd)
|
||||||
|
|
||||||
static int watchdog_stop(struct watchdog_device *wdd)
|
static int watchdog_stop(struct watchdog_device *wdd)
|
||||||
{
|
{
|
||||||
int err;
|
int err = 0;
|
||||||
|
|
||||||
if (!watchdog_active(wdd))
|
if (!watchdog_active(wdd))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -148,9 +258,15 @@ static int watchdog_stop(struct watchdog_device *wdd)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wdd->ops->stop)
|
||||||
err = wdd->ops->stop(wdd);
|
err = wdd->ops->stop(wdd);
|
||||||
if (err == 0)
|
else
|
||||||
|
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
clear_bit(WDOG_ACTIVE, &wdd->status);
|
clear_bit(WDOG_ACTIVE, &wdd->status);
|
||||||
|
watchdog_update_worker(wdd);
|
||||||
|
}
|
||||||
|
|
||||||
return err;
|
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,
|
static int watchdog_set_timeout(struct watchdog_device *wdd,
|
||||||
unsigned int timeout)
|
unsigned int timeout)
|
||||||
{
|
{
|
||||||
if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT))
|
int err = 0;
|
||||||
|
|
||||||
|
if (!(wdd->info->options & WDIOF_SETTIMEOUT))
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
if (watchdog_timeout_invalid(wdd, timeout))
|
if (watchdog_timeout_invalid(wdd, timeout))
|
||||||
return -EINVAL;
|
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
|
* If the /dev/watchdog device is open, we don't want the module
|
||||||
* to be unloaded.
|
* to be unloaded.
|
||||||
*/
|
*/
|
||||||
if (!try_module_get(wdd->ops->owner)) {
|
if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) {
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
goto out_clear;
|
goto out_clear;
|
||||||
}
|
}
|
||||||
|
@ -549,6 +674,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||||
|
|
||||||
file->private_data = wd_data;
|
file->private_data = wd_data;
|
||||||
|
|
||||||
|
if (!watchdog_hw_running(wdd))
|
||||||
kref_get(&wd_data->kref);
|
kref_get(&wd_data->kref);
|
||||||
|
|
||||||
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
|
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
|
||||||
|
@ -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_core_data *wd_data = file->private_data;
|
||||||
struct watchdog_device *wdd;
|
struct watchdog_device *wdd;
|
||||||
int err = -EBUSY;
|
int err = -EBUSY;
|
||||||
|
bool running;
|
||||||
|
|
||||||
mutex_lock(&wd_data->lock);
|
mutex_lock(&wd_data->lock);
|
||||||
|
|
||||||
|
@ -609,14 +736,24 @@ static int watchdog_release(struct inode *inode, struct file *file)
|
||||||
watchdog_ping(wdd);
|
watchdog_ping(wdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel_delayed_work_sync(&wd_data->work);
|
||||||
|
watchdog_update_worker(wdd);
|
||||||
|
|
||||||
/* make sure that /dev/watchdog can be re-opened */
|
/* make sure that /dev/watchdog can be re-opened */
|
||||||
clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
|
clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
running = wdd && watchdog_hw_running(wdd);
|
||||||
mutex_unlock(&wd_data->lock);
|
mutex_unlock(&wd_data->lock);
|
||||||
/* Allow the owner module to be unloaded again */
|
/*
|
||||||
|
* 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);
|
module_put(wd_data->cdev.owner);
|
||||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,6 +795,11 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||||
wd_data->wdd = wdd;
|
wd_data->wdd = wdd;
|
||||||
wdd->wd_data = wd_data;
|
wdd->wd_data = wd_data;
|
||||||
|
|
||||||
|
if (!watchdog_wq)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work);
|
||||||
|
|
||||||
if (wdd->id == 0) {
|
if (wdd->id == 0) {
|
||||||
old_wd_data = wd_data;
|
old_wd_data = wd_data;
|
||||||
watchdog_miscdev.parent = wdd->parent;
|
watchdog_miscdev.parent = wdd->parent;
|
||||||
|
@ -688,10 +830,25 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||||
old_wd_data = NULL;
|
old_wd_data = NULL;
|
||||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* watchdog_cdev_unregister: unregister watchdog character device
|
* watchdog_cdev_unregister: unregister watchdog character device
|
||||||
* @watchdog: watchdog device
|
* @watchdog: watchdog device
|
||||||
|
@ -715,6 +872,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||||
wdd->wd_data = NULL;
|
wdd->wd_data = NULL;
|
||||||
mutex_unlock(&wd_data->lock);
|
mutex_unlock(&wd_data->lock);
|
||||||
|
|
||||||
|
cancel_delayed_work_sync(&wd_data->work);
|
||||||
|
|
||||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,6 +939,13 @@ int __init watchdog_dev_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
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);
|
err = class_register(&watchdog_class);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
pr_err("couldn't register class\n");
|
pr_err("couldn't register class\n");
|
||||||
|
@ -806,4 +972,5 @@ void __exit watchdog_dev_exit(void)
|
||||||
{
|
{
|
||||||
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
|
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
|
||||||
class_unregister(&watchdog_class);
|
class_unregister(&watchdog_class);
|
||||||
|
destroy_workqueue(watchdog_wq);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
#define ZIIRAVE_STATE_OFF 0x1
|
#define ZIIRAVE_STATE_OFF 0x1
|
||||||
#define ZIIRAVE_STATE_ON 0x2
|
#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",
|
"host request", NULL, "illegal configuration",
|
||||||
"illegal instruction", "illegal trap",
|
"illegal instruction", "illegal trap",
|
||||||
"unknown"};
|
"unknown"};
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
|
|
||||||
|
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/device.h>
|
|
||||||
#include <linux/cdev.h>
|
#include <linux/cdev.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
||||||
#include <uapi/linux/watchdog.h>
|
#include <uapi/linux/watchdog.h>
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ struct watchdog_ops {
|
||||||
unsigned int (*status)(struct watchdog_device *);
|
unsigned int (*status)(struct watchdog_device *);
|
||||||
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
int (*set_timeout)(struct watchdog_device *, unsigned int);
|
||||||
unsigned int (*get_timeleft)(struct watchdog_device *);
|
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);
|
long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,13 +62,20 @@ struct watchdog_ops {
|
||||||
* @bootstatus: Status of the watchdog device at boot.
|
* @bootstatus: Status of the watchdog device at boot.
|
||||||
* @timeout: The watchdog devices timeout value (in seconds).
|
* @timeout: The watchdog devices timeout value (in seconds).
|
||||||
* @min_timeout:The watchdog devices minimum 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.
|
* @reboot_nb: The notifier block to stop watchdog on reboot.
|
||||||
* @restart_nb: The notifier block to register a restart function.
|
* @restart_nb: The notifier block to register a restart function.
|
||||||
* @driver_data:Pointer to the drivers private data.
|
* @driver_data:Pointer to the drivers private data.
|
||||||
* @wd_data: Pointer to watchdog core internal data.
|
* @wd_data: Pointer to watchdog core internal data.
|
||||||
* @status: Field that contains the devices internal status bits.
|
* @status: Field that contains the devices internal status bits.
|
||||||
* @deferred: entry in wtd_deferred_reg_list which is used to
|
* @deferred: Entry in wtd_deferred_reg_list which is used to
|
||||||
* register early initialized watchdogs.
|
* register early initialized watchdogs.
|
||||||
*
|
*
|
||||||
* The watchdog_device structure contains all information about a
|
* The watchdog_device structure contains all information about a
|
||||||
|
@ -89,6 +97,8 @@ struct watchdog_device {
|
||||||
unsigned int timeout;
|
unsigned int timeout;
|
||||||
unsigned int min_timeout;
|
unsigned int min_timeout;
|
||||||
unsigned int max_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 reboot_nb;
|
||||||
struct notifier_block restart_nb;
|
struct notifier_block restart_nb;
|
||||||
void *driver_data;
|
void *driver_data;
|
||||||
|
@ -98,6 +108,7 @@ struct watchdog_device {
|
||||||
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
|
#define WDOG_ACTIVE 0 /* Is the watchdog running/active */
|
||||||
#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */
|
#define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */
|
||||||
#define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */
|
#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;
|
struct list_head deferred;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +121,15 @@ static inline bool watchdog_active(struct watchdog_device *wdd)
|
||||||
return test_bit(WDOG_ACTIVE, &wdd->status);
|
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 */
|
/* Use the following function to set the nowayout feature */
|
||||||
static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout)
|
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 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,
|
* - the requested value is smaller than the configured minimum timeout,
|
||||||
* or
|
* or
|
||||||
* - a maximum timeout is configured, and the requested value is larger
|
* - a maximum hardware timeout is not configured, a maximum timeout
|
||||||
* than the maximum timeout.
|
* is configured, and the requested value is larger than the
|
||||||
|
* configured maximum timeout.
|
||||||
*/
|
*/
|
||||||
return t < wdd->min_timeout ||
|
return t > UINT_MAX / 1000 || t < wdd->min_timeout ||
|
||||||
(wdd->max_timeout && t > wdd->max_timeout);
|
(!wdd->max_hw_heartbeat_ms && wdd->max_timeout &&
|
||||||
|
t > wdd->max_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Use the following functions to manipulate watchdog driver specific data */
|
/* Use the following functions to manipulate watchdog driver specific data */
|
||||||
|
|
Loading…
Reference in New Issue