linux/drivers/watchdog/sp805_wdt.c
<<
>>
Prefs
   1/*
   2 * drivers/char/watchdog/sp805-wdt.c
   3 *
   4 * Watchdog driver for ARM SP805 watchdog module
   5 *
   6 * Copyright (C) 2010 ST Microelectronics
   7 * Viresh Kumar<viresh.kumar@st.com>
   8 *
   9 * This file is licensed under the terms of the GNU General Public
  10 * License version 2 or later. This program is licensed "as is" without any
  11 * warranty of any kind, whether express or implied.
  12 */
  13
  14#include <linux/device.h>
  15#include <linux/resource.h>
  16#include <linux/amba/bus.h>
  17#include <linux/bitops.h>
  18#include <linux/clk.h>
  19#include <linux/fs.h>
  20#include <linux/init.h>
  21#include <linux/io.h>
  22#include <linux/ioport.h>
  23#include <linux/kernel.h>
  24#include <linux/math64.h>
  25#include <linux/miscdevice.h>
  26#include <linux/module.h>
  27#include <linux/moduleparam.h>
  28#include <linux/slab.h>
  29#include <linux/spinlock.h>
  30#include <linux/types.h>
  31#include <linux/uaccess.h>
  32#include <linux/watchdog.h>
  33
  34/* default timeout in seconds */
  35#define DEFAULT_TIMEOUT         60
  36
  37#define MODULE_NAME             "sp805-wdt"
  38
  39/* watchdog register offsets and masks */
  40#define WDTLOAD                 0x000
  41        #define LOAD_MIN        0x00000001
  42        #define LOAD_MAX        0xFFFFFFFF
  43#define WDTVALUE                0x004
  44#define WDTCONTROL              0x008
  45        /* control register masks */
  46        #define INT_ENABLE      (1 << 0)
  47        #define RESET_ENABLE    (1 << 1)
  48#define WDTINTCLR               0x00C
  49#define WDTRIS                  0x010
  50#define WDTMIS                  0x014
  51        #define INT_MASK        (1 << 0)
  52#define WDTLOCK                 0xC00
  53        #define UNLOCK          0x1ACCE551
  54        #define LOCK            0x00000001
  55
  56/**
  57 * struct sp805_wdt: sp805 wdt device structure
  58 *
  59 * lock: spin lock protecting dev structure and io access
  60 * base: base address of wdt
  61 * clk: clock structure of wdt
  62 * dev: amba device structure of wdt
  63 * status: current status of wdt
  64 * load_val: load value to be set for current timeout
  65 * timeout: current programmed timeout
  66 */
  67struct sp805_wdt {
  68        spinlock_t                      lock;
  69        void __iomem                    *base;
  70        struct clk                      *clk;
  71        struct amba_device              *adev;
  72        unsigned long                   status;
  73        #define WDT_BUSY                0
  74        #define WDT_CAN_BE_CLOSED       1
  75        unsigned int                    load_val;
  76        unsigned int                    timeout;
  77};
  78
  79/* local variables */
  80static struct sp805_wdt *wdt;
  81static int nowayout = WATCHDOG_NOWAYOUT;
  82
  83/* This routine finds load value that will reset system in required timout */
  84static void wdt_setload(unsigned int timeout)
  85{
  86        u64 load, rate;
  87
  88        rate = clk_get_rate(wdt->clk);
  89
  90        /*
  91         * sp805 runs counter with given value twice, after the end of first
  92         * counter it gives an interrupt and then starts counter again. If
  93         * interrupt already occured then it resets the system. This is why
  94         * load is half of what should be required.
  95         */
  96        load = div_u64(rate, 2) * timeout - 1;
  97
  98        load = (load > LOAD_MAX) ? LOAD_MAX : load;
  99        load = (load < LOAD_MIN) ? LOAD_MIN : load;
 100
 101        spin_lock(&wdt->lock);
 102        wdt->load_val = load;
 103        /* roundup timeout to closest positive integer value */
 104        wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate);
 105        spin_unlock(&wdt->lock);
 106}
 107
 108/* returns number of seconds left for reset to occur */
 109static u32 wdt_timeleft(void)
 110{
 111        u64 load, rate;
 112
 113        rate = clk_get_rate(wdt->clk);
 114
 115        spin_lock(&wdt->lock);
 116        load = readl(wdt->base + WDTVALUE);
 117
 118        /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */
 119        if (!(readl(wdt->base + WDTRIS) & INT_MASK))
 120                load += wdt->load_val + 1;
 121        spin_unlock(&wdt->lock);
 122
 123        return div_u64(load, rate);
 124}
 125
 126/* enables watchdog timers reset */
 127static void wdt_enable(void)
 128{
 129        spin_lock(&wdt->lock);
 130
 131        writel(UNLOCK, wdt->base + WDTLOCK);
 132        writel(wdt->load_val, wdt->base + WDTLOAD);
 133        writel(INT_MASK, wdt->base + WDTINTCLR);
 134        writel(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL);
 135        writel(LOCK, wdt->base + WDTLOCK);
 136
 137        spin_unlock(&wdt->lock);
 138}
 139
 140/* disables watchdog timers reset */
 141static void wdt_disable(void)
 142{
 143        spin_lock(&wdt->lock);
 144
 145        writel(UNLOCK, wdt->base + WDTLOCK);
 146        writel(0, wdt->base + WDTCONTROL);
 147        writel(0, wdt->base + WDTLOAD);
 148        writel(LOCK, wdt->base + WDTLOCK);
 149
 150        spin_unlock(&wdt->lock);
 151}
 152
 153static ssize_t sp805_wdt_write(struct file *file, const char *data,
 154                size_t len, loff_t *ppos)
 155{
 156        if (len) {
 157                if (!nowayout) {
 158                        size_t i;
 159
 160                        clear_bit(WDT_CAN_BE_CLOSED, &wdt->status);
 161
 162                        for (i = 0; i != len; i++) {
 163                                char c;
 164
 165                                if (get_user(c, data + i))
 166                                        return -EFAULT;
 167                                /* Check for Magic Close character */
 168                                if (c == 'V') {
 169                                        set_bit(WDT_CAN_BE_CLOSED,
 170                                                        &wdt->status);
 171                                        break;
 172                                }
 173                        }
 174                }
 175                wdt_enable();
 176        }
 177        return len;
 178}
 179
 180static const struct watchdog_info ident = {
 181        .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 182        .identity = MODULE_NAME,
 183};
 184
 185static long sp805_wdt_ioctl(struct file *file, unsigned int cmd,
 186                unsigned long arg)
 187{
 188        int ret = -ENOTTY;
 189        unsigned int timeout;
 190
 191        switch (cmd) {
 192        case WDIOC_GETSUPPORT:
 193                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 194                                sizeof(ident)) ? -EFAULT : 0;
 195                break;
 196
 197        case WDIOC_GETSTATUS:
 198                ret = put_user(0, (int *)arg);
 199                break;
 200
 201        case WDIOC_KEEPALIVE:
 202                wdt_enable();
 203                ret = 0;
 204                break;
 205
 206        case WDIOC_SETTIMEOUT:
 207                ret = get_user(timeout, (unsigned int *)arg);
 208                if (ret)
 209                        break;
 210
 211                wdt_setload(timeout);
 212
 213                wdt_enable();
 214                /* Fall through */
 215
 216        case WDIOC_GETTIMEOUT:
 217                ret = put_user(wdt->timeout, (unsigned int *)arg);
 218                break;
 219        case WDIOC_GETTIMELEFT:
 220                ret = put_user(wdt_timeleft(), (unsigned int *)arg);
 221                break;
 222        }
 223        return ret;
 224}
 225
 226static int sp805_wdt_open(struct inode *inode, struct file *file)
 227{
 228        int ret = 0;
 229
 230        if (test_and_set_bit(WDT_BUSY, &wdt->status))
 231                return -EBUSY;
 232
 233        ret = clk_enable(wdt->clk);
 234        if (ret) {
 235                dev_err(&wdt->adev->dev, "clock enable fail");
 236                goto err;
 237        }
 238
 239        wdt_enable();
 240
 241        /* can not be closed, once enabled */
 242        clear_bit(WDT_CAN_BE_CLOSED, &wdt->status);
 243        return nonseekable_open(inode, file);
 244
 245err:
 246        clear_bit(WDT_BUSY, &wdt->status);
 247        return ret;
 248}
 249
 250static int sp805_wdt_release(struct inode *inode, struct file *file)
 251{
 252        if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) {
 253                clear_bit(WDT_BUSY, &wdt->status);
 254                dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n");
 255                return 0;
 256        }
 257
 258        wdt_disable();
 259        clk_disable(wdt->clk);
 260        clear_bit(WDT_BUSY, &wdt->status);
 261
 262        return 0;
 263}
 264
 265static const struct file_operations sp805_wdt_fops = {
 266        .owner = THIS_MODULE,
 267        .llseek = no_llseek,
 268        .write = sp805_wdt_write,
 269        .unlocked_ioctl = sp805_wdt_ioctl,
 270        .open = sp805_wdt_open,
 271        .release = sp805_wdt_release,
 272};
 273
 274static struct miscdevice sp805_wdt_miscdev = {
 275        .minor = WATCHDOG_MINOR,
 276        .name = "watchdog",
 277        .fops = &sp805_wdt_fops,
 278};
 279
 280static int __devinit
 281sp805_wdt_probe(struct amba_device *adev, struct amba_id *id)
 282{
 283        int ret = 0;
 284
 285        if (!request_mem_region(adev->res.start, resource_size(&adev->res),
 286                                "sp805_wdt")) {
 287                dev_warn(&adev->dev, "Failed to get memory region resource\n");
 288                ret = -ENOENT;
 289                goto err;
 290        }
 291
 292        wdt = kzalloc(sizeof(*wdt), GFP_KERNEL);
 293        if (!wdt) {
 294                dev_warn(&adev->dev, "Kzalloc failed\n");
 295                ret = -ENOMEM;
 296                goto err_kzalloc;
 297        }
 298
 299        wdt->clk = clk_get(&adev->dev, NULL);
 300        if (IS_ERR(wdt->clk)) {
 301                dev_warn(&adev->dev, "Clock not found\n");
 302                ret = PTR_ERR(wdt->clk);
 303                goto err_clk_get;
 304        }
 305
 306        wdt->base = ioremap(adev->res.start, resource_size(&adev->res));
 307        if (!wdt->base) {
 308                ret = -ENOMEM;
 309                dev_warn(&adev->dev, "ioremap fail\n");
 310                goto err_ioremap;
 311        }
 312
 313        wdt->adev = adev;
 314        spin_lock_init(&wdt->lock);
 315        wdt_setload(DEFAULT_TIMEOUT);
 316
 317        ret = misc_register(&sp805_wdt_miscdev);
 318        if (ret < 0) {
 319                dev_warn(&adev->dev, "cannot register misc device\n");
 320                goto err_misc_register;
 321        }
 322
 323        dev_info(&adev->dev, "registration successful\n");
 324        return 0;
 325
 326err_misc_register:
 327        iounmap(wdt->base);
 328err_ioremap:
 329        clk_put(wdt->clk);
 330err_clk_get:
 331        kfree(wdt);
 332        wdt = NULL;
 333err_kzalloc:
 334        release_mem_region(adev->res.start, resource_size(&adev->res));
 335err:
 336        dev_err(&adev->dev, "Probe Failed!!!\n");
 337        return ret;
 338}
 339
 340static int __devexit sp805_wdt_remove(struct amba_device *adev)
 341{
 342        misc_deregister(&sp805_wdt_miscdev);
 343        iounmap(wdt->base);
 344        clk_put(wdt->clk);
 345        kfree(wdt);
 346        release_mem_region(adev->res.start, resource_size(&adev->res));
 347
 348        return 0;
 349}
 350
 351static struct amba_id sp805_wdt_ids[] __initdata = {
 352        {
 353                .id     = 0x00141805,
 354                .mask   = 0x00ffffff,
 355        },
 356        { 0, 0 },
 357};
 358
 359static struct amba_driver sp805_wdt_driver = {
 360        .drv = {
 361                .name   = MODULE_NAME,
 362        },
 363        .id_table       = sp805_wdt_ids,
 364        .probe          = sp805_wdt_probe,
 365        .remove = __devexit_p(sp805_wdt_remove),
 366};
 367
 368static int __init sp805_wdt_init(void)
 369{
 370        return amba_driver_register(&sp805_wdt_driver);
 371}
 372module_init(sp805_wdt_init);
 373
 374static void __exit sp805_wdt_exit(void)
 375{
 376        amba_driver_unregister(&sp805_wdt_driver);
 377}
 378module_exit(sp805_wdt_exit);
 379
 380module_param(nowayout, int, 0);
 381MODULE_PARM_DESC(nowayout,
 382                "Set to 1 to keep watchdog running after device release");
 383
 384MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>");
 385MODULE_DESCRIPTION("ARM SP805 Watchdog Driver");
 386MODULE_LICENSE("GPL");
 387MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 388