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 38 static int ticks = 1000; 39 40 /* some device data */ 41 42 static 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 56 static 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 84 static void rdc321x_wdt_reset(void) 85 { 86 ticks = rdc321x_wdt_device.default_ticks; 87 } 88 89 static 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 115 static 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 */ 126 static 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 134 static int rdc321x_wdt_release(struct inode *inode, struct file *file) 135 { 136 clear_bit(0, &rdc321x_wdt_device.inuse); 137 return 0; 138 } 139 140 static 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 187 static 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 198 static 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 208 static struct miscdevice rdc321x_wdt_misc = { 209 .minor = WATCHDOG_MINOR, 210 .name = "watchdog", 211 .fops = &rdc321x_wdt_fops, 212 }; 213 214 static 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 235 err = misc_register(&rdc321x_wdt_misc); 236 if (err < 0) { 237 dev_err(&pdev->dev, "misc_register failed\n"); 238 return err; 239 } 240 241 spin_lock_init(&rdc321x_wdt_device.lock); 242 243 /* Reset the watchdog */ 244 pci_write_config_dword(rdc321x_wdt_device.sb_pdev, 245 rdc321x_wdt_device.base_reg, RDC_WDT_RST); 246 247 init_completion(&rdc321x_wdt_device.stop); 248 rdc321x_wdt_device.queue = 0; 249 250 clear_bit(0, &rdc321x_wdt_device.inuse); 251 252 timer_setup(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0); 253 254 rdc321x_wdt_device.default_ticks = ticks; 255 256 dev_info(&pdev->dev, "watchdog init success\n"); 257 258 return 0; 259 } 260 261 static int rdc321x_wdt_remove(struct platform_device *pdev) 262 { 263 if (rdc321x_wdt_device.queue) { 264 rdc321x_wdt_device.queue = 0; 265 wait_for_completion(&rdc321x_wdt_device.stop); 266 } 267 268 misc_deregister(&rdc321x_wdt_misc); 269 270 return 0; 271 } 272 273 static struct platform_driver rdc321x_wdt_driver = { 274 .probe = rdc321x_wdt_probe, 275 .remove = rdc321x_wdt_remove, 276 .driver = { 277 .name = "rdc321x-wdt", 278 }, 279 }; 280 281 module_platform_driver(rdc321x_wdt_driver); 282 283 MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 284 MODULE_DESCRIPTION("RDC321x watchdog driver"); 285 MODULE_LICENSE("GPL"); 286