brintos

brintos / linux-shallow public Read only

0
0
Text · 7.3 KiB · d854fcf Raw
329 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * GE watchdog userspace interface4 *5 * Author:  Martyn Welch <martyn.welch@ge.com>6 *7 * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc.8 *9 * Based on: mv64x60_wdt.c (MV64X60 watchdog userspace interface)10 *   Author: James Chapman <jchapman@katalix.com>11 */12 13/* TODO:14 * This driver does not provide support for the hardwares capability of sending15 * an interrupt at a programmable threshold.16 *17 * This driver currently can only support 1 watchdog - there are 2 in the18 * hardware that this driver supports. Thus one could be configured as a19 * process-based watchdog (via /dev/watchdog), the second (using the interrupt20 * capabilities) a kernel-based watchdog.21 */22 23#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt24 25#include <linux/kernel.h>26#include <linux/compiler.h>27#include <linux/init.h>28#include <linux/module.h>29#include <linux/miscdevice.h>30#include <linux/watchdog.h>31#include <linux/fs.h>32#include <linux/of.h>33#include <linux/of_address.h>34#include <linux/platform_device.h>35#include <linux/io.h>36#include <linux/uaccess.h>37 38#include <sysdev/fsl_soc.h>39 40/*41 * The watchdog configuration register contains a pair of 2-bit fields,42 *   1.  a reload field, bits 27-26, which triggers a reload of43 *       the countdown register, and44 *   2.  an enable field, bits 25-24, which toggles between45 *       enabling and disabling the watchdog timer.46 * Bit 31 is a read-only field which indicates whether the47 * watchdog timer is currently enabled.48 *49 * The low 24 bits contain the timer reload value.50 */51#define GEF_WDC_ENABLE_SHIFT	2452#define GEF_WDC_SERVICE_SHIFT	2653#define GEF_WDC_ENABLED_SHIFT	3154 55#define GEF_WDC_ENABLED_TRUE	156#define GEF_WDC_ENABLED_FALSE	057 58/* Flags bits */59#define GEF_WDOG_FLAG_OPENED	060 61static unsigned long wdt_flags;62static int wdt_status;63static void __iomem *gef_wdt_regs;64static int gef_wdt_timeout;65static int gef_wdt_count;66static unsigned int bus_clk;67static char expect_close;68static DEFINE_SPINLOCK(gef_wdt_spinlock);69 70static bool nowayout = WATCHDOG_NOWAYOUT;71module_param(nowayout, bool, 0);72MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="73	__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");74 75 76static int gef_wdt_toggle_wdc(int enabled_predicate, int field_shift)77{78	u32 data;79	u32 enabled;80	int ret = 0;81 82	spin_lock(&gef_wdt_spinlock);83	data = ioread32be(gef_wdt_regs);84	enabled = (data >> GEF_WDC_ENABLED_SHIFT) & 1;85 86	/* only toggle the requested field if enabled state matches predicate */87	if ((enabled ^ enabled_predicate) == 0) {88		/* We write a 1, then a 2 -- to the appropriate field */89		data = (1 << field_shift) | gef_wdt_count;90		iowrite32be(data, gef_wdt_regs);91 92		data = (2 << field_shift) | gef_wdt_count;93		iowrite32be(data, gef_wdt_regs);94		ret = 1;95	}96	spin_unlock(&gef_wdt_spinlock);97 98	return ret;99}100 101static void gef_wdt_service(void)102{103	gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,104		GEF_WDC_SERVICE_SHIFT);105}106 107static void gef_wdt_handler_enable(void)108{109	if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_FALSE,110				   GEF_WDC_ENABLE_SHIFT)) {111		gef_wdt_service();112		pr_notice("watchdog activated\n");113	}114}115 116static void gef_wdt_handler_disable(void)117{118	if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,119				   GEF_WDC_ENABLE_SHIFT))120		pr_notice("watchdog deactivated\n");121}122 123static void gef_wdt_set_timeout(unsigned int timeout)124{125	/* maximum bus cycle count is 0xFFFFFFFF */126	if (timeout > 0xFFFFFFFF / bus_clk)127		timeout = 0xFFFFFFFF / bus_clk;128 129	/* Register only holds upper 24 bits, bit shifted into lower 24 */130	gef_wdt_count = (timeout * bus_clk) >> 8;131	gef_wdt_timeout = timeout;132}133 134 135static ssize_t gef_wdt_write(struct file *file, const char __user *data,136				 size_t len, loff_t *ppos)137{138	if (len) {139		if (!nowayout) {140			size_t i;141 142			expect_close = 0;143 144			for (i = 0; i != len; i++) {145				char c;146				if (get_user(c, data + i))147					return -EFAULT;148				if (c == 'V')149					expect_close = 42;150			}151		}152		gef_wdt_service();153	}154 155	return len;156}157 158static long gef_wdt_ioctl(struct file *file, unsigned int cmd,159							unsigned long arg)160{161	int timeout;162	int options;163	void __user *argp = (void __user *)arg;164	static const struct watchdog_info info = {165		.options =	WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |166				WDIOF_KEEPALIVEPING,167		.firmware_version = 0,168		.identity = "GE watchdog",169	};170 171	switch (cmd) {172	case WDIOC_GETSUPPORT:173		if (copy_to_user(argp, &info, sizeof(info)))174			return -EFAULT;175		break;176 177	case WDIOC_GETSTATUS:178	case WDIOC_GETBOOTSTATUS:179		if (put_user(wdt_status, (int __user *)argp))180			return -EFAULT;181		wdt_status &= ~WDIOF_KEEPALIVEPING;182		break;183 184	case WDIOC_SETOPTIONS:185		if (get_user(options, (int __user *)argp))186			return -EFAULT;187 188		if (options & WDIOS_DISABLECARD)189			gef_wdt_handler_disable();190 191		if (options & WDIOS_ENABLECARD)192			gef_wdt_handler_enable();193		break;194 195	case WDIOC_KEEPALIVE:196		gef_wdt_service();197		wdt_status |= WDIOF_KEEPALIVEPING;198		break;199 200	case WDIOC_SETTIMEOUT:201		if (get_user(timeout, (int __user *)argp))202			return -EFAULT;203		gef_wdt_set_timeout(timeout);204		fallthrough;205 206	case WDIOC_GETTIMEOUT:207		if (put_user(gef_wdt_timeout, (int __user *)argp))208			return -EFAULT;209		break;210 211	default:212		return -ENOTTY;213	}214 215	return 0;216}217 218static int gef_wdt_open(struct inode *inode, struct file *file)219{220	if (test_and_set_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags))221		return -EBUSY;222 223	if (nowayout)224		__module_get(THIS_MODULE);225 226	gef_wdt_handler_enable();227 228	return stream_open(inode, file);229}230 231static int gef_wdt_release(struct inode *inode, struct file *file)232{233	if (expect_close == 42)234		gef_wdt_handler_disable();235	else {236		pr_crit("unexpected close, not stopping timer!\n");237		gef_wdt_service();238	}239	expect_close = 0;240 241	clear_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags);242 243	return 0;244}245 246static const struct file_operations gef_wdt_fops = {247	.owner = THIS_MODULE,248	.write = gef_wdt_write,249	.unlocked_ioctl = gef_wdt_ioctl,250	.compat_ioctl	= compat_ptr_ioctl,251	.open = gef_wdt_open,252	.release = gef_wdt_release,253};254 255static struct miscdevice gef_wdt_miscdev = {256	.minor = WATCHDOG_MINOR,257	.name = "watchdog",258	.fops = &gef_wdt_fops,259};260 261 262static int gef_wdt_probe(struct platform_device *dev)263{264	int timeout = 10;265	u32 freq;266 267	bus_clk = 133; /* in MHz */268 269	freq = fsl_get_sys_freq();270	if (freq != -1)271		bus_clk = freq;272 273	/* Map devices registers into memory */274	gef_wdt_regs = of_iomap(dev->dev.of_node, 0);275	if (gef_wdt_regs == NULL)276		return -ENOMEM;277 278	gef_wdt_set_timeout(timeout);279 280	gef_wdt_handler_disable();	/* in case timer was already running */281 282	return misc_register(&gef_wdt_miscdev);283}284 285static void gef_wdt_remove(struct platform_device *dev)286{287	misc_deregister(&gef_wdt_miscdev);288 289	gef_wdt_handler_disable();290 291	iounmap(gef_wdt_regs);292}293 294static const struct of_device_id gef_wdt_ids[] = {295	{296		.compatible = "gef,fpga-wdt",297	},298	{},299};300MODULE_DEVICE_TABLE(of, gef_wdt_ids);301 302static struct platform_driver gef_wdt_driver = {303	.driver = {304		.name = "gef_wdt",305		.of_match_table = gef_wdt_ids,306	},307	.probe		= gef_wdt_probe,308	.remove_new	= gef_wdt_remove,309};310 311static int __init gef_wdt_init(void)312{313	pr_info("GE watchdog driver\n");314	return platform_driver_register(&gef_wdt_driver);315}316 317static void __exit gef_wdt_exit(void)318{319	platform_driver_unregister(&gef_wdt_driver);320}321 322module_init(gef_wdt_init);323module_exit(gef_wdt_exit);324 325MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>");326MODULE_DESCRIPTION("GE watchdog driver");327MODULE_LICENSE("GPL");328MODULE_ALIAS("platform:gef_wdt");329