brintos

brintos / linux-shallow public Read only

0
0
Text · 6.5 KiB · 8049031 Raw
282 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * RDC321x watchdog driver4 *5 * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>6 *7 * This driver is highly inspired from the cpu5_wdt driver8 */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	.unlocked_ioctl	= rdc321x_wdt_ioctl,201	.compat_ioctl	= compat_ptr_ioctl,202	.open		= rdc321x_wdt_open,203	.write		= rdc321x_wdt_write,204	.release	= rdc321x_wdt_release,205};206 207static struct miscdevice rdc321x_wdt_misc = {208	.minor	= WATCHDOG_MINOR,209	.name	= "watchdog",210	.fops	= &rdc321x_wdt_fops,211};212 213static int rdc321x_wdt_probe(struct platform_device *pdev)214{215	int err;216	struct resource *r;217	struct rdc321x_wdt_pdata *pdata;218 219	pdata = dev_get_platdata(&pdev->dev);220	if (!pdata) {221		dev_err(&pdev->dev, "no platform data supplied\n");222		return -ENODEV;223	}224 225	r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");226	if (!r) {227		dev_err(&pdev->dev, "failed to get wdt-reg resource\n");228		return -ENODEV;229	}230 231	rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;232	rdc321x_wdt_device.base_reg = r->start;233	rdc321x_wdt_device.queue = 0;234	rdc321x_wdt_device.default_ticks = ticks;235 236	err = misc_register(&rdc321x_wdt_misc);237	if (err < 0) {238		dev_err(&pdev->dev, "misc_register failed\n");239		return err;240	}241 242	spin_lock_init(&rdc321x_wdt_device.lock);243 244	/* Reset the watchdog */245	pci_write_config_dword(rdc321x_wdt_device.sb_pdev,246				rdc321x_wdt_device.base_reg, RDC_WDT_RST);247 248	init_completion(&rdc321x_wdt_device.stop);249 250	clear_bit(0, &rdc321x_wdt_device.inuse);251 252	timer_setup(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);253 254	dev_info(&pdev->dev, "watchdog init success\n");255 256	return 0;257}258 259static void rdc321x_wdt_remove(struct platform_device *pdev)260{261	if (rdc321x_wdt_device.queue) {262		rdc321x_wdt_device.queue = 0;263		wait_for_completion(&rdc321x_wdt_device.stop);264	}265 266	misc_deregister(&rdc321x_wdt_misc);267}268 269static struct platform_driver rdc321x_wdt_driver = {270	.probe = rdc321x_wdt_probe,271	.remove_new = rdc321x_wdt_remove,272	.driver = {273		.name = "rdc321x-wdt",274	},275};276 277module_platform_driver(rdc321x_wdt_driver);278 279MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");280MODULE_DESCRIPTION("RDC321x watchdog driver");281MODULE_LICENSE("GPL");282