1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Intel 21285 watchdog driver |
4 | * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998 |
5 | * |
6 | * based on |
7 | * |
8 | * SoftDog 0.05: A Software Watchdog Device |
9 | * |
10 | * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, |
11 | * All Rights Reserved. |
12 | */ |
13 | |
14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
15 | |
16 | #include <linux/module.h> |
17 | #include <linux/moduleparam.h> |
18 | #include <linux/types.h> |
19 | #include <linux/kernel.h> |
20 | #include <linux/fs.h> |
21 | #include <linux/mm.h> |
22 | #include <linux/miscdevice.h> |
23 | #include <linux/watchdog.h> |
24 | #include <linux/reboot.h> |
25 | #include <linux/init.h> |
26 | #include <linux/interrupt.h> |
27 | #include <linux/uaccess.h> |
28 | #include <linux/irq.h> |
29 | #include <mach/hardware.h> |
30 | |
31 | #include <asm/mach-types.h> |
32 | #include <asm/system_info.h> |
33 | #include <asm/hardware/dec21285.h> |
34 | |
35 | /* |
36 | * Define this to stop the watchdog actually rebooting the machine. |
37 | */ |
38 | #undef ONLY_TESTING |
39 | |
40 | static unsigned int soft_margin = 60; /* in seconds */ |
41 | static unsigned int reload; |
42 | static unsigned long timer_alive; |
43 | |
44 | #ifdef ONLY_TESTING |
45 | /* |
46 | * If the timer expires.. |
47 | */ |
48 | static void watchdog_fire(int irq, void *dev_id) |
49 | { |
50 | pr_crit("Would Reboot\n" ); |
51 | *CSR_TIMER4_CNTL = 0; |
52 | *CSR_TIMER4_CLR = 0; |
53 | } |
54 | #endif |
55 | |
56 | /* |
57 | * Refresh the timer. |
58 | */ |
59 | static void watchdog_ping(void) |
60 | { |
61 | *CSR_TIMER4_LOAD = reload; |
62 | } |
63 | |
64 | /* |
65 | * Allow only one person to hold it open |
66 | */ |
67 | static int watchdog_open(struct inode *inode, struct file *file) |
68 | { |
69 | int ret; |
70 | |
71 | if (*CSR_SA110_CNTL & (1 << 13)) |
72 | return -EBUSY; |
73 | |
74 | if (test_and_set_bit(nr: 1, addr: &timer_alive)) |
75 | return -EBUSY; |
76 | |
77 | reload = soft_margin * (mem_fclk_21285 / 256); |
78 | |
79 | *CSR_TIMER4_CLR = 0; |
80 | watchdog_ping(); |
81 | *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD |
82 | | TIMER_CNTL_DIV256; |
83 | |
84 | #ifdef ONLY_TESTING |
85 | ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog" , NULL); |
86 | if (ret) { |
87 | *CSR_TIMER4_CNTL = 0; |
88 | clear_bit(1, &timer_alive); |
89 | } |
90 | #else |
91 | /* |
92 | * Setting this bit is irreversible; once enabled, there is |
93 | * no way to disable the watchdog. |
94 | */ |
95 | *CSR_SA110_CNTL |= 1 << 13; |
96 | |
97 | ret = 0; |
98 | #endif |
99 | stream_open(inode, filp: file); |
100 | return ret; |
101 | } |
102 | |
103 | /* |
104 | * Shut off the timer. |
105 | * Note: if we really have enabled the watchdog, there |
106 | * is no way to turn off. |
107 | */ |
108 | static int watchdog_release(struct inode *inode, struct file *file) |
109 | { |
110 | #ifdef ONLY_TESTING |
111 | free_irq(IRQ_TIMER4, NULL); |
112 | clear_bit(1, &timer_alive); |
113 | #endif |
114 | return 0; |
115 | } |
116 | |
117 | static ssize_t watchdog_write(struct file *file, const char __user *data, |
118 | size_t len, loff_t *ppos) |
119 | { |
120 | /* |
121 | * Refresh the timer. |
122 | */ |
123 | if (len) |
124 | watchdog_ping(); |
125 | |
126 | return len; |
127 | } |
128 | |
129 | static const struct watchdog_info ident = { |
130 | .options = WDIOF_SETTIMEOUT, |
131 | .identity = "Footbridge Watchdog" , |
132 | }; |
133 | |
134 | static long watchdog_ioctl(struct file *file, unsigned int cmd, |
135 | unsigned long arg) |
136 | { |
137 | int __user *int_arg = (int __user *)arg; |
138 | int new_margin, ret = -ENOTTY; |
139 | |
140 | switch (cmd) { |
141 | case WDIOC_GETSUPPORT: |
142 | ret = 0; |
143 | if (copy_to_user(to: (void __user *)arg, from: &ident, n: sizeof(ident))) |
144 | ret = -EFAULT; |
145 | break; |
146 | |
147 | case WDIOC_GETSTATUS: |
148 | case WDIOC_GETBOOTSTATUS: |
149 | ret = put_user(0, int_arg); |
150 | break; |
151 | |
152 | case WDIOC_KEEPALIVE: |
153 | watchdog_ping(); |
154 | ret = 0; |
155 | break; |
156 | |
157 | case WDIOC_SETTIMEOUT: |
158 | ret = get_user(new_margin, int_arg); |
159 | if (ret) |
160 | break; |
161 | |
162 | /* Arbitrary, can't find the card's limits */ |
163 | if (new_margin < 0 || new_margin > 60) { |
164 | ret = -EINVAL; |
165 | break; |
166 | } |
167 | |
168 | soft_margin = new_margin; |
169 | reload = soft_margin * (mem_fclk_21285 / 256); |
170 | watchdog_ping(); |
171 | fallthrough; |
172 | case WDIOC_GETTIMEOUT: |
173 | ret = put_user(soft_margin, int_arg); |
174 | break; |
175 | } |
176 | return ret; |
177 | } |
178 | |
179 | static const struct file_operations watchdog_fops = { |
180 | .owner = THIS_MODULE, |
181 | .llseek = no_llseek, |
182 | .write = watchdog_write, |
183 | .unlocked_ioctl = watchdog_ioctl, |
184 | .compat_ioctl = compat_ptr_ioctl, |
185 | .open = watchdog_open, |
186 | .release = watchdog_release, |
187 | }; |
188 | |
189 | static struct miscdevice watchdog_miscdev = { |
190 | .minor = WATCHDOG_MINOR, |
191 | .name = "watchdog" , |
192 | .fops = &watchdog_fops, |
193 | }; |
194 | |
195 | static int __init (void) |
196 | { |
197 | int retval; |
198 | |
199 | if (machine_is_netwinder()) |
200 | return -ENODEV; |
201 | |
202 | retval = misc_register(misc: &watchdog_miscdev); |
203 | if (retval < 0) |
204 | return retval; |
205 | |
206 | pr_info("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n" , |
207 | soft_margin); |
208 | |
209 | return 0; |
210 | } |
211 | |
212 | static void __exit (void) |
213 | { |
214 | misc_deregister(misc: &watchdog_miscdev); |
215 | } |
216 | |
217 | MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>" ); |
218 | MODULE_DESCRIPTION("Footbridge watchdog driver" ); |
219 | MODULE_LICENSE("GPL" ); |
220 | |
221 | module_param(soft_margin, int, 0); |
222 | MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds" ); |
223 | |
224 | module_init(footbridge_watchdog_init); |
225 | module_exit(footbridge_watchdog_exit); |
226 | |