loongson1_wdt.c 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com>
  4. * Copyright (C) 2025 Binbin Zhou <zhoubinbin@loongson.cn>
  5. */
  6. #include <linux/clk.h>
  7. #include <linux/io.h>
  8. #include <linux/module.h>
  9. #include <linux/of.h>
  10. #include <linux/platform_device.h>
  11. #include <linux/watchdog.h>
  12. /* Loongson Watchdog Register Definitions */
  13. #define WDT_EN 0x0
  14. #define DEFAULT_HEARTBEAT 30
  15. static bool nowayout = WATCHDOG_NOWAYOUT;
  16. module_param(nowayout, bool, 0);
  17. MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  18. __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  19. static unsigned int heartbeat;
  20. module_param(heartbeat, uint, 0);
  21. MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
  22. __MODULE_STRING(DEFAULT_HEARTBEAT) ")");
  23. struct ls1x_wdt_pdata {
  24. u32 timer_offset;
  25. u32 set_offset;
  26. u32 wdt_en_bit;
  27. };
  28. static const struct ls1x_wdt_pdata ls1b_wdt_pdata = {
  29. .timer_offset = 0x4,
  30. .set_offset = 0x8,
  31. .wdt_en_bit = BIT(0),
  32. };
  33. static const struct ls1x_wdt_pdata ls2k0300_wdt_pdata = {
  34. .timer_offset = 0x8,
  35. .set_offset = 0x4,
  36. .wdt_en_bit = BIT(1),
  37. };
  38. struct ls1x_wdt_drvdata {
  39. void __iomem *base;
  40. struct clk *clk;
  41. unsigned long clk_rate;
  42. struct watchdog_device wdt;
  43. const struct ls1x_wdt_pdata *pdata;
  44. };
  45. static int ls1x_wdt_ping(struct watchdog_device *wdt_dev)
  46. {
  47. struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  48. writel(0x1, drvdata->base + drvdata->pdata->set_offset);
  49. return 0;
  50. }
  51. static int ls1x_wdt_set_timeout(struct watchdog_device *wdt_dev,
  52. unsigned int timeout)
  53. {
  54. struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  55. unsigned int max_hw_heartbeat = wdt_dev->max_hw_heartbeat_ms / 1000;
  56. unsigned int counts;
  57. wdt_dev->timeout = timeout;
  58. counts = drvdata->clk_rate * min(timeout, max_hw_heartbeat);
  59. writel(counts, drvdata->base + drvdata->pdata->timer_offset);
  60. return 0;
  61. }
  62. static int ls1x_wdt_start(struct watchdog_device *wdt_dev)
  63. {
  64. struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  65. writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN);
  66. return 0;
  67. }
  68. static int ls1x_wdt_stop(struct watchdog_device *wdt_dev)
  69. {
  70. struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  71. u32 val = readl(drvdata->base + WDT_EN);
  72. val &= ~(drvdata->pdata->wdt_en_bit);
  73. writel(val, drvdata->base + WDT_EN);
  74. return 0;
  75. }
  76. static int ls1x_wdt_restart(struct watchdog_device *wdt_dev,
  77. unsigned long action, void *data)
  78. {
  79. struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
  80. writel(drvdata->pdata->wdt_en_bit, drvdata->base + WDT_EN);
  81. writel(0x1, drvdata->base + drvdata->pdata->timer_offset);
  82. writel(0x1, drvdata->base + drvdata->pdata->set_offset);
  83. return 0;
  84. }
  85. static const struct watchdog_info ls1x_wdt_info = {
  86. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
  87. .identity = "Loongson1 Watchdog",
  88. };
  89. static const struct watchdog_ops ls1x_wdt_ops = {
  90. .owner = THIS_MODULE,
  91. .start = ls1x_wdt_start,
  92. .stop = ls1x_wdt_stop,
  93. .ping = ls1x_wdt_ping,
  94. .set_timeout = ls1x_wdt_set_timeout,
  95. .restart = ls1x_wdt_restart,
  96. };
  97. static int ls1x_wdt_probe(struct platform_device *pdev)
  98. {
  99. struct device *dev = &pdev->dev;
  100. struct ls1x_wdt_drvdata *drvdata;
  101. struct watchdog_device *ls1x_wdt;
  102. unsigned long clk_rate;
  103. drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
  104. if (!drvdata)
  105. return -ENOMEM;
  106. platform_set_drvdata(pdev, drvdata);
  107. drvdata->pdata = of_device_get_match_data(dev);
  108. drvdata->base = devm_platform_ioremap_resource(pdev, 0);
  109. if (IS_ERR(drvdata->base))
  110. return PTR_ERR(drvdata->base);
  111. drvdata->clk = devm_clk_get_enabled(dev, NULL);
  112. if (IS_ERR(drvdata->clk))
  113. return PTR_ERR(drvdata->clk);
  114. clk_rate = clk_get_rate(drvdata->clk);
  115. if (!clk_rate)
  116. return -EINVAL;
  117. drvdata->clk_rate = clk_rate;
  118. ls1x_wdt = &drvdata->wdt;
  119. ls1x_wdt->info = &ls1x_wdt_info;
  120. ls1x_wdt->ops = &ls1x_wdt_ops;
  121. ls1x_wdt->timeout = DEFAULT_HEARTBEAT;
  122. ls1x_wdt->min_timeout = 1;
  123. ls1x_wdt->max_hw_heartbeat_ms = U32_MAX / clk_rate * 1000;
  124. ls1x_wdt->parent = dev;
  125. watchdog_init_timeout(ls1x_wdt, heartbeat, dev);
  126. watchdog_set_nowayout(ls1x_wdt, nowayout);
  127. watchdog_set_drvdata(ls1x_wdt, drvdata);
  128. return devm_watchdog_register_device(dev, &drvdata->wdt);
  129. }
  130. static int ls1x_wdt_resume(struct device *dev)
  131. {
  132. struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev);
  133. if (watchdog_active(&data->wdt))
  134. ls1x_wdt_start(&data->wdt);
  135. return 0;
  136. }
  137. static int ls1x_wdt_suspend(struct device *dev)
  138. {
  139. struct ls1x_wdt_drvdata *data = dev_get_drvdata(dev);
  140. if (watchdog_active(&data->wdt))
  141. ls1x_wdt_stop(&data->wdt);
  142. return 0;
  143. }
  144. static DEFINE_SIMPLE_DEV_PM_OPS(ls1x_wdt_pm_ops, ls1x_wdt_suspend, ls1x_wdt_resume);
  145. static const struct of_device_id ls1x_wdt_dt_ids[] = {
  146. { .compatible = "loongson,ls1b-wdt", .data = &ls1b_wdt_pdata },
  147. { .compatible = "loongson,ls1c-wdt", .data = &ls1b_wdt_pdata },
  148. { .compatible = "loongson,ls2k0300-wdt", .data = &ls2k0300_wdt_pdata },
  149. { /* sentinel */ }
  150. };
  151. MODULE_DEVICE_TABLE(of, ls1x_wdt_dt_ids);
  152. static struct platform_driver ls1x_wdt_driver = {
  153. .probe = ls1x_wdt_probe,
  154. .driver = {
  155. .name = "ls1x-wdt",
  156. .of_match_table = ls1x_wdt_dt_ids,
  157. .pm = pm_ptr(&ls1x_wdt_pm_ops),
  158. },
  159. };
  160. module_platform_driver(ls1x_wdt_driver);
  161. MODULE_AUTHOR("Yang Ling <gnaygnil@gmail.com>");
  162. MODULE_AUTHOR("Binbin Zhou <zhoubinbin@loongson.cn>");
  163. MODULE_DESCRIPTION("Loongson Watchdog Driver");
  164. MODULE_LICENSE("GPL");