1 /* 2 * drivers/char/watchdog/ixp4xx_wdt.c 3 * 4 * Watchdog driver for Intel IXP4xx network processors 5 * 6 * Author: Deepak Saxena <dsaxena@plexity.net> 7 * 8 * Copyright 2004 (c) MontaVista, Software, Inc. 9 * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> 10 * 11 * This file is licensed under the terms of the GNU General Public 12 * License version 2. This program is licensed "as is" without any 13 * warranty of any kind, whether express or implied. 14 */ 15 16 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 17 18 #include <linux/module.h> 19 #include <linux/moduleparam.h> 20 #include <linux/types.h> 21 #include <linux/kernel.h> 22 #include <linux/fs.h> 23 #include <linux/miscdevice.h> 24 #include <linux/of.h> 25 #include <linux/watchdog.h> 26 #include <linux/init.h> 27 #include <linux/bitops.h> 28 #include <linux/uaccess.h> 29 #include <mach/hardware.h> 30 31 static bool nowayout = WATCHDOG_NOWAYOUT; 32 static int heartbeat = 60; /* (secs) Default is 1 minute */ 33 static unsigned long wdt_status; 34 static unsigned long boot_status; 35 static DEFINE_SPINLOCK(wdt_lock); 36 37 #define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) 38 39 #define WDT_IN_USE 0 40 #define WDT_OK_TO_CLOSE 1 41 42 static void wdt_enable(void) 43 { 44 spin_lock(&wdt_lock); 45 *IXP4XX_OSWK = IXP4XX_WDT_KEY; 46 *IXP4XX_OSWE = 0; 47 *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; 48 *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; 49 *IXP4XX_OSWK = 0; 50 spin_unlock(&wdt_lock); 51 } 52 53 static void wdt_disable(void) 54 { 55 spin_lock(&wdt_lock); 56 *IXP4XX_OSWK = IXP4XX_WDT_KEY; 57 *IXP4XX_OSWE = 0; 58 *IXP4XX_OSWK = 0; 59 spin_unlock(&wdt_lock); 60 } 61 62 static int ixp4xx_wdt_open(struct inode *inode, struct file *file) 63 { 64 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 65 return -EBUSY; 66 67 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 68 wdt_enable(); 69 return stream_open(inode, file); 70 } 71 72 static ssize_t 73 ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) 74 { 75 if (len) { 76 if (!nowayout) { 77 size_t i; 78 79 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 80 81 for (i = 0; i != len; i++) { 82 char c; 83 84 if (get_user(c, data + i)) 85 return -EFAULT; 86 if (c == 'V') 87 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 88 } 89 } 90 wdt_enable(); 91 } 92 return len; 93 } 94 95 static const struct watchdog_info ident = { 96 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 97 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 98 .identity = "IXP4xx Watchdog", 99 }; 100 101 102 static long ixp4xx_wdt_ioctl(struct file *file, unsigned int cmd, 103 unsigned long arg) 104 { 105 int ret = -ENOTTY; 106 int time; 107 108 switch (cmd) { 109 case WDIOC_GETSUPPORT: 110 ret = copy_to_user((struct watchdog_info *)arg, &ident, 111 sizeof(ident)) ? -EFAULT : 0; 112 break; 113 114 case WDIOC_GETSTATUS: 115 ret = put_user(0, (int *)arg); 116 break; 117 118 case WDIOC_GETBOOTSTATUS: 119 ret = put_user(boot_status, (int *)arg); 120 break; 121 122 case WDIOC_KEEPALIVE: 123 wdt_enable(); 124 ret = 0; 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_enable(); 139 /* Fall through */ 140 141 case WDIOC_GETTIMEOUT: 142 ret = put_user(heartbeat, (int *)arg); 143 break; 144 } 145 return ret; 146 } 147 148 static int ixp4xx_wdt_release(struct inode *inode, struct file *file) 149 { 150 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 151 wdt_disable(); 152 else 153 pr_crit("Device closed unexpectedly - timer will not stop\n"); 154 clear_bit(WDT_IN_USE, &wdt_status); 155 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 156 157 return 0; 158 } 159 160 161 static const struct file_operations ixp4xx_wdt_fops = { 162 .owner = THIS_MODULE, 163 .llseek = no_llseek, 164 .write = ixp4xx_wdt_write, 165 .unlocked_ioctl = ixp4xx_wdt_ioctl, 166 .compat_ioctl = compat_ptr_ioctl, 167 .open = ixp4xx_wdt_open, 168 .release = ixp4xx_wdt_release, 169 }; 170 171 static struct miscdevice ixp4xx_wdt_miscdev = { 172 .minor = WATCHDOG_MINOR, 173 .name = "watchdog", 174 .fops = &ixp4xx_wdt_fops, 175 }; 176 177 static int __init ixp4xx_wdt_init(void) 178 { 179 int ret; 180 181 /* 182 * FIXME: we bail out on device tree boot but this really needs 183 * to be fixed in a nicer way: this registers the MDIO bus before 184 * even matching the driver infrastructure, we should only probe 185 * detected hardware. 186 */ 187 if (of_have_populated_dt()) 188 return -ENODEV; 189 if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { 190 pr_err("Rev. A0 IXP42x CPU detected - watchdog disabled\n"); 191 192 return -ENODEV; 193 } 194 boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? 195 WDIOF_CARDRESET : 0; 196 ret = misc_register(&ixp4xx_wdt_miscdev); 197 if (ret == 0) 198 pr_info("timer heartbeat %d sec\n", heartbeat); 199 return ret; 200 } 201 202 static void __exit ixp4xx_wdt_exit(void) 203 { 204 misc_deregister(&ixp4xx_wdt_miscdev); 205 } 206 207 208 module_init(ixp4xx_wdt_init); 209 module_exit(ixp4xx_wdt_exit); 210 211 MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); 212 MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); 213 214 module_param(heartbeat, int, 0); 215 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); 216 217 module_param(nowayout, bool, 0); 218 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 219 220 MODULE_LICENSE("GPL"); 221