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