Commit | Line | Data |
---|---|---|
2e62c498 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
502a0106 MB |
2 | /* |
3 | * Watchdog driver for the wm831x PMICs | |
4 | * | |
5 | * Copyright (C) 2009 Wolfson Microelectronics | |
502a0106 MB |
6 | */ |
7 | ||
8 | #include <linux/module.h> | |
9 | #include <linux/moduleparam.h> | |
10 | #include <linux/types.h> | |
11 | #include <linux/kernel.h> | |
00411ee9 | 12 | #include <linux/slab.h> |
502a0106 MB |
13 | #include <linux/platform_device.h> |
14 | #include <linux/watchdog.h> | |
15 | #include <linux/uaccess.h> | |
16 | #include <linux/gpio.h> | |
17 | ||
18 | #include <linux/mfd/wm831x/core.h> | |
19 | #include <linux/mfd/wm831x/pdata.h> | |
20 | #include <linux/mfd/wm831x/watchdog.h> | |
21 | ||
86a1e189 WVS |
22 | static bool nowayout = WATCHDOG_NOWAYOUT; |
23 | module_param(nowayout, bool, 0); | |
502a0106 MB |
24 | MODULE_PARM_DESC(nowayout, |
25 | "Watchdog cannot be stopped once started (default=" | |
26 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
27 | ||
00411ee9 MB |
28 | struct wm831x_wdt_drvdata { |
29 | struct watchdog_device wdt; | |
30 | struct wm831x *wm831x; | |
31 | struct mutex lock; | |
32 | int update_gpio; | |
33 | int update_state; | |
34 | }; | |
502a0106 MB |
35 | |
36 | /* We can't use the sub-second values here but they're included | |
37 | * for completeness. */ | |
38 | static struct { | |
00411ee9 MB |
39 | unsigned int time; /* Seconds */ |
40 | u16 val; /* WDOG_TO value */ | |
502a0106 MB |
41 | } wm831x_wdt_cfgs[] = { |
42 | { 1, 2 }, | |
43 | { 2, 3 }, | |
44 | { 4, 4 }, | |
45 | { 8, 5 }, | |
46 | { 16, 6 }, | |
47 | { 32, 7 }, | |
48 | { 33, 7 }, /* Actually 32.768s so include both, others round down */ | |
49 | }; | |
50 | ||
00411ee9 | 51 | static int wm831x_wdt_start(struct watchdog_device *wdt_dev) |
502a0106 | 52 | { |
00411ee9 MB |
53 | struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); |
54 | struct wm831x *wm831x = driver_data->wm831x; | |
502a0106 MB |
55 | int ret; |
56 | ||
00411ee9 | 57 | mutex_lock(&driver_data->lock); |
502a0106 MB |
58 | |
59 | ret = wm831x_reg_unlock(wm831x); | |
60 | if (ret == 0) { | |
61 | ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
62 | WM831X_WDOG_ENA, WM831X_WDOG_ENA); | |
63 | wm831x_reg_lock(wm831x); | |
64 | } else { | |
65 | dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
66 | ret); | |
67 | } | |
68 | ||
00411ee9 | 69 | mutex_unlock(&driver_data->lock); |
502a0106 MB |
70 | |
71 | return ret; | |
72 | } | |
73 | ||
00411ee9 | 74 | static int wm831x_wdt_stop(struct watchdog_device *wdt_dev) |
502a0106 | 75 | { |
00411ee9 MB |
76 | struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); |
77 | struct wm831x *wm831x = driver_data->wm831x; | |
502a0106 MB |
78 | int ret; |
79 | ||
00411ee9 | 80 | mutex_lock(&driver_data->lock); |
502a0106 MB |
81 | |
82 | ret = wm831x_reg_unlock(wm831x); | |
83 | if (ret == 0) { | |
84 | ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
85 | WM831X_WDOG_ENA, 0); | |
86 | wm831x_reg_lock(wm831x); | |
87 | } else { | |
88 | dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
89 | ret); | |
90 | } | |
91 | ||
00411ee9 | 92 | mutex_unlock(&driver_data->lock); |
502a0106 MB |
93 | |
94 | return ret; | |
95 | } | |
96 | ||
00411ee9 | 97 | static int wm831x_wdt_ping(struct watchdog_device *wdt_dev) |
502a0106 | 98 | { |
00411ee9 MB |
99 | struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); |
100 | struct wm831x *wm831x = driver_data->wm831x; | |
502a0106 MB |
101 | int ret; |
102 | u16 reg; | |
103 | ||
00411ee9 | 104 | mutex_lock(&driver_data->lock); |
502a0106 | 105 | |
00411ee9 MB |
106 | if (driver_data->update_gpio) { |
107 | gpio_set_value_cansleep(driver_data->update_gpio, | |
108 | driver_data->update_state); | |
109 | driver_data->update_state = !driver_data->update_state; | |
502a0106 MB |
110 | ret = 0; |
111 | goto out; | |
112 | } | |
113 | ||
502a0106 MB |
114 | reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); |
115 | ||
116 | if (!(reg & WM831X_WDOG_RST_SRC)) { | |
117 | dev_err(wm831x->dev, "Hardware watchdog update unsupported\n"); | |
118 | ret = -EINVAL; | |
119 | goto out; | |
120 | } | |
121 | ||
122 | reg |= WM831X_WDOG_RESET; | |
123 | ||
124 | ret = wm831x_reg_unlock(wm831x); | |
125 | if (ret == 0) { | |
126 | ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); | |
127 | wm831x_reg_lock(wm831x); | |
128 | } else { | |
129 | dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
130 | ret); | |
131 | } | |
132 | ||
133 | out: | |
00411ee9 | 134 | mutex_unlock(&driver_data->lock); |
502a0106 MB |
135 | |
136 | return ret; | |
137 | } | |
138 | ||
00411ee9 MB |
139 | static int wm831x_wdt_set_timeout(struct watchdog_device *wdt_dev, |
140 | unsigned int timeout) | |
502a0106 | 141 | { |
00411ee9 MB |
142 | struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev); |
143 | struct wm831x *wm831x = driver_data->wm831x; | |
144 | int ret, i; | |
502a0106 | 145 | |
00411ee9 MB |
146 | for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) |
147 | if (wm831x_wdt_cfgs[i].time == timeout) | |
148 | break; | |
149 | if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) | |
f9849100 | 150 | return -EINVAL; |
502a0106 | 151 | |
00411ee9 MB |
152 | ret = wm831x_reg_unlock(wm831x); |
153 | if (ret == 0) { | |
154 | ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG, | |
155 | WM831X_WDOG_TO_MASK, | |
156 | wm831x_wdt_cfgs[i].val); | |
157 | wm831x_reg_lock(wm831x); | |
158 | } else { | |
159 | dev_err(wm831x->dev, "Failed to unlock security key: %d\n", | |
160 | ret); | |
502a0106 MB |
161 | } |
162 | ||
0197c1c4 WVS |
163 | wdt_dev->timeout = timeout; |
164 | ||
00411ee9 | 165 | return ret; |
502a0106 MB |
166 | } |
167 | ||
00411ee9 | 168 | static const struct watchdog_info wm831x_wdt_info = { |
502a0106 MB |
169 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, |
170 | .identity = "WM831x Watchdog", | |
171 | }; | |
172 | ||
00411ee9 | 173 | static const struct watchdog_ops wm831x_wdt_ops = { |
502a0106 | 174 | .owner = THIS_MODULE, |
00411ee9 MB |
175 | .start = wm831x_wdt_start, |
176 | .stop = wm831x_wdt_stop, | |
177 | .ping = wm831x_wdt_ping, | |
178 | .set_timeout = wm831x_wdt_set_timeout, | |
502a0106 MB |
179 | }; |
180 | ||
2d991a16 | 181 | static int wm831x_wdt_probe(struct platform_device *pdev) |
502a0106 | 182 | { |
00411ee9 | 183 | struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); |
bc8fdfbe | 184 | struct wm831x_pdata *chip_pdata = dev_get_platdata(pdev->dev.parent); |
502a0106 | 185 | struct wm831x_watchdog_pdata *pdata; |
00411ee9 MB |
186 | struct wm831x_wdt_drvdata *driver_data; |
187 | struct watchdog_device *wm831x_wdt; | |
188 | int reg, ret, i; | |
502a0106 MB |
189 | |
190 | ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG); | |
191 | if (ret < 0) { | |
192 | dev_err(wm831x->dev, "Failed to read watchdog status: %d\n", | |
193 | ret); | |
30cba9a1 | 194 | return ret; |
502a0106 MB |
195 | } |
196 | reg = ret; | |
197 | ||
198 | if (reg & WM831X_WDOG_DEBUG) | |
199 | dev_warn(wm831x->dev, "Watchdog is paused\n"); | |
200 | ||
7b9bb6d8 MB |
201 | driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), |
202 | GFP_KERNEL); | |
30cba9a1 GR |
203 | if (!driver_data) |
204 | return -ENOMEM; | |
00411ee9 MB |
205 | |
206 | mutex_init(&driver_data->lock); | |
207 | driver_data->wm831x = wm831x; | |
208 | ||
209 | wm831x_wdt = &driver_data->wdt; | |
210 | ||
211 | wm831x_wdt->info = &wm831x_wdt_info; | |
212 | wm831x_wdt->ops = &wm831x_wdt_ops; | |
6551881c | 213 | wm831x_wdt->parent = &pdev->dev; |
ff0b3cd4 | 214 | watchdog_set_nowayout(wm831x_wdt, nowayout); |
00411ee9 MB |
215 | watchdog_set_drvdata(wm831x_wdt, driver_data); |
216 | ||
00411ee9 MB |
217 | reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG); |
218 | reg &= WM831X_WDOG_TO_MASK; | |
219 | for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++) | |
220 | if (wm831x_wdt_cfgs[i].val == reg) | |
221 | break; | |
222 | if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) | |
223 | dev_warn(wm831x->dev, | |
224 | "Unknown watchdog timeout: %x\n", reg); | |
225 | else | |
226 | wm831x_wdt->timeout = wm831x_wdt_cfgs[i].time; | |
227 | ||
502a0106 | 228 | /* Apply any configuration */ |
bc8fdfbe | 229 | if (chip_pdata) |
502a0106 | 230 | pdata = chip_pdata->watchdog; |
bc8fdfbe | 231 | else |
502a0106 | 232 | pdata = NULL; |
502a0106 MB |
233 | |
234 | if (pdata) { | |
235 | reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK | | |
236 | WM831X_WDOG_RST_SRC); | |
237 | ||
238 | reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT; | |
239 | reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT; | |
240 | reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT; | |
241 | ||
242 | if (pdata->update_gpio) { | |
7a5da030 JH |
243 | ret = devm_gpio_request_one(&pdev->dev, |
244 | pdata->update_gpio, | |
245 | GPIOF_OUT_INIT_LOW, | |
246 | "Watchdog update"); | |
502a0106 MB |
247 | if (ret < 0) { |
248 | dev_err(wm831x->dev, | |
249 | "Failed to request update GPIO: %d\n", | |
250 | ret); | |
30cba9a1 | 251 | return ret; |
502a0106 MB |
252 | } |
253 | ||
00411ee9 | 254 | driver_data->update_gpio = pdata->update_gpio; |
502a0106 MB |
255 | |
256 | /* Make sure the watchdog takes hardware updates */ | |
257 | reg |= WM831X_WDOG_RST_SRC; | |
258 | } | |
259 | ||
260 | ret = wm831x_reg_unlock(wm831x); | |
261 | if (ret == 0) { | |
262 | ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg); | |
263 | wm831x_reg_lock(wm831x); | |
264 | } else { | |
265 | dev_err(wm831x->dev, | |
266 | "Failed to unlock security key: %d\n", ret); | |
30cba9a1 | 267 | return ret; |
502a0106 MB |
268 | } |
269 | } | |
270 | ||
30cba9a1 | 271 | ret = devm_watchdog_register_device(&pdev->dev, &driver_data->wdt); |
502a0106 | 272 | if (ret != 0) { |
00411ee9 MB |
273 | dev_err(wm831x->dev, "watchdog_register_device() failed: %d\n", |
274 | ret); | |
30cba9a1 | 275 | return ret; |
502a0106 MB |
276 | } |
277 | ||
502a0106 MB |
278 | return 0; |
279 | } | |
280 | ||
281 | static struct platform_driver wm831x_wdt_driver = { | |
282 | .probe = wm831x_wdt_probe, | |
502a0106 MB |
283 | .driver = { |
284 | .name = "wm831x-watchdog", | |
285 | }, | |
286 | }; | |
287 | ||
216f3ad9 | 288 | module_platform_driver(wm831x_wdt_driver); |
502a0106 MB |
289 | |
290 | MODULE_AUTHOR("Mark Brown"); | |
291 | MODULE_DESCRIPTION("WM831x Watchdog"); | |
292 | MODULE_LICENSE("GPL"); | |
293 | MODULE_ALIAS("platform:wm831x-watchdog"); |