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 SB |
44 | |
45 | static void wdt_enable(void) | |
46 | { | |
47 | u32 reg; | |
48 | ||
6d0f0dfd WVS |
49 | spin_lock(&wdt_lock); |
50 | ||
22ac9232 | 51 | /* Set watchdog duration */ |
9e058d4f | 52 | writel(wdt_tclk * heartbeat, WDT_VAL); |
22ac9232 SB |
53 | |
54 | /* Clear watchdog timer interrupt */ | |
55 | reg = readl(BRIDGE_CAUSE); | |
56 | reg &= ~WDT_INT_REQ; | |
57 | writel(reg, BRIDGE_CAUSE); | |
58 | ||
59 | /* Enable watchdog timer */ | |
60 | reg = readl(TIMER_CTRL); | |
61 | reg |= WDT_EN; | |
62 | writel(reg, TIMER_CTRL); | |
63 | ||
64 | /* Enable reset on watchdog */ | |
65 | reg = readl(CPU_RESET_MASK); | |
66 | reg |= WDT_RESET; | |
67 | writel(reg, CPU_RESET_MASK); | |
6d0f0dfd WVS |
68 | |
69 | spin_unlock(&wdt_lock); | |
22ac9232 SB |
70 | } |
71 | ||
72 | static void wdt_disable(void) | |
73 | { | |
74 | u32 reg; | |
75 | ||
6d0f0dfd WVS |
76 | spin_lock(&wdt_lock); |
77 | ||
22ac9232 SB |
78 | /* Disable reset on watchdog */ |
79 | reg = readl(CPU_RESET_MASK); | |
80 | reg &= ~WDT_RESET; | |
81 | writel(reg, CPU_RESET_MASK); | |
82 | ||
83 | /* Disable watchdog timer */ | |
84 | reg = readl(TIMER_CTRL); | |
85 | reg &= ~WDT_EN; | |
86 | writel(reg, TIMER_CTRL); | |
6d0f0dfd WVS |
87 | |
88 | spin_unlock(&wdt_lock); | |
89 | } | |
90 | ||
91 | static int orion5x_wdt_get_timeleft(int *time_left) | |
92 | { | |
93 | spin_lock(&wdt_lock); | |
9e058d4f | 94 | *time_left = readl(WDT_VAL) / wdt_tclk; |
6d0f0dfd WVS |
95 | spin_unlock(&wdt_lock); |
96 | return 0; | |
22ac9232 SB |
97 | } |
98 | ||
99 | static int orion5x_wdt_open(struct inode *inode, struct file *file) | |
100 | { | |
101 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | |
102 | return -EBUSY; | |
103 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
104 | wdt_enable(); | |
105 | return nonseekable_open(inode, file); | |
106 | } | |
107 | ||
22ac9232 SB |
108 | static ssize_t orion5x_wdt_write(struct file *file, const char *data, |
109 | size_t len, loff_t *ppos) | |
110 | { | |
111 | if (len) { | |
112 | if (!nowayout) { | |
113 | size_t i; | |
114 | ||
115 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
116 | for (i = 0; i != len; i++) { | |
117 | char c; | |
118 | ||
119 | if (get_user(c, data + i)) | |
120 | return -EFAULT; | |
121 | if (c == 'V') | |
122 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
123 | } | |
124 | } | |
125 | wdt_enable(); | |
126 | } | |
127 | return len; | |
128 | } | |
129 | ||
130 | static struct watchdog_info ident = { | |
131 | .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | | |
132 | WDIOF_KEEPALIVEPING, | |
133 | .identity = "Orion5x Watchdog", | |
134 | }; | |
135 | ||
136 | ||
137 | static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd, | |
138 | unsigned long arg) | |
139 | { | |
140 | int ret = -ENOTTY; | |
141 | int time; | |
142 | ||
143 | switch (cmd) { | |
144 | case WDIOC_GETSUPPORT: | |
145 | ret = copy_to_user((struct watchdog_info *)arg, &ident, | |
146 | sizeof(ident)) ? -EFAULT : 0; | |
147 | break; | |
148 | ||
149 | case WDIOC_GETSTATUS: | |
150 | case WDIOC_GETBOOTSTATUS: | |
151 | ret = put_user(0, (int *)arg); | |
152 | break; | |
153 | ||
154 | case WDIOC_KEEPALIVE: | |
155 | wdt_enable(); | |
156 | ret = 0; | |
157 | break; | |
158 | ||
159 | case WDIOC_SETTIMEOUT: | |
160 | ret = get_user(time, (int *)arg); | |
161 | if (ret) | |
162 | break; | |
163 | ||
9e058d4f | 164 | if (time <= 0 || time > wdt_max_duration) { |
22ac9232 SB |
165 | ret = -EINVAL; |
166 | break; | |
167 | } | |
168 | heartbeat = time; | |
169 | wdt_enable(); | |
170 | /* Fall through */ | |
171 | ||
172 | case WDIOC_GETTIMEOUT: | |
173 | ret = put_user(heartbeat, (int *)arg); | |
174 | break; | |
175 | ||
176 | case WDIOC_GETTIMELEFT: | |
177 | if (orion5x_wdt_get_timeleft(&time)) { | |
178 | ret = -EINVAL; | |
179 | break; | |
180 | } | |
181 | ret = put_user(time, (int *)arg); | |
182 | break; | |
183 | } | |
184 | return ret; | |
185 | } | |
186 | ||
187 | static int orion5x_wdt_release(struct inode *inode, struct file *file) | |
188 | { | |
189 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | |
190 | wdt_disable(); | |
191 | else | |
192 | printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " | |
193 | "timer will not stop\n"); | |
194 | clear_bit(WDT_IN_USE, &wdt_status); | |
195 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | ||
201 | static const struct file_operations orion5x_wdt_fops = { | |
202 | .owner = THIS_MODULE, | |
203 | .llseek = no_llseek, | |
204 | .write = orion5x_wdt_write, | |
205 | .unlocked_ioctl = orion5x_wdt_ioctl, | |
206 | .open = orion5x_wdt_open, | |
207 | .release = orion5x_wdt_release, | |
208 | }; | |
209 | ||
210 | static struct miscdevice orion5x_wdt_miscdev = { | |
211 | .minor = WATCHDOG_MINOR, | |
212 | .name = "watchdog", | |
213 | .fops = &orion5x_wdt_fops, | |
214 | }; | |
215 | ||
9e058d4f | 216 | static int __devinit orion5x_wdt_probe(struct platform_device *pdev) |
22ac9232 | 217 | { |
9e058d4f | 218 | struct orion5x_wdt_platform_data *pdata = pdev->dev.platform_data; |
22ac9232 SB |
219 | int ret; |
220 | ||
9e058d4f TR |
221 | if (pdata) { |
222 | wdt_tclk = pdata->tclk; | |
223 | } else { | |
224 | printk(KERN_ERR "Orion5x Watchdog misses platform data\n"); | |
225 | return -ENODEV; | |
226 | } | |
227 | ||
228 | if (orion5x_wdt_miscdev.parent) | |
229 | return -EBUSY; | |
230 | orion5x_wdt_miscdev.parent = &pdev->dev; | |
231 | ||
232 | wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; | |
233 | if (heartbeat <= 0 || heartbeat > wdt_max_duration) | |
234 | heartbeat = wdt_max_duration; | |
6d0f0dfd | 235 | |
22ac9232 | 236 | ret = misc_register(&orion5x_wdt_miscdev); |
9e058d4f TR |
237 | if (ret) |
238 | return ret; | |
239 | ||
240 | printk(KERN_INFO "Orion5x Watchdog Timer: Initial timeout %d sec%s\n", | |
241 | heartbeat, nowayout ? ", nowayout" : ""); | |
242 | return 0; | |
243 | } | |
244 | ||
245 | static int __devexit orion5x_wdt_remove(struct platform_device *pdev) | |
246 | { | |
247 | int ret; | |
248 | ||
249 | if (test_bit(WDT_IN_USE, &wdt_status)) { | |
250 | wdt_disable(); | |
251 | clear_bit(WDT_IN_USE, &wdt_status); | |
252 | } | |
253 | ||
254 | ret = misc_deregister(&orion5x_wdt_miscdev); | |
255 | if (!ret) | |
256 | orion5x_wdt_miscdev.parent = NULL; | |
22ac9232 SB |
257 | |
258 | return ret; | |
259 | } | |
260 | ||
9e058d4f TR |
261 | static struct platform_driver orion5x_wdt_driver = { |
262 | .probe = orion5x_wdt_probe, | |
263 | .remove = __devexit_p(orion5x_wdt_remove), | |
264 | .driver = { | |
265 | .owner = THIS_MODULE, | |
266 | .name = "orion5x_wdt", | |
267 | }, | |
268 | }; | |
269 | ||
270 | static int __init orion5x_wdt_init(void) | |
271 | { | |
272 | spin_lock_init(&wdt_lock); | |
273 | return platform_driver_register(&orion5x_wdt_driver); | |
274 | } | |
275 | ||
22ac9232 SB |
276 | static void __exit orion5x_wdt_exit(void) |
277 | { | |
9e058d4f | 278 | platform_driver_unregister(&orion5x_wdt_driver); |
22ac9232 SB |
279 | } |
280 | ||
281 | module_init(orion5x_wdt_init); | |
282 | module_exit(orion5x_wdt_exit); | |
283 | ||
284 | MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); | |
285 | MODULE_DESCRIPTION("Orion5x Processor Watchdog"); | |
286 | ||
287 | module_param(heartbeat, int, 0); | |
9e058d4f | 288 | MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds"); |
22ac9232 SB |
289 | |
290 | module_param(nowayout, int, 0); | |
291 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); | |
292 | ||
293 | MODULE_LICENSE("GPL"); | |
294 | MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |