asm9260_wdt.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /*
  2. * Watchdog driver for Alphascale ASM9260.
  3. *
  4. * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
  5. *
  6. * Licensed under GPLv2 or later.
  7. */
  8. #include <linux/bitops.h>
  9. #include <linux/clk.h>
  10. #include <linux/delay.h>
  11. #include <linux/interrupt.h>
  12. #include <linux/io.h>
  13. #include <linux/module.h>
  14. #include <linux/of.h>
  15. #include <linux/platform_device.h>
  16. #include <linux/reboot.h>
  17. #include <linux/reset.h>
  18. #include <linux/watchdog.h>
  19. #define CLOCK_FREQ 1000000
  20. /* Watchdog Mode register */
  21. #define HW_WDMOD 0x00
  22. /* Wake interrupt. Set by HW, can't be cleared. */
  23. #define BM_MOD_WDINT BIT(3)
  24. /* This bit set if timeout reached. Cleared by SW. */
  25. #define BM_MOD_WDTOF BIT(2)
  26. /* HW Reset on timeout */
  27. #define BM_MOD_WDRESET BIT(1)
  28. /* WD enable */
  29. #define BM_MOD_WDEN BIT(0)
  30. /*
  31. * Watchdog Timer Constant register
  32. * Minimal value is 0xff, the meaning of this value
  33. * depends on used clock: T = WDCLK * (0xff + 1) * 4
  34. */
  35. #define HW_WDTC 0x04
  36. #define BM_WDTC_MAX(freq) (0x7fffffff / (freq))
  37. /* Watchdog Feed register */
  38. #define HW_WDFEED 0x08
  39. /* Watchdog Timer Value register */
  40. #define HW_WDTV 0x0c
  41. #define ASM9260_WDT_DEFAULT_TIMEOUT 30
  42. enum asm9260_wdt_mode {
  43. HW_RESET,
  44. SW_RESET,
  45. DEBUG,
  46. };
  47. struct asm9260_wdt_priv {
  48. struct device *dev;
  49. struct watchdog_device wdd;
  50. struct clk *clk;
  51. struct clk *clk_ahb;
  52. struct reset_control *rst;
  53. struct notifier_block restart_handler;
  54. void __iomem *iobase;
  55. int irq;
  56. unsigned long wdt_freq;
  57. enum asm9260_wdt_mode mode;
  58. };
  59. static int asm9260_wdt_feed(struct watchdog_device *wdd)
  60. {
  61. struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  62. iowrite32(0xaa, priv->iobase + HW_WDFEED);
  63. iowrite32(0x55, priv->iobase + HW_WDFEED);
  64. return 0;
  65. }
  66. static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
  67. {
  68. struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  69. u32 counter;
  70. counter = ioread32(priv->iobase + HW_WDTV);
  71. return DIV_ROUND_CLOSEST(counter, priv->wdt_freq);
  72. }
  73. static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
  74. {
  75. struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  76. u32 counter;
  77. counter = wdd->timeout * priv->wdt_freq;
  78. iowrite32(counter, priv->iobase + HW_WDTC);
  79. return 0;
  80. }
  81. static int asm9260_wdt_enable(struct watchdog_device *wdd)
  82. {
  83. struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  84. u32 mode = 0;
  85. if (priv->mode == HW_RESET)
  86. mode = BM_MOD_WDRESET;
  87. iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
  88. asm9260_wdt_updatetimeout(wdd);
  89. asm9260_wdt_feed(wdd);
  90. return 0;
  91. }
  92. static int asm9260_wdt_disable(struct watchdog_device *wdd)
  93. {
  94. struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  95. /* The only way to disable WD is to reset it. */
  96. reset_control_assert(priv->rst);
  97. reset_control_deassert(priv->rst);
  98. return 0;
  99. }
  100. static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
  101. {
  102. wdd->timeout = to;
  103. asm9260_wdt_updatetimeout(wdd);
  104. return 0;
  105. }
  106. static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
  107. {
  108. /* init WD if it was not started */
  109. iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
  110. iowrite32(0xff, priv->iobase + HW_WDTC);
  111. /* first pass correct sequence */
  112. asm9260_wdt_feed(&priv->wdd);
  113. /*
  114. * Then write wrong pattern to the feed to trigger reset
  115. * ASAP.
  116. */
  117. iowrite32(0xff, priv->iobase + HW_WDFEED);
  118. mdelay(1000);
  119. }
  120. static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
  121. {
  122. struct asm9260_wdt_priv *priv = devid;
  123. u32 stat;
  124. stat = ioread32(priv->iobase + HW_WDMOD);
  125. if (!(stat & BM_MOD_WDINT))
  126. return IRQ_NONE;
  127. if (priv->mode == DEBUG) {
  128. dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
  129. } else {
  130. dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
  131. asm9260_wdt_sys_reset(priv);
  132. }
  133. return IRQ_HANDLED;
  134. }
  135. static int asm9260_restart_handler(struct notifier_block *this,
  136. unsigned long mode, void *cmd)
  137. {
  138. struct asm9260_wdt_priv *priv =
  139. container_of(this, struct asm9260_wdt_priv, restart_handler);
  140. asm9260_wdt_sys_reset(priv);
  141. return NOTIFY_DONE;
  142. }
  143. static const struct watchdog_info asm9260_wdt_ident = {
  144. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
  145. | WDIOF_MAGICCLOSE,
  146. .identity = "Alphascale asm9260 Watchdog",
  147. };
  148. static struct watchdog_ops asm9260_wdt_ops = {
  149. .owner = THIS_MODULE,
  150. .start = asm9260_wdt_enable,
  151. .stop = asm9260_wdt_disable,
  152. .get_timeleft = asm9260_wdt_gettimeleft,
  153. .ping = asm9260_wdt_feed,
  154. .set_timeout = asm9260_wdt_settimeout,
  155. };
  156. static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
  157. {
  158. int err;
  159. unsigned long clk;
  160. priv->clk = devm_clk_get(priv->dev, "mod");
  161. if (IS_ERR(priv->clk)) {
  162. dev_err(priv->dev, "Failed to get \"mod\" clk\n");
  163. return PTR_ERR(priv->clk);
  164. }
  165. /* configure AHB clock */
  166. priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
  167. if (IS_ERR(priv->clk_ahb)) {
  168. dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
  169. return PTR_ERR(priv->clk_ahb);
  170. }
  171. err = clk_prepare_enable(priv->clk_ahb);
  172. if (err) {
  173. dev_err(priv->dev, "Failed to enable ahb_clk!\n");
  174. return err;
  175. }
  176. err = clk_set_rate(priv->clk, CLOCK_FREQ);
  177. if (err) {
  178. clk_disable_unprepare(priv->clk_ahb);
  179. dev_err(priv->dev, "Failed to set rate!\n");
  180. return err;
  181. }
  182. err = clk_prepare_enable(priv->clk);
  183. if (err) {
  184. clk_disable_unprepare(priv->clk_ahb);
  185. dev_err(priv->dev, "Failed to enable clk!\n");
  186. return err;
  187. }
  188. /* wdt has internal divider */
  189. clk = clk_get_rate(priv->clk);
  190. if (!clk) {
  191. clk_disable_unprepare(priv->clk);
  192. clk_disable_unprepare(priv->clk_ahb);
  193. dev_err(priv->dev, "Failed, clk is 0!\n");
  194. return -EINVAL;
  195. }
  196. priv->wdt_freq = clk / 2;
  197. return 0;
  198. }
  199. static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
  200. {
  201. const char *tmp;
  202. int ret;
  203. /* default mode */
  204. priv->mode = HW_RESET;
  205. ret = of_property_read_string(priv->dev->of_node,
  206. "alphascale,mode", &tmp);
  207. if (ret < 0)
  208. return;
  209. if (!strcmp(tmp, "hw"))
  210. priv->mode = HW_RESET;
  211. else if (!strcmp(tmp, "sw"))
  212. priv->mode = SW_RESET;
  213. else if (!strcmp(tmp, "debug"))
  214. priv->mode = DEBUG;
  215. else
  216. dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
  217. tmp);
  218. }
  219. static int asm9260_wdt_probe(struct platform_device *pdev)
  220. {
  221. struct asm9260_wdt_priv *priv;
  222. struct watchdog_device *wdd;
  223. struct resource *res;
  224. int ret;
  225. const char * const mode_name[] = { "hw", "sw", "debug", };
  226. priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv),
  227. GFP_KERNEL);
  228. if (!priv)
  229. return -ENOMEM;
  230. priv->dev = &pdev->dev;
  231. res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
  232. priv->iobase = devm_ioremap_resource(&pdev->dev, res);
  233. if (IS_ERR(priv->iobase))
  234. return PTR_ERR(priv->iobase);
  235. ret = asm9260_wdt_get_dt_clks(priv);
  236. if (ret)
  237. return ret;
  238. priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst");
  239. if (IS_ERR(priv->rst))
  240. return PTR_ERR(priv->rst);
  241. wdd = &priv->wdd;
  242. wdd->info = &asm9260_wdt_ident;
  243. wdd->ops = &asm9260_wdt_ops;
  244. wdd->min_timeout = 1;
  245. wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
  246. wdd->parent = &pdev->dev;
  247. watchdog_set_drvdata(wdd, priv);
  248. /*
  249. * If 'timeout-sec' unspecified in devicetree, assume a 30 second
  250. * default, unless the max timeout is less than 30 seconds, then use
  251. * the max instead.
  252. */
  253. wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
  254. watchdog_init_timeout(wdd, 0, &pdev->dev);
  255. asm9260_wdt_get_dt_mode(priv);
  256. if (priv->mode != HW_RESET)
  257. priv->irq = platform_get_irq(pdev, 0);
  258. if (priv->irq > 0) {
  259. /*
  260. * Not all supported platforms specify an interrupt for the
  261. * watchdog, so let's make it optional.
  262. */
  263. ret = devm_request_irq(&pdev->dev, priv->irq,
  264. asm9260_wdt_irq, 0, pdev->name, priv);
  265. if (ret < 0)
  266. dev_warn(&pdev->dev, "failed to request IRQ\n");
  267. }
  268. ret = watchdog_register_device(wdd);
  269. if (ret)
  270. goto clk_off;
  271. platform_set_drvdata(pdev, priv);
  272. priv->restart_handler.notifier_call = asm9260_restart_handler;
  273. priv->restart_handler.priority = 128;
  274. ret = register_restart_handler(&priv->restart_handler);
  275. if (ret)
  276. dev_warn(&pdev->dev, "cannot register restart handler\n");
  277. dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
  278. wdd->timeout, mode_name[priv->mode]);
  279. return 0;
  280. clk_off:
  281. clk_disable_unprepare(priv->clk);
  282. clk_disable_unprepare(priv->clk_ahb);
  283. return ret;
  284. }
  285. static void asm9260_wdt_shutdown(struct platform_device *pdev)
  286. {
  287. struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
  288. asm9260_wdt_disable(&priv->wdd);
  289. }
  290. static int asm9260_wdt_remove(struct platform_device *pdev)
  291. {
  292. struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
  293. asm9260_wdt_disable(&priv->wdd);
  294. unregister_restart_handler(&priv->restart_handler);
  295. watchdog_unregister_device(&priv->wdd);
  296. clk_disable_unprepare(priv->clk);
  297. clk_disable_unprepare(priv->clk_ahb);
  298. return 0;
  299. }
  300. static const struct of_device_id asm9260_wdt_of_match[] = {
  301. { .compatible = "alphascale,asm9260-wdt"},
  302. {},
  303. };
  304. MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
  305. static struct platform_driver asm9260_wdt_driver = {
  306. .driver = {
  307. .name = "asm9260-wdt",
  308. .of_match_table = asm9260_wdt_of_match,
  309. },
  310. .probe = asm9260_wdt_probe,
  311. .remove = asm9260_wdt_remove,
  312. .shutdown = asm9260_wdt_shutdown,
  313. };
  314. module_platform_driver(asm9260_wdt_driver);
  315. MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
  316. MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
  317. MODULE_LICENSE("GPL");