linux/drivers/watchdog/txx9wdt.c
<<
>>
Prefs
   1/*
   2 * txx9wdt: A Hardware Watchdog Driver for TXx9 SoCs
   3 *
   4 * Copyright (C) 2007 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 */
  10#include <linux/module.h>
  11#include <linux/moduleparam.h>
  12#include <linux/types.h>
  13#include <linux/miscdevice.h>
  14#include <linux/watchdog.h>
  15#include <linux/fs.h>
  16#include <linux/reboot.h>
  17#include <linux/init.h>
  18#include <linux/uaccess.h>
  19#include <linux/platform_device.h>
  20#include <linux/clk.h>
  21#include <linux/err.h>
  22#include <linux/io.h>
  23#include <asm/txx9tmr.h>
  24
  25#define TIMER_MARGIN    60              /* Default is 60 seconds */
  26
  27static int timeout = TIMER_MARGIN;      /* in seconds */
  28module_param(timeout, int, 0);
  29MODULE_PARM_DESC(timeout,
  30        "Watchdog timeout in seconds. "
  31        "(0<timeout<((2^" __MODULE_STRING(TXX9_TIMER_BITS) ")/(IMCLK/256)), "
  32        "default=" __MODULE_STRING(TIMER_MARGIN) ")");
  33
  34static int nowayout = WATCHDOG_NOWAYOUT;
  35module_param(nowayout, int, 0);
  36MODULE_PARM_DESC(nowayout,
  37        "Watchdog cannot be stopped once started "
  38        "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  39
  40#define WD_TIMER_CCD    7       /* 1/256 */
  41#define WD_TIMER_CLK    (clk_get_rate(txx9_imclk) / (2 << WD_TIMER_CCD))
  42#define WD_MAX_TIMEOUT  ((0xffffffff >> (32 - TXX9_TIMER_BITS)) / WD_TIMER_CLK)
  43
  44static unsigned long txx9wdt_alive;
  45static int expect_close;
  46static struct txx9_tmr_reg __iomem *txx9wdt_reg;
  47static struct clk *txx9_imclk;
  48static DEFINE_SPINLOCK(txx9_lock);
  49
  50static void txx9wdt_ping(void)
  51{
  52        spin_lock(&txx9_lock);
  53        __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr);
  54        spin_unlock(&txx9_lock);
  55}
  56
  57static void txx9wdt_start(void)
  58{
  59        spin_lock(&txx9_lock);
  60        __raw_writel(WD_TIMER_CLK * timeout, &txx9wdt_reg->cpra);
  61        __raw_writel(WD_TIMER_CCD, &txx9wdt_reg->ccdr);
  62        __raw_writel(0, &txx9wdt_reg->tisr);    /* clear pending interrupt */
  63        __raw_writel(TXx9_TMTCR_TCE | TXx9_TMTCR_CCDE | TXx9_TMTCR_TMODE_WDOG,
  64                     &txx9wdt_reg->tcr);
  65        __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr);
  66        spin_unlock(&txx9_lock);
  67}
  68
  69static void txx9wdt_stop(void)
  70{
  71        spin_lock(&txx9_lock);
  72        __raw_writel(TXx9_TMWTMR_WDIS, &txx9wdt_reg->wtmr);
  73        __raw_writel(__raw_readl(&txx9wdt_reg->tcr) & ~TXx9_TMTCR_TCE,
  74                     &txx9wdt_reg->tcr);
  75        spin_unlock(&txx9_lock);
  76}
  77
  78static int txx9wdt_open(struct inode *inode, struct file *file)
  79{
  80        if (test_and_set_bit(0, &txx9wdt_alive))
  81                return -EBUSY;
  82
  83        if (__raw_readl(&txx9wdt_reg->tcr) & TXx9_TMTCR_TCE) {
  84                clear_bit(0, &txx9wdt_alive);
  85                return -EBUSY;
  86        }
  87
  88        if (nowayout)
  89                __module_get(THIS_MODULE);
  90
  91        txx9wdt_start();
  92        return nonseekable_open(inode, file);
  93}
  94
  95static int txx9wdt_release(struct inode *inode, struct file *file)
  96{
  97        if (expect_close)
  98                txx9wdt_stop();
  99        else {
 100                printk(KERN_CRIT "txx9wdt: "
 101                       "Unexpected close, not stopping watchdog!\n");
 102                txx9wdt_ping();
 103        }
 104        clear_bit(0, &txx9wdt_alive);
 105        expect_close = 0;
 106        return 0;
 107}
 108
 109static ssize_t txx9wdt_write(struct file *file, const char __user *data,
 110                             size_t len, loff_t *ppos)
 111{
 112        if (len) {
 113                if (!nowayout) {
 114                        size_t i;
 115
 116                        expect_close = 0;
 117                        for (i = 0; i != len; i++) {
 118                                char c;
 119                                if (get_user(c, data + i))
 120                                        return -EFAULT;
 121                                if (c == 'V')
 122                                        expect_close = 1;
 123                        }
 124                }
 125                txx9wdt_ping();
 126        }
 127        return len;
 128}
 129
 130static long txx9wdt_ioctl(struct file *file, unsigned int cmd,
 131                                                        unsigned long arg)
 132{
 133        void __user *argp = (void __user *)arg;
 134        int __user *p = argp;
 135        int new_timeout;
 136        static const struct watchdog_info ident = {
 137                .options =              WDIOF_SETTIMEOUT |
 138                                        WDIOF_KEEPALIVEPING |
 139                                        WDIOF_MAGICCLOSE,
 140                .firmware_version =     0,
 141                .identity =             "Hardware Watchdog for TXx9",
 142        };
 143
 144        switch (cmd) {
 145        case WDIOC_GETSUPPORT:
 146                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 147        case WDIOC_GETSTATUS:
 148        case WDIOC_GETBOOTSTATUS:
 149                return put_user(0, p);
 150        case WDIOC_KEEPALIVE:
 151                txx9wdt_ping();
 152                return 0;
 153        case WDIOC_SETTIMEOUT:
 154                if (get_user(new_timeout, p))
 155                        return -EFAULT;
 156                if (new_timeout < 1 || new_timeout > WD_MAX_TIMEOUT)
 157                        return -EINVAL;
 158                timeout = new_timeout;
 159                txx9wdt_stop();
 160                txx9wdt_start();
 161                /* Fall */
 162        case WDIOC_GETTIMEOUT:
 163                return put_user(timeout, p);
 164        default:
 165                return -ENOTTY;
 166        }
 167}
 168
 169static int txx9wdt_notify_sys(struct notifier_block *this, unsigned long code,
 170        void *unused)
 171{
 172        if (code == SYS_DOWN || code == SYS_HALT)
 173                txx9wdt_stop();
 174        return NOTIFY_DONE;
 175}
 176
 177static const struct file_operations txx9wdt_fops = {
 178        .owner          =       THIS_MODULE,
 179        .llseek         =       no_llseek,
 180        .write          =       txx9wdt_write,
 181        .unlocked_ioctl =       txx9wdt_ioctl,
 182        .open           =       txx9wdt_open,
 183        .release        =       txx9wdt_release,
 184};
 185
 186static struct miscdevice txx9wdt_miscdev = {
 187        .minor  =       WATCHDOG_MINOR,
 188        .name   =       "watchdog",
 189        .fops   =       &txx9wdt_fops,
 190};
 191
 192static struct notifier_block txx9wdt_notifier = {
 193        .notifier_call = txx9wdt_notify_sys,
 194};
 195
 196static int __init txx9wdt_probe(struct platform_device *dev)
 197{
 198        struct resource *res;
 199        int ret;
 200
 201        txx9_imclk = clk_get(NULL, "imbus_clk");
 202        if (IS_ERR(txx9_imclk)) {
 203                ret = PTR_ERR(txx9_imclk);
 204                txx9_imclk = NULL;
 205                goto exit;
 206        }
 207        ret = clk_enable(txx9_imclk);
 208        if (ret) {
 209                clk_put(txx9_imclk);
 210                txx9_imclk = NULL;
 211                goto exit;
 212        }
 213
 214        res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 215        if (!res)
 216                goto exit_busy;
 217        if (!devm_request_mem_region(&dev->dev,
 218                                     res->start, res->end - res->start + 1,
 219                                     "txx9wdt"))
 220                goto exit_busy;
 221        txx9wdt_reg = devm_ioremap(&dev->dev,
 222                                   res->start, res->end - res->start + 1);
 223        if (!txx9wdt_reg)
 224                goto exit_busy;
 225
 226        ret = register_reboot_notifier(&txx9wdt_notifier);
 227        if (ret)
 228                goto exit;
 229
 230        ret = misc_register(&txx9wdt_miscdev);
 231        if (ret) {
 232                unregister_reboot_notifier(&txx9wdt_notifier);
 233                goto exit;
 234        }
 235
 236        printk(KERN_INFO "Hardware Watchdog Timer for TXx9: "
 237               "timeout=%d sec (max %ld) (nowayout= %d)\n",
 238               timeout, WD_MAX_TIMEOUT, nowayout);
 239
 240        return 0;
 241exit_busy:
 242        ret = -EBUSY;
 243exit:
 244        if (txx9_imclk) {
 245                clk_disable(txx9_imclk);
 246                clk_put(txx9_imclk);
 247        }
 248        return ret;
 249}
 250
 251static int __exit txx9wdt_remove(struct platform_device *dev)
 252{
 253        misc_deregister(&txx9wdt_miscdev);
 254        unregister_reboot_notifier(&txx9wdt_notifier);
 255        clk_disable(txx9_imclk);
 256        clk_put(txx9_imclk);
 257        return 0;
 258}
 259
 260static struct platform_driver txx9wdt_driver = {
 261        .remove = __exit_p(txx9wdt_remove),
 262        .driver = {
 263                .name = "txx9wdt",
 264                .owner = THIS_MODULE,
 265        },
 266};
 267
 268static int __init watchdog_init(void)
 269{
 270        return platform_driver_probe(&txx9wdt_driver, txx9wdt_probe);
 271}
 272
 273static void __exit watchdog_exit(void)
 274{
 275        platform_driver_unregister(&txx9wdt_driver);
 276}
 277
 278module_init(watchdog_init);
 279module_exit(watchdog_exit);
 280
 281MODULE_DESCRIPTION("TXx9 Watchdog Driver");
 282MODULE_LICENSE("GPL");
 283MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 284MODULE_ALIAS("platform:txx9wdt");
 285