Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 * Watchdog driver for Alphascale ASM9260.
  3 *
  4 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
  5 *
  6 * Licensed under GPLv2 or later.
  7 */
  8
  9#include <linux/bitops.h>
 10#include <linux/clk.h>
 11#include <linux/delay.h>
 12#include <linux/interrupt.h>
 13#include <linux/io.h>
 14#include <linux/module.h>
 15#include <linux/of.h>
 16#include <linux/platform_device.h>
 17#include <linux/reboot.h>
 18#include <linux/reset.h>
 19#include <linux/watchdog.h>
 20
 21#define CLOCK_FREQ	1000000
 22
 23/* Watchdog Mode register */
 24#define HW_WDMOD			0x00
 25/* Wake interrupt. Set by HW, can't be cleared. */
 26#define BM_MOD_WDINT			BIT(3)
 27/* This bit set if timeout reached. Cleared by SW. */
 28#define BM_MOD_WDTOF			BIT(2)
 29/* HW Reset on timeout */
 30#define BM_MOD_WDRESET			BIT(1)
 31/* WD enable */
 32#define BM_MOD_WDEN			BIT(0)
 33
 34/*
 35 * Watchdog Timer Constant register
 36 * Minimal value is 0xff, the meaning of this value
 37 * depends on used clock: T = WDCLK * (0xff + 1) * 4
 38 */
 39#define HW_WDTC				0x04
 40#define BM_WDTC_MAX(freq)		(0x7fffffff / (freq))
 41
 42/* Watchdog Feed register */
 43#define HW_WDFEED			0x08
 44
 45/* Watchdog Timer Value register */
 46#define HW_WDTV				0x0c
 47
 48#define ASM9260_WDT_DEFAULT_TIMEOUT	30
 49
 50enum asm9260_wdt_mode {
 51	HW_RESET,
 52	SW_RESET,
 53	DEBUG,
 54};
 55
 56struct asm9260_wdt_priv {
 57	struct device		*dev;
 58	struct watchdog_device	wdd;
 59	struct clk		*clk;
 60	struct clk		*clk_ahb;
 61	struct reset_control	*rst;
 62	struct notifier_block	restart_handler;
 63
 64	void __iomem		*iobase;
 65	int			irq;
 66	unsigned long		wdt_freq;
 67	enum asm9260_wdt_mode	mode;
 68};
 69
 70static int asm9260_wdt_feed(struct watchdog_device *wdd)
 71{
 72	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 73
 74	iowrite32(0xaa, priv->iobase + HW_WDFEED);
 75	iowrite32(0x55, priv->iobase + HW_WDFEED);
 76
 77	return 0;
 78}
 79
 80static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
 81{
 82	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 83	u32 counter;
 84
 85	counter = ioread32(priv->iobase + HW_WDTV);
 86
 87	return DIV_ROUND_CLOSEST(counter, priv->wdt_freq);
 88}
 89
 90static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
 91{
 92	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
 93	u32 counter;
 94
 95	counter = wdd->timeout * priv->wdt_freq;
 96
 97	iowrite32(counter, priv->iobase + HW_WDTC);
 98
 99	return 0;
100}
101
102static int asm9260_wdt_enable(struct watchdog_device *wdd)
103{
104	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
105	u32 mode = 0;
106
107	if (priv->mode == HW_RESET)
108		mode = BM_MOD_WDRESET;
109
110	iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
111
112	asm9260_wdt_updatetimeout(wdd);
113
114	asm9260_wdt_feed(wdd);
115
116	return 0;
117}
118
119static int asm9260_wdt_disable(struct watchdog_device *wdd)
120{
121	struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
122
123	/* The only way to disable WD is to reset it. */
124	reset_control_assert(priv->rst);
125	reset_control_deassert(priv->rst);
126
127	return 0;
128}
129
130static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
131{
132	wdd->timeout = to;
133	asm9260_wdt_updatetimeout(wdd);
134
135	return 0;
136}
137
138static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
139{
140	/* init WD if it was not started */
141
142	iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
143
144	iowrite32(0xff, priv->iobase + HW_WDTC);
145	/* first pass correct sequence */
146	asm9260_wdt_feed(&priv->wdd);
147	/*
148	 * Then write wrong pattern to the feed to trigger reset
149	 * ASAP.
150	 */
151	iowrite32(0xff, priv->iobase + HW_WDFEED);
152
153	mdelay(1000);
154}
155
156static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
157{
158	struct asm9260_wdt_priv *priv = devid;
159	u32 stat;
160
161	stat = ioread32(priv->iobase + HW_WDMOD);
162	if (!(stat & BM_MOD_WDINT))
163		return IRQ_NONE;
164
165	if (priv->mode == DEBUG) {
166		dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
167	} else {
168		dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
169		asm9260_wdt_sys_reset(priv);
170	}
171
172	return IRQ_HANDLED;
173}
174
175static int asm9260_restart_handler(struct notifier_block *this,
176				   unsigned long mode, void *cmd)
177{
178	struct asm9260_wdt_priv *priv =
179		container_of(this, struct asm9260_wdt_priv, restart_handler);
180
181	asm9260_wdt_sys_reset(priv);
182
183	return NOTIFY_DONE;
184}
185
186static const struct watchdog_info asm9260_wdt_ident = {
187	.options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
188				| WDIOF_MAGICCLOSE,
189	.identity         =	"Alphascale asm9260 Watchdog",
190};
191
192static struct watchdog_ops asm9260_wdt_ops = {
193	.owner		= THIS_MODULE,
194	.start		= asm9260_wdt_enable,
195	.stop		= asm9260_wdt_disable,
196	.get_timeleft	= asm9260_wdt_gettimeleft,
197	.ping		= asm9260_wdt_feed,
198	.set_timeout	= asm9260_wdt_settimeout,
199};
200
201static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
202{
203	int err;
204	unsigned long clk;
205
206	priv->clk = devm_clk_get(priv->dev, "mod");
207	if (IS_ERR(priv->clk)) {
208		dev_err(priv->dev, "Failed to get \"mod\" clk\n");
209		return PTR_ERR(priv->clk);
210	}
211
212	/* configure AHB clock */
213	priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
214	if (IS_ERR(priv->clk_ahb)) {
215		dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
216		return PTR_ERR(priv->clk_ahb);
217	}
218
219	err = clk_prepare_enable(priv->clk_ahb);
220	if (err) {
221		dev_err(priv->dev, "Failed to enable ahb_clk!\n");
222		return err;
223	}
224
225	err = clk_set_rate(priv->clk, CLOCK_FREQ);
226	if (err) {
227		clk_disable_unprepare(priv->clk_ahb);
228		dev_err(priv->dev, "Failed to set rate!\n");
229		return err;
230	}
231
232	err = clk_prepare_enable(priv->clk);
233	if (err) {
234		clk_disable_unprepare(priv->clk_ahb);
235		dev_err(priv->dev, "Failed to enable clk!\n");
236		return err;
237	}
238
239	/* wdt has internal divider */
240	clk = clk_get_rate(priv->clk);
241	if (!clk) {
242		clk_disable_unprepare(priv->clk);
243		clk_disable_unprepare(priv->clk_ahb);
244		dev_err(priv->dev, "Failed, clk is 0!\n");
245		return -EINVAL;
246	}
247
248	priv->wdt_freq = clk / 2;
249
250	return 0;
251}
252
253static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
254{
255	const char *tmp;
256	int ret;
257
258	/* default mode */
259	priv->mode = HW_RESET;
260
261	ret = of_property_read_string(priv->dev->of_node,
262				      "alphascale,mode", &tmp);
263	if (ret < 0)
264		return;
265
266	if (!strcmp(tmp, "hw"))
267		priv->mode = HW_RESET;
268	else if (!strcmp(tmp, "sw"))
269		priv->mode = SW_RESET;
270	else if (!strcmp(tmp, "debug"))
271		priv->mode = DEBUG;
272	else
273		dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
274			 tmp);
275}
276
277static int asm9260_wdt_probe(struct platform_device *pdev)
278{
279	struct asm9260_wdt_priv *priv;
280	struct watchdog_device *wdd;
281	struct resource *res;
282	int ret;
283	const char * const mode_name[] = { "hw", "sw", "debug", };
284
285	priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv),
286			    GFP_KERNEL);
287	if (!priv)
288		return -ENOMEM;
289
290	priv->dev = &pdev->dev;
291
292	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
293	priv->iobase = devm_ioremap_resource(&pdev->dev, res);
294	if (IS_ERR(priv->iobase))
295		return PTR_ERR(priv->iobase);
296
297	ret = asm9260_wdt_get_dt_clks(priv);
298	if (ret)
299		return ret;
300
301	priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst");
302	if (IS_ERR(priv->rst))
303		return PTR_ERR(priv->rst);
304
305	wdd = &priv->wdd;
306	wdd->info = &asm9260_wdt_ident;
307	wdd->ops = &asm9260_wdt_ops;
308	wdd->min_timeout = 1;
309	wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
310	wdd->parent = &pdev->dev;
311
312	watchdog_set_drvdata(wdd, priv);
313
314	/*
315	 * If 'timeout-sec' unspecified in devicetree, assume a 30 second
316	 * default, unless the max timeout is less than 30 seconds, then use
317	 * the max instead.
318	 */
319	wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
320	watchdog_init_timeout(wdd, 0, &pdev->dev);
321
322	asm9260_wdt_get_dt_mode(priv);
323
324	if (priv->mode != HW_RESET)
325		priv->irq = platform_get_irq(pdev, 0);
326
327	if (priv->irq > 0) {
328		/*
329		 * Not all supported platforms specify an interrupt for the
330		 * watchdog, so let's make it optional.
331		 */
332		ret = devm_request_irq(&pdev->dev, priv->irq,
333				       asm9260_wdt_irq, 0, pdev->name, priv);
334		if (ret < 0)
335			dev_warn(&pdev->dev, "failed to request IRQ\n");
336	}
337
338	ret = watchdog_register_device(wdd);
339	if (ret)
340		goto clk_off;
341
342	platform_set_drvdata(pdev, priv);
343
344	priv->restart_handler.notifier_call = asm9260_restart_handler;
345	priv->restart_handler.priority = 128;
346	ret = register_restart_handler(&priv->restart_handler);
347	if (ret)
348		dev_warn(&pdev->dev, "cannot register restart handler\n");
349
350	dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
351		 wdd->timeout, mode_name[priv->mode]);
352	return 0;
353
354clk_off:
355	clk_disable_unprepare(priv->clk);
356	clk_disable_unprepare(priv->clk_ahb);
357	return ret;
358}
359
360static void asm9260_wdt_shutdown(struct platform_device *pdev)
361{
362	struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
363
364	asm9260_wdt_disable(&priv->wdd);
365}
366
367static int asm9260_wdt_remove(struct platform_device *pdev)
368{
369	struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
370
371	asm9260_wdt_disable(&priv->wdd);
372
373	unregister_restart_handler(&priv->restart_handler);
374
375	watchdog_unregister_device(&priv->wdd);
376
377	clk_disable_unprepare(priv->clk);
378	clk_disable_unprepare(priv->clk_ahb);
379
380	return 0;
381}
382
383static const struct of_device_id asm9260_wdt_of_match[] = {
384	{ .compatible = "alphascale,asm9260-wdt"},
385	{},
386};
387MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
388
389static struct platform_driver asm9260_wdt_driver = {
390	.driver = {
391		.name = "asm9260-wdt",
392		.owner = THIS_MODULE,
393		.of_match_table	= asm9260_wdt_of_match,
394	},
395	.probe = asm9260_wdt_probe,
396	.remove = asm9260_wdt_remove,
397	.shutdown = asm9260_wdt_shutdown,
398};
399module_platform_driver(asm9260_wdt_driver);
400
401MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
402MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
403MODULE_LICENSE("GPL");