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