ie6xx_wdt.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /*
  2. * Intel Atom E6xx Watchdog driver
  3. *
  4. * Copyright (C) 2011 Alexander Stein
  5. * <alexander.stein@systec-electronic.com>
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of version 2 of the GNU General
  9. * Public License as published by the Free Software Foundation.
  10. *
  11. * This program is distributed in the hope that it will be
  12. * useful, but WITHOUT ANY WARRANTY; without even the implied
  13. * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  14. * PURPOSE. See the GNU General Public License for more details.
  15. * You should have received a copy of the GNU General Public
  16. * License along with this program; if not, write to the Free
  17. * Software Foundation, Inc., 59 Temple Place - Suite 330,
  18. * Boston, MA 02111-1307, USA.
  19. * The full GNU General Public License is included in this
  20. * distribution in the file called COPYING.
  21. *
  22. */
  23. #include <linux/module.h>
  24. #include <linux/moduleparam.h>
  25. #include <linux/platform_device.h>
  26. #include <linux/io.h>
  27. #include <linux/kernel.h>
  28. #include <linux/types.h>
  29. #include <linux/watchdog.h>
  30. #include <linux/miscdevice.h>
  31. #include <linux/seq_file.h>
  32. #include <linux/debugfs.h>
  33. #include <linux/uaccess.h>
  34. #include <linux/spinlock.h>
  35. #define DRIVER_NAME "ie6xx_wdt"
  36. #define PV1 0x00
  37. #define PV2 0x04
  38. #define RR0 0x0c
  39. #define RR1 0x0d
  40. #define WDT_RELOAD 0x01
  41. #define WDT_TOUT 0x02
  42. #define WDTCR 0x10
  43. #define WDT_PRE_SEL 0x04
  44. #define WDT_RESET_SEL 0x08
  45. #define WDT_RESET_EN 0x10
  46. #define WDT_TOUT_EN 0x20
  47. #define DCR 0x14
  48. #define WDTLR 0x18
  49. #define WDT_LOCK 0x01
  50. #define WDT_ENABLE 0x02
  51. #define WDT_TOUT_CNF 0x03
  52. #define MIN_TIME 1
  53. #define MAX_TIME (10 * 60) /* 10 minutes */
  54. #define DEFAULT_TIME 60
  55. static unsigned int timeout = DEFAULT_TIME;
  56. module_param(timeout, uint, 0);
  57. MODULE_PARM_DESC(timeout,
  58. "Default Watchdog timer setting ("
  59. __MODULE_STRING(DEFAULT_TIME) "s)."
  60. "The range is from 1 to 600");
  61. static bool nowayout = WATCHDOG_NOWAYOUT;
  62. module_param(nowayout, bool, 0);
  63. MODULE_PARM_DESC(nowayout,
  64. "Watchdog cannot be stopped once started (default="
  65. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  66. static u8 resetmode = 0x10;
  67. module_param(resetmode, byte, 0);
  68. MODULE_PARM_DESC(resetmode,
  69. "Resetmode bits: 0x08 warm reset (cold reset otherwise), "
  70. "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
  71. static struct {
  72. unsigned short sch_wdtba;
  73. struct spinlock unlock_sequence;
  74. #ifdef CONFIG_DEBUG_FS
  75. struct dentry *debugfs;
  76. #endif
  77. } ie6xx_wdt_data;
  78. /*
  79. * This is needed to write to preload and reload registers
  80. * struct ie6xx_wdt_data.unlock_sequence must be used
  81. * to prevent sequence interrupts
  82. */
  83. static void ie6xx_wdt_unlock_registers(void)
  84. {
  85. outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
  86. outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
  87. }
  88. static int ie6xx_wdt_ping(struct watchdog_device *wdd)
  89. {
  90. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  91. ie6xx_wdt_unlock_registers();
  92. outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
  93. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  94. return 0;
  95. }
  96. static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
  97. {
  98. u32 preload;
  99. u64 clock;
  100. u8 wdtcr;
  101. /* Watchdog clock is PCI Clock (33MHz) */
  102. clock = 33000000;
  103. /* and the preload value is loaded into [34:15] of the down counter */
  104. preload = (t * clock) >> 15;
  105. /*
  106. * Manual states preload must be one less.
  107. * Does not wrap as t is at least 1
  108. */
  109. preload -= 1;
  110. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  111. /* Set ResetMode & Enable prescaler for range 10ms to 10 min */
  112. wdtcr = resetmode & 0x38;
  113. outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
  114. ie6xx_wdt_unlock_registers();
  115. outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
  116. ie6xx_wdt_unlock_registers();
  117. outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
  118. ie6xx_wdt_unlock_registers();
  119. outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
  120. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  121. wdd->timeout = t;
  122. return 0;
  123. }
  124. static int ie6xx_wdt_start(struct watchdog_device *wdd)
  125. {
  126. ie6xx_wdt_set_timeout(wdd, wdd->timeout);
  127. /* Enable the watchdog timer */
  128. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  129. outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
  130. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  131. return 0;
  132. }
  133. static int ie6xx_wdt_stop(struct watchdog_device *wdd)
  134. {
  135. if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
  136. return -1;
  137. /* Disable the watchdog timer */
  138. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  139. outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
  140. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  141. return 0;
  142. }
  143. static const struct watchdog_info ie6xx_wdt_info = {
  144. .identity = "Intel Atom E6xx Watchdog",
  145. .options = WDIOF_SETTIMEOUT |
  146. WDIOF_MAGICCLOSE |
  147. WDIOF_KEEPALIVEPING,
  148. };
  149. static const struct watchdog_ops ie6xx_wdt_ops = {
  150. .owner = THIS_MODULE,
  151. .start = ie6xx_wdt_start,
  152. .stop = ie6xx_wdt_stop,
  153. .ping = ie6xx_wdt_ping,
  154. .set_timeout = ie6xx_wdt_set_timeout,
  155. };
  156. static struct watchdog_device ie6xx_wdt_dev = {
  157. .info = &ie6xx_wdt_info,
  158. .ops = &ie6xx_wdt_ops,
  159. .min_timeout = MIN_TIME,
  160. .max_timeout = MAX_TIME,
  161. };
  162. #ifdef CONFIG_DEBUG_FS
  163. static int ie6xx_wdt_dbg_show(struct seq_file *s, void *unused)
  164. {
  165. seq_printf(s, "PV1 = 0x%08x\n",
  166. inl(ie6xx_wdt_data.sch_wdtba + PV1));
  167. seq_printf(s, "PV2 = 0x%08x\n",
  168. inl(ie6xx_wdt_data.sch_wdtba + PV2));
  169. seq_printf(s, "RR = 0x%08x\n",
  170. inw(ie6xx_wdt_data.sch_wdtba + RR0));
  171. seq_printf(s, "WDTCR = 0x%08x\n",
  172. inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
  173. seq_printf(s, "DCR = 0x%08x\n",
  174. inl(ie6xx_wdt_data.sch_wdtba + DCR));
  175. seq_printf(s, "WDTLR = 0x%08x\n",
  176. inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
  177. seq_printf(s, "\n");
  178. return 0;
  179. }
  180. static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file)
  181. {
  182. return single_open(file, ie6xx_wdt_dbg_show, NULL);
  183. }
  184. static const struct file_operations ie6xx_wdt_dbg_operations = {
  185. .open = ie6xx_wdt_dbg_open,
  186. .read = seq_read,
  187. .llseek = seq_lseek,
  188. .release = single_release,
  189. };
  190. static void ie6xx_wdt_debugfs_init(void)
  191. {
  192. /* /sys/kernel/debug/ie6xx_wdt */
  193. ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
  194. S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_dbg_operations);
  195. }
  196. static void ie6xx_wdt_debugfs_exit(void)
  197. {
  198. debugfs_remove(ie6xx_wdt_data.debugfs);
  199. }
  200. #else
  201. static void ie6xx_wdt_debugfs_init(void)
  202. {
  203. }
  204. static void ie6xx_wdt_debugfs_exit(void)
  205. {
  206. }
  207. #endif
  208. static int ie6xx_wdt_probe(struct platform_device *pdev)
  209. {
  210. struct resource *res;
  211. u8 wdtlr;
  212. int ret;
  213. res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  214. if (!res)
  215. return -ENODEV;
  216. if (!request_region(res->start, resource_size(res), pdev->name)) {
  217. dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
  218. (u64)res->start);
  219. return -EBUSY;
  220. }
  221. ie6xx_wdt_data.sch_wdtba = res->start;
  222. dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
  223. ie6xx_wdt_dev.timeout = timeout;
  224. watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
  225. spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
  226. wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
  227. if (wdtlr & WDT_LOCK)
  228. dev_warn(&pdev->dev,
  229. "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
  230. ie6xx_wdt_debugfs_init();
  231. ret = watchdog_register_device(&ie6xx_wdt_dev);
  232. if (ret) {
  233. dev_err(&pdev->dev,
  234. "Watchdog timer: cannot register device (err =%d)\n",
  235. ret);
  236. goto misc_register_error;
  237. }
  238. return 0;
  239. misc_register_error:
  240. ie6xx_wdt_debugfs_exit();
  241. release_region(res->start, resource_size(res));
  242. ie6xx_wdt_data.sch_wdtba = 0;
  243. return ret;
  244. }
  245. static int ie6xx_wdt_remove(struct platform_device *pdev)
  246. {
  247. struct resource *res;
  248. res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  249. ie6xx_wdt_stop(NULL);
  250. watchdog_unregister_device(&ie6xx_wdt_dev);
  251. ie6xx_wdt_debugfs_exit();
  252. release_region(res->start, resource_size(res));
  253. ie6xx_wdt_data.sch_wdtba = 0;
  254. return 0;
  255. }
  256. static struct platform_driver ie6xx_wdt_driver = {
  257. .probe = ie6xx_wdt_probe,
  258. .remove = ie6xx_wdt_remove,
  259. .driver = {
  260. .name = DRIVER_NAME,
  261. .owner = THIS_MODULE,
  262. },
  263. };
  264. static int __init ie6xx_wdt_init(void)
  265. {
  266. /* Check boot parameters to verify that their initial values */
  267. /* are in range. */
  268. if ((timeout < MIN_TIME) ||
  269. (timeout > MAX_TIME)) {
  270. pr_err("Watchdog timer: value of timeout %d (dec) "
  271. "is out of range from %d to %d (dec)\n",
  272. timeout, MIN_TIME, MAX_TIME);
  273. return -EINVAL;
  274. }
  275. return platform_driver_register(&ie6xx_wdt_driver);
  276. }
  277. static void __exit ie6xx_wdt_exit(void)
  278. {
  279. platform_driver_unregister(&ie6xx_wdt_driver);
  280. }
  281. late_initcall(ie6xx_wdt_init);
  282. module_exit(ie6xx_wdt_exit);
  283. MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
  284. MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
  285. MODULE_LICENSE("GPL");
  286. MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
  287. MODULE_ALIAS("platform:" DRIVER_NAME);