xref: /openbmc/linux/drivers/watchdog/exar_wdt.c (revision 82ebbe65)
181126222SDavid Müller // SPDX-License-Identifier: GPL-2.0+
281126222SDavid Müller /*
381126222SDavid Müller  *	exar_wdt.c - Driver for the watchdog present in some
481126222SDavid Müller  *		     Exar/MaxLinear UART chips like the XR28V38x.
581126222SDavid Müller  *
681126222SDavid Müller  *	(c) Copyright 2022 D. Müller <d.mueller@elsoft.ch>.
781126222SDavid Müller  *
881126222SDavid Müller  */
981126222SDavid Müller 
1081126222SDavid Müller #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1181126222SDavid Müller 
1281126222SDavid Müller #include <linux/io.h>
1381126222SDavid Müller #include <linux/list.h>
1481126222SDavid Müller #include <linux/module.h>
1581126222SDavid Müller #include <linux/platform_device.h>
1681126222SDavid Müller #include <linux/slab.h>
1781126222SDavid Müller #include <linux/watchdog.h>
1881126222SDavid Müller 
1981126222SDavid Müller #define DRV_NAME	"exar_wdt"
2081126222SDavid Müller 
2181126222SDavid Müller static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };
2281126222SDavid Müller static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };
2381126222SDavid Müller #define EXAR_EXIT_KEY	0xAA
2481126222SDavid Müller 
2581126222SDavid Müller #define EXAR_LDN	0x07
2681126222SDavid Müller #define EXAR_DID	0x20
2781126222SDavid Müller #define EXAR_VID	0x23
2881126222SDavid Müller #define EXAR_WDT	0x26
2981126222SDavid Müller #define EXAR_ACT	0x30
3081126222SDavid Müller #define EXAR_RTBASE	0x60
3181126222SDavid Müller 
3281126222SDavid Müller #define EXAR_WDT_LDEV	0x08
3381126222SDavid Müller 
3481126222SDavid Müller #define EXAR_VEN_ID	0x13A8
3581126222SDavid Müller #define EXAR_DEV_382	0x0382
3681126222SDavid Müller #define EXAR_DEV_384	0x0384
3781126222SDavid Müller 
3881126222SDavid Müller /* WDT runtime registers */
3981126222SDavid Müller #define WDT_CTRL	0x00
4081126222SDavid Müller #define WDT_VAL		0x01
4181126222SDavid Müller 
4281126222SDavid Müller #define WDT_UNITS_10MS	0x0	/* the 10 millisec unit of the HW is not used */
4381126222SDavid Müller #define WDT_UNITS_SEC	0x2
4481126222SDavid Müller #define WDT_UNITS_MIN	0x4
4581126222SDavid Müller 
4681126222SDavid Müller /* default WDT control for WDTOUT signal activ / rearm by read */
4781126222SDavid Müller #define EXAR_WDT_DEF_CONF	0
4881126222SDavid Müller 
4981126222SDavid Müller struct wdt_pdev_node {
5081126222SDavid Müller 	struct list_head list;
5181126222SDavid Müller 	struct platform_device *pdev;
5281126222SDavid Müller 	const char name[16];
5381126222SDavid Müller };
5481126222SDavid Müller 
5581126222SDavid Müller struct wdt_priv {
5681126222SDavid Müller 	/* the lock for WDT io operations */
5781126222SDavid Müller 	spinlock_t io_lock;
5881126222SDavid Müller 	struct resource wdt_res;
5981126222SDavid Müller 	struct watchdog_device wdt_dev;
6081126222SDavid Müller 	unsigned short did;
6181126222SDavid Müller 	unsigned short config_port;
6281126222SDavid Müller 	unsigned char enter_key;
6381126222SDavid Müller 	unsigned char unit;
6481126222SDavid Müller 	unsigned char timeout;
6581126222SDavid Müller };
6681126222SDavid Müller 
6781126222SDavid Müller #define WATCHDOG_TIMEOUT 60
6881126222SDavid Müller 
6981126222SDavid Müller static int timeout = WATCHDOG_TIMEOUT;
7081126222SDavid Müller module_param(timeout, int, 0);
7181126222SDavid Müller MODULE_PARM_DESC(timeout,
7281126222SDavid Müller 		 "Watchdog timeout in seconds. 1<=timeout<=15300, default="
7381126222SDavid Müller 		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
7481126222SDavid Müller 
7581126222SDavid Müller static bool nowayout = WATCHDOG_NOWAYOUT;
7681126222SDavid Müller module_param(nowayout, bool, 0);
7781126222SDavid Müller MODULE_PARM_DESC(nowayout,
7881126222SDavid Müller 		 "Watchdog cannot be stopped once started (default="
7981126222SDavid Müller 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
8081126222SDavid Müller 
exar_sio_enter(const unsigned short config_port,const unsigned char key)8181126222SDavid Müller static int exar_sio_enter(const unsigned short config_port,
8281126222SDavid Müller 			  const unsigned char key)
8381126222SDavid Müller {
8481126222SDavid Müller 	if (!request_muxed_region(config_port, 2, DRV_NAME))
8581126222SDavid Müller 		return -EBUSY;
8681126222SDavid Müller 
8781126222SDavid Müller 	/* write the ENTER-KEY twice */
8881126222SDavid Müller 	outb(key, config_port);
8981126222SDavid Müller 	outb(key, config_port);
9081126222SDavid Müller 
9181126222SDavid Müller 	return 0;
9281126222SDavid Müller }
9381126222SDavid Müller 
exar_sio_exit(const unsigned short config_port)9481126222SDavid Müller static void exar_sio_exit(const unsigned short config_port)
9581126222SDavid Müller {
9681126222SDavid Müller 	outb(EXAR_EXIT_KEY, config_port);
9781126222SDavid Müller 	release_region(config_port, 2);
9881126222SDavid Müller }
9981126222SDavid Müller 
exar_sio_read(const unsigned short config_port,const unsigned char reg)10081126222SDavid Müller static unsigned char exar_sio_read(const unsigned short config_port,
10181126222SDavid Müller 				   const unsigned char reg)
10281126222SDavid Müller {
10381126222SDavid Müller 	outb(reg, config_port);
10481126222SDavid Müller 	return inb(config_port + 1);
10581126222SDavid Müller }
10681126222SDavid Müller 
exar_sio_write(const unsigned short config_port,const unsigned char reg,const unsigned char val)10781126222SDavid Müller static void exar_sio_write(const unsigned short config_port,
10881126222SDavid Müller 			   const unsigned char reg, const unsigned char val)
10981126222SDavid Müller {
11081126222SDavid Müller 	outb(reg, config_port);
11181126222SDavid Müller 	outb(val, config_port + 1);
11281126222SDavid Müller }
11381126222SDavid Müller 
exar_sio_read16(const unsigned short config_port,const unsigned char reg)11481126222SDavid Müller static unsigned short exar_sio_read16(const unsigned short config_port,
11581126222SDavid Müller 				      const unsigned char reg)
11681126222SDavid Müller {
11781126222SDavid Müller 	unsigned char msb, lsb;
11881126222SDavid Müller 
11981126222SDavid Müller 	msb = exar_sio_read(config_port, reg);
12081126222SDavid Müller 	lsb = exar_sio_read(config_port, reg + 1);
12181126222SDavid Müller 
12281126222SDavid Müller 	return (msb << 8) | lsb;
12381126222SDavid Müller }
12481126222SDavid Müller 
exar_sio_select_wdt(const unsigned short config_port)12581126222SDavid Müller static void exar_sio_select_wdt(const unsigned short config_port)
12681126222SDavid Müller {
12781126222SDavid Müller 	exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);
12881126222SDavid Müller }
12981126222SDavid Müller 
exar_wdt_arm(const struct wdt_priv * priv)13081126222SDavid Müller static void exar_wdt_arm(const struct wdt_priv *priv)
13181126222SDavid Müller {
13281126222SDavid Müller 	unsigned short rt_base = priv->wdt_res.start;
13381126222SDavid Müller 
13481126222SDavid Müller 	/* write timeout value twice to arm watchdog */
13581126222SDavid Müller 	outb(priv->timeout, rt_base + WDT_VAL);
13681126222SDavid Müller 	outb(priv->timeout, rt_base + WDT_VAL);
13781126222SDavid Müller }
13881126222SDavid Müller 
exar_wdt_disarm(const struct wdt_priv * priv)13981126222SDavid Müller static void exar_wdt_disarm(const struct wdt_priv *priv)
14081126222SDavid Müller {
14181126222SDavid Müller 	unsigned short rt_base = priv->wdt_res.start;
14281126222SDavid Müller 
14381126222SDavid Müller 	/*
14481126222SDavid Müller 	 * use two accesses with different values to make sure
14581126222SDavid Müller 	 * that a combination of a previous single access and
14681126222SDavid Müller 	 * the ones below with the same value are not falsely
14781126222SDavid Müller 	 * interpreted as "arm watchdog"
14881126222SDavid Müller 	 */
14981126222SDavid Müller 	outb(0xFF, rt_base + WDT_VAL);
15081126222SDavid Müller 	outb(0, rt_base + WDT_VAL);
15181126222SDavid Müller }
15281126222SDavid Müller 
exar_wdt_start(struct watchdog_device * wdog)15381126222SDavid Müller static int exar_wdt_start(struct watchdog_device *wdog)
15481126222SDavid Müller {
15581126222SDavid Müller 	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
15681126222SDavid Müller 	unsigned short rt_base = priv->wdt_res.start;
15781126222SDavid Müller 
15881126222SDavid Müller 	spin_lock(&priv->io_lock);
15981126222SDavid Müller 
16081126222SDavid Müller 	exar_wdt_disarm(priv);
16181126222SDavid Müller 	outb(priv->unit, rt_base + WDT_CTRL);
16281126222SDavid Müller 	exar_wdt_arm(priv);
16381126222SDavid Müller 
16481126222SDavid Müller 	spin_unlock(&priv->io_lock);
16581126222SDavid Müller 	return 0;
16681126222SDavid Müller }
16781126222SDavid Müller 
exar_wdt_stop(struct watchdog_device * wdog)16881126222SDavid Müller static int exar_wdt_stop(struct watchdog_device *wdog)
16981126222SDavid Müller {
17081126222SDavid Müller 	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
17181126222SDavid Müller 
17281126222SDavid Müller 	spin_lock(&priv->io_lock);
17381126222SDavid Müller 
17481126222SDavid Müller 	exar_wdt_disarm(priv);
17581126222SDavid Müller 
17681126222SDavid Müller 	spin_unlock(&priv->io_lock);
17781126222SDavid Müller 	return 0;
17881126222SDavid Müller }
17981126222SDavid Müller 
exar_wdt_keepalive(struct watchdog_device * wdog)18081126222SDavid Müller static int exar_wdt_keepalive(struct watchdog_device *wdog)
18181126222SDavid Müller {
18281126222SDavid Müller 	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
18381126222SDavid Müller 	unsigned short rt_base = priv->wdt_res.start;
18481126222SDavid Müller 
18581126222SDavid Müller 	spin_lock(&priv->io_lock);
18681126222SDavid Müller 
18781126222SDavid Müller 	/* reading the WDT_VAL reg will feed the watchdog */
18881126222SDavid Müller 	inb(rt_base + WDT_VAL);
18981126222SDavid Müller 
19081126222SDavid Müller 	spin_unlock(&priv->io_lock);
19181126222SDavid Müller 	return 0;
19281126222SDavid Müller }
19381126222SDavid Müller 
exar_wdt_set_timeout(struct watchdog_device * wdog,unsigned int t)19481126222SDavid Müller static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
19581126222SDavid Müller {
19681126222SDavid Müller 	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
19781126222SDavid Müller 	bool unit_min = false;
19881126222SDavid Müller 
19981126222SDavid Müller 	/*
20081126222SDavid Müller 	 * if new timeout is bigger then 255 seconds, change the
20181126222SDavid Müller 	 * unit to minutes and round the timeout up to the next whole minute
20281126222SDavid Müller 	 */
20381126222SDavid Müller 	if (t > 255) {
20481126222SDavid Müller 		unit_min = true;
20581126222SDavid Müller 		t = DIV_ROUND_UP(t, 60);
20681126222SDavid Müller 	}
20781126222SDavid Müller 
20881126222SDavid Müller 	/* save for later use in exar_wdt_start() */
20981126222SDavid Müller 	priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;
21081126222SDavid Müller 	priv->timeout = t;
21181126222SDavid Müller 
21281126222SDavid Müller 	wdog->timeout = unit_min ? t * 60 : t;
21381126222SDavid Müller 
21481126222SDavid Müller 	if (watchdog_hw_running(wdog))
21581126222SDavid Müller 		exar_wdt_start(wdog);
21681126222SDavid Müller 
21781126222SDavid Müller 	return 0;
21881126222SDavid Müller }
21981126222SDavid Müller 
22081126222SDavid Müller static const struct watchdog_info exar_wdt_info = {
22181126222SDavid Müller 	.options	= WDIOF_KEEPALIVEPING |
22281126222SDavid Müller 			  WDIOF_SETTIMEOUT |
22381126222SDavid Müller 			  WDIOF_MAGICCLOSE,
22481126222SDavid Müller 	.identity	= "Exar/MaxLinear XR28V38x Watchdog",
22581126222SDavid Müller };
22681126222SDavid Müller 
22781126222SDavid Müller static const struct watchdog_ops exar_wdt_ops = {
22881126222SDavid Müller 	.owner		= THIS_MODULE,
22981126222SDavid Müller 	.start		= exar_wdt_start,
23081126222SDavid Müller 	.stop		= exar_wdt_stop,
23181126222SDavid Müller 	.ping		= exar_wdt_keepalive,
23281126222SDavid Müller 	.set_timeout	= exar_wdt_set_timeout,
23381126222SDavid Müller };
23481126222SDavid Müller 
exar_wdt_config(struct watchdog_device * wdog,const unsigned char conf)23581126222SDavid Müller static int exar_wdt_config(struct watchdog_device *wdog,
23681126222SDavid Müller 			   const unsigned char conf)
23781126222SDavid Müller {
23881126222SDavid Müller 	struct wdt_priv *priv = watchdog_get_drvdata(wdog);
23981126222SDavid Müller 	int ret;
24081126222SDavid Müller 
24181126222SDavid Müller 	ret = exar_sio_enter(priv->config_port, priv->enter_key);
24281126222SDavid Müller 	if (ret)
24381126222SDavid Müller 		return ret;
24481126222SDavid Müller 
24581126222SDavid Müller 	exar_sio_select_wdt(priv->config_port);
24681126222SDavid Müller 	exar_sio_write(priv->config_port, EXAR_WDT, conf);
24781126222SDavid Müller 
24881126222SDavid Müller 	exar_sio_exit(priv->config_port);
24981126222SDavid Müller 
25081126222SDavid Müller 	return 0;
25181126222SDavid Müller }
25281126222SDavid Müller 
exar_wdt_probe(struct platform_device * pdev)25381126222SDavid Müller static int __init exar_wdt_probe(struct platform_device *pdev)
25481126222SDavid Müller {
25581126222SDavid Müller 	struct device *dev = &pdev->dev;
25681126222SDavid Müller 	struct wdt_priv *priv = dev->platform_data;
25781126222SDavid Müller 	struct watchdog_device *wdt_dev = &priv->wdt_dev;
25881126222SDavid Müller 	struct resource *res;
25981126222SDavid Müller 	int ret;
26081126222SDavid Müller 
26181126222SDavid Müller 	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
26281126222SDavid Müller 	if (!res)
26381126222SDavid Müller 		return -ENXIO;
26481126222SDavid Müller 
26581126222SDavid Müller 	spin_lock_init(&priv->io_lock);
26681126222SDavid Müller 
26781126222SDavid Müller 	wdt_dev->info = &exar_wdt_info;
26881126222SDavid Müller 	wdt_dev->ops = &exar_wdt_ops;
26981126222SDavid Müller 	wdt_dev->min_timeout = 1;
27081126222SDavid Müller 	wdt_dev->max_timeout = 255 * 60;
27181126222SDavid Müller 
27281126222SDavid Müller 	watchdog_init_timeout(wdt_dev, timeout, NULL);
27381126222SDavid Müller 	watchdog_set_nowayout(wdt_dev, nowayout);
27481126222SDavid Müller 	watchdog_stop_on_reboot(wdt_dev);
27581126222SDavid Müller 	watchdog_stop_on_unregister(wdt_dev);
27681126222SDavid Müller 	watchdog_set_drvdata(wdt_dev, priv);
27781126222SDavid Müller 
27881126222SDavid Müller 	ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);
27981126222SDavid Müller 	if (ret)
28081126222SDavid Müller 		return ret;
28181126222SDavid Müller 
28281126222SDavid Müller 	exar_wdt_set_timeout(wdt_dev, timeout);
28381126222SDavid Müller 	/* Make sure that the watchdog is not running */
28481126222SDavid Müller 	exar_wdt_stop(wdt_dev);
28581126222SDavid Müller 
28681126222SDavid Müller 	ret = devm_watchdog_register_device(dev, wdt_dev);
28781126222SDavid Müller 	if (ret)
28881126222SDavid Müller 		return ret;
28981126222SDavid Müller 
29081126222SDavid Müller 	dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",
29181126222SDavid Müller 		 priv->did, timeout, nowayout);
29281126222SDavid Müller 
29381126222SDavid Müller 	return 0;
29481126222SDavid Müller }
29581126222SDavid Müller 
exar_detect(const unsigned short config_port,const unsigned char key,unsigned short * rt_base)29681126222SDavid Müller static unsigned short __init exar_detect(const unsigned short config_port,
29781126222SDavid Müller 					 const unsigned char key,
29881126222SDavid Müller 					 unsigned short *rt_base)
29981126222SDavid Müller {
30081126222SDavid Müller 	int ret;
30181126222SDavid Müller 	unsigned short base = 0;
30281126222SDavid Müller 	unsigned short vid, did;
30381126222SDavid Müller 
30481126222SDavid Müller 	ret = exar_sio_enter(config_port, key);
30581126222SDavid Müller 	if (ret)
30681126222SDavid Müller 		return 0;
30781126222SDavid Müller 
30881126222SDavid Müller 	vid = exar_sio_read16(config_port, EXAR_VID);
30981126222SDavid Müller 	did = exar_sio_read16(config_port, EXAR_DID);
31081126222SDavid Müller 
31181126222SDavid Müller 	/* check for the vendor and device IDs we currently know about */
31281126222SDavid Müller 	if (vid == EXAR_VEN_ID &&
31381126222SDavid Müller 	    (did == EXAR_DEV_382 ||
31481126222SDavid Müller 	     did == EXAR_DEV_384)) {
31581126222SDavid Müller 		exar_sio_select_wdt(config_port);
31681126222SDavid Müller 		/* is device active? */
31781126222SDavid Müller 		if (exar_sio_read(config_port, EXAR_ACT) == 0x01)
31881126222SDavid Müller 			base = exar_sio_read16(config_port, EXAR_RTBASE);
31981126222SDavid Müller 	}
32081126222SDavid Müller 
32181126222SDavid Müller 	exar_sio_exit(config_port);
32281126222SDavid Müller 
32381126222SDavid Müller 	if (base) {
32481126222SDavid Müller 		pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",
32581126222SDavid Müller 			 did, config_port, base);
32681126222SDavid Müller 		*rt_base = base;
32781126222SDavid Müller 		return did;
32881126222SDavid Müller 	}
32981126222SDavid Müller 
33081126222SDavid Müller 	return 0;
33181126222SDavid Müller }
33281126222SDavid Müller 
33381126222SDavid Müller static struct platform_driver exar_wdt_driver = {
33481126222SDavid Müller 	.driver = {
33581126222SDavid Müller 		.name = DRV_NAME,
33681126222SDavid Müller 	},
33781126222SDavid Müller };
33881126222SDavid Müller 
33981126222SDavid Müller static LIST_HEAD(pdev_list);
34081126222SDavid Müller 
exar_wdt_register(struct wdt_priv * priv,const int idx)34181126222SDavid Müller static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)
34281126222SDavid Müller {
34381126222SDavid Müller 	struct wdt_pdev_node *n;
34481126222SDavid Müller 
34581126222SDavid Müller 	n = kzalloc(sizeof(*n), GFP_KERNEL);
34681126222SDavid Müller 	if (!n)
34781126222SDavid Müller 		return -ENOMEM;
34881126222SDavid Müller 
34981126222SDavid Müller 	INIT_LIST_HEAD(&n->list);
35081126222SDavid Müller 
35181126222SDavid Müller 	scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);
35281126222SDavid Müller 	priv->wdt_res.name = n->name;
35381126222SDavid Müller 
35481126222SDavid Müller 	n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,
35581126222SDavid Müller 						    &priv->wdt_res, 1,
35681126222SDavid Müller 						    priv, sizeof(*priv));
35781126222SDavid Müller 	if (IS_ERR(n->pdev)) {
358*82ebbe65SManank Patel 		int err = PTR_ERR(n->pdev);
359*82ebbe65SManank Patel 
36081126222SDavid Müller 		kfree(n);
361*82ebbe65SManank Patel 		return err;
36281126222SDavid Müller 	}
36381126222SDavid Müller 
36481126222SDavid Müller 	list_add_tail(&n->list, &pdev_list);
36581126222SDavid Müller 
36681126222SDavid Müller 	return 0;
36781126222SDavid Müller }
36881126222SDavid Müller 
exar_wdt_unregister(void)36981126222SDavid Müller static void exar_wdt_unregister(void)
37081126222SDavid Müller {
37181126222SDavid Müller 	struct wdt_pdev_node *n, *t;
37281126222SDavid Müller 
37381126222SDavid Müller 	list_for_each_entry_safe(n, t, &pdev_list, list) {
37481126222SDavid Müller 		platform_device_unregister(n->pdev);
37581126222SDavid Müller 		list_del(&n->list);
37681126222SDavid Müller 		kfree(n);
37781126222SDavid Müller 	}
37881126222SDavid Müller }
37981126222SDavid Müller 
exar_wdt_init(void)38081126222SDavid Müller static int __init exar_wdt_init(void)
38181126222SDavid Müller {
38281126222SDavid Müller 	int ret, i, j, idx = 0;
38381126222SDavid Müller 
38481126222SDavid Müller 	/* search for active Exar watchdogs on all possible locations */
38581126222SDavid Müller 	for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {
38681126222SDavid Müller 		for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {
38781126222SDavid Müller 			unsigned short did, rt_base = 0;
38881126222SDavid Müller 
38981126222SDavid Müller 			did = exar_detect(sio_config_ports[i],
39081126222SDavid Müller 					  sio_enter_keys[j],
39181126222SDavid Müller 					  &rt_base);
39281126222SDavid Müller 
39381126222SDavid Müller 			if (did) {
39481126222SDavid Müller 				struct wdt_priv priv = {
39581126222SDavid Müller 					.wdt_res = DEFINE_RES_IO(rt_base, 2),
39681126222SDavid Müller 					.did = did,
39781126222SDavid Müller 					.config_port = sio_config_ports[i],
39881126222SDavid Müller 					.enter_key = sio_enter_keys[j],
39981126222SDavid Müller 				};
40081126222SDavid Müller 
40181126222SDavid Müller 				ret = exar_wdt_register(&priv, idx);
40281126222SDavid Müller 				if (!ret)
40381126222SDavid Müller 					idx++;
40481126222SDavid Müller 			}
40581126222SDavid Müller 		}
40681126222SDavid Müller 	}
40781126222SDavid Müller 
40881126222SDavid Müller 	if (!idx)
40981126222SDavid Müller 		return -ENODEV;
41081126222SDavid Müller 
41181126222SDavid Müller 	ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);
41281126222SDavid Müller 	if (ret)
41381126222SDavid Müller 		exar_wdt_unregister();
41481126222SDavid Müller 
41581126222SDavid Müller 	return ret;
41681126222SDavid Müller }
41781126222SDavid Müller 
exar_wdt_exit(void)41881126222SDavid Müller static void __exit exar_wdt_exit(void)
41981126222SDavid Müller {
42081126222SDavid Müller 	exar_wdt_unregister();
42181126222SDavid Müller 	platform_driver_unregister(&exar_wdt_driver);
42281126222SDavid Müller }
42381126222SDavid Müller 
42481126222SDavid Müller module_init(exar_wdt_init);
42581126222SDavid Müller module_exit(exar_wdt_exit);
42681126222SDavid Müller 
42781126222SDavid Müller MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>");
42881126222SDavid Müller MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");
42981126222SDavid Müller MODULE_LICENSE("GPL");
430