205 lines · c
1// SPDX-License-Identifier: GPL-2.02/*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 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->req41 };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 52static 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 66static 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 82static 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 96static 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 109static 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 115static 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 123static 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 163static 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 174static 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 185static const struct platform_device_id cros_ec_wdt_id[] = {186 { DRV_NAME, 0 },187 {}188};189 190static 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 200module_platform_driver(cros_ec_wdt_driver);201 202MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id);203MODULE_DESCRIPTION("Cros EC Watchdog Device Driver");204MODULE_LICENSE("GPL");205