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
38static unsigned int timeout[NCT6694_WDT_MAX_DEVS] = {
39 [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_TIMEOUT
40};
41module_param_array(timeout, int, NULL, 0644);
42MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
43
44static unsigned int pretimeout[NCT6694_WDT_MAX_DEVS] = {
45 [0 ... (NCT6694_WDT_MAX_DEVS - 1)] = NCT6694_DEFAULT_PRETIMEOUT
46};
47module_param_array(pretimeout, int, NULL, 0644);
48MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds");
49
50static bool nowayout = WATCHDOG_NOWAYOUT;
51module_param(nowayout, bool, 0);
52MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
53 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
54
55enum {
56 NCT6694_ACTION_NONE = 0,
57 NCT6694_ACTION_SIRQ,
58 NCT6694_ACTION_GPO,
59};
60
61struct __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
71struct __packed nct6694_wdt_cmd {
72 __le32 wdt_cmd;
73 __le32 reserved;
74};
75
76union __packed nct6694_wdt_msg {
77 struct nct6694_wdt_setup setup;
78 struct nct6694_wdt_cmd cmd;
79};
80
81struct 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
89static 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(wdd: 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(nct6694: data->nct6694, cmd_hd: &cmd_hd, buf: setup);
114}
115
116static int nct6694_wdt_start(struct watchdog_device *wdev)
117{
118 struct nct6694_wdt_data *data = watchdog_get_drvdata(wdd: wdev);
119 int ret;
120
121 ret = nct6694_wdt_setting(wdev, timeout_val: wdev->timeout, timeout_act: NCT6694_ACTION_GPO,
122 pretimeout_val: wdev->pretimeout, pretimeout_act: 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
132static int nct6694_wdt_stop(struct watchdog_device *wdev)
133{
134 struct nct6694_wdt_data *data = watchdog_get_drvdata(wdd: 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(nct6694: data->nct6694, cmd_hd: &cmd_hd, buf: cmd);
147}
148
149static int nct6694_wdt_ping(struct watchdog_device *wdev)
150{
151 struct nct6694_wdt_data *data = watchdog_get_drvdata(wdd: 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(nct6694: data->nct6694, cmd_hd: &cmd_hd, buf: cmd);
164}
165
166static 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, timeout_val: new_timeout, timeout_act: NCT6694_ACTION_GPO,
172 pretimeout_val: wdev->pretimeout, pretimeout_act: NCT6694_ACTION_GPO);
173 if (ret)
174 return ret;
175
176 wdev->timeout = new_timeout;
177
178 return 0;
179}
180
181static 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, timeout_val: wdev->timeout, timeout_act: NCT6694_ACTION_GPO,
187 pretimeout_val: new_pretimeout, pretimeout_act: NCT6694_ACTION_GPO);
188 if (ret)
189 return ret;
190
191 wdev->pretimeout = new_pretimeout;
192
193 return 0;
194}
195
196static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
197{
198 struct nct6694_wdt_data *data = watchdog_get_drvdata(wdd: 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(nct6694: data->nct6694, cmd_hd: &cmd_hd, buf: setup);
210 if (ret)
211 return 0;
212
213 timeleft_ms = le32_to_cpu(setup->countdown);
214
215 return timeleft_ms / 1000;
216}
217
218static 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
226static 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
236static 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, id: data->wdev_idx);
242}
243
244static int nct6694_wdt_probe(struct platform_device *pdev)
245{
246 struct device *dev = &pdev->dev;
247 struct nct6694 *nct6694 = dev_get_drvdata(dev: dev->parent);
248 struct nct6694_wdt_data *data;
249 struct watchdog_device *wdev;
250 int ret;
251
252 data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL);
253 if (!data)
254 return -ENOMEM;
255
256 data->msg = devm_kzalloc(dev, size: 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(ida: &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(wdd: &data->wdev, data);
289 watchdog_set_nowayout(wdd: &data->wdev, nowayout);
290 watchdog_stop_on_reboot(wdd: &data->wdev);
291
292 return devm_watchdog_register_device(dev, &data->wdev);
293}
294
295static struct platform_driver nct6694_wdt_driver = {
296 .driver = {
297 .name = DEVICE_NAME,
298 },
299 .probe = nct6694_wdt_probe,
300};
301
302module_platform_driver(nct6694_wdt_driver);
303
304MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
305MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
306MODULE_LICENSE("GPL");
307MODULE_ALIAS("platform:nct6694-wdt");
308

source code of linux/drivers/watchdog/nct6694_wdt.c