linux/drivers/watchdog/orion5x_wdt.c
<<
>>
Prefs
   1/*
   2 * drivers/watchdog/orion5x_wdt.c
   3 *
   4 * Watchdog driver for Orion5x processors
   5 *
   6 * Author: Sylver Bruneau <sylver.bruneau@googlemail.com>
   7 *
   8 * This file is licensed under  the terms of the GNU General Public
   9 * License version 2. This program is licensed "as is" without any
  10 * warranty of any kind, whether express or implied.
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/moduleparam.h>
  15#include <linux/types.h>
  16#include <linux/kernel.h>
  17#include <linux/fs.h>
  18#include <linux/miscdevice.h>
  19#include <linux/watchdog.h>
  20#include <linux/init.h>
  21#include <linux/uaccess.h>
  22#include <linux/io.h>
  23#include <linux/spinlock.h>
  24
  25/*
  26 * Watchdog timer block registers.
  27 */
  28#define TIMER_CTRL              (TIMER_VIRT_BASE + 0x0000)
  29#define  WDT_EN                 0x0010
  30#define WDT_VAL                 (TIMER_VIRT_BASE + 0x0024)
  31
  32#define WDT_MAX_DURATION        (0xffffffff / ORION5X_TCLK)
  33#define WDT_IN_USE              0
  34#define WDT_OK_TO_CLOSE         1
  35
  36static int nowayout = WATCHDOG_NOWAYOUT;
  37static int heartbeat =  WDT_MAX_DURATION;       /* (seconds) */
  38static unsigned long wdt_status;
  39static spinlock_t wdt_lock;
  40
  41static void wdt_enable(void)
  42{
  43        u32 reg;
  44
  45        spin_lock(&wdt_lock);
  46
  47        /* Set watchdog duration */
  48        writel(ORION5X_TCLK * heartbeat, WDT_VAL);
  49
  50        /* Clear watchdog timer interrupt */
  51        reg = readl(BRIDGE_CAUSE);
  52        reg &= ~WDT_INT_REQ;
  53        writel(reg, BRIDGE_CAUSE);
  54
  55        /* Enable watchdog timer */
  56        reg = readl(TIMER_CTRL);
  57        reg |= WDT_EN;
  58        writel(reg, TIMER_CTRL);
  59
  60        /* Enable reset on watchdog */
  61        reg = readl(CPU_RESET_MASK);
  62        reg |= WDT_RESET;
  63        writel(reg, CPU_RESET_MASK);
  64
  65        spin_unlock(&wdt_lock);
  66}
  67
  68static void wdt_disable(void)
  69{
  70        u32 reg;
  71
  72        spin_lock(&wdt_lock);
  73
  74        /* Disable reset on watchdog */
  75        reg = readl(CPU_RESET_MASK);
  76        reg &= ~WDT_RESET;
  77        writel(reg, CPU_RESET_MASK);
  78
  79        /* Disable watchdog timer */
  80        reg = readl(TIMER_CTRL);
  81        reg &= ~WDT_EN;
  82        writel(reg, TIMER_CTRL);
  83
  84        spin_unlock(&wdt_lock);
  85}
  86
  87static int orion5x_wdt_get_timeleft(int *time_left)
  88{
  89        spin_lock(&wdt_lock);
  90        *time_left = readl(WDT_VAL) / ORION5X_TCLK;
  91        spin_unlock(&wdt_lock);
  92        return 0;
  93}
  94
  95static int orion5x_wdt_open(struct inode *inode, struct file *file)
  96{
  97        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  98                return -EBUSY;
  99        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 100        wdt_enable();
 101        return nonseekable_open(inode, file);
 102}
 103
 104static ssize_t orion5x_wdt_write(struct file *file, const char *data,
 105                                        size_t len, loff_t *ppos)
 106{
 107        if (len) {
 108                if (!nowayout) {
 109                        size_t i;
 110
 111                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 112                        for (i = 0; i != len; i++) {
 113                                char c;
 114
 115                                if (get_user(c, data + i))
 116                                        return -EFAULT;
 117                                if (c == 'V')
 118                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 119                        }
 120                }
 121                wdt_enable();
 122        }
 123        return len;
 124}
 125
 126static struct watchdog_info ident = {
 127        .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
 128                          WDIOF_KEEPALIVEPING,
 129        .identity       = "Orion5x Watchdog",
 130};
 131
 132
 133static long orion5x_wdt_ioctl(struct file *file, unsigned int cmd,
 134                                unsigned long arg)
 135{
 136        int ret = -ENOTTY;
 137        int time;
 138
 139        switch (cmd) {
 140        case WDIOC_GETSUPPORT:
 141                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 142                                   sizeof(ident)) ? -EFAULT : 0;
 143                break;
 144
 145        case WDIOC_GETSTATUS:
 146        case WDIOC_GETBOOTSTATUS:
 147                ret = put_user(0, (int *)arg);
 148                break;
 149
 150        case WDIOC_KEEPALIVE:
 151                wdt_enable();
 152                ret = 0;
 153                break;
 154
 155        case WDIOC_SETTIMEOUT:
 156                ret = get_user(time, (int *)arg);
 157                if (ret)
 158                        break;
 159
 160                if (time <= 0 || time > WDT_MAX_DURATION) {
 161                        ret = -EINVAL;
 162                        break;
 163                }
 164                heartbeat = time;
 165                wdt_enable();
 166                /* Fall through */
 167
 168        case WDIOC_GETTIMEOUT:
 169                ret = put_user(heartbeat, (int *)arg);
 170                break;
 171
 172        case WDIOC_GETTIMELEFT:
 173                if (orion5x_wdt_get_timeleft(&time)) {
 174                        ret = -EINVAL;
 175                        break;
 176                }
 177                ret = put_user(time, (int *)arg);
 178                break;
 179        }
 180        return ret;
 181}
 182
 183static int orion5x_wdt_release(struct inode *inode, struct file *file)
 184{
 185        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 186                wdt_disable();
 187        else
 188                printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
 189                                        "timer will not stop\n");
 190        clear_bit(WDT_IN_USE, &wdt_status);
 191        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 192
 193        return 0;
 194}
 195
 196
 197static const struct file_operations orion5x_wdt_fops = {
 198        .owner          = THIS_MODULE,
 199        .llseek         = no_llseek,
 200        .write          = orion5x_wdt_write,
 201        .unlocked_ioctl = orion5x_wdt_ioctl,
 202        .open           = orion5x_wdt_open,
 203        .release        = orion5x_wdt_release,
 204};
 205
 206static struct miscdevice orion5x_wdt_miscdev = {
 207        .minor          = WATCHDOG_MINOR,
 208        .name           = "watchdog",
 209        .fops           = &orion5x_wdt_fops,
 210};
 211
 212static int __init orion5x_wdt_init(void)
 213{
 214        int ret;
 215
 216        spin_lock_init(&wdt_lock);
 217
 218        ret = misc_register(&orion5x_wdt_miscdev);
 219        if (ret == 0)
 220                printk("Orion5x Watchdog Timer: heartbeat %d sec\n",
 221                                                                heartbeat);
 222
 223        return ret;
 224}
 225
 226static void __exit orion5x_wdt_exit(void)
 227{
 228        misc_deregister(&orion5x_wdt_miscdev);
 229}
 230
 231module_init(orion5x_wdt_init);
 232module_exit(orion5x_wdt_exit);
 233
 234MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>");
 235MODULE_DESCRIPTION("Orion5x Processor Watchdog");
 236
 237module_param(heartbeat, int, 0);
 238MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default is "
 239                                        __MODULE_STRING(WDT_MAX_DURATION) ")");
 240
 241module_param(nowayout, int, 0);
 242MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 243
 244MODULE_LICENSE("GPL");
 245MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 246