From mboxrd@z Thu Jan 1 00:00:00 1970 From: Oleksij Rempel Subject: [PATCH v2] watchdog: add Alphascale asm9260-wdt driver Date: Thu, 5 Nov 2015 10:06:56 +0100 Message-ID: <1446714416-22587-2-git-send-email-linux@rempel-privat.de> References: <20151027023132.GA1270@roeck-us.net> <1446714416-22587-1-git-send-email-linux@rempel-privat.de> Return-path: In-Reply-To: <1446714416-22587-1-git-send-email-linux-YEK0n+YFykbzxQdaRaTXBw@public.gmane.org> Sender: linux-watchdog-owner-u79uwXL29TY76Z2rM5mHXA@public.gmane.org To: linux-watchdog-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, linux-0h96xk9xTtrk1uMJSBkQmQ@public.gmane.org, wim-IQzOog9fTRqzQB+pC5nmwQ@public.gmane.org, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org Cc: Oleksij Rempel List-Id: devicetree@vger.kernel.org Add WD support for Alphascale asm9260 SoC. This driver provide support for different function modes: - HW mode to trigger SoC reset on timeout - SW mode do soft reset if needed - DEBUG mode Optional support for stopping watchdog. If reset binding are not provided this driver will work in nowayout mode. Signed-off-by: Oleksij Rempel --- .../bindings/watchdog/alphascale-asm9260.txt | 39 ++ drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 1 + drivers/watchdog/asm9260_wdt.c | 415 +++++++++++++++++++++ 4 files changed, 464 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt create mode 100644 drivers/watchdog/asm9260_wdt.c diff --git a/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt new file mode 100644 index 0000000..6e54d1f --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt @@ -0,0 +1,39 @@ +Alphascale asm9260 Watchdog timer + +Required properties: + +- compatible : should be "alphascale,asm9260-wdt". +- reg : Specifies base physical address and size of the registers. +- clocks : the clocks feeding the watchdog timer. See clock-bindings.txt +- clock-names : should be set to + "mod" - source for tick counter. + "ahb" - ahb gate. + +Optional properties: +- resets : phandle pointing to the system reset controller with correct + reset line index for watchdog controller reset. This propertie is + required if you need to disable "nowayout" and it neened only with + CONFIG_WATCHDOG_NOWAYOUT=n. + Without reseting this WD controller, it is not possible to stop + counter. +- reset-names : should be set to "wdt_rst" if "resets" is used. +- timeout-sec : shall contain the default watchdog timeout in seconds, + if unset, the default timeout is 30 seconds. +- alphascale,mode : tree modes are supported + "hw" - hw reset (defaul). + "sw" - sw reset. + "debug" - no action is taken. + +Example: + +watchdog0: watchdog@80048000 { + compatible = "alphascale,asm9260-wdt"; + reg = <0x80048000 0x10>; + clocks = <&acc CLKID_SYS_WDT>, <&acc CLKID_AHB_WDT>; + clock-names = "mod", "ahb"; + interrupts = <55>; + resets = <&rst WDT_RESET>; + reset-names = "wdt_rst"; + timeout-sec = <30>; + alphascale,mode = "hw"; +}; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c68edc1..cc5f675 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -173,6 +173,15 @@ config ARM_SP805_WATCHDOG ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. +config ASM9260_WATCHDOG + tristate "Alphascale ASM9260 watchdog" + depends on MACH_ASM9260 + depends on OF + select WATCHDOG_CORE + help + Watchdog timer embedded into Alphascale asm9260 chips. This will reboot your + system when the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on SOC_AT91RM9200 && MFD_SYSCON diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0c616e3..bd7b0cd 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o +obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o diff --git a/drivers/watchdog/asm9260_wdt.c b/drivers/watchdog/asm9260_wdt.c new file mode 100644 index 0000000..9f2c321 --- /dev/null +++ b/drivers/watchdog/asm9260_wdt.c @@ -0,0 +1,415 @@ +/* + * Watchdog driver for Alphascale ASM9260. + * + * Copyright (c) 2014 Oleksij Rempel + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLOCK_FREQ 1000000 + +/* Watchdog Mode register */ +#define HW_WDMOD 0x00 +/* Wake interrupt. Set by HW, can't be cleared. */ +#define BM_MOD_WDINT BIT(3) +/* This bit set if timeout reached. Cleared by SW. */ +#define BM_MOD_WDTOF BIT(2) +/* HW Reset on timeout */ +#define BM_MOD_WDRESET BIT(1) +/* WD enable */ +#define BM_MOD_WDEN BIT(0) + +/* + * Watchdog Timer Constant register + * Minimal value is 0xff, the meaning of this value + * depends on used clock: T = WDCLK * (0xff + 1) * 4 + */ +#define HW_WDTC 0x04 +#define BM_WDTC_MAX(freq) (0x7fffffff / (freq)) + +/* Watchdog Feed register */ +#define HW_WDFEED 0x08 + +/* Watchdog Timer Value register */ +#define HW_WDTV 0x0c + +#define ASM9260_WDT_DEFAULT_TIMEOUT 30 + +enum asm9260_wdt_mode { + HW_RESET, + SW_RESET, + DEBUG, +}; + +struct asm9260_wdt_priv { + struct device *dev; + struct watchdog_device wdd; + struct clk *clk; + struct clk *clk_ahb; + struct reset_control *rst; + struct notifier_block restart_handler; + + void __iomem *iobase; + int irq; + unsigned long wdt_freq; + enum asm9260_wdt_mode mode; +}; + +static int asm9260_wdt_feed(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + iowrite32(0xaa, priv->iobase + HW_WDFEED); + iowrite32(0x55, priv->iobase + HW_WDFEED); + + return 0; +} + +static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = ioread32(priv->iobase + HW_WDTV); + + return DIV_ROUND_CLOSEST(counter, priv->wdt_freq); +} + +static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = wdd->timeout * priv->wdt_freq; + + iowrite32(counter, priv->iobase + HW_WDTC); + + return 0; +} + +static int asm9260_wdt_enable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 mode = 0; + + if (priv->mode == HW_RESET) + mode = BM_MOD_WDRESET; + + iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD); + + asm9260_wdt_updatetimeout(wdd); + + asm9260_wdt_feed(wdd); + + return 0; +} + +static int asm9260_wdt_disable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + /* The only way to disable WD is to reset it. */ + reset_control_assert(priv->rst); + reset_control_deassert(priv->rst); + + return 0; +} + +static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) +{ + wdd->timeout = to; + asm9260_wdt_updatetimeout(wdd); + + return 0; +} + +static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv) +{ + /* init WD if it was not started */ + priv->wdd.timeout = 1; + + iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD); + + asm9260_wdt_updatetimeout(&priv->wdd); + /* first pass correct sequence */ + asm9260_wdt_feed(&priv->wdd); + /* + * Then write wrong pattern to the feed to trigger reset + * ASAP. + */ + iowrite32(0xff, priv->iobase + HW_WDFEED); + + mdelay(1000); +} + +static irqreturn_t asm9260_wdt_irq(int irq, void *devid) +{ + struct asm9260_wdt_priv *priv = devid; + u32 stat; + + stat = ioread32(priv->iobase + HW_WDMOD); + if (!(stat & BM_MOD_WDINT)) + return IRQ_NONE; + + if (priv->mode == DEBUG) + dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n"); + else { + dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n"); + asm9260_wdt_sys_reset(priv); + } + + return IRQ_HANDLED; +} + +static int asm9260_restart_handler(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct asm9260_wdt_priv *priv = + container_of(this, struct asm9260_wdt_priv, restart_handler); + + asm9260_wdt_sys_reset(priv); + + return NOTIFY_DONE; +} + +static const struct watchdog_info asm9260_wdt_ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE, + .identity = "Alphascale asm9260 Watchdog", +}; + +static struct watchdog_ops asm9260_wdt_ops = { + .owner = THIS_MODULE, + .start = asm9260_wdt_enable, + .stop = asm9260_wdt_disable, + .get_timeleft = asm9260_wdt_gettimeleft, + .ping = asm9260_wdt_feed, + .set_timeout = asm9260_wdt_settimeout, +}; + +static int __init asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv) +{ + int clk_idx = 0, err; + unsigned long clk; + + priv->clk = devm_clk_get(priv->dev, "mod"); + if (IS_ERR(priv->clk)) + goto clk_err; + + /* configure AHB clock */ + clk_idx = 1; + priv->clk_ahb = devm_clk_get(priv->dev, "ahb"); + if (IS_ERR(priv->clk_ahb)) + goto clk_err; + + err = clk_prepare_enable(priv->clk_ahb); + if (err) { + dev_err(priv->dev, "Failed to enable ahb_clk!\n"); + goto out_err; + } + + err = clk_set_rate(priv->clk, CLOCK_FREQ); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to set rate!\n"); + goto out_err; + } + + err = clk_prepare_enable(priv->clk); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to enable clk!\n"); + goto out_err; + } + + /* wdt has internal divider */ + clk = clk_get_rate(priv->clk); + if (!clk) { + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed, clk is 0!\n"); + goto out_err; + } + + priv->wdt_freq = clk / 2; + + return 0; + +clk_err: + dev_err(priv->dev, "Failed to get clk (%s)\n", + clk_idx ? "ahb" : "mod"); +out_err: + return -EFAULT; +} + +static int __init asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv) +{ + const char *tmp; + int ret; + + /* default mode */ + priv->mode = HW_RESET; + + ret = of_property_read_string(priv->dev->of_node, + "alphascale,mode", &tmp); + if (ret < 0) + return ret; + + if (!strcmp(tmp, "hw")) + priv->mode = HW_RESET; + else if (!strcmp(tmp, "sw")) + priv->mode = SW_RESET; + else if (!strcmp(tmp, "debug")) + priv->mode = DEBUG; + else { + dev_warn(priv->dev, "unknown reset-type: %s. Using defaul \"hw\" mode.", + tmp); + return -EINVAL; + } + + return 0; +} + +static int __init asm9260_wdt_probe(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv; + struct watchdog_device *wdd; + struct resource *res; + bool nowayout = WATCHDOG_NOWAYOUT; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->iobase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->iobase)) + return PTR_ERR(priv->iobase); + + ret = asm9260_wdt_get_dt_clks(priv); + if (ret) + return ret; + + priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst"); + if (IS_ERR(priv->rst)) { + nowayout = 1; + priv->rst = NULL; + } + + wdd = &priv->wdd; + wdd->info = &asm9260_wdt_ident; + wdd->ops = &asm9260_wdt_ops; + wdd->min_timeout = 1; + wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq); + wdd->parent = &pdev->dev; + + watchdog_set_drvdata(wdd, priv); + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 30 second + * default, unless the max timeout is less than 30 seconds, then use + * the max instead. + */ + wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT; + watchdog_init_timeout(wdd, 0, &pdev->dev); + + watchdog_set_nowayout(wdd, nowayout); + + asm9260_wdt_get_dt_mode(priv); + + if (priv->mode != HW_RESET) + priv->irq = platform_get_irq(pdev, 0); + + if (priv->irq > 0) { + /* + * Not all supported platforms specify an interrupt for the + * watchdog, so let's make it optional. + */ + ret = devm_request_irq(&pdev->dev, priv->irq, + asm9260_wdt_irq, IRQF_SHARED, + pdev->name, priv); + if (ret < 0) + dev_err(&pdev->dev, "failed to request IRQ\n"); + } + + ret = watchdog_register_device(wdd); + if (ret) + goto clk_off; + + platform_set_drvdata(pdev, priv); + + priv->restart_handler.notifier_call = asm9260_restart_handler; + priv->restart_handler.priority = 128; + ret = register_restart_handler(&priv->restart_handler); + if (ret) + dev_err(&pdev->dev, "cannot register restart handler\n"); + + dev_info(&pdev->dev, "Watchdog enabled (to=%d, nw=%d, mode=%d)\n", + wdd->timeout, nowayout, priv->mode); + return 0; + +clk_off: + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + return ret; +} + +static void asm9260_wdt_shutdown(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_disable(&priv->wdd); +} + +static int __exit asm9260_wdt_remove(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_shutdown(pdev); + + unregister_restart_handler(&priv->restart_handler); + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + + return 0; +} + +static const struct of_device_id asm9260_wdt_of_match[] = { + { .compatible = "alphascale,asm9260-wdt"}, + {}, +}; +MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); + +static struct platform_driver asm9260_wdt_driver = { + .driver = { + .name = "asm9260-wdt", + .owner = THIS_MODULE, + .of_match_table = asm9260_wdt_of_match, + }, + .probe = asm9260_wdt_probe, + .remove = asm9260_wdt_remove, + .shutdown = asm9260_wdt_shutdown, +}; +module_platform_driver(asm9260_wdt_driver); + +MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver"); +MODULE_AUTHOR("Oleksij Rempel "); +MODULE_LICENSE("GPL"); -- 2.5.0 -- To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org More majordomo info at http://vger.kernel.org/majordomo-info.html From mboxrd@z Thu Jan 1 00:00:00 1970 Return-path: Received: from mout.gmx.net ([212.227.15.18]:55278 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1030521AbbKEJHQ (ORCPT ); Thu, 5 Nov 2015 04:07:16 -0500 From: Oleksij Rempel To: linux-watchdog@vger.kernel.org, linux@roeck-us.net, wim@iguana.be, devicetree@vger.kernel.org Cc: Oleksij Rempel Subject: [PATCH v2] watchdog: add Alphascale asm9260-wdt driver Date: Thu, 5 Nov 2015 10:06:56 +0100 Message-Id: <1446714416-22587-2-git-send-email-linux@rempel-privat.de> In-Reply-To: <1446714416-22587-1-git-send-email-linux@rempel-privat.de> References: <20151027023132.GA1270@roeck-us.net> <1446714416-22587-1-git-send-email-linux@rempel-privat.de> Sender: linux-watchdog-owner@vger.kernel.org List-Id: linux-watchdog@vger.kernel.org Add WD support for Alphascale asm9260 SoC. This driver provide support for different function modes: - HW mode to trigger SoC reset on timeout - SW mode do soft reset if needed - DEBUG mode Optional support for stopping watchdog. If reset binding are not provided this driver will work in nowayout mode. Signed-off-by: Oleksij Rempel --- .../bindings/watchdog/alphascale-asm9260.txt | 39 ++ drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 1 + drivers/watchdog/asm9260_wdt.c | 415 +++++++++++++++++++++ 4 files changed, 464 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt create mode 100644 drivers/watchdog/asm9260_wdt.c diff --git a/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt new file mode 100644 index 0000000..6e54d1f --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/alphascale-asm9260.txt @@ -0,0 +1,39 @@ +Alphascale asm9260 Watchdog timer + +Required properties: + +- compatible : should be "alphascale,asm9260-wdt". +- reg : Specifies base physical address and size of the registers. +- clocks : the clocks feeding the watchdog timer. See clock-bindings.txt +- clock-names : should be set to + "mod" - source for tick counter. + "ahb" - ahb gate. + +Optional properties: +- resets : phandle pointing to the system reset controller with correct + reset line index for watchdog controller reset. This propertie is + required if you need to disable "nowayout" and it neened only with + CONFIG_WATCHDOG_NOWAYOUT=n. + Without reseting this WD controller, it is not possible to stop + counter. +- reset-names : should be set to "wdt_rst" if "resets" is used. +- timeout-sec : shall contain the default watchdog timeout in seconds, + if unset, the default timeout is 30 seconds. +- alphascale,mode : tree modes are supported + "hw" - hw reset (defaul). + "sw" - sw reset. + "debug" - no action is taken. + +Example: + +watchdog0: watchdog@80048000 { + compatible = "alphascale,asm9260-wdt"; + reg = <0x80048000 0x10>; + clocks = <&acc CLKID_SYS_WDT>, <&acc CLKID_AHB_WDT>; + clock-names = "mod", "ahb"; + interrupts = <55>; + resets = <&rst WDT_RESET>; + reset-names = "wdt_rst"; + timeout-sec = <30>; + alphascale,mode = "hw"; +}; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c68edc1..cc5f675 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -173,6 +173,15 @@ config ARM_SP805_WATCHDOG ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. +config ASM9260_WATCHDOG + tristate "Alphascale ASM9260 watchdog" + depends on MACH_ASM9260 + depends on OF + select WATCHDOG_CORE + help + Watchdog timer embedded into Alphascale asm9260 chips. This will reboot your + system when the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on SOC_AT91RM9200 && MFD_SYSCON diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 0c616e3..bd7b0cd 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o +obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o diff --git a/drivers/watchdog/asm9260_wdt.c b/drivers/watchdog/asm9260_wdt.c new file mode 100644 index 0000000..9f2c321 --- /dev/null +++ b/drivers/watchdog/asm9260_wdt.c @@ -0,0 +1,415 @@ +/* + * Watchdog driver for Alphascale ASM9260. + * + * Copyright (c) 2014 Oleksij Rempel + * + * Licensed under GPLv2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLOCK_FREQ 1000000 + +/* Watchdog Mode register */ +#define HW_WDMOD 0x00 +/* Wake interrupt. Set by HW, can't be cleared. */ +#define BM_MOD_WDINT BIT(3) +/* This bit set if timeout reached. Cleared by SW. */ +#define BM_MOD_WDTOF BIT(2) +/* HW Reset on timeout */ +#define BM_MOD_WDRESET BIT(1) +/* WD enable */ +#define BM_MOD_WDEN BIT(0) + +/* + * Watchdog Timer Constant register + * Minimal value is 0xff, the meaning of this value + * depends on used clock: T = WDCLK * (0xff + 1) * 4 + */ +#define HW_WDTC 0x04 +#define BM_WDTC_MAX(freq) (0x7fffffff / (freq)) + +/* Watchdog Feed register */ +#define HW_WDFEED 0x08 + +/* Watchdog Timer Value register */ +#define HW_WDTV 0x0c + +#define ASM9260_WDT_DEFAULT_TIMEOUT 30 + +enum asm9260_wdt_mode { + HW_RESET, + SW_RESET, + DEBUG, +}; + +struct asm9260_wdt_priv { + struct device *dev; + struct watchdog_device wdd; + struct clk *clk; + struct clk *clk_ahb; + struct reset_control *rst; + struct notifier_block restart_handler; + + void __iomem *iobase; + int irq; + unsigned long wdt_freq; + enum asm9260_wdt_mode mode; +}; + +static int asm9260_wdt_feed(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + iowrite32(0xaa, priv->iobase + HW_WDFEED); + iowrite32(0x55, priv->iobase + HW_WDFEED); + + return 0; +} + +static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = ioread32(priv->iobase + HW_WDTV); + + return DIV_ROUND_CLOSEST(counter, priv->wdt_freq); +} + +static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 counter; + + counter = wdd->timeout * priv->wdt_freq; + + iowrite32(counter, priv->iobase + HW_WDTC); + + return 0; +} + +static int asm9260_wdt_enable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + u32 mode = 0; + + if (priv->mode == HW_RESET) + mode = BM_MOD_WDRESET; + + iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD); + + asm9260_wdt_updatetimeout(wdd); + + asm9260_wdt_feed(wdd); + + return 0; +} + +static int asm9260_wdt_disable(struct watchdog_device *wdd) +{ + struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); + + /* The only way to disable WD is to reset it. */ + reset_control_assert(priv->rst); + reset_control_deassert(priv->rst); + + return 0; +} + +static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) +{ + wdd->timeout = to; + asm9260_wdt_updatetimeout(wdd); + + return 0; +} + +static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv) +{ + /* init WD if it was not started */ + priv->wdd.timeout = 1; + + iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD); + + asm9260_wdt_updatetimeout(&priv->wdd); + /* first pass correct sequence */ + asm9260_wdt_feed(&priv->wdd); + /* + * Then write wrong pattern to the feed to trigger reset + * ASAP. + */ + iowrite32(0xff, priv->iobase + HW_WDFEED); + + mdelay(1000); +} + +static irqreturn_t asm9260_wdt_irq(int irq, void *devid) +{ + struct asm9260_wdt_priv *priv = devid; + u32 stat; + + stat = ioread32(priv->iobase + HW_WDMOD); + if (!(stat & BM_MOD_WDINT)) + return IRQ_NONE; + + if (priv->mode == DEBUG) + dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n"); + else { + dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n"); + asm9260_wdt_sys_reset(priv); + } + + return IRQ_HANDLED; +} + +static int asm9260_restart_handler(struct notifier_block *this, + unsigned long mode, void *cmd) +{ + struct asm9260_wdt_priv *priv = + container_of(this, struct asm9260_wdt_priv, restart_handler); + + asm9260_wdt_sys_reset(priv); + + return NOTIFY_DONE; +} + +static const struct watchdog_info asm9260_wdt_ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE, + .identity = "Alphascale asm9260 Watchdog", +}; + +static struct watchdog_ops asm9260_wdt_ops = { + .owner = THIS_MODULE, + .start = asm9260_wdt_enable, + .stop = asm9260_wdt_disable, + .get_timeleft = asm9260_wdt_gettimeleft, + .ping = asm9260_wdt_feed, + .set_timeout = asm9260_wdt_settimeout, +}; + +static int __init asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv) +{ + int clk_idx = 0, err; + unsigned long clk; + + priv->clk = devm_clk_get(priv->dev, "mod"); + if (IS_ERR(priv->clk)) + goto clk_err; + + /* configure AHB clock */ + clk_idx = 1; + priv->clk_ahb = devm_clk_get(priv->dev, "ahb"); + if (IS_ERR(priv->clk_ahb)) + goto clk_err; + + err = clk_prepare_enable(priv->clk_ahb); + if (err) { + dev_err(priv->dev, "Failed to enable ahb_clk!\n"); + goto out_err; + } + + err = clk_set_rate(priv->clk, CLOCK_FREQ); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to set rate!\n"); + goto out_err; + } + + err = clk_prepare_enable(priv->clk); + if (err) { + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed to enable clk!\n"); + goto out_err; + } + + /* wdt has internal divider */ + clk = clk_get_rate(priv->clk); + if (!clk) { + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + dev_err(priv->dev, "Failed, clk is 0!\n"); + goto out_err; + } + + priv->wdt_freq = clk / 2; + + return 0; + +clk_err: + dev_err(priv->dev, "Failed to get clk (%s)\n", + clk_idx ? "ahb" : "mod"); +out_err: + return -EFAULT; +} + +static int __init asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv) +{ + const char *tmp; + int ret; + + /* default mode */ + priv->mode = HW_RESET; + + ret = of_property_read_string(priv->dev->of_node, + "alphascale,mode", &tmp); + if (ret < 0) + return ret; + + if (!strcmp(tmp, "hw")) + priv->mode = HW_RESET; + else if (!strcmp(tmp, "sw")) + priv->mode = SW_RESET; + else if (!strcmp(tmp, "debug")) + priv->mode = DEBUG; + else { + dev_warn(priv->dev, "unknown reset-type: %s. Using defaul \"hw\" mode.", + tmp); + return -EINVAL; + } + + return 0; +} + +static int __init asm9260_wdt_probe(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv; + struct watchdog_device *wdd; + struct resource *res; + bool nowayout = WATCHDOG_NOWAYOUT; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->iobase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->iobase)) + return PTR_ERR(priv->iobase); + + ret = asm9260_wdt_get_dt_clks(priv); + if (ret) + return ret; + + priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst"); + if (IS_ERR(priv->rst)) { + nowayout = 1; + priv->rst = NULL; + } + + wdd = &priv->wdd; + wdd->info = &asm9260_wdt_ident; + wdd->ops = &asm9260_wdt_ops; + wdd->min_timeout = 1; + wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq); + wdd->parent = &pdev->dev; + + watchdog_set_drvdata(wdd, priv); + + /* + * If 'timeout-sec' unspecified in devicetree, assume a 30 second + * default, unless the max timeout is less than 30 seconds, then use + * the max instead. + */ + wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT; + watchdog_init_timeout(wdd, 0, &pdev->dev); + + watchdog_set_nowayout(wdd, nowayout); + + asm9260_wdt_get_dt_mode(priv); + + if (priv->mode != HW_RESET) + priv->irq = platform_get_irq(pdev, 0); + + if (priv->irq > 0) { + /* + * Not all supported platforms specify an interrupt for the + * watchdog, so let's make it optional. + */ + ret = devm_request_irq(&pdev->dev, priv->irq, + asm9260_wdt_irq, IRQF_SHARED, + pdev->name, priv); + if (ret < 0) + dev_err(&pdev->dev, "failed to request IRQ\n"); + } + + ret = watchdog_register_device(wdd); + if (ret) + goto clk_off; + + platform_set_drvdata(pdev, priv); + + priv->restart_handler.notifier_call = asm9260_restart_handler; + priv->restart_handler.priority = 128; + ret = register_restart_handler(&priv->restart_handler); + if (ret) + dev_err(&pdev->dev, "cannot register restart handler\n"); + + dev_info(&pdev->dev, "Watchdog enabled (to=%d, nw=%d, mode=%d)\n", + wdd->timeout, nowayout, priv->mode); + return 0; + +clk_off: + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + return ret; +} + +static void asm9260_wdt_shutdown(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_disable(&priv->wdd); +} + +static int __exit asm9260_wdt_remove(struct platform_device *pdev) +{ + struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev); + + asm9260_wdt_shutdown(pdev); + + unregister_restart_handler(&priv->restart_handler); + clk_disable_unprepare(priv->clk); + clk_disable_unprepare(priv->clk_ahb); + + return 0; +} + +static const struct of_device_id asm9260_wdt_of_match[] = { + { .compatible = "alphascale,asm9260-wdt"}, + {}, +}; +MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); + +static struct platform_driver asm9260_wdt_driver = { + .driver = { + .name = "asm9260-wdt", + .owner = THIS_MODULE, + .of_match_table = asm9260_wdt_of_match, + }, + .probe = asm9260_wdt_probe, + .remove = asm9260_wdt_remove, + .shutdown = asm9260_wdt_shutdown, +}; +module_platform_driver(asm9260_wdt_driver); + +MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver"); +MODULE_AUTHOR("Oleksij Rempel "); +MODULE_LICENSE("GPL"); -- 2.5.0