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