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 <[email protected]>
---

[v4]
Removed COMPILE_TEST
pm_status is now bool
removed some #includes
use do_div instead
display arch version if unsupported
remove watchdog_set_drvdata

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

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index e5e7c55..7720190 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
+       depends on ARM_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..12ed520e
--- /dev/null
+++ b/drivers/watchdog/arm_sbsa_wdt.c
@@ -0,0 +1,295 @@
+/*
+ * 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/interrupt.h>
+#include <linux/reboot.h>
+
+#include <asm/arch_timer.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;
+       bool 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(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(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);
+
+       wdev->timeout = timeout;
+       writel(arch_timer_get_cntfrq() * wdev->timeout, &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(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 (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 diff = readq(&data->control->wcv) - arch_counter_get_cntvct();
+
+       do_div(diff, arch_timer_get_cntfrq());
+
+       return diff;
+}
+
+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;
+}
+
+/*
+ * Disable watchdog if it is active during suspend
+ */
+static int __maybe_unused arm_sbsa_wdt_suspend(struct device *dev)
+{
+       struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+       data->pm_status = !!(readl(&data->control->wcs) & 1);
+
+       if (data->pm_status)
+               writel(0, &data->control->wcs);
+
+       return 0;
+}
+
+/*
+ * Enable watchdog and configure it if necessary
+ */
+static int __maybe_unused arm_sbsa_wdt_resume(struct device *dev)
+{
+       struct arm_sbsa_watchdog_data *data = dev_get_drvdata(dev);
+
+       if (data->pm_status)
+               writel(1, &data->control->wcs);
+
+       return 0;
+}
+
+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;
+       unsigned int arch;
+       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);
+
+       /* We only support architecture version 0 */
+       iidr = readl(&data->control->ident.w_iidr);
+       arch = (iidr >> 16) & 0xf;
+       if (arch != 0) {
+               dev_err(&pdev->dev,
+                        "architecture version %u is not supported\n", arch);
+               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();
+
+       /*
+        * 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.

--
To unsubscribe from this list: send the line "unsubscribe linux-watchdog" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to