From 7f7d6bf3cac17fbcedfd168447e555bd962200cf Mon Sep 17 00:00:00 2001
From: Cerrato Renaud <r.cerrato@til-technologies.fr>
Date: Tue, 12 Mar 2013 15:14:16 +0100
Subject: [PATCH] added watchdog driver for at91sam9x

---
 arch/arm/mach-at91/at91sam9260_devices.c |   16 ++++
 arch/arm/mach-at91/include/mach/board.h  |    3 +
 drivers/watchdog/Kconfig                 |    8 ++
 drivers/watchdog/Makefile                |    1 +
 drivers/watchdog/at91sam9wd.c            |  143 ++++++++++++++++++++++++++++++
 5 files changed, 171 insertions(+), 0 deletions(-)
 create mode 100644 drivers/watchdog/at91sam9wd.c

diff --git a/arch/arm/mach-at91/at91sam9260_devices.c b/arch/arm/mach-at91/at91sam9260_devices.c
index 1c375ee..2ff3c55 100644
--- a/arch/arm/mach-at91/at91sam9260_devices.c
+++ b/arch/arm/mach-at91/at91sam9260_devices.c
@@ -80,6 +80,22 @@ void __init at91_add_device_udc(struct at91_udc_data *data)
 void __init at91_add_device_udc(struct at91_udc_data *data) {}
 #endif
 
+/* --------------------------------------------------------------------
+ *  Watchdog
+ * -------------------------------------------------------------------- */
+#ifdef CONFIG_WATCHDOG_AT91SAM9
+void at91_add_device_watchdog(void)
+{
+	add_generic_device("at91_wd", DEVICE_ID_DYNAMIC, NULL, AT91SAM9260_BASE_WDT,
+			16, IORESOURCE_MEM, NULL);
+}
+#else
+	void at91_add_device_watchdog(void) {}
+#endif
+
+/* --------------------------------------------------------------------
+ *  Ethernet
+ * -------------------------------------------------------------------- */
 #if defined(CONFIG_DRIVER_NET_MACB)
 void at91_add_device_eth(int id, struct at91_ether_platform_data *data)
 {
diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h
index 71267e1..c32926c 100644
--- a/arch/arm/mach-at91/include/mach/board.h
+++ b/arch/arm/mach-at91/include/mach/board.h
@@ -168,4 +168,7 @@ struct at91_spi_platform_data {
 void at91_add_device_spi(int spi_id, struct at91_spi_platform_data *pdata);
 
 void __init at91_add_device_lcdc(struct atmel_lcdfb_platform_data *data);
+
+void at91_add_device_watchdog(void);
+
 #endif
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2e2900c..b022d7a 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -21,4 +21,12 @@ config WATCHDOG_IMX
 	depends on ARCH_IMX
 	help
 	  Add support for watchdog found on Freescale i.MX SoCs.
+
+config WATCHDOG_AT91SAM9
+	bool "AT91SAM9x"
+	depends on SOC_AT91SAM9
+	help	  
+	  Add support for watchdog found on AT91SAM9 SoC.
+	  
 endif
+	  
\ No newline at end of file
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index f522b88..bb106e1 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_WATCHDOG) += wd_core.o
 obj-$(CONFIG_WATCHDOG_MXS28) += im28wd.o
 obj-$(CONFIG_WATCHDOG_IMX_RESET_SOURCE) += imxwd.o
+obj-$(CONFIG_WATCHDOG_AT91SAM9) += at91sam9wd.o
diff --git a/drivers/watchdog/at91sam9wd.c b/drivers/watchdog/at91sam9wd.c
new file mode 100644
index 0000000..d8d0c6e
--- /dev/null
+++ b/drivers/watchdog/at91sam9wd.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 Cerrato Renaud <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 version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <errno.h>
+#include <malloc.h>
+
+#include <mach/at91_wdt.h>
+
+#include <watchdog.h>
+
+#define at91_wdt_read(wdt, reg) \
+	__raw_readl((wdt)->baseaddr + (reg))
+#define at91_wdt_write(wdt, reg, val) \
+	__raw_writel((val), (wdt)->baseaddr + (reg))
+
+/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
+ * use this to convert a watchdog
+ * value from seconds.
+ */
+#define sec_to_ticks(t)  (((t) << 8) - 1)
+#define ticks_to_sec(t)  (((t) + 1) >> 8)
+
+static struct sam9_wdt {
+	struct watchdog wd;
+	void __iomem *baseaddr;
+	unsigned timeout;
+} sam9wdt;
+
+static int sam9_watchdog_is_disabled(struct sam9_wdt *wdt)
+{
+	unsigned int mr = at91_wdt_read(wdt, AT91_WDT_MR);
+	return (mr & AT91_WDT_WDDIS) != 0;
+}
+
+static int sam9_watchdog_set_timeout(struct watchdog *wdt, unsigned timeout) {
+	struct sam9_wdt *priv = (struct sam9_wdt *) wdt;
+	unsigned int reg;
+
+	/* Check if disabled */
+	if(sam9_watchdog_is_disabled(priv))
+		return timeout == 0 ? 0 : -EIO;
+
+	/*
+	 * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
+	 *
+	 * Since WDV is a 12-bit counter, the maximum period is
+	 * 4096 / 256 = 16 seconds.
+	 */
+	if(timeout && sec_to_ticks(timeout) > AT91_WDT_WDV)
+		return -EINVAL;
+
+	/*
+	 * Ping the watchdog?
+	 */
+	if(timeout == priv->timeout)
+	{
+		at91_wdt_write(priv, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
+		return 0;
+	}
+
+	/*
+	 * AT91SAM9 watchdog is write-once.
+	 */
+	if(priv->timeout != -1)
+		return -EPERM;
+
+	if(timeout == 0) {
+		reg = AT91_WDT_WDDIS;
+	} else {
+		reg = AT91_WDT_WDRSTEN  /* causes watchdog reset */
+			| AT91_WDT_WDDBGHLT /* disabled in debug mode */
+			| AT91_WDT_WDD      /* restart at any time */
+			| (sec_to_ticks(timeout) & AT91_WDT_WDV);  /* timer value */
+	}
+
+	at91_wdt_write(priv, AT91_WDT_MR, reg);
+	priv->timeout = timeout;
+
+	return at91_wdt_read(priv, AT91_WDT_MR) == reg ? 0 : -EIO;
+}
+
+
+static int sam9_wd_probe(struct device_d *dev)
+{
+	struct sam9_wdt *priv;
+	int rc;
+
+	priv = &sam9wdt;
+	priv->baseaddr = dev_request_mem_region(dev, 0);
+
+	if (!priv->baseaddr) {
+		rc = -ENOMEM;
+		goto on_error;
+	}
+
+	priv->wd.set_timeout = sam9_watchdog_set_timeout;
+	priv->timeout = -1;
+
+	rc = watchdog_register(&priv->wd);
+	if (rc != 0)
+		goto on_error;
+
+	if (sam9_watchdog_is_disabled(priv))
+		dev_warn(dev, "watchdog is disabled\n");
+	else
+		dev_info(dev, "enabled, timeout is %d sec.\n",
+				ticks_to_sec(at91_wdt_read(priv, AT91_WDT_MR) & AT91_WDT_WDV));
+
+	dev->priv = priv;
+	return 0;
+
+on_error:
+	free(priv);
+	return rc;
+}
+
+static void sam9_wd_remove(struct device_d *dev)
+{
+	struct sam9_wdt *priv= dev->priv;
+	watchdog_deregister(&priv->wd);
+	free(priv);
+}
+
+static struct driver_d sam9_wd_driver = {
+	.name   = "at91_wd",
+	.probe  = sam9_wd_probe,
+	.remove = sam9_wd_remove,
+};
+
+static int sam9_wd_init(void)
+{
+	platform_driver_register(&sam9_wd_driver);
+	return 0;
+}
+
+device_initcall(sam9_wd_init);
-- 
1.7.2.5

