cros_ec_wdt.c 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Copyright 2024 Google LLC.
  4. * Author: Lukasz Majczak <lma@chromium.com>
  5. */
  6. #include <linux/err.h>
  7. #include <linux/kernel.h>
  8. #include <linux/module.h>
  9. #include <linux/mod_devicetable.h>
  10. #include <linux/platform_data/cros_ec_commands.h>
  11. #include <linux/platform_data/cros_ec_proto.h>
  12. #include <linux/platform_device.h>
  13. #include <linux/watchdog.h>
  14. #define CROS_EC_WATCHDOG_DEFAULT_TIME 30 /* seconds */
  15. #define DRV_NAME "cros-ec-wdt"
  16. union cros_ec_wdt_data {
  17. struct ec_params_hang_detect req;
  18. struct ec_response_hang_detect resp;
  19. } __packed;
  20. static int cros_ec_wdt_send_cmd(struct cros_ec_device *cros_ec,
  21. union cros_ec_wdt_data *arg)
  22. {
  23. int ret;
  24. DEFINE_RAW_FLEX(struct cros_ec_command, msg, data,
  25. sizeof(union cros_ec_wdt_data));
  26. msg->version = 0;
  27. msg->command = EC_CMD_HANG_DETECT;
  28. msg->insize = (arg->req.command == EC_HANG_DETECT_CMD_GET_STATUS) ?
  29. sizeof(struct ec_response_hang_detect) :
  30. 0;
  31. msg->outsize = sizeof(struct ec_params_hang_detect);
  32. *(struct ec_params_hang_detect *)msg->data = arg->req;
  33. ret = cros_ec_cmd_xfer_status(cros_ec, msg);
  34. if (ret < 0)
  35. return ret;
  36. arg->resp = *(struct ec_response_hang_detect *)msg->data;
  37. return 0;
  38. }
  39. static int cros_ec_wdt_ping(struct watchdog_device *wdd)
  40. {
  41. struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd);
  42. union cros_ec_wdt_data arg;
  43. int ret;
  44. arg.req.command = EC_HANG_DETECT_CMD_RELOAD;
  45. ret = cros_ec_wdt_send_cmd(cros_ec, &arg);
  46. if (ret < 0)
  47. dev_dbg(wdd->parent, "Failed to ping watchdog (%d)\n", ret);
  48. return ret;
  49. }
  50. static int cros_ec_wdt_start(struct watchdog_device *wdd)
  51. {
  52. struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd);
  53. union cros_ec_wdt_data arg;
  54. int ret;
  55. /* Prepare watchdog on EC side */
  56. arg.req.command = EC_HANG_DETECT_CMD_SET_TIMEOUT;
  57. arg.req.reboot_timeout_sec = wdd->timeout;
  58. ret = cros_ec_wdt_send_cmd(cros_ec, &arg);
  59. if (ret < 0)
  60. dev_dbg(wdd->parent, "Failed to start watchdog (%d)\n", ret);
  61. return ret;
  62. }
  63. static int cros_ec_wdt_stop(struct watchdog_device *wdd)
  64. {
  65. struct cros_ec_device *cros_ec = watchdog_get_drvdata(wdd);
  66. union cros_ec_wdt_data arg;
  67. int ret;
  68. arg.req.command = EC_HANG_DETECT_CMD_CANCEL;
  69. ret = cros_ec_wdt_send_cmd(cros_ec, &arg);
  70. if (ret < 0)
  71. dev_dbg(wdd->parent, "Failed to stop watchdog (%d)\n", ret);
  72. return ret;
  73. }
  74. static int cros_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
  75. {
  76. unsigned int old_timeout = wdd->timeout;
  77. int ret;
  78. wdd->timeout = t;
  79. ret = cros_ec_wdt_start(wdd);
  80. if (ret < 0)
  81. wdd->timeout = old_timeout;
  82. return ret;
  83. }
  84. static const struct watchdog_info cros_ec_wdt_ident = {
  85. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
  86. .firmware_version = 0,
  87. .identity = DRV_NAME,
  88. };
  89. static const struct watchdog_ops cros_ec_wdt_ops = {
  90. .owner = THIS_MODULE,
  91. .ping = cros_ec_wdt_ping,
  92. .start = cros_ec_wdt_start,
  93. .stop = cros_ec_wdt_stop,
  94. .set_timeout = cros_ec_wdt_set_timeout,
  95. };
  96. static int cros_ec_wdt_probe(struct platform_device *pdev)
  97. {
  98. struct device *dev = &pdev->dev;
  99. struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
  100. struct cros_ec_device *cros_ec = ec_dev->ec_dev;
  101. struct watchdog_device *wdd;
  102. union cros_ec_wdt_data arg;
  103. int ret = 0;
  104. wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL);
  105. if (!wdd)
  106. return -ENOMEM;
  107. arg.req.command = EC_HANG_DETECT_CMD_GET_STATUS;
  108. ret = cros_ec_wdt_send_cmd(cros_ec, &arg);
  109. if (ret < 0)
  110. return dev_err_probe(dev, ret, "Failed to get watchdog bootstatus\n");
  111. wdd->parent = &pdev->dev;
  112. wdd->info = &cros_ec_wdt_ident;
  113. wdd->ops = &cros_ec_wdt_ops;
  114. wdd->timeout = CROS_EC_WATCHDOG_DEFAULT_TIME;
  115. wdd->min_timeout = EC_HANG_DETECT_MIN_TIMEOUT;
  116. wdd->max_timeout = EC_HANG_DETECT_MAX_TIMEOUT;
  117. if (arg.resp.status == EC_HANG_DETECT_AP_BOOT_EC_WDT)
  118. wdd->bootstatus = WDIOF_CARDRESET;
  119. arg.req.command = EC_HANG_DETECT_CMD_CLEAR_STATUS;
  120. ret = cros_ec_wdt_send_cmd(cros_ec, &arg);
  121. if (ret < 0)
  122. return dev_err_probe(dev, ret, "Failed to clear watchdog bootstatus\n");
  123. watchdog_stop_on_reboot(wdd);
  124. watchdog_stop_on_unregister(wdd);
  125. watchdog_set_drvdata(wdd, cros_ec);
  126. platform_set_drvdata(pdev, wdd);
  127. return devm_watchdog_register_device(dev, wdd);
  128. }
  129. static int __maybe_unused cros_ec_wdt_suspend(struct platform_device *pdev, pm_message_t state)
  130. {
  131. struct watchdog_device *wdd = platform_get_drvdata(pdev);
  132. int ret = 0;
  133. if (watchdog_active(wdd))
  134. ret = cros_ec_wdt_stop(wdd);
  135. return ret;
  136. }
  137. static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev)
  138. {
  139. struct watchdog_device *wdd = platform_get_drvdata(pdev);
  140. int ret = 0;
  141. if (watchdog_active(wdd))
  142. ret = cros_ec_wdt_start(wdd);
  143. return ret;
  144. }
  145. static const struct platform_device_id cros_ec_wdt_id[] = {
  146. { DRV_NAME, 0 },
  147. {}
  148. };
  149. static struct platform_driver cros_ec_wdt_driver = {
  150. .probe = cros_ec_wdt_probe,
  151. .suspend = pm_ptr(cros_ec_wdt_suspend),
  152. .resume = pm_ptr(cros_ec_wdt_resume),
  153. .driver = {
  154. .name = DRV_NAME,
  155. },
  156. .id_table = cros_ec_wdt_id,
  157. };
  158. module_platform_driver(cros_ec_wdt_driver);
  159. MODULE_DEVICE_TABLE(platform, cros_ec_wdt_id);
  160. MODULE_DESCRIPTION("Cros EC Watchdog Device Driver");
  161. MODULE_LICENSE("GPL");