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