blob: af6e6b16475afeb831f16df00e846f160f452647 [file] [log] [blame]
Thierry Reding38461c52009-09-23 13:49:52 +02001/*
2 * Copyright (C) 2008-2009 Avionic Design GmbH
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/fs.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090010#include <linux/gfp.h>
Thierry Reding38461c52009-09-23 13:49:52 +020011#include <linux/io.h>
12#include <linux/miscdevice.h>
13#include <linux/module.h>
14#include <linux/platform_device.h>
15#include <linux/types.h>
16#include <linux/uaccess.h>
17#include <linux/watchdog.h>
18
19#define WATCHDOG_NAME "adx-wdt"
20
21/* register offsets */
22#define ADX_WDT_CONTROL 0x00
23#define ADX_WDT_CONTROL_ENABLE (1 << 0)
24#define ADX_WDT_CONTROL_nRESET (1 << 1)
25#define ADX_WDT_TIMEOUT 0x08
26
27static struct platform_device *adx_wdt_dev;
28static unsigned long driver_open;
29
30#define WDT_STATE_STOP 0
31#define WDT_STATE_START 1
32
33struct adx_wdt {
34 void __iomem *base;
35 unsigned long timeout;
36 unsigned int state;
37 unsigned int wake;
38 spinlock_t lock;
39};
40
Wim Van Sebroeck42747d72009-12-26 18:55:22 +000041static const struct watchdog_info adx_wdt_info = {
Thierry Reding38461c52009-09-23 13:49:52 +020042 .identity = "Avionic Design Xanthos Watchdog",
43 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
44};
45
46static void adx_wdt_start_locked(struct adx_wdt *wdt)
47{
48 u32 ctrl;
49
50 ctrl = readl(wdt->base + ADX_WDT_CONTROL);
51 ctrl |= ADX_WDT_CONTROL_ENABLE;
52 writel(ctrl, wdt->base + ADX_WDT_CONTROL);
53 wdt->state = WDT_STATE_START;
54}
55
56static void adx_wdt_start(struct adx_wdt *wdt)
57{
58 unsigned long flags;
59
60 spin_lock_irqsave(&wdt->lock, flags);
61 adx_wdt_start_locked(wdt);
62 spin_unlock_irqrestore(&wdt->lock, flags);
63}
64
65static void adx_wdt_stop_locked(struct adx_wdt *wdt)
66{
67 u32 ctrl;
68
69 ctrl = readl(wdt->base + ADX_WDT_CONTROL);
70 ctrl &= ~ADX_WDT_CONTROL_ENABLE;
71 writel(ctrl, wdt->base + ADX_WDT_CONTROL);
72 wdt->state = WDT_STATE_STOP;
73}
74
75static void adx_wdt_stop(struct adx_wdt *wdt)
76{
77 unsigned long flags;
78
79 spin_lock_irqsave(&wdt->lock, flags);
80 adx_wdt_stop_locked(wdt);
81 spin_unlock_irqrestore(&wdt->lock, flags);
82}
83
84static void adx_wdt_set_timeout(struct adx_wdt *wdt, unsigned long seconds)
85{
86 unsigned long timeout = seconds * 1000;
87 unsigned long flags;
88 unsigned int state;
89
90 spin_lock_irqsave(&wdt->lock, flags);
91 state = wdt->state;
92 adx_wdt_stop_locked(wdt);
93 writel(timeout, wdt->base + ADX_WDT_TIMEOUT);
94
95 if (state == WDT_STATE_START)
96 adx_wdt_start_locked(wdt);
97
98 wdt->timeout = timeout;
99 spin_unlock_irqrestore(&wdt->lock, flags);
100}
101
102static void adx_wdt_get_timeout(struct adx_wdt *wdt, unsigned long *seconds)
103{
104 *seconds = wdt->timeout / 1000;
105}
106
107static void adx_wdt_keepalive(struct adx_wdt *wdt)
108{
109 unsigned long flags;
110
111 spin_lock_irqsave(&wdt->lock, flags);
112 writel(wdt->timeout, wdt->base + ADX_WDT_TIMEOUT);
113 spin_unlock_irqrestore(&wdt->lock, flags);
114}
115
116static int adx_wdt_open(struct inode *inode, struct file *file)
117{
118 struct adx_wdt *wdt = platform_get_drvdata(adx_wdt_dev);
119
120 if (test_and_set_bit(0, &driver_open))
121 return -EBUSY;
122
123 file->private_data = wdt;
124 adx_wdt_set_timeout(wdt, 30);
125 adx_wdt_start(wdt);
126
127 return nonseekable_open(inode, file);
128}
129
130static int adx_wdt_release(struct inode *inode, struct file *file)
131{
132 struct adx_wdt *wdt = file->private_data;
133
134 adx_wdt_stop(wdt);
135 clear_bit(0, &driver_open);
136
137 return 0;
138}
139
140static long adx_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
141{
142 struct adx_wdt *wdt = file->private_data;
143 void __user *argp = (void __user *)arg;
144 unsigned long __user *p = argp;
145 unsigned long seconds = 0;
146 unsigned int options;
147 long ret = -EINVAL;
148
149 switch (cmd) {
150 case WDIOC_GETSUPPORT:
151 if (copy_to_user(argp, &adx_wdt_info, sizeof(adx_wdt_info)))
152 return -EFAULT;
153 else
154 return 0;
155
156 case WDIOC_GETSTATUS:
157 case WDIOC_GETBOOTSTATUS:
158 return put_user(0, p);
159
160 case WDIOC_KEEPALIVE:
161 adx_wdt_keepalive(wdt);
162 return 0;
163
164 case WDIOC_SETTIMEOUT:
165 if (get_user(seconds, p))
166 return -EFAULT;
167
168 adx_wdt_set_timeout(wdt, seconds);
169
170 /* fallthrough */
171 case WDIOC_GETTIMEOUT:
172 adx_wdt_get_timeout(wdt, &seconds);
173 return put_user(seconds, p);
174
175 case WDIOC_SETOPTIONS:
176 if (copy_from_user(&options, argp, sizeof(options)))
177 return -EFAULT;
178
179 if (options & WDIOS_DISABLECARD) {
180 adx_wdt_stop(wdt);
181 ret = 0;
182 }
183
184 if (options & WDIOS_ENABLECARD) {
185 adx_wdt_start(wdt);
186 ret = 0;
187 }
188
189 return ret;
190
191 default:
192 break;
193 }
194
195 return -ENOTTY;
196}
197
198static ssize_t adx_wdt_write(struct file *file, const char __user *data,
199 size_t len, loff_t *ppos)
200{
201 struct adx_wdt *wdt = file->private_data;
202
203 if (len)
204 adx_wdt_keepalive(wdt);
205
206 return len;
207}
208
209static const struct file_operations adx_wdt_fops = {
210 .owner = THIS_MODULE,
211 .llseek = no_llseek,
212 .open = adx_wdt_open,
213 .release = adx_wdt_release,
214 .unlocked_ioctl = adx_wdt_ioctl,
215 .write = adx_wdt_write,
216};
217
218static struct miscdevice adx_wdt_miscdev = {
219 .minor = WATCHDOG_MINOR,
220 .name = "watchdog",
221 .fops = &adx_wdt_fops,
222};
223
224static int __devinit adx_wdt_probe(struct platform_device *pdev)
225{
226 struct resource *res;
227 struct adx_wdt *wdt;
228 int ret = 0;
229 u32 ctrl;
230
231 wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
232 if (!wdt) {
233 dev_err(&pdev->dev, "cannot allocate WDT structure\n");
234 return -ENOMEM;
235 }
236
237 spin_lock_init(&wdt->lock);
238
239 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
240 if (!res) {
241 dev_err(&pdev->dev, "cannot obtain I/O memory region\n");
242 return -ENXIO;
243 }
244
245 res = devm_request_mem_region(&pdev->dev, res->start,
H Hartley Sweetenb782a562009-12-04 12:24:04 -0500246 resource_size(res), res->name);
Thierry Reding38461c52009-09-23 13:49:52 +0200247 if (!res) {
248 dev_err(&pdev->dev, "cannot request I/O memory region\n");
249 return -ENXIO;
250 }
251
252 wdt->base = devm_ioremap_nocache(&pdev->dev, res->start,
H Hartley Sweetenb782a562009-12-04 12:24:04 -0500253 resource_size(res));
Thierry Reding38461c52009-09-23 13:49:52 +0200254 if (!wdt->base) {
255 dev_err(&pdev->dev, "cannot remap I/O memory region\n");
256 return -ENXIO;
257 }
258
259 /* disable watchdog and reboot on timeout */
260 ctrl = readl(wdt->base + ADX_WDT_CONTROL);
261 ctrl &= ~ADX_WDT_CONTROL_ENABLE;
262 ctrl &= ~ADX_WDT_CONTROL_nRESET;
263 writel(ctrl, wdt->base + ADX_WDT_CONTROL);
264
265 platform_set_drvdata(pdev, wdt);
266 adx_wdt_dev = pdev;
267
268 ret = misc_register(&adx_wdt_miscdev);
269 if (ret) {
270 dev_err(&pdev->dev, "cannot register miscdev on minor %d "
271 "(err=%d)\n", WATCHDOG_MINOR, ret);
272 return ret;
273 }
274
275 return 0;
276}
277
278static int __devexit adx_wdt_remove(struct platform_device *pdev)
279{
280 struct adx_wdt *wdt = platform_get_drvdata(pdev);
281
282 misc_deregister(&adx_wdt_miscdev);
283 adx_wdt_stop(wdt);
284 platform_set_drvdata(pdev, NULL);
285
286 return 0;
287}
288
289static void adx_wdt_shutdown(struct platform_device *pdev)
290{
291 struct adx_wdt *wdt = platform_get_drvdata(pdev);
292 adx_wdt_stop(wdt);
293}
294
295#ifdef CONFIG_PM
296static int adx_wdt_suspend(struct device *dev)
297{
298 struct platform_device *pdev = to_platform_device(dev);
299 struct adx_wdt *wdt = platform_get_drvdata(pdev);
300
301 wdt->wake = (wdt->state == WDT_STATE_START) ? 1 : 0;
302 adx_wdt_stop(wdt);
303
304 return 0;
305}
306
307static int adx_wdt_resume(struct device *dev)
308{
309 struct platform_device *pdev = to_platform_device(dev);
310 struct adx_wdt *wdt = platform_get_drvdata(pdev);
311
312 if (wdt->wake)
313 adx_wdt_start(wdt);
314
315 return 0;
316}
317
Alexey Dobriyan47145212009-12-14 18:00:08 -0800318static const struct dev_pm_ops adx_wdt_pm_ops = {
Thierry Reding38461c52009-09-23 13:49:52 +0200319 .suspend = adx_wdt_suspend,
320 .resume = adx_wdt_resume,
321};
322
323# define ADX_WDT_PM_OPS (&adx_wdt_pm_ops)
324#else
325# define ADX_WDT_PM_OPS NULL
326#endif
327
328static struct platform_driver adx_wdt_driver = {
329 .probe = adx_wdt_probe,
330 .remove = __devexit_p(adx_wdt_remove),
331 .shutdown = adx_wdt_shutdown,
332 .driver = {
333 .name = WATCHDOG_NAME,
334 .owner = THIS_MODULE,
335 .pm = ADX_WDT_PM_OPS,
336 },
337};
338
339static int __init adx_wdt_init(void)
340{
341 return platform_driver_register(&adx_wdt_driver);
342}
343
344static void __exit adx_wdt_exit(void)
345{
346 platform_driver_unregister(&adx_wdt_driver);
347}
348
349module_init(adx_wdt_init);
350module_exit(adx_wdt_exit);
351
352MODULE_DESCRIPTION("Avionic Design Xanthos Watchdog Driver");
353MODULE_LICENSE("GPL v2");
354MODULE_AUTHOR("Thierry Reding <thierry.reding@avionic-design.de>");
355MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);