summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/watchdog')
-rw-r--r--drivers/watchdog/Kconfig12
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/cros_ec_wdt.c204
-rw-r--r--drivers/watchdog/hpwdt.c25
-rw-r--r--drivers/watchdog/intel-mid_wdt.c11
-rw-r--r--drivers/watchdog/it87_wdt.c4
-rw-r--r--drivers/watchdog/qcom-wdt.c7
-rw-r--r--drivers/watchdog/s3c2410_wdt.c8
-rw-r--r--drivers/watchdog/sp805_wdt.c8
-rw-r--r--drivers/watchdog/starfive-wdt.c5
-rw-r--r--drivers/watchdog/watchdog_core.c17
11 files changed, 283 insertions, 19 deletions
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 <lma@chromium.com>
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#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 <david.a.cohen@linux.intel.com>
*/
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
#include <linux/interrupt.h>
+#include <linux/math.h>
#include <linux/module.h>
-#include <linux/nmi.h>
+#include <linux/panic.h>
#include <linux/platform_device.h>
+#include <linux/types.h>
#include <linux/watchdog.h>
+
#include <linux/platform_data/intel-mid_wdt.h>
#include <asm/intel_scu_ipc.h>
-#include <asm/intel-mid.h>
#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 <linux/slab.h>
#include <linux/err.h>
#include <linux/of.h>
-#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
#include <linux/delay.h>
+#include <linux/soc/samsung/exynos-pmu.h>
#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 <linux/moduleparam.h>
#include <linux/pm.h>
#include <linux/property.h>
+#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
@@ -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);
}
/**