brintos

brintos / linux-shallow public Read only

0
0
Text · 5.0 KiB · ba045e2 Raw
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