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 DEFINE_RAW_FLEX(struct cros_ec_command, msg, data, 29 sizeof(union cros_ec_wdt_data)); 30 31 msg->version = 0; 32 msg->command = EC_CMD_HANG_DETECT; 33 msg->insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ? 34 sizeof(struct ec_response_hang_detect) : 35 0; 36 msg->outsize = sizeof(struct ec_params_hang_detect); 37 *(struct ec_params_hang_detect *)msg->data = arg->req; 38 39 ret = cros_ec_cmd_xfer_status(cros_ec, msg); 40 if (ret < 0) 41 return ret; 42 43 arg->resp = *(struct ec_response_hang_detect *)msg->data; 44 45 return 0; 46 } 47 48 static int cros_ec_wdt_ping(struct watchdog_device *wdd) 49 { 50 struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 51 union cros_ec_wdt_data arg; 52 int ret; 53 54 arg.req.command = EC_HANG_DETECT_CMD_RELOAD; 55 ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 56 if (ret < 0) 57 dev_dbg(wdd->parent, "Failed to ping watchdog (%d)\n", ret); 58 59 return ret; 60 } 61 62 static int cros_ec_wdt_start(struct watchdog_device *wdd) 63 { 64 struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 65 union cros_ec_wdt_data arg; 66 int ret; 67 68 /* Prepare watchdog on EC side */ 69 arg.req.command = EC_HANG_DETECT_CMD_SET_TIMEOUT; 70 arg.req.reboot_timeout_sec = wdd->timeout; 71 ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 72 if (ret < 0) 73 dev_dbg(wdd->parent, "Failed to start watchdog (%d)\n", ret); 74 75 return ret; 76 } 77 78 static int cros_ec_wdt_stop(struct watchdog_device *wdd) 79 { 80 struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd); 81 union cros_ec_wdt_data arg; 82 int ret; 83 84 arg.req.command = EC_HANG_DETECT_CMD_CANCEL; 85 ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 86 if (ret < 0) 87 dev_dbg(wdd->parent, "Failed to stop watchdog (%d)\n", ret); 88 89 return ret; 90 } 91 92 static int cros_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 93 { 94 unsigned int old_timeout = wdd->timeout; 95 int ret; 96 97 wdd->timeout = t; 98 ret = cros_ec_wdt_start(wdd); 99 if (ret < 0) 100 wdd->timeout = old_timeout; 101 102 return ret; 103 } 104 105 static const struct watchdog_info cros_ec_wdt_ident = { 106 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 107 .firmware_version = 0, 108 .identity = DRV_NAME, 109 }; 110 111 static const struct watchdog_ops cros_ec_wdt_ops = { 112 .owner = THIS_MODULE, 113 .ping = cros_ec_wdt_ping, 114 .start = cros_ec_wdt_start, 115 .stop = cros_ec_wdt_stop, 116 .set_timeout = cros_ec_wdt_set_timeout, 117 }; 118 119 static int cros_ec_wdt_probe(struct platform_device *pdev) 120 { 121 struct device *dev = &pdev->dev; 122 struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); 123 struct cros_ec_device *cros_ec = ec_dev->ec_dev; 124 struct watchdog_device *wdd; 125 union cros_ec_wdt_data arg; 126 int ret = 0; 127 128 wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL); 129 if (!wdd) 130 return -ENOMEM; 131 132 arg.req.command = EC_HANG_DETECT_CMD_GET_STATUS; 133 ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 134 if (ret < 0) 135 return dev_err_probe(dev, ret, "Failed to get watchdog bootstatus\n"); 136 137 wdd->parent = &pdev->dev; 138 wdd->info = &cros_ec_wdt_ident; 139 wdd->ops = &cros_ec_wdt_ops; 140 wdd->timeout = CROS_EC_WATCHDOG_DEFAULT_TIME; 141 wdd->min_timeout = EC_HANG_DETECT_MIN_TIMEOUT; 142 wdd->max_timeout = EC_HANG_DETECT_MAX_TIMEOUT; 143 if (arg.resp.status == EC_HANG_DETECT_AP_BOOT_EC_WDT) 144 wdd->bootstatus = WDIOF_CARDRESET; 145 146 arg.req.command = EC_HANG_DETECT_CMD_CLEAR_STATUS; 147 ret = cros_ec_wdt_send_cmd(cros_ec, &arg); 148 if (ret < 0) 149 return dev_err_probe(dev, ret, "Failed to clear watchdog bootstatus\n"); 150 151 watchdog_stop_on_reboot(wdd); 152 watchdog_stop_on_unregister(wdd); 153 watchdog_set_drvdata(wdd, cros_ec); 154 platform_set_drvdata(pdev, wdd); 155 156 return devm_watchdog_register_device(dev, wdd); 157 } 158 159 static int __maybe_unused cros_ec_wdt_suspend(struct platform_device *pdev, pm_message_t state) 160 { 161 struct watchdog_device *wdd = platform_get_drvdata(pdev); 162 int ret = 0; 163 164 if (watchdog_active(wdd)) 165 ret = cros_ec_wdt_stop(wdd); 166 167 return ret; 168 } 169 170 static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev) 171 { 172 struct watchdog_device *wdd = platform_get_drvdata(pdev); 173 int ret = 0; 174 175 if (watchdog_active(wdd)) 176 ret = cros_ec_wdt_start(wdd); 177 178 return ret; 179 } 180 181 static const struct platform_device_id cros_ec_wdt_id[] = { 182 { DRV_NAME, 0 }, 183 {} 184 }; 185 186 static struct platform_driver cros_ec_wdt_driver = { 187 .probe = cros_ec_wdt_probe, 188 .suspend = pm_ptr(cros_ec_wdt_suspend), 189 .resume = pm_ptr(cros_ec_wdt_resume), 190 .driver = { 191 .name = DRV_NAME, 192 }, 193 .id_table = cros_ec_wdt_id, 194 }; 195 196 module_platform_driver(cros_ec_wdt_driver); 197 198 MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id); 199 MODULE_DESCRIPTION("Cros EC Watchdog Device Driver"); 200 MODULE_LICENSE("GPL"); 201