291 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * NXP LPC18xx Watchdog Timer (WDT)4 *5 * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com>6 *7 * Notes8 * -----9 * The Watchdog consists of a fixed divide-by-4 clock pre-scaler and a 24-bit10 * 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 0x0022#define LPC18XX_WDT_MOD_WDEN BIT(0)23#define LPC18XX_WDT_MOD_WDRESET BIT(1)24 25#define LPC18XX_WDT_TC 0x0426#define LPC18XX_WDT_TC_MIN 0xff27#define LPC18XX_WDT_TC_MAX 0xffffff28 29#define LPC18XX_WDT_FEED 0x0830#define LPC18XX_WDT_FEED_MAGIC1 0xaa31#define LPC18XX_WDT_FEED_MAGIC2 0x5532 33#define LPC18XX_WDT_TV 0x0c34 35/* Clock pre-scaler */36#define LPC18XX_WDT_CLK_DIV 437 38/* Timeout values in seconds */39#define LPC18XX_WDT_DEF_TIMEOUT 30U40 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 feed68 * 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 feeding92 * 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 to147 * enable the Watchdog. A valid feed sequence must be completed after148 * 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 int lpc18xx_wdt_probe(struct platform_device *pdev)201{202 struct lpc18xx_wdt_dev *lpc18xx_wdt;203 struct device *dev = &pdev->dev;204 205 lpc18xx_wdt = devm_kzalloc(dev, sizeof(*lpc18xx_wdt), GFP_KERNEL);206 if (!lpc18xx_wdt)207 return -ENOMEM;208 209 lpc18xx_wdt->base = devm_platform_ioremap_resource(pdev, 0);210 if (IS_ERR(lpc18xx_wdt->base))211 return PTR_ERR(lpc18xx_wdt->base);212 213 lpc18xx_wdt->reg_clk = devm_clk_get_enabled(dev, "reg");214 if (IS_ERR(lpc18xx_wdt->reg_clk)) {215 dev_err(dev, "failed to get the reg clock\n");216 return PTR_ERR(lpc18xx_wdt->reg_clk);217 }218 219 lpc18xx_wdt->wdt_clk = devm_clk_get_enabled(dev, "wdtclk");220 if (IS_ERR(lpc18xx_wdt->wdt_clk)) {221 dev_err(dev, "failed to get the wdt clock\n");222 return PTR_ERR(lpc18xx_wdt->wdt_clk);223 }224 225 /* We use the clock rate to calculate timeouts */226 lpc18xx_wdt->clk_rate = clk_get_rate(lpc18xx_wdt->wdt_clk);227 if (lpc18xx_wdt->clk_rate == 0) {228 dev_err(dev, "failed to get clock rate\n");229 return -EINVAL;230 }231 232 lpc18xx_wdt->wdt_dev.info = &lpc18xx_wdt_info;233 lpc18xx_wdt->wdt_dev.ops = &lpc18xx_wdt_ops;234 235 lpc18xx_wdt->wdt_dev.min_timeout = DIV_ROUND_UP(LPC18XX_WDT_TC_MIN *236 LPC18XX_WDT_CLK_DIV, lpc18xx_wdt->clk_rate);237 238 lpc18xx_wdt->wdt_dev.max_timeout = (LPC18XX_WDT_TC_MAX *239 LPC18XX_WDT_CLK_DIV) / lpc18xx_wdt->clk_rate;240 241 lpc18xx_wdt->wdt_dev.timeout = min(lpc18xx_wdt->wdt_dev.max_timeout,242 LPC18XX_WDT_DEF_TIMEOUT);243 244 spin_lock_init(&lpc18xx_wdt->lock);245 246 lpc18xx_wdt->wdt_dev.parent = dev;247 watchdog_set_drvdata(&lpc18xx_wdt->wdt_dev, lpc18xx_wdt);248 249 watchdog_init_timeout(&lpc18xx_wdt->wdt_dev, heartbeat, dev);250 251 __lpc18xx_wdt_set_timeout(lpc18xx_wdt);252 253 timer_setup(&lpc18xx_wdt->timer, lpc18xx_wdt_timer_feed, 0);254 255 watchdog_set_nowayout(&lpc18xx_wdt->wdt_dev, nowayout);256 watchdog_set_restart_priority(&lpc18xx_wdt->wdt_dev, 128);257 258 platform_set_drvdata(pdev, lpc18xx_wdt);259 260 watchdog_stop_on_reboot(&lpc18xx_wdt->wdt_dev);261 return devm_watchdog_register_device(dev, &lpc18xx_wdt->wdt_dev);262}263 264static void lpc18xx_wdt_remove(struct platform_device *pdev)265{266 struct lpc18xx_wdt_dev *lpc18xx_wdt = platform_get_drvdata(pdev);267 268 dev_warn(&pdev->dev, "I quit now, hardware will probably reboot!\n");269 del_timer_sync(&lpc18xx_wdt->timer);270}271 272static const struct of_device_id lpc18xx_wdt_match[] = {273 { .compatible = "nxp,lpc1850-wwdt" },274 {}275};276MODULE_DEVICE_TABLE(of, lpc18xx_wdt_match);277 278static struct platform_driver lpc18xx_wdt_driver = {279 .driver = {280 .name = "lpc18xx-wdt",281 .of_match_table = lpc18xx_wdt_match,282 },283 .probe = lpc18xx_wdt_probe,284 .remove_new = lpc18xx_wdt_remove,285};286module_platform_driver(lpc18xx_wdt_driver);287 288MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");289MODULE_DESCRIPTION("NXP LPC18xx Watchdog Timer Driver");290MODULE_LICENSE("GPL v2");291