linux/drivers/watchdog/asm9260_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Watchdog driver for Alphascale ASM9260.
   4 *
   5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
   6 */
   7
   8#include <linux/bitops.h>
   9#include <linux/clk.h>
  10#include <linux/delay.h>
  11#include <linux/interrupt.h>
  12#include <linux/io.h>
  13#include <linux/module.h>
  14#include <linux/of.h>
  15#include <linux/platform_device.h>
  16#include <linux/reset.h>
  17#include <linux/watchdog.h>
  18
  19#define CLOCK_FREQ      1000000
  20
  21/* Watchdog Mode register */
  22#define HW_WDMOD                        0x00
  23/* Wake interrupt. Set by HW, can't be cleared. */
  24#define BM_MOD_WDINT                    BIT(3)
  25/* This bit set if timeout reached. Cleared by SW. */
  26#define BM_MOD_WDTOF                    BIT(2)
  27/* HW Reset on timeout */
  28#define BM_MOD_WDRESET                  BIT(1)
  29/* WD enable */
  30#define BM_MOD_WDEN                     BIT(0)
  31
  32/*
  33 * Watchdog Timer Constant register
  34 * Minimal value is 0xff, the meaning of this value
  35 * depends on used clock: T = WDCLK * (0xff + 1) * 4
  36 */
  37#define HW_WDTC                         0x04
  38#define BM_WDTC_MAX(freq)               (0x7fffffff / (freq))
  39
  40/* Watchdog Feed register */
  41#define HW_WDFEED                       0x08
  42
  43/* Watchdog Timer Value register */
  44#define HW_WDTV                         0x0c
  45
  46#define ASM9260_WDT_DEFAULT_TIMEOUT     30
  47
  48enum asm9260_wdt_mode {
  49        HW_RESET,
  50        SW_RESET,
  51        DEBUG,
  52};
  53
  54struct asm9260_wdt_priv {
  55        struct device           *dev;
  56        struct watchdog_device  wdd;
  57        struct clk              *clk;
  58        struct clk              *clk_ahb;
  59        struct reset_control    *rst;
  60
  61        void __iomem            *iobase;
  62        int                     irq;
  63        unsigned long           wdt_freq;
  64        enum asm9260_wdt_mode   mode;
  65};
  66
  67static int asm9260_wdt_feed(struct watchdog_device *wdd)
  68{
  69        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  70
  71        iowrite32(0xaa, priv->iobase + HW_WDFEED);
  72        iowrite32(0x55, priv->iobase + HW_WDFEED);
  73
  74        return 0;
  75}
  76
  77static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
  78{
  79        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  80        u32 counter;
  81
  82        counter = ioread32(priv->iobase + HW_WDTV);
  83
  84        return counter / priv->wdt_freq;
  85}
  86
  87static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
  88{
  89        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
  90        u32 counter;
  91
  92        counter = wdd->timeout * priv->wdt_freq;
  93
  94        iowrite32(counter, priv->iobase + HW_WDTC);
  95
  96        return 0;
  97}
  98
  99static int asm9260_wdt_enable(struct watchdog_device *wdd)
 100{
 101        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 102        u32 mode = 0;
 103
 104        if (priv->mode == HW_RESET)
 105                mode = BM_MOD_WDRESET;
 106
 107        iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
 108
 109        asm9260_wdt_updatetimeout(wdd);
 110
 111        asm9260_wdt_feed(wdd);
 112
 113        return 0;
 114}
 115
 116static int asm9260_wdt_disable(struct watchdog_device *wdd)
 117{
 118        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 119
 120        /* The only way to disable WD is to reset it. */
 121        reset_control_assert(priv->rst);
 122        reset_control_deassert(priv->rst);
 123
 124        return 0;
 125}
 126
 127static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
 128{
 129        wdd->timeout = to;
 130        asm9260_wdt_updatetimeout(wdd);
 131
 132        return 0;
 133}
 134
 135static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
 136{
 137        /* init WD if it was not started */
 138
 139        iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
 140
 141        iowrite32(0xff, priv->iobase + HW_WDTC);
 142        /* first pass correct sequence */
 143        asm9260_wdt_feed(&priv->wdd);
 144        /*
 145         * Then write wrong pattern to the feed to trigger reset
 146         * ASAP.
 147         */
 148        iowrite32(0xff, priv->iobase + HW_WDFEED);
 149
 150        mdelay(1000);
 151}
 152
 153static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
 154{
 155        struct asm9260_wdt_priv *priv = devid;
 156        u32 stat;
 157
 158        stat = ioread32(priv->iobase + HW_WDMOD);
 159        if (!(stat & BM_MOD_WDINT))
 160                return IRQ_NONE;
 161
 162        if (priv->mode == DEBUG) {
 163                dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
 164        } else {
 165                dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
 166                asm9260_wdt_sys_reset(priv);
 167        }
 168
 169        return IRQ_HANDLED;
 170}
 171
 172static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
 173                           void *data)
 174{
 175        struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 176
 177        asm9260_wdt_sys_reset(priv);
 178
 179        return 0;
 180}
 181
 182static const struct watchdog_info asm9260_wdt_ident = {
 183        .options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
 184                                | WDIOF_MAGICCLOSE,
 185        .identity         =     "Alphascale asm9260 Watchdog",
 186};
 187
 188static const struct watchdog_ops asm9260_wdt_ops = {
 189        .owner          = THIS_MODULE,
 190        .start          = asm9260_wdt_enable,
 191        .stop           = asm9260_wdt_disable,
 192        .get_timeleft   = asm9260_wdt_gettimeleft,
 193        .ping           = asm9260_wdt_feed,
 194        .set_timeout    = asm9260_wdt_settimeout,
 195        .restart        = asm9260_restart,
 196};
 197
 198static void asm9260_clk_disable_unprepare(void *data)
 199{
 200        clk_disable_unprepare(data);
 201}
 202
 203static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
 204{
 205        int err;
 206        unsigned long clk;
 207
 208        priv->clk = devm_clk_get(priv->dev, "mod");
 209        if (IS_ERR(priv->clk)) {
 210                dev_err(priv->dev, "Failed to get \"mod\" clk\n");
 211                return PTR_ERR(priv->clk);
 212        }
 213
 214        /* configure AHB clock */
 215        priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
 216        if (IS_ERR(priv->clk_ahb)) {
 217                dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
 218                return PTR_ERR(priv->clk_ahb);
 219        }
 220
 221        err = clk_prepare_enable(priv->clk_ahb);
 222        if (err) {
 223                dev_err(priv->dev, "Failed to enable ahb_clk!\n");
 224                return err;
 225        }
 226        err = devm_add_action_or_reset(priv->dev,
 227                                       asm9260_clk_disable_unprepare,
 228                                       priv->clk_ahb);
 229        if (err)
 230                return err;
 231
 232        err = clk_set_rate(priv->clk, CLOCK_FREQ);
 233        if (err) {
 234                dev_err(priv->dev, "Failed to set rate!\n");
 235                return err;
 236        }
 237
 238        err = clk_prepare_enable(priv->clk);
 239        if (err) {
 240                dev_err(priv->dev, "Failed to enable clk!\n");
 241                return err;
 242        }
 243        err = devm_add_action_or_reset(priv->dev,
 244                                       asm9260_clk_disable_unprepare,
 245                                       priv->clk);
 246        if (err)
 247                return err;
 248
 249        /* wdt has internal divider */
 250        clk = clk_get_rate(priv->clk);
 251        if (!clk) {
 252                dev_err(priv->dev, "Failed, clk is 0!\n");
 253                return -EINVAL;
 254        }
 255
 256        priv->wdt_freq = clk / 2;
 257
 258        return 0;
 259}
 260
 261static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
 262{
 263        const char *tmp;
 264        int ret;
 265
 266        /* default mode */
 267        priv->mode = HW_RESET;
 268
 269        ret = of_property_read_string(priv->dev->of_node,
 270                                      "alphascale,mode", &tmp);
 271        if (ret < 0)
 272                return;
 273
 274        if (!strcmp(tmp, "hw"))
 275                priv->mode = HW_RESET;
 276        else if (!strcmp(tmp, "sw"))
 277                priv->mode = SW_RESET;
 278        else if (!strcmp(tmp, "debug"))
 279                priv->mode = DEBUG;
 280        else
 281                dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
 282                         tmp);
 283}
 284
 285static int asm9260_wdt_probe(struct platform_device *pdev)
 286{
 287        struct device *dev = &pdev->dev;
 288        struct asm9260_wdt_priv *priv;
 289        struct watchdog_device *wdd;
 290        int ret;
 291        static const char * const mode_name[] = { "hw", "sw", "debug", };
 292
 293        priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
 294        if (!priv)
 295                return -ENOMEM;
 296
 297        priv->dev = dev;
 298
 299        priv->iobase = devm_platform_ioremap_resource(pdev, 0);
 300        if (IS_ERR(priv->iobase))
 301                return PTR_ERR(priv->iobase);
 302
 303        priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
 304        if (IS_ERR(priv->rst))
 305                return PTR_ERR(priv->rst);
 306
 307        ret = asm9260_wdt_get_dt_clks(priv);
 308        if (ret)
 309                return ret;
 310
 311        wdd = &priv->wdd;
 312        wdd->info = &asm9260_wdt_ident;
 313        wdd->ops = &asm9260_wdt_ops;
 314        wdd->min_timeout = 1;
 315        wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
 316        wdd->parent = dev;
 317
 318        watchdog_set_drvdata(wdd, priv);
 319
 320        /*
 321         * If 'timeout-sec' unspecified in devicetree, assume a 30 second
 322         * default, unless the max timeout is less than 30 seconds, then use
 323         * the max instead.
 324         */
 325        wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
 326        watchdog_init_timeout(wdd, 0, dev);
 327
 328        asm9260_wdt_get_dt_mode(priv);
 329
 330        if (priv->mode != HW_RESET)
 331                priv->irq = platform_get_irq(pdev, 0);
 332
 333        if (priv->irq > 0) {
 334                /*
 335                 * Not all supported platforms specify an interrupt for the
 336                 * watchdog, so let's make it optional.
 337                 */
 338                ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
 339                                       pdev->name, priv);
 340                if (ret < 0)
 341                        dev_warn(dev, "failed to request IRQ\n");
 342        }
 343
 344        watchdog_set_restart_priority(wdd, 128);
 345
 346        watchdog_stop_on_reboot(wdd);
 347        watchdog_stop_on_unregister(wdd);
 348        ret = devm_watchdog_register_device(dev, wdd);
 349        if (ret)
 350                return ret;
 351
 352        platform_set_drvdata(pdev, priv);
 353
 354        dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
 355                 wdd->timeout, mode_name[priv->mode]);
 356        return 0;
 357}
 358
 359static const struct of_device_id asm9260_wdt_of_match[] = {
 360        { .compatible = "alphascale,asm9260-wdt"},
 361        {},
 362};
 363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
 364
 365static struct platform_driver asm9260_wdt_driver = {
 366        .driver = {
 367                .name = "asm9260-wdt",
 368                .of_match_table = asm9260_wdt_of_match,
 369        },
 370        .probe = asm9260_wdt_probe,
 371};
 372module_platform_driver(asm9260_wdt_driver);
 373
 374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
 375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
 376MODULE_LICENSE("GPL");
 377