linux/drivers/watchdog/wm8350_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for the wm8350
   3 *
   4 * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com>
   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
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/moduleparam.h>
  13#include <linux/types.h>
  14#include <linux/kernel.h>
  15#include <linux/fs.h>
  16#include <linux/miscdevice.h>
  17#include <linux/platform_device.h>
  18#include <linux/watchdog.h>
  19#include <linux/uaccess.h>
  20#include <linux/mfd/wm8350/core.h>
  21
  22static int nowayout = WATCHDOG_NOWAYOUT;
  23module_param(nowayout, int, 0);
  24MODULE_PARM_DESC(nowayout,
  25                 "Watchdog cannot be stopped once started (default="
  26                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  27
  28static unsigned long wm8350_wdt_users;
  29static struct miscdevice wm8350_wdt_miscdev;
  30static int wm8350_wdt_expect_close;
  31static DEFINE_MUTEX(wdt_mutex);
  32
  33static struct {
  34        int time;  /* Seconds */
  35        u16 val;   /* To be set in WM8350_SYSTEM_CONTROL_2 */
  36} wm8350_wdt_cfgs[] = {
  37        { 1, 0x02 },
  38        { 2, 0x04 },
  39        { 4, 0x05 },
  40};
  41
  42static struct wm8350 *get_wm8350(void)
  43{
  44        return dev_get_drvdata(wm8350_wdt_miscdev.parent);
  45}
  46
  47static int wm8350_wdt_set_timeout(struct wm8350 *wm8350, u16 value)
  48{
  49        int ret;
  50        u16 reg;
  51
  52        mutex_lock(&wdt_mutex);
  53        wm8350_reg_unlock(wm8350);
  54
  55        reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
  56        reg &= ~WM8350_WDOG_TO_MASK;
  57        reg |= value;
  58        ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
  59
  60        wm8350_reg_lock(wm8350);
  61        mutex_unlock(&wdt_mutex);
  62
  63        return ret;
  64}
  65
  66static int wm8350_wdt_start(struct wm8350 *wm8350)
  67{
  68        int ret;
  69        u16 reg;
  70
  71        mutex_lock(&wdt_mutex);
  72        wm8350_reg_unlock(wm8350);
  73
  74        reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
  75        reg &= ~WM8350_WDOG_MODE_MASK;
  76        reg |= 0x20;
  77        ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
  78
  79        wm8350_reg_lock(wm8350);
  80        mutex_unlock(&wdt_mutex);
  81
  82        return ret;
  83}
  84
  85static int wm8350_wdt_stop(struct wm8350 *wm8350)
  86{
  87        int ret;
  88        u16 reg;
  89
  90        mutex_lock(&wdt_mutex);
  91        wm8350_reg_unlock(wm8350);
  92
  93        reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
  94        reg &= ~WM8350_WDOG_MODE_MASK;
  95        ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
  96
  97        wm8350_reg_lock(wm8350);
  98        mutex_unlock(&wdt_mutex);
  99
 100        return ret;
 101}
 102
 103static int wm8350_wdt_kick(struct wm8350 *wm8350)
 104{
 105        int ret;
 106        u16 reg;
 107
 108        mutex_lock(&wdt_mutex);
 109
 110        reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
 111        ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
 112
 113        mutex_unlock(&wdt_mutex);
 114
 115        return ret;
 116}
 117
 118static int wm8350_wdt_open(struct inode *inode, struct file *file)
 119{
 120        struct wm8350 *wm8350 = get_wm8350();
 121        int ret;
 122
 123        if (!wm8350)
 124                return -ENODEV;
 125
 126        if (test_and_set_bit(0, &wm8350_wdt_users))
 127                return -EBUSY;
 128
 129        ret = wm8350_wdt_start(wm8350);
 130        if (ret != 0)
 131                return ret;
 132
 133        return nonseekable_open(inode, file);
 134}
 135
 136static int wm8350_wdt_release(struct inode *inode, struct file *file)
 137{
 138        struct wm8350 *wm8350 = get_wm8350();
 139
 140        if (wm8350_wdt_expect_close)
 141                wm8350_wdt_stop(wm8350);
 142        else {
 143                dev_warn(wm8350->dev, "Watchdog device closed uncleanly\n");
 144                wm8350_wdt_kick(wm8350);
 145        }
 146
 147        clear_bit(0, &wm8350_wdt_users);
 148
 149        return 0;
 150}
 151
 152static ssize_t wm8350_wdt_write(struct file *file,
 153                                const char __user *data, size_t count,
 154                                loff_t *ppos)
 155{
 156        struct wm8350 *wm8350 = get_wm8350();
 157        size_t i;
 158
 159        if (count) {
 160                wm8350_wdt_kick(wm8350);
 161
 162                if (!nowayout) {
 163                        /* In case it was set long ago */
 164                        wm8350_wdt_expect_close = 0;
 165
 166                        /* scan to see whether or not we got the magic
 167                           character */
 168                        for (i = 0; i != count; i++) {
 169                                char c;
 170                                if (get_user(c, data + i))
 171                                        return -EFAULT;
 172                                if (c == 'V')
 173                                        wm8350_wdt_expect_close = 42;
 174                        }
 175                }
 176        }
 177        return count;
 178}
 179
 180static const struct watchdog_info ident = {
 181        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 182        .identity = "WM8350 Watchdog",
 183};
 184
 185static long wm8350_wdt_ioctl(struct file *file, unsigned int cmd,
 186                             unsigned long arg)
 187{
 188        struct wm8350 *wm8350 = get_wm8350();
 189        int ret = -ENOTTY, time, i;
 190        void __user *argp = (void __user *)arg;
 191        int __user *p = argp;
 192        u16 reg;
 193
 194        switch (cmd) {
 195        case WDIOC_GETSUPPORT:
 196                ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 197                break;
 198
 199        case WDIOC_GETSTATUS:
 200        case WDIOC_GETBOOTSTATUS:
 201                ret = put_user(0, p);
 202                break;
 203
 204        case WDIOC_SETOPTIONS:
 205        {
 206                int options;
 207
 208                if (get_user(options, p))
 209                        return -EFAULT;
 210
 211                ret = -EINVAL;
 212
 213                /* Setting both simultaneously means at least one must fail */
 214                if (options == WDIOS_DISABLECARD)
 215                        ret = wm8350_wdt_start(wm8350);
 216
 217                if (options == WDIOS_ENABLECARD)
 218                        ret = wm8350_wdt_stop(wm8350);
 219                break;
 220        }
 221
 222        case WDIOC_KEEPALIVE:
 223                ret = wm8350_wdt_kick(wm8350);
 224                break;
 225
 226        case WDIOC_SETTIMEOUT:
 227                ret = get_user(time, p);
 228                if (ret)
 229                        break;
 230
 231                if (time == 0) {
 232                        if (nowayout)
 233                                ret = -EINVAL;
 234                        else
 235                                wm8350_wdt_stop(wm8350);
 236                        break;
 237                }
 238
 239                for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
 240                        if (wm8350_wdt_cfgs[i].time == time)
 241                                break;
 242                if (i == ARRAY_SIZE(wm8350_wdt_cfgs))
 243                        ret = -EINVAL;
 244                else
 245                        ret = wm8350_wdt_set_timeout(wm8350,
 246                                                     wm8350_wdt_cfgs[i].val);
 247                break;
 248
 249        case WDIOC_GETTIMEOUT:
 250                reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
 251                reg &= WM8350_WDOG_TO_MASK;
 252                for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
 253                        if (wm8350_wdt_cfgs[i].val == reg)
 254                                break;
 255                if (i == ARRAY_SIZE(wm8350_wdt_cfgs)) {
 256                        dev_warn(wm8350->dev,
 257                                 "Unknown watchdog configuration: %x\n", reg);
 258                        ret = -EINVAL;
 259                } else
 260                        ret = put_user(wm8350_wdt_cfgs[i].time, p);
 261
 262        }
 263
 264        return ret;
 265}
 266
 267static const struct file_operations wm8350_wdt_fops = {
 268        .owner = THIS_MODULE,
 269        .llseek = no_llseek,
 270        .write = wm8350_wdt_write,
 271        .unlocked_ioctl = wm8350_wdt_ioctl,
 272        .open = wm8350_wdt_open,
 273        .release = wm8350_wdt_release,
 274};
 275
 276static struct miscdevice wm8350_wdt_miscdev = {
 277        .minor = WATCHDOG_MINOR,
 278        .name = "watchdog",
 279        .fops = &wm8350_wdt_fops,
 280};
 281
 282static int __devinit wm8350_wdt_probe(struct platform_device *pdev)
 283{
 284        struct wm8350 *wm8350 = platform_get_drvdata(pdev);
 285
 286        if (!wm8350) {
 287                pr_err("No driver data supplied\n");
 288                return -ENODEV;
 289        }
 290
 291        /* Default to 4s timeout */
 292        wm8350_wdt_set_timeout(wm8350, 0x05);
 293
 294        wm8350_wdt_miscdev.parent = &pdev->dev;
 295
 296        return misc_register(&wm8350_wdt_miscdev);
 297}
 298
 299static int __devexit wm8350_wdt_remove(struct platform_device *pdev)
 300{
 301        misc_deregister(&wm8350_wdt_miscdev);
 302
 303        return 0;
 304}
 305
 306static struct platform_driver wm8350_wdt_driver = {
 307        .probe = wm8350_wdt_probe,
 308        .remove = __devexit_p(wm8350_wdt_remove),
 309        .driver = {
 310                .name = "wm8350-wdt",
 311        },
 312};
 313
 314static int __init wm8350_wdt_init(void)
 315{
 316        return platform_driver_register(&wm8350_wdt_driver);
 317}
 318module_init(wm8350_wdt_init);
 319
 320static void __exit wm8350_wdt_exit(void)
 321{
 322        platform_driver_unregister(&wm8350_wdt_driver);
 323}
 324module_exit(wm8350_wdt_exit);
 325
 326MODULE_AUTHOR("Mark Brown");
 327MODULE_DESCRIPTION("WM8350 Watchdog");
 328MODULE_LICENSE("GPL");
 329MODULE_ALIAS("platform:wm8350-wdt");
 330