linux/drivers/watchdog/bcm47xx_wdt.c
<<
>>
Prefs
   1/*
   2 *  Watchdog driver for Broadcom BCM47XX
   3 *
   4 *  Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>
   5 *  Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>
   6 *
   7 *  This program is free software; you can redistribute it and/or
   8 *  modify it under the terms of the GNU General Public License
   9 *  as published by the Free Software Foundation; either version
  10 *  2 of the License, or (at your option) any later version.
  11 */
  12
  13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  14
  15#include <linux/bitops.h>
  16#include <linux/errno.h>
  17#include <linux/fs.h>
  18#include <linux/init.h>
  19#include <linux/kernel.h>
  20#include <linux/miscdevice.h>
  21#include <linux/module.h>
  22#include <linux/moduleparam.h>
  23#include <linux/reboot.h>
  24#include <linux/types.h>
  25#include <linux/uaccess.h>
  26#include <linux/watchdog.h>
  27#include <linux/timer.h>
  28#include <linux/jiffies.h>
  29#include <linux/ssb/ssb_embedded.h>
  30#include <asm/mach-bcm47xx/bcm47xx.h>
  31
  32#define DRV_NAME                "bcm47xx_wdt"
  33
  34#define WDT_DEFAULT_TIME        30      /* seconds */
  35#define WDT_MAX_TIME            255     /* seconds */
  36
  37static int wdt_time = WDT_DEFAULT_TIME;
  38static bool nowayout = WATCHDOG_NOWAYOUT;
  39
  40module_param(wdt_time, int, 0);
  41MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="
  42                                __MODULE_STRING(WDT_DEFAULT_TIME) ")");
  43
  44#ifdef CONFIG_WATCHDOG_NOWAYOUT
  45module_param(nowayout, bool, 0);
  46MODULE_PARM_DESC(nowayout,
  47                "Watchdog cannot be stopped once started (default="
  48                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  49#endif
  50
  51static unsigned long bcm47xx_wdt_busy;
  52static char expect_release;
  53static struct timer_list wdt_timer;
  54static atomic_t ticks;
  55
  56static inline void bcm47xx_wdt_hw_start(void)
  57{
  58        /* this is 2,5s on 100Mhz clock  and 2s on 133 Mhz */
  59        switch (bcm47xx_bus_type) {
  60#ifdef CONFIG_BCM47XX_SSB
  61        case BCM47XX_BUS_TYPE_SSB:
  62                ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0xfffffff);
  63                break;
  64#endif
  65#ifdef CONFIG_BCM47XX_BCMA
  66        case BCM47XX_BUS_TYPE_BCMA:
  67                bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc,
  68                                               0xfffffff);
  69                break;
  70#endif
  71        }
  72}
  73
  74static inline int bcm47xx_wdt_hw_stop(void)
  75{
  76        switch (bcm47xx_bus_type) {
  77#ifdef CONFIG_BCM47XX_SSB
  78        case BCM47XX_BUS_TYPE_SSB:
  79                return ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0);
  80#endif
  81#ifdef CONFIG_BCM47XX_BCMA
  82        case BCM47XX_BUS_TYPE_BCMA:
  83                bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, 0);
  84                return 0;
  85#endif
  86        }
  87        return -EINVAL;
  88}
  89
  90static void bcm47xx_timer_tick(unsigned long unused)
  91{
  92        if (!atomic_dec_and_test(&ticks)) {
  93                bcm47xx_wdt_hw_start();
  94                mod_timer(&wdt_timer, jiffies + HZ);
  95        } else {
  96                pr_crit("Watchdog will fire soon!!!\n");
  97        }
  98}
  99
 100static inline void bcm47xx_wdt_pet(void)
 101{
 102        atomic_set(&ticks, wdt_time);
 103}
 104
 105static void bcm47xx_wdt_start(void)
 106{
 107        bcm47xx_wdt_pet();
 108        bcm47xx_timer_tick(0);
 109}
 110
 111static void bcm47xx_wdt_pause(void)
 112{
 113        del_timer_sync(&wdt_timer);
 114        bcm47xx_wdt_hw_stop();
 115}
 116
 117static void bcm47xx_wdt_stop(void)
 118{
 119        bcm47xx_wdt_pause();
 120}
 121
 122static int bcm47xx_wdt_settimeout(int new_time)
 123{
 124        if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
 125                return -EINVAL;
 126
 127        wdt_time = new_time;
 128        return 0;
 129}
 130
 131static int bcm47xx_wdt_open(struct inode *inode, struct file *file)
 132{
 133        if (test_and_set_bit(0, &bcm47xx_wdt_busy))
 134                return -EBUSY;
 135
 136        bcm47xx_wdt_start();
 137        return nonseekable_open(inode, file);
 138}
 139
 140static int bcm47xx_wdt_release(struct inode *inode, struct file *file)
 141{
 142        if (expect_release == 42) {
 143                bcm47xx_wdt_stop();
 144        } else {
 145                pr_crit("Unexpected close, not stopping watchdog!\n");
 146                bcm47xx_wdt_start();
 147        }
 148
 149        clear_bit(0, &bcm47xx_wdt_busy);
 150        expect_release = 0;
 151        return 0;
 152}
 153
 154static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data,
 155                                size_t len, loff_t *ppos)
 156{
 157        if (len) {
 158                if (!nowayout) {
 159                        size_t i;
 160
 161                        expect_release = 0;
 162
 163                        for (i = 0; i != len; i++) {
 164                                char c;
 165                                if (get_user(c, data + i))
 166                                        return -EFAULT;
 167                                if (c == 'V')
 168                                        expect_release = 42;
 169                        }
 170                }
 171                bcm47xx_wdt_pet();
 172        }
 173        return len;
 174}
 175
 176static const struct watchdog_info bcm47xx_wdt_info = {
 177        .identity       = DRV_NAME,
 178        .options        = WDIOF_SETTIMEOUT |
 179                                WDIOF_KEEPALIVEPING |
 180                                WDIOF_MAGICCLOSE,
 181};
 182
 183static long bcm47xx_wdt_ioctl(struct file *file,
 184                                        unsigned int cmd, unsigned long arg)
 185{
 186        void __user *argp = (void __user *)arg;
 187        int __user *p = argp;
 188        int new_value, retval = -EINVAL;
 189
 190        switch (cmd) {
 191        case WDIOC_GETSUPPORT:
 192                return copy_to_user(argp, &bcm47xx_wdt_info,
 193                                sizeof(bcm47xx_wdt_info)) ? -EFAULT : 0;
 194
 195        case WDIOC_GETSTATUS:
 196        case WDIOC_GETBOOTSTATUS:
 197                return put_user(0, p);
 198
 199        case WDIOC_SETOPTIONS:
 200                if (get_user(new_value, p))
 201                        return -EFAULT;
 202
 203                if (new_value & WDIOS_DISABLECARD) {
 204                        bcm47xx_wdt_stop();
 205                        retval = 0;
 206                }
 207
 208                if (new_value & WDIOS_ENABLECARD) {
 209                        bcm47xx_wdt_start();
 210                        retval = 0;
 211                }
 212
 213                return retval;
 214
 215        case WDIOC_KEEPALIVE:
 216                bcm47xx_wdt_pet();
 217                return 0;
 218
 219        case WDIOC_SETTIMEOUT:
 220                if (get_user(new_value, p))
 221                        return -EFAULT;
 222
 223                if (bcm47xx_wdt_settimeout(new_value))
 224                        return -EINVAL;
 225
 226                bcm47xx_wdt_pet();
 227
 228        case WDIOC_GETTIMEOUT:
 229                return put_user(wdt_time, p);
 230
 231        default:
 232                return -ENOTTY;
 233        }
 234}
 235
 236static int bcm47xx_wdt_notify_sys(struct notifier_block *this,
 237        unsigned long code, void *unused)
 238{
 239        if (code == SYS_DOWN || code == SYS_HALT)
 240                bcm47xx_wdt_stop();
 241        return NOTIFY_DONE;
 242}
 243
 244static const struct file_operations bcm47xx_wdt_fops = {
 245        .owner          = THIS_MODULE,
 246        .llseek         = no_llseek,
 247        .unlocked_ioctl = bcm47xx_wdt_ioctl,
 248        .open           = bcm47xx_wdt_open,
 249        .release        = bcm47xx_wdt_release,
 250        .write          = bcm47xx_wdt_write,
 251};
 252
 253static struct miscdevice bcm47xx_wdt_miscdev = {
 254        .minor          = WATCHDOG_MINOR,
 255        .name           = "watchdog",
 256        .fops           = &bcm47xx_wdt_fops,
 257};
 258
 259static struct notifier_block bcm47xx_wdt_notifier = {
 260        .notifier_call = bcm47xx_wdt_notify_sys,
 261};
 262
 263static int __init bcm47xx_wdt_init(void)
 264{
 265        int ret;
 266
 267        if (bcm47xx_wdt_hw_stop() < 0)
 268                return -ENODEV;
 269
 270        setup_timer(&wdt_timer, bcm47xx_timer_tick, 0L);
 271
 272        if (bcm47xx_wdt_settimeout(wdt_time)) {
 273                bcm47xx_wdt_settimeout(WDT_DEFAULT_TIME);
 274                pr_info("wdt_time value must be 0 < wdt_time < %d, using %d\n",
 275                        (WDT_MAX_TIME + 1), wdt_time);
 276        }
 277
 278        ret = register_reboot_notifier(&bcm47xx_wdt_notifier);
 279        if (ret)
 280                return ret;
 281
 282        ret = misc_register(&bcm47xx_wdt_miscdev);
 283        if (ret) {
 284                unregister_reboot_notifier(&bcm47xx_wdt_notifier);
 285                return ret;
 286        }
 287
 288        pr_info("BCM47xx Watchdog Timer enabled (%d seconds%s)\n",
 289                wdt_time, nowayout ? ", nowayout" : "");
 290        return 0;
 291}
 292
 293static void __exit bcm47xx_wdt_exit(void)
 294{
 295        if (!nowayout)
 296                bcm47xx_wdt_stop();
 297
 298        misc_deregister(&bcm47xx_wdt_miscdev);
 299
 300        unregister_reboot_notifier(&bcm47xx_wdt_notifier);
 301}
 302
 303module_init(bcm47xx_wdt_init);
 304module_exit(bcm47xx_wdt_exit);
 305
 306MODULE_AUTHOR("Aleksandar Radovanovic");
 307MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
 308MODULE_LICENSE("GPL");
 309MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 310