1// SPDX-License-Identifier: GPL-2.0+
2/*
3 *	exar_wdt.c - Driver for the watchdog present in some
4 *		     Exar/MaxLinear UART chips like the XR28V38x.
5 *
6 *	(c) Copyright 2022 D. M��ller <d.mueller@elsoft.ch>.
7 *
8 */
9
10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
11
12#include <linux/io.h>
13#include <linux/list.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/slab.h>
17#include <linux/watchdog.h>
18
19#define DRV_NAME	"exar_wdt"
20
21static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };
22static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };
23#define EXAR_EXIT_KEY	0xAA
24
25#define EXAR_LDN	0x07
26#define EXAR_DID	0x20
27#define EXAR_VID	0x23
28#define EXAR_WDT	0x26
29#define EXAR_ACT	0x30
30#define EXAR_RTBASE	0x60
31
32#define EXAR_WDT_LDEV	0x08
33
34#define EXAR_VEN_ID	0x13A8
35#define EXAR_DEV_382	0x0382
36#define EXAR_DEV_384	0x0384
37
38/* WDT runtime registers */
39#define WDT_CTRL	0x00
40#define WDT_VAL		0x01
41
42#define WDT_UNITS_10MS	0x0	/* the 10 millisec unit of the HW is not used */
43#define WDT_UNITS_SEC	0x2
44#define WDT_UNITS_MIN	0x4
45
46/* default WDT control for WDTOUT signal activ / rearm by read */
47#define EXAR_WDT_DEF_CONF	0
48
49struct wdt_pdev_node {
50	struct list_head list;
51	struct platform_device *pdev;
52	const char name[16];
53};
54
55struct wdt_priv {
56	/* the lock for WDT io operations */
57	spinlock_t io_lock;
58	struct resource wdt_res;
59	struct watchdog_device wdt_dev;
60	unsigned short did;
61	unsigned short config_port;
62	unsigned char enter_key;
63	unsigned char unit;
64	unsigned char timeout;
65};
66
67#define WATCHDOG_TIMEOUT 60
68
69static int timeout = WATCHDOG_TIMEOUT;
70module_param(timeout, int, 0);
71MODULE_PARM_DESC(timeout,
72		 "Watchdog timeout in seconds. 1<=timeout<=15300, default="
73		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
74
75static bool nowayout = WATCHDOG_NOWAYOUT;
76module_param(nowayout, bool, 0);
77MODULE_PARM_DESC(nowayout,
78		 "Watchdog cannot be stopped once started (default="
79		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
80
81static int exar_sio_enter(const unsigned short config_port,
82			  const unsigned char key)
83{
84	if (!request_muxed_region(config_port, 2, DRV_NAME))
85		return -EBUSY;
86
87	/* write the ENTER-KEY twice */
88	outb(key, config_port);
89	outb(key, config_port);
90
91	return 0;
92}
93
94static void exar_sio_exit(const unsigned short config_port)
95{
96	outb(EXAR_EXIT_KEY, config_port);
97	release_region(config_port, 2);
98}
99
100static unsigned char exar_sio_read(const unsigned short config_port,
101				   const unsigned char reg)
102{
103	outb(reg, config_port);
104	return inb(config_port + 1);
105}
106
107static void exar_sio_write(const unsigned short config_port,
108			   const unsigned char reg, const unsigned char val)
109{
110	outb(reg, config_port);
111	outb(val, config_port + 1);
112}
113
114static unsigned short exar_sio_read16(const unsigned short config_port,
115				      const unsigned char reg)
116{
117	unsigned char msb, lsb;
118
119	msb = exar_sio_read(config_port, reg);
120	lsb = exar_sio_read(config_port, reg + 1);
121
122	return (msb << 8) | lsb;
123}
124
125static void exar_sio_select_wdt(const unsigned short config_port)
126{
127	exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);
128}
129
130static void exar_wdt_arm(const struct wdt_priv *priv)
131{
132	unsigned short rt_base = priv->wdt_res.start;
133
134	/* write timeout value twice to arm watchdog */
135	outb(priv->timeout, rt_base + WDT_VAL);
136	outb(priv->timeout, rt_base + WDT_VAL);
137}
138
139static void exar_wdt_disarm(const struct wdt_priv *priv)
140{
141	unsigned short rt_base = priv->wdt_res.start;
142
143	/*
144	 * use two accesses with different values to make sure
145	 * that a combination of a previous single access and
146	 * the ones below with the same value are not falsely
147	 * interpreted as "arm watchdog"
148	 */
149	outb(0xFF, rt_base + WDT_VAL);
150	outb(0, rt_base + WDT_VAL);
151}
152
153static int exar_wdt_start(struct watchdog_device *wdog)
154{
155	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
156	unsigned short rt_base = priv->wdt_res.start;
157
158	spin_lock(&priv->io_lock);
159
160	exar_wdt_disarm(priv);
161	outb(priv->unit, rt_base + WDT_CTRL);
162	exar_wdt_arm(priv);
163
164	spin_unlock(&priv->io_lock);
165	return 0;
166}
167
168static int exar_wdt_stop(struct watchdog_device *wdog)
169{
170	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
171
172	spin_lock(&priv->io_lock);
173
174	exar_wdt_disarm(priv);
175
176	spin_unlock(&priv->io_lock);
177	return 0;
178}
179
180static int exar_wdt_keepalive(struct watchdog_device *wdog)
181{
182	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
183	unsigned short rt_base = priv->wdt_res.start;
184
185	spin_lock(&priv->io_lock);
186
187	/* reading the WDT_VAL reg will feed the watchdog */
188	inb(rt_base + WDT_VAL);
189
190	spin_unlock(&priv->io_lock);
191	return 0;
192}
193
194static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
195{
196	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
197	bool unit_min = false;
198
199	/*
200	 * if new timeout is bigger then 255 seconds, change the
201	 * unit to minutes and round the timeout up to the next whole minute
202	 */
203	if (t > 255) {
204		unit_min = true;
205		t = DIV_ROUND_UP(t, 60);
206	}
207
208	/* save for later use in exar_wdt_start() */
209	priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;
210	priv->timeout = t;
211
212	wdog->timeout = unit_min ? t * 60 : t;
213
214	if (watchdog_hw_running(wdog))
215		exar_wdt_start(wdog);
216
217	return 0;
218}
219
220static const struct watchdog_info exar_wdt_info = {
221	.options	= WDIOF_KEEPALIVEPING |
222			  WDIOF_SETTIMEOUT |
223			  WDIOF_MAGICCLOSE,
224	.identity	= "Exar/MaxLinear XR28V38x Watchdog",
225};
226
227static const struct watchdog_ops exar_wdt_ops = {
228	.owner		= THIS_MODULE,
229	.start		= exar_wdt_start,
230	.stop		= exar_wdt_stop,
231	.ping		= exar_wdt_keepalive,
232	.set_timeout	= exar_wdt_set_timeout,
233};
234
235static int exar_wdt_config(struct watchdog_device *wdog,
236			   const unsigned char conf)
237{
238	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
239	int ret;
240
241	ret = exar_sio_enter(priv->config_port, priv->enter_key);
242	if (ret)
243		return ret;
244
245	exar_sio_select_wdt(priv->config_port);
246	exar_sio_write(priv->config_port, EXAR_WDT, conf);
247
248	exar_sio_exit(priv->config_port);
249
250	return 0;
251}
252
253static int __init exar_wdt_probe(struct platform_device *pdev)
254{
255	struct device *dev = &pdev->dev;
256	struct wdt_priv *priv = dev->platform_data;
257	struct watchdog_device *wdt_dev = &priv->wdt_dev;
258	struct resource *res;
259	int ret;
260
261	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
262	if (!res)
263		return -ENXIO;
264
265	spin_lock_init(&priv->io_lock);
266
267	wdt_dev->info = &exar_wdt_info;
268	wdt_dev->ops = &exar_wdt_ops;
269	wdt_dev->min_timeout = 1;
270	wdt_dev->max_timeout = 255 * 60;
271
272	watchdog_init_timeout(wdt_dev, timeout, NULL);
273	watchdog_set_nowayout(wdt_dev, nowayout);
274	watchdog_stop_on_reboot(wdt_dev);
275	watchdog_stop_on_unregister(wdt_dev);
276	watchdog_set_drvdata(wdt_dev, priv);
277
278	ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);
279	if (ret)
280		return ret;
281
282	exar_wdt_set_timeout(wdt_dev, timeout);
283	/* Make sure that the watchdog is not running */
284	exar_wdt_stop(wdt_dev);
285
286	ret = devm_watchdog_register_device(dev, wdt_dev);
287	if (ret)
288		return ret;
289
290	dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",
291		 priv->did, timeout, nowayout);
292
293	return 0;
294}
295
296static unsigned short __init exar_detect(const unsigned short config_port,
297					 const unsigned char key,
298					 unsigned short *rt_base)
299{
300	int ret;
301	unsigned short base = 0;
302	unsigned short vid, did;
303
304	ret = exar_sio_enter(config_port, key);
305	if (ret)
306		return 0;
307
308	vid = exar_sio_read16(config_port, EXAR_VID);
309	did = exar_sio_read16(config_port, EXAR_DID);
310
311	/* check for the vendor and device IDs we currently know about */
312	if (vid == EXAR_VEN_ID &&
313	    (did == EXAR_DEV_382 ||
314	     did == EXAR_DEV_384)) {
315		exar_sio_select_wdt(config_port);
316		/* is device active? */
317		if (exar_sio_read(config_port, EXAR_ACT) == 0x01)
318			base = exar_sio_read16(config_port, EXAR_RTBASE);
319	}
320
321	exar_sio_exit(config_port);
322
323	if (base) {
324		pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",
325			 did, config_port, base);
326		*rt_base = base;
327		return did;
328	}
329
330	return 0;
331}
332
333static struct platform_driver exar_wdt_driver = {
334	.driver = {
335		.name = DRV_NAME,
336	},
337};
338
339static LIST_HEAD(pdev_list);
340
341static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)
342{
343	struct wdt_pdev_node *n;
344
345	n = kzalloc(sizeof(*n), GFP_KERNEL);
346	if (!n)
347		return -ENOMEM;
348
349	INIT_LIST_HEAD(&n->list);
350
351	scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);
352	priv->wdt_res.name = n->name;
353
354	n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,
355						    &priv->wdt_res, 1,
356						    priv, sizeof(*priv));
357	if (IS_ERR(n->pdev)) {
358		int err = PTR_ERR(n->pdev);
359
360		kfree(n);
361		return err;
362	}
363
364	list_add_tail(&n->list, &pdev_list);
365
366	return 0;
367}
368
369static void exar_wdt_unregister(void)
370{
371	struct wdt_pdev_node *n, *t;
372
373	list_for_each_entry_safe(n, t, &pdev_list, list) {
374		platform_device_unregister(n->pdev);
375		list_del(&n->list);
376		kfree(n);
377	}
378}
379
380static int __init exar_wdt_init(void)
381{
382	int ret, i, j, idx = 0;
383
384	/* search for active Exar watchdogs on all possible locations */
385	for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {
386		for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {
387			unsigned short did, rt_base = 0;
388
389			did = exar_detect(sio_config_ports[i],
390					  sio_enter_keys[j],
391					  &rt_base);
392
393			if (did) {
394				struct wdt_priv priv = {
395					.wdt_res = DEFINE_RES_IO(rt_base, 2),
396					.did = did,
397					.config_port = sio_config_ports[i],
398					.enter_key = sio_enter_keys[j],
399				};
400
401				ret = exar_wdt_register(&priv, idx);
402				if (!ret)
403					idx++;
404			}
405		}
406	}
407
408	if (!idx)
409		return -ENODEV;
410
411	ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);
412	if (ret)
413		exar_wdt_unregister();
414
415	return ret;
416}
417
418static void __exit exar_wdt_exit(void)
419{
420	exar_wdt_unregister();
421	platform_driver_unregister(&exar_wdt_driver);
422}
423
424module_init(exar_wdt_init);
425module_exit(exar_wdt_exit);
426
427MODULE_AUTHOR("David M��ller <d.mueller@elsoft.ch>");
428MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");
429MODULE_LICENSE("GPL");
430