summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-09-07 14:27:37 +0100
committerMark Brown <broonie@kernel.org>2026-09-07 14:27:37 +0100
commita1572372040d80a96d390f4e5fc8cef428eec6d8 (patch)
treee0a5c5ececbb5b2f13c468ff7e053530bf7e63c3 /drivers
parent8f104edba298ce5caf7dd7af424b9b50736cd20c (diff)
parentb85ed7f7259b49077955e835ecc32b32b75053e8 (diff)
downloadlinux-next-a1572372040d80a96d390f4e5fc8cef428eec6d8.tar.gz
linux-next-a1572372040d80a96d390f4e5fc8cef428eec6d8.zip
Merge branch 'watchdog-next' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git
Diffstat (limited to 'drivers')
-rw-r--r--drivers/watchdog/mtk_wdt.c62
-rw-r--r--drivers/watchdog/s3c2410_wdt.c16
-rw-r--r--drivers/watchdog/sbsa_gwdt.c19
-rw-r--r--drivers/watchdog/sp5100_tco.c61
-rw-r--r--drivers/watchdog/watchdog_dev.c4
5 files changed, 149 insertions, 13 deletions
diff --git a/drivers/watchdog/mtk_wdt.c b/drivers/watchdog/mtk_wdt.c
index d9c30e4c80e3..ddc4d2ff9d9f 100644
--- a/drivers/watchdog/mtk_wdt.c
+++ b/drivers/watchdog/mtk_wdt.c
@@ -35,6 +35,7 @@
#define WDT_MAX_TIMEOUT 31
#define WDT_MIN_TIMEOUT 2
#define WDT_LENGTH_TIMEOUT(n) ((n) << 5)
+#define WDT_IRQ_LEVEL_SYNC_US 70
#define WDT_LENGTH 0x04
#define WDT_LENGTH_KEY 0x8
@@ -49,6 +50,7 @@
#define WDT_MODE_EXRST_EN (1 << 2)
#define WDT_MODE_IRQ_EN (1 << 3)
#define WDT_MODE_AUTO_START (1 << 4)
+#define WDT_MODE_IRQ_LEVEL_EN (1 << 5)
#define WDT_MODE_DUAL_EN (1 << 6)
#define WDT_MODE_CNT_SEL (1 << 8)
#define WDT_MODE_KEY 0x22000000
@@ -72,7 +74,7 @@ static unsigned int timeout;
struct mtk_wdt_dev {
struct watchdog_device wdt_dev;
void __iomem *wdt_base;
- spinlock_t lock; /* protects WDT_SWSYSRST reg */
+ spinlock_t lock; /* protects WDT_MODE and WDT_SWSYSRST reg */
struct reset_controller_dev rcdev;
bool disable_wdt_extrst;
bool reset_by_toprgu;
@@ -212,8 +214,6 @@ static int toprgu_register_reset_controller(struct platform_device *pdev,
int ret;
struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev);
- spin_lock_init(&mtk_wdt->lock);
-
mtk_wdt->rcdev.owner = THIS_MODULE;
mtk_wdt->rcdev.nr_resets = rst_num;
mtk_wdt->rcdev.ops = &toprgu_reset_ops;
@@ -302,12 +302,15 @@ static int mtk_wdt_stop(struct watchdog_device *wdt_dev)
{
struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
void __iomem *wdt_base = mtk_wdt->wdt_base;
+ unsigned long flags;
u32 reg;
+ spin_lock_irqsave(&mtk_wdt->lock, flags);
reg = readl(wdt_base + WDT_MODE);
reg &= ~WDT_MODE_EN;
reg |= WDT_MODE_KEY;
iowrite32(reg, wdt_base + WDT_MODE);
+ spin_unlock_irqrestore(&mtk_wdt->lock, flags);
return 0;
}
@@ -317,12 +320,14 @@ static int mtk_wdt_start(struct watchdog_device *wdt_dev)
u32 reg;
struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
void __iomem *wdt_base = mtk_wdt->wdt_base;
+ unsigned long flags;
int ret;
ret = mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
if (ret < 0)
return ret;
+ spin_lock_irqsave(&mtk_wdt->lock, flags);
reg = ioread32(wdt_base + WDT_MODE);
if (wdt_dev->pretimeout)
reg |= (WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
@@ -334,6 +339,7 @@ static int mtk_wdt_start(struct watchdog_device *wdt_dev)
reg |= WDT_MODE_CNT_SEL;
reg |= (WDT_MODE_EN | WDT_MODE_KEY);
iowrite32(reg, wdt_base + WDT_MODE);
+ spin_unlock_irqrestore(&mtk_wdt->lock, flags);
return 0;
}
@@ -343,7 +349,11 @@ static int mtk_wdt_set_pretimeout(struct watchdog_device *wdd,
{
struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdd);
void __iomem *wdt_base = mtk_wdt->wdt_base;
- u32 reg = ioread32(wdt_base + WDT_MODE);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&mtk_wdt->lock, flags);
+ reg = ioread32(wdt_base + WDT_MODE);
if (timeout && !wdd->pretimeout) {
wdd->pretimeout = wdd->timeout / 2;
@@ -352,19 +362,54 @@ static int mtk_wdt_set_pretimeout(struct watchdog_device *wdd,
wdd->pretimeout = 0;
reg &= ~(WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
} else {
+ spin_unlock_irqrestore(&mtk_wdt->lock, flags);
return 0;
}
reg |= WDT_MODE_KEY;
iowrite32(reg, wdt_base + WDT_MODE);
+ spin_unlock_irqrestore(&mtk_wdt->lock, flags);
return mtk_wdt_set_timeout(wdd, wdd->timeout);
}
+static void mtk_wdt_deassert_irq(struct watchdog_device *wdd)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdd);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&mtk_wdt->lock, flags);
+
+ reg = ioread32(wdt_base + WDT_MODE);
+ reg ^= WDT_MODE_IRQ_LEVEL_EN;
+ iowrite32(reg | WDT_MODE_KEY, wdt_base + WDT_MODE);
+
+ /*
+ * Wait for two 32 kHz watchdog clock cycles so the IRQ level
+ * change can propagate across the clock domain.
+ */
+ udelay(WDT_IRQ_LEVEL_SYNC_US);
+
+ reg = ioread32(wdt_base + WDT_MODE);
+ reg ^= WDT_MODE_IRQ_LEVEL_EN;
+ iowrite32(reg | WDT_MODE_KEY, wdt_base + WDT_MODE);
+
+ /*
+ * Flush the posted write before releasing the lock and notifying
+ * the watchdog core.
+ */
+ ioread32(wdt_base + WDT_MODE);
+
+ spin_unlock_irqrestore(&mtk_wdt->lock, flags);
+}
+
static irqreturn_t mtk_wdt_isr(int irq, void *arg)
{
struct watchdog_device *wdd = arg;
+ mtk_wdt_deassert_irq(wdd);
watchdog_notify_pretimeout(wdd);
return IRQ_HANDLED;
@@ -406,6 +451,8 @@ static int mtk_wdt_probe(struct platform_device *pdev)
if (!mtk_wdt)
return -ENOMEM;
+ spin_lock_init(&mtk_wdt->lock);
+
platform_set_drvdata(pdev, mtk_wdt);
mtk_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
@@ -414,8 +461,8 @@ static int mtk_wdt_probe(struct platform_device *pdev)
irq = platform_get_irq_optional(pdev, 0);
if (irq > 0) {
- err = devm_request_irq(&pdev->dev, irq, mtk_wdt_isr, 0, "wdt_bark",
- &mtk_wdt->wdt_dev);
+ err = devm_request_irq(&pdev->dev, irq, mtk_wdt_isr, IRQF_NO_AUTOEN,
+ "wdt_bark", &mtk_wdt->wdt_dev);
if (err)
return err;
@@ -447,6 +494,9 @@ static int mtk_wdt_probe(struct platform_device *pdev)
if (unlikely(err))
return err;
+ if (irq > 0)
+ enable_irq(irq);
+
dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)\n",
mtk_wdt->wdt_dev.timeout, nowayout);
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c
index e31f93db0509..e61439a3f4b2 100644
--- a/drivers/watchdog/s3c2410_wdt.c
+++ b/drivers/watchdog/s3c2410_wdt.c
@@ -232,6 +232,20 @@ static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
};
+/*
+ * Unlike similar SoCs like GS101, Exynos5515's PMU does not require
+ * explicit counter enablement. Hence, QUIRK_HAS_PMU_CNT_EN is not set.
+ */
+static const struct s3c2410_wdt_variant drv_data_exynos5515 = {
+ .mask_reset_reg = EXYNOSAUTOV920_CLUSTER0_NONCPU_INT_EN,
+ .mask_bit = 2,
+ .mask_reset_inv = true,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = 24,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_DBGACK_BIT,
+};
+
static const struct s3c2410_wdt_variant drv_data_exynos7 = {
.disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
.mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
@@ -379,6 +393,8 @@ static const struct of_device_id s3c2410_wdt_match[] = {
.data = &drv_data_exynos5250 },
{ .compatible = "samsung,exynos5420-wdt",
.data = &drv_data_exynos5420 },
+ { .compatible = "samsung,exynos5515-wdt",
+ .data = &drv_data_exynos5515 },
{ .compatible = "samsung,exynos7-wdt",
.data = &drv_data_exynos7 },
{ .compatible = "samsung,exynos850-wdt",
diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c
index e04d42cc7774..3c5bfd8641c6 100644
--- a/drivers/watchdog/sbsa_gwdt.c
+++ b/drivers/watchdog/sbsa_gwdt.c
@@ -122,6 +122,11 @@ MODULE_PARM_DESC(nowayout,
"Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+static bool early_enable;
+module_param(early_enable, bool, 0);
+MODULE_PARM_DESC(early_enable,
+ "Watchdog is started on module insertion (default=0)");
+
/*
* Arm Base System Architecture 1.0 introduces watchdog v1 which
* increases the length watchdog offset register to 48 bits.
@@ -296,6 +301,7 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
struct sbsa_gwdt *gwdt;
int ret, irq;
u32 status;
+ bool early_action;
gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
if (!gwdt)
@@ -386,14 +392,23 @@ static int sbsa_gwdt_probe(struct platform_device *pdev)
*/
sbsa_gwdt_set_timeout(wdd, wdd->timeout);
+ early_action = early_enable && !(status & SBSA_GWDT_WCS_EN);
+ if (early_action) {
+ sbsa_gwdt_start(wdd);
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
watchdog_stop_on_reboot(wdd);
ret = devm_watchdog_register_device(dev, wdd);
- if (ret)
+ if (ret) {
+ if (early_action)
+ sbsa_gwdt_stop(wdd);
return ret;
+ }
dev_info(dev, "Initialized with %ds timeout @ %u Hz, action=%d.%s\n",
wdd->timeout, gwdt->clk, action,
- status & SBSA_GWDT_WCS_EN ? " [enabled]" : "");
+ watchdog_hw_running(wdd) ? " [enabled]" : "");
return 0;
}
diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c
index 7e99c3b1f367..6c85f11cff24 100644
--- a/drivers/watchdog/sp5100_tco.c
+++ b/drivers/watchdog/sp5100_tco.c
@@ -33,6 +33,7 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
+#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
@@ -240,6 +241,35 @@ static u32 sp5100_tco_read_pm_reg32(u8 index)
return val;
}
+/*
+ * The Gigabyte GA-78LMT-USB3 firmware programs the legacy SP5100 watchdog
+ * MMIO window at 0xfec000f0. This address lies inside the IOAPIC resource,
+ * so the generic resource reservation fails even though firmware explicitly
+ * assigns the watchdog to this address.
+ *
+ * Keep this exception narrowly scoped to the affected system and firmware
+ * address. Do not relocate or otherwise reprogram the watchdog.
+ */
+#define SP5100_WDT_GA78LMT_MMIO 0xfec000f0
+
+static const struct dmi_system_id sp5100_tco_unreserved_mmio_dmi[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_MATCH(DMI_PRODUCT_NAME, "GA-78LMT-USB3"),
+ },
+ },
+ {}
+};
+
+static bool sp5100_tco_allow_unreserved_mmio(struct sp5100_tco *tco,
+ u32 mmio_addr)
+{
+ return tco->tco_reg_layout == sp5100 &&
+ mmio_addr == SP5100_WDT_GA78LMT_MMIO &&
+ dmi_check_system(sp5100_tco_unreserved_mmio_dmi);
+}
+
static u32 sp5100_tco_request_region(struct device *dev,
u32 mmio_addr,
const char *dev_name)
@@ -259,6 +289,7 @@ static u32 sp5100_tco_prepare_base(struct sp5100_tco *tco,
const char *dev_name)
{
struct device *dev = tco->wdd.parent;
+ bool reserved = false;
dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n", mmio_addr);
@@ -266,11 +297,29 @@ static u32 sp5100_tco_prepare_base(struct sp5100_tco *tco,
return -ENODEV;
/* Check for MMIO address and alternate MMIO address conflicts */
- if (mmio_addr)
- mmio_addr = sp5100_tco_request_region(dev, mmio_addr, dev_name);
+ if (mmio_addr) {
+ u32 requested_addr;
+
+ requested_addr = sp5100_tco_request_region(dev, mmio_addr,
+ dev_name);
+ if (requested_addr) {
+ mmio_addr = requested_addr;
+ reserved = true;
+ } else if (sp5100_tco_allow_unreserved_mmio(tco, mmio_addr)) {
+ dev_info(dev,
+ "Using firmware watchdog MMIO 0x%08x without reserving it\n",
+ mmio_addr);
+ } else {
+ mmio_addr = 0;
+ }
+ }
- if (!mmio_addr && alt_mmio_addr)
- mmio_addr = sp5100_tco_request_region(dev, alt_mmio_addr, dev_name);
+ if (!mmio_addr && alt_mmio_addr) {
+ mmio_addr = sp5100_tco_request_region(dev, alt_mmio_addr,
+ dev_name);
+ if (mmio_addr)
+ reserved = true;
+ }
if (!mmio_addr) {
dev_err(dev, "Failed to reserve MMIO or alternate MMIO region\n");
@@ -280,7 +329,9 @@ static u32 sp5100_tco_prepare_base(struct sp5100_tco *tco,
tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
if (!tco->tcobase) {
dev_err(dev, "MMIO address 0x%08x failed mapping\n", mmio_addr);
- devm_release_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
+ if (reserved)
+ devm_release_mem_region(dev, mmio_addr,
+ SP5100_WDT_MEM_MAP_SIZE);
return -ENOMEM;
}
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index d7895009a2de..d8ed77d8a220 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -305,6 +305,10 @@ static int watchdog_stop(struct watchdog_device *wdd)
if (wdd->ops->stop) {
clear_bit(WDOG_HW_RUNNING, &wdd->status);
err = wdd->ops->stop(wdd);
+ if (err < 0) {
+ pr_err("watchdog%d: Failed to stop watchdog: %pe\n",
+ wdd->id, ERR_PTR(err));
+ }
trace_watchdog_stop(wdd, err);
} else {
set_bit(WDOG_HW_RUNNING, &wdd->status);