From patchwork Fri Mar 23 16:44:22 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Marek_Beh=C3=BAn?= X-Patchwork-Id: 10305133 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id D91A3600F6 for ; Fri, 23 Mar 2018 16:44:51 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id C6B4228F24 for ; Fri, 23 Mar 2018 16:44:51 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B9D0328F2D; Fri, 23 Mar 2018 16:44:51 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=ham version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id B96D628F24 for ; Fri, 23 Mar 2018 16:44:50 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:Message-Id:Date: Subject:To:From:Reply-To:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Owner; bh=1KdOeaVTJa/CqqfIx6y1aIzOvd0H5L0TeS3emSDdn04=; b=E6W Spr5jSRwCMSjdLN7TtWXM3oJtE1aOPCi7mNqgZjZ1A4SBs/eC08fRTWunaUZbFxgEMHPlaurgxFzI 8FRsu7/pw+XvumOQC/f2hbbJBUai+gIE6WIB6QUZYgd8LcQPravraMXzu4CDRympnskLCJvDckQdW qI2N3oJjnZnHibKSjgk1Jv8D2dSM98QKfO8km8n/paaNutYwYeyBitWYBaEHJ4kPcQVSX8r7H8CES FP/HB466bXFOehHvs2cORWxga3qyGC2JZxIDXFCRjhfkXgWb2QGMaIFzKIC8m6vbwjt9xIzXqfEaQ kgFsEPAPVHynNdiJOmC+XrWBaHwUUKQ==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1ezPo3-0002eW-Ud; Fri, 23 Mar 2018 16:44:43 +0000 Received: from mail.nic.cz ([217.31.204.67]) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1ezPnz-0002cq-Dj for linux-arm-kernel@lists.infradead.org; Fri, 23 Mar 2018 16:44:41 +0000 Received: from dellmb.labs.office.nic.cz (unknown [IPv6:2001:1488:fffe:6:cac7:3539:7f1f:463]) by mail.nic.cz (Postfix) with ESMTP id B32606257C; Fri, 23 Mar 2018 17:44:24 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=nic.cz; s=default; t=1521823464; bh=WNibAjDKQHOwUbYVLaphM4Tn/LC75PzAKg9mLItBr3E=; h=From:To:Date; b=C024BRJ4B/di4kHHx35yufKv+BK0EyPEZqmboxJe/h7YJXfie27XnRi/dTnV2gxRe KX6dkeqY768ZQ0kIsiWNeSYfvNip4CjOvTnfFz29lsJbakG+GK5oQ3zfLx8wJDoR45 AxNdAEgL4SPpGgFiWmMTM92CyQWES8HAYXuDPC/Y= From: =?UTF-8?q?Marek=20Beh=C3=BAn?= To: linux-arm-kernel@lists.infradead.org Subject: [PATCH] watchdog: Add support for Armada 37xx CPU watchdog Date: Fri, 23 Mar 2018 17:44:22 +0100 Message-Id: <20180323164422.27959-1-marek.behun@nic.cz> X-Mailer: git-send-email 2.16.1 X-Virus-Scanned: clamav-milter 0.99.2 at mail X-Virus-Status: Clean X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180323_094439_908591_DAFB3B27 X-CRM114-Status: GOOD ( 23.22 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Gregory CLEMENT , =?UTF-8?q?Marek=20Beh=C3=BAn?= MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP This adds support for the CPU watchdog found on Marvell Armada 37xx SoCs. There are 4 counters which can be set as CPU watchdog counters. This driver uses the second counter (ID 1, counting from 0) (Marvell's Linux also uses second counter by default). In the future it could be adapted to use other counters, with definition in the device tree. Signed-off-by: Marek Behun Tested-by: Gregory CLEMENT --- arch/arm64/boot/dts/marvell/armada-37xx.dtsi | 7 + drivers/watchdog/Kconfig | 11 + drivers/watchdog/Makefile | 1 + drivers/watchdog/armada_37xx_wdt.c | 356 +++++++++++++++++++++++++++ 4 files changed, 375 insertions(+) create mode 100644 drivers/watchdog/armada_37xx_wdt.c diff --git a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi index 8c0cf7efac65..fcd2186e869c 100644 --- a/arch/arm64/boot/dts/marvell/armada-37xx.dtsi +++ b/arch/arm64/boot/dts/marvell/armada-37xx.dtsi @@ -139,6 +139,13 @@ status = "disabled"; }; + wdt: watchdog-timer@8300 { + compatible = "marvell,armada-3700-wdt"; + reg = <0xd064 0x4>, + <0x8300 0x40>; + clocks = <&xtalclk>; + }; + nb_periph_clk: nb-periph-clk@13000 { compatible = "marvell,armada-3700-periph-clock-nb"; reg = <0x13000 0x100>; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c722cbfdc7e6..b2dfafcde741 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -255,6 +255,17 @@ config ARM_SBSA_WATCHDOG To compile this driver as module, choose M here: The module will be called sbsa_gwdt. +config ARMADA_37XX_WATCHDOG + tristate "Armada 37xx watchdog" + depends on ARCH_MVEBU || COMPILE_TEST + depends on ARM64 + select WATCHDOG_CORE + help + Say Y here to include support for the watchdog timer found on + Marvell Armada 37xx SoCs. + To compile this driver as a module, choose M here: the + module will be called armada_37xx_wdt. + config ASM9260_WATCHDOG tristate "Alphascale ASM9260 watchdog" depends on MACH_ASM9260 || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 715a21078e0c..eae72c5fa1e0 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -38,6 +38,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o obj-$(CONFIG_ARM_SBSA_WATCHDOG) += sbsa_gwdt.o +obj-$(CONFIG_ARMADA_37XX_WATCHDOG) += armada_37xx_wdt.o obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o diff --git a/drivers/watchdog/armada_37xx_wdt.c b/drivers/watchdog/armada_37xx_wdt.c new file mode 100644 index 000000000000..99145168e93e --- /dev/null +++ b/drivers/watchdog/armada_37xx_wdt.c @@ -0,0 +1,356 @@ +/* + * drivers/watchdog/armada_37xx_wdt.c + * + * Watchdog driver for Marvell Armada 37xx SoCs + * + * Author: Marek Behun + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * There are four counters that can be used for watchdog on Armada 37xx. + * The adresses for counter control registers are register base plus ID*0x10, + * where ID is 0, 1, 2 or 3. + * In this driver we use ID 1. Marvell's Linux also uses this ID by default, + * and the U-Boot driver written simultaneosly by the same author as this + * driver also uses ID 1. + * Maybe in the future we could change this driver to support other counters, + * depending on the device tree, but I don't think this is necessary. + * + * Note that CNTR_ID cannot be 3, because the third counter is an increment + * counter, and this driver is written to support decrementing counters only. + */ + +#define CNTR_ID 1 + +#define CNTR_CTRL (CNTR_ID * 0x10) +#define CNTR_CTRL_ENABLE 0x0001 +#define CNTR_CTRL_ACTIVE 0x0002 +#define CNTR_CTRL_MODE_MASK 0x000c +#define CNTR_CTRL_MODE_ONESHOT 0x0000 +#define CNTR_CTRL_PRESCALE_MASK 0xff00 +#define CNTR_CTRL_PRESCALE_MIN 2 +#define CNTR_CTRL_PRESCALE_SHIFT 8 + +#define CNTR_COUNT_LOW (CNTR_CTRL + 0x4) +#define CNTR_COUNT_HIGH (CNTR_CTRL + 0x8) + +#define WDT_TIMER_SELECT_MASK 0xf +#define WDT_TIMER_SELECT (1 << CNTR_ID) + +#define WATCHDOG_TIMEOUT 120 + +static unsigned int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +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) ")"); + +struct armada_37xx_watchdog { + struct watchdog_device wdt; + void __iomem *sel_reg; + void __iomem *reg; + u64 timeout; /* in clock ticks */ + unsigned long clk_rate; + struct clk *clk; +}; + +static u64 get_counter_value(struct armada_37xx_watchdog *dev) +{ + u64 val; + + val = readl(dev->reg + CNTR_COUNT_HIGH); + val = (val << 32) | readl(dev->reg + CNTR_COUNT_LOW); + + return val; +} + +static void set_counter_value(struct armada_37xx_watchdog *dev) +{ + writel(dev->timeout & 0xffffffff, dev->reg + CNTR_COUNT_LOW); + writel(dev->timeout >> 32, dev->reg + CNTR_COUNT_HIGH); +} + +static void armada_37xx_wdt_counter_enable(struct armada_37xx_watchdog *dev) +{ + u32 reg; + + reg = readl(dev->reg + CNTR_CTRL); + reg |= CNTR_CTRL_ENABLE; + writel(reg, dev->reg + CNTR_CTRL); +} + +static void armada_37xx_wdt_counter_disable(struct armada_37xx_watchdog *dev) +{ + u32 reg; + + reg = readl(dev->reg + CNTR_CTRL); + reg &= ~CNTR_CTRL_ENABLE; + writel(reg, dev->reg + CNTR_CTRL); +} + +static int armada_37xx_wdt_ping(struct watchdog_device *wdt) +{ + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + + armada_37xx_wdt_counter_disable(dev); + set_counter_value(dev); + armada_37xx_wdt_counter_enable(dev); + + return 0; +} + +static unsigned int armada_37xx_wdt_get_timeleft(struct watchdog_device *wdt) +{ + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + unsigned int res; + + res = get_counter_value(dev) * CNTR_CTRL_PRESCALE_MIN / dev->clk_rate; + + return res; +} + +static int armada_37xx_wdt_set_timeout(struct watchdog_device *wdt, + unsigned int timeout) +{ + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + + wdt->timeout = timeout; + + /* + * Compute the timeout in clock rate. We use smallest possible prescaler, + * which divides the clock rate by 2 (CNTR_CTRL_PRESCALE_MIN). + */ + dev->timeout = (u64)dev->clk_rate * timeout / CNTR_CTRL_PRESCALE_MIN; + + return 0; +} + +static bool armada_37xx_wdt_is_running(struct armada_37xx_watchdog *dev) +{ + u32 reg; + + reg = readl(dev->sel_reg); + if ((reg & WDT_TIMER_SELECT_MASK) != WDT_TIMER_SELECT) + return false; + + reg = readl(dev->reg + CNTR_CTRL); + return !!(reg & CNTR_CTRL_ACTIVE); +} + +static int armada_37xx_wdt_start(struct watchdog_device *wdt) +{ + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + u32 reg; + + reg = readl(dev->reg + CNTR_CTRL); + + if (reg & CNTR_CTRL_ACTIVE) + return -EBUSY; + + /* set mode */ + reg = (reg & ~CNTR_CTRL_MODE_MASK) | CNTR_CTRL_MODE_ONESHOT; + + /* set prescaler to the min value of 2 */ + reg &= ~CNTR_CTRL_PRESCALE_MASK; + reg |= CNTR_CTRL_PRESCALE_MIN << CNTR_CTRL_PRESCALE_SHIFT; + + writel(reg, dev->reg + CNTR_CTRL); + + set_counter_value(dev); + + writel(WDT_TIMER_SELECT, dev->sel_reg); + armada_37xx_wdt_counter_enable(dev); + + return 0; +} + +static int armada_37xx_wdt_stop(struct watchdog_device *wdt) +{ + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + + armada_37xx_wdt_counter_disable(dev); + writel(0, dev->sel_reg); + + return 0; +} + +static const struct watchdog_info armada_37xx_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Armada 37xx Watchdog", +}; + +static const struct watchdog_ops armada_37xx_wdt_ops = { + .owner = THIS_MODULE, + .start = armada_37xx_wdt_start, + .stop = armada_37xx_wdt_stop, + .ping = armada_37xx_wdt_ping, + .set_timeout = armada_37xx_wdt_set_timeout, + .get_timeleft = armada_37xx_wdt_get_timeleft, +}; + +static int armada_37xx_wdt_probe(struct platform_device *pdev) +{ + struct armada_37xx_watchdog *dev; + struct resource *res; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(struct armada_37xx_watchdog), + GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->wdt.info = &armada_37xx_wdt_info; + dev->wdt.ops = &armada_37xx_wdt_ops; + dev->wdt.min_timeout = 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + dev->sel_reg = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -ENODEV; + dev->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + + /* init clock */ + dev->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); + if (ret) { + clk_put(dev->clk); + return ret; + } + + dev->clk_rate = clk_get_rate(dev->clk); + + /* + * Since the timeout in seconds is given as 32 bit unsigned int, and + * the counters hold 64 bit values, even after multiplication by clock + * rate the counter can hold timeout of UINT_MAX seconds. + */ + dev->wdt.min_timeout = 0; + dev->wdt.max_timeout = UINT_MAX; + dev->wdt.parent = &pdev->dev; + + /* default value, possibly override by module parameter or dtb */ + dev->wdt.timeout = WATCHDOG_TIMEOUT; + watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); + + platform_set_drvdata(pdev, &dev->wdt); + watchdog_set_drvdata(&dev->wdt, dev); + + armada_37xx_wdt_set_timeout(&dev->wdt, dev->wdt.timeout); + + if (armada_37xx_wdt_is_running(dev)) + set_bit(WDOG_HW_RUNNING, &dev->wdt.status); + else + armada_37xx_wdt_stop(&dev->wdt); + + watchdog_set_nowayout(&dev->wdt, nowayout); + ret = watchdog_register_device(&dev->wdt); + if (ret) + goto disable_clk; + + dev_info(&pdev->dev, "Initial timeout %d sec%s\n", + dev->wdt.timeout, nowayout ? ", nowayout" : ""); + + return 0; + +disable_clk: + clk_disable_unprepare(dev->clk); + clk_put(dev->clk); + return ret; +} + +static int armada_37xx_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdt = platform_get_drvdata(pdev); + struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt); + + watchdog_unregister_device(wdt); + clk_disable_unprepare(dev->clk); + clk_put(dev->clk); + return 0; +} + +static void armada_37xx_wdt_shutdown(struct platform_device *pdev) +{ + struct watchdog_device *wdt = platform_get_drvdata(pdev); + + armada_37xx_wdt_stop(wdt); +} + +static int __maybe_unused armada_37xx_wdt_suspend(struct device *dev) +{ + struct watchdog_device *wdt = dev_get_drvdata(dev); + + return armada_37xx_wdt_stop(wdt); +} + +static int __maybe_unused armada_37xx_wdt_resume(struct device *dev) +{ + struct watchdog_device *wdt = dev_get_drvdata(dev); + + if (watchdog_active(wdt)) + return armada_37xx_wdt_start(wdt); + + return 0; +} + +static const struct dev_pm_ops armada_37xx_wdt_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(armada_37xx_wdt_suspend, + armada_37xx_wdt_resume) +}; + +#ifdef CONFIG_OF +static const struct of_device_id armada_37xx_wdt_match[] = { + { .compatible = "marvell,armada-3700-wdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, armada_37xx_wdt_match); +#endif + +static struct platform_driver armada_37xx_wdt_driver = { + .probe = armada_37xx_wdt_probe, + .remove = armada_37xx_wdt_remove, + .shutdown = armada_37xx_wdt_shutdown, + .driver = { + .name = "armada_37xx_wdt", + .of_match_table = of_match_ptr(armada_37xx_wdt_match), + .pm = &armada_37xx_wdt_dev_pm_ops, + }, +}; + +module_platform_driver(armada_37xx_wdt_driver); + +MODULE_AUTHOR("Marek Behun "); +MODULE_DESCRIPTION("Armada 37xx CPU Watchdog"); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:armada_37xx_wdt");