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