brintos

brintos / linux-shallow public Read only

0
0
Text · 7.9 KiB · efadbb9 Raw
325 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  IDT Interprise 79RC32434 watchdog driver4 *5 *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>6 *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>7 *8 *  based on9 *  SoftDog 0.05:	A Software Watchdog Device10 *11 *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,12 *					All Rights Reserved.13 */14 15#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt16 17#include <linux/module.h>		/* For module specific items */18#include <linux/moduleparam.h>		/* For new moduleparam's */19#include <linux/types.h>		/* For standard types (like size_t) */20#include <linux/errno.h>		/* For the -ENODEV/... values */21#include <linux/kernel.h>		/* For printk/panic/... */22#include <linux/fs.h>			/* For file operations */23#include <linux/miscdevice.h>		/* For struct miscdevice */24#include <linux/watchdog.h>		/* For the watchdog specific items */25#include <linux/init.h>			/* For __init/__exit/... */26#include <linux/platform_device.h>	/* For platform_driver framework */27#include <linux/spinlock.h>		/* For spin_lock/spin_unlock/... */28#include <linux/uaccess.h>		/* For copy_to_user/put_user/... */29#include <linux/io.h>			/* For devm_ioremap */30 31#include <asm/mach-rc32434/integ.h>	/* For the Watchdog registers */32 33#define VERSION "1.0"34 35static struct {36	unsigned long inuse;37	spinlock_t io_lock;38} rc32434_wdt_device;39 40static struct integ __iomem *wdt_reg;41 42static int expect_close;43 44/* Board internal clock speed in Hz,45 * the watchdog timer ticks at. */46extern unsigned int idt_cpu_freq;47 48/* translate wtcompare value to seconds and vice versa */49#define WTCOMP2SEC(x)	(x / idt_cpu_freq)50#define SEC2WTCOMP(x)	(x * idt_cpu_freq)51 52/* Use a default timeout of 20s. This should be53 * safe for CPU clock speeds up to 400MHz, as54 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s.  */55#define WATCHDOG_TIMEOUT 2056 57static int timeout = WATCHDOG_TIMEOUT;58module_param(timeout, int, 0);59MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="60		__MODULE_STRING(WATCHDOG_TIMEOUT) ")");61 62static bool nowayout = WATCHDOG_NOWAYOUT;63module_param(nowayout, bool, 0);64MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="65	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");66 67/* apply or and nand masks to data read from addr and write back */68#define SET_BITS(addr, or, nand) \69	writel((readl(&addr) | or) & ~nand, &addr)70 71static int rc32434_wdt_set(int new_timeout)72{73	int max_to = WTCOMP2SEC((u32)-1);74 75	if (new_timeout < 0 || new_timeout > max_to) {76		pr_err("timeout value must be between 0 and %d\n", max_to);77		return -EINVAL;78	}79	timeout = new_timeout;80	spin_lock(&rc32434_wdt_device.io_lock);81	writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);82	spin_unlock(&rc32434_wdt_device.io_lock);83 84	return 0;85}86 87static void rc32434_wdt_start(void)88{89	u32 or, nand;90 91	spin_lock(&rc32434_wdt_device.io_lock);92 93	/* zero the counter before enabling */94	writel(0, &wdt_reg->wtcount);95 96	/* don't generate a non-maskable interrupt,97	 * do a warm reset instead */98	nand = 1 << RC32434_ERR_WNE;99	or = 1 << RC32434_ERR_WRE;100 101	/* reset the ERRCS timeout bit in case it's set */102	nand |= 1 << RC32434_ERR_WTO;103 104	SET_BITS(wdt_reg->errcs, or, nand);105 106	/* set the timeout (either default or based on module param) */107	rc32434_wdt_set(timeout);108 109	/* reset WTC timeout bit and enable WDT */110	nand = 1 << RC32434_WTC_TO;111	or = 1 << RC32434_WTC_EN;112 113	SET_BITS(wdt_reg->wtc, or, nand);114 115	spin_unlock(&rc32434_wdt_device.io_lock);116	pr_info("Started watchdog timer\n");117}118 119static void rc32434_wdt_stop(void)120{121	spin_lock(&rc32434_wdt_device.io_lock);122 123	/* Disable WDT */124	SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);125 126	spin_unlock(&rc32434_wdt_device.io_lock);127	pr_info("Stopped watchdog timer\n");128}129 130static void rc32434_wdt_ping(void)131{132	spin_lock(&rc32434_wdt_device.io_lock);133	writel(0, &wdt_reg->wtcount);134	spin_unlock(&rc32434_wdt_device.io_lock);135}136 137static int rc32434_wdt_open(struct inode *inode, struct file *file)138{139	if (test_and_set_bit(0, &rc32434_wdt_device.inuse))140		return -EBUSY;141 142	if (nowayout)143		__module_get(THIS_MODULE);144 145	rc32434_wdt_start();146	rc32434_wdt_ping();147 148	return stream_open(inode, file);149}150 151static int rc32434_wdt_release(struct inode *inode, struct file *file)152{153	if (expect_close == 42) {154		rc32434_wdt_stop();155		module_put(THIS_MODULE);156	} else {157		pr_crit("device closed unexpectedly. WDT will not stop!\n");158		rc32434_wdt_ping();159	}160	clear_bit(0, &rc32434_wdt_device.inuse);161	return 0;162}163 164static ssize_t rc32434_wdt_write(struct file *file, const char *data,165				size_t len, loff_t *ppos)166{167	if (len) {168		if (!nowayout) {169			size_t i;170 171			/* In case it was set long ago */172			expect_close = 0;173 174			for (i = 0; i != len; i++) {175				char c;176				if (get_user(c, data + i))177					return -EFAULT;178				if (c == 'V')179					expect_close = 42;180			}181		}182		rc32434_wdt_ping();183		return len;184	}185	return 0;186}187 188static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,189				unsigned long arg)190{191	void __user *argp = (void __user *)arg;192	int new_timeout;193	unsigned int value;194	static const struct watchdog_info ident = {195		.options =		WDIOF_SETTIMEOUT |196					WDIOF_KEEPALIVEPING |197					WDIOF_MAGICCLOSE,198		.identity =		"RC32434_WDT Watchdog",199	};200	switch (cmd) {201	case WDIOC_GETSUPPORT:202		if (copy_to_user(argp, &ident, sizeof(ident)))203			return -EFAULT;204		break;205	case WDIOC_GETSTATUS:206	case WDIOC_GETBOOTSTATUS:207		value = 0;208		if (copy_to_user(argp, &value, sizeof(int)))209			return -EFAULT;210		break;211	case WDIOC_SETOPTIONS:212		if (copy_from_user(&value, argp, sizeof(int)))213			return -EFAULT;214		switch (value) {215		case WDIOS_ENABLECARD:216			rc32434_wdt_start();217			break;218		case WDIOS_DISABLECARD:219			rc32434_wdt_stop();220			break;221		default:222			return -EINVAL;223		}224		break;225	case WDIOC_KEEPALIVE:226		rc32434_wdt_ping();227		break;228	case WDIOC_SETTIMEOUT:229		if (copy_from_user(&new_timeout, argp, sizeof(int)))230			return -EFAULT;231		if (rc32434_wdt_set(new_timeout))232			return -EINVAL;233		fallthrough;234	case WDIOC_GETTIMEOUT:235		return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;236	default:237		return -ENOTTY;238	}239 240	return 0;241}242 243static const struct file_operations rc32434_wdt_fops = {244	.owner		= THIS_MODULE,245	.write		= rc32434_wdt_write,246	.unlocked_ioctl	= rc32434_wdt_ioctl,247	.compat_ioctl	= compat_ptr_ioctl,248	.open		= rc32434_wdt_open,249	.release	= rc32434_wdt_release,250};251 252static struct miscdevice rc32434_wdt_miscdev = {253	.minor	= WATCHDOG_MINOR,254	.name	= "watchdog",255	.fops	= &rc32434_wdt_fops,256};257 258static int rc32434_wdt_probe(struct platform_device *pdev)259{260	int ret;261	struct resource *r;262 263	r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");264	if (!r) {265		pr_err("failed to retrieve resources\n");266		return -ENODEV;267	}268 269	wdt_reg = devm_ioremap(&pdev->dev, r->start, resource_size(r));270	if (!wdt_reg) {271		pr_err("failed to remap I/O resources\n");272		return -ENXIO;273	}274 275	spin_lock_init(&rc32434_wdt_device.io_lock);276 277	/* Make sure the watchdog is not running */278	rc32434_wdt_stop();279 280	/* Check that the heartbeat value is within it's range;281	 * if not reset to the default */282	if (rc32434_wdt_set(timeout)) {283		rc32434_wdt_set(WATCHDOG_TIMEOUT);284		pr_info("timeout value must be between 0 and %d\n",285			WTCOMP2SEC((u32)-1));286	}287 288	ret = misc_register(&rc32434_wdt_miscdev);289	if (ret < 0) {290		pr_err("failed to register watchdog device\n");291		return ret;292	}293 294	pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",295		timeout);296 297	return 0;298}299 300static void rc32434_wdt_remove(struct platform_device *pdev)301{302	misc_deregister(&rc32434_wdt_miscdev);303}304 305static void rc32434_wdt_shutdown(struct platform_device *pdev)306{307	rc32434_wdt_stop();308}309 310static struct platform_driver rc32434_wdt_driver = {311	.probe		= rc32434_wdt_probe,312	.remove_new	= rc32434_wdt_remove,313	.shutdown	= rc32434_wdt_shutdown,314	.driver		= {315			.name = "rc32434_wdt",316	}317};318 319module_platform_driver(rc32434_wdt_driver);320 321MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"322		"Florian Fainelli <florian@openwrt.org>");323MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");324MODULE_LICENSE("GPL");325