nuc900_wdt.c 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*
  2. * Copyright (c) 2009 Nuvoton technology corporation.
  3. *
  4. * Wan ZongShun <mcuos.com@gmail.com>
  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/init.h>
  15. #include <linux/io.h>
  16. #include <linux/clk.h>
  17. #include <linux/kernel.h>
  18. #include <linux/miscdevice.h>
  19. #include <linux/module.h>
  20. #include <linux/moduleparam.h>
  21. #include <linux/platform_device.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 int nowayout = WATCHDOG_NOWAYOUT;
  54. module_param(nowayout, int, 0);
  55. MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  56. "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  57. struct nuc900_wdt {
  58. struct resource *res;
  59. struct clk *wdt_clock;
  60. struct platform_device *pdev;
  61. void __iomem *wdt_base;
  62. char expect_close;
  63. struct timer_list timer;
  64. spinlock_t wdt_lock;
  65. unsigned long next_heartbeat;
  66. };
  67. static unsigned long nuc900wdt_busy;
  68. struct nuc900_wdt *nuc900_wdt;
  69. static inline void nuc900_wdt_keepalive(void)
  70. {
  71. unsigned int val;
  72. spin_lock(&nuc900_wdt->wdt_lock);
  73. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  74. val |= (WTR | WTIF);
  75. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  76. spin_unlock(&nuc900_wdt->wdt_lock);
  77. }
  78. static inline void nuc900_wdt_start(void)
  79. {
  80. unsigned int val;
  81. spin_lock(&nuc900_wdt->wdt_lock);
  82. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  83. val |= (WTRE | WTE | WTR | WTCLK | WTIF);
  84. val &= ~WTIS;
  85. val |= (WDT_HW_TIMEOUT << 0x04);
  86. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  87. spin_unlock(&nuc900_wdt->wdt_lock);
  88. nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
  89. mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
  90. }
  91. static inline void nuc900_wdt_stop(void)
  92. {
  93. unsigned int val;
  94. del_timer(&nuc900_wdt->timer);
  95. spin_lock(&nuc900_wdt->wdt_lock);
  96. val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
  97. val &= ~WTE;
  98. __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
  99. spin_unlock(&nuc900_wdt->wdt_lock);
  100. }
  101. static inline void nuc900_wdt_ping(void)
  102. {
  103. nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
  104. }
  105. static int nuc900_wdt_open(struct inode *inode, struct file *file)
  106. {
  107. if (test_and_set_bit(0, &nuc900wdt_busy))
  108. return -EBUSY;
  109. nuc900_wdt_start();
  110. return nonseekable_open(inode, file);
  111. }
  112. static int nuc900_wdt_close(struct inode *inode, struct file *file)
  113. {
  114. if (nuc900_wdt->expect_close == 42)
  115. nuc900_wdt_stop();
  116. else {
  117. dev_crit(&nuc900_wdt->pdev->dev,
  118. "Unexpected close, not stopping watchdog!\n");
  119. nuc900_wdt_ping();
  120. }
  121. nuc900_wdt->expect_close = 0;
  122. clear_bit(0, &nuc900wdt_busy);
  123. return 0;
  124. }
  125. static const struct watchdog_info nuc900_wdt_info = {
  126. .identity = "nuc900 watchdog",
  127. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  128. WDIOF_MAGICCLOSE,
  129. };
  130. static long nuc900_wdt_ioctl(struct file *file,
  131. unsigned int cmd, unsigned long arg)
  132. {
  133. void __user *argp = (void __user *)arg;
  134. int __user *p = argp;
  135. int new_value;
  136. switch (cmd) {
  137. case WDIOC_GETSUPPORT:
  138. return copy_to_user(argp, &nuc900_wdt_info,
  139. sizeof(nuc900_wdt_info)) ? -EFAULT : 0;
  140. case WDIOC_GETSTATUS:
  141. case WDIOC_GETBOOTSTATUS:
  142. return put_user(0, p);
  143. case WDIOC_KEEPALIVE:
  144. nuc900_wdt_ping();
  145. return 0;
  146. case WDIOC_SETTIMEOUT:
  147. if (get_user(new_value, p))
  148. return -EFAULT;
  149. heartbeat = new_value;
  150. nuc900_wdt_ping();
  151. return put_user(new_value, p);
  152. case WDIOC_GETTIMEOUT:
  153. return put_user(heartbeat, p);
  154. default:
  155. return -ENOTTY;
  156. }
  157. }
  158. static ssize_t nuc900_wdt_write(struct file *file, const char __user *data,
  159. size_t len, loff_t *ppos)
  160. {
  161. if (!len)
  162. return 0;
  163. /* Scan for magic character */
  164. if (!nowayout) {
  165. size_t i;
  166. nuc900_wdt->expect_close = 0;
  167. for (i = 0; i < len; i++) {
  168. char c;
  169. if (get_user(c, data + i))
  170. return -EFAULT;
  171. if (c == 'V') {
  172. nuc900_wdt->expect_close = 42;
  173. break;
  174. }
  175. }
  176. }
  177. nuc900_wdt_ping();
  178. return len;
  179. }
  180. static void nuc900_wdt_timer_ping(unsigned long data)
  181. {
  182. if (time_before(jiffies, nuc900_wdt->next_heartbeat)) {
  183. nuc900_wdt_keepalive();
  184. mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
  185. } else
  186. dev_warn(&nuc900_wdt->pdev->dev, "Will reset the machine !\n");
  187. }
  188. static const struct file_operations nuc900wdt_fops = {
  189. .owner = THIS_MODULE,
  190. .llseek = no_llseek,
  191. .unlocked_ioctl = nuc900_wdt_ioctl,
  192. .open = nuc900_wdt_open,
  193. .release = nuc900_wdt_close,
  194. .write = nuc900_wdt_write,
  195. };
  196. static struct miscdevice nuc900wdt_miscdev = {
  197. .minor = WATCHDOG_MINOR,
  198. .name = "watchdog",
  199. .fops = &nuc900wdt_fops,
  200. };
  201. static int __devinit nuc900wdt_probe(struct platform_device *pdev)
  202. {
  203. int ret = 0;
  204. nuc900_wdt = kzalloc(sizeof(struct nuc900_wdt), GFP_KERNEL);
  205. if (!nuc900_wdt)
  206. return -ENOMEM;
  207. nuc900_wdt->pdev = pdev;
  208. spin_lock_init(&nuc900_wdt->wdt_lock);
  209. nuc900_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  210. if (nuc900_wdt->res == NULL) {
  211. dev_err(&pdev->dev, "no memory resource specified\n");
  212. ret = -ENOENT;
  213. goto err_get;
  214. }
  215. if (!request_mem_region(nuc900_wdt->res->start,
  216. resource_size(nuc900_wdt->res), pdev->name)) {
  217. dev_err(&pdev->dev, "failed to get memory region\n");
  218. ret = -ENOENT;
  219. goto err_get;
  220. }
  221. nuc900_wdt->wdt_base = ioremap(nuc900_wdt->res->start,
  222. resource_size(nuc900_wdt->res));
  223. if (nuc900_wdt->wdt_base == NULL) {
  224. dev_err(&pdev->dev, "failed to ioremap() region\n");
  225. ret = -EINVAL;
  226. goto err_req;
  227. }
  228. nuc900_wdt->wdt_clock = clk_get(&pdev->dev, NULL);
  229. if (IS_ERR(nuc900_wdt->wdt_clock)) {
  230. dev_err(&pdev->dev, "failed to find watchdog clock source\n");
  231. ret = PTR_ERR(nuc900_wdt->wdt_clock);
  232. goto err_map;
  233. }
  234. clk_enable(nuc900_wdt->wdt_clock);
  235. setup_timer(&nuc900_wdt->timer, nuc900_wdt_timer_ping, 0);
  236. if (misc_register(&nuc900wdt_miscdev)) {
  237. dev_err(&pdev->dev, "err register miscdev on minor=%d (%d)\n",
  238. WATCHDOG_MINOR, ret);
  239. goto err_clk;
  240. }
  241. return 0;
  242. err_clk:
  243. clk_disable(nuc900_wdt->wdt_clock);
  244. clk_put(nuc900_wdt->wdt_clock);
  245. err_map:
  246. iounmap(nuc900_wdt->wdt_base);
  247. err_req:
  248. release_mem_region(nuc900_wdt->res->start,
  249. resource_size(nuc900_wdt->res));
  250. err_get:
  251. kfree(nuc900_wdt);
  252. return ret;
  253. }
  254. static int __devexit nuc900wdt_remove(struct platform_device *pdev)
  255. {
  256. misc_deregister(&nuc900wdt_miscdev);
  257. clk_disable(nuc900_wdt->wdt_clock);
  258. clk_put(nuc900_wdt->wdt_clock);
  259. iounmap(nuc900_wdt->wdt_base);
  260. release_mem_region(nuc900_wdt->res->start,
  261. resource_size(nuc900_wdt->res));
  262. kfree(nuc900_wdt);
  263. return 0;
  264. }
  265. static struct platform_driver nuc900wdt_driver = {
  266. .probe = nuc900wdt_probe,
  267. .remove = __devexit_p(nuc900wdt_remove),
  268. .driver = {
  269. .name = "nuc900-wdt",
  270. .owner = THIS_MODULE,
  271. },
  272. };
  273. static int __init nuc900_wdt_init(void)
  274. {
  275. return platform_driver_register(&nuc900wdt_driver);
  276. }
  277. static void __exit nuc900_wdt_exit(void)
  278. {
  279. platform_driver_unregister(&nuc900wdt_driver);
  280. }
  281. module_init(nuc900_wdt_init);
  282. module_exit(nuc900_wdt_exit);
  283. MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
  284. MODULE_DESCRIPTION("Watchdog driver for NUC900");
  285. MODULE_LICENSE("GPL");
  286. MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
  287. MODULE_ALIAS("platform:nuc900-wdt");