Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

watchdog: Add ChromeOS EC-based watchdog driver

Embedded Controller (EC) present on Chromebook devices
can be used as a watchdog.
Implement a driver to support it.

Signed-off-by: Lukasz Majczak <lma@chromium.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20240126095721.782782-3-lma@chromium.org
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Lukasz Majczak and committed by
Lee Jones
843dac4d 4d2ff655

+222
+6
MAINTAINERS
··· 4990 4990 S: Maintained 4991 4991 F: drivers/platform/chrome/cros_hps_i2c.c 4992 4992 4993 + CHROMEOS EC WATCHDOG 4994 + M: Lukasz Majczak <lma@chromium.org> 4995 + L: chrome-platform@lists.linux.dev 4996 + S: Maintained 4997 + F: drivers/watchdog/cros_ec_wdt.c 4998 + 4993 4999 CHRONTEL CH7322 CEC DRIVER 4994 5000 M: Joe Tessler <jrt@google.com> 4995 5001 L: linux-media@vger.kernel.org
+11
drivers/watchdog/Kconfig
··· 181 181 watchdog. Alternatively say M to compile the driver as a module, 182 182 which will be called bd9576_wdt. 183 183 184 + config CROS_EC_WATCHDOG 185 + tristate "ChromeOS EC-based watchdog" 186 + select WATCHDOG_CORE 187 + depends on CROS_EC 188 + help 189 + Watchdog driver for Chromebook devices equipped with embedded controller. 190 + Trigger event is recorded in EC and checked on the subsequent boot. 191 + 192 + To compile this driver as a module, choose M here: the 193 + module will be called cros_ec_wdt. 194 + 184 195 config DA9052_WATCHDOG 185 196 tristate "Dialog DA9052 Watchdog" 186 197 depends on PMIC_DA9052 || COMPILE_TEST
+1
drivers/watchdog/Makefile
··· 217 217 218 218 # Architecture Independent 219 219 obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o 220 + obj-$(CONFIG_CROS_EC_WATCHDOG) += cros_ec_wdt.o 220 221 obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o 221 222 obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o 222 223 obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
+204
drivers/watchdog/cros_ec_wdt.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright 2024 Google LLC. 4 + * Author: Lukasz Majczak <lma@chromium.com> 5 + */ 6 + 7 + #include <linux/err.h> 8 + #include <linux/kernel.h> 9 + #include <linux/module.h> 10 + #include <linux/mod_devicetable.h> 11 + #include <linux/platform_data/cros_ec_commands.h> 12 + #include <linux/platform_data/cros_ec_proto.h> 13 + #include <linux/platform_device.h> 14 + #include <linux/watchdog.h> 15 + 16 + #define CROS_EC_WATCHDOG_DEFAULT_TIME 30 /* seconds */ 17 + #define DRV_NAME "cros-ec-wdt" 18 + 19 + union cros_ec_wdt_data { 20 + struct ec_params_hang_detect req; 21 + struct ec_response_hang_detect resp; 22 + } __packed; 23 + 24 + static int cros_ec_wdt_send_cmd(struct cros_ec_device *cros_ec, 25 + union cros_ec_wdt_data *arg) 26 + { 27 + int ret; 28 + struct { 29 + struct cros_ec_command msg; 30 + union cros_ec_wdt_data data; 31 + } __packed buf = { 32 + .msg = { 33 + .version = 0, 34 + .command = EC_CMD_HANG_DETECT, 35 + .insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ? 36 + sizeof(struct ec_response_hang_detect) : 37 + 0, 38 + .outsize = sizeof(struct ec_params_hang_detect), 39 + }, 40 + .data.req = arg->req 41 + }; 42 + 43 + ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg); 44 + if (ret < 0) 45 + return ret; 46 + 47 + arg->resp = buf.data.resp; 48 + 49 + return 0; 50 + } 51 + 52 + static int cros_ec_wdt_ping(struct watchdog_device *wdd) 53 + { 54 + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 55 + union cros_ec_wdt_data arg; 56 + int ret; 57 + 58 + arg.req.command = EC_HANG_DETECT_CMD_RELOAD; 59 + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 60 + if (ret < 0) 61 + dev_dbg(wdd->parent, "Failed to ping watchdog (%d)", ret); 62 + 63 + return ret; 64 + } 65 + 66 + static int cros_ec_wdt_start(struct watchdog_device *wdd) 67 + { 68 + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 69 + union cros_ec_wdt_data arg; 70 + int ret; 71 + 72 + /* Prepare watchdog on EC side */ 73 + arg.req.command = EC_HANG_DETECT_CMD_SET_TIMEOUT; 74 + arg.req.reboot_timeout_sec = wdd->timeout; 75 + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 76 + if (ret < 0) 77 + dev_dbg(wdd->parent, "Failed to start watchdog (%d)", ret); 78 + 79 + return ret; 80 + } 81 + 82 + static int cros_ec_wdt_stop(struct watchdog_device *wdd) 83 + { 84 + struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 85 + union cros_ec_wdt_data arg; 86 + int ret; 87 + 88 + arg.req.command = EC_HANG_DETECT_CMD_CANCEL; 89 + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 90 + if (ret < 0) 91 + dev_dbg(wdd->parent, "Failed to stop watchdog (%d)", ret); 92 + 93 + return ret; 94 + } 95 + 96 + static int cros_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 97 + { 98 + unsigned int old_timeout = wdd->timeout; 99 + int ret; 100 + 101 + wdd->timeout = t; 102 + ret = cros_ec_wdt_start(wdd); 103 + if (ret < 0) 104 + wdd->timeout = old_timeout; 105 + 106 + return ret; 107 + } 108 + 109 + static const struct watchdog_info cros_ec_wdt_ident = { 110 + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 111 + .firmware_version = 0, 112 + .identity = DRV_NAME, 113 + }; 114 + 115 + static const struct watchdog_ops cros_ec_wdt_ops = { 116 + .owner = THIS_MODULE, 117 + .ping = cros_ec_wdt_ping, 118 + .start = cros_ec_wdt_start, 119 + .stop = cros_ec_wdt_stop, 120 + .set_timeout = cros_ec_wdt_set_timeout, 121 + }; 122 + 123 + static int cros_ec_wdt_probe(struct platform_device *pdev) 124 + { 125 + struct device *dev = &pdev->dev; 126 + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); 127 + struct cros_ec_device *cros_ec = ec_dev->ec_dev; 128 + struct watchdog_device *wdd; 129 + union cros_ec_wdt_data arg; 130 + int ret = 0; 131 + 132 + wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); 133 + if (!wdd) 134 + return -ENOMEM; 135 + 136 + arg.req.command = EC_HANG_DETECT_CMD_GET_STATUS; 137 + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 138 + if (ret < 0) 139 + return dev_err_probe(dev, ret, "Failed to get watchdog bootstatus"); 140 + 141 + wdd->parent = &pdev->dev; 142 + wdd->info = &cros_ec_wdt_ident; 143 + wdd->ops = &cros_ec_wdt_ops; 144 + wdd->timeout = CROS_EC_WATCHDOG_DEFAULT_TIME; 145 + wdd->min_timeout = EC_HANG_DETECT_MIN_TIMEOUT; 146 + wdd->max_timeout = EC_HANG_DETECT_MAX_TIMEOUT; 147 + if (arg.resp.status == EC_HANG_DETECT_AP_BOOT_EC_WDT) 148 + wdd->bootstatus = WDIOF_CARDRESET; 149 + 150 + arg.req.command = EC_HANG_DETECT_CMD_CLEAR_STATUS; 151 + ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 152 + if (ret < 0) 153 + return dev_err_probe(dev, ret, "Failed to clear watchdog bootstatus"); 154 + 155 + watchdog_stop_on_reboot(wdd); 156 + watchdog_stop_on_unregister(wdd); 157 + watchdog_set_drvdata(wdd, cros_ec); 158 + platform_set_drvdata(pdev, wdd); 159 + 160 + return devm_watchdog_register_device(dev, wdd); 161 + } 162 + 163 + static int __maybe_unused cros_ec_wdt_suspend(struct platform_device *pdev, pm_message_t state) 164 + { 165 + struct watchdog_device *wdd = platform_get_drvdata(pdev); 166 + int ret = 0; 167 + 168 + if (watchdog_active(wdd)) 169 + ret = cros_ec_wdt_stop(wdd); 170 + 171 + return ret; 172 + } 173 + 174 + static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev) 175 + { 176 + struct watchdog_device *wdd = platform_get_drvdata(pdev); 177 + int ret = 0; 178 + 179 + if (watchdog_active(wdd)) 180 + ret = cros_ec_wdt_start(wdd); 181 + 182 + return ret; 183 + } 184 + 185 + static const struct platform_device_id cros_ec_wdt_id[] = { 186 + { DRV_NAME, 0 }, 187 + {} 188 + }; 189 + 190 + static struct platform_driver cros_ec_wdt_driver = { 191 + .probe = cros_ec_wdt_probe, 192 + .suspend = pm_ptr(cros_ec_wdt_suspend), 193 + .resume = pm_ptr(cros_ec_wdt_resume), 194 + .driver = { 195 + .name = DRV_NAME, 196 + }, 197 + .id_table = cros_ec_wdt_id, 198 + }; 199 + 200 + module_platform_driver(cros_ec_wdt_driver); 201 + 202 + MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id); 203 + MODULE_DESCRIPTION("Cros EC Watchdog Device Driver"); 204 + MODULE_LICENSE("GPL");