Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
s3c2410_wdt.c
Go to the documentation of this file.
1 /* linux/drivers/char/watchdog/s3c2410_wdt.c
2  *
3  * Copyright (c) 2004 Simtec Electronics
4  * Ben Dooks <[email protected]>
5  *
6  * S3C2410 Watchdog Timer Support
7  *
8  * Based on, softdog.c by Alan Cox,
9  * (c) Copyright 1996 Alan Cox <[email protected]>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 */
25 
26 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
27 
28 #include <linux/module.h>
29 #include <linux/moduleparam.h>
30 #include <linux/types.h>
31 #include <linux/timer.h>
32 #include <linux/miscdevice.h> /* for MODULE_ALIAS_MISCDEV */
33 #include <linux/watchdog.h>
34 #include <linux/init.h>
35 #include <linux/platform_device.h>
36 #include <linux/interrupt.h>
37 #include <linux/clk.h>
38 #include <linux/uaccess.h>
39 #include <linux/io.h>
40 #include <linux/cpufreq.h>
41 #include <linux/slab.h>
42 #include <linux/err.h>
43 #include <linux/of.h>
44 
45 #include <mach/map.h>
46 
47 #undef S3C_VA_WATCHDOG
48 #define S3C_VA_WATCHDOG (0)
49 
50 #include <plat/regs-watchdog.h>
51 
52 #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
53 #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
54 
55 static bool nowayout = WATCHDOG_NOWAYOUT;
56 static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
57 static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;
58 static int soft_noboot;
59 static int debug;
60 
61 module_param(tmr_margin, int, 0);
62 module_param(tmr_atboot, int, 0);
63 module_param(nowayout, bool, 0);
64 module_param(soft_noboot, int, 0);
65 module_param(debug, int, 0);
66 
67 MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
69 MODULE_PARM_DESC(tmr_atboot,
70  "Watchdog is started at boot time if set to 1, default="
72 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
74 MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
75  "0 to reboot (default 0)");
76 MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)");
77 
78 static struct device *wdt_dev; /* platform device attached to */
79 static struct resource *wdt_mem;
80 static struct resource *wdt_irq;
81 static struct clk *wdt_clock;
82 static void __iomem *wdt_base;
83 static unsigned int wdt_count;
84 static DEFINE_SPINLOCK(wdt_lock);
85 
86 /* watchdog control routines */
87 
88 #define DBG(fmt, ...) \
89 do { \
90  if (debug) \
91  pr_info(fmt, ##__VA_ARGS__); \
92 } while (0)
93 
94 /* functions */
95 
96 static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
97 {
98  spin_lock(&wdt_lock);
99  writel(wdt_count, wdt_base + S3C2410_WTCNT);
100  spin_unlock(&wdt_lock);
101 
102  return 0;
103 }
104 
105 static void __s3c2410wdt_stop(void)
106 {
107  unsigned long wtcon;
108 
109  wtcon = readl(wdt_base + S3C2410_WTCON);
111  writel(wtcon, wdt_base + S3C2410_WTCON);
112 }
113 
114 static int s3c2410wdt_stop(struct watchdog_device *wdd)
115 {
116  spin_lock(&wdt_lock);
117  __s3c2410wdt_stop();
118  spin_unlock(&wdt_lock);
119 
120  return 0;
121 }
122 
123 static int s3c2410wdt_start(struct watchdog_device *wdd)
124 {
125  unsigned long wtcon;
126 
127  spin_lock(&wdt_lock);
128 
129  __s3c2410wdt_stop();
130 
131  wtcon = readl(wdt_base + S3C2410_WTCON);
133 
134  if (soft_noboot) {
135  wtcon |= S3C2410_WTCON_INTEN;
136  wtcon &= ~S3C2410_WTCON_RSTEN;
137  } else {
138  wtcon &= ~S3C2410_WTCON_INTEN;
139  wtcon |= S3C2410_WTCON_RSTEN;
140  }
141 
142  DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
143  __func__, wdt_count, wtcon);
144 
145  writel(wdt_count, wdt_base + S3C2410_WTDAT);
146  writel(wdt_count, wdt_base + S3C2410_WTCNT);
147  writel(wtcon, wdt_base + S3C2410_WTCON);
148  spin_unlock(&wdt_lock);
149 
150  return 0;
151 }
152 
153 static inline int s3c2410wdt_is_running(void)
154 {
155  return readl(wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
156 }
157 
158 static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout)
159 {
160  unsigned long freq = clk_get_rate(wdt_clock);
161  unsigned int count;
162  unsigned int divisor = 1;
163  unsigned long wtcon;
164 
165  if (timeout < 1)
166  return -EINVAL;
167 
168  freq /= 128;
169  count = timeout * freq;
170 
171  DBG("%s: count=%d, timeout=%d, freq=%lu\n",
172  __func__, count, timeout, freq);
173 
174  /* if the count is bigger than the watchdog register,
175  then work out what we need to do (and if) we can
176  actually make this value
177  */
178 
179  if (count >= 0x10000) {
180  for (divisor = 1; divisor <= 0x100; divisor++) {
181  if ((count / divisor) < 0x10000)
182  break;
183  }
184 
185  if ((count / divisor) >= 0x10000) {
186  dev_err(wdt_dev, "timeout %d too big\n", timeout);
187  return -EINVAL;
188  }
189  }
190 
191  DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
192  __func__, timeout, divisor, count, count/divisor);
193 
194  count /= divisor;
195  wdt_count = count;
196 
197  /* update the pre-scaler */
198  wtcon = readl(wdt_base + S3C2410_WTCON);
199  wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
200  wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
201 
202  writel(count, wdt_base + S3C2410_WTDAT);
203  writel(wtcon, wdt_base + S3C2410_WTCON);
204 
205  wdd->timeout = (count * divisor) / freq;
206 
207  return 0;
208 }
209 
210 #define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
211 
212 static const struct watchdog_info s3c2410_wdt_ident = {
213  .options = OPTIONS,
214  .firmware_version = 0,
215  .identity = "S3C2410 Watchdog",
216 };
217 
218 static struct watchdog_ops s3c2410wdt_ops = {
219  .owner = THIS_MODULE,
220  .start = s3c2410wdt_start,
221  .stop = s3c2410wdt_stop,
222  .ping = s3c2410wdt_keepalive,
223  .set_timeout = s3c2410wdt_set_heartbeat,
224 };
225 
226 static struct watchdog_device s3c2410_wdd = {
227  .info = &s3c2410_wdt_ident,
228  .ops = &s3c2410wdt_ops,
229 };
230 
231 /* interrupt handler code */
232 
233 static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
234 {
235  dev_info(wdt_dev, "watchdog timer expired (irq)\n");
236 
237  s3c2410wdt_keepalive(&s3c2410_wdd);
238  return IRQ_HANDLED;
239 }
240 
241 
242 #ifdef CONFIG_CPU_FREQ
243 
244 static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
245  unsigned long val, void *data)
246 {
247  int ret;
248 
249  if (!s3c2410wdt_is_running())
250  goto done;
251 
252  if (val == CPUFREQ_PRECHANGE) {
253  /* To ensure that over the change we don't cause the
254  * watchdog to trigger, we perform an keep-alive if
255  * the watchdog is running.
256  */
257 
258  s3c2410wdt_keepalive(&s3c2410_wdd);
259  } else if (val == CPUFREQ_POSTCHANGE) {
260  s3c2410wdt_stop(&s3c2410_wdd);
261 
262  ret = s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout);
263 
264  if (ret >= 0)
265  s3c2410wdt_start(&s3c2410_wdd);
266  else
267  goto err;
268  }
269 
270 done:
271  return 0;
272 
273  err:
274  dev_err(wdt_dev, "cannot set new value for timeout %d\n",
275  s3c2410_wdd.timeout);
276  return ret;
277 }
278 
279 static struct notifier_block s3c2410wdt_cpufreq_transition_nb = {
280  .notifier_call = s3c2410wdt_cpufreq_transition,
281 };
282 
283 static inline int s3c2410wdt_cpufreq_register(void)
284 {
285  return cpufreq_register_notifier(&s3c2410wdt_cpufreq_transition_nb,
287 }
288 
289 static inline void s3c2410wdt_cpufreq_deregister(void)
290 {
291  cpufreq_unregister_notifier(&s3c2410wdt_cpufreq_transition_nb,
293 }
294 
295 #else
296 static inline int s3c2410wdt_cpufreq_register(void)
297 {
298  return 0;
299 }
300 
301 static inline void s3c2410wdt_cpufreq_deregister(void)
302 {
303 }
304 #endif
305 
306 static int __devinit s3c2410wdt_probe(struct platform_device *pdev)
307 {
308  struct device *dev;
309  unsigned int wtcon;
310  int started = 0;
311  int ret;
312  int size;
313 
314  DBG("%s: probe=%p\n", __func__, pdev);
315 
316  dev = &pdev->dev;
317  wdt_dev = &pdev->dev;
318 
319  wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
320  if (wdt_mem == NULL) {
321  dev_err(dev, "no memory resource specified\n");
322  return -ENOENT;
323  }
324 
325  wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
326  if (wdt_irq == NULL) {
327  dev_err(dev, "no irq resource specified\n");
328  ret = -ENOENT;
329  goto err;
330  }
331 
332  /* get the memory region for the watchdog timer */
333 
334  size = resource_size(wdt_mem);
335  if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
336  dev_err(dev, "failed to get memory region\n");
337  ret = -EBUSY;
338  goto err;
339  }
340 
341  wdt_base = ioremap(wdt_mem->start, size);
342  if (wdt_base == NULL) {
343  dev_err(dev, "failed to ioremap() region\n");
344  ret = -EINVAL;
345  goto err_req;
346  }
347 
348  DBG("probe: mapped wdt_base=%p\n", wdt_base);
349 
350  wdt_clock = clk_get(&pdev->dev, "watchdog");
351  if (IS_ERR(wdt_clock)) {
352  dev_err(dev, "failed to find watchdog clock source\n");
353  ret = PTR_ERR(wdt_clock);
354  goto err_map;
355  }
356 
357  clk_enable(wdt_clock);
358 
359  ret = s3c2410wdt_cpufreq_register();
360  if (ret < 0) {
361  pr_err("failed to register cpufreq\n");
362  goto err_clk;
363  }
364 
365  /* see if we can actually set the requested timer margin, and if
366  * not, try the default value */
367 
368  if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, tmr_margin)) {
369  started = s3c2410wdt_set_heartbeat(&s3c2410_wdd,
371 
372  if (started == 0)
373  dev_info(dev,
374  "tmr_margin value out of range, default %d used\n",
376  else
377  dev_info(dev, "default timer value is out of range, "
378  "cannot start\n");
379  }
380 
381  ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
382  if (ret != 0) {
383  dev_err(dev, "failed to install irq (%d)\n", ret);
384  goto err_cpufreq;
385  }
386 
387  watchdog_set_nowayout(&s3c2410_wdd, nowayout);
388 
389  ret = watchdog_register_device(&s3c2410_wdd);
390  if (ret) {
391  dev_err(dev, "cannot register watchdog (%d)\n", ret);
392  goto err_irq;
393  }
394 
395  if (tmr_atboot && started == 0) {
396  dev_info(dev, "starting watchdog timer\n");
397  s3c2410wdt_start(&s3c2410_wdd);
398  } else if (!tmr_atboot) {
399  /* if we're not enabling the watchdog, then ensure it is
400  * disabled if it has been left running from the bootloader
401  * or other source */
402 
403  s3c2410wdt_stop(&s3c2410_wdd);
404  }
405 
406  /* print out a statement of readiness */
407 
408  wtcon = readl(wdt_base + S3C2410_WTCON);
409 
410  dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
411  (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
412  (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
413  (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
414 
415  return 0;
416 
417  err_irq:
418  free_irq(wdt_irq->start, pdev);
419 
420  err_cpufreq:
421  s3c2410wdt_cpufreq_deregister();
422 
423  err_clk:
424  clk_disable(wdt_clock);
425  clk_put(wdt_clock);
426  wdt_clock = NULL;
427 
428  err_map:
429  iounmap(wdt_base);
430 
431  err_req:
432  release_mem_region(wdt_mem->start, size);
433 
434  err:
435  wdt_irq = NULL;
436  wdt_mem = NULL;
437  return ret;
438 }
439 
440 static int __devexit s3c2410wdt_remove(struct platform_device *dev)
441 {
442  watchdog_unregister_device(&s3c2410_wdd);
443 
444  free_irq(wdt_irq->start, dev);
445 
446  s3c2410wdt_cpufreq_deregister();
447 
448  clk_disable(wdt_clock);
449  clk_put(wdt_clock);
450  wdt_clock = NULL;
451 
452  iounmap(wdt_base);
453 
454  release_mem_region(wdt_mem->start, resource_size(wdt_mem));
455  wdt_irq = NULL;
456  wdt_mem = NULL;
457  return 0;
458 }
459 
460 static void s3c2410wdt_shutdown(struct platform_device *dev)
461 {
462  s3c2410wdt_stop(&s3c2410_wdd);
463 }
464 
465 #ifdef CONFIG_PM
466 
467 static unsigned long wtcon_save;
468 static unsigned long wtdat_save;
469 
470 static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
471 {
472  /* Save watchdog state, and turn it off. */
473  wtcon_save = readl(wdt_base + S3C2410_WTCON);
474  wtdat_save = readl(wdt_base + S3C2410_WTDAT);
475 
476  /* Note that WTCNT doesn't need to be saved. */
477  s3c2410wdt_stop(&s3c2410_wdd);
478 
479  return 0;
480 }
481 
482 static int s3c2410wdt_resume(struct platform_device *dev)
483 {
484  /* Restore watchdog state. */
485 
486  writel(wtdat_save, wdt_base + S3C2410_WTDAT);
487  writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
488  writel(wtcon_save, wdt_base + S3C2410_WTCON);
489 
490  pr_info("watchdog %sabled\n",
491  (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
492 
493  return 0;
494 }
495 
496 #else
497 #define s3c2410wdt_suspend NULL
498 #define s3c2410wdt_resume NULL
499 #endif /* CONFIG_PM */
500 
501 #ifdef CONFIG_OF
502 static const struct of_device_id s3c2410_wdt_match[] = {
503  { .compatible = "samsung,s3c2410-wdt" },
504  {},
505 };
506 MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
507 #endif
508 
509 static struct platform_driver s3c2410wdt_driver = {
510  .probe = s3c2410wdt_probe,
511  .remove = __devexit_p(s3c2410wdt_remove),
512  .shutdown = s3c2410wdt_shutdown,
513  .suspend = s3c2410wdt_suspend,
514  .resume = s3c2410wdt_resume,
515  .driver = {
516  .owner = THIS_MODULE,
517  .name = "s3c2410-wdt",
518  .of_match_table = of_match_ptr(s3c2410_wdt_match),
519  },
520 };
521 
522 module_platform_driver(s3c2410wdt_driver);
523 
524 MODULE_AUTHOR("Ben Dooks <[email protected]>, "
525  "Dimitry Andric <[email protected]>");
526 MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
527 MODULE_LICENSE("GPL");
529 MODULE_ALIAS("platform:s3c2410-wdt");