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