linux/drivers/watchdog/ar7_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * drivers/watchdog/ar7_wdt.c
   4 *
   5 * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
   6 * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
   7 *
   8 * Some code taken from:
   9 * National Semiconductor SCx200 Watchdog support
  10 * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
  11 *
  12 */
  13
  14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  15
  16#include <linux/module.h>
  17#include <linux/moduleparam.h>
  18#include <linux/errno.h>
  19#include <linux/miscdevice.h>
  20#include <linux/platform_device.h>
  21#include <linux/watchdog.h>
  22#include <linux/fs.h>
  23#include <linux/ioport.h>
  24#include <linux/io.h>
  25#include <linux/uaccess.h>
  26#include <linux/clk.h>
  27
  28#include <asm/addrspace.h>
  29#include <asm/mach-ar7/ar7.h>
  30
  31#define LONGNAME "TI AR7 Watchdog Timer"
  32
  33MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
  34MODULE_DESCRIPTION(LONGNAME);
  35MODULE_LICENSE("GPL");
  36
  37static int margin = 60;
  38module_param(margin, int, 0);
  39MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  40
  41static bool nowayout = WATCHDOG_NOWAYOUT;
  42module_param(nowayout, bool, 0);
  43MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  44
  45#define READ_REG(x) readl((void __iomem *)&(x))
  46#define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
  47
  48struct ar7_wdt {
  49        u32 kick_lock;
  50        u32 kick;
  51        u32 change_lock;
  52        u32 change;
  53        u32 disable_lock;
  54        u32 disable;
  55        u32 prescale_lock;
  56        u32 prescale;
  57};
  58
  59static unsigned long wdt_is_open;
  60static unsigned expect_close;
  61static DEFINE_SPINLOCK(wdt_lock);
  62
  63/* XXX currently fixed, allows max margin ~68.72 secs */
  64#define prescale_value 0xffff
  65
  66/* Resource of the WDT registers */
  67static struct resource *ar7_regs_wdt;
  68/* Pointer to the remapped WDT IO space */
  69static struct ar7_wdt *ar7_wdt;
  70
  71static struct clk *vbus_clk;
  72
  73static void ar7_wdt_kick(u32 value)
  74{
  75        WRITE_REG(ar7_wdt->kick_lock, 0x5555);
  76        if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
  77                WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
  78                if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
  79                        WRITE_REG(ar7_wdt->kick, value);
  80                        return;
  81                }
  82        }
  83        pr_err("failed to unlock WDT kick reg\n");
  84}
  85
  86static void ar7_wdt_prescale(u32 value)
  87{
  88        WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
  89        if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
  90                WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
  91                if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
  92                        WRITE_REG(ar7_wdt->prescale, value);
  93                        return;
  94                }
  95        }
  96        pr_err("failed to unlock WDT prescale reg\n");
  97}
  98
  99static void ar7_wdt_change(u32 value)
 100{
 101        WRITE_REG(ar7_wdt->change_lock, 0x6666);
 102        if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
 103                WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
 104                if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
 105                        WRITE_REG(ar7_wdt->change, value);
 106                        return;
 107                }
 108        }
 109        pr_err("failed to unlock WDT change reg\n");
 110}
 111
 112static void ar7_wdt_disable(u32 value)
 113{
 114        WRITE_REG(ar7_wdt->disable_lock, 0x7777);
 115        if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
 116                WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
 117                if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
 118                        WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
 119                        if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
 120                                WRITE_REG(ar7_wdt->disable, value);
 121                                return;
 122                        }
 123                }
 124        }
 125        pr_err("failed to unlock WDT disable reg\n");
 126}
 127
 128static void ar7_wdt_update_margin(int new_margin)
 129{
 130        u32 change;
 131        u32 vbus_rate;
 132
 133        vbus_rate = clk_get_rate(vbus_clk);
 134        change = new_margin * (vbus_rate / prescale_value);
 135        if (change < 1)
 136                change = 1;
 137        if (change > 0xffff)
 138                change = 0xffff;
 139        ar7_wdt_change(change);
 140        margin = change * prescale_value / vbus_rate;
 141        pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
 142                margin, prescale_value, change, vbus_rate);
 143}
 144
 145static void ar7_wdt_enable_wdt(void)
 146{
 147        pr_debug("enabling watchdog timer\n");
 148        ar7_wdt_disable(1);
 149        ar7_wdt_kick(1);
 150}
 151
 152static void ar7_wdt_disable_wdt(void)
 153{
 154        pr_debug("disabling watchdog timer\n");
 155        ar7_wdt_disable(0);
 156}
 157
 158static int ar7_wdt_open(struct inode *inode, struct file *file)
 159{
 160        /* only allow one at a time */
 161        if (test_and_set_bit(0, &wdt_is_open))
 162                return -EBUSY;
 163        ar7_wdt_enable_wdt();
 164        expect_close = 0;
 165
 166        return stream_open(inode, file);
 167}
 168
 169static int ar7_wdt_release(struct inode *inode, struct file *file)
 170{
 171        if (!expect_close)
 172                pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
 173        else if (!nowayout)
 174                ar7_wdt_disable_wdt();
 175        clear_bit(0, &wdt_is_open);
 176        return 0;
 177}
 178
 179static ssize_t ar7_wdt_write(struct file *file, const char *data,
 180                             size_t len, loff_t *ppos)
 181{
 182        /* check for a magic close character */
 183        if (len) {
 184                size_t i;
 185
 186                spin_lock(&wdt_lock);
 187                ar7_wdt_kick(1);
 188                spin_unlock(&wdt_lock);
 189
 190                expect_close = 0;
 191                for (i = 0; i < len; ++i) {
 192                        char c;
 193                        if (get_user(c, data + i))
 194                                return -EFAULT;
 195                        if (c == 'V')
 196                                expect_close = 1;
 197                }
 198
 199        }
 200        return len;
 201}
 202
 203static long ar7_wdt_ioctl(struct file *file,
 204                                        unsigned int cmd, unsigned long arg)
 205{
 206        static const struct watchdog_info ident = {
 207                .identity = LONGNAME,
 208                .firmware_version = 1,
 209                .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 210                                                WDIOF_MAGICCLOSE),
 211        };
 212        int new_margin;
 213
 214        switch (cmd) {
 215        case WDIOC_GETSUPPORT:
 216                if (copy_to_user((struct watchdog_info *)arg, &ident,
 217                                sizeof(ident)))
 218                        return -EFAULT;
 219                return 0;
 220        case WDIOC_GETSTATUS:
 221        case WDIOC_GETBOOTSTATUS:
 222                if (put_user(0, (int *)arg))
 223                        return -EFAULT;
 224                return 0;
 225        case WDIOC_KEEPALIVE:
 226                ar7_wdt_kick(1);
 227                return 0;
 228        case WDIOC_SETTIMEOUT:
 229                if (get_user(new_margin, (int *)arg))
 230                        return -EFAULT;
 231                if (new_margin < 1)
 232                        return -EINVAL;
 233
 234                spin_lock(&wdt_lock);
 235                ar7_wdt_update_margin(new_margin);
 236                ar7_wdt_kick(1);
 237                spin_unlock(&wdt_lock);
 238                fallthrough;
 239        case WDIOC_GETTIMEOUT:
 240                if (put_user(margin, (int *)arg))
 241                        return -EFAULT;
 242                return 0;
 243        default:
 244                return -ENOTTY;
 245        }
 246}
 247
 248static const struct file_operations ar7_wdt_fops = {
 249        .owner          = THIS_MODULE,
 250        .write          = ar7_wdt_write,
 251        .unlocked_ioctl = ar7_wdt_ioctl,
 252        .compat_ioctl   = compat_ptr_ioctl,
 253        .open           = ar7_wdt_open,
 254        .release        = ar7_wdt_release,
 255        .llseek         = no_llseek,
 256};
 257
 258static struct miscdevice ar7_wdt_miscdev = {
 259        .minor          = WATCHDOG_MINOR,
 260        .name           = "watchdog",
 261        .fops           = &ar7_wdt_fops,
 262};
 263
 264static int ar7_wdt_probe(struct platform_device *pdev)
 265{
 266        int rc;
 267
 268        ar7_regs_wdt =
 269                platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
 270        ar7_wdt = devm_ioremap_resource(&pdev->dev, ar7_regs_wdt);
 271        if (IS_ERR(ar7_wdt))
 272                return PTR_ERR(ar7_wdt);
 273
 274        vbus_clk = clk_get(NULL, "vbus");
 275        if (IS_ERR(vbus_clk)) {
 276                pr_err("could not get vbus clock\n");
 277                return PTR_ERR(vbus_clk);
 278        }
 279
 280        ar7_wdt_disable_wdt();
 281        ar7_wdt_prescale(prescale_value);
 282        ar7_wdt_update_margin(margin);
 283
 284        rc = misc_register(&ar7_wdt_miscdev);
 285        if (rc) {
 286                pr_err("unable to register misc device\n");
 287                goto out;
 288        }
 289        return 0;
 290
 291out:
 292        clk_put(vbus_clk);
 293        vbus_clk = NULL;
 294        return rc;
 295}
 296
 297static int ar7_wdt_remove(struct platform_device *pdev)
 298{
 299        misc_deregister(&ar7_wdt_miscdev);
 300        clk_put(vbus_clk);
 301        vbus_clk = NULL;
 302        return 0;
 303}
 304
 305static void ar7_wdt_shutdown(struct platform_device *pdev)
 306{
 307        if (!nowayout)
 308                ar7_wdt_disable_wdt();
 309}
 310
 311static struct platform_driver ar7_wdt_driver = {
 312        .probe = ar7_wdt_probe,
 313        .remove = ar7_wdt_remove,
 314        .shutdown = ar7_wdt_shutdown,
 315        .driver = {
 316                .name = "ar7_wdt",
 317        },
 318};
 319
 320module_platform_driver(ar7_wdt_driver);
 321