linux/drivers/watchdog/lpc18xx_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * NXP LPC18xx Watchdog Timer (WDT)
   4 *
   5 * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com>
   6 *
   7 * Notes
   8 * -----
   9 * The Watchdog consists of a fixed divide-by-4 clock pre-scaler and a 24-bit
  10 * counter which decrements on every clock cycle.
  11 */
  12
  13#include <linux/clk.h>
  14#include <linux/io.h>
  15#include <linux/module.h>
  16#include <linux/of.h>
  17#include <linux/platform_device.h>
  18#include <linux/watchdog.h>
  19
  20/* Registers */
  21#define LPC18XX_WDT_MOD                 0x00
  22#define LPC18XX_WDT_MOD_WDEN            BIT(0)
  23#define LPC18XX_WDT_MOD_WDRESET         BIT(1)
  24
  25#define LPC18XX_WDT_TC                  0x04
  26#define LPC18XX_WDT_TC_MIN              0xff
  27#define LPC18XX_WDT_TC_MAX              0xffffff
  28
  29#define LPC18XX_WDT_FEED                0x08
  30#define LPC18XX_WDT_FEED_MAGIC1         0xaa
  31#define LPC18XX_WDT_FEED_MAGIC2         0x55
  32
  33#define LPC18XX_WDT_TV                  0x0c
  34
  35/* Clock pre-scaler */
  36#define LPC18XX_WDT_CLK_DIV             4
  37
  38/* Timeout values in seconds */
  39#define LPC18XX_WDT_DEF_TIMEOUT         30U
  40
  41static int heartbeat;
  42module_param(heartbeat, int, 0);
  43MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds (default="
  44                 __MODULE_STRING(LPC18XX_WDT_DEF_TIMEOUT) ")");
  45
  46static bool nowayout = WATCHDOG_NOWAYOUT;
  47module_param(nowayout, bool, 0);
  48MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  49                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  50
  51struct lpc18xx_wdt_dev {
  52        struct watchdog_device  wdt_dev;
  53        struct clk              *reg_clk;
  54        struct clk              *wdt_clk;
  55        unsigned long           clk_rate;
  56        void __iomem            *base;
  57        struct timer_list       timer;
  58        spinlock_t              lock;
  59};
  60
  61static int lpc18xx_wdt_feed(struct watchdog_device *wdt_dev)
  62{
  63        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
  64        unsigned long flags;
  65
  66        /*
  67         * An abort condition will occur if an interrupt happens during the feed
  68         * sequence.
  69         */
  70        spin_lock_irqsave(&lpc18xx_wdt->lock, flags);
  71        writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
  72        writel(LPC18XX_WDT_FEED_MAGIC2, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
  73        spin_unlock_irqrestore(&lpc18xx_wdt->lock, flags);
  74
  75        return 0;
  76}
  77
  78static void lpc18xx_wdt_timer_feed(struct timer_list *t)
  79{
  80        struct lpc18xx_wdt_dev *lpc18xx_wdt = from_timer(lpc18xx_wdt, t, timer);
  81        struct watchdog_device *wdt_dev = &lpc18xx_wdt->wdt_dev;
  82
  83        lpc18xx_wdt_feed(wdt_dev);
  84
  85        /* Use safe value (1/2 of real timeout) */
  86        mod_timer(&lpc18xx_wdt->timer, jiffies +
  87                  msecs_to_jiffies((wdt_dev->timeout * MSEC_PER_SEC) / 2));
  88}
  89
  90/*
  91 * Since LPC18xx Watchdog cannot be disabled in hardware, we must keep feeding
  92 * it with a timer until userspace watchdog software takes over.
  93 */
  94static int lpc18xx_wdt_stop(struct watchdog_device *wdt_dev)
  95{
  96        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
  97
  98        lpc18xx_wdt_timer_feed(&lpc18xx_wdt->timer);
  99
 100        return 0;
 101}
 102
 103static void __lpc18xx_wdt_set_timeout(struct lpc18xx_wdt_dev *lpc18xx_wdt)
 104{
 105        unsigned int val;
 106
 107        val = DIV_ROUND_UP(lpc18xx_wdt->wdt_dev.timeout * lpc18xx_wdt->clk_rate,
 108                           LPC18XX_WDT_CLK_DIV);
 109        writel(val, lpc18xx_wdt->base + LPC18XX_WDT_TC);
 110}
 111
 112static int lpc18xx_wdt_set_timeout(struct watchdog_device *wdt_dev,
 113                                   unsigned int new_timeout)
 114{
 115        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
 116
 117        lpc18xx_wdt->wdt_dev.timeout = new_timeout;
 118        __lpc18xx_wdt_set_timeout(lpc18xx_wdt);
 119
 120        return 0;
 121}
 122
 123static unsigned int lpc18xx_wdt_get_timeleft(struct watchdog_device *wdt_dev)
 124{
 125        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
 126        unsigned int val;
 127
 128        val = readl(lpc18xx_wdt->base + LPC18XX_WDT_TV);
 129        return (val * LPC18XX_WDT_CLK_DIV) / lpc18xx_wdt->clk_rate;
 130}
 131
 132static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev)
 133{
 134        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
 135        unsigned int val;
 136
 137        if (timer_pending(&lpc18xx_wdt->timer))
 138                del_timer(&lpc18xx_wdt->timer);
 139
 140        val = readl(lpc18xx_wdt->base + LPC18XX_WDT_MOD);
 141        val |= LPC18XX_WDT_MOD_WDEN;
 142        val |= LPC18XX_WDT_MOD_WDRESET;
 143        writel(val, lpc18xx_wdt->base + LPC18XX_WDT_MOD);
 144
 145        /*
 146         * Setting the WDEN bit in the WDMOD register is not sufficient to
 147         * enable the Watchdog. A valid feed sequence must be completed after
 148         * setting WDEN before the Watchdog is capable of generating a reset.
 149         */
 150        lpc18xx_wdt_feed(wdt_dev);
 151
 152        return 0;
 153}
 154
 155static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev,
 156                               unsigned long action, void *data)
 157{
 158        struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
 159        unsigned long flags;
 160        int val;
 161
 162        /*
 163         * Incorrect feed sequence causes immediate watchdog reset if enabled.
 164         */
 165        spin_lock_irqsave(&lpc18xx_wdt->lock, flags);
 166
 167        val = readl(lpc18xx_wdt->base + LPC18XX_WDT_MOD);
 168        val |= LPC18XX_WDT_MOD_WDEN;
 169        val |= LPC18XX_WDT_MOD_WDRESET;
 170        writel(val, lpc18xx_wdt->base + LPC18XX_WDT_MOD);
 171
 172        writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
 173        writel(LPC18XX_WDT_FEED_MAGIC2, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
 174
 175        writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
 176        writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
 177
 178        spin_unlock_irqrestore(&lpc18xx_wdt->lock, flags);
 179
 180        return 0;
 181}
 182
 183static const struct watchdog_info lpc18xx_wdt_info = {
 184        .identity       = "NXP LPC18xx Watchdog",
 185        .options        = WDIOF_SETTIMEOUT |
 186                          WDIOF_KEEPALIVEPING |
 187                          WDIOF_MAGICCLOSE,
 188};
 189
 190static const struct watchdog_ops lpc18xx_wdt_ops = {
 191        .owner          = THIS_MODULE,
 192        .start          = lpc18xx_wdt_start,
 193        .stop           = lpc18xx_wdt_stop,
 194        .ping           = lpc18xx_wdt_feed,
 195        .set_timeout    = lpc18xx_wdt_set_timeout,
 196        .get_timeleft   = lpc18xx_wdt_get_timeleft,
 197        .restart        = lpc18xx_wdt_restart,
 198};
 199
 200static void lpc18xx_clk_disable_unprepare(void *data)
 201{
 202        clk_disable_unprepare(data);
 203}
 204
 205static int lpc18xx_wdt_probe(struct platform_device *pdev)
 206{
 207        struct lpc18xx_wdt_dev *lpc18xx_wdt;
 208        struct device *dev = &pdev->dev;
 209        int ret;
 210
 211        lpc18xx_wdt = devm_kzalloc(dev, sizeof(*lpc18xx_wdt), GFP_KERNEL);
 212        if (!lpc18xx_wdt)
 213                return -ENOMEM;
 214
 215        lpc18xx_wdt->base = devm_platform_ioremap_resource(pdev, 0);
 216        if (IS_ERR(lpc18xx_wdt->base))
 217                return PTR_ERR(lpc18xx_wdt->base);
 218
 219        lpc18xx_wdt->reg_clk = devm_clk_get(dev, "reg");
 220        if (IS_ERR(lpc18xx_wdt->reg_clk)) {
 221                dev_err(dev, "failed to get the reg clock\n");
 222                return PTR_ERR(lpc18xx_wdt->reg_clk);
 223        }
 224
 225        lpc18xx_wdt->wdt_clk = devm_clk_get(dev, "wdtclk");
 226        if (IS_ERR(lpc18xx_wdt->wdt_clk)) {
 227                dev_err(dev, "failed to get the wdt clock\n");
 228                return PTR_ERR(lpc18xx_wdt->wdt_clk);
 229        }
 230
 231        ret = clk_prepare_enable(lpc18xx_wdt->reg_clk);
 232        if (ret) {
 233                dev_err(dev, "could not prepare or enable sys clock\n");
 234                return ret;
 235        }
 236        ret = devm_add_action_or_reset(dev, lpc18xx_clk_disable_unprepare,
 237                                       lpc18xx_wdt->reg_clk);
 238        if (ret)
 239                return ret;
 240
 241        ret = clk_prepare_enable(lpc18xx_wdt->wdt_clk);
 242        if (ret) {
 243                dev_err(dev, "could not prepare or enable wdt clock\n");
 244                return ret;
 245        }
 246        ret = devm_add_action_or_reset(dev, lpc18xx_clk_disable_unprepare,
 247                                       lpc18xx_wdt->wdt_clk);
 248        if (ret)
 249                return ret;
 250
 251        /* We use the clock rate to calculate timeouts */
 252        lpc18xx_wdt->clk_rate = clk_get_rate(lpc18xx_wdt->wdt_clk);
 253        if (lpc18xx_wdt->clk_rate == 0) {
 254                dev_err(dev, "failed to get clock rate\n");
 255                return -EINVAL;
 256        }
 257
 258        lpc18xx_wdt->wdt_dev.info = &lpc18xx_wdt_info;
 259        lpc18xx_wdt->wdt_dev.ops = &lpc18xx_wdt_ops;
 260
 261        lpc18xx_wdt->wdt_dev.min_timeout = DIV_ROUND_UP(LPC18XX_WDT_TC_MIN *
 262                                LPC18XX_WDT_CLK_DIV, lpc18xx_wdt->clk_rate);
 263
 264        lpc18xx_wdt->wdt_dev.max_timeout = (LPC18XX_WDT_TC_MAX *
 265                                LPC18XX_WDT_CLK_DIV) / lpc18xx_wdt->clk_rate;
 266
 267        lpc18xx_wdt->wdt_dev.timeout = min(lpc18xx_wdt->wdt_dev.max_timeout,
 268                                           LPC18XX_WDT_DEF_TIMEOUT);
 269
 270        spin_lock_init(&lpc18xx_wdt->lock);
 271
 272        lpc18xx_wdt->wdt_dev.parent = dev;
 273        watchdog_set_drvdata(&lpc18xx_wdt->wdt_dev, lpc18xx_wdt);
 274
 275        watchdog_init_timeout(&lpc18xx_wdt->wdt_dev, heartbeat, dev);
 276
 277        __lpc18xx_wdt_set_timeout(lpc18xx_wdt);
 278
 279        timer_setup(&lpc18xx_wdt->timer, lpc18xx_wdt_timer_feed, 0);
 280
 281        watchdog_set_nowayout(&lpc18xx_wdt->wdt_dev, nowayout);
 282        watchdog_set_restart_priority(&lpc18xx_wdt->wdt_dev, 128);
 283
 284        platform_set_drvdata(pdev, lpc18xx_wdt);
 285
 286        watchdog_stop_on_reboot(&lpc18xx_wdt->wdt_dev);
 287        return devm_watchdog_register_device(dev, &lpc18xx_wdt->wdt_dev);
 288}
 289
 290static int lpc18xx_wdt_remove(struct platform_device *pdev)
 291{
 292        struct lpc18xx_wdt_dev *lpc18xx_wdt = platform_get_drvdata(pdev);
 293
 294        dev_warn(&pdev->dev, "I quit now, hardware will probably reboot!\n");
 295        del_timer(&lpc18xx_wdt->timer);
 296
 297        return 0;
 298}
 299
 300static const struct of_device_id lpc18xx_wdt_match[] = {
 301        { .compatible = "nxp,lpc1850-wwdt" },
 302        {}
 303};
 304MODULE_DEVICE_TABLE(of, lpc18xx_wdt_match);
 305
 306static struct platform_driver lpc18xx_wdt_driver = {
 307        .driver = {
 308                .name = "lpc18xx-wdt",
 309                .of_match_table = lpc18xx_wdt_match,
 310        },
 311        .probe = lpc18xx_wdt_probe,
 312        .remove = lpc18xx_wdt_remove,
 313};
 314module_platform_driver(lpc18xx_wdt_driver);
 315
 316MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");
 317MODULE_DESCRIPTION("NXP LPC18xx Watchdog Timer Driver");
 318MODULE_LICENSE("GPL v2");
 319