[linux-yocto] [PATCH 06/65] watchdog: add owl watchdog driver

Jiang Lu lu.jiang at windriver.com
Wed Dec 21 01:16:07 PST 2016


From: wurui <wurui at actions-semi.com>

commit 9cd8f83b3e395a97f19697d3613628491e971282 from
https://github.com/xapp-le/kernel.git

Change-Id: Ifc7a052af9d996851f9425c7e41845ecbac11f78
---
 drivers/watchdog/Kconfig   |   8 +
 drivers/watchdog/Makefile  |   2 +
 drivers/watchdog/owl_wdt.c | 379 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 389 insertions(+)
 mode change 100644 => 100755 drivers/watchdog/Kconfig
 mode change 100644 => 100755 drivers/watchdog/Makefile
 create mode 100755 drivers/watchdog/owl_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
old mode 100644
new mode 100755
index e656736..cc3eee4
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -153,6 +153,14 @@ config XILINX_WATCHDOG
 
 # ARM Architecture
 
+config OWL_WATCHDOG
+	bool "owl watchdog"
+	depends on ARCH_OWL
+	select WATCHDOG_CORE
+	help
+	  Watchdog timer embedded into OWL chips. This will reboot your
+	  system when the timeout is reached.
+
 config ARM_SP805_WATCHDOG
 	tristate "ARM SP805 Watchdog"
 	depends on (ARM || ARM64) && ARM_AMBA
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
old mode 100644
new mode 100755
index 59ea9a1..d8a93cb
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -29,6 +29,8 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
 # ALPHA Architecture
 
 # ARM Architecture
+
+obj-$(CONFIG_OWL_WATCHDOG) += owl_wdt.o
 obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o
 obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
 obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
diff --git a/drivers/watchdog/owl_wdt.c b/drivers/watchdog/owl_wdt.c
new file mode 100755
index 0000000..44ebc74
--- /dev/null
+++ b/drivers/watchdog/owl_wdt.c
@@ -0,0 +1,379 @@
+/*
+ * watchdog driver for OWL SOC
+ *
+ * Copyright 2013 Actions Semi Inc.
+ * Author: Actions Semi, Inc.
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <asm/div64.h>
+#include <linux/of_irq.h>
+
+#include <asm/smp_twd.h>
+#include <mach/powergate.h>
+#include <mach/clkname.h>
+#include <asm/system_misc.h>
+
+struct owl_wdt {
+	void __iomem	*wd_base;
+	void __iomem	*cmu_base;
+	int		irq;
+	struct clk *periph_clk;
+	struct notifier_block nb;
+	int wd_type;	//0-hard watchdog; 1-soft watchdog
+};
+
+static DEFINE_SPINLOCK(wdt_lock);
+
+#define TIMER_MARGIN	60		/* Default is 60 seconds */
+static unsigned int owl_margin = TIMER_MARGIN;	/* in seconds */
+module_param(owl_margin, uint, 0);
+MODULE_PARM_DESC(owl_margin,
+	"OWL watchdog owl_margin in seconds. (0 < owl_margin < 3600, default="
+					__MODULE_STRING(TIMER_MARGIN) ")");
+
+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) ")");
+
+static int owl_noboot = 0;
+module_param(owl_noboot, int, 0);
+MODULE_PARM_DESC(owl_noboot,
+	"OWL watchdog action, set to 1 to ignore reboots, 0 to reboot (default=0)");
+
+
+static unsigned int owl_get_periphclk(struct owl_wdt *wdt)
+{
+	unsigned int corepll, div;
+	
+	corepll = (readl_relaxed(wdt->cmu_base + 0x0) & 0xff) * (12 * 1000000);
+	div =((readl_relaxed(wdt->cmu_base + 0x1c) >> 20) & 0x7) + 1;
+	
+	return (corepll / div);
+}
+
+/*
+ *	owl_wdt_keepalive - reload the timer
+ *
+ *	Note that the spec says a DIFFERENT value must be written to the reload
+ *	register each time.  The "perturb" variable deals with this by adding 1
+ *	to the count every other time the function is called.
+ */
+static void __owl_wdt_keepalive(struct watchdog_device *wdd)
+{
+	unsigned int twd_clk_hz;
+	struct owl_wdt *wdt = watchdog_get_drvdata(wdd);
+	unsigned long count;
+
+	spin_lock(&wdt_lock);
+	twd_clk_hz = owl_get_periphclk(wdt);
+	count = wdd->timeout * (twd_clk_hz / 256);
+	/* Reload the counter */
+	writel_relaxed(count, wdt->wd_base + TWD_WDOG_LOAD);
+	spin_unlock(&wdt_lock);
+}
+
+static void __owl_wdt_start(struct watchdog_device *wdd)
+{
+	struct owl_wdt *wdt = watchdog_get_drvdata(wdd);
+
+	dev_info(wdd->dev, "enabling watchdog timeout=%ds\n", wdd->timeout);
+
+	/* This loads the count register but does NOT start the count yet */
+	__owl_wdt_keepalive(wdd);
+
+	if (owl_noboot || wdt->wd_type == 1) {
+		/* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */
+		writel_relaxed(0x0000FF05, wdt->wd_base + TWD_WDOG_CONTROL);
+		enable_percpu_irq(wdt->irq, 0);
+		if(wdt->wd_type == 1 && panic_timeout == 0)
+			panic_timeout = 5;
+	} else {
+		/* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */
+		writel_relaxed(0x0000FF09, wdt->wd_base + TWD_WDOG_CONTROL);
+	}
+}
+
+static void __owl_wdt_stop(struct watchdog_device *wdd)
+{
+	unsigned int val;
+	struct owl_wdt *wdt = watchdog_get_drvdata(wdd);
+	
+	dev_info(wdd->dev, "disabling watchdog\n");
+
+	val = readl_relaxed(wdt->wd_base + TWD_WDOG_CONTROL);
+	if(val & 0x1) {
+		if(val & 0x4) {
+			disable_percpu_irq(wdt->irq);
+		}
+		writel_relaxed(0x12345678, wdt->wd_base + TWD_WDOG_DISABLE);
+		writel_relaxed(0x87654321, wdt->wd_base + TWD_WDOG_DISABLE);
+		writel_relaxed(0x0, wdt->wd_base + TWD_WDOG_CONTROL);
+	}
+}
+
+static int owl_wdt_start(struct watchdog_device *wdd)
+{
+	if(smp_call_function_single(0, (void*)__owl_wdt_start, wdd, 1) != 0)
+		BUG();
+		
+	return 0;
+}
+
+static int owl_wdt_ping(struct watchdog_device *wdd)
+{
+	if(smp_call_function_single(0, (void*)__owl_wdt_keepalive, wdd, 1) != 0)
+		BUG();
+		
+	return 0;
+}
+
+static int owl_wdt_stop(struct watchdog_device *wdd)
+{
+	if(smp_call_function_single(0, (void*)__owl_wdt_stop, wdd, 1) != 0)
+		BUG();
+		
+	return 0;
+}
+
+static int owl_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
+{
+	if (t < 1 || t > 3600) {
+		dev_warn(wdd->dev,
+			"owl_margin value must be 1<=x<=3600, using %d\n",
+			owl_margin);
+		return -EINVAL;
+	}
+	
+	dev_info(wdd->dev, "change watchdog timeout from %ds to %ds\n", wdd->timeout, t);
+	wdd->timeout = t;
+	return 0;
+}
+
+static struct watchdog_info owl_wdt_info = {
+	.identity = "OWL Watchdog",
+	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static struct watchdog_ops owl_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = owl_wdt_start,
+	.stop = owl_wdt_stop,
+	.ping = owl_wdt_ping,
+	.set_timeout = owl_wdt_set_timeout,
+};
+
+static struct watchdog_device owl_wdt_wdd= {
+	.info = &owl_wdt_info,
+	.ops = &owl_wdt_ops,
+	.min_timeout = 1,
+	.max_timeout = 3600,
+};
+
+static void periph_clk_notify_on_cpu0(struct clk_notifier_data *cnd)
+{
+	unsigned long long newcount;
+	unsigned int oldcount;
+	struct owl_wdt *wdt = watchdog_get_drvdata(&owl_wdt_wdd);
+
+	spin_lock(&wdt_lock);
+	if(readl_relaxed(wdt->wd_base + TWD_WDOG_CONTROL) & 0x1) {
+		oldcount =  readl_relaxed(wdt->wd_base + TWD_WDOG_COUNTER);
+		newcount = (unsigned long long)oldcount * cnd->new_rate;
+		do_div(newcount, cnd->old_rate);
+		writel_relaxed((unsigned int)newcount, wdt->wd_base + TWD_WDOG_LOAD);
+	}
+	spin_unlock(&wdt_lock);
+}
+
+static int periph_clk_notify(struct notifier_block *nb, unsigned long action, void *data)
+{
+	struct clk_notifier_data *cnd = data;
+	
+	if (action == POST_RATE_CHANGE && cnd->new_rate < cnd->old_rate) {
+		if(smp_call_function_single(0, (void*)periph_clk_notify_on_cpu0, data, 1) != 0)
+			BUG();
+	} else if(action == PRE_RATE_CHANGE && cnd->new_rate > cnd->old_rate) {
+		if(smp_call_function_single(0, (void*)periph_clk_notify_on_cpu0, data, 1) != 0)
+			BUG();
+	}
+	
+	return NOTIFY_OK;
+}
+
+/*
+ *	This is the interrupt handler.  Note that we only use this
+ *	in testing mode, so don't actually do a reboot here.
+ */
+static irqreturn_t owl_wdt_fire(int irq, void *arg)
+{
+	struct owl_wdt *wdt = watchdog_get_drvdata(&owl_wdt_wdd);
+
+	/* Check it really was our interrupt */
+	if (readl_relaxed(wdt->wd_base + TWD_WDOG_INTSTAT)) {
+		if(wdt->wd_type == 1) {
+			pr_emerg("watchdog - Rebooting now\n");
+			arm_pm_restart('h', NULL);
+		} else {
+			pr_emerg("watchdog - Reboot ignored\n");
+		}
+		/* Clear the interrupt on the watchdog */
+		__owl_wdt_stop(&owl_wdt_wdd);
+		writel_relaxed(1, wdt->wd_base + TWD_WDOG_INTSTAT);
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+static int _get_watchdog_type(struct platform_device *pdev)
+{
+	const char *type;
+	
+	if (of_property_read_string(pdev->dev.of_node, "wd_type", &type) == 0) {
+		if(strcmp(type, "soft") == 0) {
+			dev_info(&pdev->dev, "watchdog soft mode\n");
+			return 1;
+		}
+	}
+
+	dev_info(&pdev->dev, "watchdog hard mode\n");
+	return 0;
+}
+
+static int owl_wdt_probe(struct platform_device *pdev)
+{
+	struct owl_wdt *wdt;
+	struct resource *res;
+	int ret;
+
+	wdt = devm_kzalloc(&pdev->dev, sizeof(struct owl_wdt), GFP_KERNEL);
+	if (!wdt) {
+		dev_err(&pdev->dev, "alloc owl_wdt failed\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "get resource failed\n");
+		return -ENODEV;
+	}
+	wdt->wd_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+	if (!wdt->wd_base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!res) {
+		dev_err(&pdev->dev, "get resource failed\n");
+		return -ENODEV;
+	}
+	wdt->cmu_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+	if (!wdt->cmu_base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		return -ENOMEM;
+	}
+		
+	wdt->irq = platform_get_irq(pdev, 0);
+	if (wdt->irq >= 0) {
+		ret = request_percpu_irq(wdt->irq, owl_wdt_fire, "owl_wdt", wdt);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"cannot register IRQ%d for watchdog\n",
+				wdt->irq);
+			return ret;
+		}
+	}
+	
+	if (owl_margin < 1 || owl_margin > 3600) {
+		owl_margin = TIMER_MARGIN;
+		dev_warn(&pdev->dev,
+			"owl_margin value must be 1<=x<=3600, using %d\n",
+			owl_margin);
+	}
+
+	owl_wdt_wdd.timeout = owl_margin;
+	watchdog_set_drvdata(&owl_wdt_wdd, wdt);
+	watchdog_set_nowayout(&owl_wdt_wdd, nowayout);
+
+	owl_powergate_power_on(OWL_POWERGATE_VDE);
+	
+	wdt->periph_clk = clk_get_sys(NULL, CLKNAME_PERIPH_CLK); 
+	if (IS_ERR(wdt->periph_clk)){
+		free_percpu_irq(wdt->irq, wdt);
+		dev_err(&pdev->dev,
+			"clk_get_sys(%s) failed\n",	CLKNAME_PERIPH_CLK);
+		return PTR_ERR(wdt->periph_clk); 		 
+	} 
+	wdt->nb.notifier_call = periph_clk_notify;
+	clk_notifier_register(wdt->periph_clk, &wdt->nb);
+
+	wdt->wd_type = _get_watchdog_type(pdev);
+	
+	ret = watchdog_register_device(&owl_wdt_wdd);
+	if (ret) {
+		clk_notifier_unregister(wdt->periph_clk, &wdt->nb);
+		clk_put(wdt->periph_clk);
+		free_percpu_irq(wdt->irq, wdt);
+		dev_err(&pdev->dev, "register failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int owl_wdt_remove(struct platform_device *pdev)
+{
+	struct owl_wdt *wdt = watchdog_get_drvdata(&owl_wdt_wdd);
+
+	clk_notifier_unregister(wdt->periph_clk, &wdt->nb);
+	clk_put(wdt->periph_clk);
+	free_percpu_irq(wdt->irq, wdt);
+	watchdog_unregister_device(&owl_wdt_wdd);
+	owl_powergate_power_off(OWL_POWERGATE_VDE);
+	return 0;
+}
+
+static struct of_device_id owl_wdt_id_table[] = {
+	{ .compatible = "actions,owl-wdt" },
+	{ .compatible = "actions,atm7059-wdt" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, owl_wdt_id_table);
+
+static struct platform_driver owl_wdt_driver = {
+	.probe		= owl_wdt_probe,
+	.remove		= owl_wdt_remove,
+	.driver		= {
+		.name	= "owl_wdt",
+		.owner	= THIS_MODULE,
+		.of_match_table	= owl_wdt_id_table,
+	},
+};
+
+module_platform_driver(owl_wdt_driver);
+
+MODULE_AUTHOR("Actions Semi Inc.");
+MODULE_DESCRIPTION("watchdog driver for OWL SOC");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
-- 
2.7.4



More information about the linux-yocto mailing list