1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Window watchdog device driver for Xilinx Versal WWDT
4  *
5  * Copyright (C) 2022 - 2023, Advanced Micro Devices, Inc.
6  */
7 
8 #include <linux/clk.h>
9 #include <linux/interrupt.h>
10 #include <linux/io.h>
11 #include <linux/ioport.h>
12 #include <linux/module.h>
13 #include <linux/of_device.h>
14 #include <linux/of_address.h>
15 #include <linux/watchdog.h>
16 
17 /* Max timeout is calculated at 100MHz source clock */
18 #define XWWDT_DEFAULT_TIMEOUT	42
19 #define XWWDT_MIN_TIMEOUT	1
20 
21 /* Register offsets for the WWDT device */
22 #define XWWDT_MWR_OFFSET	0x00
23 #define XWWDT_ESR_OFFSET	0x04
24 #define XWWDT_FCR_OFFSET	0x08
25 #define XWWDT_FWR_OFFSET	0x0c
26 #define XWWDT_SWR_OFFSET	0x10
27 
28 /* Master Write Control Register Masks */
29 #define XWWDT_MWR_MASK		BIT(0)
30 
31 /* Enable and Status Register Masks */
32 #define XWWDT_ESR_WINT_MASK	BIT(16)
33 #define XWWDT_ESR_WSW_MASK	BIT(8)
34 #define XWWDT_ESR_WEN_MASK	BIT(0)
35 
36 #define XWWDT_CLOSE_WINDOW_PERCENT	50
37 
38 static int wwdt_timeout;
39 static int closed_window_percent;
40 
41 module_param(wwdt_timeout, int, 0);
42 MODULE_PARM_DESC(wwdt_timeout,
43 		 "Watchdog time in seconds. (default="
44 		 __MODULE_STRING(XWWDT_DEFAULT_TIMEOUT) ")");
45 module_param(closed_window_percent, int, 0);
46 MODULE_PARM_DESC(closed_window_percent,
47 		 "Watchdog closed window percentage. (default="
48 		 __MODULE_STRING(XWWDT_CLOSE_WINDOW_PERCENT) ")");
49 /**
50  * struct xwwdt_device - Watchdog device structure
51  * @base: base io address of WDT device
52  * @spinlock: spinlock for IO register access
53  * @xilinx_wwdt_wdd: watchdog device structure
54  * @freq: source clock frequency of WWDT
55  * @close_percent: Closed window percent
56  */
57 struct xwwdt_device {
58 	void __iomem *base;
59 	spinlock_t spinlock; /* spinlock for register handling */
60 	struct watchdog_device xilinx_wwdt_wdd;
61 	unsigned long freq;
62 	u32 close_percent;
63 };
64 
65 static int xilinx_wwdt_start(struct watchdog_device *wdd)
66 {
67 	struct xwwdt_device *xdev = watchdog_get_drvdata(wdd);
68 	struct watchdog_device *xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd;
69 	u64 time_out, closed_timeout, open_timeout;
70 	u32 control_status_reg;
71 
72 	/* Calculate timeout count */
73 	time_out = xdev->freq * wdd->timeout;
74 	closed_timeout = (time_out * xdev->close_percent) / 100;
75 	open_timeout = time_out - closed_timeout;
76 	wdd->min_hw_heartbeat_ms = xdev->close_percent * 10 * wdd->timeout;
77 
78 	spin_lock(&xdev->spinlock);
79 
80 	iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET);
81 	iowrite32(~(u32)XWWDT_ESR_WEN_MASK, xdev->base + XWWDT_ESR_OFFSET);
82 	iowrite32((u32)closed_timeout, xdev->base + XWWDT_FWR_OFFSET);
83 	iowrite32((u32)open_timeout, xdev->base + XWWDT_SWR_OFFSET);
84 
85 	/* Enable the window watchdog timer */
86 	control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET);
87 	control_status_reg |= XWWDT_ESR_WEN_MASK;
88 	iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET);
89 
90 	spin_unlock(&xdev->spinlock);
91 
92 	dev_dbg(xilinx_wwdt_wdd->parent, "Watchdog Started!\n");
93 
94 	return 0;
95 }
96 
97 static int xilinx_wwdt_keepalive(struct watchdog_device *wdd)
98 {
99 	struct xwwdt_device *xdev = watchdog_get_drvdata(wdd);
100 	u32 control_status_reg;
101 
102 	spin_lock(&xdev->spinlock);
103 
104 	/* Enable write access control bit for the window watchdog */
105 	iowrite32(XWWDT_MWR_MASK, xdev->base + XWWDT_MWR_OFFSET);
106 
107 	/* Trigger restart kick to watchdog */
108 	control_status_reg = ioread32(xdev->base + XWWDT_ESR_OFFSET);
109 	control_status_reg |= XWWDT_ESR_WSW_MASK;
110 	iowrite32(control_status_reg, xdev->base + XWWDT_ESR_OFFSET);
111 
112 	spin_unlock(&xdev->spinlock);
113 
114 	return 0;
115 }
116 
117 static const struct watchdog_info xilinx_wwdt_ident = {
118 	.options = WDIOF_KEEPALIVEPING |
119 		WDIOF_SETTIMEOUT,
120 	.firmware_version = 1,
121 	.identity = "xlnx_window watchdog",
122 };
123 
124 static const struct watchdog_ops xilinx_wwdt_ops = {
125 	.owner = THIS_MODULE,
126 	.start = xilinx_wwdt_start,
127 	.ping = xilinx_wwdt_keepalive,
128 };
129 
130 static int xwwdt_probe(struct platform_device *pdev)
131 {
132 	struct watchdog_device *xilinx_wwdt_wdd;
133 	struct device *dev = &pdev->dev;
134 	struct xwwdt_device *xdev;
135 	struct clk *clk;
136 	int ret;
137 
138 	xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL);
139 	if (!xdev)
140 		return -ENOMEM;
141 
142 	xilinx_wwdt_wdd = &xdev->xilinx_wwdt_wdd;
143 	xilinx_wwdt_wdd->info = &xilinx_wwdt_ident;
144 	xilinx_wwdt_wdd->ops = &xilinx_wwdt_ops;
145 	xilinx_wwdt_wdd->parent = dev;
146 
147 	xdev->base = devm_platform_ioremap_resource(pdev, 0);
148 	if (IS_ERR(xdev->base))
149 		return PTR_ERR(xdev->base);
150 
151 	clk = devm_clk_get_enabled(dev, NULL);
152 	if (IS_ERR(clk))
153 		return PTR_ERR(clk);
154 
155 	xdev->freq = clk_get_rate(clk);
156 	if (!xdev->freq)
157 		return -EINVAL;
158 
159 	xilinx_wwdt_wdd->min_timeout = XWWDT_MIN_TIMEOUT;
160 	xilinx_wwdt_wdd->timeout = XWWDT_DEFAULT_TIMEOUT;
161 	xilinx_wwdt_wdd->max_hw_heartbeat_ms = 1000 * xilinx_wwdt_wdd->timeout;
162 
163 	if (closed_window_percent == 0 || closed_window_percent >= 100)
164 		xdev->close_percent = XWWDT_CLOSE_WINDOW_PERCENT;
165 	else
166 		xdev->close_percent = closed_window_percent;
167 
168 	watchdog_init_timeout(xilinx_wwdt_wdd, wwdt_timeout, &pdev->dev);
169 	spin_lock_init(&xdev->spinlock);
170 	watchdog_set_drvdata(xilinx_wwdt_wdd, xdev);
171 	watchdog_set_nowayout(xilinx_wwdt_wdd, 1);
172 
173 	ret = devm_watchdog_register_device(dev, xilinx_wwdt_wdd);
174 	if (ret)
175 		return ret;
176 
177 	dev_info(dev, "Xilinx window watchdog Timer with timeout %ds\n",
178 		 xilinx_wwdt_wdd->timeout);
179 
180 	return 0;
181 }
182 
183 static const struct of_device_id xwwdt_of_match[] = {
184 	{ .compatible = "xlnx,versal-wwdt", },
185 	{},
186 };
187 MODULE_DEVICE_TABLE(of, xwwdt_of_match);
188 
189 static struct platform_driver xwwdt_driver = {
190 	.probe = xwwdt_probe,
191 	.driver = {
192 		.name = "Xilinx window watchdog",
193 		.of_match_table = xwwdt_of_match,
194 	},
195 };
196 
197 module_platform_driver(xwwdt_driver);
198 
199 MODULE_AUTHOR("Neeli Srinivas <srinivas.neeli@amd.com>");
200 MODULE_DESCRIPTION("Xilinx window watchdog driver");
201 MODULE_LICENSE("GPL");
202