linux/drivers/watchdog/ts72xx_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for Technologic Systems TS-72xx based SBCs
   3 * (TS-7200, TS-7250 and TS-7260). These boards have external
   4 * glue logic CPLD chip, which includes programmable watchdog
   5 * timer.
   6 *
   7 * Copyright (c) 2009 Mika Westerberg <mika.westerberg@iki.fi>
   8 *
   9 * This driver is based on ep93xx_wdt and wm831x_wdt drivers.
  10 *
  11 * This file is licensed under the terms of the GNU General Public
  12 * License version 2. This program is licensed "as is" without any
  13 * warranty of any kind, whether express or implied.
  14 */
  15
  16#include <linux/fs.h>
  17#include <linux/io.h>
  18#include <linux/module.h>
  19#include <linux/moduleparam.h>
  20#include <linux/miscdevice.h>
  21#include <linux/mutex.h>
  22#include <linux/platform_device.h>
  23#include <linux/slab.h>
  24#include <linux/watchdog.h>
  25#include <linux/uaccess.h>
  26
  27#define TS72XX_WDT_FEED_VAL             0x05
  28#define TS72XX_WDT_DEFAULT_TIMEOUT      8
  29
  30static int timeout = TS72XX_WDT_DEFAULT_TIMEOUT;
  31module_param(timeout, int, 0);
  32MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. "
  33                          "(1 <= timeout <= 8, default="
  34                          __MODULE_STRING(TS72XX_WDT_DEFAULT_TIMEOUT)
  35                          ")");
  36
  37static bool nowayout = WATCHDOG_NOWAYOUT;
  38module_param(nowayout, bool, 0);
  39MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  40
  41/**
  42 * struct ts72xx_wdt - watchdog control structure
  43 * @lock: lock that protects this structure
  44 * @regval: watchdog timeout value suitable for control register
  45 * @flags: flags controlling watchdog device state
  46 * @control_reg: watchdog control register
  47 * @feed_reg: watchdog feed register
  48 * @pdev: back pointer to platform dev
  49 */
  50struct ts72xx_wdt {
  51        struct mutex    lock;
  52        int             regval;
  53
  54#define TS72XX_WDT_BUSY_FLAG            1
  55#define TS72XX_WDT_EXPECT_CLOSE_FLAG    2
  56        int             flags;
  57
  58        void __iomem    *control_reg;
  59        void __iomem    *feed_reg;
  60
  61        struct platform_device *pdev;
  62};
  63
  64static struct platform_device *ts72xx_wdt_pdev;
  65
  66/*
  67 * TS-72xx Watchdog supports following timeouts (value written
  68 * to control register):
  69 *      value   description
  70 *      -------------------------
  71 *      0x00    watchdog disabled
  72 *      0x01    250ms
  73 *      0x02    500ms
  74 *      0x03    1s
  75 *      0x04    reserved
  76 *      0x05    2s
  77 *      0x06    4s
  78 *      0x07    8s
  79 *
  80 * Timeouts below 1s are not very usable so we don't
  81 * allow them at all.
  82 *
  83 * We provide two functions that convert between these:
  84 * timeout_to_regval() and regval_to_timeout().
  85 */
  86static const struct {
  87        int     timeout;
  88        int     regval;
  89} ts72xx_wdt_map[] = {
  90        { 1, 3 },
  91        { 2, 5 },
  92        { 4, 6 },
  93        { 8, 7 },
  94};
  95
  96/**
  97 * timeout_to_regval() - converts given timeout to control register value
  98 * @new_timeout: timeout in seconds to be converted
  99 *
 100 * Function converts given @new_timeout into valid value that can
 101 * be programmed into watchdog control register. When conversion is
 102 * not possible, function returns %-EINVAL.
 103 */
 104static int timeout_to_regval(int new_timeout)
 105{
 106        int i;
 107
 108        /* first limit it to 1 - 8 seconds */
 109        new_timeout = clamp_val(new_timeout, 1, 8);
 110
 111        for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) {
 112                if (ts72xx_wdt_map[i].timeout >= new_timeout)
 113                        return ts72xx_wdt_map[i].regval;
 114        }
 115
 116        return -EINVAL;
 117}
 118
 119/**
 120 * regval_to_timeout() - converts control register value to timeout
 121 * @regval: control register value to be converted
 122 *
 123 * Function converts given @regval to timeout in seconds (1, 2, 4 or 8).
 124 * If @regval cannot be converted, function returns %-EINVAL.
 125 */
 126static int regval_to_timeout(int regval)
 127{
 128        int i;
 129
 130        for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) {
 131                if (ts72xx_wdt_map[i].regval == regval)
 132                        return ts72xx_wdt_map[i].timeout;
 133        }
 134
 135        return -EINVAL;
 136}
 137
 138/**
 139 * ts72xx_wdt_kick() - kick the watchdog
 140 * @wdt: watchdog to be kicked
 141 *
 142 * Called with @wdt->lock held.
 143 */
 144static inline void ts72xx_wdt_kick(struct ts72xx_wdt *wdt)
 145{
 146        __raw_writeb(TS72XX_WDT_FEED_VAL, wdt->feed_reg);
 147}
 148
 149/**
 150 * ts72xx_wdt_start() - starts the watchdog timer
 151 * @wdt: watchdog to be started
 152 *
 153 * This function programs timeout to watchdog timer
 154 * and starts it.
 155 *
 156 * Called with @wdt->lock held.
 157 */
 158static void ts72xx_wdt_start(struct ts72xx_wdt *wdt)
 159{
 160        /*
 161         * To program the wdt, it first must be "fed" and
 162         * only after that (within 30 usecs) the configuration
 163         * can be changed.
 164         */
 165        ts72xx_wdt_kick(wdt);
 166        __raw_writeb((u8)wdt->regval, wdt->control_reg);
 167}
 168
 169/**
 170 * ts72xx_wdt_stop() - stops the watchdog timer
 171 * @wdt: watchdog to be stopped
 172 *
 173 * Called with @wdt->lock held.
 174 */
 175static void ts72xx_wdt_stop(struct ts72xx_wdt *wdt)
 176{
 177        ts72xx_wdt_kick(wdt);
 178        __raw_writeb(0, wdt->control_reg);
 179}
 180
 181static int ts72xx_wdt_open(struct inode *inode, struct file *file)
 182{
 183        struct ts72xx_wdt *wdt = platform_get_drvdata(ts72xx_wdt_pdev);
 184        int regval;
 185
 186        /*
 187         * Try to convert default timeout to valid register
 188         * value first.
 189         */
 190        regval = timeout_to_regval(timeout);
 191        if (regval < 0) {
 192                dev_err(&wdt->pdev->dev,
 193                        "failed to convert timeout (%d) to register value\n",
 194                        timeout);
 195                return regval;
 196        }
 197
 198        if (mutex_lock_interruptible(&wdt->lock))
 199                return -ERESTARTSYS;
 200
 201        if ((wdt->flags & TS72XX_WDT_BUSY_FLAG) != 0) {
 202                mutex_unlock(&wdt->lock);
 203                return -EBUSY;
 204        }
 205
 206        wdt->flags = TS72XX_WDT_BUSY_FLAG;
 207        wdt->regval = regval;
 208        file->private_data = wdt;
 209
 210        ts72xx_wdt_start(wdt);
 211
 212        mutex_unlock(&wdt->lock);
 213        return nonseekable_open(inode, file);
 214}
 215
 216static int ts72xx_wdt_release(struct inode *inode, struct file *file)
 217{
 218        struct ts72xx_wdt *wdt = file->private_data;
 219
 220        if (mutex_lock_interruptible(&wdt->lock))
 221                return -ERESTARTSYS;
 222
 223        if ((wdt->flags & TS72XX_WDT_EXPECT_CLOSE_FLAG) != 0) {
 224                ts72xx_wdt_stop(wdt);
 225        } else {
 226                dev_warn(&wdt->pdev->dev,
 227                         "TS-72XX WDT device closed unexpectly. "
 228                         "Watchdog timer will not stop!\n");
 229                /*
 230                 * Kick it one more time, to give userland some time
 231                 * to recover (for example, respawning the kicker
 232                 * daemon).
 233                 */
 234                ts72xx_wdt_kick(wdt);
 235        }
 236
 237        wdt->flags = 0;
 238
 239        mutex_unlock(&wdt->lock);
 240        return 0;
 241}
 242
 243static ssize_t ts72xx_wdt_write(struct file *file,
 244                                const char __user *data,
 245                                size_t len,
 246                                loff_t *ppos)
 247{
 248        struct ts72xx_wdt *wdt = file->private_data;
 249
 250        if (!len)
 251                return 0;
 252
 253        if (mutex_lock_interruptible(&wdt->lock))
 254                return -ERESTARTSYS;
 255
 256        ts72xx_wdt_kick(wdt);
 257
 258        /*
 259         * Support for magic character closing. User process
 260         * writes 'V' into the device, just before it is closed.
 261         * This means that we know that the wdt timer can be
 262         * stopped after user closes the device.
 263         */
 264        if (!nowayout) {
 265                int i;
 266
 267                for (i = 0; i < len; i++) {
 268                        char c;
 269
 270                        /* In case it was set long ago */
 271                        wdt->flags &= ~TS72XX_WDT_EXPECT_CLOSE_FLAG;
 272
 273                        if (get_user(c, data + i)) {
 274                                mutex_unlock(&wdt->lock);
 275                                return -EFAULT;
 276                        }
 277                        if (c == 'V') {
 278                                wdt->flags |= TS72XX_WDT_EXPECT_CLOSE_FLAG;
 279                                break;
 280                        }
 281                }
 282        }
 283
 284        mutex_unlock(&wdt->lock);
 285        return len;
 286}
 287
 288static const struct watchdog_info winfo = {
 289        .options                = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
 290                                  WDIOF_MAGICCLOSE,
 291        .firmware_version       = 1,
 292        .identity               = "TS-72XX WDT",
 293};
 294
 295static long ts72xx_wdt_ioctl(struct file *file, unsigned int cmd,
 296                             unsigned long arg)
 297{
 298        struct ts72xx_wdt *wdt = file->private_data;
 299        void __user *argp = (void __user *)arg;
 300        int __user *p = (int __user *)argp;
 301        int error = 0;
 302
 303        if (mutex_lock_interruptible(&wdt->lock))
 304                return -ERESTARTSYS;
 305
 306        switch (cmd) {
 307        case WDIOC_GETSUPPORT:
 308                if (copy_to_user(argp, &winfo, sizeof(winfo)))
 309                        error = -EFAULT;
 310                break;
 311
 312        case WDIOC_GETSTATUS:
 313        case WDIOC_GETBOOTSTATUS:
 314                error = put_user(0, p);
 315                break;
 316
 317        case WDIOC_KEEPALIVE:
 318                ts72xx_wdt_kick(wdt);
 319                break;
 320
 321        case WDIOC_SETOPTIONS: {
 322                int options;
 323
 324                error = get_user(options, p);
 325                if (error)
 326                        break;
 327
 328                error = -EINVAL;
 329
 330                if ((options & WDIOS_DISABLECARD) != 0) {
 331                        ts72xx_wdt_stop(wdt);
 332                        error = 0;
 333                }
 334                if ((options & WDIOS_ENABLECARD) != 0) {
 335                        ts72xx_wdt_start(wdt);
 336                        error = 0;
 337                }
 338
 339                break;
 340        }
 341
 342        case WDIOC_SETTIMEOUT: {
 343                int new_timeout;
 344                int regval;
 345
 346                error = get_user(new_timeout, p);
 347                if (error)
 348                        break;
 349
 350                regval = timeout_to_regval(new_timeout);
 351                if (regval < 0) {
 352                        error = regval;
 353                        break;
 354                }
 355                ts72xx_wdt_stop(wdt);
 356                wdt->regval = regval;
 357                ts72xx_wdt_start(wdt);
 358
 359                /*FALLTHROUGH*/
 360        }
 361
 362        case WDIOC_GETTIMEOUT:
 363                error = put_user(regval_to_timeout(wdt->regval), p);
 364                break;
 365
 366        default:
 367                error = -ENOTTY;
 368                break;
 369        }
 370
 371        mutex_unlock(&wdt->lock);
 372        return error;
 373}
 374
 375static const struct file_operations ts72xx_wdt_fops = {
 376        .owner          = THIS_MODULE,
 377        .llseek         = no_llseek,
 378        .open           = ts72xx_wdt_open,
 379        .release        = ts72xx_wdt_release,
 380        .write          = ts72xx_wdt_write,
 381        .unlocked_ioctl = ts72xx_wdt_ioctl,
 382};
 383
 384static struct miscdevice ts72xx_wdt_miscdev = {
 385        .minor          = WATCHDOG_MINOR,
 386        .name           = "watchdog",
 387        .fops           = &ts72xx_wdt_fops,
 388};
 389
 390static int ts72xx_wdt_probe(struct platform_device *pdev)
 391{
 392        struct ts72xx_wdt *wdt;
 393        struct resource *r1, *r2;
 394        int error = 0;
 395
 396        wdt = devm_kzalloc(&pdev->dev, sizeof(struct ts72xx_wdt), GFP_KERNEL);
 397        if (!wdt)
 398                return -ENOMEM;
 399
 400        r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 401        wdt->control_reg = devm_ioremap_resource(&pdev->dev, r1);
 402        if (IS_ERR(wdt->control_reg))
 403                return PTR_ERR(wdt->control_reg);
 404
 405        r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 406        wdt->feed_reg = devm_ioremap_resource(&pdev->dev, r2);
 407        if (IS_ERR(wdt->feed_reg))
 408                return PTR_ERR(wdt->feed_reg);
 409
 410        platform_set_drvdata(pdev, wdt);
 411        ts72xx_wdt_pdev = pdev;
 412        wdt->pdev = pdev;
 413        mutex_init(&wdt->lock);
 414
 415        /* make sure that the watchdog is disabled */
 416        ts72xx_wdt_stop(wdt);
 417
 418        error = misc_register(&ts72xx_wdt_miscdev);
 419        if (error) {
 420                dev_err(&pdev->dev, "failed to register miscdev\n");
 421                return error;
 422        }
 423
 424        dev_info(&pdev->dev, "TS-72xx Watchdog driver\n");
 425
 426        return 0;
 427}
 428
 429static int ts72xx_wdt_remove(struct platform_device *pdev)
 430{
 431        misc_deregister(&ts72xx_wdt_miscdev);
 432        return 0;
 433}
 434
 435static struct platform_driver ts72xx_wdt_driver = {
 436        .probe          = ts72xx_wdt_probe,
 437        .remove         = ts72xx_wdt_remove,
 438        .driver         = {
 439                .name   = "ts72xx-wdt",
 440        },
 441};
 442
 443module_platform_driver(ts72xx_wdt_driver);
 444
 445MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
 446MODULE_DESCRIPTION("TS-72xx SBC Watchdog");
 447MODULE_LICENSE("GPL");
 448MODULE_ALIAS("platform:ts72xx-wdt");
 449