diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 11:54:34 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 11:54:34 -0800 |
commit | 7e6127c1240ed569cdda2a67c8f03836f9f28c05 (patch) | |
tree | f25db0464b51c2197ec706b0fc4bc38e70659f23 | |
parent | 413879a10b0b0eb563a23c4df896773b2d9413f9 (diff) | |
parent | 592a547adf686dbec7687e816ecdf9101fe227f5 (diff) |
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
...
43 files changed, 1711 insertions, 1801 deletions
diff --git a/Documentation/devicetree/bindings/watchdog/cortina,gemini-watchdog.txt b/Documentation/devicetree/bindings/watchdog/cortina,gemini-watchdog.txt deleted file mode 100644 index bc4b865d178b..000000000000 --- a/Documentation/devicetree/bindings/watchdog/cortina,gemini-watchdog.txt +++ /dev/null @@ -1,17 +0,0 @@ -Cortina Systems Gemini SoC Watchdog - -Required properties: -- compatible : must be "cortina,gemini-watchdog" -- reg : shall contain base register location and length -- interrupts : shall contain the interrupt for the watchdog - -Optional properties: -- timeout-sec : the default watchdog timeout in seconds. - -Example: - -watchdog@41000000 { - compatible = "cortina,gemini-watchdog"; - reg = <0x41000000 0x1000>; - interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; -}; diff --git a/Documentation/devicetree/bindings/watchdog/cortina,gemin-watchdog.txt b/Documentation/devicetree/bindings/watchdog/faraday,ftwdt010.txt index bc4b865d178b..9ecdb502e605 100644 --- a/Documentation/devicetree/bindings/watchdog/cortina,gemin-watchdog.txt +++ b/Documentation/devicetree/bindings/watchdog/faraday,ftwdt010.txt @@ -1,7 +1,12 @@ -Cortina Systems Gemini SoC Watchdog +Faraday Technology FTWDT010 watchdog + +This is an IP part from Faraday Technology found in the Gemini +SoCs and others. Required properties: -- compatible : must be "cortina,gemini-watchdog" +- compatible : must be one of + "faraday,ftwdt010" + "cortina,gemini-watchdog", "faraday,ftwdt010" - reg : shall contain base register location and length - interrupts : shall contain the interrupt for the watchdog @@ -11,7 +16,7 @@ Optional properties: Example: watchdog@41000000 { - compatible = "cortina,gemini-watchdog"; + compatible = "faraday,ftwdt010"; reg = <0x41000000 0x1000>; interrupts = <3 IRQ_TYPE_LEVEL_HIGH>; }; diff --git a/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt b/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt index 3c7a1cd13b10..cb44918f01a8 100644 --- a/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt +++ b/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt @@ -1,7 +1,7 @@ -Ingenic Watchdog Timer (WDT) Controller for JZ4740 +Ingenic Watchdog Timer (WDT) Controller for JZ4740 & JZ4780 Required properties: -compatible: "ingenic,jz4740-watchdog" +compatible: "ingenic,jz4740-watchdog" or "ingenic,jz4780-watchdog" reg: Register address and length for watchdog registers Example: diff --git a/Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt b/Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt new file mode 100644 index 000000000000..05653054bd5b --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt @@ -0,0 +1,17 @@ +Realtek RTD1295 Watchdog +======================== + +Required properties: + +- compatible : Should be "realtek,rtd1295-watchdog" +- reg : Specifies the physical base address and size of registers +- clocks : Specifies one clock input + + +Example: + + watchdog@98007680 { + compatible = "realtek,rtd1295-watchdog"; + reg = <0x98007680 0x100>; + clocks = <&osc27M>; + }; diff --git a/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt b/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt index bf6d1ca58af7..74b2f03c1515 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt +++ b/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt @@ -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 diff --git a/Documentation/devicetree/bindings/watchdog/sprd-wdt.txt b/Documentation/devicetree/bindings/watchdog/sprd-wdt.txt new file mode 100644 index 000000000000..aeaf3e0caf47 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/sprd-wdt.txt @@ -0,0 +1,19 @@ +Spreadtrum SoCs Watchdog timer + +Required properties: +- compatible : Should be "sprd,sp9860-wdt". +- reg : Specifies base physical address and size of the registers. +- interrupts : Exactly one interrupt specifier. +- timeout-sec : Contain the default watchdog timeout in seconds. +- clock-names : Contain the input clock names. +- clocks : Phandles to input clocks. + +Example: + watchdog: watchdog@40310000 { + compatible = "sprd,sp9860-wdt"; + reg = <0 0x40310000 0 0x1000>; + interrupts = <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>; + timeout-sec = <12>; + clock-names = "enable", "rtc_enable"; + clocks = <&clk_aon_apb_gates1 8>, <&clk_aon_apb_rtc_gates 9>; + }; diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 6f9d7b418917..beea975980f6 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt @@ -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 diff --git a/MAINTAINERS b/MAINTAINERS index ba3842a9e73e..c344e9e1177a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -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 diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 5bf613d3b7d6..aff773bcebdb 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 135c5e81f25e..0474d38aa854 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -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 diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c index 7d7db0c5a64e..f61944369c1a 100644 --- a/drivers/watchdog/advantechwdt.c +++ b/drivers/watchdog/advantechwdt.c @@ -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: diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c index 3a17fbd39f8a..60f0c2eb8531 100644 --- a/drivers/watchdog/alim1535_wdt.c +++ b/drivers/watchdog/alim1535_wdt.c @@ -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: diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index 79cc766cd30f..ca5b91e2eb92 100644 --- a/drivers/watchdog/aspeed_wdt.c +++ b/drivers/watchdog/aspeed_wdt.c @@ -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"); diff --git a/drivers/watchdog/at32ap700x_wdt.c b/drivers/watchdog/at32ap700x_wdt.c deleted file mode 100644 index 81ba8920f127..000000000000 --- a/drivers/watchdog/at32ap700x_wdt.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Watchdog driver for Atmel AT32AP700X devices - * - * Copyright (C) 2005-2006 Atmel Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * - * Errata: WDT Clear is blocked after WDT Reset - * - * A watchdog timer event will, after reset, block writes to the WDT_CLEAR - * register, preventing the program to clear the next Watchdog Timer Reset. - * - * If you still want to use the WDT after a WDT reset a small code can be - * insterted at the startup checking the AVR32_PM.rcause register for WDT reset - * and use a GPIO pin to reset the system. This method requires that one of the - * GPIO pins are available and connected externally to the RESET_N pin. After - * the GPIO pin has pulled down the reset line the GPIO will be reset and leave - * the pin tristated with pullup. - */ - -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/miscdevice.h> -#include <linux/fs.h> -#include <linux/platform_device.h> -#include <linux/watchdog.h> -#include <linux/uaccess.h> -#include <linux/io.h> -#include <linux/spinlock.h> -#include <linux/slab.h> - -#define TIMEOUT_MIN 1 -#define TIMEOUT_MAX 2 -#define TIMEOUT_DEFAULT TIMEOUT_MAX - -/* module parameters */ -static int timeout = TIMEOUT_DEFAULT; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, - "Timeout value. Limited to be 1 or 2 seconds. (default=" - __MODULE_STRING(TIMEOUT_DEFAULT) ")"); - -static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -/* Watchdog registers and write/read macro */ -#define WDT_CTRL 0x00 -#define WDT_CTRL_EN 0 -#define WDT_CTRL_PSEL 8 -#define WDT_CTRL_KEY 24 - -#define WDT_CLR 0x04 - -#define WDT_RCAUSE 0x10 -#define WDT_RCAUSE_POR 0 -#define WDT_RCAUSE_EXT 2 -#define WDT_RCAUSE_WDT 3 -#define WDT_RCAUSE_JTAG 4 -#define WDT_RCAUSE_SERP 5 - -#define WDT_BIT(name) (1 << WDT_##name) -#define WDT_BF(name, value) ((value) << WDT_##name) - -#define wdt_readl(dev, reg) \ - __raw_readl((dev)->regs + WDT_##reg) -#define wdt_writel(dev, reg, value) \ - __raw_writel((value), (dev)->regs + WDT_##reg) - -struct wdt_at32ap700x { - void __iomem *regs; - spinlock_t io_lock; - int timeout; - int boot_status; - unsigned long users; - struct miscdevice miscdev; -}; - -static struct wdt_at32ap700x *wdt; -static char expect_release; - -/* - * Disable the watchdog. - */ -static inline void at32_wdt_stop(void) -{ - unsigned long psel; - - spin_lock(&wdt->io_lock); - psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f); - wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55)); - wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa)); - spin_unlock(&wdt->io_lock); -} - -/* - * Enable and reset the watchdog. - */ -static inline void at32_wdt_start(void) -{ - /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */ - unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe; - - spin_lock(&wdt->io_lock); - wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) - | WDT_BF(CTRL_PSEL, psel) - | WDT_BF(CTRL_KEY, 0x55)); - wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) - | WDT_BF(CTRL_PSEL, psel) - | WDT_BF(CTRL_KEY, 0xaa)); - spin_unlock(&wdt->io_lock); -} - -/* - * Pat the watchdog timer. - */ -static inline void at32_wdt_pat(void) -{ - spin_lock(&wdt->io_lock); - wdt_writel(wdt, CLR, 0x42); - spin_unlock(&wdt->io_lock); -} - -/* - * Watchdog device is opened, and watchdog starts running. - */ -static int at32_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(1, &wdt->users)) - return -EBUSY; - - at32_wdt_start(); - return nonseekable_open(inode, file); -} - -/* - * Close the watchdog device. - */ -static int at32_wdt_close(struct inode *inode, struct file *file) -{ - if (expect_release == 42) { - at32_wdt_stop(); - } else { - dev_dbg(wdt->miscdev.parent, - "unexpected close, not stopping watchdog!\n"); - at32_wdt_pat(); - } - clear_bit(1, &wdt->users); - expect_release = 0; - return 0; -} - -/* - * Change the watchdog time interval. - */ -static int at32_wdt_settimeout(int time) -{ - /* - * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is - * 2 ^ 16 allowing up to 2 seconds timeout. - */ - if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX)) - return -EINVAL; - - /* - * Set new watchdog time. It will be used when at32_wdt_start() is - * called. - */ - wdt->timeout = time; - return 0; -} - -/* - * Get the watchdog status. - */ -static int at32_wdt_get_status(void) -{ - int rcause; - int status = 0; - - rcause = wdt_readl(wdt, RCAUSE); - - switch (rcause) { - case WDT_BIT(RCAUSE_EXT): - status = WDIOF_EXTERN1; - break; - case WDT_BIT(RCAUSE_WDT): - status = WDIOF_CARDRESET; - break; - case WDT_BIT(RCAUSE_POR): /* fall through */ - case WDT_BIT(RCAUSE_JTAG): /* fall through */ - case WDT_BIT(RCAUSE_SERP): /* fall through */ - default: - break; - } - - return status; -} - -static const struct watchdog_info at32_wdt_info = { - .identity = "at32ap700x watchdog", - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, -}; - -/* - * Handle commands from user-space. - */ -static long at32_wdt_ioctl(struct file *file, - unsigned int cmd, unsigned long arg) -{ - int ret = -ENOTTY; - int time; - void __user *argp = (void __user *)arg; - int __user *p = argp; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user(argp, &at32_wdt_info, - sizeof(at32_wdt_info)) ? -EFAULT : 0; - break; - case WDIOC_GETSTATUS: - ret = put_user(0, p); - break; - case WDIOC_GETBOOTSTATUS: - ret = put_user(wdt->boot_status, p); - break; - case WDIOC_SETOPTIONS: - ret = get_user(time, p); - if (ret) - break; - if (time & WDIOS_DISABLECARD) - at32_wdt_stop(); - if (time & WDIOS_ENABLECARD) - at32_wdt_start(); - ret = 0; - break; - case WDIOC_KEEPALIVE: - at32_wdt_pat(); - ret = 0; - break; - case WDIOC_SETTIMEOUT: - ret = get_user(time, p); - if (ret) - break; - ret = at32_wdt_settimeout(time); - if (ret) - break; - /* Enable new time value */ - at32_wdt_start(); - /* fall through */ - case WDIOC_GETTIMEOUT: - ret = put_user(wdt->timeout, p); - break; - } - - return ret; -} - -static ssize_t at32_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - /* See if we got the magic character 'V' and reload the timer */ - if (len) { - if (!nowayout) { - size_t i; - - /* - * note: just in case someone wrote the magic - * character five months ago... - */ - expect_release = 0; - - /* - * scan to see whether or not we got the magic - * character - */ - for (i = 0; i != len; i++) { - char c; - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - expect_release = 42; - } - } - /* someone wrote to us, we should pat the watchdog */ - at32_wdt_pat(); - } - return len; -} - -static const struct file_operations at32_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .unlocked_ioctl = at32_wdt_ioctl, - .open = at32_wdt_open, - .release = at32_wdt_close, - .write = at32_wdt_write, -}; - -static int __init at32_wdt_probe(struct platform_device *pdev) -{ - struct resource *regs; - int ret; - - if (wdt) { - dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n"); - return -EBUSY; - } - - regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) { - dev_dbg(&pdev->dev, "missing mmio resource\n"); - return -ENXIO; - } - - wdt = devm_kzalloc(&pdev->dev, sizeof(struct wdt_at32ap700x), - GFP_KERNEL); - if (!wdt) - return -ENOMEM; - - wdt->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs)); - if (!wdt->regs) { - ret = -ENOMEM; - dev_dbg(&pdev->dev, "could not map I/O memory\n"); - goto err_free; - } - - spin_lock_init(&wdt->io_lock); - wdt->boot_status = at32_wdt_get_status(); - - /* Work-around for watchdog silicon errata. */ - if (wdt->boot_status & WDIOF_CARDRESET) { - dev_info(&pdev->dev, "CPU must be reset with external " - "reset or POR due to silicon errata.\n"); - ret = -EIO; - goto err_free; - } else { - wdt->users = 0; - } - - wdt->miscdev.minor = WATCHDOG_MINOR; - wdt->miscdev.name = "watchdog"; - wdt->miscdev.fops = &at32_wdt_fops; - wdt->miscdev.parent = &pdev->dev; - - platform_set_drvdata(pdev, wdt); - - if (at32_wdt_settimeout(timeout)) { - at32_wdt_settimeout(TIMEOUT_DEFAULT); - dev_dbg(&pdev->dev, - "default timeout invalid, set to %d sec.\n", - TIMEOUT_DEFAULT); - } - - ret = misc_register(&wdt->miscdev); - if (ret) { - dev_dbg(&pdev->dev, "failed to register wdt miscdev\n"); - goto err_free; - } - - dev_info(&pdev->dev, - "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n", - wdt->regs, wdt->timeout, nowayout); - - return 0; - -err_free: - wdt = NULL; - return ret; -} - -static int __exit at32_wdt_remove(struct platform_device *pdev) -{ - if (wdt && platform_get_drvdata(pdev) == wdt) { - /* Stop the timer before we leave */ - if (!nowayout) - at32_wdt_stop(); - - misc_deregister(&wdt->miscdev); - wdt = NULL; - } - return 0; -} - -static void at32_wdt_shutdown(struct platform_device *pdev) -{ - at32_wdt_stop(); -} - -#ifdef CONFIG_PM -static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message) -{ - at32_wdt_stop(); - return 0; -} - -static int at32_wdt_resume(struct platform_device *pdev) -{ - if (wdt->users) - at32_wdt_start(); - return 0; -} -#else -#define at32_wdt_suspend NULL -#define at32_wdt_resume NULL -#endif - -/* work with hotplug and coldplug */ -MODULE_ALIAS("platform:at32_wdt"); - -static struct platform_driver at32_wdt_driver = { - .remove = __exit_p(at32_wdt_remove), - .suspend = at32_wdt_suspend, - .resume = at32_wdt_resume, - .driver = { - .name = "at32_wdt", - }, - .shutdown = at32_wdt_shutdown, -}; - -module_platform_driver_probe(at32_wdt_driver, at32_wdt_probe); - -MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); -MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X"); -MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 9083d3d922b0..814dff6045a4 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -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); diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c index 2f46487af86d..3e4c592c239f 100644 --- a/drivers/watchdog/davinci_wdt.c +++ b/drivers/watchdog/davinci_wdt.c @@ -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); diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 36be987ff9ef..c2f4ff516230 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -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, diff --git a/drivers/watchdog/eurotechwdt.c b/drivers/watchdog/eurotechwdt.c index 38e96712264f..47f77a6fdfd6 100644 --- a/drivers/watchdog/eurotechwdt.c +++ b/drivers/watchdog/eurotechwdt.c @@ -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); diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 8658dba21768..e0678c14480f 100644 --- a/drivers/watchdog/f71808e_wdt.c +++ b/drivers/watchdog/f71808e_wdt.c @@ -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); diff --git a/drivers/watchdog/ftwdt010_wdt.c b/drivers/watchdog/ftwdt010_wdt.c new file mode 100644 index 000000000000..a9c2912ee280 --- /dev/null +++ b/drivers/watchdog/ftwdt010_wdt.c @@ -0,0 +1,236 @@ +/* + * Watchdog driver for Faraday Technology FTWDT010 + * + * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> + * + * Inspired by the out-of-tree drivers from OpenWRT: + * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/watchdog.h> + +#define FTWDT010_WDCOUNTER 0x0 +#define FTWDT010_WDLOAD 0x4 +#define FTWDT010_WDRESTART 0x8 +#define FTWDT010_WDCR 0xC + +#define WDRESTART_MAGIC 0x5AB9 + +#define WDCR_CLOCK_5MHZ BIT(4) +#define WDCR_WDEXT BIT(3) +#define WDCR_WDINTR BIT(2) +#define WDCR_SYS_RST BIT(1) +#define WDCR_ENABLE BIT(0) + +#define WDT_CLOCK 5000000 /* 5 MHz */ + +struct ftwdt010_wdt { + struct watchdog_device wdd; + struct device *dev; + void __iomem *base; + bool has_irq; +}; + +static inline +struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd) +{ + return container_of(wdd, struct ftwdt010_wdt, wdd); +} + +static int ftwdt010_wdt_start(struct watchdog_device *wdd) +{ + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); + u32 enable; + + writel(wdd->timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD); + writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART); + /* set clock before enabling */ + enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST; + writel(enable, gwdt->base + FTWDT010_WDCR); + if (gwdt->has_irq) + enable |= WDCR_WDINTR; + enable |= WDCR_ENABLE; + writel(enable, gwdt->base + FTWDT010_WDCR); + + return 0; +} + +static int ftwdt010_wdt_stop(struct watchdog_device *wdd) +{ + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); + + writel(0, gwdt->base + FTWDT010_WDCR); + + return 0; +} + +static int ftwdt010_wdt_ping(struct watchdog_device *wdd) +{ + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); + + writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART); + + return 0; +} + +static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + wdd->timeout = timeout; + if (watchdog_active(wdd)) + ftwdt010_wdt_start(wdd); + + return 0; +} + +static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data) +{ + struct ftwdt010_wdt *gwdt = data; + + watchdog_notify_pretimeout(&gwdt->wdd); + + return IRQ_HANDLED; +} + +static const struct watchdog_ops ftwdt010_wdt_ops = { + .start = ftwdt010_wdt_start, + .stop = ftwdt010_wdt_stop, + .ping = ftwdt010_wdt_ping, + .set_timeout = ftwdt010_wdt_set_timeout, + .owner = THIS_MODULE, +}; + +static const struct watchdog_info ftwdt010_wdt_info = { + .options = WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE + | WDIOF_SETTIMEOUT, + .identity = KBUILD_MODNAME, +}; + + +static int ftwdt010_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct ftwdt010_wdt *gwdt; + unsigned int reg; + int irq; + int ret; + + gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL); + if (!gwdt) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + gwdt->base = devm_ioremap_resource(dev, res); + if (IS_ERR(gwdt->base)) + return PTR_ERR(gwdt->base); + + gwdt->dev = dev; + gwdt->wdd.info = &ftwdt010_wdt_info; + gwdt->wdd.ops = &ftwdt010_wdt_ops; + gwdt->wdd.min_timeout = 1; + gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK; + gwdt->wdd.parent = dev; + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 13 second + * default. + */ + gwdt->wdd.timeout = 13U; + watchdog_init_timeout(&gwdt->wdd, 0, dev); + + reg = readw(gwdt->base + FTWDT010_WDCR); + if (reg & WDCR_ENABLE) { + /* Watchdog was enabled by the bootloader, disable it. */ + reg &= ~WDCR_ENABLE; + writel(reg, gwdt->base + FTWDT010_WDCR); + } + + irq = platform_get_irq(pdev, 0); + if (irq) { + ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0, + "watchdog bark", gwdt); + if (ret) + return ret; + gwdt->has_irq = true; + } + + ret = devm_watchdog_register_device(dev, &gwdt->wdd); + if (ret) { + dev_err(&pdev->dev, "failed to register watchdog\n"); + return ret; + } + + /* Set up platform driver data */ + platform_set_drvdata(pdev, gwdt); + dev_info(dev, "FTWDT010 watchdog driver enabled\n"); + + return 0; +} + +static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev) +{ + struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev); + unsigned int reg; + + reg = readw(gwdt->base + FTWDT010_WDCR); + reg &= ~WDCR_ENABLE; + writel(reg, gwdt->base + FTWDT010_WDCR); + + return 0; +} + +static int __maybe_unused ftwdt010_wdt_resume(struct device *dev) +{ + struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev); + unsigned int reg; + + if (watchdog_active(&gwdt->wdd)) { + reg = readw(gwdt->base + FTWDT010_WDCR); + reg |= WDCR_ENABLE; + writel(reg, gwdt->base + FTWDT010_WDCR); + } + + return 0; +} + +static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend, + ftwdt010_wdt_resume) +}; + +#ifdef CONFIG_OF +static const struct of_device_id ftwdt010_wdt_match[] = { + { .compatible = "faraday,ftwdt010" }, + { .compatible = "cortina,gemini-watchdog" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match); +#endif + +static struct platform_driver ftwdt010_wdt_driver = { + .probe = ftwdt010_wdt_probe, + .driver = { + .name = "ftwdt010-wdt", + .of_match_table = of_match_ptr(ftwdt010_wdt_match), + .pm = &ftwdt010_wdt_dev_pm_ops, + }, +}; +module_platform_driver(ftwdt010_wdt_driver); +MODULE_AUTHOR("Linus Walleij"); +MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/gemini_wdt.c b/drivers/watchdog/gemini_wdt.c deleted file mode 100644 index 8155aa619e4c..000000000000 --- a/drivers/watchdog/gemini_wdt.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Watchdog driver for Cortina Systems Gemini SoC - * - * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> - * - * Inspired by the out-of-tree drivers from OpenWRT: - * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/bitops.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/of_device.h> -#include <linux/platform_device.h> -#include <linux/slab.h> -#include <linux/watchdog.h> - -#define GEMINI_WDCOUNTER 0x0 -#define GEMINI_WDLOAD 0x4 -#define GEMINI_WDRESTART 0x8 -#define GEMINI_WDCR 0xC - -#define WDRESTART_MAGIC 0x5AB9 - -#define WDCR_CLOCK_5MHZ BIT(4) -#define WDCR_SYS_RST BIT(1) -#define WDCR_ENABLE BIT(0) - -#define WDT_CLOCK 5000000 /* 5 MHz */ - -struct gemini_wdt { - struct watchdog_device wdd; - struct device *dev; - void __iomem *base; -}; - -static inline -struct gemini_wdt *to_gemini_wdt(struct watchdog_device *wdd) -{ - return container_of(wdd, struct gemini_wdt, wdd); -} - -static int gemini_wdt_start(struct watchdog_device *wdd) -{ - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); - - writel(wdd->timeout * WDT_CLOCK, gwdt->base + GEMINI_WDLOAD); - writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); - /* set clock before enabling */ - writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, - gwdt->base + GEMINI_WDCR); - writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, - gwdt->base + GEMINI_WDCR); - - return 0; -} - -static int gemini_wdt_stop(struct watchdog_device *wdd) -{ - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); - - writel(0, gwdt->base + GEMINI_WDCR); - - return 0; -} - -static int gemini_wdt_ping(struct watchdog_device *wdd) -{ - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); - - writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); - - return 0; -} - -static int gemini_wdt_set_timeout(struct watchdog_device *wdd, - unsigned int timeout) -{ - wdd->timeout = timeout; - if (watchdog_active(wdd)) - gemini_wdt_start(wdd); - - return 0; -} - -static irqreturn_t gemini_wdt_interrupt(int irq, void *data) -{ - struct gemini_wdt *gwdt = data; - - watchdog_notify_pretimeout(&gwdt->wdd); - - return IRQ_HANDLED; -} - -static const struct watchdog_ops gemini_wdt_ops = { - .start = gemini_wdt_start, - .stop = gemini_wdt_stop, - .ping = gemini_wdt_ping, - .set_timeout = gemini_wdt_set_timeout, - .owner = THIS_MODULE, -}; - -static const struct watchdog_info gemini_wdt_info = { - .options = WDIOF_KEEPALIVEPING - | WDIOF_MAGICCLOSE - | WDIOF_SETTIMEOUT, - .identity = KBUILD_MODNAME, -}; - - -static int gemini_wdt_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct resource *res; - struct gemini_wdt *gwdt; - unsigned int reg; - int irq; - int ret; - - gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL); - if (!gwdt) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - gwdt->base = devm_ioremap_resource(dev, res); - if (IS_ERR(gwdt->base)) - return PTR_ERR(gwdt->base); - - irq = platform_get_irq(pdev, 0); - if (!irq) - return -EINVAL; - - gwdt->dev = dev; - gwdt->wdd.info = &gemini_wdt_info; - gwdt->wdd.ops = &gemini_wdt_ops; - gwdt->wdd.min_timeout = 1; - gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK; - gwdt->wdd.parent = dev; - - /* - * If 'timeout-sec' unspecified in devicetree, assume a 13 second - * default. - */ - gwdt->wdd.timeout = 13U; - watchdog_init_timeout(&gwdt->wdd, 0, dev); - - reg = readw(gwdt->base + GEMINI_WDCR); - if (reg & WDCR_ENABLE) { - /* Watchdog was enabled by the bootloader, disable it. */ - reg &= ~WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); - } - - ret = devm_request_irq(dev, irq, gemini_wdt_interrupt, 0, - "watchdog bark", gwdt); - if (ret) - return ret; - - ret = devm_watchdog_register_device(dev, &gwdt->wdd); - if (ret) { - dev_err(&pdev->dev, "failed to register watchdog\n"); - return ret; - } - - /* Set up platform driver data */ - platform_set_drvdata(pdev, gwdt); - dev_info(dev, "Gemini watchdog driver enabled\n"); - - return 0; -} - -static int __maybe_unused gemini_wdt_suspend(struct device *dev) -{ - struct gemini_wdt *gwdt = dev_get_drvdata(dev); - unsigned int reg; - - reg = readw(gwdt->base + GEMINI_WDCR); - reg &= ~WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); - - return 0; -} - -static int __maybe_unused gemini_wdt_resume(struct device *dev) -{ - struct gemini_wdt *gwdt = dev_get_drvdata(dev); - unsigned int reg; - - if (watchdog_active(&gwdt->wdd)) { - reg = readw(gwdt->base + GEMINI_WDCR); - reg |= WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); - } - - return 0; -} - -static const struct dev_pm_ops gemini_wdt_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(gemini_wdt_suspend, - gemini_wdt_resume) -}; - -#ifdef CONFIG_OF -static const struct of_device_id gemini_wdt_match[] = { - { .compatible = "cortina,gemini-watchdog" }, - {}, -}; -MODULE_DEVICE_TABLE(of, gemini_wdt_match); -#endif - -static struct platform_driver gemini_wdt_driver = { - .probe = gemini_wdt_probe, - .driver = { - .name = "gemini-wdt", - .of_match_table = of_match_ptr(gemini_wdt_match), - .pm = &gemini_wdt_dev_pm_ops, - }, -}; -module_platform_driver(gemini_wdt_driver); -MODULE_AUTHOR("Linus Walleij"); -MODULE_DESCRIPTION("Watchdog driver for Gemini"); -MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c index cb66c2f99ff1..3ade28190341 100644 --- a/drivers/watchdog/gpio_wdt.c +++ b/drivers/watchdog/gpio_wdt.c @@ -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); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 67fbe35ce7cf..f1f00dfc0e68 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -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; diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index d7befd58b391..950c71a8bb22 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -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); diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c index f2e4e1eeb8dd..cc262284a6aa 100644 --- a/drivers/watchdog/ib700wdt.c +++ b/drivers/watchdog/ib700wdt.c @@ -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); diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 4874b0f18650..518dfa1047cb 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -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); } diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index 20627f22baf6..aafbeb96561b 100644 --- a/drivers/watchdog/jz4740_wdt.c +++ b/drivers/watchdog/jz4740_wdt.c @@ -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); diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c index ea60b29494fb..b8194b02abe0 100644 --- a/drivers/watchdog/mei_wdt.c +++ b/drivers/watchdog/mei_wdt.c @@ -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); } diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c index 6610e9217dbc..aca2d6323f8a 100644 --- a/drivers/watchdog/mpc8xxx_wdt.c +++ b/drivers/watchdog/mpc8xxx_wdt.c @@ -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; diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c index db38f8017218..5c4a764717c4 100644 --- a/drivers/watchdog/mt7621_wdt.c +++ b/drivers/watchdog/mt7621_wdt.c @@ -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, diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c index 83af7d6cc37c..ea676d233e1e 100644 --- a/drivers/watchdog/orion_wdt.c +++ b/drivers/watchdog/orion_wdt.c @@ -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)) diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c index c0d07eef2640..1f78f0908621 100644 --- a/drivers/watchdog/pcwd_pci.c +++ b/drivers/watchdog/pcwd_pci.c @@ -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); diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index b9e376c8e2e3..4d02f26156f9 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -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); diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c new file mode 100644 index 000000000000..d001c17ddfde --- /dev/null +++ b/drivers/watchdog/rtd119x_wdt.c @@ -0,0 +1,168 @@ +/* + * Realtek RTD129x watchdog + * + * Copyright (c) 2017 Andreas Färber + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define RTD119X_TCWCR 0x0 +#define RTD119X_TCWTR 0x4 +#define RTD119X_TCWOV 0xc + +#define RTD119X_TCWCR_WDEN_DISABLED 0xa5 +#define RTD119X_TCWCR_WDEN_ENABLED 0xff +#define RTD119X_TCWCR_WDEN_MASK 0xff + +#define RTD119X_TCWTR_WDCLR BIT(0) + +struct rtd119x_watchdog_device { + struct watchdog_device wdt_dev; + void __iomem *base; + struct clk *clk; +}; + +static int rtd119x_wdt_start(struct watchdog_device *wdev) +{ + struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); + u32 val; + + val = readl_relaxed(data->base + RTD119X_TCWCR); + val &= ~RTD119X_TCWCR_WDEN_MASK; + val |= RTD119X_TCWCR_WDEN_ENABLED; + writel(val, data->base + RTD119X_TCWCR); + + return 0; +} + +static int rtd119x_wdt_stop(struct watchdog_device *wdev) +{ + struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); + u32 val; + + val = readl_relaxed(data->base + RTD119X_TCWCR); + val &= ~RTD119X_TCWCR_WDEN_MASK; + val |= RTD119X_TCWCR_WDEN_DISABLED; + writel(val, data->base + RTD119X_TCWCR); + + return 0; +} + +static int rtd119x_wdt_ping(struct watchdog_device *wdev) +{ + struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); + + writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR); + + return rtd119x_wdt_start(wdev); +} + +static int rtd119x_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val) +{ + struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); + + writel(val * clk_get_rate(data->clk), data->base + RTD119X_TCWOV); + + data->wdt_dev.timeout = val; + + return 0; +} + +static const struct watchdog_ops rtd119x_wdt_ops = { + .owner = THIS_MODULE, + .start = rtd119x_wdt_start, + .stop = rtd119x_wdt_stop, + .ping = rtd119x_wdt_ping, + .set_timeout = rtd119x_wdt_set_timeout, +}; + +static const struct watchdog_info rtd119x_wdt_info = { + .identity = "rtd119x-wdt", + .options = 0, +}; + +static const struct of_device_id rtd119x_wdt_dt_ids[] = { + { .compatible = "realtek,rtd1295-watchdog" }, + { } +}; + +static int rtd119x_wdt_probe(struct platform_device *pdev) +{ + struct rtd119x_watchdog_device *data; + struct resource *res; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->clk = of_clk_get(pdev->dev.of_node, 0); + if (IS_ERR(data->clk)) + return PTR_ERR(data->clk); + + ret = clk_prepare_enable(data->clk); + if (ret) { + clk_put(data->clk); + return ret; + } + + data->wdt_dev.info = &rtd119x_wdt_info; + data->wdt_dev.ops = &rtd119x_wdt_ops; + data->wdt_dev.timeout = 120; + data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk); + data->wdt_dev.min_timeout = 1; + data->wdt_dev.parent = &pdev->dev; + + watchdog_stop_on_reboot(&data->wdt_dev); + watchdog_set_drvdata(&data->wdt_dev, data); + platform_set_drvdata(pdev, data); + + writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR); + rtd119x_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout); + rtd119x_wdt_stop(&data->wdt_dev); + + ret = devm_watchdog_register_device(&pdev->dev, &data->wdt_dev); + if (ret) { + clk_disable_unprepare(data->clk); + clk_put(data->clk); + return ret; + } + + return 0; +} + +static int rtd119x_wdt_remove(struct platform_device *pdev) +{ + struct rtd119x_watchdog_device *data = platform_get_drvdata(pdev); + + watchdog_unregister_device(&data->wdt_dev); + + clk_disable_unprepare(data->clk); + clk_put(data->clk); + + return 0; +} + +static struct platform_driver rtd119x_wdt_driver = { + .probe = rtd119x_wdt_probe, + .remove = rtd119x_wdt_remove, + .driver = { + .name = "rtd1295-watchdog", + .of_match_table = rtd119x_wdt_dt_ids, + }, +}; +builtin_platform_driver(rtd119x_wdt_driver); diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 028618c5eeba..41aaae2d5287 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -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 @@ unreg_platform_driver: return err; } -static void __exit sp5100_tco_cleanup_module(void) +static void __exit sp5100_tco_exit(void) { platform_device_unregister(sp5100_tco_platform_device); platform_driver_unregister(&sp5100_tco_driver); - pr_info("SP5100/SB800 TCO Watchdog Module Unloaded\n"); } -module_init(sp5100_tco_init_module); -module_exit(sp5100_tco_cleanup_module); +module_init(sp5100_tco_init); +module_exit(sp5100_tco_exit); MODULE_AUTHOR("Priyanka Gupta"); MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset"); diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index 1af4dee71337..87eaf357ae01 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -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 diff --git a/drivers/watchdog/sprd_wdt.c b/drivers/watchdog/sprd_wdt.c new file mode 100644 index 000000000000..a8b280ff33e0 --- /dev/null +++ b/drivers/watchdog/sprd_wdt.c @@ -0,0 +1,399 @@ +/* + * Spreadtrum watchdog driver + * Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define SPRD_WDT_LOAD_LOW 0x0 +#define SPRD_WDT_LOAD_HIGH 0x4 +#define SPRD_WDT_CTRL 0x8 +#define SPRD_WDT_INT_CLR 0xc +#define SPRD_WDT_INT_RAW 0x10 +#define SPRD_WDT_INT_MSK 0x14 +#define SPRD_WDT_CNT_LOW 0x18 +#define SPRD_WDT_CNT_HIGH 0x1c +#define SPRD_WDT_LOCK 0x20 +#define SPRD_WDT_IRQ_LOAD_LOW 0x2c +#define SPRD_WDT_IRQ_LOAD_HIGH 0x30 + +/* WDT_CTRL */ +#define SPRD_WDT_INT_EN_BIT BIT(0) +#define SPRD_WDT_CNT_EN_BIT BIT(1) +#define SPRD_WDT_NEW_VER_EN BIT(2) +#define SPRD_WDT_RST_EN_BIT BIT(3) + +/* WDT_INT_CLR */ +#define SPRD_WDT_INT_CLEAR_BIT BIT(0) +#define SPRD_WDT_RST_CLEAR_BIT BIT(3) + +/* WDT_INT_RAW */ +#define SPRD_WDT_INT_RAW_BIT BIT(0) +#define SPRD_WDT_RST_RAW_BIT BIT(3) +#define SPRD_WDT_LD_BUSY_BIT BIT(4) + +/* 1s equal to 32768 counter steps */ +#define SPRD_WDT_CNT_STEP 32768 + +#define SPRD_WDT_UNLOCK_KEY 0xe551 +#define SPRD_WDT_MIN_TIMEOUT 3 +#define SPRD_WDT_MAX_TIMEOUT 60 + +#define SPRD_WDT_CNT_HIGH_SHIFT 16 +#define SPRD_WDT_LOW_VALUE_MASK GENMASK(15, 0) +#define SPRD_WDT_LOAD_TIMEOUT 1000 + +struct sprd_wdt { + void __iomem *base; + struct watchdog_device wdd; + struct clk *enable; + struct clk *rtc_enable; + int irq; +}; + +static inline struct sprd_wdt *to_sprd_wdt(struct watchdog_device *wdd) +{ + return container_of(wdd, struct sprd_wdt, wdd); +} + +static inline void sprd_wdt_lock(void __iomem *addr) +{ + writel_relaxed(0x0, addr + SPRD_WDT_LOCK); +} + +static inline void sprd_wdt_unlock(void __iomem *addr) +{ + writel_relaxed(SPRD_WDT_UNLOCK_KEY, addr + SPRD_WDT_LOCK); +} + +static irqreturn_t sprd_wdt_isr(int irq, void *dev_id) +{ + struct sprd_wdt *wdt = (struct sprd_wdt *)dev_id; + + sprd_wdt_unlock(wdt->base); + writel_relaxed(SPRD_WDT_INT_CLEAR_BIT, wdt->base + SPRD_WDT_INT_CLR); + sprd_wdt_lock(wdt->base); + watchdog_notify_pretimeout(&wdt->wdd); + return IRQ_HANDLED; +} + +static u32 sprd_wdt_get_cnt_value(struct sprd_wdt *wdt) +{ + u32 val; + + val = readl_relaxed(wdt->base + SPRD_WDT_CNT_HIGH) << + SPRD_WDT_CNT_HIGH_SHIFT; + val |= readl_relaxed(wdt->base + SPRD_WDT_CNT_LOW) & + SPRD_WDT_LOW_VALUE_MASK; + + return val; +} + +static int sprd_wdt_load_value(struct sprd_wdt *wdt, u32 timeout, + u32 pretimeout) +{ + u32 val, delay_cnt = 0; + u32 tmr_step = timeout * SPRD_WDT_CNT_STEP; + u32 prtmr_step = pretimeout * SPRD_WDT_CNT_STEP; + + sprd_wdt_unlock(wdt->base); + writel_relaxed((tmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) & + SPRD_WDT_LOW_VALUE_MASK, wdt->base + SPRD_WDT_LOAD_HIGH); + writel_relaxed((tmr_step & SPRD_WDT_LOW_VALUE_MASK), + wdt->base + SPRD_WDT_LOAD_LOW); + writel_relaxed((prtmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) & + SPRD_WDT_LOW_VALUE_MASK, + wdt->base + SPRD_WDT_IRQ_LOAD_HIGH); + writel_relaxed(prtmr_step & SPRD_WDT_LOW_VALUE_MASK, + wdt->base + SPRD_WDT_IRQ_LOAD_LOW); + sprd_wdt_lock(wdt->base); + + /* + * Waiting the load value operation done, + * it needs two or three RTC clock cycles. + */ + do { + val = readl_relaxed(wdt->base + SPRD_WDT_INT_RAW); + if (!(val & SPRD_WDT_LD_BUSY_BIT)) + break; + + cpu_relax(); + } while (delay_cnt++ < SPRD_WDT_LOAD_TIMEOUT); + + if (delay_cnt >= SPRD_WDT_LOAD_TIMEOUT) + return -EBUSY; + return 0; +} + +static int sprd_wdt_enable(struct sprd_wdt *wdt) +{ + u32 val; + int ret; + + ret = clk_prepare_enable(wdt->enable); + if (ret) + return ret; + ret = clk_prepare_enable(wdt->rtc_enable); + if (ret) + return ret; + + sprd_wdt_unlock(wdt->base); + val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); + val |= SPRD_WDT_NEW_VER_EN; + writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); + sprd_wdt_lock(wdt->base); + return 0; +} + +static void sprd_wdt_disable(void *_data) +{ + struct sprd_wdt *wdt = _data; + + sprd_wdt_unlock(wdt->base); + writel_relaxed(0x0, wdt->base + SPRD_WDT_CTRL); + sprd_wdt_lock(wdt->base); + + clk_disable_unprepare(wdt->rtc_enable); + clk_disable_unprepare(wdt->enable); +} + +static int sprd_wdt_start(struct watchdog_device *wdd) +{ + struct sprd_wdt *wdt = to_sprd_wdt(wdd); + u32 val; + int ret; + + ret = sprd_wdt_load_value(wdt, wdd->timeout, wdd->pretimeout); + if (ret) + return ret; + + sprd_wdt_unlock(wdt->base); + val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); + val |= SPRD_WDT_CNT_EN_BIT | SPRD_WDT_INT_EN_BIT | SPRD_WDT_RST_EN_BIT; + writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); + sprd_wdt_lock(wdt->base); + set_bit(WDOG_HW_RUNNING, &wdd->status); + + return 0; +} + +static int sprd_wdt_stop(struct watchdog_device *wdd) +{ + struct sprd_wdt *wdt = to_sprd_wdt(wdd); + u32 val; + + sprd_wdt_unlock(wdt->base); + val = readl_relaxed(wdt->base + SPRD_WDT_CTRL); + val &= ~(SPRD_WDT_CNT_EN_BIT | SPRD_WDT_RST_EN_BIT | + SPRD_WDT_INT_EN_BIT); + writel_relaxed(val, wdt->base + SPRD_WDT_CTRL); + sprd_wdt_lock(wdt->base); + return 0; +} + +static int sprd_wdt_set_timeout(struct watchdog_device *wdd, + u32 timeout) +{ + struct sprd_wdt *wdt = to_sprd_wdt(wdd); + + if (timeout == wdd->timeout) + return 0; + + wdd->timeout = timeout; + + return sprd_wdt_load_value(wdt, timeout, wdd->pretimeout); +} + +static int sprd_wdt_set_pretimeout(struct watchdog_device *wdd, + u32 new_pretimeout) +{ + struct sprd_wdt *wdt = to_sprd_wdt(wdd); + + if (new_pretimeout < wdd->min_timeout) + return -EINVAL; + + wdd->pretimeout = new_pretimeout; + + return sprd_wdt_load_value(wdt, wdd->timeout, new_pretimeout); +} + +static u32 sprd_wdt_get_timeleft(struct watchdog_device *wdd) +{ + struct sprd_wdt *wdt = to_sprd_wdt(wdd); + u32 val; + + val = sprd_wdt_get_cnt_value(wdt); + val = val / SPRD_WDT_CNT_STEP; + + return val; +} + +static const struct watchdog_ops sprd_wdt_ops = { + .owner = THIS_MODULE, + .start = sprd_wdt_start, + .stop = sprd_wdt_stop, + .set_timeout = sprd_wdt_set_timeout, + .set_pretimeout = sprd_wdt_set_pretimeout, + .get_timeleft = sprd_wdt_get_timeleft, +}; + +static const struct watchdog_info sprd_wdt_info = { + .options = WDIOF_SETTIMEOUT | + WDIOF_PRETIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .identity = "Spreadtrum Watchdog Timer", +}; + +static int sprd_wdt_probe(struct platform_device *pdev) +{ + struct resource *wdt_res; + struct sprd_wdt *wdt; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, wdt_res); + if (IS_ERR(wdt->base)) { + dev_err(&pdev->dev, "failed to map memory resource\n"); + return PTR_ERR(wdt->base); + } + + wdt->enable = devm_clk_get(&pdev->dev, "enable"); + if (IS_ERR(wdt->enable)) { + dev_err(&pdev->dev, "can't get the enable clock\n"); + return PTR_ERR(wdt->enable); + } + + wdt->rtc_enable = devm_clk_get(&pdev->dev, "rtc_enable"); + if (IS_ERR(wdt->rtc_enable)) { + dev_err(&pdev->dev, "can't get the rtc enable clock\n"); + return PTR_ERR(wdt->rtc_enable); + } + + wdt->irq = platform_get_irq(pdev, 0); + if (wdt->irq < 0) { + dev_err(&pdev->dev, "failed to get IRQ resource\n"); + return wdt->irq; + } + + ret = devm_request_irq(&pdev->dev, wdt->irq, sprd_wdt_isr, + IRQF_NO_SUSPEND, "sprd-wdt", (void *)wdt); + if (ret) { + dev_err(&pdev->dev, "failed to register irq\n"); + return ret; + } + + wdt->wdd.info = &sprd_wdt_info; + wdt->wdd.ops = &sprd_wdt_ops; + wdt->wdd.parent = &pdev->dev; + wdt->wdd.min_timeout = SPRD_WDT_MIN_TIMEOUT; + wdt->wdd.max_timeout = SPRD_WDT_MAX_TIMEOUT; + wdt->wdd.timeout = SPRD_WDT_MAX_TIMEOUT; + + ret = sprd_wdt_enable(wdt); + if (ret) { + dev_err(&pdev->dev, "failed to enable wdt\n"); + return ret; + } + ret = devm_add_action(&pdev->dev, sprd_wdt_disable, wdt); + if (ret) { + sprd_wdt_disable(wdt); + dev_err(&pdev->dev, "Failed to add wdt disable action\n"); + return ret; + } + + watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT); + watchdog_init_timeout(&wdt->wdd, 0, &pdev->dev); + + ret = devm_watchdog_register_device(&pdev->dev, &wdt->wdd); + if (ret) { + sprd_wdt_disable(wdt); + dev_err(&pdev->dev, "failed to register watchdog\n"); + return ret; + } + platform_set_drvdata(pdev, wdt); + + return 0; +} + +static int __maybe_unused sprd_wdt_pm_suspend(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + struct sprd_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + sprd_wdt_stop(&wdt->wdd); + sprd_wdt_disable(wdt); + + return 0; +} + +static int __maybe_unused sprd_wdt_pm_resume(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + struct sprd_wdt *wdt = dev_get_drvdata(dev); + int ret; + + ret = sprd_wdt_enable(wdt); + if (ret) + return ret; + + if (watchdog_active(wdd)) { + ret = sprd_wdt_start(&wdt->wdd); + if (ret) { + sprd_wdt_disable(wdt); + return ret; + } + } + + return 0; +} + +static const struct dev_pm_ops sprd_wdt_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sprd_wdt_pm_suspend, + sprd_wdt_pm_resume) +}; + +static const struct of_device_id sprd_wdt_match_table[] = { + { .compatible = "sprd,sp9860-wdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sprd_wdt_match_table); + +static struct platform_driver sprd_watchdog_driver = { + .probe = sprd_wdt_probe, + .driver = { + .name = "sprd-wdt", + .of_match_table = sprd_wdt_match_table, + .pm = &sprd_wdt_pm_ops, + }, +}; +module_platform_driver(sprd_watchdog_driver); + +MODULE_AUTHOR("Eric Long <eric.long@spreadtrum.com>"); +MODULE_DESCRIPTION("Spreadtrum Watchdog Timer Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index be64a8699de3..c97ad5619cb0 100644 --- a/drivers/watchdog/stm32_iwdg.c +++ b/drivers/watchdog/stm32_iwdg.c @@ -1,12 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Driver for STM32 Independent Watchdog * - * Copyright (C) Yannick Fertre 2017 - * Author: Yannick Fertre <yannick.fertre@st.com> + * Copyright (C) STMicroelectronics 2017 + * Author: Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics. * * This driver is based on tegra_wdt.c * - * License terms: GNU General Public License (GPL), version 2 */ #include <linux/clk.h> diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index 9728fa32c357..802e31b1416d 100644 --- a/drivers/watchdog/sunxi_wdt.c +++ b/drivers/watchdog/sunxi_wdt.c @@ -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)) diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 8a8d952f8df9..eb8fa25f8eb2 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -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 * diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 1e971a50d7fb..ffbdc4642ea5 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -36,9 +36,10 @@ #include <linux/errno.h> /* For the -ENODEV/... values */ #include <linux/fs.h> /* For file operations */ #include <linux/init.h> /* For __init/__exit/... */ -#include <linux/jiffies.h> /* For timeout functions */ +#include <linux/hrtimer.h> /* For hrtimers */ #include <linux/kernel.h> /* For printk/panic/... */ #include <linux/kref.h> /* For data references */ +#include <linux/kthread.h> /* For kthread_work */ #include <linux/miscdevice.h> /* For handling misc devices */ #include <linux/module.h> /* For module stuff/... */ #include <linux/mutex.h> /* For mutexes */ @@ -46,9 +47,10 @@ #include <linux/slab.h> /* For memory functions */ #include <linux/types.h> /* For standard types (like size_t) */ #include <linux/watchdog.h> /* For watchdog specific items */ -#include <linux/workqueue.h> /* For workqueue */ #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <uapi/linux/sched/types.h> /* For struct sched_param */ + #include "watchdog_core.h" #include "watchdog_pretimeout.h" @@ -65,9 +67,10 @@ struct watchdog_core_data { struct cdev cdev; struct watchdog_device *wdd; struct mutex lock; - unsigned long last_keepalive; - unsigned long last_hw_keepalive; - struct delayed_work work; + ktime_t last_keepalive; + ktime_t last_hw_keepalive; + struct hrtimer timer; + struct kthread_work work; unsigned long status; /* Internal status bits */ #define _WDOG_DEV_OPEN 0 /* Opened ? */ #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ @@ -79,7 +82,7 @@ static dev_t watchdog_devt; /* Reference to watchdog device behind /dev/watchdog */ static struct watchdog_core_data *old_wd_data; -static struct workqueue_struct *watchdog_wq; +static struct kthread_worker *watchdog_kworker; static bool handle_boot_enabled = IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED); @@ -107,18 +110,19 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd) (t && !watchdog_active(wdd) && watchdog_hw_running(wdd)); } -static long watchdog_next_keepalive(struct watchdog_device *wdd) +static ktime_t watchdog_next_keepalive(struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; unsigned int timeout_ms = wdd->timeout * 1000; - unsigned long keepalive_interval; - unsigned long last_heartbeat; - unsigned long virt_timeout; + ktime_t keepalive_interval; + ktime_t last_heartbeat, latest_heartbeat; + ktime_t virt_timeout; unsigned int hw_heartbeat_ms; - virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms); + virt_timeout = ktime_add(wd_data->last_keepalive, + ms_to_ktime(timeout_ms)); hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms); - keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); + keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2); if (!watchdog_active(wdd)) return keepalive_interval; @@ -128,8 +132,11 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd) * after the most recent ping from userspace, the last * worker ping has to come in hw_heartbeat_ms before this timeout. */ - last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms); - return min_t(long, last_heartbeat - jiffies, keepalive_interval); + last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms)); + latest_heartbeat = ktime_sub(last_heartbeat, ktime_get()); + if (ktime_before(latest_heartbeat, keepalive_interval)) + return latest_heartbeat; + return keepalive_interval; } static inline void watchdog_update_worker(struct watchdog_device *wdd) @@ -137,29 +144,33 @@ static inline void watchdog_update_worker(struct watchdog_device *wdd) struct watchdog_core_data *wd_data = wdd->wd_data; if (watchdog_need_worker(wdd)) { - long t = watchdog_next_keepalive(wdd); + ktime_t t = watchdog_next_keepalive(wdd); if (t > 0) - mod_delayed_work(watchdog_wq, &wd_data->work, t); + hrtimer_start(&wd_data->timer, t, HRTIMER_MODE_REL); } else { - cancel_delayed_work(&wd_data->work); + hrtimer_cancel(&wd_data->timer); } } static int __watchdog_ping(struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; - unsigned long earliest_keepalive = wd_data->last_hw_keepalive + - msecs_to_jiffies(wdd->min_hw_heartbeat_ms); + ktime_t earliest_keepalive, now; int err; - if (time_is_after_jiffies(earliest_keepalive)) { - mod_delayed_work(watchdog_wq, &wd_data->work, - earliest_keepalive - jiffies); + earliest_keepalive = ktime_add(wd_data->last_hw_keepalive, + ms_to_ktime(wdd->min_hw_heartbeat_ms)); + now = ktime_get(); + + if (ktime_after(earliest_keepalive, now)) { + hrtimer_start(&wd_data->timer, + ktime_sub(earliest_keepalive, now), + HRTIMER_MODE_REL); return 0; } - wd_data->last_hw_keepalive = jiffies; + wd_data->last_hw_keepalive = now; if (wdd->ops->ping) err = wdd->ops->ping(wdd); /* ping the watchdog */ @@ -192,7 +203,7 @@ static int watchdog_ping(struct watchdog_device *wdd) set_bit(_WDOG_KEEPALIVE, &wd_data->status); - wd_data->last_keepalive = jiffies; + wd_data->last_keepalive = ktime_get(); return __watchdog_ping(wdd); } @@ -203,12 +214,11 @@ static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data) return wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd)); } -static void watchdog_ping_work(struct work_struct *work) +static void watchdog_ping_work(struct kthread_work *work) { struct watchdog_core_data *wd_data; - wd_data = container_of(to_delayed_work(work), struct watchdog_core_data, - work); + wd_data = container_of(work, struct watchdog_core_data, work); mutex_lock(&wd_data->lock); if (watchdog_worker_should_ping(wd_data)) @@ -216,6 +226,16 @@ static void watchdog_ping_work(struct work_struct *work) mutex_unlock(&wd_data->lock); } +static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer) +{ + struct watchdog_core_data *wd_data; + + wd_data = container_of(timer, struct watchdog_core_data, timer); + + kthread_queue_work(watchdog_kworker, &wd_data->work); + return HRTIMER_NORESTART; +} + /* * watchdog_start: wrapper to start the watchdog. * @wdd: the watchdog device to start @@ -230,7 +250,7 @@ static void watchdog_ping_work(struct work_struct *work) static int watchdog_start(struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; - unsigned long started_at; + ktime_t started_at; int err; if (watchdog_active(wdd)) @@ -238,7 +258,7 @@ static int watchdog_start(struct watchdog_device *wdd) set_bit(_WDOG_KEEPALIVE, &wd_data->status); - started_at = jiffies; + started_at = ktime_get(); if (watchdog_hw_running(wdd) && wdd->ops->ping) err = wdd->ops->ping(wdd); else @@ -720,7 +740,7 @@ static long watchdog_ioctl(struct file *file, unsigned int cmd, err = watchdog_ping(wdd); if (err < 0) break; - /* Fall */ + /* fall through */ case WDIOC_GETTIMEOUT: /* timeout == 0 means that we don't know the timeout */ if (wdd->timeout == 0) { @@ -769,6 +789,7 @@ static int watchdog_open(struct inode *inode, struct file *file) { struct watchdog_core_data *wd_data; struct watchdog_device *wdd; + bool hw_running; int err; /* Get the corresponding watchdog device */ @@ -788,7 +809,8 @@ static int watchdog_open(struct inode *inode, struct file *file) * If the /dev/watchdog device is open, we don't want the module * to be unloaded. */ - if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) { + hw_running = watchdog_hw_running(wdd); + if (!hw_running && !try_module_get(wdd->ops->owner)) { err = -EBUSY; goto out_clear; } @@ -799,7 +821,7 @@ static int watchdog_open(struct inode *inode, struct file *file) file->private_data = wd_data; - if (!watchdog_hw_running(wdd)) + if (!hw_running) kref_get(&wd_data->kref); /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ @@ -919,10 +941,12 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) wd_data->wdd = wdd; wdd->wd_data = wd_data; - if (!watchdog_wq) + if (IS_ERR_OR_NULL(watchdog_kworker)) return -ENODEV; - INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work); + kthread_init_work(&wd_data->work, watchdog_ping_work); + hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + wd_data->timer.function = watchdog_timer_expired; if (wdd->id == 0) { old_wd_data = wd_data; @@ -958,21 +982,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) } /* Record time of most recent heartbeat as 'just before now'. */ - wd_data->last_hw_keepalive = jiffies - 1; + wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1); /* * If the watchdog is running, prevent its driver from being unloaded, * and schedule an immediate ping. */ if (watchdog_hw_running(wdd)) { - if (handle_boot_enabled) { - __module_get(wdd->ops->owner); - kref_get(&wd_data->kref); - queue_delayed_work(watchdog_wq, &wd_data->work, 0); - } else { + __module_get(wdd->ops->owner); + kref_get(&wd_data->kref); + if (handle_boot_enabled) + hrtimer_start(&wd_data->timer, 0, HRTIMER_MODE_REL); + else pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n", - wdd->id); - } + wdd->id); } return 0; @@ -1006,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd) watchdog_stop(wdd); } - cancel_delayed_work_sync(&wd_data->work); + hrtimer_cancel(&wd_data->timer); + kthread_cancel_work_sync(&wd_data->work); kref_put(&wd_data->kref, watchdog_core_data_release); } @@ -1110,13 +1134,14 @@ void watchdog_dev_unregister(struct watchdog_device *wdd) int __init watchdog_dev_init(void) { int err; + struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1,}; - watchdog_wq = alloc_workqueue("watchdogd", - WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); - if (!watchdog_wq) { - pr_err("Failed to create watchdog workqueue\n"); - return -ENOMEM; + watchdog_kworker = kthread_create_worker(0, "watchdogd"); + if (IS_ERR(watchdog_kworker)) { + pr_err("Failed to create watchdog kworker\n"); + return PTR_ERR(watchdog_kworker); } + sched_setscheduler(watchdog_kworker->task, SCHED_FIFO, ¶m); err = class_register(&watchdog_class); if (err < 0) { @@ -1135,7 +1160,7 @@ int __init watchdog_dev_init(void) err_alloc: class_unregister(&watchdog_class); err_register: - destroy_workqueue(watchdog_wq); + kthread_destroy_worker(watchdog_kworker); return err; } @@ -1149,7 +1174,7 @@ void __exit watchdog_dev_exit(void) { unregister_chrdev_region(watchdog_devt, MAX_DOGS); class_unregister(&watchdog_class); - destroy_workqueue(watchdog_wq); + kthread_destroy_worker(watchdog_kworker); } module_param(handle_boot_enabled, bool, 0444); diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c index bc7addc2dc06..10e2cda0ee5a 100644 --- a/drivers/watchdog/wdt_pci.c +++ b/drivers/watchdog/wdt_pci.c @@ -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: diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c index cf0e650c2015..f1c016d015b3 100644 --- a/drivers/watchdog/xen_wdt.c +++ b/drivers/watchdog/xen_wdt.c @@ -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"); |