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