xref: /openbmc/linux/drivers/watchdog/wm8350_wdt.c (revision 006948ba)
1006948baSMark Brown /*
2006948baSMark Brown  * Watchdog driver for the wm8350
3006948baSMark Brown  *
4006948baSMark Brown  * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com>
5006948baSMark Brown  *
6006948baSMark Brown  * This program is free software; you can redistribute it and/or
7006948baSMark Brown  * modify it under the terms of the GNU General Public License
8006948baSMark Brown  * as published by the Free Software Foundation
9006948baSMark Brown  */
10006948baSMark Brown 
11006948baSMark Brown #include <linux/module.h>
12006948baSMark Brown #include <linux/moduleparam.h>
13006948baSMark Brown #include <linux/types.h>
14006948baSMark Brown #include <linux/kernel.h>
15006948baSMark Brown #include <linux/fs.h>
16006948baSMark Brown #include <linux/miscdevice.h>
17006948baSMark Brown #include <linux/platform_device.h>
18006948baSMark Brown #include <linux/watchdog.h>
19006948baSMark Brown #include <linux/uaccess.h>
20006948baSMark Brown #include <linux/mfd/wm8350/core.h>
21006948baSMark Brown 
22006948baSMark Brown static int nowayout = WATCHDOG_NOWAYOUT;
23006948baSMark Brown module_param(nowayout, int, 0);
24006948baSMark Brown MODULE_PARM_DESC(nowayout,
25006948baSMark Brown 		 "Watchdog cannot be stopped once started (default="
26006948baSMark Brown 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
27006948baSMark Brown 
28006948baSMark Brown static unsigned long wm8350_wdt_users;
29006948baSMark Brown static struct miscdevice wm8350_wdt_miscdev;
30006948baSMark Brown static int wm8350_wdt_expect_close;
31006948baSMark Brown static DEFINE_MUTEX(wdt_mutex);
32006948baSMark Brown 
33006948baSMark Brown static struct {
34006948baSMark Brown 	int time;  /* Seconds */
35006948baSMark Brown 	u16 val;   /* To be set in WM8350_SYSTEM_CONTROL_2 */
36006948baSMark Brown } wm8350_wdt_cfgs[] = {
37006948baSMark Brown 	{ 1, 0x02 },
38006948baSMark Brown 	{ 2, 0x04 },
39006948baSMark Brown 	{ 4, 0x05 },
40006948baSMark Brown };
41006948baSMark Brown 
42006948baSMark Brown static struct wm8350 *get_wm8350(void)
43006948baSMark Brown {
44006948baSMark Brown 	return dev_get_drvdata(wm8350_wdt_miscdev.parent);
45006948baSMark Brown }
46006948baSMark Brown 
47006948baSMark Brown static int wm8350_wdt_set_timeout(struct wm8350 *wm8350, u16 value)
48006948baSMark Brown {
49006948baSMark Brown 	int ret;
50006948baSMark Brown 	u16 reg;
51006948baSMark Brown 
52006948baSMark Brown 	mutex_lock(&wdt_mutex);
53006948baSMark Brown 	wm8350_reg_unlock(wm8350);
54006948baSMark Brown 
55006948baSMark Brown 	reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
56006948baSMark Brown 	reg &= ~WM8350_WDOG_TO_MASK;
57006948baSMark Brown 	reg |= value;
58006948baSMark Brown 	ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
59006948baSMark Brown 
60006948baSMark Brown 	wm8350_reg_lock(wm8350);
61006948baSMark Brown 	mutex_unlock(&wdt_mutex);
62006948baSMark Brown 
63006948baSMark Brown 	return ret;
64006948baSMark Brown }
65006948baSMark Brown 
66006948baSMark Brown static int wm8350_wdt_start(struct wm8350 *wm8350)
67006948baSMark Brown {
68006948baSMark Brown 	int ret;
69006948baSMark Brown 	u16 reg;
70006948baSMark Brown 
71006948baSMark Brown 	mutex_lock(&wdt_mutex);
72006948baSMark Brown 	wm8350_reg_unlock(wm8350);
73006948baSMark Brown 
74006948baSMark Brown 	reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
75006948baSMark Brown 	reg &= ~WM8350_WDOG_MODE_MASK;
76006948baSMark Brown 	reg |= 0x20;
77006948baSMark Brown 	ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
78006948baSMark Brown 
79006948baSMark Brown 	wm8350_reg_lock(wm8350);
80006948baSMark Brown 	mutex_unlock(&wdt_mutex);
81006948baSMark Brown 
82006948baSMark Brown 	return ret;
83006948baSMark Brown }
84006948baSMark Brown 
85006948baSMark Brown static int wm8350_wdt_stop(struct wm8350 *wm8350)
86006948baSMark Brown {
87006948baSMark Brown 	int ret;
88006948baSMark Brown 	u16 reg;
89006948baSMark Brown 
90006948baSMark Brown 	mutex_lock(&wdt_mutex);
91006948baSMark Brown 	wm8350_reg_unlock(wm8350);
92006948baSMark Brown 
93006948baSMark Brown 	reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
94006948baSMark Brown 	reg &= ~WM8350_WDOG_MODE_MASK;
95006948baSMark Brown 	ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
96006948baSMark Brown 
97006948baSMark Brown 	wm8350_reg_lock(wm8350);
98006948baSMark Brown 	mutex_unlock(&wdt_mutex);
99006948baSMark Brown 
100006948baSMark Brown 	return ret;
101006948baSMark Brown }
102006948baSMark Brown 
103006948baSMark Brown static int wm8350_wdt_kick(struct wm8350 *wm8350)
104006948baSMark Brown {
105006948baSMark Brown 	int ret;
106006948baSMark Brown 	u16 reg;
107006948baSMark Brown 
108006948baSMark Brown 	mutex_lock(&wdt_mutex);
109006948baSMark Brown 
110006948baSMark Brown 	reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
111006948baSMark Brown 	ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
112006948baSMark Brown 
113006948baSMark Brown 	mutex_unlock(&wdt_mutex);
114006948baSMark Brown 
115006948baSMark Brown 	return ret;
116006948baSMark Brown }
117006948baSMark Brown 
118006948baSMark Brown static int wm8350_wdt_open(struct inode *inode, struct file *file)
119006948baSMark Brown {
120006948baSMark Brown 	struct wm8350 *wm8350 = get_wm8350();
121006948baSMark Brown 	int ret;
122006948baSMark Brown 
123006948baSMark Brown 	if (!wm8350)
124006948baSMark Brown 		return -ENODEV;
125006948baSMark Brown 
126006948baSMark Brown 	if (test_and_set_bit(0, &wm8350_wdt_users))
127006948baSMark Brown 		return -EBUSY;
128006948baSMark Brown 
129006948baSMark Brown 	ret = wm8350_wdt_start(wm8350);
130006948baSMark Brown 	if (ret != 0)
131006948baSMark Brown 		return ret;
132006948baSMark Brown 
133006948baSMark Brown 	return nonseekable_open(inode, file);
134006948baSMark Brown }
135006948baSMark Brown 
136006948baSMark Brown static int wm8350_wdt_release(struct inode *inode, struct file *file)
137006948baSMark Brown {
138006948baSMark Brown 	struct wm8350 *wm8350 = get_wm8350();
139006948baSMark Brown 
140006948baSMark Brown 	if (wm8350_wdt_expect_close)
141006948baSMark Brown 		wm8350_wdt_stop(wm8350);
142006948baSMark Brown 	else {
143006948baSMark Brown 		dev_warn(wm8350->dev, "Watchdog device closed uncleanly\n");
144006948baSMark Brown 		wm8350_wdt_kick(wm8350);
145006948baSMark Brown 	}
146006948baSMark Brown 
147006948baSMark Brown 	clear_bit(0, &wm8350_wdt_users);
148006948baSMark Brown 
149006948baSMark Brown 	return 0;
150006948baSMark Brown }
151006948baSMark Brown 
152006948baSMark Brown static ssize_t wm8350_wdt_write(struct file *file,
153006948baSMark Brown 				const char __user *data, size_t count,
154006948baSMark Brown 				loff_t *ppos)
155006948baSMark Brown {
156006948baSMark Brown 	struct wm8350 *wm8350 = get_wm8350();
157006948baSMark Brown 	size_t i;
158006948baSMark Brown 
159006948baSMark Brown 	if (count) {
160006948baSMark Brown 		wm8350_wdt_kick(wm8350);
161006948baSMark Brown 
162006948baSMark Brown 		if (!nowayout) {
163006948baSMark Brown 			/* In case it was set long ago */
164006948baSMark Brown 			wm8350_wdt_expect_close = 0;
165006948baSMark Brown 
166006948baSMark Brown 			/* scan to see whether or not we got the magic
167006948baSMark Brown 			   character */
168006948baSMark Brown 			for (i = 0; i != count; i++) {
169006948baSMark Brown 				char c;
170006948baSMark Brown 				if (get_user(c, data + i))
171006948baSMark Brown 					return -EFAULT;
172006948baSMark Brown 				if (c == 'V')
173006948baSMark Brown 					wm8350_wdt_expect_close = 42;
174006948baSMark Brown 			}
175006948baSMark Brown 		}
176006948baSMark Brown 	}
177006948baSMark Brown 	return count;
178006948baSMark Brown }
179006948baSMark Brown 
180006948baSMark Brown static struct watchdog_info ident = {
181006948baSMark Brown 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
182006948baSMark Brown 	.identity = "WM8350 Watchdog",
183006948baSMark Brown };
184006948baSMark Brown 
185006948baSMark Brown static long wm8350_wdt_ioctl(struct file *file, unsigned int cmd,
186006948baSMark Brown 			     unsigned long arg)
187006948baSMark Brown {
188006948baSMark Brown 	struct wm8350 *wm8350 = get_wm8350();
189006948baSMark Brown 	int ret = -ENOTTY, time, i;
190006948baSMark Brown 	void __user *argp = (void __user *)arg;
191006948baSMark Brown 	int __user *p = argp;
192006948baSMark Brown 	u16 reg;
193006948baSMark Brown 
194006948baSMark Brown 	switch (cmd) {
195006948baSMark Brown 	case WDIOC_GETSUPPORT:
196006948baSMark Brown 		ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
197006948baSMark Brown 		break;
198006948baSMark Brown 
199006948baSMark Brown 	case WDIOC_GETSTATUS:
200006948baSMark Brown 	case WDIOC_GETBOOTSTATUS:
201006948baSMark Brown 		ret = put_user(0, p);
202006948baSMark Brown 		break;
203006948baSMark Brown 
204006948baSMark Brown 	case WDIOC_SETOPTIONS:
205006948baSMark Brown 	{
206006948baSMark Brown 		int options;
207006948baSMark Brown 
208006948baSMark Brown 		if (get_user(options, p))
209006948baSMark Brown 			return -EFAULT;
210006948baSMark Brown 
211006948baSMark Brown 		ret = -EINVAL;
212006948baSMark Brown 
213006948baSMark Brown 		/* Setting both simultaneously means at least one must fail */
214006948baSMark Brown 		if (options == WDIOS_DISABLECARD)
215006948baSMark Brown 			ret = wm8350_wdt_start(wm8350);
216006948baSMark Brown 
217006948baSMark Brown 		if (options == WDIOS_ENABLECARD)
218006948baSMark Brown 			ret = wm8350_wdt_stop(wm8350);
219006948baSMark Brown 		break;
220006948baSMark Brown 	}
221006948baSMark Brown 
222006948baSMark Brown 	case WDIOC_KEEPALIVE:
223006948baSMark Brown 		ret = wm8350_wdt_kick(wm8350);
224006948baSMark Brown 		break;
225006948baSMark Brown 
226006948baSMark Brown 	case WDIOC_SETTIMEOUT:
227006948baSMark Brown 		ret = get_user(time, p);
228006948baSMark Brown 		if (ret)
229006948baSMark Brown 			break;
230006948baSMark Brown 
231006948baSMark Brown 		if (time == 0) {
232006948baSMark Brown 			if (nowayout)
233006948baSMark Brown 				ret = -EINVAL;
234006948baSMark Brown 			else
235006948baSMark Brown 				wm8350_wdt_stop(wm8350);
236006948baSMark Brown 			break;
237006948baSMark Brown 		}
238006948baSMark Brown 
239006948baSMark Brown 		for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
240006948baSMark Brown 			if (wm8350_wdt_cfgs[i].time == time)
241006948baSMark Brown 				break;
242006948baSMark Brown 		if (i == ARRAY_SIZE(wm8350_wdt_cfgs))
243006948baSMark Brown 			ret = -EINVAL;
244006948baSMark Brown 		else
245006948baSMark Brown 			ret = wm8350_wdt_set_timeout(wm8350,
246006948baSMark Brown 						     wm8350_wdt_cfgs[i].val);
247006948baSMark Brown 		break;
248006948baSMark Brown 
249006948baSMark Brown 	case WDIOC_GETTIMEOUT:
250006948baSMark Brown 		reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
251006948baSMark Brown 		reg &= WM8350_WDOG_TO_MASK;
252006948baSMark Brown 		for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
253006948baSMark Brown 			if (wm8350_wdt_cfgs[i].val == reg)
254006948baSMark Brown 				break;
255006948baSMark Brown 		if (i == ARRAY_SIZE(wm8350_wdt_cfgs)) {
256006948baSMark Brown 			dev_warn(wm8350->dev,
257006948baSMark Brown 				 "Unknown watchdog configuration: %x\n", reg);
258006948baSMark Brown 			ret = -EINVAL;
259006948baSMark Brown 		} else
260006948baSMark Brown 			ret = put_user(wm8350_wdt_cfgs[i].time, p);
261006948baSMark Brown 
262006948baSMark Brown 	}
263006948baSMark Brown 
264006948baSMark Brown 	return ret;
265006948baSMark Brown }
266006948baSMark Brown 
267006948baSMark Brown static const struct file_operations wm8350_wdt_fops = {
268006948baSMark Brown 	.owner = THIS_MODULE,
269006948baSMark Brown 	.llseek = no_llseek,
270006948baSMark Brown 	.write = wm8350_wdt_write,
271006948baSMark Brown 	.unlocked_ioctl = wm8350_wdt_ioctl,
272006948baSMark Brown 	.open = wm8350_wdt_open,
273006948baSMark Brown 	.release = wm8350_wdt_release,
274006948baSMark Brown };
275006948baSMark Brown 
276006948baSMark Brown static struct miscdevice wm8350_wdt_miscdev = {
277006948baSMark Brown 	.minor = WATCHDOG_MINOR,
278006948baSMark Brown 	.name = "watchdog",
279006948baSMark Brown 	.fops = &wm8350_wdt_fops,
280006948baSMark Brown };
281006948baSMark Brown 
282006948baSMark Brown static int wm8350_wdt_probe(struct platform_device *pdev)
283006948baSMark Brown {
284006948baSMark Brown 	struct wm8350 *wm8350 = platform_get_drvdata(pdev);
285006948baSMark Brown 
286006948baSMark Brown 	if (!wm8350) {
287006948baSMark Brown 		dev_err(wm8350->dev, "No driver data supplied\n");
288006948baSMark Brown 		return -ENODEV;
289006948baSMark Brown 	}
290006948baSMark Brown 
291006948baSMark Brown 	/* Default to 4s timeout */
292006948baSMark Brown 	wm8350_wdt_set_timeout(wm8350, 0x05);
293006948baSMark Brown 
294006948baSMark Brown 	wm8350_wdt_miscdev.parent = &pdev->dev;
295006948baSMark Brown 
296006948baSMark Brown 	return misc_register(&wm8350_wdt_miscdev);
297006948baSMark Brown }
298006948baSMark Brown 
299006948baSMark Brown static int __exit wm8350_wdt_remove(struct platform_device *pdev)
300006948baSMark Brown {
301006948baSMark Brown 	misc_deregister(&wm8350_wdt_miscdev);
302006948baSMark Brown 
303006948baSMark Brown 	return 0;
304006948baSMark Brown }
305006948baSMark Brown 
306006948baSMark Brown static struct platform_driver wm8350_wdt_driver = {
307006948baSMark Brown 	.probe = wm8350_wdt_probe,
308006948baSMark Brown 	.remove = wm8350_wdt_remove,
309006948baSMark Brown 	.driver = {
310006948baSMark Brown 		.name = "wm8350-wdt",
311006948baSMark Brown 	},
312006948baSMark Brown };
313006948baSMark Brown 
314006948baSMark Brown static int __init wm8350_wdt_init(void)
315006948baSMark Brown {
316006948baSMark Brown 	return platform_driver_register(&wm8350_wdt_driver);
317006948baSMark Brown }
318006948baSMark Brown module_init(wm8350_wdt_init);
319006948baSMark Brown 
320006948baSMark Brown static void __exit wm8350_wdt_exit(void)
321006948baSMark Brown {
322006948baSMark Brown 	platform_driver_unregister(&wm8350_wdt_driver);
323006948baSMark Brown }
324006948baSMark Brown module_exit(wm8350_wdt_exit);
325006948baSMark Brown 
326006948baSMark Brown MODULE_AUTHOR("Mark Brown");
327006948baSMark Brown MODULE_DESCRIPTION("WM8350 Watchdog");
328006948baSMark Brown MODULE_LICENSE("GPL");
329006948baSMark Brown MODULE_ALIAS("platform:wm8350-wdt");
330