xref: /linux/drivers/watchdog/riowd.c (revision 942baad211336efefb93a8369478888ab845c450)
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