aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/sec_watchdog.c
blob: 4ee118ad539108b13d8cfadd6ff7c64e20bada8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/* sec_watchdog.c copied from herring-watchdog.c
 *
 * Copyright (C) 2010 Google, Inc.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 *
 */

#include <plat/regs-watchdog.h>
#include <mach/map.h>

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/cpu.h>

#ifdef CONFIG_SEC_DEBUG
#include <mach/sec_debug.h>
#endif

/* PCLK(=PERIR=ACLK_100)/256/128 (~3200:1s) */
#define TPS 3200
#if defined(CONFIG_MACH_P4) && defined(CONFIG_TARGET_LOCALE_USA)
#define PET_BY_WORKQUEUE
#else
#define PET_BY_DIRECT_TIMER
#endif

/* reset timeout in seconds */
static unsigned watchdog_reset = 20;
module_param_named(sec_reset, watchdog_reset, uint, 0644);

/* pet timeout in seconds
 * 0 means off, 5 is proper */
static unsigned watchdog_pet = CONFIG_SEC_WATCHDOG_PET_TIME;
module_param_named(sec_pet, watchdog_pet, uint, 0644);

#if defined(PET_BY_WORKQUEUE)
static struct workqueue_struct *watchdog_wq;
static void watchdog_workfunc(struct work_struct *work);
static DECLARE_DELAYED_WORK(watchdog_work, watchdog_workfunc);
#elif defined(PET_BY_DIRECT_TIMER)
static struct timer_list pet_watchdog_timer;
static void pet_watchdog_timer_fn(unsigned long data);
#else
static struct hrtimer watchdog_timer;
static enum hrtimer_restart watchdog_timerfunc(struct hrtimer *timer);
#endif

static struct clk *wd_clk;
static spinlock_t wdt_lock;

#if defined(PET_BY_WORKQUEUE)
static void watchdog_workfunc(struct work_struct *work)
{
	pr_debug("%s kicking...%x\n", __func__, readl(S3C2410_WTCNT));
	writel(watchdog_reset * TPS, S3C2410_WTCNT);
	queue_delayed_work_on(0, watchdog_wq, &watchdog_work,
			      watchdog_pet * HZ);
}
#elif defined(PET_BY_DIRECT_TIMER)
static void pet_watchdog_timer_fn(unsigned long data)
{
	pr_debug("%s kicking...%x\n", __func__, readl(S3C2410_WTCNT));
	writel(watchdog_reset * TPS, S3C2410_WTCNT);
	pet_watchdog_timer.expires += watchdog_pet * HZ;
	add_timer_on(&pet_watchdog_timer, 0);
}
#else
static enum hrtimer_restart watchdog_timerfunc(struct hrtimer *timer)
{
	pr_debug("%s kicking...%x\n", __func__, readl(S3C2410_WTCNT));
	writel(watchdog_reset * TPS, S3C2410_WTCNT);
	hrtimer_start(&watchdog_timer,
		      ktime_set(watchdog_pet, 0), HRTIMER_MODE_REL);
	return HRTIMER_NORESTART;
}
#endif

static void watchdog_start(void)
{
	unsigned int val;
	unsigned long flags;

	spin_lock_irqsave(&wdt_lock, flags);

	/* set to PCLK / 256 / 128 */
	val = S3C2410_WTCON_DIV128;
	val |= S3C2410_WTCON_PRESCALE(255);
	writel(val, S3C2410_WTCON);

	/* program initial count */
	writel(watchdog_reset * TPS, S3C2410_WTCNT);
	writel(watchdog_reset * TPS, S3C2410_WTDAT);

	/* start timer */
	val |= S3C2410_WTCON_RSTEN | S3C2410_WTCON_ENABLE;
	writel(val, S3C2410_WTCON);
	spin_unlock_irqrestore(&wdt_lock, flags);

	/* make sure we're ready to pet the dog */
#if defined(PET_BY_WORKQUEUE)
	queue_delayed_work_on(0, watchdog_wq, &watchdog_work,
			      watchdog_pet * HZ);
#elif defined(PET_BY_DIRECT_TIMER)
	pet_watchdog_timer.expires = jiffies + watchdog_pet * HZ;
	add_timer_on(&pet_watchdog_timer, 0);
#else
	hrtimer_start(&watchdog_timer,
		      ktime_set(watchdog_pet, 0), HRTIMER_MODE_REL);
#endif
}

static void watchdog_stop(void)
{
	writel(0, S3C2410_WTCON);
#if defined(PET_BY_WORKQUEUE)
	/* do nothing? */
#elif defined(PET_BY_DIRECT_TIMER)
	del_timer(&pet_watchdog_timer);
#else
	hrtimer_cancel(&watchdog_timer);
#endif
}

static int watchdog_probe(struct platform_device *pdev)
{
	wd_clk = clk_get(NULL, "watchdog");
	BUG_ON(!wd_clk);
	clk_enable(wd_clk);

	spin_lock_init(&wdt_lock);

	/* watchdog can be disabled by providing either
	 * "exynos4210_watchdog.sec_pet=0" or
	 * "exynos4210_watchdog.sec_reset=0" to CMDLINE */
	if (!watchdog_reset || !watchdog_pet) {
		clk_disable(wd_clk);
		return -ENODEV;
	}

#if defined(PET_BY_WORKQUEUE)
	watchdog_wq = create_singlethread_workqueue("pet_watchdog");
	watchdog_start();
#elif defined(PET_BY_DIRECT_TIMER)
	init_timer(&pet_watchdog_timer);
	pet_watchdog_timer.function = pet_watchdog_timer_fn;
	watchdog_start();
#else
	hrtimer_init(&watchdog_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	watchdog_timer.function = watchdog_timerfunc;
	watchdog_start();
#endif

	return 0;
}

static int watchdog_suspend(struct device *dev)
{
	watchdog_stop();
	return 0;
}

static int watchdog_resume(struct device *dev)
{
	watchdog_start();
	return 0;
}

static const struct dev_pm_ops watchdog_pm_ops = {
	.suspend_noirq = watchdog_suspend,
	.resume_noirq = watchdog_resume,
};

static struct platform_driver watchdog_driver = {
	.probe = watchdog_probe,
	.driver = {
		   .owner = THIS_MODULE,
		   .name = "watchdog-reset",
		   .pm = &watchdog_pm_ops,
	},
};

static int __init watchdog_init(void)
{
	return platform_driver_register(&watchdog_driver);
}

module_init(watchdog_init);