Commit | Line | Data |
---|---|---|
2cbc5cd0 BD |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Renesas RZ/G2L WDT Watchdog Driver | |
4 | * | |
5 | * Copyright (C) 2021 Renesas Electronics Corporation | |
6 | */ | |
7 | #include <linux/bitops.h> | |
8 | #include <linux/clk.h> | |
9 | #include <linux/delay.h> | |
10 | #include <linux/io.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/pm_runtime.h> | |
16 | #include <linux/reset.h> | |
17 | #include <linux/units.h> | |
18 | #include <linux/watchdog.h> | |
19 | ||
20 | #define WDTCNT 0x00 | |
21 | #define WDTSET 0x04 | |
22 | #define WDTTIM 0x08 | |
23 | #define WDTINT 0x0C | |
f43e6ddb BD |
24 | #define PECR 0x10 |
25 | #define PEEN 0x14 | |
2cbc5cd0 BD |
26 | #define WDTCNT_WDTEN BIT(0) |
27 | #define WDTINT_INTDISP BIT(0) | |
f43e6ddb | 28 | #define PEEN_FORCE BIT(0) |
2cbc5cd0 BD |
29 | |
30 | #define WDT_DEFAULT_TIMEOUT 60U | |
31 | ||
32 | /* Setting period time register only 12 bit set in WDTSET[31:20] */ | |
33 | #define WDTSET_COUNTER_MASK (0xFFF00000) | |
34 | #define WDTSET_COUNTER_VAL(f) ((f) << 20) | |
35 | ||
36 | #define F2CYCLE_NSEC(f) (1000000000 / (f)) | |
37 | ||
38 | static bool nowayout = WATCHDOG_NOWAYOUT; | |
39 | module_param(nowayout, bool, 0); | |
40 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" | |
41 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
42 | ||
43 | struct rzg2l_wdt_priv { | |
44 | void __iomem *base; | |
45 | struct watchdog_device wdev; | |
46 | struct reset_control *rstc; | |
47 | unsigned long osc_clk_rate; | |
48 | unsigned long delay; | |
e4cf8959 BD |
49 | struct clk *pclk; |
50 | struct clk *osc_clk; | |
2cbc5cd0 BD |
51 | }; |
52 | ||
53 | static void rzg2l_wdt_wait_delay(struct rzg2l_wdt_priv *priv) | |
54 | { | |
55 | /* delay timer when change the setting register */ | |
56 | ndelay(priv->delay); | |
57 | } | |
58 | ||
59 | static u32 rzg2l_wdt_get_cycle_usec(unsigned long cycle, u32 wdttime) | |
60 | { | |
ea2949df | 61 | u64 timer_cycle_us = 1024 * 1024ULL * (wdttime + 1) * MICRO; |
2cbc5cd0 BD |
62 | |
63 | return div64_ul(timer_cycle_us, cycle); | |
64 | } | |
65 | ||
66 | static void rzg2l_wdt_write(struct rzg2l_wdt_priv *priv, u32 val, unsigned int reg) | |
67 | { | |
68 | if (reg == WDTSET) | |
69 | val &= WDTSET_COUNTER_MASK; | |
70 | ||
71 | writel_relaxed(val, priv->base + reg); | |
72 | /* Registers other than the WDTINT is always synchronized with WDT_CLK */ | |
73 | if (reg != WDTINT) | |
74 | rzg2l_wdt_wait_delay(priv); | |
75 | } | |
76 | ||
77 | static void rzg2l_wdt_init_timeout(struct watchdog_device *wdev) | |
78 | { | |
79 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
80 | u32 time_out; | |
81 | ||
82 | /* Clear Lapsed Time Register and clear Interrupt */ | |
83 | rzg2l_wdt_write(priv, WDTINT_INTDISP, WDTINT); | |
84 | /* 2 consecutive overflow cycle needed to trigger reset */ | |
85 | time_out = (wdev->timeout * (MICRO / 2)) / | |
86 | rzg2l_wdt_get_cycle_usec(priv->osc_clk_rate, 0); | |
87 | rzg2l_wdt_write(priv, WDTSET_COUNTER_VAL(time_out), WDTSET); | |
88 | } | |
89 | ||
90 | static int rzg2l_wdt_start(struct watchdog_device *wdev) | |
91 | { | |
92 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
93 | ||
2cbc5cd0 BD |
94 | pm_runtime_get_sync(wdev->parent); |
95 | ||
96 | /* Initialize time out */ | |
97 | rzg2l_wdt_init_timeout(wdev); | |
98 | ||
99 | /* Initialize watchdog counter register */ | |
100 | rzg2l_wdt_write(priv, 0, WDTTIM); | |
101 | ||
102 | /* Enable watchdog timer*/ | |
103 | rzg2l_wdt_write(priv, WDTCNT_WDTEN, WDTCNT); | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int rzg2l_wdt_stop(struct watchdog_device *wdev) | |
109 | { | |
110 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
111 | ||
112 | pm_runtime_put(wdev->parent); | |
33d04d0f | 113 | reset_control_reset(priv->rstc); |
2cbc5cd0 BD |
114 | |
115 | return 0; | |
116 | } | |
117 | ||
4055ee81 BD |
118 | static int rzg2l_wdt_set_timeout(struct watchdog_device *wdev, unsigned int timeout) |
119 | { | |
120 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
121 | ||
122 | wdev->timeout = timeout; | |
123 | ||
124 | /* | |
125 | * If the watchdog is active, reset the module for updating the WDTSET | |
126 | * register so that it is updated with new timeout values. | |
127 | */ | |
128 | if (watchdog_active(wdev)) { | |
129 | pm_runtime_put(wdev->parent); | |
130 | reset_control_reset(priv->rstc); | |
131 | rzg2l_wdt_start(wdev); | |
132 | } | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
2cbc5cd0 BD |
137 | static int rzg2l_wdt_restart(struct watchdog_device *wdev, |
138 | unsigned long action, void *data) | |
139 | { | |
140 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
141 | ||
e4cf8959 BD |
142 | clk_prepare_enable(priv->pclk); |
143 | clk_prepare_enable(priv->osc_clk); | |
2cbc5cd0 | 144 | |
f43e6ddb BD |
145 | /* Generate Reset (WDTRSTB) Signal on parity error */ |
146 | rzg2l_wdt_write(priv, 0, PECR); | |
2cbc5cd0 | 147 | |
f43e6ddb BD |
148 | /* Force parity error */ |
149 | rzg2l_wdt_write(priv, PEEN_FORCE, PEEN); | |
2cbc5cd0 BD |
150 | |
151 | return 0; | |
152 | } | |
153 | ||
154 | static const struct watchdog_info rzg2l_wdt_ident = { | |
155 | .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, | |
156 | .identity = "Renesas RZ/G2L WDT Watchdog", | |
157 | }; | |
158 | ||
159 | static int rzg2l_wdt_ping(struct watchdog_device *wdev) | |
160 | { | |
161 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
162 | ||
163 | rzg2l_wdt_write(priv, WDTINT_INTDISP, WDTINT); | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
168 | static const struct watchdog_ops rzg2l_wdt_ops = { | |
169 | .owner = THIS_MODULE, | |
170 | .start = rzg2l_wdt_start, | |
171 | .stop = rzg2l_wdt_stop, | |
172 | .ping = rzg2l_wdt_ping, | |
4055ee81 | 173 | .set_timeout = rzg2l_wdt_set_timeout, |
2cbc5cd0 BD |
174 | .restart = rzg2l_wdt_restart, |
175 | }; | |
176 | ||
95abafe7 | 177 | static void rzg2l_wdt_reset_assert_pm_disable(void *data) |
2cbc5cd0 BD |
178 | { |
179 | struct watchdog_device *wdev = data; | |
180 | struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
181 | ||
2cbc5cd0 BD |
182 | pm_runtime_disable(wdev->parent); |
183 | reset_control_assert(priv->rstc); | |
184 | } | |
185 | ||
186 | static int rzg2l_wdt_probe(struct platform_device *pdev) | |
187 | { | |
188 | struct device *dev = &pdev->dev; | |
189 | struct rzg2l_wdt_priv *priv; | |
190 | unsigned long pclk_rate; | |
2cbc5cd0 BD |
191 | int ret; |
192 | ||
193 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
194 | if (!priv) | |
195 | return -ENOMEM; | |
196 | ||
197 | priv->base = devm_platform_ioremap_resource(pdev, 0); | |
198 | if (IS_ERR(priv->base)) | |
199 | return PTR_ERR(priv->base); | |
200 | ||
201 | /* Get watchdog main clock */ | |
e4cf8959 BD |
202 | priv->osc_clk = devm_clk_get(&pdev->dev, "oscclk"); |
203 | if (IS_ERR(priv->osc_clk)) | |
204 | return dev_err_probe(&pdev->dev, PTR_ERR(priv->osc_clk), "no oscclk"); | |
2cbc5cd0 | 205 | |
e4cf8959 | 206 | priv->osc_clk_rate = clk_get_rate(priv->osc_clk); |
2cbc5cd0 BD |
207 | if (!priv->osc_clk_rate) |
208 | return dev_err_probe(&pdev->dev, -EINVAL, "oscclk rate is 0"); | |
209 | ||
210 | /* Get Peripheral clock */ | |
e4cf8959 BD |
211 | priv->pclk = devm_clk_get(&pdev->dev, "pclk"); |
212 | if (IS_ERR(priv->pclk)) | |
213 | return dev_err_probe(&pdev->dev, PTR_ERR(priv->pclk), "no pclk"); | |
2cbc5cd0 | 214 | |
e4cf8959 | 215 | pclk_rate = clk_get_rate(priv->pclk); |
2cbc5cd0 BD |
216 | if (!pclk_rate) |
217 | return dev_err_probe(&pdev->dev, -EINVAL, "pclk rate is 0"); | |
218 | ||
219 | priv->delay = F2CYCLE_NSEC(priv->osc_clk_rate) * 6 + F2CYCLE_NSEC(pclk_rate) * 9; | |
220 | ||
221 | priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); | |
222 | if (IS_ERR(priv->rstc)) | |
223 | return dev_err_probe(&pdev->dev, PTR_ERR(priv->rstc), | |
224 | "failed to get cpg reset"); | |
225 | ||
baf1aace BD |
226 | ret = reset_control_deassert(priv->rstc); |
227 | if (ret) | |
228 | return dev_err_probe(dev, ret, "failed to deassert"); | |
229 | ||
2cbc5cd0 | 230 | pm_runtime_enable(&pdev->dev); |
2cbc5cd0 BD |
231 | |
232 | priv->wdev.info = &rzg2l_wdt_ident; | |
233 | priv->wdev.ops = &rzg2l_wdt_ops; | |
234 | priv->wdev.parent = dev; | |
235 | priv->wdev.min_timeout = 1; | |
236 | priv->wdev.max_timeout = rzg2l_wdt_get_cycle_usec(priv->osc_clk_rate, 0xfff) / | |
237 | USEC_PER_SEC; | |
238 | priv->wdev.timeout = WDT_DEFAULT_TIMEOUT; | |
239 | ||
240 | watchdog_set_drvdata(&priv->wdev, priv); | |
241 | ret = devm_add_action_or_reset(&pdev->dev, | |
95abafe7 | 242 | rzg2l_wdt_reset_assert_pm_disable, |
2cbc5cd0 BD |
243 | &priv->wdev); |
244 | if (ret < 0) | |
245 | return ret; | |
246 | ||
247 | watchdog_set_nowayout(&priv->wdev, nowayout); | |
248 | watchdog_stop_on_unregister(&priv->wdev); | |
249 | ||
250 | ret = watchdog_init_timeout(&priv->wdev, 0, dev); | |
251 | if (ret) | |
252 | dev_warn(dev, "Specified timeout invalid, using default"); | |
253 | ||
254 | return devm_watchdog_register_device(&pdev->dev, &priv->wdev); | |
2cbc5cd0 BD |
255 | } |
256 | ||
257 | static const struct of_device_id rzg2l_wdt_ids[] = { | |
258 | { .compatible = "renesas,rzg2l-wdt", }, | |
259 | { /* sentinel */ } | |
260 | }; | |
261 | MODULE_DEVICE_TABLE(of, rzg2l_wdt_ids); | |
262 | ||
263 | static struct platform_driver rzg2l_wdt_driver = { | |
264 | .driver = { | |
265 | .name = "rzg2l_wdt", | |
266 | .of_match_table = rzg2l_wdt_ids, | |
267 | }, | |
268 | .probe = rzg2l_wdt_probe, | |
269 | }; | |
270 | module_platform_driver(rzg2l_wdt_driver); | |
271 | ||
272 | MODULE_DESCRIPTION("Renesas RZ/G2L WDT Watchdog Driver"); | |
273 | MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>"); | |
274 | MODULE_LICENSE("GPL v2"); |