summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog')
-rw-r--r--drivers/watchdog/Kconfig92
-rw-r--r--drivers/watchdog/Makefile10
-rw-r--r--drivers/watchdog/ar9344_wdt.c35
-rw-r--r--drivers/watchdog/at91sam9_wdt.c106
-rw-r--r--drivers/watchdog/bcm2835_wdt.c48
-rw-r--r--drivers/watchdog/cadence_wdt.c278
-rw-r--r--drivers/watchdog/davinci_wdt.c11
-rw-r--r--drivers/watchdog/dw_wdt.c44
-rw-r--r--drivers/watchdog/efi_wdt.c10
-rw-r--r--drivers/watchdog/f71808e_wdt.c67
-rw-r--r--drivers/watchdog/gpio_wdt.c143
-rw-r--r--drivers/watchdog/im28wd.c29
-rw-r--r--drivers/watchdog/imxulp-wdt.c168
-rw-r--r--drivers/watchdog/imxwd.c86
-rw-r--r--drivers/watchdog/itco_wdt.c346
-rw-r--r--drivers/watchdog/jz4740.c12
-rw-r--r--drivers/watchdog/kvx_wdt.c93
-rw-r--r--drivers/watchdog/omap_wdt.c11
-rw-r--r--drivers/watchdog/orion_wdt.c15
-rw-r--r--drivers/watchdog/rave-sp-wdt.c14
-rw-r--r--drivers/watchdog/rn5t568_wdt.c147
-rw-r--r--drivers/watchdog/starfive_wdt.c107
-rw-r--r--drivers/watchdog/stm32_iwdg.c7
-rw-r--r--drivers/watchdog/stpmic1_wdt.c12
-rw-r--r--drivers/watchdog/wd_core.c127
-rw-r--r--drivers/watchdog/wdat_wdt.c496
26 files changed, 2303 insertions, 211 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 45dd41a2a2..762e37c9c2 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
config WATCHDOG_IMX_RESET_SOURCE
bool
@@ -18,19 +19,25 @@ config WATCHDOG_POLLER
config WATCHDOG_AR9344
bool "QCA AR9344"
- depends on SOC_QCA_AR9344 || SOC_QCA_AR9331
+ depends on SOC_QCA_AR9344 || SOC_QCA_AR9331 || COMPILE_TEST
help
Add support for watchdog on the QCA AR9344 SoC.
+config WATCHDOG_AT91SAM9
+ bool "Watchdog for AT91SAM9 and SAMA5 SoCs"
+ depends on ARCH_AT91
+ help
+ Support for the watchdog in AT91SAM9X and SAMA5D{2,3,4} SoCs.
+
config WATCHDOG_EFI
bool "Generic EFI Watchdog Driver"
- depends on EFI_BOOTUP
+ depends on EFI_PAYLOAD
help
Add support for the EFI watchdog.
config WATCHDOG_DAVINCI
bool "TI Davinci"
- depends on ARCH_DAVINCI
+ depends on ARCH_DAVINCI || COMPILE_TEST
help
Add support for watchdog on the TI Davinci SoC.
@@ -42,37 +49,49 @@ config WATCHDOG_DW
config WATCHDOG_MXS28
bool "i.MX28"
- depends on ARCH_IMX28
+ depends on ARCH_IMX28 || COMPILE_TEST
help
Add support for watchdog management for the i.MX28 SoC.
config WATCHDOG_IMX
bool "i.MX watchdog"
- depends on ARCH_IMX || ARCH_LAYERSCAPE
+ depends on ARCH_IMX || ARCH_LAYERSCAPE || COMPILE_TEST
+ help
+ Add support for watchdog found on Freescale i.MX SoCs.
+
+config WATCHDOG_IMXULP
+ bool "i.MX ULP watchdog"
+ depends on ARCH_IMX || COMPILE_TEST
help
Add support for watchdog found on Freescale i.MX SoCs.
config WATCHDOG_JZ4740
bool "Ingenic jz4740 SoC hardware watchdog"
- depends on MACH_MIPS_XBURST
+ depends on MACH_MIPS_XBURST || COMPILE_TEST
help
Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs.
config WATCHDOG_OMAP
bool "TI OMAP"
- depends on ARCH_OMAP
+ depends on ARCH_OMAP || COMPILE_TEST
help
Add support for watchdog on the TI OMAP SoC.
config WATCHDOG_ORION
bool "Watchdog for Armada XP"
- depends on ARCH_ARMADA_XP
+ depends on ARCH_ARMADA_XP || COMPILE_TEST
help
Add support for watchdog on the Marvall Armada XP
+config WATCHDOG_KVX
+ bool "KVX Core watchdog"
+ depends on KVX
+ help
+ Add support for the KVX core watchdog.
+
config WATCHDOG_BCM2835
bool "Watchdog for BCM283x SoCs"
- depends on ARCH_BCM283X
+ depends on ARCH_BCM283X || COMPILE_TEST
help
Add support for watchdog on the Broadcom BCM283X SoCs.
@@ -84,7 +103,7 @@ config RAVE_SP_WATCHDOG
config STM32_IWDG_WATCHDOG
bool "STM32 IWDG"
- depends on ARCH_STM32MP
+ depends on ARCH_STM32 || COMPILE_TEST
select MFD_SYSCON
help
Enable to support configuration of the STM32's on-SoC IWDG watchdog.
@@ -96,6 +115,12 @@ config STPMIC1_WATCHDOG
help
Enable to support configuration of the stpmic1's built-in watchdog.
+config RN568_WATCHDOG
+ bool "Ricoh RN5t568 PMIC based Watchdog"
+ depends on MFD_RN568PMIC
+ help
+ Enable to support system control via the PMIC based watchdog.
+
config F71808E_WDT
bool "Fintek F718xx, F818xx Super I/O Watchdog"
depends on X86
@@ -105,4 +130,51 @@ config F71808E_WDT
F71862FG, F71868, F71869, F71882FG, F71889FG, F81865 and F81866
Super I/O controllers.
+config GPIO_WATCHDOG
+ tristate "Watchdog device controlled through GPIO-line"
+ depends on OF_GPIO
+ help
+ If you say yes here you get support for watchdog device
+ controlled through GPIO-line.
+
+config ITCO_WDT
+ bool "Intel TCO Timer/Watchdog"
+ depends on X86
+ depends on PCI
+ help
+ Hardware driver for the intel TCO timer based watchdog devices.
+ These drivers are included in the Intel 82801 I/O Controller
+ Hub family (from ICH0 up to ICH10) and in the Intel 63xxESB
+ controller hub.
+
+ The TCO (Total Cost of Ownership) timer is a watchdog timer
+ that will reboot the machine after its second expiration.
+
+ On some motherboards the driver may fail to reset the chipset's
+ NO_REBOOT flag which prevents the watchdog from rebooting the
+ machine.
+
+config STARFIVE_WDT
+ tristate "StarFive Watchdog Timer"
+ depends on SOC_STARFIVE && OFDEVICE
+ help
+ If you say yes here you get support for the watchdog device
+ on StarFive SoCs.
+
+config WDAT_WDT
+ bool "ACPI Watchdog Action Table (WDAT)"
+ depends on X86
+ depends on ACPI
+ help
+ This driver adds support for systems with ACPI Watchdog Action
+ Table (WDAT) table. Servers typically have this but it can be
+ found on some desktop machines as well. This driver will take
+ over the native iTCO watchdog driver found on many Intel CPUs.
+
+config CADENCE_WATCHDOG
+ tristate "Cadence Watchdog Timer"
+ help
+ Say Y here if you want to include support for the watchdog
+ timer in the Xilinx Zynq.
+
endif
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 63efc2a87e..2b0da7cea9 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -1,5 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_WATCHDOG) += wd_core.o
obj-$(CONFIG_WATCHDOG_AR9344) += ar9344_wdt.o
+obj-$(CONFIG_WATCHDOG_AT91SAM9) += at91sam9_wdt.o
obj-$(CONFIG_WATCHDOG_EFI) += efi_wdt.o
obj-$(CONFIG_WATCHDOG_DAVINCI) += davinci_wdt.o
obj-$(CONFIG_WATCHDOG_OMAP) += omap_wdt.o
@@ -8,9 +10,17 @@ obj-$(CONFIG_WATCHDOG_DW) += dw_wdt.o
obj-$(CONFIG_WATCHDOG_JZ4740) += jz4740.o
obj-$(CONFIG_WATCHDOG_IMX_RESET_SOURCE) += imxwd.o
obj-$(CONFIG_WATCHDOG_IMX) += imxwd.o
+obj-$(CONFIG_WATCHDOG_IMXULP) += imxulp-wdt.o
+obj-$(CONFIG_WATCHDOG_KVX) += kvx_wdt.o
obj-$(CONFIG_WATCHDOG_ORION) += orion_wdt.o
obj-$(CONFIG_ARCH_BCM283X) += bcm2835_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
obj-$(CONFIG_STM32_IWDG_WATCHDOG) += stm32_iwdg.o
obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
+obj-$(CONFIG_RN568_WATCHDOG) += rn5t568_wdt.o
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
+obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o
+obj-$(CONFIG_ITCO_WDT) += itco_wdt.o
+obj-$(CONFIG_STARFIVE_WDT) += starfive_wdt.o
+obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
+obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o
diff --git a/drivers/watchdog/ar9344_wdt.c b/drivers/watchdog/ar9344_wdt.c
index 4615288631..50e83fa685 100644
--- a/drivers/watchdog/ar9344_wdt.c
+++ b/drivers/watchdog/ar9344_wdt.c
@@ -1,13 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AR9344 Watchdog driver
*
* Copyright (C) 2017 Oleksij Rempel <linux@rempel-privat.de>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
*/
#include <common.h>
@@ -34,8 +29,8 @@
struct ar9344_wd {
struct watchdog wd;
void __iomem *base;
- struct clk *clk;
- struct device_d *dev;
+ struct device *dev;
+ unsigned int rate;
};
static int ar9344_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
@@ -45,7 +40,7 @@ static int ar9344_watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
if (timeout) {
ctrl = AR9344_WD_CTRL_ACTION_FCR;
- val = timeout * clk_get_rate(priv->clk);
+ val = timeout * priv->rate;
} else {
ctrl = AR9344_WD_CTRL_ACTION_NONE;
val = U32_MAX;
@@ -70,10 +65,11 @@ static void ar9344_watchdog_detect_reset_source(struct ar9344_wd *priv)
/* else keep the default 'unknown' state */
}
-static int ar9344_wdt_probe(struct device_d *dev)
+static int ar9344_wdt_probe(struct device *dev)
{
struct resource *iores;
struct ar9344_wd *priv;
+ struct clk *clk;
int ret;
priv = xzalloc(sizeof(struct ar9344_wd));
@@ -93,16 +89,22 @@ static int ar9344_wdt_probe(struct device_d *dev)
ar9344_watchdog_detect_reset_source(priv);
- priv->clk = clk_get(dev, NULL);
- if (IS_ERR(priv->clk)) {
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
dev_err(dev, "could not get clk\n");
- ret = PTR_ERR(priv->clk);
+ ret = PTR_ERR(clk);
goto on_error;
}
- clk_enable(priv->clk);
+ clk_enable(clk);
+
+ priv->rate = clk_get_rate(clk);
+ if (priv->rate == 0) {
+ ret = -EINVAL;
+ goto on_error;
+ }
- priv->wd.timeout_max = U32_MAX / clk_get_rate(priv->clk);
+ priv->wd.timeout_max = U32_MAX / priv->rate;
ret = watchdog_register(&priv->wd);
if (ret)
@@ -122,8 +124,9 @@ static __maybe_unused struct of_device_id ar9344_wdt_dt_ids[] = {
/* sentinel */
}
};
+MODULE_DEVICE_TABLE(of, ar9344_wdt_dt_ids);
-static struct driver_d ar9344_wdt_driver = {
+static struct driver ar9344_wdt_driver = {
.name = "ar9344-wdt",
.probe = ar9344_wdt_probe,
.of_compatible = DRV_OF_COMPAT(ar9344_wdt_dt_ids),
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c
new file mode 100644
index 0000000000..46bb986229
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2019 Pengutronix, Ahmad Fatoum <a.fatoum@pengutronix.de>
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <watchdog.h>
+#include <linux/clk.h>
+#include <mach/at91/at91_wdt.h>
+
+#define MIN_WDT_TIMEOUT 1
+#define MAX_WDT_TIMEOUT 16
+#define SECS_TO_WDOG_TICKS(s) ((s) ? (((s) << 8) - 1) : 0)
+
+struct at91sam9x_wdt {
+ struct watchdog wdd;
+ void __iomem *base;
+};
+
+static inline void at91sam9x_wdt_ping(struct at91sam9x_wdt *wdt)
+{
+ writel(AT91_WDT_WDRSTT | AT91_WDT_KEY, wdt->base + AT91_WDT_CR);
+}
+
+static int at91sam9x_wdt_set_timeout(struct watchdog *wdd, unsigned timeout)
+{
+ struct at91sam9x_wdt *wdt = container_of(wdd, struct at91sam9x_wdt, wdd);
+ u32 mr_old, mr_new;
+
+ mr_old = readl(wdt->base + AT91_WDT_MR);
+
+ if (!timeout) {
+ mr_new = mr_old | AT91_WDT_WDDIS;
+ writel(mr_new, wdt->base + AT91_WDT_MR);
+ return 0;
+ }
+
+ mr_new = AT91_WDT_WDRSTEN
+ | AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT
+ | AT91_WDT_WDD
+ | (SECS_TO_WDOG_TICKS(timeout) & AT91_WDT_WDV);
+
+ if (mr_new != mr_old)
+ writel(mr_new, wdt->base + AT91_WDT_MR);
+
+ at91sam9x_wdt_ping(wdt);
+ return 0;
+}
+
+static inline bool at91sam9x_wdt_is_disabled(struct at91sam9x_wdt *wdt)
+{
+ return readl(wdt->base + AT91_WDT_MR) & AT91_WDT_WDDIS;
+}
+
+static int at91sam9x_wdt_probe(struct device *dev)
+{
+ struct at91sam9x_wdt *wdt;
+ struct resource *iores;
+ struct clk *clk;
+ int ret;
+
+ wdt = xzalloc(sizeof(*wdt));
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ dev_err(dev, "could not get watchdog memory region\n");
+ return PTR_ERR(iores);
+ }
+ wdt->base = IOMEM(iores->start);
+ clk = clk_get(dev, NULL);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ clk_enable(clk);
+
+ wdt->wdd.set_timeout = at91sam9x_wdt_set_timeout;
+ wdt->wdd.timeout_max = MAX_WDT_TIMEOUT;
+ wdt->wdd.hwdev = dev;
+
+ if (at91sam9x_wdt_is_disabled(wdt))
+ wdt->wdd.running = WDOG_HW_NOT_RUNNING;
+ else
+ wdt->wdd.running = WDOG_HW_RUNNING;
+
+ ret = watchdog_register(&wdt->wdd);
+ if (ret)
+ free(wdt);
+
+ return ret;
+}
+
+static const __maybe_unused struct of_device_id at91sam9x_wdt_dt_ids[] = {
+ { .compatible = "atmel,at91sam9260-wdt", },
+ { .compatible = "atmel,sama5d4-wdt", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, at91sam9x_wdt_dt_ids);
+
+static struct driver at91sam9x_wdt_driver = {
+ .name = "at91sam9x-wdt",
+ .of_compatible = DRV_OF_COMPAT(at91sam9x_wdt_dt_ids),
+ .probe = at91sam9x_wdt_probe,
+};
+
+device_platform_driver(at91sam9x_wdt_driver);
diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c
index 781626fa0f..874315d502 100644
--- a/drivers/watchdog/bcm2835_wdt.c
+++ b/drivers/watchdog/bcm2835_wdt.c
@@ -1,18 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017 Pengutronix, Lucas Stach <l.stach@pengutronix.de>
*
* Based on code from Carlo Caione <carlo@carlocaione.org>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * 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 <common.h>
#include <init.h>
@@ -20,28 +10,7 @@
#include <restart.h>
#include <watchdog.h>
-#define PM_RSTC 0x1c
-#define PM_RSTS 0x20
-#define PM_WDOG 0x24
-
-#define PM_WDOG_RESET 0000000000
-#define PM_PASSWORD 0x5a000000
-#define PM_WDOG_TIME_SET 0x000fffff
-#define PM_RSTC_WRCFG_CLR 0xffffffcf
-#define PM_RSTC_WRCFG_SET 0x00000030
-#define PM_RSTC_WRCFG_FULL_RESET 0x00000020
-#define PM_RSTC_RESET 0x00000102
-
-#define PM_RSTS_HADPOR_SET 0x00001000
-#define PM_RSTS_HADSRH_SET 0x00000400
-#define PM_RSTS_HADSRF_SET 0x00000200
-#define PM_RSTS_HADSRQ_SET 0x00000100
-#define PM_RSTS_HADWRH_SET 0x00000040
-#define PM_RSTS_HADWRF_SET 0x00000020
-#define PM_RSTS_HADWRQ_SET 0x00000010
-#define PM_RSTS_HADDRH_SET 0x00000004
-#define PM_RSTS_HADDRF_SET 0x00000002
-#define PM_RSTS_HADDRQ_SET 0x00000001
+#include <soc/bcm283x/wdt.h>
#define SECS_TO_WDOG_TICKS(x) ((x) << 16)
@@ -52,7 +21,7 @@
struct bcm2835_wd {
struct watchdog wd;
void __iomem *base;
- struct device_d *dev;
+ struct device *dev;
struct restart_handler restart;
};
@@ -91,7 +60,7 @@ static int bcm2835_wd_set_timeout(struct watchdog *wd, unsigned timeout)
return 0;
}
-static int bcm2835_wd_probe(struct device_d *dev)
+static int bcm2835_wd_probe(struct device *dev)
{
struct resource *iores;
struct bcm2835_wd *priv;
@@ -132,15 +101,12 @@ static __maybe_unused struct of_device_id bcm2835_wd_dt_ids[] = {
/* sentinel */
},
};
+MODULE_DEVICE_TABLE(of, bcm2835_wd_dt_ids);
-static struct driver_d bcm2835_wd_driver = {
+static struct driver bcm2835_wd_driver = {
.name = "bcm2835_wd",
.of_compatible = DRV_OF_COMPAT(bcm2835_wd_dt_ids),
.probe = bcm2835_wd_probe,
};
-static int __init bcm2835_wd_init(void)
-{
- return platform_driver_register(&bcm2835_wd_driver);
-}
-device_initcall(bcm2835_wd_init);
+device_platform_driver(bcm2835_wd_driver);
diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c
new file mode 100644
index 0000000000..17655a188c
--- /dev/null
+++ b/drivers/watchdog/cadence_wdt.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Cadence WDT driver - Used by Xilinx Zynq
+ *
+ * Copyright (C) 2010 - 2014 Xilinx, Inc.
+ *
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <restart.h>
+#include <watchdog.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/reset.h>
+
+/* Supports 1 - 516 sec */
+#define CDNS_WDT_MAX_TIMEOUT 516
+
+/* Restart key */
+#define CDNS_WDT_RESTART_KEY 0x00001999
+
+/* Counter register access key */
+#define CDNS_WDT_REGISTER_ACCESS_KEY 0x00920000
+
+/* Counter value divisor */
+#define CDNS_WDT_COUNTER_VALUE_DIVISOR 0x1000
+
+/* Clock prescaler value and selection */
+#define CDNS_WDT_PRESCALE_64 64
+#define CDNS_WDT_PRESCALE_512 512
+#define CDNS_WDT_PRESCALE_4096 4096
+#define CDNS_WDT_PRESCALE_SELECT_64 1
+#define CDNS_WDT_PRESCALE_SELECT_512 2
+#define CDNS_WDT_PRESCALE_SELECT_4096 3
+
+/* Input clock frequency */
+#define CDNS_WDT_CLK_10MHZ 10000000
+#define CDNS_WDT_CLK_75MHZ 75000000
+
+/* Counter maximum value */
+#define CDNS_WDT_COUNTER_MAX 0xFFF
+
+/**
+ * struct cdns_wdt - Watchdog device structure
+ * @regs: baseaddress of device
+ * @clk: struct clk * of a clock source
+ * @prescaler: for saving prescaler value
+ * @ctrl_clksel: counter clock prescaler selection
+ * @cdns_wdt_device: watchdog device structure
+ *
+ * Structure containing parameters specific to cadence watchdog.
+ */
+struct cdns_wdt {
+ void __iomem *regs;
+ struct clk *clk;
+ u32 prescaler;
+ u32 ctrl_clksel;
+ struct watchdog cdns_wdt_device;
+ unsigned timeout;
+};
+
+static inline struct cdns_wdt *to_cdns_wdt(struct watchdog *wdd)
+{
+ return container_of(wdd, struct cdns_wdt, cdns_wdt_device);
+}
+
+/* Write access to Registers */
+static inline void cdns_wdt_writereg(struct cdns_wdt *wdt, u32 offset, u32 val)
+{
+ writel_relaxed(val, wdt->regs + offset);
+}
+
+/*************************Register Map**************************************/
+
+/* Register Offsets for the WDT */
+#define CDNS_WDT_ZMR_OFFSET 0x0 /* Zero Mode Register */
+#define CDNS_WDT_CCR_OFFSET 0x4 /* Counter Control Register */
+#define CDNS_WDT_RESTART_OFFSET 0x8 /* Restart Register */
+#define CDNS_WDT_SR_OFFSET 0xC /* Status Register */
+
+/*
+ * Zero Mode Register - This register controls how the time out is indicated
+ * and also contains the access code to allow writes to the register (0xABC).
+ */
+#define CDNS_WDT_ZMR_WDEN_MASK 0x00000001 /* Enable the WDT */
+#define CDNS_WDT_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */
+#define CDNS_WDT_ZMR_IRQEN_MASK 0x00000004 /* Enable IRQ output */
+#define CDNS_WDT_ZMR_RSTLEN_16 0x00000030 /* Reset pulse of 16 pclk cycles */
+#define CDNS_WDT_ZMR_ZKEY_VAL 0x00ABC000 /* Access key, 0xABC << 12 */
+/*
+ * Counter Control register - This register controls how fast the timer runs
+ * and the reset value and also contains the access code to allow writes to
+ * the register.
+ */
+#define CDNS_WDT_CCR_CRV_MASK 0x00003FFC /* Counter reset value */
+
+/**
+ * cdns_wdt_stop - Stop the watchdog.
+ *
+ * @wdt: cadence watchdog device
+ *
+ * Read the contents of the ZMR register, clear the WDEN bit
+ * in the register and set the access key for successful write.
+ */
+static void cdns_wdt_stop(struct cdns_wdt *wdt)
+{
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET,
+ CDNS_WDT_ZMR_ZKEY_VAL & (~CDNS_WDT_ZMR_WDEN_MASK));
+}
+
+/**
+ * cdns_wdt_reload - Reload the watchdog timer (i.e. pat the watchdog).
+ *
+ * @wdt: cadence watchdog device
+ *
+ * Write the restart key value (0x00001999) to the restart register.
+ */
+static void cdns_wdt_reload(struct cdns_wdt *wdt)
+{
+ cdns_wdt_writereg(wdt, CDNS_WDT_RESTART_OFFSET,
+ CDNS_WDT_RESTART_KEY);
+}
+
+/**
+ * cdns_wdt_start - Enable and start the watchdog.
+ *
+ * @wdt: cadence watchdog device
+ * @timeout: new timeout
+ *
+ * The counter value is calculated according to the formula:
+ * calculated count = (timeout * clock) / prescaler + 1.
+ * The calculated count is divided by 0x1000 to obtain the field value
+ * to write to counter control register.
+ * Clears the contents of prescaler and counter reset value. Sets the
+ * prescaler to 4096 and the calculated count and access key
+ * to write to CCR Register.
+ * Sets the WDT (WDEN bit) and either the Reset signal(RSTEN bit)
+ * or Interrupt signal(IRQEN) with a specified cycles and the access
+ * key to write to ZMR Register.
+ */
+static void cdns_wdt_start(struct cdns_wdt *wdt, unsigned timeout)
+{
+ unsigned int data = 0;
+ unsigned short count;
+ unsigned long clock_f = clk_get_rate(wdt->clk);
+
+ /*
+ * Counter value divisor to obtain the value of
+ * counter reset to be written to control register.
+ */
+ count = (timeout * (clock_f / wdt->prescaler)) /
+ CDNS_WDT_COUNTER_VALUE_DIVISOR + 1;
+
+ if (count > CDNS_WDT_COUNTER_MAX)
+ count = CDNS_WDT_COUNTER_MAX;
+
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET,
+ CDNS_WDT_ZMR_ZKEY_VAL);
+
+ count = (count << 2) & CDNS_WDT_CCR_CRV_MASK;
+
+ /* Write counter access key first to be able write to register */
+ data = count | CDNS_WDT_REGISTER_ACCESS_KEY | wdt->ctrl_clksel;
+ cdns_wdt_writereg(wdt, CDNS_WDT_CCR_OFFSET, data);
+ data = CDNS_WDT_ZMR_WDEN_MASK | CDNS_WDT_ZMR_RSTLEN_16 |
+ CDNS_WDT_ZMR_ZKEY_VAL;
+
+ /* Reset on timeout regardless of what's specified in device tree. */
+ data |= CDNS_WDT_ZMR_RSTEN_MASK;
+ data &= ~CDNS_WDT_ZMR_IRQEN_MASK;
+
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET, data);
+ cdns_wdt_writereg(wdt, CDNS_WDT_RESTART_OFFSET,
+ CDNS_WDT_RESTART_KEY);
+}
+
+/**
+ * cdns_wdt_settimeout - Set a new timeout value for the watchdog device.
+ *
+ * @wdd: watchdog device
+ * @new_time: new timeout value that needs to be set
+ * Return: 0 on success
+ *
+ * Update the watchdog device timeout with new value which is used when
+ * cdns_wdt_start is called.
+ */
+static int cdns_wdt_settimeout(struct watchdog *wdd,
+ unsigned int new_time)
+{
+ struct cdns_wdt *wdt = to_cdns_wdt(wdd);
+
+ if (new_time > wdd->timeout_max)
+ return -EINVAL;
+
+ if (new_time == 0) {
+ cdns_wdt_stop(wdt);
+ } else if (wdt->timeout != new_time) {
+ cdns_wdt_start(wdt, new_time);
+ wdt->timeout = new_time;
+ } else {
+ cdns_wdt_reload(wdt);
+ }
+
+ return 0;
+}
+
+/************************Platform Operations*****************************/
+/**
+ * cdns_wdt_probe - Probe call for the device.
+ *
+ * @pdev: handle to the platform device structure.
+ * Return: 0 on success, negative error otherwise.
+ *
+ * It does all the memory allocation and registration for the device.
+ */
+static int cdns_wdt_probe(struct device *dev)
+{
+ unsigned long clock_f;
+ struct cdns_wdt *wdt;
+ struct resource *res;
+ struct watchdog *cdns_wdt_device;
+
+ wdt = xzalloc(sizeof(*wdt));
+
+ cdns_wdt_device = &wdt->cdns_wdt_device;
+ cdns_wdt_device->name = "cdns_wdt";
+ cdns_wdt_device->hwdev = dev;
+ cdns_wdt_device->set_timeout = cdns_wdt_settimeout;
+ cdns_wdt_device->timeout_max = CDNS_WDT_MAX_TIMEOUT;
+
+ res = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ wdt->regs = IOMEM(res->start);
+
+ /* We don't service interrupts in barebox, so a watchdog that doesn't
+ * reset the system isn't a useful thing to register
+ */
+ if (!of_property_read_bool(dev->of_node, "reset-on-timeout"))
+ dev_notice(dev, "proceeding as if reset-on-timeout was set\n");
+
+ wdt->clk = clk_get_enabled(dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return dev_err_probe(dev, PTR_ERR(wdt->clk),
+ "input clock not found\n");
+
+ clock_f = clk_get_rate(wdt->clk);
+ if (clock_f <= CDNS_WDT_CLK_75MHZ) {
+ wdt->prescaler = CDNS_WDT_PRESCALE_512;
+ wdt->ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_512;
+ } else {
+ wdt->prescaler = CDNS_WDT_PRESCALE_4096;
+ wdt->ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_4096;
+ }
+
+ return watchdog_register(cdns_wdt_device);
+}
+
+static const struct of_device_id cdns_wdt_of_match[] = {
+ { .compatible = "cdns,wdt-r1p2", },
+ { /* end of table */ }
+};
+MODULE_DEVICE_TABLE(of, cdns_wdt_of_match);
+
+static struct driver cdns_wdt_driver = {
+ .name = "cdns-wdt",
+ .probe = cdns_wdt_probe,
+ .of_match_table = cdns_wdt_of_match,
+};
+device_platform_driver(cdns_wdt_driver);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Watchdog driver for Cadence WDT");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c
index 2ac5f8b38d..0b2df50c48 100644
--- a/drivers/watchdog/davinci_wdt.c
+++ b/drivers/watchdog/davinci_wdt.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/char/watchdog/davinci_wdt.c
*
@@ -6,10 +7,7 @@
* Copyright (C) 2006-2013 Texas Instruments.
* Copyright (C) 2015 Jan Luebbe <jluebbe@debian.org>
*
- * 2007 (c) MontaVista Software, Inc. This file is licensed under
- * the terms of the GNU General Public License version 2. This program
- * is licensed "as is" without any warranty of any kind, whether express
- * or implied.
+ * 2007 (c) MontaVista Software, Inc.
*/
#include <common.h>
@@ -129,7 +127,7 @@ static int davinci_wdt_set_timeout(struct watchdog *wd, unsigned timeout)
return 0;
}
-static int davinci_wdt_probe(struct device_d *dev)
+static int davinci_wdt_probe(struct device *dev)
{
struct resource *iores;
int ret = 0;
@@ -162,8 +160,9 @@ static __maybe_unused struct of_device_id davinci_wdt_of_match[] = {
{ .compatible = "ti,davinci-wdt", },
{},
};
+MODULE_DEVICE_TABLE(of, davinci_wdt_of_match);
-static struct driver_d platform_wdt_driver = {
+static struct driver platform_wdt_driver = {
.name = "davinci-wdt",
.of_compatible = DRV_OF_COMPAT(davinci_wdt_of_match),
.probe = davinci_wdt_probe,
diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
index cb0d17e361..178e0a29f1 100644
--- a/drivers/watchdog/dw_wdt.c
+++ b/drivers/watchdog/dw_wdt.c
@@ -1,12 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2010-2011 Picochip Ltd., Jamie Iles
* http://www.picochip.com
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 2 of the License, or (at your option) any later version.
- *
* This file implements a driver for the Synopsys DesignWare watchdog device
* in the many subsystems. The watchdog has 16 different timeout periods
* and these are a function of the input clock frequency.
@@ -41,10 +37,10 @@
struct dw_wdt {
void __iomem *regs;
- struct clk *clk;
struct restart_handler restart;
struct watchdog wdd;
struct reset_control *rst;
+ unsigned int rate;
};
#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd)
@@ -55,7 +51,7 @@ static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top)
* There are 16 possible timeout values in 0..15 where the number of
* cycles is 2 ^ (16 + i) and the watchdog counts down.
*/
- return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk);
+ return (1U << (16 + top)) / dw_wdt->rate;
}
static int dw_wdt_start(struct watchdog *wdd)
@@ -111,6 +107,9 @@ static int dw_wdt_set_timeout(struct watchdog *wdd, unsigned int top_s)
writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+ writel(WDOG_COUNTER_RESTART_KICK_VALUE,
+ dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
+
dw_wdt_start(wdd);
return 0;
@@ -129,25 +128,27 @@ static void __noreturn dw_wdt_restart_handle(struct restart_handler *rst)
hang();
}
-static int dw_wdt_drv_probe(struct device_d *dev)
+static int dw_wdt_drv_probe(struct device *dev)
{
struct watchdog *wdd;
struct dw_wdt *dw_wdt;
struct resource *mem;
+ struct clk *clk;
int ret;
dw_wdt = xzalloc(sizeof(*dw_wdt));
mem = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
dw_wdt->regs = IOMEM(mem->start);
- if (IS_ERR(dw_wdt->regs))
- return PTR_ERR(dw_wdt->regs);
- dw_wdt->clk = clk_get(dev, NULL);
- if (IS_ERR(dw_wdt->clk))
- return PTR_ERR(dw_wdt->clk);
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
- ret = clk_enable(dw_wdt->clk);
+ ret = clk_enable(clk);
if (ret)
return ret;
@@ -155,10 +156,18 @@ static int dw_wdt_drv_probe(struct device_d *dev)
if (IS_ERR(dw_wdt->rst))
return PTR_ERR(dw_wdt->rst);
+ dw_wdt->rate = clk_get_rate(clk);
+ if (dw_wdt->rate == 0)
+ return -EINVAL;
+
wdd = &dw_wdt->wdd;
wdd->name = "dw_wdt";
wdd->hwdev = dev;
wdd->set_timeout = dw_wdt_set_timeout;
+ wdd->timeout_max = dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP);
+
+ wdd->running = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) &
+ WDOG_CONTROL_REG_WDT_EN_MASK ? WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
ret = watchdog_register(wdd);
if (ret)
@@ -174,12 +183,12 @@ static int dw_wdt_drv_probe(struct device_d *dev)
if (dw_wdt->rst)
reset_control_deassert(dw_wdt->rst);
else
- dev_warn(dev, "No reset lines. Will not be able to stop once started.\n");
+ dev_dbg(dev, "No reset lines. Will not be able to stop once started.\n");
return 0;
out_disable_clk:
- clk_disable(dw_wdt->clk);
+ clk_disable(clk);
return ret;
}
@@ -187,8 +196,9 @@ static struct of_device_id dw_wdt_of_match[] = {
{ .compatible = "snps,dw-wdt", },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
-static struct driver_d dw_wdt_driver = {
+static struct driver dw_wdt_driver = {
.name = "dw-wdt",
.probe = dw_wdt_drv_probe,
.of_compatible = DRV_OF_COMPAT(dw_wdt_of_match),
diff --git a/drivers/watchdog/efi_wdt.c b/drivers/watchdog/efi_wdt.c
index ea1ede1381..072240fcaf 100644
--- a/drivers/watchdog/efi_wdt.c
+++ b/drivers/watchdog/efi_wdt.c
@@ -7,12 +7,12 @@
#include <init.h>
#include <driver.h>
#include <efi.h>
-#include <efi/efi.h>
+#include <efi/efi-payload.h>
#include <watchdog.h>
struct efi_wdt_priv {
struct watchdog wd;
- struct device_d *dev;
+ struct device *dev;
};
#define to_efi_wdt(h) container_of(h, struct efi_wdt_priv, wd)
@@ -24,14 +24,14 @@ static int efi_wdt_set_timeout(struct watchdog *wd, unsigned timeout)
efiret = BS->set_watchdog_timer(timeout, 0, 0, NULL);
if (EFI_ERROR(efiret)) {
- dev_err(priv->dev, "filed to set EFI watchdog: %lx\n", efiret);
+ dev_err(priv->dev, "failed to set EFI watchdog: %lx\n", efiret);
return -EINVAL;
}
return 0;
}
-static int efi_wdt_probe(struct device_d *dev)
+static int efi_wdt_probe(struct device *dev)
{
struct efi_wdt_priv *priv;
int ret;
@@ -58,7 +58,7 @@ on_error:
return ret;
}
-static struct driver_d efi_wdt_driver = {
+static struct driver efi_wdt_driver = {
.name = "efi-wdt",
.probe = efi_wdt_probe,
};
diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c
index 6f2d30ec77..5bee066366 100644
--- a/drivers/watchdog/f71808e_wdt.c
+++ b/drivers/watchdog/f71808e_wdt.c
@@ -11,10 +11,9 @@
#include <init.h>
#include <asm/io.h>
-#include <linux/bitops.h>
#include <driver.h>
#include <watchdog.h>
-#include <printk.h>
+#include <linux/printk.h>
#include <reset_source.h>
#include <superio.h>
#include <common.h>
@@ -114,30 +113,50 @@ static inline void superio_exit(u16 base)
outb(SIO_LOCK_KEY, base);
}
+static inline u8 f71808e_wdt_conf_in(struct f71808e_wdt *wd)
+{
+ return superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF);
+}
+
+static inline void f71808e_wdt_conf_out(struct f71808e_wdt *wd, u8 wdt_conf)
+{
+ /*
+ * Writing 1 to WDTMOUT_STS clears it. Writing 0 keeps the old state.
+ * We want the latter, so the OS driver can check it later on.
+ */
+ wdt_conf &= ~BIT(F71808FG_FLAG_WDTMOUT_STS);
+ superio_outb(wd->sioaddr, F71808FG_REG_WDT_CONF, wdt_conf);
+}
+
static void f71808e_wdt_keepalive(struct f71808e_wdt *wd)
{
+ u8 wdt_conf;
+
superio_enter(wd->sioaddr);
superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+ wdt_conf = f71808e_wdt_conf_in(wd);
+
if (wd->minutes_mode)
/* select minutes for timer units */
- superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
- F71808FG_FLAG_WD_UNIT);
+ wdt_conf |= BIT(F71808FG_FLAG_WD_UNIT);
else
/* select seconds for timer units */
- superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
- F71808FG_FLAG_WD_UNIT);
+ wdt_conf &= ~BIT(F71808FG_FLAG_WD_UNIT);
+
+ f71808e_wdt_conf_out(wd, wdt_conf);
/* Set timer value */
- superio_outb(wd->sioaddr, F71808FG_REG_WD_TIME,
- wd->timer_val);
+ superio_outb(wd->sioaddr, F71808FG_REG_WD_TIME, wd->timer_val);
superio_exit(wd->sioaddr);
}
static void f71808e_wdt_start(struct f71808e_wdt *wd)
{
+ u8 wdt_conf;
+
/* Make sure we don't die as soon as the watchdog is enabled below */
f71808e_wdt_keepalive(wd);
@@ -158,36 +177,38 @@ static void f71808e_wdt_start(struct f71808e_wdt *wd)
superio_set_bit(wd->sioaddr, F71808FG_REG_WDO_CONF,
F71808FG_FLAG_WDOUT_EN);
- superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
- F71808FG_FLAG_WD_EN);
+ wdt_conf = f71808e_wdt_conf_in(wd);
+ wdt_conf |= BIT(F71808FG_FLAG_WD_EN);
+ f71808e_wdt_conf_out(wd, wdt_conf);
if (wd->pulse_width > 0) {
/* Select "pulse" output mode with given duration */
- u8 wdt_conf = superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF);
-
/* Set WD_PSWIDTH bits (1:0) */
wdt_conf = (wdt_conf & 0xfc) | (wd->pulse_width & 0x03);
/* Set WD_PULSE to "pulse" mode */
wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE);
- superio_outb(wd->sioaddr, F71808FG_REG_WDT_CONF, wdt_conf);
} else {
/* Select "level" output mode */
- superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
- F71808FG_FLAG_WD_PULSE);
+ wdt_conf &= ~BIT(F71808FG_FLAG_WD_PULSE);
}
+ f71808e_wdt_conf_out(wd, wdt_conf);
+
superio_exit(wd->sioaddr);
}
static void f71808e_wdt_stop(struct f71808e_wdt *wd)
{
+ u8 wdt_conf;
+
superio_enter(wd->sioaddr);
superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
- superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
- F71808FG_FLAG_WD_EN);
+ wdt_conf = f71808e_wdt_conf_in(wd);
+ wdt_conf &= ~BIT(F71808FG_FLAG_WD_EN);
+ f71808e_wdt_conf_out(wd, wdt_conf);
superio_exit(wd->sioaddr);
}
@@ -218,18 +239,18 @@ static int f71808e_wdt_set_timeout(struct watchdog *wdd, unsigned int new_timeou
return 0;
}
-static int f71808e_wdt_init(struct f71808e_wdt *wd, struct device_d *dev)
+static int f71808e_wdt_init(struct f71808e_wdt *wd, struct device *dev)
{
struct watchdog *wdd = &wd->wdd;
const char * const *names = pulse_width_names;
- unsigned long wdt_conf;
+ u8 wdt_conf;
int ret;
superio_enter(wd->sioaddr);
superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
- wdt_conf = superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF);
+ wdt_conf = f71808e_wdt_conf_in(wd);
superio_exit(wd->sioaddr);
@@ -262,7 +283,7 @@ static int f71808e_wdt_init(struct f71808e_wdt *wd, struct device_d *dev)
}
- if (test_bit(F71808FG_FLAG_WD_EN, &wdt_conf))
+ if (wdt_conf & BIT(F71808FG_FLAG_WD_EN))
wdd->running = WDOG_HW_RUNNING;
else
wdd->running = WDOG_HW_NOT_RUNNING;
@@ -355,7 +376,7 @@ static struct platform_device_id f71808e_wdt_ids[] = {
{ /* sentinel */ },
};
-static int f71808e_probe(struct device_d *dev)
+static int f71808e_probe(struct device *dev)
{
struct f71808e_wdt *wd;
struct resource *res;
@@ -375,7 +396,7 @@ static int f71808e_probe(struct device_d *dev)
return f71808e_wdt_init(wd, dev);
}
-static struct driver_d f71808e_wdt_driver = {
+static struct driver f71808e_wdt_driver = {
.probe = f71808e_probe,
.name = "f71808e_wdt",
.id_table = f71808e_wdt_ids,
diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c
new file mode 100644
index 0000000000..5fa98f87c6
--- /dev/null
+++ b/drivers/watchdog/gpio_wdt.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for watchdog device controlled through GPIO-line
+ *
+ * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <common.h>
+#include <driver.h>
+#include <watchdog.h>
+#include <superio.h>
+#include <linux/gpio/consumer.h>
+
+enum {
+ HW_ALGO_TOGGLE,
+ HW_ALGO_LEVEL,
+};
+
+struct gpio_wdt_priv {
+ struct gpio_desc *gpiod;
+ bool state;
+ bool started;
+ unsigned int hw_algo;
+ struct watchdog wdd;
+};
+
+static inline struct gpio_wdt_priv *to_gpio_wdt_priv(struct watchdog *wdd)
+{
+ return container_of(wdd, struct gpio_wdt_priv, wdd);
+}
+
+static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
+{
+ /* Eternal ping */
+ gpiod_set_value(priv->gpiod, 1);
+
+ /* Put GPIO back to tristate */
+ if (priv->hw_algo == HW_ALGO_TOGGLE)
+ gpiod_direction_input(priv->gpiod);
+
+ priv->started = false;
+}
+
+static void gpio_wdt_ping(struct gpio_wdt_priv *priv)
+{
+ switch (priv->hw_algo) {
+ case HW_ALGO_TOGGLE:
+ /* Toggle output pin */
+ priv->state = !priv->state;
+ gpiod_set_value(priv->gpiod, priv->state);
+ break;
+ case HW_ALGO_LEVEL:
+ /* Pulse */
+ gpiod_set_value(priv->gpiod, true);
+ udelay(1);
+ gpiod_set_value(priv->gpiod, false);
+ break;
+ }
+}
+
+static void gpio_wdt_start(struct gpio_wdt_priv *priv)
+{
+ priv->state = false;
+ gpiod_direction_output(priv->gpiod, priv->state);
+ priv->started = true;
+}
+
+static int gpio_wdt_set_timeout(struct watchdog *wdd, unsigned int new_timeout)
+{
+ struct gpio_wdt_priv *priv = to_gpio_wdt_priv(wdd);
+
+ if (!new_timeout) {
+ gpio_wdt_disable(priv);
+ return 0;
+ }
+
+ if (!priv->started)
+ gpio_wdt_start(priv);
+
+ gpio_wdt_ping(priv);
+ return 0;
+}
+
+static int gpio_wdt_probe(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct gpio_wdt_priv *priv;
+ enum gpiod_flags gflags;
+ unsigned int hw_margin;
+ const char *algo;
+ int ret;
+
+ priv = xzalloc(sizeof(*priv));
+
+ ret = of_property_read_u32(np, "hw_margin_ms", &hw_margin);
+ if (ret)
+ return ret;
+
+ /* Autoping is fixed at one ping every 500 ms. Round it up to a second */
+ if (hw_margin < 1000)
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "hw_algo", &algo);
+ if (ret)
+ return ret;
+ if (!strcmp(algo, "toggle")) {
+ priv->hw_algo = HW_ALGO_TOGGLE;
+ gflags = GPIOD_IN;
+ } else if (!strcmp(algo, "level")) {
+ priv->hw_algo = HW_ALGO_LEVEL;
+ gflags = GPIOD_OUT_LOW;
+ } else {
+ return -EINVAL;
+ }
+
+ priv->gpiod = gpiod_get(dev, NULL, gflags);
+ if (IS_ERR(priv->gpiod))
+ return PTR_ERR(priv->gpiod);
+
+ priv->wdd.hwdev = dev;
+ priv->wdd.timeout_max = hw_margin / 1000;
+ priv->wdd.priority = 129;
+ priv->wdd.set_timeout = gpio_wdt_set_timeout;
+
+ return watchdog_register(&priv->wdd);
+}
+
+static const struct of_device_id gpio_wdt_dt_ids[] = {
+ { .compatible = "linux,wdt-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
+
+static struct driver gpio_wdt_driver = {
+ .name = "gpio-wdt",
+ .of_compatible = gpio_wdt_dt_ids,
+ .probe = gpio_wdt_probe,
+};
+device_platform_driver(gpio_wdt_driver);
+
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("GPIO Watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/im28wd.c b/drivers/watchdog/im28wd.c
index 2b233ede20..b52e585175 100644
--- a/drivers/watchdog/im28wd.c
+++ b/drivers/watchdog/im28wd.c
@@ -1,16 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* (c) 2012 Juergen Beisert <kernel@pengutronix.de>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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.
- *
* Note: this driver works for the i.MX28 SoC. It might work for the
* i.MX23 Soc as well, but is not tested yet.
*/
@@ -184,7 +175,7 @@ static void __maybe_unused imx28_detect_reset_source(const struct imx28_wd *p)
reset_source_set(RESET_RST);
}
-static int imx28_wd_probe(struct device_d *dev)
+static int imx28_wd_probe(struct device *dev)
{
struct resource *iores;
struct imx28_wd *priv;
@@ -196,7 +187,7 @@ static int imx28_wd_probe(struct device_d *dev)
return PTR_ERR(iores);
priv->regs = IOMEM(iores->start);
priv->wd.set_timeout = imx28_watchdog_set_timeout;
- priv->wd.timeout_max = ULONG_MAX / WDOG_TICK_RATE;
+ priv->wd.timeout_max = U32_MAX / WDOG_TICK_RATE;
priv->wd.hwdev = dev;
if (!(readl(priv->regs + MXS_RTC_STAT) & MXS_RTC_STAT_WD_PRESENT)) {
@@ -222,16 +213,26 @@ on_error:
return rc;
}
-static void imx28_wd_remove(struct device_d *dev)
+static void imx28_wd_remove(struct device *dev)
{
struct imx28_wd *priv= dev->priv;
watchdog_deregister(&priv->wd);
free(priv);
}
-static struct driver_d imx28_wd_driver = {
+static __maybe_unused struct of_device_id imx28_wdt_dt_ids[] = {
+ {
+ .compatible = "fsl,stmp3xxx-rtc",
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, imx28_wdt_dt_ids);
+
+static struct driver imx28_wd_driver = {
.name = "im28wd",
.probe = imx28_wd_probe,
.remove = imx28_wd_remove,
+ .of_compatible = DRV_OF_COMPAT(imx28_wdt_dt_ids),
};
device_platform_driver(imx28_wd_driver);
diff --git a/drivers/watchdog/imxulp-wdt.c b/drivers/watchdog/imxulp-wdt.c
new file mode 100644
index 0000000000..5a89876175
--- /dev/null
+++ b/drivers/watchdog/imxulp-wdt.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <errno.h>
+#include <malloc.h>
+#include <restart.h>
+#include <watchdog.h>
+#include <reset_source.h>
+#include <linux/clk.h>
+#include <asm/system.h>
+
+struct imxulp_socdata {
+ bool prescaler_enable;
+ unsigned int rate;
+};
+
+struct imxulp_wd {
+ struct watchdog wd;
+ void __iomem *base;
+ bool prescaler_enable;
+ struct device *dev;
+ const struct imxulp_socdata *socdata;
+};
+
+#define REFRESH_WORD0 0xA602 /* 1st refresh word */
+#define REFRESH_WORD1 0xB480 /* 2nd refresh word */
+
+#define UNLOCK_WORD0 0xC520 /* 1st unlock word */
+#define UNLOCK_WORD1 0xD928 /* 2nd unlock word */
+
+#define UNLOCK_WORD 0xD928C520 /* unlock word */
+#define REFRESH_WORD 0xB480A602 /* refresh word */
+
+#define WDOG_CS 0x0
+#define WDOG_CS_UPDATE BIT(5)
+#define WDOG_CS_EN BIT(7)
+#define WDOG_CS_RCS BIT(10)
+#define WDOG_CS_ULK BIT(11)
+#define WDOG_CS_PRES BIT(12)
+#define WDOG_CS_CMD32EN BIT(13)
+#define WDOG_CS_FLG BIT(14)
+#define WDOG_CS_INT BIT(6)
+#define WDOG_CS_LPO_CLK (0x1 << 8)
+
+#define WDOG_CNT 0x4
+#define WDOG_TOVAL 0x8
+
+#define CLK_RATE_1KHZ 1000
+#define CLK_RATE_32KHZ 128
+
+static int imxulp_watchdog_set_timeout(struct watchdog *wd, unsigned int timeout)
+{
+ struct imxulp_wd *imxwd = container_of(wd, struct imxulp_wd, wd);
+ u32 cmd32 = 0;
+
+ if (timeout == 0)
+ return -ENOSYS;
+
+ if (readl(imxwd->base + WDOG_CS) & WDOG_CS_CMD32EN) {
+ writel(UNLOCK_WORD, imxwd->base + WDOG_CNT);
+ cmd32 = WDOG_CS_CMD32EN;
+ } else {
+ writel(UNLOCK_WORD0, imxwd->base + WDOG_CNT);
+ writel(UNLOCK_WORD1, imxwd->base + WDOG_CNT);
+ }
+
+ /* Wait WDOG Unlock */
+ while (!(readl(imxwd->base + WDOG_CS) & WDOG_CS_ULK))
+ ;
+
+ writel(timeout * imxwd->socdata->rate, imxwd->base + WDOG_TOVAL);
+
+ if (imxwd->socdata->prescaler_enable)
+ cmd32 |= WDOG_CS_PRES;
+
+ writel(cmd32 | WDOG_CS_EN | WDOG_CS_UPDATE | WDOG_CS_LPO_CLK |
+ WDOG_CS_FLG | WDOG_CS_INT, imxwd->base + WDOG_CS);
+
+ /* Wait WDOG reconfiguration */
+ while (!(readl(imxwd->base + WDOG_CS) & WDOG_CS_RCS))
+ ;
+
+ if (readl(imxwd->base + WDOG_CS) & WDOG_CS_CMD32EN) {
+ writel(REFRESH_WORD, imxwd->base + WDOG_CNT);
+ } else {
+ writel(REFRESH_WORD0, imxwd->base + WDOG_CNT);
+ writel(REFRESH_WORD1, imxwd->base + WDOG_CNT);
+ }
+
+ return 0;
+}
+
+static enum wdog_hw_runnning imxulp_wd_running(struct imxulp_wd *imxwd)
+{
+ if (readl(imxwd->base + WDOG_CS) & WDOG_CS_EN)
+ return WDOG_HW_RUNNING;
+ else
+ return WDOG_HW_NOT_RUNNING;
+}
+
+static int imxulp_wd_probe(struct device *dev)
+{
+ struct imxulp_wd *imxwd;
+ struct resource *iores;
+ struct imxulp_socdata *socdata;
+ int ret;
+
+ ret = dev_get_drvdata(dev, (const void **)&socdata);
+ if (ret)
+ return ret;
+
+ imxwd = xzalloc(sizeof(*imxwd));
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return dev_err_probe(dev, PTR_ERR(iores), "could not get memory region\n");
+
+ imxwd->socdata = socdata;
+ imxwd->base = IOMEM(iores->start);
+ imxwd->wd.set_timeout = imxulp_watchdog_set_timeout;
+ imxwd->wd.timeout_max = 0xffff / imxwd->socdata->rate;
+ imxwd->wd.hwdev = dev;
+ imxwd->wd.running = imxulp_wd_running(imxwd);
+
+ ret = watchdog_register(&imxwd->wd);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register watchdog device\n");
+
+ return 0;
+}
+
+static struct imxulp_socdata imx7ulp_wd_socdata = {
+ .prescaler_enable = false,
+ .rate = CLK_RATE_1KHZ,
+};
+
+static struct imxulp_socdata imx93_wd_socdata = {
+ .prescaler_enable = true,
+ .rate = CLK_RATE_32KHZ,
+};
+
+static __maybe_unused struct of_device_id imxulp_wdt_dt_ids[] = {
+ {
+ .compatible = "fsl,imx7ulp-wdt",
+ .data = &imx7ulp_wd_socdata,
+ }, {
+ .compatible = "fsl,imx8ulp-wdt",
+ .data = &imx7ulp_wd_socdata,
+ }, {
+ .compatible = "fsl,imx93-wdt",
+ .data = &imx93_wd_socdata,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, imx_wdt_dt_ids);
+
+static struct driver imxulp_wd_driver = {
+ .name = "imxulp-watchdog",
+ .probe = imxulp_wd_probe,
+ .of_compatible = DRV_OF_COMPAT(imxulp_wdt_dt_ids),
+};
+device_platform_driver(imxulp_wd_driver);
diff --git a/drivers/watchdog/imxwd.c b/drivers/watchdog/imxwd.c
index b2cfd1cd3a..8f4de5a994 100644
--- a/drivers/watchdog/imxwd.c
+++ b/drivers/watchdog/imxwd.c
@@ -1,15 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* (c) 2012 Sascha Hauer <s.hauer@pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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 <common.h>
@@ -21,6 +12,7 @@
#include <restart.h>
#include <watchdog.h>
#include <reset_source.h>
+#include <linux/clk.h>
struct imx_wd;
@@ -35,9 +27,10 @@ struct imx_wd_ops {
struct imx_wd {
struct watchdog wd;
void __iomem *base;
- struct device_d *dev;
+ struct device *dev;
const struct imx_wd_ops *ops;
struct restart_handler restart;
+ struct restart_handler restart_warm;
bool ext_reset;
bool bigendian;
};
@@ -54,6 +47,7 @@ struct imx_wd {
#define IMX21_WDOG_WSR 0x02 /* Watchdog Service Register */
#define IMX21_WDOG_WSTR 0x04 /* Watchdog Status Register */
#define IMX21_WDOG_WMCR 0x08 /* Misc Register */
+#define IMX21_WDOG_WCR_WDZST (1 << 0)
#define IMX21_WDOG_WCR_WDE (1 << 2)
#define IMX21_WDOG_WCR_WDT (1 << 3)
#define IMX21_WDOG_WCR_SRS (1 << 4)
@@ -132,6 +126,9 @@ static int imx21_watchdog_set_timeout(struct imx_wd *priv, unsigned timeout)
if (priv->ext_reset)
val |= IMX21_WDOG_WCR_WDT;
+ /* Suspend timer in low power mode */
+ val |= IMX21_WDOG_WCR_WDZST;
+
/*
* set time and some write once bits first prior enabling the
* watchdog according to the datasheet
@@ -182,6 +179,14 @@ static void __noreturn imxwd_force_soc_reset(struct restart_handler *rst)
hang();
}
+static void __noreturn imxwd_force_soc_reset_internal(struct restart_handler *rst)
+{
+ struct imx_wd *priv = container_of(rst, struct imx_wd, restart_warm);
+
+ priv->ext_reset = false;
+ imxwd_force_soc_reset(&priv->restart);
+}
+
static void imx_watchdog_detect_reset_source(struct imx_wd *priv)
{
u16 val = imxwd_read(priv, IMX21_WDOG_WSTR);
@@ -208,7 +213,7 @@ static void imx_watchdog_detect_reset_source(struct imx_wd *priv)
/* else keep the default 'unknown' state */
}
-static int imx21_wd_init(struct imx_wd *priv)
+static int imx21_wd_init_no_warm_reset(struct imx_wd *priv)
{
imx_watchdog_detect_reset_source(priv);
@@ -220,10 +225,23 @@ static int imx21_wd_init(struct imx_wd *priv)
return 0;
}
-static int imx_wd_probe(struct device_d *dev)
+static int imx21_wd_init(struct imx_wd *priv)
+{
+ priv->restart_warm.name = "imxwd-warm";
+ priv->restart_warm.restart = imxwd_force_soc_reset_internal;
+ priv->restart_warm.priority = RESTART_DEFAULT_PRIORITY - 10;
+ priv->restart_warm.flags = RESTART_FLAG_WARM_BOOTROM;
+
+ restart_handler_register(&priv->restart_warm);
+
+ return imx21_wd_init_no_warm_reset(priv);
+}
+
+static int imx_wd_probe(struct device *dev)
{
struct resource *iores;
struct imx_wd *priv;
+ struct clk *clk;
void *ops;
int ret;
@@ -233,19 +251,27 @@ static int imx_wd_probe(struct device_d *dev)
priv = xzalloc(sizeof(struct imx_wd));
iores = dev_request_mem_resource(dev, 0);
- if (IS_ERR(iores)) {
- dev_err(dev, "could not get memory region\n");
- return PTR_ERR(iores);
- }
+ if (IS_ERR(iores))
+ return dev_err_probe(dev, PTR_ERR(iores),
+ "could not get memory region\n");
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Failed to get clk\n");
+
+ ret = clk_enable(clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable clk\n");
+
priv->base = IOMEM(iores->start);
priv->ops = ops;
priv->wd.set_timeout = imx_watchdog_set_timeout;
priv->wd.timeout_max = priv->ops->timeout_max;
priv->wd.hwdev = dev;
priv->dev = dev;
- priv->bigendian = of_device_is_big_endian(dev->device_node);
+ priv->bigendian = of_device_is_big_endian(dev->of_node);
- priv->ext_reset = of_property_read_bool(dev->device_node,
+ priv->ext_reset = of_property_read_bool(dev->of_node,
"fsl,ext-reset-output");
if (IS_ENABLED(CONFIG_WATCHDOG_IMX)) {
@@ -257,14 +283,17 @@ static int imx_wd_probe(struct device_d *dev)
}
ret = watchdog_register(&priv->wd);
- if (ret)
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register watchdog device\n");
goto on_error;
+ }
}
if (priv->ops->init) {
ret = priv->ops->init(priv);
if (ret) {
- dev_err(dev, "Failed to init watchdog device %d\n", ret);
+ dev_err_probe(dev, ret,
+ "Failed to init watchdog device\n");
goto error_unregister;
}
}
@@ -273,6 +302,7 @@ static int imx_wd_probe(struct device_d *dev)
priv->restart.name = "imxwd";
priv->restart.restart = imxwd_force_soc_reset;
+ priv->restart.priority = RESTART_DEFAULT_PRIORITY;
restart_handler_register(&priv->restart);
@@ -286,6 +316,14 @@ on_error:
return ret;
}
+static const struct imx_wd_ops imx7d_wd_ops = {
+ .set_timeout = imx21_watchdog_set_timeout,
+ .soc_reset = imx21_soc_reset,
+ .init = imx21_wd_init_no_warm_reset,
+ .is_running = imx21_watchdog_is_running,
+ .timeout_max = 128,
+};
+
static const struct imx_wd_ops imx21_wd_ops = {
.set_timeout = imx21_watchdog_set_timeout,
.soc_reset = imx21_soc_reset,
@@ -308,9 +346,13 @@ static __maybe_unused struct of_device_id imx_wdt_dt_ids[] = {
.compatible = "fsl,imx21-wdt",
.data = &imx21_wd_ops,
}, {
+ .compatible = "fsl,imx7d-wdt",
+ .data = &imx7d_wd_ops,
+ }, {
/* sentinel */
}
};
+MODULE_DEVICE_TABLE(of, imx_wdt_dt_ids);
static struct platform_device_id imx_wdt_ids[] = {
{
@@ -324,7 +366,7 @@ static struct platform_device_id imx_wdt_ids[] = {
},
};
-static struct driver_d imx_wd_driver = {
+static struct driver imx_wd_driver = {
.name = "imx-watchdog",
.probe = imx_wd_probe,
.of_compatible = DRV_OF_COMPAT(imx_wdt_dt_ids),
diff --git a/drivers/watchdog/itco_wdt.c b/drivers/watchdog/itco_wdt.c
new file mode 100644
index 0000000000..ca012c92dc
--- /dev/null
+++ b/drivers/watchdog/itco_wdt.c
@@ -0,0 +1,346 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * EFI Boot Guard, iTCO support (Version 3 and later)
+ *
+ * Copyright (c) 2006-2011 Wim Van Sebroeck <wim@iguana.be>.
+ * Copyright (c) 2019 Siemens AG
+ * Copyright (c) 2019 Ahmad Fatoum, Pengutronix
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ * Christian Storm <christian.storm@siemens.com>
+ */
+
+#include <common.h>
+#include <init.h>
+#include <efi.h>
+#include <linux/pci.h>
+#include <watchdog.h>
+
+#define ACPIBASE 0x40
+#define ACPICTRL_PMCBASE 0x44
+
+#define PMBASE_ADDRMASK 0x0000ff80
+#define PMCBASE_ADDRMASK 0xfffffe00
+
+#define ACPIBASE_GCS_OFF 0x3410
+
+#define ACPIBASE_SMI_OFF 0x30
+#define ACPIBASE_SMI_END 0x33
+#define ACPIBASE_PMC_OFF 0x08
+#define ACPIBASE_PMC_END 0x0c
+#define ACPIBASE_TCO_OFF 0x60
+#define ACPIBASE_TCO_END 0x7f
+
+#define SMI_TCO_MASK (1 << 13)
+
+#define TCO_TMR_HLT_MASK (1 << 11)
+
+/* SMI Control and Enable Register */
+#define SMI_EN(itco) ((itco)->smibase)
+/* TCO base address */
+#define TCOBASE(itco) ((itco)->tcobase)
+
+#define TCO_RLD(p) (TCOBASE(p) + 0x00) /* TCO Timer Reload/Curr. Value */
+#define TCOv1_TMR(p) (TCOBASE(p) + 0x01) /* TCOv1 Timer Initial Value*/
+#define TCO_DAT_IN(p) (TCOBASE(p) + 0x02) /* TCO Data In Register */
+#define TCO_DAT_OUT(p) (TCOBASE(p) + 0x03) /* TCO Data Out Register */
+#define TCO1_STS(p) (TCOBASE(p) + 0x04) /* TCO1 Status Register */
+#define TCO2_STS(p) (TCOBASE(p) + 0x06) /* TCO2 Status Register */
+#define TCO1_CNT(p) (TCOBASE(p) + 0x08) /* TCO1 Control Register */
+#define TCO2_CNT(p) (TCOBASE(p) + 0x0a) /* TCO2 Control Register */
+#define TCOv2_TMR(p) (TCOBASE(p) + 0x12) /* TCOv2 Timer Initial Value*/
+
+#define PMC_NO_REBOOT_MASK (1 << 4)
+
+#define RCBABASE 0xf0
+
+#define PCI_ID_ITCO_INTEL_ICH9 0x2918
+
+struct itco_priv;
+
+struct itco_info {
+ u32 pci_id;
+ const char *name;
+ u32 pmbase;
+ u32 smireg;
+ int (*update_no_reboot_bit)(struct itco_priv *itco, bool set);
+ unsigned version;
+};
+
+struct itco_priv {
+ struct pci_dev *pdev;
+ struct watchdog wdd;
+ void __iomem *io;
+ u32 smibase;
+ u32 tcobase;
+ void __iomem *gcs_pmc;
+ struct itco_info *info;
+ unsigned timeout;
+};
+
+static u32 itco_get_pmbase(struct itco_priv *itco)
+{
+ u32 pmbase = itco->info->pmbase;
+
+ if (!pmbase)
+ pci_read_config_dword(itco->pdev, ACPIBASE, &pmbase);
+
+ return pmbase & PMBASE_ADDRMASK;
+}
+
+static inline struct itco_priv *to_itco_priv(struct watchdog *wdd)
+{
+ return container_of(wdd, struct itco_priv, wdd);
+}
+
+static void itco_wdt_ping(struct itco_priv *itco)
+{
+ /* Reload the timer by writing to the TCO Timer Counter register */
+ outw(0x0001, TCO_RLD(itco));
+}
+
+static inline unsigned int seconds_to_ticks(struct itco_priv *itco, int secs)
+{
+ return itco->info->version == 3 ? secs : (secs * 10) / 6;
+}
+
+static inline unsigned int ticks_to_seconds(struct itco_priv *itco, int ticks)
+{
+ return itco->info->version == 3 ? ticks : (ticks * 6) / 10;
+}
+
+
+static int itco_wdt_start(struct itco_priv *itco, unsigned int timeout)
+{
+ unsigned tmrval;
+ u32 value;
+ int ret;
+
+ tmrval = seconds_to_ticks(itco, timeout);
+
+ /* Enable TCO SMIs */
+ value = inl(SMI_EN(itco)) | SMI_TCO_MASK;
+ outl(value, SMI_EN(itco));
+
+ /* Set timer value */
+ value = inw(TCOv2_TMR(itco));
+
+ value &= 0xfc00;
+ value |= tmrval & 0x3ff;
+
+ outw(value, TCOv2_TMR(itco));
+ value = inw(TCOv2_TMR(itco));
+
+ if ((value & 0x3ff) != tmrval)
+ return -EINVAL;
+
+ /* Force reloading of timer value */
+ outw(1, TCO_RLD(itco));
+
+ /* Clear NO_REBOOT flag */
+ ret = itco->info->update_no_reboot_bit(itco, false);
+ if (ret)
+ return ret;
+
+ /* Clear HLT flag to start timer */
+ value = inw(TCO1_CNT(itco)) & ~TCO_TMR_HLT_MASK;
+ outw(value, TCO1_CNT(itco));
+ value = inw(TCO1_CNT(itco));
+
+ if (value & 0x0800)
+ return -EIO;
+
+ return 0;
+}
+
+static int itco_wdt_stop(struct itco_priv *itco)
+{
+ u32 val;
+
+ /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
+ val = inw(TCO1_CNT(itco)) | 0x0800;
+ outw(val, TCO1_CNT(itco));
+ val = inb(TCO1_CNT(itco));
+
+ /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+ itco->info->update_no_reboot_bit(itco, true);
+
+ if ((val & 0x0800) == 0)
+ return -EIO;
+ return 0;
+}
+
+static int itco_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout)
+{
+ struct itco_priv *itco = to_itco_priv(wdd);
+ int ret;
+
+ if (!timeout)
+ return itco_wdt_stop(itco);
+
+ /* from the specs: */
+ /* "Values of 0h-3h are ignored and should not be attempted" */
+ if (timeout < 0x04)
+ return -EINVAL;
+
+ if (itco->timeout != timeout) {
+ ret = itco_wdt_start(itco, timeout);
+ if (ret) {
+ dev_err(wdd->hwdev, "Fail to (re)start watchdog\n");
+ return ret;
+ }
+ }
+
+ itco_wdt_ping(itco);
+ return 0;
+}
+
+static inline u32 no_reboot_bit(unsigned version)
+{
+ u32 enable_bit;
+
+ switch (version) {
+ case 5:
+ case 3:
+ enable_bit = 0x00000010;
+ break;
+ case 2:
+ enable_bit = 0x00000020;
+ break;
+ case 4:
+ case 1:
+ default:
+ enable_bit = 0x00000002;
+ break;
+ }
+
+ return enable_bit;
+}
+
+
+static int update_no_reboot_bit(struct itco_priv *itco, bool set)
+{
+ u32 val32 = 0, newval32 = 0;
+
+ val32 = readl(itco->gcs_pmc);
+ if (set)
+ val32 |= no_reboot_bit(itco->info->version);
+ else
+ val32 &= ~no_reboot_bit(itco->info->version);
+ writel(val32, itco->gcs_pmc);
+ newval32 = readl(itco->gcs_pmc);
+
+ /* make sure the update is successful */
+ if (val32 != newval32)
+ return -EPERM;
+
+ return 0;
+}
+
+static void lpc_ich_enable_acpi_space(struct itco_priv *itco)
+{
+ u8 reg_save;
+
+ pci_read_config_byte(itco->pdev, ACPICTRL_PMCBASE, &reg_save);
+ pci_write_config_byte(itco->pdev, ACPICTRL_PMCBASE, reg_save | 0x80);
+}
+
+enum itco_chipsets {
+ ITCO_INTEL_ICH9,
+};
+
+/* version 1 not supported! */
+static struct itco_info itco_chipset_info[] = {
+ [ITCO_INTEL_ICH9] = {
+ .pci_id = PCI_ID_ITCO_INTEL_ICH9,
+ .name = "ICH9", /* QEmu machine q35 */
+ .smireg = 0x30,
+ .update_no_reboot_bit = update_no_reboot_bit,
+ .version = 2,
+ },
+};
+
+static int itco_wdt_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct itco_priv *itco;
+ struct watchdog *wdd;
+ u32 rcba_base_cfg;
+ u32 pmbase;
+ int ret;
+ int i;
+
+ pci_enable_device(pdev);
+ pci_set_master(pdev);
+
+ itco = xzalloc(sizeof(*itco));
+
+ itco->pdev = pdev;
+
+ for (i = 0; i < ARRAY_SIZE(itco_chipset_info); i++) {
+ if (id->device == itco_chipset_info[i].pci_id) {
+ itco->info = &itco_chipset_info[i];
+ break;
+ }
+ }
+
+ if (!itco->info)
+ return -ENODEV;
+
+
+ pci_read_config_dword(itco->pdev, RCBABASE, &rcba_base_cfg);
+ if (!(rcba_base_cfg & 1)) {
+ dev_notice(&pdev->dev, "RCBA is disabled by hardware/BIOS, device disabled\n");
+ return -ENODEV;
+ }
+
+ pmbase = itco_get_pmbase(itco);
+ if (!pmbase) {
+ dev_notice(&itco->pdev->dev, "I/O space for ACPI uninitialized\n");
+ return -ENODEV;
+ }
+
+ itco->smibase = pmbase + ACPIBASE_SMI_OFF;
+ itco->tcobase = pmbase + ACPIBASE_TCO_OFF;
+
+ lpc_ich_enable_acpi_space(itco);
+
+ itco->gcs_pmc = IOMEM(rcba_base_cfg & 0xffffc000UL) + ACPIBASE_GCS_OFF;
+
+
+ dev_notice(&pdev->dev, "gcs_pmc = 0x%p, smibase = 0x%x, tcobase = 0x%x\n",
+ itco->gcs_pmc, itco->smibase, itco->tcobase);
+
+ wdd = &itco->wdd;
+ wdd->hwdev = &pdev->dev;
+ wdd->set_timeout = itco_wdt_set_timeout;
+
+ wdd->timeout_max = ticks_to_seconds(itco, 0x3ff);
+
+ outw(0x0008, TCO1_STS(itco)); /* Clear the Time Out Status bit */
+ outw(0x0002, TCO2_STS(itco)); /* Clear SECOND_TO_STS bit */
+ outw(0x0004, TCO2_STS(itco)); /* Clear BOOT_STS bit */
+
+ ret = watchdog_register(wdd);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register watchdog device\n");
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "probed Intel TCO %s watchdog\n", itco->info->name);
+
+ return 0;
+}
+
+
+static DEFINE_PCI_DEVICE_TABLE(itco_wdt_pci_tbl) = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_ID_ITCO_INTEL_ICH9) },
+ { /* sentinel */ },
+};
+
+static struct pci_driver itco_wdt_driver = {
+ .name = "itco_wdt",
+ .id_table = itco_wdt_pci_tbl,
+ .probe = itco_wdt_probe,
+};
+device_pci_driver(itco_wdt_driver);
diff --git a/drivers/watchdog/jz4740.c b/drivers/watchdog/jz4740.c
index f28bb9177a..8b4b985cd4 100644
--- a/drivers/watchdog/jz4740.c
+++ b/drivers/watchdog/jz4740.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* JZ4740 Watchdog driver
*
@@ -6,12 +7,6 @@
* Based on jz4740_wdt.c from linux-3.15.
*
* Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
*/
#include <common.h>
@@ -65,7 +60,7 @@ static void __noreturn jz4740_reset_soc(struct restart_handler *rst)
hang();
}
-static int jz4740_wdt_probe(struct device_d *dev)
+static int jz4740_wdt_probe(struct device *dev)
{
struct resource *iores;
struct jz4740_wdt_drvdata *priv;
@@ -94,8 +89,9 @@ static __maybe_unused struct of_device_id jz4740_wdt_dt_ids[] = {
/* sentinel */
}
};
+MODULE_DEVICE_TABLE(of, jz4740_wdt_dt_ids);
-static struct driver_d jz4740_wdt_driver = {
+static struct driver jz4740_wdt_driver = {
.name = "jz4740-wdt",
.probe = jz4740_wdt_probe,
.of_compatible = DRV_OF_COMPAT(jz4740_wdt_dt_ids),
diff --git a/drivers/watchdog/kvx_wdt.c b/drivers/watchdog/kvx_wdt.c
new file mode 100644
index 0000000000..be6b08b71c
--- /dev/null
+++ b/drivers/watchdog/kvx_wdt.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Kalray Inc.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <watchdog.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <asm/sfr.h>
+
+struct kvx_wdt {
+ uint64_t clk_rate;
+ struct watchdog wdd;
+};
+
+static void kvx_watchdog_disable(void)
+{
+ kvx_sfr_set_field(TCR, WUI, 0);
+ kvx_sfr_set_field(TCR, WCE, 0);
+}
+
+static int kvx_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout)
+{
+ struct kvx_wdt *wdt = container_of(wdd, struct kvx_wdt, wdd);
+ uint64_t cycle_timeout = wdt->clk_rate * timeout;
+
+ /* Disable watchdog */
+ if (timeout == 0) {
+ kvx_watchdog_disable();
+ return 0;
+ }
+
+ kvx_sfr_set(WDV, cycle_timeout);
+ kvx_sfr_set(WDR, 0);
+
+ /* Start watchdog counting */
+ kvx_sfr_set_field(TCR, WUI, 1);
+ kvx_sfr_set_field(TCR, WCE, 1);
+
+ return 0;
+}
+
+static int count;
+
+static int kvx_wdt_drv_probe(struct device *dev)
+{
+ struct watchdog *wdd;
+ struct clk *clk;
+ struct kvx_wdt *kvx_wdt;
+
+ if (count != 0) {
+ dev_warn(dev, "Tried to register core watchdog twice\n");
+ return -EINVAL;
+ }
+ count++;
+
+ kvx_wdt = xzalloc(sizeof(*kvx_wdt));
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ kvx_wdt->clk_rate = clk_get_rate(clk);
+ clk_put(clk);
+
+ wdd = &kvx_wdt->wdd;
+ wdd->name = "kvx_wdt";
+ wdd->hwdev = dev;
+ wdd->set_timeout = kvx_wdt_set_timeout;
+
+ /* Be sure that interrupt are disabled */
+ kvx_sfr_set_field(TCR, WIE, 0);
+
+ return watchdog_register(wdd);
+}
+
+static struct of_device_id kvx_wdt_of_match[] = {
+ { .compatible = "kalray,kvx-core-watchdog", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, kvx_wdt_of_match);
+
+static struct driver kvx_wdt_driver = {
+ .name = "kvx-wdt",
+ .probe = kvx_wdt_drv_probe,
+ .of_compatible = DRV_OF_COMPAT(kvx_wdt_of_match),
+};
+device_platform_driver(kvx_wdt_driver);
diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c
index c8a7ccf607..0ebc1172aa 100644
--- a/drivers/watchdog/omap_wdt.c
+++ b/drivers/watchdog/omap_wdt.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* omap_wdt.c
*
@@ -11,10 +12,7 @@
* Author: MontaVista Software, Inc.
* <gdavis@mvista.com> or <source@mvista.com>
*
- * 2003 (c) MontaVista Software, Inc. This file is licensed under the
- * terms of the GNU General Public License version 2. This program is
- * licensed "as is" without any warranty of any kind, whether express
- * or implied.
+ * 2003 (c) MontaVista Software, Inc.
*
* History:
*
@@ -152,7 +150,7 @@ static int omap_wdt_set_timeout(struct watchdog *wdog,
return 0;
}
-static int omap_wdt_probe(struct device_d *dev)
+static int omap_wdt_probe(struct device *dev)
{
struct resource *iores;
struct omap_wdt_dev *wdev;
@@ -193,8 +191,9 @@ static const struct of_device_id omap_wdt_of_match[] = {
{ .compatible = "ti,omap3-wdt", },
{},
};
+MODULE_DEVICE_TABLE(of, omap_wdt_of_match);
-static struct driver_d omap_wdt_driver = {
+static struct driver omap_wdt_driver = {
.probe = omap_wdt_probe,
.name = "omap_wdt",
.of_compatible = DRV_OF_COMPAT(omap_wdt_of_match),
diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c
index dd1fa3a04d..227f8b7bb1 100644
--- a/drivers/watchdog/orion_wdt.c
+++ b/drivers/watchdog/orion_wdt.c
@@ -1,16 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Watchdog driver for Marvell Armada XP.
*
* Copyright (C) 2017 Pengutronix, Uwe Kleine-König <kernel@pengutronix.de>
- *
- * 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 <common.h>
@@ -76,7 +68,7 @@ static int armada_xp_set_timeout(struct watchdog *wd, unsigned timeout)
return 0;
}
-static int orion_wdt_probe(struct device_d *dev)
+static int orion_wdt_probe(struct device *dev)
{
struct orion_wdt_ddata* ddata;
struct resource *res_timer, *res_rstout;
@@ -112,8 +104,9 @@ static const struct of_device_id orion_wdt_of_match[] = {
.compatible = "marvell,armada-xp-wdt",
}, { /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, orion_wdt_of_match);
-static struct driver_d orion_wdt_driver = {
+static struct driver orion_wdt_driver = {
.probe = orion_wdt_probe,
.name = "orion_wdt",
.of_compatible = DRV_OF_COMPAT(orion_wdt_of_match),
diff --git a/drivers/watchdog/rave-sp-wdt.c b/drivers/watchdog/rave-sp-wdt.c
index dc673ee35f..b4fc18cb8b 100644
--- a/drivers/watchdog/rave-sp-wdt.c
+++ b/drivers/watchdog/rave-sp-wdt.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0+
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for watchdog aspect of for Zodiac Inflight Innovations RAVE
@@ -252,6 +252,7 @@ static const struct of_device_id rave_sp_wdt_of_match[] = {
},
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, rave_sp_wdt_of_match);
static int rave_sp_wdt_set_boot_source(struct param_d *param, void *priv)
{
@@ -286,7 +287,7 @@ static int rave_sp_wdt_get_boot_source(struct param_d *param, void *priv)
static int rave_sp_wdt_add_params(struct rave_sp_wdt *sp_wd)
{
struct watchdog *wdd = &sp_wd->wdd;
- struct device_node *np = wdd->hwdev->device_node;
+ struct device_node *np = wdd->hwdev->of_node;
struct param_d *p;
sp_wd->boot_source_cell = of_nvmem_cell_get(np, "boot-source");
@@ -299,13 +300,10 @@ static int rave_sp_wdt_add_params(struct rave_sp_wdt *sp_wd)
rave_sp_wdt_set_boot_source,
rave_sp_wdt_get_boot_source,
&sp_wd->boot_source, "%u", sp_wd);
- if (IS_ERR(p))
- return PTR_ERR(p);
-
- return 0;
+ return PTR_ERR_OR_ZERO(p);
}
-static int rave_sp_wdt_probe(struct device_d *dev)
+static int rave_sp_wdt_probe(struct device *dev)
{
struct rave_sp_wdt *sp_wd;
const char *reset_reason;
@@ -418,7 +416,7 @@ static int rave_sp_wdt_probe(struct device_d *dev)
return 0;
}
-static struct driver_d rave_sp_wdt_driver = {
+static struct driver rave_sp_wdt_driver = {
.name = "rave-sp-wdt",
.probe = rave_sp_wdt_probe,
.of_compatible = DRV_OF_COMPAT(rave_sp_wdt_of_match),
diff --git a/drivers/watchdog/rn5t568_wdt.c b/drivers/watchdog/rn5t568_wdt.c
new file mode 100644
index 0000000000..2011e3e01c
--- /dev/null
+++ b/drivers/watchdog/rn5t568_wdt.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Watchdog driver for Ricoh RN5T618 PMIC
+ *
+ * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
+ */
+
+#include <common.h>
+#include <init.h>
+#include <watchdog.h>
+#include <linux/regmap.h>
+#include <of.h>
+
+#define RN5T568_WATCHDOG 0x0b
+# define RN5T568_WATCHDOG_WDPWROFFEN BIT(2)
+# define RN5T568_WATCHDOG_WDOGTIM_M (BIT(0) | BIT(1))
+#define RN5T568_PWRIREN 0x12
+# define RN5T568_PWRIREN_EN_WDOG BIT(6)
+#define RN5T568_PWRIRQ 0x13
+# define RN5T568_PWRIRQ_IR_WDOG BIT(6)
+
+struct rn5t568_wdt {
+ struct watchdog wdd;
+ struct regmap *regmap;
+ unsigned int timeout;
+};
+
+struct rn5t568_wdt_tim {
+ u8 reg_val;
+ u8 time;
+};
+
+static const struct rn5t568_wdt_tim rn5t568_wdt_timeout[] = {
+ { .reg_val = 0, .time = 1, },
+ { .reg_val = 1, .time = 8, },
+ { .reg_val = 2, .time = 32, },
+ { .reg_val = 3, .time = 128, },
+};
+
+#define PMIC_WDT_MAX_TIMEOUT 128
+
+static int rn5t568_wdt_start(struct regmap *regmap, int idx)
+{
+ int ret;
+
+ ret = regmap_update_bits(regmap, RN5T568_WATCHDOG, RN5T568_WATCHDOG_WDOGTIM_M,
+ rn5t568_wdt_timeout[idx].reg_val);
+ if (ret)
+ return ret;
+
+ regmap_clear_bits(regmap, RN5T568_PWRIRQ, RN5T568_PWRIRQ_IR_WDOG);
+ regmap_set_bits(regmap, RN5T568_PWRIREN, RN5T568_PWRIREN_EN_WDOG);
+
+ pr_debug("RN5t: Starting the watchdog with %u seconds\n", rn5t568_wdt_timeout[idx].time);
+
+ return regmap_set_bits(regmap, RN5T568_WATCHDOG, RN5T568_WATCHDOG_WDPWROFFEN);
+}
+
+static int rn5t568_wdt_stop(struct regmap *regmap)
+{
+ int ret;
+
+ ret = regmap_clear_bits(regmap, RN5T568_PWRIREN, RN5T568_PWRIREN_EN_WDOG);
+ if (ret)
+ return ret;
+
+ return regmap_clear_bits(regmap, RN5T568_WATCHDOG, RN5T568_WATCHDOG_WDPWROFFEN);
+}
+
+static int rn5t568_wdt_ping(struct regmap *regmap)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(regmap, RN5T568_WATCHDOG, &val);
+ if (ret)
+ return ret;
+
+ return regmap_write(regmap, RN5T568_WATCHDOG, val);
+}
+
+static int rn5t568_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout)
+{
+ struct rn5t568_wdt *wdt = container_of(wdd, struct rn5t568_wdt, wdd);
+ int ret, i;
+
+ if (!timeout)
+ return rn5t568_wdt_stop(wdt->regmap);
+
+ for (i = 0; i < ARRAY_SIZE(rn5t568_wdt_timeout); i++) {
+ if (timeout < rn5t568_wdt_timeout[i].time)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(rn5t568_wdt_timeout))
+ return -EINVAL;
+
+ if (wdt->timeout == timeout)
+ return rn5t568_wdt_ping(wdt->regmap);
+
+ ret = rn5t568_wdt_start(wdt->regmap, i);
+ if (ret)
+ return ret;
+
+ wdt->timeout = rn5t568_wdt_timeout[i].time;
+
+ return ret;
+}
+
+static int rn5t568_wdt_probe(struct device *dev)
+{
+ struct rn5t568_wdt *wdt;
+ struct watchdog *wdd;
+ unsigned int val;
+ int ret;
+
+ wdt = xzalloc(sizeof(*wdt));
+
+ wdt->regmap = dev_get_regmap(dev->parent, NULL);
+ if (IS_ERR(wdt->regmap))
+ return PTR_ERR(wdt->regmap);
+
+ wdd = &wdt->wdd;
+ wdd->hwdev = dev;
+ wdd->set_timeout = rn5t568_wdt_set_timeout;
+ wdd->timeout_max = PMIC_WDT_MAX_TIMEOUT;
+
+ ret = regmap_read(wdt->regmap, RN5T568_WATCHDOG, &val);
+ if (ret == 0)
+ wdd->running = val & RN5T568_WATCHDOG_WDPWROFFEN ?
+ WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
+
+ return watchdog_register(wdd);
+}
+
+static __maybe_unused const struct of_device_id rn5t568_wdt_of_match[] = {
+ { .compatible = "ricoh,rn5t568-wdt" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rn5t568_wdt_of_match);
+
+static struct driver rn5t568_wdt_driver = {
+ .name = "rn5t568-wdt",
+ .probe = rn5t568_wdt_probe,
+ .of_compatible = DRV_OF_COMPAT(rn5t568_wdt_of_match),
+};
+device_platform_driver(rn5t568_wdt_driver);
diff --git a/drivers/watchdog/starfive_wdt.c b/drivers/watchdog/starfive_wdt.c
new file mode 100644
index 0000000000..90f1e0ae6b
--- /dev/null
+++ b/drivers/watchdog/starfive_wdt.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2018 Kalray Inc.
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <watchdog.h>
+#include <linux/reset.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#define WDT_REG_RESET_EN 0x104
+#define WDT_REG_TIMEOUT 0x108
+#define WDT_REG_CONTROL 0x110
+#define WDT_REG_UNLOCK 0x13c
+
+#define WDT_UNLOCK_KEY 0x378f0765
+
+#define WDT_TICKS_PER_SEC 50000000
+
+struct starfive_wdt {
+ u32 clk_rate;
+ struct watchdog wdd;
+ void __iomem *base;
+ bool setup;
+};
+
+static int starfive_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout)
+{
+ struct starfive_wdt *wd = container_of(wdd, struct starfive_wdt, wdd);
+
+ writel(0, wd->base + WDT_REG_CONTROL);
+
+ if (timeout > 0) {
+ timeout *= wd->clk_rate;
+ writel(timeout, wd->base + WDT_REG_TIMEOUT);
+ writel(1, wd->base + WDT_REG_CONTROL);
+ }
+
+ return 0;
+}
+
+static int starfive_wdt_drv_probe(struct device *dev)
+{
+ struct starfive_wdt *wd;
+ struct resource *iores;
+ struct watchdog *wdd;
+ struct clk_bulk_data clks[] = {
+ { .id = "bus" },
+ { .id = "core" },
+ };
+ int ret;
+
+ ret = clk_bulk_get(dev, ARRAY_SIZE(clks), clks);
+ if (ret)
+ return ret;
+
+ ret = clk_bulk_enable(ARRAY_SIZE(clks), clks);
+ if (ret < 0)
+ return ret;
+
+ ret = device_reset_all(dev);
+ if (ret)
+ return ret;
+
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores))
+ return PTR_ERR(iores);
+
+ wd = xzalloc(sizeof(*wd));
+ wd->base = IOMEM(iores->start);
+
+ wd->clk_rate = WDT_TICKS_PER_SEC;
+
+ writel(WDT_UNLOCK_KEY, wd->base + WDT_REG_UNLOCK);
+ wd->base = IOMEM(iores->start);
+ /* reset, not interrupt, on timer expiry */
+ writel(1, wd->base + WDT_REG_RESET_EN);
+
+ wdd = &wd->wdd;
+ wdd->name = "starfive_wdt";
+ wdd->hwdev = dev;
+ wdd->set_timeout = starfive_wdt_set_timeout;
+ wdd->timeout_max = U32_MAX / wd->clk_rate;
+
+ wdd->running = readl(wd->base + WDT_REG_CONTROL) & 1 ?
+ WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
+
+ return watchdog_register(wdd);
+}
+
+static struct of_device_id starfive_wdt_of_match[] = {
+ { .compatible = "starfive,wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, starfive_wdt_of_match);
+
+static struct driver starfive_wdt_driver = {
+ .name = "starfive-wdt",
+ .probe = starfive_wdt_drv_probe,
+ .of_compatible = starfive_wdt_of_match,
+};
+device_platform_driver(starfive_wdt_driver);
diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c
index 9e38f1a669..6ac9e7d56e 100644
--- a/drivers/watchdog/stm32_iwdg.c
+++ b/drivers/watchdog/stm32_iwdg.c
@@ -125,8 +125,9 @@ static const struct of_device_id stm32_iwdg_of_match[] = {
{ .compatible = "st,stm32mp1-iwdg", .data = &stm32mp1_iwdg_data },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);
-static int stm32_iwdg_probe(struct device_d *dev)
+static int stm32_iwdg_probe(struct device *dev)
{
struct stm32_iwdg_data *data;
struct stm32_iwdg *wd;
@@ -157,6 +158,8 @@ static int stm32_iwdg_probe(struct device_d *dev)
return ret;
wd->rate = clk_get_rate(clk);
+ if (wd->rate == 0)
+ return -EINVAL;
if (data->has_pclk) {
clk = clk_get(dev, "pclk");
@@ -185,7 +188,7 @@ static int stm32_iwdg_probe(struct device_d *dev)
return 0;
}
-static struct driver_d stm32_iwdg_driver = {
+static struct driver stm32_iwdg_driver = {
.name = "stm32-iwdg",
.probe = stm32_iwdg_probe,
.of_compatible = DRV_OF_COMPAT(stm32_iwdg_of_match),
diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
index 458c5c16a3..4a0519690a 100644
--- a/drivers/watchdog/stpmic1_wdt.c
+++ b/drivers/watchdog/stpmic1_wdt.c
@@ -11,6 +11,7 @@
#include <linux/iopoll.h>
#include <poweroff.h>
#include <mfd/syscon.h>
+#include <linux/regmap.h>
#include <restart.h>
#include <reset_source.h>
@@ -157,7 +158,7 @@ static int stpmic1_set_reset_reason(struct regmap *map)
return 0;
}
-static int stpmic1_wdt_probe(struct device_d *dev)
+static int stpmic1_wdt_probe(struct device *dev)
{
struct stpmic1_wdt *wdt;
struct watchdog *wdd;
@@ -174,10 +175,6 @@ static int stpmic1_wdt_probe(struct device_d *dev)
wdd->set_timeout = stpmic1_wdt_set_timeout;
wdd->timeout_max = PMIC_WDT_MAX_TIMEOUT;
- /* have the watchdog reset, not power-off the system */
- regmap_write_bits(wdt->regmap, SWOFF_PWRCTRL_CR,
- RESTART_REQUEST_ENABLED, RESTART_REQUEST_ENABLED);
-
ret = watchdog_register(wdd);
if (ret) {
dev_err(dev, "Failed to register watchdog device\n");
@@ -200,7 +197,7 @@ static int stpmic1_wdt_probe(struct device_d *dev)
if (ret)
dev_warn(dev, "Cannot register poweroff handler\n");
- stpmic1_set_reset_reason(wdt->regmap);
+ ret = stpmic1_set_reset_reason(wdt->regmap);
if (ret)
dev_warn(dev, "Cannot query reset reason\n");
@@ -212,8 +209,9 @@ static __maybe_unused const struct of_device_id stpmic1_wdt_of_match[] = {
{ .compatible = "st,stpmic1-wdt" },
{ /* sentinel */ }
};
+MODULE_DEVICE_TABLE(of, stpmic1_wdt_of_match);
-static struct driver_d stpmic1_wdt_driver = {
+static struct driver stpmic1_wdt_driver = {
.name = "stpmic1-wdt",
.probe = stpmic1_wdt_probe,
.of_compatible = DRV_OF_COMPAT(stpmic1_wdt_of_match),
diff --git a/drivers/watchdog/wd_core.c b/drivers/watchdog/wd_core.c
index 34040408f7..f39a8f4522 100644
--- a/drivers/watchdog/wd_core.c
+++ b/drivers/watchdog/wd_core.c
@@ -1,15 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* (c) 2012 Juergen Beisert <kernel@pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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.
*/
#define pr_fmt(fmt) "watchdog: " fmt
@@ -18,6 +9,7 @@
#include <errno.h>
#include <linux/ctype.h>
#include <watchdog.h>
+#include <restart.h>
static LIST_HEAD(watchdog_list);
@@ -45,12 +37,18 @@ int watchdog_set_timeout(struct watchdog *wd, unsigned timeout)
if (timeout > wd->timeout_max)
return -EINVAL;
+ if (!timeout && !watchdog_hw_running(wd))
+ return 0;
+
pr_debug("setting timeout on %s to %ds\n", watchdog_name(wd), timeout);
ret = wd->set_timeout(wd, timeout);
if (ret)
return ret;
+ wd->last_ping = get_time_ns();
+ wd->timeout_cur = timeout;
+
wd->running = timeout ? WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
return 0;
@@ -116,7 +114,7 @@ static int watchdog_register_poller(struct watchdog *wd)
struct param_d *p;
int ret;
- ret = poller_async_register(&wd->poller);
+ ret = poller_async_register(&wd->poller, dev_name(&wd->dev));
if (ret)
return ret;
@@ -141,17 +139,56 @@ static int watchdog_register_dev(struct watchdog *wd, const char *name, int id)
*
* return: The priority
*/
-static unsigned int dev_get_watchdog_priority(struct device_d *dev)
+static unsigned int dev_get_watchdog_priority(struct device *dev)
{
unsigned int priority = WATCHDOG_DEFAULT_PRIORITY;
if (dev)
- of_property_read_u32(dev->device_node, "watchdog-priority",
+ of_property_read_u32(dev->of_node, "watchdog-priority",
&priority);
return priority;
}
+static int seconds_to_expire_get(struct param_d *p, void *priv)
+{
+ struct watchdog *wd = priv;
+ uint64_t diff;
+
+ if (!wd->timeout_cur) {
+ wd->seconds_to_expire = -1;
+ return 0;
+ }
+
+ diff = get_time_ns() - wd->last_ping;
+
+ do_div(diff, 1000000000);
+
+ wd->seconds_to_expire = wd->timeout_cur - diff;
+
+ return 0;
+}
+
+static void __noreturn watchdog_restart_handle(struct restart_handler *this)
+{
+ struct watchdog *wd = watchdog_get_default();
+ int ret = -ENODEV;
+
+ if (wd)
+ ret = watchdog_set_timeout(wd, 1);
+
+ BUG_ON(ret);
+ mdelay(2000);
+
+ pr_emerg("Watchdog failed to reset the machine\n");
+ hang();
+}
+
+static struct restart_handler restart_handler = {
+ .restart = watchdog_restart_handle,
+ .name = "watchdog-restart",
+};
+
int watchdog_register(struct watchdog *wd)
{
struct param_d *p;
@@ -159,7 +196,7 @@ int watchdog_register(struct watchdog *wd)
int ret = 0;
if (wd->hwdev)
- alias = of_alias_get(wd->hwdev->device_node);
+ alias = of_alias_get(wd->hwdev->of_node);
if (alias)
ret = watchdog_register_dev(wd, alias, DEVICE_ID_SINGLE);
@@ -215,6 +252,20 @@ int watchdog_register(struct watchdog *wd)
goto error_unregister;
}
+ p = dev_add_param_uint32(&wd->dev, "seconds_to_expire", param_set_readonly,
+ seconds_to_expire_get, &wd->seconds_to_expire, "%d", wd);
+ if (IS_ERR(p)) {
+ ret = PTR_ERR(p);
+ goto error_unregister;
+ }
+
+ if (!restart_handler.priority) {
+ restart_handler.priority = 10; /* don't override others */
+ ret = restart_handler_register(&restart_handler);
+ if (ret)
+ dev_warn(&wd->dev, "failed to register restart handler\n");
+ }
+
list_add_tail(&wd->list, &watchdog_list);
pr_debug("registering watchdog %s with priority %d\n", watchdog_name(wd),
@@ -258,10 +309,29 @@ struct watchdog *watchdog_get_default(void)
}
EXPORT_SYMBOL(watchdog_get_default);
+int watchdog_get_alias_id_from(struct watchdog *wd, struct device_node *root)
+{
+ struct device_node *dstnp, *srcnp = wd->hwdev ? wd->hwdev->of_node : NULL;
+ char *name;
+
+ if (!srcnp)
+ return -ENODEV;
+
+ name = of_get_reproducible_name(srcnp);
+ dstnp = of_find_node_by_reproducible_name(root, name);
+ free(name);
+
+ if (!dstnp)
+ return -ENODEV;
+
+ return of_alias_get_id_from(root, wd->hwdev->of_node, "watchdog");
+}
+EXPORT_SYMBOL(watchdog_get_alias_id_from);
+
struct watchdog *watchdog_get_by_name(const char *name)
{
struct watchdog *tmp;
- struct device_d *dev = get_device_by_name(name);
+ struct device *dev = get_device_by_name(name);
if (!dev)
return NULL;
@@ -273,3 +343,30 @@ struct watchdog *watchdog_get_by_name(const char *name)
return NULL;
}
EXPORT_SYMBOL(watchdog_get_by_name);
+
+int watchdog_inhibit_all(void)
+{
+ struct watchdog *wd;
+ int ret = 0;
+
+ list_for_each_entry(wd, &watchdog_list, list) {
+ int err;
+ if (!wd->priority || watchdog_hw_running(wd) == false)
+ continue;
+
+ err = watchdog_set_timeout(wd, 0);
+ if (!err)
+ continue;
+
+ if (err != -ENOSYS || !IS_ENABLED(CONFIG_WATCHDOG_POLLER)) {
+ ret = err;
+ continue;
+ }
+
+ wd->poller_enable = true;
+ watchdog_poller_start(wd);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(watchdog_inhibit_all);
diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c
new file mode 100644
index 0000000000..522f1c25f1
--- /dev/null
+++ b/drivers/watchdog/wdat_wdt.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI Hardware Watchdog (WDAT) driver.
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+#include <common.h>
+#include <acpi.h>
+#include <errno.h>
+#include <init.h>
+#include <io.h>
+#include <malloc.h>
+#include <of.h>
+#include <watchdog.h>
+
+enum acpi_wdat_actions {
+ ACPI_WDAT_RESET = 1,
+ ACPI_WDAT_GET_CURRENT_COUNTDOWN = 4,
+ ACPI_WDAT_GET_COUNTDOWN = 5,
+ ACPI_WDAT_SET_COUNTDOWN = 6,
+ ACPI_WDAT_GET_RUNNING_STATE = 8,
+ ACPI_WDAT_SET_RUNNING_STATE = 9,
+ ACPI_WDAT_GET_STOPPED_STATE = 10,
+ ACPI_WDAT_SET_STOPPED_STATE = 11,
+ ACPI_WDAT_GET_REBOOT = 16,
+ ACPI_WDAT_SET_REBOOT = 17,
+ ACPI_WDAT_GET_SHUTDOWN = 18,
+ ACPI_WDAT_SET_SHUTDOWN = 19,
+ ACPI_WDAT_GET_STATUS = 32,
+ ACPI_WDAT_SET_STATUS = 33,
+ ACPI_WDAT_ACTION_RESERVED = 34 /* 34 and greater are reserved */
+};
+
+enum acpi_wdat_instructions {
+ ACPI_WDAT_READ_VALUE = 0,
+ ACPI_WDAT_READ_COUNTDOWN = 1,
+ ACPI_WDAT_WRITE_VALUE = 2,
+ ACPI_WDAT_WRITE_COUNTDOWN = 3,
+ ACPI_WDAT_INSTRUCTION_RESERVED = 4, /* 4 and greater are reserved */
+ ACPI_WDAT_PRESERVE_REGISTER = 0x80 /* Except for this value */
+};
+
+#define MAX_WDAT_ACTIONS ACPI_WDAT_ACTION_RESERVED
+
+#define WDAT_DEFAULT_TIMEOUT 30
+
+/* WDAT Instruction Entries (actions) */
+
+struct __packed acpi_wdat_entry {
+ u8 action;
+ u8 instruction;
+ u16 reserved;
+ struct acpi_generic_address register_region;
+ u32 value; /* Value used with Read/Write register */
+ u32 mask; /* Bitmask required for this register instruction */
+};
+
+/**
+ * struct wdat_instruction - Single ACPI WDAT instruction
+ * @entry: Copy of the ACPI table instruction
+ * @reg: Register the instruction is accessing
+ * @node: Next instruction in action sequence
+ */
+struct wdat_instruction {
+ struct acpi_wdat_entry entry;
+ void __iomem *reg;
+ struct list_head node;
+};
+
+/**
+ * struct wdat_wdt - ACPI WDAT watchdog device
+ * @dev: Parent platform device
+ * @wdd: Watchdog core device
+ * @period: How long is one watchdog period in ms
+ * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5
+ * @stopped: Was the watchdog stopped by the driver in suspend
+ * @instructions: An array of instruction lists indexed by an action number from
+ * the WDAT table. There can be %NULL entries for not implemented
+ * actions.
+ */
+struct wdat_wdt {
+ struct watchdog wdd;
+ unsigned int period;
+ bool stopped_in_sleep;
+ bool stopped;
+ struct list_head *instructions[MAX_WDAT_ACTIONS];
+};
+
+struct __packed acpi_table_wdat {
+ struct acpi_table_header header; /* Common ACPI table header */
+ u32 header_length; /* Watchdog Header Length */
+ u16 pci_segment; /* PCI Segment number */
+ u8 pci_bus; /* PCI Bus number */
+ u8 pci_device; /* PCI Device number */
+ u8 pci_function; /* PCI Function number */
+ u8 reserved[3];
+ u32 timer_period; /* Period of one timer count (msec) */
+ u32 max_count; /* Maximum counter value supported */
+ u32 min_count; /* Minimum counter value */
+ u8 flags;
+ u8 reserved2[3];
+ u32 nr_entries; /* Number of watchdog entries that follow */
+ struct acpi_wdat_entry entries[];
+};
+
+#define ACPI_WDAT_ENABLED (1)
+#define ACPI_WDAT_STOPPED 0x80
+
+#define IO_COND(instr, is_pio, is_mmio) do { \
+ const struct acpi_generic_address *gas = &instr->entry.register_region; \
+ unsigned long port = (unsigned long __force)instr->reg; \
+ if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { \
+ is_mmio; \
+ } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { \
+ is_pio; \
+ } \
+} while (0)
+
+static unsigned int read8(const struct wdat_instruction *instr)
+{
+ IO_COND(instr, return inb(port), return readb(instr->reg));
+ return 0xff;
+}
+
+static unsigned int read16(const struct wdat_instruction *instr)
+{
+ IO_COND(instr, return inw(port), return readw(instr->reg));
+ return 0xffff;
+}
+
+static unsigned int read32(const struct wdat_instruction *instr)
+{
+ IO_COND(instr, return inl(port), return readl(instr->reg));
+ return 0xffffffff;
+}
+
+static void write8(u8 val, const struct wdat_instruction *instr)
+{
+ IO_COND(instr, outb(val,port), writeb(val, instr->reg));
+}
+
+static void write16(u16 val, const struct wdat_instruction *instr)
+{
+ IO_COND(instr, outw(val,port), writew(val, instr->reg));
+}
+
+static void write32(u32 val, const struct wdat_instruction *instr)
+{
+ IO_COND(instr, outl(val,port), writel(val, instr->reg));
+}
+
+static int wdat_wdt_read(struct wdat_wdt *wdat,
+ const struct wdat_instruction *instr, u32 *value)
+{
+ const struct acpi_generic_address *gas = &instr->entry.register_region;
+
+ switch (gas->access_width) {
+ case 1:
+ *value = read8(instr);
+ break;
+ case 2:
+ *value = read16(instr);
+ break;
+ case 3:
+ *value = read32(instr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(wdat->wdd.hwdev, "Read %#x from 0x%08llx\n", *value,
+ gas->address);
+
+ return 0;
+}
+
+static int wdat_wdt_write(struct wdat_wdt *wdat,
+ const struct wdat_instruction *instr, u32 value)
+{
+ const struct acpi_generic_address *gas = &instr->entry.register_region;
+
+ switch (gas->access_width) {
+ case 1:
+ write8((u8)value, instr);
+ break;
+ case 2:
+ write16((u16)value, instr);
+ break;
+ case 3:
+ write32(value, instr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(wdat->wdd.hwdev, "Wrote %#x to 0x%08llx\n", value,
+ gas->address);
+
+ return 0;
+}
+
+static int wdat_wdt_run_action(struct wdat_wdt *wdat, unsigned int action,
+ u32 param, u32 *retval)
+{
+ struct wdat_instruction *instr;
+
+ if (action >= ARRAY_SIZE(wdat->instructions)) {
+ dev_dbg(wdat->wdd.hwdev, "Invalid action %#x\n", action);
+ return -EINVAL;
+ }
+
+ if (!wdat->instructions[action]) {
+ dev_dbg(wdat->wdd.hwdev, "Unsupported action %#x\n", action);
+ return -EOPNOTSUPP;
+ }
+
+ dev_dbg(wdat->wdd.hwdev, "Running action %#x\n", action);
+
+ /* Run each instruction sequentially */
+ list_for_each_entry(instr, wdat->instructions[action], node) {
+ const struct acpi_wdat_entry *entry = &instr->entry;
+ const struct acpi_generic_address *gas;
+ u32 flags, value, mask, x, y;
+ bool preserve;
+ int ret;
+
+ gas = &entry->register_region;
+
+ preserve = entry->instruction & ACPI_WDAT_PRESERVE_REGISTER;
+ flags = entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER;
+ value = entry->value;
+ mask = entry->mask;
+
+ switch (flags) {
+ case ACPI_WDAT_READ_VALUE:
+ ret = wdat_wdt_read(wdat, instr, &x);
+ if (ret)
+ return ret;
+ x >>= gas->bit_offset;
+ x &= mask;
+ if (retval)
+ *retval = x == value;
+ break;
+
+ case ACPI_WDAT_READ_COUNTDOWN:
+ ret = wdat_wdt_read(wdat, instr, &x);
+ if (ret)
+ return ret;
+ x >>= gas->bit_offset;
+ x &= mask;
+ if (retval)
+ *retval = x;
+ break;
+
+ case ACPI_WDAT_WRITE_VALUE:
+ x = value & mask;
+ x <<= gas->bit_offset;
+ if (preserve) {
+ ret = wdat_wdt_read(wdat, instr, &y);
+ if (ret)
+ return ret;
+ y = y & ~(mask << gas->bit_offset);
+ x |= y;
+ }
+ ret = wdat_wdt_write(wdat, instr, x);
+ if (ret)
+ return ret;
+ break;
+
+ case ACPI_WDAT_WRITE_COUNTDOWN:
+ x = param;
+ x &= mask;
+ x <<= gas->bit_offset;
+ if (preserve) {
+ ret = wdat_wdt_read(wdat, instr, &y);
+ if (ret)
+ return ret;
+ y = y & ~(mask << gas->bit_offset);
+ x |= y;
+ }
+ ret = wdat_wdt_write(wdat, instr, x);
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ dev_err(wdat->wdd.hwdev, "Unknown instruction: %u\n",
+ flags);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void wdat_wdt_boot_status(struct wdat_wdt *wdat)
+{
+ u32 boot_status = 0;
+ int ret;
+
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_STATUS, 0, &boot_status);
+ if (ret && ret != -EOPNOTSUPP) {
+ dev_err(wdat->wdd.hwdev, "Failed to read boot status\n");
+ return;
+ }
+
+ /* Clear the boot status in case BIOS did not do it */
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_STATUS, 0, NULL);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_err(wdat->wdd.hwdev, "Failed to clear boot status\n");
+}
+
+static int wdat_wdt_start(struct watchdog *wdd)
+{
+ struct wdat_wdt *wdat = container_of(wdd, struct wdat_wdt, wdd);
+
+ return wdat_wdt_run_action(wdat, ACPI_WDAT_SET_RUNNING_STATE, 0, NULL);
+}
+
+static int wdat_wdt_stop(struct watchdog *wdd)
+{
+ struct wdat_wdt *wdat = container_of(wdd, struct wdat_wdt, wdd);
+
+ return wdat_wdt_run_action(wdat, ACPI_WDAT_SET_STOPPED_STATE, 0, NULL);
+}
+
+static void wdat_wdt_set_running(struct wdat_wdt *wdat)
+{
+ u32 running = 0;
+ int ret;
+
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_RUNNING_STATE, 0,
+ &running);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_err(wdat->wdd.hwdev, "Failed to read running state\n");
+
+ dev_dbg(wdat->wdd.hwdev, "Running state: %d\n", running);
+
+ wdat->wdd.running = running ? WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
+}
+
+static int wdat_wdt_set_timeout(struct watchdog *wdd, unsigned int timeout)
+{
+ struct wdat_wdt *wdat = container_of(wdd, struct wdat_wdt, wdd);
+ unsigned int periods;
+ int ret;
+
+ if (timeout == 0)
+ return wdat_wdt_stop(wdd);
+
+ periods = timeout * 1000 / wdat->period;
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_COUNTDOWN, periods, NULL);
+ if (ret)
+ return ret;
+
+ if (wdat->wdd.running == WDOG_HW_NOT_RUNNING) {
+ wdat_wdt_start(wdd);
+ wdat->wdd.running = WDOG_HW_RUNNING;
+ }
+
+ return wdat_wdt_run_action(wdat, ACPI_WDAT_RESET, 0, NULL);
+}
+
+static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat)
+{
+ int ret;
+
+ /*
+ * WDAT specification says that the watchdog is required to reboot
+ * the system when it fires. However, it also states that it is
+ * recommeded to make it configurable through hardware register. We
+ * enable reboot now if it is configrable, just in case.
+ */
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_REBOOT, 0, NULL);
+ if (ret && ret != -EOPNOTSUPP) {
+ dev_err(wdat->wdd.hwdev,
+ "Failed to enable reboot when watchdog triggers\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int wdat_wdt_probe(struct device *const dev)
+{
+ const struct acpi_wdat_entry *entries;
+ struct acpi_table_wdat *tbl;
+ struct wdat_wdt *wdat;
+ int i, ret;
+
+ dev_dbg(dev, "driver initializing...\n");
+
+ tbl = (struct acpi_table_wdat __force *)dev_request_mem_region_by_name(dev, "SDT");
+ if (IS_ERR(tbl)) {
+ dev_err(dev, "no SDT resource available: %pe\n", tbl);
+ return PTR_ERR(tbl);
+ }
+
+ dev_dbg(dev, "SDT is at 0x%p\n", tbl);
+
+ wdat = xzalloc(sizeof(*wdat));
+
+ /* WDAT specification wants to have >= 1ms period */
+ if (tbl->timer_period < 1) {
+ dev_dbg(dev, "timer_period is less than 1: %d\n", tbl->timer_period);
+ return -EINVAL;
+ }
+ if (tbl->min_count > tbl->max_count) {
+ dev_dbg(dev, "min_count must be greater than max_count\n");
+ return -EINVAL;
+ }
+
+ wdat->period = tbl->timer_period;
+ wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED;
+ wdat->wdd.set_timeout = wdat_wdt_set_timeout;
+ wdat->wdd.hwdev = dev;
+ wdat->wdd.timeout_max = U32_MAX;
+
+ entries = tbl->entries;
+
+ for (i = 0; i < tbl->nr_entries; i++) {
+ const struct acpi_generic_address *gas;
+ struct wdat_instruction *instr;
+ struct list_head *instructions;
+ struct resource *res;
+ unsigned int action;
+ struct resource r;
+
+ action = entries[i].action;
+ if (action >= MAX_WDAT_ACTIONS) {
+ dev_dbg(dev, "Skipping unknown action: %u\n", action);
+ continue;
+ }
+
+ instr = xzalloc(sizeof(*instr));
+
+ INIT_LIST_HEAD(&instr->node);
+ instr->entry = entries[i];
+
+ gas = &entries[i].register_region;
+
+ memset(&r, 0, sizeof(r));
+ r.start = gas->address;
+ r.end = r.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1;
+ if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ res = request_iomem_region(dev_name(dev), r.start, r.end);
+ } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+ res = request_ioport_region(dev_name(dev), r.start, r.end);
+ } else {
+ dev_dbg(dev, "Unsupported address space: %d\n",
+ gas->space_id);
+ continue;
+ }
+
+ /*
+ * Some entries have the same gas->address.
+ * We want the action but can't request the region multiple times.
+ */
+ if (IS_ERR(res) && (PTR_ERR(res) != -EBUSY))
+ return PTR_ERR(res);
+
+ instr->reg = IOMEM(r.start);
+
+ instructions = wdat->instructions[action];
+ if (!instructions) {
+ instructions = xzalloc(sizeof(*instructions));
+ if (!instructions)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(instructions);
+ wdat->instructions[action] = instructions;
+ }
+
+ list_add_tail(&instr->node, instructions);
+ }
+
+ wdat_wdt_boot_status(wdat);
+ wdat_wdt_set_running(wdat);
+
+ ret = wdat_wdt_enable_reboot(wdat);
+ if (ret)
+ return ret;
+
+ return watchdog_register(&wdat->wdd);
+}
+
+
+static struct acpi_driver wdat_wdt_driver = {
+ .signature = "WDAT",
+ .driver = {
+ .name = "wdat-wdt",
+ .probe = wdat_wdt_probe,
+ }
+};
+device_acpi_driver(wdat_wdt_driver);