ar7_wdt.c 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * drivers/watchdog/ar7_wdt.c
  4. *
  5. * Copyright (C) 2007 Nicolas Thill <[email protected]>
  6. * Copyright (c) 2005 Enrik Berkhan <[email protected]>
  7. *
  8. * Some code taken from:
  9. * National Semiconductor SCx200 Watchdog support
  10. * Copyright (c) 2001,2002 Christer Weinigel <[email protected]>
  11. *
  12. */
  13. #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  14. #include <linux/module.h>
  15. #include <linux/moduleparam.h>
  16. #include <linux/errno.h>
  17. #include <linux/miscdevice.h>
  18. #include <linux/platform_device.h>
  19. #include <linux/watchdog.h>
  20. #include <linux/fs.h>
  21. #include <linux/ioport.h>
  22. #include <linux/io.h>
  23. #include <linux/uaccess.h>
  24. #include <linux/clk.h>
  25. #include <asm/addrspace.h>
  26. #include <asm/mach-ar7/ar7.h>
  27. #define LONGNAME "TI AR7 Watchdog Timer"
  28. MODULE_AUTHOR("Nicolas Thill <[email protected]>");
  29. MODULE_DESCRIPTION(LONGNAME);
  30. MODULE_LICENSE("GPL");
  31. static int margin = 60;
  32. module_param(margin, int, 0);
  33. MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  34. static bool nowayout = WATCHDOG_NOWAYOUT;
  35. module_param(nowayout, bool, 0);
  36. MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  37. #define READ_REG(x) readl((void __iomem *)&(x))
  38. #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
  39. struct ar7_wdt {
  40. u32 kick_lock;
  41. u32 kick;
  42. u32 change_lock;
  43. u32 change;
  44. u32 disable_lock;
  45. u32 disable;
  46. u32 prescale_lock;
  47. u32 prescale;
  48. };
  49. static unsigned long wdt_is_open;
  50. static unsigned expect_close;
  51. static DEFINE_SPINLOCK(wdt_lock);
  52. /* XXX currently fixed, allows max margin ~68.72 secs */
  53. #define prescale_value 0xffff
  54. /* Pointer to the remapped WDT IO space */
  55. static struct ar7_wdt *ar7_wdt;
  56. static struct clk *vbus_clk;
  57. static void ar7_wdt_kick(u32 value)
  58. {
  59. WRITE_REG(ar7_wdt->kick_lock, 0x5555);
  60. if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
  61. WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
  62. if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
  63. WRITE_REG(ar7_wdt->kick, value);
  64. return;
  65. }
  66. }
  67. pr_err("failed to unlock WDT kick reg\n");
  68. }
  69. static void ar7_wdt_prescale(u32 value)
  70. {
  71. WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
  72. if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
  73. WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
  74. if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
  75. WRITE_REG(ar7_wdt->prescale, value);
  76. return;
  77. }
  78. }
  79. pr_err("failed to unlock WDT prescale reg\n");
  80. }
  81. static void ar7_wdt_change(u32 value)
  82. {
  83. WRITE_REG(ar7_wdt->change_lock, 0x6666);
  84. if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
  85. WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
  86. if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
  87. WRITE_REG(ar7_wdt->change, value);
  88. return;
  89. }
  90. }
  91. pr_err("failed to unlock WDT change reg\n");
  92. }
  93. static void ar7_wdt_disable(u32 value)
  94. {
  95. WRITE_REG(ar7_wdt->disable_lock, 0x7777);
  96. if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
  97. WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
  98. if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
  99. WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
  100. if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
  101. WRITE_REG(ar7_wdt->disable, value);
  102. return;
  103. }
  104. }
  105. }
  106. pr_err("failed to unlock WDT disable reg\n");
  107. }
  108. static void ar7_wdt_update_margin(int new_margin)
  109. {
  110. u32 change;
  111. u32 vbus_rate;
  112. vbus_rate = clk_get_rate(vbus_clk);
  113. change = new_margin * (vbus_rate / prescale_value);
  114. if (change < 1)
  115. change = 1;
  116. if (change > 0xffff)
  117. change = 0xffff;
  118. ar7_wdt_change(change);
  119. margin = change * prescale_value / vbus_rate;
  120. pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
  121. margin, prescale_value, change, vbus_rate);
  122. }
  123. static void ar7_wdt_enable_wdt(void)
  124. {
  125. pr_debug("enabling watchdog timer\n");
  126. ar7_wdt_disable(1);
  127. ar7_wdt_kick(1);
  128. }
  129. static void ar7_wdt_disable_wdt(void)
  130. {
  131. pr_debug("disabling watchdog timer\n");
  132. ar7_wdt_disable(0);
  133. }
  134. static int ar7_wdt_open(struct inode *inode, struct file *file)
  135. {
  136. /* only allow one at a time */
  137. if (test_and_set_bit(0, &wdt_is_open))
  138. return -EBUSY;
  139. ar7_wdt_enable_wdt();
  140. expect_close = 0;
  141. return stream_open(inode, file);
  142. }
  143. static int ar7_wdt_release(struct inode *inode, struct file *file)
  144. {
  145. if (!expect_close)
  146. pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
  147. else if (!nowayout)
  148. ar7_wdt_disable_wdt();
  149. clear_bit(0, &wdt_is_open);
  150. return 0;
  151. }
  152. static ssize_t ar7_wdt_write(struct file *file, const char *data,
  153. size_t len, loff_t *ppos)
  154. {
  155. /* check for a magic close character */
  156. if (len) {
  157. size_t i;
  158. spin_lock(&wdt_lock);
  159. ar7_wdt_kick(1);
  160. spin_unlock(&wdt_lock);
  161. expect_close = 0;
  162. for (i = 0; i < len; ++i) {
  163. char c;
  164. if (get_user(c, data + i))
  165. return -EFAULT;
  166. if (c == 'V')
  167. expect_close = 1;
  168. }
  169. }
  170. return len;
  171. }
  172. static long ar7_wdt_ioctl(struct file *file,
  173. unsigned int cmd, unsigned long arg)
  174. {
  175. static const struct watchdog_info ident = {
  176. .identity = LONGNAME,
  177. .firmware_version = 1,
  178. .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
  179. WDIOF_MAGICCLOSE),
  180. };
  181. int new_margin;
  182. switch (cmd) {
  183. case WDIOC_GETSUPPORT:
  184. if (copy_to_user((struct watchdog_info *)arg, &ident,
  185. sizeof(ident)))
  186. return -EFAULT;
  187. return 0;
  188. case WDIOC_GETSTATUS:
  189. case WDIOC_GETBOOTSTATUS:
  190. if (put_user(0, (int *)arg))
  191. return -EFAULT;
  192. return 0;
  193. case WDIOC_KEEPALIVE:
  194. ar7_wdt_kick(1);
  195. return 0;
  196. case WDIOC_SETTIMEOUT:
  197. if (get_user(new_margin, (int *)arg))
  198. return -EFAULT;
  199. if (new_margin < 1)
  200. return -EINVAL;
  201. spin_lock(&wdt_lock);
  202. ar7_wdt_update_margin(new_margin);
  203. ar7_wdt_kick(1);
  204. spin_unlock(&wdt_lock);
  205. fallthrough;
  206. case WDIOC_GETTIMEOUT:
  207. if (put_user(margin, (int *)arg))
  208. return -EFAULT;
  209. return 0;
  210. default:
  211. return -ENOTTY;
  212. }
  213. }
  214. static const struct file_operations ar7_wdt_fops = {
  215. .owner = THIS_MODULE,
  216. .write = ar7_wdt_write,
  217. .unlocked_ioctl = ar7_wdt_ioctl,
  218. .compat_ioctl = compat_ptr_ioctl,
  219. .open = ar7_wdt_open,
  220. .release = ar7_wdt_release,
  221. .llseek = no_llseek,
  222. };
  223. static struct miscdevice ar7_wdt_miscdev = {
  224. .minor = WATCHDOG_MINOR,
  225. .name = "watchdog",
  226. .fops = &ar7_wdt_fops,
  227. };
  228. static int ar7_wdt_probe(struct platform_device *pdev)
  229. {
  230. int rc;
  231. ar7_wdt = devm_platform_ioremap_resource_byname(pdev, "regs");
  232. if (IS_ERR(ar7_wdt))
  233. return PTR_ERR(ar7_wdt);
  234. vbus_clk = clk_get(NULL, "vbus");
  235. if (IS_ERR(vbus_clk)) {
  236. pr_err("could not get vbus clock\n");
  237. return PTR_ERR(vbus_clk);
  238. }
  239. ar7_wdt_disable_wdt();
  240. ar7_wdt_prescale(prescale_value);
  241. ar7_wdt_update_margin(margin);
  242. rc = misc_register(&ar7_wdt_miscdev);
  243. if (rc) {
  244. pr_err("unable to register misc device\n");
  245. goto out;
  246. }
  247. return 0;
  248. out:
  249. clk_put(vbus_clk);
  250. vbus_clk = NULL;
  251. return rc;
  252. }
  253. static int ar7_wdt_remove(struct platform_device *pdev)
  254. {
  255. misc_deregister(&ar7_wdt_miscdev);
  256. clk_put(vbus_clk);
  257. vbus_clk = NULL;
  258. return 0;
  259. }
  260. static void ar7_wdt_shutdown(struct platform_device *pdev)
  261. {
  262. if (!nowayout)
  263. ar7_wdt_disable_wdt();
  264. }
  265. static struct platform_driver ar7_wdt_driver = {
  266. .probe = ar7_wdt_probe,
  267. .remove = ar7_wdt_remove,
  268. .shutdown = ar7_wdt_shutdown,
  269. .driver = {
  270. .name = "ar7_wdt",
  271. },
  272. };
  273. module_platform_driver(ar7_wdt_driver);