s3c2410_wdt.c 12.6 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8
/* linux/drivers/char/watchdog/s3c2410_wdt.c
 *
 * Copyright (c) 2004 Simtec Electronics
 *	Ben Dooks <ben@simtec.co.uk>
 *
 * S3C2410 Watchdog Timer Support
 *
 * Based on, softdog.c by Alan Cox,
9
 *     (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>
L
Linus Torvalds 已提交
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

26 27
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

L
Linus Torvalds 已提交
28 29 30 31
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
32
#include <linux/miscdevice.h> /* for MODULE_ALIAS_MISCDEV */
L
Linus Torvalds 已提交
33 34
#include <linux/watchdog.h>
#include <linux/init.h>
35
#include <linux/platform_device.h>
L
Linus Torvalds 已提交
36
#include <linux/interrupt.h>
37
#include <linux/clk.h>
38 39
#include <linux/uaccess.h>
#include <linux/io.h>
40
#include <linux/cpufreq.h>
41
#include <linux/slab.h>
42
#include <linux/err.h>
43
#include <linux/of.h>
L
Linus Torvalds 已提交
44

45
#include <mach/map.h>
L
Linus Torvalds 已提交
46

47 48
#undef S3C_VA_WATCHDOG
#define S3C_VA_WATCHDOG (0)
L
Linus Torvalds 已提交
49

50
#include <plat/regs-watchdog.h>
L
Linus Torvalds 已提交
51 52 53 54

#define CONFIG_S3C2410_WATCHDOG_ATBOOT		(0)
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME	(15)

W
Wim Van Sebroeck 已提交
55
static bool nowayout	= WATCHDOG_NOWAYOUT;
L
Linus Torvalds 已提交
56 57
static int tmr_margin	= CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
static int tmr_atboot	= CONFIG_S3C2410_WATCHDOG_ATBOOT;
58 59
static int soft_noboot;
static int debug;
L
Linus Torvalds 已提交
60 61 62

module_param(tmr_margin,  int, 0);
module_param(tmr_atboot,  int, 0);
W
Wim Van Sebroeck 已提交
63
module_param(nowayout,   bool, 0);
L
Linus Torvalds 已提交
64 65 66
module_param(soft_noboot, int, 0);
module_param(debug,	  int, 0);

67
MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
68 69 70 71 72 73
		__MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
MODULE_PARM_DESC(tmr_atboot,
		"Watchdog is started at boot time if set to 1, default="
			__MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
			__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
74
MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, "
75 76
			"0 to reboot (default 0)");
MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (default 0)");
L
Linus Torvalds 已提交
77

78
static struct device    *wdt_dev;	/* platform device attached to */
L
Linus Torvalds 已提交
79 80 81 82 83
static struct resource	*wdt_mem;
static struct resource	*wdt_irq;
static struct clk	*wdt_clock;
static void __iomem	*wdt_base;
static unsigned int	 wdt_count;
84
static DEFINE_SPINLOCK(wdt_lock);
L
Linus Torvalds 已提交
85 86 87

/* watchdog control routines */

88 89 90 91 92
#define DBG(fmt, ...)					\
do {							\
	if (debug)					\
		pr_info(fmt, ##__VA_ARGS__);		\
} while (0)
L
Linus Torvalds 已提交
93 94 95

/* functions */

96
static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
L
Linus Torvalds 已提交
97
{
98
	spin_lock(&wdt_lock);
L
Linus Torvalds 已提交
99
	writel(wdt_count, wdt_base + S3C2410_WTCNT);
100
	spin_unlock(&wdt_lock);
101 102

	return 0;
L
Linus Torvalds 已提交
103 104
}

105 106 107 108 109 110 111 112 113
static void __s3c2410wdt_stop(void)
{
	unsigned long wtcon;

	wtcon = readl(wdt_base + S3C2410_WTCON);
	wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
	writel(wtcon, wdt_base + S3C2410_WTCON);
}

114
static int s3c2410wdt_stop(struct watchdog_device *wdd)
115 116 117 118
{
	spin_lock(&wdt_lock);
	__s3c2410wdt_stop();
	spin_unlock(&wdt_lock);
119 120

	return 0;
L
Linus Torvalds 已提交
121 122
}

123
static int s3c2410wdt_start(struct watchdog_device *wdd)
L
Linus Torvalds 已提交
124 125 126
{
	unsigned long wtcon;

127 128 129
	spin_lock(&wdt_lock);

	__s3c2410wdt_stop();
L
Linus Torvalds 已提交
130 131 132 133 134 135 136 137 138 139 140 141 142

	wtcon = readl(wdt_base + S3C2410_WTCON);
	wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;

	if (soft_noboot) {
		wtcon |= S3C2410_WTCON_INTEN;
		wtcon &= ~S3C2410_WTCON_RSTEN;
	} else {
		wtcon &= ~S3C2410_WTCON_INTEN;
		wtcon |= S3C2410_WTCON_RSTEN;
	}

	DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
143
	    __func__, wdt_count, wtcon);
L
Linus Torvalds 已提交
144 145 146 147

	writel(wdt_count, wdt_base + S3C2410_WTDAT);
	writel(wdt_count, wdt_base + S3C2410_WTCNT);
	writel(wtcon, wdt_base + S3C2410_WTCON);
148
	spin_unlock(&wdt_lock);
149 150

	return 0;
L
Linus Torvalds 已提交
151 152
}

153 154 155 156 157
static inline int s3c2410wdt_is_running(void)
{
	return readl(wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
}

158
static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout)
L
Linus Torvalds 已提交
159
{
160
	unsigned long freq = clk_get_rate(wdt_clock);
L
Linus Torvalds 已提交
161 162 163 164 165 166 167 168 169 170
	unsigned int count;
	unsigned int divisor = 1;
	unsigned long wtcon;

	if (timeout < 1)
		return -EINVAL;

	freq /= 128;
	count = timeout * freq;

171
	DBG("%s: count=%d, timeout=%d, freq=%lu\n",
172
	    __func__, count, timeout, freq);
L
Linus Torvalds 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185

	/* if the count is bigger than the watchdog register,
	   then work out what we need to do (and if) we can
	   actually make this value
	*/

	if (count >= 0x10000) {
		for (divisor = 1; divisor <= 0x100; divisor++) {
			if ((count / divisor) < 0x10000)
				break;
		}

		if ((count / divisor) >= 0x10000) {
186
			dev_err(wdt_dev, "timeout %d too big\n", timeout);
L
Linus Torvalds 已提交
187 188 189 190 191
			return -EINVAL;
		}
	}

	DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
192
	    __func__, timeout, divisor, count, count/divisor);
L
Linus Torvalds 已提交
193 194 195 196 197 198 199 200 201 202 203 204

	count /= divisor;
	wdt_count = count;

	/* update the pre-scaler */
	wtcon = readl(wdt_base + S3C2410_WTCON);
	wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
	wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);

	writel(count, wdt_base + S3C2410_WTDAT);
	writel(wtcon, wdt_base + S3C2410_WTCON);

205
	wdd->timeout = (count * divisor) / freq;
206

L
Linus Torvalds 已提交
207 208 209
	return 0;
}

210
#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
L
Linus Torvalds 已提交
211

212
static const struct watchdog_info s3c2410_wdt_ident = {
L
Linus Torvalds 已提交
213 214 215 216 217
	.options          =     OPTIONS,
	.firmware_version =	0,
	.identity         =	"S3C2410 Watchdog",
};

218 219 220 221 222 223
static struct watchdog_ops s3c2410wdt_ops = {
	.owner = THIS_MODULE,
	.start = s3c2410wdt_start,
	.stop = s3c2410wdt_stop,
	.ping = s3c2410wdt_keepalive,
	.set_timeout = s3c2410wdt_set_heartbeat,
L
Linus Torvalds 已提交
224 225
};

226 227 228
static struct watchdog_device s3c2410_wdd = {
	.info = &s3c2410_wdt_ident,
	.ops = &s3c2410wdt_ops,
L
Linus Torvalds 已提交
229 230 231 232
};

/* interrupt handler code */

233
static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
L
Linus Torvalds 已提交
234
{
235
	dev_info(wdt_dev, "watchdog timer expired (irq)\n");
L
Linus Torvalds 已提交
236

237
	s3c2410wdt_keepalive(&s3c2410_wdd);
L
Linus Torvalds 已提交
238 239
	return IRQ_HANDLED;
}
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257


#ifdef CONFIG_CPU_FREQ

static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
					  unsigned long val, void *data)
{
	int ret;

	if (!s3c2410wdt_is_running())
		goto done;

	if (val == CPUFREQ_PRECHANGE) {
		/* To ensure that over the change we don't cause the
		 * watchdog to trigger, we perform an keep-alive if
		 * the watchdog is running.
		 */

258
		s3c2410wdt_keepalive(&s3c2410_wdd);
259
	} else if (val == CPUFREQ_POSTCHANGE) {
260
		s3c2410wdt_stop(&s3c2410_wdd);
261

262
		ret = s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout);
263 264

		if (ret >= 0)
265
			s3c2410wdt_start(&s3c2410_wdd);
266 267 268 269 270 271 272 273
		else
			goto err;
	}

done:
	return 0;

 err:
274 275
	dev_err(wdt_dev, "cannot set new value for timeout %d\n",
				s3c2410_wdd.timeout);
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
	return ret;
}

static struct notifier_block s3c2410wdt_cpufreq_transition_nb = {
	.notifier_call	= s3c2410wdt_cpufreq_transition,
};

static inline int s3c2410wdt_cpufreq_register(void)
{
	return cpufreq_register_notifier(&s3c2410wdt_cpufreq_transition_nb,
					 CPUFREQ_TRANSITION_NOTIFIER);
}

static inline void s3c2410wdt_cpufreq_deregister(void)
{
	cpufreq_unregister_notifier(&s3c2410wdt_cpufreq_transition_nb,
				    CPUFREQ_TRANSITION_NOTIFIER);
}

#else
static inline int s3c2410wdt_cpufreq_register(void)
{
	return 0;
}

static inline void s3c2410wdt_cpufreq_deregister(void)
{
}
#endif

306
static int __devinit s3c2410wdt_probe(struct platform_device *pdev)
L
Linus Torvalds 已提交
307
{
308
	struct device *dev;
309
	unsigned int wtcon;
L
Linus Torvalds 已提交
310 311 312 313
	int started = 0;
	int ret;
	int size;

314
	DBG("%s: probe=%p\n", __func__, pdev);
L
Linus Torvalds 已提交
315

316 317 318
	dev = &pdev->dev;
	wdt_dev = &pdev->dev;

319 320
	wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (wdt_mem == NULL) {
321
		dev_err(dev, "no memory resource specified\n");
L
Linus Torvalds 已提交
322 323 324
		return -ENOENT;
	}

325 326 327 328 329 330 331 332 333
	wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (wdt_irq == NULL) {
		dev_err(dev, "no irq resource specified\n");
		ret = -ENOENT;
		goto err;
	}

	/* get the memory region for the watchdog timer */

334 335
	size = resource_size(wdt_mem);
	if (!request_mem_region(wdt_mem->start, size, pdev->name)) {
336
		dev_err(dev, "failed to get memory region\n");
337 338
		ret = -EBUSY;
		goto err;
L
Linus Torvalds 已提交
339 340
	}

341
	wdt_base = ioremap(wdt_mem->start, size);
342
	if (wdt_base == NULL) {
343
		dev_err(dev, "failed to ioremap() region\n");
344 345
		ret = -EINVAL;
		goto err_req;
L
Linus Torvalds 已提交
346 347 348 349
	}

	DBG("probe: mapped wdt_base=%p\n", wdt_base);

350
	wdt_clock = clk_get(&pdev->dev, "watchdog");
351
	if (IS_ERR(wdt_clock)) {
352
		dev_err(dev, "failed to find watchdog clock source\n");
353
		ret = PTR_ERR(wdt_clock);
354
		goto err_map;
L
Linus Torvalds 已提交
355 356 357 358
	}

	clk_enable(wdt_clock);

359 360
	ret = s3c2410wdt_cpufreq_register();
	if (ret < 0) {
361
		pr_err("failed to register cpufreq\n");
362 363 364
		goto err_clk;
	}

L
Linus Torvalds 已提交
365 366 367
	/* see if we can actually set the requested timer margin, and if
	 * not, try the default value */

368 369
	if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, tmr_margin)) {
		started = s3c2410wdt_set_heartbeat(&s3c2410_wdd,
370
					CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
L
Linus Torvalds 已提交
371

372 373 374
		if (started == 0)
			dev_info(dev,
			   "tmr_margin value out of range, default %d used\n",
L
Linus Torvalds 已提交
375
			       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
376
		else
377 378
			dev_info(dev, "default timer value is out of range, "
							"cannot start\n");
L
Linus Torvalds 已提交
379 380
	}

381 382 383 384 385 386
	ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
	if (ret != 0) {
		dev_err(dev, "failed to install irq (%d)\n", ret);
		goto err_cpufreq;
	}

387 388
	watchdog_set_nowayout(&s3c2410_wdd, nowayout);

389
	ret = watchdog_register_device(&s3c2410_wdd);
L
Linus Torvalds 已提交
390
	if (ret) {
391
		dev_err(dev, "cannot register watchdog (%d)\n", ret);
392
		goto err_irq;
L
Linus Torvalds 已提交
393 394 395
	}

	if (tmr_atboot && started == 0) {
396
		dev_info(dev, "starting watchdog timer\n");
397
		s3c2410wdt_start(&s3c2410_wdd);
398 399 400 401 402
	} else if (!tmr_atboot) {
		/* if we're not enabling the watchdog, then ensure it is
		 * disabled if it has been left running from the bootloader
		 * or other source */

403
		s3c2410wdt_stop(&s3c2410_wdd);
L
Linus Torvalds 已提交
404 405
	}

406 407 408 409
	/* print out a statement of readiness */

	wtcon = readl(wdt_base + S3C2410_WTCON);

410
	dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
411
		 (wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",
412 413
		 (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
		 (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
414

L
Linus Torvalds 已提交
415
	return 0;
416

417 418 419
 err_irq:
	free_irq(wdt_irq->start, pdev);

420 421 422
 err_cpufreq:
	s3c2410wdt_cpufreq_deregister();

423 424 425
 err_clk:
	clk_disable(wdt_clock);
	clk_put(wdt_clock);
426
	wdt_clock = NULL;
427 428 429 430 431

 err_map:
	iounmap(wdt_base);

 err_req:
432
	release_mem_region(wdt_mem->start, size);
433

434 435 436
 err:
	wdt_irq = NULL;
	wdt_mem = NULL;
437
	return ret;
L
Linus Torvalds 已提交
438 439
}

440
static int __devexit s3c2410wdt_remove(struct platform_device *dev)
L
Linus Torvalds 已提交
441
{
442
	watchdog_unregister_device(&s3c2410_wdd);
L
Linus Torvalds 已提交
443

444 445
	free_irq(wdt_irq->start, dev);

446
	s3c2410wdt_cpufreq_deregister();
L
Linus Torvalds 已提交
447

448 449 450
	clk_disable(wdt_clock);
	clk_put(wdt_clock);
	wdt_clock = NULL;
L
Linus Torvalds 已提交
451

452
	iounmap(wdt_base);
453

454
	release_mem_region(wdt_mem->start, resource_size(wdt_mem));
455
	wdt_irq = NULL;
456
	wdt_mem = NULL;
L
Linus Torvalds 已提交
457 458 459
	return 0;
}

460
static void s3c2410wdt_shutdown(struct platform_device *dev)
461
{
462
	s3c2410wdt_stop(&s3c2410_wdd);
463 464
}

465 466 467 468 469
#ifdef CONFIG_PM

static unsigned long wtcon_save;
static unsigned long wtdat_save;

470
static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state)
471
{
472 473 474
	/* Save watchdog state, and turn it off. */
	wtcon_save = readl(wdt_base + S3C2410_WTCON);
	wtdat_save = readl(wdt_base + S3C2410_WTDAT);
475

476
	/* Note that WTCNT doesn't need to be saved. */
477
	s3c2410wdt_stop(&s3c2410_wdd);
478 479 480 481

	return 0;
}

482
static int s3c2410wdt_resume(struct platform_device *dev)
483
{
484
	/* Restore watchdog state. */
485

486 487 488
	writel(wtdat_save, wdt_base + S3C2410_WTDAT);
	writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */
	writel(wtcon_save, wdt_base + S3C2410_WTCON);
489

490 491
	pr_info("watchdog %sabled\n",
		(wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
492 493 494 495 496 497 498 499 500

	return 0;
}

#else
#define s3c2410wdt_suspend NULL
#define s3c2410wdt_resume  NULL
#endif /* CONFIG_PM */

501 502 503 504 505 506 507
#ifdef CONFIG_OF
static const struct of_device_id s3c2410_wdt_match[] = {
	{ .compatible = "samsung,s3c2410-wdt" },
	{},
};
MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
#endif
508

509
static struct platform_driver s3c2410wdt_driver = {
L
Linus Torvalds 已提交
510
	.probe		= s3c2410wdt_probe,
511
	.remove		= __devexit_p(s3c2410wdt_remove),
512
	.shutdown	= s3c2410wdt_shutdown,
513 514
	.suspend	= s3c2410wdt_suspend,
	.resume		= s3c2410wdt_resume,
515 516 517
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c2410-wdt",
518
		.of_match_table	= of_match_ptr(s3c2410_wdt_match),
519
	},
L
Linus Torvalds 已提交
520 521
};

522
module_platform_driver(s3c2410wdt_driver);
L
Linus Torvalds 已提交
523

524 525
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, "
	      "Dimitry Andric <dimitry.andric@tomtom.com>");
L
Linus Torvalds 已提交
526 527 528
MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
529
MODULE_ALIAS("platform:s3c2410-wdt");