From: wurui <wu...@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 -- _______________________________________________ linux-yocto mailing list linux-yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/linux-yocto