linux/drivers/watchdog/lantiq_wdt.c
<<
>>
Prefs
   1/*
   2 *  This program is free software; you can redistribute it and/or modify it
   3 *  under the terms of the GNU General Public License version 2 as published
   4 *  by the Free Software Foundation.
   5 *
   6 *  Copyright (C) 2010 John Crispin <blogic@openwrt.org>
   7 *  Based on EP93xx wdt driver
   8 */
   9
  10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11
  12#include <linux/module.h>
  13#include <linux/fs.h>
  14#include <linux/miscdevice.h>
  15#include <linux/watchdog.h>
  16#include <linux/of_platform.h>
  17#include <linux/uaccess.h>
  18#include <linux/clk.h>
  19#include <linux/io.h>
  20
  21#include <lantiq_soc.h>
  22
  23/*
  24 * Section 3.4 of the datasheet
  25 * The password sequence protects the WDT control register from unintended
  26 * write actions, which might cause malfunction of the WDT.
  27 *
  28 * essentially the following two magic passwords need to be written to allow
  29 * IO access to the WDT core
  30 */
  31#define LTQ_WDT_PW1             0x00BE0000
  32#define LTQ_WDT_PW2             0x00DC0000
  33
  34#define LTQ_WDT_CR              0x0     /* watchdog control register */
  35#define LTQ_WDT_SR              0x8     /* watchdog status register */
  36
  37#define LTQ_WDT_SR_EN           (0x1 << 31)     /* enable bit */
  38#define LTQ_WDT_SR_PWD          (0x3 << 26)     /* turn on power */
  39#define LTQ_WDT_SR_CLKDIV       (0x3 << 24)     /* turn on clock and set */
  40                                                /* divider to 0x40000 */
  41#define LTQ_WDT_DIVIDER         0x40000
  42#define LTQ_MAX_TIMEOUT         ((1 << 16) - 1) /* the reload field is 16 bit */
  43
  44static bool nowayout = WATCHDOG_NOWAYOUT;
  45
  46static void __iomem *ltq_wdt_membase;
  47static unsigned long ltq_io_region_clk_rate;
  48
  49static unsigned long ltq_wdt_bootstatus;
  50static unsigned long ltq_wdt_in_use;
  51static int ltq_wdt_timeout = 30;
  52static int ltq_wdt_ok_to_close;
  53
  54static void
  55ltq_wdt_enable(void)
  56{
  57        unsigned long int timeout = ltq_wdt_timeout *
  58                        (ltq_io_region_clk_rate / LTQ_WDT_DIVIDER) + 0x1000;
  59        if (timeout > LTQ_MAX_TIMEOUT)
  60                timeout = LTQ_MAX_TIMEOUT;
  61
  62        /* write the first password magic */
  63        ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR);
  64        /* write the second magic plus the configuration and new timeout */
  65        ltq_w32(LTQ_WDT_SR_EN | LTQ_WDT_SR_PWD | LTQ_WDT_SR_CLKDIV |
  66                LTQ_WDT_PW2 | timeout, ltq_wdt_membase + LTQ_WDT_CR);
  67}
  68
  69static void
  70ltq_wdt_disable(void)
  71{
  72        /* write the first password magic */
  73        ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR);
  74        /*
  75         * write the second password magic with no config
  76         * this turns the watchdog off
  77         */
  78        ltq_w32(LTQ_WDT_PW2, ltq_wdt_membase + LTQ_WDT_CR);
  79}
  80
  81static ssize_t
  82ltq_wdt_write(struct file *file, const char __user *data,
  83                size_t len, loff_t *ppos)
  84{
  85        if (len) {
  86                if (!nowayout) {
  87                        size_t i;
  88
  89                        ltq_wdt_ok_to_close = 0;
  90                        for (i = 0; i != len; i++) {
  91                                char c;
  92
  93                                if (get_user(c, data + i))
  94                                        return -EFAULT;
  95                                if (c == 'V')
  96                                        ltq_wdt_ok_to_close = 1;
  97                                else
  98                                        ltq_wdt_ok_to_close = 0;
  99                        }
 100                }
 101                ltq_wdt_enable();
 102        }
 103
 104        return len;
 105}
 106
 107static struct watchdog_info ident = {
 108        .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 109                        WDIOF_CARDRESET,
 110        .identity = "ltq_wdt",
 111};
 112
 113static long
 114ltq_wdt_ioctl(struct file *file,
 115                unsigned int cmd, unsigned long arg)
 116{
 117        int ret = -ENOTTY;
 118
 119        switch (cmd) {
 120        case WDIOC_GETSUPPORT:
 121                ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
 122                                sizeof(ident)) ? -EFAULT : 0;
 123                break;
 124
 125        case WDIOC_GETBOOTSTATUS:
 126                ret = put_user(ltq_wdt_bootstatus, (int __user *)arg);
 127                break;
 128
 129        case WDIOC_GETSTATUS:
 130                ret = put_user(0, (int __user *)arg);
 131                break;
 132
 133        case WDIOC_SETTIMEOUT:
 134                ret = get_user(ltq_wdt_timeout, (int __user *)arg);
 135                if (!ret)
 136                        ltq_wdt_enable();
 137                /* intentional drop through */
 138        case WDIOC_GETTIMEOUT:
 139                ret = put_user(ltq_wdt_timeout, (int __user *)arg);
 140                break;
 141
 142        case WDIOC_KEEPALIVE:
 143                ltq_wdt_enable();
 144                ret = 0;
 145                break;
 146        }
 147        return ret;
 148}
 149
 150static int
 151ltq_wdt_open(struct inode *inode, struct file *file)
 152{
 153        if (test_and_set_bit(0, &ltq_wdt_in_use))
 154                return -EBUSY;
 155        ltq_wdt_in_use = 1;
 156        ltq_wdt_enable();
 157
 158        return nonseekable_open(inode, file);
 159}
 160
 161static int
 162ltq_wdt_release(struct inode *inode, struct file *file)
 163{
 164        if (ltq_wdt_ok_to_close)
 165                ltq_wdt_disable();
 166        else
 167                pr_err("watchdog closed without warning\n");
 168        ltq_wdt_ok_to_close = 0;
 169        clear_bit(0, &ltq_wdt_in_use);
 170
 171        return 0;
 172}
 173
 174static const struct file_operations ltq_wdt_fops = {
 175        .owner          = THIS_MODULE,
 176        .write          = ltq_wdt_write,
 177        .unlocked_ioctl = ltq_wdt_ioctl,
 178        .open           = ltq_wdt_open,
 179        .release        = ltq_wdt_release,
 180        .llseek         = no_llseek,
 181};
 182
 183static struct miscdevice ltq_wdt_miscdev = {
 184        .minor  = WATCHDOG_MINOR,
 185        .name   = "watchdog",
 186        .fops   = &ltq_wdt_fops,
 187};
 188
 189static int
 190ltq_wdt_probe(struct platform_device *pdev)
 191{
 192        struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 193        struct clk *clk;
 194
 195        if (!res) {
 196                dev_err(&pdev->dev, "cannot obtain I/O memory region");
 197                return -ENOENT;
 198        }
 199
 200        ltq_wdt_membase = devm_ioremap_resource(&pdev->dev, res);
 201        if (IS_ERR(ltq_wdt_membase))
 202                return PTR_ERR(ltq_wdt_membase);
 203
 204        /* we do not need to enable the clock as it is always running */
 205        clk = clk_get_io();
 206        if (IS_ERR(clk)) {
 207                dev_err(&pdev->dev, "Failed to get clock\n");
 208                return -ENOENT;
 209        }
 210        ltq_io_region_clk_rate = clk_get_rate(clk);
 211        clk_put(clk);
 212
 213        /* find out if the watchdog caused the last reboot */
 214        if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST)
 215                ltq_wdt_bootstatus = WDIOF_CARDRESET;
 216
 217        dev_info(&pdev->dev, "Init done\n");
 218        return misc_register(&ltq_wdt_miscdev);
 219}
 220
 221static int
 222ltq_wdt_remove(struct platform_device *pdev)
 223{
 224        misc_deregister(&ltq_wdt_miscdev);
 225
 226        return 0;
 227}
 228
 229static const struct of_device_id ltq_wdt_match[] = {
 230        { .compatible = "lantiq,wdt" },
 231        {},
 232};
 233MODULE_DEVICE_TABLE(of, ltq_wdt_match);
 234
 235static struct platform_driver ltq_wdt_driver = {
 236        .probe = ltq_wdt_probe,
 237        .remove = ltq_wdt_remove,
 238        .driver = {
 239                .name = "wdt",
 240                .owner = THIS_MODULE,
 241                .of_match_table = ltq_wdt_match,
 242        },
 243};
 244
 245module_platform_driver(ltq_wdt_driver);
 246
 247module_param(nowayout, bool, 0);
 248MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 249MODULE_AUTHOR("John Crispin <blogic@openwrt.org>");
 250MODULE_DESCRIPTION("Lantiq SoC Watchdog");
 251MODULE_LICENSE("GPL");
 252MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 253