nuc900_wdt.c 8.0 KB

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