linux/drivers/watchdog/dw_wdt.c
<<
>>
Prefs
   1/*
   2 * Copyright 2010-2011 Picochip Ltd., Jamie Iles
   3 * http://www.picochip.com
   4 *
   5 * This program is free software; you can redistribute it and/or
   6 * modify it under the terms of the GNU General Public License
   7 * as published by the Free Software Foundation; either version
   8 * 2 of the License, or (at your option) any later version.
   9 *
  10 * This file implements a driver for the Synopsys DesignWare watchdog device
  11 * in the many ARM subsystems. The watchdog has 16 different timeout periods
  12 * and these are a function of the input clock frequency.
  13 *
  14 * The DesignWare watchdog cannot be stopped once it has been started so we
  15 * use a software timer to implement a ping that will keep the watchdog alive.
  16 * If we receive an expected close for the watchdog then we keep the timer
  17 * running, otherwise the timer is stopped and the watchdog will expire.
  18 */
  19#define pr_fmt(fmt) "dw_wdt: " fmt
  20
  21#include <linux/bitops.h>
  22#include <linux/clk.h>
  23#include <linux/device.h>
  24#include <linux/err.h>
  25#include <linux/fs.h>
  26#include <linux/io.h>
  27#include <linux/kernel.h>
  28#include <linux/miscdevice.h>
  29#include <linux/module.h>
  30#include <linux/moduleparam.h>
  31#include <linux/pm.h>
  32#include <linux/platform_device.h>
  33#include <linux/spinlock.h>
  34#include <linux/timer.h>
  35#include <linux/uaccess.h>
  36#include <linux/watchdog.h>
  37
  38#define WDOG_CONTROL_REG_OFFSET             0x00
  39#define WDOG_CONTROL_REG_WDT_EN_MASK        0x01
  40#define WDOG_TIMEOUT_RANGE_REG_OFFSET       0x04
  41#define WDOG_CURRENT_COUNT_REG_OFFSET       0x08
  42#define WDOG_COUNTER_RESTART_REG_OFFSET     0x0c
  43#define WDOG_COUNTER_RESTART_KICK_VALUE     0x76
  44
  45/* The maximum TOP (timeout period) value that can be set in the watchdog. */
  46#define DW_WDT_MAX_TOP          15
  47
  48static int nowayout = WATCHDOG_NOWAYOUT;
  49module_param(nowayout, int, 0);
  50MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  51                 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  52
  53#define WDT_TIMEOUT             (HZ / 2)
  54
  55static struct {
  56        spinlock_t              lock;
  57        void __iomem            *regs;
  58        struct clk              *clk;
  59        unsigned long           in_use;
  60        unsigned long           next_heartbeat;
  61        struct timer_list       timer;
  62        int                     expect_close;
  63} dw_wdt;
  64
  65static inline int dw_wdt_is_enabled(void)
  66{
  67        return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) &
  68                WDOG_CONTROL_REG_WDT_EN_MASK;
  69}
  70
  71static inline int dw_wdt_top_in_seconds(unsigned top)
  72{
  73        /*
  74         * There are 16 possible timeout values in 0..15 where the number of
  75         * cycles is 2 ^ (16 + i) and the watchdog counts down.
  76         */
  77        return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk);
  78}
  79
  80static int dw_wdt_get_top(void)
  81{
  82        int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
  83
  84        return dw_wdt_top_in_seconds(top);
  85}
  86
  87static inline void dw_wdt_set_next_heartbeat(void)
  88{
  89        dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ;
  90}
  91
  92static int dw_wdt_set_top(unsigned top_s)
  93{
  94        int i, top_val = DW_WDT_MAX_TOP;
  95
  96        /*
  97         * Iterate over the timeout values until we find the closest match. We
  98         * always look for >=.
  99         */
 100        for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
 101                if (dw_wdt_top_in_seconds(i) >= top_s) {
 102                        top_val = i;
 103                        break;
 104                }
 105
 106        /* Set the new value in the watchdog. */
 107        writel(top_val, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
 108
 109        dw_wdt_set_next_heartbeat();
 110
 111        return dw_wdt_top_in_seconds(top_val);
 112}
 113
 114static void dw_wdt_keepalive(void)
 115{
 116        writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs +
 117               WDOG_COUNTER_RESTART_REG_OFFSET);
 118}
 119
 120static void dw_wdt_ping(unsigned long data)
 121{
 122        if (time_before(jiffies, dw_wdt.next_heartbeat) ||
 123            (!nowayout && !dw_wdt.in_use)) {
 124                dw_wdt_keepalive();
 125                mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
 126        } else
 127                pr_crit("keepalive missed, machine will reset\n");
 128}
 129
 130static int dw_wdt_open(struct inode *inode, struct file *filp)
 131{
 132        if (test_and_set_bit(0, &dw_wdt.in_use))
 133                return -EBUSY;
 134
 135        /* Make sure we don't get unloaded. */
 136        __module_get(THIS_MODULE);
 137
 138        spin_lock(&dw_wdt.lock);
 139        if (!dw_wdt_is_enabled()) {
 140                /*
 141                 * The watchdog is not currently enabled. Set the timeout to
 142                 * the maximum and then start it.
 143                 */
 144                dw_wdt_set_top(DW_WDT_MAX_TOP);
 145                writel(WDOG_CONTROL_REG_WDT_EN_MASK,
 146                       dw_wdt.regs + WDOG_CONTROL_REG_OFFSET);
 147        }
 148
 149        dw_wdt_set_next_heartbeat();
 150
 151        spin_unlock(&dw_wdt.lock);
 152
 153        return nonseekable_open(inode, filp);
 154}
 155
 156ssize_t dw_wdt_write(struct file *filp, const char __user *buf, size_t len,
 157                     loff_t *offset)
 158{
 159        if (!len)
 160                return 0;
 161
 162        if (!nowayout) {
 163                size_t i;
 164
 165                dw_wdt.expect_close = 0;
 166
 167                for (i = 0; i < len; ++i) {
 168                        char c;
 169
 170                        if (get_user(c, buf + i))
 171                                return -EFAULT;
 172
 173                        if (c == 'V') {
 174                                dw_wdt.expect_close = 1;
 175                                break;
 176                        }
 177                }
 178        }
 179
 180        dw_wdt_set_next_heartbeat();
 181        mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
 182
 183        return len;
 184}
 185
 186static u32 dw_wdt_time_left(void)
 187{
 188        return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
 189                clk_get_rate(dw_wdt.clk);
 190}
 191
 192static const struct watchdog_info dw_wdt_ident = {
 193        .options        = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
 194                          WDIOF_MAGICCLOSE,
 195        .identity       = "Synopsys DesignWare Watchdog",
 196};
 197
 198static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 199{
 200        unsigned long val;
 201        int timeout;
 202
 203        switch (cmd) {
 204        case WDIOC_GETSUPPORT:
 205                return copy_to_user((struct watchdog_info *)arg, &dw_wdt_ident,
 206                                    sizeof(dw_wdt_ident)) ? -EFAULT : 0;
 207
 208        case WDIOC_GETSTATUS:
 209        case WDIOC_GETBOOTSTATUS:
 210                return put_user(0, (int *)arg);
 211
 212        case WDIOC_KEEPALIVE:
 213                dw_wdt_set_next_heartbeat();
 214                return 0;
 215
 216        case WDIOC_SETTIMEOUT:
 217                if (get_user(val, (int __user *)arg))
 218                        return -EFAULT;
 219                timeout = dw_wdt_set_top(val);
 220                return put_user(timeout , (int __user *)arg);
 221
 222        case WDIOC_GETTIMEOUT:
 223                return put_user(dw_wdt_get_top(), (int __user *)arg);
 224
 225        case WDIOC_GETTIMELEFT:
 226                /* Get the time left until expiry. */
 227                if (get_user(val, (int __user *)arg))
 228                        return -EFAULT;
 229                return put_user(dw_wdt_time_left(), (int __user *)arg);
 230
 231        default:
 232                return -ENOTTY;
 233        }
 234}
 235
 236static int dw_wdt_release(struct inode *inode, struct file *filp)
 237{
 238        clear_bit(0, &dw_wdt.in_use);
 239
 240        if (!dw_wdt.expect_close) {
 241                del_timer(&dw_wdt.timer);
 242
 243                if (!nowayout)
 244                        pr_crit("unexpected close, system will reboot soon\n");
 245                else
 246                        pr_crit("watchdog cannot be disabled, system will reboot soon\n");
 247        }
 248
 249        dw_wdt.expect_close = 0;
 250
 251        return 0;
 252}
 253
 254#ifdef CONFIG_PM
 255static int dw_wdt_suspend(struct device *dev)
 256{
 257        clk_disable(dw_wdt.clk);
 258
 259        return 0;
 260}
 261
 262static int dw_wdt_resume(struct device *dev)
 263{
 264        int err = clk_enable(dw_wdt.clk);
 265
 266        if (err)
 267                return err;
 268
 269        dw_wdt_keepalive();
 270
 271        return 0;
 272}
 273
 274static const struct dev_pm_ops dw_wdt_pm_ops = {
 275        .suspend        = dw_wdt_suspend,
 276        .resume         = dw_wdt_resume,
 277};
 278#endif /* CONFIG_PM */
 279
 280static const struct file_operations wdt_fops = {
 281        .owner          = THIS_MODULE,
 282        .llseek         = no_llseek,
 283        .open           = dw_wdt_open,
 284        .write          = dw_wdt_write,
 285        .unlocked_ioctl = dw_wdt_ioctl,
 286        .release        = dw_wdt_release
 287};
 288
 289static struct miscdevice dw_wdt_miscdev = {
 290        .fops           = &wdt_fops,
 291        .name           = "watchdog",
 292        .minor          = WATCHDOG_MINOR,
 293};
 294
 295static int __devinit dw_wdt_drv_probe(struct platform_device *pdev)
 296{
 297        int ret;
 298        struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 299
 300        if (!mem)
 301                return -EINVAL;
 302
 303        dw_wdt.regs = devm_request_and_ioremap(&pdev->dev, mem);
 304        if (!dw_wdt.regs)
 305                return -ENOMEM;
 306
 307        dw_wdt.clk = clk_get(&pdev->dev, NULL);
 308        if (IS_ERR(dw_wdt.clk))
 309                return PTR_ERR(dw_wdt.clk);
 310
 311        ret = clk_enable(dw_wdt.clk);
 312        if (ret)
 313                goto out_put_clk;
 314
 315        spin_lock_init(&dw_wdt.lock);
 316
 317        ret = misc_register(&dw_wdt_miscdev);
 318        if (ret)
 319                goto out_disable_clk;
 320
 321        dw_wdt_set_next_heartbeat();
 322        setup_timer(&dw_wdt.timer, dw_wdt_ping, 0);
 323        mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT);
 324
 325        return 0;
 326
 327out_disable_clk:
 328        clk_disable(dw_wdt.clk);
 329out_put_clk:
 330        clk_put(dw_wdt.clk);
 331
 332        return ret;
 333}
 334
 335static int __devexit dw_wdt_drv_remove(struct platform_device *pdev)
 336{
 337        misc_deregister(&dw_wdt_miscdev);
 338
 339        clk_disable(dw_wdt.clk);
 340        clk_put(dw_wdt.clk);
 341
 342        return 0;
 343}
 344
 345static struct platform_driver dw_wdt_driver = {
 346        .probe          = dw_wdt_drv_probe,
 347        .remove         = __devexit_p(dw_wdt_drv_remove),
 348        .driver         = {
 349                .name   = "dw_wdt",
 350                .owner  = THIS_MODULE,
 351#ifdef CONFIG_PM
 352                .pm     = &dw_wdt_pm_ops,
 353#endif /* CONFIG_PM */
 354        },
 355};
 356
 357module_platform_driver(dw_wdt_driver);
 358
 359MODULE_AUTHOR("Jamie Iles");
 360MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver");
 361MODULE_LICENSE("GPL");
 362MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 363
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.