nic7018_wdt.c 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*
  2. * Copyright (C) 2016 National Instruments Corp.
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. */
  14. #include <linux/acpi.h>
  15. #include <linux/bitops.h>
  16. #include <linux/device.h>
  17. #include <linux/io.h>
  18. #include <linux/module.h>
  19. #include <linux/platform_device.h>
  20. #include <linux/watchdog.h>
  21. #define LOCK 0xA5
  22. #define UNLOCK 0x5A
  23. #define WDT_CTRL_RESET_EN BIT(7)
  24. #define WDT_RELOAD_PORT_EN BIT(7)
  25. #define WDT_CTRL 1
  26. #define WDT_RELOAD_CTRL 2
  27. #define WDT_PRESET_PRESCALE 4
  28. #define WDT_REG_LOCK 5
  29. #define WDT_COUNT 6
  30. #define WDT_RELOAD_PORT 7
  31. #define WDT_MIN_TIMEOUT 1
  32. #define WDT_MAX_TIMEOUT 464
  33. #define WDT_DEFAULT_TIMEOUT 80
  34. #define WDT_MAX_COUNTER 15
  35. static unsigned int timeout;
  36. module_param(timeout, uint, 0);
  37. MODULE_PARM_DESC(timeout,
  38. "Watchdog timeout in seconds. (default="
  39. __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
  40. static bool nowayout = WATCHDOG_NOWAYOUT;
  41. module_param(nowayout, bool, 0);
  42. MODULE_PARM_DESC(nowayout,
  43. "Watchdog cannot be stopped once started. (default="
  44. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  45. struct nic7018_wdt {
  46. u16 io_base;
  47. u32 period;
  48. struct watchdog_device wdd;
  49. };
  50. struct nic7018_config {
  51. u32 period;
  52. u8 divider;
  53. };
  54. static const struct nic7018_config nic7018_configs[] = {
  55. { 2, 4 },
  56. { 32, 5 },
  57. };
  58. static inline u32 nic7018_timeout(u32 period, u8 counter)
  59. {
  60. return period * counter - period / 2;
  61. }
  62. static const struct nic7018_config *nic7018_get_config(u32 timeout,
  63. u8 *counter)
  64. {
  65. const struct nic7018_config *config;
  66. u8 count;
  67. if (timeout < 30 && timeout != 16) {
  68. config = &nic7018_configs[0];
  69. count = timeout / 2 + 1;
  70. } else {
  71. config = &nic7018_configs[1];
  72. count = DIV_ROUND_UP(timeout + 16, 32);
  73. if (count > WDT_MAX_COUNTER)
  74. count = WDT_MAX_COUNTER;
  75. }
  76. *counter = count;
  77. return config;
  78. }
  79. static int nic7018_set_timeout(struct watchdog_device *wdd,
  80. unsigned int timeout)
  81. {
  82. struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
  83. const struct nic7018_config *config;
  84. u8 counter;
  85. config = nic7018_get_config(timeout, &counter);
  86. outb(counter << 4 | config->divider,
  87. wdt->io_base + WDT_PRESET_PRESCALE);
  88. wdd->timeout = nic7018_timeout(config->period, counter);
  89. wdt->period = config->period;
  90. return 0;
  91. }
  92. static int nic7018_start(struct watchdog_device *wdd)
  93. {
  94. struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
  95. u8 control;
  96. nic7018_set_timeout(wdd, wdd->timeout);
  97. control = inb(wdt->io_base + WDT_RELOAD_CTRL);
  98. outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL);
  99. outb(1, wdt->io_base + WDT_RELOAD_PORT);
  100. control = inb(wdt->io_base + WDT_CTRL);
  101. outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL);
  102. return 0;
  103. }
  104. static int nic7018_stop(struct watchdog_device *wdd)
  105. {
  106. struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
  107. outb(0, wdt->io_base + WDT_CTRL);
  108. outb(0, wdt->io_base + WDT_RELOAD_CTRL);
  109. outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE);
  110. return 0;
  111. }
  112. static int nic7018_ping(struct watchdog_device *wdd)
  113. {
  114. struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
  115. outb(1, wdt->io_base + WDT_RELOAD_PORT);
  116. return 0;
  117. }
  118. static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd)
  119. {
  120. struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
  121. u8 count;
  122. count = inb(wdt->io_base + WDT_COUNT) & 0xF;
  123. if (!count)
  124. return 0;
  125. return nic7018_timeout(wdt->period, count);
  126. }
  127. static const struct watchdog_info nic7018_wdd_info = {
  128. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
  129. .identity = "NIC7018 Watchdog",
  130. };
  131. static const struct watchdog_ops nic7018_wdd_ops = {
  132. .owner = THIS_MODULE,
  133. .start = nic7018_start,
  134. .stop = nic7018_stop,
  135. .ping = nic7018_ping,
  136. .set_timeout = nic7018_set_timeout,
  137. .get_timeleft = nic7018_get_timeleft,
  138. };
  139. static int nic7018_probe(struct platform_device *pdev)
  140. {
  141. struct device *dev = &pdev->dev;
  142. struct watchdog_device *wdd;
  143. struct nic7018_wdt *wdt;
  144. struct resource *io_rc;
  145. int ret;
  146. wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
  147. if (!wdt)
  148. return -ENOMEM;
  149. platform_set_drvdata(pdev, wdt);
  150. io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
  151. if (!io_rc) {
  152. dev_err(dev, "missing IO resources\n");
  153. return -EINVAL;
  154. }
  155. if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
  156. KBUILD_MODNAME)) {
  157. dev_err(dev, "failed to get IO region\n");
  158. return -EBUSY;
  159. }
  160. wdt->io_base = io_rc->start;
  161. wdd = &wdt->wdd;
  162. wdd->info = &nic7018_wdd_info;
  163. wdd->ops = &nic7018_wdd_ops;
  164. wdd->min_timeout = WDT_MIN_TIMEOUT;
  165. wdd->max_timeout = WDT_MAX_TIMEOUT;
  166. wdd->timeout = WDT_DEFAULT_TIMEOUT;
  167. wdd->parent = dev;
  168. watchdog_set_drvdata(wdd, wdt);
  169. watchdog_set_nowayout(wdd, nowayout);
  170. ret = watchdog_init_timeout(wdd, timeout, dev);
  171. if (ret)
  172. dev_warn(dev, "unable to set timeout value, using default\n");
  173. /* Unlock WDT register */
  174. outb(UNLOCK, wdt->io_base + WDT_REG_LOCK);
  175. ret = watchdog_register_device(wdd);
  176. if (ret) {
  177. outb(LOCK, wdt->io_base + WDT_REG_LOCK);
  178. dev_err(dev, "failed to register watchdog\n");
  179. return ret;
  180. }
  181. dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
  182. wdt->io_base, timeout, nowayout);
  183. return 0;
  184. }
  185. static int nic7018_remove(struct platform_device *pdev)
  186. {
  187. struct nic7018_wdt *wdt = platform_get_drvdata(pdev);
  188. watchdog_unregister_device(&wdt->wdd);
  189. /* Lock WDT register */
  190. outb(LOCK, wdt->io_base + WDT_REG_LOCK);
  191. return 0;
  192. }
  193. static const struct acpi_device_id nic7018_device_ids[] = {
  194. {"NIC7018", 0},
  195. {"", 0},
  196. };
  197. MODULE_DEVICE_TABLE(acpi, nic7018_device_ids);
  198. static struct platform_driver watchdog_driver = {
  199. .probe = nic7018_probe,
  200. .remove = nic7018_remove,
  201. .driver = {
  202. .name = KBUILD_MODNAME,
  203. .acpi_match_table = ACPI_PTR(nic7018_device_ids),
  204. },
  205. };
  206. module_platform_driver(watchdog_driver);
  207. MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver");
  208. MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
  209. MODULE_LICENSE("GPL");