linux/drivers/watchdog/geodewdt.c
<<
>>
Prefs
   1/* Watchdog timer for machines with the CS5535/CS5536 companion chip
   2 *
   3 * Copyright (C) 2006-2007, Advanced Micro Devices, Inc.
   4 * Copyright (C) 2009  Andres Salomon <dilinger@collabora.co.uk>
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License
   8 * as published by the Free Software Foundation; either version
   9 * 2 of the License, or (at your option) any later version.
  10 */
  11
  12
  13#include <linux/module.h>
  14#include <linux/moduleparam.h>
  15#include <linux/types.h>
  16#include <linux/miscdevice.h>
  17#include <linux/watchdog.h>
  18#include <linux/fs.h>
  19#include <linux/platform_device.h>
  20#include <linux/reboot.h>
  21#include <linux/uaccess.h>
  22
  23#include <linux/cs5535.h>
  24
  25#define GEODEWDT_HZ 500
  26#define GEODEWDT_SCALE 6
  27#define GEODEWDT_MAX_SECONDS 131
  28
  29#define WDT_FLAGS_OPEN 1
  30#define WDT_FLAGS_ORPHAN 2
  31
  32#define DRV_NAME "geodewdt"
  33#define WATCHDOG_NAME "Geode GX/LX WDT"
  34#define WATCHDOG_TIMEOUT 60
  35
  36static int timeout = WATCHDOG_TIMEOUT;
  37module_param(timeout, int, 0);
  38MODULE_PARM_DESC(timeout,
  39        "Watchdog timeout in seconds. 1<= timeout <=131, default="
  40                                __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
  41
  42static int nowayout = WATCHDOG_NOWAYOUT;
  43module_param(nowayout, int, 0);
  44MODULE_PARM_DESC(nowayout,
  45        "Watchdog cannot be stopped once started (default="
  46                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  47
  48static struct platform_device *geodewdt_platform_device;
  49static unsigned long wdt_flags;
  50static struct cs5535_mfgpt_timer *wdt_timer;
  51static int safe_close;
  52
  53static void geodewdt_ping(void)
  54{
  55        /* Stop the counter */
  56        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
  57
  58        /* Reset the counter */
  59        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
  60
  61        /* Enable the counter */
  62        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
  63}
  64
  65static void geodewdt_disable(void)
  66{
  67        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
  68        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
  69}
  70
  71static int geodewdt_set_heartbeat(int val)
  72{
  73        if (val < 1 || val > GEODEWDT_MAX_SECONDS)
  74                return -EINVAL;
  75
  76        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
  77        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2, val * GEODEWDT_HZ);
  78        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
  79        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
  80
  81        timeout = val;
  82        return 0;
  83}
  84
  85static int geodewdt_open(struct inode *inode, struct file *file)
  86{
  87        if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags))
  88                return -EBUSY;
  89
  90        if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags))
  91                __module_get(THIS_MODULE);
  92
  93        geodewdt_ping();
  94        return nonseekable_open(inode, file);
  95}
  96
  97static int geodewdt_release(struct inode *inode, struct file *file)
  98{
  99        if (safe_close) {
 100                geodewdt_disable();
 101                module_put(THIS_MODULE);
 102        } else {
 103                printk(KERN_CRIT "Unexpected close - watchdog is not stopping.\n");
 104                geodewdt_ping();
 105
 106                set_bit(WDT_FLAGS_ORPHAN, &wdt_flags);
 107        }
 108
 109        clear_bit(WDT_FLAGS_OPEN, &wdt_flags);
 110        safe_close = 0;
 111        return 0;
 112}
 113
 114static ssize_t geodewdt_write(struct file *file, const char __user *data,
 115                                size_t len, loff_t *ppos)
 116{
 117        if (len) {
 118                if (!nowayout) {
 119                        size_t i;
 120                        safe_close = 0;
 121
 122                        for (i = 0; i != len; i++) {
 123                                char c;
 124
 125                                if (get_user(c, data + i))
 126                                        return -EFAULT;
 127
 128                                if (c == 'V')
 129                                        safe_close = 1;
 130                        }
 131                }
 132
 133                geodewdt_ping();
 134        }
 135        return len;
 136}
 137
 138static long geodewdt_ioctl(struct file *file, unsigned int cmd,
 139                                unsigned long arg)
 140{
 141        void __user *argp = (void __user *)arg;
 142        int __user *p = argp;
 143        int interval;
 144
 145        static const struct watchdog_info ident = {
 146                .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
 147                | WDIOF_MAGICCLOSE,
 148                .firmware_version =     1,
 149                .identity =             WATCHDOG_NAME,
 150        };
 151
 152        switch (cmd) {
 153        case WDIOC_GETSUPPORT:
 154                return copy_to_user(argp, &ident,
 155                                    sizeof(ident)) ? -EFAULT : 0;
 156                break;
 157
 158        case WDIOC_GETSTATUS:
 159        case WDIOC_GETBOOTSTATUS:
 160                return put_user(0, p);
 161
 162        case WDIOC_SETOPTIONS:
 163        {
 164                int options, ret = -EINVAL;
 165
 166                if (get_user(options, p))
 167                        return -EFAULT;
 168
 169                if (options & WDIOS_DISABLECARD) {
 170                        geodewdt_disable();
 171                        ret = 0;
 172                }
 173
 174                if (options & WDIOS_ENABLECARD) {
 175                        geodewdt_ping();
 176                        ret = 0;
 177                }
 178
 179                return ret;
 180        }
 181        case WDIOC_KEEPALIVE:
 182                geodewdt_ping();
 183                return 0;
 184
 185        case WDIOC_SETTIMEOUT:
 186                if (get_user(interval, p))
 187                        return -EFAULT;
 188
 189                if (geodewdt_set_heartbeat(interval))
 190                        return -EINVAL;
 191        /* Fall through */
 192        case WDIOC_GETTIMEOUT:
 193                return put_user(timeout, p);
 194
 195        default:
 196                return -ENOTTY;
 197        }
 198
 199        return 0;
 200}
 201
 202static const struct file_operations geodewdt_fops = {
 203        .owner          = THIS_MODULE,
 204        .llseek         = no_llseek,
 205        .write          = geodewdt_write,
 206        .unlocked_ioctl = geodewdt_ioctl,
 207        .open           = geodewdt_open,
 208        .release        = geodewdt_release,
 209};
 210
 211static struct miscdevice geodewdt_miscdev = {
 212        .minor = WATCHDOG_MINOR,
 213        .name = "watchdog",
 214        .fops = &geodewdt_fops,
 215};
 216
 217static int __devinit geodewdt_probe(struct platform_device *dev)
 218{
 219        int ret;
 220
 221        wdt_timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
 222        if (!wdt_timer) {
 223                printk(KERN_ERR "geodewdt:  No timers were available\n");
 224                return -ENODEV;
 225        }
 226
 227        /* Set up the timer */
 228
 229        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP,
 230                          GEODEWDT_SCALE | (3 << 8));
 231
 232        /* Set up comparator 2 to reset when the event fires */
 233        cs5535_mfgpt_toggle_event(wdt_timer, MFGPT_CMP2, MFGPT_EVENT_RESET, 1);
 234
 235        /* Set up the initial timeout */
 236
 237        cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2,
 238                timeout * GEODEWDT_HZ);
 239
 240        ret = misc_register(&geodewdt_miscdev);
 241
 242        return ret;
 243}
 244
 245static int __devexit geodewdt_remove(struct platform_device *dev)
 246{
 247        misc_deregister(&geodewdt_miscdev);
 248        return 0;
 249}
 250
 251static void geodewdt_shutdown(struct platform_device *dev)
 252{
 253        geodewdt_disable();
 254}
 255
 256static struct platform_driver geodewdt_driver = {
 257        .probe          = geodewdt_probe,
 258        .remove         = __devexit_p(geodewdt_remove),
 259        .shutdown       = geodewdt_shutdown,
 260        .driver         = {
 261                .owner  = THIS_MODULE,
 262                .name   = DRV_NAME,
 263        },
 264};
 265
 266static int __init geodewdt_init(void)
 267{
 268        int ret;
 269
 270        ret = platform_driver_register(&geodewdt_driver);
 271        if (ret)
 272                return ret;
 273
 274        geodewdt_platform_device = platform_device_register_simple(DRV_NAME,
 275                                                                -1, NULL, 0);
 276        if (IS_ERR(geodewdt_platform_device)) {
 277                ret = PTR_ERR(geodewdt_platform_device);
 278                goto err;
 279        }
 280
 281        return 0;
 282err:
 283        platform_driver_unregister(&geodewdt_driver);
 284        return ret;
 285}
 286
 287static void __exit geodewdt_exit(void)
 288{
 289        platform_device_unregister(geodewdt_platform_device);
 290        platform_driver_unregister(&geodewdt_driver);
 291}
 292
 293module_init(geodewdt_init);
 294module_exit(geodewdt_exit);
 295
 296MODULE_AUTHOR("Advanced Micro Devices, Inc");
 297MODULE_DESCRIPTION("Geode GX/LX Watchdog Driver");
 298MODULE_LICENSE("GPL");
 299MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 300