--- /dev/null
+// 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");