root/drivers/watchdog/lpc18xx_wdt.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. lpc18xx_wdt_feed
  2. lpc18xx_wdt_timer_feed
  3. lpc18xx_wdt_stop
  4. __lpc18xx_wdt_set_timeout
  5. lpc18xx_wdt_set_timeout
  6. lpc18xx_wdt_get_timeleft
  7. lpc18xx_wdt_start
  8. lpc18xx_wdt_restart
  9. lpc18xx_clk_disable_unprepare
  10. lpc18xx_wdt_probe
  11. lpc18xx_wdt_remove

   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 
  41 static int heartbeat;
  42 module_param(heartbeat, int, 0);
  43 MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds (default="
  44                  __MODULE_STRING(LPC18XX_WDT_DEF_TIMEOUT) ")");
  45 
  46 static bool nowayout = WATCHDOG_NOWAYOUT;
  47 module_param(nowayout, bool, 0);
  48 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
  49                  __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  50 
  51 struct 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 
  61 static 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 
  78 static 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  */
  94 static 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 
 103 static 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 
 112 static 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 
 123 static 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 
 132 static 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 
 155 static 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 
 183 static const struct watchdog_info lpc18xx_wdt_info = {
 184         .identity       = "NXP LPC18xx Watchdog",
 185         .options        = WDIOF_SETTIMEOUT |
 186                           WDIOF_KEEPALIVEPING |
 187                           WDIOF_MAGICCLOSE,
 188 };
 189 
 190 static 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 
 200 static void lpc18xx_clk_disable_unprepare(void *data)
 201 {
 202         clk_disable_unprepare(data);
 203 }
 204 
 205 static 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 
 290 static 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 
 300 static const struct of_device_id lpc18xx_wdt_match[] = {
 301         { .compatible = "nxp,lpc1850-wwdt" },
 302         {}
 303 };
 304 MODULE_DEVICE_TABLE(of, lpc18xx_wdt_match);
 305 
 306 static 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 };
 314 module_platform_driver(lpc18xx_wdt_driver);
 315 
 316 MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");
 317 MODULE_DESCRIPTION("NXP LPC18xx Watchdog Timer Driver");
 318 MODULE_LICENSE("GPL v2");

/* [<][>][^][v][top][bottom][index][help] */