From 4bcd615fad6adddc68b058d498b30a9e0e0db77a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 25 Sep 2017 09:17:01 -0700 Subject: [PATCH 01/74] watchdog: Fix potential kref imbalance when opening watchdog If a watchdog driver's open function sets WDOG_HW_RUNNING with the expectation that the watchdog can not be stopped, but then stops the watchdog anyway in its stop function, kref_get() wil not be called in watchdog_open(). If the watchdog then stops on close, WDOG_HW_RUNNING will be cleared and kref_put() will be called, causing a kref imbalance. As result the character device data structure will be released, which in turn will cause the system to crash on the next call to watchdog_open(). Fixes: ee142889e32f5 ("watchdog: Introduce WDOG_HW_RUNNING flag") Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 1e971a50d7fb7..12fe47d8237b0 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -769,6 +769,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 +789,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 +801,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 */ From 914d65f3f013ba2556c7beec5d3baac7b3292504 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 25 Sep 2017 09:17:02 -0700 Subject: [PATCH 02/74] watchdog: Fix kref imbalance seen if handle_boot_enabled=0 If handle_boot_enabled is set to 0, the watchdog driver module use counter will not be increased and kref_get() will not be called when registering the watchdog. Subsequently, on open, this does not happen either because the code believes that it was already done because the hardware watchdog is marked as running. We could introduce a state variable to indicate this state, but let's just increase the module use counter and call kref_get() unconditionally if the hardware watchdog is running when a driver is registering itself to keep the code simple. Fixes: 2501b015313fe ("watchdog: core: add option to avoid early ...") Cc: Sebastian Reichel Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 12fe47d8237b0..95b96f3cb36f7 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -967,14 +967,13 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) * and schedule an immediate ping. */ if (watchdog_hw_running(wdd)) { - if (handle_boot_enabled) { - __module_get(wdd->ops->owner); - kref_get(&wd_data->kref); + __module_get(wdd->ops->owner); + kref_get(&wd_data->kref); + if (handle_boot_enabled) queue_delayed_work(watchdog_wq, &wd_data->work, 0); - } else { + else pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n", - wdd->id); - } + wdd->id); } return 0; From 24f8d233074badd4c18e4dafd2fb97d65838afed Mon Sep 17 00:00:00 2001 From: Matt Redfearn Date: Tue, 14 Nov 2017 10:52:54 +0000 Subject: [PATCH 03/74] watchdog: indydog: Add dependency on SGI_HAS_INDYDOG Commit da2a68b3eb47 ("watchdog: Enable COMPILE_TEST where possible") enabled building the Indy watchdog driver when COMPILE_TEST is enabled. However, the driver makes reference to symbols that are only defined for certain platforms are selected in the config. These platforms select SGI_HAS_INDYDOG. Without this, link time errors result, for example when building a MIPS allyesconfig. drivers/watchdog/indydog.o: In function `indydog_write': indydog.c:(.text+0x18): undefined reference to `sgimc' indydog.c:(.text+0x1c): undefined reference to `sgimc' drivers/watchdog/indydog.o: In function `indydog_start': indydog.c:(.text+0x54): undefined reference to `sgimc' indydog.c:(.text+0x58): undefined reference to `sgimc' drivers/watchdog/indydog.o: In function `indydog_stop': indydog.c:(.text+0xa4): undefined reference to `sgimc' drivers/watchdog/indydog.o:indydog.c:(.text+0xa8): more undefined references to `sgimc' follow make: *** [Makefile:1005: vmlinux] Error 1 Fix this by ensuring that CONFIG_INDIDOG can only be selected when the necessary dependent platform symbols are built in. Fixes: da2a68b3eb47 ("watchdog: Enable COMPILE_TEST where possible") Signed-off-by: Matt Redfearn Cc: # 4.11 + Signed-off-by: Ralf Baechle Suggested-by: James Hogan Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index ca200d1f310ab..5a606a4aee6aa 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1451,7 +1451,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 From c42cbe41727a138905a28f8e0b00c147be77ee93 Mon Sep 17 00:00:00 2001 From: Jerry Hoemann Date: Mon, 23 Oct 2017 16:46:16 -0600 Subject: [PATCH 04/74] watchdog: hpwdt: SMBIOS check This corrects: commit cce78da76601 ("watchdog: hpwdt: Add check for UEFI bits") The test on HPE SMBIOS extension type 219 record "Misc Features" bits for UEFI support is incorrect. The definition of the Misc Features bits in the HPE SMBIOS OEM Extensions specification (and related firmware) was changed to use a different pair of bits to represent UEFI supported. Howerver, a corresponding change to Linux was missed. Current code/platform work because the iCRU test is working. But purpose of cce78da766 is to ensure correct functionality on future systems where iCRU isn't supported. Signed-off-by: Jerry Hoemann Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/hpwdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 67fbe35ce7cfe..9fd869fbd8a96 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -700,7 +700,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; } } From f78d9f92eedea14cc04db39c9757ee5d69c74db5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 26 Dec 2017 09:48:12 -0800 Subject: [PATCH 05/74] MAINTAINERS: Add Guenter Roeck as co-maintainer of watchdog subsystem Having a co-maintainer will enable us to be more flexible with pull requests. Cc: Wim Van Sebroeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index a6e86e20761e1..074abb9f778ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14722,7 +14722,7 @@ F: drivers/input/tablet/wacom_serial4.c WATCHDOG DEVICE DRIVERS M: Wim Van Sebroeck -R: Guenter Roeck +M: Guenter Roeck L: linux-watchdog@vger.kernel.org W: http://www.linux-watchdog.org/ T: git git://www.linux-watchdog.org/linux-watchdog.git From 77f89eab9321bd51e874d92f820db5ee8488ae82 Mon Sep 17 00:00:00 2001 From: Wim Van Sebroeck Date: Thu, 28 Dec 2017 17:24:26 +0100 Subject: [PATCH 06/74] Change my E-mail address. Cc: Wim Van Sebroeck Signed-off-by: Wim Van Sebroeck --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 074abb9f778ac..a86211049e61e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14721,7 +14721,7 @@ S: Maintained F: drivers/input/tablet/wacom_serial4.c WATCHDOG DEVICE DRIVERS -M: Wim Van Sebroeck +M: Wim Van Sebroeck M: Guenter Roeck L: linux-watchdog@vger.kernel.org W: http://www.linux-watchdog.org/ From 5c06756cf20df5c9b974aaac3be8eb7b0e83c2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20F=C3=A4rber?= Date: Tue, 5 Sep 2017 01:16:00 +0200 Subject: [PATCH 07/74] dt-bindings: watchdog: Add Realtek RTD1295 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a binding for the Realtek RTD1295 watchdog. Acked-by: Rob Herring Signed-off-by: Andreas Färber Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../bindings/watchdog/realtek,rtd119x.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt diff --git a/Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt b/Documentation/devicetree/bindings/watchdog/realtek,rtd119x.txt new file mode 100644 index 0000000000000..05653054bd5b2 --- /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>; + }; From 2bdf6acbfead7e9aa69f36ee5682d1e5c8f70367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20F=C3=A4rber?= Date: Tue, 5 Sep 2017 01:16:01 +0200 Subject: [PATCH 08/74] watchdog: Add Realtek RTD1295 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a watchdog driver for the Realtek RTD1295 SoC. Based on QNAP's arch/arm/mach-rtk119x/driver/rtk_watchdog.c code and mach-rtk119x/driver/dc2vo/fpga/include/iso_reg.h register defines. Signed-off-by: Andreas Färber Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 10 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/rtd119x_wdt.c | 168 +++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/watchdog/rtd119x_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 5a606a4aee6aa..6fdf3f95d1466 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -787,6 +787,16 @@ config UNIPHIER_WATCHDOG To compile this driver as a module, choose M here: the module will be called uniphier_wdt. +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. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 715a21078e0c7..cb5fe3d414bbc 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -88,6 +88,7 @@ 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 +obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c new file mode 100644 index 0000000000000..d001c17ddfdee --- /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 +#include +#include +#include +#include +#include +#include +#include + +#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); From 71246c3528780edc45990d189b856447879d1318 Mon Sep 17 00:00:00 2001 From: Mathieu Malaterre Date: Fri, 15 Sep 2017 21:20:19 +0200 Subject: [PATCH 09/74] watchdog: jz4740: Add support for the watchdog in jz4780 SoC The watchdog unit present in the JZ4780 works the same as the one in the JZ4740. Signed-off-by: Mathieu Malaterre Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/jz4740_wdt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c index 20627f22baf6a..aafbeb96561b5 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); From be17980d9be5a5c1ece83836b24fabce648b92aa Mon Sep 17 00:00:00 2001 From: Mathieu Malaterre Date: Fri, 15 Sep 2017 21:20:40 +0200 Subject: [PATCH 10/74] dt: watchdog: Document compatibility with JZ4780 Suggested-by: Zubair Lutfullah Kakakhel Signed-off-by: Mathieu Malaterre Acked-by: Rob Herring Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt b/Documentation/devicetree/bindings/watchdog/ingenic,jz4740-wdt.txt index 3c7a1cd13b101..cb44918f01a8b 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: From 9f3e13c74e1b4f370c3de06cb504c003091c9673 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Wed, 20 Sep 2017 15:00:17 +0930 Subject: [PATCH 11/74] watchdog: aspeed: Retain watchdog enabled state An unintended post-condition of probe() is that the watchdog is disabled. This behaviour was introduced by an unnecessary write to the control register to configure the hardware based on the devicetree. The write is unnecessary because the cached control value that is manipulated by the code parsing the devicetree is eventually written by aspeed_wdt_enable(), which is when we care how the control register should be configured. Remove the write to restore expected behaviour. Fixes: b7f0b8ad25f3 ("drivers/watchdog: ASPEED reference dev tree properties for config") Signed-off-by: Andrew Jeffery Reviewed-by: Joel Stanley Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/aspeed_wdt.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index 79cc766cd30fd..6c6dd3f4c48d2 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); } From ffff023a72e28e3ab4e7e43d8d41cb74f4b85e97 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Wed, 20 Sep 2017 15:00:18 +0930 Subject: [PATCH 12/74] watchdog: aspeed: Fix 'Apseed' typo in Kconfig Apseed sounds like a good name for a web/mobile start-up incubator, but isn't a reflection of Aspeed themselves. Signed-off-by: Andrew Jeffery Reviewed-by: Joel Stanley Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 6fdf3f95d1466..60ab8d90da084 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -746,7 +746,7 @@ config ASPEED_WATCHDOG 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. From 2050dd0610237488df12fce45966db7debbfa4f0 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Wed, 20 Sep 2017 15:00:19 +0930 Subject: [PATCH 13/74] watchdog: aspeed: Remove specific reference to AST2400 in Kconfig The driver also supports the watchdog in the AST25xx series, and may work on earlier SoCs as well. Signed-off-by: Andrew Jeffery Reviewed-by: Joel Stanley Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 60ab8d90da084..ce8013cbdcf24 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -741,7 +741,7 @@ 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 From d4238aa458b8c3e64d6f124aafa5c230cae31d6a Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Wed, 20 Sep 2017 15:00:20 +0930 Subject: [PATCH 14/74] watchdog: aspeed: Move init to arch_initcall Probing at device_initcall time lead to perverse cases where the watchdog was probed after, say, I2C devices, which then leaves a potentially running watchdog at the mercy of I2C device behaviour and bus conditions. Load the watchdog driver early to ensure that the kernel is patting it well before initialising peripherals. Signed-off-by: Andrew Jeffery Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/aspeed_wdt.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c index 6c6dd3f4c48d2..ca5b91e2eb925 100644 --- a/drivers/watchdog/aspeed_wdt.c +++ b/drivers/watchdog/aspeed_wdt.c @@ -316,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"); From 1bfe8889380890efe4943d125124f5a7b48571b0 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 26 Sep 2017 08:11:22 +0200 Subject: [PATCH 15/74] watchdog: dw_wdt: add stop watchdog operation The only way of stopping the watchdog is by resetting it. Add the watchdog op for stopping the device and reset if a reset line is provided. At same time WDOG_HW_RUNNING should be remove from dw_wdt_start. As commented by Guenter Roeck: dw_wdt sets WDOG_HW_RUNNING in its open function. Result is that the kref_get() in watchdog_open() won't be executed. But then kref_put() in close will be called since the watchdog now does stop. This causes the imbalance. Signed-off-by: Oleksij Rempel Cc: Wim Van Sebroeck Cc: Guenter Roeck Cc: linux-watchdog@vger.kernel.org Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/dw_wdt.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 36be987ff9efc..c2f4ff5162301 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, From d0d0677e8fdfba18582bb2dc4bd07189da598e1d Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 9 Oct 2017 01:28:46 +0200 Subject: [PATCH 16/74] watchdog: gpio: Add some local helper variables This add "dev" and "np" variables to make the probe() function a bit easier to read. Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/gpio_wdt.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c index cb66c2f99ff15..448e6c3ba73c4 100644 --- a/drivers/watchdog/gpio_wdt.c +++ b/drivers/watchdog/gpio_wdt.c @@ -101,6 +101,8 @@ 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; unsigned int hw_margin; @@ -108,19 +110,19 @@ static int gpio_wdt_probe(struct platform_device *pdev) 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); + priv->gpio = of_get_gpio_flags(np, 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")) { @@ -133,12 +135,12 @@ static int gpio_wdt_probe(struct platform_device *pdev) return -EINVAL; } - ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, - dev_name(&pdev->dev)); + ret = devm_gpio_request_one(dev, priv->gpio, f, + dev_name(dev)); if (ret) return ret; - 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 +148,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 +157,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); From a2363f9d2f34bbfd9899b3a3b3cc7022e0f85724 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 9 Oct 2017 01:28:47 +0200 Subject: [PATCH 17/74] watchdog: gpio: Convert to use GPIO descriptors This converts the GPIO watchdog driver to use GPIO descriptors instead of relying on the old method to read out GPIO numbers from the device tree and then using those with the old GPIO API. The descriptor API keeps track of whether the line is active low so we can remove all active low handling and rely on the GPIO descriptor to deal with this for us. Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/gpio_wdt.c | 41 +++++++++++++++---------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c index 448e6c3ba73c4..b077c88a5ceb0 100644 --- a/drivers/watchdog/gpio_wdt.c +++ b/drivers/watchdog/gpio_wdt.c @@ -12,7 +12,8 @@ #include #include #include -#include +#include +#include #include #include @@ -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); @@ -104,9 +105,8 @@ 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; @@ -116,29 +116,22 @@ static int gpio_wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - priv->gpio = of_get_gpio_flags(np, 0, &flags); - if (!gpio_is_valid(priv->gpio)) - return priv->gpio; - - priv->active_low = flags & OF_GPIO_ACTIVE_LOW; - 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(dev, priv->gpio, f, - dev_name(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(np, "hw_margin_ms", &hw_margin); From 844ecd970f2c7932878dc363da0a07012a221437 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 11 Oct 2017 15:29:56 +1300 Subject: [PATCH 18/74] watchdog: orion: fix typo Correct typo in comment "insterted" -> "inserted". Signed-off-by: Chris Packham Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/orion_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c index 83af7d6cc37cc..ea676d233e1e5 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)) From ef3c9cc1a57bc1aa2136337c4e670511e46191f5 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 16 Oct 2017 22:54:23 +0200 Subject: [PATCH 19/74] watchdog: gemini/ftwdt010: rename DT bindings The device tree bindings are in two copies and also should be consolidated into a single Faraday Technology FTWDT010 binding since we uncovered that this IP part is a standard IP from Faraday. Cc: devicetree@vger.kernel.org Acked-by: Rob Herring Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../watchdog/cortina,gemini-watchdog.txt | 17 ----------------- ...,gemin-watchdog.txt => faraday,ftwdt010.txt} | 11 ++++++++--- 2 files changed, 8 insertions(+), 20 deletions(-) delete mode 100644 Documentation/devicetree/bindings/watchdog/cortina,gemini-watchdog.txt rename Documentation/devicetree/bindings/watchdog/{cortina,gemin-watchdog.txt => faraday,ftwdt010.txt} (55%) 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 bc4b865d178b4..0000000000000 --- 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 similarity index 55% rename from Documentation/devicetree/bindings/watchdog/cortina,gemin-watchdog.txt rename to Documentation/devicetree/bindings/watchdog/faraday,ftwdt010.txt index bc4b865d178b4..9ecdb502e6059 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>; }; From 766a2aad645e6c4c4dec0c02be34318d5538e75e Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 16 Oct 2017 22:54:24 +0200 Subject: [PATCH 20/74] watchdog: gemini/ftwdt010: rename driver and symbols This renames all the driver files and symbols for the Gemini watchdog to FTWDT010 as it has been revealed that this IP block is a generic watchdog timer from Faraday Technology used in several SoC designs. Select this driver by default for the Gemini, it is a sensible driver to always have enabled. Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 14 ++- drivers/watchdog/Makefile | 2 +- .../watchdog/{gemini_wdt.c => ftwdt010_wdt.c} | 117 +++++++++--------- 3 files changed, 68 insertions(+), 65 deletions(-) rename drivers/watchdog/{gemini_wdt.c => ftwdt010_wdt.c} (50%) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index ce8013cbdcf24..be37e102dd307 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -321,16 +321,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" diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index cb5fe3d414bbc..b6cf527b31fbe 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 diff --git a/drivers/watchdog/gemini_wdt.c b/drivers/watchdog/ftwdt010_wdt.c similarity index 50% rename from drivers/watchdog/gemini_wdt.c rename to drivers/watchdog/ftwdt010_wdt.c index 8155aa619e4c8..637ffd812f0b9 100644 --- a/drivers/watchdog/gemini_wdt.c +++ b/drivers/watchdog/ftwdt010_wdt.c @@ -1,5 +1,5 @@ /* - * Watchdog driver for Cortina Systems Gemini SoC + * Watchdog driver for Faraday Technology FTWDT010 * * Copyright (C) 2017 Linus Walleij * @@ -22,10 +22,10 @@ #include #include -#define GEMINI_WDCOUNTER 0x0 -#define GEMINI_WDLOAD 0x4 -#define GEMINI_WDRESTART 0x8 -#define GEMINI_WDCR 0xC +#define FTWDT010_WDCOUNTER 0x0 +#define FTWDT010_WDLOAD 0x4 +#define FTWDT010_WDRESTART 0x8 +#define FTWDT010_WDCR 0xC #define WDRESTART_MAGIC 0x5AB9 @@ -35,79 +35,79 @@ #define WDT_CLOCK 5000000 /* 5 MHz */ -struct gemini_wdt { +struct ftwdt010_wdt { struct watchdog_device wdd; struct device *dev; void __iomem *base; }; static inline -struct gemini_wdt *to_gemini_wdt(struct watchdog_device *wdd) +struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd) { - return container_of(wdd, struct gemini_wdt, wdd); + return container_of(wdd, struct ftwdt010_wdt, wdd); } -static int gemini_wdt_start(struct watchdog_device *wdd) +static int ftwdt010_wdt_start(struct watchdog_device *wdd) { - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); - writel(wdd->timeout * WDT_CLOCK, gwdt->base + GEMINI_WDLOAD); - writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); + writel(wdd->timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD); + writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART); /* set clock before enabling */ writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, - gwdt->base + GEMINI_WDCR); + gwdt->base + FTWDT010_WDCR); writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, - gwdt->base + GEMINI_WDCR); + gwdt->base + FTWDT010_WDCR); return 0; } -static int gemini_wdt_stop(struct watchdog_device *wdd) +static int ftwdt010_wdt_stop(struct watchdog_device *wdd) { - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); - writel(0, gwdt->base + GEMINI_WDCR); + writel(0, gwdt->base + FTWDT010_WDCR); return 0; } -static int gemini_wdt_ping(struct watchdog_device *wdd) +static int ftwdt010_wdt_ping(struct watchdog_device *wdd) { - struct gemini_wdt *gwdt = to_gemini_wdt(wdd); + struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd); - writel(WDRESTART_MAGIC, gwdt->base + GEMINI_WDRESTART); + writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART); return 0; } -static int gemini_wdt_set_timeout(struct watchdog_device *wdd, +static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { wdd->timeout = timeout; if (watchdog_active(wdd)) - gemini_wdt_start(wdd); + ftwdt010_wdt_start(wdd); return 0; } -static irqreturn_t gemini_wdt_interrupt(int irq, void *data) +static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data) { - struct gemini_wdt *gwdt = data; + struct ftwdt010_wdt *gwdt = data; watchdog_notify_pretimeout(&gwdt->wdd); return IRQ_HANDLED; } -static const struct watchdog_ops gemini_wdt_ops = { - .start = gemini_wdt_start, - .stop = gemini_wdt_stop, - .ping = gemini_wdt_ping, - .set_timeout = gemini_wdt_set_timeout, +static const struct watchdog_ops ftwdt010_wdt_ops = { + .start = ftwdt010_wdt_start, + .stop = ftwdt010_wdt_stop, + .ping = ftwdt010_wdt_ping, + .set_timeout = ftwdt010_wdt_set_timeout, .owner = THIS_MODULE, }; -static const struct watchdog_info gemini_wdt_info = { +static const struct watchdog_info ftwdt010_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT, @@ -115,11 +115,11 @@ static const struct watchdog_info gemini_wdt_info = { }; -static int gemini_wdt_probe(struct platform_device *pdev) +static int ftwdt010_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct resource *res; - struct gemini_wdt *gwdt; + struct ftwdt010_wdt *gwdt; unsigned int reg; int irq; int ret; @@ -138,8 +138,8 @@ static int gemini_wdt_probe(struct platform_device *pdev) return -EINVAL; gwdt->dev = dev; - gwdt->wdd.info = &gemini_wdt_info; - gwdt->wdd.ops = &gemini_wdt_ops; + gwdt->wdd.info = &ftwdt010_wdt_info; + gwdt->wdd.ops = &ftwdt010_wdt_ops; gwdt->wdd.min_timeout = 1; gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK; gwdt->wdd.parent = dev; @@ -151,14 +151,14 @@ static int gemini_wdt_probe(struct platform_device *pdev) gwdt->wdd.timeout = 13U; watchdog_init_timeout(&gwdt->wdd, 0, dev); - reg = readw(gwdt->base + GEMINI_WDCR); + reg = readw(gwdt->base + FTWDT010_WDCR); if (reg & WDCR_ENABLE) { /* Watchdog was enabled by the bootloader, disable it. */ reg &= ~WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); + writel(reg, gwdt->base + FTWDT010_WDCR); } - ret = devm_request_irq(dev, irq, gemini_wdt_interrupt, 0, + ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0, "watchdog bark", gwdt); if (ret) return ret; @@ -171,59 +171,60 @@ static int gemini_wdt_probe(struct platform_device *pdev) /* Set up platform driver data */ platform_set_drvdata(pdev, gwdt); - dev_info(dev, "Gemini watchdog driver enabled\n"); + dev_info(dev, "FTWDT010 watchdog driver enabled\n"); return 0; } -static int __maybe_unused gemini_wdt_suspend(struct device *dev) +static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev) { - struct gemini_wdt *gwdt = dev_get_drvdata(dev); + struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev); unsigned int reg; - reg = readw(gwdt->base + GEMINI_WDCR); + reg = readw(gwdt->base + FTWDT010_WDCR); reg &= ~WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); + writel(reg, gwdt->base + FTWDT010_WDCR); return 0; } -static int __maybe_unused gemini_wdt_resume(struct device *dev) +static int __maybe_unused ftwdt010_wdt_resume(struct device *dev) { - struct gemini_wdt *gwdt = dev_get_drvdata(dev); + struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev); unsigned int reg; if (watchdog_active(&gwdt->wdd)) { - reg = readw(gwdt->base + GEMINI_WDCR); + reg = readw(gwdt->base + FTWDT010_WDCR); reg |= WDCR_ENABLE; - writel(reg, gwdt->base + GEMINI_WDCR); + writel(reg, gwdt->base + FTWDT010_WDCR); } return 0; } -static const struct dev_pm_ops gemini_wdt_dev_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(gemini_wdt_suspend, - gemini_wdt_resume) +static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend, + ftwdt010_wdt_resume) }; #ifdef CONFIG_OF -static const struct of_device_id gemini_wdt_match[] = { +static const struct of_device_id ftwdt010_wdt_match[] = { + { .compatible = "faraday,ftwdt010" }, { .compatible = "cortina,gemini-watchdog" }, {}, }; -MODULE_DEVICE_TABLE(of, gemini_wdt_match); +MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match); #endif -static struct platform_driver gemini_wdt_driver = { - .probe = gemini_wdt_probe, +static struct platform_driver ftwdt010_wdt_driver = { + .probe = ftwdt010_wdt_probe, .driver = { - .name = "gemini-wdt", - .of_match_table = of_match_ptr(gemini_wdt_match), - .pm = &gemini_wdt_dev_pm_ops, + .name = "ftwdt010-wdt", + .of_match_table = of_match_ptr(ftwdt010_wdt_match), + .pm = &ftwdt010_wdt_dev_pm_ops, }, }; -module_platform_driver(gemini_wdt_driver); +module_platform_driver(ftwdt010_wdt_driver); MODULE_AUTHOR("Linus Walleij"); -MODULE_DESCRIPTION("Watchdog driver for Gemini"); +MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010"); MODULE_LICENSE("GPL"); From d5433fd60df0d77b4751715e22a2dd715d26d926 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 16 Oct 2017 22:54:25 +0200 Subject: [PATCH 21/74] watchdog: ftwdt010: Make interrupt optional The Moxart does not appear to be using the interrupt from the watchdog timer, maybe it's not even routed, so as to support more architectures with this driver, make the interrupt optional. While we are at it: actually enable the use of the interrupt if present by setting the right bit in the control register and define the missing control register bits. Signed-off-by: Linus Walleij Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/ftwdt010_wdt.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/watchdog/ftwdt010_wdt.c b/drivers/watchdog/ftwdt010_wdt.c index 637ffd812f0b9..a9c2912ee2802 100644 --- a/drivers/watchdog/ftwdt010_wdt.c +++ b/drivers/watchdog/ftwdt010_wdt.c @@ -30,6 +30,8 @@ #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) @@ -39,6 +41,7 @@ struct ftwdt010_wdt { struct watchdog_device wdd; struct device *dev; void __iomem *base; + bool has_irq; }; static inline @@ -50,14 +53,17 @@ struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *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 */ - writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST, - gwdt->base + FTWDT010_WDCR); - writel(WDCR_CLOCK_5MHZ | WDCR_SYS_RST | WDCR_ENABLE, - gwdt->base + FTWDT010_WDCR); + enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST; + writel(enable, gwdt->base + FTWDT010_WDCR); + if (gwdt->has_irq) + enable |= WDCR_WDINTR; + enable |= WDCR_ENABLE; + writel(enable, gwdt->base + FTWDT010_WDCR); return 0; } @@ -133,10 +139,6 @@ static int ftwdt010_wdt_probe(struct platform_device *pdev) if (IS_ERR(gwdt->base)) return PTR_ERR(gwdt->base); - irq = platform_get_irq(pdev, 0); - if (!irq) - return -EINVAL; - gwdt->dev = dev; gwdt->wdd.info = &ftwdt010_wdt_info; gwdt->wdd.ops = &ftwdt010_wdt_ops; @@ -158,10 +160,14 @@ static int ftwdt010_wdt_probe(struct platform_device *pdev) writel(reg, gwdt->base + FTWDT010_WDCR); } - ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0, - "watchdog bark", gwdt); - if (ret) - return ret; + irq = platform_get_irq(pdev, 0); + if (irq) { + ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0, + "watchdog bark", gwdt); + if (ret) + return ret; + gwdt->has_irq = true; + } ret = devm_watchdog_register_device(dev, &gwdt->wdd); if (ret) { From 540f635192bb222f252724f70b9240d17742f1b1 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 17 Oct 2017 17:30:22 +0200 Subject: [PATCH 22/74] watchdog: da9062: Add restart handler support Register a restart handler for the da9062 watchdog. System restart is triggered by sending the shutdown command to the PMIC. As more-suitable restart handlers may exist, the priority of the watchdog restart handler is set to 128. Signed-off-by: Michael Grzeschik Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/da9062_wdt.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 9083d3d922b0b..4349a02215489 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -175,6 +175,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 +205,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[] = { @@ -219,6 +239,8 @@ static int da9062_wdt_probe(struct platform_device *pdev) 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); From f31b2a9bdeaea38957f4675ee0d1b8421595dc67 Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 17 Oct 2017 17:30:25 +0200 Subject: [PATCH 23/74] watchdog: da9062: Disable and wait before changing timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DA9062 watchdog occasionally enters error condition and resets the system if the timeout is changed quickly after the timer was enabled. The method of disabling and waiting for > 150 µs before setting the new timeout is taken from the DA9052 driver. Signed-off-by: Michael Grzeschik Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/da9062_wdt.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 4349a02215489..dbb970e87d3df 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -100,6 +100,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, From fb484262f9ef0d73e0116b942c114bc48c6c7b3b Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Tue, 17 Oct 2017 17:30:26 +0200 Subject: [PATCH 24/74] watchdog: da9062: use protection delay mechanism from core This patch removes the windows protection routine that got now covered by the wdt core. Signed-off-by: Michael Grzeschik Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/da9062_wdt.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index dbb970e87d3df..814dff6045a44 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, @@ -242,6 +224,7 @@ 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; From b6c84c9ff3f48a476451eceb5e8478b8aadda8dc Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 19 Oct 2017 17:05:48 +0200 Subject: [PATCH 25/74] watchdog: xen: use time64_t for timeouts The Xen watchdog driver uses __kernel_time_t and ktime_to_timespec() internally for managing its timeouts. Both are deprecated because of y2038 problems. The driver itself is fine, since it only uses monotonic times, but converting it to use ktime_get_seconds() avoids the deprecated interfaces and is slightly simpler. Signed-off-by: Arnd Bergmann Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/xen_wdt.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c index cf0e650c2015a..5dd5c3494d55e 100644 --- a/drivers/watchdog/xen_wdt.c +++ b/drivers/watchdog/xen_wdt.c @@ -35,7 +35,7 @@ static struct platform_device *platform_device; static DEFINE_SPINLOCK(wdt_lock); static struct sched_watchdog wdt; -static __kernel_time_t wdt_expires; +static time64_t wdt_expires; static bool is_active, expect_release; #define WATCHDOG_TIMEOUT 60 /* in seconds */ @@ -49,15 +49,15 @@ 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(void) { wdt.timeout = timeout; - return ktime_to_timespec(ktime_get()).tv_sec + timeout; + return ktime_get_seconds() + timeout; } static int xen_wdt_start(void) { - __kernel_time_t expires; + time64_t expires; int err; spin_lock(&wdt_lock); @@ -98,7 +98,7 @@ static int xen_wdt_stop(void) static int xen_wdt_kick(void) { - __kernel_time_t expires; + time64_t expires; int err; spin_lock(&wdt_lock); @@ -222,7 +222,7 @@ static long xen_wdt_ioctl(struct file *file, unsigned int cmd, return put_user(timeout, argp); case WDIOC_GETTIMELEFT: - retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec; + retval = wdt_expires - ktime_get_seconds(); return put_user(retval, argp); } From e531037145df630ff9d7be3858497388cc700f79 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Sat, 21 Oct 2017 19:45:13 +0200 Subject: [PATCH 26/74] watchdog: sunxi_wdt: use of_device_get_match_data The usage of of_device_get_match_data reduce the code size a bit. Furthermore, it prevents an improbable dereference when of_match_device() return NULL. Signed-off-by: Corentin Labbe Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sunxi_wdt.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index 9728fa32c357b..802e31b1416d5 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)) From 838534e50e2e5c1e644e30ab6cb28da88eb31368 Mon Sep 17 00:00:00 2001 From: Jerry Hoemann Date: Mon, 23 Oct 2017 16:46:17 -0600 Subject: [PATCH 27/74] watchdog: hpwdt: Check source of NMI Do not claim the NMI (i.e. return NMI_DONE) if the source of the NMI isn't the iLO watchdog or debug. Signed-off-by: Jerry Hoemann Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/hpwdt.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 9fd869fbd8a96..e61658310381e 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; @@ -474,6 +475,11 @@ static int hpwdt_time_left(void) return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); } +static int hpwdt_my_nmi(void) +{ + return ioread8(hpwdt_nmistat) & 0x6; +} + #ifdef CONFIG_HPWDT_NMI_DECODING /* * 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); @@ -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; From 7af4ac8772a8f9af40f998671e6d09f5de137854 Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Thu, 26 Oct 2017 17:10:13 +0100 Subject: [PATCH 28/74] watchdog: i6300esb: use the watchdog subsystem Change the i6300esb driver to use the watchdog subsystem instead of the legacy watchdog API. This is mainly just getting rid of the "write" and "ioctl" methods and part of the watchdog control logic (which are all implemented by the watchdog subsystem). Even though the watchdog subsystem supports registering and handling multiple watchdog devices at the same time, the i6300esb driver still has a limitation of only one i6300esb device due to some global variable usage that comes from the original design. However, the driver can now coexist with other watchdog devices (supported by different drivers). Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/i6300esb.c | 232 ++++++++---------------------------- 1 file changed, 47 insertions(+), 185 deletions(-) diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index d7befd58b391a..4f4287d83c213 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -21,6 +21,8 @@ * Version 0.02 * 20050210 David Härdeman * Ported driver to kernel 2.6 + * 20171016 Radu Rendec + * Change driver to use the watchdog subsystem */ /* @@ -44,7 +46,6 @@ /* 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 */ @@ -76,11 +77,7 @@ /* 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; @@ -88,7 +85,7 @@ 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 */ +static int heartbeat; /* in seconds */ module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1dev, "failed to enable device\n"); goto err_devput; } if (pci_request_region(pdev, 0, ESB_MODULE_NAME)) { - pr_err("failed to request region\n"); + dev_err(&pdev->dev, "failed to request region\n"); goto err_disable; } BASEADDR = pci_ioremap_bar(pdev, 0); if (BASEADDR == NULL) { /* Something's wrong here, BASEADDR has to be set */ - pr_err("failed to get BASEADDR\n"); + dev_err(&pdev->dev, "failed to get BASEADDR\n"); goto err_release; } @@ -396,7 +270,7 @@ static void esb_initdevice(void) /* Check that the WDT isn't already locked */ pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1); if (val1 & ESB_WDT_LOCK) - pr_warn("nowayout already set\n"); + dev_warn(&esb_pci->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); @@ -405,14 +279,14 @@ static void esb_initdevice(void) esb_unlock_registers(); val2 = readw(ESB_RELOAD_REG); if (val2 & ESB_WDT_TIMEOUT) - triggered = WDIOF_CARDRESET; + esb_dev.bootstatus = WDIOF_CARDRESET; /* Reset WDT_TIMEOUT flag and timers */ esb_unlock_registers(); writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG); /* And set the correct timeout value */ - esb_timer_set_heartbeat(heartbeat); + esb_timer_set_heartbeat(&esb_dev, esb_dev.timeout); } static int esb_probe(struct pci_dev *pdev, @@ -434,26 +308,25 @@ static int esb_probe(struct pci_dev *pdev, if (!esb_getdevice(pdev) || esb_pci == NULL) 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 1dev, + "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", + BASEADDR, esb_dev.timeout, nowayout); return 0; err_unmap: @@ -466,29 +339,18 @@ static int esb_probe(struct pci_dev *pdev, static void esb_remove(struct pci_dev *pdev) { - /* Stop the timer before we leave */ - if (!nowayout) - esb_timer_stop(); - - /* Deregister */ - misc_deregister(&esb_miscdev); + watchdog_unregister_device(&esb_dev); iounmap(BASEADDR); pci_release_region(esb_pci, 0); pci_disable_device(esb_pci); esb_pci = NULL; } -static void esb_shutdown(struct pci_dev *pdev) -{ - esb_timer_stop(); -} - static struct pci_driver esb_driver = { .name = ESB_MODULE_NAME, .id_table = esb_pci_tbl, .probe = esb_probe, .remove = esb_remove, - .shutdown = esb_shutdown, }; module_pci_driver(esb_driver); From cf73120ba43172e181905133cfcc5711485fd4b3 Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Thu, 26 Oct 2017 17:10:14 +0100 Subject: [PATCH 29/74] watchdog: i6300esb: support multiple devices Support multiple i6300esb devices simultaneously, by removing the single device restriction in the original design and leveraging the multiple device support of the watchdog subsystem. This patch replaces the global definitions of watchdog device data with a dynamically allocated structure. This structure is allocated during device probe, so multiple independent structures can be allocated if multiple devices are probed. Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/i6300esb.c | 170 +++++++++++++++++++----------------- 1 file changed, 90 insertions(+), 80 deletions(-) diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index 4f4287d83c213..147462f431de3 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -23,6 +23,7 @@ * Ported driver to kernel 2.6 * 20171016 Radu Rendec * Change driver to use the watchdog subsystem + * Add support for multiple 6300ESB devices */ /* @@ -52,10 +53,10 @@ #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 */ @@ -75,11 +76,7 @@ #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 struct pci_dev *esb_pci; - -/* We can only use 1 card due to the /dev/watchdog restriction */ +/* Probed devices counter; used only for printing the initial info message */ static int cards_found; /* module parameters */ @@ -97,6 +94,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 */ @@ -107,35 +113,37 @@ 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(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; - 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 | (_wdd_nowayout ? ESB_WDT_LOCK : 0x00); - pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); + pci_write_config_byte(edev->pdev, ESB_LOCK_REG, val); return 0; } static int esb_timer_stop(struct watchdog_device *wdd) { + struct esb_dev *edev = to_esb_dev(wdd); u8 val; /* 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); + 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; @@ -143,8 +151,10 @@ static int esb_timer_stop(struct watchdog_device *wdd) static int esb_timer_keepalive(struct watchdog_device *wdd) { - 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? */ return 0; } @@ -152,6 +162,7 @@ static int esb_timer_keepalive(struct watchdog_device *wdd) static int esb_timer_set_heartbeat(struct watchdog_device *wdd, unsigned int time) { + struct esb_dev *edev = to_esb_dev(wdd); u32 val; /* We shift by 9, so if we are passed a value of 1 sec, @@ -161,16 +172,16 @@ static int esb_timer_set_heartbeat(struct watchdog_device *wdd, 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? */ @@ -196,14 +207,6 @@ static const struct watchdog_ops esb_ops = { .ping = esb_timer_keepalive, }; -static struct watchdog_device esb_dev = { - .info = &esb_info, - .ops = &esb_ops, - .min_timeout = 1, - .max_timeout = 2 * 0x03ff, - .timeout = WATCHDOG_HEARTBEAT, -}; - /* * Data for PCI driver interface */ @@ -217,38 +220,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)) { - dev_err(&pdev->dev, "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)) { - dev_err(&pdev->dev, "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 */ - dev_err(&pdev->dev, "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; @@ -265,33 +268,34 @@ 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) - dev_warn(&esb_pci->dev, "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) - esb_dev.bootstatus = 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(&esb_dev, esb_dev.timeout); + 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++; @@ -299,26 +303,32 @@ static int esb_probe(struct pci_dev *pdev, 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; /* Initialize the watchdog and make sure it does not run */ - if (watchdog_init_timeout(&esb_dev, heartbeat, NULL)) - pr_info("heartbeat value must be 1wdd.info = &esb_info; + edev->wdd.ops = &esb_ops; + edev->wdd.min_timeout = 1; + edev->wdd.max_timeout = 2 * 0x03ff; + edev->wdd.timeout = WATCHDOG_HEARTBEAT; + if (watchdog_init_timeout(&edev->wdd, heartbeat, NULL)) + dev_info(&pdev->dev, + "heartbeat value must be 1wdd.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 = watchdog_register_device(&esb_dev); + ret = watchdog_register_device(&edev->wdd); if (ret != 0) { dev_err(&pdev->dev, "cannot register watchdog device (err=%d)\n", ret); @@ -326,24 +336,24 @@ static int esb_probe(struct pci_dev *pdev, } dev_info(&pdev->dev, "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", - BASEADDR, esb_dev.timeout, nowayout); + 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) { - watchdog_unregister_device(&esb_dev); - 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); + + 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 = { From 568d6015dbdef2c437da2fb6e697c43df6a52fea Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Thu, 26 Oct 2017 17:10:15 +0100 Subject: [PATCH 30/74] watchdog: i6300esb: do not hardcode heartbeat limits The minimum, maximum and default values for the watchdog heartbeat (timeout) were hardcoded in several places (including module parameter description and warning message for invalid module parameter value). This patch adds macros for the aforementioned values and replaces all occurences of hardcoded values by these macros. Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/i6300esb.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index 147462f431de3..4d92925cdc1e3 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -81,12 +81,16 @@ static int cards_found; /* module parameters */ /* 30 sec default heartbeat (1 < heartbeat < 2*1023) */ -#define WATCHDOG_HEARTBEAT 30 +#define ESB_HEARTBEAT_MIN 1 +#define ESB_HEARTBEAT_MAX 2046 +#define ESB_HEARTBEAT_DEFAULT 30 +#define ESB_HEARTBEAT_RANGE __MODULE_STRING(ESB_HEARTBEAT_MIN) \ + "wdd.info = &esb_info; edev->wdd.ops = &esb_ops; - edev->wdd.min_timeout = 1; - edev->wdd.max_timeout = 2 * 0x03ff; - edev->wdd.timeout = WATCHDOG_HEARTBEAT; + 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 1wdd.timeout); + "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); From 788c2fdb4f6f1d781d9db584f0c9bca54043dc84 Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Thu, 26 Oct 2017 17:10:16 +0100 Subject: [PATCH 31/74] watchdog: i6300esb: remove info message and version number The initial info message (early in the esb_probe() function) is not very useful and we already have a message on successful probe, which includes device identification. If the probe fails (e.g. PCI related errors), additional messages are printed anyway. The version number was only used in the initial info message. Other than that, it serves no useful purpose and it ran out of favor upstream anyway. Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/i6300esb.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index 4d92925cdc1e3..950c71a8bb22f 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -30,8 +30,6 @@ * Includes, defines, variables, module parameters, ... */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - #include #include #include @@ -45,7 +43,6 @@ #include /* Module and version information */ -#define ESB_VERSION "0.05" #define ESB_MODULE_NAME "i6300ESB timer" /* PCI configuration registers */ @@ -76,9 +73,6 @@ #define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ #define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ -/* Probed devices counter; used only for printing the initial info message */ -static int cards_found; - /* module parameters */ /* 30 sec default heartbeat (1 < heartbeat < 2*1023) */ #define ESB_HEARTBEAT_MIN 1 @@ -302,11 +296,6 @@ static int esb_probe(struct pci_dev *pdev, struct esb_dev *edev; int ret; - cards_found++; - if (cards_found == 1) - pr_info("Intel 6300ESB WatchDog Timer Driver v%s\n", - ESB_VERSION); - edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL); if (!edev) return -ENOMEM; From 7692a982698504465640b90f6d64ce62ca0a5bb9 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Sun, 29 Oct 2017 09:51:19 +0200 Subject: [PATCH 32/74] watchdog: mei_wdt: don't use of variable length array Fixes sparse warning: drivers/watchdog/mei_wdt.c:530:18: warning: Variable length array is used Signed-off-by: Tomas Winkler Reviewed-by: Alexander Usyskin Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/mei_wdt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c index ea60b29494fb6..b8194b02abe08 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); } From c94ad30add4e741ab260874e898a6448cdd4ff30 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 30 Oct 2017 16:43:19 +0100 Subject: [PATCH 33/74] dt-bindings: watchdog: renesas-wdt: Add support for the r8a77970 wdt Document support for the Watchdog Timer (WDT) Controller in the Renesas R-Car V3M (r8a77970) SoC. Restore sort order while at it. No driver update is needed. Signed-off-by: Geert Uytterhoeven Acked-by: Rob Herring Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/devicetree/bindings/watchdog/renesas-wdt.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt b/Documentation/devicetree/bindings/watchdog/renesas-wdt.txt index bf6d1ca58af7d..74b2f03c15155 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,-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 From 083ccebb564245d75e956eaa934bcfd0f0a21dea Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 2 Nov 2017 14:21:36 -0500 Subject: [PATCH 34/74] watchdog: advantechwdt: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/advantechwdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c index 7d7db0c5a64e6..f61944369c1ad 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: From a9868bcc4f7c4eea9e1cf3431d46f29999227b35 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 2 Nov 2017 14:25:09 -0500 Subject: [PATCH 35/74] watchdog: alim1535_wdt: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/alim1535_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c index 3a17fbd39f8ae..60f0c2eb85312 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: From fd846b57a6b0b551cf7a31c301bd9705759ce825 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 2 Nov 2017 14:28:17 -0500 Subject: [PATCH 36/74] watchdog: f71808e_wdt: mark expected switch fall-throughs In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/f71808e_wdt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c index 8658dba217681..e0678c14480f2 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); From 3d002ea611ad964c28d9a97ab500a0272deec776 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 3 Nov 2017 09:53:41 -0500 Subject: [PATCH 37/74] watchdog: pcwd_usb: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pcwd_usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index b9e376c8e2e36..47fe4c5449c99 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -456,8 +456,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); From 4d379727afd07fb901003e52673c3d705aa342f6 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 3 Nov 2017 11:02:30 -0500 Subject: [PATCH 38/74] watchdog: pcwd_pci: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pcwd_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c index c0d07eef2640b..1f78f09086218 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); From e2af3092d05a814d670a045df983fcefb18e7c14 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 3 Nov 2017 18:03:27 -0500 Subject: [PATCH 39/74] watchdog: watchdog_dev: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 95b96f3cb36f7..eb35700962987 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -720,7 +720,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) { From 57b6d481f7a59c091b53a18c843d8631fa04f7eb Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 3 Nov 2017 18:07:23 -0500 Subject: [PATCH 40/74] watchdog: wdt_pci: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/wdt_pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c index bc7addc2dc06d..10e2cda0ee5a4 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: From bc137dfdbec27c0ec5731a89002daded4a4aa1ea Mon Sep 17 00:00:00 2001 From: Rasmus Villemoes Date: Thu, 9 Nov 2017 14:39:55 +0100 Subject: [PATCH 41/74] watchdog: gpio_wdt: set WDOG_HW_RUNNING in gpio_wdt_stop The first patch above (https://patchwork.kernel.org/patch/9970181/) makes the oops go away, but it just papers over the problem. The real problem is that the watchdog core clears WDOG_HW_RUNNING in watchdog_stop, and the gpio driver fails to set it in its stop function when it doesn't actually stop it. This means that the core doesn't know that it now has responsibility for petting the device, in turn causing the device to reset the system (I hadn't noticed this because the board I'm working on has that reset logic disabled). How about this (other drivers may of course have the same problem, I haven't checked). One might say that ->stop should return an error when the device can't be stopped, but OTOH this brings parity between a device without a ->stop method and a GPIO wd that has always-running set. IOW, I think ->stop should only return an error when an actual attempt to stop the hardware failed. From: Rasmus Villemoes The watchdog framework clears WDOG_HW_RUNNING before calling ->stop. If the driver is unable to stop the device, it is supposed to set that bit again so that the watchdog core takes care of sending heart-beats while the device is not open from user-space. Update the gpio_wdt driver to honour that contract (and get rid of the redundant clearing of WDOG_HW_RUNNING). Fixes: 3c10bbde10 ("watchdog: core: Clear WDOG_HW_RUNNING before calling the stop function") Signed-off-by: Rasmus Villemoes Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/gpio_wdt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c index b077c88a5ceb0..3ade281903412 100644 --- a/drivers/watchdog/gpio_wdt.c +++ b/drivers/watchdog/gpio_wdt.c @@ -81,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; From 19ce9490aa840164a88aad033200ac8c2117c7da Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Wed, 8 Nov 2017 15:39:44 +0100 Subject: [PATCH 42/74] watchdog: mpc8xxx: use the core worker function The watchdog core includes a worker function which pings the watchdog until user app starts pinging it and which also pings it if the HW require more frequent pings. Use that function instead of the dedicated timer. In the mean time, we can allow the user to change the timeout. Then change the timeout module parameter to use seconds and use the watchdog_init_timeout() core function. On some HW (eg: the 8xx), SWCRR contains bits unrelated to the watchdog which have to be preserved upon write. This driver has nothing preventing the use of the magic close, so enable it. Signed-off-by: Christophe Leroy Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/mpc8xxx_wdt.c | 83 ++++++++++++++++------------------ 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c index 6610e9217dbc2..aca2d6323f8ad 100644 --- a/drivers/watchdog/mpc8xxx_wdt.c +++ b/drivers/watchdog/mpc8xxx_wdt.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -31,10 +30,13 @@ #include #include +#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. (0lock); } -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; From c5d8d40b28ba0b619f4ef5dd77169b0f1ff77148 Mon Sep 17 00:00:00 2001 From: Eric Long Date: Mon, 6 Nov 2017 10:46:27 +0800 Subject: [PATCH 43/74] dt-bindings: watchdog: Add Spreadtrum watchdog documentation This patch adds the documentation for Spreadtrum watchdog driver. Signed-off-by: Eric Long Acked-by: Rob Herring Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../devicetree/bindings/watchdog/sprd-wdt.txt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/sprd-wdt.txt diff --git a/Documentation/devicetree/bindings/watchdog/sprd-wdt.txt b/Documentation/devicetree/bindings/watchdog/sprd-wdt.txt new file mode 100644 index 0000000000000..aeaf3e0caf47b --- /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 = ; + timeout-sec = <12>; + clock-names = "enable", "rtc_enable"; + clocks = <&clk_aon_apb_gates1 8>, <&clk_aon_apb_rtc_gates 9>; + }; From 477603467009673d0965ca7ec165a09bd0fcb87e Mon Sep 17 00:00:00 2001 From: Eric Long Date: Mon, 6 Nov 2017 10:46:28 +0800 Subject: [PATCH 44/74] watchdog: Add Spreadtrum watchdog driver This patch adds the watchdog driver for Spreadtrum SC9860 platform. Signed-off-by: Eric Long Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 8 + drivers/watchdog/Makefile | 1 + drivers/watchdog/sprd_wdt.c | 399 ++++++++++++++++++++++++++++++++++++ 3 files changed, 408 insertions(+) create mode 100644 drivers/watchdog/sprd_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index be37e102dd307..633173260f5a9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -799,6 +799,14 @@ config RTD119X_WATCHDOG Say Y here to include support for the watchdog timer in Realtek RTD1295 SoCs. +config SPRD_WATCHDOG + tristate "Spreadtrum watchdog support" + depends on ARCH_SPRD || COMPILE_TEST + select WATCHDOG_CORE + help + Say Y here to include watchdog timer supported + by Spreadtrum system. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index b6cf527b31fbe..4b374b9e55e89 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_ZX2967_WATCHDOG) += zx2967_wdt.o obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o +obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/sprd_wdt.c b/drivers/watchdog/sprd_wdt.c new file mode 100644 index 0000000000000..a8b280ff33e05 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Spreadtrum Watchdog Timer Controller Driver"); +MODULE_LICENSE("GPL v2"); From 18cffd68e0c44fc09817ea2fb66bebe8a9188093 Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Wed, 15 Nov 2017 19:34:41 +0000 Subject: [PATCH 45/74] watchdog: xen_wdt: use the watchdog subsystem Change the xen_wdt driver to use the watchdog subsystem instead of registering and manipulating the char device directly through the misc API. This is mainly getting rid of the "write" and "ioctl" methods and part of the watchdog control logic (which are all implemented by the watchdog subsystem). Even though the watchdog subsystem supports registering and handling multiple watchdog devices at the same time, the xen_wdt driver has an inherent limitation of only one device due to the way the Xen hypervisor exposes watchdog functionality. However, the driver can now coexist with other watchdog devices (supported by different drivers). Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/xen_wdt.c | 245 ++++++++----------------------------- 1 file changed, 51 insertions(+), 194 deletions(-) diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c index 5dd5c3494d55e..51576e15a086c 100644 --- a/drivers/watchdog/xen_wdt.c +++ b/drivers/watchdog/xen_wdt.c @@ -9,9 +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_NAME "xen_wdt" #define DRV_VERSION "0.01" #include @@ -21,25 +19,20 @@ #include #include #include -#include #include #include #include -#include -#include #include #include #include #include static struct platform_device *platform_device; -static DEFINE_SPINLOCK(wdt_lock); static struct sched_watchdog wdt; static time64_t wdt_expires; -static bool is_active, expect_release; #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 +42,18 @@ module_param(nowayout, bool, S_IRUGO); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -static inline time64_t set_timeout(void) +static inline time64_t set_timeout(struct watchdog_device *wdd) { - wdt.timeout = timeout; - return ktime_get_seconds() + 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) { 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 +65,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) { 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 +94,72 @@ static int xen_wdt_kick(void) if (!err) wdt_expires = expires; - spin_unlock(&wdt_lock); - return err; } -static int xen_wdt_open(struct inode *inode, struct file *file) +static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd) { - int err; - - /* /dev/watchdog can only be opened once */ - if (xchg(&is_active, true)) - return -EBUSY; - - err = xen_wdt_start(); - if (err == -EBUSY) - err = xen_wdt_kick(); - return err ?: nonseekable_open(inode, file); + return wdt_expires - ktime_get_seconds(); } -static int xen_wdt_release(struct inode *inode, struct file *file) -{ - int err = 0; - - if (expect_release) - err = xen_wdt_stop(); - else { - pr_crit("unexpected close, not stopping watchdog!\n"); - xen_wdt_kick(); - } - is_active = err; - expect_release = false; - return err; -} - -static ssize_t xen_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - /* See if we got the magic character 'V' and reload the timer */ - if (len) { - if (!nowayout) { - size_t i; - - /* in case it was set long ago */ - expect_release = false; - - /* scan to see whether or not we got the magic - character */ - for (i = 0; i != len; i++) { - char c; - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - expect_release = true; - } - } - - /* someone wrote to us, we should reload the timer */ - xen_wdt_kick(); - } - return len; -} - -static long xen_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int new_options, retval = -EINVAL; - int new_timeout; - int __user *argp = (void __user *)arg; - static const struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = DRV_NAME, - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, argp); - - case WDIOC_SETOPTIONS: - if (get_user(new_options, argp)) - return -EFAULT; - - if (new_options & WDIOS_DISABLECARD) - retval = xen_wdt_stop(); - if (new_options & WDIOS_ENABLECARD) { - retval = xen_wdt_start(); - if (retval == -EBUSY) - retval = xen_wdt_kick(); - } - return retval; - - case WDIOC_KEEPALIVE: - xen_wdt_kick(); - return 0; - - case WDIOC_SETTIMEOUT: - if (get_user(new_timeout, argp)) - return -EFAULT; - if (!new_timeout) - return -EINVAL; - timeout = new_timeout; - xen_wdt_kick(); - /* fall through */ - case WDIOC_GETTIMEOUT: - return put_user(timeout, argp); - - case WDIOC_GETTIMELEFT: - retval = wdt_expires - ktime_get_seconds(); - 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 +170,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 = { @@ -351,7 +209,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); From c41fe7cdbd548121a59323a26d45e20ff08cc2eb Mon Sep 17 00:00:00 2001 From: Radu Rendec Date: Wed, 15 Nov 2017 19:34:42 +0000 Subject: [PATCH 46/74] watchdog: xen_wdt: remove info message and version number The initial info message (early in the xen_wdt_init_module() function) is not very useful and we already have a message on successful probe. If the probe fails, additional messages are printed anyway. The version number serves no useful purpose and it ran out of favor upstream anyway. Signed-off-by: Radu Rendec Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/xen_wdt.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c index 51576e15a086c..f1c016d015b3b 100644 --- a/drivers/watchdog/xen_wdt.c +++ b/drivers/watchdog/xen_wdt.c @@ -10,7 +10,6 @@ */ #define DRV_NAME "xen_wdt" -#define DRV_VERSION "0.01" #include #include @@ -189,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; @@ -216,5 +213,4 @@ module_exit(xen_wdt_cleanup_module); MODULE_AUTHOR("Jan Beulich "); MODULE_DESCRIPTION("Xen WatchDog Timer Driver"); -MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL"); From bf0209747e97fa5e75da8ab52ccb99faad96659c Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 17 Nov 2017 15:22:03 +0100 Subject: [PATCH 47/74] watchdog: pcwd_usb: remove unneeded DRIVER_LICENSE #define There is no need to #define the license of the driver, just put it in the MODULE_LICENSE() line directly as a text string. This allows tools that check that the module license matches the source code license to work properly, as there is no need to unwind the unneeded dereference, especially when it is defined just a few lines above from where it is used. Cc: Wim Van Sebroeck Cc: Guenter Roeck Reported-by: Philippe Ombredanne Signed-off-by: Greg Kroah-Hartman Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pcwd_usb.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index 47fe4c5449c99..4d02f26156f9c 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 " #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 */ From 49620a283eed91cd2fcd87af4f5381e8f03285aa Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Thu, 30 Nov 2017 09:48:04 +0100 Subject: [PATCH 48/74] watchdog: stm32: Fix copyright Uniformize STMicroelectronics copyrights header Add SPDX identifier Signed-off-by: Benjamin Gaignard Acked-by: Alexandre TORGUE CC: Yannick Fertre Acked-by: Yannick Fertre Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/stm32_iwdg.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c index be64a8699de3d..c97ad5619cb00 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 + * Copyright (C) STMicroelectronics 2017 + * Author: Yannick Fertre for STMicroelectronics. * * This driver is based on tegra_wdt.c * - * License terms: GNU General Public License (GPL), version 2 */ #include From 82c8b5c9774e64f8e1b9abec876fcfa8d8e3d791 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 20 Nov 2017 11:06:45 -0600 Subject: [PATCH 49/74] watchdog: eurotechwdt: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/eurotechwdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/eurotechwdt.c b/drivers/watchdog/eurotechwdt.c index 38e96712264f9..47f77a6fdfd66 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); From 13983959e804691b09322c2c8d71cd94f365655f Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Mon, 20 Nov 2017 11:22:21 -0600 Subject: [PATCH 50/74] watchdog: ib700wdt: mark expected switch fall-through In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. Notice that in this particular case I replaced "Fall" with a proper "fall through" comment, which is what GCC is expecting to find. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/ib700wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c index f2e4e1eeb8ddf..cc262284a6aa3 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); From aeebc6ba88ba3758ad95467ff6191fabf2074c13 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 6 Dec 2017 22:02:37 +0100 Subject: [PATCH 51/74] watchdog: hpwdt: fix unused variable warning The new hpwdt_my_nmi() function is used conditionally, which produces a harmless warning in some configurations: drivers/watchdog/hpwdt.c:478:12: error: 'hpwdt_my_nmi' defined but not used [-Werror=unused-function] This moves it inside of the #ifdef that protects its caller, to silence the warning. Fixes: 621174a92851 ("watchdog: hpwdt: Check source of NMI") Signed-off-by: Arnd Bergmann Reviewed-by: Jerry Hoemann Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/hpwdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index e61658310381e..f1f00dfc0e68c 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -475,12 +475,12 @@ static int hpwdt_time_left(void) return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); } +#ifdef CONFIG_HPWDT_NMI_DECODING static int hpwdt_my_nmi(void) { return ioread8(hpwdt_nmistat) & 0x6; } -#ifdef CONFIG_HPWDT_NMI_DECODING /* * NMI Handler */ From 8bc86475a0ded71bb0cfa95d88b1708292741356 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Wed, 13 Dec 2017 20:41:40 +0100 Subject: [PATCH 52/74] watchdog: document watchdog_init_timeout() wdd parameter All parameters of watchdog_init_timeout() are documented with exception of wdd, thus generating a build warning. This patch document it and so remove the following build warning: drivers/watchdog/watchdog_core.c:113: warning: No description found for parameter 'wdd' Signed-off-by: Corentin Labbe Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 8a8d952f8df96..eb8fa25f8eb29 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 * From 71d1f058844de6052835549f06c113f5014a8290 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 11 Dec 2017 11:21:08 -0600 Subject: [PATCH 53/74] watchdog: davinci_wdt: add restart function This adds a restart function to the davinci watchdog timer driver. This is copied from arch/arm/mach-davinci/time.c and will allow us to remove the code from there. Signed-off-by: David Lechner Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/davinci_wdt.c | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c index 2f46487af86d0..3e4c592c239f5 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); From 38a1222ae4f364d5bd5221fe305dbb0889f45d15 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Fri, 8 Dec 2017 11:18:35 +0100 Subject: [PATCH 54/74] watchdog: core: make sure the watchdog worker always works When running a command like 'chrt -f 50 dd if=/dev/zero of=/dev/null', the watchdog_worker fails to service the HW watchdog and the HW watchdog fires long before the watchdog soft timeout. At the moment, the watchdog_worker is invoked as a delayed work. Delayed works are handled by non realtime kernel threads. The WQ_HIGHPRI flag only increases the niceness of that threads. This patch replaces the delayed work logic by kthread delayed work, and sets the associated kernel task to SCHED_FIFO with the highest priority, in order to ensure that the watchdog worker will run as soon as possible. Signed-off-by: Christophe Leroy Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 48 ++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index eb35700962987..68bc29e6e79e3 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -39,6 +39,7 @@ #include /* For timeout functions */ #include /* For printk/panic/... */ #include /* For data references */ +#include /* For kthread_delayed_work */ #include /* For handling misc devices */ #include /* For module stuff/... */ #include /* For mutexes */ @@ -46,9 +47,10 @@ #include /* For memory functions */ #include /* For standard types (like size_t) */ #include /* For watchdog specific items */ -#include /* For workqueue */ #include /* For copy_to_user/put_user/... */ +#include /* For struct sched_param */ + #include "watchdog_core.h" #include "watchdog_pretimeout.h" @@ -67,7 +69,7 @@ struct watchdog_core_data { struct mutex lock; unsigned long last_keepalive; unsigned long last_hw_keepalive; - struct delayed_work work; + struct kthread_delayed_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 +81,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); @@ -140,9 +142,10 @@ static inline void watchdog_update_worker(struct watchdog_device *wdd) long t = watchdog_next_keepalive(wdd); if (t > 0) - mod_delayed_work(watchdog_wq, &wd_data->work, t); + kthread_mod_delayed_work(watchdog_kworker, + &wd_data->work, t); } else { - cancel_delayed_work(&wd_data->work); + kthread_cancel_delayed_work_sync(&wd_data->work); } } @@ -154,8 +157,8 @@ static int __watchdog_ping(struct watchdog_device *wdd) int err; if (time_is_after_jiffies(earliest_keepalive)) { - mod_delayed_work(watchdog_wq, &wd_data->work, - earliest_keepalive - jiffies); + kthread_mod_delayed_work(watchdog_kworker, &wd_data->work, + earliest_keepalive - jiffies); return 0; } @@ -203,12 +206,13 @@ 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(container_of(work, struct kthread_delayed_work, + work), + struct watchdog_core_data, work); mutex_lock(&wd_data->lock); if (watchdog_worker_should_ping(wd_data)) @@ -921,10 +925,10 @@ 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_delayed_work(&wd_data->work, watchdog_ping_work); if (wdd->id == 0) { old_wd_data = wd_data; @@ -970,7 +974,8 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) __module_get(wdd->ops->owner); kref_get(&wd_data->kref); if (handle_boot_enabled) - queue_delayed_work(watchdog_wq, &wd_data->work, 0); + kthread_queue_delayed_work(watchdog_kworker, + &wd_data->work, 0); else pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n", wdd->id); @@ -1007,7 +1012,7 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd) watchdog_stop(wdd); } - cancel_delayed_work_sync(&wd_data->work); + kthread_cancel_delayed_work_sync(&wd_data->work); kref_put(&wd_data->kref, watchdog_core_data_release); } @@ -1111,13 +1116,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) { @@ -1136,7 +1142,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; } @@ -1150,7 +1156,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); From 0be267255cef64e1c58475baa7b25568355a3816 Mon Sep 17 00:00:00 2001 From: Martin Kaiser Date: Mon, 1 Jan 2018 18:26:47 +0100 Subject: [PATCH 55/74] watchdog: imx2_wdt: restore previous timeout after suspend+resume When the watchdog device is suspended, its timeout is set to the maximum value. During resume, the previously set timeout should be restored. This does not work at the moment. The suspend function calls imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); and resume reverts this by calling imx2_wdt_set_timeout(wdog, wdog->timeout); However, imx2_wdt_set_timeout() updates wdog->timeout. Therefore, wdog->timeout is set to IMX2_WDT_MAX_TIME when we enter the resume function. Fix this by adding a new function __imx2_wdt_set_timeout() which only updates the hardware settings. imx2_wdt_set_timeout() now calls __imx2_wdt_set_timeout() and then saves the new timeout to wdog->timeout. During suspend, we call __imx2_wdt_set_timeout() directly so that wdog->timeout won't be updated and we can restore the previous value during resume. This approach makes wdog->timeout different from the actual setting in the hardware which is usually not a good thing. However, the two differ only while we're suspended and no kernel code is running, so it should be ok in this case. Signed-off-by: Martin Kaiser Cc: stable@vger.kernel.org Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/imx2_wdt.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 4874b0f18650f..518dfa1047cbd 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); } From 392d39a863583eb6439e2ec543818250b57ef99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 12 Jan 2018 09:44:53 +0000 Subject: [PATCH 56/74] watchdog: mt7621: set WDOG_HW_RUNNING bit when appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the watchdog hardware is enabled/running during boot, e.g. due to a boot loader configuring it, we must tell the watchdog framework about this fact so that it can ping the watchdog until userspace opens the device and takes over control. Do so using the WDOG_HW_RUNNING flag that exists for exactly that use-case. Given the watchdog driver core doesn't know what timeout was originally set by whoever started the watchdog (boot loader), we make sure to update the timeout in the hardware according to what the watchdog core thinks it is. Signed-off-by: André Draszik Cc: linux-watchdog@vger.kernel.org Cc: John Crispin Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/mt7621_wdt.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c index db38f80172187..eec57e5e1eaee 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, @@ -144,6 +149,20 @@ static int mt7621_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout, &pdev->dev); watchdog_set_nowayout(&mt7621_wdt_dev, nowayout); + if (mt7621_wdt_is_running(&mt7621_wdt_dev)) { + /* + * Make sure to apply timeout from watchdog core, taking + * the prescaler of this driver here into account (the + * boot loader might be using a different prescaler). + * + * To avoid spurious resets because of different scaling, + * we first disable the watchdog, set the new prescaler + * and timeout, and then re-enable the watchdog. + */ + mt7621_wdt_stop(&mt7621_wdt_dev); + mt7621_wdt_start(&mt7621_wdt_dev); + set_bit(WDOG_HW_RUNNING, &mt7621_wdt_dev.status); + } ret = watchdog_register_device(&mt7621_wdt_dev); From 1d2e5eb521a7edeac9e9decd3d526b04e307bb41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Draszik?= Date: Fri, 12 Jan 2018 09:44:54 +0000 Subject: [PATCH 57/74] watchdog: mt7621: switch to using managed devm_watchdog_register_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This does the necessary cleanup on driver unload automatically. Signed-off-by: André Draszik Cc: linux-watchdog@vger.kernel.org Cc: John Crispin Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/mt7621_wdt.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c index eec57e5e1eaee..5c4a764717c4d 100644 --- a/drivers/watchdog/mt7621_wdt.c +++ b/drivers/watchdog/mt7621_wdt.c @@ -133,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); @@ -164,16 +163,7 @@ static int mt7621_wdt_probe(struct platform_device *pdev) set_bit(WDOG_HW_RUNNING, &mt7621_wdt_dev.status); } - ret = watchdog_register_device(&mt7621_wdt_dev); - - return 0; -} - -static int mt7621_wdt_remove(struct platform_device *pdev) -{ - watchdog_unregister_device(&mt7621_wdt_dev); - - return 0; + return devm_watchdog_register_device(&pdev->dev, &mt7621_wdt_dev); } static void mt7621_wdt_shutdown(struct platform_device *pdev) @@ -189,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, From 1ff688209e2ed23f699269b9733993e2ce123fd2 Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 18 Jan 2018 12:11:21 +0100 Subject: [PATCH 58/74] watchdog: core: make sure the watchdog_worker is not deferred commit 4cd13c21b207e ("softirq: Let ksoftirqd do its job") has the effect of deferring timer handling in case of high CPU load, hence delaying the delayed work allthought the worker is running which high realtime priority. As hrtimers are not managed by softirqs, this patch replaces the delayed work by a plain work and uses an hrtimer to schedule that work. Signed-off-by: Christophe Leroy Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 86 ++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 68bc29e6e79e3..ffbdc4642ea55 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -36,10 +36,10 @@ #include /* For the -ENODEV/... values */ #include /* For file operations */ #include /* For __init/__exit/... */ -#include /* For timeout functions */ +#include /* For hrtimers */ #include /* For printk/panic/... */ #include /* For data references */ -#include /* For kthread_delayed_work */ +#include /* For kthread_work */ #include /* For handling misc devices */ #include /* For module stuff/... */ #include /* For mutexes */ @@ -67,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 kthread_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 ? */ @@ -109,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; @@ -130,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) @@ -139,30 +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) - kthread_mod_delayed_work(watchdog_kworker, - &wd_data->work, t); + hrtimer_start(&wd_data->timer, t, HRTIMER_MODE_REL); } else { - kthread_cancel_delayed_work_sync(&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)) { - kthread_mod_delayed_work(watchdog_kworker, &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 */ @@ -195,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); } @@ -210,9 +218,7 @@ static void watchdog_ping_work(struct kthread_work *work) { struct watchdog_core_data *wd_data; - wd_data = container_of(container_of(work, struct kthread_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)) @@ -220,6 +226,16 @@ static void watchdog_ping_work(struct kthread_work *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 @@ -234,7 +250,7 @@ static void watchdog_ping_work(struct kthread_work *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)) @@ -242,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 @@ -928,7 +944,9 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) if (IS_ERR_OR_NULL(watchdog_kworker)) return -ENODEV; - kthread_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; @@ -964,7 +982,7 @@ 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, @@ -974,8 +992,7 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) __module_get(wdd->ops->owner); kref_get(&wd_data->kref); if (handle_boot_enabled) - kthread_queue_delayed_work(watchdog_kworker, - &wd_data->work, 0); + 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); @@ -1012,7 +1029,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd) watchdog_stop(wdd); } - kthread_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); } From 2b750cffe1ed05c9001d9524b5815e1f50461a44 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:06 -0800 Subject: [PATCH 59/74] watchdog: sp5100_tco: Always use SP5100_IO_PM_{INDEX_REG,DATA_REG} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SP5100_IO_PM_INDEX_REG and SB800_IO_PM_INDEX_REG are used inconsistently and define the same value. Just use SP5100_IO_PM_INDEX_REG throughout. Do the same for SP5100_IO_PM_DATA_REG and SB800_IO_PM_DATA_REG. Use helper functions to access the indexed registers. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 94 ++++++++++++++++++----------------- drivers/watchdog/sp5100_tco.h | 7 +-- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 028618c5eebac..05f9d27a306a8 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -48,7 +48,6 @@ 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; @@ -132,25 +131,38 @@ static int tco_timer_set_heartbeat(int 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) { - int val; + 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(void) +{ if (!tco_has_sp5100_reg_layout(sp5100_tco_pci)) { /* 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); + sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL, + ~SB800_PM_WATCHDOG_DISABLE, + SB800_PCI_WATCHDOG_DECODE_EN); } else { + u32 val; + /* For SP5100 or SB7x0 */ /* Enable watchdog decode bit */ pci_read_config_dword(sp5100_tco_pci, @@ -164,11 +176,9 @@ 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); } } @@ -321,6 +331,17 @@ static const struct pci_device_id sp5100_tco_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); +static u8 sp5100_tco_read_pm_reg32(u8 index) +{ + u32 val = 0; + int i; + + for (i = 3; i >= 0; i--) + val = (val << 8) + sp5100_tco_read_pm_reg8(index + i); + + return val; +} + /* * Init & exit routines */ @@ -329,7 +350,7 @@ 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; + u8 base_addr; /* Match the PCI device */ for_each_pci_dev(dev) { @@ -351,35 +372,25 @@ static unsigned char sp5100_tco_setupdevice(void) */ if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) { dev_name = SP5100_DEVNAME; - index_reg = SP5100_IO_PM_INDEX_REG; - data_reg = SP5100_IO_PM_DATA_REG; base_addr = SP5100_PM_WATCHDOG_BASE; } else { dev_name = SB800_DEVNAME; - index_reg = SB800_IO_PM_INDEX_REG; - data_reg = SB800_IO_PM_DATA_REG; base_addr = SB800_PM_WATCHDOG_BASE; } /* Request the IO ports used by this driver */ - pm_iobase = SP5100_IO_PM_INDEX_REG; - if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, dev_name)) { - pr_err("I/O address 0x%04x already in use\n", pm_iobase); + if (!request_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE, + dev_name)) { + pr_err("I/O address 0x%04x already in use\n", + SP5100_IO_PM_INDEX_REG); goto exit; } /* * First, Find the watchdog timer MMIO address from indirect I/O. + * Low three bits of BASE are reserved. */ - 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); + val = sp5100_tco_read_pm_reg32(base_addr) & 0xfffffff8; pr_debug("Got 0x%04x from indirect I/O\n", val); @@ -400,14 +411,7 @@ static unsigned char sp5100_tco_setupdevice(void) 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); + val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN); } /* The SBResource_MMIO is enabled and mapped memory space? */ @@ -470,7 +474,7 @@ static unsigned char sp5100_tco_setupdevice(void) unreg_mem_region: release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); unreg_region: - release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); + release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); exit: return 0; } @@ -517,7 +521,7 @@ static int sp5100_tco_init(struct platform_device *dev) exit: iounmap(tcobase); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); - release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); + release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); return ret; } @@ -531,7 +535,7 @@ static void sp5100_tco_cleanup(void) 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); + release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); } static int sp5100_tco_remove(struct platform_device *dev) diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index 1af4dee713371..f495fe03887ab 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -24,10 +24,11 @@ * 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 @@ -44,11 +45,7 @@ #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 From f541c09ebfc61697b586b38c9ebaf4b70defb278 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:07 -0800 Subject: [PATCH 60/74] watchdog: sp5100_tco: Fix watchdog disable bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to all published information, the watchdog disable bit for SB800 compatible controllers is bit 1 of PM register 0x48, not bit 2. For the most part that doesn't matter in practice, since the bit has to be cleared to enable watchdog address decoding, which is the default setting, but it still needs to be fixed. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index f495fe03887ab..2622cfe23dc19 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -52,7 +52,7 @@ #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_DISABLE (1 << 1) #define SB800_PM_WATCHDOG_SECOND_RES (3 << 0) #define SB800_ACPI_MMIO_DECODE_EN (1 << 0) #define SB800_ACPI_MMIO_SEL (1 << 1) From 16e7730bd7ec7f4ec19628e5ddd172df28cf996d Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:08 -0800 Subject: [PATCH 61/74] watchdog: sp5100_tco: Use request_muxed_region where possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use request_muxed_region for multiplexed IO memory regions. Also, SP5100_IO_PM_INDEX_REG/SP5100_IO_PM_DATA_REG are only used during initialization; it is unnecessary to keep the address range reserved. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 05f9d27a306a8..11109ac959e2b 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -379,8 +379,8 @@ static unsigned char sp5100_tco_setupdevice(void) } /* Request the IO ports used by this driver */ - if (!request_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE, - dev_name)) { + if (!request_muxed_region(SP5100_IO_PM_INDEX_REG, + SP5100_PM_IOPORTS_SIZE, dev_name)) { pr_err("I/O address 0x%04x already in use\n", SP5100_IO_PM_INDEX_REG); goto exit; @@ -468,6 +468,7 @@ static unsigned char sp5100_tco_setupdevice(void) */ tco_timer_stop(); + release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); /* Done */ return 1; @@ -521,7 +522,6 @@ static int sp5100_tco_init(struct platform_device *dev) exit: iounmap(tcobase); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); - release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); return ret; } @@ -535,7 +535,6 @@ static void sp5100_tco_cleanup(void) misc_deregister(&sp5100_tco_miscdev); iounmap(tcobase); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); - release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); } static int sp5100_tco_remove(struct platform_device *dev) From 23dfe140057baa165daebadf8f4600e3603e0954 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:09 -0800 Subject: [PATCH 62/74] watchdog: sp5100_tco: Use standard error codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By using standard error codes, we can identify and return more than one error condition. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 11109ac959e2b..0e816f2cdb070 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -345,12 +345,13 @@ static u8 sp5100_tco_read_pm_reg32(u8 index) /* * Init & exit routines */ -static unsigned char sp5100_tco_setupdevice(void) +static int sp5100_tco_setupdevice(void) { struct pci_dev *dev = NULL; const char *dev_name = NULL; u32 val; u8 base_addr; + int ret; /* Match the PCI device */ for_each_pci_dev(dev) { @@ -361,7 +362,7 @@ static unsigned char sp5100_tco_setupdevice(void) } if (!sp5100_tco_pci) - return 0; + return -ENODEV; pr_info("PCI Vendor ID: 0x%x, Device ID: 0x%x, Revision ID: 0x%x\n", sp5100_tco_pci->vendor, sp5100_tco_pci->device, @@ -383,7 +384,7 @@ static unsigned char sp5100_tco_setupdevice(void) SP5100_PM_IOPORTS_SIZE, dev_name)) { pr_err("I/O address 0x%04x already in use\n", SP5100_IO_PM_INDEX_REG); - goto exit; + return -EBUSY; } /* @@ -433,6 +434,7 @@ static unsigned char sp5100_tco_setupdevice(void) pr_debug("SBResource_MMIO is disabled(0x%04x)\n", val); pr_notice("failed to find MMIO address, giving up.\n"); + ret = -ENODEV; goto unreg_region; setup_wdt: @@ -441,6 +443,7 @@ static unsigned char sp5100_tco_setupdevice(void) tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); if (!tcobase) { pr_err("failed to get tcobase address\n"); + ret = -ENOMEM; goto unreg_mem_region; } @@ -470,14 +473,13 @@ static unsigned char sp5100_tco_setupdevice(void) release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); /* Done */ - return 1; + return 0; unreg_mem_region: release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); unreg_region: release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); -exit: - return 0; + return ret; } static int sp5100_tco_init(struct platform_device *dev) @@ -488,8 +490,9 @@ static int sp5100_tco_init(struct platform_device *dev) * Check whether or not the hardware watchdog is there. If found, then * set it up. */ - if (!sp5100_tco_setupdevice()) - return -ENODEV; + ret = sp5100_tco_setupdevice(); + if (ret) + return ret; /* Check to see if last reboot was due to watchdog timeout */ pr_info("Last reboot was %striggered by watchdog.\n", From e189410cbef1374ec29d42b72df0d58d5c7e193c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:10 -0800 Subject: [PATCH 63/74] watchdog: sp5100_tco: Clean up sp5100_tco_setupdevice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are too many unnecessary goto statements in sp5100_tco_setupdevice(). Rearrange the code and limit goto statements to error handling. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 62 ++++++++++++++++------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 0e816f2cdb070..5a13ab483c508 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -396,48 +396,44 @@ static int sp5100_tco_setupdevice(void) 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 + if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) { 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) */ + val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN); + } - /* - * 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) */ - val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN); - } - - /* The SBResource_MMIO is enabled and mapped memory space? */ - if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) == + /* 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) { + pr_notice("failed to find MMIO address, giving up.\n"); + ret = -ENODEV; + goto unreg_region; + } /* 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 + if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) { 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"); - ret = -ENODEV; - goto unreg_region; + ret = -EBUSY; + goto unreg_region; + } + pr_debug("Got 0x%04x from SBResource_MMIO register\n", val); + } -setup_wdt: tcobase_phys = val; tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); @@ -472,7 +468,7 @@ static int sp5100_tco_setupdevice(void) tco_timer_stop(); release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); - /* Done */ + return 0; unreg_mem_region: From a34834435f46225d9371b77599f44e3ec2e43b08 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:11 -0800 Subject: [PATCH 64/74] watchdog: sp5100_tco: Match PCI device early MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match PCI device in module init function, not in the probe function. It is pointless trying to probe if we can determine early that the device is not supported. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 66 ++++++++++++++++------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 5a13ab483c508..5868c6b6bf170 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -312,25 +312,6 @@ static struct miscdevice sp5100_tco_miscdev = { .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); - static u8 sp5100_tco_read_pm_reg32(u8 index) { u32 val = 0; @@ -347,27 +328,11 @@ static u8 sp5100_tco_read_pm_reg32(u8 index) */ static int sp5100_tco_setupdevice(void) { - struct pci_dev *dev = NULL; const char *dev_name = NULL; u32 val; u8 base_addr; 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; - } - } - - if (!sp5100_tco_pci) - return -ENODEV; - - 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. */ @@ -557,10 +522,41 @@ static struct platform_driver sp5100_tco_driver = { }, }; +/* + * 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_module(void) { + struct pci_dev *dev = NULL; int err; + /* 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 v%s\n", TCO_VERSION); err = platform_driver_register(&sp5100_tco_driver); From fd8f9093a07306036cf63eda35e3357db3387d3a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:12 -0800 Subject: [PATCH 65/74] watchdog: sp5100_tco: Use dev_ print functions where possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use dev_ instead of pr_ functions where possible. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 40 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 5868c6b6bf170..ff240e5be833a 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -326,7 +326,7 @@ static u8 sp5100_tco_read_pm_reg32(u8 index) /* * Init & exit routines */ -static int sp5100_tco_setupdevice(void) +static int sp5100_tco_setupdevice(struct device *dev) { const char *dev_name = NULL; u32 val; @@ -347,8 +347,8 @@ static int sp5100_tco_setupdevice(void) /* Request the IO ports used by this driver */ if (!request_muxed_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE, dev_name)) { - pr_err("I/O address 0x%04x already in use\n", - SP5100_IO_PM_INDEX_REG); + dev_err(dev, "I/O address 0x%04x already in use\n", + SP5100_IO_PM_INDEX_REG); return -EBUSY; } @@ -358,12 +358,12 @@ static int sp5100_tco_setupdevice(void) */ val = sp5100_tco_read_pm_reg32(base_addr) & 0xfffffff8; - pr_debug("Got 0x%04x from indirect I/O\n", val); + dev_dbg(dev, "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)) { - pr_debug("MMIO address 0x%04x already in use\n", val); + dev_dbg(dev, "MMIO address 0x%04x already in use\n", val); /* * Secondly, Find the watchdog timer MMIO address * from SBResource_MMIO register. @@ -381,7 +381,8 @@ static int sp5100_tco_setupdevice(void) /* 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) { - pr_notice("failed to find MMIO address, giving up.\n"); + dev_notice(dev, + "failed to find MMIO address, giving up.\n"); ret = -ENODEV; goto unreg_region; } @@ -392,23 +393,24 @@ static int sp5100_tco_setupdevice(void) /* Check MMIO address conflict */ if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, dev_name)) { - pr_debug("MMIO address 0x%04x already in use\n", val); + dev_dbg(dev, "MMIO address 0x%04x already in use\n", + val); ret = -EBUSY; goto unreg_region; } - pr_debug("Got 0x%04x from SBResource_MMIO register\n", val); + dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val); } tcobase_phys = val; tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); if (!tcobase) { - pr_err("failed to get tcobase address\n"); + dev_err(dev, "failed to get tcobase address\n"); ret = -ENOMEM; goto unreg_mem_region; } - pr_info("Using 0x%04x for watchdog MMIO address\n", val); + dev_info(dev, "Using 0x%04x for watchdog MMIO address\n", val); /* Setup the watchdog timer */ tco_timer_enable(); @@ -443,21 +445,22 @@ static int sp5100_tco_setupdevice(void) return ret; } -static int sp5100_tco_init(struct platform_device *dev) +static int sp5100_tco_init(struct platform_device *pdev) { + struct device *dev = &pdev->dev; int ret; /* * Check whether or not the hardware watchdog is there. If found, then * set it up. */ - ret = sp5100_tco_setupdevice(); + ret = sp5100_tco_setupdevice(dev); if (ret) return ret; /* Check to see if last reboot was due to watchdog timeout */ - pr_info("Last reboot was %striggered by watchdog.\n", - tco_wdt_fired ? "" : "not "); + dev_info(dev, "Last reboot was %striggered by watchdog.\n", + tco_wdt_fired ? "" : "not "); /* * Check that the heartbeat value is within it's range. @@ -470,16 +473,16 @@ static int sp5100_tco_init(struct platform_device *dev) ret = misc_register(&sp5100_tco_miscdev); if (ret != 0) { - pr_err("cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto exit; } clear_bit(0, &timer_alive); /* Show module parameters */ - pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", - tcobase, heartbeat, nowayout); + dev_info(dev, "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", + tcobase, heartbeat, nowayout); return 0; @@ -581,7 +584,6 @@ static void __exit sp5100_tco_cleanup_module(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); From 5bbecc5d3454d1069baf061a2cf0f04d0f0b9e7f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:13 -0800 Subject: [PATCH 66/74] watchdog: sp5100_tco: Clean up function and variable names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use more common function and variable names. Use pdev instead of dev for platform device. Use sp5100_tco_probe() instead of sp5100_tco_init() for the probe function. Drop sp5100_tco_cleanup(); just move the code into sp5100_tco_remove(). Use sp5100_tco_init() instead of sp5100_tco_init_module() for the module initialization function. Use sp5100_tco_exit() instead of sp5100_tco_cleanup_module() for the module exit function. Use consistent defines for accessing the watchdog control register. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 25 ++++++++++--------------- drivers/watchdog/sp5100_tco.h | 5 ++--- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index ff240e5be833a..1123fad38fdfe 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -421,8 +421,8 @@ static int sp5100_tco_setupdevice(struct device *dev) * Save WatchDogFired status, because WatchDogFired flag is * cleared here. */ - tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED; - val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; + tco_wdt_fired = val & SP5100_WDT_FIRED; + val &= ~SP5100_WDT_ACTION_RESET; writel(val, SP5100_WDT_CONTROL(tcobase)); /* Set a reasonable heartbeat before we stop the timer */ @@ -445,7 +445,7 @@ static int sp5100_tco_setupdevice(struct device *dev) return ret; } -static int sp5100_tco_init(struct platform_device *pdev) +static int sp5100_tco_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; int ret; @@ -492,7 +492,7 @@ static int sp5100_tco_init(struct platform_device *pdev) return ret; } -static void sp5100_tco_cleanup(void) +static int sp5100_tco_remove(struct platform_device *pdev) { /* Stop the timer before we leave */ if (!nowayout) @@ -502,22 +502,17 @@ static void sp5100_tco_cleanup(void) misc_deregister(&sp5100_tco_miscdev); iounmap(tcobase); release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); -} -static int sp5100_tco_remove(struct platform_device *dev) -{ - if (tcobase) - sp5100_tco_cleanup(); return 0; } -static void sp5100_tco_shutdown(struct platform_device *dev) +static void sp5100_tco_shutdown(struct platform_device *pdev) { tco_timer_stop(); } static struct platform_driver sp5100_tco_driver = { - .probe = sp5100_tco_init, + .probe = sp5100_tco_probe, .remove = sp5100_tco_remove, .shutdown = sp5100_tco_shutdown, .driver = { @@ -544,7 +539,7 @@ static const struct pci_device_id sp5100_tco_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); -static int __init sp5100_tco_init_module(void) +static int __init sp5100_tco_init(void) { struct pci_dev *dev = NULL; int err; @@ -580,14 +575,14 @@ static int __init sp5100_tco_init_module(void) return err; } -static void __exit sp5100_tco_cleanup_module(void) +static void __exit sp5100_tco_exit(void) { platform_device_unregister(sp5100_tco_platform_device); platform_driver_unregister(&sp5100_tco_driver); } -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 2622cfe23dc19..cc00f1157220a 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -15,6 +15,8 @@ #define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */ #define SP5100_WDT_START_STOP_BIT (1 << 0) +#define SP5100_WDT_FIRED (1 << 1) +#define SP5100_WDT_ACTION_RESET (1 << 2) #define SP5100_WDT_TRIGGER_BIT (1 << 7) #define SP5100_PM_IOPORTS_SIZE 0x02 @@ -34,9 +36,6 @@ #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) From 7cd9d5fff792026a908ccf3c229e1ec84668733c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:14 -0800 Subject: [PATCH 67/74] watchdog: sp5100_tco: Convert to use watchdog subsystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert to watchdog subsystem. As part of that rework, use devm functions where possible, and replace almost all static variables with a dynamically allocated data structure. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 358 ++++++++++------------------------ 1 file changed, 102 insertions(+), 256 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 1123fad38fdfe..bb6c4608c1c05 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -24,37 +24,31 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include #include #include -#include -#include -#include -#include -#include #include -#include #include -#include -#include +#include +#include #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 DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ -static unsigned long timer_alive; -static char tco_expect_close; -static struct pci_dev *sp5100_tco_pci; + +struct sp5100_tco { + struct watchdog_device wdd; + void __iomem *tcobase; +}; /* 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 */ @@ -79,55 +73,52 @@ static bool tco_has_sp5100_reg_layout(struct pci_dev *dev) dev->revision < 0x40; } -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; } @@ -182,137 +173,7 @@ static void tco_timer_enable(void) } } -/* - * /dev/watchdog handling - */ - -static int sp5100_tco_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 */ - tco_timer_start(); - tco_timer_keepalive(); - return nonseekable_open(inode, file); -} - -static int sp5100_tco_release(struct inode *inode, struct file *file) -{ - /* Shut off the timer. */ - if (tco_expect_close == 42) { - tco_timer_stop(); - } else { - pr_crit("Unexpected close, not stopping watchdog!\n"); - tco_timer_keepalive(); - } - clear_bit(0, &timer_alive); - tco_expect_close = 0; - return 0; -} - -static ssize_t sp5100_tco_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - /* See if we got the magic character 'V' and reload the timer */ - if (len) { - if (!nowayout) { - size_t i; - - /* note: just in case someone wrote the magic character - * five months ago... */ - tco_expect_close = 0; - - /* scan to see whether or not we got the magic character - */ - for (i = 0; i != len; i++) { - char c; - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - tco_expect_close = 42; - } - } - - /* someone wrote to us, we should reload the timer */ - tco_timer_keepalive(); - } - return len; -} - -static long sp5100_tco_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int new_options, retval = -EINVAL; - int new_heartbeat; - void __user *argp = (void __user *)arg; - int __user *p = argp; - static const struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = TCO_MODULE_NAME, - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, - sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_SETOPTIONS: - if (get_user(new_options, p)) - return -EFAULT; - if (new_options & WDIOS_DISABLECARD) { - tco_timer_stop(); - retval = 0; - } - if (new_options & WDIOS_ENABLECARD) { - tco_timer_start(); - tco_timer_keepalive(); - retval = 0; - } - return retval; - case WDIOC_KEEPALIVE: - tco_timer_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_heartbeat, p)) - return -EFAULT; - if (tco_timer_set_heartbeat(new_heartbeat)) - return -EINVAL; - tco_timer_keepalive(); - /* Fall through */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - default: - return -ENOTTY; - } -} - -/* - * Kernel Interfaces - */ - -static const struct file_operations sp5100_tco_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sp5100_tco_write, - .unlocked_ioctl = sp5100_tco_ioctl, - .open = sp5100_tco_open, - .release = sp5100_tco_release, -}; - -static struct miscdevice sp5100_tco_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sp5100_tco_fops, -}; - -static u8 sp5100_tco_read_pm_reg32(u8 index) +static u32 sp5100_tco_read_pm_reg32(u8 index) { u32 val = 0; int i; @@ -323,14 +184,13 @@ static u8 sp5100_tco_read_pm_reg32(u8 index) return val; } -/* - * Init & exit routines - */ -static int sp5100_tco_setupdevice(struct device *dev) +static int sp5100_tco_setupdevice(struct device *dev, + struct watchdog_device *wdd) { - const char *dev_name = NULL; - u32 val; + struct sp5100_tco *tco = watchdog_get_drvdata(wdd); + const char *dev_name; u8 base_addr; + u32 val; int ret; /* @@ -361,8 +221,8 @@ static int sp5100_tco_setupdevice(struct device *dev) dev_dbg(dev, "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)) { + if (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) { dev_dbg(dev, "MMIO address 0x%04x already in use\n", val); /* * Secondly, Find the watchdog timer MMIO address @@ -391,8 +251,8 @@ static int sp5100_tco_setupdevice(struct device *dev) /* 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)) { + if (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) { dev_dbg(dev, "MMIO address 0x%04x already in use\n", val); ret = -EBUSY; @@ -401,13 +261,11 @@ static int sp5100_tco_setupdevice(struct device *dev) dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val); } - tcobase_phys = val; - - tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); - if (!tcobase) { + tco->tcobase = devm_ioremap(dev, val, SP5100_WDT_MEM_MAP_SIZE); + if (!tco->tcobase) { dev_err(dev, "failed to get tcobase address\n"); ret = -ENOMEM; - goto unreg_mem_region; + goto unreg_region; } dev_info(dev, "Using 0x%04x for watchdog MMIO address\n", val); @@ -416,107 +274,95 @@ static int sp5100_tco_setupdevice(struct device *dev) tco_timer_enable(); /* Check that the watchdog action is set to reset the system */ - val = readl(SP5100_WDT_CONTROL(tcobase)); + val = readl(SP5100_WDT_CONTROL(tco->tcobase)); /* * Save WatchDogFired status, because WatchDogFired flag is * cleared here. */ - tco_wdt_fired = val & SP5100_WDT_FIRED; + if (val & SP5100_WDT_FIRED) + wdd->bootstatus = WDIOF_CARDRESET; val &= ~SP5100_WDT_ACTION_RESET; - writel(val, SP5100_WDT_CONTROL(tcobase)); + 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); release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); return 0; -unreg_mem_region: - release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); unreg_region: release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE); return ret; } +static struct watchdog_info sp5100_tco_wdt_info = { + .identity = "SP5100 TCO timer", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops sp5100_tco_wdt_ops = { + .owner = THIS_MODULE, + .start = tco_timer_start, + .stop = tco_timer_stop, + .ping = tco_timer_ping, + .set_timeout = tco_timer_set_timeout, +}; + static int sp5100_tco_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + struct watchdog_device *wdd; + struct sp5100_tco *tco; int ret; - /* - * Check whether or not the hardware watchdog is there. If found, then - * set it up. - */ - ret = sp5100_tco_setupdevice(dev); + tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL); + if (!tco) + return -ENOMEM; + + 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; - /* Check to see if last reboot was due to watchdog timeout */ - dev_info(dev, "Last reboot was %striggered by watchdog.\n", - tco_wdt_fired ? "" : "not "); - - /* - * Check that the heartbeat value is within it's range. - * If not, reset to the default. - */ - if (tco_timer_set_heartbeat(heartbeat)) { - heartbeat = WATCHDOG_HEARTBEAT; - tco_timer_set_heartbeat(heartbeat); - } - - ret = misc_register(&sp5100_tco_miscdev); - if (ret != 0) { - dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto exit; + 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 */ - dev_info(dev, "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", - tcobase, heartbeat, nowayout); + dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n", + wdd->timeout, nowayout); return 0; - -exit: - iounmap(tcobase); - release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); - return ret; -} - -static int sp5100_tco_remove(struct platform_device *pdev) -{ - /* 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); - - return 0; -} - -static void sp5100_tco_shutdown(struct platform_device *pdev) -{ - tco_timer_stop(); } static struct platform_driver sp5100_tco_driver = { .probe = sp5100_tco_probe, - .remove = sp5100_tco_remove, - .shutdown = sp5100_tco_shutdown, .driver = { - .name = TCO_MODULE_NAME, + .name = TCO_DRIVER_NAME, }, }; @@ -555,14 +401,14 @@ static int __init sp5100_tco_init(void) if (!sp5100_tco_pci) return -ENODEV; - pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION); + pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n"); err = platform_driver_register(&sp5100_tco_driver); if (err) return err; - sp5100_tco_platform_device = platform_device_register_simple( - TCO_MODULE_NAME, -1, NULL, 0); + sp5100_tco_platform_device = + platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0); if (IS_ERR(sp5100_tco_platform_device)) { err = PTR_ERR(sp5100_tco_platform_device); goto unreg_platform_driver; From 17b20833ff427e285e36e4ce6e5dc8b0046fa0a4 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:15 -0800 Subject: [PATCH 68/74] watchdog: sp5100_tco: Use bit operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using bit operations makes it easier to improve the driver. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.h | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index cc00f1157220a..ca0721c8d8793 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -7,6 +7,8 @@ * TCO timer driver for sp5100 chipsets */ +#include + /* * Some address definitions for the Watchdog */ @@ -14,10 +16,10 @@ #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_FIRED (1 << 1) -#define SP5100_WDT_ACTION_RESET (1 << 2) -#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_TRIGGER_BIT BIT(7) #define SP5100_PM_IOPORTS_SIZE 0x02 @@ -37,10 +39,10 @@ #define SP5100_PM_WATCHDOG_BASE 0x6C #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" @@ -50,12 +52,11 @@ #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 << 1) -#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 From f7781b067522aa269213e8025c80cbed1868d349 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:16 -0800 Subject: [PATCH 69/74] watchdog: sp5100-tco: Abort if watchdog is disabled by hardware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the watchdog control register indicates that the watchdog hardware is disabled even after we tried to enable it, there is no point to instantiate the driver. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 8 +++++++- drivers/watchdog/sp5100_tco.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index bb6c4608c1c05..23246cb40598c 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -273,14 +273,20 @@ static int sp5100_tco_setupdevice(struct device *dev, /* Setup the watchdog timer */ tco_timer_enable(); - /* Check that the watchdog action is set to reset the system */ val = readl(SP5100_WDT_CONTROL(tco->tcobase)); + if (val & SP5100_WDT_DISABLED) { + dev_err(dev, "Watchdog hardware is disabled\n"); + ret = -ENODEV; + goto unreg_region; + } + /* * Save WatchDogFired status, because WatchDogFired flag is * cleared here. */ if (val & SP5100_WDT_FIRED) wdd->bootstatus = WDIOF_CARDRESET; + /* Set watchdog action to reset the system */ val &= ~SP5100_WDT_ACTION_RESET; writel(val, SP5100_WDT_CONTROL(tco->tcobase)); diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index ca0721c8d8793..008b2094bd13a 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -19,6 +19,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 From 887d2ec51e34b704837816a10f185f3b604170fd Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 24 Dec 2017 13:04:17 -0800 Subject: [PATCH 70/74] watchdog: sp5100_tco: Add support for recent FCH versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting with Family 16h Models 30h-3Fh and Family 15h Models 60h-6Fh, watchdog address space decoding has changed. The cutover point is already identified in the i2c-piix2 driver, so use the same mechanism. Cc: Zoltán Böszörményi Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/sp5100_tco.c | 169 ++++++++++++++++++++++------------ drivers/watchdog/sp5100_tco.h | 21 +++++ 2 files changed, 133 insertions(+), 57 deletions(-) diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 23246cb40598c..41aaae2d5287f 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" */ /* @@ -40,9 +45,14 @@ /* internal variables */ +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 */ @@ -67,10 +77,20 @@ 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 int tco_timer_start(struct watchdog_device *wdd) @@ -139,9 +159,12 @@ static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set) outb(val, SP5100_IO_PM_DATA_REG); } -static void tco_timer_enable(void) +static void tco_timer_enable(struct sp5100_tco *tco) { - if (!tco_has_sp5100_reg_layout(sp5100_tco_pci)) { + u32 val; + + switch (tco->tco_reg_layout) { + case sb800: /* For SB800 or later */ /* Set the Watchdog timer resolution to 1 sec */ sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG, @@ -151,9 +174,8 @@ static void tco_timer_enable(void) sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL, ~SB800_PM_WATCHDOG_DISABLE, SB800_PCI_WATCHDOG_DECODE_EN); - } else { - u32 val; - + break; + case sp5100: /* For SP5100 or SB7x0 */ /* Enable watchdog decode bit */ pci_read_config_dword(sp5100_tco_pci, @@ -170,6 +192,13 @@ static void tco_timer_enable(void) 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; } } @@ -189,89 +218,113 @@ static int sp5100_tco_setupdevice(struct device *dev, { struct sp5100_tco *tco = watchdog_get_drvdata(wdd); const char *dev_name; - u8 base_addr; - u32 val; + u32 mmio_addr = 0, val; int ret; - /* - * Determine type of southbridge chipset. - */ - if (tco_has_sp5100_reg_layout(sp5100_tco_pci)) { - dev_name = SP5100_DEVNAME; - base_addr = SP5100_PM_WATCHDOG_BASE; - } else { - dev_name = SB800_DEVNAME; - base_addr = SB800_PM_WATCHDOG_BASE; - } - /* Request the IO ports used by this driver */ if (!request_muxed_region(SP5100_IO_PM_INDEX_REG, - SP5100_PM_IOPORTS_SIZE, dev_name)) { + 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; } /* - * First, Find the watchdog timer MMIO address from indirect I/O. - * Low three bits of BASE are reserved. + * Determine type of southbridge chipset. */ - val = sp5100_tco_read_pm_reg32(base_addr) & 0xfffffff8; - - dev_dbg(dev, "Got 0x%04x from indirect I/O\n", val); + switch (tco->tco_reg_layout) { + case sp5100: + dev_name = SP5100_DEVNAME; + mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) & + 0xfffffff8; + break; + case sb800: + dev_name = SB800_DEVNAME; + mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) & + 0xfffffff8; + break; + case efch: + dev_name = SB800_DEVNAME; + val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN); + if (val & EFCH_PM_DECODEEN_WDT_TMREN) + mmio_addr = EFCH_PM_WDT_ADDR; + break; + default: + return -ENODEV; + } /* Check MMIO address conflict */ - if (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE, + if (!mmio_addr || + !devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE, dev_name)) { - dev_dbg(dev, "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)) { + 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, - &val); - } else { + &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) */ - val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN); - } - - /* The SBResource_MMIO is enabled and mapped memory space? */ - if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) != + 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) { - dev_notice(dev, - "failed to find MMIO address, giving up.\n"); - ret = -ENODEV; - goto unreg_region; + 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; } - /* 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 (!devm_request_mem_region(dev, val, SP5100_WDT_MEM_MAP_SIZE, + 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%04x already in use\n", - val); + dev_dbg(dev, "MMIO address 0x%08x already in use\n", + mmio_addr); ret = -EBUSY; goto unreg_region; } - dev_dbg(dev, "Got 0x%04x from SBResource_MMIO register\n", val); } - tco->tcobase = devm_ioremap(dev, val, SP5100_WDT_MEM_MAP_SIZE); + tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE); if (!tco->tcobase) { dev_err(dev, "failed to get tcobase address\n"); ret = -ENOMEM; goto unreg_region; } - dev_info(dev, "Using 0x%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) { @@ -332,6 +385,8 @@ static int sp5100_tco_probe(struct platform_device *pdev) 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; diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h index 008b2094bd13a..87eaf357ae01f 100644 --- a/drivers/watchdog/sp5100_tco.h +++ b/drivers/watchdog/sp5100_tco.h @@ -62,3 +62,24 @@ #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 From d040a2ee0e6292b73a328e671eb92a7c9753d18f Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 18 Jan 2018 20:52:56 +0100 Subject: [PATCH 71/74] watchdog: remove at32ap700x_wdt Since AVR32 is gone, this driver is useless. Signed-off-by: Corentin Labbe Acked-by: Hans-Christian Noren Egtvedt Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 9 - drivers/watchdog/Makefile | 3 - drivers/watchdog/at32ap700x_wdt.c | 433 ------------------------------ 3 files changed, 445 deletions(-) delete mode 100644 drivers/watchdog/at32ap700x_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 633173260f5a9..6a602f70aaa4b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -807,15 +807,6 @@ config SPRD_WATCHDOG Say Y here to include watchdog timer supported by Spreadtrum system. -# AVR32 Architecture - -config AT32AP700X_WDT - tristate "AT32AP700x watchdog" - depends on CPU_AT32AP700X || COMPILE_TEST - help - Watchdog timer embedded into AT32AP700x devices. This will reboot - your system when the timeout is reached. - # BLACKFIN Architecture config BFIN_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 4b374b9e55e89..b7123bab9f8e5 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -91,9 +91,6 @@ obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o -# AVR32 Architecture -obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o - # BLACKFIN Architecture obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o diff --git a/drivers/watchdog/at32ap700x_wdt.c b/drivers/watchdog/at32ap700x_wdt.c deleted file mode 100644 index 81ba8920f1279..0000000000000 --- 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X"); -MODULE_LICENSE("GPL"); From 71ea98ffef522e1526cd8c089eceeea6b7385b89 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 1 Feb 2018 20:44:10 +0000 Subject: [PATCH 72/74] documentation: watchdog: remove documentation of at32ap700x_wdt Since at32ap700x_wdt is gone, no need to keep its documentation Signed-off-by: Corentin Labbe Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-parameters.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 6f9d7b4189170..407d013b86432 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 From 7171b137f842d16641185e3c3daa8ebb32f67e1b Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 1 Feb 2018 20:44:11 +0000 Subject: [PATCH 73/74] documentation: watchdog: remove documentation for ixp2000 The ixp2000 watchdog driver was removed in commit 065e8238302b ("watchdog: remove ixp2000 driver") No need to keep its documentation, so remove it. Signed-off-by: Corentin Labbe Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-parameters.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 407d013b86432..603140485ac60 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt @@ -157,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 From 592a547adf686dbec7687e816ecdf9101fe227f5 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Thu, 1 Feb 2018 20:44:12 +0000 Subject: [PATCH 74/74] documentation: watchdog: remove documentation of w83697hf_wdt/w83697ug_wdt Since w83697hf_wdt/w83697ug_wdt watchdogs drivers were removed in commit 7285fae9345e ("watchdog: Remove drivers for W83697HF and W83697UG") There are no need to keep their documentation Signed-off-by: Corentin Labbe Reviewed-by: Guenter Roeck Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-parameters.txt | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 603140485ac60..beea975980f63 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt @@ -371,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