1/* 2 * avila-wdt.c 3 * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name> 4 * 5 * based on: 6 * drivers/char/watchdog/ixp4xx_wdt.c 7 * 8 * Watchdog driver for Intel IXP4xx network processors 9 * 10 * Author: Deepak Saxena <dsaxena@plexity.net> 11 * 12 * Copyright 2004 (c) MontaVista, Software, Inc. 13 * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> 14 * 15 * This file is licensed under the terms of the GNU General Public 16 * License version 2. This program is licensed "as is" without any 17 * warranty of any kind, whether express or implied. 18 */ 19 20#include <linux/module.h> 21#include <linux/moduleparam.h> 22#include <linux/types.h> 23#include <linux/kernel.h> 24#include <linux/jiffies.h> 25#include <linux/timer.h> 26#include <linux/fs.h> 27#include <linux/miscdevice.h> 28#include <linux/watchdog.h> 29#include <linux/init.h> 30#include <linux/bitops.h> 31#include <linux/uaccess.h> 32#include <mach/hardware.h> 33 34static int nowayout = WATCHDOG_NOWAYOUT; 35static int heartbeat = 20; /* (secs) Default is 20 seconds */ 36static unsigned long wdt_status; 37static atomic_t wdt_counter; 38struct timer_list wdt_timer; 39 40#define WDT_IN_USE 0 41#define WDT_OK_TO_CLOSE 1 42#define WDT_RUNNING 2 43 44static void wdt_refresh(unsigned long data) 45{ 46 if (test_bit(WDT_RUNNING, &wdt_status)) { 47 if (atomic_dec_and_test(&wdt_counter)) { 48 printk(KERN_WARNING "Avila watchdog expired, expect a reboot soon!\n"); 49 clear_bit(WDT_RUNNING, &wdt_status); 50 return; 51 } 52 } 53 54 /* strobe to the watchdog */ 55 gpio_line_set(14, IXP4XX_GPIO_HIGH); 56 gpio_line_set(14, IXP4XX_GPIO_LOW); 57 58 mod_timer(&wdt_timer, jiffies + msecs_to_jiffies(500)); 59} 60 61static void wdt_enable(void) 62{ 63 atomic_set(&wdt_counter, heartbeat * 2); 64 65 /* Disable clock generator output on GPIO 14/15 */ 66 *IXP4XX_GPIO_GPCLKR &= ~(1 << 8); 67 68 /* activate GPIO 14 out */ 69 gpio_line_config(14, IXP4XX_GPIO_OUT); 70 gpio_line_set(14, IXP4XX_GPIO_LOW); 71 72 if (!test_bit(WDT_RUNNING, &wdt_status)) 73 wdt_refresh(0); 74 set_bit(WDT_RUNNING, &wdt_status); 75} 76 77static void wdt_disable(void) 78{ 79 /* Re-enable clock generator output on GPIO 14/15 */ 80 *IXP4XX_GPIO_GPCLKR |= (1 << 8); 81} 82 83static int avila_wdt_open(struct inode *inode, struct file *file) 84{ 85 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 86 return -EBUSY; 87 88 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 89 wdt_enable(); 90 return nonseekable_open(inode, file); 91} 92 93static ssize_t 94avila_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) 95{ 96 if (len) { 97 if (!nowayout) { 98 size_t i; 99 100 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 101 102 for (i = 0; i != len; i++) { 103 char c; 104 105 if (get_user(c, data + i)) 106 return -EFAULT; 107 if (c == 'V') 108 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 109 } 110 } 111 wdt_enable(); 112 } 113 return len; 114} 115 116static struct watchdog_info ident = { 117 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 118 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 119 .identity = "Avila Watchdog", 120}; 121 122 123static long avila_wdt_ioctl(struct file *file, unsigned int cmd, 124 unsigned long arg) 125{ 126 int ret = -ENOTTY; 127 int time; 128 129 switch (cmd) { 130 case WDIOC_GETSUPPORT: 131 ret = copy_to_user((struct watchdog_info *)arg, &ident, 132 sizeof(ident)) ? -EFAULT : 0; 133 break; 134 135 case WDIOC_GETSTATUS: 136 ret = put_user(0, (int *)arg); 137 break; 138 139 case WDIOC_KEEPALIVE: 140 wdt_enable(); 141 ret = 0; 142 break; 143 144 case WDIOC_SETTIMEOUT: 145 ret = get_user(time, (int *)arg); 146 if (ret) 147 break; 148 149 if (time <= 0 || time > 60) { 150 ret = -EINVAL; 151 break; 152 } 153 154 heartbeat = time; 155 wdt_enable(); 156 /* Fall through */ 157 158 case WDIOC_GETTIMEOUT: 159 ret = put_user(heartbeat, (int *)arg); 160 break; 161 } 162 return ret; 163} 164 165static int avila_wdt_release(struct inode *inode, struct file *file) 166{ 167 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 168 wdt_disable(); 169 else 170 printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " 171 "timer will not stop\n"); 172 clear_bit(WDT_IN_USE, &wdt_status); 173 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 174 175 return 0; 176} 177 178 179static const struct file_operations avila_wdt_fops = { 180 .owner = THIS_MODULE, 181 .llseek = no_llseek, 182 .write = avila_wdt_write, 183 .unlocked_ioctl = avila_wdt_ioctl, 184 .open = avila_wdt_open, 185 .release = avila_wdt_release, 186}; 187 188static struct miscdevice avila_wdt_miscdev = { 189 .minor = WATCHDOG_MINOR + 1, 190 .name = "avila_watchdog", 191 .fops = &avila_wdt_fops, 192}; 193 194static int __init avila_wdt_init(void) 195{ 196 int ret; 197 198 init_timer(&wdt_timer); 199 wdt_timer.expires = 0; 200 wdt_timer.data = 0; 201 wdt_timer.function = wdt_refresh; 202 ret = misc_register(&avila_wdt_miscdev); 203 if (ret == 0) 204 printk(KERN_INFO "Avila Watchdog Timer: heartbeat %d sec\n", 205 heartbeat); 206 return ret; 207} 208 209static void __exit avila_wdt_exit(void) 210{ 211 misc_deregister(&avila_wdt_miscdev); 212 del_timer(&wdt_timer); 213 wdt_disable(); 214} 215 216 217module_init(avila_wdt_init); 218module_exit(avila_wdt_exit); 219 220MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>"); 221MODULE_DESCRIPTION("Gateworks Avila Hardware Watchdog"); 222 223module_param(heartbeat, int, 0); 224MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 20s)"); 225 226module_param(nowayout, int, 0); 227MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); 228 229MODULE_LICENSE("GPL"); 230MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 231 232