Commit | Line | Data |
---|---|---|
22ac9232 SB |
1 | /* |
2 | * drivers/watchdog/orion5x_wdt.c | |
3 | * | |
4 | * Watchdog driver for Orion5x processors | |
5 | * | |
6 | * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> | |
7 | * | |
8 | * This file is licensed under the terms of the GNU General Public | |
9 | * License version 2. This program is licensed "as is" without any | |
10 | * warranty of any kind, whether express or implied. | |
11 | */ | |
12 | ||
13 | #include <linux/module.h> | |
14 | #include <linux/moduleparam.h> | |
15 | #include <linux/types.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/fs.h> | |
18 | #include <linux/miscdevice.h> | |
9e058d4f | 19 | #include <linux/platform_device.h> |
22ac9232 SB |
20 | #include <linux/watchdog.h> |
21 | #include <linux/init.h> | |
22 | #include <linux/uaccess.h> | |
23 | #include <linux/io.h> | |
6d0f0dfd | 24 | #include <linux/spinlock.h> |
9e058d4f | 25 | #include <plat/orion5x_wdt.h> |
22ac9232 SB |
26 | |
27 | /* | |
28 | * Watchdog timer block registers. | |
29 | */ | |
30 | #define TIMER_CTRL (TIMER_VIRT_BASE + 0x0000) | |
31 | #define WDT_EN 0x0010 | |
32 | #define WDT_VAL (TIMER_VIRT_BASE + 0x0024) | |
33 | ||
9e058d4f | 34 | #define WDT_MAX_CYCLE_COUNT 0xffffffff |
22ac9232 SB |
35 | #define WDT_IN_USE 0 |
36 | #define WDT_OK_TO_CLOSE 1 | |
37 | ||
38 | static int nowayout = WATCHDOG_NOWAYOUT; | |
9e058d4f TR |
39 | static int heartbeat = -1; /* module parameter (seconds) */ |
40 | static unsigned int wdt_max_duration; /* (seconds) */ | |
41 | static unsigned int wdt_tclk; | |
22ac9232 | 42 | static unsigned long wdt_status; |
6d0f0dfd | 43 | static spinlock_t wdt_lock; |
22ac9232 | 44 | |
df6707b2 TR |
45 | static void orion5x_wdt_ping(void) |
46 | { | |
47 | spin_lock(&wdt_lock); | |
48 | ||
49 | /* Reload watchdog duration */ | |
50 | writel(wdt_tclk * heartbeat, WDT_VAL); | |
51 | ||
52 | spin_unlock(&wdt_lock); | |
53 | } | |
54 | ||
55 | static void orion5x_wdt_enable(void) | |
22ac9232 SB |
56 | { |
57 | u32 reg; | |
58 | ||
6d0f0dfd WVS |
59 | spin_lock(&wdt_lock); |
60 | ||
22ac9232 | 61 | /* Set watchdog duration */ |
9e058d4f | 62 | writel(wdt_tclk * heartbeat, WDT_VAL); |
22ac9232 SB |
63 | |
64 | /* Clear watchdog timer interrupt */ | |
65 | reg = readl(BRIDGE_CAUSE); | |
66 | reg &= ~WDT_INT_REQ; | |
67 | writel(reg, BRIDGE_CAUSE); | |
68 | ||
69 | /* Enable watchdog timer */ | |
70 | reg = readl(TIMER_CTRL); | |
71 | reg |= WDT_EN; | |
72 | writel(reg, TIMER_CTRL); | |
73 | ||
74 | /* Enable reset on watchdog */ | |
75 | reg = readl(CPU_RESET_MASK); | |
76 | reg |= WDT_RESET; | |
77 | writel(reg, CPU_RESET_MASK); | |
6d0f0dfd WVS |
78 | |
79 | spin_unlock(&wdt_lock); | |
22ac9232 SB |
80 | } |
81 | ||
df6707b2 | 82 | static void orion5x_wdt_disable(void) |
22ac9232 SB |
83 | { |
84 | u32 reg; | |
85 | ||
6d0f0dfd WVS |
86 | spin_lock(&wdt_lock); |
87 | ||
22ac9232 SB |
88 | /* Disable reset on watchdog */ |
89 | reg = readl(CPU_RESET_MASK); | |
90 | reg &= ~WDT_RESET; | |
91 | writel(reg, CPU_RESET_MASK); | |
92 | ||
93 | /* Disable watchdog timer */ | |
94 | reg = readl(TIMER_CTRL); | |
95 | reg &= ~WDT_EN; | |
96 | writel(reg, TIMER_CTRL); | |
6d0f0dfd WVS |
97 | |
98 | spin_unlock(&wdt_lock); | |
99 | } | |
100 | ||
101 | static int orion5x_wdt_get_timeleft(int *time_left) | |
102 | { | |
103 | spin_lock(&wdt_lock); | |
9e058d4f | 104 | *time_left = readl(WDT_VAL) / wdt_tclk; |
6d0f0dfd WVS |
105 | spin_unlock(&wdt_lock); |
106 | return 0; | |
22ac9232 SB |
107 | } |
108 | ||
109 | static int orion5x_wdt_open(struct inode *inode, struct file *file) | |
110 | { | |
111 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | |
112 | return -EBUSY; | |
113 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
df6707b2 | 114 | orion5x_wdt_enable(); |
22ac9232 SB |
115 | return nonseekable_open(inode, file); |
116 | } | |
117 | ||
22ac9232 SB |
118 | static ssize_t orion5x_wdt_write(struct file *file, const char *data, |
119 | size_t len, loff_t *ppos) | |
120 | { | |
121 | if (len) { | |
122 | if (!nowayout) { | |
123 | size_t i; | |
124 | ||
125 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
126 | for (i = 0; i != len; i++) { | |
127 | char c; | |
128 | ||
129 | if (get_user(c, data + i)) | |
130 | return -EFAULT; | |
131 | if (c == 'V') | |
132 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
133 | } | |
134 | } | |
df6707b2 | 135 | orion5x_wdt_ping(); |
22ac9232 SB |
136 | } |
137 | return len; | |
138 | } | |
139 | ||
df6707b2 TR |
140 | static int orion5x_wdt_settimeout(int new_time) |
141 | { | |
142 | if ((new_time <= 0) || (new_time > wdt_max_duration)) | |
143 | return -EINVAL; | |
144 | ||
145 | /* Set new watchdog time to be used when | |
146 | * orion5x_wdt_enable() or orion5x_wdt_ping() is called. */ | |
147 | heartbeat = new_time; | |
148 | return 0; | |
149 | } | |
150 | ||
151 | static const struct watchdog_info ident = { | |
22ac9232 SB |
152 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | |
153 | WDIOF_KEEPALIVEPING, | |
154 | .identity = "Orion5x Watchdog", | |
155 | }; | |
156 | ||
22ac9232 SB |
157 | static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd, |
158 | unsigned long arg) | |
159 | { | |
160 | int ret = -ENOTTY; | |
161 | int time; | |
162 | ||
163 | switch (cmd) { | |
164 | case WDIOC_GETSUPPORT: | |
165 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | |
166 | sizeof(ident)) ? -EFAULT : 0; | |
167 | break; | |
168 | ||
169 | case WDIOC_GETSTATUS: | |
170 | case WDIOC_GETBOOTSTATUS: | |
171 | ret = put_user(0, (int *)arg); | |
172 | break; | |
173 | ||
174 | case WDIOC_KEEPALIVE: | |
df6707b2 | 175 | orion5x_wdt_ping(); |
22ac9232 SB |
176 | ret = 0; |
177 | break; | |
178 | ||
179 | case WDIOC_SETTIMEOUT: | |
180 | ret = get_user(time, (int *)arg); | |
181 | if (ret) | |
182 | break; | |
183 | ||
df6707b2 | 184 | if (orion5x_wdt_settimeout(time)) { |
22ac9232 SB |
185 | ret = -EINVAL; |
186 | break; | |
187 | } | |
df6707b2 | 188 | orion5x_wdt_ping(); |
22ac9232 SB |
189 | /* Fall through */ |
190 | ||
191 | case WDIOC_GETTIMEOUT: | |
192 | ret = put_user(heartbeat, (int *)arg); | |
193 | break; | |
194 | ||
195 | case WDIOC_GETTIMELEFT: | |
196 | if (orion5x_wdt_get_timeleft(&time)) { | |
197 | ret = -EINVAL; | |
198 | break; | |
199 | } | |
200 | ret = put_user(time, (int *)arg); | |
201 | break; | |
202 | } | |
203 | return ret; | |
204 | } | |
205 | ||
206 | static int orion5x_wdt_release(struct inode *inode, struct file *file) | |
207 | { | |
208 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | |
df6707b2 | 209 | orion5x_wdt_disable(); |
22ac9232 SB |
210 | else |
211 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | |
212 | "timer will not stop\n"); | |
213 | clear_bit(WDT_IN_USE, &wdt_status); | |
214 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
215 | ||
216 | return 0; | |
217 | } | |
218 | ||
219 | ||
220 | static const struct file_operations orion5x_wdt_fops = { | |
221 | .owner = THIS_MODULE, | |
222 | .llseek = no_llseek, | |
223 | .write = orion5x_wdt_write, | |
224 | .unlocked_ioctl = orion5x_wdt_ioctl, | |
225 | .open = orion5x_wdt_open, | |
226 | .release = orion5x_wdt_release, | |
227 | }; | |
228 | ||
229 | static struct miscdevice orion5x_wdt_miscdev = { | |
230 | .minor = WATCHDOG_MINOR, | |
231 | .name = "watchdog", | |
232 | .fops = &orion5x_wdt_fops, | |
233 | }; | |
234 | ||
9e058d4f | 235 | static int __devinit orion5x_wdt_probe(struct platform_device *pdev) |
22ac9232 | 236 | { |
9e058d4f | 237 | struct orion5x_wdt_platform_data *pdata = pdev->dev.platform_data; |
22ac9232 SB |
238 | int ret; |
239 | ||
9e058d4f TR |
240 | if (pdata) { |
241 | wdt_tclk = pdata->tclk; | |
242 | } else { | |
243 | printk(KERN_ERR "Orion5x Watchdog misses platform data\n"); | |
244 | return -ENODEV; | |
245 | } | |
246 | ||
247 | if (orion5x_wdt_miscdev.parent) | |
248 | return -EBUSY; | |
249 | orion5x_wdt_miscdev.parent = &pdev->dev; | |
250 | ||
251 | wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; | |
df6707b2 | 252 | if (orion5x_wdt_settimeout(heartbeat)) |
9e058d4f | 253 | heartbeat = wdt_max_duration; |
6d0f0dfd | 254 | |
22ac9232 | 255 | ret = misc_register(&orion5x_wdt_miscdev); |
9e058d4f TR |
256 | if (ret) |
257 | return ret; | |
258 | ||
259 | printk(KERN_INFO "Orion5x Watchdog Timer: Initial timeout %d sec%s\n", | |
260 | heartbeat, nowayout ? ", nowayout" : ""); | |
261 | return 0; | |
262 | } | |
263 | ||
264 | static int __devexit orion5x_wdt_remove(struct platform_device *pdev) | |
265 | { | |
266 | int ret; | |
267 | ||
268 | if (test_bit(WDT_IN_USE, &wdt_status)) { | |
df6707b2 | 269 | orion5x_wdt_disable(); |
9e058d4f TR |
270 | clear_bit(WDT_IN_USE, &wdt_status); |
271 | } | |
272 | ||
273 | ret = misc_deregister(&orion5x_wdt_miscdev); | |
274 | if (!ret) | |
275 | orion5x_wdt_miscdev.parent = NULL; | |
22ac9232 SB |
276 | |
277 | return ret; | |
278 | } | |
279 | ||
df6707b2 TR |
280 | static void orion5x_wdt_shutdown(struct platform_device *pdev) |
281 | { | |
282 | if (test_bit(WDT_IN_USE, &wdt_status)) | |
283 | orion5x_wdt_disable(); | |
284 | } | |
285 | ||
9e058d4f TR |
286 | static struct platform_driver orion5x_wdt_driver = { |
287 | .probe = orion5x_wdt_probe, | |
288 | .remove = __devexit_p(orion5x_wdt_remove), | |
df6707b2 | 289 | .shutdown = orion5x_wdt_shutdown, |
9e058d4f TR |
290 | .driver = { |
291 | .owner = THIS_MODULE, | |
292 | .name = "orion5x_wdt", | |
293 | }, | |
294 | }; | |
295 | ||
296 | static int __init orion5x_wdt_init(void) | |
297 | { | |
298 | spin_lock_init(&wdt_lock); | |
299 | return platform_driver_register(&orion5x_wdt_driver); | |
300 | } | |
301 | ||
22ac9232 SB |
302 | static void __exit orion5x_wdt_exit(void) |
303 | { | |
9e058d4f | 304 | platform_driver_unregister(&orion5x_wdt_driver); |
22ac9232 SB |
305 | } |
306 | ||
307 | module_init(orion5x_wdt_init); | |
308 | module_exit(orion5x_wdt_exit); | |
309 | ||
310 | MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); | |
311 | MODULE_DESCRIPTION("Orion5x Processor Watchdog"); | |
312 | ||
313 | module_param(heartbeat, int, 0); | |
df6707b2 | 314 | MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); |
22ac9232 SB |
315 | |
316 | module_param(nowayout, int, 0); | |
df6707b2 TR |
317 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" |
318 | __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); | |
22ac9232 SB |
319 | |
320 | MODULE_LICENSE("GPL"); | |
321 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |