diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 22:43:21 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 22:43:21 +0100 |
| commit | d4fbe67d77c3d934bbae2c1008293d3f1f1377f5 (patch) | |
| tree | 34c25b8c7294d33953c9ad2580df78e299c81416 | |
| parent | f96ab5c085d4a0e0b3ad5256057124e92a30e38a (diff) | |
| parent | c574bdb524095d24169e229b2e3b9318c72e733a (diff) | |
| download | linux-next-history-d4fbe67d77c3d934bbae2c1008293d3f1f1377f5.tar.gz | |
Merge branch 'watchdog-next' of https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git
40 files changed, 1269 insertions, 1123 deletions
diff --git a/Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml b/Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml new file mode 100644 index 0000000000000..f1107c5527887 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/watchdog/andestech,ae350-wdt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Andes ATCWDT200 Watchdog Timer + +maintainers: + - CL Wang <cl634@andestech.com> + +allOf: + - $ref: watchdog.yaml# + +properties: + compatible: + oneOf: + - items: + - enum: + - andestech,qilai-wdt + - const: andestech,ae350-wdt + - const: andestech,ae350-wdt + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + andestech,clock-source: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [0, 1] + description: | + Select the clock source for the watchdog timer. + 0 - External clock + 1 - P clock + +required: + - compatible + - reg + - clocks + - andestech,clock-source + +unevaluatedProperties: false + +examples: + - | + watchdog@f0500000 { + compatible = "andestech,ae350-wdt"; + reg = <0xf0500000 0x20>; + clocks = <&clk_wdt>; + andestech,clock-source = <0>; + }; diff --git a/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml b/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml index 05602678c070d..845b5e8b5abc3 100644 --- a/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/apple,wdt.yaml @@ -16,7 +16,9 @@ properties: compatible: oneOf: - items: - - const: apple,t6020-wdt + - enum: + - apple,t6020-wdt + - apple,t8122-wdt - const: apple,t8103-wdt - items: - enum: diff --git a/Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml b/Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml index 0821ba0e84a3c..872a8471ef65f 100644 --- a/Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml @@ -18,12 +18,7 @@ allOf: properties: compatible: - oneOf: - - items: - - enum: - - kontron,sa67mcu-wdt - - const: kontron,sl28cpld-wdt - - const: kontron,sl28cpld-wdt + const: kontron,sl28cpld-wdt reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml b/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml index 9f861045b71e8..d647bf5f19a4b 100644 --- a/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/qcom-wdt.yaml @@ -18,15 +18,18 @@ properties: - items: - enum: - qcom,apss-wdt-glymur + - qcom,apss-wdt-hawi - qcom,kpss-wdt-ipq4019 - qcom,apss-wdt-ipq5018 - qcom,apss-wdt-ipq5332 - qcom,apss-wdt-ipq5424 - qcom,apss-wdt-ipq9574 + - qcom,apss-wdt-ipq9650 - qcom,apss-wdt-kaanapali - qcom,apss-wdt-msm8226 - qcom,apss-wdt-msm8974 - qcom,apss-wdt-msm8994 + - qcom,apss-wdt-nord - qcom,apss-wdt-qcm2290 - qcom,apss-wdt-qcs404 - qcom,apss-wdt-qcs615 @@ -40,6 +43,7 @@ properties: - qcom,apss-wdt-sdm845 - qcom,apss-wdt-sdx55 - qcom,apss-wdt-sdx65 + - qcom,apss-wdt-shikra - qcom,apss-wdt-sm6115 - qcom,apss-wdt-sm6350 - qcom,apss-wdt-sm8150 @@ -84,6 +88,12 @@ properties: minItems: 1 maxItems: 5 + sram: + maxItems: 1 + description: + A reference to an region residing in IMEM(on-chip SRAM), which contains + the system restart reason value populated by the bootloader. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/watchdog/renesas,r9a09g057-wdt.yaml b/Documentation/devicetree/bindings/watchdog/renesas,r9a09g057-wdt.yaml index 099200c4f1364..975c5aa4d747f 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas,r9a09g057-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/renesas,r9a09g057-wdt.yaml @@ -89,11 +89,11 @@ examples: - | #include <dt-bindings/clock/renesas,r9a09g057-cpg.h> - watchdog@11c00400 { + watchdog@14400000 { compatible = "renesas,r9a09g057-wdt"; - reg = <0x11c00400 0x400>; - clocks = <&cpg CPG_MOD 0x4b>, <&cpg CPG_MOD 0x4c>; + reg = <0x14400000 0x400>; + clocks = <&cpg CPG_MOD 0x4d>, <&cpg CPG_MOD 0x4e>; clock-names = "pclk", "oscclk"; - resets = <&cpg 0x75>; + resets = <&cpg 0x76>; power-domains = <&cpg>; }; diff --git a/Documentation/devicetree/bindings/watchdog/renesas,rzg2l-wdt.yaml b/Documentation/devicetree/bindings/watchdog/renesas,rzg2l-wdt.yaml index a4d06c9c8b86b..40b4fb26d9c57 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas,rzg2l-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/renesas,rzg2l-wdt.yaml @@ -18,6 +18,7 @@ properties: - renesas,r9a07g044-wdt # RZ/G2{L,LC} - renesas,r9a07g054-wdt # RZ/V2L - renesas,r9a08g045-wdt # RZ/G3S + - renesas,r9a08g046-wdt # RZ/G3L - const: renesas,rzg2l-wdt - items: diff --git a/Documentation/devicetree/bindings/watchdog/renesas,rzn1-wdt.yaml b/Documentation/devicetree/bindings/watchdog/renesas,rzn1-wdt.yaml index 7e3ee533cd568..0e4b5b529e9c5 100644 --- a/Documentation/devicetree/bindings/watchdog/renesas,rzn1-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/renesas,rzn1-wdt.yaml @@ -29,7 +29,6 @@ properties: required: - compatible - reg - - interrupts - clocks allOf: diff --git a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml index 609e98cdaaffd..9d25f5e497e23 100644 --- a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml @@ -29,10 +29,12 @@ properties: - rockchip,rk3368-wdt - rockchip,rk3399-wdt - rockchip,rk3506-wdt + - rockchip,rk3528-wdt - rockchip,rk3562-wdt - rockchip,rk3568-wdt - rockchip,rk3576-wdt - rockchip,rk3588-wdt + - rockchip,rv1103b-wdt - rockchip,rv1108-wdt - rockchip,rv1126-wdt - const: snps,dw-wdt diff --git a/Documentation/devicetree/bindings/watchdog/technologic,ts4800-wdt.yaml b/Documentation/devicetree/bindings/watchdog/technologic,ts4800-wdt.yaml new file mode 100644 index 0000000000000..5c2541ac60cf3 --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/technologic,ts4800-wdt.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/watchdog/technologic,ts4800-wdt.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Technologic Systems TS-4800 Watchdog + +maintainers: + - Eduard Bostina <egbostina@gmail.com> + +properties: + compatible: + const: technologic,ts4800-wdt + + syscon: + $ref: /schemas/types.yaml#/definitions/phandle-array + items: + - items: + - description: Phandle to the FPGA's syscon + - description: Offset to the watchdog register + description: Phandle / integers array that points to the syscon node which + describes the FPGA's syscon registers. + +required: + - compatible + - syscon + +allOf: + - $ref: watchdog.yaml# + +unevaluatedProperties: false + +examples: + - | + watchdog { + compatible = "technologic,ts4800-wdt"; + syscon = <&syscon 0xe>; + timeout-sec = <10>; + }; diff --git a/Documentation/devicetree/bindings/watchdog/ti,rti-wdt.yaml b/Documentation/devicetree/bindings/watchdog/ti,rti-wdt.yaml index 62ddc284a524c..2966e5bfb6c09 100644 --- a/Documentation/devicetree/bindings/watchdog/ti,rti-wdt.yaml +++ b/Documentation/devicetree/bindings/watchdog/ti,rti-wdt.yaml @@ -23,6 +23,7 @@ allOf: properties: compatible: enum: + - ti,am62l-rti-wdt - ti,j7-rti-wdt reg: diff --git a/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt b/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt deleted file mode 100644 index 8f6caad4258d1..0000000000000 --- a/Documentation/devicetree/bindings/watchdog/ts4800-wdt.txt +++ /dev/null @@ -1,25 +0,0 @@ -Technologic Systems Watchdog - -Required properties: -- compatible: must be "technologic,ts4800-wdt" -- syscon: phandle / integer array that points to the syscon node which - describes the FPGA's syscon registers. - - phandle to FPGA's syscon - - offset to the watchdog register - -Optional property: -- timeout-sec: contains the watchdog timeout in seconds. - -Example: - -syscon: syscon@b0010000 { - compatible = "syscon", "simple-mfd"; - reg = <0xb0010000 0x3d>; - reg-io-width = <2>; - - wdt@e { - compatible = "technologic,ts4800-wdt"; - syscon = <&syscon 0xe>; - timeout-sec = <10>; - }; -} diff --git a/Documentation/watchdog/convert_drivers_to_kernel_api.rst b/Documentation/watchdog/convert_drivers_to_kernel_api.rst index e83609a5d0071..004fe6ef76e94 100644 --- a/Documentation/watchdog/convert_drivers_to_kernel_api.rst +++ b/Documentation/watchdog/convert_drivers_to_kernel_api.rst @@ -11,12 +11,12 @@ This document shall guide you for this task. The necessary steps are described as well as things to look out for. -Remove the file_operations struct +Remove the struct file_operations --------------------------------- Old drivers define their own file_operations for actions like open(), write(), etc... These are now handled by the framework and just call the driver when -needed. So, in general, the 'file_operations' struct and assorted functions can +needed. So, in general, the struct file_operations and assorted functions can go. Only very few driver-specific details have to be moved to other functions. Here is a overview of the functions and probably needed actions: @@ -36,7 +36,7 @@ Here is a overview of the functions and probably needed actions: from the driver: WDIOC_GETSUPPORT: - Returns the mandatory watchdog_info struct from the driver + Returns the mandatory struct watchdog_info from the driver WDIOC_GETSTATUS: Needs the status-callback defined, otherwise returns 0 @@ -88,8 +88,8 @@ refactoring. The rest can go. Remove the miscdevice --------------------- -Since the file_operations are gone now, you can also remove the 'struct -miscdevice'. The framework will create it on watchdog_dev_register() called by +Since the file_operations are gone now, you can also remove the struct +miscdevice. The framework will create it on watchdog_dev_register() called by watchdog_register_device():: -static struct miscdevice s3c2410wdt_miscdev = { @@ -113,10 +113,10 @@ them. Includes can be removed, too. For example:: Add the watchdog operations --------------------------- -All possible callbacks are defined in 'struct watchdog_ops'. You can find it -explained in 'watchdog-kernel-api.txt' in this directory. start() and +All possible callbacks are defined in struct watchdog_ops. You can find it +explained in watchdog-kernel-api.rst in this directory. start() and owner must be set, the rest are optional. You will easily find corresponding -functions in the old driver. Note that you will now get a pointer to the +functions in the old driver. Note that you will now get a pointer to the struct watchdog_device as a parameter to these functions, so you probably have to change the function header. Other changes are most likely not needed, because here simply happens the direct hardware access. If you have device-specific @@ -151,12 +151,12 @@ A typical function-header change looks like:: Add the watchdog device ----------------------- -Now we need to create a 'struct watchdog_device' and populate it with the -necessary information for the framework. The struct is also explained in detail -in 'watchdog-kernel-api.txt' in this directory. We pass it the mandatory -watchdog_info struct and the newly created watchdog_ops. Often, old drivers +Now we need to create a struct watchdog_device and populate it with the +necessary information for the framework. The structure is also explained in detail +in watchdog-kernel-api.rst in this directory. We pass it the mandatory +struct watchdog_info and the newly created struct watchdog_ops. Often, old drivers have their own record-keeping for things like bootstatus and timeout using -static variables. Those have to be converted to use the members in +static variables. Those have to be converted to use the members in struct watchdog_device. Note that the timeout values are unsigned int. Some drivers use signed int, so this has to be converted, too. @@ -174,7 +174,7 @@ Handle the 'nowayout' feature A few drivers use nowayout statically, i.e. there is no module parameter for it and only CONFIG_WATCHDOG_NOWAYOUT determines if the feature is going to be used. This needs to be converted by initializing the status variable of the -watchdog_device like this:: +struct watchdog_device like this:: .status = WATCHDOG_NOWAYOUT_INIT_STATUS, diff --git a/Documentation/watchdog/wdt.rst b/Documentation/watchdog/ics-wdt.rst index d97b0361535b0..b0dc29e56358d 100644 --- a/Documentation/watchdog/wdt.rst +++ b/Documentation/watchdog/ics-wdt.rst @@ -1,6 +1,8 @@ -============================================================ -WDT Watchdog Timer Interfaces For The Linux Operating System -============================================================ +.. SPDX-License-Identifier: GPL-2.0-or-later + +========================================== +Industrial Computer Source Watchdog Driver +========================================== Last Reviewed: 10/05/2007 diff --git a/Documentation/watchdog/index.rst b/Documentation/watchdog/index.rst index 1cea24681e6bd..dbc702b31a43e 100644 --- a/Documentation/watchdog/index.rst +++ b/Documentation/watchdog/index.rst @@ -4,15 +4,25 @@ Watchdog Support ================ +Kernel infrastructure +===================== + .. toctree:: :maxdepth: 1 - hpwdt - mlx-wdt - pcwd-watchdog watchdog-api watchdog-kernel-api - watchdog-parameters watchdog-pm - wdt convert_drivers_to_kernel_api + +Driver specific +=============== + +.. toctree:: + :maxdepth: 1 + + watchdog-parameters + hpwdt + ics-wdt + mlx-wdt + pcwd-watchdog diff --git a/Documentation/watchdog/watchdog-api.rst b/Documentation/watchdog/watchdog-api.rst index 983d4800d2102..0e6e526d7b957 100644 --- a/Documentation/watchdog/watchdog-api.rst +++ b/Documentation/watchdog/watchdog-api.rst @@ -158,10 +158,10 @@ available to ask what the device can do:: struct watchdog_info ident; ioctl(fd, WDIOC_GETSUPPORT, &ident); -the fields returned in the ident struct are: +the fields returned in the struct watchdog_info are: ================ ============================================= - identity a string identifying the watchdog driver + identity a string identifying the watchdog driver firmware_version the firmware version of the card if available options a flags describing what the device supports ================ ============================================= @@ -270,4 +270,13 @@ The following options are available: WDIOS_TEMPPANIC Kernel panic on temperature trip ================= ================================ -[FIXME -- better explanations] +The WDIOS_DISABLECARD option allows you to stop the watchdog timer via +software. Note that this will only work if the "nowayout" module parameter +(or CONFIG_WATCHDOG_NOWAYOUT) is not set. + +The WDIOS_ENABLECARD option turns on the watchdog timer. + +The WDIOS_TEMPPANIC option is used by some drivers (like the pcwd driver) +to trigger a system halt (typically a kernel panic or power off) when the +temperature trip point is reached. This ensures that the system is halted +immediately in case of an overheat condition. diff --git a/Documentation/watchdog/watchdog-kernel-api.rst b/Documentation/watchdog/watchdog-kernel-api.rst index 350e9037a69eb..b6a70e6d28d87 100644 --- a/Documentation/watchdog/watchdog-kernel-api.rst +++ b/Documentation/watchdog/watchdog-kernel-api.rst @@ -263,7 +263,7 @@ Helper Functions To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog timer device) you can either: - * set it statically in your watchdog_device struct with + * set it statically in your struct watchdog_device with .status = WATCHDOG_NOWAYOUT_INIT_STATUS, diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst index 2359aa32e25d5..ec5f67c060cbd 100644 --- a/Documentation/watchdog/watchdog-parameters.rst +++ b/Documentation/watchdog/watchdog-parameters.rst @@ -293,16 +293,6 @@ ixp4xx_wdt: ------------------------------------------------- -machzwd: - nowayout: - Watchdog cannot be stopped once started - (default=kernel config parameter) - action: - after watchdog resets, generate: - 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI - -------------------------------------------------- - max63xx_wdt: heartbeat: Watchdog heartbeat period in seconds from 1 to 60, default 60 @@ -541,15 +531,6 @@ sc1200wdt: ------------------------------------------------- -sc520_wdt: - timeout: - Watchdog timeout in seconds. (1 <= timeout <= 3600, default=30) - nowayout: - Watchdog cannot be stopped once started - (default=kernel config parameter) - -------------------------------------------------- - sch311x_wdt: force_id: Override the detected device ID diff --git a/MAINTAINERS b/MAINTAINERS index ed382ff688076..143b4e97d47ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1913,6 +1913,12 @@ S: Supported F: Documentation/devicetree/bindings/spi/andestech,ae350-spi.yaml F: drivers/spi/spi-atcspi200.c +ANDES ATCWDT200 WATCHDOG DRIVER +M: CL Wang <cl634@andestech.com> +S: Supported +F: Documentation/devicetree/bindings/watchdog/andestech,ae350-wdt.yaml +F: drivers/watchdog/atcwdt200_wdt.c + ANDROID DRIVERS M: Greg Kroah-Hartman <gregkh@linuxfoundation.org> M: Arve Hjønnevåg <arve@android.com> @@ -3383,6 +3389,7 @@ F: arch/arm64/boot/dts/qcom/ F: drivers/bus/qcom* F: drivers/firmware/qcom/ F: drivers/soc/qcom/ +F: drivers/watchdog/gunyah_wdt.c F: include/dt-bindings/arm/qcom,ids.h F: include/dt-bindings/firmware/qcom,scm.h F: include/dt-bindings/soc/qcom* diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index dc78729ba2a5d..a8fc1d7a5031c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -250,7 +250,7 @@ config DA9062_WATCHDOG config GPIO_WATCHDOG tristate "Watchdog device controlled through GPIO-line" - depends on OF_GPIO + depends on GPIOLIB && (ACPI || OF) select WATCHDOG_CORE help If you say yes here you get support for watchdog device @@ -882,7 +882,7 @@ config TEGRA_WATCHDOG module will be called tegra_wdt. config QCOM_WDT - tristate "QCOM watchdog" + tristate "Qualcomm watchdog" depends on HAS_IOMEM depends on ARCH_QCOM || COMPILE_TEST select WATCHDOG_CORE @@ -1093,7 +1093,7 @@ config SPRD_WATCHDOG by Spreadtrum system. config PM8916_WATCHDOG - tristate "QCOM PM8916 pmic watchdog" + tristate "Qualcomm PM8916 pmic watchdog" depends on OF && MFD_SPMI_PMIC select WATCHDOG_CORE help @@ -1270,19 +1270,6 @@ config GEODE_WDT You can compile this driver directly into the kernel, or use it as a module. The module will be called geodewdt. -config SC520_WDT - tristate "AMD Elan SC520 processor Watchdog" - depends on MELAN || COMPILE_TEST - help - This is the driver for the hardware watchdog built in to the - AMD "Elan" SC520 microcomputer commonly used in embedded systems. - This watchdog simply watches your kernel to make sure it doesn't - freeze, and if it does, it reboots your computer after a certain - amount of time. - - You can compile this driver directly into the kernel, or use - it as a module. The module will be called sc520_wdt. - config SBC_FITPC2_WATCHDOG tristate "Compulab SBC-FITPC2 watchdog" depends on (X86 || COMPILE_TEST) && HAS_IOPORT @@ -1712,19 +1699,6 @@ config W83977F_WDT To compile this driver as a module, choose M here: the module will be called w83977f_wdt. -config MACHZ_WDT - tristate "ZF MachZ Watchdog" - depends on (X86 || COMPILE_TEST) && HAS_IOPORT - help - If you are using a ZF Micro MachZ processor, say Y here, otherwise - N. This is the driver for the watchdog timer built-in on that - processor using ZF-Logic interface. This watchdog simply watches - your kernel to make sure it doesn't freeze, and if it does, it - reboots your computer after a certain amount of time. - - To compile this driver as a module, choose M here: the - module will be called machzwd. - config SBC_EPX_C3_WATCHDOG tristate "Winsystems SBC EPX-C3 watchdog" depends on (X86 || COMPILE_TEST) && HAS_IOPORT @@ -2011,7 +1985,7 @@ config MT7621_WDT config PIC32_WDT tristate "Microchip PIC32 hardware watchdog" select WATCHDOG_CORE - depends on MACH_PIC32 || (MIPS && COMPILE_TEST) + depends on MACH_PIC32 || COMPILE_TEST help Watchdog driver for the built in watchdog hardware in a PIC32. @@ -2024,7 +1998,7 @@ config PIC32_WDT config PIC32_DMT tristate "Microchip PIC32 Deadman Timer" select WATCHDOG_CORE - depends on MACH_PIC32 || (MIPS && COMPILE_TEST) + depends on MACH_PIC32 || COMPILE_TEST help Watchdog driver for PIC32 instruction fetch counting timer. This specific timer is typically be used in mission critical and safety @@ -2132,6 +2106,15 @@ config WATCHDOG_RTAS # RISC-V Architecture +config ATCWDT200_WATCHDOG + tristate "Andes ATCWDT200 Watchdog support" + depends on ARCH_ANDES || COMPILE_TEST + help + Driver for the Andes ATCWDT200 watchdog timer. It provides access to + programmable reset and interrupt counters with clock-source dependent + timing. The driver automatically detects the supported IntTime bit-width + and is fully integrated with the Linux Watchdog Framework. + config STARFIVE_WATCHDOG tristate "StarFive Watchdog support" depends on ARCH_STARFIVE || COMPILE_TEST @@ -2270,10 +2253,11 @@ config MIXCOMWD Most people will say N. config WDT - tristate "WDT Watchdog timer" + tristate "ICS WDT500P/501P Watchdog timer" depends on ISA help - If you have a WDT500P or WDT501P watchdog board, say Y here, + If you have an Industrial Computer Source (ICS) WDT500P or WDT501P + watchdog board, say Y here, otherwise N. It is not possible to probe for this board, which means that you have to inform the kernel about the IO port and IRQ that is needed (you can do this via the io and irq parameters) @@ -2304,10 +2288,11 @@ config PCIPCWATCHDOG Most people will say N. config WDTPCI - tristate "PCI-WDT500/501 Watchdog timer" + tristate "ICS PCI-WDT500/501 Watchdog timer" depends on PCI && HAS_IOPORT help - If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. + If you have an Industrial Computer Source (ICS) PCI-WDT500/501 watchdog + board, say Y here, otherwise N. If you have a PCI-WDT501 watchdog board then you can enable the temperature sensor by setting the type parameter to 501. @@ -2354,4 +2339,17 @@ config KEEMBAY_WATCHDOG To compile this driver as a module, choose M here: the module will be called keembay_wdt. +config GUNYAH_WATCHDOG + tristate "Qualcomm Gunyah Watchdog" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAVE_ARM_SMCCC + select WATCHDOG_CORE + help + Say Y here to include support for watchdog timer provided by the + Gunyah hypervisor. The driver uses ARM SMC Calling Convention (SMCCC) + to interact with Gunyah Watchdog. + + To compile this driver as a module, choose M here: the + module will be called gunyah_wdt. + endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index d2fb16b9f9ce7..bc1d52220f223 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o 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 # X86 (i386 + ia64 + x86_64) Architecture obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o @@ -116,7 +117,6 @@ obj-$(CONFIG_EXAR_WDT) += exar_wdt.o obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o -obj-$(CONFIG_SC520_WDT) += sc520_wdt.o obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o obj-$(CONFIG_IB700_WDT) += ib700wdt.o @@ -146,7 +146,6 @@ obj-$(CONFIG_VIA_WDT) += via_wdt.o obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o -obj-$(CONFIG_MACHZ_WDT) += machzwd.o obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o obj-$(CONFIG_INTEL_OC_WATCHDOG) += intel_oc_wdt.o @@ -198,6 +197,7 @@ obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o # RISC-V Architecture +obj-$(CONFIG_ATCWDT200_WATCHDOG) += atcwdt200_wdt.o obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o # S390 Architecture diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c index 66a158f67a712..6b9b0f9b05ced 100644 --- a/drivers/watchdog/apple_wdt.c +++ b/drivers/watchdog/apple_wdt.c @@ -218,6 +218,7 @@ static int apple_wdt_suspend(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(apple_wdt_pm_ops, apple_wdt_suspend, apple_wdt_resume); static const struct of_device_id apple_wdt_of_match[] = { + { .compatible = "apple,t8103-wdt" }, { .compatible = "apple,wdt" }, {}, }; diff --git a/drivers/watchdog/at91sam9_wdt.h b/drivers/watchdog/at91sam9_wdt.h index 298d545df1a15..2020694f8f6f3 100644 --- a/drivers/watchdog/at91sam9_wdt.h +++ b/drivers/watchdog/at91sam9_wdt.h @@ -9,6 +9,8 @@ * Watchdog Timer (WDT) - System peripherals regsters. * Based on AT91SAM9261 datasheet revision D. * Based on SAM9X60 datasheet. + * Based on SAMA7G5 datasheet. + * Based on SAM9X75 datasheet. * */ @@ -27,10 +29,10 @@ #define AT91_SAM9X60_PERIODRST BIT(4) /* Period Reset */ #define AT91_SAM9X60_RPTHRST BIT(5) /* Minimum Restart Period */ #define AT91_WDT_WDFIEN BIT(12) /* Fault Interrupt Enable */ -#define AT91_SAM9X60_WDDIS BIT(12) /* Watchdog Disable */ +#define AT91_SAM9X60_WDDIS BIT(12) /* Watchdog Disable (SAM9X60, SAMA7G5, SAM9X75) */ #define AT91_WDT_WDRSTEN BIT(13) /* Reset Processor */ #define AT91_WDT_WDRPROC BIT(14) /* Timer Restart */ -#define AT91_WDT_WDDIS BIT(15) /* Watchdog Disable */ +#define AT91_WDT_WDDIS BIT(15) /* Watchdog Disable (SAMA5, AT91SAM9261) */ #define AT91_WDT_WDD (0xfffUL << 16) /* Delta Value */ #define AT91_WDT_SET_WDD(x) (((x) << 16) & AT91_WDT_WDD) #define AT91_WDT_WDDBGHLT BIT(28) /* Debug Halt */ diff --git a/drivers/watchdog/atcwdt200_wdt.c b/drivers/watchdog/atcwdt200_wdt.c new file mode 100644 index 0000000000000..8e3b18aea368f --- /dev/null +++ b/drivers/watchdog/atcwdt200_wdt.c @@ -0,0 +1,580 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Andes ATCWDT200 watchdog timer driver. + * + * Copyright (C) 2025 Andes Technology Corporation + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/math64.h> +#include <linux/minmax.h> +#include <linux/moduleparam.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/watchdog.h> + +/* Register definitions */ +#define REG_CTRL 0x10 +#define REG_RESTART 0x14 +#define REG_WRITE_EN 0x18 +#define REG_STATUS 0x1C + +/* Control Register */ +#define CTRL_RST_TIME_MSK GENMASK(10, 8) +#define CTRL_RST_TIME_SET(x) FIELD_PREP(CTRL_RST_TIME_MSK, x) +#define CTRL_INT_TIME_MSK GENMASK(7, 4) +#define CTRL_INT_TIME_SET(x) FIELD_PREP(CTRL_INT_TIME_MSK, x) +#define CTRL_INT_TIME_GET(x) FIELD_GET(CTRL_INT_TIME_MSK, x) +#define CTRL_RST_EN BIT(3) +#define CTRL_CLK_SEL BIT(1) +#define CTRL_CLK_SEL_PCLK 1 +#define CTRL_CLK_SEL_SET(x) FIELD_PREP(CTRL_CLK_SEL, x) +#define CTRL_WDT_EN BIT(0) + +/* Restart Register */ +#define RESTART_MAGIC 0xCAFE + +/* Write Enable Register */ +#define WRITE_EN_MAGIC 0x5AA5 + +/* Status Register */ +#define STATUS_INT_EXPIRED BIT(1) + +/* The default timeout value in seconds */ +#define ATCWDT_TIMEOUT 4 + +/* Define the array size for each timer type */ +#define TMR_SZ_RST 8 +#define TMR_SZ_INT_16 8 +#define TMR_SZ_INT_32 16 + +#define DRV_NAME "atcwdt200" +/** + * enum timer_type - Supported timer types for ATCWDT200 watchdog driver + * @TMR_RST: Reset timer (non-interrupt). + * @TMR_INT_16: 16-bit interrupt timer supported by hardware. + * @TMR_INT_32: 32-bit interrupt timer supported by hardware. + * @TMR_UNKNOWN: Timer type cannot be determined. + */ +enum timer_type { + TMR_RST, + TMR_INT_16, + TMR_INT_32, + TMR_UNKNOWN +}; + +static unsigned int timeout = ATCWDT_TIMEOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; + +/** + * struct atcwdt_drv - ATCWDT200 watchdog driver private data + * @wdt_dev: Watchdog device used by the watchdog framework. + * @regmap: Register map for accessing hardware registers. + * @clk: Hardware clock used by the watchdog timer. + * @lock: Spinlock protecting register accesses and driver state. + * @clk_freq: Input clock frequency of the ATCWDT200. + * @clk_src: Selected clock source for the watchdog timer. + * @int_timer_type: Detected interrupt timer type (16-bit, 32-bit, or unknown). + */ +struct atcwdt_drv { + struct watchdog_device wdt_dev; + struct regmap *regmap; + struct clk *clk; + spinlock_t lock; + unsigned int clk_freq; + unsigned char clk_src; + unsigned char int_timer_type; +}; + +static const struct watchdog_info atcwdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +/** + * atcwdt_get_index - Get the interval value for the specified timer type + * @index: The index of the interval in the array + * @timer_type: The type of timer, which can be TMR_RST, TMR_INT_16, or + * TMR_INT_32. + * + * This function retrieves the interval value based on the timer type and + * ensures the index stays within the valid range for the given timer type. + * For TMR_RST: + * - The maximum array size is 8 (index range: 0-7). + * For TMR_INT_16: + * - The maximum array size is 8 (index range: 0-7). + * For TMR_INT_32: + * - The maximum array size is 16 (index range: 0-15). + * + * If the index exceeds the maximum array size, the function will return + * the last element of the respective array. + */ +static inline unsigned char atcwdt_get_index(unsigned char index, + enum timer_type timer_type) +{ + static const unsigned char rst_timer_interval[TMR_SZ_RST] = { + 7, 8, 9, 10, 11, 12, 13, 14}; + static const unsigned char int_timer_interval[TMR_SZ_INT_32] = { + 6, 8, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 25, 27, 29, 31}; + unsigned char array_index; + + if (timer_type == TMR_RST) { + array_index = min(index, TMR_SZ_RST - 1); + return rst_timer_interval[array_index]; + } + + if (timer_type == TMR_INT_32) + array_index = min(index, TMR_SZ_INT_32 - 1); + else + array_index = min(index, TMR_SZ_INT_16 - 1); + + return int_timer_interval[array_index]; +} + +/** + * atcwdt_get_clock_period - Calculate the closest clock period based on a + * given tick count + * @tick: The target tick count to match + * @timer_type: The type of timer, which can be TMR_RST, TMR_INT_16, or + * TMR_INT_32. + * @index: Pointer to store the index of the selected parameter + * + * This function calculates the closest clock period to the given tick count + * by iterating through the timer parameters and selecting the one that + * minimizes the difference between the target tick count and the calculated + * clock period. The function determines the index of the closest parameter + * and returns the difference between the target tick count and the selected + * clock period. + * + * Return: The difference between the target tick count and the selected + * clock period. + */ +static long long atcwdt_get_clock_period(long long tick, + enum timer_type timer_type, + unsigned char *index) +{ + long long result; + unsigned char size; + char i; + + if (timer_type == TMR_RST) + size = TMR_SZ_RST; + else if (timer_type == TMR_INT_32) + size = TMR_SZ_INT_32; + else + size = TMR_SZ_INT_16; + + *index = size - 1; + for (i = 0; i < size; i++) { + result = tick - (1LL << atcwdt_get_index(i, timer_type)); + + if (result <= 1) { + *index = i; + break; + } + } + + return result; +} + +/** + * atcwdt_get_timeout_params - Calculate optimal parameters for Watchdog Timer + * @drv_data: Pointer to the Watchdog driver data structure + * @timeout: Desired timeout value (in seconds) + * @int_timer_params: Pointer to store the calculated interrupt timer + * parameter index + * @rst_timer_params: Pointer to store the calculated reset timer parameter + * index + * + * This function calculates the optimal parameter combination for the + * interrupt timer and reset timer of the Watchdog Timer to achieve a + * timeout value closest to, but not less than the specified timeout. + * + * Algorithm: + * 1. The parameters for both the interrupt timer and reset timer are + * predefined as a series of options represented as powers of 2. + * 2. The function first determines the interrupt timer's parameter index + * that provides a time closest to and not exceeding the desired timeout. + * 3. Based on the selected interrupt timer, it calculates the required + * reset timer parameter to ensure the total timeout matches the target. + * + * Return: The calculated parameter indices are stored in the provided + * pointers. + */ +static void atcwdt_get_timeout_params(struct atcwdt_drv *drv_data, + unsigned int timeout, + unsigned char *int_timer_params, + unsigned char *rst_timer_params) +{ + long long rest_time_ms; + long long result; + long long tick; + unsigned char rst_index; + unsigned char int_index; + unsigned char above; + unsigned char below; + + tick = (long long)timeout * drv_data->clk_freq; + result = atcwdt_get_clock_period(tick, + drv_data->int_timer_type, + &above); + if (result == 0 || above == 0) { + *int_timer_params = above; + *rst_timer_params = 0; + return; + } + below = above - 1; + + int_index = atcwdt_get_index(below, drv_data->int_timer_type); + rest_time_ms = timeout * 1000LL + - div64_s64(1000LL << int_index, drv_data->clk_freq); + + result = atcwdt_get_clock_period(rest_time_ms * drv_data->clk_freq, + TMR_RST, + &rst_index); + + if (result > 1) { + *int_timer_params = above; + *rst_timer_params = 0; + } else { + *int_timer_params = below; + *rst_timer_params = rst_index; + } +} + +/** + * atcwdt_get_int_timer_type - Get the supported interrupt timer type. + * @drv_data: Pointer to the watchdog driver data structure. + * + * This function tests the writable bits in the IntTime field of the control + * register to determine the interrupt timer type supported by the hardware. + * + * Note: This function must only be called when the ATCWDT200 watchdog is + * disabled. If the watchdog is enabled, this function returns TMR_UNKNOWN. + * + * Returns: The interrupt timer type supported by the hardware. + */ +static int atcwdt_get_int_timer_type(struct atcwdt_drv *drv_data) +{ + struct device *dev = drv_data->wdt_dev.parent; + unsigned int val; + int ret = 0; + + spin_lock(&drv_data->lock); + regmap_read(drv_data->regmap, REG_CTRL, &val); + if (val & CTRL_WDT_EN) { + spin_unlock(&drv_data->lock); + return TMR_UNKNOWN; + } + + /* + * Configures the IntTime field with the maximum mask value + * (CTRL_INT_TIME_MSK), reads its value from the control register + * to identify the maximum writable bits. + */ + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_write(drv_data->regmap, REG_CTRL, CTRL_INT_TIME_MSK); + regmap_read(drv_data->regmap, REG_CTRL, &val); + spin_unlock(&drv_data->lock); + + val = CTRL_INT_TIME_GET(val); + switch (val) { + case 7: + drv_data->int_timer_type = TMR_INT_16; + break; + case 15: + drv_data->int_timer_type = TMR_INT_32; + break; + default: + drv_data->int_timer_type = TMR_UNKNOWN; + ret = dev_err_probe(dev, -ENODEV, + "Failed to detect interrupt timer type\n"); + } + + return ret; +} + +static int atcwdt_ping(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_write(drv_data->regmap, REG_RESTART, RESTART_MAGIC); + regmap_update_bits(drv_data->regmap, REG_STATUS, STATUS_INT_EXPIRED, + STATUS_INT_EXPIRED); + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + unsigned int value; + unsigned char rst_val; + unsigned char int_val; + + wdt_dev->timeout = timeout; + atcwdt_get_timeout_params(drv_data, timeout, &int_val, &rst_val); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + + value = CTRL_RST_TIME_SET(rst_val) | + CTRL_INT_TIME_SET(int_val) | + CTRL_CLK_SEL_SET(drv_data->clk_src); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_TIME_MSK | + CTRL_INT_TIME_MSK | + CTRL_CLK_SEL, + value); + + spin_unlock(&drv_data->lock); + atcwdt_ping(wdt_dev); + + return 0; +} + +static int atcwdt_start(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + atcwdt_set_timeout(wdt_dev, wdt_dev->timeout); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + CTRL_RST_EN | CTRL_WDT_EN); + + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_stop(struct watchdog_device *wdt_dev) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + 0); + spin_unlock(&drv_data->lock); + + return 0; +} + +static int atcwdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) +{ + struct atcwdt_drv *drv_data = watchdog_get_drvdata(wdt_dev); + + atcwdt_set_timeout(wdt_dev, 0); + + spin_lock(&drv_data->lock); + regmap_write(drv_data->regmap, REG_WRITE_EN, WRITE_EN_MAGIC); + regmap_update_bits(drv_data->regmap, + REG_CTRL, + CTRL_RST_EN | CTRL_WDT_EN, + CTRL_RST_EN | CTRL_WDT_EN); + spin_unlock(&drv_data->lock); + + return 0; +} + +static const struct watchdog_ops atcwdt_ops = { + .owner = THIS_MODULE, + .start = atcwdt_start, + .stop = atcwdt_stop, + .ping = atcwdt_ping, + .set_timeout = atcwdt_set_timeout, + .restart = atcwdt_restart, +}; + +static int atcwdt_init_resource(struct platform_device *pdev, + struct atcwdt_drv *drv_data) +{ + struct device *dev = &pdev->dev; + void __iomem *base; + const struct regmap_config cfg = { + .name = "atcwdt", + .reg_bits = 32, + .val_bits = 32, + .cache_type = REGCACHE_NONE, + .reg_stride = 4, + .max_register = REG_STATUS, + }; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), + "Failed to ioremap I/O resource\n"); + + drv_data->regmap = devm_regmap_init_mmio(dev, base, &cfg); + if (IS_ERR(drv_data->regmap)) + return dev_err_probe(dev, PTR_ERR(drv_data->regmap), + "Failed to create regmap\n"); + + return 0; +} + +static int atcwdt_enable_clk(struct atcwdt_drv *drv_data) +{ + struct device *dev = drv_data->wdt_dev.parent; + unsigned int val; + int clk_src; + + drv_data->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(drv_data->clk)) + return dev_err_probe(dev, PTR_ERR(drv_data->clk), + "Failed to get watchdog clock\n"); + + drv_data->clk_freq = clk_get_rate(drv_data->clk); + if (!drv_data->clk_freq) + return dev_err_probe(dev, -EINVAL, + "Failed to get clock rate\n"); + + clk_src = device_property_read_u32(dev, "andestech,clock-source", &val); + drv_data->clk_src = (!clk_src && val != 0) ? CTRL_CLK_SEL_PCLK : 0; + + return 0; +} + +static int atcwdt_init_wdt_device(struct device *dev, + struct atcwdt_drv *drv_data) +{ + struct watchdog_device *wdd = &drv_data->wdt_dev; + + wdd->parent = dev; + wdd->info = &atcwdt_info; + wdd->ops = &atcwdt_ops; + wdd->timeout = ATCWDT_TIMEOUT; + wdd->min_timeout = 1; + + watchdog_set_nowayout(wdd, nowayout); + watchdog_set_drvdata(wdd, drv_data); + + return 0; +} + +static void atcwdt_calc_max_timeout(struct atcwdt_drv *drv_data) +{ + unsigned char rst_idx = atcwdt_get_index(0xFF, TMR_RST); + unsigned char int_idx = atcwdt_get_index(0xFF, + drv_data->int_timer_type); + + drv_data->wdt_dev.max_timeout = + ((1U << rst_idx) + (1U << int_idx)) / drv_data->clk_freq; +} + +static int atcwdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct atcwdt_drv *drv_data; + int ret; + + drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + platform_set_drvdata(pdev, drv_data); + spin_lock_init(&drv_data->lock); + + ret = atcwdt_init_wdt_device(dev, drv_data); + if (ret) + return ret; + + ret = atcwdt_init_resource(pdev, drv_data); + if (ret) + return ret; + + ret = atcwdt_enable_clk(drv_data); + if (ret) + return ret; + + ret = atcwdt_get_int_timer_type(drv_data); + if (ret) + return ret; + + atcwdt_calc_max_timeout(drv_data); + + ret = devm_watchdog_register_device(dev, &drv_data->wdt_dev); + + return ret; +} + +static int atcwdt_suspend(struct device *dev) +{ + struct atcwdt_drv *drv_data = dev_get_drvdata(dev); + + if (watchdog_active(&drv_data->wdt_dev)) { + atcwdt_stop(&drv_data->wdt_dev); + clk_disable_unprepare(drv_data->clk); + } + + return 0; +} + +static int atcwdt_resume(struct device *dev) +{ + struct atcwdt_drv *drv_data = dev_get_drvdata(dev); + int ret = 0; + + if (watchdog_active(&drv_data->wdt_dev)) { + ret = clk_prepare_enable(drv_data->clk); + if (ret) + return ret; + atcwdt_start(&drv_data->wdt_dev); + atcwdt_ping(&drv_data->wdt_dev); + } + + return ret; +} + +static const struct of_device_id atcwdt_match[] = { + { .compatible = "andestech,ae350-wdt" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atcwdt_match); + +static DEFINE_SIMPLE_DEV_PM_OPS(atcwdt_pm_ops, atcwdt_suspend, atcwdt_resume); + +static struct platform_driver atcwdt_driver = { + .probe = atcwdt_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = atcwdt_match, + .pm = pm_sleep_ptr(&atcwdt_pm_ops), + }, +}; + +module_platform_driver(atcwdt_driver); + +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(ATCWDT_TIMEOUT) ")"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("CL Wang <cl634@andestech.com>"); +MODULE_DESCRIPTION("Andes ATCWDT200 Watchdog timer driver"); diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c index 9fcfee63905b9..6fd8b1b8e386a 100644 --- a/drivers/watchdog/bcm2835_wdt.c +++ b/drivers/watchdog/bcm2835_wdt.c @@ -19,6 +19,7 @@ #include <linux/platform_device.h> #include <linux/of_address.h> #include <linux/of_platform.h> +#include <linux/reboot.h> #define PM_RSTC 0x1c #define PM_RSTS 0x20 @@ -49,8 +50,6 @@ struct bcm2835_wdt { spinlock_t lock; }; -static struct bcm2835_wdt *bcm2835_power_off_wdt; - static unsigned int heartbeat; static bool nowayout = WATCHDOG_NOWAYOUT; @@ -150,9 +149,9 @@ static struct watchdog_device bcm2835_wdt_wdd = { * indicate to bootcode.bin not to reboot, then most of the chip will be * powered off. */ -static void bcm2835_power_off(void) +static int bcm2835_power_off(struct sys_off_data *data) { - struct bcm2835_wdt *wdt = bcm2835_power_off_wdt; + struct bcm2835_wdt *wdt = data->cb_data; u32 val; /* @@ -166,6 +165,8 @@ static void bcm2835_power_off(void) /* Continue with normal reset mechanism */ __bcm2835_restart(wdt); + + return NOTIFY_DONE; } static int bcm2835_wdt_probe(struct platform_device *pdev) @@ -206,28 +207,17 @@ static int bcm2835_wdt_probe(struct platform_device *pdev) if (err) return err; - if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) { - if (!pm_power_off) { - pm_power_off = bcm2835_power_off; - bcm2835_power_off_wdt = wdt; - } else { - dev_info(dev, "Poweroff handler already present!\n"); - } - } + if (of_device_is_system_power_controller(pdev->dev.parent->of_node)) + devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_DEFAULT, + bcm2835_power_off, wdt); dev_info(dev, "Broadcom BCM2835 watchdog timer"); return 0; } -static void bcm2835_wdt_remove(struct platform_device *pdev) -{ - if (pm_power_off == bcm2835_power_off) - pm_power_off = NULL; -} - static struct platform_driver bcm2835_wdt_driver = { .probe = bcm2835_wdt_probe, - .remove = bcm2835_wdt_remove, .driver = { .name = "bcm2835-wdt", }, diff --git a/drivers/watchdog/gunyah_wdt.c b/drivers/watchdog/gunyah_wdt.c new file mode 100644 index 0000000000000..49dfef459e847 --- /dev/null +++ b/drivers/watchdog/gunyah_wdt.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/arm-smccc.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define GUNYAH_WDT_SMCCC_CALL_VAL(func_id) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\ + ARM_SMCCC_OWNER_VENDOR_HYP, func_id) + +/* SMCCC function IDs for watchdog operations */ +#define GUNYAH_WDT_CONTROL GUNYAH_WDT_SMCCC_CALL_VAL(0x0005) +#define GUNYAH_WDT_STATUS GUNYAH_WDT_SMCCC_CALL_VAL(0x0006) +#define GUNYAH_WDT_PING GUNYAH_WDT_SMCCC_CALL_VAL(0x0007) +#define GUNYAH_WDT_SET_TIME GUNYAH_WDT_SMCCC_CALL_VAL(0x0008) + +/* + * Control values for GUNYAH_WDT_CONTROL. + * Bit 0 is used to enable or disable the watchdog. If this bit is set, + * then the watchdog is enabled and vice versa. + * Bit 1 should always be set to 1 as this bit is reserved in Gunyah and + * it's expected to be 1. + */ +#define WDT_CTRL_ENABLE (BIT(1) | BIT(0)) +#define WDT_CTRL_DISABLE BIT(1) + +enum gunyah_error { + GUNYAH_ERROR_OK = 0, + GUNYAH_ERROR_UNIMPLEMENTED = -1, + GUNYAH_ERROR_ARG_INVAL = 1, +}; + +/** + * gunyah_error_remap() - Remap Gunyah hypervisor errors into a Linux error code + * @gunyah_error: Gunyah hypercall return value + */ +static inline int gunyah_error_remap(enum gunyah_error gunyah_error) +{ + switch (gunyah_error) { + case GUNYAH_ERROR_OK: + return 0; + case GUNYAH_ERROR_UNIMPLEMENTED: + return -EOPNOTSUPP; + default: + return -EINVAL; + } +} + +static int gunyah_wdt_call(unsigned long func_id, unsigned long arg1, + unsigned long arg2) +{ + struct arm_smccc_res res; + + arm_smccc_1_1_smc(func_id, arg1, arg2, &res); + return gunyah_error_remap(res.a0); +} + +static int gunyah_wdt_start(struct watchdog_device *wdd) +{ + unsigned int timeout_ms; + struct device *dev = wdd->parent; + int ret; + + ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); + if (ret && watchdog_active(wdd)) { + dev_err(dev, "%s: Failed to stop gunyah wdt %d\n", __func__, ret); + return ret; + } + + timeout_ms = wdd->timeout * 1000; + ret = gunyah_wdt_call(GUNYAH_WDT_SET_TIME, timeout_ms, timeout_ms); + if (ret) { + dev_err(dev, "%s: Failed to set timeout for gunyah wdt %d\n", + __func__, ret); + return ret; + } + + ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0); + if (ret) + dev_err(dev, "%s: Failed to start gunyah wdt %d\n", __func__, ret); + + return ret; +} + +static int gunyah_wdt_stop(struct watchdog_device *wdd) +{ + return gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); +} + +static int gunyah_wdt_ping(struct watchdog_device *wdd) +{ + return gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0); +} + +static int gunyah_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout_sec) +{ + wdd->timeout = timeout_sec; + + if (watchdog_active(wdd)) + return gunyah_wdt_start(wdd); + + return 0; +} + +static int gunyah_wdt_get_time_since_last_ping(void) +{ + struct arm_smccc_res res; + + arm_smccc_1_1_smc(GUNYAH_WDT_STATUS, 0, 0, &res); + if (res.a0) + return gunyah_error_remap(res.a0); + + return res.a2 / 1000; +} + +static unsigned int gunyah_wdt_get_timeleft(struct watchdog_device *wdd) +{ + int seconds_since_last_ping; + + seconds_since_last_ping = gunyah_wdt_get_time_since_last_ping(); + if (seconds_since_last_ping < 0 || + seconds_since_last_ping > wdd->timeout) + return 0; + + return wdd->timeout - seconds_since_last_ping; +} + +static int gunyah_wdt_restart(struct watchdog_device *wdd, + unsigned long action, void *data) +{ + /* Set timeout to 1ms and send a ping */ + gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0); + gunyah_wdt_call(GUNYAH_WDT_SET_TIME, 1, 1); + gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0); + gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0); + + /* Wait to make sure reset occurs */ + mdelay(100); + + return 0; +} + +static const struct watchdog_info gunyah_wdt_info = { + .identity = "Gunyah Watchdog", + .options = WDIOF_SETTIMEOUT + | WDIOF_KEEPALIVEPING + | WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops gunyah_wdt_ops = { + .owner = THIS_MODULE, + .start = gunyah_wdt_start, + .stop = gunyah_wdt_stop, + .ping = gunyah_wdt_ping, + .set_timeout = gunyah_wdt_set_timeout, + .get_timeleft = gunyah_wdt_get_timeleft, + .restart = gunyah_wdt_restart +}; + +static int gunyah_wdt_probe(struct platform_device *pdev) +{ + struct watchdog_device *wdd; + struct device *dev = &pdev->dev; + int ret; + + ret = gunyah_wdt_call(GUNYAH_WDT_STATUS, 0, 0); + if (ret == -EOPNOTSUPP) + return -ENODEV; + + if (ret) + return dev_err_probe(dev, ret, "status check failed\n"); + + wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL); + if (!wdd) + return -ENOMEM; + + wdd->info = &gunyah_wdt_info; + wdd->ops = &gunyah_wdt_ops; + wdd->parent = dev; + + /* + * Although Gunyah expects 16-bit unsigned int values as timeout values + * in milliseconds, values above 0x8000 are reserved. This limits the + * max timeout value to 32 seconds. + */ + wdd->max_timeout = 32; /* seconds */ + wdd->min_timeout = 1; /* seconds */ + wdd->timeout = wdd->max_timeout; + + gunyah_wdt_stop(wdd); + platform_set_drvdata(pdev, wdd); + watchdog_set_restart_priority(wdd, 0); + + return devm_watchdog_register_device(dev, wdd); +} + +static void gunyah_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + + gunyah_wdt_stop(wdd); +} + +static int gunyah_wdt_suspend(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + gunyah_wdt_stop(wdd); + + return 0; +} + +static int gunyah_wdt_resume(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + if (watchdog_active(wdd)) + gunyah_wdt_start(wdd); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(gunyah_wdt_pm_ops, gunyah_wdt_suspend, gunyah_wdt_resume); + +/* + * Gunyah watchdog is a vendor-specific hypervisor interface provided by the + * Gunyah hypervisor. Using QCOM SCM driver to detect Gunyah watchdog SMCCC + * hypervisor service and register platform device when the service is available + * allows this driver to operate independently of the devicetree and avoids + * adding the non-hardware nodes to the devicetree. + */ +static const struct platform_device_id gunyah_wdt_id[] = { + { .name = "gunyah-wdt" }, + {} +}; +MODULE_DEVICE_TABLE(platform, gunyah_wdt_id); + +static struct platform_driver gunyah_wdt_driver = { + .driver = { + .name = "gunyah-wdt", + .pm = pm_sleep_ptr(&gunyah_wdt_pm_ops), + }, + .id_table = gunyah_wdt_id, + .probe = gunyah_wdt_probe, + .remove = gunyah_wdt_remove, +}; + +module_platform_driver(gunyah_wdt_driver); + +MODULE_DESCRIPTION("Gunyah Watchdog Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 2a848c35c14d2..8af1fad2de0bd 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -23,6 +23,7 @@ #include <linux/watchdog.h> #ifdef CONFIG_HPWDT_NMI_DECODING #include <asm/nmi.h> +#include <asm/uv/uv.h> #endif #include <linux/crash_dump.h> @@ -159,24 +160,31 @@ static int hpwdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req) return 0; } -static int hpwdt_my_nmi(void) -{ - return ioread8(hpwdt_nmistat) & 0x6; -} +#define NMISTAT_EASR BIT(0) +#define NMISTAT_EWDOG BIT(1) +#define NMISTAT_RTRAP BIT(2) +#define NMISTAT_DBELL BIT(3) +#define NMISTAT_EMSWDG BIT(4) +#define NMISTAT_GPI BIT(5) +#define NMISTAT_NMIOUT BIT(7) /* * NMI Handler */ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) { - unsigned int mynmi = hpwdt_my_nmi(); - static char panic_msg[] = + u8 nmistat = ioread8(hpwdt_nmistat); + bool mynmi = (nmistat & (NMISTAT_EWDOG | NMISTAT_RTRAP)) != 0; + static char panic_msg_default[] = "00: An NMI occurred. Depending on your system the reason " "for the NMI is logged in any one of the following resources:\n" "1. Integrated Management Log (IML)\n" "2. OA Syslog\n" "3. OA Forward Progress Log\n" "4. iLO Event Log"; + static char panic_msg_uv[] = + "00: A watchdog NMI occurred."; + char *panic_msg = is_uv_system() ? panic_msg_uv : panic_msg_default; if (ulReason == NMI_UNKNOWN && !mynmi) return NMI_DONE; @@ -190,7 +198,7 @@ static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) hpwdt_ping_ticks(SECS_TO_TICKS(val)); } - hex_byte_pack(panic_msg, mynmi); + hex_byte_pack(panic_msg, nmistat); nmi_panic(regs, panic_msg); return NMI_HANDLED; diff --git a/drivers/watchdog/imx7ulp_wdt.c b/drivers/watchdog/imx7ulp_wdt.c index 03479110453ce..855dc9d5083a5 100644 --- a/drivers/watchdog/imx7ulp_wdt.c +++ b/drivers/watchdog/imx7ulp_wdt.c @@ -56,6 +56,7 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" struct imx_wdt_hw_feature { bool prescaler_enable; bool post_rcs_wait; + bool cpu_lpm_auto_cg; u32 wdog_clock_rate; }; @@ -360,7 +361,7 @@ static int __maybe_unused imx7ulp_wdt_suspend_noirq(struct device *dev) { struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); - if (watchdog_active(&imx7ulp_wdt->wdd)) + if (watchdog_active(&imx7ulp_wdt->wdd) && !imx7ulp_wdt->hw->cpu_lpm_auto_cg) imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); clk_disable_unprepare(imx7ulp_wdt->clk); @@ -408,10 +409,17 @@ static const struct imx_wdt_hw_feature imx93_wdt_hw = { .wdog_clock_rate = 125, }; +static const struct imx_wdt_hw_feature imx94_wdt_hw = { + .prescaler_enable = true, + .wdog_clock_rate = 125, + .cpu_lpm_auto_cg = true, +}; + static const struct of_device_id imx7ulp_wdt_dt_ids[] = { { .compatible = "fsl,imx7ulp-wdt", .data = &imx7ulp_wdt_hw, }, { .compatible = "fsl,imx8ulp-wdt", .data = &imx8ulp_wdt_hw, }, { .compatible = "fsl,imx93-wdt", .data = &imx93_wdt_hw, }, + { .compatible = "fsl,imx94-wdt", .data = &imx94_wdt_hw, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); diff --git a/drivers/watchdog/lenovo_se10_wdt.c b/drivers/watchdog/lenovo_se10_wdt.c index cd0500e5080b4..503e220263f95 100644 --- a/drivers/watchdog/lenovo_se10_wdt.c +++ b/drivers/watchdog/lenovo_se10_wdt.c @@ -178,7 +178,7 @@ static int se10_wdt_probe(struct platform_device *pdev) return -EBUSY; chip_id = get_chipID(); - if (chip_id != 0x5632) { + if (chip_id != 0x5632 && chip_id != 0x5652) { release_region(CFG_PORT, CFG_SIZE); return -ENODEV; } @@ -224,7 +224,7 @@ static struct platform_driver se10_wdt_driver = { .probe = se10_wdt_probe, }; -static int se10_create_platform_device(const struct dmi_system_id *id) +static int se10_create_platform_device(void) { int err; @@ -233,9 +233,10 @@ static int se10_create_platform_device(const struct dmi_system_id *id) return -ENOMEM; err = platform_device_add(se10_pdev); - if (err) + if (err) { platform_device_put(se10_pdev); - + se10_pdev = NULL; + } return err; } @@ -246,7 +247,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NH"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -254,7 +254,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NJ"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -262,7 +261,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NK"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -270,7 +268,6 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NL"), }, - .callback = se10_create_platform_device, }, { .ident = "LENOVO-SE10", @@ -278,7 +275,62 @@ static const struct dmi_system_id se10_dmi_table[] __initconst = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), DMI_MATCH(DMI_PRODUCT_NAME, "12NM"), }, - .callback = se10_create_platform_device, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13LJ"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13LK"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S1"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S2"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S3"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S4"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S5"), + }, + }, + { + .ident = "LENOVO-SE10-G2", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "13S6"), + }, }, {} }; @@ -286,10 +338,20 @@ MODULE_DEVICE_TABLE(dmi, se10_dmi_table); static int __init se10_wdt_init(void) { + int err; + if (!dmi_check_system(se10_dmi_table)) return -ENODEV; - return platform_driver_register(&se10_wdt_driver); + err = platform_driver_register(&se10_wdt_driver); + if (err) + return err; + + err = se10_create_platform_device(); + if (err) + platform_driver_unregister(&se10_wdt_driver); + + return err; } static void __exit se10_wdt_exit(void) diff --git a/drivers/watchdog/machzwd.c b/drivers/watchdog/machzwd.c deleted file mode 100644 index 0ae8e5bc10ae9..0000000000000 --- a/drivers/watchdog/machzwd.c +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * MachZ ZF-Logic Watchdog Timer driver for Linux - * - * The author does NOT admit liability nor provide warranty for - * any of this software. This material is provided "AS-IS" in - * the hope that it may be useful for others. - * - * Author: Fernando Fuganti <fuganti@conectiva.com.br> - * - * Based on sbc60xxwdt.c by Jakob Oestergaard - * - * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the - * following periods: - * wd#1 - 2 seconds; - * wd#2 - 7.2 ms; - * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or - * a system RESET and it starts wd#2 that unconditionally will RESET - * the system when the counter reaches zero. - * - * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> - * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/types.h> -#include <linux/timer.h> -#include <linux/jiffies.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <linux/fs.h> -#include <linux/ioport.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/uaccess.h> - - -/* ports */ -#define ZF_IOBASE 0x218 -#define INDEX 0x218 -#define DATA_B 0x219 -#define DATA_W 0x21A -#define DATA_D 0x21A - -/* indexes */ /* size */ -#define ZFL_VERSION 0x02 /* 16 */ -#define CONTROL 0x10 /* 16 */ -#define STATUS 0x12 /* 8 */ -#define COUNTER_1 0x0C /* 16 */ -#define COUNTER_2 0x0E /* 8 */ -#define PULSE_LEN 0x0F /* 8 */ - -/* controls */ -#define ENABLE_WD1 0x0001 -#define ENABLE_WD2 0x0002 -#define RESET_WD1 0x0010 -#define RESET_WD2 0x0020 -#define GEN_SCI 0x0100 -#define GEN_NMI 0x0200 -#define GEN_SMI 0x0400 -#define GEN_RESET 0x0800 - - -/* utilities */ - -#define WD1 0 -#define WD2 1 - -#define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } -#define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } -#define zf_get_ZFL_version() zf_readw(ZFL_VERSION) - - -static unsigned short zf_readw(unsigned char port) -{ - outb(port, INDEX); - return inw(DATA_W); -} - - -MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); -MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); -MODULE_LICENSE("GPL"); - -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) ")"); - -#define PFX "machzwd" - -static const struct watchdog_info zf_info = { - .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "ZF-Logic watchdog", -}; - - -/* - * action refers to action taken when watchdog resets - * 0 = GEN_RESET - * 1 = GEN_SMI - * 2 = GEN_NMI - * 3 = GEN_SCI - * defaults to GEN_RESET (0) - */ -static int action; -module_param(action, int, 0); -MODULE_PARM_DESC(action, "after watchdog resets, generate: " - "0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); - -static void zf_ping(struct timer_list *unused); - -static int zf_action = GEN_RESET; -static unsigned long zf_is_open; -static char zf_expect_close; -static DEFINE_SPINLOCK(zf_port_lock); -static DEFINE_TIMER(zf_timer, zf_ping); -static unsigned long next_heartbeat; - - -/* timeout for user land heart beat (10 seconds) */ -#define ZF_USER_TIMEO (HZ*10) - -/* timeout for hardware watchdog (~500ms) */ -#define ZF_HW_TIMEO (HZ/2) - -/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ -#define ZF_CTIMEOUT 0xffff - -#ifndef ZF_DEBUG -#define dprintk(format, args...) -#else -#define dprintk(format, args...) \ - pr_debug(":%s:%d: " format, __func__, __LINE__ , ## args) -#endif - - -static inline void zf_set_status(unsigned char new) -{ - zf_writeb(STATUS, new); -} - - -/* CONTROL register functions */ - -static inline unsigned short zf_get_control(void) -{ - return zf_readw(CONTROL); -} - -static inline void zf_set_control(unsigned short new) -{ - zf_writew(CONTROL, new); -} - - -/* WD#? counter functions */ -/* - * Just set counter value - */ - -static inline void zf_set_timer(unsigned short new, unsigned char n) -{ - switch (n) { - case WD1: - zf_writew(COUNTER_1, new); - fallthrough; - case WD2: - zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); - fallthrough; - default: - return; - } -} - -/* - * stop hardware timer - */ -static void zf_timer_off(void) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - /* stop internal ping */ - timer_delete_sync(&zf_timer); - - spin_lock_irqsave(&zf_port_lock, flags); - /* stop watchdog timer */ - ctrl_reg = zf_get_control(); - ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ - ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - pr_info("Watchdog timer is now disabled\n"); -} - - -/* - * start hardware timer - */ -static void zf_timer_on(void) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - spin_lock_irqsave(&zf_port_lock, flags); - - zf_writeb(PULSE_LEN, 0xff); - - zf_set_timer(ZF_CTIMEOUT, WD1); - - /* user land ping */ - next_heartbeat = jiffies + ZF_USER_TIMEO; - - /* start the timer for internal ping */ - mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); - - /* start watchdog timer */ - ctrl_reg = zf_get_control(); - ctrl_reg |= (ENABLE_WD1|zf_action); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - pr_info("Watchdog timer is now enabled\n"); -} - - -static void zf_ping(struct timer_list *unused) -{ - unsigned int ctrl_reg = 0; - unsigned long flags; - - zf_writeb(COUNTER_2, 0xff); - - if (time_before(jiffies, next_heartbeat)) { - dprintk("time_before: %ld\n", next_heartbeat - jiffies); - /* - * reset event is activated by transition from 0 to 1 on - * RESET_WD1 bit and we assume that it is already zero... - */ - - spin_lock_irqsave(&zf_port_lock, flags); - ctrl_reg = zf_get_control(); - ctrl_reg |= RESET_WD1; - zf_set_control(ctrl_reg); - - /* ...and nothing changes until here */ - ctrl_reg &= ~(RESET_WD1); - zf_set_control(ctrl_reg); - spin_unlock_irqrestore(&zf_port_lock, flags); - - mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); - } else - pr_crit("I will reset your machine\n"); -} - -static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, - loff_t *ppos) -{ - /* See if we got the magic character */ - if (count) { - /* - * no need to check for close confirmation - * no way to disable watchdog ;) - */ - if (!nowayout) { - size_t ofs; - /* - * note: just in case someone wrote the magic character - * five months ago... - */ - zf_expect_close = 0; - - /* now scan */ - for (ofs = 0; ofs != count; ofs++) { - char c; - if (get_user(c, buf + ofs)) - return -EFAULT; - if (c == 'V') { - zf_expect_close = 42; - dprintk("zf_expect_close = 42\n"); - } - } - } - - /* - * Well, anyhow someone wrote to us, - * we should return that favour - */ - next_heartbeat = jiffies + ZF_USER_TIMEO; - dprintk("user ping at %ld\n", jiffies); - } - return count; -} - -static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - switch (cmd) { - case WDIOC_GETSUPPORT: - if (copy_to_user(argp, &zf_info, sizeof(zf_info))) - return -EFAULT; - break; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_KEEPALIVE: - zf_ping(NULL); - break; - default: - return -ENOTTY; - } - return 0; -} - -static int zf_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &zf_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); - zf_timer_on(); - return stream_open(inode, file); -} - -static int zf_close(struct inode *inode, struct file *file) -{ - if (zf_expect_close == 42) - zf_timer_off(); - else { - timer_delete(&zf_timer); - pr_err("device file closed unexpectedly. Will not stop the WDT!\n"); - } - clear_bit(0, &zf_is_open); - zf_expect_close = 0; - return 0; -} - -/* - * Notifier for system down - */ - -static int zf_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - zf_timer_off(); - return NOTIFY_DONE; -} - -static const struct file_operations zf_fops = { - .owner = THIS_MODULE, - .write = zf_write, - .unlocked_ioctl = zf_ioctl, - .compat_ioctl = compat_ptr_ioctl, - .open = zf_open, - .release = zf_close, -}; - -static struct miscdevice zf_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &zf_fops, -}; - - -/* - * The device needs to learn about soft shutdowns in order to - * turn the timebomb registers off. - */ -static struct notifier_block zf_notifier = { - .notifier_call = zf_notify_sys, -}; - -static void __init zf_show_action(int act) -{ - static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" }; - - pr_info("Watchdog using action = %s\n", str[act]); -} - -static int __init zf_init(void) -{ - int ret; - - pr_info("MachZ ZF-Logic Watchdog driver initializing\n"); - - ret = zf_get_ZFL_version(); - if (!ret || ret == 0xffff) { - pr_warn("no ZF-Logic found\n"); - return -ENODEV; - } - - if (action <= 3 && action >= 0) - zf_action = zf_action >> action; - else - action = 0; - - zf_show_action(action); - - if (!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")) { - pr_err("cannot reserve I/O ports at %d\n", ZF_IOBASE); - ret = -EBUSY; - goto no_region; - } - - ret = register_reboot_notifier(&zf_notifier); - if (ret) { - pr_err("can't register reboot notifier (err=%d)\n", ret); - goto no_reboot; - } - - ret = misc_register(&zf_miscdev); - if (ret) { - pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); - goto no_misc; - } - - zf_set_status(0); - zf_set_control(0); - - return 0; - -no_misc: - unregister_reboot_notifier(&zf_notifier); -no_reboot: - release_region(ZF_IOBASE, 3); -no_region: - return ret; -} - - -static void __exit zf_exit(void) -{ - zf_timer_off(); - - misc_deregister(&zf_miscdev); - unregister_reboot_notifier(&zf_notifier); - release_region(ZF_IOBASE, 3); -} - -module_init(zf_init); -module_exit(zf_exit); diff --git a/drivers/watchdog/menz69_wdt.c b/drivers/watchdog/menz69_wdt.c index 6e5e4e5c0b566..3fe23451135d7 100644 --- a/drivers/watchdog/menz69_wdt.c +++ b/drivers/watchdog/menz69_wdt.c @@ -163,5 +163,4 @@ module_mcb_driver(men_z069_driver); MODULE_AUTHOR("Johannes Thumshirn <jth@kernel.org>"); MODULE_DESCRIPTION("Watchdog driver for the MEN z069 IP-Core"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("mcb:16z069"); MODULE_IMPORT_NS("MCB"); diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index dfaac5995c84c..49bd04841f0c8 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -9,6 +9,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/watchdog.h> @@ -42,6 +43,7 @@ struct qcom_wdt_match_data { const u32 *offset; bool pretimeout; u32 max_tick_count; + u32 wdt_reason_val; }; struct qcom_wdt { @@ -185,6 +187,7 @@ static const struct qcom_wdt_match_data match_data_ipq5424 = { .offset = reg_offset_data_kpss, .pretimeout = true, .max_tick_count = 0xFFFFFU, + .wdt_reason_val = 5, }; static const struct qcom_wdt_match_data match_data_kpss = { @@ -193,6 +196,40 @@ static const struct qcom_wdt_match_data match_data_kpss = { .max_tick_count = 0xFFFFFU, }; +static int qcom_wdt_get_bootstatus(struct device *dev, struct qcom_wdt *wdt, + u32 val) +{ + struct device_node *imem; + struct resource res; + void __iomem *addr; + int ret; + + imem = of_parse_phandle(dev->of_node, "sram", 0); + if (!imem) { + /* Read the EXPIRED_STATUS bit as a fallback */ + if (readl(wdt_addr(wdt, WDT_STS)) & 1) + wdt->wdd.bootstatus = WDIOF_CARDRESET; + + return 0; + } + + ret = of_address_to_resource(imem, 0, &res); + of_node_put(imem); + if (ret) + return ret; + + addr = ioremap(res.start, resource_size(&res)); + if (!addr) + return -ENOMEM; + + if (readl(addr) == val) + wdt->wdd.bootstatus = WDIOF_CARDRESET; + + iounmap(addr); + + return 0; +} + static int qcom_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -273,8 +310,9 @@ static int qcom_wdt_probe(struct platform_device *pdev) wdt->wdd.parent = dev; wdt->layout = data->offset; - if (readl(wdt_addr(wdt, WDT_STS)) & 1) - wdt->wdd.bootstatus = WDIOF_CARDRESET; + ret = qcom_wdt_get_bootstatus(dev, wdt, data->wdt_reason_val); + if (ret) + dev_err(dev, "failed to get the bootstatus, %d\n", ret); /* * If 'timeout-sec' unspecified in devicetree, assume a 30 second diff --git a/drivers/watchdog/realtek_otto_wdt.c b/drivers/watchdog/realtek_otto_wdt.c index 2c30ddd574c59..01b3ef89bacfc 100644 --- a/drivers/watchdog/realtek_otto_wdt.c +++ b/drivers/watchdog/realtek_otto_wdt.c @@ -114,12 +114,6 @@ static int otto_wdt_tick_ms(struct otto_wdt_ctrl *ctrl, int prescale) * the value stored in those fields. This means each phase will run for at least * one tick, so small values need to be clamped to correctly reflect the timeout. */ -static inline unsigned int div_round_ticks(unsigned int val, unsigned int tick_duration, - unsigned int min_ticks) -{ - return max(min_ticks, DIV_ROUND_UP(val, tick_duration)); -} - static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned int timeout, unsigned int pretimeout) { @@ -140,9 +134,9 @@ static int otto_wdt_determine_timeouts(struct watchdog_device *wdev, unsigned in return -EINVAL; tick_ms = otto_wdt_tick_ms(ctrl, prescale); - total_ticks = div_round_ticks(timeout_ms, tick_ms, 2); - phase1_ticks = div_round_ticks(timeout_ms - pretimeout_ms, tick_ms, 1); - phase2_ticks = total_ticks - phase1_ticks; + total_ticks = max(2, DIV_ROUND_UP(timeout_ms, tick_ms)); + phase2_ticks = max(1, pretimeout_ms / tick_ms); + phase1_ticks = total_ticks - phase2_ticks; prescale_next++; } while (phase1_ticks > OTTO_WDT_PHASE_TICKS_MAX @@ -302,15 +296,15 @@ static int otto_wdt_probe(struct platform_device *pdev) if (IS_ERR(ctrl->base)) return PTR_ERR(ctrl->base); + 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); - ret = otto_wdt_probe_clk(ctrl); - if (ret) - return ret; - ctrl->irq_phase1 = platform_get_irq_byname(pdev, "phase1"); if (ctrl->irq_phase1 < 0) return ctrl->irq_phase1; diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c index be7d7db475911..c3c7715140ea8 100644 --- a/drivers/watchdog/rti_wdt.c +++ b/drivers/watchdog/rti_wdt.c @@ -35,7 +35,8 @@ #define RTIWWDRXCTRL 0xa4 #define RTIWWDSIZECTRL 0xa8 -#define RTIWWDRX_NMI 0xa +#define RTIWWDRXN_RST 0x5 +#define RTIWWDRXN_NMI 0xa #define RTIWWDSIZE_50P 0x50 #define RTIWWDSIZE_25P 0x500 @@ -63,22 +64,29 @@ static int heartbeat; +struct rti_wdt_data { + bool nmi; +}; + /* * struct to hold data for each WDT device * @base - base io address of WD device * @freq - source clock frequency of WDT * @wdd - hold watchdog device as is in WDT core + * @nmi - Set if this WDT instance supports generating NMI */ struct rti_wdt_device { void __iomem *base; unsigned long freq; struct watchdog_device wdd; + bool nmi; }; static int rti_wdt_start(struct watchdog_device *wdd) { u32 timer_margin; struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd); + u8 reaction; int ret; ret = pm_runtime_resume_and_get(wdd->parent); @@ -101,8 +109,13 @@ static int rti_wdt_start(struct watchdog_device *wdd) */ wdd->min_hw_heartbeat_ms = 520 * wdd->timeout + MAX_HW_ERROR; - /* Generate NMI when wdt expires */ - writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL); + /* When WDT expires, generate NMI or reset if NMI not supported */ + if (wdt->nmi) + reaction = RTIWWDRXN_NMI; + else + reaction = RTIWWDRXN_RST; + + writel_relaxed(reaction, wdt->base + RTIWWDRXCTRL); /* Open window size 50%; this is the largest window size available */ writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL); @@ -210,6 +223,7 @@ static int rti_wdt_probe(struct platform_device *pdev) { int ret = 0; struct device *dev = &pdev->dev; + const struct rti_wdt_data *data; struct watchdog_device *wdd; struct rti_wdt_device *wdt; struct clk *clk; @@ -254,6 +268,14 @@ static int rti_wdt_probe(struct platform_device *pdev) wdd->timeout = DEFAULT_HEARTBEAT; wdd->parent = dev; + data = device_get_match_data(dev); + if (!data) { + ret = -ENODEV; + goto err_iomap; + } + + wdt->nmi = data->nmi; + watchdog_set_drvdata(wdd, wdt); watchdog_set_nowayout(wdd, 1); watchdog_set_restart_priority(wdd, 128); @@ -361,8 +383,17 @@ static void rti_wdt_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); } +static const struct rti_wdt_data rti_wdt_j7_data = { + .nmi = true, +}; + +static const struct rti_wdt_data rti_wdt_am62l_data = { + .nmi = false, +}; + static const struct of_device_id rti_wdt_of_match[] = { - { .compatible = "ti,j7-rti-wdt", }, + { .compatible = "ti,j7-rti-wdt", .data = &rti_wdt_j7_data }, + { .compatible = "ti,am62l-rti-wdt", .data = &rti_wdt_am62l_data }, {}, }; MODULE_DEVICE_TABLE(of, rti_wdt_of_match); diff --git a/drivers/watchdog/rzn1_wdt.c b/drivers/watchdog/rzn1_wdt.c index 96fd04fbc2a26..4fdc5363ba98f 100644 --- a/drivers/watchdog/rzn1_wdt.c +++ b/drivers/watchdog/rzn1_wdt.c @@ -79,14 +79,6 @@ static int rzn1_wdt_start(struct watchdog_device *w) return 0; } -static irqreturn_t rzn1_wdt_irq(int irq, void *_wdt) -{ - pr_crit("RZN1 Watchdog. Initiating system reboot\n"); - emergency_restart(); - - return IRQ_HANDLED; -} - static struct watchdog_info rzn1_wdt_info = { .identity = "RZ/N1 Watchdog", .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, @@ -102,11 +94,9 @@ static int rzn1_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rzn1_watchdog *wdt; - struct device_node *np = dev->of_node; - struct clk *clk; unsigned long clk_rate; + struct clk *clk; int ret; - int irq; wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) @@ -116,28 +106,13 @@ static int rzn1_wdt_probe(struct platform_device *pdev) if (IS_ERR(wdt->base)) return PTR_ERR(wdt->base); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_irq(dev, irq, rzn1_wdt_irq, 0, - np->name, wdt); - if (ret) { - dev_err(dev, "failed to request irq %d\n", irq); - return ret; - } - clk = devm_clk_get_enabled(dev, NULL); - if (IS_ERR(clk)) { - dev_err(dev, "failed to get the clock\n"); - return PTR_ERR(clk); - } + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get the clock\n"); clk_rate = clk_get_rate(clk); - if (!clk_rate) { - dev_err(dev, "failed to get the clock rate\n"); - return -EINVAL; - } + if (!clk_rate) + return dev_err_probe(dev, -EINVAL, "failed to get the clock rate\n"); wdt->clk_rate_khz = clk_rate / 1000; wdt->wdtdev.info = &rzn1_wdt_info; diff --git a/drivers/watchdog/sama5d4_wdt.c b/drivers/watchdog/sama5d4_wdt.c index 13e72918338a2..704b786cc2ec6 100644 --- a/drivers/watchdog/sama5d4_wdt.c +++ b/drivers/watchdog/sama5d4_wdt.c @@ -30,6 +30,7 @@ struct sama5d4_wdt { void __iomem *reg_base; u32 mr; u32 ir; + u32 wddis_mask; unsigned long last_ping; bool need_irq; bool sam9x60_support; @@ -48,7 +49,10 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -#define wdt_enabled (!(wdt->mr & AT91_WDT_WDDIS)) +static inline bool wdt_enabled(struct sama5d4_wdt *wdt) +{ + return !(wdt->mr & wdt->wddis_mask); +} #define wdt_read(wdt, field) \ readl_relaxed((wdt)->reg_base + (field)) @@ -81,12 +85,9 @@ static int sama5d4_wdt_start(struct watchdog_device *wdd) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); - if (wdt->sam9x60_support) { + if (wdt->sam9x60_support) writel_relaxed(wdt->ir, wdt->reg_base + AT91_SAM9X60_IER); - wdt->mr &= ~AT91_SAM9X60_WDDIS; - } else { - wdt->mr &= ~AT91_WDT_WDDIS; - } + wdt->mr &= ~wdt->wddis_mask; wdt_write(wdt, AT91_WDT_MR, wdt->mr); return 0; @@ -96,12 +97,9 @@ static int sama5d4_wdt_stop(struct watchdog_device *wdd) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); - if (wdt->sam9x60_support) { + if (wdt->sam9x60_support) writel_relaxed(wdt->ir, wdt->reg_base + AT91_SAM9X60_IDR); - wdt->mr |= AT91_SAM9X60_WDDIS; - } else { - wdt->mr |= AT91_WDT_WDDIS; - } + wdt->mr |= wdt->wddis_mask; wdt_write(wdt, AT91_WDT_MR, wdt->mr); return 0; @@ -117,7 +115,7 @@ static int sama5d4_wdt_ping(struct watchdog_device *wdd) } static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd, - unsigned int timeout) + unsigned int timeout) { struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd); u32 value = WDT_SEC2TICKS(timeout); @@ -140,8 +138,8 @@ static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd, * If the watchdog is enabled, then the timeout can be updated. Else, * wait that the user enables it. */ - if (wdt_enabled) - wdt_write(wdt, AT91_WDT_MR, wdt->mr & ~AT91_WDT_WDDIS); + if (wdt_enabled(wdt)) + wdt_write(wdt, AT91_WDT_MR, wdt->mr & ~wdt->wddis_mask); wdd->timeout = timeout; @@ -184,10 +182,7 @@ static int of_sama5d4_wdt_init(struct device_node *np, struct sama5d4_wdt *wdt) { const char *tmp; - if (wdt->sam9x60_support) - wdt->mr = AT91_SAM9X60_WDDIS; - else - wdt->mr = AT91_WDT_WDDIS; + wdt->mr = wdt->wddis_mask; if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) && !strcmp(tmp, "software")) @@ -213,15 +208,11 @@ static int sama5d4_wdt_init(struct sama5d4_wdt *wdt) * If the watchdog is already running, we can safely update it. * Else, we have to disable it properly. */ - if (!wdt_enabled) { + if (!wdt_enabled(wdt)) { reg = wdt_read(wdt, AT91_WDT_MR); - if (wdt->sam9x60_support && (!(reg & AT91_SAM9X60_WDDIS))) - wdt_write_nosleep(wdt, AT91_WDT_MR, - reg | AT91_SAM9X60_WDDIS); - else if (!wdt->sam9x60_support && - (!(reg & AT91_WDT_WDDIS))) + if (!(reg & wdt->wddis_mask)) wdt_write_nosleep(wdt, AT91_WDT_MR, - reg | AT91_WDT_WDDIS); + reg | wdt->wddis_mask); } if (wdt->sam9x60_support) { @@ -273,6 +264,9 @@ static int sama5d4_wdt_probe(struct platform_device *pdev) of_device_is_compatible(dev->of_node, "microchip,sama7g5-wdt")) wdt->sam9x60_support = true; + wdt->wddis_mask = wdt->sam9x60_support ? AT91_SAM9X60_WDDIS + : AT91_WDT_WDDIS; + watchdog_set_drvdata(wdd, wdt); regs = devm_platform_ioremap_resource(pdev, 0); @@ -306,8 +300,8 @@ static int sama5d4_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(wdd, wdt_timeout, dev); reg = wdt_read(wdt, AT91_WDT_MR); - if (!(reg & AT91_WDT_WDDIS)) { - wdt->mr &= ~AT91_WDT_WDDIS; + if (!(reg & wdt->wddis_mask)) { + wdt->mr &= ~wdt->wddis_mask; set_bit(WDOG_HW_RUNNING, &wdd->status); } diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c deleted file mode 100644 index 005f62e4a4fb3..0000000000000 --- a/drivers/watchdog/sc520_wdt.c +++ /dev/null @@ -1,430 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * AMD Elan SC520 processor Watchdog Timer driver - * - * Based on acquirewdt.c by Alan Cox, - * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> - * - * The authors do NOT admit liability nor provide warranty for - * any of this software. This material is provided "AS-IS" in - * the hope that it may be useful for others. - * - * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> - * 9/27 - 2001 [Initial release] - * - * Additional fixes Alan Cox - * - Fixed formatting - * - Removed debug printks - * - Fixed SMP built kernel deadlock - * - Switched to private locks not lock_kernel - * - Used ioremap/writew/readw - * - Added NOWAYOUT support - * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> - * - Change comments - * - Eliminate fop_llseek - * - Change CONFIG_WATCHDOG_NOWAYOUT semantics - * - Add KERN_* tags to printks - * - fix possible wdt_is_open race - * - Report proper capabilities in watchdog_info - * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, - * GETTIMEOUT, SETOPTIONS} ioctls - * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> - * - cleanup of trailing spaces - * - added extra printk's for startup problems - * - use module_param - * - made timeout (the emulated heartbeat) a module_param - * - made the keepalive ping an internal subroutine - * 3/27 - 2004 Changes by Sean Young <sean@mess.org> - * - set MMCR_BASE to 0xfffef000 - * - CBAR does not need to be read - * - removed debugging printks - * - * This WDT driver is different from most other Linux WDT - * drivers in that the driver will ping the watchdog by itself, - * because this particular WDT has a very short timeout (1.6 - * seconds) and it would be insane to count on any userspace - * daemon always getting scheduled within that time frame. - * - * This driver uses memory mapped IO, and spinlock. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/types.h> -#include <linux/timer.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <linux/fs.h> -#include <linux/ioport.h> -#include <linux/notifier.h> -#include <linux/reboot.h> -#include <linux/init.h> -#include <linux/jiffies.h> -#include <linux/io.h> -#include <linux/uaccess.h> - - -/* - * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) - * - * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s - * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s - * - * We will program the SC520 watchdog for a timeout of 2.01s. - * If we reset the watchdog every ~250ms we should be safe. - */ - -#define WDT_INTERVAL (HZ/4+1) - -/* - * We must not require too good response from the userspace daemon. - * Here we require the userspace daemon to send us a heartbeat - * char to /dev/watchdog every 30 seconds. - */ - -#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ -static int timeout = WATCHDOG_TIMEOUT; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, - "Watchdog timeout in seconds. (1 <= timeout <= 3600, 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) ")"); - -/* - * AMD Elan SC520 - Watchdog Timer Registers - */ -#define MMCR_BASE 0xfffef000 /* The default base address */ -#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ - -/* WDT Control Register bit definitions */ -#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ -#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ -#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ -#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ -#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ -#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ -#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ - -static __u16 __iomem *wdtmrctl; - -static void wdt_timer_ping(struct timer_list *); -static DEFINE_TIMER(timer, wdt_timer_ping); -static unsigned long next_heartbeat; -static unsigned long wdt_is_open; -static char wdt_expect_close; -static DEFINE_SPINLOCK(wdt_spinlock); - -/* - * Whack the dog - */ - -static void wdt_timer_ping(struct timer_list *unused) -{ - /* If we got a heartbeat pulse within the WDT_US_INTERVAL - * we agree to ping the WDT - */ - if (time_before(jiffies, next_heartbeat)) { - /* Ping the WDT */ - spin_lock(&wdt_spinlock); - writew(0xAAAA, wdtmrctl); - writew(0x5555, wdtmrctl); - spin_unlock(&wdt_spinlock); - - /* Re-set the timer interval */ - mod_timer(&timer, jiffies + WDT_INTERVAL); - } else - pr_warn("Heartbeat lost! Will not ping the watchdog\n"); -} - -/* - * Utility routines - */ - -static void wdt_config(int writeval) -{ - unsigned long flags; - - /* buy some time (ping) */ - spin_lock_irqsave(&wdt_spinlock, flags); - readw(wdtmrctl); /* ensure write synchronization */ - writew(0xAAAA, wdtmrctl); - writew(0x5555, wdtmrctl); - /* unlock WDT = make WDT configuration register writable one time */ - writew(0x3333, wdtmrctl); - writew(0xCCCC, wdtmrctl); - /* write WDT configuration register */ - writew(writeval, wdtmrctl); - spin_unlock_irqrestore(&wdt_spinlock, flags); -} - -static int wdt_startup(void) -{ - next_heartbeat = jiffies + (timeout * HZ); - - /* Start the timer */ - mod_timer(&timer, jiffies + WDT_INTERVAL); - - /* Start the watchdog */ - wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); - - pr_info("Watchdog timer is now enabled\n"); - return 0; -} - -static int wdt_turnoff(void) -{ - /* Stop the timer */ - timer_delete_sync(&timer); - - /* Stop the watchdog */ - wdt_config(0); - - pr_info("Watchdog timer is now disabled...\n"); - return 0; -} - -static int wdt_keepalive(void) -{ - /* user land ping */ - next_heartbeat = jiffies + (timeout * HZ); - return 0; -} - -static int wdt_set_heartbeat(int t) -{ - if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ - return -EINVAL; - - timeout = t; - return 0; -} - -/* - * /dev/watchdog handling - */ - -static ssize_t fop_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - /* See if we got the magic character 'V' and reload the timer */ - if (count) { - if (!nowayout) { - size_t ofs; - - /* note: just in case someone wrote the magic character - * five months ago... */ - wdt_expect_close = 0; - - /* now scan */ - for (ofs = 0; ofs != count; ofs++) { - char c; - if (get_user(c, buf + ofs)) - return -EFAULT; - if (c == 'V') - wdt_expect_close = 42; - } - } - - /* Well, anyhow someone wrote to us, we should - return that favour */ - wdt_keepalive(); - } - return count; -} - -static int fop_open(struct inode *inode, struct file *file) -{ - /* Just in case we're already talking to someone... */ - if (test_and_set_bit(0, &wdt_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); - - /* Good, fire up the show */ - wdt_startup(); - return stream_open(inode, file); -} - -static int fop_close(struct inode *inode, struct file *file) -{ - if (wdt_expect_close == 42) - wdt_turnoff(); - else { - pr_crit("Unexpected close, not stopping watchdog!\n"); - wdt_keepalive(); - } - clear_bit(0, &wdt_is_open); - wdt_expect_close = 0; - return 0; -} - -static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - static const struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT - | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "SC520", - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_SETOPTIONS: - { - int new_options, retval = -EINVAL; - - if (get_user(new_options, p)) - return -EFAULT; - - if (new_options & WDIOS_DISABLECARD) { - wdt_turnoff(); - retval = 0; - } - - if (new_options & WDIOS_ENABLECARD) { - wdt_startup(); - retval = 0; - } - - return retval; - } - case WDIOC_KEEPALIVE: - wdt_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - { - int new_timeout; - - if (get_user(new_timeout, p)) - return -EFAULT; - - if (wdt_set_heartbeat(new_timeout)) - return -EINVAL; - - wdt_keepalive(); - } - fallthrough; - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - default: - return -ENOTTY; - } -} - -static const struct file_operations wdt_fops = { - .owner = THIS_MODULE, - .write = fop_write, - .open = fop_open, - .release = fop_close, - .unlocked_ioctl = fop_ioctl, - .compat_ioctl = compat_ptr_ioctl, -}; - -static struct miscdevice wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &wdt_fops, -}; - -/* - * Notifier for system down - */ - -static int wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - wdt_turnoff(); - return NOTIFY_DONE; -} - -/* - * The WDT needs to learn about soft shutdowns in order to - * turn the timebomb registers off. - */ - -static struct notifier_block wdt_notifier = { - .notifier_call = wdt_notify_sys, -}; - -static void __exit sc520_wdt_unload(void) -{ - if (!nowayout) - wdt_turnoff(); - - /* Deregister */ - misc_deregister(&wdt_miscdev); - unregister_reboot_notifier(&wdt_notifier); - iounmap(wdtmrctl); -} - -static int __init sc520_wdt_init(void) -{ - int rc = -EBUSY; - - /* Check that the timeout value is within it's range ; - if not reset to the default */ - if (wdt_set_heartbeat(timeout)) { - wdt_set_heartbeat(WATCHDOG_TIMEOUT); - pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n", - WATCHDOG_TIMEOUT); - } - - wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2); - if (!wdtmrctl) { - pr_err("Unable to remap memory\n"); - rc = -ENOMEM; - goto err_out_region2; - } - - rc = register_reboot_notifier(&wdt_notifier); - if (rc) { - pr_err("cannot register reboot notifier (err=%d)\n", rc); - goto err_out_ioremap; - } - - rc = misc_register(&wdt_miscdev); - if (rc) { - pr_err("cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, rc); - goto err_out_notifier; - } - - pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", - timeout, nowayout); - - return 0; - -err_out_notifier: - unregister_reboot_notifier(&wdt_notifier); -err_out_ioremap: - iounmap(wdtmrctl); -err_out_region2: - return rc; -} - -module_init(sc520_wdt_init); -module_exit(sc520_wdt_unload); - -MODULE_AUTHOR("Scott and Bill Jennings"); -MODULE_DESCRIPTION( - "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); -MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c index 3011e1af00f98..9f2c2ab4e2f15 100644 --- a/drivers/watchdog/smsc37b787_wdt.c +++ b/drivers/watchdog/smsc37b787_wdt.c @@ -36,7 +36,7 @@ * mknod /dev/watchdog c 10 130 * * For an example userspace keep-alive daemon, see: - * Documentation/watchdog/wdt.rst + * samples/watchdog/watchdog-simple.c */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 2bd3dc25cb030..7e99c3b1f3676 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -92,7 +92,8 @@ static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev) dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && dev->revision < 0x40) { return sp5100; - } else if (dev->vendor == PCI_VENDOR_ID_AMD && + } else if ((dev->vendor == PCI_VENDOR_ID_AMD || + dev->vendor == PCI_VENDOR_ID_HYGON) && sp5100_tco_pci->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS && sp5100_tco_pci->revision >= AMD_ZEN_SMBUS_PCI_REV) { return efch_mmio; diff --git a/drivers/watchdog/sprd_wdt.c b/drivers/watchdog/sprd_wdt.c index 4e689b6ff1418..aacf04616fefe 100644 --- a/drivers/watchdog/sprd_wdt.c +++ b/drivers/watchdog/sprd_wdt.c @@ -320,10 +320,9 @@ static int sprd_wdt_probe(struct platform_device *pdev) watchdog_init_timeout(&wdt->wdd, 0, dev); ret = devm_watchdog_register_device(dev, &wdt->wdd); - if (ret) { - sprd_wdt_disable(wdt); + if (ret) return ret; - } + platform_set_drvdata(pdev, wdt); return 0; diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index 5c6e3fa001d88..f3bb935c08c2e 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -718,7 +718,7 @@ static void ziirave_wdt_remove(struct i2c_client *client) } static const struct i2c_device_id ziirave_wdt_id[] = { - { "rave-wdt" }, + { .name = "rave-wdt" }, { } }; MODULE_DEVICE_TABLE(i2c, ziirave_wdt_id); |
