linux/drivers/watchdog/mv64x60_wdt.c
<<
>>
Prefs
   1/*
   2 * mv64x60_wdt.c - MV64X60 (Marvell Discovery) watchdog userspace interface
   3 *
   4 * Author: James Chapman <jchapman@katalix.com>
   5 *
   6 * Platform-specific setup code should configure the dog to generate
   7 * interrupt or reset as required.  This code only enables/disables
   8 * and services the watchdog.
   9 *
  10 * Derived from mpc8xx_wdt.c, with the following copyright.
  11 *
  12 * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under
  13 * the terms of the GNU General Public License version 2. This program
  14 * is licensed "as is" without any warranty of any kind, whether express
  15 * or implied.
  16 */
  17
  18#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  19
  20#include <linux/fs.h>
  21#include <linux/init.h>
  22#include <linux/kernel.h>
  23#include <linux/miscdevice.h>
  24#include <linux/module.h>
  25#include <linux/watchdog.h>
  26#include <linux/platform_device.h>
  27#include <linux/mv643xx.h>
  28#include <linux/uaccess.h>
  29#include <linux/io.h>
  30
  31#define MV64x60_WDT_WDC_OFFSET  0
  32
  33/*
  34 * The watchdog configuration register contains a pair of 2-bit fields,
  35 *   1.  a reload field, bits 27-26, which triggers a reload of
  36 *       the countdown register, and
  37 *   2.  an enable field, bits 25-24, which toggles between
  38 *       enabling and disabling the watchdog timer.
  39 * Bit 31 is a read-only field which indicates whether the
  40 * watchdog timer is currently enabled.
  41 *
  42 * The low 24 bits contain the timer reload value.
  43 */
  44#define MV64x60_WDC_ENABLE_SHIFT        24
  45#define MV64x60_WDC_SERVICE_SHIFT       26
  46#define MV64x60_WDC_ENABLED_SHIFT       31
  47
  48#define MV64x60_WDC_ENABLED_TRUE        1
  49#define MV64x60_WDC_ENABLED_FALSE       0
  50
  51/* Flags bits */
  52#define MV64x60_WDOG_FLAG_OPENED        0
  53
  54static unsigned long wdt_flags;
  55static int wdt_status;
  56static void __iomem *mv64x60_wdt_regs;
  57static int mv64x60_wdt_timeout;
  58static int mv64x60_wdt_count;
  59static unsigned int bus_clk;
  60static char expect_close;
  61static DEFINE_SPINLOCK(mv64x60_wdt_spinlock);
  62
  63static bool nowayout = WATCHDOG_NOWAYOUT;
  64module_param(nowayout, bool, 0);
  65MODULE_PARM_DESC(nowayout,
  66                "Watchdog cannot be stopped once started (default="
  67                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  68
  69static int mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift)
  70{
  71        u32 data;
  72        u32 enabled;
  73        int ret = 0;
  74
  75        spin_lock(&mv64x60_wdt_spinlock);
  76        data = readl(mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET);
  77        enabled = (data >> MV64x60_WDC_ENABLED_SHIFT) & 1;
  78
  79        /* only toggle the requested field if enabled state matches predicate */
  80        if ((enabled ^ enabled_predicate) == 0) {
  81                /* We write a 1, then a 2 -- to the appropriate field */
  82                data = (1 << field_shift) | mv64x60_wdt_count;
  83                writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET);
  84
  85                data = (2 << field_shift) | mv64x60_wdt_count;
  86                writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET);
  87                ret = 1;
  88        }
  89        spin_unlock(&mv64x60_wdt_spinlock);
  90
  91        return ret;
  92}
  93
  94static void mv64x60_wdt_service(void)
  95{
  96        mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE,
  97                               MV64x60_WDC_SERVICE_SHIFT);
  98}
  99
 100static void mv64x60_wdt_handler_enable(void)
 101{
 102        if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE,
 103                                   MV64x60_WDC_ENABLE_SHIFT)) {
 104                mv64x60_wdt_service();
 105                pr_notice("watchdog activated\n");
 106        }
 107}
 108
 109static void mv64x60_wdt_handler_disable(void)
 110{
 111        if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE,
 112                                   MV64x60_WDC_ENABLE_SHIFT))
 113                pr_notice("watchdog deactivated\n");
 114}
 115
 116static void mv64x60_wdt_set_timeout(unsigned int timeout)
 117{
 118        /* maximum bus cycle count is 0xFFFFFFFF */
 119        if (timeout > 0xFFFFFFFF / bus_clk)
 120                timeout = 0xFFFFFFFF / bus_clk;
 121
 122        mv64x60_wdt_count = timeout * bus_clk >> 8;
 123        mv64x60_wdt_timeout = timeout;
 124}
 125
 126static int mv64x60_wdt_open(struct inode *inode, struct file *file)
 127{
 128        if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags))
 129                return -EBUSY;
 130
 131        if (nowayout)
 132                __module_get(THIS_MODULE);
 133
 134        mv64x60_wdt_handler_enable();
 135
 136        return nonseekable_open(inode, file);
 137}
 138
 139static int mv64x60_wdt_release(struct inode *inode, struct file *file)
 140{
 141        if (expect_close == 42)
 142                mv64x60_wdt_handler_disable();
 143        else {
 144                pr_crit("unexpected close, not stopping timer!\n");
 145                mv64x60_wdt_service();
 146        }
 147        expect_close = 0;
 148
 149        clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags);
 150
 151        return 0;
 152}
 153
 154static ssize_t mv64x60_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_close = 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_close = 42;
 169                        }
 170                }
 171                mv64x60_wdt_service();
 172        }
 173
 174        return len;
 175}
 176
 177static long mv64x60_wdt_ioctl(struct file *file,
 178                                        unsigned int cmd, unsigned long arg)
 179{
 180        int timeout;
 181        int options;
 182        void __user *argp = (void __user *)arg;
 183        static const struct watchdog_info info = {
 184                .options =      WDIOF_SETTIMEOUT        |
 185                                WDIOF_MAGICCLOSE        |
 186                                WDIOF_KEEPALIVEPING,
 187                .firmware_version = 0,
 188                .identity = "MV64x60 watchdog",
 189        };
 190
 191        switch (cmd) {
 192        case WDIOC_GETSUPPORT:
 193                if (copy_to_user(argp, &info, sizeof(info)))
 194                        return -EFAULT;
 195                break;
 196
 197        case WDIOC_GETSTATUS:
 198        case WDIOC_GETBOOTSTATUS:
 199                if (put_user(wdt_status, (int __user *)argp))
 200                        return -EFAULT;
 201                wdt_status &= ~WDIOF_KEEPALIVEPING;
 202                break;
 203
 204        case WDIOC_GETTEMP:
 205                return -EOPNOTSUPP;
 206
 207        case WDIOC_SETOPTIONS:
 208                if (get_user(options, (int __user *)argp))
 209                        return -EFAULT;
 210
 211                if (options & WDIOS_DISABLECARD)
 212                        mv64x60_wdt_handler_disable();
 213
 214                if (options & WDIOS_ENABLECARD)
 215                        mv64x60_wdt_handler_enable();
 216                break;
 217
 218        case WDIOC_KEEPALIVE:
 219                mv64x60_wdt_service();
 220                wdt_status |= WDIOF_KEEPALIVEPING;
 221                break;
 222
 223        case WDIOC_SETTIMEOUT:
 224                if (get_user(timeout, (int __user *)argp))
 225                        return -EFAULT;
 226                mv64x60_wdt_set_timeout(timeout);
 227                /* Fall through */
 228
 229        case WDIOC_GETTIMEOUT:
 230                if (put_user(mv64x60_wdt_timeout, (int __user *)argp))
 231                        return -EFAULT;
 232                break;
 233
 234        default:
 235                return -ENOTTY;
 236        }
 237
 238        return 0;
 239}
 240
 241static const struct file_operations mv64x60_wdt_fops = {
 242        .owner = THIS_MODULE,
 243        .llseek = no_llseek,
 244        .write = mv64x60_wdt_write,
 245        .unlocked_ioctl = mv64x60_wdt_ioctl,
 246        .open = mv64x60_wdt_open,
 247        .release = mv64x60_wdt_release,
 248};
 249
 250static struct miscdevice mv64x60_wdt_miscdev = {
 251        .minor = WATCHDOG_MINOR,
 252        .name = "watchdog",
 253        .fops = &mv64x60_wdt_fops,
 254};
 255
 256static int mv64x60_wdt_probe(struct platform_device *dev)
 257{
 258        struct mv64x60_wdt_pdata *pdata = dev_get_platdata(&dev->dev);
 259        struct resource *r;
 260        int timeout = 10;
 261
 262        bus_clk = 133;                  /* in MHz */
 263        if (pdata) {
 264                timeout = pdata->timeout;
 265                bus_clk = pdata->bus_clk;
 266        }
 267
 268        /* Since bus_clk is truncated MHz, actual frequency could be
 269         * up to 1MHz higher.  Round up, since it's better to time out
 270         * too late than too soon.
 271         */
 272        bus_clk++;
 273        bus_clk *= 1000000;             /* convert to Hz */
 274
 275        r = platform_get_resource(dev, IORESOURCE_MEM, 0);
 276        if (!r)
 277                return -ENODEV;
 278
 279        mv64x60_wdt_regs = devm_ioremap(&dev->dev, r->start, resource_size(r));
 280        if (mv64x60_wdt_regs == NULL)
 281                return -ENOMEM;
 282
 283        mv64x60_wdt_set_timeout(timeout);
 284
 285        mv64x60_wdt_handler_disable();  /* in case timer was already running */
 286
 287        return misc_register(&mv64x60_wdt_miscdev);
 288}
 289
 290static int mv64x60_wdt_remove(struct platform_device *dev)
 291{
 292        misc_deregister(&mv64x60_wdt_miscdev);
 293
 294        mv64x60_wdt_handler_disable();
 295
 296        return 0;
 297}
 298
 299static struct platform_driver mv64x60_wdt_driver = {
 300        .probe = mv64x60_wdt_probe,
 301        .remove = mv64x60_wdt_remove,
 302        .driver = {
 303                .name = MV64x60_WDT_NAME,
 304        },
 305};
 306
 307static int __init mv64x60_wdt_init(void)
 308{
 309        pr_info("MV64x60 watchdog driver\n");
 310
 311        return platform_driver_register(&mv64x60_wdt_driver);
 312}
 313
 314static void __exit mv64x60_wdt_exit(void)
 315{
 316        platform_driver_unregister(&mv64x60_wdt_driver);
 317}
 318
 319module_init(mv64x60_wdt_init);
 320module_exit(mv64x60_wdt_exit);
 321
 322MODULE_AUTHOR("James Chapman <jchapman@katalix.com>");
 323MODULE_DESCRIPTION("MV64x60 watchdog driver");
 324MODULE_LICENSE("GPL");
 325MODULE_ALIAS("platform:" MV64x60_WDT_NAME);
 326