diff options
Diffstat (limited to 'drivers/watchdog')
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, ®_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); |