1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * ST's LPC Watchdog |
4 | * |
5 | * Copyright (C) 2014 STMicroelectronics -- All Rights Reserved |
6 | * |
7 | * Author: David Paris <david.paris@st.com> for STMicroelectronics |
8 | * Lee Jones <lee.jones@linaro.org> for STMicroelectronics |
9 | */ |
10 | |
11 | #include <linux/clk.h> |
12 | #include <linux/init.h> |
13 | #include <linux/io.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/mfd/syscon.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_platform.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/regmap.h> |
21 | #include <linux/watchdog.h> |
22 | |
23 | #include <dt-bindings/mfd/st-lpc.h> |
24 | |
25 | /* Low Power Alarm */ |
26 | #define LPC_LPA_LSB_OFF 0x410 |
27 | #define LPC_LPA_START_OFF 0x418 |
28 | |
29 | /* LPC as WDT */ |
30 | #define LPC_WDT_OFF 0x510 |
31 | |
32 | static struct watchdog_device st_wdog_dev; |
33 | |
34 | struct st_wdog_syscfg { |
35 | unsigned int reset_type_reg; |
36 | unsigned int reset_type_mask; |
37 | unsigned int enable_reg; |
38 | unsigned int enable_mask; |
39 | }; |
40 | |
41 | struct st_wdog { |
42 | void __iomem *base; |
43 | struct device *dev; |
44 | struct regmap *regmap; |
45 | struct st_wdog_syscfg *syscfg; |
46 | struct clk *clk; |
47 | unsigned long clkrate; |
48 | bool warm_reset; |
49 | }; |
50 | |
51 | static struct st_wdog_syscfg stih407_syscfg = { |
52 | .enable_reg = 0x204, |
53 | .enable_mask = BIT(19), |
54 | }; |
55 | |
56 | static const struct of_device_id st_wdog_match[] = { |
57 | { |
58 | .compatible = "st,stih407-lpc" , |
59 | .data = &stih407_syscfg, |
60 | }, |
61 | {}, |
62 | }; |
63 | MODULE_DEVICE_TABLE(of, st_wdog_match); |
64 | |
65 | static void st_wdog_setup(struct st_wdog *st_wdog, bool enable) |
66 | { |
67 | /* Type of watchdog reset - 0: Cold 1: Warm */ |
68 | if (st_wdog->syscfg->reset_type_reg) |
69 | regmap_update_bits(map: st_wdog->regmap, |
70 | reg: st_wdog->syscfg->reset_type_reg, |
71 | mask: st_wdog->syscfg->reset_type_mask, |
72 | val: st_wdog->warm_reset); |
73 | |
74 | /* Mask/unmask watchdog reset */ |
75 | regmap_update_bits(map: st_wdog->regmap, |
76 | reg: st_wdog->syscfg->enable_reg, |
77 | mask: st_wdog->syscfg->enable_mask, |
78 | val: enable ? 0 : st_wdog->syscfg->enable_mask); |
79 | } |
80 | |
81 | static void st_wdog_load_timer(struct st_wdog *st_wdog, unsigned int timeout) |
82 | { |
83 | unsigned long clkrate = st_wdog->clkrate; |
84 | |
85 | writel_relaxed(timeout * clkrate, st_wdog->base + LPC_LPA_LSB_OFF); |
86 | writel_relaxed(1, st_wdog->base + LPC_LPA_START_OFF); |
87 | } |
88 | |
89 | static int st_wdog_start(struct watchdog_device *wdd) |
90 | { |
91 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); |
92 | |
93 | writel_relaxed(1, st_wdog->base + LPC_WDT_OFF); |
94 | |
95 | return 0; |
96 | } |
97 | |
98 | static int st_wdog_stop(struct watchdog_device *wdd) |
99 | { |
100 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); |
101 | |
102 | writel_relaxed(0, st_wdog->base + LPC_WDT_OFF); |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | static int st_wdog_set_timeout(struct watchdog_device *wdd, |
108 | unsigned int timeout) |
109 | { |
110 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); |
111 | |
112 | wdd->timeout = timeout; |
113 | st_wdog_load_timer(st_wdog, timeout); |
114 | |
115 | return 0; |
116 | } |
117 | |
118 | static int st_wdog_keepalive(struct watchdog_device *wdd) |
119 | { |
120 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); |
121 | |
122 | st_wdog_load_timer(st_wdog, timeout: wdd->timeout); |
123 | |
124 | return 0; |
125 | } |
126 | |
127 | static const struct watchdog_info st_wdog_info = { |
128 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
129 | .identity = "ST LPC WDT" , |
130 | }; |
131 | |
132 | static const struct watchdog_ops st_wdog_ops = { |
133 | .owner = THIS_MODULE, |
134 | .start = st_wdog_start, |
135 | .stop = st_wdog_stop, |
136 | .ping = st_wdog_keepalive, |
137 | .set_timeout = st_wdog_set_timeout, |
138 | }; |
139 | |
140 | static struct watchdog_device st_wdog_dev = { |
141 | .info = &st_wdog_info, |
142 | .ops = &st_wdog_ops, |
143 | }; |
144 | |
145 | static void st_clk_disable_unprepare(void *data) |
146 | { |
147 | clk_disable_unprepare(clk: data); |
148 | } |
149 | |
150 | static int st_wdog_probe(struct platform_device *pdev) |
151 | { |
152 | struct device *dev = &pdev->dev; |
153 | const struct of_device_id *match; |
154 | struct device_node *np = dev->of_node; |
155 | struct st_wdog *st_wdog; |
156 | struct regmap *regmap; |
157 | struct clk *clk; |
158 | void __iomem *base; |
159 | uint32_t mode; |
160 | int ret; |
161 | |
162 | ret = of_property_read_u32(np, propname: "st,lpc-mode" , out_value: &mode); |
163 | if (ret) { |
164 | dev_err(dev, "An LPC mode must be provided\n" ); |
165 | return -EINVAL; |
166 | } |
167 | |
168 | /* LPC can either run as a Clocksource or in RTC or WDT mode */ |
169 | if (mode != ST_LPC_MODE_WDT) |
170 | return -ENODEV; |
171 | |
172 | st_wdog = devm_kzalloc(dev, size: sizeof(*st_wdog), GFP_KERNEL); |
173 | if (!st_wdog) |
174 | return -ENOMEM; |
175 | |
176 | match = of_match_device(matches: st_wdog_match, dev); |
177 | if (!match) { |
178 | dev_err(dev, "Couldn't match device\n" ); |
179 | return -ENODEV; |
180 | } |
181 | st_wdog->syscfg = (struct st_wdog_syscfg *)match->data; |
182 | |
183 | base = devm_platform_ioremap_resource(pdev, index: 0); |
184 | if (IS_ERR(ptr: base)) |
185 | return PTR_ERR(ptr: base); |
186 | |
187 | regmap = syscon_regmap_lookup_by_phandle(np, property: "st,syscfg" ); |
188 | if (IS_ERR(ptr: regmap)) { |
189 | dev_err(dev, "No syscfg phandle specified\n" ); |
190 | return PTR_ERR(ptr: regmap); |
191 | } |
192 | |
193 | clk = devm_clk_get(dev, NULL); |
194 | if (IS_ERR(ptr: clk)) { |
195 | dev_err(dev, "Unable to request clock\n" ); |
196 | return PTR_ERR(ptr: clk); |
197 | } |
198 | |
199 | st_wdog->dev = dev; |
200 | st_wdog->base = base; |
201 | st_wdog->clk = clk; |
202 | st_wdog->regmap = regmap; |
203 | st_wdog->warm_reset = of_property_read_bool(np, propname: "st,warm_reset" ); |
204 | st_wdog->clkrate = clk_get_rate(clk: st_wdog->clk); |
205 | |
206 | if (!st_wdog->clkrate) { |
207 | dev_err(dev, "Unable to fetch clock rate\n" ); |
208 | return -EINVAL; |
209 | } |
210 | st_wdog_dev.max_timeout = 0xFFFFFFFF / st_wdog->clkrate; |
211 | st_wdog_dev.parent = dev; |
212 | |
213 | ret = clk_prepare_enable(clk); |
214 | if (ret) { |
215 | dev_err(dev, "Unable to enable clock\n" ); |
216 | return ret; |
217 | } |
218 | ret = devm_add_action_or_reset(dev, st_clk_disable_unprepare, clk); |
219 | if (ret) |
220 | return ret; |
221 | |
222 | watchdog_set_drvdata(wdd: &st_wdog_dev, data: st_wdog); |
223 | watchdog_set_nowayout(wdd: &st_wdog_dev, WATCHDOG_NOWAYOUT); |
224 | |
225 | /* Init Watchdog timeout with value in DT */ |
226 | ret = watchdog_init_timeout(wdd: &st_wdog_dev, timeout_parm: 0, dev); |
227 | if (ret) |
228 | return ret; |
229 | |
230 | ret = devm_watchdog_register_device(dev, &st_wdog_dev); |
231 | if (ret) |
232 | return ret; |
233 | |
234 | st_wdog_setup(st_wdog, enable: true); |
235 | |
236 | dev_info(dev, "LPC Watchdog driver registered, reset type is %s" , |
237 | st_wdog->warm_reset ? "warm" : "cold" ); |
238 | |
239 | return ret; |
240 | } |
241 | |
242 | static void st_wdog_remove(struct platform_device *pdev) |
243 | { |
244 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd: &st_wdog_dev); |
245 | |
246 | st_wdog_setup(st_wdog, enable: false); |
247 | } |
248 | |
249 | static int st_wdog_suspend(struct device *dev) |
250 | { |
251 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd: &st_wdog_dev); |
252 | |
253 | if (watchdog_active(wdd: &st_wdog_dev)) |
254 | st_wdog_stop(wdd: &st_wdog_dev); |
255 | |
256 | st_wdog_setup(st_wdog, enable: false); |
257 | |
258 | clk_disable(clk: st_wdog->clk); |
259 | |
260 | return 0; |
261 | } |
262 | |
263 | static int st_wdog_resume(struct device *dev) |
264 | { |
265 | struct st_wdog *st_wdog = watchdog_get_drvdata(wdd: &st_wdog_dev); |
266 | int ret; |
267 | |
268 | ret = clk_enable(clk: st_wdog->clk); |
269 | if (ret) { |
270 | dev_err(dev, "Unable to re-enable clock\n" ); |
271 | watchdog_unregister_device(&st_wdog_dev); |
272 | clk_unprepare(clk: st_wdog->clk); |
273 | return ret; |
274 | } |
275 | |
276 | st_wdog_setup(st_wdog, enable: true); |
277 | |
278 | if (watchdog_active(wdd: &st_wdog_dev)) { |
279 | st_wdog_load_timer(st_wdog, timeout: st_wdog_dev.timeout); |
280 | st_wdog_start(wdd: &st_wdog_dev); |
281 | } |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | static DEFINE_SIMPLE_DEV_PM_OPS(st_wdog_pm_ops, |
287 | st_wdog_suspend, st_wdog_resume); |
288 | |
289 | static struct platform_driver st_wdog_driver = { |
290 | .driver = { |
291 | .name = "st-lpc-wdt" , |
292 | .pm = pm_sleep_ptr(&st_wdog_pm_ops), |
293 | .of_match_table = st_wdog_match, |
294 | }, |
295 | .probe = st_wdog_probe, |
296 | .remove_new = st_wdog_remove, |
297 | }; |
298 | module_platform_driver(st_wdog_driver); |
299 | |
300 | MODULE_AUTHOR("David Paris <david.paris@st.com>" ); |
301 | MODULE_DESCRIPTION("ST LPC Watchdog Driver" ); |
302 | MODULE_LICENSE("GPL" ); |
303 | |