# HG changeset patch
# User Dmitriy Taychenachev <dimichxp@gmail.com>
# Date 1247977147 -32400

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -266,6 +266,12 @@
 	  To compile this driver as a module, choose M here: the
 	  module will be called stmp3xxx_wdt.
 
+config SCMA11_WATCHDOG
+	tristate "Freescale SCMA-11 watchdog"
+	depends on ARCH_SCMA11
+	help
+	  Say Y here. Aŭ M. Aŭ N.
+
 # AVR32 Architecture
 
 config AT32AP700X_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -44,6 +44,7 @@
 obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
 obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o
 obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o
+obj-$(CONFIG_SCMA11_WATCHDOG) += scma11_wdt.o
 
 # AVR32 Architecture
 obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/scma11_wdt.c b/drivers/watchdog/scma11_wdt.c
new file mode 100644
--- /dev/null
+++ b/drivers/watchdog/scma11_wdt.c
@@ -0,0 +1,335 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/watchdog.h>
+#include <linux/clk.h>
+
+#define WDT_WCR		(0x0)
+#define WDT_WSR		(0x2)
+#define WDT_WRSR	(0x4)
+
+#define WDT_WCR_WT_MASK	(0xff)
+#define WDT_WCR_WT_OFF	(8)
+#define WDT_WCR_WDE	(1 << 2)
+
+#define WDT_WRSR_TOUT	(1 << 1)
+
+/* all timeouts are in halves-of-the-second */
+#define TIMEOUT_MIN 1
+#define TIMEOUT_MAX 255
+#define TIMEOUT_DEFAULT 64
+
+static int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+		 "Timeout value in 0.5 secs (default="
+		 __MODULE_STRING(TIMEOUT_DEFAULT) ")");
+
+struct scma11_wdt {
+	struct miscdevice device;
+	struct watchdog_info info;
+	spinlock_t io_lock;
+	void __iomem *base;
+	struct clk *clk;
+	unsigned long users;
+
+	int bootstatus;
+};
+
+struct scma11_wdt *g_wdt;
+
+/* Is the given timeout in valid range */
+static inline int is_timeout_ok(int t)
+{
+	return (t >= TIMEOUT_MIN) && (t <= TIMEOUT_MAX);
+}
+
+/* Kick the dog */
+static void scma11_wdt_kick(struct scma11_wdt *wdt)
+{
+	spin_lock(&wdt->io_lock);
+	writew(0x5555, wdt->base + WDT_WSR);
+	writew(0xAAAA, wdt->base + WDT_WSR);
+	spin_unlock(&wdt->io_lock);
+}
+
+/* Set the given timeout, assuming it is valid */
+static int scma11_wdt_get_timeout(struct scma11_wdt *wdt)
+{
+	u16 wcr;
+
+	wcr = readw(wdt->base + WDT_WCR);
+	return (wcr >> WDT_WCR_WT_OFF) && WDT_WCR_WT_MASK;
+}
+
+static void scma11_wdt_set_timeout(struct scma11_wdt *wdt, int t)
+{
+	u16 wcr;
+
+	spin_lock(&wdt->io_lock);
+	wcr = readw(wdt->base + WDT_WCR);
+	wcr &= ~(WDT_WCR_WT_MASK << WDT_WCR_WT_OFF);
+	wcr |= (t & WDT_WCR_WT_MASK) << WDT_WCR_WT_OFF;
+	writew(wcr, wdt->base + WDT_WCR);
+	spin_unlock(&wdt->io_lock);
+}
+
+static int scma11_wdt_is_enabled(struct scma11_wdt *wdt)
+{
+	u16 wcr;
+
+	spin_lock(&wdt->io_lock);
+	wcr = readw(wdt->base + WDT_WCR);
+	spin_unlock(&wdt->io_lock);
+
+	return !!(wcr & WDT_WCR_WDE);
+}
+
+static void scma11_wdt_enable(struct scma11_wdt *wdt)
+{
+	u16 wcr;
+
+	spin_lock(&wdt->io_lock);
+	wcr = readw(wdt->base + WDT_WCR);
+	wcr |= WDT_WCR_WDE;
+	writew(wcr, wdt->base + WDT_WCR);
+	spin_unlock(&wdt->io_lock);
+}
+
+static long scma11_wdt_ioctl(struct file *file, unsigned int cmd,
+			    unsigned long arg)
+{
+	struct scma11_wdt *wdt = file->private_data;
+	int ret = 0;
+	int time;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = copy_to_user(argp, &wdt->info, sizeof(struct watchdog_info))
+			? -EFAULT : 0;
+		break;
+	case WDIOC_GETSTATUS:
+		ret = put_user(0, p);
+		break;
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(wdt->bootstatus, p);
+		break;
+	case WDIOC_SETOPTIONS:
+		ret = get_user(time, p);
+		if (ret)
+			break;
+		if (time & WDIOS_DISABLECARD)
+			ret = -EINVAL;
+		if (time & WDIOS_ENABLECARD) {
+		        scma11_wdt_enable(wdt);
+			scma11_wdt_kick(wdt);
+			ret = 0;
+		}
+		break;
+	case WDIOC_KEEPALIVE:
+		scma11_wdt_kick(wdt);
+		break;
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(time, p);
+		if (ret)
+			break;
+		time *= 2;
+		if (!is_timeout_ok(time)) {
+			ret = -EINVAL;
+			break;
+		}
+		scma11_wdt_set_timeout(wdt, time);
+		scma11_wdt_enable(wdt);
+		scma11_wdt_kick(wdt);
+		/* fall through */
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(scma11_wdt_get_timeout(wdt) / 2, p);
+		break;
+	}
+
+	return ret;
+}
+
+static int scma11_wdt_open(struct inode *ino, struct file *file)
+{
+	struct scma11_wdt *wdt = g_wdt;
+
+	if (test_and_set_bit(0, &wdt->users)) {
+		return -EBUSY;
+	}
+
+	clk_enable(wdt->clk);
+	scma11_wdt_enable(wdt);
+	scma11_wdt_kick(wdt);
+
+	file->private_data = wdt;
+	return nonseekable_open(ino, file);
+}
+
+static int scma11_wdt_close(struct inode *ino, struct file *file)
+{
+	struct scma11_wdt *wdt = file->private_data;
+
+	/* we can't stop wdog, do our best */
+	clear_bit(1, &wdt->users);
+	clk_disable(wdt->clk);
+
+	return 0;
+}
+
+static ssize_t scma11_wdt_write(struct file *file, const char __user *data,
+				size_t len, loff_t *ppos)
+{
+	struct scma11_wdt *wdt = file->private_data;
+
+	if (len) {
+		scma11_wdt_kick(wdt);
+	}
+	return len;
+}
+
+static const struct file_operations scma11_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.unlocked_ioctl	= scma11_wdt_ioctl,
+	.open		= scma11_wdt_open,
+	.release	= scma11_wdt_close,
+	.write		= scma11_wdt_write,
+};
+
+static int __devinit scma11_wdt_probe(struct platform_device *pdev)
+{
+	struct clk *clk;
+	struct resource *mmio;
+	struct scma11_wdt *wdt;
+	int ret;
+
+	if (g_wdt) {
+		dev_dbg(&pdev->dev, "there is no support for multiple wdogs\n");
+		return -EBUSY;
+	}
+	mmio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mmio) {
+		dev_dbg(&pdev->dev, "no memory resource supplied\n");
+		return -ENXIO;
+	}
+	clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_dbg(&pdev->dev, "no clk supplied\n");
+		return PTR_ERR(clk);
+	}
+	wdt = kzalloc(sizeof(struct scma11_wdt), GFP_KERNEL);
+	if (!wdt) {
+		dev_dbg(&pdev->dev, "can't allocate scma11_wdt struct\n");
+		ret = -ENOMEM;
+		goto free_clk;
+	}
+	g_wdt = wdt;
+	wdt->base = ioremap(mmio->start, mmio->end - mmio->start + 1);
+	if (!wdt->base) {
+		dev_dbg(&pdev->dev, "can't remap mmio region\n");
+		ret = -ENOMEM;
+		goto free_dev;
+	}
+	spin_lock_init(&wdt->io_lock);
+
+	wdt->clk = clk;
+	clk_enable(wdt->clk);
+
+	wdt->device.minor = WATCHDOG_MINOR;
+	wdt->device.name = "watchdog";
+	wdt->device.fops = &scma11_wdt_fops;
+
+	if (!is_timeout_ok(timeout)) {
+		dev_info(&pdev->dev, "falling back to default timeout\n");
+	}
+	timeout = TIMEOUT_DEFAULT;
+	scma11_wdt_set_timeout(wdt, timeout);
+
+	if (scma11_wdt_is_enabled(wdt)) {
+		dev_dbg(&pdev->dev, "wdt is enabled, kicking it\n");
+		scma11_wdt_kick(wdt);
+	}
+
+	strncpy(wdt->info.identity, "scma11 watchdog",
+		sizeof(((struct watchdog_info*)0)->identity));
+	wdt->info.options = WDIOF_CARDRESET |
+	                    WDIOF_SETTIMEOUT |
+	                    WDIOF_KEEPALIVEPING;
+
+	if (readw(wdt->base + WDT_WRSR) & WDT_WRSR_TOUT) {
+		wdt->bootstatus = WDIOF_CARDRESET;
+	}
+
+	ret = misc_register(&wdt->device);
+	if (ret) {
+		dev_dbg(&pdev->dev, "can't register device\n");
+		goto free_mapping;
+	}
+
+	platform_set_drvdata(pdev, wdt);
+	wdt->device.parent = &pdev->dev;
+	dev_info(&pdev->dev, "scma11 wdt registered\n");
+
+	clk_disable(clk);
+
+	return 0;
+
+free_mapping:
+	iounmap(wdt->base);
+free_dev:
+	kfree(wdt);
+free_clk:
+	clk_disable(clk);
+	clk_put(clk);
+	return ret;
+}
+
+
+static int __devexit scma11_wdt_remove(struct platform_device *pdev)
+{
+	struct scma11_wdt *wdt;
+	wdt = platform_get_drvdata(pdev);
+	if (wdt) {
+		misc_deregister(&wdt->device);
+		iounmap(wdt->base);
+		clk_put(wdt->clk);
+		kfree(wdt);
+		g_wdt = NULL;
+		platform_set_drvdata(pdev, NULL);
+	}
+	return 0;
+}
+
+static struct platform_driver scma11_wdt_driver = {
+	.probe = scma11_wdt_probe,
+	.remove = __devexit_p(scma11_wdt_remove),
+	.driver = {
+		.name = "scma11-wdt",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init scma11_wdt_init(void)
+{
+	return platform_driver_register(&scma11_wdt_driver);
+}
+
+static void __exit scma11_wdt_exit(void)
+{
+	platform_driver_unregister(&scma11_wdt_driver);
+}
+
+module_init(scma11_wdt_init);
+module_exit(scma11_wdt_exit);
+
+MODULE_LICENSE("GPL");


