Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  *  IDT Interprise 79RC32434 watchdog driver
0004  *
0005  *  Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
0006  *  Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
0007  *
0008  *  based on
0009  *  SoftDog 0.05:   A Software Watchdog Device
0010  *
0011  *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
0012  *                  All Rights Reserved.
0013  */
0014 
0015 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0016 
0017 #include <linux/module.h>       /* For module specific items */
0018 #include <linux/moduleparam.h>      /* For new moduleparam's */
0019 #include <linux/types.h>        /* For standard types (like size_t) */
0020 #include <linux/errno.h>        /* For the -ENODEV/... values */
0021 #include <linux/kernel.h>       /* For printk/panic/... */
0022 #include <linux/fs.h>           /* For file operations */
0023 #include <linux/miscdevice.h>       /* For struct miscdevice */
0024 #include <linux/watchdog.h>     /* For the watchdog specific items */
0025 #include <linux/init.h>         /* For __init/__exit/... */
0026 #include <linux/platform_device.h>  /* For platform_driver framework */
0027 #include <linux/spinlock.h>     /* For spin_lock/spin_unlock/... */
0028 #include <linux/uaccess.h>      /* For copy_to_user/put_user/... */
0029 #include <linux/io.h>           /* For devm_ioremap */
0030 
0031 #include <asm/mach-rc32434/integ.h> /* For the Watchdog registers */
0032 
0033 #define VERSION "1.0"
0034 
0035 static struct {
0036     unsigned long inuse;
0037     spinlock_t io_lock;
0038 } rc32434_wdt_device;
0039 
0040 static struct integ __iomem *wdt_reg;
0041 
0042 static int expect_close;
0043 
0044 /* Board internal clock speed in Hz,
0045  * the watchdog timer ticks at. */
0046 extern unsigned int idt_cpu_freq;
0047 
0048 /* translate wtcompare value to seconds and vice versa */
0049 #define WTCOMP2SEC(x)   (x / idt_cpu_freq)
0050 #define SEC2WTCOMP(x)   (x * idt_cpu_freq)
0051 
0052 /* Use a default timeout of 20s. This should be
0053  * safe for CPU clock speeds up to 400MHz, as
0054  * ((2 ^ 32) - 1) / (400MHz / 2) = 21s.  */
0055 #define WATCHDOG_TIMEOUT 20
0056 
0057 static int timeout = WATCHDOG_TIMEOUT;
0058 module_param(timeout, int, 0);
0059 MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (default="
0060         __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
0061 
0062 static bool nowayout = WATCHDOG_NOWAYOUT;
0063 module_param(nowayout, bool, 0);
0064 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
0065     __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0066 
0067 /* apply or and nand masks to data read from addr and write back */
0068 #define SET_BITS(addr, or, nand) \
0069     writel((readl(&addr) | or) & ~nand, &addr)
0070 
0071 static int rc32434_wdt_set(int new_timeout)
0072 {
0073     int max_to = WTCOMP2SEC((u32)-1);
0074 
0075     if (new_timeout < 0 || new_timeout > max_to) {
0076         pr_err("timeout value must be between 0 and %d\n", max_to);
0077         return -EINVAL;
0078     }
0079     timeout = new_timeout;
0080     spin_lock(&rc32434_wdt_device.io_lock);
0081     writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
0082     spin_unlock(&rc32434_wdt_device.io_lock);
0083 
0084     return 0;
0085 }
0086 
0087 static void rc32434_wdt_start(void)
0088 {
0089     u32 or, nand;
0090 
0091     spin_lock(&rc32434_wdt_device.io_lock);
0092 
0093     /* zero the counter before enabling */
0094     writel(0, &wdt_reg->wtcount);
0095 
0096     /* don't generate a non-maskable interrupt,
0097      * do a warm reset instead */
0098     nand = 1 << RC32434_ERR_WNE;
0099     or = 1 << RC32434_ERR_WRE;
0100 
0101     /* reset the ERRCS timeout bit in case it's set */
0102     nand |= 1 << RC32434_ERR_WTO;
0103 
0104     SET_BITS(wdt_reg->errcs, or, nand);
0105 
0106     /* set the timeout (either default or based on module param) */
0107     rc32434_wdt_set(timeout);
0108 
0109     /* reset WTC timeout bit and enable WDT */
0110     nand = 1 << RC32434_WTC_TO;
0111     or = 1 << RC32434_WTC_EN;
0112 
0113     SET_BITS(wdt_reg->wtc, or, nand);
0114 
0115     spin_unlock(&rc32434_wdt_device.io_lock);
0116     pr_info("Started watchdog timer\n");
0117 }
0118 
0119 static void rc32434_wdt_stop(void)
0120 {
0121     spin_lock(&rc32434_wdt_device.io_lock);
0122 
0123     /* Disable WDT */
0124     SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
0125 
0126     spin_unlock(&rc32434_wdt_device.io_lock);
0127     pr_info("Stopped watchdog timer\n");
0128 }
0129 
0130 static void rc32434_wdt_ping(void)
0131 {
0132     spin_lock(&rc32434_wdt_device.io_lock);
0133     writel(0, &wdt_reg->wtcount);
0134     spin_unlock(&rc32434_wdt_device.io_lock);
0135 }
0136 
0137 static int rc32434_wdt_open(struct inode *inode, struct file *file)
0138 {
0139     if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
0140         return -EBUSY;
0141 
0142     if (nowayout)
0143         __module_get(THIS_MODULE);
0144 
0145     rc32434_wdt_start();
0146     rc32434_wdt_ping();
0147 
0148     return stream_open(inode, file);
0149 }
0150 
0151 static int rc32434_wdt_release(struct inode *inode, struct file *file)
0152 {
0153     if (expect_close == 42) {
0154         rc32434_wdt_stop();
0155         module_put(THIS_MODULE);
0156     } else {
0157         pr_crit("device closed unexpectedly. WDT will not stop!\n");
0158         rc32434_wdt_ping();
0159     }
0160     clear_bit(0, &rc32434_wdt_device.inuse);
0161     return 0;
0162 }
0163 
0164 static ssize_t rc32434_wdt_write(struct file *file, const char *data,
0165                 size_t len, loff_t *ppos)
0166 {
0167     if (len) {
0168         if (!nowayout) {
0169             size_t i;
0170 
0171             /* In case it was set long ago */
0172             expect_close = 0;
0173 
0174             for (i = 0; i != len; i++) {
0175                 char c;
0176                 if (get_user(c, data + i))
0177                     return -EFAULT;
0178                 if (c == 'V')
0179                     expect_close = 42;
0180             }
0181         }
0182         rc32434_wdt_ping();
0183         return len;
0184     }
0185     return 0;
0186 }
0187 
0188 static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
0189                 unsigned long arg)
0190 {
0191     void __user *argp = (void __user *)arg;
0192     int new_timeout;
0193     unsigned int value;
0194     static const struct watchdog_info ident = {
0195         .options =      WDIOF_SETTIMEOUT |
0196                     WDIOF_KEEPALIVEPING |
0197                     WDIOF_MAGICCLOSE,
0198         .identity =     "RC32434_WDT Watchdog",
0199     };
0200     switch (cmd) {
0201     case WDIOC_GETSUPPORT:
0202         if (copy_to_user(argp, &ident, sizeof(ident)))
0203             return -EFAULT;
0204         break;
0205     case WDIOC_GETSTATUS:
0206     case WDIOC_GETBOOTSTATUS:
0207         value = 0;
0208         if (copy_to_user(argp, &value, sizeof(int)))
0209             return -EFAULT;
0210         break;
0211     case WDIOC_SETOPTIONS:
0212         if (copy_from_user(&value, argp, sizeof(int)))
0213             return -EFAULT;
0214         switch (value) {
0215         case WDIOS_ENABLECARD:
0216             rc32434_wdt_start();
0217             break;
0218         case WDIOS_DISABLECARD:
0219             rc32434_wdt_stop();
0220             break;
0221         default:
0222             return -EINVAL;
0223         }
0224         break;
0225     case WDIOC_KEEPALIVE:
0226         rc32434_wdt_ping();
0227         break;
0228     case WDIOC_SETTIMEOUT:
0229         if (copy_from_user(&new_timeout, argp, sizeof(int)))
0230             return -EFAULT;
0231         if (rc32434_wdt_set(new_timeout))
0232             return -EINVAL;
0233         fallthrough;
0234     case WDIOC_GETTIMEOUT:
0235         return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
0236     default:
0237         return -ENOTTY;
0238     }
0239 
0240     return 0;
0241 }
0242 
0243 static const struct file_operations rc32434_wdt_fops = {
0244     .owner      = THIS_MODULE,
0245     .llseek     = no_llseek,
0246     .write      = rc32434_wdt_write,
0247     .unlocked_ioctl = rc32434_wdt_ioctl,
0248     .compat_ioctl   = compat_ptr_ioctl,
0249     .open       = rc32434_wdt_open,
0250     .release    = rc32434_wdt_release,
0251 };
0252 
0253 static struct miscdevice rc32434_wdt_miscdev = {
0254     .minor  = WATCHDOG_MINOR,
0255     .name   = "watchdog",
0256     .fops   = &rc32434_wdt_fops,
0257 };
0258 
0259 static int rc32434_wdt_probe(struct platform_device *pdev)
0260 {
0261     int ret;
0262     struct resource *r;
0263 
0264     r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
0265     if (!r) {
0266         pr_err("failed to retrieve resources\n");
0267         return -ENODEV;
0268     }
0269 
0270     wdt_reg = devm_ioremap(&pdev->dev, r->start, resource_size(r));
0271     if (!wdt_reg) {
0272         pr_err("failed to remap I/O resources\n");
0273         return -ENXIO;
0274     }
0275 
0276     spin_lock_init(&rc32434_wdt_device.io_lock);
0277 
0278     /* Make sure the watchdog is not running */
0279     rc32434_wdt_stop();
0280 
0281     /* Check that the heartbeat value is within it's range;
0282      * if not reset to the default */
0283     if (rc32434_wdt_set(timeout)) {
0284         rc32434_wdt_set(WATCHDOG_TIMEOUT);
0285         pr_info("timeout value must be between 0 and %d\n",
0286             WTCOMP2SEC((u32)-1));
0287     }
0288 
0289     ret = misc_register(&rc32434_wdt_miscdev);
0290     if (ret < 0) {
0291         pr_err("failed to register watchdog device\n");
0292         return ret;
0293     }
0294 
0295     pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",
0296         timeout);
0297 
0298     return 0;
0299 }
0300 
0301 static int rc32434_wdt_remove(struct platform_device *pdev)
0302 {
0303     misc_deregister(&rc32434_wdt_miscdev);
0304     return 0;
0305 }
0306 
0307 static void rc32434_wdt_shutdown(struct platform_device *pdev)
0308 {
0309     rc32434_wdt_stop();
0310 }
0311 
0312 static struct platform_driver rc32434_wdt_driver = {
0313     .probe      = rc32434_wdt_probe,
0314     .remove     = rc32434_wdt_remove,
0315     .shutdown   = rc32434_wdt_shutdown,
0316     .driver     = {
0317             .name = "rc32434_wdt",
0318     }
0319 };
0320 
0321 module_platform_driver(rc32434_wdt_driver);
0322 
0323 MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
0324         "Florian Fainelli <florian@openwrt.org>");
0325 MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
0326 MODULE_LICENSE("GPL");