Commit 7e6127c1 authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents 413879a1 592a547a
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>;
};
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: 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 - reg : shall contain base register location and length
- interrupts : shall contain the interrupt for the watchdog - interrupts : shall contain the interrupt for the watchdog
...@@ -11,7 +16,7 @@ Optional properties: ...@@ -11,7 +16,7 @@ Optional properties:
Example: Example:
watchdog@41000000 { watchdog@41000000 {
compatible = "cortina,gemini-watchdog"; compatible = "faraday,ftwdt010";
reg = <0x41000000 0x1000>; reg = <0x41000000 0x1000>;
interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; interrupts = <3 IRQ_TYPE_LEVEL_HIGH>;
}; };
Ingenic Watchdog Timer (WDT) Controller for JZ4740 Ingenic Watchdog Timer (WDT) Controller for JZ4740 & JZ4780
Required properties: Required properties:
compatible: "ingenic,jz4740-watchdog" compatible: "ingenic,jz4740-watchdog" or "ingenic,jz4780-watchdog"
reg: Register address and length for watchdog registers reg: Register address and length for watchdog registers
Example: Example:
......
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: ...@@ -4,10 +4,11 @@ Required properties:
- compatible : Should be "renesas,<soctype>-wdt", and - compatible : Should be "renesas,<soctype>-wdt", and
"renesas,rcar-gen3-wdt" or "renesas,rza-wdt" as fallback. "renesas,rcar-gen3-wdt" or "renesas,rza-wdt" as fallback.
Examples with soctypes are: Examples with soctypes are:
- "renesas,r7s72100-wdt" (RZ/A1)
- "renesas,r8a7795-wdt" (R-Car H3) - "renesas,r8a7795-wdt" (R-Car H3)
- "renesas,r8a7796-wdt" (R-Car M3-W) - "renesas,r8a7796-wdt" (R-Car M3-W)
- "renesas,r8a77970-wdt" (R-Car V3M)
- "renesas,r8a77995-wdt" (R-Car D3) - "renesas,r8a77995-wdt" (R-Car D3)
- "renesas,r7s72100-wdt" (RZ/A1)
When compatible with the generic version, nodes must list the SoC-specific When compatible with the generic version, nodes must list the SoC-specific
version corresponding to the platform first, followed by the generic version corresponding to the platform first, followed by the generic
......
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) ...@@ -40,11 +40,6 @@ margin: Watchdog margin in seconds (default=60)
nowayout: Disable watchdog shutdown on close nowayout: Disable watchdog shutdown on close
(default=kernel config parameter) (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: at91rm9200_wdt:
wdt_time: Watchdog time in seconds. (default=5) wdt_time: Watchdog time in seconds. (default=5)
nowayout: Watchdog cannot be stopped once started nowayout: Watchdog cannot be stopped once started
...@@ -162,11 +157,6 @@ testmode: Watchdog test mode (1 = no reboot), default=0 ...@@ -162,11 +157,6 @@ testmode: Watchdog test mode (1 = no reboot), default=0
nowayout: Watchdog cannot be stopped once started nowayout: Watchdog cannot be stopped once started
(default=kernel config parameter) (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: ixp4xx_wdt:
heartbeat: Watchdog heartbeat in seconds (default 60s) heartbeat: Watchdog heartbeat in seconds (default 60s)
nowayout: Watchdog cannot be stopped once started nowayout: Watchdog cannot be stopped once started
...@@ -381,19 +371,6 @@ timeout: Watchdog timeout in seconds. 1 <= timeout <= 255, default=60. ...@@ -381,19 +371,6 @@ timeout: Watchdog timeout in seconds. 1 <= timeout <= 255, default=60.
nowayout: Watchdog cannot be stopped once started nowayout: Watchdog cannot be stopped once started
(default=kernel config parameter) (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: w83877f_wdt:
timeout: Watchdog timeout in seconds. (1<=timeout<=3600, default=30) timeout: Watchdog timeout in seconds. (1<=timeout<=3600, default=30)
nowayout: Watchdog cannot be stopped once started nowayout: Watchdog cannot be stopped once started
......
...@@ -14991,8 +14991,8 @@ S: Maintained ...@@ -14991,8 +14991,8 @@ S: Maintained
F: drivers/input/tablet/wacom_serial4.c F: drivers/input/tablet/wacom_serial4.c
WATCHDOG DEVICE DRIVERS WATCHDOG DEVICE DRIVERS
M: Wim Van Sebroeck <wim@iguana.be> M: Wim Van Sebroeck <wim@linux-watchdog.org>
R: Guenter Roeck <linux@roeck-us.net> M: Guenter Roeck <linux@roeck-us.net>
L: linux-watchdog@vger.kernel.org L: linux-watchdog@vger.kernel.org
W: http://www.linux-watchdog.org/ W: http://www.linux-watchdog.org/
T: git git://www.linux-watchdog.org/linux-watchdog.git T: git git://www.linux-watchdog.org/linux-watchdog.git
......
...@@ -328,16 +328,18 @@ config 977_WATCHDOG ...@@ -328,16 +328,18 @@ config 977_WATCHDOG
Not sure? It's safe to say N. Not sure? It's safe to say N.
config GEMINI_WATCHDOG config FTWDT010_WATCHDOG
tristate "Gemini watchdog" tristate "Faraday Technology FTWDT010 watchdog"
depends on ARCH_GEMINI depends on ARM || COMPILE_TEST
select WATCHDOG_CORE select WATCHDOG_CORE
default ARCH_GEMINI
help help
Say Y here if to include support for the watchdog timer Say Y here if to include support for the Faraday Technology
embedded in the Cortina Systems Gemini family of devices. FTWDT010 watchdog timer embedded in the Cortina Systems Gemini
family of devices.
To compile this driver as a module, choose M here: the To compile this driver as a module, choose M here: the
module will be called gemini_wdt. module will be called ftwdt010_wdt.
config IXP4XX_WATCHDOG config IXP4XX_WATCHDOG
tristate "IXP4xx Watchdog" tristate "IXP4xx Watchdog"
...@@ -748,12 +750,12 @@ config RENESAS_RZAWDT ...@@ -748,12 +750,12 @@ config RENESAS_RZAWDT
Renesas RZ/A SoCs. These watchdogs can be used to reset a system. Renesas RZ/A SoCs. These watchdogs can be used to reset a system.
config ASPEED_WATCHDOG config ASPEED_WATCHDOG
tristate "Aspeed 2400 watchdog support" tristate "Aspeed BMC watchdog support"
depends on ARCH_ASPEED || COMPILE_TEST depends on ARCH_ASPEED || COMPILE_TEST
select WATCHDOG_CORE select WATCHDOG_CORE
help help
Say Y here to include support for the watchdog timer 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. This driver is required to reboot the SoC.
...@@ -794,14 +796,23 @@ config UNIPHIER_WATCHDOG ...@@ -794,14 +796,23 @@ config UNIPHIER_WATCHDOG
To compile this driver as a module, choose M here: the To compile this driver as a module, choose M here: the
module will be called uniphier_wdt. module will be called uniphier_wdt.
# AVR32 Architecture 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
Say Y here to include support for the watchdog timer in
Realtek RTD1295 SoCs.
config AT32AP700X_WDT config SPRD_WATCHDOG
tristate "AT32AP700x watchdog" tristate "Spreadtrum watchdog support"
depends on CPU_AT32AP700X || COMPILE_TEST depends on ARCH_SPRD || COMPILE_TEST
select WATCHDOG_CORE
help help
Watchdog timer embedded into AT32AP700x devices. This will reboot Say Y here to include watchdog timer supported
your system when the timeout is reached. by Spreadtrum system.
# BLACKFIN Architecture # BLACKFIN Architecture
...@@ -1458,7 +1469,7 @@ config RC32434_WDT ...@@ -1458,7 +1469,7 @@ config RC32434_WDT
config INDYDOG config INDYDOG
tristate "Indy/I2 Hardware Watchdog" tristate "Indy/I2 Hardware Watchdog"
depends on SGI_HAS_INDYDOG || (MIPS && COMPILE_TEST) depends on SGI_HAS_INDYDOG
help help
Hardware driver for the Indy's/I2's watchdog. This is a Hardware driver for the Indy's/I2's watchdog. This is a
watchdog timer that will reboot the machine after a 60 second watchdog timer that will reboot the machine after a 60 second
......
...@@ -46,7 +46,7 @@ obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o ...@@ -46,7 +46,7 @@ obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o
obj-$(CONFIG_21285_WATCHDOG) += wdt285.o obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
obj-$(CONFIG_977_WATCHDOG) += wdt977.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_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o
obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
...@@ -88,9 +88,8 @@ obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o ...@@ -88,9 +88,8 @@ obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o
obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o
obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o
obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o
# AVR32 Architecture obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
# BLACKFIN Architecture # BLACKFIN Architecture
obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o
......
...@@ -181,7 +181,7 @@ static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ...@@ -181,7 +181,7 @@ static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (advwdt_set_heartbeat(new_timeout)) if (advwdt_set_heartbeat(new_timeout))
return -EINVAL; return -EINVAL;
advwdt_ping(); advwdt_ping();
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(timeout, p); return put_user(timeout, p);
default: default:
......
...@@ -223,8 +223,8 @@ static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ...@@ -223,8 +223,8 @@ static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (ali_settimer(new_timeout)) if (ali_settimer(new_timeout))
return -EINVAL; return -EINVAL;
ali_keepalive(); ali_keepalive();
/* Fall */
} }
/* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(timeout, p); return put_user(timeout, p);
default: default:
......
...@@ -243,9 +243,13 @@ static int aspeed_wdt_probe(struct platform_device *pdev) ...@@ -243,9 +243,13 @@ static int aspeed_wdt_probe(struct platform_device *pdev)
if (of_property_read_bool(np, "aspeed,external-signal")) if (of_property_read_bool(np, "aspeed,external-signal"))
wdt->ctrl |= WDT_CTRL_WDT_EXT; wdt->ctrl |= WDT_CTRL_WDT_EXT;
writel(wdt->ctrl, wdt->base + WDT_CTRL);
if (readl(wdt->base + WDT_CTRL) & WDT_CTRL_ENABLE) { 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); aspeed_wdt_start(&wdt->wdd);
set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
} }
...@@ -312,7 +316,18 @@ static struct platform_driver aspeed_watchdog_driver = { ...@@ -312,7 +316,18 @@ static struct platform_driver aspeed_watchdog_driver = {
.of_match_table = of_match_ptr(aspeed_wdt_of_table), .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_DESCRIPTION("Aspeed Watchdog Driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
/*
* 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) ...@@ -46,22 +46,6 @@ static void da9062_set_window_start(struct da9062_watchdog *wdt)
wdt->j_time_stamp = jiffies; 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) static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
{ {
unsigned int i; unsigned int i;
...@@ -78,8 +62,6 @@ static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt) ...@@ -78,8 +62,6 @@ static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
{ {
int ret; int ret;
da9062_apply_window_protection(wdt);
ret = regmap_update_bits(wdt->hw->regmap, ret = regmap_update_bits(wdt->hw->regmap,
DA9062AA_CONTROL_F, DA9062AA_CONTROL_F,
DA9062AA_WATCHDOG_MASK, DA9062AA_WATCHDOG_MASK,
...@@ -100,6 +82,13 @@ static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt, ...@@ -100,6 +82,13 @@ static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
if (ret) if (ret)
return 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, return regmap_update_bits(chip->regmap,
DA9062AA_CONTROL_D, DA9062AA_CONTROL_D,
DA9062AA_TWDSCALE_MASK, DA9062AA_TWDSCALE_MASK,
...@@ -175,6 +164,25 @@ static int da9062_wdt_set_timeout(struct watchdog_device *wdd, ...@@ -175,6 +164,25 @@ static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
return ret; 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 = { static const struct watchdog_info da9062_watchdog_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "DA9062 WDT", .identity = "DA9062 WDT",
...@@ -186,6 +194,7 @@ static const struct watchdog_ops da9062_watchdog_ops = { ...@@ -186,6 +194,7 @@ static const struct watchdog_ops da9062_watchdog_ops = {
.stop = da9062_wdt_stop, .stop = da9062_wdt_stop,
.ping = da9062_wdt_ping, .ping = da9062_wdt_ping,
.set_timeout = da9062_wdt_set_timeout, .set_timeout = da9062_wdt_set_timeout,
.restart = da9062_wdt_restart,
}; };
static const struct of_device_id da9062_compatible_id_table[] = { static const struct of_device_id da9062_compatible_id_table[] = {
...@@ -215,10 +224,13 @@ static int da9062_wdt_probe(struct platform_device *pdev) ...@@ -215,10 +224,13 @@ static int da9062_wdt_probe(struct platform_device *pdev)
wdt->wdtdev.ops = &da9062_watchdog_ops; wdt->wdtdev.ops = &da9062_watchdog_ops;
wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT; wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
wdt->wdtdev.max_timeout = DA9062_WDT_MAX_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.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS; wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
wdt->wdtdev.parent = &pdev->dev; wdt->wdtdev.parent = &pdev->dev;
watchdog_set_restart_priority(&wdt->wdtdev, 128);
watchdog_set_drvdata(&wdt->wdtdev, wdt); watchdog_set_drvdata(&wdt->wdtdev, wdt);
ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev); ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdtdev);
......
...@@ -140,6 +140,42 @@ static unsigned int davinci_wdt_get_timeleft(struct watchdog_device *wdd) ...@@ -140,6 +140,42 @@ static unsigned int davinci_wdt_get_timeleft(struct watchdog_device *wdd)
return wdd->timeout - timer_counter; 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 = { static const struct watchdog_info davinci_wdt_info = {
.options = WDIOF_KEEPALIVEPING, .options = WDIOF_KEEPALIVEPING,
.identity = "DaVinci/Keystone Watchdog", .identity = "DaVinci/Keystone Watchdog",
...@@ -151,6 +187,7 @@ static const struct watchdog_ops davinci_wdt_ops = { ...@@ -151,6 +187,7 @@ static const struct watchdog_ops davinci_wdt_ops = {
.stop = davinci_wdt_ping, .stop = davinci_wdt_ping,
.ping = davinci_wdt_ping, .ping = davinci_wdt_ping,
.get_timeleft = davinci_wdt_get_timeleft, .get_timeleft = davinci_wdt_get_timeleft,
.restart = davinci_wdt_restart,
}; };
static int davinci_wdt_probe(struct platform_device *pdev) static int davinci_wdt_probe(struct platform_device *pdev)
...@@ -195,6 +232,7 @@ 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_drvdata(wdd, davinci_wdt);
watchdog_set_nowayout(wdd, 1); watchdog_set_nowayout(wdd, 1);
watchdog_set_restart_priority(wdd, 128);
wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
davinci_wdt->base = devm_ioremap_resource(dev, wdt_mem); davinci_wdt->base = devm_ioremap_resource(dev, wdt_mem);
......
...@@ -127,14 +127,27 @@ static int dw_wdt_start(struct watchdog_device *wdd) ...@@ -127,14 +127,27 @@ static int dw_wdt_start(struct watchdog_device *wdd)
dw_wdt_set_timeout(wdd, wdd->timeout); dw_wdt_set_timeout(wdd, wdd->timeout);
set_bit(WDOG_HW_RUNNING, &wdd->status);
writel(WDOG_CONTROL_REG_WDT_EN_MASK, writel(WDOG_CONTROL_REG_WDT_EN_MASK,
dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
return 0; 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, static int dw_wdt_restart(struct watchdog_device *wdd,
unsigned long action, void *data) unsigned long action, void *data)
{ {
...@@ -173,6 +186,7 @@ static const struct watchdog_info dw_wdt_ident = { ...@@ -173,6 +186,7 @@ static const struct watchdog_info dw_wdt_ident = {
static const struct watchdog_ops dw_wdt_ops = { static const struct watchdog_ops dw_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.start = dw_wdt_start, .start = dw_wdt_start,
.stop = dw_wdt_stop,
.ping = dw_wdt_ping, .ping = dw_wdt_ping,
.set_timeout = dw_wdt_set_timeout, .set_timeout = dw_wdt_set_timeout,
.get_timeleft = dw_wdt_get_timeleft, .get_timeleft = dw_wdt_get_timeleft,
......
...@@ -290,7 +290,7 @@ static long eurwdt_ioctl(struct file *file, ...@@ -290,7 +290,7 @@ static long eurwdt_ioctl(struct file *file,
eurwdt_timeout = time; eurwdt_timeout = time;
eurwdt_set_timeout(time); eurwdt_set_timeout(time);
spin_unlock(&eurwdt_lock); spin_unlock(&eurwdt_lock);
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(eurwdt_timeout, p); return put_user(eurwdt_timeout, p);
......
...@@ -627,7 +627,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, ...@@ -627,7 +627,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
if (new_options & WDIOS_ENABLECARD) if (new_options & WDIOS_ENABLECARD)
return watchdog_start(); return watchdog_start();
/* fall through */
case WDIOC_KEEPALIVE: case WDIOC_KEEPALIVE:
watchdog_keepalive(); watchdog_keepalive();
...@@ -641,7 +641,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, ...@@ -641,7 +641,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
return -EINVAL; return -EINVAL;
watchdog_keepalive(); watchdog_keepalive();
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(watchdog.timeout, uarg.i); return put_user(watchdog.timeout, uarg.i);
......
/* /*
* Watchdog driver for Cortina Systems Gemini SoC * Watchdog driver for Faraday Technology FTWDT010
* *
* Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
* *
...@@ -22,92 +22,98 @@ ...@@ -22,92 +22,98 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#define GEMINI_WDCOUNTER 0x0 #define FTWDT010_WDCOUNTER 0x0
#define GEMINI_WDLOAD 0x4 #define FTWDT010_WDLOAD 0x4
#define GEMINI_WDRESTART 0x8 #define FTWDT010_WDRESTART 0x8
#define GEMINI_WDCR 0xC #define FTWDT010_WDCR 0xC
#define WDRESTART_MAGIC 0x5AB9 #define WDRESTART_MAGIC 0x5AB9
#define WDCR_CLOCK_5MHZ BIT(4) #define WDCR_CLOCK_5MHZ BIT(4)
#define WDCR_WDEXT BIT(3)
#define WDCR_WDINTR BIT(2)
#define WDCR_SYS_RST BIT(1) #define WDCR_SYS_RST BIT(1)
#define WDCR_ENABLE BIT(0) #define WDCR_ENABLE BIT(0)
#define WDT_CLOCK 5000000 /* 5 MHz */ #define WDT_CLOCK 5000000 /* 5 MHz */
struct gemini_wdt { struct ftwdt010_wdt {
struct watchdog_device wdd; struct watchdog_device wdd;
struct device *dev; struct device *dev;
void __iomem *base; void __iomem *base;
bool has_irq;
}; };
static inline static inline
struct gemini_wdt *to_gemini_wdt(struct watchdog_device *wdd) struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd)
{ {
return container_of(wdd, struct gemini_wdt, wdd); return container_of(wdd, struct ftwdt010_wdt, wdd);
} }
static int gemini_wdt_start(struct watchdog_device *wdd) static int ftwdt010_wdt_start(struct watchdog_device *wdd)
{ {
struct gemini_wdt *gwdt = to_gemini_wdt(wdd); struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
u32 enable;
writel(wdd->timeout * WDT_CLOCK, gwdt->base + GEMINI_WDLOAD); writel(wdd->timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD);
writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
/* set clock before enabling */ /* set clock before enabling */
writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST;
gwdt->base + GEMINI_WDCR); writel(enable, gwdt->base + FTWDT010_WDCR);
writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, if (gwdt->has_irq)
gwdt->base + GEMINI_WDCR); enable |= WDCR_WDINTR;
enable |= WDCR_ENABLE;
writel(enable, gwdt->base + FTWDT010_WDCR);
return 0; return 0;
} }
static int gemini_wdt_stop(struct watchdog_device *wdd) static int ftwdt010_wdt_stop(struct watchdog_device *wdd)
{ {
struct gemini_wdt *gwdt = to_gemini_wdt(wdd); struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
writel(0, gwdt->base + GEMINI_WDCR); writel(0, gwdt->base + FTWDT010_WDCR);
return 0; return 0;
} }
static int gemini_wdt_ping(struct watchdog_device *wdd) static int ftwdt010_wdt_ping(struct watchdog_device *wdd)
{ {
struct gemini_wdt *gwdt = to_gemini_wdt(wdd); struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
return 0; return 0;
} }
static int gemini_wdt_set_timeout(struct watchdog_device *wdd, static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd,
unsigned int timeout) unsigned int timeout)
{ {
wdd->timeout = timeout; wdd->timeout = timeout;
if (watchdog_active(wdd)) if (watchdog_active(wdd))
gemini_wdt_start(wdd); ftwdt010_wdt_start(wdd);
return 0; return 0;
} }
static irqreturn_t gemini_wdt_interrupt(int irq, void *data) static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data)
{ {
struct gemini_wdt *gwdt = data; struct ftwdt010_wdt *gwdt = data;
watchdog_notify_pretimeout(&gwdt->wdd); watchdog_notify_pretimeout(&gwdt->wdd);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static const struct watchdog_ops gemini_wdt_ops = { static const struct watchdog_ops ftwdt010_wdt_ops = {
.start = gemini_wdt_start, .start = ftwdt010_wdt_start,
.stop = gemini_wdt_stop, .stop = ftwdt010_wdt_stop,
.ping = gemini_wdt_ping, .ping = ftwdt010_wdt_ping,
.set_timeout = gemini_wdt_set_timeout, .set_timeout = ftwdt010_wdt_set_timeout,
.owner = THIS_MODULE, .owner = THIS_MODULE,
}; };
static const struct watchdog_info gemini_wdt_info = { static const struct watchdog_info ftwdt010_wdt_info = {
.options = WDIOF_KEEPALIVEPING .options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE | WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT, | WDIOF_SETTIMEOUT,
...@@ -115,11 +121,11 @@ static const struct watchdog_info gemini_wdt_info = { ...@@ -115,11 +121,11 @@ static const struct watchdog_info gemini_wdt_info = {
}; };
static int gemini_wdt_probe(struct platform_device *pdev) static int ftwdt010_wdt_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct resource *res; struct resource *res;
struct gemini_wdt *gwdt; struct ftwdt010_wdt *gwdt;
unsigned int reg; unsigned int reg;
int irq; int irq;
int ret; int ret;
...@@ -133,13 +139,9 @@ static int gemini_wdt_probe(struct platform_device *pdev) ...@@ -133,13 +139,9 @@ static int gemini_wdt_probe(struct platform_device *pdev)
if (IS_ERR(gwdt->base)) if (IS_ERR(gwdt->base))
return PTR_ERR(gwdt->base); return PTR_ERR(gwdt->base);
irq = platform_get_irq(pdev, 0);
if (!irq)
return -EINVAL;
gwdt->dev = dev; gwdt->dev = dev;
gwdt->wdd.info = &gemini_wdt_info; gwdt->wdd.info = &ftwdt010_wdt_info;
gwdt->wdd.ops = &gemini_wdt_ops; gwdt->wdd.ops = &ftwdt010_wdt_ops;
gwdt->wdd.min_timeout = 1; gwdt->wdd.min_timeout = 1;
gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK; gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;
gwdt->wdd.parent = dev; gwdt->wdd.parent = dev;
...@@ -151,17 +153,21 @@ static int gemini_wdt_probe(struct platform_device *pdev) ...@@ -151,17 +153,21 @@ static int gemini_wdt_probe(struct platform_device *pdev)
gwdt->wdd.timeout = 13U; gwdt->wdd.timeout = 13U;
watchdog_init_timeout(&gwdt->wdd, 0, dev); watchdog_init_timeout(&gwdt->wdd, 0, dev);
reg = readw(gwdt->base + GEMINI_WDCR); reg = readw(gwdt->base + FTWDT010_WDCR);
if (reg & WDCR_ENABLE) { if (reg & WDCR_ENABLE) {
/* Watchdog was enabled by the bootloader, disable it. */ /* Watchdog was enabled by the bootloader, disable it. */
reg &= ~WDCR_ENABLE; reg &= ~WDCR_ENABLE;
writel(reg, gwdt->base + GEMINI_WDCR); writel(reg, gwdt->base + FTWDT010_WDCR);
} }
ret = devm_request_irq(dev, irq, gemini_wdt_interrupt, 0, irq = platform_get_irq(pdev, 0);
"watchdog bark", gwdt); if (irq) {
if (ret) ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0,
return ret; "watchdog bark", gwdt);
if (ret)
return ret;
gwdt->has_irq = true;
}
ret = devm_watchdog_register_device(dev, &gwdt->wdd); ret = devm_watchdog_register_device(dev, &gwdt->wdd);
if (ret) { if (ret) {
...@@ -171,59 +177,60 @@ static int gemini_wdt_probe(struct platform_device *pdev) ...@@ -171,59 +177,60 @@ static int gemini_wdt_probe(struct platform_device *pdev)
/* Set up platform driver data */ /* Set up platform driver data */
platform_set_drvdata(pdev, gwdt); platform_set_drvdata(pdev, gwdt);
dev_info(dev, "Gemini watchdog driver enabled\n"); dev_info(dev, "FTWDT010 watchdog driver enabled\n");
return 0; return 0;
} }
static int __maybe_unused gemini_wdt_suspend(struct device *dev) static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev)
{ {
struct gemini_wdt *gwdt = dev_get_drvdata(dev); struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
unsigned int reg; unsigned int reg;
reg = readw(gwdt->base + GEMINI_WDCR); reg = readw(gwdt->base + FTWDT010_WDCR);
reg &= ~WDCR_ENABLE; reg &= ~WDCR_ENABLE;
writel(reg, gwdt->base + GEMINI_WDCR); writel(reg, gwdt->base + FTWDT010_WDCR);
return 0; return 0;
} }
static int __maybe_unused gemini_wdt_resume(struct device *dev) static int __maybe_unused ftwdt010_wdt_resume(struct device *dev)
{ {
struct gemini_wdt *gwdt = dev_get_drvdata(dev); struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
unsigned int reg; unsigned int reg;
if (watchdog_active(&gwdt->wdd)) { if (watchdog_active(&gwdt->wdd)) {
reg = readw(gwdt->base + GEMINI_WDCR); reg = readw(gwdt->base + FTWDT010_WDCR);
reg |= WDCR_ENABLE; reg |= WDCR_ENABLE;
writel(reg, gwdt->base + GEMINI_WDCR); writel(reg, gwdt->base + FTWDT010_WDCR);
} }
return 0; return 0;
} }
static const struct dev_pm_ops gemini_wdt_dev_pm_ops = { static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(gemini_wdt_suspend, SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend,
gemini_wdt_resume) ftwdt010_wdt_resume)
}; };
#ifdef CONFIG_OF #ifdef CONFIG_OF
static const struct of_device_id gemini_wdt_match[] = { static const struct of_device_id ftwdt010_wdt_match[] = {
{ .compatible = "faraday,ftwdt010" },
{ .compatible = "cortina,gemini-watchdog" }, { .compatible = "cortina,gemini-watchdog" },
{}, {},
}; };
MODULE_DEVICE_TABLE(of, gemini_wdt_match); MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match);
#endif #endif
static struct platform_driver gemini_wdt_driver = { static struct platform_driver ftwdt010_wdt_driver = {
.probe = gemini_wdt_probe, .probe = ftwdt010_wdt_probe,
.driver = { .driver = {
.name = "gemini-wdt", .name = "ftwdt010-wdt",
.of_match_table = of_match_ptr(gemini_wdt_match), .of_match_table = of_match_ptr(ftwdt010_wdt_match),
.pm = &gemini_wdt_dev_pm_ops, .pm = &ftwdt010_wdt_dev_pm_ops,
}, },
}; };
module_platform_driver(gemini_wdt_driver); module_platform_driver(ftwdt010_wdt_driver);
MODULE_AUTHOR("Linus Walleij"); MODULE_AUTHOR("Linus Walleij");
MODULE_DESCRIPTION("Watchdog driver for Gemini"); MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/module.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/platform_device.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
...@@ -25,8 +26,7 @@ enum { ...@@ -25,8 +26,7 @@ enum {
}; };
struct gpio_wdt_priv { struct gpio_wdt_priv {
int gpio; struct gpio_desc *gpiod;
bool active_low;
bool state; bool state;
bool always_running; bool always_running;
unsigned int hw_algo; unsigned int hw_algo;
...@@ -35,11 +35,12 @@ struct gpio_wdt_priv { ...@@ -35,11 +35,12 @@ struct gpio_wdt_priv {
static void gpio_wdt_disable(struct gpio_wdt_priv *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 */ /* Put GPIO back to tristate */
if (priv->hw_algo == HW_ALGO_TOGGLE) 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) static int gpio_wdt_ping(struct watchdog_device *wdd)
...@@ -50,13 +51,13 @@ 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: case HW_ALGO_TOGGLE:
/* Toggle output pin */ /* Toggle output pin */
priv->state = !priv->state; priv->state = !priv->state;
gpio_set_value_cansleep(priv->gpio, priv->state); gpiod_set_value_cansleep(priv->gpiod, priv->state);
break; break;
case HW_ALGO_LEVEL: case HW_ALGO_LEVEL:
/* Pulse */ /* Pulse */
gpio_set_value_cansleep(priv->gpio, !priv->active_low); gpiod_set_value_cansleep(priv->gpiod, 1);
udelay(1); udelay(1);
gpio_set_value_cansleep(priv->gpio, priv->active_low); gpiod_set_value_cansleep(priv->gpiod, 0);
break; break;
} }
return 0; return 0;
...@@ -66,8 +67,8 @@ static int gpio_wdt_start(struct watchdog_device *wdd) ...@@ -66,8 +67,8 @@ static int gpio_wdt_start(struct watchdog_device *wdd)
{ {
struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
priv->state = priv->active_low; priv->state = 0;
gpio_direction_output(priv->gpio, priv->state); gpiod_direction_output(priv->gpiod, priv->state);
set_bit(WDOG_HW_RUNNING, &wdd->status); set_bit(WDOG_HW_RUNNING, &wdd->status);
...@@ -80,7 +81,8 @@ static int gpio_wdt_stop(struct watchdog_device *wdd) ...@@ -80,7 +81,8 @@ static int gpio_wdt_stop(struct watchdog_device *wdd)
if (!priv->always_running) { if (!priv->always_running) {
gpio_wdt_disable(priv); gpio_wdt_disable(priv);
clear_bit(WDOG_HW_RUNNING, &wdd->status); } else {
set_bit(WDOG_HW_RUNNING, &wdd->status);
} }
return 0; return 0;
...@@ -101,44 +103,38 @@ static const struct watchdog_ops gpio_wdt_ops = { ...@@ -101,44 +103,38 @@ static const struct watchdog_ops gpio_wdt_ops = {
static int gpio_wdt_probe(struct platform_device *pdev) 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; struct gpio_wdt_priv *priv;
enum of_gpio_flags flags; enum gpiod_flags gflags;
unsigned int hw_margin; unsigned int hw_margin;
unsigned long f = 0;
const char *algo; const char *algo;
int ret; int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) if (!priv)
return -ENOMEM; return -ENOMEM;
platform_set_drvdata(pdev, priv); platform_set_drvdata(pdev, priv);
priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); ret = of_property_read_string(np, "hw_algo", &algo);
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);
if (ret) if (ret)
return ret; return ret;
if (!strcmp(algo, "toggle")) { if (!strcmp(algo, "toggle")) {
priv->hw_algo = HW_ALGO_TOGGLE; priv->hw_algo = HW_ALGO_TOGGLE;
f = GPIOF_IN; gflags = GPIOD_IN;
} else if (!strcmp(algo, "level")) { } else if (!strcmp(algo, "level")) {
priv->hw_algo = HW_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 { } else {
return -EINVAL; return -EINVAL;
} }
ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
dev_name(&pdev->dev)); if (IS_ERR(priv->gpiod))
if (ret) return PTR_ERR(priv->gpiod);
return ret;
ret = of_property_read_u32(pdev->dev.of_node, ret = of_property_read_u32(np,
"hw_margin_ms", &hw_margin); "hw_margin_ms", &hw_margin);
if (ret) if (ret)
return ret; return ret;
...@@ -146,7 +142,7 @@ static int gpio_wdt_probe(struct platform_device *pdev) ...@@ -146,7 +142,7 @@ static int gpio_wdt_probe(struct platform_device *pdev)
if (hw_margin < 2 || hw_margin > 65535) if (hw_margin < 2 || hw_margin > 65535)
return -EINVAL; return -EINVAL;
priv->always_running = of_property_read_bool(pdev->dev.of_node, priv->always_running = of_property_read_bool(np,
"always-running"); "always-running");
watchdog_set_drvdata(&priv->wdd, priv); watchdog_set_drvdata(&priv->wdd, priv);
...@@ -155,9 +151,9 @@ static int gpio_wdt_probe(struct platform_device *pdev) ...@@ -155,9 +151,9 @@ static int gpio_wdt_probe(struct platform_device *pdev)
priv->wdd.ops = &gpio_wdt_ops; priv->wdd.ops = &gpio_wdt_ops;
priv->wdd.min_timeout = SOFT_TIMEOUT_MIN; priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
priv->wdd.max_hw_heartbeat_ms = hw_margin; 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; priv->wdd.timeout = SOFT_TIMEOUT_DEF;
watchdog_stop_on_reboot(&priv->wdd); watchdog_stop_on_reboot(&priv->wdd);
......
...@@ -52,6 +52,7 @@ static char expect_release; ...@@ -52,6 +52,7 @@ static char expect_release;
static unsigned long hpwdt_is_open; static unsigned long hpwdt_is_open;
static void __iomem *pci_mem_addr; /* the PCI-memory address */ 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_reg;
static unsigned long __iomem *hpwdt_timer_con; static unsigned long __iomem *hpwdt_timer_con;
...@@ -475,6 +476,11 @@ static int hpwdt_time_left(void) ...@@ -475,6 +476,11 @@ static int hpwdt_time_left(void)
} }
#ifdef CONFIG_HPWDT_NMI_DECODING #ifdef CONFIG_HPWDT_NMI_DECODING
static int hpwdt_my_nmi(void)
{
return ioread8(hpwdt_nmistat) & 0x6;
}
/* /*
* NMI Handler * NMI Handler
*/ */
...@@ -486,6 +492,9 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) ...@@ -486,6 +492,9 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs)
if (!hpwdt_nmi_decoding) if (!hpwdt_nmi_decoding)
return NMI_DONE; return NMI_DONE;
if ((ulReason == NMI_UNKNOWN) && !hpwdt_my_nmi())
return NMI_DONE;
spin_lock_irqsave(&rom_lock, rom_pl); spin_lock_irqsave(&rom_lock, rom_pl);
if (!die_nmi_called && !is_icru && !is_uefi) if (!die_nmi_called && !is_icru && !is_uefi)
asminline_call(&cmn_regs, cru_rom_addr); asminline_call(&cmn_regs, cru_rom_addr);
...@@ -700,7 +709,7 @@ static void dmi_find_icru(const struct dmi_header *dm, void *dummy) ...@@ -700,7 +709,7 @@ static void dmi_find_icru(const struct dmi_header *dm, void *dummy)
smbios_proliant_ptr = (struct smbios_proliant_info *) dm; smbios_proliant_ptr = (struct smbios_proliant_info *) dm;
if (smbios_proliant_ptr->misc_features & 0x01) if (smbios_proliant_ptr->misc_features & 0x01)
is_icru = 1; is_icru = 1;
if (smbios_proliant_ptr->misc_features & 0x408) if (smbios_proliant_ptr->misc_features & 0x1400)
is_uefi = 1; is_uefi = 1;
} }
} }
...@@ -842,6 +851,7 @@ static int hpwdt_init_one(struct pci_dev *dev, ...@@ -842,6 +851,7 @@ static int hpwdt_init_one(struct pci_dev *dev,
retval = -ENOMEM; retval = -ENOMEM;
goto error_pci_iomap; goto error_pci_iomap;
} }
hpwdt_nmistat = pci_mem_addr + 0x6e;
hpwdt_timer_reg = pci_mem_addr + 0x70; hpwdt_timer_reg = pci_mem_addr + 0x70;
hpwdt_timer_con = pci_mem_addr + 0x72; hpwdt_timer_con = pci_mem_addr + 0x72;
......
...@@ -21,14 +21,15 @@ ...@@ -21,14 +21,15 @@
* Version 0.02 * Version 0.02
* 20050210 David Härdeman <david@2gen.com> * 20050210 David Härdeman <david@2gen.com>
* Ported driver to kernel 2.6 * 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, ... * Includes, defines, variables, module parameters, ...
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h> #include <linux/module.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -42,19 +43,17 @@ ...@@ -42,19 +43,17 @@
#include <linux/io.h> #include <linux/io.h>
/* Module and version information */ /* Module and version information */
#define ESB_VERSION "0.05"
#define ESB_MODULE_NAME "i6300ESB timer" #define ESB_MODULE_NAME "i6300ESB timer"
#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION
/* PCI configuration registers */ /* PCI configuration registers */
#define ESB_CONFIG_REG 0x60 /* Config register */ #define ESB_CONFIG_REG 0x60 /* Config register */
#define ESB_LOCK_REG 0x68 /* WDT lock register */ #define ESB_LOCK_REG 0x68 /* WDT lock register */
/* Memory mapped registers */ /* Memory mapped registers */
#define ESB_TIMER1_REG (BASEADDR + 0x00)/* Timer1 value after each reset */ #define ESB_TIMER1_REG(w) ((w)->base + 0x00)/* Timer1 value after each reset */
#define ESB_TIMER2_REG (BASEADDR + 0x04)/* Timer2 value after each reset */ #define ESB_TIMER2_REG(w) ((w)->base + 0x04)/* Timer2 value after each reset */
#define ESB_GINTSR_REG (BASEADDR + 0x08)/* General Interrupt Status Register */ #define ESB_GINTSR_REG(w) ((w)->base + 0x08)/* General Interrupt Status Reg */
#define ESB_RELOAD_REG (BASEADDR + 0x0c)/* Reload register */ #define ESB_RELOAD_REG(w) ((w)->base + 0x0c)/* Reload register */
/* Lock register bits */ /* Lock register bits */
#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */ #define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
...@@ -74,25 +73,18 @@ ...@@ -74,25 +73,18 @@
#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ #define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
#define ESB_UNLOCK2 0x86 /* Step 2 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 */ /* module parameters */
/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */ /* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
#define WATCHDOG_HEARTBEAT 30 #define ESB_HEARTBEAT_MIN 1
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ #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_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, MODULE_PARM_DESC(heartbeat,
"Watchdog heartbeat in seconds. (1<heartbeat<2046, default=" "Watchdog heartbeat in seconds. (" ESB_HEARTBEAT_RANGE
__MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); ", default=" __MODULE_STRING(ESB_HEARTBEAT_DEFAULT) ")");
static bool nowayout = WATCHDOG_NOWAYOUT; static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0); module_param(nowayout, bool, 0);
...@@ -100,6 +92,15 @@ MODULE_PARM_DESC(nowayout, ...@@ -100,6 +92,15 @@ MODULE_PARM_DESC(nowayout,
"Watchdog cannot be stopped once started (default=" "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); __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 * Some i6300ESB specific functions
*/ */
...@@ -110,61 +111,58 @@ MODULE_PARM_DESC(nowayout, ...@@ -110,61 +111,58 @@ MODULE_PARM_DESC(nowayout,
* reload register. After this the appropriate registers can be written * reload register. After this the appropriate registers can be written
* to once before they need to be unlocked again. * 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_UNLOCK1, ESB_RELOAD_REG(edev));
writew(ESB_UNLOCK2, ESB_RELOAD_REG); 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; u8 val;
spin_lock(&esb_lock); esb_unlock_registers(edev);
esb_unlock_registers(); writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG);
/* Enable or Enable + Lock? */ /* Enable or Enable + Lock? */
val = ESB_WDT_ENABLE | (nowayout ? ESB_WDT_LOCK : 0x00); val = ESB_WDT_ENABLE | (_wdd_nowayout ? ESB_WDT_LOCK : 0x00);
pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); pci_write_config_byte(edev->pdev, ESB_LOCK_REG, val);
spin_unlock(&esb_lock);
return 0; 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; u8 val;
spin_lock(&esb_lock);
/* First, reset timers as suggested by the docs */ /* First, reset timers as suggested by the docs */
esb_unlock_registers(); esb_unlock_registers(edev);
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
/* Then disable the WDT */ /* Then disable the WDT */
pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x0); pci_write_config_byte(edev->pdev, ESB_LOCK_REG, 0x0);
pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val); pci_read_config_byte(edev->pdev, ESB_LOCK_REG, &val);
spin_unlock(&esb_lock);
/* Returns 0 if the timer was disabled, non-zero otherwise */ /* Returns 0 if the timer was disabled, non-zero otherwise */
return val & ESB_WDT_ENABLE; return val & ESB_WDT_ENABLE;
} }
static void esb_timer_keepalive(void) static int esb_timer_keepalive(struct watchdog_device *wdd)
{ {
spin_lock(&esb_lock); struct esb_dev *edev = to_esb_dev(wdd);
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 anything here? */ /* 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; 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, /* 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 * val will be 1 << 9 = 512, then write that to two
* timers => 2 * 512 = 1024 (which is decremented at 1KHz) * timers => 2 * 512 = 1024 (which is decremented at 1KHz)
...@@ -172,162 +170,39 @@ static int esb_timer_set_heartbeat(int time) ...@@ -172,162 +170,39 @@ static int esb_timer_set_heartbeat(int time)
val = time << 9; val = time << 9;
/* Write timer 1 */ /* Write timer 1 */
esb_unlock_registers(); esb_unlock_registers(edev);
writel(val, ESB_TIMER1_REG); writel(val, ESB_TIMER1_REG(edev));
/* Write timer 2 */ /* Write timer 2 */
esb_unlock_registers(); esb_unlock_registers(edev);
writel(val, ESB_TIMER2_REG); writel(val, ESB_TIMER2_REG(edev));
/* Reload */ /* Reload */
esb_unlock_registers(); esb_unlock_registers(edev);
writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
/* FIXME: Do we need to flush everything out? */ /* FIXME: Do we need to flush everything out? */
/* Done */ /* Done */
heartbeat = time; wdd->timeout = time;
spin_unlock(&esb_lock);
return 0;
}
/*
* /dev/watchdog handling
*/
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; 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 * Watchdog Subsystem Interfaces
*/ */
static const struct file_operations esb_fops = { static struct watchdog_info esb_info = {
.owner = THIS_MODULE, .identity = ESB_MODULE_NAME,
.llseek = no_llseek, .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.write = esb_write,
.unlocked_ioctl = esb_ioctl,
.open = esb_open,
.release = esb_release,
}; };
static struct miscdevice esb_miscdev = { static const struct watchdog_ops esb_ops = {
.minor = WATCHDOG_MINOR, .owner = THIS_MODULE,
.name = "watchdog", .start = esb_timer_start,
.fops = &esb_fops, .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); ...@@ -343,38 +218,38 @@ MODULE_DEVICE_TABLE(pci, esb_pci_tbl);
* Init & exit routines * 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)) { if (pci_enable_device(edev->pdev)) {
pr_err("failed to enable device\n"); dev_err(&edev->pdev->dev, "failed to enable device\n");
goto err_devput; goto err_devput;
} }
if (pci_request_region(pdev, 0, ESB_MODULE_NAME)) { if (pci_request_region(edev->pdev, 0, ESB_MODULE_NAME)) {
pr_err("failed to request region\n"); dev_err(&edev->pdev->dev, "failed to request region\n");
goto err_disable; goto err_disable;
} }
BASEADDR = pci_ioremap_bar(pdev, 0); edev->base = pci_ioremap_bar(edev->pdev, 0);
if (BASEADDR == NULL) { if (edev->base == NULL) {
/* Something's wrong here, BASEADDR has to be set */ /* 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; goto err_release;
} }
/* Done */ /* Done */
esb_pci = pdev; dev_set_drvdata(&edev->pdev->dev, edev);
return 1; return 1;
err_release: err_release:
pci_release_region(pdev, 0); pci_release_region(edev->pdev, 0);
err_disable: err_disable:
pci_disable_device(pdev); pci_disable_device(edev->pdev);
err_devput: err_devput:
return 0; return 0;
} }
static void esb_initdevice(void) static void esb_initdevice(struct esb_dev *edev)
{ {
u8 val1; u8 val1;
u16 val2; u16 val2;
...@@ -391,96 +266,87 @@ static void esb_initdevice(void) ...@@ -391,96 +266,87 @@ static void esb_initdevice(void)
* any interrupts as there is not much we can do with it * any interrupts as there is not much we can do with it
* right now. * 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 */ /* 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) 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 */ /* 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 */ /* Check if the watchdog was previously triggered */
esb_unlock_registers(); esb_unlock_registers(edev);
val2 = readw(ESB_RELOAD_REG); val2 = readw(ESB_RELOAD_REG(edev));
if (val2 & ESB_WDT_TIMEOUT) if (val2 & ESB_WDT_TIMEOUT)
triggered = WDIOF_CARDRESET; edev->wdd.bootstatus = WDIOF_CARDRESET;
/* Reset WDT_TIMEOUT flag and timers */ /* Reset WDT_TIMEOUT flag and timers */
esb_unlock_registers(); esb_unlock_registers(edev);
writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG); writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG(edev));
/* And set the correct timeout value */ /* 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, static int esb_probe(struct pci_dev *pdev,
const struct pci_device_id *ent) const struct pci_device_id *ent)
{ {
struct esb_dev *edev;
int ret; int ret;
cards_found++; edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL);
if (cards_found == 1) if (!edev)
pr_info("Intel 6300ESB WatchDog Timer Driver v%s\n", return -ENOMEM;
ESB_VERSION);
if (cards_found > 1) {
pr_err("This driver only supports 1 device\n");
return -ENODEV;
}
/* Check whether or not the hardware watchdog is there */ /* 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; 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 */ /* 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 */ /* Register the watchdog so that userspace has access to it */
ret = misc_register(&esb_miscdev); ret = watchdog_register_device(&edev->wdd);
if (ret != 0) { if (ret != 0) {
pr_err("cannot register miscdev on minor=%d (err=%d)\n", dev_err(&pdev->dev,
WATCHDOG_MINOR, ret); "cannot register watchdog device (err=%d)\n", ret);
goto err_unmap; goto err_unmap;
} }
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", dev_info(&pdev->dev,
BASEADDR, heartbeat, nowayout); "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
edev->base, edev->wdd.timeout, nowayout);
return 0; return 0;
err_unmap: err_unmap:
iounmap(BASEADDR); iounmap(edev->base);
pci_release_region(esb_pci, 0); pci_release_region(edev->pdev, 0);
pci_disable_device(esb_pci); pci_disable_device(edev->pdev);
esb_pci = NULL;
return ret; return ret;
} }
static void esb_remove(struct pci_dev *pdev) static void esb_remove(struct pci_dev *pdev)
{ {
/* Stop the timer before we leave */ struct esb_dev *edev = dev_get_drvdata(&pdev->dev);
if (!nowayout)
esb_timer_stop();
/* 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) watchdog_unregister_device(&edev->wdd);
{ iounmap(edev->base);
esb_timer_stop(); pci_release_region(edev->pdev, 0);
pci_disable_device(edev->pdev);
} }
static struct pci_driver esb_driver = { static struct pci_driver esb_driver = {
...@@ -488,7 +354,6 @@ static struct pci_driver esb_driver = { ...@@ -488,7 +354,6 @@ static struct pci_driver esb_driver = {
.id_table = esb_pci_tbl, .id_table = esb_pci_tbl,
.probe = esb_probe, .probe = esb_probe,
.remove = esb_remove, .remove = esb_remove,
.shutdown = esb_shutdown,
}; };
module_pci_driver(esb_driver); module_pci_driver(esb_driver);
......
...@@ -218,7 +218,7 @@ static long ibwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) ...@@ -218,7 +218,7 @@ static long ibwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
if (ibwdt_set_heartbeat(new_margin)) if (ibwdt_set_heartbeat(new_margin))
return -EINVAL; return -EINVAL;
ibwdt_ping(); ibwdt_ping();
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(timeout, p); return put_user(timeout, p);
......
...@@ -169,15 +169,21 @@ static int imx2_wdt_ping(struct watchdog_device *wdog) ...@@ -169,15 +169,21 @@ static int imx2_wdt_ping(struct watchdog_device *wdog)
return 0; return 0;
} }
static int imx2_wdt_set_timeout(struct watchdog_device *wdog, static void __imx2_wdt_set_timeout(struct watchdog_device *wdog,
unsigned int new_timeout) unsigned int new_timeout)
{ {
struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); 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, regmap_update_bits(wdev->regmap, IMX2_WDT_WCR, IMX2_WDT_WCR_WT,
WDOG_SEC_TO_COUNT(new_timeout)); 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; return 0;
} }
...@@ -371,7 +377,11 @@ static int imx2_wdt_suspend(struct device *dev) ...@@ -371,7 +377,11 @@ static int imx2_wdt_suspend(struct device *dev)
/* The watchdog IP block is running */ /* The watchdog IP block is running */
if (imx2_wdt_is_running(wdev)) { 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); imx2_wdt_ping(wdog);
} }
......
...@@ -146,6 +146,7 @@ static const struct watchdog_ops jz4740_wdt_ops = { ...@@ -146,6 +146,7 @@ static const struct watchdog_ops jz4740_wdt_ops = {
#ifdef CONFIG_OF #ifdef CONFIG_OF
static const struct of_device_id jz4740_wdt_of_matches[] = { static const struct of_device_id jz4740_wdt_of_matches[] = {
{ .compatible = "ingenic,jz4740-watchdog", }, { .compatible = "ingenic,jz4740-watchdog", },
{ .compatible = "ingenic,jz4780-watchdog", },
{ /* sentinel */ } { /* sentinel */ }
}; };
MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 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, ...@@ -526,12 +526,11 @@ static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf,
size_t cnt, loff_t *ppos) size_t cnt, loff_t *ppos)
{ {
struct mei_wdt *wdt = file->private_data; struct mei_wdt *wdt = file->private_data;
const size_t bufsz = 32; char buf[32];
char buf[bufsz];
ssize_t pos; ssize_t pos;
pos = scnprintf(buf, bufsz, "state: %s\n", pos = scnprintf(buf, sizeof(buf), "state: %s\n",
mei_wdt_state_str(wdt->state)); mei_wdt_state_str(wdt->state));
return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos); return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
} }
......
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/of_address.h> #include <linux/of_address.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/module.h> #include <linux/module.h>
...@@ -31,10 +30,13 @@ ...@@ -31,10 +30,13 @@
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <sysdev/fsl_soc.h> #include <sysdev/fsl_soc.h>
#define WATCHDOG_TIMEOUT 10
struct mpc8xxx_wdt { struct mpc8xxx_wdt {
__be32 res0; __be32 res0;
__be32 swcrr; /* System watchdog control register */ __be32 swcrr; /* System watchdog control register */
#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ #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_SWEN 0x00000004 /* Watchdog Enable bit. */
#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/ #define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */ #define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
...@@ -52,14 +54,15 @@ struct mpc8xxx_wdt_type { ...@@ -52,14 +54,15 @@ struct mpc8xxx_wdt_type {
struct mpc8xxx_wdt_ddata { struct mpc8xxx_wdt_ddata {
struct mpc8xxx_wdt __iomem *base; struct mpc8xxx_wdt __iomem *base;
struct watchdog_device wdd; struct watchdog_device wdd;
struct timer_list timer;
spinlock_t lock; spinlock_t lock;
u16 swtc;
}; };
static u16 timeout = 0xffff; static u16 timeout;
module_param(timeout, ushort, 0); module_param(timeout, ushort, 0);
MODULE_PARM_DESC(timeout, 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; static bool reset = 1;
module_param(reset, bool, 0); module_param(reset, bool, 0);
...@@ -80,31 +83,27 @@ static void mpc8xxx_wdt_keepalive(struct mpc8xxx_wdt_ddata *ddata) ...@@ -80,31 +83,27 @@ static void mpc8xxx_wdt_keepalive(struct mpc8xxx_wdt_ddata *ddata)
spin_unlock(&ddata->lock); 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) static int mpc8xxx_wdt_start(struct watchdog_device *w)
{ {
struct mpc8xxx_wdt_ddata *ddata = struct mpc8xxx_wdt_ddata *ddata =
container_of(w, struct mpc8xxx_wdt_ddata, wdd); container_of(w, struct mpc8xxx_wdt_ddata, wdd);
u32 tmp = in_be32(&ddata->base->swcrr);
u32 tmp = SWCRR_SWEN | SWCRR_SWPR;
/* Good, fire up the show */ /* 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) if (reset)
tmp |= SWCRR_SWRI; tmp |= SWCRR_SWRI;
tmp |= timeout << 16;
out_be32(&ddata->base->swcrr, tmp); 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; return 0;
} }
...@@ -118,17 +117,8 @@ static int mpc8xxx_wdt_ping(struct watchdog_device *w) ...@@ -118,17 +117,8 @@ static int mpc8xxx_wdt_ping(struct watchdog_device *w)
return 0; 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 = { static struct watchdog_info mpc8xxx_wdt_info = {
.options = WDIOF_KEEPALIVEPING, .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT,
.firmware_version = 1, .firmware_version = 1,
.identity = "MPC8xxx", .identity = "MPC8xxx",
}; };
...@@ -137,7 +127,6 @@ static struct watchdog_ops mpc8xxx_wdt_ops = { ...@@ -137,7 +127,6 @@ static struct watchdog_ops mpc8xxx_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.start = mpc8xxx_wdt_start, .start = mpc8xxx_wdt_start,
.ping = mpc8xxx_wdt_ping, .ping = mpc8xxx_wdt_ping,
.stop = mpc8xxx_wdt_stop,
}; };
static int mpc8xxx_wdt_probe(struct platform_device *ofdev) static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
...@@ -148,7 +137,6 @@ 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; struct mpc8xxx_wdt_ddata *ddata;
u32 freq = fsl_get_sys_freq(); u32 freq = fsl_get_sys_freq();
bool enabled; bool enabled;
unsigned int timeout_sec;
wdt_type = of_device_get_match_data(&ofdev->dev); wdt_type = of_device_get_match_data(&ofdev->dev);
if (!wdt_type) if (!wdt_type)
...@@ -173,26 +161,17 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev) ...@@ -173,26 +161,17 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
} }
spin_lock_init(&ddata->lock); spin_lock_init(&ddata->lock);
timer_setup(&ddata->timer, mpc8xxx_wdt_timer_ping, 0);
ddata->wdd.info = &mpc8xxx_wdt_info, ddata->wdd.info = &mpc8xxx_wdt_info,
ddata->wdd.ops = &mpc8xxx_wdt_ops, ddata->wdd.ops = &mpc8xxx_wdt_ops,
/* Calculate the timeout in seconds */ ddata->wdd.timeout = WATCHDOG_TIMEOUT;
timeout_sec = (timeout * wdt_type->prescaler) / freq; watchdog_init_timeout(&ddata->wdd, timeout, &ofdev->dev);
ddata->wdd.timeout = timeout_sec;
watchdog_set_nowayout(&ddata->wdd, nowayout); watchdog_set_nowayout(&ddata->wdd, nowayout);
ret = watchdog_register_device(&ddata->wdd); ddata->swtc = min(ddata->wdd.timeout * freq / wdt_type->prescaler,
if (ret) { 0xffffU);
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);
/* /*
* If the watchdog was previously enabled or we're running on * If the watchdog was previously enabled or we're running on
...@@ -200,7 +179,22 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev) ...@@ -200,7 +179,22 @@ static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
* userspace handles it. * userspace handles it.
*/ */
if (enabled) 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); platform_set_drvdata(ofdev, ddata);
return 0; return 0;
...@@ -212,7 +206,6 @@ static int mpc8xxx_wdt_remove(struct platform_device *ofdev) ...@@ -212,7 +206,6 @@ static int mpc8xxx_wdt_remove(struct platform_device *ofdev)
pr_crit("Watchdog removed, expect the %s soon!\n", pr_crit("Watchdog removed, expect the %s soon!\n",
reset ? "reset" : "machine check exception"); reset ? "reset" : "machine check exception");
del_timer_sync(&ddata->timer);
watchdog_unregister_device(&ddata->wdd); watchdog_unregister_device(&ddata->wdd);
return 0; return 0;
......
...@@ -105,6 +105,11 @@ static int mt7621_wdt_bootcause(void) ...@@ -105,6 +105,11 @@ static int mt7621_wdt_bootcause(void)
return 0; 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 = { static const struct watchdog_info mt7621_wdt_info = {
.identity = "Mediatek Watchdog", .identity = "Mediatek Watchdog",
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
...@@ -128,7 +133,6 @@ static struct watchdog_device mt7621_wdt_dev = { ...@@ -128,7 +133,6 @@ static struct watchdog_device mt7621_wdt_dev = {
static int mt7621_wdt_probe(struct platform_device *pdev) static int mt7621_wdt_probe(struct platform_device *pdev)
{ {
struct resource *res; struct resource *res;
int ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mt7621_wdt_base = devm_ioremap_resource(&pdev->dev, res); mt7621_wdt_base = devm_ioremap_resource(&pdev->dev, res);
...@@ -144,17 +148,22 @@ static int mt7621_wdt_probe(struct platform_device *pdev) ...@@ -144,17 +148,22 @@ static int mt7621_wdt_probe(struct platform_device *pdev)
watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout, watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout,
&pdev->dev); &pdev->dev);
watchdog_set_nowayout(&mt7621_wdt_dev, nowayout); watchdog_set_nowayout(&mt7621_wdt_dev, nowayout);
if (mt7621_wdt_is_running(&mt7621_wdt_dev)) {
ret = watchdog_register_device(&mt7621_wdt_dev); /*
* Make sure to apply timeout from watchdog core, taking
return 0; * the prescaler of this driver here into account (the
} * boot loader might be using a different prescaler).
*
static int mt7621_wdt_remove(struct platform_device *pdev) * To avoid spurious resets because of different scaling,
{ * we first disable the watchdog, set the new prescaler
watchdog_unregister_device(&mt7621_wdt_dev); * and timeout, and then re-enable the watchdog.
*/
return 0; mt7621_wdt_stop(&mt7621_wdt_dev);
mt7621_wdt_start(&mt7621_wdt_dev);
set_bit(WDOG_HW_RUNNING, &mt7621_wdt_dev.status);
}
return devm_watchdog_register_device(&pdev->dev, &mt7621_wdt_dev);
} }
static void mt7621_wdt_shutdown(struct platform_device *pdev) static void mt7621_wdt_shutdown(struct platform_device *pdev)
...@@ -170,7 +179,6 @@ MODULE_DEVICE_TABLE(of, mt7621_wdt_match); ...@@ -170,7 +179,6 @@ MODULE_DEVICE_TABLE(of, mt7621_wdt_match);
static struct platform_driver mt7621_wdt_driver = { static struct platform_driver mt7621_wdt_driver = {
.probe = mt7621_wdt_probe, .probe = mt7621_wdt_probe,
.remove = mt7621_wdt_remove,
.shutdown = mt7621_wdt_shutdown, .shutdown = mt7621_wdt_shutdown,
.driver = { .driver = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
......
...@@ -576,7 +576,7 @@ static int orion_wdt_probe(struct platform_device *pdev) ...@@ -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 * Let's make sure the watchdog is fully stopped, unless it's
* explicitly enabled. This may be the case if the module was * 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. * set a running watchdog before booting the kernel.
*/ */
if (!orion_wdt_enabled(&dev->wdt)) if (!orion_wdt_enabled(&dev->wdt))
......
...@@ -545,8 +545,8 @@ static long pcipcwd_ioctl(struct file *file, unsigned int cmd, ...@@ -545,8 +545,8 @@ static long pcipcwd_ioctl(struct file *file, unsigned int cmd,
return -EINVAL; return -EINVAL;
pcipcwd_keepalive(); pcipcwd_keepalive();
/* Fall */
} }
/* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p); return put_user(heartbeat, p);
......
...@@ -49,12 +49,11 @@ ...@@ -49,12 +49,11 @@
#define DRIVER_VERSION "1.02" #define DRIVER_VERSION "1.02"
#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>" #define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
#define DRIVER_DESC "Berkshire USB-PC Watchdog driver" #define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
#define DRIVER_LICENSE "GPL"
#define DRIVER_NAME "pcwd_usb" #define DRIVER_NAME "pcwd_usb"
MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC); MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE); MODULE_LICENSE("GPL");
#define WATCHDOG_HEARTBEAT 0 /* default heartbeat = #define WATCHDOG_HEARTBEAT 0 /* default heartbeat =
delay-time from dip-switches */ delay-time from dip-switches */
...@@ -456,8 +455,8 @@ static long usb_pcwd_ioctl(struct file *file, unsigned int cmd, ...@@ -456,8 +455,8 @@ static long usb_pcwd_ioctl(struct file *file, unsigned int cmd,
return -EINVAL; return -EINVAL;
usb_pcwd_keepalive(usb_pcwd_device); usb_pcwd_keepalive(usb_pcwd_device);
/* Fall */
} }
/* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p); return put_user(heartbeat, p);
......
/*
* 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 @@ ...@@ -16,6 +16,11 @@
* See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide", * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
* AMD Publication 45482 "AMD SB800-Series Southbridges Register * AMD Publication 45482 "AMD SB800-Series Southbridges Register
* Reference Guide" * 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 @@ ...@@ -24,38 +29,36 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #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/module.h>
#include <linux/moduleparam.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/pci.h>
#include <linux/ioport.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/uaccess.h> #include <linux/types.h>
#include <linux/io.h> #include <linux/watchdog.h>
#include "sp5100_tco.h" #include "sp5100_tco.h"
/* Module and version information */ #define TCO_DRIVER_NAME "sp5100-tco"
#define TCO_VERSION "0.05"
#define TCO_MODULE_NAME "SP5100 TCO timer"
#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
/* internal variables */ /* internal variables */
static u32 tcobase_phys;
static u32 tco_wdt_fired; enum tco_reg_layout {
static void __iomem *tcobase; sp5100, sb800, efch
static unsigned int pm_iobase; };
static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
static unsigned long timer_alive; struct sp5100_tco {
static char tco_expect_close; struct watchdog_device wdd;
static struct pci_dev *sp5100_tco_pci; void __iomem *tcobase;
enum tco_reg_layout tco_reg_layout;
};
/* the watchdog platform device */ /* the watchdog platform device */
static struct platform_device *sp5100_tco_platform_device; static struct platform_device *sp5100_tco_platform_device;
/* the associated PCI device */
static struct pci_dev *sp5100_tco_pci;
/* module parameters */ /* module parameters */
...@@ -74,83 +77,105 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started." ...@@ -74,83 +77,105 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started."
* Some TCO specific functions * 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 && if (dev->vendor == PCI_VENDOR_ID_ATI &&
dev->revision < 0x40; 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; u32 val;
unsigned long flags;
spin_lock_irqsave(&tco_lock, flags); val = readl(SP5100_WDT_CONTROL(tco->tcobase));
val = readl(SP5100_WDT_CONTROL(tcobase));
val |= SP5100_WDT_START_STOP_BIT; val |= SP5100_WDT_START_STOP_BIT;
writel(val, SP5100_WDT_CONTROL(tcobase)); writel(val, SP5100_WDT_CONTROL(tco->tcobase));
spin_unlock_irqrestore(&tco_lock, flags);
return 0;
} }
static void tco_timer_stop(void) static int tco_timer_stop(struct watchdog_device *wdd)
{ {
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
u32 val; u32 val;
unsigned long flags;
spin_lock_irqsave(&tco_lock, flags); val = readl(SP5100_WDT_CONTROL(tco->tcobase));
val = readl(SP5100_WDT_CONTROL(tcobase));
val &= ~SP5100_WDT_START_STOP_BIT; val &= ~SP5100_WDT_START_STOP_BIT;
writel(val, SP5100_WDT_CONTROL(tcobase)); writel(val, SP5100_WDT_CONTROL(tco->tcobase));
spin_unlock_irqrestore(&tco_lock, flags);
return 0;
} }
static void tco_timer_keepalive(void) static int tco_timer_ping(struct watchdog_device *wdd)
{ {
struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
u32 val; u32 val;
unsigned long flags;
spin_lock_irqsave(&tco_lock, flags); val = readl(SP5100_WDT_CONTROL(tco->tcobase));
val = readl(SP5100_WDT_CONTROL(tcobase));
val |= SP5100_WDT_TRIGGER_BIT; val |= SP5100_WDT_TRIGGER_BIT;
writel(val, SP5100_WDT_CONTROL(tcobase)); writel(val, SP5100_WDT_CONTROL(tco->tcobase));
spin_unlock_irqrestore(&tco_lock, flags);
return 0;
} }
static int tco_timer_set_heartbeat(int t) static int tco_timer_set_timeout(struct watchdog_device *wdd,
unsigned int t)
{ {
unsigned long flags; struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
if (t < 0 || t > 0xffff)
return -EINVAL;
/* Write new heartbeat to watchdog */ /* Write new heartbeat to watchdog */
spin_lock_irqsave(&tco_lock, flags); writel(t, SP5100_WDT_COUNT(tco->tcobase));
writel(t, SP5100_WDT_COUNT(tcobase));
spin_unlock_irqrestore(&tco_lock, flags); wdd->timeout = t;
heartbeat = t;
return 0; return 0;
} }
static void tco_timer_enable(void) 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)
{ {
int val; u32 val;
if (!tco_has_sp5100_reg_layout(sp5100_tco_pci)) { switch (tco->tco_reg_layout) {
case sb800:
/* For SB800 or later */ /* For SB800 or later */
/* Set the Watchdog timer resolution to 1 sec */ /* Set the Watchdog timer resolution to 1 sec */
outb(SB800_PM_WATCHDOG_CONFIG, SB800_IO_PM_INDEX_REG); sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,
val = inb(SB800_IO_PM_DATA_REG); 0xff, SB800_PM_WATCHDOG_SECOND_RES);
val |= SB800_PM_WATCHDOG_SECOND_RES;
outb(val, SB800_IO_PM_DATA_REG);
/* Enable watchdog decode bit and watchdog timer */ /* Enable watchdog decode bit and watchdog timer */
outb(SB800_PM_WATCHDOG_CONTROL, SB800_IO_PM_INDEX_REG); sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,
val = inb(SB800_IO_PM_DATA_REG); ~SB800_PM_WATCHDOG_DISABLE,
val |= SB800_PCI_WATCHDOG_DECODE_EN; SB800_PCI_WATCHDOG_DECODE_EN);
val &= ~SB800_PM_WATCHDOG_DISABLE; break;
outb(val, SB800_IO_PM_DATA_REG); case sp5100:
} else {
/* For SP5100 or SB7x0 */ /* For SP5100 or SB7x0 */
/* Enable watchdog decode bit */ /* Enable watchdog decode bit */
pci_read_config_dword(sp5100_tco_pci, pci_read_config_dword(sp5100_tco_pci,
...@@ -164,409 +189,287 @@ static void tco_timer_enable(void) ...@@ -164,409 +189,287 @@ static void tco_timer_enable(void)
val); val);
/* Enable Watchdog timer and set the resolution to 1 sec */ /* Enable Watchdog timer and set the resolution to 1 sec */
outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,
val = inb(SP5100_IO_PM_DATA_REG); ~SP5100_PM_WATCHDOG_DISABLE,
val |= SP5100_PM_WATCHDOG_SECOND_RES; SP5100_PM_WATCHDOG_SECOND_RES);
val &= ~SP5100_PM_WATCHDOG_DISABLE; break;
outb(val, SP5100_IO_PM_DATA_REG); 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;
} }
} }
/* static u32 sp5100_tco_read_pm_reg32(u8 index)
* /dev/watchdog handling
*/
static int sp5100_tco_open(struct inode *inode, struct file *file)
{ {
/* /dev/watchdog can only be opened once */ u32 val = 0;
if (test_and_set_bit(0, &timer_alive)) int i;
return -EBUSY;
/* Reload and activate timer */
tco_timer_start();
tco_timer_keepalive();
return nonseekable_open(inode, file);
}
static int sp5100_tco_release(struct inode *inode, struct file *file)
{
/* Shut off the timer. */
if (tco_expect_close == 42) {
tco_timer_stop();
} else {
pr_crit("Unexpected close, not stopping watchdog!\n");
tco_timer_keepalive();
}
clear_bit(0, &timer_alive);
tco_expect_close = 0;
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 = 3; i >= 0; i--)
*/ val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);
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 */ return val;
tco_timer_keepalive();
}
return len;
} }
static long sp5100_tco_ioctl(struct file *file, unsigned int cmd, static int sp5100_tco_setupdevice(struct device *dev,
unsigned long arg) struct watchdog_device *wdd)
{ {
int new_options, retval = -EINVAL; struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
int new_heartbeat; const char *dev_name;
void __user *argp = (void __user *)arg; u32 mmio_addr = 0, val;
int __user *p = argp; int ret;
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,
};
/*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE. We do not actually
* register a pci_driver, because someone else might
* want to register another driver on the same PCI id.
*/
static const struct pci_device_id sp5100_tco_pci_tbl[] = {
{ PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ 0, }, /* End of list */
};
MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
/*
* Init & exit routines
*/
static unsigned char sp5100_tco_setupdevice(void)
{
struct pci_dev *dev = NULL;
const char *dev_name = NULL;
u32 val;
u32 index_reg, data_reg, base_addr;
/* Match the PCI device */ /* Request the IO ports used by this driver */
for_each_pci_dev(dev) { if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,
if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) { SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) {
sp5100_tco_pci = dev; dev_err(dev, "I/O address 0x%04x already in use\n",
break; SP5100_IO_PM_INDEX_REG);
} return -EBUSY;
} }
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. * Determine type of southbridge chipset.
*/ */
if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) { switch (tco->tco_reg_layout) {
case sp5100:
dev_name = SP5100_DEVNAME; dev_name = SP5100_DEVNAME;
index_reg = SP5100_IO_PM_INDEX_REG; mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) &
data_reg = SP5100_IO_PM_DATA_REG; 0xfffffff8;
base_addr = SP5100_PM_WATCHDOG_BASE; break;
} else { case sb800:
dev_name = SB800_DEVNAME; dev_name = SB800_DEVNAME;
index_reg = SB800_IO_PM_INDEX_REG; mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) &
data_reg = SB800_IO_PM_DATA_REG; 0xfffffff8;
base_addr = SB800_PM_WATCHDOG_BASE; break;
} case efch:
dev_name = SB800_DEVNAME;
/* Request the IO ports used by this driver */ val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);
pm_iobase = SP5100_IO_PM_INDEX_REG; if (val & EFCH_PM_DECODEEN_WDT_TMREN)
if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, dev_name)) { mmio_addr = EFCH_PM_WDT_ADDR;
pr_err("I/O address 0x%04x already in use\n", pm_iobase); break;
goto exit; default:
return -ENODEV;
} }
/*
* 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 */ /* Check MMIO address conflict */
if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, if (!mmio_addr ||
dev_name)) !devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE,
goto setup_wdt; dev_name)) {
else if (mmio_addr)
pr_debug("MMIO address 0x%04x already in use\n", val); dev_dbg(dev, "MMIO address 0x%08x already in use\n",
mmio_addr);
/* switch (tco->tco_reg_layout) {
* Secondly, Find the watchdog timer MMIO address case sp5100:
* from SBResource_MMIO register. /*
*/ * Secondly, Find the watchdog timer MMIO address
if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) { * from SBResource_MMIO register.
/* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */ */
pci_read_config_dword(sp5100_tco_pci, /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
SP5100_SB_RESOURCE_MMIO_BASE, &val); pci_read_config_dword(sp5100_tco_pci,
} else { SP5100_SB_RESOURCE_MMIO_BASE,
/* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */ &mmio_addr);
outb(SB800_PM_ACPI_MMIO_EN+3, SB800_IO_PM_INDEX_REG); if ((mmio_addr & (SB800_ACPI_MMIO_DECODE_EN |
val = inb(SB800_IO_PM_DATA_REG); SB800_ACPI_MMIO_SEL)) !=
outb(SB800_PM_ACPI_MMIO_EN+2, SB800_IO_PM_INDEX_REG); SB800_ACPI_MMIO_DECODE_EN) {
val = val << 8 | inb(SB800_IO_PM_DATA_REG); ret = -ENODEV;
outb(SB800_PM_ACPI_MMIO_EN+1, SB800_IO_PM_INDEX_REG); goto unreg_region;
val = val << 8 | inb(SB800_IO_PM_DATA_REG); }
outb(SB800_PM_ACPI_MMIO_EN+0, SB800_IO_PM_INDEX_REG); mmio_addr &= ~0xFFF;
val = val << 8 | inb(SB800_IO_PM_DATA_REG); 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;
}
} }
/* The SBResource_MMIO is enabled and mapped memory space? */ tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) == if (!tco->tcobase) {
SB800_ACPI_MMIO_DECODE_EN) { dev_err(dev, "failed to get tcobase address\n");
/* Clear unnecessary the low twelve bits */ ret = -ENOMEM;
val &= ~0xFFF; goto unreg_region;
/* 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); dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr);
/* Setup the watchdog timer */ /* Setup the watchdog timer */
tco_timer_enable(); 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;
}
/* Check that the watchdog action is set to reset the system */
val = readl(SP5100_WDT_CONTROL(tcobase));
/* /*
* Save WatchDogFired status, because WatchDogFired flag is * Save WatchDogFired status, because WatchDogFired flag is
* cleared here. * cleared here.
*/ */
tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED; if (val & SP5100_WDT_FIRED)
val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; wdd->bootstatus = WDIOF_CARDRESET;
writel(val, SP5100_WDT_CONTROL(tcobase)); /* 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 */ /* Set a reasonable heartbeat before we stop the timer */
tco_timer_set_heartbeat(heartbeat); tco_timer_set_timeout(wdd, wdd->timeout);
/* /*
* Stop the TCO before we change anything so we don't race with * Stop the TCO before we change anything so we don't race with
* a zeroed timer. * a zeroed timer.
*/ */
tco_timer_stop(); tco_timer_stop(wdd);
/* Done */ release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
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; return 0;
}
static int sp5100_tco_init(struct platform_device *dev) unreg_region:
{ release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
int ret; return ret;
}
/* static struct watchdog_info sp5100_tco_wdt_info = {
* Check whether or not the hardware watchdog is there. If found, then .identity = "SP5100 TCO timer",
* set it up. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
*/ };
if (!sp5100_tco_setupdevice())
return -ENODEV;
/* Check to see if last reboot was due to watchdog timeout */ static const struct watchdog_ops sp5100_tco_wdt_ops = {
pr_info("Last reboot was %striggered by watchdog.\n", .owner = THIS_MODULE,
tco_wdt_fired ? "" : "not "); .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)
* Check that the heartbeat value is within it's range. {
* If not, reset to the default. struct device *dev = &pdev->dev;
*/ struct watchdog_device *wdd;
if (tco_timer_set_heartbeat(heartbeat)) { struct sp5100_tco *tco;
heartbeat = WATCHDOG_HEARTBEAT; int ret;
tco_timer_set_heartbeat(heartbeat);
}
ret = misc_register(&sp5100_tco_miscdev); tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);
if (ret != 0) { if (!tco)
pr_err("cannot register miscdev on minor=%d (err=%d)\n", return -ENOMEM;
WATCHDOG_MINOR, ret);
goto exit; 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);
/* Show module parameters */ /* Show module parameters */
pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
tcobase, heartbeat, nowayout); wdd->timeout, 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; return 0;
} }
static void sp5100_tco_shutdown(struct platform_device *dev)
{
tco_timer_stop();
}
static struct platform_driver sp5100_tco_driver = { static struct platform_driver sp5100_tco_driver = {
.probe = sp5100_tco_init, .probe = sp5100_tco_probe,
.remove = sp5100_tco_remove,
.shutdown = sp5100_tco_shutdown,
.driver = { .driver = {
.name = TCO_MODULE_NAME, .name = TCO_DRIVER_NAME,
}, },
}; };
static int __init sp5100_tco_init_module(void) /*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE. We do not actually
* register a pci_driver, because someone else might
* want to register another driver on the same PCI id.
*/
static const struct pci_device_id sp5100_tco_pci_tbl[] = {
{ PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
PCI_ANY_ID, },
{ 0, }, /* End of list */
};
MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
static int __init sp5100_tco_init(void)
{ {
struct pci_dev *dev = NULL;
int err; int err;
pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION); /* Match the PCI device */
for_each_pci_dev(dev) {
if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {
sp5100_tco_pci = dev;
break;
}
}
if (!sp5100_tco_pci)
return -ENODEV;
pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");
err = platform_driver_register(&sp5100_tco_driver); err = platform_driver_register(&sp5100_tco_driver);
if (err) if (err)
return err; return err;
sp5100_tco_platform_device = platform_device_register_simple( sp5100_tco_platform_device =
TCO_MODULE_NAME, -1, NULL, 0); platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);
if (IS_ERR(sp5100_tco_platform_device)) { if (IS_ERR(sp5100_tco_platform_device)) {
err = PTR_ERR(sp5100_tco_platform_device); err = PTR_ERR(sp5100_tco_platform_device);
goto unreg_platform_driver; goto unreg_platform_driver;
...@@ -579,15 +482,14 @@ static int __init sp5100_tco_init_module(void) ...@@ -579,15 +482,14 @@ static int __init sp5100_tco_init_module(void)
return err; 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_device_unregister(sp5100_tco_platform_device);
platform_driver_unregister(&sp5100_tco_driver); platform_driver_unregister(&sp5100_tco_driver);
pr_info("SP5100/SB800 TCO Watchdog Module Unloaded\n");
} }
module_init(sp5100_tco_init_module); module_init(sp5100_tco_init);
module_exit(sp5100_tco_cleanup_module); module_exit(sp5100_tco_exit);
MODULE_AUTHOR("Priyanka Gupta"); MODULE_AUTHOR("Priyanka Gupta");
MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset"); MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
* TCO timer driver for sp5100 chipsets * TCO timer driver for sp5100 chipsets
*/ */
#include <linux/bitops.h>
/* /*
* Some address definitions for the Watchdog * Some address definitions for the Watchdog
*/ */
...@@ -14,8 +16,11 @@ ...@@ -14,8 +16,11 @@
#define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */ #define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */
#define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ #define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */
#define SP5100_WDT_START_STOP_BIT (1 << 0) #define SP5100_WDT_START_STOP_BIT BIT(0)
#define SP5100_WDT_TRIGGER_BIT (1 << 7) #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 #define SP5100_PM_IOPORTS_SIZE 0x02
...@@ -24,43 +29,57 @@ ...@@ -24,43 +29,57 @@
* read them from a register. * 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_INDEX_REG 0xCD6
#define SP5100_IO_PM_DATA_REG 0xCD7 #define SP5100_IO_PM_DATA_REG 0xCD7
/* For SP5100/SB7x0 chipset */
#define SP5100_SB_RESOURCE_MMIO_BASE 0x9C #define SP5100_SB_RESOURCE_MMIO_BASE 0x9C
#define SP5100_PM_WATCHDOG_CONTROL 0x69 #define SP5100_PM_WATCHDOG_CONTROL 0x69
#define SP5100_PM_WATCHDOG_BASE 0x6C #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_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_DISABLE ((u8)BIT(0))
#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) #define SP5100_PM_WATCHDOG_SECOND_RES GENMASK(2, 1)
#define SP5100_DEVNAME "SP5100 TCO" #define SP5100_DEVNAME "SP5100 TCO"
/* For SB8x0(or later) chipset */ /* 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_ACPI_MMIO_EN 0x24
#define SB800_PM_WATCHDOG_CONTROL 0x48 #define SB800_PM_WATCHDOG_CONTROL 0x48
#define SB800_PM_WATCHDOG_BASE 0x48 #define SB800_PM_WATCHDOG_BASE 0x48
#define SB800_PM_WATCHDOG_CONFIG 0x4C #define SB800_PM_WATCHDOG_CONFIG 0x4C
#define SB800_PCI_WATCHDOG_DECODE_EN (1 << 0) #define SB800_PCI_WATCHDOG_DECODE_EN BIT(0)
#define SB800_PM_WATCHDOG_DISABLE (1 << 2) #define SB800_PM_WATCHDOG_DISABLE ((u8)BIT(1))
#define SB800_PM_WATCHDOG_SECOND_RES (3 << 0) #define SB800_PM_WATCHDOG_SECOND_RES GENMASK(1, 0)
#define SB800_ACPI_MMIO_DECODE_EN (1 << 0) #define SB800_ACPI_MMIO_DECODE_EN BIT(0)
#define SB800_ACPI_MMIO_SEL (1 << 1) #define SB800_ACPI_MMIO_SEL BIT(1)
#define SB800_PM_WDT_MMIO_OFFSET 0xB00 #define SB800_PM_WDT_MMIO_OFFSET 0xB00
#define SB800_DEVNAME "SB800 TCO" #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
/*
* 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");
// SPDX-License-Identifier: GPL-2.0
/* /*
* Driver for STM32 Independent Watchdog * Driver for STM32 Independent Watchdog
* *
* Copyright (C) Yannick Fertre 2017 * Copyright (C) STMicroelectronics 2017
* Author: Yannick Fertre <yannick.fertre@st.com> * Author: Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
* *
* This driver is based on tegra_wdt.c * This driver is based on tegra_wdt.c
* *
* License terms: GNU General Public License (GPL), version 2
*/ */
#include <linux/clk.h> #include <linux/clk.h>
......
...@@ -234,7 +234,6 @@ MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); ...@@ -234,7 +234,6 @@ MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
static int sunxi_wdt_probe(struct platform_device *pdev) static int sunxi_wdt_probe(struct platform_device *pdev)
{ {
struct sunxi_wdt_dev *sunxi_wdt; struct sunxi_wdt_dev *sunxi_wdt;
const struct of_device_id *device;
struct resource *res; struct resource *res;
int err; int err;
...@@ -242,12 +241,10 @@ static int sunxi_wdt_probe(struct platform_device *pdev) ...@@ -242,12 +241,10 @@ static int sunxi_wdt_probe(struct platform_device *pdev)
if (!sunxi_wdt) if (!sunxi_wdt)
return -EINVAL; return -EINVAL;
device = of_match_device(sunxi_wdt_dt_ids, &pdev->dev); sunxi_wdt->wdt_regs = of_device_get_match_data(&pdev->dev);
if (!device) if (!sunxi_wdt->wdt_regs)
return -ENODEV; return -ENODEV;
sunxi_wdt->wdt_regs = device->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(sunxi_wdt->wdt_base)) if (IS_ERR(sunxi_wdt->wdt_base))
......
...@@ -97,6 +97,7 @@ static void watchdog_check_min_max_timeout(struct watchdog_device *wdd) ...@@ -97,6 +97,7 @@ static void watchdog_check_min_max_timeout(struct watchdog_device *wdd)
/** /**
* watchdog_init_timeout() - initialize the timeout field * watchdog_init_timeout() - initialize the timeout field
* @wdd: watchdog device
* @timeout_parm: timeout module parameter * @timeout_parm: timeout module parameter
* @dev: Device that stores the timeout-sec property * @dev: Device that stores the timeout-sec property
* *
......
...@@ -36,9 +36,10 @@ ...@@ -36,9 +36,10 @@
#include <linux/errno.h> /* For the -ENODEV/... values */ #include <linux/errno.h> /* For the -ENODEV/... values */
#include <linux/fs.h> /* For file operations */ #include <linux/fs.h> /* For file operations */
#include <linux/init.h> /* For __init/__exit/... */ #include <linux/init.h> /* For __init/__exit/... */
#include <linux/jiffies.h> /* For timeout functions */ #include <linux/hrtimer.h> /* For hrtimers */
#include <linux/kernel.h> /* For printk/panic/... */ #include <linux/kernel.h> /* For printk/panic/... */
#include <linux/kref.h> /* For data references */ #include <linux/kref.h> /* For data references */
#include <linux/kthread.h> /* For kthread_work */
#include <linux/miscdevice.h> /* For handling misc devices */ #include <linux/miscdevice.h> /* For handling misc devices */
#include <linux/module.h> /* For module stuff/... */ #include <linux/module.h> /* For module stuff/... */
#include <linux/mutex.h> /* For mutexes */ #include <linux/mutex.h> /* For mutexes */
...@@ -46,9 +47,10 @@ ...@@ -46,9 +47,10 @@
#include <linux/slab.h> /* For memory functions */ #include <linux/slab.h> /* For memory functions */
#include <linux/types.h> /* For standard types (like size_t) */ #include <linux/types.h> /* For standard types (like size_t) */
#include <linux/watchdog.h> /* For watchdog specific items */ #include <linux/watchdog.h> /* For watchdog specific items */
#include <linux/workqueue.h> /* For workqueue */
#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */
#include <uapi/linux/sched/types.h> /* For struct sched_param */
#include "watchdog_core.h" #include "watchdog_core.h"
#include "watchdog_pretimeout.h" #include "watchdog_pretimeout.h"
...@@ -65,9 +67,10 @@ struct watchdog_core_data { ...@@ -65,9 +67,10 @@ struct watchdog_core_data {
struct cdev cdev; struct cdev cdev;
struct watchdog_device *wdd; struct watchdog_device *wdd;
struct mutex lock; struct mutex lock;
unsigned long last_keepalive; ktime_t last_keepalive;
unsigned long last_hw_keepalive; ktime_t last_hw_keepalive;
struct delayed_work work; struct hrtimer timer;
struct kthread_work work;
unsigned long status; /* Internal status bits */ unsigned long status; /* Internal status bits */
#define _WDOG_DEV_OPEN 0 /* Opened ? */ #define _WDOG_DEV_OPEN 0 /* Opened ? */
#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
...@@ -79,7 +82,7 @@ static dev_t watchdog_devt; ...@@ -79,7 +82,7 @@ static dev_t watchdog_devt;
/* Reference to watchdog device behind /dev/watchdog */ /* Reference to watchdog device behind /dev/watchdog */
static struct watchdog_core_data *old_wd_data; static struct watchdog_core_data *old_wd_data;
static struct workqueue_struct *watchdog_wq; static struct kthread_worker *watchdog_kworker;
static bool handle_boot_enabled = static bool handle_boot_enabled =
IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED); IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED);
...@@ -107,18 +110,19 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd) ...@@ -107,18 +110,19 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd)
(t && !watchdog_active(wdd) && watchdog_hw_running(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; struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned int timeout_ms = wdd->timeout * 1000; unsigned int timeout_ms = wdd->timeout * 1000;
unsigned long keepalive_interval; ktime_t keepalive_interval;
unsigned long last_heartbeat; ktime_t last_heartbeat, latest_heartbeat;
unsigned long virt_timeout; ktime_t virt_timeout;
unsigned int hw_heartbeat_ms; 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); 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)) if (!watchdog_active(wdd))
return keepalive_interval; return keepalive_interval;
...@@ -128,8 +132,11 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd) ...@@ -128,8 +132,11 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd)
* after the most recent ping from userspace, the last * after the most recent ping from userspace, the last
* worker ping has to come in hw_heartbeat_ms before this timeout. * worker ping has to come in hw_heartbeat_ms before this timeout.
*/ */
last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms); last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));
return min_t(long, last_heartbeat - jiffies, keepalive_interval); 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) static inline void watchdog_update_worker(struct watchdog_device *wdd)
...@@ -137,29 +144,33 @@ 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; struct watchdog_core_data *wd_data = wdd->wd_data;
if (watchdog_need_worker(wdd)) { if (watchdog_need_worker(wdd)) {
long t = watchdog_next_keepalive(wdd); ktime_t t = watchdog_next_keepalive(wdd);
if (t > 0) if (t > 0)
mod_delayed_work(watchdog_wq, &wd_data->work, t); hrtimer_start(&wd_data->timer, t, HRTIMER_MODE_REL);
} else { } else {
cancel_delayed_work(&wd_data->work); hrtimer_cancel(&wd_data->timer);
} }
} }
static int __watchdog_ping(struct watchdog_device *wdd) static int __watchdog_ping(struct watchdog_device *wdd)
{ {
struct watchdog_core_data *wd_data = wdd->wd_data; struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned long earliest_keepalive = wd_data->last_hw_keepalive + ktime_t earliest_keepalive, now;
msecs_to_jiffies(wdd->min_hw_heartbeat_ms);
int err; int err;
if (time_is_after_jiffies(earliest_keepalive)) { earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
mod_delayed_work(watchdog_wq, &wd_data->work, ms_to_ktime(wdd->min_hw_heartbeat_ms));
earliest_keepalive - jiffies); now = ktime_get();
if (ktime_after(earliest_keepalive, now)) {
hrtimer_start(&wd_data->timer,
ktime_sub(earliest_keepalive, now),
HRTIMER_MODE_REL);
return 0; return 0;
} }
wd_data->last_hw_keepalive = jiffies; wd_data->last_hw_keepalive = now;
if (wdd->ops->ping) if (wdd->ops->ping)
err = wdd->ops->ping(wdd); /* ping the watchdog */ err = wdd->ops->ping(wdd); /* ping the watchdog */
...@@ -192,7 +203,7 @@ static int watchdog_ping(struct watchdog_device *wdd) ...@@ -192,7 +203,7 @@ static int watchdog_ping(struct watchdog_device *wdd)
set_bit(_WDOG_KEEPALIVE, &wd_data->status); set_bit(_WDOG_KEEPALIVE, &wd_data->status);
wd_data->last_keepalive = jiffies; wd_data->last_keepalive = ktime_get();
return __watchdog_ping(wdd); return __watchdog_ping(wdd);
} }
...@@ -203,12 +214,11 @@ static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data) ...@@ -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)); 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; struct watchdog_core_data *wd_data;
wd_data = container_of(to_delayed_work(work), struct watchdog_core_data, wd_data = container_of(work, struct watchdog_core_data, work);
work);
mutex_lock(&wd_data->lock); mutex_lock(&wd_data->lock);
if (watchdog_worker_should_ping(wd_data)) if (watchdog_worker_should_ping(wd_data))
...@@ -216,6 +226,16 @@ static void watchdog_ping_work(struct work_struct *work) ...@@ -216,6 +226,16 @@ static void watchdog_ping_work(struct work_struct *work)
mutex_unlock(&wd_data->lock); 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. * watchdog_start: wrapper to start the watchdog.
* @wdd: the watchdog device to start * @wdd: the watchdog device to start
...@@ -230,7 +250,7 @@ static void watchdog_ping_work(struct work_struct *work) ...@@ -230,7 +250,7 @@ static void watchdog_ping_work(struct work_struct *work)
static int watchdog_start(struct watchdog_device *wdd) static int watchdog_start(struct watchdog_device *wdd)
{ {
struct watchdog_core_data *wd_data = wdd->wd_data; struct watchdog_core_data *wd_data = wdd->wd_data;
unsigned long started_at; ktime_t started_at;
int err; int err;
if (watchdog_active(wdd)) if (watchdog_active(wdd))
...@@ -238,7 +258,7 @@ static int watchdog_start(struct watchdog_device *wdd) ...@@ -238,7 +258,7 @@ static int watchdog_start(struct watchdog_device *wdd)
set_bit(_WDOG_KEEPALIVE, &wd_data->status); set_bit(_WDOG_KEEPALIVE, &wd_data->status);
started_at = jiffies; started_at = ktime_get();
if (watchdog_hw_running(wdd) && wdd->ops->ping) if (watchdog_hw_running(wdd) && wdd->ops->ping)
err = wdd->ops->ping(wdd); err = wdd->ops->ping(wdd);
else else
...@@ -720,7 +740,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, ...@@ -720,7 +740,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd,
err = watchdog_ping(wdd); err = watchdog_ping(wdd);
if (err < 0) if (err < 0)
break; break;
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
/* timeout == 0 means that we don't know the timeout */ /* timeout == 0 means that we don't know the timeout */
if (wdd->timeout == 0) { if (wdd->timeout == 0) {
...@@ -769,6 +789,7 @@ static int watchdog_open(struct inode *inode, struct file *file) ...@@ -769,6 +789,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
{ {
struct watchdog_core_data *wd_data; struct watchdog_core_data *wd_data;
struct watchdog_device *wdd; struct watchdog_device *wdd;
bool hw_running;
int err; int err;
/* Get the corresponding watchdog device */ /* Get the corresponding watchdog device */
...@@ -788,7 +809,8 @@ static int watchdog_open(struct inode *inode, struct file *file) ...@@ -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 * If the /dev/watchdog device is open, we don't want the module
* to be unloaded. * 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; err = -EBUSY;
goto out_clear; goto out_clear;
} }
...@@ -799,7 +821,7 @@ static int watchdog_open(struct inode *inode, struct file *file) ...@@ -799,7 +821,7 @@ static int watchdog_open(struct inode *inode, struct file *file)
file->private_data = wd_data; file->private_data = wd_data;
if (!watchdog_hw_running(wdd)) if (!hw_running)
kref_get(&wd_data->kref); kref_get(&wd_data->kref);
/* dev/watchdog is a virtual (and thus non-seekable) filesystem */ /* dev/watchdog is a virtual (and thus non-seekable) filesystem */
...@@ -919,10 +941,12 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) ...@@ -919,10 +941,12 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno)
wd_data->wdd = wdd; wd_data->wdd = wdd;
wdd->wd_data = wd_data; wdd->wd_data = wd_data;
if (!watchdog_wq) if (IS_ERR_OR_NULL(watchdog_kworker))
return -ENODEV; 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) { if (wdd->id == 0) {
old_wd_data = wd_data; old_wd_data = wd_data;
...@@ -958,21 +982,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) ...@@ -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'. */ /* 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, * If the watchdog is running, prevent its driver from being unloaded,
* and schedule an immediate ping. * and schedule an immediate ping.
*/ */
if (watchdog_hw_running(wdd)) { if (watchdog_hw_running(wdd)) {
if (handle_boot_enabled) { __module_get(wdd->ops->owner);
__module_get(wdd->ops->owner); kref_get(&wd_data->kref);
kref_get(&wd_data->kref); if (handle_boot_enabled)
queue_delayed_work(watchdog_wq, &wd_data->work, 0); hrtimer_start(&wd_data->timer, 0, HRTIMER_MODE_REL);
} else { else
pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n", pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
wdd->id); wdd->id);
}
} }
return 0; return 0;
...@@ -1006,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd) ...@@ -1006,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd)
watchdog_stop(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); kref_put(&wd_data->kref, watchdog_core_data_release);
} }
...@@ -1110,13 +1134,14 @@ void watchdog_dev_unregister(struct watchdog_device *wdd) ...@@ -1110,13 +1134,14 @@ void watchdog_dev_unregister(struct watchdog_device *wdd)
int __init watchdog_dev_init(void) int __init watchdog_dev_init(void)
{ {
int err; int err;
struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1,};
watchdog_wq = alloc_workqueue("watchdogd", watchdog_kworker = kthread_create_worker(0, "watchdogd");
WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); if (IS_ERR(watchdog_kworker)) {
if (!watchdog_wq) { pr_err("Failed to create watchdog kworker\n");
pr_err("Failed to create watchdog workqueue\n"); return PTR_ERR(watchdog_kworker);
return -ENOMEM;
} }
sched_setscheduler(watchdog_kworker->task, SCHED_FIFO, &param);
err = class_register(&watchdog_class); err = class_register(&watchdog_class);
if (err < 0) { if (err < 0) {
...@@ -1135,7 +1160,7 @@ int __init watchdog_dev_init(void) ...@@ -1135,7 +1160,7 @@ int __init watchdog_dev_init(void)
err_alloc: err_alloc:
class_unregister(&watchdog_class); class_unregister(&watchdog_class);
err_register: err_register:
destroy_workqueue(watchdog_wq); kthread_destroy_worker(watchdog_kworker);
return err; return err;
} }
...@@ -1149,7 +1174,7 @@ void __exit watchdog_dev_exit(void) ...@@ -1149,7 +1174,7 @@ void __exit watchdog_dev_exit(void)
{ {
unregister_chrdev_region(watchdog_devt, MAX_DOGS); unregister_chrdev_region(watchdog_devt, MAX_DOGS);
class_unregister(&watchdog_class); class_unregister(&watchdog_class);
destroy_workqueue(watchdog_wq); kthread_destroy_worker(watchdog_kworker);
} }
module_param(handle_boot_enabled, bool, 0444); module_param(handle_boot_enabled, bool, 0444);
......
...@@ -430,7 +430,7 @@ static long wdtpci_ioctl(struct file *file, unsigned int cmd, ...@@ -430,7 +430,7 @@ static long wdtpci_ioctl(struct file *file, unsigned int cmd,
if (wdtpci_set_heartbeat(new_heartbeat)) if (wdtpci_set_heartbeat(new_heartbeat))
return -EINVAL; return -EINVAL;
wdtpci_ping(); wdtpci_ping();
/* Fall */ /* fall through */
case WDIOC_GETTIMEOUT: case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p); return put_user(heartbeat, p);
default: default:
......
...@@ -9,10 +9,7 @@ ...@@ -9,10 +9,7 @@
* 2 of the License, or (at your option) any later version. * 2 of the License, or (at your option) any later version.
*/ */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define DRV_NAME "xen_wdt"
#define DRV_NAME "wdt"
#define DRV_VERSION "0.01"
#include <linux/bug.h> #include <linux/bug.h>
#include <linux/errno.h> #include <linux/errno.h>
...@@ -21,25 +18,20 @@ ...@@ -21,25 +18,20 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/ktime.h> #include <linux/ktime.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h> #include <linux/moduleparam.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h> #include <linux/watchdog.h>
#include <xen/xen.h> #include <xen/xen.h>
#include <asm/xen/hypercall.h> #include <asm/xen/hypercall.h>
#include <xen/interface/sched.h> #include <xen/interface/sched.h>
static struct platform_device *platform_device; static struct platform_device *platform_device;
static DEFINE_SPINLOCK(wdt_lock);
static struct sched_watchdog wdt; static struct sched_watchdog wdt;
static __kernel_time_t wdt_expires; static time64_t wdt_expires;
static bool is_active, expect_release;
#define WATCHDOG_TIMEOUT 60 /* in seconds */ #define WATCHDOG_TIMEOUT 60 /* in seconds */
static unsigned int timeout = WATCHDOG_TIMEOUT; static unsigned int timeout;
module_param(timeout, uint, S_IRUGO); module_param(timeout, uint, S_IRUGO);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds " MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
"(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); "(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
...@@ -49,20 +41,18 @@ module_param(nowayout, bool, S_IRUGO); ...@@ -49,20 +41,18 @@ module_param(nowayout, bool, S_IRUGO);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static inline __kernel_time_t set_timeout(void) static inline time64_t set_timeout(struct watchdog_device *wdd)
{ {
wdt.timeout = timeout; wdt.timeout = wdd->timeout;
return ktime_to_timespec(ktime_get()).tv_sec + 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; int err;
spin_lock(&wdt_lock); expires = set_timeout(wdd);
expires = set_timeout();
if (!wdt.id) if (!wdt.id)
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
else else
...@@ -74,36 +64,28 @@ static int xen_wdt_start(void) ...@@ -74,36 +64,28 @@ static int xen_wdt_start(void)
} else } else
BUG_ON(!err); BUG_ON(!err);
spin_unlock(&wdt_lock);
return err; return err;
} }
static int xen_wdt_stop(void) static int xen_wdt_stop(struct watchdog_device *wdd)
{ {
int err = 0; int err = 0;
spin_lock(&wdt_lock);
wdt.timeout = 0; wdt.timeout = 0;
if (wdt.id) if (wdt.id)
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
if (!err) if (!err)
wdt.id = 0; wdt.id = 0;
spin_unlock(&wdt_lock);
return err; 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; int err;
spin_lock(&wdt_lock); expires = set_timeout(wdd);
expires = set_timeout();
if (wdt.id) if (wdt.id)
err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
else else
...@@ -111,195 +93,72 @@ static int xen_wdt_kick(void) ...@@ -111,195 +93,72 @@ static int xen_wdt_kick(void)
if (!err) if (!err)
wdt_expires = expires; wdt_expires = expires;
spin_unlock(&wdt_lock);
return err;
}
static int xen_wdt_open(struct inode *inode, struct file *file)
{
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);
}
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; return err;
} }
static ssize_t xen_wdt_write(struct file *file, const char __user *data, static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd)
size_t len, loff_t *ppos)
{ {
/* See if we got the magic character 'V' and reload the timer */ return wdt_expires - ktime_get_seconds();
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, static struct watchdog_info xen_wdt_info = {
unsigned long arg) .identity = DRV_NAME,
{ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
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 = { static const struct watchdog_ops xen_wdt_ops = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.llseek = no_llseek, .start = xen_wdt_start,
.write = xen_wdt_write, .stop = xen_wdt_stop,
.unlocked_ioctl = xen_wdt_ioctl, .ping = xen_wdt_kick,
.open = xen_wdt_open, .get_timeleft = xen_wdt_get_timeleft,
.release = xen_wdt_release,
}; };
static struct miscdevice xen_wdt_miscdev = { static struct watchdog_device xen_wdt_dev = {
.minor = WATCHDOG_MINOR, .info = &xen_wdt_info,
.name = "watchdog", .ops = &xen_wdt_ops,
.fops = &xen_wdt_fops, .timeout = WATCHDOG_TIMEOUT,
}; };
static int xen_wdt_probe(struct platform_device *dev) static int xen_wdt_probe(struct platform_device *pdev)
{ {
struct sched_watchdog wd = { .id = ~0 }; struct sched_watchdog wd = { .id = ~0 };
int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd); int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd);
switch (ret) { if (ret == -ENOSYS) {
case -EINVAL: dev_err(&pdev->dev, "watchdog not supported by hypervisor\n");
if (!timeout) { return -ENODEV;
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;
} }
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) if (watchdog_init_timeout(&xen_wdt_dev, timeout, NULL))
{ dev_info(&pdev->dev, "timeout value invalid, using %d\n",
/* Stop the timer before we leave */ xen_wdt_dev.timeout);
if (!nowayout) watchdog_set_nowayout(&xen_wdt_dev, nowayout);
xen_wdt_stop(); watchdog_stop_on_reboot(&xen_wdt_dev);
watchdog_stop_on_unregister(&xen_wdt_dev);
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;
}
misc_deregister(&xen_wdt_miscdev); dev_info(&pdev->dev, "initialized (timeout=%ds, nowayout=%d)\n",
xen_wdt_dev.timeout, nowayout);
return 0; 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) static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state)
{ {
typeof(wdt.id) id = wdt.id; typeof(wdt.id) id = wdt.id;
int rc = xen_wdt_stop(); int rc = xen_wdt_stop(&xen_wdt_dev);
wdt.id = id; wdt.id = id;
return rc; return rc;
...@@ -310,13 +169,11 @@ static int xen_wdt_resume(struct platform_device *dev) ...@@ -310,13 +169,11 @@ static int xen_wdt_resume(struct platform_device *dev)
if (!wdt.id) if (!wdt.id)
return 0; return 0;
wdt.id = 0; wdt.id = 0;
return xen_wdt_start(); return xen_wdt_start(&xen_wdt_dev);
} }
static struct platform_driver xen_wdt_driver = { static struct platform_driver xen_wdt_driver = {
.probe = xen_wdt_probe, .probe = xen_wdt_probe,
.remove = xen_wdt_remove,
.shutdown = xen_wdt_shutdown,
.suspend = xen_wdt_suspend, .suspend = xen_wdt_suspend,
.resume = xen_wdt_resume, .resume = xen_wdt_resume,
.driver = { .driver = {
...@@ -331,8 +188,6 @@ static int __init xen_wdt_init_module(void) ...@@ -331,8 +188,6 @@ static int __init xen_wdt_init_module(void)
if (!xen_domain()) if (!xen_domain())
return -ENODEV; return -ENODEV;
pr_info("Xen WatchDog Timer Driver v%s\n", DRV_VERSION);
err = platform_driver_register(&xen_wdt_driver); err = platform_driver_register(&xen_wdt_driver);
if (err) if (err)
return err; return err;
...@@ -351,7 +206,6 @@ static void __exit xen_wdt_cleanup_module(void) ...@@ -351,7 +206,6 @@ static void __exit xen_wdt_cleanup_module(void)
{ {
platform_device_unregister(platform_device); platform_device_unregister(platform_device);
platform_driver_unregister(&xen_wdt_driver); platform_driver_unregister(&xen_wdt_driver);
pr_info("module unloaded\n");
} }
module_init(xen_wdt_init_module); module_init(xen_wdt_init_module);
...@@ -359,5 +213,4 @@ module_exit(xen_wdt_cleanup_module); ...@@ -359,5 +213,4 @@ module_exit(xen_wdt_cleanup_module);
MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>"); MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>");
MODULE_DESCRIPTION("Xen WatchDog Timer Driver"); MODULE_DESCRIPTION("Xen WatchDog Timer Driver");
MODULE_VERSION(DRV_VERSION);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment