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