[PATCH] [v2] watchdog: introduce the ARM64 SBSA watchdog driver

Timur Tabi
2015-05-14T16:52:33Z

The ARM Server Base System Architecture is a specification for ARM-based
server systems. Among other things, it defines the behavior and register
interface for a watchdog timer.

Signed-off-by: Timur Tabi <timur@codeaurora.org>
---

[v2]
added PM support
now depends on Fu Wei's "ACPI: import watchdog info of GTDT into platform device"
patch (GTDT platform code removed)
driver compiles as a module
replaced ARM-specific timer references
hardware registers now marked as little-endian
add OF support

drivers/watchdog/Kconfig | 9 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/arm_sbsa_wdt.c | 303 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 313 insertions(+)
create mode 100644 drivers/watchdog/arm_sbsa_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index e5e7c55..b97919f 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -514,6 +514,15 @@ config MEDIATEK_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called mtk_wdt.

+config ARM_SBSA_WDT
+ tristate "ARM Server Base System Architecture watchdog"
+ depends on ARM64 || COMPILE_TEST
+ depends on ARCH_TIMER
+ select WATCHDOG_CORE
+ help
+ Say Y here to include watchdog timer support for ARM Server Base
+ System Architecture (SBSA) systems.
+
# AVR32 Architecture

config AT32AP700X_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 5c19294..063ab8c 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_ARM_SBSA_WDT) += arm_sbsa_wdt.o

# AVR32 Architecture
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
diff --git a/drivers/watchdog/arm_sbsa_wdt.c b/drivers/watchdog/arm_sbsa_wdt.c
new file mode 100644
index 0000000..5af35bf
--- /dev/null
+++ b/drivers/watchdog/arm_sbsa_wdt.c
@@ -0,0 +1,303 @@
+/*
+ * Watchdog driver for SBSA-compliant watchdog timers
+ *
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * ARM Server Base System Architecture watchdog driver.
+ *
+ * Register descriptions are taken from the ARM Server Base System
+ * Architecture document (ARM-DEN-0029)
+ */
+
+#define pr_fmt(fmt) "sbsa-gwdt: " fmt
+
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/device.h>
+#include <linux/io.h>
+
+#include <linux/irqdomain.h>
+#include <linux/interrupt.h>
+#include <linux/reboot.h>
+
+#include <asm/arch_timer.h>
+#include <linux/acpi.h>
+
+/* Watchdog Interface Identification Registers */
+struct arm_sbsa_watchdog_ident {
+ __le32 w_iidr; /* Watchdog Interface Identification Register */
+ uint8_t res2[0xFE8 - 0xFD0];
+ __le32 w_pidr2; /* Peripheral ID2 Register */
+};
+
+/* Watchdog Refresh Frame */
+struct arm_sbsa_watchdog_refresh {
+ __le32 wrr; /* Watchdog Refresh Register */
+ uint8_t res1[0xFCC - 0x004];
+ struct arm_sbsa_watchdog_ident ident;
+};
+
+/* Watchdog Control Frame */
+struct arm_sbsa_watchdog_control {
+ __le32 wcs;
+ __le32 res1;
+ __le32 wor;
+ __le32 res2;
+ __le64 wcv;
+ uint8_t res3[0xFCC - 0x018];
+ struct arm_sbsa_watchdog_ident ident;
+};
+
+struct arm_sbsa_watchdog_data {
+ struct watchdog_device wdev;
+ unsigned int pm_status;
+ struct arm_sbsa_watchdog_refresh __iomem *refresh;
+ struct arm_sbsa_watchdog_control __iomem *control;
+};
+
+static int arm_sbsa_wdt_start(struct watchdog_device *wdev)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+ /* Writing to the control register will also reset the counter */
+ writel(cpu_to_le32(1), &data->control->wcs);
+
+ return 0;
+}
+
+static int arm_sbsa_wdt_stop(struct watchdog_device *wdev)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+ writel(cpu_to_le32(0), &data->control->wcs);
+
+ return 0;
+}
+
+static int arm_sbsa_wdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int timeout)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+ uint32_t wor;
+
+ wdev->timeout = timeout;
+
+ wor = arch_timer_get_cntfrq() * wdev->timeout;
+ writel(cpu_to_le32(wor), &data->control->wor);
+
+ return 0;
+}
+
+static int arm_sbsa_wdt_ping(struct watchdog_device *wdev)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+ writel(cpu_to_le32(1), &data->refresh->wrr);
+
+ return 0;
+}
+
+static unsigned int arm_sbsa_wdt_status(struct watchdog_device *wdev)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+
+ return le32_to_cpu(readl(&data->control->wcs) & 1) << WDOG_ACTIVE;
+}
+
+static unsigned int arm_sbsa_wdt_timeleft(struct watchdog_device *wdev)
+{
+ struct arm_sbsa_watchdog_data *data =
+ container_of(wdev, struct arm_sbsa_watchdog_data, wdev);
+ uint64_t wcv = le64_to_cpu(readq(&data->control->wcv));
+ uint64_t diff = wcv - arch_counter_get_cntvct();
+
+ return diff / arch_timer_get_cntfrq();
+}
+
+static irqreturn_t arm_sbsa_wdt_interrupt(int irq, void *p)
+{
+ /*
+ * The WS0 interrupt occurs after the first timeout, so we attempt
+ * a manual reboot. If this doesn't work, the WS1 timeout will
+ * cause a hardware reset.
+ */
+ pr_crit("Initiating system reboot\n");
+ emergency_restart();
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM_SLEEP
+/*
+ * Disable watchdog if it is active during suspend
+ */
+static int arm_sbsa_wdt_suspend(struct device *dev)
+{
+ struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+ data->pm_status = le32_to_cpu(readl(&data->control->wcs)) & 1;
+
+ if (data->pm_status)
+ writel(cpu_to_le32(0), &data->control->wcs);
+
+ return 0;
+}
+
+/*
+ * Enable watchdog and configure it if necessary
+ */
+static int arm_sbsa_wdt_resume(struct device *dev)
+{
+ struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+ if (data->pm_status)
+ writel(cpu_to_le32(0), &data->control->wcs);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops arm_sbsa_wdt_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(arm_sbsa_wdt_suspend, arm_sbsa_wdt_resume)
+};
+
+static struct watchdog_info arm_sbsa_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "ARM SBSA watchdog",
+};
+
+static struct watchdog_ops arm_sbsa_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = arm_sbsa_wdt_start,
+ .stop = arm_sbsa_wdt_stop,
+ .ping = arm_sbsa_wdt_ping,
+ .set_timeout = arm_sbsa_wdt_set_timeout,
+ .status = arm_sbsa_wdt_status,
+ .get_timeleft = arm_sbsa_wdt_timeleft,
+};
+
+static int __init arm_sbsa_wdt_probe(struct platform_device *pdev)
+{
+ struct arm_sbsa_watchdog_data *data;
+ struct resource *res;
+ uint32_t iidr;
+ int irq, ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "control");
+ data->control = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->control))
+ return PTR_ERR(data->control);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "refresh");
+ data->refresh = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(data->refresh))
+ return PTR_ERR(data->refresh);
+
+ iidr = le32_to_cpu(readl(&data->control->ident.w_iidr));
+
+ /* We only support architecture version 0 */
+ if (((iidr >> 16) & 0xf) != 0) {
+ dev_info(&pdev->dev,
+ "only architecture version 0 is supported\n");
+ return -ENODEV;
+ }
+
+ irq = platform_get_irq_byname(pdev, "ws0");
+ if (irq < 0) {
+ dev_err(&pdev->dev, "could not get interrupt\n");
+ return irq;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, arm_sbsa_wdt_interrupt,
+ 0, pdev->name, NULL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq %i (ret=%i)\n",
+ irq, ret);
+ return ret;
+ }
+
+ data->wdev.info = &arm_sbsa_wdt_info;
+ data->wdev.ops = &arm_sbsa_wdt_ops;
+ data->wdev.min_timeout = 1;
+ data->wdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+ /* Calculate the maximum timeout in seconds that we can support */
+ data->wdev.max_timeout = U32_MAX / arch_timer_get_cntfrq();
+
+ watchdog_set_drvdata(&data->wdev, data);
+
+ /*
+ * Bits [15:12] are an implementation-defined revision number
+ * for the component.
+ */
+ arm_sbsa_wdt_info.firmware_version = (iidr >> 12) & 0xf;
+
+ ret = watchdog_register_device(&data->wdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "could not register watchdog device (ret=%i)\n", ret);
+ return ret;
+ }
+
+ dev_dbg(&pdev->dev, "implementer code is %03x\n",
+ (iidr & 0xf00) >> 1 | (iidr & 0x7f));
+ dev_info(&pdev->dev, "maximum timeout is %u seconds\n",
+ data->wdev.max_timeout);
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+}
+
+static int __exit arm_sbsa_wdt_remove(struct platform_device *pdev)
+{
+ struct arm_sbsa_watchdog_data *data = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&data->wdev);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id arm_sbsa_wdt_of_match[] = {
+ { .compatible = "arm,sbsa-gwdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, arm_sbsa_wdt_of_match);
+#endif
+
+static struct platform_driver arm_sbsa_wdt_driver = {
+ .driver = {
+ .name = "sbsa-gwdt",
+ .owner = THIS_MODULE,
+ .pm = &arm_sbsa_wdt_pm_ops,
+ .of_match_table = of_match_ptr(arm_sbsa_wdt_of_match),
+ },
+ .probe = arm_sbsa_wdt_probe,
+ .remove = __exit_p(arm_sbsa_wdt_remove),
+};
+
+module_platform_driver(arm_sbsa_wdt_driver);
+
+MODULE_DESCRIPTION("ARM Server Base System Architecture Watchdog Driver");
+MODULE_LICENSE("GPL v2");
--
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project.