Release 4.12 drivers/watchdog/at91sam9_wdt.c
  
  
  
/*
 * Watchdog driver for Atmel AT91SAM9x processors.
 *
 * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */
/*
 * The Watchdog Timer Mode Register can be only written to once. If the
 * timeout need to be set from Linux, be sure that the bootstrap or the
 * bootloader doesn't write to this register.
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/clk.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/bitops.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include "at91sam9_wdt.h"
#define DRV_NAME "AT91SAM9 Watchdog"
#define wdt_read(wdt, field) \
	readl_relaxed((wdt)->base + (field))
#define wdt_write(wtd, field, val) \
	writel_relaxed((val), (wdt)->base + (field))
/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
 * use this to convert a watchdog
 * value from/to milliseconds.
 */
#define ticks_to_hz_rounddown(t)	((((t) + 1) * HZ) >> 8)
#define ticks_to_hz_roundup(t)		(((((t) + 1) * HZ) + 255) >> 8)
#define ticks_to_secs(t)		(((t) + 1) >> 8)
#define secs_to_ticks(s)		((s) ? (((s) << 8) - 1) : 0)
#define WDT_MR_RESET	0x3FFF2FFF
/* Watchdog max counter value in ticks */
#define WDT_COUNTER_MAX_TICKS	0xFFF
/* Watchdog max delta/value in secs */
#define WDT_COUNTER_MAX_SECS	ticks_to_secs(WDT_COUNTER_MAX_TICKS)
/* Hardware timeout in seconds */
#define WDT_HW_TIMEOUT 2
/* Timer heartbeat (500ms) */
#define WDT_TIMEOUT	(HZ/2)
/* User land timeout */
#define WDT_HEARTBEAT 15
static int heartbeat;
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
	"(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
	"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#define to_wdt(wdd) container_of(wdd, struct at91wdt, wdd)
struct at91wdt {
	
struct watchdog_device wdd;
	
void __iomem *base;
	
unsigned long next_heartbeat;	/* the next_heartbeat for the timer */
	
struct timer_list timer;	/* The timer that pings the watchdog */
	
u32 mr;
	
u32 mr_mask;
	
unsigned long heartbeat;	/* WDT heartbeat in jiffies */
	
bool nowayout;
	
unsigned int irq;
	
struct clk *sclk;
};
/* ......................................................................... */
static irqreturn_t wdt_interrupt(int irq, void *dev_id)
{
	struct at91wdt *wdt = (struct at91wdt *)dev_id;
	if (wdt_read(wdt, AT91_WDT_SR)) {
		pr_crit("at91sam9 WDT software reset\n");
		emergency_restart();
		pr_crit("Reboot didn't ?????\n");
	}
	return IRQ_HANDLED;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 52 | 100.00% | 1 | 100.00% | 
| Total | 52 | 100.00% | 1 | 100.00% | 
/*
 * Reload the watchdog timer.  (ie, pat the watchdog)
 */
static inline void at91_wdt_reset(struct at91wdt *wdt)
{
	wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Renaud Cerrato | 16 | 69.57% | 1 | 33.33% | 
| Boris Brezillon | 6 | 26.09% | 1 | 33.33% | 
| Jean-Christophe Plagniol-Villard | 1 | 4.35% | 1 | 33.33% | 
| Total | 23 | 100.00% | 3 | 100.00% | 
/*
 * Timer tick
 */
static void at91_ping(unsigned long data)
{
	struct at91wdt *wdt = (struct at91wdt *)data;
	if (time_before(jiffies, wdt->next_heartbeat) ||
	    !watchdog_active(&wdt->wdd)) {
		at91_wdt_reset(wdt);
		mod_timer(&wdt->timer, jiffies + wdt->heartbeat);
	} else {
		pr_crit("I will reset your machine !\n");
	}
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Renaud Cerrato | 37 | 52.11% | 1 | 25.00% | 
| Boris Brezillon | 27 | 38.03% | 1 | 25.00% | 
| Wenyou Yang | 4 | 5.63% | 1 | 25.00% | 
| Joe Perches | 3 | 4.23% | 1 | 25.00% | 
| Total | 71 | 100.00% | 4 | 100.00% | 
static int at91_wdt_start(struct watchdog_device *wdd)
{
	struct at91wdt *wdt = to_wdt(wdd);
	/* calculate when the next userspace timeout will be */
	wdt->next_heartbeat = jiffies + wdd->timeout * HZ;
	return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Renaud Cerrato | 14 | 37.84% | 1 | 33.33% | 
| Boris Brezillon | 13 | 35.14% | 1 | 33.33% | 
| Wenyou Yang | 10 | 27.03% | 1 | 33.33% | 
| Total | 37 | 100.00% | 3 | 100.00% | 
static int at91_wdt_stop(struct watchdog_device *wdd)
{
	/* The watchdog timer hardware can not be stopped... */
	return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Wenyou Yang | 10 | 66.67% | 1 | 50.00% | 
| Renaud Cerrato | 5 | 33.33% | 1 | 50.00% | 
| Total | 15 | 100.00% | 2 | 100.00% | 
static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
{
	wdd->timeout = new_timeout;
	return at91_wdt_start(wdd);
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Wenyou Yang | 14 | 51.85% | 1 | 33.33% | 
| Renaud Cerrato | 9 | 33.33% | 1 | 33.33% | 
| Boris Brezillon | 4 | 14.81% | 1 | 33.33% | 
| Total | 27 | 100.00% | 3 | 100.00% | 
static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
{
	u32 tmp;
	u32 delta;
	u32 value;
	int err;
	u32 mask = wdt->mr_mask;
	unsigned long min_heartbeat = 1;
	unsigned long max_heartbeat;
	struct device *dev = &pdev->dev;
	tmp = wdt_read(wdt, AT91_WDT_MR);
	if ((tmp & mask) != (wdt->mr & mask)) {
		if (tmp == WDT_MR_RESET) {
			wdt_write(wdt, AT91_WDT_MR, wdt->mr);
			tmp = wdt_read(wdt, AT91_WDT_MR);
		}
	}
	if (tmp & AT91_WDT_WDDIS) {
		if (wdt->mr & AT91_WDT_WDDIS)
			return 0;
		dev_err(dev, "watchdog is disabled\n");
		return -EINVAL;
	}
	value = tmp & AT91_WDT_WDV;
	delta = (tmp & AT91_WDT_WDD) >> 16;
	if (delta < value)
		min_heartbeat = ticks_to_hz_roundup(value - delta);
	max_heartbeat = ticks_to_hz_rounddown(value);
	if (!max_heartbeat) {
		dev_err(dev,
			"heartbeat is too small for the system to handle it correctly\n");
		return -EINVAL;
	}
	/*
         * Try to reset the watchdog counter 4 or 2 times more often than
         * actually requested, to avoid spurious watchdog reset.
         * If this is not possible because of the min_heartbeat value, reset
         * it at the min_heartbeat period.
         */
	if ((max_heartbeat / 4) >= min_heartbeat)
		wdt->heartbeat = max_heartbeat / 4;
	else if ((max_heartbeat / 2) >= min_heartbeat)
		wdt->heartbeat = max_heartbeat / 2;
	else
		wdt->heartbeat = min_heartbeat;
	if (max_heartbeat < min_heartbeat + 4)
		dev_warn(dev,
			 "min heartbeat and max heartbeat might be too close for the system to handle it correctly\n");
	if ((tmp & AT91_WDT_WDFIEN) && wdt->irq) {
		err = request_irq(wdt->irq, wdt_interrupt,
				  IRQF_SHARED | IRQF_IRQPOLL |
				  IRQF_NO_SUSPEND,
				  pdev->name, wdt);
		if (err)
			return err;
	}
	if ((tmp & wdt->mr_mask) != (wdt->mr & wdt->mr_mask))
		dev_warn(dev,
			 "watchdog already configured differently (mr = %x expecting %x)\n",
			 tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask);
	setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt);
	/*
         * Use min_heartbeat the first time to avoid spurious watchdog reset:
         * we don't know for how long the watchdog counter is running, and
         *  - resetting it right now might trigger a watchdog fault reset
         *  - waiting for heartbeat time might lead to a watchdog timeout
         *    reset
         */
	mod_timer(&wdt->timer, jiffies + min_heartbeat);
	/* Try to set timeout from device tree first */
	if (watchdog_init_timeout(&wdt->wdd, 0, dev))
		watchdog_init_timeout(&wdt->wdd, heartbeat, dev);
	watchdog_set_nowayout(&wdt->wdd, wdt->nowayout);
	err = watchdog_register_device(&wdt->wdd);
	if (err)
		goto out_stop_timer;
	wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ;
	return 0;
out_stop_timer:
	del_timer(&wdt->timer);
	return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 415 | 91.01% | 4 | 66.67% | 
| Renaud Cerrato | 40 | 8.77% | 1 | 16.67% | 
| Jean-Christophe Plagniol-Villard | 1 | 0.22% | 1 | 16.67% | 
| Total | 456 | 100.00% | 6 | 100.00% | 
/* ......................................................................... */
static const struct watchdog_info at91_wdt_info = {
	.identity	= DRV_NAME,
	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
						WDIOF_MAGICCLOSE,
};
static const struct watchdog_ops at91_wdt_ops = {
	.owner =	THIS_MODULE,
	.start =	at91_wdt_start,
	.stop =		at91_wdt_stop,
	.set_timeout =	at91_wdt_set_timeout,
};
#if defined(CONFIG_OF)
static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
{
	u32 min = 0;
	u32 max = WDT_COUNTER_MAX_SECS;
	const char *tmp;
	/* Get the interrupts property */
	wdt->irq = irq_of_parse_and_map(np, 0);
	if (!wdt->irq)
		dev_warn(wdt->wdd.parent, "failed to get IRQ from DT\n");
	if (!of_property_read_u32_index(np, "atmel,max-heartbeat-sec", 0,
					&max)) {
		if (!max || max > WDT_COUNTER_MAX_SECS)
			max = WDT_COUNTER_MAX_SECS;
		if (!of_property_read_u32_index(np, "atmel,min-heartbeat-sec",
						0, &min)) {
			if (min >= max)
				min = max - 1;
		}
	}
	min = secs_to_ticks(min);
	max = secs_to_ticks(max);
	wdt->mr_mask = 0x3FFFFFFF;
	wdt->mr = 0;
	if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) &&
	    !strcmp(tmp, "software")) {
		wdt->mr |= AT91_WDT_WDFIEN;
		wdt->mr_mask &= ~AT91_WDT_WDRPROC;
	} else {
		wdt->mr |= AT91_WDT_WDRSTEN;
	}
	if (!of_property_read_string(np, "atmel,reset-type", &tmp) &&
	    !strcmp(tmp, "proc"))
		wdt->mr |= AT91_WDT_WDRPROC;
	if (of_property_read_bool(np, "atmel,disable")) {
		wdt->mr |= AT91_WDT_WDDIS;
		wdt->mr_mask &= AT91_WDT_WDDIS;
	}
	if (of_property_read_bool(np, "atmel,idle-halt"))
		wdt->mr |= AT91_WDT_WDIDLEHLT;
	if (of_property_read_bool(np, "atmel,dbg-halt"))
		wdt->mr |= AT91_WDT_WDDBGHLT;
	wdt->mr |= max | ((max - min) << 16);
	return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 290 | 100.00% | 1 | 100.00% | 
| Total | 290 | 100.00% | 1 | 100.00% | 
#else
static inline int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
{
	return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 17 | 85.00% | 1 | 50.00% | 
| Renaud Cerrato | 3 | 15.00% | 1 | 50.00% | 
| Total | 20 | 100.00% | 2 | 100.00% | 
#endif
static int __init at91wdt_probe(struct platform_device *pdev)
{
	struct resource	*r;
	int err;
	struct at91wdt *wdt;
	wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
	if (!wdt)
		return -ENOMEM;
	wdt->mr = (WDT_HW_TIMEOUT * 256) | AT91_WDT_WDRSTEN | AT91_WDT_WDD |
		  AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT;
	wdt->mr_mask = 0x3FFFFFFF;
	wdt->nowayout = nowayout;
	wdt->wdd.parent = &pdev->dev;
	wdt->wdd.info = &at91_wdt_info;
	wdt->wdd.ops = &at91_wdt_ops;
	wdt->wdd.timeout = WDT_HEARTBEAT;
	wdt->wdd.min_timeout = 1;
	wdt->wdd.max_timeout = 0xFFFF;
	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	wdt->base = devm_ioremap_resource(&pdev->dev, r);
	if (IS_ERR(wdt->base))
		return PTR_ERR(wdt->base);
	wdt->sclk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(wdt->sclk))
		return PTR_ERR(wdt->sclk);
	err = clk_prepare_enable(wdt->sclk);
	if (err) {
		dev_err(&pdev->dev, "Could not enable slow clock\n");
		return err;
	}
	if (pdev->dev.of_node) {
		err = of_at91wdt_init(pdev->dev.of_node, wdt);
		if (err)
			goto err_clk;
	}
	err = at91_wdt_init(pdev, wdt);
	if (err)
		goto err_clk;
	platform_set_drvdata(pdev, wdt);
	pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n",
		wdt->wdd.timeout, wdt->nowayout);
	return 0;
err_clk:
	clk_disable_unprepare(wdt->sclk);
	return err;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 150 | 47.02% | 1 | 14.29% | 
| Alexandre Belloni | 77 | 24.14% | 1 | 14.29% | 
| Renaud Cerrato | 46 | 14.42% | 1 | 14.29% | 
| Jean-Christophe Plagniol-Villard | 24 | 7.52% | 1 | 14.29% | 
| Wenyou Yang | 10 | 3.13% | 1 | 14.29% | 
| Fabio Porcedda | 8 | 2.51% | 1 | 14.29% | 
| Joe Perches | 4 | 1.25% | 1 | 14.29% | 
| Total | 319 | 100.00% | 7 | 100.00% | 
static int __exit at91wdt_remove(struct platform_device *pdev)
{
	struct at91wdt *wdt = platform_get_drvdata(pdev);
	watchdog_unregister_device(&wdt->wdd);
	pr_warn("I quit now, hardware will probably reboot!\n");
	del_timer(&wdt->timer);
	clk_disable_unprepare(wdt->sclk);
	return 0;
}
Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Renaud Cerrato | 19 | 35.85% | 1 | 25.00% | 
| Boris Brezillon | 15 | 28.30% | 1 | 25.00% | 
| Wenyou Yang | 12 | 22.64% | 1 | 25.00% | 
| Alexandre Belloni | 7 | 13.21% | 1 | 25.00% | 
| Total | 53 | 100.00% | 4 | 100.00% | 
#if defined(CONFIG_OF)
static const struct of_device_id at91_wdt_dt_ids[] = {
	{ .compatible = "atmel,at91sam9260-wdt" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
#endif
static struct platform_driver at91wdt_driver = {
	.remove		= __exit_p(at91wdt_remove),
	.driver		= {
		.name	= "at91_wdt",
		.of_match_table = of_match_ptr(at91_wdt_dt_ids),
        },
};
module_platform_driver_probe(at91wdt_driver, at91wdt_probe);
MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
MODULE_LICENSE("GPL");
Overall Contributors
| Person | Tokens | Prop | Commits | CommitProp | 
| Boris Brezillon | 1072 | 60.39% | 5 | 27.78% | 
| Renaud Cerrato | 418 | 23.55% | 1 | 5.56% | 
| Alexandre Belloni | 92 | 5.18% | 1 | 5.56% | 
| Wenyou Yang | 69 | 3.89% | 1 | 5.56% | 
| Fabio Porcedda | 53 | 2.99% | 3 | 16.67% | 
| Jean-Christophe Plagniol-Villard | 48 | 2.70% | 2 | 11.11% | 
| Joe Perches | 14 | 0.79% | 1 | 5.56% | 
| Wim Van Sebroeck | 4 | 0.23% | 2 | 11.11% | 
| Andrew Victor | 3 | 0.17% | 1 | 5.56% | 
| Ben Dooks | 2 | 0.11% | 1 | 5.56% | 
| Arnd Bergmann |  | 0.00% | 0 | 0.00% | 
| Total | 1775 | 100.00% | 18 | 100.00% | 
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.