| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * Window watchdog device driver for Xilinx Versal WWDT | 
 |  * | 
 |  * Copyright (C) 2022 - 2023, Advanced Micro Devices, Inc. | 
 |  */ | 
 |  | 
 | #include <linux/clk.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/io.h> | 
 | #include <linux/ioport.h> | 
 | #include <linux/math64.h> | 
 | #include <linux/mod_devicetable.h> | 
 | #include <linux/module.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/watchdog.h> | 
 |  | 
 | /* Max timeout is calculated at 100MHz source clock */ | 
 | #define XWWDT_DEFAULT_TIMEOUT	42 | 
 | #define XWWDT_MIN_TIMEOUT	1 | 
 |  | 
 | /* Register offsets for the WWDT device */ | 
 | #define XWWDT_MWR_OFFSET	0x00 | 
 | #define XWWDT_ESR_OFFSET	0x04 | 
 | #define XWWDT_FCR_OFFSET	0x08 | 
 | #define XWWDT_FWR_OFFSET	0x0c | 
 | #define XWWDT_SWR_OFFSET	0x10 | 
 |  | 
 | /* Master Write Control Register Masks */ | 
 | #define XWWDT_MWR_MASK		BIT(0) | 
 |  | 
 | /* Enable and Status Register Masks */ | 
 | #define XWWDT_ESR_WINT_MASK	BIT(16) | 
 | #define XWWDT_ESR_WSW_MASK	BIT(8) | 
 | #define XWWDT_ESR_WEN_MASK	BIT(0) | 
 |  | 
 | #define XWWDT_CLOSE_WINDOW_PERCENT	50 | 
 |  | 
 | static int wwdt_timeout; | 
 | static int closed_window_percent; | 
 |  | 
 | module_param(wwdt_timeout, int, 0); | 
 | MODULE_PARM_DESC(wwdt_timeout, | 
 | 		 "Watchdog time in seconds. (default=" | 
 | 		 __MODULE_STRING(XWWDT_DEFAULT_TIMEOUT) ")"); | 
 | module_param(closed_window_percent, int, 0); | 
 | MODULE_PARM_DESC(closed_window_percent, | 
 | 		 "Watchdog closed window percentage. (default=" | 
 | 		 __MODULE_STRING(XWWDT_CLOSE_WINDOW_PERCENT) ")"); | 
 | /** | 
 |  * struct xwwdt_device - Watchdog device structure | 
 |  * @base: base io address of WDT device | 
 |  * @spinlock: spinlock for IO register access | 
 |  * @xilinx_wwdt_wdd: watchdog device structure | 
 |  * @freq: source clock frequency of WWDT | 
 |  * @close_percent: Closed window percent | 
 |  */ | 
 | struct xwwdt_device { | 
 | 	void __iomem *base; | 
 | 	spinlock_t spinlock; /* spinlock for register handling */ | 
 | 	struct watchdog_device xilinx_wwdt_wdd; | 
 | 	unsigned long freq; | 
 | 	u32 close_percent; | 
 | }; | 
 |  | 
 | static int xilinx_wwdt_start(struct watchdog_device *wdd) | 
 | { | 
 | 	struct xwwdt_device *xdev = watchdog_get_drvdata(wdd); | 
 | 	struct watchdog_device *xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd; | 
 | 	u64 time_out, closed_timeout, open_timeout; | 
 | 	u32 control_status_reg; | 
 |  | 
 | 	/* Calculate timeout count */ | 
 | 	time_out = xdev->freq * wdd->timeout; | 
 | 	closed_timeout = div_u64(time_out * xdev->close_percent, 100); | 
 | 	open_timeout = time_out - closed_timeout; | 
 | 	wdd->min_hw_heartbeat_ms = xdev->close_percent * 10 * wdd->timeout; | 
 |  | 
 | 	spin_lock(&xdev->spinlock); | 
 |  | 
 | 	iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET); | 
 | 	iowrite32(~(u32)XWWDT_ESR_WEN_MASK, xdev->base + XWWDT_ESR_OFFSET); | 
 | 	iowrite32((u32)closed_timeout, xdev->base + XWWDT_FWR_OFFSET); | 
 | 	iowrite32((u32)open_timeout, xdev->base + XWWDT_SWR_OFFSET); | 
 |  | 
 | 	/* Enable the window watchdog timer */ | 
 | 	control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET); | 
 | 	control_status_reg |= XWWDT_ESR_WEN_MASK; | 
 | 	iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET); | 
 |  | 
 | 	spin_unlock(&xdev->spinlock); | 
 |  | 
 | 	dev_dbg(xilinx_wwdt_wdd->parent, "Watchdog Started!\n"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int xilinx_wwdt_keepalive(struct watchdog_device *wdd) | 
 | { | 
 | 	struct xwwdt_device *xdev = watchdog_get_drvdata(wdd); | 
 | 	u32 control_status_reg; | 
 |  | 
 | 	spin_lock(&xdev->spinlock); | 
 |  | 
 | 	/* Enable write access control bit for the window watchdog */ | 
 | 	iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET); | 
 |  | 
 | 	/* Trigger restart kick to watchdog */ | 
 | 	control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET); | 
 | 	control_status_reg |= XWWDT_ESR_WSW_MASK; | 
 | 	iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET); | 
 |  | 
 | 	spin_unlock(&xdev->spinlock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct watchdog_info xilinx_wwdt_ident = { | 
 | 	.options = WDIOF_KEEPALIVEPING | | 
 | 		WDIOF_SETTIMEOUT, | 
 | 	.firmware_version = 1, | 
 | 	.identity = "xlnx_window watchdog", | 
 | }; | 
 |  | 
 | static const struct watchdog_ops xilinx_wwdt_ops = { | 
 | 	.owner = THIS_MODULE, | 
 | 	.start = xilinx_wwdt_start, | 
 | 	.ping = xilinx_wwdt_keepalive, | 
 | }; | 
 |  | 
 | static int xwwdt_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct watchdog_device *xilinx_wwdt_wdd; | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct xwwdt_device *xdev; | 
 | 	struct clk *clk; | 
 | 	int ret; | 
 |  | 
 | 	xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL); | 
 | 	if (!xdev) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd; | 
 | 	xilinx_wwdt_wdd->info = &xilinx_wwdt_ident; | 
 | 	xilinx_wwdt_wdd->ops = &xilinx_wwdt_ops; | 
 | 	xilinx_wwdt_wdd->parent = dev; | 
 |  | 
 | 	xdev->base = devm_platform_ioremap_resource(pdev, 0); | 
 | 	if (IS_ERR(xdev->base)) | 
 | 		return PTR_ERR(xdev->base); | 
 |  | 
 | 	clk = devm_clk_get_enabled(dev, NULL); | 
 | 	if (IS_ERR(clk)) | 
 | 		return PTR_ERR(clk); | 
 |  | 
 | 	xdev->freq = clk_get_rate(clk); | 
 | 	if (!xdev->freq) | 
 | 		return -EINVAL; | 
 |  | 
 | 	xilinx_wwdt_wdd->min_timeout = XWWDT_MIN_TIMEOUT; | 
 | 	xilinx_wwdt_wdd->timeout = XWWDT_DEFAULT_TIMEOUT; | 
 | 	xilinx_wwdt_wdd->max_hw_heartbeat_ms = 1000 * xilinx_wwdt_wdd->timeout; | 
 |  | 
 | 	if (closed_window_percent == 0 || closed_window_percent >= 100) | 
 | 		xdev->close_percent = XWWDT_CLOSE_WINDOW_PERCENT; | 
 | 	else | 
 | 		xdev->close_percent = closed_window_percent; | 
 |  | 
 | 	watchdog_init_timeout(xilinx_wwdt_wdd, wwdt_timeout, &pdev->dev); | 
 | 	spin_lock_init(&xdev->spinlock); | 
 | 	watchdog_set_drvdata(xilinx_wwdt_wdd, xdev); | 
 | 	watchdog_set_nowayout(xilinx_wwdt_wdd, 1); | 
 |  | 
 | 	ret = devm_watchdog_register_device(dev, xilinx_wwdt_wdd); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	dev_info(dev, "Xilinx window watchdog Timer with timeout %ds\n", | 
 | 		 xilinx_wwdt_wdd->timeout); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct of_device_id xwwdt_of_match[] = { | 
 | 	{ .compatible = "xlnx,versal-wwdt", }, | 
 | 	{}, | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, xwwdt_of_match); | 
 |  | 
 | static struct platform_driver xwwdt_driver = { | 
 | 	.probe = xwwdt_probe, | 
 | 	.driver = { | 
 | 		.name = "Xilinx window watchdog", | 
 | 		.of_match_table = xwwdt_of_match, | 
 | 	}, | 
 | }; | 
 |  | 
 | module_platform_driver(xwwdt_driver); | 
 |  | 
 | MODULE_AUTHOR("Neeli Srinivas <srinivas.neeli@amd.com>"); | 
 | MODULE_DESCRIPTION("Xilinx window watchdog driver"); | 
 | MODULE_LICENSE("GPL"); |