linux/drivers/watchdog/orion_wdt.c
<<
>>
Prefs
   1/*
   2 * drivers/watchdog/orion_wdt.c
   3 *
   4 * Watchdog driver for Orion/Kirkwood 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/platform_device.h>
  20#include <linux/watchdog.h>
  21#include <linux/init.h>
  22#include <linux/uaccess.h>
  23#include <linux/io.h>
  24#include <linux/spinlock.h>
  25#include <mach/bridge-regs.h>
  26#include <plat/orion_wdt.h>
  27
  28/*
  29 * Watchdog timer block registers.
  30 */
  31#define TIMER_CTRL              (TIMER_VIRT_BASE + 0x0000)
  32#define  WDT_EN                 0x0010
  33#define WDT_VAL                 (TIMER_VIRT_BASE + 0x0024)
  34
  35#define WDT_MAX_CYCLE_COUNT     0xffffffff
  36#define WDT_IN_USE              0
  37#define WDT_OK_TO_CLOSE         1
  38
  39static int nowayout = WATCHDOG_NOWAYOUT;
  40static int heartbeat = -1;              /* module parameter (seconds) */
  41static unsigned int wdt_max_duration;   /* (seconds) */
  42static unsigned int wdt_tclk;
  43static unsigned long wdt_status;
  44static spinlock_t wdt_lock;
  45
  46static void orion_wdt_ping(void)
  47{
  48        spin_lock(&wdt_lock);
  49
  50        /* Reload watchdog duration */
  51        writel(wdt_tclk * heartbeat, WDT_VAL);
  52
  53        spin_unlock(&wdt_lock);
  54}
  55
  56static void orion_wdt_enable(void)
  57{
  58        u32 reg;
  59
  60        spin_lock(&wdt_lock);
  61
  62        /* Set watchdog duration */
  63        writel(wdt_tclk * heartbeat, WDT_VAL);
  64
  65        /* Clear watchdog timer interrupt */
  66        reg = readl(BRIDGE_CAUSE);
  67        reg &= ~WDT_INT_REQ;
  68        writel(reg, BRIDGE_CAUSE);
  69
  70        /* Enable watchdog timer */
  71        reg = readl(TIMER_CTRL);
  72        reg |= WDT_EN;
  73        writel(reg, TIMER_CTRL);
  74
  75        /* Enable reset on watchdog */
  76        reg = readl(RSTOUTn_MASK);
  77        reg |= WDT_RESET_OUT_EN;
  78        writel(reg, RSTOUTn_MASK);
  79
  80        spin_unlock(&wdt_lock);
  81}
  82
  83static void orion_wdt_disable(void)
  84{
  85        u32 reg;
  86
  87        spin_lock(&wdt_lock);
  88
  89        /* Disable reset on watchdog */
  90        reg = readl(RSTOUTn_MASK);
  91        reg &= ~WDT_RESET_OUT_EN;
  92        writel(reg, RSTOUTn_MASK);
  93
  94        /* Disable watchdog timer */
  95        reg = readl(TIMER_CTRL);
  96        reg &= ~WDT_EN;
  97        writel(reg, TIMER_CTRL);
  98
  99        spin_unlock(&wdt_lock);
 100}
 101
 102static int orion_wdt_get_timeleft(int *time_left)
 103{
 104        spin_lock(&wdt_lock);
 105        *time_left = readl(WDT_VAL) / wdt_tclk;
 106        spin_unlock(&wdt_lock);
 107        return 0;
 108}
 109
 110static int orion_wdt_open(struct inode *inode, struct file *file)
 111{
 112        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
 113                return -EBUSY;
 114        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 115        orion_wdt_enable();
 116        return nonseekable_open(inode, file);
 117}
 118
 119static ssize_t orion_wdt_write(struct file *file, const char *data,
 120                                        size_t len, loff_t *ppos)
 121{
 122        if (len) {
 123                if (!nowayout) {
 124                        size_t i;
 125
 126                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 127                        for (i = 0; i != len; i++) {
 128                                char c;
 129
 130                                if (get_user(c, data + i))
 131                                        return -EFAULT;
 132                                if (c == 'V')
 133                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 134                        }
 135                }
 136                orion_wdt_ping();
 137        }
 138        return len;
 139}
 140
 141static int orion_wdt_settimeout(int new_time)
 142{
 143        if ((new_time <= 0) || (new_time > wdt_max_duration))
 144                return -EINVAL;
 145
 146        /* Set new watchdog time to be used when
 147         * orion_wdt_enable() or orion_wdt_ping() is called. */
 148        heartbeat = new_time;
 149        return 0;
 150}
 151
 152static const struct watchdog_info ident = {
 153        .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
 154                          WDIOF_KEEPALIVEPING,
 155        .identity       = "Orion Watchdog",
 156};
 157
 158static long orion_wdt_ioctl(struct file *file, unsigned int cmd,
 159                                unsigned long arg)
 160{
 161        int ret = -ENOTTY;
 162        int time;
 163
 164        switch (cmd) {
 165        case WDIOC_GETSUPPORT:
 166                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 167                                   sizeof(ident)) ? -EFAULT : 0;
 168                break;
 169
 170        case WDIOC_GETSTATUS:
 171        case WDIOC_GETBOOTSTATUS:
 172                ret = put_user(0, (int *)arg);
 173                break;
 174
 175        case WDIOC_KEEPALIVE:
 176                orion_wdt_ping();
 177                ret = 0;
 178                break;
 179
 180        case WDIOC_SETTIMEOUT:
 181                ret = get_user(time, (int *)arg);
 182                if (ret)
 183                        break;
 184
 185                if (orion_wdt_settimeout(time)) {
 186                        ret = -EINVAL;
 187                        break;
 188                }
 189                orion_wdt_ping();
 190                /* Fall through */
 191
 192        case WDIOC_GETTIMEOUT:
 193                ret = put_user(heartbeat, (int *)arg);
 194                break;
 195
 196        case WDIOC_GETTIMELEFT:
 197                if (orion_wdt_get_timeleft(&time)) {
 198                        ret = -EINVAL;
 199                        break;
 200                }
 201                ret = put_user(time, (int *)arg);
 202                break;
 203        }
 204        return ret;
 205}
 206
 207static int orion_wdt_release(struct inode *inode, struct file *file)
 208{
 209        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 210                orion_wdt_disable();
 211        else
 212                printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
 213                                        "timer will not stop\n");
 214        clear_bit(WDT_IN_USE, &wdt_status);
 215        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 216
 217        return 0;
 218}
 219
 220
 221static const struct file_operations orion_wdt_fops = {
 222        .owner          = THIS_MODULE,
 223        .llseek         = no_llseek,
 224        .write          = orion_wdt_write,
 225        .unlocked_ioctl = orion_wdt_ioctl,
 226        .open           = orion_wdt_open,
 227        .release        = orion_wdt_release,
 228};
 229
 230static struct miscdevice orion_wdt_miscdev = {
 231        .minor          = WATCHDOG_MINOR,
 232        .name           = "watchdog",
 233        .fops           = &orion_wdt_fops,
 234};
 235
 236static int __devinit orion_wdt_probe(struct platform_device *pdev)
 237{
 238        struct orion_wdt_platform_data *pdata = pdev->dev.platform_data;
 239        int ret;
 240
 241        if (pdata) {
 242                wdt_tclk = pdata->tclk;
 243        } else {
 244                printk(KERN_ERR "Orion Watchdog misses platform data\n");
 245                return -ENODEV;
 246        }
 247
 248        if (orion_wdt_miscdev.parent)
 249                return -EBUSY;
 250        orion_wdt_miscdev.parent = &pdev->dev;
 251
 252        wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk;
 253        if (orion_wdt_settimeout(heartbeat))
 254                heartbeat = wdt_max_duration;
 255
 256        ret = misc_register(&orion_wdt_miscdev);
 257        if (ret)
 258                return ret;
 259
 260        printk(KERN_INFO "Orion Watchdog Timer: Initial timeout %d sec%s\n",
 261                                heartbeat, nowayout ? ", nowayout" : "");
 262        return 0;
 263}
 264
 265static int __devexit orion_wdt_remove(struct platform_device *pdev)
 266{
 267        int ret;
 268
 269        if (test_bit(WDT_IN_USE, &wdt_status)) {
 270                orion_wdt_disable();
 271                clear_bit(WDT_IN_USE, &wdt_status);
 272        }
 273
 274        ret = misc_deregister(&orion_wdt_miscdev);
 275        if (!ret)
 276                orion_wdt_miscdev.parent = NULL;
 277
 278        return ret;
 279}
 280
 281static void orion_wdt_shutdown(struct platform_device *pdev)
 282{
 283        if (test_bit(WDT_IN_USE, &wdt_status))
 284                orion_wdt_disable();
 285}
 286
 287static struct platform_driver orion_wdt_driver = {
 288        .probe          = orion_wdt_probe,
 289        .remove         = __devexit_p(orion_wdt_remove),
 290        .shutdown       = orion_wdt_shutdown,
 291        .driver         = {
 292                .owner  = THIS_MODULE,
 293                .name   = "orion_wdt",
 294        },
 295};
 296
 297static int __init orion_wdt_init(void)
 298{
 299        spin_lock_init(&wdt_lock);
 300        return platform_driver_register(&orion_wdt_driver);
 301}
 302
 303static void __exit orion_wdt_exit(void)
 304{
 305        platform_driver_unregister(&orion_wdt_driver);
 306}
 307
 308module_init(orion_wdt_init);
 309module_exit(orion_wdt_exit);
 310
 311MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>");
 312MODULE_DESCRIPTION("Orion Processor Watchdog");
 313
 314module_param(heartbeat, int, 0);
 315MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
 316
 317module_param(nowayout, int, 0);
 318MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
 319                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 320
 321MODULE_LICENSE("GPL");
 322MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 323