light_wdt.c 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * Copyright (C) 2021 Alibaba Group Holding Limited.
  4. */
  5. #include <linux/clk.h>
  6. #include <linux/delay.h>
  7. #include <linux/init.h>
  8. #include <linux/interrupt.h>
  9. #include <linux/io.h>
  10. #include <linux/kernel.h>
  11. #include <linux/module.h>
  12. #include <linux/moduleparam.h>
  13. #include <linux/of_address.h>
  14. #include <linux/platform_device.h>
  15. #include <linux/regmap.h>
  16. #include <linux/device.h>
  17. #include <linux/watchdog.h>
  18. #include <linux/firmware/thead/ipc.h>
  19. #define DRV_NAME "light-wdt"
  20. /*
  21. * Watchdog selector to timeout in seconds.
  22. * 0: WDT disabled;
  23. * others: timeout = 2048 ms * 2^(TWDSCALE-1).
  24. */
  25. static const unsigned int wdt_timeout[] = {8, 16, 32,128};
  26. #define LIGHT_TWDSCALE_DISABLE 0
  27. #define LIGHT_TWDSCALE_MIN 1
  28. #define LIGHT_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
  29. #define LIGHT_WDT_MIN_TIMEOUT wdt_timeout[LIGHT_TWDSCALE_MIN]
  30. #define LIGHT_WDT_MAX_TIMEOUT wdt_timeout[LIGHT_TWDSCALE_MAX]
  31. #define LIGHT_WDT_TIMEOUT wdt_timeout[3]
  32. #define LIGHT_RESET_PROTECTION_MS 256
  33. struct light_aon_msg_wdg_ctrl {
  34. struct light_aon_rpc_msg_hdr hdr;
  35. u32 timeout;
  36. u32 running_state;
  37. u32 reserved[4];
  38. }_packed __aligned(4);
  39. struct light_wdt_device {
  40. struct device *dev;
  41. struct light_aon_ipc *ipc_handle;
  42. struct light_aon_msg_wdg_ctrl msg;
  43. unsigned int is_aon_wdt_ena;
  44. };
  45. struct light_wdt_device *light_power_off_wdt;
  46. static unsigned int light_wdt_timeout_to_sel(unsigned secs)
  47. {
  48. unsigned int i;
  49. for (i = LIGHT_TWDSCALE_MIN; i <= LIGHT_TWDSCALE_MAX; i++) {
  50. if (wdt_timeout[i] >= secs)
  51. return i;
  52. }
  53. return LIGHT_TWDSCALE_MAX;
  54. }
  55. static void light_wdt_msg_hdr_fill(struct light_aon_rpc_msg_hdr *hdr, enum light_aon_misc_func func)
  56. {
  57. hdr->ver = LIGHT_AON_RPC_VERSION;
  58. hdr->svc = (uint8_t)LIGHT_AON_RPC_SVC_MISC;
  59. hdr->func = (uint8_t)func;
  60. hdr->size = LIGHT_AON_RPC_MSG_NUM;
  61. }
  62. static int light_wdt_is_running(struct light_wdt_device *wdt_dev)
  63. {
  64. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  65. int ret;
  66. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_GET_STATE);
  67. wdt_dev->msg.running_state = -1;
  68. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  69. if (ret)
  70. return ret;
  71. pr_debug("ret = %d, timeout = %d, running_state = %d\n", ret, wdt_dev->msg.timeout,
  72. wdt_dev->msg.running_state);
  73. return wdt_dev->msg.running_state;
  74. }
  75. static int light_wdt_update_timeout(struct light_wdt_device *wdt_dev, unsigned int timeout)
  76. {
  77. /*
  78. * The watchdog triggers a reboot if a timeout value is already
  79. * programmed because the timeout value combines two functions
  80. * in one: indicating the counter limit and starting the watchdog.
  81. * The watchdog must be disabled to be able to change the timeout
  82. * value if the watchdog is already running. Then we can set the
  83. * new timeout value which enables the watchdog again.
  84. */
  85. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  86. int ret;
  87. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_TIMEOUTSET);
  88. wdt_dev->msg.timeout = timeout;
  89. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  90. if (ret)
  91. return ret;
  92. return 0;
  93. }
  94. static int light_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
  95. {
  96. struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
  97. int ret = 0;
  98. /*
  99. * There are two cases when a set_timeout() will be called:
  100. * 1. The watchdog is off and someone wants to set the timeout for the
  101. * further use.
  102. * 2. The watchdog is already running and a new timeout value should be
  103. * set.
  104. *
  105. * The watchdog can't store a timeout value not equal zero without
  106. * enabling the watchdog, so the timeout must be buffered by the driver.
  107. */
  108. if (watchdog_active(wdd))
  109. ret = light_wdt_update_timeout(wdt_dev, timeout);
  110. else
  111. wdd->timeout = wdt_timeout[light_wdt_timeout_to_sel(timeout)];
  112. return ret;
  113. }
  114. static int light_wdt_start(struct watchdog_device *wdd)
  115. {
  116. struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
  117. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  118. int ret;
  119. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_START);
  120. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  121. if (ret)
  122. return ret;
  123. return 0;
  124. }
  125. static int light_wdt_stop(struct watchdog_device *wdd)
  126. {
  127. struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
  128. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  129. int ret;
  130. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_STOP);
  131. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  132. if (ret)
  133. return ret;
  134. return 0;
  135. }
  136. static int light_wdt_ping(struct watchdog_device *wdd)
  137. {
  138. struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
  139. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  140. int ret;
  141. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_PING);
  142. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  143. if (ret)
  144. return ret;
  145. return 0;
  146. }
  147. static int light_wdt_restart(struct watchdog_device *wdd, unsigned long action, void *data)
  148. {
  149. struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
  150. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  151. int ret;
  152. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_RESTART);
  153. pr_debug("[%s,%d]: Inform aon to restart the whole system....\n", __func__, __LINE__);
  154. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, false);
  155. if (ret)
  156. return ret;
  157. pr_debug("[%s,%d]: Finish to inform aon to restart the whole system....\n", __func__, __LINE__);
  158. return 0;
  159. }
  160. static const struct watchdog_info light_watchdog_info = {
  161. .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
  162. .identity = "Light Watchdog",
  163. };
  164. static const struct watchdog_ops light_watchdog_ops = {
  165. .owner = THIS_MODULE,
  166. .start = light_wdt_start,
  167. .stop = light_wdt_stop,
  168. .ping = light_wdt_ping,
  169. .set_timeout = light_wdt_set_timeout,
  170. .restart = light_wdt_restart,
  171. };
  172. static ssize_t aon_sys_wdt_show(struct device *dev,
  173. struct device_attribute *attr, char *buf)
  174. {
  175. struct platform_device *pdev = to_platform_device(dev);
  176. struct light_wdt_device *wdt_dev = platform_get_drvdata(pdev);
  177. return sprintf(buf,"%u\n",wdt_dev->is_aon_wdt_ena);
  178. }
  179. static ssize_t aon_sys_wdt_store(struct device *dev,
  180. struct device_attribute *attr,
  181. const char *buf, size_t size)
  182. {
  183. struct platform_device *pdev = to_platform_device(dev);
  184. struct light_wdt_device *wdt_dev = platform_get_drvdata(pdev);
  185. struct light_aon_ipc *ipc;
  186. int ret;
  187. char *start = (char *)buf;
  188. unsigned long val;
  189. ipc = wdt_dev->ipc_handle;
  190. val = simple_strtoul(start, &start, 0);
  191. wdt_dev->is_aon_wdt_ena = val;
  192. if (val)
  193. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_AON_WDT_ON);
  194. else
  195. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_AON_WDT_OFF);
  196. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  197. if (ret){
  198. pr_err("%s: err:%d \n",__func__,ret);
  199. return -EINVAL;
  200. }
  201. return size;
  202. }
  203. void light_pm_power_off(void)
  204. {
  205. struct light_wdt_device *wdt_dev = light_power_off_wdt;
  206. struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
  207. int ret;
  208. pr_info("[%s,%d]poweroff system...\n", __func__, __LINE__);
  209. light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_MISC_FUNC_WDG_POWER_OFF);
  210. ret = light_aon_call_rpc(ipc, &wdt_dev->msg, true);
  211. if (ret)
  212. pr_err("failed to power off the system\n");
  213. }
  214. static DEVICE_ATTR(aon_sys_wdt, 0644, aon_sys_wdt_show, aon_sys_wdt_store);
  215. static struct attribute *aon_sys_wdt_sysfs_entries[] = {
  216. &dev_attr_aon_sys_wdt.attr,
  217. NULL
  218. };
  219. static const struct attribute_group dev_attr_aon_sys_wdt_group = {
  220. .attrs = aon_sys_wdt_sysfs_entries,
  221. };
  222. static int light_wdt_probe(struct platform_device *pdev)
  223. {
  224. struct device *dev = &pdev->dev;
  225. struct light_wdt_device *wdt_dev;
  226. int ret;
  227. struct watchdog_device *wdd;
  228. wdt_dev = devm_kzalloc(dev, sizeof(*wdt_dev), GFP_KERNEL);
  229. if (!wdt_dev)
  230. return -ENOMEM;
  231. wdt_dev->is_aon_wdt_ena = 0;
  232. ret = light_aon_get_handle(&(wdt_dev->ipc_handle));
  233. if (ret == -EPROBE_DEFER)
  234. return ret;
  235. wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
  236. if (!wdd)
  237. return -ENOMEM;
  238. wdd->info = &light_watchdog_info;
  239. wdd->ops = &light_watchdog_ops;
  240. wdd->min_timeout = LIGHT_WDT_MIN_TIMEOUT;
  241. wdd->max_timeout = LIGHT_WDT_MAX_TIMEOUT;
  242. wdd->min_hw_heartbeat_ms = LIGHT_RESET_PROTECTION_MS;
  243. wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
  244. watchdog_set_restart_priority(wdd, 128);
  245. watchdog_set_drvdata(wdd, wdt_dev);
  246. /* Set default timeout, maybe default value if the watchdog is running */
  247. wdd->timeout = LIGHT_WDT_TIMEOUT;
  248. watchdog_init_timeout(wdd, 0, dev);
  249. light_wdt_set_timeout(wdd, wdd->timeout);
  250. platform_set_drvdata(pdev, wdt_dev);
  251. ret = light_wdt_is_running(wdt_dev);
  252. if (ret < 0) {
  253. pr_err("failed to get pmic wdt running state\n");
  254. return ret;
  255. }
  256. if (ret) {
  257. light_wdt_update_timeout(wdt_dev, wdd->timeout);
  258. set_bit(WDOG_HW_RUNNING, &wdd->status);
  259. }
  260. ret = devm_watchdog_register_device(dev, wdd);
  261. if (ret)
  262. return ret;
  263. pr_info("[%s,%d] register power off callback\n", __func__, __LINE__);
  264. pm_power_off = light_pm_power_off;
  265. light_power_off_wdt = wdt_dev;
  266. ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_aon_sys_wdt_group);
  267. if (ret) {
  268. dev_err(&pdev->dev, "Failed to create aon_sys_wdt sysfs.\n");
  269. return ret;
  270. }
  271. pr_info("succeed to register light pmic watchdog\n");
  272. return 0;
  273. }
  274. static struct platform_driver light_wdt_driver = {
  275. .driver = {
  276. .name = DRV_NAME,
  277. },
  278. .probe = light_wdt_probe,
  279. };
  280. static int __init light_wdt_init(void)
  281. {
  282. static struct platform_device *pdev;
  283. int ret;
  284. pdev = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
  285. if (IS_ERR(pdev))
  286. return PTR_ERR(pdev);
  287. ret = platform_driver_register(&light_wdt_driver);
  288. if (ret) {
  289. platform_device_unregister(pdev);
  290. return PTR_ERR(pdev);
  291. }
  292. pr_info("Watchdog module: %s loaded\n", DRV_NAME);
  293. return 0;
  294. }
  295. device_initcall(light_wdt_init);
  296. MODULE_AUTHOR("Wei.Liu <lw312886@linux.alibaba.com>");
  297. MODULE_DESCRIPTION("PMIC Watchdog Driver for Light");
  298. MODULE_LICENSE("GPL");