at32ap700x_wdt.c 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. * Watchdog driver for Atmel AT32AP700X devices
  3. *
  4. * Copyright (C) 2005-2006 Atmel Corporation
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License version 2 as
  8. * published by the Free Software Foundation.
  9. */
  10. #include <linux/init.h>
  11. #include <linux/kernel.h>
  12. #include <linux/module.h>
  13. #include <linux/moduleparam.h>
  14. #include <linux/miscdevice.h>
  15. #include <linux/fs.h>
  16. #include <linux/platform_device.h>
  17. #include <linux/watchdog.h>
  18. #include <linux/uaccess.h>
  19. #include <linux/io.h>
  20. #define TIMEOUT_MIN 1
  21. #define TIMEOUT_MAX 2
  22. #define TIMEOUT_DEFAULT TIMEOUT_MAX
  23. /* module parameters */
  24. static int timeout = TIMEOUT_DEFAULT;
  25. module_param(timeout, int, 0);
  26. MODULE_PARM_DESC(timeout,
  27. "Timeout value. Limited to be 1 or 2 seconds. (default="
  28. __MODULE_STRING(TIMEOUT_DEFAULT) ")");
  29. /* Watchdog registers and write/read macro */
  30. #define WDT_CTRL 0x00
  31. #define WDT_CTRL_EN 0
  32. #define WDT_CTRL_PSEL 8
  33. #define WDT_CTRL_KEY 24
  34. #define WDT_CLR 0x04
  35. #define WDT_BIT(name) (1 << WDT_##name)
  36. #define WDT_BF(name, value) ((value) << WDT_##name)
  37. #define wdt_readl(dev, reg) \
  38. __raw_readl((dev)->regs + WDT_##reg)
  39. #define wdt_writel(dev, reg, value) \
  40. __raw_writel((value), (dev)->regs + WDT_##reg)
  41. struct wdt_at32ap700x {
  42. void __iomem *regs;
  43. int timeout;
  44. int users;
  45. struct miscdevice miscdev;
  46. };
  47. static struct wdt_at32ap700x *wdt;
  48. /*
  49. * Disable the watchdog.
  50. */
  51. static inline void at32_wdt_stop(void)
  52. {
  53. unsigned long psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f);
  54. wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55));
  55. wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa));
  56. }
  57. /*
  58. * Enable and reset the watchdog.
  59. */
  60. static inline void at32_wdt_start(void)
  61. {
  62. /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */
  63. unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe;
  64. wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
  65. | WDT_BF(CTRL_PSEL, psel)
  66. | WDT_BF(CTRL_KEY, 0x55));
  67. wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN)
  68. | WDT_BF(CTRL_PSEL, psel)
  69. | WDT_BF(CTRL_KEY, 0xaa));
  70. }
  71. /*
  72. * Pat the watchdog timer.
  73. */
  74. static inline void at32_wdt_pat(void)
  75. {
  76. wdt_writel(wdt, CLR, 0x42);
  77. }
  78. /*
  79. * Watchdog device is opened, and watchdog starts running.
  80. */
  81. static int at32_wdt_open(struct inode *inode, struct file *file)
  82. {
  83. if (test_and_set_bit(1, &wdt->users))
  84. return -EBUSY;
  85. at32_wdt_start();
  86. return nonseekable_open(inode, file);
  87. }
  88. /*
  89. * Close the watchdog device. If CONFIG_WATCHDOG_NOWAYOUT is _not_ defined then
  90. * the watchdog is also disabled.
  91. */
  92. static int at32_wdt_close(struct inode *inode, struct file *file)
  93. {
  94. #ifndef CONFIG_WATCHDOG_NOWAYOUT
  95. at32_wdt_stop();
  96. #endif
  97. clear_bit(1, &wdt->users);
  98. return 0;
  99. }
  100. /*
  101. * Change the watchdog time interval.
  102. */
  103. static int at32_wdt_settimeout(int time)
  104. {
  105. /*
  106. * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is
  107. * 2 ^ 16 allowing up to 2 seconds timeout.
  108. */
  109. if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX))
  110. return -EINVAL;
  111. /*
  112. * Set new watchdog time. It will be used when at32_wdt_start() is
  113. * called.
  114. */
  115. wdt->timeout = time;
  116. return 0;
  117. }
  118. static struct watchdog_info at32_wdt_info = {
  119. .identity = "at32ap700x watchdog",
  120. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
  121. };
  122. /*
  123. * Handle commands from user-space.
  124. */
  125. static int at32_wdt_ioctl(struct inode *inode, struct file *file,
  126. unsigned int cmd, unsigned long arg)
  127. {
  128. int ret = -ENOTTY;
  129. int time;
  130. void __user *argp = (void __user *)arg;
  131. int __user *p = argp;
  132. switch (cmd) {
  133. case WDIOC_KEEPALIVE:
  134. at32_wdt_pat();
  135. ret = 0;
  136. break;
  137. case WDIOC_GETSUPPORT:
  138. ret = copy_to_user(argp, &at32_wdt_info,
  139. sizeof(at32_wdt_info)) ? -EFAULT : 0;
  140. break;
  141. case WDIOC_SETTIMEOUT:
  142. ret = get_user(time, p);
  143. if (ret)
  144. break;
  145. ret = at32_wdt_settimeout(time);
  146. if (ret)
  147. break;
  148. /* Enable new time value */
  149. at32_wdt_start();
  150. /* fall through */
  151. case WDIOC_GETTIMEOUT:
  152. ret = put_user(wdt->timeout, p);
  153. break;
  154. case WDIOC_GETSTATUS: /* fall through */
  155. case WDIOC_GETBOOTSTATUS:
  156. ret = put_user(0, p);
  157. break;
  158. case WDIOC_SETOPTIONS:
  159. ret = get_user(time, p);
  160. if (ret)
  161. break;
  162. if (time & WDIOS_DISABLECARD)
  163. at32_wdt_stop();
  164. if (time & WDIOS_ENABLECARD)
  165. at32_wdt_start();
  166. ret = 0;
  167. break;
  168. }
  169. return ret;
  170. }
  171. static ssize_t at32_wdt_write(struct file *file, const char *data, size_t len,
  172. loff_t *ppos)
  173. {
  174. at32_wdt_pat();
  175. return len;
  176. }
  177. static const struct file_operations at32_wdt_fops = {
  178. .owner = THIS_MODULE,
  179. .llseek = no_llseek,
  180. .ioctl = at32_wdt_ioctl,
  181. .open = at32_wdt_open,
  182. .release = at32_wdt_close,
  183. .write = at32_wdt_write,
  184. };
  185. static int __init at32_wdt_probe(struct platform_device *pdev)
  186. {
  187. struct resource *regs;
  188. int ret;
  189. if (wdt) {
  190. dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n");
  191. return -EBUSY;
  192. }
  193. regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  194. if (!regs) {
  195. dev_dbg(&pdev->dev, "missing mmio resource\n");
  196. return -ENXIO;
  197. }
  198. wdt = kzalloc(sizeof(struct wdt_at32ap700x), GFP_KERNEL);
  199. if (!wdt) {
  200. dev_dbg(&pdev->dev, "no memory for wdt structure\n");
  201. return -ENOMEM;
  202. }
  203. wdt->regs = ioremap(regs->start, regs->end - regs->start + 1);
  204. if (!wdt->regs) {
  205. ret = -ENOMEM;
  206. dev_dbg(&pdev->dev, "could not map I/O memory\n");
  207. goto err_free;
  208. }
  209. wdt->users = 0;
  210. wdt->miscdev.minor = WATCHDOG_MINOR;
  211. wdt->miscdev.name = "watchdog";
  212. wdt->miscdev.fops = &at32_wdt_fops;
  213. if (at32_wdt_settimeout(timeout)) {
  214. at32_wdt_settimeout(TIMEOUT_DEFAULT);
  215. dev_dbg(&pdev->dev,
  216. "default timeout invalid, set to %d sec.\n",
  217. TIMEOUT_DEFAULT);
  218. }
  219. ret = misc_register(&wdt->miscdev);
  220. if (ret) {
  221. dev_dbg(&pdev->dev, "failed to register wdt miscdev\n");
  222. goto err_iounmap;
  223. }
  224. platform_set_drvdata(pdev, wdt);
  225. wdt->miscdev.parent = &pdev->dev;
  226. dev_info(&pdev->dev, "AT32AP700X WDT at 0x%p, timeout %d sec\n",
  227. wdt->regs, wdt->timeout);
  228. return 0;
  229. err_iounmap:
  230. iounmap(wdt->regs);
  231. err_free:
  232. kfree(wdt);
  233. wdt = NULL;
  234. return ret;
  235. }
  236. static int __exit at32_wdt_remove(struct platform_device *pdev)
  237. {
  238. if (wdt && platform_get_drvdata(pdev) == wdt) {
  239. misc_deregister(&wdt->miscdev);
  240. iounmap(wdt->regs);
  241. kfree(wdt);
  242. wdt = NULL;
  243. platform_set_drvdata(pdev, NULL);
  244. }
  245. return 0;
  246. }
  247. static void at32_wdt_shutdown(struct platform_device *pdev)
  248. {
  249. at32_wdt_stop();
  250. }
  251. #ifdef CONFIG_PM
  252. static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message)
  253. {
  254. at32_wdt_stop();
  255. return 0;
  256. }
  257. static int at32_wdt_resume(struct platform_device *pdev)
  258. {
  259. if (wdt->users)
  260. at32_wdt_start();
  261. return 0;
  262. }
  263. #else
  264. #define at32_wdt_suspend NULL
  265. #define at32_wdt_resume NULL
  266. #endif
  267. static struct platform_driver at32_wdt_driver = {
  268. .remove = __exit_p(at32_wdt_remove),
  269. .suspend = at32_wdt_suspend,
  270. .resume = at32_wdt_resume,
  271. .driver = {
  272. .name = "at32_wdt",
  273. .owner = THIS_MODULE,
  274. },
  275. .shutdown = at32_wdt_shutdown,
  276. };
  277. static int __init at32_wdt_init(void)
  278. {
  279. return platform_driver_probe(&at32_wdt_driver, at32_wdt_probe);
  280. }
  281. module_init(at32_wdt_init);
  282. static void __exit at32_wdt_exit(void)
  283. {
  284. platform_driver_unregister(&at32_wdt_driver);
  285. }
  286. module_exit(at32_wdt_exit);
  287. MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>");
  288. MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X");
  289. MODULE_LICENSE("GPL");
  290. MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);