linux-watchdog 4.16-rc1 merge window tag
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.14 (GNU/Linux) iEYEABECAAYFAlp4I7MACgkQ+iyteGJfRsp34QCgi3O78Sajso9iJNMj5KJsQTEt VOsAn1ioW2jO9CmZQoj4IlVStlKU0NCN =jeHv -----END PGP SIGNATURE----- Merge tag 'linux-watchdog-4.16-rc1' of git://www.linux-watchdog.org/linux-watchdog Pull watchdog updates from Wim Van Sebroeck: - new watchdog device drivers for Realtek RTD1295 and Spreadtrum SC9860 platform - add support for the following devices: jz4780 SoC, AST25xx series SoC and r8a77970 SoC - convert to watchdog framework: i6300esb_wdt, xen_wdt and sp5100_tco - several fixes for watchdog core - remove at32ap700x and obsolete documentation - gpio: Convert to use GPIO descriptors - rename gemini into FTWDT010 as this IP block is generc from Faraday Technology - various clean-ups and small bugfixes - add Guenter Roeck as co-maintainer - change maintainers e-mail address * tag 'linux-watchdog-4.16-rc1' of git://www.linux-watchdog.org/linux-watchdog: (74 commits) documentation: watchdog: remove documentation of w83697hf_wdt/w83697ug_wdt documentation: watchdog: remove documentation for ixp2000 documentation: watchdog: remove documentation of at32ap700x_wdt watchdog: remove at32ap700x_wdt watchdog: sp5100_tco: Add support for recent FCH versions watchdog: sp5100-tco: Abort if watchdog is disabled by hardware watchdog: sp5100_tco: Use bit operations watchdog: sp5100_tco: Convert to use watchdog subsystem watchdog: sp5100_tco: Clean up function and variable names watchdog: sp5100_tco: Use dev_ print functions where possible watchdog: sp5100_tco: Match PCI device early watchdog: sp5100_tco: Clean up sp5100_tco_setupdevice watchdog: sp5100_tco: Use standard error codes watchdog: sp5100_tco: Use request_muxed_region where possible watchdog: sp5100_tco: Fix watchdog disable bit watchdog: sp5100_tco: Always use SP5100_IO_PM_{INDEX_REG,DATA_REG} watchdog: core: make sure the watchdog_worker is not deferred watchdog: mt7621: switch to using managed devm_watchdog_register_device() watchdog: mt7621: set WDOG_HW_RUNNING bit when appropriate watchdog: imx2_wdt: restore previous timeout after suspend+resume ...
This commit is contained in:
commit
7e6127c124
@ -1,17 +0,0 @@
|
||||
Cortina Systems Gemini SoC Watchdog
|
||||
|
||||
Required properties:
|
||||
- compatible : must be "cortina,gemini-watchdog"
|
||||
- reg : shall contain base register location and length
|
||||
- interrupts : shall contain the interrupt for the watchdog
|
||||
|
||||
Optional properties:
|
||||
- timeout-sec : the default watchdog timeout in seconds.
|
||||
|
||||
Example:
|
||||
|
||||
watchdog@41000000 {
|
||||
compatible = "cortina,gemini-watchdog";
|
||||
reg = <0x41000000 0x1000>;
|
||||
interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
@ -1,7 +1,12 @@
|
||||
Cortina Systems Gemini SoC Watchdog
|
||||
Faraday Technology FTWDT010 watchdog
|
||||
|
||||
This is an IP part from Faraday Technology found in the Gemini
|
||||
SoCs and others.
|
||||
|
||||
Required properties:
|
||||
- compatible : must be "cortina,gemini-watchdog"
|
||||
- compatible : must be one of
|
||||
"faraday,ftwdt010"
|
||||
"cortina,gemini-watchdog", "faraday,ftwdt010"
|
||||
- reg : shall contain base register location and length
|
||||
- interrupts : shall contain the interrupt for the watchdog
|
||||
|
||||
@ -11,7 +16,7 @@ Optional properties:
|
||||
Example:
|
||||
|
||||
watchdog@41000000 {
|
||||
compatible = "cortina,gemini-watchdog";
|
||||
compatible = "faraday,ftwdt010";
|
||||
reg = <0x41000000 0x1000>;
|
||||
interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
Ingenic Watchdog Timer (WDT) Controller for JZ4740
|
||||
Ingenic Watchdog Timer (WDT) Controller for JZ4740 & JZ4780
|
||||
|
||||
Required properties:
|
||||
compatible: "ingenic,jz4740-watchdog"
|
||||
compatible: "ingenic,jz4740-watchdog" or "ingenic,jz4780-watchdog"
|
||||
reg: Register address and length for watchdog registers
|
||||
|
||||
Example:
|
||||
|
@ -0,0 +1,17 @@
|
||||
Realtek RTD1295 Watchdog
|
||||
========================
|
||||
|
||||
Required properties:
|
||||
|
||||
- compatible : Should be "realtek,rtd1295-watchdog"
|
||||
- reg : Specifies the physical base address and size of registers
|
||||
- clocks : Specifies one clock input
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
watchdog@98007680 {
|
||||
compatible = "realtek,rtd1295-watchdog";
|
||||
reg = <0x98007680 0x100>;
|
||||
clocks = <&osc27M>;
|
||||
};
|
@ -4,10 +4,11 @@ Required properties:
|
||||
- compatible : Should be "renesas,<soctype>-wdt", and
|
||||
"renesas,rcar-gen3-wdt" or "renesas,rza-wdt" as fallback.
|
||||
Examples with soctypes are:
|
||||
- "renesas,r7s72100-wdt" (RZ/A1)
|
||||
- "renesas,r8a7795-wdt" (R-Car H3)
|
||||
- "renesas,r8a7796-wdt" (R-Car M3-W)
|
||||
- "renesas,r8a77970-wdt" (R-Car V3M)
|
||||
- "renesas,r8a77995-wdt" (R-Car D3)
|
||||
- "renesas,r7s72100-wdt" (RZ/A1)
|
||||
|
||||
When compatible with the generic version, nodes must list the SoC-specific
|
||||
version corresponding to the platform first, followed by the generic
|
||||
|
19
Documentation/devicetree/bindings/watchdog/sprd-wdt.txt
Normal file
19
Documentation/devicetree/bindings/watchdog/sprd-wdt.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Spreadtrum SoCs Watchdog timer
|
||||
|
||||
Required properties:
|
||||
- compatible : Should be "sprd,sp9860-wdt".
|
||||
- reg : Specifies base physical address and size of the registers.
|
||||
- interrupts : Exactly one interrupt specifier.
|
||||
- timeout-sec : Contain the default watchdog timeout in seconds.
|
||||
- clock-names : Contain the input clock names.
|
||||
- clocks : Phandles to input clocks.
|
||||
|
||||
Example:
|
||||
watchdog: watchdog@40310000 {
|
||||
compatible = "sprd,sp9860-wdt";
|
||||
reg = <0 0x40310000 0 0x1000>;
|
||||
interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>;
|
||||
timeout-sec = <12>;
|
||||
clock-names = "enable", "rtc_enable";
|
||||
clocks = <&clk_aon_apb_gates1 8>, <&clk_aon_apb_rtc_gates 9>;
|
||||
};
|
@ -40,11 +40,6 @@ margin: Watchdog margin in seconds (default=60)
|
||||
nowayout: Disable watchdog shutdown on close
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
at32ap700x_wdt:
|
||||
timeout: Timeout value. Limited to be 1 or 2 seconds. (default=2)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
at91rm9200_wdt:
|
||||
wdt_time: Watchdog time in seconds. (default=5)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
@ -162,11 +157,6 @@ testmode: Watchdog test mode (1 = no reboot), default=0
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
ixp2000_wdt:
|
||||
heartbeat: Watchdog heartbeat in seconds (default 60s)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
ixp4xx_wdt:
|
||||
heartbeat: Watchdog heartbeat in seconds (default 60s)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
@ -381,19 +371,6 @@ timeout: Watchdog timeout in seconds. 1 <= timeout <= 255, default=60.
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
w83697hf_wdt:
|
||||
wdt_io: w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)
|
||||
timeout: Watchdog timeout in seconds. 1<= timeout <=255 (default=60)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
early_disable: Watchdog gets disabled at boot time (default=1)
|
||||
-------------------------------------------------
|
||||
w83697ug_wdt:
|
||||
wdt_io: w83697ug/uf WDT io port (default 0x2e)
|
||||
timeout: Watchdog timeout in seconds. 1<= timeout <=255 (default=60)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
(default=kernel config parameter)
|
||||
-------------------------------------------------
|
||||
w83877f_wdt:
|
||||
timeout: Watchdog timeout in seconds. (1<=timeout<=3600, default=30)
|
||||
nowayout: Watchdog cannot be stopped once started
|
||||
|
@ -14991,8 +14991,8 @@ S: Maintained
|
||||
F: drivers/input/tablet/wacom_serial4.c
|
||||
|
||||
WATCHDOG DEVICE DRIVERS
|
||||
M: Wim Van Sebroeck <wim@iguana.be>
|
||||
R: Guenter Roeck <linux@roeck-us.net>
|
||||
M: Wim Van Sebroeck <wim@linux-watchdog.org>
|
||||
M: Guenter Roeck <linux@roeck-us.net>
|
||||
L: linux-watchdog@vger.kernel.org
|
||||
W: http://www.linux-watchdog.org/
|
||||
T: git git://www.linux-watchdog.org/linux-watchdog.git
|
||||
|
@ -328,16 +328,18 @@ config 977_WATCHDOG
|
||||
|
||||
Not sure? It's safe to say N.
|
||||
|
||||
config GEMINI_WATCHDOG
|
||||
tristate "Gemini watchdog"
|
||||
depends on ARCH_GEMINI
|
||||
config FTWDT010_WATCHDOG
|
||||
tristate "Faraday Technology FTWDT010 watchdog"
|
||||
depends on ARM || COMPILE_TEST
|
||||
select WATCHDOG_CORE
|
||||
default ARCH_GEMINI
|
||||
help
|
||||
Say Y here if to include support for the watchdog timer
|
||||
embedded in the Cortina Systems Gemini family of devices.
|
||||
Say Y here if to include support for the Faraday Technology
|
||||
FTWDT010 watchdog timer embedded in the Cortina Systems Gemini
|
||||
family of devices.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called gemini_wdt.
|
||||
module will be called ftwdt010_wdt.
|
||||
|
||||
config IXP4XX_WATCHDOG
|
||||
tristate "IXP4xx Watchdog"
|
||||
@ -748,12 +750,12 @@ config RENESAS_RZAWDT
|
||||
Renesas RZ/A SoCs. These watchdogs can be used to reset a system.
|
||||
|
||||
config ASPEED_WATCHDOG
|
||||
tristate "Aspeed 2400 watchdog support"
|
||||
tristate "Aspeed BMC watchdog support"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Say Y here to include support for the watchdog timer
|
||||
in Apseed BMC SoCs.
|
||||
in Aspeed BMC SoCs.
|
||||
|
||||
This driver is required to reboot the SoC.
|
||||
|
||||
@ -794,14 +796,23 @@ config UNIPHIER_WATCHDOG
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called uniphier_wdt.
|
||||
|
||||
# AVR32 Architecture
|
||||
|
||||
config AT32AP700X_WDT
|
||||
tristate "AT32AP700x watchdog"
|
||||
depends on CPU_AT32AP700X || COMPILE_TEST
|
||||
config RTD119X_WATCHDOG
|
||||
bool "Realtek RTD119x/RTD129x watchdog support"
|
||||
depends on ARCH_REALTEK || COMPILE_TEST
|
||||
depends on OF
|
||||
select WATCHDOG_CORE
|
||||
default ARCH_REALTEK
|
||||
help
|
||||
Watchdog timer embedded into AT32AP700x devices. This will reboot
|
||||
your system when the timeout is reached.
|
||||
Say Y here to include support for the watchdog timer in
|
||||
Realtek RTD1295 SoCs.
|
||||
|
||||
config SPRD_WATCHDOG
|
||||
tristate "Spreadtrum watchdog support"
|
||||
depends on ARCH_SPRD || COMPILE_TEST
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
Say Y here to include watchdog timer supported
|
||||
by Spreadtrum system.
|
||||
|
||||
# BLACKFIN Architecture
|
||||
|
||||
@ -1458,7 +1469,7 @@ config RC32434_WDT
|
||||
|
||||
config INDYDOG
|
||||
tristate "Indy/I2 Hardware Watchdog"
|
||||
depends on SGI_HAS_INDYDOG || (MIPS && COMPILE_TEST)
|
||||
depends on SGI_HAS_INDYDOG
|
||||
help
|
||||
Hardware driver for the Indy's/I2's watchdog. This is a
|
||||
watchdog timer that will reboot the machine after a 60 second
|
||||
|
@ -46,7 +46,7 @@ obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
|
||||
obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o
|
||||
obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
|
||||
obj-$(CONFIG_977_WATCHDOG) += wdt977.o
|
||||
obj-$(CONFIG_GEMINI_WATCHDOG) += gemini_wdt.o
|
||||
obj-$(CONFIG_FTWDT010_WATCHDOG) += ftwdt010_wdt.o
|
||||
obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
|
||||
obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o
|
||||
obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
|
||||
@ -88,9 +88,8 @@ obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
|
||||
obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
|
||||
obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o
|
||||
obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o
|
||||
|
||||
# AVR32 Architecture
|
||||
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
|
||||
obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o
|
||||
obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o
|
||||
|
||||
# BLACKFIN Architecture
|
||||
obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o
|
||||
|
@ -181,7 +181,7 @@ static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
if (advwdt_set_heartbeat(new_timeout))
|
||||
return -EINVAL;
|
||||
advwdt_ping();
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(timeout, p);
|
||||
default:
|
||||
|
@ -223,8 +223,8 @@ static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
if (ali_settimer(new_timeout))
|
||||
return -EINVAL;
|
||||
ali_keepalive();
|
||||
/* Fall */
|
||||
}
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(timeout, p);
|
||||
default:
|
||||
|
@ -243,9 +243,13 @@ static int aspeed_wdt_probe(struct platform_device *pdev)
|
||||
if (of_property_read_bool(np, "aspeed,external-signal"))
|
||||
wdt->ctrl |= WDT_CTRL_WDT_EXT;
|
||||
|
||||
writel(wdt->ctrl, wdt->base + WDT_CTRL);
|
||||
|
||||
if (readl(wdt->base + WDT_CTRL) & WDT_CTRL_ENABLE) {
|
||||
/*
|
||||
* The watchdog is running, but invoke aspeed_wdt_start() to
|
||||
* write wdt->ctrl to WDT_CTRL to ensure the watchdog's
|
||||
* configuration conforms to the driver's expectations.
|
||||
* Primarily, ensure we're using the 1MHz clock source.
|
||||
*/
|
||||
aspeed_wdt_start(&wdt->wdd);
|
||||
set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
|
||||
}
|
||||
@ -312,7 +316,18 @@ static struct platform_driver aspeed_watchdog_driver = {
|
||||
.of_match_table = of_match_ptr(aspeed_wdt_of_table),
|
||||
},
|
||||
};
|
||||
module_platform_driver(aspeed_watchdog_driver);
|
||||
|
||||
static int __init aspeed_wdt_init(void)
|
||||
{
|
||||
return platform_driver_register(&aspeed_watchdog_driver);
|
||||
}
|
||||
arch_initcall(aspeed_wdt_init);
|
||||
|
||||
static void __exit aspeed_wdt_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&aspeed_watchdog_driver);
|
||||
}
|
||||
module_exit(aspeed_wdt_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Aspeed Watchdog Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -1,433 +0,0 @@
|
||||
/*
|
||||
* Watchdog driver for Atmel AT32AP700X devices
|
||||
*
|
||||
* Copyright (C) 2005-2006 Atmel Corporation
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* Errata: WDT Clear is blocked after WDT Reset
|
||||
*
|
||||
* A watchdog timer event will, after reset, block writes to the WDT_CLEAR
|
||||
* register, preventing the program to clear the next Watchdog Timer Reset.
|
||||
*
|
||||
* If you still want to use the WDT after a WDT reset a small code can be
|
||||
* insterted at the startup checking the AVR32_PM.rcause register for WDT reset
|
||||
* and use a GPIO pin to reset the system. This method requires that one of the
|
||||
* GPIO pins are available and connected externally to the RESET_N pin. After
|
||||
* the GPIO pin has pulled down the reset line the GPIO will be reset and leave
|
||||
* the pin tristated with pullup.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define TIMEOUT_MIN 1
|
||||
#define TIMEOUT_MAX 2
|
||||
#define TIMEOUT_DEFAULT TIMEOUT_MAX
|
||||
|
||||
/* module parameters */
|
||||
static int timeout = TIMEOUT_DEFAULT;
|
||||
module_param(timeout, int, 0);
|
||||
MODULE_PARM_DESC(timeout,
|
||||
"Timeout value. Limited to be 1 or 2 seconds. (default="
|
||||
__MODULE_STRING(TIMEOUT_DEFAULT) ")");
|
||||
|
||||
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) ")");
|
||||
|
||||
/* Watchdog registers and write/read macro */
|
||||
#define WDT_CTRL 0x00
|
||||
#define WDT_CTRL_EN 0
|
||||
#define WDT_CTRL_PSEL 8
|
||||
#define WDT_CTRL_KEY 24
|
||||
|
||||
#define WDT_CLR 0x04
|
||||
|
||||
#define WDT_RCAUSE 0x10
|
||||
#define WDT_RCAUSE_POR 0
|
||||
#define WDT_RCAUSE_EXT 2
|
||||
#define WDT_RCAUSE_WDT 3
|
||||
#define WDT_RCAUSE_JTAG 4
|
||||
#define WDT_RCAUSE_SERP 5
|
||||
|
||||
#define WDT_BIT(name) (1 << WDT_##name)
|
||||
#define WDT_BF(name, value) ((value) << WDT_##name)
|
||||
|
||||
#define wdt_readl(dev, reg) \
|
||||
__raw_readl((dev)->regs + WDT_##reg)
|
||||
#define wdt_writel(dev, reg, value) \
|
||||
__raw_writel((value), (dev)->regs + WDT_##reg)
|
||||
|
||||
struct wdt_at32ap700x {
|
||||
void __iomem *regs;
|
||||
spinlock_t io_lock;
|
||||
int timeout;
|
||||
int boot_status;
|
||||
unsigned long users;
|
||||
struct miscdevice miscdev;
|
||||
};
|
||||
|
||||
static struct wdt_at32ap700x *wdt;
|
||||
static char expect_release;
|
||||
|
||||
/*
|
||||
* Disable the watchdog.
|
||||
*/
|
||||
static inline void at32_wdt_stop(void)
|
||||
{
|
||||
unsigned long psel;
|
||||
|
||||
spin_lock(&wdt->io_lock);
|
||||
psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f);
|
||||
wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55));
|
||||
wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa));
|
||||
spin_unlock(&wdt->io_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable and reset the watchdog.
|
||||
*/
|
||||
static inline void at32_wdt_start(void)
|
||||
{
|
||||
/* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */
|
||||
unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe;
|
||||
|
||||
spin_lock(&wdt->io_lock);
|
||||
wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
|
||||
| WDT_BF(CTRL_PSEL, psel)
|
||||
| WDT_BF(CTRL_KEY, 0x55));
|
||||
wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
|
||||
| WDT_BF(CTRL_PSEL, psel)
|
||||
| WDT_BF(CTRL_KEY, 0xaa));
|
||||
spin_unlock(&wdt->io_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pat the watchdog timer.
|
||||
*/
|
||||
static inline void at32_wdt_pat(void)
|
||||
{
|
||||
spin_lock(&wdt->io_lock);
|
||||
wdt_writel(wdt, CLR, 0x42);
|
||||
spin_unlock(&wdt->io_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Watchdog device is opened, and watchdog starts running.
|
||||
*/
|
||||
static int at32_wdt_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (test_and_set_bit(1, &wdt->users))
|
||||
return -EBUSY;
|
||||
|
||||
at32_wdt_start();
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the watchdog device.
|
||||
*/
|
||||
static int at32_wdt_close(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (expect_release == 42) {
|
||||
at32_wdt_stop();
|
||||
} else {
|
||||
dev_dbg(wdt->miscdev.parent,
|
||||
"unexpected close, not stopping watchdog!\n");
|
||||
at32_wdt_pat();
|
||||
}
|
||||
clear_bit(1, &wdt->users);
|
||||
expect_release = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the watchdog time interval.
|
||||
*/
|
||||
static int at32_wdt_settimeout(int time)
|
||||
{
|
||||
/*
|
||||
* All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is
|
||||
* 2 ^ 16 allowing up to 2 seconds timeout.
|
||||
*/
|
||||
if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Set new watchdog time. It will be used when at32_wdt_start() is
|
||||
* called.
|
||||
*/
|
||||
wdt->timeout = time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the watchdog status.
|
||||
*/
|
||||
static int at32_wdt_get_status(void)
|
||||
{
|
||||
int rcause;
|
||||
int status = 0;
|
||||
|
||||
rcause = wdt_readl(wdt, RCAUSE);
|
||||
|
||||
switch (rcause) {
|
||||
case WDT_BIT(RCAUSE_EXT):
|
||||
status = WDIOF_EXTERN1;
|
||||
break;
|
||||
case WDT_BIT(RCAUSE_WDT):
|
||||
status = WDIOF_CARDRESET;
|
||||
break;
|
||||
case WDT_BIT(RCAUSE_POR): /* fall through */
|
||||
case WDT_BIT(RCAUSE_JTAG): /* fall through */
|
||||
case WDT_BIT(RCAUSE_SERP): /* fall through */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct watchdog_info at32_wdt_info = {
|
||||
.identity = "at32ap700x watchdog",
|
||||
.options = WDIOF_SETTIMEOUT |
|
||||
WDIOF_KEEPALIVEPING |
|
||||
WDIOF_MAGICCLOSE,
|
||||
};
|
||||
|
||||
/*
|
||||
* Handle commands from user-space.
|
||||
*/
|
||||
static long at32_wdt_ioctl(struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int ret = -ENOTTY;
|
||||
int time;
|
||||
void __user *argp = (void __user *)arg;
|
||||
int __user *p = argp;
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
ret = copy_to_user(argp, &at32_wdt_info,
|
||||
sizeof(at32_wdt_info)) ? -EFAULT : 0;
|
||||
break;
|
||||
case WDIOC_GETSTATUS:
|
||||
ret = put_user(0, p);
|
||||
break;
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
ret = put_user(wdt->boot_status, p);
|
||||
break;
|
||||
case WDIOC_SETOPTIONS:
|
||||
ret = get_user(time, p);
|
||||
if (ret)
|
||||
break;
|
||||
if (time & WDIOS_DISABLECARD)
|
||||
at32_wdt_stop();
|
||||
if (time & WDIOS_ENABLECARD)
|
||||
at32_wdt_start();
|
||||
ret = 0;
|
||||
break;
|
||||
case WDIOC_KEEPALIVE:
|
||||
at32_wdt_pat();
|
||||
ret = 0;
|
||||
break;
|
||||
case WDIOC_SETTIMEOUT:
|
||||
ret = get_user(time, p);
|
||||
if (ret)
|
||||
break;
|
||||
ret = at32_wdt_settimeout(time);
|
||||
if (ret)
|
||||
break;
|
||||
/* Enable new time value */
|
||||
at32_wdt_start();
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
ret = put_user(wdt->timeout, p);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t at32_wdt_write(struct file *file, const char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
/* See if we got the magic character 'V' and reload the timer */
|
||||
if (len) {
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
/*
|
||||
* note: just in case someone wrote the magic
|
||||
* character five months ago...
|
||||
*/
|
||||
expect_release = 0;
|
||||
|
||||
/*
|
||||
* scan to see whether or not we got the magic
|
||||
* character
|
||||
*/
|
||||
for (i = 0; i != len; i++) {
|
||||
char c;
|
||||
if (get_user(c, data + i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
expect_release = 42;
|
||||
}
|
||||
}
|
||||
/* someone wrote to us, we should pat the watchdog */
|
||||
at32_wdt_pat();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static const struct file_operations at32_wdt_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.unlocked_ioctl = at32_wdt_ioctl,
|
||||
.open = at32_wdt_open,
|
||||
.release = at32_wdt_close,
|
||||
.write = at32_wdt_write,
|
||||
};
|
||||
|
||||
static int __init at32_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *regs;
|
||||
int ret;
|
||||
|
||||
if (wdt) {
|
||||
dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!regs) {
|
||||
dev_dbg(&pdev->dev, "missing mmio resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
wdt = devm_kzalloc(&pdev->dev, sizeof(struct wdt_at32ap700x),
|
||||
GFP_KERNEL);
|
||||
if (!wdt)
|
||||
return -ENOMEM;
|
||||
|
||||
wdt->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs));
|
||||
if (!wdt->regs) {
|
||||
ret = -ENOMEM;
|
||||
dev_dbg(&pdev->dev, "could not map I/O memory\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
spin_lock_init(&wdt->io_lock);
|
||||
wdt->boot_status = at32_wdt_get_status();
|
||||
|
||||
/* Work-around for watchdog silicon errata. */
|
||||
if (wdt->boot_status & WDIOF_CARDRESET) {
|
||||
dev_info(&pdev->dev, "CPU must be reset with external "
|
||||
"reset or POR due to silicon errata.\n");
|
||||
ret = -EIO;
|
||||
goto err_free;
|
||||
} else {
|
||||
wdt->users = 0;
|
||||
}
|
||||
|
||||
wdt->miscdev.minor = WATCHDOG_MINOR;
|
||||
wdt->miscdev.name = "watchdog";
|
||||
wdt->miscdev.fops = &at32_wdt_fops;
|
||||
wdt->miscdev.parent = &pdev->dev;
|
||||
|
||||
platform_set_drvdata(pdev, wdt);
|
||||
|
||||
if (at32_wdt_settimeout(timeout)) {
|
||||
at32_wdt_settimeout(TIMEOUT_DEFAULT);
|
||||
dev_dbg(&pdev->dev,
|
||||
"default timeout invalid, set to %d sec.\n",
|
||||
TIMEOUT_DEFAULT);
|
||||
}
|
||||
|
||||
ret = misc_register(&wdt->miscdev);
|
||||
if (ret) {
|
||||
dev_dbg(&pdev->dev, "failed to register wdt miscdev\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev,
|
||||
"AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n",
|
||||
wdt->regs, wdt->timeout, nowayout);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
wdt = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit at32_wdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
if (wdt && platform_get_drvdata(pdev) == wdt) {
|
||||
/* Stop the timer before we leave */
|
||||
if (!nowayout)
|
||||
at32_wdt_stop();
|
||||
|
||||
misc_deregister(&wdt->miscdev);
|
||||
wdt = NULL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void at32_wdt_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
at32_wdt_stop();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message)
|
||||
{
|
||||
at32_wdt_stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at32_wdt_resume(struct platform_device *pdev)
|
||||
{
|
||||
if (wdt->users)
|
||||
at32_wdt_start();
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#define at32_wdt_suspend NULL
|
||||
#define at32_wdt_resume NULL
|
||||
#endif
|
||||
|
||||
/* work with hotplug and coldplug */
|
||||
MODULE_ALIAS("platform:at32_wdt");
|
||||
|
||||
static struct platform_driver at32_wdt_driver = {
|
||||
.remove = __exit_p(at32_wdt_remove),
|
||||
.suspend = at32_wdt_suspend,
|
||||
.resume = at32_wdt_resume,
|
||||
.driver = {
|
||||
.name = "at32_wdt",
|
||||
},
|
||||
.shutdown = at32_wdt_shutdown,
|
||||
};
|
||||
|
||||
module_platform_driver_probe(at32_wdt_driver, at32_wdt_probe);
|
||||
|
||||
MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>");
|
||||
MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X");
|
||||
MODULE_LICENSE("GPL");
|
@ -46,22 +46,6 @@ static void da9062_set_window_start(struct da9062_watchdog *wdt)
|
||||
wdt->j_time_stamp = jiffies;
|
||||
}
|
||||
|
||||
static void da9062_apply_window_protection(struct da9062_watchdog *wdt)
|
||||
{
|
||||
unsigned long delay = msecs_to_jiffies(DA9062_RESET_PROTECTION_MS);
|
||||
unsigned long timeout = wdt->j_time_stamp + delay;
|
||||
unsigned long now = jiffies;
|
||||
unsigned int diff_ms;
|
||||
|
||||
/* if time-limit has not elapsed then wait for remainder */
|
||||
if (time_before(now, timeout)) {
|
||||
diff_ms = jiffies_to_msecs(timeout-now);
|
||||
dev_dbg(wdt->hw->dev,
|
||||
"Kicked too quickly. Delaying %u msecs\n", diff_ms);
|
||||
msleep(diff_ms);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
|
||||
{
|
||||
unsigned int i;
|
||||
@ -78,8 +62,6 @@ static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
da9062_apply_window_protection(wdt);
|
||||
|
||||
ret = regmap_update_bits(wdt->hw->regmap,
|
||||
DA9062AA_CONTROL_F,
|
||||
DA9062AA_WATCHDOG_MASK,
|
||||
@ -100,6 +82,13 @@ static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
regmap_update_bits(chip->regmap,
|
||||
DA9062AA_CONTROL_D,
|
||||
DA9062AA_TWDSCALE_MASK,
|
||||
DA9062_TWDSCALE_DISABLE);
|
||||
|
||||
usleep_range(150, 300);
|
||||
|
||||
return regmap_update_bits(chip->regmap,
|
||||
DA9062AA_CONTROL_D,
|
||||
DA9062AA_TWDSCALE_MASK,
|
||||
@ -175,6 +164,25 @@ static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
|
||||
void *data)
|
||||
{
|
||||
struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
|
||||
int ret;
|
||||
|
||||
ret = regmap_write(wdt->hw->regmap,
|
||||
DA9062AA_CONTROL_F,
|
||||
DA9062AA_SHUTDOWN_MASK);
|
||||
if (ret)
|
||||
dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
|
||||
ret);
|
||||
|
||||
/* wait for reset to assert... */
|
||||
mdelay(500);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct watchdog_info da9062_watchdog_info = {
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
|
||||
.identity = "DA9062 WDT",
|
||||
@ -186,6 +194,7 @@ static const struct watchdog_ops da9062_watchdog_ops = {
|
||||
.stop = da9062_wdt_stop,
|
||||
.ping = da9062_wdt_ping,
|
||||
.set_timeout = da9062_wdt_set_timeout,
|
||||
.restart = da9062_wdt_restart,
|
||||
};
|
||||
|
||||
static const struct of_device_id da9062_compatible_id_table[] = {
|
||||
@ -215,10 +224,13 @@ static int da9062_wdt_probe(struct platform_device *pdev)
|
||||
wdt->wdtdev.ops = &da9062_watchdog_ops;
|
||||
wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
|
||||
wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
|
||||
wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
|
||||
wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
|
||||
wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
|
||||
wdt->wdtdev.parent = &pdev->dev;
|
||||
|
||||
watchdog_set_restart_priority(&wdt->wdtdev, 128);
|
||||
|
||||
watchdog_set_drvdata(&wdt->wdtdev, wdt);
|
||||
|
||||
ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev);
|
||||
|
@ -140,6 +140,42 @@ static unsigned int davinci_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
return wdd->timeout - timer_counter;
|
||||
}
|
||||
|
||||
static int davinci_wdt_restart(struct watchdog_device *wdd,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd);
|
||||
u32 tgcr, wdtcr;
|
||||
|
||||
/* disable, internal clock source */
|
||||
iowrite32(0, davinci_wdt->base + TCR);
|
||||
|
||||
/* reset timer, set mode to 64-bit watchdog, and unreset */
|
||||
tgcr = 0;
|
||||
iowrite32(tgcr, davinci_wdt->base + TGCR);
|
||||
tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET;
|
||||
iowrite32(tgcr, davinci_wdt->base + TGCR);
|
||||
|
||||
/* clear counter and period regs */
|
||||
iowrite32(0, davinci_wdt->base + TIM12);
|
||||
iowrite32(0, davinci_wdt->base + TIM34);
|
||||
iowrite32(0, davinci_wdt->base + PRD12);
|
||||
iowrite32(0, davinci_wdt->base + PRD34);
|
||||
|
||||
/* put watchdog in pre-active state */
|
||||
wdtcr = WDKEY_SEQ0 | WDEN;
|
||||
iowrite32(wdtcr, davinci_wdt->base + WDTCR);
|
||||
|
||||
/* put watchdog in active state */
|
||||
wdtcr = WDKEY_SEQ1 | WDEN;
|
||||
iowrite32(wdtcr, davinci_wdt->base + WDTCR);
|
||||
|
||||
/* write an invalid value to the WDKEY field to trigger a restart */
|
||||
wdtcr = 0x00004000;
|
||||
iowrite32(wdtcr, davinci_wdt->base + WDTCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_info davinci_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING,
|
||||
.identity = "DaVinci/Keystone Watchdog",
|
||||
@ -151,6 +187,7 @@ static const struct watchdog_ops davinci_wdt_ops = {
|
||||
.stop = davinci_wdt_ping,
|
||||
.ping = davinci_wdt_ping,
|
||||
.get_timeleft = davinci_wdt_get_timeleft,
|
||||
.restart = davinci_wdt_restart,
|
||||
};
|
||||
|
||||
static int davinci_wdt_probe(struct platform_device *pdev)
|
||||
@ -195,6 +232,7 @@ static int davinci_wdt_probe(struct platform_device *pdev)
|
||||
|
||||
watchdog_set_drvdata(wdd, davinci_wdt);
|
||||
watchdog_set_nowayout(wdd, 1);
|
||||
watchdog_set_restart_priority(wdd, 128);
|
||||
|
||||
wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
davinci_wdt->base = devm_ioremap_resource(dev, wdt_mem);
|
||||
|
@ -127,14 +127,27 @@ static int dw_wdt_start(struct watchdog_device *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_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
|
||||
|
||||
if (!dw_wdt->rst) {
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
reset_control_assert(dw_wdt->rst);
|
||||
reset_control_deassert(dw_wdt->rst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_wdt_restart(struct watchdog_device *wdd,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
@ -173,6 +186,7 @@ static const struct watchdog_info dw_wdt_ident = {
|
||||
static const struct watchdog_ops dw_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = dw_wdt_start,
|
||||
.stop = dw_wdt_stop,
|
||||
.ping = dw_wdt_ping,
|
||||
.set_timeout = dw_wdt_set_timeout,
|
||||
.get_timeleft = dw_wdt_get_timeleft,
|
||||
|
@ -290,7 +290,7 @@ static long eurwdt_ioctl(struct file *file,
|
||||
eurwdt_timeout = time;
|
||||
eurwdt_set_timeout(time);
|
||||
spin_unlock(&eurwdt_lock);
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(eurwdt_timeout, p);
|
||||
|
@ -627,7 +627,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
|
||||
|
||||
if (new_options & WDIOS_ENABLECARD)
|
||||
return watchdog_start();
|
||||
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
watchdog_keepalive();
|
||||
@ -641,7 +641,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
|
||||
return -EINVAL;
|
||||
|
||||
watchdog_keepalive();
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(watchdog.timeout, uarg.i);
|
||||
|
236
drivers/watchdog/ftwdt010_wdt.c
Normal file
236
drivers/watchdog/ftwdt010_wdt.c
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Watchdog driver for Faraday Technology FTWDT010
|
||||
*
|
||||
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
|
||||
*
|
||||
* Inspired by the out-of-tree drivers from OpenWRT:
|
||||
* Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define FTWDT010_WDCOUNTER 0x0
|
||||
#define FTWDT010_WDLOAD 0x4
|
||||
#define FTWDT010_WDRESTART 0x8
|
||||
#define FTWDT010_WDCR 0xC
|
||||
|
||||
#define WDRESTART_MAGIC 0x5AB9
|
||||
|
||||
#define WDCR_CLOCK_5MHZ BIT(4)
|
||||
#define WDCR_WDEXT BIT(3)
|
||||
#define WDCR_WDINTR BIT(2)
|
||||
#define WDCR_SYS_RST BIT(1)
|
||||
#define WDCR_ENABLE BIT(0)
|
||||
|
||||
#define WDT_CLOCK 5000000 /* 5 MHz */
|
||||
|
||||
struct ftwdt010_wdt {
|
||||
struct watchdog_device wdd;
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
bool has_irq;
|
||||
};
|
||||
|
||||
static inline
|
||||
struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd)
|
||||
{
|
||||
return container_of(wdd, struct ftwdt010_wdt, wdd);
|
||||
}
|
||||
|
||||
static int ftwdt010_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
|
||||
u32 enable;
|
||||
|
||||
writel(wdd->timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD);
|
||||
writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
|
||||
/* set clock before enabling */
|
||||
enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST;
|
||||
writel(enable, gwdt->base + FTWDT010_WDCR);
|
||||
if (gwdt->has_irq)
|
||||
enable |= WDCR_WDINTR;
|
||||
enable |= WDCR_ENABLE;
|
||||
writel(enable, gwdt->base + FTWDT010_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftwdt010_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
|
||||
|
||||
writel(0, gwdt->base + FTWDT010_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftwdt010_wdt_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
|
||||
|
||||
writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
wdd->timeout = timeout;
|
||||
if (watchdog_active(wdd))
|
||||
ftwdt010_wdt_start(wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = data;
|
||||
|
||||
watchdog_notify_pretimeout(&gwdt->wdd);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops ftwdt010_wdt_ops = {
|
||||
.start = ftwdt010_wdt_start,
|
||||
.stop = ftwdt010_wdt_stop,
|
||||
.ping = ftwdt010_wdt_ping,
|
||||
.set_timeout = ftwdt010_wdt_set_timeout,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct watchdog_info ftwdt010_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING
|
||||
| WDIOF_MAGICCLOSE
|
||||
| WDIOF_SETTIMEOUT,
|
||||
.identity = KBUILD_MODNAME,
|
||||
};
|
||||
|
||||
|
||||
static int ftwdt010_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct ftwdt010_wdt *gwdt;
|
||||
unsigned int reg;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
|
||||
if (!gwdt)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
gwdt->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(gwdt->base))
|
||||
return PTR_ERR(gwdt->base);
|
||||
|
||||
gwdt->dev = dev;
|
||||
gwdt->wdd.info = &ftwdt010_wdt_info;
|
||||
gwdt->wdd.ops = &ftwdt010_wdt_ops;
|
||||
gwdt->wdd.min_timeout = 1;
|
||||
gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;
|
||||
gwdt->wdd.parent = dev;
|
||||
|
||||
/*
|
||||
* If 'timeout-sec' unspecified in devicetree, assume a 13 second
|
||||
* default.
|
||||
*/
|
||||
gwdt->wdd.timeout = 13U;
|
||||
watchdog_init_timeout(&gwdt->wdd, 0, dev);
|
||||
|
||||
reg = readw(gwdt->base + FTWDT010_WDCR);
|
||||
if (reg & WDCR_ENABLE) {
|
||||
/* Watchdog was enabled by the bootloader, disable it. */
|
||||
reg &= ~WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + FTWDT010_WDCR);
|
||||
}
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq) {
|
||||
ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0,
|
||||
"watchdog bark", gwdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
gwdt->has_irq = true;
|
||||
}
|
||||
|
||||
ret = devm_watchdog_register_device(dev, &gwdt->wdd);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register watchdog\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set up platform driver data */
|
||||
platform_set_drvdata(pdev, gwdt);
|
||||
dev_info(dev, "FTWDT010 watchdog driver enabled\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
|
||||
unsigned int reg;
|
||||
|
||||
reg = readw(gwdt->base + FTWDT010_WDCR);
|
||||
reg &= ~WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + FTWDT010_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused ftwdt010_wdt_resume(struct device *dev)
|
||||
{
|
||||
struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
|
||||
unsigned int reg;
|
||||
|
||||
if (watchdog_active(&gwdt->wdd)) {
|
||||
reg = readw(gwdt->base + FTWDT010_WDCR);
|
||||
reg |= WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + FTWDT010_WDCR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend,
|
||||
ftwdt010_wdt_resume)
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id ftwdt010_wdt_match[] = {
|
||||
{ .compatible = "faraday,ftwdt010" },
|
||||
{ .compatible = "cortina,gemini-watchdog" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver ftwdt010_wdt_driver = {
|
||||
.probe = ftwdt010_wdt_probe,
|
||||
.driver = {
|
||||
.name = "ftwdt010-wdt",
|
||||
.of_match_table = of_match_ptr(ftwdt010_wdt_match),
|
||||
.pm = &ftwdt010_wdt_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(ftwdt010_wdt_driver);
|
||||
MODULE_AUTHOR("Linus Walleij");
|
||||
MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010");
|
||||
MODULE_LICENSE("GPL");
|
@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Watchdog driver for Cortina Systems Gemini SoC
|
||||
*
|
||||
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
|
||||
*
|
||||
* Inspired by the out-of-tree drivers from OpenWRT:
|
||||
* Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define GEMINI_WDCOUNTER 0x0
|
||||
#define GEMINI_WDLOAD 0x4
|
||||
#define GEMINI_WDRESTART 0x8
|
||||
#define GEMINI_WDCR 0xC
|
||||
|
||||
#define WDRESTART_MAGIC 0x5AB9
|
||||
|
||||
#define WDCR_CLOCK_5MHZ BIT(4)
|
||||
#define WDCR_SYS_RST BIT(1)
|
||||
#define WDCR_ENABLE BIT(0)
|
||||
|
||||
#define WDT_CLOCK 5000000 /* 5 MHz */
|
||||
|
||||
struct gemini_wdt {
|
||||
struct watchdog_device wdd;
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
static inline
|
||||
struct gemini_wdt *to_gemini_wdt(struct watchdog_device *wdd)
|
||||
{
|
||||
return container_of(wdd, struct gemini_wdt, wdd);
|
||||
}
|
||||
|
||||
static int gemini_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gemini_wdt *gwdt = to_gemini_wdt(wdd);
|
||||
|
||||
writel(wdd->timeout * WDT_CLOCK, gwdt->base + GEMINI_WDLOAD);
|
||||
writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART);
|
||||
/* set clock before enabling */
|
||||
writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST,
|
||||
gwdt->base + GEMINI_WDCR);
|
||||
writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE,
|
||||
gwdt->base + GEMINI_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gemini_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gemini_wdt *gwdt = to_gemini_wdt(wdd);
|
||||
|
||||
writel(0, gwdt->base + GEMINI_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gemini_wdt_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gemini_wdt *gwdt = to_gemini_wdt(wdd);
|
||||
|
||||
writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gemini_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int timeout)
|
||||
{
|
||||
wdd->timeout = timeout;
|
||||
if (watchdog_active(wdd))
|
||||
gemini_wdt_start(wdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t gemini_wdt_interrupt(int irq, void *data)
|
||||
{
|
||||
struct gemini_wdt *gwdt = data;
|
||||
|
||||
watchdog_notify_pretimeout(&gwdt->wdd);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops gemini_wdt_ops = {
|
||||
.start = gemini_wdt_start,
|
||||
.stop = gemini_wdt_stop,
|
||||
.ping = gemini_wdt_ping,
|
||||
.set_timeout = gemini_wdt_set_timeout,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static const struct watchdog_info gemini_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING
|
||||
| WDIOF_MAGICCLOSE
|
||||
| WDIOF_SETTIMEOUT,
|
||||
.identity = KBUILD_MODNAME,
|
||||
};
|
||||
|
||||
|
||||
static int gemini_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct gemini_wdt *gwdt;
|
||||
unsigned int reg;
|
||||
int irq;
|
||||
int ret;
|
||||
|
||||
gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
|
||||
if (!gwdt)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
gwdt->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(gwdt->base))
|
||||
return PTR_ERR(gwdt->base);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (!irq)
|
||||
return -EINVAL;
|
||||
|
||||
gwdt->dev = dev;
|
||||
gwdt->wdd.info = &gemini_wdt_info;
|
||||
gwdt->wdd.ops = &gemini_wdt_ops;
|
||||
gwdt->wdd.min_timeout = 1;
|
||||
gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;
|
||||
gwdt->wdd.parent = dev;
|
||||
|
||||
/*
|
||||
* If 'timeout-sec' unspecified in devicetree, assume a 13 second
|
||||
* default.
|
||||
*/
|
||||
gwdt->wdd.timeout = 13U;
|
||||
watchdog_init_timeout(&gwdt->wdd, 0, dev);
|
||||
|
||||
reg = readw(gwdt->base + GEMINI_WDCR);
|
||||
if (reg & WDCR_ENABLE) {
|
||||
/* Watchdog was enabled by the bootloader, disable it. */
|
||||
reg &= ~WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + GEMINI_WDCR);
|
||||
}
|
||||
|
||||
ret = devm_request_irq(dev, irq, gemini_wdt_interrupt, 0,
|
||||
"watchdog bark", gwdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_watchdog_register_device(dev, &gwdt->wdd);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register watchdog\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Set up platform driver data */
|
||||
platform_set_drvdata(pdev, gwdt);
|
||||
dev_info(dev, "Gemini watchdog driver enabled\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused gemini_wdt_suspend(struct device *dev)
|
||||
{
|
||||
struct gemini_wdt *gwdt = dev_get_drvdata(dev);
|
||||
unsigned int reg;
|
||||
|
||||
reg = readw(gwdt->base + GEMINI_WDCR);
|
||||
reg &= ~WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + GEMINI_WDCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused gemini_wdt_resume(struct device *dev)
|
||||
{
|
||||
struct gemini_wdt *gwdt = dev_get_drvdata(dev);
|
||||
unsigned int reg;
|
||||
|
||||
if (watchdog_active(&gwdt->wdd)) {
|
||||
reg = readw(gwdt->base + GEMINI_WDCR);
|
||||
reg |= WDCR_ENABLE;
|
||||
writel(reg, gwdt->base + GEMINI_WDCR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops gemini_wdt_dev_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(gemini_wdt_suspend,
|
||||
gemini_wdt_resume)
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id gemini_wdt_match[] = {
|
||||
{ .compatible = "cortina,gemini-watchdog" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, gemini_wdt_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver gemini_wdt_driver = {
|
||||
.probe = gemini_wdt_probe,
|
||||
.driver = {
|
||||
.name = "gemini-wdt",
|
||||
.of_match_table = of_match_ptr(gemini_wdt_match),
|
||||
.pm = &gemini_wdt_dev_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(gemini_wdt_driver);
|
||||
MODULE_AUTHOR("Linus Walleij");
|
||||
MODULE_DESCRIPTION("Watchdog driver for Gemini");
|
||||
MODULE_LICENSE("GPL");
|
@ -12,7 +12,8 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
@ -25,8 +26,7 @@ enum {
|
||||
};
|
||||
|
||||
struct gpio_wdt_priv {
|
||||
int gpio;
|
||||
bool active_low;
|
||||
struct gpio_desc *gpiod;
|
||||
bool state;
|
||||
bool always_running;
|
||||
unsigned int hw_algo;
|
||||
@ -35,11 +35,12 @@ struct gpio_wdt_priv {
|
||||
|
||||
static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
|
||||
{
|
||||
gpio_set_value_cansleep(priv->gpio, !priv->active_low);
|
||||
/* Eternal ping */
|
||||
gpiod_set_value_cansleep(priv->gpiod, 1);
|
||||
|
||||
/* Put GPIO back to tristate */
|
||||
if (priv->hw_algo == HW_ALGO_TOGGLE)
|
||||
gpio_direction_input(priv->gpio);
|
||||
gpiod_direction_input(priv->gpiod);
|
||||
}
|
||||
|
||||
static int gpio_wdt_ping(struct watchdog_device *wdd)
|
||||
@ -50,13 +51,13 @@ static int gpio_wdt_ping(struct watchdog_device *wdd)
|
||||
case HW_ALGO_TOGGLE:
|
||||
/* Toggle output pin */
|
||||
priv->state = !priv->state;
|
||||
gpio_set_value_cansleep(priv->gpio, priv->state);
|
||||
gpiod_set_value_cansleep(priv->gpiod, priv->state);
|
||||
break;
|
||||
case HW_ALGO_LEVEL:
|
||||
/* Pulse */
|
||||
gpio_set_value_cansleep(priv->gpio, !priv->active_low);
|
||||
gpiod_set_value_cansleep(priv->gpiod, 1);
|
||||
udelay(1);
|
||||
gpio_set_value_cansleep(priv->gpio, priv->active_low);
|
||||
gpiod_set_value_cansleep(priv->gpiod, 0);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@ -66,8 +67,8 @@ static int gpio_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
|
||||
|
||||
priv->state = priv->active_low;
|
||||
gpio_direction_output(priv->gpio, priv->state);
|
||||
priv->state = 0;
|
||||
gpiod_direction_output(priv->gpiod, priv->state);
|
||||
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
|
||||
@ -80,7 +81,8 @@ static int gpio_wdt_stop(struct watchdog_device *wdd)
|
||||
|
||||
if (!priv->always_running) {
|
||||
gpio_wdt_disable(priv);
|
||||
clear_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
} else {
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -101,44 +103,38 @@ static const struct watchdog_ops gpio_wdt_ops = {
|
||||
|
||||
static int gpio_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct gpio_wdt_priv *priv;
|
||||
enum of_gpio_flags flags;
|
||||
enum gpiod_flags gflags;
|
||||
unsigned int hw_margin;
|
||||
unsigned long f = 0;
|
||||
const char *algo;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags);
|
||||
if (!gpio_is_valid(priv->gpio))
|
||||
return priv->gpio;
|
||||
|
||||
priv->active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
|
||||
ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo);
|
||||
ret = of_property_read_string(np, "hw_algo", &algo);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (!strcmp(algo, "toggle")) {
|
||||
priv->hw_algo = HW_ALGO_TOGGLE;
|
||||
f = GPIOF_IN;
|
||||
gflags = GPIOD_IN;
|
||||
} else if (!strcmp(algo, "level")) {
|
||||
priv->hw_algo = HW_ALGO_LEVEL;
|
||||
f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
|
||||
gflags = GPIOD_OUT_LOW;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f,
|
||||
dev_name(&pdev->dev));
|
||||
if (ret)
|
||||
return ret;
|
||||
priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
|
||||
if (IS_ERR(priv->gpiod))
|
||||
return PTR_ERR(priv->gpiod);
|
||||
|
||||
ret = of_property_read_u32(pdev->dev.of_node,
|
||||
ret = of_property_read_u32(np,
|
||||
"hw_margin_ms", &hw_margin);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -146,7 +142,7 @@ static int gpio_wdt_probe(struct platform_device *pdev)
|
||||
if (hw_margin < 2 || hw_margin > 65535)
|
||||
return -EINVAL;
|
||||
|
||||
priv->always_running = of_property_read_bool(pdev->dev.of_node,
|
||||
priv->always_running = of_property_read_bool(np,
|
||||
"always-running");
|
||||
|
||||
watchdog_set_drvdata(&priv->wdd, priv);
|
||||
@ -155,9 +151,9 @@ static int gpio_wdt_probe(struct platform_device *pdev)
|
||||
priv->wdd.ops = &gpio_wdt_ops;
|
||||
priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
|
||||
priv->wdd.max_hw_heartbeat_ms = hw_margin;
|
||||
priv->wdd.parent = &pdev->dev;
|
||||
priv->wdd.parent = dev;
|
||||
|
||||
if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0)
|
||||
if (watchdog_init_timeout(&priv->wdd, 0, dev) < 0)
|
||||
priv->wdd.timeout = SOFT_TIMEOUT_DEF;
|
||||
|
||||
watchdog_stop_on_reboot(&priv->wdd);
|
||||
|
@ -52,6 +52,7 @@ static char expect_release;
|
||||
static unsigned long hpwdt_is_open;
|
||||
|
||||
static void __iomem *pci_mem_addr; /* the PCI-memory address */
|
||||
static unsigned long __iomem *hpwdt_nmistat;
|
||||
static unsigned long __iomem *hpwdt_timer_reg;
|
||||
static unsigned long __iomem *hpwdt_timer_con;
|
||||
|
||||
@ -475,6 +476,11 @@ static int hpwdt_time_left(void)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HPWDT_NMI_DECODING
|
||||
static int hpwdt_my_nmi(void)
|
||||
{
|
||||
return ioread8(hpwdt_nmistat) & 0x6;
|
||||
}
|
||||
|
||||
/*
|
||||
* NMI Handler
|
||||
*/
|
||||
@ -486,6 +492,9 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs)
|
||||
if (!hpwdt_nmi_decoding)
|
||||
return NMI_DONE;
|
||||
|
||||
if ((ulReason == NMI_UNKNOWN) && !hpwdt_my_nmi())
|
||||
return NMI_DONE;
|
||||
|
||||
spin_lock_irqsave(&rom_lock, rom_pl);
|
||||
if (!die_nmi_called && !is_icru && !is_uefi)
|
||||
asminline_call(&cmn_regs, cru_rom_addr);
|
||||
@ -700,7 +709,7 @@ static void dmi_find_icru(const struct dmi_header *dm, void *dummy)
|
||||
smbios_proliant_ptr = (struct smbios_proliant_info *) dm;
|
||||
if (smbios_proliant_ptr->misc_features & 0x01)
|
||||
is_icru = 1;
|
||||
if (smbios_proliant_ptr->misc_features & 0x408)
|
||||
if (smbios_proliant_ptr->misc_features & 0x1400)
|
||||
is_uefi = 1;
|
||||
}
|
||||
}
|
||||
@ -842,6 +851,7 @@ static int hpwdt_init_one(struct pci_dev *dev,
|
||||
retval = -ENOMEM;
|
||||
goto error_pci_iomap;
|
||||
}
|
||||
hpwdt_nmistat = pci_mem_addr + 0x6e;
|
||||
hpwdt_timer_reg = pci_mem_addr + 0x70;
|
||||
hpwdt_timer_con = pci_mem_addr + 0x72;
|
||||
|
||||
|
@ -21,14 +21,15 @@
|
||||
* Version 0.02
|
||||
* 20050210 David Härdeman <david@2gen.com>
|
||||
* Ported driver to kernel 2.6
|
||||
* 20171016 Radu Rendec <rrendec@arista.com>
|
||||
* Change driver to use the watchdog subsystem
|
||||
* Add support for multiple 6300ESB devices
|
||||
*/
|
||||
|
||||
/*
|
||||
* Includes, defines, variables, module parameters, ...
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/kernel.h>
|
||||
@ -42,19 +43,17 @@
|
||||
#include <linux/io.h>
|
||||
|
||||
/* Module and version information */
|
||||
#define ESB_VERSION "0.05"
|
||||
#define ESB_MODULE_NAME "i6300ESB timer"
|
||||
#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION
|
||||
|
||||
/* PCI configuration registers */
|
||||
#define ESB_CONFIG_REG 0x60 /* Config register */
|
||||
#define ESB_LOCK_REG 0x68 /* WDT lock register */
|
||||
|
||||
/* Memory mapped registers */
|
||||
#define ESB_TIMER1_REG (BASEADDR + 0x00)/* Timer1 value after each reset */
|
||||
#define ESB_TIMER2_REG (BASEADDR + 0x04)/* Timer2 value after each reset */
|
||||
#define ESB_GINTSR_REG (BASEADDR + 0x08)/* General Interrupt Status Register */
|
||||
#define ESB_RELOAD_REG (BASEADDR + 0x0c)/* Reload register */
|
||||
#define ESB_TIMER1_REG(w) ((w)->base + 0x00)/* Timer1 value after each reset */
|
||||
#define ESB_TIMER2_REG(w) ((w)->base + 0x04)/* Timer2 value after each reset */
|
||||
#define ESB_GINTSR_REG(w) ((w)->base + 0x08)/* General Interrupt Status Reg */
|
||||
#define ESB_RELOAD_REG(w) ((w)->base + 0x0c)/* Reload register */
|
||||
|
||||
/* Lock register bits */
|
||||
#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
|
||||
@ -74,25 +73,18 @@
|
||||
#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
|
||||
#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
|
||||
|
||||
/* internal variables */
|
||||
static void __iomem *BASEADDR;
|
||||
static DEFINE_SPINLOCK(esb_lock); /* Guards the hardware */
|
||||
static unsigned long timer_alive;
|
||||
static struct pci_dev *esb_pci;
|
||||
static unsigned short triggered; /* The status of the watchdog upon boot */
|
||||
static char esb_expect_close;
|
||||
|
||||
/* We can only use 1 card due to the /dev/watchdog restriction */
|
||||
static int cards_found;
|
||||
|
||||
/* module parameters */
|
||||
/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
|
||||
#define WATCHDOG_HEARTBEAT 30
|
||||
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
|
||||
#define ESB_HEARTBEAT_MIN 1
|
||||
#define ESB_HEARTBEAT_MAX 2046
|
||||
#define ESB_HEARTBEAT_DEFAULT 30
|
||||
#define ESB_HEARTBEAT_RANGE __MODULE_STRING(ESB_HEARTBEAT_MIN) \
|
||||
"<heartbeat<" __MODULE_STRING(ESB_HEARTBEAT_MAX)
|
||||
static int heartbeat; /* in seconds */
|
||||
module_param(heartbeat, int, 0);
|
||||
MODULE_PARM_DESC(heartbeat,
|
||||
"Watchdog heartbeat in seconds. (1<heartbeat<2046, default="
|
||||
__MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
|
||||
"Watchdog heartbeat in seconds. (" ESB_HEARTBEAT_RANGE
|
||||
", default=" __MODULE_STRING(ESB_HEARTBEAT_DEFAULT) ")");
|
||||
|
||||
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||
module_param(nowayout, bool, 0);
|
||||
@ -100,6 +92,15 @@ MODULE_PARM_DESC(nowayout,
|
||||
"Watchdog cannot be stopped once started (default="
|
||||
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
/* internal variables */
|
||||
struct esb_dev {
|
||||
struct watchdog_device wdd;
|
||||
void __iomem *base;
|
||||
struct pci_dev *pdev;
|
||||
};
|
||||
|
||||
#define to_esb_dev(wptr) container_of(wptr, struct esb_dev, wdd)
|
||||
|
||||
/*
|
||||
* Some i6300ESB specific functions
|
||||
*/
|
||||
@ -110,61 +111,58 @@ MODULE_PARM_DESC(nowayout,
|
||||
* reload register. After this the appropriate registers can be written
|
||||
* to once before they need to be unlocked again.
|
||||
*/
|
||||
static inline void esb_unlock_registers(void)
|
||||
static inline void esb_unlock_registers(struct esb_dev *edev)
|
||||
{
|
||||
writew(ESB_UNLOCK1, ESB_RELOAD_REG);
|
||||
writew(ESB_UNLOCK2, ESB_RELOAD_REG);
|
||||
writew(ESB_UNLOCK1, ESB_RELOAD_REG(edev));
|
||||
writew(ESB_UNLOCK2, ESB_RELOAD_REG(edev));
|
||||
}
|
||||
|
||||
static int esb_timer_start(void)
|
||||
static int esb_timer_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct esb_dev *edev = to_esb_dev(wdd);
|
||||
int _wdd_nowayout = test_bit(WDOG_NO_WAY_OUT, &wdd->status);
|
||||
u8 val;
|
||||
|
||||
spin_lock(&esb_lock);
|
||||
esb_unlock_registers();
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
|
||||
/* Enable or Enable + Lock? */
|
||||
val = ESB_WDT_ENABLE | (nowayout ? ESB_WDT_LOCK : 0x00);
|
||||
pci_write_config_byte(esb_pci, ESB_LOCK_REG, val);
|
||||
spin_unlock(&esb_lock);
|
||||
val = ESB_WDT_ENABLE | (_wdd_nowayout ? ESB_WDT_LOCK : 0x00);
|
||||
pci_write_config_byte(edev->pdev, ESB_LOCK_REG, val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int esb_timer_stop(void)
|
||||
static int esb_timer_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct esb_dev *edev = to_esb_dev(wdd);
|
||||
u8 val;
|
||||
|
||||
spin_lock(&esb_lock);
|
||||
/* First, reset timers as suggested by the docs */
|
||||
esb_unlock_registers();
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
|
||||
/* Then disable the WDT */
|
||||
pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x0);
|
||||
pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val);
|
||||
spin_unlock(&esb_lock);
|
||||
pci_write_config_byte(edev->pdev, ESB_LOCK_REG, 0x0);
|
||||
pci_read_config_byte(edev->pdev, ESB_LOCK_REG, &val);
|
||||
|
||||
/* Returns 0 if the timer was disabled, non-zero otherwise */
|
||||
return val & ESB_WDT_ENABLE;
|
||||
}
|
||||
|
||||
static void esb_timer_keepalive(void)
|
||||
static int esb_timer_keepalive(struct watchdog_device *wdd)
|
||||
{
|
||||
spin_lock(&esb_lock);
|
||||
esb_unlock_registers();
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG);
|
||||
struct esb_dev *edev = to_esb_dev(wdd);
|
||||
|
||||
esb_unlock_registers(edev);
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
|
||||
/* FIXME: Do we need to flush anything here? */
|
||||
spin_unlock(&esb_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int esb_timer_set_heartbeat(int time)
|
||||
static int esb_timer_set_heartbeat(struct watchdog_device *wdd,
|
||||
unsigned int time)
|
||||
{
|
||||
struct esb_dev *edev = to_esb_dev(wdd);
|
||||
u32 val;
|
||||
|
||||
if (time < 0x1 || time > (2 * 0x03ff))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock(&esb_lock);
|
||||
|
||||
/* We shift by 9, so if we are passed a value of 1 sec,
|
||||
* val will be 1 << 9 = 512, then write that to two
|
||||
* timers => 2 * 512 = 1024 (which is decremented at 1KHz)
|
||||
@ -172,162 +170,39 @@ static int esb_timer_set_heartbeat(int time)
|
||||
val = time << 9;
|
||||
|
||||
/* Write timer 1 */
|
||||
esb_unlock_registers();
|
||||
writel(val, ESB_TIMER1_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writel(val, ESB_TIMER1_REG(edev));
|
||||
|
||||
/* Write timer 2 */
|
||||
esb_unlock_registers();
|
||||
writel(val, ESB_TIMER2_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writel(val, ESB_TIMER2_REG(edev));
|
||||
|
||||
/* Reload */
|
||||
esb_unlock_registers();
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
|
||||
|
||||
/* FIXME: Do we need to flush everything out? */
|
||||
|
||||
/* Done */
|
||||
heartbeat = time;
|
||||
spin_unlock(&esb_lock);
|
||||
wdd->timeout = time;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* /dev/watchdog handling
|
||||
* Watchdog Subsystem Interfaces
|
||||
*/
|
||||
|
||||
static int esb_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
/* /dev/watchdog can only be opened once */
|
||||
if (test_and_set_bit(0, &timer_alive))
|
||||
return -EBUSY;
|
||||
|
||||
/* Reload and activate timer */
|
||||
esb_timer_start();
|
||||
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static int esb_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
/* Shut off the timer. */
|
||||
if (esb_expect_close == 42)
|
||||
esb_timer_stop();
|
||||
else {
|
||||
pr_crit("Unexpected close, not stopping watchdog!\n");
|
||||
esb_timer_keepalive();
|
||||
}
|
||||
clear_bit(0, &timer_alive);
|
||||
esb_expect_close = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t esb_write(struct file *file, const char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
/* See if we got the magic character 'V' and reload the timer */
|
||||
if (len) {
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
/* note: just in case someone wrote the magic character
|
||||
* five months ago... */
|
||||
esb_expect_close = 0;
|
||||
|
||||
/* scan to see whether or not we got the
|
||||
* magic character */
|
||||
for (i = 0; i != len; i++) {
|
||||
char c;
|
||||
if (get_user(c, data + i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
esb_expect_close = 42;
|
||||
}
|
||||
}
|
||||
|
||||
/* someone wrote to us, we should reload the timer */
|
||||
esb_timer_keepalive();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static long esb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int new_options, retval = -EINVAL;
|
||||
int new_heartbeat;
|
||||
void __user *argp = (void __user *)arg;
|
||||
int __user *p = argp;
|
||||
static const struct watchdog_info ident = {
|
||||
.options = WDIOF_SETTIMEOUT |
|
||||
WDIOF_KEEPALIVEPING |
|
||||
WDIOF_MAGICCLOSE,
|
||||
.firmware_version = 0,
|
||||
.identity = ESB_MODULE_NAME,
|
||||
};
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
return copy_to_user(argp, &ident,
|
||||
sizeof(ident)) ? -EFAULT : 0;
|
||||
|
||||
case WDIOC_GETSTATUS:
|
||||
return put_user(0, p);
|
||||
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
return put_user(triggered, p);
|
||||
|
||||
case WDIOC_SETOPTIONS:
|
||||
{
|
||||
if (get_user(new_options, p))
|
||||
return -EFAULT;
|
||||
|
||||
if (new_options & WDIOS_DISABLECARD) {
|
||||
esb_timer_stop();
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
if (new_options & WDIOS_ENABLECARD) {
|
||||
esb_timer_start();
|
||||
retval = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
case WDIOC_KEEPALIVE:
|
||||
esb_timer_keepalive();
|
||||
return 0;
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
{
|
||||
if (get_user(new_heartbeat, p))
|
||||
return -EFAULT;
|
||||
if (esb_timer_set_heartbeat(new_heartbeat))
|
||||
return -EINVAL;
|
||||
esb_timer_keepalive();
|
||||
/* Fall */
|
||||
}
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernel Interfaces
|
||||
*/
|
||||
|
||||
static const struct file_operations esb_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.write = esb_write,
|
||||
.unlocked_ioctl = esb_ioctl,
|
||||
.open = esb_open,
|
||||
.release = esb_release,
|
||||
static struct watchdog_info esb_info = {
|
||||
.identity = ESB_MODULE_NAME,
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||
};
|
||||
|
||||
static struct miscdevice esb_miscdev = {
|
||||
.minor = WATCHDOG_MINOR,
|
||||
.name = "watchdog",
|
||||
.fops = &esb_fops,
|
||||
static const struct watchdog_ops esb_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = esb_timer_start,
|
||||
.stop = esb_timer_stop,
|
||||
.set_timeout = esb_timer_set_heartbeat,
|
||||
.ping = esb_timer_keepalive,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -343,38 +218,38 @@ MODULE_DEVICE_TABLE(pci, esb_pci_tbl);
|
||||
* Init & exit routines
|
||||
*/
|
||||
|
||||
static unsigned char esb_getdevice(struct pci_dev *pdev)
|
||||
static unsigned char esb_getdevice(struct esb_dev *edev)
|
||||
{
|
||||
if (pci_enable_device(pdev)) {
|
||||
pr_err("failed to enable device\n");
|
||||
if (pci_enable_device(edev->pdev)) {
|
||||
dev_err(&edev->pdev->dev, "failed to enable device\n");
|
||||
goto err_devput;
|
||||
}
|
||||
|
||||
if (pci_request_region(pdev, 0, ESB_MODULE_NAME)) {
|
||||
pr_err("failed to request region\n");
|
||||
if (pci_request_region(edev->pdev, 0, ESB_MODULE_NAME)) {
|
||||
dev_err(&edev->pdev->dev, "failed to request region\n");
|
||||
goto err_disable;
|
||||
}
|
||||
|
||||
BASEADDR = pci_ioremap_bar(pdev, 0);
|
||||
if (BASEADDR == NULL) {
|
||||
edev->base = pci_ioremap_bar(edev->pdev, 0);
|
||||
if (edev->base == NULL) {
|
||||
/* Something's wrong here, BASEADDR has to be set */
|
||||
pr_err("failed to get BASEADDR\n");
|
||||
dev_err(&edev->pdev->dev, "failed to get BASEADDR\n");
|
||||
goto err_release;
|
||||
}
|
||||
|
||||
/* Done */
|
||||
esb_pci = pdev;
|
||||
dev_set_drvdata(&edev->pdev->dev, edev);
|
||||
return 1;
|
||||
|
||||
err_release:
|
||||
pci_release_region(pdev, 0);
|
||||
pci_release_region(edev->pdev, 0);
|
||||
err_disable:
|
||||
pci_disable_device(pdev);
|
||||
pci_disable_device(edev->pdev);
|
||||
err_devput:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void esb_initdevice(void)
|
||||
static void esb_initdevice(struct esb_dev *edev)
|
||||
{
|
||||
u8 val1;
|
||||
u16 val2;
|
||||
@ -391,96 +266,87 @@ static void esb_initdevice(void)
|
||||
* any interrupts as there is not much we can do with it
|
||||
* right now.
|
||||
*/
|
||||
pci_write_config_word(esb_pci, ESB_CONFIG_REG, 0x0003);
|
||||
pci_write_config_word(edev->pdev, ESB_CONFIG_REG, 0x0003);
|
||||
|
||||
/* Check that the WDT isn't already locked */
|
||||
pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1);
|
||||
pci_read_config_byte(edev->pdev, ESB_LOCK_REG, &val1);
|
||||
if (val1 & ESB_WDT_LOCK)
|
||||
pr_warn("nowayout already set\n");
|
||||
dev_warn(&edev->pdev->dev, "nowayout already set\n");
|
||||
|
||||
/* Set the timer to watchdog mode and disable it for now */
|
||||
pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00);
|
||||
pci_write_config_byte(edev->pdev, ESB_LOCK_REG, 0x00);
|
||||
|
||||
/* Check if the watchdog was previously triggered */
|
||||
esb_unlock_registers();
|
||||
val2 = readw(ESB_RELOAD_REG);
|
||||
esb_unlock_registers(edev);
|
||||
val2 = readw(ESB_RELOAD_REG(edev));
|
||||
if (val2 & ESB_WDT_TIMEOUT)
|
||||
triggered = WDIOF_CARDRESET;
|
||||
edev->wdd.bootstatus = WDIOF_CARDRESET;
|
||||
|
||||
/* Reset WDT_TIMEOUT flag and timers */
|
||||
esb_unlock_registers();
|
||||
writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG);
|
||||
esb_unlock_registers(edev);
|
||||
writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG(edev));
|
||||
|
||||
/* And set the correct timeout value */
|
||||
esb_timer_set_heartbeat(heartbeat);
|
||||
esb_timer_set_heartbeat(&edev->wdd, edev->wdd.timeout);
|
||||
}
|
||||
|
||||
static int esb_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
struct esb_dev *edev;
|
||||
int ret;
|
||||
|
||||
cards_found++;
|
||||
if (cards_found == 1)
|
||||
pr_info("Intel 6300ESB WatchDog Timer Driver v%s\n",
|
||||
ESB_VERSION);
|
||||
|
||||
if (cards_found > 1) {
|
||||
pr_err("This driver only supports 1 device\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL);
|
||||
if (!edev)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Check whether or not the hardware watchdog is there */
|
||||
if (!esb_getdevice(pdev) || esb_pci == NULL)
|
||||
edev->pdev = pdev;
|
||||
if (!esb_getdevice(edev))
|
||||
return -ENODEV;
|
||||
|
||||
/* Check that the heartbeat value is within it's range;
|
||||
if not reset to the default */
|
||||
if (heartbeat < 0x1 || heartbeat > 2 * 0x03ff) {
|
||||
heartbeat = WATCHDOG_HEARTBEAT;
|
||||
pr_info("heartbeat value must be 1<heartbeat<2046, using %d\n",
|
||||
heartbeat);
|
||||
}
|
||||
|
||||
/* Initialize the watchdog and make sure it does not run */
|
||||
esb_initdevice();
|
||||
edev->wdd.info = &esb_info;
|
||||
edev->wdd.ops = &esb_ops;
|
||||
edev->wdd.min_timeout = ESB_HEARTBEAT_MIN;
|
||||
edev->wdd.max_timeout = ESB_HEARTBEAT_MAX;
|
||||
edev->wdd.timeout = ESB_HEARTBEAT_DEFAULT;
|
||||
if (watchdog_init_timeout(&edev->wdd, heartbeat, NULL))
|
||||
dev_info(&pdev->dev,
|
||||
"heartbeat value must be " ESB_HEARTBEAT_RANGE
|
||||
", using %u\n", edev->wdd.timeout);
|
||||
watchdog_set_nowayout(&edev->wdd, nowayout);
|
||||
watchdog_stop_on_reboot(&edev->wdd);
|
||||
watchdog_stop_on_unregister(&edev->wdd);
|
||||
esb_initdevice(edev);
|
||||
|
||||
/* Register the watchdog so that userspace has access to it */
|
||||
ret = misc_register(&esb_miscdev);
|
||||
ret = watchdog_register_device(&edev->wdd);
|
||||
if (ret != 0) {
|
||||
pr_err("cannot register miscdev on minor=%d (err=%d)\n",
|
||||
WATCHDOG_MINOR, ret);
|
||||
dev_err(&pdev->dev,
|
||||
"cannot register watchdog device (err=%d)\n", ret);
|
||||
goto err_unmap;
|
||||
}
|
||||
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
|
||||
BASEADDR, heartbeat, nowayout);
|
||||
dev_info(&pdev->dev,
|
||||
"initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
|
||||
edev->base, edev->wdd.timeout, nowayout);
|
||||
return 0;
|
||||
|
||||
err_unmap:
|
||||
iounmap(BASEADDR);
|
||||
pci_release_region(esb_pci, 0);
|
||||
pci_disable_device(esb_pci);
|
||||
esb_pci = NULL;
|
||||
iounmap(edev->base);
|
||||
pci_release_region(edev->pdev, 0);
|
||||
pci_disable_device(edev->pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void esb_remove(struct pci_dev *pdev)
|
||||
{
|
||||
/* Stop the timer before we leave */
|
||||
if (!nowayout)
|
||||
esb_timer_stop();
|
||||
struct esb_dev *edev = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
/* Deregister */
|
||||
misc_deregister(&esb_miscdev);
|
||||
iounmap(BASEADDR);
|
||||
pci_release_region(esb_pci, 0);
|
||||
pci_disable_device(esb_pci);
|
||||
esb_pci = NULL;
|
||||
}
|
||||
|
||||
static void esb_shutdown(struct pci_dev *pdev)
|
||||
{
|
||||
esb_timer_stop();
|
||||
watchdog_unregister_device(&edev->wdd);
|
||||
iounmap(edev->base);
|
||||
pci_release_region(edev->pdev, 0);
|
||||
pci_disable_device(edev->pdev);
|
||||
}
|
||||
|
||||
static struct pci_driver esb_driver = {
|
||||
@ -488,7 +354,6 @@ static struct pci_driver esb_driver = {
|
||||
.id_table = esb_pci_tbl,
|
||||
.probe = esb_probe,
|
||||
.remove = esb_remove,
|
||||
.shutdown = esb_shutdown,
|
||||
};
|
||||
|
||||
module_pci_driver(esb_driver);
|
||||
|
@ -218,7 +218,7 @@ static long ibwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
if (ibwdt_set_heartbeat(new_margin))
|
||||
return -EINVAL;
|
||||
ibwdt_ping();
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(timeout, p);
|
||||
|
@ -169,15 +169,21 @@ static int imx2_wdt_ping(struct watchdog_device *wdog)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int imx2_wdt_set_timeout(struct watchdog_device *wdog,
|
||||
unsigned int new_timeout)
|
||||
static void __imx2_wdt_set_timeout(struct watchdog_device *wdog,
|
||||
unsigned int new_timeout)
|
||||
{
|
||||
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
|
||||
|
||||
wdog->timeout = new_timeout;
|
||||
|
||||
regmap_update_bits(wdev->regmap, IMX2_WDT_WCR, IMX2_WDT_WCR_WT,
|
||||
WDOG_SEC_TO_COUNT(new_timeout));
|
||||
}
|
||||
|
||||
static int imx2_wdt_set_timeout(struct watchdog_device *wdog,
|
||||
unsigned int new_timeout)
|
||||
{
|
||||
__imx2_wdt_set_timeout(wdog, new_timeout);
|
||||
|
||||
wdog->timeout = new_timeout;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -371,7 +377,11 @@ static int imx2_wdt_suspend(struct device *dev)
|
||||
|
||||
/* The watchdog IP block is running */
|
||||
if (imx2_wdt_is_running(wdev)) {
|
||||
imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||
/*
|
||||
* Don't update wdog->timeout, we'll restore the current value
|
||||
* during resume.
|
||||
*/
|
||||
__imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
|
||||
imx2_wdt_ping(wdog);
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,7 @@ static const struct watchdog_ops jz4740_wdt_ops = {
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id jz4740_wdt_of_matches[] = {
|
||||
{ .compatible = "ingenic,jz4740-watchdog", },
|
||||
{ .compatible = "ingenic,jz4780-watchdog", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
|
||||
|
@ -526,12 +526,11 @@ static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf,
|
||||
size_t cnt, loff_t *ppos)
|
||||
{
|
||||
struct mei_wdt *wdt = file->private_data;
|
||||
const size_t bufsz = 32;
|
||||
char buf[bufsz];
|
||||
char buf[32];
|
||||
ssize_t pos;
|
||||
|
||||
pos = scnprintf(buf, bufsz, "state: %s\n",
|
||||
mei_wdt_state_str(wdt->state));
|
||||
pos = scnprintf(buf, sizeof(buf), "state: %s\n",
|
||||
mei_wdt_state_str(wdt->state));
|
||||
|
||||
return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
|
||||
}
|
||||
|
@ -22,7 +22,6 @@
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/module.h>
|
||||
@ -31,10 +30,13 @@
|
||||
#include <linux/uaccess.h>
|
||||
#include <sysdev/fsl_soc.h>
|
||||
|
||||
#define WATCHDOG_TIMEOUT 10
|
||||
|
||||
struct mpc8xxx_wdt {
|
||||
__be32 res0;
|
||||
__be32 swcrr; /* System watchdog control register */
|
||||
#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */
|
||||
#define SWCRR_SWF 0x00000008 /* Software Watchdog Freeze (mpc8xx). */
|
||||
#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
|
||||
#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
|
||||
#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
|
||||
@ -52,14 +54,15 @@ struct mpc8xxx_wdt_type {
|
||||
struct mpc8xxx_wdt_ddata {
|
||||
struct mpc8xxx_wdt __iomem *base;
|
||||
struct watchdog_device wdd;
|
||||
struct timer_list timer;
|
||||
spinlock_t lock;
|
||||
u16 swtc;
|
||||
};
|
||||
|
||||
static u16 timeout = 0xffff;
|
||||
static u16 timeout;
|
||||
module_param(timeout, ushort, 0);
|
||||
MODULE_PARM_DESC(timeout,
|
||||
"Watchdog timeout in ticks. (0<timeout<65536, default=65535)");
|
||||
"Watchdog timeout in seconds. (1<timeout<65535, default="
|
||||
__MODULE_STRING(WATCHDOG_TIMEOUT) ")");
|
||||
|
||||
static bool reset = 1;
|
||||
module_param(reset, bool, 0);
|
||||
@ -80,31 +83,27 @@ static void mpc8xxx_wdt_keepalive(struct mpc8xxx_wdt_ddata *ddata)
|
||||
spin_unlock(&ddata->lock);
|
||||
}
|
||||
|
||||
static void mpc8xxx_wdt_timer_ping(struct timer_list *t)
|
||||
{
|
||||
struct mpc8xxx_wdt_ddata *ddata = from_timer(ddata, t, timer);
|
||||
|
||||
mpc8xxx_wdt_keepalive(ddata);
|
||||
/* We're pinging it twice faster than needed, just to be sure. */
|
||||
mod_timer(&ddata->timer, jiffies + HZ * ddata->wdd.timeout / 2);
|
||||
}
|
||||
|
||||
static int mpc8xxx_wdt_start(struct watchdog_device *w)
|
||||
{
|
||||
struct mpc8xxx_wdt_ddata *ddata =
|
||||
container_of(w, struct mpc8xxx_wdt_ddata, wdd);
|
||||
|
||||
u32 tmp = SWCRR_SWEN | SWCRR_SWPR;
|
||||
u32 tmp = in_be32(&ddata->base->swcrr);
|
||||
|
||||
/* Good, fire up the show */
|
||||
tmp &= ~(SWCRR_SWTC | SWCRR_SWF | SWCRR_SWEN | SWCRR_SWRI | SWCRR_SWPR);
|
||||
tmp |= SWCRR_SWEN | SWCRR_SWPR | (ddata->swtc << 16);
|
||||
|
||||
if (reset)
|
||||
tmp |= SWCRR_SWRI;
|
||||
|
||||
tmp |= timeout << 16;
|
||||
|
||||
out_be32(&ddata->base->swcrr, tmp);
|
||||
|
||||
del_timer_sync(&ddata->timer);
|
||||
tmp = in_be32(&ddata->base->swcrr);
|
||||
if (!(tmp & SWCRR_SWEN))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ddata->swtc = tmp >> 16;
|
||||
set_bit(WDOG_HW_RUNNING, &ddata->wdd.status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -118,17 +117,8 @@ static int mpc8xxx_wdt_ping(struct watchdog_device *w)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpc8xxx_wdt_stop(struct watchdog_device *w)
|
||||
{
|
||||
struct mpc8xxx_wdt_ddata *ddata =
|
||||
container_of(w, struct mpc8xxx_wdt_ddata, wdd);
|
||||
|
||||
mod_timer(&ddata->timer, jiffies);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct watchdog_info mpc8xxx_wdt_info = {
|
||||
.options = WDIOF_KEEPALIVEPING,
|
||||
.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT,
|
||||
.firmware_version = 1,
|
||||
.identity = "MPC8xxx",
|
||||
};
|
||||
@ -137,7 +127,6 @@ static struct watchdog_ops mpc8xxx_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = mpc8xxx_wdt_start,
|
||||
.ping = mpc8xxx_wdt_ping,
|
||||
.stop = mpc8xxx_wdt_stop,
|
||||
};
|
||||
|
||||
static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
|
||||
@ -148,7 +137,6 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
|
||||
struct mpc8xxx_wdt_ddata *ddata;
|
||||
u32 freq = fsl_get_sys_freq();
|
||||
bool enabled;
|
||||
unsigned int timeout_sec;
|
||||
|
||||
wdt_type = of_device_get_match_data(&ofdev->dev);
|
||||
if (!wdt_type)
|
||||
@ -173,26 +161,17 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
|
||||
}
|
||||
|
||||
spin_lock_init(&ddata->lock);
|
||||
timer_setup(&ddata->timer, mpc8xxx_wdt_timer_ping, 0);
|
||||
|
||||
ddata->wdd.info = &mpc8xxx_wdt_info,
|
||||
ddata->wdd.ops = &mpc8xxx_wdt_ops,
|
||||
|
||||
/* Calculate the timeout in seconds */
|
||||
timeout_sec = (timeout * wdt_type->prescaler) / freq;
|
||||
|
||||
ddata->wdd.timeout = timeout_sec;
|
||||
ddata->wdd.timeout = WATCHDOG_TIMEOUT;
|
||||
watchdog_init_timeout(&ddata->wdd, timeout, &ofdev->dev);
|
||||
|
||||
watchdog_set_nowayout(&ddata->wdd, nowayout);
|
||||
|
||||
ret = watchdog_register_device(&ddata->wdd);
|
||||
if (ret) {
|
||||
pr_err("cannot register watchdog device (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_info("WDT driver for MPC8xxx initialized. mode:%s timeout=%d (%d seconds)\n",
|
||||
reset ? "reset" : "interrupt", timeout, timeout_sec);
|
||||
ddata->swtc = min(ddata->wdd.timeout * freq / wdt_type->prescaler,
|
||||
0xffffU);
|
||||
|
||||
/*
|
||||
* If the watchdog was previously enabled or we're running on
|
||||
@ -200,7 +179,22 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
|
||||
* userspace handles it.
|
||||
*/
|
||||
if (enabled)
|
||||
mod_timer(&ddata->timer, jiffies);
|
||||
mpc8xxx_wdt_start(&ddata->wdd);
|
||||
|
||||
ddata->wdd.max_hw_heartbeat_ms = (ddata->swtc * wdt_type->prescaler) /
|
||||
(freq / 1000);
|
||||
ddata->wdd.min_timeout = ddata->wdd.max_hw_heartbeat_ms / 1000;
|
||||
if (ddata->wdd.timeout < ddata->wdd.min_timeout)
|
||||
ddata->wdd.timeout = ddata->wdd.min_timeout;
|
||||
|
||||
ret = watchdog_register_device(&ddata->wdd);
|
||||
if (ret) {
|
||||
pr_err("cannot register watchdog device (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_info("WDT driver for MPC8xxx initialized. mode:%s timeout=%d sec\n",
|
||||
reset ? "reset" : "interrupt", ddata->wdd.timeout);
|
||||
|
||||
platform_set_drvdata(ofdev, ddata);
|
||||
return 0;
|
||||
@ -212,7 +206,6 @@ static int mpc8xxx_wdt_remove(struct platform_device *ofdev)
|
||||
|
||||
pr_crit("Watchdog removed, expect the %s soon!\n",
|
||||
reset ? "reset" : "machine check exception");
|
||||
del_timer_sync(&ddata->timer);
|
||||
watchdog_unregister_device(&ddata->wdd);
|
||||
|
||||
return 0;
|
||||
|
@ -105,6 +105,11 @@ static int mt7621_wdt_bootcause(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt7621_wdt_is_running(struct watchdog_device *w)
|
||||
{
|
||||
return !!(rt_wdt_r32(TIMER_REG_TMR1CTL) & TMR1CTL_ENABLE);
|
||||
}
|
||||
|
||||
static const struct watchdog_info mt7621_wdt_info = {
|
||||
.identity = "Mediatek Watchdog",
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||
@ -128,7 +133,6 @@ static struct watchdog_device mt7621_wdt_dev = {
|
||||
static int mt7621_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
mt7621_wdt_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
@ -144,17 +148,22 @@ static int mt7621_wdt_probe(struct platform_device *pdev)
|
||||
watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout,
|
||||
&pdev->dev);
|
||||
watchdog_set_nowayout(&mt7621_wdt_dev, nowayout);
|
||||
if (mt7621_wdt_is_running(&mt7621_wdt_dev)) {
|
||||
/*
|
||||
* Make sure to apply timeout from watchdog core, taking
|
||||
* the prescaler of this driver here into account (the
|
||||
* boot loader might be using a different prescaler).
|
||||
*
|
||||
* To avoid spurious resets because of different scaling,
|
||||
* we first disable the watchdog, set the new prescaler
|
||||
* and timeout, and then re-enable the watchdog.
|
||||
*/
|
||||
mt7621_wdt_stop(&mt7621_wdt_dev);
|
||||
mt7621_wdt_start(&mt7621_wdt_dev);
|
||||
set_bit(WDOG_HW_RUNNING, &mt7621_wdt_dev.status);
|
||||
}
|
||||
|
||||
ret = watchdog_register_device(&mt7621_wdt_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt7621_wdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
watchdog_unregister_device(&mt7621_wdt_dev);
|
||||
|
||||
return 0;
|
||||
return devm_watchdog_register_device(&pdev->dev, &mt7621_wdt_dev);
|
||||
}
|
||||
|
||||
static void mt7621_wdt_shutdown(struct platform_device *pdev)
|
||||
@ -170,7 +179,6 @@ MODULE_DEVICE_TABLE(of, mt7621_wdt_match);
|
||||
|
||||
static struct platform_driver mt7621_wdt_driver = {
|
||||
.probe = mt7621_wdt_probe,
|
||||
.remove = mt7621_wdt_remove,
|
||||
.shutdown = mt7621_wdt_shutdown,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
|
@ -576,7 +576,7 @@ static int orion_wdt_probe(struct platform_device *pdev)
|
||||
/*
|
||||
* Let's make sure the watchdog is fully stopped, unless it's
|
||||
* explicitly enabled. This may be the case if the module was
|
||||
* removed and re-insterted, or if the bootloader explicitly
|
||||
* removed and re-inserted, or if the bootloader explicitly
|
||||
* set a running watchdog before booting the kernel.
|
||||
*/
|
||||
if (!orion_wdt_enabled(&dev->wdt))
|
||||
|
@ -545,8 +545,8 @@ static long pcipcwd_ioctl(struct file *file, unsigned int cmd,
|
||||
return -EINVAL;
|
||||
|
||||
pcipcwd_keepalive();
|
||||
/* Fall */
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
|
@ -49,12 +49,11 @@
|
||||
#define DRIVER_VERSION "1.02"
|
||||
#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
|
||||
#define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
|
||||
#define DRIVER_LICENSE "GPL"
|
||||
#define DRIVER_NAME "pcwd_usb"
|
||||
|
||||
MODULE_AUTHOR(DRIVER_AUTHOR);
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE(DRIVER_LICENSE);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
#define WATCHDOG_HEARTBEAT 0 /* default heartbeat =
|
||||
delay-time from dip-switches */
|
||||
@ -456,8 +455,8 @@ static long usb_pcwd_ioctl(struct file *file, unsigned int cmd,
|
||||
return -EINVAL;
|
||||
|
||||
usb_pcwd_keepalive(usb_pcwd_device);
|
||||
/* Fall */
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
|
168
drivers/watchdog/rtd119x_wdt.c
Normal file
168
drivers/watchdog/rtd119x_wdt.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Realtek RTD129x watchdog
|
||||
*
|
||||
* Copyright (c) 2017 Andreas Färber
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define RTD119X_TCWCR 0x0
|
||||
#define RTD119X_TCWTR 0x4
|
||||
#define RTD119X_TCWOV 0xc
|
||||
|
||||
#define RTD119X_TCWCR_WDEN_DISABLED 0xa5
|
||||
#define RTD119X_TCWCR_WDEN_ENABLED 0xff
|
||||
#define RTD119X_TCWCR_WDEN_MASK 0xff
|
||||
|
||||
#define RTD119X_TCWTR_WDCLR BIT(0)
|
||||
|
||||
struct rtd119x_watchdog_device {
|
||||
struct watchdog_device wdt_dev;
|
||||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
};
|
||||
|
||||
static int rtd119x_wdt_start(struct watchdog_device *wdev)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(data->base + RTD119X_TCWCR);
|
||||
val &= ~RTD119X_TCWCR_WDEN_MASK;
|
||||
val |= RTD119X_TCWCR_WDEN_ENABLED;
|
||||
writel(val, data->base + RTD119X_TCWCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd119x_wdt_stop(struct watchdog_device *wdev)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(data->base + RTD119X_TCWCR);
|
||||
val &= ~RTD119X_TCWCR_WDEN_MASK;
|
||||
val |= RTD119X_TCWCR_WDEN_DISABLED;
|
||||
writel(val, data->base + RTD119X_TCWCR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd119x_wdt_ping(struct watchdog_device *wdev)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
|
||||
|
||||
writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
|
||||
|
||||
return rtd119x_wdt_start(wdev);
|
||||
}
|
||||
|
||||
static int rtd119x_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
|
||||
|
||||
writel(val * clk_get_rate(data->clk), data->base + RTD119X_TCWOV);
|
||||
|
||||
data->wdt_dev.timeout = val;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops rtd119x_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = rtd119x_wdt_start,
|
||||
.stop = rtd119x_wdt_stop,
|
||||
.ping = rtd119x_wdt_ping,
|
||||
.set_timeout = rtd119x_wdt_set_timeout,
|
||||
};
|
||||
|
||||
static const struct watchdog_info rtd119x_wdt_info = {
|
||||
.identity = "rtd119x-wdt",
|
||||
.options = 0,
|
||||
};
|
||||
|
||||
static const struct of_device_id rtd119x_wdt_dt_ids[] = {
|
||||
{ .compatible = "realtek,rtd1295-watchdog" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static int rtd119x_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->base))
|
||||
return PTR_ERR(data->base);
|
||||
|
||||
data->clk = of_clk_get(pdev->dev.of_node, 0);
|
||||
if (IS_ERR(data->clk))
|
||||
return PTR_ERR(data->clk);
|
||||
|
||||
ret = clk_prepare_enable(data->clk);
|
||||
if (ret) {
|
||||
clk_put(data->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->wdt_dev.info = &rtd119x_wdt_info;
|
||||
data->wdt_dev.ops = &rtd119x_wdt_ops;
|
||||
data->wdt_dev.timeout = 120;
|
||||
data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk);
|
||||
data->wdt_dev.min_timeout = 1;
|
||||
data->wdt_dev.parent = &pdev->dev;
|
||||
|
||||
watchdog_stop_on_reboot(&data->wdt_dev);
|
||||
watchdog_set_drvdata(&data->wdt_dev, data);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
|
||||
rtd119x_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
|
||||
rtd119x_wdt_stop(&data->wdt_dev);
|
||||
|
||||
ret = devm_watchdog_register_device(&pdev->dev, &data->wdt_dev);
|
||||
if (ret) {
|
||||
clk_disable_unprepare(data->clk);
|
||||
clk_put(data->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtd119x_wdt_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtd119x_watchdog_device *data = platform_get_drvdata(pdev);
|
||||
|
||||
watchdog_unregister_device(&data->wdt_dev);
|
||||
|
||||
clk_disable_unprepare(data->clk);
|
||||
clk_put(data->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rtd119x_wdt_driver = {
|
||||
.probe = rtd119x_wdt_probe,
|
||||
.remove = rtd119x_wdt_remove,
|
||||
.driver = {
|
||||
.name = "rtd1295-watchdog",
|
||||
.of_match_table = rtd119x_wdt_dt_ids,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(rtd119x_wdt_driver);
|
@ -16,6 +16,11 @@
|
||||
* See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
|
||||
* AMD Publication 45482 "AMD SB800-Series Southbridges Register
|
||||
* Reference Guide"
|
||||
* AMD Publication 48751 "BIOS and Kernel Developer’s Guide (BKDG)
|
||||
* for AMD Family 16h Models 00h-0Fh Processors"
|
||||
* AMD Publication 51192 "AMD Bolton FCH Register Reference Guide"
|
||||
* AMD Publication 52740 "BIOS and Kernel Developer’s Guide (BKDG)
|
||||
* for AMD Family 16h Models 30h-3Fh Processors"
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -24,38 +29,36 @@
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#include "sp5100_tco.h"
|
||||
|
||||
/* Module and version information */
|
||||
#define TCO_VERSION "0.05"
|
||||
#define TCO_MODULE_NAME "SP5100 TCO timer"
|
||||
#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
|
||||
#define TCO_DRIVER_NAME "sp5100-tco"
|
||||
|
||||
/* internal variables */
|
||||
static u32 tcobase_phys;
|
||||
static u32 tco_wdt_fired;
|
||||
static void __iomem *tcobase;
|
||||
static unsigned int pm_iobase;
|
||||
static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
|
||||
static unsigned long timer_alive;
|
||||
static char tco_expect_close;
|
||||
static struct pci_dev *sp5100_tco_pci;
|
||||
|
||||
enum tco_reg_layout {
|
||||
sp5100, sb800, efch
|
||||
};
|
||||
|
||||
struct sp5100_tco {
|
||||
struct watchdog_device wdd;
|
||||
void __iomem *tcobase;
|
||||
enum tco_reg_layout tco_reg_layout;
|
||||
};
|
||||
|
||||
/* the watchdog platform device */
|
||||
static struct platform_device *sp5100_tco_platform_device;
|
||||
/* the associated PCI device */
|
||||
static struct pci_dev *sp5100_tco_pci;
|
||||
|
||||
/* module parameters */
|
||||
|
||||
@ -74,83 +77,105 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."
|
||||
* Some TCO specific functions
|
||||
*/
|
||||
|
||||
static bool tco_has_sp5100_reg_layout(struct pci_dev *dev)
|
||||
static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev)
|
||||
{
|
||||
return dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
|
||||
dev->revision < 0x40;
|
||||
if (dev->vendor == PCI_VENDOR_ID_ATI &&
|
||||
dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
|
||||
dev->revision < 0x40) {
|
||||
return sp5100;
|
||||
} else if (dev->vendor == PCI_VENDOR_ID_AMD &&
|
||||
((dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS &&
|
||||
dev->revision >= 0x41) ||
|
||||
(dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
|
||||
dev->revision >= 0x49))) {
|
||||
return efch;
|
||||
}
|
||||
return sb800;
|
||||
}
|
||||
|
||||
static void tco_timer_start(void)
|
||||
static int tco_timer_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tco_lock, flags);
|
||||
val = readl(SP5100_WDT_CONTROL(tcobase));
|
||||
val = readl(SP5100_WDT_CONTROL(tco->tcobase));
|
||||
val |= SP5100_WDT_START_STOP_BIT;
|
||||
writel(val, SP5100_WDT_CONTROL(tcobase));
|
||||
spin_unlock_irqrestore(&tco_lock, flags);
|
||||
}
|
||||
writel(val, SP5100_WDT_CONTROL(tco->tcobase));
|
||||
|
||||
static void tco_timer_stop(void)
|
||||
{
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tco_lock, flags);
|
||||
val = readl(SP5100_WDT_CONTROL(tcobase));
|
||||
val &= ~SP5100_WDT_START_STOP_BIT;
|
||||
writel(val, SP5100_WDT_CONTROL(tcobase));
|
||||
spin_unlock_irqrestore(&tco_lock, flags);
|
||||
}
|
||||
|
||||
static void tco_timer_keepalive(void)
|
||||
{
|
||||
u32 val;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&tco_lock, flags);
|
||||
val = readl(SP5100_WDT_CONTROL(tcobase));
|
||||
val |= SP5100_WDT_TRIGGER_BIT;
|
||||
writel(val, SP5100_WDT_CONTROL(tcobase));
|
||||
spin_unlock_irqrestore(&tco_lock, flags);
|
||||
}
|
||||
|
||||
static int tco_timer_set_heartbeat(int t)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (t < 0 || t > 0xffff)
|
||||
return -EINVAL;
|
||||
|
||||
/* Write new heartbeat to watchdog */
|
||||
spin_lock_irqsave(&tco_lock, flags);
|
||||
writel(t, SP5100_WDT_COUNT(tcobase));
|
||||
spin_unlock_irqrestore(&tco_lock, flags);
|
||||
|
||||
heartbeat = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tco_timer_enable(void)
|
||||
static int tco_timer_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
int val;
|
||||
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
|
||||
u32 val;
|
||||
|
||||
if (!tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
|
||||
val = readl(SP5100_WDT_CONTROL(tco->tcobase));
|
||||
val &= ~SP5100_WDT_START_STOP_BIT;
|
||||
writel(val, SP5100_WDT_CONTROL(tco->tcobase));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_ping(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
|
||||
u32 val;
|
||||
|
||||
val = readl(SP5100_WDT_CONTROL(tco->tcobase));
|
||||
val |= SP5100_WDT_TRIGGER_BIT;
|
||||
writel(val, SP5100_WDT_CONTROL(tco->tcobase));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_set_timeout(struct watchdog_device *wdd,
|
||||
unsigned int t)
|
||||
{
|
||||
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
|
||||
|
||||
/* Write new heartbeat to watchdog */
|
||||
writel(t, SP5100_WDT_COUNT(tco->tcobase));
|
||||
|
||||
wdd->timeout = t;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u8 sp5100_tco_read_pm_reg8(u8 index)
|
||||
{
|
||||
outb(index, SP5100_IO_PM_INDEX_REG);
|
||||
return inb(SP5100_IO_PM_DATA_REG);
|
||||
}
|
||||
|
||||
static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set)
|
||||
{
|
||||
u8 val;
|
||||
|
||||
outb(index, SP5100_IO_PM_INDEX_REG);
|
||||
val = inb(SP5100_IO_PM_DATA_REG);
|
||||
val &= reset;
|
||||
val |= set;
|
||||
outb(val, SP5100_IO_PM_DATA_REG);
|
||||
}
|
||||
|
||||
static void tco_timer_enable(struct sp5100_tco *tco)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
switch (tco->tco_reg_layout) {
|
||||
case sb800:
|
||||
/* For SB800 or later */
|
||||
/* Set the Watchdog timer resolution to 1 sec */
|
||||
outb(SB800_PM_WATCHDOG_CONFIG, SB800_IO_PM_INDEX_REG);
|
||||
val = inb(SB800_IO_PM_DATA_REG);
|
||||
val |= SB800_PM_WATCHDOG_SECOND_RES;
|
||||
outb(val, SB800_IO_PM_DATA_REG);
|
||||
sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,
|
||||
0xff, SB800_PM_WATCHDOG_SECOND_RES);
|
||||
|
||||
/* Enable watchdog decode bit and watchdog timer */
|
||||
outb(SB800_PM_WATCHDOG_CONTROL, SB800_IO_PM_INDEX_REG);
|
||||
val = inb(SB800_IO_PM_DATA_REG);
|
||||
val |= SB800_PCI_WATCHDOG_DECODE_EN;
|
||||
val &= ~SB800_PM_WATCHDOG_DISABLE;
|
||||
outb(val, SB800_IO_PM_DATA_REG);
|
||||
} else {
|
||||
sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,
|
||||
~SB800_PM_WATCHDOG_DISABLE,
|
||||
SB800_PCI_WATCHDOG_DECODE_EN);
|
||||
break;
|
||||
case sp5100:
|
||||
/* For SP5100 or SB7x0 */
|
||||
/* Enable watchdog decode bit */
|
||||
pci_read_config_dword(sp5100_tco_pci,
|
||||
@ -164,142 +189,242 @@ static void tco_timer_enable(void)
|
||||
val);
|
||||
|
||||
/* Enable Watchdog timer and set the resolution to 1 sec */
|
||||
outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG);
|
||||
val = inb(SP5100_IO_PM_DATA_REG);
|
||||
val |= SP5100_PM_WATCHDOG_SECOND_RES;
|
||||
val &= ~SP5100_PM_WATCHDOG_DISABLE;
|
||||
outb(val, SP5100_IO_PM_DATA_REG);
|
||||
sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,
|
||||
~SP5100_PM_WATCHDOG_DISABLE,
|
||||
SP5100_PM_WATCHDOG_SECOND_RES);
|
||||
break;
|
||||
case efch:
|
||||
/* Set the Watchdog timer resolution to 1 sec and enable */
|
||||
sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN3,
|
||||
~EFCH_PM_WATCHDOG_DISABLE,
|
||||
EFCH_PM_DECODEEN_SECOND_RES);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* /dev/watchdog handling
|
||||
*/
|
||||
|
||||
static int sp5100_tco_open(struct inode *inode, struct file *file)
|
||||
static u32 sp5100_tco_read_pm_reg32(u8 index)
|
||||
{
|
||||
/* /dev/watchdog can only be opened once */
|
||||
if (test_and_set_bit(0, &timer_alive))
|
||||
u32 val = 0;
|
||||
int i;
|
||||
|
||||
for (i = 3; i >= 0; i--)
|
||||
val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int sp5100_tco_setupdevice(struct device *dev,
|
||||
struct watchdog_device *wdd)
|
||||
{
|
||||
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
|
||||
const char *dev_name;
|
||||
u32 mmio_addr = 0, val;
|
||||
int ret;
|
||||
|
||||
/* Request the IO ports used by this driver */
|
||||
if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,
|
||||
SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) {
|
||||
dev_err(dev, "I/O address 0x%04x already in use\n",
|
||||
SP5100_IO_PM_INDEX_REG);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
/* Reload and activate timer */
|
||||
tco_timer_start();
|
||||
tco_timer_keepalive();
|
||||
return nonseekable_open(inode, file);
|
||||
/*
|
||||
* Determine type of southbridge chipset.
|
||||
*/
|
||||
switch (tco->tco_reg_layout) {
|
||||
case sp5100:
|
||||
dev_name = SP5100_DEVNAME;
|
||||
mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) &
|
||||
0xfffffff8;
|
||||
break;
|
||||
case sb800:
|
||||
dev_name = SB800_DEVNAME;
|
||||
mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) &
|
||||
0xfffffff8;
|
||||
break;
|
||||
case efch:
|
||||
dev_name = SB800_DEVNAME;
|
||||
val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);
|
||||
if (val & EFCH_PM_DECODEEN_WDT_TMREN)
|
||||
mmio_addr = EFCH_PM_WDT_ADDR;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Check MMIO address conflict */
|
||||
if (!mmio_addr ||
|
||||
!devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE,
|
||||
dev_name)) {
|
||||
if (mmio_addr)
|
||||
dev_dbg(dev, "MMIO address 0x%08x already in use\n",
|
||||
mmio_addr);
|
||||
switch (tco->tco_reg_layout) {
|
||||
case sp5100:
|
||||
/*
|
||||
* Secondly, Find the watchdog timer MMIO address
|
||||
* from SBResource_MMIO register.
|
||||
*/
|
||||
/* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
|
||||
pci_read_config_dword(sp5100_tco_pci,
|
||||
SP5100_SB_RESOURCE_MMIO_BASE,
|
||||
&mmio_addr);
|
||||
if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN |
|
||||
SB800_ACPI_MMIO_SEL)) !=
|
||||
SB800_ACPI_MMIO_DECODE_EN) {
|
||||
ret = -ENODEV;
|
||||
goto unreg_region;
|
||||
}
|
||||
mmio_addr &= ~0xFFF;
|
||||
mmio_addr += SB800_PM_WDT_MMIO_OFFSET;
|
||||
break;
|
||||
case sb800:
|
||||
/* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
|
||||
mmio_addr =
|
||||
sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN);
|
||||
if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN |
|
||||
SB800_ACPI_MMIO_SEL)) !=
|
||||
SB800_ACPI_MMIO_DECODE_EN) {
|
||||
ret = -ENODEV;
|
||||
goto unreg_region;
|
||||
}
|
||||
mmio_addr &= ~0xFFF;
|
||||
mmio_addr += SB800_PM_WDT_MMIO_OFFSET;
|
||||
break;
|
||||
case efch:
|
||||
val = sp5100_tco_read_pm_reg8(EFCH_PM_ISACONTROL);
|
||||
if (!(val & EFCH_PM_ISACONTROL_MMIOEN)) {
|
||||
ret = -ENODEV;
|
||||
goto unreg_region;
|
||||
}
|
||||
mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +
|
||||
EFCH_PM_ACPI_MMIO_WDT_OFFSET;
|
||||
break;
|
||||
}
|
||||
dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n",
|
||||
mmio_addr);
|
||||
if (!devm_request_mem_region(dev, mmio_addr,
|
||||
SP5100_WDT_MEM_MAP_SIZE,
|
||||
dev_name)) {
|
||||
dev_dbg(dev, "MMIO address 0x%08x already in use\n",
|
||||
mmio_addr);
|
||||
ret = -EBUSY;
|
||||
goto unreg_region;
|
||||
}
|
||||
}
|
||||
|
||||
tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
|
||||
if (!tco->tcobase) {
|
||||
dev_err(dev, "failed to get tcobase address\n");
|
||||
ret = -ENOMEM;
|
||||
goto unreg_region;
|
||||
}
|
||||
|
||||
dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr);
|
||||
|
||||
/* Setup the watchdog timer */
|
||||
tco_timer_enable(tco);
|
||||
|
||||
val = readl(SP5100_WDT_CONTROL(tco->tcobase));
|
||||
if (val & SP5100_WDT_DISABLED) {
|
||||
dev_err(dev, "Watchdog hardware is disabled\n");
|
||||
ret = -ENODEV;
|
||||
goto unreg_region;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save WatchDogFired status, because WatchDogFired flag is
|
||||
* cleared here.
|
||||
*/
|
||||
if (val & SP5100_WDT_FIRED)
|
||||
wdd->bootstatus = WDIOF_CARDRESET;
|
||||
/* Set watchdog action to reset the system */
|
||||
val &= ~SP5100_WDT_ACTION_RESET;
|
||||
writel(val, SP5100_WDT_CONTROL(tco->tcobase));
|
||||
|
||||
/* Set a reasonable heartbeat before we stop the timer */
|
||||
tco_timer_set_timeout(wdd, wdd->timeout);
|
||||
|
||||
/*
|
||||
* Stop the TCO before we change anything so we don't race with
|
||||
* a zeroed timer.
|
||||
*/
|
||||
tco_timer_stop(wdd);
|
||||
|
||||
release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
|
||||
|
||||
return 0;
|
||||
|
||||
unreg_region:
|
||||
release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sp5100_tco_release(struct inode *inode, struct file *file)
|
||||
static struct watchdog_info sp5100_tco_wdt_info = {
|
||||
.identity = "SP5100 TCO timer",
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||
};
|
||||
|
||||
static const struct watchdog_ops sp5100_tco_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = tco_timer_start,
|
||||
.stop = tco_timer_stop,
|
||||
.ping = tco_timer_ping,
|
||||
.set_timeout = tco_timer_set_timeout,
|
||||
};
|
||||
|
||||
static int sp5100_tco_probe(struct platform_device *pdev)
|
||||
{
|
||||
/* Shut off the timer. */
|
||||
if (tco_expect_close == 42) {
|
||||
tco_timer_stop();
|
||||
} else {
|
||||
pr_crit("Unexpected close, not stopping watchdog!\n");
|
||||
tco_timer_keepalive();
|
||||
struct device *dev = &pdev->dev;
|
||||
struct watchdog_device *wdd;
|
||||
struct sp5100_tco *tco;
|
||||
int ret;
|
||||
|
||||
tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);
|
||||
if (!tco)
|
||||
return -ENOMEM;
|
||||
|
||||
tco->tco_reg_layout = tco_reg_layout(sp5100_tco_pci);
|
||||
|
||||
wdd = &tco->wdd;
|
||||
wdd->parent = dev;
|
||||
wdd->info = &sp5100_tco_wdt_info;
|
||||
wdd->ops = &sp5100_tco_wdt_ops;
|
||||
wdd->timeout = WATCHDOG_HEARTBEAT;
|
||||
wdd->min_timeout = 1;
|
||||
wdd->max_timeout = 0xffff;
|
||||
|
||||
if (watchdog_init_timeout(wdd, heartbeat, NULL))
|
||||
dev_info(dev, "timeout value invalid, using %d\n",
|
||||
wdd->timeout);
|
||||
watchdog_set_nowayout(wdd, nowayout);
|
||||
watchdog_stop_on_reboot(wdd);
|
||||
watchdog_stop_on_unregister(wdd);
|
||||
watchdog_set_drvdata(wdd, tco);
|
||||
|
||||
ret = sp5100_tco_setupdevice(dev, wdd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_watchdog_register_device(dev, wdd);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot register watchdog device (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
clear_bit(0, &timer_alive);
|
||||
tco_expect_close = 0;
|
||||
|
||||
/* Show module parameters */
|
||||
dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
|
||||
wdd->timeout, nowayout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t sp5100_tco_write(struct file *file, const char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
/* See if we got the magic character 'V' and reload the timer */
|
||||
if (len) {
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
/* note: just in case someone wrote the magic character
|
||||
* five months ago... */
|
||||
tco_expect_close = 0;
|
||||
|
||||
/* scan to see whether or not we got the magic character
|
||||
*/
|
||||
for (i = 0; i != len; i++) {
|
||||
char c;
|
||||
if (get_user(c, data + i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
tco_expect_close = 42;
|
||||
}
|
||||
}
|
||||
|
||||
/* someone wrote to us, we should reload the timer */
|
||||
tco_timer_keepalive();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static long sp5100_tco_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int new_options, retval = -EINVAL;
|
||||
int new_heartbeat;
|
||||
void __user *argp = (void __user *)arg;
|
||||
int __user *p = argp;
|
||||
static const struct watchdog_info ident = {
|
||||
.options = WDIOF_SETTIMEOUT |
|
||||
WDIOF_KEEPALIVEPING |
|
||||
WDIOF_MAGICCLOSE,
|
||||
.firmware_version = 0,
|
||||
.identity = TCO_MODULE_NAME,
|
||||
};
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
return copy_to_user(argp, &ident,
|
||||
sizeof(ident)) ? -EFAULT : 0;
|
||||
case WDIOC_GETSTATUS:
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
return put_user(0, p);
|
||||
case WDIOC_SETOPTIONS:
|
||||
if (get_user(new_options, p))
|
||||
return -EFAULT;
|
||||
if (new_options & WDIOS_DISABLECARD) {
|
||||
tco_timer_stop();
|
||||
retval = 0;
|
||||
}
|
||||
if (new_options & WDIOS_ENABLECARD) {
|
||||
tco_timer_start();
|
||||
tco_timer_keepalive();
|
||||
retval = 0;
|
||||
}
|
||||
return retval;
|
||||
case WDIOC_KEEPALIVE:
|
||||
tco_timer_keepalive();
|
||||
return 0;
|
||||
case WDIOC_SETTIMEOUT:
|
||||
if (get_user(new_heartbeat, p))
|
||||
return -EFAULT;
|
||||
if (tco_timer_set_heartbeat(new_heartbeat))
|
||||
return -EINVAL;
|
||||
tco_timer_keepalive();
|
||||
/* Fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernel Interfaces
|
||||
*/
|
||||
|
||||
static const struct file_operations sp5100_tco_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.write = sp5100_tco_write,
|
||||
.unlocked_ioctl = sp5100_tco_ioctl,
|
||||
.open = sp5100_tco_open,
|
||||
.release = sp5100_tco_release,
|
||||
};
|
||||
|
||||
static struct miscdevice sp5100_tco_miscdev = {
|
||||
.minor = WATCHDOG_MINOR,
|
||||
.name = "watchdog",
|
||||
.fops = &sp5100_tco_fops,
|
||||
static struct platform_driver sp5100_tco_driver = {
|
||||
.probe = sp5100_tco_probe,
|
||||
.driver = {
|
||||
.name = TCO_DRIVER_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
@ -321,15 +446,10 @@ static const struct pci_device_id sp5100_tco_pci_tbl[] = {
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
|
||||
|
||||
/*
|
||||
* Init & exit routines
|
||||
*/
|
||||
static unsigned char sp5100_tco_setupdevice(void)
|
||||
static int __init sp5100_tco_init(void)
|
||||
{
|
||||
struct pci_dev *dev = NULL;
|
||||
const char *dev_name = NULL;
|
||||
u32 val;
|
||||
u32 index_reg, data_reg, base_addr;
|
||||
int err;
|
||||
|
||||
/* Match the PCI device */
|
||||
for_each_pci_dev(dev) {
|
||||
@ -340,233 +460,16 @@ static unsigned char sp5100_tco_setupdevice(void)
|
||||
}
|
||||
|
||||
if (!sp5100_tco_pci)
|
||||
return 0;
|
||||
|
||||
pr_info("PCI Vendor ID: 0x%x, Device ID: 0x%x, Revision ID: 0x%x\n",
|
||||
sp5100_tco_pci->vendor, sp5100_tco_pci->device,
|
||||
sp5100_tco_pci->revision);
|
||||
|
||||
/*
|
||||
* Determine type of southbridge chipset.
|
||||
*/
|
||||
if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
|
||||
dev_name = SP5100_DEVNAME;
|
||||
index_reg = SP5100_IO_PM_INDEX_REG;
|
||||
data_reg = SP5100_IO_PM_DATA_REG;
|
||||
base_addr = SP5100_PM_WATCHDOG_BASE;
|
||||
} else {
|
||||
dev_name = SB800_DEVNAME;
|
||||
index_reg = SB800_IO_PM_INDEX_REG;
|
||||
data_reg = SB800_IO_PM_DATA_REG;
|
||||
base_addr = SB800_PM_WATCHDOG_BASE;
|
||||
}
|
||||
|
||||
/* Request the IO ports used by this driver */
|
||||
pm_iobase = SP5100_IO_PM_INDEX_REG;
|
||||
if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, dev_name)) {
|
||||
pr_err("I/O address 0x%04x already in use\n", pm_iobase);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* First, Find the watchdog timer MMIO address from indirect I/O.
|
||||
*/
|
||||
outb(base_addr+3, index_reg);
|
||||
val = inb(data_reg);
|
||||
outb(base_addr+2, index_reg);
|
||||
val = val << 8 | inb(data_reg);
|
||||
outb(base_addr+1, index_reg);
|
||||
val = val << 8 | inb(data_reg);
|
||||
outb(base_addr+0, index_reg);
|
||||
/* Low three bits of BASE are reserved */
|
||||
val = val << 8 | (inb(data_reg) & 0xf8);
|
||||
|
||||
pr_debug("Got 0x%04x from indirect I/O\n", val);
|
||||
|
||||
/* Check MMIO address conflict */
|
||||
if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
|
||||
dev_name))
|
||||
goto setup_wdt;
|
||||
else
|
||||
pr_debug("MMIO address 0x%04x already in use\n", val);
|
||||
|
||||
/*
|
||||
* Secondly, Find the watchdog timer MMIO address
|
||||
* from SBResource_MMIO register.
|
||||
*/
|
||||
if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) {
|
||||
/* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
|
||||
pci_read_config_dword(sp5100_tco_pci,
|
||||
SP5100_SB_RESOURCE_MMIO_BASE, &val);
|
||||
} else {
|
||||
/* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
|
||||
outb(SB800_PM_ACPI_MMIO_EN+3, SB800_IO_PM_INDEX_REG);
|
||||
val = inb(SB800_IO_PM_DATA_REG);
|
||||
outb(SB800_PM_ACPI_MMIO_EN+2, SB800_IO_PM_INDEX_REG);
|
||||
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
|
||||
outb(SB800_PM_ACPI_MMIO_EN+1, SB800_IO_PM_INDEX_REG);
|
||||
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
|
||||
outb(SB800_PM_ACPI_MMIO_EN+0, SB800_IO_PM_INDEX_REG);
|
||||
val = val << 8 | inb(SB800_IO_PM_DATA_REG);
|
||||
}
|
||||
|
||||
/* The SBResource_MMIO is enabled and mapped memory space? */
|
||||
if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) ==
|
||||
SB800_ACPI_MMIO_DECODE_EN) {
|
||||
/* Clear unnecessary the low twelve bits */
|
||||
val &= ~0xFFF;
|
||||
/* Add the Watchdog Timer offset to base address. */
|
||||
val += SB800_PM_WDT_MMIO_OFFSET;
|
||||
/* Check MMIO address conflict */
|
||||
if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE,
|
||||
dev_name)) {
|
||||
pr_debug("Got 0x%04x from SBResource_MMIO register\n",
|
||||
val);
|
||||
goto setup_wdt;
|
||||
} else
|
||||
pr_debug("MMIO address 0x%04x already in use\n", val);
|
||||
} else
|
||||
pr_debug("SBResource_MMIO is disabled(0x%04x)\n", val);
|
||||
|
||||
pr_notice("failed to find MMIO address, giving up.\n");
|
||||
goto unreg_region;
|
||||
|
||||
setup_wdt:
|
||||
tcobase_phys = val;
|
||||
|
||||
tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE);
|
||||
if (!tcobase) {
|
||||
pr_err("failed to get tcobase address\n");
|
||||
goto unreg_mem_region;
|
||||
}
|
||||
|
||||
pr_info("Using 0x%04x for watchdog MMIO address\n", val);
|
||||
|
||||
/* Setup the watchdog timer */
|
||||
tco_timer_enable();
|
||||
|
||||
/* Check that the watchdog action is set to reset the system */
|
||||
val = readl(SP5100_WDT_CONTROL(tcobase));
|
||||
/*
|
||||
* Save WatchDogFired status, because WatchDogFired flag is
|
||||
* cleared here.
|
||||
*/
|
||||
tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED;
|
||||
val &= ~SP5100_PM_WATCHDOG_ACTION_RESET;
|
||||
writel(val, SP5100_WDT_CONTROL(tcobase));
|
||||
|
||||
/* Set a reasonable heartbeat before we stop the timer */
|
||||
tco_timer_set_heartbeat(heartbeat);
|
||||
|
||||
/*
|
||||
* Stop the TCO before we change anything so we don't race with
|
||||
* a zeroed timer.
|
||||
*/
|
||||
tco_timer_stop();
|
||||
|
||||
/* Done */
|
||||
return 1;
|
||||
|
||||
unreg_mem_region:
|
||||
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
|
||||
unreg_region:
|
||||
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
|
||||
exit:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sp5100_tco_init(struct platform_device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Check whether or not the hardware watchdog is there. If found, then
|
||||
* set it up.
|
||||
*/
|
||||
if (!sp5100_tco_setupdevice())
|
||||
return -ENODEV;
|
||||
|
||||
/* Check to see if last reboot was due to watchdog timeout */
|
||||
pr_info("Last reboot was %striggered by watchdog.\n",
|
||||
tco_wdt_fired ? "" : "not ");
|
||||
|
||||
/*
|
||||
* Check that the heartbeat value is within it's range.
|
||||
* If not, reset to the default.
|
||||
*/
|
||||
if (tco_timer_set_heartbeat(heartbeat)) {
|
||||
heartbeat = WATCHDOG_HEARTBEAT;
|
||||
tco_timer_set_heartbeat(heartbeat);
|
||||
}
|
||||
|
||||
ret = misc_register(&sp5100_tco_miscdev);
|
||||
if (ret != 0) {
|
||||
pr_err("cannot register miscdev on minor=%d (err=%d)\n",
|
||||
WATCHDOG_MINOR, ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
clear_bit(0, &timer_alive);
|
||||
|
||||
/* Show module parameters */
|
||||
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
|
||||
tcobase, heartbeat, nowayout);
|
||||
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
iounmap(tcobase);
|
||||
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
|
||||
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sp5100_tco_cleanup(void)
|
||||
{
|
||||
/* Stop the timer before we leave */
|
||||
if (!nowayout)
|
||||
tco_timer_stop();
|
||||
|
||||
/* Deregister */
|
||||
misc_deregister(&sp5100_tco_miscdev);
|
||||
iounmap(tcobase);
|
||||
release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE);
|
||||
release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE);
|
||||
}
|
||||
|
||||
static int sp5100_tco_remove(struct platform_device *dev)
|
||||
{
|
||||
if (tcobase)
|
||||
sp5100_tco_cleanup();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sp5100_tco_shutdown(struct platform_device *dev)
|
||||
{
|
||||
tco_timer_stop();
|
||||
}
|
||||
|
||||
static struct platform_driver sp5100_tco_driver = {
|
||||
.probe = sp5100_tco_init,
|
||||
.remove = sp5100_tco_remove,
|
||||
.shutdown = sp5100_tco_shutdown,
|
||||
.driver = {
|
||||
.name = TCO_MODULE_NAME,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init sp5100_tco_init_module(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
|
||||
pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");
|
||||
|
||||
err = platform_driver_register(&sp5100_tco_driver);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
sp5100_tco_platform_device = platform_device_register_simple(
|
||||
TCO_MODULE_NAME, -1, NULL, 0);
|
||||
sp5100_tco_platform_device =
|
||||
platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);
|
||||
if (IS_ERR(sp5100_tco_platform_device)) {
|
||||
err = PTR_ERR(sp5100_tco_platform_device);
|
||||
goto unreg_platform_driver;
|
||||
@ -579,15 +482,14 @@ unreg_platform_driver:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit sp5100_tco_cleanup_module(void)
|
||||
static void __exit sp5100_tco_exit(void)
|
||||
{
|
||||
platform_device_unregister(sp5100_tco_platform_device);
|
||||
platform_driver_unregister(&sp5100_tco_driver);
|
||||
pr_info("SP5100/SB800 TCO Watchdog Module Unloaded\n");
|
||||
}
|
||||
|
||||
module_init(sp5100_tco_init_module);
|
||||
module_exit(sp5100_tco_cleanup_module);
|
||||
module_init(sp5100_tco_init);
|
||||
module_exit(sp5100_tco_exit);
|
||||
|
||||
MODULE_AUTHOR("Priyanka Gupta");
|
||||
MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
|
||||
|
@ -7,6 +7,8 @@
|
||||
* TCO timer driver for sp5100 chipsets
|
||||
*/
|
||||
|
||||
#include <linux/bitops.h>
|
||||
|
||||
/*
|
||||
* Some address definitions for the Watchdog
|
||||
*/
|
||||
@ -14,8 +16,11 @@
|
||||
#define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */
|
||||
#define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */
|
||||
|
||||
#define SP5100_WDT_START_STOP_BIT (1 << 0)
|
||||
#define SP5100_WDT_TRIGGER_BIT (1 << 7)
|
||||
#define SP5100_WDT_START_STOP_BIT BIT(0)
|
||||
#define SP5100_WDT_FIRED BIT(1)
|
||||
#define SP5100_WDT_ACTION_RESET BIT(2)
|
||||
#define SP5100_WDT_DISABLED BIT(3)
|
||||
#define SP5100_WDT_TRIGGER_BIT BIT(7)
|
||||
|
||||
#define SP5100_PM_IOPORTS_SIZE 0x02
|
||||
|
||||
@ -24,43 +29,57 @@
|
||||
* read them from a register.
|
||||
*/
|
||||
|
||||
/* For SP5100/SB7x0 chipset */
|
||||
/* For SP5100/SB7x0/SB8x0 chipset */
|
||||
#define SP5100_IO_PM_INDEX_REG 0xCD6
|
||||
#define SP5100_IO_PM_DATA_REG 0xCD7
|
||||
|
||||
/* For SP5100/SB7x0 chipset */
|
||||
#define SP5100_SB_RESOURCE_MMIO_BASE 0x9C
|
||||
|
||||
#define SP5100_PM_WATCHDOG_CONTROL 0x69
|
||||
#define SP5100_PM_WATCHDOG_BASE 0x6C
|
||||
|
||||
#define SP5100_PM_WATCHDOG_FIRED (1 << 1)
|
||||
#define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2)
|
||||
|
||||
#define SP5100_PCI_WATCHDOG_MISC_REG 0x41
|
||||
#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3)
|
||||
#define SP5100_PCI_WATCHDOG_DECODE_EN BIT(3)
|
||||
|
||||
#define SP5100_PM_WATCHDOG_DISABLE (1 << 0)
|
||||
#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1)
|
||||
#define SP5100_PM_WATCHDOG_DISABLE ((u8)BIT(0))
|
||||
#define SP5100_PM_WATCHDOG_SECOND_RES GENMASK(2, 1)
|
||||
|
||||
#define SP5100_DEVNAME "SP5100 TCO"
|
||||
|
||||
|
||||
/* For SB8x0(or later) chipset */
|
||||
#define SB800_IO_PM_INDEX_REG 0xCD6
|
||||
#define SB800_IO_PM_DATA_REG 0xCD7
|
||||
|
||||
#define SB800_PM_ACPI_MMIO_EN 0x24
|
||||
#define SB800_PM_WATCHDOG_CONTROL 0x48
|
||||
#define SB800_PM_WATCHDOG_BASE 0x48
|
||||
#define SB800_PM_WATCHDOG_CONFIG 0x4C
|
||||
|
||||
#define SB800_PCI_WATCHDOG_DECODE_EN (1 << 0)
|
||||
#define SB800_PM_WATCHDOG_DISABLE (1 << 2)
|
||||
#define SB800_PM_WATCHDOG_SECOND_RES (3 << 0)
|
||||
#define SB800_ACPI_MMIO_DECODE_EN (1 << 0)
|
||||
#define SB800_ACPI_MMIO_SEL (1 << 1)
|
||||
|
||||
#define SB800_PCI_WATCHDOG_DECODE_EN BIT(0)
|
||||
#define SB800_PM_WATCHDOG_DISABLE ((u8)BIT(1))
|
||||
#define SB800_PM_WATCHDOG_SECOND_RES GENMASK(1, 0)
|
||||
#define SB800_ACPI_MMIO_DECODE_EN BIT(0)
|
||||
#define SB800_ACPI_MMIO_SEL BIT(1)
|
||||
|
||||
#define SB800_PM_WDT_MMIO_OFFSET 0xB00
|
||||
|
||||
#define SB800_DEVNAME "SB800 TCO"
|
||||
|
||||
/* For recent chips with embedded FCH (rev 40+) */
|
||||
|
||||
#define EFCH_PM_DECODEEN 0x00
|
||||
|
||||
#define EFCH_PM_DECODEEN_WDT_TMREN BIT(7)
|
||||
|
||||
|
||||
#define EFCH_PM_DECODEEN3 0x00
|
||||
#define EFCH_PM_DECODEEN_SECOND_RES GENMASK(1, 0)
|
||||
#define EFCH_PM_WATCHDOG_DISABLE ((u8)GENMASK(3, 2))
|
||||
|
||||
/* WDT MMIO if enabled with PM00_DECODEEN_WDT_TMREN */
|
||||
#define EFCH_PM_WDT_ADDR 0xfeb00000
|
||||
|
||||
#define EFCH_PM_ISACONTROL 0x04
|
||||
|
||||
#define EFCH_PM_ISACONTROL_MMIOEN BIT(1)
|
||||
|
||||
#define EFCH_PM_ACPI_MMIO_ADDR 0xfed80000
|
||||
#define EFCH_PM_ACPI_MMIO_WDT_OFFSET 0x00000b00
|
||||
|
399
drivers/watchdog/sprd_wdt.c
Normal file
399
drivers/watchdog/sprd_wdt.c
Normal file
@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Spreadtrum watchdog driver
|
||||
* Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com
|
||||
*
|
||||
* 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/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define SPRD_WDT_LOAD_LOW 0x0
|
||||
#define SPRD_WDT_LOAD_HIGH 0x4
|
||||
#define SPRD_WDT_CTRL 0x8
|
||||
#define SPRD_WDT_INT_CLR 0xc
|
||||
#define SPRD_WDT_INT_RAW 0x10
|
||||
#define SPRD_WDT_INT_MSK 0x14
|
||||
#define SPRD_WDT_CNT_LOW 0x18
|
||||
#define SPRD_WDT_CNT_HIGH 0x1c
|
||||
#define SPRD_WDT_LOCK 0x20
|
||||
#define SPRD_WDT_IRQ_LOAD_LOW 0x2c
|
||||
#define SPRD_WDT_IRQ_LOAD_HIGH 0x30
|
||||
|
||||
/* WDT_CTRL */
|
||||
#define SPRD_WDT_INT_EN_BIT BIT(0)
|
||||
#define SPRD_WDT_CNT_EN_BIT BIT(1)
|
||||
#define SPRD_WDT_NEW_VER_EN BIT(2)
|
||||
#define SPRD_WDT_RST_EN_BIT BIT(3)
|
||||
|
||||
/* WDT_INT_CLR */
|
||||
#define SPRD_WDT_INT_CLEAR_BIT BIT(0)
|
||||
#define SPRD_WDT_RST_CLEAR_BIT BIT(3)
|
||||
|
||||
/* WDT_INT_RAW */
|
||||
#define SPRD_WDT_INT_RAW_BIT BIT(0)
|
||||
#define SPRD_WDT_RST_RAW_BIT BIT(3)
|
||||
#define SPRD_WDT_LD_BUSY_BIT BIT(4)
|
||||
|
||||
/* 1s equal to 32768 counter steps */
|
||||
#define SPRD_WDT_CNT_STEP 32768
|
||||
|
||||
#define SPRD_WDT_UNLOCK_KEY 0xe551
|
||||
#define SPRD_WDT_MIN_TIMEOUT 3
|
||||
#define SPRD_WDT_MAX_TIMEOUT 60
|
||||
|
||||
#define SPRD_WDT_CNT_HIGH_SHIFT 16
|
||||
#define SPRD_WDT_LOW_VALUE_MASK GENMASK(15, 0)
|
||||
#define SPRD_WDT_LOAD_TIMEOUT 1000
|
||||
|
||||
struct sprd_wdt {
|
||||
void __iomem *base;
|
||||
struct watchdog_device wdd;
|
||||
struct clk *enable;
|
||||
struct clk *rtc_enable;
|
||||
int irq;
|
||||
};
|
||||
|
||||
static inline struct sprd_wdt *to_sprd_wdt(struct watchdog_device *wdd)
|
||||
{
|
||||
return container_of(wdd, struct sprd_wdt, wdd);
|
||||
}
|
||||
|
||||
static inline void sprd_wdt_lock(void __iomem *addr)
|
||||
{
|
||||
writel_relaxed(0x0, addr + SPRD_WDT_LOCK);
|
||||
}
|
||||
|
||||
static inline void sprd_wdt_unlock(void __iomem *addr)
|
||||
{
|
||||
writel_relaxed(SPRD_WDT_UNLOCK_KEY, addr + SPRD_WDT_LOCK);
|
||||
}
|
||||
|
||||
static irqreturn_t sprd_wdt_isr(int irq, void *dev_id)
|
||||
{
|
||||
struct sprd_wdt *wdt = (struct sprd_wdt *)dev_id;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
writel_relaxed(SPRD_WDT_INT_CLEAR_BIT, wdt->base + SPRD_WDT_INT_CLR);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
watchdog_notify_pretimeout(&wdt->wdd);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static u32 sprd_wdt_get_cnt_value(struct sprd_wdt *wdt)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl_relaxed(wdt->base + SPRD_WDT_CNT_HIGH) <<
|
||||
SPRD_WDT_CNT_HIGH_SHIFT;
|
||||
val |= readl_relaxed(wdt->base + SPRD_WDT_CNT_LOW) &
|
||||
SPRD_WDT_LOW_VALUE_MASK;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int sprd_wdt_load_value(struct sprd_wdt *wdt, u32 timeout,
|
||||
u32 pretimeout)
|
||||
{
|
||||
u32 val, delay_cnt = 0;
|
||||
u32 tmr_step = timeout * SPRD_WDT_CNT_STEP;
|
||||
u32 prtmr_step = pretimeout * SPRD_WDT_CNT_STEP;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
writel_relaxed((tmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) &
|
||||
SPRD_WDT_LOW_VALUE_MASK, wdt->base + SPRD_WDT_LOAD_HIGH);
|
||||
writel_relaxed((tmr_step & SPRD_WDT_LOW_VALUE_MASK),
|
||||
wdt->base + SPRD_WDT_LOAD_LOW);
|
||||
writel_relaxed((prtmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) &
|
||||
SPRD_WDT_LOW_VALUE_MASK,
|
||||
wdt->base + SPRD_WDT_IRQ_LOAD_HIGH);
|
||||
writel_relaxed(prtmr_step & SPRD_WDT_LOW_VALUE_MASK,
|
||||
wdt->base + SPRD_WDT_IRQ_LOAD_LOW);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
|
||||
/*
|
||||
* Waiting the load value operation done,
|
||||
* it needs two or three RTC clock cycles.
|
||||
*/
|
||||
do {
|
||||
val = readl_relaxed(wdt->base + SPRD_WDT_INT_RAW);
|
||||
if (!(val & SPRD_WDT_LD_BUSY_BIT))
|
||||
break;
|
||||
|
||||
cpu_relax();
|
||||
} while (delay_cnt++ < SPRD_WDT_LOAD_TIMEOUT);
|
||||
|
||||
if (delay_cnt >= SPRD_WDT_LOAD_TIMEOUT)
|
||||
return -EBUSY;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sprd_wdt_enable(struct sprd_wdt *wdt)
|
||||
{
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(wdt->enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = clk_prepare_enable(wdt->rtc_enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
|
||||
val |= SPRD_WDT_NEW_VER_EN;
|
||||
writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sprd_wdt_disable(void *_data)
|
||||
{
|
||||
struct sprd_wdt *wdt = _data;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
writel_relaxed(0x0, wdt->base + SPRD_WDT_CTRL);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
|
||||
clk_disable_unprepare(wdt->rtc_enable);
|
||||
clk_disable_unprepare(wdt->enable);
|
||||
}
|
||||
|
||||
static int sprd_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sprd_wdt *wdt = to_sprd_wdt(wdd);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = sprd_wdt_load_value(wdt, wdd->timeout, wdd->pretimeout);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
|
||||
val |= SPRD_WDT_CNT_EN_BIT | SPRD_WDT_INT_EN_BIT | SPRD_WDT_RST_EN_BIT;
|
||||
writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
set_bit(WDOG_HW_RUNNING, &wdd->status);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sprd_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sprd_wdt *wdt = to_sprd_wdt(wdd);
|
||||
u32 val;
|
||||
|
||||
sprd_wdt_unlock(wdt->base);
|
||||
val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
|
||||
val &= ~(SPRD_WDT_CNT_EN_BIT | SPRD_WDT_RST_EN_BIT |
|
||||
SPRD_WDT_INT_EN_BIT);
|
||||
writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
|
||||
sprd_wdt_lock(wdt->base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sprd_wdt_set_timeout(struct watchdog_device *wdd,
|
||||
u32 timeout)
|
||||
{
|
||||
struct sprd_wdt *wdt = to_sprd_wdt(wdd);
|
||||
|
||||
if (timeout == wdd->timeout)
|
||||
return 0;
|
||||
|
||||
wdd->timeout = timeout;
|
||||
|
||||
return sprd_wdt_load_value(wdt, timeout, wdd->pretimeout);
|
||||
}
|
||||
|
||||
static int sprd_wdt_set_pretimeout(struct watchdog_device *wdd,
|
||||
u32 new_pretimeout)
|
||||
{
|
||||
struct sprd_wdt *wdt = to_sprd_wdt(wdd);
|
||||
|
||||
if (new_pretimeout < wdd->min_timeout)
|
||||
return -EINVAL;
|
||||
|
||||
wdd->pretimeout = new_pretimeout;
|
||||
|
||||
return sprd_wdt_load_value(wdt, wdd->timeout, new_pretimeout);
|
||||
}
|
||||
|
||||
static u32 sprd_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
struct sprd_wdt *wdt = to_sprd_wdt(wdd);
|
||||
u32 val;
|
||||
|
||||
val = sprd_wdt_get_cnt_value(wdt);
|
||||
val = val / SPRD_WDT_CNT_STEP;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static const struct watchdog_ops sprd_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = sprd_wdt_start,
|
||||
.stop = sprd_wdt_stop,
|
||||
.set_timeout = sprd_wdt_set_timeout,
|
||||
.set_pretimeout = sprd_wdt_set_pretimeout,
|
||||
.get_timeleft = sprd_wdt_get_timeleft,
|
||||
};
|
||||
|
||||
static const struct watchdog_info sprd_wdt_info = {
|
||||
.options = WDIOF_SETTIMEOUT |
|
||||
WDIOF_PRETIMEOUT |
|
||||
WDIOF_MAGICCLOSE |
|
||||
WDIOF_KEEPALIVEPING,
|
||||
.identity = "Spreadtrum Watchdog Timer",
|
||||
};
|
||||
|
||||
static int sprd_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *wdt_res;
|
||||
struct sprd_wdt *wdt;
|
||||
int ret;
|
||||
|
||||
wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
|
||||
if (!wdt)
|
||||
return -ENOMEM;
|
||||
|
||||
wdt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
wdt->base = devm_ioremap_resource(&pdev->dev, wdt_res);
|
||||
if (IS_ERR(wdt->base)) {
|
||||
dev_err(&pdev->dev, "failed to map memory resource\n");
|
||||
return PTR_ERR(wdt->base);
|
||||
}
|
||||
|
||||
wdt->enable = devm_clk_get(&pdev->dev, "enable");
|
||||
if (IS_ERR(wdt->enable)) {
|
||||
dev_err(&pdev->dev, "can't get the enable clock\n");
|
||||
return PTR_ERR(wdt->enable);
|
||||
}
|
||||
|
||||
wdt->rtc_enable = devm_clk_get(&pdev->dev, "rtc_enable");
|
||||
if (IS_ERR(wdt->rtc_enable)) {
|
||||
dev_err(&pdev->dev, "can't get the rtc enable clock\n");
|
||||
return PTR_ERR(wdt->rtc_enable);
|
||||
}
|
||||
|
||||
wdt->irq = platform_get_irq(pdev, 0);
|
||||
if (wdt->irq < 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ resource\n");
|
||||
return wdt->irq;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, wdt->irq, sprd_wdt_isr,
|
||||
IRQF_NO_SUSPEND, "sprd-wdt", (void *)wdt);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
wdt->wdd.info = &sprd_wdt_info;
|
||||
wdt->wdd.ops = &sprd_wdt_ops;
|
||||
wdt->wdd.parent = &pdev->dev;
|
||||
wdt->wdd.min_timeout = SPRD_WDT_MIN_TIMEOUT;
|
||||
wdt->wdd.max_timeout = SPRD_WDT_MAX_TIMEOUT;
|
||||
wdt->wdd.timeout = SPRD_WDT_MAX_TIMEOUT;
|
||||
|
||||
ret = sprd_wdt_enable(wdt);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable wdt\n");
|
||||
return ret;
|
||||
}
|
||||
ret = devm_add_action(&pdev->dev, sprd_wdt_disable, wdt);
|
||||
if (ret) {
|
||||
sprd_wdt_disable(wdt);
|
||||
dev_err(&pdev->dev, "Failed to add wdt disable action\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
|
||||
watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev);
|
||||
|
||||
ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd);
|
||||
if (ret) {
|
||||
sprd_wdt_disable(wdt);
|
||||
dev_err(&pdev->dev, "failed to register watchdog\n");
|
||||
return ret;
|
||||
}
|
||||
platform_set_drvdata(pdev, wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused sprd_wdt_pm_suspend(struct device *dev)
|
||||
{
|
||||
struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
struct sprd_wdt *wdt = dev_get_drvdata(dev);
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
sprd_wdt_stop(&wdt->wdd);
|
||||
sprd_wdt_disable(wdt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused sprd_wdt_pm_resume(struct device *dev)
|
||||
{
|
||||
struct watchdog_device *wdd = dev_get_drvdata(dev);
|
||||
struct sprd_wdt *wdt = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = sprd_wdt_enable(wdt);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (watchdog_active(wdd)) {
|
||||
ret = sprd_wdt_start(&wdt->wdd);
|
||||
if (ret) {
|
||||
sprd_wdt_disable(wdt);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops sprd_wdt_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(sprd_wdt_pm_suspend,
|
||||
sprd_wdt_pm_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id sprd_wdt_match_table[] = {
|
||||
{ .compatible = "sprd,sp9860-wdt", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sprd_wdt_match_table);
|
||||
|
||||
static struct platform_driver sprd_watchdog_driver = {
|
||||
.probe = sprd_wdt_probe,
|
||||
.driver = {
|
||||
.name = "sprd-wdt",
|
||||
.of_match_table = sprd_wdt_match_table,
|
||||
.pm = &sprd_wdt_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(sprd_watchdog_driver);
|
||||
|
||||
MODULE_AUTHOR("Eric Long <eric.long@spreadtrum.com>");
|
||||
MODULE_DESCRIPTION("Spreadtrum Watchdog Timer Controller Driver");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -1,12 +1,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for STM32 Independent Watchdog
|
||||
*
|
||||
* Copyright (C) Yannick Fertre 2017
|
||||
* Author: Yannick Fertre <yannick.fertre@st.com>
|
||||
* Copyright (C) STMicroelectronics 2017
|
||||
* Author: Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
|
||||
*
|
||||
* This driver is based on tegra_wdt.c
|
||||
*
|
||||
* License terms: GNU General Public License (GPL), version 2
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
@ -234,7 +234,6 @@ MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
|
||||
static int sunxi_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sunxi_wdt_dev *sunxi_wdt;
|
||||
const struct of_device_id *device;
|
||||
struct resource *res;
|
||||
int err;
|
||||
|
||||
@ -242,12 +241,10 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
|
||||
if (!sunxi_wdt)
|
||||
return -EINVAL;
|
||||
|
||||
device = of_match_device(sunxi_wdt_dt_ids, &pdev->dev);
|
||||
if (!device)
|
||||
sunxi_wdt->wdt_regs = of_device_get_match_data(&pdev->dev);
|
||||
if (!sunxi_wdt->wdt_regs)
|
||||
return -ENODEV;
|
||||
|
||||
sunxi_wdt->wdt_regs = device->data;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(sunxi_wdt->wdt_base))
|
||||
|
@ -97,6 +97,7 @@ static void watchdog_check_min_max_timeout(struct watchdog_device *wdd)
|
||||
|
||||
/**
|
||||
* watchdog_init_timeout() - initialize the timeout field
|
||||
* @wdd: watchdog device
|
||||
* @timeout_parm: timeout module parameter
|
||||
* @dev: Device that stores the timeout-sec property
|
||||
*
|
||||
|
@ -36,9 +36,10 @@
|
||||
#include <linux/errno.h> /* For the -ENODEV/... values */
|
||||
#include <linux/fs.h> /* For file operations */
|
||||
#include <linux/init.h> /* For __init/__exit/... */
|
||||
#include <linux/jiffies.h> /* For timeout functions */
|
||||
#include <linux/hrtimer.h> /* For hrtimers */
|
||||
#include <linux/kernel.h> /* For printk/panic/... */
|
||||
#include <linux/kref.h> /* For data references */
|
||||
#include <linux/kthread.h> /* For kthread_work */
|
||||
#include <linux/miscdevice.h> /* For handling misc devices */
|
||||
#include <linux/module.h> /* For module stuff/... */
|
||||
#include <linux/mutex.h> /* For mutexes */
|
||||
@ -46,9 +47,10 @@
|
||||
#include <linux/slab.h> /* For memory functions */
|
||||
#include <linux/types.h> /* For standard types (like size_t) */
|
||||
#include <linux/watchdog.h> /* For watchdog specific items */
|
||||
#include <linux/workqueue.h> /* For workqueue */
|
||||
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
|
||||
|
||||
#include <uapi/linux/sched/types.h> /* For struct sched_param */
|
||||
|
||||
#include "watchdog_core.h"
|
||||
#include "watchdog_pretimeout.h"
|
||||
|
||||
@ -65,9 +67,10 @@ struct watchdog_core_data {
|
||||
struct cdev cdev;
|
||||
struct watchdog_device *wdd;
|
||||
struct mutex lock;
|
||||
unsigned long last_keepalive;
|
||||
unsigned long last_hw_keepalive;
|
||||
struct delayed_work work;
|
||||
ktime_t last_keepalive;
|
||||
ktime_t last_hw_keepalive;
|
||||
struct hrtimer timer;
|
||||
struct kthread_work work;
|
||||
unsigned long status; /* Internal status bits */
|
||||
#define _WDOG_DEV_OPEN 0 /* Opened ? */
|
||||
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
|
||||
@ -79,7 +82,7 @@ static dev_t watchdog_devt;
|
||||
/* Reference to watchdog device behind /dev/watchdog */
|
||||
static struct watchdog_core_data *old_wd_data;
|
||||
|
||||
static struct workqueue_struct *watchdog_wq;
|
||||
static struct kthread_worker *watchdog_kworker;
|
||||
|
||||
static bool handle_boot_enabled =
|
||||
IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED);
|
||||
@ -107,18 +110,19 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd)
|
||||
(t && !watchdog_active(wdd) && watchdog_hw_running(wdd));
|
||||
}
|
||||
|
||||
static long watchdog_next_keepalive(struct watchdog_device *wdd)
|
||||
static ktime_t 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;
|
||||
ktime_t keepalive_interval;
|
||||
ktime_t last_heartbeat, latest_heartbeat;
|
||||
ktime_t virt_timeout;
|
||||
unsigned int hw_heartbeat_ms;
|
||||
|
||||
virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms);
|
||||
virt_timeout = ktime_add(wd_data->last_keepalive,
|
||||
ms_to_ktime(timeout_ms));
|
||||
hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms);
|
||||
keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2);
|
||||
keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2);
|
||||
|
||||
if (!watchdog_active(wdd))
|
||||
return keepalive_interval;
|
||||
@ -128,8 +132,11 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd)
|
||||
* 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);
|
||||
last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));
|
||||
latest_heartbeat = ktime_sub(last_heartbeat, ktime_get());
|
||||
if (ktime_before(latest_heartbeat, keepalive_interval))
|
||||
return latest_heartbeat;
|
||||
return keepalive_interval;
|
||||
}
|
||||
|
||||
static inline void watchdog_update_worker(struct watchdog_device *wdd)
|
||||
@ -137,29 +144,33 @@ 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);
|
||||
ktime_t t = watchdog_next_keepalive(wdd);
|
||||
|
||||
if (t > 0)
|
||||
mod_delayed_work(watchdog_wq, &wd_data->work, t);
|
||||
hrtimer_start(&wd_data->timer, t, HRTIMER_MODE_REL);
|
||||
} else {
|
||||
cancel_delayed_work(&wd_data->work);
|
||||
hrtimer_cancel(&wd_data->timer);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
ktime_t earliest_keepalive, now;
|
||||
int err;
|
||||
|
||||
if (time_is_after_jiffies(earliest_keepalive)) {
|
||||
mod_delayed_work(watchdog_wq, &wd_data->work,
|
||||
earliest_keepalive - jiffies);
|
||||
earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
|
||||
ms_to_ktime(wdd->min_hw_heartbeat_ms));
|
||||
now = ktime_get();
|
||||
|
||||
if (ktime_after(earliest_keepalive, now)) {
|
||||
hrtimer_start(&wd_data->timer,
|
||||
ktime_sub(earliest_keepalive, now),
|
||||
HRTIMER_MODE_REL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
wd_data->last_hw_keepalive = jiffies;
|
||||
wd_data->last_hw_keepalive = now;
|
||||
|
||||
if (wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd); /* ping the watchdog */
|
||||
@ -192,7 +203,7 @@ static int watchdog_ping(struct watchdog_device *wdd)
|
||||
|
||||
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
||||
|
||||
wd_data->last_keepalive = jiffies;
|
||||
wd_data->last_keepalive = ktime_get();
|
||||
return __watchdog_ping(wdd);
|
||||
}
|
||||
|
||||
@ -203,12 +214,11 @@ static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data)
|
||||
return wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd));
|
||||
}
|
||||
|
||||
static void watchdog_ping_work(struct work_struct *work)
|
||||
static void watchdog_ping_work(struct kthread_work *work)
|
||||
{
|
||||
struct watchdog_core_data *wd_data;
|
||||
|
||||
wd_data = container_of(to_delayed_work(work), struct watchdog_core_data,
|
||||
work);
|
||||
wd_data = container_of(work, struct watchdog_core_data, work);
|
||||
|
||||
mutex_lock(&wd_data->lock);
|
||||
if (watchdog_worker_should_ping(wd_data))
|
||||
@ -216,6 +226,16 @@ static void watchdog_ping_work(struct work_struct *work)
|
||||
mutex_unlock(&wd_data->lock);
|
||||
}
|
||||
|
||||
static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
|
||||
{
|
||||
struct watchdog_core_data *wd_data;
|
||||
|
||||
wd_data = container_of(timer, struct watchdog_core_data, timer);
|
||||
|
||||
kthread_queue_work(watchdog_kworker, &wd_data->work);
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
/*
|
||||
* watchdog_start: wrapper to start the watchdog.
|
||||
* @wdd: the watchdog device to start
|
||||
@ -230,7 +250,7 @@ static void watchdog_ping_work(struct work_struct *work)
|
||||
static int watchdog_start(struct watchdog_device *wdd)
|
||||
{
|
||||
struct watchdog_core_data *wd_data = wdd->wd_data;
|
||||
unsigned long started_at;
|
||||
ktime_t started_at;
|
||||
int err;
|
||||
|
||||
if (watchdog_active(wdd))
|
||||
@ -238,7 +258,7 @@ static int watchdog_start(struct watchdog_device *wdd)
|
||||
|
||||
set_bit(_WDOG_KEEPALIVE, &wd_data->status);
|
||||
|
||||
started_at = jiffies;
|
||||
started_at = ktime_get();
|
||||
if (watchdog_hw_running(wdd) && wdd->ops->ping)
|
||||
err = wdd->ops->ping(wdd);
|
||||
else
|
||||
@ -720,7 +740,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
|
||||
err = watchdog_ping(wdd);
|
||||
if (err < 0)
|
||||
break;
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
/* timeout == 0 means that we don't know the timeout */
|
||||
if (wdd->timeout == 0) {
|
||||
@ -769,6 +789,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct watchdog_core_data *wd_data;
|
||||
struct watchdog_device *wdd;
|
||||
bool hw_running;
|
||||
int err;
|
||||
|
||||
/* Get the corresponding watchdog device */
|
||||
@ -788,7 +809,8 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||
* If the /dev/watchdog device is open, we don't want the module
|
||||
* to be unloaded.
|
||||
*/
|
||||
if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) {
|
||||
hw_running = watchdog_hw_running(wdd);
|
||||
if (!hw_running && !try_module_get(wdd->ops->owner)) {
|
||||
err = -EBUSY;
|
||||
goto out_clear;
|
||||
}
|
||||
@ -799,7 +821,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
|
||||
|
||||
file->private_data = wd_data;
|
||||
|
||||
if (!watchdog_hw_running(wdd))
|
||||
if (!hw_running)
|
||||
kref_get(&wd_data->kref);
|
||||
|
||||
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */
|
||||
@ -919,10 +941,12 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
wd_data->wdd = wdd;
|
||||
wdd->wd_data = wd_data;
|
||||
|
||||
if (!watchdog_wq)
|
||||
if (IS_ERR_OR_NULL(watchdog_kworker))
|
||||
return -ENODEV;
|
||||
|
||||
INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work);
|
||||
kthread_init_work(&wd_data->work, watchdog_ping_work);
|
||||
hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
wd_data->timer.function = watchdog_timer_expired;
|
||||
|
||||
if (wdd->id == 0) {
|
||||
old_wd_data = wd_data;
|
||||
@ -958,21 +982,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
|
||||
}
|
||||
|
||||
/* Record time of most recent heartbeat as 'just before now'. */
|
||||
wd_data->last_hw_keepalive = jiffies - 1;
|
||||
wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);
|
||||
|
||||
/*
|
||||
* If the watchdog is running, prevent its driver from being unloaded,
|
||||
* and schedule an immediate ping.
|
||||
*/
|
||||
if (watchdog_hw_running(wdd)) {
|
||||
if (handle_boot_enabled) {
|
||||
__module_get(wdd->ops->owner);
|
||||
kref_get(&wd_data->kref);
|
||||
queue_delayed_work(watchdog_wq, &wd_data->work, 0);
|
||||
} else {
|
||||
__module_get(wdd->ops->owner);
|
||||
kref_get(&wd_data->kref);
|
||||
if (handle_boot_enabled)
|
||||
hrtimer_start(&wd_data->timer, 0, HRTIMER_MODE_REL);
|
||||
else
|
||||
pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
|
||||
wdd->id);
|
||||
}
|
||||
wdd->id);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1006,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
|
||||
watchdog_stop(wdd);
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&wd_data->work);
|
||||
hrtimer_cancel(&wd_data->timer);
|
||||
kthread_cancel_work_sync(&wd_data->work);
|
||||
|
||||
kref_put(&wd_data->kref, watchdog_core_data_release);
|
||||
}
|
||||
@ -1110,13 +1134,14 @@ void watchdog_dev_unregister(struct watchdog_device *wdd)
|
||||
int __init watchdog_dev_init(void)
|
||||
{
|
||||
int err;
|
||||
struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1,};
|
||||
|
||||
watchdog_wq = alloc_workqueue("watchdogd",
|
||||
WQ_HIGHPRI | WQ_MEM_RECLAIM, 0);
|
||||
if (!watchdog_wq) {
|
||||
pr_err("Failed to create watchdog workqueue\n");
|
||||
return -ENOMEM;
|
||||
watchdog_kworker = kthread_create_worker(0, "watchdogd");
|
||||
if (IS_ERR(watchdog_kworker)) {
|
||||
pr_err("Failed to create watchdog kworker\n");
|
||||
return PTR_ERR(watchdog_kworker);
|
||||
}
|
||||
sched_setscheduler(watchdog_kworker->task, SCHED_FIFO, ¶m);
|
||||
|
||||
err = class_register(&watchdog_class);
|
||||
if (err < 0) {
|
||||
@ -1135,7 +1160,7 @@ int __init watchdog_dev_init(void)
|
||||
err_alloc:
|
||||
class_unregister(&watchdog_class);
|
||||
err_register:
|
||||
destroy_workqueue(watchdog_wq);
|
||||
kthread_destroy_worker(watchdog_kworker);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -1149,7 +1174,7 @@ void __exit watchdog_dev_exit(void)
|
||||
{
|
||||
unregister_chrdev_region(watchdog_devt, MAX_DOGS);
|
||||
class_unregister(&watchdog_class);
|
||||
destroy_workqueue(watchdog_wq);
|
||||
kthread_destroy_worker(watchdog_kworker);
|
||||
}
|
||||
|
||||
module_param(handle_boot_enabled, bool, 0444);
|
||||
|
@ -430,7 +430,7 @@ static long wdtpci_ioctl(struct file *file, unsigned int cmd,
|
||||
if (wdtpci_set_heartbeat(new_heartbeat))
|
||||
return -EINVAL;
|
||||
wdtpci_ping();
|
||||
/* Fall */
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
default:
|
||||
|
@ -9,10 +9,7 @@
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#define DRV_NAME "wdt"
|
||||
#define DRV_VERSION "0.01"
|
||||
#define DRV_NAME "xen_wdt"
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/errno.h>
|
||||
@ -21,25 +18,20 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <xen/xen.h>
|
||||
#include <asm/xen/hypercall.h>
|
||||
#include <xen/interface/sched.h>
|
||||
|
||||
static struct platform_device *platform_device;
|
||||
static DEFINE_SPINLOCK(wdt_lock);
|
||||
static struct sched_watchdog wdt;
|
||||
static __kernel_time_t wdt_expires;
|
||||
static bool is_active, expect_release;
|
||||
static time64_t wdt_expires;
|
||||
|
||||
#define WATCHDOG_TIMEOUT 60 /* in seconds */
|
||||
static unsigned int timeout = WATCHDOG_TIMEOUT;
|
||||
static unsigned int timeout;
|
||||
module_param(timeout, uint, S_IRUGO);
|
||||
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
|
||||
"(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
|
||||
@ -49,20 +41,18 @@ module_param(nowayout, bool, S_IRUGO);
|
||||
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
|
||||
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||
|
||||
static inline __kernel_time_t set_timeout(void)
|
||||
static inline time64_t set_timeout(struct watchdog_device *wdd)
|
||||
{
|
||||
wdt.timeout = timeout;
|
||||
return ktime_to_timespec(ktime_get()).tv_sec + timeout;
|
||||
wdt.timeout = wdd->timeout;
|
||||
return ktime_get_seconds() + wdd->timeout;
|
||||
}
|
||||
|
||||
static int xen_wdt_start(void)
|
||||
static int xen_wdt_start(struct watchdog_device *wdd)
|
||||
{
|
||||
__kernel_time_t expires;
|
||||
time64_t expires;
|
||||
int err;
|
||||
|
||||
spin_lock(&wdt_lock);
|
||||
|
||||
expires = set_timeout();
|
||||
expires = set_timeout(wdd);
|
||||
if (!wdt.id)
|
||||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
|
||||
else
|
||||
@ -74,36 +64,28 @@ static int xen_wdt_start(void)
|
||||
} else
|
||||
BUG_ON(!err);
|
||||
|
||||
spin_unlock(&wdt_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int xen_wdt_stop(void)
|
||||
static int xen_wdt_stop(struct watchdog_device *wdd)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
spin_lock(&wdt_lock);
|
||||
|
||||
wdt.timeout = 0;
|
||||
if (wdt.id)
|
||||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
|
||||
if (!err)
|
||||
wdt.id = 0;
|
||||
|
||||
spin_unlock(&wdt_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int xen_wdt_kick(void)
|
||||
static int xen_wdt_kick(struct watchdog_device *wdd)
|
||||
{
|
||||
__kernel_time_t expires;
|
||||
time64_t expires;
|
||||
int err;
|
||||
|
||||
spin_lock(&wdt_lock);
|
||||
|
||||
expires = set_timeout();
|
||||
expires = set_timeout(wdd);
|
||||
if (wdt.id)
|
||||
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
|
||||
else
|
||||
@ -111,195 +93,72 @@ static int xen_wdt_kick(void)
|
||||
if (!err)
|
||||
wdt_expires = expires;
|
||||
|
||||
spin_unlock(&wdt_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int xen_wdt_open(struct inode *inode, struct file *file)
|
||||
static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* /dev/watchdog can only be opened once */
|
||||
if (xchg(&is_active, true))
|
||||
return -EBUSY;
|
||||
|
||||
err = xen_wdt_start();
|
||||
if (err == -EBUSY)
|
||||
err = xen_wdt_kick();
|
||||
return err ?: nonseekable_open(inode, file);
|
||||
return wdt_expires - ktime_get_seconds();
|
||||
}
|
||||
|
||||
static int xen_wdt_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (expect_release)
|
||||
err = xen_wdt_stop();
|
||||
else {
|
||||
pr_crit("unexpected close, not stopping watchdog!\n");
|
||||
xen_wdt_kick();
|
||||
}
|
||||
is_active = err;
|
||||
expect_release = false;
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t xen_wdt_write(struct file *file, const char __user *data,
|
||||
size_t len, loff_t *ppos)
|
||||
{
|
||||
/* See if we got the magic character 'V' and reload the timer */
|
||||
if (len) {
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
/* in case it was set long ago */
|
||||
expect_release = false;
|
||||
|
||||
/* scan to see whether or not we got the magic
|
||||
character */
|
||||
for (i = 0; i != len; i++) {
|
||||
char c;
|
||||
if (get_user(c, data + i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
expect_release = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* someone wrote to us, we should reload the timer */
|
||||
xen_wdt_kick();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static long xen_wdt_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
int new_options, retval = -EINVAL;
|
||||
int new_timeout;
|
||||
int __user *argp = (void __user *)arg;
|
||||
static const struct watchdog_info ident = {
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
|
||||
.firmware_version = 0,
|
||||
.identity = DRV_NAME,
|
||||
};
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
|
||||
|
||||
case WDIOC_GETSTATUS:
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
return put_user(0, argp);
|
||||
|
||||
case WDIOC_SETOPTIONS:
|
||||
if (get_user(new_options, argp))
|
||||
return -EFAULT;
|
||||
|
||||
if (new_options & WDIOS_DISABLECARD)
|
||||
retval = xen_wdt_stop();
|
||||
if (new_options & WDIOS_ENABLECARD) {
|
||||
retval = xen_wdt_start();
|
||||
if (retval == -EBUSY)
|
||||
retval = xen_wdt_kick();
|
||||
}
|
||||
return retval;
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
xen_wdt_kick();
|
||||
return 0;
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
if (get_user(new_timeout, argp))
|
||||
return -EFAULT;
|
||||
if (!new_timeout)
|
||||
return -EINVAL;
|
||||
timeout = new_timeout;
|
||||
xen_wdt_kick();
|
||||
/* fall through */
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(timeout, argp);
|
||||
|
||||
case WDIOC_GETTIMELEFT:
|
||||
retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec;
|
||||
return put_user(retval, argp);
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
static const struct file_operations xen_wdt_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.write = xen_wdt_write,
|
||||
.unlocked_ioctl = xen_wdt_ioctl,
|
||||
.open = xen_wdt_open,
|
||||
.release = xen_wdt_release,
|
||||
static struct watchdog_info xen_wdt_info = {
|
||||
.identity = DRV_NAME,
|
||||
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||
};
|
||||
|
||||
static struct miscdevice xen_wdt_miscdev = {
|
||||
.minor = WATCHDOG_MINOR,
|
||||
.name = "watchdog",
|
||||
.fops = &xen_wdt_fops,
|
||||
static const struct watchdog_ops xen_wdt_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.start = xen_wdt_start,
|
||||
.stop = xen_wdt_stop,
|
||||
.ping = xen_wdt_kick,
|
||||
.get_timeleft = xen_wdt_get_timeleft,
|
||||
};
|
||||
|
||||
static int xen_wdt_probe(struct platform_device *dev)
|
||||
static struct watchdog_device xen_wdt_dev = {
|
||||
.info = &xen_wdt_info,
|
||||
.ops = &xen_wdt_ops,
|
||||
.timeout = WATCHDOG_TIMEOUT,
|
||||
};
|
||||
|
||||
static int xen_wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sched_watchdog wd = { .id = ~0 };
|
||||
int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd);
|
||||
|
||||
switch (ret) {
|
||||
case -EINVAL:
|
||||
if (!timeout) {
|
||||
timeout = WATCHDOG_TIMEOUT;
|
||||
pr_info("timeout value invalid, using %d\n", timeout);
|
||||
}
|
||||
|
||||
ret = misc_register(&xen_wdt_miscdev);
|
||||
if (ret) {
|
||||
pr_err("cannot register miscdev on minor=%d (%d)\n",
|
||||
WATCHDOG_MINOR, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
pr_info("initialized (timeout=%ds, nowayout=%d)\n",
|
||||
timeout, nowayout);
|
||||
break;
|
||||
|
||||
case -ENOSYS:
|
||||
pr_info("not supported\n");
|
||||
ret = -ENODEV;
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_info("bogus return value %d\n", ret);
|
||||
break;
|
||||
if (ret == -ENOSYS) {
|
||||
dev_err(&pdev->dev, "watchdog not supported by hypervisor\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
if (ret != -EINVAL) {
|
||||
dev_err(&pdev->dev, "unexpected hypervisor error (%d)\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static int xen_wdt_remove(struct platform_device *dev)
|
||||
{
|
||||
/* Stop the timer before we leave */
|
||||
if (!nowayout)
|
||||
xen_wdt_stop();
|
||||
if (watchdog_init_timeout(&xen_wdt_dev, timeout, NULL))
|
||||
dev_info(&pdev->dev, "timeout value invalid, using %d\n",
|
||||
xen_wdt_dev.timeout);
|
||||
watchdog_set_nowayout(&xen_wdt_dev, nowayout);
|
||||
watchdog_stop_on_reboot(&xen_wdt_dev);
|
||||
watchdog_stop_on_unregister(&xen_wdt_dev);
|
||||
|
||||
misc_deregister(&xen_wdt_miscdev);
|
||||
ret = devm_watchdog_register_device(&pdev->dev, &xen_wdt_dev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot register watchdog device (%d)\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(&pdev->dev, "initialized (timeout=%ds, nowayout=%d)\n",
|
||||
xen_wdt_dev.timeout, nowayout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xen_wdt_shutdown(struct platform_device *dev)
|
||||
{
|
||||
xen_wdt_stop();
|
||||
}
|
||||
|
||||
static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state)
|
||||
{
|
||||
typeof(wdt.id) id = wdt.id;
|
||||
int rc = xen_wdt_stop();
|
||||
int rc = xen_wdt_stop(&xen_wdt_dev);
|
||||
|
||||
wdt.id = id;
|
||||
return rc;
|
||||
@ -310,13 +169,11 @@ static int xen_wdt_resume(struct platform_device *dev)
|
||||
if (!wdt.id)
|
||||
return 0;
|
||||
wdt.id = 0;
|
||||
return xen_wdt_start();
|
||||
return xen_wdt_start(&xen_wdt_dev);
|
||||
}
|
||||
|
||||
static struct platform_driver xen_wdt_driver = {
|
||||
.probe = xen_wdt_probe,
|
||||
.remove = xen_wdt_remove,
|
||||
.shutdown = xen_wdt_shutdown,
|
||||
.suspend = xen_wdt_suspend,
|
||||
.resume = xen_wdt_resume,
|
||||
.driver = {
|
||||
@ -331,8 +188,6 @@ static int __init xen_wdt_init_module(void)
|
||||
if (!xen_domain())
|
||||
return -ENODEV;
|
||||
|
||||
pr_info("Xen WatchDog Timer Driver v%s\n", DRV_VERSION);
|
||||
|
||||
err = platform_driver_register(&xen_wdt_driver);
|
||||
if (err)
|
||||
return err;
|
||||
@ -351,7 +206,6 @@ static void __exit xen_wdt_cleanup_module(void)
|
||||
{
|
||||
platform_device_unregister(platform_device);
|
||||
platform_driver_unregister(&xen_wdt_driver);
|
||||
pr_info("module unloaded\n");
|
||||
}
|
||||
|
||||
module_init(xen_wdt_init_module);
|
||||
@ -359,5 +213,4 @@ module_exit(xen_wdt_cleanup_module);
|
||||
|
||||
MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>");
|
||||
MODULE_DESCRIPTION("Xen WatchDog Timer Driver");
|
||||
MODULE_VERSION(DRV_VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
Loading…
Reference in New Issue
Block a user