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