File Index
Files and directories
Sign in
FILE

cros_ec_wdt.c

File preview with syntax highlighting.

linux-6.19.14/drivers/watchdog/cros_ec_wdt.c 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.