1/* 2 * RDC321x watchdog driver 3 * 4 * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org> 5 * 6 * This driver is highly inspired from the cpu5_wdt driver 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 * 22 */ 23 24#include <linux/module.h> 25#include <linux/moduleparam.h> 26#include <linux/types.h> 27#include <linux/errno.h> 28#include <linux/miscdevice.h> 29#include <linux/fs.h> 30#include <linux/ioport.h> 31#include <linux/timer.h> 32#include <linux/completion.h> 33#include <linux/jiffies.h> 34#include <linux/platform_device.h> 35#include <linux/watchdog.h> 36#include <linux/io.h> 37#include <linux/uaccess.h> 38#include <linux/mfd/rdc321x.h> 39 40#define RDC_WDT_MASK 0x80000000 /* Mask */ 41#define RDC_WDT_EN 0x00800000 /* Enable bit */ 42#define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */ 43#define RDC_WDT_RST 0x00100000 /* Reset bit */ 44#define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */ 45#define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */ 46#define RDC_WDT_CNT 0x00000001 /* WDT count */ 47 48#define RDC_CLS_TMR 0x80003844 /* Clear timer */ 49 50#define RDC_WDT_INTERVAL (HZ/10+1) 51 52static int ticks = 1000; 53 54/* some device data */ 55 56static struct { 57 struct completion stop; 58 int running; 59 struct timer_list timer; 60 int queue; 61 int default_ticks; 62 unsigned long inuse; 63 spinlock_t lock; 64 struct pci_dev *sb_pdev; 65 int base_reg; 66} rdc321x_wdt_device; 67 68/* generic helper functions */ 69 70static void rdc321x_wdt_trigger(unsigned long unused) 71{ 72 unsigned long flags; 73 u32 val; 74 75 if (rdc321x_wdt_device.running) 76 ticks--; 77 78 /* keep watchdog alive */ 79 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 80 pci_read_config_dword(rdc321x_wdt_device.sb_pdev, 81 rdc321x_wdt_device.base_reg, &val); 82 val |= RDC_WDT_EN; 83 pci_write_config_dword(rdc321x_wdt_device.sb_pdev, 84 rdc321x_wdt_device.base_reg, val); 85 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 86 87 /* requeue?? */ 88 if (rdc321x_wdt_device.queue && ticks) 89 mod_timer(&rdc321x_wdt_device.timer, 90 jiffies + RDC_WDT_INTERVAL); 91 else { 92 /* ticks doesn't matter anyway */ 93 complete(&rdc321x_wdt_device.stop); 94 } 95 96} 97 98static void rdc321x_wdt_reset(void) 99{ 100 ticks = rdc321x_wdt_device.default_ticks; 101} 102 103static void rdc321x_wdt_start(void) 104{ 105 unsigned long flags; 106 107 if (!rdc321x_wdt_device.queue) { 108 rdc321x_wdt_device.queue = 1; 109 110 /* Clear the timer */ 111 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 112 pci_write_config_dword(rdc321x_wdt_device.sb_pdev, 113 rdc321x_wdt_device.base_reg, RDC_CLS_TMR); 114 115 /* Enable watchdog and set the timeout to 81.92 us */ 116 pci_write_config_dword(rdc321x_wdt_device.sb_pdev, 117 rdc321x_wdt_device.base_reg, 118 RDC_WDT_EN | RDC_WDT_CNT); 119 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 120 121 mod_timer(&rdc321x_wdt_device.timer, 122 jiffies + RDC_WDT_INTERVAL); 123 } 124 125 /* if process dies, counter is not decremented */ 126 rdc321x_wdt_device.running++; 127} 128 129static int rdc321x_wdt_stop(void) 130{ 131 if (rdc321x_wdt_device.running) 132 rdc321x_wdt_device.running = 0; 133 134 ticks = rdc321x_wdt_device.default_ticks; 135 136 return -EIO; 137} 138 139/* filesystem operations */ 140static int rdc321x_wdt_open(struct inode *inode, struct file *file) 141{ 142 if (test_and_set_bit(0, &rdc321x_wdt_device.inuse)) 143 return -EBUSY; 144 145 return nonseekable_open(inode, file); 146} 147 148static int rdc321x_wdt_release(struct inode *inode, struct file *file) 149{ 150 clear_bit(0, &rdc321x_wdt_device.inuse); 151 return 0; 152} 153 154static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd, 155 unsigned long arg) 156{ 157 void __user *argp = (void __user *)arg; 158 u32 value; 159 static const struct watchdog_info ident = { 160 .options = WDIOF_CARDRESET, 161 .identity = "RDC321x WDT", 162 }; 163 unsigned long flags; 164 165 switch (cmd) { 166 case WDIOC_KEEPALIVE: 167 rdc321x_wdt_reset(); 168 break; 169 case WDIOC_GETSTATUS: 170 /* Read the value from the DATA register */ 171 spin_lock_irqsave(&rdc321x_wdt_device.lock, flags); 172 pci_read_config_dword(rdc321x_wdt_device.sb_pdev, 173 rdc321x_wdt_device.base_reg, &value); 174 spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags); 175 if (copy_to_user(argp, &value, sizeof(u32))) 176 return -EFAULT; 177 break; 178 case WDIOC_GETSUPPORT: 179 if (copy_to_user(argp, &ident, sizeof(ident))) 180 return -EFAULT; 181 break; 182 case WDIOC_SETOPTIONS: 183 if (copy_from_user(&value, argp, sizeof(int))) 184 return -EFAULT; 185 switch (value) { 186 case WDIOS_ENABLECARD: 187 rdc321x_wdt_start(); 188 break; 189 case WDIOS_DISABLECARD: 190 return rdc321x_wdt_stop(); 191 default: 192 return -EINVAL; 193 } 194 break; 195 default: 196 return -ENOTTY; 197 } 198 return 0; 199} 200 201static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf, 202 size_t count, loff_t *ppos) 203{ 204 if (!count) 205 return -EIO; 206 207 rdc321x_wdt_reset(); 208 209 return count; 210} 211 212static const struct file_operations rdc321x_wdt_fops = { 213 .owner = THIS_MODULE, 214 .llseek = no_llseek, 215 .unlocked_ioctl = rdc321x_wdt_ioctl, 216 .open = rdc321x_wdt_open, 217 .write = rdc321x_wdt_write, 218 .release = rdc321x_wdt_release, 219}; 220 221static struct miscdevice rdc321x_wdt_misc = { 222 .minor = WATCHDOG_MINOR, 223 .name = "watchdog", 224 .fops = &rdc321x_wdt_fops, 225}; 226 227static int rdc321x_wdt_probe(struct platform_device *pdev) 228{ 229 int err; 230 struct resource *r; 231 struct rdc321x_wdt_pdata *pdata; 232 233 pdata = dev_get_platdata(&pdev->dev); 234 if (!pdata) { 235 dev_err(&pdev->dev, "no platform data supplied\n"); 236 return -ENODEV; 237 } 238 239 r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg"); 240 if (!r) { 241 dev_err(&pdev->dev, "failed to get wdt-reg resource\n"); 242 return -ENODEV; 243 } 244 245 rdc321x_wdt_device.sb_pdev = pdata->sb_pdev; 246 rdc321x_wdt_device.base_reg = r->start; 247 248 err = misc_register(&rdc321x_wdt_misc); 249 if (err < 0) { 250 dev_err(&pdev->dev, "misc_register failed\n"); 251 return err; 252 } 253 254 spin_lock_init(&rdc321x_wdt_device.lock); 255 256 /* Reset the watchdog */ 257 pci_write_config_dword(rdc321x_wdt_device.sb_pdev, 258 rdc321x_wdt_device.base_reg, RDC_WDT_RST); 259 260 init_completion(&rdc321x_wdt_device.stop); 261 rdc321x_wdt_device.queue = 0; 262 263 clear_bit(0, &rdc321x_wdt_device.inuse); 264 265 setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0); 266 267 rdc321x_wdt_device.default_ticks = ticks; 268 269 dev_info(&pdev->dev, "watchdog init success\n"); 270 271 return 0; 272} 273 274static int rdc321x_wdt_remove(struct platform_device *pdev) 275{ 276 if (rdc321x_wdt_device.queue) { 277 rdc321x_wdt_device.queue = 0; 278 wait_for_completion(&rdc321x_wdt_device.stop); 279 } 280 281 misc_deregister(&rdc321x_wdt_misc); 282 283 return 0; 284} 285 286static struct platform_driver rdc321x_wdt_driver = { 287 .probe = rdc321x_wdt_probe, 288 .remove = rdc321x_wdt_remove, 289 .driver = { 290 .name = "rdc321x-wdt", 291 }, 292}; 293 294module_platform_driver(rdc321x_wdt_driver); 295 296MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 297MODULE_DESCRIPTION("RDC321x watchdog driver"); 298MODULE_LICENSE("GPL"); 299