linux/drivers/watchdog/stmp3xxx_wdt.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for Freescale STMP37XX/STMP378X
   3 *
   4 * Author: Vitaly Wool <vital@embeddedalley.com>
   5 *
   6 * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved.
   7 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
   8 */
   9
  10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11
  12#include <linux/init.h>
  13#include <linux/kernel.h>
  14#include <linux/fs.h>
  15#include <linux/miscdevice.h>
  16#include <linux/watchdog.h>
  17#include <linux/platform_device.h>
  18#include <linux/spinlock.h>
  19#include <linux/uaccess.h>
  20#include <linux/module.h>
  21
  22#include <mach/platform.h>
  23#include <mach/regs-rtc.h>
  24
  25#define DEFAULT_HEARTBEAT       19
  26#define MAX_HEARTBEAT           (0x10000000 >> 6)
  27
  28/* missing bitmask in headers */
  29#define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER     0x80000000
  30
  31#define WDT_IN_USE              0
  32#define WDT_OK_TO_CLOSE         1
  33
  34#define WDOG_COUNTER_RATE       1000 /* 1 kHz clock */
  35
  36static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock);
  37static unsigned long wdt_status;
  38static const bool nowayout = WATCHDOG_NOWAYOUT;
  39static int heartbeat = DEFAULT_HEARTBEAT;
  40static unsigned long boot_status;
  41
  42static void wdt_enable(u32 value)
  43{
  44        spin_lock(&stmp3xxx_wdt_io_lock);
  45        __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG);
  46        stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
  47        stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
  48                        REGS_RTC_BASE + HW_RTC_PERSISTENT1);
  49        spin_unlock(&stmp3xxx_wdt_io_lock);
  50}
  51
  52static void wdt_disable(void)
  53{
  54        spin_lock(&stmp3xxx_wdt_io_lock);
  55        stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
  56                        REGS_RTC_BASE + HW_RTC_PERSISTENT1);
  57        stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL);
  58        spin_unlock(&stmp3xxx_wdt_io_lock);
  59}
  60
  61static void wdt_ping(void)
  62{
  63        wdt_enable(heartbeat * WDOG_COUNTER_RATE);
  64}
  65
  66static int stmp3xxx_wdt_open(struct inode *inode, struct file *file)
  67{
  68        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  69                return -EBUSY;
  70
  71        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  72        wdt_ping();
  73
  74        return nonseekable_open(inode, file);
  75}
  76
  77static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data,
  78        size_t len, loff_t *ppos)
  79{
  80        if (len) {
  81                if (!nowayout) {
  82                        size_t i;
  83
  84                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  85
  86                        for (i = 0; i != len; i++) {
  87                                char c;
  88
  89                                if (get_user(c, data + i))
  90                                        return -EFAULT;
  91                                if (c == 'V')
  92                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
  93                        }
  94                }
  95                wdt_ping();
  96        }
  97
  98        return len;
  99}
 100
 101static const struct watchdog_info ident = {
 102        .options        = WDIOF_CARDRESET |
 103                          WDIOF_MAGICCLOSE |
 104                          WDIOF_SETTIMEOUT |
 105                          WDIOF_KEEPALIVEPING,
 106        .identity       = "STMP3XXX Watchdog",
 107};
 108
 109static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd,
 110        unsigned long arg)
 111{
 112        void __user *argp = (void __user *)arg;
 113        int __user *p = argp;
 114        int new_heartbeat, opts;
 115        int ret = -ENOTTY;
 116
 117        switch (cmd) {
 118        case WDIOC_GETSUPPORT:
 119                ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 120                break;
 121
 122        case WDIOC_GETSTATUS:
 123                ret = put_user(0, p);
 124                break;
 125
 126        case WDIOC_GETBOOTSTATUS:
 127                ret = put_user(boot_status, p);
 128                break;
 129
 130        case WDIOC_SETOPTIONS:
 131                if (get_user(opts, p)) {
 132                        ret = -EFAULT;
 133                        break;
 134                }
 135                if (opts & WDIOS_DISABLECARD)
 136                        wdt_disable();
 137                else if (opts & WDIOS_ENABLECARD)
 138                        wdt_ping();
 139                else {
 140                        pr_debug("%s: unknown option 0x%x\n", __func__, opts);
 141                        ret = -EINVAL;
 142                        break;
 143                }
 144                ret = 0;
 145                break;
 146
 147        case WDIOC_KEEPALIVE:
 148                wdt_ping();
 149                ret = 0;
 150                break;
 151
 152        case WDIOC_SETTIMEOUT:
 153                if (get_user(new_heartbeat, p)) {
 154                        ret = -EFAULT;
 155                        break;
 156                }
 157                if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) {
 158                        ret = -EINVAL;
 159                        break;
 160                }
 161
 162                heartbeat = new_heartbeat;
 163                wdt_ping();
 164                /* Fall through */
 165
 166        case WDIOC_GETTIMEOUT:
 167                ret = put_user(heartbeat, p);
 168                break;
 169        }
 170        return ret;
 171}
 172
 173static int stmp3xxx_wdt_release(struct inode *inode, struct file *file)
 174{
 175        int ret = 0;
 176
 177        if (!nowayout) {
 178                if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
 179                        wdt_ping();
 180                        pr_debug("%s: Device closed unexpectedly\n", __func__);
 181                        ret = -EINVAL;
 182                } else {
 183                        wdt_disable();
 184                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 185                }
 186        }
 187        clear_bit(WDT_IN_USE, &wdt_status);
 188
 189        return ret;
 190}
 191
 192static const struct file_operations stmp3xxx_wdt_fops = {
 193        .owner = THIS_MODULE,
 194        .llseek = no_llseek,
 195        .write = stmp3xxx_wdt_write,
 196        .unlocked_ioctl = stmp3xxx_wdt_ioctl,
 197        .open = stmp3xxx_wdt_open,
 198        .release = stmp3xxx_wdt_release,
 199};
 200
 201static struct miscdevice stmp3xxx_wdt_miscdev = {
 202        .minor = WATCHDOG_MINOR,
 203        .name = "watchdog",
 204        .fops = &stmp3xxx_wdt_fops,
 205};
 206
 207static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev)
 208{
 209        int ret = 0;
 210
 211        if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
 212                heartbeat = DEFAULT_HEARTBEAT;
 213
 214        boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) &
 215                        BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER;
 216        boot_status = !!boot_status;
 217        stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER,
 218                        REGS_RTC_BASE + HW_RTC_PERSISTENT1);
 219        wdt_disable();          /* disable for now */
 220
 221        ret = misc_register(&stmp3xxx_wdt_miscdev);
 222        if (ret < 0) {
 223                dev_err(&pdev->dev, "cannot register misc device\n");
 224                return ret;
 225        }
 226
 227        pr_info("initialized, heartbeat %d sec\n", heartbeat);
 228
 229        return ret;
 230}
 231
 232static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev)
 233{
 234        misc_deregister(&stmp3xxx_wdt_miscdev);
 235        return 0;
 236}
 237
 238#ifdef CONFIG_PM
 239static int wdt_suspended;
 240static u32 wdt_saved_time;
 241
 242static int stmp3xxx_wdt_suspend(struct platform_device *pdev,
 243                                pm_message_t state)
 244{
 245        if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) &
 246                BM_RTC_CTRL_WATCHDOGEN) {
 247                wdt_suspended = 1;
 248                wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG);
 249                wdt_disable();
 250        }
 251        return 0;
 252}
 253
 254static int stmp3xxx_wdt_resume(struct platform_device *pdev)
 255{
 256        if (wdt_suspended) {
 257                wdt_enable(wdt_saved_time);
 258                wdt_suspended = 0;
 259        }
 260        return 0;
 261}
 262#else
 263#define stmp3xxx_wdt_suspend    NULL
 264#define stmp3xxx_wdt_resume     NULL
 265#endif
 266
 267static struct platform_driver platform_wdt_driver = {
 268        .driver = {
 269                .name = "stmp3xxx_wdt",
 270        },
 271        .probe = stmp3xxx_wdt_probe,
 272        .remove = __devexit_p(stmp3xxx_wdt_remove),
 273        .suspend = stmp3xxx_wdt_suspend,
 274        .resume = stmp3xxx_wdt_resume,
 275};
 276
 277module_platform_driver(platform_wdt_driver);
 278
 279MODULE_DESCRIPTION("STMP3XXX Watchdog Driver");
 280MODULE_LICENSE("GPL");
 281
 282module_param(heartbeat, int, 0);
 283MODULE_PARM_DESC(heartbeat,
 284                 "Watchdog heartbeat period in seconds from 1 to "
 285                 __MODULE_STRING(MAX_HEARTBEAT) ", default "
 286                 __MODULE_STRING(DEFAULT_HEARTBEAT));
 287
 288MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 289