xref: /linux/drivers/watchdog/wm831x_wdt.c (revision 502a0106b2cc31940f690dc6693fddfd3b97cab5)
1*502a0106SMark Brown /*
2*502a0106SMark Brown  * Watchdog driver for the wm831x PMICs
3*502a0106SMark Brown  *
4*502a0106SMark Brown  * Copyright (C) 2009 Wolfson Microelectronics
5*502a0106SMark Brown  *
6*502a0106SMark Brown  * This program is free software; you can redistribute it and/or
7*502a0106SMark Brown  * modify it under the terms of the GNU General Public License
8*502a0106SMark Brown  * as published by the Free Software Foundation
9*502a0106SMark Brown  */
10*502a0106SMark Brown 
11*502a0106SMark Brown #include <linux/module.h>
12*502a0106SMark Brown #include <linux/moduleparam.h>
13*502a0106SMark Brown #include <linux/types.h>
14*502a0106SMark Brown #include <linux/kernel.h>
15*502a0106SMark Brown #include <linux/fs.h>
16*502a0106SMark Brown #include <linux/miscdevice.h>
17*502a0106SMark Brown #include <linux/platform_device.h>
18*502a0106SMark Brown #include <linux/watchdog.h>
19*502a0106SMark Brown #include <linux/uaccess.h>
20*502a0106SMark Brown #include <linux/gpio.h>
21*502a0106SMark Brown 
22*502a0106SMark Brown #include <linux/mfd/wm831x/core.h>
23*502a0106SMark Brown #include <linux/mfd/wm831x/pdata.h>
24*502a0106SMark Brown #include <linux/mfd/wm831x/watchdog.h>
25*502a0106SMark Brown 
26*502a0106SMark Brown static int nowayout = WATCHDOG_NOWAYOUT;
27*502a0106SMark Brown module_param(nowayout, int, 0);
28*502a0106SMark Brown MODULE_PARM_DESC(nowayout,
29*502a0106SMark Brown 		 "Watchdog cannot be stopped once started (default="
30*502a0106SMark Brown 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
31*502a0106SMark Brown 
32*502a0106SMark Brown static unsigned long wm831x_wdt_users;
33*502a0106SMark Brown static struct miscdevice wm831x_wdt_miscdev;
34*502a0106SMark Brown static int wm831x_wdt_expect_close;
35*502a0106SMark Brown static DEFINE_MUTEX(wdt_mutex);
36*502a0106SMark Brown static struct wm831x *wm831x;
37*502a0106SMark Brown static unsigned int update_gpio;
38*502a0106SMark Brown static unsigned int update_state;
39*502a0106SMark Brown 
40*502a0106SMark Brown /* We can't use the sub-second values here but they're included
41*502a0106SMark Brown  * for completeness.  */
42*502a0106SMark Brown static struct {
43*502a0106SMark Brown 	int time;  /* Seconds */
44*502a0106SMark Brown 	u16 val;   /* WDOG_TO value */
45*502a0106SMark Brown } wm831x_wdt_cfgs[] = {
46*502a0106SMark Brown 	{  1, 2 },
47*502a0106SMark Brown 	{  2, 3 },
48*502a0106SMark Brown 	{  4, 4 },
49*502a0106SMark Brown 	{  8, 5 },
50*502a0106SMark Brown 	{ 16, 6 },
51*502a0106SMark Brown 	{ 32, 7 },
52*502a0106SMark Brown 	{ 33, 7 },  /* Actually 32.768s so include both, others round down */
53*502a0106SMark Brown };
54*502a0106SMark Brown 
55*502a0106SMark Brown static int wm831x_wdt_set_timeout(struct wm831x *wm831x, u16 value)
56*502a0106SMark Brown {
57*502a0106SMark Brown 	int ret;
58*502a0106SMark Brown 
59*502a0106SMark Brown 	mutex_lock(&wdt_mutex);
60*502a0106SMark Brown 
61*502a0106SMark Brown 	ret = wm831x_reg_unlock(wm831x);
62*502a0106SMark Brown 	if (ret == 0) {
63*502a0106SMark Brown 		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
64*502a0106SMark Brown 				      WM831X_WDOG_TO_MASK, value);
65*502a0106SMark Brown 		wm831x_reg_lock(wm831x);
66*502a0106SMark Brown 	} else {
67*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
68*502a0106SMark Brown 			ret);
69*502a0106SMark Brown 	}
70*502a0106SMark Brown 
71*502a0106SMark Brown 	mutex_unlock(&wdt_mutex);
72*502a0106SMark Brown 
73*502a0106SMark Brown 	return ret;
74*502a0106SMark Brown }
75*502a0106SMark Brown 
76*502a0106SMark Brown static int wm831x_wdt_start(struct wm831x *wm831x)
77*502a0106SMark Brown {
78*502a0106SMark Brown 	int ret;
79*502a0106SMark Brown 
80*502a0106SMark Brown 	mutex_lock(&wdt_mutex);
81*502a0106SMark Brown 
82*502a0106SMark Brown 	ret = wm831x_reg_unlock(wm831x);
83*502a0106SMark Brown 	if (ret == 0) {
84*502a0106SMark Brown 		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
85*502a0106SMark Brown 				      WM831X_WDOG_ENA, WM831X_WDOG_ENA);
86*502a0106SMark Brown 		wm831x_reg_lock(wm831x);
87*502a0106SMark Brown 	} else {
88*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
89*502a0106SMark Brown 			ret);
90*502a0106SMark Brown 	}
91*502a0106SMark Brown 
92*502a0106SMark Brown 	mutex_unlock(&wdt_mutex);
93*502a0106SMark Brown 
94*502a0106SMark Brown 	return ret;
95*502a0106SMark Brown }
96*502a0106SMark Brown 
97*502a0106SMark Brown static int wm831x_wdt_stop(struct wm831x *wm831x)
98*502a0106SMark Brown {
99*502a0106SMark Brown 	int ret;
100*502a0106SMark Brown 
101*502a0106SMark Brown 	mutex_lock(&wdt_mutex);
102*502a0106SMark Brown 
103*502a0106SMark Brown 	ret = wm831x_reg_unlock(wm831x);
104*502a0106SMark Brown 	if (ret == 0) {
105*502a0106SMark Brown 		ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
106*502a0106SMark Brown 				      WM831X_WDOG_ENA, 0);
107*502a0106SMark Brown 		wm831x_reg_lock(wm831x);
108*502a0106SMark Brown 	} else {
109*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
110*502a0106SMark Brown 			ret);
111*502a0106SMark Brown 	}
112*502a0106SMark Brown 
113*502a0106SMark Brown 	mutex_unlock(&wdt_mutex);
114*502a0106SMark Brown 
115*502a0106SMark Brown 	return ret;
116*502a0106SMark Brown }
117*502a0106SMark Brown 
118*502a0106SMark Brown static int wm831x_wdt_kick(struct wm831x *wm831x)
119*502a0106SMark Brown {
120*502a0106SMark Brown 	int ret;
121*502a0106SMark Brown 	u16 reg;
122*502a0106SMark Brown 
123*502a0106SMark Brown 	mutex_lock(&wdt_mutex);
124*502a0106SMark Brown 
125*502a0106SMark Brown 	if (update_gpio) {
126*502a0106SMark Brown 		gpio_set_value_cansleep(update_gpio, update_state);
127*502a0106SMark Brown 		update_state = !update_state;
128*502a0106SMark Brown 		ret = 0;
129*502a0106SMark Brown 		goto out;
130*502a0106SMark Brown 	}
131*502a0106SMark Brown 
132*502a0106SMark Brown 
133*502a0106SMark Brown 	reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
134*502a0106SMark Brown 
135*502a0106SMark Brown 	if (!(reg & WM831X_WDOG_RST_SRC)) {
136*502a0106SMark Brown 		dev_err(wm831x->dev, "Hardware watchdog update unsupported\n");
137*502a0106SMark Brown 		ret = -EINVAL;
138*502a0106SMark Brown 		goto out;
139*502a0106SMark Brown 	}
140*502a0106SMark Brown 
141*502a0106SMark Brown 	reg |= WM831X_WDOG_RESET;
142*502a0106SMark Brown 
143*502a0106SMark Brown 	ret = wm831x_reg_unlock(wm831x);
144*502a0106SMark Brown 	if (ret == 0) {
145*502a0106SMark Brown 		ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
146*502a0106SMark Brown 		wm831x_reg_lock(wm831x);
147*502a0106SMark Brown 	} else {
148*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
149*502a0106SMark Brown 			ret);
150*502a0106SMark Brown 	}
151*502a0106SMark Brown 
152*502a0106SMark Brown out:
153*502a0106SMark Brown 	mutex_unlock(&wdt_mutex);
154*502a0106SMark Brown 
155*502a0106SMark Brown 	return ret;
156*502a0106SMark Brown }
157*502a0106SMark Brown 
158*502a0106SMark Brown static int wm831x_wdt_open(struct inode *inode, struct file *file)
159*502a0106SMark Brown {
160*502a0106SMark Brown 	int ret;
161*502a0106SMark Brown 
162*502a0106SMark Brown 	if (!wm831x)
163*502a0106SMark Brown 		return -ENODEV;
164*502a0106SMark Brown 
165*502a0106SMark Brown 	if (test_and_set_bit(0, &wm831x_wdt_users))
166*502a0106SMark Brown 		return -EBUSY;
167*502a0106SMark Brown 
168*502a0106SMark Brown 	ret = wm831x_wdt_start(wm831x);
169*502a0106SMark Brown 	if (ret != 0)
170*502a0106SMark Brown 		return ret;
171*502a0106SMark Brown 
172*502a0106SMark Brown 	return nonseekable_open(inode, file);
173*502a0106SMark Brown }
174*502a0106SMark Brown 
175*502a0106SMark Brown static int wm831x_wdt_release(struct inode *inode, struct file *file)
176*502a0106SMark Brown {
177*502a0106SMark Brown 	if (wm831x_wdt_expect_close)
178*502a0106SMark Brown 		wm831x_wdt_stop(wm831x);
179*502a0106SMark Brown 	else {
180*502a0106SMark Brown 		dev_warn(wm831x->dev, "Watchdog device closed uncleanly\n");
181*502a0106SMark Brown 		wm831x_wdt_kick(wm831x);
182*502a0106SMark Brown 	}
183*502a0106SMark Brown 
184*502a0106SMark Brown 	clear_bit(0, &wm831x_wdt_users);
185*502a0106SMark Brown 
186*502a0106SMark Brown 	return 0;
187*502a0106SMark Brown }
188*502a0106SMark Brown 
189*502a0106SMark Brown static ssize_t wm831x_wdt_write(struct file *file,
190*502a0106SMark Brown 				const char __user *data, size_t count,
191*502a0106SMark Brown 				loff_t *ppos)
192*502a0106SMark Brown {
193*502a0106SMark Brown 	size_t i;
194*502a0106SMark Brown 
195*502a0106SMark Brown 	if (count) {
196*502a0106SMark Brown 		wm831x_wdt_kick(wm831x);
197*502a0106SMark Brown 
198*502a0106SMark Brown 		if (!nowayout) {
199*502a0106SMark Brown 			/* In case it was set long ago */
200*502a0106SMark Brown 			wm831x_wdt_expect_close = 0;
201*502a0106SMark Brown 
202*502a0106SMark Brown 			/* scan to see whether or not we got the magic
203*502a0106SMark Brown 			   character */
204*502a0106SMark Brown 			for (i = 0; i != count; i++) {
205*502a0106SMark Brown 				char c;
206*502a0106SMark Brown 				if (get_user(c, data + i))
207*502a0106SMark Brown 					return -EFAULT;
208*502a0106SMark Brown 				if (c == 'V')
209*502a0106SMark Brown 					wm831x_wdt_expect_close = 42;
210*502a0106SMark Brown 			}
211*502a0106SMark Brown 		}
212*502a0106SMark Brown 	}
213*502a0106SMark Brown 	return count;
214*502a0106SMark Brown }
215*502a0106SMark Brown 
216*502a0106SMark Brown static struct watchdog_info ident = {
217*502a0106SMark Brown 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
218*502a0106SMark Brown 	.identity = "WM831x Watchdog",
219*502a0106SMark Brown };
220*502a0106SMark Brown 
221*502a0106SMark Brown static long wm831x_wdt_ioctl(struct file *file, unsigned int cmd,
222*502a0106SMark Brown 			     unsigned long arg)
223*502a0106SMark Brown {
224*502a0106SMark Brown 	int ret = -ENOTTY, time, i;
225*502a0106SMark Brown 	void __user *argp = (void __user *)arg;
226*502a0106SMark Brown 	int __user *p = argp;
227*502a0106SMark Brown 	u16 reg;
228*502a0106SMark Brown 
229*502a0106SMark Brown 	switch (cmd) {
230*502a0106SMark Brown 	case WDIOC_GETSUPPORT:
231*502a0106SMark Brown 		ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
232*502a0106SMark Brown 		break;
233*502a0106SMark Brown 
234*502a0106SMark Brown 	case WDIOC_GETSTATUS:
235*502a0106SMark Brown 	case WDIOC_GETBOOTSTATUS:
236*502a0106SMark Brown 		ret = put_user(0, p);
237*502a0106SMark Brown 		break;
238*502a0106SMark Brown 
239*502a0106SMark Brown 	case WDIOC_SETOPTIONS:
240*502a0106SMark Brown 	{
241*502a0106SMark Brown 		int options;
242*502a0106SMark Brown 
243*502a0106SMark Brown 		if (get_user(options, p))
244*502a0106SMark Brown 			return -EFAULT;
245*502a0106SMark Brown 
246*502a0106SMark Brown 		ret = -EINVAL;
247*502a0106SMark Brown 
248*502a0106SMark Brown 		/* Setting both simultaneously means at least one must fail */
249*502a0106SMark Brown 		if (options == WDIOS_DISABLECARD)
250*502a0106SMark Brown 			ret = wm831x_wdt_start(wm831x);
251*502a0106SMark Brown 
252*502a0106SMark Brown 		if (options == WDIOS_ENABLECARD)
253*502a0106SMark Brown 			ret = wm831x_wdt_stop(wm831x);
254*502a0106SMark Brown 		break;
255*502a0106SMark Brown 	}
256*502a0106SMark Brown 
257*502a0106SMark Brown 	case WDIOC_KEEPALIVE:
258*502a0106SMark Brown 		ret = wm831x_wdt_kick(wm831x);
259*502a0106SMark Brown 		break;
260*502a0106SMark Brown 
261*502a0106SMark Brown 	case WDIOC_SETTIMEOUT:
262*502a0106SMark Brown 		ret = get_user(time, p);
263*502a0106SMark Brown 		if (ret)
264*502a0106SMark Brown 			break;
265*502a0106SMark Brown 
266*502a0106SMark Brown 		if (time == 0) {
267*502a0106SMark Brown 			if (nowayout)
268*502a0106SMark Brown 				ret = -EINVAL;
269*502a0106SMark Brown 			else
270*502a0106SMark Brown 				wm831x_wdt_stop(wm831x);
271*502a0106SMark Brown 			break;
272*502a0106SMark Brown 		}
273*502a0106SMark Brown 
274*502a0106SMark Brown 		for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
275*502a0106SMark Brown 			if (wm831x_wdt_cfgs[i].time == time)
276*502a0106SMark Brown 				break;
277*502a0106SMark Brown 		if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
278*502a0106SMark Brown 			ret = -EINVAL;
279*502a0106SMark Brown 		else
280*502a0106SMark Brown 			ret = wm831x_wdt_set_timeout(wm831x,
281*502a0106SMark Brown 						     wm831x_wdt_cfgs[i].val);
282*502a0106SMark Brown 		break;
283*502a0106SMark Brown 
284*502a0106SMark Brown 	case WDIOC_GETTIMEOUT:
285*502a0106SMark Brown 		reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
286*502a0106SMark Brown 		reg &= WM831X_WDOG_TO_MASK;
287*502a0106SMark Brown 		for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
288*502a0106SMark Brown 			if (wm831x_wdt_cfgs[i].val == reg)
289*502a0106SMark Brown 				break;
290*502a0106SMark Brown 		if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) {
291*502a0106SMark Brown 			dev_warn(wm831x->dev,
292*502a0106SMark Brown 				 "Unknown watchdog configuration: %x\n", reg);
293*502a0106SMark Brown 			ret = -EINVAL;
294*502a0106SMark Brown 		} else
295*502a0106SMark Brown 			ret = put_user(wm831x_wdt_cfgs[i].time, p);
296*502a0106SMark Brown 
297*502a0106SMark Brown 	}
298*502a0106SMark Brown 
299*502a0106SMark Brown 	return ret;
300*502a0106SMark Brown }
301*502a0106SMark Brown 
302*502a0106SMark Brown static const struct file_operations wm831x_wdt_fops = {
303*502a0106SMark Brown 	.owner = THIS_MODULE,
304*502a0106SMark Brown 	.llseek = no_llseek,
305*502a0106SMark Brown 	.write = wm831x_wdt_write,
306*502a0106SMark Brown 	.unlocked_ioctl = wm831x_wdt_ioctl,
307*502a0106SMark Brown 	.open = wm831x_wdt_open,
308*502a0106SMark Brown 	.release = wm831x_wdt_release,
309*502a0106SMark Brown };
310*502a0106SMark Brown 
311*502a0106SMark Brown static struct miscdevice wm831x_wdt_miscdev = {
312*502a0106SMark Brown 	.minor = WATCHDOG_MINOR,
313*502a0106SMark Brown 	.name = "watchdog",
314*502a0106SMark Brown 	.fops = &wm831x_wdt_fops,
315*502a0106SMark Brown };
316*502a0106SMark Brown 
317*502a0106SMark Brown static int __devinit wm831x_wdt_probe(struct platform_device *pdev)
318*502a0106SMark Brown {
319*502a0106SMark Brown 	struct wm831x_pdata *chip_pdata;
320*502a0106SMark Brown 	struct wm831x_watchdog_pdata *pdata;
321*502a0106SMark Brown 	int reg, ret;
322*502a0106SMark Brown 
323*502a0106SMark Brown 	wm831x = dev_get_drvdata(pdev->dev.parent);
324*502a0106SMark Brown 
325*502a0106SMark Brown 	ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
326*502a0106SMark Brown 	if (ret < 0) {
327*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to read watchdog status: %d\n",
328*502a0106SMark Brown 			ret);
329*502a0106SMark Brown 		goto err;
330*502a0106SMark Brown 	}
331*502a0106SMark Brown 	reg = ret;
332*502a0106SMark Brown 
333*502a0106SMark Brown 	if (reg & WM831X_WDOG_DEBUG)
334*502a0106SMark Brown 		dev_warn(wm831x->dev, "Watchdog is paused\n");
335*502a0106SMark Brown 
336*502a0106SMark Brown 	/* Apply any configuration */
337*502a0106SMark Brown 	if (pdev->dev.parent->platform_data) {
338*502a0106SMark Brown 		chip_pdata = pdev->dev.parent->platform_data;
339*502a0106SMark Brown 		pdata = chip_pdata->watchdog;
340*502a0106SMark Brown 	} else {
341*502a0106SMark Brown 		pdata = NULL;
342*502a0106SMark Brown 	}
343*502a0106SMark Brown 
344*502a0106SMark Brown 	if (pdata) {
345*502a0106SMark Brown 		reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
346*502a0106SMark Brown 			 WM831X_WDOG_RST_SRC);
347*502a0106SMark Brown 
348*502a0106SMark Brown 		reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT;
349*502a0106SMark Brown 		reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT;
350*502a0106SMark Brown 		reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT;
351*502a0106SMark Brown 
352*502a0106SMark Brown 		if (pdata->update_gpio) {
353*502a0106SMark Brown 			ret = gpio_request(pdata->update_gpio,
354*502a0106SMark Brown 					   "Watchdog update");
355*502a0106SMark Brown 			if (ret < 0) {
356*502a0106SMark Brown 				dev_err(wm831x->dev,
357*502a0106SMark Brown 					"Failed to request update GPIO: %d\n",
358*502a0106SMark Brown 					ret);
359*502a0106SMark Brown 				goto err;
360*502a0106SMark Brown 			}
361*502a0106SMark Brown 
362*502a0106SMark Brown 			ret = gpio_direction_output(pdata->update_gpio, 0);
363*502a0106SMark Brown 			if (ret != 0) {
364*502a0106SMark Brown 				dev_err(wm831x->dev,
365*502a0106SMark Brown 					"gpio_direction_output returned: %d\n",
366*502a0106SMark Brown 					ret);
367*502a0106SMark Brown 				goto err_gpio;
368*502a0106SMark Brown 			}
369*502a0106SMark Brown 
370*502a0106SMark Brown 			update_gpio = pdata->update_gpio;
371*502a0106SMark Brown 
372*502a0106SMark Brown 			/* Make sure the watchdog takes hardware updates */
373*502a0106SMark Brown 			reg |= WM831X_WDOG_RST_SRC;
374*502a0106SMark Brown 		}
375*502a0106SMark Brown 
376*502a0106SMark Brown 		ret = wm831x_reg_unlock(wm831x);
377*502a0106SMark Brown 		if (ret == 0) {
378*502a0106SMark Brown 			ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
379*502a0106SMark Brown 			wm831x_reg_lock(wm831x);
380*502a0106SMark Brown 		} else {
381*502a0106SMark Brown 			dev_err(wm831x->dev,
382*502a0106SMark Brown 				"Failed to unlock security key: %d\n", ret);
383*502a0106SMark Brown 			goto err_gpio;
384*502a0106SMark Brown 		}
385*502a0106SMark Brown 	}
386*502a0106SMark Brown 
387*502a0106SMark Brown 	wm831x_wdt_miscdev.parent = &pdev->dev;
388*502a0106SMark Brown 
389*502a0106SMark Brown 	ret = misc_register(&wm831x_wdt_miscdev);
390*502a0106SMark Brown 	if (ret != 0) {
391*502a0106SMark Brown 		dev_err(wm831x->dev, "Failed to register miscdev: %d\n", ret);
392*502a0106SMark Brown 		goto err_gpio;
393*502a0106SMark Brown 	}
394*502a0106SMark Brown 
395*502a0106SMark Brown 	return 0;
396*502a0106SMark Brown 
397*502a0106SMark Brown err_gpio:
398*502a0106SMark Brown 	if (update_gpio) {
399*502a0106SMark Brown 		gpio_free(update_gpio);
400*502a0106SMark Brown 		update_gpio = 0;
401*502a0106SMark Brown 	}
402*502a0106SMark Brown err:
403*502a0106SMark Brown 	return ret;
404*502a0106SMark Brown }
405*502a0106SMark Brown 
406*502a0106SMark Brown static int __devexit wm831x_wdt_remove(struct platform_device *pdev)
407*502a0106SMark Brown {
408*502a0106SMark Brown 	if (update_gpio) {
409*502a0106SMark Brown 		gpio_free(update_gpio);
410*502a0106SMark Brown 		update_gpio = 0;
411*502a0106SMark Brown 	}
412*502a0106SMark Brown 
413*502a0106SMark Brown 	misc_deregister(&wm831x_wdt_miscdev);
414*502a0106SMark Brown 
415*502a0106SMark Brown 	return 0;
416*502a0106SMark Brown }
417*502a0106SMark Brown 
418*502a0106SMark Brown static struct platform_driver wm831x_wdt_driver = {
419*502a0106SMark Brown 	.probe = wm831x_wdt_probe,
420*502a0106SMark Brown 	.remove = __devexit_p(wm831x_wdt_remove),
421*502a0106SMark Brown 	.driver = {
422*502a0106SMark Brown 		.name = "wm831x-watchdog",
423*502a0106SMark Brown 	},
424*502a0106SMark Brown };
425*502a0106SMark Brown 
426*502a0106SMark Brown static int __init wm831x_wdt_init(void)
427*502a0106SMark Brown {
428*502a0106SMark Brown 	return platform_driver_register(&wm831x_wdt_driver);
429*502a0106SMark Brown }
430*502a0106SMark Brown module_init(wm831x_wdt_init);
431*502a0106SMark Brown 
432*502a0106SMark Brown static void __exit wm831x_wdt_exit(void)
433*502a0106SMark Brown {
434*502a0106SMark Brown 	platform_driver_unregister(&wm831x_wdt_driver);
435*502a0106SMark Brown }
436*502a0106SMark Brown module_exit(wm831x_wdt_exit);
437*502a0106SMark Brown 
438*502a0106SMark Brown MODULE_AUTHOR("Mark Brown");
439*502a0106SMark Brown MODULE_DESCRIPTION("WM831x Watchdog");
440*502a0106SMark Brown MODULE_LICENSE("GPL");
441*502a0106SMark Brown MODULE_ALIAS("platform:wm831x-watchdog");
442