linux/drivers/watchdog/rc32434_wdt.c
<<
>>
Prefs
   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");
 327