From: Michal Wilczynski Date: Mon, 14 Oct 2024 08:21:06 +0000 (+0200) Subject: watchdog: light_wdt: Introduce thead watchdog driver X-Git-Tag: accepted/tizen/unified/x/20241017.075855~3 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=d36859bb56714608ebb5205b6319f22edbb7f26f;p=platform%2Fkernel%2Flinux-thead.git watchdog: light_wdt: Introduce thead watchdog driver 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 --- diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 751458959411..56820cf508b1 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 7eab9de311cb..de17c814ac14 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -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 index 000000000000..d3ba15dd650e --- /dev/null +++ b/drivers/watchdog/light_wdt.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Alibaba Group Holding Limited. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("PMIC Watchdog Driver for Light"); +MODULE_LICENSE("GPL");