1/* 2 * drivers/char/watchdog/ixp2000_wdt.c 3 * 4 * Watchdog driver for Intel IXP2000 network processors 5 * 6 * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek. 7 * The original version carries these notices: 8 * 9 * Author: Deepak Saxena <dsaxena@plexity.net> 10 * 11 * Copyright 2004 (c) MontaVista, Software, Inc. 12 * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> 13 * 14 * This file is licensed under the terms of the GNU General Public 15 * License version 2. This program is licensed "as is" without any 16 * warranty of any kind, whether express or implied. 17 */ 18 19#include <linux/module.h> 20#include <linux/moduleparam.h> 21#include <linux/types.h> 22#include <linux/kernel.h> 23#include <linux/fs.h> 24#include <linux/miscdevice.h> 25#include <linux/watchdog.h> 26#include <linux/init.h> 27#include <linux/bitops.h> 28 29#include <asm/hardware.h> 30#include <asm/uaccess.h> 31 32static int nowayout = WATCHDOG_NOWAYOUT; 33static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ 34static unsigned long wdt_status; 35 36#define WDT_IN_USE 0 37#define WDT_OK_TO_CLOSE 1 38 39static unsigned long wdt_tick_rate; 40 41static void 42wdt_enable(void) 43{ 44 ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); 45 ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); 46 ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); 47 ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); 48} 49 50static void 51wdt_disable(void) 52{ 53 ixp2000_reg_write(IXP2000_T4_CTL, 0); 54} 55 56static void 57wdt_keepalive(void) 58{ 59 ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); 60} 61 62static int 63ixp2000_wdt_open(struct inode *inode, struct file *file) 64{ 65 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 66 return -EBUSY; 67 68 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 69 70 wdt_enable(); 71 72 return nonseekable_open(inode, file); 73} 74 75static ssize_t 76ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) 77{ 78 if (len) { 79 if (!nowayout) { 80 size_t i; 81 82 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 83 84 for (i = 0; i != len; i++) { 85 char c; 86 87 if (get_user(c, data + i)) 88 return -EFAULT; 89 if (c == 'V') 90 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 91 } 92 } 93 wdt_keepalive(); 94 } 95 96 return len; 97} 98 99 100static struct watchdog_info ident = { 101 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | 102 WDIOF_KEEPALIVEPING, 103 .identity = "IXP2000 Watchdog", 104}; 105 106static int 107ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, 108 unsigned long arg) 109{ 110 int ret = -ENOTTY; 111 int time; 112 113 switch (cmd) { 114 case WDIOC_GETSUPPORT: 115 ret = copy_to_user((struct watchdog_info *)arg, &ident, 116 sizeof(ident)) ? -EFAULT : 0; 117 break; 118 119 case WDIOC_GETSTATUS: 120 ret = put_user(0, (int *)arg); 121 break; 122 123 case WDIOC_GETBOOTSTATUS: 124 ret = put_user(0, (int *)arg); 125 break; 126 127 case WDIOC_SETTIMEOUT: 128 ret = get_user(time, (int *)arg); 129 if (ret) 130 break; 131 132 if (time <= 0 || time > 60) { 133 ret = -EINVAL; 134 break; 135 } 136 137 heartbeat = time; 138 wdt_keepalive(); 139 /* Fall through */ 140 141 case WDIOC_GETTIMEOUT: 142 ret = put_user(heartbeat, (int *)arg); 143 break; 144 145 case WDIOC_KEEPALIVE: 146 wdt_enable(); 147 ret = 0; 148 break; 149 } 150 151 return ret; 152} 153 154static int 155ixp2000_wdt_release(struct inode *inode, struct file *file) 156{ 157 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { 158 wdt_disable(); 159 } else { 160 printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " 161 "timer will not stop\n"); 162 } 163 164 clear_bit(WDT_IN_USE, &wdt_status); 165 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 166 167 return 0; 168} 169 170 171static const struct file_operations ixp2000_wdt_fops = 172{ 173 .owner = THIS_MODULE, 174 .llseek = no_llseek, 175 .write = ixp2000_wdt_write, 176 .ioctl = ixp2000_wdt_ioctl, 177 .open = ixp2000_wdt_open, 178 .release = ixp2000_wdt_release, 179}; 180 181static struct miscdevice ixp2000_wdt_miscdev = 182{ 183 .minor = WATCHDOG_MINOR, 184 .name = "watchdog", 185 .fops = &ixp2000_wdt_fops, 186}; 187 188static int __init ixp2000_wdt_init(void) 189{ 190 if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) { 191 printk(KERN_INFO "Unable to use IXP2000 watchdog due to IXP2800 erratum #25.\n"); 192 return -EIO; 193 } 194 195 wdt_tick_rate = (*IXP2000_T1_CLD * HZ) / 256; 196 197 return misc_register(&ixp2000_wdt_miscdev); 198} 199 200static void __exit ixp2000_wdt_exit(void) 201{ 202 misc_deregister(&ixp2000_wdt_miscdev); 203} 204 205module_init(ixp2000_wdt_init); 206module_exit(ixp2000_wdt_exit); 207 208MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); 209MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog"); 210 211module_param(heartbeat, int, 0); 212MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); 213 214module_param(nowayout, int, 0); 215MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 216 217MODULE_LICENSE("GPL"); 218MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 219