xref: /linux/drivers/watchdog/nct6694_wdt.c (revision 55a42f78ffd386e01a5404419f8c5ded7db70a21)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Nuvoton NCT6694 WDT driver based on USB interface.
4  *
5  * Copyright (C) 2025 Nuvoton Technology Corp.
6  */
7 
8 #include <linux/idr.h>
9 #include <linux/kernel.h>
10 #include <linux/mfd/nct6694.h>
11 #include <linux/module.h>
12 #include <linux/platform_device.h>
13 #include <linux/slab.h>
14 #include <linux/watchdog.h>
15 
16 #define DEVICE_NAME "nct6694-wdt"
17 
18 #define NCT6694_DEFAULT_TIMEOUT		10
19 #define NCT6694_DEFAULT_PRETIMEOUT	0
20 
21 #define NCT6694_WDT_MAX_DEVS		2
22 
23 /*
24  * USB command module type for NCT6694 WDT controller.
25  * This defines the module type used for communication with the NCT6694
26  * WDT controller over the USB interface.
27  */
28 #define NCT6694_WDT_MOD			0x07
29 
30 /* Command 00h - WDT Setup */
31 #define NCT6694_WDT_SETUP		0x00
32 #define NCT6694_WDT_SETUP_SEL(idx)	(idx ? 0x01 : 0x00)
33 
34 /* Command 01h - WDT Command */
35 #define NCT6694_WDT_COMMAND		0x01
36 #define NCT6694_WDT_COMMAND_SEL(idx)	(idx ? 0x01 : 0x00)
37 
38 static unsigned int timeout[NCT6694_WDT_MAX_DEVS] = {
39 	[0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_TIMEOUT
40 };
41 module_param_array(timeout, int, NULL, 0644);
42 MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
43 
44 static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] = {
45 	[0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_PRETIMEOUT
46 };
47 module_param_array(pretimeout, int, NULL, 0644);
48 MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds");
49 
50 static bool nowayout = WATCHDOG_NOWAYOUT;
51 module_param(nowayout, bool, 0);
52 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
53 			   __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
54 
55 enum {
56 	NCT6694_ACTION_NONE = 0,
57 	NCT6694_ACTION_SIRQ,
58 	NCT6694_ACTION_GPO,
59 };
60 
61 struct __packed nct6694_wdt_setup {
62 	__le32 pretimeout;
63 	__le32 timeout;
64 	u8 owner;
65 	u8 scratch;
66 	u8 control;
67 	u8 status;
68 	__le32 countdown;
69 };
70 
71 struct __packed nct6694_wdt_cmd {
72 	__le32 wdt_cmd;
73 	__le32 reserved;
74 };
75 
76 union __packed nct6694_wdt_msg {
77 	struct nct6694_wdt_setup setup;
78 	struct nct6694_wdt_cmd cmd;
79 };
80 
81 struct nct6694_wdt_data {
82 	struct watchdog_device wdev;
83 	struct device *dev;
84 	struct nct6694 *nct6694;
85 	union nct6694_wdt_msg *msg;
86 	unsigned char wdev_idx;
87 };
88 
89 static int nct6694_wdt_setting(struct watchdog_device *wdev,
90 			       u32 timeout_val, u8 timeout_act,
91 			       u32 pretimeout_val, u8 pretimeout_act)
92 {
93 	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
94 	struct nct6694_wdt_setup *setup = &data->msg->setup;
95 	const struct nct6694_cmd_header cmd_hd = {
96 		.mod = NCT6694_WDT_MOD,
97 		.cmd = NCT6694_WDT_SETUP,
98 		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
99 		.len = cpu_to_le16(sizeof(*setup))
100 	};
101 	unsigned int timeout_fmt, pretimeout_fmt;
102 
103 	if (pretimeout_val == 0)
104 		pretimeout_act = NCT6694_ACTION_NONE;
105 
106 	timeout_fmt = (timeout_val * 1000) | (timeout_act << 24);
107 	pretimeout_fmt = (pretimeout_val * 1000) | (pretimeout_act << 24);
108 
109 	memset(setup, 0, sizeof(*setup));
110 	setup->timeout = cpu_to_le32(timeout_fmt);
111 	setup->pretimeout = cpu_to_le32(pretimeout_fmt);
112 
113 	return nct6694_write_msg(data->nct6694, &cmd_hd, setup);
114 }
115 
116 static int nct6694_wdt_start(struct watchdog_device *wdev)
117 {
118 	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
119 	int ret;
120 
121 	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
122 				  wdev->pretimeout, NCT6694_ACTION_GPO);
123 	if (ret)
124 		return ret;
125 
126 	dev_dbg(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
127 		data->wdev_idx, wdev->timeout, wdev->pretimeout);
128 
129 	return ret;
130 }
131 
132 static int nct6694_wdt_stop(struct watchdog_device *wdev)
133 {
134 	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
135 	struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
136 	const struct nct6694_cmd_header cmd_hd = {
137 		.mod = NCT6694_WDT_MOD,
138 		.cmd = NCT6694_WDT_COMMAND,
139 		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
140 		.len = cpu_to_le16(sizeof(*cmd))
141 	};
142 
143 	memcpy(&cmd->wdt_cmd, "WDTC", 4);
144 	cmd->reserved = 0;
145 
146 	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
147 }
148 
149 static int nct6694_wdt_ping(struct watchdog_device *wdev)
150 {
151 	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
152 	struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
153 	const struct nct6694_cmd_header cmd_hd = {
154 		.mod = NCT6694_WDT_MOD,
155 		.cmd = NCT6694_WDT_COMMAND,
156 		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
157 		.len = cpu_to_le16(sizeof(*cmd))
158 	};
159 
160 	memcpy(&cmd->wdt_cmd, "WDTS", 4);
161 	cmd->reserved = 0;
162 
163 	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
164 }
165 
166 static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
167 				   unsigned int new_timeout)
168 {
169 	int ret;
170 
171 	ret = nct6694_wdt_setting(wdev, new_timeout, NCT6694_ACTION_GPO,
172 				  wdev->pretimeout, NCT6694_ACTION_GPO);
173 	if (ret)
174 		return ret;
175 
176 	wdev->timeout = new_timeout;
177 
178 	return 0;
179 }
180 
181 static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
182 				      unsigned int new_pretimeout)
183 {
184 	int ret;
185 
186 	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
187 				  new_pretimeout, NCT6694_ACTION_GPO);
188 	if (ret)
189 		return ret;
190 
191 	wdev->pretimeout = new_pretimeout;
192 
193 	return 0;
194 }
195 
196 static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
197 {
198 	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
199 	struct nct6694_wdt_setup *setup = &data->msg->setup;
200 	const struct nct6694_cmd_header cmd_hd = {
201 		.mod = NCT6694_WDT_MOD,
202 		.cmd = NCT6694_WDT_SETUP,
203 		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
204 		.len = cpu_to_le16(sizeof(*setup))
205 	};
206 	unsigned int timeleft_ms;
207 	int ret;
208 
209 	ret = nct6694_read_msg(data->nct6694, &cmd_hd, setup);
210 	if (ret)
211 		return 0;
212 
213 	timeleft_ms = le32_to_cpu(setup->countdown);
214 
215 	return timeleft_ms / 1000;
216 }
217 
218 static const struct watchdog_info nct6694_wdt_info = {
219 	.options = WDIOF_SETTIMEOUT	|
220 		   WDIOF_KEEPALIVEPING	|
221 		   WDIOF_MAGICCLOSE	|
222 		   WDIOF_PRETIMEOUT,
223 	.identity = DEVICE_NAME,
224 };
225 
226 static const struct watchdog_ops nct6694_wdt_ops = {
227 	.owner = THIS_MODULE,
228 	.start = nct6694_wdt_start,
229 	.stop = nct6694_wdt_stop,
230 	.set_timeout = nct6694_wdt_set_timeout,
231 	.set_pretimeout = nct6694_wdt_set_pretimeout,
232 	.get_timeleft = nct6694_wdt_get_time,
233 	.ping = nct6694_wdt_ping,
234 };
235 
236 static void nct6694_wdt_ida_free(void *d)
237 {
238 	struct nct6694_wdt_data *data = d;
239 	struct nct6694 *nct6694 = data->nct6694;
240 
241 	ida_free(&nct6694->wdt_ida, data->wdev_idx);
242 }
243 
244 static int nct6694_wdt_probe(struct platform_device *pdev)
245 {
246 	struct device *dev = &pdev->dev;
247 	struct nct6694 *nct6694 = dev_get_drvdata(dev->parent);
248 	struct nct6694_wdt_data *data;
249 	struct watchdog_device *wdev;
250 	int ret;
251 
252 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
253 	if (!data)
254 		return -ENOMEM;
255 
256 	data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg),
257 				 GFP_KERNEL);
258 	if (!data->msg)
259 		return -ENOMEM;
260 
261 	data->dev = dev;
262 	data->nct6694 = nct6694;
263 
264 	ret = ida_alloc(&nct6694->wdt_ida, GFP_KERNEL);
265 	if (ret < 0)
266 		return ret;
267 	data->wdev_idx = ret;
268 
269 	ret = devm_add_action_or_reset(dev, nct6694_wdt_ida_free, data);
270 	if (ret)
271 		return ret;
272 
273 	wdev = &data->wdev;
274 	wdev->info = &nct6694_wdt_info;
275 	wdev->ops = &nct6694_wdt_ops;
276 	wdev->timeout = timeout[data->wdev_idx];
277 	wdev->pretimeout = pretimeout[data->wdev_idx];
278 	if (timeout[data->wdev_idx] < pretimeout[data->wdev_idx]) {
279 		dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
280 		wdev->pretimeout = 0;
281 	}
282 
283 	wdev->min_timeout = 1;
284 	wdev->max_timeout = 255;
285 
286 	platform_set_drvdata(pdev, data);
287 
288 	watchdog_set_drvdata(&data->wdev, data);
289 	watchdog_set_nowayout(&data->wdev, nowayout);
290 	watchdog_stop_on_reboot(&data->wdev);
291 
292 	return devm_watchdog_register_device(dev, &data->wdev);
293 }
294 
295 static struct platform_driver nct6694_wdt_driver = {
296 	.driver = {
297 		.name	= DEVICE_NAME,
298 	},
299 	.probe		= nct6694_wdt_probe,
300 };
301 
302 module_platform_driver(nct6694_wdt_driver);
303 
304 MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
305 MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
306 MODULE_LICENSE("GPL");
307 MODULE_ALIAS("platform:nct6694-wdt");
308