112984ceaSSrinivas Neeli // SPDX-License-Identifier: GPL-2.0
212984ceaSSrinivas Neeli /*
312984ceaSSrinivas Neeli * Window watchdog device driver for Xilinx Versal WWDT
412984ceaSSrinivas Neeli *
5*babc8a52SHarini T * Copyright (C) 2022 - 2024, Advanced Micro Devices, Inc.
612984ceaSSrinivas Neeli */
712984ceaSSrinivas Neeli
812984ceaSSrinivas Neeli #include <linux/clk.h>
912984ceaSSrinivas Neeli #include <linux/interrupt.h>
1012984ceaSSrinivas Neeli #include <linux/io.h>
1112984ceaSSrinivas Neeli #include <linux/ioport.h>
1298334dc2SNathan Chancellor #include <linux/math64.h>
13cc85f87aSRob Herring #include <linux/mod_devicetable.h>
1412984ceaSSrinivas Neeli #include <linux/module.h>
15cc85f87aSRob Herring #include <linux/platform_device.h>
1612984ceaSSrinivas Neeli #include <linux/watchdog.h>
1712984ceaSSrinivas Neeli
1812984ceaSSrinivas Neeli /* Max timeout is calculated at 100MHz source clock */
1912984ceaSSrinivas Neeli #define XWWDT_DEFAULT_TIMEOUT 42
2012984ceaSSrinivas Neeli #define XWWDT_MIN_TIMEOUT 1
2112984ceaSSrinivas Neeli
2212984ceaSSrinivas Neeli /* Register offsets for the WWDT device */
2312984ceaSSrinivas Neeli #define XWWDT_MWR_OFFSET 0x00
2412984ceaSSrinivas Neeli #define XWWDT_ESR_OFFSET 0x04
2512984ceaSSrinivas Neeli #define XWWDT_FCR_OFFSET 0x08
2612984ceaSSrinivas Neeli #define XWWDT_FWR_OFFSET 0x0c
2712984ceaSSrinivas Neeli #define XWWDT_SWR_OFFSET 0x10
2812984ceaSSrinivas Neeli
2912984ceaSSrinivas Neeli /* Master Write Control Register Masks */
3012984ceaSSrinivas Neeli #define XWWDT_MWR_MASK BIT(0)
3112984ceaSSrinivas Neeli
3212984ceaSSrinivas Neeli /* Enable and Status Register Masks */
3312984ceaSSrinivas Neeli #define XWWDT_ESR_WINT_MASK BIT(16)
3412984ceaSSrinivas Neeli #define XWWDT_ESR_WSW_MASK BIT(8)
3512984ceaSSrinivas Neeli #define XWWDT_ESR_WEN_MASK BIT(0)
3612984ceaSSrinivas Neeli
3712984ceaSSrinivas Neeli #define XWWDT_CLOSE_WINDOW_PERCENT 50
3812984ceaSSrinivas Neeli
39*babc8a52SHarini T /* Maximum count value of each 32 bit window */
40*babc8a52SHarini T #define XWWDT_MAX_COUNT_WINDOW GENMASK(31, 0)
41*babc8a52SHarini T
42*babc8a52SHarini T /* Maximum count value of closed and open window combined */
43*babc8a52SHarini T #define XWWDT_MAX_COUNT_WINDOW_COMBINED GENMASK_ULL(32, 1)
44*babc8a52SHarini T
4512984ceaSSrinivas Neeli static int wwdt_timeout;
4612984ceaSSrinivas Neeli static int closed_window_percent;
4712984ceaSSrinivas Neeli
4812984ceaSSrinivas Neeli module_param(wwdt_timeout, int, 0);
4912984ceaSSrinivas Neeli MODULE_PARM_DESC(wwdt_timeout,
5012984ceaSSrinivas Neeli "Watchdog time in seconds. (default="
5112984ceaSSrinivas Neeli __MODULE_STRING(XWWDT_DEFAULT_TIMEOUT) ")");
5212984ceaSSrinivas Neeli module_param(closed_window_percent, int, 0);
5312984ceaSSrinivas Neeli MODULE_PARM_DESC(closed_window_percent,
5412984ceaSSrinivas Neeli "Watchdog closed window percentage. (default="
5512984ceaSSrinivas Neeli __MODULE_STRING(XWWDT_CLOSE_WINDOW_PERCENT) ")");
5612984ceaSSrinivas Neeli /**
5712984ceaSSrinivas Neeli * struct xwwdt_device - Watchdog device structure
5812984ceaSSrinivas Neeli * @base: base io address of WDT device
5912984ceaSSrinivas Neeli * @spinlock: spinlock for IO register access
6012984ceaSSrinivas Neeli * @xilinx_wwdt_wdd: watchdog device structure
6112984ceaSSrinivas Neeli * @freq: source clock frequency of WWDT
6212984ceaSSrinivas Neeli * @close_percent: Closed window percent
63*babc8a52SHarini T * @closed_timeout: Closed window timeout in ticks
64*babc8a52SHarini T * @open_timeout: Open window timeout in ticks
6512984ceaSSrinivas Neeli */
6612984ceaSSrinivas Neeli struct xwwdt_device {
6712984ceaSSrinivas Neeli void __iomem *base;
6812984ceaSSrinivas Neeli spinlock_t spinlock; /* spinlock for register handling */
6912984ceaSSrinivas Neeli struct watchdog_device xilinx_wwdt_wdd;
7012984ceaSSrinivas Neeli unsigned long freq;
7112984ceaSSrinivas Neeli u32 close_percent;
72*babc8a52SHarini T u64 closed_timeout;
73*babc8a52SHarini T u64 open_timeout;
7412984ceaSSrinivas Neeli };
7512984ceaSSrinivas Neeli
xilinx_wwdt_start(struct watchdog_device * wdd)7612984ceaSSrinivas Neeli static int xilinx_wwdt_start(struct watchdog_device *wdd)
7712984ceaSSrinivas Neeli {
7812984ceaSSrinivas Neeli struct xwwdt_device *xdev = watchdog_get_drvdata(wdd);
7912984ceaSSrinivas Neeli struct watchdog_device *xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd;
8012984ceaSSrinivas Neeli u32 control_status_reg;
8112984ceaSSrinivas Neeli
8212984ceaSSrinivas Neeli spin_lock(&xdev->spinlock);
8312984ceaSSrinivas Neeli
8412984ceaSSrinivas Neeli iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET);
8512984ceaSSrinivas Neeli iowrite32(~(u32)XWWDT_ESR_WEN_MASK, xdev->base + XWWDT_ESR_OFFSET);
86*babc8a52SHarini T iowrite32((u32)xdev->closed_timeout, xdev->base + XWWDT_FWR_OFFSET);
87*babc8a52SHarini T iowrite32((u32)xdev->open_timeout, xdev->base + XWWDT_SWR_OFFSET);
8812984ceaSSrinivas Neeli
8912984ceaSSrinivas Neeli /* Enable the window watchdog timer */
9012984ceaSSrinivas Neeli control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET);
9112984ceaSSrinivas Neeli control_status_reg |= XWWDT_ESR_WEN_MASK;
9212984ceaSSrinivas Neeli iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET);
9312984ceaSSrinivas Neeli
9412984ceaSSrinivas Neeli spin_unlock(&xdev->spinlock);
9512984ceaSSrinivas Neeli
9612984ceaSSrinivas Neeli dev_dbg(xilinx_wwdt_wdd->parent, "Watchdog Started!\n");
9712984ceaSSrinivas Neeli
9812984ceaSSrinivas Neeli return 0;
9912984ceaSSrinivas Neeli }
10012984ceaSSrinivas Neeli
xilinx_wwdt_keepalive(struct watchdog_device * wdd)10112984ceaSSrinivas Neeli static int xilinx_wwdt_keepalive(struct watchdog_device *wdd)
10212984ceaSSrinivas Neeli {
10312984ceaSSrinivas Neeli struct xwwdt_device *xdev = watchdog_get_drvdata(wdd);
10412984ceaSSrinivas Neeli u32 control_status_reg;
10512984ceaSSrinivas Neeli
10612984ceaSSrinivas Neeli spin_lock(&xdev->spinlock);
10712984ceaSSrinivas Neeli
10812984ceaSSrinivas Neeli /* Enable write access control bit for the window watchdog */
10912984ceaSSrinivas Neeli iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET);
11012984ceaSSrinivas Neeli
11112984ceaSSrinivas Neeli /* Trigger restart kick to watchdog */
11212984ceaSSrinivas Neeli control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET);
11312984ceaSSrinivas Neeli control_status_reg |= XWWDT_ESR_WSW_MASK;
11412984ceaSSrinivas Neeli iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET);
11512984ceaSSrinivas Neeli
11612984ceaSSrinivas Neeli spin_unlock(&xdev->spinlock);
11712984ceaSSrinivas Neeli
11812984ceaSSrinivas Neeli return 0;
11912984ceaSSrinivas Neeli }
12012984ceaSSrinivas Neeli
12112984ceaSSrinivas Neeli static const struct watchdog_info xilinx_wwdt_ident = {
12212984ceaSSrinivas Neeli .options = WDIOF_KEEPALIVEPING |
12312984ceaSSrinivas Neeli WDIOF_SETTIMEOUT,
12412984ceaSSrinivas Neeli .firmware_version = 1,
12512984ceaSSrinivas Neeli .identity = "xlnx_window watchdog",
12612984ceaSSrinivas Neeli };
12712984ceaSSrinivas Neeli
12812984ceaSSrinivas Neeli static const struct watchdog_ops xilinx_wwdt_ops = {
12912984ceaSSrinivas Neeli .owner = THIS_MODULE,
13012984ceaSSrinivas Neeli .start = xilinx_wwdt_start,
13112984ceaSSrinivas Neeli .ping = xilinx_wwdt_keepalive,
13212984ceaSSrinivas Neeli };
13312984ceaSSrinivas Neeli
xwwdt_probe(struct platform_device * pdev)13412984ceaSSrinivas Neeli static int xwwdt_probe(struct platform_device *pdev)
13512984ceaSSrinivas Neeli {
13612984ceaSSrinivas Neeli struct watchdog_device *xilinx_wwdt_wdd;
13712984ceaSSrinivas Neeli struct device *dev = &pdev->dev;
13812984ceaSSrinivas Neeli struct xwwdt_device *xdev;
139*babc8a52SHarini T u64 max_per_window_ms;
140*babc8a52SHarini T u64 min_per_window_ms;
141*babc8a52SHarini T u64 timeout_count;
14212984ceaSSrinivas Neeli struct clk *clk;
143*babc8a52SHarini T u32 timeout_ms;
144*babc8a52SHarini T u64 ms_count;
14512984ceaSSrinivas Neeli int ret;
14612984ceaSSrinivas Neeli
14712984ceaSSrinivas Neeli xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL);
14812984ceaSSrinivas Neeli if (!xdev)
14912984ceaSSrinivas Neeli return -ENOMEM;
15012984ceaSSrinivas Neeli
15112984ceaSSrinivas Neeli xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd;
15212984ceaSSrinivas Neeli xilinx_wwdt_wdd->info = &xilinx_wwdt_ident;
15312984ceaSSrinivas Neeli xilinx_wwdt_wdd->ops = &xilinx_wwdt_ops;
15412984ceaSSrinivas Neeli xilinx_wwdt_wdd->parent = dev;
15512984ceaSSrinivas Neeli
15612984ceaSSrinivas Neeli xdev->base = devm_platform_ioremap_resource(pdev, 0);
15712984ceaSSrinivas Neeli if (IS_ERR(xdev->base))
15812984ceaSSrinivas Neeli return PTR_ERR(xdev->base);
15912984ceaSSrinivas Neeli
16012984ceaSSrinivas Neeli clk = devm_clk_get_enabled(dev, NULL);
16112984ceaSSrinivas Neeli if (IS_ERR(clk))
16212984ceaSSrinivas Neeli return PTR_ERR(clk);
16312984ceaSSrinivas Neeli
16412984ceaSSrinivas Neeli xdev->freq = clk_get_rate(clk);
165*babc8a52SHarini T if (xdev->freq < 1000000)
16612984ceaSSrinivas Neeli return -EINVAL;
16712984ceaSSrinivas Neeli
16812984ceaSSrinivas Neeli xilinx_wwdt_wdd->min_timeout = XWWDT_MIN_TIMEOUT;
16912984ceaSSrinivas Neeli xilinx_wwdt_wdd->timeout = XWWDT_DEFAULT_TIMEOUT;
170*babc8a52SHarini T xilinx_wwdt_wdd->max_hw_heartbeat_ms =
171*babc8a52SHarini T div64_u64(XWWDT_MAX_COUNT_WINDOW_COMBINED, xdev->freq) * 1000;
17212984ceaSSrinivas Neeli
17312984ceaSSrinivas Neeli if (closed_window_percent == 0 || closed_window_percent >= 100)
17412984ceaSSrinivas Neeli xdev->close_percent = XWWDT_CLOSE_WINDOW_PERCENT;
17512984ceaSSrinivas Neeli else
17612984ceaSSrinivas Neeli xdev->close_percent = closed_window_percent;
17712984ceaSSrinivas Neeli
17812984ceaSSrinivas Neeli watchdog_init_timeout(xilinx_wwdt_wdd, wwdt_timeout, &pdev->dev);
179*babc8a52SHarini T
180*babc8a52SHarini T /* Calculate ticks for 1 milli-second */
181*babc8a52SHarini T ms_count = div_u64(xdev->freq, 1000);
182*babc8a52SHarini T timeout_ms = xilinx_wwdt_wdd->timeout * 1000;
183*babc8a52SHarini T timeout_count = timeout_ms * ms_count;
184*babc8a52SHarini T
185*babc8a52SHarini T if (timeout_ms > xilinx_wwdt_wdd->max_hw_heartbeat_ms) {
186*babc8a52SHarini T /*
187*babc8a52SHarini T * To avoid ping restrictions until the minimum hardware heartbeat,
188*babc8a52SHarini T * we will solely rely on the open window and
189*babc8a52SHarini T * adjust the minimum hardware heartbeat to 0.
190*babc8a52SHarini T */
191*babc8a52SHarini T xdev->closed_timeout = 0;
192*babc8a52SHarini T xdev->open_timeout = XWWDT_MAX_COUNT_WINDOW;
193*babc8a52SHarini T xilinx_wwdt_wdd->min_hw_heartbeat_ms = 0;
194*babc8a52SHarini T xilinx_wwdt_wdd->max_hw_heartbeat_ms = xilinx_wwdt_wdd->max_hw_heartbeat_ms / 2;
195*babc8a52SHarini T } else {
196*babc8a52SHarini T xdev->closed_timeout = div64_u64(timeout_count * xdev->close_percent, 100);
197*babc8a52SHarini T xilinx_wwdt_wdd->min_hw_heartbeat_ms =
198*babc8a52SHarini T div64_u64(timeout_ms * xdev->close_percent, 100);
199*babc8a52SHarini T
200*babc8a52SHarini T if (timeout_ms > xilinx_wwdt_wdd->max_hw_heartbeat_ms / 2) {
201*babc8a52SHarini T max_per_window_ms = xilinx_wwdt_wdd->max_hw_heartbeat_ms / 2;
202*babc8a52SHarini T min_per_window_ms = timeout_ms - max_per_window_ms;
203*babc8a52SHarini T
204*babc8a52SHarini T if (xilinx_wwdt_wdd->min_hw_heartbeat_ms > max_per_window_ms) {
205*babc8a52SHarini T dev_info(xilinx_wwdt_wdd->parent,
206*babc8a52SHarini T "Closed window cannot be set to %d%%. Using maximum supported value.\n",
207*babc8a52SHarini T xdev->close_percent);
208*babc8a52SHarini T xdev->closed_timeout = max_per_window_ms * ms_count;
209*babc8a52SHarini T xilinx_wwdt_wdd->min_hw_heartbeat_ms = max_per_window_ms;
210*babc8a52SHarini T } else if (xilinx_wwdt_wdd->min_hw_heartbeat_ms < min_per_window_ms) {
211*babc8a52SHarini T dev_info(xilinx_wwdt_wdd->parent,
212*babc8a52SHarini T "Closed window cannot be set to %d%%. Using minimum supported value.\n",
213*babc8a52SHarini T xdev->close_percent);
214*babc8a52SHarini T xdev->closed_timeout = min_per_window_ms * ms_count;
215*babc8a52SHarini T xilinx_wwdt_wdd->min_hw_heartbeat_ms = min_per_window_ms;
216*babc8a52SHarini T }
217*babc8a52SHarini T }
218*babc8a52SHarini T xdev->open_timeout = timeout_count - xdev->closed_timeout;
219*babc8a52SHarini T }
220*babc8a52SHarini T
22112984ceaSSrinivas Neeli spin_lock_init(&xdev->spinlock);
22212984ceaSSrinivas Neeli watchdog_set_drvdata(xilinx_wwdt_wdd, xdev);
22312984ceaSSrinivas Neeli watchdog_set_nowayout(xilinx_wwdt_wdd, 1);
22412984ceaSSrinivas Neeli
22512984ceaSSrinivas Neeli ret = devm_watchdog_register_device(dev, xilinx_wwdt_wdd);
22612984ceaSSrinivas Neeli if (ret)
22712984ceaSSrinivas Neeli return ret;
22812984ceaSSrinivas Neeli
22912984ceaSSrinivas Neeli dev_info(dev, "Xilinx window watchdog Timer with timeout %ds\n",
23012984ceaSSrinivas Neeli xilinx_wwdt_wdd->timeout);
23112984ceaSSrinivas Neeli
23212984ceaSSrinivas Neeli return 0;
23312984ceaSSrinivas Neeli }
23412984ceaSSrinivas Neeli
23512984ceaSSrinivas Neeli static const struct of_device_id xwwdt_of_match[] = {
23612984ceaSSrinivas Neeli { .compatible = "xlnx,versal-wwdt", },
23712984ceaSSrinivas Neeli {},
23812984ceaSSrinivas Neeli };
23912984ceaSSrinivas Neeli MODULE_DEVICE_TABLE(of, xwwdt_of_match);
24012984ceaSSrinivas Neeli
24112984ceaSSrinivas Neeli static struct platform_driver xwwdt_driver = {
24212984ceaSSrinivas Neeli .probe = xwwdt_probe,
24312984ceaSSrinivas Neeli .driver = {
24412984ceaSSrinivas Neeli .name = "Xilinx window watchdog",
24512984ceaSSrinivas Neeli .of_match_table = xwwdt_of_match,
24612984ceaSSrinivas Neeli },
24712984ceaSSrinivas Neeli };
24812984ceaSSrinivas Neeli
24912984ceaSSrinivas Neeli module_platform_driver(xwwdt_driver);
25012984ceaSSrinivas Neeli
25112984ceaSSrinivas Neeli MODULE_AUTHOR("Neeli Srinivas <srinivas.neeli@amd.com>");
25212984ceaSSrinivas Neeli MODULE_DESCRIPTION("Xilinx window watchdog driver");
25312984ceaSSrinivas Neeli MODULE_LICENSE("GPL");
254