// 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");