Linux Audio

Check our new training course

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