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