nuc900_wdt.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /*
  2. * Copyright (c) 2009 Nuvoton technology corporation.
  3. *
  4. * Wan ZongShun <[email protected]>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation;version 2 of the License.
  9. *
  10. */
  11. #include <linux/bitops.h>
  12. #include <linux/errno.h>
  13. #include <linux/fs.h>
  14. #include <linux/io.h>
  15. #include <linux/clk.h>
  16. #include <linux/kernel.h>
  17. #include <linux/miscdevice.h>
  18. #include <linux/module.h>
  19. #include <linux/moduleparam.h>
  20. #include <linux/platform_device.h>
  21. #include <linux/slab.h>
  22. #include <linux/interrupt.h>
  23. #include <linux/types.h>
  24. #include <linux/watchdog.h>
  25. #include <linux/uaccess.h>
  26. #define REG_WTCR 0x1c
  27. #define WTCLK (0x01 << 10)
  28. #define WTE (0x01 << 7) /*wdt enable*/
  29. #define WTIS (0x03 << 4)
  30. #define WTIF (0x01 << 3)
  31. #define WTRF (0x01 << 2)
  32. #define WTRE (0x01 << 1)
  33. #define WTR (0x01 << 0)
  34. /*
  35. * The watchdog time interval can be calculated via following formula:
  36. * WTIS real time interval (formula)
  37. * 0x00 ((2^ 14 ) * ((external crystal freq) / 256))seconds
  38. * 0x01 ((2^ 16 ) * ((external crystal freq) / 256))seconds
  39. * 0x02 ((2^ 18 ) * ((external crystal freq) / 256))seconds
  40. * 0x03 ((2^ 20 ) * ((external crystal freq) / 256))seconds
  41. *
  42. * The external crystal freq is 15Mhz in the nuc900 evaluation board.
  43. * So 0x00 = +-0.28 seconds, 0x01 = +-1.12 seconds, 0x02 = +-4.48 seconds,
  44. * 0x03 = +- 16.92 seconds..
  45. */
  46. #define WDT_HW_TIMEOUT 0x02
  47. #define WDT_TIMEOUT (HZ/2)
  48. #define WDT_HEARTBEAT 15
  49. static int heartbeat = WDT_HEARTBEAT;
  50. module_param(heartbeat, int, 0);
  51. MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
  52. "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
  53. static bool nowayout = WATCHDOG_NOWAYOUT;
  54. module_param(nowayout, bool, 0);
  55. MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  56. "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  57. struct nuc900_wdt {
  58. struct clk *wdt_clock;
  59. struct platform_device *pdev;
  60. void __iomem *wdt_base;
  61. char expect_close;
  62. struct timer_list timer;
  63. spinlock_t wdt_lock;
  64. unsigned long next_heartbeat;
  65. };
  66. static unsigned long nuc900wdt_busy;
  67. static struct nuc900_wdt *nuc900_wdt;
  68. static inline void nuc900_wdt_keepalive(void)
  69. {
  70. unsigned int val;
  71. spin_lock(&nuc900_wdt->wdt_lock);
  72. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  73. val |= (WTR | WTIF);
  74. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  75. spin_unlock(&nuc900_wdt->wdt_lock);
  76. }
  77. static inline void nuc900_wdt_start(void)
  78. {
  79. unsigned int val;
  80. spin_lock(&nuc900_wdt->wdt_lock);
  81. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  82. val |= (WTRE | WTE | WTR | WTCLK | WTIF);
  83. val &= ~WTIS;
  84. val |= (WDT_HW_TIMEOUT << 0x04);
  85. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  86. spin_unlock(&nuc900_wdt->wdt_lock);
  87. nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
  88. mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
  89. }
  90. static inline void nuc900_wdt_stop(void)
  91. {
  92. unsigned int val;
  93. del_timer(&nuc900_wdt->timer);
  94. spin_lock(&nuc900_wdt->wdt_lock);
  95. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  96. val &= ~WTE;
  97. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  98. spin_unlock(&nuc900_wdt->wdt_lock);
  99. }
  100. static inline void nuc900_wdt_ping(void)
  101. {
  102. nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
  103. }
  104. static int nuc900_wdt_open(struct inode *inode, struct file *file)
  105. {
  106. if (test_and_set_bit(0, &nuc900wdt_busy))
  107. return -EBUSY;
  108. nuc900_wdt_start();
  109. return nonseekable_open(inode, file);
  110. }
  111. static int nuc900_wdt_close(struct inode *inode, struct file *file)
  112. {
  113. if (nuc900_wdt->expect_close == 42)
  114. nuc900_wdt_stop();
  115. else {
  116. dev_crit(&nuc900_wdt->pdev->dev,
  117. "Unexpected close, not stopping watchdog!\n");
  118. nuc900_wdt_ping();
  119. }
  120. nuc900_wdt->expect_close = 0;
  121. clear_bit(0, &nuc900wdt_busy);
  122. return 0;
  123. }
  124. static const struct watchdog_info nuc900_wdt_info = {
  125. .identity = "nuc900 watchdog",
  126. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  127. WDIOF_MAGICCLOSE,
  128. };
  129. static long nuc900_wdt_ioctl(struct file *file,
  130. unsigned int cmd, unsigned long arg)
  131. {
  132. void __user *argp = (void __user *)arg;
  133. int __user *p = argp;
  134. int new_value;
  135. switch (cmd) {
  136. case WDIOC_GETSUPPORT:
  137. return copy_to_user(argp, &nuc900_wdt_info,
  138. sizeof(nuc900_wdt_info)) ? -EFAULT : 0;
  139. case WDIOC_GETSTATUS:
  140. case WDIOC_GETBOOTSTATUS:
  141. return put_user(0, p);
  142. case WDIOC_KEEPALIVE:
  143. nuc900_wdt_ping();
  144. return 0;
  145. case WDIOC_SETTIMEOUT:
  146. if (get_user(new_value, p))
  147. return -EFAULT;
  148. heartbeat = new_value;
  149. nuc900_wdt_ping();
  150. return put_user(new_value, p);
  151. case WDIOC_GETTIMEOUT:
  152. return put_user(heartbeat, p);
  153. default:
  154. return -ENOTTY;
  155. }
  156. }
  157. static ssize_t nuc900_wdt_write(struct file *file, const char __user *data,
  158. size_t len, loff_t *ppos)
  159. {
  160. if (!len)
  161. return 0;
  162. /* Scan for magic character */
  163. if (!nowayout) {
  164. size_t i;
  165. nuc900_wdt->expect_close = 0;
  166. for (i = 0; i < len; i++) {
  167. char c;
  168. if (get_user(c, data + i))
  169. return -EFAULT;
  170. if (c == 'V') {
  171. nuc900_wdt->expect_close = 42;
  172. break;
  173. }
  174. }
  175. }
  176. nuc900_wdt_ping();
  177. return len;
  178. }
  179. static void nuc900_wdt_timer_ping(unsigned long data)
  180. {
  181. if (time_before(jiffies, nuc900_wdt->next_heartbeat)) {
  182. nuc900_wdt_keepalive();
  183. mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
  184. } else
  185. dev_warn(&nuc900_wdt->pdev->dev, "Will reset the machine !\n");
  186. }
  187. static const struct file_operations nuc900wdt_fops = {
  188. .owner = THIS_MODULE,
  189. .llseek = no_llseek,
  190. .unlocked_ioctl = nuc900_wdt_ioctl,
  191. .open = nuc900_wdt_open,
  192. .release = nuc900_wdt_close,
  193. .write = nuc900_wdt_write,
  194. };
  195. static struct miscdevice nuc900wdt_miscdev = {
  196. .minor = WATCHDOG_MINOR,
  197. .name = "watchdog",
  198. .fops = &nuc900wdt_fops,
  199. };
  200. static int nuc900wdt_probe(struct platform_device *pdev)
  201. {
  202. struct resource *res;
  203. int ret = 0;
  204. nuc900_wdt = devm_kzalloc(&pdev->dev, sizeof(*nuc900_wdt),
  205. GFP_KERNEL);
  206. if (!nuc900_wdt)
  207. return -ENOMEM;
  208. nuc900_wdt->pdev = pdev;
  209. spin_lock_init(&nuc900_wdt->wdt_lock);
  210. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  211. nuc900_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
  212. if (IS_ERR(nuc900_wdt->wdt_base))
  213. return PTR_ERR(nuc900_wdt->wdt_base);
  214. nuc900_wdt->wdt_clock = devm_clk_get(&pdev->dev, NULL);
  215. if (IS_ERR(nuc900_wdt->wdt_clock)) {
  216. dev_err(&pdev->dev, "failed to find watchdog clock source\n");
  217. return PTR_ERR(nuc900_wdt->wdt_clock);
  218. }
  219. clk_enable(nuc900_wdt->wdt_clock);
  220. setup_timer(&nuc900_wdt->timer, nuc900_wdt_timer_ping, 0);
  221. ret = misc_register(&nuc900wdt_miscdev);
  222. if (ret) {
  223. dev_err(&pdev->dev, "err register miscdev on minor=%d (%d)\n",
  224. WATCHDOG_MINOR, ret);
  225. goto err_clk;
  226. }
  227. return 0;
  228. err_clk:
  229. clk_disable(nuc900_wdt->wdt_clock);
  230. return ret;
  231. }
  232. static int nuc900wdt_remove(struct platform_device *pdev)
  233. {
  234. misc_deregister(&nuc900wdt_miscdev);
  235. clk_disable(nuc900_wdt->wdt_clock);
  236. return 0;
  237. }
  238. static struct platform_driver nuc900wdt_driver = {
  239. .probe = nuc900wdt_probe,
  240. .remove = nuc900wdt_remove,
  241. .driver = {
  242. .name = "nuc900-wdt",
  243. },
  244. };
  245. module_platform_driver(nuc900wdt_driver);
  246. MODULE_AUTHOR("Wan ZongShun <[email protected]>");
  247. MODULE_DESCRIPTION("Watchdog driver for NUC900");
  248. MODULE_LICENSE("GPL");
  249. MODULE_ALIAS("platform:nuc900-wdt");