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