watchdog: Add a driver for the Apple watchdog
authorMark Kettenis <kettenis@openbsd.org>
Wed, 12 Jan 2022 18:55:15 +0000 (19:55 +0100)
committerStefan Roese <sr@denx.de>
Thu, 13 Jan 2022 05:55:46 +0000 (06:55 +0100)
This driver supports the watchdog timer found on Apple's M1 SoC.
On systems that use these SoC, the watchdog timer is the primary
way to reboot the system.

Signed-off-by: Mark Kettenis <kettenis@openbsd.org>
Reviewed-by: Sven Peter <sven@svenpeter.dev>
Reviewed-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Stefan Roese <sr@denx.de>
Tested-on: Apple M1 Macbook
Tested-by: Simon Glass <sjg@chromium.org>
MAINTAINERS
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/apple_wdt.c [new file with mode: 0644]

index f4bd3d7..6ae81c5 100644 (file)
@@ -116,6 +116,7 @@ F:  arch/arm/mach-apple/
 F:     configs/apple_m1_defconfig
 F:     drivers/iommu/apple_dart.c
 F:     drivers/pinctrl/pinctrl-apple.c
+F:     drivers/watchdog/apple_wdt.c
 F:     include/configs/apple.h
 
 ARM
index 1177f17..cabac29 100644 (file)
@@ -81,6 +81,15 @@ config WDT
          What exactly happens when the timer expires is up to a particular
          device/driver.
 
+config WDT_APPLE
+       bool "Apple watchdog timer support"
+       depends on WDT
+       default y if ARCH_APPLE
+       help
+         Enable support for the watchdog timer on Apple SoCs.
+         The watchdog will perform a full SoC reset resulting in a
+         reboot of the entire system.
+
 config WDT_ARMADA_37XX
        bool "Marvell Armada 37xx watchdog timer support"
        depends on WDT && ARMADA_3700
index fa7ce58..6d2b382 100644 (file)
@@ -17,6 +17,7 @@ obj-$(CONFIG_DESIGNWARE_WATCHDOG) += designware_wdt.o
 obj-$(CONFIG_ULP_WATCHDOG) += ulp_wdog.o
 obj-$(CONFIG_$(SPL_TPL_)WDT) += wdt-uclass.o
 obj-$(CONFIG_WDT_SANDBOX) += sandbox_wdt.o
+obj-$(CONFIG_WDT_APPLE) += apple_wdt.o
 obj-$(CONFIG_WDT_ARMADA_37XX) += armada-37xx-wdt.o
 obj-$(CONFIG_WDT_ASPEED) += ast_wdt.o
 obj-$(CONFIG_WDT_AST2600) += ast2600_wdt.o
diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c
new file mode 100644 (file)
index 0000000..c7307f4
--- /dev/null
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2021 Mark Kettenis <kettenis@openbsd.org>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <wdt.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+
+#define APPLE_WDT_CUR_TIME             0x10
+#define APPLE_WDT_BARK_TIME            0x14
+#define APPLE_WDT_CTRL                 0x1c
+#define  APPLE_WDT_CTRL_RESET_EN       BIT(2)
+
+struct apple_wdt_priv {
+       void *base;
+       ulong clk_rate;
+};
+
+static int apple_wdt_reset(struct udevice *dev)
+{
+       struct apple_wdt_priv *priv = dev_get_priv(dev);
+
+       writel(0, priv->base + APPLE_WDT_CUR_TIME);
+
+       return 0;
+}
+
+static int apple_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
+{
+       struct apple_wdt_priv *priv = dev_get_priv(dev);
+       u64 timeout;
+
+       timeout = (timeout_ms * priv->clk_rate) / 1000;
+       if (timeout > U32_MAX)
+               return -EINVAL;
+
+       writel(0, priv->base + APPLE_WDT_CUR_TIME);
+       writel(timeout, priv->base + APPLE_WDT_BARK_TIME);
+       writel(APPLE_WDT_CTRL_RESET_EN, priv->base + APPLE_WDT_CTRL);
+
+       return 0;
+}
+
+static int apple_wdt_stop(struct udevice *dev)
+{
+       struct apple_wdt_priv *priv = dev_get_priv(dev);
+
+       writel(0, priv->base + APPLE_WDT_CTRL);
+
+       return 0;
+}
+
+static int apple_wdt_expire_now(struct udevice *dev, ulong flags)
+{
+       int ret;
+
+       ret = apple_wdt_start(dev, 0, flags);
+       if (ret)
+               return ret;
+
+       /*
+        * It can take up to 25ms until the SoC actually resets, so
+        * wait 50ms just to be sure.
+        */
+       mdelay(50);
+
+       return 0;
+}
+
+static const struct wdt_ops apple_wdt_ops = {
+       .reset = apple_wdt_reset,
+       .start = apple_wdt_start,
+       .stop = apple_wdt_stop,
+       .expire_now = apple_wdt_expire_now,
+};
+
+static const struct udevice_id apple_wdt_ids[] = {
+       { .compatible = "apple,wdt" },
+       { /* sentinel */ }
+};
+
+static int apple_wdt_probe(struct udevice *dev)
+{
+       struct apple_wdt_priv *priv = dev_get_priv(dev);
+       struct clk clk;
+       int ret;
+
+       priv->base = dev_read_addr_ptr(dev);
+       if (!priv->base)
+               return -EINVAL;
+
+       ret = clk_get_by_index(dev, 0, &clk);
+       if (ret)
+               return ret;
+
+       ret = clk_enable(&clk);
+       if (ret)
+               return ret;
+
+       priv->clk_rate = clk_get_rate(&clk);
+
+       return 0;
+}
+
+U_BOOT_DRIVER(apple_wdt) = {
+       .name = "apple_wdt",
+       .id = UCLASS_WDT,
+       .of_match = apple_wdt_ids,
+       .priv_auto = sizeof(struct apple_wdt_priv),
+       .probe = apple_wdt_probe,
+       .ops = &apple_wdt_ops,
+};