Commit | Line | Data |
---|---|---|
2e62c498 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
f27925a6 LJ |
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 | |
f27925a6 LJ |
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 | ||
f27925a6 LJ |
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 | }, | |
f27925a6 LJ |
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(st_wdog->regmap, | |
70 | st_wdog->syscfg->reset_type_reg, | |
71 | st_wdog->syscfg->reset_type_mask, | |
72 | st_wdog->warm_reset); | |
73 | ||
74 | /* Mask/unmask watchdog reset */ | |
75 | regmap_update_bits(st_wdog->regmap, | |
76 | st_wdog->syscfg->enable_reg, | |
77 | st_wdog->syscfg->enable_mask, | |
78 | 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, 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 | ||
cfe9ee3a GR |
145 | static void st_clk_disable_unprepare(void *data) |
146 | { | |
147 | clk_disable_unprepare(data); | |
148 | } | |
149 | ||
f27925a6 LJ |
150 | static int st_wdog_probe(struct platform_device *pdev) |
151 | { | |
cfe9ee3a | 152 | struct device *dev = &pdev->dev; |
f27925a6 | 153 | const struct of_device_id *match; |
cfe9ee3a | 154 | struct device_node *np = dev->of_node; |
f27925a6 LJ |
155 | struct st_wdog *st_wdog; |
156 | struct regmap *regmap; | |
f27925a6 LJ |
157 | struct clk *clk; |
158 | void __iomem *base; | |
159 | uint32_t mode; | |
160 | int ret; | |
161 | ||
162 | ret = of_property_read_u32(np, "st,lpc-mode", &mode); | |
163 | if (ret) { | |
cfe9ee3a | 164 | dev_err(dev, "An LPC mode must be provided\n"); |
f27925a6 LJ |
165 | return -EINVAL; |
166 | } | |
167 | ||
79cb0976 | 168 | /* LPC can either run as a Clocksource or in RTC or WDT mode */ |
f27925a6 LJ |
169 | if (mode != ST_LPC_MODE_WDT) |
170 | return -ENODEV; | |
171 | ||
cfe9ee3a | 172 | st_wdog = devm_kzalloc(dev, sizeof(*st_wdog), GFP_KERNEL); |
f27925a6 LJ |
173 | if (!st_wdog) |
174 | return -ENOMEM; | |
175 | ||
cfe9ee3a | 176 | match = of_match_device(st_wdog_match, dev); |
f27925a6 | 177 | if (!match) { |
cfe9ee3a | 178 | dev_err(dev, "Couldn't match device\n"); |
f27925a6 LJ |
179 | return -ENODEV; |
180 | } | |
181 | st_wdog->syscfg = (struct st_wdog_syscfg *)match->data; | |
182 | ||
0f0a6a28 | 183 | base = devm_platform_ioremap_resource(pdev, 0); |
f27925a6 LJ |
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)) { | |
cfe9ee3a | 189 | dev_err(dev, "No syscfg phandle specified\n"); |
f27925a6 LJ |
190 | return PTR_ERR(regmap); |
191 | } | |
192 | ||
cfe9ee3a | 193 | clk = devm_clk_get(dev, NULL); |
f27925a6 | 194 | if (IS_ERR(clk)) { |
cfe9ee3a | 195 | dev_err(dev, "Unable to request clock\n"); |
f27925a6 LJ |
196 | return PTR_ERR(clk); |
197 | } | |
198 | ||
cfe9ee3a | 199 | st_wdog->dev = dev; |
f27925a6 LJ |
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) { | |
cfe9ee3a | 207 | dev_err(dev, "Unable to fetch clock rate\n"); |
f27925a6 LJ |
208 | return -EINVAL; |
209 | } | |
210 | st_wdog_dev.max_timeout = 0xFFFFFFFF / st_wdog->clkrate; | |
cfe9ee3a | 211 | st_wdog_dev.parent = dev; |
f27925a6 LJ |
212 | |
213 | ret = clk_prepare_enable(clk); | |
214 | if (ret) { | |
cfe9ee3a | 215 | dev_err(dev, "Unable to enable clock\n"); |
f27925a6 LJ |
216 | return ret; |
217 | } | |
cfe9ee3a GR |
218 | ret = devm_add_action_or_reset(dev, st_clk_disable_unprepare, clk); |
219 | if (ret) | |
220 | return ret; | |
f27925a6 LJ |
221 | |
222 | watchdog_set_drvdata(&st_wdog_dev, st_wdog); | |
223 | watchdog_set_nowayout(&st_wdog_dev, WATCHDOG_NOWAYOUT); | |
224 | ||
225 | /* Init Watchdog timeout with value in DT */ | |
cfe9ee3a | 226 | ret = watchdog_init_timeout(&st_wdog_dev, 0, dev); |
b4214185 | 227 | if (ret) |
f27925a6 | 228 | return ret; |
f27925a6 | 229 | |
cfe9ee3a | 230 | ret = devm_watchdog_register_device(dev, &st_wdog_dev); |
7283b217 | 231 | if (ret) |
f27925a6 | 232 | return ret; |
f27925a6 LJ |
233 | |
234 | st_wdog_setup(st_wdog, true); | |
235 | ||
cfe9ee3a | 236 | dev_info(dev, "LPC Watchdog driver registered, reset type is %s", |
f27925a6 LJ |
237 | st_wdog->warm_reset ? "warm" : "cold"); |
238 | ||
239 | return ret; | |
240 | } | |
241 | ||
e7a84d45 | 242 | static void st_wdog_remove(struct platform_device *pdev) |
f27925a6 LJ |
243 | { |
244 | struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); | |
245 | ||
246 | st_wdog_setup(st_wdog, false); | |
f27925a6 LJ |
247 | } |
248 | ||
f27925a6 LJ |
249 | static int st_wdog_suspend(struct device *dev) |
250 | { | |
251 | struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); | |
252 | ||
253 | if (watchdog_active(&st_wdog_dev)) | |
254 | st_wdog_stop(&st_wdog_dev); | |
255 | ||
256 | st_wdog_setup(st_wdog, false); | |
257 | ||
258 | clk_disable(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(&st_wdog_dev); | |
266 | int ret; | |
267 | ||
268 | ret = clk_enable(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(st_wdog->clk); | |
273 | return ret; | |
274 | } | |
275 | ||
276 | st_wdog_setup(st_wdog, true); | |
277 | ||
278 | if (watchdog_active(&st_wdog_dev)) { | |
279 | st_wdog_load_timer(st_wdog, st_wdog_dev.timeout); | |
280 | st_wdog_start(&st_wdog_dev); | |
281 | } | |
282 | ||
283 | return 0; | |
284 | } | |
f27925a6 | 285 | |
29958ab7 PC |
286 | static DEFINE_SIMPLE_DEV_PM_OPS(st_wdog_pm_ops, |
287 | st_wdog_suspend, st_wdog_resume); | |
f27925a6 LJ |
288 | |
289 | static struct platform_driver st_wdog_driver = { | |
290 | .driver = { | |
291 | .name = "st-lpc-wdt", | |
29958ab7 | 292 | .pm = pm_sleep_ptr(&st_wdog_pm_ops), |
f27925a6 LJ |
293 | .of_match_table = st_wdog_match, |
294 | }, | |
295 | .probe = st_wdog_probe, | |
e7a84d45 | 296 | .remove_new = st_wdog_remove, |
f27925a6 LJ |
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"); |