rc32434_wdt.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * IDT Interprise 79RC32434 watchdog driver
  4. *
  5. * Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
  6. * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
  7. *
  8. * based on
  9. * SoftDog 0.05: A Software Watchdog Device
  10. *
  11. * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  12. * All Rights Reserved.
  13. */
  14. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  15. #include <linux/module.h> /* For module specific items */
  16. #include <linux/moduleparam.h> /* For new moduleparam's */
  17. #include <linux/types.h> /* For standard types (like size_t) */
  18. #include <linux/errno.h> /* For the -ENODEV/... values */
  19. #include <linux/kernel.h> /* For printk/panic/... */
  20. #include <linux/fs.h> /* For file operations */
  21. #include <linux/miscdevice.h> /* For struct miscdevice */
  22. #include <linux/watchdog.h> /* For the watchdog specific items */
  23. #include <linux/init.h> /* For __init/__exit/... */
  24. #include <linux/platform_device.h> /* For platform_driver framework */
  25. #include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
  26. #include <linux/uaccess.h> /* For copy_to_user/put_user/... */
  27. #include <linux/io.h> /* For devm_ioremap_nocache */
  28. #include <asm/mach-rc32434/integ.h> /* For the Watchdog registers */
  29. #define VERSION "1.0"
  30. static struct {
  31. unsigned long inuse;
  32. spinlock_t io_lock;
  33. } rc32434_wdt_device;
  34. static struct integ __iomem *wdt_reg;
  35. static int expect_close;
  36. /* Board internal clock speed in Hz,
  37. * the watchdog timer ticks at. */
  38. extern unsigned int idt_cpu_freq;
  39. /* translate wtcompare value to seconds and vice versa */
  40. #define WTCOMP2SEC(x) (x / idt_cpu_freq)
  41. #define SEC2WTCOMP(x) (x * idt_cpu_freq)
  42. /* Use a default timeout of 20s. This should be
  43. * safe for CPU clock speeds up to 400MHz, as
  44. * ((2 ^ 32) - 1) / (400MHz / 2) = 21s. */
  45. #define WATCHDOG_TIMEOUT 20
  46. static int timeout = WATCHDOG_TIMEOUT;
  47. module_param(timeout, int, 0);
  48. MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="
  49. __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  50. static bool nowayout = WATCHDOG_NOWAYOUT;
  51. module_param(nowayout, bool, 0);
  52. MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  53. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  54. /* apply or and nand masks to data read from addr and write back */
  55. #define SET_BITS(addr, or, nand) \
  56. writel((readl(&addr) | or) & ~nand, &addr)
  57. static int rc32434_wdt_set(int new_timeout)
  58. {
  59. int max_to = WTCOMP2SEC((u32)-1);
  60. if (new_timeout < 0 || new_timeout > max_to) {
  61. pr_err("timeout value must be between 0 and %d\n", max_to);
  62. return -EINVAL;
  63. }
  64. timeout = new_timeout;
  65. spin_lock(&rc32434_wdt_device.io_lock);
  66. writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
  67. spin_unlock(&rc32434_wdt_device.io_lock);
  68. return 0;
  69. }
  70. static void rc32434_wdt_start(void)
  71. {
  72. u32 or, nand;
  73. spin_lock(&rc32434_wdt_device.io_lock);
  74. /* zero the counter before enabling */
  75. writel(0, &wdt_reg->wtcount);
  76. /* don't generate a non-maskable interrupt,
  77. * do a warm reset instead */
  78. nand = 1 << RC32434_ERR_WNE;
  79. or = 1 << RC32434_ERR_WRE;
  80. /* reset the ERRCS timeout bit in case it's set */
  81. nand |= 1 << RC32434_ERR_WTO;
  82. SET_BITS(wdt_reg->errcs, or, nand);
  83. /* set the timeout (either default or based on module param) */
  84. rc32434_wdt_set(timeout);
  85. /* reset WTC timeout bit and enable WDT */
  86. nand = 1 << RC32434_WTC_TO;
  87. or = 1 << RC32434_WTC_EN;
  88. SET_BITS(wdt_reg->wtc, or, nand);
  89. spin_unlock(&rc32434_wdt_device.io_lock);
  90. pr_info("Started watchdog timer\n");
  91. }
  92. static void rc32434_wdt_stop(void)
  93. {
  94. spin_lock(&rc32434_wdt_device.io_lock);
  95. /* Disable WDT */
  96. SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
  97. spin_unlock(&rc32434_wdt_device.io_lock);
  98. pr_info("Stopped watchdog timer\n");
  99. }
  100. static void rc32434_wdt_ping(void)
  101. {
  102. spin_lock(&rc32434_wdt_device.io_lock);
  103. writel(0, &wdt_reg->wtcount);
  104. spin_unlock(&rc32434_wdt_device.io_lock);
  105. }
  106. static int rc32434_wdt_open(struct inode *inode, struct file *file)
  107. {
  108. if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
  109. return -EBUSY;
  110. if (nowayout)
  111. __module_get(THIS_MODULE);
  112. rc32434_wdt_start();
  113. rc32434_wdt_ping();
  114. return stream_open(inode, file);
  115. }
  116. static int rc32434_wdt_release(struct inode *inode, struct file *file)
  117. {
  118. if (expect_close == 42) {
  119. rc32434_wdt_stop();
  120. module_put(THIS_MODULE);
  121. } else {
  122. pr_crit("device closed unexpectedly. WDT will not stop!\n");
  123. rc32434_wdt_ping();
  124. }
  125. clear_bit(0, &rc32434_wdt_device.inuse);
  126. return 0;
  127. }
  128. static ssize_t rc32434_wdt_write(struct file *file, const char *data,
  129. size_t len, loff_t *ppos)
  130. {
  131. if (len) {
  132. if (!nowayout) {
  133. size_t i;
  134. /* In case it was set long ago */
  135. expect_close = 0;
  136. for (i = 0; i != len; i++) {
  137. char c;
  138. if (get_user(c, data + i))
  139. return -EFAULT;
  140. if (c == 'V')
  141. expect_close = 42;
  142. }
  143. }
  144. rc32434_wdt_ping();
  145. return len;
  146. }
  147. return 0;
  148. }
  149. static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
  150. unsigned long arg)
  151. {
  152. void __user *argp = (void __user *)arg;
  153. int new_timeout;
  154. unsigned int value;
  155. static const struct watchdog_info ident = {
  156. .options = WDIOF_SETTIMEOUT |
  157. WDIOF_KEEPALIVEPING |
  158. WDIOF_MAGICCLOSE,
  159. .identity = "RC32434_WDT Watchdog",
  160. };
  161. switch (cmd) {
  162. case WDIOC_GETSUPPORT:
  163. if (copy_to_user(argp, &ident, sizeof(ident)))
  164. return -EFAULT;
  165. break;
  166. case WDIOC_GETSTATUS:
  167. case WDIOC_GETBOOTSTATUS:
  168. value = 0;
  169. if (copy_to_user(argp, &value, sizeof(int)))
  170. return -EFAULT;
  171. break;
  172. case WDIOC_SETOPTIONS:
  173. if (copy_from_user(&value, argp, sizeof(int)))
  174. return -EFAULT;
  175. switch (value) {
  176. case WDIOS_ENABLECARD:
  177. rc32434_wdt_start();
  178. break;
  179. case WDIOS_DISABLECARD:
  180. rc32434_wdt_stop();
  181. break;
  182. default:
  183. return -EINVAL;
  184. }
  185. break;
  186. case WDIOC_KEEPALIVE:
  187. rc32434_wdt_ping();
  188. break;
  189. case WDIOC_SETTIMEOUT:
  190. if (copy_from_user(&new_timeout, argp, sizeof(int)))
  191. return -EFAULT;
  192. if (rc32434_wdt_set(new_timeout))
  193. return -EINVAL;
  194. /* Fall through */
  195. case WDIOC_GETTIMEOUT:
  196. return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
  197. default:
  198. return -ENOTTY;
  199. }
  200. return 0;
  201. }
  202. static const struct file_operations rc32434_wdt_fops = {
  203. .owner = THIS_MODULE,
  204. .llseek = no_llseek,
  205. .write = rc32434_wdt_write,
  206. .unlocked_ioctl = rc32434_wdt_ioctl,
  207. .open = rc32434_wdt_open,
  208. .release = rc32434_wdt_release,
  209. };
  210. static struct miscdevice rc32434_wdt_miscdev = {
  211. .minor = WATCHDOG_MINOR,
  212. .name = "watchdog",
  213. .fops = &rc32434_wdt_fops,
  214. };
  215. static int rc32434_wdt_probe(struct platform_device *pdev)
  216. {
  217. int ret;
  218. struct resource *r;
  219. r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
  220. if (!r) {
  221. pr_err("failed to retrieve resources\n");
  222. return -ENODEV;
  223. }
  224. wdt_reg = devm_ioremap_nocache(&pdev->dev, r->start, resource_size(r));
  225. if (!wdt_reg) {
  226. pr_err("failed to remap I/O resources\n");
  227. return -ENXIO;
  228. }
  229. spin_lock_init(&rc32434_wdt_device.io_lock);
  230. /* Make sure the watchdog is not running */
  231. rc32434_wdt_stop();
  232. /* Check that the heartbeat value is within it's range;
  233. * if not reset to the default */
  234. if (rc32434_wdt_set(timeout)) {
  235. rc32434_wdt_set(WATCHDOG_TIMEOUT);
  236. pr_info("timeout value must be between 0 and %d\n",
  237. WTCOMP2SEC((u32)-1));
  238. }
  239. ret = misc_register(&rc32434_wdt_miscdev);
  240. if (ret < 0) {
  241. pr_err("failed to register watchdog device\n");
  242. return ret;
  243. }
  244. pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",
  245. timeout);
  246. return 0;
  247. }
  248. static int rc32434_wdt_remove(struct platform_device *pdev)
  249. {
  250. misc_deregister(&rc32434_wdt_miscdev);
  251. return 0;
  252. }
  253. static void rc32434_wdt_shutdown(struct platform_device *pdev)
  254. {
  255. rc32434_wdt_stop();
  256. }
  257. static struct platform_driver rc32434_wdt_driver = {
  258. .probe = rc32434_wdt_probe,
  259. .remove = rc32434_wdt_remove,
  260. .shutdown = rc32434_wdt_shutdown,
  261. .driver = {
  262. .name = "rc32434_wdt",
  263. }
  264. };
  265. module_platform_driver(rc32434_wdt_driver);
  266. MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
  267. "Florian Fainelli <florian@openwrt.org>");
  268. MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
  269. MODULE_LICENSE("GPL");