diff --git a/Documentation/devicetree/bindings/watchdog/arm-smc-wdt.yaml b/Documentation/devicetree/bindings/watchdog/arm-smc-wdt.yaml new file mode 100644 index 000000000000..bec651541e0c --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/arm-smc-wdt.yaml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/watchdog/arm-smc-wdt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ARM Secure Monitor Call based watchdog + +allOf: + - $ref: "watchdog.yaml#" + +maintainers: + - Julius Werner + +properties: + compatible: + enum: + - arm,smc-wdt + arm,smc-id: + allOf: + - $ref: /schemas/types.yaml#/definitions/uint32 + description: | + The ATF smc function id used by the firmware. + Defaults to 0x82003D06 if unset. + +required: + - compatible + +examples: + - | + watchdog { + compatible = "arm,smc-wdt"; + arm,smc-id = <0x82003D06>; + timeout-sec = <15>; + }; + +... diff --git a/Documentation/devicetree/bindings/watchdog/renesas,wdt.txt b/Documentation/devicetree/bindings/watchdog/renesas,wdt.txt index 79b3c62f183d..e42fd3019cb7 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas,wdt.txt +++ b/Documentation/devicetree/bindings/watchdog/renesas,wdt.txt @@ -5,6 +5,7 @@ Required properties: fallback compatible string when compatible with the generic version. Examples with soctypes are: + - "renesas,r8a7742-wdt" (RZ/G1H) - "renesas,r8a7743-wdt" (RZ/G1M) - "renesas,r8a7744-wdt" (RZ/G1N) - "renesas,r8a7745-wdt" (RZ/G1E) diff --git a/MAINTAINERS b/MAINTAINERS index 9f7fb09a0140..465210f17948 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1468,6 +1468,13 @@ S: Maintained F: Documentation/devicetree/bindings/interrupt-controller/arm,vic.txt F: drivers/irqchip/irq-vic.c +ARM SMC WATCHDOG DRIVER +M: Julius Werner +R: Evan Benn +S: Maintained +F: devicetree/bindings/watchdog/arm-smc-wdt.yaml +F: drivers/watchdog/arm_smc_wdt.c + ARM SMMU DRIVERS M: Will Deacon R: Robin Murphy diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 03d0189f7d68..71d9ca727eee 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -513,6 +513,7 @@ CONFIG_UNIPHIER_THERMAL=y CONFIG_WATCHDOG=y CONFIG_ARM_SP805_WATCHDOG=y CONFIG_ARM_SBSA_WATCHDOG=y +CONFIG_ARM_SMC_WATCHDOG=y CONFIG_S3C2410_WATCHDOG=y CONFIG_DW_WATCHDOG=y CONFIG_SUNXI_WATCHDOG=m diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b739c476955b..55b910c453da 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -678,6 +678,7 @@ config TS4800_WATCHDOG config TS72XX_WATCHDOG tristate "TS-72XX SBC Watchdog" depends on MACH_TS72XX || COMPILE_TEST + select WATCHDOG_CORE help Technologic Systems TS-7200, TS-7250 and TS-7260 boards have watchdog timer implemented in a external CPLD chip. Say Y here @@ -867,6 +868,19 @@ config DIGICOLOR_WATCHDOG To compile this driver as a module, choose M here: the module will be called digicolor_wdt. +config ARM_SMC_WATCHDOG + tristate "ARM Secure Monitor Call based watchdog support" + depends on ARM || ARM64 + depends on OF + depends on HAVE_ARM_SMCCC + select WATCHDOG_CORE + help + Say Y here to include support for a watchdog timer + implemented by the EL3 Secure Monitor on ARM platforms. + Requires firmware support. + To compile this driver as a module, choose M here: the + module will be called arm_smc_wdt. + config LPC18XX_WATCHDOG tristate "LPC18xx/43xx Watchdog" depends on ARCH_LPC18XX || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 6de2e4ceef19..97bed1d3d97c 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -94,6 +94,7 @@ obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o +obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o # X86 (i386 + ia64 + x86_64) Architecture obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o diff --git a/drivers/watchdog/arm_smc_wdt.c b/drivers/watchdog/arm_smc_wdt.c new file mode 100644 index 000000000000..8f3d0c3a005f --- /dev/null +++ b/drivers/watchdog/arm_smc_wdt.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ARM Secure Monitor Call watchdog driver + * + * Copyright 2020 Google LLC. + * Julius Werner + * Based on mtk_wdt.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "arm_smc_wdt" +#define DRV_VERSION "1.0" + +enum smcwd_call { + SMCWD_INIT = 0, + SMCWD_SET_TIMEOUT = 1, + SMCWD_ENABLE = 2, + SMCWD_PET = 3, + SMCWD_GET_TIMELEFT = 4, +}; + +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned int timeout; + +static int smcwd_call(struct watchdog_device *wdd, enum smcwd_call call, + unsigned long arg, struct arm_smccc_res *res) +{ + struct arm_smccc_res local_res; + + if (!res) + res = &local_res; + + arm_smccc_smc((u32)(uintptr_t)watchdog_get_drvdata(wdd), call, arg, 0, + 0, 0, 0, 0, res); + + if (res->a0 == PSCI_RET_NOT_SUPPORTED) + return -ENODEV; + if (res->a0 == PSCI_RET_INVALID_PARAMS) + return -EINVAL; + if (res->a0 != PSCI_RET_SUCCESS) + return -EIO; + return 0; +} + +static int smcwd_ping(struct watchdog_device *wdd) +{ + return smcwd_call(wdd, SMCWD_PET, 0, NULL); +} + +static unsigned int smcwd_get_timeleft(struct watchdog_device *wdd) +{ + struct arm_smccc_res res; + + smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, &res); + if (res.a0) + return 0; + return res.a1; +} + +static int smcwd_set_timeout(struct watchdog_device *wdd, unsigned int timeout) +{ + int res; + + res = smcwd_call(wdd, SMCWD_SET_TIMEOUT, timeout, NULL); + if (!res) + wdd->timeout = timeout; + return res; +} + +static int smcwd_stop(struct watchdog_device *wdd) +{ + return smcwd_call(wdd, SMCWD_ENABLE, 0, NULL); +} + +static int smcwd_start(struct watchdog_device *wdd) +{ + return smcwd_call(wdd, SMCWD_ENABLE, 1, NULL); +} + +static const struct watchdog_info smcwd_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops smcwd_ops = { + .start = smcwd_start, + .stop = smcwd_stop, + .ping = smcwd_ping, + .set_timeout = smcwd_set_timeout, +}; + +static const struct watchdog_ops smcwd_timeleft_ops = { + .start = smcwd_start, + .stop = smcwd_stop, + .ping = smcwd_ping, + .set_timeout = smcwd_set_timeout, + .get_timeleft = smcwd_get_timeleft, +}; + +static int smcwd_probe(struct platform_device *pdev) +{ + struct watchdog_device *wdd; + int err; + struct arm_smccc_res res; + u32 smc_func_id; + + wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); + if (!wdd) + return -ENOMEM; + platform_set_drvdata(pdev, wdd); + + if (of_property_read_u32(pdev->dev.of_node, "arm,smc-id", + &smc_func_id)) + smc_func_id = 0x82003D06; + watchdog_set_drvdata(wdd, (void *)(uintptr_t)smc_func_id); + + err = smcwd_call(wdd, SMCWD_INIT, 0, &res); + if (err < 0) + return err; + + wdd->info = &smcwd_info; + /* get_timeleft is optional */ + if (smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL)) + wdd->ops = &smcwd_ops; + else + wdd->ops = &smcwd_timeleft_ops; + wdd->timeout = res.a2; + wdd->max_timeout = res.a2; + wdd->min_timeout = res.a1; + wdd->parent = &pdev->dev; + + watchdog_stop_on_reboot(wdd); + watchdog_stop_on_unregister(wdd); + watchdog_set_nowayout(wdd, nowayout); + watchdog_init_timeout(wdd, timeout, &pdev->dev); + err = smcwd_set_timeout(wdd, wdd->timeout); + if (err) + return err; + + err = devm_watchdog_register_device(&pdev->dev, wdd); + if (err) + return err; + + dev_info(&pdev->dev, + "Watchdog registered (timeout=%d sec, nowayout=%d)\n", + wdd->timeout, nowayout); + + return 0; +} + +static const struct of_device_id smcwd_dt_ids[] = { + { .compatible = "arm,smc-wdt" }, + {} +}; +MODULE_DEVICE_TABLE(of, smcwd_dt_ids); + +static struct platform_driver smcwd_driver = { + .probe = smcwd_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = smcwd_dt_ids, + }, +}; + +module_platform_driver(smcwd_driver); + +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Julius Werner "); +MODULE_DESCRIPTION("ARM Secure Monitor Call Watchdog Driver"); +MODULE_VERSION(DRV_VERSION); diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c index 0ad15d55071c..706fb09c2f24 100644 --- a/drivers/watchdog/da9062_wdt.c +++ b/drivers/watchdog/da9062_wdt.c @@ -35,6 +35,15 @@ struct da9062_watchdog { bool use_sw_pm; }; +static unsigned int da9062_wdt_read_timeout(struct da9062_watchdog *wdt) +{ + unsigned int val; + + regmap_read(wdt->hw->regmap, DA9062AA_CONTROL_D, &val); + + return wdt_timeout[val & DA9062AA_TWDSCALE_MASK]; +} + static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs) { unsigned int i; @@ -58,11 +67,6 @@ static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt, unsigned int regval) { struct da9062 *chip = wdt->hw; - int ret; - - ret = da9062_reset_watchdog_timer(wdt); - if (ret) - return ret; regmap_update_bits(chip->regmap, DA9062AA_CONTROL_D, @@ -183,7 +187,7 @@ MODULE_DEVICE_TABLE(of, da9062_compatible_id_table); static int da9062_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - int ret; + unsigned int timeout; struct da9062 *chip; struct da9062_watchdog *wdt; @@ -213,11 +217,19 @@ static int da9062_wdt_probe(struct platform_device *pdev) watchdog_set_drvdata(&wdt->wdtdev, wdt); dev_set_drvdata(dev, &wdt->wdtdev); - ret = devm_watchdog_register_device(dev, &wdt->wdtdev); - if (ret < 0) - return ret; + timeout = da9062_wdt_read_timeout(wdt); + if (timeout) + wdt->wdtdev.timeout = timeout; - return da9062_wdt_ping(&wdt->wdtdev); + /* Set timeout from DT value if available */ + watchdog_init_timeout(&wdt->wdtdev, 0, dev); + + if (timeout) { + da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout); + set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status); + } + + return devm_watchdog_register_device(dev, &wdt->wdtdev); } static int __maybe_unused da9062_wdt_suspend(struct device *dev) diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 3d65e92a4e3f..423584252606 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -46,15 +46,16 @@ static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs) } /* - * Return 0 if watchdog is disabled, else non zero. + * Read the currently active timeout. + * Zero means the watchdog is disabled. */ -static unsigned int da9063_wdt_is_running(struct da9063 *da9063) +static unsigned int da9063_wdt_read_timeout(struct da9063 *da9063) { unsigned int val; regmap_read(da9063->regmap, DA9063_REG_CONTROL_D, &val); - return val & DA9063_TWDSCALE_MASK; + return wdt_timeout[val & DA9063_TWDSCALE_MASK]; } static int da9063_wdt_disable_timer(struct da9063 *da9063) @@ -191,6 +192,7 @@ static int da9063_wdt_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct da9063 *da9063; struct watchdog_device *wdd; + unsigned int timeout; if (!dev->parent) return -EINVAL; @@ -214,13 +216,19 @@ static int da9063_wdt_probe(struct platform_device *pdev) watchdog_set_restart_priority(wdd, 128); watchdog_set_drvdata(wdd, da9063); - /* Set default timeout, maybe override it with DT value, scale it */ wdd->timeout = DA9063_WDG_TIMEOUT; + + /* Use pre-configured timeout if watchdog is already running. */ + timeout = da9063_wdt_read_timeout(da9063); + if (timeout) + wdd->timeout = timeout; + + /* Set timeout, maybe override it with DT value, scale it */ watchdog_init_timeout(wdd, 0, dev); da9063_wdt_set_timeout(wdd, wdd->timeout); - /* Change the timeout to the default value if the watchdog is running */ - if (da9063_wdt_is_running(da9063)) { + /* Update timeout if the watchdog is already running. */ + if (timeout) { da9063_wdt_update_timeout(da9063, wdd->timeout); set_bit(WDOG_HW_RUNNING, &wdd->status); } diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 1fe472f56cb3..b84f80f7d342 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -2,7 +2,7 @@ /* * Watchdog driver for IMX2 and later processors * - * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. + * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. * Copyright (C) 2014 Freescale Semiconductor, Inc. * * some parts adapted by similar drivers from Darius Augulis and Vladimir diff --git a/drivers/watchdog/imx_sc_wdt.c b/drivers/watchdog/imx_sc_wdt.c index 60a32469f7de..e9ee22a7cb45 100644 --- a/drivers/watchdog/imx_sc_wdt.c +++ b/drivers/watchdog/imx_sc_wdt.c @@ -175,6 +175,11 @@ static int imx_sc_wdt_probe(struct platform_device *pdev) wdog->timeout = DEFAULT_TIMEOUT; watchdog_init_timeout(wdog, 0, dev); + + ret = imx_sc_wdt_set_timeout(wdog, wdog->timeout); + if (ret) + return ret; + watchdog_stop_on_reboot(wdog); watchdog_stop_on_unregister(wdog); diff --git a/drivers/watchdog/m54xx_wdt.c b/drivers/watchdog/m54xx_wdt.c index 22f335e1e164..60ed6252e5f4 100644 --- a/drivers/watchdog/m54xx_wdt.c +++ b/drivers/watchdog/m54xx_wdt.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index 9b91882fe3c4..1616f93dfad7 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@ -273,6 +273,7 @@ static int omap_wdt_probe(struct platform_device *pdev) ret = watchdog_register_device(&wdev->wdog); if (ret) { + pm_runtime_put(wdev->dev); pm_runtime_disable(wdev->dev); return ret; } diff --git a/drivers/watchdog/riowd.c b/drivers/watchdog/riowd.c index dc3c06a92f93..1b9a6dc8f982 100644 --- a/drivers/watchdog/riowd.c +++ b/drivers/watchdog/riowd.c @@ -141,7 +141,7 @@ static long riowd_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) default: return -EINVAL; - }; + } return 0; }