1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright 2024 Google LLC.
4 * Author: Lukasz Majczak <lma@chromium.com>
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>
16 #define CROS_EC_WATCHDOG_DEFAULT_TIME 30 /* seconds */
17 #define DRV_NAME "cros-ec-wdt"
19 union cros_ec_wdt_data
{
20 struct ec_params_hang_detect req
;
21 struct ec_response_hang_detect resp
;
24 static int cros_ec_wdt_send_cmd(struct cros_ec_device
*cros_ec
,
25 union cros_ec_wdt_data
*arg
)
29 struct cros_ec_command msg
;
30 union cros_ec_wdt_data data
;
34 .command
= EC_CMD_HANG_DETECT
,
35 .insize
= (arg
->req
.command
== EC_HANG_DETECT_CMD_GET_STATUS
) ?
36 sizeof(struct ec_response_hang_detect
) :
38 .outsize
= sizeof(struct ec_params_hang_detect
),
43 ret
= cros_ec_cmd_xfer_status(cros_ec
, &buf
.msg
);
47 arg
->resp
= buf
.data
.resp
;
52 static int cros_ec_wdt_ping(struct watchdog_device
*wdd
)
54 struct cros_ec_device
*cros_ec
= watchdog_get_drvdata(wdd
);
55 union cros_ec_wdt_data arg
;
58 arg
.req
.command
= EC_HANG_DETECT_CMD_RELOAD
;
59 ret
= cros_ec_wdt_send_cmd(cros_ec
, &arg
);
61 dev_dbg(wdd
->parent
, "Failed to ping watchdog (%d)", ret
);
66 static int cros_ec_wdt_start(struct watchdog_device
*wdd
)
68 struct cros_ec_device
*cros_ec
= watchdog_get_drvdata(wdd
);
69 union cros_ec_wdt_data arg
;
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
);
77 dev_dbg(wdd
->parent
, "Failed to start watchdog (%d)", ret
);
82 static int cros_ec_wdt_stop(struct watchdog_device
*wdd
)
84 struct cros_ec_device
*cros_ec
= watchdog_get_drvdata(wdd
);
85 union cros_ec_wdt_data arg
;
88 arg
.req
.command
= EC_HANG_DETECT_CMD_CANCEL
;
89 ret
= cros_ec_wdt_send_cmd(cros_ec
, &arg
);
91 dev_dbg(wdd
->parent
, "Failed to stop watchdog (%d)", ret
);
96 static int cros_ec_wdt_set_timeout(struct watchdog_device
*wdd
, unsigned int t
)
98 unsigned int old_timeout
= wdd
->timeout
;
102 ret
= cros_ec_wdt_start(wdd
);
104 wdd
->timeout
= old_timeout
;
109 static const struct watchdog_info cros_ec_wdt_ident
= {
110 .options
= WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
,
111 .firmware_version
= 0,
112 .identity
= DRV_NAME
,
115 static 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
,
123 static int cros_ec_wdt_probe(struct platform_device
*pdev
)
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
;
132 wdd
= devm_kzalloc(&pdev
->dev
, sizeof(*wdd
), GFP_KERNEL
);
136 arg
.req
.command
= EC_HANG_DETECT_CMD_GET_STATUS
;
137 ret
= cros_ec_wdt_send_cmd(cros_ec
, &arg
);
139 return dev_err_probe(dev
, ret
, "Failed to get watchdog bootstatus");
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
;
150 arg
.req
.command
= EC_HANG_DETECT_CMD_CLEAR_STATUS
;
151 ret
= cros_ec_wdt_send_cmd(cros_ec
, &arg
);
153 return dev_err_probe(dev
, ret
, "Failed to clear watchdog bootstatus");
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
);
160 return devm_watchdog_register_device(dev
, wdd
);
163 static int __maybe_unused
cros_ec_wdt_suspend(struct platform_device
*pdev
, pm_message_t state
)
165 struct watchdog_device
*wdd
= platform_get_drvdata(pdev
);
168 if (watchdog_active(wdd
))
169 ret
= cros_ec_wdt_stop(wdd
);
174 static int __maybe_unused
cros_ec_wdt_resume(struct platform_device
*pdev
)
176 struct watchdog_device
*wdd
= platform_get_drvdata(pdev
);
179 if (watchdog_active(wdd
))
180 ret
= cros_ec_wdt_start(wdd
);
185 static const struct platform_device_id cros_ec_wdt_id
[] = {
190 static 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
),
197 .id_table
= cros_ec_wdt_id
,
200 module_platform_driver(cros_ec_wdt_driver
);
202 MODULE_DEVICE_TABLE(platform
, cros_ec_wdt_id
);
203 MODULE_DESCRIPTION("Cros EC Watchdog Device Driver");
204 MODULE_LICENSE("GPL");