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
19 union cros_ec_wdt_data {
20 struct ec_params_hang_detect req;
21 struct ec_response_hang_detect resp;
22 } __packed;
23
cros_ec_wdt_send_cmd(struct cros_ec_device * cros_ec,union cros_ec_wdt_data * arg)24 static 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
cros_ec_wdt_ping(struct watchdog_device * wdd)48 static 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
cros_ec_wdt_start(struct watchdog_device * wdd)62 static 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
cros_ec_wdt_stop(struct watchdog_device * wdd)78 static 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
cros_ec_wdt_set_timeout(struct watchdog_device * wdd,unsigned int t)92 static 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
105 static 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
111 static 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
cros_ec_wdt_probe(struct platform_device * pdev)119 static 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
cros_ec_wdt_suspend(struct platform_device * pdev,pm_message_t state)159 static 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
cros_ec_wdt_resume(struct platform_device * pdev)170 static 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
181 static const struct platform_device_id cros_ec_wdt_id[] = {
182 { DRV_NAME, 0 },
183 {}
184 };
185
186 static 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
196 module_platform_driver(cros_ec_wdt_driver);
197
198 MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id);
199 MODULE_DESCRIPTION("Cros EC Watchdog Device Driver");
200 MODULE_LICENSE("GPL");
201