Commit | Line | Data |
---|---|---|
3b20eb23 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
70c14ff0 DW |
2 | /* |
3 | * drivers/char/watchdog/iop_wdt.c | |
4 | * | |
5 | * WDT driver for Intel I/O Processors | |
6 | * Copyright (C) 2005, Intel Corporation. | |
7 | * | |
8 | * Based on ixp4xx driver, Copyright 2004 (c) MontaVista, Software, Inc. | |
9 | * | |
70c14ff0 DW |
10 | * Curt E Bruns <curt.e.bruns@intel.com> |
11 | * Peter Milne <peter.milne@d-tacq.com> | |
12 | * Dan Williams <dan.j.williams@intel.com> | |
13 | */ | |
14 | ||
27c766aa JP |
15 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
16 | ||
70c14ff0 DW |
17 | #include <linux/module.h> |
18 | #include <linux/kernel.h> | |
19 | #include <linux/fs.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/device.h> | |
22 | #include <linux/miscdevice.h> | |
23 | #include <linux/watchdog.h> | |
24 | #include <linux/uaccess.h> | |
a09e64fb | 25 | #include <mach/hardware.h> |
70c14ff0 | 26 | |
86a1e189 | 27 | static bool nowayout = WATCHDOG_NOWAYOUT; |
70c14ff0 DW |
28 | static unsigned long wdt_status; |
29 | static unsigned long boot_status; | |
1334f329 | 30 | static DEFINE_SPINLOCK(wdt_lock); |
70c14ff0 DW |
31 | |
32 | #define WDT_IN_USE 0 | |
33 | #define WDT_OK_TO_CLOSE 1 | |
34 | #define WDT_ENABLED 2 | |
35 | ||
36 | static unsigned long iop_watchdog_timeout(void) | |
37 | { | |
38 | return (0xffffffffUL / get_iop_tick_rate()); | |
39 | } | |
40 | ||
41 | /** | |
42 | * wdt_supports_disable - determine if we are accessing a iop13xx watchdog | |
43 | * or iop3xx by whether it has a disable command | |
44 | */ | |
45 | static int wdt_supports_disable(void) | |
46 | { | |
47 | int can_disable; | |
48 | ||
49 | if (IOP_WDTCR_EN_ARM != IOP_WDTCR_DIS_ARM) | |
50 | can_disable = 1; | |
51 | else | |
52 | can_disable = 0; | |
53 | ||
54 | return can_disable; | |
55 | } | |
56 | ||
57 | static void wdt_enable(void) | |
58 | { | |
59 | /* Arm and enable the Timer to starting counting down from 0xFFFF.FFFF | |
60 | * Takes approx. 10.7s to timeout | |
61 | */ | |
02e3814e | 62 | spin_lock(&wdt_lock); |
70c14ff0 DW |
63 | write_wdtcr(IOP_WDTCR_EN_ARM); |
64 | write_wdtcr(IOP_WDTCR_EN); | |
02e3814e | 65 | spin_unlock(&wdt_lock); |
70c14ff0 DW |
66 | } |
67 | ||
68 | /* returns 0 if the timer was successfully disabled */ | |
69 | static int wdt_disable(void) | |
70 | { | |
71 | /* Stop Counting */ | |
72 | if (wdt_supports_disable()) { | |
02e3814e | 73 | spin_lock(&wdt_lock); |
70c14ff0 DW |
74 | write_wdtcr(IOP_WDTCR_DIS_ARM); |
75 | write_wdtcr(IOP_WDTCR_DIS); | |
76 | clear_bit(WDT_ENABLED, &wdt_status); | |
02e3814e | 77 | spin_unlock(&wdt_lock); |
27c766aa | 78 | pr_info("Disabled\n"); |
70c14ff0 DW |
79 | return 0; |
80 | } else | |
81 | return 1; | |
82 | } | |
83 | ||
84 | static int iop_wdt_open(struct inode *inode, struct file *file) | |
85 | { | |
86 | if (test_and_set_bit(WDT_IN_USE, &wdt_status)) | |
87 | return -EBUSY; | |
88 | ||
89 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
70c14ff0 | 90 | wdt_enable(); |
70c14ff0 | 91 | set_bit(WDT_ENABLED, &wdt_status); |
c5bf68fe | 92 | return stream_open(inode, file); |
70c14ff0 DW |
93 | } |
94 | ||
02e3814e | 95 | static ssize_t iop_wdt_write(struct file *file, const char *data, size_t len, |
70c14ff0 DW |
96 | loff_t *ppos) |
97 | { | |
98 | if (len) { | |
99 | if (!nowayout) { | |
100 | size_t i; | |
101 | ||
102 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
103 | ||
104 | for (i = 0; i != len; i++) { | |
105 | char c; | |
106 | ||
107 | if (get_user(c, data + i)) | |
108 | return -EFAULT; | |
109 | if (c == 'V') | |
110 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
111 | } | |
112 | } | |
113 | wdt_enable(); | |
114 | } | |
70c14ff0 DW |
115 | return len; |
116 | } | |
117 | ||
02e3814e | 118 | static const struct watchdog_info ident = { |
70c14ff0 DW |
119 | .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, |
120 | .identity = "iop watchdog", | |
121 | }; | |
122 | ||
02e3814e AC |
123 | static long iop_wdt_ioctl(struct file *file, |
124 | unsigned int cmd, unsigned long arg) | |
70c14ff0 DW |
125 | { |
126 | int options; | |
127 | int ret = -ENOTTY; | |
02e3814e | 128 | int __user *argp = (int __user *)arg; |
70c14ff0 DW |
129 | |
130 | switch (cmd) { | |
131 | case WDIOC_GETSUPPORT: | |
e04ab958 | 132 | if (copy_to_user(argp, &ident, sizeof(ident))) |
70c14ff0 DW |
133 | ret = -EFAULT; |
134 | else | |
135 | ret = 0; | |
136 | break; | |
137 | ||
138 | case WDIOC_GETSTATUS: | |
02e3814e | 139 | ret = put_user(0, argp); |
70c14ff0 DW |
140 | break; |
141 | ||
142 | case WDIOC_GETBOOTSTATUS: | |
02e3814e | 143 | ret = put_user(boot_status, argp); |
70c14ff0 DW |
144 | break; |
145 | ||
146 | case WDIOC_SETOPTIONS: | |
147 | if (get_user(options, (int *)arg)) | |
148 | return -EFAULT; | |
149 | ||
150 | if (options & WDIOS_DISABLECARD) { | |
151 | if (!nowayout) { | |
152 | if (wdt_disable() == 0) { | |
153 | set_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
154 | ret = 0; | |
155 | } else | |
156 | ret = -ENXIO; | |
157 | } else | |
158 | ret = 0; | |
159 | } | |
70c14ff0 DW |
160 | if (options & WDIOS_ENABLECARD) { |
161 | wdt_enable(); | |
162 | ret = 0; | |
163 | } | |
164 | break; | |
70c14ff0 | 165 | |
0c06090c WVS |
166 | case WDIOC_KEEPALIVE: |
167 | wdt_enable(); | |
168 | ret = 0; | |
169 | break; | |
170 | ||
171 | case WDIOC_GETTIMEOUT: | |
172 | ret = put_user(iop_watchdog_timeout(), argp); | |
173 | break; | |
70c14ff0 | 174 | } |
70c14ff0 DW |
175 | return ret; |
176 | } | |
177 | ||
178 | static int iop_wdt_release(struct inode *inode, struct file *file) | |
179 | { | |
180 | int state = 1; | |
181 | if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) | |
182 | if (test_bit(WDT_ENABLED, &wdt_status)) | |
183 | state = wdt_disable(); | |
184 | ||
4b512d26 | 185 | /* if the timer is not disabled reload and notify that we are still |
70c14ff0 DW |
186 | * going down |
187 | */ | |
188 | if (state != 0) { | |
189 | wdt_enable(); | |
27c766aa JP |
190 | pr_crit("Device closed unexpectedly - reset in %lu seconds\n", |
191 | iop_watchdog_timeout()); | |
70c14ff0 DW |
192 | } |
193 | ||
194 | clear_bit(WDT_IN_USE, &wdt_status); | |
195 | clear_bit(WDT_OK_TO_CLOSE, &wdt_status); | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | static const struct file_operations iop_wdt_fops = { | |
201 | .owner = THIS_MODULE, | |
202 | .llseek = no_llseek, | |
203 | .write = iop_wdt_write, | |
02e3814e | 204 | .unlocked_ioctl = iop_wdt_ioctl, |
b6dfb247 | 205 | .compat_ioctl = compat_ptr_ioctl, |
70c14ff0 DW |
206 | .open = iop_wdt_open, |
207 | .release = iop_wdt_release, | |
208 | }; | |
209 | ||
210 | static struct miscdevice iop_wdt_miscdev = { | |
211 | .minor = WATCHDOG_MINOR, | |
212 | .name = "watchdog", | |
213 | .fops = &iop_wdt_fops, | |
214 | }; | |
215 | ||
216 | static int __init iop_wdt_init(void) | |
217 | { | |
218 | int ret; | |
219 | ||
70c14ff0 DW |
220 | /* check if the reset was caused by the watchdog timer */ |
221 | boot_status = (read_rcsr() & IOP_RCSR_WDT) ? WDIOF_CARDRESET : 0; | |
222 | ||
223 | /* Configure Watchdog Timeout to cause an Internal Bus (IB) Reset | |
224 | * NOTE: An IB Reset will Reset both cores in the IOP342 | |
225 | */ | |
226 | write_wdtsr(IOP13XX_WDTCR_IB_RESET); | |
227 | ||
02e3814e AC |
228 | /* Register after we have the device set up so we cannot race |
229 | with an open */ | |
230 | ret = misc_register(&iop_wdt_miscdev); | |
231 | if (ret == 0) | |
27c766aa | 232 | pr_info("timeout %lu sec\n", iop_watchdog_timeout()); |
02e3814e | 233 | |
70c14ff0 DW |
234 | return ret; |
235 | } | |
236 | ||
237 | static void __exit iop_wdt_exit(void) | |
238 | { | |
239 | misc_deregister(&iop_wdt_miscdev); | |
240 | } | |
241 | ||
242 | module_init(iop_wdt_init); | |
243 | module_exit(iop_wdt_exit); | |
244 | ||
86a1e189 | 245 | module_param(nowayout, bool, 0); |
70c14ff0 DW |
246 | MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); |
247 | ||
248 | MODULE_AUTHOR("Curt E Bruns <curt.e.bruns@intel.com>"); | |
249 | MODULE_DESCRIPTION("iop watchdog timer driver"); | |
250 | MODULE_LICENSE("GPL"); |