brintos

brintos / linux-shallow public Read only

0
0
Text · 8.1 KiB · 19535f4 Raw
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