Commit | Line | Data |
---|---|---|
12984cea SN |
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> | |
98334dc2 | 12 | #include <linux/math64.h> |
cc85f87a | 13 | #include <linux/mod_devicetable.h> |
12984cea | 14 | #include <linux/module.h> |
cc85f87a | 15 | #include <linux/platform_device.h> |
12984cea SN |
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; | |
98334dc2 | 75 | closed_timeout = div_u64(time_out * xdev->close_percent, 100); |
12984cea SN |
76 | open_timeout = time_out - closed_timeout; |
77 | wdd->min_hw_heartbeat_ms = xdev->close_percent * 10 * wdd->timeout; | |
78 | ||
79 | spin_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(&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(&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(&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, 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, 0); | |
149 | if (IS_ERR(xdev->base)) | |
150 | return PTR_ERR(xdev->base); | |
151 | ||
152 | clk = devm_clk_get_enabled(dev, NULL); | |
153 | if (IS_ERR(clk)) | |
154 | return PTR_ERR(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(xilinx_wwdt_wdd, wwdt_timeout, &pdev->dev); | |
170 | spin_lock_init(&xdev->spinlock); | |
171 | watchdog_set_drvdata(xilinx_wwdt_wdd, xdev); | |
172 | watchdog_set_nowayout(xilinx_wwdt_wdd, 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"); |