Merge tag 'watchdog-for-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging

Pull watchdog updates from Guenter Roeck:
 "New Drivers:
   - Nuvoton MA35D1
   - Lenovo SE30G2 and SE60

  Added support to existing drivers:
   - snps,dw-wdt: Add RV1106 compatible
   - apple,wdt: Add t6030, t6031, and t8132 compatibles

  Other notable changes:
   - New "dump" pretimeout governor
   - Propagate errors from optional IRQ lookup
   - Remove redundant dev_err() and dev_err_probe() messages
   - npcm, qcom: Improved bootstatus reports
   - realtek-otto: Change to use regmap API
   - w83627hf_wdt: Report running watchdog, identify NCT6126

  Various other minor fixes and improvements"

* tag 'watchdog-for-v7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (40 commits)
  watchdog: orion_wdt: Propagate errors from optional IRQ lookup
  watchdog: qcom: Propagate errors from optional IRQ lookup
  watchdog: aspeed: Propagate errors from optional IRQ lookup
  watchdog: stm32_iwdg: Propagate errors from optional IRQ lookup
  watchdog: dw_wdt: Propagate errors from optional IRQ lookup
  watchdog: mediatek: Propagate errors from optional IRQ lookup
  watchdog: apple: Constify some structures
  watchdog: pretimeout: Convert dump pretimeout governor to tristate
  nmi: Export CPU backtrace APIs for loadable modules
  watchdog: booke_wdt: Document unused parameter of __booke_wdt_disable()
  watchdog: wdat_wdt: map registers that fall inside ACPI NVS
  watchdog: Add Nuvoton MA35D1 watchdog driver support
  dt-bindings: watchdog: Add MA35D1 Watchdog
  watchdog: qcom: report bootstatus on IPQ9574 and IPQ5332
  watchdog: qcom: report WDIOF_POWERUNDER in bootstatus
  watchdog: sprd: Remove redundant dev_err()
  watchdog: sama5d4: Remove redundant dev_err()
  watchdog: realtek_otto: Remove redundant dev_err_probe()
  watchdog: orion: Remove redundant dev_err()
  watchdog: marvell_gti: Remove redundant dev_err_probe()
  ...
This commit is contained in:
Linus Torvalds
2026-08-19 09:53:35 -07:00
39 changed files with 1303 additions and 115 deletions
@@ -18,7 +18,10 @@ properties:
- items:
- enum:
- apple,t6020-wdt
- apple,t6030-wdt
- apple,t6031-wdt
- apple,t8122-wdt
- apple,t8132-wdt
- const: apple,t8103-wdt
- items:
- enum:
@@ -0,0 +1,55 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/watchdog/nuvoton,ma35d1-wdt.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Nuvoton MA35D1 Watchdog Timer
maintainers:
- Zi-Yu Chen <zychennvt@gmail.com>
allOf:
- $ref: watchdog.yaml#
properties:
compatible:
enum:
- nuvoton,ma35d1-wdt
reg:
maxItems: 1
interrupts:
maxItems: 1
clocks:
maxItems: 1
wakeup-source: true
required:
- compatible
- reg
- interrupts
- clocks
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/clock/nuvoton,ma35d1-clk.h>
soc {
#address-cells = <2>;
#size-cells = <2>;
wdt1: watchdog@40440000 {
compatible = "nuvoton,ma35d1-wdt";
reg = <0x0 0x40440000 0x0 0x100>;
interrupts = <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk WDT1_GATE>;
};
};
...
@@ -16,6 +16,14 @@ description:
allOf:
- $ref: watchdog.yaml#
- if:
properties:
compatible:
contains:
const: nuvoton,wpcm450-wdt
then:
properties:
nuvoton,sysgcr: false
properties:
compatible:
@@ -40,6 +48,12 @@ properties:
clock-frequency:
description: Frequency in Hz of the clock that drives the NPCM timer.
nuvoton,sysgcr:
$ref: /schemas/types.yaml#/definitions/phandle
description:
a phandle to access the GCR reset-status registers that latch
watchdog reset indications on NPCM750 and NPCM845.
required:
- compatible
- reg
@@ -27,6 +27,7 @@ properties:
- qcom,apss-wdt-ipq9574
- qcom,apss-wdt-ipq9650
- qcom,apss-wdt-kaanapali
- qcom,apss-wdt-maili
- qcom,apss-wdt-msm8226
- qcom,apss-wdt-msm8974
- qcom,apss-wdt-msm8994
@@ -35,6 +35,7 @@ properties:
- rockchip,rk3576-wdt
- rockchip,rk3588-wdt
- rockchip,rv1103b-wdt
- rockchip,rv1106-wdt
- rockchip,rv1108-wdt
- rockchip,rv1126-wdt
- const: snps,dw-wdt
+1
View File
@@ -25,4 +25,5 @@ Driver specific
hpwdt
ics-wdt
mlx-wdt
npcm_wdt
pcwd-watchdog
+71
View File
@@ -0,0 +1,71 @@
.. SPDX-License-Identifier: GPL-2.0
=============
NPCM Watchdog
=============
The NPCM watchdog driver can report reset-cause information on
``nuvoton,npcm750-wdt`` and ``nuvoton,npcm845-wdt`` systems.
Userspace can read the latched reset cause through
``WDIOC_GETBOOTSTATUS``. When ``CONFIG_WATCHDOG_SYSFS`` is enabled, the
same value is also visible through ``/sys/class/watchdog/watchdogN/bootstatus``.
The mapping is fixed in the driver. It exposes the SoC reset indications
through the generic watchdog bootstatus flags and is not configurable from
Device Tree.
.. list-table:: Reset-cause mapping
:header-rows: 1
* - Platform
- Reset indication
- Bootstatus flag
- Reported meaning
* - NPCM750 and NPCM845
- ``PORST``
- ``WDIOF_OVERHEAT``
- power-on reset
* - NPCM750 and NPCM845
- ``CORST``
- ``WDIOF_FANFAULT``
- core reset
* - NPCM750 and NPCM845
- ``SWR1RST``
- ``WDIOF_EXTERN1``
- software reset source 1
* - NPCM750 and NPCM845
- ``SWR2RST``
- ``WDIOF_EXTERN2``
- software reset source 2
* - NPCM750 and NPCM845
- ``SWR3RST``
- ``WDIOF_POWERUNDER``
- software reset source 3
* - NPCM750
- ``SWR4RST``
- ``WDIOF_POWEROVER``
- software reset source 4
* - NPCM845
- ``TIP reset`` (``INTCR2[25]``)
- ``WDIOF_POWEROVER``
- TIP reset
``WDIOF_CARDRESET`` is reported only for the watchdog instance whose own
reset-status bit is latched. On systems with three watchdog instances, this
maps ``WD0RST``, ``WD1RST``, and ``WD2RST`` to ``watchdog0``, ``watchdog1``,
and ``watchdog2`` respectively.
The driver may report ``WDIOF_CARDRESET`` together with one or more of the
reset-cause flags listed above.
On NPCM750, the driver samples ``RESSR``. When reset bits are still latched,
it clears them and stores the sampled value in ``SCRPAD2`` so later watchdog
probes can report the same boot-time state.
On NPCM845, the driver samples ``INTCR2``. When reset bits are still latched,
it clears them and stores the sampled value in ``SCRPAD10`` so later watchdog
probes can report the same boot-time state.
The WPCM450 watchdog continues to operate without this reset-indication
mapping.
+41 -1
View File
@@ -93,10 +93,19 @@ config WATCHDOG_PRETIMEOUT_GOV_SEL
tristate
depends on WATCHDOG_PRETIMEOUT_GOV
default m
select WATCHDOG_PRETIMEOUT_GOV_PANIC if WATCHDOG_PRETIMEOUT_GOV_NOOP=n
select WATCHDOG_PRETIMEOUT_GOV_PANIC if \
WATCHDOG_PRETIMEOUT_GOV_NOOP=n && WATCHDOG_PRETIMEOUT_GOV_DUMP=n
if WATCHDOG_PRETIMEOUT_GOV
config WATCHDOG_PRETIMEOUT_GOV_DUMP
tristate "Dump watchdog pretimeout governor"
depends on WATCHDOG_CORE
default WATCHDOG_CORE
help
Dump watchdog pretimeout governor, all cpu backtrace is
added to kernel log buffer.
config WATCHDOG_PRETIMEOUT_GOV_NOOP
tristate "Noop watchdog pretimeout governor"
depends on WATCHDOG_CORE
@@ -121,6 +130,14 @@ choice
The governor takes its action, if a watchdog is capable
to report a pretimeout event.
config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_DUMP
bool "dump"
depends on WATCHDOG_PRETIMEOUT_GOV_DUMP
help
Use dump watchdog pretimeout governor by default. If dump
governor is selected by a user, dump all cpu backtrace to
the kernel log buffer and don't do any system changes.
config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP
bool "noop"
depends on WATCHDOG_PRETIMEOUT_GOV_NOOP
@@ -291,6 +308,18 @@ config LENOVO_SE30_WDT
This driver can also be built as a module. If so, the module
will be called lenovo-se30-wdt.
config LENOVO_SE30G2_SE60_WDT
tristate "Lenovo SE30 Gen 2, SE60 Watchdog"
depends on (X86 && DMI) || COMPILE_TEST
depends on HAS_IOPORT
select WATCHDOG_CORE
help
If you say yes here you get support for the watchdog
functionality for the Lenovo SE30 Gen2 and SE60 platforms.
This driver can also be built as a module. If so, the module
will be called lenovo-se30g2-se60-wdt.
config MENF21BMC_WATCHDOG
tristate "MEN 14F021P00 BMC Watchdog"
depends on MFD_MENF21BMC || COMPILE_TEST
@@ -720,6 +749,16 @@ config STMP3XXX_RTC_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called stmp3xxx_rtc_wdt.
config MA35D1_WDT
tristate "Nuvoton MA35D1 Watchdog Timer"
depends on ARCH_MA35 || COMPILE_TEST
select WATCHDOG_CORE
help
Say Y here to include support for the watchdog timer
for the Nuvoton MA35D1 series microprocessor.
To compile this driver as a module, choose M here: the
module will be called ma35d1_wdt.
config TS4800_WATCHDOG
tristate "TS-4800 Watchdog"
depends on HAS_IOMEM && OF
@@ -1076,6 +1115,7 @@ config REALTEK_OTTO_WDT
depends on MACH_REALTEK_RTL || COMPILE_TEST
depends on COMMON_CLK
select WATCHDOG_CORE
select REGMAP_MMIO
default MACH_REALTEK_RTL
help
Say Y here to include support for the watchdog timer on Realtek
+3
View File
@@ -11,6 +11,7 @@ watchdog-objs += watchdog_core.o watchdog_dev.o
watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o
watchdog-$(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT) += watchdog_hrtimer_pretimeout.o
obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_DUMP) += pretimeout_dump.o
obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP) += pretimeout_noop.o
obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_PANIC) += pretimeout_panic.o
@@ -104,6 +105,7 @@ obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o
obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o
obj-$(CONFIG_MARVELL_GTI_WDT) += marvell_gti_wdt.o
obj-$(CONFIG_GUNYAH_WATCHDOG) += gunyah_wdt.o
obj-$(CONFIG_MA35D1_WDT) += ma35d1_wdt.o
# X86 (i386 + ia64 + x86_64) Architecture
obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
@@ -127,6 +129,7 @@ obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o
obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
obj-$(CONFIG_LENOVO_SE10_WDT) += lenovo_se10_wdt.o
obj-$(CONFIG_LENOVO_SE30_WDT) += lenovo_se30_wdt.o
obj-$(CONFIG_LENOVO_SE30G2_SE60_WDT) += lenovo_se30g2_se60_wdt.o
obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
obj-$(CONFIG_IT87_WDT) += it87_wdt.o
obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+2 -2
View File
@@ -139,7 +139,7 @@ static int apple_wdt_restart(struct watchdog_device *wdd, unsigned long mode,
return 0;
}
static struct watchdog_ops apple_wdt_ops = {
static const struct watchdog_ops apple_wdt_ops = {
.owner = THIS_MODULE,
.start = apple_wdt_start,
.stop = apple_wdt_stop,
@@ -149,7 +149,7 @@ static struct watchdog_ops apple_wdt_ops = {
.restart = apple_wdt_restart,
};
static struct watchdog_info apple_wdt_info = {
static const struct watchdog_info apple_wdt_info = {
.identity = "Apple SoC Watchdog",
.options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
};
+2
View File
@@ -429,6 +429,8 @@ static int aspeed_wdt_probe(struct platform_device *pdev)
if (wdt->cfg->irq_mask) {
int irq = platform_get_irq_optional(pdev, 0);
if (irq < 0 && irq != -ENXIO)
return irq;
if (irq > 0) {
ret = devm_request_irq(dev, irq, aspeed_wdt_irq,
IRQF_SHARED, dev_name(dev),
+1 -2
View File
@@ -389,8 +389,7 @@ static int bd96801_wdt_probe(struct platform_device *pdev)
IRQF_ONESHOT, "bd96801-wdg",
NULL);
if (ret)
return dev_err_probe(&pdev->dev, ret,
"Failed to register IRQ\n");
return ret;
}
return devm_watchdog_register_device(&pdev->dev, &w->wdt);
+1
View File
@@ -149,6 +149,7 @@ static void __booke_wdt_enable(void *data)
/**
* __booke_wdt_disable - disable the watchdog on the given CPU
* @data: unused
*
* This function is called on each CPU. It disables the watchdog on that CPU.
*
+1 -5
View File
@@ -312,12 +312,8 @@ static int cdns_wdt_probe(struct platform_device *pdev)
if (!wdt->rst && irq >= 0) {
ret = devm_request_irq(dev, irq, cdns_wdt_irq_handler, 0,
pdev->name, pdev);
if (ret) {
dev_err(dev,
"cannot register interrupt handler err=%d\n",
ret);
if (ret)
return ret;
}
}
/* Initialize the members of cdns_wdt structure */
+2 -2
View File
@@ -178,8 +178,8 @@ static int __maybe_unused cros_ec_wdt_resume(struct platform_device *pdev)
}
static const struct platform_device_id cros_ec_wdt_id[] = {
{ DRV_NAME, 0 },
{}
{ .name = DRV_NAME },
{ }
};
static struct platform_driver cros_ec_wdt_driver = {
+1 -1
View File
@@ -611,7 +611,7 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
dw_wdt->wdd.info = &dw_wdt_pt_ident;
} else {
if (ret == -EPROBE_DEFER)
if (ret != -ENXIO)
return ret;
dw_wdt->wdd.info = &dw_wdt_ident;
+1 -3
View File
@@ -165,10 +165,8 @@ static int mid_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq,
IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog",
wdt_dev);
if (ret) {
dev_err(dev, "error requesting warning irq %d\n", pdata->irq);
if (ret)
return ret;
}
/*
* The firmware followed by U-Boot leaves the watchdog running
+2 -2
View File
@@ -214,7 +214,7 @@ static int keembay_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, wdt->th_irq, keembay_wdt_th_isr, 0,
"keembay-wdt", wdt);
if (ret)
return dev_err_probe(dev, ret, "Failed to request IRQ for threshold\n");
return ret;
wdt->to_irq = platform_get_irq_byname(pdev, "timeout");
if (wdt->to_irq < 0)
@@ -223,7 +223,7 @@ static int keembay_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, wdt->to_irq, keembay_wdt_to_isr, 0,
"keembay-wdt", wdt);
if (ret)
return dev_err_probe(dev, ret, "Failed to request IRQ for timeout\n");
return ret;
wdt->wdd.parent = dev;
wdt->wdd.info = &keembay_wdt_info;
+289
View File
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* WDT driver for Lenovo SE30G2 & SE60.
*/
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#define CFG_PORT 0x2E
#define CFG_SIZE 2
#define CFG_LDN 0x07
#define CFG_BRAM_LDN 0x10
#define BRAM_SIZE 2
#define BRAM_WDT_REG 0x48
#define DRVNAME "lenovo-se30g2-se60-wdt"
/*The timeout range is 1-255 seconds*/
#define MIN_TIMEOUT 1
#define MAX_TIMEOUT 255
#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
static unsigned short bram_base;
static struct platform_device *se_30g2_60_pdev;
static int timeout; /* in seconds */
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout,
"Watchdog timeout in seconds. 1 <= timeout <= 255, default="
__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout,
"Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
struct se_30g2_60_wdt {
struct watchdog_device wdd;
};
static void enter_pnp_mode(void)
{
outb(0x87, CFG_PORT);
outb(0x01, CFG_PORT);
outb(0x55, CFG_PORT);
outb(0x55, CFG_PORT);
}
static void exit_pnp_mode(void)
{
outb(0x2, CFG_PORT);
outb(0x2, CFG_PORT + 1);
}
static void lpc_write(unsigned char index, unsigned char data)
{
outb(index, CFG_PORT);
outb(data, CFG_PORT + 1);
}
static unsigned char lpc_read(unsigned char index)
{
outb(index, CFG_PORT);
return inb(CFG_PORT + 1);
}
static unsigned short lpc_chip_id(void)
{
unsigned char msb, lsb;
msb = lpc_read(0x20);
lsb = lpc_read(0x21);
return (msb << 8 | lsb);
}
static void bram_write(unsigned char reg, unsigned char val)
{
outb(reg | 0x80, bram_base);
outb(val, bram_base + 1);
}
static unsigned char bram_read(unsigned char reg)
{
unsigned char val;
outb(reg | 0x80, bram_base);
val = inb(bram_base + 1);
return val;
}
static int wdt_read(unsigned short *val)
{
if (!request_muxed_region(bram_base, BRAM_SIZE, DRVNAME))
return -EACCES;
*val = ((bram_read(BRAM_WDT_REG) & 0xFF) << 8);
*val |= bram_read(BRAM_WDT_REG + 1) & 0xFF;
release_region(bram_base, BRAM_SIZE);
return 0;
}
static int wdt_write(unsigned short val)
{
if (!request_muxed_region(bram_base, BRAM_SIZE, DRVNAME))
return -EACCES;
bram_write(BRAM_WDT_REG, (val >> 8) & 0xFF);
bram_write(BRAM_WDT_REG + 1, val & 0xFF);
release_region(bram_base, BRAM_SIZE);
return 0;
}
static int wdt_start(struct watchdog_device *wdog)
{
return wdt_write(wdog->timeout);
}
static int wdt_stop(struct watchdog_device *wdog)
{
return wdt_write(0);
}
static int wdt_ping(struct watchdog_device *wdog)
{
return wdt_write(wdog->timeout);
}
static unsigned int wdt_get_timeleft(struct watchdog_device *wdog)
{
unsigned short val;
int err;
err = wdt_read(&val);
return err ? 0 : val;
}
static const struct watchdog_info wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.identity = "Lenovo SE30G2 SE60 WDOG",
};
static const struct watchdog_ops se_30g2_60_wdt_ops = {
.owner = THIS_MODULE,
.start = wdt_start,
.stop = wdt_stop,
.ping = wdt_ping,
.get_timeleft = wdt_get_timeleft,
};
static int se_30g2_60_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct se_30g2_60_wdt *priv;
unsigned int chip_id;
int ret;
if (!request_muxed_region(CFG_PORT, CFG_SIZE, DRVNAME))
return -EBUSY;
/* identify the chip */
enter_pnp_mode();
chip_id = lpc_chip_id();
if (chip_id != 0x5782) {
exit_pnp_mode();
release_region(CFG_PORT, CFG_SIZE);
return -ENODEV;
}
/* probe the BRAM base address */
lpc_write(CFG_LDN, CFG_BRAM_LDN);
bram_base = (lpc_read(0x60) << 8) | lpc_read(0x61);
exit_pnp_mode();
release_region(CFG_PORT, CFG_SIZE);
dev_info(dev, "Found Lenovo SE30G2 SE60 0x%x\n", chip_id);
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
watchdog_set_drvdata(&priv->wdd, priv);
priv->wdd.parent = dev;
priv->wdd.info = &wdt_info;
priv->wdd.ops = &se_30g2_60_wdt_ops;
priv->wdd.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
priv->wdd.min_timeout = MIN_TIMEOUT;
priv->wdd.max_timeout = MAX_TIMEOUT;
watchdog_init_timeout(&priv->wdd, timeout, dev);
watchdog_set_nowayout(&priv->wdd, nowayout);
watchdog_stop_on_reboot(&priv->wdd);
watchdog_stop_on_unregister(&priv->wdd);
ret = devm_watchdog_register_device(dev, &priv->wdd);
dev_dbg(&pdev->dev, "initialized. timeout=%d sec (nowayout=%d)\n",
priv->wdd.timeout, nowayout);
return ret;
}
static struct platform_driver se_30g2_60_wdt_driver = {
.driver = {
.name = DRVNAME,
},
.probe = se_30g2_60_wdt_probe,
};
static int se_30g2_60_create_device(const struct dmi_system_id *id)
{
int err;
se_30g2_60_pdev = platform_device_alloc("lenovo-se30g2-se60-wdt", -1);
if (!se_30g2_60_pdev)
return -ENOMEM;
err = platform_device_add(se_30g2_60_pdev);
if (err) {
platform_device_put(se_30g2_60_pdev);
se_30g2_60_pdev = NULL;
}
return err;
}
static const struct dmi_system_id se_30g2_60[] __initconst = {
{
.ident = "LENOVO-SE30G2",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_BOARD_NAME, "33BD"),
},
.callback = se_30g2_60_create_device,
},
{
.ident = "LENOVO-SE60",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_BOARD_NAME, "33BF"),
},
.callback = se_30g2_60_create_device,
},
{}
};
MODULE_DEVICE_TABLE(dmi, se_30g2_60);
static int __init se_30g2_60_wdt_init(void)
{
int err;
if (!dmi_check_system(se_30g2_60))
return -ENODEV;
err = platform_driver_register(&se_30g2_60_wdt_driver);
if (err && se_30g2_60_pdev) {
platform_device_unregister(se_30g2_60_pdev);
se_30g2_60_pdev = NULL;
}
return err;
}
static void __exit se_30g2_60_wdt_exit(void)
{
if (se_30g2_60_pdev)
platform_device_unregister(se_30g2_60_pdev);
platform_driver_unregister(&se_30g2_60_wdt_driver);
}
module_init(se_30g2_60_wdt_init);
module_exit(se_30g2_60_wdt_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>");
MODULE_DESCRIPTION("WDT driver for Lenovo SE30G2 & SE60");
+363
View File
@@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2026 Nuvoton technology corporation.
*
* Author: Zi-Yu Chen <zychennvt@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeirq.h>
#include <linux/property.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#define DRV_NAME "ma35d1-wdt"
#define REG_WDT_CTL 0x00
#define REG_WDT_RSTCNT 0x08
#define TOUTSEL GENMASK(11, 8)
#define WDTEN BIT(7)
#define INTEN BIT(6)
#define WKF BIT(5)
#define WKEN BIT(4)
#define IF BIT(3)
#define RSTF BIT(2)
#define RSTEN BIT(1)
#define SYNC BIT(30)
#define WDT_DEFAULT_TIMEOUT 32
#define RESET_COUNTER 0x00005AA5
static bool nowayout = WATCHDOG_NOWAYOUT;
static unsigned int timeout;
struct ma35d1_wdt_dev {
struct watchdog_device wdt_dev;
spinlock_t lock;
void __iomem *wdt_base;
struct clk *clk;
unsigned long clk_rate;
int irq;
};
static int ma35d1_wdt_wait_sync(struct ma35d1_wdt_dev *ma35d1_wdt)
{
unsigned int val;
return readl_relaxed_poll_timeout_atomic(ma35d1_wdt->wdt_base +
REG_WDT_CTL, val, !(val & SYNC), 10, 125);
}
/**
* ma35d1_wdt_stop - Stop the watchdog.
*
* @wdt_dev: watchdog device
*
* Read the contents of the CTL register, clear the WDTEN bit
* in the register and set the access key for successful write.
*
* Return: 0 on success, negative error otherwise.
*/
static int ma35d1_wdt_stop(struct watchdog_device *wdt_dev)
{
struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
unsigned int val;
int ret;
guard(spinlock_irqsave)(&ma35d1_wdt->lock);
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
val &= ~WDTEN;
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
if (ret) {
dev_err(wdt_dev->parent, "Wait for WDTEN SYNC timeout!\n");
return ret;
}
return 0;
}
static int ma35d1_wdt_ping(struct watchdog_device *wdt_dev)
{
struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
writel_relaxed(RESET_COUNTER, ma35d1_wdt->wdt_base + REG_WDT_RSTCNT);
return 0;
}
static int ma35d1_wdt_set_timeout(struct watchdog_device *wdt_dev,
unsigned int timeout)
{
struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
unsigned long target_ticks;
unsigned int val, i;
static const uint8_t toutsel_shifts[] = { 4, 6, 8, 10, 12,
14, 16, 18, 20 };
if (timeout < (ma35d1_wdt->wdt_dev.max_hw_heartbeat_ms / 1000)) {
target_ticks = (unsigned long)timeout * ma35d1_wdt->clk_rate;
for (i = 0; i < ARRAY_SIZE(toutsel_shifts); i++) {
if ((1UL << toutsel_shifts[i]) >= target_ticks)
break;
}
/* To avoid truncation errors (0 seconds) during division. */
wdt_dev->timeout =
(1UL << toutsel_shifts[i]) / ma35d1_wdt->clk_rate;
if (wdt_dev->timeout == 0)
wdt_dev->timeout = 1;
} else {
i = ARRAY_SIZE(toutsel_shifts) - 1;
wdt_dev->timeout = timeout;
}
guard(spinlock_irqsave)(&ma35d1_wdt->lock);
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
val &= ~TOUTSEL;
val |= FIELD_PREP(TOUTSEL, i);
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
ma35d1_wdt_ping(wdt_dev);
return 0;
}
static int ma35d1_wdt_start(struct watchdog_device *wdt_dev)
{
struct ma35d1_wdt_dev *ma35d1_wdt = watchdog_get_drvdata(wdt_dev);
void __iomem *wdt_base = ma35d1_wdt->wdt_base;
unsigned int val;
int ret;
ret = ma35d1_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
if (ret < 0)
return ret;
guard(spinlock_irqsave)(&ma35d1_wdt->lock);
val = readl_relaxed(wdt_base + REG_WDT_CTL);
val |= (WDTEN | RSTEN);
writel_relaxed(val, wdt_base + REG_WDT_CTL);
ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
if (ret) {
dev_err(wdt_dev->parent, "Wait for WDTEN SYNC timeout!\n");
return ret;
}
writel_relaxed(RESET_COUNTER, wdt_base + REG_WDT_RSTCNT);
return 0;
}
static const struct watchdog_info ma35d1_wdt_info = {
.identity = DRV_NAME,
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
WDIOF_CARDRESET,
};
static const struct watchdog_ops ma35d1_wdt_ops = {
.owner = THIS_MODULE,
.start = ma35d1_wdt_start,
.stop = ma35d1_wdt_stop,
.ping = ma35d1_wdt_ping,
.set_timeout = ma35d1_wdt_set_timeout,
};
static irqreturn_t ma35d1_wdt_isr(int irq, void *dev_id)
{
struct ma35d1_wdt_dev *ma35d1_wdt = dev_id;
unsigned int val;
/* Clear the flag if set */
guard(spinlock)(&ma35d1_wdt->lock);
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
return IRQ_HANDLED;
}
static int ma35d1_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ma35d1_wdt_dev *ma35d1_wdt;
unsigned long clk_rate, val;
int ret;
ma35d1_wdt = devm_kzalloc(dev, sizeof(*ma35d1_wdt), GFP_KERNEL);
if (!ma35d1_wdt)
return -ENOMEM;
spin_lock_init(&ma35d1_wdt->lock);
platform_set_drvdata(pdev, ma35d1_wdt);
ma35d1_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ma35d1_wdt->wdt_base))
return PTR_ERR(ma35d1_wdt->wdt_base);
ma35d1_wdt->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(ma35d1_wdt->clk))
return dev_err_probe(dev, PTR_ERR(ma35d1_wdt->clk),
"Can't get Watchdog clock\n");
clk_rate = clk_get_rate(ma35d1_wdt->clk);
if (!clk_rate)
return -EINVAL;
ma35d1_wdt->clk_rate = clk_rate;
ma35d1_wdt->irq = platform_get_irq(pdev, 0);
if (ma35d1_wdt->irq < 0)
return dev_err_probe(dev, ma35d1_wdt->irq,
"failed to get irq\n");
ma35d1_wdt->wdt_dev.info = &ma35d1_wdt_info;
ma35d1_wdt->wdt_dev.ops = &ma35d1_wdt_ops;
ma35d1_wdt->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT;
ma35d1_wdt->wdt_dev.min_timeout = 1;
ma35d1_wdt->wdt_dev.max_hw_heartbeat_ms = (1U << 20) * 1000 / clk_rate;
ma35d1_wdt->wdt_dev.parent = dev;
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
if (val & RSTF) {
ma35d1_wdt->wdt_dev.bootstatus = WDIOF_CARDRESET;
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
}
if (val & WDTEN)
set_bit(WDOG_HW_RUNNING, &ma35d1_wdt->wdt_dev.status);
watchdog_set_drvdata(&ma35d1_wdt->wdt_dev, ma35d1_wdt);
watchdog_set_nowayout(&ma35d1_wdt->wdt_dev, nowayout);
watchdog_init_timeout(&ma35d1_wdt->wdt_dev, timeout, &pdev->dev);
ma35d1_wdt_set_timeout(&ma35d1_wdt->wdt_dev,
ma35d1_wdt->wdt_dev.timeout);
ret = devm_request_irq(dev, ma35d1_wdt->irq, ma35d1_wdt_isr, 0,
dev_name(dev), ma35d1_wdt);
if (ret)
return dev_err_probe(dev, ret, "cannot claim IRQ %d\n",
ma35d1_wdt->irq);
if (device_property_read_bool(dev, "wakeup-source")) {
ret = devm_device_init_wakeup(dev);
if (ret)
return ret;
ret = devm_pm_set_wake_irq(dev, ma35d1_wdt->irq);
if (ret)
return ret;
}
watchdog_stop_on_reboot(&ma35d1_wdt->wdt_dev);
watchdog_stop_on_unregister(&ma35d1_wdt->wdt_dev);
ret = devm_watchdog_register_device(dev, &ma35d1_wdt->wdt_dev);
if (ret)
return ret;
return 0;
}
static int ma35d1_wdt_suspend(struct device *dev)
{
struct ma35d1_wdt_dev *ma35d1_wdt = dev_get_drvdata(dev);
if (watchdog_active(&ma35d1_wdt->wdt_dev) ||
watchdog_hw_running(&ma35d1_wdt->wdt_dev)) {
u32 val;
int ret;
guard(spinlock_irqsave)(&ma35d1_wdt->lock);
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
if (device_may_wakeup(dev)) {
val &= ~RSTEN;
val |= (INTEN | WKEN);
} else {
val &= ~(WDTEN | RSTEN);
}
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
if (ret) {
dev_err(dev, "Wait for WDTEN SYNC timeout!\n");
return ret;
}
}
return 0;
}
static int ma35d1_wdt_resume(struct device *dev)
{
struct ma35d1_wdt_dev *ma35d1_wdt = dev_get_drvdata(dev);
if (watchdog_active(&ma35d1_wdt->wdt_dev) ||
watchdog_hw_running(&ma35d1_wdt->wdt_dev)) {
u32 val;
int ret;
guard(spinlock_irqsave)(&ma35d1_wdt->lock);
val = readl_relaxed(ma35d1_wdt->wdt_base + REG_WDT_CTL);
if (device_may_wakeup(dev)) {
val |= RSTEN;
val &= ~(INTEN | WKEN);
} else {
val |= (WDTEN | RSTEN);
}
writel_relaxed(val, ma35d1_wdt->wdt_base + REG_WDT_CTL);
writel_relaxed(RESET_COUNTER,
ma35d1_wdt->wdt_base + REG_WDT_RSTCNT);
ret = ma35d1_wdt_wait_sync(ma35d1_wdt);
if (ret) {
dev_err(dev, "Wait for WDTEN SYNC timeout!\n");
return ret;
}
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ma35d1_wdt_pm_ops, ma35d1_wdt_suspend,
ma35d1_wdt_resume);
static const struct of_device_id ma35d1_wdt_dt_ids[] = {
{ .compatible = "nuvoton,ma35d1-wdt" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, ma35d1_wdt_dt_ids);
static struct platform_driver ma35d1_wdt_driver = {
.probe = ma35d1_wdt_probe,
.driver = {
.name = DRV_NAME,
.pm = pm_ptr(&ma35d1_wdt_pm_ops),
.of_match_table = ma35d1_wdt_dt_ids,
},
};
module_platform_driver(ma35d1_wdt_driver);
module_param(timeout, uint, 0);
MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(
nowayout,
"Watchdog cannot be stopped once started (default=" __MODULE_STRING(
WATCHDOG_NOWAYOUT) ")");
MODULE_AUTHOR("Zi-Yu Chen <zychennvt@gmail.com>");
MODULE_DESCRIPTION("Nuvoton MA35D1 Watchdog Timer Driver");
MODULE_LICENSE("GPL");
+1 -1
View File
@@ -321,7 +321,7 @@ static int gti_wdt_probe(struct platform_device *pdev)
err = devm_request_irq(dev, irq, gti_wdt_interrupt, 0,
pdev->name, &priv->wdev);
if (err)
return dev_err_probe(dev, err, "Failed to register interrupt handler\n");
return err;
dev_info(dev, "Watchdog enabled (timeout=%d sec)\n", wdog_dev->timeout);
return 0;
+7 -7
View File
@@ -245,13 +245,13 @@ static int max63xx_wdt_probe(struct platform_device *pdev)
}
static const struct platform_device_id max63xx_id_table[] = {
{ "max6369_wdt", (kernel_ulong_t)max6369_table, },
{ "max6370_wdt", (kernel_ulong_t)max6369_table, },
{ "max6371_wdt", (kernel_ulong_t)max6371_table, },
{ "max6372_wdt", (kernel_ulong_t)max6371_table, },
{ "max6373_wdt", (kernel_ulong_t)max6373_table, },
{ "max6374_wdt", (kernel_ulong_t)max6373_table, },
{ },
{ .name = "max6369_wdt", .driver_data = (kernel_ulong_t)max6369_table },
{ .name = "max6370_wdt", .driver_data = (kernel_ulong_t)max6369_table },
{ .name = "max6371_wdt", .driver_data = (kernel_ulong_t)max6371_table },
{ .name = "max6372_wdt", .driver_data = (kernel_ulong_t)max6371_table },
{ .name = "max6373_wdt", .driver_data = (kernel_ulong_t)max6373_table },
{ .name = "max6374_wdt", .driver_data = (kernel_ulong_t)max6373_table },
{ }
};
MODULE_DEVICE_TABLE(platform, max63xx_id_table);
+3 -3
View File
@@ -235,9 +235,9 @@ static int max77620_wdt_probe(struct platform_device *pdev)
}
static const struct platform_device_id max77620_wdt_devtype[] = {
{ "max77620-watchdog", (kernel_ulong_t)&max77620_wdt_data },
{ "max77714-watchdog", (kernel_ulong_t)&max77714_wdt_data },
{ },
{ .name = "max77620-watchdog", .driver_data = (kernel_ulong_t)&max77620_wdt_data },
{ .name = "max77714-watchdog", .driver_data = (kernel_ulong_t)&max77714_wdt_data },
{ }
};
MODULE_DEVICE_TABLE(platform, max77620_wdt_devtype);
+2 -2
View File
@@ -422,8 +422,8 @@ static int mtk_wdt_probe(struct platform_device *pdev)
mtk_wdt->wdt_dev.info = &mtk_wdt_pt_info;
mtk_wdt->wdt_dev.pretimeout = WDT_MAX_TIMEOUT / 2;
} else {
if (irq == -EPROBE_DEFER)
return -EPROBE_DEFER;
if (irq != -ENXIO)
return irq;
mtk_wdt->wdt_dev.info = &mtk_wdt_info;
}
+215 -2
View File
@@ -7,14 +7,52 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/watchdog.h>
#define NPCM_WTCR 0x1C
/* NPCM GCR module */
#define NPCM_RESSR_OFFSET 0x6C
#define NPCM_INTCR2_OFFSET 0x60
#define NPCM7XX_SCRPAD2_OFFSET 0x84
#define NPCM8XX_SCRPAD10_OFFSET 0xE28
#define NPCM_PORST BIT(31)
#define NPCM_CORST BIT(30)
#define NPCM_WD0RST BIT(29)
#define NPCM_SWR1RST BIT(28)
#define NPCM_SWR2RST BIT(27)
#define NPCM_SWR3RST BIT(26)
#define NPCM_SWR4RST BIT(25)
#define NPCM_WD1RST BIT(24)
#define NPCM_WD2RST BIT(23)
#define NPCM_RST GENMASK(31, 23)
#define NPCM8XX_TIP_RESET BIT(25) /* Replaces SWRST4 on NPCM8xx */
/* Per-instance mapping of MMIO base address to its RESSR/INTCR2 reset bit. */
struct npcm_wdt_rst_map {
phys_addr_t base;
u32 rst_bit;
};
struct npcm_wdt_status_map {
u32 rst_bit;
u32 wdiof_flag;
};
struct npcm_wdt_data {
const struct npcm_wdt_rst_map *rst_map;
unsigned int rst_map_size;
const struct npcm_wdt_status_map *status_map;
unsigned int status_map_size;
};
#define NPCM_WTCLK (BIT(10) | BIT(11)) /* Clock divider */
#define NPCM_WTE BIT(7) /* Enable */
#define NPCM_WTIE BIT(6) /* Enable irq */
@@ -47,6 +85,50 @@ struct npcm_wdt {
struct clk *clk;
};
static const struct npcm_wdt_rst_map npcm750_rst_map[] = {
{ 0xf000801c, NPCM_WD0RST },
{ 0xf000901c, NPCM_WD1RST },
{ 0xf000a01c, NPCM_WD2RST },
};
static const struct npcm_wdt_status_map npcm750_status_map[] = {
{ NPCM_PORST, WDIOF_OVERHEAT },
{ NPCM_CORST, WDIOF_FANFAULT },
{ NPCM_SWR1RST, WDIOF_EXTERN1 },
{ NPCM_SWR2RST, WDIOF_EXTERN2 },
{ NPCM_SWR3RST, WDIOF_POWERUNDER },
{ NPCM_SWR4RST, WDIOF_POWEROVER },
};
static const struct npcm_wdt_data __maybe_unused npcm750_data = {
.rst_map = npcm750_rst_map,
.rst_map_size = ARRAY_SIZE(npcm750_rst_map),
.status_map = npcm750_status_map,
.status_map_size = ARRAY_SIZE(npcm750_status_map),
};
static const struct npcm_wdt_rst_map npcm845_rst_map[] = {
{ 0xf000801c, NPCM_WD0RST },
{ 0xf000901c, NPCM_WD1RST },
{ 0xf000a01c, NPCM_WD2RST },
};
static const struct npcm_wdt_status_map npcm845_status_map[] = {
{ NPCM_PORST, WDIOF_OVERHEAT },
{ NPCM_CORST, WDIOF_FANFAULT },
{ NPCM_SWR1RST, WDIOF_EXTERN1 },
{ NPCM_SWR2RST, WDIOF_EXTERN2 },
{ NPCM_SWR3RST, WDIOF_POWERUNDER },
{ NPCM8XX_TIP_RESET, WDIOF_POWEROVER },
};
static const struct npcm_wdt_data __maybe_unused npcm845_data = {
.rst_map = npcm845_rst_map,
.rst_map_size = ARRAY_SIZE(npcm845_rst_map),
.status_map = npcm845_status_map,
.status_map_size = ARRAY_SIZE(npcm845_status_map),
};
static inline struct npcm_wdt *to_npcm_wdt(struct watchdog_device *wdd)
{
return container_of(wdd, struct npcm_wdt, wdd);
@@ -169,6 +251,111 @@ static bool npcm_is_running(struct watchdog_device *wdd)
return readl(wdt->reg) & NPCM_WTE;
}
static void npcm_get_reset_status(struct npcm_wdt *wdt, struct device *dev,
const struct npcm_wdt_data *data,
resource_size_t start)
{
struct regmap *gcr_regmap;
u32 rstval = 0;
unsigned int i;
int ret;
if (!data)
return;
gcr_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
"nuvoton,sysgcr");
if (IS_ERR(gcr_regmap)) {
dev_warn(dev,
"Failed to find nuvoton,sysgcr, WD reset status not supported\n");
return;
}
if (of_device_is_compatible(dev->of_node, "nuvoton,npcm845-wdt")) {
ret = regmap_read(gcr_regmap, NPCM_INTCR2_OFFSET, &rstval);
if (ret) {
dev_warn(dev, "Failed to read INTCR2 reset status: %d\n",
ret);
return;
}
if (rstval & NPCM_RST) {
ret = regmap_write(gcr_regmap, NPCM_INTCR2_OFFSET,
rstval & ~NPCM_RST);
if (ret) {
dev_warn(dev,
"Failed to clear INTCR2 reset status: %d\n",
ret);
return;
}
ret = regmap_write(gcr_regmap, NPCM8XX_SCRPAD10_OFFSET,
rstval);
if (ret) {
dev_warn(dev,
"Failed to cache reset status in SCRPAD10: %d\n",
ret);
return;
}
} else {
ret = regmap_read(gcr_regmap, NPCM8XX_SCRPAD10_OFFSET,
&rstval);
if (ret) {
dev_warn(dev,
"Failed to read cached reset status from SCRPAD10: %d\n",
ret);
return;
}
}
} else if (of_device_is_compatible(dev->of_node, "nuvoton,npcm750-wdt")) {
ret = regmap_read(gcr_regmap, NPCM_RESSR_OFFSET, &rstval);
if (ret) {
dev_warn(dev, "Failed to read RESSR reset status: %d\n",
ret);
return;
}
if (rstval & NPCM_RST) {
ret = regmap_write(gcr_regmap, NPCM_RESSR_OFFSET,
rstval & ~NPCM_RST);
if (ret) {
dev_warn(dev, "Failed to clear RESSR reset status: %d\n", ret);
return;
}
ret = regmap_write(gcr_regmap, NPCM7XX_SCRPAD2_OFFSET,
rstval);
if (ret) {
dev_warn(dev,
"Failed to cache reset status in SCRPAD2: %d\n", ret);
return;
}
} else {
ret = regmap_read(gcr_regmap, NPCM7XX_SCRPAD2_OFFSET,
&rstval);
if (ret) {
dev_warn(dev,
"Failed to read cached reset status from SCRPAD2: %d\n",
ret);
return;
}
}
}
for (i = 0; i < data->status_map_size; i++) {
if (rstval & data->status_map[i].rst_bit)
wdt->wdd.bootstatus |= data->status_map[i].wdiof_flag;
}
for (i = 0; i < data->rst_map_size; i++) {
if (data->rst_map[i].base == start &&
rstval & data->rst_map[i].rst_bit) {
wdt->wdd.bootstatus |= WDIOF_CARDRESET;
break;
}
}
}
static const struct watchdog_info npcm_wdt_info = {
.identity = KBUILD_MODNAME,
.options = WDIOF_SETTIMEOUT
@@ -176,6 +363,20 @@ static const struct watchdog_info npcm_wdt_info = {
| WDIOF_MAGICCLOSE,
};
static const struct watchdog_info npcm_wdt_rst_info = {
.identity = KBUILD_MODNAME,
.options = WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_CARDRESET
| WDIOF_OVERHEAT
| WDIOF_FANFAULT
| WDIOF_EXTERN1
| WDIOF_EXTERN2
| WDIOF_POWERUNDER
| WDIOF_POWEROVER,
};
static const struct watchdog_ops npcm_wdt_ops = {
.owner = THIS_MODULE,
.start = npcm_wdt_start,
@@ -188,7 +389,10 @@ static const struct watchdog_ops npcm_wdt_ops = {
static int npcm_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct npcm_wdt_data *data = device_get_match_data(dev);
struct resource *res;
struct npcm_wdt *wdt;
resource_size_t start;
int irq;
int ret;
@@ -196,10 +400,16 @@ static int npcm_wdt_probe(struct platform_device *pdev)
if (!wdt)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -EINVAL;
wdt->reg = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(wdt->reg))
return PTR_ERR(wdt->reg);
start = res->start;
wdt->clk = devm_clk_get_optional(&pdev->dev, NULL);
if (IS_ERR(wdt->clk))
return PTR_ERR(wdt->clk);
@@ -208,7 +418,7 @@ static int npcm_wdt_probe(struct platform_device *pdev)
if (irq < 0)
return irq;
wdt->wdd.info = &npcm_wdt_info;
wdt->wdd.info = data ? &npcm_wdt_rst_info : &npcm_wdt_info;
wdt->wdd.ops = &npcm_wdt_ops;
wdt->wdd.min_timeout = 1;
wdt->wdd.max_timeout = 2750;
@@ -220,6 +430,8 @@ static int npcm_wdt_probe(struct platform_device *pdev)
/* Ensure timeout is able to be represented by the hardware */
npcm_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
npcm_get_reset_status(wdt, dev, data, start);
if (npcm_is_running(&wdt->wdd)) {
/* Restart with the default or device-tree specified timeout */
npcm_wdt_start(&wdt->wdd);
@@ -243,7 +455,8 @@ static int npcm_wdt_probe(struct platform_device *pdev)
#ifdef CONFIG_OF
static const struct of_device_id npcm_wdt_match[] = {
{.compatible = "nuvoton,wpcm450-wdt"},
{.compatible = "nuvoton,npcm750-wdt"},
{.compatible = "nuvoton,npcm750-wdt", .data = &npcm750_data},
{.compatible = "nuvoton,npcm845-wdt", .data = &npcm845_data},
{},
};
MODULE_DEVICE_TABLE(of, npcm_wdt_match);
+10 -6
View File
@@ -606,6 +606,10 @@ static int orion_wdt_probe(struct platform_device *pdev)
/* Request the IRQ only after the watchdog is disabled */
irq = platform_get_irq_optional(pdev, 0);
if (irq < 0 && irq != -ENXIO) {
ret = irq;
goto disable_clk;
}
if (irq > 0) {
/*
* Not all supported platforms specify an interrupt for the
@@ -613,22 +617,22 @@ static int orion_wdt_probe(struct platform_device *pdev)
*/
ret = devm_request_irq(&pdev->dev, irq, orion_wdt_irq, 0,
pdev->name, dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request IRQ\n");
if (ret < 0)
goto disable_clk;
}
}
/* Optional 2nd interrupt for pretimeout */
irq = platform_get_irq_optional(pdev, 1);
if (irq < 0 && irq != -ENXIO) {
ret = irq;
goto disable_clk;
}
if (irq > 0) {
orion_wdt_info.options |= WDIOF_PRETIMEOUT;
ret = devm_request_irq(&pdev->dev, irq, orion_wdt_pre_irq,
0, pdev->name, dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request IRQ\n");
if (ret < 0)
goto disable_clk;
}
}
+45
View File
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2026 Google LLC
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/nmi.h>
#include <linux/watchdog.h>
#include "watchdog_pretimeout.h"
/**
* pretimeout_dump - Dump on watchdog pretimeout event
* @wdd: watchdog_device
*
* Dump all cpu backtrace on pretimeout event.
*/
static void pretimeout_dump(struct watchdog_device *wdd)
{
pr_alert("watchdog%d: pretimeout event\n", wdd->id);
if (!trigger_all_cpu_backtrace())
pr_alert("trigger_all_cpu_backtrace() isn't available\n");
}
static struct watchdog_governor watchdog_gov_dump = {
.name = "dump",
.pretimeout = pretimeout_dump,
};
static int __init watchdog_gov_dump_register(void)
{
return watchdog_register_governor(&watchdog_gov_dump);
}
static void __exit watchdog_gov_dump_unregister(void)
{
watchdog_unregister_governor(&watchdog_gov_dump);
}
module_init(watchdog_gov_dump_register);
module_exit(watchdog_gov_dump_unregister);
MODULE_AUTHOR("Tzung-Bi Shih <tzungbi@kernel.org>");
MODULE_DESCRIPTION("Dump watchdog pretimeout governor");
MODULE_LICENSE("GPL");
+37 -18
View File
@@ -44,6 +44,7 @@ struct qcom_wdt_match_data {
bool pretimeout;
u32 max_tick_count;
u32 wdt_reason_val;
u32 powerunder_reason_val;
};
struct qcom_wdt {
@@ -164,7 +165,8 @@ static const struct watchdog_info qcom_wdt_info = {
.options = WDIOF_KEEPALIVEPING
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT
| WDIOF_CARDRESET,
| WDIOF_CARDRESET
| WDIOF_POWERUNDER,
.identity = KBUILD_MODNAME,
};
@@ -173,7 +175,8 @@ static const struct watchdog_info qcom_wdt_pt_info = {
| WDIOF_MAGICCLOSE
| WDIOF_SETTIMEOUT
| WDIOF_PRETIMEOUT
| WDIOF_CARDRESET,
| WDIOF_CARDRESET
| WDIOF_POWERUNDER,
.identity = KBUILD_MODNAME,
};
@@ -188,6 +191,15 @@ static const struct qcom_wdt_match_data match_data_ipq5424 = {
.pretimeout = true,
.max_tick_count = 0xFFFFFU,
.wdt_reason_val = 5,
.powerunder_reason_val = 1,
};
static const struct qcom_wdt_match_data match_data_ipq9574 = {
.offset = reg_offset_data_kpss,
.pretimeout = true,
.max_tick_count = 0xFFFFFU,
.wdt_reason_val = 1,
.powerunder_reason_val = 32,
};
static const struct qcom_wdt_match_data match_data_kpss = {
@@ -197,12 +209,13 @@ static const struct qcom_wdt_match_data match_data_kpss = {
};
static int qcom_wdt_get_bootstatus(struct device *dev, struct qcom_wdt *wdt,
u32 val)
const struct qcom_wdt_match_data *data)
{
struct device_node *imem;
struct resource res;
void __iomem *addr;
int ret;
int val;
imem = of_parse_phandle(dev->of_node, "sram", 0);
if (!imem) {
@@ -222,8 +235,11 @@ static int qcom_wdt_get_bootstatus(struct device *dev, struct qcom_wdt *wdt,
if (!addr)
return -ENOMEM;
if (readl(addr) == val)
val = readl(addr);
if (val == data->wdt_reason_val)
wdt->wdd.bootstatus = WDIOF_CARDRESET;
else if (val == data->powerunder_reason_val)
wdt->wdd.bootstatus = WDIOF_POWERUNDER;
iounmap(addr);
@@ -287,21 +303,22 @@ static int qcom_wdt_probe(struct platform_device *pdev)
return -EINVAL;
}
wdt->wdd.info = &qcom_wdt_info;
/* check if there is pretimeout support */
irq = platform_get_irq_optional(pdev, 0);
if (data->pretimeout && irq > 0) {
ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,
"wdt_bark", &wdt->wdd);
if (ret)
return ret;
if (data->pretimeout) {
irq = platform_get_irq_optional(pdev, 0);
if (irq < 0 && irq != -ENXIO)
return irq;
if (irq > 0) {
ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,
"wdt_bark", &wdt->wdd);
if (ret)
return ret;
wdt->wdd.info = &qcom_wdt_pt_info;
wdt->wdd.pretimeout = 1;
} else {
if (irq == -EPROBE_DEFER)
return -EPROBE_DEFER;
wdt->wdd.info = &qcom_wdt_info;
wdt->wdd.info = &qcom_wdt_pt_info;
wdt->wdd.pretimeout = 1;
}
}
wdt->wdd.ops = &qcom_wdt_ops;
@@ -310,7 +327,7 @@ static int qcom_wdt_probe(struct platform_device *pdev)
wdt->wdd.parent = dev;
wdt->layout = data->offset;
ret = qcom_wdt_get_bootstatus(dev, wdt, data->wdt_reason_val);
ret = qcom_wdt_get_bootstatus(dev, wdt, data);
if (ret)
dev_err(dev, "failed to get the bootstatus, %d\n", ret);
@@ -366,7 +383,9 @@ static const struct dev_pm_ops qcom_wdt_pm_ops = {
};
static const struct of_device_id qcom_wdt_of_table[] = {
{ .compatible = "qcom,apss-wdt-ipq5332", .data = &match_data_ipq9574 },
{ .compatible = "qcom,apss-wdt-ipq5424", .data = &match_data_ipq5424 },
{ .compatible = "qcom,apss-wdt-ipq9574", .data = &match_data_ipq9574 },
{ .compatible = "qcom,kpss-timer", .data = &match_data_apcs_tmr },
{ .compatible = "qcom,scss-timer", .data = &match_data_apcs_tmr },
{ .compatible = "qcom,kpss-wdt", .data = &match_data_kpss },
+39 -37
View File
@@ -27,6 +27,7 @@
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/watchdog.h>
#define OTTO_WDT_REG_CNTR 0x0
@@ -65,7 +66,7 @@
struct otto_wdt_ctrl {
struct watchdog_device wdev;
struct device *dev;
void __iomem *base;
struct regmap *regmap;
unsigned int clk_rate_khz;
int irq_phase1;
};
@@ -73,24 +74,17 @@ struct otto_wdt_ctrl {
static int otto_wdt_start(struct watchdog_device *wdev)
{
struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
u32 v;
v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
v |= OTTO_WDT_CTRL_ENABLE;
iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
regmap_set_bits(ctrl->regmap, OTTO_WDT_REG_CTRL, OTTO_WDT_CTRL_ENABLE);
return 0;
}
static int otto_wdt_stop(struct watchdog_device *wdev)
{
struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
u32 v;
v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
v &= ~OTTO_WDT_CTRL_ENABLE;
iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
regmap_clear_bits(ctrl->regmap, OTTO_WDT_REG_CTRL,
OTTO_WDT_CTRL_ENABLE);
return 0;
}
@@ -98,8 +92,7 @@ static int otto_wdt_ping(struct watchdog_device *wdev)
{
struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
iowrite32(OTTO_WDT_CNTR_PING, ctrl->base + OTTO_WDT_REG_CNTR);
regmap_write(ctrl->regmap, OTTO_WDT_REG_CNTR, OTTO_WDT_CNTR_PING);
return 0;
}
@@ -125,7 +118,7 @@ static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned in
unsigned int total_ticks;
unsigned int prescale;
unsigned int tick_ms;
u32 v;
u32 mask, val;
do {
prescale = prescale_next;
@@ -141,14 +134,11 @@ static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned in
} while (phase1_ticks > OTTO_WDT_PHASE_TICKS_MAX
|| phase2_ticks > OTTO_WDT_PHASE_TICKS_MAX);
v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
v &= ~(OTTO_WDT_CTRL_PRESCALE | OTTO_WDT_CTRL_PHASE1 | OTTO_WDT_CTRL_PHASE2);
v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE1, phase1_ticks - 1);
v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE2, phase2_ticks - 1);
v |= FIELD_PREP(OTTO_WDT_CTRL_PRESCALE, prescale);
iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
mask = OTTO_WDT_CTRL_PRESCALE | OTTO_WDT_CTRL_PHASE1 | OTTO_WDT_CTRL_PHASE2;
val = FIELD_PREP(OTTO_WDT_CTRL_PHASE1, phase1_ticks - 1);
val |= FIELD_PREP(OTTO_WDT_CTRL_PHASE2, phase2_ticks - 1);
val |= FIELD_PREP(OTTO_WDT_CTRL_PRESCALE, prescale);
regmap_update_bits(ctrl->regmap, OTTO_WDT_REG_CTRL, mask, val);
timeout_ms = total_ticks * tick_ms;
ctrl->wdev.timeout = timeout_ms / 1000;
@@ -192,7 +182,7 @@ static int otto_wdt_restart(struct watchdog_device *wdev, unsigned long reboot_m
/* Configure for shortest timeout and wait for reset to occur */
v = FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, reset_mode) | OTTO_WDT_CTRL_ENABLE;
iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
regmap_write(ctrl->regmap, OTTO_WDT_REG_CTRL, v);
mdelay(3 * otto_wdt_tick_ms(ctrl, 0));
@@ -203,7 +193,7 @@ static irqreturn_t otto_wdt_phase1_isr(int irq, void *dev_id)
{
struct otto_wdt_ctrl *ctrl = dev_id;
iowrite32(OTTO_WDT_INTR_PHASE_1, ctrl->base + OTTO_WDT_REG_INTR);
regmap_write(ctrl->regmap, OTTO_WDT_REG_INTR, OTTO_WDT_INTR_PHASE_1);
dev_crit(ctrl->dev, "phase 1 timeout\n");
watchdog_notify_pretimeout(&ctrl->wdev);
@@ -249,7 +239,6 @@ static int otto_wdt_probe_reset_mode(struct otto_wdt_ctrl *ctrl)
const struct fwnode_handle *node = ctrl->dev->fwnode;
int mode_count;
u32 mode;
u32 v;
if (!node)
return -ENXIO;
@@ -271,19 +260,25 @@ static int otto_wdt_probe_reset_mode(struct otto_wdt_ctrl *ctrl)
else
return -EINVAL;
v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
v &= ~OTTO_WDT_CTRL_RST_MODE;
v |= FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, mode);
iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
regmap_update_bits(ctrl->regmap, OTTO_WDT_REG_CTRL,
OTTO_WDT_CTRL_RST_MODE,
FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, mode));
return 0;
}
static const struct regmap_config realtek_otto_wdt_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.disable_locking = true,
};
static int otto_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct otto_wdt_ctrl *ctrl;
unsigned int max_tick_ms;
void __iomem *base;
int ret;
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
@@ -291,18 +286,25 @@ static int otto_wdt_probe(struct platform_device *pdev)
return -ENOMEM;
ctrl->dev = dev;
ctrl->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ctrl->base))
return PTR_ERR(ctrl->base);
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
ctrl->regmap = devm_regmap_init_mmio(dev, base,
&realtek_otto_wdt_regmap_config);
if (IS_ERR(ctrl->regmap)) {
dev_err(dev, "regmap init failed\n");
return PTR_ERR(ctrl->regmap);
}
ret = otto_wdt_probe_clk(ctrl);
if (ret)
return ret;
/* Clear any old interrupts and reset initial state */
iowrite32(OTTO_WDT_INTR_PHASE_1 | OTTO_WDT_INTR_PHASE_2,
ctrl->base + OTTO_WDT_REG_INTR);
iowrite32(OTTO_WDT_CTRL_DEFAULT, ctrl->base + OTTO_WDT_REG_CTRL);
regmap_write(ctrl->regmap, OTTO_WDT_REG_INTR,
OTTO_WDT_INTR_PHASE_1 | OTTO_WDT_INTR_PHASE_2);
regmap_write(ctrl->regmap, OTTO_WDT_REG_CTRL, OTTO_WDT_CTRL_DEFAULT);
ctrl->irq_phase1 = platform_get_irq_byname(pdev, "phase1");
if (ctrl->irq_phase1 < 0)
@@ -311,7 +313,7 @@ static int otto_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, ctrl->irq_phase1, otto_wdt_phase1_isr, 0,
"realtek-otto-wdt", ctrl);
if (ret)
return dev_err_probe(dev, ret, "Failed to get IRQ for phase1\n");
return ret;
ret = otto_wdt_probe_reset_mode(ctrl);
if (ret)
+1 -3
View File
@@ -291,10 +291,8 @@ static int sama5d4_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, irq, sama5d4_wdt_irq_handler,
IRQF_SHARED | IRQF_IRQPOLL |
IRQF_NO_SUSPEND, pdev->name, pdev);
if (ret) {
dev_err(dev, "cannot register interrupt handler\n");
if (ret)
return ret;
}
}
watchdog_init_timeout(wdd, wdt_timeout, dev);
+1 -3
View File
@@ -293,10 +293,8 @@ static int sprd_wdt_probe(struct platform_device *pdev)
ret = devm_request_irq(dev, wdt->irq, sprd_wdt_isr, IRQF_NO_SUSPEND,
"sprd-wdt", (void *)wdt);
if (ret) {
dev_err(dev, "failed to register irq\n");
if (ret)
return ret;
}
wdt->wdd.info = &sprd_wdt_info;
wdt->wdd.ops = &sprd_wdt_ops;
+4 -1
View File
@@ -287,8 +287,11 @@ static int stm32_iwdg_irq_init(struct platform_device *pdev,
return 0;
irq = platform_get_irq_optional(pdev, 0);
if (irq <= 0)
if (irq < 0) {
if (irq != -ENXIO)
return irq;
return 0;
}
if (of_property_read_bool(np, "wakeup-source")) {
ret = devm_device_init_wakeup(dev);
+1
View File
@@ -247,6 +247,7 @@ static const struct pci_device_id wdt_pci_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX855) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, wdt_pci_table);
static struct pci_driver wdt_driver = {
.name = "via_wdt",
+17 -5
View File
@@ -49,7 +49,7 @@ static int wdt_cfg_leave = 0xAA;/* key to lock configuration space */
enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf,
w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p,
w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793,
nct6795, nct6796, nct6102, nct6116 };
nct6795, nct6796, nct6102, nct6116, nct6126 };
static int timeout; /* in seconds */
module_param(timeout, int, 0);
@@ -94,7 +94,9 @@ MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)");
#define NCT6775_ID 0xb4
#define NCT6776_ID 0xc3
#define NCT6102_ID 0xc4
#define NCT6116_ID 0xd2
#define NCT6116_ID 0xd2 /* also NCT6126D */
#define NCT6126_VER_A_LOW_ID 0x83 /* ... version A */
#define NCT6126_VER_B_LOW_ID 0x84 /* ... version B */
#define NCT6779_ID 0xc5
#define NCT6791_ID 0xc8
#define NCT6792_ID 0xc9
@@ -217,6 +219,7 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip)
case nct6796:
case nct6102:
case nct6116:
case nct6126:
/*
* These chips have a fixed WDTO# output pin (W83627UHG),
* or support more than one WDTO# output pin.
@@ -241,6 +244,7 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip)
pr_info("Watchdog already running. Resetting timeout to %d sec\n",
wdog->timeout);
superio_outb(cr_wdt_timeout, wdog->timeout);
set_bit(WDOG_HW_RUNNING, &wdog->status);
}
}
@@ -313,9 +317,8 @@ static unsigned int wdt_get_time(struct watchdog_device *wdog)
* Kernel Interfaces
*/
static const struct watchdog_info wdt_info = {
static struct watchdog_info wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.identity = "W83627HF Watchdog",
};
static const struct watchdog_ops wdt_ops = {
@@ -428,7 +431,12 @@ static int wdt_find(int addr)
cr_wdt_csr = NCT6102D_WDT_CSR;
break;
case NCT6116_ID:
ret = nct6116;
val = superio_inb(0x21);
if (val == NCT6126_VER_A_LOW_ID || val == NCT6126_VER_B_LOW_ID)
ret = nct6126;
else
ret = nct6116;
cr_wdt_timeout = NCT6102D_WDT_TIMEOUT;
cr_wdt_control = NCT6102D_WDT_CONTROL;
cr_wdt_csr = NCT6102D_WDT_CSR;
@@ -499,6 +507,7 @@ static int __init wdt_init(void)
"NCT6796",
"NCT6102",
"NCT6116",
"NCT6126"
};
/* Apply system-specific quirks */
@@ -516,6 +525,9 @@ static int __init wdt_init(void)
pr_info("WDT driver for %s Super I/O chip initialising\n",
chip_name[chip]);
snprintf(wdt_info.identity, sizeof(wdt_info.identity), "%s Watchdog",
chip_name[chip]);
watchdog_init_timeout(&wdt_dev, timeout, NULL);
watchdog_set_nowayout(&wdt_dev, nowayout);
watchdog_stop_on_reboot(&wdt_dev);
+23 -3
View File
@@ -240,7 +240,9 @@ EXPORT_SYMBOL_GPL(watchdog_set_restart_priority);
static int ___watchdog_register_device(struct watchdog_device *wdd)
{
int ret, id = -1;
int ret, min_id, id = -1;
struct device_node *np;
char alias[16];
if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
return -EINVAL;
@@ -265,8 +267,26 @@ static int ___watchdog_register_device(struct watchdog_device *wdd)
GFP_KERNEL);
}
if (id < 0)
id = ida_alloc_max(&watchdog_ida, MAX_DOGS - 1, GFP_KERNEL);
/*
* Find an id which is not pre-assigned via a DT alias to some
* other, possibly not yet probed, watchdog device.
*/
if (id < 0) {
np = of_find_node_by_path("/aliases");
for (min_id = 0; ; min_id = id + 1) {
id = ida_alloc_range(&watchdog_ida, min_id, MAX_DOGS - 1,
GFP_KERNEL);
if (!np || id < 0)
break;
scnprintf(alias, sizeof(alias), "watchdog%d", id);
if (!of_get_property(np, alias, NULL))
break;
ida_free(&watchdog_ida, id);
}
of_node_put(np);
}
if (id < 0)
return id;
+3 -1
View File
@@ -24,7 +24,9 @@ int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf);
int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
const char *buf);
#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP)
#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_DUMP)
#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "dump"
#elif IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP)
#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "noop"
#elif IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC)
#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "panic"
+26 -1
View File
@@ -7,7 +7,9 @@
*/
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
@@ -309,6 +311,29 @@ static struct watchdog_ops wdat_wdt_ops = {
.set_timeout = wdat_wdt_set_timeout,
};
static void __iomem *wdat_wdt_map_mem(struct device *dev, struct resource *res)
{
resource_size_t size = resource_size(res);
void *addr;
/* Map memory region without reserving it if it falls inside ACPI NVS */
if (region_intersects(res->start, size, IORESOURCE_MEM,
IORES_DESC_ACPI_NV_STORAGE) == REGION_INTERSECTS) {
dev_warn(dev, "%pR is inside ACPI NVS, mapping without reservation\n",
res);
addr = devm_memremap(dev, res->start, size, MEMREMAP_WB);
if (IS_ERR(addr)) {
dev_err(dev, "failed to map resource %pR\n", res);
return IOMEM_ERR_PTR(PTR_ERR(addr));
}
return (void __iomem __force *)addr;
}
return devm_ioremap_resource(dev, res);
}
static int wdat_wdt_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -362,7 +387,7 @@ static int wdat_wdt_probe(struct platform_device *pdev)
res = &pdev->resource[i];
if (resource_type(res) == IORESOURCE_MEM) {
reg = devm_ioremap_resource(dev, res);
reg = wdat_wdt_map_mem(dev, res);
if (IS_ERR(reg)) {
ret = PTR_ERR(reg);
goto out_put_table;
+6 -4
View File
@@ -157,27 +157,29 @@ static inline void touch_nmi_watchdog(void)
* to allow calling code to fall back to some other mechanism:
*/
#ifdef arch_trigger_cpumask_backtrace
void cpumask_backtrace(const cpumask_t *mask, int exclude_cpu);
static inline bool trigger_all_cpu_backtrace(void)
{
arch_trigger_cpumask_backtrace(cpu_online_mask, -1);
cpumask_backtrace(cpu_online_mask, -1);
return true;
}
static inline bool trigger_allbutcpu_cpu_backtrace(int exclude_cpu)
{
arch_trigger_cpumask_backtrace(cpu_online_mask, exclude_cpu);
cpumask_backtrace(cpu_online_mask, exclude_cpu);
return true;
}
static inline bool trigger_cpumask_backtrace(struct cpumask *mask)
{
arch_trigger_cpumask_backtrace(mask, -1);
cpumask_backtrace(mask, -1);
return true;
}
static inline bool trigger_single_cpu_backtrace(int cpu)
{
arch_trigger_cpumask_backtrace(cpumask_of(cpu), -1);
cpumask_backtrace(cpumask_of(cpu), -1);
return true;
}
+7
View File
@@ -19,6 +19,7 @@
#include <linux/stringify.h>
#include <linux/nmi.h>
#include <linux/cpu.h>
#include <linux/export.h>
#include <linux/sched/debug.h>
#ifdef arch_trigger_cpumask_backtrace
@@ -129,4 +130,10 @@ bool nmi_cpu_backtrace(struct pt_regs *regs)
return false;
}
NOKPROBE_SYMBOL(nmi_cpu_backtrace);
void cpumask_backtrace(const cpumask_t *mask, int exclude_cpu)
{
arch_trigger_cpumask_backtrace(mask, exclude_cpu);
}
EXPORT_SYMBOL_GPL(cpumask_backtrace);
#endif