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