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