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 int nowayout = WATCHDOG_NOWAYOUT;
  38module_param(nowayout, int, 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
  64struct 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 -EINVAL;
 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                error = copy_to_user(argp, &winfo, sizeof(winfo));
 309                break;
 310
 311        case WDIOC_GETSTATUS:
 312        case WDIOC_GETBOOTSTATUS:
 313                return put_user(0, p);
 314
 315        case WDIOC_KEEPALIVE:
 316                ts72xx_wdt_kick(wdt);
 317                break;
 318
 319        case WDIOC_SETOPTIONS: {
 320                int options;
 321
 322                if (get_user(options, p)) {
 323                        error = -EFAULT;
 324                        break;
 325                }
 326
 327                error = -EINVAL;
 328
 329                if ((options & WDIOS_DISABLECARD) != 0) {
 330                        ts72xx_wdt_stop(wdt);
 331                        error = 0;
 332                }
 333                if ((options & WDIOS_ENABLECARD) != 0) {
 334                        ts72xx_wdt_start(wdt);
 335                        error = 0;
 336                }
 337
 338                break;
 339        }
 340
 341        case WDIOC_SETTIMEOUT: {
 342                int new_timeout;
 343
 344                if (get_user(new_timeout, p)) {
 345                        error = -EFAULT;
 346                } else {
 347                        int regval;
 348
 349                        regval = timeout_to_regval(new_timeout);
 350                        if (regval < 0) {
 351                                error = -EINVAL;
 352                        } else {
 353                                ts72xx_wdt_stop(wdt);
 354                                wdt->regval = regval;
 355                                ts72xx_wdt_start(wdt);
 356                        }
 357                }
 358                if (error)
 359                        break;
 360
 361                /*FALLTHROUGH*/
 362        }
 363
 364        case WDIOC_GETTIMEOUT:
 365                if (put_user(regval_to_timeout(wdt->regval), p))
 366                        error = -EFAULT;
 367                break;
 368
 369        default:
 370                error = -ENOTTY;
 371                break;
 372        }
 373
 374        mutex_unlock(&wdt->lock);
 375        return error;
 376}
 377
 378static const struct file_operations ts72xx_wdt_fops = {
 379        .owner          = THIS_MODULE,
 380        .llseek         = no_llseek,
 381        .open           = ts72xx_wdt_open,
 382        .release        = ts72xx_wdt_release,
 383        .write          = ts72xx_wdt_write,
 384        .unlocked_ioctl = ts72xx_wdt_ioctl,
 385};
 386
 387static struct miscdevice ts72xx_wdt_miscdev = {
 388        .minor          = WATCHDOG_MINOR,
 389        .name           = "watchdog",
 390        .fops           = &ts72xx_wdt_fops,
 391};
 392
 393static __devinit int ts72xx_wdt_probe(struct platform_device *pdev)
 394{
 395        struct ts72xx_wdt *wdt;
 396        struct resource *r1, *r2;
 397        int error = 0;
 398
 399        wdt = kzalloc(sizeof(struct ts72xx_wdt), GFP_KERNEL);
 400        if (!wdt) {
 401                dev_err(&pdev->dev, "failed to allocate memory\n");
 402                return -ENOMEM;
 403        }
 404
 405        r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 406        if (!r1) {
 407                dev_err(&pdev->dev, "failed to get memory resource\n");
 408                error = -ENODEV;
 409                goto fail;
 410        }
 411
 412        r1 = request_mem_region(r1->start, resource_size(r1), pdev->name);
 413        if (!r1) {
 414                dev_err(&pdev->dev, "cannot request memory region\n");
 415                error = -EBUSY;
 416                goto fail;
 417        }
 418
 419        wdt->control_reg = ioremap(r1->start, resource_size(r1));
 420        if (!wdt->control_reg) {
 421                dev_err(&pdev->dev, "failed to map memory\n");
 422                error = -ENODEV;
 423                goto fail_free_control;
 424        }
 425
 426        r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 427        if (!r2) {
 428                dev_err(&pdev->dev, "failed to get memory resource\n");
 429                error = -ENODEV;
 430                goto fail_unmap_control;
 431        }
 432
 433        r2 = request_mem_region(r2->start, resource_size(r2), pdev->name);
 434        if (!r2) {
 435                dev_err(&pdev->dev, "cannot request memory region\n");
 436                error = -EBUSY;
 437                goto fail_unmap_control;
 438        }
 439
 440        wdt->feed_reg = ioremap(r2->start, resource_size(r2));
 441        if (!wdt->feed_reg) {
 442                dev_err(&pdev->dev, "failed to map memory\n");
 443                error = -ENODEV;
 444                goto fail_free_feed;
 445        }
 446
 447        platform_set_drvdata(pdev, wdt);
 448        ts72xx_wdt_pdev = pdev;
 449        wdt->pdev = pdev;
 450        mutex_init(&wdt->lock);
 451
 452        /* make sure that the watchdog is disabled */
 453        ts72xx_wdt_stop(wdt);
 454
 455        error = misc_register(&ts72xx_wdt_miscdev);
 456        if (error) {
 457                dev_err(&pdev->dev, "failed to register miscdev\n");
 458                goto fail_unmap_feed;
 459        }
 460
 461        dev_info(&pdev->dev, "TS-72xx Watchdog driver\n");
 462
 463        return 0;
 464
 465fail_unmap_feed:
 466        platform_set_drvdata(pdev, NULL);
 467        iounmap(wdt->feed_reg);
 468fail_free_feed:
 469        release_mem_region(r2->start, resource_size(r2));
 470fail_unmap_control:
 471        iounmap(wdt->control_reg);
 472fail_free_control:
 473        release_mem_region(r1->start, resource_size(r1));
 474fail:
 475        kfree(wdt);
 476        return error;
 477}
 478
 479static __devexit int ts72xx_wdt_remove(struct platform_device *pdev)
 480{
 481        struct ts72xx_wdt *wdt = platform_get_drvdata(pdev);
 482        struct resource *res;
 483        int error;
 484
 485        error = misc_deregister(&ts72xx_wdt_miscdev);
 486        platform_set_drvdata(pdev, NULL);
 487
 488        iounmap(wdt->feed_reg);
 489        res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 490        release_mem_region(res->start, resource_size(res));
 491
 492        iounmap(wdt->control_reg);
 493        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 494        release_mem_region(res->start, resource_size(res));
 495
 496        kfree(wdt);
 497        return error;
 498}
 499
 500static struct platform_driver ts72xx_wdt_driver = {
 501        .probe          = ts72xx_wdt_probe,
 502        .remove         = __devexit_p(ts72xx_wdt_remove),
 503        .driver         = {
 504                .name   = "ts72xx-wdt",
 505                .owner  = THIS_MODULE,
 506        },
 507};
 508
 509static __init int ts72xx_wdt_init(void)
 510{
 511        return platform_driver_register(&ts72xx_wdt_driver);
 512}
 513module_init(ts72xx_wdt_init);
 514
 515static __exit void ts72xx_wdt_exit(void)
 516{
 517        platform_driver_unregister(&ts72xx_wdt_driver);
 518}
 519module_exit(ts72xx_wdt_exit);
 520
 521MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
 522MODULE_DESCRIPTION("TS-72xx SBC Watchdog");
 523MODULE_LICENSE("GPL");
 524MODULE_ALIAS("platform:ts72xx-wdt");
 525