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