diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-07 14:27:37 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-07 14:27:37 +0100 |
| commit | a1572372040d80a96d390f4e5fc8cef428eec6d8 (patch) | |
| tree | e0a5c5ececbb5b2f13c468ff7e053530bf7e63c3 /drivers | |
| parent | 8f104edba298ce5caf7dd7af424b9b50736cd20c (diff) | |
| parent | b85ed7f7259b49077955e835ecc32b32b75053e8 (diff) | |
| download | linux-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.c | 62 | ||||
| -rw-r--r-- | drivers/watchdog/s3c2410_wdt.c | 16 | ||||
| -rw-r--r-- | drivers/watchdog/sbsa_gwdt.c | 19 | ||||
| -rw-r--r-- | drivers/watchdog/sp5100_tco.c | 61 | ||||
| -rw-r--r-- | drivers/watchdog/watchdog_dev.c | 4 |
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); |
