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(1, &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, 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((void __user *)arg, &ident, 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 /* Fall through */ 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 footbridge_watchdog_init(void) 196 { 197 int retval; 198 199 if (machine_is_netwinder()) 200 return -ENODEV; 201 202 retval = misc_register(&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 if (machine_is_cats()) 210 pr_warn("Warning: Watchdog reset may not work on this machine\n"); 211 return 0; 212 } 213 214 static void __exit footbridge_watchdog_exit(void) 215 { 216 misc_deregister(&watchdog_miscdev); 217 } 218 219 MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>"); 220 MODULE_DESCRIPTION("Footbridge watchdog driver"); 221 MODULE_LICENSE("GPL"); 222 223 module_param(soft_margin, int, 0); 224 MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); 225 226 module_init(footbridge_watchdog_init); 227 module_exit(footbridge_watchdog_exit); 228