watchdog: light_wdt: Introduce thead watchdog driver 55/319055/1
authorMichal Wilczynski <m.wilczynski@samsung.com>
Mon, 14 Oct 2024 08:21:06 +0000 (10:21 +0200)
committerMichal Wilczynski <m.wilczynski@samsung.com>
Mon, 14 Oct 2024 08:48:28 +0000 (10:48 +0200)
T-Head watchdog driver is required for 'reboot' feature to work.
Port it from the vendor kernel [1].

[1] - https://gitee.com/thead-yocto/kernel.git

Change-Id: If4d54fcdc65c77b6068b4cd989f3f869fec5428a
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/light_wdt.c [new file with mode: 0644]

index 751458959411ccee91ff62a2ba6b65378a88d569..56820cf508b14aede3a8c0a8b11034e9eff2cf4a 100644 (file)
@@ -1047,6 +1047,23 @@ config SUNPLUS_WATCHDOG
          To compile this driver as a module, choose M here: the
          module will be called sunplus_wdt.
 
+# RISCV Architecture
+
+config LIGHT_PMIC_WATCHDOG
+       tristate "THEAD Light pmic watchdog"
+       depends on THEAD_LIGHT_MBOX
+       select WATCHDOG_CORE
+       help
+         This is the driver for the hardware watchdog on Light Board. This watchdog
+         simply watches your kernel to make sure it doesn't freeze, and if
+         it does, it reboots your computer after a certain amount of time.
+
+         To compile this driver as a module, choose M here: the
+         module will be called acquirewdt.
+
+         Most people will say N.
+
+
 # X86 (i386 + ia64 + x86_64) Architecture
 
 config ACQUIRE_WDT
index 7eab9de311cb93a3579615ed91dbe2eecaa816dc..de17c814ac14203f73e32084003e05e5e80805c9 100644 (file)
@@ -100,6 +100,8 @@ obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o
 obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o
 obj-$(CONFIG_MARVELL_GTI_WDT) += marvell_gti_wdt.o
 
+obj-$(CONFIG_LIGHT_PMIC_WATCHDOG) += light_wdt.o
+
 # X86 (i386 + ia64 + x86_64) Architecture
 obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
 obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
diff --git a/drivers/watchdog/light_wdt.c b/drivers/watchdog/light_wdt.c
new file mode 100644 (file)
index 0000000..d3ba15d
--- /dev/null
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 Alibaba Group Holding Limited.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/device.h>
+#include <linux/watchdog.h>
+#include <linux/firmware/thead/ipc.h>
+#include <linux/firmware/thead/light_event.h>
+
+#define DRV_NAME       "light-wdt"
+
+/*
+ *   Watchdog selector to timeout in seconds.
+ *   0: WDT disabled;
+ *   others: timeout = 2048 ms * 2^(TWDSCALE-1).
+ */
+static const unsigned int wdt_timeout[] = {8, 16, 32,128};
+#define LIGHT_TWDSCALE_DISABLE         0
+#define LIGHT_TWDSCALE_MIN             1
+#define LIGHT_TWDSCALE_MAX             (ARRAY_SIZE(wdt_timeout) - 1)
+#define LIGHT_WDT_MIN_TIMEOUT          wdt_timeout[LIGHT_TWDSCALE_MIN]
+#define LIGHT_WDT_MAX_TIMEOUT          wdt_timeout[LIGHT_TWDSCALE_MAX]
+#define LIGHT_WDT_TIMEOUT              wdt_timeout[3]
+#define LIGHT_RESET_PROTECTION_MS      256
+
+struct light_aon_msg_wdg_ctrl {
+       struct light_aon_rpc_msg_hdr hdr;
+       u32 timeout;
+       u32 running_state;
+       u32 reserved[1];
+} __packed __aligned(1);
+
+struct light_aon_msg_wdg_ctrl_ack {
+       struct light_aon_rpc_ack_common ack_hdr;
+       u32 timeout;
+       u32 running_state;
+       u32 reserved[1];
+} __packed __aligned(1);
+
+struct light_wdt_device {
+       struct device *dev;
+       struct light_aon_ipc *ipc_handle;
+       struct light_aon_msg_wdg_ctrl msg;
+       unsigned int    is_aon_wdt_ena;
+};
+
+struct light_wdt_device *light_power_off_wdt;
+
+static unsigned int light_wdt_timeout_to_sel(unsigned secs)
+{
+       unsigned int i;
+
+       for (i = LIGHT_TWDSCALE_MIN; i <= LIGHT_TWDSCALE_MAX; i++) {
+               if (wdt_timeout[i] >= secs)
+                       return i;
+       }
+
+       return LIGHT_TWDSCALE_MAX;
+}
+
+static void light_wdt_msg_hdr_fill(struct light_aon_rpc_msg_hdr *hdr, enum light_aon_misc_func func)
+{
+       hdr->svc = (uint8_t)LIGHT_AON_RPC_SVC_WDG;
+       hdr->func = (uint8_t)func;
+       hdr->size = LIGHT_AON_RPC_MSG_NUM;
+}
+
+static int light_wdt_is_running(struct light_wdt_device *wdt_dev)
+{
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_msg_wdg_ctrl_ack ack_msg= {0};
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_GET_STATE);
+       wdt_dev->msg.running_state = -1;
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret)
+               return ret;
+
+       //RPC_GET_BE32(&ack_msg.timeout, 0, &wdt_dev->msg.timeout);
+       RPC_GET_BE32(&ack_msg.timeout, 4, &wdt_dev->msg.running_state);
+
+       pr_debug("ret = %d, timeout = %d, running_state = %d\n", ret, wdt_dev->msg.timeout,
+                       wdt_dev->msg.running_state);
+
+       return wdt_dev->msg.running_state;
+}
+
+static int light_wdt_update_timeout(struct light_wdt_device *wdt_dev, unsigned int timeout)
+{
+       /*
+        * The watchdog triggers a reboot if a timeout value is already
+        * programmed because the timeout value combines two functions
+        * in one: indicating the counter limit and starting the watchdog.
+        * The watchdog must be disabled to be able to change the timeout
+        * value if the watchdog is already running. Then we can set the
+        * new timeout value which enables the watchdog again.
+        */
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_msg_wdg_ctrl_ack ack_msg= {0};
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_TIMEOUTSET);
+
+    RPC_SET_BE32(&wdt_dev->msg.timeout, 0 , timeout);
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int light_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+       struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
+       int ret = 0;
+
+       /*
+        * There are two cases when a set_timeout() will be called:
+        * 1. The watchdog is off and someone wants to set the timeout for the
+        *    further use.
+        * 2. The watchdog is already running and a new timeout value should be
+        *    set.
+        *
+        * The watchdog can't store a timeout value not equal zero without
+        * enabling the watchdog, so the timeout must be buffered by the driver.
+        */
+       if (watchdog_active(wdd))
+               ret = light_wdt_update_timeout(wdt_dev, timeout);
+       else
+               wdd->timeout = wdt_timeout[light_wdt_timeout_to_sel(timeout)];
+
+       return ret;
+}
+
+static int light_wdt_start(struct watchdog_device *wdd)
+{
+       struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_rpc_ack_common ack_msg = {0};
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_START);
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int light_wdt_stop(struct watchdog_device *wdd)
+{
+       struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_rpc_ack_common ack_msg = {0};
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_STOP);
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int light_wdt_ping(struct watchdog_device *wdd)
+{
+       struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_rpc_ack_common ack_msg = {0};
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_PING);
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int light_wdt_restart(struct watchdog_device *wdd, unsigned long action, void *data)
+{
+       struct light_wdt_device *wdt_dev = watchdog_get_drvdata(wdd);
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       int ret;
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_RESTART);
+
+       pr_debug("[%s,%d]: Inform aon to restart the whole system....\n", __func__, __LINE__);
+
+       light_event_set_rebootmode(LIGHT_EVENT_SW_REBOOT);
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, NULL, false);
+       if (ret)
+               return ret;
+       pr_debug("[%s,%d]: Finish to inform aon to restart the whole system....\n", __func__, __LINE__);
+
+       return 0;
+}
+
+static const struct watchdog_info light_watchdog_info = {
+       .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+       .identity = "Light Watchdog",
+};
+
+
+static const struct watchdog_ops light_watchdog_ops = {
+       .owner = THIS_MODULE,
+       .start = light_wdt_start,
+       .stop = light_wdt_stop,
+       .ping = light_wdt_ping,
+       .set_timeout = light_wdt_set_timeout,
+       .restart = light_wdt_restart,
+};
+
+static ssize_t aon_sys_wdt_show(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct light_wdt_device *wdt_dev = platform_get_drvdata(pdev);
+       return sprintf(buf,"%u\n",wdt_dev->is_aon_wdt_ena);
+}
+
+static ssize_t aon_sys_wdt_store(struct device *dev,
+                                struct device_attribute *attr,
+                                const char *buf, size_t size)
+{
+       struct platform_device *pdev = to_platform_device(dev);
+       struct light_wdt_device *wdt_dev = platform_get_drvdata(pdev);
+       struct light_aon_rpc_ack_common ack_msg = {0};
+       struct light_aon_ipc *ipc;
+       int ret;
+       char *start = (char *)buf;
+       unsigned long val;
+
+       ipc = wdt_dev->ipc_handle;
+       val = simple_strtoul(start, &start, 0);
+       wdt_dev->is_aon_wdt_ena = val;
+       if (val)
+               light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_AON_WDT_ON);
+       else
+               light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_AON_WDT_OFF);
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+       if (ret){
+               pr_err("%s: err:%d \n",__func__,ret);
+               return -EINVAL;
+       }
+       return size;
+}
+
+void light_pm_power_off(void)
+{
+       struct light_wdt_device *wdt_dev = light_power_off_wdt;
+       struct light_aon_ipc *ipc = wdt_dev->ipc_handle;
+       struct light_aon_rpc_ack_common ack_msg = {0};
+       int ret;
+
+       pr_info("[%s,%d]poweroff system...\n", __func__, __LINE__);
+
+       light_wdt_msg_hdr_fill(&wdt_dev->msg.hdr, LIGHT_AON_WDG_FUNC_POWER_OFF);
+
+       ret = light_aon_call_rpc(ipc, &wdt_dev->msg, &ack_msg, true);
+
+       if (ret)
+               pr_err("failed to power off the system\n");
+}
+
+
+static DEVICE_ATTR(aon_sys_wdt, 0644, aon_sys_wdt_show, aon_sys_wdt_store);
+
+static struct attribute *aon_sys_wdt_sysfs_entries[] = {
+       &dev_attr_aon_sys_wdt.attr,
+       NULL
+};
+static const struct attribute_group dev_attr_aon_sys_wdt_group = {
+       .attrs = aon_sys_wdt_sysfs_entries,
+};
+
+static int light_wdt_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct light_wdt_device *wdt_dev;
+       int ret;
+       struct watchdog_device *wdd;
+
+       wdt_dev = devm_kzalloc(dev, sizeof(*wdt_dev), GFP_KERNEL);
+       if (!wdt_dev)
+               return -ENOMEM;
+       wdt_dev->is_aon_wdt_ena = 0;
+
+       ret = light_aon_get_handle(&(wdt_dev->ipc_handle));
+       if (ret == -EPROBE_DEFER)
+               return ret;
+
+       wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
+       if (!wdd)
+               return -ENOMEM;
+
+       wdd->info = &light_watchdog_info;
+       wdd->ops = &light_watchdog_ops;
+       wdd->min_timeout = LIGHT_WDT_MIN_TIMEOUT;
+       wdd->max_timeout = LIGHT_WDT_MAX_TIMEOUT;
+       wdd->min_hw_heartbeat_ms = LIGHT_RESET_PROTECTION_MS;
+       wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+       watchdog_set_restart_priority(wdd, 128);
+       watchdog_set_drvdata(wdd, wdt_dev);
+
+       /* Set default timeout, maybe default value if the watchdog is running */
+       wdd->timeout = LIGHT_WDT_TIMEOUT;
+       watchdog_init_timeout(wdd, 0, dev);
+       light_wdt_set_timeout(wdd, wdd->timeout);
+
+       platform_set_drvdata(pdev, wdt_dev);
+       ret = light_wdt_is_running(wdt_dev);
+       if (ret < 0) {
+               pr_err("failed to get pmic wdt running state\n");
+               return ret;
+       }
+
+       if (ret) {
+               light_wdt_update_timeout(wdt_dev, wdd->timeout);
+               set_bit(WDOG_HW_RUNNING, &wdd->status);
+       }
+
+       ret = devm_watchdog_register_device(dev, wdd);
+       if (ret)
+               return ret;
+
+       pr_info("[%s,%d] register power off callback\n", __func__, __LINE__);
+
+       pm_power_off = light_pm_power_off;
+
+       light_power_off_wdt = wdt_dev;
+
+       ret = sysfs_create_group(&pdev->dev.kobj, &dev_attr_aon_sys_wdt_group);
+       if (ret) {
+               dev_err(&pdev->dev, "Failed to create aon_sys_wdt sysfs.\n");
+               return ret;
+       }
+
+       pr_info("succeed to register light pmic watchdog\n");
+
+       return 0;
+}
+
+static struct platform_driver light_wdt_driver = {
+       .driver = {
+               .name = DRV_NAME,
+       },
+       .probe = light_wdt_probe,
+};
+
+static int __init light_wdt_init(void)
+{
+       static struct platform_device *pdev;
+       int ret;
+
+       pdev = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
+       if (IS_ERR(pdev))
+               return PTR_ERR(pdev);
+
+       ret = platform_driver_register(&light_wdt_driver);
+       if (ret) {
+               platform_device_unregister(pdev);
+               return PTR_ERR(pdev);
+       }
+
+       pr_info("Watchdog module: %s loaded\n", DRV_NAME);
+
+       return 0;
+}
+device_initcall(light_wdt_init);
+
+MODULE_AUTHOR("Wei.Liu <lw312886@linux.alibaba.com>");
+MODULE_DESCRIPTION("PMIC Watchdog Driver for Light");
+MODULE_LICENSE("GPL");