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