109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2957183f3SDavid S. Miller /* riowd.c - driver for hw watchdog inside Super I/O of RIO 31da177e4SLinus Torvalds * 4e42311d7SDavid S. Miller * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) 51da177e4SLinus Torvalds */ 61da177e4SLinus Torvalds 727c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 827c766aaSJoe Perches 91da177e4SLinus Torvalds #include <linux/kernel.h> 101da177e4SLinus Torvalds #include <linux/module.h> 111da177e4SLinus Torvalds #include <linux/types.h> 121da177e4SLinus Torvalds #include <linux/fs.h> 131da177e4SLinus Torvalds #include <linux/errno.h> 141da177e4SLinus Torvalds #include <linux/miscdevice.h> 15e42311d7SDavid S. Miller #include <linux/watchdog.h> 16e42311d7SDavid S. Miller #include <linux/of.h> 17e42311d7SDavid S. Miller #include <linux/of_device.h> 18278aefc5SWim Van Sebroeck #include <linux/io.h> 19278aefc5SWim Van Sebroeck #include <linux/uaccess.h> 205a0e3ad6STejun Heo #include <linux/slab.h> 211da177e4SLinus Torvalds 221da177e4SLinus Torvalds 231da177e4SLinus Torvalds /* RIO uses the NatSemi Super I/O power management logical device 241da177e4SLinus Torvalds * as its' watchdog. 251da177e4SLinus Torvalds * 261da177e4SLinus Torvalds * When the watchdog triggers, it asserts a line to the BBC (Boot Bus 271da177e4SLinus Torvalds * Controller) of the machine. The BBC can only be configured to 281da177e4SLinus Torvalds * trigger a power-on reset when the signal is asserted. The BBC 291da177e4SLinus Torvalds * can be configured to ignore the signal entirely as well. 301da177e4SLinus Torvalds * 311da177e4SLinus Torvalds * The only Super I/O device register we care about is at index 321da177e4SLinus Torvalds * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255). 331da177e4SLinus Torvalds * If set to zero, this disables the watchdog. When set, the system 341da177e4SLinus Torvalds * must periodically (before watchdog expires) clear (set to zero) and 351da177e4SLinus Torvalds * re-set the watchdog else it will trigger. 361da177e4SLinus Torvalds * 371da177e4SLinus Torvalds * There are two other indexed watchdog registers inside this Super I/O 381da177e4SLinus Torvalds * logical device, but they are unused. The first, at index 0x06 is 391da177e4SLinus Torvalds * the watchdog control and can be used to make the watchdog timer re-set 401da177e4SLinus Torvalds * when the PS/2 mouse or serial lines show activity. The second, at 411da177e4SLinus Torvalds * index 0x07 is merely a sampling of the line from the watchdog to the 421da177e4SLinus Torvalds * BBC. 431da177e4SLinus Torvalds * 441da177e4SLinus Torvalds * The watchdog device generates no interrupts. 451da177e4SLinus Torvalds */ 461da177e4SLinus Torvalds 47e42311d7SDavid S. Miller MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); 481da177e4SLinus Torvalds MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO"); 491da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 501da177e4SLinus Torvalds 51e25ecd08SDavid S. Miller #define DRIVER_NAME "riowd" 52e25ecd08SDavid S. Miller #define PFX DRIVER_NAME ": " 531da177e4SLinus Torvalds 54e42311d7SDavid S. Miller struct riowd { 55e42311d7SDavid S. Miller void __iomem *regs; 56e42311d7SDavid S. Miller spinlock_t lock; 57e42311d7SDavid S. Miller }; 581da177e4SLinus Torvalds 59e42311d7SDavid S. Miller static struct riowd *riowd_device; 60e42311d7SDavid S. Miller 611da177e4SLinus Torvalds #define WDTO_INDEX 0x05 621da177e4SLinus Torvalds 631da177e4SLinus Torvalds static int riowd_timeout = 1; /* in minutes */ 641da177e4SLinus Torvalds module_param(riowd_timeout, int, 0); 651da177e4SLinus Torvalds MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes"); 661da177e4SLinus Torvalds 67e42311d7SDavid S. Miller static void riowd_writereg(struct riowd *p, u8 val, int index) 681da177e4SLinus Torvalds { 691da177e4SLinus Torvalds unsigned long flags; 701da177e4SLinus Torvalds 71e42311d7SDavid S. Miller spin_lock_irqsave(&p->lock, flags); 72e42311d7SDavid S. Miller writeb(index, p->regs + 0); 73e42311d7SDavid S. Miller writeb(val, p->regs + 1); 74e42311d7SDavid S. Miller spin_unlock_irqrestore(&p->lock, flags); 751da177e4SLinus Torvalds } 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds static int riowd_open(struct inode *inode, struct file *filp) 781da177e4SLinus Torvalds { 79c5bf68feSKirill Smelkov stream_open(inode, filp); 801da177e4SLinus Torvalds return 0; 811da177e4SLinus Torvalds } 821da177e4SLinus Torvalds 831da177e4SLinus Torvalds static int riowd_release(struct inode *inode, struct file *filp) 841da177e4SLinus Torvalds { 851da177e4SLinus Torvalds return 0; 861da177e4SLinus Torvalds } 871da177e4SLinus Torvalds 889626dd75SWim Van Sebroeck static long riowd_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 891da177e4SLinus Torvalds { 9042747d71SWim Van Sebroeck static const struct watchdog_info info = { 91e42311d7SDavid S. Miller .options = WDIOF_SETTIMEOUT, 92e42311d7SDavid S. Miller .firmware_version = 1, 93e25ecd08SDavid S. Miller .identity = DRIVER_NAME, 941da177e4SLinus Torvalds }; 951da177e4SLinus Torvalds void __user *argp = (void __user *)arg; 96e42311d7SDavid S. Miller struct riowd *p = riowd_device; 971da177e4SLinus Torvalds unsigned int options; 981da177e4SLinus Torvalds int new_margin; 991da177e4SLinus Torvalds 1001da177e4SLinus Torvalds switch (cmd) { 1011da177e4SLinus Torvalds case WDIOC_GETSUPPORT: 1021da177e4SLinus Torvalds if (copy_to_user(argp, &info, sizeof(info))) 1031da177e4SLinus Torvalds return -EFAULT; 1041da177e4SLinus Torvalds break; 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds case WDIOC_GETSTATUS: 1071da177e4SLinus Torvalds case WDIOC_GETBOOTSTATUS: 1081da177e4SLinus Torvalds if (put_user(0, (int __user *)argp)) 1091da177e4SLinus Torvalds return -EFAULT; 1101da177e4SLinus Torvalds break; 1111da177e4SLinus Torvalds 1121da177e4SLinus Torvalds case WDIOC_KEEPALIVE: 113e42311d7SDavid S. Miller riowd_writereg(p, riowd_timeout, WDTO_INDEX); 1141da177e4SLinus Torvalds break; 1151da177e4SLinus Torvalds 1161da177e4SLinus Torvalds case WDIOC_SETOPTIONS: 1171da177e4SLinus Torvalds if (copy_from_user(&options, argp, sizeof(options))) 1181da177e4SLinus Torvalds return -EFAULT; 1191da177e4SLinus Torvalds 1201da177e4SLinus Torvalds if (options & WDIOS_DISABLECARD) 121e42311d7SDavid S. Miller riowd_writereg(p, 0, WDTO_INDEX); 1221da177e4SLinus Torvalds else if (options & WDIOS_ENABLECARD) 123e42311d7SDavid S. Miller riowd_writereg(p, riowd_timeout, WDTO_INDEX); 1241da177e4SLinus Torvalds else 1251da177e4SLinus Torvalds return -EINVAL; 1261da177e4SLinus Torvalds 1271da177e4SLinus Torvalds break; 1281da177e4SLinus Torvalds 1291da177e4SLinus Torvalds case WDIOC_SETTIMEOUT: 1301da177e4SLinus Torvalds if (get_user(new_margin, (int __user *)argp)) 1311da177e4SLinus Torvalds return -EFAULT; 1321da177e4SLinus Torvalds if ((new_margin < 60) || (new_margin > (255 * 60))) 1331da177e4SLinus Torvalds return -EINVAL; 1341da177e4SLinus Torvalds riowd_timeout = (new_margin + 59) / 60; 135e42311d7SDavid S. Miller riowd_writereg(p, riowd_timeout, WDTO_INDEX); 136*bd490f82SGustavo A. R. Silva fallthrough; 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds case WDIOC_GETTIMEOUT: 1391da177e4SLinus Torvalds return put_user(riowd_timeout * 60, (int __user *)argp); 1401da177e4SLinus Torvalds 1411da177e4SLinus Torvalds default: 1421da177e4SLinus Torvalds return -EINVAL; 1435e31896aSJason Yan } 1441da177e4SLinus Torvalds 1451da177e4SLinus Torvalds return 0; 1461da177e4SLinus Torvalds } 1471da177e4SLinus Torvalds 148143a2e54SWim Van Sebroeck static ssize_t riowd_write(struct file *file, const char __user *buf, 149143a2e54SWim Van Sebroeck size_t count, loff_t *ppos) 1501da177e4SLinus Torvalds { 151e42311d7SDavid S. Miller struct riowd *p = riowd_device; 152e42311d7SDavid S. Miller 1531da177e4SLinus Torvalds if (count) { 154e42311d7SDavid S. Miller riowd_writereg(p, riowd_timeout, WDTO_INDEX); 1551da177e4SLinus Torvalds return 1; 1561da177e4SLinus Torvalds } 1571da177e4SLinus Torvalds 1581da177e4SLinus Torvalds return 0; 1591da177e4SLinus Torvalds } 1601da177e4SLinus Torvalds 16100977a59SArjan van de Ven static const struct file_operations riowd_fops = { 1621da177e4SLinus Torvalds .owner = THIS_MODULE, 163e42311d7SDavid S. Miller .llseek = no_llseek, 1649626dd75SWim Van Sebroeck .unlocked_ioctl = riowd_ioctl, 165b6dfb247SArnd Bergmann .compat_ioctl = compat_ptr_ioctl, 1661da177e4SLinus Torvalds .open = riowd_open, 1671da177e4SLinus Torvalds .write = riowd_write, 1681da177e4SLinus Torvalds .release = riowd_release, 1691da177e4SLinus Torvalds }; 1701da177e4SLinus Torvalds 171e42311d7SDavid S. Miller static struct miscdevice riowd_miscdev = { 172e42311d7SDavid S. Miller .minor = WATCHDOG_MINOR, 173e42311d7SDavid S. Miller .name = "watchdog", 174e42311d7SDavid S. Miller .fops = &riowd_fops 175e42311d7SDavid S. Miller }; 1761da177e4SLinus Torvalds 1772d991a16SBill Pemberton static int riowd_probe(struct platform_device *op) 1781da177e4SLinus Torvalds { 179e42311d7SDavid S. Miller struct riowd *p; 180e42311d7SDavid S. Miller int err = -EINVAL; 1811da177e4SLinus Torvalds 182e42311d7SDavid S. Miller if (riowd_device) 183e42311d7SDavid S. Miller goto out; 184e42311d7SDavid S. Miller 185e42311d7SDavid S. Miller err = -ENOMEM; 186a508e2e6SJingoo Han p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); 187e42311d7SDavid S. Miller if (!p) 188e42311d7SDavid S. Miller goto out; 189e42311d7SDavid S. Miller 190e42311d7SDavid S. Miller spin_lock_init(&p->lock); 191e42311d7SDavid S. Miller 192e25ecd08SDavid S. Miller p->regs = of_ioremap(&op->resource[0], 0, 2, DRIVER_NAME); 193e42311d7SDavid S. Miller if (!p->regs) { 19427c766aaSJoe Perches pr_err("Cannot map registers\n"); 195a508e2e6SJingoo Han goto out; 1961da177e4SLinus Torvalds } 197462265bfSThomas Gleixner /* Make miscdev useable right away */ 198462265bfSThomas Gleixner riowd_device = p; 1991da177e4SLinus Torvalds 200e42311d7SDavid S. Miller err = misc_register(&riowd_miscdev); 201e42311d7SDavid S. Miller if (err) { 20227c766aaSJoe Perches pr_err("Cannot register watchdog misc device\n"); 203e42311d7SDavid S. Miller goto out_iounmap; 204e42311d7SDavid S. Miller } 2051da177e4SLinus Torvalds 20627c766aaSJoe Perches pr_info("Hardware watchdog [%i minutes], regs at %p\n", 20727c766aaSJoe Perches riowd_timeout, p->regs); 208e42311d7SDavid S. Miller 209b94828ffSJingoo Han platform_set_drvdata(op, p); 21003717e3dSThomas Gleixner return 0; 211e42311d7SDavid S. Miller 212e42311d7SDavid S. Miller out_iounmap: 213462265bfSThomas Gleixner riowd_device = NULL; 214e42311d7SDavid S. Miller of_iounmap(&op->resource[0], p->regs, 2); 215e42311d7SDavid S. Miller 216e42311d7SDavid S. Miller out: 217e42311d7SDavid S. Miller return err; 218e42311d7SDavid S. Miller } 219e42311d7SDavid S. Miller 2204b12b896SBill Pemberton static int riowd_remove(struct platform_device *op) 221e42311d7SDavid S. Miller { 222b94828ffSJingoo Han struct riowd *p = platform_get_drvdata(op); 223e42311d7SDavid S. Miller 224e42311d7SDavid S. Miller misc_deregister(&riowd_miscdev); 225e42311d7SDavid S. Miller of_iounmap(&op->resource[0], p->regs, 2); 2261da177e4SLinus Torvalds 2271da177e4SLinus Torvalds return 0; 2281da177e4SLinus Torvalds } 2291da177e4SLinus Torvalds 230fd098316SDavid S. Miller static const struct of_device_id riowd_match[] = { 231e42311d7SDavid S. Miller { 232e42311d7SDavid S. Miller .name = "pmc", 233e42311d7SDavid S. Miller }, 234e42311d7SDavid S. Miller {}, 235e42311d7SDavid S. Miller }; 236e42311d7SDavid S. Miller MODULE_DEVICE_TABLE(of, riowd_match); 237e42311d7SDavid S. Miller 2381c48a5c9SGrant Likely static struct platform_driver riowd_driver = { 2394018294bSGrant Likely .driver = { 240e25ecd08SDavid S. Miller .name = DRIVER_NAME, 2414018294bSGrant Likely .of_match_table = riowd_match, 2424018294bSGrant Likely }, 243e42311d7SDavid S. Miller .probe = riowd_probe, 24482268714SBill Pemberton .remove = riowd_remove, 245e42311d7SDavid S. Miller }; 246e42311d7SDavid S. Miller 247b8ec6118SAxel Lin module_platform_driver(riowd_driver); 248