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