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