linux/drivers/watchdog/pnx4008_wdt.c
<<
>>
Prefs
   1/*
   2 * drivers/char/watchdog/pnx4008_wdt.c
   3 *
   4 * Watchdog driver for PNX4008 board
   5 *
   6 * Authors: Dmitry Chigirev <source@mvista.com>,
   7 *          Vitaly Wool <vitalywool@gmail.com>
   8 * Based on sa1100 driver,
   9 * Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
  10 *
  11 * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under
  12 * the terms of the GNU General Public License version 2. This program
  13 * is licensed "as is" without any warranty of any kind, whether express
  14 * or implied.
  15 */
  16
  17#include <linux/module.h>
  18#include <linux/moduleparam.h>
  19#include <linux/types.h>
  20#include <linux/kernel.h>
  21#include <linux/fs.h>
  22#include <linux/miscdevice.h>
  23#include <linux/watchdog.h>
  24#include <linux/init.h>
  25#include <linux/bitops.h>
  26#include <linux/ioport.h>
  27#include <linux/device.h>
  28#include <linux/platform_device.h>
  29#include <linux/clk.h>
  30#include <linux/spinlock.h>
  31#include <linux/uaccess.h>
  32#include <linux/io.h>
  33#include <linux/slab.h>
  34#include <mach/hardware.h>
  35
  36#define MODULE_NAME "PNX4008-WDT: "
  37
  38/* WatchDog Timer - Chapter 23 Page 207 */
  39
  40#define DEFAULT_HEARTBEAT 19
  41#define MAX_HEARTBEAT     60
  42
  43/* Watchdog timer register set definition */
  44#define WDTIM_INT(p)     ((p) + 0x0)
  45#define WDTIM_CTRL(p)    ((p) + 0x4)
  46#define WDTIM_COUNTER(p) ((p) + 0x8)
  47#define WDTIM_MCTRL(p)   ((p) + 0xC)
  48#define WDTIM_MATCH0(p)  ((p) + 0x10)
  49#define WDTIM_EMR(p)     ((p) + 0x14)
  50#define WDTIM_PULSE(p)   ((p) + 0x18)
  51#define WDTIM_RES(p)     ((p) + 0x1C)
  52
  53/* WDTIM_INT bit definitions */
  54#define MATCH_INT      1
  55
  56/* WDTIM_CTRL bit definitions */
  57#define COUNT_ENAB     1
  58#define RESET_COUNT    (1 << 1)
  59#define DEBUG_EN       (1 << 2)
  60
  61/* WDTIM_MCTRL bit definitions */
  62#define MR0_INT        1
  63#undef  RESET_COUNT0
  64#define RESET_COUNT0   (1 << 2)
  65#define STOP_COUNT0    (1 << 2)
  66#define M_RES1         (1 << 3)
  67#define M_RES2         (1 << 4)
  68#define RESFRC1        (1 << 5)
  69#define RESFRC2        (1 << 6)
  70
  71/* WDTIM_EMR bit definitions */
  72#define EXT_MATCH0      1
  73#define MATCH_OUTPUT_HIGH (2 << 4)      /*a MATCH_CTRL setting */
  74
  75/* WDTIM_RES bit definitions */
  76#define WDOG_RESET      1       /* read only */
  77
  78#define WDOG_COUNTER_RATE 13000000      /*the counter clock is 13 MHz fixed */
  79
  80static int nowayout = WATCHDOG_NOWAYOUT;
  81static int heartbeat = DEFAULT_HEARTBEAT;
  82
  83static DEFINE_SPINLOCK(io_lock);
  84static unsigned long wdt_status;
  85#define WDT_IN_USE        0
  86#define WDT_OK_TO_CLOSE   1
  87#define WDT_REGION_INITED 2
  88#define WDT_DEVICE_INITED 3
  89
  90static unsigned long boot_status;
  91
  92static struct resource  *wdt_mem;
  93static void __iomem     *wdt_base;
  94struct clk              *wdt_clk;
  95
  96static void wdt_enable(void)
  97{
  98        spin_lock(&io_lock);
  99
 100        /* stop counter, initiate counter reset */
 101        __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base));
 102        /*wait for reset to complete. 100% guarantee event */
 103        while (__raw_readl(WDTIM_COUNTER(wdt_base)))
 104                cpu_relax();
 105        /* internal and external reset, stop after that */
 106        __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0,
 107                WDTIM_MCTRL(wdt_base));
 108        /* configure match output */
 109        __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base));
 110        /* clear interrupt, just in case */
 111        __raw_writel(MATCH_INT, WDTIM_INT(wdt_base));
 112        /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */
 113        __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base));
 114        __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base));
 115        /*enable counter, stop when debugger active */
 116        __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base));
 117
 118        spin_unlock(&io_lock);
 119}
 120
 121static void wdt_disable(void)
 122{
 123        spin_lock(&io_lock);
 124
 125        __raw_writel(0, WDTIM_CTRL(wdt_base));  /*stop counter */
 126
 127        spin_unlock(&io_lock);
 128}
 129
 130static int pnx4008_wdt_open(struct inode *inode, struct file *file)
 131{
 132        int ret;
 133
 134        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
 135                return -EBUSY;
 136
 137        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 138
 139        ret = clk_enable(wdt_clk);
 140        if (ret) {
 141                clear_bit(WDT_IN_USE, &wdt_status);
 142                return ret;
 143        }
 144
 145        wdt_enable();
 146
 147        return nonseekable_open(inode, file);
 148}
 149
 150static ssize_t pnx4008_wdt_write(struct file *file, const char *data,
 151                                        size_t len, loff_t *ppos)
 152{
 153        if (len) {
 154                if (!nowayout) {
 155                        size_t i;
 156
 157                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 158
 159                        for (i = 0; i != len; i++) {
 160                                char c;
 161
 162                                if (get_user(c, data + i))
 163                                        return -EFAULT;
 164                                if (c == 'V')
 165                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 166                        }
 167                }
 168                wdt_enable();
 169        }
 170
 171        return len;
 172}
 173
 174static const struct watchdog_info ident = {
 175        .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
 176            WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
 177        .identity = "PNX4008 Watchdog",
 178};
 179
 180static long pnx4008_wdt_ioctl(struct file *file, unsigned int cmd,
 181                                unsigned long arg)
 182{
 183        int ret = -ENOTTY;
 184        int time;
 185
 186        switch (cmd) {
 187        case WDIOC_GETSUPPORT:
 188                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 189                                   sizeof(ident)) ? -EFAULT : 0;
 190                break;
 191
 192        case WDIOC_GETSTATUS:
 193                ret = put_user(0, (int *)arg);
 194                break;
 195
 196        case WDIOC_GETBOOTSTATUS:
 197                ret = put_user(boot_status, (int *)arg);
 198                break;
 199
 200        case WDIOC_KEEPALIVE:
 201                wdt_enable();
 202                ret = 0;
 203                break;
 204
 205        case WDIOC_SETTIMEOUT:
 206                ret = get_user(time, (int *)arg);
 207                if (ret)
 208                        break;
 209
 210                if (time <= 0 || time > MAX_HEARTBEAT) {
 211                        ret = -EINVAL;
 212                        break;
 213                }
 214
 215                heartbeat = time;
 216                wdt_enable();
 217                /* Fall through */
 218
 219        case WDIOC_GETTIMEOUT:
 220                ret = put_user(heartbeat, (int *)arg);
 221                break;
 222        }
 223        return ret;
 224}
 225
 226static int pnx4008_wdt_release(struct inode *inode, struct file *file)
 227{
 228        if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 229                printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n");
 230
 231        wdt_disable();
 232        clk_disable(wdt_clk);
 233        clear_bit(WDT_IN_USE, &wdt_status);
 234        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 235
 236        return 0;
 237}
 238
 239static const struct file_operations pnx4008_wdt_fops = {
 240        .owner = THIS_MODULE,
 241        .llseek = no_llseek,
 242        .write = pnx4008_wdt_write,
 243        .unlocked_ioctl = pnx4008_wdt_ioctl,
 244        .open = pnx4008_wdt_open,
 245        .release = pnx4008_wdt_release,
 246};
 247
 248static struct miscdevice pnx4008_wdt_miscdev = {
 249        .minor = WATCHDOG_MINOR,
 250        .name = "watchdog",
 251        .fops = &pnx4008_wdt_fops,
 252};
 253
 254static int __devinit pnx4008_wdt_probe(struct platform_device *pdev)
 255{
 256        int ret = 0, size;
 257        struct resource *res;
 258
 259        if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
 260                heartbeat = DEFAULT_HEARTBEAT;
 261
 262        printk(KERN_INFO MODULE_NAME
 263                "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat);
 264
 265        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 266        if (res == NULL) {
 267                printk(KERN_INFO MODULE_NAME
 268                        "failed to get memory region resouce\n");
 269                return -ENOENT;
 270        }
 271
 272        size = resource_size(res);
 273        wdt_mem = request_mem_region(res->start, size, pdev->name);
 274
 275        if (wdt_mem == NULL) {
 276                printk(KERN_INFO MODULE_NAME "failed to get memory region\n");
 277                return -ENOENT;
 278        }
 279        wdt_base = (void __iomem *)IO_ADDRESS(res->start);
 280
 281        wdt_clk = clk_get(&pdev->dev, NULL);
 282        if (IS_ERR(wdt_clk)) {
 283                ret = PTR_ERR(wdt_clk);
 284                release_resource(wdt_mem);
 285                kfree(wdt_mem);
 286                goto out;
 287        }
 288
 289        ret = clk_enable(wdt_clk);
 290        if (ret) {
 291                release_resource(wdt_mem);
 292                kfree(wdt_mem);
 293                goto out;
 294        }
 295
 296        ret = misc_register(&pnx4008_wdt_miscdev);
 297        if (ret < 0) {
 298                printk(KERN_ERR MODULE_NAME "cannot register misc device\n");
 299                release_resource(wdt_mem);
 300                kfree(wdt_mem);
 301                clk_disable(wdt_clk);
 302                clk_put(wdt_clk);
 303        } else {
 304                boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ?
 305                    WDIOF_CARDRESET : 0;
 306                wdt_disable();          /*disable for now */
 307                clk_disable(wdt_clk);
 308                set_bit(WDT_DEVICE_INITED, &wdt_status);
 309        }
 310
 311out:
 312        return ret;
 313}
 314
 315static int __devexit pnx4008_wdt_remove(struct platform_device *pdev)
 316{
 317        misc_deregister(&pnx4008_wdt_miscdev);
 318
 319        clk_disable(wdt_clk);
 320        clk_put(wdt_clk);
 321
 322        if (wdt_mem) {
 323                release_resource(wdt_mem);
 324                kfree(wdt_mem);
 325                wdt_mem = NULL;
 326        }
 327        return 0;
 328}
 329
 330static struct platform_driver platform_wdt_driver = {
 331        .driver = {
 332                .name = "pnx4008-watchdog",
 333                .owner  = THIS_MODULE,
 334        },
 335        .probe = pnx4008_wdt_probe,
 336        .remove = __devexit_p(pnx4008_wdt_remove),
 337};
 338
 339static int __init pnx4008_wdt_init(void)
 340{
 341        return platform_driver_register(&platform_wdt_driver);
 342}
 343
 344static void __exit pnx4008_wdt_exit(void)
 345{
 346        platform_driver_unregister(&platform_wdt_driver);
 347}
 348
 349module_init(pnx4008_wdt_init);
 350module_exit(pnx4008_wdt_exit);
 351
 352MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>");
 353MODULE_DESCRIPTION("PNX4008 Watchdog Driver");
 354
 355module_param(heartbeat, int, 0);
 356MODULE_PARM_DESC(heartbeat,
 357                 "Watchdog heartbeat period in seconds from 1 to "
 358                 __MODULE_STRING(MAX_HEARTBEAT) ", default "
 359                 __MODULE_STRING(DEFAULT_HEARTBEAT));
 360
 361module_param(nowayout, int, 0);
 362MODULE_PARM_DESC(nowayout,
 363                 "Set to 1 to keep watchdog running after device release");
 364
 365MODULE_LICENSE("GPL");
 366MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 367MODULE_ALIAS("platform:pnx4008-watchdog");
 368