From 6d03a247468059b0e59c821ef39e6762d4d6fc30 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 19 Jun 2024 23:00:51 +0200 Subject: Merging upstream version 6.9.2. Signed-off-by: Daniel Baumann --- drivers/watchdog/Kconfig | 12 ++- drivers/watchdog/Makefile | 1 + drivers/watchdog/cros_ec_wdt.c | 204 +++++++++++++++++++++++++++++++++++++++ drivers/watchdog/hpwdt.c | 25 +++++ drivers/watchdog/intel-mid_wdt.c | 11 ++- drivers/watchdog/it87_wdt.c | 4 + drivers/watchdog/qcom-wdt.c | 7 +- drivers/watchdog/s3c2410_wdt.c | 8 +- drivers/watchdog/sp805_wdt.c | 8 ++ drivers/watchdog/starfive-wdt.c | 5 +- drivers/watchdog/watchdog_core.c | 17 ++-- 11 files changed, 283 insertions(+), 19 deletions(-) create mode 100644 drivers/watchdog/cros_ec_wdt.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7d22051b15..6bee137cfb 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -181,6 +181,17 @@ config BD957XMUF_WATCHDOG watchdog. Alternatively say M to compile the driver as a module, which will be called bd9576_wdt. +config CROS_EC_WATCHDOG + tristate "ChromeOS EC-based watchdog" + select WATCHDOG_CORE + depends on CROS_EC + help + Watchdog driver for Chromebook devices equipped with embedded controller. + Trigger event is recorded in EC and checked on the subsequent boot. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_wdt. + config DA9052_WATCHDOG tristate "Dialog DA9052 Watchdog" depends on PMIC_DA9052 || COMPILE_TEST @@ -512,7 +523,6 @@ config S3C2410_WATCHDOG tristate "S3C6410/S5Pv210/Exynos Watchdog" depends on ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST select WATCHDOG_CORE - select MFD_SYSCON if ARCH_EXYNOS help Watchdog timer block in the Samsung S3C64xx, S5Pv210 and Exynos SoCs. This will reboot the system when the timer expires with diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 7cbc34514e..3710c218f0 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -217,6 +217,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o # Architecture Independent obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o +obj-$(CONFIG_CROS_EC_WATCHDOG) += cros_ec_wdt.o obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o diff --git a/drivers/watchdog/cros_ec_wdt.c b/drivers/watchdog/cros_ec_wdt.c new file mode 100644 index 0000000000..ba045e29f9 --- /dev/null +++ b/drivers/watchdog/cros_ec_wdt.c @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2024 Google LLC. + * Author: Lukasz Majczak + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CROS_EC_WATCHDOG_DEFAULT_TIME 30 /* seconds */ +#define DRV_NAME "cros-ec-wdt" + +union cros_ec_wdt_data { + struct ec_params_hang_detect req; + struct ec_response_hang_detect resp; +} __packed; + +static int cros_ec_wdt_send_cmd(struct cros_ec_device *cros_ec, + union cros_ec_wdt_data *arg) +{ + int ret; + struct { + struct cros_ec_command msg; + union cros_ec_wdt_data data; + } __packed buf = { + .msg = { + .version = 0, + .command = EC_CMD_HANG_DETECT, + .insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ? + sizeof(struct ec_response_hang_detect) : + 0, + .outsize = sizeof(struct ec_params_hang_detect), + }, + .data.req = arg->req + }; + + ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg); + if (ret < 0) + return ret; + + arg->resp = buf.data.resp; + + return 0; +} + +static int cros_ec_wdt_ping(struct watchdog_device *wdd) +{ + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); + union cros_ec_wdt_data arg; + int ret; + + arg.req.command = EC_HANG_DETECT_CMD_RELOAD; + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); + if (ret < 0) + dev_dbg(wdd->parent, "Failed to ping watchdog (%d)", ret); + + return ret; +} + +static int cros_ec_wdt_start(struct watchdog_device *wdd) +{ + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); + union cros_ec_wdt_data arg; + int ret; + + /* Prepare watchdog on EC side */ + arg.req.command = EC_HANG_DETECT_CMD_SET_TIMEOUT; + arg.req.reboot_timeout_sec = wdd->timeout; + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); + if (ret < 0) + dev_dbg(wdd->parent, "Failed to start watchdog (%d)", ret); + + return ret; +} + +static int cros_ec_wdt_stop(struct watchdog_device *wdd) +{ + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); + union cros_ec_wdt_data arg; + int ret; + + arg.req.command = EC_HANG_DETECT_CMD_CANCEL; + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); + if (ret < 0) + dev_dbg(wdd->parent, "Failed to stop watchdog (%d)", ret); + + return ret; +} + +static int cros_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) +{ + unsigned int old_timeout = wdd->timeout; + int ret; + + wdd->timeout = t; + ret = cros_ec_wdt_start(wdd); + if (ret < 0) + wdd->timeout = old_timeout; + + return ret; +} + +static const struct watchdog_info cros_ec_wdt_ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = DRV_NAME, +}; + +static const struct watchdog_ops cros_ec_wdt_ops = { + .owner = THIS_MODULE, + .ping = cros_ec_wdt_ping, + .start = cros_ec_wdt_start, + .stop = cros_ec_wdt_stop, + .set_timeout = cros_ec_wdt_set_timeout, +}; + +static int cros_ec_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct cros_ec_device *cros_ec = ec_dev->ec_dev; + struct watchdog_device *wdd; + union cros_ec_wdt_data arg; + int ret = 0; + + wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); + if (!wdd) + return -ENOMEM; + + arg.req.command = EC_HANG_DETECT_CMD_GET_STATUS; + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get watchdog bootstatus"); + + wdd->parent = &pdev->dev; + wdd->info = &cros_ec_wdt_ident; + wdd->ops = &cros_ec_wdt_ops; + wdd->timeout = CROS_EC_WATCHDOG_DEFAULT_TIME; + wdd->min_timeout = EC_HANG_DETECT_MIN_TIMEOUT; + wdd->max_timeout = EC_HANG_DETECT_MAX_TIMEOUT; + if (arg.resp.status == EC_HANG_DETECT_AP_BOOT_EC_WDT) + wdd->bootstatus = WDIOF_CARDRESET; + + arg.req.command = EC_HANG_DETECT_CMD_CLEAR_STATUS; + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to clear watchdog bootstatus"); + + watchdog_stop_on_reboot(wdd); + watchdog_stop_on_unregister(wdd); + watchdog_set_drvdata(wdd, cros_ec); + platform_set_drvdata(pdev, wdd); + + return devm_watchdog_register_device(dev, wdd); +} + +static int __maybe_unused cros_ec_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + int ret = 0; + + if (watchdog_active(wdd)) + ret = cros_ec_wdt_stop(wdd); + + return ret; +} + +static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + int ret = 0; + + if (watchdog_active(wdd)) + ret = cros_ec_wdt_start(wdd); + + return ret; +} + +static const struct platform_device_id cros_ec_wdt_id[] = { + { DRV_NAME, 0 }, + {} +}; + +static struct platform_driver cros_ec_wdt_driver = { + .probe = cros_ec_wdt_probe, + .suspend = pm_ptr(cros_ec_wdt_suspend), + .resume = pm_ptr(cros_ec_wdt_resume), + .driver = { + .name = DRV_NAME, + }, + .id_table = cros_ec_wdt_id, +}; + +module_platform_driver(cros_ec_wdt_driver); + +MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id); +MODULE_DESCRIPTION("Cros EC Watchdog Device Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 138dc8d8ca..ae30e394d1 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -378,11 +378,36 @@ static void hpwdt_exit(struct pci_dev *dev) pci_disable_device(dev); } +static int hpwdt_suspend(struct device *dev) +{ + if (watchdog_active(&hpwdt_dev)) + hpwdt_stop(); + + return 0; +} + +static int hpwdt_resume(struct device *dev) +{ + if (watchdog_active(&hpwdt_dev)) + hpwdt_start(&hpwdt_dev); + + return 0; +} + +static const struct dev_pm_ops hpwdt_pm_ops = { + LATE_SYSTEM_SLEEP_PM_OPS(hpwdt_suspend, hpwdt_resume) +}; + static struct pci_driver hpwdt_driver = { .name = "hpwdt", .id_table = hpwdt_devices, .probe = hpwdt_init_one, .remove = hpwdt_exit, + + .driver = { + .name = "hpwdt", + .pm = &hpwdt_pm_ops, + } }; MODULE_AUTHOR("Tom Mingarelli"); diff --git a/drivers/watchdog/intel-mid_wdt.c b/drivers/watchdog/intel-mid_wdt.c index fb7fae7501..8d71f6a223 100644 --- a/drivers/watchdog/intel-mid_wdt.c +++ b/drivers/watchdog/intel-mid_wdt.c @@ -9,15 +9,20 @@ * Contact: David Cohen */ +#include +#include +#include #include +#include #include -#include +#include #include +#include #include + #include #include -#include #define IPC_WATCHDOG 0xf8 @@ -122,7 +127,7 @@ static int mid_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct watchdog_device *wdt_dev; - struct intel_mid_wdt_pdata *pdata = dev->platform_data; + struct intel_mid_wdt_pdata *pdata = dev_get_platdata(dev); struct mid_wdt *mid; int ret; diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c index 9297a58919..3e8c15138e 100644 --- a/drivers/watchdog/it87_wdt.c +++ b/drivers/watchdog/it87_wdt.c @@ -213,12 +213,16 @@ static int wdt_stop(struct watchdog_device *wdd) /** * wdt_set_timeout - set a new timeout value with watchdog ioctl + * @wdd: pointer to the watchdog_device structure * @t: timeout value in seconds * * The hardware device has a 8 or 16 bit watchdog timer (depends on * chip version) that can be configured to count seconds or minutes. * * Used within WDIOC_SETTIMEOUT watchdog device ioctl. + * + * Return: 0 if the timeout was set successfully, or a negative error code on + * failure. */ static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 9e790f0c20..006f9c61aa 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -41,6 +41,7 @@ static const u32 reg_offset_data_kpss[] = { struct qcom_wdt_match_data { const u32 *offset; bool pretimeout; + u32 max_tick_count; }; struct qcom_wdt { @@ -177,11 +178,13 @@ static const struct watchdog_info qcom_wdt_pt_info = { static const struct qcom_wdt_match_data match_data_apcs_tmr = { .offset = reg_offset_data_apcs_tmr, .pretimeout = false, + .max_tick_count = 0x10000000U, }; static const struct qcom_wdt_match_data match_data_kpss = { .offset = reg_offset_data_kpss, .pretimeout = true, + .max_tick_count = 0xFFFFFU, }; static int qcom_wdt_probe(struct platform_device *pdev) @@ -236,7 +239,7 @@ static int qcom_wdt_probe(struct platform_device *pdev) */ wdt->rate = clk_get_rate(clk); if (wdt->rate == 0 || - wdt->rate > 0x10000000U) { + wdt->rate > data->max_tick_count) { dev_err(dev, "invalid clock rate\n"); return -EINVAL; } @@ -260,7 +263,7 @@ static int qcom_wdt_probe(struct platform_device *pdev) wdt->wdd.ops = &qcom_wdt_ops; wdt->wdd.min_timeout = 1; - wdt->wdd.max_timeout = 0x10000000U / wdt->rate; + wdt->wdd.max_timeout = data->max_tick_count / wdt->rate; wdt->wdd.parent = dev; wdt->layout = data->offset; diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 349d30462c..686cf544d0 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -24,9 +24,9 @@ #include #include #include -#include #include #include +#include #define S3C2410_WTCON 0x00 #define S3C2410_WTDAT 0x04 @@ -699,11 +699,11 @@ static int s3c2410wdt_probe(struct platform_device *pdev) return ret; if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) { - wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, - "samsung,syscon-phandle"); + wdt->pmureg = exynos_get_pmu_regmap_by_phandle(dev->of_node, + "samsung,syscon-phandle"); if (IS_ERR(wdt->pmureg)) return dev_err_probe(dev, PTR_ERR(wdt->pmureg), - "syscon regmap lookup failed.\n"); + "PMU regmap lookup failed.\n"); } wdt_irq = platform_get_irq(pdev, 0); diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index 2756ed54ca..109e2e37e8 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -232,6 +233,7 @@ static int sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { struct sp805_wdt *wdt; + struct reset_control *rst; u64 rate = 0; int ret = 0; @@ -264,6 +266,12 @@ sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) return -ENODEV; } + rst = devm_reset_control_get_optional_exclusive(&adev->dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(&adev->dev, PTR_ERR(rst), "Can not get reset\n"); + + reset_control_deassert(rst); + wdt->adev = adev; wdt->wdd.info = &wdt_info; wdt->wdd.ops = &wdt_ops; diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c index df68ae4acb..b4b0598836 100644 --- a/drivers/watchdog/starfive-wdt.c +++ b/drivers/watchdog/starfive-wdt.c @@ -559,7 +559,10 @@ static int starfive_wdt_resume(struct device *dev) starfive_wdt_set_reload_count(wdt, wdt->reload); starfive_wdt_lock(wdt); - return starfive_wdt_start(wdt); + if (watchdog_active(&wdt->wdd)) + return starfive_wdt_start(wdt); + + return 0; } static int starfive_wdt_runtime_suspend(struct device *dev) diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index 5b55ccae06..aff2c3912e 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -260,12 +260,12 @@ static int __watchdog_register_device(struct watchdog_device *wdd) if (wdd->parent) { ret = of_alias_get_id(wdd->parent->of_node, "watchdog"); if (ret >= 0) - id = ida_simple_get(&watchdog_ida, ret, - ret + 1, GFP_KERNEL); + id = ida_alloc_range(&watchdog_ida, ret, ret, + GFP_KERNEL); } if (id < 0) - id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL); + id = ida_alloc_max(&watchdog_ida, MAX_DOGS - 1, GFP_KERNEL); if (id < 0) return id; @@ -273,19 +273,20 @@ static int __watchdog_register_device(struct watchdog_device *wdd) ret = watchdog_dev_register(wdd); if (ret) { - ida_simple_remove(&watchdog_ida, id); + ida_free(&watchdog_ida, id); if (!(id == 0 && ret == -EBUSY)) return ret; /* Retry in case a legacy watchdog module exists */ - id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL); + id = ida_alloc_range(&watchdog_ida, 1, MAX_DOGS - 1, + GFP_KERNEL); if (id < 0) return id; wdd->id = id; ret = watchdog_dev_register(wdd); if (ret) { - ida_simple_remove(&watchdog_ida, id); + ida_free(&watchdog_ida, id); return ret; } } @@ -309,7 +310,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd) pr_err("watchdog%d: Cannot register reboot notifier (%d)\n", wdd->id, ret); watchdog_dev_unregister(wdd); - ida_simple_remove(&watchdog_ida, id); + ida_free(&watchdog_ida, id); return ret; } } @@ -382,7 +383,7 @@ static void __watchdog_unregister_device(struct watchdog_device *wdd) unregister_reboot_notifier(&wdd->reboot_nb); watchdog_dev_unregister(wdd); - ida_simple_remove(&watchdog_ida, wdd->id); + ida_free(&watchdog_ida, wdd->id); } /** -- cgit v1.2.3