linux/drivers/watchdog/ath79_wdt.c
<<
>>
Prefs
   1/*
   2 * Atheros AR71XX/AR724X/AR913X built-in hardware watchdog timer.
   3 *
   4 * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
   5 * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
   6 *
   7 * This driver was based on: drivers/watchdog/ixp4xx_wdt.c
   8 *      Author: Deepak Saxena <dsaxena@plexity.net>
   9 *      Copyright 2004 (c) MontaVista, Software, Inc.
  10 *
  11 * which again was based on sa1100 driver,
  12 *      Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
  13 *
  14 * This program is free software; you can redistribute it and/or modify it
  15 * under the terms of the GNU General Public License version 2 as published
  16 * by the Free Software Foundation.
  17 *
  18 */
  19
  20#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  21
  22#include <linux/bitops.h>
  23#include <linux/errno.h>
  24#include <linux/fs.h>
  25#include <linux/init.h>
  26#include <linux/io.h>
  27#include <linux/kernel.h>
  28#include <linux/miscdevice.h>
  29#include <linux/module.h>
  30#include <linux/moduleparam.h>
  31#include <linux/platform_device.h>
  32#include <linux/types.h>
  33#include <linux/watchdog.h>
  34#include <linux/clk.h>
  35#include <linux/err.h>
  36#include <linux/of.h>
  37#include <linux/of_platform.h>
  38
  39#define DRIVER_NAME     "ath79-wdt"
  40
  41#define WDT_TIMEOUT     15      /* seconds */
  42
  43#define WDOG_REG_CTRL           0x00
  44#define WDOG_REG_TIMER          0x04
  45
  46#define WDOG_CTRL_LAST_RESET    BIT(31)
  47#define WDOG_CTRL_ACTION_MASK   3
  48#define WDOG_CTRL_ACTION_NONE   0       /* no action */
  49#define WDOG_CTRL_ACTION_GPI    1       /* general purpose interrupt */
  50#define WDOG_CTRL_ACTION_NMI    2       /* NMI */
  51#define WDOG_CTRL_ACTION_FCR    3       /* full chip reset */
  52
  53static bool nowayout = WATCHDOG_NOWAYOUT;
  54module_param(nowayout, bool, 0);
  55MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  56                           "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  57
  58static int timeout = WDT_TIMEOUT;
  59module_param(timeout, int, 0);
  60MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
  61                          "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)");
  62
  63static unsigned long wdt_flags;
  64
  65#define WDT_FLAGS_BUSY          0
  66#define WDT_FLAGS_EXPECT_CLOSE  1
  67
  68static struct clk *wdt_clk;
  69static unsigned long wdt_freq;
  70static int boot_status;
  71static int max_timeout;
  72static void __iomem *wdt_base;
  73
  74static inline void ath79_wdt_wr(unsigned reg, u32 val)
  75{
  76        iowrite32(val, wdt_base + reg);
  77}
  78
  79static inline u32 ath79_wdt_rr(unsigned reg)
  80{
  81        return ioread32(wdt_base + reg);
  82}
  83
  84static inline void ath79_wdt_keepalive(void)
  85{
  86        ath79_wdt_wr(WDOG_REG_TIMER, wdt_freq * timeout);
  87        /* flush write */
  88        ath79_wdt_rr(WDOG_REG_TIMER);
  89}
  90
  91static inline void ath79_wdt_enable(void)
  92{
  93        ath79_wdt_keepalive();
  94        ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_FCR);
  95        /* flush write */
  96        ath79_wdt_rr(WDOG_REG_CTRL);
  97}
  98
  99static inline void ath79_wdt_disable(void)
 100{
 101        ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_NONE);
 102        /* flush write */
 103        ath79_wdt_rr(WDOG_REG_CTRL);
 104}
 105
 106static int ath79_wdt_set_timeout(int val)
 107{
 108        if (val < 1 || val > max_timeout)
 109                return -EINVAL;
 110
 111        timeout = val;
 112        ath79_wdt_keepalive();
 113
 114        return 0;
 115}
 116
 117static int ath79_wdt_open(struct inode *inode, struct file *file)
 118{
 119        if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags))
 120                return -EBUSY;
 121
 122        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 123        ath79_wdt_enable();
 124
 125        return nonseekable_open(inode, file);
 126}
 127
 128static int ath79_wdt_release(struct inode *inode, struct file *file)
 129{
 130        if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags))
 131                ath79_wdt_disable();
 132        else {
 133                pr_crit("device closed unexpectedly, watchdog timer will not stop!\n");
 134                ath79_wdt_keepalive();
 135        }
 136
 137        clear_bit(WDT_FLAGS_BUSY, &wdt_flags);
 138        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 139
 140        return 0;
 141}
 142
 143static ssize_t ath79_wdt_write(struct file *file, const char *data,
 144                                size_t len, loff_t *ppos)
 145{
 146        if (len) {
 147                if (!nowayout) {
 148                        size_t i;
 149
 150                        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 151
 152                        for (i = 0; i != len; i++) {
 153                                char c;
 154
 155                                if (get_user(c, data + i))
 156                                        return -EFAULT;
 157
 158                                if (c == 'V')
 159                                        set_bit(WDT_FLAGS_EXPECT_CLOSE,
 160                                                &wdt_flags);
 161                        }
 162                }
 163
 164                ath79_wdt_keepalive();
 165        }
 166
 167        return len;
 168}
 169
 170static const struct watchdog_info ath79_wdt_info = {
 171        .options                = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 172                                  WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
 173        .firmware_version       = 0,
 174        .identity               = "ATH79 watchdog",
 175};
 176
 177static long ath79_wdt_ioctl(struct file *file, unsigned int cmd,
 178                            unsigned long arg)
 179{
 180        void __user *argp = (void __user *)arg;
 181        int __user *p = argp;
 182        int err;
 183        int t;
 184
 185        switch (cmd) {
 186        case WDIOC_GETSUPPORT:
 187                err = copy_to_user(argp, &ath79_wdt_info,
 188                                   sizeof(ath79_wdt_info)) ? -EFAULT : 0;
 189                break;
 190
 191        case WDIOC_GETSTATUS:
 192                err = put_user(0, p);
 193                break;
 194
 195        case WDIOC_GETBOOTSTATUS:
 196                err = put_user(boot_status, p);
 197                break;
 198
 199        case WDIOC_KEEPALIVE:
 200                ath79_wdt_keepalive();
 201                err = 0;
 202                break;
 203
 204        case WDIOC_SETTIMEOUT:
 205                err = get_user(t, p);
 206                if (err)
 207                        break;
 208
 209                err = ath79_wdt_set_timeout(t);
 210                if (err)
 211                        break;
 212
 213                /* fallthrough */
 214        case WDIOC_GETTIMEOUT:
 215                err = put_user(timeout, p);
 216                break;
 217
 218        default:
 219                err = -ENOTTY;
 220                break;
 221        }
 222
 223        return err;
 224}
 225
 226static const struct file_operations ath79_wdt_fops = {
 227        .owner          = THIS_MODULE,
 228        .llseek         = no_llseek,
 229        .write          = ath79_wdt_write,
 230        .unlocked_ioctl = ath79_wdt_ioctl,
 231        .open           = ath79_wdt_open,
 232        .release        = ath79_wdt_release,
 233};
 234
 235static struct miscdevice ath79_wdt_miscdev = {
 236        .minor = WATCHDOG_MINOR,
 237        .name = "watchdog",
 238        .fops = &ath79_wdt_fops,
 239};
 240
 241static int ath79_wdt_probe(struct platform_device *pdev)
 242{
 243        struct resource *res;
 244        u32 ctrl;
 245        int err;
 246
 247        if (wdt_base)
 248                return -EBUSY;
 249
 250        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 251        wdt_base = devm_ioremap_resource(&pdev->dev, res);
 252        if (IS_ERR(wdt_base))
 253                return PTR_ERR(wdt_base);
 254
 255        wdt_clk = devm_clk_get(&pdev->dev, "wdt");
 256        if (IS_ERR(wdt_clk))
 257                return PTR_ERR(wdt_clk);
 258
 259        err = clk_enable(wdt_clk);
 260        if (err)
 261                return err;
 262
 263        wdt_freq = clk_get_rate(wdt_clk);
 264        if (!wdt_freq) {
 265                err = -EINVAL;
 266                goto err_clk_disable;
 267        }
 268
 269        max_timeout = (0xfffffffful / wdt_freq);
 270        if (timeout < 1 || timeout > max_timeout) {
 271                timeout = max_timeout;
 272                dev_info(&pdev->dev,
 273                        "timeout value must be 0 < timeout < %d, using %d\n",
 274                        max_timeout, timeout);
 275        }
 276
 277        ctrl = ath79_wdt_rr(WDOG_REG_CTRL);
 278        boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0;
 279
 280        err = misc_register(&ath79_wdt_miscdev);
 281        if (err) {
 282                dev_err(&pdev->dev,
 283                        "unable to register misc device, err=%d\n", err);
 284                goto err_clk_disable;
 285        }
 286
 287        return 0;
 288
 289err_clk_disable:
 290        clk_disable(wdt_clk);
 291        return err;
 292}
 293
 294static int ath79_wdt_remove(struct platform_device *pdev)
 295{
 296        misc_deregister(&ath79_wdt_miscdev);
 297        clk_disable(wdt_clk);
 298        return 0;
 299}
 300
 301static void ath97_wdt_shutdown(struct platform_device *pdev)
 302{
 303        ath79_wdt_disable();
 304}
 305
 306#ifdef CONFIG_OF
 307static const struct of_device_id ath79_wdt_match[] = {
 308        { .compatible = "qca,ar7130-wdt" },
 309        {},
 310};
 311MODULE_DEVICE_TABLE(of, ath79_wdt_match);
 312#endif
 313
 314static struct platform_driver ath79_wdt_driver = {
 315        .probe          = ath79_wdt_probe,
 316        .remove         = ath79_wdt_remove,
 317        .shutdown       = ath97_wdt_shutdown,
 318        .driver         = {
 319                .name   = DRIVER_NAME,
 320                .owner  = THIS_MODULE,
 321                .of_match_table = of_match_ptr(ath79_wdt_match),
 322        },
 323};
 324
 325module_platform_driver(ath79_wdt_driver);
 326
 327MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver");
 328MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
 329MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org");
 330MODULE_LICENSE("GPL v2");
 331MODULE_ALIAS("platform:" DRIVER_NAME);
 332MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 333