Commit | Line | Data |
---|---|---|
4a370278 VK |
1 | /* |
2 | * drivers/char/watchdog/sp805-wdt.c | |
3 | * | |
4 | * Watchdog driver for ARM SP805 watchdog module | |
5 | * | |
6 | * Copyright (C) 2010 ST Microelectronics | |
7 | * Viresh Kumar<viresh.kumar@st.com> | |
8 | * | |
9 | * This file is licensed under the terms of the GNU General Public | |
10 | * License version 2 or later. This program is licensed "as is" without any | |
11 | * warranty of any kind, whether express or implied. | |
12 | */ | |
13 | ||
14 | #include <linux/device.h> | |
15 | #include <linux/resource.h> | |
16 | #include <linux/amba/bus.h> | |
17 | #include <linux/bitops.h> | |
18 | #include <linux/clk.h> | |
19 | #include <linux/fs.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/io.h> | |
22 | #include <linux/ioport.h> | |
23 | #include <linux/kernel.h> | |
24 | #include <linux/math64.h> | |
25 | #include <linux/miscdevice.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/moduleparam.h> | |
16ac4abe | 28 | #include <linux/pm.h> |
4a370278 VK |
29 | #include <linux/slab.h> |
30 | #include <linux/spinlock.h> | |
31 | #include <linux/types.h> | |
32 | #include <linux/uaccess.h> | |
33 | #include <linux/watchdog.h> | |
34 | ||
35 | /* default timeout in seconds */ | |
36 | #define DEFAULT_TIMEOUT 60 | |
37 | ||
38 | #define MODULE_NAME "sp805-wdt" | |
39 | ||
40 | /* watchdog register offsets and masks */ | |
41 | #define WDTLOAD 0x000 | |
42 | #define LOAD_MIN 0x00000001 | |
43 | #define LOAD_MAX 0xFFFFFFFF | |
44 | #define WDTVALUE 0x004 | |
45 | #define WDTCONTROL 0x008 | |
46 | /* control register masks */ | |
47 | #define INT_ENABLE (1 << 0) | |
48 | #define RESET_ENABLE (1 << 1) | |
49 | #define WDTINTCLR 0x00C | |
50 | #define WDTRIS 0x010 | |
51 | #define WDTMIS 0x014 | |
52 | #define INT_MASK (1 << 0) | |
53 | #define WDTLOCK 0xC00 | |
54 | #define UNLOCK 0x1ACCE551 | |
55 | #define LOCK 0x00000001 | |
56 | ||
57 | /** | |
58 | * struct sp805_wdt: sp805 wdt device structure | |
59 | * | |
60 | * lock: spin lock protecting dev structure and io access | |
61 | * base: base address of wdt | |
62 | * clk: clock structure of wdt | |
63 | * dev: amba device structure of wdt | |
64 | * status: current status of wdt | |
65 | * load_val: load value to be set for current timeout | |
66 | * timeout: current programmed timeout | |
67 | */ | |
68 | struct sp805_wdt { | |
69 | spinlock_t lock; | |
70 | void __iomem *base; | |
71 | struct clk *clk; | |
72 | struct amba_device *adev; | |
73 | unsigned long status; | |
74 | #define WDT_BUSY 0 | |
75 | #define WDT_CAN_BE_CLOSED 1 | |
76 | unsigned int load_val; | |
77 | unsigned int timeout; | |
78 | }; | |
79 | ||
80 | /* local variables */ | |
81 | static struct sp805_wdt *wdt; | |
86a1e189 | 82 | static bool nowayout = WATCHDOG_NOWAYOUT; |
4a370278 VK |
83 | |
84 | /* This routine finds load value that will reset system in required timout */ | |
85 | static void wdt_setload(unsigned int timeout) | |
86 | { | |
87 | u64 load, rate; | |
88 | ||
89 | rate = clk_get_rate(wdt->clk); | |
90 | ||
91 | /* | |
92 | * sp805 runs counter with given value twice, after the end of first | |
93 | * counter it gives an interrupt and then starts counter again. If | |
25985edc | 94 | * interrupt already occurred then it resets the system. This is why |
4a370278 VK |
95 | * load is half of what should be required. |
96 | */ | |
97 | load = div_u64(rate, 2) * timeout - 1; | |
98 | ||
99 | load = (load > LOAD_MAX) ? LOAD_MAX : load; | |
100 | load = (load < LOAD_MIN) ? LOAD_MIN : load; | |
101 | ||
102 | spin_lock(&wdt->lock); | |
103 | wdt->load_val = load; | |
104 | /* roundup timeout to closest positive integer value */ | |
105 | wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); | |
106 | spin_unlock(&wdt->lock); | |
107 | } | |
108 | ||
109 | /* returns number of seconds left for reset to occur */ | |
110 | static u32 wdt_timeleft(void) | |
111 | { | |
112 | u64 load, rate; | |
113 | ||
114 | rate = clk_get_rate(wdt->clk); | |
115 | ||
116 | spin_lock(&wdt->lock); | |
117 | load = readl(wdt->base + WDTVALUE); | |
118 | ||
119 | /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */ | |
120 | if (!(readl(wdt->base + WDTRIS) & INT_MASK)) | |
121 | load += wdt->load_val + 1; | |
122 | spin_unlock(&wdt->lock); | |
123 | ||
124 | return div_u64(load, rate); | |
125 | } | |
126 | ||
127 | /* enables watchdog timers reset */ | |
128 | static void wdt_enable(void) | |
129 | { | |
130 | spin_lock(&wdt->lock); | |
131 | ||
132 | writel(UNLOCK, wdt->base + WDTLOCK); | |
133 | writel(wdt->load_val, wdt->base + WDTLOAD); | |
134 | writel(INT_MASK, wdt->base + WDTINTCLR); | |
135 | writel(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL); | |
136 | writel(LOCK, wdt->base + WDTLOCK); | |
137 | ||
081d83a3 NB |
138 | /* Flush posted writes. */ |
139 | readl(wdt->base + WDTLOCK); | |
4a370278 VK |
140 | spin_unlock(&wdt->lock); |
141 | } | |
142 | ||
143 | /* disables watchdog timers reset */ | |
144 | static void wdt_disable(void) | |
145 | { | |
146 | spin_lock(&wdt->lock); | |
147 | ||
148 | writel(UNLOCK, wdt->base + WDTLOCK); | |
149 | writel(0, wdt->base + WDTCONTROL); | |
4a370278 VK |
150 | writel(LOCK, wdt->base + WDTLOCK); |
151 | ||
081d83a3 NB |
152 | /* Flush posted writes. */ |
153 | readl(wdt->base + WDTLOCK); | |
4a370278 VK |
154 | spin_unlock(&wdt->lock); |
155 | } | |
156 | ||
157 | static ssize_t sp805_wdt_write(struct file *file, const char *data, | |
158 | size_t len, loff_t *ppos) | |
159 | { | |
160 | if (len) { | |
161 | if (!nowayout) { | |
162 | size_t i; | |
163 | ||
164 | clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); | |
165 | ||
166 | for (i = 0; i != len; i++) { | |
167 | char c; | |
168 | ||
169 | if (get_user(c, data + i)) | |
170 | return -EFAULT; | |
171 | /* Check for Magic Close character */ | |
172 | if (c == 'V') { | |
173 | set_bit(WDT_CAN_BE_CLOSED, | |
174 | &wdt->status); | |
175 | break; | |
176 | } | |
177 | } | |
178 | } | |
179 | wdt_enable(); | |
180 | } | |
181 | return len; | |
182 | } | |
183 | ||
184 | static const struct watchdog_info ident = { | |
185 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, | |
186 | .identity = MODULE_NAME, | |
187 | }; | |
188 | ||
189 | static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, | |
190 | unsigned long arg) | |
191 | { | |
192 | int ret = -ENOTTY; | |
193 | unsigned int timeout; | |
194 | ||
195 | switch (cmd) { | |
196 | case WDIOC_GETSUPPORT: | |
197 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | |
198 | sizeof(ident)) ? -EFAULT : 0; | |
199 | break; | |
200 | ||
201 | case WDIOC_GETSTATUS: | |
202 | ret = put_user(0, (int *)arg); | |
203 | break; | |
204 | ||
205 | case WDIOC_KEEPALIVE: | |
206 | wdt_enable(); | |
207 | ret = 0; | |
208 | break; | |
209 | ||
210 | case WDIOC_SETTIMEOUT: | |
211 | ret = get_user(timeout, (unsigned int *)arg); | |
212 | if (ret) | |
213 | break; | |
214 | ||
215 | wdt_setload(timeout); | |
216 | ||
217 | wdt_enable(); | |
218 | /* Fall through */ | |
219 | ||
220 | case WDIOC_GETTIMEOUT: | |
221 | ret = put_user(wdt->timeout, (unsigned int *)arg); | |
222 | break; | |
223 | case WDIOC_GETTIMELEFT: | |
224 | ret = put_user(wdt_timeleft(), (unsigned int *)arg); | |
225 | break; | |
226 | } | |
227 | return ret; | |
228 | } | |
229 | ||
230 | static int sp805_wdt_open(struct inode *inode, struct file *file) | |
231 | { | |
232 | int ret = 0; | |
233 | ||
234 | if (test_and_set_bit(WDT_BUSY, &wdt->status)) | |
235 | return -EBUSY; | |
236 | ||
237 | ret = clk_enable(wdt->clk); | |
238 | if (ret) { | |
239 | dev_err(&wdt->adev->dev, "clock enable fail"); | |
240 | goto err; | |
241 | } | |
242 | ||
243 | wdt_enable(); | |
244 | ||
245 | /* can not be closed, once enabled */ | |
246 | clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); | |
247 | return nonseekable_open(inode, file); | |
248 | ||
249 | err: | |
250 | clear_bit(WDT_BUSY, &wdt->status); | |
251 | return ret; | |
252 | } | |
253 | ||
254 | static int sp805_wdt_release(struct inode *inode, struct file *file) | |
255 | { | |
256 | if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { | |
257 | clear_bit(WDT_BUSY, &wdt->status); | |
258 | dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n"); | |
259 | return 0; | |
260 | } | |
261 | ||
262 | wdt_disable(); | |
263 | clk_disable(wdt->clk); | |
264 | clear_bit(WDT_BUSY, &wdt->status); | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | static const struct file_operations sp805_wdt_fops = { | |
270 | .owner = THIS_MODULE, | |
271 | .llseek = no_llseek, | |
272 | .write = sp805_wdt_write, | |
273 | .unlocked_ioctl = sp805_wdt_ioctl, | |
274 | .open = sp805_wdt_open, | |
275 | .release = sp805_wdt_release, | |
276 | }; | |
277 | ||
278 | static struct miscdevice sp805_wdt_miscdev = { | |
279 | .minor = WATCHDOG_MINOR, | |
280 | .name = "watchdog", | |
281 | .fops = &sp805_wdt_fops, | |
282 | }; | |
283 | ||
284 | static int __devinit | |
aa25afad | 285 | sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) |
4a370278 VK |
286 | { |
287 | int ret = 0; | |
288 | ||
289 | if (!request_mem_region(adev->res.start, resource_size(&adev->res), | |
290 | "sp805_wdt")) { | |
291 | dev_warn(&adev->dev, "Failed to get memory region resource\n"); | |
292 | ret = -ENOENT; | |
293 | goto err; | |
294 | } | |
295 | ||
296 | wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); | |
297 | if (!wdt) { | |
298 | dev_warn(&adev->dev, "Kzalloc failed\n"); | |
299 | ret = -ENOMEM; | |
300 | goto err_kzalloc; | |
301 | } | |
302 | ||
303 | wdt->clk = clk_get(&adev->dev, NULL); | |
304 | if (IS_ERR(wdt->clk)) { | |
305 | dev_warn(&adev->dev, "Clock not found\n"); | |
306 | ret = PTR_ERR(wdt->clk); | |
307 | goto err_clk_get; | |
308 | } | |
309 | ||
310 | wdt->base = ioremap(adev->res.start, resource_size(&adev->res)); | |
311 | if (!wdt->base) { | |
312 | ret = -ENOMEM; | |
313 | dev_warn(&adev->dev, "ioremap fail\n"); | |
314 | goto err_ioremap; | |
315 | } | |
316 | ||
317 | wdt->adev = adev; | |
318 | spin_lock_init(&wdt->lock); | |
319 | wdt_setload(DEFAULT_TIMEOUT); | |
320 | ||
321 | ret = misc_register(&sp805_wdt_miscdev); | |
322 | if (ret < 0) { | |
323 | dev_warn(&adev->dev, "cannot register misc device\n"); | |
324 | goto err_misc_register; | |
325 | } | |
326 | ||
327 | dev_info(&adev->dev, "registration successful\n"); | |
328 | return 0; | |
329 | ||
330 | err_misc_register: | |
331 | iounmap(wdt->base); | |
332 | err_ioremap: | |
333 | clk_put(wdt->clk); | |
334 | err_clk_get: | |
335 | kfree(wdt); | |
336 | wdt = NULL; | |
337 | err_kzalloc: | |
338 | release_mem_region(adev->res.start, resource_size(&adev->res)); | |
339 | err: | |
340 | dev_err(&adev->dev, "Probe Failed!!!\n"); | |
341 | return ret; | |
342 | } | |
343 | ||
344 | static int __devexit sp805_wdt_remove(struct amba_device *adev) | |
345 | { | |
346 | misc_deregister(&sp805_wdt_miscdev); | |
347 | iounmap(wdt->base); | |
348 | clk_put(wdt->clk); | |
349 | kfree(wdt); | |
350 | release_mem_region(adev->res.start, resource_size(&adev->res)); | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
16ac4abe VK |
355 | #ifdef CONFIG_PM |
356 | static int sp805_wdt_suspend(struct device *dev) | |
357 | { | |
358 | if (test_bit(WDT_BUSY, &wdt->status)) { | |
359 | wdt_disable(); | |
360 | clk_disable(wdt->clk); | |
361 | } | |
362 | ||
363 | return 0; | |
364 | } | |
365 | ||
366 | static int sp805_wdt_resume(struct device *dev) | |
367 | { | |
368 | int ret = 0; | |
369 | ||
370 | if (test_bit(WDT_BUSY, &wdt->status)) { | |
371 | ret = clk_enable(wdt->clk); | |
372 | if (ret) { | |
373 | dev_err(dev, "clock enable fail"); | |
374 | return ret; | |
375 | } | |
376 | wdt_enable(); | |
377 | } | |
378 | ||
379 | return ret; | |
380 | } | |
381 | #endif /* CONFIG_PM */ | |
382 | ||
383 | static SIMPLE_DEV_PM_OPS(sp805_wdt_dev_pm_ops, sp805_wdt_suspend, | |
384 | sp805_wdt_resume); | |
385 | ||
bb558dac | 386 | static struct amba_id sp805_wdt_ids[] = { |
4a370278 VK |
387 | { |
388 | .id = 0x00141805, | |
389 | .mask = 0x00ffffff, | |
390 | }, | |
391 | { 0, 0 }, | |
392 | }; | |
393 | ||
17885b05 DM |
394 | MODULE_DEVICE_TABLE(amba, sp805_wdt_ids); |
395 | ||
4a370278 VK |
396 | static struct amba_driver sp805_wdt_driver = { |
397 | .drv = { | |
398 | .name = MODULE_NAME, | |
16ac4abe | 399 | .pm = &sp805_wdt_dev_pm_ops, |
4a370278 VK |
400 | }, |
401 | .id_table = sp805_wdt_ids, | |
402 | .probe = sp805_wdt_probe, | |
403 | .remove = __devexit_p(sp805_wdt_remove), | |
404 | }; | |
405 | ||
9e5ed094 | 406 | module_amba_driver(sp805_wdt_driver); |
4a370278 | 407 | |
86a1e189 | 408 | module_param(nowayout, bool, 0); |
4a370278 VK |
409 | MODULE_PARM_DESC(nowayout, |
410 | "Set to 1 to keep watchdog running after device release"); | |
411 | ||
412 | MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); | |
413 | MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); | |
414 | MODULE_LICENSE("GPL"); | |
415 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |