19f806850SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2e51c288eSKevin Strasser /*
3e51c288eSKevin Strasser * Kontron PLD watchdog driver
4e51c288eSKevin Strasser *
5e51c288eSKevin Strasser * Copyright (c) 2010-2013 Kontron Europe GmbH
6e51c288eSKevin Strasser * Author: Michael Brunner <michael.brunner@kontron.com>
7e51c288eSKevin Strasser *
8e51c288eSKevin Strasser * Note: From the PLD watchdog point of view timeout and pretimeout are
9e51c288eSKevin Strasser * defined differently than in the kernel.
10e51c288eSKevin Strasser * First the pretimeout stage runs out before the timeout stage gets
11e51c288eSKevin Strasser * active.
12e51c288eSKevin Strasser *
13e51c288eSKevin Strasser * Kernel/API: P-----| pretimeout
14e51c288eSKevin Strasser * |-----------------------T timeout
15e51c288eSKevin Strasser * Watchdog: |-----------------P pretimeout_stage
16e51c288eSKevin Strasser * |-----T timeout_stage
17e51c288eSKevin Strasser */
18e51c288eSKevin Strasser
19e51c288eSKevin Strasser #include <linux/module.h>
20e51c288eSKevin Strasser #include <linux/moduleparam.h>
21e51c288eSKevin Strasser #include <linux/uaccess.h>
22e51c288eSKevin Strasser #include <linux/watchdog.h>
23e51c288eSKevin Strasser #include <linux/platform_device.h>
24e51c288eSKevin Strasser #include <linux/mfd/kempld.h>
25e51c288eSKevin Strasser
26e51c288eSKevin Strasser #define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)
27e51c288eSKevin Strasser #define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))
28e51c288eSKevin Strasser #define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)
294c4e4566SJingoo Han #define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)
30e51c288eSKevin Strasser #define STAGE_CFG_PRESCALER_MASK 0x30
31e51c288eSKevin Strasser #define STAGE_CFG_ACTION_MASK 0x7
32e51c288eSKevin Strasser #define STAGE_CFG_ASSERT (1 << 3)
33e51c288eSKevin Strasser
34e51c288eSKevin Strasser #define KEMPLD_WDT_MAX_STAGES 2
35e51c288eSKevin Strasser #define KEMPLD_WDT_KICK 0x16
36e51c288eSKevin Strasser #define KEMPLD_WDT_CFG 0x17
37e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_ENABLE 0x10
38e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8
39e51c288eSKevin Strasser #define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80
40e51c288eSKevin Strasser
41e51c288eSKevin Strasser enum {
42e51c288eSKevin Strasser ACTION_NONE = 0,
43e51c288eSKevin Strasser ACTION_RESET,
44e51c288eSKevin Strasser ACTION_NMI,
45e51c288eSKevin Strasser ACTION_SMI,
46e51c288eSKevin Strasser ACTION_SCI,
47e51c288eSKevin Strasser ACTION_DELAY,
48e51c288eSKevin Strasser };
49e51c288eSKevin Strasser
50e51c288eSKevin Strasser enum {
51e51c288eSKevin Strasser STAGE_TIMEOUT = 0,
52e51c288eSKevin Strasser STAGE_PRETIMEOUT,
53e51c288eSKevin Strasser };
54e51c288eSKevin Strasser
55e51c288eSKevin Strasser enum {
56e51c288eSKevin Strasser PRESCALER_21 = 0,
57e51c288eSKevin Strasser PRESCALER_17,
58e51c288eSKevin Strasser PRESCALER_12,
59e51c288eSKevin Strasser };
60e51c288eSKevin Strasser
61b3970bdeSJingoo Han static const u32 kempld_prescaler[] = {
62e51c288eSKevin Strasser [PRESCALER_21] = (1 << 21) - 1,
63e51c288eSKevin Strasser [PRESCALER_17] = (1 << 17) - 1,
64e51c288eSKevin Strasser [PRESCALER_12] = (1 << 12) - 1,
65e51c288eSKevin Strasser 0,
66e51c288eSKevin Strasser };
67e51c288eSKevin Strasser
68e51c288eSKevin Strasser struct kempld_wdt_stage {
69e51c288eSKevin Strasser unsigned int id;
70e51c288eSKevin Strasser u32 mask;
71e51c288eSKevin Strasser };
72e51c288eSKevin Strasser
73e51c288eSKevin Strasser struct kempld_wdt_data {
74e51c288eSKevin Strasser struct kempld_device_data *pld;
75e51c288eSKevin Strasser struct watchdog_device wdd;
76e51c288eSKevin Strasser unsigned int pretimeout;
77e51c288eSKevin Strasser struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES];
78e51c288eSKevin Strasser u8 pm_status_store;
79e51c288eSKevin Strasser };
80e51c288eSKevin Strasser
81e51c288eSKevin Strasser #define DEFAULT_TIMEOUT 30 /* seconds */
82e51c288eSKevin Strasser #define DEFAULT_PRETIMEOUT 0
83e51c288eSKevin Strasser
84e51c288eSKevin Strasser static unsigned int timeout = DEFAULT_TIMEOUT;
85e51c288eSKevin Strasser module_param(timeout, uint, 0);
86e51c288eSKevin Strasser MODULE_PARM_DESC(timeout,
87e51c288eSKevin Strasser "Watchdog timeout in seconds. (>=0, default="
88e51c288eSKevin Strasser __MODULE_STRING(DEFAULT_TIMEOUT) ")");
89e51c288eSKevin Strasser
90e51c288eSKevin Strasser static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
91e51c288eSKevin Strasser module_param(pretimeout, uint, 0);
92e51c288eSKevin Strasser MODULE_PARM_DESC(pretimeout,
93e51c288eSKevin Strasser "Watchdog pretimeout in seconds. (>=0, default="
94e51c288eSKevin Strasser __MODULE_STRING(DEFAULT_PRETIMEOUT) ")");
95e51c288eSKevin Strasser
96e51c288eSKevin Strasser static bool nowayout = WATCHDOG_NOWAYOUT;
97e51c288eSKevin Strasser module_param(nowayout, bool, 0);
98e51c288eSKevin Strasser MODULE_PARM_DESC(nowayout,
99e51c288eSKevin Strasser "Watchdog cannot be stopped once started (default="
100e51c288eSKevin Strasser __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
101e51c288eSKevin Strasser
kempld_wdt_set_stage_action(struct kempld_wdt_data * wdt_data,struct kempld_wdt_stage * stage,u8 action)102e51c288eSKevin Strasser static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
103e51c288eSKevin Strasser struct kempld_wdt_stage *stage,
104e51c288eSKevin Strasser u8 action)
105e51c288eSKevin Strasser {
106e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
107e51c288eSKevin Strasser u8 stage_cfg;
108e51c288eSKevin Strasser
109e51c288eSKevin Strasser if (!stage || !stage->mask)
110e51c288eSKevin Strasser return -EINVAL;
111e51c288eSKevin Strasser
112e51c288eSKevin Strasser kempld_get_mutex(pld);
113e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
114e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_ACTION_MASK;
115e51c288eSKevin Strasser stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
116e51c288eSKevin Strasser
117e51c288eSKevin Strasser if (action == ACTION_RESET)
118e51c288eSKevin Strasser stage_cfg |= STAGE_CFG_ASSERT;
119e51c288eSKevin Strasser else
120e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_ASSERT;
121e51c288eSKevin Strasser
122e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
123e51c288eSKevin Strasser kempld_release_mutex(pld);
124e51c288eSKevin Strasser
125e51c288eSKevin Strasser return 0;
126e51c288eSKevin Strasser }
127e51c288eSKevin Strasser
kempld_wdt_set_stage_timeout(struct kempld_wdt_data * wdt_data,struct kempld_wdt_stage * stage,unsigned int timeout)128e51c288eSKevin Strasser static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
129e51c288eSKevin Strasser struct kempld_wdt_stage *stage,
130e51c288eSKevin Strasser unsigned int timeout)
131e51c288eSKevin Strasser {
132e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
1333736d4ebSArnd Bergmann u32 prescaler;
134e51c288eSKevin Strasser u64 stage_timeout64;
135e51c288eSKevin Strasser u32 stage_timeout;
136e51c288eSKevin Strasser u32 remainder;
137e51c288eSKevin Strasser u8 stage_cfg;
138e51c288eSKevin Strasser
1393736d4ebSArnd Bergmann prescaler = kempld_prescaler[PRESCALER_21];
1403736d4ebSArnd Bergmann
141e51c288eSKevin Strasser if (!stage)
142e51c288eSKevin Strasser return -EINVAL;
143e51c288eSKevin Strasser
144e51c288eSKevin Strasser stage_timeout64 = (u64)timeout * pld->pld_clock;
145e51c288eSKevin Strasser remainder = do_div(stage_timeout64, prescaler);
146e51c288eSKevin Strasser if (remainder)
147e51c288eSKevin Strasser stage_timeout64++;
148e51c288eSKevin Strasser
149e51c288eSKevin Strasser if (stage_timeout64 > stage->mask)
150e51c288eSKevin Strasser return -EINVAL;
151e51c288eSKevin Strasser
152e51c288eSKevin Strasser stage_timeout = stage_timeout64 & stage->mask;
153e51c288eSKevin Strasser
154e51c288eSKevin Strasser kempld_get_mutex(pld);
155e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
156e51c288eSKevin Strasser stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
157a9e0436bSgundberg stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21);
158e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
159e51c288eSKevin Strasser kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
160e51c288eSKevin Strasser stage_timeout);
161e51c288eSKevin Strasser kempld_release_mutex(pld);
162e51c288eSKevin Strasser
163e51c288eSKevin Strasser return 0;
164e51c288eSKevin Strasser }
165e51c288eSKevin Strasser
166e51c288eSKevin Strasser /*
167e51c288eSKevin Strasser * kempld_get_mutex must be called prior to calling this function.
168e51c288eSKevin Strasser */
kempld_wdt_get_timeout(struct kempld_wdt_data * wdt_data,struct kempld_wdt_stage * stage)169e51c288eSKevin Strasser static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
170e51c288eSKevin Strasser struct kempld_wdt_stage *stage)
171e51c288eSKevin Strasser {
172e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
173e51c288eSKevin Strasser unsigned int timeout;
174e51c288eSKevin Strasser u64 stage_timeout;
175e51c288eSKevin Strasser u32 prescaler;
176e51c288eSKevin Strasser u32 remainder;
177e51c288eSKevin Strasser u8 stage_cfg;
178e51c288eSKevin Strasser
179e51c288eSKevin Strasser if (!stage->mask)
180e51c288eSKevin Strasser return 0;
181e51c288eSKevin Strasser
182e51c288eSKevin Strasser stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
183e51c288eSKevin Strasser stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
184e51c288eSKevin Strasser prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
185e51c288eSKevin Strasser
186e51c288eSKevin Strasser stage_timeout = (stage_timeout & stage->mask) * prescaler;
187e51c288eSKevin Strasser remainder = do_div(stage_timeout, pld->pld_clock);
188e51c288eSKevin Strasser if (remainder)
189e51c288eSKevin Strasser stage_timeout++;
190e51c288eSKevin Strasser
191e51c288eSKevin Strasser timeout = stage_timeout;
192e51c288eSKevin Strasser WARN_ON_ONCE(timeout != stage_timeout);
193e51c288eSKevin Strasser
194e51c288eSKevin Strasser return timeout;
195e51c288eSKevin Strasser }
196e51c288eSKevin Strasser
kempld_wdt_set_timeout(struct watchdog_device * wdd,unsigned int timeout)197e51c288eSKevin Strasser static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
198e51c288eSKevin Strasser unsigned int timeout)
199e51c288eSKevin Strasser {
200e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
201e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage;
202e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage;
203e51c288eSKevin Strasser int ret;
204e51c288eSKevin Strasser
205e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
206e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
207e51c288eSKevin Strasser
208e51c288eSKevin Strasser if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
209e51c288eSKevin Strasser timeout = wdt_data->pretimeout;
210e51c288eSKevin Strasser
211e51c288eSKevin Strasser ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
212e51c288eSKevin Strasser ACTION_RESET);
213e51c288eSKevin Strasser if (ret)
214e51c288eSKevin Strasser return ret;
215e51c288eSKevin Strasser ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
216e51c288eSKevin Strasser timeout);
217e51c288eSKevin Strasser if (ret)
218e51c288eSKevin Strasser return ret;
219e51c288eSKevin Strasser
220e51c288eSKevin Strasser wdd->timeout = timeout;
221e51c288eSKevin Strasser return 0;
222e51c288eSKevin Strasser }
223e51c288eSKevin Strasser
kempld_wdt_set_pretimeout(struct watchdog_device * wdd,unsigned int pretimeout)224e51c288eSKevin Strasser static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
225e51c288eSKevin Strasser unsigned int pretimeout)
226e51c288eSKevin Strasser {
227e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
228e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage;
229e51c288eSKevin Strasser u8 action = ACTION_NONE;
230e51c288eSKevin Strasser int ret;
231e51c288eSKevin Strasser
232e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
233e51c288eSKevin Strasser
234e51c288eSKevin Strasser if (!pretimeout_stage->mask)
235e51c288eSKevin Strasser return -ENXIO;
236e51c288eSKevin Strasser
237e51c288eSKevin Strasser if (pretimeout > wdd->timeout)
238e51c288eSKevin Strasser return -EINVAL;
239e51c288eSKevin Strasser
240e51c288eSKevin Strasser if (pretimeout > 0)
241e51c288eSKevin Strasser action = ACTION_NMI;
242e51c288eSKevin Strasser
243e51c288eSKevin Strasser ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
244e51c288eSKevin Strasser action);
245e51c288eSKevin Strasser if (ret)
246e51c288eSKevin Strasser return ret;
247e51c288eSKevin Strasser ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
248e51c288eSKevin Strasser wdd->timeout - pretimeout);
249e51c288eSKevin Strasser if (ret)
250e51c288eSKevin Strasser return ret;
251e51c288eSKevin Strasser
252e51c288eSKevin Strasser wdt_data->pretimeout = pretimeout;
253e51c288eSKevin Strasser return 0;
254e51c288eSKevin Strasser }
255e51c288eSKevin Strasser
kempld_wdt_update_timeouts(struct kempld_wdt_data * wdt_data)256e51c288eSKevin Strasser static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
257e51c288eSKevin Strasser {
258e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
259e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage;
260e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage;
261e51c288eSKevin Strasser unsigned int pretimeout, timeout;
262e51c288eSKevin Strasser
263e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
264e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
265e51c288eSKevin Strasser
266e51c288eSKevin Strasser kempld_get_mutex(pld);
267e51c288eSKevin Strasser pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
268e51c288eSKevin Strasser timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
269e51c288eSKevin Strasser kempld_release_mutex(pld);
270e51c288eSKevin Strasser
271e51c288eSKevin Strasser if (pretimeout)
272e51c288eSKevin Strasser wdt_data->pretimeout = timeout;
273e51c288eSKevin Strasser else
274e51c288eSKevin Strasser wdt_data->pretimeout = 0;
275e51c288eSKevin Strasser
276e51c288eSKevin Strasser wdt_data->wdd.timeout = pretimeout + timeout;
277e51c288eSKevin Strasser }
278e51c288eSKevin Strasser
kempld_wdt_start(struct watchdog_device * wdd)279e51c288eSKevin Strasser static int kempld_wdt_start(struct watchdog_device *wdd)
280e51c288eSKevin Strasser {
281e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
282e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
283e51c288eSKevin Strasser u8 status;
284e51c288eSKevin Strasser int ret;
285e51c288eSKevin Strasser
286e51c288eSKevin Strasser ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
287e51c288eSKevin Strasser if (ret)
288e51c288eSKevin Strasser return ret;
289e51c288eSKevin Strasser
290e51c288eSKevin Strasser kempld_get_mutex(pld);
291e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG);
292e51c288eSKevin Strasser status |= KEMPLD_WDT_CFG_ENABLE;
293e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_CFG, status);
294e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG);
295e51c288eSKevin Strasser kempld_release_mutex(pld);
296e51c288eSKevin Strasser
297e51c288eSKevin Strasser /* Check if the watchdog was enabled */
298e51c288eSKevin Strasser if (!(status & KEMPLD_WDT_CFG_ENABLE))
299e51c288eSKevin Strasser return -EACCES;
300e51c288eSKevin Strasser
301e51c288eSKevin Strasser return 0;
302e51c288eSKevin Strasser }
303e51c288eSKevin Strasser
kempld_wdt_stop(struct watchdog_device * wdd)304e51c288eSKevin Strasser static int kempld_wdt_stop(struct watchdog_device *wdd)
305e51c288eSKevin Strasser {
306e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
307e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
308e51c288eSKevin Strasser u8 status;
309e51c288eSKevin Strasser
310e51c288eSKevin Strasser kempld_get_mutex(pld);
311e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG);
312e51c288eSKevin Strasser status &= ~KEMPLD_WDT_CFG_ENABLE;
313e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_CFG, status);
314e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG);
315e51c288eSKevin Strasser kempld_release_mutex(pld);
316e51c288eSKevin Strasser
317e51c288eSKevin Strasser /* Check if the watchdog was disabled */
318e51c288eSKevin Strasser if (status & KEMPLD_WDT_CFG_ENABLE)
319e51c288eSKevin Strasser return -EACCES;
320e51c288eSKevin Strasser
321e51c288eSKevin Strasser return 0;
322e51c288eSKevin Strasser }
323e51c288eSKevin Strasser
kempld_wdt_keepalive(struct watchdog_device * wdd)324e51c288eSKevin Strasser static int kempld_wdt_keepalive(struct watchdog_device *wdd)
325e51c288eSKevin Strasser {
326e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
327e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
328e51c288eSKevin Strasser
329e51c288eSKevin Strasser kempld_get_mutex(pld);
330e51c288eSKevin Strasser kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
331e51c288eSKevin Strasser kempld_release_mutex(pld);
332e51c288eSKevin Strasser
333e51c288eSKevin Strasser return 0;
334e51c288eSKevin Strasser }
335e51c288eSKevin Strasser
kempld_wdt_ioctl(struct watchdog_device * wdd,unsigned int cmd,unsigned long arg)336e51c288eSKevin Strasser static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
337e51c288eSKevin Strasser unsigned long arg)
338e51c288eSKevin Strasser {
339e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
340e51c288eSKevin Strasser void __user *argp = (void __user *)arg;
341e51c288eSKevin Strasser int ret = -ENOIOCTLCMD;
342e51c288eSKevin Strasser int __user *p = argp;
343e51c288eSKevin Strasser int new_value;
344e51c288eSKevin Strasser
345e51c288eSKevin Strasser switch (cmd) {
346e51c288eSKevin Strasser case WDIOC_SETPRETIMEOUT:
347e51c288eSKevin Strasser if (get_user(new_value, p))
348e51c288eSKevin Strasser return -EFAULT;
349e51c288eSKevin Strasser ret = kempld_wdt_set_pretimeout(wdd, new_value);
350e51c288eSKevin Strasser if (ret)
351e51c288eSKevin Strasser return ret;
352e51c288eSKevin Strasser ret = kempld_wdt_keepalive(wdd);
353e51c288eSKevin Strasser break;
354e51c288eSKevin Strasser case WDIOC_GETPRETIMEOUT:
355b3970bdeSJingoo Han ret = put_user(wdt_data->pretimeout, (int __user *)arg);
356e51c288eSKevin Strasser break;
357e51c288eSKevin Strasser }
358e51c288eSKevin Strasser
359e51c288eSKevin Strasser return ret;
360e51c288eSKevin Strasser }
361e51c288eSKevin Strasser
kempld_wdt_probe_stages(struct watchdog_device * wdd)362e51c288eSKevin Strasser static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
363e51c288eSKevin Strasser {
364e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
365e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
366e51c288eSKevin Strasser struct kempld_wdt_stage *pretimeout_stage;
367e51c288eSKevin Strasser struct kempld_wdt_stage *timeout_stage;
368e51c288eSKevin Strasser u8 index, data, data_orig;
369e51c288eSKevin Strasser u32 mask;
370e51c288eSKevin Strasser int i, j;
371e51c288eSKevin Strasser
372e51c288eSKevin Strasser pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
373e51c288eSKevin Strasser timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
374e51c288eSKevin Strasser
375e51c288eSKevin Strasser pretimeout_stage->mask = 0;
376e51c288eSKevin Strasser timeout_stage->mask = 0;
377e51c288eSKevin Strasser
378e51c288eSKevin Strasser for (i = 0; i < 3; i++) {
379e51c288eSKevin Strasser index = KEMPLD_WDT_STAGE_TIMEOUT(i);
380e51c288eSKevin Strasser mask = 0;
381e51c288eSKevin Strasser
382e51c288eSKevin Strasser kempld_get_mutex(pld);
383e51c288eSKevin Strasser /* Probe each byte individually. */
384e51c288eSKevin Strasser for (j = 0; j < 4; j++) {
385e51c288eSKevin Strasser data_orig = kempld_read8(pld, index + j);
386e51c288eSKevin Strasser kempld_write8(pld, index + j, 0x00);
387e51c288eSKevin Strasser data = kempld_read8(pld, index + j);
388e51c288eSKevin Strasser /* A failed write means this byte is reserved */
389e51c288eSKevin Strasser if (data != 0x00)
390e51c288eSKevin Strasser break;
391e51c288eSKevin Strasser kempld_write8(pld, index + j, data_orig);
392e51c288eSKevin Strasser mask |= 0xff << (j * 8);
393e51c288eSKevin Strasser }
394e51c288eSKevin Strasser kempld_release_mutex(pld);
395e51c288eSKevin Strasser
396e51c288eSKevin Strasser /* Assign available stages to timeout and pretimeout */
397e51c288eSKevin Strasser if (!timeout_stage->mask) {
398e51c288eSKevin Strasser timeout_stage->mask = mask;
399e51c288eSKevin Strasser timeout_stage->id = i;
400e51c288eSKevin Strasser } else {
401e51c288eSKevin Strasser if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
402e51c288eSKevin Strasser pretimeout_stage->mask = timeout_stage->mask;
403e51c288eSKevin Strasser timeout_stage->mask = mask;
404e51c288eSKevin Strasser pretimeout_stage->id = timeout_stage->id;
405e51c288eSKevin Strasser timeout_stage->id = i;
406e51c288eSKevin Strasser }
407e51c288eSKevin Strasser break;
408e51c288eSKevin Strasser }
409e51c288eSKevin Strasser }
410e51c288eSKevin Strasser
411e51c288eSKevin Strasser if (!timeout_stage->mask)
412e51c288eSKevin Strasser return -ENODEV;
413e51c288eSKevin Strasser
414e51c288eSKevin Strasser return 0;
415e51c288eSKevin Strasser }
416e51c288eSKevin Strasser
4176c368932SBhumika Goyal static const struct watchdog_info kempld_wdt_info = {
418e51c288eSKevin Strasser .identity = "KEMPLD Watchdog",
419e51c288eSKevin Strasser .options = WDIOF_SETTIMEOUT |
420e51c288eSKevin Strasser WDIOF_KEEPALIVEPING |
421e51c288eSKevin Strasser WDIOF_MAGICCLOSE |
422e51c288eSKevin Strasser WDIOF_PRETIMEOUT
423e51c288eSKevin Strasser };
424e51c288eSKevin Strasser
42585f15cfcSJulia Lawall static const struct watchdog_ops kempld_wdt_ops = {
426e51c288eSKevin Strasser .owner = THIS_MODULE,
427e51c288eSKevin Strasser .start = kempld_wdt_start,
428e51c288eSKevin Strasser .stop = kempld_wdt_stop,
429e51c288eSKevin Strasser .ping = kempld_wdt_keepalive,
430e51c288eSKevin Strasser .set_timeout = kempld_wdt_set_timeout,
431e51c288eSKevin Strasser .ioctl = kempld_wdt_ioctl,
432e51c288eSKevin Strasser };
433e51c288eSKevin Strasser
kempld_wdt_probe(struct platform_device * pdev)434e51c288eSKevin Strasser static int kempld_wdt_probe(struct platform_device *pdev)
435e51c288eSKevin Strasser {
436e51c288eSKevin Strasser struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
437e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data;
438e51c288eSKevin Strasser struct device *dev = &pdev->dev;
439e51c288eSKevin Strasser struct watchdog_device *wdd;
440e51c288eSKevin Strasser u8 status;
441e51c288eSKevin Strasser int ret = 0;
442e51c288eSKevin Strasser
443e51c288eSKevin Strasser wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
444e51c288eSKevin Strasser if (!wdt_data)
445e51c288eSKevin Strasser return -ENOMEM;
446e51c288eSKevin Strasser
447e51c288eSKevin Strasser wdt_data->pld = pld;
448e51c288eSKevin Strasser wdd = &wdt_data->wdd;
449e51c288eSKevin Strasser wdd->parent = dev;
450e51c288eSKevin Strasser
451e51c288eSKevin Strasser kempld_get_mutex(pld);
452e51c288eSKevin Strasser status = kempld_read8(pld, KEMPLD_WDT_CFG);
453e51c288eSKevin Strasser kempld_release_mutex(pld);
454e51c288eSKevin Strasser
455e51c288eSKevin Strasser /* Enable nowayout if watchdog is already locked */
456e51c288eSKevin Strasser if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
457e51c288eSKevin Strasser KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
458e51c288eSKevin Strasser if (!nowayout)
459e51c288eSKevin Strasser dev_warn(dev,
460e51c288eSKevin Strasser "Forcing nowayout - watchdog lock enabled!\n");
461e51c288eSKevin Strasser nowayout = true;
462e51c288eSKevin Strasser }
463e51c288eSKevin Strasser
464e51c288eSKevin Strasser wdd->info = &kempld_wdt_info;
465e51c288eSKevin Strasser wdd->ops = &kempld_wdt_ops;
466e51c288eSKevin Strasser
467e51c288eSKevin Strasser watchdog_set_drvdata(wdd, wdt_data);
468e51c288eSKevin Strasser watchdog_set_nowayout(wdd, nowayout);
469e51c288eSKevin Strasser
470e51c288eSKevin Strasser ret = kempld_wdt_probe_stages(wdd);
471e51c288eSKevin Strasser if (ret)
472e51c288eSKevin Strasser return ret;
473e51c288eSKevin Strasser
474e51c288eSKevin Strasser kempld_wdt_set_timeout(wdd, timeout);
475e51c288eSKevin Strasser kempld_wdt_set_pretimeout(wdd, pretimeout);
476e51c288eSKevin Strasser
477e51c288eSKevin Strasser /* Check if watchdog is already enabled */
478e51c288eSKevin Strasser if (status & KEMPLD_WDT_CFG_ENABLE) {
479e51c288eSKevin Strasser /* Get current watchdog settings */
480e51c288eSKevin Strasser kempld_wdt_update_timeouts(wdt_data);
481e51c288eSKevin Strasser dev_info(dev, "Watchdog was already enabled\n");
482e51c288eSKevin Strasser }
483e51c288eSKevin Strasser
484e51c288eSKevin Strasser platform_set_drvdata(pdev, wdt_data);
4854689ba97SGuenter Roeck watchdog_stop_on_reboot(wdd);
4864689ba97SGuenter Roeck watchdog_stop_on_unregister(wdd);
4874689ba97SGuenter Roeck ret = devm_watchdog_register_device(dev, wdd);
488e51c288eSKevin Strasser if (ret)
489e51c288eSKevin Strasser return ret;
490e51c288eSKevin Strasser
491e51c288eSKevin Strasser dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
492e51c288eSKevin Strasser
493e51c288eSKevin Strasser return 0;
494e51c288eSKevin Strasser }
495e51c288eSKevin Strasser
496e51c288eSKevin Strasser /* Disable watchdog if it is active during suspend */
kempld_wdt_suspend(struct platform_device * pdev,pm_message_t message)497e51c288eSKevin Strasser static int kempld_wdt_suspend(struct platform_device *pdev,
498e51c288eSKevin Strasser pm_message_t message)
499e51c288eSKevin Strasser {
500e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
501e51c288eSKevin Strasser struct kempld_device_data *pld = wdt_data->pld;
502e51c288eSKevin Strasser struct watchdog_device *wdd = &wdt_data->wdd;
503e51c288eSKevin Strasser
504e51c288eSKevin Strasser kempld_get_mutex(pld);
505e51c288eSKevin Strasser wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
506e51c288eSKevin Strasser kempld_release_mutex(pld);
507e51c288eSKevin Strasser
508e51c288eSKevin Strasser kempld_wdt_update_timeouts(wdt_data);
509e51c288eSKevin Strasser
510e51c288eSKevin Strasser if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
511e51c288eSKevin Strasser return kempld_wdt_stop(wdd);
512e51c288eSKevin Strasser
513e51c288eSKevin Strasser return 0;
514e51c288eSKevin Strasser }
515e51c288eSKevin Strasser
516e51c288eSKevin Strasser /* Enable watchdog and configure it if necessary */
kempld_wdt_resume(struct platform_device * pdev)517e51c288eSKevin Strasser static int kempld_wdt_resume(struct platform_device *pdev)
518e51c288eSKevin Strasser {
519e51c288eSKevin Strasser struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
520e51c288eSKevin Strasser struct watchdog_device *wdd = &wdt_data->wdd;
521e51c288eSKevin Strasser
522e51c288eSKevin Strasser /*
523e51c288eSKevin Strasser * If watchdog was stopped before suspend be sure it gets disabled
524e51c288eSKevin Strasser * again, for the case BIOS has enabled it during resume
525e51c288eSKevin Strasser */
526e51c288eSKevin Strasser if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
527e51c288eSKevin Strasser return kempld_wdt_start(wdd);
528e51c288eSKevin Strasser else
529e51c288eSKevin Strasser return kempld_wdt_stop(wdd);
530e51c288eSKevin Strasser }
531e51c288eSKevin Strasser
532e51c288eSKevin Strasser static struct platform_driver kempld_wdt_driver = {
533e51c288eSKevin Strasser .driver = {
534e51c288eSKevin Strasser .name = "kempld-wdt",
535e51c288eSKevin Strasser },
536e51c288eSKevin Strasser .probe = kempld_wdt_probe,
537*758f46c2SPaul Cercueil .suspend = pm_ptr(kempld_wdt_suspend),
538*758f46c2SPaul Cercueil .resume = pm_ptr(kempld_wdt_resume),
539e51c288eSKevin Strasser };
540e51c288eSKevin Strasser
541e51c288eSKevin Strasser module_platform_driver(kempld_wdt_driver);
542e51c288eSKevin Strasser
543e51c288eSKevin Strasser MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
544e51c288eSKevin Strasser MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
545e51c288eSKevin Strasser MODULE_LICENSE("GPL");
546