blob: f18499eaeaa75281572013a3a9d815aacc294aeb [file] [log] [blame]
Mark Brown006948b2008-11-06 10:56:21 +00001/*
2 * Watchdog driver for the wm8350
3 *
4 * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation
9 */
10
Joe Perches27c766a2012-02-15 15:06:19 -080011#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
Mark Brown006948b2008-11-06 10:56:21 +000013#include <linux/module.h>
14#include <linux/moduleparam.h>
15#include <linux/types.h>
16#include <linux/kernel.h>
17#include <linux/fs.h>
18#include <linux/miscdevice.h>
19#include <linux/platform_device.h>
20#include <linux/watchdog.h>
21#include <linux/uaccess.h>
22#include <linux/mfd/wm8350/core.h>
23
24static int nowayout = WATCHDOG_NOWAYOUT;
25module_param(nowayout, int, 0);
26MODULE_PARM_DESC(nowayout,
27 "Watchdog cannot be stopped once started (default="
28 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
29
30static unsigned long wm8350_wdt_users;
31static struct miscdevice wm8350_wdt_miscdev;
32static int wm8350_wdt_expect_close;
33static DEFINE_MUTEX(wdt_mutex);
34
35static struct {
36 int time; /* Seconds */
37 u16 val; /* To be set in WM8350_SYSTEM_CONTROL_2 */
38} wm8350_wdt_cfgs[] = {
39 { 1, 0x02 },
40 { 2, 0x04 },
41 { 4, 0x05 },
42};
43
44static struct wm8350 *get_wm8350(void)
45{
46 return dev_get_drvdata(wm8350_wdt_miscdev.parent);
47}
48
49static int wm8350_wdt_set_timeout(struct wm8350 *wm8350, u16 value)
50{
51 int ret;
52 u16 reg;
53
54 mutex_lock(&wdt_mutex);
55 wm8350_reg_unlock(wm8350);
56
57 reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
58 reg &= ~WM8350_WDOG_TO_MASK;
59 reg |= value;
60 ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
61
62 wm8350_reg_lock(wm8350);
63 mutex_unlock(&wdt_mutex);
64
65 return ret;
66}
67
68static int wm8350_wdt_start(struct wm8350 *wm8350)
69{
70 int ret;
71 u16 reg;
72
73 mutex_lock(&wdt_mutex);
74 wm8350_reg_unlock(wm8350);
75
76 reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
77 reg &= ~WM8350_WDOG_MODE_MASK;
78 reg |= 0x20;
79 ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
80
81 wm8350_reg_lock(wm8350);
82 mutex_unlock(&wdt_mutex);
83
84 return ret;
85}
86
87static int wm8350_wdt_stop(struct wm8350 *wm8350)
88{
89 int ret;
90 u16 reg;
91
92 mutex_lock(&wdt_mutex);
93 wm8350_reg_unlock(wm8350);
94
95 reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
96 reg &= ~WM8350_WDOG_MODE_MASK;
97 ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
98
99 wm8350_reg_lock(wm8350);
100 mutex_unlock(&wdt_mutex);
101
102 return ret;
103}
104
105static int wm8350_wdt_kick(struct wm8350 *wm8350)
106{
107 int ret;
108 u16 reg;
109
110 mutex_lock(&wdt_mutex);
111
112 reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
113 ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
114
115 mutex_unlock(&wdt_mutex);
116
117 return ret;
118}
119
120static int wm8350_wdt_open(struct inode *inode, struct file *file)
121{
122 struct wm8350 *wm8350 = get_wm8350();
123 int ret;
124
125 if (!wm8350)
126 return -ENODEV;
127
128 if (test_and_set_bit(0, &wm8350_wdt_users))
129 return -EBUSY;
130
131 ret = wm8350_wdt_start(wm8350);
132 if (ret != 0)
133 return ret;
134
135 return nonseekable_open(inode, file);
136}
137
138static int wm8350_wdt_release(struct inode *inode, struct file *file)
139{
140 struct wm8350 *wm8350 = get_wm8350();
141
142 if (wm8350_wdt_expect_close)
143 wm8350_wdt_stop(wm8350);
144 else {
145 dev_warn(wm8350->dev, "Watchdog device closed uncleanly\n");
146 wm8350_wdt_kick(wm8350);
147 }
148
149 clear_bit(0, &wm8350_wdt_users);
150
151 return 0;
152}
153
154static ssize_t wm8350_wdt_write(struct file *file,
155 const char __user *data, size_t count,
156 loff_t *ppos)
157{
158 struct wm8350 *wm8350 = get_wm8350();
159 size_t i;
160
161 if (count) {
162 wm8350_wdt_kick(wm8350);
163
164 if (!nowayout) {
165 /* In case it was set long ago */
166 wm8350_wdt_expect_close = 0;
167
168 /* scan to see whether or not we got the magic
169 character */
170 for (i = 0; i != count; i++) {
171 char c;
172 if (get_user(c, data + i))
173 return -EFAULT;
174 if (c == 'V')
175 wm8350_wdt_expect_close = 42;
176 }
177 }
178 }
179 return count;
180}
181
Wim Van Sebroeck42747d72009-12-26 18:55:22 +0000182static const struct watchdog_info ident = {
Mark Brown006948b2008-11-06 10:56:21 +0000183 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
184 .identity = "WM8350 Watchdog",
185};
186
187static long wm8350_wdt_ioctl(struct file *file, unsigned int cmd,
188 unsigned long arg)
189{
190 struct wm8350 *wm8350 = get_wm8350();
191 int ret = -ENOTTY, time, i;
192 void __user *argp = (void __user *)arg;
193 int __user *p = argp;
194 u16 reg;
195
196 switch (cmd) {
197 case WDIOC_GETSUPPORT:
198 ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
199 break;
200
201 case WDIOC_GETSTATUS:
202 case WDIOC_GETBOOTSTATUS:
203 ret = put_user(0, p);
204 break;
205
206 case WDIOC_SETOPTIONS:
207 {
208 int options;
209
210 if (get_user(options, p))
211 return -EFAULT;
212
213 ret = -EINVAL;
214
215 /* Setting both simultaneously means at least one must fail */
216 if (options == WDIOS_DISABLECARD)
Axel Linebe06e82012-01-18 19:25:01 +0800217 ret = wm8350_wdt_stop(wm8350);
Mark Brown006948b2008-11-06 10:56:21 +0000218
219 if (options == WDIOS_ENABLECARD)
Axel Linebe06e82012-01-18 19:25:01 +0800220 ret = wm8350_wdt_start(wm8350);
Mark Brown006948b2008-11-06 10:56:21 +0000221 break;
222 }
223
224 case WDIOC_KEEPALIVE:
225 ret = wm8350_wdt_kick(wm8350);
226 break;
227
228 case WDIOC_SETTIMEOUT:
229 ret = get_user(time, p);
230 if (ret)
231 break;
232
233 if (time == 0) {
234 if (nowayout)
235 ret = -EINVAL;
236 else
237 wm8350_wdt_stop(wm8350);
238 break;
239 }
240
241 for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
242 if (wm8350_wdt_cfgs[i].time == time)
243 break;
244 if (i == ARRAY_SIZE(wm8350_wdt_cfgs))
245 ret = -EINVAL;
246 else
247 ret = wm8350_wdt_set_timeout(wm8350,
248 wm8350_wdt_cfgs[i].val);
249 break;
250
251 case WDIOC_GETTIMEOUT:
252 reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
253 reg &= WM8350_WDOG_TO_MASK;
254 for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
255 if (wm8350_wdt_cfgs[i].val == reg)
256 break;
257 if (i == ARRAY_SIZE(wm8350_wdt_cfgs)) {
258 dev_warn(wm8350->dev,
259 "Unknown watchdog configuration: %x\n", reg);
260 ret = -EINVAL;
261 } else
262 ret = put_user(wm8350_wdt_cfgs[i].time, p);
263
264 }
265
266 return ret;
267}
268
269static const struct file_operations wm8350_wdt_fops = {
270 .owner = THIS_MODULE,
271 .llseek = no_llseek,
272 .write = wm8350_wdt_write,
273 .unlocked_ioctl = wm8350_wdt_ioctl,
274 .open = wm8350_wdt_open,
275 .release = wm8350_wdt_release,
276};
277
278static struct miscdevice wm8350_wdt_miscdev = {
279 .minor = WATCHDOG_MINOR,
280 .name = "watchdog",
281 .fops = &wm8350_wdt_fops,
282};
283
Mark Brownb1cf3e92009-01-08 12:04:27 +0000284static int __devinit wm8350_wdt_probe(struct platform_device *pdev)
Mark Brown006948b2008-11-06 10:56:21 +0000285{
286 struct wm8350 *wm8350 = platform_get_drvdata(pdev);
287
288 if (!wm8350) {
Julia Lawallcfca31c2010-05-27 14:32:24 +0200289 pr_err("No driver data supplied\n");
Mark Brown006948b2008-11-06 10:56:21 +0000290 return -ENODEV;
291 }
292
293 /* Default to 4s timeout */
294 wm8350_wdt_set_timeout(wm8350, 0x05);
295
296 wm8350_wdt_miscdev.parent = &pdev->dev;
297
298 return misc_register(&wm8350_wdt_miscdev);
299}
300
Mark Brownb1cf3e92009-01-08 12:04:27 +0000301static int __devexit wm8350_wdt_remove(struct platform_device *pdev)
Mark Brown006948b2008-11-06 10:56:21 +0000302{
303 misc_deregister(&wm8350_wdt_miscdev);
304
305 return 0;
306}
307
308static struct platform_driver wm8350_wdt_driver = {
309 .probe = wm8350_wdt_probe,
Mark Brownb1cf3e92009-01-08 12:04:27 +0000310 .remove = __devexit_p(wm8350_wdt_remove),
Mark Brown006948b2008-11-06 10:56:21 +0000311 .driver = {
312 .name = "wm8350-wdt",
313 },
314};
315
Mark Brown216f3ad2011-11-23 15:22:36 +0000316module_platform_driver(wm8350_wdt_driver);
Mark Brown006948b2008-11-06 10:56:21 +0000317
318MODULE_AUTHOR("Mark Brown");
319MODULE_DESCRIPTION("WM8350 Watchdog");
320MODULE_LICENSE("GPL");
321MODULE_ALIAS("platform:wm8350-wdt");