ie6xx_wdt.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  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/seq_file.h>
  31. #include <linux/debugfs.h>
  32. #include <linux/uaccess.h>
  33. #include <linux/spinlock.h>
  34. #define DRIVER_NAME "ie6xx_wdt"
  35. #define PV1 0x00
  36. #define PV2 0x04
  37. #define RR0 0x0c
  38. #define RR1 0x0d
  39. #define WDT_RELOAD 0x01
  40. #define WDT_TOUT 0x02
  41. #define WDTCR 0x10
  42. #define WDT_PRE_SEL 0x04
  43. #define WDT_RESET_SEL 0x08
  44. #define WDT_RESET_EN 0x10
  45. #define WDT_TOUT_EN 0x20
  46. #define DCR 0x14
  47. #define WDTLR 0x18
  48. #define WDT_LOCK 0x01
  49. #define WDT_ENABLE 0x02
  50. #define WDT_TOUT_CNF 0x03
  51. #define MIN_TIME 1
  52. #define MAX_TIME (10 * 60) /* 10 minutes */
  53. #define DEFAULT_TIME 60
  54. static unsigned int timeout = DEFAULT_TIME;
  55. module_param(timeout, uint, 0);
  56. MODULE_PARM_DESC(timeout,
  57. "Default Watchdog timer setting ("
  58. __MODULE_STRING(DEFAULT_TIME) "s)."
  59. "The range is from 1 to 600");
  60. static bool nowayout = WATCHDOG_NOWAYOUT;
  61. module_param(nowayout, bool, 0);
  62. MODULE_PARM_DESC(nowayout,
  63. "Watchdog cannot be stopped once started (default="
  64. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  65. static u8 resetmode = 0x10;
  66. module_param(resetmode, byte, 0);
  67. MODULE_PARM_DESC(resetmode,
  68. "Resetmode bits: 0x08 warm reset (cold reset otherwise), "
  69. "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
  70. static struct {
  71. unsigned short sch_wdtba;
  72. struct spinlock unlock_sequence;
  73. #ifdef CONFIG_DEBUG_FS
  74. struct dentry *debugfs;
  75. #endif
  76. } ie6xx_wdt_data;
  77. /*
  78. * This is needed to write to preload and reload registers
  79. * struct ie6xx_wdt_data.unlock_sequence must be used
  80. * to prevent sequence interrupts
  81. */
  82. static void ie6xx_wdt_unlock_registers(void)
  83. {
  84. outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
  85. outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
  86. }
  87. static int ie6xx_wdt_ping(struct watchdog_device *wdd)
  88. {
  89. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  90. ie6xx_wdt_unlock_registers();
  91. outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
  92. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  93. return 0;
  94. }
  95. static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
  96. {
  97. u32 preload;
  98. u64 clock;
  99. u8 wdtcr;
  100. /* Watchdog clock is PCI Clock (33MHz) */
  101. clock = 33000000;
  102. /* and the preload value is loaded into [34:15] of the down counter */
  103. preload = (t * clock) >> 15;
  104. /*
  105. * Manual states preload must be one less.
  106. * Does not wrap as t is at least 1
  107. */
  108. preload -= 1;
  109. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  110. /* Set ResetMode & Enable prescaler for range 10ms to 10 min */
  111. wdtcr = resetmode & 0x38;
  112. outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
  113. ie6xx_wdt_unlock_registers();
  114. outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
  115. ie6xx_wdt_unlock_registers();
  116. outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
  117. ie6xx_wdt_unlock_registers();
  118. outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
  119. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  120. wdd->timeout = t;
  121. return 0;
  122. }
  123. static int ie6xx_wdt_start(struct watchdog_device *wdd)
  124. {
  125. ie6xx_wdt_set_timeout(wdd, wdd->timeout);
  126. /* Enable the watchdog timer */
  127. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  128. outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
  129. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  130. return 0;
  131. }
  132. static int ie6xx_wdt_stop(struct watchdog_device *wdd)
  133. {
  134. if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
  135. return -1;
  136. /* Disable the watchdog timer */
  137. spin_lock(&ie6xx_wdt_data.unlock_sequence);
  138. outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
  139. spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  140. return 0;
  141. }
  142. static const struct watchdog_info ie6xx_wdt_info = {
  143. .identity = "Intel Atom E6xx Watchdog",
  144. .options = WDIOF_SETTIMEOUT |
  145. WDIOF_MAGICCLOSE |
  146. WDIOF_KEEPALIVEPING,
  147. };
  148. static const struct watchdog_ops ie6xx_wdt_ops = {
  149. .owner = THIS_MODULE,
  150. .start = ie6xx_wdt_start,
  151. .stop = ie6xx_wdt_stop,
  152. .ping = ie6xx_wdt_ping,
  153. .set_timeout = ie6xx_wdt_set_timeout,
  154. };
  155. static struct watchdog_device ie6xx_wdt_dev = {
  156. .info = &ie6xx_wdt_info,
  157. .ops = &ie6xx_wdt_ops,
  158. .min_timeout = MIN_TIME,
  159. .max_timeout = MAX_TIME,
  160. };
  161. #ifdef CONFIG_DEBUG_FS
  162. static int ie6xx_wdt_dbg_show(struct seq_file *s, void *unused)
  163. {
  164. seq_printf(s, "PV1 = 0x%08x\n",
  165. inl(ie6xx_wdt_data.sch_wdtba + PV1));
  166. seq_printf(s, "PV2 = 0x%08x\n",
  167. inl(ie6xx_wdt_data.sch_wdtba + PV2));
  168. seq_printf(s, "RR = 0x%08x\n",
  169. inw(ie6xx_wdt_data.sch_wdtba + RR0));
  170. seq_printf(s, "WDTCR = 0x%08x\n",
  171. inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
  172. seq_printf(s, "DCR = 0x%08x\n",
  173. inl(ie6xx_wdt_data.sch_wdtba + DCR));
  174. seq_printf(s, "WDTLR = 0x%08x\n",
  175. inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
  176. seq_printf(s, "\n");
  177. return 0;
  178. }
  179. static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file)
  180. {
  181. return single_open(file, ie6xx_wdt_dbg_show, NULL);
  182. }
  183. static const struct file_operations ie6xx_wdt_dbg_operations = {
  184. .open = ie6xx_wdt_dbg_open,
  185. .read = seq_read,
  186. .llseek = seq_lseek,
  187. .release = single_release,
  188. };
  189. static void ie6xx_wdt_debugfs_init(void)
  190. {
  191. /* /sys/kernel/debug/ie6xx_wdt */
  192. ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
  193. S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_dbg_operations);
  194. }
  195. static void ie6xx_wdt_debugfs_exit(void)
  196. {
  197. debugfs_remove(ie6xx_wdt_data.debugfs);
  198. }
  199. #else
  200. static void ie6xx_wdt_debugfs_init(void)
  201. {
  202. }
  203. static void ie6xx_wdt_debugfs_exit(void)
  204. {
  205. }
  206. #endif
  207. static int ie6xx_wdt_probe(struct platform_device *pdev)
  208. {
  209. struct resource *res;
  210. u8 wdtlr;
  211. int ret;
  212. res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  213. if (!res)
  214. return -ENODEV;
  215. if (!request_region(res->start, resource_size(res), pdev->name)) {
  216. dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
  217. (u64)res->start);
  218. return -EBUSY;
  219. }
  220. ie6xx_wdt_data.sch_wdtba = res->start;
  221. dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
  222. ie6xx_wdt_dev.timeout = timeout;
  223. watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
  224. ie6xx_wdt_dev.parent = &pdev->dev;
  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. },
  262. };
  263. static int __init ie6xx_wdt_init(void)
  264. {
  265. /* Check boot parameters to verify that their initial values */
  266. /* are in range. */
  267. if ((timeout < MIN_TIME) ||
  268. (timeout > MAX_TIME)) {
  269. pr_err("Watchdog timer: value of timeout %d (dec) "
  270. "is out of range from %d to %d (dec)\n",
  271. timeout, MIN_TIME, MAX_TIME);
  272. return -EINVAL;
  273. }
  274. return platform_driver_register(&ie6xx_wdt_driver);
  275. }
  276. static void __exit ie6xx_wdt_exit(void)
  277. {
  278. platform_driver_unregister(&ie6xx_wdt_driver);
  279. }
  280. late_initcall(ie6xx_wdt_init);
  281. module_exit(ie6xx_wdt_exit);
  282. MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
  283. MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
  284. MODULE_LICENSE("GPL");
  285. MODULE_ALIAS("platform:" DRIVER_NAME);