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