rc32434_wdt.c (8115B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * IDT Interprise 79RC32434 watchdog driver 4 * 5 * Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org> 6 * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org> 7 * 8 * based on 9 * SoftDog 0.05: A Software Watchdog Device 10 * 11 * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, 12 * All Rights Reserved. 13 */ 14 15#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 16 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 be 53 * safe for CPU clock speeds up to 400MHz, as 54 * ((2 ^ 32) - 1) / (400MHz / 2) = 21s. */ 55#define WATCHDOG_TIMEOUT 20 56 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 .llseek = no_llseek, 246 .write = rc32434_wdt_write, 247 .unlocked_ioctl = rc32434_wdt_ioctl, 248 .compat_ioctl = compat_ptr_ioctl, 249 .open = rc32434_wdt_open, 250 .release = rc32434_wdt_release, 251}; 252 253static struct miscdevice rc32434_wdt_miscdev = { 254 .minor = WATCHDOG_MINOR, 255 .name = "watchdog", 256 .fops = &rc32434_wdt_fops, 257}; 258 259static int rc32434_wdt_probe(struct platform_device *pdev) 260{ 261 int ret; 262 struct resource *r; 263 264 r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res"); 265 if (!r) { 266 pr_err("failed to retrieve resources\n"); 267 return -ENODEV; 268 } 269 270 wdt_reg = devm_ioremap(&pdev->dev, r->start, resource_size(r)); 271 if (!wdt_reg) { 272 pr_err("failed to remap I/O resources\n"); 273 return -ENXIO; 274 } 275 276 spin_lock_init(&rc32434_wdt_device.io_lock); 277 278 /* Make sure the watchdog is not running */ 279 rc32434_wdt_stop(); 280 281 /* Check that the heartbeat value is within it's range; 282 * if not reset to the default */ 283 if (rc32434_wdt_set(timeout)) { 284 rc32434_wdt_set(WATCHDOG_TIMEOUT); 285 pr_info("timeout value must be between 0 and %d\n", 286 WTCOMP2SEC((u32)-1)); 287 } 288 289 ret = misc_register(&rc32434_wdt_miscdev); 290 if (ret < 0) { 291 pr_err("failed to register watchdog device\n"); 292 return ret; 293 } 294 295 pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n", 296 timeout); 297 298 return 0; 299} 300 301static int rc32434_wdt_remove(struct platform_device *pdev) 302{ 303 misc_deregister(&rc32434_wdt_miscdev); 304 return 0; 305} 306 307static void rc32434_wdt_shutdown(struct platform_device *pdev) 308{ 309 rc32434_wdt_stop(); 310} 311 312static struct platform_driver rc32434_wdt_driver = { 313 .probe = rc32434_wdt_probe, 314 .remove = rc32434_wdt_remove, 315 .shutdown = rc32434_wdt_shutdown, 316 .driver = { 317 .name = "rc32434_wdt", 318 } 319}; 320 321module_platform_driver(rc32434_wdt_driver); 322 323MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>," 324 "Florian Fainelli <florian@openwrt.org>"); 325MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog"); 326MODULE_LICENSE("GPL");