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/kernel.h>
  27#include <linux/miscdevice.h>
  28#include <linux/module.h>
  29#include <linux/moduleparam.h>
  30#include <linux/platform_device.h>
  31#include <linux/types.h>
  32#include <linux/watchdog.h>
  33#include <linux/clk.h>
  34#include <linux/err.h>
  35
  36#include <asm/mach-ath79/ath79.h>
  37#include <asm/mach-ath79/ar71xx_regs.h>
  38
  39#define DRIVER_NAME     "ath79-wdt"
  40
  41#define WDT_TIMEOUT     15      /* seconds */
  42
  43#define WDOG_CTRL_LAST_RESET    BIT(31)
  44#define WDOG_CTRL_ACTION_MASK   3
  45#define WDOG_CTRL_ACTION_NONE   0       /* no action */
  46#define WDOG_CTRL_ACTION_GPI    1       /* general purpose interrupt */
  47#define WDOG_CTRL_ACTION_NMI    2       /* NMI */
  48#define WDOG_CTRL_ACTION_FCR    3       /* full chip reset */
  49
  50static bool nowayout = WATCHDOG_NOWAYOUT;
  51module_param(nowayout, bool, 0);
  52MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  53                           "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  54
  55static int timeout = WDT_TIMEOUT;
  56module_param(timeout, int, 0);
  57MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
  58                          "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)");
  59
  60static unsigned long wdt_flags;
  61
  62#define WDT_FLAGS_BUSY          0
  63#define WDT_FLAGS_EXPECT_CLOSE  1
  64
  65static struct clk *wdt_clk;
  66static unsigned long wdt_freq;
  67static int boot_status;
  68static int max_timeout;
  69
  70static inline void ath79_wdt_keepalive(void)
  71{
  72        ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout);
  73        /* flush write */
  74        ath79_reset_rr(AR71XX_RESET_REG_WDOG);
  75}
  76
  77static inline void ath79_wdt_enable(void)
  78{
  79        ath79_wdt_keepalive();
  80        ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR);
  81        /* flush write */
  82        ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL);
  83}
  84
  85static inline void ath79_wdt_disable(void)
  86{
  87        ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE);
  88        /* flush write */
  89        ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL);
  90}
  91
  92static int ath79_wdt_set_timeout(int val)
  93{
  94        if (val < 1 || val > max_timeout)
  95                return -EINVAL;
  96
  97        timeout = val;
  98        ath79_wdt_keepalive();
  99
 100        return 0;
 101}
 102
 103static int ath79_wdt_open(struct inode *inode, struct file *file)
 104{
 105        if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags))
 106                return -EBUSY;
 107
 108        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 109        ath79_wdt_enable();
 110
 111        return nonseekable_open(inode, file);
 112}
 113
 114static int ath79_wdt_release(struct inode *inode, struct file *file)
 115{
 116        if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags))
 117                ath79_wdt_disable();
 118        else {
 119                pr_crit("device closed unexpectedly, watchdog timer will not stop!\n");
 120                ath79_wdt_keepalive();
 121        }
 122
 123        clear_bit(WDT_FLAGS_BUSY, &wdt_flags);
 124        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 125
 126        return 0;
 127}
 128
 129static ssize_t ath79_wdt_write(struct file *file, const char *data,
 130                                size_t len, loff_t *ppos)
 131{
 132        if (len) {
 133                if (!nowayout) {
 134                        size_t i;
 135
 136                        clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
 137
 138                        for (i = 0; i != len; i++) {
 139                                char c;
 140
 141                                if (get_user(c, data + i))
 142                                        return -EFAULT;
 143
 144                                if (c == 'V')
 145                                        set_bit(WDT_FLAGS_EXPECT_CLOSE,
 146                                                &wdt_flags);
 147                        }
 148                }
 149
 150                ath79_wdt_keepalive();
 151        }
 152
 153        return len;
 154}
 155
 156static const struct watchdog_info ath79_wdt_info = {
 157        .options                = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 158                                  WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
 159        .firmware_version       = 0,
 160        .identity               = "ATH79 watchdog",
 161};
 162
 163static long ath79_wdt_ioctl(struct file *file, unsigned int cmd,
 164                            unsigned long arg)
 165{
 166        void __user *argp = (void __user *)arg;
 167        int __user *p = argp;
 168        int err;
 169        int t;
 170
 171        switch (cmd) {
 172        case WDIOC_GETSUPPORT:
 173                err = copy_to_user(argp, &ath79_wdt_info,
 174                                   sizeof(ath79_wdt_info)) ? -EFAULT : 0;
 175                break;
 176
 177        case WDIOC_GETSTATUS:
 178                err = put_user(0, p);
 179                break;
 180
 181        case WDIOC_GETBOOTSTATUS:
 182                err = put_user(boot_status, p);
 183                break;
 184
 185        case WDIOC_KEEPALIVE:
 186                ath79_wdt_keepalive();
 187                err = 0;
 188                break;
 189
 190        case WDIOC_SETTIMEOUT:
 191                err = get_user(t, p);
 192                if (err)
 193                        break;
 194
 195                err = ath79_wdt_set_timeout(t);
 196                if (err)
 197                        break;
 198
 199                /* fallthrough */
 200        case WDIOC_GETTIMEOUT:
 201                err = put_user(timeout, p);
 202                break;
 203
 204        default:
 205                err = -ENOTTY;
 206                break;
 207        }
 208
 209        return err;
 210}
 211
 212static const struct file_operations ath79_wdt_fops = {
 213        .owner          = THIS_MODULE,
 214        .llseek         = no_llseek,
 215        .write          = ath79_wdt_write,
 216        .unlocked_ioctl = ath79_wdt_ioctl,
 217        .open           = ath79_wdt_open,
 218        .release        = ath79_wdt_release,
 219};
 220
 221static struct miscdevice ath79_wdt_miscdev = {
 222        .minor = WATCHDOG_MINOR,
 223        .name = "watchdog",
 224        .fops = &ath79_wdt_fops,
 225};
 226
 227static int ath79_wdt_probe(struct platform_device *pdev)
 228{
 229        u32 ctrl;
 230        int err;
 231
 232        wdt_clk = clk_get(&pdev->dev, "wdt");
 233        if (IS_ERR(wdt_clk))
 234                return PTR_ERR(wdt_clk);
 235
 236        err = clk_enable(wdt_clk);
 237        if (err)
 238                goto err_clk_put;
 239
 240        wdt_freq = clk_get_rate(wdt_clk);
 241        if (!wdt_freq) {
 242                err = -EINVAL;
 243                goto err_clk_disable;
 244        }
 245
 246        max_timeout = (0xfffffffful / wdt_freq);
 247        if (timeout < 1 || timeout > max_timeout) {
 248                timeout = max_timeout;
 249                dev_info(&pdev->dev,
 250                        "timeout value must be 0 < timeout < %d, using %d\n",
 251                        max_timeout, timeout);
 252        }
 253
 254        ctrl = ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL);
 255        boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0;
 256
 257        err = misc_register(&ath79_wdt_miscdev);
 258        if (err) {
 259                dev_err(&pdev->dev,
 260                        "unable to register misc device, err=%d\n", err);
 261                goto err_clk_disable;
 262        }
 263
 264        return 0;
 265
 266err_clk_disable:
 267        clk_disable(wdt_clk);
 268err_clk_put:
 269        clk_put(wdt_clk);
 270        return err;
 271}
 272
 273static int ath79_wdt_remove(struct platform_device *pdev)
 274{
 275        misc_deregister(&ath79_wdt_miscdev);
 276        clk_disable(wdt_clk);
 277        clk_put(wdt_clk);
 278        return 0;
 279}
 280
 281static void ath97_wdt_shutdown(struct platform_device *pdev)
 282{
 283        ath79_wdt_disable();
 284}
 285
 286static struct platform_driver ath79_wdt_driver = {
 287        .probe          = ath79_wdt_probe,
 288        .remove         = ath79_wdt_remove,
 289        .shutdown       = ath97_wdt_shutdown,
 290        .driver         = {
 291                .name   = DRIVER_NAME,
 292                .owner  = THIS_MODULE,
 293        },
 294};
 295
 296module_platform_driver(ath79_wdt_driver);
 297
 298MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver");
 299MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
 300MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org");
 301MODULE_LICENSE("GPL v2");
 302MODULE_ALIAS("platform:" DRIVER_NAME);
 303MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 304
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.