mirror of
https://github.com/armbian/build.git
synced 2025-09-19 12:41:39 +02:00
908 lines
22 KiB
Diff
908 lines
22 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Patrick Yavitz <pyavitz@armbian.com>
|
|
Date: Fri, 21 Jun 2024 11:54:06 -0400
|
|
Subject: add spacemit patch set
|
|
|
|
source: https://gitee.com/bianbu-linux/linux-6.1
|
|
|
|
Signed-off-by: Patrick Yavitz <pyavitz@armbian.com>
|
|
---
|
|
drivers/watchdog/Kconfig | 14 +
|
|
drivers/watchdog/Makefile | 1 +
|
|
drivers/watchdog/k1x_wdt.c | 835 ++++++++++
|
|
fs/btrfs/sysfs.c | 2 +-
|
|
4 files changed, 851 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/watchdog/Kconfig
|
|
+++ b/drivers/watchdog/Kconfig
|
|
@@ -2204,4 +2204,18 @@ config KEEMBAY_WATCHDOG
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called keembay_wdt.
|
|
|
|
+config SPACEMIT_WATCHDOG
|
|
+ tristate "Spacemit-k1x SoC Watchdog"
|
|
+ depends on SOC_SPACEMIT_K1X
|
|
+ select WATCHDOG_CORE
|
|
+ help
|
|
+ spacemit k1x plat SoC Watchdog timer. This will reboot your system when
|
|
+ the timeout is reached.
|
|
+
|
|
+config K1X_WDT_TEST
|
|
+ bool "Support K1X watchdog test"
|
|
+ depends on SOC_SPACEMIT_K1X && SPACEMIT_WATCHDOG
|
|
+ help
|
|
+ This will enable K1X watchdog timer test
|
|
+
|
|
endif # WATCHDOG
|
|
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/watchdog/Makefile
|
|
+++ b/drivers/watchdog/Makefile
|
|
@@ -37,6 +37,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
|
|
# ALPHA Architecture
|
|
|
|
# ARM Architecture
|
|
+obj-$(CONFIG_SPACEMIT_WATCHDOG) += k1x_wdt.o
|
|
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
|
|
diff --git a/drivers/watchdog/k1x_wdt.c b/drivers/watchdog/k1x_wdt.c
|
|
new file mode 100644
|
|
index 000000000000..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/watchdog/k1x_wdt.c
|
|
@@ -0,0 +1,835 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * spacemit-k1x watchdog driver
|
|
+ *
|
|
+ * Copyright (C) 2023 Spacemit
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/timer.h>
|
|
+#include <linux/miscdevice.h> /* for MODULE_ALIAS_MISCDEV */
|
|
+#include <linux/watchdog.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/clk.h>
|
|
+#include <linux/reset.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/io.h>
|
|
+#include <linux/cpufreq.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/hrtimer.h>
|
|
+#include <asm/cacheflush.h>
|
|
+
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_address.h>
|
|
+#include <linux/of_device.h>
|
|
+#include <linux/reboot.h>
|
|
+
|
|
+/* Watchdog Timer Registers Offset */
|
|
+#define WDT_WMER (0x00b8)
|
|
+#define WDT_WMR (0x00bc)
|
|
+#define WDT_WVR (0x00cc)
|
|
+#define WDT_WCR (0x00c8)
|
|
+#define WDT_WSR (0x00c0)
|
|
+#define WDT_WFAR (0x00b0)
|
|
+#define WDT_WSAR (0x00b4)
|
|
+#define WDT_WICR (0x00c4)
|
|
+
|
|
+#define CONFIG_SPACEMIT_WATCHDOG_ATBOOT (0)
|
|
+/* default timeout is 60s */
|
|
+#define CONFIG_SPACEMIT_WATCHDOG_DEFAULT_TIME (60)
|
|
+#define SPACEMIT_WATCHDOG_MAX_TIMEOUT (255)
|
|
+#define SPACEMIT_WATCHDOG_EXPIRE_TIME (100)
|
|
+/* touch watchdog every 30s */
|
|
+#define SPACEMIT_WATCHDOG_FEED_TIMEOUT (30)
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+#define K1X_WATCHDOG_IRQ_EXPIRE_TIME (16)
|
|
+#define K1X_WATCHDOG_IRQ_TEST_TIME (64)
|
|
+#define K1X_WATCHDOG_IRQ_TEST_ID 0
|
|
+#define K1X_WATCHDOG_RESET_TEST_ID 1
|
|
+#endif
|
|
+
|
|
+#define MPMU_APRR (0x1020)
|
|
+#define MPMU_APRR_WDTR (1<<4)
|
|
+#define DEFAULT_SHIFT (8)
|
|
+/*
|
|
+ * MPMU_APSR is a dummy reg which is used to handle reboot
|
|
+ * cmds. Its layout is:
|
|
+ * bit0~7: untouchable
|
|
+ * bit8~11: set to 0x1 when normal boot with no parameter
|
|
+ * set to 0x5 for other valid cmds.
|
|
+ */
|
|
+#define MPMU_ARSR (0x1028)
|
|
+#define MPMU_ARSR_REBOOT_CMD(x) ((x) << 8)
|
|
+#define MPMU_ARSR_SWR_MASK (0xf << 8)
|
|
+#define REBOOT_CMD_NORMAL 0x1
|
|
+#define REBOOT_CMD_VALID 0x5
|
|
+
|
|
+static bool nowayout = WATCHDOG_NOWAYOUT ? true : false;
|
|
+static spinlock_t reboot_lock;
|
|
+static DEFINE_MUTEX(wdt_clk_lock);
|
|
+
|
|
+phys_addr_t reboot_cmd_mem = 0;
|
|
+uint32_t reboot_cmd_size = 0;
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+static int wdt_irq_count;
|
|
+#endif
|
|
+
|
|
+module_param(nowayout, bool, 0);
|
|
+
|
|
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
|
|
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
|
+
|
|
+static struct spa_wdt_info *syscore_info;
|
|
+struct spa_wdt_info {
|
|
+ void __iomem *wdt_base;
|
|
+ void __iomem *mpmu_base;
|
|
+ struct device *dev;
|
|
+ struct clk *clk;
|
|
+ struct reset_control *reset;
|
|
+ struct hrtimer feed_timer;
|
|
+ ktime_t feed_timeout;
|
|
+ spinlock_t wdt_lock;
|
|
+ struct watchdog_device wdt_dev;
|
|
+ int ctrl;
|
|
+ bool wdt_clk_open;
|
|
+ int enable_restart_handler;
|
|
+ struct notifier_block restart_handler;
|
|
+};
|
|
+
|
|
+void spa_wdt_shutdown_reason(char *cmd)
|
|
+{
|
|
+ void __iomem *mpmu_arsr;
|
|
+ u32 reg;
|
|
+
|
|
+ if (!syscore_info) {
|
|
+ pr_err("syscore_info not ready\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!reboot_cmd_mem || !reboot_cmd_size) {
|
|
+ pr_err("No reboot cmd buffer reserved, cmd omitted!\n");
|
|
+ cmd = NULL;
|
|
+ }
|
|
+
|
|
+ if (cmd) {
|
|
+ if ((strlen(cmd) + 1) > reboot_cmd_size) {
|
|
+ pr_err("Reboot cmd len(%lu bytes) oversizes reserved mem (%d bytes), \
|
|
+ cmd omitted!\n", strlen(cmd) + 1, reboot_cmd_size);
|
|
+ cmd = NULL;
|
|
+ } else {
|
|
+ /* save cmd to reserved memory */
|
|
+ memcpy(phys_to_virt(reboot_cmd_mem), cmd, strlen(cmd) + 1);
|
|
+ printk("cmd = %s\n", cmd);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ //set reboot flag
|
|
+ mpmu_arsr = syscore_info->mpmu_base + MPMU_ARSR;
|
|
+ reg = readl(mpmu_arsr);
|
|
+ reg &= ~MPMU_ARSR_SWR_MASK;
|
|
+ if (!cmd)
|
|
+ reg |= MPMU_ARSR_REBOOT_CMD(REBOOT_CMD_NORMAL);
|
|
+ else
|
|
+ reg |= MPMU_ARSR_REBOOT_CMD(REBOOT_CMD_VALID);
|
|
+ writel(reg, mpmu_arsr);
|
|
+}
|
|
+EXPORT_SYMBOL(spa_wdt_shutdown_reason);
|
|
+
|
|
+static inline u32 spa_wdt_read(struct spa_wdt_info *info,
|
|
+ unsigned reg)
|
|
+{
|
|
+ return readl(info->wdt_base + reg);
|
|
+}
|
|
+
|
|
+static inline void spa_wdt_write_access(struct spa_wdt_info *info)
|
|
+{
|
|
+ writel(0xbaba, info->wdt_base + WDT_WFAR);
|
|
+ writel(0xeb10, info->wdt_base + WDT_WSAR);
|
|
+}
|
|
+
|
|
+static inline void spa_wdt_write(struct spa_wdt_info *info,
|
|
+ unsigned reg, u32 val)
|
|
+{
|
|
+ spa_wdt_write_access(info);
|
|
+ writel(val, info->wdt_base + reg);
|
|
+}
|
|
+
|
|
+static int spa_wdt_set_timeout(struct watchdog_device *wdd, unsigned timeout)
|
|
+{
|
|
+ struct spa_wdt_info *info =
|
|
+ container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+ /*
|
|
+ * the wdt timer is 16 bit,
|
|
+ * frequence is 256HZ
|
|
+ */
|
|
+ unsigned int tick = timeout << DEFAULT_SHIFT;
|
|
+ if ((long long)tick > 0xffff) {
|
|
+ dev_info(info->dev, "use default value!\n");
|
|
+ timeout = SPACEMIT_WATCHDOG_MAX_TIMEOUT;
|
|
+ tick = timeout << DEFAULT_SHIFT;
|
|
+ }
|
|
+
|
|
+ spa_wdt_write(info, WDT_WMR, tick);
|
|
+
|
|
+ wdd->timeout = timeout;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void spa_enable_wdt_clk(struct spa_wdt_info *info)
|
|
+{
|
|
+ mutex_lock(&wdt_clk_lock);
|
|
+ if (!info->wdt_clk_open) {
|
|
+ clk_prepare_enable(info->clk);
|
|
+ reset_control_deassert(info->reset);
|
|
+ info->wdt_clk_open = true;
|
|
+ }
|
|
+ mutex_unlock(&wdt_clk_lock);
|
|
+}
|
|
+
|
|
+static void spa_disable_wdt_clk(struct spa_wdt_info *info)
|
|
+{
|
|
+ mutex_lock(&wdt_clk_lock);
|
|
+ if (info->wdt_clk_open) {
|
|
+ clk_disable_unprepare(info->clk);
|
|
+ reset_control_assert(info->reset);
|
|
+ info->wdt_clk_open = false;
|
|
+ }
|
|
+ mutex_unlock(&wdt_clk_lock);
|
|
+}
|
|
+
|
|
+static int spa_wdt_stop(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct spa_wdt_info *info =
|
|
+ container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+ spin_lock(&info->wdt_lock);
|
|
+ dev_dbg(info->dev, "cnt = 0x%x , match = 0x%x\n", spa_wdt_read(info, WDT_WVR), spa_wdt_read(info, WDT_WMR));
|
|
+
|
|
+ /* reset counter */
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+
|
|
+ /* disable WDT */
|
|
+ spa_wdt_write(info, WDT_WMER, 0x0);
|
|
+
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+
|
|
+ msleep(3);
|
|
+
|
|
+ spa_disable_wdt_clk(info);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int spa_wdt_start(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct spa_wdt_info *info =
|
|
+ container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+ void __iomem *mpmu_aprr;
|
|
+ u32 reg;
|
|
+
|
|
+ spa_enable_wdt_clk(info);
|
|
+
|
|
+ spin_lock(&info->wdt_lock);
|
|
+
|
|
+ /* set timeout = 100s */
|
|
+ spa_wdt_set_timeout(&info->wdt_dev,
|
|
+ SPACEMIT_WATCHDOG_EXPIRE_TIME);
|
|
+
|
|
+ /* enable counter and reset/interrupt */
|
|
+ spa_wdt_write(info, WDT_WMER, 0x3);
|
|
+
|
|
+ /* negate hardware reset to the WDT after system reset */
|
|
+ mpmu_aprr = info->mpmu_base + MPMU_APRR;
|
|
+ reg = readl(mpmu_aprr);
|
|
+ reg |= MPMU_APRR_WDTR;
|
|
+ writel(reg, mpmu_aprr);
|
|
+
|
|
+ /* clear previous WDT status */
|
|
+ spa_wdt_write(info, WDT_WSR, 0x0);
|
|
+
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+static int spa_wdt_start_irq(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct spa_wdt_info *info =container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+
|
|
+ spa_enable_wdt_clk(info);
|
|
+
|
|
+ spin_lock(&info->wdt_lock);
|
|
+
|
|
+ /* set timeout = 2/256 s */
|
|
+ spa_wdt_set_timeout(&info->wdt_dev, K1X_WATCHDOG_IRQ_EXPIRE_TIME);
|
|
+ /* enable counter and reset/interrupt */
|
|
+ spa_wdt_write(info, WDT_WMER, 0x1);
|
|
+
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void spa_wdt_stop_irq(struct watchdog_device *wdd)
|
|
+{
|
|
+ struct spa_wdt_info *info =container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+
|
|
+ spin_lock(&info->wdt_lock);
|
|
+
|
|
+ /* reset counter */
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+
|
|
+ /* disable WDT */
|
|
+ spa_wdt_write(info, WDT_WMER, 0x0);
|
|
+
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+
|
|
+ msleep(3);
|
|
+
|
|
+ spa_disable_wdt_clk(info);
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int spa_wdt_ping(struct watchdog_device *wdd)
|
|
+{
|
|
+ int ret = 0;
|
|
+
|
|
+ struct spa_wdt_info *info =
|
|
+ container_of(wdd, struct spa_wdt_info, wdt_dev);
|
|
+
|
|
+ spin_lock(&reboot_lock);
|
|
+ spin_lock(&info->wdt_lock);
|
|
+
|
|
+ /* reset counter */
|
|
+ if (wdd->timeout > 0) {
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+ } else
|
|
+ ret = -EINVAL;
|
|
+
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+ spin_unlock(&reboot_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING)
|
|
+
|
|
+static const struct watchdog_info spa_wdt_ident = {
|
|
+ .options = OPTIONS,
|
|
+ .firmware_version = 0,
|
|
+ .identity = "K1X Watchdog",
|
|
+};
|
|
+
|
|
+static struct watchdog_ops spa_wdt_ops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .start = spa_wdt_start,
|
|
+ .stop = spa_wdt_stop,
|
|
+ .ping = spa_wdt_ping,
|
|
+ .set_timeout = spa_wdt_set_timeout,
|
|
+};
|
|
+
|
|
+static struct watchdog_device spa_wdt = {
|
|
+ .info = &spa_wdt_ident,
|
|
+ .ops = &spa_wdt_ops,
|
|
+};
|
|
+
|
|
+static void spa_init_wdt(struct spa_wdt_info *info)
|
|
+{
|
|
+
|
|
+ if (info->ctrl) {
|
|
+ spa_wdt_start(&info->wdt_dev);
|
|
+ hrtimer_start(&info->feed_timer, info->feed_timeout, HRTIMER_MODE_REL);
|
|
+ } else
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+
|
|
+ if (test_bit(WDOG_ACTIVE, &((info->wdt_dev).status)))
|
|
+ spa_wdt_ping(&info->wdt_dev);
|
|
+}
|
|
+
|
|
+static const struct of_device_id spa_wdt_match[] = {
|
|
+ { .compatible = "spacemit,soc-wdt", .data = NULL},
|
|
+ {},
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, spa_wdt_match);
|
|
+
|
|
+static ssize_t wdt_ctrl_show(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct spa_wdt_info *info = dev_get_drvdata(dev);
|
|
+ int s = 0;
|
|
+
|
|
+ s += sprintf(buf, "wdt control: %d\n", info->ctrl);
|
|
+ return s;
|
|
+}
|
|
+
|
|
+static ssize_t wdt_ctrl_store(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t size)
|
|
+{
|
|
+ struct spa_wdt_info *info = dev_get_drvdata(dev);
|
|
+ ssize_t ret = 0;
|
|
+ int ctrl;
|
|
+
|
|
+ if (info == NULL) {
|
|
+ pr_err("device info is empty!\n");
|
|
+ return 0;
|
|
+ }
|
|
+ ret = sscanf(buf, "%d", &ctrl);
|
|
+ if (ret == 0) {
|
|
+ pr_err("sscanf() error, try again\n");
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+ dev_info(dev, "%s: wdt control %s\n",
|
|
+ __func__, (ctrl ? "enabled" : "disabled"));
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ if (ctrl && (info->ctrl == 0)) {
|
|
+ spa_wdt_start(&info->wdt_dev);
|
|
+ hrtimer_start(&info->feed_timer, info->feed_timeout, HRTIMER_MODE_REL);
|
|
+ } else if ((ctrl == 0) && info->ctrl) {
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+ }
|
|
+
|
|
+ info->ctrl = ctrl;
|
|
+ return size;
|
|
+}
|
|
+static DEVICE_ATTR(wdt_ctrl, S_IRUGO | S_IWUSR, wdt_ctrl_show, wdt_ctrl_store);
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+static void spa_wdt_reset_test(struct spa_wdt_info *info)
|
|
+{
|
|
+ void __iomem *mpmu_aprr;
|
|
+ unsigned long flags = 0;
|
|
+ u32 reg;
|
|
+
|
|
+ mpmu_aprr = info->mpmu_base + MPMU_APRR;
|
|
+
|
|
+ spin_lock_irqsave(&info->wdt_lock, flags);
|
|
+ spa_wdt_shutdown_reason(NULL);
|
|
+
|
|
+ spa_wdt_write(info, WDT_WSR, 0x0);
|
|
+ spa_wdt_set_timeout(&info->wdt_dev, 0);
|
|
+ spa_wdt_write(info, WDT_WMER, 0x3);
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+
|
|
+ reg = readl(mpmu_aprr);
|
|
+ reg |= MPMU_APRR_WDTR;
|
|
+ writel(reg, mpmu_aprr);
|
|
+ spin_unlock_irqrestore(&info->wdt_lock, flags);
|
|
+
|
|
+ mdelay(5000);
|
|
+ panic("reboot system failed");
|
|
+
|
|
+ pr_err("reboot system failed: this line shouldn't appear.\n");
|
|
+}
|
|
+
|
|
+static void spa_wdt_irq_test(struct spa_wdt_info *info)
|
|
+{
|
|
+ int expected_irq_count;
|
|
+
|
|
+ wdt_irq_count = 0;
|
|
+ expected_irq_count = K1X_WATCHDOG_IRQ_TEST_TIME / K1X_WATCHDOG_IRQ_EXPIRE_TIME;
|
|
+
|
|
+ /* avoid suspend within 15 seconds */
|
|
+ pm_wakeup_event(info->dev, 15000);
|
|
+ if (info->ctrl) {
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+ info->ctrl = 0;
|
|
+ }
|
|
+ /* start watchdog timer with irq mode */
|
|
+ spa_wdt_start_irq(&info->wdt_dev);
|
|
+
|
|
+ mdelay((K1X_WATCHDOG_IRQ_TEST_TIME) * 1000 / 256 + 50);
|
|
+ /* stop watchdog timer with irq mode */
|
|
+ spa_wdt_stop_irq(&info->wdt_dev);
|
|
+
|
|
+ if (!info->ctrl) {
|
|
+ spa_wdt_start(&info->wdt_dev);
|
|
+ hrtimer_start(&info->feed_timer, info->feed_timeout, HRTIMER_MODE_REL);
|
|
+ info->ctrl = 1;
|
|
+ }
|
|
+ pr_err("irq count: expected(%d), actual(%d) %s\n", expected_irq_count, wdt_irq_count,
|
|
+ (expected_irq_count == wdt_irq_count) ? "PASS" : "FAIL");
|
|
+}
|
|
+
|
|
+static ssize_t wdt_debug_show(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ int s = 0;
|
|
+
|
|
+ s += sprintf(buf, "wdt irq count: %d\n", wdt_irq_count);
|
|
+ return s;
|
|
+}
|
|
+
|
|
+static ssize_t wdt_debug_store(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t size)
|
|
+{
|
|
+ struct spa_wdt_info *info = dev_get_drvdata(dev);
|
|
+ ssize_t ret = 0;
|
|
+ int test_id;
|
|
+
|
|
+ if (info == NULL) {
|
|
+ pr_err("device info is empty!\n");
|
|
+ return 0;
|
|
+ }
|
|
+ ret = sscanf(buf, "%d", &test_id);
|
|
+ if (ret == 0) {
|
|
+ pr_err("sscanf() error, try again\n");
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ if (test_id == K1X_WATCHDOG_IRQ_TEST_ID)
|
|
+ spa_wdt_irq_test(info);
|
|
+ else
|
|
+ spa_wdt_reset_test(info);
|
|
+
|
|
+ return size;
|
|
+}
|
|
+static DEVICE_ATTR(wdt_debug, S_IRUGO | S_IWUSR, wdt_debug_show, wdt_debug_store);
|
|
+
|
|
+static irqreturn_t wdt_irq_handler(int irq, void *data)
|
|
+{
|
|
+ struct spa_wdt_info *info = data;
|
|
+
|
|
+ wdt_irq_count++;
|
|
+ /* clear irq flag */
|
|
+ spin_lock(&info->wdt_lock);
|
|
+ spa_wdt_write(info, WDT_WICR, 0x1);
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+ spin_unlock(&info->wdt_lock);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+#endif
|
|
+
|
|
+static int spa_wdt_restart_handler(struct notifier_block *this, unsigned long mode,
|
|
+ void *cmd)
|
|
+{
|
|
+ struct spa_wdt_info *info = container_of(this, struct spa_wdt_info,
|
|
+ restart_handler);
|
|
+ void __iomem *mpmu_aprr;
|
|
+ u32 reg;
|
|
+
|
|
+ spin_lock(&reboot_lock);
|
|
+ spa_wdt_shutdown_reason(cmd);
|
|
+
|
|
+ spa_enable_wdt_clk(info);
|
|
+
|
|
+ spa_wdt_write(info, WDT_WSR, 0x0);
|
|
+ spa_wdt_write(info, WDT_WMR, 0x1);
|
|
+ spa_wdt_write(info, WDT_WMER, 0x3);
|
|
+ spa_wdt_write(info, WDT_WCR, 0x1);
|
|
+
|
|
+ mpmu_aprr = info->mpmu_base + MPMU_APRR;
|
|
+ reg = readl(mpmu_aprr);
|
|
+ reg |= MPMU_APRR_WDTR;
|
|
+ writel(reg, mpmu_aprr);
|
|
+
|
|
+ mdelay(5000);
|
|
+ panic("reboot system failed");
|
|
+ spin_unlock(&reboot_lock);
|
|
+
|
|
+ pr_err("reboot system failed: this line shouldn't appear.\n");
|
|
+ return NOTIFY_DONE;
|
|
+}
|
|
+
|
|
+static int spa_wdt_dt_init(struct device_node *np, struct device *dev,
|
|
+ struct spa_wdt_info *info)
|
|
+{
|
|
+ if (info == NULL) {
|
|
+ pr_err("watchdog dt is empty!\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ if (of_get_property(np, "spa,wdt-disabled", NULL))
|
|
+ info->ctrl = 0;
|
|
+ else
|
|
+ info->ctrl = 1;
|
|
+
|
|
+ if (of_get_property(np, "spa,wdt-enable-restart-handler", NULL))
|
|
+ info->enable_restart_handler = 1;
|
|
+ else
|
|
+ info->enable_restart_handler = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static enum hrtimer_restart spa_wdt_feed(struct hrtimer *timer)
|
|
+{
|
|
+ struct spa_wdt_info *info = container_of(timer, struct spa_wdt_info, feed_timer);
|
|
+
|
|
+ /* reset counter value */
|
|
+ if (likely(info->ctrl)) {
|
|
+ spa_wdt_ping(&info->wdt_dev);
|
|
+ hrtimer_forward_now(timer, info->feed_timeout);
|
|
+ } else
|
|
+ return HRTIMER_NORESTART;
|
|
+
|
|
+ return HRTIMER_RESTART;
|
|
+}
|
|
+
|
|
+static int spa_wdt_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct device_node *np = pdev->dev.of_node;
|
|
+ struct resource wdt_mem;
|
|
+ struct resource mpmu_mem;
|
|
+ void __iomem *mpmu_arsr;
|
|
+ u32 reg;
|
|
+
|
|
+ int ret;
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+ int irq;
|
|
+#endif
|
|
+ static int is_wdt_reset;
|
|
+ struct spa_wdt_info *info;
|
|
+ info = devm_kzalloc(&pdev->dev, sizeof(struct spa_wdt_info),
|
|
+ GFP_KERNEL);
|
|
+ if (info == NULL) {
|
|
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ info->dev = &pdev->dev;
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+ irq = platform_get_irq(pdev, 0);
|
|
+ ret = devm_request_irq(&pdev->dev, irq, wdt_irq_handler,
|
|
+ IRQF_TIMER | IRQF_IRQPOLL,
|
|
+ "watchdog",
|
|
+ info);
|
|
+ if (ret) {
|
|
+ dev_err(&pdev->dev, "%s: Failed to request irq!\n", __func__);
|
|
+ return ret;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ ret = of_address_to_resource(np, 0, &wdt_mem);
|
|
+ if (ret < 0) {
|
|
+ dev_err(info->dev, "no memory resource specified for WDT\n");
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ info->wdt_base = devm_ioremap(&pdev->dev, wdt_mem.start,
|
|
+ resource_size(&wdt_mem));
|
|
+ if (IS_ERR(info->wdt_base))
|
|
+ return PTR_ERR(info->wdt_base);
|
|
+
|
|
+ ret = of_address_to_resource(np, 1, &mpmu_mem);
|
|
+ if (ret < 0) {
|
|
+ dev_err(info->dev, "no memory resource specified for MPMU\n");
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ info->mpmu_base = devm_ioremap(&pdev->dev,
|
|
+ mpmu_mem.start, resource_size(&mpmu_mem));
|
|
+ if (IS_ERR(info->mpmu_base))
|
|
+ return PTR_ERR(info->mpmu_base);
|
|
+
|
|
+ mpmu_arsr = info->mpmu_base + MPMU_ARSR;
|
|
+ reg = readl(mpmu_arsr);
|
|
+ reg &= ~MPMU_ARSR_SWR_MASK;
|
|
+ writel(reg, mpmu_arsr);
|
|
+
|
|
+ /* get WDT clock */
|
|
+ info->clk = devm_clk_get(info->dev, NULL);
|
|
+ if (IS_ERR(info->clk)) {
|
|
+ dev_err(info->dev, "failed to get WDT clock\n");
|
|
+ return PTR_ERR(info->clk);
|
|
+ }
|
|
+
|
|
+ info->reset = devm_reset_control_get_optional(info->dev,NULL);
|
|
+ if(IS_ERR(info->reset)) {
|
|
+ dev_err(info->dev, "watchdog get reset failed\n");
|
|
+ return PTR_ERR(info->reset);
|
|
+ }
|
|
+ /*
|
|
+ * the writing of some WDT registers must be
|
|
+ * under the condition of that WDT clock is on
|
|
+ */
|
|
+ spa_enable_wdt_clk(info);
|
|
+
|
|
+ /* check before the watchdog is initialized */
|
|
+ is_wdt_reset = spa_wdt_read(info, WDT_WSR);
|
|
+ if (is_wdt_reset)
|
|
+ pr_info("System boots up because of SoC watchdog reset.\n");
|
|
+ else
|
|
+ pr_info("System boots up not because of SoC watchdog reset.\n");
|
|
+
|
|
+ spin_lock_init(&info->wdt_lock);
|
|
+
|
|
+ watchdog_set_nowayout(&spa_wdt, nowayout);
|
|
+
|
|
+ info->wdt_dev = spa_wdt;
|
|
+ ret = watchdog_register_device(&info->wdt_dev);
|
|
+ if (ret) {
|
|
+ dev_err(info->dev, "cannot register watchdog (%d)\n", ret);
|
|
+ goto err_register_fail;
|
|
+ }
|
|
+
|
|
+ info->feed_timeout = ktime_set(SPACEMIT_WATCHDOG_FEED_TIMEOUT, 0);
|
|
+ hrtimer_init(&info->feed_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
+ info->feed_timer.function = spa_wdt_feed;
|
|
+
|
|
+ platform_set_drvdata(pdev, info);
|
|
+
|
|
+ spa_wdt_dt_init(np, info->dev, info);
|
|
+
|
|
+ spa_init_wdt(info);
|
|
+
|
|
+ ret = device_create_file(info->dev, &dev_attr_wdt_ctrl);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&pdev->dev, "device attr create fail: %d\n", ret);
|
|
+ goto err_alloc;
|
|
+ }
|
|
+
|
|
+#ifdef CONFIG_K1X_WDT_TEST
|
|
+ ret = device_create_file(info->dev, &dev_attr_wdt_debug);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&pdev->dev, "device attr create fail: %d\n", ret);
|
|
+ goto err_alloc;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (info->enable_restart_handler) {
|
|
+ info->restart_handler.notifier_call = spa_wdt_restart_handler;
|
|
+ info->restart_handler.priority = 0;
|
|
+ ret = register_restart_handler(&info->restart_handler);
|
|
+ if (ret)
|
|
+ dev_warn(&pdev->dev,
|
|
+ "cannot register restart handler (err=%d)\n", ret);
|
|
+ }
|
|
+
|
|
+ syscore_info = info;
|
|
+ return 0;
|
|
+
|
|
+err_alloc:
|
|
+ if (info->ctrl) {
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+ }
|
|
+
|
|
+ watchdog_unregister_device(&info->wdt_dev);
|
|
+err_register_fail:
|
|
+ spa_disable_wdt_clk(info);
|
|
+ clk_put(info->clk);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int spa_wdt_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct spa_wdt_info *info = platform_get_drvdata(pdev);
|
|
+
|
|
+ watchdog_unregister_device(&info->wdt_dev);
|
|
+
|
|
+ if (info->ctrl) {
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+ }
|
|
+
|
|
+ spa_disable_wdt_clk(info);
|
|
+ clk_put(info->clk);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void spa_wdt_shutdown(struct platform_device *pdev)
|
|
+{
|
|
+ struct spa_wdt_info *info = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (info->ctrl)
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+
|
|
+ /* no need to disable clk if enable restart_handler */
|
|
+ if (info->enable_restart_handler)
|
|
+ spa_enable_wdt_clk(info);
|
|
+}
|
|
+
|
|
+#ifdef CONFIG_PM
|
|
+static int spa_wdt_suspend(struct platform_device *pdev, pm_message_t state)
|
|
+{
|
|
+ struct spa_wdt_info *info = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (info->ctrl) {
|
|
+ /* turn watchdog off */
|
|
+ hrtimer_cancel(&info->feed_timer);
|
|
+ spa_wdt_stop(&info->wdt_dev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int spa_wdt_resume(struct platform_device *pdev)
|
|
+{
|
|
+ struct spa_wdt_info *info = platform_get_drvdata(pdev);
|
|
+
|
|
+ if (info->ctrl) {
|
|
+ spa_wdt_start(&info->wdt_dev);
|
|
+ hrtimer_start(&info->feed_timer, info->feed_timeout, HRTIMER_MODE_REL);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#else
|
|
+#define spa_wdt_suspend NULL
|
|
+#define spa_wdt_resume NULL
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+#ifdef CONFIG_OF_RESERVED_MEM
|
|
+#include <linux/of.h>
|
|
+#include <linux/of_fdt.h>
|
|
+#include <linux/of_reserved_mem.h>
|
|
+
|
|
+static int __init rmem_reboot_setup(struct reserved_mem *rmem)
|
|
+{
|
|
+ phys_addr_t mask = PAGE_SIZE - 1;
|
|
+
|
|
+ if ((rmem->base & mask) || (rmem->size & mask)) {
|
|
+ pr_err("Reserved memory: incorrect alignment of reboot region\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ pr_info("Reserved memory: detected reboot memory at %pa, size %ld KiB\n",
|
|
+ &rmem->base, (unsigned long)rmem->size / SZ_1K);
|
|
+
|
|
+ reboot_cmd_mem = rmem->base;
|
|
+ reboot_cmd_size = rmem->size;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+RESERVEDMEM_OF_DECLARE(reboot, "reboot_page", rmem_reboot_setup);
|
|
+#endif
|
|
+
|
|
+
|
|
+static struct platform_driver spa_wdt_driver = {
|
|
+ .probe = spa_wdt_probe,
|
|
+ .remove = spa_wdt_remove,
|
|
+ .shutdown = spa_wdt_shutdown,
|
|
+ .suspend = spa_wdt_suspend,
|
|
+ .resume = spa_wdt_resume,
|
|
+ .driver = {
|
|
+ .name = "spa-wdt",
|
|
+ .of_match_table = of_match_ptr(spa_wdt_match),
|
|
+ },
|
|
+};
|
|
+
|
|
+
|
|
+module_platform_driver(spa_wdt_driver);
|
|
+
|
|
+MODULE_DESCRIPTION("Spacemit k1x-plat Watchdog Device Driver");
|
|
+MODULE_LICENSE("GPL-v2");
|
|
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|
|
+MODULE_ALIAS("platform:soc-wdt");
|
|
diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c
|
|
index 111111111111..222222222222 100644
|
|
--- a/fs/btrfs/sysfs.c
|
|
+++ b/fs/btrfs/sysfs.c
|
|
@@ -626,7 +626,7 @@ static const struct attribute_group btrfs_debug_feature_attr_group = {
|
|
|
|
#endif
|
|
|
|
-static ssize_t btrfs_show_u64(u64 *value_ptr, spinlock_t *lock, char *buf)
|
|
+static noinline ssize_t btrfs_show_u64(u64 *value_ptr, spinlock_t *lock, char *buf)
|
|
{
|
|
u64 val;
|
|
if (lock)
|
|
--
|
|
Armbian
|
|
|