FILE
cros_ec_wdt.c
File preview with syntax highlighting.
linux-6.19.14/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 19union cros_ec_wdt_data { 20 struct ec_params_hang_detect req; 21 struct ec_response_hang_detect resp; 22} __packed; 23 24static 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 48static 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 62static 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 78static 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 92static 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 105static 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 111static 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 119static 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 159static 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 170static 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 181static const struct platform_device_id cros_ec_wdt_id[] = { 182 { DRV_NAME, 0 }, 183 {} 184}; 185 186static 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 196module_platform_driver(cros_ec_wdt_driver); 197 198MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id); 199MODULE_DESCRIPTION("Cros EC Watchdog Device Driver"); 200MODULE_LICENSE("GPL");
~/
Read-only file preview.