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