summaryrefslogtreecommitdiffstats
path: root/drivers/watchdog
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--drivers/watchdog/Kconfig2207
-rw-r--r--drivers/watchdog/Makefile231
-rw-r--r--drivers/watchdog/acquirewdt.c331
-rw-r--r--drivers/watchdog/advantechwdt.c340
-rw-r--r--drivers/watchdog/alim1535_wdt.c451
-rw-r--r--drivers/watchdog/alim7101_wdt.c451
-rw-r--r--drivers/watchdog/apple_wdt.c226
-rw-r--r--drivers/watchdog/ar7_wdt.c316
-rw-r--r--drivers/watchdog/arm_smc_wdt.c188
-rw-r--r--drivers/watchdog/armada_37xx_wdt.c377
-rw-r--r--drivers/watchdog/asm9260_wdt.c376
-rw-r--r--drivers/watchdog/aspeed_wdt.c412
-rw-r--r--drivers/watchdog/at91rm9200_wdt.c343
-rw-r--r--drivers/watchdog/at91sam9_wdt.c417
-rw-r--r--drivers/watchdog/at91sam9_wdt.h61
-rw-r--r--drivers/watchdog/ath79_wdt.c335
-rw-r--r--drivers/watchdog/bcm2835_wdt.c249
-rw-r--r--drivers/watchdog/bcm47xx_wdt.c243
-rw-r--r--drivers/watchdog/bcm7038_wdt.c247
-rw-r--r--drivers/watchdog/bcm_kona_wdt.c341
-rw-r--r--drivers/watchdog/bd9576_wdt.c302
-rw-r--r--drivers/watchdog/booke_wdt.c243
-rw-r--r--drivers/watchdog/cadence_wdt.c438
-rw-r--r--drivers/watchdog/cpu5wdt.c285
-rw-r--r--drivers/watchdog/cpwd.c662
-rw-r--r--drivers/watchdog/da9052_wdt.c194
-rw-r--r--drivers/watchdog/da9055_wdt.c169
-rw-r--r--drivers/watchdog/da9062_wdt.c286
-rw-r--r--drivers/watchdog/da9063_wdt.c299
-rw-r--r--drivers/watchdog/davinci_wdt.c274
-rw-r--r--drivers/watchdog/db8500_wdt.c152
-rw-r--r--drivers/watchdog/diag288_wdt.c319
-rw-r--r--drivers/watchdog/digicolor_wdt.c163
-rw-r--r--drivers/watchdog/dw_wdt.c722
-rw-r--r--drivers/watchdog/ebc-c384_wdt.c142
-rw-r--r--drivers/watchdog/ep93xx_wdt.c146
-rw-r--r--drivers/watchdog/eurotechwdt.c476
-rw-r--r--drivers/watchdog/exar_wdt.c429
-rw-r--r--drivers/watchdog/f71808e_wdt.c668
-rw-r--r--drivers/watchdog/ftwdt010_wdt.c244
-rw-r--r--drivers/watchdog/gef_wdt.c331
-rw-r--r--drivers/watchdog/geodewdt.c290
-rw-r--r--drivers/watchdog/gpio_wdt.c196
-rw-r--r--drivers/watchdog/gxp-wdt.c175
-rw-r--r--drivers/watchdog/hpwdt.c418
-rw-r--r--drivers/watchdog/i6300esb.c353
-rw-r--r--drivers/watchdog/iTCO_vendor.h14
-rw-r--r--drivers/watchdog/iTCO_vendor_support.c216
-rw-r--r--drivers/watchdog/iTCO_wdt.c664
-rw-r--r--drivers/watchdog/ib700wdt.c379
-rw-r--r--drivers/watchdog/ibmasr.c422
-rw-r--r--drivers/watchdog/ie6xx_wdt.c317
-rw-r--r--drivers/watchdog/imgpdc_wdt.c316
-rw-r--r--drivers/watchdog/imx2_wdt.c441
-rw-r--r--drivers/watchdog/imx7ulp_wdt.c434
-rw-r--r--drivers/watchdog/imx_sc_wdt.c257
-rw-r--r--drivers/watchdog/indydog.c204
-rw-r--r--drivers/watchdog/intel-mid_wdt.c206
-rw-r--r--drivers/watchdog/it8712f_wdt.c450
-rw-r--r--drivers/watchdog/it87_wdt.c354
-rw-r--r--drivers/watchdog/ixp4xx_wdt.c209
-rw-r--r--drivers/watchdog/jz4740_wdt.c200
-rw-r--r--drivers/watchdog/keembay_wdt.c296
-rw-r--r--drivers/watchdog/kempld_wdt.c552
-rw-r--r--drivers/watchdog/lantiq_wdt.c288
-rw-r--r--drivers/watchdog/loongson1_wdt.c156
-rw-r--r--drivers/watchdog/lpc18xx_wdt.c318
-rw-r--r--drivers/watchdog/m54xx_wdt.c227
-rw-r--r--drivers/watchdog/machzwd.c453
-rw-r--r--drivers/watchdog/max63xx_wdt.c302
-rw-r--r--drivers/watchdog/max77620_wdt.c264
-rw-r--r--drivers/watchdog/mei_wdt.c664
-rw-r--r--drivers/watchdog/mena21_wdt.c228
-rw-r--r--drivers/watchdog/menf21bmc_wdt.c184
-rw-r--r--drivers/watchdog/menz69_wdt.c167
-rw-r--r--drivers/watchdog/meson_gxbb_wdt.c231
-rw-r--r--drivers/watchdog/meson_wdt.c226
-rw-r--r--drivers/watchdog/mixcomwd.c313
-rw-r--r--drivers/watchdog/mlx_wdt.c340
-rw-r--r--drivers/watchdog/moxart_wdt.c167
-rw-r--r--drivers/watchdog/mpc8xxx_wdt.c266
-rw-r--r--drivers/watchdog/msc313e_wdt.c170
-rw-r--r--drivers/watchdog/mt7621_wdt.c189
-rw-r--r--drivers/watchdog/mtk_wdt.c462
-rw-r--r--drivers/watchdog/mtx-1_wdt.c248
-rw-r--r--drivers/watchdog/ni903x_wdt.c257
-rw-r--r--drivers/watchdog/nic7018_wdt.c252
-rw-r--r--drivers/watchdog/npcm_wdt.c266
-rw-r--r--drivers/watchdog/nv_tco.c515
-rw-r--r--drivers/watchdog/nv_tco.h60
-rw-r--r--drivers/watchdog/octeon-wdt-main.c613
-rw-r--r--drivers/watchdog/octeon-wdt-nmi.S90
-rw-r--r--drivers/watchdog/of_xilinx_wdt.c312
-rw-r--r--drivers/watchdog/omap_wdt.c383
-rw-r--r--drivers/watchdog/omap_wdt.h36
-rw-r--r--drivers/watchdog/orion_wdt.c692
-rw-r--r--drivers/watchdog/pc87413_wdt.c592
-rw-r--r--drivers/watchdog/pcwd.c998
-rw-r--r--drivers/watchdog/pcwd_pci.c819
-rw-r--r--drivers/watchdog/pcwd_usb.c807
-rw-r--r--drivers/watchdog/pic32-dmt.c240
-rw-r--r--drivers/watchdog/pic32-wdt.c244
-rw-r--r--drivers/watchdog/pika_wdt.c303
-rw-r--r--drivers/watchdog/pm8916_wdt.c277
-rw-r--r--drivers/watchdog/pnx4008_wdt.c261
-rw-r--r--drivers/watchdog/pretimeout_noop.c42
-rw-r--r--drivers/watchdog/pretimeout_panic.c42
-rw-r--r--drivers/watchdog/pseries-wdt.c239
-rw-r--r--drivers/watchdog/qcom-wdt.c355
-rw-r--r--drivers/watchdog/rave-sp-wdt.c336
-rw-r--r--drivers/watchdog/rc32434_wdt.c326
-rw-r--r--drivers/watchdog/rdc321x_wdt.c284
-rw-r--r--drivers/watchdog/realtek_otto_wdt.c385
-rw-r--r--drivers/watchdog/renesas_wdt.c348
-rw-r--r--drivers/watchdog/retu_wdt.c160
-rw-r--r--drivers/watchdog/riowd.c247
-rw-r--r--drivers/watchdog/rn5t618_wdt.c194
-rw-r--r--drivers/watchdog/rt2880_wdt.c192
-rw-r--r--drivers/watchdog/rtd119x_wdt.c153
-rw-r--r--drivers/watchdog/rti_wdt.c356
-rw-r--r--drivers/watchdog/rza_wdt.c247
-rw-r--r--drivers/watchdog/rzg2l_wdt.c334
-rw-r--r--drivers/watchdog/rzn1_wdt.c203
-rw-r--r--drivers/watchdog/s3c2410_wdt.c939
-rw-r--r--drivers/watchdog/sa1100_wdt.c254
-rw-r--r--drivers/watchdog/sama5d4_wdt.c386
-rw-r--r--drivers/watchdog/sb_wdog.c363
-rw-r--r--drivers/watchdog/sbc60xxwdt.c384
-rw-r--r--drivers/watchdog/sbc7240_wdt.c302
-rw-r--r--drivers/watchdog/sbc8360.c404
-rw-r--r--drivers/watchdog/sbc_epx_c3.c219
-rw-r--r--drivers/watchdog/sbc_fitpc2_wdt.c266
-rw-r--r--drivers/watchdog/sbsa_gwdt.c414
-rw-r--r--drivers/watchdog/sc1200wdt.c475
-rw-r--r--drivers/watchdog/sc520_wdt.c431
-rw-r--r--drivers/watchdog/sch311x_wdt.c542
-rw-r--r--drivers/watchdog/scx200_wdt.c263
-rw-r--r--drivers/watchdog/shwdt.c344
-rw-r--r--drivers/watchdog/simatic-ipc-wdt.c229
-rw-r--r--drivers/watchdog/sl28cpld_wdt.c229
-rw-r--r--drivers/watchdog/smsc37b787_wdt.c615
-rw-r--r--drivers/watchdog/softdog.c224
-rw-r--r--drivers/watchdog/sp5100_tco.c635
-rw-r--r--drivers/watchdog/sp5100_tco.h92
-rw-r--r--drivers/watchdog/sp805_wdt.c368
-rw-r--r--drivers/watchdog/sprd_wdt.c381
-rw-r--r--drivers/watchdog/st_lpc_wdt.c304
-rw-r--r--drivers/watchdog/stm32_iwdg.c298
-rw-r--r--drivers/watchdog/stmp3xxx_rtc_wdt.c153
-rw-r--r--drivers/watchdog/stpmic1_wdt.c140
-rw-r--r--drivers/watchdog/sun4v_wdt.c187
-rw-r--r--drivers/watchdog/sunplus_wdt.c220
-rw-r--r--drivers/watchdog/sunxi_wdt.c313
-rw-r--r--drivers/watchdog/tegra_wdt.c274
-rw-r--r--drivers/watchdog/tqmx86_wdt.c126
-rw-r--r--drivers/watchdog/ts4800_wdt.c206
-rw-r--r--drivers/watchdog/ts72xx_wdt.c177
-rw-r--r--drivers/watchdog/twl4030_wdt.c128
-rw-r--r--drivers/watchdog/txx9wdt.c173
-rw-r--r--drivers/watchdog/uniphier_wdt.c257
-rw-r--r--drivers/watchdog/via_wdt.c261
-rw-r--r--drivers/watchdog/visconti_wdt.c195
-rw-r--r--drivers/watchdog/w83627hf_wdt.c549
-rw-r--r--drivers/watchdog/w83877f_wdt.c405
-rw-r--r--drivers/watchdog/w83977f_wdt.c525
-rw-r--r--drivers/watchdog/wafer5823wdt.c323
-rw-r--r--drivers/watchdog/watchdog_core.c485
-rw-r--r--drivers/watchdog/watchdog_core.h81
-rw-r--r--drivers/watchdog/watchdog_dev.c1306
-rw-r--r--drivers/watchdog/watchdog_hrtimer_pretimeout.c44
-rw-r--r--drivers/watchdog/watchdog_pretimeout.c216
-rw-r--r--drivers/watchdog/watchdog_pretimeout.h61
-rw-r--r--drivers/watchdog/wd501p.h47
-rw-r--r--drivers/watchdog/wdat_wdt.c547
-rw-r--r--drivers/watchdog/wdrtas.c628
-rw-r--r--drivers/watchdog/wdt.c664
-rw-r--r--drivers/watchdog/wdt285.c227
-rw-r--r--drivers/watchdog/wdt977.c506
-rw-r--r--drivers/watchdog/wdt_pci.c742
-rw-r--r--drivers/watchdog/wm831x_wdt.c258
-rw-r--r--drivers/watchdog/wm8350_wdt.c178
-rw-r--r--drivers/watchdog/xen_wdt.c208
-rw-r--r--drivers/watchdog/ziirave_wdt.c744
183 files changed, 61687 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
new file mode 100644
index 000000000..b64bc49c7
--- /dev/null
+++ b/drivers/watchdog/Kconfig
@@ -0,0 +1,2207 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+#
+# Watchdog device configuration
+#
+
+menuconfig WATCHDOG
+ bool "Watchdog Timer Support"
+ help
+ If you say Y here (and to one of the following options) and create a
+ character special file /dev/watchdog with major number 10 and minor
+ number 130 using mknod ("man mknod"), you will get a watchdog, i.e.:
+ subsequently opening the file and then failing to write to it for
+ longer than 1 minute will result in rebooting the machine. This
+ could be useful for a networked machine that needs to come back
+ on-line as fast as possible after a lock-up. There's both a watchdog
+ implementation entirely in software (which can sometimes fail to
+ reboot the machine) and a driver for hardware watchdog boards, which
+ are more robust and can also keep track of the temperature inside
+ your computer. For details, read
+ <file:Documentation/watchdog/watchdog-api.rst> in the kernel source.
+
+ The watchdog is usually used together with the watchdog daemon
+ which is available from
+ <https://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon
+ can also monitor NFS connections and can reboot the machine when the
+ process table is full.
+
+ If unsure, say N.
+
+if WATCHDOG
+
+config WATCHDOG_CORE
+ tristate "WatchDog Timer Driver Core"
+ help
+ Say Y here if you want to use the new watchdog timer driver core.
+ This driver provides a framework for all watchdog timer drivers
+ and gives them the /dev/watchdog interface (and later also the
+ sysfs interface).
+
+config WATCHDOG_NOWAYOUT
+ bool "Disable watchdog shutdown on close"
+ help
+ The default watchdog behaviour (which you get if you say N here) is
+ to stop the timer if the process managing it closes the file
+ /dev/watchdog. It's always remotely possible that this process might
+ get killed. If you say Y here, the watchdog cannot be stopped once
+ it has been started.
+
+config WATCHDOG_HANDLE_BOOT_ENABLED
+ bool "Update boot-enabled watchdog until userspace takes over"
+ default y
+ help
+ The default watchdog behaviour (which you get if you say Y here) is
+ to ping watchdog devices that were enabled before the driver has
+ been loaded until control is taken over from userspace using the
+ /dev/watchdog file. If you say N here, the kernel will not update
+ the watchdog on its own. Thus if your userspace does not start fast
+ enough your device will reboot.
+
+config WATCHDOG_OPEN_TIMEOUT
+ int "Timeout value for opening watchdog device"
+ default 0
+ help
+ The maximum time, in seconds, for which the watchdog framework takes
+ care of pinging a hardware watchdog. A value of 0 means infinite. The
+ value set here can be overridden by the commandline parameter
+ "watchdog.open_timeout".
+
+config WATCHDOG_SYSFS
+ bool "Read different watchdog information through sysfs"
+ help
+ Say Y here if you want to enable watchdog device status read through
+ sysfs attributes.
+
+config WATCHDOG_HRTIMER_PRETIMEOUT
+ bool "Enable watchdog hrtimer-based pretimeouts"
+ help
+ Enable this if you want to use a hrtimer timer based pretimeout for
+ watchdogs that do not natively support pretimeout support. Be aware
+ that because this pretimeout functionality uses hrtimers, it may not
+ be able to fire before the actual watchdog fires in some situations.
+
+comment "Watchdog Pretimeout Governors"
+
+config WATCHDOG_PRETIMEOUT_GOV
+ bool "Enable watchdog pretimeout governors"
+ depends on WATCHDOG_CORE
+ help
+ The option allows to select watchdog pretimeout governors.
+
+config WATCHDOG_PRETIMEOUT_GOV_SEL
+ tristate
+ depends on WATCHDOG_PRETIMEOUT_GOV
+ default m
+ select WATCHDOG_PRETIMEOUT_GOV_PANIC if WATCHDOG_PRETIMEOUT_GOV_NOOP=n
+
+if WATCHDOG_PRETIMEOUT_GOV
+
+config WATCHDOG_PRETIMEOUT_GOV_NOOP
+ tristate "Noop watchdog pretimeout governor"
+ depends on WATCHDOG_CORE
+ default WATCHDOG_CORE
+ help
+ Noop watchdog pretimeout governor, only an informational
+ message is added to kernel log buffer.
+
+config WATCHDOG_PRETIMEOUT_GOV_PANIC
+ tristate "Panic watchdog pretimeout governor"
+ depends on WATCHDOG_CORE
+ default WATCHDOG_CORE
+ help
+ Panic watchdog pretimeout governor, on watchdog pretimeout
+ event put the kernel into panic.
+
+choice
+ prompt "Default Watchdog Pretimeout Governor"
+ default WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC
+ help
+ This option selects a default watchdog pretimeout governor.
+ The governor takes its action, if a watchdog is capable
+ to report a pretimeout event.
+
+config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP
+ bool "noop"
+ depends on WATCHDOG_PRETIMEOUT_GOV_NOOP
+ help
+ Use noop watchdog pretimeout governor by default. If noop
+ governor is selected by a user, write a short message to
+ the kernel log buffer and don't do any system changes.
+
+config WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC
+ bool "panic"
+ depends on WATCHDOG_PRETIMEOUT_GOV_PANIC
+ help
+ Use panic watchdog pretimeout governor by default, if
+ a watchdog pretimeout event happens, consider that
+ a watchdog feeder is dead and reboot is unavoidable.
+
+endchoice
+
+endif # WATCHDOG_PRETIMEOUT_GOV
+
+#
+# General Watchdog drivers
+#
+
+comment "Watchdog Device Drivers"
+
+# Architecture Independent
+
+config SOFT_WATCHDOG
+ tristate "Software watchdog"
+ select WATCHDOG_CORE
+ help
+ A software monitoring watchdog. This will fail to reboot your system
+ from some situations that the hardware watchdog will recover
+ from. Equally it's a lot cheaper to install.
+
+ To compile this driver as a module, choose M here: the
+ module will be called softdog.
+
+config SOFT_WATCHDOG_PRETIMEOUT
+ bool "Software watchdog pretimeout governor support"
+ depends on SOFT_WATCHDOG && WATCHDOG_PRETIMEOUT_GOV
+ help
+ Enable this if you want to use pretimeout governors with the software
+ watchdog. Be aware that governors might affect the watchdog because it
+ is purely software, e.g. the panic governor will stall it!
+
+config BD957XMUF_WATCHDOG
+ tristate "ROHM BD9576MUF and BD9573MUF PMIC Watchdog"
+ depends on MFD_ROHM_BD957XMUF
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the ROHM BD9576 and BD9573 PMICs.
+ These PMIC ICs contain watchdog block which can be configured
+ to toggle reset line if SoC fails to ping watchdog via GPIO.
+
+ Say Y here to include support for the ROHM BD9576 or BD9573
+ watchdog. Alternatively say M to compile the driver as a module,
+ which will be called bd9576_wdt.
+
+config DA9052_WATCHDOG
+ tristate "Dialog DA9052 Watchdog"
+ depends on PMIC_DA9052 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the DA9052 PMIC. Watchdog trigger
+ cause system reset.
+
+ Say Y here to include support for the DA9052 watchdog.
+ Alternatively say M to compile the driver as a module,
+ which will be called da9052_wdt.
+
+config DA9055_WATCHDOG
+ tristate "Dialog Semiconductor DA9055 Watchdog"
+ depends on MFD_DA9055 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ If you say yes here you get support for watchdog on the Dialog
+ Semiconductor DA9055 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called da9055_wdt.
+
+config DA9063_WATCHDOG
+ tristate "Dialog DA9063 Watchdog"
+ depends on MFD_DA9063 || COMPILE_TEST
+ depends on I2C
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the DA9063 PMIC.
+
+ This driver can be built as a module. The module name is da9063_wdt.
+
+config DA9062_WATCHDOG
+ tristate "Dialog DA9062/61 Watchdog"
+ depends on MFD_DA9062 || COMPILE_TEST
+ depends on I2C
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the DA9062 and DA9061 PMICs.
+
+ This driver can be built as a module. The module name is da9062_wdt.
+
+config GPIO_WATCHDOG
+ tristate "Watchdog device controlled through GPIO-line"
+ depends on OF_GPIO
+ select WATCHDOG_CORE
+ help
+ If you say yes here you get support for watchdog device
+ controlled through GPIO-line.
+
+config GPIO_WATCHDOG_ARCH_INITCALL
+ bool "Register the watchdog as early as possible"
+ depends on GPIO_WATCHDOG=y
+ help
+ In some situations, the default initcall level (module_init)
+ in not early enough in the boot process to avoid the watchdog
+ to be triggered.
+ If you say yes here, the initcall level would be raised to
+ arch_initcall.
+ If in doubt, say N.
+
+config MENF21BMC_WATCHDOG
+ tristate "MEN 14F021P00 BMC Watchdog"
+ depends on MFD_MENF21BMC || COMPILE_TEST
+ depends on I2C
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the MEN 14F021P00 BMC Watchdog.
+
+ This driver can also be built as a module. If so the module
+ will be called menf21bmc_wdt.
+
+config MENZ069_WATCHDOG
+ tristate "MEN 16Z069 Watchdog"
+ depends on MCB
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the MEN 16Z069 Watchdog.
+
+ This driver can also be built as a module. If so the module
+ will be called menz069_wdt.
+
+config WDAT_WDT
+ tristate "ACPI Watchdog Action Table (WDAT)"
+ depends on ACPI
+ select WATCHDOG_CORE
+ select ACPI_WATCHDOG
+ help
+ This driver adds support for systems with ACPI Watchdog Action
+ Table (WDAT) table. Servers typically have this but it can be
+ found on some desktop machines as well. This driver will take
+ over the native iTCO watchdog driver found on many Intel CPUs.
+
+ To compile this driver as module, choose M here: the module will
+ be called wdat_wdt.
+
+config WM831X_WATCHDOG
+ tristate "WM831x watchdog"
+ depends on MFD_WM831X
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the WM831x AudioPlus PMICs. When
+ the watchdog triggers the system will be reset.
+
+config WM8350_WATCHDOG
+ tristate "WM8350 watchdog"
+ depends on MFD_WM8350
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the WM8350 AudioPlus PMIC. When
+ the watchdog triggers the system will be reset.
+
+config XILINX_WATCHDOG
+ tristate "Xilinx Watchdog timer"
+ depends on HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Watchdog driver for the xps_timebase_wdt IP core.
+
+ To compile this driver as a module, choose M here: the
+ module will be called of_xilinx_wdt.
+
+config ZIIRAVE_WATCHDOG
+ tristate "Zodiac RAVE Watchdog Timer"
+ depends on I2C
+ select WATCHDOG_CORE
+ help
+ Watchdog driver for the Zodiac Aerospace RAVE Switch Watchdog
+ Processor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ziirave_wdt.
+
+config RAVE_SP_WATCHDOG
+ tristate "RAVE SP Watchdog timer"
+ depends on RAVE_SP_CORE
+ depends on NVMEM || !NVMEM
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog on RAVE SP device.
+
+config MLX_WDT
+ tristate "Mellanox Watchdog"
+ depends on MELLANOX_PLATFORM
+ select WATCHDOG_CORE
+ select REGMAP
+ help
+ This is the driver for the hardware watchdog on Mellanox systems.
+ If you are going to use it, say Y here, otherwise N.
+ This driver can be used together with the watchdog daemon.
+ It can also watch your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your system after a certain amount of
+ time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mlx-wdt.
+
+config SL28CPLD_WATCHDOG
+ tristate "Kontron sl28cpld Watchdog"
+ depends on MFD_SL28CPLD || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ on the Kontron sl28 CPLD.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sl28cpld_wdt.
+
+# ALPHA Architecture
+
+# ARM Architecture
+
+config ARM_SP805_WATCHDOG
+ tristate "ARM SP805 Watchdog"
+ depends on (ARM || ARM64 || COMPILE_TEST) && ARM_AMBA
+ select WATCHDOG_CORE
+ help
+ ARM Primecell SP805 Watchdog timer. This will reboot your system when
+ the timeout is reached.
+
+config ARM_SBSA_WATCHDOG
+ tristate "ARM SBSA Generic Watchdog"
+ depends on ARM64
+ depends on ARM_ARCH_TIMER
+ select WATCHDOG_CORE
+ help
+ ARM SBSA Generic Watchdog has two stage timeouts:
+ the first signal (WS0) is for alerting the system by interrupt,
+ the second one (WS1) is a real hardware reset.
+ More details: ARM DEN0029B - Server Base System Architecture (SBSA)
+
+ This driver can operate ARM SBSA Generic Watchdog as a single stage
+ or a two stages watchdog, it depends on the module parameter "action".
+
+ Note: the maximum timeout in the two stages mode is half of that in
+ the single stage mode.
+
+ To compile this driver as module, choose M here: The module
+ will be called sbsa_gwdt.
+
+config ARMADA_37XX_WATCHDOG
+ tristate "Armada 37xx watchdog"
+ depends on ARCH_MVEBU || COMPILE_TEST
+ depends on HAS_IOMEM
+ select MFD_SYSCON
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer found on
+ Marvell Armada 37xx SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called armada_37xx_wdt.
+
+config ASM9260_WATCHDOG
+ tristate "Alphascale ASM9260 watchdog"
+ depends on MACH_ASM9260 || COMPILE_TEST
+ depends on OF
+ select WATCHDOG_CORE
+ select RESET_CONTROLLER
+ help
+ Watchdog timer embedded into Alphascale asm9260 chips. This will
+ reboot your system when the timeout is reached.
+
+config AT91RM9200_WATCHDOG
+ tristate "AT91RM9200 watchdog"
+ depends on (SOC_AT91RM9200 && MFD_SYSCON) || COMPILE_TEST
+ help
+ Watchdog timer embedded into AT91RM9200 chips. This will reboot your
+ system when the timeout is reached.
+
+config AT91SAM9X_WATCHDOG
+ tristate "AT91SAM9X / AT91CAP9 watchdog"
+ depends on ARCH_AT91 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Watchdog timer embedded into AT91SAM9X and AT91CAP9 chips. This will
+ reboot your system when the timeout is reached.
+
+config SAMA5D4_WATCHDOG
+ tristate "Atmel SAMA5D4 Watchdog Timer"
+ depends on ARCH_AT91 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Atmel SAMA5D4 watchdog timer is embedded into SAMA5D4 chips.
+ Its Watchdog Timer Mode Register can be written more than once.
+ This will reboot your system when the timeout is reached.
+
+config CADENCE_WATCHDOG
+ tristate "Cadence Watchdog Timer"
+ depends on HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Say Y here if you want to include support for the watchdog
+ timer in the Xilinx Zynq.
+
+config 21285_WATCHDOG
+ tristate "DC21285 watchdog"
+ depends on FOOTBRIDGE
+ help
+ The Intel Footbridge chip contains a built-in watchdog circuit. Say Y
+ here if you wish to use this. Alternatively say M to compile the
+ driver as a module, which will be called wdt285.
+
+ This driver does not work on all machines. In particular, early CATS
+ boards have hardware problems that will cause the machine to simply
+ lock up if the watchdog fires.
+
+ "If in doubt, leave it out" - say N.
+
+config 977_WATCHDOG
+ tristate "NetWinder WB83C977 watchdog"
+ depends on (FOOTBRIDGE && ARCH_NETWINDER) || (ARM && COMPILE_TEST)
+ help
+ Say Y here to include support for the WB977 watchdog included in
+ NetWinder machines. Alternatively say M to compile the driver as
+ a module, which will be called wdt977.
+
+ Not sure? It's safe to say N.
+
+config FTWDT010_WATCHDOG
+ tristate "Faraday Technology FTWDT010 watchdog"
+ depends on ARM || COMPILE_TEST
+ select WATCHDOG_CORE
+ default ARCH_GEMINI
+ help
+ Say Y here if to include support for the Faraday Technology
+ FTWDT010 watchdog timer embedded in the Cortina Systems Gemini
+ family of devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ftwdt010_wdt.
+
+config IXP4XX_WATCHDOG
+ tristate "IXP4xx Watchdog"
+ depends on ARCH_IXP4XX
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ in the Intel IXP4xx network processors. This driver can
+ be built as a module by choosing M. The module will
+ be called ixp4xx_wdt.
+
+ Note: The internal IXP4xx watchdog does a soft CPU reset
+ which doesn't reset any peripherals. There are circumstances
+ where the watchdog will fail to reset the board correctly
+ (e.g., if the boot ROM is in an unreadable state).
+
+ Say N if you are unsure.
+
+config S3C2410_WATCHDOG
+ tristate "S3C2410 Watchdog"
+ depends on ARCH_S3C24XX || ARCH_S3C64XX || ARCH_S5PV210 || ARCH_EXYNOS || \
+ COMPILE_TEST
+ select WATCHDOG_CORE
+ select MFD_SYSCON if ARCH_EXYNOS
+ help
+ Watchdog timer block in the Samsung S3C24xx, S3C64xx, S5Pv210 and
+ Exynos SoCs. This will reboot the system when the timer expires with
+ the watchdog enabled.
+
+ The driver is limited by the speed of the system's PCLK
+ signal, so with reasonably fast systems (PCLK around 50-66MHz)
+ then watchdog intervals of over approximately 20seconds are
+ unavailable.
+
+ Choose Y/M here only if you build for such Samsung SoC.
+ The driver can be built as a module by choosing M, and will
+ be called s3c2410_wdt.
+
+config SA1100_WATCHDOG
+ tristate "SA1100/PXA2xx watchdog"
+ depends on ARCH_SA1100 || ARCH_PXA
+ help
+ Watchdog timer embedded into SA11x0 and PXA2xx chips. This will
+ reboot your system when timeout is reached.
+
+ NOTE: once enabled, this timer cannot be disabled.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sa1100_wdt.
+
+config DW_WATCHDOG
+ tristate "Synopsys DesignWare watchdog"
+ depends on HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the Synopsys DesignWare
+ watchdog timer found in many chips.
+ To compile this driver as a module, choose M here: the
+ module will be called dw_wdt.
+
+config EP93XX_WATCHDOG
+ tristate "EP93xx Watchdog"
+ depends on ARCH_EP93XX || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ embedded in the Cirrus Logic EP93xx family of devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ep93xx_wdt.
+
+config OMAP_WATCHDOG
+ tristate "OMAP Watchdog"
+ depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Support for TI OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog.
+ Say 'Y' here to enable the
+ OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog timer.
+
+config PNX4008_WATCHDOG
+ tristate "LPC32XX Watchdog"
+ depends on ARCH_LPC32XX || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ in the LPC32XX processor.
+ This driver can be built as a module by choosing M. The module
+ will be called pnx4008_wdt.
+
+ Say N if you are unsure.
+
+config DAVINCI_WATCHDOG
+ tristate "DaVinci watchdog"
+ depends on ARCH_DAVINCI || ARCH_KEYSTONE || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ in the DaVinci DM644x/DM646x or Keystone processors.
+ To compile this driver as a module, choose M here: the
+ module will be called davinci_wdt.
+
+ NOTE: once enabled, this timer cannot be disabled.
+ Say N if you are unsure.
+
+config K3_RTI_WATCHDOG
+ tristate "Texas Instruments K3 RTI watchdog"
+ depends on ARCH_K3 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here if you want to include support for the K3 watchdog
+ timer (RTI module) available in the K3 generation of processors.
+
+config ORION_WATCHDOG
+ tristate "Orion watchdog"
+ depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU || COMPILE_TEST
+ depends on ARM
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ in the Marvell Orion5x and Kirkwood ARM SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called orion_wdt.
+
+config RN5T618_WATCHDOG
+ tristate "Ricoh RN5T618 watchdog"
+ depends on MFD_RN5T618 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ If you say yes here you get support for watchdog on the Ricoh
+ RN5T618 PMIC.
+
+ This driver can also be built as a module. If so, the module
+ will be called rn5t618_wdt.
+
+config SUNXI_WATCHDOG
+ tristate "Allwinner SoCs watchdog support"
+ depends on ARCH_SUNXI || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Allwinner SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called sunxi_wdt.
+
+config NPCM7XX_WATCHDOG
+ tristate "Nuvoton NPCM750 watchdog"
+ depends on ARCH_NPCM || COMPILE_TEST
+ default y if ARCH_NPCM7XX
+ select WATCHDOG_CORE
+ help
+ Say Y here to include Watchdog timer support for the
+ watchdog embedded into the NPCM7xx.
+ This watchdog is used to reset the system and thus cannot be
+ compiled as a module.
+
+config TWL4030_WATCHDOG
+ tristate "TWL4030 Watchdog"
+ depends on TWL4030_CORE
+ select WATCHDOG_CORE
+ help
+ Support for TI TWL4030 watchdog. Say 'Y' here to enable the
+ watchdog timer support for TWL4030 chips.
+
+config STMP3XXX_RTC_WATCHDOG
+ tristate "Freescale STMP3XXX & i.MX23/28 watchdog"
+ depends on RTC_DRV_STMP || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer inside
+ the RTC for the STMP37XX/378X or i.MX23/28 SoC.
+ To compile this driver as a module, choose M here: the
+ module will be called stmp3xxx_rtc_wdt.
+
+config TS4800_WATCHDOG
+ tristate "TS-4800 Watchdog"
+ depends on HAS_IOMEM && OF
+ depends on SOC_IMX51 || COMPILE_TEST
+ select WATCHDOG_CORE
+ select MFD_SYSCON
+ help
+ Technologic Systems TS-4800 has watchdog timer implemented in
+ an external FPGA. Say Y here if you want to support for the
+ watchdog timer on TS-4800 board.
+
+config TS72XX_WATCHDOG
+ tristate "TS-72XX SBC Watchdog"
+ depends on MACH_TS72XX || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Technologic Systems TS-7200, TS-7250 and TS-7260 boards have
+ watchdog timer implemented in a external CPLD chip. Say Y here
+ if you want to support for the watchdog timer on TS-72XX boards.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ts72xx_wdt.
+
+config MAX63XX_WATCHDOG
+ tristate "Max63xx watchdog"
+ depends on HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Support for memory mapped max63{69,70,71,72,73,74} watchdog timer.
+
+config MAX77620_WATCHDOG
+ tristate "Maxim Max77620 Watchdog Timer"
+ depends on MFD_MAX77620 || MFD_MAX77714 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This is the driver for the Max77620 watchdog timer.
+ Say 'Y' here to enable the watchdog timer support for
+ MAX77620 chips. To compile this driver as a module,
+ choose M here: the module will be called max77620_wdt.
+
+config IMX2_WDT
+ tristate "IMX2+ Watchdog"
+ depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST
+ select REGMAP_MMIO
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog
+ on the Freescale IMX2 and later processors.
+ If you have one of these processors and wish to have
+ watchdog support enabled, say Y, otherwise say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx2_wdt.
+
+config IMX_SC_WDT
+ tristate "IMX SC Watchdog"
+ depends on HAVE_ARM_SMCCC
+ depends on IMX_SCU
+ select WATCHDOG_CORE
+ help
+ This is the driver for the system controller watchdog
+ on the NXP i.MX SoCs with system controller inside, the
+ watchdog driver will call ARM SMC API and trap into
+ ARM-Trusted-Firmware for operations, ARM-Trusted-Firmware
+ will request system controller to execute the operations.
+ If you have one of these processors and wish to have
+ watchdog support enabled, say Y, otherwise say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx_sc_wdt.
+
+config IMX7ULP_WDT
+ tristate "IMX7ULP Watchdog"
+ depends on ARCH_MXC || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog on the Freescale
+ IMX7ULP and later processors. If you have one of these
+ processors and wish to have watchdog support enabled,
+ say Y, otherwise say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called imx7ulp_wdt.
+
+config DB500_WATCHDOG
+ tristate "ST-Ericsson DB800 watchdog"
+ depends on MFD_DB8500_PRCMU
+ select WATCHDOG_CORE
+ default y
+ help
+ Say Y here to include Watchdog timer support for the watchdog
+ existing in the prcmu of ST-Ericsson DB8500 platform.
+
+ To compile this driver as a module, choose M here: the
+ module will be called db500_wdt.
+
+config RETU_WATCHDOG
+ tristate "Retu watchdog"
+ depends on MFD_RETU
+ select WATCHDOG_CORE
+ help
+ Retu watchdog driver for Nokia Internet Tablets (770, N800,
+ N810). At least on N800 the watchdog cannot be disabled, so
+ this driver is essential and you should enable it.
+
+ To compile this driver as a module, choose M here: the
+ module will be called retu_wdt.
+
+config MOXART_WDT
+ tristate "MOXART watchdog"
+ depends on ARCH_MOXART || COMPILE_TEST
+ help
+ Say Y here to include Watchdog timer support for the watchdog
+ existing on the MOXA ART SoC series platforms.
+
+ To compile this driver as a module, choose M here: the
+ module will be called moxart_wdt.
+
+config ST_LPC_WATCHDOG
+ tristate "STMicroelectronics LPC Watchdog"
+ depends on ARCH_STI || COMPILE_TEST
+ depends on OF
+ select WATCHDOG_CORE
+ help
+ Say Y here to include STMicroelectronics Low Power Controller
+ (LPC) based Watchdog timer support.
+
+ To compile this driver as a module, choose M here: the
+ module will be called st_lpc_wdt.
+
+config TEGRA_WATCHDOG
+ tristate "Tegra watchdog"
+ depends on (ARCH_TEGRA || COMPILE_TEST) && HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ embedded in NVIDIA Tegra SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called tegra_wdt.
+
+config QCOM_WDT
+ tristate "QCOM watchdog"
+ depends on HAS_IOMEM
+ depends on ARCH_QCOM || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include Watchdog timer support for the watchdog found
+ on QCOM chipsets. Currently supported targets are the MSM8960,
+ APQ8064, and IPQ8064.
+
+ To compile this driver as a module, choose M here: the
+ module will be called qcom_wdt.
+
+config MESON_GXBB_WATCHDOG
+ tristate "Amlogic Meson GXBB SoCs watchdog support"
+ depends on ARCH_MESON || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Amlogic Meson GXBB SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called meson_gxbb_wdt.
+
+config MESON_WATCHDOG
+ tristate "Amlogic Meson SoCs watchdog support"
+ depends on ARCH_MESON || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Amlogic Meson SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called meson_wdt.
+
+config MEDIATEK_WATCHDOG
+ tristate "Mediatek SoCs watchdog support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ default ARCH_MEDIATEK
+ select WATCHDOG_CORE
+ select RESET_CONTROLLER
+ help
+ Say Y here to include support for the watchdog timer
+ in Mediatek SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called mtk_wdt.
+
+config DIGICOLOR_WATCHDOG
+ tristate "Conexant Digicolor SoCs watchdog support"
+ depends on ARCH_DIGICOLOR || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Conexant Digicolor SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called digicolor_wdt.
+
+config ARM_SMC_WATCHDOG
+ tristate "ARM Secure Monitor Call based watchdog support"
+ depends on ARM || ARM64
+ depends on OF
+ depends on HAVE_ARM_SMCCC
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for a watchdog timer
+ implemented by the EL3 Secure Monitor on ARM platforms.
+ Requires firmware support.
+ To compile this driver as a module, choose M here: the
+ module will be called arm_smc_wdt.
+
+config LPC18XX_WATCHDOG
+ tristate "LPC18xx/43xx Watchdog"
+ depends on ARCH_LPC18XX || COMPILE_TEST
+ depends on HAS_IOMEM
+ select WATCHDOG_CORE
+ help
+ Say Y here if to include support for the watchdog timer
+ in NXP LPC SoCs family, which includes LPC18xx/LPC43xx
+ processors.
+ To compile this driver as a module, choose M here: the
+ module will be called lpc18xx_wdt.
+
+config RENESAS_WDT
+ tristate "Renesas WDT Watchdog"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdogs in the
+ Renesas R-Car and other SH-Mobile SoCs (usually named RWDT or SWDT).
+
+config RENESAS_RZAWDT
+ tristate "Renesas RZ/A WDT Watchdog"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdogs in the
+ Renesas RZ/A SoCs. These watchdogs can be used to reset a system.
+
+config RENESAS_RZN1WDT
+ tristate "Renesas RZ/N1 watchdog"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdogs in the
+ Renesas RZ/N1 SoCs. These watchdogs can be used to reset a system.
+
+config RENESAS_RZG2LWDT
+ tristate "Renesas RZ/G2L WDT Watchdog"
+ depends on ARCH_RENESAS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdogs in the
+ Renesas RZ/G2L SoCs. These watchdogs can be used to reset a system.
+
+config ASPEED_WATCHDOG
+ tristate "Aspeed BMC watchdog support"
+ depends on ARCH_ASPEED || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Aspeed BMC SoCs.
+
+ This driver is required to reboot the SoC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called aspeed_wdt.
+
+config STM32_WATCHDOG
+ tristate "STM32 Independent WatchDoG (IWDG) support"
+ depends on ARCH_STM32
+ select WATCHDOG_CORE
+ default y
+ help
+ Say Y here to include support for the watchdog timer
+ in stm32 SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stm32_iwdg.
+
+config STPMIC1_WATCHDOG
+ tristate "STPMIC1 PMIC watchdog support"
+ depends on MFD_STPMIC1
+ select WATCHDOG_CORE
+ help
+ Say Y here to include watchdog support embedded into STPMIC1 PMIC.
+ If the watchdog timer expires, stpmic1 will shut down all its power
+ supplies.
+
+ To compile this driver as a module, choose M here: the
+ module will be called spmic1_wdt.
+
+config UNIPHIER_WATCHDOG
+ tristate "UniPhier watchdog support"
+ depends on ARCH_UNIPHIER || COMPILE_TEST
+ depends on OF && MFD_SYSCON
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support watchdog timer embedded
+ into the UniPhier system.
+
+ To compile this driver as a module, choose M here: the
+ module will be called uniphier_wdt.
+
+config RTD119X_WATCHDOG
+ bool "Realtek RTD119x/RTD129x watchdog support"
+ depends on ARCH_REALTEK || COMPILE_TEST
+ depends on OF
+ select WATCHDOG_CORE
+ default ARCH_REALTEK
+ help
+ Say Y here to include support for the watchdog timer in
+ Realtek RTD1295 SoCs.
+
+config REALTEK_OTTO_WDT
+ tristate "Realtek Otto MIPS watchdog support"
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ depends on COMMON_CLK
+ select WATCHDOG_CORE
+ default MACH_REALTEK_RTL
+ help
+ Say Y here to include support for the watchdog timer on Realtek
+ RTL838x, RTL839x, RTL930x SoCs. This watchdog has pretimeout
+ notifications and system reset on timeout.
+
+ When built as a module this will be called realtek_otto_wdt.
+
+config SPRD_WATCHDOG
+ tristate "Spreadtrum watchdog support"
+ depends on ARCH_SPRD || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include watchdog timer supported
+ by Spreadtrum system.
+
+config PM8916_WATCHDOG
+ tristate "QCOM PM8916 pmic watchdog"
+ depends on OF && MFD_SPMI_PMIC
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support watchdog timer embedded into the
+ pm8916 module.
+
+config VISCONTI_WATCHDOG
+ tristate "Toshiba Visconti series watchdog support"
+ depends on ARCH_VISCONTI || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer in Toshiba
+ Visconti SoCs.
+
+config MSC313E_WATCHDOG
+ tristate "MStar MSC313e watchdog"
+ depends on ARCH_MSTARV7 || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the Watchdog timer embedded
+ into MStar MSC313e chips. This will reboot your system when the
+ timeout is reached.
+
+ To compile this driver as a module, choose M here: the
+ module will be called msc313e_wdt.
+
+config APPLE_WATCHDOG
+ tristate "Apple SoC watchdog"
+ depends on ARCH_APPLE || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the Watchdog found in Apple
+ SoCs such as the M1. Next to the common watchdog features this
+ driver is also required in order to reboot these SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called apple_wdt.
+
+config SUNPLUS_WATCHDOG
+ tristate "Sunplus watchdog support"
+ depends on ARCH_SUNPLUS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in Sunplus SoCs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sunplus_wdt.
+
+# X86 (i386 + ia64 + x86_64) Architecture
+
+config ACQUIRE_WDT
+ tristate "Acquire SBC Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog on Single Board
+ Computers produced by Acquire Inc (and others). 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 acquirewdt.
+
+ Most people will say N.
+
+config ADVANTECH_WDT
+ tristate "Advantech SBC Watchdog Timer"
+ depends on X86
+ help
+ If you are configuring a Linux kernel for the Advantech single-board
+ computer, say `Y' here to support its built-in watchdog timer
+ feature. More information can be found at
+ <https://www.advantech.com.tw/products/>
+
+config ALIM1535_WDT
+ tristate "ALi M1535 PMU Watchdog Timer"
+ depends on X86 && PCI
+ help
+ This is the driver for the hardware watchdog on the ALi M1535 PMU.
+
+ To compile this driver as a module, choose M here: the
+ module will be called alim1535_wdt.
+
+ Most people will say N.
+
+config ALIM7101_WDT
+ tristate "ALi M7101 PMU Computer Watchdog"
+ depends on PCI
+ help
+ This is the driver for the hardware watchdog on the ALi M7101 PMU
+ as used in the x86 Cobalt servers and also found in some
+ SPARC Netra servers too.
+
+ To compile this driver as a module, choose M here: the
+ module will be called alim7101_wdt.
+
+ Most people will say N.
+
+config EBC_C384_WDT
+ tristate "WinSystems EBC-C384 Watchdog Timer"
+ depends on X86
+ select ISA_BUS_API
+ select WATCHDOG_CORE
+ help
+ Enables watchdog timer support for the watchdog timer on the
+ WinSystems EBC-C384 motherboard. The timeout may be configured via
+ the timeout module parameter.
+
+config EXAR_WDT
+ tristate "Exar Watchdog Timer"
+ depends on X86
+ select WATCHDOG_CORE
+ help
+ Enables watchdog timer support for the watchdog timer present
+ in some Exar/MaxLinear UART chips like the XR28V38x.
+
+ To compile this driver as a module, choose M here: the
+ module will be called exar_wdt.
+
+config F71808E_WDT
+ tristate "Fintek F718xx, F818xx Super I/O Watchdog"
+ depends on X86
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog on the Fintek F71808E,
+ F71862FG, F71868, F71869, F71882FG, F71889FG, F81803, F81865, and
+ F81866 Super I/O controllers.
+
+ You can compile this driver directly into the kernel, or use
+ it as a module. The module will be called f71808e_wdt.
+
+config SP5100_TCO
+ tristate "AMD/ATI SP5100 TCO Timer/Watchdog"
+ depends on X86 && PCI
+ select WATCHDOG_CORE
+ help
+ Hardware watchdog driver for the AMD/ATI SP5100 chipset. The TCO
+ (Total Cost of Ownership) timer is a watchdog timer that will reboot
+ the machine after its expiration. The expiration time can be
+ configured with the "heartbeat" parameter.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sp5100_tco.
+
+config GEODE_WDT
+ tristate "AMD Geode CS5535/CS5536 Watchdog"
+ depends on CS5535_MFGPT
+ help
+ This driver enables a watchdog capability built into the
+ CS5535/CS5536 companion chips for the AMD Geode GX and LX
+ processors. This watchdog 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 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
+ help
+ This is the driver for the built-in watchdog timer on the fit-PC2,
+ fit-PC2i, CM-iAM single-board computers made by Compulab.
+
+ It's possible to enable the watchdog timer either from BIOS (F2) or
+ from booted Linux.
+ When the "Watchdog Timer Value" is enabled one can set 31-255 seconds
+ operational range.
+
+ Entering BIOS setup temporarily disables watchdog operation regardless
+ of current state, so system will not be restarted while user is in
+ BIOS setup.
+
+ Once the watchdog is enabled the system will be restarted every
+ "Watchdog Timer Value" period, so to prevent it user can restart or
+ disable the watchdog.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbc_fitpc2_wdt.
+
+ Most people will say N.
+
+config EUROTECH_WDT
+ tristate "Eurotech CPU-1220/1410 Watchdog Timer"
+ depends on X86
+ help
+ Enable support for the watchdog timer on the Eurotech CPU-1220 and
+ CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product
+ information are at <http://www.eurotech.it/>.
+
+config IB700_WDT
+ tristate "IB700 SBC Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog on the IB700 Single
+ Board Computer produced by TMC Technology (www.tmc-uk.com). 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.
+
+ This driver is like the WDT501 driver but for slightly different
+ hardware.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ib700wdt.
+
+ Most people will say N.
+
+config IBMASR
+ tristate "IBM Automatic Server Restart"
+ depends on X86
+ help
+ This is the driver for the IBM Automatic Server Restart watchdog
+ timer built-in into some eServer xSeries machines.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ibmasr.
+
+config WAFER_WDT
+ tristate "ICP Single Board Computer Watchdog Timer"
+ depends on X86
+ help
+ This is a driver for the hardware watchdog on the ICP Single
+ Board Computer. This driver is working on (at least) the following
+ IPC SBC's: Wafer 5823, Rocky 4783, Rocky 3703 and Rocky 3782.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wafer5823wdt.
+
+config I6300ESB_WDT
+ tristate "Intel 6300ESB Timer/Watchdog"
+ depends on PCI
+ select WATCHDOG_CORE
+ help
+ Hardware driver for the watchdog timer built into the Intel
+ 6300ESB controller hub.
+
+ To compile this driver as a module, choose M here: the
+ module will be called i6300esb.
+
+config IE6XX_WDT
+ tristate "Intel Atom E6xx Watchdog"
+ depends on X86 && PCI
+ select WATCHDOG_CORE
+ select MFD_CORE
+ select LPC_SCH
+ help
+ Hardware driver for the watchdog timer built into the Intel
+ Atom E6XX (TunnelCreek) processor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ie6xx_wdt.
+
+config INTEL_MID_WATCHDOG
+ tristate "Intel MID Watchdog Timer"
+ depends on X86_INTEL_MID
+ select WATCHDOG_CORE
+ help
+ Watchdog timer driver built into the Intel SCU for Intel MID
+ Platforms.
+
+ This driver currently supports only the watchdog evolution
+ implementation in SCU, available for Merrifield generation.
+
+ To compile this driver as a module, choose M here.
+
+config ITCO_WDT
+ tristate "Intel TCO Timer/Watchdog"
+ depends on (X86 || IA64) && PCI
+ select WATCHDOG_CORE
+ depends on I2C || I2C=n
+ depends on MFD_INTEL_PMC_BXT || !MFD_INTEL_PMC_BXT
+ select LPC_ICH if !EXPERT
+ select I2C_I801 if !EXPERT && I2C
+ help
+ Hardware driver for the intel TCO timer based watchdog devices.
+ These drivers are included in the Intel 82801 I/O Controller
+ Hub family (from ICH0 up to ICH10) and in the Intel 63xxESB
+ controller hub.
+
+ The TCO (Total Cost of Ownership) timer is a watchdog timer
+ that will reboot the machine after its second expiration. The
+ expiration time can be configured with the "heartbeat" parameter.
+
+ On some motherboards the driver may fail to reset the chipset's
+ NO_REBOOT flag which prevents the watchdog from rebooting the
+ machine. If this is the case you will get a kernel message like
+ "failed to reset NO_REBOOT flag, reboot disabled by hardware".
+
+ To compile this driver as a module, choose M here: the
+ module will be called iTCO_wdt.
+
+config ITCO_VENDOR_SUPPORT
+ bool "Intel TCO Timer/Watchdog Specific Vendor Support"
+ depends on ITCO_WDT
+ help
+ Add vendor specific support to the intel TCO timer based watchdog
+ devices. At this moment we only have additional support for some
+ SuperMicro Inc. motherboards.
+
+config IT8712F_WDT
+ tristate "IT8712F (Smart Guardian) Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the built-in watchdog timer on the IT8712F
+ Super I/0 chipset used on many motherboards.
+
+ If the driver does not work, then make sure that the game port in
+ the BIOS is enabled.
+
+ To compile this driver as a module, choose M here: the
+ module will be called it8712f_wdt.
+
+config IT87_WDT
+ tristate "IT87 Watchdog Timer"
+ depends on X86
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog on the ITE IT8607,
+ IT8620, IT8622, IT8625, IT8628, IT8655, IT8665, IT8686, IT8702,
+ IT8712, IT8716, IT8718, IT8720, IT8721, IT8726, IT8728, and
+ IT8783 Super I/O chips.
+
+ 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 it87_wdt.
+
+config HP_WATCHDOG
+ tristate "HP ProLiant iLO2+ Hardware Watchdog Timer"
+ select WATCHDOG_CORE
+ depends on (ARM64 || X86) && PCI
+ help
+ A software monitoring watchdog and NMI handling driver. This driver
+ will detect lockups and provide a stack trace. This is a driver that
+ will only load on an HP ProLiant system with a minimum of iLO2 support.
+ To compile this driver as a module, choose M here: the module will be
+ called hpwdt.
+
+config HPWDT_NMI_DECODING
+ bool "NMI support for the HP ProLiant iLO2+ Hardware Watchdog Timer"
+ depends on X86 && HP_WATCHDOG
+ default y
+ help
+ Enables the NMI handler for the watchdog pretimeout NMI and the iLO
+ "Generate NMI to System" virtual button. When an NMI is claimed
+ by the driver, panic is called.
+
+config KEMPLD_WDT
+ tristate "Kontron COM Watchdog Timer"
+ depends on MFD_KEMPLD
+ select WATCHDOG_CORE
+ help
+ Support for the PLD watchdog on some Kontron ETX and COMexpress
+ (ETXexpress) modules
+
+ This driver can also be built as a module. If so, the module will be
+ called kempld_wdt.
+
+config SC1200_WDT
+ tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog"
+ depends on X86
+ help
+ This is a driver for National Semiconductor PC87307/PC97307 hardware
+ watchdog cards as found on the SC1200. This watchdog is mainly used
+ for power management purposes and can be used to power down the device
+ during inactivity periods (includes interrupt activity monitoring).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sc1200wdt.
+
+ Most people will say N.
+
+config SCx200_WDT
+ tristate "National Semiconductor SCx200 Watchdog"
+ depends on SCx200 && PCI
+ help
+ Enable the built-in watchdog timer support on the National
+ Semiconductor SCx200 processors.
+
+ If compiled as a module, it will be called scx200_wdt.
+
+config PC87413_WDT
+ tristate "NS PC87413 watchdog"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog on the PC87413 chipset
+ 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 pc87413_wdt.
+
+ Most people will say N.
+
+config NV_TCO
+ tristate "nVidia TCO Timer/Watchdog"
+ depends on X86 && PCI
+ help
+ Hardware driver for the TCO timer built into the nVidia Hub family
+ (such as the MCP51). The TCO (Total Cost of Ownership) timer is a
+ watchdog timer that will reboot the machine after its second
+ expiration. The expiration time can be configured with the
+ "heartbeat" parameter.
+
+ On some motherboards the driver may fail to reset the chipset's
+ NO_REBOOT flag which prevents the watchdog from rebooting the
+ machine. If this is the case you will get a kernel message like
+ "failed to reset NO_REBOOT flag, reboot disabled by hardware".
+
+ To compile this driver as a module, choose M here: the
+ module will be called nv_tco.
+
+config RDC321X_WDT
+ tristate "RDC R-321x SoC watchdog"
+ depends on X86_RDC321X || COMPILE_TEST
+ depends on PCI
+ help
+ This is the driver for the built in hardware watchdog
+ in the RDC R-321x SoC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rdc321x_wdt.
+
+config 60XX_WDT
+ tristate "SBC-60XX Watchdog Timer"
+ depends on X86
+ help
+ This driver can be used with the watchdog timer found on some
+ single board computers, namely the 6010 PII based computer.
+ It may well work with other cards. It reads port 0x443 to enable
+ and re-set the watchdog timer, and reads port 0x45 to disable
+ the watchdog. If you have a card that behave in similar ways,
+ you can probably make this driver work with your card as well.
+
+ You can compile this driver directly into the kernel, or use
+ it as a module. The module will be called sbc60xxwdt.
+
+config SBC8360_WDT
+ tristate "SBC8360 Watchdog Timer"
+ depends on X86_32
+ help
+
+ This is the driver for the hardware watchdog on the SBC8360 Single
+ Board Computer produced by Axiomtek Co., Ltd. (www.axiomtek.com).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbc8360.
+
+ Most people will say N.
+
+config SBC7240_WDT
+ tristate "SBC Nano 7240 Watchdog Timer"
+ depends on X86_32 && !UML
+ help
+ This is the driver for the hardware watchdog found on the IEI
+ single board computers EPIC Nano 7240 (and likely others). 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 sbc7240_wdt.
+
+config CPU5_WDT
+ tristate "SMA CPU5 Watchdog"
+ depends on X86
+ help
+ TBD.
+ To compile this driver as a module, choose M here: the
+ module will be called cpu5wdt.
+
+config SMSC_SCH311X_WDT
+ tristate "SMSC SCH311X Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog timer on the
+ SMSC SCH3112, SCH3114 and SCH3116 Super IO chipset
+ (LPC IO with 8042 KBC, Reset Generation, HWM and multiple
+ serial ports).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sch311x_wdt.
+
+config SMSC37B787_WDT
+ tristate "Winbond SMsC37B787 Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog component on the
+ Winbond SMsC37B787 chipset as used on the NetRunner Mainboard
+ from Vision Systems and maybe others.
+
+ 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.
+
+ Usually a userspace daemon will notify the kernel WDT driver that
+ userspace is still alive, at regular intervals.
+
+ To compile this driver as a module, choose M here: the
+ module will be called smsc37b787_wdt.
+
+ Most people will say N.
+
+config TQMX86_WDT
+ tristate "TQ-Systems TQMX86 Watchdog Timer"
+ depends on X86
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog timer in the TQMX86 IO
+ controller found on some of their ComExpress Modules.
+
+ To compile this driver as a module, choose M here; the module
+ will be called tqmx86_wdt.
+
+ Most people will say N.
+
+config VIA_WDT
+ tristate "VIA Watchdog Timer"
+ depends on X86 && PCI
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog timer on VIA
+ southbridge chipset CX700, VX800/VX820 or VX855/VX875.
+
+ To compile this driver as a module, choose M here; the module
+ will be called via_wdt.
+
+ Most people will say N.
+
+config W83627HF_WDT
+ tristate "Watchdog timer for W83627HF/W83627DHG and compatibles"
+ depends on X86
+ select WATCHDOG_CORE
+ help
+ This is the driver for the hardware watchdog on the following
+ Super I/O chips.
+ W83627DHG/DHG-P/EHF/EHG/F/G/HF/S/SF/THF/UHG/UG
+ W83637HF
+ W83667HG/HG-B
+ W83687THF
+ W83697HF
+ W83697UG
+ NCT6775
+ NCT6776
+ NCT6779
+ NCT6791
+ NCT6792
+ NCT6102D/04D/06D
+ NCT6116D
+
+ 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 w83627hf_wdt.
+
+ Most people will say N.
+
+config W83877F_WDT
+ tristate "W83877F (EMACS) Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog on the W83877F chipset
+ as used in EMACS PC-104 motherboards (and likely others). 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 w83877f_wdt.
+
+ Most people will say N.
+
+config W83977F_WDT
+ tristate "W83977F (PCM-5335) Watchdog Timer"
+ depends on X86
+ help
+ This is the driver for the hardware watchdog on the W83977F I/O chip
+ as used in AAEON's PCM-5335 SBC (and likely others). 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 w83977f_wdt.
+
+config MACHZ_WDT
+ tristate "ZF MachZ Watchdog"
+ depends on X86
+ 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
+ help
+ This is the driver for the built-in watchdog timer on the EPX-C3
+ Single-board computer made by Winsystems, Inc.
+
+ *Note*: This hardware watchdog is not probeable and thus there
+ is no way to know if writing to its IO address will corrupt
+ your system or have any real effect. The only way to be sure
+ that this driver does what you want is to make sure you
+ are running it on an EPX-C3 from Winsystems with the watchdog
+ timer at IO address 0x1ee and 0x1ef. It will write to both those
+ IO ports. Basically, the assumption is made that if you compile
+ this driver into your kernel and/or load it as a module, that you
+ know what you are doing and that you are in fact running on an
+ EPX-C3 board!
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbc_epx_c3.
+
+config INTEL_MEI_WDT
+ tristate "Intel MEI iAMT Watchdog"
+ depends on INTEL_MEI && X86
+ select WATCHDOG_CORE
+ help
+ A device driver for the Intel MEI iAMT watchdog.
+
+ The Intel AMT Watchdog is an OS Health (Hang/Crash) watchdog.
+ Whenever the OS hangs or crashes, iAMT will send an event
+ to any subscriber to this event. The watchdog doesn't reset the
+ the platform.
+
+ To compile this driver as a module, choose M here:
+ the module will be called mei_wdt.
+
+config NI903X_WDT
+ tristate "NI 903x/913x Watchdog"
+ depends on X86 && ACPI
+ select WATCHDOG_CORE
+ help
+ This is the driver for the watchdog timer on the National Instruments
+ 903x/913x real-time controllers.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ni903x_wdt.
+
+config NIC7018_WDT
+ tristate "NIC7018 Watchdog"
+ depends on X86 && ACPI
+ select WATCHDOG_CORE
+ help
+ Support for National Instruments NIC7018 Watchdog.
+
+ To compile this driver as a module, choose M here: the module will be
+ called nic7018_wdt.
+
+config SIEMENS_SIMATIC_IPC_WDT
+ tristate "Siemens Simatic IPC Watchdog"
+ depends on SIEMENS_SIMATIC_IPC
+ select WATCHDOG_CORE
+ select P2SB
+ help
+ This driver adds support for several watchdogs found in Industrial
+ PCs from Siemens.
+
+ To compile this driver as a module, choose M here: the module will be
+ called simatic-ipc-wdt.
+
+# M68K Architecture
+
+config M54xx_WATCHDOG
+ tristate "MCF54xx watchdog support"
+ depends on M548x
+ help
+ To compile this driver as a module, choose M here: the
+ module will be called m54xx_wdt.
+
+# MicroBlaze Architecture
+
+# MIPS Architecture
+
+config ATH79_WDT
+ tristate "Atheros AR71XX/AR724X/AR913X hardware watchdog"
+ depends on ATH79 || (ARM && COMPILE_TEST)
+ help
+ Hardware driver for the built-in watchdog timer on the Atheros
+ AR71XX/AR724X/AR913X SoCs.
+
+config BCM47XX_WDT
+ tristate "Broadcom BCM47xx Watchdog Timer"
+ depends on BCM47XX || ARCH_BCM_5301X || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Hardware driver for the Broadcom BCM47xx Watchdog Timer.
+
+config RC32434_WDT
+ tristate "IDT RC32434 SoC Watchdog Timer"
+ depends on MIKROTIK_RB532
+ help
+ Hardware driver for the IDT RC32434 SoC built-in
+ watchdog timer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rc32434_wdt.
+
+config INDYDOG
+ tristate "Indy/I2 Hardware Watchdog"
+ depends on SGI_HAS_INDYDOG
+ help
+ Hardware driver for the Indy's/I2's watchdog. This is a
+ watchdog timer that will reboot the machine after a 60 second
+ timer expired and no process has written to /dev/watchdog during
+ that time.
+
+config JZ4740_WDT
+ tristate "Ingenic jz4740 SoC hardware watchdog"
+ depends on MIPS
+ depends on COMMON_CLK
+ select WATCHDOG_CORE
+ select MFD_SYSCON
+ help
+ Hardware driver for the built-in watchdog timer on Ingenic jz4740 SoCs.
+
+config WDT_MTX1
+ tristate "MTX-1 Hardware Watchdog"
+ depends on MIPS_MTX1 || (MIPS && COMPILE_TEST)
+ help
+ Hardware driver for the MTX-1 boards. This is a watchdog timer that
+ will reboot the machine after a 100 seconds timer expired.
+
+config SIBYTE_WDOG
+ tristate "Sibyte SoC hardware watchdog"
+ depends on CPU_SB1
+ help
+ Watchdog driver for the built in watchdog hardware in Sibyte
+ SoC processors. There are apparently two watchdog timers
+ on such processors; this driver supports only the first one,
+ because currently Linux only supports exporting one watchdog
+ to userspace.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called sb_wdog.
+
+config AR7_WDT
+ tristate "TI AR7 Watchdog Timer"
+ depends on AR7 || (MIPS && 32BIT && COMPILE_TEST)
+ help
+ Hardware driver for the TI AR7 Watchdog Timer.
+
+config TXX9_WDT
+ tristate "Toshiba TXx9 Watchdog Timer"
+ depends on CPU_TX49XX || (MIPS && COMPILE_TEST)
+ select WATCHDOG_CORE
+ help
+ Hardware driver for the built-in watchdog timer on TXx9 MIPS SoCs.
+
+config OCTEON_WDT
+ tristate "Cavium OCTEON SOC family Watchdog Timer"
+ depends on CAVIUM_OCTEON_SOC
+ default y
+ select WATCHDOG_CORE
+ select EXPORT_UASM if OCTEON_WDT = m
+ help
+ Hardware driver for OCTEON's on chip watchdog timer.
+ Enables the watchdog for all cores running Linux. It
+ installs a NMI handler and pokes the watchdog based on an
+ interrupt. On first expiration of the watchdog, the
+ interrupt handler pokes it. The second expiration causes an
+ NMI that prints a message. The third expiration causes a
+ global soft reset.
+
+ When userspace has /dev/watchdog open, no poking is done
+ from the first interrupt, it is then only poked when the
+ device is written.
+
+config BCM2835_WDT
+ tristate "Broadcom BCM2835 hardware watchdog"
+ depends on ARCH_BCM2835 || (OF && COMPILE_TEST)
+ select WATCHDOG_CORE
+ help
+ Watchdog driver for the built in watchdog hardware in Broadcom
+ BCM2835 SoC.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called bcm2835_wdt.
+
+config BCM_KONA_WDT
+ tristate "BCM Kona Watchdog"
+ depends on ARCH_BCM_MOBILE || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog timer on the following Broadcom BCM281xx
+ family, which includes BCM11130, BCM11140, BCM11351, BCM28145 and
+ BCM28155 variants.
+
+ Say 'Y' or 'M' here to enable the driver. The module will be called
+ bcm_kona_wdt.
+
+config BCM_KONA_WDT_DEBUG
+ bool "DEBUGFS support for BCM Kona Watchdog"
+ depends on BCM_KONA_WDT
+ help
+ If enabled, adds /sys/kernel/debug/bcm_kona_wdt/info which provides
+ access to the driver's internal data structures as well as watchdog
+ timer hardware registres.
+
+ If in doubt, say 'N'.
+
+config BCM7038_WDT
+ tristate "BCM63xx/BCM7038 Watchdog"
+ select WATCHDOG_CORE
+ depends on HAS_IOMEM
+ depends on ARCH_BCMBCA || ARCH_BRCMSTB || BMIPS_GENERIC || BCM63XX || COMPILE_TEST
+ help
+ Watchdog driver for the built-in hardware in Broadcom 7038 and
+ later SoCs used in set-top boxes. BCM7038 was made public
+ during the 2004 CES, and since then, many Broadcom chips use this
+ watchdog block, including some cable modem chips and DSL (63xx)
+ chips.
+
+config IMGPDC_WDT
+ tristate "Imagination Technologies PDC Watchdog Timer"
+ depends on HAS_IOMEM
+ depends on MIPS || COMPILE_TEST
+ select WATCHDOG_CORE
+ help
+ Driver for Imagination Technologies PowerDown Controller
+ Watchdog Timer.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called imgpdc_wdt.
+
+config LANTIQ_WDT
+ tristate "Lantiq SoC watchdog"
+ depends on LANTIQ
+ select WATCHDOG_CORE
+ help
+ Hardware driver for the Lantiq SoC Watchdog Timer.
+
+config LOONGSON1_WDT
+ tristate "Loongson1 SoC hardware watchdog"
+ depends on MACH_LOONGSON32
+ select WATCHDOG_CORE
+ help
+ Hardware driver for the Loongson1 SoC Watchdog Timer.
+
+config RALINK_WDT
+ tristate "Ralink SoC watchdog"
+ select WATCHDOG_CORE
+ depends on RALINK
+ help
+ Hardware driver for the Ralink SoC Watchdog Timer.
+
+config GXP_WATCHDOG
+ tristate "HPE GXP watchdog support"
+ depends on ARCH_HPE_GXP
+ select WATCHDOG_CORE
+ help
+ Say Y here to include support for the watchdog timer
+ in HPE GXP SoCs.
+
+ To compile this driver as a module, choose M here.
+ The module will be called gxp-wdt.
+
+config MT7621_WDT
+ tristate "Mediatek SoC watchdog"
+ select WATCHDOG_CORE
+ depends on SOC_MT7620 || SOC_MT7621
+ help
+ Hardware driver for the Mediatek/Ralink MT7621/8 SoC Watchdog Timer.
+
+config PIC32_WDT
+ tristate "Microchip PIC32 hardware watchdog"
+ select WATCHDOG_CORE
+ depends on MACH_PIC32 || (MIPS && COMPILE_TEST)
+ help
+ Watchdog driver for the built in watchdog hardware in a PIC32.
+
+ Configuration bits must be set appropriately for the watchdog to be
+ controlled by this driver.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called pic32-wdt.
+
+config PIC32_DMT
+ tristate "Microchip PIC32 Deadman Timer"
+ select WATCHDOG_CORE
+ depends on MACH_PIC32 || (MIPS && COMPILE_TEST)
+ help
+ Watchdog driver for PIC32 instruction fetch counting timer. This
+ specific timer is typically be used in mission critical and safety
+ critical applications, where any single failure of the software
+ functionality and sequencing must be detected.
+
+ To compile this driver as a loadable module, choose M here.
+ The module will be called pic32-dmt.
+
+# PARISC Architecture
+
+# POWERPC Architecture
+
+config GEF_WDT
+ tristate "GE Watchdog Timer"
+ depends on GE_FPGA
+ help
+ Watchdog timer found in a number of GE single board computers.
+
+config MPC5200_WDT
+ bool "MPC52xx Watchdog Timer"
+ depends on PPC_MPC52xx || COMPILE_TEST
+ help
+ Use General Purpose Timer (GPT) 0 on the MPC5200 as Watchdog.
+
+config 8xxx_WDT
+ tristate "MPC8xxx Platform Watchdog Timer"
+ depends on PPC_8xx || PPC_83xx || PPC_86xx || PPC_MPC512x
+ select WATCHDOG_CORE
+ help
+ This driver is for a SoC level watchdog that exists on some
+ Freescale PowerPC processors. So far this driver supports:
+ - MPC8xx watchdogs
+ - MPC83xx watchdogs
+ - MPC86xx watchdogs
+
+ For BookE processors (MPC85xx) use the BOOKE_WDT driver instead.
+
+config PIKA_WDT
+ tristate "PIKA FPGA Watchdog"
+ depends on WARP || (PPC64 && COMPILE_TEST)
+ default y
+ help
+ This enables the watchdog in the PIKA FPGA. Currently used on
+ the Warp platform.
+
+config BOOKE_WDT
+ tristate "PowerPC Book-E Watchdog Timer"
+ depends on BOOKE || 4xx
+ select WATCHDOG_CORE
+ help
+ Watchdog driver for PowerPC Book-E chips, such as the Freescale
+ MPC85xx SOCs and the IBM PowerPC 440.
+
+ Please see Documentation/watchdog/watchdog-api.rst for
+ more information.
+
+config BOOKE_WDT_DEFAULT_TIMEOUT
+ int "PowerPC Book-E Watchdog Timer Default Timeout"
+ depends on BOOKE_WDT
+ default 38 if PPC_E500
+ range 0 63 if PPC_E500
+ default 3 if !PPC_E500
+ range 0 3 if !PPC_E500
+ help
+ Select the default watchdog timer period to be used by the PowerPC
+ Book-E watchdog driver. A watchdog "event" occurs when the bit
+ position represented by this number transitions from zero to one.
+
+ For Freescale Book-E processors, this is a number between 0 and 63.
+ For other Book-E processors, this is a number between 0 and 3.
+
+ The value can be overridden by the wdt_period command-line parameter.
+
+config MEN_A21_WDT
+ tristate "MEN A21 VME CPU Carrier Board Watchdog Timer"
+ select WATCHDOG_CORE
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Watchdog driver for MEN A21 VMEbus CPU Carrier Boards.
+
+ The driver can also be built as a module. If so, the module will be
+ called mena21_wdt.
+
+ If unsure select N here.
+
+# PPC64 Architecture
+
+config PSERIES_WDT
+ tristate "POWER Architecture Platform Watchdog Timer"
+ depends on PPC_PSERIES
+ select WATCHDOG_CORE
+ help
+ Driver for virtual watchdog timers provided by PAPR
+ hypervisors (e.g. PowerVM, KVM).
+
+config WATCHDOG_RTAS
+ tristate "RTAS watchdog"
+ depends on PPC_RTAS
+ help
+ This driver adds watchdog support for the RTAS watchdog.
+
+ To compile this driver as a module, choose M here. The module
+ will be called wdrtas.
+
+# S390 Architecture
+
+config DIAG288_WATCHDOG
+ tristate "System z diag288 Watchdog"
+ depends on S390
+ select WATCHDOG_CORE
+ help
+ IBM s/390 and zSeries machines running under z/VM 5.1 or later
+ provide a virtual watchdog timer to their guest that cause a
+ user define Control Program command to be executed after a
+ timeout.
+ LPAR provides a very similar interface. This driver handles
+ both.
+
+ To compile this driver as a module, choose M here. The module
+ will be called diag288_wdt.
+
+# SUPERH (sh + sh64) Architecture
+
+config SH_WDT
+ tristate "SuperH Watchdog"
+ depends on SUPERH && (CPU_SH3 || CPU_SH4 || COMPILE_TEST)
+ select WATCHDOG_CORE
+ help
+ This driver adds watchdog support for the integrated watchdog in the
+ SuperH processors. If you have one of these processors and wish
+ to have watchdog support enabled, say Y, otherwise say N.
+
+ As a side note, saying Y here will automatically boost HZ to 1000
+ so that the timer has a chance to clear the overflow counter. On
+ slower systems (such as the SH-2 and SH-3) this will likely yield
+ some performance issues. As such, the WDT should be avoided here
+ unless it is absolutely necessary.
+
+ To compile this driver as a module, choose M here: the
+ module will be called shwdt.
+
+# SPARC Architecture
+
+# SPARC64 Architecture
+
+config WATCHDOG_CP1XXX
+ tristate "CP1XXX Hardware Watchdog support"
+ depends on SPARC64 && PCI
+ help
+ This is the driver for the hardware watchdog timers present on
+ Sun Microsystems CompactPCI models CP1400 and CP1500.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cpwatchdog.
+
+ If you do not have a CompactPCI model CP1400 or CP1500, or
+ another UltraSPARC-IIi-cEngine boardset with hardware watchdog,
+ you should say N to this option.
+
+config WATCHDOG_RIO
+ tristate "RIO Hardware Watchdog support"
+ depends on SPARC64 && PCI
+ help
+ Say Y here to support the hardware watchdog capability on Sun RIO
+ machines. The watchdog timeout period is normally one minute but
+ can be changed with a boot-time parameter.
+
+config WATCHDOG_SUN4V
+ tristate "Sun4v Watchdog support"
+ select WATCHDOG_CORE
+ depends on SPARC64
+ help
+ Say Y here to support the hypervisor watchdog capability embedded
+ in the SPARC sun4v architecture.
+
+ To compile this driver as a module, choose M here. The module will
+ be called sun4v_wdt.
+
+# XTENSA Architecture
+
+# Xen Architecture
+
+config XEN_WDT
+ tristate "Xen Watchdog support"
+ depends on XEN
+ select WATCHDOG_CORE
+ help
+ Say Y here to support the hypervisor watchdog capability provided
+ by Xen 4.0 and newer. The watchdog timeout period is normally one
+ minute but can be changed with a boot-time parameter.
+
+config UML_WATCHDOG
+ tristate "UML watchdog"
+ depends on UML || COMPILE_TEST
+
+#
+# ISA-based Watchdog Cards
+#
+
+comment "ISA-based Watchdog Cards"
+ depends on ISA
+
+config PCWATCHDOG
+ tristate "Berkshire Products ISA-PC Watchdog"
+ depends on ISA
+ help
+ This is the driver for the Berkshire Products ISA-PC Watchdog card.
+ This card 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. This driver is like the WDT501 driver but for different
+ hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.rst>.
+ The PC watchdog cards can be ordered from <http://www.berkprod.com/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd.
+
+ Most people will say N.
+
+config MIXCOMWD
+ tristate "Mixcom Watchdog"
+ depends on ISA
+ help
+ This is a driver for the Mixcom hardware watchdog cards. 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 mixcomwd.
+
+ Most people will say N.
+
+config WDT
+ tristate "WDT Watchdog timer"
+ depends on ISA
+ help
+ If you have a 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)
+
+ To compile this driver as a module, choose M here: the
+ module will be called wdt.
+
+#
+# PCI-based Watchdog Cards
+#
+
+comment "PCI-based Watchdog Cards"
+ depends on PCI
+
+config PCIPCWATCHDOG
+ tristate "Berkshire Products PCI-PC Watchdog"
+ depends on PCI
+ help
+ This is the driver for the Berkshire Products PCI-PC Watchdog card.
+ This card 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. The card can also monitor the internal temperature of the PC.
+ More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd_pci.
+
+ Most people will say N.
+
+config WDTPCI
+ tristate "PCI-WDT500/501 Watchdog timer"
+ depends on PCI
+ help
+ If you have a 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.
+
+ If you want to enable the Fan Tachometer on the PCI-WDT501, then you
+ can do this via the tachometer parameter. Only do this if you have a
+ fan tachometer actually set up.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wdt_pci.
+
+#
+# USB-based Watchdog Cards
+#
+
+comment "USB-based Watchdog Cards"
+ depends on USB
+
+config USBPCWATCHDOG
+ tristate "Berkshire Products USB-PC Watchdog"
+ depends on USB
+ help
+ This is the driver for the Berkshire Products USB-PC Watchdog card.
+ This card 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. The card can also monitor the internal temperature of the PC.
+ More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd_usb.
+
+ Most people will say N.
+
+config KEEMBAY_WATCHDOG
+ tristate "Intel Keem Bay SoC non-secure watchdog"
+ depends on ARCH_KEEMBAY || (ARM64 && COMPILE_TEST)
+ select WATCHDOG_CORE
+ help
+ This option enable support for an In-secure watchdog timer driver for
+ Intel Keem Bay SoC. This WDT has a 32 bit timer and decrements in every
+ count unit. An interrupt will be triggered, when the count crosses
+ the threshold configured in the register.
+
+ To compile this driver as a module, choose M here: the
+ module will be called keembay_wdt.
+
+endif # WATCHDOG
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
new file mode 100644
index 000000000..d41e5f830
--- /dev/null
+++ b/drivers/watchdog/Makefile
@@ -0,0 +1,231 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the WatchDog device drivers.
+#
+
+# The WatchDog Timer Driver Core.
+obj-$(CONFIG_WATCHDOG_CORE) += watchdog.o
+
+watchdog-objs += watchdog_core.o watchdog_dev.o
+
+watchdog-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV) += watchdog_pretimeout.o
+watchdog-$(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT) += watchdog_hrtimer_pretimeout.o
+
+obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_NOOP) += pretimeout_noop.o
+obj-$(CONFIG_WATCHDOG_PRETIMEOUT_GOV_PANIC) += pretimeout_panic.o
+
+# Only one watchdog can succeed. We probe the ISA/PCI/USB based
+# watchdog-cards first, then the architecture specific watchdog
+# drivers and then the architecture independent "softdog" driver.
+# This means that if your ISA/PCI/USB card isn't detected that
+# you can fall back to an architecture specific driver and if
+# that also fails then you can fall back to the software watchdog
+# to give you some cover.
+
+# ISA-based Watchdog Cards
+obj-$(CONFIG_PCWATCHDOG) += pcwd.o
+obj-$(CONFIG_MIXCOMWD) += mixcomwd.o
+obj-$(CONFIG_WDT) += wdt.o
+
+# PCI-based Watchdog Cards
+obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o
+obj-$(CONFIG_WDTPCI) += wdt_pci.o
+
+# USB-based Watchdog Cards
+obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
+
+# ALPHA Architecture
+
+# ARM Architecture
+obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o
+obj-$(CONFIG_ARM_SBSA_WATCHDOG) += sbsa_gwdt.o
+obj-$(CONFIG_ARMADA_37XX_WATCHDOG) += armada_37xx_wdt.o
+obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o
+obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
+obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o
+obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o
+obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
+obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o
+obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
+obj-$(CONFIG_977_WATCHDOG) += wdt977.o
+obj-$(CONFIG_FTWDT010_WATCHDOG) += ftwdt010_wdt.o
+obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
+obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
+obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
+obj-$(CONFIG_SAMA5D4_WATCHDOG) += sama5d4_wdt.o
+obj-$(CONFIG_DW_WATCHDOG) += dw_wdt.o
+obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o
+obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o
+obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o
+obj-$(CONFIG_K3_RTI_WATCHDOG) += rti_wdt.o
+obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
+obj-$(CONFIG_SUNXI_WATCHDOG) += sunxi_wdt.o
+obj-$(CONFIG_RN5T618_WATCHDOG) += rn5t618_wdt.o
+obj-$(CONFIG_NPCM7XX_WATCHDOG) += npcm_wdt.o
+obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o
+obj-$(CONFIG_TS4800_WATCHDOG) += ts4800_wdt.o
+obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o
+obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o
+obj-$(CONFIG_IMX_SC_WDT) += imx_sc_wdt.o
+obj-$(CONFIG_IMX7ULP_WDT) += imx7ulp_wdt.o
+obj-$(CONFIG_DB500_WATCHDOG) += db8500_wdt.o
+obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o
+obj-$(CONFIG_BCM2835_WDT) += bcm2835_wdt.o
+obj-$(CONFIG_MOXART_WDT) += moxart_wdt.o
+obj-$(CONFIG_ST_LPC_WATCHDOG) += st_lpc_wdt.o
+obj-$(CONFIG_QCOM_WDT) += qcom-wdt.o
+obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o
+obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o
+obj-$(CONFIG_MESON_GXBB_WATCHDOG) += meson_gxbb_wdt.o
+obj-$(CONFIG_MESON_WATCHDOG) += meson_wdt.o
+obj-$(CONFIG_MEDIATEK_WATCHDOG) += mtk_wdt.o
+obj-$(CONFIG_DIGICOLOR_WATCHDOG) += digicolor_wdt.o
+obj-$(CONFIG_LPC18XX_WATCHDOG) += lpc18xx_wdt.o
+obj-$(CONFIG_BCM7038_WDT) += bcm7038_wdt.o
+obj-$(CONFIG_RENESAS_WDT) += renesas_wdt.o
+obj-$(CONFIG_RENESAS_RZAWDT) += rza_wdt.o
+obj-$(CONFIG_RENESAS_RZN1WDT) += rzn1_wdt.o
+obj-$(CONFIG_RENESAS_RZG2LWDT) += rzg2l_wdt.o
+obj-$(CONFIG_ASPEED_WATCHDOG) += aspeed_wdt.o
+obj-$(CONFIG_STM32_WATCHDOG) += stm32_iwdg.o
+obj-$(CONFIG_UNIPHIER_WATCHDOG) += uniphier_wdt.o
+obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o
+obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o
+obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o
+obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o
+obj-$(CONFIG_GXP_WATCHDOG) += gxp-wdt.o
+obj-$(CONFIG_VISCONTI_WATCHDOG) += visconti_wdt.o
+obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o
+obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o
+obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o
+
+# X86 (i386 + ia64 + x86_64) Architecture
+obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
+obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
+obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
+obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
+obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o
+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
+obj-$(CONFIG_IBMASR) += ibmasr.o
+obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o
+obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o
+obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o
+obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
+ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
+obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
+endif
+obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
+obj-$(CONFIG_IT87_WDT) += it87_wdt.o
+obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
+obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
+obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
+obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
+obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
+obj-$(CONFIG_NV_TCO) += nv_tco.o
+obj-$(CONFIG_RDC321X_WDT) += rdc321x_wdt.o
+obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
+obj-$(CONFIG_SBC8360_WDT) += sbc8360.o
+obj-$(CONFIG_SBC7240_WDT) += sbc7240_wdt.o
+obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o
+obj-$(CONFIG_SMSC_SCH311X_WDT) += sch311x_wdt.o
+obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o
+obj-$(CONFIG_TQMX86_WDT) += tqmx86_wdt.o
+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_MEI_WDT) += mei_wdt.o
+obj-$(CONFIG_NI903X_WDT) += ni903x_wdt.o
+obj-$(CONFIG_NIC7018_WDT) += nic7018_wdt.o
+obj-$(CONFIG_MLX_WDT) += mlx_wdt.o
+obj-$(CONFIG_KEEMBAY_WATCHDOG) += keembay_wdt.o
+obj-$(CONFIG_SIEMENS_SIMATIC_IPC_WDT) += simatic-ipc-wdt.o
+
+# M68K Architecture
+obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o
+
+# MicroBlaze Architecture
+obj-$(CONFIG_XILINX_WATCHDOG) += of_xilinx_wdt.o
+
+# MIPS Architecture
+obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o
+obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o
+obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o
+obj-$(CONFIG_INDYDOG) += indydog.o
+obj-$(CONFIG_JZ4740_WDT) += jz4740_wdt.o
+obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o
+obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o
+obj-$(CONFIG_AR7_WDT) += ar7_wdt.o
+obj-$(CONFIG_TXX9_WDT) += txx9wdt.o
+obj-$(CONFIG_OCTEON_WDT) += octeon-wdt.o
+octeon-wdt-y := octeon-wdt-main.o octeon-wdt-nmi.o
+obj-$(CONFIG_LANTIQ_WDT) += lantiq_wdt.o
+obj-$(CONFIG_LOONGSON1_WDT) += loongson1_wdt.o
+obj-$(CONFIG_RALINK_WDT) += rt2880_wdt.o
+obj-$(CONFIG_IMGPDC_WDT) += imgpdc_wdt.o
+obj-$(CONFIG_MT7621_WDT) += mt7621_wdt.o
+obj-$(CONFIG_PIC32_WDT) += pic32-wdt.o
+obj-$(CONFIG_PIC32_DMT) += pic32-dmt.o
+obj-$(CONFIG_REALTEK_OTTO_WDT) += realtek_otto_wdt.o
+
+# PARISC Architecture
+
+# POWERPC Architecture
+obj-$(CONFIG_GEF_WDT) += gef_wdt.o
+obj-$(CONFIG_8xxx_WDT) += mpc8xxx_wdt.o
+obj-$(CONFIG_PIKA_WDT) += pika_wdt.o
+obj-$(CONFIG_BOOKE_WDT) += booke_wdt.o
+obj-$(CONFIG_MEN_A21_WDT) += mena21_wdt.o
+
+# PPC64 Architecture
+obj-$(CONFIG_PSERIES_WDT) += pseries-wdt.o
+obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o
+
+# S390 Architecture
+obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o
+
+# SUPERH (sh + sh64) Architecture
+obj-$(CONFIG_SH_WDT) += shwdt.o
+
+# SPARC Architecture
+
+# SPARC64 Architecture
+
+obj-$(CONFIG_WATCHDOG_RIO) += riowd.o
+obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o
+obj-$(CONFIG_WATCHDOG_SUN4V) += sun4v_wdt.o
+
+# XTENSA Architecture
+
+# Xen
+obj-$(CONFIG_XEN_WDT) += xen_wdt.o
+
+# Architecture Independent
+obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o
+obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
+obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
+obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
+obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
+obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o
+obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
+obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
+obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
+obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
+obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
+obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
+obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
+obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o
+obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
+obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o
+obj-$(CONFIG_SL28CPLD_WATCHDOG) += sl28cpld_wdt.o
diff --git a/drivers/watchdog/acquirewdt.c b/drivers/watchdog/acquirewdt.c
new file mode 100644
index 000000000..bc6f33356
--- /dev/null
+++ b/drivers/watchdog/acquirewdt.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Acquire Single Board Computer Watchdog Timer driver
+ *
+ * Based on wdt.c. Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Can't add timeout - driver doesn't allow changing value
+ */
+
+/*
+ * Theory of Operation:
+ * The Watch-Dog Timer is provided to ensure that standalone
+ * Systems can always recover from catastrophic conditions that
+ * caused the CPU to crash. This condition may have occurred by
+ * external EMI or a software bug. When the CPU stops working
+ * correctly, hardware on the board will either perform a hardware
+ * reset (cold boot) or a non-maskable interrupt (NMI) to bring the
+ * system back to a known state.
+ *
+ * The Watch-Dog Timer is controlled by two I/O Ports.
+ * 443 hex - Read - Enable or refresh the Watch-Dog Timer
+ * 043 hex - Read - Disable the Watch-Dog Timer
+ *
+ * To enable the Watch-Dog Timer, a read from I/O port 443h must
+ * be performed. This will enable and activate the countdown timer
+ * which will eventually time out and either reset the CPU or cause
+ * an NMI depending on the setting of a jumper. To ensure that this
+ * reset condition does not occur, the Watch-Dog Timer must be
+ * periodically refreshed by reading the same I/O port 443h.
+ * The Watch-Dog Timer is disabled by reading I/O port 043h.
+ *
+ * The Watch-Dog Timer Time-Out Period is set via jumpers.
+ * It can be 1, 2, 10, 20, 110 or 220 seconds.
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/* Includes */
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/fs.h> /* For file operations */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/platform_device.h> /* For platform_driver framework */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For inb/outb/... */
+
+/* Module information */
+#define DRV_NAME "acquirewdt"
+#define WATCHDOG_NAME "Acquire WDT"
+/* There is no way to see what the correct time-out period is */
+#define WATCHDOG_HEARTBEAT 0
+
+/* internal variables */
+/* the watchdog platform device */
+static struct platform_device *acq_platform_device;
+static unsigned long acq_is_open;
+static char expect_close;
+
+/* module parameters */
+/* You must set this - there is no sane way to probe for this board. */
+static int wdt_stop = 0x43;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
+
+/* You must set this - there is no sane way to probe for this board. */
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
+
+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) ")");
+
+/*
+ * Watchdog Operations
+ */
+
+static void acq_keepalive(void)
+{
+ /* Write a watchdog value */
+ inb_p(wdt_start);
+}
+
+static void acq_stop(void)
+{
+ /* Turn the card off */
+ inb_p(wdt_stop);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t acq_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 i;
+ /* note: just in case someone wrote the magic character
+ five months ago... */
+ expect_close = 0;
+ /* scan to see whether or not we got the
+ magic character */
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ /* Well, anyhow someone wrote to us, we should
+ return that favour */
+ acq_keepalive();
+ }
+ return count;
+}
+
+static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int options, retval = -EINVAL;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+ };
+
+ 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:
+ {
+ if (get_user(options, p))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ acq_stop();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ acq_keepalive();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ acq_keepalive();
+ return 0;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(WATCHDOG_HEARTBEAT, p);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int acq_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &acq_is_open))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate */
+ acq_keepalive();
+ return stream_open(inode, file);
+}
+
+static int acq_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ acq_stop();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ acq_keepalive();
+ }
+ clear_bit(0, &acq_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations acq_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = acq_write,
+ .unlocked_ioctl = acq_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = acq_open,
+ .release = acq_close,
+};
+
+static struct miscdevice acq_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &acq_fops,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int __init acq_probe(struct platform_device *dev)
+{
+ int ret;
+
+ if (wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_stop);
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_start);
+ ret = -EIO;
+ goto unreg_stop;
+ }
+ ret = misc_register(&acq_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_regions;
+ }
+ pr_info("initialized. (nowayout=%d)\n", nowayout);
+
+ return 0;
+unreg_regions:
+ release_region(wdt_start, 1);
+unreg_stop:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+out:
+ return ret;
+}
+
+static int acq_remove(struct platform_device *dev)
+{
+ misc_deregister(&acq_miscdev);
+ release_region(wdt_start, 1);
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+
+ return 0;
+}
+
+static void acq_shutdown(struct platform_device *dev)
+{
+ /* Turn the WDT off if we have a soft shutdown */
+ acq_stop();
+}
+
+static struct platform_driver acquirewdt_driver = {
+ .remove = acq_remove,
+ .shutdown = acq_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init acq_init(void)
+{
+ int err;
+
+ pr_info("WDT driver for Acquire single board computer initialising\n");
+
+ acq_platform_device = platform_device_register_simple(DRV_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(acq_platform_device))
+ return PTR_ERR(acq_platform_device);
+
+ err = platform_driver_probe(&acquirewdt_driver, acq_probe);
+ if (err)
+ goto unreg_platform_device;
+ return 0;
+
+unreg_platform_device:
+ platform_device_unregister(acq_platform_device);
+ return err;
+}
+
+static void __exit acq_exit(void)
+{
+ platform_device_unregister(acq_platform_device);
+ platform_driver_unregister(&acquirewdt_driver);
+ pr_info("Watchdog Module Unloaded\n");
+}
+
+module_init(acq_init);
+module_exit(acq_exit);
+
+MODULE_AUTHOR("David Woodhouse");
+MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c
new file mode 100644
index 000000000..554fe85da
--- /dev/null
+++ b/drivers/watchdog/advantechwdt.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Advantech Single Board Computer WDT driver
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * Based on acquirewdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * 16-Oct-2002 Rob Radez <rob@osinvestor.com>
+ * Clean up ioctls, clean up init + exit, add expect close support,
+ * add wdt_start and wdt_stop as parameters.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+#define DRV_NAME "advantechwdt"
+#define WATCHDOG_NAME "Advantech WDT"
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+/* the watchdog platform device */
+static struct platform_device *advwdt_platform_device;
+static unsigned long advwdt_is_open;
+static char adv_expect_close;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ *
+ * To enable or restart, write the timeout value in seconds (1 to 63)
+ * to I/O port wdt_start. To disable, read I/O port wdt_stop.
+ * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but
+ * check your manual (at least the PCA-6159 seems to be different -
+ * the manual says wdt_stop is 0x43, not 0x443).
+ * (0x43 is also a write-only control register for the 8254 timer!)
+ */
+
+static int wdt_stop = 0x443;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)");
+
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1<= timeout <=63, 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) ")");
+
+/*
+ * Watchdog Operations
+ */
+
+static void advwdt_ping(void)
+{
+ /* Write a watchdog value */
+ outb_p(timeout, wdt_start);
+}
+
+static void advwdt_disable(void)
+{
+ inb_p(wdt_stop);
+}
+
+static int advwdt_set_heartbeat(int t)
+{
+ if (t < 1 || t > 63)
+ return -EINVAL;
+ timeout = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t advwdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ adv_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ adv_expect_close = 42;
+ }
+ }
+ advwdt_ping();
+ }
+ return count;
+}
+
+static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int new_timeout;
+ 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 = WATCHDOG_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ advwdt_disable();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ advwdt_ping();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ advwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if (advwdt_set_heartbeat(new_timeout))
+ return -EINVAL;
+ advwdt_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int advwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &advwdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+
+ advwdt_ping();
+ return stream_open(inode, file);
+}
+
+static int advwdt_close(struct inode *inode, struct file *file)
+{
+ if (adv_expect_close == 42) {
+ advwdt_disable();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ advwdt_ping();
+ }
+ clear_bit(0, &advwdt_is_open);
+ adv_expect_close = 0;
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations advwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = advwdt_write,
+ .unlocked_ioctl = advwdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = advwdt_open,
+ .release = advwdt_close,
+};
+
+static struct miscdevice advwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &advwdt_fops,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int __init advwdt_probe(struct platform_device *dev)
+{
+ int ret;
+
+ if (wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n",
+ wdt_stop);
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_start);
+ ret = -EIO;
+ goto unreg_stop;
+ }
+
+ /* Check that the heartbeat value is within it's range ;
+ * if not reset to the default */
+ if (advwdt_set_heartbeat(timeout)) {
+ advwdt_set_heartbeat(WATCHDOG_TIMEOUT);
+ pr_info("timeout value must be 1<=x<=63, using %d\n", timeout);
+ }
+
+ ret = misc_register(&advwdt_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_regions;
+ }
+ pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+out:
+ return ret;
+unreg_regions:
+ release_region(wdt_start, 1);
+unreg_stop:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+ goto out;
+}
+
+static int advwdt_remove(struct platform_device *dev)
+{
+ misc_deregister(&advwdt_miscdev);
+ release_region(wdt_start, 1);
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+
+ return 0;
+}
+
+static void advwdt_shutdown(struct platform_device *dev)
+{
+ /* Turn the WDT off if we have a soft shutdown */
+ advwdt_disable();
+}
+
+static struct platform_driver advwdt_driver = {
+ .remove = advwdt_remove,
+ .shutdown = advwdt_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init advwdt_init(void)
+{
+ int err;
+
+ pr_info("WDT driver for Advantech single board computer initialising\n");
+
+ advwdt_platform_device = platform_device_register_simple(DRV_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(advwdt_platform_device))
+ return PTR_ERR(advwdt_platform_device);
+
+ err = platform_driver_probe(&advwdt_driver, advwdt_probe);
+ if (err)
+ goto unreg_platform_device;
+
+ return 0;
+
+unreg_platform_device:
+ platform_device_unregister(advwdt_platform_device);
+ return err;
+}
+
+static void __exit advwdt_exit(void)
+{
+ platform_device_unregister(advwdt_platform_device);
+ platform_driver_unregister(&advwdt_driver);
+ pr_info("Watchdog Module Unloaded\n");
+}
+
+module_init(advwdt_init);
+module_exit(advwdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>");
+MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver");
diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c
new file mode 100644
index 000000000..bfb9a91ca
--- /dev/null
+++ b/drivers/watchdog/alim1535_wdt.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#define WATCHDOG_NAME "ALi_M1535"
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+/* internal variables */
+static unsigned long ali_is_open;
+static char ali_expect_release;
+static struct pci_dev *ali_pci;
+static u32 ali_timeout_bits; /* stores the computed timeout */
+static DEFINE_SPINLOCK(ali_lock); /* Guards the hardware */
+
+/* module parameters */
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (0 < timeout < 18000, 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) ")");
+
+/*
+ * ali_start - start watchdog countdown
+ *
+ * Starts the timer running providing the timer has a counter
+ * configuration set.
+ */
+
+static void ali_start(void)
+{
+ u32 val;
+
+ spin_lock(&ali_lock);
+
+ pci_read_config_dword(ali_pci, 0xCC, &val);
+ val &= ~0x3F; /* Mask count */
+ val |= (1 << 25) | ali_timeout_bits;
+ pci_write_config_dword(ali_pci, 0xCC, val);
+
+ spin_unlock(&ali_lock);
+}
+
+/*
+ * ali_stop - stop the timer countdown
+ *
+ * Stop the ALi watchdog countdown
+ */
+
+static void ali_stop(void)
+{
+ u32 val;
+
+ spin_lock(&ali_lock);
+
+ pci_read_config_dword(ali_pci, 0xCC, &val);
+ val &= ~0x3F; /* Mask count to zero (disabled) */
+ val &= ~(1 << 25); /* and for safety mask the reset enable */
+ pci_write_config_dword(ali_pci, 0xCC, val);
+
+ spin_unlock(&ali_lock);
+}
+
+/*
+ * ali_keepalive - send a keepalive to the watchdog
+ *
+ * Send a keepalive to the timer (actually we restart the timer).
+ */
+
+static void ali_keepalive(void)
+{
+ ali_start();
+}
+
+/*
+ * ali_settimer - compute the timer reload value
+ * @t: time in seconds
+ *
+ * Computes the timeout values needed
+ */
+
+static int ali_settimer(int t)
+{
+ if (t < 0)
+ return -EINVAL;
+ else if (t < 60)
+ ali_timeout_bits = t|(1 << 6);
+ else if (t < 3600)
+ ali_timeout_bits = (t / 60)|(1 << 7);
+ else if (t < 18000)
+ ali_timeout_bits = (t / 300)|(1 << 6)|(1 << 7);
+ else
+ return -EINVAL;
+
+ timeout = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+/*
+ * ali_write - writes to ALi watchdog
+ * @file: file from VFS
+ * @data: user address of data
+ * @len: length of data
+ * @ppos: pointer to the file offset
+ *
+ * Handle a write to the ALi watchdog. Writing to the file pings
+ * the watchdog and resets it. Writing the magic 'V' sequence allows
+ * the next close to turn off the watchdog.
+ */
+
+static ssize_t ali_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the
+ magic character five months ago... */
+ ali_expect_release = 0;
+
+ /* scan to see whether or not we got
+ the magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ ali_expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ ali_start();
+ }
+ return len;
+}
+
+/*
+ * ali_ioctl - handle watchdog ioctls
+ * @file: VFS file pointer
+ * @cmd: ioctl number
+ * @arg: arguments to the ioctl
+ *
+ * Handle the watchdog ioctls supported by the ALi driver. Really
+ * we want an extension to enable irq ack monitoring and the like
+ */
+
+static long ali_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 = 0,
+ .identity = "ALi M1535 WatchDog Timer",
+ };
+
+ 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) {
+ ali_stop();
+ retval = 0;
+ }
+ if (new_options & WDIOS_ENABLECARD) {
+ ali_start();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ ali_keepalive();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if (ali_settimer(new_timeout))
+ return -EINVAL;
+ ali_keepalive();
+ }
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * ali_open - handle open of ali watchdog
+ * @inode: inode from VFS
+ * @file: file from VFS
+ *
+ * Open the ALi watchdog device. Ensure only one person opens it
+ * at a time. Also start the watchdog running.
+ */
+
+static int ali_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &ali_is_open))
+ return -EBUSY;
+
+ /* Activate */
+ ali_start();
+ return stream_open(inode, file);
+}
+
+/*
+ * ali_release - close an ALi watchdog
+ * @inode: inode from VFS
+ * @file: file from VFS
+ *
+ * Close the ALi watchdog device. Actual shutdown of the timer
+ * only occurs if the magic sequence has been set.
+ */
+
+static int ali_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (ali_expect_release == 42)
+ ali_stop();
+ else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ ali_keepalive();
+ }
+ clear_bit(0, &ali_is_open);
+ ali_expect_release = 0;
+ return 0;
+}
+
+/*
+ * ali_notify_sys - System down notifier
+ *
+ * Notifier for system down
+ */
+
+
+static int ali_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ ali_stop(); /* Turn the WDT off */
+ return NOTIFY_DONE;
+}
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+
+static const struct pci_device_id ali_pci_tbl[] __used = {
+ { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,},
+ { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,},
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
+
+/*
+ * ali_find_watchdog - find a 1535 and 7101
+ *
+ * Scans the PCI hardware for a 1535 series bridge and matching 7101
+ * watchdog device. This may be overtight but it is better to be safe
+ */
+
+static int __init ali_find_watchdog(void)
+{
+ struct pci_dev *pdev;
+ u32 wdog;
+
+ /* Check for a 1533/1535 series bridge */
+ pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1535, NULL);
+ if (pdev == NULL)
+ pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1533, NULL);
+ if (pdev == NULL)
+ return -ENODEV;
+ pci_dev_put(pdev);
+
+ /* Check for the a 7101 PMU */
+ pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x7101, NULL);
+ if (pdev == NULL)
+ return -ENODEV;
+
+ if (pci_enable_device(pdev)) {
+ pci_dev_put(pdev);
+ return -EIO;
+ }
+
+ ali_pci = pdev;
+
+ /*
+ * Initialize the timer bits
+ */
+ pci_read_config_dword(pdev, 0xCC, &wdog);
+
+ /* Timer bits */
+ wdog &= ~0x3F;
+ /* Issued events */
+ wdog &= ~((1 << 27)|(1 << 26)|(1 << 25)|(1 << 24));
+ /* No monitor bits */
+ wdog &= ~((1 << 16)|(1 << 13)|(1 << 12)|(1 << 11)|(1 << 10)|(1 << 9));
+
+ pci_write_config_dword(pdev, 0xCC, wdog);
+
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations ali_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ali_write,
+ .unlocked_ioctl = ali_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = ali_open,
+ .release = ali_release,
+};
+
+static struct miscdevice ali_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ali_fops,
+};
+
+static struct notifier_block ali_notifier = {
+ .notifier_call = ali_notify_sys,
+};
+
+/*
+ * watchdog_init - module initialiser
+ *
+ * Scan for a suitable watchdog and if so initialize it. Return an error
+ * if we cannot, the error causes the module to unload
+ */
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ /* Check whether or not the hardware watchdog is there */
+ if (ali_find_watchdog() != 0)
+ return -ENODEV;
+
+ /* Check that the timeout value is within it's range;
+ if not reset to the default */
+ if (timeout < 1 || timeout >= 18000) {
+ timeout = WATCHDOG_TIMEOUT;
+ pr_info("timeout value must be 0 < timeout < 18000, using %d\n",
+ timeout);
+ }
+
+ /* Calculate the watchdog's timeout */
+ ali_settimer(timeout);
+
+ ret = register_reboot_notifier(&ali_notifier);
+ if (ret != 0) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto out;
+ }
+
+ ret = misc_register(&ali_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_reboot;
+ }
+
+ pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+out:
+ return ret;
+unreg_reboot:
+ unregister_reboot_notifier(&ali_notifier);
+ goto out;
+}
+
+/*
+ * watchdog_exit - module de-initialiser
+ *
+ * Called while unloading a successfully installed watchdog module.
+ */
+
+static void __exit watchdog_exit(void)
+{
+ /* Stop the timer before we leave */
+ ali_stop();
+
+ /* Deregister */
+ misc_deregister(&ali_miscdev);
+ unregister_reboot_notifier(&ali_notifier);
+ pci_dev_put(ali_pci);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/alim7101_wdt.c b/drivers/watchdog/alim7101_wdt.c
new file mode 100644
index 000000000..4ff7f5afb
--- /dev/null
+++ b/drivers/watchdog/alim7101_wdt.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALi M7101 PMU Computer Watchdog Timer driver
+ *
+ * Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net>
+ * and the Cobalt kernel WDT timer driver by Tim Hockin
+ * <thockin@cobaltnet.com>
+ *
+ * (c)2002 Steve Hill <steve@navaho.co.uk>
+ *
+ * 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.
+ *
+ * Additions:
+ * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
+ * found on very old cobalt hardware.
+ * -- Mike Waychison <michael.waychison@sun.com>
+ */
+
+#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/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+#define ALI_7101_WDT 0x92
+#define ALI_7101_GPIO 0x7D
+#define ALI_7101_GPIO_O 0x7E
+#define ALI_WDT_ARM 0x01
+
+/*
+ * We're going to use a 1 second timeout.
+ * 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 int use_gpio; /* Use the pic (for a1d revision alim7101) */
+module_param(use_gpio, int, 0);
+MODULE_PARM_DESC(use_gpio,
+ "Use the gpio watchdog (required by old cobalt boards).");
+
+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 struct pci_dev *alim7101_pmu;
+
+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) ")");
+
+/*
+ * 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
+ */
+ char tmp;
+
+ if (time_before(jiffies, next_heartbeat)) {
+ /* Ping the WDT (this is actually a disarm/arm sequence) */
+ pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, tmp | 0x20);
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, tmp & ~0x20);
+ }
+ } else {
+ pr_warn("Heartbeat lost! Will not ping the watchdog\n");
+ }
+ /* Re-set the timer interval */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+ char tmp;
+
+ pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
+ if (writeval == WDT_ENABLE) {
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, tmp & ~0x20);
+ }
+
+ } else {
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu,
+ ALI_7101_GPIO_O, tmp | 0x20);
+ }
+ }
+}
+
+static void wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* We must enable before we kick off the timer in case the timer
+ occurs as we ping it */
+
+ wdt_change(WDT_ENABLE);
+
+ /* Start the timer */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+ pr_info("Watchdog timer is now enabled\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer_sync(&timer);
+ wdt_change(WDT_DISABLE);
+ pr_info("Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /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;
+ }
+ }
+ /* someone wrote to us, we should restart timer */
+ 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;
+ /* 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 {
+ /* wim: shouldn't there be a: del_timer(&timer); */
+ pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ 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 = "ALiM7101",
+ };
+
+ 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;
+ /* arbitrary upper limit */
+ if (new_timeout < 1 || new_timeout > 3600)
+ return -EINVAL;
+ timeout = new_timeout;
+ wdt_keepalive();
+ }
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .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,
+};
+
+static int wdt_restart_handle(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ /*
+ * Cobalt devices have no way of rebooting themselves other
+ * than getting the watchdog to pull reset, so we restart the
+ * watchdog on reboot with no heartbeat.
+ */
+ wdt_change(WDT_ENABLE);
+
+ /* loop until the watchdog fires */
+ while (true)
+ ;
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block wdt_restart_handler = {
+ .notifier_call = wdt_restart_handle,
+ .priority = 128,
+};
+
+/*
+ * 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 alim7101_wdt_unload(void)
+{
+ wdt_turnoff();
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+ unregister_restart_handler(&wdt_restart_handler);
+ pci_dev_put(alim7101_pmu);
+}
+
+static int __init alim7101_wdt_init(void)
+{
+ int rc = -EBUSY;
+ struct pci_dev *ali1543_south;
+ char tmp;
+
+ pr_info("Steve Hill <steve@navaho.co.uk>\n");
+ alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,
+ NULL);
+ if (!alim7101_pmu) {
+ pr_info("ALi M7101 PMU not present - WDT not set\n");
+ return -EBUSY;
+ }
+
+ /* Set the WDT in the PMU to 1 second */
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
+
+ ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533,
+ NULL);
+ if (!ali1543_south) {
+ pr_info("ALi 1543 South-Bridge not present - WDT not set\n");
+ goto err_out;
+ }
+ pci_read_config_byte(ali1543_south, 0x5e, &tmp);
+ pci_dev_put(ali1543_south);
+ if ((tmp & 0x1e) == 0x00) {
+ if (!use_gpio) {
+ pr_info("Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n");
+ goto err_out;
+ }
+ nowayout = 1;
+ } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
+ pr_info("ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
+ goto err_out;
+ }
+
+ if (timeout < 1 || timeout > 3600) {
+ /* arbitrary upper limit */
+ timeout = WATCHDOG_TIMEOUT;
+ pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
+ timeout);
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out;
+ }
+
+ rc = register_restart_handler(&wdt_restart_handler);
+ if (rc) {
+ pr_err("cannot register restart handler (err=%d)\n", rc);
+ goto err_out_reboot;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_restart;
+ }
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ pr_info("WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+ return 0;
+
+err_out_restart:
+ unregister_restart_handler(&wdt_restart_handler);
+err_out_reboot:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out:
+ pci_dev_put(alim7101_pmu);
+ return rc;
+}
+
+module_init(alim7101_wdt_init);
+module_exit(alim7101_wdt_unload);
+
+static const struct pci_device_id alim7101_pci_tbl[] __used = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl);
+
+MODULE_AUTHOR("Steve Hill");
+MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c
new file mode 100644
index 000000000..16aca21f1
--- /dev/null
+++ b/drivers/watchdog/apple_wdt.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SoC Watchdog driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+/*
+ * Apple Watchdog MMIO registers
+ *
+ * This HW block has three separate watchdogs. WD0 resets the machine
+ * to recovery mode and is not very useful for us. WD1 and WD2 trigger a normal
+ * machine reset. WD0 additionally supports a configurable interrupt.
+ * This information can be used to implement pretimeout support at a later time.
+ *
+ * APPLE_WDT_WDx_CUR_TIME is a simple counter incremented for each tick of the
+ * reference clock. It can also be overwritten to any value.
+ * Whenever APPLE_WDT_CTRL_RESET_EN is set in APPLE_WDT_WDx_CTRL and
+ * APPLE_WDT_WDx_CUR_TIME >= APPLE_WDT_WDx_BITE_TIME the entire machine is
+ * reset.
+ * Whenever APPLE_WDT_CTRL_IRQ_EN is set and APPLE_WDTx_WD1_CUR_TIME >=
+ * APPLE_WDTx_WD1_BARK_TIME an interrupt is triggered and
+ * APPLE_WDT_CTRL_IRQ_STATUS is set. The interrupt can be cleared by writing
+ * 1 to APPLE_WDT_CTRL_IRQ_STATUS.
+ */
+#define APPLE_WDT_WD0_CUR_TIME 0x00
+#define APPLE_WDT_WD0_BITE_TIME 0x04
+#define APPLE_WDT_WD0_BARK_TIME 0x08
+#define APPLE_WDT_WD0_CTRL 0x0c
+
+#define APPLE_WDT_WD1_CUR_TIME 0x10
+#define APPLE_WDT_WD1_BITE_TIME 0x14
+#define APPLE_WDT_WD1_CTRL 0x1c
+
+#define APPLE_WDT_WD2_CUR_TIME 0x20
+#define APPLE_WDT_WD2_BITE_TIME 0x24
+#define APPLE_WDT_WD2_CTRL 0x2c
+
+#define APPLE_WDT_CTRL_IRQ_EN BIT(0)
+#define APPLE_WDT_CTRL_IRQ_STATUS BIT(1)
+#define APPLE_WDT_CTRL_RESET_EN BIT(2)
+
+#define APPLE_WDT_TIMEOUT_DEFAULT 30
+
+struct apple_wdt {
+ struct watchdog_device wdd;
+ void __iomem *regs;
+ unsigned long clk_rate;
+};
+
+static struct apple_wdt *to_apple_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct apple_wdt, wdd);
+}
+
+static int apple_wdt_start(struct watchdog_device *wdd)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+ writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);
+
+ return 0;
+}
+
+static int apple_wdt_stop(struct watchdog_device *wdd)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CTRL);
+
+ return 0;
+}
+
+static int apple_wdt_ping(struct watchdog_device *wdd)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+
+ return 0;
+}
+
+static int apple_wdt_set_timeout(struct watchdog_device *wdd, unsigned int s)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+ writel_relaxed(wdt->clk_rate * s, wdt->regs + APPLE_WDT_WD1_BITE_TIME);
+
+ wdd->timeout = s;
+
+ return 0;
+}
+
+static unsigned int apple_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+ u32 cur_time, reset_time;
+
+ cur_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+ reset_time = readl_relaxed(wdt->regs + APPLE_WDT_WD1_BITE_TIME);
+
+ return (reset_time - cur_time) / wdt->clk_rate;
+}
+
+static int apple_wdt_restart(struct watchdog_device *wdd, unsigned long mode,
+ void *cmd)
+{
+ struct apple_wdt *wdt = to_apple_wdt(wdd);
+
+ writel_relaxed(APPLE_WDT_CTRL_RESET_EN, wdt->regs + APPLE_WDT_WD1_CTRL);
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_BITE_TIME);
+ writel_relaxed(0, wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+
+ /*
+ * Flush writes and then wait for the SoC to reset. Even though the
+ * reset is queued almost immediately experiments have shown that it
+ * can take up to ~20-25ms until the SoC is actually reset. Just wait
+ * 50ms here to be safe.
+ */
+ (void)readl_relaxed(wdt->regs + APPLE_WDT_WD1_CUR_TIME);
+ mdelay(50);
+
+ return 0;
+}
+
+static void apple_wdt_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static struct watchdog_ops apple_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = apple_wdt_start,
+ .stop = apple_wdt_stop,
+ .ping = apple_wdt_ping,
+ .set_timeout = apple_wdt_set_timeout,
+ .get_timeleft = apple_wdt_get_timeleft,
+ .restart = apple_wdt_restart,
+};
+
+static struct watchdog_info apple_wdt_info = {
+ .identity = "Apple SoC Watchdog",
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+};
+
+static int apple_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_wdt *wdt;
+ struct clk *clk;
+ u32 wdt_ctrl;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->regs))
+ return PTR_ERR(wdt->regs);
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, apple_wdt_clk_disable_unprepare,
+ clk);
+ if (ret)
+ return ret;
+
+ wdt->clk_rate = clk_get_rate(clk);
+ if (!wdt->clk_rate)
+ return -EINVAL;
+
+ wdt->wdd.ops = &apple_wdt_ops;
+ wdt->wdd.info = &apple_wdt_info;
+ wdt->wdd.max_timeout = U32_MAX / wdt->clk_rate;
+ wdt->wdd.timeout = APPLE_WDT_TIMEOUT_DEFAULT;
+
+ wdt_ctrl = readl_relaxed(wdt->regs + APPLE_WDT_WD1_CTRL);
+ if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN)
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+
+ watchdog_init_timeout(&wdt->wdd, 0, dev);
+ apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
+ watchdog_stop_on_unregister(&wdt->wdd);
+ watchdog_set_restart_priority(&wdt->wdd, 128);
+
+ return devm_watchdog_register_device(dev, &wdt->wdd);
+}
+
+static const struct of_device_id apple_wdt_of_match[] = {
+ { .compatible = "apple,wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apple_wdt_of_match);
+
+static struct platform_driver apple_wdt_driver = {
+ .driver = {
+ .name = "apple-watchdog",
+ .of_match_table = apple_wdt_of_match,
+ },
+ .probe = apple_wdt_probe,
+};
+module_platform_driver(apple_wdt_driver);
+
+MODULE_DESCRIPTION("Apple SoC watchdog driver");
+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/watchdog/ar7_wdt.c b/drivers/watchdog/ar7_wdt.c
new file mode 100644
index 000000000..743e171d9
--- /dev/null
+++ b/drivers/watchdog/ar7_wdt.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/watchdog/ar7_wdt.c
+ *
+ * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
+ * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
+ *
+ * Some code taken from:
+ * National Semiconductor SCx200 Watchdog support
+ * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/clk.h>
+
+#include <asm/addrspace.h>
+#include <asm/mach-ar7/ar7.h>
+
+#define LONGNAME "TI AR7 Watchdog Timer"
+
+MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
+MODULE_DESCRIPTION(LONGNAME);
+MODULE_LICENSE("GPL");
+
+static int margin = 60;
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+#define READ_REG(x) readl((void __iomem *)&(x))
+#define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
+
+struct ar7_wdt {
+ u32 kick_lock;
+ u32 kick;
+ u32 change_lock;
+ u32 change;
+ u32 disable_lock;
+ u32 disable;
+ u32 prescale_lock;
+ u32 prescale;
+};
+
+static unsigned long wdt_is_open;
+static unsigned expect_close;
+static DEFINE_SPINLOCK(wdt_lock);
+
+/* XXX currently fixed, allows max margin ~68.72 secs */
+#define prescale_value 0xffff
+
+/* Pointer to the remapped WDT IO space */
+static struct ar7_wdt *ar7_wdt;
+
+static struct clk *vbus_clk;
+
+static void ar7_wdt_kick(u32 value)
+{
+ WRITE_REG(ar7_wdt->kick_lock, 0x5555);
+ if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
+ WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
+ if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
+ WRITE_REG(ar7_wdt->kick, value);
+ return;
+ }
+ }
+ pr_err("failed to unlock WDT kick reg\n");
+}
+
+static void ar7_wdt_prescale(u32 value)
+{
+ WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
+ if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
+ WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
+ if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
+ WRITE_REG(ar7_wdt->prescale, value);
+ return;
+ }
+ }
+ pr_err("failed to unlock WDT prescale reg\n");
+}
+
+static void ar7_wdt_change(u32 value)
+{
+ WRITE_REG(ar7_wdt->change_lock, 0x6666);
+ if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
+ WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
+ if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
+ WRITE_REG(ar7_wdt->change, value);
+ return;
+ }
+ }
+ pr_err("failed to unlock WDT change reg\n");
+}
+
+static void ar7_wdt_disable(u32 value)
+{
+ WRITE_REG(ar7_wdt->disable_lock, 0x7777);
+ if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
+ WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
+ if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
+ WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
+ if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
+ WRITE_REG(ar7_wdt->disable, value);
+ return;
+ }
+ }
+ }
+ pr_err("failed to unlock WDT disable reg\n");
+}
+
+static void ar7_wdt_update_margin(int new_margin)
+{
+ u32 change;
+ u32 vbus_rate;
+
+ vbus_rate = clk_get_rate(vbus_clk);
+ change = new_margin * (vbus_rate / prescale_value);
+ if (change < 1)
+ change = 1;
+ if (change > 0xffff)
+ change = 0xffff;
+ ar7_wdt_change(change);
+ margin = change * prescale_value / vbus_rate;
+ pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
+ margin, prescale_value, change, vbus_rate);
+}
+
+static void ar7_wdt_enable_wdt(void)
+{
+ pr_debug("enabling watchdog timer\n");
+ ar7_wdt_disable(1);
+ ar7_wdt_kick(1);
+}
+
+static void ar7_wdt_disable_wdt(void)
+{
+ pr_debug("disabling watchdog timer\n");
+ ar7_wdt_disable(0);
+}
+
+static int ar7_wdt_open(struct inode *inode, struct file *file)
+{
+ /* only allow one at a time */
+ if (test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ ar7_wdt_enable_wdt();
+ expect_close = 0;
+
+ return stream_open(inode, file);
+}
+
+static int ar7_wdt_release(struct inode *inode, struct file *file)
+{
+ if (!expect_close)
+ pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
+ else if (!nowayout)
+ ar7_wdt_disable_wdt();
+ clear_bit(0, &wdt_is_open);
+ return 0;
+}
+
+static ssize_t ar7_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ /* check for a magic close character */
+ if (len) {
+ size_t i;
+
+ spin_lock(&wdt_lock);
+ ar7_wdt_kick(1);
+ spin_unlock(&wdt_lock);
+
+ expect_close = 0;
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 1;
+ }
+
+ }
+ return len;
+}
+
+static long ar7_wdt_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ static const struct watchdog_info ident = {
+ .identity = LONGNAME,
+ .firmware_version = 1,
+ .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE),
+ };
+ int new_margin;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, (int *)arg))
+ return -EFAULT;
+ return 0;
+ case WDIOC_KEEPALIVE:
+ ar7_wdt_kick(1);
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, (int *)arg))
+ return -EFAULT;
+ if (new_margin < 1)
+ return -EINVAL;
+
+ spin_lock(&wdt_lock);
+ ar7_wdt_update_margin(new_margin);
+ ar7_wdt_kick(1);
+ spin_unlock(&wdt_lock);
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ if (put_user(margin, (int *)arg))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations ar7_wdt_fops = {
+ .owner = THIS_MODULE,
+ .write = ar7_wdt_write,
+ .unlocked_ioctl = ar7_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = ar7_wdt_open,
+ .release = ar7_wdt_release,
+ .llseek = no_llseek,
+};
+
+static struct miscdevice ar7_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ar7_wdt_fops,
+};
+
+static int ar7_wdt_probe(struct platform_device *pdev)
+{
+ int rc;
+
+ ar7_wdt = devm_platform_ioremap_resource_byname(pdev, "regs");
+ if (IS_ERR(ar7_wdt))
+ return PTR_ERR(ar7_wdt);
+
+ vbus_clk = clk_get(NULL, "vbus");
+ if (IS_ERR(vbus_clk)) {
+ pr_err("could not get vbus clock\n");
+ return PTR_ERR(vbus_clk);
+ }
+
+ ar7_wdt_disable_wdt();
+ ar7_wdt_prescale(prescale_value);
+ ar7_wdt_update_margin(margin);
+
+ rc = misc_register(&ar7_wdt_miscdev);
+ if (rc) {
+ pr_err("unable to register misc device\n");
+ goto out;
+ }
+ return 0;
+
+out:
+ clk_put(vbus_clk);
+ vbus_clk = NULL;
+ return rc;
+}
+
+static int ar7_wdt_remove(struct platform_device *pdev)
+{
+ misc_deregister(&ar7_wdt_miscdev);
+ clk_put(vbus_clk);
+ vbus_clk = NULL;
+ return 0;
+}
+
+static void ar7_wdt_shutdown(struct platform_device *pdev)
+{
+ if (!nowayout)
+ ar7_wdt_disable_wdt();
+}
+
+static struct platform_driver ar7_wdt_driver = {
+ .probe = ar7_wdt_probe,
+ .remove = ar7_wdt_remove,
+ .shutdown = ar7_wdt_shutdown,
+ .driver = {
+ .name = "ar7_wdt",
+ },
+};
+
+module_platform_driver(ar7_wdt_driver);
diff --git a/drivers/watchdog/arm_smc_wdt.c b/drivers/watchdog/arm_smc_wdt.c
new file mode 100644
index 000000000..8f3d0c3a0
--- /dev/null
+++ b/drivers/watchdog/arm_smc_wdt.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ARM Secure Monitor Call watchdog driver
+ *
+ * Copyright 2020 Google LLC.
+ * Julius Werner <jwerner@chromium.org>
+ * Based on mtk_wdt.c
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <uapi/linux/psci.h>
+
+#define DRV_NAME "arm_smc_wdt"
+#define DRV_VERSION "1.0"
+
+enum smcwd_call {
+ SMCWD_INIT = 0,
+ SMCWD_SET_TIMEOUT = 1,
+ SMCWD_ENABLE = 2,
+ SMCWD_PET = 3,
+ SMCWD_GET_TIMELEFT = 4,
+};
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+static int smcwd_call(struct watchdog_device *wdd, enum smcwd_call call,
+ unsigned long arg, struct arm_smccc_res *res)
+{
+ struct arm_smccc_res local_res;
+
+ if (!res)
+ res = &local_res;
+
+ arm_smccc_smc((u32)(uintptr_t)watchdog_get_drvdata(wdd), call, arg, 0,
+ 0, 0, 0, 0, res);
+
+ if (res->a0 == PSCI_RET_NOT_SUPPORTED)
+ return -ENODEV;
+ if (res->a0 == PSCI_RET_INVALID_PARAMS)
+ return -EINVAL;
+ if (res->a0 != PSCI_RET_SUCCESS)
+ return -EIO;
+ return 0;
+}
+
+static int smcwd_ping(struct watchdog_device *wdd)
+{
+ return smcwd_call(wdd, SMCWD_PET, 0, NULL);
+}
+
+static unsigned int smcwd_get_timeleft(struct watchdog_device *wdd)
+{
+ struct arm_smccc_res res;
+
+ smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, &res);
+ if (res.a0)
+ return 0;
+ return res.a1;
+}
+
+static int smcwd_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ int res;
+
+ res = smcwd_call(wdd, SMCWD_SET_TIMEOUT, timeout, NULL);
+ if (!res)
+ wdd->timeout = timeout;
+ return res;
+}
+
+static int smcwd_stop(struct watchdog_device *wdd)
+{
+ return smcwd_call(wdd, SMCWD_ENABLE, 0, NULL);
+}
+
+static int smcwd_start(struct watchdog_device *wdd)
+{
+ return smcwd_call(wdd, SMCWD_ENABLE, 1, NULL);
+}
+
+static const struct watchdog_info smcwd_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops smcwd_ops = {
+ .start = smcwd_start,
+ .stop = smcwd_stop,
+ .ping = smcwd_ping,
+ .set_timeout = smcwd_set_timeout,
+};
+
+static const struct watchdog_ops smcwd_timeleft_ops = {
+ .start = smcwd_start,
+ .stop = smcwd_stop,
+ .ping = smcwd_ping,
+ .set_timeout = smcwd_set_timeout,
+ .get_timeleft = smcwd_get_timeleft,
+};
+
+static int smcwd_probe(struct platform_device *pdev)
+{
+ struct watchdog_device *wdd;
+ int err;
+ struct arm_smccc_res res;
+ u32 smc_func_id;
+
+ wdd = devm_kzalloc(&pdev->dev, sizeof(*wdd), GFP_KERNEL);
+ if (!wdd)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, wdd);
+
+ if (of_property_read_u32(pdev->dev.of_node, "arm,smc-id",
+ &smc_func_id))
+ smc_func_id = 0x82003D06;
+ watchdog_set_drvdata(wdd, (void *)(uintptr_t)smc_func_id);
+
+ err = smcwd_call(wdd, SMCWD_INIT, 0, &res);
+ if (err < 0)
+ return err;
+
+ wdd->info = &smcwd_info;
+ /* get_timeleft is optional */
+ if (smcwd_call(wdd, SMCWD_GET_TIMELEFT, 0, NULL))
+ wdd->ops = &smcwd_ops;
+ else
+ wdd->ops = &smcwd_timeleft_ops;
+ wdd->timeout = res.a2;
+ wdd->max_timeout = res.a2;
+ wdd->min_timeout = res.a1;
+ wdd->parent = &pdev->dev;
+
+ watchdog_stop_on_reboot(wdd);
+ watchdog_stop_on_unregister(wdd);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, timeout, &pdev->dev);
+ err = smcwd_set_timeout(wdd, wdd->timeout);
+ if (err)
+ return err;
+
+ err = devm_watchdog_register_device(&pdev->dev, wdd);
+ if (err)
+ return err;
+
+ dev_info(&pdev->dev,
+ "Watchdog registered (timeout=%d sec, nowayout=%d)\n",
+ wdd->timeout, nowayout);
+
+ return 0;
+}
+
+static const struct of_device_id smcwd_dt_ids[] = {
+ { .compatible = "arm,smc-wdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, smcwd_dt_ids);
+
+static struct platform_driver smcwd_driver = {
+ .probe = smcwd_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = smcwd_dt_ids,
+ },
+};
+
+module_platform_driver(smcwd_driver);
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Julius Werner <jwerner@chromium.org>");
+MODULE_DESCRIPTION("ARM Secure Monitor Call Watchdog Driver");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/watchdog/armada_37xx_wdt.c b/drivers/watchdog/armada_37xx_wdt.c
new file mode 100644
index 000000000..ac9fed1ef
--- /dev/null
+++ b/drivers/watchdog/armada_37xx_wdt.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Marvell Armada 37xx SoCs
+ *
+ * Author: Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+/*
+ * There are four counters that can be used for watchdog on Armada 37xx.
+ * The addresses for counter control registers are register base plus ID*0x10,
+ * where ID is 0, 1, 2 or 3.
+ *
+ * In this driver we use IDs 0 and 1. Counter ID 1 is used as watchdog counter,
+ * while counter ID 0 is used to implement pinging the watchdog: counter ID 1 is
+ * set to restart counting from initial value on counter ID 0 end count event.
+ * Pinging is done by forcing immediate end count event on counter ID 0.
+ * If only one counter was used, pinging would have to be implemented by
+ * disabling and enabling the counter, leaving the system in a vulnerable state
+ * for a (really) short period of time.
+ *
+ * Counters ID 2 and 3 are enabled by default even before U-Boot loads,
+ * therefore this driver does not provide a way to use them, eg. by setting a
+ * property in device tree.
+ */
+
+#define CNTR_ID_RETRIGGER 0
+#define CNTR_ID_WDOG 1
+
+/* relative to cpu_misc */
+#define WDT_TIMER_SELECT 0x64
+#define WDT_TIMER_SELECT_MASK 0xf
+#define WDT_TIMER_SELECT_VAL BIT(CNTR_ID_WDOG)
+
+/* relative to reg */
+#define CNTR_CTRL(id) ((id) * 0x10)
+#define CNTR_CTRL_ENABLE 0x0001
+#define CNTR_CTRL_ACTIVE 0x0002
+#define CNTR_CTRL_MODE_MASK 0x000c
+#define CNTR_CTRL_MODE_ONESHOT 0x0000
+#define CNTR_CTRL_MODE_HWSIG 0x000c
+#define CNTR_CTRL_TRIG_SRC_MASK 0x00f0
+#define CNTR_CTRL_TRIG_SRC_PREV_CNTR 0x0050
+#define CNTR_CTRL_PRESCALE_MASK 0xff00
+#define CNTR_CTRL_PRESCALE_MIN 2
+#define CNTR_CTRL_PRESCALE_SHIFT 8
+
+#define CNTR_COUNT_LOW(id) (CNTR_CTRL(id) + 0x4)
+#define CNTR_COUNT_HIGH(id) (CNTR_CTRL(id) + 0x8)
+
+#define WATCHDOG_TIMEOUT 120
+
+static unsigned int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct armada_37xx_watchdog {
+ struct watchdog_device wdt;
+ struct regmap *cpu_misc;
+ void __iomem *reg;
+ u64 timeout; /* in clock ticks */
+ unsigned long clk_rate;
+ struct clk *clk;
+};
+
+static u64 get_counter_value(struct armada_37xx_watchdog *dev, int id)
+{
+ u64 val;
+
+ /*
+ * when low is read, high is latched into flip-flops so that it can be
+ * read consistently without using software debouncing
+ */
+ val = readl(dev->reg + CNTR_COUNT_LOW(id));
+ val |= ((u64)readl(dev->reg + CNTR_COUNT_HIGH(id))) << 32;
+
+ return val;
+}
+
+static void set_counter_value(struct armada_37xx_watchdog *dev, int id, u64 val)
+{
+ writel(val & 0xffffffff, dev->reg + CNTR_COUNT_LOW(id));
+ writel(val >> 32, dev->reg + CNTR_COUNT_HIGH(id));
+}
+
+static void counter_enable(struct armada_37xx_watchdog *dev, int id)
+{
+ u32 reg;
+
+ reg = readl(dev->reg + CNTR_CTRL(id));
+ reg |= CNTR_CTRL_ENABLE;
+ writel(reg, dev->reg + CNTR_CTRL(id));
+}
+
+static void counter_disable(struct armada_37xx_watchdog *dev, int id)
+{
+ u32 reg;
+
+ reg = readl(dev->reg + CNTR_CTRL(id));
+ reg &= ~CNTR_CTRL_ENABLE;
+ writel(reg, dev->reg + CNTR_CTRL(id));
+}
+
+static void init_counter(struct armada_37xx_watchdog *dev, int id, u32 mode,
+ u32 trig_src)
+{
+ u32 reg;
+
+ reg = readl(dev->reg + CNTR_CTRL(id));
+
+ reg &= ~(CNTR_CTRL_MODE_MASK | CNTR_CTRL_PRESCALE_MASK |
+ CNTR_CTRL_TRIG_SRC_MASK);
+
+ /* set mode */
+ reg |= mode & CNTR_CTRL_MODE_MASK;
+
+ /* set prescaler to the min value */
+ reg |= CNTR_CTRL_PRESCALE_MIN << CNTR_CTRL_PRESCALE_SHIFT;
+
+ /* set trigger source */
+ reg |= trig_src & CNTR_CTRL_TRIG_SRC_MASK;
+
+ writel(reg, dev->reg + CNTR_CTRL(id));
+}
+
+static int armada_37xx_wdt_ping(struct watchdog_device *wdt)
+{
+ struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt);
+
+ /* counter 1 is retriggered by forcing end count on counter 0 */
+ counter_disable(dev, CNTR_ID_RETRIGGER);
+ counter_enable(dev, CNTR_ID_RETRIGGER);
+
+ return 0;
+}
+
+static unsigned int armada_37xx_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+ struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt);
+ u64 res;
+
+ res = get_counter_value(dev, CNTR_ID_WDOG) * CNTR_CTRL_PRESCALE_MIN;
+ do_div(res, dev->clk_rate);
+
+ return res;
+}
+
+static int armada_37xx_wdt_set_timeout(struct watchdog_device *wdt,
+ unsigned int timeout)
+{
+ struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt);
+
+ wdt->timeout = timeout;
+
+ /*
+ * Compute the timeout in clock rate. We use smallest possible
+ * prescaler, which divides the clock rate by 2
+ * (CNTR_CTRL_PRESCALE_MIN).
+ */
+ dev->timeout = (u64)dev->clk_rate * timeout;
+ do_div(dev->timeout, CNTR_CTRL_PRESCALE_MIN);
+
+ set_counter_value(dev, CNTR_ID_WDOG, dev->timeout);
+
+ return 0;
+}
+
+static bool armada_37xx_wdt_is_running(struct armada_37xx_watchdog *dev)
+{
+ u32 reg;
+
+ regmap_read(dev->cpu_misc, WDT_TIMER_SELECT, &reg);
+ if ((reg & WDT_TIMER_SELECT_MASK) != WDT_TIMER_SELECT_VAL)
+ return false;
+
+ reg = readl(dev->reg + CNTR_CTRL(CNTR_ID_WDOG));
+ return !!(reg & CNTR_CTRL_ACTIVE);
+}
+
+static int armada_37xx_wdt_start(struct watchdog_device *wdt)
+{
+ struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt);
+
+ /* select counter 1 as watchdog counter */
+ regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, WDT_TIMER_SELECT_VAL);
+
+ /* init counter 0 as retrigger counter for counter 1 */
+ init_counter(dev, CNTR_ID_RETRIGGER, CNTR_CTRL_MODE_ONESHOT, 0);
+ set_counter_value(dev, CNTR_ID_RETRIGGER, 0);
+
+ /* init counter 1 to be retriggerable by counter 0 end count */
+ init_counter(dev, CNTR_ID_WDOG, CNTR_CTRL_MODE_HWSIG,
+ CNTR_CTRL_TRIG_SRC_PREV_CNTR);
+ set_counter_value(dev, CNTR_ID_WDOG, dev->timeout);
+
+ /* enable counter 1 */
+ counter_enable(dev, CNTR_ID_WDOG);
+
+ /* start counter 1 by forcing immediate end count on counter 0 */
+ counter_enable(dev, CNTR_ID_RETRIGGER);
+
+ return 0;
+}
+
+static int armada_37xx_wdt_stop(struct watchdog_device *wdt)
+{
+ struct armada_37xx_watchdog *dev = watchdog_get_drvdata(wdt);
+
+ counter_disable(dev, CNTR_ID_WDOG);
+ counter_disable(dev, CNTR_ID_RETRIGGER);
+ regmap_write(dev->cpu_misc, WDT_TIMER_SELECT, 0);
+
+ return 0;
+}
+
+static const struct watchdog_info armada_37xx_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Armada 37xx Watchdog",
+};
+
+static const struct watchdog_ops armada_37xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = armada_37xx_wdt_start,
+ .stop = armada_37xx_wdt_stop,
+ .ping = armada_37xx_wdt_ping,
+ .set_timeout = armada_37xx_wdt_set_timeout,
+ .get_timeleft = armada_37xx_wdt_get_timeleft,
+};
+
+static void armada_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int armada_37xx_wdt_probe(struct platform_device *pdev)
+{
+ struct armada_37xx_watchdog *dev;
+ struct resource *res;
+ struct regmap *regmap;
+ int ret;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(struct armada_37xx_watchdog),
+ GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->wdt.info = &armada_37xx_wdt_info;
+ dev->wdt.ops = &armada_37xx_wdt_ops;
+
+ regmap = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
+ "marvell,system-controller");
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+ dev->cpu_misc = regmap;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+ dev->reg = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (!dev->reg)
+ return -ENOMEM;
+
+ /* init clock */
+ dev->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+
+ ret = clk_prepare_enable(dev->clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(&pdev->dev,
+ armada_clk_disable_unprepare, dev->clk);
+ if (ret)
+ return ret;
+
+ dev->clk_rate = clk_get_rate(dev->clk);
+ if (!dev->clk_rate)
+ return -EINVAL;
+
+ /*
+ * Since the timeout in seconds is given as 32 bit unsigned int, and
+ * the counters hold 64 bit values, even after multiplication by clock
+ * rate the counter can hold timeout of UINT_MAX seconds.
+ */
+ dev->wdt.min_timeout = 1;
+ dev->wdt.max_timeout = UINT_MAX;
+ dev->wdt.parent = &pdev->dev;
+
+ /* default value, possibly override by module parameter or dtb */
+ dev->wdt.timeout = WATCHDOG_TIMEOUT;
+ watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev);
+
+ platform_set_drvdata(pdev, &dev->wdt);
+ watchdog_set_drvdata(&dev->wdt, dev);
+
+ armada_37xx_wdt_set_timeout(&dev->wdt, dev->wdt.timeout);
+
+ if (armada_37xx_wdt_is_running(dev))
+ set_bit(WDOG_HW_RUNNING, &dev->wdt.status);
+
+ watchdog_set_nowayout(&dev->wdt, nowayout);
+ watchdog_stop_on_reboot(&dev->wdt);
+ ret = devm_watchdog_register_device(&pdev->dev, &dev->wdt);
+ if (ret)
+ return ret;
+
+ dev_info(&pdev->dev, "Initial timeout %d sec%s\n",
+ dev->wdt.timeout, nowayout ? ", nowayout" : "");
+
+ return 0;
+}
+
+static int __maybe_unused armada_37xx_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdt = dev_get_drvdata(dev);
+
+ return armada_37xx_wdt_stop(wdt);
+}
+
+static int __maybe_unused armada_37xx_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdt))
+ return armada_37xx_wdt_start(wdt);
+
+ return 0;
+}
+
+static const struct dev_pm_ops armada_37xx_wdt_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(armada_37xx_wdt_suspend,
+ armada_37xx_wdt_resume)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id armada_37xx_wdt_match[] = {
+ { .compatible = "marvell,armada-3700-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, armada_37xx_wdt_match);
+#endif
+
+static struct platform_driver armada_37xx_wdt_driver = {
+ .probe = armada_37xx_wdt_probe,
+ .driver = {
+ .name = "armada_37xx_wdt",
+ .of_match_table = of_match_ptr(armada_37xx_wdt_match),
+ .pm = &armada_37xx_wdt_dev_pm_ops,
+ },
+};
+
+module_platform_driver(armada_37xx_wdt_driver);
+
+MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
+MODULE_DESCRIPTION("Armada 37xx CPU Watchdog");
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:armada_37xx_wdt");
diff --git a/drivers/watchdog/asm9260_wdt.c b/drivers/watchdog/asm9260_wdt.c
new file mode 100644
index 000000000..45047e514
--- /dev/null
+++ b/drivers/watchdog/asm9260_wdt.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Watchdog driver for Alphascale ASM9260.
+ *
+ * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/watchdog.h>
+
+#define CLOCK_FREQ 1000000
+
+/* Watchdog Mode register */
+#define HW_WDMOD 0x00
+/* Wake interrupt. Set by HW, can't be cleared. */
+#define BM_MOD_WDINT BIT(3)
+/* This bit set if timeout reached. Cleared by SW. */
+#define BM_MOD_WDTOF BIT(2)
+/* HW Reset on timeout */
+#define BM_MOD_WDRESET BIT(1)
+/* WD enable */
+#define BM_MOD_WDEN BIT(0)
+
+/*
+ * Watchdog Timer Constant register
+ * Minimal value is 0xff, the meaning of this value
+ * depends on used clock: T = WDCLK * (0xff + 1) * 4
+ */
+#define HW_WDTC 0x04
+#define BM_WDTC_MAX(freq) (0x7fffffff / (freq))
+
+/* Watchdog Feed register */
+#define HW_WDFEED 0x08
+
+/* Watchdog Timer Value register */
+#define HW_WDTV 0x0c
+
+#define ASM9260_WDT_DEFAULT_TIMEOUT 30
+
+enum asm9260_wdt_mode {
+ HW_RESET,
+ SW_RESET,
+ DEBUG,
+};
+
+struct asm9260_wdt_priv {
+ struct device *dev;
+ struct watchdog_device wdd;
+ struct clk *clk;
+ struct clk *clk_ahb;
+ struct reset_control *rst;
+
+ void __iomem *iobase;
+ int irq;
+ unsigned long wdt_freq;
+ enum asm9260_wdt_mode mode;
+};
+
+static int asm9260_wdt_feed(struct watchdog_device *wdd)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ iowrite32(0xaa, priv->iobase + HW_WDFEED);
+ iowrite32(0x55, priv->iobase + HW_WDFEED);
+
+ return 0;
+}
+
+static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+ u32 counter;
+
+ counter = ioread32(priv->iobase + HW_WDTV);
+
+ return counter / priv->wdt_freq;
+}
+
+static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+ u32 counter;
+
+ counter = wdd->timeout * priv->wdt_freq;
+
+ iowrite32(counter, priv->iobase + HW_WDTC);
+
+ return 0;
+}
+
+static int asm9260_wdt_enable(struct watchdog_device *wdd)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+ u32 mode = 0;
+
+ if (priv->mode == HW_RESET)
+ mode = BM_MOD_WDRESET;
+
+ iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
+
+ asm9260_wdt_updatetimeout(wdd);
+
+ asm9260_wdt_feed(wdd);
+
+ return 0;
+}
+
+static int asm9260_wdt_disable(struct watchdog_device *wdd)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ /* The only way to disable WD is to reset it. */
+ reset_control_assert(priv->rst);
+ reset_control_deassert(priv->rst);
+
+ return 0;
+}
+
+static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
+{
+ wdd->timeout = to;
+ asm9260_wdt_updatetimeout(wdd);
+
+ return 0;
+}
+
+static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
+{
+ /* init WD if it was not started */
+
+ iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
+
+ iowrite32(0xff, priv->iobase + HW_WDTC);
+ /* first pass correct sequence */
+ asm9260_wdt_feed(&priv->wdd);
+ /*
+ * Then write wrong pattern to the feed to trigger reset
+ * ASAP.
+ */
+ iowrite32(0xff, priv->iobase + HW_WDFEED);
+
+ mdelay(1000);
+}
+
+static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
+{
+ struct asm9260_wdt_priv *priv = devid;
+ u32 stat;
+
+ stat = ioread32(priv->iobase + HW_WDMOD);
+ if (!(stat & BM_MOD_WDINT))
+ return IRQ_NONE;
+
+ if (priv->mode == DEBUG) {
+ dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
+ } else {
+ dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
+ asm9260_wdt_sys_reset(priv);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ asm9260_wdt_sys_reset(priv);
+
+ return 0;
+}
+
+static const struct watchdog_info asm9260_wdt_ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE,
+ .identity = "Alphascale asm9260 Watchdog",
+};
+
+static const struct watchdog_ops asm9260_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = asm9260_wdt_enable,
+ .stop = asm9260_wdt_disable,
+ .get_timeleft = asm9260_wdt_gettimeleft,
+ .ping = asm9260_wdt_feed,
+ .set_timeout = asm9260_wdt_settimeout,
+ .restart = asm9260_restart,
+};
+
+static void asm9260_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
+{
+ int err;
+ unsigned long clk;
+
+ priv->clk = devm_clk_get(priv->dev, "mod");
+ if (IS_ERR(priv->clk)) {
+ dev_err(priv->dev, "Failed to get \"mod\" clk\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ /* configure AHB clock */
+ priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
+ if (IS_ERR(priv->clk_ahb)) {
+ dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
+ return PTR_ERR(priv->clk_ahb);
+ }
+
+ err = clk_prepare_enable(priv->clk_ahb);
+ if (err) {
+ dev_err(priv->dev, "Failed to enable ahb_clk!\n");
+ return err;
+ }
+ err = devm_add_action_or_reset(priv->dev,
+ asm9260_clk_disable_unprepare,
+ priv->clk_ahb);
+ if (err)
+ return err;
+
+ err = clk_set_rate(priv->clk, CLOCK_FREQ);
+ if (err) {
+ dev_err(priv->dev, "Failed to set rate!\n");
+ return err;
+ }
+
+ err = clk_prepare_enable(priv->clk);
+ if (err) {
+ dev_err(priv->dev, "Failed to enable clk!\n");
+ return err;
+ }
+ err = devm_add_action_or_reset(priv->dev,
+ asm9260_clk_disable_unprepare,
+ priv->clk);
+ if (err)
+ return err;
+
+ /* wdt has internal divider */
+ clk = clk_get_rate(priv->clk);
+ if (!clk) {
+ dev_err(priv->dev, "Failed, clk is 0!\n");
+ return -EINVAL;
+ }
+
+ priv->wdt_freq = clk / 2;
+
+ return 0;
+}
+
+static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
+{
+ const char *tmp;
+ int ret;
+
+ /* default mode */
+ priv->mode = HW_RESET;
+
+ ret = of_property_read_string(priv->dev->of_node,
+ "alphascale,mode", &tmp);
+ if (ret < 0)
+ return;
+
+ if (!strcmp(tmp, "hw"))
+ priv->mode = HW_RESET;
+ else if (!strcmp(tmp, "sw"))
+ priv->mode = SW_RESET;
+ else if (!strcmp(tmp, "debug"))
+ priv->mode = DEBUG;
+ else
+ dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
+ tmp);
+}
+
+static int asm9260_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct asm9260_wdt_priv *priv;
+ struct watchdog_device *wdd;
+ int ret;
+ static const char * const mode_name[] = { "hw", "sw", "debug", };
+
+ priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+
+ priv->iobase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->iobase))
+ return PTR_ERR(priv->iobase);
+
+ priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst");
+ if (IS_ERR(priv->rst))
+ return PTR_ERR(priv->rst);
+
+ ret = asm9260_wdt_get_dt_clks(priv);
+ if (ret)
+ return ret;
+
+ wdd = &priv->wdd;
+ wdd->info = &asm9260_wdt_ident;
+ wdd->ops = &asm9260_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
+ wdd->parent = dev;
+
+ watchdog_set_drvdata(wdd, priv);
+
+ /*
+ * If 'timeout-sec' unspecified in devicetree, assume a 30 second
+ * default, unless the max timeout is less than 30 seconds, then use
+ * the max instead.
+ */
+ wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
+ watchdog_init_timeout(wdd, 0, dev);
+
+ asm9260_wdt_get_dt_mode(priv);
+
+ if (priv->mode != HW_RESET)
+ priv->irq = platform_get_irq(pdev, 0);
+
+ if (priv->irq > 0) {
+ /*
+ * Not all supported platforms specify an interrupt for the
+ * watchdog, so let's make it optional.
+ */
+ ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0,
+ pdev->name, priv);
+ if (ret < 0)
+ dev_warn(dev, "failed to request IRQ\n");
+ }
+
+ watchdog_set_restart_priority(wdd, 128);
+
+ watchdog_stop_on_reboot(wdd);
+ watchdog_stop_on_unregister(wdd);
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, priv);
+
+ dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
+ wdd->timeout, mode_name[priv->mode]);
+ return 0;
+}
+
+static const struct of_device_id asm9260_wdt_of_match[] = {
+ { .compatible = "alphascale,asm9260-wdt"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
+
+static struct platform_driver asm9260_wdt_driver = {
+ .driver = {
+ .name = "asm9260-wdt",
+ .of_match_table = asm9260_wdt_of_match,
+ },
+ .probe = asm9260_wdt_probe,
+};
+module_platform_driver(asm9260_wdt_driver);
+
+MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
+MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/aspeed_wdt.c b/drivers/watchdog/aspeed_wdt.c
new file mode 100644
index 000000000..0cff2adfb
--- /dev/null
+++ b/drivers/watchdog/aspeed_wdt.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2016 IBM Corporation
+ *
+ * Joel Stanley <joel@jms.id.au>
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct aspeed_wdt {
+ struct watchdog_device wdd;
+ void __iomem *base;
+ u32 ctrl;
+};
+
+struct aspeed_wdt_config {
+ u32 ext_pulse_width_mask;
+};
+
+static const struct aspeed_wdt_config ast2400_config = {
+ .ext_pulse_width_mask = 0xff,
+};
+
+static const struct aspeed_wdt_config ast2500_config = {
+ .ext_pulse_width_mask = 0xfffff,
+};
+
+static const struct of_device_id aspeed_wdt_of_table[] = {
+ { .compatible = "aspeed,ast2400-wdt", .data = &ast2400_config },
+ { .compatible = "aspeed,ast2500-wdt", .data = &ast2500_config },
+ { .compatible = "aspeed,ast2600-wdt", .data = &ast2500_config },
+ { },
+};
+MODULE_DEVICE_TABLE(of, aspeed_wdt_of_table);
+
+#define WDT_STATUS 0x00
+#define WDT_RELOAD_VALUE 0x04
+#define WDT_RESTART 0x08
+#define WDT_CTRL 0x0C
+#define WDT_CTRL_BOOT_SECONDARY BIT(7)
+#define WDT_CTRL_RESET_MODE_SOC (0x00 << 5)
+#define WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
+#define WDT_CTRL_RESET_MODE_ARM_CPU (0x10 << 5)
+#define WDT_CTRL_1MHZ_CLK BIT(4)
+#define WDT_CTRL_WDT_EXT BIT(3)
+#define WDT_CTRL_WDT_INTR BIT(2)
+#define WDT_CTRL_RESET_SYSTEM BIT(1)
+#define WDT_CTRL_ENABLE BIT(0)
+#define WDT_TIMEOUT_STATUS 0x10
+#define WDT_TIMEOUT_STATUS_BOOT_SECONDARY BIT(1)
+#define WDT_CLEAR_TIMEOUT_STATUS 0x14
+#define WDT_CLEAR_TIMEOUT_AND_BOOT_CODE_SELECTION BIT(0)
+
+/*
+ * WDT_RESET_WIDTH controls the characteristics of the external pulse (if
+ * enabled), specifically:
+ *
+ * * Pulse duration
+ * * Drive mode: push-pull vs open-drain
+ * * Polarity: Active high or active low
+ *
+ * Pulse duration configuration is available on both the AST2400 and AST2500,
+ * though the field changes between SoCs:
+ *
+ * AST2400: Bits 7:0
+ * AST2500: Bits 19:0
+ *
+ * This difference is captured in struct aspeed_wdt_config.
+ *
+ * The AST2500 exposes the drive mode and polarity options, but not in a
+ * regular fashion. For read purposes, bit 31 represents active high or low,
+ * and bit 30 represents push-pull or open-drain. With respect to write, magic
+ * values need to be written to the top byte to change the state of the drive
+ * mode and polarity bits. Any other value written to the top byte has no
+ * effect on the state of the drive mode or polarity bits. However, the pulse
+ * width value must be preserved (as desired) if written.
+ */
+#define WDT_RESET_WIDTH 0x18
+#define WDT_RESET_WIDTH_ACTIVE_HIGH BIT(31)
+#define WDT_ACTIVE_HIGH_MAGIC (0xA5 << 24)
+#define WDT_ACTIVE_LOW_MAGIC (0x5A << 24)
+#define WDT_RESET_WIDTH_PUSH_PULL BIT(30)
+#define WDT_PUSH_PULL_MAGIC (0xA8 << 24)
+#define WDT_OPEN_DRAIN_MAGIC (0x8A << 24)
+
+#define WDT_RESTART_MAGIC 0x4755
+
+/* 32 bits at 1MHz, in milliseconds */
+#define WDT_MAX_TIMEOUT_MS 4294967
+#define WDT_DEFAULT_TIMEOUT 30
+#define WDT_RATE_1MHZ 1000000
+
+static struct aspeed_wdt *to_aspeed_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct aspeed_wdt, wdd);
+}
+
+static void aspeed_wdt_enable(struct aspeed_wdt *wdt, int count)
+{
+ wdt->ctrl |= WDT_CTRL_ENABLE;
+
+ writel(0, wdt->base + WDT_CTRL);
+ writel(count, wdt->base + WDT_RELOAD_VALUE);
+ writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART);
+ writel(wdt->ctrl, wdt->base + WDT_CTRL);
+}
+
+static int aspeed_wdt_start(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = to_aspeed_wdt(wdd);
+
+ aspeed_wdt_enable(wdt, wdd->timeout * WDT_RATE_1MHZ);
+
+ return 0;
+}
+
+static int aspeed_wdt_stop(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = to_aspeed_wdt(wdd);
+
+ wdt->ctrl &= ~WDT_CTRL_ENABLE;
+ writel(wdt->ctrl, wdt->base + WDT_CTRL);
+
+ return 0;
+}
+
+static int aspeed_wdt_ping(struct watchdog_device *wdd)
+{
+ struct aspeed_wdt *wdt = to_aspeed_wdt(wdd);
+
+ writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART);
+
+ return 0;
+}
+
+static int aspeed_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct aspeed_wdt *wdt = to_aspeed_wdt(wdd);
+ u32 actual;
+
+ wdd->timeout = timeout;
+
+ actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000);
+
+ writel(actual * WDT_RATE_1MHZ, wdt->base + WDT_RELOAD_VALUE);
+ writel(WDT_RESTART_MAGIC, wdt->base + WDT_RESTART);
+
+ return 0;
+}
+
+static int aspeed_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct aspeed_wdt *wdt = to_aspeed_wdt(wdd);
+
+ wdt->ctrl &= ~WDT_CTRL_BOOT_SECONDARY;
+ aspeed_wdt_enable(wdt, 128 * WDT_RATE_1MHZ / 1000);
+
+ mdelay(1000);
+
+ return 0;
+}
+
+/* access_cs0 shows if cs0 is accessible, hence the reverted bit */
+static ssize_t access_cs0_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct aspeed_wdt *wdt = dev_get_drvdata(dev);
+ u32 status = readl(wdt->base + WDT_TIMEOUT_STATUS);
+
+ return sysfs_emit(buf, "%u\n",
+ !(status & WDT_TIMEOUT_STATUS_BOOT_SECONDARY));
+}
+
+static ssize_t access_cs0_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct aspeed_wdt *wdt = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (kstrtoul(buf, 10, &val))
+ return -EINVAL;
+
+ if (val)
+ writel(WDT_CLEAR_TIMEOUT_AND_BOOT_CODE_SELECTION,
+ wdt->base + WDT_CLEAR_TIMEOUT_STATUS);
+
+ return size;
+}
+
+/*
+ * This attribute exists only if the system has booted from the alternate
+ * flash with 'alt-boot' option.
+ *
+ * At alternate flash the 'access_cs0' sysfs node provides:
+ * ast2400: a way to get access to the primary SPI flash chip at CS0
+ * after booting from the alternate chip at CS1.
+ * ast2500: a way to restore the normal address mapping from
+ * (CS0->CS1, CS1->CS0) to (CS0->CS0, CS1->CS1).
+ *
+ * Clearing the boot code selection and timeout counter also resets to the
+ * initial state the chip select line mapping. When the SoC is in normal
+ * mapping state (i.e. booted from CS0), clearing those bits does nothing for
+ * both versions of the SoC. For alternate boot mode (booted from CS1 due to
+ * wdt2 expiration) the behavior differs as described above.
+ *
+ * This option can be used with wdt2 (watchdog1) only.
+ */
+static DEVICE_ATTR_RW(access_cs0);
+
+static struct attribute *bswitch_attrs[] = {
+ &dev_attr_access_cs0.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(bswitch);
+
+static const struct watchdog_ops aspeed_wdt_ops = {
+ .start = aspeed_wdt_start,
+ .stop = aspeed_wdt_stop,
+ .ping = aspeed_wdt_ping,
+ .set_timeout = aspeed_wdt_set_timeout,
+ .restart = aspeed_wdt_restart,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info aspeed_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT,
+ .identity = KBUILD_MODNAME,
+};
+
+static int aspeed_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct aspeed_wdt_config *config;
+ const struct of_device_id *ofdid;
+ struct aspeed_wdt *wdt;
+ struct device_node *np;
+ const char *reset_type;
+ u32 duration;
+ u32 status;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->wdd.info = &aspeed_wdt_info;
+ wdt->wdd.ops = &aspeed_wdt_ops;
+ wdt->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS;
+ wdt->wdd.parent = dev;
+
+ wdt->wdd.timeout = WDT_DEFAULT_TIMEOUT;
+ watchdog_init_timeout(&wdt->wdd, 0, dev);
+
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+
+ np = dev->of_node;
+
+ ofdid = of_match_node(aspeed_wdt_of_table, np);
+ if (!ofdid)
+ return -EINVAL;
+ config = ofdid->data;
+
+ /*
+ * On clock rates:
+ * - ast2400 wdt can run at PCLK, or 1MHz
+ * - ast2500 only runs at 1MHz, hard coding bit 4 to 1
+ * - ast2600 always runs at 1MHz
+ *
+ * Set the ast2400 to run at 1MHz as it simplifies the driver.
+ */
+ if (of_device_is_compatible(np, "aspeed,ast2400-wdt"))
+ wdt->ctrl = WDT_CTRL_1MHZ_CLK;
+
+ /*
+ * Control reset on a per-device basis to ensure the
+ * host is not affected by a BMC reboot
+ */
+ ret = of_property_read_string(np, "aspeed,reset-type", &reset_type);
+ if (ret) {
+ wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC | WDT_CTRL_RESET_SYSTEM;
+ } else {
+ if (!strcmp(reset_type, "cpu"))
+ wdt->ctrl |= WDT_CTRL_RESET_MODE_ARM_CPU |
+ WDT_CTRL_RESET_SYSTEM;
+ else if (!strcmp(reset_type, "soc"))
+ wdt->ctrl |= WDT_CTRL_RESET_MODE_SOC |
+ WDT_CTRL_RESET_SYSTEM;
+ else if (!strcmp(reset_type, "system"))
+ wdt->ctrl |= WDT_CTRL_RESET_MODE_FULL_CHIP |
+ WDT_CTRL_RESET_SYSTEM;
+ else if (strcmp(reset_type, "none"))
+ return -EINVAL;
+ }
+ if (of_property_read_bool(np, "aspeed,external-signal"))
+ wdt->ctrl |= WDT_CTRL_WDT_EXT;
+ if (of_property_read_bool(np, "aspeed,alt-boot"))
+ wdt->ctrl |= WDT_CTRL_BOOT_SECONDARY;
+
+ if (readl(wdt->base + WDT_CTRL) & WDT_CTRL_ENABLE) {
+ /*
+ * The watchdog is running, but invoke aspeed_wdt_start() to
+ * write wdt->ctrl to WDT_CTRL to ensure the watchdog's
+ * configuration conforms to the driver's expectations.
+ * Primarily, ensure we're using the 1MHz clock source.
+ */
+ aspeed_wdt_start(&wdt->wdd);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+
+ if ((of_device_is_compatible(np, "aspeed,ast2500-wdt")) ||
+ (of_device_is_compatible(np, "aspeed,ast2600-wdt"))) {
+ u32 reg = readl(wdt->base + WDT_RESET_WIDTH);
+
+ reg &= config->ext_pulse_width_mask;
+ if (of_property_read_bool(np, "aspeed,ext-active-high"))
+ reg |= WDT_ACTIVE_HIGH_MAGIC;
+ else
+ reg |= WDT_ACTIVE_LOW_MAGIC;
+
+ writel(reg, wdt->base + WDT_RESET_WIDTH);
+
+ reg &= config->ext_pulse_width_mask;
+ if (of_property_read_bool(np, "aspeed,ext-push-pull"))
+ reg |= WDT_PUSH_PULL_MAGIC;
+ else
+ reg |= WDT_OPEN_DRAIN_MAGIC;
+
+ writel(reg, wdt->base + WDT_RESET_WIDTH);
+ }
+
+ if (!of_property_read_u32(np, "aspeed,ext-pulse-duration", &duration)) {
+ u32 max_duration = config->ext_pulse_width_mask + 1;
+
+ if (duration == 0 || duration > max_duration) {
+ dev_err(dev, "Invalid pulse duration: %uus\n",
+ duration);
+ duration = max(1U, min(max_duration, duration));
+ dev_info(dev, "Pulse duration set to %uus\n",
+ duration);
+ }
+
+ /*
+ * The watchdog is always configured with a 1MHz source, so
+ * there is no need to scale the microsecond value. However we
+ * need to offset it - from the datasheet:
+ *
+ * "This register decides the asserting duration of wdt_ext and
+ * wdt_rstarm signal. The default value is 0xFF. It means the
+ * default asserting duration of wdt_ext and wdt_rstarm is
+ * 256us."
+ *
+ * This implies a value of 0 gives a 1us pulse.
+ */
+ writel(duration - 1, wdt->base + WDT_RESET_WIDTH);
+ }
+
+ status = readl(wdt->base + WDT_TIMEOUT_STATUS);
+ if (status & WDT_TIMEOUT_STATUS_BOOT_SECONDARY) {
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
+
+ if (of_device_is_compatible(np, "aspeed,ast2400-wdt") ||
+ of_device_is_compatible(np, "aspeed,ast2500-wdt"))
+ wdt->wdd.groups = bswitch_groups;
+ }
+
+ dev_set_drvdata(dev, wdt);
+
+ return devm_watchdog_register_device(dev, &wdt->wdd);
+}
+
+static struct platform_driver aspeed_watchdog_driver = {
+ .probe = aspeed_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = of_match_ptr(aspeed_wdt_of_table),
+ },
+};
+
+static int __init aspeed_wdt_init(void)
+{
+ return platform_driver_register(&aspeed_watchdog_driver);
+}
+arch_initcall(aspeed_wdt_init);
+
+static void __exit aspeed_wdt_exit(void)
+{
+ platform_driver_unregister(&aspeed_watchdog_driver);
+}
+module_exit(aspeed_wdt_exit);
+
+MODULE_DESCRIPTION("Aspeed Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/at91rm9200_wdt.c b/drivers/watchdog/at91rm9200_wdt.c
new file mode 100644
index 000000000..6d751eb81
--- /dev/null
+++ b/drivers/watchdog/at91rm9200_wdt.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Atmel AT91RM9200 (Thunder)
+ *
+ * Copyright (C) 2003 SAN People (Pty) Ltd
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/atmel-st.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#define WDT_DEFAULT_TIME 5 /* seconds */
+#define WDT_MAX_TIME 256 /* seconds */
+
+static int wdt_time = WDT_DEFAULT_TIME;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static struct regmap *regmap_st;
+
+module_param(wdt_time, int, 0);
+MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="
+ __MODULE_STRING(WDT_DEFAULT_TIME) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+#endif
+
+
+static unsigned long at91wdt_busy;
+
+/* ......................................................................... */
+
+static int at91rm9200_restart(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ /*
+ * Perform a hardware reset with the use of the Watchdog timer.
+ */
+ regmap_write(regmap_st, AT91_ST_WDMR,
+ AT91_ST_RSTEN | AT91_ST_EXTEN | 1);
+ regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
+
+ mdelay(2000);
+
+ pr_emerg("Unable to restart system\n");
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block at91rm9200_restart_nb = {
+ .notifier_call = at91rm9200_restart,
+ .priority = 192,
+};
+
+/*
+ * Disable the watchdog.
+ */
+static inline void at91_wdt_stop(void)
+{
+ regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN);
+}
+
+/*
+ * Enable and reset the watchdog.
+ */
+static inline void at91_wdt_start(void)
+{
+ regmap_write(regmap_st, AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN |
+ (((65536 * wdt_time) >> 8) & AT91_ST_WDV));
+ regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
+}
+
+/*
+ * Reload the watchdog timer. (ie, pat the watchdog)
+ */
+static inline void at91_wdt_reload(void)
+{
+ regmap_write(regmap_st, AT91_ST_CR, AT91_ST_WDRST);
+}
+
+/* ......................................................................... */
+
+/*
+ * Watchdog device is opened, and watchdog starts running.
+ */
+static int at91_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &at91wdt_busy))
+ return -EBUSY;
+
+ at91_wdt_start();
+ return stream_open(inode, file);
+}
+
+/*
+ * Close the watchdog device.
+ * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also
+ * disabled.
+ */
+static int at91_wdt_close(struct inode *inode, struct file *file)
+{
+ /* Disable the watchdog when file is closed */
+ if (!nowayout)
+ at91_wdt_stop();
+
+ clear_bit(0, &at91wdt_busy);
+ return 0;
+}
+
+/*
+ * Change the watchdog time interval.
+ */
+static int at91_wdt_settimeout(int new_time)
+{
+ /*
+ * All counting occurs at SLOW_CLOCK / 128 = 256 Hz
+ *
+ * Since WDV is a 16-bit counter, the maximum period is
+ * 65536 / 256 = 256 seconds.
+ */
+ if ((new_time <= 0) || (new_time > WDT_MAX_TIME))
+ return -EINVAL;
+
+ /* Set new watchdog time. It will be used when
+ at91_wdt_start() is called. */
+ wdt_time = new_time;
+ return 0;
+}
+
+static const struct watchdog_info at91_wdt_info = {
+ .identity = "at91 watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+};
+
+/*
+ * Handle commands from user-space.
+ */
+static long at91_wdt_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_value;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &at91_wdt_info,
+ sizeof(at91_wdt_info)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_SETOPTIONS:
+ if (get_user(new_value, p))
+ return -EFAULT;
+ if (new_value & WDIOS_DISABLECARD)
+ at91_wdt_stop();
+ if (new_value & WDIOS_ENABLECARD)
+ at91_wdt_start();
+ return 0;
+ case WDIOC_KEEPALIVE:
+ at91_wdt_reload(); /* pat the watchdog */
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_value, p))
+ return -EFAULT;
+ if (at91_wdt_settimeout(new_value))
+ return -EINVAL;
+ /* Enable new time value */
+ at91_wdt_start();
+ /* Return current value */
+ return put_user(wdt_time, p);
+ case WDIOC_GETTIMEOUT:
+ return put_user(wdt_time, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * Pat the watchdog whenever device is written to.
+ */
+static ssize_t at91_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ at91_wdt_reload(); /* pat the watchdog */
+ return len;
+}
+
+/* ......................................................................... */
+
+static const struct file_operations at91wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = at91_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = at91_wdt_open,
+ .release = at91_wdt_close,
+ .write = at91_wdt_write,
+};
+
+static struct miscdevice at91wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &at91wdt_fops,
+};
+
+static int at91wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device *parent;
+ int res;
+
+ if (at91wdt_miscdev.parent)
+ return -EBUSY;
+ at91wdt_miscdev.parent = &pdev->dev;
+
+ parent = dev->parent;
+ if (!parent) {
+ dev_err(dev, "no parent\n");
+ return -ENODEV;
+ }
+
+ regmap_st = syscon_node_to_regmap(parent->of_node);
+ if (IS_ERR(regmap_st))
+ return -ENODEV;
+
+ res = misc_register(&at91wdt_miscdev);
+ if (res)
+ return res;
+
+ res = register_restart_handler(&at91rm9200_restart_nb);
+ if (res)
+ dev_warn(dev, "failed to register restart handler\n");
+
+ pr_info("AT91 Watchdog Timer enabled (%d seconds%s)\n",
+ wdt_time, nowayout ? ", nowayout" : "");
+ return 0;
+}
+
+static int at91wdt_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int res;
+
+ res = unregister_restart_handler(&at91rm9200_restart_nb);
+ if (res)
+ dev_warn(dev, "failed to unregister restart handler\n");
+
+ misc_deregister(&at91wdt_miscdev);
+ at91wdt_miscdev.parent = NULL;
+
+ return res;
+}
+
+static void at91wdt_shutdown(struct platform_device *pdev)
+{
+ at91_wdt_stop();
+}
+
+#ifdef CONFIG_PM
+
+static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message)
+{
+ at91_wdt_stop();
+ return 0;
+}
+
+static int at91wdt_resume(struct platform_device *pdev)
+{
+ if (at91wdt_busy)
+ at91_wdt_start();
+ return 0;
+}
+
+#else
+#define at91wdt_suspend NULL
+#define at91wdt_resume NULL
+#endif
+
+static const struct of_device_id at91_wdt_dt_ids[] = {
+ { .compatible = "atmel,at91rm9200-wdt" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
+
+static struct platform_driver at91wdt_driver = {
+ .probe = at91wdt_probe,
+ .remove = at91wdt_remove,
+ .shutdown = at91wdt_shutdown,
+ .suspend = at91wdt_suspend,
+ .resume = at91wdt_resume,
+ .driver = {
+ .name = "atmel_st_watchdog",
+ .of_match_table = at91_wdt_dt_ids,
+ },
+};
+
+static int __init at91_wdt_init(void)
+{
+ /* Check that the heartbeat value is within range;
+ if not reset to the default */
+ if (at91_wdt_settimeout(wdt_time)) {
+ at91_wdt_settimeout(WDT_DEFAULT_TIME);
+ pr_info("wdt_time value must be 1 <= wdt_time <= 256, using %d\n",
+ wdt_time);
+ }
+
+ return platform_driver_register(&at91wdt_driver);
+}
+
+static void __exit at91_wdt_exit(void)
+{
+ platform_driver_unregister(&at91wdt_driver);
+}
+
+module_init(at91_wdt_init);
+module_exit(at91_wdt_exit);
+
+MODULE_AUTHOR("Andrew Victor");
+MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_st_watchdog");
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c
new file mode 100644
index 000000000..fed7be246
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Atmel AT91SAM9x processors.
+ *
+ * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr
+ *
+ */
+
+/*
+ * The Watchdog Timer Mode Register can be only written to once. If the
+ * timeout need to be set from Linux, be sure that the bootstrap or the
+ * bootloader doesn't write to this register.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+#include "at91sam9_wdt.h"
+
+#define DRV_NAME "AT91SAM9 Watchdog"
+
+#define wdt_read(wdt, field) \
+ readl_relaxed((wdt)->base + (field))
+#define wdt_write(wtd, field, val) \
+ writel_relaxed((val), (wdt)->base + (field))
+
+/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz,
+ * use this to convert a watchdog
+ * value from/to milliseconds.
+ */
+#define ticks_to_hz_rounddown(t) ((((t) + 1) * HZ) >> 8)
+#define ticks_to_hz_roundup(t) (((((t) + 1) * HZ) + 255) >> 8)
+#define ticks_to_secs(t) (((t) + 1) >> 8)
+#define secs_to_ticks(s) ((s) ? (((s) << 8) - 1) : 0)
+
+#define WDT_MR_RESET 0x3FFF2FFF
+
+/* Watchdog max counter value in ticks */
+#define WDT_COUNTER_MAX_TICKS 0xFFF
+
+/* Watchdog max delta/value in secs */
+#define WDT_COUNTER_MAX_SECS ticks_to_secs(WDT_COUNTER_MAX_TICKS)
+
+/* Hardware timeout in seconds */
+#define WDT_HW_TIMEOUT 2
+
+/* Timer heartbeat (500ms) */
+#define WDT_TIMEOUT (HZ/2)
+
+/* User land timeout */
+#define WDT_HEARTBEAT 15
+static int heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
+ "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
+
+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 to_wdt(wdd) container_of(wdd, struct at91wdt, wdd)
+struct at91wdt {
+ struct watchdog_device wdd;
+ void __iomem *base;
+ unsigned long next_heartbeat; /* the next_heartbeat for the timer */
+ struct timer_list timer; /* The timer that pings the watchdog */
+ u32 mr;
+ u32 mr_mask;
+ unsigned long heartbeat; /* WDT heartbeat in jiffies */
+ bool nowayout;
+ unsigned int irq;
+ struct clk *sclk;
+};
+
+/* ......................................................................... */
+
+static irqreturn_t wdt_interrupt(int irq, void *dev_id)
+{
+ struct at91wdt *wdt = (struct at91wdt *)dev_id;
+
+ if (wdt_read(wdt, AT91_WDT_SR)) {
+ pr_crit("at91sam9 WDT software reset\n");
+ emergency_restart();
+ pr_crit("Reboot didn't ?????\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Reload the watchdog timer. (ie, pat the watchdog)
+ */
+static inline void at91_wdt_reset(struct at91wdt *wdt)
+{
+ wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
+}
+
+/*
+ * Timer tick
+ */
+static void at91_ping(struct timer_list *t)
+{
+ struct at91wdt *wdt = from_timer(wdt, t, timer);
+ if (time_before(jiffies, wdt->next_heartbeat) ||
+ !watchdog_active(&wdt->wdd)) {
+ at91_wdt_reset(wdt);
+ mod_timer(&wdt->timer, jiffies + wdt->heartbeat);
+ } else {
+ pr_crit("I will reset your machine !\n");
+ }
+}
+
+static int at91_wdt_start(struct watchdog_device *wdd)
+{
+ struct at91wdt *wdt = to_wdt(wdd);
+ /* calculate when the next userspace timeout will be */
+ wdt->next_heartbeat = jiffies + wdd->timeout * HZ;
+ return 0;
+}
+
+static int at91_wdt_stop(struct watchdog_device *wdd)
+{
+ /* The watchdog timer hardware can not be stopped... */
+ return 0;
+}
+
+static int at91_wdt_set_timeout(struct watchdog_device *wdd, unsigned int new_timeout)
+{
+ wdd->timeout = new_timeout;
+ return at91_wdt_start(wdd);
+}
+
+static int at91_wdt_init(struct platform_device *pdev, struct at91wdt *wdt)
+{
+ u32 tmp;
+ u32 delta;
+ u32 value;
+ int err;
+ u32 mask = wdt->mr_mask;
+ unsigned long min_heartbeat = 1;
+ unsigned long max_heartbeat;
+ struct device *dev = &pdev->dev;
+
+ tmp = wdt_read(wdt, AT91_WDT_MR);
+ if ((tmp & mask) != (wdt->mr & mask)) {
+ if (tmp == WDT_MR_RESET) {
+ wdt_write(wdt, AT91_WDT_MR, wdt->mr);
+ tmp = wdt_read(wdt, AT91_WDT_MR);
+ }
+ }
+
+ if (tmp & AT91_WDT_WDDIS) {
+ if (wdt->mr & AT91_WDT_WDDIS)
+ return 0;
+ dev_err(dev, "watchdog is disabled\n");
+ return -EINVAL;
+ }
+
+ value = tmp & AT91_WDT_WDV;
+ delta = (tmp & AT91_WDT_WDD) >> 16;
+
+ if (delta < value)
+ min_heartbeat = ticks_to_hz_roundup(value - delta);
+
+ max_heartbeat = ticks_to_hz_rounddown(value);
+ if (!max_heartbeat) {
+ dev_err(dev,
+ "heartbeat is too small for the system to handle it correctly\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Try to reset the watchdog counter 4 or 2 times more often than
+ * actually requested, to avoid spurious watchdog reset.
+ * If this is not possible because of the min_heartbeat value, reset
+ * it at the min_heartbeat period.
+ */
+ if ((max_heartbeat / 4) >= min_heartbeat)
+ wdt->heartbeat = max_heartbeat / 4;
+ else if ((max_heartbeat / 2) >= min_heartbeat)
+ wdt->heartbeat = max_heartbeat / 2;
+ else
+ wdt->heartbeat = min_heartbeat;
+
+ if (max_heartbeat < min_heartbeat + 4)
+ dev_warn(dev,
+ "min heartbeat and max heartbeat might be too close for the system to handle it correctly\n");
+
+ if ((tmp & AT91_WDT_WDFIEN) && wdt->irq) {
+ err = devm_request_irq(dev, wdt->irq, wdt_interrupt,
+ IRQF_SHARED | IRQF_IRQPOLL | IRQF_NO_SUSPEND,
+ pdev->name, wdt);
+ if (err)
+ return err;
+ }
+
+ if ((tmp & wdt->mr_mask) != (wdt->mr & wdt->mr_mask))
+ dev_warn(dev,
+ "watchdog already configured differently (mr = %x expecting %x)\n",
+ tmp & wdt->mr_mask, wdt->mr & wdt->mr_mask);
+
+ timer_setup(&wdt->timer, at91_ping, 0);
+
+ /*
+ * Use min_heartbeat the first time to avoid spurious watchdog reset:
+ * we don't know for how long the watchdog counter is running, and
+ * - resetting it right now might trigger a watchdog fault reset
+ * - waiting for heartbeat time might lead to a watchdog timeout
+ * reset
+ */
+ mod_timer(&wdt->timer, jiffies + min_heartbeat);
+
+ /* Try to set timeout from device tree first */
+ if (watchdog_init_timeout(&wdt->wdd, 0, dev))
+ watchdog_init_timeout(&wdt->wdd, heartbeat, dev);
+ watchdog_set_nowayout(&wdt->wdd, wdt->nowayout);
+ err = watchdog_register_device(&wdt->wdd);
+ if (err)
+ goto out_stop_timer;
+
+ wdt->next_heartbeat = jiffies + wdt->wdd.timeout * HZ;
+
+ return 0;
+
+out_stop_timer:
+ del_timer(&wdt->timer);
+ return err;
+}
+
+/* ......................................................................... */
+
+static const struct watchdog_info at91_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops at91_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = at91_wdt_start,
+ .stop = at91_wdt_stop,
+ .set_timeout = at91_wdt_set_timeout,
+};
+
+#if defined(CONFIG_OF)
+static int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
+{
+ u32 min = 0;
+ u32 max = WDT_COUNTER_MAX_SECS;
+ const char *tmp;
+
+ /* Get the interrupts property */
+ wdt->irq = irq_of_parse_and_map(np, 0);
+ if (!wdt->irq)
+ dev_warn(wdt->wdd.parent, "failed to get IRQ from DT\n");
+
+ if (!of_property_read_u32_index(np, "atmel,max-heartbeat-sec", 0,
+ &max)) {
+ if (!max || max > WDT_COUNTER_MAX_SECS)
+ max = WDT_COUNTER_MAX_SECS;
+
+ if (!of_property_read_u32_index(np, "atmel,min-heartbeat-sec",
+ 0, &min)) {
+ if (min >= max)
+ min = max - 1;
+ }
+ }
+
+ min = secs_to_ticks(min);
+ max = secs_to_ticks(max);
+
+ wdt->mr_mask = 0x3FFFFFFF;
+ wdt->mr = 0;
+ if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) &&
+ !strcmp(tmp, "software")) {
+ wdt->mr |= AT91_WDT_WDFIEN;
+ wdt->mr_mask &= ~AT91_WDT_WDRPROC;
+ } else {
+ wdt->mr |= AT91_WDT_WDRSTEN;
+ }
+
+ if (!of_property_read_string(np, "atmel,reset-type", &tmp) &&
+ !strcmp(tmp, "proc"))
+ wdt->mr |= AT91_WDT_WDRPROC;
+
+ if (of_property_read_bool(np, "atmel,disable")) {
+ wdt->mr |= AT91_WDT_WDDIS;
+ wdt->mr_mask &= AT91_WDT_WDDIS;
+ }
+
+ if (of_property_read_bool(np, "atmel,idle-halt"))
+ wdt->mr |= AT91_WDT_WDIDLEHLT;
+
+ if (of_property_read_bool(np, "atmel,dbg-halt"))
+ wdt->mr |= AT91_WDT_WDDBGHLT;
+
+ wdt->mr |= max | ((max - min) << 16);
+
+ return 0;
+}
+#else
+static inline int of_at91wdt_init(struct device_node *np, struct at91wdt *wdt)
+{
+ return 0;
+}
+#endif
+
+static int __init at91wdt_probe(struct platform_device *pdev)
+{
+ int err;
+ struct at91wdt *wdt;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->mr = (WDT_HW_TIMEOUT * 256) | AT91_WDT_WDRSTEN | AT91_WDT_WDD |
+ AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT;
+ wdt->mr_mask = 0x3FFFFFFF;
+ wdt->nowayout = nowayout;
+ wdt->wdd.parent = &pdev->dev;
+ wdt->wdd.info = &at91_wdt_info;
+ wdt->wdd.ops = &at91_wdt_ops;
+ wdt->wdd.timeout = WDT_HEARTBEAT;
+ wdt->wdd.min_timeout = 1;
+ wdt->wdd.max_timeout = 0xFFFF;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->sclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(wdt->sclk))
+ return PTR_ERR(wdt->sclk);
+
+ err = clk_prepare_enable(wdt->sclk);
+ if (err) {
+ dev_err(&pdev->dev, "Could not enable slow clock\n");
+ return err;
+ }
+
+ if (pdev->dev.of_node) {
+ err = of_at91wdt_init(pdev->dev.of_node, wdt);
+ if (err)
+ goto err_clk;
+ }
+
+ err = at91_wdt_init(pdev, wdt);
+ if (err)
+ goto err_clk;
+
+ platform_set_drvdata(pdev, wdt);
+
+ pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n",
+ wdt->wdd.timeout, wdt->nowayout);
+
+ return 0;
+
+err_clk:
+ clk_disable_unprepare(wdt->sclk);
+
+ return err;
+}
+
+static int __exit at91wdt_remove(struct platform_device *pdev)
+{
+ struct at91wdt *wdt = platform_get_drvdata(pdev);
+ watchdog_unregister_device(&wdt->wdd);
+
+ pr_warn("I quit now, hardware will probably reboot!\n");
+ del_timer(&wdt->timer);
+ clk_disable_unprepare(wdt->sclk);
+
+ return 0;
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id at91_wdt_dt_ids[] = {
+ { .compatible = "atmel,at91sam9260-wdt" },
+ { /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, at91_wdt_dt_ids);
+#endif
+
+static struct platform_driver at91wdt_driver = {
+ .remove = __exit_p(at91wdt_remove),
+ .driver = {
+ .name = "at91_wdt",
+ .of_match_table = of_match_ptr(at91_wdt_dt_ids),
+ },
+};
+
+module_platform_driver_probe(at91wdt_driver, at91wdt_probe);
+
+MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>");
+MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/at91sam9_wdt.h b/drivers/watchdog/at91sam9_wdt.h
new file mode 100644
index 000000000..298d545df
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * drivers/watchdog/at91sam9_wdt.h
+ *
+ * Copyright (C) 2007 Andrew Victor
+ * Copyright (C) 2007 Atmel Corporation.
+ * Copyright (C) 2019 Microchip Technology Inc. and its subsidiaries
+ *
+ * Watchdog Timer (WDT) - System peripherals regsters.
+ * Based on AT91SAM9261 datasheet revision D.
+ * Based on SAM9X60 datasheet.
+ *
+ */
+
+#ifndef AT91_WDT_H
+#define AT91_WDT_H
+
+#include <linux/bits.h>
+
+#define AT91_WDT_CR 0x00 /* Watchdog Control Register */
+#define AT91_WDT_WDRSTT BIT(0) /* Restart */
+#define AT91_WDT_KEY (0xa5UL << 24) /* KEY Password */
+
+#define AT91_WDT_MR 0x04 /* Watchdog Mode Register */
+#define AT91_WDT_WDV (0xfffUL << 0) /* Counter Value */
+#define AT91_WDT_SET_WDV(x) ((x) & AT91_WDT_WDV)
+#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_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_WDD (0xfffUL << 16) /* Delta Value */
+#define AT91_WDT_SET_WDD(x) (((x) << 16) & AT91_WDT_WDD)
+#define AT91_WDT_WDDBGHLT BIT(28) /* Debug Halt */
+#define AT91_WDT_WDIDLEHLT BIT(29) /* Idle Halt */
+
+#define AT91_WDT_SR 0x08 /* Watchdog Status Register */
+#define AT91_WDT_WDUNF BIT(0) /* Watchdog Underflow */
+#define AT91_WDT_WDERR BIT(1) /* Watchdog Error */
+
+/* Watchdog Timer Value Register */
+#define AT91_SAM9X60_VR 0x08
+
+/* Watchdog Window Level Register */
+#define AT91_SAM9X60_WLR 0x0c
+/* Watchdog Period Value */
+#define AT91_SAM9X60_COUNTER (0xfffUL << 0)
+#define AT91_SAM9X60_SET_COUNTER(x) ((x) & AT91_SAM9X60_COUNTER)
+
+/* Interrupt Enable Register */
+#define AT91_SAM9X60_IER 0x14
+/* Period Interrupt Enable */
+#define AT91_SAM9X60_PERINT BIT(0)
+/* Interrupt Disable Register */
+#define AT91_SAM9X60_IDR 0x18
+/* Interrupt Status Register */
+#define AT91_SAM9X60_ISR 0x1c
+
+#endif
diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c
new file mode 100644
index 000000000..0f18f06a2
--- /dev/null
+++ b/drivers/watchdog/ath79_wdt.c
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Atheros AR71XX/AR724X/AR913X built-in hardware watchdog timer.
+ *
+ * Copyright (C) 2008-2011 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2008 Imre Kaloz <kaloz@openwrt.org>
+ *
+ * This driver was based on: drivers/watchdog/ixp4xx_wdt.c
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ *
+ * which again was based on sa1100 driver,
+ * Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+
+#define DRIVER_NAME "ath79-wdt"
+
+#define WDT_TIMEOUT 15 /* seconds */
+
+#define WDOG_REG_CTRL 0x00
+#define WDOG_REG_TIMER 0x04
+
+#define WDOG_CTRL_LAST_RESET BIT(31)
+#define WDOG_CTRL_ACTION_MASK 3
+#define WDOG_CTRL_ACTION_NONE 0 /* no action */
+#define WDOG_CTRL_ACTION_GPI 1 /* general purpose interrupt */
+#define WDOG_CTRL_ACTION_NMI 2 /* NMI */
+#define WDOG_CTRL_ACTION_FCR 3 /* full chip reset */
+
+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) ")");
+
+static int timeout = WDT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
+ "(default=" __MODULE_STRING(WDT_TIMEOUT) "s)");
+
+static unsigned long wdt_flags;
+
+#define WDT_FLAGS_BUSY 0
+#define WDT_FLAGS_EXPECT_CLOSE 1
+
+static struct clk *wdt_clk;
+static unsigned long wdt_freq;
+static int boot_status;
+static int max_timeout;
+static void __iomem *wdt_base;
+
+static inline void ath79_wdt_wr(unsigned reg, u32 val)
+{
+ iowrite32(val, wdt_base + reg);
+}
+
+static inline u32 ath79_wdt_rr(unsigned reg)
+{
+ return ioread32(wdt_base + reg);
+}
+
+static inline void ath79_wdt_keepalive(void)
+{
+ ath79_wdt_wr(WDOG_REG_TIMER, wdt_freq * timeout);
+ /* flush write */
+ ath79_wdt_rr(WDOG_REG_TIMER);
+}
+
+static inline void ath79_wdt_enable(void)
+{
+ ath79_wdt_keepalive();
+
+ /*
+ * Updating the TIMER register requires a few microseconds
+ * on the AR934x SoCs at least. Use a small delay to ensure
+ * that the TIMER register is updated within the hardware
+ * before enabling the watchdog.
+ */
+ udelay(2);
+
+ ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_FCR);
+ /* flush write */
+ ath79_wdt_rr(WDOG_REG_CTRL);
+}
+
+static inline void ath79_wdt_disable(void)
+{
+ ath79_wdt_wr(WDOG_REG_CTRL, WDOG_CTRL_ACTION_NONE);
+ /* flush write */
+ ath79_wdt_rr(WDOG_REG_CTRL);
+}
+
+static int ath79_wdt_set_timeout(int val)
+{
+ if (val < 1 || val > max_timeout)
+ return -EINVAL;
+
+ timeout = val;
+ ath79_wdt_keepalive();
+
+ return 0;
+}
+
+static int ath79_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_FLAGS_BUSY, &wdt_flags))
+ return -EBUSY;
+
+ clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+ ath79_wdt_enable();
+
+ return stream_open(inode, file);
+}
+
+static int ath79_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags))
+ ath79_wdt_disable();
+ else {
+ pr_crit("device closed unexpectedly, watchdog timer will not stop!\n");
+ ath79_wdt_keepalive();
+ }
+
+ clear_bit(WDT_FLAGS_BUSY, &wdt_flags);
+ clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+
+ return 0;
+}
+
+static ssize_t ath79_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ clear_bit(WDT_FLAGS_EXPECT_CLOSE, &wdt_flags);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ if (c == 'V')
+ set_bit(WDT_FLAGS_EXPECT_CLOSE,
+ &wdt_flags);
+ }
+ }
+
+ ath79_wdt_keepalive();
+ }
+
+ return len;
+}
+
+static const struct watchdog_info ath79_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
+ .firmware_version = 0,
+ .identity = "ATH79 watchdog",
+};
+
+static long ath79_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int err;
+ int t;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ err = copy_to_user(argp, &ath79_wdt_info,
+ sizeof(ath79_wdt_info)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ err = put_user(0, p);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ err = put_user(boot_status, p);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ ath79_wdt_keepalive();
+ err = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ err = get_user(t, p);
+ if (err)
+ break;
+
+ err = ath79_wdt_set_timeout(t);
+ if (err)
+ break;
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ err = put_user(timeout, p);
+ break;
+
+ default:
+ err = -ENOTTY;
+ break;
+ }
+
+ return err;
+}
+
+static const struct file_operations ath79_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ath79_wdt_write,
+ .unlocked_ioctl = ath79_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = ath79_wdt_open,
+ .release = ath79_wdt_release,
+};
+
+static struct miscdevice ath79_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ath79_wdt_fops,
+};
+
+static int ath79_wdt_probe(struct platform_device *pdev)
+{
+ u32 ctrl;
+ int err;
+
+ if (wdt_base)
+ return -EBUSY;
+
+ wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt_base))
+ return PTR_ERR(wdt_base);
+
+ wdt_clk = devm_clk_get(&pdev->dev, "wdt");
+ if (IS_ERR(wdt_clk))
+ return PTR_ERR(wdt_clk);
+
+ err = clk_prepare_enable(wdt_clk);
+ if (err)
+ return err;
+
+ wdt_freq = clk_get_rate(wdt_clk);
+ if (!wdt_freq) {
+ err = -EINVAL;
+ goto err_clk_disable;
+ }
+
+ max_timeout = (0xfffffffful / wdt_freq);
+ if (timeout < 1 || timeout > max_timeout) {
+ timeout = max_timeout;
+ dev_info(&pdev->dev,
+ "timeout value must be 0 < timeout < %d, using %d\n",
+ max_timeout, timeout);
+ }
+
+ ctrl = ath79_wdt_rr(WDOG_REG_CTRL);
+ boot_status = (ctrl & WDOG_CTRL_LAST_RESET) ? WDIOF_CARDRESET : 0;
+
+ err = misc_register(&ath79_wdt_miscdev);
+ if (err) {
+ dev_err(&pdev->dev,
+ "unable to register misc device, err=%d\n", err);
+ goto err_clk_disable;
+ }
+
+ return 0;
+
+err_clk_disable:
+ clk_disable_unprepare(wdt_clk);
+ return err;
+}
+
+static int ath79_wdt_remove(struct platform_device *pdev)
+{
+ misc_deregister(&ath79_wdt_miscdev);
+ clk_disable_unprepare(wdt_clk);
+ return 0;
+}
+
+static void ath79_wdt_shutdown(struct platform_device *pdev)
+{
+ ath79_wdt_disable();
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ath79_wdt_match[] = {
+ { .compatible = "qca,ar7130-wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ath79_wdt_match);
+#endif
+
+static struct platform_driver ath79_wdt_driver = {
+ .probe = ath79_wdt_probe,
+ .remove = ath79_wdt_remove,
+ .shutdown = ath79_wdt_shutdown,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(ath79_wdt_match),
+ },
+};
+
+module_platform_driver(ath79_wdt_driver);
+
+MODULE_DESCRIPTION("Atheros AR71XX/AR724X/AR913X hardware watchdog driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
+MODULE_AUTHOR("Imre Kaloz <kaloz@openwrt.org");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c
new file mode 100644
index 000000000..55c0f7b0e
--- /dev/null
+++ b/drivers/watchdog/bcm2835_wdt.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Broadcom BCM2835
+ *
+ * "bcm2708_wdog" driver written by Luke Diamand that was obtained from
+ * branch "rpi-3.6.y" of git://github.com/raspberrypi/linux.git was used
+ * as a hardware reference for the Broadcom BCM2835 watchdog timer.
+ *
+ * Copyright (C) 2013 Lubomir Rintel <lkundrak@v3.sk>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/mfd/bcm2835-pm.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#define PM_RSTC 0x1c
+#define PM_RSTS 0x20
+#define PM_WDOG 0x24
+
+#define PM_PASSWORD 0x5a000000
+
+#define PM_WDOG_TIME_SET 0x000fffff
+#define PM_RSTC_WRCFG_CLR 0xffffffcf
+#define PM_RSTS_HADWRH_SET 0x00000040
+#define PM_RSTC_WRCFG_SET 0x00000030
+#define PM_RSTC_WRCFG_FULL_RESET 0x00000020
+#define PM_RSTC_RESET 0x00000102
+
+/*
+ * The Raspberry Pi firmware uses the RSTS register to know which partition
+ * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10.
+ * Partition 63 is a special partition used by the firmware to indicate halt.
+ */
+#define PM_RSTS_RASPBERRYPI_HALT 0x555
+
+#define SECS_TO_WDOG_TICKS(x) ((x) << 16)
+#define WDOG_TICKS_TO_SECS(x) ((x) >> 16)
+#define WDOG_TICKS_TO_MSECS(x) ((x) * 1000 >> 16)
+
+struct bcm2835_wdt {
+ void __iomem *base;
+ spinlock_t lock;
+};
+
+static struct bcm2835_wdt *bcm2835_power_off_wdt;
+
+static unsigned int heartbeat;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+static bool bcm2835_wdt_is_running(struct bcm2835_wdt *wdt)
+{
+ uint32_t cur;
+
+ cur = readl(wdt->base + PM_RSTC);
+
+ return !!(cur & PM_RSTC_WRCFG_FULL_RESET);
+}
+
+static int bcm2835_wdt_start(struct watchdog_device *wdog)
+{
+ struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
+ uint32_t cur;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+
+ writel_relaxed(PM_PASSWORD | (SECS_TO_WDOG_TICKS(wdog->timeout) &
+ PM_WDOG_TIME_SET), wdt->base + PM_WDOG);
+ cur = readl_relaxed(wdt->base + PM_RSTC);
+ writel_relaxed(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) |
+ PM_RSTC_WRCFG_FULL_RESET, wdt->base + PM_RSTC);
+
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ return 0;
+}
+
+static int bcm2835_wdt_stop(struct watchdog_device *wdog)
+{
+ struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ writel_relaxed(PM_PASSWORD | PM_RSTC_RESET, wdt->base + PM_RSTC);
+ return 0;
+}
+
+static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ uint32_t ret = readl_relaxed(wdt->base + PM_WDOG);
+ return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET);
+}
+
+static void __bcm2835_restart(struct bcm2835_wdt *wdt)
+{
+ u32 val;
+
+ /* use a timeout of 10 ticks (~150us) */
+ writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG);
+ val = readl_relaxed(wdt->base + PM_RSTC);
+ val &= PM_RSTC_WRCFG_CLR;
+ val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
+ writel_relaxed(val, wdt->base + PM_RSTC);
+
+ /* No sleeping, possibly atomic. */
+ mdelay(1);
+}
+
+static int bcm2835_restart(struct watchdog_device *wdog,
+ unsigned long action, void *data)
+{
+ struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ __bcm2835_restart(wdt);
+
+ return 0;
+}
+
+static const struct watchdog_ops bcm2835_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm2835_wdt_start,
+ .stop = bcm2835_wdt_stop,
+ .get_timeleft = bcm2835_wdt_get_timeleft,
+ .restart = bcm2835_restart,
+};
+
+static const struct watchdog_info bcm2835_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "Broadcom BCM2835 Watchdog timer",
+};
+
+static struct watchdog_device bcm2835_wdt_wdd = {
+ .info = &bcm2835_wdt_info,
+ .ops = &bcm2835_wdt_ops,
+ .min_timeout = 1,
+ .max_hw_heartbeat_ms = WDOG_TICKS_TO_MSECS(PM_WDOG_TIME_SET),
+ .timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET),
+};
+
+/*
+ * We can't really power off, but if we do the normal reset scheme, and
+ * indicate to bootcode.bin not to reboot, then most of the chip will be
+ * powered off.
+ */
+static void bcm2835_power_off(void)
+{
+ struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;
+ u32 val;
+
+ /*
+ * We set the watchdog hard reset bit here to distinguish this reset
+ * from the normal (full) reset. bootcode.bin will not reboot after a
+ * hard reset.
+ */
+ val = readl_relaxed(wdt->base + PM_RSTS);
+ val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
+ writel_relaxed(val, wdt->base + PM_RSTS);
+
+ /* Continue with normal reset mechanism */
+ __bcm2835_restart(wdt);
+}
+
+static int bcm2835_wdt_probe(struct platform_device *pdev)
+{
+ struct bcm2835_pm *pm = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct bcm2835_wdt *wdt;
+ int err;
+
+ wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ spin_lock_init(&wdt->lock);
+
+ wdt->base = pm->base;
+
+ watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt);
+ watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev);
+ watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout);
+ bcm2835_wdt_wdd.parent = dev;
+ if (bcm2835_wdt_is_running(wdt)) {
+ /*
+ * The currently active timeout value (set by the
+ * bootloader) may be different from the module
+ * heartbeat parameter or the value in device
+ * tree. But we just need to set WDOG_HW_RUNNING,
+ * because then the framework will "immediately" ping
+ * the device, updating the timeout.
+ */
+ set_bit(WDOG_HW_RUNNING, &bcm2835_wdt_wdd.status);
+ }
+
+ watchdog_set_restart_priority(&bcm2835_wdt_wdd, 128);
+
+ watchdog_stop_on_reboot(&bcm2835_wdt_wdd);
+ err = devm_watchdog_register_device(dev, &bcm2835_wdt_wdd);
+ 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");
+ }
+ }
+
+ dev_info(dev, "Broadcom BCM2835 watchdog timer");
+ return 0;
+}
+
+static int bcm2835_wdt_remove(struct platform_device *pdev)
+{
+ if (pm_power_off == bcm2835_power_off)
+ pm_power_off = NULL;
+
+ return 0;
+}
+
+static struct platform_driver bcm2835_wdt_driver = {
+ .probe = bcm2835_wdt_probe,
+ .remove = bcm2835_wdt_remove,
+ .driver = {
+ .name = "bcm2835-wdt",
+ },
+};
+module_platform_driver(bcm2835_wdt_driver);
+
+module_param(heartbeat, uint, 0);
+MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_ALIAS("platform:bcm2835-wdt");
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("Driver for Broadcom BCM2835 watchdog timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c
new file mode 100644
index 000000000..05425c1df
--- /dev/null
+++ b/drivers/watchdog/bcm47xx_wdt.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Broadcom BCM47XX
+ *
+ * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs>
+ * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.fr>
+ * Copyright (C) 2012-2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bcm47xx_wdt.h>
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+
+#define DRV_NAME "bcm47xx_wdt"
+
+#define WDT_DEFAULT_TIME 30 /* seconds */
+#define WDT_SOFTTIMER_MAX 255 /* seconds */
+#define WDT_SOFTTIMER_THRESHOLD 60 /* seconds */
+
+static int timeout = WDT_DEFAULT_TIME;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog time in seconds. (default="
+ __MODULE_STRING(WDT_DEFAULT_TIME) ")");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static inline struct bcm47xx_wdt *bcm47xx_wdt_get(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct bcm47xx_wdt, wdd);
+}
+
+static int bcm47xx_wdt_hard_keepalive(struct watchdog_device *wdd)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ wdt->timer_set_ms(wdt, wdd->timeout * 1000);
+
+ return 0;
+}
+
+static int bcm47xx_wdt_hard_start(struct watchdog_device *wdd)
+{
+ return 0;
+}
+
+static int bcm47xx_wdt_hard_stop(struct watchdog_device *wdd)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ wdt->timer_set(wdt, 0);
+
+ return 0;
+}
+
+static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd,
+ unsigned int new_time)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+ u32 max_timer = wdt->max_timer_ms;
+
+ if (new_time < 1 || new_time > max_timer / 1000) {
+ pr_warn("timeout value must be 1<=x<=%d, using %d\n",
+ max_timer / 1000, new_time);
+ return -EINVAL;
+ }
+
+ wdd->timeout = new_time;
+ return 0;
+}
+
+static int bcm47xx_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ wdt->timer_set(wdt, 1);
+
+ return 0;
+}
+
+static const struct watchdog_ops bcm47xx_wdt_hard_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm47xx_wdt_hard_start,
+ .stop = bcm47xx_wdt_hard_stop,
+ .ping = bcm47xx_wdt_hard_keepalive,
+ .set_timeout = bcm47xx_wdt_hard_set_timeout,
+ .restart = bcm47xx_wdt_restart,
+};
+
+static void bcm47xx_wdt_soft_timer_tick(struct timer_list *t)
+{
+ struct bcm47xx_wdt *wdt = from_timer(wdt, t, soft_timer);
+ u32 next_tick = min(wdt->wdd.timeout * 1000, wdt->max_timer_ms);
+
+ if (!atomic_dec_and_test(&wdt->soft_ticks)) {
+ wdt->timer_set_ms(wdt, next_tick);
+ mod_timer(&wdt->soft_timer, jiffies + HZ);
+ } else {
+ pr_crit("Watchdog will fire soon!!!\n");
+ }
+}
+
+static int bcm47xx_wdt_soft_keepalive(struct watchdog_device *wdd)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ atomic_set(&wdt->soft_ticks, wdd->timeout);
+
+ return 0;
+}
+
+static int bcm47xx_wdt_soft_start(struct watchdog_device *wdd)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ bcm47xx_wdt_soft_keepalive(wdd);
+ bcm47xx_wdt_soft_timer_tick(&wdt->soft_timer);
+
+ return 0;
+}
+
+static int bcm47xx_wdt_soft_stop(struct watchdog_device *wdd)
+{
+ struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd);
+
+ del_timer_sync(&wdt->soft_timer);
+ wdt->timer_set(wdt, 0);
+
+ return 0;
+}
+
+static int bcm47xx_wdt_soft_set_timeout(struct watchdog_device *wdd,
+ unsigned int new_time)
+{
+ if (new_time < 1 || new_time > WDT_SOFTTIMER_MAX) {
+ pr_warn("timeout value must be 1<=x<=%d, using %d\n",
+ WDT_SOFTTIMER_MAX, new_time);
+ return -EINVAL;
+ }
+
+ wdd->timeout = new_time;
+ return 0;
+}
+
+static const struct watchdog_info bcm47xx_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops bcm47xx_wdt_soft_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm47xx_wdt_soft_start,
+ .stop = bcm47xx_wdt_soft_stop,
+ .ping = bcm47xx_wdt_soft_keepalive,
+ .set_timeout = bcm47xx_wdt_soft_set_timeout,
+ .restart = bcm47xx_wdt_restart,
+};
+
+static int bcm47xx_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+ bool soft;
+ struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
+
+ if (!wdt)
+ return -ENXIO;
+
+ soft = wdt->max_timer_ms < WDT_SOFTTIMER_THRESHOLD * 1000;
+
+ if (soft) {
+ wdt->wdd.ops = &bcm47xx_wdt_soft_ops;
+ timer_setup(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, 0);
+ } else {
+ wdt->wdd.ops = &bcm47xx_wdt_hard_ops;
+ }
+
+ wdt->wdd.info = &bcm47xx_wdt_info;
+ wdt->wdd.timeout = WDT_DEFAULT_TIME;
+ wdt->wdd.parent = &pdev->dev;
+ ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout);
+ if (ret)
+ goto err_timer;
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+ watchdog_set_restart_priority(&wdt->wdd, 64);
+ watchdog_stop_on_reboot(&wdt->wdd);
+
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret)
+ goto err_timer;
+
+ dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n",
+ timeout, nowayout ? ", nowayout" : "",
+ soft ? ", Software Timer" : "");
+ return 0;
+
+err_timer:
+ if (soft)
+ del_timer_sync(&wdt->soft_timer);
+
+ return ret;
+}
+
+static int bcm47xx_wdt_remove(struct platform_device *pdev)
+{
+ struct bcm47xx_wdt *wdt = dev_get_platdata(&pdev->dev);
+
+ watchdog_unregister_device(&wdt->wdd);
+
+ return 0;
+}
+
+static struct platform_driver bcm47xx_wdt_driver = {
+ .driver = {
+ .name = "bcm47xx-wdt",
+ },
+ .probe = bcm47xx_wdt_probe,
+ .remove = bcm47xx_wdt_remove,
+};
+
+module_platform_driver(bcm47xx_wdt_driver);
+
+MODULE_AUTHOR("Aleksandar Radovanovic");
+MODULE_AUTHOR("Hauke Mehrtens <hauke@hauke-m.de>");
+MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/bcm7038_wdt.c b/drivers/watchdog/bcm7038_wdt.c
new file mode 100644
index 000000000..938883889
--- /dev/null
+++ b/drivers/watchdog/bcm7038_wdt.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2015 Broadcom Corporation
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/bcm7038_wdt.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#define WDT_START_1 0xff00
+#define WDT_START_2 0x00ff
+#define WDT_STOP_1 0xee00
+#define WDT_STOP_2 0x00ee
+
+#define WDT_TIMEOUT_REG 0x0
+#define WDT_CMD_REG 0x4
+
+#define WDT_MIN_TIMEOUT 1 /* seconds */
+#define WDT_DEFAULT_TIMEOUT 30 /* seconds */
+#define WDT_DEFAULT_RATE 27000000
+
+struct bcm7038_watchdog {
+ void __iomem *base;
+ struct watchdog_device wdd;
+ u32 rate;
+ struct clk *clk;
+};
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+static inline void bcm7038_wdt_write(u32 value, void __iomem *addr)
+{
+ /* MIPS chips strapped for BE will automagically configure the
+ * peripheral registers for CPU-native byte order.
+ */
+ if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+ __raw_writel(value, addr);
+ else
+ writel_relaxed(value, addr);
+}
+
+static inline u32 bcm7038_wdt_read(void __iomem *addr)
+{
+ if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
+ return __raw_readl(addr);
+ else
+ return readl_relaxed(addr);
+}
+
+static void bcm7038_wdt_set_timeout_reg(struct watchdog_device *wdog)
+{
+ struct bcm7038_watchdog *wdt = watchdog_get_drvdata(wdog);
+ u32 timeout;
+
+ timeout = wdt->rate * wdog->timeout;
+
+ bcm7038_wdt_write(timeout, wdt->base + WDT_TIMEOUT_REG);
+}
+
+static int bcm7038_wdt_ping(struct watchdog_device *wdog)
+{
+ struct bcm7038_watchdog *wdt = watchdog_get_drvdata(wdog);
+
+ bcm7038_wdt_write(WDT_START_1, wdt->base + WDT_CMD_REG);
+ bcm7038_wdt_write(WDT_START_2, wdt->base + WDT_CMD_REG);
+
+ return 0;
+}
+
+static int bcm7038_wdt_start(struct watchdog_device *wdog)
+{
+ bcm7038_wdt_set_timeout_reg(wdog);
+ bcm7038_wdt_ping(wdog);
+
+ return 0;
+}
+
+static int bcm7038_wdt_stop(struct watchdog_device *wdog)
+{
+ struct bcm7038_watchdog *wdt = watchdog_get_drvdata(wdog);
+
+ bcm7038_wdt_write(WDT_STOP_1, wdt->base + WDT_CMD_REG);
+ bcm7038_wdt_write(WDT_STOP_2, wdt->base + WDT_CMD_REG);
+
+ return 0;
+}
+
+static int bcm7038_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int t)
+{
+ /* Can't modify timeout value if watchdog timer is running */
+ bcm7038_wdt_stop(wdog);
+ wdog->timeout = t;
+ bcm7038_wdt_start(wdog);
+
+ return 0;
+}
+
+static unsigned int bcm7038_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct bcm7038_watchdog *wdt = watchdog_get_drvdata(wdog);
+ u32 time_left;
+
+ time_left = bcm7038_wdt_read(wdt->base + WDT_CMD_REG);
+
+ return time_left / wdt->rate;
+}
+
+static const struct watchdog_info bcm7038_wdt_info = {
+ .identity = "Broadcom BCM7038 Watchdog Timer",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE
+};
+
+static const struct watchdog_ops bcm7038_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm7038_wdt_start,
+ .stop = bcm7038_wdt_stop,
+ .set_timeout = bcm7038_wdt_set_timeout,
+ .get_timeleft = bcm7038_wdt_get_timeleft,
+};
+
+static void bcm7038_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int bcm7038_wdt_probe(struct platform_device *pdev)
+{
+ struct bcm7038_wdt_platform_data *pdata = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct bcm7038_watchdog *wdt;
+ const char *clk_name = NULL;
+ int err;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, wdt);
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ if (pdata && pdata->clk_name)
+ clk_name = pdata->clk_name;
+
+ wdt->clk = devm_clk_get(dev, clk_name);
+ /* If unable to get clock, use default frequency */
+ if (!IS_ERR(wdt->clk)) {
+ err = clk_prepare_enable(wdt->clk);
+ if (err)
+ return err;
+ err = devm_add_action_or_reset(dev,
+ bcm7038_clk_disable_unprepare,
+ wdt->clk);
+ if (err)
+ return err;
+ wdt->rate = clk_get_rate(wdt->clk);
+ /* Prevent divide-by-zero exception */
+ if (!wdt->rate)
+ wdt->rate = WDT_DEFAULT_RATE;
+ } else {
+ wdt->rate = WDT_DEFAULT_RATE;
+ wdt->clk = NULL;
+ }
+
+ wdt->wdd.info = &bcm7038_wdt_info;
+ wdt->wdd.ops = &bcm7038_wdt_ops;
+ wdt->wdd.min_timeout = WDT_MIN_TIMEOUT;
+ wdt->wdd.timeout = WDT_DEFAULT_TIMEOUT;
+ wdt->wdd.max_timeout = 0xffffffff / wdt->rate;
+ wdt->wdd.parent = dev;
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+
+ watchdog_stop_on_reboot(&wdt->wdd);
+ watchdog_stop_on_unregister(&wdt->wdd);
+ err = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (err)
+ return err;
+
+ dev_info(dev, "Registered BCM7038 Watchdog\n");
+
+ return 0;
+}
+
+static int bcm7038_wdt_suspend(struct device *dev)
+{
+ struct bcm7038_watchdog *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return bcm7038_wdt_stop(&wdt->wdd);
+
+ return 0;
+}
+
+static int bcm7038_wdt_resume(struct device *dev)
+{
+ struct bcm7038_watchdog *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return bcm7038_wdt_start(&wdt->wdd);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(bcm7038_wdt_pm_ops,
+ bcm7038_wdt_suspend, bcm7038_wdt_resume);
+
+static const struct of_device_id bcm7038_wdt_match[] = {
+ { .compatible = "brcm,bcm6345-wdt" },
+ { .compatible = "brcm,bcm7038-wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm7038_wdt_match);
+
+static const struct platform_device_id bcm7038_wdt_devtype[] = {
+ { .name = "bcm63xx-wdt" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(platform, bcm7038_wdt_devtype);
+
+static struct platform_driver bcm7038_wdt_driver = {
+ .probe = bcm7038_wdt_probe,
+ .id_table = bcm7038_wdt_devtype,
+ .driver = {
+ .name = "bcm7038-wdt",
+ .of_match_table = bcm7038_wdt_match,
+ .pm = pm_sleep_ptr(&bcm7038_wdt_pm_ops),
+ }
+};
+module_platform_driver(bcm7038_wdt_driver);
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Broadcom 7038 SoCs Watchdog");
+MODULE_AUTHOR("Justin Chen");
diff --git a/drivers/watchdog/bcm_kona_wdt.c b/drivers/watchdog/bcm_kona_wdt.c
new file mode 100644
index 000000000..8237c4e9c
--- /dev/null
+++ b/drivers/watchdog/bcm_kona_wdt.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2013 Broadcom Corporation
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define SECWDOG_CTRL_REG 0x00000000
+#define SECWDOG_COUNT_REG 0x00000004
+
+#define SECWDOG_RESERVED_MASK 0x1dffffff
+#define SECWDOG_WD_LOAD_FLAG 0x10000000
+#define SECWDOG_EN_MASK 0x08000000
+#define SECWDOG_SRSTEN_MASK 0x04000000
+#define SECWDOG_RES_MASK 0x00f00000
+#define SECWDOG_COUNT_MASK 0x000fffff
+
+#define SECWDOG_MAX_COUNT SECWDOG_COUNT_MASK
+#define SECWDOG_CLKS_SHIFT 20
+#define SECWDOG_MAX_RES 15
+#define SECWDOG_DEFAULT_RESOLUTION 4
+#define SECWDOG_MAX_TRY 1000
+
+#define SECS_TO_TICKS(x, w) ((x) << (w)->resolution)
+#define TICKS_TO_SECS(x, w) ((x) >> (w)->resolution)
+
+#define BCM_KONA_WDT_NAME "bcm_kona_wdt"
+
+struct bcm_kona_wdt {
+ void __iomem *base;
+ /*
+ * One watchdog tick is 1/(2^resolution) seconds. Resolution can take
+ * the values 0-15, meaning one tick can be 1s to 30.52us. Our default
+ * resolution of 4 means one tick is 62.5ms.
+ *
+ * The watchdog counter is 20 bits. Depending on resolution, the maximum
+ * counter value of 0xfffff expires after about 12 days (resolution 0)
+ * down to only 32s (resolution 15). The default resolution of 4 gives
+ * us a maximum of about 18 hours and 12 minutes before the watchdog
+ * times out.
+ */
+ int resolution;
+ spinlock_t lock;
+#ifdef CONFIG_BCM_KONA_WDT_DEBUG
+ unsigned long busy_count;
+ struct dentry *debugfs;
+#endif
+};
+
+static int secure_register_read(struct bcm_kona_wdt *wdt, uint32_t offset)
+{
+ uint32_t val;
+ unsigned count = 0;
+
+ /*
+ * If the WD_LOAD_FLAG is set, the watchdog counter field is being
+ * updated in hardware. Once the WD timer is updated in hardware, it
+ * gets cleared.
+ */
+ do {
+ if (unlikely(count > 1))
+ udelay(5);
+ val = readl_relaxed(wdt->base + offset);
+ count++;
+ } while ((val & SECWDOG_WD_LOAD_FLAG) && count < SECWDOG_MAX_TRY);
+
+#ifdef CONFIG_BCM_KONA_WDT_DEBUG
+ /* Remember the maximum number iterations due to WD_LOAD_FLAG */
+ if (count > wdt->busy_count)
+ wdt->busy_count = count;
+#endif
+
+ /* This is the only place we return a negative value. */
+ if (val & SECWDOG_WD_LOAD_FLAG)
+ return -ETIMEDOUT;
+
+ /* We always mask out reserved bits. */
+ val &= SECWDOG_RESERVED_MASK;
+
+ return val;
+}
+
+#ifdef CONFIG_BCM_KONA_WDT_DEBUG
+
+static int bcm_kona_show(struct seq_file *s, void *data)
+{
+ int ctl_val, cur_val;
+ unsigned long flags;
+ struct bcm_kona_wdt *wdt = s->private;
+
+ if (!wdt) {
+ seq_puts(s, "No device pointer\n");
+ return 0;
+ }
+
+ spin_lock_irqsave(&wdt->lock, flags);
+ ctl_val = secure_register_read(wdt, SECWDOG_CTRL_REG);
+ cur_val = secure_register_read(wdt, SECWDOG_COUNT_REG);
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ if (ctl_val < 0 || cur_val < 0) {
+ seq_puts(s, "Error accessing hardware\n");
+ } else {
+ int ctl, cur, ctl_sec, cur_sec, res;
+
+ ctl = ctl_val & SECWDOG_COUNT_MASK;
+ res = (ctl_val & SECWDOG_RES_MASK) >> SECWDOG_CLKS_SHIFT;
+ cur = cur_val & SECWDOG_COUNT_MASK;
+ ctl_sec = TICKS_TO_SECS(ctl, wdt);
+ cur_sec = TICKS_TO_SECS(cur, wdt);
+ seq_printf(s,
+ "Resolution: %d / %d\n"
+ "Control: %d s / %d (%#x) ticks\n"
+ "Current: %d s / %d (%#x) ticks\n"
+ "Busy count: %lu\n",
+ res, wdt->resolution,
+ ctl_sec, ctl, ctl,
+ cur_sec, cur, cur,
+ wdt->busy_count);
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(bcm_kona);
+
+static void bcm_kona_wdt_debug_init(struct platform_device *pdev)
+{
+ struct dentry *dir;
+ struct bcm_kona_wdt *wdt = platform_get_drvdata(pdev);
+
+ if (!wdt)
+ return;
+
+ wdt->debugfs = NULL;
+
+ dir = debugfs_create_dir(BCM_KONA_WDT_NAME, NULL);
+
+ debugfs_create_file("info", S_IFREG | S_IRUGO, dir, wdt,
+ &bcm_kona_fops);
+ wdt->debugfs = dir;
+}
+
+static void bcm_kona_wdt_debug_exit(struct platform_device *pdev)
+{
+ struct bcm_kona_wdt *wdt = platform_get_drvdata(pdev);
+
+ if (wdt)
+ debugfs_remove_recursive(wdt->debugfs);
+}
+
+#else
+
+static void bcm_kona_wdt_debug_init(struct platform_device *pdev) {}
+static void bcm_kona_wdt_debug_exit(struct platform_device *pdev) {}
+
+#endif /* CONFIG_BCM_KONA_WDT_DEBUG */
+
+static int bcm_kona_wdt_ctrl_reg_modify(struct bcm_kona_wdt *wdt,
+ unsigned mask, unsigned newval)
+{
+ int val;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+
+ val = secure_register_read(wdt, SECWDOG_CTRL_REG);
+ if (val < 0) {
+ ret = val;
+ } else {
+ val &= ~mask;
+ val |= newval;
+ writel_relaxed(val, wdt->base + SECWDOG_CTRL_REG);
+ }
+
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ return ret;
+}
+
+static int bcm_kona_wdt_set_resolution_reg(struct bcm_kona_wdt *wdt)
+{
+ if (wdt->resolution > SECWDOG_MAX_RES)
+ return -EINVAL;
+
+ return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_RES_MASK,
+ wdt->resolution << SECWDOG_CLKS_SHIFT);
+}
+
+static int bcm_kona_wdt_set_timeout_reg(struct watchdog_device *wdog,
+ unsigned watchdog_flags)
+{
+ struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_COUNT_MASK,
+ SECS_TO_TICKS(wdog->timeout, wdt) |
+ watchdog_flags);
+}
+
+static int bcm_kona_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int t)
+{
+ wdog->timeout = t;
+ return 0;
+}
+
+static unsigned int bcm_kona_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog);
+ int val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+ val = secure_register_read(wdt, SECWDOG_COUNT_REG);
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ if (val < 0)
+ return val;
+
+ return TICKS_TO_SECS(val & SECWDOG_COUNT_MASK, wdt);
+}
+
+static int bcm_kona_wdt_start(struct watchdog_device *wdog)
+{
+ return bcm_kona_wdt_set_timeout_reg(wdog,
+ SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK);
+}
+
+static int bcm_kona_wdt_stop(struct watchdog_device *wdog)
+{
+ struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_EN_MASK |
+ SECWDOG_SRSTEN_MASK, 0);
+}
+
+static const struct watchdog_ops bcm_kona_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = bcm_kona_wdt_start,
+ .stop = bcm_kona_wdt_stop,
+ .set_timeout = bcm_kona_wdt_set_timeout,
+ .get_timeleft = bcm_kona_wdt_get_timeleft,
+};
+
+static const struct watchdog_info bcm_kona_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "Broadcom Kona Watchdog Timer",
+};
+
+static struct watchdog_device bcm_kona_wdt_wdd = {
+ .info = &bcm_kona_wdt_info,
+ .ops = &bcm_kona_wdt_ops,
+ .min_timeout = 1,
+ .max_timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION,
+ .timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION,
+};
+
+static int bcm_kona_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bcm_kona_wdt *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ spin_lock_init(&wdt->lock);
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->resolution = SECWDOG_DEFAULT_RESOLUTION;
+ ret = bcm_kona_wdt_set_resolution_reg(wdt);
+ if (ret) {
+ dev_err(dev, "Failed to set resolution (error: %d)", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, wdt);
+ watchdog_set_drvdata(&bcm_kona_wdt_wdd, wdt);
+ bcm_kona_wdt_wdd.parent = dev;
+
+ ret = bcm_kona_wdt_set_timeout_reg(&bcm_kona_wdt_wdd, 0);
+ if (ret) {
+ dev_err(dev, "Failed set watchdog timeout");
+ return ret;
+ }
+
+ watchdog_stop_on_reboot(&bcm_kona_wdt_wdd);
+ watchdog_stop_on_unregister(&bcm_kona_wdt_wdd);
+ ret = devm_watchdog_register_device(dev, &bcm_kona_wdt_wdd);
+ if (ret)
+ return ret;
+
+ bcm_kona_wdt_debug_init(pdev);
+ dev_dbg(dev, "Broadcom Kona Watchdog Timer");
+
+ return 0;
+}
+
+static int bcm_kona_wdt_remove(struct platform_device *pdev)
+{
+ bcm_kona_wdt_debug_exit(pdev);
+ dev_dbg(&pdev->dev, "Watchdog driver disabled");
+
+ return 0;
+}
+
+static const struct of_device_id bcm_kona_wdt_of_match[] = {
+ { .compatible = "brcm,kona-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bcm_kona_wdt_of_match);
+
+static struct platform_driver bcm_kona_wdt_driver = {
+ .driver = {
+ .name = BCM_KONA_WDT_NAME,
+ .of_match_table = bcm_kona_wdt_of_match,
+ },
+ .probe = bcm_kona_wdt_probe,
+ .remove = bcm_kona_wdt_remove,
+};
+
+module_platform_driver(bcm_kona_wdt_driver);
+
+MODULE_ALIAS("platform:" BCM_KONA_WDT_NAME);
+MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom Kona Watchdog Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/bd9576_wdt.c b/drivers/watchdog/bd9576_wdt.c
new file mode 100644
index 000000000..4a20e07fb
--- /dev/null
+++ b/drivers/watchdog/bd9576_wdt.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 ROHM Semiconductors
+ *
+ * ROHM BD9576MUF and BD9573MUF Watchdog driver
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/rohm-bd957x.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+static bool nowayout;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default=\"false\")");
+
+#define HW_MARGIN_MIN 2
+#define HW_MARGIN_MAX 4416
+#define BD957X_WDT_DEFAULT_MARGIN 4416
+#define WATCHDOG_TIMEOUT 30
+
+struct bd9576_wdt_priv {
+ struct gpio_desc *gpiod_ping;
+ struct gpio_desc *gpiod_en;
+ struct device *dev;
+ struct regmap *regmap;
+ bool always_running;
+ struct watchdog_device wdd;
+};
+
+static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv)
+{
+ gpiod_set_value_cansleep(priv->gpiod_en, 0);
+}
+
+static int bd9576_wdt_ping(struct watchdog_device *wdd)
+{
+ struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ /* Pulse */
+ gpiod_set_value_cansleep(priv->gpiod_ping, 1);
+ gpiod_set_value_cansleep(priv->gpiod_ping, 0);
+
+ return 0;
+}
+
+static int bd9576_wdt_start(struct watchdog_device *wdd)
+{
+ struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ gpiod_set_value_cansleep(priv->gpiod_en, 1);
+
+ return bd9576_wdt_ping(wdd);
+}
+
+static int bd9576_wdt_stop(struct watchdog_device *wdd)
+{
+ struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ if (!priv->always_running)
+ bd9576_wdt_disable(priv);
+ else
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return 0;
+}
+
+static const struct watchdog_info bd957x_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+ .identity = "BD957x Watchdog",
+};
+
+static const struct watchdog_ops bd957x_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = bd9576_wdt_start,
+ .stop = bd9576_wdt_stop,
+ .ping = bd9576_wdt_ping,
+};
+
+/* Unit is hundreds of uS */
+#define FASTNG_MIN 23
+
+static int find_closest_fast(int target, int *sel, int *val)
+{
+ int i;
+ int window = FASTNG_MIN;
+
+ for (i = 0; i < 8 && window < target; i++)
+ window <<= 1;
+
+ *val = window;
+ *sel = i;
+
+ if (i == 8)
+ return -EINVAL;
+
+ return 0;
+
+}
+
+static int find_closest_slow_by_fast(int fast_val, int target, int *slowsel)
+{
+ int sel;
+ static const int multipliers[] = {2, 3, 7, 15};
+
+ for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
+ multipliers[sel] * fast_val < target; sel++)
+ ;
+
+ if (sel == ARRAY_SIZE(multipliers))
+ return -EINVAL;
+
+ *slowsel = sel;
+
+ return 0;
+}
+
+static int find_closest_slow(int target, int *slow_sel, int *fast_sel)
+{
+ static const int multipliers[] = {2, 3, 7, 15};
+ int i, j;
+ int val = 0;
+ int window = FASTNG_MIN;
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
+ int slow;
+
+ slow = window * multipliers[j];
+ if (slow >= target && (!val || slow < val)) {
+ val = slow;
+ *fast_sel = i;
+ *slow_sel = j;
+ }
+ }
+ window <<= 1;
+ }
+ if (!val)
+ return -EINVAL;
+
+ return 0;
+}
+
+#define BD957X_WDG_TYPE_WINDOW BIT(5)
+#define BD957X_WDG_TYPE_SLOW 0
+#define BD957X_WDG_TYPE_MASK BIT(5)
+#define BD957X_WDG_NG_RATIO_MASK 0x18
+#define BD957X_WDG_FASTNG_MASK 0x7
+
+static int bd957x_set_wdt_mode(struct bd9576_wdt_priv *priv, int hw_margin,
+ int hw_margin_min)
+{
+ int ret, fastng, slowng, type, reg, mask;
+ struct device *dev = priv->dev;
+
+ /* convert to 100uS */
+ hw_margin *= 10;
+ hw_margin_min *= 10;
+ if (hw_margin_min) {
+ int min;
+
+ type = BD957X_WDG_TYPE_WINDOW;
+ dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
+ ret = find_closest_fast(hw_margin_min, &fastng, &min);
+ if (ret) {
+ dev_err(dev, "bad WDT window for fast timeout\n");
+ return ret;
+ }
+
+ ret = find_closest_slow_by_fast(min, hw_margin, &slowng);
+ if (ret) {
+ dev_err(dev, "bad WDT window\n");
+ return ret;
+ }
+
+ } else {
+ type = BD957X_WDG_TYPE_SLOW;
+ dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
+ ret = find_closest_slow(hw_margin, &slowng, &fastng);
+ if (ret) {
+ dev_err(dev, "bad WDT window\n");
+ return ret;
+ }
+ }
+
+ slowng <<= ffs(BD957X_WDG_NG_RATIO_MASK) - 1;
+ reg = type | slowng | fastng;
+ mask = BD957X_WDG_TYPE_MASK | BD957X_WDG_NG_RATIO_MASK |
+ BD957X_WDG_FASTNG_MASK;
+ ret = regmap_update_bits(priv->regmap, BD957X_REG_WDT_CONF,
+ mask, reg);
+
+ return ret;
+}
+
+static int bd9576_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bd9576_wdt_priv *priv;
+ u32 hw_margin[2];
+ u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
+ int count;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ priv->dev = dev;
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap) {
+ dev_err(dev, "No regmap found\n");
+ return -ENODEV;
+ }
+
+ priv->gpiod_en = devm_fwnode_gpiod_get(dev, dev_fwnode(dev->parent),
+ "rohm,watchdog-enable",
+ GPIOD_OUT_LOW,
+ "watchdog-enable");
+ if (IS_ERR(priv->gpiod_en))
+ return dev_err_probe(dev, PTR_ERR(priv->gpiod_en),
+ "getting watchdog-enable GPIO failed\n");
+
+ priv->gpiod_ping = devm_fwnode_gpiod_get(dev, dev_fwnode(dev->parent),
+ "rohm,watchdog-ping",
+ GPIOD_OUT_LOW,
+ "watchdog-ping");
+ if (IS_ERR(priv->gpiod_ping))
+ return dev_err_probe(dev, PTR_ERR(priv->gpiod_ping),
+ "getting watchdog-ping GPIO failed\n");
+
+ count = device_property_count_u32(dev->parent, "rohm,hw-timeout-ms");
+ if (count < 0 && count != -EINVAL)
+ return count;
+
+ if (count > 0) {
+ if (count > ARRAY_SIZE(hw_margin))
+ return -EINVAL;
+
+ ret = device_property_read_u32_array(dev->parent,
+ "rohm,hw-timeout-ms",
+ hw_margin, count);
+ if (ret < 0)
+ return ret;
+
+ if (count == 1)
+ hw_margin_max = hw_margin[0];
+
+ if (count == 2) {
+ hw_margin_max = hw_margin[1];
+ hw_margin_min = hw_margin[0];
+ }
+ }
+
+ ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min);
+ if (ret)
+ return ret;
+
+ priv->always_running = device_property_read_bool(dev->parent,
+ "always-running");
+
+ watchdog_set_drvdata(&priv->wdd, priv);
+
+ priv->wdd.info = &bd957x_wdt_ident;
+ priv->wdd.ops = &bd957x_wdt_ops;
+ priv->wdd.min_hw_heartbeat_ms = hw_margin_min;
+ priv->wdd.max_hw_heartbeat_ms = hw_margin_max;
+ priv->wdd.parent = dev;
+ priv->wdd.timeout = WATCHDOG_TIMEOUT;
+
+ watchdog_init_timeout(&priv->wdd, 0, dev);
+ watchdog_set_nowayout(&priv->wdd, nowayout);
+
+ watchdog_stop_on_reboot(&priv->wdd);
+
+ if (priv->always_running)
+ bd9576_wdt_start(&priv->wdd);
+
+ return devm_watchdog_register_device(dev, &priv->wdd);
+}
+
+static struct platform_driver bd9576_wdt_driver = {
+ .driver = {
+ .name = "bd9576-wdt",
+ },
+ .probe = bd9576_wdt_probe,
+};
+
+module_platform_driver(bd9576_wdt_driver);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bd9576-wdt");
diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c
new file mode 100644
index 000000000..932a03f44
--- /dev/null
+++ b/drivers/watchdog/booke_wdt.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Watchdog timer for PowerPC Book-E systems
+ *
+ * Author: Matthew McClintock
+ * Maintainer: Kumar Gala <galak@kernel.crashing.org>
+ *
+ * Copyright 2005, 2008, 2010-2011 Freescale Semiconductor Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/smp.h>
+#include <linux/watchdog.h>
+
+#include <asm/reg_booke.h>
+#include <asm/time.h>
+#include <asm/div64.h>
+
+/* If the kernel parameter wdt=1, the watchdog will be enabled at boot.
+ * Also, the wdt_period sets the watchdog timer period timeout.
+ * For E500 cpus the wdt_period sets which bit changing from 0->1 will
+ * trigger a watchdog timeout. This watchdog timeout will occur 3 times, the
+ * first time nothing will happen, the second time a watchdog exception will
+ * occur, and the final time the board will reset.
+ */
+
+
+#ifdef CONFIG_PPC_E500
+#define WDTP(x) ((((x)&0x3)<<30)|(((x)&0x3c)<<15))
+#define WDTP_MASK (WDTP(0x3f))
+#else
+#define WDTP(x) (TCR_WP(x))
+#define WDTP_MASK (TCR_WP_MASK)
+#endif
+
+static bool booke_wdt_enabled;
+module_param(booke_wdt_enabled, bool, 0);
+static int booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT;
+module_param(booke_wdt_period, int, 0);
+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) ")");
+
+#ifdef CONFIG_PPC_E500
+
+/* For the specified period, determine the number of seconds
+ * corresponding to the reset time. There will be a watchdog
+ * exception at approximately 3/5 of this time.
+ *
+ * The formula to calculate this is given by:
+ * 2.5 * (2^(63-period+1)) / timebase_freq
+ *
+ * In order to simplify things, we assume that period is
+ * at least 1. This will still result in a very long timeout.
+ */
+static unsigned long long period_to_sec(unsigned int period)
+{
+ unsigned long long tmp = 1ULL << (64 - period);
+ unsigned long tmp2 = ppc_tb_freq;
+
+ /* tmp may be a very large number and we don't want to overflow,
+ * so divide the timebase freq instead of multiplying tmp
+ */
+ tmp2 = tmp2 / 5 * 2;
+
+ do_div(tmp, tmp2);
+ return tmp;
+}
+
+/*
+ * This procedure will find the highest period which will give a timeout
+ * greater than the one required. e.g. for a bus speed of 66666666 and
+ * a parameter of 2 secs, then this procedure will return a value of 38.
+ */
+static unsigned int sec_to_period(unsigned int secs)
+{
+ unsigned int period;
+ for (period = 63; period > 0; period--) {
+ if (period_to_sec(period) >= secs)
+ return period;
+ }
+ return 0;
+}
+
+#define MAX_WDT_TIMEOUT period_to_sec(1)
+
+#else /* CONFIG_PPC_E500 */
+
+static unsigned long long period_to_sec(unsigned int period)
+{
+ return period;
+}
+
+static unsigned int sec_to_period(unsigned int secs)
+{
+ return secs;
+}
+
+#define MAX_WDT_TIMEOUT 3 /* from Kconfig */
+
+#endif /* !CONFIG_PPC_E500 */
+
+static void __booke_wdt_set(void *data)
+{
+ u32 val;
+ struct watchdog_device *wdog = data;
+
+ val = mfspr(SPRN_TCR);
+ val &= ~WDTP_MASK;
+ val |= WDTP(sec_to_period(wdog->timeout));
+
+ mtspr(SPRN_TCR, val);
+}
+
+static void booke_wdt_set(void *data)
+{
+ on_each_cpu(__booke_wdt_set, data, 0);
+}
+
+static void __booke_wdt_ping(void *data)
+{
+ mtspr(SPRN_TSR, TSR_ENW|TSR_WIS);
+}
+
+static int booke_wdt_ping(struct watchdog_device *wdog)
+{
+ on_each_cpu(__booke_wdt_ping, NULL, 0);
+
+ return 0;
+}
+
+static void __booke_wdt_enable(void *data)
+{
+ u32 val;
+ struct watchdog_device *wdog = data;
+
+ /* clear status before enabling watchdog */
+ __booke_wdt_ping(NULL);
+ val = mfspr(SPRN_TCR);
+ val &= ~WDTP_MASK;
+ val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(sec_to_period(wdog->timeout)));
+
+ mtspr(SPRN_TCR, val);
+}
+
+/**
+ * __booke_wdt_disable - disable the watchdog on the given CPU
+ *
+ * This function is called on each CPU. It disables the watchdog on that CPU.
+ *
+ * TCR[WRC] cannot be changed once it has been set to non-zero, but we can
+ * effectively disable the watchdog by setting its period to the maximum value.
+ */
+static void __booke_wdt_disable(void *data)
+{
+ u32 val;
+
+ val = mfspr(SPRN_TCR);
+ val &= ~(TCR_WIE | WDTP_MASK);
+ mtspr(SPRN_TCR, val);
+
+ /* clear status to make sure nothing is pending */
+ __booke_wdt_ping(NULL);
+
+}
+
+static int booke_wdt_start(struct watchdog_device *wdog)
+{
+ on_each_cpu(__booke_wdt_enable, wdog, 0);
+ pr_debug("watchdog enabled (timeout = %u sec)\n", wdog->timeout);
+
+ return 0;
+}
+
+static int booke_wdt_stop(struct watchdog_device *wdog)
+{
+ on_each_cpu(__booke_wdt_disable, NULL, 0);
+ pr_debug("watchdog disabled\n");
+
+ return 0;
+}
+
+static int booke_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ wdt_dev->timeout = timeout;
+ booke_wdt_set(wdt_dev);
+
+ return 0;
+}
+
+static struct watchdog_info booke_wdt_info __ro_after_init = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "PowerPC Book-E Watchdog",
+};
+
+static const struct watchdog_ops booke_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = booke_wdt_start,
+ .stop = booke_wdt_stop,
+ .ping = booke_wdt_ping,
+ .set_timeout = booke_wdt_set_timeout,
+};
+
+static struct watchdog_device booke_wdt_dev = {
+ .info = &booke_wdt_info,
+ .ops = &booke_wdt_ops,
+ .min_timeout = 1,
+};
+
+static void __exit booke_wdt_exit(void)
+{
+ watchdog_unregister_device(&booke_wdt_dev);
+}
+
+static int __init booke_wdt_init(void)
+{
+ int ret = 0;
+
+ pr_info("powerpc book-e watchdog driver loaded\n");
+ booke_wdt_info.firmware_version = cur_cpu_spec->pvr_value;
+ booke_wdt_set_timeout(&booke_wdt_dev,
+ period_to_sec(booke_wdt_period));
+ watchdog_set_nowayout(&booke_wdt_dev, nowayout);
+ booke_wdt_dev.max_timeout = MAX_WDT_TIMEOUT;
+ if (booke_wdt_enabled)
+ booke_wdt_start(&booke_wdt_dev);
+
+ ret = watchdog_register_device(&booke_wdt_dev);
+
+ return ret;
+}
+
+module_init(booke_wdt_init);
+module_exit(booke_wdt_exit);
+
+MODULE_ALIAS("booke_wdt");
+MODULE_DESCRIPTION("PowerPC Book-E watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/cadence_wdt.c b/drivers/watchdog/cadence_wdt.c
new file mode 100644
index 000000000..bc99e9164
--- /dev/null
+++ b/drivers/watchdog/cadence_wdt.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Cadence WDT driver - Used by Xilinx Zynq
+ *
+ * Copyright (C) 2010 - 2014 Xilinx, Inc.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define CDNS_WDT_DEFAULT_TIMEOUT 10
+/* Supports 1 - 516 sec */
+#define CDNS_WDT_MIN_TIMEOUT 1
+#define CDNS_WDT_MAX_TIMEOUT 516
+
+/* Restart key */
+#define CDNS_WDT_RESTART_KEY 0x00001999
+
+/* Counter register access key */
+#define CDNS_WDT_REGISTER_ACCESS_KEY 0x00920000
+
+/* Counter value divisor */
+#define CDNS_WDT_COUNTER_VALUE_DIVISOR 0x1000
+
+/* Clock prescaler value and selection */
+#define CDNS_WDT_PRESCALE_64 64
+#define CDNS_WDT_PRESCALE_512 512
+#define CDNS_WDT_PRESCALE_4096 4096
+#define CDNS_WDT_PRESCALE_SELECT_64 1
+#define CDNS_WDT_PRESCALE_SELECT_512 2
+#define CDNS_WDT_PRESCALE_SELECT_4096 3
+
+/* Input clock frequency */
+#define CDNS_WDT_CLK_10MHZ 10000000
+#define CDNS_WDT_CLK_75MHZ 75000000
+
+/* Counter maximum value */
+#define CDNS_WDT_COUNTER_MAX 0xFFF
+
+static int wdt_timeout;
+static int nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(wdt_timeout, int, 0644);
+MODULE_PARM_DESC(wdt_timeout,
+ "Watchdog time in seconds. (default="
+ __MODULE_STRING(CDNS_WDT_DEFAULT_TIMEOUT) ")");
+
+module_param(nowayout, int, 0644);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/**
+ * struct cdns_wdt - Watchdog device structure
+ * @regs: baseaddress of device
+ * @rst: reset flag
+ * @clk: struct clk * of a clock source
+ * @prescaler: for saving prescaler value
+ * @ctrl_clksel: counter clock prescaler selection
+ * @io_lock: spinlock for IO register access
+ * @cdns_wdt_device: watchdog device structure
+ *
+ * Structure containing parameters specific to cadence watchdog.
+ */
+struct cdns_wdt {
+ void __iomem *regs;
+ bool rst;
+ struct clk *clk;
+ u32 prescaler;
+ u32 ctrl_clksel;
+ spinlock_t io_lock;
+ struct watchdog_device cdns_wdt_device;
+};
+
+/* Write access to Registers */
+static inline void cdns_wdt_writereg(struct cdns_wdt *wdt, u32 offset, u32 val)
+{
+ writel_relaxed(val, wdt->regs + offset);
+}
+
+/*************************Register Map**************************************/
+
+/* Register Offsets for the WDT */
+#define CDNS_WDT_ZMR_OFFSET 0x0 /* Zero Mode Register */
+#define CDNS_WDT_CCR_OFFSET 0x4 /* Counter Control Register */
+#define CDNS_WDT_RESTART_OFFSET 0x8 /* Restart Register */
+#define CDNS_WDT_SR_OFFSET 0xC /* Status Register */
+
+/*
+ * Zero Mode Register - This register controls how the time out is indicated
+ * and also contains the access code to allow writes to the register (0xABC).
+ */
+#define CDNS_WDT_ZMR_WDEN_MASK 0x00000001 /* Enable the WDT */
+#define CDNS_WDT_ZMR_RSTEN_MASK 0x00000002 /* Enable the reset output */
+#define CDNS_WDT_ZMR_IRQEN_MASK 0x00000004 /* Enable IRQ output */
+#define CDNS_WDT_ZMR_RSTLEN_16 0x00000030 /* Reset pulse of 16 pclk cycles */
+#define CDNS_WDT_ZMR_ZKEY_VAL 0x00ABC000 /* Access key, 0xABC << 12 */
+/*
+ * Counter Control register - This register controls how fast the timer runs
+ * and the reset value and also contains the access code to allow writes to
+ * the register.
+ */
+#define CDNS_WDT_CCR_CRV_MASK 0x00003FFC /* Counter reset value */
+
+/**
+ * cdns_wdt_stop - Stop the watchdog.
+ *
+ * @wdd: watchdog device
+ *
+ * Read the contents of the ZMR register, clear the WDEN bit
+ * in the register and set the access key for successful write.
+ *
+ * Return: always 0
+ */
+static int cdns_wdt_stop(struct watchdog_device *wdd)
+{
+ struct cdns_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->io_lock);
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET,
+ CDNS_WDT_ZMR_ZKEY_VAL & (~CDNS_WDT_ZMR_WDEN_MASK));
+ spin_unlock(&wdt->io_lock);
+
+ return 0;
+}
+
+/**
+ * cdns_wdt_reload - Reload the watchdog timer (i.e. pat the watchdog).
+ *
+ * @wdd: watchdog device
+ *
+ * Write the restart key value (0x00001999) to the restart register.
+ *
+ * Return: always 0
+ */
+static int cdns_wdt_reload(struct watchdog_device *wdd)
+{
+ struct cdns_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->io_lock);
+ cdns_wdt_writereg(wdt, CDNS_WDT_RESTART_OFFSET,
+ CDNS_WDT_RESTART_KEY);
+ spin_unlock(&wdt->io_lock);
+
+ return 0;
+}
+
+/**
+ * cdns_wdt_start - Enable and start the watchdog.
+ *
+ * @wdd: watchdog device
+ *
+ * The counter value is calculated according to the formula:
+ * calculated count = (timeout * clock) / prescaler + 1.
+ * The calculated count is divided by 0x1000 to obtain the field value
+ * to write to counter control register.
+ * Clears the contents of prescaler and counter reset value. Sets the
+ * prescaler to 4096 and the calculated count and access key
+ * to write to CCR Register.
+ * Sets the WDT (WDEN bit) and either the Reset signal(RSTEN bit)
+ * or Interrupt signal(IRQEN) with a specified cycles and the access
+ * key to write to ZMR Register.
+ *
+ * Return: always 0
+ */
+static int cdns_wdt_start(struct watchdog_device *wdd)
+{
+ struct cdns_wdt *wdt = watchdog_get_drvdata(wdd);
+ unsigned int data = 0;
+ unsigned short count;
+ unsigned long clock_f = clk_get_rate(wdt->clk);
+
+ /*
+ * Counter value divisor to obtain the value of
+ * counter reset to be written to control register.
+ */
+ count = (wdd->timeout * (clock_f / wdt->prescaler)) /
+ CDNS_WDT_COUNTER_VALUE_DIVISOR + 1;
+
+ if (count > CDNS_WDT_COUNTER_MAX)
+ count = CDNS_WDT_COUNTER_MAX;
+
+ spin_lock(&wdt->io_lock);
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET,
+ CDNS_WDT_ZMR_ZKEY_VAL);
+
+ count = (count << 2) & CDNS_WDT_CCR_CRV_MASK;
+
+ /* Write counter access key first to be able write to register */
+ data = count | CDNS_WDT_REGISTER_ACCESS_KEY | wdt->ctrl_clksel;
+ cdns_wdt_writereg(wdt, CDNS_WDT_CCR_OFFSET, data);
+ data = CDNS_WDT_ZMR_WDEN_MASK | CDNS_WDT_ZMR_RSTLEN_16 |
+ CDNS_WDT_ZMR_ZKEY_VAL;
+
+ /* Reset on timeout if specified in device tree. */
+ if (wdt->rst) {
+ data |= CDNS_WDT_ZMR_RSTEN_MASK;
+ data &= ~CDNS_WDT_ZMR_IRQEN_MASK;
+ } else {
+ data &= ~CDNS_WDT_ZMR_RSTEN_MASK;
+ data |= CDNS_WDT_ZMR_IRQEN_MASK;
+ }
+ cdns_wdt_writereg(wdt, CDNS_WDT_ZMR_OFFSET, data);
+ cdns_wdt_writereg(wdt, CDNS_WDT_RESTART_OFFSET,
+ CDNS_WDT_RESTART_KEY);
+ spin_unlock(&wdt->io_lock);
+
+ return 0;
+}
+
+/**
+ * cdns_wdt_settimeout - Set a new timeout value for the watchdog device.
+ *
+ * @wdd: watchdog device
+ * @new_time: new timeout value that needs to be set
+ * Return: 0 on success
+ *
+ * Update the watchdog_device timeout with new value which is used when
+ * cdns_wdt_start is called.
+ */
+static int cdns_wdt_settimeout(struct watchdog_device *wdd,
+ unsigned int new_time)
+{
+ wdd->timeout = new_time;
+
+ return cdns_wdt_start(wdd);
+}
+
+/**
+ * cdns_wdt_irq_handler - Notifies of watchdog timeout.
+ *
+ * @irq: interrupt number
+ * @dev_id: pointer to a platform device structure
+ * Return: IRQ_HANDLED
+ *
+ * The handler is invoked when the watchdog times out and a
+ * reset on timeout has not been enabled.
+ */
+static irqreturn_t cdns_wdt_irq_handler(int irq, void *dev_id)
+{
+ struct platform_device *pdev = dev_id;
+
+ dev_info(&pdev->dev,
+ "Watchdog timed out. Internal reset not enabled\n");
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Info structure used to indicate the features supported by the device
+ * to the upper layers. This is defined in watchdog.h header file.
+ */
+static const struct watchdog_info cdns_wdt_info = {
+ .identity = "cdns_wdt watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+/* Watchdog Core Ops */
+static const struct watchdog_ops cdns_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = cdns_wdt_start,
+ .stop = cdns_wdt_stop,
+ .ping = cdns_wdt_reload,
+ .set_timeout = cdns_wdt_settimeout,
+};
+
+static void cdns_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+/************************Platform Operations*****************************/
+/**
+ * cdns_wdt_probe - Probe call for the device.
+ *
+ * @pdev: handle to the platform device structure.
+ * Return: 0 on success, negative error otherwise.
+ *
+ * It does all the memory allocation and registration for the device.
+ */
+static int cdns_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret, irq;
+ unsigned long clock_f;
+ struct cdns_wdt *wdt;
+ struct watchdog_device *cdns_wdt_device;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ cdns_wdt_device = &wdt->cdns_wdt_device;
+ cdns_wdt_device->info = &cdns_wdt_info;
+ cdns_wdt_device->ops = &cdns_wdt_ops;
+ cdns_wdt_device->timeout = CDNS_WDT_DEFAULT_TIMEOUT;
+ cdns_wdt_device->min_timeout = CDNS_WDT_MIN_TIMEOUT;
+ cdns_wdt_device->max_timeout = CDNS_WDT_MAX_TIMEOUT;
+
+ wdt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->regs))
+ return PTR_ERR(wdt->regs);
+
+ /* Register the interrupt */
+ wdt->rst = of_property_read_bool(dev->of_node, "reset-on-timeout");
+ irq = platform_get_irq(pdev, 0);
+ if (!wdt->rst && irq >= 0) {
+ ret = devm_request_irq(dev, irq, cdns_wdt_irq_handler, 0,
+ pdev->name, pdev);
+ if (ret) {
+ dev_err(dev,
+ "cannot register interrupt handler err=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /* Initialize the members of cdns_wdt structure */
+ cdns_wdt_device->parent = dev;
+
+ watchdog_init_timeout(cdns_wdt_device, wdt_timeout, dev);
+ watchdog_set_nowayout(cdns_wdt_device, nowayout);
+ watchdog_stop_on_reboot(cdns_wdt_device);
+ watchdog_set_drvdata(cdns_wdt_device, wdt);
+
+ wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return dev_err_probe(dev, PTR_ERR(wdt->clk),
+ "input clock not found\n");
+
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(dev, "unable to enable clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, cdns_clk_disable_unprepare,
+ wdt->clk);
+ if (ret)
+ return ret;
+
+ clock_f = clk_get_rate(wdt->clk);
+ if (clock_f <= CDNS_WDT_CLK_75MHZ) {
+ wdt->prescaler = CDNS_WDT_PRESCALE_512;
+ wdt->ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_512;
+ } else {
+ wdt->prescaler = CDNS_WDT_PRESCALE_4096;
+ wdt->ctrl_clksel = CDNS_WDT_PRESCALE_SELECT_4096;
+ }
+
+ spin_lock_init(&wdt->io_lock);
+
+ watchdog_stop_on_reboot(cdns_wdt_device);
+ watchdog_stop_on_unregister(cdns_wdt_device);
+ ret = devm_watchdog_register_device(dev, cdns_wdt_device);
+ if (ret)
+ return ret;
+ platform_set_drvdata(pdev, wdt);
+
+ dev_info(dev, "Xilinx Watchdog Timer with timeout %ds%s\n",
+ cdns_wdt_device->timeout, nowayout ? ", nowayout" : "");
+
+ return 0;
+}
+
+/**
+ * cdns_wdt_suspend - Stop the device.
+ *
+ * @dev: handle to the device structure.
+ * Return: 0 always.
+ */
+static int __maybe_unused cdns_wdt_suspend(struct device *dev)
+{
+ struct cdns_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->cdns_wdt_device)) {
+ cdns_wdt_stop(&wdt->cdns_wdt_device);
+ clk_disable_unprepare(wdt->clk);
+ }
+
+ return 0;
+}
+
+/**
+ * cdns_wdt_resume - Resume the device.
+ *
+ * @dev: handle to the device structure.
+ * Return: 0 on success, errno otherwise.
+ */
+static int __maybe_unused cdns_wdt_resume(struct device *dev)
+{
+ int ret;
+ struct cdns_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->cdns_wdt_device)) {
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(dev, "unable to enable clock\n");
+ return ret;
+ }
+ cdns_wdt_start(&wdt->cdns_wdt_device);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(cdns_wdt_pm_ops, cdns_wdt_suspend, cdns_wdt_resume);
+
+static const struct of_device_id cdns_wdt_of_match[] = {
+ { .compatible = "cdns,wdt-r1p2", },
+ { /* end of table */ }
+};
+MODULE_DEVICE_TABLE(of, cdns_wdt_of_match);
+
+/* Driver Structure */
+static struct platform_driver cdns_wdt_driver = {
+ .probe = cdns_wdt_probe,
+ .driver = {
+ .name = "cdns-wdt",
+ .of_match_table = cdns_wdt_of_match,
+ .pm = &cdns_wdt_pm_ops,
+ },
+};
+
+module_platform_driver(cdns_wdt_driver);
+
+MODULE_AUTHOR("Xilinx, Inc.");
+MODULE_DESCRIPTION("Watchdog driver for Cadence WDT");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/cpu5wdt.c b/drivers/watchdog/cpu5wdt.c
new file mode 100644
index 000000000..688b112e7
--- /dev/null
+++ b/drivers/watchdog/cpu5wdt.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * sma cpu5 watchdog driver
+ *
+ * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+
+/* adjustable parameters */
+
+static int verbose;
+static int port = 0x91;
+static int ticks = 10000;
+static DEFINE_SPINLOCK(cpu5wdt_lock);
+
+#define PFX "cpu5wdt: "
+
+#define CPU5WDT_EXTENT 0x0A
+
+#define CPU5WDT_STATUS_REG 0x00
+#define CPU5WDT_TIME_A_REG 0x02
+#define CPU5WDT_TIME_B_REG 0x03
+#define CPU5WDT_MODE_REG 0x04
+#define CPU5WDT_TRIGGER_REG 0x07
+#define CPU5WDT_ENABLE_REG 0x08
+#define CPU5WDT_RESET_REG 0x09
+
+#define CPU5WDT_INTERVAL (HZ/10+1)
+
+/* some device data */
+
+static struct {
+ struct completion stop;
+ int running;
+ struct timer_list timer;
+ int queue;
+ int default_ticks;
+ unsigned long inuse;
+} cpu5wdt_device;
+
+/* generic helper functions */
+
+static void cpu5wdt_trigger(struct timer_list *unused)
+{
+ if (verbose > 2)
+ pr_debug("trigger at %i ticks\n", ticks);
+
+ if (cpu5wdt_device.running)
+ ticks--;
+
+ spin_lock(&cpu5wdt_lock);
+ /* keep watchdog alive */
+ outb(1, port + CPU5WDT_TRIGGER_REG);
+
+ /* requeue?? */
+ if (cpu5wdt_device.queue && ticks)
+ mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);
+ else {
+ /* ticks doesn't matter anyway */
+ complete(&cpu5wdt_device.stop);
+ }
+ spin_unlock(&cpu5wdt_lock);
+
+}
+
+static void cpu5wdt_reset(void)
+{
+ ticks = cpu5wdt_device.default_ticks;
+
+ if (verbose)
+ pr_debug("reset (%i ticks)\n", (int) ticks);
+
+}
+
+static void cpu5wdt_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cpu5wdt_lock, flags);
+ if (!cpu5wdt_device.queue) {
+ cpu5wdt_device.queue = 1;
+ outb(0, port + CPU5WDT_TIME_A_REG);
+ outb(0, port + CPU5WDT_TIME_B_REG);
+ outb(1, port + CPU5WDT_MODE_REG);
+ outb(0, port + CPU5WDT_RESET_REG);
+ outb(0, port + CPU5WDT_ENABLE_REG);
+ mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL);
+ }
+ /* if process dies, counter is not decremented */
+ cpu5wdt_device.running++;
+ spin_unlock_irqrestore(&cpu5wdt_lock, flags);
+}
+
+static int cpu5wdt_stop(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cpu5wdt_lock, flags);
+ if (cpu5wdt_device.running)
+ cpu5wdt_device.running = 0;
+ ticks = cpu5wdt_device.default_ticks;
+ spin_unlock_irqrestore(&cpu5wdt_lock, flags);
+ if (verbose)
+ pr_crit("stop not possible\n");
+ return -EIO;
+}
+
+/* filesystem operations */
+
+static int cpu5wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &cpu5wdt_device.inuse))
+ return -EBUSY;
+ return stream_open(inode, file);
+}
+
+static int cpu5wdt_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &cpu5wdt_device.inuse);
+ return 0;
+}
+
+static long cpu5wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ unsigned int value;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET,
+ .identity = "CPU5 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+ case WDIOC_GETSTATUS:
+ value = inb(port + CPU5WDT_STATUS_REG);
+ value = (value >> 2) & 1;
+ return put_user(value, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_SETOPTIONS:
+ if (get_user(value, p))
+ return -EFAULT;
+ if (value & WDIOS_ENABLECARD)
+ cpu5wdt_start();
+ if (value & WDIOS_DISABLECARD)
+ cpu5wdt_stop();
+ break;
+ case WDIOC_KEEPALIVE:
+ cpu5wdt_reset();
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static ssize_t cpu5wdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (!count)
+ return -EIO;
+ cpu5wdt_reset();
+ return count;
+}
+
+static const struct file_operations cpu5wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = cpu5wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = cpu5wdt_open,
+ .write = cpu5wdt_write,
+ .release = cpu5wdt_release,
+};
+
+static struct miscdevice cpu5wdt_misc = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &cpu5wdt_fops,
+};
+
+/* init/exit function */
+
+static int cpu5wdt_init(void)
+{
+ unsigned int val;
+ int err;
+
+ if (verbose)
+ pr_debug("port=0x%x, verbose=%i\n", port, verbose);
+
+ init_completion(&cpu5wdt_device.stop);
+ cpu5wdt_device.queue = 0;
+ timer_setup(&cpu5wdt_device.timer, cpu5wdt_trigger, 0);
+ cpu5wdt_device.default_ticks = ticks;
+
+ if (!request_region(port, CPU5WDT_EXTENT, PFX)) {
+ pr_err("request_region failed\n");
+ err = -EBUSY;
+ goto no_port;
+ }
+
+ /* watchdog reboot? */
+ val = inb(port + CPU5WDT_STATUS_REG);
+ val = (val >> 2) & 1;
+ if (!val)
+ pr_info("sorry, was my fault\n");
+
+ err = misc_register(&cpu5wdt_misc);
+ if (err < 0) {
+ pr_err("misc_register failed\n");
+ goto no_misc;
+ }
+
+
+ pr_info("init success\n");
+ return 0;
+
+no_misc:
+ release_region(port, CPU5WDT_EXTENT);
+no_port:
+ return err;
+}
+
+static int cpu5wdt_init_module(void)
+{
+ return cpu5wdt_init();
+}
+
+static void cpu5wdt_exit(void)
+{
+ if (cpu5wdt_device.queue) {
+ cpu5wdt_device.queue = 0;
+ wait_for_completion(&cpu5wdt_device.stop);
+ del_timer(&cpu5wdt_device.timer);
+ }
+
+ misc_deregister(&cpu5wdt_misc);
+
+ release_region(port, CPU5WDT_EXTENT);
+
+}
+
+static void cpu5wdt_exit_module(void)
+{
+ cpu5wdt_exit();
+}
+
+/* module entry points */
+
+module_init(cpu5wdt_init_module);
+module_exit(cpu5wdt_exit_module);
+
+MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>");
+MODULE_DESCRIPTION("sma cpu5 watchdog driver");
+MODULE_LICENSE("GPL");
+
+module_param_hw(port, int, ioport, 0);
+MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91");
+
+module_param(verbose, int, 0);
+MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
+
+module_param(ticks, int, 0);
+MODULE_PARM_DESC(ticks, "count down ticks, default is 10000");
diff --git a/drivers/watchdog/cpwd.c b/drivers/watchdog/cpwd.c
new file mode 100644
index 000000000..1eafe0b4d
--- /dev/null
+++ b/drivers/watchdog/cpwd.c
@@ -0,0 +1,662 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* cpwd.c - driver implementation for hardware watchdog
+ * timers found on Sun Microsystems CP1400 and CP1500 boards.
+ *
+ * This device supports both the generic Linux watchdog
+ * interface and Solaris-compatible ioctls as best it is
+ * able.
+ *
+ * NOTE: CP1400 systems appear to have a defective intr_mask
+ * register on the PLD, preventing the disabling of
+ * timer interrupts. We use a timer to periodically
+ * reset 'stopped' watchdogs on affected platforms.
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ * Copyright (C) 2008 David S. Miller <davem@davemloft.net>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/compat.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/uaccess.h>
+
+#include <asm/irq.h>
+#include <asm/watchdog.h>
+
+#define DRIVER_NAME "cpwd"
+
+#define WD_OBPNAME "watchdog"
+#define WD_BADMODEL "SUNW,501-5336"
+#define WD_BTIMEOUT (jiffies + (HZ * 1000))
+#define WD_BLIMIT 0xFFFF
+
+#define WD0_MINOR 212
+#define WD1_MINOR 213
+#define WD2_MINOR 214
+
+/* Internal driver definitions. */
+#define WD0_ID 0
+#define WD1_ID 1
+#define WD2_ID 2
+#define WD_NUMDEVS 3
+
+#define WD_INTR_OFF 0
+#define WD_INTR_ON 1
+
+#define WD_STAT_INIT 0x01 /* Watchdog timer is initialized */
+#define WD_STAT_BSTOP 0x02 /* Watchdog timer is brokenstopped */
+#define WD_STAT_SVCD 0x04 /* Watchdog interrupt occurred */
+
+/* Register value definitions
+ */
+#define WD0_INTR_MASK 0x01 /* Watchdog device interrupt masks */
+#define WD1_INTR_MASK 0x02
+#define WD2_INTR_MASK 0x04
+
+#define WD_S_RUNNING 0x01 /* Watchdog device status running */
+#define WD_S_EXPIRED 0x02 /* Watchdog device status expired */
+
+struct cpwd {
+ void __iomem *regs;
+ spinlock_t lock;
+
+ unsigned int irq;
+
+ unsigned long timeout;
+ bool enabled;
+ bool reboot;
+ bool broken;
+ bool initialized;
+
+ struct {
+ struct miscdevice misc;
+ void __iomem *regs;
+ u8 intr_mask;
+ u8 runstatus;
+ u16 timeout;
+ } devs[WD_NUMDEVS];
+};
+
+static DEFINE_MUTEX(cpwd_mutex);
+static struct cpwd *cpwd_device;
+
+/* Sun uses Altera PLD EPF8820ATC144-4
+ * providing three hardware watchdogs:
+ *
+ * 1) RIC - sends an interrupt when triggered
+ * 2) XIR - asserts XIR_B_RESET when triggered, resets CPU
+ * 3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board
+ *
+ *** Timer register block definition (struct wd_timer_regblk)
+ *
+ * dcntr and limit registers (halfword access):
+ * -------------------
+ * | 15 | ...| 1 | 0 |
+ * -------------------
+ * |- counter val -|
+ * -------------------
+ * dcntr - Current 16-bit downcounter value.
+ * When downcounter reaches '0' watchdog expires.
+ * Reading this register resets downcounter with
+ * 'limit' value.
+ * limit - 16-bit countdown value in 1/10th second increments.
+ * Writing this register begins countdown with input value.
+ * Reading from this register does not affect counter.
+ * NOTES: After watchdog reset, dcntr and limit contain '1'
+ *
+ * status register (byte access):
+ * ---------------------------
+ * | 7 | ... | 2 | 1 | 0 |
+ * --------------+------------
+ * |- UNUSED -| EXP | RUN |
+ * ---------------------------
+ * status- Bit 0 - Watchdog is running
+ * Bit 1 - Watchdog has expired
+ *
+ *** PLD register block definition (struct wd_pld_regblk)
+ *
+ * intr_mask register (byte access):
+ * ---------------------------------
+ * | 7 | ... | 3 | 2 | 1 | 0 |
+ * +-------------+------------------
+ * |- UNUSED -| WD3 | WD2 | WD1 |
+ * ---------------------------------
+ * WD3 - 1 == Interrupt disabled for watchdog 3
+ * WD2 - 1 == Interrupt disabled for watchdog 2
+ * WD1 - 1 == Interrupt disabled for watchdog 1
+ *
+ * pld_status register (byte access):
+ * UNKNOWN, MAGICAL MYSTERY REGISTER
+ *
+ */
+#define WD_TIMER_REGSZ 16
+#define WD0_OFF 0
+#define WD1_OFF (WD_TIMER_REGSZ * 1)
+#define WD2_OFF (WD_TIMER_REGSZ * 2)
+#define PLD_OFF (WD_TIMER_REGSZ * 3)
+
+#define WD_DCNTR 0x00
+#define WD_LIMIT 0x04
+#define WD_STATUS 0x08
+
+#define PLD_IMASK (PLD_OFF + 0x00)
+#define PLD_STATUS (PLD_OFF + 0x04)
+
+static struct timer_list cpwd_timer;
+
+static int wd0_timeout;
+static int wd1_timeout;
+static int wd2_timeout;
+
+module_param(wd0_timeout, int, 0);
+MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs");
+module_param(wd1_timeout, int, 0);
+MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs");
+module_param(wd2_timeout, int, 0);
+MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs");
+
+MODULE_AUTHOR("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION("Hardware watchdog driver for Sun Microsystems CP1400/1500");
+MODULE_LICENSE("GPL");
+
+static void cpwd_writew(u16 val, void __iomem *addr)
+{
+ writew(cpu_to_le16(val), addr);
+}
+static u16 cpwd_readw(void __iomem *addr)
+{
+ u16 val = readw(addr);
+
+ return le16_to_cpu(val);
+}
+
+static void cpwd_writeb(u8 val, void __iomem *addr)
+{
+ writeb(val, addr);
+}
+
+static u8 cpwd_readb(void __iomem *addr)
+{
+ return readb(addr);
+}
+
+/* Enable or disable watchdog interrupts
+ * Because of the CP1400 defect this should only be
+ * called during initialzation or by wd_[start|stop]timer()
+ *
+ * index - sub-device index, or -1 for 'all'
+ * enable - non-zero to enable interrupts, zero to disable
+ */
+static void cpwd_toggleintr(struct cpwd *p, int index, int enable)
+{
+ unsigned char curregs = cpwd_readb(p->regs + PLD_IMASK);
+ unsigned char setregs =
+ (index == -1) ?
+ (WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) :
+ (p->devs[index].intr_mask);
+
+ if (enable == WD_INTR_ON)
+ curregs &= ~setregs;
+ else
+ curregs |= setregs;
+
+ cpwd_writeb(curregs, p->regs + PLD_IMASK);
+}
+
+/* Restarts timer with maximum limit value and
+ * does not unset 'brokenstop' value.
+ */
+static void cpwd_resetbrokentimer(struct cpwd *p, int index)
+{
+ cpwd_toggleintr(p, index, WD_INTR_ON);
+ cpwd_writew(WD_BLIMIT, p->devs[index].regs + WD_LIMIT);
+}
+
+/* Timer method called to reset stopped watchdogs--
+ * because of the PLD bug on CP1400, we cannot mask
+ * interrupts within the PLD so me must continually
+ * reset the timers ad infinitum.
+ */
+static void cpwd_brokentimer(struct timer_list *unused)
+{
+ struct cpwd *p = cpwd_device;
+ int id, tripped = 0;
+
+ /* kill a running timer instance, in case we
+ * were called directly instead of by kernel timer
+ */
+ if (timer_pending(&cpwd_timer))
+ del_timer(&cpwd_timer);
+
+ for (id = 0; id < WD_NUMDEVS; id++) {
+ if (p->devs[id].runstatus & WD_STAT_BSTOP) {
+ ++tripped;
+ cpwd_resetbrokentimer(p, id);
+ }
+ }
+
+ if (tripped) {
+ /* there is at least one timer brokenstopped-- reschedule */
+ cpwd_timer.expires = WD_BTIMEOUT;
+ add_timer(&cpwd_timer);
+ }
+}
+
+/* Reset countdown timer with 'limit' value and continue countdown.
+ * This will not start a stopped timer.
+ */
+static void cpwd_pingtimer(struct cpwd *p, int index)
+{
+ if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING)
+ cpwd_readw(p->devs[index].regs + WD_DCNTR);
+}
+
+/* Stop a running watchdog timer-- the timer actually keeps
+ * running, but the interrupt is masked so that no action is
+ * taken upon expiration.
+ */
+static void cpwd_stoptimer(struct cpwd *p, int index)
+{
+ if (cpwd_readb(p->devs[index].regs + WD_STATUS) & WD_S_RUNNING) {
+ cpwd_toggleintr(p, index, WD_INTR_OFF);
+
+ if (p->broken) {
+ p->devs[index].runstatus |= WD_STAT_BSTOP;
+ cpwd_brokentimer(NULL);
+ }
+ }
+}
+
+/* Start a watchdog timer with the specified limit value
+ * If the watchdog is running, it will be restarted with
+ * the provided limit value.
+ *
+ * This function will enable interrupts on the specified
+ * watchdog.
+ */
+static void cpwd_starttimer(struct cpwd *p, int index)
+{
+ if (p->broken)
+ p->devs[index].runstatus &= ~WD_STAT_BSTOP;
+
+ p->devs[index].runstatus &= ~WD_STAT_SVCD;
+
+ cpwd_writew(p->devs[index].timeout, p->devs[index].regs + WD_LIMIT);
+ cpwd_toggleintr(p, index, WD_INTR_ON);
+}
+
+static int cpwd_getstatus(struct cpwd *p, int index)
+{
+ unsigned char stat = cpwd_readb(p->devs[index].regs + WD_STATUS);
+ unsigned char intr = cpwd_readb(p->devs[index].regs + PLD_IMASK);
+ unsigned char ret = WD_STOPPED;
+
+ /* determine STOPPED */
+ if (!stat)
+ return ret;
+
+ /* determine EXPIRED vs FREERUN vs RUNNING */
+ else if (WD_S_EXPIRED & stat) {
+ ret = WD_EXPIRED;
+ } else if (WD_S_RUNNING & stat) {
+ if (intr & p->devs[index].intr_mask) {
+ ret = WD_FREERUN;
+ } else {
+ /* Fudge WD_EXPIRED status for defective CP1400--
+ * IF timer is running
+ * AND brokenstop is set
+ * AND an interrupt has been serviced
+ * we are WD_EXPIRED.
+ *
+ * IF timer is running
+ * AND brokenstop is set
+ * AND no interrupt has been serviced
+ * we are WD_FREERUN.
+ */
+ if (p->broken &&
+ (p->devs[index].runstatus & WD_STAT_BSTOP)) {
+ if (p->devs[index].runstatus & WD_STAT_SVCD) {
+ ret = WD_EXPIRED;
+ } else {
+ /* we could as well pretend
+ * we are expired */
+ ret = WD_FREERUN;
+ }
+ } else {
+ ret = WD_RUNNING;
+ }
+ }
+ }
+
+ /* determine SERVICED */
+ if (p->devs[index].runstatus & WD_STAT_SVCD)
+ ret |= WD_SERVICED;
+
+ return ret;
+}
+
+static irqreturn_t cpwd_interrupt(int irq, void *dev_id)
+{
+ struct cpwd *p = dev_id;
+
+ /* Only WD0 will interrupt-- others are NMI and we won't
+ * see them here....
+ */
+ spin_lock_irq(&p->lock);
+
+ cpwd_stoptimer(p, WD0_ID);
+ p->devs[WD0_ID].runstatus |= WD_STAT_SVCD;
+
+ spin_unlock_irq(&p->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int cpwd_open(struct inode *inode, struct file *f)
+{
+ struct cpwd *p = cpwd_device;
+
+ mutex_lock(&cpwd_mutex);
+ switch (iminor(inode)) {
+ case WD0_MINOR:
+ case WD1_MINOR:
+ case WD2_MINOR:
+ break;
+
+ default:
+ mutex_unlock(&cpwd_mutex);
+ return -ENODEV;
+ }
+
+ /* Register IRQ on first open of device */
+ if (!p->initialized) {
+ if (request_irq(p->irq, &cpwd_interrupt,
+ IRQF_SHARED, DRIVER_NAME, p)) {
+ pr_err("Cannot register IRQ %d\n", p->irq);
+ mutex_unlock(&cpwd_mutex);
+ return -EBUSY;
+ }
+ p->initialized = true;
+ }
+
+ mutex_unlock(&cpwd_mutex);
+
+ return stream_open(inode, f);
+}
+
+static int cpwd_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static long cpwd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ static const struct watchdog_info info = {
+ .options = WDIOF_SETTIMEOUT,
+ .firmware_version = 1,
+ .identity = DRIVER_NAME,
+ };
+ void __user *argp = (void __user *)arg;
+ struct inode *inode = file_inode(file);
+ int index = iminor(inode) - WD0_MINOR;
+ struct cpwd *p = cpwd_device;
+ int setopt = 0;
+
+ switch (cmd) {
+ /* Generic Linux IOCTLs */
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &info, sizeof(struct watchdog_info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, (int __user *)argp))
+ return -EFAULT;
+ break;
+
+ case WDIOC_KEEPALIVE:
+ cpwd_pingtimer(p, index);
+ break;
+
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&setopt, argp, sizeof(unsigned int)))
+ return -EFAULT;
+
+ if (setopt & WDIOS_DISABLECARD) {
+ if (p->enabled)
+ return -EINVAL;
+ cpwd_stoptimer(p, index);
+ } else if (setopt & WDIOS_ENABLECARD) {
+ cpwd_starttimer(p, index);
+ } else {
+ return -EINVAL;
+ }
+ break;
+
+ /* Solaris-compatible IOCTLs */
+ case WIOCGSTAT:
+ setopt = cpwd_getstatus(p, index);
+ if (copy_to_user(argp, &setopt, sizeof(unsigned int)))
+ return -EFAULT;
+ break;
+
+ case WIOCSTART:
+ cpwd_starttimer(p, index);
+ break;
+
+ case WIOCSTOP:
+ if (p->enabled)
+ return -EINVAL;
+
+ cpwd_stoptimer(p, index);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static long cpwd_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ return cpwd_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+
+static ssize_t cpwd_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct inode *inode = file_inode(file);
+ struct cpwd *p = cpwd_device;
+ int index = iminor(inode);
+
+ if (count) {
+ cpwd_pingtimer(p, index);
+ return 1;
+ }
+
+ return 0;
+}
+
+static ssize_t cpwd_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ return -EINVAL;
+}
+
+static const struct file_operations cpwd_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = cpwd_ioctl,
+ .compat_ioctl = cpwd_compat_ioctl,
+ .open = cpwd_open,
+ .write = cpwd_write,
+ .read = cpwd_read,
+ .release = cpwd_release,
+ .llseek = no_llseek,
+};
+
+static int cpwd_probe(struct platform_device *op)
+{
+ struct device_node *options;
+ const char *str_prop;
+ const void *prop_val;
+ int i, err = -EINVAL;
+ struct cpwd *p;
+
+ if (cpwd_device)
+ return -EINVAL;
+
+ p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ p->irq = op->archdata.irqs[0];
+
+ spin_lock_init(&p->lock);
+
+ p->regs = of_ioremap(&op->resource[0], 0,
+ 4 * WD_TIMER_REGSZ, DRIVER_NAME);
+ if (!p->regs) {
+ pr_err("Unable to map registers\n");
+ return -ENOMEM;
+ }
+
+ options = of_find_node_by_path("/options");
+ if (!options) {
+ err = -ENODEV;
+ pr_err("Unable to find /options node\n");
+ goto out_iounmap;
+ }
+
+ prop_val = of_get_property(options, "watchdog-enable?", NULL);
+ p->enabled = (prop_val ? true : false);
+
+ prop_val = of_get_property(options, "watchdog-reboot?", NULL);
+ p->reboot = (prop_val ? true : false);
+
+ str_prop = of_get_property(options, "watchdog-timeout", NULL);
+ if (str_prop)
+ p->timeout = simple_strtoul(str_prop, NULL, 10);
+
+ of_node_put(options);
+
+ /* CP1400s seem to have broken PLD implementations-- the
+ * interrupt_mask register cannot be written, so no timer
+ * interrupts can be masked within the PLD.
+ */
+ str_prop = of_get_property(op->dev.of_node, "model", NULL);
+ p->broken = (str_prop && !strcmp(str_prop, WD_BADMODEL));
+
+ if (!p->enabled)
+ cpwd_toggleintr(p, -1, WD_INTR_OFF);
+
+ for (i = 0; i < WD_NUMDEVS; i++) {
+ static const char *cpwd_names[] = { "RIC", "XIR", "POR" };
+ static int *parms[] = { &wd0_timeout,
+ &wd1_timeout,
+ &wd2_timeout };
+ struct miscdevice *mp = &p->devs[i].misc;
+
+ mp->minor = WD0_MINOR + i;
+ mp->name = cpwd_names[i];
+ mp->fops = &cpwd_fops;
+
+ p->devs[i].regs = p->regs + (i * WD_TIMER_REGSZ);
+ p->devs[i].intr_mask = (WD0_INTR_MASK << i);
+ p->devs[i].runstatus &= ~WD_STAT_BSTOP;
+ p->devs[i].runstatus |= WD_STAT_INIT;
+ p->devs[i].timeout = p->timeout;
+ if (*parms[i])
+ p->devs[i].timeout = *parms[i];
+
+ err = misc_register(&p->devs[i].misc);
+ if (err) {
+ pr_err("Could not register misc device for dev %d\n",
+ i);
+ goto out_unregister;
+ }
+ }
+
+ if (p->broken) {
+ timer_setup(&cpwd_timer, cpwd_brokentimer, 0);
+ cpwd_timer.expires = WD_BTIMEOUT;
+
+ pr_info("PLD defect workaround enabled for model %s\n",
+ WD_BADMODEL);
+ }
+
+ platform_set_drvdata(op, p);
+ cpwd_device = p;
+ return 0;
+
+out_unregister:
+ for (i--; i >= 0; i--)
+ misc_deregister(&p->devs[i].misc);
+
+out_iounmap:
+ of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ);
+
+ return err;
+}
+
+static int cpwd_remove(struct platform_device *op)
+{
+ struct cpwd *p = platform_get_drvdata(op);
+ int i;
+
+ for (i = 0; i < WD_NUMDEVS; i++) {
+ misc_deregister(&p->devs[i].misc);
+
+ if (!p->enabled) {
+ cpwd_stoptimer(p, i);
+ if (p->devs[i].runstatus & WD_STAT_BSTOP)
+ cpwd_resetbrokentimer(p, i);
+ }
+ }
+
+ if (p->broken)
+ del_timer_sync(&cpwd_timer);
+
+ if (p->initialized)
+ free_irq(p->irq, p);
+
+ of_iounmap(&op->resource[0], p->regs, 4 * WD_TIMER_REGSZ);
+
+ cpwd_device = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id cpwd_match[] = {
+ {
+ .name = "watchdog",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cpwd_match);
+
+static struct platform_driver cpwd_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = cpwd_match,
+ },
+ .probe = cpwd_probe,
+ .remove = cpwd_remove,
+};
+
+module_platform_driver(cpwd_driver);
diff --git a/drivers/watchdog/da9052_wdt.c b/drivers/watchdog/da9052_wdt.c
new file mode 100644
index 000000000..d708c091b
--- /dev/null
+++ b/drivers/watchdog/da9052_wdt.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * System monitoring driver for DA9052 PMICs.
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: Anthony Olech <Anthony.Olech@diasemi.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/time.h>
+#include <linux/watchdog.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+
+#include <linux/mfd/da9052/reg.h>
+#include <linux/mfd/da9052/da9052.h>
+
+#define DA9052_DEF_TIMEOUT 4
+#define DA9052_TWDMIN 256
+
+struct da9052_wdt_data {
+ struct watchdog_device wdt;
+ struct da9052 *da9052;
+ unsigned long jpast;
+};
+
+static const struct {
+ u8 reg_val;
+ int time; /* Seconds */
+} da9052_wdt_maps[] = {
+ { 1, 2 },
+ { 2, 4 },
+ { 3, 8 },
+ { 4, 16 },
+ { 5, 32 },
+ { 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */
+ { 6, 65 },
+ { 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */
+ { 7, 131 },
+};
+
+
+static int da9052_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct da9052 *da9052 = driver_data->da9052;
+ int ret, i;
+
+ /*
+ * Disable the Watchdog timer before setting
+ * new time out.
+ */
+ ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
+ DA9052_CONTROLD_TWDSCALE, 0);
+ if (ret < 0) {
+ dev_err(da9052->dev, "Failed to disable watchdog bit, %d\n",
+ ret);
+ return ret;
+ }
+ if (timeout) {
+ /*
+ * To change the timeout, da9052 needs to
+ * be disabled for at least 150 us.
+ */
+ udelay(150);
+
+ /* Set the desired timeout */
+ for (i = 0; i < ARRAY_SIZE(da9052_wdt_maps); i++)
+ if (da9052_wdt_maps[i].time == timeout)
+ break;
+
+ if (i == ARRAY_SIZE(da9052_wdt_maps))
+ ret = -EINVAL;
+ else
+ ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
+ DA9052_CONTROLD_TWDSCALE,
+ da9052_wdt_maps[i].reg_val);
+ if (ret < 0) {
+ dev_err(da9052->dev,
+ "Failed to update timescale bit, %d\n", ret);
+ return ret;
+ }
+
+ wdt_dev->timeout = timeout;
+ driver_data->jpast = jiffies;
+ }
+
+ return 0;
+}
+
+static int da9052_wdt_start(struct watchdog_device *wdt_dev)
+{
+ return da9052_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+}
+
+static int da9052_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ return da9052_wdt_set_timeout(wdt_dev, 0);
+}
+
+static int da9052_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct da9052 *da9052 = driver_data->da9052;
+ unsigned long msec, jnow = jiffies;
+ int ret;
+
+ /*
+ * We have a minimum time for watchdog window called TWDMIN. A write
+ * to the watchdog before this elapsed time should cause an error.
+ */
+ msec = (jnow - driver_data->jpast) * 1000/HZ;
+ if (msec < DA9052_TWDMIN)
+ mdelay(msec);
+
+ /* Reset the watchdog timer */
+ ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
+ DA9052_CONTROLD_WATCHDOG, 1 << 7);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * FIXME: Reset the watchdog core, in general PMIC
+ * is supposed to do this
+ */
+ return da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
+ DA9052_CONTROLD_WATCHDOG, 0 << 7);
+}
+
+static const struct watchdog_info da9052_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "DA9052 Watchdog",
+};
+
+static const struct watchdog_ops da9052_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = da9052_wdt_start,
+ .stop = da9052_wdt_stop,
+ .ping = da9052_wdt_ping,
+ .set_timeout = da9052_wdt_set_timeout,
+};
+
+
+static int da9052_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da9052 *da9052 = dev_get_drvdata(dev->parent);
+ struct da9052_wdt_data *driver_data;
+ struct watchdog_device *da9052_wdt;
+ int ret;
+
+ driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data)
+ return -ENOMEM;
+ driver_data->da9052 = da9052;
+
+ da9052_wdt = &driver_data->wdt;
+
+ da9052_wdt->timeout = DA9052_DEF_TIMEOUT;
+ da9052_wdt->info = &da9052_wdt_info;
+ da9052_wdt->ops = &da9052_wdt_ops;
+ da9052_wdt->parent = dev;
+ watchdog_set_drvdata(da9052_wdt, driver_data);
+
+ ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG,
+ DA9052_CONTROLD_TWDSCALE, 0);
+ if (ret < 0) {
+ dev_err(dev, "Failed to disable watchdog bits, %d\n", ret);
+ return ret;
+ }
+
+ return devm_watchdog_register_device(dev, &driver_data->wdt);
+}
+
+static struct platform_driver da9052_wdt_driver = {
+ .probe = da9052_wdt_probe,
+ .driver = {
+ .name = "da9052-watchdog",
+ },
+};
+
+module_platform_driver(da9052_wdt_driver);
+
+MODULE_AUTHOR("Anthony Olech <Anthony.Olech@diasemi.com>");
+MODULE_DESCRIPTION("DA9052 SM Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-watchdog");
diff --git a/drivers/watchdog/da9055_wdt.c b/drivers/watchdog/da9055_wdt.c
new file mode 100644
index 000000000..389a4bdd2
--- /dev/null
+++ b/drivers/watchdog/da9055_wdt.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * System monitoring driver for DA9055 PMICs.
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/delay.h>
+
+#include <linux/mfd/da9055/core.h>
+#include <linux/mfd/da9055/reg.h>
+
+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 DA9055_DEF_TIMEOUT 4
+#define DA9055_TWDMIN 256
+
+struct da9055_wdt_data {
+ struct watchdog_device wdt;
+ struct da9055 *da9055;
+};
+
+static const struct {
+ u8 reg_val;
+ int user_time; /* In seconds */
+} da9055_wdt_maps[] = {
+ { 0, 0 },
+ { 1, 2 },
+ { 2, 4 },
+ { 3, 8 },
+ { 4, 16 },
+ { 5, 32 },
+ { 5, 33 }, /* Actual time 32.768s so included both 32s and 33s */
+ { 6, 65 },
+ { 6, 66 }, /* Actual time 65.536s so include both, 65s and 66s */
+ { 7, 131 },
+};
+
+static int da9055_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct da9055 *da9055 = driver_data->da9055;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(da9055_wdt_maps); i++)
+ if (da9055_wdt_maps[i].user_time == timeout)
+ break;
+
+ if (i == ARRAY_SIZE(da9055_wdt_maps))
+ ret = -EINVAL;
+ else
+ ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_B,
+ DA9055_TWDSCALE_MASK,
+ da9055_wdt_maps[i].reg_val <<
+ DA9055_TWDSCALE_SHIFT);
+ if (ret < 0) {
+ dev_err(da9055->dev,
+ "Failed to update timescale bit, %d\n", ret);
+ return ret;
+ }
+
+ wdt_dev->timeout = timeout;
+
+ return 0;
+}
+
+static int da9055_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct da9055 *da9055 = driver_data->da9055;
+
+ /*
+ * We have a minimum time for watchdog window called TWDMIN. A write
+ * to the watchdog before this elapsed time will cause an error.
+ */
+ mdelay(DA9055_TWDMIN);
+
+ /* Reset the watchdog timer */
+ return da9055_reg_update(da9055, DA9055_REG_CONTROL_E,
+ DA9055_WATCHDOG_MASK, 1);
+}
+
+static int da9055_wdt_start(struct watchdog_device *wdt_dev)
+{
+ return da9055_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+}
+
+static int da9055_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ return da9055_wdt_set_timeout(wdt_dev, 0);
+}
+
+static const struct watchdog_info da9055_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "DA9055 Watchdog",
+};
+
+static const struct watchdog_ops da9055_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = da9055_wdt_start,
+ .stop = da9055_wdt_stop,
+ .ping = da9055_wdt_ping,
+ .set_timeout = da9055_wdt_set_timeout,
+};
+
+static int da9055_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da9055 *da9055 = dev_get_drvdata(dev->parent);
+ struct da9055_wdt_data *driver_data;
+ struct watchdog_device *da9055_wdt;
+ int ret;
+
+ driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data)
+ return -ENOMEM;
+
+ driver_data->da9055 = da9055;
+
+ da9055_wdt = &driver_data->wdt;
+
+ da9055_wdt->timeout = DA9055_DEF_TIMEOUT;
+ da9055_wdt->info = &da9055_wdt_info;
+ da9055_wdt->ops = &da9055_wdt_ops;
+ da9055_wdt->parent = dev;
+ watchdog_set_nowayout(da9055_wdt, nowayout);
+ watchdog_set_drvdata(da9055_wdt, driver_data);
+
+ ret = da9055_wdt_stop(da9055_wdt);
+ if (ret < 0) {
+ dev_err(dev, "Failed to stop watchdog, %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_watchdog_register_device(dev, &driver_data->wdt);
+ if (ret != 0)
+ dev_err(da9055->dev, "watchdog_register_device() failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+static struct platform_driver da9055_wdt_driver = {
+ .probe = da9055_wdt_probe,
+ .driver = {
+ .name = "da9055-watchdog",
+ },
+};
+
+module_platform_driver(da9055_wdt_driver);
+
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_DESCRIPTION("DA9055 watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9055-watchdog");
diff --git a/drivers/watchdog/da9062_wdt.c b/drivers/watchdog/da9062_wdt.c
new file mode 100644
index 000000000..f02cbd530
--- /dev/null
+++ b/drivers/watchdog/da9062_wdt.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog device driver for DA9062 and DA9061 PMICs
+ * Copyright (C) 2015 Dialog Semiconductor Ltd.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/mfd/da9062/registers.h>
+#include <linux/mfd/da9062/core.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+
+static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
+#define DA9062_TWDSCALE_DISABLE 0
+#define DA9062_TWDSCALE_MIN 1
+#define DA9062_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
+#define DA9062_WDT_MIN_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MIN]
+#define DA9062_WDT_MAX_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX]
+#define DA9062_WDG_DEFAULT_TIMEOUT wdt_timeout[DA9062_TWDSCALE_MAX-1]
+#define DA9062_RESET_PROTECTION_MS 300
+
+struct da9062_watchdog {
+ struct da9062 *hw;
+ struct watchdog_device wdtdev;
+ bool use_sw_pm;
+};
+
+static unsigned int da9062_wdt_read_timeout(struct da9062_watchdog *wdt)
+{
+ unsigned int val;
+
+ regmap_read(wdt->hw->regmap, DA9062AA_CONTROL_D, &val);
+
+ return wdt_timeout[val & DA9062AA_TWDSCALE_MASK];
+}
+
+static unsigned int da9062_wdt_timeout_to_sel(unsigned int secs)
+{
+ unsigned int i;
+
+ for (i = DA9062_TWDSCALE_MIN; i <= DA9062_TWDSCALE_MAX; i++) {
+ if (wdt_timeout[i] >= secs)
+ return i;
+ }
+
+ return DA9062_TWDSCALE_MAX;
+}
+
+static int da9062_reset_watchdog_timer(struct da9062_watchdog *wdt)
+{
+ return regmap_update_bits(wdt->hw->regmap, DA9062AA_CONTROL_F,
+ DA9062AA_WATCHDOG_MASK,
+ DA9062AA_WATCHDOG_MASK);
+}
+
+static int da9062_wdt_update_timeout_register(struct da9062_watchdog *wdt,
+ unsigned int regval)
+{
+ struct da9062 *chip = wdt->hw;
+
+ regmap_update_bits(chip->regmap,
+ DA9062AA_CONTROL_D,
+ DA9062AA_TWDSCALE_MASK,
+ DA9062_TWDSCALE_DISABLE);
+
+ usleep_range(150, 300);
+
+ return regmap_update_bits(chip->regmap,
+ DA9062AA_CONTROL_D,
+ DA9062AA_TWDSCALE_MASK,
+ regval);
+}
+
+static int da9062_wdt_start(struct watchdog_device *wdd)
+{
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+ unsigned int selector;
+ int ret;
+
+ selector = da9062_wdt_timeout_to_sel(wdt->wdtdev.timeout);
+ ret = da9062_wdt_update_timeout_register(wdt, selector);
+ if (ret)
+ dev_err(wdt->hw->dev, "Watchdog failed to start (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9062_wdt_stop(struct watchdog_device *wdd)
+{
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = regmap_update_bits(wdt->hw->regmap,
+ DA9062AA_CONTROL_D,
+ DA9062AA_TWDSCALE_MASK,
+ DA9062_TWDSCALE_DISABLE);
+ if (ret)
+ dev_err(wdt->hw->dev, "Watchdog failed to stop (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9062_wdt_ping(struct watchdog_device *wdd)
+{
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ /*
+ * Prevent pings from occurring late in system poweroff/reboot sequence
+ * and possibly locking out restart handler from accessing i2c bus.
+ */
+ if (system_state > SYSTEM_RUNNING)
+ return 0;
+
+ ret = da9062_reset_watchdog_timer(wdt);
+ if (ret)
+ dev_err(wdt->hw->dev, "Failed to ping the watchdog (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9062_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+ unsigned int selector;
+ int ret;
+
+ selector = da9062_wdt_timeout_to_sel(timeout);
+ ret = da9062_wdt_update_timeout_register(wdt, selector);
+ if (ret)
+ dev_err(wdt->hw->dev, "Failed to set watchdog timeout (err = %d)\n",
+ ret);
+ else
+ wdd->timeout = wdt_timeout[selector];
+
+ return ret;
+}
+
+static int da9062_wdt_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+ struct i2c_client *client = to_i2c_client(wdt->hw->dev);
+ int ret;
+
+ /* Don't use regmap because it is not atomic safe */
+ ret = i2c_smbus_write_byte_data(client, DA9062AA_CONTROL_F,
+ DA9062AA_SHUTDOWN_MASK);
+ if (ret < 0)
+ dev_alert(wdt->hw->dev, "Failed to shutdown (err = %d)\n",
+ ret);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return ret;
+}
+
+static const struct watchdog_info da9062_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "DA9062 WDT",
+};
+
+static const struct watchdog_ops da9062_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = da9062_wdt_start,
+ .stop = da9062_wdt_stop,
+ .ping = da9062_wdt_ping,
+ .set_timeout = da9062_wdt_set_timeout,
+ .restart = da9062_wdt_restart,
+};
+
+static const struct of_device_id da9062_compatible_id_table[] = {
+ { .compatible = "dlg,da9062-watchdog", },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, da9062_compatible_id_table);
+
+static int da9062_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ unsigned int timeout;
+ struct da9062 *chip;
+ struct da9062_watchdog *wdt;
+
+ chip = dev_get_drvdata(dev->parent);
+ if (!chip)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
+
+ wdt->hw = chip;
+
+ wdt->wdtdev.info = &da9062_watchdog_info;
+ wdt->wdtdev.ops = &da9062_watchdog_ops;
+ wdt->wdtdev.min_timeout = DA9062_WDT_MIN_TIMEOUT;
+ wdt->wdtdev.max_timeout = DA9062_WDT_MAX_TIMEOUT;
+ wdt->wdtdev.min_hw_heartbeat_ms = DA9062_RESET_PROTECTION_MS;
+ wdt->wdtdev.timeout = DA9062_WDG_DEFAULT_TIMEOUT;
+ wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+ wdt->wdtdev.parent = dev;
+
+ watchdog_set_restart_priority(&wdt->wdtdev, 128);
+
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+ dev_set_drvdata(dev, &wdt->wdtdev);
+
+ timeout = da9062_wdt_read_timeout(wdt);
+ if (timeout)
+ wdt->wdtdev.timeout = timeout;
+
+ /* Set timeout from DT value if available */
+ watchdog_init_timeout(&wdt->wdtdev, 0, dev);
+
+ if (timeout) {
+ da9062_wdt_set_timeout(&wdt->wdtdev, wdt->wdtdev.timeout);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdtdev.status);
+ }
+
+ return devm_watchdog_register_device(dev, &wdt->wdtdev);
+}
+
+static int __maybe_unused da9062_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+
+ if (!wdt->use_sw_pm)
+ return 0;
+
+ if (watchdog_active(wdd))
+ return da9062_wdt_stop(wdd);
+
+ return 0;
+}
+
+static int __maybe_unused da9062_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ struct da9062_watchdog *wdt = watchdog_get_drvdata(wdd);
+
+ if (!wdt->use_sw_pm)
+ return 0;
+
+ if (watchdog_active(wdd))
+ return da9062_wdt_start(wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(da9062_wdt_pm_ops,
+ da9062_wdt_suspend, da9062_wdt_resume);
+
+static struct platform_driver da9062_wdt_driver = {
+ .probe = da9062_wdt_probe,
+ .driver = {
+ .name = "da9062-watchdog",
+ .pm = &da9062_wdt_pm_ops,
+ .of_match_table = da9062_compatible_id_table,
+ },
+};
+module_platform_driver(da9062_wdt_driver);
+
+MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
+MODULE_DESCRIPTION("WDT device driver for Dialog DA9062 and DA9061");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9062-watchdog");
diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c
new file mode 100644
index 000000000..09a4af4c5
--- /dev/null
+++ b/drivers/watchdog/da9063_wdt.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for DA9063 PMICs.
+ *
+ * Copyright(c) 2012 Dialog Semiconductor Ltd.
+ *
+ * Author: Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/mfd/da9063/registers.h>
+#include <linux/mfd/da9063/core.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/*
+ * Watchdog selector to timeout in seconds.
+ * 0: WDT disabled;
+ * others: timeout = 2048 ms * 2^(TWDSCALE-1).
+ */
+static const unsigned int wdt_timeout[] = { 0, 2, 4, 8, 16, 32, 65, 131 };
+static bool use_sw_pm;
+
+#define DA9063_TWDSCALE_DISABLE 0
+#define DA9063_TWDSCALE_MIN 1
+#define DA9063_TWDSCALE_MAX (ARRAY_SIZE(wdt_timeout) - 1)
+#define DA9063_WDT_MIN_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MIN]
+#define DA9063_WDT_MAX_TIMEOUT wdt_timeout[DA9063_TWDSCALE_MAX]
+#define DA9063_WDG_TIMEOUT wdt_timeout[3]
+#define DA9063_RESET_PROTECTION_MS 256
+
+static unsigned int da9063_wdt_timeout_to_sel(unsigned int secs)
+{
+ unsigned int i;
+
+ for (i = DA9063_TWDSCALE_MIN; i <= DA9063_TWDSCALE_MAX; i++) {
+ if (wdt_timeout[i] >= secs)
+ return i;
+ }
+
+ return DA9063_TWDSCALE_MAX;
+}
+
+/*
+ * Read the currently active timeout.
+ * Zero means the watchdog is disabled.
+ */
+static unsigned int da9063_wdt_read_timeout(struct da9063 *da9063)
+{
+ unsigned int val;
+
+ regmap_read(da9063->regmap, DA9063_REG_CONTROL_D, &val);
+
+ return wdt_timeout[val & DA9063_TWDSCALE_MASK];
+}
+
+static int da9063_wdt_disable_timer(struct da9063 *da9063)
+{
+ return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
+ DA9063_TWDSCALE_MASK,
+ DA9063_TWDSCALE_DISABLE);
+}
+
+static int
+da9063_wdt_update_timeout(struct da9063 *da9063, unsigned int timeout)
+{
+ unsigned int regval;
+ int ret;
+
+ /*
+ * The watchdog triggers a reboot if a timeout value is already
+ * programmed because the timeout value combines two functions
+ * in one: indicating the counter limit and starting the watchdog.
+ * The watchdog must be disabled to be able to change the timeout
+ * value if the watchdog is already running. Then we can set the
+ * new timeout value which enables the watchdog again.
+ */
+ ret = da9063_wdt_disable_timer(da9063);
+ if (ret)
+ return ret;
+
+ usleep_range(150, 300);
+ regval = da9063_wdt_timeout_to_sel(timeout);
+
+ return regmap_update_bits(da9063->regmap, DA9063_REG_CONTROL_D,
+ DA9063_TWDSCALE_MASK, regval);
+}
+
+static int da9063_wdt_start(struct watchdog_device *wdd)
+{
+ struct da9063 *da9063 = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = da9063_wdt_update_timeout(da9063, wdd->timeout);
+ if (ret)
+ dev_err(da9063->dev, "Watchdog failed to start (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9063_wdt_stop(struct watchdog_device *wdd)
+{
+ struct da9063 *da9063 = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = da9063_wdt_disable_timer(da9063);
+ if (ret)
+ dev_alert(da9063->dev, "Watchdog failed to stop (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9063_wdt_ping(struct watchdog_device *wdd)
+{
+ struct da9063 *da9063 = watchdog_get_drvdata(wdd);
+ int ret;
+
+ /*
+ * Prevent pings from occurring late in system poweroff/reboot sequence
+ * and possibly locking out restart handler from accessing i2c bus.
+ */
+ if (system_state > SYSTEM_RUNNING)
+ return 0;
+
+ ret = regmap_write(da9063->regmap, DA9063_REG_CONTROL_F,
+ DA9063_WATCHDOG);
+ if (ret)
+ dev_alert(da9063->dev, "Failed to ping the watchdog (err = %d)\n",
+ ret);
+
+ return ret;
+}
+
+static int da9063_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct da9063 *da9063 = watchdog_get_drvdata(wdd);
+ int ret = 0;
+
+ /*
+ * There are two cases when a set_timeout() will be called:
+ * 1. The watchdog is off and someone wants to set the timeout for the
+ * further use.
+ * 2. The watchdog is already running and a new timeout value should be
+ * set.
+ *
+ * The watchdog can't store a timeout value not equal zero without
+ * enabling the watchdog, so the timeout must be buffered by the driver.
+ */
+ if (watchdog_active(wdd))
+ ret = da9063_wdt_update_timeout(da9063, timeout);
+
+ if (ret)
+ dev_err(da9063->dev, "Failed to set watchdog timeout (err = %d)\n",
+ ret);
+ else
+ wdd->timeout = wdt_timeout[da9063_wdt_timeout_to_sel(timeout)];
+
+ return ret;
+}
+
+static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct da9063 *da9063 = watchdog_get_drvdata(wdd);
+ struct i2c_client *client = to_i2c_client(da9063->dev);
+ int ret;
+
+ /* Don't use regmap because it is not atomic safe */
+ ret = i2c_smbus_write_byte_data(client, DA9063_REG_CONTROL_F,
+ DA9063_SHUTDOWN);
+ if (ret < 0)
+ dev_alert(da9063->dev, "Failed to shutdown (err = %d)\n",
+ ret);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return ret;
+}
+
+static const struct watchdog_info da9063_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "DA9063 Watchdog",
+};
+
+static const struct watchdog_ops da9063_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = da9063_wdt_start,
+ .stop = da9063_wdt_stop,
+ .ping = da9063_wdt_ping,
+ .set_timeout = da9063_wdt_set_timeout,
+ .restart = da9063_wdt_restart,
+};
+
+static int da9063_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da9063 *da9063;
+ struct watchdog_device *wdd;
+ unsigned int timeout;
+
+ if (!dev->parent)
+ return -EINVAL;
+
+ da9063 = dev_get_drvdata(dev->parent);
+ if (!da9063)
+ return -EINVAL;
+
+ wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
+ if (!wdd)
+ return -ENOMEM;
+
+ use_sw_pm = device_property_present(dev, "dlg,use-sw-pm");
+
+ wdd->info = &da9063_watchdog_info;
+ wdd->ops = &da9063_watchdog_ops;
+ wdd->min_timeout = DA9063_WDT_MIN_TIMEOUT;
+ wdd->max_timeout = DA9063_WDT_MAX_TIMEOUT;
+ wdd->min_hw_heartbeat_ms = DA9063_RESET_PROTECTION_MS;
+ wdd->parent = dev;
+ wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+ watchdog_set_restart_priority(wdd, 128);
+ watchdog_set_drvdata(wdd, da9063);
+ dev_set_drvdata(dev, wdd);
+
+ wdd->timeout = DA9063_WDG_TIMEOUT;
+
+ /* Use pre-configured timeout if watchdog is already running. */
+ timeout = da9063_wdt_read_timeout(da9063);
+ if (timeout)
+ wdd->timeout = timeout;
+
+ /* Set timeout, maybe override it with DT value, scale it */
+ watchdog_init_timeout(wdd, 0, dev);
+ da9063_wdt_set_timeout(wdd, wdd->timeout);
+
+ /* Update timeout if the watchdog is already running. */
+ if (timeout) {
+ da9063_wdt_update_timeout(da9063, wdd->timeout);
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ return devm_watchdog_register_device(dev, wdd);
+}
+
+static int __maybe_unused da9063_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ if (!use_sw_pm)
+ return 0;
+
+ if (watchdog_active(wdd))
+ return da9063_wdt_stop(wdd);
+
+ return 0;
+}
+
+static int __maybe_unused da9063_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ if (!use_sw_pm)
+ return 0;
+
+ if (watchdog_active(wdd))
+ return da9063_wdt_start(wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(da9063_wdt_pm_ops,
+ da9063_wdt_suspend, da9063_wdt_resume);
+
+static struct platform_driver da9063_wdt_driver = {
+ .probe = da9063_wdt_probe,
+ .driver = {
+ .name = DA9063_DRVNAME_WATCHDOG,
+ .pm = &da9063_wdt_pm_ops,
+ },
+};
+module_platform_driver(da9063_wdt_driver);
+
+MODULE_AUTHOR("Mariusz Wojtasik <mariusz.wojtasik@diasemi.com>");
+MODULE_DESCRIPTION("Watchdog driver for Dialog DA9063");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DA9063_DRVNAME_WATCHDOG);
diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c
new file mode 100644
index 000000000..584a56893
--- /dev/null
+++ b/drivers/watchdog/davinci_wdt.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/char/watchdog/davinci_wdt.c
+ *
+ * Watchdog driver for DaVinci DM644x/DM646x processors
+ *
+ * Copyright (C) 2006-2013 Texas Instruments.
+ *
+ * 2007 (c) MontaVista Software, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mod_devicetable.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#define MODULE_NAME "DAVINCI-WDT: "
+
+#define DEFAULT_HEARTBEAT 60
+#define MAX_HEARTBEAT 600 /* really the max margin is 264/27MHz*/
+
+/* Timer register set definition */
+#define PID12 (0x0)
+#define EMUMGT (0x4)
+#define TIM12 (0x10)
+#define TIM34 (0x14)
+#define PRD12 (0x18)
+#define PRD34 (0x1C)
+#define TCR (0x20)
+#define TGCR (0x24)
+#define WDTCR (0x28)
+
+/* TCR bit definitions */
+#define ENAMODE12_DISABLED (0 << 6)
+#define ENAMODE12_ONESHOT (1 << 6)
+#define ENAMODE12_PERIODIC (2 << 6)
+
+/* TGCR bit definitions */
+#define TIM12RS_UNRESET (1 << 0)
+#define TIM34RS_UNRESET (1 << 1)
+#define TIMMODE_64BIT_WDOG (2 << 2)
+
+/* WDTCR bit definitions */
+#define WDEN (1 << 14)
+#define WDFLAG (1 << 15)
+#define WDKEY_SEQ0 (0xa5c6 << 16)
+#define WDKEY_SEQ1 (0xda7e << 16)
+
+static int heartbeat;
+
+/*
+ * struct to hold data for each WDT device
+ * @base - base io address of WD device
+ * @clk - source clock of WDT
+ * @wdd - hold watchdog device as is in WDT core
+ */
+struct davinci_wdt_device {
+ void __iomem *base;
+ struct clk *clk;
+ struct watchdog_device wdd;
+};
+
+static int davinci_wdt_start(struct watchdog_device *wdd)
+{
+ u32 tgcr;
+ u32 timer_margin;
+ unsigned long wdt_freq;
+ struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd);
+
+ wdt_freq = clk_get_rate(davinci_wdt->clk);
+
+ /* disable, internal clock source */
+ iowrite32(0, davinci_wdt->base + TCR);
+ /* reset timer, set mode to 64-bit watchdog, and unreset */
+ iowrite32(0, davinci_wdt->base + TGCR);
+ tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET;
+ iowrite32(tgcr, davinci_wdt->base + TGCR);
+ /* clear counter regs */
+ iowrite32(0, davinci_wdt->base + TIM12);
+ iowrite32(0, davinci_wdt->base + TIM34);
+ /* set timeout period */
+ timer_margin = (((u64)wdd->timeout * wdt_freq) & 0xffffffff);
+ iowrite32(timer_margin, davinci_wdt->base + PRD12);
+ timer_margin = (((u64)wdd->timeout * wdt_freq) >> 32);
+ iowrite32(timer_margin, davinci_wdt->base + PRD34);
+ /* enable run continuously */
+ iowrite32(ENAMODE12_PERIODIC, davinci_wdt->base + TCR);
+ /* Once the WDT is in pre-active state write to
+ * TIM12, TIM34, PRD12, PRD34, TCR, TGCR, WDTCR are
+ * write protected (except for the WDKEY field)
+ */
+ /* put watchdog in pre-active state */
+ iowrite32(WDKEY_SEQ0 | WDEN, davinci_wdt->base + WDTCR);
+ /* put watchdog in active state */
+ iowrite32(WDKEY_SEQ1 | WDEN, davinci_wdt->base + WDTCR);
+ return 0;
+}
+
+static int davinci_wdt_ping(struct watchdog_device *wdd)
+{
+ struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd);
+
+ /* put watchdog in service state */
+ iowrite32(WDKEY_SEQ0, davinci_wdt->base + WDTCR);
+ /* put watchdog in active state */
+ iowrite32(WDKEY_SEQ1, davinci_wdt->base + WDTCR);
+ return 0;
+}
+
+static unsigned int davinci_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ u64 timer_counter;
+ unsigned long freq;
+ u32 val;
+ struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd);
+
+ /* if timeout has occured then return 0 */
+ val = ioread32(davinci_wdt->base + WDTCR);
+ if (val & WDFLAG)
+ return 0;
+
+ freq = clk_get_rate(davinci_wdt->clk);
+
+ if (!freq)
+ return 0;
+
+ timer_counter = ioread32(davinci_wdt->base + TIM12);
+ timer_counter |= ((u64)ioread32(davinci_wdt->base + TIM34) << 32);
+
+ timer_counter = div64_ul(timer_counter, freq);
+
+ return wdd->timeout - timer_counter;
+}
+
+static int davinci_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd);
+ u32 tgcr, wdtcr;
+
+ /* disable, internal clock source */
+ iowrite32(0, davinci_wdt->base + TCR);
+
+ /* reset timer, set mode to 64-bit watchdog, and unreset */
+ tgcr = 0;
+ iowrite32(tgcr, davinci_wdt->base + TGCR);
+ tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET;
+ iowrite32(tgcr, davinci_wdt->base + TGCR);
+
+ /* clear counter and period regs */
+ iowrite32(0, davinci_wdt->base + TIM12);
+ iowrite32(0, davinci_wdt->base + TIM34);
+ iowrite32(0, davinci_wdt->base + PRD12);
+ iowrite32(0, davinci_wdt->base + PRD34);
+
+ /* put watchdog in pre-active state */
+ wdtcr = WDKEY_SEQ0 | WDEN;
+ iowrite32(wdtcr, davinci_wdt->base + WDTCR);
+
+ /* put watchdog in active state */
+ wdtcr = WDKEY_SEQ1 | WDEN;
+ iowrite32(wdtcr, davinci_wdt->base + WDTCR);
+
+ /* write an invalid value to the WDKEY field to trigger a restart */
+ wdtcr = 0x00004000;
+ iowrite32(wdtcr, davinci_wdt->base + WDTCR);
+
+ return 0;
+}
+
+static const struct watchdog_info davinci_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING,
+ .identity = "DaVinci/Keystone Watchdog",
+};
+
+static const struct watchdog_ops davinci_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = davinci_wdt_start,
+ .stop = davinci_wdt_ping,
+ .ping = davinci_wdt_ping,
+ .get_timeleft = davinci_wdt_get_timeleft,
+ .restart = davinci_wdt_restart,
+};
+
+static void davinci_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int davinci_wdt_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct davinci_wdt_device *davinci_wdt;
+
+ davinci_wdt = devm_kzalloc(dev, sizeof(*davinci_wdt), GFP_KERNEL);
+ if (!davinci_wdt)
+ return -ENOMEM;
+
+ davinci_wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(davinci_wdt->clk))
+ return dev_err_probe(dev, PTR_ERR(davinci_wdt->clk),
+ "failed to get clock node\n");
+
+ ret = clk_prepare_enable(davinci_wdt->clk);
+ if (ret) {
+ dev_err(dev, "failed to prepare clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, davinci_clk_disable_unprepare,
+ davinci_wdt->clk);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, davinci_wdt);
+
+ wdd = &davinci_wdt->wdd;
+ wdd->info = &davinci_wdt_info;
+ wdd->ops = &davinci_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = MAX_HEARTBEAT;
+ wdd->timeout = DEFAULT_HEARTBEAT;
+ wdd->parent = dev;
+
+ watchdog_init_timeout(wdd, heartbeat, dev);
+
+ dev_info(dev, "heartbeat %d sec\n", wdd->timeout);
+
+ watchdog_set_drvdata(wdd, davinci_wdt);
+ watchdog_set_nowayout(wdd, 1);
+ watchdog_set_restart_priority(wdd, 128);
+
+ davinci_wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(davinci_wdt->base))
+ return PTR_ERR(davinci_wdt->base);
+
+ return devm_watchdog_register_device(dev, wdd);
+}
+
+static const struct of_device_id davinci_wdt_of_match[] = {
+ { .compatible = "ti,davinci-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, davinci_wdt_of_match);
+
+static struct platform_driver platform_wdt_driver = {
+ .driver = {
+ .name = "davinci-wdt",
+ .of_match_table = davinci_wdt_of_match,
+ },
+ .probe = davinci_wdt_probe,
+};
+
+module_platform_driver(platform_wdt_driver);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("DaVinci Watchdog Driver");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:davinci-wdt");
diff --git a/drivers/watchdog/db8500_wdt.c b/drivers/watchdog/db8500_wdt.c
new file mode 100644
index 000000000..6ed8b63d3
--- /dev/null
+++ b/drivers/watchdog/db8500_wdt.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) ST-Ericsson SA 2011-2013
+ *
+ * Author: Mathieu Poirier <mathieu.poirier@linaro.org> for ST-Ericsson
+ * Author: Jonas Aaberg <jonas.aberg@stericsson.com> for ST-Ericsson
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/moduleparam.h>
+#include <linux/err.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/dbx500-prcmu.h>
+
+#define WATCHDOG_TIMEOUT 600 /* 10 minutes */
+
+#define WATCHDOG_MIN 0
+#define WATCHDOG_MAX28 268435 /* 28 bit resolution in ms == 268435.455 s */
+
+static unsigned int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 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) ")");
+
+static int db8500_wdt_start(struct watchdog_device *wdd)
+{
+ return prcmu_enable_a9wdog(PRCMU_WDOG_ALL);
+}
+
+static int db8500_wdt_stop(struct watchdog_device *wdd)
+{
+ return prcmu_disable_a9wdog(PRCMU_WDOG_ALL);
+}
+
+static int db8500_wdt_keepalive(struct watchdog_device *wdd)
+{
+ return prcmu_kick_a9wdog(PRCMU_WDOG_ALL);
+}
+
+static int db8500_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ db8500_wdt_stop(wdd);
+ prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000);
+ db8500_wdt_start(wdd);
+
+ return 0;
+}
+
+static const struct watchdog_info db8500_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "DB8500 WDT",
+ .firmware_version = 1,
+};
+
+static const struct watchdog_ops db8500_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = db8500_wdt_start,
+ .stop = db8500_wdt_stop,
+ .ping = db8500_wdt_keepalive,
+ .set_timeout = db8500_wdt_set_timeout,
+};
+
+static struct watchdog_device db8500_wdt = {
+ .info = &db8500_wdt_info,
+ .ops = &db8500_wdt_ops,
+ .min_timeout = WATCHDOG_MIN,
+ .max_timeout = WATCHDOG_MAX28,
+};
+
+static int db8500_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ timeout = 600; /* Default to 10 minutes */
+ db8500_wdt.parent = dev;
+ watchdog_set_nowayout(&db8500_wdt, nowayout);
+
+ /* disable auto off on sleep */
+ prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false);
+
+ /* set HW initial value */
+ prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000);
+
+ ret = devm_watchdog_register_device(dev, &db8500_wdt);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "initialized\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int db8500_wdt_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ if (watchdog_active(&db8500_wdt)) {
+ db8500_wdt_stop(&db8500_wdt);
+ prcmu_config_a9wdog(PRCMU_WDOG_CPU1, true);
+
+ prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000);
+ db8500_wdt_start(&db8500_wdt);
+ }
+ return 0;
+}
+
+static int db8500_wdt_resume(struct platform_device *pdev)
+{
+ if (watchdog_active(&db8500_wdt)) {
+ db8500_wdt_stop(&db8500_wdt);
+ prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false);
+
+ prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000);
+ db8500_wdt_start(&db8500_wdt);
+ }
+ return 0;
+}
+#else
+#define db8500_wdt_suspend NULL
+#define db8500_wdt_resume NULL
+#endif
+
+static struct platform_driver db8500_wdt_driver = {
+ .probe = db8500_wdt_probe,
+ .suspend = db8500_wdt_suspend,
+ .resume = db8500_wdt_resume,
+ .driver = {
+ .name = "db8500_wdt",
+ },
+};
+
+module_platform_driver(db8500_wdt_driver);
+
+MODULE_AUTHOR("Jonas Aaberg <jonas.aberg@stericsson.com>");
+MODULE_DESCRIPTION("DB8500 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:db8500_wdt");
diff --git a/drivers/watchdog/diag288_wdt.c b/drivers/watchdog/diag288_wdt.c
new file mode 100644
index 000000000..6ca5d9515
--- /dev/null
+++ b/drivers/watchdog/diag288_wdt.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Watchdog driver for z/VM and LPAR using the diag 288 interface.
+ *
+ * Under z/VM, expiration of the watchdog will send a "system restart" command
+ * to CP.
+ *
+ * The command can be altered using the module parameter "cmd". This is
+ * not recommended because it's only supported on z/VM but not whith LPAR.
+ *
+ * On LPAR, the watchdog will always trigger a system restart. the module
+ * paramter cmd is meaningless here.
+ *
+ *
+ * Copyright IBM Corp. 2004, 2013
+ * Author(s): Arnd Bergmann (arndb@de.ibm.com)
+ * Philipp Hachtmann (phacht@de.ibm.com)
+ *
+ */
+
+#define KMSG_COMPONENT "diag288_wdt"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+#include <linux/suspend.h>
+#include <asm/ebcdic.h>
+#include <asm/diag.h>
+#include <linux/io.h>
+
+#define MAX_CMDLEN 240
+#define DEFAULT_CMD "SYSTEM RESTART"
+
+#define MIN_INTERVAL 15 /* Minimal time supported by diag88 */
+#define MAX_INTERVAL 3600 /* One hour should be enough - pure estimation */
+
+#define WDT_DEFAULT_TIMEOUT 30
+
+/* Function codes - init, change, cancel */
+#define WDT_FUNC_INIT 0
+#define WDT_FUNC_CHANGE 1
+#define WDT_FUNC_CANCEL 2
+#define WDT_FUNC_CONCEAL 0x80000000
+
+/* Action codes for LPAR watchdog */
+#define LPARWDT_RESTART 0
+
+static char wdt_cmd[MAX_CMDLEN] = DEFAULT_CMD;
+static bool conceal_on;
+static bool nowayout_info = WATCHDOG_NOWAYOUT;
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arnd Bergmann <arndb@de.ibm.com>");
+MODULE_AUTHOR("Philipp Hachtmann <phacht@de.ibm.com>");
+
+MODULE_DESCRIPTION("System z diag288 Watchdog Timer");
+
+module_param_string(cmd, wdt_cmd, MAX_CMDLEN, 0644);
+MODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers (z/VM only)");
+
+module_param_named(conceal, conceal_on, bool, 0644);
+MODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog is active (z/VM only)");
+
+module_param_named(nowayout, nowayout_info, bool, 0444);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default = CONFIG_WATCHDOG_NOWAYOUT)");
+
+MODULE_ALIAS("vmwatchdog");
+
+static int __diag288(unsigned int func, unsigned int timeout,
+ unsigned long action, unsigned int len)
+{
+ register unsigned long __func asm("2") = func;
+ register unsigned long __timeout asm("3") = timeout;
+ register unsigned long __action asm("4") = action;
+ register unsigned long __len asm("5") = len;
+ int err;
+
+ err = -EINVAL;
+ asm volatile(
+ " diag %1, %3, 0x288\n"
+ "0: la %0, 0\n"
+ "1:\n"
+ EX_TABLE(0b, 1b)
+ : "+d" (err) : "d"(__func), "d"(__timeout),
+ "d"(__action), "d"(__len) : "1", "cc", "memory");
+ return err;
+}
+
+static int __diag288_vm(unsigned int func, unsigned int timeout,
+ char *cmd, size_t len)
+{
+ diag_stat_inc(DIAG_STAT_X288);
+ return __diag288(func, timeout, virt_to_phys(cmd), len);
+}
+
+static int __diag288_lpar(unsigned int func, unsigned int timeout,
+ unsigned long action)
+{
+ diag_stat_inc(DIAG_STAT_X288);
+ return __diag288(func, timeout, action, 0);
+}
+
+static unsigned long wdt_status;
+
+#define DIAG_WDOG_BUSY 0
+
+static int wdt_start(struct watchdog_device *dev)
+{
+ char *ebc_cmd;
+ size_t len;
+ int ret;
+ unsigned int func;
+
+ if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status))
+ return -EBUSY;
+
+ if (MACHINE_IS_VM) {
+ ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL);
+ if (!ebc_cmd) {
+ clear_bit(DIAG_WDOG_BUSY, &wdt_status);
+ return -ENOMEM;
+ }
+ len = strlcpy(ebc_cmd, wdt_cmd, MAX_CMDLEN);
+ ASCEBC(ebc_cmd, MAX_CMDLEN);
+ EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
+
+ func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL)
+ : WDT_FUNC_INIT;
+ ret = __diag288_vm(func, dev->timeout, ebc_cmd, len);
+ WARN_ON(ret != 0);
+ kfree(ebc_cmd);
+ } else {
+ ret = __diag288_lpar(WDT_FUNC_INIT,
+ dev->timeout, LPARWDT_RESTART);
+ }
+
+ if (ret) {
+ pr_err("The watchdog cannot be activated\n");
+ clear_bit(DIAG_WDOG_BUSY, &wdt_status);
+ return ret;
+ }
+ return 0;
+}
+
+static int wdt_stop(struct watchdog_device *dev)
+{
+ int ret;
+
+ diag_stat_inc(DIAG_STAT_X288);
+ ret = __diag288(WDT_FUNC_CANCEL, 0, 0, 0);
+
+ clear_bit(DIAG_WDOG_BUSY, &wdt_status);
+
+ return ret;
+}
+
+static int wdt_ping(struct watchdog_device *dev)
+{
+ char *ebc_cmd;
+ size_t len;
+ int ret;
+ unsigned int func;
+
+ if (MACHINE_IS_VM) {
+ ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL);
+ if (!ebc_cmd)
+ return -ENOMEM;
+ len = strlcpy(ebc_cmd, wdt_cmd, MAX_CMDLEN);
+ ASCEBC(ebc_cmd, MAX_CMDLEN);
+ EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
+
+ /*
+ * It seems to be ok to z/VM to use the init function to
+ * retrigger the watchdog. On LPAR WDT_FUNC_CHANGE must
+ * be used when the watchdog is running.
+ */
+ func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL)
+ : WDT_FUNC_INIT;
+
+ ret = __diag288_vm(func, dev->timeout, ebc_cmd, len);
+ WARN_ON(ret != 0);
+ kfree(ebc_cmd);
+ } else {
+ ret = __diag288_lpar(WDT_FUNC_CHANGE, dev->timeout, 0);
+ }
+
+ if (ret)
+ pr_err("The watchdog timer cannot be started or reset\n");
+ return ret;
+}
+
+static int wdt_set_timeout(struct watchdog_device * dev, unsigned int new_to)
+{
+ dev->timeout = new_to;
+ return wdt_ping(dev);
+}
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .ping = wdt_ping,
+ .set_timeout = wdt_set_timeout,
+};
+
+static const struct watchdog_info wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "z Watchdog",
+};
+
+static struct watchdog_device wdt_dev = {
+ .parent = NULL,
+ .info = &wdt_info,
+ .ops = &wdt_ops,
+ .bootstatus = 0,
+ .timeout = WDT_DEFAULT_TIMEOUT,
+ .min_timeout = MIN_INTERVAL,
+ .max_timeout = MAX_INTERVAL,
+};
+
+/*
+ * It makes no sense to go into suspend while the watchdog is running.
+ * Depending on the memory size, the watchdog might trigger, while we
+ * are still saving the memory.
+ */
+static int wdt_suspend(void)
+{
+ if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status)) {
+ pr_err("Linux cannot be suspended while the watchdog is in use\n");
+ return notifier_from_errno(-EBUSY);
+ }
+ return NOTIFY_DONE;
+}
+
+static int wdt_resume(void)
+{
+ clear_bit(DIAG_WDOG_BUSY, &wdt_status);
+ return NOTIFY_DONE;
+}
+
+static int wdt_power_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ switch (event) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ return wdt_resume();
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ return wdt_suspend();
+ default:
+ return NOTIFY_DONE;
+ }
+}
+
+static struct notifier_block wdt_power_notifier = {
+ .notifier_call = wdt_power_event,
+};
+
+static int __init diag288_init(void)
+{
+ int ret;
+ char ebc_begin[] = {
+ 194, 197, 199, 201, 213
+ };
+ char *ebc_cmd;
+
+ watchdog_set_nowayout(&wdt_dev, nowayout_info);
+
+ if (MACHINE_IS_VM) {
+ ebc_cmd = kmalloc(sizeof(ebc_begin), GFP_KERNEL);
+ if (!ebc_cmd) {
+ pr_err("The watchdog cannot be initialized\n");
+ return -ENOMEM;
+ }
+ memcpy(ebc_cmd, ebc_begin, sizeof(ebc_begin));
+ ret = __diag288_vm(WDT_FUNC_INIT, 15,
+ ebc_cmd, sizeof(ebc_begin));
+ kfree(ebc_cmd);
+ if (ret != 0) {
+ pr_err("The watchdog cannot be initialized\n");
+ return -EINVAL;
+ }
+ } else {
+ if (__diag288_lpar(WDT_FUNC_INIT, 30, LPARWDT_RESTART)) {
+ pr_err("The watchdog cannot be initialized\n");
+ return -EINVAL;
+ }
+ }
+
+ if (__diag288_lpar(WDT_FUNC_CANCEL, 0, 0)) {
+ pr_err("The watchdog cannot be deactivated\n");
+ return -EINVAL;
+ }
+
+ ret = register_pm_notifier(&wdt_power_notifier);
+ if (ret)
+ return ret;
+
+ ret = watchdog_register_device(&wdt_dev);
+ if (ret)
+ unregister_pm_notifier(&wdt_power_notifier);
+
+ return ret;
+}
+
+static void __exit diag288_exit(void)
+{
+ watchdog_unregister_device(&wdt_dev);
+ unregister_pm_notifier(&wdt_power_notifier);
+}
+
+module_init(diag288_init);
+module_exit(diag288_exit);
diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c
new file mode 100644
index 000000000..073d37867
--- /dev/null
+++ b/drivers/watchdog/digicolor_wdt.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for Conexant Digicolor
+ *
+ * Copyright (C) 2015 Paradox Innovation Ltd.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+
+#define TIMER_A_CONTROL 0
+#define TIMER_A_COUNT 4
+
+#define TIMER_A_ENABLE_COUNT BIT(0)
+#define TIMER_A_ENABLE_WATCHDOG BIT(1)
+
+struct dc_wdt {
+ void __iomem *base;
+ struct clk *clk;
+ spinlock_t lock;
+};
+
+static unsigned timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+
+ writel_relaxed(0, wdt->base + TIMER_A_CONTROL);
+ writel_relaxed(ticks, wdt->base + TIMER_A_COUNT);
+ writel_relaxed(TIMER_A_ENABLE_COUNT | TIMER_A_ENABLE_WATCHDOG,
+ wdt->base + TIMER_A_CONTROL);
+
+ spin_unlock_irqrestore(&wdt->lock, flags);
+}
+
+static int dc_wdt_restart(struct watchdog_device *wdog, unsigned long action,
+ void *data)
+{
+ struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ dc_wdt_set(wdt, 1);
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return 0;
+}
+
+static int dc_wdt_start(struct watchdog_device *wdog)
+{
+ struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ dc_wdt_set(wdt, wdog->timeout * clk_get_rate(wdt->clk));
+
+ return 0;
+}
+
+static int dc_wdt_stop(struct watchdog_device *wdog)
+{
+ struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ writel_relaxed(0, wdt->base + TIMER_A_CONTROL);
+
+ return 0;
+}
+
+static int dc_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
+{
+ struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ dc_wdt_set(wdt, t * clk_get_rate(wdt->clk));
+ wdog->timeout = t;
+
+ return 0;
+}
+
+static unsigned int dc_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct dc_wdt *wdt = watchdog_get_drvdata(wdog);
+ uint32_t count = readl_relaxed(wdt->base + TIMER_A_COUNT);
+
+ return count / clk_get_rate(wdt->clk);
+}
+
+static const struct watchdog_ops dc_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = dc_wdt_start,
+ .stop = dc_wdt_stop,
+ .set_timeout = dc_wdt_set_timeout,
+ .get_timeleft = dc_wdt_get_timeleft,
+ .restart = dc_wdt_restart,
+};
+
+static const struct watchdog_info dc_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE
+ | WDIOF_KEEPALIVEPING,
+ .identity = "Conexant Digicolor Watchdog",
+};
+
+static struct watchdog_device dc_wdt_wdd = {
+ .info = &dc_wdt_info,
+ .ops = &dc_wdt_ops,
+ .min_timeout = 1,
+};
+
+static int dc_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dc_wdt *wdt;
+
+ wdt = devm_kzalloc(dev, sizeof(struct dc_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return PTR_ERR(wdt->clk);
+ dc_wdt_wdd.max_timeout = U32_MAX / clk_get_rate(wdt->clk);
+ dc_wdt_wdd.timeout = dc_wdt_wdd.max_timeout;
+ dc_wdt_wdd.parent = dev;
+
+ spin_lock_init(&wdt->lock);
+
+ watchdog_set_drvdata(&dc_wdt_wdd, wdt);
+ watchdog_set_restart_priority(&dc_wdt_wdd, 128);
+ watchdog_init_timeout(&dc_wdt_wdd, timeout, dev);
+ watchdog_stop_on_reboot(&dc_wdt_wdd);
+ return devm_watchdog_register_device(dev, &dc_wdt_wdd);
+}
+
+static const struct of_device_id dc_wdt_of_match[] = {
+ { .compatible = "cnxt,cx92755-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dc_wdt_of_match);
+
+static struct platform_driver dc_wdt_driver = {
+ .probe = dc_wdt_probe,
+ .driver = {
+ .name = "digicolor-wdt",
+ .of_match_table = dc_wdt_of_match,
+ },
+};
+module_platform_driver(dc_wdt_driver);
+
+MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>");
+MODULE_DESCRIPTION("Driver for Conexant Digicolor watchdog timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
new file mode 100644
index 000000000..61af5d133
--- /dev/null
+++ b/drivers/watchdog/dw_wdt.c
@@ -0,0 +1,722 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2010-2011 Picochip Ltd., Jamie Iles
+ * https://www.picochip.com
+ *
+ * This file implements a driver for the Synopsys DesignWare watchdog device
+ * in the many subsystems. The watchdog has 16 different timeout periods
+ * and these are a function of the input clock frequency.
+ *
+ * The DesignWare watchdog cannot be stopped once it has been started so we
+ * do not implement a stop function. The watchdog core will continue to send
+ * heartbeat requests after the watchdog device has been closed.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/reset.h>
+#include <linux/watchdog.h>
+
+#define WDOG_CONTROL_REG_OFFSET 0x00
+#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
+#define WDOG_CONTROL_REG_RESP_MODE_MASK 0x02
+#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04
+#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4
+#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
+#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
+#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
+#define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10
+#define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14
+#define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4
+#define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8
+#define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec
+#define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0
+#define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
+#define WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6)
+#define WDOG_COMP_VERSION_REG_OFFSET 0xf8
+#define WDOG_COMP_TYPE_REG_OFFSET 0xfc
+
+/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
+#define DW_WDT_NUM_TOPS 16
+#define DW_WDT_FIX_TOP(_idx) (1U << (16 + _idx))
+
+#define DW_WDT_DEFAULT_SECONDS 30
+
+static const u32 dw_wdt_fix_tops[DW_WDT_NUM_TOPS] = {
+ DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2),
+ DW_WDT_FIX_TOP(3), DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5),
+ DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7), DW_WDT_FIX_TOP(8),
+ DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11),
+ DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14),
+ DW_WDT_FIX_TOP(15)
+};
+
+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) ")");
+
+enum dw_wdt_rmod {
+ DW_WDT_RMOD_RESET = 1,
+ DW_WDT_RMOD_IRQ = 2
+};
+
+struct dw_wdt_timeout {
+ u32 top_val;
+ unsigned int sec;
+ unsigned int msec;
+};
+
+struct dw_wdt {
+ void __iomem *regs;
+ struct clk *clk;
+ struct clk *pclk;
+ unsigned long rate;
+ enum dw_wdt_rmod rmod;
+ struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
+ struct watchdog_device wdd;
+ struct reset_control *rst;
+ /* Save/restore */
+ u32 control;
+ u32 timeout;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *dbgfs_dir;
+#endif
+};
+
+#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd)
+
+static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
+{
+ return readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) &
+ WDOG_CONTROL_REG_WDT_EN_MASK;
+}
+
+static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod)
+{
+ u32 val;
+
+ val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+ if (rmod == DW_WDT_RMOD_IRQ)
+ val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
+ else
+ val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
+ writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+
+ dw_wdt->rmod = rmod;
+}
+
+static unsigned int dw_wdt_find_best_top(struct dw_wdt *dw_wdt,
+ unsigned int timeout, u32 *top_val)
+{
+ int idx;
+
+ /*
+ * Find a TOP with timeout greater or equal to the requested number.
+ * Note we'll select a TOP with maximum timeout if the requested
+ * timeout couldn't be reached.
+ */
+ for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
+ if (dw_wdt->timeouts[idx].sec >= timeout)
+ break;
+ }
+
+ if (idx == DW_WDT_NUM_TOPS)
+ --idx;
+
+ *top_val = dw_wdt->timeouts[idx].top_val;
+
+ return dw_wdt->timeouts[idx].sec;
+}
+
+static unsigned int dw_wdt_get_min_timeout(struct dw_wdt *dw_wdt)
+{
+ int idx;
+
+ /*
+ * We'll find a timeout greater or equal to one second anyway because
+ * the driver probe would have failed if there was none.
+ */
+ for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
+ if (dw_wdt->timeouts[idx].sec)
+ break;
+ }
+
+ return dw_wdt->timeouts[idx].sec;
+}
+
+static unsigned int dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt)
+{
+ struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1];
+ u64 msec;
+
+ msec = (u64)timeout->sec * MSEC_PER_SEC + timeout->msec;
+
+ return msec < UINT_MAX ? msec : UINT_MAX;
+}
+
+static unsigned int dw_wdt_get_timeout(struct dw_wdt *dw_wdt)
+{
+ int top_val = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
+ int idx;
+
+ for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
+ if (dw_wdt->timeouts[idx].top_val == top_val)
+ break;
+ }
+
+ /*
+ * In IRQ mode due to the two stages counter, the actual timeout is
+ * twice greater than the TOP setting.
+ */
+ return dw_wdt->timeouts[idx].sec * dw_wdt->rmod;
+}
+
+static int dw_wdt_ping(struct watchdog_device *wdd)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+
+ writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs +
+ WDOG_COUNTER_RESTART_REG_OFFSET);
+
+ return 0;
+}
+
+static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+ unsigned int timeout;
+ u32 top_val;
+
+ /*
+ * Note IRQ mode being enabled means having a non-zero pre-timeout
+ * setup. In this case we try to find a TOP as close to the half of the
+ * requested timeout as possible since DW Watchdog IRQ mode is designed
+ * in two stages way - first timeout rises the pre-timeout interrupt,
+ * second timeout performs the system reset. So basically the effective
+ * watchdog-caused reset happens after two watchdog TOPs elapsed.
+ */
+ timeout = dw_wdt_find_best_top(dw_wdt, DIV_ROUND_UP(top_s, dw_wdt->rmod),
+ &top_val);
+ if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
+ wdd->pretimeout = timeout;
+ else
+ wdd->pretimeout = 0;
+
+ /*
+ * Set the new value in the watchdog. Some versions of dw_wdt
+ * have TOPINIT in the TIMEOUT_RANGE register (as per
+ * CP_WDT_DUAL_TOP in WDT_COMP_PARAMS_1). On those we
+ * effectively get a pat of the watchdog right here.
+ */
+ writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
+ dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+
+ /* Kick new TOP value into the watchdog counter if activated. */
+ if (watchdog_active(wdd))
+ dw_wdt_ping(wdd);
+
+ /*
+ * In case users set bigger timeout value than HW can support,
+ * kernel(watchdog_dev.c) helps to feed watchdog before
+ * wdd->max_hw_heartbeat_ms
+ */
+ if (top_s * 1000 <= wdd->max_hw_heartbeat_ms)
+ wdd->timeout = timeout * dw_wdt->rmod;
+ else
+ wdd->timeout = top_s;
+
+ return 0;
+}
+
+static int dw_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+
+ /*
+ * We ignore actual value of the timeout passed from user-space
+ * using it as a flag whether the pretimeout functionality is intended
+ * to be activated.
+ */
+ dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET);
+ dw_wdt_set_timeout(wdd, wdd->timeout);
+
+ return 0;
+}
+
+static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
+{
+ u32 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+
+ /* Disable/enable interrupt mode depending on the RMOD flag. */
+ if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
+ val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
+ else
+ val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
+ /* Enable watchdog. */
+ val |= WDOG_CONTROL_REG_WDT_EN_MASK;
+ writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+}
+
+static int dw_wdt_start(struct watchdog_device *wdd)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+
+ dw_wdt_set_timeout(wdd, wdd->timeout);
+ dw_wdt_ping(&dw_wdt->wdd);
+ dw_wdt_arm_system_reset(dw_wdt);
+
+ return 0;
+}
+
+static int dw_wdt_stop(struct watchdog_device *wdd)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+
+ if (!dw_wdt->rst) {
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ return 0;
+ }
+
+ reset_control_assert(dw_wdt->rst);
+ reset_control_deassert(dw_wdt->rst);
+
+ return 0;
+}
+
+static int dw_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+
+ writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+ dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
+ if (dw_wdt_is_enabled(dw_wdt))
+ writel(WDOG_COUNTER_RESTART_KICK_VALUE,
+ dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
+ else
+ dw_wdt_arm_system_reset(dw_wdt);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return 0;
+}
+
+static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
+ unsigned int sec;
+ u32 val;
+
+ val = readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET);
+ sec = val / dw_wdt->rate;
+
+ if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) {
+ val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET);
+ if (!val)
+ sec += wdd->pretimeout;
+ }
+
+ return sec;
+}
+
+static const struct watchdog_info dw_wdt_ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .identity = "Synopsys DesignWare Watchdog",
+};
+
+static const struct watchdog_info dw_wdt_pt_ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE,
+ .identity = "Synopsys DesignWare Watchdog",
+};
+
+static const struct watchdog_ops dw_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = dw_wdt_start,
+ .stop = dw_wdt_stop,
+ .ping = dw_wdt_ping,
+ .set_timeout = dw_wdt_set_timeout,
+ .set_pretimeout = dw_wdt_set_pretimeout,
+ .get_timeleft = dw_wdt_get_timeleft,
+ .restart = dw_wdt_restart,
+};
+
+static irqreturn_t dw_wdt_irq(int irq, void *devid)
+{
+ struct dw_wdt *dw_wdt = devid;
+ u32 val;
+
+ /*
+ * We don't clear the IRQ status. It's supposed to be done by the
+ * following ping operations.
+ */
+ val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET);
+ if (!val)
+ return IRQ_NONE;
+
+ watchdog_notify_pretimeout(&dw_wdt->wdd);
+
+ return IRQ_HANDLED;
+}
+
+static int dw_wdt_suspend(struct device *dev)
+{
+ struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
+
+ dw_wdt->control = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+ dw_wdt->timeout = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+
+ clk_disable_unprepare(dw_wdt->pclk);
+ clk_disable_unprepare(dw_wdt->clk);
+
+ return 0;
+}
+
+static int dw_wdt_resume(struct device *dev)
+{
+ struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
+ int err = clk_prepare_enable(dw_wdt->clk);
+
+ if (err)
+ return err;
+
+ err = clk_prepare_enable(dw_wdt->pclk);
+ if (err) {
+ clk_disable_unprepare(dw_wdt->clk);
+ return err;
+ }
+
+ writel(dw_wdt->timeout, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
+ writel(dw_wdt->control, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
+
+ dw_wdt_ping(&dw_wdt->wdd);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume);
+
+/*
+ * In case if DW WDT IP core is synthesized with fixed TOP feature disabled the
+ * TOPs array can be arbitrary ordered with nearly any sixteen uint numbers
+ * depending on the system engineer imagination. The next method handles the
+ * passed TOPs array to pre-calculate the effective timeouts and to sort the
+ * TOP items out in the ascending order with respect to the timeouts.
+ */
+
+static void dw_wdt_handle_tops(struct dw_wdt *dw_wdt, const u32 *tops)
+{
+ struct dw_wdt_timeout tout, *dst;
+ int val, tidx;
+ u64 msec;
+
+ /*
+ * We walk over the passed TOPs array and calculate corresponding
+ * timeouts in seconds and milliseconds. The milliseconds granularity
+ * is needed to distinguish the TOPs with very close timeouts and to
+ * set the watchdog max heartbeat setting further.
+ */
+ for (val = 0; val < DW_WDT_NUM_TOPS; ++val) {
+ tout.top_val = val;
+ tout.sec = tops[val] / dw_wdt->rate;
+ msec = (u64)tops[val] * MSEC_PER_SEC;
+ do_div(msec, dw_wdt->rate);
+ tout.msec = msec - ((u64)tout.sec * MSEC_PER_SEC);
+
+ /*
+ * Find a suitable place for the current TOP in the timeouts
+ * array so that the list is remained in the ascending order.
+ */
+ for (tidx = 0; tidx < val; ++tidx) {
+ dst = &dw_wdt->timeouts[tidx];
+ if (tout.sec > dst->sec || (tout.sec == dst->sec &&
+ tout.msec >= dst->msec))
+ continue;
+ else
+ swap(*dst, tout);
+ }
+
+ dw_wdt->timeouts[val] = tout;
+ }
+}
+
+static int dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev)
+{
+ u32 data, of_tops[DW_WDT_NUM_TOPS];
+ const u32 *tops;
+ int ret;
+
+ /*
+ * Retrieve custom or fixed counter values depending on the
+ * WDT_USE_FIX_TOP flag found in the component specific parameters
+ * #1 register.
+ */
+ data = readl(dw_wdt->regs + WDOG_COMP_PARAMS_1_REG_OFFSET);
+ if (data & WDOG_COMP_PARAMS_1_USE_FIX_TOP) {
+ tops = dw_wdt_fix_tops;
+ } else {
+ ret = of_property_read_variable_u32_array(dev_of_node(dev),
+ "snps,watchdog-tops", of_tops, DW_WDT_NUM_TOPS,
+ DW_WDT_NUM_TOPS);
+ if (ret < 0) {
+ dev_warn(dev, "No valid TOPs array specified\n");
+ tops = dw_wdt_fix_tops;
+ } else {
+ tops = of_tops;
+ }
+ }
+
+ /* Convert the specified TOPs into an array of watchdog timeouts. */
+ dw_wdt_handle_tops(dw_wdt, tops);
+ if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec) {
+ dev_err(dev, "No any valid TOP detected\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define DW_WDT_DBGFS_REG(_name, _off) \
+{ \
+ .name = _name, \
+ .offset = _off \
+}
+
+static const struct debugfs_reg32 dw_wdt_dbgfs_regs[] = {
+ DW_WDT_DBGFS_REG("cr", WDOG_CONTROL_REG_OFFSET),
+ DW_WDT_DBGFS_REG("torr", WDOG_TIMEOUT_RANGE_REG_OFFSET),
+ DW_WDT_DBGFS_REG("ccvr", WDOG_CURRENT_COUNT_REG_OFFSET),
+ DW_WDT_DBGFS_REG("crr", WDOG_COUNTER_RESTART_REG_OFFSET),
+ DW_WDT_DBGFS_REG("stat", WDOG_INTERRUPT_STATUS_REG_OFFSET),
+ DW_WDT_DBGFS_REG("param5", WDOG_COMP_PARAMS_5_REG_OFFSET),
+ DW_WDT_DBGFS_REG("param4", WDOG_COMP_PARAMS_4_REG_OFFSET),
+ DW_WDT_DBGFS_REG("param3", WDOG_COMP_PARAMS_3_REG_OFFSET),
+ DW_WDT_DBGFS_REG("param2", WDOG_COMP_PARAMS_2_REG_OFFSET),
+ DW_WDT_DBGFS_REG("param1", WDOG_COMP_PARAMS_1_REG_OFFSET),
+ DW_WDT_DBGFS_REG("version", WDOG_COMP_VERSION_REG_OFFSET),
+ DW_WDT_DBGFS_REG("type", WDOG_COMP_TYPE_REG_OFFSET)
+};
+
+static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt)
+{
+ struct device *dev = dw_wdt->wdd.parent;
+ struct debugfs_regset32 *regset;
+
+ regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL);
+ if (!regset)
+ return;
+
+ regset->regs = dw_wdt_dbgfs_regs;
+ regset->nregs = ARRAY_SIZE(dw_wdt_dbgfs_regs);
+ regset->base = dw_wdt->regs;
+
+ dw_wdt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL);
+
+ debugfs_create_regset32("registers", 0444, dw_wdt->dbgfs_dir, regset);
+}
+
+static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt)
+{
+ debugfs_remove_recursive(dw_wdt->dbgfs_dir);
+}
+
+#else /* !CONFIG_DEBUG_FS */
+
+static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt) {}
+static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt) {}
+
+#endif /* !CONFIG_DEBUG_FS */
+
+static int dw_wdt_drv_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct dw_wdt *dw_wdt;
+ int ret;
+
+ dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
+ if (!dw_wdt)
+ return -ENOMEM;
+
+ dw_wdt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dw_wdt->regs))
+ return PTR_ERR(dw_wdt->regs);
+
+ /*
+ * Try to request the watchdog dedicated timer clock source. It must
+ * be supplied if asynchronous mode is enabled. Otherwise fallback
+ * to the common timer/bus clocks configuration, in which the very
+ * first found clock supply both timer and APB signals.
+ */
+ dw_wdt->clk = devm_clk_get(dev, "tclk");
+ if (IS_ERR(dw_wdt->clk)) {
+ dw_wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(dw_wdt->clk))
+ return PTR_ERR(dw_wdt->clk);
+ }
+
+ ret = clk_prepare_enable(dw_wdt->clk);
+ if (ret)
+ return ret;
+
+ dw_wdt->rate = clk_get_rate(dw_wdt->clk);
+ if (dw_wdt->rate == 0) {
+ ret = -EINVAL;
+ goto out_disable_clk;
+ }
+
+ /*
+ * Request APB clock if device is configured with async clocks mode.
+ * In this case both tclk and pclk clocks are supposed to be specified.
+ * Alas we can't know for sure whether async mode was really activated,
+ * so the pclk phandle reference is left optional. If it couldn't be
+ * found we consider the device configured in synchronous clocks mode.
+ */
+ dw_wdt->pclk = devm_clk_get_optional(dev, "pclk");
+ if (IS_ERR(dw_wdt->pclk)) {
+ ret = PTR_ERR(dw_wdt->pclk);
+ goto out_disable_clk;
+ }
+
+ ret = clk_prepare_enable(dw_wdt->pclk);
+ if (ret)
+ goto out_disable_clk;
+
+ dw_wdt->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
+ if (IS_ERR(dw_wdt->rst)) {
+ ret = PTR_ERR(dw_wdt->rst);
+ goto out_disable_pclk;
+ }
+
+ /* Enable normal reset without pre-timeout by default. */
+ dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
+
+ /*
+ * Pre-timeout IRQ is optional, since some hardware may lack support
+ * of it. Note we must request rising-edge IRQ, since the lane is left
+ * pending either until the next watchdog kick event or up to the
+ * system reset.
+ */
+ ret = platform_get_irq_optional(pdev, 0);
+ if (ret > 0) {
+ ret = devm_request_irq(dev, ret, dw_wdt_irq,
+ IRQF_SHARED | IRQF_TRIGGER_RISING,
+ pdev->name, dw_wdt);
+ if (ret)
+ goto out_disable_pclk;
+
+ dw_wdt->wdd.info = &dw_wdt_pt_ident;
+ } else {
+ if (ret == -EPROBE_DEFER)
+ goto out_disable_pclk;
+
+ dw_wdt->wdd.info = &dw_wdt_ident;
+ }
+
+ reset_control_deassert(dw_wdt->rst);
+
+ ret = dw_wdt_init_timeouts(dw_wdt, dev);
+ if (ret)
+ goto out_assert_rst;
+
+ wdd = &dw_wdt->wdd;
+ wdd->ops = &dw_wdt_ops;
+ wdd->min_timeout = dw_wdt_get_min_timeout(dw_wdt);
+ wdd->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);
+ wdd->parent = dev;
+
+ watchdog_set_drvdata(wdd, dw_wdt);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, 0, dev);
+
+ /*
+ * If the watchdog is already running, use its already configured
+ * timeout. Otherwise use the default or the value provided through
+ * devicetree.
+ */
+ if (dw_wdt_is_enabled(dw_wdt)) {
+ wdd->timeout = dw_wdt_get_timeout(dw_wdt);
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ } else {
+ wdd->timeout = DW_WDT_DEFAULT_SECONDS;
+ watchdog_init_timeout(wdd, 0, dev);
+ }
+
+ platform_set_drvdata(pdev, dw_wdt);
+
+ watchdog_set_restart_priority(wdd, 128);
+
+ ret = watchdog_register_device(wdd);
+ if (ret)
+ goto out_assert_rst;
+
+ dw_wdt_dbgfs_init(dw_wdt);
+
+ return 0;
+
+out_assert_rst:
+ reset_control_assert(dw_wdt->rst);
+
+out_disable_pclk:
+ clk_disable_unprepare(dw_wdt->pclk);
+
+out_disable_clk:
+ clk_disable_unprepare(dw_wdt->clk);
+ return ret;
+}
+
+static int dw_wdt_drv_remove(struct platform_device *pdev)
+{
+ struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
+
+ dw_wdt_dbgfs_clear(dw_wdt);
+
+ watchdog_unregister_device(&dw_wdt->wdd);
+ reset_control_assert(dw_wdt->rst);
+ clk_disable_unprepare(dw_wdt->pclk);
+ clk_disable_unprepare(dw_wdt->clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id dw_wdt_of_match[] = {
+ { .compatible = "snps,dw-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
+#endif
+
+static struct platform_driver dw_wdt_driver = {
+ .probe = dw_wdt_drv_probe,
+ .remove = dw_wdt_drv_remove,
+ .driver = {
+ .name = "dw_wdt",
+ .of_match_table = of_match_ptr(dw_wdt_of_match),
+ .pm = pm_sleep_ptr(&dw_wdt_pm_ops),
+ },
+};
+
+module_platform_driver(dw_wdt_driver);
+
+MODULE_AUTHOR("Jamie Iles");
+MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/ebc-c384_wdt.c b/drivers/watchdog/ebc-c384_wdt.c
new file mode 100644
index 000000000..8ef4b0df3
--- /dev/null
+++ b/drivers/watchdog/ebc-c384_wdt.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog timer driver for the WinSystems EBC-C384
+ * Copyright (C) 2016 William Breathitt Gray
+ */
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/isa.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define MODULE_NAME "ebc-c384_wdt"
+#define WATCHDOG_TIMEOUT 60
+/*
+ * The timeout value in minutes must fit in a single byte when sent to the
+ * watchdog timer; the maximum timeout possible is 15300 (255 * 60) seconds.
+ */
+#define WATCHDOG_MAX_TIMEOUT 15300
+#define BASE_ADDR 0x564
+#define ADDR_EXTENT 5
+#define CFG_ADDR (BASE_ADDR + 1)
+#define PET_ADDR (BASE_ADDR + 2)
+
+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) ")");
+
+static unsigned timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static int ebc_c384_wdt_start(struct watchdog_device *wdev)
+{
+ unsigned t = wdev->timeout;
+
+ /* resolution is in minutes for timeouts greater than 255 seconds */
+ if (t > 255)
+ t = DIV_ROUND_UP(t, 60);
+
+ outb(t, PET_ADDR);
+
+ return 0;
+}
+
+static int ebc_c384_wdt_stop(struct watchdog_device *wdev)
+{
+ outb(0x00, PET_ADDR);
+
+ return 0;
+}
+
+static int ebc_c384_wdt_set_timeout(struct watchdog_device *wdev, unsigned t)
+{
+ /* resolution is in minutes for timeouts greater than 255 seconds */
+ if (t > 255) {
+ /* round second resolution up to minute granularity */
+ wdev->timeout = roundup(t, 60);
+
+ /* set watchdog timer for minutes */
+ outb(0x00, CFG_ADDR);
+ } else {
+ wdev->timeout = t;
+
+ /* set watchdog timer for seconds */
+ outb(0x80, CFG_ADDR);
+ }
+
+ return 0;
+}
+
+static const struct watchdog_ops ebc_c384_wdt_ops = {
+ .start = ebc_c384_wdt_start,
+ .stop = ebc_c384_wdt_stop,
+ .set_timeout = ebc_c384_wdt_set_timeout
+};
+
+static const struct watchdog_info ebc_c384_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT,
+ .identity = MODULE_NAME
+};
+
+static int ebc_c384_wdt_probe(struct device *dev, unsigned int id)
+{
+ struct watchdog_device *wdd;
+
+ if (!devm_request_region(dev, BASE_ADDR, ADDR_EXTENT, dev_name(dev))) {
+ dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
+ BASE_ADDR, BASE_ADDR + ADDR_EXTENT);
+ return -EBUSY;
+ }
+
+ wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
+ if (!wdd)
+ return -ENOMEM;
+
+ wdd->info = &ebc_c384_wdt_info;
+ wdd->ops = &ebc_c384_wdt_ops;
+ wdd->timeout = WATCHDOG_TIMEOUT;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = WATCHDOG_MAX_TIMEOUT;
+
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, timeout, dev);
+
+ return devm_watchdog_register_device(dev, wdd);
+}
+
+static struct isa_driver ebc_c384_wdt_driver = {
+ .probe = ebc_c384_wdt_probe,
+ .driver = {
+ .name = MODULE_NAME
+ },
+};
+
+static int __init ebc_c384_wdt_init(void)
+{
+ if (!dmi_match(DMI_BOARD_NAME, "EBC-C384 SBC"))
+ return -ENODEV;
+
+ return isa_register_driver(&ebc_c384_wdt_driver, 1);
+}
+
+static void __exit ebc_c384_wdt_exit(void)
+{
+ isa_unregister_driver(&ebc_c384_wdt_driver);
+}
+
+module_init(ebc_c384_wdt_init);
+module_exit(ebc_c384_wdt_exit);
+
+MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>");
+MODULE_DESCRIPTION("WinSystems EBC-C384 watchdog timer driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("isa:" MODULE_NAME);
diff --git a/drivers/watchdog/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c
new file mode 100644
index 000000000..38e26f160
--- /dev/null
+++ b/drivers/watchdog/ep93xx_wdt.c
@@ -0,0 +1,146 @@
+/*
+ * Watchdog driver for Cirrus Logic EP93xx family of devices.
+ *
+ * Copyright (c) 2004 Ray Lehtiniemi
+ * Copyright (c) 2006 Tower Technologies
+ * Based on ep93xx driver, bits from alim7101_wdt.c
+ *
+ * Authors: Ray Lehtiniemi <rayl@mail.com>,
+ * Alessandro Zummo <a.zummo@towertech.it>
+ *
+ * Copyright (c) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
+ * Convert to a platform device and use the watchdog framework API
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ *
+ * This watchdog fires after 250msec, which is a too short interval
+ * for us to rely on the user space daemon alone. So we ping the
+ * wdt each ~200msec and eventually stop doing it if the user space
+ * daemon dies.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+
+/* default timeout (secs) */
+#define WDT_TIMEOUT 30
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds.");
+
+#define EP93XX_WATCHDOG 0x00
+#define EP93XX_WDSTATUS 0x04
+
+struct ep93xx_wdt_priv {
+ void __iomem *mmio;
+ struct watchdog_device wdd;
+};
+
+static int ep93xx_wdt_start(struct watchdog_device *wdd)
+{
+ struct ep93xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writel(0xaaaa, priv->mmio + EP93XX_WATCHDOG);
+
+ return 0;
+}
+
+static int ep93xx_wdt_stop(struct watchdog_device *wdd)
+{
+ struct ep93xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writel(0xaa55, priv->mmio + EP93XX_WATCHDOG);
+
+ return 0;
+}
+
+static int ep93xx_wdt_ping(struct watchdog_device *wdd)
+{
+ struct ep93xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writel(0x5555, priv->mmio + EP93XX_WATCHDOG);
+
+ return 0;
+}
+
+static const struct watchdog_info ep93xx_wdt_ident = {
+ .options = WDIOF_CARDRESET |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "EP93xx Watchdog",
+};
+
+static const struct watchdog_ops ep93xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ep93xx_wdt_start,
+ .stop = ep93xx_wdt_stop,
+ .ping = ep93xx_wdt_ping,
+};
+
+static int ep93xx_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ep93xx_wdt_priv *priv;
+ struct watchdog_device *wdd;
+ unsigned long val;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mmio = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->mmio))
+ return PTR_ERR(priv->mmio);
+
+ val = readl(priv->mmio + EP93XX_WATCHDOG);
+
+ wdd = &priv->wdd;
+ wdd->bootstatus = (val & 0x01) ? WDIOF_CARDRESET : 0;
+ wdd->info = &ep93xx_wdt_ident;
+ wdd->ops = &ep93xx_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_hw_heartbeat_ms = 200;
+ wdd->parent = dev;
+
+ watchdog_set_nowayout(wdd, nowayout);
+
+ wdd->timeout = WDT_TIMEOUT;
+ watchdog_init_timeout(wdd, timeout, dev);
+
+ watchdog_set_drvdata(wdd, priv);
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "EP93XX watchdog driver %s\n",
+ (val & 0x08) ? " (nCS1 disable detected)" : "");
+
+ return 0;
+}
+
+static struct platform_driver ep93xx_wdt_driver = {
+ .driver = {
+ .name = "ep93xx-wdt",
+ },
+ .probe = ep93xx_wdt_probe,
+};
+
+module_platform_driver(ep93xx_wdt_driver);
+
+MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>");
+MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>");
+MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
+MODULE_DESCRIPTION("EP93xx Watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/eurotechwdt.c b/drivers/watchdog/eurotechwdt.c
new file mode 100644
index 000000000..e26609ad4
--- /dev/null
+++ b/drivers/watchdog/eurotechwdt.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Eurotech CPU-1220/1410/1420 on board WDT driver
+ *
+ * (c) Copyright 2001 Ascensit <support@ascensit.com>
+ * (c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com>
+ * (c) Copyright 2002 Rob Radez <rob@osinvestor.com>
+ *
+ * Based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>*
+ */
+
+/* Changelog:
+ *
+ * 2001 - Rodolfo Giometti
+ * Initial release
+ *
+ * 2002/04/25 - Rob Radez
+ * clean up #includes
+ * clean up locking
+ * make __setup param unique
+ * proper options in watchdog_info
+ * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls
+ * add expect_close support
+ *
+ * 2002.05.30 - Joel Becker <joel.becker@oracle.com>
+ * Added Matt Domsch's nowayout module option.
+ */
+
+/*
+ * The eurotech CPU-1220/1410/1420's watchdog is a part
+ * of the on-board SUPER I/O device SMSC FDC 37B782.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.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>
+
+
+static unsigned long eurwdt_is_open;
+static int eurwdt_timeout;
+static char eur_expect_close;
+static DEFINE_SPINLOCK(eurwdt_lock);
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ */
+
+static int io = 0x3f0;
+static int irq = 10;
+static char *ev = "int";
+
+#define WDT_TIMEOUT 60 /* 1 minute */
+
+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) ")");
+
+/*
+ * Some symbolic names
+ */
+
+#define WDT_CTRL_REG 0x30
+#define WDT_OUTPIN_CFG 0xe2
+#define WDT_EVENT_INT 0x00
+#define WDT_EVENT_REBOOT 0x08
+#define WDT_UNIT_SEL 0xf1
+#define WDT_UNIT_SECS 0x80
+#define WDT_TIMEOUT_VAL 0xf2
+#define WDT_TIMER_CFG 0xf3
+
+
+module_param_hw(io, int, ioport, 0);
+MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)");
+module_param_hw(irq, int, irq, 0);
+MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)");
+module_param(ev, charp, 0);
+MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')");
+
+
+/*
+ * Programming support
+ */
+
+static inline void eurwdt_write_reg(u8 index, u8 data)
+{
+ outb(index, io);
+ outb(data, io+1);
+}
+
+static inline void eurwdt_lock_chip(void)
+{
+ outb(0xaa, io);
+}
+
+static inline void eurwdt_unlock_chip(void)
+{
+ outb(0x55, io);
+ eurwdt_write_reg(0x07, 0x08); /* set the logical device */
+}
+
+static inline void eurwdt_set_timeout(int timeout)
+{
+ eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout);
+}
+
+static inline void eurwdt_disable_timer(void)
+{
+ eurwdt_set_timeout(0);
+}
+
+static void eurwdt_activate_timer(void)
+{
+ eurwdt_disable_timer();
+ eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */
+ eurwdt_write_reg(WDT_OUTPIN_CFG,
+ !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT);
+
+ /* Setting interrupt line */
+ if (irq == 2 || irq > 15 || irq < 0) {
+ pr_err("invalid irq number\n");
+ irq = 0; /* if invalid we disable interrupt */
+ }
+ if (irq == 0)
+ pr_info("interrupt disabled\n");
+
+ eurwdt_write_reg(WDT_TIMER_CFG, irq << 4);
+
+ eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */
+ eurwdt_set_timeout(0); /* the default timeout */
+}
+
+
+/*
+ * Kernel methods.
+ */
+
+static irqreturn_t eurwdt_interrupt(int irq, void *dev_id)
+{
+ pr_crit("timeout WDT timeout\n");
+
+#ifdef ONLY_TESTING
+ pr_crit("Would Reboot\n");
+#else
+ pr_crit("Initiating system reboot\n");
+ emergency_restart();
+#endif
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * eurwdt_ping:
+ *
+ * Reload counter one with the watchdog timeout.
+ */
+
+static void eurwdt_ping(void)
+{
+ /* Write the watchdog default value */
+ eurwdt_set_timeout(eurwdt_timeout);
+}
+
+/**
+ * eurwdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we don't define content meaning.
+ */
+
+static ssize_t eurwdt_write(struct file *file, const char __user *buf,
+size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ eur_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ eur_expect_close = 42;
+ }
+ }
+ spin_lock(&eurwdt_lock);
+ eurwdt_ping(); /* the default timeout */
+ spin_unlock(&eurwdt_lock);
+ }
+ return count;
+}
+
+/**
+ * eurwdt_ioctl:
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static long eurwdt_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 = "WDT Eurotech CPU-1220/1410",
+ };
+
+ int time;
+ int options, retval = -EINVAL;
+
+ 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:
+ if (get_user(options, p))
+ return -EFAULT;
+ spin_lock(&eurwdt_lock);
+ if (options & WDIOS_DISABLECARD) {
+ eurwdt_disable_timer();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ eurwdt_activate_timer();
+ eurwdt_ping();
+ retval = 0;
+ }
+ spin_unlock(&eurwdt_lock);
+ return retval;
+
+ case WDIOC_KEEPALIVE:
+ spin_lock(&eurwdt_lock);
+ eurwdt_ping();
+ spin_unlock(&eurwdt_lock);
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (copy_from_user(&time, p, sizeof(int)))
+ return -EFAULT;
+
+ /* Sanity check */
+ if (time < 0 || time > 255)
+ return -EINVAL;
+
+ spin_lock(&eurwdt_lock);
+ eurwdt_timeout = time;
+ eurwdt_set_timeout(time);
+ spin_unlock(&eurwdt_lock);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(eurwdt_timeout, p);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+/**
+ * eurwdt_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The misc device has been opened. The watchdog device is single
+ * open and on opening we load the counter.
+ */
+
+static int eurwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &eurwdt_is_open))
+ return -EBUSY;
+ eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */
+ /* Activate the WDT */
+ eurwdt_activate_timer();
+ return stream_open(inode, file);
+}
+
+/**
+ * eurwdt_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int eurwdt_release(struct inode *inode, struct file *file)
+{
+ if (eur_expect_close == 42)
+ eurwdt_disable_timer();
+ else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ eurwdt_ping();
+ }
+ clear_bit(0, &eurwdt_is_open);
+ eur_expect_close = 0;
+ return 0;
+}
+
+/**
+ * eurwdt_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ eurwdt_disable_timer(); /* Turn the card off */
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static const struct file_operations eurwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = eurwdt_write,
+ .unlocked_ioctl = eurwdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = eurwdt_open,
+ .release = eurwdt_release,
+};
+
+static struct miscdevice eurwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &eurwdt_fops,
+};
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block eurwdt_notifier = {
+ .notifier_call = eurwdt_notify_sys,
+};
+
+/**
+ * eurwdt_exit:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit eurwdt_exit(void)
+{
+ eurwdt_lock_chip();
+
+ misc_deregister(&eurwdt_miscdev);
+
+ unregister_reboot_notifier(&eurwdt_notifier);
+ release_region(io, 2);
+ free_irq(irq, NULL);
+}
+
+/**
+ * eurwdt_init:
+ *
+ * Set up the WDT watchdog board. After grabbing the resources
+ * we require we need also to unlock the device.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init eurwdt_init(void)
+{
+ int ret;
+
+ ret = request_irq(irq, eurwdt_interrupt, 0, "eurwdt", NULL);
+ if (ret) {
+ pr_err("IRQ %d is not free\n", irq);
+ goto out;
+ }
+
+ if (!request_region(io, 2, "eurwdt")) {
+ pr_err("IO %X is not free\n", io);
+ ret = -EBUSY;
+ goto outirq;
+ }
+
+ ret = register_reboot_notifier(&eurwdt_notifier);
+ if (ret) {
+ pr_err("can't register reboot notifier (err=%d)\n", ret);
+ goto outreg;
+ }
+
+ ret = misc_register(&eurwdt_miscdev);
+ if (ret) {
+ pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR);
+ goto outreboot;
+ }
+
+ eurwdt_unlock_chip();
+
+ ret = 0;
+ pr_info("Eurotech WDT driver 0.01 at %X (Interrupt %d) - timeout event: %s\n",
+ io, irq, (!strcmp("int", ev) ? "int" : "reboot"));
+
+out:
+ return ret;
+
+outreboot:
+ unregister_reboot_notifier(&eurwdt_notifier);
+
+outreg:
+ release_region(io, 2);
+
+outirq:
+ free_irq(irq, NULL);
+ goto out;
+}
+
+module_init(eurwdt_init);
+module_exit(eurwdt_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti");
+MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/exar_wdt.c b/drivers/watchdog/exar_wdt.c
new file mode 100644
index 000000000..7c61ff343
--- /dev/null
+++ b/drivers/watchdog/exar_wdt.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * exar_wdt.c - Driver for the watchdog present in some
+ * Exar/MaxLinear UART chips like the XR28V38x.
+ *
+ * (c) Copyright 2022 D. Müller <d.mueller@elsoft.ch>.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define DRV_NAME "exar_wdt"
+
+static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };
+static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };
+#define EXAR_EXIT_KEY 0xAA
+
+#define EXAR_LDN 0x07
+#define EXAR_DID 0x20
+#define EXAR_VID 0x23
+#define EXAR_WDT 0x26
+#define EXAR_ACT 0x30
+#define EXAR_RTBASE 0x60
+
+#define EXAR_WDT_LDEV 0x08
+
+#define EXAR_VEN_ID 0x13A8
+#define EXAR_DEV_382 0x0382
+#define EXAR_DEV_384 0x0384
+
+/* WDT runtime registers */
+#define WDT_CTRL 0x00
+#define WDT_VAL 0x01
+
+#define WDT_UNITS_10MS 0x0 /* the 10 millisec unit of the HW is not used */
+#define WDT_UNITS_SEC 0x2
+#define WDT_UNITS_MIN 0x4
+
+/* default WDT control for WDTOUT signal activ / rearm by read */
+#define EXAR_WDT_DEF_CONF 0
+
+struct wdt_pdev_node {
+ struct list_head list;
+ struct platform_device *pdev;
+ const char name[16];
+};
+
+struct wdt_priv {
+ /* the lock for WDT io operations */
+ spinlock_t io_lock;
+ struct resource wdt_res;
+ struct watchdog_device wdt_dev;
+ unsigned short did;
+ unsigned short config_port;
+ unsigned char enter_key;
+ unsigned char unit;
+ unsigned char timeout;
+};
+
+#define WATCHDOG_TIMEOUT 60
+
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1<=timeout<=15300, 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) ")");
+
+static int exar_sio_enter(const unsigned short config_port,
+ const unsigned char key)
+{
+ if (!request_muxed_region(config_port, 2, DRV_NAME))
+ return -EBUSY;
+
+ /* write the ENTER-KEY twice */
+ outb(key, config_port);
+ outb(key, config_port);
+
+ return 0;
+}
+
+static void exar_sio_exit(const unsigned short config_port)
+{
+ outb(EXAR_EXIT_KEY, config_port);
+ release_region(config_port, 2);
+}
+
+static unsigned char exar_sio_read(const unsigned short config_port,
+ const unsigned char reg)
+{
+ outb(reg, config_port);
+ return inb(config_port + 1);
+}
+
+static void exar_sio_write(const unsigned short config_port,
+ const unsigned char reg, const unsigned char val)
+{
+ outb(reg, config_port);
+ outb(val, config_port + 1);
+}
+
+static unsigned short exar_sio_read16(const unsigned short config_port,
+ const unsigned char reg)
+{
+ unsigned char msb, lsb;
+
+ msb = exar_sio_read(config_port, reg);
+ lsb = exar_sio_read(config_port, reg + 1);
+
+ return (msb << 8) | lsb;
+}
+
+static void exar_sio_select_wdt(const unsigned short config_port)
+{
+ exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);
+}
+
+static void exar_wdt_arm(const struct wdt_priv *priv)
+{
+ unsigned short rt_base = priv->wdt_res.start;
+
+ /* write timeout value twice to arm watchdog */
+ outb(priv->timeout, rt_base + WDT_VAL);
+ outb(priv->timeout, rt_base + WDT_VAL);
+}
+
+static void exar_wdt_disarm(const struct wdt_priv *priv)
+{
+ unsigned short rt_base = priv->wdt_res.start;
+
+ /*
+ * use two accesses with different values to make sure
+ * that a combination of a previous single access and
+ * the ones below with the same value are not falsely
+ * interpreted as "arm watchdog"
+ */
+ outb(0xFF, rt_base + WDT_VAL);
+ outb(0, rt_base + WDT_VAL);
+}
+
+static int exar_wdt_start(struct watchdog_device *wdog)
+{
+ struct wdt_priv *priv = watchdog_get_drvdata(wdog);
+ unsigned short rt_base = priv->wdt_res.start;
+
+ spin_lock(&priv->io_lock);
+
+ exar_wdt_disarm(priv);
+ outb(priv->unit, rt_base + WDT_CTRL);
+ exar_wdt_arm(priv);
+
+ spin_unlock(&priv->io_lock);
+ return 0;
+}
+
+static int exar_wdt_stop(struct watchdog_device *wdog)
+{
+ struct wdt_priv *priv = watchdog_get_drvdata(wdog);
+
+ spin_lock(&priv->io_lock);
+
+ exar_wdt_disarm(priv);
+
+ spin_unlock(&priv->io_lock);
+ return 0;
+}
+
+static int exar_wdt_keepalive(struct watchdog_device *wdog)
+{
+ struct wdt_priv *priv = watchdog_get_drvdata(wdog);
+ unsigned short rt_base = priv->wdt_res.start;
+
+ spin_lock(&priv->io_lock);
+
+ /* reading the WDT_VAL reg will feed the watchdog */
+ inb(rt_base + WDT_VAL);
+
+ spin_unlock(&priv->io_lock);
+ return 0;
+}
+
+static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
+{
+ struct wdt_priv *priv = watchdog_get_drvdata(wdog);
+ bool unit_min = false;
+
+ /*
+ * if new timeout is bigger then 255 seconds, change the
+ * unit to minutes and round the timeout up to the next whole minute
+ */
+ if (t > 255) {
+ unit_min = true;
+ t = DIV_ROUND_UP(t, 60);
+ }
+
+ /* save for later use in exar_wdt_start() */
+ priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;
+ priv->timeout = t;
+
+ wdog->timeout = unit_min ? t * 60 : t;
+
+ if (watchdog_hw_running(wdog))
+ exar_wdt_start(wdog);
+
+ return 0;
+}
+
+static const struct watchdog_info exar_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .identity = "Exar/MaxLinear XR28V38x Watchdog",
+};
+
+static const struct watchdog_ops exar_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = exar_wdt_start,
+ .stop = exar_wdt_stop,
+ .ping = exar_wdt_keepalive,
+ .set_timeout = exar_wdt_set_timeout,
+};
+
+static int exar_wdt_config(struct watchdog_device *wdog,
+ const unsigned char conf)
+{
+ struct wdt_priv *priv = watchdog_get_drvdata(wdog);
+ int ret;
+
+ ret = exar_sio_enter(priv->config_port, priv->enter_key);
+ if (ret)
+ return ret;
+
+ exar_sio_select_wdt(priv->config_port);
+ exar_sio_write(priv->config_port, EXAR_WDT, conf);
+
+ exar_sio_exit(priv->config_port);
+
+ return 0;
+}
+
+static int __init exar_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct wdt_priv *priv = dev->platform_data;
+ struct watchdog_device *wdt_dev = &priv->wdt_dev;
+ struct resource *res;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return -ENXIO;
+
+ spin_lock_init(&priv->io_lock);
+
+ wdt_dev->info = &exar_wdt_info;
+ wdt_dev->ops = &exar_wdt_ops;
+ wdt_dev->min_timeout = 1;
+ wdt_dev->max_timeout = 255 * 60;
+
+ watchdog_init_timeout(wdt_dev, timeout, NULL);
+ watchdog_set_nowayout(wdt_dev, nowayout);
+ watchdog_stop_on_reboot(wdt_dev);
+ watchdog_stop_on_unregister(wdt_dev);
+ watchdog_set_drvdata(wdt_dev, priv);
+
+ ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);
+ if (ret)
+ return ret;
+
+ exar_wdt_set_timeout(wdt_dev, timeout);
+ /* Make sure that the watchdog is not running */
+ exar_wdt_stop(wdt_dev);
+
+ ret = devm_watchdog_register_device(dev, wdt_dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",
+ priv->did, timeout, nowayout);
+
+ return 0;
+}
+
+static unsigned short __init exar_detect(const unsigned short config_port,
+ const unsigned char key,
+ unsigned short *rt_base)
+{
+ int ret;
+ unsigned short base = 0;
+ unsigned short vid, did;
+
+ ret = exar_sio_enter(config_port, key);
+ if (ret)
+ return 0;
+
+ vid = exar_sio_read16(config_port, EXAR_VID);
+ did = exar_sio_read16(config_port, EXAR_DID);
+
+ /* check for the vendor and device IDs we currently know about */
+ if (vid == EXAR_VEN_ID &&
+ (did == EXAR_DEV_382 ||
+ did == EXAR_DEV_384)) {
+ exar_sio_select_wdt(config_port);
+ /* is device active? */
+ if (exar_sio_read(config_port, EXAR_ACT) == 0x01)
+ base = exar_sio_read16(config_port, EXAR_RTBASE);
+ }
+
+ exar_sio_exit(config_port);
+
+ if (base) {
+ pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",
+ did, config_port, base);
+ *rt_base = base;
+ return did;
+ }
+
+ return 0;
+}
+
+static struct platform_driver exar_wdt_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static LIST_HEAD(pdev_list);
+
+static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)
+{
+ struct wdt_pdev_node *n;
+
+ n = kzalloc(sizeof(*n), GFP_KERNEL);
+ if (!n)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&n->list);
+
+ scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);
+ priv->wdt_res.name = n->name;
+
+ n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,
+ &priv->wdt_res, 1,
+ priv, sizeof(*priv));
+ if (IS_ERR(n->pdev)) {
+ int err = PTR_ERR(n->pdev);
+
+ kfree(n);
+ return err;
+ }
+
+ list_add_tail(&n->list, &pdev_list);
+
+ return 0;
+}
+
+static void exar_wdt_unregister(void)
+{
+ struct wdt_pdev_node *n, *t;
+
+ list_for_each_entry_safe(n, t, &pdev_list, list) {
+ platform_device_unregister(n->pdev);
+ list_del(&n->list);
+ kfree(n);
+ }
+}
+
+static int __init exar_wdt_init(void)
+{
+ int ret, i, j, idx = 0;
+
+ /* search for active Exar watchdogs on all possible locations */
+ for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {
+ for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {
+ unsigned short did, rt_base = 0;
+
+ did = exar_detect(sio_config_ports[i],
+ sio_enter_keys[j],
+ &rt_base);
+
+ if (did) {
+ struct wdt_priv priv = {
+ .wdt_res = DEFINE_RES_IO(rt_base, 2),
+ .did = did,
+ .config_port = sio_config_ports[i],
+ .enter_key = sio_enter_keys[j],
+ };
+
+ ret = exar_wdt_register(&priv, idx);
+ if (!ret)
+ idx++;
+ }
+ }
+ }
+
+ if (!idx)
+ return -ENODEV;
+
+ ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);
+ if (ret)
+ exar_wdt_unregister();
+
+ return ret;
+}
+
+static void __exit exar_wdt_exit(void)
+{
+ exar_wdt_unregister();
+ platform_driver_unregister(&exar_wdt_driver);
+}
+
+module_init(exar_wdt_init);
+module_exit(exar_wdt_exit);
+
+MODULE_AUTHOR("David Müller <d.mueller@elsoft.ch>");
+MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c
new file mode 100644
index 000000000..6a16d3d0b
--- /dev/null
+++ b/drivers/watchdog/f71808e_wdt.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/***************************************************************************
+ * Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> *
+ * Copyright (C) 2007-2009 Hans de Goede <hdegoede@redhat.com> *
+ * Copyright (C) 2010 Giel van Schijndel <me@mortis.eu> *
+ * *
+ ***************************************************************************/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define DRVNAME "f71808e_wdt"
+
+#define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */
+#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
+#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */
+
+#define SIO_REG_LDSEL 0x07 /* Logical device select */
+#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
+#define SIO_REG_DEVREV 0x22 /* Device revision */
+#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
+#define SIO_REG_CLOCK_SEL 0x26 /* Clock select */
+#define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */
+#define SIO_F81866_REG_PORT_SEL 0x27 /* F81866 Multi-Function Register */
+#define SIO_REG_TSI_LEVEL_SEL 0x28 /* TSI Level select */
+#define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */
+#define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */
+#define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */
+#define SIO_F81866_REG_GPIO1 0x2c /* F81866 GPIO1 Enable Register */
+#define SIO_REG_ENABLE 0x30 /* Logical device enable */
+#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
+
+#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
+#define SIO_F71808_ID 0x0901 /* Chipset ID */
+#define SIO_F71858_ID 0x0507 /* Chipset ID */
+#define SIO_F71862_ID 0x0601 /* Chipset ID */
+#define SIO_F71868_ID 0x1106 /* Chipset ID */
+#define SIO_F71869_ID 0x0814 /* Chipset ID */
+#define SIO_F71869A_ID 0x1007 /* Chipset ID */
+#define SIO_F71882_ID 0x0541 /* Chipset ID */
+#define SIO_F71889_ID 0x0723 /* Chipset ID */
+#define SIO_F81803_ID 0x1210 /* Chipset ID */
+#define SIO_F81865_ID 0x0704 /* Chipset ID */
+#define SIO_F81866_ID 0x1010 /* Chipset ID */
+#define SIO_F81966_ID 0x1502 /* F81804 chipset ID, same for f81966 */
+
+#define F71808FG_REG_WDO_CONF 0xf0
+#define F71808FG_REG_WDT_CONF 0xf5
+#define F71808FG_REG_WD_TIME 0xf6
+
+#define F71808FG_FLAG_WDOUT_EN 7
+
+#define F71808FG_FLAG_WDTMOUT_STS 6
+#define F71808FG_FLAG_WD_EN 5
+#define F71808FG_FLAG_WD_PULSE 4
+#define F71808FG_FLAG_WD_UNIT 3
+
+#define F81865_REG_WDO_CONF 0xfa
+#define F81865_FLAG_WDOUT_EN 0
+
+/* Default values */
+#define WATCHDOG_TIMEOUT 60 /* 1 minute default timeout */
+#define WATCHDOG_MAX_TIMEOUT (60 * 255)
+#define WATCHDOG_PULSE_WIDTH 125 /* 125 ms, default pulse width for
+ watchdog signal */
+#define WATCHDOG_F71862FG_PIN 63 /* default watchdog reset output
+ pin number 63 */
+
+static unsigned short force_id;
+module_param(force_id, ushort, 0);
+MODULE_PARM_DESC(force_id, "Override the detected device ID");
+
+static int timeout = WATCHDOG_TIMEOUT; /* default timeout in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1<= timeout <="
+ __MODULE_STRING(WATCHDOG_MAX_TIMEOUT) " (default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static unsigned int pulse_width = WATCHDOG_PULSE_WIDTH;
+module_param(pulse_width, uint, 0);
+MODULE_PARM_DESC(pulse_width,
+ "Watchdog signal pulse width. 0(=level), 1, 25, 30, 125, 150, 5000 or 6000 ms"
+ " (default=" __MODULE_STRING(WATCHDOG_PULSE_WIDTH) ")");
+
+static unsigned int f71862fg_pin = WATCHDOG_F71862FG_PIN;
+module_param(f71862fg_pin, uint, 0);
+MODULE_PARM_DESC(f71862fg_pin,
+ "Watchdog f71862fg reset output pin configuration. Choose pin 56 or 63"
+ " (default=" __MODULE_STRING(WATCHDOG_F71862FG_PIN)")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static unsigned int start_withtimeout;
+module_param(start_withtimeout, uint, 0);
+MODULE_PARM_DESC(start_withtimeout, "Start watchdog timer on module load with"
+ " given initial timeout. Zero (default) disables this feature.");
+
+enum chips { f71808fg, f71858fg, f71862fg, f71868, f71869, f71882fg, f71889fg,
+ f81803, f81865, f81866, f81966};
+
+static const char * const fintek_wdt_names[] = {
+ "f71808fg",
+ "f71858fg",
+ "f71862fg",
+ "f71868",
+ "f71869",
+ "f71882fg",
+ "f71889fg",
+ "f81803",
+ "f81865",
+ "f81866",
+ "f81966"
+};
+
+/* Super-I/O Function prototypes */
+static inline int superio_inb(int base, int reg);
+static inline int superio_inw(int base, int reg);
+static inline void superio_outb(int base, int reg, u8 val);
+static inline void superio_set_bit(int base, int reg, int bit);
+static inline void superio_clear_bit(int base, int reg, int bit);
+static inline int superio_enter(int base);
+static inline void superio_select(int base, int ld);
+static inline void superio_exit(int base);
+
+struct fintek_wdt {
+ struct watchdog_device wdd;
+ unsigned short sioaddr;
+ enum chips type;
+ struct watchdog_info ident;
+
+ u8 timer_val; /* content for the wd_time register */
+ char minutes_mode;
+ u8 pulse_val; /* pulse width flag */
+ char pulse_mode; /* enable pulse output mode? */
+};
+
+struct fintek_wdt_pdata {
+ enum chips type;
+};
+
+/* Super I/O functions */
+static inline int superio_inb(int base, int reg)
+{
+ outb(reg, base);
+ return inb(base + 1);
+}
+
+static int superio_inw(int base, int reg)
+{
+ int val;
+ val = superio_inb(base, reg) << 8;
+ val |= superio_inb(base, reg + 1);
+ return val;
+}
+
+static inline void superio_outb(int base, int reg, u8 val)
+{
+ outb(reg, base);
+ outb(val, base + 1);
+}
+
+static inline void superio_set_bit(int base, int reg, int bit)
+{
+ unsigned long val = superio_inb(base, reg);
+ __set_bit(bit, &val);
+ superio_outb(base, reg, val);
+}
+
+static inline void superio_clear_bit(int base, int reg, int bit)
+{
+ unsigned long val = superio_inb(base, reg);
+ __clear_bit(bit, &val);
+ superio_outb(base, reg, val);
+}
+
+static inline int superio_enter(int base)
+{
+ /* Don't step on other drivers' I/O space by accident */
+ if (!request_muxed_region(base, 2, DRVNAME)) {
+ pr_err("I/O address 0x%04x already in use\n", (int)base);
+ return -EBUSY;
+ }
+
+ /* according to the datasheet the key must be sent twice! */
+ outb(SIO_UNLOCK_KEY, base);
+ outb(SIO_UNLOCK_KEY, base);
+
+ return 0;
+}
+
+static inline void superio_select(int base, int ld)
+{
+ outb(SIO_REG_LDSEL, base);
+ outb(ld, base + 1);
+}
+
+static inline void superio_exit(int base)
+{
+ outb(SIO_LOCK_KEY, base);
+ release_region(base, 2);
+}
+
+static int fintek_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
+{
+ struct fintek_wdt *wd = watchdog_get_drvdata(wdd);
+
+ if (timeout > 0xff) {
+ wd->timer_val = DIV_ROUND_UP(timeout, 60);
+ wd->minutes_mode = true;
+ timeout = wd->timer_val * 60;
+ } else {
+ wd->timer_val = timeout;
+ wd->minutes_mode = false;
+ }
+
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static int fintek_wdt_set_pulse_width(struct fintek_wdt *wd, unsigned int pw)
+{
+ unsigned int t1 = 25, t2 = 125, t3 = 5000;
+
+ if (wd->type == f71868) {
+ t1 = 30;
+ t2 = 150;
+ t3 = 6000;
+ }
+
+ if (pw <= 1) {
+ wd->pulse_val = 0;
+ } else if (pw <= t1) {
+ wd->pulse_val = 1;
+ } else if (pw <= t2) {
+ wd->pulse_val = 2;
+ } else if (pw <= t3) {
+ wd->pulse_val = 3;
+ } else {
+ pr_err("pulse width out of range\n");
+ return -EINVAL;
+ }
+
+ wd->pulse_mode = pw;
+
+ return 0;
+}
+
+static int fintek_wdt_keepalive(struct watchdog_device *wdd)
+{
+ struct fintek_wdt *wd = watchdog_get_drvdata(wdd);
+ int err;
+
+ err = superio_enter(wd->sioaddr);
+ if (err)
+ return err;
+ superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+
+ if (wd->minutes_mode)
+ /* select minutes for timer units */
+ superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ F71808FG_FLAG_WD_UNIT);
+ else
+ /* select seconds for timer units */
+ superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ F71808FG_FLAG_WD_UNIT);
+
+ /* Set timer value */
+ superio_outb(wd->sioaddr, F71808FG_REG_WD_TIME,
+ wd->timer_val);
+
+ superio_exit(wd->sioaddr);
+
+ return 0;
+}
+
+static int fintek_wdt_start(struct watchdog_device *wdd)
+{
+ struct fintek_wdt *wd = watchdog_get_drvdata(wdd);
+ int err;
+ u8 tmp;
+
+ /* Make sure we don't die as soon as the watchdog is enabled below */
+ err = fintek_wdt_keepalive(wdd);
+ if (err)
+ return err;
+
+ err = superio_enter(wd->sioaddr);
+ if (err)
+ return err;
+ superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+
+ /* Watchdog pin configuration */
+ switch (wd->type) {
+ case f71808fg:
+ /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */
+ superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT2, 3);
+ superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 3);
+ break;
+
+ case f71862fg:
+ if (f71862fg_pin == 63) {
+ /* SPI must be disabled first to use this pin! */
+ superio_clear_bit(wd->sioaddr, SIO_REG_ROM_ADDR_SEL, 6);
+ superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT3, 4);
+ } else if (f71862fg_pin == 56) {
+ superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT1, 1);
+ }
+ break;
+
+ case f71868:
+ case f71869:
+ /* GPIO14 --> WDTRST# */
+ superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT1, 4);
+ break;
+
+ case f71882fg:
+ /* Set pin 56 to WDTRST# */
+ superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT1, 1);
+ break;
+
+ case f71889fg:
+ /* set pin 40 to WDTRST# */
+ superio_outb(wd->sioaddr, SIO_REG_MFUNCT3,
+ superio_inb(wd->sioaddr, SIO_REG_MFUNCT3) & 0xcf);
+ break;
+
+ case f81803:
+ /* Enable TSI Level register bank */
+ superio_clear_bit(wd->sioaddr, SIO_REG_CLOCK_SEL, 3);
+ /* Set pin 27 to WDTRST# */
+ superio_outb(wd->sioaddr, SIO_REG_TSI_LEVEL_SEL, 0x5f &
+ superio_inb(wd->sioaddr, SIO_REG_TSI_LEVEL_SEL));
+ break;
+
+ case f81865:
+ /* Set pin 70 to WDTRST# */
+ superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 5);
+ break;
+
+ case f81866:
+ case f81966:
+ /*
+ * GPIO1 Control Register when 27h BIT3:2 = 01 & BIT0 = 0.
+ * The PIN 70(GPIO15/WDTRST) is controlled by 2Ch:
+ * BIT5: 0 -> WDTRST#
+ * 1 -> GPIO15
+ */
+ tmp = superio_inb(wd->sioaddr, SIO_F81866_REG_PORT_SEL);
+ tmp &= ~(BIT(3) | BIT(0));
+ tmp |= BIT(2);
+ superio_outb(wd->sioaddr, SIO_F81866_REG_PORT_SEL, tmp);
+
+ superio_clear_bit(wd->sioaddr, SIO_F81866_REG_GPIO1, 5);
+ break;
+
+ default:
+ /*
+ * 'default' label to shut up the compiler and catch
+ * programmer errors
+ */
+ err = -ENODEV;
+ goto exit_superio;
+ }
+
+ superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+ superio_set_bit(wd->sioaddr, SIO_REG_ENABLE, 0);
+
+ if (wd->type == f81865 || wd->type == f81866 || wd->type == f81966)
+ superio_set_bit(wd->sioaddr, F81865_REG_WDO_CONF,
+ F81865_FLAG_WDOUT_EN);
+ else
+ superio_set_bit(wd->sioaddr, F71808FG_REG_WDO_CONF,
+ F71808FG_FLAG_WDOUT_EN);
+
+ superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ F71808FG_FLAG_WD_EN);
+
+ if (wd->pulse_mode) {
+ /* Select "pulse" output mode with given duration */
+ u8 wdt_conf = superio_inb(wd->sioaddr,
+ F71808FG_REG_WDT_CONF);
+
+ /* Set WD_PSWIDTH bits (1:0) */
+ wdt_conf = (wdt_conf & 0xfc) | (wd->pulse_val & 0x03);
+ /* Set WD_PULSE to "pulse" mode */
+ wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE);
+
+ superio_outb(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ wdt_conf);
+ } else {
+ /* Select "level" output mode */
+ superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ F71808FG_FLAG_WD_PULSE);
+ }
+
+exit_superio:
+ superio_exit(wd->sioaddr);
+
+ return err;
+}
+
+static int fintek_wdt_stop(struct watchdog_device *wdd)
+{
+ struct fintek_wdt *wd = watchdog_get_drvdata(wdd);
+ int err;
+
+ err = superio_enter(wd->sioaddr);
+ if (err)
+ return err;
+ superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+
+ superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF,
+ F71808FG_FLAG_WD_EN);
+
+ superio_exit(wd->sioaddr);
+
+ return 0;
+}
+
+static bool fintek_wdt_is_running(struct fintek_wdt *wd, u8 wdt_conf)
+{
+ return (superio_inb(wd->sioaddr, SIO_REG_ENABLE) & BIT(0))
+ && (wdt_conf & BIT(F71808FG_FLAG_WD_EN));
+}
+
+static const struct watchdog_ops fintek_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = fintek_wdt_start,
+ .stop = fintek_wdt_stop,
+ .ping = fintek_wdt_keepalive,
+ .set_timeout = fintek_wdt_set_timeout,
+};
+
+static int fintek_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fintek_wdt_pdata *pdata;
+ struct watchdog_device *wdd;
+ struct fintek_wdt *wd;
+ int wdt_conf, err = 0;
+ struct resource *res;
+ int sioaddr;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return -ENXIO;
+
+ sioaddr = res->start;
+
+ wd = devm_kzalloc(dev, sizeof(*wd), GFP_KERNEL);
+ if (!wd)
+ return -ENOMEM;
+
+ pdata = dev->platform_data;
+
+ wd->type = pdata->type;
+ wd->sioaddr = sioaddr;
+ wd->ident.options = WDIOF_SETTIMEOUT
+ | WDIOF_MAGICCLOSE
+ | WDIOF_KEEPALIVEPING
+ | WDIOF_CARDRESET;
+
+ snprintf(wd->ident.identity,
+ sizeof(wd->ident.identity), "%s watchdog",
+ fintek_wdt_names[wd->type]);
+
+ err = superio_enter(sioaddr);
+ if (err)
+ return err;
+ superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT);
+
+ wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF);
+
+ /*
+ * We don't want WDTMOUT_STS to stick around till regular reboot.
+ * Write 1 to the bit to clear it to zero.
+ */
+ superio_outb(sioaddr, F71808FG_REG_WDT_CONF,
+ wdt_conf | BIT(F71808FG_FLAG_WDTMOUT_STS));
+
+ wdd = &wd->wdd;
+
+ if (fintek_wdt_is_running(wd, wdt_conf))
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ superio_exit(sioaddr);
+
+ wdd->parent = dev;
+ wdd->info = &wd->ident;
+ wdd->ops = &fintek_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = WATCHDOG_MAX_TIMEOUT;
+
+ watchdog_set_drvdata(wdd, wd);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_stop_on_unregister(wdd);
+ watchdog_stop_on_reboot(wdd);
+ watchdog_init_timeout(wdd, start_withtimeout ?: timeout, NULL);
+
+ if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS))
+ wdd->bootstatus = WDIOF_CARDRESET;
+
+ /*
+ * WATCHDOG_HANDLE_BOOT_ENABLED can result in keepalive being directly
+ * called without a set_timeout before, so it needs to be done here
+ * unconditionally.
+ */
+ fintek_wdt_set_timeout(wdd, wdd->timeout);
+ fintek_wdt_set_pulse_width(wd, pulse_width);
+
+ if (start_withtimeout) {
+ err = fintek_wdt_start(wdd);
+ if (err) {
+ dev_err(dev, "cannot start watchdog timer\n");
+ return err;
+ }
+
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ dev_info(dev, "watchdog started with initial timeout of %u sec\n",
+ start_withtimeout);
+ }
+
+ return devm_watchdog_register_device(dev, wdd);
+}
+
+static int __init fintek_wdt_find(int sioaddr)
+{
+ enum chips type;
+ u16 devid;
+ int err = superio_enter(sioaddr);
+ if (err)
+ return err;
+
+ devid = superio_inw(sioaddr, SIO_REG_MANID);
+ if (devid != SIO_FINTEK_ID) {
+ pr_debug("Not a Fintek device\n");
+ err = -ENODEV;
+ goto exit;
+ }
+
+ devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID);
+ switch (devid) {
+ case SIO_F71808_ID:
+ type = f71808fg;
+ break;
+ case SIO_F71862_ID:
+ type = f71862fg;
+ break;
+ case SIO_F71868_ID:
+ type = f71868;
+ break;
+ case SIO_F71869_ID:
+ case SIO_F71869A_ID:
+ type = f71869;
+ break;
+ case SIO_F71882_ID:
+ type = f71882fg;
+ break;
+ case SIO_F71889_ID:
+ type = f71889fg;
+ break;
+ case SIO_F71858_ID:
+ /* Confirmed (by datasheet) not to have a watchdog. */
+ err = -ENODEV;
+ goto exit;
+ case SIO_F81803_ID:
+ type = f81803;
+ break;
+ case SIO_F81865_ID:
+ type = f81865;
+ break;
+ case SIO_F81866_ID:
+ type = f81866;
+ break;
+ case SIO_F81966_ID:
+ type = f81966;
+ break;
+ default:
+ pr_info("Unrecognized Fintek device: %04x\n",
+ (unsigned int)devid);
+ err = -ENODEV;
+ goto exit;
+ }
+
+ pr_info("Found %s watchdog chip, revision %d\n",
+ fintek_wdt_names[type],
+ (int)superio_inb(sioaddr, SIO_REG_DEVREV));
+
+exit:
+ superio_exit(sioaddr);
+ return err ? err : type;
+}
+
+static struct platform_driver fintek_wdt_driver = {
+ .probe = fintek_wdt_probe,
+ .driver = {
+ .name = DRVNAME,
+ },
+};
+
+static struct platform_device *fintek_wdt_pdev;
+
+static int __init fintek_wdt_init(void)
+{
+ static const unsigned short addrs[] = { 0x2e, 0x4e };
+ struct fintek_wdt_pdata pdata;
+ struct resource wdt_res = {};
+ int ret;
+ int i;
+
+ if (f71862fg_pin != 63 && f71862fg_pin != 56) {
+ pr_err("Invalid argument f71862fg_pin=%d\n", f71862fg_pin);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(addrs); i++) {
+ ret = fintek_wdt_find(addrs[i]);
+ if (ret >= 0)
+ break;
+ }
+ if (i == ARRAY_SIZE(addrs))
+ return ret;
+
+ pdata.type = ret;
+
+ ret = platform_driver_register(&fintek_wdt_driver);
+ if (ret)
+ return ret;
+
+ wdt_res.name = "superio port";
+ wdt_res.flags = IORESOURCE_IO;
+ wdt_res.start = addrs[i];
+ wdt_res.end = addrs[i] + 1;
+
+ fintek_wdt_pdev = platform_device_register_resndata(NULL, DRVNAME, -1,
+ &wdt_res, 1,
+ &pdata, sizeof(pdata));
+ if (IS_ERR(fintek_wdt_pdev)) {
+ platform_driver_unregister(&fintek_wdt_driver);
+ return PTR_ERR(fintek_wdt_pdev);
+ }
+
+ return 0;
+}
+
+static void __exit fintek_wdt_exit(void)
+{
+ platform_device_unregister(fintek_wdt_pdev);
+ platform_driver_unregister(&fintek_wdt_driver);
+}
+
+MODULE_DESCRIPTION("F71808E Watchdog Driver");
+MODULE_AUTHOR("Giel van Schijndel <me@mortis.eu>");
+MODULE_LICENSE("GPL");
+
+module_init(fintek_wdt_init);
+module_exit(fintek_wdt_exit);
diff --git a/drivers/watchdog/ftwdt010_wdt.c b/drivers/watchdog/ftwdt010_wdt.c
new file mode 100644
index 000000000..442c5bf63
--- /dev/null
+++ b/drivers/watchdog/ftwdt010_wdt.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Watchdog driver for Faraday Technology FTWDT010
+ *
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ *
+ * Inspired by the out-of-tree drivers from OpenWRT:
+ * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ */
+
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define FTWDT010_WDCOUNTER 0x0
+#define FTWDT010_WDLOAD 0x4
+#define FTWDT010_WDRESTART 0x8
+#define FTWDT010_WDCR 0xC
+
+#define WDRESTART_MAGIC 0x5AB9
+
+#define WDCR_CLOCK_5MHZ BIT(4)
+#define WDCR_WDEXT BIT(3)
+#define WDCR_WDINTR BIT(2)
+#define WDCR_SYS_RST BIT(1)
+#define WDCR_ENABLE BIT(0)
+
+#define WDT_CLOCK 5000000 /* 5 MHz */
+
+struct ftwdt010_wdt {
+ struct watchdog_device wdd;
+ struct device *dev;
+ void __iomem *base;
+ bool has_irq;
+};
+
+static inline
+struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct ftwdt010_wdt, wdd);
+}
+
+static void ftwdt010_enable(struct ftwdt010_wdt *gwdt,
+ unsigned int timeout,
+ bool need_irq)
+{
+ u32 enable;
+
+ writel(timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD);
+ writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
+ /* set clock before enabling */
+ enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST;
+ writel(enable, gwdt->base + FTWDT010_WDCR);
+ if (need_irq)
+ enable |= WDCR_WDINTR;
+ enable |= WDCR_ENABLE;
+ writel(enable, gwdt->base + FTWDT010_WDCR);
+}
+
+static int ftwdt010_wdt_start(struct watchdog_device *wdd)
+{
+ struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
+
+ ftwdt010_enable(gwdt, wdd->timeout, gwdt->has_irq);
+ return 0;
+}
+
+static int ftwdt010_wdt_stop(struct watchdog_device *wdd)
+{
+ struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
+
+ writel(0, gwdt->base + FTWDT010_WDCR);
+
+ return 0;
+}
+
+static int ftwdt010_wdt_ping(struct watchdog_device *wdd)
+{
+ struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
+
+ writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
+
+ return 0;
+}
+
+static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ if (watchdog_active(wdd))
+ ftwdt010_wdt_start(wdd);
+
+ return 0;
+}
+
+static int ftwdt010_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ ftwdt010_enable(to_ftwdt010_wdt(wdd), 0, false);
+ return 0;
+}
+
+static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data)
+{
+ struct ftwdt010_wdt *gwdt = data;
+
+ watchdog_notify_pretimeout(&gwdt->wdd);
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_ops ftwdt010_wdt_ops = {
+ .start = ftwdt010_wdt_start,
+ .stop = ftwdt010_wdt_stop,
+ .ping = ftwdt010_wdt_ping,
+ .set_timeout = ftwdt010_wdt_set_timeout,
+ .restart = ftwdt010_wdt_restart,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info ftwdt010_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT,
+ .identity = KBUILD_MODNAME,
+};
+
+
+static int ftwdt010_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ftwdt010_wdt *gwdt;
+ unsigned int reg;
+ int irq;
+ int ret;
+
+ gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
+ if (!gwdt)
+ return -ENOMEM;
+
+ gwdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(gwdt->base))
+ return PTR_ERR(gwdt->base);
+
+ gwdt->dev = dev;
+ gwdt->wdd.info = &ftwdt010_wdt_info;
+ gwdt->wdd.ops = &ftwdt010_wdt_ops;
+ gwdt->wdd.min_timeout = 1;
+ gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;
+ gwdt->wdd.parent = dev;
+
+ /*
+ * If 'timeout-sec' unspecified in devicetree, assume a 13 second
+ * default.
+ */
+ gwdt->wdd.timeout = 13U;
+ watchdog_init_timeout(&gwdt->wdd, 0, dev);
+
+ reg = readw(gwdt->base + FTWDT010_WDCR);
+ if (reg & WDCR_ENABLE) {
+ /* Watchdog was enabled by the bootloader, disable it. */
+ reg &= ~WDCR_ENABLE;
+ writel(reg, gwdt->base + FTWDT010_WDCR);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq > 0) {
+ ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0,
+ "watchdog bark", gwdt);
+ if (ret)
+ return ret;
+ gwdt->has_irq = true;
+ }
+
+ ret = devm_watchdog_register_device(dev, &gwdt->wdd);
+ if (ret)
+ return ret;
+
+ /* Set up platform driver data */
+ platform_set_drvdata(pdev, gwdt);
+ dev_info(dev, "FTWDT010 watchdog driver enabled\n");
+
+ return 0;
+}
+
+static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev)
+{
+ struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
+ unsigned int reg;
+
+ reg = readw(gwdt->base + FTWDT010_WDCR);
+ reg &= ~WDCR_ENABLE;
+ writel(reg, gwdt->base + FTWDT010_WDCR);
+
+ return 0;
+}
+
+static int __maybe_unused ftwdt010_wdt_resume(struct device *dev)
+{
+ struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
+ unsigned int reg;
+
+ if (watchdog_active(&gwdt->wdd)) {
+ reg = readw(gwdt->base + FTWDT010_WDCR);
+ reg |= WDCR_ENABLE;
+ writel(reg, gwdt->base + FTWDT010_WDCR);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend,
+ ftwdt010_wdt_resume)
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id ftwdt010_wdt_match[] = {
+ { .compatible = "faraday,ftwdt010" },
+ { .compatible = "cortina,gemini-watchdog" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match);
+#endif
+
+static struct platform_driver ftwdt010_wdt_driver = {
+ .probe = ftwdt010_wdt_probe,
+ .driver = {
+ .name = "ftwdt010-wdt",
+ .of_match_table = of_match_ptr(ftwdt010_wdt_match),
+ .pm = &ftwdt010_wdt_dev_pm_ops,
+ },
+};
+module_platform_driver(ftwdt010_wdt_driver);
+MODULE_AUTHOR("Linus Walleij");
+MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/gef_wdt.c b/drivers/watchdog/gef_wdt.c
new file mode 100644
index 000000000..df5406aa7
--- /dev/null
+++ b/drivers/watchdog/gef_wdt.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * GE watchdog userspace interface
+ *
+ * Author: Martyn Welch <martyn.welch@ge.com>
+ *
+ * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc.
+ *
+ * Based on: mv64x60_wdt.c (MV64X60 watchdog userspace interface)
+ * Author: James Chapman <jchapman@katalix.com>
+ */
+
+/* TODO:
+ * This driver does not provide support for the hardwares capability of sending
+ * an interrupt at a programmable threshold.
+ *
+ * This driver currently can only support 1 watchdog - there are 2 in the
+ * hardware that this driver supports. Thus one could be configured as a
+ * process-based watchdog (via /dev/watchdog), the second (using the interrupt
+ * capabilities) a kernel-based watchdog.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <sysdev/fsl_soc.h>
+
+/*
+ * The watchdog configuration register contains a pair of 2-bit fields,
+ * 1. a reload field, bits 27-26, which triggers a reload of
+ * the countdown register, and
+ * 2. an enable field, bits 25-24, which toggles between
+ * enabling and disabling the watchdog timer.
+ * Bit 31 is a read-only field which indicates whether the
+ * watchdog timer is currently enabled.
+ *
+ * The low 24 bits contain the timer reload value.
+ */
+#define GEF_WDC_ENABLE_SHIFT 24
+#define GEF_WDC_SERVICE_SHIFT 26
+#define GEF_WDC_ENABLED_SHIFT 31
+
+#define GEF_WDC_ENABLED_TRUE 1
+#define GEF_WDC_ENABLED_FALSE 0
+
+/* Flags bits */
+#define GEF_WDOG_FLAG_OPENED 0
+
+static unsigned long wdt_flags;
+static int wdt_status;
+static void __iomem *gef_wdt_regs;
+static int gef_wdt_timeout;
+static int gef_wdt_count;
+static unsigned int bus_clk;
+static char expect_close;
+static DEFINE_SPINLOCK(gef_wdt_spinlock);
+
+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) ")");
+
+
+static int gef_wdt_toggle_wdc(int enabled_predicate, int field_shift)
+{
+ u32 data;
+ u32 enabled;
+ int ret = 0;
+
+ spin_lock(&gef_wdt_spinlock);
+ data = ioread32be(gef_wdt_regs);
+ enabled = (data >> GEF_WDC_ENABLED_SHIFT) & 1;
+
+ /* only toggle the requested field if enabled state matches predicate */
+ if ((enabled ^ enabled_predicate) == 0) {
+ /* We write a 1, then a 2 -- to the appropriate field */
+ data = (1 << field_shift) | gef_wdt_count;
+ iowrite32be(data, gef_wdt_regs);
+
+ data = (2 << field_shift) | gef_wdt_count;
+ iowrite32be(data, gef_wdt_regs);
+ ret = 1;
+ }
+ spin_unlock(&gef_wdt_spinlock);
+
+ return ret;
+}
+
+static void gef_wdt_service(void)
+{
+ gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,
+ GEF_WDC_SERVICE_SHIFT);
+}
+
+static void gef_wdt_handler_enable(void)
+{
+ if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_FALSE,
+ GEF_WDC_ENABLE_SHIFT)) {
+ gef_wdt_service();
+ pr_notice("watchdog activated\n");
+ }
+}
+
+static void gef_wdt_handler_disable(void)
+{
+ if (gef_wdt_toggle_wdc(GEF_WDC_ENABLED_TRUE,
+ GEF_WDC_ENABLE_SHIFT))
+ pr_notice("watchdog deactivated\n");
+}
+
+static void gef_wdt_set_timeout(unsigned int timeout)
+{
+ /* maximum bus cycle count is 0xFFFFFFFF */
+ if (timeout > 0xFFFFFFFF / bus_clk)
+ timeout = 0xFFFFFFFF / bus_clk;
+
+ /* Register only holds upper 24 bits, bit shifted into lower 24 */
+ gef_wdt_count = (timeout * bus_clk) >> 8;
+ gef_wdt_timeout = timeout;
+}
+
+
+static ssize_t gef_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ gef_wdt_service();
+ }
+
+ return len;
+}
+
+static long gef_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int timeout;
+ int options;
+ void __user *argp = (void __user *)arg;
+ static const struct watchdog_info info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 0,
+ .identity = "GE watchdog",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(wdt_status, (int __user *)argp))
+ return -EFAULT;
+ wdt_status &= ~WDIOF_KEEPALIVEPING;
+ break;
+
+ case WDIOC_SETOPTIONS:
+ if (get_user(options, (int __user *)argp))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD)
+ gef_wdt_handler_disable();
+
+ if (options & WDIOS_ENABLECARD)
+ gef_wdt_handler_enable();
+ break;
+
+ case WDIOC_KEEPALIVE:
+ gef_wdt_service();
+ wdt_status |= WDIOF_KEEPALIVEPING;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(timeout, (int __user *)argp))
+ return -EFAULT;
+ gef_wdt_set_timeout(timeout);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ if (put_user(gef_wdt_timeout, (int __user *)argp))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static int gef_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ gef_wdt_handler_enable();
+
+ return stream_open(inode, file);
+}
+
+static int gef_wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42)
+ gef_wdt_handler_disable();
+ else {
+ pr_crit("unexpected close, not stopping timer!\n");
+ gef_wdt_service();
+ }
+ expect_close = 0;
+
+ clear_bit(GEF_WDOG_FLAG_OPENED, &wdt_flags);
+
+ return 0;
+}
+
+static const struct file_operations gef_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = gef_wdt_write,
+ .unlocked_ioctl = gef_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = gef_wdt_open,
+ .release = gef_wdt_release,
+};
+
+static struct miscdevice gef_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &gef_wdt_fops,
+};
+
+
+static int gef_wdt_probe(struct platform_device *dev)
+{
+ int timeout = 10;
+ u32 freq;
+
+ bus_clk = 133; /* in MHz */
+
+ freq = fsl_get_sys_freq();
+ if (freq != -1)
+ bus_clk = freq;
+
+ /* Map devices registers into memory */
+ gef_wdt_regs = of_iomap(dev->dev.of_node, 0);
+ if (gef_wdt_regs == NULL)
+ return -ENOMEM;
+
+ gef_wdt_set_timeout(timeout);
+
+ gef_wdt_handler_disable(); /* in case timer was already running */
+
+ return misc_register(&gef_wdt_miscdev);
+}
+
+static int gef_wdt_remove(struct platform_device *dev)
+{
+ misc_deregister(&gef_wdt_miscdev);
+
+ gef_wdt_handler_disable();
+
+ iounmap(gef_wdt_regs);
+
+ return 0;
+}
+
+static const struct of_device_id gef_wdt_ids[] = {
+ {
+ .compatible = "gef,fpga-wdt",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, gef_wdt_ids);
+
+static struct platform_driver gef_wdt_driver = {
+ .driver = {
+ .name = "gef_wdt",
+ .of_match_table = gef_wdt_ids,
+ },
+ .probe = gef_wdt_probe,
+ .remove = gef_wdt_remove,
+};
+
+static int __init gef_wdt_init(void)
+{
+ pr_info("GE watchdog driver\n");
+ return platform_driver_register(&gef_wdt_driver);
+}
+
+static void __exit gef_wdt_exit(void)
+{
+ platform_driver_unregister(&gef_wdt_driver);
+}
+
+module_init(gef_wdt_init);
+module_exit(gef_wdt_exit);
+
+MODULE_AUTHOR("Martyn Welch <martyn.welch@ge.com>");
+MODULE_DESCRIPTION("GE watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gef_wdt");
diff --git a/drivers/watchdog/geodewdt.c b/drivers/watchdog/geodewdt.c
new file mode 100644
index 000000000..0b699c783
--- /dev/null
+++ b/drivers/watchdog/geodewdt.c
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Watchdog timer for machines with the CS5535/CS5536 companion chip
+ *
+ * Copyright (C) 2006-2007, Advanced Micro Devices, Inc.
+ * Copyright (C) 2009 Andres Salomon <dilinger@collabora.co.uk>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+
+#include <linux/cs5535.h>
+
+#define GEODEWDT_HZ 500
+#define GEODEWDT_SCALE 6
+#define GEODEWDT_MAX_SECONDS 131
+
+#define WDT_FLAGS_OPEN 1
+#define WDT_FLAGS_ORPHAN 2
+
+#define DRV_NAME "geodewdt"
+#define WATCHDOG_NAME "Geode GX/LX WDT"
+#define WATCHDOG_TIMEOUT 60
+
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1<= timeout <=131, 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) ")");
+
+static struct platform_device *geodewdt_platform_device;
+static unsigned long wdt_flags;
+static struct cs5535_mfgpt_timer *wdt_timer;
+static int safe_close;
+
+static void geodewdt_ping(void)
+{
+ /* Stop the counter */
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
+
+ /* Reset the counter */
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
+
+ /* Enable the counter */
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
+}
+
+static void geodewdt_disable(void)
+{
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
+}
+
+static int geodewdt_set_heartbeat(int val)
+{
+ if (val < 1 || val > GEODEWDT_MAX_SECONDS)
+ return -EINVAL;
+
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, 0);
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2, val * GEODEWDT_HZ);
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_COUNTER, 0);
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
+
+ timeout = val;
+ return 0;
+}
+
+static int geodewdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags))
+ return -EBUSY;
+
+ if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags))
+ __module_get(THIS_MODULE);
+
+ geodewdt_ping();
+ return stream_open(inode, file);
+}
+
+static int geodewdt_release(struct inode *inode, struct file *file)
+{
+ if (safe_close) {
+ geodewdt_disable();
+ module_put(THIS_MODULE);
+ } else {
+ pr_crit("Unexpected close - watchdog is not stopping\n");
+ geodewdt_ping();
+
+ set_bit(WDT_FLAGS_ORPHAN, &wdt_flags);
+ }
+
+ clear_bit(WDT_FLAGS_OPEN, &wdt_flags);
+ safe_close = 0;
+ return 0;
+}
+
+static ssize_t geodewdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+ safe_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ if (c == 'V')
+ safe_close = 1;
+ }
+ }
+
+ geodewdt_ping();
+ }
+ return len;
+}
+
+static long geodewdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int interval;
+
+ static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+ };
+
+ 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 options, ret = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ geodewdt_disable();
+ ret = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ geodewdt_ping();
+ ret = 0;
+ }
+
+ return ret;
+ }
+ case WDIOC_KEEPALIVE:
+ geodewdt_ping();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(interval, p))
+ return -EFAULT;
+
+ if (geodewdt_set_heartbeat(interval))
+ return -EINVAL;
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static const struct file_operations geodewdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = geodewdt_write,
+ .unlocked_ioctl = geodewdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = geodewdt_open,
+ .release = geodewdt_release,
+};
+
+static struct miscdevice geodewdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &geodewdt_fops,
+};
+
+static int __init geodewdt_probe(struct platform_device *dev)
+{
+ int ret;
+
+ wdt_timer = cs5535_mfgpt_alloc_timer(MFGPT_TIMER_ANY, MFGPT_DOMAIN_WORKING);
+ if (!wdt_timer) {
+ pr_err("No timers were available\n");
+ return -ENODEV;
+ }
+
+ /* Set up the timer */
+
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_SETUP,
+ GEODEWDT_SCALE | (3 << 8));
+
+ /* Set up comparator 2 to reset when the event fires */
+ cs5535_mfgpt_toggle_event(wdt_timer, MFGPT_CMP2, MFGPT_EVENT_RESET, 1);
+
+ /* Set up the initial timeout */
+
+ cs5535_mfgpt_write(wdt_timer, MFGPT_REG_CMP2,
+ timeout * GEODEWDT_HZ);
+
+ ret = misc_register(&geodewdt_miscdev);
+
+ return ret;
+}
+
+static int geodewdt_remove(struct platform_device *dev)
+{
+ misc_deregister(&geodewdt_miscdev);
+ return 0;
+}
+
+static void geodewdt_shutdown(struct platform_device *dev)
+{
+ geodewdt_disable();
+}
+
+static struct platform_driver geodewdt_driver = {
+ .remove = geodewdt_remove,
+ .shutdown = geodewdt_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init geodewdt_init(void)
+{
+ int ret;
+
+ geodewdt_platform_device = platform_device_register_simple(DRV_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(geodewdt_platform_device))
+ return PTR_ERR(geodewdt_platform_device);
+
+ ret = platform_driver_probe(&geodewdt_driver, geodewdt_probe);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ platform_device_unregister(geodewdt_platform_device);
+ return ret;
+}
+
+static void __exit geodewdt_exit(void)
+{
+ platform_device_unregister(geodewdt_platform_device);
+ platform_driver_unregister(&geodewdt_driver);
+}
+
+module_init(geodewdt_init);
+module_exit(geodewdt_exit);
+
+MODULE_AUTHOR("Advanced Micro Devices, Inc");
+MODULE_DESCRIPTION("Geode GX/LX Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/gpio_wdt.c b/drivers/watchdog/gpio_wdt.c
new file mode 100644
index 000000000..0923201ce
--- /dev/null
+++ b/drivers/watchdog/gpio_wdt.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for watchdog device controlled through GPIO-line
+ *
+ * Author: 2013, Alexander Shiyan <shc_work@mail.ru>
+ */
+
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+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 SOFT_TIMEOUT_MIN 1
+#define SOFT_TIMEOUT_DEF 60
+
+enum {
+ HW_ALGO_TOGGLE,
+ HW_ALGO_LEVEL,
+};
+
+struct gpio_wdt_priv {
+ struct gpio_desc *gpiod;
+ bool state;
+ bool always_running;
+ unsigned int hw_algo;
+ struct watchdog_device wdd;
+};
+
+static void gpio_wdt_disable(struct gpio_wdt_priv *priv)
+{
+ /* Eternal ping */
+ gpiod_set_value_cansleep(priv->gpiod, 1);
+
+ /* Put GPIO back to tristate */
+ if (priv->hw_algo == HW_ALGO_TOGGLE)
+ gpiod_direction_input(priv->gpiod);
+}
+
+static int gpio_wdt_ping(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ switch (priv->hw_algo) {
+ case HW_ALGO_TOGGLE:
+ /* Toggle output pin */
+ priv->state = !priv->state;
+ gpiod_set_value_cansleep(priv->gpiod, priv->state);
+ break;
+ case HW_ALGO_LEVEL:
+ /* Pulse */
+ gpiod_set_value_cansleep(priv->gpiod, 1);
+ udelay(1);
+ gpiod_set_value_cansleep(priv->gpiod, 0);
+ break;
+ }
+ return 0;
+}
+
+static int gpio_wdt_start(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ priv->state = 0;
+ gpiod_direction_output(priv->gpiod, priv->state);
+
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return gpio_wdt_ping(wdd);
+}
+
+static int gpio_wdt_stop(struct watchdog_device *wdd)
+{
+ struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ if (!priv->always_running) {
+ gpio_wdt_disable(priv);
+ } else {
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ return 0;
+}
+
+static const struct watchdog_info gpio_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+ .identity = "GPIO Watchdog",
+};
+
+static const struct watchdog_ops gpio_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = gpio_wdt_start,
+ .stop = gpio_wdt_stop,
+ .ping = gpio_wdt_ping,
+};
+
+static int gpio_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct gpio_wdt_priv *priv;
+ enum gpiod_flags gflags;
+ unsigned int hw_margin;
+ const char *algo;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ ret = of_property_read_string(np, "hw_algo", &algo);
+ if (ret)
+ return ret;
+ if (!strcmp(algo, "toggle")) {
+ priv->hw_algo = HW_ALGO_TOGGLE;
+ gflags = GPIOD_IN;
+ } else if (!strcmp(algo, "level")) {
+ priv->hw_algo = HW_ALGO_LEVEL;
+ gflags = GPIOD_OUT_LOW;
+ } else {
+ return -EINVAL;
+ }
+
+ priv->gpiod = devm_gpiod_get(dev, NULL, gflags);
+ if (IS_ERR(priv->gpiod))
+ return PTR_ERR(priv->gpiod);
+
+ ret = of_property_read_u32(np,
+ "hw_margin_ms", &hw_margin);
+ if (ret)
+ return ret;
+ /* Disallow values lower than 2 and higher than 65535 ms */
+ if (hw_margin < 2 || hw_margin > 65535)
+ return -EINVAL;
+
+ priv->always_running = of_property_read_bool(np,
+ "always-running");
+
+ watchdog_set_drvdata(&priv->wdd, priv);
+
+ priv->wdd.info = &gpio_wdt_ident;
+ priv->wdd.ops = &gpio_wdt_ops;
+ priv->wdd.min_timeout = SOFT_TIMEOUT_MIN;
+ priv->wdd.max_hw_heartbeat_ms = hw_margin;
+ priv->wdd.parent = dev;
+ priv->wdd.timeout = SOFT_TIMEOUT_DEF;
+
+ watchdog_init_timeout(&priv->wdd, 0, dev);
+ watchdog_set_nowayout(&priv->wdd, nowayout);
+
+ watchdog_stop_on_reboot(&priv->wdd);
+
+ if (priv->always_running)
+ gpio_wdt_start(&priv->wdd);
+
+ return devm_watchdog_register_device(dev, &priv->wdd);
+}
+
+static const struct of_device_id gpio_wdt_dt_ids[] = {
+ { .compatible = "linux,wdt-gpio", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_wdt_dt_ids);
+
+static struct platform_driver gpio_wdt_driver = {
+ .driver = {
+ .name = "gpio-wdt",
+ .of_match_table = gpio_wdt_dt_ids,
+ },
+ .probe = gpio_wdt_probe,
+};
+
+#ifdef CONFIG_GPIO_WATCHDOG_ARCH_INITCALL
+static int __init gpio_wdt_init(void)
+{
+ return platform_driver_register(&gpio_wdt_driver);
+}
+arch_initcall(gpio_wdt_init);
+#else
+module_platform_driver(gpio_wdt_driver);
+#endif
+
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("GPIO Watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/gxp-wdt.c b/drivers/watchdog/gxp-wdt.c
new file mode 100644
index 000000000..2fd85be88
--- /dev/null
+++ b/drivers/watchdog/gxp-wdt.c
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define MASK_WDGCS_ENABLE 0x01
+#define MASK_WDGCS_RELOAD 0x04
+#define MASK_WDGCS_NMIEN 0x08
+#define MASK_WDGCS_WARN 0x80
+
+#define WDT_MAX_TIMEOUT_MS 655350
+#define WDT_DEFAULT_TIMEOUT 30
+#define SECS_TO_WDOG_TICKS(x) ((x) * 100)
+#define WDOG_TICKS_TO_SECS(x) ((x) / 100)
+
+#define GXP_WDT_CNT_OFS 0x10
+#define GXP_WDT_CTRL_OFS 0x16
+
+struct gxp_wdt {
+ void __iomem *base;
+ struct watchdog_device wdd;
+};
+
+static void gxp_wdt_enable_reload(struct gxp_wdt *drvdata)
+{
+ u8 val;
+
+ val = readb(drvdata->base + GXP_WDT_CTRL_OFS);
+ val |= (MASK_WDGCS_ENABLE | MASK_WDGCS_RELOAD);
+ writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
+}
+
+static int gxp_wdt_start(struct watchdog_device *wdd)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+
+ writew(SECS_TO_WDOG_TICKS(wdd->timeout), drvdata->base + GXP_WDT_CNT_OFS);
+ gxp_wdt_enable_reload(drvdata);
+ return 0;
+}
+
+static int gxp_wdt_stop(struct watchdog_device *wdd)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+ u8 val;
+
+ val = readb_relaxed(drvdata->base + GXP_WDT_CTRL_OFS);
+ val &= ~MASK_WDGCS_ENABLE;
+ writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
+ return 0;
+}
+
+static int gxp_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+ u32 actual;
+
+ wdd->timeout = timeout;
+ actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10);
+ writew(actual, drvdata->base + GXP_WDT_CNT_OFS);
+
+ return 0;
+}
+
+static unsigned int gxp_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+ u32 val = readw(drvdata->base + GXP_WDT_CNT_OFS);
+
+ return WDOG_TICKS_TO_SECS(val);
+}
+
+static int gxp_wdt_ping(struct watchdog_device *wdd)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+
+ gxp_wdt_enable_reload(drvdata);
+ return 0;
+}
+
+static int gxp_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
+
+ writew(1, drvdata->base + GXP_WDT_CNT_OFS);
+ gxp_wdt_enable_reload(drvdata);
+ mdelay(100);
+ return 0;
+}
+
+static const struct watchdog_ops gxp_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = gxp_wdt_start,
+ .stop = gxp_wdt_stop,
+ .ping = gxp_wdt_ping,
+ .set_timeout = gxp_wdt_set_timeout,
+ .get_timeleft = gxp_wdt_get_timeleft,
+ .restart = gxp_restart,
+};
+
+static const struct watchdog_info gxp_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "HPE GXP Watchdog timer",
+};
+
+static int gxp_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct gxp_wdt *drvdata;
+ int err;
+ u8 val;
+
+ drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ /*
+ * The register area where the timer and watchdog reside is disarranged.
+ * Hence mapping individual register blocks for the timer and watchdog
+ * is not recommended as they would have access to each others
+ * registers. Based on feedback the watchdog is no longer part of the
+ * device tree file and the timer driver now creates the watchdog as a
+ * child device. During the watchdogs creation, the timer driver passes
+ * the base address to the watchdog over the private interface.
+ */
+
+ drvdata->base = (void __iomem *)dev->platform_data;
+
+ drvdata->wdd.info = &gxp_wdt_info;
+ drvdata->wdd.ops = &gxp_wdt_ops;
+ drvdata->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS;
+ drvdata->wdd.parent = dev;
+ drvdata->wdd.timeout = WDT_DEFAULT_TIMEOUT;
+
+ watchdog_set_drvdata(&drvdata->wdd, drvdata);
+ watchdog_set_nowayout(&drvdata->wdd, WATCHDOG_NOWAYOUT);
+
+ val = readb(drvdata->base + GXP_WDT_CTRL_OFS);
+
+ if (val & MASK_WDGCS_ENABLE)
+ set_bit(WDOG_HW_RUNNING, &drvdata->wdd.status);
+
+ watchdog_set_restart_priority(&drvdata->wdd, 128);
+
+ watchdog_stop_on_reboot(&drvdata->wdd);
+ err = devm_watchdog_register_device(dev, &drvdata->wdd);
+ if (err) {
+ dev_err(dev, "Failed to register watchdog device");
+ return err;
+ }
+
+ dev_info(dev, "HPE GXP watchdog timer");
+
+ return 0;
+}
+
+static struct platform_driver gxp_wdt_driver = {
+ .probe = gxp_wdt_probe,
+ .driver = {
+ .name = "gxp-wdt",
+ },
+};
+module_platform_driver(gxp_wdt_driver);
+
+MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
+MODULE_AUTHOR("Jean-Marie Verdun <verdun@hpe.com>");
+MODULE_DESCRIPTION("Driver for GXP watchdog timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c
new file mode 100644
index 000000000..79ed1626d
--- /dev/null
+++ b/drivers/watchdog/hpwdt.c
@@ -0,0 +1,418 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HPE WatchDog Driver
+ * based on
+ *
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 2018 Hewlett Packard Enterprise Development LP
+ * Thomas Mingarelli <thomas.mingarelli@hpe.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#ifdef CONFIG_HPWDT_NMI_DECODING
+#include <asm/nmi.h>
+#endif
+#include <linux/crash_dump.h>
+
+#define HPWDT_VERSION "2.0.4"
+#define SECS_TO_TICKS(secs) ((secs) * 1000 / 128)
+#define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000)
+#define HPWDT_MAX_TICKS 65535
+#define HPWDT_MAX_TIMER TICKS_TO_SECS(HPWDT_MAX_TICKS)
+#define DEFAULT_MARGIN 30
+#define PRETIMEOUT_SEC 9
+
+static bool ilo5;
+static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static bool pretimeout = IS_ENABLED(CONFIG_HPWDT_NMI_DECODING);
+static int kdumptimeout = -1;
+
+static void __iomem *pci_mem_addr; /* the PCI-memory address */
+static unsigned long __iomem *hpwdt_nmistat;
+static unsigned long __iomem *hpwdt_timer_reg;
+static unsigned long __iomem *hpwdt_timer_con;
+
+static const struct pci_device_id hpwdt_devices[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB203) }, /* iLO2 */
+ { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3306) }, /* iLO3 */
+ { PCI_DEVICE(PCI_VENDOR_ID_HP_3PAR, 0x0389) }, /* PCtrl */
+ {0}, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, hpwdt_devices);
+
+static const struct pci_device_id hpwdt_blacklist[] = {
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP, 0x1979) }, /* auxilary iLO */
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3306, PCI_VENDOR_ID_HP_3PAR, 0x0289) }, /* CL */
+ {0}, /* terminate list */
+};
+
+static struct watchdog_device hpwdt_dev;
+/*
+ * Watchdog operations
+ */
+static int hpwdt_hw_is_running(void)
+{
+ return ioread8(hpwdt_timer_con) & 0x01;
+}
+
+static int hpwdt_start(struct watchdog_device *wdd)
+{
+ int control = 0x81 | (pretimeout ? 0x4 : 0);
+ int reload = SECS_TO_TICKS(min(wdd->timeout, wdd->max_hw_heartbeat_ms/1000));
+
+ dev_dbg(wdd->parent, "start watchdog 0x%08x:0x%08x:0x%02x\n", wdd->timeout, reload, control);
+ iowrite16(reload, hpwdt_timer_reg);
+ iowrite8(control, hpwdt_timer_con);
+
+ return 0;
+}
+
+static void hpwdt_stop(void)
+{
+ unsigned long data;
+
+ pr_debug("stop watchdog\n");
+
+ data = ioread8(hpwdt_timer_con);
+ data &= 0xFE;
+ iowrite8(data, hpwdt_timer_con);
+}
+
+static int hpwdt_stop_core(struct watchdog_device *wdd)
+{
+ hpwdt_stop();
+
+ return 0;
+}
+
+static void hpwdt_ping_ticks(int val)
+{
+ val = min(val, HPWDT_MAX_TICKS);
+ iowrite16(val, hpwdt_timer_reg);
+}
+
+static int hpwdt_ping(struct watchdog_device *wdd)
+{
+ int reload = SECS_TO_TICKS(min(wdd->timeout, wdd->max_hw_heartbeat_ms/1000));
+
+ dev_dbg(wdd->parent, "ping watchdog 0x%08x:0x%08x\n", wdd->timeout, reload);
+ hpwdt_ping_ticks(reload);
+
+ return 0;
+}
+
+static unsigned int hpwdt_gettimeleft(struct watchdog_device *wdd)
+{
+ return TICKS_TO_SECS(ioread16(hpwdt_timer_reg));
+}
+
+static int hpwdt_settimeout(struct watchdog_device *wdd, unsigned int val)
+{
+ dev_dbg(wdd->parent, "set_timeout = %d\n", val);
+
+ wdd->timeout = val;
+ if (val <= wdd->pretimeout) {
+ dev_dbg(wdd->parent, "pretimeout < timeout. Setting to zero\n");
+ wdd->pretimeout = 0;
+ pretimeout = false;
+ if (watchdog_active(wdd))
+ hpwdt_start(wdd);
+ }
+ hpwdt_ping(wdd);
+
+ return 0;
+}
+
+#ifdef CONFIG_HPWDT_NMI_DECODING
+static int hpwdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req)
+{
+ unsigned int val = 0;
+
+ dev_dbg(wdd->parent, "set_pretimeout = %d\n", req);
+ if (req) {
+ val = PRETIMEOUT_SEC;
+ if (val >= wdd->timeout)
+ return -EINVAL;
+ }
+
+ if (val != req)
+ dev_dbg(wdd->parent, "Rounding pretimeout to: %d\n", val);
+
+ wdd->pretimeout = val;
+ pretimeout = !!val;
+
+ if (watchdog_active(wdd))
+ hpwdt_start(wdd);
+
+ return 0;
+}
+
+static int hpwdt_my_nmi(void)
+{
+ return ioread8(hpwdt_nmistat) & 0x6;
+}
+
+/*
+ * NMI Handler
+ */
+static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs)
+{
+ unsigned int mynmi = hpwdt_my_nmi();
+ static char panic_msg[] =
+ "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";
+
+ if (ulReason == NMI_UNKNOWN && !mynmi)
+ return NMI_DONE;
+
+ if (ilo5 && !pretimeout && !mynmi)
+ return NMI_DONE;
+
+ if (kdumptimeout < 0)
+ hpwdt_stop();
+ else if (kdumptimeout == 0)
+ ;
+ else {
+ unsigned int val = max((unsigned int)kdumptimeout, hpwdt_dev.timeout);
+ hpwdt_ping_ticks(SECS_TO_TICKS(val));
+ }
+
+ hex_byte_pack(panic_msg, mynmi);
+ nmi_panic(regs, panic_msg);
+
+ return NMI_HANDLED;
+}
+#endif /* CONFIG_HPWDT_NMI_DECODING */
+
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_PRETIMEOUT |
+ WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .identity = "HPE iLO2+ HW Watchdog Timer",
+};
+
+/*
+ * Kernel interfaces
+ */
+
+static const struct watchdog_ops hpwdt_ops = {
+ .owner = THIS_MODULE,
+ .start = hpwdt_start,
+ .stop = hpwdt_stop_core,
+ .ping = hpwdt_ping,
+ .set_timeout = hpwdt_settimeout,
+ .get_timeleft = hpwdt_gettimeleft,
+#ifdef CONFIG_HPWDT_NMI_DECODING
+ .set_pretimeout = hpwdt_set_pretimeout,
+#endif
+};
+
+static struct watchdog_device hpwdt_dev = {
+ .info = &ident,
+ .ops = &hpwdt_ops,
+ .min_timeout = 1,
+ .timeout = DEFAULT_MARGIN,
+ .pretimeout = PRETIMEOUT_SEC,
+ .max_hw_heartbeat_ms = HPWDT_MAX_TIMER * 1000,
+};
+
+
+/*
+ * Init & Exit
+ */
+
+static int hpwdt_init_nmi_decoding(struct pci_dev *dev)
+{
+#ifdef CONFIG_HPWDT_NMI_DECODING
+ int retval;
+ /*
+ * Only one function can register for NMI_UNKNOWN
+ */
+ retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, 0, "hpwdt");
+ if (retval)
+ goto error;
+ retval = register_nmi_handler(NMI_SERR, hpwdt_pretimeout, 0, "hpwdt");
+ if (retval)
+ goto error1;
+ retval = register_nmi_handler(NMI_IO_CHECK, hpwdt_pretimeout, 0, "hpwdt");
+ if (retval)
+ goto error2;
+
+ dev_info(&dev->dev,
+ "HPE Watchdog Timer Driver: NMI decoding initialized\n");
+
+ return 0;
+
+error2:
+ unregister_nmi_handler(NMI_SERR, "hpwdt");
+error1:
+ unregister_nmi_handler(NMI_UNKNOWN, "hpwdt");
+error:
+ dev_warn(&dev->dev,
+ "Unable to register a die notifier (err=%d).\n",
+ retval);
+ return retval;
+#endif /* CONFIG_HPWDT_NMI_DECODING */
+ return 0;
+}
+
+static void hpwdt_exit_nmi_decoding(void)
+{
+#ifdef CONFIG_HPWDT_NMI_DECODING
+ unregister_nmi_handler(NMI_UNKNOWN, "hpwdt");
+ unregister_nmi_handler(NMI_SERR, "hpwdt");
+ unregister_nmi_handler(NMI_IO_CHECK, "hpwdt");
+#endif
+}
+
+static int hpwdt_init_one(struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ int retval;
+
+ /*
+ * First let's find out if we are on an iLO2+ server. We will
+ * not run on a legacy ASM box.
+ * So we only support the G5 ProLiant servers and higher.
+ */
+ if (dev->subsystem_vendor != PCI_VENDOR_ID_HP &&
+ dev->subsystem_vendor != PCI_VENDOR_ID_HP_3PAR) {
+ dev_warn(&dev->dev,
+ "This server does not have an iLO2+ ASIC.\n");
+ return -ENODEV;
+ }
+
+ if (pci_match_id(hpwdt_blacklist, dev)) {
+ dev_dbg(&dev->dev, "Not supported on this device\n");
+ return -ENODEV;
+ }
+
+ if (pci_enable_device(dev)) {
+ dev_warn(&dev->dev,
+ "Not possible to enable PCI Device: 0x%x:0x%x.\n",
+ ent->vendor, ent->device);
+ return -ENODEV;
+ }
+
+ pci_mem_addr = pci_iomap(dev, 1, 0x80);
+ if (!pci_mem_addr) {
+ dev_warn(&dev->dev,
+ "Unable to detect the iLO2+ server memory.\n");
+ retval = -ENOMEM;
+ goto error_pci_iomap;
+ }
+ hpwdt_nmistat = pci_mem_addr + 0x6e;
+ hpwdt_timer_reg = pci_mem_addr + 0x70;
+ hpwdt_timer_con = pci_mem_addr + 0x72;
+
+ /* Have the core update running timer until user space is ready */
+ if (hpwdt_hw_is_running()) {
+ dev_info(&dev->dev, "timer is running\n");
+ set_bit(WDOG_HW_RUNNING, &hpwdt_dev.status);
+ }
+
+ /* Initialize NMI Decoding functionality */
+ retval = hpwdt_init_nmi_decoding(dev);
+ if (retval != 0)
+ goto error_init_nmi_decoding;
+
+ watchdog_stop_on_unregister(&hpwdt_dev);
+ watchdog_set_nowayout(&hpwdt_dev, nowayout);
+ watchdog_init_timeout(&hpwdt_dev, soft_margin, NULL);
+
+ if (is_kdump_kernel()) {
+ pretimeout = false;
+ kdumptimeout = 0;
+ }
+
+ if (pretimeout && hpwdt_dev.timeout <= PRETIMEOUT_SEC) {
+ dev_warn(&dev->dev, "timeout <= pretimeout. Setting pretimeout to zero\n");
+ pretimeout = false;
+ }
+ hpwdt_dev.pretimeout = pretimeout ? PRETIMEOUT_SEC : 0;
+ kdumptimeout = min(kdumptimeout, HPWDT_MAX_TIMER);
+
+ hpwdt_dev.parent = &dev->dev;
+ retval = watchdog_register_device(&hpwdt_dev);
+ if (retval < 0)
+ goto error_wd_register;
+
+ dev_info(&dev->dev, "HPE Watchdog Timer Driver: Version: %s\n",
+ HPWDT_VERSION);
+ dev_info(&dev->dev, "timeout: %d seconds (nowayout=%d)\n",
+ hpwdt_dev.timeout, nowayout);
+ dev_info(&dev->dev, "pretimeout: %s.\n",
+ pretimeout ? "on" : "off");
+ dev_info(&dev->dev, "kdumptimeout: %d.\n", kdumptimeout);
+
+ if (dev->subsystem_vendor == PCI_VENDOR_ID_HP_3PAR)
+ ilo5 = true;
+
+ return 0;
+
+error_wd_register:
+ hpwdt_exit_nmi_decoding();
+error_init_nmi_decoding:
+ pci_iounmap(dev, pci_mem_addr);
+error_pci_iomap:
+ pci_disable_device(dev);
+ return retval;
+}
+
+static void hpwdt_exit(struct pci_dev *dev)
+{
+ watchdog_unregister_device(&hpwdt_dev);
+ hpwdt_exit_nmi_decoding();
+ pci_iounmap(dev, pci_mem_addr);
+ pci_disable_device(dev);
+}
+
+static struct pci_driver hpwdt_driver = {
+ .name = "hpwdt",
+ .id_table = hpwdt_devices,
+ .probe = hpwdt_init_one,
+ .remove = hpwdt_exit,
+};
+
+MODULE_AUTHOR("Tom Mingarelli");
+MODULE_DESCRIPTION("hpe watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(HPWDT_VERSION);
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds");
+
+module_param_named(timeout, soft_margin, int, 0);
+MODULE_PARM_DESC(timeout, "Alias of soft_margin");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+module_param(kdumptimeout, int, 0444);
+MODULE_PARM_DESC(kdumptimeout, "Timeout applied for crash kernel transition in seconds");
+
+#ifdef CONFIG_HPWDT_NMI_DECODING
+module_param(pretimeout, bool, 0);
+MODULE_PARM_DESC(pretimeout, "Watchdog pretimeout enabled");
+#endif
+
+module_pci_driver(hpwdt_driver);
diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c
new file mode 100644
index 000000000..a30835f54
--- /dev/null
+++ b/drivers/watchdog/i6300esb.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * i6300esb: Watchdog timer driver for Intel 6300ESB chipset
+ *
+ * (c) Copyright 2004 Google Inc.
+ * (c) Copyright 2005 David Härdeman <david@2gen.com>
+ *
+ * based on i810-tco.c which is in turn based on softdog.c
+ *
+ * The timer is implemented in the following I/O controller hubs:
+ * (See the intel documentation on http://developer.intel.com.)
+ * 6300ESB chip : document number 300641-004
+ *
+ * 2004YYZZ Ross Biro
+ * Initial version 0.01
+ * 2004YYZZ Ross Biro
+ * Version 0.02
+ * 20050210 David Härdeman <david@2gen.com>
+ * Ported driver to kernel 2.6
+ * 20171016 Radu Rendec <rrendec@arista.com>
+ * Change driver to use the watchdog subsystem
+ * Add support for multiple 6300ESB devices
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+/* Module and version information */
+#define ESB_MODULE_NAME "i6300ESB timer"
+
+/* PCI configuration registers */
+#define ESB_CONFIG_REG 0x60 /* Config register */
+#define ESB_LOCK_REG 0x68 /* WDT lock register */
+
+/* Memory mapped registers */
+#define ESB_TIMER1_REG(w) ((w)->base + 0x00)/* Timer1 value after each reset */
+#define ESB_TIMER2_REG(w) ((w)->base + 0x04)/* Timer2 value after each reset */
+#define ESB_GINTSR_REG(w) ((w)->base + 0x08)/* General Interrupt Status Reg */
+#define ESB_RELOAD_REG(w) ((w)->base + 0x0c)/* Reload register */
+
+/* Lock register bits */
+#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */
+#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */
+#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */
+
+/* Config register bits */
+#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */
+#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */
+#define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */
+
+/* Reload register bits */
+#define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */
+#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */
+
+/* Magic constants */
+#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */
+#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */
+
+/* module parameters */
+/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */
+#define ESB_HEARTBEAT_MIN 1
+#define ESB_HEARTBEAT_MAX 2046
+#define ESB_HEARTBEAT_DEFAULT 30
+#define ESB_HEARTBEAT_RANGE __MODULE_STRING(ESB_HEARTBEAT_MIN) \
+ "<heartbeat<" __MODULE_STRING(ESB_HEARTBEAT_MAX)
+static int heartbeat; /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat in seconds. (" ESB_HEARTBEAT_RANGE
+ ", default=" __MODULE_STRING(ESB_HEARTBEAT_DEFAULT) ")");
+
+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) ")");
+
+/* internal variables */
+struct esb_dev {
+ struct watchdog_device wdd;
+ void __iomem *base;
+ struct pci_dev *pdev;
+};
+
+#define to_esb_dev(wptr) container_of(wptr, struct esb_dev, wdd)
+
+/*
+ * Some i6300ESB specific functions
+ */
+
+/*
+ * Prepare for reloading the timer by unlocking the proper registers.
+ * This is performed by first writing 0x80 followed by 0x86 to the
+ * reload register. After this the appropriate registers can be written
+ * to once before they need to be unlocked again.
+ */
+static inline void esb_unlock_registers(struct esb_dev *edev)
+{
+ writew(ESB_UNLOCK1, ESB_RELOAD_REG(edev));
+ writew(ESB_UNLOCK2, ESB_RELOAD_REG(edev));
+}
+
+static int esb_timer_start(struct watchdog_device *wdd)
+{
+ struct esb_dev *edev = to_esb_dev(wdd);
+ int _wdd_nowayout = test_bit(WDOG_NO_WAY_OUT, &wdd->status);
+ u8 val;
+
+ esb_unlock_registers(edev);
+ writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
+ /* Enable or Enable + Lock? */
+ val = ESB_WDT_ENABLE | (_wdd_nowayout ? ESB_WDT_LOCK : 0x00);
+ pci_write_config_byte(edev->pdev, ESB_LOCK_REG, val);
+ return 0;
+}
+
+static int esb_timer_stop(struct watchdog_device *wdd)
+{
+ struct esb_dev *edev = to_esb_dev(wdd);
+ u8 val;
+
+ /* First, reset timers as suggested by the docs */
+ esb_unlock_registers(edev);
+ writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
+ /* Then disable the WDT */
+ pci_write_config_byte(edev->pdev, ESB_LOCK_REG, 0x0);
+ pci_read_config_byte(edev->pdev, ESB_LOCK_REG, &val);
+
+ /* Returns 0 if the timer was disabled, non-zero otherwise */
+ return val & ESB_WDT_ENABLE;
+}
+
+static int esb_timer_keepalive(struct watchdog_device *wdd)
+{
+ struct esb_dev *edev = to_esb_dev(wdd);
+
+ esb_unlock_registers(edev);
+ writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
+ /* FIXME: Do we need to flush anything here? */
+ return 0;
+}
+
+static int esb_timer_set_heartbeat(struct watchdog_device *wdd,
+ unsigned int time)
+{
+ struct esb_dev *edev = to_esb_dev(wdd);
+ u32 val;
+
+ /* We shift by 9, so if we are passed a value of 1 sec,
+ * val will be 1 << 9 = 512, then write that to two
+ * timers => 2 * 512 = 1024 (which is decremented at 1KHz)
+ */
+ val = time << 9;
+
+ /* Write timer 1 */
+ esb_unlock_registers(edev);
+ writel(val, ESB_TIMER1_REG(edev));
+
+ /* Write timer 2 */
+ esb_unlock_registers(edev);
+ writel(val, ESB_TIMER2_REG(edev));
+
+ /* Reload */
+ esb_unlock_registers(edev);
+ writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(edev));
+
+ /* FIXME: Do we need to flush everything out? */
+
+ /* Done */
+ wdd->timeout = time;
+ return 0;
+}
+
+/*
+ * Watchdog Subsystem Interfaces
+ */
+
+static struct watchdog_info esb_info = {
+ .identity = ESB_MODULE_NAME,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops esb_ops = {
+ .owner = THIS_MODULE,
+ .start = esb_timer_start,
+ .stop = esb_timer_stop,
+ .set_timeout = esb_timer_set_heartbeat,
+ .ping = esb_timer_keepalive,
+};
+
+/*
+ * Data for PCI driver interface
+ */
+static const struct pci_device_id esb_pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), },
+ { 0, }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, esb_pci_tbl);
+
+/*
+ * Init & exit routines
+ */
+
+static unsigned char esb_getdevice(struct esb_dev *edev)
+{
+ if (pci_enable_device(edev->pdev)) {
+ dev_err(&edev->pdev->dev, "failed to enable device\n");
+ goto err_devput;
+ }
+
+ if (pci_request_region(edev->pdev, 0, ESB_MODULE_NAME)) {
+ dev_err(&edev->pdev->dev, "failed to request region\n");
+ goto err_disable;
+ }
+
+ edev->base = pci_ioremap_bar(edev->pdev, 0);
+ if (edev->base == NULL) {
+ /* Something's wrong here, BASEADDR has to be set */
+ dev_err(&edev->pdev->dev, "failed to get BASEADDR\n");
+ goto err_release;
+ }
+
+ /* Done */
+ dev_set_drvdata(&edev->pdev->dev, edev);
+ return 1;
+
+err_release:
+ pci_release_region(edev->pdev, 0);
+err_disable:
+ pci_disable_device(edev->pdev);
+err_devput:
+ return 0;
+}
+
+static void esb_initdevice(struct esb_dev *edev)
+{
+ u8 val1;
+ u16 val2;
+
+ /*
+ * Config register:
+ * Bit 5 : 0 = Enable WDT_OUTPUT
+ * Bit 2 : 0 = set the timer frequency to the PCI clock
+ * divided by 2^15 (approx 1KHz).
+ * Bits 1:0 : 11 = WDT_INT_TYPE Disabled.
+ * The watchdog has two timers, it can be setup so that the
+ * expiry of timer1 results in an interrupt and the expiry of
+ * timer2 results in a reboot. We set it to not generate
+ * any interrupts as there is not much we can do with it
+ * right now.
+ */
+ pci_write_config_word(edev->pdev, ESB_CONFIG_REG, 0x0003);
+
+ /* Check that the WDT isn't already locked */
+ pci_read_config_byte(edev->pdev, ESB_LOCK_REG, &val1);
+ if (val1 & ESB_WDT_LOCK)
+ dev_warn(&edev->pdev->dev, "nowayout already set\n");
+
+ /* Set the timer to watchdog mode and disable it for now */
+ pci_write_config_byte(edev->pdev, ESB_LOCK_REG, 0x00);
+
+ /* Check if the watchdog was previously triggered */
+ esb_unlock_registers(edev);
+ val2 = readw(ESB_RELOAD_REG(edev));
+ if (val2 & ESB_WDT_TIMEOUT)
+ edev->wdd.bootstatus = WDIOF_CARDRESET;
+
+ /* Reset WDT_TIMEOUT flag and timers */
+ esb_unlock_registers(edev);
+ writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG(edev));
+
+ /* And set the correct timeout value */
+ esb_timer_set_heartbeat(&edev->wdd, edev->wdd.timeout);
+}
+
+static int esb_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct esb_dev *edev;
+ int ret;
+
+ edev = devm_kzalloc(&pdev->dev, sizeof(*edev), GFP_KERNEL);
+ if (!edev)
+ return -ENOMEM;
+
+ /* Check whether or not the hardware watchdog is there */
+ edev->pdev = pdev;
+ if (!esb_getdevice(edev))
+ return -ENODEV;
+
+ /* Initialize the watchdog and make sure it does not run */
+ edev->wdd.info = &esb_info;
+ edev->wdd.ops = &esb_ops;
+ edev->wdd.min_timeout = ESB_HEARTBEAT_MIN;
+ edev->wdd.max_timeout = ESB_HEARTBEAT_MAX;
+ edev->wdd.timeout = ESB_HEARTBEAT_DEFAULT;
+ watchdog_init_timeout(&edev->wdd, heartbeat, NULL);
+ watchdog_set_nowayout(&edev->wdd, nowayout);
+ watchdog_stop_on_reboot(&edev->wdd);
+ watchdog_stop_on_unregister(&edev->wdd);
+ esb_initdevice(edev);
+
+ /* Register the watchdog so that userspace has access to it */
+ ret = watchdog_register_device(&edev->wdd);
+ if (ret != 0)
+ goto err_unmap;
+ dev_info(&pdev->dev,
+ "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ edev->wdd.timeout, nowayout);
+ return 0;
+
+err_unmap:
+ iounmap(edev->base);
+ pci_release_region(edev->pdev, 0);
+ pci_disable_device(edev->pdev);
+ return ret;
+}
+
+static void esb_remove(struct pci_dev *pdev)
+{
+ struct esb_dev *edev = dev_get_drvdata(&pdev->dev);
+
+ watchdog_unregister_device(&edev->wdd);
+ iounmap(edev->base);
+ pci_release_region(edev->pdev, 0);
+ pci_disable_device(edev->pdev);
+}
+
+static struct pci_driver esb_driver = {
+ .name = ESB_MODULE_NAME,
+ .id_table = esb_pci_tbl,
+ .probe = esb_probe,
+ .remove = esb_remove,
+};
+
+module_pci_driver(esb_driver);
+
+MODULE_AUTHOR("Ross Biro and David Härdeman");
+MODULE_DESCRIPTION("Watchdog driver for Intel 6300ESB chipsets");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h
new file mode 100644
index 000000000..69e92e692
--- /dev/null
+++ b/drivers/watchdog/iTCO_vendor.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* iTCO Vendor Specific Support hooks */
+#ifdef CONFIG_ITCO_VENDOR_SUPPORT
+extern int iTCO_vendorsupport;
+extern void iTCO_vendor_pre_start(struct resource *, unsigned int);
+extern void iTCO_vendor_pre_stop(struct resource *);
+extern int iTCO_vendor_check_noreboot_on(void);
+#else
+#define iTCO_vendorsupport 0
+#define iTCO_vendor_pre_start(acpibase, heartbeat) {}
+#define iTCO_vendor_pre_stop(acpibase) {}
+#define iTCO_vendor_check_noreboot_on() 1
+ /* 1=check noreboot; 0=don't check */
+#endif
diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c
new file mode 100644
index 000000000..cf0eaa04b
--- /dev/null
+++ b/drivers/watchdog/iTCO_vendor_support.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * intel TCO vendor specific watchdog driver support
+ *
+ * (c) Copyright 2006-2009 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/* Module and version information */
+#define DRV_NAME "iTCO_vendor_support"
+#define DRV_VERSION "1.04"
+
+/* Includes */
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/io.h> /* For inb/outb/... */
+
+#include "iTCO_vendor.h"
+
+/* List of vendor support modes */
+/* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */
+#define SUPERMICRO_OLD_BOARD 1
+/* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems - no longer supported */
+#define SUPERMICRO_NEW_BOARD 2
+/* Broken BIOS */
+#define BROKEN_BIOS 911
+
+int iTCO_vendorsupport;
+EXPORT_SYMBOL(iTCO_vendorsupport);
+
+module_param_named(vendorsupport, iTCO_vendorsupport, int, 0);
+MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default="
+ "0 (none), 1=SuperMicro Pent3, 911=Broken SMI BIOS");
+
+/*
+ * Vendor Specific Support
+ */
+
+/*
+ * Vendor Support: 1
+ * Board: Super Micro Computer Inc. 370SSE+-OEM1/P3TSSE
+ * iTCO chipset: ICH2
+ *
+ * Code contributed by: R. Seretny <lkpatches@paypc.com>
+ * Documentation obtained by R. Seretny from SuperMicro Technical Support
+ *
+ * To enable Watchdog function:
+ * BIOS setup -> Power -> TCO Logic SMI Enable -> Within5Minutes
+ * This setting enables SMI to clear the watchdog expired flag.
+ * If BIOS or CPU fail which may cause SMI hang, then system will
+ * reboot. When application starts to use watchdog function,
+ * application has to take over the control from SMI.
+ *
+ * For P3TSSE, J36 jumper needs to be removed to enable the Watchdog
+ * function.
+ *
+ * Note: The system will reboot when Expire Flag is set TWICE.
+ * So, if the watchdog timer is 20 seconds, then the maximum hang
+ * time is about 40 seconds, and the minimum hang time is about
+ * 20.6 seconds.
+ */
+
+static void supermicro_old_pre_start(struct resource *smires)
+{
+ unsigned long val32;
+
+ /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */
+ val32 = inl(smires->start);
+ val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
+ outl(val32, smires->start); /* Needed to activate watchdog */
+}
+
+static void supermicro_old_pre_stop(struct resource *smires)
+{
+ unsigned long val32;
+
+ /* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */
+ val32 = inl(smires->start);
+ val32 |= 0x00002000; /* Turn on SMI clearing watchdog */
+ outl(val32, smires->start); /* Needed to deactivate watchdog */
+}
+
+/*
+ * Vendor Support: 911
+ * Board: Some Intel ICHx based motherboards
+ * iTCO chipset: ICH7+
+ *
+ * Some Intel motherboards have a broken BIOS implementation: i.e.
+ * the SMI handler clear's the TIMEOUT bit in the TC01_STS register
+ * and does not reload the time. Thus the TCO watchdog does not reboot
+ * the system.
+ *
+ * These are the conclusions of Andriy Gapon <avg@icyb.net.ua> after
+ * debugging: the SMI handler is quite simple - it tests value in
+ * TCO1_CNT against 0x800, i.e. checks TCO_TMR_HLT. If the bit is set
+ * the handler goes into an infinite loop, apparently to allow the
+ * second timeout and reboot. Otherwise it simply clears TIMEOUT bit
+ * in TCO1_STS and that's it.
+ * So the logic seems to be reversed, because it is hard to see how
+ * TIMEOUT can get set to 1 and SMI generated when TCO_TMR_HLT is set
+ * (other than a transitional effect).
+ *
+ * The only fix found to get the motherboard(s) to reboot is to put
+ * the glb_smi_en bit to 0. This is a dirty hack that bypasses the
+ * broken code by disabling Global SMI.
+ *
+ * WARNING: globally disabling SMI could possibly lead to dramatic
+ * problems, especially on laptops! I.e. various ACPI things where
+ * SMI is used for communication between OS and firmware.
+ *
+ * Don't use this fix if you don't need to!!!
+ */
+
+static void broken_bios_start(struct resource *smires)
+{
+ unsigned long val32;
+
+ val32 = inl(smires->start);
+ /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI#
+ Bit 0: GBL_SMI_EN -> 0 = No SMI# will be generated by ICH. */
+ val32 &= 0xffffdffe;
+ outl(val32, smires->start);
+}
+
+static void broken_bios_stop(struct resource *smires)
+{
+ unsigned long val32;
+
+ val32 = inl(smires->start);
+ /* Bit 13: TCO_EN -> 1 = Enables TCO logic generating an SMI#
+ Bit 0: GBL_SMI_EN -> 1 = Turn global SMI on again. */
+ val32 |= 0x00002001;
+ outl(val32, smires->start);
+}
+
+/*
+ * Generic Support Functions
+ */
+
+void iTCO_vendor_pre_start(struct resource *smires,
+ unsigned int heartbeat)
+{
+ switch (iTCO_vendorsupport) {
+ case SUPERMICRO_OLD_BOARD:
+ supermicro_old_pre_start(smires);
+ break;
+ case BROKEN_BIOS:
+ broken_bios_start(smires);
+ break;
+ }
+}
+EXPORT_SYMBOL(iTCO_vendor_pre_start);
+
+void iTCO_vendor_pre_stop(struct resource *smires)
+{
+ switch (iTCO_vendorsupport) {
+ case SUPERMICRO_OLD_BOARD:
+ supermicro_old_pre_stop(smires);
+ break;
+ case BROKEN_BIOS:
+ broken_bios_stop(smires);
+ break;
+ }
+}
+EXPORT_SYMBOL(iTCO_vendor_pre_stop);
+
+int iTCO_vendor_check_noreboot_on(void)
+{
+ switch (iTCO_vendorsupport) {
+ case SUPERMICRO_OLD_BOARD:
+ return 0;
+ default:
+ return 1;
+ }
+}
+EXPORT_SYMBOL(iTCO_vendor_check_noreboot_on);
+
+static int __init iTCO_vendor_init_module(void)
+{
+ if (iTCO_vendorsupport == SUPERMICRO_NEW_BOARD) {
+ pr_warn("Option vendorsupport=%d is no longer supported, "
+ "please use the w83627hf_wdt driver instead\n",
+ SUPERMICRO_NEW_BOARD);
+ return -EINVAL;
+ }
+ pr_info("vendor-support=%d\n", iTCO_vendorsupport);
+ return 0;
+}
+
+static void __exit iTCO_vendor_exit_module(void)
+{
+ pr_info("Module Unloaded\n");
+}
+
+module_init(iTCO_vendor_init_module);
+module_exit(iTCO_vendor_exit_module);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>, "
+ "R. Seretny <lkpatches@paypc.com>");
+MODULE_DESCRIPTION("Intel TCO Vendor Specific WatchDog Timer Driver Support");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c
new file mode 100644
index 000000000..e937b4dd2
--- /dev/null
+++ b/drivers/watchdog/iTCO_wdt.c
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * intel TCO Watchdog Driver
+ *
+ * (c) Copyright 2006-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ * The TCO watchdog is implemented in the following I/O controller hubs:
+ * (See the intel documentation on http://developer.intel.com.)
+ * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO)
+ * document number 290687-002, 298242-027: 82801BA (ICH2)
+ * document number 290733-003, 290739-013: 82801CA (ICH3-S)
+ * document number 290716-001, 290718-007: 82801CAM (ICH3-M)
+ * document number 290744-001, 290745-025: 82801DB (ICH4)
+ * document number 252337-001, 252663-008: 82801DBM (ICH4-M)
+ * document number 273599-001, 273645-002: 82801E (C-ICH)
+ * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R)
+ * document number 300641-004, 300884-013: 6300ESB
+ * document number 301473-002, 301474-026: 82801F (ICH6)
+ * document number 313082-001, 313075-006: 631xESB, 632xESB
+ * document number 307013-003, 307014-024: 82801G (ICH7)
+ * document number 322896-001, 322897-001: NM10
+ * document number 313056-003, 313057-017: 82801H (ICH8)
+ * document number 316972-004, 316973-012: 82801I (ICH9)
+ * document number 319973-002, 319974-002: 82801J (ICH10)
+ * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH)
+ * document number 320066-003, 320257-008: EP80597 (IICH)
+ * document number 324645-001, 324646-001: Cougar Point (CPT)
+ * document number TBD : Patsburg (PBG)
+ * document number TBD : DH89xxCC
+ * document number TBD : Panther Point
+ * document number TBD : Lynx Point
+ * document number TBD : Lynx Point-LP
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+/* Module and version information */
+#define DRV_NAME "iTCO_wdt"
+#define DRV_VERSION "1.11"
+
+/* Includes */
+#include <linux/acpi.h> /* For ACPI support */
+#include <linux/bits.h> /* For BIT() */
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/platform_device.h> /* For platform_driver framework */
+#include <linux/pci.h> /* For pci functions */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For inb/outb/... */
+#include <linux/platform_data/itco_wdt.h>
+#include <linux/mfd/intel_pmc_bxt.h>
+
+#include "iTCO_vendor.h"
+
+/* Address definitions for the TCO */
+/* TCO base address */
+#define TCOBASE(p) ((p)->tco_res->start)
+/* SMI Control and Enable Register */
+#define SMI_EN(p) ((p)->smi_res->start)
+
+#define TCO_RLD(p) (TCOBASE(p) + 0x00) /* TCO Timer Reload/Curr. Value */
+#define TCOv1_TMR(p) (TCOBASE(p) + 0x01) /* TCOv1 Timer Initial Value*/
+#define TCO_DAT_IN(p) (TCOBASE(p) + 0x02) /* TCO Data In Register */
+#define TCO_DAT_OUT(p) (TCOBASE(p) + 0x03) /* TCO Data Out Register */
+#define TCO1_STS(p) (TCOBASE(p) + 0x04) /* TCO1 Status Register */
+#define TCO2_STS(p) (TCOBASE(p) + 0x06) /* TCO2 Status Register */
+#define TCO1_CNT(p) (TCOBASE(p) + 0x08) /* TCO1 Control Register */
+#define TCO2_CNT(p) (TCOBASE(p) + 0x0a) /* TCO2 Control Register */
+#define TCOv2_TMR(p) (TCOBASE(p) + 0x12) /* TCOv2 Timer Initial Value*/
+
+/* internal variables */
+struct iTCO_wdt_private {
+ struct watchdog_device wddev;
+
+ /* TCO version/generation */
+ unsigned int iTCO_version;
+ struct resource *tco_res;
+ struct resource *smi_res;
+ /*
+ * NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2),
+ * or memory-mapped PMC register bit 4 (TCO version 3).
+ */
+ unsigned long __iomem *gcs_pmc;
+ /* the lock for io operations */
+ spinlock_t io_lock;
+ /* the PCI-device */
+ struct pci_dev *pci_dev;
+ /* whether or not the watchdog has been suspended */
+ bool suspended;
+ /* no reboot API private data */
+ void *no_reboot_priv;
+ /* no reboot update function pointer */
+ int (*update_no_reboot_bit)(void *p, bool set);
+};
+
+/* module parameters */
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default heartbeat */
+static int heartbeat = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. "
+ "5..76 (TCO v1) or 3..614 (TCO v2), 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) ")");
+
+static int turn_SMI_watchdog_clear_off = 1;
+module_param(turn_SMI_watchdog_clear_off, int, 0);
+MODULE_PARM_DESC(turn_SMI_watchdog_clear_off,
+ "Turn off SMI clearing watchdog (depends on TCO-version)(default=1)");
+
+/*
+ * Some TCO specific functions
+ */
+
+/*
+ * The iTCO v1 and v2's internal timer is stored as ticks which decrement
+ * every 0.6 seconds. v3's internal timer is stored as seconds (some
+ * datasheets incorrectly state 0.6 seconds).
+ */
+static inline unsigned int seconds_to_ticks(struct iTCO_wdt_private *p,
+ int secs)
+{
+ return p->iTCO_version == 3 ? secs : (secs * 10) / 6;
+}
+
+static inline unsigned int ticks_to_seconds(struct iTCO_wdt_private *p,
+ int ticks)
+{
+ return p->iTCO_version == 3 ? ticks : (ticks * 6) / 10;
+}
+
+static inline u32 no_reboot_bit(struct iTCO_wdt_private *p)
+{
+ u32 enable_bit;
+
+ switch (p->iTCO_version) {
+ case 5:
+ case 3:
+ enable_bit = 0x00000010;
+ break;
+ case 2:
+ enable_bit = 0x00000020;
+ break;
+ case 4:
+ case 1:
+ default:
+ enable_bit = 0x00000002;
+ break;
+ }
+
+ return enable_bit;
+}
+
+static int update_no_reboot_bit_def(void *priv, bool set)
+{
+ return 0;
+}
+
+static int update_no_reboot_bit_pci(void *priv, bool set)
+{
+ struct iTCO_wdt_private *p = priv;
+ u32 val32 = 0, newval32 = 0;
+
+ pci_read_config_dword(p->pci_dev, 0xd4, &val32);
+ if (set)
+ val32 |= no_reboot_bit(p);
+ else
+ val32 &= ~no_reboot_bit(p);
+ pci_write_config_dword(p->pci_dev, 0xd4, val32);
+ pci_read_config_dword(p->pci_dev, 0xd4, &newval32);
+
+ /* make sure the update is successful */
+ if (val32 != newval32)
+ return -EIO;
+
+ return 0;
+}
+
+static int update_no_reboot_bit_mem(void *priv, bool set)
+{
+ struct iTCO_wdt_private *p = priv;
+ u32 val32 = 0, newval32 = 0;
+
+ val32 = readl(p->gcs_pmc);
+ if (set)
+ val32 |= no_reboot_bit(p);
+ else
+ val32 &= ~no_reboot_bit(p);
+ writel(val32, p->gcs_pmc);
+ newval32 = readl(p->gcs_pmc);
+
+ /* make sure the update is successful */
+ if (val32 != newval32)
+ return -EIO;
+
+ return 0;
+}
+
+static int update_no_reboot_bit_cnt(void *priv, bool set)
+{
+ struct iTCO_wdt_private *p = priv;
+ u16 val, newval;
+
+ val = inw(TCO1_CNT(p));
+ if (set)
+ val |= BIT(0);
+ else
+ val &= ~BIT(0);
+ outw(val, TCO1_CNT(p));
+ newval = inw(TCO1_CNT(p));
+
+ /* make sure the update is successful */
+ return val != newval ? -EIO : 0;
+}
+
+static int update_no_reboot_bit_pmc(void *priv, bool set)
+{
+ struct intel_pmc_dev *pmc = priv;
+ u32 bits = PMC_CFG_NO_REBOOT_EN;
+ u32 value = set ? bits : 0;
+
+ return intel_pmc_gcr_update(pmc, PMC_GCR_PMC_CFG_REG, bits, value);
+}
+
+static void iTCO_wdt_no_reboot_bit_setup(struct iTCO_wdt_private *p,
+ struct platform_device *pdev,
+ struct itco_wdt_platform_data *pdata)
+{
+ if (pdata->no_reboot_use_pmc) {
+ struct intel_pmc_dev *pmc = dev_get_drvdata(pdev->dev.parent);
+
+ p->update_no_reboot_bit = update_no_reboot_bit_pmc;
+ p->no_reboot_priv = pmc;
+ return;
+ }
+
+ if (p->iTCO_version >= 6)
+ p->update_no_reboot_bit = update_no_reboot_bit_cnt;
+ else if (p->iTCO_version >= 2)
+ p->update_no_reboot_bit = update_no_reboot_bit_mem;
+ else if (p->iTCO_version == 1)
+ p->update_no_reboot_bit = update_no_reboot_bit_pci;
+ else
+ p->update_no_reboot_bit = update_no_reboot_bit_def;
+
+ p->no_reboot_priv = p;
+}
+
+static int iTCO_wdt_start(struct watchdog_device *wd_dev)
+{
+ struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
+ unsigned int val;
+
+ spin_lock(&p->io_lock);
+
+ iTCO_vendor_pre_start(p->smi_res, wd_dev->timeout);
+
+ /* disable chipset's NO_REBOOT bit */
+ if (p->update_no_reboot_bit(p->no_reboot_priv, false)) {
+ spin_unlock(&p->io_lock);
+ dev_err(wd_dev->parent, "failed to reset NO_REBOOT flag, reboot disabled by hardware/BIOS\n");
+ return -EIO;
+ }
+
+ /* Force the timer to its reload value by writing to the TCO_RLD
+ register */
+ if (p->iTCO_version >= 2)
+ outw(0x01, TCO_RLD(p));
+ else if (p->iTCO_version == 1)
+ outb(0x01, TCO_RLD(p));
+
+ /* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */
+ val = inw(TCO1_CNT(p));
+ val &= 0xf7ff;
+ outw(val, TCO1_CNT(p));
+ val = inw(TCO1_CNT(p));
+ spin_unlock(&p->io_lock);
+
+ if (val & 0x0800)
+ return -1;
+ return 0;
+}
+
+static int iTCO_wdt_stop(struct watchdog_device *wd_dev)
+{
+ struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
+ unsigned int val;
+
+ spin_lock(&p->io_lock);
+
+ iTCO_vendor_pre_stop(p->smi_res);
+
+ /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
+ val = inw(TCO1_CNT(p));
+ val |= 0x0800;
+ outw(val, TCO1_CNT(p));
+ val = inw(TCO1_CNT(p));
+
+ /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+ p->update_no_reboot_bit(p->no_reboot_priv, true);
+
+ spin_unlock(&p->io_lock);
+
+ if ((val & 0x0800) == 0)
+ return -1;
+ return 0;
+}
+
+static int iTCO_wdt_ping(struct watchdog_device *wd_dev)
+{
+ struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
+
+ spin_lock(&p->io_lock);
+
+ /* Reload the timer by writing to the TCO Timer Counter register */
+ if (p->iTCO_version >= 2) {
+ outw(0x01, TCO_RLD(p));
+ } else if (p->iTCO_version == 1) {
+ /* Reset the timeout status bit so that the timer
+ * needs to count down twice again before rebooting */
+ outw(0x0008, TCO1_STS(p)); /* write 1 to clear bit */
+
+ outb(0x01, TCO_RLD(p));
+ }
+
+ spin_unlock(&p->io_lock);
+ return 0;
+}
+
+static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t)
+{
+ struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
+ unsigned int val16;
+ unsigned char val8;
+ unsigned int tmrval;
+
+ tmrval = seconds_to_ticks(p, t);
+
+ /* For TCO v1 the timer counts down twice before rebooting */
+ if (p->iTCO_version == 1)
+ tmrval /= 2;
+
+ /* from the specs: */
+ /* "Values of 0h-3h are ignored and should not be attempted" */
+ if (tmrval < 0x04)
+ return -EINVAL;
+ if ((p->iTCO_version >= 2 && tmrval > 0x3ff) ||
+ (p->iTCO_version == 1 && tmrval > 0x03f))
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ if (p->iTCO_version >= 2) {
+ spin_lock(&p->io_lock);
+ val16 = inw(TCOv2_TMR(p));
+ val16 &= 0xfc00;
+ val16 |= tmrval;
+ outw(val16, TCOv2_TMR(p));
+ val16 = inw(TCOv2_TMR(p));
+ spin_unlock(&p->io_lock);
+
+ if ((val16 & 0x3ff) != tmrval)
+ return -EINVAL;
+ } else if (p->iTCO_version == 1) {
+ spin_lock(&p->io_lock);
+ val8 = inb(TCOv1_TMR(p));
+ val8 &= 0xc0;
+ val8 |= (tmrval & 0xff);
+ outb(val8, TCOv1_TMR(p));
+ val8 = inb(TCOv1_TMR(p));
+ spin_unlock(&p->io_lock);
+
+ if ((val8 & 0x3f) != tmrval)
+ return -EINVAL;
+ }
+
+ wd_dev->timeout = t;
+ return 0;
+}
+
+static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev)
+{
+ struct iTCO_wdt_private *p = watchdog_get_drvdata(wd_dev);
+ unsigned int val16;
+ unsigned char val8;
+ unsigned int time_left = 0;
+
+ /* read the TCO Timer */
+ if (p->iTCO_version >= 2) {
+ spin_lock(&p->io_lock);
+ val16 = inw(TCO_RLD(p));
+ val16 &= 0x3ff;
+ spin_unlock(&p->io_lock);
+
+ time_left = ticks_to_seconds(p, val16);
+ } else if (p->iTCO_version == 1) {
+ spin_lock(&p->io_lock);
+ val8 = inb(TCO_RLD(p));
+ val8 &= 0x3f;
+ if (!(inw(TCO1_STS(p)) & 0x0008))
+ val8 += (inb(TCOv1_TMR(p)) & 0x3f);
+ spin_unlock(&p->io_lock);
+
+ time_left = ticks_to_seconds(p, val8);
+ }
+ return time_left;
+}
+
+/* Returns true if the watchdog was running */
+static bool iTCO_wdt_set_running(struct iTCO_wdt_private *p)
+{
+ u16 val;
+
+ /* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled */
+ val = inw(TCO1_CNT(p));
+ if (!(val & BIT(11))) {
+ set_bit(WDOG_HW_RUNNING, &p->wddev.status);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = DRV_NAME,
+};
+
+static const struct watchdog_ops iTCO_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = iTCO_wdt_start,
+ .stop = iTCO_wdt_stop,
+ .ping = iTCO_wdt_ping,
+ .set_timeout = iTCO_wdt_set_timeout,
+ .get_timeleft = iTCO_wdt_get_timeleft,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int iTCO_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct itco_wdt_platform_data *pdata = dev_get_platdata(dev);
+ struct iTCO_wdt_private *p;
+ unsigned long val32;
+ int ret;
+
+ if (!pdata)
+ return -ENODEV;
+
+ p = devm_kzalloc(dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ spin_lock_init(&p->io_lock);
+
+ p->tco_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_TCO);
+ if (!p->tco_res)
+ return -ENODEV;
+
+ p->iTCO_version = pdata->version;
+ p->pci_dev = to_pci_dev(dev->parent);
+
+ p->smi_res = platform_get_resource(pdev, IORESOURCE_IO, ICH_RES_IO_SMI);
+ if (p->smi_res) {
+ /* The TCO logic uses the TCO_EN bit in the SMI_EN register */
+ if (!devm_request_region(dev, p->smi_res->start,
+ resource_size(p->smi_res),
+ pdev->name)) {
+ dev_err(dev, "I/O address 0x%04llx already in use, device disabled\n",
+ (u64)SMI_EN(p));
+ return -EBUSY;
+ }
+ } else if (iTCO_vendorsupport ||
+ turn_SMI_watchdog_clear_off >= p->iTCO_version) {
+ dev_err(dev, "SMI I/O resource is missing\n");
+ return -ENODEV;
+ }
+
+ iTCO_wdt_no_reboot_bit_setup(p, pdev, pdata);
+
+ /*
+ * Get the Memory-Mapped GCS or PMC register, we need it for the
+ * NO_REBOOT flag (TCO v2 and v3).
+ */
+ if (p->iTCO_version >= 2 && p->iTCO_version < 6 &&
+ !pdata->no_reboot_use_pmc) {
+ p->gcs_pmc = devm_platform_ioremap_resource(pdev, ICH_RES_MEM_GCS_PMC);
+ if (IS_ERR(p->gcs_pmc))
+ return PTR_ERR(p->gcs_pmc);
+ }
+
+ /* Check chipset's NO_REBOOT bit */
+ if (p->update_no_reboot_bit(p->no_reboot_priv, false) &&
+ iTCO_vendor_check_noreboot_on()) {
+ dev_info(dev, "unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n");
+ return -ENODEV; /* Cannot reset NO_REBOOT bit */
+ }
+
+ if (turn_SMI_watchdog_clear_off >= p->iTCO_version) {
+ /*
+ * Bit 13: TCO_EN -> 0
+ * Disables TCO logic generating an SMI#
+ */
+ val32 = inl(SMI_EN(p));
+ val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
+ outl(val32, SMI_EN(p));
+ }
+
+ if (!devm_request_region(dev, p->tco_res->start,
+ resource_size(p->tco_res),
+ pdev->name)) {
+ dev_err(dev, "I/O address 0x%04llx already in use, device disabled\n",
+ (u64)TCOBASE(p));
+ return -EBUSY;
+ }
+
+ dev_info(dev, "Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n",
+ pdata->name, pdata->version, (u64)TCOBASE(p));
+
+ /* Clear out the (probably old) status */
+ switch (p->iTCO_version) {
+ case 6:
+ case 5:
+ case 4:
+ outw(0x0008, TCO1_STS(p)); /* Clear the Time Out Status bit */
+ outw(0x0002, TCO2_STS(p)); /* Clear SECOND_TO_STS bit */
+ break;
+ case 3:
+ outl(0x20008, TCO1_STS(p));
+ break;
+ case 2:
+ case 1:
+ default:
+ outw(0x0008, TCO1_STS(p)); /* Clear the Time Out Status bit */
+ outw(0x0002, TCO2_STS(p)); /* Clear SECOND_TO_STS bit */
+ outw(0x0004, TCO2_STS(p)); /* Clear BOOT_STS bit */
+ break;
+ }
+
+ p->wddev.info = &ident,
+ p->wddev.ops = &iTCO_wdt_ops,
+ p->wddev.bootstatus = 0;
+ p->wddev.timeout = WATCHDOG_TIMEOUT;
+ watchdog_set_nowayout(&p->wddev, nowayout);
+ p->wddev.parent = dev;
+
+ watchdog_set_drvdata(&p->wddev, p);
+ platform_set_drvdata(pdev, p);
+
+ if (!iTCO_wdt_set_running(p)) {
+ /*
+ * If the watchdog was not running set NO_REBOOT now to
+ * prevent later reboots.
+ */
+ p->update_no_reboot_bit(p->no_reboot_priv, true);
+ }
+
+ /* Check that the heartbeat value is within it's range;
+ if not reset to the default */
+ if (iTCO_wdt_set_timeout(&p->wddev, heartbeat)) {
+ iTCO_wdt_set_timeout(&p->wddev, WATCHDOG_TIMEOUT);
+ dev_info(dev, "timeout value out of range, using %d\n",
+ WATCHDOG_TIMEOUT);
+ }
+
+ watchdog_stop_on_reboot(&p->wddev);
+ watchdog_stop_on_unregister(&p->wddev);
+ ret = devm_watchdog_register_device(dev, &p->wddev);
+ if (ret != 0) {
+ dev_err(dev, "cannot register watchdog device (err=%d)\n", ret);
+ return ret;
+ }
+
+ dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+}
+
+/*
+ * Suspend-to-idle requires this, because it stops the ticks and timekeeping, so
+ * the watchdog cannot be pinged while in that state. In ACPI sleep states the
+ * watchdog is stopped by the platform firmware.
+ */
+
+#ifdef CONFIG_ACPI
+static inline bool __maybe_unused need_suspend(void)
+{
+ return acpi_target_system_state() == ACPI_STATE_S0;
+}
+#else
+static inline bool __maybe_unused need_suspend(void) { return true; }
+#endif
+
+static int __maybe_unused iTCO_wdt_suspend_noirq(struct device *dev)
+{
+ struct iTCO_wdt_private *p = dev_get_drvdata(dev);
+ int ret = 0;
+
+ p->suspended = false;
+ if (watchdog_active(&p->wddev) && need_suspend()) {
+ ret = iTCO_wdt_stop(&p->wddev);
+ if (!ret)
+ p->suspended = true;
+ }
+ return ret;
+}
+
+static int __maybe_unused iTCO_wdt_resume_noirq(struct device *dev)
+{
+ struct iTCO_wdt_private *p = dev_get_drvdata(dev);
+
+ if (p->suspended)
+ iTCO_wdt_start(&p->wddev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops iTCO_wdt_pm = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(iTCO_wdt_suspend_noirq,
+ iTCO_wdt_resume_noirq)
+};
+
+static struct platform_driver iTCO_wdt_driver = {
+ .probe = iTCO_wdt_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = &iTCO_wdt_pm,
+ },
+};
+
+module_platform_driver(iTCO_wdt_driver);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver");
+MODULE_VERSION(DRV_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c
new file mode 100644
index 000000000..a0ddedc36
--- /dev/null
+++ b/drivers/watchdog/ib700wdt.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IB700 Single Board Computer WDT driver
+ *
+ * (c) Copyright 2001 Charles Howes <chowes@vsol.net>
+ *
+ * Based on advantechwdt.c which is based on acquirewdt.c which
+ * is based on wdt.c.
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * Based on acquirewdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Added timeout module option to override default
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+static struct platform_device *ibwdt_platform_device;
+static unsigned long ibwdt_is_open;
+static DEFINE_SPINLOCK(ibwdt_lock);
+static char expect_close;
+
+/* Module information */
+#define DRV_NAME "ib700wdt"
+
+/*
+ *
+ * Watchdog Timer Configuration
+ *
+ * The function of the watchdog timer is to reset the system
+ * automatically and is defined at I/O port 0443H. To enable the
+ * watchdog timer and allow the system to reset, write I/O port 0443H.
+ * To disable the timer, write I/O port 0441H for the system to stop the
+ * watchdog function. The timer has a tolerance of 20% for its
+ * intervals.
+ *
+ * The following describes how the timer should be programmed.
+ *
+ * Enabling Watchdog:
+ * MOV AX,000FH (Choose the values from 0 to F)
+ * MOV DX,0443H
+ * OUT DX,AX
+ *
+ * Disabling Watchdog:
+ * MOV AX,000FH (Any value is fine.)
+ * MOV DX,0441H
+ * OUT DX,AX
+ *
+ * Watchdog timer control table:
+ * Level Value Time/sec | Level Value Time/sec
+ * 1 F 0 | 9 7 16
+ * 2 E 2 | 10 6 18
+ * 3 D 4 | 11 5 20
+ * 4 C 6 | 12 4 22
+ * 5 B 8 | 13 3 24
+ * 6 A 10 | 14 2 26
+ * 7 9 12 | 15 1 28
+ * 8 8 14 | 16 0 30
+ *
+ */
+
+#define WDT_STOP 0x441
+#define WDT_START 0x443
+
+/* Default timeout */
+#define WATCHDOG_TIMEOUT 30 /* 30 seconds +/- 20% */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 0<= timeout <=30, 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) ")");
+
+
+/*
+ * Watchdog Operations
+ */
+
+static void ibwdt_ping(void)
+{
+ int wd_margin = 15 - ((timeout + 1) / 2);
+
+ spin_lock(&ibwdt_lock);
+
+ /* Write a watchdog value */
+ outb_p(wd_margin, WDT_START);
+
+ spin_unlock(&ibwdt_lock);
+}
+
+static void ibwdt_disable(void)
+{
+ spin_lock(&ibwdt_lock);
+ outb_p(0, WDT_STOP);
+ spin_unlock(&ibwdt_lock);
+}
+
+static int ibwdt_set_heartbeat(int t)
+{
+ if (t < 0 || t > 30)
+ return -EINVAL;
+
+ timeout = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t ibwdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ ibwdt_ping();
+ }
+ return count;
+}
+
+static long ibwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int new_margin;
+ 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 = "IB700 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ ibwdt_disable();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ ibwdt_ping();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ ibwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+ if (ibwdt_set_heartbeat(new_margin))
+ return -EINVAL;
+ ibwdt_ping();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int ibwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &ibwdt_is_open))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate */
+ ibwdt_ping();
+ return stream_open(inode, file);
+}
+
+static int ibwdt_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ ibwdt_disable();
+ } else {
+ pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
+ ibwdt_ping();
+ }
+ clear_bit(0, &ibwdt_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations ibwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ibwdt_write,
+ .unlocked_ioctl = ibwdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = ibwdt_open,
+ .release = ibwdt_close,
+};
+
+static struct miscdevice ibwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ibwdt_fops,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int __init ibwdt_probe(struct platform_device *dev)
+{
+ int res;
+
+#if WDT_START != WDT_STOP
+ if (!request_region(WDT_STOP, 1, "IB700 WDT")) {
+ pr_err("STOP method I/O %X is not available\n", WDT_STOP);
+ res = -EIO;
+ goto out_nostopreg;
+ }
+#endif
+
+ if (!request_region(WDT_START, 1, "IB700 WDT")) {
+ pr_err("START method I/O %X is not available\n", WDT_START);
+ res = -EIO;
+ goto out_nostartreg;
+ }
+
+ /* Check that the heartbeat value is within it's range ;
+ * if not reset to the default */
+ if (ibwdt_set_heartbeat(timeout)) {
+ ibwdt_set_heartbeat(WATCHDOG_TIMEOUT);
+ pr_info("timeout value must be 0<=x<=30, using %d\n", timeout);
+ }
+
+ res = misc_register(&ibwdt_miscdev);
+ if (res) {
+ pr_err("failed to register misc device\n");
+ goto out_nomisc;
+ }
+ return 0;
+
+out_nomisc:
+ release_region(WDT_START, 1);
+out_nostartreg:
+#if WDT_START != WDT_STOP
+ release_region(WDT_STOP, 1);
+#endif
+out_nostopreg:
+ return res;
+}
+
+static int ibwdt_remove(struct platform_device *dev)
+{
+ misc_deregister(&ibwdt_miscdev);
+ release_region(WDT_START, 1);
+#if WDT_START != WDT_STOP
+ release_region(WDT_STOP, 1);
+#endif
+ return 0;
+}
+
+static void ibwdt_shutdown(struct platform_device *dev)
+{
+ /* Turn the WDT off if we have a soft shutdown */
+ ibwdt_disable();
+}
+
+static struct platform_driver ibwdt_driver = {
+ .remove = ibwdt_remove,
+ .shutdown = ibwdt_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init ibwdt_init(void)
+{
+ int err;
+
+ pr_info("WDT driver for IB700 single board computer initialising\n");
+
+ ibwdt_platform_device = platform_device_register_simple(DRV_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(ibwdt_platform_device))
+ return PTR_ERR(ibwdt_platform_device);
+
+ err = platform_driver_probe(&ibwdt_driver, ibwdt_probe);
+ if (err)
+ goto unreg_platform_device;
+
+ return 0;
+
+unreg_platform_device:
+ platform_device_unregister(ibwdt_platform_device);
+ return err;
+}
+
+static void __exit ibwdt_exit(void)
+{
+ platform_device_unregister(ibwdt_platform_device);
+ platform_driver_unregister(&ibwdt_driver);
+ pr_info("Watchdog Module Unloaded\n");
+}
+
+module_init(ibwdt_init);
+module_exit(ibwdt_exit);
+
+MODULE_AUTHOR("Charles Howes <chowes@vsol.net>");
+MODULE_DESCRIPTION("IB700 SBC watchdog driver");
+MODULE_LICENSE("GPL");
+
+/* end of ib700wdt.c */
diff --git a/drivers/watchdog/ibmasr.c b/drivers/watchdog/ibmasr.c
new file mode 100644
index 000000000..4a22fe152
--- /dev/null
+++ b/drivers/watchdog/ibmasr.c
@@ -0,0 +1,422 @@
+/*
+ * IBM Automatic Server Restart driver.
+ *
+ * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru>
+ *
+ * Based on driver written by Pete Reynolds.
+ * Copyright (c) IBM Corporation, 1998-2004.
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU Public License, incorporated herein by reference.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+enum {
+ ASMTYPE_UNKNOWN,
+ ASMTYPE_TOPAZ,
+ ASMTYPE_JASPER,
+ ASMTYPE_PEARL,
+ ASMTYPE_JUNIPER,
+ ASMTYPE_SPRUCE,
+};
+
+#define TOPAZ_ASR_REG_OFFSET 4
+#define TOPAZ_ASR_TOGGLE 0x40
+#define TOPAZ_ASR_DISABLE 0x80
+
+/* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */
+#define PEARL_BASE 0xe04
+#define PEARL_WRITE 0xe06
+#define PEARL_READ 0xe07
+
+#define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */
+#define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */
+
+/* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */
+#define JASPER_ASR_REG_OFFSET 0x38
+
+#define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */
+#define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */
+
+#define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */
+#define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */
+#define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */
+
+#define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */
+#define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */
+#define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */
+
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+static unsigned long asr_is_open;
+static char asr_expect_close;
+
+static unsigned int asr_type, asr_base, asr_length;
+static unsigned int asr_read_addr, asr_write_addr;
+static unsigned char asr_toggle_mask, asr_disable_mask;
+static DEFINE_SPINLOCK(asr_lock);
+
+static void __asr_toggle(void)
+{
+ unsigned char reg;
+
+ reg = inb(asr_read_addr);
+
+ outb(reg & ~asr_toggle_mask, asr_write_addr);
+ reg = inb(asr_read_addr);
+
+ outb(reg | asr_toggle_mask, asr_write_addr);
+ reg = inb(asr_read_addr);
+
+ outb(reg & ~asr_toggle_mask, asr_write_addr);
+ reg = inb(asr_read_addr);
+}
+
+static void asr_toggle(void)
+{
+ spin_lock(&asr_lock);
+ __asr_toggle();
+ spin_unlock(&asr_lock);
+}
+
+static void asr_enable(void)
+{
+ unsigned char reg;
+
+ spin_lock(&asr_lock);
+ if (asr_type == ASMTYPE_TOPAZ) {
+ /* asr_write_addr == asr_read_addr */
+ reg = inb(asr_read_addr);
+ outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE),
+ asr_read_addr);
+ } else {
+ /*
+ * First make sure the hardware timer is reset by toggling
+ * ASR hardware timer line.
+ */
+ __asr_toggle();
+
+ reg = inb(asr_read_addr);
+ outb(reg & ~asr_disable_mask, asr_write_addr);
+ }
+ reg = inb(asr_read_addr);
+ spin_unlock(&asr_lock);
+}
+
+static void asr_disable(void)
+{
+ unsigned char reg;
+
+ spin_lock(&asr_lock);
+ reg = inb(asr_read_addr);
+
+ if (asr_type == ASMTYPE_TOPAZ)
+ /* asr_write_addr == asr_read_addr */
+ outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE,
+ asr_read_addr);
+ else {
+ outb(reg | asr_toggle_mask, asr_write_addr);
+ reg = inb(asr_read_addr);
+
+ outb(reg | asr_disable_mask, asr_write_addr);
+ }
+ reg = inb(asr_read_addr);
+ spin_unlock(&asr_lock);
+}
+
+static int __init asr_get_base_address(void)
+{
+ unsigned char low, high;
+ const char *type = "";
+
+ asr_length = 1;
+
+ switch (asr_type) {
+ case ASMTYPE_TOPAZ:
+ /* SELECT SuperIO CHIP FOR QUERYING
+ (WRITE 0x07 TO BOTH 0x2E and 0x2F) */
+ outb(0x07, 0x2e);
+ outb(0x07, 0x2f);
+
+ /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */
+ outb(0x60, 0x2e);
+ high = inb(0x2f);
+
+ /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */
+ outb(0x61, 0x2e);
+ low = inb(0x2f);
+
+ asr_base = (high << 16) | low;
+ asr_read_addr = asr_write_addr =
+ asr_base + TOPAZ_ASR_REG_OFFSET;
+ asr_length = 5;
+
+ break;
+
+ case ASMTYPE_JASPER:
+ type = "Jaspers ";
+#if 0
+ u32 r;
+ /* Suggested fix */
+ pdev = pci_get_bus_and_slot(0, DEVFN(0x1f, 0));
+ if (pdev == NULL)
+ return -ENODEV;
+ pci_read_config_dword(pdev, 0x58, &r);
+ asr_base = r & 0xFFFE;
+ pci_dev_put(pdev);
+#else
+ /* FIXME: need to use pci_config_lock here,
+ but it's not exported */
+
+/* spin_lock_irqsave(&pci_config_lock, flags);*/
+
+ /* Select the SuperIO chip in the PCI I/O port register */
+ outl(0x8000f858, 0xcf8);
+
+ /* BUS 0, Slot 1F, fnc 0, offset 58 */
+
+ /*
+ * Read the base address for the SuperIO chip.
+ * Only the lower 16 bits are valid, but the address is word
+ * aligned so the last bit must be masked off.
+ */
+ asr_base = inl(0xcfc) & 0xfffe;
+
+/* spin_unlock_irqrestore(&pci_config_lock, flags);*/
+#endif
+ asr_read_addr = asr_write_addr =
+ asr_base + JASPER_ASR_REG_OFFSET;
+ asr_toggle_mask = JASPER_ASR_TOGGLE_MASK;
+ asr_disable_mask = JASPER_ASR_DISABLE_MASK;
+ asr_length = JASPER_ASR_REG_OFFSET + 1;
+
+ break;
+
+ case ASMTYPE_PEARL:
+ type = "Pearls ";
+ asr_base = PEARL_BASE;
+ asr_read_addr = PEARL_READ;
+ asr_write_addr = PEARL_WRITE;
+ asr_toggle_mask = PEARL_ASR_TOGGLE_MASK;
+ asr_disable_mask = PEARL_ASR_DISABLE_MASK;
+ asr_length = 4;
+ break;
+
+ case ASMTYPE_JUNIPER:
+ type = "Junipers ";
+ asr_base = JUNIPER_BASE_ADDRESS;
+ asr_read_addr = asr_write_addr = asr_base;
+ asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK;
+ asr_disable_mask = JUNIPER_ASR_DISABLE_MASK;
+ break;
+
+ case ASMTYPE_SPRUCE:
+ type = "Spruce's ";
+ asr_base = SPRUCE_BASE_ADDRESS;
+ asr_read_addr = asr_write_addr = asr_base;
+ asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK;
+ asr_disable_mask = SPRUCE_ASR_DISABLE_MASK;
+ break;
+ }
+
+ if (!request_region(asr_base, asr_length, "ibmasr")) {
+ pr_err("address %#x already in use\n", asr_base);
+ return -EBUSY;
+ }
+
+ pr_info("found %sASR @ addr %#x\n", type, asr_base);
+
+ return 0;
+}
+
+
+static ssize_t asr_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ asr_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ asr_expect_close = 42;
+ }
+ }
+ asr_toggle();
+ }
+ return count;
+}
+
+static long asr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .identity = "IBM ASR",
+ };
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int heartbeat;
+
+ 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) {
+ asr_disable();
+ retval = 0;
+ }
+ if (new_options & WDIOS_ENABLECARD) {
+ asr_enable();
+ asr_toggle();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ asr_toggle();
+ return 0;
+ /*
+ * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT
+ * and WDIOC_GETTIMEOUT always returns 256.
+ */
+ case WDIOC_GETTIMEOUT:
+ heartbeat = 256;
+ return put_user(heartbeat, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int asr_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &asr_is_open))
+ return -EBUSY;
+
+ asr_toggle();
+ asr_enable();
+
+ return stream_open(inode, file);
+}
+
+static int asr_release(struct inode *inode, struct file *file)
+{
+ if (asr_expect_close == 42)
+ asr_disable();
+ else {
+ pr_crit("unexpected close, not stopping watchdog!\n");
+ asr_toggle();
+ }
+ clear_bit(0, &asr_is_open);
+ asr_expect_close = 0;
+ return 0;
+}
+
+static const struct file_operations asr_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = asr_write,
+ .unlocked_ioctl = asr_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = asr_open,
+ .release = asr_release,
+};
+
+static struct miscdevice asr_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &asr_fops,
+};
+
+
+struct ibmasr_id {
+ const char *desc;
+ int type;
+};
+
+static struct ibmasr_id ibmasr_id_table[] __initdata = {
+ { "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ },
+ { "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL },
+ { "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER },
+ { "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER },
+ { "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE },
+ { NULL }
+};
+
+static int __init ibmasr_init(void)
+{
+ struct ibmasr_id *id;
+ int rc;
+
+ for (id = ibmasr_id_table; id->desc; id++) {
+ if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) {
+ asr_type = id->type;
+ break;
+ }
+ }
+
+ if (!asr_type)
+ return -ENODEV;
+
+ rc = asr_get_base_address();
+ if (rc)
+ return rc;
+
+ rc = misc_register(&asr_miscdev);
+ if (rc < 0) {
+ release_region(asr_base, asr_length);
+ pr_err("failed to register misc device\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static void __exit ibmasr_exit(void)
+{
+ if (!nowayout)
+ asr_disable();
+
+ misc_deregister(&asr_miscdev);
+
+ release_region(asr_base, asr_length);
+}
+
+module_init(ibmasr_init);
+module_exit(ibmasr_exit);
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_DESCRIPTION("IBM Automatic Server Restart driver");
+MODULE_AUTHOR("Andrey Panin");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/ie6xx_wdt.c b/drivers/watchdog/ie6xx_wdt.c
new file mode 100644
index 000000000..8f28993fa
--- /dev/null
+++ b/drivers/watchdog/ie6xx_wdt.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel Atom E6xx Watchdog driver
+ *
+ * Copyright (C) 2011 Alexander Stein
+ * <alexander.stein@systec-electronic.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/spinlock.h>
+
+#define DRIVER_NAME "ie6xx_wdt"
+
+#define PV1 0x00
+#define PV2 0x04
+
+#define RR0 0x0c
+#define RR1 0x0d
+#define WDT_RELOAD 0x01
+#define WDT_TOUT 0x02
+
+#define WDTCR 0x10
+#define WDT_PRE_SEL 0x04
+#define WDT_RESET_SEL 0x08
+#define WDT_RESET_EN 0x10
+#define WDT_TOUT_EN 0x20
+
+#define DCR 0x14
+
+#define WDTLR 0x18
+#define WDT_LOCK 0x01
+#define WDT_ENABLE 0x02
+#define WDT_TOUT_CNF 0x03
+
+#define MIN_TIME 1
+#define MAX_TIME (10 * 60) /* 10 minutes */
+#define DEFAULT_TIME 60
+
+static unsigned int timeout = DEFAULT_TIME;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Default Watchdog timer setting ("
+ __MODULE_STRING(DEFAULT_TIME) "s)."
+ "The range is from 1 to 600");
+
+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) ")");
+
+static u8 resetmode = 0x10;
+module_param(resetmode, byte, 0);
+MODULE_PARM_DESC(resetmode,
+ "Resetmode bits: 0x08 warm reset (cold reset otherwise), "
+ "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
+
+static struct {
+ unsigned short sch_wdtba;
+ spinlock_t unlock_sequence;
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs;
+#endif
+} ie6xx_wdt_data;
+
+/*
+ * This is needed to write to preload and reload registers
+ * struct ie6xx_wdt_data.unlock_sequence must be used
+ * to prevent sequence interrupts
+ */
+static void ie6xx_wdt_unlock_registers(void)
+{
+ outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
+ outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
+}
+
+static int ie6xx_wdt_ping(struct watchdog_device *wdd)
+{
+ spin_lock(&ie6xx_wdt_data.unlock_sequence);
+ ie6xx_wdt_unlock_registers();
+ outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
+ spin_unlock(&ie6xx_wdt_data.unlock_sequence);
+ return 0;
+}
+
+static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
+{
+ u32 preload;
+ u64 clock;
+ u8 wdtcr;
+
+ /* Watchdog clock is PCI Clock (33MHz) */
+ clock = 33000000;
+ /* and the preload value is loaded into [34:15] of the down counter */
+ preload = (t * clock) >> 15;
+ /*
+ * Manual states preload must be one less.
+ * Does not wrap as t is at least 1
+ */
+ preload -= 1;
+
+ spin_lock(&ie6xx_wdt_data.unlock_sequence);
+
+ /* Set ResetMode & Enable prescaler for range 10ms to 10 min */
+ wdtcr = resetmode & 0x38;
+ outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
+
+ ie6xx_wdt_unlock_registers();
+ outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
+
+ ie6xx_wdt_unlock_registers();
+ outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
+
+ ie6xx_wdt_unlock_registers();
+ outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
+
+ spin_unlock(&ie6xx_wdt_data.unlock_sequence);
+
+ wdd->timeout = t;
+ return 0;
+}
+
+static int ie6xx_wdt_start(struct watchdog_device *wdd)
+{
+ ie6xx_wdt_set_timeout(wdd, wdd->timeout);
+
+ /* Enable the watchdog timer */
+ spin_lock(&ie6xx_wdt_data.unlock_sequence);
+ outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
+ spin_unlock(&ie6xx_wdt_data.unlock_sequence);
+
+ return 0;
+}
+
+static int ie6xx_wdt_stop(struct watchdog_device *wdd)
+{
+ if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
+ return -1;
+
+ /* Disable the watchdog timer */
+ spin_lock(&ie6xx_wdt_data.unlock_sequence);
+ outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
+ spin_unlock(&ie6xx_wdt_data.unlock_sequence);
+
+ return 0;
+}
+
+static const struct watchdog_info ie6xx_wdt_info = {
+ .identity = "Intel Atom E6xx Watchdog",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+};
+
+static const struct watchdog_ops ie6xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ie6xx_wdt_start,
+ .stop = ie6xx_wdt_stop,
+ .ping = ie6xx_wdt_ping,
+ .set_timeout = ie6xx_wdt_set_timeout,
+};
+
+static struct watchdog_device ie6xx_wdt_dev = {
+ .info = &ie6xx_wdt_info,
+ .ops = &ie6xx_wdt_ops,
+ .min_timeout = MIN_TIME,
+ .max_timeout = MAX_TIME,
+};
+
+#ifdef CONFIG_DEBUG_FS
+
+static int ie6xx_wdt_show(struct seq_file *s, void *unused)
+{
+ seq_printf(s, "PV1 = 0x%08x\n",
+ inl(ie6xx_wdt_data.sch_wdtba + PV1));
+ seq_printf(s, "PV2 = 0x%08x\n",
+ inl(ie6xx_wdt_data.sch_wdtba + PV2));
+ seq_printf(s, "RR = 0x%08x\n",
+ inw(ie6xx_wdt_data.sch_wdtba + RR0));
+ seq_printf(s, "WDTCR = 0x%08x\n",
+ inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
+ seq_printf(s, "DCR = 0x%08x\n",
+ inl(ie6xx_wdt_data.sch_wdtba + DCR));
+ seq_printf(s, "WDTLR = 0x%08x\n",
+ inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
+
+ seq_printf(s, "\n");
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt);
+
+static void ie6xx_wdt_debugfs_init(void)
+{
+ /* /sys/kernel/debug/ie6xx_wdt */
+ ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
+ S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops);
+}
+
+static void ie6xx_wdt_debugfs_exit(void)
+{
+ debugfs_remove(ie6xx_wdt_data.debugfs);
+}
+
+#else
+static void ie6xx_wdt_debugfs_init(void)
+{
+}
+
+static void ie6xx_wdt_debugfs_exit(void)
+{
+}
+#endif
+
+static int ie6xx_wdt_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+ u8 wdtlr;
+ int ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return -ENODEV;
+
+ if (!request_region(res->start, resource_size(res), pdev->name)) {
+ dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
+ (u64)res->start);
+ return -EBUSY;
+ }
+
+ ie6xx_wdt_data.sch_wdtba = res->start;
+ dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
+
+ ie6xx_wdt_dev.timeout = timeout;
+ watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
+ ie6xx_wdt_dev.parent = &pdev->dev;
+
+ spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
+
+ wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
+ if (wdtlr & WDT_LOCK)
+ dev_warn(&pdev->dev,
+ "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
+
+ ie6xx_wdt_debugfs_init();
+
+ ret = watchdog_register_device(&ie6xx_wdt_dev);
+ if (ret)
+ goto misc_register_error;
+
+ return 0;
+
+misc_register_error:
+ ie6xx_wdt_debugfs_exit();
+ release_region(res->start, resource_size(res));
+ ie6xx_wdt_data.sch_wdtba = 0;
+ return ret;
+}
+
+static int ie6xx_wdt_remove(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ ie6xx_wdt_stop(NULL);
+ watchdog_unregister_device(&ie6xx_wdt_dev);
+ ie6xx_wdt_debugfs_exit();
+ release_region(res->start, resource_size(res));
+ ie6xx_wdt_data.sch_wdtba = 0;
+
+ return 0;
+}
+
+static struct platform_driver ie6xx_wdt_driver = {
+ .probe = ie6xx_wdt_probe,
+ .remove = ie6xx_wdt_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+static int __init ie6xx_wdt_init(void)
+{
+ /* Check boot parameters to verify that their initial values */
+ /* are in range. */
+ if ((timeout < MIN_TIME) ||
+ (timeout > MAX_TIME)) {
+ pr_err("Watchdog timer: value of timeout %d (dec) "
+ "is out of range from %d to %d (dec)\n",
+ timeout, MIN_TIME, MAX_TIME);
+ return -EINVAL;
+ }
+
+ return platform_driver_register(&ie6xx_wdt_driver);
+}
+
+static void __exit ie6xx_wdt_exit(void)
+{
+ platform_driver_unregister(&ie6xx_wdt_driver);
+}
+
+late_initcall(ie6xx_wdt_init);
+module_exit(ie6xx_wdt_exit);
+
+MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
+MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/watchdog/imgpdc_wdt.c b/drivers/watchdog/imgpdc_wdt.c
new file mode 100644
index 000000000..b57ff3787
--- /dev/null
+++ b/drivers/watchdog/imgpdc_wdt.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Imagination Technologies PowerDown Controller Watchdog Timer.
+ *
+ * Copyright (c) 2014 Imagination Technologies Ltd.
+ *
+ * Based on drivers/watchdog/sunxi_wdt.c Copyright (c) 2013 Carlo Caione
+ * 2012 Henrik Nordstrom
+ *
+ * Notes
+ * -----
+ * The timeout value is rounded to the next power of two clock cycles.
+ * This is configured using the PDC_WDT_CONFIG register, according to this
+ * formula:
+ *
+ * timeout = 2^(delay + 1) clock cycles
+ *
+ * Where 'delay' is the value written in PDC_WDT_CONFIG register.
+ *
+ * Therefore, the hardware only allows to program watchdog timeouts, expressed
+ * as a power of two number of watchdog clock cycles. The current implementation
+ * guarantees that the actual watchdog timeout will be _at least_ the value
+ * programmed in the imgpdg_wdt driver.
+ *
+ * The following table shows how the user-configured timeout relates
+ * to the actual hardware timeout (watchdog clock @ 40000 Hz):
+ *
+ * input timeout | WD_DELAY | actual timeout
+ * -----------------------------------
+ * 10 | 18 | 13 seconds
+ * 20 | 19 | 26 seconds
+ * 30 | 20 | 52 seconds
+ * 60 | 21 | 104 seconds
+ *
+ * Albeit coarse, this granularity would suffice most watchdog uses.
+ * If the platform allows it, the user should be able to change the watchdog
+ * clock rate and achieve a finer timeout granularity.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+/* registers */
+#define PDC_WDT_SOFT_RESET 0x00
+#define PDC_WDT_CONFIG 0x04
+ #define PDC_WDT_CONFIG_ENABLE BIT(31)
+ #define PDC_WDT_CONFIG_DELAY_MASK 0x1f
+
+#define PDC_WDT_TICKLE1 0x08
+#define PDC_WDT_TICKLE1_MAGIC 0xabcd1234
+#define PDC_WDT_TICKLE2 0x0c
+#define PDC_WDT_TICKLE2_MAGIC 0x4321dcba
+
+#define PDC_WDT_TICKLE_STATUS_MASK 0x7
+#define PDC_WDT_TICKLE_STATUS_SHIFT 0
+#define PDC_WDT_TICKLE_STATUS_HRESET 0x0 /* Hard reset */
+#define PDC_WDT_TICKLE_STATUS_TIMEOUT 0x1 /* Timeout */
+#define PDC_WDT_TICKLE_STATUS_TICKLE 0x2 /* Tickled incorrectly */
+#define PDC_WDT_TICKLE_STATUS_SRESET 0x3 /* Soft reset */
+#define PDC_WDT_TICKLE_STATUS_USER 0x4 /* User reset */
+
+/* Timeout values are in seconds */
+#define PDC_WDT_MIN_TIMEOUT 1
+#define PDC_WDT_DEF_TIMEOUT 64
+
+static int heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds "
+ "(default=" __MODULE_STRING(PDC_WDT_DEF_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+ "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct pdc_wdt_dev {
+ struct watchdog_device wdt_dev;
+ struct clk *wdt_clk;
+ struct clk *sys_clk;
+ void __iomem *base;
+};
+
+static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev)
+{
+ struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel(PDC_WDT_TICKLE1_MAGIC, wdt->base + PDC_WDT_TICKLE1);
+ writel(PDC_WDT_TICKLE2_MAGIC, wdt->base + PDC_WDT_TICKLE2);
+
+ return 0;
+}
+
+static int pdc_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ unsigned int val;
+ struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
+
+ val = readl(wdt->base + PDC_WDT_CONFIG);
+ val &= ~PDC_WDT_CONFIG_ENABLE;
+ writel(val, wdt->base + PDC_WDT_CONFIG);
+
+ /* Must tickle to finish the stop */
+ pdc_wdt_keepalive(wdt_dev);
+
+ return 0;
+}
+
+static void __pdc_wdt_set_timeout(struct pdc_wdt_dev *wdt)
+{
+ unsigned long clk_rate = clk_get_rate(wdt->wdt_clk);
+ unsigned int val;
+
+ val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK;
+ val |= order_base_2(wdt->wdt_dev.timeout * clk_rate) - 1;
+ writel(val, wdt->base + PDC_WDT_CONFIG);
+}
+
+static int pdc_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
+
+ wdt->wdt_dev.timeout = new_timeout;
+
+ __pdc_wdt_set_timeout(wdt);
+
+ return 0;
+}
+
+/* Start the watchdog timer (delay should already be set) */
+static int pdc_wdt_start(struct watchdog_device *wdt_dev)
+{
+ unsigned int val;
+ struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
+
+ __pdc_wdt_set_timeout(wdt);
+
+ val = readl(wdt->base + PDC_WDT_CONFIG);
+ val |= PDC_WDT_CONFIG_ENABLE;
+ writel(val, wdt->base + PDC_WDT_CONFIG);
+
+ return 0;
+}
+
+static int pdc_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev);
+
+ /* Assert SOFT_RESET */
+ writel(0x1, wdt->base + PDC_WDT_SOFT_RESET);
+
+ return 0;
+}
+
+static const struct watchdog_info pdc_wdt_info = {
+ .identity = "IMG PDC Watchdog",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops pdc_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pdc_wdt_start,
+ .stop = pdc_wdt_stop,
+ .ping = pdc_wdt_keepalive,
+ .set_timeout = pdc_wdt_set_timeout,
+ .restart = pdc_wdt_restart,
+};
+
+static void pdc_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int pdc_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ u64 div;
+ int ret, val;
+ unsigned long clk_rate;
+ struct pdc_wdt_dev *pdc_wdt;
+
+ pdc_wdt = devm_kzalloc(dev, sizeof(*pdc_wdt), GFP_KERNEL);
+ if (!pdc_wdt)
+ return -ENOMEM;
+
+ pdc_wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(pdc_wdt->base))
+ return PTR_ERR(pdc_wdt->base);
+
+ pdc_wdt->sys_clk = devm_clk_get(dev, "sys");
+ if (IS_ERR(pdc_wdt->sys_clk)) {
+ dev_err(dev, "failed to get the sys clock\n");
+ return PTR_ERR(pdc_wdt->sys_clk);
+ }
+
+ pdc_wdt->wdt_clk = devm_clk_get(dev, "wdt");
+ if (IS_ERR(pdc_wdt->wdt_clk)) {
+ dev_err(dev, "failed to get the wdt clock\n");
+ return PTR_ERR(pdc_wdt->wdt_clk);
+ }
+
+ ret = clk_prepare_enable(pdc_wdt->sys_clk);
+ if (ret) {
+ dev_err(dev, "could not prepare or enable sys clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, pdc_clk_disable_unprepare,
+ pdc_wdt->sys_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(pdc_wdt->wdt_clk);
+ if (ret) {
+ dev_err(dev, "could not prepare or enable wdt clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, pdc_clk_disable_unprepare,
+ pdc_wdt->wdt_clk);
+ if (ret)
+ return ret;
+
+ /* We use the clock rate to calculate the max timeout */
+ clk_rate = clk_get_rate(pdc_wdt->wdt_clk);
+ if (clk_rate == 0) {
+ dev_err(dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ if (order_base_2(clk_rate) > PDC_WDT_CONFIG_DELAY_MASK + 1) {
+ dev_err(dev, "invalid clock rate\n");
+ return -EINVAL;
+ }
+
+ if (order_base_2(clk_rate) == 0)
+ pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT + 1;
+ else
+ pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT;
+
+ pdc_wdt->wdt_dev.info = &pdc_wdt_info;
+ pdc_wdt->wdt_dev.ops = &pdc_wdt_ops;
+
+ div = 1ULL << (PDC_WDT_CONFIG_DELAY_MASK + 1);
+ do_div(div, clk_rate);
+ pdc_wdt->wdt_dev.max_timeout = div;
+ pdc_wdt->wdt_dev.timeout = PDC_WDT_DEF_TIMEOUT;
+ pdc_wdt->wdt_dev.parent = dev;
+ watchdog_set_drvdata(&pdc_wdt->wdt_dev, pdc_wdt);
+
+ watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, dev);
+
+ pdc_wdt_stop(&pdc_wdt->wdt_dev);
+
+ /* Find what caused the last reset */
+ val = readl(pdc_wdt->base + PDC_WDT_TICKLE1);
+ val = (val & PDC_WDT_TICKLE_STATUS_MASK) >> PDC_WDT_TICKLE_STATUS_SHIFT;
+ switch (val) {
+ case PDC_WDT_TICKLE_STATUS_TICKLE:
+ case PDC_WDT_TICKLE_STATUS_TIMEOUT:
+ pdc_wdt->wdt_dev.bootstatus |= WDIOF_CARDRESET;
+ dev_info(dev, "watchdog module last reset due to timeout\n");
+ break;
+ case PDC_WDT_TICKLE_STATUS_HRESET:
+ dev_info(dev,
+ "watchdog module last reset due to hard reset\n");
+ break;
+ case PDC_WDT_TICKLE_STATUS_SRESET:
+ dev_info(dev,
+ "watchdog module last reset due to soft reset\n");
+ break;
+ case PDC_WDT_TICKLE_STATUS_USER:
+ dev_info(dev,
+ "watchdog module last reset due to user reset\n");
+ break;
+ default:
+ dev_info(dev, "contains an illegal status code (%08x)\n", val);
+ break;
+ }
+
+ watchdog_set_nowayout(&pdc_wdt->wdt_dev, nowayout);
+ watchdog_set_restart_priority(&pdc_wdt->wdt_dev, 128);
+
+ platform_set_drvdata(pdev, pdc_wdt);
+
+ watchdog_stop_on_reboot(&pdc_wdt->wdt_dev);
+ watchdog_stop_on_unregister(&pdc_wdt->wdt_dev);
+ return devm_watchdog_register_device(dev, &pdc_wdt->wdt_dev);
+}
+
+static const struct of_device_id pdc_wdt_match[] = {
+ { .compatible = "img,pdc-wdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pdc_wdt_match);
+
+static struct platform_driver pdc_wdt_driver = {
+ .driver = {
+ .name = "imgpdc-wdt",
+ .of_match_table = pdc_wdt_match,
+ },
+ .probe = pdc_wdt_probe,
+};
+module_platform_driver(pdc_wdt_driver);
+
+MODULE_AUTHOR("Jude Abraham <Jude.Abraham@imgtec.com>");
+MODULE_AUTHOR("Naidu Tellapati <Naidu.Tellapati@imgtec.com>");
+MODULE_DESCRIPTION("Imagination Technologies PDC Watchdog Timer Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c
new file mode 100644
index 000000000..d0c5d47dd
--- /dev/null
+++ b/drivers/watchdog/imx2_wdt.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for IMX2 and later processors
+ *
+ * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. <kernel@pengutronix.de>
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * some parts adapted by similar drivers from Darius Augulis and Vladimir
+ * Zapolskiy, additional improvements by Wim Van Sebroeck.
+ *
+ * NOTE: MX1 has a slightly different Watchdog than MX2 and later:
+ *
+ * MX1: MX2+:
+ * ---- -----
+ * Registers: 32-bit 16-bit
+ * Stopable timer: Yes No
+ * Need to enable clk: No Yes
+ * Halt on suspend: Manual Can be automatic
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define DRIVER_NAME "imx2-wdt"
+
+#define IMX2_WDT_WCR 0x00 /* Control Register */
+#define IMX2_WDT_WCR_WT (0xFF << 8) /* -> Watchdog Timeout Field */
+#define IMX2_WDT_WCR_WDA BIT(5) /* -> External Reset WDOG_B */
+#define IMX2_WDT_WCR_SRS BIT(4) /* -> Software Reset Signal */
+#define IMX2_WDT_WCR_WRE BIT(3) /* -> WDOG Reset Enable */
+#define IMX2_WDT_WCR_WDE BIT(2) /* -> Watchdog Enable */
+#define IMX2_WDT_WCR_WDZST BIT(0) /* -> Watchdog timer Suspend */
+
+#define IMX2_WDT_WSR 0x02 /* Service Register */
+#define IMX2_WDT_SEQ1 0x5555 /* -> service sequence 1 */
+#define IMX2_WDT_SEQ2 0xAAAA /* -> service sequence 2 */
+
+#define IMX2_WDT_WRSR 0x04 /* Reset Status Register */
+#define IMX2_WDT_WRSR_TOUT BIT(1) /* -> Reset due to Timeout */
+
+#define IMX2_WDT_WICR 0x06 /* Interrupt Control Register */
+#define IMX2_WDT_WICR_WIE BIT(15) /* -> Interrupt Enable */
+#define IMX2_WDT_WICR_WTIS BIT(14) /* -> Interrupt Status */
+#define IMX2_WDT_WICR_WICT 0xFF /* -> Interrupt Count Timeout */
+
+#define IMX2_WDT_WMCR 0x08 /* Misc Register */
+
+#define IMX2_WDT_MAX_TIME 128U
+#define IMX2_WDT_DEFAULT_TIME 60 /* in seconds */
+
+#define WDOG_SEC_TO_COUNT(s) ((s * 2 - 1) << 8)
+
+struct imx2_wdt_device {
+ struct clk *clk;
+ struct regmap *regmap;
+ struct watchdog_device wdog;
+ bool ext_reset;
+ bool clk_is_on;
+ bool no_ping;
+};
+
+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) ")");
+
+static unsigned timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
+ __MODULE_STRING(IMX2_WDT_DEFAULT_TIME) ")");
+
+static const struct watchdog_info imx2_wdt_info = {
+ .identity = "imx2+ watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_info imx2_wdt_pretimeout_info = {
+ .identity = "imx2+ watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
+ WDIOF_PRETIMEOUT,
+};
+
+static int imx2_wdt_restart(struct watchdog_device *wdog, unsigned long action,
+ void *data)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+ unsigned int wcr_enable = IMX2_WDT_WCR_WDE;
+
+ /* Use internal reset or external - not both */
+ if (wdev->ext_reset)
+ wcr_enable |= IMX2_WDT_WCR_SRS; /* do not assert int reset */
+ else
+ wcr_enable |= IMX2_WDT_WCR_WDA; /* do not assert ext-reset */
+
+ /* Assert SRS signal */
+ regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable);
+ /*
+ * Due to imx6q errata ERR004346 (WDOG: WDOG SRS bit requires to be
+ * written twice), we add another two writes to ensure there must be at
+ * least two writes happen in the same one 32kHz clock period. We save
+ * the target check here, since the writes shouldn't be a huge burden
+ * for other platforms.
+ */
+ regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable);
+ regmap_write(wdev->regmap, IMX2_WDT_WCR, wcr_enable);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return 0;
+}
+
+static inline void imx2_wdt_setup(struct watchdog_device *wdog)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+ u32 val;
+
+ regmap_read(wdev->regmap, IMX2_WDT_WCR, &val);
+
+ /* Suspend timer in low power mode, write once-only */
+ val |= IMX2_WDT_WCR_WDZST;
+ /* Strip the old watchdog Time-Out value */
+ val &= ~IMX2_WDT_WCR_WT;
+ /* Generate internal chip-level reset if WDOG times out */
+ if (!wdev->ext_reset)
+ val &= ~IMX2_WDT_WCR_WRE;
+ /* Or if external-reset assert WDOG_B reset only on time-out */
+ else
+ val |= IMX2_WDT_WCR_WRE;
+ /* Keep Watchdog Disabled */
+ val &= ~IMX2_WDT_WCR_WDE;
+ /* Set the watchdog's Time-Out value */
+ val |= WDOG_SEC_TO_COUNT(wdog->timeout);
+
+ regmap_write(wdev->regmap, IMX2_WDT_WCR, val);
+
+ /* enable the watchdog */
+ val |= IMX2_WDT_WCR_WDE;
+ regmap_write(wdev->regmap, IMX2_WDT_WCR, val);
+}
+
+static inline bool imx2_wdt_is_running(struct imx2_wdt_device *wdev)
+{
+ u32 val;
+
+ regmap_read(wdev->regmap, IMX2_WDT_WCR, &val);
+
+ return val & IMX2_WDT_WCR_WDE;
+}
+
+static int imx2_wdt_ping(struct watchdog_device *wdog)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ if (!wdev->clk_is_on)
+ return 0;
+
+ regmap_write(wdev->regmap, IMX2_WDT_WSR, IMX2_WDT_SEQ1);
+ regmap_write(wdev->regmap, IMX2_WDT_WSR, IMX2_WDT_SEQ2);
+ return 0;
+}
+
+static void __imx2_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int new_timeout)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ regmap_update_bits(wdev->regmap, IMX2_WDT_WCR, IMX2_WDT_WCR_WT,
+ WDOG_SEC_TO_COUNT(new_timeout));
+}
+
+static int imx2_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int new_timeout)
+{
+ unsigned int actual;
+
+ actual = min(new_timeout, IMX2_WDT_MAX_TIME);
+ __imx2_wdt_set_timeout(wdog, actual);
+ wdog->timeout = new_timeout;
+ return 0;
+}
+
+static int imx2_wdt_set_pretimeout(struct watchdog_device *wdog,
+ unsigned int new_pretimeout)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ if (new_pretimeout >= IMX2_WDT_MAX_TIME)
+ return -EINVAL;
+
+ wdog->pretimeout = new_pretimeout;
+
+ regmap_update_bits(wdev->regmap, IMX2_WDT_WICR,
+ IMX2_WDT_WICR_WIE | IMX2_WDT_WICR_WICT,
+ IMX2_WDT_WICR_WIE | (new_pretimeout << 1));
+ return 0;
+}
+
+static irqreturn_t imx2_wdt_isr(int irq, void *wdog_arg)
+{
+ struct watchdog_device *wdog = wdog_arg;
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ regmap_write_bits(wdev->regmap, IMX2_WDT_WICR,
+ IMX2_WDT_WICR_WTIS, IMX2_WDT_WICR_WTIS);
+
+ watchdog_notify_pretimeout(wdog);
+
+ return IRQ_HANDLED;
+}
+
+static int imx2_wdt_start(struct watchdog_device *wdog)
+{
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ if (imx2_wdt_is_running(wdev))
+ imx2_wdt_set_timeout(wdog, wdog->timeout);
+ else
+ imx2_wdt_setup(wdog);
+
+ set_bit(WDOG_HW_RUNNING, &wdog->status);
+
+ return imx2_wdt_ping(wdog);
+}
+
+static const struct watchdog_ops imx2_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = imx2_wdt_start,
+ .ping = imx2_wdt_ping,
+ .set_timeout = imx2_wdt_set_timeout,
+ .set_pretimeout = imx2_wdt_set_pretimeout,
+ .restart = imx2_wdt_restart,
+};
+
+static const struct regmap_config imx2_wdt_regmap_config = {
+ .reg_bits = 16,
+ .reg_stride = 2,
+ .val_bits = 16,
+ .max_register = 0x8,
+};
+
+static void imx2_wdt_action(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int __init imx2_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx2_wdt_device *wdev;
+ struct watchdog_device *wdog;
+ void __iomem *base;
+ int ret;
+ u32 val;
+
+ wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL);
+ if (!wdev)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ wdev->regmap = devm_regmap_init_mmio_clk(dev, NULL, base,
+ &imx2_wdt_regmap_config);
+ if (IS_ERR(wdev->regmap)) {
+ dev_err(dev, "regmap init failed\n");
+ return PTR_ERR(wdev->regmap);
+ }
+
+ wdev->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdev->clk)) {
+ dev_err(dev, "can't get Watchdog clock\n");
+ return PTR_ERR(wdev->clk);
+ }
+
+ wdog = &wdev->wdog;
+ wdog->info = &imx2_wdt_info;
+ wdog->ops = &imx2_wdt_ops;
+ wdog->min_timeout = 1;
+ wdog->timeout = IMX2_WDT_DEFAULT_TIME;
+ wdog->max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000;
+ wdog->parent = dev;
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret > 0)
+ if (!devm_request_irq(dev, ret, imx2_wdt_isr, 0,
+ dev_name(dev), wdog))
+ wdog->info = &imx2_wdt_pretimeout_info;
+
+ ret = clk_prepare_enable(wdev->clk);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, imx2_wdt_action, wdev->clk);
+ if (ret)
+ return ret;
+
+ wdev->clk_is_on = true;
+
+ regmap_read(wdev->regmap, IMX2_WDT_WRSR, &val);
+ wdog->bootstatus = val & IMX2_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0;
+
+ wdev->ext_reset = of_property_read_bool(dev->of_node,
+ "fsl,ext-reset-output");
+ /*
+ * The i.MX7D doesn't support low power mode, so we need to ping the watchdog
+ * during suspend.
+ */
+ wdev->no_ping = !of_device_is_compatible(dev->of_node, "fsl,imx7d-wdt");
+ platform_set_drvdata(pdev, wdog);
+ watchdog_set_drvdata(wdog, wdev);
+ watchdog_set_nowayout(wdog, nowayout);
+ watchdog_set_restart_priority(wdog, 128);
+ watchdog_init_timeout(wdog, timeout, dev);
+ if (wdev->no_ping)
+ watchdog_stop_ping_on_suspend(wdog);
+
+ if (imx2_wdt_is_running(wdev)) {
+ imx2_wdt_set_timeout(wdog, wdog->timeout);
+ set_bit(WDOG_HW_RUNNING, &wdog->status);
+ }
+
+ /*
+ * Disable the watchdog power down counter at boot. Otherwise the power
+ * down counter will pull down the #WDOG interrupt line for one clock
+ * cycle.
+ */
+ regmap_write(wdev->regmap, IMX2_WDT_WMCR, 0);
+
+ return devm_watchdog_register_device(dev, wdog);
+}
+
+static void imx2_wdt_shutdown(struct platform_device *pdev)
+{
+ struct watchdog_device *wdog = platform_get_drvdata(pdev);
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ if (imx2_wdt_is_running(wdev)) {
+ /*
+ * We are running, configure max timeout before reboot
+ * will take place.
+ */
+ imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
+ imx2_wdt_ping(wdog);
+ dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n");
+ }
+}
+
+/* Disable watchdog if it is active or non-active but still running */
+static int __maybe_unused imx2_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdog = dev_get_drvdata(dev);
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+
+ /* The watchdog IP block is running */
+ if (imx2_wdt_is_running(wdev)) {
+ /*
+ * Don't update wdog->timeout, we'll restore the current value
+ * during resume.
+ */
+ __imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME);
+ imx2_wdt_ping(wdog);
+ }
+
+ if (wdev->no_ping) {
+ clk_disable_unprepare(wdev->clk);
+
+ wdev->clk_is_on = false;
+ }
+
+ return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int __maybe_unused imx2_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdog = dev_get_drvdata(dev);
+ struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog);
+ int ret;
+
+ if (wdev->no_ping) {
+ ret = clk_prepare_enable(wdev->clk);
+
+ if (ret)
+ return ret;
+
+ wdev->clk_is_on = true;
+ }
+
+ if (watchdog_active(wdog) && !imx2_wdt_is_running(wdev)) {
+ /*
+ * If the watchdog is still active and resumes
+ * from deep sleep state, need to restart the
+ * watchdog again.
+ */
+ imx2_wdt_setup(wdog);
+ }
+ if (imx2_wdt_is_running(wdev)) {
+ imx2_wdt_set_timeout(wdog, wdog->timeout);
+ imx2_wdt_ping(wdog);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(imx2_wdt_pm_ops, imx2_wdt_suspend,
+ imx2_wdt_resume);
+
+static const struct of_device_id imx2_wdt_dt_ids[] = {
+ { .compatible = "fsl,imx21-wdt", },
+ { .compatible = "fsl,imx7d-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx2_wdt_dt_ids);
+
+static struct platform_driver imx2_wdt_driver = {
+ .shutdown = imx2_wdt_shutdown,
+ .driver = {
+ .name = DRIVER_NAME,
+ .pm = &imx2_wdt_pm_ops,
+ .of_match_table = imx2_wdt_dt_ids,
+ },
+};
+
+module_platform_driver_probe(imx2_wdt_driver, imx2_wdt_probe);
+
+MODULE_AUTHOR("Wolfram Sang");
+MODULE_DESCRIPTION("Watchdog driver for IMX2 and later");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/watchdog/imx7ulp_wdt.c b/drivers/watchdog/imx7ulp_wdt.c
new file mode 100644
index 000000000..289790209
--- /dev/null
+++ b/drivers/watchdog/imx7ulp_wdt.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+
+#define WDOG_CS 0x0
+#define WDOG_CS_FLG BIT(14)
+#define WDOG_CS_CMD32EN BIT(13)
+#define WDOG_CS_PRES BIT(12)
+#define WDOG_CS_ULK BIT(11)
+#define WDOG_CS_RCS BIT(10)
+#define LPO_CLK 0x1
+#define LPO_CLK_SHIFT 8
+#define WDOG_CS_CLK (LPO_CLK << LPO_CLK_SHIFT)
+#define WDOG_CS_EN BIT(7)
+#define WDOG_CS_UPDATE BIT(5)
+#define WDOG_CS_WAIT BIT(1)
+#define WDOG_CS_STOP BIT(0)
+
+#define WDOG_CNT 0x4
+#define WDOG_TOVAL 0x8
+
+#define REFRESH_SEQ0 0xA602
+#define REFRESH_SEQ1 0xB480
+#define REFRESH ((REFRESH_SEQ1 << 16) | REFRESH_SEQ0)
+
+#define UNLOCK_SEQ0 0xC520
+#define UNLOCK_SEQ1 0xD928
+#define UNLOCK ((UNLOCK_SEQ1 << 16) | UNLOCK_SEQ0)
+
+#define DEFAULT_TIMEOUT 60
+#define MAX_TIMEOUT 128
+#define WDOG_CLOCK_RATE 1000
+#define WDOG_ULK_WAIT_TIMEOUT 1000
+#define WDOG_RCS_WAIT_TIMEOUT 10000
+#define WDOG_RCS_POST_WAIT 3000
+
+#define RETRY_MAX 5
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0000);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct imx_wdt_hw_feature {
+ bool prescaler_enable;
+ u32 wdog_clock_rate;
+};
+
+struct imx7ulp_wdt_device {
+ struct watchdog_device wdd;
+ void __iomem *base;
+ struct clk *clk;
+ bool post_rcs_wait;
+ const struct imx_wdt_hw_feature *hw;
+};
+
+static int imx7ulp_wdt_wait_ulk(void __iomem *base)
+{
+ u32 val = readl(base + WDOG_CS);
+
+ if (!(val & WDOG_CS_ULK) &&
+ readl_poll_timeout_atomic(base + WDOG_CS, val,
+ val & WDOG_CS_ULK, 0,
+ WDOG_ULK_WAIT_TIMEOUT))
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int imx7ulp_wdt_wait_rcs(struct imx7ulp_wdt_device *wdt)
+{
+ int ret = 0;
+ u32 val = readl(wdt->base + WDOG_CS);
+ u64 timeout = (val & WDOG_CS_PRES) ?
+ WDOG_RCS_WAIT_TIMEOUT * 256 : WDOG_RCS_WAIT_TIMEOUT;
+ unsigned long wait_min = (val & WDOG_CS_PRES) ?
+ WDOG_RCS_POST_WAIT * 256 : WDOG_RCS_POST_WAIT;
+
+ if (!(val & WDOG_CS_RCS) &&
+ readl_poll_timeout(wdt->base + WDOG_CS, val, val & WDOG_CS_RCS, 100,
+ timeout))
+ ret = -ETIMEDOUT;
+
+ /* Wait 2.5 clocks after RCS done */
+ if (wdt->post_rcs_wait)
+ usleep_range(wait_min, wait_min + 2000);
+
+ return ret;
+}
+
+static int _imx7ulp_wdt_enable(struct imx7ulp_wdt_device *wdt, bool enable)
+{
+ u32 val = readl(wdt->base + WDOG_CS);
+ int ret;
+
+ local_irq_disable();
+ writel(UNLOCK, wdt->base + WDOG_CNT);
+ ret = imx7ulp_wdt_wait_ulk(wdt->base);
+ if (ret)
+ goto enable_out;
+ if (enable)
+ writel(val | WDOG_CS_EN, wdt->base + WDOG_CS);
+ else
+ writel(val & ~WDOG_CS_EN, wdt->base + WDOG_CS);
+
+ local_irq_enable();
+ ret = imx7ulp_wdt_wait_rcs(wdt);
+
+ return ret;
+
+enable_out:
+ local_irq_enable();
+ return ret;
+}
+
+static int imx7ulp_wdt_enable(struct watchdog_device *wdog, bool enable)
+{
+ struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog);
+ int ret;
+ u32 val;
+ u32 loop = RETRY_MAX;
+
+ do {
+ ret = _imx7ulp_wdt_enable(wdt, enable);
+ val = readl(wdt->base + WDOG_CS);
+ } while (--loop > 0 && ((!!(val & WDOG_CS_EN)) != enable || ret));
+
+ if (loop == 0)
+ return -EBUSY;
+
+ return ret;
+}
+
+static int imx7ulp_wdt_ping(struct watchdog_device *wdog)
+{
+ struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog);
+
+ writel(REFRESH, wdt->base + WDOG_CNT);
+
+ return 0;
+}
+
+static int imx7ulp_wdt_start(struct watchdog_device *wdog)
+{
+ return imx7ulp_wdt_enable(wdog, true);
+}
+
+static int imx7ulp_wdt_stop(struct watchdog_device *wdog)
+{
+ return imx7ulp_wdt_enable(wdog, false);
+}
+
+static int _imx7ulp_wdt_set_timeout(struct imx7ulp_wdt_device *wdt,
+ unsigned int toval)
+{
+ int ret;
+
+ local_irq_disable();
+ writel(UNLOCK, wdt->base + WDOG_CNT);
+ ret = imx7ulp_wdt_wait_ulk(wdt->base);
+ if (ret)
+ goto timeout_out;
+ writel(toval, wdt->base + WDOG_TOVAL);
+ local_irq_enable();
+ ret = imx7ulp_wdt_wait_rcs(wdt);
+ return ret;
+
+timeout_out:
+ local_irq_enable();
+ return ret;
+}
+
+static int imx7ulp_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int timeout)
+{
+ struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog);
+ u32 toval = wdt->hw->wdog_clock_rate * timeout;
+ u32 val;
+ int ret;
+ u32 loop = RETRY_MAX;
+
+ do {
+ ret = _imx7ulp_wdt_set_timeout(wdt, toval);
+ val = readl(wdt->base + WDOG_TOVAL);
+ } while (--loop > 0 && (val != toval || ret));
+
+ if (loop == 0)
+ return -EBUSY;
+
+ wdog->timeout = timeout;
+ return ret;
+}
+
+static int imx7ulp_wdt_restart(struct watchdog_device *wdog,
+ unsigned long action, void *data)
+{
+ struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog);
+ int ret;
+
+ ret = imx7ulp_wdt_enable(wdog, true);
+ if (ret)
+ return ret;
+
+ ret = imx7ulp_wdt_set_timeout(&wdt->wdd, 1);
+ if (ret)
+ return ret;
+
+ /* wait for wdog to fire */
+ while (true)
+ ;
+
+ return NOTIFY_DONE;
+}
+
+static const struct watchdog_ops imx7ulp_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = imx7ulp_wdt_start,
+ .stop = imx7ulp_wdt_stop,
+ .ping = imx7ulp_wdt_ping,
+ .set_timeout = imx7ulp_wdt_set_timeout,
+ .restart = imx7ulp_wdt_restart,
+};
+
+static const struct watchdog_info imx7ulp_wdt_info = {
+ .identity = "i.MX7ULP watchdog timer",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static int _imx7ulp_wdt_init(struct imx7ulp_wdt_device *wdt, unsigned int timeout, unsigned int cs)
+{
+ u32 val;
+ int ret;
+
+ local_irq_disable();
+
+ val = readl(wdt->base + WDOG_CS);
+ if (val & WDOG_CS_CMD32EN) {
+ writel(UNLOCK, wdt->base + WDOG_CNT);
+ } else {
+ mb();
+ /* unlock the wdog for reconfiguration */
+ writel_relaxed(UNLOCK_SEQ0, wdt->base + WDOG_CNT);
+ writel_relaxed(UNLOCK_SEQ1, wdt->base + WDOG_CNT);
+ mb();
+ }
+
+ ret = imx7ulp_wdt_wait_ulk(wdt->base);
+ if (ret)
+ goto init_out;
+
+ /* set an initial timeout value in TOVAL */
+ writel(timeout, wdt->base + WDOG_TOVAL);
+ writel(cs, wdt->base + WDOG_CS);
+ local_irq_enable();
+ ret = imx7ulp_wdt_wait_rcs(wdt);
+
+ return ret;
+
+init_out:
+ local_irq_enable();
+ return ret;
+}
+
+static int imx7ulp_wdt_init(struct imx7ulp_wdt_device *wdt, unsigned int timeout)
+{
+ /* enable 32bit command sequence and reconfigure */
+ u32 val = WDOG_CS_CMD32EN | WDOG_CS_CLK | WDOG_CS_UPDATE |
+ WDOG_CS_WAIT | WDOG_CS_STOP;
+ u32 cs, toval;
+ int ret;
+ u32 loop = RETRY_MAX;
+
+ if (wdt->hw->prescaler_enable)
+ val |= WDOG_CS_PRES;
+
+ do {
+ ret = _imx7ulp_wdt_init(wdt, timeout, val);
+ toval = readl(wdt->base + WDOG_TOVAL);
+ cs = readl(wdt->base + WDOG_CS);
+ cs &= ~(WDOG_CS_FLG | WDOG_CS_ULK | WDOG_CS_RCS);
+ } while (--loop > 0 && (cs != val || toval != timeout || ret));
+
+ if (loop == 0)
+ return -EBUSY;
+
+ return ret;
+}
+
+static void imx7ulp_wdt_action(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int imx7ulp_wdt_probe(struct platform_device *pdev)
+{
+ struct imx7ulp_wdt_device *imx7ulp_wdt;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdog;
+ int ret;
+
+ imx7ulp_wdt = devm_kzalloc(dev, sizeof(*imx7ulp_wdt), GFP_KERNEL);
+ if (!imx7ulp_wdt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, imx7ulp_wdt);
+
+ imx7ulp_wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(imx7ulp_wdt->base))
+ return PTR_ERR(imx7ulp_wdt->base);
+
+ imx7ulp_wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(imx7ulp_wdt->clk)) {
+ dev_err(dev, "Failed to get watchdog clock\n");
+ return PTR_ERR(imx7ulp_wdt->clk);
+ }
+
+ imx7ulp_wdt->post_rcs_wait = true;
+ if (of_device_is_compatible(dev->of_node,
+ "fsl,imx8ulp-wdt")) {
+ dev_info(dev, "imx8ulp wdt probe\n");
+ imx7ulp_wdt->post_rcs_wait = false;
+ } else {
+ dev_info(dev, "imx7ulp wdt probe\n");
+ }
+
+ ret = clk_prepare_enable(imx7ulp_wdt->clk);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, imx7ulp_wdt_action, imx7ulp_wdt->clk);
+ if (ret)
+ return ret;
+
+ wdog = &imx7ulp_wdt->wdd;
+ wdog->info = &imx7ulp_wdt_info;
+ wdog->ops = &imx7ulp_wdt_ops;
+ wdog->min_timeout = 1;
+ wdog->max_timeout = MAX_TIMEOUT;
+ wdog->parent = dev;
+ wdog->timeout = DEFAULT_TIMEOUT;
+
+ watchdog_init_timeout(wdog, 0, dev);
+ watchdog_stop_on_reboot(wdog);
+ watchdog_stop_on_unregister(wdog);
+ watchdog_set_drvdata(wdog, imx7ulp_wdt);
+
+ imx7ulp_wdt->hw = of_device_get_match_data(dev);
+ ret = imx7ulp_wdt_init(imx7ulp_wdt, wdog->timeout * imx7ulp_wdt->hw->wdog_clock_rate);
+ if (ret)
+ return ret;
+
+ return devm_watchdog_register_device(dev, wdog);
+}
+
+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))
+ imx7ulp_wdt_stop(&imx7ulp_wdt->wdd);
+
+ clk_disable_unprepare(imx7ulp_wdt->clk);
+
+ return 0;
+}
+
+static int __maybe_unused imx7ulp_wdt_resume_noirq(struct device *dev)
+{
+ struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev);
+ u32 timeout = imx7ulp_wdt->wdd.timeout * imx7ulp_wdt->hw->wdog_clock_rate;
+ int ret;
+
+ ret = clk_prepare_enable(imx7ulp_wdt->clk);
+ if (ret)
+ return ret;
+
+ if (watchdog_active(&imx7ulp_wdt->wdd)) {
+ imx7ulp_wdt_init(imx7ulp_wdt, timeout);
+ imx7ulp_wdt_start(&imx7ulp_wdt->wdd);
+ imx7ulp_wdt_ping(&imx7ulp_wdt->wdd);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops imx7ulp_wdt_pm_ops = {
+ SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx7ulp_wdt_suspend_noirq,
+ imx7ulp_wdt_resume_noirq)
+};
+
+static const struct imx_wdt_hw_feature imx7ulp_wdt_hw = {
+ .prescaler_enable = false,
+ .wdog_clock_rate = 1000,
+};
+
+static const struct imx_wdt_hw_feature imx93_wdt_hw = {
+ .prescaler_enable = true,
+ .wdog_clock_rate = 125,
+};
+
+static const struct of_device_id imx7ulp_wdt_dt_ids[] = {
+ { .compatible = "fsl,imx8ulp-wdt", .data = &imx7ulp_wdt_hw, },
+ { .compatible = "fsl,imx7ulp-wdt", .data = &imx7ulp_wdt_hw, },
+ { .compatible = "fsl,imx93-wdt", .data = &imx93_wdt_hw, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids);
+
+static struct platform_driver imx7ulp_wdt_driver = {
+ .probe = imx7ulp_wdt_probe,
+ .driver = {
+ .name = "imx7ulp-wdt",
+ .pm = &imx7ulp_wdt_pm_ops,
+ .of_match_table = imx7ulp_wdt_dt_ids,
+ },
+};
+module_platform_driver(imx7ulp_wdt_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("Freescale i.MX7ULP watchdog driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/imx_sc_wdt.c b/drivers/watchdog/imx_sc_wdt.c
new file mode 100644
index 000000000..8ac021748
--- /dev/null
+++ b/drivers/watchdog/imx_sc_wdt.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2019 NXP.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define DEFAULT_TIMEOUT 60
+/*
+ * Software timer tick implemented in scfw side, support 10ms to 0xffffffff ms
+ * in theory, but for normal case, 1s~128s is enough, you can change this max
+ * value in case it's not enough.
+ */
+#define MAX_TIMEOUT 128
+
+#define IMX_SIP_TIMER 0xC2000002
+#define IMX_SIP_TIMER_START_WDOG 0x01
+#define IMX_SIP_TIMER_STOP_WDOG 0x02
+#define IMX_SIP_TIMER_SET_WDOG_ACT 0x03
+#define IMX_SIP_TIMER_PING_WDOG 0x04
+#define IMX_SIP_TIMER_SET_TIMEOUT_WDOG 0x05
+#define IMX_SIP_TIMER_GET_WDOG_STAT 0x06
+#define IMX_SIP_TIMER_SET_PRETIME_WDOG 0x07
+
+#define SC_TIMER_WDOG_ACTION_PARTITION 0
+
+#define SC_IRQ_WDOG 1
+#define SC_IRQ_GROUP_WDOG 1
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0000);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct imx_sc_wdt_device {
+ struct watchdog_device wdd;
+ struct notifier_block wdt_notifier;
+};
+
+static int imx_sc_wdt_ping(struct watchdog_device *wdog)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_PING_WDOG,
+ 0, 0, 0, 0, 0, 0, &res);
+
+ return 0;
+}
+
+static int imx_sc_wdt_start(struct watchdog_device *wdog)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_START_WDOG,
+ 0, 0, 0, 0, 0, 0, &res);
+ if (res.a0)
+ return -EACCES;
+
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_WDOG_ACT,
+ SC_TIMER_WDOG_ACTION_PARTITION,
+ 0, 0, 0, 0, 0, &res);
+ return res.a0 ? -EACCES : 0;
+}
+
+static int imx_sc_wdt_stop(struct watchdog_device *wdog)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_STOP_WDOG,
+ 0, 0, 0, 0, 0, 0, &res);
+
+ return res.a0 ? -EACCES : 0;
+}
+
+static int imx_sc_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int timeout)
+{
+ struct arm_smccc_res res;
+
+ wdog->timeout = timeout;
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_TIMEOUT_WDOG,
+ timeout * 1000, 0, 0, 0, 0, 0, &res);
+
+ return res.a0 ? -EACCES : 0;
+}
+
+static int imx_sc_wdt_set_pretimeout(struct watchdog_device *wdog,
+ unsigned int pretimeout)
+{
+ struct arm_smccc_res res;
+
+ /*
+ * SCU firmware calculates pretimeout based on current time
+ * stamp instead of watchdog timeout stamp, need to convert
+ * the pretimeout to SCU firmware's timeout value.
+ */
+ arm_smccc_smc(IMX_SIP_TIMER, IMX_SIP_TIMER_SET_PRETIME_WDOG,
+ (wdog->timeout - pretimeout) * 1000, 0, 0, 0,
+ 0, 0, &res);
+ if (res.a0)
+ return -EACCES;
+
+ wdog->pretimeout = pretimeout;
+
+ return 0;
+}
+
+static int imx_sc_wdt_notify(struct notifier_block *nb,
+ unsigned long event, void *group)
+{
+ struct imx_sc_wdt_device *imx_sc_wdd =
+ container_of(nb,
+ struct imx_sc_wdt_device,
+ wdt_notifier);
+
+ if (event & SC_IRQ_WDOG &&
+ *(u8 *)group == SC_IRQ_GROUP_WDOG)
+ watchdog_notify_pretimeout(&imx_sc_wdd->wdd);
+
+ return 0;
+}
+
+static void imx_sc_wdt_action(void *data)
+{
+ struct notifier_block *wdt_notifier = data;
+
+ imx_scu_irq_unregister_notifier(wdt_notifier);
+ imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
+ SC_IRQ_WDOG,
+ false);
+}
+
+static const struct watchdog_ops imx_sc_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = imx_sc_wdt_start,
+ .stop = imx_sc_wdt_stop,
+ .ping = imx_sc_wdt_ping,
+ .set_timeout = imx_sc_wdt_set_timeout,
+ .set_pretimeout = imx_sc_wdt_set_pretimeout,
+};
+
+static struct watchdog_info imx_sc_wdt_info = {
+ .identity = "i.MX SC watchdog timer",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static int imx_sc_wdt_probe(struct platform_device *pdev)
+{
+ struct imx_sc_wdt_device *imx_sc_wdd;
+ struct watchdog_device *wdog;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ imx_sc_wdd = devm_kzalloc(dev, sizeof(*imx_sc_wdd), GFP_KERNEL);
+ if (!imx_sc_wdd)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, imx_sc_wdd);
+
+ wdog = &imx_sc_wdd->wdd;
+ wdog->info = &imx_sc_wdt_info;
+ wdog->ops = &imx_sc_wdt_ops;
+ wdog->min_timeout = 1;
+ wdog->max_timeout = MAX_TIMEOUT;
+ wdog->parent = dev;
+ wdog->timeout = DEFAULT_TIMEOUT;
+
+ watchdog_init_timeout(wdog, 0, dev);
+
+ ret = imx_sc_wdt_set_timeout(wdog, wdog->timeout);
+ if (ret)
+ return ret;
+
+ watchdog_stop_on_reboot(wdog);
+ watchdog_stop_on_unregister(wdog);
+
+ ret = imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
+ SC_IRQ_WDOG,
+ true);
+ if (ret) {
+ dev_warn(dev, "Enable irq failed, pretimeout NOT supported\n");
+ goto register_device;
+ }
+
+ imx_sc_wdd->wdt_notifier.notifier_call = imx_sc_wdt_notify;
+ ret = imx_scu_irq_register_notifier(&imx_sc_wdd->wdt_notifier);
+ if (ret) {
+ imx_scu_irq_group_enable(SC_IRQ_GROUP_WDOG,
+ SC_IRQ_WDOG,
+ false);
+ dev_warn(dev,
+ "Register irq notifier failed, pretimeout NOT supported\n");
+ goto register_device;
+ }
+
+ ret = devm_add_action_or_reset(dev, imx_sc_wdt_action,
+ &imx_sc_wdd->wdt_notifier);
+ if (!ret)
+ imx_sc_wdt_info.options |= WDIOF_PRETIMEOUT;
+ else
+ dev_warn(dev, "Add action failed, pretimeout NOT supported\n");
+
+register_device:
+ return devm_watchdog_register_device(dev, wdog);
+}
+
+static int __maybe_unused imx_sc_wdt_suspend(struct device *dev)
+{
+ struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev);
+
+ if (watchdog_active(&imx_sc_wdd->wdd))
+ imx_sc_wdt_stop(&imx_sc_wdd->wdd);
+
+ return 0;
+}
+
+static int __maybe_unused imx_sc_wdt_resume(struct device *dev)
+{
+ struct imx_sc_wdt_device *imx_sc_wdd = dev_get_drvdata(dev);
+
+ if (watchdog_active(&imx_sc_wdd->wdd))
+ imx_sc_wdt_start(&imx_sc_wdd->wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(imx_sc_wdt_pm_ops,
+ imx_sc_wdt_suspend, imx_sc_wdt_resume);
+
+static const struct of_device_id imx_sc_wdt_dt_ids[] = {
+ { .compatible = "fsl,imx-sc-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sc_wdt_dt_ids);
+
+static struct platform_driver imx_sc_wdt_driver = {
+ .probe = imx_sc_wdt_probe,
+ .driver = {
+ .name = "imx-sc-wdt",
+ .of_match_table = imx_sc_wdt_dt_ids,
+ .pm = &imx_sc_wdt_pm_ops,
+ },
+};
+module_platform_driver(imx_sc_wdt_driver);
+
+MODULE_AUTHOR("Robin Gong <yibin.gong@nxp.com>");
+MODULE_DESCRIPTION("NXP i.MX system controller watchdog driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/indydog.c b/drivers/watchdog/indydog.c
new file mode 100644
index 000000000..9857bb74a
--- /dev/null
+++ b/drivers/watchdog/indydog.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22
+ *
+ * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>,
+ * All Rights Reserved.
+ *
+ * based on softdog.c by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <asm/sgi/mc.h>
+
+static unsigned long indydog_alive;
+static DEFINE_SPINLOCK(indydog_lock);
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default 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) ")");
+
+static void indydog_start(void)
+{
+ spin_lock(&indydog_lock);
+ sgimc->cpuctrl0 |= SGIMC_CCTRL0_WDOG;
+ spin_unlock(&indydog_lock);
+}
+
+static void indydog_stop(void)
+{
+ spin_lock(&indydog_lock);
+ sgimc->cpuctrl0 &= ~SGIMC_CCTRL0_WDOG;
+ spin_unlock(&indydog_lock);
+
+ pr_info("Stopped watchdog timer\n");
+}
+
+static void indydog_ping(void)
+{
+ sgimc->watchdogt = 0;
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int indydog_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &indydog_alive))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate timer */
+ indydog_start();
+ indydog_ping();
+
+ pr_info("Started watchdog timer\n");
+
+ return stream_open(inode, file);
+}
+
+static int indydog_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer.
+ * Lock it in if it's a module and we defined ...NOWAYOUT */
+ if (!nowayout)
+ indydog_stop(); /* Turn the WDT off */
+ clear_bit(0, &indydog_alive);
+ return 0;
+}
+
+static ssize_t indydog_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ /* Refresh the timer. */
+ if (len)
+ indydog_ping();
+ return len;
+}
+
+static long indydog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int options, retval = -EINVAL;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING,
+ .firmware_version = 0,
+ .identity = "Hardware Watchdog for SGI IP22",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((struct watchdog_info *)arg,
+ &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, (int *)arg);
+ case WDIOC_SETOPTIONS:
+ {
+ if (get_user(options, (int *)arg))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ indydog_stop();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ indydog_start();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ indydog_ping();
+ return 0;
+ case WDIOC_GETTIMEOUT:
+ return put_user(WATCHDOG_TIMEOUT, (int *)arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int indydog_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ indydog_stop(); /* Turn the WDT off */
+
+ return NOTIFY_DONE;
+}
+
+static const struct file_operations indydog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = indydog_write,
+ .unlocked_ioctl = indydog_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = indydog_open,
+ .release = indydog_release,
+};
+
+static struct miscdevice indydog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &indydog_fops,
+};
+
+static struct notifier_block indydog_notifier = {
+ .notifier_call = indydog_notify_sys,
+};
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ ret = register_reboot_notifier(&indydog_notifier);
+ if (ret) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ return ret;
+ }
+
+ ret = misc_register(&indydog_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ unregister_reboot_notifier(&indydog_notifier);
+ return ret;
+ }
+
+ pr_info("Hardware Watchdog Timer for SGI IP22: 0.3\n");
+
+ return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+ misc_deregister(&indydog_miscdev);
+ unregister_reboot_notifier(&indydog_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>");
+MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/intel-mid_wdt.c b/drivers/watchdog/intel-mid_wdt.c
new file mode 100644
index 000000000..fb7fae750
--- /dev/null
+++ b/drivers/watchdog/intel-mid_wdt.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * intel-mid_wdt: generic Intel MID SCU watchdog driver
+ *
+ * Platforms supported so far:
+ * - Merrifield only
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ * Contact: David Cohen <david.a.cohen@linux.intel.com>
+ */
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/nmi.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/platform_data/intel-mid_wdt.h>
+
+#include <asm/intel_scu_ipc.h>
+#include <asm/intel-mid.h>
+
+#define IPC_WATCHDOG 0xf8
+
+#define MID_WDT_PRETIMEOUT 15
+#define MID_WDT_TIMEOUT_MIN (1 + MID_WDT_PRETIMEOUT)
+#define MID_WDT_TIMEOUT_MAX 170
+#define MID_WDT_DEFAULT_TIMEOUT 90
+
+/* SCU watchdog messages */
+enum {
+ SCU_WATCHDOG_START = 0,
+ SCU_WATCHDOG_STOP,
+ SCU_WATCHDOG_KEEPALIVE,
+};
+
+struct mid_wdt {
+ struct watchdog_device wd;
+ struct device *dev;
+ struct intel_scu_ipc_dev *scu;
+};
+
+static inline int
+wdt_command(struct mid_wdt *mid, int sub, const void *in, size_t inlen, size_t size)
+{
+ struct intel_scu_ipc_dev *scu = mid->scu;
+
+ return intel_scu_ipc_dev_command_with_size(scu, IPC_WATCHDOG, sub, in,
+ inlen, size, NULL, 0);
+}
+
+static int wdt_start(struct watchdog_device *wd)
+{
+ struct mid_wdt *mid = watchdog_get_drvdata(wd);
+ int ret, in_size;
+ int timeout = wd->timeout;
+ struct ipc_wd_start {
+ u32 pretimeout;
+ u32 timeout;
+ } ipc_wd_start = { timeout - MID_WDT_PRETIMEOUT, timeout };
+
+ /*
+ * SCU expects the input size for watchdog IPC to be 2 which is the
+ * size of the structure in dwords. SCU IPC normally takes bytes
+ * but this is a special case where we specify size to be different
+ * than inlen.
+ */
+ in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4);
+
+ ret = wdt_command(mid, SCU_WATCHDOG_START, &ipc_wd_start,
+ sizeof(ipc_wd_start), in_size);
+ if (ret)
+ dev_crit(mid->dev, "error starting watchdog: %d\n", ret);
+
+ return ret;
+}
+
+static int wdt_ping(struct watchdog_device *wd)
+{
+ struct mid_wdt *mid = watchdog_get_drvdata(wd);
+ int ret;
+
+ ret = wdt_command(mid, SCU_WATCHDOG_KEEPALIVE, NULL, 0, 0);
+ if (ret)
+ dev_crit(mid->dev, "Error executing keepalive: %d\n", ret);
+
+ return ret;
+}
+
+static int wdt_stop(struct watchdog_device *wd)
+{
+ struct mid_wdt *mid = watchdog_get_drvdata(wd);
+ int ret;
+
+ ret = wdt_command(mid, SCU_WATCHDOG_STOP, NULL, 0, 0);
+ if (ret)
+ dev_crit(mid->dev, "Error stopping watchdog: %d\n", ret);
+
+ return ret;
+}
+
+static irqreturn_t mid_wdt_irq(int irq, void *dev_id)
+{
+ panic("Kernel Watchdog");
+
+ /* This code should not be reached */
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_info mid_wdt_info = {
+ .identity = "Intel MID SCU watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops mid_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .ping = wdt_ping,
+};
+
+static int mid_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdt_dev;
+ struct intel_mid_wdt_pdata *pdata = dev->platform_data;
+ struct mid_wdt *mid;
+ int ret;
+
+ if (!pdata) {
+ dev_err(dev, "missing platform data\n");
+ return -EINVAL;
+ }
+
+ if (pdata->probe) {
+ ret = pdata->probe(pdev);
+ if (ret)
+ return ret;
+ }
+
+ mid = devm_kzalloc(dev, sizeof(*mid), GFP_KERNEL);
+ if (!mid)
+ return -ENOMEM;
+
+ mid->dev = dev;
+ wdt_dev = &mid->wd;
+
+ wdt_dev->info = &mid_wdt_info;
+ wdt_dev->ops = &mid_wdt_ops;
+ wdt_dev->min_timeout = MID_WDT_TIMEOUT_MIN;
+ wdt_dev->max_timeout = MID_WDT_TIMEOUT_MAX;
+ wdt_dev->timeout = MID_WDT_DEFAULT_TIMEOUT;
+ wdt_dev->parent = dev;
+
+ watchdog_set_nowayout(wdt_dev, WATCHDOG_NOWAYOUT);
+ watchdog_set_drvdata(wdt_dev, mid);
+
+ mid->scu = devm_intel_scu_ipc_dev_get(dev);
+ if (!mid->scu)
+ return -EPROBE_DEFER;
+
+ ret = devm_request_irq(dev, pdata->irq, mid_wdt_irq,
+ IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog",
+ wdt_dev);
+ if (ret) {
+ dev_err(dev, "error requesting warning irq %d\n", pdata->irq);
+ return ret;
+ }
+
+ /*
+ * The firmware followed by U-Boot leaves the watchdog running
+ * with the default threshold which may vary. When we get here
+ * we should make a decision to prevent any side effects before
+ * user space daemon will take care of it. The best option,
+ * taking into consideration that there is no way to read values
+ * back from hardware, is to enforce watchdog being run with
+ * deterministic values.
+ */
+ ret = wdt_start(wdt_dev);
+ if (ret)
+ return ret;
+
+ /* Make sure the watchdog is serviced */
+ set_bit(WDOG_HW_RUNNING, &wdt_dev->status);
+
+ ret = devm_watchdog_register_device(dev, wdt_dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "Intel MID watchdog device probed\n");
+
+ return 0;
+}
+
+static struct platform_driver mid_wdt_driver = {
+ .probe = mid_wdt_probe,
+ .driver = {
+ .name = "intel_mid_wdt",
+ },
+};
+
+module_platform_driver(mid_wdt_driver);
+
+MODULE_AUTHOR("David Cohen <david.a.cohen@linux.intel.com>");
+MODULE_DESCRIPTION("Watchdog Driver for Intel MID platform");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:intel_mid_wdt");
diff --git a/drivers/watchdog/it8712f_wdt.c b/drivers/watchdog/it8712f_wdt.c
new file mode 100644
index 000000000..3ce6a58bd
--- /dev/null
+++ b/drivers/watchdog/it8712f_wdt.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IT8712F "Smart Guardian" Watchdog support
+ *
+ * Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net>
+ *
+ * Based on info and code taken from:
+ *
+ * drivers/char/watchdog/scx200_wdt.c
+ * drivers/hwmon/it87.c
+ * IT8712F EC-LPC I/O Preliminary Specification 0.8.2
+ * IT8712F EC-LPC I/O Preliminary Specification 0.9.3
+ *
+ * The author(s) of this software shall not be held liable for damages
+ * of any nature resulting due to the use of this software. This
+ * software is provided AS-IS with no warranties.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+
+#define NAME "it8712f_wdt"
+
+MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>");
+MODULE_DESCRIPTION("IT8712F Watchdog Driver");
+MODULE_LICENSE("GPL");
+
+static int max_units = 255;
+static int margin = 60; /* in seconds */
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static unsigned long wdt_open;
+static unsigned expect_close;
+static unsigned char revision;
+
+/* Dog Food address - We use the game port address */
+static unsigned short address;
+
+#define REG 0x2e /* The register to read/write */
+#define VAL 0x2f /* The value to read/write */
+
+#define LDN 0x07 /* Register: Logical device select */
+#define DEVID 0x20 /* Register: Device ID */
+#define DEVREV 0x22 /* Register: Device Revision */
+#define ACT_REG 0x30 /* LDN Register: Activation */
+#define BASE_REG 0x60 /* LDN Register: Base address */
+
+#define IT8712F_DEVID 0x8712
+
+#define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */
+#define LDN_GAME 0x09 /* Game Port */
+
+#define WDT_CONTROL 0x71 /* WDT Register: Control */
+#define WDT_CONFIG 0x72 /* WDT Register: Configuration */
+#define WDT_TIMEOUT 0x73 /* WDT Register: Timeout Value */
+
+#define WDT_RESET_GAME 0x10 /* Reset timer on read or write to game port */
+#define WDT_RESET_KBD 0x20 /* Reset timer on keyboard interrupt */
+#define WDT_RESET_MOUSE 0x40 /* Reset timer on mouse interrupt */
+#define WDT_RESET_CIR 0x80 /* Reset timer on consumer IR interrupt */
+
+#define WDT_UNIT_SEC 0x80 /* If 0 in MINUTES */
+
+#define WDT_OUT_PWROK 0x10 /* Pulse PWROK on timeout */
+#define WDT_OUT_KRST 0x40 /* Pulse reset on timeout */
+
+static int wdt_control_reg = WDT_RESET_GAME;
+module_param(wdt_control_reg, int, 0);
+MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control "
+ "register. The default WDT_RESET_GAME resets the timer on "
+ "game port reads that this driver generates. You can also "
+ "use KBD, MOUSE or CIR if you have some external way to "
+ "generate those interrupts.");
+
+static int superio_inb(int reg)
+{
+ outb(reg, REG);
+ return inb(VAL);
+}
+
+static void superio_outb(int val, int reg)
+{
+ outb(reg, REG);
+ outb(val, VAL);
+}
+
+static int superio_inw(int reg)
+{
+ int val;
+ outb(reg++, REG);
+ val = inb(VAL) << 8;
+ outb(reg, REG);
+ val |= inb(VAL);
+ return val;
+}
+
+static inline void superio_select(int ldn)
+{
+ outb(LDN, REG);
+ outb(ldn, VAL);
+}
+
+static inline int superio_enter(void)
+{
+ /*
+ * Try to reserve REG and REG + 1 for exclusive access.
+ */
+ if (!request_muxed_region(REG, 2, NAME))
+ return -EBUSY;
+
+ outb(0x87, REG);
+ outb(0x01, REG);
+ outb(0x55, REG);
+ outb(0x55, REG);
+ return 0;
+}
+
+static inline void superio_exit(void)
+{
+ outb(0x02, REG);
+ outb(0x02, VAL);
+ release_region(REG, 2);
+}
+
+static inline void it8712f_wdt_ping(void)
+{
+ if (wdt_control_reg & WDT_RESET_GAME)
+ inb(address);
+}
+
+static void it8712f_wdt_update_margin(void)
+{
+ int config = WDT_OUT_KRST | WDT_OUT_PWROK;
+ int units = margin;
+
+ /* Switch to minutes precision if the configured margin
+ * value does not fit within the register width.
+ */
+ if (units <= max_units) {
+ config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */
+ pr_info("timer margin %d seconds\n", units);
+ } else {
+ units /= 60;
+ pr_info("timer margin %d minutes\n", units);
+ }
+ superio_outb(config, WDT_CONFIG);
+
+ if (revision >= 0x08)
+ superio_outb(units >> 8, WDT_TIMEOUT + 1);
+ superio_outb(units, WDT_TIMEOUT);
+}
+
+static int it8712f_wdt_get_status(void)
+{
+ if (superio_inb(WDT_CONTROL) & 0x01)
+ return WDIOF_CARDRESET;
+ else
+ return 0;
+}
+
+static int it8712f_wdt_enable(void)
+{
+ int ret = superio_enter();
+ if (ret)
+ return ret;
+
+ pr_debug("enabling watchdog timer\n");
+ superio_select(LDN_GPIO);
+
+ superio_outb(wdt_control_reg, WDT_CONTROL);
+
+ it8712f_wdt_update_margin();
+
+ superio_exit();
+
+ it8712f_wdt_ping();
+
+ return 0;
+}
+
+static int it8712f_wdt_disable(void)
+{
+ int ret = superio_enter();
+ if (ret)
+ return ret;
+
+ pr_debug("disabling watchdog timer\n");
+ superio_select(LDN_GPIO);
+
+ superio_outb(0, WDT_CONFIG);
+ superio_outb(0, WDT_CONTROL);
+ if (revision >= 0x08)
+ superio_outb(0, WDT_TIMEOUT + 1);
+ superio_outb(0, WDT_TIMEOUT);
+
+ superio_exit();
+ return 0;
+}
+
+static int it8712f_wdt_notify(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_HALT || code == SYS_POWER_OFF)
+ if (!nowayout)
+ it8712f_wdt_disable();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block it8712f_wdt_notifier = {
+ .notifier_call = it8712f_wdt_notify,
+};
+
+static ssize_t it8712f_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* check for a magic close character */
+ if (len) {
+ size_t i;
+
+ it8712f_wdt_ping();
+
+ expect_close = 0;
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ return len;
+}
+
+static long it8712f_wdt_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 = {
+ .identity = "IT8712F Watchdog",
+ .firmware_version = 1,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ };
+ int value;
+ int ret;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ ret = superio_enter();
+ if (ret)
+ return ret;
+ superio_select(LDN_GPIO);
+
+ value = it8712f_wdt_get_status();
+
+ superio_exit();
+
+ return put_user(value, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ it8712f_wdt_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(value, p))
+ return -EFAULT;
+ if (value < 1)
+ return -EINVAL;
+ if (value > (max_units * 60))
+ return -EINVAL;
+ margin = value;
+ ret = superio_enter();
+ if (ret)
+ return ret;
+ superio_select(LDN_GPIO);
+
+ it8712f_wdt_update_margin();
+
+ superio_exit();
+ it8712f_wdt_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ if (put_user(margin, p))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int it8712f_wdt_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ /* only allow one at a time */
+ if (test_and_set_bit(0, &wdt_open))
+ return -EBUSY;
+
+ ret = it8712f_wdt_enable();
+ if (ret)
+ return ret;
+ return stream_open(inode, file);
+}
+
+static int it8712f_wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close != 42) {
+ pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
+ } else if (!nowayout) {
+ if (it8712f_wdt_disable())
+ pr_warn("Watchdog disable failed\n");
+ }
+ expect_close = 0;
+ clear_bit(0, &wdt_open);
+
+ return 0;
+}
+
+static const struct file_operations it8712f_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = it8712f_wdt_write,
+ .unlocked_ioctl = it8712f_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = it8712f_wdt_open,
+ .release = it8712f_wdt_release,
+};
+
+static struct miscdevice it8712f_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &it8712f_wdt_fops,
+};
+
+static int __init it8712f_wdt_find(unsigned short *address)
+{
+ int err = -ENODEV;
+ int chip_type;
+ int ret = superio_enter();
+ if (ret)
+ return ret;
+
+ chip_type = superio_inw(DEVID);
+ if (chip_type != IT8712F_DEVID)
+ goto exit;
+
+ superio_select(LDN_GAME);
+ superio_outb(1, ACT_REG);
+ if (!(superio_inb(ACT_REG) & 0x01)) {
+ pr_err("Device not activated, skipping\n");
+ goto exit;
+ }
+
+ *address = superio_inw(BASE_REG);
+ if (*address == 0) {
+ pr_err("Base address not set, skipping\n");
+ goto exit;
+ }
+
+ err = 0;
+ revision = superio_inb(DEVREV) & 0x0f;
+
+ /* Later revisions have 16-bit values per datasheet 0.9.1 */
+ if (revision >= 0x08)
+ max_units = 65535;
+
+ if (margin > (max_units * 60))
+ margin = (max_units * 60);
+
+ pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n",
+ chip_type, revision, *address);
+
+exit:
+ superio_exit();
+ return err;
+}
+
+static int __init it8712f_wdt_init(void)
+{
+ int err = 0;
+
+ if (it8712f_wdt_find(&address))
+ return -ENODEV;
+
+ if (!request_region(address, 1, "IT8712F Watchdog")) {
+ pr_warn("watchdog I/O region busy\n");
+ return -EBUSY;
+ }
+
+ err = it8712f_wdt_disable();
+ if (err) {
+ pr_err("unable to disable watchdog timer\n");
+ goto out;
+ }
+
+ err = register_reboot_notifier(&it8712f_wdt_notifier);
+ if (err) {
+ pr_err("unable to register reboot notifier\n");
+ goto out;
+ }
+
+ err = misc_register(&it8712f_wdt_miscdev);
+ if (err) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, err);
+ goto reboot_out;
+ }
+
+ return 0;
+
+
+reboot_out:
+ unregister_reboot_notifier(&it8712f_wdt_notifier);
+out:
+ release_region(address, 1);
+ return err;
+}
+
+static void __exit it8712f_wdt_exit(void)
+{
+ misc_deregister(&it8712f_wdt_miscdev);
+ unregister_reboot_notifier(&it8712f_wdt_notifier);
+ release_region(address, 1);
+}
+
+module_init(it8712f_wdt_init);
+module_exit(it8712f_wdt_exit);
diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c
new file mode 100644
index 000000000..bb1122909
--- /dev/null
+++ b/drivers/watchdog/it87_wdt.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Watchdog Timer Driver
+ * for ITE IT87xx Environment Control - Low Pin Count Input / Output
+ *
+ * (c) Copyright 2007 Oliver Schuster <olivers137@aol.com>
+ *
+ * Based on softdog.c by Alan Cox,
+ * 83977f_wdt.c by Jose Goncalves,
+ * it87.c by Chris Gauthron, Jean Delvare
+ *
+ * Data-sheets: Publicly available at the ITE website
+ * http://www.ite.com.tw/
+ *
+ * Support of the watchdog timers, which are available on
+ * IT8607, IT8620, IT8622, IT8625, IT8628, IT8655, IT8665, IT8686,
+ * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721, IT8726, IT8728,
+ * IT8772, IT8783 and IT8784.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define WATCHDOG_NAME "IT87 WDT"
+
+/* Defaults for Module Parameter */
+#define DEFAULT_TIMEOUT 60
+#define DEFAULT_TESTMODE 0
+#define DEFAULT_NOWAYOUT WATCHDOG_NOWAYOUT
+
+/* IO Ports */
+#define REG 0x2e
+#define VAL 0x2f
+
+/* Logical device Numbers LDN */
+#define GPIO 0x07
+
+/* Configuration Registers and Functions */
+#define LDNREG 0x07
+#define CHIPID 0x20
+#define CHIPREV 0x22
+
+/* Chip Id numbers */
+#define NO_DEV_ID 0xffff
+#define IT8607_ID 0x8607
+#define IT8620_ID 0x8620
+#define IT8622_ID 0x8622
+#define IT8625_ID 0x8625
+#define IT8628_ID 0x8628
+#define IT8655_ID 0x8655
+#define IT8665_ID 0x8665
+#define IT8686_ID 0x8686
+#define IT8702_ID 0x8702
+#define IT8705_ID 0x8705
+#define IT8712_ID 0x8712
+#define IT8716_ID 0x8716
+#define IT8718_ID 0x8718
+#define IT8720_ID 0x8720
+#define IT8721_ID 0x8721
+#define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */
+#define IT8728_ID 0x8728
+#define IT8772_ID 0x8772
+#define IT8783_ID 0x8783
+#define IT8784_ID 0x8784
+#define IT8786_ID 0x8786
+
+/* GPIO Configuration Registers LDN=0x07 */
+#define WDTCTRL 0x71
+#define WDTCFG 0x72
+#define WDTVALLSB 0x73
+#define WDTVALMSB 0x74
+
+/* GPIO Bits WDTCFG */
+#define WDT_TOV1 0x80
+#define WDT_KRST 0x40
+#define WDT_TOVE 0x20
+#define WDT_PWROK 0x10 /* not in it8721 */
+#define WDT_INT_MASK 0x0f
+
+static unsigned int max_units, chip_type;
+
+static unsigned int timeout = DEFAULT_TIMEOUT;
+static int testmode = DEFAULT_TESTMODE;
+static bool nowayout = DEFAULT_NOWAYOUT;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, default="
+ __MODULE_STRING(DEFAULT_TIMEOUT));
+module_param(testmode, int, 0);
+MODULE_PARM_DESC(testmode, "Watchdog test mode (1 = no reboot), default="
+ __MODULE_STRING(DEFAULT_TESTMODE));
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started, default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT));
+
+/* Superio Chip */
+
+static inline int superio_enter(void)
+{
+ /*
+ * Try to reserve REG and REG + 1 for exclusive access.
+ */
+ if (!request_muxed_region(REG, 2, WATCHDOG_NAME))
+ return -EBUSY;
+
+ outb(0x87, REG);
+ outb(0x01, REG);
+ outb(0x55, REG);
+ outb(0x55, REG);
+ return 0;
+}
+
+static inline void superio_exit(void)
+{
+ outb(0x02, REG);
+ outb(0x02, VAL);
+ release_region(REG, 2);
+}
+
+static inline void superio_select(int ldn)
+{
+ outb(LDNREG, REG);
+ outb(ldn, VAL);
+}
+
+static inline int superio_inb(int reg)
+{
+ outb(reg, REG);
+ return inb(VAL);
+}
+
+static inline void superio_outb(int val, int reg)
+{
+ outb(reg, REG);
+ outb(val, VAL);
+}
+
+static inline int superio_inw(int reg)
+{
+ int val;
+ outb(reg++, REG);
+ val = inb(VAL) << 8;
+ outb(reg, REG);
+ val |= inb(VAL);
+ return val;
+}
+
+/* Internal function, should be called after superio_select(GPIO) */
+static void _wdt_update_timeout(unsigned int t)
+{
+ unsigned char cfg = WDT_KRST;
+
+ if (testmode)
+ cfg = 0;
+
+ if (t <= max_units)
+ cfg |= WDT_TOV1;
+ else
+ t /= 60;
+
+ if (chip_type != IT8721_ID)
+ cfg |= WDT_PWROK;
+
+ superio_outb(cfg, WDTCFG);
+ superio_outb(t, WDTVALLSB);
+ if (max_units > 255)
+ superio_outb(t >> 8, WDTVALMSB);
+}
+
+static int wdt_update_timeout(unsigned int t)
+{
+ int ret;
+
+ ret = superio_enter();
+ if (ret)
+ return ret;
+
+ superio_select(GPIO);
+ _wdt_update_timeout(t);
+ superio_exit();
+
+ return 0;
+}
+
+static int wdt_round_time(int t)
+{
+ t += 59;
+ t -= t % 60;
+ return t;
+}
+
+/* watchdog timer handling */
+
+static int wdt_start(struct watchdog_device *wdd)
+{
+ return wdt_update_timeout(wdd->timeout);
+}
+
+static int wdt_stop(struct watchdog_device *wdd)
+{
+ return wdt_update_timeout(0);
+}
+
+/**
+ * wdt_set_timeout - set a new timeout value with watchdog ioctl
+ * @t: timeout value in seconds
+ *
+ * The hardware device has a 8 or 16 bit watchdog timer (depends on
+ * chip version) that can be configured to count seconds or minutes.
+ *
+ * Used within WDIOC_SETTIMEOUT watchdog device ioctl.
+ */
+
+static int wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
+{
+ int ret = 0;
+
+ if (t > max_units)
+ t = wdt_round_time(t);
+
+ wdd->timeout = t;
+
+ if (watchdog_hw_running(wdd))
+ ret = wdt_update_timeout(t);
+
+ return ret;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+};
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .set_timeout = wdt_set_timeout,
+};
+
+static struct watchdog_device wdt_dev = {
+ .info = &ident,
+ .ops = &wdt_ops,
+ .min_timeout = 1,
+};
+
+static int __init it87_wdt_init(void)
+{
+ u8 chip_rev;
+ int rc;
+
+ rc = superio_enter();
+ if (rc)
+ return rc;
+
+ chip_type = superio_inw(CHIPID);
+ chip_rev = superio_inb(CHIPREV) & 0x0f;
+ superio_exit();
+
+ switch (chip_type) {
+ case IT8702_ID:
+ max_units = 255;
+ break;
+ case IT8712_ID:
+ max_units = (chip_rev < 8) ? 255 : 65535;
+ break;
+ case IT8716_ID:
+ case IT8726_ID:
+ max_units = 65535;
+ break;
+ case IT8607_ID:
+ case IT8620_ID:
+ case IT8622_ID:
+ case IT8625_ID:
+ case IT8628_ID:
+ case IT8655_ID:
+ case IT8665_ID:
+ case IT8686_ID:
+ case IT8718_ID:
+ case IT8720_ID:
+ case IT8721_ID:
+ case IT8728_ID:
+ case IT8772_ID:
+ case IT8783_ID:
+ case IT8784_ID:
+ case IT8786_ID:
+ max_units = 65535;
+ break;
+ case IT8705_ID:
+ pr_err("Unsupported Chip found, Chip %04x Revision %02x\n",
+ chip_type, chip_rev);
+ return -ENODEV;
+ case NO_DEV_ID:
+ pr_err("no device\n");
+ return -ENODEV;
+ default:
+ pr_err("Unknown Chip found, Chip %04x Revision %04x\n",
+ chip_type, chip_rev);
+ return -ENODEV;
+ }
+
+ rc = superio_enter();
+ if (rc)
+ return rc;
+
+ superio_select(GPIO);
+ superio_outb(WDT_TOV1, WDTCFG);
+ superio_outb(0x00, WDTCTRL);
+ superio_exit();
+
+ if (timeout < 1 || timeout > max_units * 60) {
+ timeout = DEFAULT_TIMEOUT;
+ pr_warn("Timeout value out of range, use default %d sec\n",
+ DEFAULT_TIMEOUT);
+ }
+
+ if (timeout > max_units)
+ timeout = wdt_round_time(timeout);
+
+ wdt_dev.timeout = timeout;
+ wdt_dev.max_timeout = max_units * 60;
+
+ watchdog_stop_on_reboot(&wdt_dev);
+ rc = watchdog_register_device(&wdt_dev);
+ if (rc) {
+ pr_err("Cannot register watchdog device (err=%d)\n", rc);
+ return rc;
+ }
+
+ pr_info("Chip IT%04x revision %d initialized. timeout=%d sec (nowayout=%d testmode=%d)\n",
+ chip_type, chip_rev, timeout, nowayout, testmode);
+
+ return 0;
+}
+
+static void __exit it87_wdt_exit(void)
+{
+ watchdog_unregister_device(&wdt_dev);
+}
+
+module_init(it87_wdt_init);
+module_exit(it87_wdt_exit);
+
+MODULE_AUTHOR("Oliver Schuster");
+MODULE_DESCRIPTION("Hardware Watchdog Device Driver for IT87xx EC-LPC I/O");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c
new file mode 100644
index 000000000..0fc91e9c4
--- /dev/null
+++ b/drivers/watchdog/ixp4xx_wdt.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/char/watchdog/ixp4xx_wdt.c
+ *
+ * Watchdog driver for Intel IXP4xx network processors
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/bits.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/soc/ixp4xx/cpu.h>
+
+struct ixp4xx_wdt {
+ struct watchdog_device wdd;
+ void __iomem *base;
+ unsigned long rate;
+};
+
+/* Fallback if we do not have a clock for this */
+#define IXP4XX_TIMER_FREQ 66666000
+
+/* Registers after the timer registers */
+#define IXP4XX_OSWT_OFFSET 0x14 /* Watchdog Timer */
+#define IXP4XX_OSWE_OFFSET 0x18 /* Watchdog Enable */
+#define IXP4XX_OSWK_OFFSET 0x1C /* Watchdog Key */
+#define IXP4XX_OSST_OFFSET 0x20 /* Timer Status */
+
+#define IXP4XX_OSST_TIMER_WDOG_PEND 0x00000008
+#define IXP4XX_OSST_TIMER_WARM_RESET 0x00000010
+#define IXP4XX_WDT_KEY 0x0000482E
+#define IXP4XX_WDT_RESET_ENABLE 0x00000001
+#define IXP4XX_WDT_IRQ_ENABLE 0x00000002
+#define IXP4XX_WDT_COUNT_ENABLE 0x00000004
+
+static inline
+struct ixp4xx_wdt *to_ixp4xx_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct ixp4xx_wdt, wdd);
+}
+
+static int ixp4xx_wdt_start(struct watchdog_device *wdd)
+{
+ struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd);
+
+ __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET);
+ __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET);
+ __raw_writel(wdd->timeout * iwdt->rate,
+ iwdt->base + IXP4XX_OSWT_OFFSET);
+ __raw_writel(IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE,
+ iwdt->base + IXP4XX_OSWE_OFFSET);
+ __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET);
+
+ return 0;
+}
+
+static int ixp4xx_wdt_stop(struct watchdog_device *wdd)
+{
+ struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd);
+
+ __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET);
+ __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET);
+ __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET);
+
+ return 0;
+}
+
+static int ixp4xx_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ if (watchdog_active(wdd))
+ ixp4xx_wdt_start(wdd);
+
+ return 0;
+}
+
+static int ixp4xx_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd);
+
+ __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET);
+ __raw_writel(0, iwdt->base + IXP4XX_OSWT_OFFSET);
+ __raw_writel(IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE,
+ iwdt->base + IXP4XX_OSWE_OFFSET);
+
+ return 0;
+}
+
+static const struct watchdog_ops ixp4xx_wdt_ops = {
+ .start = ixp4xx_wdt_start,
+ .stop = ixp4xx_wdt_stop,
+ .set_timeout = ixp4xx_wdt_set_timeout,
+ .restart = ixp4xx_wdt_restart,
+ .owner = THIS_MODULE,
+};
+
+/*
+ * The A0 version of the IXP422 had a bug in the watchdog making
+ * is useless, but we still need to use it to restart the system
+ * as it is the only way, so in this special case we register a
+ * "dummy" watchdog that doesn't really work, but will support
+ * the restart operation.
+ */
+static int ixp4xx_wdt_dummy(struct watchdog_device *wdd)
+{
+ return 0;
+}
+
+static const struct watchdog_ops ixp4xx_wdt_restart_only_ops = {
+ .start = ixp4xx_wdt_dummy,
+ .stop = ixp4xx_wdt_dummy,
+ .restart = ixp4xx_wdt_restart,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info ixp4xx_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT,
+ .identity = KBUILD_MODNAME,
+};
+
+/* Devres-handled clock disablement */
+static void ixp4xx_clock_action(void *d)
+{
+ clk_disable_unprepare(d);
+}
+
+static int ixp4xx_wdt_probe(struct platform_device *pdev)
+{
+ static const struct watchdog_ops *iwdt_ops;
+ struct device *dev = &pdev->dev;
+ struct ixp4xx_wdt *iwdt;
+ struct clk *clk;
+ int ret;
+
+ if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) {
+ dev_info(dev, "Rev. A0 IXP42x CPU detected - only restart supported\n");
+ iwdt_ops = &ixp4xx_wdt_restart_only_ops;
+ } else {
+ iwdt_ops = &ixp4xx_wdt_ops;
+ }
+
+ iwdt = devm_kzalloc(dev, sizeof(*iwdt), GFP_KERNEL);
+ if (!iwdt)
+ return -ENOMEM;
+ iwdt->base = (void __iomem *)dev->platform_data;
+
+ /*
+ * Retrieve rate from a fixed clock from the device tree if
+ * the parent has that, else use the default clock rate.
+ */
+ clk = devm_clk_get(dev->parent, NULL);
+ if (!IS_ERR(clk)) {
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, ixp4xx_clock_action, clk);
+ if (ret)
+ return ret;
+ iwdt->rate = clk_get_rate(clk);
+ }
+ if (!iwdt->rate)
+ iwdt->rate = IXP4XX_TIMER_FREQ;
+
+ iwdt->wdd.info = &ixp4xx_wdt_info;
+ iwdt->wdd.ops = iwdt_ops;
+ iwdt->wdd.min_timeout = 1;
+ iwdt->wdd.max_timeout = U32_MAX / iwdt->rate;
+ iwdt->wdd.parent = dev;
+ /* Default to 60 seconds */
+ iwdt->wdd.timeout = 60U;
+ watchdog_init_timeout(&iwdt->wdd, 0, dev);
+
+ if (__raw_readl(iwdt->base + IXP4XX_OSST_OFFSET) &
+ IXP4XX_OSST_TIMER_WARM_RESET)
+ iwdt->wdd.bootstatus = WDIOF_CARDRESET;
+
+ ret = devm_watchdog_register_device(dev, &iwdt->wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "IXP4xx watchdog available\n");
+
+ return 0;
+}
+
+static struct platform_driver ixp4xx_wdt_driver = {
+ .probe = ixp4xx_wdt_probe,
+ .driver = {
+ .name = "ixp4xx-watchdog",
+ },
+};
+module_platform_driver(ixp4xx_wdt_driver);
+
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
+MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c
new file mode 100644
index 000000000..395bde79e
--- /dev/null
+++ b/drivers/watchdog/jz4740_wdt.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net>
+ * JZ4740 Watchdog driver
+ */
+
+#include <linux/mfd/ingenic-tcu.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define DEFAULT_HEARTBEAT 5
+#define MAX_HEARTBEAT 2048
+
+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) ")");
+
+static unsigned int heartbeat = DEFAULT_HEARTBEAT;
+module_param(heartbeat, uint, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+struct jz4740_wdt_drvdata {
+ struct watchdog_device wdt;
+ struct regmap *map;
+ struct clk *clk;
+ unsigned long clk_rate;
+};
+
+static int jz4740_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
+
+ return 0;
+}
+
+static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout);
+ unsigned int tcer;
+
+ regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
+ regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
+
+ regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value);
+ regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0);
+
+ if (tcer & TCU_WDT_TCER_TCEN)
+ regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
+
+ wdt_dev->timeout = new_timeout;
+ return 0;
+}
+
+static int jz4740_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ unsigned int tcer;
+ int ret;
+
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret)
+ return ret;
+
+ regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer);
+
+ jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+
+ /* Start watchdog if it wasn't started already */
+ if (!(tcer & TCU_WDT_TCER_TCEN))
+ regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN);
+
+ return 0;
+}
+
+static int jz4740_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0);
+ clk_disable_unprepare(drvdata->clk);
+
+ return 0;
+}
+
+static int jz4740_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ wdt_dev->timeout = 0;
+ jz4740_wdt_start(wdt_dev);
+ return 0;
+}
+
+static const struct watchdog_info jz4740_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "jz4740 Watchdog",
+};
+
+static const struct watchdog_ops jz4740_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = jz4740_wdt_start,
+ .stop = jz4740_wdt_stop,
+ .ping = jz4740_wdt_ping,
+ .set_timeout = jz4740_wdt_set_timeout,
+ .restart = jz4740_wdt_restart,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id jz4740_wdt_of_matches[] = {
+ { .compatible = "ingenic,jz4740-watchdog", },
+ { .compatible = "ingenic,jz4780-watchdog", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches);
+#endif
+
+static int jz4740_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct jz4740_wdt_drvdata *drvdata;
+ struct watchdog_device *jz4740_wdt;
+ long rate;
+ int ret;
+
+ drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata),
+ GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->clk = devm_clk_get(&pdev->dev, "wdt");
+ if (IS_ERR(drvdata->clk)) {
+ dev_err(&pdev->dev, "cannot find WDT clock\n");
+ return PTR_ERR(drvdata->clk);
+ }
+
+ /* Set smallest clock possible */
+ rate = clk_round_rate(drvdata->clk, 1);
+ if (rate < 0)
+ return rate;
+
+ ret = clk_set_rate(drvdata->clk, rate);
+ if (ret)
+ return ret;
+
+ drvdata->clk_rate = rate;
+ jz4740_wdt = &drvdata->wdt;
+ jz4740_wdt->info = &jz4740_wdt_info;
+ jz4740_wdt->ops = &jz4740_wdt_ops;
+ jz4740_wdt->min_timeout = 1;
+ jz4740_wdt->max_timeout = 0xffff / rate;
+ jz4740_wdt->timeout = clamp(heartbeat,
+ jz4740_wdt->min_timeout,
+ jz4740_wdt->max_timeout);
+ jz4740_wdt->parent = dev;
+ watchdog_set_nowayout(jz4740_wdt, nowayout);
+ watchdog_set_drvdata(jz4740_wdt, drvdata);
+
+ drvdata->map = device_node_to_regmap(dev->parent->of_node);
+ if (IS_ERR(drvdata->map)) {
+ dev_err(dev, "regmap not found\n");
+ return PTR_ERR(drvdata->map);
+ }
+
+ return devm_watchdog_register_device(dev, &drvdata->wdt);
+}
+
+static struct platform_driver jz4740_wdt_driver = {
+ .probe = jz4740_wdt_probe,
+ .driver = {
+ .name = "jz4740-wdt",
+ .of_match_table = of_match_ptr(jz4740_wdt_of_matches),
+ },
+};
+
+module_platform_driver(jz4740_wdt_driver);
+
+MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
+MODULE_DESCRIPTION("jz4740 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-wdt");
diff --git a/drivers/watchdog/keembay_wdt.c b/drivers/watchdog/keembay_wdt.c
new file mode 100644
index 000000000..2a39114db
--- /dev/null
+++ b/drivers/watchdog/keembay_wdt.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Watchdog driver for Intel Keem Bay non-secure watchdog.
+ *
+ * Copyright (C) 2020 Intel Corporation
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+
+/* Non-secure watchdog register offsets */
+#define TIM_WATCHDOG 0x0
+#define TIM_WATCHDOG_INT_THRES 0x4
+#define TIM_WDOG_EN 0x8
+#define TIM_SAFE 0xc
+
+#define WDT_TH_INT_MASK BIT(8)
+#define WDT_TO_INT_MASK BIT(9)
+#define WDT_INT_CLEAR_SMC 0x8200ff18
+
+#define WDT_UNLOCK 0xf1d0dead
+#define WDT_DISABLE 0x0
+#define WDT_ENABLE 0x1
+
+#define WDT_LOAD_MAX U32_MAX
+#define WDT_LOAD_MIN 1
+
+#define WDT_TIMEOUT 5
+#define WDT_PRETIMEOUT 4
+
+static unsigned int timeout = WDT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout period in seconds (default = "
+ __MODULE_STRING(WDT_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default = "
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct keembay_wdt {
+ struct watchdog_device wdd;
+ struct clk *clk;
+ unsigned int rate;
+ int to_irq;
+ int th_irq;
+ void __iomem *base;
+};
+
+static inline u32 keembay_wdt_readl(struct keembay_wdt *wdt, u32 offset)
+{
+ return readl(wdt->base + offset);
+}
+
+static inline void keembay_wdt_writel(struct keembay_wdt *wdt, u32 offset, u32 val)
+{
+ writel(WDT_UNLOCK, wdt->base + TIM_SAFE);
+ writel(val, wdt->base + offset);
+}
+
+static void keembay_wdt_set_timeout_reg(struct watchdog_device *wdog)
+{
+ struct keembay_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ keembay_wdt_writel(wdt, TIM_WATCHDOG, wdog->timeout * wdt->rate);
+}
+
+static void keembay_wdt_set_pretimeout_reg(struct watchdog_device *wdog)
+{
+ struct keembay_wdt *wdt = watchdog_get_drvdata(wdog);
+ u32 th_val = 0;
+
+ if (wdog->pretimeout)
+ th_val = wdog->timeout - wdog->pretimeout;
+
+ keembay_wdt_writel(wdt, TIM_WATCHDOG_INT_THRES, th_val * wdt->rate);
+}
+
+static int keembay_wdt_start(struct watchdog_device *wdog)
+{
+ struct keembay_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ keembay_wdt_writel(wdt, TIM_WDOG_EN, WDT_ENABLE);
+
+ return 0;
+}
+
+static int keembay_wdt_stop(struct watchdog_device *wdog)
+{
+ struct keembay_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ keembay_wdt_writel(wdt, TIM_WDOG_EN, WDT_DISABLE);
+
+ return 0;
+}
+
+static int keembay_wdt_ping(struct watchdog_device *wdog)
+{
+ keembay_wdt_set_timeout_reg(wdog);
+
+ return 0;
+}
+
+static int keembay_wdt_set_timeout(struct watchdog_device *wdog, u32 t)
+{
+ wdog->timeout = t;
+ keembay_wdt_set_timeout_reg(wdog);
+ keembay_wdt_set_pretimeout_reg(wdog);
+
+ return 0;
+}
+
+static int keembay_wdt_set_pretimeout(struct watchdog_device *wdog, u32 t)
+{
+ if (t > wdog->timeout)
+ return -EINVAL;
+
+ wdog->pretimeout = t;
+ keembay_wdt_set_pretimeout_reg(wdog);
+
+ return 0;
+}
+
+static unsigned int keembay_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct keembay_wdt *wdt = watchdog_get_drvdata(wdog);
+
+ return keembay_wdt_readl(wdt, TIM_WATCHDOG) / wdt->rate;
+}
+
+/*
+ * SMC call is used to clear the interrupt bits, because the TIM_GEN_CONFIG
+ * register is in the secure bank.
+ */
+static irqreturn_t keembay_wdt_to_isr(int irq, void *dev_id)
+{
+ struct keembay_wdt *wdt = dev_id;
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(WDT_INT_CLEAR_SMC, WDT_TO_INT_MASK, 0, 0, 0, 0, 0, 0, &res);
+ dev_crit(wdt->wdd.parent, "Intel Keem Bay non-secure wdt timeout.\n");
+ emergency_restart();
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t keembay_wdt_th_isr(int irq, void *dev_id)
+{
+ struct keembay_wdt *wdt = dev_id;
+ struct arm_smccc_res res;
+
+ keembay_wdt_set_pretimeout(&wdt->wdd, 0x0);
+
+ arm_smccc_smc(WDT_INT_CLEAR_SMC, WDT_TH_INT_MASK, 0, 0, 0, 0, 0, 0, &res);
+ dev_crit(wdt->wdd.parent, "Intel Keem Bay non-secure wdt pre-timeout.\n");
+ watchdog_notify_pretimeout(&wdt->wdd);
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_info keembay_wdt_info = {
+ .identity = "Intel Keem Bay Watchdog Timer",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_PRETIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+};
+
+static const struct watchdog_ops keembay_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = keembay_wdt_start,
+ .stop = keembay_wdt_stop,
+ .ping = keembay_wdt_ping,
+ .set_timeout = keembay_wdt_set_timeout,
+ .set_pretimeout = keembay_wdt_set_pretimeout,
+ .get_timeleft = keembay_wdt_get_timeleft,
+};
+
+static int keembay_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct keembay_wdt *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ /* we do not need to enable the clock as it is enabled by default */
+ wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return dev_err_probe(dev, PTR_ERR(wdt->clk), "Failed to get clock\n");
+
+ wdt->rate = clk_get_rate(wdt->clk);
+ if (!wdt->rate)
+ return dev_err_probe(dev, -EINVAL, "Failed to get clock rate\n");
+
+ wdt->th_irq = platform_get_irq_byname(pdev, "threshold");
+ if (wdt->th_irq < 0)
+ return dev_err_probe(dev, wdt->th_irq, "Failed to get IRQ for threshold\n");
+
+ ret = devm_request_irq(dev, wdt->th_irq, keembay_wdt_th_isr, 0,
+ "keembay-wdt", wdt);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ for threshold\n");
+
+ wdt->to_irq = platform_get_irq_byname(pdev, "timeout");
+ if (wdt->to_irq < 0)
+ return dev_err_probe(dev, wdt->to_irq, "Failed to get IRQ for timeout\n");
+
+ ret = devm_request_irq(dev, wdt->to_irq, keembay_wdt_to_isr, 0,
+ "keembay-wdt", wdt);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ for timeout\n");
+
+ wdt->wdd.parent = dev;
+ wdt->wdd.info = &keembay_wdt_info;
+ wdt->wdd.ops = &keembay_wdt_ops;
+ wdt->wdd.min_timeout = WDT_LOAD_MIN;
+ wdt->wdd.max_timeout = WDT_LOAD_MAX / wdt->rate;
+ wdt->wdd.timeout = WDT_TIMEOUT;
+ wdt->wdd.pretimeout = WDT_PRETIMEOUT;
+
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+ watchdog_init_timeout(&wdt->wdd, timeout, dev);
+ keembay_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
+ keembay_wdt_set_pretimeout(&wdt->wdd, wdt->wdd.pretimeout);
+
+ ret = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register watchdog device.\n");
+
+ platform_set_drvdata(pdev, wdt);
+ dev_info(dev, "Initial timeout %d sec%s.\n",
+ wdt->wdd.timeout, nowayout ? ", nowayout" : "");
+
+ return 0;
+}
+
+static int __maybe_unused keembay_wdt_suspend(struct device *dev)
+{
+ struct keembay_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return keembay_wdt_stop(&wdt->wdd);
+
+ return 0;
+}
+
+static int __maybe_unused keembay_wdt_resume(struct device *dev)
+{
+ struct keembay_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return keembay_wdt_start(&wdt->wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(keembay_wdt_pm_ops, keembay_wdt_suspend,
+ keembay_wdt_resume);
+
+static const struct of_device_id keembay_wdt_match[] = {
+ { .compatible = "intel,keembay-wdt" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, keembay_wdt_match);
+
+static struct platform_driver keembay_wdt_driver = {
+ .probe = keembay_wdt_probe,
+ .driver = {
+ .name = "keembay_wdt",
+ .of_match_table = keembay_wdt_match,
+ .pm = &keembay_wdt_pm_ops,
+ },
+};
+
+module_platform_driver(keembay_wdt_driver);
+
+MODULE_DESCRIPTION("Intel Keem Bay SoC watchdog driver");
+MODULE_AUTHOR("Wan Ahmad Zainie <wan.ahmad.zainie.wan.mohamad@intel.com");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c
new file mode 100644
index 000000000..40bd518ed
--- /dev/null
+++ b/drivers/watchdog/kempld_wdt.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kontron PLD watchdog driver
+ *
+ * Copyright (c) 2010-2013 Kontron Europe GmbH
+ * Author: Michael Brunner <michael.brunner@kontron.com>
+ *
+ * Note: From the PLD watchdog point of view timeout and pretimeout are
+ * defined differently than in the kernel.
+ * First the pretimeout stage runs out before the timeout stage gets
+ * active.
+ *
+ * Kernel/API: P-----| pretimeout
+ * |-----------------------T timeout
+ * Watchdog: |-----------------P pretimeout_stage
+ * |-----T timeout_stage
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/kempld.h>
+
+#define KEMPLD_WDT_STAGE_TIMEOUT(x) (0x1b + (x) * 4)
+#define KEMPLD_WDT_STAGE_CFG(x) (0x18 + (x))
+#define STAGE_CFG_GET_PRESCALER(x) (((x) & 0x30) >> 4)
+#define STAGE_CFG_SET_PRESCALER(x) (((x) & 0x3) << 4)
+#define STAGE_CFG_PRESCALER_MASK 0x30
+#define STAGE_CFG_ACTION_MASK 0x7
+#define STAGE_CFG_ASSERT (1 << 3)
+
+#define KEMPLD_WDT_MAX_STAGES 2
+#define KEMPLD_WDT_KICK 0x16
+#define KEMPLD_WDT_CFG 0x17
+#define KEMPLD_WDT_CFG_ENABLE 0x10
+#define KEMPLD_WDT_CFG_ENABLE_LOCK 0x8
+#define KEMPLD_WDT_CFG_GLOBAL_LOCK 0x80
+
+enum {
+ ACTION_NONE = 0,
+ ACTION_RESET,
+ ACTION_NMI,
+ ACTION_SMI,
+ ACTION_SCI,
+ ACTION_DELAY,
+};
+
+enum {
+ STAGE_TIMEOUT = 0,
+ STAGE_PRETIMEOUT,
+};
+
+enum {
+ PRESCALER_21 = 0,
+ PRESCALER_17,
+ PRESCALER_12,
+};
+
+static const u32 kempld_prescaler[] = {
+ [PRESCALER_21] = (1 << 21) - 1,
+ [PRESCALER_17] = (1 << 17) - 1,
+ [PRESCALER_12] = (1 << 12) - 1,
+ 0,
+};
+
+struct kempld_wdt_stage {
+ unsigned int id;
+ u32 mask;
+};
+
+struct kempld_wdt_data {
+ struct kempld_device_data *pld;
+ struct watchdog_device wdd;
+ unsigned int pretimeout;
+ struct kempld_wdt_stage stage[KEMPLD_WDT_MAX_STAGES];
+#ifdef CONFIG_PM
+ u8 pm_status_store;
+#endif
+};
+
+#define DEFAULT_TIMEOUT 30 /* seconds */
+#define DEFAULT_PRETIMEOUT 0
+
+static unsigned int timeout = DEFAULT_TIMEOUT;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (>=0, default="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+
+static unsigned int pretimeout = DEFAULT_PRETIMEOUT;
+module_param(pretimeout, uint, 0);
+MODULE_PARM_DESC(pretimeout,
+ "Watchdog pretimeout in seconds. (>=0, default="
+ __MODULE_STRING(DEFAULT_PRETIMEOUT) ")");
+
+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) ")");
+
+static int kempld_wdt_set_stage_action(struct kempld_wdt_data *wdt_data,
+ struct kempld_wdt_stage *stage,
+ u8 action)
+{
+ struct kempld_device_data *pld = wdt_data->pld;
+ u8 stage_cfg;
+
+ if (!stage || !stage->mask)
+ return -EINVAL;
+
+ kempld_get_mutex(pld);
+ stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+ stage_cfg &= ~STAGE_CFG_ACTION_MASK;
+ stage_cfg |= (action & STAGE_CFG_ACTION_MASK);
+
+ if (action == ACTION_RESET)
+ stage_cfg |= STAGE_CFG_ASSERT;
+ else
+ stage_cfg &= ~STAGE_CFG_ASSERT;
+
+ kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+ kempld_release_mutex(pld);
+
+ return 0;
+}
+
+static int kempld_wdt_set_stage_timeout(struct kempld_wdt_data *wdt_data,
+ struct kempld_wdt_stage *stage,
+ unsigned int timeout)
+{
+ struct kempld_device_data *pld = wdt_data->pld;
+ u32 prescaler;
+ u64 stage_timeout64;
+ u32 stage_timeout;
+ u32 remainder;
+ u8 stage_cfg;
+
+ prescaler = kempld_prescaler[PRESCALER_21];
+
+ if (!stage)
+ return -EINVAL;
+
+ stage_timeout64 = (u64)timeout * pld->pld_clock;
+ remainder = do_div(stage_timeout64, prescaler);
+ if (remainder)
+ stage_timeout64++;
+
+ if (stage_timeout64 > stage->mask)
+ return -EINVAL;
+
+ stage_timeout = stage_timeout64 & stage->mask;
+
+ kempld_get_mutex(pld);
+ stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+ stage_cfg &= ~STAGE_CFG_PRESCALER_MASK;
+ stage_cfg |= STAGE_CFG_SET_PRESCALER(PRESCALER_21);
+ kempld_write8(pld, KEMPLD_WDT_STAGE_CFG(stage->id), stage_cfg);
+ kempld_write32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id),
+ stage_timeout);
+ kempld_release_mutex(pld);
+
+ return 0;
+}
+
+/*
+ * kempld_get_mutex must be called prior to calling this function.
+ */
+static unsigned int kempld_wdt_get_timeout(struct kempld_wdt_data *wdt_data,
+ struct kempld_wdt_stage *stage)
+{
+ struct kempld_device_data *pld = wdt_data->pld;
+ unsigned int timeout;
+ u64 stage_timeout;
+ u32 prescaler;
+ u32 remainder;
+ u8 stage_cfg;
+
+ if (!stage->mask)
+ return 0;
+
+ stage_cfg = kempld_read8(pld, KEMPLD_WDT_STAGE_CFG(stage->id));
+ stage_timeout = kempld_read32(pld, KEMPLD_WDT_STAGE_TIMEOUT(stage->id));
+ prescaler = kempld_prescaler[STAGE_CFG_GET_PRESCALER(stage_cfg)];
+
+ stage_timeout = (stage_timeout & stage->mask) * prescaler;
+ remainder = do_div(stage_timeout, pld->pld_clock);
+ if (remainder)
+ stage_timeout++;
+
+ timeout = stage_timeout;
+ WARN_ON_ONCE(timeout != stage_timeout);
+
+ return timeout;
+}
+
+static int kempld_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_wdt_stage *pretimeout_stage;
+ struct kempld_wdt_stage *timeout_stage;
+ int ret;
+
+ timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+ pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+ if (pretimeout_stage->mask && wdt_data->pretimeout > 0)
+ timeout = wdt_data->pretimeout;
+
+ ret = kempld_wdt_set_stage_action(wdt_data, timeout_stage,
+ ACTION_RESET);
+ if (ret)
+ return ret;
+ ret = kempld_wdt_set_stage_timeout(wdt_data, timeout_stage,
+ timeout);
+ if (ret)
+ return ret;
+
+ wdd->timeout = timeout;
+ return 0;
+}
+
+static int kempld_wdt_set_pretimeout(struct watchdog_device *wdd,
+ unsigned int pretimeout)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_wdt_stage *pretimeout_stage;
+ u8 action = ACTION_NONE;
+ int ret;
+
+ pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+
+ if (!pretimeout_stage->mask)
+ return -ENXIO;
+
+ if (pretimeout > wdd->timeout)
+ return -EINVAL;
+
+ if (pretimeout > 0)
+ action = ACTION_NMI;
+
+ ret = kempld_wdt_set_stage_action(wdt_data, pretimeout_stage,
+ action);
+ if (ret)
+ return ret;
+ ret = kempld_wdt_set_stage_timeout(wdt_data, pretimeout_stage,
+ wdd->timeout - pretimeout);
+ if (ret)
+ return ret;
+
+ wdt_data->pretimeout = pretimeout;
+ return 0;
+}
+
+static void kempld_wdt_update_timeouts(struct kempld_wdt_data *wdt_data)
+{
+ struct kempld_device_data *pld = wdt_data->pld;
+ struct kempld_wdt_stage *pretimeout_stage;
+ struct kempld_wdt_stage *timeout_stage;
+ unsigned int pretimeout, timeout;
+
+ pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+ timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+ kempld_get_mutex(pld);
+ pretimeout = kempld_wdt_get_timeout(wdt_data, pretimeout_stage);
+ timeout = kempld_wdt_get_timeout(wdt_data, timeout_stage);
+ kempld_release_mutex(pld);
+
+ if (pretimeout)
+ wdt_data->pretimeout = timeout;
+ else
+ wdt_data->pretimeout = 0;
+
+ wdt_data->wdd.timeout = pretimeout + timeout;
+}
+
+static int kempld_wdt_start(struct watchdog_device *wdd)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_device_data *pld = wdt_data->pld;
+ u8 status;
+ int ret;
+
+ ret = kempld_wdt_set_timeout(wdd, wdd->timeout);
+ if (ret)
+ return ret;
+
+ kempld_get_mutex(pld);
+ status = kempld_read8(pld, KEMPLD_WDT_CFG);
+ status |= KEMPLD_WDT_CFG_ENABLE;
+ kempld_write8(pld, KEMPLD_WDT_CFG, status);
+ status = kempld_read8(pld, KEMPLD_WDT_CFG);
+ kempld_release_mutex(pld);
+
+ /* Check if the watchdog was enabled */
+ if (!(status & KEMPLD_WDT_CFG_ENABLE))
+ return -EACCES;
+
+ return 0;
+}
+
+static int kempld_wdt_stop(struct watchdog_device *wdd)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_device_data *pld = wdt_data->pld;
+ u8 status;
+
+ kempld_get_mutex(pld);
+ status = kempld_read8(pld, KEMPLD_WDT_CFG);
+ status &= ~KEMPLD_WDT_CFG_ENABLE;
+ kempld_write8(pld, KEMPLD_WDT_CFG, status);
+ status = kempld_read8(pld, KEMPLD_WDT_CFG);
+ kempld_release_mutex(pld);
+
+ /* Check if the watchdog was disabled */
+ if (status & KEMPLD_WDT_CFG_ENABLE)
+ return -EACCES;
+
+ return 0;
+}
+
+static int kempld_wdt_keepalive(struct watchdog_device *wdd)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_device_data *pld = wdt_data->pld;
+
+ kempld_get_mutex(pld);
+ kempld_write8(pld, KEMPLD_WDT_KICK, 'K');
+ kempld_release_mutex(pld);
+
+ return 0;
+}
+
+static long kempld_wdt_ioctl(struct watchdog_device *wdd, unsigned int cmd,
+ unsigned long arg)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ void __user *argp = (void __user *)arg;
+ int ret = -ENOIOCTLCMD;
+ int __user *p = argp;
+ int new_value;
+
+ switch (cmd) {
+ case WDIOC_SETPRETIMEOUT:
+ if (get_user(new_value, p))
+ return -EFAULT;
+ ret = kempld_wdt_set_pretimeout(wdd, new_value);
+ if (ret)
+ return ret;
+ ret = kempld_wdt_keepalive(wdd);
+ break;
+ case WDIOC_GETPRETIMEOUT:
+ ret = put_user(wdt_data->pretimeout, (int __user *)arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int kempld_wdt_probe_stages(struct watchdog_device *wdd)
+{
+ struct kempld_wdt_data *wdt_data = watchdog_get_drvdata(wdd);
+ struct kempld_device_data *pld = wdt_data->pld;
+ struct kempld_wdt_stage *pretimeout_stage;
+ struct kempld_wdt_stage *timeout_stage;
+ u8 index, data, data_orig;
+ u32 mask;
+ int i, j;
+
+ pretimeout_stage = &wdt_data->stage[STAGE_PRETIMEOUT];
+ timeout_stage = &wdt_data->stage[STAGE_TIMEOUT];
+
+ pretimeout_stage->mask = 0;
+ timeout_stage->mask = 0;
+
+ for (i = 0; i < 3; i++) {
+ index = KEMPLD_WDT_STAGE_TIMEOUT(i);
+ mask = 0;
+
+ kempld_get_mutex(pld);
+ /* Probe each byte individually. */
+ for (j = 0; j < 4; j++) {
+ data_orig = kempld_read8(pld, index + j);
+ kempld_write8(pld, index + j, 0x00);
+ data = kempld_read8(pld, index + j);
+ /* A failed write means this byte is reserved */
+ if (data != 0x00)
+ break;
+ kempld_write8(pld, index + j, data_orig);
+ mask |= 0xff << (j * 8);
+ }
+ kempld_release_mutex(pld);
+
+ /* Assign available stages to timeout and pretimeout */
+ if (!timeout_stage->mask) {
+ timeout_stage->mask = mask;
+ timeout_stage->id = i;
+ } else {
+ if (pld->feature_mask & KEMPLD_FEATURE_BIT_NMI) {
+ pretimeout_stage->mask = timeout_stage->mask;
+ timeout_stage->mask = mask;
+ pretimeout_stage->id = timeout_stage->id;
+ timeout_stage->id = i;
+ }
+ break;
+ }
+ }
+
+ if (!timeout_stage->mask)
+ return -ENODEV;
+
+ return 0;
+}
+
+static const struct watchdog_info kempld_wdt_info = {
+ .identity = "KEMPLD Watchdog",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE |
+ WDIOF_PRETIMEOUT
+};
+
+static const struct watchdog_ops kempld_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = kempld_wdt_start,
+ .stop = kempld_wdt_stop,
+ .ping = kempld_wdt_keepalive,
+ .set_timeout = kempld_wdt_set_timeout,
+ .ioctl = kempld_wdt_ioctl,
+};
+
+static int kempld_wdt_probe(struct platform_device *pdev)
+{
+ struct kempld_device_data *pld = dev_get_drvdata(pdev->dev.parent);
+ struct kempld_wdt_data *wdt_data;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ u8 status;
+ int ret = 0;
+
+ wdt_data = devm_kzalloc(dev, sizeof(*wdt_data), GFP_KERNEL);
+ if (!wdt_data)
+ return -ENOMEM;
+
+ wdt_data->pld = pld;
+ wdd = &wdt_data->wdd;
+ wdd->parent = dev;
+
+ kempld_get_mutex(pld);
+ status = kempld_read8(pld, KEMPLD_WDT_CFG);
+ kempld_release_mutex(pld);
+
+ /* Enable nowayout if watchdog is already locked */
+ if (status & (KEMPLD_WDT_CFG_ENABLE_LOCK |
+ KEMPLD_WDT_CFG_GLOBAL_LOCK)) {
+ if (!nowayout)
+ dev_warn(dev,
+ "Forcing nowayout - watchdog lock enabled!\n");
+ nowayout = true;
+ }
+
+ wdd->info = &kempld_wdt_info;
+ wdd->ops = &kempld_wdt_ops;
+
+ watchdog_set_drvdata(wdd, wdt_data);
+ watchdog_set_nowayout(wdd, nowayout);
+
+ ret = kempld_wdt_probe_stages(wdd);
+ if (ret)
+ return ret;
+
+ kempld_wdt_set_timeout(wdd, timeout);
+ kempld_wdt_set_pretimeout(wdd, pretimeout);
+
+ /* Check if watchdog is already enabled */
+ if (status & KEMPLD_WDT_CFG_ENABLE) {
+ /* Get current watchdog settings */
+ kempld_wdt_update_timeouts(wdt_data);
+ dev_info(dev, "Watchdog was already enabled\n");
+ }
+
+ platform_set_drvdata(pdev, wdt_data);
+ watchdog_stop_on_reboot(wdd);
+ watchdog_stop_on_unregister(wdd);
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/* Disable watchdog if it is active during suspend */
+static int kempld_wdt_suspend(struct platform_device *pdev,
+ pm_message_t message)
+{
+ struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+ struct kempld_device_data *pld = wdt_data->pld;
+ struct watchdog_device *wdd = &wdt_data->wdd;
+
+ kempld_get_mutex(pld);
+ wdt_data->pm_status_store = kempld_read8(pld, KEMPLD_WDT_CFG);
+ kempld_release_mutex(pld);
+
+ kempld_wdt_update_timeouts(wdt_data);
+
+ if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+ return kempld_wdt_stop(wdd);
+
+ return 0;
+}
+
+/* Enable watchdog and configure it if necessary */
+static int kempld_wdt_resume(struct platform_device *pdev)
+{
+ struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev);
+ struct watchdog_device *wdd = &wdt_data->wdd;
+
+ /*
+ * If watchdog was stopped before suspend be sure it gets disabled
+ * again, for the case BIOS has enabled it during resume
+ */
+ if (wdt_data->pm_status_store & KEMPLD_WDT_CFG_ENABLE)
+ return kempld_wdt_start(wdd);
+ else
+ return kempld_wdt_stop(wdd);
+}
+#else
+#define kempld_wdt_suspend NULL
+#define kempld_wdt_resume NULL
+#endif
+
+static struct platform_driver kempld_wdt_driver = {
+ .driver = {
+ .name = "kempld-wdt",
+ },
+ .probe = kempld_wdt_probe,
+ .suspend = kempld_wdt_suspend,
+ .resume = kempld_wdt_resume,
+};
+
+module_platform_driver(kempld_wdt_driver);
+
+MODULE_DESCRIPTION("KEM PLD Watchdog Driver");
+MODULE_AUTHOR("Michael Brunner <michael.brunner@kontron.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/lantiq_wdt.c b/drivers/watchdog/lantiq_wdt.c
new file mode 100644
index 000000000..6fab504af
--- /dev/null
+++ b/drivers/watchdog/lantiq_wdt.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * Copyright (C) 2010 John Crispin <john@phrozen.org>
+ * Copyright (C) 2017 Hauke Mehrtens <hauke@hauke-m.de>
+ * Based on EP93xx wdt driver
+ */
+
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/watchdog.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include <lantiq_soc.h>
+
+#define LTQ_XRX_RCU_RST_STAT 0x0014
+#define LTQ_XRX_RCU_RST_STAT_WDT BIT(31)
+
+/* CPU0 Reset Source Register */
+#define LTQ_FALCON_SYS1_CPU0RS 0x0060
+/* reset cause mask */
+#define LTQ_FALCON_SYS1_CPU0RS_MASK 0x0007
+#define LTQ_FALCON_SYS1_CPU0RS_WDT 0x02
+
+/*
+ * Section 3.4 of the datasheet
+ * The password sequence protects the WDT control register from unintended
+ * write actions, which might cause malfunction of the WDT.
+ *
+ * essentially the following two magic passwords need to be written to allow
+ * IO access to the WDT core
+ */
+#define LTQ_WDT_CR_PW1 0x00BE0000
+#define LTQ_WDT_CR_PW2 0x00DC0000
+
+#define LTQ_WDT_CR 0x0 /* watchdog control register */
+#define LTQ_WDT_CR_GEN BIT(31) /* enable bit */
+/* Pre-warning limit set to 1/16 of max WDT period */
+#define LTQ_WDT_CR_PWL (0x3 << 26)
+/* set clock divider to 0x40000 */
+#define LTQ_WDT_CR_CLKDIV (0x3 << 24)
+#define LTQ_WDT_CR_PW_MASK GENMASK(23, 16) /* Password field */
+#define LTQ_WDT_CR_MAX_TIMEOUT ((1 << 16) - 1) /* The reload field is 16 bit */
+#define LTQ_WDT_SR 0x8 /* watchdog status register */
+#define LTQ_WDT_SR_EN BIT(31) /* Enable */
+#define LTQ_WDT_SR_VALUE_MASK GENMASK(15, 0) /* Timer value */
+
+#define LTQ_WDT_DIVIDER 0x40000
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+struct ltq_wdt_hw {
+ int (*bootstatus_get)(struct device *dev);
+};
+
+struct ltq_wdt_priv {
+ struct watchdog_device wdt;
+ void __iomem *membase;
+ unsigned long clk_rate;
+};
+
+static u32 ltq_wdt_r32(struct ltq_wdt_priv *priv, u32 offset)
+{
+ return __raw_readl(priv->membase + offset);
+}
+
+static void ltq_wdt_w32(struct ltq_wdt_priv *priv, u32 val, u32 offset)
+{
+ __raw_writel(val, priv->membase + offset);
+}
+
+static void ltq_wdt_mask(struct ltq_wdt_priv *priv, u32 clear, u32 set,
+ u32 offset)
+{
+ u32 val = ltq_wdt_r32(priv, offset);
+
+ val &= ~(clear);
+ val |= set;
+ ltq_wdt_w32(priv, val, offset);
+}
+
+static struct ltq_wdt_priv *ltq_wdt_get_priv(struct watchdog_device *wdt)
+{
+ return container_of(wdt, struct ltq_wdt_priv, wdt);
+}
+
+static struct watchdog_info ltq_wdt_info = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_CARDRESET,
+ .identity = "ltq_wdt",
+};
+
+static int ltq_wdt_start(struct watchdog_device *wdt)
+{
+ struct ltq_wdt_priv *priv = ltq_wdt_get_priv(wdt);
+ u32 timeout;
+
+ timeout = wdt->timeout * priv->clk_rate;
+
+ ltq_wdt_mask(priv, LTQ_WDT_CR_PW_MASK, LTQ_WDT_CR_PW1, LTQ_WDT_CR);
+ /* write the second magic plus the configuration and new timeout */
+ ltq_wdt_mask(priv, LTQ_WDT_CR_PW_MASK | LTQ_WDT_CR_MAX_TIMEOUT,
+ LTQ_WDT_CR_GEN | LTQ_WDT_CR_PWL | LTQ_WDT_CR_CLKDIV |
+ LTQ_WDT_CR_PW2 | timeout,
+ LTQ_WDT_CR);
+
+ return 0;
+}
+
+static int ltq_wdt_stop(struct watchdog_device *wdt)
+{
+ struct ltq_wdt_priv *priv = ltq_wdt_get_priv(wdt);
+
+ ltq_wdt_mask(priv, LTQ_WDT_CR_PW_MASK, LTQ_WDT_CR_PW1, LTQ_WDT_CR);
+ ltq_wdt_mask(priv, LTQ_WDT_CR_GEN | LTQ_WDT_CR_PW_MASK,
+ LTQ_WDT_CR_PW2, LTQ_WDT_CR);
+
+ return 0;
+}
+
+static int ltq_wdt_ping(struct watchdog_device *wdt)
+{
+ struct ltq_wdt_priv *priv = ltq_wdt_get_priv(wdt);
+ u32 timeout;
+
+ timeout = wdt->timeout * priv->clk_rate;
+
+ ltq_wdt_mask(priv, LTQ_WDT_CR_PW_MASK, LTQ_WDT_CR_PW1, LTQ_WDT_CR);
+ /* write the second magic plus the configuration and new timeout */
+ ltq_wdt_mask(priv, LTQ_WDT_CR_PW_MASK | LTQ_WDT_CR_MAX_TIMEOUT,
+ LTQ_WDT_CR_PW2 | timeout, LTQ_WDT_CR);
+
+ return 0;
+}
+
+static unsigned int ltq_wdt_get_timeleft(struct watchdog_device *wdt)
+{
+ struct ltq_wdt_priv *priv = ltq_wdt_get_priv(wdt);
+ u64 timeout;
+
+ timeout = ltq_wdt_r32(priv, LTQ_WDT_SR) & LTQ_WDT_SR_VALUE_MASK;
+ return do_div(timeout, priv->clk_rate);
+}
+
+static const struct watchdog_ops ltq_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ltq_wdt_start,
+ .stop = ltq_wdt_stop,
+ .ping = ltq_wdt_ping,
+ .get_timeleft = ltq_wdt_get_timeleft,
+};
+
+static int ltq_wdt_xrx_bootstatus_get(struct device *dev)
+{
+ struct regmap *rcu_regmap;
+ u32 val;
+ int err;
+
+ rcu_regmap = syscon_regmap_lookup_by_phandle(dev->of_node, "regmap");
+ if (IS_ERR(rcu_regmap))
+ return PTR_ERR(rcu_regmap);
+
+ err = regmap_read(rcu_regmap, LTQ_XRX_RCU_RST_STAT, &val);
+ if (err)
+ return err;
+
+ if (val & LTQ_XRX_RCU_RST_STAT_WDT)
+ return WDIOF_CARDRESET;
+
+ return 0;
+}
+
+static int ltq_wdt_falcon_bootstatus_get(struct device *dev)
+{
+ struct regmap *rcu_regmap;
+ u32 val;
+ int err;
+
+ rcu_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "lantiq,rcu");
+ if (IS_ERR(rcu_regmap))
+ return PTR_ERR(rcu_regmap);
+
+ err = regmap_read(rcu_regmap, LTQ_FALCON_SYS1_CPU0RS, &val);
+ if (err)
+ return err;
+
+ if ((val & LTQ_FALCON_SYS1_CPU0RS_MASK) == LTQ_FALCON_SYS1_CPU0RS_WDT)
+ return WDIOF_CARDRESET;
+
+ return 0;
+}
+
+static int ltq_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ltq_wdt_priv *priv;
+ struct watchdog_device *wdt;
+ struct clk *clk;
+ const struct ltq_wdt_hw *ltq_wdt_hw;
+ int ret;
+ u32 status;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->membase = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->membase))
+ return PTR_ERR(priv->membase);
+
+ /* we do not need to enable the clock as it is always running */
+ clk = clk_get_io();
+ priv->clk_rate = clk_get_rate(clk) / LTQ_WDT_DIVIDER;
+ if (!priv->clk_rate) {
+ dev_err(dev, "clock rate less than divider %i\n",
+ LTQ_WDT_DIVIDER);
+ return -EINVAL;
+ }
+
+ wdt = &priv->wdt;
+ wdt->info = &ltq_wdt_info;
+ wdt->ops = &ltq_wdt_ops;
+ wdt->min_timeout = 1;
+ wdt->max_timeout = LTQ_WDT_CR_MAX_TIMEOUT / priv->clk_rate;
+ wdt->timeout = wdt->max_timeout;
+ wdt->parent = dev;
+
+ ltq_wdt_hw = of_device_get_match_data(dev);
+ if (ltq_wdt_hw && ltq_wdt_hw->bootstatus_get) {
+ ret = ltq_wdt_hw->bootstatus_get(dev);
+ if (ret >= 0)
+ wdt->bootstatus = ret;
+ }
+
+ watchdog_set_nowayout(wdt, nowayout);
+ watchdog_init_timeout(wdt, 0, dev);
+
+ status = ltq_wdt_r32(priv, LTQ_WDT_SR);
+ if (status & LTQ_WDT_SR_EN) {
+ /*
+ * If the watchdog is already running overwrite it with our
+ * new settings. Stop is not needed as the start call will
+ * replace all settings anyway.
+ */
+ ltq_wdt_start(wdt);
+ set_bit(WDOG_HW_RUNNING, &wdt->status);
+ }
+
+ return devm_watchdog_register_device(dev, wdt);
+}
+
+static const struct ltq_wdt_hw ltq_wdt_xrx100 = {
+ .bootstatus_get = ltq_wdt_xrx_bootstatus_get,
+};
+
+static const struct ltq_wdt_hw ltq_wdt_falcon = {
+ .bootstatus_get = ltq_wdt_falcon_bootstatus_get,
+};
+
+static const struct of_device_id ltq_wdt_match[] = {
+ { .compatible = "lantiq,wdt", .data = NULL },
+ { .compatible = "lantiq,xrx100-wdt", .data = &ltq_wdt_xrx100 },
+ { .compatible = "lantiq,falcon-wdt", .data = &ltq_wdt_falcon },
+ {},
+};
+MODULE_DEVICE_TABLE(of, ltq_wdt_match);
+
+static struct platform_driver ltq_wdt_driver = {
+ .probe = ltq_wdt_probe,
+ .driver = {
+ .name = "wdt",
+ .of_match_table = ltq_wdt_match,
+ },
+};
+
+module_platform_driver(ltq_wdt_driver);
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+MODULE_AUTHOR("John Crispin <john@phrozen.org>");
+MODULE_DESCRIPTION("Lantiq SoC Watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/loongson1_wdt.c b/drivers/watchdog/loongson1_wdt.c
new file mode 100644
index 000000000..bb3d075c0
--- /dev/null
+++ b/drivers/watchdog/loongson1_wdt.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <loongson1.h>
+
+#define DEFAULT_HEARTBEAT 30
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+
+static unsigned int heartbeat;
+module_param(heartbeat, uint, 0444);
+
+struct ls1x_wdt_drvdata {
+ void __iomem *base;
+ struct clk *clk;
+ unsigned long clk_rate;
+ struct watchdog_device wdt;
+};
+
+static int ls1x_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ writel(0x1, drvdata->base + WDT_SET);
+
+ return 0;
+}
+
+static int ls1x_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ unsigned int max_hw_heartbeat = wdt_dev->max_hw_heartbeat_ms / 1000;
+ unsigned int counts;
+
+ wdt_dev->timeout = timeout;
+
+ counts = drvdata->clk_rate * min(timeout, max_hw_heartbeat);
+ writel(counts, drvdata->base + WDT_TIMER);
+
+ return 0;
+}
+
+static int ls1x_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ writel(0x1, drvdata->base + WDT_EN);
+
+ return 0;
+}
+
+static int ls1x_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ writel(0x0, drvdata->base + WDT_EN);
+
+ return 0;
+}
+
+static const struct watchdog_info ls1x_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Loongson1 Watchdog",
+};
+
+static const struct watchdog_ops ls1x_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ls1x_wdt_start,
+ .stop = ls1x_wdt_stop,
+ .ping = ls1x_wdt_ping,
+ .set_timeout = ls1x_wdt_set_timeout,
+};
+
+static void ls1x_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int ls1x_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ls1x_wdt_drvdata *drvdata;
+ struct watchdog_device *ls1x_wdt;
+ unsigned long clk_rate;
+ int err;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ drvdata->clk = devm_clk_get(dev, pdev->name);
+ if (IS_ERR(drvdata->clk))
+ return PTR_ERR(drvdata->clk);
+
+ err = clk_prepare_enable(drvdata->clk);
+ if (err) {
+ dev_err(dev, "clk enable failed\n");
+ return err;
+ }
+ err = devm_add_action_or_reset(dev, ls1x_clk_disable_unprepare,
+ drvdata->clk);
+ if (err)
+ return err;
+
+ clk_rate = clk_get_rate(drvdata->clk);
+ if (!clk_rate)
+ return -EINVAL;
+ drvdata->clk_rate = clk_rate;
+
+ ls1x_wdt = &drvdata->wdt;
+ ls1x_wdt->info = &ls1x_wdt_info;
+ ls1x_wdt->ops = &ls1x_wdt_ops;
+ ls1x_wdt->timeout = DEFAULT_HEARTBEAT;
+ ls1x_wdt->min_timeout = 1;
+ ls1x_wdt->max_hw_heartbeat_ms = U32_MAX / clk_rate * 1000;
+ ls1x_wdt->parent = dev;
+
+ watchdog_init_timeout(ls1x_wdt, heartbeat, dev);
+ watchdog_set_nowayout(ls1x_wdt, nowayout);
+ watchdog_set_drvdata(ls1x_wdt, drvdata);
+
+ err = devm_watchdog_register_device(dev, &drvdata->wdt);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ dev_info(dev, "Loongson1 Watchdog driver registered\n");
+
+ return 0;
+}
+
+static struct platform_driver ls1x_wdt_driver = {
+ .probe = ls1x_wdt_probe,
+ .driver = {
+ .name = "ls1x-wdt",
+ },
+};
+
+module_platform_driver(ls1x_wdt_driver);
+
+MODULE_AUTHOR("Yang Ling <gnaygnil@gmail.com>");
+MODULE_DESCRIPTION("Loongson1 Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/lpc18xx_wdt.c b/drivers/watchdog/lpc18xx_wdt.c
new file mode 100644
index 000000000..60b6d74f2
--- /dev/null
+++ b/drivers/watchdog/lpc18xx_wdt.c
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NXP LPC18xx Watchdog Timer (WDT)
+ *
+ * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com>
+ *
+ * Notes
+ * -----
+ * The Watchdog consists of a fixed divide-by-4 clock pre-scaler and a 24-bit
+ * counter which decrements on every clock cycle.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+/* Registers */
+#define LPC18XX_WDT_MOD 0x00
+#define LPC18XX_WDT_MOD_WDEN BIT(0)
+#define LPC18XX_WDT_MOD_WDRESET BIT(1)
+
+#define LPC18XX_WDT_TC 0x04
+#define LPC18XX_WDT_TC_MIN 0xff
+#define LPC18XX_WDT_TC_MAX 0xffffff
+
+#define LPC18XX_WDT_FEED 0x08
+#define LPC18XX_WDT_FEED_MAGIC1 0xaa
+#define LPC18XX_WDT_FEED_MAGIC2 0x55
+
+#define LPC18XX_WDT_TV 0x0c
+
+/* Clock pre-scaler */
+#define LPC18XX_WDT_CLK_DIV 4
+
+/* Timeout values in seconds */
+#define LPC18XX_WDT_DEF_TIMEOUT 30U
+
+static int heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds (default="
+ __MODULE_STRING(LPC18XX_WDT_DEF_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct lpc18xx_wdt_dev {
+ struct watchdog_device wdt_dev;
+ struct clk *reg_clk;
+ struct clk *wdt_clk;
+ unsigned long clk_rate;
+ void __iomem *base;
+ struct timer_list timer;
+ spinlock_t lock;
+};
+
+static int lpc18xx_wdt_feed(struct watchdog_device *wdt_dev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+
+ /*
+ * An abort condition will occur if an interrupt happens during the feed
+ * sequence.
+ */
+ spin_lock_irqsave(&lpc18xx_wdt->lock, flags);
+ writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+ writel(LPC18XX_WDT_FEED_MAGIC2, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+ spin_unlock_irqrestore(&lpc18xx_wdt->lock, flags);
+
+ return 0;
+}
+
+static void lpc18xx_wdt_timer_feed(struct timer_list *t)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = from_timer(lpc18xx_wdt, t, timer);
+ struct watchdog_device *wdt_dev = &lpc18xx_wdt->wdt_dev;
+
+ lpc18xx_wdt_feed(wdt_dev);
+
+ /* Use safe value (1/2 of real timeout) */
+ mod_timer(&lpc18xx_wdt->timer, jiffies +
+ msecs_to_jiffies((wdt_dev->timeout * MSEC_PER_SEC) / 2));
+}
+
+/*
+ * Since LPC18xx Watchdog cannot be disabled in hardware, we must keep feeding
+ * it with a timer until userspace watchdog software takes over.
+ */
+static int lpc18xx_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+
+ lpc18xx_wdt_timer_feed(&lpc18xx_wdt->timer);
+
+ return 0;
+}
+
+static void __lpc18xx_wdt_set_timeout(struct lpc18xx_wdt_dev *lpc18xx_wdt)
+{
+ unsigned int val;
+
+ val = DIV_ROUND_UP(lpc18xx_wdt->wdt_dev.timeout * lpc18xx_wdt->clk_rate,
+ LPC18XX_WDT_CLK_DIV);
+ writel(val, lpc18xx_wdt->base + LPC18XX_WDT_TC);
+}
+
+static int lpc18xx_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+
+ lpc18xx_wdt->wdt_dev.timeout = new_timeout;
+ __lpc18xx_wdt_set_timeout(lpc18xx_wdt);
+
+ return 0;
+}
+
+static unsigned int lpc18xx_wdt_get_timeleft(struct watchdog_device *wdt_dev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned int val;
+
+ val = readl(lpc18xx_wdt->base + LPC18XX_WDT_TV);
+ return (val * LPC18XX_WDT_CLK_DIV) / lpc18xx_wdt->clk_rate;
+}
+
+static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned int val;
+
+ if (timer_pending(&lpc18xx_wdt->timer))
+ del_timer(&lpc18xx_wdt->timer);
+
+ val = readl(lpc18xx_wdt->base + LPC18XX_WDT_MOD);
+ val |= LPC18XX_WDT_MOD_WDEN;
+ val |= LPC18XX_WDT_MOD_WDRESET;
+ writel(val, lpc18xx_wdt->base + LPC18XX_WDT_MOD);
+
+ /*
+ * Setting the WDEN bit in the WDMOD register is not sufficient to
+ * enable the Watchdog. A valid feed sequence must be completed after
+ * setting WDEN before the Watchdog is capable of generating a reset.
+ */
+ lpc18xx_wdt_feed(wdt_dev);
+
+ return 0;
+}
+
+static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+ int val;
+
+ /*
+ * Incorrect feed sequence causes immediate watchdog reset if enabled.
+ */
+ spin_lock_irqsave(&lpc18xx_wdt->lock, flags);
+
+ val = readl(lpc18xx_wdt->base + LPC18XX_WDT_MOD);
+ val |= LPC18XX_WDT_MOD_WDEN;
+ val |= LPC18XX_WDT_MOD_WDRESET;
+ writel(val, lpc18xx_wdt->base + LPC18XX_WDT_MOD);
+
+ writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+ writel(LPC18XX_WDT_FEED_MAGIC2, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+
+ writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+ writel(LPC18XX_WDT_FEED_MAGIC1, lpc18xx_wdt->base + LPC18XX_WDT_FEED);
+
+ spin_unlock_irqrestore(&lpc18xx_wdt->lock, flags);
+
+ return 0;
+}
+
+static const struct watchdog_info lpc18xx_wdt_info = {
+ .identity = "NXP LPC18xx Watchdog",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops lpc18xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = lpc18xx_wdt_start,
+ .stop = lpc18xx_wdt_stop,
+ .ping = lpc18xx_wdt_feed,
+ .set_timeout = lpc18xx_wdt_set_timeout,
+ .get_timeleft = lpc18xx_wdt_get_timeleft,
+ .restart = lpc18xx_wdt_restart,
+};
+
+static void lpc18xx_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int lpc18xx_wdt_probe(struct platform_device *pdev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ lpc18xx_wdt = devm_kzalloc(dev, sizeof(*lpc18xx_wdt), GFP_KERNEL);
+ if (!lpc18xx_wdt)
+ return -ENOMEM;
+
+ lpc18xx_wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lpc18xx_wdt->base))
+ return PTR_ERR(lpc18xx_wdt->base);
+
+ lpc18xx_wdt->reg_clk = devm_clk_get(dev, "reg");
+ if (IS_ERR(lpc18xx_wdt->reg_clk)) {
+ dev_err(dev, "failed to get the reg clock\n");
+ return PTR_ERR(lpc18xx_wdt->reg_clk);
+ }
+
+ lpc18xx_wdt->wdt_clk = devm_clk_get(dev, "wdtclk");
+ if (IS_ERR(lpc18xx_wdt->wdt_clk)) {
+ dev_err(dev, "failed to get the wdt clock\n");
+ return PTR_ERR(lpc18xx_wdt->wdt_clk);
+ }
+
+ ret = clk_prepare_enable(lpc18xx_wdt->reg_clk);
+ if (ret) {
+ dev_err(dev, "could not prepare or enable sys clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, lpc18xx_clk_disable_unprepare,
+ lpc18xx_wdt->reg_clk);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(lpc18xx_wdt->wdt_clk);
+ if (ret) {
+ dev_err(dev, "could not prepare or enable wdt clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, lpc18xx_clk_disable_unprepare,
+ lpc18xx_wdt->wdt_clk);
+ if (ret)
+ return ret;
+
+ /* We use the clock rate to calculate timeouts */
+ lpc18xx_wdt->clk_rate = clk_get_rate(lpc18xx_wdt->wdt_clk);
+ if (lpc18xx_wdt->clk_rate == 0) {
+ dev_err(dev, "failed to get clock rate\n");
+ return -EINVAL;
+ }
+
+ lpc18xx_wdt->wdt_dev.info = &lpc18xx_wdt_info;
+ lpc18xx_wdt->wdt_dev.ops = &lpc18xx_wdt_ops;
+
+ lpc18xx_wdt->wdt_dev.min_timeout = DIV_ROUND_UP(LPC18XX_WDT_TC_MIN *
+ LPC18XX_WDT_CLK_DIV, lpc18xx_wdt->clk_rate);
+
+ lpc18xx_wdt->wdt_dev.max_timeout = (LPC18XX_WDT_TC_MAX *
+ LPC18XX_WDT_CLK_DIV) / lpc18xx_wdt->clk_rate;
+
+ lpc18xx_wdt->wdt_dev.timeout = min(lpc18xx_wdt->wdt_dev.max_timeout,
+ LPC18XX_WDT_DEF_TIMEOUT);
+
+ spin_lock_init(&lpc18xx_wdt->lock);
+
+ lpc18xx_wdt->wdt_dev.parent = dev;
+ watchdog_set_drvdata(&lpc18xx_wdt->wdt_dev, lpc18xx_wdt);
+
+ watchdog_init_timeout(&lpc18xx_wdt->wdt_dev, heartbeat, dev);
+
+ __lpc18xx_wdt_set_timeout(lpc18xx_wdt);
+
+ timer_setup(&lpc18xx_wdt->timer, lpc18xx_wdt_timer_feed, 0);
+
+ watchdog_set_nowayout(&lpc18xx_wdt->wdt_dev, nowayout);
+ watchdog_set_restart_priority(&lpc18xx_wdt->wdt_dev, 128);
+
+ platform_set_drvdata(pdev, lpc18xx_wdt);
+
+ watchdog_stop_on_reboot(&lpc18xx_wdt->wdt_dev);
+ return devm_watchdog_register_device(dev, &lpc18xx_wdt->wdt_dev);
+}
+
+static int lpc18xx_wdt_remove(struct platform_device *pdev)
+{
+ struct lpc18xx_wdt_dev *lpc18xx_wdt = platform_get_drvdata(pdev);
+
+ dev_warn(&pdev->dev, "I quit now, hardware will probably reboot!\n");
+ del_timer_sync(&lpc18xx_wdt->timer);
+
+ return 0;
+}
+
+static const struct of_device_id lpc18xx_wdt_match[] = {
+ { .compatible = "nxp,lpc1850-wwdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lpc18xx_wdt_match);
+
+static struct platform_driver lpc18xx_wdt_driver = {
+ .driver = {
+ .name = "lpc18xx-wdt",
+ .of_match_table = lpc18xx_wdt_match,
+ },
+ .probe = lpc18xx_wdt_probe,
+ .remove = lpc18xx_wdt_remove,
+};
+module_platform_driver(lpc18xx_wdt_driver);
+
+MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>");
+MODULE_DESCRIPTION("NXP LPC18xx Watchdog Timer Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/m54xx_wdt.c b/drivers/watchdog/m54xx_wdt.c
new file mode 100644
index 000000000..f388a769d
--- /dev/null
+++ b/drivers/watchdog/m54xx_wdt.c
@@ -0,0 +1,227 @@
+/*
+ * drivers/watchdog/m54xx_wdt.c
+ *
+ * Watchdog driver for ColdFire MCF547x & MCF548x processors
+ * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be>
+ *
+ * Adapted from the IXP4xx watchdog driver, which carries these notices:
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/ioport.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include <asm/coldfire.h>
+#include <asm/m54xxsim.h>
+#include <asm/m54xxgpt.h>
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int heartbeat = 30; /* (secs) Default is 0.5 minute */
+static unsigned long wdt_status;
+
+#define WDT_IN_USE 0
+#define WDT_OK_TO_CLOSE 1
+
+static void wdt_enable(void)
+{
+ unsigned int gms0;
+
+ /* preserve GPIO usage, if any */
+ gms0 = __raw_readl(MCF_GPT_GMS0);
+ if (gms0 & MCF_GPT_GMS_TMS_GPIO)
+ gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK
+ | MCF_GPT_GMS_OD);
+ else
+ gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD;
+ __raw_writel(gms0, MCF_GPT_GMS0);
+ __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) |
+ MCF_GPT_GCIR_CNT(0xffff), MCF_GPT_GCIR0);
+ gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE;
+ __raw_writel(gms0, MCF_GPT_GMS0);
+}
+
+static void wdt_disable(void)
+{
+ unsigned int gms0;
+
+ /* disable watchdog */
+ gms0 = __raw_readl(MCF_GPT_GMS0);
+ gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE);
+ __raw_writel(gms0, MCF_GPT_GMS0);
+}
+
+static void wdt_keepalive(void)
+{
+ unsigned int gms0;
+
+ gms0 = __raw_readl(MCF_GPT_GMS0);
+ gms0 |= MCF_GPT_GMS_OCPW(0xA5);
+ __raw_writel(gms0, MCF_GPT_GMS0);
+}
+
+static int m54xx_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+ return -EBUSY;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ wdt_enable();
+ return stream_open(inode, file);
+}
+
+static ssize_t m54xx_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ }
+ }
+ wdt_keepalive();
+ }
+ return len;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING,
+ .identity = "Coldfire M54xx Watchdog",
+};
+
+static long m54xx_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int *)arg);
+ if (ret)
+ break;
+
+ if (time <= 0 || time > 30) {
+ ret = -EINVAL;
+ break;
+ }
+
+ heartbeat = time;
+ wdt_enable();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(heartbeat, (int *)arg);
+ break;
+ }
+ return ret;
+}
+
+static int m54xx_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
+ wdt_disable();
+ else {
+ pr_crit("Device closed unexpectedly - timer will not stop\n");
+ wdt_keepalive();
+ }
+ clear_bit(WDT_IN_USE, &wdt_status);
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ return 0;
+}
+
+
+static const struct file_operations m54xx_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = m54xx_wdt_write,
+ .unlocked_ioctl = m54xx_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = m54xx_wdt_open,
+ .release = m54xx_wdt_release,
+};
+
+static struct miscdevice m54xx_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &m54xx_wdt_fops,
+};
+
+static int __init m54xx_wdt_init(void)
+{
+ if (!request_mem_region(MCF_GPT_GCIR0, 4, "Coldfire M54xx Watchdog")) {
+ pr_warn("I/O region busy\n");
+ return -EBUSY;
+ }
+ pr_info("driver is loaded\n");
+
+ return misc_register(&m54xx_wdt_miscdev);
+}
+
+static void __exit m54xx_wdt_exit(void)
+{
+ misc_deregister(&m54xx_wdt_miscdev);
+ release_mem_region(MCF_GPT_GCIR0, 4);
+}
+
+module_init(m54xx_wdt_init);
+module_exit(m54xx_wdt_exit);
+
+MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>");
+MODULE_DESCRIPTION("Coldfire M54xx Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/machzwd.c b/drivers/watchdog/machzwd.c
new file mode 100644
index 000000000..73f2221f6
--- /dev/null
+++ b/drivers/watchdog/machzwd.c
@@ -0,0 +1,453 @@
+// 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 */
+ del_timer_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 {
+ del_timer(&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,
+ .llseek = no_llseek,
+ .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/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c
new file mode 100644
index 000000000..9e1541cfa
--- /dev/null
+++ b/drivers/watchdog/max63xx_wdt.c
@@ -0,0 +1,302 @@
+/*
+ * drivers/char/watchdog/max63xx_wdt.c
+ *
+ * Driver for max63{69,70,71,72,73,74} watchdog timers
+ *
+ * Copyright (C) 2009 Marc Zyngier <maz@misterjones.org>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ *
+ * This driver assumes the watchdog pins are memory mapped (as it is
+ * the case for the Arcom Zeus). Should it be connected over GPIOs or
+ * another interface, some abstraction will have to be introduced.
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mod_devicetable.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/property.h>
+
+#define DEFAULT_HEARTBEAT 60
+#define MAX_HEARTBEAT 60
+
+static unsigned int heartbeat = DEFAULT_HEARTBEAT;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+/*
+ * Memory mapping: a single byte, 3 first lower bits to select bit 3
+ * to ping the watchdog.
+ */
+#define MAX6369_WDSET (7 << 0)
+#define MAX6369_WDI (1 << 3)
+
+#define MAX6369_WDSET_DISABLED 3
+
+static int nodelay;
+
+struct max63xx_wdt {
+ struct watchdog_device wdd;
+ const struct max63xx_timeout *timeout;
+
+ /* memory mapping */
+ void __iomem *base;
+ spinlock_t lock;
+
+ /* WDI and WSET bits write access routines */
+ void (*ping)(struct max63xx_wdt *wdt);
+ void (*set)(struct max63xx_wdt *wdt, u8 set);
+};
+
+/*
+ * The timeout values used are actually the absolute minimum the chip
+ * offers. Typical values on my board are slightly over twice as long
+ * (10s setting ends up with a 25s timeout), and can be up to 3 times
+ * the nominal setting (according to the datasheet). So please take
+ * these values with a grain of salt. Same goes for the initial delay
+ * "feature". Only max6373/74 have a few settings without this initial
+ * delay (selected with the "nodelay" parameter).
+ *
+ * I also decided to remove from the tables any timeout smaller than a
+ * second, as it looked completly overkill...
+ */
+
+/* Timeouts in second */
+struct max63xx_timeout {
+ const u8 wdset;
+ const u8 tdelay;
+ const u8 twd;
+};
+
+static const struct max63xx_timeout max6369_table[] = {
+ { 5, 1, 1 },
+ { 6, 10, 10 },
+ { 7, 60, 60 },
+ { },
+};
+
+static const struct max63xx_timeout max6371_table[] = {
+ { 6, 60, 3 },
+ { 7, 60, 60 },
+ { },
+};
+
+static const struct max63xx_timeout max6373_table[] = {
+ { 2, 60, 1 },
+ { 5, 0, 1 },
+ { 1, 3, 3 },
+ { 7, 60, 10 },
+ { 6, 0, 10 },
+ { },
+};
+
+static const struct max63xx_timeout *
+max63xx_select_timeout(const struct max63xx_timeout *table, int value)
+{
+ while (table->twd) {
+ if (value <= table->twd) {
+ if (nodelay && table->tdelay == 0)
+ return table;
+
+ if (!nodelay)
+ return table;
+ }
+
+ table++;
+ }
+
+ return NULL;
+}
+
+static int max63xx_wdt_ping(struct watchdog_device *wdd)
+{
+ struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdt->ping(wdt);
+ return 0;
+}
+
+static int max63xx_wdt_start(struct watchdog_device *wdd)
+{
+ struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdt->set(wdt, wdt->timeout->wdset);
+
+ /* check for a edge triggered startup */
+ if (wdt->timeout->tdelay == 0)
+ wdt->ping(wdt);
+ return 0;
+}
+
+static int max63xx_wdt_stop(struct watchdog_device *wdd)
+{
+ struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdt->set(wdt, MAX6369_WDSET_DISABLED);
+ return 0;
+}
+
+static const struct watchdog_ops max63xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = max63xx_wdt_start,
+ .stop = max63xx_wdt_stop,
+ .ping = max63xx_wdt_ping,
+};
+
+static const struct watchdog_info max63xx_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "max63xx Watchdog",
+};
+
+static void max63xx_mmap_ping(struct max63xx_wdt *wdt)
+{
+ u8 val;
+
+ spin_lock(&wdt->lock);
+
+ val = __raw_readb(wdt->base);
+
+ __raw_writeb(val | MAX6369_WDI, wdt->base);
+ __raw_writeb(val & ~MAX6369_WDI, wdt->base);
+
+ spin_unlock(&wdt->lock);
+}
+
+static void max63xx_mmap_set(struct max63xx_wdt *wdt, u8 set)
+{
+ u8 val;
+
+ spin_lock(&wdt->lock);
+
+ val = __raw_readb(wdt->base);
+ val &= ~MAX6369_WDSET;
+ val |= set & MAX6369_WDSET;
+ __raw_writeb(val, wdt->base);
+
+ spin_unlock(&wdt->lock);
+}
+
+static int max63xx_mmap_init(struct platform_device *p, struct max63xx_wdt *wdt)
+{
+ wdt->base = devm_platform_ioremap_resource(p, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ spin_lock_init(&wdt->lock);
+
+ wdt->ping = max63xx_mmap_ping;
+ wdt->set = max63xx_mmap_set;
+ return 0;
+}
+
+static int max63xx_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct max63xx_wdt *wdt;
+ const struct max63xx_timeout *table;
+ int err;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ /* Attempt to use fwnode first */
+ table = device_get_match_data(dev);
+ if (!table)
+ table = (struct max63xx_timeout *)pdev->id_entry->driver_data;
+
+ if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
+ heartbeat = DEFAULT_HEARTBEAT;
+
+ wdt->timeout = max63xx_select_timeout(table, heartbeat);
+ if (!wdt->timeout) {
+ dev_err(dev, "unable to satisfy %ds heartbeat request\n",
+ heartbeat);
+ return -EINVAL;
+ }
+
+ err = max63xx_mmap_init(pdev, wdt);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, &wdt->wdd);
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+
+ wdt->wdd.parent = dev;
+ wdt->wdd.timeout = wdt->timeout->twd;
+ wdt->wdd.info = &max63xx_wdt_info;
+ wdt->wdd.ops = &max63xx_wdt_ops;
+
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+
+ err = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (err)
+ return err;
+
+ dev_info(dev, "using %ds heartbeat with %ds initial delay\n",
+ wdt->timeout->twd, wdt->timeout->tdelay);
+ return 0;
+}
+
+static const struct platform_device_id max63xx_id_table[] = {
+ { "max6369_wdt", (kernel_ulong_t)max6369_table, },
+ { "max6370_wdt", (kernel_ulong_t)max6369_table, },
+ { "max6371_wdt", (kernel_ulong_t)max6371_table, },
+ { "max6372_wdt", (kernel_ulong_t)max6371_table, },
+ { "max6373_wdt", (kernel_ulong_t)max6373_table, },
+ { "max6374_wdt", (kernel_ulong_t)max6373_table, },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, max63xx_id_table);
+
+static const struct of_device_id max63xx_dt_id_table[] = {
+ { .compatible = "maxim,max6369", .data = max6369_table, },
+ { .compatible = "maxim,max6370", .data = max6369_table, },
+ { .compatible = "maxim,max6371", .data = max6371_table, },
+ { .compatible = "maxim,max6372", .data = max6371_table, },
+ { .compatible = "maxim,max6373", .data = max6373_table, },
+ { .compatible = "maxim,max6374", .data = max6373_table, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max63xx_dt_id_table);
+
+static struct platform_driver max63xx_wdt_driver = {
+ .probe = max63xx_wdt_probe,
+ .id_table = max63xx_id_table,
+ .driver = {
+ .name = "max63xx_wdt",
+ .of_match_table = max63xx_dt_id_table,
+ },
+};
+
+module_platform_driver(max63xx_wdt_driver);
+
+MODULE_AUTHOR("Marc Zyngier <maz@misterjones.org>");
+MODULE_DESCRIPTION("max63xx Watchdog Driver");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+module_param(nodelay, int, 0);
+MODULE_PARM_DESC(nodelay,
+ "Force selection of a timeout setting without initial delay "
+ "(max6373/74 only, default=0)");
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/max77620_wdt.c b/drivers/watchdog/max77620_wdt.c
new file mode 100644
index 000000000..33835c0b0
--- /dev/null
+++ b/drivers/watchdog/max77620_wdt.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Maxim MAX77620 Watchdog Driver
+ *
+ * Copyright (C) 2016 NVIDIA CORPORATION. All rights reserved.
+ * Copyright (C) 2022 Luca Ceresoli
+ *
+ * Author: Laxman Dewangan <ldewangan@nvidia.com>
+ * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mfd/max77620.h>
+#include <linux/mfd/max77714.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+/**
+ * struct max77620_variant - Data specific to a chip variant
+ * @wdt_info: watchdog descriptor
+ * @reg_onoff_cnfg2: ONOFF_CNFG2 register offset
+ * @reg_cnfg_glbl2: CNFG_GLBL2 register offset
+ * @reg_cnfg_glbl3: CNFG_GLBL3 register offset
+ * @wdtc_mask: WDTC bit mask in CNFG_GLBL3 (=bits to update to ping the watchdog)
+ * @bit_wd_rst_wk: WD_RST_WK bit offset within ONOFF_CNFG2
+ * @cnfg_glbl2_cfg_bits: configuration bits to enable in CNFG_GLBL2 register
+ */
+struct max77620_variant {
+ u8 reg_onoff_cnfg2;
+ u8 reg_cnfg_glbl2;
+ u8 reg_cnfg_glbl3;
+ u8 wdtc_mask;
+ u8 bit_wd_rst_wk;
+ u8 cnfg_glbl2_cfg_bits;
+};
+
+struct max77620_wdt {
+ struct device *dev;
+ struct regmap *rmap;
+ const struct max77620_variant *drv_data;
+ struct watchdog_device wdt_dev;
+};
+
+static const struct max77620_variant max77620_wdt_data = {
+ .reg_onoff_cnfg2 = MAX77620_REG_ONOFFCNFG2,
+ .reg_cnfg_glbl2 = MAX77620_REG_CNFGGLBL2,
+ .reg_cnfg_glbl3 = MAX77620_REG_CNFGGLBL3,
+ .wdtc_mask = MAX77620_WDTC_MASK,
+ .bit_wd_rst_wk = MAX77620_ONOFFCNFG2_WD_RST_WK,
+ /* Set WDT clear in OFF and sleep mode */
+ .cnfg_glbl2_cfg_bits = MAX77620_WDTSLPC | MAX77620_WDTOFFC,
+};
+
+static const struct max77620_variant max77714_wdt_data = {
+ .reg_onoff_cnfg2 = MAX77714_CNFG2_ONOFF,
+ .reg_cnfg_glbl2 = MAX77714_CNFG_GLBL2,
+ .reg_cnfg_glbl3 = MAX77714_CNFG_GLBL3,
+ .wdtc_mask = MAX77714_WDTC,
+ .bit_wd_rst_wk = MAX77714_WD_RST_WK,
+ /* Set WDT clear in sleep mode (there is no WDTOFFC on MAX77714) */
+ .cnfg_glbl2_cfg_bits = MAX77714_WDTSLPC,
+};
+
+static int max77620_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+
+ return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
+ MAX77620_WDTEN, MAX77620_WDTEN);
+}
+
+static int max77620_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+
+ return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
+ MAX77620_WDTEN, 0);
+}
+
+static int max77620_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+
+ return regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl3,
+ wdt->drv_data->wdtc_mask, 0x1);
+}
+
+static int max77620_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct max77620_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned int wdt_timeout;
+ u8 regval;
+ int ret;
+
+ switch (timeout) {
+ case 0 ... 2:
+ regval = MAX77620_TWD_2s;
+ wdt_timeout = 2;
+ break;
+
+ case 3 ... 16:
+ regval = MAX77620_TWD_16s;
+ wdt_timeout = 16;
+ break;
+
+ case 17 ... 64:
+ regval = MAX77620_TWD_64s;
+ wdt_timeout = 64;
+ break;
+
+ default:
+ regval = MAX77620_TWD_128s;
+ wdt_timeout = 128;
+ break;
+ }
+
+ /*
+ * "If the value of TWD needs to be changed, clear the system
+ * watchdog timer first [...], then change the value of TWD."
+ * (MAX77714 datasheet but applies to MAX77620 too)
+ */
+ ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl3,
+ wdt->drv_data->wdtc_mask, 0x1);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
+ MAX77620_TWD_MASK, regval);
+ if (ret < 0)
+ return ret;
+
+ wdt_dev->timeout = wdt_timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info max77620_wdt_info = {
+ .identity = "max77620-watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops max77620_wdt_ops = {
+ .start = max77620_wdt_start,
+ .stop = max77620_wdt_stop,
+ .ping = max77620_wdt_ping,
+ .set_timeout = max77620_wdt_set_timeout,
+};
+
+static int max77620_wdt_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct device *dev = &pdev->dev;
+ struct max77620_wdt *wdt;
+ struct watchdog_device *wdt_dev;
+ unsigned int regval;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->dev = dev;
+ wdt->drv_data = (const struct max77620_variant *) id->driver_data;
+
+ wdt->rmap = dev_get_regmap(dev->parent, NULL);
+ if (!wdt->rmap) {
+ dev_err(wdt->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ wdt_dev = &wdt->wdt_dev;
+ wdt_dev->info = &max77620_wdt_info;
+ wdt_dev->ops = &max77620_wdt_ops;
+ wdt_dev->min_timeout = 2;
+ wdt_dev->max_timeout = 128;
+ wdt_dev->max_hw_heartbeat_ms = 128 * 1000;
+
+ platform_set_drvdata(pdev, wdt);
+
+ /* Enable WD_RST_WK - WDT expire results in a restart */
+ ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_onoff_cnfg2,
+ wdt->drv_data->bit_wd_rst_wk,
+ wdt->drv_data->bit_wd_rst_wk);
+ if (ret < 0) {
+ dev_err(wdt->dev, "Failed to set WD_RST_WK: %d\n", ret);
+ return ret;
+ }
+
+ /* Set the "auto WDT clear" bits available on the chip */
+ ret = regmap_update_bits(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2,
+ wdt->drv_data->cnfg_glbl2_cfg_bits,
+ wdt->drv_data->cnfg_glbl2_cfg_bits);
+ if (ret < 0) {
+ dev_err(wdt->dev, "Failed to set WDT OFF mode: %d\n", ret);
+ return ret;
+ }
+
+ /* Check if WDT running and if yes then set flags properly */
+ ret = regmap_read(wdt->rmap, wdt->drv_data->reg_cnfg_glbl2, &regval);
+ if (ret < 0) {
+ dev_err(wdt->dev, "Failed to read WDT CFG register: %d\n", ret);
+ return ret;
+ }
+
+ switch (regval & MAX77620_TWD_MASK) {
+ case MAX77620_TWD_2s:
+ wdt_dev->timeout = 2;
+ break;
+ case MAX77620_TWD_16s:
+ wdt_dev->timeout = 16;
+ break;
+ case MAX77620_TWD_64s:
+ wdt_dev->timeout = 64;
+ break;
+ default:
+ wdt_dev->timeout = 128;
+ break;
+ }
+
+ if (regval & MAX77620_WDTEN)
+ set_bit(WDOG_HW_RUNNING, &wdt_dev->status);
+
+ watchdog_set_nowayout(wdt_dev, nowayout);
+ watchdog_set_drvdata(wdt_dev, wdt);
+
+ watchdog_stop_on_unregister(wdt_dev);
+ return devm_watchdog_register_device(dev, wdt_dev);
+}
+
+static const struct platform_device_id max77620_wdt_devtype[] = {
+ { "max77620-watchdog", (kernel_ulong_t)&max77620_wdt_data },
+ { "max77714-watchdog", (kernel_ulong_t)&max77714_wdt_data },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, max77620_wdt_devtype);
+
+static struct platform_driver max77620_wdt_driver = {
+ .driver = {
+ .name = "max77620-watchdog",
+ },
+ .probe = max77620_wdt_probe,
+ .id_table = max77620_wdt_devtype,
+};
+
+module_platform_driver(max77620_wdt_driver);
+
+MODULE_DESCRIPTION("Max77620 watchdog timer driver");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+ "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/mei_wdt.c b/drivers/watchdog/mei_wdt.c
new file mode 100644
index 000000000..c7a7235e6
--- /dev/null
+++ b/drivers/watchdog/mei_wdt.c
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Management Engine Interface (Intel MEI) Linux driver
+ * Copyright (c) 2015, Intel Corporation.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/debugfs.h>
+#include <linux/completion.h>
+#include <linux/watchdog.h>
+
+#include <linux/uuid.h>
+#include <linux/mei_cl_bus.h>
+
+/*
+ * iAMT Watchdog Device
+ */
+#define INTEL_AMT_WATCHDOG_ID "iamt_wdt"
+
+#define MEI_WDT_DEFAULT_TIMEOUT 120 /* seconds */
+#define MEI_WDT_MIN_TIMEOUT 120 /* seconds */
+#define MEI_WDT_MAX_TIMEOUT 65535 /* seconds */
+
+/* Commands */
+#define MEI_MANAGEMENT_CONTROL 0x02
+
+/* MEI Management Control version number */
+#define MEI_MC_VERSION_NUMBER 0x10
+
+/* Sub Commands */
+#define MEI_MC_START_WD_TIMER_REQ 0x13
+#define MEI_MC_START_WD_TIMER_RES 0x83
+#define MEI_WDT_STATUS_SUCCESS 0
+#define MEI_WDT_WDSTATE_NOT_REQUIRED 0x1
+#define MEI_MC_STOP_WD_TIMER_REQ 0x14
+
+/**
+ * enum mei_wdt_state - internal watchdog state
+ *
+ * @MEI_WDT_PROBE: wd in probing stage
+ * @MEI_WDT_IDLE: wd is idle and not opened
+ * @MEI_WDT_START: wd was opened, start was called
+ * @MEI_WDT_RUNNING: wd is expecting keep alive pings
+ * @MEI_WDT_STOPPING: wd is stopping and will move to IDLE
+ * @MEI_WDT_NOT_REQUIRED: wd device is not required
+ */
+enum mei_wdt_state {
+ MEI_WDT_PROBE,
+ MEI_WDT_IDLE,
+ MEI_WDT_START,
+ MEI_WDT_RUNNING,
+ MEI_WDT_STOPPING,
+ MEI_WDT_NOT_REQUIRED,
+};
+
+static const char *mei_wdt_state_str(enum mei_wdt_state state)
+{
+ switch (state) {
+ case MEI_WDT_PROBE:
+ return "PROBE";
+ case MEI_WDT_IDLE:
+ return "IDLE";
+ case MEI_WDT_START:
+ return "START";
+ case MEI_WDT_RUNNING:
+ return "RUNNING";
+ case MEI_WDT_STOPPING:
+ return "STOPPING";
+ case MEI_WDT_NOT_REQUIRED:
+ return "NOT_REQUIRED";
+ default:
+ return "unknown";
+ }
+}
+
+/**
+ * struct mei_wdt - mei watchdog driver
+ * @wdd: watchdog device
+ *
+ * @cldev: mei watchdog client device
+ * @state: watchdog internal state
+ * @resp_required: ping required response
+ * @response: ping response completion
+ * @unregister: unregister worker
+ * @reg_lock: watchdog device registration lock
+ * @timeout: watchdog current timeout
+ *
+ * @dbgfs_dir: debugfs dir entry
+ */
+struct mei_wdt {
+ struct watchdog_device wdd;
+
+ struct mei_cl_device *cldev;
+ enum mei_wdt_state state;
+ bool resp_required;
+ struct completion response;
+ struct work_struct unregister;
+ struct mutex reg_lock;
+ u16 timeout;
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+ struct dentry *dbgfs_dir;
+#endif /* CONFIG_DEBUG_FS */
+};
+
+/**
+ * struct mei_mc_hdr - Management Control Command Header
+ *
+ * @command: Management Control (0x2)
+ * @bytecount: Number of bytes in the message beyond this byte
+ * @subcommand: Management Control Subcommand
+ * @versionnumber: Management Control Version (0x10)
+ */
+struct mei_mc_hdr {
+ u8 command;
+ u8 bytecount;
+ u8 subcommand;
+ u8 versionnumber;
+};
+
+/**
+ * struct mei_wdt_start_request - watchdog start/ping
+ *
+ * @hdr: Management Control Command Header
+ * @timeout: timeout value
+ * @reserved: reserved (legacy)
+ */
+struct mei_wdt_start_request {
+ struct mei_mc_hdr hdr;
+ u16 timeout;
+ u8 reserved[17];
+} __packed;
+
+/**
+ * struct mei_wdt_start_response - watchdog start/ping response
+ *
+ * @hdr: Management Control Command Header
+ * @status: operation status
+ * @wdstate: watchdog status bit mask
+ */
+struct mei_wdt_start_response {
+ struct mei_mc_hdr hdr;
+ u8 status;
+ u8 wdstate;
+} __packed;
+
+/**
+ * struct mei_wdt_stop_request - watchdog stop
+ *
+ * @hdr: Management Control Command Header
+ */
+struct mei_wdt_stop_request {
+ struct mei_mc_hdr hdr;
+} __packed;
+
+/**
+ * mei_wdt_ping - send wd start/ping command
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 on success,
+ * negative errno code on failure
+ */
+static int mei_wdt_ping(struct mei_wdt *wdt)
+{
+ struct mei_wdt_start_request req;
+ const size_t req_len = sizeof(req);
+ int ret;
+
+ memset(&req, 0, req_len);
+ req.hdr.command = MEI_MANAGEMENT_CONTROL;
+ req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand);
+ req.hdr.subcommand = MEI_MC_START_WD_TIMER_REQ;
+ req.hdr.versionnumber = MEI_MC_VERSION_NUMBER;
+ req.timeout = wdt->timeout;
+
+ ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * mei_wdt_stop - send wd stop command
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 on success,
+ * negative errno code on failure
+ */
+static int mei_wdt_stop(struct mei_wdt *wdt)
+{
+ struct mei_wdt_stop_request req;
+ const size_t req_len = sizeof(req);
+ int ret;
+
+ memset(&req, 0, req_len);
+ req.hdr.command = MEI_MANAGEMENT_CONTROL;
+ req.hdr.bytecount = req_len - offsetof(struct mei_mc_hdr, subcommand);
+ req.hdr.subcommand = MEI_MC_STOP_WD_TIMER_REQ;
+ req.hdr.versionnumber = MEI_MC_VERSION_NUMBER;
+
+ ret = mei_cldev_send(wdt->cldev, (u8 *)&req, req_len);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * mei_wdt_ops_start - wd start command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 on success or -ENODEV;
+ */
+static int mei_wdt_ops_start(struct watchdog_device *wdd)
+{
+ struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdt->state = MEI_WDT_START;
+ wdd->timeout = wdt->timeout;
+ return 0;
+}
+
+/**
+ * mei_wdt_ops_stop - wd stop command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_ops_stop(struct watchdog_device *wdd)
+{
+ struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ if (wdt->state != MEI_WDT_RUNNING)
+ return 0;
+
+ wdt->state = MEI_WDT_STOPPING;
+
+ ret = mei_wdt_stop(wdt);
+ if (ret)
+ return ret;
+
+ wdt->state = MEI_WDT_IDLE;
+
+ return 0;
+}
+
+/**
+ * mei_wdt_ops_ping - wd ping command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ *
+ * Return: 0 if success, negative errno code on failure
+ */
+static int mei_wdt_ops_ping(struct watchdog_device *wdd)
+{
+ struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ if (wdt->state != MEI_WDT_START && wdt->state != MEI_WDT_RUNNING)
+ return 0;
+
+ if (wdt->resp_required)
+ init_completion(&wdt->response);
+
+ wdt->state = MEI_WDT_RUNNING;
+ ret = mei_wdt_ping(wdt);
+ if (ret)
+ return ret;
+
+ if (wdt->resp_required)
+ ret = wait_for_completion_killable(&wdt->response);
+
+ return ret;
+}
+
+/**
+ * mei_wdt_ops_set_timeout - wd set timeout command from the watchdog core.
+ *
+ * @wdd: watchdog device
+ * @timeout: timeout value to set
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_ops_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+
+ struct mei_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ /* valid value is already checked by the caller */
+ wdt->timeout = timeout;
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_ops wd_ops = {
+ .owner = THIS_MODULE,
+ .start = mei_wdt_ops_start,
+ .stop = mei_wdt_ops_stop,
+ .ping = mei_wdt_ops_ping,
+ .set_timeout = mei_wdt_ops_set_timeout,
+};
+
+/* not const as the firmware_version field need to be retrieved */
+static struct watchdog_info wd_info = {
+ .identity = INTEL_AMT_WATCHDOG_ID,
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_ALARMONLY,
+};
+
+/**
+ * __mei_wdt_is_registered - check if wdt is registered
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: true if the wdt is registered with the watchdog subsystem
+ * Locking: should be called under wdt->reg_lock
+ */
+static inline bool __mei_wdt_is_registered(struct mei_wdt *wdt)
+{
+ return !!watchdog_get_drvdata(&wdt->wdd);
+}
+
+/**
+ * mei_wdt_unregister - unregister from the watchdog subsystem
+ *
+ * @wdt: mei watchdog device
+ */
+static void mei_wdt_unregister(struct mei_wdt *wdt)
+{
+ mutex_lock(&wdt->reg_lock);
+
+ if (__mei_wdt_is_registered(wdt)) {
+ watchdog_unregister_device(&wdt->wdd);
+ watchdog_set_drvdata(&wdt->wdd, NULL);
+ memset(&wdt->wdd, 0, sizeof(wdt->wdd));
+ }
+
+ mutex_unlock(&wdt->reg_lock);
+}
+
+/**
+ * mei_wdt_register - register with the watchdog subsystem
+ *
+ * @wdt: mei watchdog device
+ *
+ * Return: 0 if success, negative errno code for failure
+ */
+static int mei_wdt_register(struct mei_wdt *wdt)
+{
+ struct device *dev;
+ int ret;
+
+ if (!wdt || !wdt->cldev)
+ return -EINVAL;
+
+ dev = &wdt->cldev->dev;
+
+ mutex_lock(&wdt->reg_lock);
+
+ if (__mei_wdt_is_registered(wdt)) {
+ ret = 0;
+ goto out;
+ }
+
+ wdt->wdd.info = &wd_info;
+ wdt->wdd.ops = &wd_ops;
+ wdt->wdd.parent = dev;
+ wdt->wdd.timeout = MEI_WDT_DEFAULT_TIMEOUT;
+ wdt->wdd.min_timeout = MEI_WDT_MIN_TIMEOUT;
+ wdt->wdd.max_timeout = MEI_WDT_MAX_TIMEOUT;
+
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ watchdog_stop_on_reboot(&wdt->wdd);
+ watchdog_stop_on_unregister(&wdt->wdd);
+
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret)
+ watchdog_set_drvdata(&wdt->wdd, NULL);
+
+ wdt->state = MEI_WDT_IDLE;
+
+out:
+ mutex_unlock(&wdt->reg_lock);
+ return ret;
+}
+
+static void mei_wdt_unregister_work(struct work_struct *work)
+{
+ struct mei_wdt *wdt = container_of(work, struct mei_wdt, unregister);
+
+ mei_wdt_unregister(wdt);
+}
+
+/**
+ * mei_wdt_rx - callback for data receive
+ *
+ * @cldev: bus device
+ */
+static void mei_wdt_rx(struct mei_cl_device *cldev)
+{
+ struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
+ struct mei_wdt_start_response res;
+ const size_t res_len = sizeof(res);
+ int ret;
+
+ ret = mei_cldev_recv(wdt->cldev, (u8 *)&res, res_len);
+ if (ret < 0) {
+ dev_err(&cldev->dev, "failure in recv %d\n", ret);
+ return;
+ }
+
+ /* Empty response can be sent on stop */
+ if (ret == 0)
+ return;
+
+ if (ret < sizeof(struct mei_mc_hdr)) {
+ dev_err(&cldev->dev, "recv small data %d\n", ret);
+ return;
+ }
+
+ if (res.hdr.command != MEI_MANAGEMENT_CONTROL ||
+ res.hdr.versionnumber != MEI_MC_VERSION_NUMBER) {
+ dev_err(&cldev->dev, "wrong command received\n");
+ return;
+ }
+
+ if (res.hdr.subcommand != MEI_MC_START_WD_TIMER_RES) {
+ dev_warn(&cldev->dev, "unsupported command %d :%s[%d]\n",
+ res.hdr.subcommand,
+ mei_wdt_state_str(wdt->state),
+ wdt->state);
+ return;
+ }
+
+ /* Run the unregistration in a worker as this can be
+ * run only after ping completion, otherwise the flow will
+ * deadlock on watchdog core mutex.
+ */
+ if (wdt->state == MEI_WDT_RUNNING) {
+ if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
+ wdt->state = MEI_WDT_NOT_REQUIRED;
+ schedule_work(&wdt->unregister);
+ }
+ goto out;
+ }
+
+ if (wdt->state == MEI_WDT_PROBE) {
+ if (res.wdstate & MEI_WDT_WDSTATE_NOT_REQUIRED) {
+ wdt->state = MEI_WDT_NOT_REQUIRED;
+ } else {
+ /* stop the watchdog and register watchdog device */
+ mei_wdt_stop(wdt);
+ mei_wdt_register(wdt);
+ }
+ return;
+ }
+
+ dev_warn(&cldev->dev, "not in correct state %s[%d]\n",
+ mei_wdt_state_str(wdt->state), wdt->state);
+
+out:
+ if (!completion_done(&wdt->response))
+ complete(&wdt->response);
+}
+
+/**
+ * mei_wdt_notif - callback for event notification
+ *
+ * @cldev: bus device
+ */
+static void mei_wdt_notif(struct mei_cl_device *cldev)
+{
+ struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
+
+ if (wdt->state != MEI_WDT_NOT_REQUIRED)
+ return;
+
+ mei_wdt_register(wdt);
+}
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+
+static ssize_t mei_dbgfs_read_activation(struct file *file, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ struct mei_wdt *wdt = file->private_data;
+ const size_t bufsz = 32;
+ char buf[32];
+ ssize_t pos;
+
+ mutex_lock(&wdt->reg_lock);
+ pos = scnprintf(buf, bufsz, "%s\n",
+ __mei_wdt_is_registered(wdt) ? "activated" : "deactivated");
+ mutex_unlock(&wdt->reg_lock);
+
+ return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
+}
+
+static const struct file_operations dbgfs_fops_activation = {
+ .open = simple_open,
+ .read = mei_dbgfs_read_activation,
+ .llseek = generic_file_llseek,
+};
+
+static ssize_t mei_dbgfs_read_state(struct file *file, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ struct mei_wdt *wdt = file->private_data;
+ char buf[32];
+ ssize_t pos;
+
+ pos = scnprintf(buf, sizeof(buf), "state: %s\n",
+ mei_wdt_state_str(wdt->state));
+
+ return simple_read_from_buffer(ubuf, cnt, ppos, buf, pos);
+}
+
+static const struct file_operations dbgfs_fops_state = {
+ .open = simple_open,
+ .read = mei_dbgfs_read_state,
+ .llseek = generic_file_llseek,
+};
+
+static void dbgfs_unregister(struct mei_wdt *wdt)
+{
+ debugfs_remove_recursive(wdt->dbgfs_dir);
+ wdt->dbgfs_dir = NULL;
+}
+
+static void dbgfs_register(struct mei_wdt *wdt)
+{
+ struct dentry *dir;
+
+ dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
+ wdt->dbgfs_dir = dir;
+
+ debugfs_create_file("state", S_IRUSR, dir, wdt, &dbgfs_fops_state);
+
+ debugfs_create_file("activation", S_IRUSR, dir, wdt,
+ &dbgfs_fops_activation);
+}
+
+#else
+
+static inline void dbgfs_unregister(struct mei_wdt *wdt) {}
+static inline void dbgfs_register(struct mei_wdt *wdt) {}
+#endif /* CONFIG_DEBUG_FS */
+
+static int mei_wdt_probe(struct mei_cl_device *cldev,
+ const struct mei_cl_device_id *id)
+{
+ struct mei_wdt *wdt;
+ int ret;
+
+ wdt = kzalloc(sizeof(struct mei_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->timeout = MEI_WDT_DEFAULT_TIMEOUT;
+ wdt->state = MEI_WDT_PROBE;
+ wdt->cldev = cldev;
+ wdt->resp_required = mei_cldev_ver(cldev) > 0x1;
+ mutex_init(&wdt->reg_lock);
+ init_completion(&wdt->response);
+ INIT_WORK(&wdt->unregister, mei_wdt_unregister_work);
+
+ mei_cldev_set_drvdata(cldev, wdt);
+
+ ret = mei_cldev_enable(cldev);
+ if (ret < 0) {
+ dev_err(&cldev->dev, "Could not enable cl device\n");
+ goto err_out;
+ }
+
+ ret = mei_cldev_register_rx_cb(wdt->cldev, mei_wdt_rx);
+ if (ret) {
+ dev_err(&cldev->dev, "Could not reg rx event ret=%d\n", ret);
+ goto err_disable;
+ }
+
+ ret = mei_cldev_register_notif_cb(wdt->cldev, mei_wdt_notif);
+ /* on legacy devices notification is not supported
+ */
+ if (ret && ret != -EOPNOTSUPP) {
+ dev_err(&cldev->dev, "Could not reg notif event ret=%d\n", ret);
+ goto err_disable;
+ }
+
+ wd_info.firmware_version = mei_cldev_ver(cldev);
+
+ if (wdt->resp_required)
+ ret = mei_wdt_ping(wdt);
+ else
+ ret = mei_wdt_register(wdt);
+
+ if (ret)
+ goto err_disable;
+
+ dbgfs_register(wdt);
+
+ return 0;
+
+err_disable:
+ mei_cldev_disable(cldev);
+
+err_out:
+ kfree(wdt);
+
+ return ret;
+}
+
+static void mei_wdt_remove(struct mei_cl_device *cldev)
+{
+ struct mei_wdt *wdt = mei_cldev_get_drvdata(cldev);
+
+ /* Free the caller in case of fw initiated or unexpected reset */
+ if (!completion_done(&wdt->response))
+ complete(&wdt->response);
+
+ cancel_work_sync(&wdt->unregister);
+
+ mei_wdt_unregister(wdt);
+
+ mei_cldev_disable(cldev);
+
+ dbgfs_unregister(wdt);
+
+ kfree(wdt);
+}
+
+#define MEI_UUID_WD UUID_LE(0x05B79A6F, 0x4628, 0x4D7F, \
+ 0x89, 0x9D, 0xA9, 0x15, 0x14, 0xCB, 0x32, 0xAB)
+
+static const struct mei_cl_device_id mei_wdt_tbl[] = {
+ { .uuid = MEI_UUID_WD, .version = MEI_CL_VERSION_ANY },
+ /* required last entry */
+ { }
+};
+MODULE_DEVICE_TABLE(mei, mei_wdt_tbl);
+
+static struct mei_cl_driver mei_wdt_driver = {
+ .id_table = mei_wdt_tbl,
+ .name = KBUILD_MODNAME,
+
+ .probe = mei_wdt_probe,
+ .remove = mei_wdt_remove,
+};
+
+module_mei_cl_driver(mei_wdt_driver);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Device driver for Intel MEI iAMT watchdog");
diff --git a/drivers/watchdog/mena21_wdt.c b/drivers/watchdog/mena21_wdt.c
new file mode 100644
index 000000000..99d2359d5
--- /dev/null
+++ b/drivers/watchdog/mena21_wdt.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for the A21 VME CPU Boards
+ *
+ * Copyright (C) 2013 MEN Mikro Elektronik Nuernberg GmbH
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/of.h>
+
+#define NUM_GPIOS 6
+
+enum a21_wdt_gpios {
+ GPIO_WD_ENAB,
+ GPIO_WD_FAST,
+ GPIO_WD_TRIG,
+ GPIO_WD_RST0,
+ GPIO_WD_RST1,
+ GPIO_WD_RST2,
+};
+
+struct a21_wdt_drv {
+ struct watchdog_device wdt;
+ struct gpio_desc *gpios[NUM_GPIOS];
+};
+
+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) ")");
+
+static unsigned int a21_wdt_get_bootstatus(struct a21_wdt_drv *drv)
+{
+ int reset = 0;
+
+ reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST0]) ? (1 << 0) : 0;
+ reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST1]) ? (1 << 1) : 0;
+ reset |= gpiod_get_value(drv->gpios[GPIO_WD_RST2]) ? (1 << 2) : 0;
+
+ return reset;
+}
+
+static int a21_wdt_start(struct watchdog_device *wdt)
+{
+ struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
+
+ gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 1);
+
+ return 0;
+}
+
+static int a21_wdt_stop(struct watchdog_device *wdt)
+{
+ struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
+
+ gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 0);
+
+ return 0;
+}
+
+static int a21_wdt_ping(struct watchdog_device *wdt)
+{
+ struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
+
+ gpiod_set_value(drv->gpios[GPIO_WD_TRIG], 0);
+ ndelay(10);
+ gpiod_set_value(drv->gpios[GPIO_WD_TRIG], 1);
+
+ return 0;
+}
+
+static int a21_wdt_set_timeout(struct watchdog_device *wdt,
+ unsigned int timeout)
+{
+ struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt);
+
+ if (timeout != 1 && timeout != 30) {
+ dev_err(wdt->parent, "Only 1 and 30 allowed as timeout\n");
+ return -EINVAL;
+ }
+
+ if (timeout == 30 && wdt->timeout == 1) {
+ dev_err(wdt->parent,
+ "Transition from fast to slow mode not allowed\n");
+ return -EINVAL;
+ }
+
+ if (timeout == 1)
+ gpiod_set_value(drv->gpios[GPIO_WD_FAST], 1);
+ else
+ gpiod_set_value(drv->gpios[GPIO_WD_FAST], 0);
+
+ wdt->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info a21_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "MEN A21 Watchdog",
+};
+
+static const struct watchdog_ops a21_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = a21_wdt_start,
+ .stop = a21_wdt_stop,
+ .ping = a21_wdt_ping,
+ .set_timeout = a21_wdt_set_timeout,
+};
+
+static struct watchdog_device a21_wdt = {
+ .info = &a21_wdt_info,
+ .ops = &a21_wdt_ops,
+ .min_timeout = 1,
+ .max_timeout = 30,
+};
+
+static int a21_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct a21_wdt_drv *drv;
+ unsigned int reset = 0;
+ int num_gpios;
+ int ret;
+ int i;
+
+ drv = devm_kzalloc(dev, sizeof(struct a21_wdt_drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ num_gpios = gpiod_count(dev, NULL);
+ if (num_gpios != NUM_GPIOS) {
+ dev_err(dev, "gpios DT property wrong, got %d want %d",
+ num_gpios, NUM_GPIOS);
+ return -ENODEV;
+ }
+
+ /* Request the used GPIOs */
+ for (i = 0; i < num_gpios; i++) {
+ enum gpiod_flags gflags;
+
+ if (i < GPIO_WD_RST0)
+ gflags = GPIOD_ASIS;
+ else
+ gflags = GPIOD_IN;
+ drv->gpios[i] = devm_gpiod_get_index(dev, NULL, i, gflags);
+ if (IS_ERR(drv->gpios[i]))
+ return PTR_ERR(drv->gpios[i]);
+
+ gpiod_set_consumer_name(drv->gpios[i], "MEN A21 Watchdog");
+
+ /*
+ * Retrieve the initial value from the GPIOs that should be
+ * output, then set up the line as output with that value.
+ */
+ if (i < GPIO_WD_RST0) {
+ int val;
+
+ val = gpiod_get_value(drv->gpios[i]);
+ gpiod_direction_output(drv->gpios[i], val);
+ }
+ }
+
+ watchdog_init_timeout(&a21_wdt, 30, dev);
+ watchdog_set_nowayout(&a21_wdt, nowayout);
+ watchdog_set_drvdata(&a21_wdt, drv);
+ a21_wdt.parent = dev;
+
+ reset = a21_wdt_get_bootstatus(drv);
+ if (reset == 2)
+ a21_wdt.bootstatus |= WDIOF_EXTERN1;
+ else if (reset == 4)
+ a21_wdt.bootstatus |= WDIOF_CARDRESET;
+ else if (reset == 5)
+ a21_wdt.bootstatus |= WDIOF_POWERUNDER;
+ else if (reset == 7)
+ a21_wdt.bootstatus |= WDIOF_EXTERN2;
+
+ drv->wdt = a21_wdt;
+ dev_set_drvdata(dev, drv);
+
+ ret = devm_watchdog_register_device(dev, &a21_wdt);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "MEN A21 watchdog timer driver enabled\n");
+
+ return 0;
+}
+
+static void a21_wdt_shutdown(struct platform_device *pdev)
+{
+ struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev);
+
+ gpiod_set_value(drv->gpios[GPIO_WD_ENAB], 0);
+}
+
+static const struct of_device_id a21_wdt_ids[] = {
+ { .compatible = "men,a021-wdt" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, a21_wdt_ids);
+
+static struct platform_driver a21_wdt_driver = {
+ .probe = a21_wdt_probe,
+ .shutdown = a21_wdt_shutdown,
+ .driver = {
+ .name = "a21-watchdog",
+ .of_match_table = a21_wdt_ids,
+ },
+};
+
+module_platform_driver(a21_wdt_driver);
+
+MODULE_AUTHOR("MEN Mikro Elektronik");
+MODULE_DESCRIPTION("MEN A21 Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:a21-watchdog");
diff --git a/drivers/watchdog/menf21bmc_wdt.c b/drivers/watchdog/menf21bmc_wdt.c
new file mode 100644
index 000000000..81ebdfc37
--- /dev/null
+++ b/drivers/watchdog/menf21bmc_wdt.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MEN 14F021P00 Board Management Controller (BMC) Watchdog Driver.
+ *
+ * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+
+#define DEVNAME "menf21bmc_wdt"
+
+#define BMC_CMD_WD_ON 0x11
+#define BMC_CMD_WD_OFF 0x12
+#define BMC_CMD_WD_TRIG 0x13
+#define BMC_CMD_WD_TIME 0x14
+#define BMC_CMD_WD_STATE 0x17
+#define BMC_WD_OFF_VAL 0x69
+#define BMC_CMD_RST_RSN 0x92
+
+#define BMC_WD_TIMEOUT_MIN 1 /* in sec */
+#define BMC_WD_TIMEOUT_MAX 6553 /* in sec */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct menf21bmc_wdt {
+ struct watchdog_device wdt;
+ struct i2c_client *i2c_client;
+};
+
+static int menf21bmc_wdt_set_bootstatus(struct menf21bmc_wdt *data)
+{
+ int rst_rsn;
+
+ rst_rsn = i2c_smbus_read_byte_data(data->i2c_client, BMC_CMD_RST_RSN);
+ if (rst_rsn < 0)
+ return rst_rsn;
+
+ if (rst_rsn == 0x02)
+ data->wdt.bootstatus |= WDIOF_CARDRESET;
+ else if (rst_rsn == 0x05)
+ data->wdt.bootstatus |= WDIOF_EXTERN1;
+ else if (rst_rsn == 0x06)
+ data->wdt.bootstatus |= WDIOF_EXTERN2;
+ else if (rst_rsn == 0x0A)
+ data->wdt.bootstatus |= WDIOF_POWERUNDER;
+
+ return 0;
+}
+
+static int menf21bmc_wdt_start(struct watchdog_device *wdt)
+{
+ struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
+
+ return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_ON);
+}
+
+static int menf21bmc_wdt_stop(struct watchdog_device *wdt)
+{
+ struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
+
+ return i2c_smbus_write_byte_data(drv_data->i2c_client,
+ BMC_CMD_WD_OFF, BMC_WD_OFF_VAL);
+}
+
+static int
+menf21bmc_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout)
+{
+ int ret;
+ struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
+
+ /*
+ * BMC Watchdog does have a resolution of 100ms.
+ * Watchdog API defines the timeout in seconds, so we have to
+ * multiply the value.
+ */
+ ret = i2c_smbus_write_word_data(drv_data->i2c_client,
+ BMC_CMD_WD_TIME, timeout * 10);
+ if (ret < 0)
+ return ret;
+
+ wdt->timeout = timeout;
+
+ return 0;
+}
+
+static int menf21bmc_wdt_ping(struct watchdog_device *wdt)
+{
+ struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt);
+
+ return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_TRIG);
+}
+
+static const struct watchdog_info menf21bmc_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = DEVNAME,
+};
+
+static const struct watchdog_ops menf21bmc_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = menf21bmc_wdt_start,
+ .stop = menf21bmc_wdt_stop,
+ .ping = menf21bmc_wdt_ping,
+ .set_timeout = menf21bmc_wdt_settimeout,
+};
+
+static int menf21bmc_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret, bmc_timeout;
+ struct menf21bmc_wdt *drv_data;
+ struct i2c_client *i2c_client = to_i2c_client(dev->parent);
+
+ drv_data = devm_kzalloc(dev, sizeof(struct menf21bmc_wdt), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ drv_data->wdt.ops = &menf21bmc_wdt_ops;
+ drv_data->wdt.info = &menf21bmc_wdt_info;
+ drv_data->wdt.min_timeout = BMC_WD_TIMEOUT_MIN;
+ drv_data->wdt.max_timeout = BMC_WD_TIMEOUT_MAX;
+ drv_data->wdt.parent = dev;
+ drv_data->i2c_client = i2c_client;
+
+ /*
+ * Get the current wdt timeout value from the BMC because
+ * the BMC will save the value set before if the system restarts.
+ */
+ bmc_timeout = i2c_smbus_read_word_data(drv_data->i2c_client,
+ BMC_CMD_WD_TIME);
+ if (bmc_timeout < 0) {
+ dev_err(dev, "failed to get current WDT timeout\n");
+ return bmc_timeout;
+ }
+
+ watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, dev);
+ watchdog_set_nowayout(&drv_data->wdt, nowayout);
+ watchdog_set_drvdata(&drv_data->wdt, drv_data);
+ platform_set_drvdata(pdev, drv_data);
+
+ ret = menf21bmc_wdt_set_bootstatus(drv_data);
+ if (ret < 0) {
+ dev_err(dev, "failed to set Watchdog bootstatus\n");
+ return ret;
+ }
+
+ ret = devm_watchdog_register_device(dev, &drv_data->wdt);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "MEN 14F021P00 BMC Watchdog device enabled\n");
+
+ return 0;
+}
+
+static void menf21bmc_wdt_shutdown(struct platform_device *pdev)
+{
+ struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev);
+
+ i2c_smbus_write_word_data(drv_data->i2c_client,
+ BMC_CMD_WD_OFF, BMC_WD_OFF_VAL);
+}
+
+static struct platform_driver menf21bmc_wdt = {
+ .driver = {
+ .name = DEVNAME,
+ },
+ .probe = menf21bmc_wdt_probe,
+ .shutdown = menf21bmc_wdt_shutdown,
+};
+
+module_platform_driver(menf21bmc_wdt);
+
+MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver");
+MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:menf21bmc_wdt");
diff --git a/drivers/watchdog/menz69_wdt.c b/drivers/watchdog/menz69_wdt.c
new file mode 100644
index 000000000..bca0938f3
--- /dev/null
+++ b/drivers/watchdog/menz69_wdt.c
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for the MEN z069 IP-Core
+ *
+ * Copyright (C) 2018 Johannes Thumshirn <jth@kernel.org>
+ */
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mcb.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+
+struct men_z069_drv {
+ struct watchdog_device wdt;
+ void __iomem *base;
+ struct resource *mem;
+};
+
+#define MEN_Z069_WTR 0x10
+#define MEN_Z069_WTR_WDEN BIT(15)
+#define MEN_Z069_WTR_WDET_MASK 0x7fff
+#define MEN_Z069_WVR 0x14
+
+#define MEN_Z069_TIMER_FREQ 500 /* 500 Hz */
+#define MEN_Z069_WDT_COUNTER_MIN 1
+#define MEN_Z069_WDT_COUNTER_MAX 0x7fff
+#define MEN_Z069_DEFAULT_TIMEOUT 30
+
+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) ")");
+
+static int men_z069_wdt_start(struct watchdog_device *wdt)
+{
+ struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
+ u16 val;
+
+ val = readw(drv->base + MEN_Z069_WTR);
+ val |= MEN_Z069_WTR_WDEN;
+ writew(val, drv->base + MEN_Z069_WTR);
+
+ return 0;
+}
+
+static int men_z069_wdt_stop(struct watchdog_device *wdt)
+{
+ struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
+ u16 val;
+
+ val = readw(drv->base + MEN_Z069_WTR);
+ val &= ~MEN_Z069_WTR_WDEN;
+ writew(val, drv->base + MEN_Z069_WTR);
+
+ return 0;
+}
+
+static int men_z069_wdt_ping(struct watchdog_device *wdt)
+{
+ struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
+ u16 val;
+
+ /* The watchdog trigger value toggles between 0x5555 and 0xaaaa */
+ val = readw(drv->base + MEN_Z069_WVR);
+ val ^= 0xffff;
+ writew(val, drv->base + MEN_Z069_WVR);
+
+ return 0;
+}
+
+static int men_z069_wdt_set_timeout(struct watchdog_device *wdt,
+ unsigned int timeout)
+{
+ struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
+ u16 reg, val, ena;
+
+ wdt->timeout = timeout;
+ val = timeout * MEN_Z069_TIMER_FREQ;
+
+ reg = readw(drv->base + MEN_Z069_WVR);
+ ena = reg & MEN_Z069_WTR_WDEN;
+ reg = ena | val;
+ writew(reg, drv->base + MEN_Z069_WTR);
+
+ return 0;
+}
+
+static const struct watchdog_info men_z069_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "MEN z069 Watchdog",
+};
+
+static const struct watchdog_ops men_z069_ops = {
+ .owner = THIS_MODULE,
+ .start = men_z069_wdt_start,
+ .stop = men_z069_wdt_stop,
+ .ping = men_z069_wdt_ping,
+ .set_timeout = men_z069_wdt_set_timeout,
+};
+
+static int men_z069_probe(struct mcb_device *dev,
+ const struct mcb_device_id *id)
+{
+ struct men_z069_drv *drv;
+ struct resource *mem;
+
+ drv = devm_kzalloc(&dev->dev, sizeof(struct men_z069_drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ mem = mcb_request_mem(dev, "z069-wdt");
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ drv->base = devm_ioremap(&dev->dev, mem->start, resource_size(mem));
+ if (drv->base == NULL)
+ goto release_mem;
+
+ drv->mem = mem;
+ drv->wdt.info = &men_z069_info;
+ drv->wdt.ops = &men_z069_ops;
+ drv->wdt.timeout = MEN_Z069_DEFAULT_TIMEOUT;
+ drv->wdt.min_timeout = 1;
+ drv->wdt.max_timeout = MEN_Z069_WDT_COUNTER_MAX / MEN_Z069_TIMER_FREQ;
+
+ watchdog_init_timeout(&drv->wdt, 0, &dev->dev);
+ watchdog_set_nowayout(&drv->wdt, nowayout);
+ watchdog_set_drvdata(&drv->wdt, drv);
+ drv->wdt.parent = &dev->dev;
+ mcb_set_drvdata(dev, drv);
+
+ return watchdog_register_device(&drv->wdt);
+
+release_mem:
+ mcb_release_mem(mem);
+ return -ENOMEM;
+}
+
+static void men_z069_remove(struct mcb_device *dev)
+{
+ struct men_z069_drv *drv = mcb_get_drvdata(dev);
+
+ watchdog_unregister_device(&drv->wdt);
+ mcb_release_mem(drv->mem);
+}
+
+static const struct mcb_device_id men_z069_ids[] = {
+ { .device = 0x45 },
+ { }
+};
+MODULE_DEVICE_TABLE(mcb, men_z069_ids);
+
+static struct mcb_driver men_z069_driver = {
+ .driver = {
+ .name = "z069-wdt",
+ .owner = THIS_MODULE,
+ },
+ .probe = men_z069_probe,
+ .remove = men_z069_remove,
+ .id_table = men_z069_ids,
+};
+module_mcb_driver(men_z069_driver);
+
+MODULE_AUTHOR("Johannes Thumshirn <jth@kernel.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("mcb:16z069");
+MODULE_IMPORT_NS(MCB);
diff --git a/drivers/watchdog/meson_gxbb_wdt.c b/drivers/watchdog/meson_gxbb_wdt.c
new file mode 100644
index 000000000..981a2f7c3
--- /dev/null
+++ b/drivers/watchdog/meson_gxbb_wdt.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/*
+ * Copyright (c) 2016 BayLibre, SAS.
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ *
+ */
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define DEFAULT_TIMEOUT 30 /* seconds */
+
+#define GXBB_WDT_CTRL_REG 0x0
+#define GXBB_WDT_TCNT_REG 0x8
+#define GXBB_WDT_RSET_REG 0xc
+
+#define GXBB_WDT_CTRL_CLKDIV_EN BIT(25)
+#define GXBB_WDT_CTRL_CLK_EN BIT(24)
+#define GXBB_WDT_CTRL_EE_RESET BIT(21)
+#define GXBB_WDT_CTRL_EN BIT(18)
+#define GXBB_WDT_CTRL_DIV_MASK (BIT(18) - 1)
+
+#define GXBB_WDT_TCNT_SETUP_MASK (BIT(16) - 1)
+#define GXBB_WDT_TCNT_CNT_SHIFT 16
+
+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) ")");
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+
+struct meson_gxbb_wdt {
+ void __iomem *reg_base;
+ struct watchdog_device wdt_dev;
+ struct clk *clk;
+};
+
+static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
+
+ writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
+ data->reg_base + GXBB_WDT_CTRL_REG);
+
+ return 0;
+}
+
+static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
+
+ writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
+ data->reg_base + GXBB_WDT_CTRL_REG);
+
+ return 0;
+}
+
+static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
+
+ writel(0, data->reg_base + GXBB_WDT_RSET_REG);
+
+ return 0;
+}
+
+static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
+ unsigned long tcnt = timeout * 1000;
+
+ if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
+ tcnt = GXBB_WDT_TCNT_SETUP_MASK;
+
+ wdt_dev->timeout = timeout;
+
+ meson_gxbb_wdt_ping(wdt_dev);
+
+ writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
+
+ return 0;
+}
+
+static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
+{
+ struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
+ unsigned long reg;
+
+ reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
+
+ return ((reg & GXBB_WDT_TCNT_SETUP_MASK) -
+ (reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000;
+}
+
+static const struct watchdog_ops meson_gxbb_wdt_ops = {
+ .start = meson_gxbb_wdt_start,
+ .stop = meson_gxbb_wdt_stop,
+ .ping = meson_gxbb_wdt_ping,
+ .set_timeout = meson_gxbb_wdt_set_timeout,
+ .get_timeleft = meson_gxbb_wdt_get_timeleft,
+};
+
+static const struct watchdog_info meson_gxbb_wdt_info = {
+ .identity = "Meson GXBB Watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
+{
+ struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
+
+ if (watchdog_active(&data->wdt_dev))
+ meson_gxbb_wdt_start(&data->wdt_dev);
+
+ return 0;
+}
+
+static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
+{
+ struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
+
+ if (watchdog_active(&data->wdt_dev))
+ meson_gxbb_wdt_stop(&data->wdt_dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
+};
+
+static const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
+ { .compatible = "amlogic,meson-gxbb-wdt", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
+
+static void meson_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int meson_gxbb_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct meson_gxbb_wdt *data;
+ int ret;
+ u32 ctrl_reg;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(data->reg_base))
+ return PTR_ERR(data->reg_base);
+
+ data->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(data->clk))
+ return PTR_ERR(data->clk);
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, meson_clk_disable_unprepare,
+ data->clk);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, data);
+
+ data->wdt_dev.parent = dev;
+ data->wdt_dev.info = &meson_gxbb_wdt_info;
+ data->wdt_dev.ops = &meson_gxbb_wdt_ops;
+ data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
+ data->wdt_dev.min_timeout = 1;
+ data->wdt_dev.timeout = DEFAULT_TIMEOUT;
+ watchdog_init_timeout(&data->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&data->wdt_dev, nowayout);
+ watchdog_set_drvdata(&data->wdt_dev, data);
+
+ ctrl_reg = readl(data->reg_base + GXBB_WDT_CTRL_REG) &
+ GXBB_WDT_CTRL_EN;
+
+ if (ctrl_reg) {
+ /* Watchdog is running - keep it running but extend timeout
+ * to the maximum while setting the timebase
+ */
+ set_bit(WDOG_HW_RUNNING, &data->wdt_dev.status);
+ meson_gxbb_wdt_set_timeout(&data->wdt_dev,
+ GXBB_WDT_TCNT_SETUP_MASK / 1000);
+ }
+
+ /* Setup with 1ms timebase */
+ ctrl_reg |= ((clk_get_rate(data->clk) / 1000) &
+ GXBB_WDT_CTRL_DIV_MASK) |
+ GXBB_WDT_CTRL_EE_RESET |
+ GXBB_WDT_CTRL_CLK_EN |
+ GXBB_WDT_CTRL_CLKDIV_EN;
+
+ writel(ctrl_reg, data->reg_base + GXBB_WDT_CTRL_REG);
+ meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
+
+ return devm_watchdog_register_device(dev, &data->wdt_dev);
+}
+
+static struct platform_driver meson_gxbb_wdt_driver = {
+ .probe = meson_gxbb_wdt_probe,
+ .driver = {
+ .name = "meson-gxbb-wdt",
+ .pm = &meson_gxbb_wdt_pm_ops,
+ .of_match_table = meson_gxbb_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(meson_gxbb_wdt_driver);
+
+MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/watchdog/meson_wdt.c b/drivers/watchdog/meson_wdt.c
new file mode 100644
index 000000000..539feaa1f
--- /dev/null
+++ b/drivers/watchdog/meson_wdt.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Meson Watchdog Driver
+ *
+ * Copyright (c) 2014 Carlo Caione
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define DRV_NAME "meson_wdt"
+
+#define MESON_WDT_TC 0x00
+#define MESON_WDT_DC_RESET (3 << 24)
+
+#define MESON_WDT_RESET 0x04
+
+#define MESON_WDT_TIMEOUT 30
+#define MESON_WDT_MIN_TIMEOUT 1
+
+#define MESON_SEC_TO_TC(s, c) ((s) * (c))
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+struct meson_wdt_data {
+ unsigned int enable;
+ unsigned int terminal_count_mask;
+ unsigned int count_unit;
+};
+
+static struct meson_wdt_data meson6_wdt_data = {
+ .enable = BIT(22),
+ .terminal_count_mask = 0x3fffff,
+ .count_unit = 100000, /* 10 us */
+};
+
+static struct meson_wdt_data meson8b_wdt_data = {
+ .enable = BIT(19),
+ .terminal_count_mask = 0xffff,
+ .count_unit = 7812, /* 128 us */
+};
+
+struct meson_wdt_dev {
+ struct watchdog_device wdt_dev;
+ void __iomem *wdt_base;
+ const struct meson_wdt_data *data;
+};
+
+static int meson_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
+ u32 tc_reboot = MESON_WDT_DC_RESET;
+
+ tc_reboot |= meson_wdt->data->enable;
+
+ while (1) {
+ writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC);
+ mdelay(5);
+ }
+
+ return 0;
+}
+
+static int meson_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel(0, meson_wdt->wdt_base + MESON_WDT_RESET);
+
+ return 0;
+}
+
+static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
+ u32 reg;
+
+ reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
+ reg &= ~meson_wdt->data->terminal_count_mask;
+ reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit);
+ writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
+}
+
+static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ wdt_dev->timeout = timeout;
+
+ meson_wdt_change_timeout(wdt_dev, timeout);
+ meson_wdt_ping(wdt_dev);
+
+ return 0;
+}
+
+static int meson_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
+ u32 reg;
+
+ reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
+ reg &= ~meson_wdt->data->enable;
+ writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
+
+ return 0;
+}
+
+static int meson_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
+ u32 reg;
+
+ meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout);
+ meson_wdt_ping(wdt_dev);
+
+ reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
+ reg |= meson_wdt->data->enable;
+ writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
+
+ return 0;
+}
+
+static const struct watchdog_info meson_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops meson_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = meson_wdt_start,
+ .stop = meson_wdt_stop,
+ .ping = meson_wdt_ping,
+ .set_timeout = meson_wdt_set_timeout,
+ .restart = meson_wdt_restart,
+};
+
+static const struct of_device_id meson_wdt_dt_ids[] = {
+ { .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data },
+ { .compatible = "amlogic,meson8-wdt", .data = &meson6_wdt_data },
+ { .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data },
+ { .compatible = "amlogic,meson8m2-wdt", .data = &meson8b_wdt_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids);
+
+static int meson_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct meson_wdt_dev *meson_wdt;
+ int err;
+
+ meson_wdt = devm_kzalloc(dev, sizeof(*meson_wdt), GFP_KERNEL);
+ if (!meson_wdt)
+ return -ENOMEM;
+
+ meson_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(meson_wdt->wdt_base))
+ return PTR_ERR(meson_wdt->wdt_base);
+
+ meson_wdt->data = device_get_match_data(dev);
+
+ meson_wdt->wdt_dev.parent = dev;
+ meson_wdt->wdt_dev.info = &meson_wdt_info;
+ meson_wdt->wdt_dev.ops = &meson_wdt_ops;
+ meson_wdt->wdt_dev.max_timeout =
+ meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit;
+ meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT;
+ meson_wdt->wdt_dev.timeout = min_t(unsigned int,
+ MESON_WDT_TIMEOUT,
+ meson_wdt->wdt_dev.max_timeout);
+
+ watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt);
+
+ watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout);
+ watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128);
+
+ meson_wdt_stop(&meson_wdt->wdt_dev);
+
+ watchdog_stop_on_reboot(&meson_wdt->wdt_dev);
+ err = devm_watchdog_register_device(dev, &meson_wdt->wdt_dev);
+ if (err)
+ return err;
+
+ dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
+ meson_wdt->wdt_dev.timeout, nowayout);
+
+ return 0;
+}
+
+static struct platform_driver meson_wdt_driver = {
+ .probe = meson_wdt_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = meson_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(meson_wdt_driver);
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
+MODULE_DESCRIPTION("Meson Watchdog Timer Driver");
diff --git a/drivers/watchdog/mixcomwd.c b/drivers/watchdog/mixcomwd.c
new file mode 100644
index 000000000..d387bad37
--- /dev/null
+++ b/drivers/watchdog/mixcomwd.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MixCom Watchdog: A Simple Hardware Watchdog Device
+ * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
+ *
+ * Author: Gergely Madarasz <gorgo@itc.hu>
+ *
+ * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu>
+ *
+ * Version 0.1 (99/04/15):
+ * - first version
+ *
+ * Version 0.2 (99/06/16):
+ * - added kernel timer watchdog ping after close
+ * since the hardware does not support watchdog shutdown
+ *
+ * Version 0.3 (99/06/21):
+ * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
+ *
+ * Version 0.3.1 (99/06/22):
+ * - allow module removal while internal timer is active,
+ * print warning about probable reset
+ *
+ * Version 0.4 (99/11/15):
+ * - support for one more type board
+ *
+ * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com>
+ * - added nowayout module option to override
+ * CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com>
+ * - make mixcomwd_opened unsigned,
+ * removed lock_kernel/unlock_kernel from mixcomwd_release,
+ * modified ioctl a bit to conform to API
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define VERSION "0.6"
+#define WATCHDOG_NAME "mixcomwd"
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+/*
+ * We have two types of cards that can be probed:
+ * 1) The Mixcom cards: these cards can be found at addresses
+ * 0x180, 0x280, 0x380 with an additional offset of 0xc10.
+ * (Or 0xd90, 0xe90, 0xf90).
+ * 2) The FlashCOM cards: these cards can be set up at
+ * 0x300 -> 0x378, in 0x8 jumps with an offset of 0x04.
+ * (Or 0x304 -> 0x37c in 0x8 jumps).
+ * Each card has it's own ID.
+ */
+#define MIXCOM_ID 0x11
+#define FLASHCOM_ID 0x18
+static struct {
+ int ioport;
+ int id;
+} mixcomwd_io_info[] = {
+ /* The Mixcom cards */
+ {0x0d90, MIXCOM_ID},
+ {0x0e90, MIXCOM_ID},
+ {0x0f90, MIXCOM_ID},
+ /* The FlashCOM cards */
+ {0x0304, FLASHCOM_ID},
+ {0x030c, FLASHCOM_ID},
+ {0x0314, FLASHCOM_ID},
+ {0x031c, FLASHCOM_ID},
+ {0x0324, FLASHCOM_ID},
+ {0x032c, FLASHCOM_ID},
+ {0x0334, FLASHCOM_ID},
+ {0x033c, FLASHCOM_ID},
+ {0x0344, FLASHCOM_ID},
+ {0x034c, FLASHCOM_ID},
+ {0x0354, FLASHCOM_ID},
+ {0x035c, FLASHCOM_ID},
+ {0x0364, FLASHCOM_ID},
+ {0x036c, FLASHCOM_ID},
+ {0x0374, FLASHCOM_ID},
+ {0x037c, FLASHCOM_ID},
+ /* The end of the list */
+ {0x0000, 0},
+};
+
+static void mixcomwd_timerfun(struct timer_list *unused);
+
+static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */
+
+static int watchdog_port;
+static int mixcomwd_timer_alive;
+static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun);
+static char expect_close;
+
+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) ")");
+
+static void mixcomwd_ping(void)
+{
+ outb_p(55, watchdog_port);
+ return;
+}
+
+static void mixcomwd_timerfun(struct timer_list *unused)
+{
+ mixcomwd_ping();
+ mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+
+static int mixcomwd_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &mixcomwd_opened))
+ return -EBUSY;
+
+ mixcomwd_ping();
+
+ if (nowayout)
+ /*
+ * fops_get() code via open() has already done
+ * a try_module_get() so it is safe to do the
+ * __module_get().
+ */
+ __module_get(THIS_MODULE);
+ else {
+ if (mixcomwd_timer_alive) {
+ del_timer(&mixcomwd_timer);
+ mixcomwd_timer_alive = 0;
+ }
+ }
+ return stream_open(inode, file);
+}
+
+static int mixcomwd_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ if (mixcomwd_timer_alive) {
+ pr_err("release called while internal timer alive\n");
+ return -EBUSY;
+ }
+ mixcomwd_timer_alive = 1;
+ mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
+ } else
+ pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
+
+ clear_bit(0, &mixcomwd_opened);
+ expect_close = 0;
+ return 0;
+}
+
+
+static ssize_t mixcomwd_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ mixcomwd_ping();
+ }
+ return len;
+}
+
+static long mixcomwd_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int status;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "MixCOM watchdog",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+ case WDIOC_GETSTATUS:
+ status = mixcomwd_opened;
+ if (!nowayout)
+ status |= mixcomwd_timer_alive;
+ return put_user(status, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ mixcomwd_ping();
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static const struct file_operations mixcomwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = mixcomwd_write,
+ .unlocked_ioctl = mixcomwd_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = mixcomwd_open,
+ .release = mixcomwd_release,
+};
+
+static struct miscdevice mixcomwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mixcomwd_fops,
+};
+
+static int __init checkcard(int port, int card_id)
+{
+ int id;
+
+ if (!request_region(port, 1, "MixCOM watchdog"))
+ return 0;
+
+ id = inb_p(port);
+ if (card_id == MIXCOM_ID)
+ id &= 0x3f;
+
+ if (id != card_id) {
+ release_region(port, 1);
+ return 0;
+ }
+ return 1;
+}
+
+static int __init mixcomwd_init(void)
+{
+ int i, ret, found = 0;
+
+ for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) {
+ if (checkcard(mixcomwd_io_info[i].ioport,
+ mixcomwd_io_info[i].id)) {
+ found = 1;
+ watchdog_port = mixcomwd_io_info[i].ioport;
+ }
+ }
+
+ if (!found) {
+ pr_err("No card detected, or port not available\n");
+ return -ENODEV;
+ }
+
+ ret = misc_register(&mixcomwd_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto error_misc_register_watchdog;
+ }
+
+ pr_info("MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",
+ VERSION, watchdog_port);
+
+ return 0;
+
+error_misc_register_watchdog:
+ release_region(watchdog_port, 1);
+ watchdog_port = 0x0000;
+ return ret;
+}
+
+static void __exit mixcomwd_exit(void)
+{
+ if (!nowayout) {
+ if (mixcomwd_timer_alive) {
+ pr_warn("I quit now, hardware will probably reboot!\n");
+ del_timer_sync(&mixcomwd_timer);
+ mixcomwd_timer_alive = 0;
+ }
+ }
+ misc_deregister(&mixcomwd_miscdev);
+ release_region(watchdog_port, 1);
+}
+
+module_init(mixcomwd_init);
+module_exit(mixcomwd_exit);
+
+MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
+MODULE_DESCRIPTION("MixCom Watchdog driver");
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/mlx_wdt.c b/drivers/watchdog/mlx_wdt.c
new file mode 100644
index 000000000..9c5b6616f
--- /dev/null
+++ b/drivers/watchdog/mlx_wdt.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Mellanox watchdog driver
+ *
+ * Copyright (C) 2019 Mellanox Technologies
+ * Copyright (C) 2019 Michael Shych <mshych@mellanox.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/platform_data/mlxreg.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define MLXREG_WDT_CLOCK_SCALE 1000
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE1 32
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE2 255
+#define MLXREG_WDT_MAX_TIMEOUT_TYPE3 65535
+#define MLXREG_WDT_MIN_TIMEOUT 1
+#define MLXREG_WDT_OPTIONS_BASE (WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | \
+ WDIOF_SETTIMEOUT)
+
+/**
+ * struct mlxreg_wdt - wd private data:
+ *
+ * @wdd: watchdog device;
+ * @device: basic device;
+ * @pdata: data received from platform driver;
+ * @regmap: register map of parent device;
+ * @timeout: defined timeout in sec.;
+ * @action_idx: index for direct access to action register;
+ * @timeout_idx:index for direct access to TO register;
+ * @tleft_idx: index for direct access to time left register;
+ * @ping_idx: index for direct access to ping register;
+ * @reset_idx: index for direct access to reset cause register;
+ * @wd_type: watchdog HW type;
+ */
+struct mlxreg_wdt {
+ struct watchdog_device wdd;
+ struct mlxreg_core_platform_data *pdata;
+ void *regmap;
+ int action_idx;
+ int timeout_idx;
+ int tleft_idx;
+ int ping_idx;
+ int reset_idx;
+ int regmap_val_sz;
+ enum mlxreg_wdt_type wdt_type;
+};
+
+static void mlxreg_wdt_check_card_reset(struct mlxreg_wdt *wdt)
+{
+ struct mlxreg_core_data *reg_data;
+ u32 regval;
+ int rc;
+
+ if (wdt->reset_idx == -EINVAL)
+ return;
+
+ if (!(wdt->wdd.info->options & WDIOF_CARDRESET))
+ return;
+
+ reg_data = &wdt->pdata->data[wdt->reset_idx];
+ rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+ if (!rc) {
+ if (regval & ~reg_data->mask) {
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
+ dev_info(wdt->wdd.parent,
+ "watchdog previously reset the CPU\n");
+ }
+ }
+}
+
+static int mlxreg_wdt_start(struct watchdog_device *wdd)
+{
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
+
+ return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask,
+ BIT(reg_data->bit));
+}
+
+static int mlxreg_wdt_stop(struct watchdog_device *wdd)
+{
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->action_idx];
+
+ return regmap_update_bits(wdt->regmap, reg_data->reg, ~reg_data->mask,
+ ~BIT(reg_data->bit));
+}
+
+static int mlxreg_wdt_ping(struct watchdog_device *wdd)
+{
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->ping_idx];
+
+ return regmap_write_bits(wdt->regmap, reg_data->reg, ~reg_data->mask,
+ BIT(reg_data->bit));
+}
+
+static int mlxreg_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->timeout_idx];
+ u32 regval, set_time, hw_timeout;
+ int rc;
+
+ switch (wdt->wdt_type) {
+ case MLX_WDT_TYPE1:
+ rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+ if (rc)
+ return rc;
+
+ hw_timeout = order_base_2(timeout * MLXREG_WDT_CLOCK_SCALE);
+ regval = (regval & reg_data->mask) | hw_timeout;
+ /* Rowndown to actual closest number of sec. */
+ set_time = BIT(hw_timeout) / MLXREG_WDT_CLOCK_SCALE;
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+ break;
+ case MLX_WDT_TYPE2:
+ set_time = timeout;
+ rc = regmap_write(wdt->regmap, reg_data->reg, timeout);
+ break;
+ case MLX_WDT_TYPE3:
+ /* WD_TYPE3 has 2B set time register */
+ set_time = timeout;
+ if (wdt->regmap_val_sz == 1) {
+ regval = timeout & 0xff;
+ rc = regmap_write(wdt->regmap, reg_data->reg, regval);
+ if (!rc) {
+ regval = (timeout & 0xff00) >> 8;
+ rc = regmap_write(wdt->regmap,
+ reg_data->reg + 1, regval);
+ }
+ } else {
+ rc = regmap_write(wdt->regmap, reg_data->reg, timeout);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wdd->timeout = set_time;
+ if (!rc) {
+ /*
+ * Restart watchdog with new timeout period
+ * if watchdog is already started.
+ */
+ if (watchdog_active(wdd)) {
+ rc = mlxreg_wdt_stop(wdd);
+ if (!rc)
+ rc = mlxreg_wdt_start(wdd);
+ }
+ }
+
+ return rc;
+}
+
+static unsigned int mlxreg_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct mlxreg_wdt *wdt = watchdog_get_drvdata(wdd);
+ struct mlxreg_core_data *reg_data = &wdt->pdata->data[wdt->tleft_idx];
+ u32 regval, msb, lsb;
+ int rc;
+
+ if (wdt->wdt_type == MLX_WDT_TYPE2) {
+ rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+ } else {
+ /* WD_TYPE3 has 2 byte timeleft register */
+ if (wdt->regmap_val_sz == 1) {
+ rc = regmap_read(wdt->regmap, reg_data->reg, &lsb);
+ if (!rc) {
+ rc = regmap_read(wdt->regmap,
+ reg_data->reg + 1, &msb);
+ regval = (msb & 0xff) << 8 | (lsb & 0xff);
+ }
+ } else {
+ rc = regmap_read(wdt->regmap, reg_data->reg, &regval);
+ }
+ }
+
+ /* Return 0 timeleft in case of failure register read. */
+ return rc == 0 ? regval : 0;
+}
+
+static const struct watchdog_ops mlxreg_wdt_ops_type1 = {
+ .start = mlxreg_wdt_start,
+ .stop = mlxreg_wdt_stop,
+ .ping = mlxreg_wdt_ping,
+ .set_timeout = mlxreg_wdt_set_timeout,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_ops mlxreg_wdt_ops_type2 = {
+ .start = mlxreg_wdt_start,
+ .stop = mlxreg_wdt_stop,
+ .ping = mlxreg_wdt_ping,
+ .set_timeout = mlxreg_wdt_set_timeout,
+ .get_timeleft = mlxreg_wdt_get_timeleft,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info mlxreg_wdt_main_info = {
+ .options = MLXREG_WDT_OPTIONS_BASE
+ | WDIOF_CARDRESET,
+ .identity = "mlx-wdt-main",
+};
+
+static const struct watchdog_info mlxreg_wdt_aux_info = {
+ .options = MLXREG_WDT_OPTIONS_BASE
+ | WDIOF_ALARMONLY,
+ .identity = "mlx-wdt-aux",
+};
+
+static void mlxreg_wdt_config(struct mlxreg_wdt *wdt,
+ struct mlxreg_core_platform_data *pdata)
+{
+ struct mlxreg_core_data *data = pdata->data;
+ int i;
+
+ wdt->reset_idx = -EINVAL;
+ for (i = 0; i < pdata->counter; i++, data++) {
+ if (strnstr(data->label, "action", sizeof(data->label)))
+ wdt->action_idx = i;
+ else if (strnstr(data->label, "timeout", sizeof(data->label)))
+ wdt->timeout_idx = i;
+ else if (strnstr(data->label, "timeleft", sizeof(data->label)))
+ wdt->tleft_idx = i;
+ else if (strnstr(data->label, "ping", sizeof(data->label)))
+ wdt->ping_idx = i;
+ else if (strnstr(data->label, "reset", sizeof(data->label)))
+ wdt->reset_idx = i;
+ }
+
+ wdt->pdata = pdata;
+ if (strnstr(pdata->identity, mlxreg_wdt_main_info.identity,
+ sizeof(mlxreg_wdt_main_info.identity)))
+ wdt->wdd.info = &mlxreg_wdt_main_info;
+ else
+ wdt->wdd.info = &mlxreg_wdt_aux_info;
+
+ wdt->wdt_type = pdata->version;
+ switch (wdt->wdt_type) {
+ case MLX_WDT_TYPE1:
+ wdt->wdd.ops = &mlxreg_wdt_ops_type1;
+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE1;
+ break;
+ case MLX_WDT_TYPE2:
+ wdt->wdd.ops = &mlxreg_wdt_ops_type2;
+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE2;
+ break;
+ case MLX_WDT_TYPE3:
+ wdt->wdd.ops = &mlxreg_wdt_ops_type2;
+ wdt->wdd.max_timeout = MLXREG_WDT_MAX_TIMEOUT_TYPE3;
+ break;
+ default:
+ break;
+ }
+
+ wdt->wdd.min_timeout = MLXREG_WDT_MIN_TIMEOUT;
+}
+
+static int mlxreg_wdt_init_timeout(struct mlxreg_wdt *wdt,
+ struct mlxreg_core_platform_data *pdata)
+{
+ u32 timeout;
+
+ timeout = pdata->data[wdt->timeout_idx].health_cntr;
+ return mlxreg_wdt_set_timeout(&wdt->wdd, timeout);
+}
+
+static int mlxreg_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mlxreg_core_platform_data *pdata;
+ struct mlxreg_wdt *wdt;
+ int rc;
+
+ pdata = dev_get_platdata(dev);
+ if (!pdata) {
+ dev_err(dev, "Failed to get platform data.\n");
+ return -EINVAL;
+ }
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->wdd.parent = dev;
+ wdt->regmap = pdata->regmap;
+ rc = regmap_get_val_bytes(wdt->regmap);
+ if (rc < 0)
+ return -EINVAL;
+
+ wdt->regmap_val_sz = rc;
+ mlxreg_wdt_config(wdt, pdata);
+
+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_NOWAYOUT))
+ watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
+ watchdog_stop_on_reboot(&wdt->wdd);
+ watchdog_stop_on_unregister(&wdt->wdd);
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ rc = mlxreg_wdt_init_timeout(wdt, pdata);
+ if (rc)
+ goto register_error;
+
+ if ((pdata->features & MLXREG_CORE_WD_FEATURE_START_AT_BOOT)) {
+ rc = mlxreg_wdt_start(&wdt->wdd);
+ if (rc)
+ goto register_error;
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+ mlxreg_wdt_check_card_reset(wdt);
+ rc = devm_watchdog_register_device(dev, &wdt->wdd);
+
+register_error:
+ if (rc)
+ dev_err(dev, "Cannot register watchdog device (err=%d)\n", rc);
+ return rc;
+}
+
+static struct platform_driver mlxreg_wdt_driver = {
+ .probe = mlxreg_wdt_probe,
+ .driver = {
+ .name = "mlx-wdt",
+ },
+};
+
+module_platform_driver(mlxreg_wdt_driver);
+
+MODULE_AUTHOR("Michael Shych <michaelsh@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mlx-wdt");
diff --git a/drivers/watchdog/moxart_wdt.c b/drivers/watchdog/moxart_wdt.c
new file mode 100644
index 000000000..6340a1f5f
--- /dev/null
+++ b/drivers/watchdog/moxart_wdt.c
@@ -0,0 +1,167 @@
+/*
+ * MOXA ART SoCs watchdog driver.
+ *
+ * Copyright (C) 2013 Jonas Jensen
+ *
+ * Jonas Jensen <jonas.jensen@gmail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/moduleparam.h>
+
+#define REG_COUNT 0x4
+#define REG_MODE 0x8
+#define REG_ENABLE 0xC
+
+struct moxart_wdt_dev {
+ struct watchdog_device dev;
+ void __iomem *base;
+ unsigned int clock_frequency;
+};
+
+static int heartbeat;
+
+static int moxart_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel(1, moxart_wdt->base + REG_COUNT);
+ writel(0x5ab9, moxart_wdt->base + REG_MODE);
+ writel(0x03, moxart_wdt->base + REG_ENABLE);
+
+ return 0;
+}
+
+static int moxart_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel(0, moxart_wdt->base + REG_ENABLE);
+
+ return 0;
+}
+
+static int moxart_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev);
+
+ writel(moxart_wdt->clock_frequency * wdt_dev->timeout,
+ moxart_wdt->base + REG_COUNT);
+ writel(0x5ab9, moxart_wdt->base + REG_MODE);
+ writel(0x03, moxart_wdt->base + REG_ENABLE);
+
+ return 0;
+}
+
+static int moxart_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ wdt_dev->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info moxart_wdt_info = {
+ .identity = "moxart-wdt",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops moxart_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = moxart_wdt_start,
+ .stop = moxart_wdt_stop,
+ .set_timeout = moxart_wdt_set_timeout,
+ .restart = moxart_wdt_restart,
+};
+
+static int moxart_wdt_probe(struct platform_device *pdev)
+{
+ struct moxart_wdt_dev *moxart_wdt;
+ struct device *dev = &pdev->dev;
+ struct clk *clk;
+ int err;
+ unsigned int max_timeout;
+ bool nowayout = WATCHDOG_NOWAYOUT;
+
+ moxart_wdt = devm_kzalloc(dev, sizeof(*moxart_wdt), GFP_KERNEL);
+ if (!moxart_wdt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, moxart_wdt);
+
+ moxart_wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(moxart_wdt->base))
+ return PTR_ERR(moxart_wdt->base);
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ pr_err("%s: of_clk_get failed\n", __func__);
+ return PTR_ERR(clk);
+ }
+
+ moxart_wdt->clock_frequency = clk_get_rate(clk);
+ if (moxart_wdt->clock_frequency == 0) {
+ pr_err("%s: incorrect clock frequency\n", __func__);
+ return -EINVAL;
+ }
+
+ max_timeout = UINT_MAX / moxart_wdt->clock_frequency;
+
+ moxart_wdt->dev.info = &moxart_wdt_info;
+ moxart_wdt->dev.ops = &moxart_wdt_ops;
+ moxart_wdt->dev.timeout = max_timeout;
+ moxart_wdt->dev.min_timeout = 1;
+ moxart_wdt->dev.max_timeout = max_timeout;
+ moxart_wdt->dev.parent = dev;
+
+ watchdog_init_timeout(&moxart_wdt->dev, heartbeat, dev);
+ watchdog_set_nowayout(&moxart_wdt->dev, nowayout);
+ watchdog_set_restart_priority(&moxart_wdt->dev, 128);
+
+ watchdog_set_drvdata(&moxart_wdt->dev, moxart_wdt);
+
+ watchdog_stop_on_unregister(&moxart_wdt->dev);
+ err = devm_watchdog_register_device(dev, &moxart_wdt->dev);
+ if (err)
+ return err;
+
+ dev_dbg(dev, "Watchdog enabled (heartbeat=%d sec, nowayout=%d)\n",
+ moxart_wdt->dev.timeout, nowayout);
+
+ return 0;
+}
+
+static const struct of_device_id moxart_watchdog_match[] = {
+ { .compatible = "moxa,moxart-watchdog" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, moxart_watchdog_match);
+
+static struct platform_driver moxart_wdt_driver = {
+ .probe = moxart_wdt_probe,
+ .driver = {
+ .name = "moxart-watchdog",
+ .of_match_table = moxart_watchdog_match,
+ },
+};
+module_platform_driver(moxart_wdt_driver);
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds");
+
+MODULE_DESCRIPTION("MOXART watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c
new file mode 100644
index 000000000..1c569be72
--- /dev/null
+++ b/drivers/watchdog/mpc8xxx_wdt.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * mpc8xxx_wdt.c - MPC8xx/MPC83xx/MPC86xx watchdog userspace interface
+ *
+ * Authors: Dave Updegraff <dave@cray.org>
+ * Kumar Gala <galak@kernel.crashing.org>
+ * Attribution: from 83xx_wst: Florian Schirmer <jolt@tuxbox.org>
+ * ..and from sc520_wdt
+ * Copyright (c) 2008 MontaVista Software, Inc.
+ * Anton Vorontsov <avorontsov@ru.mvista.com>
+ *
+ * Note: it appears that you can only actually ENABLE or DISABLE the thing
+ * once after POR. Once enabled, you cannot disable, and vice versa.
+ */
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <sysdev/fsl_soc.h>
+
+#define WATCHDOG_TIMEOUT 10
+
+struct mpc8xxx_wdt {
+ __be32 res0;
+ __be32 swcrr; /* System watchdog control register */
+#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */
+#define SWCRR_SWF 0x00000008 /* Software Watchdog Freeze (mpc8xx). */
+#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
+#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
+#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
+ __be32 swcnr; /* System watchdog count register */
+ u8 res1[2];
+ __be16 swsrr; /* System watchdog service register */
+ u8 res2[0xF0];
+};
+
+struct mpc8xxx_wdt_type {
+ int prescaler;
+ bool hw_enabled;
+ u32 rsr_mask;
+};
+
+struct mpc8xxx_wdt_ddata {
+ struct mpc8xxx_wdt __iomem *base;
+ struct watchdog_device wdd;
+ spinlock_t lock;
+ u16 swtc;
+};
+
+static u16 timeout;
+module_param(timeout, ushort, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (1<timeout<65535, default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static bool reset = 1;
+module_param(reset, bool, 0);
+MODULE_PARM_DESC(reset,
+ "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset");
+
+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) ")");
+
+static void mpc8xxx_wdt_keepalive(struct mpc8xxx_wdt_ddata *ddata)
+{
+ /* Ping the WDT */
+ spin_lock(&ddata->lock);
+ out_be16(&ddata->base->swsrr, 0x556c);
+ out_be16(&ddata->base->swsrr, 0xaa39);
+ spin_unlock(&ddata->lock);
+}
+
+static int mpc8xxx_wdt_start(struct watchdog_device *w)
+{
+ struct mpc8xxx_wdt_ddata *ddata =
+ container_of(w, struct mpc8xxx_wdt_ddata, wdd);
+ u32 tmp = in_be32(&ddata->base->swcrr);
+
+ /* Good, fire up the show */
+ tmp &= ~(SWCRR_SWTC | SWCRR_SWF | SWCRR_SWEN | SWCRR_SWRI | SWCRR_SWPR);
+ tmp |= SWCRR_SWEN | SWCRR_SWPR | (ddata->swtc << 16);
+
+ if (reset)
+ tmp |= SWCRR_SWRI;
+
+ out_be32(&ddata->base->swcrr, tmp);
+
+ tmp = in_be32(&ddata->base->swcrr);
+ if (!(tmp & SWCRR_SWEN))
+ return -EOPNOTSUPP;
+
+ ddata->swtc = tmp >> 16;
+ set_bit(WDOG_HW_RUNNING, &ddata->wdd.status);
+
+ return 0;
+}
+
+static int mpc8xxx_wdt_ping(struct watchdog_device *w)
+{
+ struct mpc8xxx_wdt_ddata *ddata =
+ container_of(w, struct mpc8xxx_wdt_ddata, wdd);
+
+ mpc8xxx_wdt_keepalive(ddata);
+ return 0;
+}
+
+static struct watchdog_info mpc8xxx_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT,
+ .firmware_version = 1,
+ .identity = "MPC8xxx",
+};
+
+static const struct watchdog_ops mpc8xxx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = mpc8xxx_wdt_start,
+ .ping = mpc8xxx_wdt_ping,
+};
+
+static int mpc8xxx_wdt_probe(struct platform_device *ofdev)
+{
+ int ret;
+ struct resource *res;
+ const struct mpc8xxx_wdt_type *wdt_type;
+ struct mpc8xxx_wdt_ddata *ddata;
+ u32 freq = fsl_get_sys_freq();
+ bool enabled;
+ struct device *dev = &ofdev->dev;
+
+ wdt_type = of_device_get_match_data(dev);
+ if (!wdt_type)
+ return -EINVAL;
+
+ if (!freq || freq == -1)
+ return -EINVAL;
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->base = devm_platform_ioremap_resource(ofdev, 0);
+ if (IS_ERR(ddata->base))
+ return PTR_ERR(ddata->base);
+
+ enabled = in_be32(&ddata->base->swcrr) & SWCRR_SWEN;
+ if (!enabled && wdt_type->hw_enabled) {
+ dev_info(dev, "could not be enabled in software\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource(ofdev, IORESOURCE_MEM, 1);
+ if (res) {
+ bool status;
+ u32 __iomem *rsr = ioremap(res->start, resource_size(res));
+
+ if (!rsr)
+ return -ENOMEM;
+
+ status = in_be32(rsr) & wdt_type->rsr_mask;
+ ddata->wdd.bootstatus = status ? WDIOF_CARDRESET : 0;
+ /* clear reset status bits related to watchdog timer */
+ out_be32(rsr, wdt_type->rsr_mask);
+ iounmap(rsr);
+
+ dev_info(dev, "Last boot was %scaused by watchdog\n",
+ status ? "" : "not ");
+ }
+
+ spin_lock_init(&ddata->lock);
+
+ ddata->wdd.info = &mpc8xxx_wdt_info;
+ ddata->wdd.ops = &mpc8xxx_wdt_ops;
+
+ ddata->wdd.timeout = WATCHDOG_TIMEOUT;
+ watchdog_init_timeout(&ddata->wdd, timeout, dev);
+
+ watchdog_set_nowayout(&ddata->wdd, nowayout);
+
+ ddata->swtc = min(ddata->wdd.timeout * freq / wdt_type->prescaler,
+ 0xffffU);
+
+ /*
+ * If the watchdog was previously enabled or we're running on
+ * MPC8xxx, we should ping the wdt from the kernel until the
+ * userspace handles it.
+ */
+ if (enabled)
+ mpc8xxx_wdt_start(&ddata->wdd);
+
+ ddata->wdd.max_hw_heartbeat_ms = (ddata->swtc * wdt_type->prescaler) /
+ (freq / 1000);
+ ddata->wdd.min_timeout = ddata->wdd.max_hw_heartbeat_ms / 1000;
+ if (ddata->wdd.timeout < ddata->wdd.min_timeout)
+ ddata->wdd.timeout = ddata->wdd.min_timeout;
+
+ ret = devm_watchdog_register_device(dev, &ddata->wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev,
+ "WDT driver for MPC8xxx initialized. mode:%s timeout=%d sec\n",
+ reset ? "reset" : "interrupt", ddata->wdd.timeout);
+
+ platform_set_drvdata(ofdev, ddata);
+ return 0;
+}
+
+static const struct of_device_id mpc8xxx_wdt_match[] = {
+ {
+ .compatible = "mpc83xx_wdt",
+ .data = &(struct mpc8xxx_wdt_type) {
+ .prescaler = 0x10000,
+ .rsr_mask = BIT(3), /* RSR Bit SWRS */
+ },
+ },
+ {
+ .compatible = "fsl,mpc8610-wdt",
+ .data = &(struct mpc8xxx_wdt_type) {
+ .prescaler = 0x10000,
+ .hw_enabled = true,
+ .rsr_mask = BIT(20), /* RSTRSCR Bit WDT_RR */
+ },
+ },
+ {
+ .compatible = "fsl,mpc823-wdt",
+ .data = &(struct mpc8xxx_wdt_type) {
+ .prescaler = 0x800,
+ .hw_enabled = true,
+ .rsr_mask = BIT(28), /* RSR Bit SWRS */
+ },
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mpc8xxx_wdt_match);
+
+static struct platform_driver mpc8xxx_wdt_driver = {
+ .probe = mpc8xxx_wdt_probe,
+ .driver = {
+ .name = "mpc8xxx_wdt",
+ .of_match_table = mpc8xxx_wdt_match,
+ },
+};
+
+static int __init mpc8xxx_wdt_init(void)
+{
+ return platform_driver_register(&mpc8xxx_wdt_driver);
+}
+arch_initcall(mpc8xxx_wdt_init);
+
+static void __exit mpc8xxx_wdt_exit(void)
+{
+ platform_driver_unregister(&mpc8xxx_wdt_driver);
+}
+module_exit(mpc8xxx_wdt_exit);
+
+MODULE_AUTHOR("Dave Updegraff, Kumar Gala");
+MODULE_DESCRIPTION("Driver for watchdog timer in MPC8xx/MPC83xx/MPC86xx "
+ "uProcessors");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/msc313e_wdt.c b/drivers/watchdog/msc313e_wdt.c
new file mode 100644
index 000000000..90171431f
--- /dev/null
+++ b/drivers/watchdog/msc313e_wdt.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MStar WDT driver
+ *
+ * Copyright (C) 2019 - 2021 Daniel Palmer
+ * Copyright (C) 2021 Romain Perier
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define REG_WDT_CLR 0x0
+#define REG_WDT_MAX_PRD_L 0x10
+#define REG_WDT_MAX_PRD_H 0x14
+
+#define MSC313E_WDT_MIN_TIMEOUT 1
+#define MSC313E_WDT_DEFAULT_TIMEOUT 30
+
+static unsigned int timeout;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+struct msc313e_wdt_priv {
+ void __iomem *base;
+ struct watchdog_device wdev;
+ struct clk *clk;
+};
+
+static int msc313e_wdt_start(struct watchdog_device *wdev)
+{
+ struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ u32 timeout;
+ int err;
+
+ err = clk_prepare_enable(priv->clk);
+ if (err)
+ return err;
+
+ timeout = wdev->timeout * clk_get_rate(priv->clk);
+ writew(timeout & 0xffff, priv->base + REG_WDT_MAX_PRD_L);
+ writew((timeout >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H);
+ writew(1, priv->base + REG_WDT_CLR);
+ return 0;
+}
+
+static int msc313e_wdt_ping(struct watchdog_device *wdev)
+{
+ struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ writew(1, priv->base + REG_WDT_CLR);
+ return 0;
+}
+
+static int msc313e_wdt_stop(struct watchdog_device *wdev)
+{
+ struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ writew(0, priv->base + REG_WDT_MAX_PRD_L);
+ writew(0, priv->base + REG_WDT_MAX_PRD_H);
+ writew(0, priv->base + REG_WDT_CLR);
+ clk_disable_unprepare(priv->clk);
+ return 0;
+}
+
+static int msc313e_wdt_settimeout(struct watchdog_device *wdev, unsigned int new_time)
+{
+ wdev->timeout = new_time;
+
+ return msc313e_wdt_start(wdev);
+}
+
+static const struct watchdog_info msc313e_wdt_ident = {
+ .identity = "MSC313e watchdog",
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+};
+
+static const struct watchdog_ops msc313e_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = msc313e_wdt_start,
+ .stop = msc313e_wdt_stop,
+ .ping = msc313e_wdt_ping,
+ .set_timeout = msc313e_wdt_settimeout,
+};
+
+static const struct of_device_id msc313e_wdt_of_match[] = {
+ { .compatible = "mstar,msc313e-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, msc313e_wdt_of_match);
+
+static int msc313e_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct msc313e_wdt_priv *priv;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk)) {
+ dev_err(dev, "No input clock\n");
+ return PTR_ERR(priv->clk);
+ }
+
+ priv->wdev.info = &msc313e_wdt_ident,
+ priv->wdev.ops = &msc313e_wdt_ops,
+ priv->wdev.parent = dev;
+ priv->wdev.min_timeout = MSC313E_WDT_MIN_TIMEOUT;
+ priv->wdev.max_timeout = U32_MAX / clk_get_rate(priv->clk);
+ priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT;
+
+ /* If the period is non-zero the WDT is running */
+ if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16))
+ set_bit(WDOG_HW_RUNNING, &priv->wdev.status);
+
+ watchdog_set_drvdata(&priv->wdev, priv);
+
+ watchdog_init_timeout(&priv->wdev, timeout, dev);
+ watchdog_stop_on_reboot(&priv->wdev);
+ watchdog_stop_on_unregister(&priv->wdev);
+
+ return devm_watchdog_register_device(dev, &priv->wdev);
+}
+
+static int __maybe_unused msc313e_wdt_suspend(struct device *dev)
+{
+ struct msc313e_wdt_priv *priv = dev_get_drvdata(dev);
+
+ if (watchdog_active(&priv->wdev))
+ msc313e_wdt_stop(&priv->wdev);
+
+ return 0;
+}
+
+static int __maybe_unused msc313e_wdt_resume(struct device *dev)
+{
+ struct msc313e_wdt_priv *priv = dev_get_drvdata(dev);
+
+ if (watchdog_active(&priv->wdev))
+ msc313e_wdt_start(&priv->wdev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(msc313e_wdt_pm_ops, msc313e_wdt_suspend, msc313e_wdt_resume);
+
+static struct platform_driver msc313e_wdt_driver = {
+ .driver = {
+ .name = "msc313e-wdt",
+ .of_match_table = msc313e_wdt_of_match,
+ .pm = &msc313e_wdt_pm_ops,
+ },
+ .probe = msc313e_wdt_probe,
+};
+module_platform_driver(msc313e_wdt_driver);
+
+MODULE_AUTHOR("Daniel Palmer <daniel@thingy.jp>");
+MODULE_DESCRIPTION("Watchdog driver for MStar MSC313e");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/mt7621_wdt.c b/drivers/watchdog/mt7621_wdt.c
new file mode 100644
index 000000000..a8aa3522c
--- /dev/null
+++ b/drivers/watchdog/mt7621_wdt.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ralink MT7621/MT7628 built-in hardware watchdog timer
+ *
+ * Copyright (C) 2014 John Crispin <john@phrozen.org>
+ *
+ * This driver was based on: drivers/watchdog/rt2880_wdt.c
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+
+#include <asm/mach-ralink/ralink_regs.h>
+
+#define SYSC_RSTSTAT 0x38
+#define WDT_RST_CAUSE BIT(1)
+
+#define RALINK_WDT_TIMEOUT 30
+
+#define TIMER_REG_TMRSTAT 0x00
+#define TIMER_REG_TMR1LOAD 0x24
+#define TIMER_REG_TMR1CTL 0x20
+
+#define TMR1CTL_ENABLE BIT(7)
+#define TMR1CTL_RESTART BIT(9)
+#define TMR1CTL_PRESCALE_SHIFT 16
+
+static void __iomem *mt7621_wdt_base;
+static struct reset_control *mt7621_wdt_reset;
+
+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) ")");
+
+static inline void rt_wdt_w32(unsigned reg, u32 val)
+{
+ iowrite32(val, mt7621_wdt_base + reg);
+}
+
+static inline u32 rt_wdt_r32(unsigned reg)
+{
+ return ioread32(mt7621_wdt_base + reg);
+}
+
+static int mt7621_wdt_ping(struct watchdog_device *w)
+{
+ rt_wdt_w32(TIMER_REG_TMRSTAT, TMR1CTL_RESTART);
+
+ return 0;
+}
+
+static int mt7621_wdt_set_timeout(struct watchdog_device *w, unsigned int t)
+{
+ w->timeout = t;
+ rt_wdt_w32(TIMER_REG_TMR1LOAD, t * 1000);
+ mt7621_wdt_ping(w);
+
+ return 0;
+}
+
+static int mt7621_wdt_start(struct watchdog_device *w)
+{
+ u32 t;
+
+ /* set the prescaler to 1ms == 1000us */
+ rt_wdt_w32(TIMER_REG_TMR1CTL, 1000 << TMR1CTL_PRESCALE_SHIFT);
+
+ mt7621_wdt_set_timeout(w, w->timeout);
+
+ t = rt_wdt_r32(TIMER_REG_TMR1CTL);
+ t |= TMR1CTL_ENABLE;
+ rt_wdt_w32(TIMER_REG_TMR1CTL, t);
+
+ return 0;
+}
+
+static int mt7621_wdt_stop(struct watchdog_device *w)
+{
+ u32 t;
+
+ mt7621_wdt_ping(w);
+
+ t = rt_wdt_r32(TIMER_REG_TMR1CTL);
+ t &= ~TMR1CTL_ENABLE;
+ rt_wdt_w32(TIMER_REG_TMR1CTL, t);
+
+ return 0;
+}
+
+static int mt7621_wdt_bootcause(void)
+{
+ if (rt_sysc_r32(SYSC_RSTSTAT) & WDT_RST_CAUSE)
+ return WDIOF_CARDRESET;
+
+ return 0;
+}
+
+static int mt7621_wdt_is_running(struct watchdog_device *w)
+{
+ return !!(rt_wdt_r32(TIMER_REG_TMR1CTL) & TMR1CTL_ENABLE);
+}
+
+static const struct watchdog_info mt7621_wdt_info = {
+ .identity = "Mediatek Watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops mt7621_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = mt7621_wdt_start,
+ .stop = mt7621_wdt_stop,
+ .ping = mt7621_wdt_ping,
+ .set_timeout = mt7621_wdt_set_timeout,
+};
+
+static struct watchdog_device mt7621_wdt_dev = {
+ .info = &mt7621_wdt_info,
+ .ops = &mt7621_wdt_ops,
+ .min_timeout = 1,
+ .max_timeout = 0xfffful / 1000,
+};
+
+static int mt7621_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ mt7621_wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mt7621_wdt_base))
+ return PTR_ERR(mt7621_wdt_base);
+
+ mt7621_wdt_reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (!IS_ERR(mt7621_wdt_reset))
+ reset_control_deassert(mt7621_wdt_reset);
+
+ mt7621_wdt_dev.bootstatus = mt7621_wdt_bootcause();
+
+ watchdog_init_timeout(&mt7621_wdt_dev, mt7621_wdt_dev.max_timeout,
+ dev);
+ watchdog_set_nowayout(&mt7621_wdt_dev, nowayout);
+ if (mt7621_wdt_is_running(&mt7621_wdt_dev)) {
+ /*
+ * Make sure to apply timeout from watchdog core, taking
+ * the prescaler of this driver here into account (the
+ * boot loader might be using a different prescaler).
+ *
+ * To avoid spurious resets because of different scaling,
+ * we first disable the watchdog, set the new prescaler
+ * and timeout, and then re-enable the watchdog.
+ */
+ mt7621_wdt_stop(&mt7621_wdt_dev);
+ mt7621_wdt_start(&mt7621_wdt_dev);
+ set_bit(WDOG_HW_RUNNING, &mt7621_wdt_dev.status);
+ }
+
+ return devm_watchdog_register_device(dev, &mt7621_wdt_dev);
+}
+
+static void mt7621_wdt_shutdown(struct platform_device *pdev)
+{
+ mt7621_wdt_stop(&mt7621_wdt_dev);
+}
+
+static const struct of_device_id mt7621_wdt_match[] = {
+ { .compatible = "mediatek,mt7621-wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mt7621_wdt_match);
+
+static struct platform_driver mt7621_wdt_driver = {
+ .probe = mt7621_wdt_probe,
+ .shutdown = mt7621_wdt_shutdown,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = mt7621_wdt_match,
+ },
+};
+
+module_platform_driver(mt7621_wdt_driver);
+
+MODULE_DESCRIPTION("MediaTek MT762x hardware watchdog driver");
+MODULE_AUTHOR("John Crispin <john@phrozen.org");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/mtk_wdt.c b/drivers/watchdog/mtk_wdt.c
new file mode 100644
index 000000000..e97787536
--- /dev/null
+++ b/drivers/watchdog/mtk_wdt.c
@@ -0,0 +1,462 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Mediatek Watchdog Driver
+ *
+ * Copyright (C) 2014 Matthias Brugger
+ *
+ * Matthias Brugger <matthias.bgg@gmail.com>
+ *
+ * Based on sunxi_wdt.c
+ */
+
+#include <dt-bindings/reset/mt2712-resets.h>
+#include <dt-bindings/reset/mt7986-resets.h>
+#include <dt-bindings/reset/mt8183-resets.h>
+#include <dt-bindings/reset/mt8186-resets.h>
+#include <dt-bindings/reset/mt8192-resets.h>
+#include <dt-bindings/reset/mt8195-resets.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/interrupt.h>
+
+#define WDT_MAX_TIMEOUT 31
+#define WDT_MIN_TIMEOUT 2
+#define WDT_LENGTH_TIMEOUT(n) ((n) << 5)
+
+#define WDT_LENGTH 0x04
+#define WDT_LENGTH_KEY 0x8
+
+#define WDT_RST 0x08
+#define WDT_RST_RELOAD 0x1971
+
+#define WDT_MODE 0x00
+#define WDT_MODE_EN (1 << 0)
+#define WDT_MODE_EXT_POL_LOW (0 << 1)
+#define WDT_MODE_EXT_POL_HIGH (1 << 1)
+#define WDT_MODE_EXRST_EN (1 << 2)
+#define WDT_MODE_IRQ_EN (1 << 3)
+#define WDT_MODE_AUTO_START (1 << 4)
+#define WDT_MODE_DUAL_EN (1 << 6)
+#define WDT_MODE_KEY 0x22000000
+
+#define WDT_SWRST 0x14
+#define WDT_SWRST_KEY 0x1209
+
+#define WDT_SWSYSRST 0x18U
+#define WDT_SWSYS_RST_KEY 0x88000000
+
+#define DRV_NAME "mtk-wdt"
+#define DRV_VERSION "1.0"
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+struct mtk_wdt_dev {
+ struct watchdog_device wdt_dev;
+ void __iomem *wdt_base;
+ spinlock_t lock; /* protects WDT_SWSYSRST reg */
+ struct reset_controller_dev rcdev;
+ bool disable_wdt_extrst;
+};
+
+struct mtk_wdt_data {
+ int toprgu_sw_rst_num;
+};
+
+static const struct mtk_wdt_data mt2712_data = {
+ .toprgu_sw_rst_num = MT2712_TOPRGU_SW_RST_NUM,
+};
+
+static const struct mtk_wdt_data mt7986_data = {
+ .toprgu_sw_rst_num = MT7986_TOPRGU_SW_RST_NUM,
+};
+
+static const struct mtk_wdt_data mt8183_data = {
+ .toprgu_sw_rst_num = MT8183_TOPRGU_SW_RST_NUM,
+};
+
+static const struct mtk_wdt_data mt8186_data = {
+ .toprgu_sw_rst_num = MT8186_TOPRGU_SW_RST_NUM,
+};
+
+static const struct mtk_wdt_data mt8192_data = {
+ .toprgu_sw_rst_num = MT8192_TOPRGU_SW_RST_NUM,
+};
+
+static const struct mtk_wdt_data mt8195_data = {
+ .toprgu_sw_rst_num = MT8195_TOPRGU_SW_RST_NUM,
+};
+
+static int toprgu_reset_update(struct reset_controller_dev *rcdev,
+ unsigned long id, bool assert)
+{
+ unsigned int tmp;
+ unsigned long flags;
+ struct mtk_wdt_dev *data =
+ container_of(rcdev, struct mtk_wdt_dev, rcdev);
+
+ spin_lock_irqsave(&data->lock, flags);
+
+ tmp = readl(data->wdt_base + WDT_SWSYSRST);
+ if (assert)
+ tmp |= BIT(id);
+ else
+ tmp &= ~BIT(id);
+ tmp |= WDT_SWSYS_RST_KEY;
+ writel(tmp, data->wdt_base + WDT_SWSYSRST);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ return 0;
+}
+
+static int toprgu_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return toprgu_reset_update(rcdev, id, true);
+}
+
+static int toprgu_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ return toprgu_reset_update(rcdev, id, false);
+}
+
+static int toprgu_reset(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ int ret;
+
+ ret = toprgu_reset_assert(rcdev, id);
+ if (ret)
+ return ret;
+
+ return toprgu_reset_deassert(rcdev, id);
+}
+
+static const struct reset_control_ops toprgu_reset_ops = {
+ .assert = toprgu_reset_assert,
+ .deassert = toprgu_reset_deassert,
+ .reset = toprgu_reset,
+};
+
+static int toprgu_register_reset_controller(struct platform_device *pdev,
+ int rst_num)
+{
+ int ret;
+ struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev);
+
+ spin_lock_init(&mtk_wdt->lock);
+
+ mtk_wdt->rcdev.owner = THIS_MODULE;
+ mtk_wdt->rcdev.nr_resets = rst_num;
+ mtk_wdt->rcdev.ops = &toprgu_reset_ops;
+ mtk_wdt->rcdev.of_node = pdev->dev.of_node;
+ ret = devm_reset_controller_register(&pdev->dev, &mtk_wdt->rcdev);
+ if (ret != 0)
+ dev_err(&pdev->dev,
+ "couldn't register wdt reset controller: %d\n", ret);
+ return ret;
+}
+
+static int mtk_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base;
+
+ wdt_base = mtk_wdt->wdt_base;
+
+ while (1) {
+ writel(WDT_SWRST_KEY, wdt_base + WDT_SWRST);
+ mdelay(5);
+ }
+
+ return 0;
+}
+
+static int mtk_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+
+ iowrite32(WDT_RST_RELOAD, wdt_base + WDT_RST);
+
+ return 0;
+}
+
+static int mtk_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+ u32 reg;
+
+ wdt_dev->timeout = timeout;
+ /*
+ * In dual mode, irq will be triggered at timeout / 2
+ * the real timeout occurs at timeout
+ */
+ if (wdt_dev->pretimeout)
+ wdt_dev->pretimeout = timeout / 2;
+
+ /*
+ * One bit is the value of 512 ticks
+ * The clock has 32 KHz
+ */
+ reg = WDT_LENGTH_TIMEOUT((timeout - wdt_dev->pretimeout) << 6)
+ | WDT_LENGTH_KEY;
+ iowrite32(reg, wdt_base + WDT_LENGTH);
+
+ mtk_wdt_ping(wdt_dev);
+
+ return 0;
+}
+
+static void mtk_wdt_init(struct watchdog_device *wdt_dev)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base;
+
+ wdt_base = mtk_wdt->wdt_base;
+
+ if (readl(wdt_base + WDT_MODE) & WDT_MODE_EN) {
+ set_bit(WDOG_HW_RUNNING, &wdt_dev->status);
+ mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ }
+}
+
+static int mtk_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+ u32 reg;
+
+ reg = readl(wdt_base + WDT_MODE);
+ reg &= ~WDT_MODE_EN;
+ reg |= WDT_MODE_KEY;
+ iowrite32(reg, wdt_base + WDT_MODE);
+
+ return 0;
+}
+
+static int mtk_wdt_start(struct watchdog_device *wdt_dev)
+{
+ u32 reg;
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+ int ret;
+
+ ret = mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ if (ret < 0)
+ return ret;
+
+ reg = ioread32(wdt_base + WDT_MODE);
+ if (wdt_dev->pretimeout)
+ reg |= (WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
+ else
+ reg &= ~(WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
+ if (mtk_wdt->disable_wdt_extrst)
+ reg &= ~WDT_MODE_EXRST_EN;
+ reg |= (WDT_MODE_EN | WDT_MODE_KEY);
+ iowrite32(reg, wdt_base + WDT_MODE);
+
+ return 0;
+}
+
+static int mtk_wdt_set_pretimeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdd);
+ void __iomem *wdt_base = mtk_wdt->wdt_base;
+ u32 reg = ioread32(wdt_base + WDT_MODE);
+
+ if (timeout && !wdd->pretimeout) {
+ wdd->pretimeout = wdd->timeout / 2;
+ reg |= (WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
+ } else if (!timeout && wdd->pretimeout) {
+ wdd->pretimeout = 0;
+ reg &= ~(WDT_MODE_IRQ_EN | WDT_MODE_DUAL_EN);
+ } else {
+ return 0;
+ }
+
+ reg |= WDT_MODE_KEY;
+ iowrite32(reg, wdt_base + WDT_MODE);
+
+ return mtk_wdt_set_timeout(wdd, wdd->timeout);
+}
+
+static irqreturn_t mtk_wdt_isr(int irq, void *arg)
+{
+ struct watchdog_device *wdd = arg;
+
+ watchdog_notify_pretimeout(wdd);
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_info mtk_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_info mtk_wdt_pt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_PRETIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops mtk_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = mtk_wdt_start,
+ .stop = mtk_wdt_stop,
+ .ping = mtk_wdt_ping,
+ .set_timeout = mtk_wdt_set_timeout,
+ .set_pretimeout = mtk_wdt_set_pretimeout,
+ .restart = mtk_wdt_restart,
+};
+
+static int mtk_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mtk_wdt_dev *mtk_wdt;
+ const struct mtk_wdt_data *wdt_data;
+ int err, irq;
+
+ mtk_wdt = devm_kzalloc(dev, sizeof(*mtk_wdt), GFP_KERNEL);
+ if (!mtk_wdt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mtk_wdt);
+
+ mtk_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mtk_wdt->wdt_base))
+ return PTR_ERR(mtk_wdt->wdt_base);
+
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq > 0) {
+ err = devm_request_irq(&pdev->dev, irq, mtk_wdt_isr, 0, "wdt_bark",
+ &mtk_wdt->wdt_dev);
+ if (err)
+ return err;
+
+ mtk_wdt->wdt_dev.info = &mtk_wdt_pt_info;
+ mtk_wdt->wdt_dev.pretimeout = WDT_MAX_TIMEOUT / 2;
+ } else {
+ if (irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ mtk_wdt->wdt_dev.info = &mtk_wdt_info;
+ }
+
+ mtk_wdt->wdt_dev.ops = &mtk_wdt_ops;
+ mtk_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
+ mtk_wdt->wdt_dev.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT * 1000;
+ mtk_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
+ mtk_wdt->wdt_dev.parent = dev;
+
+ watchdog_init_timeout(&mtk_wdt->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&mtk_wdt->wdt_dev, nowayout);
+ watchdog_set_restart_priority(&mtk_wdt->wdt_dev, 128);
+
+ watchdog_set_drvdata(&mtk_wdt->wdt_dev, mtk_wdt);
+
+ mtk_wdt_init(&mtk_wdt->wdt_dev);
+
+ watchdog_stop_on_reboot(&mtk_wdt->wdt_dev);
+ err = devm_watchdog_register_device(dev, &mtk_wdt->wdt_dev);
+ if (unlikely(err))
+ return err;
+
+ dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)\n",
+ mtk_wdt->wdt_dev.timeout, nowayout);
+
+ wdt_data = of_device_get_match_data(dev);
+ if (wdt_data) {
+ err = toprgu_register_reset_controller(pdev,
+ wdt_data->toprgu_sw_rst_num);
+ if (err)
+ return err;
+ }
+
+ mtk_wdt->disable_wdt_extrst =
+ of_property_read_bool(dev->of_node, "mediatek,disable-extrst");
+
+ return 0;
+}
+
+static int mtk_wdt_suspend(struct device *dev)
+{
+ struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&mtk_wdt->wdt_dev))
+ mtk_wdt_stop(&mtk_wdt->wdt_dev);
+
+ return 0;
+}
+
+static int mtk_wdt_resume(struct device *dev)
+{
+ struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&mtk_wdt->wdt_dev)) {
+ mtk_wdt_start(&mtk_wdt->wdt_dev);
+ mtk_wdt_ping(&mtk_wdt->wdt_dev);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mtk_wdt_dt_ids[] = {
+ { .compatible = "mediatek,mt2712-wdt", .data = &mt2712_data },
+ { .compatible = "mediatek,mt6589-wdt" },
+ { .compatible = "mediatek,mt7986-wdt", .data = &mt7986_data },
+ { .compatible = "mediatek,mt8183-wdt", .data = &mt8183_data },
+ { .compatible = "mediatek,mt8186-wdt", .data = &mt8186_data },
+ { .compatible = "mediatek,mt8192-wdt", .data = &mt8192_data },
+ { .compatible = "mediatek,mt8195-wdt", .data = &mt8195_data },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mtk_wdt_dt_ids);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(mtk_wdt_pm_ops,
+ mtk_wdt_suspend, mtk_wdt_resume);
+
+static struct platform_driver mtk_wdt_driver = {
+ .probe = mtk_wdt_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .pm = pm_sleep_ptr(&mtk_wdt_pm_ops),
+ .of_match_table = mtk_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(mtk_wdt_driver);
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matthias Brugger <matthias.bgg@gmail.com>");
+MODULE_DESCRIPTION("Mediatek WatchDog Timer Driver");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/watchdog/mtx-1_wdt.c b/drivers/watchdog/mtx-1_wdt.c
new file mode 100644
index 000000000..ea1bbf5ee
--- /dev/null
+++ b/drivers/watchdog/mtx-1_wdt.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the MTX-1 Watchdog.
+ *
+ * (C) Copyright 2005 4G Systems <info@4g-systems.biz>,
+ * All Rights Reserved.
+ * http://www.4g-systems.biz
+ *
+ * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org>
+ * (c) Copyright 2005 4G Systems <info@4g-systems.biz>
+ *
+ * Release 0.01.
+ * Author: Michael Stickel michael.stickel@4g-systems.biz
+ *
+ * Release 0.02.
+ * Author: Florian Fainelli florian@openwrt.org
+ * use the Linux watchdog/timer APIs
+ *
+ * The Watchdog is configured to reset the MTX-1
+ * if it is not triggered for 100 seconds.
+ * It should not be triggered more often than 1.6 seconds.
+ *
+ * A timer triggers the watchdog every 5 seconds, until
+ * it is opened for the first time. After the first open
+ * it MUST be triggered every 2..95 seconds.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/gpio/consumer.h>
+
+#define MTX1_WDT_INTERVAL (5 * HZ)
+
+static int ticks = 100 * HZ;
+
+static struct {
+ struct completion stop;
+ spinlock_t lock;
+ int running;
+ struct timer_list timer;
+ int queue;
+ int default_ticks;
+ unsigned long inuse;
+ struct gpio_desc *gpiod;
+ unsigned int gstate;
+} mtx1_wdt_device;
+
+static void mtx1_wdt_trigger(struct timer_list *unused)
+{
+ spin_lock(&mtx1_wdt_device.lock);
+ if (mtx1_wdt_device.running)
+ ticks--;
+
+ /* toggle wdt gpio */
+ mtx1_wdt_device.gstate = !mtx1_wdt_device.gstate;
+ gpiod_set_value(mtx1_wdt_device.gpiod, mtx1_wdt_device.gstate);
+
+ if (mtx1_wdt_device.queue && ticks)
+ mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL);
+ else
+ complete(&mtx1_wdt_device.stop);
+ spin_unlock(&mtx1_wdt_device.lock);
+}
+
+static void mtx1_wdt_reset(void)
+{
+ ticks = mtx1_wdt_device.default_ticks;
+}
+
+
+static void mtx1_wdt_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mtx1_wdt_device.lock, flags);
+ if (!mtx1_wdt_device.queue) {
+ mtx1_wdt_device.queue = 1;
+ mtx1_wdt_device.gstate = 1;
+ gpiod_set_value(mtx1_wdt_device.gpiod, 1);
+ mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL);
+ }
+ mtx1_wdt_device.running++;
+ spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags);
+}
+
+static int mtx1_wdt_stop(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&mtx1_wdt_device.lock, flags);
+ if (mtx1_wdt_device.queue) {
+ mtx1_wdt_device.queue = 0;
+ mtx1_wdt_device.gstate = 0;
+ gpiod_set_value(mtx1_wdt_device.gpiod, 0);
+ }
+ ticks = mtx1_wdt_device.default_ticks;
+ spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags);
+ return 0;
+}
+
+/* Filesystem functions */
+
+static int mtx1_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &mtx1_wdt_device.inuse))
+ return -EBUSY;
+ return stream_open(inode, file);
+}
+
+
+static int mtx1_wdt_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &mtx1_wdt_device.inuse);
+ return 0;
+}
+
+static long mtx1_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = (int __user *)argp;
+ unsigned int value;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET,
+ .identity = "MTX-1 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ put_user(0, p);
+ break;
+ case WDIOC_SETOPTIONS:
+ if (get_user(value, p))
+ return -EFAULT;
+ if (value & WDIOS_ENABLECARD)
+ mtx1_wdt_start();
+ else if (value & WDIOS_DISABLECARD)
+ mtx1_wdt_stop();
+ else
+ return -EINVAL;
+ return 0;
+ case WDIOC_KEEPALIVE:
+ mtx1_wdt_reset();
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+
+static ssize_t mtx1_wdt_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ if (!count)
+ return -EIO;
+ mtx1_wdt_reset();
+ return count;
+}
+
+static const struct file_operations mtx1_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = mtx1_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = mtx1_wdt_open,
+ .write = mtx1_wdt_write,
+ .release = mtx1_wdt_release,
+};
+
+
+static struct miscdevice mtx1_wdt_misc = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mtx1_wdt_fops,
+};
+
+
+static int mtx1_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ mtx1_wdt_device.gpiod = devm_gpiod_get(&pdev->dev,
+ NULL, GPIOD_OUT_HIGH);
+ if (IS_ERR(mtx1_wdt_device.gpiod)) {
+ dev_err(&pdev->dev, "failed to request gpio");
+ return PTR_ERR(mtx1_wdt_device.gpiod);
+ }
+
+ spin_lock_init(&mtx1_wdt_device.lock);
+ init_completion(&mtx1_wdt_device.stop);
+ mtx1_wdt_device.queue = 0;
+ clear_bit(0, &mtx1_wdt_device.inuse);
+ timer_setup(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0);
+ mtx1_wdt_device.default_ticks = ticks;
+
+ ret = misc_register(&mtx1_wdt_misc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register\n");
+ return ret;
+ }
+ mtx1_wdt_start();
+ dev_info(&pdev->dev, "MTX-1 Watchdog driver\n");
+ return 0;
+}
+
+static int mtx1_wdt_remove(struct platform_device *pdev)
+{
+ /* FIXME: do we need to lock this test ? */
+ if (mtx1_wdt_device.queue) {
+ mtx1_wdt_device.queue = 0;
+ wait_for_completion(&mtx1_wdt_device.stop);
+ }
+
+ misc_deregister(&mtx1_wdt_misc);
+ return 0;
+}
+
+static struct platform_driver mtx1_wdt_driver = {
+ .probe = mtx1_wdt_probe,
+ .remove = mtx1_wdt_remove,
+ .driver.name = "mtx1-wdt",
+ .driver.owner = THIS_MODULE,
+};
+
+module_platform_driver(mtx1_wdt_driver);
+
+MODULE_AUTHOR("Michael Stickel, Florian Fainelli");
+MODULE_DESCRIPTION("Driver for the MTX-1 watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mtx1-wdt");
diff --git a/drivers/watchdog/ni903x_wdt.c b/drivers/watchdog/ni903x_wdt.c
new file mode 100644
index 000000000..4cebad324
--- /dev/null
+++ b/drivers/watchdog/ni903x_wdt.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 National Instruments Corp.
+ */
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+
+#define NIWD_CONTROL 0x01
+#define NIWD_COUNTER2 0x02
+#define NIWD_COUNTER1 0x03
+#define NIWD_COUNTER0 0x04
+#define NIWD_SEED2 0x05
+#define NIWD_SEED1 0x06
+#define NIWD_SEED0 0x07
+
+#define NIWD_IO_SIZE 0x08
+
+#define NIWD_CONTROL_MODE 0x80
+#define NIWD_CONTROL_PROC_RESET 0x20
+#define NIWD_CONTROL_PET 0x10
+#define NIWD_CONTROL_RUNNING 0x08
+#define NIWD_CONTROL_CAPTURECOUNTER 0x04
+#define NIWD_CONTROL_RESET 0x02
+#define NIWD_CONTROL_ALARM 0x01
+
+#define NIWD_PERIOD_NS 30720
+#define NIWD_MIN_TIMEOUT 1
+#define NIWD_MAX_TIMEOUT 515
+#define NIWD_DEFAULT_TIMEOUT 60
+
+#define NIWD_NAME "ni903x_wdt"
+
+struct ni903x_wdt {
+ struct device *dev;
+ u16 io_base;
+ struct watchdog_device wdd;
+};
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (default="
+ __MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")");
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, S_IRUGO);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static void ni903x_start(struct ni903x_wdt *wdt)
+{
+ u8 control = inb(wdt->io_base + NIWD_CONTROL);
+
+ outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
+ outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
+}
+
+static int ni903x_wdd_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS);
+
+ outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2);
+ outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1);
+ outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0);
+
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd)
+{
+ struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
+ u8 control, counter0, counter1, counter2;
+ u32 counter;
+
+ control = inb(wdt->io_base + NIWD_CONTROL);
+ control |= NIWD_CONTROL_CAPTURECOUNTER;
+ outb(control, wdt->io_base + NIWD_CONTROL);
+
+ counter2 = inb(wdt->io_base + NIWD_COUNTER2);
+ counter1 = inb(wdt->io_base + NIWD_COUNTER1);
+ counter0 = inb(wdt->io_base + NIWD_COUNTER0);
+
+ counter = (counter2 << 16) | (counter1 << 8) | counter0;
+
+ return counter / (1000000000 / NIWD_PERIOD_NS);
+}
+
+static int ni903x_wdd_ping(struct watchdog_device *wdd)
+{
+ struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
+ u8 control;
+
+ control = inb(wdt->io_base + NIWD_CONTROL);
+ outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
+
+ return 0;
+}
+
+static int ni903x_wdd_start(struct watchdog_device *wdd)
+{
+ struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET,
+ wdt->io_base + NIWD_CONTROL);
+
+ ni903x_wdd_set_timeout(wdd, wdd->timeout);
+ ni903x_start(wdt);
+
+ return 0;
+}
+
+static int ni903x_wdd_stop(struct watchdog_device *wdd)
+{
+ struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
+
+ return 0;
+}
+
+static acpi_status ni903x_resources(struct acpi_resource *res, void *data)
+{
+ struct ni903x_wdt *wdt = data;
+ u16 io_size;
+
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_IO:
+ if (wdt->io_base != 0) {
+ dev_err(wdt->dev, "too many IO resources\n");
+ return AE_ERROR;
+ }
+
+ wdt->io_base = res->data.io.minimum;
+ io_size = res->data.io.address_length;
+
+ if (io_size < NIWD_IO_SIZE) {
+ dev_err(wdt->dev, "memory region too small\n");
+ return AE_ERROR;
+ }
+
+ if (!devm_request_region(wdt->dev, wdt->io_base, io_size,
+ NIWD_NAME)) {
+ dev_err(wdt->dev, "failed to get memory region\n");
+ return AE_ERROR;
+ }
+
+ return AE_OK;
+
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ default:
+ /* Ignore unsupported resources, e.g. IRQ */
+ return AE_OK;
+ }
+}
+
+static const struct watchdog_info ni903x_wdd_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "NI Watchdog",
+};
+
+static const struct watchdog_ops ni903x_wdd_ops = {
+ .owner = THIS_MODULE,
+ .start = ni903x_wdd_start,
+ .stop = ni903x_wdd_stop,
+ .ping = ni903x_wdd_ping,
+ .set_timeout = ni903x_wdd_set_timeout,
+ .get_timeleft = ni903x_wdd_get_timeleft,
+};
+
+static int ni903x_acpi_add(struct acpi_device *device)
+{
+ struct device *dev = &device->dev;
+ struct watchdog_device *wdd;
+ struct ni903x_wdt *wdt;
+ acpi_status status;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ device->driver_data = wdt;
+ wdt->dev = dev;
+
+ status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+ ni903x_resources, wdt);
+ if (ACPI_FAILURE(status) || wdt->io_base == 0) {
+ dev_err(dev, "failed to get resources\n");
+ return -ENODEV;
+ }
+
+ wdd = &wdt->wdd;
+ wdd->info = &ni903x_wdd_info;
+ wdd->ops = &ni903x_wdd_ops;
+ wdd->min_timeout = NIWD_MIN_TIMEOUT;
+ wdd->max_timeout = NIWD_MAX_TIMEOUT;
+ wdd->timeout = NIWD_DEFAULT_TIMEOUT;
+ wdd->parent = dev;
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, timeout, dev);
+
+ ret = watchdog_register_device(wdd);
+ if (ret)
+ return ret;
+
+ /* Switch from boot mode to user mode */
+ outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE,
+ wdt->io_base + NIWD_CONTROL);
+
+ dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
+ wdt->io_base, timeout, nowayout);
+
+ return 0;
+}
+
+static int ni903x_acpi_remove(struct acpi_device *device)
+{
+ struct ni903x_wdt *wdt = acpi_driver_data(device);
+
+ ni903x_wdd_stop(&wdt->wdd);
+ watchdog_unregister_device(&wdt->wdd);
+
+ return 0;
+}
+
+static const struct acpi_device_id ni903x_device_ids[] = {
+ {"NIC775C", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, ni903x_device_ids);
+
+static struct acpi_driver ni903x_acpi_driver = {
+ .name = NIWD_NAME,
+ .ids = ni903x_device_ids,
+ .ops = {
+ .add = ni903x_acpi_add,
+ .remove = ni903x_acpi_remove,
+ },
+};
+
+module_acpi_driver(ni903x_acpi_driver);
+
+MODULE_DESCRIPTION("NI 903x Watchdog");
+MODULE_AUTHOR("Jeff Westfahl <jeff.westfahl@ni.com>");
+MODULE_AUTHOR("Kyle Roeschley <kyle.roeschley@ni.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/nic7018_wdt.c b/drivers/watchdog/nic7018_wdt.c
new file mode 100644
index 000000000..2a46cc662
--- /dev/null
+++ b/drivers/watchdog/nic7018_wdt.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 National Instruments Corp.
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define LOCK 0xA5
+#define UNLOCK 0x5A
+
+#define WDT_CTRL_RESET_EN BIT(7)
+#define WDT_RELOAD_PORT_EN BIT(7)
+
+#define WDT_CTRL 1
+#define WDT_RELOAD_CTRL 2
+#define WDT_PRESET_PRESCALE 4
+#define WDT_REG_LOCK 5
+#define WDT_COUNT 6
+#define WDT_RELOAD_PORT 7
+
+#define WDT_MIN_TIMEOUT 1
+#define WDT_MAX_TIMEOUT 464
+#define WDT_DEFAULT_TIMEOUT 80
+
+#define WDT_MAX_COUNTER 15
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (default="
+ __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started. (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct nic7018_wdt {
+ u16 io_base;
+ u32 period;
+ struct watchdog_device wdd;
+};
+
+struct nic7018_config {
+ u32 period;
+ u8 divider;
+};
+
+static const struct nic7018_config nic7018_configs[] = {
+ { 2, 4 },
+ { 32, 5 },
+};
+
+static inline u32 nic7018_timeout(u32 period, u8 counter)
+{
+ return period * counter - period / 2;
+}
+
+static const struct nic7018_config *nic7018_get_config(u32 timeout,
+ u8 *counter)
+{
+ const struct nic7018_config *config;
+ u8 count;
+
+ if (timeout < 30 && timeout != 16) {
+ config = &nic7018_configs[0];
+ count = timeout / 2 + 1;
+ } else {
+ config = &nic7018_configs[1];
+ count = DIV_ROUND_UP(timeout + 16, 32);
+
+ if (count > WDT_MAX_COUNTER)
+ count = WDT_MAX_COUNTER;
+ }
+ *counter = count;
+ return config;
+}
+
+static int nic7018_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
+ const struct nic7018_config *config;
+ u8 counter;
+
+ config = nic7018_get_config(timeout, &counter);
+
+ outb(counter << 4 | config->divider,
+ wdt->io_base + WDT_PRESET_PRESCALE);
+
+ wdd->timeout = nic7018_timeout(config->period, counter);
+ wdt->period = config->period;
+
+ return 0;
+}
+
+static int nic7018_start(struct watchdog_device *wdd)
+{
+ struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
+ u8 control;
+
+ nic7018_set_timeout(wdd, wdd->timeout);
+
+ control = inb(wdt->io_base + WDT_RELOAD_CTRL);
+ outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL);
+
+ outb(1, wdt->io_base + WDT_RELOAD_PORT);
+
+ control = inb(wdt->io_base + WDT_CTRL);
+ outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL);
+
+ return 0;
+}
+
+static int nic7018_stop(struct watchdog_device *wdd)
+{
+ struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ outb(0, wdt->io_base + WDT_CTRL);
+ outb(0, wdt->io_base + WDT_RELOAD_CTRL);
+ outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE);
+
+ return 0;
+}
+
+static int nic7018_ping(struct watchdog_device *wdd)
+{
+ struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ outb(1, wdt->io_base + WDT_RELOAD_PORT);
+
+ return 0;
+}
+
+static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd)
+{
+ struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
+ u8 count;
+
+ count = inb(wdt->io_base + WDT_COUNT) & 0xF;
+ if (!count)
+ return 0;
+
+ return nic7018_timeout(wdt->period, count);
+}
+
+static const struct watchdog_info nic7018_wdd_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "NIC7018 Watchdog",
+};
+
+static const struct watchdog_ops nic7018_wdd_ops = {
+ .owner = THIS_MODULE,
+ .start = nic7018_start,
+ .stop = nic7018_stop,
+ .ping = nic7018_ping,
+ .set_timeout = nic7018_set_timeout,
+ .get_timeleft = nic7018_get_timeleft,
+};
+
+static int nic7018_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct nic7018_wdt *wdt;
+ struct resource *io_rc;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, wdt);
+
+ io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!io_rc) {
+ dev_err(dev, "missing IO resources\n");
+ return -EINVAL;
+ }
+
+ if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
+ KBUILD_MODNAME)) {
+ dev_err(dev, "failed to get IO region\n");
+ return -EBUSY;
+ }
+
+ wdt->io_base = io_rc->start;
+ wdd = &wdt->wdd;
+ wdd->info = &nic7018_wdd_info;
+ wdd->ops = &nic7018_wdd_ops;
+ wdd->min_timeout = WDT_MIN_TIMEOUT;
+ wdd->max_timeout = WDT_MAX_TIMEOUT;
+ wdd->timeout = WDT_DEFAULT_TIMEOUT;
+ wdd->parent = dev;
+
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, timeout, dev);
+
+ /* Unlock WDT register */
+ outb(UNLOCK, wdt->io_base + WDT_REG_LOCK);
+
+ ret = watchdog_register_device(wdd);
+ if (ret) {
+ outb(LOCK, wdt->io_base + WDT_REG_LOCK);
+ return ret;
+ }
+
+ dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
+ wdt->io_base, timeout, nowayout);
+ return 0;
+}
+
+static int nic7018_remove(struct platform_device *pdev)
+{
+ struct nic7018_wdt *wdt = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&wdt->wdd);
+
+ /* Lock WDT register */
+ outb(LOCK, wdt->io_base + WDT_REG_LOCK);
+
+ return 0;
+}
+
+static const struct acpi_device_id nic7018_device_ids[] = {
+ {"NIC7018", 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, nic7018_device_ids);
+
+static struct platform_driver watchdog_driver = {
+ .probe = nic7018_probe,
+ .remove = nic7018_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .acpi_match_table = ACPI_PTR(nic7018_device_ids),
+ },
+};
+
+module_platform_driver(watchdog_driver);
+
+MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver");
+MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/npcm_wdt.c b/drivers/watchdog/npcm_wdt.c
new file mode 100644
index 000000000..a5dd1c230
--- /dev/null
+++ b/drivers/watchdog/npcm_wdt.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 Nuvoton Technology corporation.
+// Copyright (c) 2018 IBM Corp.
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define NPCM_WTCR 0x1C
+
+#define NPCM_WTCLK (BIT(10) | BIT(11)) /* Clock divider */
+#define NPCM_WTE BIT(7) /* Enable */
+#define NPCM_WTIE BIT(6) /* Enable irq */
+#define NPCM_WTIS (BIT(4) | BIT(5)) /* Interval selection */
+#define NPCM_WTIF BIT(3) /* Interrupt flag*/
+#define NPCM_WTRF BIT(2) /* Reset flag */
+#define NPCM_WTRE BIT(1) /* Reset enable */
+#define NPCM_WTR BIT(0) /* Reset counter */
+
+/*
+ * Watchdog timeouts
+ *
+ * 170 msec: WTCLK=01 WTIS=00 VAL= 0x400
+ * 670 msec: WTCLK=01 WTIS=01 VAL= 0x410
+ * 1360 msec: WTCLK=10 WTIS=00 VAL= 0x800
+ * 2700 msec: WTCLK=01 WTIS=10 VAL= 0x420
+ * 5360 msec: WTCLK=10 WTIS=01 VAL= 0x810
+ * 10700 msec: WTCLK=01 WTIS=11 VAL= 0x430
+ * 21600 msec: WTCLK=10 WTIS=10 VAL= 0x820
+ * 43000 msec: WTCLK=11 WTIS=00 VAL= 0xC00
+ * 85600 msec: WTCLK=10 WTIS=11 VAL= 0x830
+ * 172000 msec: WTCLK=11 WTIS=01 VAL= 0xC10
+ * 687000 msec: WTCLK=11 WTIS=10 VAL= 0xC20
+ * 2750000 msec: WTCLK=11 WTIS=11 VAL= 0xC30
+ */
+
+struct npcm_wdt {
+ struct watchdog_device wdd;
+ void __iomem *reg;
+ struct clk *clk;
+};
+
+static inline struct npcm_wdt *to_npcm_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct npcm_wdt, wdd);
+}
+
+static int npcm_wdt_ping(struct watchdog_device *wdd)
+{
+ struct npcm_wdt *wdt = to_npcm_wdt(wdd);
+ u32 val;
+
+ val = readl(wdt->reg);
+ writel(val | NPCM_WTR, wdt->reg);
+
+ return 0;
+}
+
+static int npcm_wdt_start(struct watchdog_device *wdd)
+{
+ struct npcm_wdt *wdt = to_npcm_wdt(wdd);
+ u32 val;
+
+ if (wdt->clk)
+ clk_prepare_enable(wdt->clk);
+
+ if (wdd->timeout < 2)
+ val = 0x800;
+ else if (wdd->timeout < 3)
+ val = 0x420;
+ else if (wdd->timeout < 6)
+ val = 0x810;
+ else if (wdd->timeout < 11)
+ val = 0x430;
+ else if (wdd->timeout < 22)
+ val = 0x820;
+ else if (wdd->timeout < 44)
+ val = 0xC00;
+ else if (wdd->timeout < 87)
+ val = 0x830;
+ else if (wdd->timeout < 173)
+ val = 0xC10;
+ else if (wdd->timeout < 688)
+ val = 0xC20;
+ else
+ val = 0xC30;
+
+ val |= NPCM_WTRE | NPCM_WTE | NPCM_WTR | NPCM_WTIE;
+
+ writel(val, wdt->reg);
+
+ return 0;
+}
+
+static int npcm_wdt_stop(struct watchdog_device *wdd)
+{
+ struct npcm_wdt *wdt = to_npcm_wdt(wdd);
+
+ writel(0, wdt->reg);
+
+ if (wdt->clk)
+ clk_disable_unprepare(wdt->clk);
+
+ return 0;
+}
+
+static int npcm_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ if (timeout < 2)
+ wdd->timeout = 1;
+ else if (timeout < 3)
+ wdd->timeout = 2;
+ else if (timeout < 6)
+ wdd->timeout = 5;
+ else if (timeout < 11)
+ wdd->timeout = 10;
+ else if (timeout < 22)
+ wdd->timeout = 21;
+ else if (timeout < 44)
+ wdd->timeout = 43;
+ else if (timeout < 87)
+ wdd->timeout = 86;
+ else if (timeout < 173)
+ wdd->timeout = 172;
+ else if (timeout < 688)
+ wdd->timeout = 687;
+ else
+ wdd->timeout = 2750;
+
+ if (watchdog_active(wdd))
+ npcm_wdt_start(wdd);
+
+ return 0;
+}
+
+static irqreturn_t npcm_wdt_interrupt(int irq, void *data)
+{
+ struct npcm_wdt *wdt = data;
+
+ watchdog_notify_pretimeout(&wdt->wdd);
+
+ return IRQ_HANDLED;
+}
+
+static int npcm_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct npcm_wdt *wdt = to_npcm_wdt(wdd);
+
+ /* For reset, we start the WDT clock and leave it running. */
+ if (wdt->clk)
+ clk_prepare_enable(wdt->clk);
+
+ writel(NPCM_WTR | NPCM_WTRE | NPCM_WTE, wdt->reg);
+ udelay(1000);
+
+ return 0;
+}
+
+static bool npcm_is_running(struct watchdog_device *wdd)
+{
+ struct npcm_wdt *wdt = to_npcm_wdt(wdd);
+
+ return readl(wdt->reg) & NPCM_WTE;
+}
+
+static const struct watchdog_info npcm_wdt_info = {
+ .identity = KBUILD_MODNAME,
+ .options = WDIOF_SETTIMEOUT
+ | WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops npcm_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = npcm_wdt_start,
+ .stop = npcm_wdt_stop,
+ .ping = npcm_wdt_ping,
+ .set_timeout = npcm_wdt_set_timeout,
+ .restart = npcm_wdt_restart,
+};
+
+static int npcm_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct npcm_wdt *wdt;
+ int irq;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->reg))
+ return PTR_ERR(wdt->reg);
+
+ wdt->clk = devm_clk_get_optional(&pdev->dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return PTR_ERR(wdt->clk);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ wdt->wdd.info = &npcm_wdt_info;
+ wdt->wdd.ops = &npcm_wdt_ops;
+ wdt->wdd.min_timeout = 1;
+ wdt->wdd.max_timeout = 2750;
+ wdt->wdd.parent = dev;
+
+ wdt->wdd.timeout = 86;
+ watchdog_init_timeout(&wdt->wdd, 0, dev);
+
+ /* Ensure timeout is able to be represented by the hardware */
+ npcm_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout);
+
+ if (npcm_is_running(&wdt->wdd)) {
+ /* Restart with the default or device-tree specified timeout */
+ npcm_wdt_start(&wdt->wdd);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+
+ ret = devm_request_irq(dev, irq, npcm_wdt_interrupt, 0, "watchdog",
+ wdt);
+ if (ret)
+ return ret;
+
+ ret = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "NPCM watchdog driver enabled\n");
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id npcm_wdt_match[] = {
+ {.compatible = "nuvoton,wpcm450-wdt"},
+ {.compatible = "nuvoton,npcm750-wdt"},
+ {},
+};
+MODULE_DEVICE_TABLE(of, npcm_wdt_match);
+#endif
+
+static struct platform_driver npcm_wdt_driver = {
+ .probe = npcm_wdt_probe,
+ .driver = {
+ .name = "npcm-wdt",
+ .of_match_table = of_match_ptr(npcm_wdt_match),
+ },
+};
+module_platform_driver(npcm_wdt_driver);
+
+MODULE_AUTHOR("Joel Stanley");
+MODULE_DESCRIPTION("Watchdog driver for NPCM");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c
new file mode 100644
index 000000000..f6902a337
--- /dev/null
+++ b/drivers/watchdog/nv_tco.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * nv_tco 0.01: TCO timer driver for NV chipsets
+ *
+ * (c) Copyright 2005 Google Inc., All Rights Reserved.
+ *
+ * Based off i8xx_tco.c:
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ * Reserved.
+ * https://www.kernelconcepts.de
+ *
+ * TCO timer driver for NV chipsets
+ * based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "nv_tco.h"
+
+/* Module and version information */
+#define TCO_VERSION "0.01"
+#define TCO_MODULE_NAME "NV_TCO"
+#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
+
+/* internal variables */
+static unsigned int tcobase;
+static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */
+static unsigned long timer_alive;
+static char tco_expect_close;
+static struct pci_dev *tco_pci;
+
+/* the watchdog platform device */
+static struct platform_device *nv_tco_platform_device;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
+static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, "
+ "default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+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) ")");
+
+/*
+ * Some TCO specific functions
+ */
+static inline unsigned char seconds_to_ticks(int seconds)
+{
+ /* the internal timer is stored as ticks which decrement
+ * every 0.6 seconds */
+ return (seconds * 10) / 6;
+}
+
+static void tco_timer_start(void)
+{
+ u32 val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tco_lock, flags);
+ val = inl(TCO_CNT(tcobase));
+ val &= ~TCO_CNT_TCOHALT;
+ outl(val, TCO_CNT(tcobase));
+ spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_stop(void)
+{
+ u32 val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tco_lock, flags);
+ val = inl(TCO_CNT(tcobase));
+ val |= TCO_CNT_TCOHALT;
+ outl(val, TCO_CNT(tcobase));
+ spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static void tco_timer_keepalive(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tco_lock, flags);
+ outb(0x01, TCO_RLD(tcobase));
+ spin_unlock_irqrestore(&tco_lock, flags);
+}
+
+static int tco_timer_set_heartbeat(int t)
+{
+ int ret = 0;
+ unsigned char tmrval;
+ unsigned long flags;
+ u8 val;
+
+ /*
+ * note seconds_to_ticks(t) > t, so if t > 0x3f, so is
+ * tmrval=seconds_to_ticks(t). Check that the count in seconds isn't
+ * out of range on it's own (to avoid overflow in tmrval).
+ */
+ if (t < 0 || t > 0x3f)
+ return -EINVAL;
+ tmrval = seconds_to_ticks(t);
+
+ /* "Values of 0h-3h are ignored and should not be attempted" */
+ if (tmrval > 0x3f || tmrval < 0x04)
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ spin_lock_irqsave(&tco_lock, flags);
+ val = inb(TCO_TMR(tcobase));
+ val &= 0xc0;
+ val |= tmrval;
+ outb(val, TCO_TMR(tcobase));
+ val = inb(TCO_TMR(tcobase));
+
+ if ((val & 0x3f) != tmrval)
+ ret = -EINVAL;
+ spin_unlock_irqrestore(&tco_lock, flags);
+
+ if (ret)
+ return ret;
+
+ heartbeat = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int nv_tco_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &timer_alive))
+ return -EBUSY;
+
+ /* Reload and activate timer */
+ tco_timer_keepalive();
+ tco_timer_start();
+ return stream_open(inode, file);
+}
+
+static int nv_tco_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer */
+ if (tco_expect_close == 42) {
+ tco_timer_stop();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ tco_timer_keepalive();
+ }
+ clear_bit(0, &timer_alive);
+ tco_expect_close = 0;
+ return 0;
+}
+
+static ssize_t nv_tco_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /*
+ * note: just in case someone wrote the magic character
+ * five months ago...
+ */
+ tco_expect_close = 0;
+
+ /*
+ * scan to see whether or not we got the magic
+ * character
+ */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ tco_expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ tco_timer_keepalive();
+ }
+ return len;
+}
+
+static long nv_tco_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_options, retval = -EINVAL;
+ int new_heartbeat;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = TCO_MODULE_NAME,
+ };
+
+ 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:
+ if (get_user(new_options, p))
+ return -EFAULT;
+ if (new_options & WDIOS_DISABLECARD) {
+ tco_timer_stop();
+ retval = 0;
+ }
+ if (new_options & WDIOS_ENABLECARD) {
+ tco_timer_keepalive();
+ tco_timer_start();
+ retval = 0;
+ }
+ return retval;
+ case WDIOC_KEEPALIVE:
+ tco_timer_keepalive();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+ if (tco_timer_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+ tco_timer_keepalive();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations nv_tco_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = nv_tco_write,
+ .unlocked_ioctl = nv_tco_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = nv_tco_open,
+ .release = nv_tco_release,
+};
+
+static struct miscdevice nv_tco_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &nv_tco_fops,
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static const struct pci_device_id tco_pci_tbl[] = {
+ { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SMBUS,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SMBUS,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP78S_SMBUS,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP79_SMBUS,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, tco_pci_tbl);
+
+/*
+ * Init & exit routines
+ */
+
+static unsigned char nv_tco_getdevice(void)
+{
+ struct pci_dev *dev = NULL;
+ u32 val;
+
+ /* Find the PCI device */
+ for_each_pci_dev(dev) {
+ if (pci_match_id(tco_pci_tbl, dev) != NULL) {
+ tco_pci = dev;
+ break;
+ }
+ }
+
+ if (!tco_pci)
+ return 0;
+
+ /* Find the base io port */
+ pci_read_config_dword(tco_pci, 0x64, &val);
+ val &= 0xffff;
+ if (val == 0x0001 || val == 0x0000) {
+ /* Something is wrong here, bar isn't setup */
+ pr_err("failed to get tcobase address\n");
+ return 0;
+ }
+ val &= 0xff00;
+ tcobase = val + 0x40;
+
+ if (!request_region(tcobase, 0x10, "NV TCO")) {
+ pr_err("I/O address 0x%04x already in use\n", tcobase);
+ return 0;
+ }
+
+ /* Set a reasonable heartbeat before we stop the timer */
+ tco_timer_set_heartbeat(30);
+
+ /*
+ * Stop the TCO before we change anything so we don't race with
+ * a zeroed timer.
+ */
+ tco_timer_keepalive();
+ tco_timer_stop();
+
+ /* Disable SMI caused by TCO */
+ if (!request_region(MCP51_SMI_EN(tcobase), 4, "NV TCO")) {
+ pr_err("I/O address 0x%04x already in use\n",
+ MCP51_SMI_EN(tcobase));
+ goto out;
+ }
+ val = inl(MCP51_SMI_EN(tcobase));
+ val &= ~MCP51_SMI_EN_TCO;
+ outl(val, MCP51_SMI_EN(tcobase));
+ val = inl(MCP51_SMI_EN(tcobase));
+ release_region(MCP51_SMI_EN(tcobase), 4);
+ if (val & MCP51_SMI_EN_TCO) {
+ pr_err("Could not disable SMI caused by TCO\n");
+ goto out;
+ }
+
+ /* Check chipset's NO_REBOOT bit */
+ pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+ val |= MCP51_SMBUS_SETUP_B_TCO_REBOOT;
+ pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
+ pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+ if (!(val & MCP51_SMBUS_SETUP_B_TCO_REBOOT)) {
+ pr_err("failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
+ goto out;
+ }
+
+ return 1;
+out:
+ release_region(tcobase, 0x10);
+ return 0;
+}
+
+static int nv_tco_init(struct platform_device *dev)
+{
+ int ret;
+
+ /* Check whether or not the hardware watchdog is there */
+ if (!nv_tco_getdevice())
+ return -ENODEV;
+
+ /* Check to see if last reboot was due to watchdog timeout */
+ pr_info("Watchdog reboot %sdetected\n",
+ inl(TCO_STS(tcobase)) & TCO_STS_TCO2TO_STS ? "" : "not ");
+
+ /* Clear out the old status */
+ outl(TCO_STS_RESET, TCO_STS(tcobase));
+
+ /*
+ * Check that the heartbeat value is within it's range.
+ * If not, reset to the default.
+ */
+ if (tco_timer_set_heartbeat(heartbeat)) {
+ heartbeat = WATCHDOG_HEARTBEAT;
+ tco_timer_set_heartbeat(heartbeat);
+ pr_info("heartbeat value must be 2<heartbeat<39, using %d\n",
+ heartbeat);
+ }
+
+ ret = misc_register(&nv_tco_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_region;
+ }
+
+ clear_bit(0, &timer_alive);
+
+ tco_timer_stop();
+
+ pr_info("initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
+ tcobase, heartbeat, nowayout);
+
+ return 0;
+
+unreg_region:
+ release_region(tcobase, 0x10);
+ return ret;
+}
+
+static void nv_tco_cleanup(void)
+{
+ u32 val;
+
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ tco_timer_stop();
+
+ /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+ pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+ val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
+ pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
+ pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+ if (val & MCP51_SMBUS_SETUP_B_TCO_REBOOT) {
+ pr_crit("Couldn't unset REBOOT bit. Machine may soon reset\n");
+ }
+
+ /* Deregister */
+ misc_deregister(&nv_tco_miscdev);
+ release_region(tcobase, 0x10);
+}
+
+static int nv_tco_remove(struct platform_device *dev)
+{
+ if (tcobase)
+ nv_tco_cleanup();
+
+ return 0;
+}
+
+static void nv_tco_shutdown(struct platform_device *dev)
+{
+ u32 val;
+
+ tco_timer_stop();
+
+ /* Some BIOSes fail the POST (once) if the NO_REBOOT flag is not
+ * unset during shutdown. */
+ pci_read_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, &val);
+ val &= ~MCP51_SMBUS_SETUP_B_TCO_REBOOT;
+ pci_write_config_dword(tco_pci, MCP51_SMBUS_SETUP_B, val);
+}
+
+static struct platform_driver nv_tco_driver = {
+ .probe = nv_tco_init,
+ .remove = nv_tco_remove,
+ .shutdown = nv_tco_shutdown,
+ .driver = {
+ .name = TCO_MODULE_NAME,
+ },
+};
+
+static int __init nv_tco_init_module(void)
+{
+ int err;
+
+ pr_info("NV TCO WatchDog Timer Driver v%s\n", TCO_VERSION);
+
+ err = platform_driver_register(&nv_tco_driver);
+ if (err)
+ return err;
+
+ nv_tco_platform_device = platform_device_register_simple(
+ TCO_MODULE_NAME, -1, NULL, 0);
+ if (IS_ERR(nv_tco_platform_device)) {
+ err = PTR_ERR(nv_tco_platform_device);
+ goto unreg_platform_driver;
+ }
+
+ return 0;
+
+unreg_platform_driver:
+ platform_driver_unregister(&nv_tco_driver);
+ return err;
+}
+
+static void __exit nv_tco_cleanup_module(void)
+{
+ platform_device_unregister(nv_tco_platform_device);
+ platform_driver_unregister(&nv_tco_driver);
+ pr_info("NV TCO Watchdog Module Unloaded\n");
+}
+
+module_init(nv_tco_init_module);
+module_exit(nv_tco_cleanup_module);
+
+MODULE_AUTHOR("Mike Waychison");
+MODULE_DESCRIPTION("TCO timer driver for NV chipsets");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/nv_tco.h b/drivers/watchdog/nv_tco.h
new file mode 100644
index 000000000..c65f82588
--- /dev/null
+++ b/drivers/watchdog/nv_tco.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * nv_tco: TCO timer driver for nVidia chipsets.
+ *
+ * (c) Copyright 2005 Google Inc., All Rights Reserved.
+ *
+ * Supported Chipsets:
+ * - MCP51/MCP55
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ * Reserved.
+ * https://www.kernelconcepts.de
+ *
+ * Neither kernel concepts nor Nils Faerber admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
+ * developed for
+ * Jentro AG, Haar/Munich (Germany)
+ *
+ * TCO timer driver for NV chipsets
+ * based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+/*
+ * Some address definitions for the TCO
+ */
+
+#define TCO_RLD(base) ((base) + 0x00) /* TCO Timer Reload and Current Value */
+#define TCO_TMR(base) ((base) + 0x01) /* TCO Timer Initial Value */
+
+#define TCO_STS(base) ((base) + 0x04) /* TCO Status Register */
+/*
+ * TCO Boot Status bit: set on TCO reset, reset by software or standby
+ * power-good (survives reboots), unfortunately this bit is never
+ * set.
+ */
+# define TCO_STS_BOOT_STS (1 << 9)
+/*
+ * first and 2nd timeout status bits, these also survive a warm boot,
+ * and they work, so we use them.
+ */
+# define TCO_STS_TCO_INT_STS (1 << 1)
+# define TCO_STS_TCO2TO_STS (1 << 10)
+# define TCO_STS_RESET (TCO_STS_BOOT_STS | TCO_STS_TCO2TO_STS | \
+ TCO_STS_TCO_INT_STS)
+
+#define TCO_CNT(base) ((base) + 0x08) /* TCO Control Register */
+# define TCO_CNT_TCOHALT (1 << 12)
+
+#define MCP51_SMBUS_SETUP_B 0xe8
+# define MCP51_SMBUS_SETUP_B_TCO_REBOOT (1 << 25)
+
+/*
+ * The SMI_EN register is at the base io address + 0x04,
+ * while TCOBASE is + 0x40.
+ */
+#define MCP51_SMI_EN(base) ((base) - 0x40 + 0x04)
+# define MCP51_SMI_EN_TCO ((1 << 4) | (1 << 5))
diff --git a/drivers/watchdog/octeon-wdt-main.c b/drivers/watchdog/octeon-wdt-main.c
new file mode 100644
index 000000000..0fe71f7e6
--- /dev/null
+++ b/drivers/watchdog/octeon-wdt-main.c
@@ -0,0 +1,613 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Octeon Watchdog driver
+ *
+ * Copyright (C) 2007-2017 Cavium, Inc.
+ *
+ * Converted to use WATCHDOG_CORE by Aaro Koskinen <aaro.koskinen@iki.fi>.
+ *
+ * Some parts derived from wdt.c
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * The OCTEON watchdog has a maximum timeout of 2^32 * io_clock.
+ * For most systems this is less than 10 seconds, so to allow for
+ * software to request longer watchdog heartbeats, we maintain software
+ * counters to count multiples of the base rate. If the system locks
+ * up in such a manner that we can not run the software counters, the
+ * only result is a watchdog reset sooner than was requested. But
+ * that is OK, because in this case userspace would likely not be able
+ * to do anything anyhow.
+ *
+ * The hardware watchdog interval we call the period. The OCTEON
+ * watchdog goes through several stages, after the first period an
+ * irq is asserted, then if it is not reset, after the next period NMI
+ * is asserted, then after an additional period a chip wide soft reset.
+ * So for the software counters, we reset watchdog after each period
+ * and decrement the counter. But for the last two periods we need to
+ * let the watchdog progress to the NMI stage so we disable the irq
+ * and let it proceed. Once in the NMI, we print the register state
+ * to the serial port and then wait for the reset.
+ *
+ * A watchdog is maintained for each CPU in the system, that way if
+ * one CPU suffers a lockup, we also get a register dump and reset.
+ * The userspace ping resets the watchdog on all CPUs.
+ *
+ * Before userspace opens the watchdog device, we still run the
+ * watchdogs to catch any lockups that may be kernel related.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/interrupt.h>
+#include <linux/watchdog.h>
+#include <linux/cpumask.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/cpu.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+
+#include <asm/mipsregs.h>
+#include <asm/uasm.h>
+
+#include <asm/octeon/octeon.h>
+#include <asm/octeon/cvmx-boot-vector.h>
+#include <asm/octeon/cvmx-ciu2-defs.h>
+#include <asm/octeon/cvmx-rst-defs.h>
+
+/* Watchdog interrupt major block number (8 MSBs of intsn) */
+#define WD_BLOCK_NUMBER 0x01
+
+static int divisor;
+
+/* The count needed to achieve timeout_sec. */
+static unsigned int timeout_cnt;
+
+/* The maximum period supported. */
+static unsigned int max_timeout_sec;
+
+/* The current period. */
+static unsigned int timeout_sec;
+
+/* Set to non-zero when userspace countdown mode active */
+static bool do_countdown;
+static unsigned int countdown_reset;
+static unsigned int per_cpu_countdown[NR_CPUS];
+
+static cpumask_t irq_enabled_cpus;
+
+#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
+
+#define CVMX_GSERX_SCRATCH(offset) (CVMX_ADD_IO_SEG(0x0001180090000020ull) + ((offset) & 15) * 0x1000000ull)
+
+static int heartbeat = WD_TIMO;
+module_param(heartbeat, int, 0444);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat in seconds. (0 < heartbeat, default="
+ __MODULE_STRING(WD_TIMO) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int disable;
+module_param(disable, int, 0444);
+MODULE_PARM_DESC(disable,
+ "Disable the watchdog entirely (default=0)");
+
+static struct cvmx_boot_vector_element *octeon_wdt_bootvector;
+
+void octeon_wdt_nmi_stage2(void);
+
+static int cpu2core(int cpu)
+{
+#ifdef CONFIG_SMP
+ return cpu_logical_map(cpu) & 0x3f;
+#else
+ return cvmx_get_core_num();
+#endif
+}
+
+/**
+ * octeon_wdt_poke_irq - Poke the watchdog when an interrupt is received
+ *
+ * @cpl:
+ * @dev_id:
+ *
+ * Returns
+ */
+static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id)
+{
+ int cpu = raw_smp_processor_id();
+ unsigned int core = cpu2core(cpu);
+ int node = cpu_to_node(cpu);
+
+ if (do_countdown) {
+ if (per_cpu_countdown[cpu] > 0) {
+ /* We're alive, poke the watchdog */
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1);
+ per_cpu_countdown[cpu]--;
+ } else {
+ /* Bad news, you are about to reboot. */
+ disable_irq_nosync(cpl);
+ cpumask_clear_cpu(cpu, &irq_enabled_cpus);
+ }
+ } else {
+ /* Not open, just ping away... */
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1);
+ }
+ return IRQ_HANDLED;
+}
+
+/* From setup.c */
+extern int prom_putchar(char c);
+
+/**
+ * octeon_wdt_write_string - Write a string to the uart
+ *
+ * @str: String to write
+ */
+static void octeon_wdt_write_string(const char *str)
+{
+ /* Just loop writing one byte at a time */
+ while (*str)
+ prom_putchar(*str++);
+}
+
+/**
+ * octeon_wdt_write_hex() - Write a hex number out of the uart
+ *
+ * @value: Number to display
+ * @digits: Number of digits to print (1 to 16)
+ */
+static void octeon_wdt_write_hex(u64 value, int digits)
+{
+ int d;
+ int v;
+
+ for (d = 0; d < digits; d++) {
+ v = (value >> ((digits - d - 1) * 4)) & 0xf;
+ if (v >= 10)
+ prom_putchar('a' + v - 10);
+ else
+ prom_putchar('0' + v);
+ }
+}
+
+static const char reg_name[][3] = {
+ "$0", "at", "v0", "v1", "a0", "a1", "a2", "a3",
+ "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3",
+ "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7",
+ "t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra"
+};
+
+/**
+ * octeon_wdt_nmi_stage3:
+ *
+ * NMI stage 3 handler. NMIs are handled in the following manner:
+ * 1) The first NMI handler enables CVMSEG and transfers from
+ * the bootbus region into normal memory. It is careful to not
+ * destroy any registers.
+ * 2) The second stage handler uses CVMSEG to save the registers
+ * and create a stack for C code. It then calls the third level
+ * handler with one argument, a pointer to the register values.
+ * 3) The third, and final, level handler is the following C
+ * function that prints out some useful infomration.
+ *
+ * @reg: Pointer to register state before the NMI
+ */
+void octeon_wdt_nmi_stage3(u64 reg[32])
+{
+ u64 i;
+
+ unsigned int coreid = cvmx_get_core_num();
+ /*
+ * Save status and cause early to get them before any changes
+ * might happen.
+ */
+ u64 cp0_cause = read_c0_cause();
+ u64 cp0_status = read_c0_status();
+ u64 cp0_error_epc = read_c0_errorepc();
+ u64 cp0_epc = read_c0_epc();
+
+ /* Delay so output from all cores output is not jumbled together. */
+ udelay(85000 * coreid);
+
+ octeon_wdt_write_string("\r\n*** NMI Watchdog interrupt on Core 0x");
+ octeon_wdt_write_hex(coreid, 2);
+ octeon_wdt_write_string(" ***\r\n");
+ for (i = 0; i < 32; i++) {
+ octeon_wdt_write_string("\t");
+ octeon_wdt_write_string(reg_name[i]);
+ octeon_wdt_write_string("\t0x");
+ octeon_wdt_write_hex(reg[i], 16);
+ if (i & 1)
+ octeon_wdt_write_string("\r\n");
+ }
+ octeon_wdt_write_string("\terr_epc\t0x");
+ octeon_wdt_write_hex(cp0_error_epc, 16);
+
+ octeon_wdt_write_string("\tepc\t0x");
+ octeon_wdt_write_hex(cp0_epc, 16);
+ octeon_wdt_write_string("\r\n");
+
+ octeon_wdt_write_string("\tstatus\t0x");
+ octeon_wdt_write_hex(cp0_status, 16);
+ octeon_wdt_write_string("\tcause\t0x");
+ octeon_wdt_write_hex(cp0_cause, 16);
+ octeon_wdt_write_string("\r\n");
+
+ /* The CIU register is different for each Octeon model. */
+ if (OCTEON_IS_MODEL(OCTEON_CN68XX)) {
+ octeon_wdt_write_string("\tsrc_wd\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SRC_PPX_IP2_WDOG(coreid)), 16);
+ octeon_wdt_write_string("\ten_wd\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_EN_PPX_IP2_WDOG(coreid)), 16);
+ octeon_wdt_write_string("\r\n");
+ octeon_wdt_write_string("\tsrc_rml\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SRC_PPX_IP2_RML(coreid)), 16);
+ octeon_wdt_write_string("\ten_rml\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_EN_PPX_IP2_RML(coreid)), 16);
+ octeon_wdt_write_string("\r\n");
+ octeon_wdt_write_string("\tsum\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SUM_PPX_IP2(coreid)), 16);
+ octeon_wdt_write_string("\r\n");
+ } else if (!octeon_has_feature(OCTEON_FEATURE_CIU3)) {
+ octeon_wdt_write_string("\tsum0\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_SUM0(coreid * 2)), 16);
+ octeon_wdt_write_string("\ten0\t0x");
+ octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_EN0(coreid * 2)), 16);
+ octeon_wdt_write_string("\r\n");
+ }
+
+ octeon_wdt_write_string("*** Chip soft reset soon ***\r\n");
+
+ /*
+ * G-30204: We must trigger a soft reset before watchdog
+ * does an incomplete job of doing it.
+ */
+ if (OCTEON_IS_OCTEON3() && !OCTEON_IS_MODEL(OCTEON_CN70XX)) {
+ u64 scr;
+ unsigned int node = cvmx_get_node_num();
+ unsigned int lcore = cvmx_get_local_core_num();
+ union cvmx_ciu_wdogx ciu_wdog;
+
+ /*
+ * Wait for other cores to print out information, but
+ * not too long. Do the soft reset before watchdog
+ * can trigger it.
+ */
+ do {
+ ciu_wdog.u64 = cvmx_read_csr_node(node, CVMX_CIU_WDOGX(lcore));
+ } while (ciu_wdog.s.cnt > 0x10000);
+
+ scr = cvmx_read_csr_node(0, CVMX_GSERX_SCRATCH(0));
+ scr |= 1 << 11; /* Indicate watchdog in bit 11 */
+ cvmx_write_csr_node(0, CVMX_GSERX_SCRATCH(0), scr);
+ cvmx_write_csr_node(0, CVMX_RST_SOFT_RST, 1);
+ }
+}
+
+static int octeon_wdt_cpu_to_irq(int cpu)
+{
+ unsigned int coreid;
+ int node;
+ int irq;
+
+ coreid = cpu2core(cpu);
+ node = cpu_to_node(cpu);
+
+ if (octeon_has_feature(OCTEON_FEATURE_CIU3)) {
+ struct irq_domain *domain;
+ int hwirq;
+
+ domain = octeon_irq_get_block_domain(node,
+ WD_BLOCK_NUMBER);
+ hwirq = WD_BLOCK_NUMBER << 12 | 0x200 | coreid;
+ irq = irq_find_mapping(domain, hwirq);
+ } else {
+ irq = OCTEON_IRQ_WDOG0 + coreid;
+ }
+ return irq;
+}
+
+static int octeon_wdt_cpu_pre_down(unsigned int cpu)
+{
+ unsigned int core;
+ int node;
+ union cvmx_ciu_wdogx ciu_wdog;
+
+ core = cpu2core(cpu);
+
+ node = cpu_to_node(cpu);
+
+ /* Poke the watchdog to clear out its state */
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1);
+
+ /* Disable the hardware. */
+ ciu_wdog.u64 = 0;
+ cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64);
+
+ free_irq(octeon_wdt_cpu_to_irq(cpu), octeon_wdt_poke_irq);
+ return 0;
+}
+
+static int octeon_wdt_cpu_online(unsigned int cpu)
+{
+ unsigned int core;
+ unsigned int irq;
+ union cvmx_ciu_wdogx ciu_wdog;
+ int node;
+ struct irq_domain *domain;
+ int hwirq;
+
+ core = cpu2core(cpu);
+ node = cpu_to_node(cpu);
+
+ octeon_wdt_bootvector[core].target_ptr = (u64)octeon_wdt_nmi_stage2;
+
+ /* Disable it before doing anything with the interrupts. */
+ ciu_wdog.u64 = 0;
+ cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64);
+
+ per_cpu_countdown[cpu] = countdown_reset;
+
+ if (octeon_has_feature(OCTEON_FEATURE_CIU3)) {
+ /* Must get the domain for the watchdog block */
+ domain = octeon_irq_get_block_domain(node, WD_BLOCK_NUMBER);
+
+ /* Get a irq for the wd intsn (hardware interrupt) */
+ hwirq = WD_BLOCK_NUMBER << 12 | 0x200 | core;
+ irq = irq_create_mapping(domain, hwirq);
+ irqd_set_trigger_type(irq_get_irq_data(irq),
+ IRQ_TYPE_EDGE_RISING);
+ } else
+ irq = OCTEON_IRQ_WDOG0 + core;
+
+ if (request_irq(irq, octeon_wdt_poke_irq,
+ IRQF_NO_THREAD, "octeon_wdt", octeon_wdt_poke_irq))
+ panic("octeon_wdt: Couldn't obtain irq %d", irq);
+
+ /* Must set the irq affinity here */
+ if (octeon_has_feature(OCTEON_FEATURE_CIU3)) {
+ cpumask_t mask;
+
+ cpumask_clear(&mask);
+ cpumask_set_cpu(cpu, &mask);
+ irq_set_affinity(irq, &mask);
+ }
+
+ cpumask_set_cpu(cpu, &irq_enabled_cpus);
+
+ /* Poke the watchdog to clear out its state */
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1);
+
+ /* Finally enable the watchdog now that all handlers are installed */
+ ciu_wdog.u64 = 0;
+ ciu_wdog.s.len = timeout_cnt;
+ ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */
+ cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64);
+
+ return 0;
+}
+
+static int octeon_wdt_ping(struct watchdog_device __always_unused *wdog)
+{
+ int cpu;
+ int coreid;
+ int node;
+
+ if (disable)
+ return 0;
+
+ for_each_online_cpu(cpu) {
+ coreid = cpu2core(cpu);
+ node = cpu_to_node(cpu);
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1);
+ per_cpu_countdown[cpu] = countdown_reset;
+ if ((countdown_reset || !do_countdown) &&
+ !cpumask_test_cpu(cpu, &irq_enabled_cpus)) {
+ /* We have to enable the irq */
+ enable_irq(octeon_wdt_cpu_to_irq(cpu));
+ cpumask_set_cpu(cpu, &irq_enabled_cpus);
+ }
+ }
+ return 0;
+}
+
+static void octeon_wdt_calc_parameters(int t)
+{
+ unsigned int periods;
+
+ timeout_sec = max_timeout_sec;
+
+
+ /*
+ * Find the largest interrupt period, that can evenly divide
+ * the requested heartbeat time.
+ */
+ while ((t % timeout_sec) != 0)
+ timeout_sec--;
+
+ periods = t / timeout_sec;
+
+ /*
+ * The last two periods are after the irq is disabled, and
+ * then to the nmi, so we subtract them off.
+ */
+
+ countdown_reset = periods > 2 ? periods - 2 : 0;
+ heartbeat = t;
+ timeout_cnt = ((octeon_get_io_clock_rate() / divisor) * timeout_sec) >> 8;
+}
+
+static int octeon_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int t)
+{
+ int cpu;
+ int coreid;
+ union cvmx_ciu_wdogx ciu_wdog;
+ int node;
+
+ if (t <= 0)
+ return -1;
+
+ octeon_wdt_calc_parameters(t);
+
+ if (disable)
+ return 0;
+
+ for_each_online_cpu(cpu) {
+ coreid = cpu2core(cpu);
+ node = cpu_to_node(cpu);
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1);
+ ciu_wdog.u64 = 0;
+ ciu_wdog.s.len = timeout_cnt;
+ ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */
+ cvmx_write_csr_node(node, CVMX_CIU_WDOGX(coreid), ciu_wdog.u64);
+ cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1);
+ }
+ octeon_wdt_ping(wdog); /* Get the irqs back on. */
+ return 0;
+}
+
+static int octeon_wdt_start(struct watchdog_device *wdog)
+{
+ octeon_wdt_ping(wdog);
+ do_countdown = 1;
+ return 0;
+}
+
+static int octeon_wdt_stop(struct watchdog_device *wdog)
+{
+ do_countdown = 0;
+ octeon_wdt_ping(wdog);
+ return 0;
+}
+
+static const struct watchdog_info octeon_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "OCTEON",
+};
+
+static const struct watchdog_ops octeon_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = octeon_wdt_start,
+ .stop = octeon_wdt_stop,
+ .ping = octeon_wdt_ping,
+ .set_timeout = octeon_wdt_set_timeout,
+};
+
+static struct watchdog_device octeon_wdt = {
+ .info = &octeon_wdt_info,
+ .ops = &octeon_wdt_ops,
+};
+
+static enum cpuhp_state octeon_wdt_online;
+/**
+ * octeon_wdt_init - Module/ driver initialization.
+ *
+ * Returns Zero on success
+ */
+static int __init octeon_wdt_init(void)
+{
+ int ret;
+
+ octeon_wdt_bootvector = cvmx_boot_vector_get();
+ if (!octeon_wdt_bootvector) {
+ pr_err("Error: Cannot allocate boot vector.\n");
+ return -ENOMEM;
+ }
+
+ if (OCTEON_IS_MODEL(OCTEON_CN68XX))
+ divisor = 0x200;
+ else if (OCTEON_IS_MODEL(OCTEON_CN78XX))
+ divisor = 0x400;
+ else
+ divisor = 0x100;
+
+ /*
+ * Watchdog time expiration length = The 16 bits of LEN
+ * represent the most significant bits of a 24 bit decrementer
+ * that decrements every divisor cycle.
+ *
+ * Try for a timeout of 5 sec, if that fails a smaller number
+ * of even seconds,
+ */
+ max_timeout_sec = 6;
+ do {
+ max_timeout_sec--;
+ timeout_cnt = ((octeon_get_io_clock_rate() / divisor) * max_timeout_sec) >> 8;
+ } while (timeout_cnt > 65535);
+
+ BUG_ON(timeout_cnt == 0);
+
+ octeon_wdt_calc_parameters(heartbeat);
+
+ pr_info("Initial granularity %d Sec\n", timeout_sec);
+
+ octeon_wdt.timeout = timeout_sec;
+ octeon_wdt.max_timeout = UINT_MAX;
+
+ watchdog_set_nowayout(&octeon_wdt, nowayout);
+
+ ret = watchdog_register_device(&octeon_wdt);
+ if (ret) {
+ pr_err("watchdog_register_device() failed: %d\n", ret);
+ return ret;
+ }
+
+ if (disable) {
+ pr_notice("disabled\n");
+ return 0;
+ }
+
+ cpumask_clear(&irq_enabled_cpus);
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "watchdog/octeon:online",
+ octeon_wdt_cpu_online, octeon_wdt_cpu_pre_down);
+ if (ret < 0)
+ goto err;
+ octeon_wdt_online = ret;
+ return 0;
+err:
+ cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0);
+ watchdog_unregister_device(&octeon_wdt);
+ return ret;
+}
+
+/**
+ * octeon_wdt_cleanup - Module / driver shutdown
+ */
+static void __exit octeon_wdt_cleanup(void)
+{
+ watchdog_unregister_device(&octeon_wdt);
+
+ if (disable)
+ return;
+
+ cpuhp_remove_state(octeon_wdt_online);
+
+ /*
+ * Disable the boot-bus memory, the code it points to is soon
+ * to go missing.
+ */
+ cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Cavium Inc. <support@cavium.com>");
+MODULE_DESCRIPTION("Cavium Inc. OCTEON Watchdog driver.");
+module_init(octeon_wdt_init);
+module_exit(octeon_wdt_cleanup);
diff --git a/drivers/watchdog/octeon-wdt-nmi.S b/drivers/watchdog/octeon-wdt-nmi.S
new file mode 100644
index 000000000..97f6eb7b5
--- /dev/null
+++ b/drivers/watchdog/octeon-wdt-nmi.S
@@ -0,0 +1,90 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2007-2017 Cavium, Inc.
+ */
+#include <asm/asm.h>
+#include <asm/regdef.h>
+
+#define CVMSEG_BASE -32768
+#define CVMSEG_SIZE 6912
+#define SAVE_REG(r) sd $r, CVMSEG_BASE + CVMSEG_SIZE - ((32 - r) * 8)($0)
+
+ NESTED(octeon_wdt_nmi_stage2, 0, sp)
+ .set push
+ .set noreorder
+ .set noat
+ /* Clear Dcache so cvmseg works right. */
+ cache 1,0($0)
+ /* Use K0 to do a read/modify/write of CVMMEMCTL */
+ dmfc0 k0, $11, 7
+ /* Clear out the size of CVMSEG */
+ dins k0, $0, 0, 6
+ /* Set CVMSEG to its largest value */
+ ori k0, k0, 0x1c0 | 54
+ /* Store the CVMMEMCTL value */
+ dmtc0 k0, $11, 7
+ /*
+ * Restore K0 from the debug scratch register, it was saved in
+ * the boot-vector code.
+ */
+ dmfc0 k0, $31
+
+ /*
+ * Save all registers to the top CVMSEG. This shouldn't
+ * corrupt any state used by the kernel. Also all registers
+ * should have the value right before the NMI.
+ */
+ SAVE_REG(0)
+ SAVE_REG(1)
+ SAVE_REG(2)
+ SAVE_REG(3)
+ SAVE_REG(4)
+ SAVE_REG(5)
+ SAVE_REG(6)
+ SAVE_REG(7)
+ SAVE_REG(8)
+ SAVE_REG(9)
+ SAVE_REG(10)
+ SAVE_REG(11)
+ SAVE_REG(12)
+ SAVE_REG(13)
+ SAVE_REG(14)
+ SAVE_REG(15)
+ SAVE_REG(16)
+ SAVE_REG(17)
+ SAVE_REG(18)
+ SAVE_REG(19)
+ SAVE_REG(20)
+ SAVE_REG(21)
+ SAVE_REG(22)
+ SAVE_REG(23)
+ SAVE_REG(24)
+ SAVE_REG(25)
+ SAVE_REG(26)
+ SAVE_REG(27)
+ SAVE_REG(28)
+ SAVE_REG(29)
+ SAVE_REG(30)
+ SAVE_REG(31)
+ /* Write zero to all CVMSEG locations per Core-15169 */
+ dli a0, CVMSEG_SIZE - (33 * 8)
+1: sd zero, CVMSEG_BASE(a0)
+ daddiu a0, a0, -8
+ bgez a0, 1b
+ nop
+ /* Set the stack to begin right below the registers */
+ dli sp, CVMSEG_BASE + CVMSEG_SIZE - (32 * 8)
+ /* Load the address of the third stage handler */
+ dla $25, octeon_wdt_nmi_stage3
+ /* Call the third stage handler */
+ jal $25
+ /* a0 is the address of the saved registers */
+ move a0, sp
+ /* Loop forvever if we get here. */
+2: b 2b
+ nop
+ .set pop
+ END(octeon_wdt_nmi_stage2)
diff --git a/drivers/watchdog/of_xilinx_wdt.c b/drivers/watchdog/of_xilinx_wdt.c
new file mode 100644
index 000000000..331854436
--- /dev/null
+++ b/drivers/watchdog/of_xilinx_wdt.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog Device Driver for Xilinx axi/xps_timebase_wdt
+ *
+ * (C) Copyright 2013 - 2014 Xilinx, Inc.
+ * (C) Copyright 2011 (Alejandro Cabrera <aldaya@gmail.com>)
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+
+/* Register offsets for the Wdt device */
+#define XWT_TWCSR0_OFFSET 0x0 /* Control/Status Register0 */
+#define XWT_TWCSR1_OFFSET 0x4 /* Control/Status Register1 */
+#define XWT_TBR_OFFSET 0x8 /* Timebase Register Offset */
+
+/* Control/Status Register Masks */
+#define XWT_CSR0_WRS_MASK BIT(3) /* Reset status */
+#define XWT_CSR0_WDS_MASK BIT(2) /* Timer state */
+#define XWT_CSR0_EWDT1_MASK BIT(1) /* Enable bit 1 */
+
+/* Control/Status Register 0/1 bits */
+#define XWT_CSRX_EWDT2_MASK BIT(0) /* Enable bit 2 */
+
+/* SelfTest constants */
+#define XWT_MAX_SELFTEST_LOOP_COUNT 0x00010000
+#define XWT_TIMER_FAILED 0xFFFFFFFF
+
+#define WATCHDOG_NAME "Xilinx Watchdog"
+
+struct xwdt_device {
+ void __iomem *base;
+ u32 wdt_interval;
+ spinlock_t spinlock; /* spinlock for register handling */
+ struct watchdog_device xilinx_wdt_wdd;
+ struct clk *clk;
+};
+
+static int xilinx_wdt_start(struct watchdog_device *wdd)
+{
+ int ret;
+ u32 control_status_reg;
+ struct xwdt_device *xdev = watchdog_get_drvdata(wdd);
+
+ ret = clk_enable(xdev->clk);
+ if (ret) {
+ dev_err(wdd->parent, "Failed to enable clock\n");
+ return ret;
+ }
+
+ spin_lock(&xdev->spinlock);
+
+ /* Clean previous status and enable the watchdog timer */
+ control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET);
+ control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK);
+
+ iowrite32((control_status_reg | XWT_CSR0_EWDT1_MASK),
+ xdev->base + XWT_TWCSR0_OFFSET);
+
+ iowrite32(XWT_CSRX_EWDT2_MASK, xdev->base + XWT_TWCSR1_OFFSET);
+
+ spin_unlock(&xdev->spinlock);
+
+ dev_dbg(wdd->parent, "Watchdog Started!\n");
+
+ return 0;
+}
+
+static int xilinx_wdt_stop(struct watchdog_device *wdd)
+{
+ u32 control_status_reg;
+ struct xwdt_device *xdev = watchdog_get_drvdata(wdd);
+
+ spin_lock(&xdev->spinlock);
+
+ control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET);
+
+ iowrite32((control_status_reg & ~XWT_CSR0_EWDT1_MASK),
+ xdev->base + XWT_TWCSR0_OFFSET);
+
+ iowrite32(0, xdev->base + XWT_TWCSR1_OFFSET);
+
+ spin_unlock(&xdev->spinlock);
+
+ clk_disable(xdev->clk);
+
+ dev_dbg(wdd->parent, "Watchdog Stopped!\n");
+
+ return 0;
+}
+
+static int xilinx_wdt_keepalive(struct watchdog_device *wdd)
+{
+ u32 control_status_reg;
+ struct xwdt_device *xdev = watchdog_get_drvdata(wdd);
+
+ spin_lock(&xdev->spinlock);
+
+ control_status_reg = ioread32(xdev->base + XWT_TWCSR0_OFFSET);
+ control_status_reg |= (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK);
+ iowrite32(control_status_reg, xdev->base + XWT_TWCSR0_OFFSET);
+
+ spin_unlock(&xdev->spinlock);
+
+ return 0;
+}
+
+static const struct watchdog_info xilinx_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+};
+
+static const struct watchdog_ops xilinx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = xilinx_wdt_start,
+ .stop = xilinx_wdt_stop,
+ .ping = xilinx_wdt_keepalive,
+};
+
+static u32 xwdt_selftest(struct xwdt_device *xdev)
+{
+ int i;
+ u32 timer_value1;
+ u32 timer_value2;
+
+ spin_lock(&xdev->spinlock);
+
+ timer_value1 = ioread32(xdev->base + XWT_TBR_OFFSET);
+ timer_value2 = ioread32(xdev->base + XWT_TBR_OFFSET);
+
+ for (i = 0;
+ ((i <= XWT_MAX_SELFTEST_LOOP_COUNT) &&
+ (timer_value2 == timer_value1)); i++) {
+ timer_value2 = ioread32(xdev->base + XWT_TBR_OFFSET);
+ }
+
+ spin_unlock(&xdev->spinlock);
+
+ if (timer_value2 != timer_value1)
+ return ~XWT_TIMER_FAILED;
+ else
+ return XWT_TIMER_FAILED;
+}
+
+static void xwdt_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int xwdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int rc;
+ u32 pfreq = 0, enable_once = 0;
+ struct xwdt_device *xdev;
+ struct watchdog_device *xilinx_wdt_wdd;
+
+ xdev = devm_kzalloc(dev, sizeof(*xdev), GFP_KERNEL);
+ if (!xdev)
+ return -ENOMEM;
+
+ xilinx_wdt_wdd = &xdev->xilinx_wdt_wdd;
+ xilinx_wdt_wdd->info = &xilinx_wdt_ident;
+ xilinx_wdt_wdd->ops = &xilinx_wdt_ops;
+ xilinx_wdt_wdd->parent = dev;
+
+ xdev->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(xdev->base))
+ return PTR_ERR(xdev->base);
+
+ rc = of_property_read_u32(dev->of_node, "xlnx,wdt-interval",
+ &xdev->wdt_interval);
+ if (rc)
+ dev_warn(dev, "Parameter \"xlnx,wdt-interval\" not found\n");
+
+ rc = of_property_read_u32(dev->of_node, "xlnx,wdt-enable-once",
+ &enable_once);
+ if (rc)
+ dev_warn(dev,
+ "Parameter \"xlnx,wdt-enable-once\" not found\n");
+
+ watchdog_set_nowayout(xilinx_wdt_wdd, enable_once);
+
+ xdev->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(xdev->clk)) {
+ if (PTR_ERR(xdev->clk) != -ENOENT)
+ return PTR_ERR(xdev->clk);
+
+ /*
+ * Clock framework support is optional, continue on
+ * anyways if we don't find a matching clock.
+ */
+ xdev->clk = NULL;
+
+ rc = of_property_read_u32(dev->of_node, "clock-frequency",
+ &pfreq);
+ if (rc)
+ dev_warn(dev,
+ "The watchdog clock freq cannot be obtained\n");
+ } else {
+ pfreq = clk_get_rate(xdev->clk);
+ rc = clk_prepare_enable(xdev->clk);
+ if (rc) {
+ dev_err(dev, "unable to enable clock\n");
+ return rc;
+ }
+ rc = devm_add_action_or_reset(dev, xwdt_clk_disable_unprepare,
+ xdev->clk);
+ if (rc)
+ return rc;
+ }
+
+ /*
+ * Twice of the 2^wdt_interval / freq because the first wdt overflow is
+ * ignored (interrupt), reset is only generated at second wdt overflow
+ */
+ if (pfreq && xdev->wdt_interval)
+ xilinx_wdt_wdd->timeout = 2 * ((1 << xdev->wdt_interval) /
+ pfreq);
+
+ spin_lock_init(&xdev->spinlock);
+ watchdog_set_drvdata(xilinx_wdt_wdd, xdev);
+
+ rc = xwdt_selftest(xdev);
+ if (rc == XWT_TIMER_FAILED) {
+ dev_err(dev, "SelfTest routine error\n");
+ return rc;
+ }
+
+ rc = devm_watchdog_register_device(dev, xilinx_wdt_wdd);
+ if (rc)
+ return rc;
+
+ clk_disable(xdev->clk);
+
+ dev_info(dev, "Xilinx Watchdog Timer with timeout %ds\n",
+ xilinx_wdt_wdd->timeout);
+
+ platform_set_drvdata(pdev, xdev);
+
+ return 0;
+}
+
+/**
+ * xwdt_suspend - Suspend the device.
+ *
+ * @dev: handle to the device structure.
+ * Return: 0 always.
+ */
+static int __maybe_unused xwdt_suspend(struct device *dev)
+{
+ struct xwdt_device *xdev = dev_get_drvdata(dev);
+
+ if (watchdog_active(&xdev->xilinx_wdt_wdd))
+ xilinx_wdt_stop(&xdev->xilinx_wdt_wdd);
+
+ return 0;
+}
+
+/**
+ * xwdt_resume - Resume the device.
+ *
+ * @dev: handle to the device structure.
+ * Return: 0 on success, errno otherwise.
+ */
+static int __maybe_unused xwdt_resume(struct device *dev)
+{
+ struct xwdt_device *xdev = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (watchdog_active(&xdev->xilinx_wdt_wdd))
+ ret = xilinx_wdt_start(&xdev->xilinx_wdt_wdd);
+
+ return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(xwdt_pm_ops, xwdt_suspend, xwdt_resume);
+
+/* Match table for of_platform binding */
+static const struct of_device_id xwdt_of_match[] = {
+ { .compatible = "xlnx,xps-timebase-wdt-1.00.a", },
+ { .compatible = "xlnx,xps-timebase-wdt-1.01.a", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, xwdt_of_match);
+
+static struct platform_driver xwdt_driver = {
+ .probe = xwdt_probe,
+ .driver = {
+ .name = WATCHDOG_NAME,
+ .of_match_table = xwdt_of_match,
+ .pm = &xwdt_pm_ops,
+ },
+};
+
+module_platform_driver(xwdt_driver);
+
+MODULE_AUTHOR("Alejandro Cabrera <aldaya@gmail.com>");
+MODULE_DESCRIPTION("Xilinx Watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c
new file mode 100644
index 000000000..74d785b2b
--- /dev/null
+++ b/drivers/watchdog/omap_wdt.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * omap_wdt.c
+ *
+ * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog
+ *
+ * Author: MontaVista Software, Inc.
+ * <gdavis@mvista.com> or <source@mvista.com>
+ *
+ * 2003 (c) MontaVista Software, Inc.
+ *
+ * History:
+ *
+ * 20030527: George G. Davis <gdavis@mvista.com>
+ * Initially based on linux-2.4.19-rmk7-pxa1/drivers/char/sa1100_wdt.c
+ * (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Copyright (c) 2004 Texas Instruments.
+ * 1. Modified to support OMAP1610 32-KHz watchdog timer
+ * 2. Ported to 2.6 kernel
+ *
+ * Copyright (c) 2005 David Brownell
+ * Use the driver model and standard identifiers; handle bigger timeouts.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/platform_data/omap-wd-timer.h>
+
+#include "omap_wdt.h"
+
+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) ")");
+
+static unsigned timer_margin;
+module_param(timer_margin, uint, 0);
+MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
+
+#define to_omap_wdt_dev(_wdog) container_of(_wdog, struct omap_wdt_dev, wdog)
+
+static bool early_enable;
+module_param(early_enable, bool, 0);
+MODULE_PARM_DESC(early_enable,
+ "Watchdog is started on module insertion (default=0)");
+
+struct omap_wdt_dev {
+ struct watchdog_device wdog;
+ void __iomem *base; /* physical */
+ struct device *dev;
+ bool omap_wdt_users;
+ int wdt_trgr_pattern;
+ struct mutex lock; /* to avoid races with PM */
+};
+
+static void omap_wdt_reload(struct omap_wdt_dev *wdev)
+{
+ void __iomem *base = wdev->base;
+
+ /* wait for posted write to complete */
+ while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x08)
+ cpu_relax();
+
+ wdev->wdt_trgr_pattern = ~wdev->wdt_trgr_pattern;
+ writel_relaxed(wdev->wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR));
+
+ /* wait for posted write to complete */
+ while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x08)
+ cpu_relax();
+ /* reloaded WCRR from WLDR */
+}
+
+static void omap_wdt_enable(struct omap_wdt_dev *wdev)
+{
+ void __iomem *base = wdev->base;
+
+ /* Sequence to enable the watchdog */
+ writel_relaxed(0xBBBB, base + OMAP_WATCHDOG_SPR);
+ while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x10)
+ cpu_relax();
+
+ writel_relaxed(0x4444, base + OMAP_WATCHDOG_SPR);
+ while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x10)
+ cpu_relax();
+}
+
+static void omap_wdt_disable(struct omap_wdt_dev *wdev)
+{
+ void __iomem *base = wdev->base;
+
+ /* sequence required to disable watchdog */
+ writel_relaxed(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x10)
+ cpu_relax();
+
+ writel_relaxed(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x10)
+ cpu_relax();
+}
+
+static void omap_wdt_set_timer(struct omap_wdt_dev *wdev,
+ unsigned int timeout)
+{
+ u32 pre_margin = GET_WLDR_VAL(timeout);
+ void __iomem *base = wdev->base;
+
+ /* just count up at 32 KHz */
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x04)
+ cpu_relax();
+
+ writel_relaxed(pre_margin, base + OMAP_WATCHDOG_LDR);
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x04)
+ cpu_relax();
+}
+
+static int omap_wdt_start(struct watchdog_device *wdog)
+{
+ struct omap_wdt_dev *wdev = to_omap_wdt_dev(wdog);
+ void __iomem *base = wdev->base;
+
+ mutex_lock(&wdev->lock);
+
+ wdev->omap_wdt_users = true;
+
+ pm_runtime_get_sync(wdev->dev);
+
+ /*
+ * Make sure the watchdog is disabled. This is unfortunately required
+ * because writing to various registers with the watchdog running has no
+ * effect.
+ */
+ omap_wdt_disable(wdev);
+
+ /* initialize prescaler */
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x01)
+ cpu_relax();
+
+ writel_relaxed((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL);
+ while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x01)
+ cpu_relax();
+
+ omap_wdt_set_timer(wdev, wdog->timeout);
+ omap_wdt_reload(wdev); /* trigger loading of new timeout value */
+ omap_wdt_enable(wdev);
+
+ mutex_unlock(&wdev->lock);
+
+ return 0;
+}
+
+static int omap_wdt_stop(struct watchdog_device *wdog)
+{
+ struct omap_wdt_dev *wdev = to_omap_wdt_dev(wdog);
+
+ mutex_lock(&wdev->lock);
+ omap_wdt_disable(wdev);
+ pm_runtime_put_sync(wdev->dev);
+ wdev->omap_wdt_users = false;
+ mutex_unlock(&wdev->lock);
+ return 0;
+}
+
+static int omap_wdt_ping(struct watchdog_device *wdog)
+{
+ struct omap_wdt_dev *wdev = to_omap_wdt_dev(wdog);
+
+ mutex_lock(&wdev->lock);
+ omap_wdt_reload(wdev);
+ mutex_unlock(&wdev->lock);
+
+ return 0;
+}
+
+static int omap_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int timeout)
+{
+ struct omap_wdt_dev *wdev = to_omap_wdt_dev(wdog);
+
+ mutex_lock(&wdev->lock);
+ omap_wdt_disable(wdev);
+ omap_wdt_set_timer(wdev, timeout);
+ omap_wdt_enable(wdev);
+ omap_wdt_reload(wdev);
+ wdog->timeout = timeout;
+ mutex_unlock(&wdev->lock);
+
+ return 0;
+}
+
+static unsigned int omap_wdt_get_timeleft(struct watchdog_device *wdog)
+{
+ struct omap_wdt_dev *wdev = to_omap_wdt_dev(wdog);
+ void __iomem *base = wdev->base;
+ u32 value;
+
+ value = readl_relaxed(base + OMAP_WATCHDOG_CRR);
+ return GET_WCCR_SECS(value);
+}
+
+static const struct watchdog_info omap_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "OMAP Watchdog",
+};
+
+static const struct watchdog_ops omap_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = omap_wdt_start,
+ .stop = omap_wdt_stop,
+ .ping = omap_wdt_ping,
+ .set_timeout = omap_wdt_set_timeout,
+ .get_timeleft = omap_wdt_get_timeleft,
+};
+
+static int omap_wdt_probe(struct platform_device *pdev)
+{
+ struct omap_wd_timer_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct omap_wdt_dev *wdev;
+ int ret;
+
+ wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
+ if (!wdev)
+ return -ENOMEM;
+
+ wdev->omap_wdt_users = false;
+ wdev->dev = &pdev->dev;
+ wdev->wdt_trgr_pattern = 0x1234;
+ mutex_init(&wdev->lock);
+
+ /* reserve static register mappings */
+ wdev->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdev->base))
+ return PTR_ERR(wdev->base);
+
+ wdev->wdog.info = &omap_wdt_info;
+ wdev->wdog.ops = &omap_wdt_ops;
+ wdev->wdog.min_timeout = TIMER_MARGIN_MIN;
+ wdev->wdog.max_timeout = TIMER_MARGIN_MAX;
+ wdev->wdog.timeout = TIMER_MARGIN_DEFAULT;
+ wdev->wdog.parent = &pdev->dev;
+
+ watchdog_init_timeout(&wdev->wdog, timer_margin, &pdev->dev);
+
+ watchdog_set_nowayout(&wdev->wdog, nowayout);
+
+ platform_set_drvdata(pdev, wdev);
+
+ pm_runtime_enable(wdev->dev);
+ pm_runtime_get_sync(wdev->dev);
+
+ if (pdata && pdata->read_reset_sources) {
+ u32 rs = pdata->read_reset_sources();
+ if (rs & (1 << OMAP_MPU_WD_RST_SRC_ID_SHIFT))
+ wdev->wdog.bootstatus = WDIOF_CARDRESET;
+ }
+
+ if (early_enable) {
+ omap_wdt_start(&wdev->wdog);
+ set_bit(WDOG_HW_RUNNING, &wdev->wdog.status);
+ } else {
+ omap_wdt_disable(wdev);
+ }
+
+ ret = watchdog_register_device(&wdev->wdog);
+ if (ret) {
+ pm_runtime_put(wdev->dev);
+ pm_runtime_disable(wdev->dev);
+ return ret;
+ }
+
+ pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n",
+ readl_relaxed(wdev->base + OMAP_WATCHDOG_REV) & 0xFF,
+ wdev->wdog.timeout);
+
+ if (early_enable)
+ omap_wdt_start(&wdev->wdog);
+
+ pm_runtime_put(wdev->dev);
+
+ return 0;
+}
+
+static void omap_wdt_shutdown(struct platform_device *pdev)
+{
+ struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
+
+ mutex_lock(&wdev->lock);
+ if (wdev->omap_wdt_users) {
+ omap_wdt_disable(wdev);
+ pm_runtime_put_sync(wdev->dev);
+ }
+ mutex_unlock(&wdev->lock);
+}
+
+static int omap_wdt_remove(struct platform_device *pdev)
+{
+ struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
+
+ pm_runtime_disable(wdev->dev);
+ watchdog_unregister_device(&wdev->wdog);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+/* REVISIT ... not clear this is the best way to handle system suspend; and
+ * it's very inappropriate for selective device suspend (e.g. suspending this
+ * through sysfs rather than by stopping the watchdog daemon). Also, this
+ * may not play well enough with NOWAYOUT...
+ */
+
+static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
+
+ mutex_lock(&wdev->lock);
+ if (wdev->omap_wdt_users) {
+ omap_wdt_disable(wdev);
+ pm_runtime_put_sync(wdev->dev);
+ }
+ mutex_unlock(&wdev->lock);
+
+ return 0;
+}
+
+static int omap_wdt_resume(struct platform_device *pdev)
+{
+ struct omap_wdt_dev *wdev = platform_get_drvdata(pdev);
+
+ mutex_lock(&wdev->lock);
+ if (wdev->omap_wdt_users) {
+ pm_runtime_get_sync(wdev->dev);
+ omap_wdt_enable(wdev);
+ omap_wdt_reload(wdev);
+ }
+ mutex_unlock(&wdev->lock);
+
+ return 0;
+}
+
+#else
+#define omap_wdt_suspend NULL
+#define omap_wdt_resume NULL
+#endif
+
+static const struct of_device_id omap_wdt_of_match[] = {
+ { .compatible = "ti,omap3-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, omap_wdt_of_match);
+
+static struct platform_driver omap_wdt_driver = {
+ .probe = omap_wdt_probe,
+ .remove = omap_wdt_remove,
+ .shutdown = omap_wdt_shutdown,
+ .suspend = omap_wdt_suspend,
+ .resume = omap_wdt_resume,
+ .driver = {
+ .name = "omap_wdt",
+ .of_match_table = omap_wdt_of_match,
+ },
+};
+
+module_platform_driver(omap_wdt_driver);
+
+MODULE_AUTHOR("George G. Davis");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:omap_wdt");
diff --git a/drivers/watchdog/omap_wdt.h b/drivers/watchdog/omap_wdt.h
new file mode 100644
index 000000000..950b4643f
--- /dev/null
+++ b/drivers/watchdog/omap_wdt.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * linux/drivers/char/watchdog/omap_wdt.h
+ *
+ * BRIEF MODULE DESCRIPTION
+ * OMAP Watchdog timer register definitions
+ *
+ * Copyright (C) 2004 Texas Instruments.
+ */
+
+#ifndef _OMAP_WATCHDOG_H
+#define _OMAP_WATCHDOG_H
+
+#define OMAP_WATCHDOG_REV (0x00)
+#define OMAP_WATCHDOG_SYS_CONFIG (0x10)
+#define OMAP_WATCHDOG_STATUS (0x14)
+#define OMAP_WATCHDOG_CNTRL (0x24)
+#define OMAP_WATCHDOG_CRR (0x28)
+#define OMAP_WATCHDOG_LDR (0x2c)
+#define OMAP_WATCHDOG_TGR (0x30)
+#define OMAP_WATCHDOG_WPS (0x34)
+#define OMAP_WATCHDOG_SPR (0x48)
+
+/* Using the prescaler, the OMAP watchdog could go for many
+ * months before firing. These limits work without scaling,
+ * with the 60 second default assumed by most tools and docs.
+ */
+#define TIMER_MARGIN_MAX (24 * 60 * 60) /* 1 day */
+#define TIMER_MARGIN_DEFAULT 60 /* 60 secs */
+#define TIMER_MARGIN_MIN 1
+
+#define PTV 0 /* prescale */
+#define GET_WLDR_VAL(secs) (0xffffffff - ((secs) * (32768/(1<<PTV))) + 1)
+#define GET_WCCR_SECS(val) ((0xffffffff - (val) + 1) / (32768/(1<<PTV)))
+
+#endif /* _OMAP_WATCHDOG_H */
diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c
new file mode 100644
index 000000000..e25e6bf46
--- /dev/null
+++ b/drivers/watchdog/orion_wdt.c
@@ -0,0 +1,692 @@
+/*
+ * drivers/watchdog/orion_wdt.c
+ *
+ * Watchdog driver for Orion/Kirkwood processors
+ *
+ * Author: Sylver Bruneau <sylver.bruneau@googlemail.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+/* RSTOUT mask register physical address for Orion5x, Kirkwood and Dove */
+#define ORION_RSTOUT_MASK_OFFSET 0x20108
+
+/* Internal registers can be configured at any 1 MiB aligned address */
+#define INTERNAL_REGS_MASK ~(SZ_1M - 1)
+
+/*
+ * Watchdog timer block registers.
+ */
+#define TIMER_CTRL 0x0000
+#define TIMER1_FIXED_ENABLE_BIT BIT(12)
+#define WDT_AXP_FIXED_ENABLE_BIT BIT(10)
+#define TIMER1_ENABLE_BIT BIT(2)
+
+#define TIMER_A370_STATUS 0x0004
+#define WDT_A370_EXPIRED BIT(31)
+#define TIMER1_STATUS_BIT BIT(8)
+
+#define TIMER1_VAL_OFF 0x001c
+
+#define WDT_MAX_CYCLE_COUNT 0xffffffff
+
+#define WDT_A370_RATIO_MASK(v) ((v) << 16)
+#define WDT_A370_RATIO_SHIFT 5
+#define WDT_A370_RATIO (1 << WDT_A370_RATIO_SHIFT)
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static int heartbeat; /* module parameter (seconds) */
+
+struct orion_watchdog;
+
+struct orion_watchdog_data {
+ int wdt_counter_offset;
+ int wdt_enable_bit;
+ int rstout_enable_bit;
+ int rstout_mask_bit;
+ int (*clock_init)(struct platform_device *,
+ struct orion_watchdog *);
+ int (*enabled)(struct orion_watchdog *);
+ int (*start)(struct watchdog_device *);
+ int (*stop)(struct watchdog_device *);
+};
+
+struct orion_watchdog {
+ struct watchdog_device wdt;
+ void __iomem *reg;
+ void __iomem *rstout;
+ void __iomem *rstout_mask;
+ unsigned long clk_rate;
+ struct clk *clk;
+ const struct orion_watchdog_data *data;
+};
+
+static int orion_wdt_clock_init(struct platform_device *pdev,
+ struct orion_watchdog *dev)
+{
+ int ret;
+
+ dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+ ret = clk_prepare_enable(dev->clk);
+ if (ret) {
+ clk_put(dev->clk);
+ return ret;
+ }
+
+ dev->clk_rate = clk_get_rate(dev->clk);
+ return 0;
+}
+
+static int armada370_wdt_clock_init(struct platform_device *pdev,
+ struct orion_watchdog *dev)
+{
+ int ret;
+
+ dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+ ret = clk_prepare_enable(dev->clk);
+ if (ret) {
+ clk_put(dev->clk);
+ return ret;
+ }
+
+ /* Setup watchdog input clock */
+ atomic_io_modify(dev->reg + TIMER_CTRL,
+ WDT_A370_RATIO_MASK(WDT_A370_RATIO_SHIFT),
+ WDT_A370_RATIO_MASK(WDT_A370_RATIO_SHIFT));
+
+ dev->clk_rate = clk_get_rate(dev->clk) / WDT_A370_RATIO;
+ return 0;
+}
+
+static int armada375_wdt_clock_init(struct platform_device *pdev,
+ struct orion_watchdog *dev)
+{
+ int ret;
+
+ dev->clk = of_clk_get_by_name(pdev->dev.of_node, "fixed");
+ if (!IS_ERR(dev->clk)) {
+ ret = clk_prepare_enable(dev->clk);
+ if (ret) {
+ clk_put(dev->clk);
+ return ret;
+ }
+
+ atomic_io_modify(dev->reg + TIMER_CTRL,
+ WDT_AXP_FIXED_ENABLE_BIT,
+ WDT_AXP_FIXED_ENABLE_BIT);
+ dev->clk_rate = clk_get_rate(dev->clk);
+
+ return 0;
+ }
+
+ /* Mandatory fallback for proper devicetree backward compatibility */
+ dev->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+
+ ret = clk_prepare_enable(dev->clk);
+ if (ret) {
+ clk_put(dev->clk);
+ return ret;
+ }
+
+ atomic_io_modify(dev->reg + TIMER_CTRL,
+ WDT_A370_RATIO_MASK(WDT_A370_RATIO_SHIFT),
+ WDT_A370_RATIO_MASK(WDT_A370_RATIO_SHIFT));
+ dev->clk_rate = clk_get_rate(dev->clk) / WDT_A370_RATIO;
+
+ return 0;
+}
+
+static int armadaxp_wdt_clock_init(struct platform_device *pdev,
+ struct orion_watchdog *dev)
+{
+ int ret;
+ u32 val;
+
+ dev->clk = of_clk_get_by_name(pdev->dev.of_node, "fixed");
+ if (IS_ERR(dev->clk))
+ return PTR_ERR(dev->clk);
+ ret = clk_prepare_enable(dev->clk);
+ if (ret) {
+ clk_put(dev->clk);
+ return ret;
+ }
+
+ /* Fix the wdt and timer1 clock frequency to 25MHz */
+ val = WDT_AXP_FIXED_ENABLE_BIT | TIMER1_FIXED_ENABLE_BIT;
+ atomic_io_modify(dev->reg + TIMER_CTRL, val, val);
+
+ dev->clk_rate = clk_get_rate(dev->clk);
+ return 0;
+}
+
+static int orion_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ /* Reload watchdog duration */
+ writel(dev->clk_rate * wdt_dev->timeout,
+ dev->reg + dev->data->wdt_counter_offset);
+ if (dev->wdt.info->options & WDIOF_PRETIMEOUT)
+ writel(dev->clk_rate * (wdt_dev->timeout - wdt_dev->pretimeout),
+ dev->reg + TIMER1_VAL_OFF);
+
+ return 0;
+}
+
+static int armada375_start(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ u32 reg;
+
+ /* Set watchdog duration */
+ writel(dev->clk_rate * wdt_dev->timeout,
+ dev->reg + dev->data->wdt_counter_offset);
+ if (dev->wdt.info->options & WDIOF_PRETIMEOUT)
+ writel(dev->clk_rate * (wdt_dev->timeout - wdt_dev->pretimeout),
+ dev->reg + TIMER1_VAL_OFF);
+
+ /* Clear the watchdog expiration bit */
+ atomic_io_modify(dev->reg + TIMER_A370_STATUS, WDT_A370_EXPIRED, 0);
+
+ /* Enable watchdog timer */
+ reg = dev->data->wdt_enable_bit;
+ if (dev->wdt.info->options & WDIOF_PRETIMEOUT)
+ reg |= TIMER1_ENABLE_BIT;
+ atomic_io_modify(dev->reg + TIMER_CTRL, reg, reg);
+
+ /* Enable reset on watchdog */
+ reg = readl(dev->rstout);
+ reg |= dev->data->rstout_enable_bit;
+ writel(reg, dev->rstout);
+
+ atomic_io_modify(dev->rstout_mask, dev->data->rstout_mask_bit, 0);
+ return 0;
+}
+
+static int armada370_start(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ u32 reg;
+
+ /* Set watchdog duration */
+ writel(dev->clk_rate * wdt_dev->timeout,
+ dev->reg + dev->data->wdt_counter_offset);
+
+ /* Clear the watchdog expiration bit */
+ atomic_io_modify(dev->reg + TIMER_A370_STATUS, WDT_A370_EXPIRED, 0);
+
+ /* Enable watchdog timer */
+ reg = dev->data->wdt_enable_bit;
+ if (dev->wdt.info->options & WDIOF_PRETIMEOUT)
+ reg |= TIMER1_ENABLE_BIT;
+ atomic_io_modify(dev->reg + TIMER_CTRL, reg, reg);
+
+ /* Enable reset on watchdog */
+ reg = readl(dev->rstout);
+ reg |= dev->data->rstout_enable_bit;
+ writel(reg, dev->rstout);
+ return 0;
+}
+
+static int orion_start(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ /* Set watchdog duration */
+ writel(dev->clk_rate * wdt_dev->timeout,
+ dev->reg + dev->data->wdt_counter_offset);
+
+ /* Enable watchdog timer */
+ atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit,
+ dev->data->wdt_enable_bit);
+
+ /* Enable reset on watchdog */
+ atomic_io_modify(dev->rstout, dev->data->rstout_enable_bit,
+ dev->data->rstout_enable_bit);
+
+ return 0;
+}
+
+static int orion_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ /* There are some per-SoC quirks to handle */
+ return dev->data->start(wdt_dev);
+}
+
+static int orion_stop(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ /* Disable reset on watchdog */
+ atomic_io_modify(dev->rstout, dev->data->rstout_enable_bit, 0);
+
+ /* Disable watchdog timer */
+ atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit, 0);
+
+ return 0;
+}
+
+static int armada375_stop(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ u32 reg, mask;
+
+ /* Disable reset on watchdog */
+ atomic_io_modify(dev->rstout_mask, dev->data->rstout_mask_bit,
+ dev->data->rstout_mask_bit);
+ reg = readl(dev->rstout);
+ reg &= ~dev->data->rstout_enable_bit;
+ writel(reg, dev->rstout);
+
+ /* Disable watchdog timer */
+ mask = dev->data->wdt_enable_bit;
+ if (wdt_dev->info->options & WDIOF_PRETIMEOUT)
+ mask |= TIMER1_ENABLE_BIT;
+ atomic_io_modify(dev->reg + TIMER_CTRL, mask, 0);
+
+ return 0;
+}
+
+static int armada370_stop(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ u32 reg, mask;
+
+ /* Disable reset on watchdog */
+ reg = readl(dev->rstout);
+ reg &= ~dev->data->rstout_enable_bit;
+ writel(reg, dev->rstout);
+
+ /* Disable watchdog timer */
+ mask = dev->data->wdt_enable_bit;
+ if (wdt_dev->info->options & WDIOF_PRETIMEOUT)
+ mask |= TIMER1_ENABLE_BIT;
+ atomic_io_modify(dev->reg + TIMER_CTRL, mask, 0);
+
+ return 0;
+}
+
+static int orion_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ return dev->data->stop(wdt_dev);
+}
+
+static int orion_enabled(struct orion_watchdog *dev)
+{
+ bool enabled, running;
+
+ enabled = readl(dev->rstout) & dev->data->rstout_enable_bit;
+ running = readl(dev->reg + TIMER_CTRL) & dev->data->wdt_enable_bit;
+
+ return enabled && running;
+}
+
+static int armada375_enabled(struct orion_watchdog *dev)
+{
+ bool masked, enabled, running;
+
+ masked = readl(dev->rstout_mask) & dev->data->rstout_mask_bit;
+ enabled = readl(dev->rstout) & dev->data->rstout_enable_bit;
+ running = readl(dev->reg + TIMER_CTRL) & dev->data->wdt_enable_bit;
+
+ return !masked && enabled && running;
+}
+
+static int orion_wdt_enabled(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ return dev->data->enabled(dev);
+}
+
+static unsigned int orion_wdt_get_timeleft(struct watchdog_device *wdt_dev)
+{
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+ return readl(dev->reg + dev->data->wdt_counter_offset) / dev->clk_rate;
+}
+
+static struct watchdog_info orion_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Orion Watchdog",
+};
+
+static const struct watchdog_ops orion_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = orion_wdt_start,
+ .stop = orion_wdt_stop,
+ .ping = orion_wdt_ping,
+ .get_timeleft = orion_wdt_get_timeleft,
+};
+
+static irqreturn_t orion_wdt_irq(int irq, void *devid)
+{
+ panic("Watchdog Timeout");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t orion_wdt_pre_irq(int irq, void *devid)
+{
+ struct orion_watchdog *dev = devid;
+
+ atomic_io_modify(dev->reg + TIMER_A370_STATUS,
+ TIMER1_STATUS_BIT, 0);
+ watchdog_notify_pretimeout(&dev->wdt);
+ return IRQ_HANDLED;
+}
+
+/*
+ * The original devicetree binding for this driver specified only
+ * one memory resource, so in order to keep DT backwards compatibility
+ * we try to fallback to a hardcoded register address, if the resource
+ * is missing from the devicetree.
+ */
+static void __iomem *orion_wdt_ioremap_rstout(struct platform_device *pdev,
+ phys_addr_t internal_regs)
+{
+ struct resource *res;
+ phys_addr_t rstout;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (res)
+ return devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+
+ rstout = internal_regs + ORION_RSTOUT_MASK_OFFSET;
+
+ WARN(1, FW_BUG "falling back to hardcoded RSTOUT reg %pa\n", &rstout);
+ return devm_ioremap(&pdev->dev, rstout, 0x4);
+}
+
+static const struct orion_watchdog_data orion_data = {
+ .rstout_enable_bit = BIT(1),
+ .wdt_enable_bit = BIT(4),
+ .wdt_counter_offset = 0x24,
+ .clock_init = orion_wdt_clock_init,
+ .enabled = orion_enabled,
+ .start = orion_start,
+ .stop = orion_stop,
+};
+
+static const struct orion_watchdog_data armada370_data = {
+ .rstout_enable_bit = BIT(8),
+ .wdt_enable_bit = BIT(8),
+ .wdt_counter_offset = 0x34,
+ .clock_init = armada370_wdt_clock_init,
+ .enabled = orion_enabled,
+ .start = armada370_start,
+ .stop = armada370_stop,
+};
+
+static const struct orion_watchdog_data armadaxp_data = {
+ .rstout_enable_bit = BIT(8),
+ .wdt_enable_bit = BIT(8),
+ .wdt_counter_offset = 0x34,
+ .clock_init = armadaxp_wdt_clock_init,
+ .enabled = orion_enabled,
+ .start = armada370_start,
+ .stop = armada370_stop,
+};
+
+static const struct orion_watchdog_data armada375_data = {
+ .rstout_enable_bit = BIT(8),
+ .rstout_mask_bit = BIT(10),
+ .wdt_enable_bit = BIT(8),
+ .wdt_counter_offset = 0x34,
+ .clock_init = armada375_wdt_clock_init,
+ .enabled = armada375_enabled,
+ .start = armada375_start,
+ .stop = armada375_stop,
+};
+
+static const struct orion_watchdog_data armada380_data = {
+ .rstout_enable_bit = BIT(8),
+ .rstout_mask_bit = BIT(10),
+ .wdt_enable_bit = BIT(8),
+ .wdt_counter_offset = 0x34,
+ .clock_init = armadaxp_wdt_clock_init,
+ .enabled = armada375_enabled,
+ .start = armada375_start,
+ .stop = armada375_stop,
+};
+
+static const struct of_device_id orion_wdt_of_match_table[] = {
+ {
+ .compatible = "marvell,orion-wdt",
+ .data = &orion_data,
+ },
+ {
+ .compatible = "marvell,armada-370-wdt",
+ .data = &armada370_data,
+ },
+ {
+ .compatible = "marvell,armada-xp-wdt",
+ .data = &armadaxp_data,
+ },
+ {
+ .compatible = "marvell,armada-375-wdt",
+ .data = &armada375_data,
+ },
+ {
+ .compatible = "marvell,armada-380-wdt",
+ .data = &armada380_data,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, orion_wdt_of_match_table);
+
+static int orion_wdt_get_regs(struct platform_device *pdev,
+ struct orion_watchdog *dev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+ dev->reg = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!dev->reg)
+ return -ENOMEM;
+
+ /* Each supported compatible has some RSTOUT register quirk */
+ if (of_device_is_compatible(node, "marvell,orion-wdt")) {
+
+ dev->rstout = orion_wdt_ioremap_rstout(pdev, res->start &
+ INTERNAL_REGS_MASK);
+ if (!dev->rstout)
+ return -ENODEV;
+
+ } else if (of_device_is_compatible(node, "marvell,armada-370-wdt") ||
+ of_device_is_compatible(node, "marvell,armada-xp-wdt")) {
+
+ /* Dedicated RSTOUT register, can be requested. */
+ dev->rstout = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dev->rstout))
+ return PTR_ERR(dev->rstout);
+
+ } else if (of_device_is_compatible(node, "marvell,armada-375-wdt") ||
+ of_device_is_compatible(node, "marvell,armada-380-wdt")) {
+
+ /* Dedicated RSTOUT register, can be requested. */
+ dev->rstout = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dev->rstout))
+ return PTR_ERR(dev->rstout);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (!res)
+ return -ENODEV;
+ dev->rstout_mask = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!dev->rstout_mask)
+ return -ENOMEM;
+
+ } else {
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int orion_wdt_probe(struct platform_device *pdev)
+{
+ struct orion_watchdog *dev;
+ const struct of_device_id *match;
+ unsigned int wdt_max_duration; /* (seconds) */
+ int ret, irq;
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(struct orion_watchdog),
+ GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ match = of_match_device(orion_wdt_of_match_table, &pdev->dev);
+ if (!match)
+ /* Default legacy match */
+ match = &orion_wdt_of_match_table[0];
+
+ dev->wdt.info = &orion_wdt_info;
+ dev->wdt.ops = &orion_wdt_ops;
+ dev->wdt.min_timeout = 1;
+ dev->data = match->data;
+
+ ret = orion_wdt_get_regs(pdev, dev);
+ if (ret)
+ return ret;
+
+ ret = dev->data->clock_init(pdev, dev);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot initialize clock\n");
+ return ret;
+ }
+
+ wdt_max_duration = WDT_MAX_CYCLE_COUNT / dev->clk_rate;
+
+ dev->wdt.timeout = wdt_max_duration;
+ dev->wdt.max_timeout = wdt_max_duration;
+ dev->wdt.parent = &pdev->dev;
+ watchdog_init_timeout(&dev->wdt, heartbeat, &pdev->dev);
+
+ platform_set_drvdata(pdev, &dev->wdt);
+ watchdog_set_drvdata(&dev->wdt, dev);
+
+ /*
+ * Let's make sure the watchdog is fully stopped, unless it's
+ * explicitly enabled. This may be the case if the module was
+ * removed and re-inserted, or if the bootloader explicitly
+ * set a running watchdog before booting the kernel.
+ */
+ if (!orion_wdt_enabled(&dev->wdt))
+ orion_wdt_stop(&dev->wdt);
+ else
+ set_bit(WDOG_HW_RUNNING, &dev->wdt.status);
+
+ /* Request the IRQ only after the watchdog is disabled */
+ irq = platform_get_irq_optional(pdev, 0);
+ if (irq > 0) {
+ /*
+ * Not all supported platforms specify an interrupt for the
+ * watchdog, so let's make it optional.
+ */
+ ret = devm_request_irq(&pdev->dev, irq, orion_wdt_irq, 0,
+ pdev->name, dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request IRQ\n");
+ goto disable_clk;
+ }
+ }
+
+ /* Optional 2nd interrupt for pretimeout */
+ irq = platform_get_irq_optional(pdev, 1);
+ if (irq > 0) {
+ orion_wdt_info.options |= WDIOF_PRETIMEOUT;
+ ret = devm_request_irq(&pdev->dev, irq, orion_wdt_pre_irq,
+ 0, pdev->name, dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request IRQ\n");
+ goto disable_clk;
+ }
+ }
+
+
+ watchdog_set_nowayout(&dev->wdt, nowayout);
+ ret = watchdog_register_device(&dev->wdt);
+ if (ret)
+ goto disable_clk;
+
+ pr_info("Initial timeout %d sec%s\n",
+ dev->wdt.timeout, nowayout ? ", nowayout" : "");
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(dev->clk);
+ clk_put(dev->clk);
+ return ret;
+}
+
+static int orion_wdt_remove(struct platform_device *pdev)
+{
+ struct watchdog_device *wdt_dev = platform_get_drvdata(pdev);
+ struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev);
+
+ watchdog_unregister_device(wdt_dev);
+ clk_disable_unprepare(dev->clk);
+ clk_put(dev->clk);
+ return 0;
+}
+
+static void orion_wdt_shutdown(struct platform_device *pdev)
+{
+ struct watchdog_device *wdt_dev = platform_get_drvdata(pdev);
+ orion_wdt_stop(wdt_dev);
+}
+
+static struct platform_driver orion_wdt_driver = {
+ .probe = orion_wdt_probe,
+ .remove = orion_wdt_remove,
+ .shutdown = orion_wdt_shutdown,
+ .driver = {
+ .name = "orion_wdt",
+ .of_match_table = orion_wdt_of_match_table,
+ },
+};
+
+module_platform_driver(orion_wdt_driver);
+
+MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>");
+MODULE_DESCRIPTION("Orion Processor Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:orion_wdt");
diff --git a/drivers/watchdog/pc87413_wdt.c b/drivers/watchdog/pc87413_wdt.c
new file mode 100644
index 000000000..c7f745caf
--- /dev/null
+++ b/drivers/watchdog/pc87413_wdt.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x
+ *
+ * This code is based on wdt.c with original copyright.
+ *
+ * (C) Copyright 2006 Sven Anders, <anders@anduras.de>
+ * and Marcus Junker, <junker@anduras.de>
+ *
+ * Neither Sven Anders, Marcus Junker nor ANDURAS AG
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ *
+ * Release 1.1
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+/* #define DEBUG 1 */
+
+#define DEFAULT_TIMEOUT 1 /* 1 minute */
+#define MAX_TIMEOUT 255
+
+#define VERSION "1.1"
+#define MODNAME "pc87413 WDT"
+#define DPFX MODNAME " - DEBUG: "
+
+#define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */
+#define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1)
+#define SWC_LDN 0x04
+#define SIOCFG2 0x22 /* Serial IO register */
+#define WDCTL 0x10 /* Watchdog-Timer-Control-Register */
+#define WDTO 0x11 /* Watchdog timeout register */
+#define WDCFG 0x12 /* Watchdog config register */
+
+#define IO_DEFAULT 0x2E /* Address used on Portwell Boards */
+
+static int io = IO_DEFAULT;
+static int swc_base_addr = -1;
+
+static int timeout = DEFAULT_TIMEOUT; /* timeout value */
+static unsigned long timer_enabled; /* is the timer enabled? */
+
+static char expect_close; /* is the close expected? */
+
+static DEFINE_SPINLOCK(io_lock); /* to guard us from io races */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+/* -- Low level function ----------------------------------------*/
+
+/* Select pins for Watchdog output */
+
+static inline void pc87413_select_wdt_out(void)
+{
+ unsigned int cr_data = 0;
+
+ /* Step 1: Select multiple pin,pin55,as WDT output */
+
+ outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
+
+ cr_data = inb(WDT_DATA_IO_PORT);
+
+ cr_data |= 0x80; /* Set Bit7 to 1*/
+ outb_p(SIOCFG2, WDT_INDEX_IO_PORT);
+
+ outb_p(cr_data, WDT_DATA_IO_PORT);
+
+#ifdef DEBUG
+ pr_info(DPFX
+ "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n",
+ cr_data);
+#endif
+}
+
+/* Enable SWC functions */
+
+static inline void pc87413_enable_swc(void)
+{
+ unsigned int cr_data = 0;
+
+ /* Step 2: Enable SWC functions */
+
+ outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */
+ outb_p(SWC_LDN, WDT_DATA_IO_PORT);
+
+ outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */
+ cr_data = inb(WDT_DATA_IO_PORT);
+ cr_data |= 0x01; /* Set Bit0 to 1 */
+ outb_p(0x30, WDT_INDEX_IO_PORT);
+ outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */
+
+#ifdef DEBUG
+ pr_info(DPFX "pc87413 - Enable SWC functions\n");
+#endif
+}
+
+/* Read SWC I/O base address */
+
+static void pc87413_get_swc_base_addr(void)
+{
+ unsigned char addr_l, addr_h = 0;
+
+ /* Step 3: Read SWC I/O Base Address */
+
+ outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */
+ addr_h = inb(WDT_DATA_IO_PORT);
+
+ outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */
+
+ addr_l = inb(WDT_DATA_IO_PORT);
+
+ swc_base_addr = (addr_h << 8) + addr_l;
+#ifdef DEBUG
+ pr_info(DPFX
+ "Read SWC I/O Base Address: low %d, high %d, res %d\n",
+ addr_l, addr_h, swc_base_addr);
+#endif
+}
+
+/* Select Bank 3 of SWC */
+
+static inline void pc87413_swc_bank3(void)
+{
+ /* Step 4: Select Bank3 of SWC */
+ outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f);
+#ifdef DEBUG
+ pr_info(DPFX "Select Bank3 of SWC\n");
+#endif
+}
+
+/* Set watchdog timeout to x minutes */
+
+static inline void pc87413_programm_wdto(char pc87413_time)
+{
+ /* Step 5: Programm WDTO, Twd. */
+ outb_p(pc87413_time, swc_base_addr + WDTO);
+#ifdef DEBUG
+ pr_info(DPFX "Set WDTO to %d minutes\n", pc87413_time);
+#endif
+}
+
+/* Enable WDEN */
+
+static inline void pc87413_enable_wden(void)
+{
+ /* Step 6: Enable WDEN */
+ outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL);
+#ifdef DEBUG
+ pr_info(DPFX "Enable WDEN\n");
+#endif
+}
+
+/* Enable SW_WD_TREN */
+static inline void pc87413_enable_sw_wd_tren(void)
+{
+ /* Enable SW_WD_TREN */
+ outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG);
+#ifdef DEBUG
+ pr_info(DPFX "Enable SW_WD_TREN\n");
+#endif
+}
+
+/* Disable SW_WD_TREN */
+
+static inline void pc87413_disable_sw_wd_tren(void)
+{
+ /* Disable SW_WD_TREN */
+ outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG);
+#ifdef DEBUG
+ pr_info(DPFX "pc87413 - Disable SW_WD_TREN\n");
+#endif
+}
+
+/* Enable SW_WD_TRG */
+
+static inline void pc87413_enable_sw_wd_trg(void)
+{
+ /* Enable SW_WD_TRG */
+ outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL);
+#ifdef DEBUG
+ pr_info(DPFX "pc87413 - Enable SW_WD_TRG\n");
+#endif
+}
+
+/* Disable SW_WD_TRG */
+
+static inline void pc87413_disable_sw_wd_trg(void)
+{
+ /* Disable SW_WD_TRG */
+ outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL);
+#ifdef DEBUG
+ pr_info(DPFX "Disable SW_WD_TRG\n");
+#endif
+}
+
+/* -- Higher level functions ------------------------------------*/
+
+/* Enable the watchdog */
+
+static void pc87413_enable(void)
+{
+ spin_lock(&io_lock);
+
+ pc87413_swc_bank3();
+ pc87413_programm_wdto(timeout);
+ pc87413_enable_wden();
+ pc87413_enable_sw_wd_tren();
+ pc87413_enable_sw_wd_trg();
+
+ spin_unlock(&io_lock);
+}
+
+/* Disable the watchdog */
+
+static void pc87413_disable(void)
+{
+ spin_lock(&io_lock);
+
+ pc87413_swc_bank3();
+ pc87413_disable_sw_wd_tren();
+ pc87413_disable_sw_wd_trg();
+ pc87413_programm_wdto(0);
+
+ spin_unlock(&io_lock);
+}
+
+/* Refresh the watchdog */
+
+static void pc87413_refresh(void)
+{
+ spin_lock(&io_lock);
+
+ pc87413_swc_bank3();
+ pc87413_disable_sw_wd_tren();
+ pc87413_disable_sw_wd_trg();
+ pc87413_programm_wdto(timeout);
+ pc87413_enable_wden();
+ pc87413_enable_sw_wd_tren();
+ pc87413_enable_sw_wd_trg();
+
+ spin_unlock(&io_lock);
+}
+
+/* -- File operations -------------------------------------------*/
+
+/**
+ * pc87413_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ */
+
+static int pc87413_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+
+ if (test_and_set_bit(0, &timer_enabled))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Reload and activate timer */
+ pc87413_refresh();
+
+ pr_info("Watchdog enabled. Timeout set to %d minute(s).\n", timeout);
+
+ return stream_open(inode, file);
+}
+
+/**
+ * pc87413_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int pc87413_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer. */
+
+ if (expect_close == 42) {
+ pc87413_disable();
+ pr_info("Watchdog disabled, sleeping again...\n");
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ pc87413_refresh();
+ }
+ clear_bit(0, &timer_enabled);
+ expect_close = 0;
+ return 0;
+}
+
+/**
+ * pc87413_status:
+ *
+ * return, if the watchdog is enabled (timeout is set...)
+ */
+
+
+static int pc87413_status(void)
+{
+ return 0; /* currently not supported */
+}
+
+/**
+ * pc87413_write:
+ * @file: file handle to the watchdog
+ * @data: data buffer to write
+ * @len: length in bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t pc87413_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* reset expect flag */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the
+ magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ pc87413_refresh();
+ }
+ return len;
+}
+
+/**
+ * pc87413_ioctl:
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features. We only actually usefully support
+ * querying capabilities and current status.
+ */
+
+static long pc87413_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_timeout;
+
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "PC87413(HF/F) watchdog",
+ };
+
+ uarg.i = (int __user *)arg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ return put_user(pc87413_status(), uarg.i);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, uarg.i);
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+ if (get_user(options, uarg.i))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ pc87413_disable();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ pc87413_enable();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ pc87413_refresh();
+#ifdef DEBUG
+ pr_info(DPFX "keepalive\n");
+#endif
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, uarg.i))
+ return -EFAULT;
+ /* the API states this is given in secs */
+ new_timeout /= 60;
+ if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+ return -EINVAL;
+ timeout = new_timeout;
+ pc87413_refresh();
+ fallthrough; /* and return the new timeout */
+ case WDIOC_GETTIMEOUT:
+ new_timeout = timeout * 60;
+ return put_user(new_timeout, uarg.i);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/* -- Notifier functions -----------------------------------------*/
+
+/**
+ * pc87413_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int pc87413_notify_sys(struct notifier_block *this,
+ unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ /* Turn the card off */
+ pc87413_disable();
+ return NOTIFY_DONE;
+}
+
+/* -- Module's structures ---------------------------------------*/
+
+static const struct file_operations pc87413_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = pc87413_write,
+ .unlocked_ioctl = pc87413_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = pc87413_open,
+ .release = pc87413_release,
+};
+
+static struct notifier_block pc87413_notifier = {
+ .notifier_call = pc87413_notify_sys,
+};
+
+static struct miscdevice pc87413_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pc87413_fops,
+};
+
+/* -- Module init functions -------------------------------------*/
+
+/**
+ * pc87413_init: module's "constructor"
+ *
+ * Set up the WDT watchdog board. All we have to do is grab the
+ * resources we require and bitch if anyone beat us to them.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init pc87413_init(void)
+{
+ int ret;
+
+ pr_info("Version " VERSION " at io 0x%X\n",
+ WDT_INDEX_IO_PORT);
+
+ if (!request_muxed_region(io, 2, MODNAME))
+ return -EBUSY;
+
+ ret = register_reboot_notifier(&pc87413_notifier);
+ if (ret != 0)
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+
+ ret = misc_register(&pc87413_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto reboot_unreg;
+ }
+ pr_info("initialized. timeout=%d min\n", timeout);
+
+ pc87413_select_wdt_out();
+ pc87413_enable_swc();
+ pc87413_get_swc_base_addr();
+
+ if (!request_region(swc_base_addr, 0x20, MODNAME)) {
+ pr_err("cannot request SWC region at 0x%x\n", swc_base_addr);
+ ret = -EBUSY;
+ goto misc_unreg;
+ }
+
+ pc87413_enable();
+
+ release_region(io, 2);
+ return 0;
+
+misc_unreg:
+ misc_deregister(&pc87413_miscdev);
+reboot_unreg:
+ unregister_reboot_notifier(&pc87413_notifier);
+ release_region(io, 2);
+ return ret;
+}
+
+/**
+ * pc87413_exit: module's "destructor"
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit pc87413_exit(void)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout) {
+ pc87413_disable();
+ pr_info("Watchdog disabled\n");
+ }
+
+ misc_deregister(&pc87413_miscdev);
+ unregister_reboot_notifier(&pc87413_notifier);
+ release_region(swc_base_addr, 0x20);
+
+ pr_info("watchdog component driver removed\n");
+}
+
+module_init(pc87413_init);
+module_exit(pc87413_exit);
+
+MODULE_AUTHOR("Sven Anders <anders@anduras.de>");
+MODULE_AUTHOR("Marcus Junker <junker@anduras.de>");
+MODULE_DESCRIPTION("PC87413 WDT driver");
+MODULE_LICENSE("GPL");
+
+module_param_hw(io, int, ioport, 0);
+MODULE_PARM_DESC(io, MODNAME " I/O port (default: "
+ __MODULE_STRING(IO_DEFAULT) ").");
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in minutes (default="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ").");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
diff --git a/drivers/watchdog/pcwd.c b/drivers/watchdog/pcwd.c
new file mode 100644
index 000000000..a793b03a7
--- /dev/null
+++ b/drivers/watchdog/pcwd.c
@@ -0,0 +1,998 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PC Watchdog Driver
+ * by Ken Hollis (khollis@bitgate.com)
+ *
+ * Permission granted from Simon Machell (smachell@berkprod.com)
+ * Written for the Linux Kernel, and GPLed by Ken Hollis
+ *
+ * 960107 Added request_region routines, modulized the whole thing.
+ * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
+ * WD_TIMEOUT define.
+ * 960216 Added eof marker on the file, and changed verbose messages.
+ * 960716 Made functional and cosmetic changes to the source for
+ * inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
+ * 960717 Removed read/seek routines, replaced with ioctl. Also, added
+ * check_region command due to Alan's suggestion.
+ * 960821 Made changes to compile in newer 2.0.x kernels. Added
+ * "cold reboot sense" entry.
+ * 960825 Made a few changes to code, deleted some defines and made
+ * typedefs to replace them. Made heartbeat reset only available
+ * via ioctl, and removed the write routine.
+ * 960828 Added new items for PC Watchdog Rev.C card.
+ * 960829 Changed around all of the IOCTLs, added new features,
+ * added watchdog disable/re-enable routines. Added firmware
+ * version reporting. Added read routine for temperature.
+ * Removed some extra defines, added an autodetect Revision
+ * routine.
+ * 961006 Revised some documentation, fixed some cosmetic bugs. Made
+ * drivers to panic the system if it's overheating at bootup.
+ * 961118 Changed some verbiage on some of the output, tidied up
+ * code bits, and added compatibility to 2.1.x.
+ * 970912 Enabled board on open and disable on close.
+ * 971107 Took account of recent VFS changes (broke read).
+ * 971210 Disable board on initialisation in case board already ticking.
+ * 971222 Changed open/close for temperature handling
+ * Michael Meskes <meskes@debian.org>.
+ * 980112 Used minor numbers from include/linux/miscdevice.h
+ * 990403 Clear reset status after reading control status register in
+ * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
+ * 990605 Made changes to code to support Firmware 1.22a, added
+ * fairly useless proc entry.
+ * 990610 removed said useless proc code for the merge <alan>
+ * 000403 Removed last traces of proc code. <davej>
+ * 011214 Added nowayout module option to override
+ * CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
+ * Added timeout module option to override default
+ */
+
+/*
+ * A bells and whistles driver is available from http://www.pcwd.de/
+ * More info available at http://www.berkprod.com/ or
+ * http://www.pcwatchdog.com/
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/delay.h> /* For mdelay function */
+#include <linux/timer.h> /* For timer related operations */
+#include <linux/jiffies.h> /* For jiffies stuff */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/reboot.h> /* For kernel_power_off() */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/isa.h> /* For isa devices */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For inb/outb/... */
+
+/* Module and version information */
+#define WATCHDOG_VERSION "1.20"
+#define WATCHDOG_DATE "18 Feb 2007"
+#define WATCHDOG_DRIVER_NAME "ISA-PC Watchdog"
+#define WATCHDOG_NAME "pcwd"
+#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION "\n"
+
+/*
+ * It should be noted that PCWD_REVISION_B was removed because A and B
+ * are essentially the same types of card, with the exception that B
+ * has temperature reporting. Since I didn't receive a Rev.B card,
+ * the Rev.B card is not supported. (It's a good thing too, as they
+ * are no longer in production.)
+ */
+#define PCWD_REVISION_A 1
+#define PCWD_REVISION_C 2
+
+/*
+ * These are the auto-probe addresses available.
+ *
+ * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
+ * Revision A has an address range of 2 addresses, while Revision C has 4.
+ */
+#define PCWD_ISA_NR_CARDS 3
+static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PCI-PC Watchdog card.
+*/
+/* Port 1 : Control Status #1 for the PC Watchdog card, revision A. */
+#define WD_WDRST 0x01 /* Previously reset state */
+#define WD_T110 0x02 /* Temperature overheat sense */
+#define WD_HRTBT 0x04 /* Heartbeat sense */
+#define WD_RLY2 0x08 /* External relay triggered */
+#define WD_SRLY2 0x80 /* Software external relay triggered */
+/* Port 1 : Control Status #1 for the PC Watchdog card, revision C. */
+#define WD_REVC_WTRP 0x01 /* Watchdog Trip status */
+#define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */
+#define WD_REVC_TTRP 0x04 /* Temperature Trip status */
+#define WD_REVC_RL2A 0x08 /* Relay 2 activated by
+ on-board processor */
+#define WD_REVC_RL1A 0x10 /* Relay 1 active */
+#define WD_REVC_R2DS 0x40 /* Relay 2 disable */
+#define WD_REVC_RLY2 0x80 /* Relay 2 activated? */
+/* Port 2 : Control Status #2 */
+#define WD_WDIS 0x10 /* Watchdog Disabled */
+#define WD_ENTP 0x20 /* Watchdog Enable Temperature Trip */
+#define WD_SSEL 0x40 /* Watchdog Switch Select
+ (1:SW1 <-> 0:SW2) */
+#define WD_WCMD 0x80 /* Watchdog Command Mode */
+
+/* max. time we give an ISA watchdog card to process a command */
+/* 500ms for each 4 bit response (according to spec.) */
+#define ISA_COMMAND_TIMEOUT 1000
+
+/* Watchdog's internal commands */
+#define CMD_ISA_IDLE 0x00
+#define CMD_ISA_VERSION_INTEGER 0x01
+#define CMD_ISA_VERSION_TENTH 0x02
+#define CMD_ISA_VERSION_HUNDRETH 0x03
+#define CMD_ISA_VERSION_MINOR 0x04
+#define CMD_ISA_SWITCH_SETTINGS 0x05
+#define CMD_ISA_RESET_PC 0x06
+#define CMD_ISA_ARM_0 0x07
+#define CMD_ISA_ARM_30 0x08
+#define CMD_ISA_ARM_60 0x09
+#define CMD_ISA_DELAY_TIME_2SECS 0x0A
+#define CMD_ISA_DELAY_TIME_4SECS 0x0B
+#define CMD_ISA_DELAY_TIME_8SECS 0x0C
+#define CMD_ISA_RESET_RELAYS 0x0D
+
+/* Watchdog's Dip Switch heartbeat values */
+static const int heartbeat_tbl[] = {
+ 20, /* OFF-OFF-OFF = 20 Sec */
+ 40, /* OFF-OFF-ON = 40 Sec */
+ 60, /* OFF-ON-OFF = 1 Min */
+ 300, /* OFF-ON-ON = 5 Min */
+ 600, /* ON-OFF-OFF = 10 Min */
+ 1800, /* ON-OFF-ON = 30 Min */
+ 3600, /* ON-ON-OFF = 1 Hour */
+ 7200, /* ON-ON-ON = 2 hour */
+};
+
+/*
+ * We are using an kernel timer to do the pinging of the watchdog
+ * every ~500ms. We try to set the internal heartbeat of the
+ * watchdog to 2 ms.
+ */
+
+#define WDT_INTERVAL (HZ/2+1)
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static unsigned long open_allowed;
+static char expect_close;
+static int temp_panic;
+
+/* this is private data for each ISA-PC watchdog card */
+static struct {
+ char fw_ver_str[6]; /* The cards firmware version */
+ int revision; /* The card's revision */
+ int supports_temp; /* Whether or not the card has
+ a temperature device */
+ int command_mode; /* Whether or not the card is in
+ command mode */
+ int boot_status; /* The card's boot status */
+ int io_addr; /* The cards I/O address */
+ spinlock_t io_lock; /* the lock for io operations */
+ struct timer_list timer; /* The timer that pings the watchdog */
+ unsigned long next_heartbeat; /* the next_heartbeat for the timer */
+} pcwd_private;
+
+/* module parameters */
+#define QUIET 0 /* Default */
+#define VERBOSE 1 /* Verbose */
+#define DEBUG 2 /* print fancy stuff too */
+static int debug = QUIET;
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug,
+ "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)");
+
+/* default heartbeat = delay-time from dip-switches */
+#define WATCHDOG_HEARTBEAT 0
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. "
+ "(2 <= heartbeat <= 7200 or 0=delay-time from dip-switches, default="
+ __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+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) ")");
+
+/*
+ * Internal functions
+ */
+
+static int send_isa_command(int cmd)
+{
+ int i;
+ int control_status;
+ int port0, last_port0; /* Double read for stabilising */
+
+ if (debug >= DEBUG)
+ pr_debug("sending following data cmd=0x%02x\n", cmd);
+
+ /* The WCMD bit must be 1 and the command is only 4 bits in size */
+ control_status = (cmd & 0x0F) | WD_WCMD;
+ outb_p(control_status, pcwd_private.io_addr + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+
+ port0 = inb_p(pcwd_private.io_addr);
+ for (i = 0; i < 25; ++i) {
+ last_port0 = port0;
+ port0 = inb_p(pcwd_private.io_addr);
+
+ if (port0 == last_port0)
+ break; /* Data is stable */
+
+ udelay(250);
+ }
+
+ if (debug >= DEBUG)
+ pr_debug("received following data for cmd=0x%02x: port0=0x%02x last_port0=0x%02x\n",
+ cmd, port0, last_port0);
+
+ return port0;
+}
+
+static int set_command_mode(void)
+{
+ int i, found = 0, count = 0;
+
+ /* Set the card into command mode */
+ spin_lock(&pcwd_private.io_lock);
+ while ((!found) && (count < 3)) {
+ i = send_isa_command(CMD_ISA_IDLE);
+
+ if (i == 0x00)
+ found = 1;
+ else if (i == 0xF3) {
+ /* Card does not like what we've done to it */
+ outb_p(0x00, pcwd_private.io_addr + 2);
+ udelay(1200); /* Spec says wait 1ms */
+ outb_p(0x00, pcwd_private.io_addr + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+ }
+ count++;
+ }
+ spin_unlock(&pcwd_private.io_lock);
+ pcwd_private.command_mode = found;
+
+ if (debug >= DEBUG)
+ pr_debug("command_mode=%d\n", pcwd_private.command_mode);
+
+ return found;
+}
+
+static void unset_command_mode(void)
+{
+ /* Set the card into normal mode */
+ spin_lock(&pcwd_private.io_lock);
+ outb_p(0x00, pcwd_private.io_addr + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+ spin_unlock(&pcwd_private.io_lock);
+
+ pcwd_private.command_mode = 0;
+
+ if (debug >= DEBUG)
+ pr_debug("command_mode=%d\n", pcwd_private.command_mode);
+}
+
+static inline void pcwd_check_temperature_support(void)
+{
+ if (inb(pcwd_private.io_addr) != 0xF0)
+ pcwd_private.supports_temp = 1;
+}
+
+static inline void pcwd_get_firmware(void)
+{
+ int one, ten, hund, minor;
+
+ strcpy(pcwd_private.fw_ver_str, "ERROR");
+
+ if (set_command_mode()) {
+ one = send_isa_command(CMD_ISA_VERSION_INTEGER);
+ ten = send_isa_command(CMD_ISA_VERSION_TENTH);
+ hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH);
+ minor = send_isa_command(CMD_ISA_VERSION_MINOR);
+ sprintf(pcwd_private.fw_ver_str, "%c.%c%c%c",
+ one, ten, hund, minor);
+ }
+ unset_command_mode();
+
+ return;
+}
+
+static inline int pcwd_get_option_switches(void)
+{
+ int option_switches = 0;
+
+ if (set_command_mode()) {
+ /* Get switch settings */
+ option_switches = send_isa_command(CMD_ISA_SWITCH_SETTINGS);
+ }
+
+ unset_command_mode();
+ return option_switches;
+}
+
+static void pcwd_show_card_info(void)
+{
+ int option_switches;
+
+ /* Get some extra info from the hardware (in command/debug/diag mode) */
+ if (pcwd_private.revision == PCWD_REVISION_A)
+ pr_info("ISA-PC Watchdog (REV.A) detected at port 0x%04x\n",
+ pcwd_private.io_addr);
+ else if (pcwd_private.revision == PCWD_REVISION_C) {
+ pcwd_get_firmware();
+ pr_info("ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n",
+ pcwd_private.io_addr, pcwd_private.fw_ver_str);
+ option_switches = pcwd_get_option_switches();
+ pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ /* Reprogram internal heartbeat to 2 seconds */
+ if (set_command_mode()) {
+ send_isa_command(CMD_ISA_DELAY_TIME_2SECS);
+ unset_command_mode();
+ }
+ }
+
+ if (pcwd_private.supports_temp)
+ pr_info("Temperature Option Detected\n");
+
+ if (pcwd_private.boot_status & WDIOF_CARDRESET)
+ pr_info("Previous reboot was caused by the card\n");
+
+ if (pcwd_private.boot_status & WDIOF_OVERHEAT) {
+ pr_emerg("Card senses a CPU Overheat. Panicking!\n");
+ pr_emerg("CPU Overheat\n");
+ }
+
+ if (pcwd_private.boot_status == 0)
+ pr_info("No previous trip detected - Cold boot or reset\n");
+}
+
+static void pcwd_timer_ping(struct timer_list *unused)
+{
+ int wdrst_stat;
+
+ /* If we got a heartbeat pulse within the WDT_INTERVAL
+ * we agree to ping the WDT */
+ if (time_before(jiffies, pcwd_private.next_heartbeat)) {
+ /* Ping the watchdog */
+ spin_lock(&pcwd_private.io_lock);
+ if (pcwd_private.revision == PCWD_REVISION_A) {
+ /* Rev A cards are reset by setting the
+ WD_WDRST bit in register 1 */
+ wdrst_stat = inb_p(pcwd_private.io_addr);
+ wdrst_stat &= 0x0F;
+ wdrst_stat |= WD_WDRST;
+
+ outb_p(wdrst_stat, pcwd_private.io_addr + 1);
+ } else {
+ /* Re-trigger watchdog by writing to port 0 */
+ outb_p(0x00, pcwd_private.io_addr);
+ }
+
+ /* Re-set the timer interval */
+ mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL);
+
+ spin_unlock(&pcwd_private.io_lock);
+ } else {
+ pr_warn("Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+static int pcwd_start(void)
+{
+ int stat_reg;
+
+ pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ);
+
+ /* Start the timer */
+ mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL);
+
+ /* Enable the port */
+ if (pcwd_private.revision == PCWD_REVISION_C) {
+ spin_lock(&pcwd_private.io_lock);
+ outb_p(0x00, pcwd_private.io_addr + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ stat_reg = inb_p(pcwd_private.io_addr + 2);
+ spin_unlock(&pcwd_private.io_lock);
+ if (stat_reg & WD_WDIS) {
+ pr_info("Could not start watchdog\n");
+ return -EIO;
+ }
+ }
+
+ if (debug >= VERBOSE)
+ pr_debug("Watchdog started\n");
+
+ return 0;
+}
+
+static int pcwd_stop(void)
+{
+ int stat_reg;
+
+ /* Stop the timer */
+ del_timer(&pcwd_private.timer);
+
+ /* Disable the board */
+ if (pcwd_private.revision == PCWD_REVISION_C) {
+ spin_lock(&pcwd_private.io_lock);
+ outb_p(0xA5, pcwd_private.io_addr + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ outb_p(0xA5, pcwd_private.io_addr + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ stat_reg = inb_p(pcwd_private.io_addr + 2);
+ spin_unlock(&pcwd_private.io_lock);
+ if ((stat_reg & WD_WDIS) == 0) {
+ pr_info("Could not stop watchdog\n");
+ return -EIO;
+ }
+ }
+
+ if (debug >= VERBOSE)
+ pr_debug("Watchdog stopped\n");
+
+ return 0;
+}
+
+static int pcwd_keepalive(void)
+{
+ /* user land ping */
+ pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ);
+
+ if (debug >= DEBUG)
+ pr_debug("Watchdog keepalive signal send\n");
+
+ return 0;
+}
+
+static int pcwd_set_heartbeat(int t)
+{
+ if (t < 2 || t > 7200) /* arbitrary upper limit */
+ return -EINVAL;
+
+ heartbeat = t;
+
+ if (debug >= VERBOSE)
+ pr_debug("New heartbeat: %d\n", heartbeat);
+
+ return 0;
+}
+
+static int pcwd_get_status(int *status)
+{
+ int control_status;
+
+ *status = 0;
+ spin_lock(&pcwd_private.io_lock);
+ if (pcwd_private.revision == PCWD_REVISION_A)
+ /* Rev A cards return status information from
+ * the base register, which is used for the
+ * temperature in other cards. */
+ control_status = inb(pcwd_private.io_addr);
+ else {
+ /* Rev C cards return card status in the base
+ * address + 1 register. And use different bits
+ * to indicate a card initiated reset, and an
+ * over-temperature condition. And the reboot
+ * status can be reset. */
+ control_status = inb(pcwd_private.io_addr + 1);
+ }
+ spin_unlock(&pcwd_private.io_lock);
+
+ if (pcwd_private.revision == PCWD_REVISION_A) {
+ if (control_status & WD_WDRST)
+ *status |= WDIOF_CARDRESET;
+
+ if (control_status & WD_T110) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic) {
+ pr_info("Temperature overheat trip!\n");
+ kernel_power_off();
+ }
+ }
+ } else {
+ if (control_status & WD_REVC_WTRP)
+ *status |= WDIOF_CARDRESET;
+
+ if (control_status & WD_REVC_TTRP) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic) {
+ pr_info("Temperature overheat trip!\n");
+ kernel_power_off();
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int pcwd_clear_status(void)
+{
+ int control_status;
+
+ if (pcwd_private.revision == PCWD_REVISION_C) {
+ spin_lock(&pcwd_private.io_lock);
+
+ if (debug >= VERBOSE)
+ pr_info("clearing watchdog trip status\n");
+
+ control_status = inb_p(pcwd_private.io_addr + 1);
+
+ if (debug >= DEBUG) {
+ pr_debug("status was: 0x%02x\n", control_status);
+ pr_debug("sending: 0x%02x\n",
+ (control_status & WD_REVC_R2DS));
+ }
+
+ /* clear reset status & Keep Relay 2 disable state as it is */
+ outb_p((control_status & WD_REVC_R2DS),
+ pcwd_private.io_addr + 1);
+
+ spin_unlock(&pcwd_private.io_lock);
+ }
+ return 0;
+}
+
+static int pcwd_get_temperature(int *temperature)
+{
+ /* check that port 0 gives temperature info and no command results */
+ if (pcwd_private.command_mode)
+ return -1;
+
+ *temperature = 0;
+ if (!pcwd_private.supports_temp)
+ return -ENODEV;
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ spin_lock(&pcwd_private.io_lock);
+ *temperature = ((inb(pcwd_private.io_addr)) * 9 / 5) + 32;
+ spin_unlock(&pcwd_private.io_lock);
+
+ if (debug >= DEBUG) {
+ pr_debug("temperature is: %d F\n", *temperature);
+ }
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static long pcwd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int rv;
+ int status;
+ int temperature;
+ int new_heartbeat;
+ int __user *argp = (int __user *)arg;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_OVERHEAT |
+ WDIOF_CARDRESET |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "PCWD",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ pcwd_get_status(&status);
+ return put_user(status, argp);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(pcwd_private.boot_status, argp);
+
+ case WDIOC_GETTEMP:
+ if (pcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ return put_user(temperature, argp);
+
+ case WDIOC_SETOPTIONS:
+ if (pcwd_private.revision == PCWD_REVISION_C) {
+ if (get_user(rv, argp))
+ return -EFAULT;
+
+ if (rv & WDIOS_DISABLECARD) {
+ status = pcwd_stop();
+ if (status < 0)
+ return status;
+ }
+ if (rv & WDIOS_ENABLECARD) {
+ status = pcwd_start();
+ if (status < 0)
+ return status;
+ }
+ if (rv & WDIOS_TEMPPANIC)
+ temp_panic = 1;
+ }
+ return -EINVAL;
+
+ case WDIOC_KEEPALIVE:
+ pcwd_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, argp))
+ return -EFAULT;
+
+ if (pcwd_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ pcwd_keepalive();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, argp);
+
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ pcwd_keepalive();
+ }
+ return len;
+}
+
+static int pcwd_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &open_allowed))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+ /* Activate */
+ pcwd_start();
+ pcwd_keepalive();
+ return stream_open(inode, file);
+}
+
+static int pcwd_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42)
+ pcwd_stop();
+ else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ pcwd_keepalive();
+ }
+ expect_close = 0;
+ clear_bit(0, &open_allowed);
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int temperature;
+
+ if (pcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user(buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int pcwd_temp_open(struct inode *inode, struct file *file)
+{
+ if (!pcwd_private.supports_temp)
+ return -ENODEV;
+
+ return stream_open(inode, file);
+}
+
+static int pcwd_temp_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations pcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = pcwd_write,
+ .unlocked_ioctl = pcwd_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = pcwd_open,
+ .release = pcwd_close,
+};
+
+static struct miscdevice pcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pcwd_fops,
+};
+
+static const struct file_operations pcwd_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pcwd_temp_read,
+ .open = pcwd_temp_open,
+ .release = pcwd_temp_close,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &pcwd_temp_fops,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static inline int get_revision(void)
+{
+ int r = PCWD_REVISION_C;
+
+ spin_lock(&pcwd_private.io_lock);
+ /* REV A cards use only 2 io ports; test
+ * presumes a floating bus reads as 0xff. */
+ if ((inb(pcwd_private.io_addr + 2) == 0xFF) ||
+ (inb(pcwd_private.io_addr + 3) == 0xFF))
+ r = PCWD_REVISION_A;
+ spin_unlock(&pcwd_private.io_lock);
+
+ return r;
+}
+
+/*
+ * The ISA cards have a heartbeat bit in one of the registers, which
+ * register is card dependent. The heartbeat bit is monitored, and if
+ * found, is considered proof that a Berkshire card has been found.
+ * The initial rate is once per second at board start up, then twice
+ * per second for normal operation.
+ */
+static int pcwd_isa_match(struct device *dev, unsigned int id)
+{
+ int base_addr = pcwd_ioports[id];
+ int port0, last_port0; /* Reg 0, in case it's REV A */
+ int port1, last_port1; /* Register 1 for REV C cards */
+ int i;
+ int retval;
+
+ if (debug >= DEBUG)
+ pr_debug("pcwd_isa_match id=%d\n", id);
+
+ if (!request_region(base_addr, 4, "PCWD")) {
+ pr_info("Port 0x%04x unavailable\n", base_addr);
+ return 0;
+ }
+
+ retval = 0;
+
+ port0 = inb_p(base_addr); /* For REV A boards */
+ port1 = inb_p(base_addr + 1); /* For REV C boards */
+ if (port0 != 0xff || port1 != 0xff) {
+ /* Not an 'ff' from a floating bus, so must be a card! */
+ for (i = 0; i < 4; ++i) {
+
+ msleep(500);
+
+ last_port0 = port0;
+ last_port1 = port1;
+
+ port0 = inb_p(base_addr);
+ port1 = inb_p(base_addr + 1);
+
+ /* Has either hearbeat bit changed? */
+ if ((port0 ^ last_port0) & WD_HRTBT ||
+ (port1 ^ last_port1) & WD_REVC_HRBT) {
+ retval = 1;
+ break;
+ }
+ }
+ }
+ release_region(base_addr, 4);
+
+ return retval;
+}
+
+static int pcwd_isa_probe(struct device *dev, unsigned int id)
+{
+ int ret;
+
+ if (debug >= DEBUG)
+ pr_debug("pcwd_isa_probe id=%d\n", id);
+
+ cards_found++;
+ if (cards_found == 1)
+ pr_info("v%s Ken Hollis (kenji@bitgate.com)\n",
+ WATCHDOG_VERSION);
+
+ if (cards_found > 1) {
+ pr_err("This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ if (pcwd_ioports[id] == 0x0000) {
+ pr_err("No I/O-Address for card detected\n");
+ return -ENODEV;
+ }
+ pcwd_private.io_addr = pcwd_ioports[id];
+
+ spin_lock_init(&pcwd_private.io_lock);
+
+ /* Check card's revision */
+ pcwd_private.revision = get_revision();
+
+ if (!request_region(pcwd_private.io_addr,
+ (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) {
+ pr_err("I/O address 0x%04x already in use\n",
+ pcwd_private.io_addr);
+ ret = -EIO;
+ goto error_request_region;
+ }
+
+ /* Initial variables */
+ pcwd_private.supports_temp = 0;
+ temp_panic = 0;
+ pcwd_private.boot_status = 0x0000;
+
+ /* get the boot_status */
+ pcwd_get_status(&pcwd_private.boot_status);
+
+ /* clear the "card caused reboot" flag */
+ pcwd_clear_status();
+
+ timer_setup(&pcwd_private.timer, pcwd_timer_ping, 0);
+
+ /* Disable the board */
+ pcwd_stop();
+
+ /* Check whether or not the card supports the temperature device */
+ pcwd_check_temperature_support();
+
+ /* Show info about the card itself */
+ pcwd_show_card_info();
+
+ /* If heartbeat = 0 then we use the heartbeat from the dip-switches */
+ if (heartbeat == 0)
+ heartbeat = heartbeat_tbl[(pcwd_get_option_switches() & 0x07)];
+
+ /* Check that the heartbeat value is within it's range;
+ if not reset to the default */
+ if (pcwd_set_heartbeat(heartbeat)) {
+ pcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+ pr_info("heartbeat value must be 2 <= heartbeat <= 7200, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ if (pcwd_private.supports_temp) {
+ ret = misc_register(&temp_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto error_misc_register_temp;
+ }
+ }
+
+ ret = misc_register(&pcwd_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto error_misc_register_watchdog;
+ }
+
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+
+error_misc_register_watchdog:
+ if (pcwd_private.supports_temp)
+ misc_deregister(&temp_miscdev);
+error_misc_register_temp:
+ release_region(pcwd_private.io_addr,
+ (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4);
+error_request_region:
+ pcwd_private.io_addr = 0x0000;
+ cards_found--;
+ return ret;
+}
+
+static void pcwd_isa_remove(struct device *dev, unsigned int id)
+{
+ if (debug >= DEBUG)
+ pr_debug("pcwd_isa_remove id=%d\n", id);
+
+ /* Disable the board */
+ if (!nowayout)
+ pcwd_stop();
+
+ /* Deregister */
+ misc_deregister(&pcwd_miscdev);
+ if (pcwd_private.supports_temp)
+ misc_deregister(&temp_miscdev);
+ release_region(pcwd_private.io_addr,
+ (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4);
+ pcwd_private.io_addr = 0x0000;
+ cards_found--;
+}
+
+static void pcwd_isa_shutdown(struct device *dev, unsigned int id)
+{
+ if (debug >= DEBUG)
+ pr_debug("pcwd_isa_shutdown id=%d\n", id);
+
+ pcwd_stop();
+}
+
+static struct isa_driver pcwd_isa_driver = {
+ .match = pcwd_isa_match,
+ .probe = pcwd_isa_probe,
+ .remove = pcwd_isa_remove,
+ .shutdown = pcwd_isa_shutdown,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = WATCHDOG_NAME,
+ },
+};
+
+module_isa_driver(pcwd_isa_driver, PCWD_ISA_NR_CARDS);
+
+MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>, "
+ "Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver");
+MODULE_VERSION(WATCHDOG_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c
new file mode 100644
index 000000000..54d86fcb1
--- /dev/null
+++ b/drivers/watchdog/pcwd_pci.c
@@ -0,0 +1,819 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Berkshire PCI-PC Watchdog Card Driver
+ *
+ * (c) Copyright 2003-2007 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Based on source code of the following authors:
+ * Ken Hollis <kenji@bitgate.com>,
+ * Lindsay Harris <lindsay@bluegum.com>,
+ * Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ */
+
+/*
+ * A bells and whistles driver is available from:
+ * http://www.kernel.org/pub/linux/kernel/people/wim/pcwd/pcwd_pci/
+ *
+ * More info available at
+ * http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/delay.h> /* For mdelay function */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/notifier.h> /* For notifier support */
+#include <linux/reboot.h> /* For reboot_notifier stuff */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/pci.h> /* For pci functions */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For inb/outb/... */
+
+/* Module and version information */
+#define WATCHDOG_VERSION "1.03"
+#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog"
+#define WATCHDOG_NAME "pcwd_pci"
+#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION
+
+/* Stuff for the PCI ID's */
+#ifndef PCI_VENDOR_ID_QUICKLOGIC
+#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3
+#endif
+
+#ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD
+#define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030
+#endif
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PCI-PC Watchdog card.
+ */
+/* Port 1 : Control Status #1 */
+#define WD_PCI_WTRP 0x01 /* Watchdog Trip status */
+#define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */
+#define WD_PCI_TTRP 0x04 /* Temperature Trip status */
+#define WD_PCI_RL2A 0x08 /* Relay 2 Active */
+#define WD_PCI_RL1A 0x10 /* Relay 1 Active */
+#define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temperature-trip /
+ reset */
+#define WD_PCI_RLY2 0x80 /* Activate Relay 2 on the board */
+/* Port 2 : Control Status #2 */
+#define WD_PCI_WDIS 0x10 /* Watchdog Disable */
+#define WD_PCI_ENTP 0x20 /* Enable Temperature Trip Reset */
+#define WD_PCI_WRSP 0x40 /* Watchdog wrote response */
+#define WD_PCI_PCMD 0x80 /* PC has sent command */
+
+/* according to documentation max. time to process a command for the pci
+ * watchdog card is 100 ms, so we give it 150 ms to do it's job */
+#define PCI_COMMAND_TIMEOUT 150
+
+/* Watchdog's internal commands */
+#define CMD_GET_STATUS 0x04
+#define CMD_GET_FIRMWARE_VERSION 0x08
+#define CMD_READ_WATCHDOG_TIMEOUT 0x18
+#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19
+#define CMD_GET_CLEAR_RESET_COUNT 0x84
+
+/* Watchdog's Dip Switch heartbeat values */
+static const int heartbeat_tbl[] = {
+ 5, /* OFF-OFF-OFF = 5 Sec */
+ 10, /* OFF-OFF-ON = 10 Sec */
+ 30, /* OFF-ON-OFF = 30 Sec */
+ 60, /* OFF-ON-ON = 1 Min */
+ 300, /* ON-OFF-OFF = 5 Min */
+ 600, /* ON-OFF-ON = 10 Min */
+ 1800, /* ON-ON-OFF = 30 Min */
+ 3600, /* ON-ON-ON = 1 hour */
+};
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static int temp_panic;
+static unsigned long is_active;
+static char expect_release;
+/* this is private data for each PCI-PC watchdog card */
+static struct {
+ /* Wether or not the card has a temperature device */
+ int supports_temp;
+ /* The card's boot status */
+ int boot_status;
+ /* The cards I/O address */
+ unsigned long io_addr;
+ /* the lock for io operations */
+ spinlock_t io_lock;
+ /* the PCI-device */
+ struct pci_dev *pdev;
+} pcipcwd_private;
+
+/* module parameters */
+#define QUIET 0 /* Default */
+#define VERBOSE 1 /* Verbose */
+#define DEBUG 2 /* print fancy stuff too */
+static int debug = QUIET;
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)");
+
+#define WATCHDOG_HEARTBEAT 0 /* default heartbeat =
+ delay-time from dip-switches */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. "
+ "(0<heartbeat<65536 or 0=delay-time from dip-switches, default="
+ __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+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) ")");
+
+/*
+ * Internal functions
+ */
+
+static int send_command(int cmd, int *msb, int *lsb)
+{
+ int got_response, count;
+
+ if (debug >= DEBUG)
+ pr_debug("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x\n",
+ cmd, *msb, *lsb);
+
+ spin_lock(&pcipcwd_private.io_lock);
+ /* If a command requires data it should be written first.
+ * Data for commands with 8 bits of data should be written to port 4.
+ * Commands with 16 bits of data, should be written as LSB to port 4
+ * and MSB to port 5.
+ * After the required data has been written then write the command to
+ * port 6. */
+ outb_p(*lsb, pcipcwd_private.io_addr + 4);
+ outb_p(*msb, pcipcwd_private.io_addr + 5);
+ outb_p(cmd, pcipcwd_private.io_addr + 6);
+
+ /* wait till the pci card processed the command, signaled by
+ * the WRSP bit in port 2 and give it a max. timeout of
+ * PCI_COMMAND_TIMEOUT to process */
+ got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP;
+ for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response);
+ count++) {
+ mdelay(1);
+ got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP;
+ }
+
+ if (debug >= DEBUG) {
+ if (got_response) {
+ pr_debug("time to process command was: %d ms\n",
+ count);
+ } else {
+ pr_debug("card did not respond on command!\n");
+ }
+ }
+
+ if (got_response) {
+ /* read back response */
+ *lsb = inb_p(pcipcwd_private.io_addr + 4);
+ *msb = inb_p(pcipcwd_private.io_addr + 5);
+
+ /* clear WRSP bit */
+ inb_p(pcipcwd_private.io_addr + 6);
+
+ if (debug >= DEBUG)
+ pr_debug("received following data for cmd=0x%02x: msb=0x%02x lsb=0x%02x\n",
+ cmd, *msb, *lsb);
+ }
+
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ return got_response;
+}
+
+static inline void pcipcwd_check_temperature_support(void)
+{
+ if (inb_p(pcipcwd_private.io_addr) != 0xF0)
+ pcipcwd_private.supports_temp = 1;
+}
+
+static int pcipcwd_get_option_switches(void)
+{
+ int option_switches;
+
+ option_switches = inb_p(pcipcwd_private.io_addr + 3);
+ return option_switches;
+}
+
+static void pcipcwd_show_card_info(void)
+{
+ int got_fw_rev, fw_rev_major, fw_rev_minor;
+ char fw_ver_str[20]; /* The cards firmware version */
+ int option_switches;
+
+ got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major,
+ &fw_rev_minor);
+ if (got_fw_rev)
+ sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+ else
+ sprintf(fw_ver_str, "<card no answer>");
+
+ /* Get switch settings */
+ option_switches = pcipcwd_get_option_switches();
+
+ pr_info("Found card at port 0x%04x (Firmware: %s) %s temp option\n",
+ (int) pcipcwd_private.io_addr, fw_ver_str,
+ (pcipcwd_private.supports_temp ? "with" : "without"));
+
+ pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ if (pcipcwd_private.boot_status & WDIOF_CARDRESET)
+ pr_info("Previous reset was caused by the Watchdog card\n");
+
+ if (pcipcwd_private.boot_status & WDIOF_OVERHEAT)
+ pr_info("Card sensed a CPU Overheat\n");
+
+ if (pcipcwd_private.boot_status == 0)
+ pr_info("No previous trip detected - Cold boot or reset\n");
+}
+
+static int pcipcwd_start(void)
+{
+ int stat_reg;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ outb_p(0x00, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ if (stat_reg & WD_PCI_WDIS) {
+ pr_err("Card timer not enabled\n");
+ return -1;
+ }
+
+ if (debug >= VERBOSE)
+ pr_debug("Watchdog started\n");
+
+ return 0;
+}
+
+static int pcipcwd_stop(void)
+{
+ int stat_reg;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ outb_p(0xA5, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ outb_p(0xA5, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ if (!(stat_reg & WD_PCI_WDIS)) {
+ pr_err("Card did not acknowledge disable attempt\n");
+ return -1;
+ }
+
+ if (debug >= VERBOSE)
+ pr_debug("Watchdog stopped\n");
+
+ return 0;
+}
+
+static int pcipcwd_keepalive(void)
+{
+ /* Re-trigger watchdog by writing to port 0 */
+ spin_lock(&pcipcwd_private.io_lock);
+ outb_p(0x42, pcipcwd_private.io_addr); /* send out any data */
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ if (debug >= DEBUG)
+ pr_debug("Watchdog keepalive signal send\n");
+
+ return 0;
+}
+
+static int pcipcwd_set_heartbeat(int t)
+{
+ int t_msb = t / 256;
+ int t_lsb = t % 256;
+
+ if ((t < 0x0001) || (t > 0xFFFF))
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb);
+
+ heartbeat = t;
+ if (debug >= VERBOSE)
+ pr_debug("New heartbeat: %d\n", heartbeat);
+
+ return 0;
+}
+
+static int pcipcwd_get_status(int *status)
+{
+ int control_status;
+
+ *status = 0;
+ control_status = inb_p(pcipcwd_private.io_addr + 1);
+ if (control_status & WD_PCI_WTRP)
+ *status |= WDIOF_CARDRESET;
+ if (control_status & WD_PCI_TTRP) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic)
+ panic(KBUILD_MODNAME ": Temperature overheat trip!\n");
+ }
+
+ if (debug >= DEBUG)
+ pr_debug("Control Status #1: 0x%02x\n", control_status);
+
+ return 0;
+}
+
+static int pcipcwd_clear_status(void)
+{
+ int control_status;
+ int msb;
+ int reset_counter;
+
+ if (debug >= VERBOSE)
+ pr_info("clearing watchdog trip status & LED\n");
+
+ control_status = inb_p(pcipcwd_private.io_addr + 1);
+
+ if (debug >= DEBUG) {
+ pr_debug("status was: 0x%02x\n", control_status);
+ pr_debug("sending: 0x%02x\n",
+ (control_status & WD_PCI_R2DS) | WD_PCI_WTRP);
+ }
+
+ /* clear trip status & LED and keep mode of relay 2 */
+ outb_p((control_status & WD_PCI_R2DS) | WD_PCI_WTRP,
+ pcipcwd_private.io_addr + 1);
+
+ /* clear reset counter */
+ msb = 0;
+ reset_counter = 0xff;
+ send_command(CMD_GET_CLEAR_RESET_COUNT, &msb, &reset_counter);
+
+ if (debug >= DEBUG) {
+ pr_debug("reset count was: 0x%02x\n", reset_counter);
+ }
+
+ return 0;
+}
+
+static int pcipcwd_get_temperature(int *temperature)
+{
+ *temperature = 0;
+ if (!pcipcwd_private.supports_temp)
+ return -ENODEV;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ *temperature = inb_p(pcipcwd_private.io_addr);
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ *temperature = (*temperature * 9 / 5) + 32;
+
+ if (debug >= DEBUG) {
+ pr_debug("temperature is: %d F\n", *temperature);
+ }
+
+ return 0;
+}
+
+static int pcipcwd_get_timeleft(int *time_left)
+{
+ int msb;
+ int lsb;
+
+ /* Read the time that's left before rebooting */
+ /* Note: if the board is not yet armed then we will read 0xFFFF */
+ send_command(CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb);
+
+ *time_left = (msb << 8) + lsb;
+
+ if (debug >= VERBOSE)
+ pr_debug("Time left before next reboot: %d\n", *time_left);
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t pcipcwd_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ expect_release = 0;
+
+ /* scan to see whether or not we got the
+ * magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ pcipcwd_keepalive();
+ }
+ return len;
+}
+
+static long pcipcwd_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_OVERHEAT |
+ WDIOF_CARDRESET |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = WATCHDOG_DRIVER_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ {
+ int status;
+ pcipcwd_get_status(&status);
+ return put_user(status, p);
+ }
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(pcipcwd_private.boot_status, p);
+
+ case WDIOC_GETTEMP:
+ {
+ int temperature;
+
+ if (pcipcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ return put_user(temperature, p);
+ }
+
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if (get_user(new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ if (pcipcwd_stop())
+ return -EIO;
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ if (pcipcwd_start())
+ return -EIO;
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_TEMPPANIC) {
+ temp_panic = 1;
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_KEEPALIVE:
+ pcipcwd_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_heartbeat;
+
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (pcipcwd_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ pcipcwd_keepalive();
+ }
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+
+ case WDIOC_GETTIMELEFT:
+ {
+ int time_left;
+
+ if (pcipcwd_get_timeleft(&time_left))
+ return -EFAULT;
+
+ return put_user(time_left, p);
+ }
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int pcipcwd_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &is_active)) {
+ if (debug >= VERBOSE)
+ pr_err("Attempt to open already opened device\n");
+ return -EBUSY;
+ }
+
+ /* Activate */
+ pcipcwd_start();
+ pcipcwd_keepalive();
+ return stream_open(inode, file);
+}
+
+static int pcipcwd_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (expect_release == 42) {
+ pcipcwd_stop();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ pcipcwd_keepalive();
+ }
+ expect_release = 0;
+ clear_bit(0, &is_active);
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t pcipcwd_temp_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int temperature;
+
+ if (pcipcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user(data, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int pcipcwd_temp_open(struct inode *inode, struct file *file)
+{
+ if (!pcipcwd_private.supports_temp)
+ return -ENODEV;
+
+ return stream_open(inode, file);
+}
+
+static int pcipcwd_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Notify system
+ */
+
+static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ pcipcwd_stop(); /* Turn the WDT off */
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations pcipcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = pcipcwd_write,
+ .unlocked_ioctl = pcipcwd_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = pcipcwd_open,
+ .release = pcipcwd_release,
+};
+
+static struct miscdevice pcipcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pcipcwd_fops,
+};
+
+static const struct file_operations pcipcwd_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pcipcwd_temp_read,
+ .open = pcipcwd_temp_open,
+ .release = pcipcwd_temp_release,
+};
+
+static struct miscdevice pcipcwd_temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &pcipcwd_temp_fops,
+};
+
+static struct notifier_block pcipcwd_notifier = {
+ .notifier_call = pcipcwd_notify_sys,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int pcipcwd_card_init(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int ret = -EIO;
+
+ cards_found++;
+ if (cards_found == 1)
+ pr_info("%s\n", DRIVER_VERSION);
+
+ if (cards_found > 1) {
+ pr_err("This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ if (pci_enable_device(pdev)) {
+ pr_err("Not possible to enable PCI Device\n");
+ return -ENODEV;
+ }
+
+ if (pci_resource_start(pdev, 0) == 0x0000) {
+ pr_err("No I/O-Address for card detected\n");
+ ret = -ENODEV;
+ goto err_out_disable_device;
+ }
+
+ spin_lock_init(&pcipcwd_private.io_lock);
+ pcipcwd_private.pdev = pdev;
+ pcipcwd_private.io_addr = pci_resource_start(pdev, 0);
+
+ if (pci_request_regions(pdev, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n",
+ (int) pcipcwd_private.io_addr);
+ ret = -EIO;
+ goto err_out_disable_device;
+ }
+
+ /* get the boot_status */
+ pcipcwd_get_status(&pcipcwd_private.boot_status);
+
+ /* clear the "card caused reboot" flag */
+ pcipcwd_clear_status();
+
+ /* disable card */
+ pcipcwd_stop();
+
+ /* Check whether or not the card supports the temperature device */
+ pcipcwd_check_temperature_support();
+
+ /* Show info about the card itself */
+ pcipcwd_show_card_info();
+
+ /* If heartbeat = 0 then we use the heartbeat from the dip-switches */
+ if (heartbeat == 0)
+ heartbeat =
+ heartbeat_tbl[(pcipcwd_get_option_switches() & 0x07)];
+
+ /* Check that the heartbeat value is within it's range ;
+ * if not reset to the default */
+ if (pcipcwd_set_heartbeat(heartbeat)) {
+ pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+ pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ ret = register_reboot_notifier(&pcipcwd_notifier);
+ if (ret != 0) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto err_out_release_region;
+ }
+
+ if (pcipcwd_private.supports_temp) {
+ ret = misc_register(&pcipcwd_temp_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto err_out_unregister_reboot;
+ }
+ }
+
+ ret = misc_register(&pcipcwd_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto err_out_misc_deregister;
+ }
+
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+
+err_out_misc_deregister:
+ if (pcipcwd_private.supports_temp)
+ misc_deregister(&pcipcwd_temp_miscdev);
+err_out_unregister_reboot:
+ unregister_reboot_notifier(&pcipcwd_notifier);
+err_out_release_region:
+ pci_release_regions(pdev);
+err_out_disable_device:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void pcipcwd_card_exit(struct pci_dev *pdev)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ pcipcwd_stop();
+
+ /* Deregister */
+ misc_deregister(&pcipcwd_miscdev);
+ if (pcipcwd_private.supports_temp)
+ misc_deregister(&pcipcwd_temp_miscdev);
+ unregister_reboot_notifier(&pcipcwd_notifier);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ cards_found--;
+}
+
+static const struct pci_device_id pcipcwd_pci_tbl[] = {
+ { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0 }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl);
+
+static struct pci_driver pcipcwd_driver = {
+ .name = WATCHDOG_NAME,
+ .id_table = pcipcwd_pci_tbl,
+ .probe = pcipcwd_card_init,
+ .remove = pcipcwd_card_exit,
+};
+
+module_pci_driver(pcipcwd_driver);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c
new file mode 100644
index 000000000..8202f0a6b
--- /dev/null
+++ b/drivers/watchdog/pcwd_usb.c
@@ -0,0 +1,807 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Berkshire USB-PC Watchdog Card Driver
+ *
+ * (c) Copyright 2004-2007 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Based on source code of the following authors:
+ * Ken Hollis <kenji@bitgate.com>,
+ * Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ * Thanks also to Simon Machell at Berkshire Products Inc. for
+ * providing the test hardware. More info is available at
+ * http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/delay.h> /* For mdelay function */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/notifier.h> /* For notifier support */
+#include <linux/reboot.h> /* For reboot_notifier stuff */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/usb.h> /* For USB functions */
+#include <linux/slab.h> /* For kmalloc, ... */
+#include <linux/mutex.h> /* For mutex locking */
+#include <linux/hid.h> /* For HID_REQ_SET_REPORT & HID_DT_REPORT */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+
+
+/* Module and Version Information */
+#define DRIVER_VERSION "1.02"
+#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
+#define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
+#define DRIVER_NAME "pcwd_usb"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define WATCHDOG_HEARTBEAT 0 /* default heartbeat =
+ delay-time from dip-switches */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. "
+ "(0<heartbeat<65536 or 0=delay-time from dip-switches, default="
+ __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+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) ")");
+
+/* The vendor and product id's for the USB-PC Watchdog card */
+#define USB_PCWD_VENDOR_ID 0x0c98
+#define USB_PCWD_PRODUCT_ID 0x1140
+
+/* table of devices that work with this driver */
+static const struct usb_device_id usb_pcwd_table[] = {
+ { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, usb_pcwd_table);
+
+/* according to documentation max. time to process a command for the USB
+ * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
+#define USB_COMMAND_TIMEOUT 250
+
+/* Watchdog's internal commands */
+#define CMD_READ_TEMP 0x02 /* Read Temperature;
+ Re-trigger Watchdog */
+#define CMD_TRIGGER CMD_READ_TEMP
+#define CMD_GET_STATUS 0x04 /* Get Status Information */
+#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */
+#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */
+#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */
+#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current WatchdogTime */
+#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */
+#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG
+
+/* Watchdog's Dip Switch heartbeat values */
+static const int heartbeat_tbl[] = {
+ 5, /* OFF-OFF-OFF = 5 Sec */
+ 10, /* OFF-OFF-ON = 10 Sec */
+ 30, /* OFF-ON-OFF = 30 Sec */
+ 60, /* OFF-ON-ON = 1 Min */
+ 300, /* ON-OFF-OFF = 5 Min */
+ 600, /* ON-OFF-ON = 10 Min */
+ 1800, /* ON-ON-OFF = 30 Min */
+ 3600, /* ON-ON-ON = 1 hour */
+};
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* some internal variables */
+static unsigned long is_active;
+static char expect_release;
+
+/* Structure to hold all of our device specific stuff */
+struct usb_pcwd_private {
+ /* save off the usb device pointer */
+ struct usb_device *udev;
+ /* the interface for this device */
+ struct usb_interface *interface;
+
+ /* the interface number used for cmd's */
+ unsigned int interface_number;
+
+ /* the buffer to intr data */
+ unsigned char *intr_buffer;
+ /* the dma address for the intr buffer */
+ dma_addr_t intr_dma;
+ /* the size of the intr buffer */
+ size_t intr_size;
+ /* the urb used for the intr pipe */
+ struct urb *intr_urb;
+
+ /* The command that is reported back */
+ unsigned char cmd_command;
+ /* The data MSB that is reported back */
+ unsigned char cmd_data_msb;
+ /* The data LSB that is reported back */
+ unsigned char cmd_data_lsb;
+ /* true if we received a report after a command */
+ atomic_t cmd_received;
+
+ /* Wether or not the device exists */
+ int exists;
+ /* locks this structure */
+ struct mutex mtx;
+};
+static struct usb_pcwd_private *usb_pcwd_device;
+
+/* prevent races between open() and disconnect() */
+static DEFINE_MUTEX(disconnect_mutex);
+
+/* local function prototypes */
+static int usb_pcwd_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+static void usb_pcwd_disconnect(struct usb_interface *interface);
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver usb_pcwd_driver = {
+ .name = DRIVER_NAME,
+ .probe = usb_pcwd_probe,
+ .disconnect = usb_pcwd_disconnect,
+ .id_table = usb_pcwd_table,
+};
+
+
+static void usb_pcwd_intr_done(struct urb *urb)
+{
+ struct usb_pcwd_private *usb_pcwd =
+ (struct usb_pcwd_private *)urb->context;
+ unsigned char *data = usb_pcwd->intr_buffer;
+ struct device *dev = &usb_pcwd->interface->dev;
+ int retval;
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ECONNRESET: /* unlink */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d",
+ __func__, urb->status);
+ return;
+ /* -EPIPE: should clear the halt */
+ default: /* error */
+ dev_dbg(dev, "%s - nonzero urb status received: %d",
+ __func__, urb->status);
+ goto resubmit;
+ }
+
+ dev_dbg(dev, "received following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+ data[0], data[1], data[2]);
+
+ usb_pcwd->cmd_command = data[0];
+ usb_pcwd->cmd_data_msb = data[1];
+ usb_pcwd->cmd_data_lsb = data[2];
+
+ /* notify anyone waiting that the cmd has finished */
+ atomic_set(&usb_pcwd->cmd_received, 1);
+
+resubmit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ pr_err("can't resubmit intr, usb_submit_urb failed with result %d\n",
+ retval);
+}
+
+static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd,
+ unsigned char cmd, unsigned char *msb, unsigned char *lsb)
+{
+ int got_response, count;
+ unsigned char *buf;
+
+ /* We will not send any commands if the USB PCWD device does
+ * not exist */
+ if ((!usb_pcwd) || (!usb_pcwd->exists))
+ return -1;
+
+ buf = kmalloc(6, GFP_KERNEL);
+ if (buf == NULL)
+ return 0;
+
+ /* The USB PC Watchdog uses a 6 byte report format.
+ * The board currently uses only 3 of the six bytes of the report. */
+ buf[0] = cmd; /* Byte 0 = CMD */
+ buf[1] = *msb; /* Byte 1 = Data MSB */
+ buf[2] = *lsb; /* Byte 2 = Data LSB */
+ buf[3] = buf[4] = buf[5] = 0; /* All other bytes not used */
+
+ dev_dbg(&usb_pcwd->interface->dev,
+ "sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+ buf[0], buf[1], buf[2]);
+
+ atomic_set(&usb_pcwd->cmd_received, 0);
+
+ if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0),
+ HID_REQ_SET_REPORT, HID_DT_REPORT,
+ 0x0200, usb_pcwd->interface_number, buf, 6,
+ USB_COMMAND_TIMEOUT) != 6) {
+ dev_dbg(&usb_pcwd->interface->dev,
+ "usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n",
+ cmd, *msb, *lsb);
+ }
+ /* wait till the usb card processed the command,
+ * with a max. timeout of USB_COMMAND_TIMEOUT */
+ got_response = 0;
+ for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response);
+ count++) {
+ mdelay(1);
+ if (atomic_read(&usb_pcwd->cmd_received))
+ got_response = 1;
+ }
+
+ if ((got_response) && (cmd == usb_pcwd->cmd_command)) {
+ /* read back response */
+ *msb = usb_pcwd->cmd_data_msb;
+ *lsb = usb_pcwd->cmd_data_lsb;
+ }
+
+ kfree(buf);
+
+ return got_response;
+}
+
+static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char msb = 0x00;
+ unsigned char lsb = 0x00;
+ int retval;
+
+ /* Enable Watchdog */
+ retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG,
+ &msb, &lsb);
+
+ if ((retval == 0) || (lsb == 0)) {
+ pr_err("Card did not acknowledge enable attempt\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char msb = 0xA5;
+ unsigned char lsb = 0xC3;
+ int retval;
+
+ /* Disable Watchdog */
+ retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG,
+ &msb, &lsb);
+
+ if ((retval == 0) || (lsb != 0)) {
+ pr_err("Card did not acknowledge disable attempt\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char dummy;
+
+ /* Re-trigger Watchdog */
+ usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy);
+
+ return 0;
+}
+
+static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t)
+{
+ unsigned char msb = t / 256;
+ unsigned char lsb = t % 256;
+
+ if ((t < 0x0001) || (t > 0xFFFF))
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb);
+
+ heartbeat = t;
+ return 0;
+}
+
+static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd,
+ int *temperature)
+{
+ unsigned char msb = 0x00;
+ unsigned char lsb = 0x00;
+
+ usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb);
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ *temperature = (lsb * 9 / 5) + 32;
+
+ return 0;
+}
+
+static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd,
+ int *time_left)
+{
+ unsigned char msb = 0x00;
+ unsigned char lsb = 0x00;
+
+ /* Read the time that's left before rebooting */
+ /* Note: if the board is not yet armed then we will read 0xFFFF */
+ usb_pcwd_send_command(usb_pcwd, CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb);
+
+ *time_left = (msb << 8) + lsb;
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t usb_pcwd_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ expect_release = 0;
+
+ /* scan to see whether or not we got the
+ * magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ usb_pcwd_keepalive(usb_pcwd_device);
+ }
+ return len;
+}
+
+static long usb_pcwd_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 = DRIVER_NAME,
+ };
+
+ 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_GETTEMP:
+ {
+ int temperature;
+
+ if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+ return -EFAULT;
+
+ return put_user(temperature, p);
+ }
+
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if (get_user(new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ usb_pcwd_stop(usb_pcwd_device);
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ usb_pcwd_start(usb_pcwd_device);
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_KEEPALIVE:
+ usb_pcwd_keepalive(usb_pcwd_device);
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_heartbeat;
+
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat))
+ return -EINVAL;
+
+ usb_pcwd_keepalive(usb_pcwd_device);
+ }
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+
+ case WDIOC_GETTIMELEFT:
+ {
+ int time_left;
+
+ if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left))
+ return -EFAULT;
+
+ return put_user(time_left, p);
+ }
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int usb_pcwd_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &is_active))
+ return -EBUSY;
+
+ /* Activate */
+ usb_pcwd_start(usb_pcwd_device);
+ usb_pcwd_keepalive(usb_pcwd_device);
+ return stream_open(inode, file);
+}
+
+static int usb_pcwd_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (expect_release == 42) {
+ usb_pcwd_stop(usb_pcwd_device);
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ usb_pcwd_keepalive(usb_pcwd_device);
+ }
+ expect_release = 0;
+ clear_bit(0, &is_active);
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int temperature;
+
+ if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+ return -EFAULT;
+
+ if (copy_to_user(data, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int usb_pcwd_temperature_open(struct inode *inode, struct file *file)
+{
+ return stream_open(inode, file);
+}
+
+static int usb_pcwd_temperature_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Notify system
+ */
+
+static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ usb_pcwd_stop(usb_pcwd_device); /* Turn the WDT off */
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations usb_pcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = usb_pcwd_write,
+ .unlocked_ioctl = usb_pcwd_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = usb_pcwd_open,
+ .release = usb_pcwd_release,
+};
+
+static struct miscdevice usb_pcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &usb_pcwd_fops,
+};
+
+static const struct file_operations usb_pcwd_temperature_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = usb_pcwd_temperature_read,
+ .open = usb_pcwd_temperature_open,
+ .release = usb_pcwd_temperature_release,
+};
+
+static struct miscdevice usb_pcwd_temperature_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &usb_pcwd_temperature_fops,
+};
+
+static struct notifier_block usb_pcwd_notifier = {
+ .notifier_call = usb_pcwd_notify_sys,
+};
+
+/**
+ * usb_pcwd_delete
+ */
+static inline void usb_pcwd_delete(struct usb_pcwd_private *usb_pcwd)
+{
+ usb_free_urb(usb_pcwd->intr_urb);
+ usb_free_coherent(usb_pcwd->udev, usb_pcwd->intr_size,
+ usb_pcwd->intr_buffer, usb_pcwd->intr_dma);
+ kfree(usb_pcwd);
+}
+
+/**
+ * usb_pcwd_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int usb_pcwd_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_pcwd_private *usb_pcwd = NULL;
+ int pipe;
+ int retval = -ENOMEM;
+ int got_fw_rev;
+ unsigned char fw_rev_major, fw_rev_minor;
+ char fw_ver_str[20];
+ unsigned char option_switches, dummy;
+
+ cards_found++;
+ if (cards_found > 1) {
+ pr_err("This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ /* get the active interface descriptor */
+ iface_desc = interface->cur_altsetting;
+
+ /* check out that we have a HID device */
+ if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) {
+ pr_err("The device isn't a Human Interface Device\n");
+ return -ENODEV;
+ }
+
+ if (iface_desc->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ /* check out the endpoint: it has to be Interrupt & IN */
+ endpoint = &iface_desc->endpoint[0].desc;
+
+ if (!usb_endpoint_is_int_in(endpoint)) {
+ /* we didn't find a Interrupt endpoint with direction IN */
+ pr_err("Couldn't find an INTR & IN endpoint\n");
+ return -ENODEV;
+ }
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+
+ /* allocate memory for our device and initialize it */
+ usb_pcwd = kzalloc(sizeof(struct usb_pcwd_private), GFP_KERNEL);
+ if (usb_pcwd == NULL)
+ goto error;
+
+ usb_pcwd_device = usb_pcwd;
+
+ mutex_init(&usb_pcwd->mtx);
+ usb_pcwd->udev = udev;
+ usb_pcwd->interface = interface;
+ usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber;
+ usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ?
+ le16_to_cpu(endpoint->wMaxPacketSize) : 8);
+
+ /* set up the memory buffer's */
+ usb_pcwd->intr_buffer = usb_alloc_coherent(udev, usb_pcwd->intr_size,
+ GFP_KERNEL, &usb_pcwd->intr_dma);
+ if (!usb_pcwd->intr_buffer) {
+ pr_err("Out of memory\n");
+ goto error;
+ }
+
+ /* allocate the urb's */
+ usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usb_pcwd->intr_urb)
+ goto error;
+
+ /* initialise the intr urb's */
+ usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe,
+ usb_pcwd->intr_buffer, usb_pcwd->intr_size,
+ usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval);
+ usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma;
+ usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ /* register our interrupt URB with the USB system */
+ if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) {
+ pr_err("Problem registering interrupt URB\n");
+ retval = -EIO; /* failure */
+ goto error;
+ }
+
+ /* The device exists and can be communicated with */
+ usb_pcwd->exists = 1;
+
+ /* disable card */
+ usb_pcwd_stop(usb_pcwd);
+
+ /* Get the Firmware Version */
+ got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION,
+ &fw_rev_major, &fw_rev_minor);
+ if (got_fw_rev)
+ sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+ else
+ sprintf(fw_ver_str, "<card no answer>");
+
+ pr_info("Found card (Firmware: %s) with temp option\n", fw_ver_str);
+
+ /* Get switch settings */
+ usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy,
+ &option_switches);
+
+ pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ /* If heartbeat = 0 then we use the heartbeat from the dip-switches */
+ if (heartbeat == 0)
+ heartbeat = heartbeat_tbl[(option_switches & 0x07)];
+
+ /* Check that the heartbeat value is within it's range ;
+ * if not reset to the default */
+ if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) {
+ usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT);
+ pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ retval = register_reboot_notifier(&usb_pcwd_notifier);
+ if (retval != 0) {
+ pr_err("cannot register reboot notifier (err=%d)\n", retval);
+ goto error;
+ }
+
+ retval = misc_register(&usb_pcwd_temperature_miscdev);
+ if (retval != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, retval);
+ goto err_out_unregister_reboot;
+ }
+
+ retval = misc_register(&usb_pcwd_miscdev);
+ if (retval != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, retval);
+ goto err_out_misc_deregister;
+ }
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, usb_pcwd);
+
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+
+err_out_misc_deregister:
+ misc_deregister(&usb_pcwd_temperature_miscdev);
+err_out_unregister_reboot:
+ unregister_reboot_notifier(&usb_pcwd_notifier);
+error:
+ if (usb_pcwd)
+ usb_pcwd_delete(usb_pcwd);
+ usb_pcwd_device = NULL;
+ return retval;
+}
+
+
+/**
+ * usb_pcwd_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ *
+ * This routine guarantees that the driver will not submit any more urbs
+ * by clearing dev->udev.
+ */
+static void usb_pcwd_disconnect(struct usb_interface *interface)
+{
+ struct usb_pcwd_private *usb_pcwd;
+
+ /* prevent races with open() */
+ mutex_lock(&disconnect_mutex);
+
+ usb_pcwd = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ mutex_lock(&usb_pcwd->mtx);
+
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ usb_pcwd_stop(usb_pcwd);
+
+ /* We should now stop communicating with the USB PCWD device */
+ usb_pcwd->exists = 0;
+
+ /* Deregister */
+ misc_deregister(&usb_pcwd_miscdev);
+ misc_deregister(&usb_pcwd_temperature_miscdev);
+ unregister_reboot_notifier(&usb_pcwd_notifier);
+
+ mutex_unlock(&usb_pcwd->mtx);
+
+ /* Delete the USB PCWD device */
+ usb_pcwd_delete(usb_pcwd);
+
+ cards_found--;
+
+ mutex_unlock(&disconnect_mutex);
+
+ pr_info("USB PC Watchdog disconnected\n");
+}
+
+module_usb_driver(usb_pcwd_driver);
diff --git a/drivers/watchdog/pic32-dmt.c b/drivers/watchdog/pic32-dmt.c
new file mode 100644
index 000000000..f43062b3c
--- /dev/null
+++ b/drivers/watchdog/pic32-dmt.c
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PIC32 deadman timer driver
+ *
+ * Purna Chandra Mandal <purna.mandal@microchip.com>
+ * Copyright (c) 2016, Microchip Technology Inc.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#include <asm/mach-pic32/pic32.h>
+
+/* Deadman Timer Regs */
+#define DMTCON_REG 0x00
+#define DMTPRECLR_REG 0x10
+#define DMTCLR_REG 0x20
+#define DMTSTAT_REG 0x30
+#define DMTCNT_REG 0x40
+#define DMTPSCNT_REG 0x60
+#define DMTPSINTV_REG 0x70
+
+/* Deadman Timer Regs fields */
+#define DMT_ON BIT(15)
+#define DMT_STEP1_KEY BIT(6)
+#define DMT_STEP2_KEY BIT(3)
+#define DMTSTAT_WINOPN BIT(0)
+#define DMTSTAT_EVENT BIT(5)
+#define DMTSTAT_BAD2 BIT(6)
+#define DMTSTAT_BAD1 BIT(7)
+
+/* Reset Control Register fields for watchdog */
+#define RESETCON_DMT_TIMEOUT BIT(5)
+
+struct pic32_dmt {
+ void __iomem *regs;
+ struct clk *clk;
+};
+
+static inline void dmt_enable(struct pic32_dmt *dmt)
+{
+ writel(DMT_ON, PIC32_SET(dmt->regs + DMTCON_REG));
+}
+
+static inline void dmt_disable(struct pic32_dmt *dmt)
+{
+ writel(DMT_ON, PIC32_CLR(dmt->regs + DMTCON_REG));
+ /*
+ * Cannot touch registers in the CPU cycle following clearing the
+ * ON bit.
+ */
+ nop();
+}
+
+static inline int dmt_bad_status(struct pic32_dmt *dmt)
+{
+ u32 val;
+
+ val = readl(dmt->regs + DMTSTAT_REG);
+ val &= (DMTSTAT_BAD1 | DMTSTAT_BAD2 | DMTSTAT_EVENT);
+ if (val)
+ return -EAGAIN;
+
+ return 0;
+}
+
+static inline int dmt_keepalive(struct pic32_dmt *dmt)
+{
+ u32 v;
+ u32 timeout = 500;
+
+ /* set pre-clear key */
+ writel(DMT_STEP1_KEY << 8, dmt->regs + DMTPRECLR_REG);
+
+ /* wait for DMT window to open */
+ while (--timeout) {
+ v = readl(dmt->regs + DMTSTAT_REG) & DMTSTAT_WINOPN;
+ if (v == DMTSTAT_WINOPN)
+ break;
+ }
+
+ /* apply key2 */
+ writel(DMT_STEP2_KEY, dmt->regs + DMTCLR_REG);
+
+ /* check whether keys are latched correctly */
+ return dmt_bad_status(dmt);
+}
+
+static inline u32 pic32_dmt_get_timeout_secs(struct pic32_dmt *dmt)
+{
+ unsigned long rate;
+
+ rate = clk_get_rate(dmt->clk);
+ if (rate)
+ return readl(dmt->regs + DMTPSCNT_REG) / rate;
+
+ return 0;
+}
+
+static inline u32 pic32_dmt_bootstatus(struct pic32_dmt *dmt)
+{
+ u32 v;
+ void __iomem *rst_base;
+
+ rst_base = ioremap(PIC32_BASE_RESET, 0x10);
+ if (!rst_base)
+ return 0;
+
+ v = readl(rst_base);
+
+ writel(RESETCON_DMT_TIMEOUT, PIC32_CLR(rst_base));
+
+ iounmap(rst_base);
+ return v & RESETCON_DMT_TIMEOUT;
+}
+
+static int pic32_dmt_start(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ dmt_enable(dmt);
+ return dmt_keepalive(dmt);
+}
+
+static int pic32_dmt_stop(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ dmt_disable(dmt);
+
+ return 0;
+}
+
+static int pic32_dmt_ping(struct watchdog_device *wdd)
+{
+ struct pic32_dmt *dmt = watchdog_get_drvdata(wdd);
+
+ return dmt_keepalive(dmt);
+}
+
+static const struct watchdog_ops pic32_dmt_fops = {
+ .owner = THIS_MODULE,
+ .start = pic32_dmt_start,
+ .stop = pic32_dmt_stop,
+ .ping = pic32_dmt_ping,
+};
+
+static const struct watchdog_info pic32_dmt_ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .identity = "PIC32 Deadman Timer",
+};
+
+static struct watchdog_device pic32_dmt_wdd = {
+ .info = &pic32_dmt_ident,
+ .ops = &pic32_dmt_fops,
+};
+
+static void pic32_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int pic32_dmt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct pic32_dmt *dmt;
+ struct watchdog_device *wdd = &pic32_dmt_wdd;
+
+ dmt = devm_kzalloc(dev, sizeof(*dmt), GFP_KERNEL);
+ if (!dmt)
+ return -ENOMEM;
+
+ dmt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dmt->regs))
+ return PTR_ERR(dmt->regs);
+
+ dmt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(dmt->clk)) {
+ dev_err(dev, "clk not found\n");
+ return PTR_ERR(dmt->clk);
+ }
+
+ ret = clk_prepare_enable(dmt->clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, pic32_clk_disable_unprepare,
+ dmt->clk);
+ if (ret)
+ return ret;
+
+ wdd->timeout = pic32_dmt_get_timeout_secs(dmt);
+ if (!wdd->timeout) {
+ dev_err(dev, "failed to read watchdog register timeout\n");
+ return -EINVAL;
+ }
+
+ dev_info(dev, "timeout %d\n", wdd->timeout);
+
+ wdd->bootstatus = pic32_dmt_bootstatus(dmt) ? WDIOF_CARDRESET : 0;
+
+ watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
+ watchdog_set_drvdata(wdd, dmt);
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdd);
+ return 0;
+}
+
+static const struct of_device_id pic32_dmt_of_ids[] = {
+ { .compatible = "microchip,pic32mzda-dmt",},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pic32_dmt_of_ids);
+
+static struct platform_driver pic32_dmt_driver = {
+ .probe = pic32_dmt_probe,
+ .driver = {
+ .name = "pic32-dmt",
+ .of_match_table = of_match_ptr(pic32_dmt_of_ids),
+ }
+};
+
+module_platform_driver(pic32_dmt_driver);
+
+MODULE_AUTHOR("Purna Chandra Mandal <purna.mandal@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC32 DMT Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pic32-wdt.c b/drivers/watchdog/pic32-wdt.c
new file mode 100644
index 000000000..41715d68d
--- /dev/null
+++ b/drivers/watchdog/pic32-wdt.c
@@ -0,0 +1,244 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PIC32 watchdog driver
+ *
+ * Joshua Henderson <joshua.henderson@microchip.com>
+ * Copyright (c) 2016, Microchip Technology Inc.
+ */
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#include <asm/mach-pic32/pic32.h>
+
+/* Watchdog Timer Registers */
+#define WDTCON_REG 0x00
+
+/* Watchdog Timer Control Register fields */
+#define WDTCON_WIN_EN BIT(0)
+#define WDTCON_RMCS_MASK 0x0003
+#define WDTCON_RMCS_SHIFT 0x0006
+#define WDTCON_RMPS_MASK 0x001F
+#define WDTCON_RMPS_SHIFT 0x0008
+#define WDTCON_ON BIT(15)
+#define WDTCON_CLR_KEY 0x5743
+
+/* Reset Control Register fields for watchdog */
+#define RESETCON_TIMEOUT_IDLE BIT(2)
+#define RESETCON_TIMEOUT_SLEEP BIT(3)
+#define RESETCON_WDT_TIMEOUT BIT(4)
+
+struct pic32_wdt {
+ void __iomem *regs;
+ void __iomem *rst_base;
+ struct clk *clk;
+};
+
+static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)
+{
+ return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);
+}
+
+static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->regs + WDTCON_REG);
+
+ return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;
+}
+
+static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->regs + WDTCON_REG);
+
+ return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;
+}
+
+static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)
+{
+ u32 v = readl(wdt->rst_base);
+
+ writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));
+
+ return v & RESETCON_WDT_TIMEOUT;
+}
+
+static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)
+{
+ unsigned long rate;
+ u32 period, ps, terminal;
+
+ rate = clk_get_rate(wdt->clk);
+
+ dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",
+ pic32_wdt_get_clk_id(wdt), rate);
+
+ /* default, prescaler of 32 (i.e. div-by-32) is implicit. */
+ rate >>= 5;
+ if (!rate)
+ return 0;
+
+ /* calculate terminal count from postscaler. */
+ ps = pic32_wdt_get_post_scaler(wdt);
+ terminal = BIT(ps);
+
+ /* find time taken (in secs) to reach terminal count */
+ period = terminal / rate;
+ dev_dbg(dev,
+ "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",
+ rate, terminal, period);
+
+ return period;
+}
+
+static void pic32_wdt_keepalive(struct pic32_wdt *wdt)
+{
+ /* write key through single half-word */
+ writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);
+}
+
+static int pic32_wdt_start(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));
+ pic32_wdt_keepalive(wdt);
+
+ return 0;
+}
+
+static int pic32_wdt_stop(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));
+
+ /*
+ * Cannot touch registers in the CPU cycle following clearing the
+ * ON bit.
+ */
+ nop();
+
+ return 0;
+}
+
+static int pic32_wdt_ping(struct watchdog_device *wdd)
+{
+ struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ pic32_wdt_keepalive(wdt);
+
+ return 0;
+}
+
+static const struct watchdog_ops pic32_wdt_fops = {
+ .owner = THIS_MODULE,
+ .start = pic32_wdt_start,
+ .stop = pic32_wdt_stop,
+ .ping = pic32_wdt_ping,
+};
+
+static const struct watchdog_info pic32_wdt_ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
+ .identity = "PIC32 Watchdog",
+};
+
+static struct watchdog_device pic32_wdd = {
+ .info = &pic32_wdt_ident,
+ .ops = &pic32_wdt_fops,
+};
+
+static const struct of_device_id pic32_wdt_dt_ids[] = {
+ { .compatible = "microchip,pic32mzda-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);
+
+static void pic32_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int pic32_wdt_drv_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct watchdog_device *wdd = &pic32_wdd;
+ struct pic32_wdt *wdt;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->regs))
+ return PTR_ERR(wdt->regs);
+
+ wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10);
+ if (!wdt->rst_base)
+ return -ENOMEM;
+
+ wdt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdt->clk)) {
+ dev_err(dev, "clk not found\n");
+ return PTR_ERR(wdt->clk);
+ }
+
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(dev, "clk enable failed\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, pic32_clk_disable_unprepare,
+ wdt->clk);
+ if (ret)
+ return ret;
+
+ if (pic32_wdt_is_win_enabled(wdt)) {
+ dev_err(dev, "windowed-clear mode is not supported.\n");
+ return -ENODEV;
+ }
+
+ wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev);
+ if (!wdd->timeout) {
+ dev_err(dev, "failed to read watchdog register timeout\n");
+ return -EINVAL;
+ }
+
+ dev_info(dev, "timeout %d\n", wdd->timeout);
+
+ wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;
+
+ watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
+ watchdog_set_drvdata(wdd, wdt);
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdd);
+
+ return 0;
+}
+
+static struct platform_driver pic32_wdt_driver = {
+ .probe = pic32_wdt_drv_probe,
+ .driver = {
+ .name = "pic32-wdt",
+ .of_match_table = of_match_ptr(pic32_wdt_dt_ids),
+ }
+};
+
+module_platform_driver(pic32_wdt_driver);
+
+MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pika_wdt.c b/drivers/watchdog/pika_wdt.c
new file mode 100644
index 000000000..a98abd0d3
--- /dev/null
+++ b/drivers/watchdog/pika_wdt.c
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PIKA FPGA based Watchdog Timer
+ *
+ * Copyright (c) 2008 PIKA Technologies
+ * Sean MacLennan <smaclennan@pikatech.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#define DRV_NAME "PIKA-WDT"
+
+/* Hardware timeout in seconds */
+#define WDT_HW_TIMEOUT 2
+
+/* Timer heartbeat (500ms) */
+#define WDT_TIMEOUT (HZ/2)
+
+/* User land timeout */
+#define WDT_HEARTBEAT 15
+static int heartbeat = WDT_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
+ "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
+
+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) ")");
+
+static struct {
+ void __iomem *fpga;
+ unsigned long next_heartbeat; /* the next_heartbeat for the timer */
+ unsigned long open;
+ char expect_close;
+ int bootstatus;
+ struct timer_list timer; /* The timer that pings the watchdog */
+} pikawdt_private;
+
+static struct watchdog_info ident __ro_after_init = {
+ .identity = DRV_NAME,
+ .options = WDIOF_CARDRESET |
+ WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+/*
+ * Reload the watchdog timer. (ie, pat the watchdog)
+ */
+static inline void pikawdt_reset(void)
+{
+ /* -- FPGA: Reset Control Register (32bit R/W) (Offset: 0x14) --
+ * Bit 7, WTCHDG_EN: When set to 1, the watchdog timer is enabled.
+ * Once enabled, it cannot be disabled. The watchdog can be
+ * kicked by performing any write access to the reset
+ * control register (this register).
+ * Bit 8-11, WTCHDG_TIMEOUT_SEC: Sets the watchdog timeout value in
+ * seconds. Valid ranges are 1 to 15 seconds. The value can
+ * be modified dynamically.
+ */
+ unsigned reset = in_be32(pikawdt_private.fpga + 0x14);
+ /* enable with max timeout - 15 seconds */
+ reset |= (1 << 7) + (WDT_HW_TIMEOUT << 8);
+ out_be32(pikawdt_private.fpga + 0x14, reset);
+}
+
+/*
+ * Timer tick
+ */
+static void pikawdt_ping(struct timer_list *unused)
+{
+ if (time_before(jiffies, pikawdt_private.next_heartbeat) ||
+ (!nowayout && !pikawdt_private.open)) {
+ pikawdt_reset();
+ mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT);
+ } else
+ pr_crit("I will reset your machine !\n");
+}
+
+
+static void pikawdt_keepalive(void)
+{
+ pikawdt_private.next_heartbeat = jiffies + heartbeat * HZ;
+}
+
+static void pikawdt_start(void)
+{
+ pikawdt_keepalive();
+ mod_timer(&pikawdt_private.timer, jiffies + WDT_TIMEOUT);
+}
+
+/*
+ * Watchdog device is opened, and watchdog starts running.
+ */
+static int pikawdt_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &pikawdt_private.open))
+ return -EBUSY;
+
+ pikawdt_start();
+
+ return stream_open(inode, file);
+}
+
+/*
+ * Close the watchdog device.
+ */
+static int pikawdt_release(struct inode *inode, struct file *file)
+{
+ /* stop internal ping */
+ if (!pikawdt_private.expect_close)
+ del_timer(&pikawdt_private.timer);
+
+ clear_bit(0, &pikawdt_private.open);
+ pikawdt_private.expect_close = 0;
+ return 0;
+}
+
+/*
+ * Pat the watchdog whenever device is written to.
+ */
+static ssize_t pikawdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (!len)
+ return 0;
+
+ /* Scan for magic character */
+ if (!nowayout) {
+ size_t i;
+
+ pikawdt_private.expect_close = 0;
+
+ for (i = 0; i < len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V') {
+ pikawdt_private.expect_close = 42;
+ break;
+ }
+ }
+ }
+
+ pikawdt_keepalive();
+
+ return len;
+}
+
+/*
+ * Handle commands from user-space.
+ */
+static long pikawdt_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_value;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(pikawdt_private.bootstatus, p);
+
+ case WDIOC_KEEPALIVE:
+ pikawdt_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_value, p))
+ return -EFAULT;
+
+ heartbeat = new_value;
+ pikawdt_keepalive();
+
+ return put_user(new_value, p); /* return current value */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ }
+ return -ENOTTY;
+}
+
+
+static const struct file_operations pikawdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = pikawdt_open,
+ .release = pikawdt_release,
+ .write = pikawdt_write,
+ .unlocked_ioctl = pikawdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+static struct miscdevice pikawdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pikawdt_fops,
+};
+
+static int __init pikawdt_init(void)
+{
+ struct device_node *np;
+ void __iomem *fpga;
+ u32 post1;
+ int ret;
+
+ np = of_find_compatible_node(NULL, NULL, "pika,fpga");
+ if (np == NULL) {
+ pr_err("Unable to find fpga\n");
+ return -ENOENT;
+ }
+
+ pikawdt_private.fpga = of_iomap(np, 0);
+ of_node_put(np);
+ if (pikawdt_private.fpga == NULL) {
+ pr_err("Unable to map fpga\n");
+ return -ENOMEM;
+ }
+
+ ident.firmware_version = in_be32(pikawdt_private.fpga + 0x1c) & 0xffff;
+
+ /* POST information is in the sd area. */
+ np = of_find_compatible_node(NULL, NULL, "pika,fpga-sd");
+ if (np == NULL) {
+ pr_err("Unable to find fpga-sd\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ fpga = of_iomap(np, 0);
+ of_node_put(np);
+ if (fpga == NULL) {
+ pr_err("Unable to map fpga-sd\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* -- FPGA: POST Test Results Register 1 (32bit R/W) (Offset: 0x4040) --
+ * Bit 31, WDOG: Set to 1 when the last reset was caused by a watchdog
+ * timeout.
+ */
+ post1 = in_be32(fpga + 0x40);
+ if (post1 & 0x80000000)
+ pikawdt_private.bootstatus = WDIOF_CARDRESET;
+
+ iounmap(fpga);
+
+ timer_setup(&pikawdt_private.timer, pikawdt_ping, 0);
+
+ ret = misc_register(&pikawdt_miscdev);
+ if (ret) {
+ pr_err("Unable to register miscdev\n");
+ goto out;
+ }
+
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+ return 0;
+
+out:
+ iounmap(pikawdt_private.fpga);
+ return ret;
+}
+
+static void __exit pikawdt_exit(void)
+{
+ misc_deregister(&pikawdt_miscdev);
+
+ iounmap(pikawdt_private.fpga);
+}
+
+module_init(pikawdt_init);
+module_exit(pikawdt_exit);
+
+MODULE_AUTHOR("Sean MacLennan <smaclennan@pikatech.com>");
+MODULE_DESCRIPTION("PIKA FPGA based Watchdog Timer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pm8916_wdt.c b/drivers/watchdog/pm8916_wdt.c
new file mode 100644
index 000000000..f4bfbffaf
--- /dev/null
+++ b/drivers/watchdog/pm8916_wdt.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#define PON_POFF_REASON1 0x0c
+#define PON_POFF_REASON1_PMIC_WD BIT(2)
+#define PON_POFF_REASON2 0x0d
+#define PON_POFF_REASON2_UVLO BIT(5)
+#define PON_POFF_REASON2_OTST3 BIT(6)
+
+#define PON_INT_RT_STS 0x10
+#define PMIC_WD_BARK_STS_BIT BIT(6)
+
+#define PON_PMIC_WD_RESET_S1_TIMER 0x54
+#define PON_PMIC_WD_RESET_S2_TIMER 0x55
+
+#define PON_PMIC_WD_RESET_S2_CTL 0x56
+#define RESET_TYPE_WARM 0x01
+#define RESET_TYPE_SHUTDOWN 0x04
+#define RESET_TYPE_HARD 0x07
+
+#define PON_PMIC_WD_RESET_S2_CTL2 0x57
+#define S2_RESET_EN_BIT BIT(7)
+
+#define PON_PMIC_WD_RESET_PET 0x58
+#define WATCHDOG_PET_BIT BIT(0)
+
+#define PM8916_WDT_DEFAULT_TIMEOUT 32
+#define PM8916_WDT_MIN_TIMEOUT 1
+#define PM8916_WDT_MAX_TIMEOUT 127
+
+struct pm8916_wdt {
+ struct regmap *regmap;
+ struct watchdog_device wdev;
+ u32 baseaddr;
+};
+
+static int pm8916_wdt_start(struct watchdog_device *wdev)
+{
+ struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
+
+ return regmap_update_bits(wdt->regmap,
+ wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
+ S2_RESET_EN_BIT, S2_RESET_EN_BIT);
+}
+
+static int pm8916_wdt_stop(struct watchdog_device *wdev)
+{
+ struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
+
+ return regmap_update_bits(wdt->regmap,
+ wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
+ S2_RESET_EN_BIT, 0);
+}
+
+static int pm8916_wdt_ping(struct watchdog_device *wdev)
+{
+ struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
+
+ return regmap_write(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_PET,
+ WATCHDOG_PET_BIT);
+}
+
+static int pm8916_wdt_configure_timers(struct watchdog_device *wdev)
+{
+ struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
+ int err;
+
+ err = regmap_write(wdt->regmap,
+ wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER,
+ wdev->timeout - wdev->pretimeout);
+ if (err)
+ return err;
+
+ return regmap_write(wdt->regmap,
+ wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER,
+ wdev->pretimeout);
+}
+
+static int pm8916_wdt_set_timeout(struct watchdog_device *wdev,
+ unsigned int timeout)
+{
+ wdev->timeout = timeout;
+
+ return pm8916_wdt_configure_timers(wdev);
+}
+
+static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev,
+ unsigned int pretimeout)
+{
+ wdev->pretimeout = pretimeout;
+
+ return pm8916_wdt_configure_timers(wdev);
+}
+
+static irqreturn_t pm8916_wdt_isr(int irq, void *arg)
+{
+ struct pm8916_wdt *wdt = arg;
+ int err, sts;
+
+ err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts);
+ if (err)
+ return IRQ_HANDLED;
+
+ if (sts & PMIC_WD_BARK_STS_BIT)
+ watchdog_notify_pretimeout(&wdt->wdev);
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_info pm8916_wdt_ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
+ WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER,
+ .identity = "QCOM PM8916 PON WDT",
+};
+
+static const struct watchdog_info pm8916_wdt_pt_ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
+ WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER |
+ WDIOF_PRETIMEOUT,
+ .identity = "QCOM PM8916 PON WDT",
+};
+
+static const struct watchdog_ops pm8916_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pm8916_wdt_start,
+ .stop = pm8916_wdt_stop,
+ .ping = pm8916_wdt_ping,
+ .set_timeout = pm8916_wdt_set_timeout,
+ .set_pretimeout = pm8916_wdt_set_pretimeout,
+};
+
+static int pm8916_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct pm8916_wdt *wdt;
+ struct device *parent;
+ unsigned int val;
+ int err, irq;
+ u8 poff[2];
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ parent = dev->parent;
+
+ /*
+ * The pm8916-pon-wdt is a child of the pon device, which is a child
+ * of the pm8916 mfd device. We want access to the pm8916 registers.
+ * Retrieve regmap from pm8916 (parent->parent) and base address
+ * from pm8916-pon (pon).
+ */
+ wdt->regmap = dev_get_regmap(parent->parent, NULL);
+ if (!wdt->regmap) {
+ dev_err(dev, "failed to locate regmap\n");
+ return -ENODEV;
+ }
+
+ err = device_property_read_u32(parent, "reg", &wdt->baseaddr);
+ if (err) {
+ dev_err(dev, "failed to get pm8916-pon address\n");
+ return err;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq > 0) {
+ err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0,
+ "pm8916_wdt", wdt);
+ if (err)
+ return err;
+
+ wdt->wdev.info = &pm8916_wdt_pt_ident;
+ } else {
+ if (irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ wdt->wdev.info = &pm8916_wdt_ident;
+ }
+
+ err = regmap_bulk_read(wdt->regmap, wdt->baseaddr + PON_POFF_REASON1,
+ &poff, ARRAY_SIZE(poff));
+ if (err) {
+ dev_err(dev, "failed to read POFF reason: %d\n", err);
+ return err;
+ }
+
+ dev_dbg(dev, "POFF reason: %#x %#x\n", poff[0], poff[1]);
+ if (poff[0] & PON_POFF_REASON1_PMIC_WD)
+ wdt->wdev.bootstatus |= WDIOF_CARDRESET;
+ if (poff[1] & PON_POFF_REASON2_UVLO)
+ wdt->wdev.bootstatus |= WDIOF_POWERUNDER;
+ if (poff[1] & PON_POFF_REASON2_OTST3)
+ wdt->wdev.bootstatus |= WDIOF_OVERHEAT;
+
+ err = regmap_read(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
+ &val);
+ if (err) {
+ dev_err(dev, "failed to check if watchdog is active: %d\n", err);
+ return err;
+ }
+ if (val & S2_RESET_EN_BIT)
+ set_bit(WDOG_HW_RUNNING, &wdt->wdev.status);
+
+ /* Configure watchdog to hard-reset mode */
+ err = regmap_write(wdt->regmap,
+ wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL,
+ RESET_TYPE_HARD);
+ if (err) {
+ dev_err(dev, "failed configure watchdog\n");
+ return err;
+ }
+
+ wdt->wdev.ops = &pm8916_wdt_ops,
+ wdt->wdev.parent = dev;
+ wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT;
+ wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT;
+ wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT;
+ wdt->wdev.pretimeout = 0;
+ watchdog_set_drvdata(&wdt->wdev, wdt);
+ platform_set_drvdata(pdev, wdt);
+
+ watchdog_init_timeout(&wdt->wdev, 0, dev);
+ pm8916_wdt_configure_timers(&wdt->wdev);
+
+ return devm_watchdog_register_device(dev, &wdt->wdev);
+}
+
+static int __maybe_unused pm8916_wdt_suspend(struct device *dev)
+{
+ struct pm8916_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdev))
+ return pm8916_wdt_stop(&wdt->wdev);
+
+ return 0;
+}
+
+static int __maybe_unused pm8916_wdt_resume(struct device *dev)
+{
+ struct pm8916_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdev))
+ return pm8916_wdt_start(&wdt->wdev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend,
+ pm8916_wdt_resume);
+
+static const struct of_device_id pm8916_wdt_id_table[] = {
+ { .compatible = "qcom,pm8916-wdt" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table);
+
+static struct platform_driver pm8916_wdt_driver = {
+ .probe = pm8916_wdt_probe,
+ .driver = {
+ .name = "pm8916-wdt",
+ .of_match_table = of_match_ptr(pm8916_wdt_id_table),
+ .pm = &pm8916_wdt_pm_ops,
+ },
+};
+module_platform_driver(pm8916_wdt_driver);
+
+MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");
+MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c
new file mode 100644
index 000000000..e0ea133c1
--- /dev/null
+++ b/drivers/watchdog/pnx4008_wdt.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/char/watchdog/pnx4008_wdt.c
+ *
+ * Watchdog driver for PNX4008 board
+ *
+ * Authors: Dmitry Chigirev <source@mvista.com>,
+ * Vitaly Wool <vitalywool@gmail.com>
+ * Based on sa1100 driver,
+ * Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * 2005-2006 (c) MontaVista Software, Inc.
+ *
+ * (C) 2012 Wolfram Sang, Pengutronix
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+
+/* WatchDog Timer - Chapter 23 Page 207 */
+
+#define DEFAULT_HEARTBEAT 19
+#define MAX_HEARTBEAT 60
+
+/* Watchdog timer register set definition */
+#define WDTIM_INT(p) ((p) + 0x0)
+#define WDTIM_CTRL(p) ((p) + 0x4)
+#define WDTIM_COUNTER(p) ((p) + 0x8)
+#define WDTIM_MCTRL(p) ((p) + 0xC)
+#define WDTIM_MATCH0(p) ((p) + 0x10)
+#define WDTIM_EMR(p) ((p) + 0x14)
+#define WDTIM_PULSE(p) ((p) + 0x18)
+#define WDTIM_RES(p) ((p) + 0x1C)
+
+/* WDTIM_INT bit definitions */
+#define MATCH_INT 1
+
+/* WDTIM_CTRL bit definitions */
+#define COUNT_ENAB 1
+#define RESET_COUNT (1 << 1)
+#define DEBUG_EN (1 << 2)
+
+/* WDTIM_MCTRL bit definitions */
+#define MR0_INT 1
+#undef RESET_COUNT0
+#define RESET_COUNT0 (1 << 2)
+#define STOP_COUNT0 (1 << 2)
+#define M_RES1 (1 << 3)
+#define M_RES2 (1 << 4)
+#define RESFRC1 (1 << 5)
+#define RESFRC2 (1 << 6)
+
+/* WDTIM_EMR bit definitions */
+#define EXT_MATCH0 1
+#define MATCH_OUTPUT_HIGH (2 << 4) /*a MATCH_CTRL setting */
+
+/* WDTIM_RES bit definitions */
+#define WDOG_RESET 1 /* read only */
+
+#define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int heartbeat;
+
+static DEFINE_SPINLOCK(io_lock);
+static void __iomem *wdt_base;
+static struct clk *wdt_clk;
+
+static int pnx4008_wdt_start(struct watchdog_device *wdd)
+{
+ spin_lock(&io_lock);
+
+ /* stop counter, initiate counter reset */
+ writel(RESET_COUNT, WDTIM_CTRL(wdt_base));
+ /*wait for reset to complete. 100% guarantee event */
+ while (readl(WDTIM_COUNTER(wdt_base)))
+ cpu_relax();
+ /* internal and external reset, stop after that */
+ writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base));
+ /* configure match output */
+ writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base));
+ /* clear interrupt, just in case */
+ writel(MATCH_INT, WDTIM_INT(wdt_base));
+ /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */
+ writel(0xFFFF, WDTIM_PULSE(wdt_base));
+ writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base));
+ /*enable counter, stop when debugger active */
+ writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base));
+
+ spin_unlock(&io_lock);
+ return 0;
+}
+
+static int pnx4008_wdt_stop(struct watchdog_device *wdd)
+{
+ spin_lock(&io_lock);
+
+ writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */
+
+ spin_unlock(&io_lock);
+ return 0;
+}
+
+static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int new_timeout)
+{
+ wdd->timeout = new_timeout;
+ return 0;
+}
+
+static int pnx4008_restart_handler(struct watchdog_device *wdd,
+ unsigned long mode, void *cmd)
+{
+ const char *boot_cmd = cmd;
+
+ /*
+ * Verify if a "cmd" passed from the userspace program rebooting
+ * the system; if available, handle it.
+ * - For details, see the 'reboot' syscall in kernel/reboot.c
+ * - If the received "cmd" is not supported, use the default mode.
+ */
+ if (boot_cmd) {
+ if (boot_cmd[0] == 'h')
+ mode = REBOOT_HARD;
+ else if (boot_cmd[0] == 's')
+ mode = REBOOT_SOFT;
+ }
+
+ if (mode == REBOOT_SOFT) {
+ /* Force match output active */
+ writel(EXT_MATCH0, WDTIM_EMR(wdt_base));
+ /* Internal reset on match output (RESOUT_N not asserted) */
+ writel(M_RES1, WDTIM_MCTRL(wdt_base));
+ } else {
+ /* Instant assert of RESETOUT_N with pulse length 1mS */
+ writel(13000, WDTIM_PULSE(wdt_base));
+ writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base));
+ }
+
+ /* Wait for watchdog to reset system */
+ mdelay(1000);
+
+ return NOTIFY_DONE;
+}
+
+static const struct watchdog_info pnx4008_wdt_ident = {
+ .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+ WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "PNX4008 Watchdog",
+};
+
+static const struct watchdog_ops pnx4008_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pnx4008_wdt_start,
+ .stop = pnx4008_wdt_stop,
+ .set_timeout = pnx4008_wdt_set_timeout,
+ .restart = pnx4008_restart_handler,
+};
+
+static struct watchdog_device pnx4008_wdd = {
+ .info = &pnx4008_wdt_ident,
+ .ops = &pnx4008_wdt_ops,
+ .timeout = DEFAULT_HEARTBEAT,
+ .min_timeout = 1,
+ .max_timeout = MAX_HEARTBEAT,
+};
+
+static void pnx4008_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int pnx4008_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret = 0;
+
+ watchdog_init_timeout(&pnx4008_wdd, heartbeat, dev);
+
+ wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt_base))
+ return PTR_ERR(wdt_base);
+
+ wdt_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(wdt_clk))
+ return PTR_ERR(wdt_clk);
+
+ ret = clk_prepare_enable(wdt_clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, pnx4008_clk_disable_unprepare,
+ wdt_clk);
+ if (ret)
+ return ret;
+
+ pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ?
+ WDIOF_CARDRESET : 0;
+ pnx4008_wdd.parent = dev;
+ watchdog_set_nowayout(&pnx4008_wdd, nowayout);
+ watchdog_set_restart_priority(&pnx4008_wdd, 128);
+
+ if (readl(WDTIM_CTRL(wdt_base)) & COUNT_ENAB)
+ set_bit(WDOG_HW_RUNNING, &pnx4008_wdd.status);
+
+ ret = devm_watchdog_register_device(dev, &pnx4008_wdd);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "heartbeat %d sec\n", pnx4008_wdd.timeout);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id pnx4008_wdt_match[] = {
+ { .compatible = "nxp,pnx4008-wdt" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pnx4008_wdt_match);
+#endif
+
+static struct platform_driver platform_wdt_driver = {
+ .driver = {
+ .name = "pnx4008-watchdog",
+ .of_match_table = of_match_ptr(pnx4008_wdt_match),
+ },
+ .probe = pnx4008_wdt_probe,
+};
+
+module_platform_driver(platform_wdt_driver);
+
+MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>");
+MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>");
+MODULE_DESCRIPTION("PNX4008 Watchdog Driver");
+
+module_param(heartbeat, uint, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Set to 1 to keep watchdog running after device release");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pnx4008-watchdog");
diff --git a/drivers/watchdog/pretimeout_noop.c b/drivers/watchdog/pretimeout_noop.c
new file mode 100644
index 000000000..4799551dd
--- /dev/null
+++ b/drivers/watchdog/pretimeout_noop.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015-2016 Mentor Graphics
+ */
+
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/watchdog.h>
+
+#include "watchdog_pretimeout.h"
+
+/**
+ * pretimeout_noop - No operation on watchdog pretimeout event
+ * @wdd - watchdog_device
+ *
+ * This function prints a message about pretimeout to kernel log.
+ */
+static void pretimeout_noop(struct watchdog_device *wdd)
+{
+ pr_alert("watchdog%d: pretimeout event\n", wdd->id);
+}
+
+static struct watchdog_governor watchdog_gov_noop = {
+ .name = "noop",
+ .pretimeout = pretimeout_noop,
+};
+
+static int __init watchdog_gov_noop_register(void)
+{
+ return watchdog_register_governor(&watchdog_gov_noop);
+}
+
+static void __exit watchdog_gov_noop_unregister(void)
+{
+ watchdog_unregister_governor(&watchdog_gov_noop);
+}
+module_init(watchdog_gov_noop_register);
+module_exit(watchdog_gov_noop_unregister);
+
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
+MODULE_DESCRIPTION("Panic watchdog pretimeout governor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pretimeout_panic.c b/drivers/watchdog/pretimeout_panic.c
new file mode 100644
index 000000000..2cc3c41d2
--- /dev/null
+++ b/drivers/watchdog/pretimeout_panic.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015-2016 Mentor Graphics
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+
+#include "watchdog_pretimeout.h"
+
+/**
+ * pretimeout_panic - Panic on watchdog pretimeout event
+ * @wdd - watchdog_device
+ *
+ * Panic, watchdog has not been fed till pretimeout event.
+ */
+static void pretimeout_panic(struct watchdog_device *wdd)
+{
+ panic("watchdog pretimeout event\n");
+}
+
+static struct watchdog_governor watchdog_gov_panic = {
+ .name = "panic",
+ .pretimeout = pretimeout_panic,
+};
+
+static int __init watchdog_gov_panic_register(void)
+{
+ return watchdog_register_governor(&watchdog_gov_panic);
+}
+
+static void __exit watchdog_gov_panic_unregister(void)
+{
+ watchdog_unregister_governor(&watchdog_gov_panic);
+}
+module_init(watchdog_gov_panic_register);
+module_exit(watchdog_gov_panic_unregister);
+
+MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
+MODULE_DESCRIPTION("Panic watchdog pretimeout governor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/pseries-wdt.c b/drivers/watchdog/pseries-wdt.c
new file mode 100644
index 000000000..7f53b5293
--- /dev/null
+++ b/drivers/watchdog/pseries-wdt.c
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 International Business Machines, Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/math.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/time64.h>
+#include <linux/watchdog.h>
+
+#define DRV_NAME "pseries-wdt"
+
+/*
+ * H_WATCHDOG Input
+ *
+ * R4: "flags":
+ *
+ * Bits 48-55: "operation"
+ */
+#define PSERIES_WDTF_OP_START 0x100UL /* start timer */
+#define PSERIES_WDTF_OP_STOP 0x200UL /* stop timer */
+#define PSERIES_WDTF_OP_QUERY 0x300UL /* query timer capabilities */
+
+/*
+ * Bits 56-63: "timeoutAction" (for "Start Watchdog" only)
+ */
+#define PSERIES_WDTF_ACTION_HARD_POWEROFF 0x1UL /* poweroff */
+#define PSERIES_WDTF_ACTION_HARD_RESTART 0x2UL /* restart */
+#define PSERIES_WDTF_ACTION_DUMP_RESTART 0x3UL /* dump + restart */
+
+/*
+ * H_WATCHDOG Output
+ *
+ * R3: Return code
+ *
+ * H_SUCCESS The operation completed.
+ *
+ * H_BUSY The hypervisor is too busy; retry the operation.
+ *
+ * H_PARAMETER The given "flags" are somehow invalid. Either the
+ * "operation" or "timeoutAction" is invalid, or a
+ * reserved bit is set.
+ *
+ * H_P2 The given "watchdogNumber" is zero or exceeds the
+ * supported maximum value.
+ *
+ * H_P3 The given "timeoutInMs" is below the supported
+ * minimum value.
+ *
+ * H_NOOP The given "watchdogNumber" is already stopped.
+ *
+ * H_HARDWARE The operation failed for ineffable reasons.
+ *
+ * H_FUNCTION The H_WATCHDOG hypercall is not supported by this
+ * hypervisor.
+ *
+ * R4:
+ *
+ * - For the "Query Watchdog Capabilities" operation, a 64-bit
+ * structure:
+ */
+#define PSERIES_WDTQ_MIN_TIMEOUT(cap) (((cap) >> 48) & 0xffff)
+#define PSERIES_WDTQ_MAX_NUMBER(cap) (((cap) >> 32) & 0xffff)
+
+static const unsigned long pseries_wdt_action[] = {
+ [0] = PSERIES_WDTF_ACTION_HARD_POWEROFF,
+ [1] = PSERIES_WDTF_ACTION_HARD_RESTART,
+ [2] = PSERIES_WDTF_ACTION_DUMP_RESTART,
+};
+
+#define WATCHDOG_ACTION 1
+static unsigned int action = WATCHDOG_ACTION;
+module_param(action, uint, 0444);
+MODULE_PARM_DESC(action, "Action taken when watchdog expires (default="
+ __MODULE_STRING(WATCHDOG_ACTION) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0444);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+#define WATCHDOG_TIMEOUT 60
+static unsigned int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, uint, 0444);
+MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds (default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+struct pseries_wdt {
+ struct watchdog_device wd;
+ unsigned long action;
+ unsigned long num; /* Watchdog numbers are 1-based */
+};
+
+static int pseries_wdt_start(struct watchdog_device *wdd)
+{
+ struct pseries_wdt *pw = watchdog_get_drvdata(wdd);
+ struct device *dev = wdd->parent;
+ unsigned long flags, msecs;
+ long rc;
+
+ flags = pw->action | PSERIES_WDTF_OP_START;
+ msecs = wdd->timeout * MSEC_PER_SEC;
+ rc = plpar_hcall_norets(H_WATCHDOG, flags, pw->num, msecs);
+ if (rc != H_SUCCESS) {
+ dev_crit(dev, "H_WATCHDOG: %ld: failed to start timer %lu",
+ rc, pw->num);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int pseries_wdt_stop(struct watchdog_device *wdd)
+{
+ struct pseries_wdt *pw = watchdog_get_drvdata(wdd);
+ struct device *dev = wdd->parent;
+ long rc;
+
+ rc = plpar_hcall_norets(H_WATCHDOG, PSERIES_WDTF_OP_STOP, pw->num);
+ if (rc != H_SUCCESS && rc != H_NOOP) {
+ dev_crit(dev, "H_WATCHDOG: %ld: failed to stop timer %lu",
+ rc, pw->num);
+ return -EIO;
+ }
+ return 0;
+}
+
+static struct watchdog_info pseries_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT
+ | WDIOF_PRETIMEOUT,
+};
+
+static const struct watchdog_ops pseries_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pseries_wdt_start,
+ .stop = pseries_wdt_stop,
+};
+
+static int pseries_wdt_probe(struct platform_device *pdev)
+{
+ unsigned long ret[PLPAR_HCALL_BUFSIZE] = { 0 };
+ struct pseries_wdt *pw;
+ unsigned long cap;
+ long msecs, rc;
+ int err;
+
+ rc = plpar_hcall(H_WATCHDOG, ret, PSERIES_WDTF_OP_QUERY);
+ if (rc == H_FUNCTION)
+ return -ENODEV;
+ if (rc != H_SUCCESS)
+ return -EIO;
+ cap = ret[0];
+
+ pw = devm_kzalloc(&pdev->dev, sizeof(*pw), GFP_KERNEL);
+ if (!pw)
+ return -ENOMEM;
+
+ /*
+ * Assume watchdogNumber 1 for now. If we ever support
+ * multiple timers we will need to devise a way to choose a
+ * distinct watchdogNumber for each platform device at device
+ * registration time.
+ */
+ pw->num = 1;
+ if (PSERIES_WDTQ_MAX_NUMBER(cap) < pw->num)
+ return -ENODEV;
+
+ if (action >= ARRAY_SIZE(pseries_wdt_action))
+ return -EINVAL;
+ pw->action = pseries_wdt_action[action];
+
+ pw->wd.parent = &pdev->dev;
+ pw->wd.info = &pseries_wdt_info;
+ pw->wd.ops = &pseries_wdt_ops;
+ msecs = PSERIES_WDTQ_MIN_TIMEOUT(cap);
+ pw->wd.min_timeout = DIV_ROUND_UP(msecs, MSEC_PER_SEC);
+ pw->wd.max_timeout = UINT_MAX / 1000; /* from linux/watchdog.h */
+ pw->wd.timeout = timeout;
+ if (watchdog_init_timeout(&pw->wd, 0, NULL))
+ return -EINVAL;
+ watchdog_set_nowayout(&pw->wd, nowayout);
+ watchdog_stop_on_reboot(&pw->wd);
+ watchdog_stop_on_unregister(&pw->wd);
+ watchdog_set_drvdata(&pw->wd, pw);
+
+ err = devm_watchdog_register_device(&pdev->dev, &pw->wd);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, &pw->wd);
+
+ return 0;
+}
+
+static int pseries_wdt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct watchdog_device *wd = platform_get_drvdata(pdev);
+
+ if (watchdog_active(wd))
+ return pseries_wdt_stop(wd);
+ return 0;
+}
+
+static int pseries_wdt_resume(struct platform_device *pdev)
+{
+ struct watchdog_device *wd = platform_get_drvdata(pdev);
+
+ if (watchdog_active(wd))
+ return pseries_wdt_start(wd);
+ return 0;
+}
+
+static const struct platform_device_id pseries_wdt_id[] = {
+ { .name = "pseries-wdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, pseries_wdt_id);
+
+static struct platform_driver pseries_wdt_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+ .id_table = pseries_wdt_id,
+ .probe = pseries_wdt_probe,
+ .resume = pseries_wdt_resume,
+ .suspend = pseries_wdt_suspend,
+};
+module_platform_driver(pseries_wdt_driver);
+
+MODULE_AUTHOR("Alexey Kardashevskiy");
+MODULE_AUTHOR("Scott Cheloha");
+MODULE_DESCRIPTION("POWER Architecture Platform Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c
new file mode 100644
index 000000000..0d2209c5e
--- /dev/null
+++ b/drivers/watchdog/qcom-wdt.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ */
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/of_device.h>
+
+enum wdt_reg {
+ WDT_RST,
+ WDT_EN,
+ WDT_STS,
+ WDT_BARK_TIME,
+ WDT_BITE_TIME,
+};
+
+#define QCOM_WDT_ENABLE BIT(0)
+
+static const u32 reg_offset_data_apcs_tmr[] = {
+ [WDT_RST] = 0x38,
+ [WDT_EN] = 0x40,
+ [WDT_STS] = 0x44,
+ [WDT_BARK_TIME] = 0x4C,
+ [WDT_BITE_TIME] = 0x5C,
+};
+
+static const u32 reg_offset_data_kpss[] = {
+ [WDT_RST] = 0x4,
+ [WDT_EN] = 0x8,
+ [WDT_STS] = 0xC,
+ [WDT_BARK_TIME] = 0x10,
+ [WDT_BITE_TIME] = 0x14,
+};
+
+struct qcom_wdt_match_data {
+ const u32 *offset;
+ bool pretimeout;
+};
+
+struct qcom_wdt {
+ struct watchdog_device wdd;
+ unsigned long rate;
+ void __iomem *base;
+ const u32 *layout;
+};
+
+static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)
+{
+ return wdt->base + wdt->layout[reg];
+}
+
+static inline
+struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct qcom_wdt, wdd);
+}
+
+static irqreturn_t qcom_wdt_isr(int irq, void *arg)
+{
+ struct watchdog_device *wdd = arg;
+
+ watchdog_notify_pretimeout(wdd);
+
+ return IRQ_HANDLED;
+}
+
+static int qcom_wdt_start(struct watchdog_device *wdd)
+{
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+ unsigned int bark = wdd->timeout - wdd->pretimeout;
+
+ writel(0, wdt_addr(wdt, WDT_EN));
+ writel(1, wdt_addr(wdt, WDT_RST));
+ writel(bark * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME));
+ writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME));
+ writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN));
+ return 0;
+}
+
+static int qcom_wdt_stop(struct watchdog_device *wdd)
+{
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+
+ writel(0, wdt_addr(wdt, WDT_EN));
+ return 0;
+}
+
+static int qcom_wdt_ping(struct watchdog_device *wdd)
+{
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+
+ writel(1, wdt_addr(wdt, WDT_RST));
+ return 0;
+}
+
+static int qcom_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+ return qcom_wdt_start(wdd);
+}
+
+static int qcom_wdt_set_pretimeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->pretimeout = timeout;
+ return qcom_wdt_start(wdd);
+}
+
+static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+ u32 timeout;
+
+ /*
+ * Trigger watchdog bite:
+ * Setup BITE_TIME to be 128ms, and enable WDT.
+ */
+ timeout = 128 * wdt->rate / 1000;
+
+ writel(0, wdt_addr(wdt, WDT_EN));
+ writel(1, wdt_addr(wdt, WDT_RST));
+ writel(timeout, wdt_addr(wdt, WDT_BARK_TIME));
+ writel(timeout, wdt_addr(wdt, WDT_BITE_TIME));
+ writel(QCOM_WDT_ENABLE, wdt_addr(wdt, WDT_EN));
+
+ /*
+ * Actually make sure the above sequence hits hardware before sleeping.
+ */
+ wmb();
+
+ mdelay(150);
+ return 0;
+}
+
+static int qcom_wdt_is_running(struct watchdog_device *wdd)
+{
+ struct qcom_wdt *wdt = to_qcom_wdt(wdd);
+
+ return (readl(wdt_addr(wdt, WDT_EN)) & QCOM_WDT_ENABLE);
+}
+
+static const struct watchdog_ops qcom_wdt_ops = {
+ .start = qcom_wdt_start,
+ .stop = qcom_wdt_stop,
+ .ping = qcom_wdt_ping,
+ .set_timeout = qcom_wdt_set_timeout,
+ .set_pretimeout = qcom_wdt_set_pretimeout,
+ .restart = qcom_wdt_restart,
+ .owner = THIS_MODULE,
+};
+
+static const struct watchdog_info qcom_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT
+ | WDIOF_CARDRESET,
+ .identity = KBUILD_MODNAME,
+};
+
+static const struct watchdog_info qcom_wdt_pt_info = {
+ .options = WDIOF_KEEPALIVEPING
+ | WDIOF_MAGICCLOSE
+ | WDIOF_SETTIMEOUT
+ | WDIOF_PRETIMEOUT
+ | WDIOF_CARDRESET,
+ .identity = KBUILD_MODNAME,
+};
+
+static void qcom_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static const struct qcom_wdt_match_data match_data_apcs_tmr = {
+ .offset = reg_offset_data_apcs_tmr,
+ .pretimeout = false,
+};
+
+static const struct qcom_wdt_match_data match_data_kpss = {
+ .offset = reg_offset_data_kpss,
+ .pretimeout = true,
+};
+
+static int qcom_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct qcom_wdt *wdt;
+ struct resource *res;
+ struct device_node *np = dev->of_node;
+ const struct qcom_wdt_match_data *data;
+ u32 percpu_offset;
+ int irq, ret;
+ struct clk *clk;
+
+ data = of_device_get_match_data(dev);
+ if (!data) {
+ dev_err(dev, "Unsupported QCOM WDT module\n");
+ return -ENODEV;
+ }
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOMEM;
+
+ /* We use CPU0's DGT for the watchdog */
+ if (of_property_read_u32(np, "cpu-offset", &percpu_offset))
+ percpu_offset = 0;
+
+ res->start += percpu_offset;
+ res->end += percpu_offset;
+
+ wdt->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get input clock\n");
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(dev, "failed to setup clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, qcom_clk_disable_unprepare, clk);
+ if (ret)
+ return ret;
+
+ /*
+ * We use the clock rate to calculate the max timeout, so ensure it's
+ * not zero to avoid a divide-by-zero exception.
+ *
+ * WATCHDOG_CORE assumes units of seconds, if the WDT is clocked such
+ * that it would bite before a second elapses it's usefulness is
+ * limited. Bail if this is the case.
+ */
+ wdt->rate = clk_get_rate(clk);
+ if (wdt->rate == 0 ||
+ wdt->rate > 0x10000000U) {
+ dev_err(dev, "invalid clock rate\n");
+ return -EINVAL;
+ }
+
+ /* check if there is pretimeout support */
+ irq = platform_get_irq_optional(pdev, 0);
+ if (data->pretimeout && irq > 0) {
+ ret = devm_request_irq(dev, irq, qcom_wdt_isr, 0,
+ "wdt_bark", &wdt->wdd);
+ if (ret)
+ return ret;
+
+ wdt->wdd.info = &qcom_wdt_pt_info;
+ wdt->wdd.pretimeout = 1;
+ } else {
+ if (irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ wdt->wdd.info = &qcom_wdt_info;
+ }
+
+ wdt->wdd.ops = &qcom_wdt_ops;
+ wdt->wdd.min_timeout = 1;
+ wdt->wdd.max_timeout = 0x10000000U / wdt->rate;
+ wdt->wdd.parent = dev;
+ wdt->layout = data->offset;
+
+ if (readl(wdt_addr(wdt, WDT_STS)) & 1)
+ wdt->wdd.bootstatus = WDIOF_CARDRESET;
+
+ /*
+ * If 'timeout-sec' unspecified in devicetree, assume a 30 second
+ * default, unless the max timeout is less than 30 seconds, then use
+ * the max instead.
+ */
+ wdt->wdd.timeout = min(wdt->wdd.max_timeout, 30U);
+ watchdog_init_timeout(&wdt->wdd, 0, dev);
+
+ /*
+ * If WDT is already running, call WDT start which
+ * will stop the WDT, set timeouts as bootloader
+ * might use different ones and set running bit
+ * to inform the WDT subsystem to ping the WDT
+ */
+ if (qcom_wdt_is_running(&wdt->wdd)) {
+ qcom_wdt_start(&wdt->wdd);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+
+ ret = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+ return 0;
+}
+
+static int __maybe_unused qcom_wdt_suspend(struct device *dev)
+{
+ struct qcom_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ qcom_wdt_stop(&wdt->wdd);
+
+ return 0;
+}
+
+static int __maybe_unused qcom_wdt_resume(struct device *dev)
+{
+ struct qcom_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ qcom_wdt_start(&wdt->wdd);
+
+ return 0;
+}
+
+static const struct dev_pm_ops qcom_wdt_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(qcom_wdt_suspend, qcom_wdt_resume)
+};
+
+static const struct of_device_id qcom_wdt_of_table[] = {
+ { .compatible = "qcom,kpss-timer", .data = &match_data_apcs_tmr },
+ { .compatible = "qcom,scss-timer", .data = &match_data_apcs_tmr },
+ { .compatible = "qcom,kpss-wdt", .data = &match_data_kpss },
+ { },
+};
+MODULE_DEVICE_TABLE(of, qcom_wdt_of_table);
+
+static struct platform_driver qcom_watchdog_driver = {
+ .probe = qcom_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = qcom_wdt_of_table,
+ .pm = &qcom_wdt_pm_ops,
+ },
+};
+module_platform_driver(qcom_watchdog_driver);
+
+MODULE_DESCRIPTION("QCOM KPSS Watchdog Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/rave-sp-wdt.c b/drivers/watchdog/rave-sp-wdt.c
new file mode 100644
index 000000000..2c95615b6
--- /dev/null
+++ b/drivers/watchdog/rave-sp-wdt.c
@@ -0,0 +1,336 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Driver for watchdog aspect of for Zodiac Inflight Innovations RAVE
+ * Supervisory Processor(SP) MCU
+ *
+ * Copyright (C) 2017 Zodiac Inflight Innovation
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/mfd/rave-sp.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+enum {
+ RAVE_SP_RESET_BYTE = 1,
+ RAVE_SP_RESET_REASON_NORMAL = 0,
+ RAVE_SP_RESET_DELAY_MS = 500,
+};
+
+/**
+ * struct rave_sp_wdt_variant - RAVE SP watchdog variant
+ *
+ * @max_timeout: Largest possible watchdog timeout setting
+ * @min_timeout: Smallest possible watchdog timeout setting
+ *
+ * @configure: Function to send configuration command
+ * @restart: Function to send "restart" command
+ */
+struct rave_sp_wdt_variant {
+ unsigned int max_timeout;
+ unsigned int min_timeout;
+
+ int (*configure)(struct watchdog_device *, bool);
+ int (*restart)(struct watchdog_device *);
+};
+
+/**
+ * struct rave_sp_wdt - RAVE SP watchdog
+ *
+ * @wdd: Underlying watchdog device
+ * @sp: Pointer to parent RAVE SP device
+ * @variant: Device specific variant information
+ * @reboot_notifier: Reboot notifier implementing machine reset
+ */
+struct rave_sp_wdt {
+ struct watchdog_device wdd;
+ struct rave_sp *sp;
+ const struct rave_sp_wdt_variant *variant;
+ struct notifier_block reboot_notifier;
+};
+
+static struct rave_sp_wdt *to_rave_sp_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct rave_sp_wdt, wdd);
+}
+
+static int rave_sp_wdt_exec(struct watchdog_device *wdd, void *data,
+ size_t data_size)
+{
+ return rave_sp_exec(to_rave_sp_wdt(wdd)->sp,
+ data, data_size, NULL, 0);
+}
+
+static int rave_sp_wdt_legacy_configure(struct watchdog_device *wdd, bool on)
+{
+ u8 cmd[] = {
+ [0] = RAVE_SP_CMD_SW_WDT,
+ [1] = 0,
+ [2] = 0,
+ [3] = on,
+ [4] = on ? wdd->timeout : 0,
+ };
+
+ return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
+}
+
+static int rave_sp_wdt_rdu_configure(struct watchdog_device *wdd, bool on)
+{
+ u8 cmd[] = {
+ [0] = RAVE_SP_CMD_SW_WDT,
+ [1] = 0,
+ [2] = on,
+ [3] = (u8)wdd->timeout,
+ [4] = (u8)(wdd->timeout >> 8),
+ };
+
+ return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
+}
+
+/**
+ * rave_sp_wdt_configure - Configure watchdog device
+ *
+ * @wdd: Device to configure
+ * @on: Desired state of the watchdog timer (ON/OFF)
+ *
+ * This function configures two aspects of the watchdog timer:
+ *
+ * - Wheither it is ON or OFF
+ * - Its timeout duration
+ *
+ * with first aspect specified via function argument and second via
+ * the value of 'wdd->timeout'.
+ */
+static int rave_sp_wdt_configure(struct watchdog_device *wdd, bool on)
+{
+ return to_rave_sp_wdt(wdd)->variant->configure(wdd, on);
+}
+
+static int rave_sp_wdt_legacy_restart(struct watchdog_device *wdd)
+{
+ u8 cmd[] = {
+ [0] = RAVE_SP_CMD_RESET,
+ [1] = 0,
+ [2] = RAVE_SP_RESET_BYTE
+ };
+
+ return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
+}
+
+static int rave_sp_wdt_rdu_restart(struct watchdog_device *wdd)
+{
+ u8 cmd[] = {
+ [0] = RAVE_SP_CMD_RESET,
+ [1] = 0,
+ [2] = RAVE_SP_RESET_BYTE,
+ [3] = RAVE_SP_RESET_REASON_NORMAL
+ };
+
+ return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
+}
+
+static int rave_sp_wdt_reboot_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ /*
+ * Restart handler is called in atomic context which means we
+ * can't communicate to SP via UART. Luckily for use SP will
+ * wait 500ms before actually resetting us, so we ask it to do
+ * so here and let the rest of the system go on wrapping
+ * things up.
+ */
+ if (action == SYS_DOWN || action == SYS_HALT) {
+ struct rave_sp_wdt *sp_wd =
+ container_of(nb, struct rave_sp_wdt, reboot_notifier);
+
+ const int ret = sp_wd->variant->restart(&sp_wd->wdd);
+
+ if (ret < 0)
+ dev_err(sp_wd->wdd.parent,
+ "Failed to issue restart command (%d)", ret);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int rave_sp_wdt_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ /*
+ * The actual work was done by reboot notifier above. SP
+ * firmware waits 500 ms before issuing reset, so let's hang
+ * here for twice that delay and hopefuly we'd never reach
+ * the return statement.
+ */
+ mdelay(2 * RAVE_SP_RESET_DELAY_MS);
+
+ return -EIO;
+}
+
+static int rave_sp_wdt_start(struct watchdog_device *wdd)
+{
+ int ret;
+
+ ret = rave_sp_wdt_configure(wdd, true);
+ if (!ret)
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return ret;
+}
+
+static int rave_sp_wdt_stop(struct watchdog_device *wdd)
+{
+ return rave_sp_wdt_configure(wdd, false);
+}
+
+static int rave_sp_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+
+ return rave_sp_wdt_configure(wdd, watchdog_active(wdd));
+}
+
+static int rave_sp_wdt_ping(struct watchdog_device *wdd)
+{
+ u8 cmd[] = {
+ [0] = RAVE_SP_CMD_PET_WDT,
+ [1] = 0,
+ };
+
+ return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd));
+}
+
+static const struct watchdog_info rave_sp_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "RAVE SP Watchdog",
+};
+
+static const struct watchdog_ops rave_sp_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rave_sp_wdt_start,
+ .stop = rave_sp_wdt_stop,
+ .ping = rave_sp_wdt_ping,
+ .set_timeout = rave_sp_wdt_set_timeout,
+ .restart = rave_sp_wdt_restart,
+};
+
+static const struct rave_sp_wdt_variant rave_sp_wdt_legacy = {
+ .max_timeout = 255,
+ .min_timeout = 1,
+ .configure = rave_sp_wdt_legacy_configure,
+ .restart = rave_sp_wdt_legacy_restart,
+};
+
+static const struct rave_sp_wdt_variant rave_sp_wdt_rdu = {
+ .max_timeout = 180,
+ .min_timeout = 60,
+ .configure = rave_sp_wdt_rdu_configure,
+ .restart = rave_sp_wdt_rdu_restart,
+};
+
+static const struct of_device_id rave_sp_wdt_of_match[] = {
+ {
+ .compatible = "zii,rave-sp-watchdog-legacy",
+ .data = &rave_sp_wdt_legacy,
+ },
+ {
+ .compatible = "zii,rave-sp-watchdog",
+ .data = &rave_sp_wdt_rdu,
+ },
+ { /* sentinel */ }
+};
+
+static int rave_sp_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct rave_sp_wdt *sp_wd;
+ struct nvmem_cell *cell;
+ __le16 timeout = 0;
+ int ret;
+
+ sp_wd = devm_kzalloc(dev, sizeof(*sp_wd), GFP_KERNEL);
+ if (!sp_wd)
+ return -ENOMEM;
+
+ sp_wd->variant = of_device_get_match_data(dev);
+ sp_wd->sp = dev_get_drvdata(dev->parent);
+
+ wdd = &sp_wd->wdd;
+ wdd->parent = dev;
+ wdd->info = &rave_sp_wdt_info;
+ wdd->ops = &rave_sp_wdt_ops;
+ wdd->min_timeout = sp_wd->variant->min_timeout;
+ wdd->max_timeout = sp_wd->variant->max_timeout;
+ wdd->status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+ wdd->timeout = 60;
+
+ cell = nvmem_cell_get(dev, "wdt-timeout");
+ if (!IS_ERR(cell)) {
+ size_t len;
+ void *value = nvmem_cell_read(cell, &len);
+
+ if (!IS_ERR(value)) {
+ memcpy(&timeout, value, min(len, sizeof(timeout)));
+ kfree(value);
+ }
+ nvmem_cell_put(cell);
+ }
+ watchdog_init_timeout(wdd, le16_to_cpu(timeout), dev);
+ watchdog_set_restart_priority(wdd, 255);
+ watchdog_stop_on_unregister(wdd);
+
+ sp_wd->reboot_notifier.notifier_call = rave_sp_wdt_reboot_notifier;
+ ret = devm_register_reboot_notifier(dev, &sp_wd->reboot_notifier);
+ if (ret) {
+ dev_err(dev, "Failed to register reboot notifier\n");
+ return ret;
+ }
+
+ /*
+ * We don't know if watchdog is running now. To be sure, let's
+ * start it and depend on watchdog core to ping it
+ */
+ wdd->max_hw_heartbeat_ms = wdd->max_timeout * 1000;
+ ret = rave_sp_wdt_start(wdd);
+ if (ret) {
+ dev_err(dev, "Watchdog didn't start\n");
+ return ret;
+ }
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret) {
+ rave_sp_wdt_stop(wdd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver rave_sp_wdt_driver = {
+ .probe = rave_sp_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = rave_sp_wdt_of_match,
+ },
+};
+
+module_platform_driver(rave_sp_wdt_driver);
+
+MODULE_DEVICE_TABLE(of, rave_sp_wdt_of_match);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>");
+MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>");
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("RAVE SP Watchdog driver");
+MODULE_ALIAS("platform:rave-sp-watchdog");
diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c
new file mode 100644
index 000000000..e74802f3a
--- /dev/null
+++ b/drivers/watchdog/rc32434_wdt.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IDT Interprise 79RC32434 watchdog driver
+ *
+ * Copyright (C) 2006, Ondrej Zajicek <santiago@crfreenet.org>
+ * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org>
+ *
+ * based on
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/platform_device.h> /* For platform_driver framework */
+#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For devm_ioremap */
+
+#include <asm/mach-rc32434/integ.h> /* For the Watchdog registers */
+
+#define VERSION "1.0"
+
+static struct {
+ unsigned long inuse;
+ spinlock_t io_lock;
+} rc32434_wdt_device;
+
+static struct integ __iomem *wdt_reg;
+
+static int expect_close;
+
+/* Board internal clock speed in Hz,
+ * the watchdog timer ticks at. */
+extern unsigned int idt_cpu_freq;
+
+/* translate wtcompare value to seconds and vice versa */
+#define WTCOMP2SEC(x) (x / idt_cpu_freq)
+#define SEC2WTCOMP(x) (x * idt_cpu_freq)
+
+/* Use a default timeout of 20s. This should be
+ * safe for CPU clock speeds up to 400MHz, as
+ * ((2 ^ 32) - 1) / (400MHz / 2) = 21s. */
+#define WATCHDOG_TIMEOUT 20
+
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout value, in seconds (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) ")");
+
+/* apply or and nand masks to data read from addr and write back */
+#define SET_BITS(addr, or, nand) \
+ writel((readl(&addr) | or) & ~nand, &addr)
+
+static int rc32434_wdt_set(int new_timeout)
+{
+ int max_to = WTCOMP2SEC((u32)-1);
+
+ if (new_timeout < 0 || new_timeout > max_to) {
+ pr_err("timeout value must be between 0 and %d\n", max_to);
+ return -EINVAL;
+ }
+ timeout = new_timeout;
+ spin_lock(&rc32434_wdt_device.io_lock);
+ writel(SEC2WTCOMP(timeout), &wdt_reg->wtcompare);
+ spin_unlock(&rc32434_wdt_device.io_lock);
+
+ return 0;
+}
+
+static void rc32434_wdt_start(void)
+{
+ u32 or, nand;
+
+ spin_lock(&rc32434_wdt_device.io_lock);
+
+ /* zero the counter before enabling */
+ writel(0, &wdt_reg->wtcount);
+
+ /* don't generate a non-maskable interrupt,
+ * do a warm reset instead */
+ nand = 1 << RC32434_ERR_WNE;
+ or = 1 << RC32434_ERR_WRE;
+
+ /* reset the ERRCS timeout bit in case it's set */
+ nand |= 1 << RC32434_ERR_WTO;
+
+ SET_BITS(wdt_reg->errcs, or, nand);
+
+ /* set the timeout (either default or based on module param) */
+ rc32434_wdt_set(timeout);
+
+ /* reset WTC timeout bit and enable WDT */
+ nand = 1 << RC32434_WTC_TO;
+ or = 1 << RC32434_WTC_EN;
+
+ SET_BITS(wdt_reg->wtc, or, nand);
+
+ spin_unlock(&rc32434_wdt_device.io_lock);
+ pr_info("Started watchdog timer\n");
+}
+
+static void rc32434_wdt_stop(void)
+{
+ spin_lock(&rc32434_wdt_device.io_lock);
+
+ /* Disable WDT */
+ SET_BITS(wdt_reg->wtc, 0, 1 << RC32434_WTC_EN);
+
+ spin_unlock(&rc32434_wdt_device.io_lock);
+ pr_info("Stopped watchdog timer\n");
+}
+
+static void rc32434_wdt_ping(void)
+{
+ spin_lock(&rc32434_wdt_device.io_lock);
+ writel(0, &wdt_reg->wtcount);
+ spin_unlock(&rc32434_wdt_device.io_lock);
+}
+
+static int rc32434_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &rc32434_wdt_device.inuse))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ rc32434_wdt_start();
+ rc32434_wdt_ping();
+
+ return stream_open(inode, file);
+}
+
+static int rc32434_wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ rc32434_wdt_stop();
+ module_put(THIS_MODULE);
+ } else {
+ pr_crit("device closed unexpectedly. WDT will not stop!\n");
+ rc32434_wdt_ping();
+ }
+ clear_bit(0, &rc32434_wdt_device.inuse);
+ return 0;
+}
+
+static ssize_t rc32434_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ rc32434_wdt_ping();
+ return len;
+ }
+ return 0;
+}
+
+static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int new_timeout;
+ unsigned int value;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .identity = "RC32434_WDT Watchdog",
+ };
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ value = 0;
+ if (copy_to_user(argp, &value, sizeof(int)))
+ return -EFAULT;
+ break;
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&value, argp, sizeof(int)))
+ return -EFAULT;
+ switch (value) {
+ case WDIOS_ENABLECARD:
+ rc32434_wdt_start();
+ break;
+ case WDIOS_DISABLECARD:
+ rc32434_wdt_stop();
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case WDIOC_KEEPALIVE:
+ rc32434_wdt_ping();
+ break;
+ case WDIOC_SETTIMEOUT:
+ if (copy_from_user(&new_timeout, argp, sizeof(int)))
+ return -EFAULT;
+ if (rc32434_wdt_set(new_timeout))
+ return -EINVAL;
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0;
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static const struct file_operations rc32434_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = rc32434_wdt_write,
+ .unlocked_ioctl = rc32434_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = rc32434_wdt_open,
+ .release = rc32434_wdt_release,
+};
+
+static struct miscdevice rc32434_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &rc32434_wdt_fops,
+};
+
+static int rc32434_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rb532_wdt_res");
+ if (!r) {
+ pr_err("failed to retrieve resources\n");
+ return -ENODEV;
+ }
+
+ wdt_reg = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+ if (!wdt_reg) {
+ pr_err("failed to remap I/O resources\n");
+ return -ENXIO;
+ }
+
+ spin_lock_init(&rc32434_wdt_device.io_lock);
+
+ /* Make sure the watchdog is not running */
+ rc32434_wdt_stop();
+
+ /* Check that the heartbeat value is within it's range;
+ * if not reset to the default */
+ if (rc32434_wdt_set(timeout)) {
+ rc32434_wdt_set(WATCHDOG_TIMEOUT);
+ pr_info("timeout value must be between 0 and %d\n",
+ WTCOMP2SEC((u32)-1));
+ }
+
+ ret = misc_register(&rc32434_wdt_miscdev);
+ if (ret < 0) {
+ pr_err("failed to register watchdog device\n");
+ return ret;
+ }
+
+ pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n",
+ timeout);
+
+ return 0;
+}
+
+static int rc32434_wdt_remove(struct platform_device *pdev)
+{
+ misc_deregister(&rc32434_wdt_miscdev);
+ return 0;
+}
+
+static void rc32434_wdt_shutdown(struct platform_device *pdev)
+{
+ rc32434_wdt_stop();
+}
+
+static struct platform_driver rc32434_wdt_driver = {
+ .probe = rc32434_wdt_probe,
+ .remove = rc32434_wdt_remove,
+ .shutdown = rc32434_wdt_shutdown,
+ .driver = {
+ .name = "rc32434_wdt",
+ }
+};
+
+module_platform_driver(rc32434_wdt_driver);
+
+MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>,"
+ "Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("Driver for the IDT RC32434 SoC watchdog");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c
new file mode 100644
index 000000000..f0c94ea51
--- /dev/null
+++ b/drivers/watchdog/rdc321x_wdt.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RDC321x watchdog driver
+ *
+ * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
+ *
+ * This driver is highly inspired from the cpu5_wdt driver
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/completion.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/mfd/rdc321x.h>
+
+#define RDC_WDT_MASK 0x80000000 /* Mask */
+#define RDC_WDT_EN 0x00800000 /* Enable bit */
+#define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
+#define RDC_WDT_RST 0x00100000 /* Reset bit */
+#define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */
+#define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */
+#define RDC_WDT_CNT 0x00000001 /* WDT count */
+
+#define RDC_CLS_TMR 0x80003844 /* Clear timer */
+
+#define RDC_WDT_INTERVAL (HZ/10+1)
+
+static int ticks = 1000;
+
+/* some device data */
+
+static struct {
+ struct completion stop;
+ int running;
+ struct timer_list timer;
+ int queue;
+ int default_ticks;
+ unsigned long inuse;
+ spinlock_t lock;
+ struct pci_dev *sb_pdev;
+ int base_reg;
+} rdc321x_wdt_device;
+
+/* generic helper functions */
+
+static void rdc321x_wdt_trigger(struct timer_list *unused)
+{
+ unsigned long flags;
+ u32 val;
+
+ if (rdc321x_wdt_device.running)
+ ticks--;
+
+ /* keep watchdog alive */
+ spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+ pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg, &val);
+ val |= RDC_WDT_EN;
+ pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg, val);
+ spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+
+ /* requeue?? */
+ if (rdc321x_wdt_device.queue && ticks)
+ mod_timer(&rdc321x_wdt_device.timer,
+ jiffies + RDC_WDT_INTERVAL);
+ else {
+ /* ticks doesn't matter anyway */
+ complete(&rdc321x_wdt_device.stop);
+ }
+
+}
+
+static void rdc321x_wdt_reset(void)
+{
+ ticks = rdc321x_wdt_device.default_ticks;
+}
+
+static void rdc321x_wdt_start(void)
+{
+ unsigned long flags;
+
+ if (!rdc321x_wdt_device.queue) {
+ rdc321x_wdt_device.queue = 1;
+
+ /* Clear the timer */
+ spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+ pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
+
+ /* Enable watchdog and set the timeout to 81.92 us */
+ pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg,
+ RDC_WDT_EN | RDC_WDT_CNT);
+ spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+
+ mod_timer(&rdc321x_wdt_device.timer,
+ jiffies + RDC_WDT_INTERVAL);
+ }
+
+ /* if process dies, counter is not decremented */
+ rdc321x_wdt_device.running++;
+}
+
+static int rdc321x_wdt_stop(void)
+{
+ if (rdc321x_wdt_device.running)
+ rdc321x_wdt_device.running = 0;
+
+ ticks = rdc321x_wdt_device.default_ticks;
+
+ return -EIO;
+}
+
+/* filesystem operations */
+static int rdc321x_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
+ return -EBUSY;
+
+ return stream_open(inode, file);
+}
+
+static int rdc321x_wdt_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &rdc321x_wdt_device.inuse);
+ return 0;
+}
+
+static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ u32 value;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET,
+ .identity = "RDC321x WDT",
+ };
+ unsigned long flags;
+
+ switch (cmd) {
+ case WDIOC_KEEPALIVE:
+ rdc321x_wdt_reset();
+ break;
+ case WDIOC_GETSTATUS:
+ /* Read the value from the DATA register */
+ spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
+ pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg, &value);
+ spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
+ if (copy_to_user(argp, &value, sizeof(u32)))
+ return -EFAULT;
+ break;
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&value, argp, sizeof(int)))
+ return -EFAULT;
+ switch (value) {
+ case WDIOS_ENABLECARD:
+ rdc321x_wdt_start();
+ break;
+ case WDIOS_DISABLECARD:
+ return rdc321x_wdt_stop();
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (!count)
+ return -EIO;
+
+ rdc321x_wdt_reset();
+
+ return count;
+}
+
+static const struct file_operations rdc321x_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = rdc321x_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = rdc321x_wdt_open,
+ .write = rdc321x_wdt_write,
+ .release = rdc321x_wdt_release,
+};
+
+static struct miscdevice rdc321x_wdt_misc = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &rdc321x_wdt_fops,
+};
+
+static int rdc321x_wdt_probe(struct platform_device *pdev)
+{
+ int err;
+ struct resource *r;
+ struct rdc321x_wdt_pdata *pdata;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ return -ENODEV;
+ }
+
+ r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");
+ if (!r) {
+ dev_err(&pdev->dev, "failed to get wdt-reg resource\n");
+ return -ENODEV;
+ }
+
+ rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
+ rdc321x_wdt_device.base_reg = r->start;
+ rdc321x_wdt_device.queue = 0;
+ rdc321x_wdt_device.default_ticks = ticks;
+
+ err = misc_register(&rdc321x_wdt_misc);
+ if (err < 0) {
+ dev_err(&pdev->dev, "misc_register failed\n");
+ return err;
+ }
+
+ spin_lock_init(&rdc321x_wdt_device.lock);
+
+ /* Reset the watchdog */
+ pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
+ rdc321x_wdt_device.base_reg, RDC_WDT_RST);
+
+ init_completion(&rdc321x_wdt_device.stop);
+
+ clear_bit(0, &rdc321x_wdt_device.inuse);
+
+ timer_setup(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
+
+ dev_info(&pdev->dev, "watchdog init success\n");
+
+ return 0;
+}
+
+static int rdc321x_wdt_remove(struct platform_device *pdev)
+{
+ if (rdc321x_wdt_device.queue) {
+ rdc321x_wdt_device.queue = 0;
+ wait_for_completion(&rdc321x_wdt_device.stop);
+ }
+
+ misc_deregister(&rdc321x_wdt_misc);
+
+ return 0;
+}
+
+static struct platform_driver rdc321x_wdt_driver = {
+ .probe = rdc321x_wdt_probe,
+ .remove = rdc321x_wdt_remove,
+ .driver = {
+ .name = "rdc321x-wdt",
+ },
+};
+
+module_platform_driver(rdc321x_wdt_driver);
+
+MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_DESCRIPTION("RDC321x watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/realtek_otto_wdt.c b/drivers/watchdog/realtek_otto_wdt.c
new file mode 100644
index 000000000..2a5298c5e
--- /dev/null
+++ b/drivers/watchdog/realtek_otto_wdt.c
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Realtek Otto MIPS platform watchdog
+ *
+ * Watchdog timer that will reset the system after timeout, using the selected
+ * reset mode.
+ *
+ * Counter scaling and timeouts:
+ * - Base prescale of (2 << 25), providing tick duration T_0: 168ms @ 200MHz
+ * - PRESCALE: logarithmic prescaler adding a factor of {1, 2, 4, 8}
+ * - Phase 1: Times out after (PHASE1 + 1) × PRESCALE × T_0
+ * Generates an interrupt, WDT cannot be stopped after phase 1
+ * - Phase 2: starts after phase 1, times out after (PHASE2 + 1) × PRESCALE × T_0
+ * Resets the system according to RST_MODE
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+
+#define OTTO_WDT_REG_CNTR 0x0
+#define OTTO_WDT_CNTR_PING BIT(31)
+
+#define OTTO_WDT_REG_INTR 0x4
+#define OTTO_WDT_INTR_PHASE_1 BIT(31)
+#define OTTO_WDT_INTR_PHASE_2 BIT(30)
+
+#define OTTO_WDT_REG_CTRL 0x8
+#define OTTO_WDT_CTRL_ENABLE BIT(31)
+#define OTTO_WDT_CTRL_PRESCALE GENMASK(30, 29)
+#define OTTO_WDT_CTRL_PHASE1 GENMASK(26, 22)
+#define OTTO_WDT_CTRL_PHASE2 GENMASK(19, 15)
+#define OTTO_WDT_CTRL_RST_MODE GENMASK(1, 0)
+#define OTTO_WDT_MODE_SOC 0
+#define OTTO_WDT_MODE_CPU 1
+#define OTTO_WDT_MODE_SOFTWARE 2
+#define OTTO_WDT_CTRL_DEFAULT OTTO_WDT_MODE_CPU
+
+#define OTTO_WDT_PRESCALE_MAX 3
+
+/*
+ * One higher than the max values contained in PHASE{1,2}, since a value of 0
+ * corresponds to one tick.
+ */
+#define OTTO_WDT_PHASE_TICKS_MAX 32
+
+/*
+ * The maximum reset delay is actually 2×32 ticks, but that would require large
+ * pretimeout values for timeouts longer than 32 ticks. Limit the maximum timeout
+ * to 32 + 1 to ensure small pretimeout values can be configured as expected.
+ */
+#define OTTO_WDT_TIMEOUT_TICKS_MAX (OTTO_WDT_PHASE_TICKS_MAX + 1)
+
+struct otto_wdt_ctrl {
+ struct watchdog_device wdev;
+ struct device *dev;
+ void __iomem *base;
+ unsigned int clk_rate_khz;
+ int irq_phase1;
+};
+
+static int otto_wdt_start(struct watchdog_device *wdev)
+{
+ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
+ u32 v;
+
+ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
+ v |= OTTO_WDT_CTRL_ENABLE;
+ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
+
+ return 0;
+}
+
+static int otto_wdt_stop(struct watchdog_device *wdev)
+{
+ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
+ u32 v;
+
+ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
+ v &= ~OTTO_WDT_CTRL_ENABLE;
+ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
+
+ return 0;
+}
+
+static int otto_wdt_ping(struct watchdog_device *wdev)
+{
+ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
+
+ iowrite32(OTTO_WDT_CNTR_PING, ctrl->base + OTTO_WDT_REG_CNTR);
+
+ return 0;
+}
+
+static int otto_wdt_tick_ms(struct otto_wdt_ctrl *ctrl, int prescale)
+{
+ return DIV_ROUND_CLOSEST(1 << (25 + prescale), ctrl->clk_rate_khz);
+}
+
+/*
+ * The timer asserts the PHASE1/PHASE2 IRQs when the number of ticks exceeds
+ * 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)
+{
+ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
+ unsigned int pretimeout_ms = pretimeout * 1000;
+ unsigned int timeout_ms = timeout * 1000;
+ unsigned int prescale_next = 0;
+ unsigned int phase1_ticks;
+ unsigned int phase2_ticks;
+ unsigned int total_ticks;
+ unsigned int prescale;
+ unsigned int tick_ms;
+ u32 v;
+
+ do {
+ prescale = prescale_next;
+ if (prescale > OTTO_WDT_PRESCALE_MAX)
+ 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;
+
+ prescale_next++;
+ } while (phase1_ticks > OTTO_WDT_PHASE_TICKS_MAX
+ || phase2_ticks > OTTO_WDT_PHASE_TICKS_MAX);
+
+ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
+
+ v &= ~(OTTO_WDT_CTRL_PRESCALE | OTTO_WDT_CTRL_PHASE1 | OTTO_WDT_CTRL_PHASE2);
+ v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE1, phase1_ticks - 1);
+ v |= FIELD_PREP(OTTO_WDT_CTRL_PHASE2, phase2_ticks - 1);
+ v |= FIELD_PREP(OTTO_WDT_CTRL_PRESCALE, prescale);
+
+ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
+
+ timeout_ms = total_ticks * tick_ms;
+ ctrl->wdev.timeout = timeout_ms / 1000;
+
+ pretimeout_ms = phase2_ticks * tick_ms;
+ ctrl->wdev.pretimeout = pretimeout_ms / 1000;
+
+ return 0;
+}
+
+static int otto_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val)
+{
+ return otto_wdt_determine_timeouts(wdev, val, min(wdev->pretimeout, val - 1));
+}
+
+static int otto_wdt_set_pretimeout(struct watchdog_device *wdev, unsigned int val)
+{
+ return otto_wdt_determine_timeouts(wdev, wdev->timeout, val);
+}
+
+static int otto_wdt_restart(struct watchdog_device *wdev, unsigned long reboot_mode,
+ void *data)
+{
+ struct otto_wdt_ctrl *ctrl = watchdog_get_drvdata(wdev);
+ u32 reset_mode;
+ u32 v;
+
+ disable_irq(ctrl->irq_phase1);
+
+ switch (reboot_mode) {
+ case REBOOT_SOFT:
+ reset_mode = OTTO_WDT_MODE_SOFTWARE;
+ break;
+ case REBOOT_WARM:
+ reset_mode = OTTO_WDT_MODE_CPU;
+ break;
+ default:
+ reset_mode = OTTO_WDT_MODE_SOC;
+ break;
+ }
+
+ /* Configure for shortest timeout and wait for reset to occur */
+ v = FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, reset_mode) | OTTO_WDT_CTRL_ENABLE;
+ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
+
+ mdelay(3 * otto_wdt_tick_ms(ctrl, 0));
+
+ return 0;
+}
+
+static irqreturn_t otto_wdt_phase1_isr(int irq, void *dev_id)
+{
+ struct otto_wdt_ctrl *ctrl = dev_id;
+
+ iowrite32(OTTO_WDT_INTR_PHASE_1, ctrl->base + OTTO_WDT_REG_INTR);
+ dev_crit(ctrl->dev, "phase 1 timeout\n");
+ watchdog_notify_pretimeout(&ctrl->wdev);
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_ops otto_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = otto_wdt_start,
+ .stop = otto_wdt_stop,
+ .ping = otto_wdt_ping,
+ .set_timeout = otto_wdt_set_timeout,
+ .set_pretimeout = otto_wdt_set_pretimeout,
+ .restart = otto_wdt_restart,
+};
+
+static const struct watchdog_info otto_wdt_info = {
+ .identity = "Realtek Otto watchdog timer",
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE |
+ WDIOF_SETTIMEOUT |
+ WDIOF_PRETIMEOUT,
+};
+
+static void otto_wdt_clock_action(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int otto_wdt_probe_clk(struct otto_wdt_ctrl *ctrl)
+{
+ struct clk *clk = devm_clk_get(ctrl->dev, NULL);
+ int ret;
+
+ if (IS_ERR(clk))
+ return dev_err_probe(ctrl->dev, PTR_ERR(clk), "Failed to get clock\n");
+
+ ret = clk_prepare_enable(clk);
+ if (ret)
+ return dev_err_probe(ctrl->dev, ret, "Failed to enable clock\n");
+
+ ret = devm_add_action_or_reset(ctrl->dev, otto_wdt_clock_action, clk);
+ if (ret)
+ return ret;
+
+ ctrl->clk_rate_khz = clk_get_rate(clk) / 1000;
+ if (ctrl->clk_rate_khz == 0)
+ return dev_err_probe(ctrl->dev, -ENXIO, "Failed to get clock rate\n");
+
+ return 0;
+}
+
+static int otto_wdt_probe_reset_mode(struct otto_wdt_ctrl *ctrl)
+{
+ static const char *mode_property = "realtek,reset-mode";
+ const struct fwnode_handle *node = ctrl->dev->fwnode;
+ int mode_count;
+ u32 mode;
+ u32 v;
+
+ if (!node)
+ return -ENXIO;
+
+ mode_count = fwnode_property_string_array_count(node, mode_property);
+ if (mode_count < 0)
+ return mode_count;
+ else if (mode_count == 0)
+ return 0;
+ else if (mode_count != 1)
+ return -EINVAL;
+
+ if (fwnode_property_match_string(node, mode_property, "soc") == 0)
+ mode = OTTO_WDT_MODE_SOC;
+ else if (fwnode_property_match_string(node, mode_property, "cpu") == 0)
+ mode = OTTO_WDT_MODE_CPU;
+ else if (fwnode_property_match_string(node, mode_property, "software") == 0)
+ mode = OTTO_WDT_MODE_SOFTWARE;
+ else
+ return -EINVAL;
+
+ v = ioread32(ctrl->base + OTTO_WDT_REG_CTRL);
+ v &= ~OTTO_WDT_CTRL_RST_MODE;
+ v |= FIELD_PREP(OTTO_WDT_CTRL_RST_MODE, mode);
+ iowrite32(v, ctrl->base + OTTO_WDT_REG_CTRL);
+
+ return 0;
+}
+
+static int otto_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct otto_wdt_ctrl *ctrl;
+ unsigned int max_tick_ms;
+ int ret;
+
+ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ ctrl->dev = dev;
+ ctrl->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctrl->base))
+ return PTR_ERR(ctrl->base);
+
+ /* 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;
+
+ ret = devm_request_irq(dev, ctrl->irq_phase1, otto_wdt_phase1_isr, 0,
+ "realtek-otto-wdt", ctrl);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get IRQ for phase1\n");
+
+ ret = otto_wdt_probe_reset_mode(ctrl);
+ if (ret)
+ return dev_err_probe(dev, ret, "Invalid reset mode specified\n");
+
+ ctrl->wdev.parent = dev;
+ ctrl->wdev.info = &otto_wdt_info;
+ ctrl->wdev.ops = &otto_wdt_ops;
+
+ /*
+ * Since pretimeout cannot be disabled, min. timeout is twice the
+ * subsystem resolution. Max. timeout is ca. 43s at a bus clock of 200MHz.
+ */
+ ctrl->wdev.min_timeout = 2;
+ max_tick_ms = otto_wdt_tick_ms(ctrl, OTTO_WDT_PRESCALE_MAX);
+ ctrl->wdev.max_hw_heartbeat_ms = max_tick_ms * OTTO_WDT_TIMEOUT_TICKS_MAX;
+ ctrl->wdev.timeout = min(30U, ctrl->wdev.max_hw_heartbeat_ms / 1000);
+
+ watchdog_set_drvdata(&ctrl->wdev, ctrl);
+ watchdog_init_timeout(&ctrl->wdev, 0, dev);
+ watchdog_stop_on_reboot(&ctrl->wdev);
+ watchdog_set_restart_priority(&ctrl->wdev, 128);
+
+ ret = otto_wdt_determine_timeouts(&ctrl->wdev, ctrl->wdev.timeout, 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set timeout\n");
+
+ return devm_watchdog_register_device(dev, &ctrl->wdev);
+}
+
+static const struct of_device_id otto_wdt_ids[] = {
+ { .compatible = "realtek,rtl8380-wdt" },
+ { .compatible = "realtek,rtl8390-wdt" },
+ { .compatible = "realtek,rtl9300-wdt" },
+ { .compatible = "realtek,rtl9310-wdt" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, otto_wdt_ids);
+
+static struct platform_driver otto_wdt_driver = {
+ .probe = otto_wdt_probe,
+ .driver = {
+ .name = "realtek-otto-watchdog",
+ .of_match_table = otto_wdt_ids,
+ },
+};
+module_platform_driver(otto_wdt_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Sander Vanheule <sander@svanheule.net>");
+MODULE_DESCRIPTION("Realtek Otto watchdog timer driver");
diff --git a/drivers/watchdog/renesas_wdt.c b/drivers/watchdog/renesas_wdt.c
new file mode 100644
index 000000000..41d58ea5e
--- /dev/null
+++ b/drivers/watchdog/renesas_wdt.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for Renesas WDT watchdog
+ *
+ * Copyright (C) 2015-17 Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>
+ * Copyright (C) 2015-17 Renesas Electronics Corporation
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/smp.h>
+#include <linux/sys_soc.h>
+#include <linux/watchdog.h>
+
+#define RWTCNT 0
+#define RWTCSRA 4
+#define RWTCSRA_WOVF BIT(4)
+#define RWTCSRA_WRFLG BIT(5)
+#define RWTCSRA_TME BIT(7)
+#define RWTCSRB 8
+
+#define RWDT_DEFAULT_TIMEOUT 60U
+
+/*
+ * In probe, clk_rate is checked to be not more than 16 bit * biggest clock
+ * divider (12 bits). d is only a factor to fully utilize the WDT counter and
+ * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits.
+ */
+#define MUL_BY_CLKS_PER_SEC(p, d) \
+ DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks])
+
+/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */
+#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate)
+
+static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 };
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct rwdt_priv {
+ void __iomem *base;
+ struct watchdog_device wdev;
+ unsigned long clk_rate;
+ u8 cks;
+ struct clk *clk;
+};
+
+static void rwdt_write(struct rwdt_priv *priv, u32 val, unsigned int reg)
+{
+ if (reg == RWTCNT)
+ val |= 0x5a5a0000;
+ else
+ val |= 0xa5a5a500;
+
+ writel_relaxed(val, priv->base + reg);
+}
+
+static int rwdt_init_timeout(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rwdt_write(priv, 65536 - MUL_BY_CLKS_PER_SEC(priv, wdev->timeout), RWTCNT);
+
+ return 0;
+}
+
+static void rwdt_wait_cycles(struct rwdt_priv *priv, unsigned int cycles)
+{
+ unsigned int delay;
+
+ delay = DIV_ROUND_UP(cycles * 1000000, priv->clk_rate);
+
+ usleep_range(delay, 2 * delay);
+}
+
+static int rwdt_start(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+ u8 val;
+
+ pm_runtime_get_sync(wdev->parent);
+
+ /* Stop the timer before we modify any register */
+ val = readb_relaxed(priv->base + RWTCSRA) & ~RWTCSRA_TME;
+ rwdt_write(priv, val, RWTCSRA);
+ /* Delay 2 cycles before setting watchdog counter */
+ rwdt_wait_cycles(priv, 2);
+
+ rwdt_init_timeout(wdev);
+ rwdt_write(priv, priv->cks, RWTCSRA);
+ rwdt_write(priv, 0, RWTCSRB);
+
+ while (readb_relaxed(priv->base + RWTCSRA) & RWTCSRA_WRFLG)
+ cpu_relax();
+
+ rwdt_write(priv, priv->cks | RWTCSRA_TME, RWTCSRA);
+
+ return 0;
+}
+
+static int rwdt_stop(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rwdt_write(priv, priv->cks, RWTCSRA);
+ /* Delay 3 cycles before disabling module clock */
+ rwdt_wait_cycles(priv, 3);
+ pm_runtime_put(wdev->parent);
+
+ return 0;
+}
+
+static unsigned int rwdt_get_timeleft(struct watchdog_device *wdev)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+ u16 val = readw_relaxed(priv->base + RWTCNT);
+
+ return DIV_BY_CLKS_PER_SEC(priv, 65536 - val);
+}
+
+/* needs to be atomic - no RPM, no usleep_range, no scheduling! */
+static int rwdt_restart(struct watchdog_device *wdev, unsigned long action,
+ void *data)
+{
+ struct rwdt_priv *priv = watchdog_get_drvdata(wdev);
+ u8 val;
+
+ clk_prepare_enable(priv->clk);
+
+ /* Stop the timer before we modify any register */
+ val = readb_relaxed(priv->base + RWTCSRA) & ~RWTCSRA_TME;
+ rwdt_write(priv, val, RWTCSRA);
+ /* Delay 2 cycles before setting watchdog counter */
+ udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate));
+
+ rwdt_write(priv, 0xffff, RWTCNT);
+ /* smallest divider to reboot soon */
+ rwdt_write(priv, 0, RWTCSRA);
+
+ readb_poll_timeout_atomic(priv->base + RWTCSRA, val,
+ !(val & RWTCSRA_WRFLG), 1, 100);
+
+ rwdt_write(priv, RWTCSRA_TME, RWTCSRA);
+
+ /* wait 2 cycles, so watchdog will trigger */
+ udelay(DIV_ROUND_UP(2 * 1000000, priv->clk_rate));
+
+ return 0;
+}
+
+static const struct watchdog_info rwdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_CARDRESET,
+ .identity = "Renesas WDT Watchdog",
+};
+
+static const struct watchdog_ops rwdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rwdt_start,
+ .stop = rwdt_stop,
+ .ping = rwdt_init_timeout,
+ .get_timeleft = rwdt_get_timeleft,
+ .restart = rwdt_restart,
+};
+
+#if defined(CONFIG_ARCH_RCAR_GEN2) && defined(CONFIG_SMP)
+/*
+ * Watchdog-reset integration is broken on early revisions of R-Car Gen2 SoCs
+ */
+static const struct soc_device_attribute rwdt_quirks_match[] = {
+ {
+ .soc_id = "r8a7790",
+ .revision = "ES1.*",
+ .data = (void *)1, /* needs single CPU */
+ }, {
+ .soc_id = "r8a7791",
+ .revision = "ES1.*",
+ .data = (void *)1, /* needs single CPU */
+ }, {
+ .soc_id = "r8a7792",
+ .data = (void *)0, /* needs SMP disabled */
+ },
+ { /* sentinel */ }
+};
+
+static bool rwdt_blacklisted(struct device *dev)
+{
+ const struct soc_device_attribute *attr;
+
+ attr = soc_device_match(rwdt_quirks_match);
+ if (attr && setup_max_cpus > (uintptr_t)attr->data) {
+ dev_info(dev, "Watchdog blacklisted on %s %s\n", attr->soc_id,
+ attr->revision);
+ return true;
+ }
+
+ return false;
+}
+#else /* !CONFIG_ARCH_RCAR_GEN2 || !CONFIG_SMP */
+static inline bool rwdt_blacklisted(struct device *dev) { return false; }
+#endif /* !CONFIG_ARCH_RCAR_GEN2 || !CONFIG_SMP */
+
+static int rwdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rwdt_priv *priv;
+ unsigned long clks_per_sec;
+ int ret, i;
+ u8 csra;
+
+ if (rwdt_blacklisted(dev))
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ pm_runtime_enable(dev);
+ pm_runtime_get_sync(dev);
+ priv->clk_rate = clk_get_rate(priv->clk);
+ csra = readb_relaxed(priv->base + RWTCSRA);
+ priv->wdev.bootstatus = csra & RWTCSRA_WOVF ? WDIOF_CARDRESET : 0;
+ pm_runtime_put(dev);
+
+ if (!priv->clk_rate) {
+ ret = -ENOENT;
+ goto out_pm_disable;
+ }
+
+ for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) {
+ clks_per_sec = priv->clk_rate / clk_divs[i];
+ if (clks_per_sec && clks_per_sec < 65536) {
+ priv->cks = i;
+ break;
+ }
+ }
+
+ if (i < 0) {
+ dev_err(dev, "Can't find suitable clock divider\n");
+ ret = -ERANGE;
+ goto out_pm_disable;
+ }
+
+ priv->wdev.info = &rwdt_ident;
+ priv->wdev.ops = &rwdt_ops;
+ priv->wdev.parent = dev;
+ priv->wdev.min_timeout = 1;
+ priv->wdev.max_timeout = DIV_BY_CLKS_PER_SEC(priv, 65536);
+ priv->wdev.timeout = min(priv->wdev.max_timeout, RWDT_DEFAULT_TIMEOUT);
+
+ platform_set_drvdata(pdev, priv);
+ watchdog_set_drvdata(&priv->wdev, priv);
+ watchdog_set_nowayout(&priv->wdev, nowayout);
+ watchdog_set_restart_priority(&priv->wdev, 0);
+ watchdog_stop_on_unregister(&priv->wdev);
+
+ /* This overrides the default timeout only if DT configuration was found */
+ watchdog_init_timeout(&priv->wdev, 0, dev);
+
+ /* Check if FW enabled the watchdog */
+ if (csra & RWTCSRA_TME) {
+ /* Ensure properly initialized dividers */
+ rwdt_start(&priv->wdev);
+ set_bit(WDOG_HW_RUNNING, &priv->wdev.status);
+ }
+
+ ret = watchdog_register_device(&priv->wdev);
+ if (ret < 0)
+ goto out_pm_disable;
+
+ return 0;
+
+ out_pm_disable:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static int rwdt_remove(struct platform_device *pdev)
+{
+ struct rwdt_priv *priv = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&priv->wdev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused rwdt_suspend(struct device *dev)
+{
+ struct rwdt_priv *priv = dev_get_drvdata(dev);
+
+ if (watchdog_active(&priv->wdev))
+ rwdt_stop(&priv->wdev);
+
+ return 0;
+}
+
+static int __maybe_unused rwdt_resume(struct device *dev)
+{
+ struct rwdt_priv *priv = dev_get_drvdata(dev);
+
+ if (watchdog_active(&priv->wdev))
+ rwdt_start(&priv->wdev);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(rwdt_pm_ops, rwdt_suspend, rwdt_resume);
+
+static const struct of_device_id rwdt_ids[] = {
+ { .compatible = "renesas,rcar-gen2-wdt", },
+ { .compatible = "renesas,rcar-gen3-wdt", },
+ { .compatible = "renesas,rcar-gen4-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rwdt_ids);
+
+static struct platform_driver rwdt_driver = {
+ .driver = {
+ .name = "renesas_wdt",
+ .of_match_table = rwdt_ids,
+ .pm = &rwdt_pm_ops,
+ },
+ .probe = rwdt_probe,
+ .remove = rwdt_remove,
+};
+module_platform_driver(rwdt_driver);
+
+MODULE_DESCRIPTION("Renesas WDT Watchdog Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");
diff --git a/drivers/watchdog/retu_wdt.c b/drivers/watchdog/retu_wdt.c
new file mode 100644
index 000000000..2b9017e1c
--- /dev/null
+++ b/drivers/watchdog/retu_wdt.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Retu watchdog driver
+ *
+ * Copyright (C) 2004, 2005 Nokia Corporation
+ *
+ * Based on code written by Amit Kucheria and Michael Buesch.
+ * Rewritten by Aaro Koskinen.
+ */
+
+#include <linux/devm-helpers.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/retu.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+
+/* Watchdog timer values in seconds */
+#define RETU_WDT_MAX_TIMER 63
+
+struct retu_wdt_dev {
+ struct retu_dev *rdev;
+ struct device *dev;
+ struct delayed_work ping_work;
+};
+
+/*
+ * Since Retu watchdog cannot be disabled in hardware, we must kick it
+ * with a timer until userspace watchdog software takes over. If
+ * CONFIG_WATCHDOG_NOWAYOUT is set, we never start the feeding.
+ */
+static void retu_wdt_ping_enable(struct retu_wdt_dev *wdev)
+{
+ retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER);
+ schedule_delayed_work(&wdev->ping_work,
+ round_jiffies_relative(RETU_WDT_MAX_TIMER * HZ / 2));
+}
+
+static void retu_wdt_ping_disable(struct retu_wdt_dev *wdev)
+{
+ retu_write(wdev->rdev, RETU_REG_WATCHDOG, RETU_WDT_MAX_TIMER);
+ cancel_delayed_work_sync(&wdev->ping_work);
+}
+
+static void retu_wdt_ping_work(struct work_struct *work)
+{
+ struct retu_wdt_dev *wdev = container_of(to_delayed_work(work),
+ struct retu_wdt_dev, ping_work);
+ retu_wdt_ping_enable(wdev);
+}
+
+static int retu_wdt_start(struct watchdog_device *wdog)
+{
+ struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
+
+ retu_wdt_ping_disable(wdev);
+
+ return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
+}
+
+static int retu_wdt_stop(struct watchdog_device *wdog)
+{
+ struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
+
+ retu_wdt_ping_enable(wdev);
+
+ return 0;
+}
+
+static int retu_wdt_ping(struct watchdog_device *wdog)
+{
+ struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
+
+ return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
+}
+
+static int retu_wdt_set_timeout(struct watchdog_device *wdog,
+ unsigned int timeout)
+{
+ struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog);
+
+ wdog->timeout = timeout;
+ return retu_write(wdev->rdev, RETU_REG_WATCHDOG, wdog->timeout);
+}
+
+static const struct watchdog_info retu_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "Retu watchdog",
+};
+
+static const struct watchdog_ops retu_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = retu_wdt_start,
+ .stop = retu_wdt_stop,
+ .ping = retu_wdt_ping,
+ .set_timeout = retu_wdt_set_timeout,
+};
+
+static int retu_wdt_probe(struct platform_device *pdev)
+{
+ struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent);
+ bool nowayout = WATCHDOG_NOWAYOUT;
+ struct watchdog_device *retu_wdt;
+ struct retu_wdt_dev *wdev;
+ int ret;
+
+ retu_wdt = devm_kzalloc(&pdev->dev, sizeof(*retu_wdt), GFP_KERNEL);
+ if (!retu_wdt)
+ return -ENOMEM;
+
+ wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL);
+ if (!wdev)
+ return -ENOMEM;
+
+ retu_wdt->info = &retu_wdt_info;
+ retu_wdt->ops = &retu_wdt_ops;
+ retu_wdt->timeout = RETU_WDT_MAX_TIMER;
+ retu_wdt->min_timeout = 0;
+ retu_wdt->max_timeout = RETU_WDT_MAX_TIMER;
+ retu_wdt->parent = &pdev->dev;
+
+ watchdog_set_drvdata(retu_wdt, wdev);
+ watchdog_set_nowayout(retu_wdt, nowayout);
+
+ wdev->rdev = rdev;
+ wdev->dev = &pdev->dev;
+
+ ret = devm_delayed_work_autocancel(&pdev->dev, &wdev->ping_work,
+ retu_wdt_ping_work);
+ if (ret)
+ return ret;
+
+ ret = devm_watchdog_register_device(&pdev->dev, retu_wdt);
+ if (ret < 0)
+ return ret;
+
+ if (nowayout)
+ retu_wdt_ping(retu_wdt);
+ else
+ retu_wdt_ping_enable(wdev);
+
+ return 0;
+}
+
+static struct platform_driver retu_wdt_driver = {
+ .probe = retu_wdt_probe,
+ .driver = {
+ .name = "retu-wdt",
+ },
+};
+module_platform_driver(retu_wdt_driver);
+
+MODULE_ALIAS("platform:retu-wdt");
+MODULE_DESCRIPTION("Retu watchdog");
+MODULE_AUTHOR("Amit Kucheria");
+MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/riowd.c b/drivers/watchdog/riowd.c
new file mode 100644
index 000000000..747e346ed
--- /dev/null
+++ b/drivers/watchdog/riowd.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* riowd.c - driver for hw watchdog inside Super I/O of RIO
+ *
+ * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+
+
+/* RIO uses the NatSemi Super I/O power management logical device
+ * as its' watchdog.
+ *
+ * When the watchdog triggers, it asserts a line to the BBC (Boot Bus
+ * Controller) of the machine. The BBC can only be configured to
+ * trigger a power-on reset when the signal is asserted. The BBC
+ * can be configured to ignore the signal entirely as well.
+ *
+ * The only Super I/O device register we care about is at index
+ * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255).
+ * If set to zero, this disables the watchdog. When set, the system
+ * must periodically (before watchdog expires) clear (set to zero) and
+ * re-set the watchdog else it will trigger.
+ *
+ * There are two other indexed watchdog registers inside this Super I/O
+ * logical device, but they are unused. The first, at index 0x06 is
+ * the watchdog control and can be used to make the watchdog timer re-set
+ * when the PS/2 mouse or serial lines show activity. The second, at
+ * index 0x07 is merely a sampling of the line from the watchdog to the
+ * BBC.
+ *
+ * The watchdog device generates no interrupts.
+ */
+
+MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
+MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO");
+MODULE_LICENSE("GPL");
+
+#define DRIVER_NAME "riowd"
+#define PFX DRIVER_NAME ": "
+
+struct riowd {
+ void __iomem *regs;
+ spinlock_t lock;
+};
+
+static struct riowd *riowd_device;
+
+#define WDTO_INDEX 0x05
+
+static int riowd_timeout = 1; /* in minutes */
+module_param(riowd_timeout, int, 0);
+MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes");
+
+static void riowd_writereg(struct riowd *p, u8 val, int index)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+ writeb(index, p->regs + 0);
+ writeb(val, p->regs + 1);
+ spin_unlock_irqrestore(&p->lock, flags);
+}
+
+static int riowd_open(struct inode *inode, struct file *filp)
+{
+ stream_open(inode, filp);
+ return 0;
+}
+
+static int riowd_release(struct inode *inode, struct file *filp)
+{
+ return 0;
+}
+
+static long riowd_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ static const struct watchdog_info info = {
+ .options = WDIOF_SETTIMEOUT,
+ .firmware_version = 1,
+ .identity = DRIVER_NAME,
+ };
+ void __user *argp = (void __user *)arg;
+ struct riowd *p = riowd_device;
+ unsigned int options;
+ int new_margin;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, (int __user *)argp))
+ return -EFAULT;
+ break;
+
+ case WDIOC_KEEPALIVE:
+ riowd_writereg(p, riowd_timeout, WDTO_INDEX);
+ break;
+
+ case WDIOC_SETOPTIONS:
+ if (copy_from_user(&options, argp, sizeof(options)))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD)
+ riowd_writereg(p, 0, WDTO_INDEX);
+ else if (options & WDIOS_ENABLECARD)
+ riowd_writereg(p, riowd_timeout, WDTO_INDEX);
+ else
+ return -EINVAL;
+
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, (int __user *)argp))
+ return -EFAULT;
+ if ((new_margin < 60) || (new_margin > (255 * 60)))
+ return -EINVAL;
+ riowd_timeout = (new_margin + 59) / 60;
+ riowd_writereg(p, riowd_timeout, WDTO_INDEX);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(riowd_timeout * 60, (int __user *)argp);
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static ssize_t riowd_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct riowd *p = riowd_device;
+
+ if (count) {
+ riowd_writereg(p, riowd_timeout, WDTO_INDEX);
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct file_operations riowd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = riowd_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = riowd_open,
+ .write = riowd_write,
+ .release = riowd_release,
+};
+
+static struct miscdevice riowd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &riowd_fops
+};
+
+static int riowd_probe(struct platform_device *op)
+{
+ struct riowd *p;
+ int err = -EINVAL;
+
+ if (riowd_device)
+ goto out;
+
+ err = -ENOMEM;
+ p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ goto out;
+
+ spin_lock_init(&p->lock);
+
+ p->regs = of_ioremap(&op->resource[0], 0, 2, DRIVER_NAME);
+ if (!p->regs) {
+ pr_err("Cannot map registers\n");
+ goto out;
+ }
+ /* Make miscdev useable right away */
+ riowd_device = p;
+
+ err = misc_register(&riowd_miscdev);
+ if (err) {
+ pr_err("Cannot register watchdog misc device\n");
+ goto out_iounmap;
+ }
+
+ pr_info("Hardware watchdog [%i minutes], regs at %p\n",
+ riowd_timeout, p->regs);
+
+ platform_set_drvdata(op, p);
+ return 0;
+
+out_iounmap:
+ riowd_device = NULL;
+ of_iounmap(&op->resource[0], p->regs, 2);
+
+out:
+ return err;
+}
+
+static int riowd_remove(struct platform_device *op)
+{
+ struct riowd *p = platform_get_drvdata(op);
+
+ misc_deregister(&riowd_miscdev);
+ of_iounmap(&op->resource[0], p->regs, 2);
+
+ return 0;
+}
+
+static const struct of_device_id riowd_match[] = {
+ {
+ .name = "pmc",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, riowd_match);
+
+static struct platform_driver riowd_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = riowd_match,
+ },
+ .probe = riowd_probe,
+ .remove = riowd_remove,
+};
+
+module_platform_driver(riowd_driver);
diff --git a/drivers/watchdog/rn5t618_wdt.c b/drivers/watchdog/rn5t618_wdt.c
new file mode 100644
index 000000000..6e524c8e2
--- /dev/null
+++ b/drivers/watchdog/rn5t618_wdt.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Watchdog driver for Ricoh RN5T618 PMIC
+ *
+ * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/mfd/rn5t618.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define DRIVER_NAME "rn5t618-wdt"
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct rn5t618_wdt {
+ struct watchdog_device wdt_dev;
+ struct rn5t618 *rn5t618;
+};
+
+/*
+ * This array encodes the values of WDOGTIM field for the supported
+ * watchdog expiration times. If the watchdog is not accessed before
+ * the timer expiration, the PMU generates an interrupt and if the CPU
+ * doesn't clear it within one second the system is restarted.
+ */
+static const struct {
+ u8 reg_val;
+ unsigned int time;
+} rn5t618_wdt_map[] = {
+ { 0, 1 },
+ { 1, 8 },
+ { 2, 32 },
+ { 3, 128 },
+};
+
+static int rn5t618_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int t)
+{
+ struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(rn5t618_wdt_map); i++) {
+ if (rn5t618_wdt_map[i].time + 1 >= t)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(rn5t618_wdt_map))
+ return -EINVAL;
+
+ ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG,
+ RN5T618_WATCHDOG_WDOGTIM_M,
+ rn5t618_wdt_map[i].reg_val);
+ if (!ret)
+ wdt_dev->timeout = rn5t618_wdt_map[i].time;
+
+ return ret;
+}
+
+static int rn5t618_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ int ret;
+
+ ret = rn5t618_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ if (ret)
+ return ret;
+
+ /* enable repower-on */
+ ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_REPCNT,
+ RN5T618_REPCNT_REPWRON,
+ RN5T618_REPCNT_REPWRON);
+ if (ret)
+ return ret;
+
+ /* enable watchdog */
+ ret = regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG,
+ RN5T618_WATCHDOG_WDOGEN,
+ RN5T618_WATCHDOG_WDOGEN);
+ if (ret)
+ return ret;
+
+ /* enable watchdog interrupt */
+ return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_PWRIREN,
+ RN5T618_PWRIRQ_IR_WDOG,
+ RN5T618_PWRIRQ_IR_WDOG);
+}
+
+static int rn5t618_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+
+ return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_WATCHDOG,
+ RN5T618_WATCHDOG_WDOGEN, 0);
+}
+
+static int rn5t618_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct rn5t618_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned int val;
+ int ret;
+
+ /* The counter is restarted after a R/W access to watchdog register */
+ ret = regmap_read(wdt->rn5t618->regmap, RN5T618_WATCHDOG, &val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(wdt->rn5t618->regmap, RN5T618_WATCHDOG, val);
+ if (ret)
+ return ret;
+
+ /* Clear pending watchdog interrupt */
+ return regmap_update_bits(wdt->rn5t618->regmap, RN5T618_PWRIRQ,
+ RN5T618_PWRIRQ_IR_WDOG, 0);
+}
+
+static const struct watchdog_info rn5t618_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = DRIVER_NAME,
+};
+
+static const struct watchdog_ops rn5t618_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rn5t618_wdt_start,
+ .stop = rn5t618_wdt_stop,
+ .ping = rn5t618_wdt_ping,
+ .set_timeout = rn5t618_wdt_set_timeout,
+};
+
+static int rn5t618_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rn5t618 *rn5t618 = dev_get_drvdata(dev->parent);
+ struct rn5t618_wdt *wdt;
+ int min_timeout, max_timeout;
+
+ wdt = devm_kzalloc(dev, sizeof(struct rn5t618_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ min_timeout = rn5t618_wdt_map[0].time;
+ max_timeout = rn5t618_wdt_map[ARRAY_SIZE(rn5t618_wdt_map) - 1].time;
+
+ wdt->rn5t618 = rn5t618;
+ wdt->wdt_dev.info = &rn5t618_wdt_info;
+ wdt->wdt_dev.ops = &rn5t618_wdt_ops;
+ wdt->wdt_dev.min_timeout = min_timeout;
+ wdt->wdt_dev.max_timeout = max_timeout;
+ wdt->wdt_dev.timeout = max_timeout;
+ wdt->wdt_dev.parent = dev;
+
+ watchdog_set_drvdata(&wdt->wdt_dev, wdt);
+ watchdog_init_timeout(&wdt->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&wdt->wdt_dev, nowayout);
+
+ platform_set_drvdata(pdev, wdt);
+
+ return watchdog_register_device(&wdt->wdt_dev);
+}
+
+static int rn5t618_wdt_remove(struct platform_device *pdev)
+{
+ struct rn5t618_wdt *wdt = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&wdt->wdt_dev);
+
+ return 0;
+}
+
+static struct platform_driver rn5t618_wdt_driver = {
+ .probe = rn5t618_wdt_probe,
+ .remove = rn5t618_wdt_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+module_platform_driver(rn5t618_wdt_driver);
+
+MODULE_ALIAS("platform:rn5t618-wdt");
+MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>");
+MODULE_DESCRIPTION("RN5T618 watchdog driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/rt2880_wdt.c b/drivers/watchdog/rt2880_wdt.c
new file mode 100644
index 000000000..49aff8008
--- /dev/null
+++ b/drivers/watchdog/rt2880_wdt.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ralink RT288x/RT3xxx/MT76xx built-in hardware watchdog timer
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2013 John Crispin <john@phrozen.org>
+ *
+ * This driver was based on: drivers/watchdog/softdog.c
+ */
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+
+#include <asm/mach-ralink/ralink_regs.h>
+
+#define SYSC_RSTSTAT 0x38
+#define WDT_RST_CAUSE BIT(1)
+
+#define RALINK_WDT_TIMEOUT 30
+#define RALINK_WDT_PRESCALE 65536
+
+#define TIMER_REG_TMR1LOAD 0x00
+#define TIMER_REG_TMR1CTL 0x08
+
+#define TMRSTAT_TMR1RST BIT(5)
+
+#define TMR1CTL_ENABLE BIT(7)
+#define TMR1CTL_MODE_SHIFT 4
+#define TMR1CTL_MODE_MASK 0x3
+#define TMR1CTL_MODE_FREE_RUNNING 0x0
+#define TMR1CTL_MODE_PERIODIC 0x1
+#define TMR1CTL_MODE_TIMEOUT 0x2
+#define TMR1CTL_MODE_WDT 0x3
+#define TMR1CTL_PRESCALE_MASK 0xf
+#define TMR1CTL_PRESCALE_65536 0xf
+
+static struct clk *rt288x_wdt_clk;
+static unsigned long rt288x_wdt_freq;
+static void __iomem *rt288x_wdt_base;
+static struct reset_control *rt288x_wdt_reset;
+
+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) ")");
+
+static inline void rt_wdt_w32(unsigned reg, u32 val)
+{
+ iowrite32(val, rt288x_wdt_base + reg);
+}
+
+static inline u32 rt_wdt_r32(unsigned reg)
+{
+ return ioread32(rt288x_wdt_base + reg);
+}
+
+static int rt288x_wdt_ping(struct watchdog_device *w)
+{
+ rt_wdt_w32(TIMER_REG_TMR1LOAD, w->timeout * rt288x_wdt_freq);
+
+ return 0;
+}
+
+static int rt288x_wdt_start(struct watchdog_device *w)
+{
+ u32 t;
+
+ t = rt_wdt_r32(TIMER_REG_TMR1CTL);
+ t &= ~(TMR1CTL_MODE_MASK << TMR1CTL_MODE_SHIFT |
+ TMR1CTL_PRESCALE_MASK);
+ t |= (TMR1CTL_MODE_WDT << TMR1CTL_MODE_SHIFT |
+ TMR1CTL_PRESCALE_65536);
+ rt_wdt_w32(TIMER_REG_TMR1CTL, t);
+
+ rt288x_wdt_ping(w);
+
+ t = rt_wdt_r32(TIMER_REG_TMR1CTL);
+ t |= TMR1CTL_ENABLE;
+ rt_wdt_w32(TIMER_REG_TMR1CTL, t);
+
+ return 0;
+}
+
+static int rt288x_wdt_stop(struct watchdog_device *w)
+{
+ u32 t;
+
+ rt288x_wdt_ping(w);
+
+ t = rt_wdt_r32(TIMER_REG_TMR1CTL);
+ t &= ~TMR1CTL_ENABLE;
+ rt_wdt_w32(TIMER_REG_TMR1CTL, t);
+
+ return 0;
+}
+
+static int rt288x_wdt_set_timeout(struct watchdog_device *w, unsigned int t)
+{
+ w->timeout = t;
+ rt288x_wdt_ping(w);
+
+ return 0;
+}
+
+static int rt288x_wdt_bootcause(void)
+{
+ if (rt_sysc_r32(SYSC_RSTSTAT) & WDT_RST_CAUSE)
+ return WDIOF_CARDRESET;
+
+ return 0;
+}
+
+static const struct watchdog_info rt288x_wdt_info = {
+ .identity = "Ralink Watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops rt288x_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rt288x_wdt_start,
+ .stop = rt288x_wdt_stop,
+ .ping = rt288x_wdt_ping,
+ .set_timeout = rt288x_wdt_set_timeout,
+};
+
+static struct watchdog_device rt288x_wdt_dev = {
+ .info = &rt288x_wdt_info,
+ .ops = &rt288x_wdt_ops,
+ .min_timeout = 1,
+};
+
+static int rt288x_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ rt288x_wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rt288x_wdt_base))
+ return PTR_ERR(rt288x_wdt_base);
+
+ rt288x_wdt_clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(rt288x_wdt_clk))
+ return PTR_ERR(rt288x_wdt_clk);
+
+ rt288x_wdt_reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (!IS_ERR(rt288x_wdt_reset))
+ reset_control_deassert(rt288x_wdt_reset);
+
+ rt288x_wdt_freq = clk_get_rate(rt288x_wdt_clk) / RALINK_WDT_PRESCALE;
+
+ rt288x_wdt_dev.bootstatus = rt288x_wdt_bootcause();
+ rt288x_wdt_dev.max_timeout = (0xfffful / rt288x_wdt_freq);
+ rt288x_wdt_dev.parent = dev;
+
+ watchdog_init_timeout(&rt288x_wdt_dev, rt288x_wdt_dev.max_timeout,
+ dev);
+ watchdog_set_nowayout(&rt288x_wdt_dev, nowayout);
+
+ watchdog_stop_on_reboot(&rt288x_wdt_dev);
+ ret = devm_watchdog_register_device(dev, &rt288x_wdt_dev);
+ if (!ret)
+ dev_info(dev, "Initialized\n");
+
+ return 0;
+}
+
+static const struct of_device_id rt288x_wdt_match[] = {
+ { .compatible = "ralink,rt2880-wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rt288x_wdt_match);
+
+static struct platform_driver rt288x_wdt_driver = {
+ .probe = rt288x_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = rt288x_wdt_match,
+ },
+};
+
+module_platform_driver(rt288x_wdt_driver);
+
+MODULE_DESCRIPTION("MediaTek/Ralink RT288x/RT3xxx hardware watchdog driver");
+MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/rtd119x_wdt.c b/drivers/watchdog/rtd119x_wdt.c
new file mode 100644
index 000000000..834b94ff3
--- /dev/null
+++ b/drivers/watchdog/rtd119x_wdt.c
@@ -0,0 +1,153 @@
+/*
+ * Realtek RTD129x watchdog
+ *
+ * Copyright (c) 2017 Andreas Färber
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define RTD119X_TCWCR 0x0
+#define RTD119X_TCWTR 0x4
+#define RTD119X_TCWOV 0xc
+
+#define RTD119X_TCWCR_WDEN_DISABLED 0xa5
+#define RTD119X_TCWCR_WDEN_ENABLED 0xff
+#define RTD119X_TCWCR_WDEN_MASK 0xff
+
+#define RTD119X_TCWTR_WDCLR BIT(0)
+
+struct rtd119x_watchdog_device {
+ struct watchdog_device wdt_dev;
+ void __iomem *base;
+ struct clk *clk;
+};
+
+static int rtd119x_wdt_start(struct watchdog_device *wdev)
+{
+ struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
+ u32 val;
+
+ val = readl_relaxed(data->base + RTD119X_TCWCR);
+ val &= ~RTD119X_TCWCR_WDEN_MASK;
+ val |= RTD119X_TCWCR_WDEN_ENABLED;
+ writel(val, data->base + RTD119X_TCWCR);
+
+ return 0;
+}
+
+static int rtd119x_wdt_stop(struct watchdog_device *wdev)
+{
+ struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
+ u32 val;
+
+ val = readl_relaxed(data->base + RTD119X_TCWCR);
+ val &= ~RTD119X_TCWCR_WDEN_MASK;
+ val |= RTD119X_TCWCR_WDEN_DISABLED;
+ writel(val, data->base + RTD119X_TCWCR);
+
+ return 0;
+}
+
+static int rtd119x_wdt_ping(struct watchdog_device *wdev)
+{
+ struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
+
+ writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
+
+ return rtd119x_wdt_start(wdev);
+}
+
+static int rtd119x_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val)
+{
+ struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
+
+ writel(val * clk_get_rate(data->clk), data->base + RTD119X_TCWOV);
+
+ data->wdt_dev.timeout = val;
+
+ return 0;
+}
+
+static const struct watchdog_ops rtd119x_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rtd119x_wdt_start,
+ .stop = rtd119x_wdt_stop,
+ .ping = rtd119x_wdt_ping,
+ .set_timeout = rtd119x_wdt_set_timeout,
+};
+
+static const struct watchdog_info rtd119x_wdt_info = {
+ .identity = "rtd119x-wdt",
+ .options = 0,
+};
+
+static const struct of_device_id rtd119x_wdt_dt_ids[] = {
+ { .compatible = "realtek,rtd1295-watchdog" },
+ { }
+};
+
+static void rtd119x_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int rtd119x_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtd119x_watchdog_device *data;
+ int ret;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(data->base))
+ return PTR_ERR(data->base);
+
+ data->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(data->clk))
+ return PTR_ERR(data->clk);
+
+ ret = clk_prepare_enable(data->clk);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, rtd119x_clk_disable_unprepare,
+ data->clk);
+ if (ret)
+ return ret;
+
+ data->wdt_dev.info = &rtd119x_wdt_info;
+ data->wdt_dev.ops = &rtd119x_wdt_ops;
+ data->wdt_dev.timeout = 120;
+ data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk);
+ data->wdt_dev.min_timeout = 1;
+ data->wdt_dev.parent = dev;
+
+ watchdog_stop_on_reboot(&data->wdt_dev);
+ watchdog_set_drvdata(&data->wdt_dev, data);
+ platform_set_drvdata(pdev, data);
+
+ writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
+ rtd119x_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
+ rtd119x_wdt_stop(&data->wdt_dev);
+
+ return devm_watchdog_register_device(dev, &data->wdt_dev);
+}
+
+static struct platform_driver rtd119x_wdt_driver = {
+ .probe = rtd119x_wdt_probe,
+ .driver = {
+ .name = "rtd1295-watchdog",
+ .of_match_table = rtd119x_wdt_dt_ids,
+ },
+};
+builtin_platform_driver(rtd119x_wdt_driver);
diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c
new file mode 100644
index 000000000..ea617c0f9
--- /dev/null
+++ b/drivers/watchdog/rti_wdt.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for the K3 RTI module
+ *
+ * (c) Copyright 2019-2020 Texas Instruments Inc.
+ * All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define DEFAULT_HEARTBEAT 60
+
+/* Max heartbeat is calculated at 32kHz source clock */
+#define MAX_HEARTBEAT 1000
+
+/* Timer register set definition */
+#define RTIDWDCTRL 0x90
+#define RTIDWDPRLD 0x94
+#define RTIWDSTATUS 0x98
+#define RTIWDKEY 0x9c
+#define RTIDWDCNTR 0xa0
+#define RTIWWDRXCTRL 0xa4
+#define RTIWWDSIZECTRL 0xa8
+
+#define RTIWWDRX_NMI 0xa
+
+#define RTIWWDSIZE_50P 0x50
+#define RTIWWDSIZE_25P 0x500
+#define RTIWWDSIZE_12P5 0x5000
+#define RTIWWDSIZE_6P25 0x50000
+#define RTIWWDSIZE_3P125 0x500000
+
+#define WDENABLE_KEY 0xa98559da
+
+#define WDKEY_SEQ0 0xe51a
+#define WDKEY_SEQ1 0xa35c
+
+#define WDT_PRELOAD_SHIFT 13
+
+#define WDT_PRELOAD_MAX 0xfff
+
+#define DWDST BIT(1)
+
+static int heartbeat = DEFAULT_HEARTBEAT;
+
+/*
+ * 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
+ */
+struct rti_wdt_device {
+ void __iomem *base;
+ unsigned long freq;
+ struct watchdog_device wdd;
+};
+
+static int rti_wdt_start(struct watchdog_device *wdd)
+{
+ u32 timer_margin;
+ struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(wdd->parent);
+ if (ret)
+ return ret;
+
+ /* set timeout period */
+ timer_margin = (u64)wdd->timeout * wdt->freq;
+ timer_margin >>= WDT_PRELOAD_SHIFT;
+ if (timer_margin > WDT_PRELOAD_MAX)
+ timer_margin = WDT_PRELOAD_MAX;
+ writel_relaxed(timer_margin, wdt->base + RTIDWDPRLD);
+
+ /*
+ * RTI only supports a windowed mode, where the watchdog can only
+ * be petted during the open window; not too early or not too late.
+ * The HW configuration options only allow for the open window size
+ * to be 50% or less than that; we obviouly want to configure the open
+ * window as large as possible so we select the 50% option.
+ */
+ wdd->min_hw_heartbeat_ms = 500 * wdd->timeout;
+
+ /* Generate NMI when wdt expires */
+ writel_relaxed(RTIWWDRX_NMI, wdt->base + RTIWWDRXCTRL);
+
+ /* Open window size 50%; this is the largest window size available */
+ writel_relaxed(RTIWWDSIZE_50P, wdt->base + RTIWWDSIZECTRL);
+
+ readl_relaxed(wdt->base + RTIWWDSIZECTRL);
+
+ /* enable watchdog */
+ writel_relaxed(WDENABLE_KEY, wdt->base + RTIDWDCTRL);
+ return 0;
+}
+
+static int rti_wdt_ping(struct watchdog_device *wdd)
+{
+ struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
+
+ /* put watchdog in service state */
+ writel_relaxed(WDKEY_SEQ0, wdt->base + RTIWDKEY);
+ /* put watchdog in active state */
+ writel_relaxed(WDKEY_SEQ1, wdt->base + RTIWDKEY);
+
+ return 0;
+}
+
+static int rti_wdt_setup_hw_hb(struct watchdog_device *wdd, u32 wsize)
+{
+ /*
+ * RTI only supports a windowed mode, where the watchdog can only
+ * be petted during the open window; not too early or not too late.
+ * The HW configuration options only allow for the open window size
+ * to be 50% or less than that.
+ */
+ switch (wsize) {
+ case RTIWWDSIZE_50P:
+ /* 50% open window => 50% min heartbeat */
+ wdd->min_hw_heartbeat_ms = 500 * heartbeat;
+ break;
+
+ case RTIWWDSIZE_25P:
+ /* 25% open window => 75% min heartbeat */
+ wdd->min_hw_heartbeat_ms = 750 * heartbeat;
+ break;
+
+ case RTIWWDSIZE_12P5:
+ /* 12.5% open window => 87.5% min heartbeat */
+ wdd->min_hw_heartbeat_ms = 875 * heartbeat;
+ break;
+
+ case RTIWWDSIZE_6P25:
+ /* 6.5% open window => 93.5% min heartbeat */
+ wdd->min_hw_heartbeat_ms = 935 * heartbeat;
+ break;
+
+ case RTIWWDSIZE_3P125:
+ /* 3.125% open window => 96.9% min heartbeat */
+ wdd->min_hw_heartbeat_ms = 969 * heartbeat;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static unsigned int rti_wdt_get_timeleft_ms(struct watchdog_device *wdd)
+{
+ u64 timer_counter;
+ u32 val;
+ struct rti_wdt_device *wdt = watchdog_get_drvdata(wdd);
+
+ /* if timeout has occurred then return 0 */
+ val = readl_relaxed(wdt->base + RTIWDSTATUS);
+ if (val & DWDST)
+ return 0;
+
+ timer_counter = readl_relaxed(wdt->base + RTIDWDCNTR);
+
+ timer_counter *= 1000;
+
+ do_div(timer_counter, wdt->freq);
+
+ return timer_counter;
+}
+
+static unsigned int rti_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ return rti_wdt_get_timeleft_ms(wdd) / 1000;
+}
+
+static const struct watchdog_info rti_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING,
+ .identity = "K3 RTI Watchdog",
+};
+
+static const struct watchdog_ops rti_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rti_wdt_start,
+ .ping = rti_wdt_ping,
+ .get_timeleft = rti_wdt_get_timeleft,
+};
+
+static int rti_wdt_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct rti_wdt_device *wdt;
+ struct clk *clk;
+ u32 last_ping = 0;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+ wdt->freq = clk_get_rate(clk);
+
+ clk_put(clk);
+
+ if (!wdt->freq) {
+ dev_err(dev, "Failed to get fck rate.\n");
+ return -EINVAL;
+ }
+
+ /*
+ * If watchdog is running at 32k clock, it is not accurate.
+ * Adjust frequency down in this case so that we don't pet
+ * the watchdog too often.
+ */
+ if (wdt->freq < 32768)
+ wdt->freq = wdt->freq * 9 / 10;
+
+ pm_runtime_enable(dev);
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret < 0) {
+ pm_runtime_disable(&pdev->dev);
+ return dev_err_probe(dev, ret, "runtime pm failed\n");
+ }
+
+ platform_set_drvdata(pdev, wdt);
+
+ wdd = &wdt->wdd;
+ wdd->info = &rti_wdt_info;
+ wdd->ops = &rti_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_hw_heartbeat_ms = (WDT_PRELOAD_MAX << WDT_PRELOAD_SHIFT) /
+ wdt->freq * 1000;
+ wdd->parent = dev;
+
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_set_nowayout(wdd, 1);
+ watchdog_set_restart_priority(wdd, 128);
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base)) {
+ ret = PTR_ERR(wdt->base);
+ goto err_iomap;
+ }
+
+ if (readl(wdt->base + RTIDWDCTRL) == WDENABLE_KEY) {
+ int preset_heartbeat;
+ u32 time_left_ms;
+ u64 heartbeat_ms;
+ u32 wsize;
+
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ time_left_ms = rti_wdt_get_timeleft_ms(wdd);
+ heartbeat_ms = readl(wdt->base + RTIDWDPRLD);
+ heartbeat_ms <<= WDT_PRELOAD_SHIFT;
+ heartbeat_ms *= 1000;
+ do_div(heartbeat_ms, wdt->freq);
+ preset_heartbeat = heartbeat_ms + 500;
+ preset_heartbeat /= 1000;
+ if (preset_heartbeat != heartbeat)
+ dev_warn(dev, "watchdog already running, ignoring heartbeat config!\n");
+
+ heartbeat = preset_heartbeat;
+
+ wsize = readl(wdt->base + RTIWWDSIZECTRL);
+ ret = rti_wdt_setup_hw_hb(wdd, wsize);
+ if (ret) {
+ dev_err(dev, "bad window size.\n");
+ goto err_iomap;
+ }
+
+ last_ping = heartbeat_ms - time_left_ms;
+ if (time_left_ms > heartbeat_ms) {
+ dev_warn(dev, "time_left > heartbeat? Assuming last ping just before now.\n");
+ last_ping = 0;
+ }
+ }
+
+ watchdog_init_timeout(wdd, heartbeat, dev);
+
+ ret = watchdog_register_device(wdd);
+ if (ret) {
+ dev_err(dev, "cannot register watchdog device\n");
+ goto err_iomap;
+ }
+
+ if (last_ping)
+ watchdog_set_last_hw_keepalive(wdd, last_ping);
+
+ if (!watchdog_hw_running(wdd))
+ pm_runtime_put_sync(&pdev->dev);
+
+ return 0;
+
+err_iomap:
+ pm_runtime_put_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int rti_wdt_remove(struct platform_device *pdev)
+{
+ struct rti_wdt_device *wdt = platform_get_drvdata(pdev);
+
+ watchdog_unregister_device(&wdt->wdd);
+
+ if (!pm_runtime_suspended(&pdev->dev))
+ pm_runtime_put(&pdev->dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id rti_wdt_of_match[] = {
+ { .compatible = "ti,j7-rti-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rti_wdt_of_match);
+
+static struct platform_driver rti_wdt_driver = {
+ .driver = {
+ .name = "rti-wdt",
+ .of_match_table = rti_wdt_of_match,
+ },
+ .probe = rti_wdt_probe,
+ .remove = rti_wdt_remove,
+};
+
+module_platform_driver(rti_wdt_driver);
+
+MODULE_AUTHOR("Tero Kristo <t-kristo@ti.com>");
+MODULE_DESCRIPTION("K3 RTI Watchdog Driver");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rti-wdt");
diff --git a/drivers/watchdog/rza_wdt.c b/drivers/watchdog/rza_wdt.c
new file mode 100644
index 000000000..fe6c2ed35
--- /dev/null
+++ b/drivers/watchdog/rza_wdt.c
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/A Series WDT Driver
+ *
+ * Copyright (C) 2017 Renesas Electronics America, Inc.
+ * Copyright (C) 2017 Chris Brandt
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define DEFAULT_TIMEOUT 30
+
+/* Watchdog Timer Registers */
+#define WTCSR 0
+#define WTCSR_MAGIC 0xA500
+#define WTSCR_WT BIT(6)
+#define WTSCR_TME BIT(5)
+#define WTSCR_CKS(i) (i)
+
+#define WTCNT 2
+#define WTCNT_MAGIC 0x5A00
+
+#define WRCSR 4
+#define WRCSR_MAGIC 0x5A00
+#define WRCSR_RSTE BIT(6)
+#define WRCSR_CLEAR_WOVF 0xA500 /* special value */
+
+/* The maximum CKS register setting value to get the longest timeout */
+#define CKS_3BIT 0x7
+#define CKS_4BIT 0xF
+
+#define DIVIDER_3BIT 16384 /* Clock divider when CKS = 0x7 */
+#define DIVIDER_4BIT 4194304 /* Clock divider when CKS = 0xF */
+
+struct rza_wdt {
+ struct watchdog_device wdev;
+ void __iomem *base;
+ struct clk *clk;
+ u8 count;
+ u8 cks;
+};
+
+static void rza_wdt_calc_timeout(struct rza_wdt *priv, int timeout)
+{
+ unsigned long rate = clk_get_rate(priv->clk);
+ unsigned int ticks;
+
+ if (priv->cks == CKS_4BIT) {
+ ticks = DIV_ROUND_UP(timeout * rate, DIVIDER_4BIT);
+
+ /*
+ * Since max_timeout was set in probe, we know that the timeout
+ * value passed will never calculate to a tick value greater
+ * than 256.
+ */
+ priv->count = 256 - ticks;
+
+ } else {
+ /* Start timer with longest timeout */
+ priv->count = 0;
+ }
+
+ pr_debug("%s: timeout set to %u (WTCNT=%d)\n", __func__,
+ timeout, priv->count);
+}
+
+static int rza_wdt_start(struct watchdog_device *wdev)
+{
+ struct rza_wdt *priv = watchdog_get_drvdata(wdev);
+
+ /* Stop timer */
+ writew(WTCSR_MAGIC | 0, priv->base + WTCSR);
+
+ /* Must dummy read WRCSR:WOVF at least once before clearing */
+ readb(priv->base + WRCSR);
+ writew(WRCSR_CLEAR_WOVF, priv->base + WRCSR);
+
+ rza_wdt_calc_timeout(priv, wdev->timeout);
+
+ writew(WRCSR_MAGIC | WRCSR_RSTE, priv->base + WRCSR);
+ writew(WTCNT_MAGIC | priv->count, priv->base + WTCNT);
+ writew(WTCSR_MAGIC | WTSCR_WT | WTSCR_TME |
+ WTSCR_CKS(priv->cks), priv->base + WTCSR);
+
+ return 0;
+}
+
+static int rza_wdt_stop(struct watchdog_device *wdev)
+{
+ struct rza_wdt *priv = watchdog_get_drvdata(wdev);
+
+ writew(WTCSR_MAGIC | 0, priv->base + WTCSR);
+
+ return 0;
+}
+
+static int rza_wdt_ping(struct watchdog_device *wdev)
+{
+ struct rza_wdt *priv = watchdog_get_drvdata(wdev);
+
+ writew(WTCNT_MAGIC | priv->count, priv->base + WTCNT);
+
+ pr_debug("%s: timeout = %u\n", __func__, wdev->timeout);
+
+ return 0;
+}
+
+static int rza_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
+{
+ wdev->timeout = timeout;
+ rza_wdt_start(wdev);
+ return 0;
+}
+
+static int rza_wdt_restart(struct watchdog_device *wdev, unsigned long action,
+ void *data)
+{
+ struct rza_wdt *priv = watchdog_get_drvdata(wdev);
+
+ /* Stop timer */
+ writew(WTCSR_MAGIC | 0, priv->base + WTCSR);
+
+ /* Must dummy read WRCSR:WOVF at least once before clearing */
+ readb(priv->base + WRCSR);
+ writew(WRCSR_CLEAR_WOVF, priv->base + WRCSR);
+
+ /*
+ * Start timer with fastest clock source and only 1 clock left before
+ * overflow with reset option enabled.
+ */
+ writew(WRCSR_MAGIC | WRCSR_RSTE, priv->base + WRCSR);
+ writew(WTCNT_MAGIC | 255, priv->base + WTCNT);
+ writew(WTCSR_MAGIC | WTSCR_WT | WTSCR_TME, priv->base + WTCSR);
+
+ /*
+ * Actually make sure the above sequence hits hardware before sleeping.
+ */
+ wmb();
+
+ /* Wait for WDT overflow (reset) */
+ udelay(20);
+
+ return 0;
+}
+
+static const struct watchdog_info rza_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+ .identity = "Renesas RZ/A WDT Watchdog",
+};
+
+static const struct watchdog_ops rza_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rza_wdt_start,
+ .stop = rza_wdt_stop,
+ .ping = rza_wdt_ping,
+ .set_timeout = rza_set_timeout,
+ .restart = rza_wdt_restart,
+};
+
+static int rza_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rza_wdt *priv;
+ unsigned long rate;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return PTR_ERR(priv->clk);
+
+ rate = clk_get_rate(priv->clk);
+ if (rate < 16384) {
+ dev_err(dev, "invalid clock rate (%ld)\n", rate);
+ return -ENOENT;
+ }
+
+ priv->wdev.info = &rza_wdt_ident;
+ priv->wdev.ops = &rza_wdt_ops;
+ priv->wdev.parent = dev;
+
+ priv->cks = (u8)(uintptr_t) of_device_get_match_data(dev);
+ if (priv->cks == CKS_4BIT) {
+ /* Assume slowest clock rate possible (CKS=0xF) */
+ priv->wdev.max_timeout = (DIVIDER_4BIT * U8_MAX) / rate;
+
+ } else if (priv->cks == CKS_3BIT) {
+ /* Assume slowest clock rate possible (CKS=7) */
+ rate /= DIVIDER_3BIT;
+
+ /*
+ * Since the max possible timeout of our 8-bit count
+ * register is less than a second, we must use
+ * max_hw_heartbeat_ms.
+ */
+ priv->wdev.max_hw_heartbeat_ms = (1000 * U8_MAX) / rate;
+ dev_dbg(dev, "max hw timeout of %dms\n",
+ priv->wdev.max_hw_heartbeat_ms);
+ }
+
+ priv->wdev.min_timeout = 1;
+ priv->wdev.timeout = DEFAULT_TIMEOUT;
+
+ watchdog_init_timeout(&priv->wdev, 0, dev);
+ watchdog_set_drvdata(&priv->wdev, priv);
+
+ ret = devm_watchdog_register_device(dev, &priv->wdev);
+ if (ret)
+ dev_err(dev, "Cannot register watchdog device\n");
+
+ return ret;
+}
+
+static const struct of_device_id rza_wdt_of_match[] = {
+ { .compatible = "renesas,r7s9210-wdt", .data = (void *)CKS_4BIT, },
+ { .compatible = "renesas,rza-wdt", .data = (void *)CKS_3BIT, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rza_wdt_of_match);
+
+static struct platform_driver rza_wdt_driver = {
+ .probe = rza_wdt_probe,
+ .driver = {
+ .name = "rza_wdt",
+ .of_match_table = rza_wdt_of_match,
+ },
+};
+
+module_platform_driver(rza_wdt_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/A WDT Driver");
+MODULE_AUTHOR("Chris Brandt <chris.brandt@renesas.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/rzg2l_wdt.c b/drivers/watchdog/rzg2l_wdt.c
new file mode 100644
index 000000000..d404953d0
--- /dev/null
+++ b/drivers/watchdog/rzg2l_wdt.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L WDT Watchdog Driver
+ *
+ * Copyright (C) 2021 Renesas Electronics Corporation
+ */
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/units.h>
+#include <linux/watchdog.h>
+
+#define WDTCNT 0x00
+#define WDTSET 0x04
+#define WDTTIM 0x08
+#define WDTINT 0x0C
+#define PECR 0x10
+#define PEEN 0x14
+#define WDTCNT_WDTEN BIT(0)
+#define WDTINT_INTDISP BIT(0)
+#define PEEN_FORCE BIT(0)
+
+#define WDT_DEFAULT_TIMEOUT 60U
+
+/* Setting period time register only 12 bit set in WDTSET[31:20] */
+#define WDTSET_COUNTER_MASK (0xFFF00000)
+#define WDTSET_COUNTER_VAL(f) ((f) << 20)
+
+#define F2CYCLE_NSEC(f) (1000000000 / (f))
+
+#define RZV2M_A_NSEC 730
+
+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) ")");
+
+enum rz_wdt_type {
+ WDT_RZG2L,
+ WDT_RZV2M,
+};
+
+struct rzg2l_wdt_priv {
+ void __iomem *base;
+ struct watchdog_device wdev;
+ struct reset_control *rstc;
+ unsigned long osc_clk_rate;
+ unsigned long delay;
+ unsigned long minimum_assertion_period;
+ struct clk *pclk;
+ struct clk *osc_clk;
+ enum rz_wdt_type devtype;
+};
+
+static int rzg2l_wdt_reset(struct rzg2l_wdt_priv *priv)
+{
+ int err, status;
+
+ if (priv->devtype == WDT_RZV2M) {
+ /* WDT needs TYPE-B reset control */
+ err = reset_control_assert(priv->rstc);
+ if (err)
+ return err;
+ ndelay(priv->minimum_assertion_period);
+ err = reset_control_deassert(priv->rstc);
+ if (err)
+ return err;
+ err = read_poll_timeout(reset_control_status, status,
+ status != 1, 0, 1000, false,
+ priv->rstc);
+ } else {
+ err = reset_control_reset(priv->rstc);
+ }
+
+ return err;
+}
+
+static void rzg2l_wdt_wait_delay(struct rzg2l_wdt_priv *priv)
+{
+ /* delay timer when change the setting register */
+ ndelay(priv->delay);
+}
+
+static u32 rzg2l_wdt_get_cycle_usec(unsigned long cycle, u32 wdttime)
+{
+ u64 timer_cycle_us = 1024 * 1024ULL * (wdttime + 1) * MICRO;
+
+ return div64_ul(timer_cycle_us, cycle);
+}
+
+static void rzg2l_wdt_write(struct rzg2l_wdt_priv *priv, u32 val, unsigned int reg)
+{
+ if (reg == WDTSET)
+ val &= WDTSET_COUNTER_MASK;
+
+ writel_relaxed(val, priv->base + reg);
+ /* Registers other than the WDTINT is always synchronized with WDT_CLK */
+ if (reg != WDTINT)
+ rzg2l_wdt_wait_delay(priv);
+}
+
+static void rzg2l_wdt_init_timeout(struct watchdog_device *wdev)
+{
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ u32 time_out;
+
+ /* Clear Lapsed Time Register and clear Interrupt */
+ rzg2l_wdt_write(priv, WDTINT_INTDISP, WDTINT);
+ /* 2 consecutive overflow cycle needed to trigger reset */
+ time_out = (wdev->timeout * (MICRO / 2)) /
+ rzg2l_wdt_get_cycle_usec(priv->osc_clk_rate, 0);
+ rzg2l_wdt_write(priv, WDTSET_COUNTER_VAL(time_out), WDTSET);
+}
+
+static int rzg2l_wdt_start(struct watchdog_device *wdev)
+{
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ pm_runtime_get_sync(wdev->parent);
+
+ /* Initialize time out */
+ rzg2l_wdt_init_timeout(wdev);
+
+ /* Initialize watchdog counter register */
+ rzg2l_wdt_write(priv, 0, WDTTIM);
+
+ /* Enable watchdog timer*/
+ rzg2l_wdt_write(priv, WDTCNT_WDTEN, WDTCNT);
+
+ return 0;
+}
+
+static int rzg2l_wdt_stop(struct watchdog_device *wdev)
+{
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rzg2l_wdt_reset(priv);
+ pm_runtime_put(wdev->parent);
+
+ return 0;
+}
+
+static int rzg2l_wdt_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
+{
+ wdev->timeout = timeout;
+
+ /*
+ * If the watchdog is active, reset the module for updating the WDTSET
+ * register by calling rzg2l_wdt_stop() (which internally calls reset_control_reset()
+ * to reset the module) so that it is updated with new timeout values.
+ */
+ if (watchdog_active(wdev)) {
+ rzg2l_wdt_stop(wdev);
+ rzg2l_wdt_start(wdev);
+ }
+
+ return 0;
+}
+
+static int rzg2l_wdt_restart(struct watchdog_device *wdev,
+ unsigned long action, void *data)
+{
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ clk_prepare_enable(priv->pclk);
+ clk_prepare_enable(priv->osc_clk);
+
+ if (priv->devtype == WDT_RZG2L) {
+ /* Generate Reset (WDTRSTB) Signal on parity error */
+ rzg2l_wdt_write(priv, 0, PECR);
+
+ /* Force parity error */
+ rzg2l_wdt_write(priv, PEEN_FORCE, PEEN);
+ } else {
+ /* RZ/V2M doesn't have parity error registers */
+ rzg2l_wdt_reset(priv);
+
+ wdev->timeout = 0;
+
+ /* Initialize time out */
+ rzg2l_wdt_init_timeout(wdev);
+
+ /* Initialize watchdog counter register */
+ rzg2l_wdt_write(priv, 0, WDTTIM);
+
+ /* Enable watchdog timer*/
+ rzg2l_wdt_write(priv, WDTCNT_WDTEN, WDTCNT);
+
+ /* Wait 2 consecutive overflow cycles for reset */
+ mdelay(DIV_ROUND_UP(2 * 0xFFFFF * 1000, priv->osc_clk_rate));
+ }
+
+ return 0;
+}
+
+static const struct watchdog_info rzg2l_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+ .identity = "Renesas RZ/G2L WDT Watchdog",
+};
+
+static int rzg2l_wdt_ping(struct watchdog_device *wdev)
+{
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ rzg2l_wdt_write(priv, WDTINT_INTDISP, WDTINT);
+
+ return 0;
+}
+
+static const struct watchdog_ops rzg2l_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rzg2l_wdt_start,
+ .stop = rzg2l_wdt_stop,
+ .ping = rzg2l_wdt_ping,
+ .set_timeout = rzg2l_wdt_set_timeout,
+ .restart = rzg2l_wdt_restart,
+};
+
+static void rzg2l_wdt_reset_assert_pm_disable(void *data)
+{
+ struct watchdog_device *wdev = data;
+ struct rzg2l_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ pm_runtime_disable(wdev->parent);
+ reset_control_assert(priv->rstc);
+}
+
+static int rzg2l_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rzg2l_wdt_priv *priv;
+ unsigned long pclk_rate;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ /* Get watchdog main clock */
+ priv->osc_clk = devm_clk_get(&pdev->dev, "oscclk");
+ if (IS_ERR(priv->osc_clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->osc_clk), "no oscclk");
+
+ priv->osc_clk_rate = clk_get_rate(priv->osc_clk);
+ if (!priv->osc_clk_rate)
+ return dev_err_probe(&pdev->dev, -EINVAL, "oscclk rate is 0");
+
+ /* Get Peripheral clock */
+ priv->pclk = devm_clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(priv->pclk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->pclk), "no pclk");
+
+ pclk_rate = clk_get_rate(priv->pclk);
+ if (!pclk_rate)
+ return dev_err_probe(&pdev->dev, -EINVAL, "pclk rate is 0");
+
+ priv->delay = F2CYCLE_NSEC(priv->osc_clk_rate) * 6 + F2CYCLE_NSEC(pclk_rate) * 9;
+
+ priv->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(&pdev->dev, PTR_ERR(priv->rstc),
+ "failed to get cpg reset");
+
+ ret = reset_control_deassert(priv->rstc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to deassert");
+
+ priv->devtype = (uintptr_t)of_device_get_match_data(dev);
+
+ if (priv->devtype == WDT_RZV2M) {
+ priv->minimum_assertion_period = RZV2M_A_NSEC +
+ 3 * F2CYCLE_NSEC(pclk_rate) + 5 *
+ max(F2CYCLE_NSEC(priv->osc_clk_rate),
+ F2CYCLE_NSEC(pclk_rate));
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ priv->wdev.info = &rzg2l_wdt_ident;
+ priv->wdev.ops = &rzg2l_wdt_ops;
+ priv->wdev.parent = dev;
+ priv->wdev.min_timeout = 1;
+ priv->wdev.max_timeout = rzg2l_wdt_get_cycle_usec(priv->osc_clk_rate, 0xfff) /
+ USEC_PER_SEC;
+ priv->wdev.timeout = WDT_DEFAULT_TIMEOUT;
+
+ watchdog_set_drvdata(&priv->wdev, priv);
+ ret = devm_add_action_or_reset(&pdev->dev,
+ rzg2l_wdt_reset_assert_pm_disable,
+ &priv->wdev);
+ if (ret < 0)
+ return ret;
+
+ watchdog_set_nowayout(&priv->wdev, nowayout);
+ watchdog_stop_on_unregister(&priv->wdev);
+
+ ret = watchdog_init_timeout(&priv->wdev, 0, dev);
+ if (ret)
+ dev_warn(dev, "Specified timeout invalid, using default");
+
+ return devm_watchdog_register_device(&pdev->dev, &priv->wdev);
+}
+
+static const struct of_device_id rzg2l_wdt_ids[] = {
+ { .compatible = "renesas,rzg2l-wdt", .data = (void *)WDT_RZG2L },
+ { .compatible = "renesas,rzv2m-wdt", .data = (void *)WDT_RZV2M },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg2l_wdt_ids);
+
+static struct platform_driver rzg2l_wdt_driver = {
+ .driver = {
+ .name = "rzg2l_wdt",
+ .of_match_table = rzg2l_wdt_ids,
+ },
+ .probe = rzg2l_wdt_probe,
+};
+module_platform_driver(rzg2l_wdt_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/G2L WDT Watchdog Driver");
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/rzn1_wdt.c b/drivers/watchdog/rzn1_wdt.c
new file mode 100644
index 000000000..55ab384b9
--- /dev/null
+++ b/drivers/watchdog/rzn1_wdt.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/N1 Watchdog timer.
+ * This is a 12-bit timer driver from a (62.5/16384) MHz clock. It can't even
+ * cope with 2 seconds.
+ *
+ * Copyright 2018 Renesas Electronics Europe Ltd.
+ *
+ * Derived from Ralink RT288x watchdog timer.
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+
+#define DEFAULT_TIMEOUT 60
+
+#define RZN1_WDT_RETRIGGER 0x0
+#define RZN1_WDT_RETRIGGER_RELOAD_VAL 0
+#define RZN1_WDT_RETRIGGER_RELOAD_VAL_MASK 0xfff
+#define RZN1_WDT_RETRIGGER_PRESCALE BIT(12)
+#define RZN1_WDT_RETRIGGER_ENABLE BIT(13)
+#define RZN1_WDT_RETRIGGER_WDSI (0x2 << 14)
+
+#define RZN1_WDT_PRESCALER 16384
+#define RZN1_WDT_MAX 4095
+
+struct rzn1_watchdog {
+ struct watchdog_device wdtdev;
+ void __iomem *base;
+ unsigned long clk_rate_khz;
+};
+
+static inline uint32_t max_heart_beat_ms(unsigned long clk_rate_khz)
+{
+ return (RZN1_WDT_MAX * RZN1_WDT_PRESCALER) / clk_rate_khz;
+}
+
+static inline uint32_t compute_reload_value(uint32_t tick_ms,
+ unsigned long clk_rate_khz)
+{
+ return (tick_ms * clk_rate_khz) / RZN1_WDT_PRESCALER;
+}
+
+static int rzn1_wdt_ping(struct watchdog_device *w)
+{
+ struct rzn1_watchdog *wdt = watchdog_get_drvdata(w);
+
+ /* Any value retrigggers the watchdog */
+ writel(0, wdt->base + RZN1_WDT_RETRIGGER);
+
+ return 0;
+}
+
+static int rzn1_wdt_start(struct watchdog_device *w)
+{
+ struct rzn1_watchdog *wdt = watchdog_get_drvdata(w);
+ u32 val;
+
+ /*
+ * The hardware allows you to write to this reg only once.
+ * Since this includes the reload value, there is no way to change the
+ * timeout once started. Also note that the WDT clock is half the bus
+ * fabric clock rate, so if the bus fabric clock rate is changed after
+ * the WDT is started, the WDT interval will be wrong.
+ */
+ val = RZN1_WDT_RETRIGGER_WDSI;
+ val |= RZN1_WDT_RETRIGGER_ENABLE;
+ val |= RZN1_WDT_RETRIGGER_PRESCALE;
+ val |= compute_reload_value(w->max_hw_heartbeat_ms, wdt->clk_rate_khz);
+ writel(val, wdt->base + RZN1_WDT_RETRIGGER);
+
+ 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,
+};
+
+static const struct watchdog_ops rzn1_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = rzn1_wdt_start,
+ .ping = rzn1_wdt_ping,
+};
+
+static void rzn1_wdt_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+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;
+ int ret;
+ int irq;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ 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(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "failed to get the clock\n");
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(dev, "failed to prepare/enable the clock\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, rzn1_wdt_clk_disable_unprepare,
+ clk);
+ if (ret)
+ return ret;
+
+ clk_rate = clk_get_rate(clk);
+ if (!clk_rate) {
+ dev_err(dev, "failed to get the clock rate\n");
+ return -EINVAL;
+ }
+
+ wdt->clk_rate_khz = clk_rate / 1000;
+ wdt->wdtdev.info = &rzn1_wdt_info,
+ wdt->wdtdev.ops = &rzn1_wdt_ops,
+ wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
+ wdt->wdtdev.parent = dev;
+ /*
+ * The period of the watchdog cannot be changed once set
+ * and is limited to a very short period.
+ * Configure it for a 1s period once and for all, and
+ * rely on the heart-beat provided by the watchdog core
+ * to make this usable by the user-space.
+ */
+ wdt->wdtdev.max_hw_heartbeat_ms = max_heart_beat_ms(wdt->clk_rate_khz);
+ if (wdt->wdtdev.max_hw_heartbeat_ms > 1000)
+ wdt->wdtdev.max_hw_heartbeat_ms = 1000;
+
+ wdt->wdtdev.timeout = DEFAULT_TIMEOUT;
+ ret = watchdog_init_timeout(&wdt->wdtdev, 0, dev);
+ if (ret)
+ return ret;
+
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+
+ return devm_watchdog_register_device(dev, &wdt->wdtdev);
+}
+
+
+static const struct of_device_id rzn1_wdt_match[] = {
+ { .compatible = "renesas,rzn1-wdt" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, rzn1_wdt_match);
+
+static struct platform_driver rzn1_wdt_driver = {
+ .probe = rzn1_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = rzn1_wdt_match,
+ },
+};
+
+module_platform_driver(rzn1_wdt_driver);
+
+MODULE_DESCRIPTION("Renesas RZ/N1 hardware watchdog");
+MODULE_AUTHOR("Phil Edworthy <phil.edworthy@renesas.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c
new file mode 100644
index 000000000..d3fc8ed88
--- /dev/null
+++ b/drivers/watchdog/s3c2410_wdt.c
@@ -0,0 +1,939 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410 Watchdog Timer Support
+ *
+ * Based on, softdog.c by Alan Cox,
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/cpufreq.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+
+#define S3C2410_WTCON 0x00
+#define S3C2410_WTDAT 0x04
+#define S3C2410_WTCNT 0x08
+#define S3C2410_WTCLRINT 0x0c
+
+#define S3C2410_WTCNT_MAXCNT 0xffff
+
+#define S3C2410_WTCON_RSTEN (1 << 0)
+#define S3C2410_WTCON_INTEN (1 << 2)
+#define S3C2410_WTCON_ENABLE (1 << 5)
+
+#define S3C2410_WTCON_DIV16 (0 << 3)
+#define S3C2410_WTCON_DIV32 (1 << 3)
+#define S3C2410_WTCON_DIV64 (2 << 3)
+#define S3C2410_WTCON_DIV128 (3 << 3)
+
+#define S3C2410_WTCON_MAXDIV 0x80
+
+#define S3C2410_WTCON_PRESCALE(x) ((x) << 8)
+#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8)
+#define S3C2410_WTCON_PRESCALE_MAX 0xff
+
+#define S3C2410_WATCHDOG_ATBOOT (0)
+#define S3C2410_WATCHDOG_DEFAULT_TIME (15)
+
+#define EXYNOS5_RST_STAT_REG_OFFSET 0x0404
+#define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408
+#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c
+#define EXYNOS850_CLUSTER0_NONCPU_OUT 0x1220
+#define EXYNOS850_CLUSTER0_NONCPU_INT_EN 0x1244
+#define EXYNOS850_CLUSTER1_NONCPU_OUT 0x1620
+#define EXYNOS850_CLUSTER1_NONCPU_INT_EN 0x1644
+#define EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT 0x1520
+#define EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN 0x1544
+
+#define EXYNOS850_CLUSTER0_WDTRESET_BIT 24
+#define EXYNOS850_CLUSTER1_WDTRESET_BIT 23
+#define EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT 25
+#define EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT 24
+
+/**
+ * DOC: Quirk flags for different Samsung watchdog IP-cores
+ *
+ * This driver supports multiple Samsung SoCs, each of which might have
+ * different set of registers and features supported. As watchdog block
+ * sometimes requires modifying PMU registers for proper functioning, register
+ * differences in both watchdog and PMU IP-cores should be accounted for. Quirk
+ * flags described below serve the purpose of telling the driver about mentioned
+ * SoC traits, and can be specified in driver data for each particular supported
+ * device.
+ *
+ * %QUIRK_HAS_WTCLRINT_REG: Watchdog block has WTCLRINT register. It's used to
+ * clear the interrupt once the interrupt service routine is complete. It's
+ * write-only, writing any values to this register clears the interrupt, but
+ * reading is not permitted.
+ *
+ * %QUIRK_HAS_PMU_MASK_RESET: PMU block has the register for disabling/enabling
+ * WDT reset request. On old SoCs it's usually called MASK_WDT_RESET_REQUEST,
+ * new SoCs have CLUSTERx_NONCPU_INT_EN register, which 'mask_bit' value is
+ * inverted compared to the former one.
+ *
+ * %QUIRK_HAS_PMU_RST_STAT: PMU block has RST_STAT (reset status) register,
+ * which contains bits indicating the reason for most recent CPU reset. If
+ * present, driver will use this register to check if previous reboot was due to
+ * watchdog timer reset.
+ *
+ * %QUIRK_HAS_PMU_AUTO_DISABLE: PMU block has AUTOMATIC_WDT_RESET_DISABLE
+ * register. If 'mask_bit' bit is set, PMU will disable WDT reset when
+ * corresponding processor is in reset state.
+ *
+ * %QUIRK_HAS_PMU_CNT_EN: PMU block has some register (e.g. CLUSTERx_NONCPU_OUT)
+ * with "watchdog counter enable" bit. That bit should be set to make watchdog
+ * counter running.
+ */
+#define QUIRK_HAS_WTCLRINT_REG (1 << 0)
+#define QUIRK_HAS_PMU_MASK_RESET (1 << 1)
+#define QUIRK_HAS_PMU_RST_STAT (1 << 2)
+#define QUIRK_HAS_PMU_AUTO_DISABLE (1 << 3)
+#define QUIRK_HAS_PMU_CNT_EN (1 << 4)
+
+/* These quirks require that we have a PMU register map */
+#define QUIRKS_HAVE_PMUREG \
+ (QUIRK_HAS_PMU_MASK_RESET | QUIRK_HAS_PMU_RST_STAT | \
+ QUIRK_HAS_PMU_AUTO_DISABLE | QUIRK_HAS_PMU_CNT_EN)
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static int tmr_margin;
+static int tmr_atboot = S3C2410_WATCHDOG_ATBOOT;
+static int soft_noboot;
+
+module_param(tmr_margin, int, 0);
+module_param(tmr_atboot, int, 0);
+module_param(nowayout, bool, 0);
+module_param(soft_noboot, int, 0);
+
+MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default="
+ __MODULE_STRING(S3C2410_WATCHDOG_DEFAULT_TIME) ")");
+MODULE_PARM_DESC(tmr_atboot,
+ "Watchdog is started at boot time if set to 1, default="
+ __MODULE_STRING(S3C2410_WATCHDOG_ATBOOT));
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default 0)");
+
+/**
+ * struct s3c2410_wdt_variant - Per-variant config data
+ *
+ * @disable_reg: Offset in pmureg for the register that disables the watchdog
+ * timer reset functionality.
+ * @mask_reset_reg: Offset in pmureg for the register that masks the watchdog
+ * timer reset functionality.
+ * @mask_reset_inv: If set, mask_reset_reg value will have inverted meaning.
+ * @mask_bit: Bit number for the watchdog timer in the disable register and the
+ * mask reset register.
+ * @rst_stat_reg: Offset in pmureg for the register that has the reset status.
+ * @rst_stat_bit: Bit number in the rst_stat register indicating a watchdog
+ * reset.
+ * @cnt_en_reg: Offset in pmureg for the register that enables WDT counter.
+ * @cnt_en_bit: Bit number for "watchdog counter enable" in cnt_en register.
+ * @quirks: A bitfield of quirks.
+ */
+
+struct s3c2410_wdt_variant {
+ int disable_reg;
+ int mask_reset_reg;
+ bool mask_reset_inv;
+ int mask_bit;
+ int rst_stat_reg;
+ int rst_stat_bit;
+ int cnt_en_reg;
+ int cnt_en_bit;
+ u32 quirks;
+};
+
+struct s3c2410_wdt {
+ struct device *dev;
+ struct clk *bus_clk; /* for register interface (PCLK) */
+ struct clk *src_clk; /* for WDT counter */
+ void __iomem *reg_base;
+ unsigned int count;
+ spinlock_t lock;
+ unsigned long wtcon_save;
+ unsigned long wtdat_save;
+ struct watchdog_device wdt_device;
+ struct notifier_block freq_transition;
+ const struct s3c2410_wdt_variant *drv_data;
+ struct regmap *pmureg;
+};
+
+static const struct s3c2410_wdt_variant drv_data_s3c2410 = {
+ .quirks = 0
+};
+
+#ifdef CONFIG_OF
+static const struct s3c2410_wdt_variant drv_data_s3c6410 = {
+ .quirks = QUIRK_HAS_WTCLRINT_REG,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynos5250 = {
+ .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
+ .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
+ .mask_bit = 20,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = 20,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynos5420 = {
+ .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
+ .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
+ .mask_bit = 0,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = 9,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynos7 = {
+ .disable_reg = EXYNOS5_WDT_DISABLE_REG_OFFSET,
+ .mask_reset_reg = EXYNOS5_WDT_MASK_RESET_REG_OFFSET,
+ .mask_bit = 23,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = 23, /* A57 WDTRESET */
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_AUTO_DISABLE,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynos850_cl0 = {
+ .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN,
+ .mask_bit = 2,
+ .mask_reset_inv = true,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = EXYNOS850_CLUSTER0_WDTRESET_BIT,
+ .cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT,
+ .cnt_en_bit = 7,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynos850_cl1 = {
+ .mask_reset_reg = EXYNOS850_CLUSTER1_NONCPU_INT_EN,
+ .mask_bit = 2,
+ .mask_reset_inv = true,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = EXYNOS850_CLUSTER1_WDTRESET_BIT,
+ .cnt_en_reg = EXYNOS850_CLUSTER1_NONCPU_OUT,
+ .cnt_en_bit = 7,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET | \
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl0 = {
+ .mask_reset_reg = EXYNOS850_CLUSTER0_NONCPU_INT_EN,
+ .mask_bit = 2,
+ .mask_reset_inv = true,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = EXYNOSAUTOV9_CLUSTER0_WDTRESET_BIT,
+ .cnt_en_reg = EXYNOS850_CLUSTER0_NONCPU_OUT,
+ .cnt_en_bit = 7,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET |
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
+};
+
+static const struct s3c2410_wdt_variant drv_data_exynosautov9_cl1 = {
+ .mask_reset_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_INT_EN,
+ .mask_bit = 2,
+ .mask_reset_inv = true,
+ .rst_stat_reg = EXYNOS5_RST_STAT_REG_OFFSET,
+ .rst_stat_bit = EXYNOSAUTOV9_CLUSTER1_WDTRESET_BIT,
+ .cnt_en_reg = EXYNOSAUTOV9_CLUSTER1_NONCPU_OUT,
+ .cnt_en_bit = 7,
+ .quirks = QUIRK_HAS_WTCLRINT_REG | QUIRK_HAS_PMU_MASK_RESET |
+ QUIRK_HAS_PMU_RST_STAT | QUIRK_HAS_PMU_CNT_EN,
+};
+
+static const struct of_device_id s3c2410_wdt_match[] = {
+ { .compatible = "samsung,s3c2410-wdt",
+ .data = &drv_data_s3c2410 },
+ { .compatible = "samsung,s3c6410-wdt",
+ .data = &drv_data_s3c6410 },
+ { .compatible = "samsung,exynos5250-wdt",
+ .data = &drv_data_exynos5250 },
+ { .compatible = "samsung,exynos5420-wdt",
+ .data = &drv_data_exynos5420 },
+ { .compatible = "samsung,exynos7-wdt",
+ .data = &drv_data_exynos7 },
+ { .compatible = "samsung,exynos850-wdt",
+ .data = &drv_data_exynos850_cl0 },
+ { .compatible = "samsung,exynosautov9-wdt",
+ .data = &drv_data_exynosautov9_cl0 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, s3c2410_wdt_match);
+#endif
+
+static const struct platform_device_id s3c2410_wdt_ids[] = {
+ {
+ .name = "s3c2410-wdt",
+ .driver_data = (unsigned long)&drv_data_s3c2410,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, s3c2410_wdt_ids);
+
+/* functions */
+
+static inline unsigned long s3c2410wdt_get_freq(struct s3c2410_wdt *wdt)
+{
+ return clk_get_rate(wdt->src_clk ? wdt->src_clk : wdt->bus_clk);
+}
+
+static inline unsigned int s3c2410wdt_max_timeout(struct s3c2410_wdt *wdt)
+{
+ const unsigned long freq = s3c2410wdt_get_freq(wdt);
+
+ return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1)
+ / S3C2410_WTCON_MAXDIV);
+}
+
+static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb)
+{
+ return container_of(nb, struct s3c2410_wdt, freq_transition);
+}
+
+static int s3c2410wdt_disable_wdt_reset(struct s3c2410_wdt *wdt, bool mask)
+{
+ const u32 mask_val = BIT(wdt->drv_data->mask_bit);
+ const u32 val = mask ? mask_val : 0;
+ int ret;
+
+ ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->disable_reg,
+ mask_val, val);
+ if (ret < 0)
+ dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
+
+ return ret;
+}
+
+static int s3c2410wdt_mask_wdt_reset(struct s3c2410_wdt *wdt, bool mask)
+{
+ const u32 mask_val = BIT(wdt->drv_data->mask_bit);
+ const bool val_inv = wdt->drv_data->mask_reset_inv;
+ const u32 val = (mask ^ val_inv) ? mask_val : 0;
+ int ret;
+
+ ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->mask_reset_reg,
+ mask_val, val);
+ if (ret < 0)
+ dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
+
+ return ret;
+}
+
+static int s3c2410wdt_enable_counter(struct s3c2410_wdt *wdt, bool en)
+{
+ const u32 mask_val = BIT(wdt->drv_data->cnt_en_bit);
+ const u32 val = en ? mask_val : 0;
+ int ret;
+
+ ret = regmap_update_bits(wdt->pmureg, wdt->drv_data->cnt_en_reg,
+ mask_val, val);
+ if (ret < 0)
+ dev_err(wdt->dev, "failed to update reg(%d)\n", ret);
+
+ return ret;
+}
+
+static int s3c2410wdt_enable(struct s3c2410_wdt *wdt, bool en)
+{
+ int ret;
+
+ if (wdt->drv_data->quirks & QUIRK_HAS_PMU_AUTO_DISABLE) {
+ ret = s3c2410wdt_disable_wdt_reset(wdt, !en);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (wdt->drv_data->quirks & QUIRK_HAS_PMU_MASK_RESET) {
+ ret = s3c2410wdt_mask_wdt_reset(wdt, !en);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (wdt->drv_data->quirks & QUIRK_HAS_PMU_CNT_EN) {
+ ret = s3c2410wdt_enable_counter(wdt, en);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int s3c2410wdt_keepalive(struct watchdog_device *wdd)
+{
+ struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->lock);
+ writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
+ spin_unlock(&wdt->lock);
+
+ return 0;
+}
+
+static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt)
+{
+ unsigned long wtcon;
+
+ wtcon = readl(wdt->reg_base + S3C2410_WTCON);
+ wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
+ writel(wtcon, wdt->reg_base + S3C2410_WTCON);
+}
+
+static int s3c2410wdt_stop(struct watchdog_device *wdd)
+{
+ struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->lock);
+ __s3c2410wdt_stop(wdt);
+ spin_unlock(&wdt->lock);
+
+ return 0;
+}
+
+static int s3c2410wdt_start(struct watchdog_device *wdd)
+{
+ unsigned long wtcon;
+ struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->lock);
+
+ __s3c2410wdt_stop(wdt);
+
+ wtcon = readl(wdt->reg_base + S3C2410_WTCON);
+ wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
+
+ if (soft_noboot) {
+ wtcon |= S3C2410_WTCON_INTEN;
+ wtcon &= ~S3C2410_WTCON_RSTEN;
+ } else {
+ wtcon &= ~S3C2410_WTCON_INTEN;
+ wtcon |= S3C2410_WTCON_RSTEN;
+ }
+
+ dev_dbg(wdt->dev, "Starting watchdog: count=0x%08x, wtcon=%08lx\n",
+ wdt->count, wtcon);
+
+ writel(wdt->count, wdt->reg_base + S3C2410_WTDAT);
+ writel(wdt->count, wdt->reg_base + S3C2410_WTCNT);
+ writel(wtcon, wdt->reg_base + S3C2410_WTCON);
+ spin_unlock(&wdt->lock);
+
+ return 0;
+}
+
+static inline int s3c2410wdt_is_running(struct s3c2410_wdt *wdt)
+{
+ return readl(wdt->reg_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
+}
+
+static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
+ unsigned long freq = s3c2410wdt_get_freq(wdt);
+ unsigned int count;
+ unsigned int divisor = 1;
+ unsigned long wtcon;
+
+ if (timeout < 1)
+ return -EINVAL;
+
+ freq = DIV_ROUND_UP(freq, 128);
+ count = timeout * freq;
+
+ dev_dbg(wdt->dev, "Heartbeat: count=%d, timeout=%d, freq=%lu\n",
+ count, timeout, freq);
+
+ /* if the count is bigger than the watchdog register,
+ then work out what we need to do (and if) we can
+ actually make this value
+ */
+
+ if (count >= 0x10000) {
+ divisor = DIV_ROUND_UP(count, 0xffff);
+
+ if (divisor > 0x100) {
+ dev_err(wdt->dev, "timeout %d too big\n", timeout);
+ return -EINVAL;
+ }
+ }
+
+ dev_dbg(wdt->dev, "Heartbeat: timeout=%d, divisor=%d, count=%d (%08x)\n",
+ timeout, divisor, count, DIV_ROUND_UP(count, divisor));
+
+ count = DIV_ROUND_UP(count, divisor);
+ wdt->count = count;
+
+ /* update the pre-scaler */
+ wtcon = readl(wdt->reg_base + S3C2410_WTCON);
+ wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
+ wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
+
+ writel(count, wdt->reg_base + S3C2410_WTDAT);
+ writel(wtcon, wdt->reg_base + S3C2410_WTCON);
+
+ wdd->timeout = (count * divisor) / freq;
+
+ return 0;
+}
+
+static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action,
+ void *data)
+{
+ struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd);
+ void __iomem *wdt_base = wdt->reg_base;
+
+ /* disable watchdog, to be safe */
+ writel(0, wdt_base + S3C2410_WTCON);
+
+ /* put initial values into count and data */
+ writel(0x80, wdt_base + S3C2410_WTCNT);
+ writel(0x80, wdt_base + S3C2410_WTDAT);
+
+ /* set the watchdog to go and reset... */
+ writel(S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV16 |
+ S3C2410_WTCON_RSTEN | S3C2410_WTCON_PRESCALE(0x20),
+ wdt_base + S3C2410_WTCON);
+
+ /* wait for reset to assert... */
+ mdelay(500);
+
+ return 0;
+}
+
+#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)
+
+static const struct watchdog_info s3c2410_wdt_ident = {
+ .options = OPTIONS,
+ .firmware_version = 0,
+ .identity = "S3C2410 Watchdog",
+};
+
+static const struct watchdog_ops s3c2410wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = s3c2410wdt_start,
+ .stop = s3c2410wdt_stop,
+ .ping = s3c2410wdt_keepalive,
+ .set_timeout = s3c2410wdt_set_heartbeat,
+ .restart = s3c2410wdt_restart,
+};
+
+static const struct watchdog_device s3c2410_wdd = {
+ .info = &s3c2410_wdt_ident,
+ .ops = &s3c2410wdt_ops,
+ .timeout = S3C2410_WATCHDOG_DEFAULT_TIME,
+};
+
+/* interrupt handler code */
+
+static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
+{
+ struct s3c2410_wdt *wdt = platform_get_drvdata(param);
+
+ dev_info(wdt->dev, "watchdog timer expired (irq)\n");
+
+ s3c2410wdt_keepalive(&wdt->wdt_device);
+
+ if (wdt->drv_data->quirks & QUIRK_HAS_WTCLRINT_REG)
+ writel(0x1, wdt->reg_base + S3C2410_WTCLRINT);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
+
+static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ int ret;
+ struct s3c2410_wdt *wdt = freq_to_wdt(nb);
+
+ if (!s3c2410wdt_is_running(wdt))
+ goto done;
+
+ if (val == CPUFREQ_PRECHANGE) {
+ /* To ensure that over the change we don't cause the
+ * watchdog to trigger, we perform an keep-alive if
+ * the watchdog is running.
+ */
+
+ s3c2410wdt_keepalive(&wdt->wdt_device);
+ } else if (val == CPUFREQ_POSTCHANGE) {
+ s3c2410wdt_stop(&wdt->wdt_device);
+
+ ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
+ wdt->wdt_device.timeout);
+
+ if (ret >= 0)
+ s3c2410wdt_start(&wdt->wdt_device);
+ else
+ goto err;
+ }
+
+done:
+ return 0;
+
+ err:
+ dev_err(wdt->dev, "cannot set new value for timeout %d\n",
+ wdt->wdt_device.timeout);
+ return ret;
+}
+
+static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
+{
+ wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
+
+ return cpufreq_register_notifier(&wdt->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
+{
+ wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition;
+
+ cpufreq_unregister_notifier(&wdt->freq_transition,
+ CPUFREQ_TRANSITION_NOTIFIER);
+}
+
+#else
+
+static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt)
+{
+ return 0;
+}
+
+static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt)
+{
+}
+#endif
+
+static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt)
+{
+ unsigned int rst_stat;
+ int ret;
+
+ if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_RST_STAT))
+ return 0;
+
+ ret = regmap_read(wdt->pmureg, wdt->drv_data->rst_stat_reg, &rst_stat);
+ if (ret)
+ dev_warn(wdt->dev, "Couldn't get RST_STAT register\n");
+ else if (rst_stat & BIT(wdt->drv_data->rst_stat_bit))
+ return WDIOF_CARDRESET;
+
+ return 0;
+}
+
+static inline const struct s3c2410_wdt_variant *
+s3c2410_get_wdt_drv_data(struct platform_device *pdev)
+{
+ const struct s3c2410_wdt_variant *variant;
+ struct device *dev = &pdev->dev;
+
+ variant = of_device_get_match_data(dev);
+ if (!variant) {
+ /* Device matched by platform_device_id */
+ variant = (struct s3c2410_wdt_variant *)
+ platform_get_device_id(pdev)->driver_data;
+ }
+
+#ifdef CONFIG_OF
+ /* Choose Exynos850/ExynosAutov9 driver data w.r.t. cluster index */
+ if (variant == &drv_data_exynos850_cl0 ||
+ variant == &drv_data_exynosautov9_cl0) {
+ u32 index;
+ int err;
+
+ err = of_property_read_u32(dev->of_node,
+ "samsung,cluster-index", &index);
+ if (err) {
+ dev_err(dev, "failed to get cluster index\n");
+ return NULL;
+ }
+
+ switch (index) {
+ case 0:
+ return variant;
+ case 1:
+ return (variant == &drv_data_exynos850_cl0) ?
+ &drv_data_exynos850_cl1 :
+ &drv_data_exynosautov9_cl1;
+ default:
+ dev_err(dev, "wrong cluster index: %u\n", index);
+ return NULL;
+ }
+ }
+#endif
+
+ return variant;
+}
+
+static int s3c2410wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct s3c2410_wdt *wdt;
+ unsigned int wtcon;
+ int wdt_irq;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->dev = dev;
+ spin_lock_init(&wdt->lock);
+ wdt->wdt_device = s3c2410_wdd;
+
+ wdt->drv_data = s3c2410_get_wdt_drv_data(pdev);
+ if (!wdt->drv_data)
+ return -EINVAL;
+
+ if (wdt->drv_data->quirks & QUIRKS_HAVE_PMUREG) {
+ wdt->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "samsung,syscon-phandle");
+ if (IS_ERR(wdt->pmureg)) {
+ dev_err(dev, "syscon regmap lookup failed.\n");
+ return PTR_ERR(wdt->pmureg);
+ }
+ }
+
+ wdt_irq = platform_get_irq(pdev, 0);
+ if (wdt_irq < 0)
+ return wdt_irq;
+
+ /* get the memory region for the watchdog timer */
+ wdt->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->reg_base))
+ return PTR_ERR(wdt->reg_base);
+
+ wdt->bus_clk = devm_clk_get(dev, "watchdog");
+ if (IS_ERR(wdt->bus_clk)) {
+ dev_err(dev, "failed to find bus clock\n");
+ return PTR_ERR(wdt->bus_clk);
+ }
+
+ ret = clk_prepare_enable(wdt->bus_clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to enable bus clock\n");
+ return ret;
+ }
+
+ /*
+ * "watchdog_src" clock is optional; if it's not present -- just skip it
+ * and use "watchdog" clock as both bus and source clock.
+ */
+ wdt->src_clk = devm_clk_get_optional(dev, "watchdog_src");
+ if (IS_ERR(wdt->src_clk)) {
+ dev_err_probe(dev, PTR_ERR(wdt->src_clk),
+ "failed to get source clock\n");
+ ret = PTR_ERR(wdt->src_clk);
+ goto err_bus_clk;
+ }
+
+ ret = clk_prepare_enable(wdt->src_clk);
+ if (ret) {
+ dev_err(dev, "failed to enable source clock\n");
+ goto err_bus_clk;
+ }
+
+ wdt->wdt_device.min_timeout = 1;
+ wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt);
+
+ ret = s3c2410wdt_cpufreq_register(wdt);
+ if (ret < 0) {
+ dev_err(dev, "failed to register cpufreq\n");
+ goto err_src_clk;
+ }
+
+ watchdog_set_drvdata(&wdt->wdt_device, wdt);
+
+ /* see if we can actually set the requested timer margin, and if
+ * not, try the default value */
+
+ watchdog_init_timeout(&wdt->wdt_device, tmr_margin, dev);
+ ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
+ wdt->wdt_device.timeout);
+ if (ret) {
+ ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device,
+ S3C2410_WATCHDOG_DEFAULT_TIME);
+ if (ret == 0) {
+ dev_warn(dev, "tmr_margin value out of range, default %d used\n",
+ S3C2410_WATCHDOG_DEFAULT_TIME);
+ } else {
+ dev_err(dev, "failed to use default timeout\n");
+ goto err_cpufreq;
+ }
+ }
+
+ ret = devm_request_irq(dev, wdt_irq, s3c2410wdt_irq, 0,
+ pdev->name, pdev);
+ if (ret != 0) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ goto err_cpufreq;
+ }
+
+ watchdog_set_nowayout(&wdt->wdt_device, nowayout);
+ watchdog_set_restart_priority(&wdt->wdt_device, 128);
+
+ wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt);
+ wdt->wdt_device.parent = dev;
+
+ /*
+ * If "tmr_atboot" param is non-zero, start the watchdog right now. Also
+ * set WDOG_HW_RUNNING bit, so that watchdog core can kick the watchdog.
+ *
+ * If we're not enabling the watchdog, then ensure it is disabled if it
+ * has been left running from the bootloader or other source.
+ */
+ if (tmr_atboot) {
+ dev_info(dev, "starting watchdog timer\n");
+ s3c2410wdt_start(&wdt->wdt_device);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdt_device.status);
+ } else {
+ s3c2410wdt_stop(&wdt->wdt_device);
+ }
+
+ ret = watchdog_register_device(&wdt->wdt_device);
+ if (ret)
+ goto err_cpufreq;
+
+ ret = s3c2410wdt_enable(wdt, true);
+ if (ret < 0)
+ goto err_unregister;
+
+ platform_set_drvdata(pdev, wdt);
+
+ /* print out a statement of readiness */
+
+ wtcon = readl(wdt->reg_base + S3C2410_WTCON);
+
+ dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
+ (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in",
+ (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis",
+ (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis");
+
+ return 0;
+
+ err_unregister:
+ watchdog_unregister_device(&wdt->wdt_device);
+
+ err_cpufreq:
+ s3c2410wdt_cpufreq_deregister(wdt);
+
+ err_src_clk:
+ clk_disable_unprepare(wdt->src_clk);
+
+ err_bus_clk:
+ clk_disable_unprepare(wdt->bus_clk);
+
+ return ret;
+}
+
+static int s3c2410wdt_remove(struct platform_device *dev)
+{
+ int ret;
+ struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
+
+ ret = s3c2410wdt_enable(wdt, false);
+ if (ret < 0)
+ return ret;
+
+ watchdog_unregister_device(&wdt->wdt_device);
+
+ s3c2410wdt_cpufreq_deregister(wdt);
+
+ clk_disable_unprepare(wdt->src_clk);
+ clk_disable_unprepare(wdt->bus_clk);
+
+ return 0;
+}
+
+static void s3c2410wdt_shutdown(struct platform_device *dev)
+{
+ struct s3c2410_wdt *wdt = platform_get_drvdata(dev);
+
+ s3c2410wdt_enable(wdt, false);
+ s3c2410wdt_stop(&wdt->wdt_device);
+}
+
+static int s3c2410wdt_suspend(struct device *dev)
+{
+ int ret;
+ struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
+
+ /* Save watchdog state, and turn it off. */
+ wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON);
+ wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT);
+
+ ret = s3c2410wdt_enable(wdt, false);
+ if (ret < 0)
+ return ret;
+
+ /* Note that WTCNT doesn't need to be saved. */
+ s3c2410wdt_stop(&wdt->wdt_device);
+
+ return 0;
+}
+
+static int s3c2410wdt_resume(struct device *dev)
+{
+ int ret;
+ struct s3c2410_wdt *wdt = dev_get_drvdata(dev);
+
+ /* Restore watchdog state. */
+ writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTDAT);
+ writel(wdt->wtdat_save, wdt->reg_base + S3C2410_WTCNT);/* Reset count */
+ writel(wdt->wtcon_save, wdt->reg_base + S3C2410_WTCON);
+
+ ret = s3c2410wdt_enable(wdt, true);
+ if (ret < 0)
+ return ret;
+
+ dev_info(dev, "watchdog %sabled\n",
+ (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(s3c2410wdt_pm_ops,
+ s3c2410wdt_suspend, s3c2410wdt_resume);
+
+static struct platform_driver s3c2410wdt_driver = {
+ .probe = s3c2410wdt_probe,
+ .remove = s3c2410wdt_remove,
+ .shutdown = s3c2410wdt_shutdown,
+ .id_table = s3c2410_wdt_ids,
+ .driver = {
+ .name = "s3c2410-wdt",
+ .pm = pm_sleep_ptr(&s3c2410wdt_pm_ops),
+ .of_match_table = of_match_ptr(s3c2410_wdt_match),
+ },
+};
+
+module_platform_driver(s3c2410wdt_driver);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>, Dimitry Andric <dimitry.andric@tomtom.com>");
+MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sa1100_wdt.c b/drivers/watchdog/sa1100_wdt.c
new file mode 100644
index 000000000..82ac5d19f
--- /dev/null
+++ b/drivers/watchdog/sa1100_wdt.c
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for the SA11x0/PXA2xx
+ *
+ * (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Neither Oleg Drokin nor iXcelerator.com admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * 27/11/2000 Initial release
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/clk.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/uaccess.h>
+#include <linux/timex.h>
+
+#define REG_OSMR0 0x0000 /* OS timer Match Reg. 0 */
+#define REG_OSMR1 0x0004 /* OS timer Match Reg. 1 */
+#define REG_OSMR2 0x0008 /* OS timer Match Reg. 2 */
+#define REG_OSMR3 0x000c /* OS timer Match Reg. 3 */
+#define REG_OSCR 0x0010 /* OS timer Counter Reg. */
+#define REG_OSSR 0x0014 /* OS timer Status Reg. */
+#define REG_OWER 0x0018 /* OS timer Watch-dog Enable Reg. */
+#define REG_OIER 0x001C /* OS timer Interrupt Enable Reg. */
+
+#define OSSR_M3 (1 << 3) /* Match status channel 3 */
+#define OSSR_M2 (1 << 2) /* Match status channel 2 */
+#define OSSR_M1 (1 << 1) /* Match status channel 1 */
+#define OSSR_M0 (1 << 0) /* Match status channel 0 */
+
+#define OWER_WME (1 << 0) /* Watchdog Match Enable */
+
+#define OIER_E3 (1 << 3) /* Interrupt enable channel 3 */
+#define OIER_E2 (1 << 2) /* Interrupt enable channel 2 */
+#define OIER_E1 (1 << 1) /* Interrupt enable channel 1 */
+#define OIER_E0 (1 << 0) /* Interrupt enable channel 0 */
+
+static unsigned long oscr_freq;
+static unsigned long sa1100wdt_users;
+static unsigned int pre_margin;
+static int boot_status;
+static void __iomem *reg_base;
+
+static inline void sa1100_wr(u32 val, u32 offset)
+{
+ writel_relaxed(val, reg_base + offset);
+}
+
+static inline u32 sa1100_rd(u32 offset)
+{
+ return readl_relaxed(reg_base + offset);
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int sa1100dog_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(1, &sa1100wdt_users))
+ return -EBUSY;
+
+ /* Activate SA1100 Watchdog timer */
+ sa1100_wr(sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3);
+ sa1100_wr(OSSR_M3, REG_OSSR);
+ sa1100_wr(OWER_WME, REG_OWER);
+ sa1100_wr(sa1100_rd(REG_OIER) | OIER_E3, REG_OIER);
+ return stream_open(inode, file);
+}
+
+/*
+ * The watchdog cannot be disabled.
+ *
+ * Previous comments suggested that turning off the interrupt by
+ * clearing REG_OIER[E3] would prevent the watchdog timing out but this
+ * does not appear to be true (at least on the PXA255).
+ */
+static int sa1100dog_release(struct inode *inode, struct file *file)
+{
+ pr_crit("Device closed - timer will not stop\n");
+ clear_bit(1, &sa1100wdt_users);
+ return 0;
+}
+
+static ssize_t sa1100dog_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (len)
+ /* Refresh OSMR3 timer. */
+ sa1100_wr(sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3);
+ return len;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT
+ | WDIOF_KEEPALIVEPING,
+ .identity = "SA1100/PXA255 Watchdog",
+ .firmware_version = 1,
+};
+
+static long sa1100dog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ int time;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user(argp, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, p);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(boot_status, p);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ sa1100_wr(sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3);
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, p);
+ if (ret)
+ break;
+
+ if (time <= 0 || (oscr_freq * (long long)time >= 0xffffffff)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ pre_margin = oscr_freq * time;
+ sa1100_wr(sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(pre_margin / oscr_freq, p);
+ break;
+ }
+ return ret;
+}
+
+static const struct file_operations sa1100dog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sa1100dog_write,
+ .unlocked_ioctl = sa1100dog_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = sa1100dog_open,
+ .release = sa1100dog_release,
+};
+
+static struct miscdevice sa1100dog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sa1100dog_fops,
+};
+
+static int margin = 60; /* (secs) Default is 1 minute */
+static struct clk *clk;
+
+static int sa1100dog_probe(struct platform_device *pdev)
+{
+ int ret;
+ int *platform_data;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENXIO;
+ reg_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ ret = PTR_ERR_OR_ZERO(reg_base);
+ if (ret)
+ return ret;
+
+ clk = clk_get(NULL, "OSTIMER0");
+ if (IS_ERR(clk)) {
+ pr_err("SA1100/PXA2xx Watchdog Timer: clock not found: %d\n",
+ (int) PTR_ERR(clk));
+ return PTR_ERR(clk);
+ }
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ pr_err("SA1100/PXA2xx Watchdog Timer: clock failed to prepare+enable: %d\n",
+ ret);
+ goto err;
+ }
+
+ oscr_freq = clk_get_rate(clk);
+
+ platform_data = pdev->dev.platform_data;
+ if (platform_data && *platform_data)
+ boot_status = WDIOF_CARDRESET;
+ pre_margin = oscr_freq * margin;
+
+ ret = misc_register(&sa1100dog_miscdev);
+ if (ret == 0) {
+ pr_info("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n",
+ margin);
+ return 0;
+ }
+
+ clk_disable_unprepare(clk);
+err:
+ clk_put(clk);
+ return ret;
+}
+
+static int sa1100dog_remove(struct platform_device *pdev)
+{
+ misc_deregister(&sa1100dog_miscdev);
+ clk_disable_unprepare(clk);
+ clk_put(clk);
+
+ return 0;
+}
+
+static struct platform_driver sa1100dog_driver = {
+ .driver.name = "sa1100_wdt",
+ .probe = sa1100dog_probe,
+ .remove = sa1100dog_remove,
+};
+module_platform_driver(sa1100dog_driver);
+
+MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>");
+MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog");
+
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sama5d4_wdt.c b/drivers/watchdog/sama5d4_wdt.c
new file mode 100644
index 000000000..aeee934ca
--- /dev/null
+++ b/drivers/watchdog/sama5d4_wdt.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Atmel SAMA5D4 Watchdog Timer
+ *
+ * Copyright (C) 2015-2019 Microchip Technology Inc. and its subsidiaries
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/watchdog.h>
+
+#include "at91sam9_wdt.h"
+
+/* minimum and maximum watchdog timeout, in seconds */
+#define MIN_WDT_TIMEOUT 1
+#define MAX_WDT_TIMEOUT 16
+#define WDT_DEFAULT_TIMEOUT MAX_WDT_TIMEOUT
+
+#define WDT_SEC2TICKS(s) ((s) ? (((s) << 8) - 1) : 0)
+
+struct sama5d4_wdt {
+ struct watchdog_device wdd;
+ void __iomem *reg_base;
+ u32 mr;
+ u32 ir;
+ unsigned long last_ping;
+ bool need_irq;
+ bool sam9x60_support;
+};
+
+static int wdt_timeout;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(wdt_timeout, int, 0);
+MODULE_PARM_DESC(wdt_timeout,
+ "Watchdog timeout in seconds. (default = "
+ __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+#define wdt_enabled (!(wdt->mr & AT91_WDT_WDDIS))
+
+#define wdt_read(wdt, field) \
+ readl_relaxed((wdt)->reg_base + (field))
+
+/* 4 slow clock periods is 4/32768 = 122.07µs*/
+#define WDT_DELAY usecs_to_jiffies(123)
+
+static void wdt_write(struct sama5d4_wdt *wdt, u32 field, u32 val)
+{
+ /*
+ * WDT_CR and WDT_MR must not be modified within three slow clock
+ * periods following a restart of the watchdog performed by a write
+ * access in WDT_CR.
+ */
+ while (time_before(jiffies, wdt->last_ping + WDT_DELAY))
+ usleep_range(30, 125);
+ writel_relaxed(val, wdt->reg_base + field);
+ wdt->last_ping = jiffies;
+}
+
+static void wdt_write_nosleep(struct sama5d4_wdt *wdt, u32 field, u32 val)
+{
+ if (time_before(jiffies, wdt->last_ping + WDT_DELAY))
+ udelay(123);
+ writel_relaxed(val, wdt->reg_base + field);
+ wdt->last_ping = jiffies;
+}
+
+static int sama5d4_wdt_start(struct watchdog_device *wdd)
+{
+ struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ 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_write(wdt, AT91_WDT_MR, wdt->mr);
+
+ return 0;
+}
+
+static int sama5d4_wdt_stop(struct watchdog_device *wdd)
+{
+ struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ 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_write(wdt, AT91_WDT_MR, wdt->mr);
+
+ return 0;
+}
+
+static int sama5d4_wdt_ping(struct watchdog_device *wdd)
+{
+ struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdt_write(wdt, AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT);
+
+ return 0;
+}
+
+static int sama5d4_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct sama5d4_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 value = WDT_SEC2TICKS(timeout);
+
+ if (wdt->sam9x60_support) {
+ wdt_write(wdt, AT91_SAM9X60_WLR,
+ AT91_SAM9X60_SET_COUNTER(value));
+
+ wdd->timeout = timeout;
+ return 0;
+ }
+
+ wdt->mr &= ~AT91_WDT_WDV;
+ wdt->mr |= AT91_WDT_SET_WDV(value);
+
+ /*
+ * WDDIS has to be 0 when updating WDD/WDV. The datasheet states: When
+ * setting the WDDIS bit, and while it is set, the fields WDV and WDD
+ * must not be modified.
+ * 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);
+
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info sama5d4_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "Atmel SAMA5D4 Watchdog",
+};
+
+static const struct watchdog_ops sama5d4_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sama5d4_wdt_start,
+ .stop = sama5d4_wdt_stop,
+ .ping = sama5d4_wdt_ping,
+ .set_timeout = sama5d4_wdt_set_timeout,
+};
+
+static irqreturn_t sama5d4_wdt_irq_handler(int irq, void *dev_id)
+{
+ struct sama5d4_wdt *wdt = platform_get_drvdata(dev_id);
+ u32 reg;
+
+ if (wdt->sam9x60_support)
+ reg = wdt_read(wdt, AT91_SAM9X60_ISR);
+ else
+ reg = wdt_read(wdt, AT91_WDT_SR);
+
+ if (reg) {
+ pr_crit("Atmel Watchdog Software Reset\n");
+ emergency_restart();
+ pr_crit("Reboot didn't succeed\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+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;
+
+ if (!of_property_read_string(np, "atmel,watchdog-type", &tmp) &&
+ !strcmp(tmp, "software"))
+ wdt->need_irq = true;
+
+ if (of_property_read_bool(np, "atmel,idle-halt"))
+ wdt->mr |= AT91_WDT_WDIDLEHLT;
+
+ if (of_property_read_bool(np, "atmel,dbg-halt"))
+ wdt->mr |= AT91_WDT_WDDBGHLT;
+
+ return 0;
+}
+
+static int sama5d4_wdt_init(struct sama5d4_wdt *wdt)
+{
+ u32 reg, val;
+
+ val = WDT_SEC2TICKS(WDT_DEFAULT_TIMEOUT);
+ /*
+ * When booting and resuming, the bootloader may have changed the
+ * watchdog configuration.
+ * If the watchdog is already running, we can safely update it.
+ * Else, we have to disable it properly.
+ */
+ if (!wdt_enabled) {
+ 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)))
+ wdt_write_nosleep(wdt, AT91_WDT_MR,
+ reg | AT91_WDT_WDDIS);
+ }
+
+ if (wdt->sam9x60_support) {
+ if (wdt->need_irq)
+ wdt->ir = AT91_SAM9X60_PERINT;
+ else
+ wdt->mr |= AT91_SAM9X60_PERIODRST;
+
+ wdt_write(wdt, AT91_SAM9X60_IER, wdt->ir);
+ wdt_write(wdt, AT91_SAM9X60_WLR, AT91_SAM9X60_SET_COUNTER(val));
+ } else {
+ wdt->mr |= AT91_WDT_SET_WDD(WDT_SEC2TICKS(MAX_WDT_TIMEOUT));
+ wdt->mr |= AT91_WDT_SET_WDV(val);
+
+ if (wdt->need_irq)
+ wdt->mr |= AT91_WDT_WDFIEN;
+ else
+ wdt->mr |= AT91_WDT_WDRSTEN;
+ }
+
+ wdt_write_nosleep(wdt, AT91_WDT_MR, wdt->mr);
+
+ return 0;
+}
+
+static int sama5d4_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct sama5d4_wdt *wdt;
+ void __iomem *regs;
+ u32 irq = 0;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdd = &wdt->wdd;
+ wdd->timeout = WDT_DEFAULT_TIMEOUT;
+ wdd->info = &sama5d4_wdt_info;
+ wdd->ops = &sama5d4_wdt_ops;
+ wdd->min_timeout = MIN_WDT_TIMEOUT;
+ wdd->max_timeout = MAX_WDT_TIMEOUT;
+ wdt->last_ping = jiffies;
+
+ if (of_device_is_compatible(dev->of_node, "microchip,sam9x60-wdt") ||
+ of_device_is_compatible(dev->of_node, "microchip,sama7g5-wdt"))
+ wdt->sam9x60_support = true;
+
+ watchdog_set_drvdata(wdd, wdt);
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ wdt->reg_base = regs;
+
+ ret = of_sama5d4_wdt_init(dev->of_node, wdt);
+ if (ret)
+ return ret;
+
+ if (wdt->need_irq) {
+ irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (!irq) {
+ dev_warn(dev, "failed to get IRQ from DT\n");
+ wdt->need_irq = false;
+ }
+ }
+
+ if (wdt->need_irq) {
+ ret = devm_request_irq(dev, irq, sama5d4_wdt_irq_handler,
+ IRQF_SHARED | IRQF_IRQPOLL |
+ IRQF_NO_SUSPEND, pdev->name, pdev);
+ if (ret) {
+ dev_err(dev, "cannot register interrupt handler\n");
+ return ret;
+ }
+ }
+
+ watchdog_init_timeout(wdd, wdt_timeout, dev);
+
+ ret = sama5d4_wdt_init(wdt);
+ if (ret)
+ return ret;
+
+ watchdog_set_nowayout(wdd, nowayout);
+
+ watchdog_stop_on_unregister(wdd);
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+
+ dev_info(dev, "initialized (timeout = %d sec, nowayout = %d)\n",
+ wdd->timeout, nowayout);
+
+ return 0;
+}
+
+static const struct of_device_id sama5d4_wdt_of_match[] = {
+ {
+ .compatible = "atmel,sama5d4-wdt",
+ },
+ {
+ .compatible = "microchip,sam9x60-wdt",
+ },
+ {
+ .compatible = "microchip,sama7g5-wdt",
+ },
+
+ { }
+};
+MODULE_DEVICE_TABLE(of, sama5d4_wdt_of_match);
+
+static int sama5d4_wdt_suspend_late(struct device *dev)
+{
+ struct sama5d4_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ sama5d4_wdt_stop(&wdt->wdd);
+
+ return 0;
+}
+
+static int sama5d4_wdt_resume_early(struct device *dev)
+{
+ struct sama5d4_wdt *wdt = dev_get_drvdata(dev);
+
+ /*
+ * FIXME: writing MR also pings the watchdog which may not be desired.
+ * This should only be done when the registers are lost on suspend but
+ * there is no way to get this information right now.
+ */
+ sama5d4_wdt_init(wdt);
+
+ if (watchdog_active(&wdt->wdd))
+ sama5d4_wdt_start(&wdt->wdd);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sama5d4_wdt_pm_ops = {
+ LATE_SYSTEM_SLEEP_PM_OPS(sama5d4_wdt_suspend_late,
+ sama5d4_wdt_resume_early)
+};
+
+static struct platform_driver sama5d4_wdt_driver = {
+ .probe = sama5d4_wdt_probe,
+ .driver = {
+ .name = "sama5d4_wdt",
+ .pm = pm_sleep_ptr(&sama5d4_wdt_pm_ops),
+ .of_match_table = sama5d4_wdt_of_match,
+ }
+};
+module_platform_driver(sama5d4_wdt_driver);
+
+MODULE_AUTHOR("Atmel Corporation");
+MODULE_DESCRIPTION("Atmel SAMA5D4 Watchdog Timer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/sb_wdog.c b/drivers/watchdog/sb_wdog.c
new file mode 100644
index 000000000..504be461f
--- /dev/null
+++ b/drivers/watchdog/sb_wdog.c
@@ -0,0 +1,363 @@
+/*
+ * Watchdog driver for SiByte SB1 SoCs
+ *
+ * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@lsi.com>
+ *
+ * This driver is intended to make the second of two hardware watchdogs
+ * on the Sibyte 12XX and 11XX SoCs available to the user. There are two
+ * such devices available on the SoC, but it seems that there isn't an
+ * enumeration class for watchdogs in Linux like there is for RTCs.
+ * The second is used rather than the first because it uses IRQ 1,
+ * thereby avoiding all that IRQ 0 problematic nonsense.
+ *
+ * I have not tried this driver on a 1480 processor; it might work
+ * just well enough to really screw things up.
+ *
+ * It is a simple timer, and there is an interrupt that is raised the
+ * first time the timer expires. The second time it expires, the chip
+ * is reset and there is no way to redirect that NMI. Which could
+ * be problematic in some cases where this chip is sitting on the HT
+ * bus and has just taken responsibility for providing a cache block.
+ * Since the reset can't be redirected to the external reset pin, it is
+ * possible that other HT connected processors might hang and not reset.
+ * For Linux, a soft reset would probably be even worse than a hard reset.
+ * There you have it.
+ *
+ * The timer takes 23 bits of a 64 bit register (?) as a count value,
+ * and decrements the count every microsecond, for a max value of
+ * 0x7fffff usec or about 8.3ish seconds.
+ *
+ * This watchdog borrows some user semantics from the softdog driver,
+ * in that if you close the fd, it leaves the watchdog running, unless
+ * you previously wrote a 'V' to the fd, in which case it disables
+ * the watchdog when you close the fd like some other drivers.
+ *
+ * Based on various other watchdog drivers, which are probably all
+ * loosely based on something Alan Cox wrote years ago.
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 1 or 2 as published by the Free Software Foundation.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/interrupt.h>
+
+#include <asm/sibyte/sb1250.h>
+#include <asm/sibyte/sb1250_regs.h>
+#include <asm/sibyte/sb1250_int.h>
+#include <asm/sibyte/sb1250_scd.h>
+
+static DEFINE_SPINLOCK(sbwd_lock);
+
+/*
+ * set the initial count value of a timer
+ *
+ * wdog is the iomem address of the cfg register
+ */
+static void sbwdog_set(char __iomem *wdog, unsigned long t)
+{
+ spin_lock(&sbwd_lock);
+ __raw_writeb(0, wdog);
+ __raw_writeq(t & 0x7fffffUL, wdog - 0x10);
+ spin_unlock(&sbwd_lock);
+}
+
+/*
+ * cause the timer to [re]load it's initial count and start counting
+ * all over again
+ *
+ * wdog is the iomem address of the cfg register
+ */
+static void sbwdog_pet(char __iomem *wdog)
+{
+ spin_lock(&sbwd_lock);
+ __raw_writeb(__raw_readb(wdog) | 1, wdog);
+ spin_unlock(&sbwd_lock);
+}
+
+static unsigned long sbwdog_gate; /* keeps it to one thread only */
+static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0));
+static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1));
+static unsigned long timeout = 0x7fffffUL; /* useconds: 8.3ish secs. */
+static int expect_close;
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "SiByte Watchdog",
+};
+
+/*
+ * Allow only a single thread to walk the dog
+ */
+static int sbwdog_open(struct inode *inode, struct file *file)
+{
+ stream_open(inode, file);
+ if (test_and_set_bit(0, &sbwdog_gate))
+ return -EBUSY;
+ __module_get(THIS_MODULE);
+
+ /*
+ * Activate the timer
+ */
+ sbwdog_set(user_dog, timeout);
+ __raw_writeb(1, user_dog);
+
+ return 0;
+}
+
+/*
+ * Put the dog back in the kennel.
+ */
+static int sbwdog_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ __raw_writeb(0, user_dog);
+ module_put(THIS_MODULE);
+ } else {
+ pr_crit("%s: Unexpected close, not stopping watchdog!\n",
+ ident.identity);
+ sbwdog_pet(user_dog);
+ }
+ clear_bit(0, &sbwdog_gate);
+ expect_close = 0;
+
+ return 0;
+}
+
+/*
+ * 42 - the answer
+ */
+static ssize_t sbwdog_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int i;
+
+ if (len) {
+ /*
+ * restart the timer
+ */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ sbwdog_pet(user_dog);
+ }
+
+ return len;
+}
+
+static long sbwdog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ unsigned long time;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, p);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ sbwdog_pet(user_dog);
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, p);
+ if (ret)
+ break;
+
+ time *= 1000000;
+ if (time > 0x7fffffUL) {
+ ret = -EINVAL;
+ break;
+ }
+ timeout = time;
+ sbwdog_set(user_dog, timeout);
+ sbwdog_pet(user_dog);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ /*
+ * get the remaining count from the ... count register
+ * which is 1*8 before the config register
+ */
+ ret = put_user((u32)__raw_readq(user_dog - 8) / 1000000, p);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Notifier for system down
+ */
+static int sbwdog_notify_sys(struct notifier_block *this, unsigned long code,
+ void *erf)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /*
+ * sit and sit
+ */
+ __raw_writeb(0, user_dog);
+ __raw_writeb(0, kern_dog);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static const struct file_operations sbwdog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sbwdog_write,
+ .unlocked_ioctl = sbwdog_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = sbwdog_open,
+ .release = sbwdog_release,
+};
+
+static struct miscdevice sbwdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sbwdog_fops,
+};
+
+static struct notifier_block sbwdog_notifier = {
+ .notifier_call = sbwdog_notify_sys,
+};
+
+/*
+ * interrupt handler
+ *
+ * doesn't do a whole lot for user, but oh so cleverly written so kernel
+ * code can use it to re-up the watchdog, thereby saving the kernel from
+ * having to create and maintain a timer, just to tickle another timer,
+ * which is just so wrong.
+ */
+irqreturn_t sbwdog_interrupt(int irq, void *addr)
+{
+ unsigned long wd_init;
+ char *wd_cfg_reg = (char *)addr;
+ u8 cfg;
+
+ cfg = __raw_readb(wd_cfg_reg);
+ wd_init = __raw_readq(wd_cfg_reg - 8) & 0x7fffff;
+
+ /*
+ * if it's the second watchdog timer, it's for those users
+ */
+ if (wd_cfg_reg == user_dog)
+ pr_crit("%s in danger of initiating system reset "
+ "in %ld.%01ld seconds\n",
+ ident.identity,
+ wd_init / 1000000, (wd_init / 100000) % 10);
+ else
+ cfg |= 1;
+
+ __raw_writeb(cfg, wd_cfg_reg);
+
+ return IRQ_HANDLED;
+}
+
+static int __init sbwdog_init(void)
+{
+ int ret;
+
+ /*
+ * register a reboot notifier
+ */
+ ret = register_reboot_notifier(&sbwdog_notifier);
+ if (ret) {
+ pr_err("%s: cannot register reboot notifier (err=%d)\n",
+ ident.identity, ret);
+ return ret;
+ }
+
+ /*
+ * get the resources
+ */
+
+ ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED,
+ ident.identity, (void *)user_dog);
+ if (ret) {
+ pr_err("%s: failed to request irq 1 - %d\n",
+ ident.identity, ret);
+ goto out;
+ }
+
+ ret = misc_register(&sbwdog_miscdev);
+ if (ret == 0) {
+ pr_info("%s: timeout is %ld.%ld secs\n",
+ ident.identity,
+ timeout / 1000000, (timeout / 100000) % 10);
+ return 0;
+ }
+ free_irq(1, (void *)user_dog);
+out:
+ unregister_reboot_notifier(&sbwdog_notifier);
+
+ return ret;
+}
+
+static void __exit sbwdog_exit(void)
+{
+ misc_deregister(&sbwdog_miscdev);
+ free_irq(1, (void *)user_dog);
+ unregister_reboot_notifier(&sbwdog_notifier);
+}
+
+module_init(sbwdog_init);
+module_exit(sbwdog_exit);
+
+MODULE_AUTHOR("Andrew Sharp <andy.sharp@lsi.com>");
+MODULE_DESCRIPTION("SiByte Watchdog");
+
+module_param(timeout, ulong, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)");
+
+MODULE_LICENSE("GPL");
+
+/*
+ * example code that can be put in a platform code area to utilize the
+ * first watchdog timer for the kernels own purpose.
+
+void platform_wd_setup(void)
+{
+ int ret;
+
+ ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED,
+ "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0));
+ if (ret) {
+ pr_crit("Watchdog IRQ zero(0) failed to be requested - %d\n", ret);
+ }
+}
+
+
+ */
diff --git a/drivers/watchdog/sbc60xxwdt.c b/drivers/watchdog/sbc60xxwdt.c
new file mode 100644
index 000000000..7b974802d
--- /dev/null
+++ b/drivers/watchdog/sbc60xxwdt.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
+ *
+ * Based on acquirewdt.c by Alan Cox.
+ *
+ * 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.
+ *
+ * (c) Copyright 2000 Jakob Oestergaard <jakob@unthought.net>
+ *
+ * 12/4 - 2000 [Initial revision]
+ * 25/4 - 2000 Added /dev/watchdog support
+ * 09/5 - 2001 [smj@oro.net] fixed fop_write to "return 1"
+ * on success
+ * 12/4 - 2002 [rob@osinvestor.com] eliminate fop_read
+ * fix possible wdt_is_open race
+ * add CONFIG_WATCHDOG_NOWAYOUT support
+ * remove lock_kernel/unlock_kernel pairs
+ * added KERN_* to printk's
+ * got rid of extraneous comments
+ * changed watchdog_info to correctly reflect what
+ * the driver offers
+ * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
+ * WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, and
+ * WDIOC_SETOPTIONS ioctls
+ * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces
+ * use module_param
+ * made timeout (the emulated heartbeat) a
+ * module_param
+ * made the keepalive ping an internal subroutine
+ * made wdt_stop and wdt_start module params
+ * added extra printk's for startup problems
+ * added MODULE_AUTHOR and MODULE_DESCRIPTION info
+ *
+ * This WDT driver is different from the other Linux WDT
+ * drivers in the following ways:
+ * *) The driver will ping the watchdog by itself, because this
+ * particular WDT has a very short timeout (one second) and it
+ * would be insane to count on any userspace daemon always
+ * getting scheduled within that time frame.
+ */
+
+#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>
+
+
+#define OUR_NAME "sbc60xxwdt"
+#define PFX OUR_NAME ": "
+
+/*
+ * You must set these - The driver cannot probe for the settings
+ */
+
+static int wdt_stop = 0x45;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
+
+/*
+ * The 60xx board can use watchdog timeout values from one second
+ * to several minutes. The default is one second, so 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.
+ * If the daemon pulses us every 25 seconds, we can still afford
+ * a 5 second scheduling delay on the (high priority) daemon. That
+ * should be sufficient for a box under any load.
+ */
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, multiplied by HZ to
+ get seconds to wait for a ping */
+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) ")");
+
+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;
+
+/*
+ * 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 by reading from wdt_start */
+ inb_p(wdt_start);
+ /* 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_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* Start the timer */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+ pr_info("Watchdog timer is now enabled\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer_sync(&timer);
+ inb_p(wdt_stop);
+ pr_info("Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /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;
+
+ /* scan to see whether or not we got the
+ magic character */
+ 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 {
+ del_timer(&timer);
+ pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ 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 = "SBC60xx",
+ };
+
+ 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;
+ /* arbitrary upper limit */
+ if (new_timeout < 1 || new_timeout > 3600)
+ return -EINVAL;
+
+ timeout = new_timeout;
+ wdt_keepalive();
+ }
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .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 sbc60xxwdt_unload(void)
+{
+ wdt_turnoff();
+
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+
+ unregister_reboot_notifier(&wdt_notifier);
+ if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+ release_region(wdt_stop, 1);
+ release_region(wdt_start, 1);
+}
+
+static int __init sbc60xxwdt_init(void)
+{
+ int rc = -EBUSY;
+
+ if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
+ timeout = WATCHDOG_TIMEOUT;
+ pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
+ timeout);
+ }
+
+ if (!request_region(wdt_start, 1, "SBC 60XX WDT")) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_start);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ /* We cannot reserve 0x45 - the kernel already has! */
+ if (wdt_stop != 0x45 && wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_stop);
+ rc = -EIO;
+ goto err_out_region1;
+ }
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out_region2;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_reboot;
+ }
+ pr_info("WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return 0;
+
+err_out_reboot:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out_region2:
+ if (wdt_stop != 0x45 && wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+err_out_region1:
+ release_region(wdt_start, 1);
+err_out:
+ return rc;
+}
+
+module_init(sbc60xxwdt_init);
+module_exit(sbc60xxwdt_unload);
+
+MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
+MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sbc7240_wdt.c b/drivers/watchdog/sbc7240_wdt.c
new file mode 100644
index 000000000..d640b26e1
--- /dev/null
+++ b/drivers/watchdog/sbc7240_wdt.c
@@ -0,0 +1,302 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NANO7240 SBC Watchdog device driver
+ *
+ * Based on w83877f.c by Scott Jennings,
+ *
+ * (c) Copyright 2007 Gilles GIGAN <gilles.gigan@jcu.edu.au>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/atomic.h>
+
+#define SBC7240_ENABLE_PORT 0x443
+#define SBC7240_DISABLE_PORT 0x043
+#define SBC7240_SET_TIMEOUT_PORT SBC7240_ENABLE_PORT
+#define SBC7240_MAGIC_CHAR 'V'
+
+#define SBC7240_TIMEOUT 30
+#define SBC7240_MAX_TIMEOUT 255
+static int timeout = SBC7240_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="
+ __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="
+ __MODULE_STRING(SBC7240_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");
+
+#define SBC7240_OPEN_STATUS_BIT 0
+#define SBC7240_ENABLED_STATUS_BIT 1
+#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2
+static unsigned long wdt_status;
+
+/*
+ * Utility routines
+ */
+
+static void wdt_disable(void)
+{
+ /* disable the watchdog */
+ if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
+ inb_p(SBC7240_DISABLE_PORT);
+ pr_info("Watchdog timer is now disabled\n");
+ }
+}
+
+static void wdt_enable(void)
+{
+ /* enable the watchdog */
+ if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
+ inb_p(SBC7240_ENABLE_PORT);
+ pr_info("Watchdog timer is now enabled\n");
+ }
+}
+
+static int wdt_set_timeout(int t)
+{
+ if (t < 1 || t > SBC7240_MAX_TIMEOUT) {
+ pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT);
+ return -1;
+ }
+ /* set the timeout */
+ outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);
+ timeout = t;
+ pr_info("timeout set to %d seconds\n", t);
+ return 0;
+}
+
+/* Whack the dog */
+static inline void wdt_keepalive(void)
+{
+ if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))
+ inb_p(SBC7240_ENABLE_PORT);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+static ssize_t fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ size_t i;
+ char c;
+
+ if (count) {
+ if (!nowayout) {
+ clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
+ &wdt_status);
+
+ /* is there a magic char ? */
+ for (i = 0; i != count; i++) {
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == SBC7240_MAGIC_CHAR) {
+ set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
+ &wdt_status);
+ break;
+ }
+ }
+ }
+
+ wdt_keepalive();
+ }
+
+ return count;
+}
+
+static int fop_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))
+ return -EBUSY;
+
+ wdt_enable();
+
+ return stream_open(inode, file);
+}
+
+static int fop_close(struct inode *inode, struct file *file)
+{
+ if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)
+ || !nowayout) {
+ wdt_disable();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ wdt_keepalive();
+ }
+
+ clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);
+ return 0;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING|
+ WDIOF_SETTIMEOUT|
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "SBC7240",
+};
+
+
+static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user((void __user *)arg, &ident, sizeof(ident))
+ ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, (int __user *)arg);
+ case WDIOC_SETOPTIONS:
+ {
+ int options;
+ int retval = -EINVAL;
+
+ if (get_user(options, (int __user *)arg))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ wdt_disable();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ wdt_enable();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if (get_user(new_timeout, (int __user *)arg))
+ return -EFAULT;
+
+ if (wdt_set_timeout(new_timeout))
+ return -EINVAL;
+ }
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, (int __user *)arg);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .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_disable();
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+static void __exit sbc7240_wdt_unload(void)
+{
+ pr_info("Removing watchdog\n");
+ misc_deregister(&wdt_miscdev);
+
+ unregister_reboot_notifier(&wdt_notifier);
+ release_region(SBC7240_ENABLE_PORT, 1);
+}
+
+static int __init sbc7240_wdt_init(void)
+{
+ int rc = -EBUSY;
+
+ if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {
+ pr_err("I/O address 0x%04x already in use\n",
+ SBC7240_ENABLE_PORT);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ /* The IO port 0x043 used to disable the watchdog
+ * is already claimed by the system timer, so we
+ * can't request_region() it ...*/
+
+ if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {
+ timeout = SBC7240_TIMEOUT;
+ pr_info("timeout value must be 1<=x<=%d, using %d\n",
+ SBC7240_MAX_TIMEOUT, timeout);
+ }
+ wdt_set_timeout(timeout);
+ wdt_disable();
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out_region;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_reboot_notifier;
+ }
+
+ pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",
+ nowayout);
+
+ return 0;
+
+err_out_reboot_notifier:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out_region:
+ release_region(SBC7240_ENABLE_PORT, 1);
+err_out:
+ return rc;
+}
+
+module_init(sbc7240_wdt_init);
+module_exit(sbc7240_wdt_unload);
+
+MODULE_AUTHOR("Gilles Gigan");
+MODULE_DESCRIPTION("Watchdog device driver for single board"
+ " computers EPIC Nano 7240 from iEi");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sbc8360.c b/drivers/watchdog/sbc8360.c
new file mode 100644
index 000000000..4f8b9912f
--- /dev/null
+++ b/drivers/watchdog/sbc8360.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SBC8360 Watchdog driver
+ *
+ * (c) Copyright 2005 Webcon, Inc.
+ *
+ * Based on ib700wdt.c, which is based on advantechwdt.c which is based
+ * on acquirewdt.c which is based on wdt.c.
+ *
+ * (c) Copyright 2001 Charles Howes <chowes@vsol.net>
+ *
+ * Based on advantechwdt.c which is based on acquirewdt.c which
+ * is based on wdt.c.
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * Based on acquirewdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Added timeout module option to override default
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+static unsigned long sbc8360_is_open;
+static char expect_close;
+
+/*
+ *
+ * Watchdog Timer Configuration
+ *
+ * The function of the watchdog timer is to reset the system automatically
+ * and is defined at I/O port 0120H and 0121H. To enable the watchdog timer
+ * and allow the system to reset, write appropriate values from the table
+ * below to I/O port 0120H and 0121H. To disable the timer, write a zero
+ * value to I/O port 0121H for the system to stop the watchdog function.
+ *
+ * The following describes how the timer should be programmed (according to
+ * the vendor documentation)
+ *
+ * Enabling Watchdog:
+ * MOV AX,000AH (enable, phase I)
+ * MOV DX,0120H
+ * OUT DX,AX
+ * MOV AX,000BH (enable, phase II)
+ * MOV DX,0120H
+ * OUT DX,AX
+ * MOV AX,000nH (set multiplier n, from 1-4)
+ * MOV DX,0120H
+ * OUT DX,AX
+ * MOV AX,000mH (set base timer m, from 0-F)
+ * MOV DX,0121H
+ * OUT DX,AX
+ *
+ * Reset timer:
+ * MOV AX,000mH (same as set base timer, above)
+ * MOV DX,0121H
+ * OUT DX,AX
+ *
+ * Disabling Watchdog:
+ * MOV AX,0000H (a zero value)
+ * MOV DX,0120H
+ * OUT DX,AX
+ *
+ * Watchdog timeout configuration values:
+ * N
+ * M | 1 2 3 4
+ * --|----------------------------------
+ * 0 | 0.5s 5s 50s 100s
+ * 1 | 1s 10s 100s 200s
+ * 2 | 1.5s 15s 150s 300s
+ * 3 | 2s 20s 200s 400s
+ * 4 | 2.5s 25s 250s 500s
+ * 5 | 3s 30s 300s 600s
+ * 6 | 3.5s 35s 350s 700s
+ * 7 | 4s 40s 400s 800s
+ * 8 | 4.5s 45s 450s 900s
+ * 9 | 5s 50s 500s 1000s
+ * A | 5.5s 55s 550s 1100s
+ * B | 6s 60s 600s 1200s
+ * C | 6.5s 65s 650s 1300s
+ * D | 7s 70s 700s 1400s
+ * E | 7.5s 75s 750s 1500s
+ * F | 8s 80s 800s 1600s
+ *
+ * Another way to say the same things is:
+ * For N=1, Timeout = (M+1) * 0.5s
+ * For N=2, Timeout = (M+1) * 5s
+ * For N=3, Timeout = (M+1) * 50s
+ * For N=4, Timeout = (M+1) * 100s
+ *
+ */
+
+static int wd_times[64][2] = {
+ {0, 1}, /* 0 = 0.5s */
+ {1, 1}, /* 1 = 1s */
+ {2, 1}, /* 2 = 1.5s */
+ {3, 1}, /* 3 = 2s */
+ {4, 1}, /* 4 = 2.5s */
+ {5, 1}, /* 5 = 3s */
+ {6, 1}, /* 6 = 3.5s */
+ {7, 1}, /* 7 = 4s */
+ {8, 1}, /* 8 = 4.5s */
+ {9, 1}, /* 9 = 5s */
+ {0xA, 1}, /* 10 = 5.5s */
+ {0xB, 1}, /* 11 = 6s */
+ {0xC, 1}, /* 12 = 6.5s */
+ {0xD, 1}, /* 13 = 7s */
+ {0xE, 1}, /* 14 = 7.5s */
+ {0xF, 1}, /* 15 = 8s */
+ {0, 2}, /* 16 = 5s */
+ {1, 2}, /* 17 = 10s */
+ {2, 2}, /* 18 = 15s */
+ {3, 2}, /* 19 = 20s */
+ {4, 2}, /* 20 = 25s */
+ {5, 2}, /* 21 = 30s */
+ {6, 2}, /* 22 = 35s */
+ {7, 2}, /* 23 = 40s */
+ {8, 2}, /* 24 = 45s */
+ {9, 2}, /* 25 = 50s */
+ {0xA, 2}, /* 26 = 55s */
+ {0xB, 2}, /* 27 = 60s */
+ {0xC, 2}, /* 28 = 65s */
+ {0xD, 2}, /* 29 = 70s */
+ {0xE, 2}, /* 30 = 75s */
+ {0xF, 2}, /* 31 = 80s */
+ {0, 3}, /* 32 = 50s */
+ {1, 3}, /* 33 = 100s */
+ {2, 3}, /* 34 = 150s */
+ {3, 3}, /* 35 = 200s */
+ {4, 3}, /* 36 = 250s */
+ {5, 3}, /* 37 = 300s */
+ {6, 3}, /* 38 = 350s */
+ {7, 3}, /* 39 = 400s */
+ {8, 3}, /* 40 = 450s */
+ {9, 3}, /* 41 = 500s */
+ {0xA, 3}, /* 42 = 550s */
+ {0xB, 3}, /* 43 = 600s */
+ {0xC, 3}, /* 44 = 650s */
+ {0xD, 3}, /* 45 = 700s */
+ {0xE, 3}, /* 46 = 750s */
+ {0xF, 3}, /* 47 = 800s */
+ {0, 4}, /* 48 = 100s */
+ {1, 4}, /* 49 = 200s */
+ {2, 4}, /* 50 = 300s */
+ {3, 4}, /* 51 = 400s */
+ {4, 4}, /* 52 = 500s */
+ {5, 4}, /* 53 = 600s */
+ {6, 4}, /* 54 = 700s */
+ {7, 4}, /* 55 = 800s */
+ {8, 4}, /* 56 = 900s */
+ {9, 4}, /* 57 = 1000s */
+ {0xA, 4}, /* 58 = 1100s */
+ {0xB, 4}, /* 59 = 1200s */
+ {0xC, 4}, /* 60 = 1300s */
+ {0xD, 4}, /* 61 = 1400s */
+ {0xE, 4}, /* 62 = 1500s */
+ {0xF, 4} /* 63 = 1600s */
+};
+
+#define SBC8360_ENABLE 0x120
+#define SBC8360_BASETIME 0x121
+
+static int timeout = 27;
+static int wd_margin = 0xB;
+static int wd_multiplier = 2;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))");
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/*
+ * Kernel methods.
+ */
+
+/* Activate and pre-configure watchdog */
+static void sbc8360_activate(void)
+{
+ /* Enable the watchdog */
+ outb(0x0A, SBC8360_ENABLE);
+ msleep_interruptible(100);
+ outb(0x0B, SBC8360_ENABLE);
+ msleep_interruptible(100);
+ /* Set timeout multiplier */
+ outb(wd_multiplier, SBC8360_ENABLE);
+ msleep_interruptible(100);
+ /* Nothing happens until first sbc8360_ping() */
+}
+
+/* Kernel pings watchdog */
+static void sbc8360_ping(void)
+{
+ /* Write the base timer register */
+ outb(wd_margin, SBC8360_BASETIME);
+}
+
+/* stop watchdog */
+static void sbc8360_stop(void)
+{
+ /* De-activate the watchdog */
+ outb(0, SBC8360_ENABLE);
+}
+
+/* Userspace pings kernel driver, or requests clean close */
+static ssize_t sbc8360_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ sbc8360_ping();
+ }
+ return count;
+}
+
+static int sbc8360_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &sbc8360_is_open))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate and ping once to start the countdown */
+ sbc8360_activate();
+ sbc8360_ping();
+ return stream_open(inode, file);
+}
+
+static int sbc8360_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42)
+ sbc8360_stop();
+ else
+ pr_crit("SBC8360 device closed unexpectedly. SBC8360 will not stop!\n");
+
+ clear_bit(0, &sbc8360_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ sbc8360_stop(); /* Disable the SBC8360 Watchdog */
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations sbc8360_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sbc8360_write,
+ .open = sbc8360_open,
+ .release = sbc8360_close,
+};
+
+static struct miscdevice sbc8360_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sbc8360_fops,
+};
+
+/*
+ * The SBC8360 needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block sbc8360_notifier = {
+ .notifier_call = sbc8360_notify_sys,
+};
+
+static int __init sbc8360_init(void)
+{
+ int res;
+ unsigned long int mseconds = 60000;
+
+ if (timeout < 0 || timeout > 63) {
+ pr_err("Invalid timeout index (must be 0-63)\n");
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) {
+ pr_err("ENABLE method I/O %X is not available\n",
+ SBC8360_ENABLE);
+ res = -EIO;
+ goto out;
+ }
+ if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) {
+ pr_err("BASETIME method I/O %X is not available\n",
+ SBC8360_BASETIME);
+ res = -EIO;
+ goto out_nobasetimereg;
+ }
+
+ res = register_reboot_notifier(&sbc8360_notifier);
+ if (res) {
+ pr_err("Failed to register reboot notifier\n");
+ goto out_noreboot;
+ }
+
+ res = misc_register(&sbc8360_miscdev);
+ if (res) {
+ pr_err("failed to register misc device\n");
+ goto out_nomisc;
+ }
+
+ wd_margin = wd_times[timeout][0];
+ wd_multiplier = wd_times[timeout][1];
+
+ if (wd_multiplier == 1)
+ mseconds = (wd_margin + 1) * 500;
+ else if (wd_multiplier == 2)
+ mseconds = (wd_margin + 1) * 5000;
+ else if (wd_multiplier == 3)
+ mseconds = (wd_margin + 1) * 50000;
+ else if (wd_multiplier == 4)
+ mseconds = (wd_margin + 1) * 100000;
+
+ /* My kingdom for the ability to print "0.5 seconds" in the kernel! */
+ pr_info("Timeout set at %ld ms\n", mseconds);
+
+ return 0;
+
+out_nomisc:
+ unregister_reboot_notifier(&sbc8360_notifier);
+out_noreboot:
+ release_region(SBC8360_BASETIME, 1);
+out_nobasetimereg:
+ release_region(SBC8360_ENABLE, 1);
+out:
+ return res;
+}
+
+static void __exit sbc8360_exit(void)
+{
+ misc_deregister(&sbc8360_miscdev);
+ unregister_reboot_notifier(&sbc8360_notifier);
+ release_region(SBC8360_ENABLE, 1);
+ release_region(SBC8360_BASETIME, 1);
+}
+
+module_init(sbc8360_init);
+module_exit(sbc8360_exit);
+
+MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>");
+MODULE_DESCRIPTION("SBC8360 watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.01");
+
+/* end of sbc8360.c */
diff --git a/drivers/watchdog/sbc_epx_c3.c b/drivers/watchdog/sbc_epx_c3.c
new file mode 100644
index 000000000..5e3a9ddb9
--- /dev/null
+++ b/drivers/watchdog/sbc_epx_c3.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SBC EPX C3 0.1 A Hardware Watchdog Device for the Winsystems EPX-C3
+ * single board computer
+ *
+ * (c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights
+ * Reserved.
+ *
+ * based on softdog.c by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+static int epx_c3_alive;
+
+#define WATCHDOG_TIMEOUT 1 /* 1 sec default 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) ")");
+
+#define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */
+#define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */
+
+static void epx_c3_start(void)
+{
+ outb(1, EPXC3_WATCHDOG_CTL_REG);
+}
+
+static void epx_c3_stop(void)
+{
+
+ outb(0, EPXC3_WATCHDOG_CTL_REG);
+
+ pr_info("Stopped watchdog timer\n");
+}
+
+static void epx_c3_pet(void)
+{
+ outb(1, EPXC3_WATCHDOG_PET_REG);
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int epx_c3_open(struct inode *inode, struct file *file)
+{
+ if (epx_c3_alive)
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate timer */
+ epx_c3_start();
+ epx_c3_pet();
+
+ epx_c3_alive = 1;
+ pr_info("Started watchdog timer\n");
+
+ return stream_open(inode, file);
+}
+
+static int epx_c3_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer.
+ * Lock it in if it's a module and we defined ...NOWAYOUT */
+ if (!nowayout)
+ epx_c3_stop(); /* Turn the WDT off */
+
+ epx_c3_alive = 0;
+
+ return 0;
+}
+
+static ssize_t epx_c3_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* Refresh the timer. */
+ if (len)
+ epx_c3_pet();
+ return len;
+}
+
+static long epx_c3_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int options, retval = -EINVAL;
+ int __user *argp = (void __user *)arg;
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING,
+ .firmware_version = 0,
+ .identity = "Winsystems EPX-C3 H/W Watchdog",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, argp);
+ case WDIOC_SETOPTIONS:
+ if (get_user(options, argp))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ epx_c3_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ epx_c3_start();
+ retval = 0;
+ }
+
+ return retval;
+ case WDIOC_KEEPALIVE:
+ epx_c3_pet();
+ return 0;
+ case WDIOC_GETTIMEOUT:
+ return put_user(WATCHDOG_TIMEOUT, argp);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ epx_c3_stop(); /* Turn the WDT off */
+
+ return NOTIFY_DONE;
+}
+
+static const struct file_operations epx_c3_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = epx_c3_write,
+ .unlocked_ioctl = epx_c3_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = epx_c3_open,
+ .release = epx_c3_release,
+};
+
+static struct miscdevice epx_c3_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &epx_c3_fops,
+};
+
+static struct notifier_block epx_c3_notifier = {
+ .notifier_call = epx_c3_notify_sys,
+};
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog"))
+ return -EBUSY;
+
+ ret = register_reboot_notifier(&epx_c3_notifier);
+ if (ret) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto out;
+ }
+
+ ret = misc_register(&epx_c3_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ unregister_reboot_notifier(&epx_c3_notifier);
+ goto out;
+ }
+
+ pr_info("Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n");
+
+ return 0;
+
+out:
+ release_region(EPXC3_WATCHDOG_CTL_REG, 2);
+ return ret;
+}
+
+static void __exit watchdog_exit(void)
+{
+ misc_deregister(&epx_c3_miscdev);
+ unregister_reboot_notifier(&epx_c3_notifier);
+ release_region(EPXC3_WATCHDOG_CTL_REG, 2);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>");
+MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC. "
+ "Note that there is no way to probe for this device -- "
+ "so only use it if you are *sure* you are running on this specific "
+ "SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sbc_fitpc2_wdt.c b/drivers/watchdog/sbc_fitpc2_wdt.c
new file mode 100644
index 000000000..13db71e16
--- /dev/null
+++ b/drivers/watchdog/sbc_fitpc2_wdt.c
@@ -0,0 +1,266 @@
+/*
+ * Watchdog driver for SBC-FITPC2 board
+ *
+ * Author: Denis Turischev <denis@compulab.co.il>
+ *
+ * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int margin = 60; /* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+static DEFINE_MUTEX(wdt_lock);
+
+#define WDT_IN_USE 0
+#define WDT_OK_TO_CLOSE 1
+
+#define COMMAND_PORT 0x4c
+#define DATA_PORT 0x48
+
+#define IFACE_ON_COMMAND 1
+#define REBOOT_COMMAND 2
+
+#define WATCHDOG_NAME "SBC-FITPC2 Watchdog"
+
+static void wdt_send_data(unsigned char command, unsigned char data)
+{
+ outb(data, DATA_PORT);
+ msleep(200);
+ outb(command, COMMAND_PORT);
+ msleep(100);
+}
+
+static void wdt_enable(void)
+{
+ mutex_lock(&wdt_lock);
+ wdt_send_data(IFACE_ON_COMMAND, 1);
+ wdt_send_data(REBOOT_COMMAND, margin);
+ mutex_unlock(&wdt_lock);
+}
+
+static void wdt_disable(void)
+{
+ mutex_lock(&wdt_lock);
+ wdt_send_data(IFACE_ON_COMMAND, 0);
+ wdt_send_data(REBOOT_COMMAND, 0);
+ mutex_unlock(&wdt_lock);
+}
+
+static int fitpc2_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+ return -EBUSY;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ wdt_enable();
+
+ return stream_open(inode, file);
+}
+
+static ssize_t fitpc2_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ size_t i;
+
+ if (!len)
+ return 0;
+
+ if (nowayout) {
+ len = 0;
+ goto out;
+ }
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ if (c == 'V')
+ set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ }
+
+out:
+ wdt_enable();
+
+ return len;
+}
+
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING,
+ .identity = WATCHDOG_NAME,
+};
+
+
+static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int __user *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int __user *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_enable();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int __user *)arg);
+ if (ret)
+ break;
+
+ if (time < 31 || time > 255) {
+ ret = -EINVAL;
+ break;
+ }
+
+ margin = time;
+ wdt_enable();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(margin, (int __user *)arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int fitpc2_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+ wdt_disable();
+ pr_info("Device disabled\n");
+ } else {
+ pr_warn("Device closed unexpectedly - timer will not stop\n");
+ wdt_enable();
+ }
+
+ clear_bit(WDT_IN_USE, &wdt_status);
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ return 0;
+}
+
+
+static const struct file_operations fitpc2_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = fitpc2_wdt_write,
+ .unlocked_ioctl = fitpc2_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = fitpc2_wdt_open,
+ .release = fitpc2_wdt_release,
+};
+
+static struct miscdevice fitpc2_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &fitpc2_wdt_fops,
+};
+
+static int __init fitpc2_wdt_init(void)
+{
+ int err;
+ const char *brd_name;
+
+ brd_name = dmi_get_system_info(DMI_BOARD_NAME);
+
+ if (!brd_name || !strstr(brd_name, "SBC-FITPC2"))
+ return -ENODEV;
+
+ pr_info("%s found\n", brd_name);
+
+ if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
+ return -EIO;
+ }
+
+ if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
+ err = -EIO;
+ goto err_data_port;
+ }
+
+ if (margin < 31 || margin > 255) {
+ pr_err("margin must be in range 31 - 255 seconds, you tried to set %d\n",
+ margin);
+ err = -EINVAL;
+ goto err_margin;
+ }
+
+ err = misc_register(&fitpc2_wdt_miscdev);
+ if (err) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, err);
+ goto err_margin;
+ }
+
+ return 0;
+
+err_margin:
+ release_region(DATA_PORT, 1);
+err_data_port:
+ release_region(COMMAND_PORT, 1);
+
+ return err;
+}
+
+static void __exit fitpc2_wdt_exit(void)
+{
+ misc_deregister(&fitpc2_wdt_miscdev);
+ release_region(DATA_PORT, 1);
+ release_region(COMMAND_PORT, 1);
+}
+
+module_init(fitpc2_wdt_init);
+module_exit(fitpc2_wdt_exit);
+
+MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
+MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
+
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c
new file mode 100644
index 000000000..7bf28545b
--- /dev/null
+++ b/drivers/watchdog/sbsa_gwdt.c
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SBSA(Server Base System Architecture) Generic Watchdog driver
+ *
+ * Copyright (c) 2015, Linaro Ltd.
+ * Author: Fu Wei <fu.wei@linaro.org>
+ * Suravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>
+ * Al Stone <al.stone@linaro.org>
+ * Timur Tabi <timur@codeaurora.org>
+ *
+ * ARM SBSA Generic Watchdog has two stage timeouts:
+ * the first signal (WS0) is for alerting the system by interrupt,
+ * the second one (WS1) is a real hardware reset.
+ * More details about the hardware specification of this device:
+ * ARM DEN0029B - Server Base System Architecture (SBSA)
+ *
+ * This driver can operate ARM SBSA Generic Watchdog as a single stage watchdog
+ * or a two stages watchdog, it's set up by the module parameter "action".
+ * In the single stage mode, when the timeout is reached, your system
+ * will be reset by WS1. The first signal (WS0) is ignored.
+ * In the two stages mode, when the timeout is reached, the first signal (WS0)
+ * will trigger panic. If the system is getting into trouble and cannot be reset
+ * by panic or restart properly by the kdump kernel(if supported), then the
+ * second stage (as long as the first stage) will be reached, system will be
+ * reset by WS1. This function can help administrator to backup the system
+ * context info by panic console output or kdump.
+ *
+ * SBSA GWDT:
+ * if action is 1 (the two stages mode):
+ * |--------WOR-------WS0--------WOR-------WS1
+ * |----timeout-----(panic)----timeout-----reset
+ *
+ * if action is 0 (the single stage mode):
+ * |------WOR-----WS0(ignored)-----WOR------WS1
+ * |--------------timeout-------------------reset
+ *
+ * Note: Since this watchdog timer has two stages, and each stage is determined
+ * by WOR, in the single stage mode, the timeout is (WOR * 2); in the two
+ * stages mode, the timeout is WOR. The maximum timeout in the two stages mode
+ * is half of that in the single stage mode.
+ */
+
+#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <asm/arch_timer.h>
+
+#define DRV_NAME "sbsa-gwdt"
+#define WATCHDOG_NAME "SBSA Generic Watchdog"
+
+/* SBSA Generic Watchdog register definitions */
+/* refresh frame */
+#define SBSA_GWDT_WRR 0x000
+
+/* control frame */
+#define SBSA_GWDT_WCS 0x000
+#define SBSA_GWDT_WOR 0x008
+#define SBSA_GWDT_WCV 0x010
+
+/* refresh/control frame */
+#define SBSA_GWDT_W_IIDR 0xfcc
+#define SBSA_GWDT_IDR 0xfd0
+
+/* Watchdog Control and Status Register */
+#define SBSA_GWDT_WCS_EN BIT(0)
+#define SBSA_GWDT_WCS_WS0 BIT(1)
+#define SBSA_GWDT_WCS_WS1 BIT(2)
+
+#define SBSA_GWDT_VERSION_MASK 0xF
+#define SBSA_GWDT_VERSION_SHIFT 16
+
+/**
+ * struct sbsa_gwdt - Internal representation of the SBSA GWDT
+ * @wdd: kernel watchdog_device structure
+ * @clk: store the System Counter clock frequency, in Hz.
+ * @version: store the architecture version
+ * @refresh_base: Virtual address of the watchdog refresh frame
+ * @control_base: Virtual address of the watchdog control frame
+ */
+struct sbsa_gwdt {
+ struct watchdog_device wdd;
+ u32 clk;
+ int version;
+ void __iomem *refresh_base;
+ void __iomem *control_base;
+};
+
+#define DEFAULT_TIMEOUT 10 /* seconds */
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (>=0, default="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+
+/*
+ * action refers to action taken when watchdog gets WS0
+ * 0 = skip
+ * 1 = panic
+ * defaults to skip (0)
+ */
+static int action;
+module_param(action, int, 0);
+MODULE_PARM_DESC(action, "after watchdog gets WS0 interrupt, do: "
+ "0 = skip(*) 1 = panic");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, S_IRUGO);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+/*
+ * Arm Base System Architecture 1.0 introduces watchdog v1 which
+ * increases the length watchdog offset register to 48 bits.
+ * - For version 0: WOR is 32 bits;
+ * - For version 1: WOR is 48 bits which comprises the register
+ * offset 0x8 and 0xC, and the bits [63:48] are reserved which are
+ * Read-As-Zero and Writes-Ignored.
+ */
+static u64 sbsa_gwdt_reg_read(struct sbsa_gwdt *gwdt)
+{
+ if (gwdt->version == 0)
+ return readl(gwdt->control_base + SBSA_GWDT_WOR);
+ else
+ return lo_hi_readq(gwdt->control_base + SBSA_GWDT_WOR);
+}
+
+static void sbsa_gwdt_reg_write(u64 val, struct sbsa_gwdt *gwdt)
+{
+ if (gwdt->version == 0)
+ writel((u32)val, gwdt->control_base + SBSA_GWDT_WOR);
+ else
+ lo_hi_writeq(val, gwdt->control_base + SBSA_GWDT_WOR);
+}
+
+/*
+ * watchdog operation functions
+ */
+static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+
+ wdd->timeout = timeout;
+ timeout = clamp_t(unsigned int, timeout, 1, wdd->max_hw_heartbeat_ms / 1000);
+
+ if (action)
+ sbsa_gwdt_reg_write((u64)gwdt->clk * timeout, gwdt);
+ else
+ /*
+ * In the single stage mode, The first signal (WS0) is ignored,
+ * the timeout is (WOR * 2), so the WOR should be configured
+ * to half value of timeout.
+ */
+ sbsa_gwdt_reg_write(((u64)gwdt->clk / 2) * timeout, gwdt);
+
+ return 0;
+}
+
+static unsigned int sbsa_gwdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+ u64 timeleft = 0;
+
+ /*
+ * In the single stage mode, if WS0 is deasserted
+ * (watchdog is in the first stage),
+ * timeleft = WOR + (WCV - system counter)
+ */
+ if (!action &&
+ !(readl(gwdt->control_base + SBSA_GWDT_WCS) & SBSA_GWDT_WCS_WS0))
+ timeleft += sbsa_gwdt_reg_read(gwdt);
+
+ timeleft += lo_hi_readq(gwdt->control_base + SBSA_GWDT_WCV) -
+ arch_timer_read_counter();
+
+ do_div(timeleft, gwdt->clk);
+
+ return timeleft;
+}
+
+static int sbsa_gwdt_keepalive(struct watchdog_device *wdd)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+
+ /*
+ * Writing WRR for an explicit watchdog refresh.
+ * You can write anyting (like 0).
+ */
+ writel(0, gwdt->refresh_base + SBSA_GWDT_WRR);
+
+ return 0;
+}
+
+static void sbsa_gwdt_get_version(struct watchdog_device *wdd)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+ int ver;
+
+ ver = readl(gwdt->control_base + SBSA_GWDT_W_IIDR);
+ ver = (ver >> SBSA_GWDT_VERSION_SHIFT) & SBSA_GWDT_VERSION_MASK;
+
+ gwdt->version = ver;
+}
+
+static int sbsa_gwdt_start(struct watchdog_device *wdd)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+
+ /* writing WCS will cause an explicit watchdog refresh */
+ writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS);
+
+ return 0;
+}
+
+static int sbsa_gwdt_stop(struct watchdog_device *wdd)
+{
+ struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd);
+
+ /* Simply write 0 to WCS to clean WCS_EN bit */
+ writel(0, gwdt->control_base + SBSA_GWDT_WCS);
+
+ return 0;
+}
+
+static irqreturn_t sbsa_gwdt_interrupt(int irq, void *dev_id)
+{
+ panic(WATCHDOG_NAME " timeout");
+
+ return IRQ_HANDLED;
+}
+
+static const struct watchdog_info sbsa_gwdt_info = {
+ .identity = WATCHDOG_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE |
+ WDIOF_CARDRESET,
+};
+
+static const struct watchdog_ops sbsa_gwdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sbsa_gwdt_start,
+ .stop = sbsa_gwdt_stop,
+ .ping = sbsa_gwdt_keepalive,
+ .set_timeout = sbsa_gwdt_set_timeout,
+ .get_timeleft = sbsa_gwdt_get_timeleft,
+};
+
+static int sbsa_gwdt_probe(struct platform_device *pdev)
+{
+ void __iomem *rf_base, *cf_base;
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct sbsa_gwdt *gwdt;
+ int ret, irq;
+ u32 status;
+
+ gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
+ if (!gwdt)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, gwdt);
+
+ cf_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(cf_base))
+ return PTR_ERR(cf_base);
+
+ rf_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(rf_base))
+ return PTR_ERR(rf_base);
+
+ /*
+ * Get the frequency of system counter from the cp15 interface of ARM
+ * Generic timer. We don't need to check it, because if it returns "0",
+ * system would panic in very early stage.
+ */
+ gwdt->clk = arch_timer_get_cntfrq();
+ gwdt->refresh_base = rf_base;
+ gwdt->control_base = cf_base;
+
+ wdd = &gwdt->wdd;
+ wdd->parent = dev;
+ wdd->info = &sbsa_gwdt_info;
+ wdd->ops = &sbsa_gwdt_ops;
+ wdd->min_timeout = 1;
+ wdd->timeout = DEFAULT_TIMEOUT;
+ watchdog_set_drvdata(wdd, gwdt);
+ watchdog_set_nowayout(wdd, nowayout);
+ sbsa_gwdt_get_version(wdd);
+ if (gwdt->version == 0)
+ wdd->max_hw_heartbeat_ms = U32_MAX / gwdt->clk * 1000;
+ else
+ wdd->max_hw_heartbeat_ms = GENMASK_ULL(47, 0) / gwdt->clk * 1000;
+
+ status = readl(cf_base + SBSA_GWDT_WCS);
+ if (status & SBSA_GWDT_WCS_WS1) {
+ dev_warn(dev, "System reset by WDT.\n");
+ wdd->bootstatus |= WDIOF_CARDRESET;
+ }
+ if (status & SBSA_GWDT_WCS_EN)
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ if (action) {
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ action = 0;
+ dev_warn(dev, "unable to get ws0 interrupt.\n");
+ } else {
+ /*
+ * In case there is a pending ws0 interrupt, just ping
+ * the watchdog before registering the interrupt routine
+ */
+ writel(0, rf_base + SBSA_GWDT_WRR);
+ if (devm_request_irq(dev, irq, sbsa_gwdt_interrupt, 0,
+ pdev->name, gwdt)) {
+ action = 0;
+ dev_warn(dev, "unable to request IRQ %d.\n",
+ irq);
+ }
+ }
+ if (!action)
+ dev_warn(dev, "falling back to single stage mode.\n");
+ }
+ /*
+ * In the single stage mode, The first signal (WS0) is ignored,
+ * the timeout is (WOR * 2), so the maximum timeout should be doubled.
+ */
+ if (!action)
+ wdd->max_hw_heartbeat_ms *= 2;
+
+ watchdog_init_timeout(wdd, timeout, dev);
+ /*
+ * Update timeout to WOR.
+ * Because of the explicit watchdog refresh mechanism,
+ * it's also a ping, if watchdog is enabled.
+ */
+ sbsa_gwdt_set_timeout(wdd, wdd->timeout);
+
+ watchdog_stop_on_reboot(wdd);
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "Initialized with %ds timeout @ %u Hz, action=%d.%s\n",
+ wdd->timeout, gwdt->clk, action,
+ status & SBSA_GWDT_WCS_EN ? " [enabled]" : "");
+
+ return 0;
+}
+
+/* Disable watchdog if it is active during suspend */
+static int __maybe_unused sbsa_gwdt_suspend(struct device *dev)
+{
+ struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&gwdt->wdd))
+ sbsa_gwdt_stop(&gwdt->wdd);
+
+ return 0;
+}
+
+/* Enable watchdog if necessary */
+static int __maybe_unused sbsa_gwdt_resume(struct device *dev)
+{
+ struct sbsa_gwdt *gwdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&gwdt->wdd))
+ sbsa_gwdt_start(&gwdt->wdd);
+
+ return 0;
+}
+
+static const struct dev_pm_ops sbsa_gwdt_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sbsa_gwdt_suspend, sbsa_gwdt_resume)
+};
+
+static const struct of_device_id sbsa_gwdt_of_match[] = {
+ { .compatible = "arm,sbsa-gwdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sbsa_gwdt_of_match);
+
+static const struct platform_device_id sbsa_gwdt_pdev_match[] = {
+ { .name = DRV_NAME, },
+ {},
+};
+MODULE_DEVICE_TABLE(platform, sbsa_gwdt_pdev_match);
+
+static struct platform_driver sbsa_gwdt_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .pm = &sbsa_gwdt_pm_ops,
+ .of_match_table = sbsa_gwdt_of_match,
+ },
+ .probe = sbsa_gwdt_probe,
+ .id_table = sbsa_gwdt_pdev_match,
+};
+
+module_platform_driver(sbsa_gwdt_driver);
+
+MODULE_DESCRIPTION("SBSA Generic Watchdog Driver");
+MODULE_AUTHOR("Fu Wei <fu.wei@linaro.org>");
+MODULE_AUTHOR("Suravee Suthikulpanit <Suravee.Suthikulpanit@amd.com>");
+MODULE_AUTHOR("Al Stone <al.stone@linaro.org>");
+MODULE_AUTHOR("Timur Tabi <timur@codeaurora.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c
new file mode 100644
index 000000000..f22ebe89f
--- /dev/null
+++ b/drivers/watchdog/sc1200wdt.c
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>,
+ * All Rights Reserved.
+ * Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively.
+ *
+ * The author(s) of this software shall not be held liable for damages
+ * of any nature resulting due to the use of this software. This
+ * software is provided AS-IS with no warranties.
+ *
+ * Changelog:
+ * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware.
+ * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik
+ * and Alan Cox.
+ * 20020222 Zwane Mwaikambo Added probing.
+ * 20020225 Zwane Mwaikambo Added ISAPNP support.
+ * 20020412 Rob Radez Broke out start/stop functions
+ * <rob@osinvestor.com> Return proper status instead of
+ * temperature warning
+ * Add WDIOC_GETBOOTSTATUS and
+ * WDIOC_SETOPTIONS ioctls
+ * Fix CONFIG_WATCHDOG_NOWAYOUT
+ * 20020530 Joel Becker Add Matt Domsch's nowayout module
+ * option
+ * 20030116 Adam Belay Updated to the latest pnp code
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/fs.h>
+#include <linux/semaphore.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#define SC1200_MODULE_VER "build 20020303"
+#define SC1200_MODULE_NAME "sc1200wdt"
+
+#define MAX_TIMEOUT 255 /* 255 minutes */
+#define PMIR (io) /* Power Management Index Register */
+#define PMDR (io+1) /* Power Management Data Register */
+
+/* Data Register indexes */
+#define FER1 0x00 /* Function enable register 1 */
+#define FER2 0x01 /* Function enable register 2 */
+#define PMC1 0x02 /* Power Management Ctrl 1 */
+#define PMC2 0x03 /* Power Management Ctrl 2 */
+#define PMC3 0x04 /* Power Management Ctrl 3 */
+#define WDTO 0x05 /* Watchdog timeout register */
+#define WDCF 0x06 /* Watchdog config register */
+#define WDST 0x07 /* Watchdog status register */
+
+/* WDCF bitfields - which devices assert WDO */
+#define KBC_IRQ 0x01 /* Keyboard Controller */
+#define MSE_IRQ 0x02 /* Mouse */
+#define UART1_IRQ 0x03 /* Serial0 */
+#define UART2_IRQ 0x04 /* Serial1 */
+/* 5 -7 are reserved */
+
+static int timeout = 1;
+static int io = -1;
+static int io_len = 2; /* for non plug and play */
+static unsigned long open_flag;
+static char expect_close;
+static DEFINE_SPINLOCK(sc1200wdt_lock); /* io port access serialisation */
+
+#if defined CONFIG_PNP
+static int isapnp = 1;
+static struct pnp_dev *wdt_dev;
+
+module_param(isapnp, int, 0);
+MODULE_PARM_DESC(isapnp,
+ "When set to 0 driver ISA PnP support will be disabled");
+#endif
+
+module_param_hw(io, int, ioport, 0);
+MODULE_PARM_DESC(io, "io port");
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1");
+
+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) ")");
+
+
+
+/* Read from Data Register */
+static inline void __sc1200wdt_read_data(unsigned char index,
+ unsigned char *data)
+{
+ outb_p(index, PMIR);
+ *data = inb(PMDR);
+}
+
+static void sc1200wdt_read_data(unsigned char index, unsigned char *data)
+{
+ spin_lock(&sc1200wdt_lock);
+ __sc1200wdt_read_data(index, data);
+ spin_unlock(&sc1200wdt_lock);
+}
+
+/* Write to Data Register */
+static inline void __sc1200wdt_write_data(unsigned char index,
+ unsigned char data)
+{
+ outb_p(index, PMIR);
+ outb(data, PMDR);
+}
+
+static inline void sc1200wdt_write_data(unsigned char index,
+ unsigned char data)
+{
+ spin_lock(&sc1200wdt_lock);
+ __sc1200wdt_write_data(index, data);
+ spin_unlock(&sc1200wdt_lock);
+}
+
+
+static void sc1200wdt_start(void)
+{
+ unsigned char reg;
+ spin_lock(&sc1200wdt_lock);
+
+ __sc1200wdt_read_data(WDCF, &reg);
+ /* assert WDO when any of the following interrupts are triggered too */
+ reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ);
+ __sc1200wdt_write_data(WDCF, reg);
+ /* set the timeout and get the ball rolling */
+ __sc1200wdt_write_data(WDTO, timeout);
+
+ spin_unlock(&sc1200wdt_lock);
+}
+
+static void sc1200wdt_stop(void)
+{
+ sc1200wdt_write_data(WDTO, 0);
+}
+
+/* This returns the status of the WDO signal, inactive high. */
+static inline int sc1200wdt_status(void)
+{
+ unsigned char ret;
+
+ sc1200wdt_read_data(WDST, &ret);
+ /* If the bit is inactive, the watchdog is enabled, so return
+ * KEEPALIVEPING which is a bit of a kludge because there's nothing
+ * else for enabled/disabled status
+ */
+ return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING;
+}
+
+static int sc1200wdt_open(struct inode *inode, struct file *file)
+{
+ /* allow one at a time */
+ if (test_and_set_bit(0, &open_flag))
+ return -EBUSY;
+
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+
+ sc1200wdt_start();
+ pr_info("Watchdog enabled, timeout = %d min(s)", timeout);
+
+ return stream_open(inode, file);
+}
+
+
+static long sc1200wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_timeout;
+ 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 = 0,
+ .identity = "PC87307/PC97307",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ return put_user(sc1200wdt_status(), p);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ sc1200wdt_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ sc1200wdt_start();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ sc1200wdt_write_data(WDTO, timeout);
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ /* the API states this is given in secs */
+ new_timeout /= 60;
+ if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+ return -EINVAL;
+ timeout = new_timeout;
+ sc1200wdt_write_data(WDTO, timeout);
+ fallthrough; /* and return the new timeout */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout * 60, p);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+
+static int sc1200wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ sc1200wdt_stop();
+ pr_info("Watchdog disabled\n");
+ } else {
+ sc1200wdt_write_data(WDTO, timeout);
+ pr_crit("Unexpected close!, timeout = %d min(s)\n", timeout);
+ }
+ clear_bit(0, &open_flag);
+ expect_close = 0;
+
+ return 0;
+}
+
+
+static ssize_t sc1200wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ sc1200wdt_write_data(WDTO, timeout);
+ return len;
+ }
+
+ return 0;
+}
+
+
+static int sc1200wdt_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ sc1200wdt_stop();
+
+ return NOTIFY_DONE;
+}
+
+
+static struct notifier_block sc1200wdt_notifier = {
+ .notifier_call = sc1200wdt_notify_sys,
+};
+
+static const struct file_operations sc1200wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sc1200wdt_write,
+ .unlocked_ioctl = sc1200wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = sc1200wdt_open,
+ .release = sc1200wdt_release,
+};
+
+static struct miscdevice sc1200wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sc1200wdt_fops,
+};
+
+
+static int __init sc1200wdt_probe(void)
+{
+ /* The probe works by reading the PMC3 register's default value of 0x0e
+ * there is one caveat, if the device disables the parallel port or any
+ * of the UARTs we won't be able to detect it.
+ * NB. This could be done with accuracy by reading the SID registers,
+ * but we don't have access to those io regions.
+ */
+
+ unsigned char reg;
+
+ sc1200wdt_read_data(PMC3, &reg);
+ reg &= 0x0f; /* we don't want the UART busy bits */
+ return (reg == 0x0e) ? 0 : -ENODEV;
+}
+
+
+#if defined CONFIG_PNP
+
+static const struct pnp_device_id scl200wdt_pnp_devices[] = {
+ /* National Semiconductor PC87307/PC97307 watchdog component */
+ {.id = "NSC0800", .driver_data = 0},
+ {.id = ""},
+};
+
+static int scl200wdt_pnp_probe(struct pnp_dev *dev,
+ const struct pnp_device_id *dev_id)
+{
+ /* this driver only supports one card at a time */
+ if (wdt_dev || !isapnp)
+ return -EBUSY;
+
+ wdt_dev = dev;
+ io = pnp_port_start(wdt_dev, 0);
+ io_len = pnp_port_len(wdt_dev, 0);
+
+ if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+ pr_err("Unable to register IO port %#x\n", io);
+ return -EBUSY;
+ }
+
+ pr_info("PnP device found at io port %#x/%d\n", io, io_len);
+ return 0;
+}
+
+static void scl200wdt_pnp_remove(struct pnp_dev *dev)
+{
+ if (wdt_dev) {
+ release_region(io, io_len);
+ wdt_dev = NULL;
+ }
+}
+
+static struct pnp_driver scl200wdt_pnp_driver = {
+ .name = "scl200wdt",
+ .id_table = scl200wdt_pnp_devices,
+ .probe = scl200wdt_pnp_probe,
+ .remove = scl200wdt_pnp_remove,
+};
+
+#endif /* CONFIG_PNP */
+
+
+static int __init sc1200wdt_init(void)
+{
+ int ret;
+
+ pr_info("%s\n", SC1200_MODULE_VER);
+
+#if defined CONFIG_PNP
+ if (isapnp) {
+ ret = pnp_register_driver(&scl200wdt_pnp_driver);
+ if (ret)
+ goto out_clean;
+ }
+#endif
+
+ if (io == -1) {
+ pr_err("io parameter must be specified\n");
+ ret = -EINVAL;
+ goto out_pnp;
+ }
+
+#if defined CONFIG_PNP
+ /* now that the user has specified an IO port and we haven't detected
+ * any devices, disable pnp support */
+ if (isapnp)
+ pnp_unregister_driver(&scl200wdt_pnp_driver);
+ isapnp = 0;
+#endif
+
+ if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+ pr_err("Unable to register IO port %#x\n", io);
+ ret = -EBUSY;
+ goto out_pnp;
+ }
+
+ ret = sc1200wdt_probe();
+ if (ret)
+ goto out_io;
+
+ ret = register_reboot_notifier(&sc1200wdt_notifier);
+ if (ret) {
+ pr_err("Unable to register reboot notifier err = %d\n", ret);
+ goto out_io;
+ }
+
+ ret = misc_register(&sc1200wdt_miscdev);
+ if (ret) {
+ pr_err("Unable to register miscdev on minor %d\n",
+ WATCHDOG_MINOR);
+ goto out_rbt;
+ }
+
+ /* ret = 0 */
+
+out_clean:
+ return ret;
+
+out_rbt:
+ unregister_reboot_notifier(&sc1200wdt_notifier);
+
+out_io:
+ release_region(io, io_len);
+
+out_pnp:
+#if defined CONFIG_PNP
+ if (isapnp)
+ pnp_unregister_driver(&scl200wdt_pnp_driver);
+#endif
+ goto out_clean;
+}
+
+
+static void __exit sc1200wdt_exit(void)
+{
+ misc_deregister(&sc1200wdt_miscdev);
+ unregister_reboot_notifier(&sc1200wdt_notifier);
+
+#if defined CONFIG_PNP
+ if (isapnp)
+ pnp_unregister_driver(&scl200wdt_pnp_driver);
+ else
+#endif
+ release_region(io, io_len);
+}
+
+module_init(sc1200wdt_init);
+module_exit(sc1200wdt_exit);
+
+MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
+MODULE_DESCRIPTION(
+ "Driver for National Semiconductor PC87307/PC97307 watchdog component");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c
new file mode 100644
index 000000000..ca65468f4
--- /dev/null
+++ b/drivers/watchdog/sc520_wdt.c
@@ -0,0 +1,431 @@
+// 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 */
+ del_timer_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,
+ .llseek = no_llseek,
+ .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/sch311x_wdt.c b/drivers/watchdog/sch311x_wdt.c
new file mode 100644
index 000000000..d8b77fe10
--- /dev/null
+++ b/drivers/watchdog/sch311x_wdt.c
@@ -0,0 +1,542 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * sch311x_wdt.c - Driver for the SCH311x Super-I/O chips
+ * integrated watchdog.
+ *
+ * (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+/* Includes */
+#include <linux/module.h> /* For module specific items */
+#include <linux/moduleparam.h> /* For new moduleparam's */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/... */
+#include <linux/miscdevice.h> /* For struct miscdevice */
+#include <linux/watchdog.h> /* For the watchdog specific items */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/fs.h> /* For file operations */
+#include <linux/platform_device.h> /* For platform_driver framework */
+#include <linux/ioport.h> /* For io-port access */
+#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+#include <linux/io.h> /* For inb/outb/... */
+
+/* Module and version information */
+#define DRV_NAME "sch311x_wdt"
+
+/* Runtime registers */
+#define GP60 0x47
+#define WDT_TIME_OUT 0x65
+#define WDT_VAL 0x66
+#define WDT_CFG 0x67
+#define WDT_CTRL 0x68
+
+/* internal variables */
+static unsigned long sch311x_wdt_is_open;
+static char sch311x_wdt_expect_close;
+static struct platform_device *sch311x_wdt_pdev;
+
+static int sch311x_ioports[] = { 0x2e, 0x4e, 0x162e, 0x164e, 0x00 };
+
+static struct { /* The devices private data */
+ /* the Runtime Register base address */
+ unsigned short runtime_reg;
+ /* The card's boot status */
+ int boot_status;
+ /* the lock for io operations */
+ spinlock_t io_lock;
+} sch311x_wdt_data;
+
+/* Module load parameters */
+static unsigned short force_id;
+module_param(force_id, ushort, 0);
+MODULE_PARM_DESC(force_id, "Override the detected device ID");
+
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1<= timeout <=15300, 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) ")");
+
+/*
+ * Super-IO functions
+ */
+
+static inline void sch311x_sio_enter(int sio_config_port)
+{
+ outb(0x55, sio_config_port);
+}
+
+static inline void sch311x_sio_exit(int sio_config_port)
+{
+ outb(0xaa, sio_config_port);
+}
+
+static inline int sch311x_sio_inb(int sio_config_port, int reg)
+{
+ outb(reg, sio_config_port);
+ return inb(sio_config_port + 1);
+}
+
+static inline void sch311x_sio_outb(int sio_config_port, int reg, int val)
+{
+ outb(reg, sio_config_port);
+ outb(val, sio_config_port + 1);
+}
+
+/*
+ * Watchdog Operations
+ */
+
+static void sch311x_wdt_set_timeout(int t)
+{
+ unsigned char timeout_unit = 0x80;
+
+ /* When new timeout is bigger then 255 seconds, we will use minutes */
+ if (t > 255) {
+ timeout_unit = 0;
+ t /= 60;
+ }
+
+ /* -- Watchdog Timeout --
+ * Bit 0-6 (Reserved)
+ * Bit 7 WDT Time-out Value Units Select
+ * (0 = Minutes, 1 = Seconds)
+ */
+ outb(timeout_unit, sch311x_wdt_data.runtime_reg + WDT_TIME_OUT);
+
+ /* -- Watchdog Timer Time-out Value --
+ * Bit 0-7 Binary coded units (0=Disabled, 1..255)
+ */
+ outb(t, sch311x_wdt_data.runtime_reg + WDT_VAL);
+}
+
+static void sch311x_wdt_start(void)
+{
+ unsigned char t;
+
+ spin_lock(&sch311x_wdt_data.io_lock);
+
+ /* set watchdog's timeout */
+ sch311x_wdt_set_timeout(timeout);
+ /* enable the watchdog */
+ /* -- General Purpose I/O Bit 6.0 --
+ * Bit 0, In/Out: 0 = Output, 1 = Input
+ * Bit 1, Polarity: 0 = No Invert, 1 = Invert
+ * Bit 2-3, Function select: 00 = GPI/O, 01 = LED1, 11 = WDT,
+ * 10 = Either Edge Triggered Intr.4
+ * Bit 4-6 (Reserved)
+ * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain
+ */
+ t = inb(sch311x_wdt_data.runtime_reg + GP60);
+ outb((t & ~0x0d) | 0x0c, sch311x_wdt_data.runtime_reg + GP60);
+
+ spin_unlock(&sch311x_wdt_data.io_lock);
+
+}
+
+static void sch311x_wdt_stop(void)
+{
+ unsigned char t;
+
+ spin_lock(&sch311x_wdt_data.io_lock);
+
+ /* stop the watchdog */
+ t = inb(sch311x_wdt_data.runtime_reg + GP60);
+ outb((t & ~0x0d) | 0x01, sch311x_wdt_data.runtime_reg + GP60);
+ /* disable timeout by setting it to 0 */
+ sch311x_wdt_set_timeout(0);
+
+ spin_unlock(&sch311x_wdt_data.io_lock);
+}
+
+static void sch311x_wdt_keepalive(void)
+{
+ spin_lock(&sch311x_wdt_data.io_lock);
+ sch311x_wdt_set_timeout(timeout);
+ spin_unlock(&sch311x_wdt_data.io_lock);
+}
+
+static int sch311x_wdt_set_heartbeat(int t)
+{
+ if (t < 1 || t > (255*60))
+ return -EINVAL;
+
+ /* When new timeout is bigger then 255 seconds,
+ * we will round up to minutes (with a max of 255) */
+ if (t > 255)
+ t = (((t - 1) / 60) + 1) * 60;
+
+ timeout = t;
+ return 0;
+}
+
+static void sch311x_wdt_get_status(int *status)
+{
+ unsigned char new_status;
+
+ *status = 0;
+
+ spin_lock(&sch311x_wdt_data.io_lock);
+
+ /* -- Watchdog timer control --
+ * Bit 0 Status Bit: 0 = Timer counting, 1 = Timeout occurred
+ * Bit 1 Reserved
+ * Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning)
+ * Bit 3 P20 Force Timeout enabled:
+ * 0 = P20 activity does not generate the WD timeout event
+ * 1 = P20 Allows rising edge of P20, from the keyboard
+ * controller, to force the WD timeout event.
+ * Bit 4-7 Reserved
+ */
+ new_status = inb(sch311x_wdt_data.runtime_reg + WDT_CTRL);
+ if (new_status & 0x01)
+ *status |= WDIOF_CARDRESET;
+
+ spin_unlock(&sch311x_wdt_data.io_lock);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t sch311x_wdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ sch311x_wdt_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ sch311x_wdt_expect_close = 42;
+ }
+ }
+ sch311x_wdt_keepalive();
+ }
+ return count;
+}
+
+static long sch311x_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int status;
+ int new_timeout;
+ 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 = DRV_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ {
+ sch311x_wdt_get_status(&status);
+ return put_user(status, p);
+ }
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(sch311x_wdt_data.boot_status, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ sch311x_wdt_stop();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ sch311x_wdt_start();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ sch311x_wdt_keepalive();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if (sch311x_wdt_set_heartbeat(new_timeout))
+ return -EINVAL;
+ sch311x_wdt_keepalive();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int sch311x_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &sch311x_wdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+ sch311x_wdt_start();
+ return stream_open(inode, file);
+}
+
+static int sch311x_wdt_close(struct inode *inode, struct file *file)
+{
+ if (sch311x_wdt_expect_close == 42) {
+ sch311x_wdt_stop();
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ sch311x_wdt_keepalive();
+ }
+ clear_bit(0, &sch311x_wdt_is_open);
+ sch311x_wdt_expect_close = 0;
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations sch311x_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sch311x_wdt_write,
+ .unlocked_ioctl = sch311x_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = sch311x_wdt_open,
+ .release = sch311x_wdt_close,
+};
+
+static struct miscdevice sch311x_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sch311x_wdt_fops,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static int sch311x_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int err;
+
+ spin_lock_init(&sch311x_wdt_data.io_lock);
+
+ if (!request_region(sch311x_wdt_data.runtime_reg + GP60, 1, DRV_NAME)) {
+ dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
+ sch311x_wdt_data.runtime_reg + GP60,
+ sch311x_wdt_data.runtime_reg + GP60);
+ err = -EBUSY;
+ goto exit;
+ }
+
+ if (!request_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4,
+ DRV_NAME)) {
+ dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n",
+ sch311x_wdt_data.runtime_reg + WDT_TIME_OUT,
+ sch311x_wdt_data.runtime_reg + WDT_CTRL);
+ err = -EBUSY;
+ goto exit_release_region;
+ }
+
+ /* Make sure that the watchdog is not running */
+ sch311x_wdt_stop();
+
+ /* Disable keyboard and mouse interaction and interrupt */
+ /* -- Watchdog timer configuration --
+ * Bit 0 Reserved
+ * Bit 1 Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr.
+ * Bit 2 Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr
+ * Bit 3 Reserved
+ * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled,
+ * 0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15)
+ */
+ outb(0, sch311x_wdt_data.runtime_reg + WDT_CFG);
+
+ /* Check that the heartbeat value is within it's range ;
+ * if not reset to the default */
+ if (sch311x_wdt_set_heartbeat(timeout)) {
+ sch311x_wdt_set_heartbeat(WATCHDOG_TIMEOUT);
+ dev_info(dev, "timeout value must be 1<=x<=15300, using %d\n",
+ timeout);
+ }
+
+ /* Get status at boot */
+ sch311x_wdt_get_status(&sch311x_wdt_data.boot_status);
+
+ sch311x_wdt_miscdev.parent = dev;
+
+ err = misc_register(&sch311x_wdt_miscdev);
+ if (err != 0) {
+ dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, err);
+ goto exit_release_region2;
+ }
+
+ dev_info(dev,
+ "SMSC SCH311x WDT initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return 0;
+
+exit_release_region2:
+ release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
+exit_release_region:
+ release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
+ sch311x_wdt_data.runtime_reg = 0;
+exit:
+ return err;
+}
+
+static int sch311x_wdt_remove(struct platform_device *pdev)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ sch311x_wdt_stop();
+
+ /* Deregister */
+ misc_deregister(&sch311x_wdt_miscdev);
+ release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4);
+ release_region(sch311x_wdt_data.runtime_reg + GP60, 1);
+ sch311x_wdt_data.runtime_reg = 0;
+ return 0;
+}
+
+static void sch311x_wdt_shutdown(struct platform_device *dev)
+{
+ /* Turn the WDT off if we have a soft shutdown */
+ sch311x_wdt_stop();
+}
+
+static struct platform_driver sch311x_wdt_driver = {
+ .probe = sch311x_wdt_probe,
+ .remove = sch311x_wdt_remove,
+ .shutdown = sch311x_wdt_shutdown,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init sch311x_detect(int sio_config_port, unsigned short *addr)
+{
+ int err = 0, reg;
+ unsigned short base_addr;
+ unsigned char dev_id;
+
+ sch311x_sio_enter(sio_config_port);
+
+ /* Check device ID. We currently know about:
+ * SCH3112 (0x7c), SCH3114 (0x7d), and SCH3116 (0x7f). */
+ reg = force_id ? force_id : sch311x_sio_inb(sio_config_port, 0x20);
+ if (!(reg == 0x7c || reg == 0x7d || reg == 0x7f)) {
+ err = -ENODEV;
+ goto exit;
+ }
+ dev_id = reg == 0x7c ? 2 : reg == 0x7d ? 4 : 6;
+
+ /* Select logical device A (runtime registers) */
+ sch311x_sio_outb(sio_config_port, 0x07, 0x0a);
+
+ /* Check if Logical Device Register is currently active */
+ if ((sch311x_sio_inb(sio_config_port, 0x30) & 0x01) == 0)
+ pr_info("Seems that LDN 0x0a is not active...\n");
+
+ /* Get the base address of the runtime registers */
+ base_addr = (sch311x_sio_inb(sio_config_port, 0x60) << 8) |
+ sch311x_sio_inb(sio_config_port, 0x61);
+ if (!base_addr) {
+ pr_err("Base address not set\n");
+ err = -ENODEV;
+ goto exit;
+ }
+ *addr = base_addr;
+
+ pr_info("Found an SMSC SCH311%d chip at 0x%04x\n", dev_id, base_addr);
+
+exit:
+ sch311x_sio_exit(sio_config_port);
+ return err;
+}
+
+static int __init sch311x_wdt_init(void)
+{
+ int err, i, found = 0;
+ unsigned short addr = 0;
+
+ for (i = 0; !found && sch311x_ioports[i]; i++)
+ if (sch311x_detect(sch311x_ioports[i], &addr) == 0)
+ found++;
+
+ if (!found)
+ return -ENODEV;
+
+ sch311x_wdt_data.runtime_reg = addr;
+
+ err = platform_driver_register(&sch311x_wdt_driver);
+ if (err)
+ return err;
+
+ sch311x_wdt_pdev = platform_device_register_simple(DRV_NAME, addr,
+ NULL, 0);
+
+ if (IS_ERR(sch311x_wdt_pdev)) {
+ err = PTR_ERR(sch311x_wdt_pdev);
+ goto unreg_platform_driver;
+ }
+
+ return 0;
+
+unreg_platform_driver:
+ platform_driver_unregister(&sch311x_wdt_driver);
+ return err;
+}
+
+static void __exit sch311x_wdt_exit(void)
+{
+ platform_device_unregister(sch311x_wdt_pdev);
+ platform_driver_unregister(&sch311x_wdt_driver);
+}
+
+module_init(sch311x_wdt_init);
+module_exit(sch311x_wdt_exit);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("SMSC SCH311x WatchDog Timer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/scx200_wdt.c b/drivers/watchdog/scx200_wdt.c
new file mode 100644
index 000000000..7b5e18323
--- /dev/null
+++ b/drivers/watchdog/scx200_wdt.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* drivers/char/watchdog/scx200_wdt.c
+
+ National Semiconductor SCx200 Watchdog support
+
+ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
+
+ Some code taken from:
+ National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
+
+
+ The author(s) of this software shall not be held liable for damages
+ of any nature resulting due to the use of this software. This
+ software is provided AS-IS with no warranties. */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/scx200.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#define DEBUG
+
+MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
+MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
+MODULE_LICENSE("GPL");
+
+static int margin = 60; /* in seconds */
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static u16 wdto_restart;
+static char expect_close;
+static unsigned long open_lock;
+static DEFINE_SPINLOCK(scx_lock);
+
+/* Bits of the WDCNFG register */
+#define W_ENABLE 0x00fa /* Enable watchdog */
+#define W_DISABLE 0x0000 /* Disable watchdog */
+
+/* The scaling factor for the timer, this depends on the value of W_ENABLE */
+#define W_SCALE (32768/1024)
+
+static void scx200_wdt_ping(void)
+{
+ spin_lock(&scx_lock);
+ outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
+ spin_unlock(&scx_lock);
+}
+
+static void scx200_wdt_update_margin(void)
+{
+ pr_info("timer margin %d seconds\n", margin);
+ wdto_restart = margin * W_SCALE;
+}
+
+static void scx200_wdt_enable(void)
+{
+ pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
+
+ spin_lock(&scx_lock);
+ outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+ outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+ outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+ spin_unlock(&scx_lock);
+
+ scx200_wdt_ping();
+}
+
+static void scx200_wdt_disable(void)
+{
+ pr_debug("disabling watchdog timer\n");
+
+ spin_lock(&scx_lock);
+ outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+ outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+ outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+ spin_unlock(&scx_lock);
+}
+
+static int scx200_wdt_open(struct inode *inode, struct file *file)
+{
+ /* only allow one at a time */
+ if (test_and_set_bit(0, &open_lock))
+ return -EBUSY;
+ scx200_wdt_enable();
+
+ return stream_open(inode, file);
+}
+
+static int scx200_wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close != 42)
+ pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
+ else if (!nowayout)
+ scx200_wdt_disable();
+ expect_close = 0;
+ clear_bit(0, &open_lock);
+
+ return 0;
+}
+
+static int scx200_wdt_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_HALT || code == SYS_POWER_OFF)
+ if (!nowayout)
+ scx200_wdt_disable();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block scx200_wdt_notifier = {
+ .notifier_call = scx200_wdt_notify_sys,
+};
+
+static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* check for a magic close character */
+ if (len) {
+ size_t i;
+
+ scx200_wdt_ping();
+
+ expect_close = 0;
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+
+ return len;
+ }
+
+ return 0;
+}
+
+static long scx200_wdt_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 = {
+ .identity = "NatSemi SCx200 Watchdog",
+ .firmware_version = 1,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ };
+ int new_margin;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, p))
+ return -EFAULT;
+ return 0;
+ case WDIOC_KEEPALIVE:
+ scx200_wdt_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+ if (new_margin < 1)
+ return -EINVAL;
+ margin = new_margin;
+ scx200_wdt_update_margin();
+ scx200_wdt_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ if (put_user(margin, p))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations scx200_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = scx200_wdt_write,
+ .unlocked_ioctl = scx200_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = scx200_wdt_open,
+ .release = scx200_wdt_release,
+};
+
+static struct miscdevice scx200_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &scx200_wdt_fops,
+};
+
+static int __init scx200_wdt_init(void)
+{
+ int r;
+
+ pr_debug("NatSemi SCx200 Watchdog Driver\n");
+
+ /* check that we have found the configuration block */
+ if (!scx200_cb_present())
+ return -ENODEV;
+
+ if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE,
+ "NatSemi SCx200 Watchdog")) {
+ pr_warn("watchdog I/O region busy\n");
+ return -EBUSY;
+ }
+
+ scx200_wdt_update_margin();
+ scx200_wdt_disable();
+
+ r = register_reboot_notifier(&scx200_wdt_notifier);
+ if (r) {
+ pr_err("unable to register reboot notifier\n");
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+ return r;
+ }
+
+ r = misc_register(&scx200_wdt_miscdev);
+ if (r) {
+ unregister_reboot_notifier(&scx200_wdt_notifier);
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+ return r;
+ }
+
+ return 0;
+}
+
+static void __exit scx200_wdt_cleanup(void)
+{
+ misc_deregister(&scx200_wdt_miscdev);
+ unregister_reboot_notifier(&scx200_wdt_notifier);
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+}
+
+module_init(scx200_wdt_init);
+module_exit(scx200_wdt_cleanup);
diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c
new file mode 100644
index 000000000..f55533e0e
--- /dev/null
+++ b/drivers/watchdog/shwdt.c
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * drivers/watchdog/shwdt.c
+ *
+ * Watchdog driver for integrated watchdog in the SuperH processors.
+ *
+ * Copyright (C) 2001 - 2012 Paul Mundt <lethal@linux-sh.org>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * 19-Apr-2002 Rob Radez <rob@osinvestor.com>
+ * Added expect close support, made emulated timeout runtime changeable
+ * general cleanups, add some ioctls
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/watchdog.h>
+#include <linux/pm_runtime.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <asm/watchdog.h>
+
+#define DRV_NAME "sh-wdt"
+
+/*
+ * Default clock division ratio is 5.25 msecs. For an additional table of
+ * values, consult the asm-sh/watchdog.h. Overload this at module load
+ * time.
+ *
+ * In order for this to work reliably we need to have HZ set to 1000 or
+ * something quite higher than 100 (or we need a proper high-res timer
+ * implementation that will deal with this properly), otherwise the 10ms
+ * resolution of a jiffy is enough to trigger the overflow. For things like
+ * the SH-4 and SH-5, this isn't necessarily that big of a problem, though
+ * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely
+ * necssary.
+ *
+ * As a result of this timing problem, the only modes that are particularly
+ * feasible are the 4096 and the 2048 divisors, which yield 5.25 and 2.62ms
+ * overflow periods respectively.
+ *
+ * Also, since we can't really expect userspace to be responsive enough
+ * before the overflow happens, we maintain two separate timers .. One in
+ * the kernel for clearing out WOVF every 2ms or so (again, this depends on
+ * HZ == 1000), and another for monitoring userspace writes to the WDT device.
+ *
+ * As such, we currently use a configurable heartbeat interval which defaults
+ * to 30s. In this case, the userspace daemon is only responsible for periodic
+ * writes to the device before the next heartbeat is scheduled. If the daemon
+ * misses its deadline, the kernel timer will allow the WDT to overflow.
+ */
+static int clock_division_ratio = WTCSR_CKS_4096;
+#define next_ping_period(cks) (jiffies + msecs_to_jiffies(cks - 4))
+
+#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned long next_heartbeat;
+
+struct sh_wdt {
+ void __iomem *base;
+ struct device *dev;
+ struct clk *clk;
+ spinlock_t lock;
+
+ struct timer_list timer;
+};
+
+static int sh_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+ u8 csr;
+
+ pm_runtime_get_sync(wdt->dev);
+ clk_enable(wdt->clk);
+
+ spin_lock_irqsave(&wdt->lock, flags);
+
+ next_heartbeat = jiffies + (heartbeat * HZ);
+ mod_timer(&wdt->timer, next_ping_period(clock_division_ratio));
+
+ csr = sh_wdt_read_csr();
+ csr |= WTCSR_WT | clock_division_ratio;
+ sh_wdt_write_csr(csr);
+
+ sh_wdt_write_cnt(0);
+
+ /*
+ * These processors have a bit of an inconsistent initialization
+ * process.. starting with SH-3, RSTS was moved to WTCSR, and the
+ * RSTCSR register was removed.
+ *
+ * On the SH-2 however, in addition with bits being in different
+ * locations, we must deal with RSTCSR outright..
+ */
+ csr = sh_wdt_read_csr();
+ csr |= WTCSR_TME;
+ csr &= ~WTCSR_RSTS;
+ sh_wdt_write_csr(csr);
+
+#ifdef CONFIG_CPU_SH2
+ csr = sh_wdt_read_rstcsr();
+ csr &= ~RSTCSR_RSTS;
+ sh_wdt_write_rstcsr(csr);
+#endif
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ return 0;
+}
+
+static int sh_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+ u8 csr;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+
+ del_timer(&wdt->timer);
+
+ csr = sh_wdt_read_csr();
+ csr &= ~WTCSR_TME;
+ sh_wdt_write_csr(csr);
+
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ clk_disable(wdt->clk);
+ pm_runtime_put_sync(wdt->dev);
+
+ return 0;
+}
+
+static int sh_wdt_keepalive(struct watchdog_device *wdt_dev)
+{
+ struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+ next_heartbeat = jiffies + (heartbeat * HZ);
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ return 0;
+}
+
+static int sh_wdt_set_heartbeat(struct watchdog_device *wdt_dev, unsigned t)
+{
+ struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev);
+ unsigned long flags;
+
+ if (unlikely(t < 1 || t > 3600)) /* arbitrary upper limit */
+ return -EINVAL;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+ heartbeat = t;
+ wdt_dev->timeout = t;
+ spin_unlock_irqrestore(&wdt->lock, flags);
+
+ return 0;
+}
+
+static void sh_wdt_ping(struct timer_list *t)
+{
+ struct sh_wdt *wdt = from_timer(wdt, t, timer);
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt->lock, flags);
+ if (time_before(jiffies, next_heartbeat)) {
+ u8 csr;
+
+ csr = sh_wdt_read_csr();
+ csr &= ~WTCSR_IOVF;
+ sh_wdt_write_csr(csr);
+
+ sh_wdt_write_cnt(0);
+
+ mod_timer(&wdt->timer, next_ping_period(clock_division_ratio));
+ } else
+ dev_warn(wdt->dev, "Heartbeat lost! Will not ping "
+ "the watchdog\n");
+ spin_unlock_irqrestore(&wdt->lock, flags);
+}
+
+static const struct watchdog_info sh_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "SH WDT",
+};
+
+static const struct watchdog_ops sh_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sh_wdt_start,
+ .stop = sh_wdt_stop,
+ .ping = sh_wdt_keepalive,
+ .set_timeout = sh_wdt_set_heartbeat,
+};
+
+static struct watchdog_device sh_wdt_dev = {
+ .info = &sh_wdt_info,
+ .ops = &sh_wdt_ops,
+};
+
+static int sh_wdt_probe(struct platform_device *pdev)
+{
+ struct sh_wdt *wdt;
+ int rc;
+
+ /*
+ * As this driver only covers the global watchdog case, reject
+ * any attempts to register per-CPU watchdogs.
+ */
+ if (pdev->id != -1)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(struct sh_wdt), GFP_KERNEL);
+ if (unlikely(!wdt))
+ return -ENOMEM;
+
+ wdt->dev = &pdev->dev;
+
+ wdt->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(wdt->clk)) {
+ /*
+ * Clock framework support is optional, continue on
+ * anyways if we don't find a matching clock.
+ */
+ wdt->clk = NULL;
+ }
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ watchdog_set_nowayout(&sh_wdt_dev, nowayout);
+ watchdog_set_drvdata(&sh_wdt_dev, wdt);
+ sh_wdt_dev.parent = &pdev->dev;
+
+ spin_lock_init(&wdt->lock);
+
+ rc = sh_wdt_set_heartbeat(&sh_wdt_dev, heartbeat);
+ if (unlikely(rc)) {
+ /* Default timeout if invalid */
+ sh_wdt_set_heartbeat(&sh_wdt_dev, WATCHDOG_HEARTBEAT);
+
+ dev_warn(&pdev->dev,
+ "heartbeat value must be 1<=x<=3600, using %d\n",
+ sh_wdt_dev.timeout);
+ }
+
+ dev_info(&pdev->dev, "configured with heartbeat=%d sec (nowayout=%d)\n",
+ sh_wdt_dev.timeout, nowayout);
+
+ rc = watchdog_register_device(&sh_wdt_dev);
+ if (unlikely(rc)) {
+ dev_err(&pdev->dev, "Can't register watchdog (err=%d)\n", rc);
+ return rc;
+ }
+
+ timer_setup(&wdt->timer, sh_wdt_ping, 0);
+ wdt->timer.expires = next_ping_period(clock_division_ratio);
+
+ dev_info(&pdev->dev, "initialized.\n");
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int sh_wdt_remove(struct platform_device *pdev)
+{
+ watchdog_unregister_device(&sh_wdt_dev);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static void sh_wdt_shutdown(struct platform_device *pdev)
+{
+ sh_wdt_stop(&sh_wdt_dev);
+}
+
+static struct platform_driver sh_wdt_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ },
+
+ .probe = sh_wdt_probe,
+ .remove = sh_wdt_remove,
+ .shutdown = sh_wdt_shutdown,
+};
+
+static int __init sh_wdt_init(void)
+{
+ if (unlikely(clock_division_ratio < 0x5 ||
+ clock_division_ratio > 0x7)) {
+ clock_division_ratio = WTCSR_CKS_4096;
+
+ pr_info("divisor must be 0x5<=x<=0x7, using %d\n",
+ clock_division_ratio);
+ }
+
+ return platform_driver_register(&sh_wdt_driver);
+}
+
+static void __exit sh_wdt_exit(void)
+{
+ platform_driver_unregister(&sh_wdt_driver);
+}
+module_init(sh_wdt_init);
+module_exit(sh_wdt_exit);
+
+MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
+MODULE_DESCRIPTION("SuperH watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
+
+module_param(clock_division_ratio, int, 0);
+MODULE_PARM_DESC(clock_division_ratio,
+ "Clock division ratio. Valid ranges are from 0x5 (1.31ms) "
+ "to 0x7 (5.25ms). (default=" __MODULE_STRING(WTCSR_CKS_4096) ")");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat in seconds. (1 <= heartbeat <= 3600, default="
+ __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
diff --git a/drivers/watchdog/simatic-ipc-wdt.c b/drivers/watchdog/simatic-ipc-wdt.c
new file mode 100644
index 000000000..6599695dc
--- /dev/null
+++ b/drivers/watchdog/simatic-ipc-wdt.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for Watchdogs
+ *
+ * Copyright (c) Siemens AG, 2020-2021
+ *
+ * Authors:
+ * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/x86/p2sb.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/util_macros.h>
+#include <linux/watchdog.h>
+
+#define WD_ENABLE_IOADR 0x62
+#define WD_TRIGGER_IOADR 0x66
+#define GPIO_COMMUNITY0_PORT_ID 0xaf
+#define PAD_CFG_DW0_GPP_A_23 0x4b8
+#define SAFE_EN_N_427E 0x01
+#define SAFE_EN_N_227E 0x04
+#define WD_ENABLED 0x01
+#define WD_TRIGGERED 0x80
+#define WD_MACROMODE 0x02
+
+#define TIMEOUT_MIN 2
+#define TIMEOUT_DEF 64
+#define TIMEOUT_MAX 64
+
+#define GP_STATUS_REG_227E 0x404D /* IO PORT for SAFE_EN_N on 227E */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0000);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static struct resource gp_status_reg_227e_res =
+ DEFINE_RES_IO_NAMED(GP_STATUS_REG_227E, SZ_1, KBUILD_MODNAME);
+
+static struct resource io_resource_enable =
+ DEFINE_RES_IO_NAMED(WD_ENABLE_IOADR, SZ_1,
+ KBUILD_MODNAME " WD_ENABLE_IOADR");
+
+static struct resource io_resource_trigger =
+ DEFINE_RES_IO_NAMED(WD_TRIGGER_IOADR, SZ_1,
+ KBUILD_MODNAME " WD_TRIGGER_IOADR");
+
+/* the actual start will be discovered with p2sb, 0 is a placeholder */
+static struct resource mem_resource =
+ DEFINE_RES_MEM_NAMED(0, 0, "WD_RESET_BASE_ADR");
+
+static u32 wd_timeout_table[] = {2, 4, 6, 8, 16, 32, 48, 64 };
+static void __iomem *wd_reset_base_addr;
+
+static int wd_start(struct watchdog_device *wdd)
+{
+ outb(inb(WD_ENABLE_IOADR) | WD_ENABLED, WD_ENABLE_IOADR);
+ return 0;
+}
+
+static int wd_stop(struct watchdog_device *wdd)
+{
+ outb(inb(WD_ENABLE_IOADR) & ~WD_ENABLED, WD_ENABLE_IOADR);
+ return 0;
+}
+
+static int wd_ping(struct watchdog_device *wdd)
+{
+ inb(WD_TRIGGER_IOADR);
+ return 0;
+}
+
+static int wd_set_timeout(struct watchdog_device *wdd, unsigned int t)
+{
+ int timeout_idx = find_closest(t, wd_timeout_table,
+ ARRAY_SIZE(wd_timeout_table));
+
+ outb((inb(WD_ENABLE_IOADR) & 0xc7) | timeout_idx << 3, WD_ENABLE_IOADR);
+ wdd->timeout = wd_timeout_table[timeout_idx];
+ return 0;
+}
+
+static const struct watchdog_info wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+ .identity = KBUILD_MODNAME,
+};
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wd_start,
+ .stop = wd_stop,
+ .ping = wd_ping,
+ .set_timeout = wd_set_timeout,
+};
+
+static void wd_secondary_enable(u32 wdtmode)
+{
+ u16 resetbit;
+
+ /* set safe_en_n so we are not just WDIOF_ALARMONLY */
+ if (wdtmode == SIMATIC_IPC_DEVICE_227E) {
+ /* enable SAFE_EN_N on GP_STATUS_REG_227E */
+ resetbit = inb(GP_STATUS_REG_227E);
+ outb(resetbit & ~SAFE_EN_N_227E, GP_STATUS_REG_227E);
+ } else {
+ /* enable SAFE_EN_N on PCH D1600 */
+ resetbit = ioread16(wd_reset_base_addr);
+ iowrite16(resetbit & ~SAFE_EN_N_427E, wd_reset_base_addr);
+ }
+}
+
+static int wd_setup(u32 wdtmode)
+{
+ unsigned int bootstatus = 0;
+ int timeout_idx;
+
+ timeout_idx = find_closest(TIMEOUT_DEF, wd_timeout_table,
+ ARRAY_SIZE(wd_timeout_table));
+
+ if (inb(WD_ENABLE_IOADR) & WD_TRIGGERED)
+ bootstatus |= WDIOF_CARDRESET;
+
+ /* reset alarm bit, set macro mode, and set timeout */
+ outb(WD_TRIGGERED | WD_MACROMODE | timeout_idx << 3, WD_ENABLE_IOADR);
+
+ wd_secondary_enable(wdtmode);
+
+ return bootstatus;
+}
+
+static struct watchdog_device wdd_data = {
+ .info = &wdt_ident,
+ .ops = &wdt_ops,
+ .min_timeout = TIMEOUT_MIN,
+ .max_timeout = TIMEOUT_MAX
+};
+
+static int simatic_ipc_wdt_probe(struct platform_device *pdev)
+{
+ struct simatic_ipc_platform *plat = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ int ret;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_227E:
+ if (!devm_request_region(dev, gp_status_reg_227e_res.start,
+ resource_size(&gp_status_reg_227e_res),
+ KBUILD_MODNAME)) {
+ dev_err(dev,
+ "Unable to register IO resource at %pR\n",
+ &gp_status_reg_227e_res);
+ return -EBUSY;
+ }
+ fallthrough;
+ case SIMATIC_IPC_DEVICE_427E:
+ wdd_data.parent = dev;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!devm_request_region(dev, io_resource_enable.start,
+ resource_size(&io_resource_enable),
+ io_resource_enable.name)) {
+ dev_err(dev,
+ "Unable to register IO resource at %#x\n",
+ WD_ENABLE_IOADR);
+ return -EBUSY;
+ }
+
+ if (!devm_request_region(dev, io_resource_trigger.start,
+ resource_size(&io_resource_trigger),
+ io_resource_trigger.name)) {
+ dev_err(dev,
+ "Unable to register IO resource at %#x\n",
+ WD_TRIGGER_IOADR);
+ return -EBUSY;
+ }
+
+ if (plat->devmode == SIMATIC_IPC_DEVICE_427E) {
+ res = &mem_resource;
+
+ ret = p2sb_bar(NULL, 0, res);
+ if (ret)
+ return ret;
+
+ /* do the final address calculation */
+ res->start = res->start + (GPIO_COMMUNITY0_PORT_ID << 16) +
+ PAD_CFG_DW0_GPP_A_23;
+ res->end = res->start + SZ_4 - 1;
+
+ wd_reset_base_addr = devm_ioremap_resource(dev, res);
+ if (IS_ERR(wd_reset_base_addr))
+ return PTR_ERR(wd_reset_base_addr);
+ }
+
+ wdd_data.bootstatus = wd_setup(plat->devmode);
+ if (wdd_data.bootstatus)
+ dev_warn(dev, "last reboot caused by watchdog reset\n");
+
+ watchdog_set_nowayout(&wdd_data, nowayout);
+ watchdog_stop_on_reboot(&wdd_data);
+ return devm_watchdog_register_device(dev, &wdd_data);
+}
+
+static struct platform_driver simatic_ipc_wdt_driver = {
+ .probe = simatic_ipc_wdt_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+module_platform_driver(simatic_ipc_wdt_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_AUTHOR("Gerd Haeussler <gerd.haeussler.ext@siemens.com>");
diff --git a/drivers/watchdog/sl28cpld_wdt.c b/drivers/watchdog/sl28cpld_wdt.c
new file mode 100644
index 000000000..9ce456f09
--- /dev/null
+++ b/drivers/watchdog/sl28cpld_wdt.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sl28cpld watchdog driver
+ *
+ * Copyright 2020 Kontron Europe GmbH
+ */
+
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+/*
+ * Watchdog timer block registers.
+ */
+#define WDT_CTRL 0x00
+#define WDT_CTRL_EN BIT(0)
+#define WDT_CTRL_LOCK BIT(2)
+#define WDT_CTRL_ASSERT_SYS_RESET BIT(6)
+#define WDT_CTRL_ASSERT_WDT_TIMEOUT BIT(7)
+#define WDT_TIMEOUT 0x01
+#define WDT_KICK 0x02
+#define WDT_KICK_VALUE 0x6b
+#define WDT_COUNT 0x03
+
+#define WDT_DEFAULT_TIMEOUT 10
+
+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) ")");
+
+static int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds");
+
+struct sl28cpld_wdt {
+ struct watchdog_device wdd;
+ struct regmap *regmap;
+ u32 offset;
+ bool assert_wdt_timeout;
+};
+
+static int sl28cpld_wdt_ping(struct watchdog_device *wdd)
+{
+ struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_write(wdt->regmap, wdt->offset + WDT_KICK,
+ WDT_KICK_VALUE);
+}
+
+static int sl28cpld_wdt_start(struct watchdog_device *wdd)
+{
+ struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
+ unsigned int val;
+
+ val = WDT_CTRL_EN | WDT_CTRL_ASSERT_SYS_RESET;
+ if (wdt->assert_wdt_timeout)
+ val |= WDT_CTRL_ASSERT_WDT_TIMEOUT;
+ if (nowayout)
+ val |= WDT_CTRL_LOCK;
+
+ return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
+ val, val);
+}
+
+static int sl28cpld_wdt_stop(struct watchdog_device *wdd)
+{
+ struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL,
+ WDT_CTRL_EN, 0);
+}
+
+static unsigned int sl28cpld_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(wdt->regmap, wdt->offset + WDT_COUNT, &val);
+ if (ret)
+ return 0;
+
+ return val;
+}
+
+static int sl28cpld_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = regmap_write(wdt->regmap, wdt->offset + WDT_TIMEOUT, timeout);
+ if (ret)
+ return ret;
+
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info sl28cpld_wdt_info = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "sl28cpld watchdog",
+};
+
+static const struct watchdog_ops sl28cpld_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sl28cpld_wdt_start,
+ .stop = sl28cpld_wdt_stop,
+ .ping = sl28cpld_wdt_ping,
+ .set_timeout = sl28cpld_wdt_set_timeout,
+ .get_timeleft = sl28cpld_wdt_get_timeleft,
+};
+
+static int sl28cpld_wdt_probe(struct platform_device *pdev)
+{
+ struct watchdog_device *wdd;
+ struct sl28cpld_wdt *wdt;
+ unsigned int status;
+ unsigned int val;
+ int ret;
+
+ if (!pdev->dev.parent)
+ return -ENODEV;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!wdt->regmap)
+ return -ENODEV;
+
+ ret = device_property_read_u32(&pdev->dev, "reg", &wdt->offset);
+ if (ret)
+ return -EINVAL;
+
+ wdt->assert_wdt_timeout = device_property_read_bool(&pdev->dev,
+ "kontron,assert-wdt-timeout-pin");
+
+ /* initialize struct watchdog_device */
+ wdd = &wdt->wdd;
+ wdd->parent = &pdev->dev;
+ wdd->info = &sl28cpld_wdt_info;
+ wdd->ops = &sl28cpld_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = 255;
+
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_stop_on_reboot(wdd);
+
+ /*
+ * Read the status early, in case of an error, we haven't modified the
+ * hardware.
+ */
+ ret = regmap_read(wdt->regmap, wdt->offset + WDT_CTRL, &status);
+ if (ret)
+ return ret;
+
+ /*
+ * Initial timeout value, may be overwritten by device tree or module
+ * parameter in watchdog_init_timeout().
+ *
+ * Reading a zero here means that either the hardware has a default
+ * value of zero (which is very unlikely and definitely a hardware
+ * bug) or the bootloader set it to zero. In any case, we handle
+ * this case gracefully and set out own timeout.
+ */
+ ret = regmap_read(wdt->regmap, wdt->offset + WDT_TIMEOUT, &val);
+ if (ret)
+ return ret;
+
+ if (val)
+ wdd->timeout = val;
+ else
+ wdd->timeout = WDT_DEFAULT_TIMEOUT;
+
+ watchdog_init_timeout(wdd, timeout, &pdev->dev);
+ sl28cpld_wdt_set_timeout(wdd, wdd->timeout);
+
+ /* if the watchdog is locked, we set nowayout */
+ if (status & WDT_CTRL_LOCK)
+ nowayout = true;
+ watchdog_set_nowayout(wdd, nowayout);
+
+ /*
+ * If watchdog is already running, keep it enabled, but make
+ * sure its mode is set correctly.
+ */
+ if (status & WDT_CTRL_EN) {
+ sl28cpld_wdt_start(wdd);
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ ret = devm_watchdog_register_device(&pdev->dev, wdd);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register watchdog device\n");
+ return ret;
+ }
+
+ dev_info(&pdev->dev, "initial timeout %d sec%s\n",
+ wdd->timeout, nowayout ? ", nowayout" : "");
+
+ return 0;
+}
+
+static const struct of_device_id sl28cpld_wdt_of_match[] = {
+ { .compatible = "kontron,sl28cpld-wdt" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sl28cpld_wdt_of_match);
+
+static struct platform_driver sl28cpld_wdt_driver = {
+ .probe = sl28cpld_wdt_probe,
+ .driver = {
+ .name = "sl28cpld-wdt",
+ .of_match_table = sl28cpld_wdt_of_match,
+ },
+};
+module_platform_driver(sl28cpld_wdt_driver);
+
+MODULE_DESCRIPTION("sl28cpld Watchdog Driver");
+MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c
new file mode 100644
index 000000000..7463df479
--- /dev/null
+++ b/drivers/watchdog/smsc37b787_wdt.c
@@ -0,0 +1,615 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x
+ *
+ * Based on acquirewdt.c by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ * and some other existing drivers
+ *
+ * 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 2003-2006 Sven Anders <anders@anduras.de>
+ *
+ * History:
+ * 2003 - Created version 1.0 for Linux 2.4.x.
+ * 2006 - Ported to Linux 2.6, added nowayout and MAGICCLOSE
+ * features. Released version 1.1
+ *
+ * Theory of operation:
+ *
+ * A Watchdog Timer (WDT) is a hardware circuit that can
+ * reset the computer system in case of a software fault.
+ * You probably knew that already.
+ *
+ * Usually a userspace daemon will notify the kernel WDT driver
+ * via the /dev/watchdog special device file that userspace is
+ * still alive, at regular intervals. When such a notification
+ * occurs, the driver will usually tell the hardware watchdog
+ * that everything is in order, and that the watchdog should wait
+ * for yet another little while to reset the system.
+ * If userspace fails (RAM error, kernel bug, whatever), the
+ * notifications cease to occur, and the hardware watchdog will
+ * reset the system (causing a reboot) after the timeout occurs.
+ *
+ * Create device with:
+ * mknod /dev/watchdog c 10 130
+ *
+ * For an example userspace keep-alive daemon, see:
+ * Documentation/watchdog/wdt.rst
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+/* enable support for minutes as units? */
+/* (does not always work correctly, so disabled by default!) */
+#define SMSC_SUPPORT_MINUTES
+#undef SMSC_SUPPORT_MINUTES
+
+#define MAX_TIMEOUT 255
+
+#define UNIT_SECOND 0
+#define UNIT_MINUTE 1
+
+#define VERSION "1.1"
+
+#define IOPORT 0x3F0
+#define IOPORT_SIZE 2
+#define IODEV_NO 8
+
+static int unit = UNIT_SECOND; /* timer's unit */
+static int timeout = 60; /* timeout value: default is 60 "units" */
+static unsigned long timer_enabled; /* is the timer enabled? */
+
+static char expect_close; /* is the close expected? */
+
+static DEFINE_SPINLOCK(io_lock);/* to guard the watchdog from io races */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+/* -- Low level function ----------------------------------------*/
+
+/* unlock the IO chip */
+
+static inline void open_io_config(void)
+{
+ outb(0x55, IOPORT);
+ mdelay(1);
+ outb(0x55, IOPORT);
+}
+
+/* lock the IO chip */
+static inline void close_io_config(void)
+{
+ outb(0xAA, IOPORT);
+}
+
+/* select the IO device */
+static inline void select_io_device(unsigned char devno)
+{
+ outb(0x07, IOPORT);
+ outb(devno, IOPORT+1);
+}
+
+/* write to the control register */
+static inline void write_io_cr(unsigned char reg, unsigned char data)
+{
+ outb(reg, IOPORT);
+ outb(data, IOPORT+1);
+}
+
+/* read from the control register */
+static inline char read_io_cr(unsigned char reg)
+{
+ outb(reg, IOPORT);
+ return inb(IOPORT+1);
+}
+
+/* -- Medium level functions ------------------------------------*/
+
+static inline void gpio_bit12(unsigned char reg)
+{
+ /* -- General Purpose I/O Bit 1.2 --
+ * Bit 0, In/Out: 0 = Output, 1 = Input
+ * Bit 1, Polarity: 0 = No Invert, 1 = Invert
+ * Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable
+ * Bit 3/4, Function select: 00 = GPI/O, 01 = WDT, 10 = P17,
+ * 11 = Either Edge Triggered Intr. 2
+ * Bit 5/6 (Reserved)
+ * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain
+ */
+ write_io_cr(0xE2, reg);
+}
+
+static inline void gpio_bit13(unsigned char reg)
+{
+ /* -- General Purpose I/O Bit 1.3 --
+ * Bit 0, In/Out: 0 = Output, 1 = Input
+ * Bit 1, Polarity: 0 = No Invert, 1 = Invert
+ * Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable
+ * Bit 3, Function select: 0 = GPI/O, 1 = LED
+ * Bit 4-6 (Reserved)
+ * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain
+ */
+ write_io_cr(0xE3, reg);
+}
+
+static inline void wdt_timer_units(unsigned char new_units)
+{
+ /* -- Watchdog timer units --
+ * Bit 0-6 (Reserved)
+ * Bit 7, WDT Time-out Value Units Select
+ * (0 = Minutes, 1 = Seconds)
+ */
+ write_io_cr(0xF1, new_units);
+}
+
+static inline void wdt_timeout_value(unsigned char new_timeout)
+{
+ /* -- Watchdog Timer Time-out Value --
+ * Bit 0-7 Binary coded units (0=Disabled, 1..255)
+ */
+ write_io_cr(0xF2, new_timeout);
+}
+
+static inline void wdt_timer_conf(unsigned char conf)
+{
+ /* -- Watchdog timer configuration --
+ * Bit 0 Joystick enable: 0* = No Reset, 1 = Reset WDT upon
+ * Gameport I/O
+ * Bit 1 Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr.
+ * Bit 2 Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr
+ * Bit 3 Reset the timer
+ * (Wrong in SMsC documentation? Given as: PowerLED Timout
+ * Enabled)
+ * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled,
+ * 0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15)
+ */
+ write_io_cr(0xF3, conf);
+}
+
+static inline void wdt_timer_ctrl(unsigned char reg)
+{
+ /* -- Watchdog timer control --
+ * Bit 0 Status Bit: 0 = Timer counting, 1 = Timeout occurred
+ * Bit 1 Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz
+ * Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning)
+ * Bit 3 P20 Force Timeout enabled:
+ * 0 = P20 activity does not generate the WD timeout event
+ * 1 = P20 Allows rising edge of P20, from the keyboard
+ * controller, to force the WD timeout event.
+ * Bit 4 (Reserved)
+ * -- Soft power management --
+ * Bit 5 Stop Counter: 1 = Stop software power down counter
+ * set via register 0xB8, (self-cleaning)
+ * (Upon read: 0 = Counter running, 1 = Counter stopped)
+ * Bit 6 Restart Counter: 1 = Restart software power down counter
+ * set via register 0xB8, (self-cleaning)
+ * Bit 7 SPOFF: 1 = Force software power down (self-cleaning)
+ */
+ write_io_cr(0xF4, reg);
+}
+
+/* -- Higher level functions ------------------------------------*/
+
+/* initialize watchdog */
+
+static void wb_smsc_wdt_initialize(void)
+{
+ unsigned char old;
+
+ spin_lock(&io_lock);
+ open_io_config();
+ select_io_device(IODEV_NO);
+
+ /* enable the watchdog */
+ gpio_bit13(0x08); /* Select pin 80 = LED not GPIO */
+ gpio_bit12(0x0A); /* Set pin 79 = WDT not
+ GPIO/Output/Polarity=Invert */
+ /* disable the timeout */
+ wdt_timeout_value(0);
+
+ /* reset control register */
+ wdt_timer_ctrl(0x00);
+
+ /* reset configuration register */
+ wdt_timer_conf(0x00);
+
+ /* read old (timer units) register */
+ old = read_io_cr(0xF1) & 0x7F;
+ if (unit == UNIT_SECOND)
+ old |= 0x80; /* set to seconds */
+
+ /* set the watchdog timer units */
+ wdt_timer_units(old);
+
+ close_io_config();
+ spin_unlock(&io_lock);
+}
+
+/* shutdown the watchdog */
+
+static void wb_smsc_wdt_shutdown(void)
+{
+ spin_lock(&io_lock);
+ open_io_config();
+ select_io_device(IODEV_NO);
+
+ /* disable the watchdog */
+ gpio_bit13(0x09);
+ gpio_bit12(0x09);
+
+ /* reset watchdog config register */
+ wdt_timer_conf(0x00);
+
+ /* reset watchdog control register */
+ wdt_timer_ctrl(0x00);
+
+ /* disable timeout */
+ wdt_timeout_value(0x00);
+
+ close_io_config();
+ spin_unlock(&io_lock);
+}
+
+/* set timeout => enable watchdog */
+
+static void wb_smsc_wdt_set_timeout(unsigned char new_timeout)
+{
+ spin_lock(&io_lock);
+ open_io_config();
+ select_io_device(IODEV_NO);
+
+ /* set Power LED to blink, if we enable the timeout */
+ wdt_timer_ctrl((new_timeout == 0) ? 0x00 : 0x02);
+
+ /* set timeout value */
+ wdt_timeout_value(new_timeout);
+
+ close_io_config();
+ spin_unlock(&io_lock);
+}
+
+/* get timeout */
+
+static unsigned char wb_smsc_wdt_get_timeout(void)
+{
+ unsigned char set_timeout;
+
+ spin_lock(&io_lock);
+ open_io_config();
+ select_io_device(IODEV_NO);
+ set_timeout = read_io_cr(0xF2);
+ close_io_config();
+ spin_unlock(&io_lock);
+
+ return set_timeout;
+}
+
+/* disable watchdog */
+
+static void wb_smsc_wdt_disable(void)
+{
+ /* set the timeout to 0 to disable the watchdog */
+ wb_smsc_wdt_set_timeout(0);
+}
+
+/* enable watchdog by setting the current timeout */
+
+static void wb_smsc_wdt_enable(void)
+{
+ /* set the current timeout... */
+ wb_smsc_wdt_set_timeout(timeout);
+}
+
+/* reset the timer */
+
+static void wb_smsc_wdt_reset_timer(void)
+{
+ spin_lock(&io_lock);
+ open_io_config();
+ select_io_device(IODEV_NO);
+
+ /* reset the timer */
+ wdt_timeout_value(timeout);
+ wdt_timer_conf(0x08);
+
+ close_io_config();
+ spin_unlock(&io_lock);
+}
+
+/* return, if the watchdog is enabled (timeout is set...) */
+
+static int wb_smsc_wdt_status(void)
+{
+ return (wb_smsc_wdt_get_timeout() == 0) ? 0 : WDIOF_KEEPALIVEPING;
+}
+
+
+/* -- File operations -------------------------------------------*/
+
+/* open => enable watchdog and set initial timeout */
+
+static int wb_smsc_wdt_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+
+ if (test_and_set_bit(0, &timer_enabled))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Reload and activate timer */
+ wb_smsc_wdt_enable();
+
+ pr_info("Watchdog enabled. Timeout set to %d %s\n",
+ timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");
+
+ return stream_open(inode, file);
+}
+
+/* close => shut off the timer */
+
+static int wb_smsc_wdt_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer. */
+
+ if (expect_close == 42) {
+ wb_smsc_wdt_disable();
+ pr_info("Watchdog disabled, sleeping again...\n");
+ } else {
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ wb_smsc_wdt_reset_timer();
+ }
+
+ clear_bit(0, &timer_enabled);
+ expect_close = 0;
+ return 0;
+}
+
+/* write => update the timer to keep the machine alive */
+
+static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* reset expect flag */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the
+ magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ wb_smsc_wdt_reset_timer();
+ }
+ return len;
+}
+
+/* ioctl => control interface */
+
+static long wb_smsc_wdt_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int new_timeout;
+
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ static const struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "SMsC 37B787 Watchdog",
+ };
+
+ uarg.i = (int __user *)arg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident, sizeof(ident))
+ ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ return put_user(wb_smsc_wdt_status(), uarg.i);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, uarg.i);
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, uarg.i))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ wb_smsc_wdt_disable();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ wb_smsc_wdt_enable();
+ retval = 0;
+ }
+ return retval;
+ }
+ case WDIOC_KEEPALIVE:
+ wb_smsc_wdt_reset_timer();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, uarg.i))
+ return -EFAULT;
+ /* the API states this is given in secs */
+ if (unit == UNIT_MINUTE)
+ new_timeout /= 60;
+ if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+ return -EINVAL;
+ timeout = new_timeout;
+ wb_smsc_wdt_set_timeout(timeout);
+ fallthrough; /* and return the new timeout */
+ case WDIOC_GETTIMEOUT:
+ new_timeout = timeout;
+ if (unit == UNIT_MINUTE)
+ new_timeout *= 60;
+ return put_user(new_timeout, uarg.i);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/* -- Notifier funtions -----------------------------------------*/
+
+static int wb_smsc_wdt_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* set timeout to 0, to avoid possible race-condition */
+ timeout = 0;
+ wb_smsc_wdt_disable();
+ }
+ return NOTIFY_DONE;
+}
+
+/* -- Module's structures ---------------------------------------*/
+
+static const struct file_operations wb_smsc_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wb_smsc_wdt_write,
+ .unlocked_ioctl = wb_smsc_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wb_smsc_wdt_open,
+ .release = wb_smsc_wdt_release,
+};
+
+static struct notifier_block wb_smsc_wdt_notifier = {
+ .notifier_call = wb_smsc_wdt_notify_sys,
+};
+
+static struct miscdevice wb_smsc_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wb_smsc_wdt_fops,
+};
+
+/* -- Module init functions -------------------------------------*/
+
+/* module's "constructor" */
+
+static int __init wb_smsc_wdt_init(void)
+{
+ int ret;
+
+ pr_info("SMsC 37B787 watchdog component driver "
+ VERSION " initialising...\n");
+
+ if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) {
+ pr_err("Unable to register IO port %#x\n", IOPORT);
+ ret = -EBUSY;
+ goto out_pnp;
+ }
+
+ /* set new maximum, if it's too big */
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+
+ /* init the watchdog timer */
+ wb_smsc_wdt_initialize();
+
+ ret = register_reboot_notifier(&wb_smsc_wdt_notifier);
+ if (ret) {
+ pr_err("Unable to register reboot notifier err = %d\n", ret);
+ goto out_io;
+ }
+
+ ret = misc_register(&wb_smsc_wdt_miscdev);
+ if (ret) {
+ pr_err("Unable to register miscdev on minor %d\n",
+ WATCHDOG_MINOR);
+ goto out_rbt;
+ }
+
+ /* output info */
+ pr_info("Timeout set to %d %s\n",
+ timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");
+ pr_info("Watchdog initialized and sleeping (nowayout=%d)...\n",
+ nowayout);
+out_clean:
+ return ret;
+
+out_rbt:
+ unregister_reboot_notifier(&wb_smsc_wdt_notifier);
+
+out_io:
+ release_region(IOPORT, IOPORT_SIZE);
+
+out_pnp:
+ goto out_clean;
+}
+
+/* module's "destructor" */
+
+static void __exit wb_smsc_wdt_exit(void)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout) {
+ wb_smsc_wdt_shutdown();
+ pr_info("Watchdog disabled\n");
+ }
+
+ misc_deregister(&wb_smsc_wdt_miscdev);
+ unregister_reboot_notifier(&wb_smsc_wdt_notifier);
+ release_region(IOPORT, IOPORT_SIZE);
+
+ pr_info("SMsC 37B787 watchdog component driver removed\n");
+}
+
+module_init(wb_smsc_wdt_init);
+module_exit(wb_smsc_wdt_exit);
+
+MODULE_AUTHOR("Sven Anders <anders@anduras.de>");
+MODULE_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version "
+ VERSION ")");
+MODULE_LICENSE("GPL");
+
+#ifdef SMSC_SUPPORT_MINUTES
+module_param(unit, int, 0);
+MODULE_PARM_DESC(unit,
+ "set unit to use, 0=seconds or 1=minutes, default is 0");
+#endif
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "range is 1-255 units, default is 60");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c
new file mode 100644
index 000000000..7a1096265
--- /dev/null
+++ b/drivers/watchdog/softdog.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SoftDog: A Software Watchdog Device
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Software only watchdog driver. Unlike its big brother the WDT501P
+ * driver this won't always recover a failed machine.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hrtimer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/reboot.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/workqueue.h>
+
+#define TIMER_MARGIN 60 /* Default is 60 seconds */
+static unsigned int soft_margin = TIMER_MARGIN; /* in seconds */
+module_param(soft_margin, uint, 0);
+MODULE_PARM_DESC(soft_margin,
+ "Watchdog soft_margin in seconds. (0 < soft_margin < 65536, default="
+ __MODULE_STRING(TIMER_MARGIN) ")");
+
+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) ")");
+
+static int soft_noboot;
+module_param(soft_noboot, int, 0);
+MODULE_PARM_DESC(soft_noboot,
+ "Softdog action, set to 1 to ignore reboots, 0 to reboot (default=0)");
+
+static int soft_panic;
+module_param(soft_panic, int, 0);
+MODULE_PARM_DESC(soft_panic,
+ "Softdog action, set to 1 to panic, 0 to reboot (default=0)");
+
+static char *soft_reboot_cmd;
+module_param(soft_reboot_cmd, charp, 0000);
+MODULE_PARM_DESC(soft_reboot_cmd,
+ "Set reboot command. Emergency reboot takes place if unset");
+
+static bool soft_active_on_boot;
+module_param(soft_active_on_boot, bool, 0000);
+MODULE_PARM_DESC(soft_active_on_boot,
+ "Set to true to active Softdog on boot (default=false)");
+
+static struct hrtimer softdog_ticktock;
+static struct hrtimer softdog_preticktock;
+
+static int reboot_kthread_fn(void *data)
+{
+ kernel_restart(soft_reboot_cmd);
+ return -EPERM; /* Should not reach here */
+}
+
+static void reboot_work_fn(struct work_struct *unused)
+{
+ kthread_run(reboot_kthread_fn, NULL, "softdog_reboot");
+}
+
+static enum hrtimer_restart softdog_fire(struct hrtimer *timer)
+{
+ static bool soft_reboot_fired;
+
+ module_put(THIS_MODULE);
+ if (soft_noboot) {
+ pr_crit("Triggered - Reboot ignored\n");
+ } else if (soft_panic) {
+ pr_crit("Initiating panic\n");
+ panic("Software Watchdog Timer expired");
+ } else {
+ pr_crit("Initiating system reboot\n");
+ if (!soft_reboot_fired && soft_reboot_cmd != NULL) {
+ static DECLARE_WORK(reboot_work, reboot_work_fn);
+ /*
+ * The 'kernel_restart' is a 'might-sleep' operation.
+ * Also, executing it in system-wide workqueues blocks
+ * any driver from using the same workqueue in its
+ * shutdown callback function. Thus, we should execute
+ * the 'kernel_restart' in a standalone kernel thread.
+ * But since starting a kernel thread is also a
+ * 'might-sleep' operation, so the 'reboot_work' is
+ * required as a launcher of the kernel thread.
+ *
+ * After request the reboot, restart the timer to
+ * schedule an 'emergency_restart' reboot after
+ * 'TIMER_MARGIN' seconds. It's because if the softdog
+ * hangs, it might be because of scheduling issues. And
+ * if that is the case, both 'schedule_work' and
+ * 'kernel_restart' may possibly be malfunctional at the
+ * same time.
+ */
+ soft_reboot_fired = true;
+ schedule_work(&reboot_work);
+ hrtimer_add_expires_ns(timer,
+ (u64)TIMER_MARGIN * NSEC_PER_SEC);
+
+ return HRTIMER_RESTART;
+ }
+ emergency_restart();
+ pr_crit("Reboot didn't ?????\n");
+ }
+
+ return HRTIMER_NORESTART;
+}
+
+static struct watchdog_device softdog_dev;
+
+static enum hrtimer_restart softdog_pretimeout(struct hrtimer *timer)
+{
+ watchdog_notify_pretimeout(&softdog_dev);
+
+ return HRTIMER_NORESTART;
+}
+
+static int softdog_ping(struct watchdog_device *w)
+{
+ if (!hrtimer_active(&softdog_ticktock))
+ __module_get(THIS_MODULE);
+ hrtimer_start(&softdog_ticktock, ktime_set(w->timeout, 0),
+ HRTIMER_MODE_REL);
+
+ if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {
+ if (w->pretimeout)
+ hrtimer_start(&softdog_preticktock,
+ ktime_set(w->timeout - w->pretimeout, 0),
+ HRTIMER_MODE_REL);
+ else
+ hrtimer_cancel(&softdog_preticktock);
+ }
+
+ return 0;
+}
+
+static int softdog_stop(struct watchdog_device *w)
+{
+ if (hrtimer_cancel(&softdog_ticktock))
+ module_put(THIS_MODULE);
+
+ if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT))
+ hrtimer_cancel(&softdog_preticktock);
+
+ return 0;
+}
+
+static struct watchdog_info softdog_info = {
+ .identity = "Software Watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops softdog_ops = {
+ .owner = THIS_MODULE,
+ .start = softdog_ping,
+ .stop = softdog_stop,
+};
+
+static struct watchdog_device softdog_dev = {
+ .info = &softdog_info,
+ .ops = &softdog_ops,
+ .min_timeout = 1,
+ .max_timeout = 65535,
+ .timeout = TIMER_MARGIN,
+};
+
+static int __init softdog_init(void)
+{
+ int ret;
+
+ watchdog_init_timeout(&softdog_dev, soft_margin, NULL);
+ watchdog_set_nowayout(&softdog_dev, nowayout);
+ watchdog_stop_on_reboot(&softdog_dev);
+
+ hrtimer_init(&softdog_ticktock, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ softdog_ticktock.function = softdog_fire;
+
+ if (IS_ENABLED(CONFIG_SOFT_WATCHDOG_PRETIMEOUT)) {
+ softdog_info.options |= WDIOF_PRETIMEOUT;
+ hrtimer_init(&softdog_preticktock, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
+ softdog_preticktock.function = softdog_pretimeout;
+ }
+
+ if (soft_active_on_boot)
+ softdog_ping(&softdog_dev);
+
+ ret = watchdog_register_device(&softdog_dev);
+ if (ret)
+ return ret;
+
+ pr_info("initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n",
+ soft_noboot, softdog_dev.timeout, soft_panic, nowayout);
+ pr_info(" soft_reboot_cmd=%s soft_active_on_boot=%d\n",
+ soft_reboot_cmd ?: "<not set>", soft_active_on_boot);
+
+ return 0;
+}
+module_init(softdog_init);
+
+static void __exit softdog_exit(void)
+{
+ watchdog_unregister_device(&softdog_dev);
+}
+module_exit(softdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Software Watchdog Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c
new file mode 100644
index 000000000..2bd3dc25c
--- /dev/null
+++ b/drivers/watchdog/sp5100_tco.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * sp5100_tco : TCO timer driver for sp5100 chipsets
+ *
+ * (c) Copyright 2009 Google Inc., All Rights Reserved.
+ *
+ * Based on i8xx_tco.c:
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights
+ * Reserved.
+ * https://www.kernelconcepts.de
+ *
+ * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide",
+ * AMD Publication 44413 "AMD SP5100 Register Reference Guide"
+ * AMD Publication 45482 "AMD SB800-Series Southbridges Register
+ * Reference Guide"
+ * AMD Publication 48751 "BIOS and Kernel Developer’s Guide (BKDG)
+ * for AMD Family 16h Models 00h-0Fh Processors"
+ * AMD Publication 51192 "AMD Bolton FCH Register Reference Guide"
+ * AMD Publication 52740 "BIOS and Kernel Developer’s Guide (BKDG)
+ * for AMD Family 16h Models 30h-3Fh Processors"
+ * AMD Publication 55570-B1-PUB "Processor Programming Reference (PPR)
+ * for AMD Family 17h Model 18h, Revision B1
+ * Processors (PUB)
+ * AMD Publication 55772-A1-PUB "Processor Programming Reference (PPR)
+ * for AMD Family 17h Model 20h, Revision A1
+ * Processors (PUB)
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#include "sp5100_tco.h"
+
+#define TCO_DRIVER_NAME "sp5100-tco"
+
+/* internal variables */
+
+enum tco_reg_layout {
+ sp5100, sb800, efch, efch_mmio
+};
+
+struct sp5100_tco {
+ struct watchdog_device wdd;
+ void __iomem *tcobase;
+ enum tco_reg_layout tco_reg_layout;
+};
+
+/* the watchdog platform device */
+static struct platform_device *sp5100_tco_platform_device;
+/* the associated PCI device */
+static struct pci_dev *sp5100_tco_pci;
+
+/* module parameters */
+
+#define WATCHDOG_ACTION 0
+static bool action = WATCHDOG_ACTION;
+module_param(action, bool, 0);
+MODULE_PARM_DESC(action, "Action taken when watchdog expires, 0 to reset, 1 to poweroff (default="
+ __MODULE_STRING(WATCHDOG_ACTION) ")");
+
+#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat. */
+static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (default="
+ __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+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) ")");
+
+/*
+ * Some TCO specific functions
+ */
+
+static enum tco_reg_layout tco_reg_layout(struct pci_dev *dev)
+{
+ if (dev->vendor == PCI_VENDOR_ID_ATI &&
+ dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS &&
+ dev->revision < 0x40) {
+ return sp5100;
+ } else if (dev->vendor == PCI_VENDOR_ID_AMD &&
+ sp5100_tco_pci->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
+ sp5100_tco_pci->revision >= AMD_ZEN_SMBUS_PCI_REV) {
+ return efch_mmio;
+ } else if ((dev->vendor == PCI_VENDOR_ID_AMD || dev->vendor == PCI_VENDOR_ID_HYGON) &&
+ ((dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS &&
+ dev->revision >= 0x41) ||
+ (dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
+ dev->revision >= 0x49))) {
+ return efch;
+ }
+ return sb800;
+}
+
+static int tco_timer_start(struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+ u32 val;
+
+ val = readl(SP5100_WDT_CONTROL(tco->tcobase));
+ val |= SP5100_WDT_START_STOP_BIT;
+ writel(val, SP5100_WDT_CONTROL(tco->tcobase));
+
+ /* This must be a distinct write. */
+ val |= SP5100_WDT_TRIGGER_BIT;
+ writel(val, SP5100_WDT_CONTROL(tco->tcobase));
+
+ return 0;
+}
+
+static int tco_timer_stop(struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+ u32 val;
+
+ val = readl(SP5100_WDT_CONTROL(tco->tcobase));
+ val &= ~SP5100_WDT_START_STOP_BIT;
+ writel(val, SP5100_WDT_CONTROL(tco->tcobase));
+
+ return 0;
+}
+
+static int tco_timer_ping(struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+ u32 val;
+
+ val = readl(SP5100_WDT_CONTROL(tco->tcobase));
+ val |= SP5100_WDT_TRIGGER_BIT;
+ writel(val, SP5100_WDT_CONTROL(tco->tcobase));
+
+ return 0;
+}
+
+static int tco_timer_set_timeout(struct watchdog_device *wdd,
+ unsigned int t)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+
+ /* Write new heartbeat to watchdog */
+ writel(t, SP5100_WDT_COUNT(tco->tcobase));
+
+ wdd->timeout = t;
+
+ return 0;
+}
+
+static unsigned int tco_timer_get_timeleft(struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+
+ return readl(SP5100_WDT_COUNT(tco->tcobase));
+}
+
+static u8 sp5100_tco_read_pm_reg8(u8 index)
+{
+ outb(index, SP5100_IO_PM_INDEX_REG);
+ return inb(SP5100_IO_PM_DATA_REG);
+}
+
+static void sp5100_tco_update_pm_reg8(u8 index, u8 reset, u8 set)
+{
+ u8 val;
+
+ outb(index, SP5100_IO_PM_INDEX_REG);
+ val = inb(SP5100_IO_PM_DATA_REG);
+ val &= reset;
+ val |= set;
+ outb(val, SP5100_IO_PM_DATA_REG);
+}
+
+static void tco_timer_enable(struct sp5100_tco *tco)
+{
+ u32 val;
+
+ switch (tco->tco_reg_layout) {
+ case sb800:
+ /* For SB800 or later */
+ /* Set the Watchdog timer resolution to 1 sec */
+ sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONFIG,
+ 0xff, SB800_PM_WATCHDOG_SECOND_RES);
+
+ /* Enable watchdog decode bit and watchdog timer */
+ sp5100_tco_update_pm_reg8(SB800_PM_WATCHDOG_CONTROL,
+ ~SB800_PM_WATCHDOG_DISABLE,
+ SB800_PCI_WATCHDOG_DECODE_EN);
+ break;
+ case sp5100:
+ /* For SP5100 or SB7x0 */
+ /* Enable watchdog decode bit */
+ pci_read_config_dword(sp5100_tco_pci,
+ SP5100_PCI_WATCHDOG_MISC_REG,
+ &val);
+
+ val |= SP5100_PCI_WATCHDOG_DECODE_EN;
+
+ pci_write_config_dword(sp5100_tco_pci,
+ SP5100_PCI_WATCHDOG_MISC_REG,
+ val);
+
+ /* Enable Watchdog timer and set the resolution to 1 sec */
+ sp5100_tco_update_pm_reg8(SP5100_PM_WATCHDOG_CONTROL,
+ ~SP5100_PM_WATCHDOG_DISABLE,
+ SP5100_PM_WATCHDOG_SECOND_RES);
+ break;
+ case efch:
+ /* Set the Watchdog timer resolution to 1 sec and enable */
+ sp5100_tco_update_pm_reg8(EFCH_PM_DECODEEN3,
+ ~EFCH_PM_WATCHDOG_DISABLE,
+ EFCH_PM_DECODEEN_SECOND_RES);
+ break;
+ default:
+ break;
+ }
+}
+
+static u32 sp5100_tco_read_pm_reg32(u8 index)
+{
+ u32 val = 0;
+ int i;
+
+ for (i = 3; i >= 0; i--)
+ val = (val << 8) + sp5100_tco_read_pm_reg8(index + i);
+
+ return val;
+}
+
+static u32 sp5100_tco_request_region(struct device *dev,
+ u32 mmio_addr,
+ const char *dev_name)
+{
+ if (!devm_request_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE,
+ dev_name)) {
+ dev_dbg(dev, "MMIO address 0x%08x already in use\n", mmio_addr);
+ return 0;
+ }
+
+ return mmio_addr;
+}
+
+static u32 sp5100_tco_prepare_base(struct sp5100_tco *tco,
+ u32 mmio_addr,
+ u32 alt_mmio_addr,
+ const char *dev_name)
+{
+ struct device *dev = tco->wdd.parent;
+
+ dev_dbg(dev, "Got 0x%08x from SBResource_MMIO register\n", mmio_addr);
+
+ if (!mmio_addr && !alt_mmio_addr)
+ return -ENODEV;
+
+ /* Check for MMIO address and alternate MMIO address conflicts */
+ if (mmio_addr)
+ mmio_addr = sp5100_tco_request_region(dev, mmio_addr, dev_name);
+
+ if (!mmio_addr && alt_mmio_addr)
+ mmio_addr = sp5100_tco_request_region(dev, alt_mmio_addr, dev_name);
+
+ if (!mmio_addr) {
+ dev_err(dev, "Failed to reserve MMIO or alternate MMIO region\n");
+ return -EBUSY;
+ }
+
+ tco->tcobase = devm_ioremap(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
+ if (!tco->tcobase) {
+ dev_err(dev, "MMIO address 0x%08x failed mapping\n", mmio_addr);
+ devm_release_mem_region(dev, mmio_addr, SP5100_WDT_MEM_MAP_SIZE);
+ return -ENOMEM;
+ }
+
+ dev_info(dev, "Using 0x%08x for watchdog MMIO address\n", mmio_addr);
+
+ return 0;
+}
+
+static int sp5100_tco_timer_init(struct sp5100_tco *tco)
+{
+ struct watchdog_device *wdd = &tco->wdd;
+ struct device *dev = wdd->parent;
+ u32 val;
+
+ val = readl(SP5100_WDT_CONTROL(tco->tcobase));
+ if (val & SP5100_WDT_DISABLED) {
+ dev_err(dev, "Watchdog hardware is disabled\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Save WatchDogFired status, because WatchDogFired flag is
+ * cleared here.
+ */
+ if (val & SP5100_WDT_FIRED)
+ wdd->bootstatus = WDIOF_CARDRESET;
+
+ /* Set watchdog action */
+ if (action)
+ val |= SP5100_WDT_ACTION_RESET;
+ else
+ val &= ~SP5100_WDT_ACTION_RESET;
+ writel(val, SP5100_WDT_CONTROL(tco->tcobase));
+
+ /* Set a reasonable heartbeat before we stop the timer */
+ tco_timer_set_timeout(wdd, wdd->timeout);
+
+ /*
+ * Stop the TCO before we change anything so we don't race with
+ * a zeroed timer.
+ */
+ tco_timer_stop(wdd);
+
+ return 0;
+}
+
+static u8 efch_read_pm_reg8(void __iomem *addr, u8 index)
+{
+ return readb(addr + index);
+}
+
+static void efch_update_pm_reg8(void __iomem *addr, u8 index, u8 reset, u8 set)
+{
+ u8 val;
+
+ val = readb(addr + index);
+ val &= reset;
+ val |= set;
+ writeb(val, addr + index);
+}
+
+static void tco_timer_enable_mmio(void __iomem *addr)
+{
+ efch_update_pm_reg8(addr, EFCH_PM_DECODEEN3,
+ ~EFCH_PM_WATCHDOG_DISABLE,
+ EFCH_PM_DECODEEN_SECOND_RES);
+}
+
+static int sp5100_tco_setupdevice_mmio(struct device *dev,
+ struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+ const char *dev_name = SB800_DEVNAME;
+ u32 mmio_addr = 0, alt_mmio_addr = 0;
+ struct resource *res;
+ void __iomem *addr;
+ int ret;
+ u32 val;
+
+ res = request_mem_region_muxed(EFCH_PM_ACPI_MMIO_PM_ADDR,
+ EFCH_PM_ACPI_MMIO_PM_SIZE,
+ "sp5100_tco");
+
+ if (!res) {
+ dev_err(dev,
+ "Memory region 0x%08x already in use\n",
+ EFCH_PM_ACPI_MMIO_PM_ADDR);
+ return -EBUSY;
+ }
+
+ addr = ioremap(EFCH_PM_ACPI_MMIO_PM_ADDR, EFCH_PM_ACPI_MMIO_PM_SIZE);
+ if (!addr) {
+ dev_err(dev, "Address mapping failed\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * EFCH_PM_DECODEEN_WDT_TMREN is dual purpose. This bitfield
+ * enables sp5100_tco register MMIO space decoding. The bitfield
+ * also starts the timer operation. Enable if not already enabled.
+ */
+ val = efch_read_pm_reg8(addr, EFCH_PM_DECODEEN);
+ if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) {
+ efch_update_pm_reg8(addr, EFCH_PM_DECODEEN, 0xff,
+ EFCH_PM_DECODEEN_WDT_TMREN);
+ }
+
+ /* Error if the timer could not be enabled */
+ val = efch_read_pm_reg8(addr, EFCH_PM_DECODEEN);
+ if (!(val & EFCH_PM_DECODEEN_WDT_TMREN)) {
+ dev_err(dev, "Failed to enable the timer\n");
+ ret = -EFAULT;
+ goto out;
+ }
+
+ mmio_addr = EFCH_PM_WDT_ADDR;
+
+ /* Determine alternate MMIO base address */
+ val = efch_read_pm_reg8(addr, EFCH_PM_ISACONTROL);
+ if (val & EFCH_PM_ISACONTROL_MMIOEN)
+ alt_mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +
+ EFCH_PM_ACPI_MMIO_WDT_OFFSET;
+
+ ret = sp5100_tco_prepare_base(tco, mmio_addr, alt_mmio_addr, dev_name);
+ if (!ret) {
+ tco_timer_enable_mmio(addr);
+ ret = sp5100_tco_timer_init(tco);
+ }
+
+out:
+ if (addr)
+ iounmap(addr);
+
+ release_resource(res);
+ kfree(res);
+
+ return ret;
+}
+
+static int sp5100_tco_setupdevice(struct device *dev,
+ struct watchdog_device *wdd)
+{
+ struct sp5100_tco *tco = watchdog_get_drvdata(wdd);
+ const char *dev_name;
+ u32 mmio_addr = 0, val;
+ u32 alt_mmio_addr = 0;
+ int ret;
+
+ if (tco->tco_reg_layout == efch_mmio)
+ return sp5100_tco_setupdevice_mmio(dev, wdd);
+
+ /* Request the IO ports used by this driver */
+ if (!request_muxed_region(SP5100_IO_PM_INDEX_REG,
+ SP5100_PM_IOPORTS_SIZE, "sp5100_tco")) {
+ dev_err(dev, "I/O address 0x%04x already in use\n",
+ SP5100_IO_PM_INDEX_REG);
+ return -EBUSY;
+ }
+
+ /*
+ * Determine type of southbridge chipset.
+ */
+ switch (tco->tco_reg_layout) {
+ case sp5100:
+ dev_name = SP5100_DEVNAME;
+ mmio_addr = sp5100_tco_read_pm_reg32(SP5100_PM_WATCHDOG_BASE) &
+ 0xfffffff8;
+
+ /*
+ * Secondly, find the watchdog timer MMIO address
+ * from SBResource_MMIO register.
+ */
+
+ /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */
+ pci_read_config_dword(sp5100_tco_pci,
+ SP5100_SB_RESOURCE_MMIO_BASE,
+ &val);
+
+ /* Verify MMIO is enabled and using bar0 */
+ if ((val & SB800_ACPI_MMIO_MASK) == SB800_ACPI_MMIO_DECODE_EN)
+ alt_mmio_addr = (val & ~0xfff) + SB800_PM_WDT_MMIO_OFFSET;
+ break;
+ case sb800:
+ dev_name = SB800_DEVNAME;
+ mmio_addr = sp5100_tco_read_pm_reg32(SB800_PM_WATCHDOG_BASE) &
+ 0xfffffff8;
+
+ /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */
+ val = sp5100_tco_read_pm_reg32(SB800_PM_ACPI_MMIO_EN);
+
+ /* Verify MMIO is enabled and using bar0 */
+ if ((val & SB800_ACPI_MMIO_MASK) == SB800_ACPI_MMIO_DECODE_EN)
+ alt_mmio_addr = (val & ~0xfff) + SB800_PM_WDT_MMIO_OFFSET;
+ break;
+ case efch:
+ dev_name = SB800_DEVNAME;
+ val = sp5100_tco_read_pm_reg8(EFCH_PM_DECODEEN);
+ if (val & EFCH_PM_DECODEEN_WDT_TMREN)
+ mmio_addr = EFCH_PM_WDT_ADDR;
+
+ val = sp5100_tco_read_pm_reg8(EFCH_PM_ISACONTROL);
+ if (val & EFCH_PM_ISACONTROL_MMIOEN)
+ alt_mmio_addr = EFCH_PM_ACPI_MMIO_ADDR +
+ EFCH_PM_ACPI_MMIO_WDT_OFFSET;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ ret = sp5100_tco_prepare_base(tco, mmio_addr, alt_mmio_addr, dev_name);
+ if (!ret) {
+ /* Setup the watchdog timer */
+ tco_timer_enable(tco);
+ ret = sp5100_tco_timer_init(tco);
+ }
+
+ release_region(SP5100_IO_PM_INDEX_REG, SP5100_PM_IOPORTS_SIZE);
+ return ret;
+}
+
+static struct watchdog_info sp5100_tco_wdt_info = {
+ .identity = "SP5100 TCO timer",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops sp5100_tco_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = tco_timer_start,
+ .stop = tco_timer_stop,
+ .ping = tco_timer_ping,
+ .set_timeout = tco_timer_set_timeout,
+ .get_timeleft = tco_timer_get_timeleft,
+};
+
+static int sp5100_tco_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct sp5100_tco *tco;
+ int ret;
+
+ tco = devm_kzalloc(dev, sizeof(*tco), GFP_KERNEL);
+ if (!tco)
+ return -ENOMEM;
+
+ tco->tco_reg_layout = tco_reg_layout(sp5100_tco_pci);
+
+ wdd = &tco->wdd;
+ wdd->parent = dev;
+ wdd->info = &sp5100_tco_wdt_info;
+ wdd->ops = &sp5100_tco_wdt_ops;
+ wdd->timeout = WATCHDOG_HEARTBEAT;
+ wdd->min_timeout = 1;
+ wdd->max_timeout = 0xffff;
+
+ watchdog_init_timeout(wdd, heartbeat, NULL);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_stop_on_reboot(wdd);
+ watchdog_stop_on_unregister(wdd);
+ watchdog_set_drvdata(wdd, tco);
+
+ ret = sp5100_tco_setupdevice(dev, wdd);
+ if (ret)
+ return ret;
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ /* Show module parameters */
+ dev_info(dev, "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ wdd->timeout, nowayout);
+
+ return 0;
+}
+
+static struct platform_driver sp5100_tco_driver = {
+ .probe = sp5100_tco_probe,
+ .driver = {
+ .name = TCO_DRIVER_NAME,
+ },
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might
+ * want to register another driver on the same PCI id.
+ */
+static const struct pci_device_id sp5100_tco_pci_tbl[] = {
+ { PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, PCI_ANY_ID,
+ PCI_ANY_ID, },
+ { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS, PCI_ANY_ID,
+ PCI_ANY_ID, },
+ { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
+ PCI_ANY_ID, },
+ { PCI_VENDOR_ID_HYGON, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS, PCI_ANY_ID,
+ PCI_ANY_ID, },
+ { 0, }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl);
+
+static int __init sp5100_tco_init(void)
+{
+ struct pci_dev *dev = NULL;
+ int err;
+
+ /* Match the PCI device */
+ for_each_pci_dev(dev) {
+ if (pci_match_id(sp5100_tco_pci_tbl, dev) != NULL) {
+ sp5100_tco_pci = dev;
+ break;
+ }
+ }
+
+ if (!sp5100_tco_pci)
+ return -ENODEV;
+
+ pr_info("SP5100/SB800 TCO WatchDog Timer Driver\n");
+
+ err = platform_driver_register(&sp5100_tco_driver);
+ if (err)
+ return err;
+
+ sp5100_tco_platform_device =
+ platform_device_register_simple(TCO_DRIVER_NAME, -1, NULL, 0);
+ if (IS_ERR(sp5100_tco_platform_device)) {
+ err = PTR_ERR(sp5100_tco_platform_device);
+ goto unreg_platform_driver;
+ }
+
+ return 0;
+
+unreg_platform_driver:
+ platform_driver_unregister(&sp5100_tco_driver);
+ return err;
+}
+
+static void __exit sp5100_tco_exit(void)
+{
+ platform_device_unregister(sp5100_tco_platform_device);
+ platform_driver_unregister(&sp5100_tco_driver);
+}
+
+module_init(sp5100_tco_init);
+module_exit(sp5100_tco_exit);
+
+MODULE_AUTHOR("Priyanka Gupta");
+MODULE_DESCRIPTION("TCO timer driver for SP5100/SB800 chipset");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h
new file mode 100644
index 000000000..6a0986d2c
--- /dev/null
+++ b/drivers/watchdog/sp5100_tco.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * sp5100_tco: TCO timer driver for sp5100 chipsets.
+ *
+ * (c) Copyright 2009 Google Inc., All Rights Reserved.
+ *
+ * TCO timer driver for sp5100 chipsets
+ */
+
+#include <linux/bitops.h>
+
+/*
+ * Some address definitions for the Watchdog
+ */
+#define SP5100_WDT_MEM_MAP_SIZE 0x08
+#define SP5100_WDT_CONTROL(base) ((base) + 0x00) /* Watchdog Control */
+#define SP5100_WDT_COUNT(base) ((base) + 0x04) /* Watchdog Count */
+
+#define SP5100_WDT_START_STOP_BIT BIT(0)
+#define SP5100_WDT_FIRED BIT(1)
+#define SP5100_WDT_ACTION_RESET BIT(2)
+#define SP5100_WDT_DISABLED BIT(3)
+#define SP5100_WDT_TRIGGER_BIT BIT(7)
+
+#define SP5100_PM_IOPORTS_SIZE 0x02
+
+/*
+ * These two IO registers are hardcoded and there doesn't seem to be a way to
+ * read them from a register.
+ */
+
+/* For SP5100/SB7x0/SB8x0 chipset */
+#define SP5100_IO_PM_INDEX_REG 0xCD6
+#define SP5100_IO_PM_DATA_REG 0xCD7
+
+/* For SP5100/SB7x0 chipset */
+#define SP5100_SB_RESOURCE_MMIO_BASE 0x9C
+
+#define SP5100_PM_WATCHDOG_CONTROL 0x69
+#define SP5100_PM_WATCHDOG_BASE 0x6C
+
+#define SP5100_PCI_WATCHDOG_MISC_REG 0x41
+#define SP5100_PCI_WATCHDOG_DECODE_EN BIT(3)
+
+#define SP5100_PM_WATCHDOG_DISABLE ((u8)BIT(0))
+#define SP5100_PM_WATCHDOG_SECOND_RES GENMASK(2, 1)
+
+#define SP5100_DEVNAME "SP5100 TCO"
+
+/* For SB8x0(or later) chipset */
+#define SB800_PM_ACPI_MMIO_EN 0x24
+#define SB800_PM_WATCHDOG_CONTROL 0x48
+#define SB800_PM_WATCHDOG_BASE 0x48
+#define SB800_PM_WATCHDOG_CONFIG 0x4C
+
+#define SB800_PCI_WATCHDOG_DECODE_EN BIT(0)
+#define SB800_PM_WATCHDOG_DISABLE ((u8)BIT(1))
+#define SB800_PM_WATCHDOG_SECOND_RES GENMASK(1, 0)
+#define SB800_ACPI_MMIO_DECODE_EN BIT(0)
+#define SB800_ACPI_MMIO_SEL BIT(1)
+#define SB800_ACPI_MMIO_MASK GENMASK(1, 0)
+
+#define SB800_PM_WDT_MMIO_OFFSET 0xB00
+
+#define SB800_DEVNAME "SB800 TCO"
+
+/* For recent chips with embedded FCH (rev 40+) */
+
+#define EFCH_PM_DECODEEN 0x00
+
+#define EFCH_PM_DECODEEN_WDT_TMREN BIT(7)
+
+
+#define EFCH_PM_DECODEEN3 0x03
+#define EFCH_PM_DECODEEN_SECOND_RES GENMASK(1, 0)
+#define EFCH_PM_WATCHDOG_DISABLE ((u8)GENMASK(3, 2))
+
+/* WDT MMIO if enabled with PM00_DECODEEN_WDT_TMREN */
+#define EFCH_PM_WDT_ADDR 0xfeb00000
+
+#define EFCH_PM_ISACONTROL 0x04
+
+#define EFCH_PM_ISACONTROL_MMIOEN BIT(1)
+
+#define EFCH_PM_ACPI_MMIO_ADDR 0xfed80000
+#define EFCH_PM_ACPI_MMIO_PM_OFFSET 0x00000300
+#define EFCH_PM_ACPI_MMIO_WDT_OFFSET 0x00000b00
+
+#define EFCH_PM_ACPI_MMIO_PM_ADDR (EFCH_PM_ACPI_MMIO_ADDR + \
+ EFCH_PM_ACPI_MMIO_PM_OFFSET)
+#define EFCH_PM_ACPI_MMIO_PM_SIZE 8
+#define AMD_ZEN_SMBUS_PCI_REV 0x51
diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c
new file mode 100644
index 000000000..2756ed54c
--- /dev/null
+++ b/drivers/watchdog/sp805_wdt.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * drivers/char/watchdog/sp805-wdt.c
+ *
+ * Watchdog driver for ARM SP805 watchdog module
+ *
+ * Copyright (C) 2010 ST Microelectronics
+ * Viresh Kumar <vireshk@kernel.org>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2 or later. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/device.h>
+#include <linux/resource.h>
+#include <linux/amba/bus.h>
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+/* default timeout in seconds */
+#define DEFAULT_TIMEOUT 60
+
+#define MODULE_NAME "sp805-wdt"
+
+/* watchdog register offsets and masks */
+#define WDTLOAD 0x000
+ #define LOAD_MIN 0x00000001
+ #define LOAD_MAX 0xFFFFFFFF
+#define WDTVALUE 0x004
+#define WDTCONTROL 0x008
+ /* control register masks */
+ #define INT_ENABLE (1 << 0)
+ #define RESET_ENABLE (1 << 1)
+ #define ENABLE_MASK (INT_ENABLE | RESET_ENABLE)
+#define WDTINTCLR 0x00C
+#define WDTRIS 0x010
+#define WDTMIS 0x014
+ #define INT_MASK (1 << 0)
+#define WDTLOCK 0xC00
+ #define UNLOCK 0x1ACCE551
+ #define LOCK 0x00000001
+
+/**
+ * struct sp805_wdt: sp805 wdt device structure
+ * @wdd: instance of struct watchdog_device
+ * @lock: spin lock protecting dev structure and io access
+ * @base: base address of wdt
+ * @clk: (optional) clock structure of wdt
+ * @rate: (optional) clock rate when provided via properties
+ * @adev: amba device structure of wdt
+ * @status: current status of wdt
+ * @load_val: load value to be set for current timeout
+ */
+struct sp805_wdt {
+ struct watchdog_device wdd;
+ spinlock_t lock;
+ void __iomem *base;
+ struct clk *clk;
+ u64 rate;
+ struct amba_device *adev;
+ unsigned int load_val;
+};
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Set to 1 to keep watchdog running after device release");
+
+/* returns true if wdt is running; otherwise returns false */
+static bool wdt_is_running(struct watchdog_device *wdd)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 wdtcontrol = readl_relaxed(wdt->base + WDTCONTROL);
+
+ return (wdtcontrol & ENABLE_MASK) == ENABLE_MASK;
+}
+
+/* This routine finds load value that will reset system in required timeout */
+static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+ u64 load, rate;
+
+ rate = wdt->rate;
+
+ /*
+ * sp805 runs counter with given value twice, after the end of first
+ * counter it gives an interrupt and then starts counter again. If
+ * interrupt already occurred then it resets the system. This is why
+ * load is half of what should be required.
+ */
+ load = div_u64(rate, 2) * timeout - 1;
+
+ load = (load > LOAD_MAX) ? LOAD_MAX : load;
+ load = (load < LOAD_MIN) ? LOAD_MIN : load;
+
+ spin_lock(&wdt->lock);
+ wdt->load_val = load;
+ /* roundup timeout to closest positive integer value */
+ wdd->timeout = div_u64((load + 1) * 2 + (rate / 2), rate);
+ spin_unlock(&wdt->lock);
+
+ return 0;
+}
+
+/* returns number of seconds left for reset to occur */
+static unsigned int wdt_timeleft(struct watchdog_device *wdd)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+ u64 load;
+
+ spin_lock(&wdt->lock);
+ load = readl_relaxed(wdt->base + WDTVALUE);
+
+ /*If the interrupt is inactive then time left is WDTValue + WDTLoad. */
+ if (!(readl_relaxed(wdt->base + WDTRIS) & INT_MASK))
+ load += wdt->load_val + 1;
+ spin_unlock(&wdt->lock);
+
+ return div_u64(load, wdt->rate);
+}
+
+static int
+wdt_restart(struct watchdog_device *wdd, unsigned long mode, void *cmd)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
+ writel_relaxed(0, wdt->base + WDTCONTROL);
+ writel_relaxed(0, wdt->base + WDTLOAD);
+ writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base + WDTCONTROL);
+
+ /* Flush posted writes. */
+ readl_relaxed(wdt->base + WDTLOCK);
+
+ return 0;
+}
+
+static int wdt_config(struct watchdog_device *wdd, bool ping)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ if (!ping) {
+
+ ret = clk_prepare_enable(wdt->clk);
+ if (ret) {
+ dev_err(&wdt->adev->dev, "clock enable fail");
+ return ret;
+ }
+ }
+
+ spin_lock(&wdt->lock);
+
+ writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
+ writel_relaxed(wdt->load_val, wdt->base + WDTLOAD);
+ writel_relaxed(INT_MASK, wdt->base + WDTINTCLR);
+
+ if (!ping)
+ writel_relaxed(INT_ENABLE | RESET_ENABLE, wdt->base +
+ WDTCONTROL);
+
+ writel_relaxed(LOCK, wdt->base + WDTLOCK);
+
+ /* Flush posted writes. */
+ readl_relaxed(wdt->base + WDTLOCK);
+ spin_unlock(&wdt->lock);
+
+ return 0;
+}
+
+static int wdt_ping(struct watchdog_device *wdd)
+{
+ return wdt_config(wdd, true);
+}
+
+/* enables watchdog timers reset */
+static int wdt_enable(struct watchdog_device *wdd)
+{
+ return wdt_config(wdd, false);
+}
+
+/* disables watchdog timers reset */
+static int wdt_disable(struct watchdog_device *wdd)
+{
+ struct sp805_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ spin_lock(&wdt->lock);
+
+ writel_relaxed(UNLOCK, wdt->base + WDTLOCK);
+ writel_relaxed(0, wdt->base + WDTCONTROL);
+ writel_relaxed(LOCK, wdt->base + WDTLOCK);
+
+ /* Flush posted writes. */
+ readl_relaxed(wdt->base + WDTLOCK);
+ spin_unlock(&wdt->lock);
+
+ clk_disable_unprepare(wdt->clk);
+
+ return 0;
+}
+
+static const struct watchdog_info wdt_info = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = MODULE_NAME,
+};
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_enable,
+ .stop = wdt_disable,
+ .ping = wdt_ping,
+ .set_timeout = wdt_setload,
+ .get_timeleft = wdt_timeleft,
+ .restart = wdt_restart,
+};
+
+static int
+sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct sp805_wdt *wdt;
+ u64 rate = 0;
+ int ret = 0;
+
+ wdt = devm_kzalloc(&adev->dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ wdt->base = devm_ioremap_resource(&adev->dev, &adev->res);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ /*
+ * When driver probe with ACPI device, clock devices
+ * are not available, so watchdog rate get from
+ * clock-frequency property given in _DSD object.
+ */
+ device_property_read_u64(&adev->dev, "clock-frequency", &rate);
+
+ wdt->clk = devm_clk_get_optional(&adev->dev, NULL);
+ if (IS_ERR(wdt->clk))
+ return dev_err_probe(&adev->dev, PTR_ERR(wdt->clk), "Clock not found\n");
+
+ wdt->rate = clk_get_rate(wdt->clk);
+ if (!wdt->rate)
+ wdt->rate = rate;
+ if (!wdt->rate) {
+ dev_err(&adev->dev, "no clock-frequency property\n");
+ return -ENODEV;
+ }
+
+ wdt->adev = adev;
+ wdt->wdd.info = &wdt_info;
+ wdt->wdd.ops = &wdt_ops;
+ wdt->wdd.parent = &adev->dev;
+
+ spin_lock_init(&wdt->lock);
+ watchdog_set_nowayout(&wdt->wdd, nowayout);
+ watchdog_set_drvdata(&wdt->wdd, wdt);
+ watchdog_set_restart_priority(&wdt->wdd, 128);
+ watchdog_stop_on_unregister(&wdt->wdd);
+
+ /*
+ * If 'timeout-sec' devicetree property is specified, use that.
+ * Otherwise, use DEFAULT_TIMEOUT
+ */
+ wdt->wdd.timeout = DEFAULT_TIMEOUT;
+ watchdog_init_timeout(&wdt->wdd, 0, &adev->dev);
+ wdt_setload(&wdt->wdd, wdt->wdd.timeout);
+
+ /*
+ * If HW is already running, enable/reset the wdt and set the running
+ * bit to tell the wdt subsystem
+ */
+ if (wdt_is_running(&wdt->wdd)) {
+ wdt_enable(&wdt->wdd);
+ set_bit(WDOG_HW_RUNNING, &wdt->wdd.status);
+ }
+
+ watchdog_stop_on_reboot(&wdt->wdd);
+ ret = watchdog_register_device(&wdt->wdd);
+ if (ret)
+ goto err;
+ amba_set_drvdata(adev, wdt);
+
+ dev_info(&adev->dev, "registration successful\n");
+ return 0;
+
+err:
+ dev_err(&adev->dev, "Probe Failed!!!\n");
+ return ret;
+}
+
+static void sp805_wdt_remove(struct amba_device *adev)
+{
+ struct sp805_wdt *wdt = amba_get_drvdata(adev);
+
+ watchdog_unregister_device(&wdt->wdd);
+ watchdog_set_drvdata(&wdt->wdd, NULL);
+}
+
+static int __maybe_unused sp805_wdt_suspend(struct device *dev)
+{
+ struct sp805_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return wdt_disable(&wdt->wdd);
+
+ return 0;
+}
+
+static int __maybe_unused sp805_wdt_resume(struct device *dev)
+{
+ struct sp805_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ return wdt_enable(&wdt->wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sp805_wdt_dev_pm_ops, sp805_wdt_suspend,
+ sp805_wdt_resume);
+
+static const struct amba_id sp805_wdt_ids[] = {
+ {
+ .id = 0x00141805,
+ .mask = 0x00ffffff,
+ },
+ {
+ .id = 0x001bb824,
+ .mask = 0x00ffffff,
+ },
+ { 0, 0 },
+};
+
+MODULE_DEVICE_TABLE(amba, sp805_wdt_ids);
+
+static struct amba_driver sp805_wdt_driver = {
+ .drv = {
+ .name = MODULE_NAME,
+ .pm = &sp805_wdt_dev_pm_ops,
+ },
+ .id_table = sp805_wdt_ids,
+ .probe = sp805_wdt_probe,
+ .remove = sp805_wdt_remove,
+};
+
+module_amba_driver(sp805_wdt_driver);
+
+MODULE_AUTHOR("Viresh Kumar <vireshk@kernel.org>");
+MODULE_DESCRIPTION("ARM SP805 Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sprd_wdt.c b/drivers/watchdog/sprd_wdt.c
new file mode 100644
index 000000000..4e689b6ff
--- /dev/null
+++ b/drivers/watchdog/sprd_wdt.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Spreadtrum watchdog driver
+ * Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#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>
+
+#define SPRD_WDT_LOAD_LOW 0x0
+#define SPRD_WDT_LOAD_HIGH 0x4
+#define SPRD_WDT_CTRL 0x8
+#define SPRD_WDT_INT_CLR 0xc
+#define SPRD_WDT_INT_RAW 0x10
+#define SPRD_WDT_INT_MSK 0x14
+#define SPRD_WDT_CNT_LOW 0x18
+#define SPRD_WDT_CNT_HIGH 0x1c
+#define SPRD_WDT_LOCK 0x20
+#define SPRD_WDT_IRQ_LOAD_LOW 0x2c
+#define SPRD_WDT_IRQ_LOAD_HIGH 0x30
+
+/* WDT_CTRL */
+#define SPRD_WDT_INT_EN_BIT BIT(0)
+#define SPRD_WDT_CNT_EN_BIT BIT(1)
+#define SPRD_WDT_NEW_VER_EN BIT(2)
+#define SPRD_WDT_RST_EN_BIT BIT(3)
+
+/* WDT_INT_CLR */
+#define SPRD_WDT_INT_CLEAR_BIT BIT(0)
+#define SPRD_WDT_RST_CLEAR_BIT BIT(3)
+
+/* WDT_INT_RAW */
+#define SPRD_WDT_INT_RAW_BIT BIT(0)
+#define SPRD_WDT_RST_RAW_BIT BIT(3)
+#define SPRD_WDT_LD_BUSY_BIT BIT(4)
+
+/* 1s equal to 32768 counter steps */
+#define SPRD_WDT_CNT_STEP 32768
+
+#define SPRD_WDT_UNLOCK_KEY 0xe551
+#define SPRD_WDT_MIN_TIMEOUT 3
+#define SPRD_WDT_MAX_TIMEOUT 60
+
+#define SPRD_WDT_CNT_HIGH_SHIFT 16
+#define SPRD_WDT_LOW_VALUE_MASK GENMASK(15, 0)
+#define SPRD_WDT_LOAD_TIMEOUT 11
+
+struct sprd_wdt {
+ void __iomem *base;
+ struct watchdog_device wdd;
+ struct clk *enable;
+ struct clk *rtc_enable;
+ int irq;
+};
+
+static inline struct sprd_wdt *to_sprd_wdt(struct watchdog_device *wdd)
+{
+ return container_of(wdd, struct sprd_wdt, wdd);
+}
+
+static inline void sprd_wdt_lock(void __iomem *addr)
+{
+ writel_relaxed(0x0, addr + SPRD_WDT_LOCK);
+}
+
+static inline void sprd_wdt_unlock(void __iomem *addr)
+{
+ writel_relaxed(SPRD_WDT_UNLOCK_KEY, addr + SPRD_WDT_LOCK);
+}
+
+static irqreturn_t sprd_wdt_isr(int irq, void *dev_id)
+{
+ struct sprd_wdt *wdt = (struct sprd_wdt *)dev_id;
+
+ sprd_wdt_unlock(wdt->base);
+ writel_relaxed(SPRD_WDT_INT_CLEAR_BIT, wdt->base + SPRD_WDT_INT_CLR);
+ sprd_wdt_lock(wdt->base);
+ watchdog_notify_pretimeout(&wdt->wdd);
+ return IRQ_HANDLED;
+}
+
+static u32 sprd_wdt_get_cnt_value(struct sprd_wdt *wdt)
+{
+ u32 val;
+
+ val = readl_relaxed(wdt->base + SPRD_WDT_CNT_HIGH) <<
+ SPRD_WDT_CNT_HIGH_SHIFT;
+ val |= readl_relaxed(wdt->base + SPRD_WDT_CNT_LOW) &
+ SPRD_WDT_LOW_VALUE_MASK;
+
+ return val;
+}
+
+static int sprd_wdt_load_value(struct sprd_wdt *wdt, u32 timeout,
+ u32 pretimeout)
+{
+ u32 val, delay_cnt = 0;
+ u32 tmr_step = timeout * SPRD_WDT_CNT_STEP;
+ u32 prtmr_step = pretimeout * SPRD_WDT_CNT_STEP;
+
+ /*
+ * Checking busy bit to make sure the previous loading operation is
+ * done. According to the specification, the busy bit would be set
+ * after a new loading operation and last 2 or 3 RTC clock
+ * cycles (about 60us~92us).
+ */
+ do {
+ val = readl_relaxed(wdt->base + SPRD_WDT_INT_RAW);
+ if (!(val & SPRD_WDT_LD_BUSY_BIT))
+ break;
+
+ usleep_range(10, 100);
+ } while (delay_cnt++ < SPRD_WDT_LOAD_TIMEOUT);
+
+ if (delay_cnt >= SPRD_WDT_LOAD_TIMEOUT)
+ return -EBUSY;
+
+ sprd_wdt_unlock(wdt->base);
+ writel_relaxed((tmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) &
+ SPRD_WDT_LOW_VALUE_MASK, wdt->base + SPRD_WDT_LOAD_HIGH);
+ writel_relaxed((tmr_step & SPRD_WDT_LOW_VALUE_MASK),
+ wdt->base + SPRD_WDT_LOAD_LOW);
+ writel_relaxed((prtmr_step >> SPRD_WDT_CNT_HIGH_SHIFT) &
+ SPRD_WDT_LOW_VALUE_MASK,
+ wdt->base + SPRD_WDT_IRQ_LOAD_HIGH);
+ writel_relaxed(prtmr_step & SPRD_WDT_LOW_VALUE_MASK,
+ wdt->base + SPRD_WDT_IRQ_LOAD_LOW);
+ sprd_wdt_lock(wdt->base);
+
+ return 0;
+}
+
+static int sprd_wdt_enable(struct sprd_wdt *wdt)
+{
+ u32 val;
+ int ret;
+
+ ret = clk_prepare_enable(wdt->enable);
+ if (ret)
+ return ret;
+ ret = clk_prepare_enable(wdt->rtc_enable);
+ if (ret) {
+ clk_disable_unprepare(wdt->enable);
+ return ret;
+ }
+
+ sprd_wdt_unlock(wdt->base);
+ val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
+ val |= SPRD_WDT_NEW_VER_EN;
+ writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
+ sprd_wdt_lock(wdt->base);
+ return 0;
+}
+
+static void sprd_wdt_disable(void *_data)
+{
+ struct sprd_wdt *wdt = _data;
+
+ sprd_wdt_unlock(wdt->base);
+ writel_relaxed(0x0, wdt->base + SPRD_WDT_CTRL);
+ sprd_wdt_lock(wdt->base);
+
+ clk_disable_unprepare(wdt->rtc_enable);
+ clk_disable_unprepare(wdt->enable);
+}
+
+static int sprd_wdt_start(struct watchdog_device *wdd)
+{
+ struct sprd_wdt *wdt = to_sprd_wdt(wdd);
+ u32 val;
+ int ret;
+
+ ret = sprd_wdt_load_value(wdt, wdd->timeout, wdd->pretimeout);
+ if (ret)
+ return ret;
+
+ sprd_wdt_unlock(wdt->base);
+ val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
+ val |= SPRD_WDT_CNT_EN_BIT | SPRD_WDT_INT_EN_BIT | SPRD_WDT_RST_EN_BIT;
+ writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
+ sprd_wdt_lock(wdt->base);
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return 0;
+}
+
+static int sprd_wdt_stop(struct watchdog_device *wdd)
+{
+ struct sprd_wdt *wdt = to_sprd_wdt(wdd);
+ u32 val;
+
+ sprd_wdt_unlock(wdt->base);
+ val = readl_relaxed(wdt->base + SPRD_WDT_CTRL);
+ val &= ~(SPRD_WDT_CNT_EN_BIT | SPRD_WDT_RST_EN_BIT |
+ SPRD_WDT_INT_EN_BIT);
+ writel_relaxed(val, wdt->base + SPRD_WDT_CTRL);
+ sprd_wdt_lock(wdt->base);
+ return 0;
+}
+
+static int sprd_wdt_set_timeout(struct watchdog_device *wdd,
+ u32 timeout)
+{
+ struct sprd_wdt *wdt = to_sprd_wdt(wdd);
+
+ if (timeout == wdd->timeout)
+ return 0;
+
+ wdd->timeout = timeout;
+
+ return sprd_wdt_load_value(wdt, timeout, wdd->pretimeout);
+}
+
+static int sprd_wdt_set_pretimeout(struct watchdog_device *wdd,
+ u32 new_pretimeout)
+{
+ struct sprd_wdt *wdt = to_sprd_wdt(wdd);
+
+ if (new_pretimeout < wdd->min_timeout)
+ return -EINVAL;
+
+ wdd->pretimeout = new_pretimeout;
+
+ return sprd_wdt_load_value(wdt, wdd->timeout, new_pretimeout);
+}
+
+static u32 sprd_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct sprd_wdt *wdt = to_sprd_wdt(wdd);
+ u32 val;
+
+ val = sprd_wdt_get_cnt_value(wdt);
+ return val / SPRD_WDT_CNT_STEP;
+}
+
+static const struct watchdog_ops sprd_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sprd_wdt_start,
+ .stop = sprd_wdt_stop,
+ .set_timeout = sprd_wdt_set_timeout,
+ .set_pretimeout = sprd_wdt_set_pretimeout,
+ .get_timeleft = sprd_wdt_get_timeleft,
+};
+
+static const struct watchdog_info sprd_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_PRETIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "Spreadtrum Watchdog Timer",
+};
+
+static int sprd_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sprd_wdt *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->base))
+ return PTR_ERR(wdt->base);
+
+ wdt->enable = devm_clk_get(dev, "enable");
+ if (IS_ERR(wdt->enable)) {
+ dev_err(dev, "can't get the enable clock\n");
+ return PTR_ERR(wdt->enable);
+ }
+
+ wdt->rtc_enable = devm_clk_get(dev, "rtc_enable");
+ if (IS_ERR(wdt->rtc_enable)) {
+ dev_err(dev, "can't get the rtc enable clock\n");
+ return PTR_ERR(wdt->rtc_enable);
+ }
+
+ wdt->irq = platform_get_irq(pdev, 0);
+ if (wdt->irq < 0)
+ return wdt->irq;
+
+ ret = devm_request_irq(dev, wdt->irq, sprd_wdt_isr, IRQF_NO_SUSPEND,
+ "sprd-wdt", (void *)wdt);
+ if (ret) {
+ dev_err(dev, "failed to register irq\n");
+ return ret;
+ }
+
+ wdt->wdd.info = &sprd_wdt_info;
+ wdt->wdd.ops = &sprd_wdt_ops;
+ wdt->wdd.parent = dev;
+ wdt->wdd.min_timeout = SPRD_WDT_MIN_TIMEOUT;
+ wdt->wdd.max_timeout = SPRD_WDT_MAX_TIMEOUT;
+ wdt->wdd.timeout = SPRD_WDT_MAX_TIMEOUT;
+
+ ret = sprd_wdt_enable(wdt);
+ if (ret) {
+ dev_err(dev, "failed to enable wdt\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, sprd_wdt_disable, wdt);
+ if (ret) {
+ dev_err(dev, "Failed to add wdt disable action\n");
+ return ret;
+ }
+
+ watchdog_set_nowayout(&wdt->wdd, WATCHDOG_NOWAYOUT);
+ watchdog_init_timeout(&wdt->wdd, 0, dev);
+
+ ret = devm_watchdog_register_device(dev, &wdt->wdd);
+ if (ret) {
+ sprd_wdt_disable(wdt);
+ return ret;
+ }
+ platform_set_drvdata(pdev, wdt);
+
+ return 0;
+}
+
+static int __maybe_unused sprd_wdt_pm_suspend(struct device *dev)
+{
+ struct sprd_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ sprd_wdt_stop(&wdt->wdd);
+ sprd_wdt_disable(wdt);
+
+ return 0;
+}
+
+static int __maybe_unused sprd_wdt_pm_resume(struct device *dev)
+{
+ struct sprd_wdt *wdt = dev_get_drvdata(dev);
+ int ret;
+
+ ret = sprd_wdt_enable(wdt);
+ if (ret)
+ return ret;
+
+ if (watchdog_active(&wdt->wdd))
+ ret = sprd_wdt_start(&wdt->wdd);
+
+ return ret;
+}
+
+static const struct dev_pm_ops sprd_wdt_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(sprd_wdt_pm_suspend,
+ sprd_wdt_pm_resume)
+};
+
+static const struct of_device_id sprd_wdt_match_table[] = {
+ { .compatible = "sprd,sp9860-wdt", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sprd_wdt_match_table);
+
+static struct platform_driver sprd_watchdog_driver = {
+ .probe = sprd_wdt_probe,
+ .driver = {
+ .name = "sprd-wdt",
+ .of_match_table = sprd_wdt_match_table,
+ .pm = &sprd_wdt_pm_ops,
+ },
+};
+module_platform_driver(sprd_watchdog_driver);
+
+MODULE_AUTHOR("Eric Long <eric.long@spreadtrum.com>");
+MODULE_DESCRIPTION("Spreadtrum Watchdog Timer Controller Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/st_lpc_wdt.c b/drivers/watchdog/st_lpc_wdt.c
new file mode 100644
index 000000000..39abecdb9
--- /dev/null
+++ b/drivers/watchdog/st_lpc_wdt.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ST's LPC Watchdog
+ *
+ * Copyright (C) 2014 STMicroelectronics -- All Rights Reserved
+ *
+ * Author: David Paris <david.paris@st.com> for STMicroelectronics
+ * Lee Jones <lee.jones@linaro.org> for STMicroelectronics
+ */
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+#include <dt-bindings/mfd/st-lpc.h>
+
+/* Low Power Alarm */
+#define LPC_LPA_LSB_OFF 0x410
+#define LPC_LPA_START_OFF 0x418
+
+/* LPC as WDT */
+#define LPC_WDT_OFF 0x510
+
+static struct watchdog_device st_wdog_dev;
+
+struct st_wdog_syscfg {
+ unsigned int reset_type_reg;
+ unsigned int reset_type_mask;
+ unsigned int enable_reg;
+ unsigned int enable_mask;
+};
+
+struct st_wdog {
+ void __iomem *base;
+ struct device *dev;
+ struct regmap *regmap;
+ struct st_wdog_syscfg *syscfg;
+ struct clk *clk;
+ unsigned long clkrate;
+ bool warm_reset;
+};
+
+static struct st_wdog_syscfg stih407_syscfg = {
+ .enable_reg = 0x204,
+ .enable_mask = BIT(19),
+};
+
+static const struct of_device_id st_wdog_match[] = {
+ {
+ .compatible = "st,stih407-lpc",
+ .data = &stih407_syscfg,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, st_wdog_match);
+
+static void st_wdog_setup(struct st_wdog *st_wdog, bool enable)
+{
+ /* Type of watchdog reset - 0: Cold 1: Warm */
+ if (st_wdog->syscfg->reset_type_reg)
+ regmap_update_bits(st_wdog->regmap,
+ st_wdog->syscfg->reset_type_reg,
+ st_wdog->syscfg->reset_type_mask,
+ st_wdog->warm_reset);
+
+ /* Mask/unmask watchdog reset */
+ regmap_update_bits(st_wdog->regmap,
+ st_wdog->syscfg->enable_reg,
+ st_wdog->syscfg->enable_mask,
+ enable ? 0 : st_wdog->syscfg->enable_mask);
+}
+
+static void st_wdog_load_timer(struct st_wdog *st_wdog, unsigned int timeout)
+{
+ unsigned long clkrate = st_wdog->clkrate;
+
+ writel_relaxed(timeout * clkrate, st_wdog->base + LPC_LPA_LSB_OFF);
+ writel_relaxed(1, st_wdog->base + LPC_LPA_START_OFF);
+}
+
+static int st_wdog_start(struct watchdog_device *wdd)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(wdd);
+
+ writel_relaxed(1, st_wdog->base + LPC_WDT_OFF);
+
+ return 0;
+}
+
+static int st_wdog_stop(struct watchdog_device *wdd)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(wdd);
+
+ writel_relaxed(0, st_wdog->base + LPC_WDT_OFF);
+
+ return 0;
+}
+
+static int st_wdog_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(wdd);
+
+ wdd->timeout = timeout;
+ st_wdog_load_timer(st_wdog, timeout);
+
+ return 0;
+}
+
+static int st_wdog_keepalive(struct watchdog_device *wdd)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(wdd);
+
+ st_wdog_load_timer(st_wdog, wdd->timeout);
+
+ return 0;
+}
+
+static const struct watchdog_info st_wdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "ST LPC WDT",
+};
+
+static const struct watchdog_ops st_wdog_ops = {
+ .owner = THIS_MODULE,
+ .start = st_wdog_start,
+ .stop = st_wdog_stop,
+ .ping = st_wdog_keepalive,
+ .set_timeout = st_wdog_set_timeout,
+};
+
+static struct watchdog_device st_wdog_dev = {
+ .info = &st_wdog_info,
+ .ops = &st_wdog_ops,
+};
+
+static void st_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int st_wdog_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct device_node *np = dev->of_node;
+ struct st_wdog *st_wdog;
+ struct regmap *regmap;
+ struct clk *clk;
+ void __iomem *base;
+ uint32_t mode;
+ int ret;
+
+ ret = of_property_read_u32(np, "st,lpc-mode", &mode);
+ if (ret) {
+ dev_err(dev, "An LPC mode must be provided\n");
+ return -EINVAL;
+ }
+
+ /* LPC can either run as a Clocksource or in RTC or WDT mode */
+ if (mode != ST_LPC_MODE_WDT)
+ return -ENODEV;
+
+ st_wdog = devm_kzalloc(dev, sizeof(*st_wdog), GFP_KERNEL);
+ if (!st_wdog)
+ return -ENOMEM;
+
+ match = of_match_device(st_wdog_match, dev);
+ if (!match) {
+ dev_err(dev, "Couldn't match device\n");
+ return -ENODEV;
+ }
+ st_wdog->syscfg = (struct st_wdog_syscfg *)match->data;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
+ if (IS_ERR(regmap)) {
+ dev_err(dev, "No syscfg phandle specified\n");
+ return PTR_ERR(regmap);
+ }
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(dev, "Unable to request clock\n");
+ return PTR_ERR(clk);
+ }
+
+ st_wdog->dev = dev;
+ st_wdog->base = base;
+ st_wdog->clk = clk;
+ st_wdog->regmap = regmap;
+ st_wdog->warm_reset = of_property_read_bool(np, "st,warm_reset");
+ st_wdog->clkrate = clk_get_rate(st_wdog->clk);
+
+ if (!st_wdog->clkrate) {
+ dev_err(dev, "Unable to fetch clock rate\n");
+ return -EINVAL;
+ }
+ st_wdog_dev.max_timeout = 0xFFFFFFFF / st_wdog->clkrate;
+ st_wdog_dev.parent = dev;
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(dev, "Unable to enable clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, st_clk_disable_unprepare, clk);
+ if (ret)
+ return ret;
+
+ watchdog_set_drvdata(&st_wdog_dev, st_wdog);
+ watchdog_set_nowayout(&st_wdog_dev, WATCHDOG_NOWAYOUT);
+
+ /* Init Watchdog timeout with value in DT */
+ ret = watchdog_init_timeout(&st_wdog_dev, 0, dev);
+ if (ret)
+ return ret;
+
+ ret = devm_watchdog_register_device(dev, &st_wdog_dev);
+ if (ret)
+ return ret;
+
+ st_wdog_setup(st_wdog, true);
+
+ dev_info(dev, "LPC Watchdog driver registered, reset type is %s",
+ st_wdog->warm_reset ? "warm" : "cold");
+
+ return ret;
+}
+
+static int st_wdog_remove(struct platform_device *pdev)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev);
+
+ st_wdog_setup(st_wdog, false);
+
+ return 0;
+}
+
+static int st_wdog_suspend(struct device *dev)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev);
+
+ if (watchdog_active(&st_wdog_dev))
+ st_wdog_stop(&st_wdog_dev);
+
+ st_wdog_setup(st_wdog, false);
+
+ clk_disable(st_wdog->clk);
+
+ return 0;
+}
+
+static int st_wdog_resume(struct device *dev)
+{
+ struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev);
+ int ret;
+
+ ret = clk_enable(st_wdog->clk);
+ if (ret) {
+ dev_err(dev, "Unable to re-enable clock\n");
+ watchdog_unregister_device(&st_wdog_dev);
+ clk_unprepare(st_wdog->clk);
+ return ret;
+ }
+
+ st_wdog_setup(st_wdog, true);
+
+ if (watchdog_active(&st_wdog_dev)) {
+ st_wdog_load_timer(st_wdog, st_wdog_dev.timeout);
+ st_wdog_start(&st_wdog_dev);
+ }
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(st_wdog_pm_ops,
+ st_wdog_suspend, st_wdog_resume);
+
+static struct platform_driver st_wdog_driver = {
+ .driver = {
+ .name = "st-lpc-wdt",
+ .pm = pm_sleep_ptr(&st_wdog_pm_ops),
+ .of_match_table = st_wdog_match,
+ },
+ .probe = st_wdog_probe,
+ .remove = st_wdog_remove,
+};
+module_platform_driver(st_wdog_driver);
+
+MODULE_AUTHOR("David Paris <david.paris@st.com>");
+MODULE_DESCRIPTION("ST LPC Watchdog Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/stm32_iwdg.c b/drivers/watchdog/stm32_iwdg.c
new file mode 100644
index 000000000..570a71509
--- /dev/null
+++ b/drivers/watchdog/stm32_iwdg.c
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for STM32 Independent Watchdog
+ *
+ * Copyright (C) STMicroelectronics 2017
+ * Author: Yannick Fertre <yannick.fertre@st.com> for STMicroelectronics.
+ *
+ * This driver is based on tegra_wdt.c
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+/* IWDG registers */
+#define IWDG_KR 0x00 /* Key register */
+#define IWDG_PR 0x04 /* Prescaler Register */
+#define IWDG_RLR 0x08 /* ReLoad Register */
+#define IWDG_SR 0x0C /* Status Register */
+#define IWDG_WINR 0x10 /* Windows Register */
+
+/* IWDG_KR register bit mask */
+#define KR_KEY_RELOAD 0xAAAA /* reload counter enable */
+#define KR_KEY_ENABLE 0xCCCC /* peripheral enable */
+#define KR_KEY_EWA 0x5555 /* write access enable */
+#define KR_KEY_DWA 0x0000 /* write access disable */
+
+/* IWDG_PR register */
+#define PR_SHIFT 2
+#define PR_MIN BIT(PR_SHIFT)
+
+/* IWDG_RLR register values */
+#define RLR_MIN 0x2 /* min value recommended */
+#define RLR_MAX GENMASK(11, 0) /* max value of reload register */
+
+/* IWDG_SR register bit mask */
+#define SR_PVU BIT(0) /* Watchdog prescaler value update */
+#define SR_RVU BIT(1) /* Watchdog counter reload value update */
+
+/* set timeout to 100000 us */
+#define TIMEOUT_US 100000
+#define SLEEP_US 1000
+
+struct stm32_iwdg_data {
+ bool has_pclk;
+ u32 max_prescaler;
+};
+
+static const struct stm32_iwdg_data stm32_iwdg_data = {
+ .has_pclk = false,
+ .max_prescaler = 256,
+};
+
+static const struct stm32_iwdg_data stm32mp1_iwdg_data = {
+ .has_pclk = true,
+ .max_prescaler = 1024,
+};
+
+struct stm32_iwdg {
+ struct watchdog_device wdd;
+ const struct stm32_iwdg_data *data;
+ void __iomem *regs;
+ struct clk *clk_lsi;
+ struct clk *clk_pclk;
+ unsigned int rate;
+};
+
+static inline u32 reg_read(void __iomem *base, u32 reg)
+{
+ return readl_relaxed(base + reg);
+}
+
+static inline void reg_write(void __iomem *base, u32 reg, u32 val)
+{
+ writel_relaxed(val, base + reg);
+}
+
+static int stm32_iwdg_start(struct watchdog_device *wdd)
+{
+ struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
+ u32 tout, presc, iwdg_rlr, iwdg_pr, iwdg_sr;
+ int ret;
+
+ dev_dbg(wdd->parent, "%s\n", __func__);
+
+ tout = clamp_t(unsigned int, wdd->timeout,
+ wdd->min_timeout, wdd->max_hw_heartbeat_ms / 1000);
+
+ presc = DIV_ROUND_UP(tout * wdt->rate, RLR_MAX + 1);
+
+ /* The prescaler is align on power of 2 and start at 2 ^ PR_SHIFT. */
+ presc = roundup_pow_of_two(presc);
+ iwdg_pr = presc <= 1 << PR_SHIFT ? 0 : ilog2(presc) - PR_SHIFT;
+ iwdg_rlr = ((tout * wdt->rate) / presc) - 1;
+
+ /* enable write access */
+ reg_write(wdt->regs, IWDG_KR, KR_KEY_EWA);
+
+ /* set prescaler & reload registers */
+ reg_write(wdt->regs, IWDG_PR, iwdg_pr);
+ reg_write(wdt->regs, IWDG_RLR, iwdg_rlr);
+ reg_write(wdt->regs, IWDG_KR, KR_KEY_ENABLE);
+
+ /* wait for the registers to be updated (max 100ms) */
+ ret = readl_relaxed_poll_timeout(wdt->regs + IWDG_SR, iwdg_sr,
+ !(iwdg_sr & (SR_PVU | SR_RVU)),
+ SLEEP_US, TIMEOUT_US);
+ if (ret) {
+ dev_err(wdd->parent, "Fail to set prescaler, reload regs\n");
+ return ret;
+ }
+
+ /* reload watchdog */
+ reg_write(wdt->regs, IWDG_KR, KR_KEY_RELOAD);
+
+ return 0;
+}
+
+static int stm32_iwdg_ping(struct watchdog_device *wdd)
+{
+ struct stm32_iwdg *wdt = watchdog_get_drvdata(wdd);
+
+ dev_dbg(wdd->parent, "%s\n", __func__);
+
+ /* reload watchdog */
+ reg_write(wdt->regs, IWDG_KR, KR_KEY_RELOAD);
+
+ return 0;
+}
+
+static int stm32_iwdg_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ dev_dbg(wdd->parent, "%s timeout: %d sec\n", __func__, timeout);
+
+ wdd->timeout = timeout;
+
+ if (watchdog_active(wdd))
+ return stm32_iwdg_start(wdd);
+
+ return 0;
+}
+
+static void stm32_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int stm32_iwdg_clk_init(struct platform_device *pdev,
+ struct stm32_iwdg *wdt)
+{
+ struct device *dev = &pdev->dev;
+ u32 ret;
+
+ wdt->clk_lsi = devm_clk_get(dev, "lsi");
+ if (IS_ERR(wdt->clk_lsi))
+ return dev_err_probe(dev, PTR_ERR(wdt->clk_lsi), "Unable to get lsi clock\n");
+
+ /* optional peripheral clock */
+ if (wdt->data->has_pclk) {
+ wdt->clk_pclk = devm_clk_get(dev, "pclk");
+ if (IS_ERR(wdt->clk_pclk))
+ return dev_err_probe(dev, PTR_ERR(wdt->clk_pclk),
+ "Unable to get pclk clock\n");
+
+ ret = clk_prepare_enable(wdt->clk_pclk);
+ if (ret) {
+ dev_err(dev, "Unable to prepare pclk clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev,
+ stm32_clk_disable_unprepare,
+ wdt->clk_pclk);
+ if (ret)
+ return ret;
+ }
+
+ ret = clk_prepare_enable(wdt->clk_lsi);
+ if (ret) {
+ dev_err(dev, "Unable to prepare lsi clock\n");
+ return ret;
+ }
+ ret = devm_add_action_or_reset(dev, stm32_clk_disable_unprepare,
+ wdt->clk_lsi);
+ if (ret)
+ return ret;
+
+ wdt->rate = clk_get_rate(wdt->clk_lsi);
+
+ return 0;
+}
+
+static const struct watchdog_info stm32_iwdg_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "STM32 Independent Watchdog",
+};
+
+static const struct watchdog_ops stm32_iwdg_ops = {
+ .owner = THIS_MODULE,
+ .start = stm32_iwdg_start,
+ .ping = stm32_iwdg_ping,
+ .set_timeout = stm32_iwdg_set_timeout,
+};
+
+static const struct of_device_id stm32_iwdg_of_match[] = {
+ { .compatible = "st,stm32-iwdg", .data = &stm32_iwdg_data },
+ { .compatible = "st,stm32mp1-iwdg", .data = &stm32mp1_iwdg_data },
+ { /* end node */ }
+};
+MODULE_DEVICE_TABLE(of, stm32_iwdg_of_match);
+
+static int stm32_iwdg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct stm32_iwdg *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->data = of_device_get_match_data(&pdev->dev);
+ if (!wdt->data)
+ return -ENODEV;
+
+ /* This is the timer base. */
+ wdt->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(wdt->regs))
+ return PTR_ERR(wdt->regs);
+
+ ret = stm32_iwdg_clk_init(pdev, wdt);
+ if (ret)
+ return ret;
+
+ /* Initialize struct watchdog_device. */
+ wdd = &wdt->wdd;
+ wdd->parent = dev;
+ wdd->info = &stm32_iwdg_info;
+ wdd->ops = &stm32_iwdg_ops;
+ wdd->min_timeout = DIV_ROUND_UP((RLR_MIN + 1) * PR_MIN, wdt->rate);
+ wdd->max_hw_heartbeat_ms = ((RLR_MAX + 1) * wdt->data->max_prescaler *
+ 1000) / wdt->rate;
+
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
+ watchdog_init_timeout(wdd, 0, dev);
+
+ /*
+ * In case of CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED is set
+ * (Means U-Boot/bootloaders leaves the watchdog running)
+ * When we get here we should make a decision to prevent
+ * any side effects before user space daemon will take care of it.
+ * The best option, taking into consideration that there is no
+ * way to read values back from hardware, is to enforce watchdog
+ * being run with deterministic values.
+ */
+ if (IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED)) {
+ ret = stm32_iwdg_start(wdd);
+ if (ret)
+ return ret;
+
+ /* Make sure the watchdog is serviced */
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+
+ return 0;
+}
+
+static struct platform_driver stm32_iwdg_driver = {
+ .probe = stm32_iwdg_probe,
+ .driver = {
+ .name = "iwdg",
+ .of_match_table = of_match_ptr(stm32_iwdg_of_match),
+ },
+};
+module_platform_driver(stm32_iwdg_driver);
+
+MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
+MODULE_DESCRIPTION("STMicroelectronics STM32 Independent Watchdog Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/stmp3xxx_rtc_wdt.c b/drivers/watchdog/stmp3xxx_rtc_wdt.c
new file mode 100644
index 000000000..7caf3aa71
--- /dev/null
+++ b/drivers/watchdog/stmp3xxx_rtc_wdt.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for the RTC based watchdog in STMP3xxx and i.MX23/28
+ *
+ * Author: Wolfram Sang <kernel@pengutronix.de>
+ *
+ * Copyright (C) 2011-12 Wolfram Sang, Pengutronix
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/stmp3xxx_rtc_wdt.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+
+#define WDOG_TICK_RATE 1000 /* 1 kHz clock */
+#define STMP3XXX_DEFAULT_TIMEOUT 19
+#define STMP3XXX_MAX_TIMEOUT (UINT_MAX / WDOG_TICK_RATE)
+
+static int heartbeat = STMP3XXX_DEFAULT_TIMEOUT;
+module_param(heartbeat, uint, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(STMP3XXX_MAX_TIMEOUT) ", default "
+ __MODULE_STRING(STMP3XXX_DEFAULT_TIMEOUT));
+
+static int wdt_start(struct watchdog_device *wdd)
+{
+ struct device *dev = watchdog_get_drvdata(wdd);
+ struct stmp3xxx_wdt_pdata *pdata = dev_get_platdata(dev);
+
+ pdata->wdt_set_timeout(dev->parent, wdd->timeout * WDOG_TICK_RATE);
+ return 0;
+}
+
+static int wdt_stop(struct watchdog_device *wdd)
+{
+ struct device *dev = watchdog_get_drvdata(wdd);
+ struct stmp3xxx_wdt_pdata *pdata = dev_get_platdata(dev);
+
+ pdata->wdt_set_timeout(dev->parent, 0);
+ return 0;
+}
+
+static int wdt_set_timeout(struct watchdog_device *wdd, unsigned new_timeout)
+{
+ wdd->timeout = new_timeout;
+ return wdt_start(wdd);
+}
+
+static const struct watchdog_info stmp3xxx_wdt_ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "STMP3XXX RTC Watchdog",
+};
+
+static const struct watchdog_ops stmp3xxx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .set_timeout = wdt_set_timeout,
+};
+
+static struct watchdog_device stmp3xxx_wdd = {
+ .info = &stmp3xxx_wdt_ident,
+ .ops = &stmp3xxx_wdt_ops,
+ .min_timeout = 1,
+ .max_timeout = STMP3XXX_MAX_TIMEOUT,
+ .status = WATCHDOG_NOWAYOUT_INIT_STATUS,
+};
+
+static int wdt_notify_sys(struct notifier_block *nb, unsigned long code,
+ void *unused)
+{
+ switch (code) {
+ case SYS_DOWN: /* keep enabled, system might crash while going down */
+ break;
+ case SYS_HALT: /* allow the system to actually halt */
+ case SYS_POWER_OFF:
+ wdt_stop(&stmp3xxx_wdd);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+static int stmp3xxx_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ watchdog_set_drvdata(&stmp3xxx_wdd, dev);
+
+ stmp3xxx_wdd.timeout = clamp_t(unsigned, heartbeat, 1, STMP3XXX_MAX_TIMEOUT);
+ stmp3xxx_wdd.parent = dev;
+
+ ret = devm_watchdog_register_device(dev, &stmp3xxx_wdd);
+ if (ret < 0)
+ return ret;
+
+ if (register_reboot_notifier(&wdt_notifier))
+ dev_warn(dev, "cannot register reboot notifier\n");
+
+ dev_info(dev, "initialized watchdog with heartbeat %ds\n",
+ stmp3xxx_wdd.timeout);
+ return 0;
+}
+
+static int stmp3xxx_wdt_remove(struct platform_device *pdev)
+{
+ unregister_reboot_notifier(&wdt_notifier);
+ return 0;
+}
+
+static int __maybe_unused stmp3xxx_wdt_suspend(struct device *dev)
+{
+ struct watchdog_device *wdd = &stmp3xxx_wdd;
+
+ if (watchdog_active(wdd))
+ return wdt_stop(wdd);
+
+ return 0;
+}
+
+static int __maybe_unused stmp3xxx_wdt_resume(struct device *dev)
+{
+ struct watchdog_device *wdd = &stmp3xxx_wdd;
+
+ if (watchdog_active(wdd))
+ return wdt_start(wdd);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(stmp3xxx_wdt_pm_ops,
+ stmp3xxx_wdt_suspend, stmp3xxx_wdt_resume);
+
+static struct platform_driver stmp3xxx_wdt_driver = {
+ .driver = {
+ .name = "stmp3xxx_rtc_wdt",
+ .pm = &stmp3xxx_wdt_pm_ops,
+ },
+ .probe = stmp3xxx_wdt_probe,
+ .remove = stmp3xxx_wdt_remove,
+};
+module_platform_driver(stmp3xxx_wdt_driver);
+
+MODULE_DESCRIPTION("STMP3XXX RTC Watchdog Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Wolfram Sang <kernel@pengutronix.de>");
diff --git a/drivers/watchdog/stpmic1_wdt.c b/drivers/watchdog/stpmic1_wdt.c
new file mode 100644
index 000000000..45d0c5434
--- /dev/null
+++ b/drivers/watchdog/stpmic1_wdt.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) STMicroelectronics 2018
+// Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
+
+#include <linux/kernel.h>
+#include <linux/mfd/stpmic1.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+/* WATCHDOG CONTROL REGISTER bit */
+#define WDT_START BIT(0)
+#define WDT_PING BIT(1)
+#define WDT_START_MASK BIT(0)
+#define WDT_PING_MASK BIT(1)
+#define WDT_STOP 0
+
+#define PMIC_WDT_MIN_TIMEOUT 1
+#define PMIC_WDT_MAX_TIMEOUT 256
+#define PMIC_WDT_DEFAULT_TIMEOUT 30
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct stpmic1_wdt {
+ struct stpmic1 *pmic;
+ struct watchdog_device wdtdev;
+};
+
+static int pmic_wdt_start(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, WDT_START);
+}
+
+static int pmic_wdt_stop(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, WDT_STOP);
+}
+
+static int pmic_wdt_ping(struct watchdog_device *wdd)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_PING_MASK, WDT_PING);
+}
+
+static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct stpmic1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ wdd->timeout = timeout;
+ /* timeout is equal to register value + 1 */
+ return regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout - 1);
+}
+
+static const struct watchdog_info pmic_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "STPMIC1 PMIC Watchdog",
+};
+
+static const struct watchdog_ops pmic_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = pmic_wdt_start,
+ .stop = pmic_wdt_stop,
+ .ping = pmic_wdt_ping,
+ .set_timeout = pmic_wdt_set_timeout,
+};
+
+static int pmic_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct stpmic1 *pmic;
+ struct stpmic1_wdt *wdt;
+
+ if (!dev->parent)
+ return -EINVAL;
+
+ pmic = dev_get_drvdata(dev->parent);
+ if (!pmic)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(dev, sizeof(struct stpmic1_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->pmic = pmic;
+
+ wdt->wdtdev.info = &pmic_watchdog_info;
+ wdt->wdtdev.ops = &pmic_watchdog_ops;
+ wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
+ wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
+ wdt->wdtdev.parent = dev;
+
+ wdt->wdtdev.timeout = PMIC_WDT_DEFAULT_TIMEOUT;
+ watchdog_init_timeout(&wdt->wdtdev, 0, dev);
+
+ watchdog_set_nowayout(&wdt->wdtdev, nowayout);
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+
+ ret = devm_watchdog_register_device(dev, &wdt->wdtdev);
+ if (ret)
+ return ret;
+
+ dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");
+ return 0;
+}
+
+static const struct of_device_id of_pmic_wdt_match[] = {
+ { .compatible = "st,stpmic1-wdt" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
+
+static struct platform_driver stpmic1_wdt_driver = {
+ .probe = pmic_wdt_probe,
+ .driver = {
+ .name = "stpmic1-wdt",
+ .of_match_table = of_pmic_wdt_match,
+ },
+};
+module_platform_driver(stpmic1_wdt_driver);
+
+MODULE_DESCRIPTION("Watchdog driver for STPMIC1 device");
+MODULE_AUTHOR("Pascal Paillet <p.paillet@st.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/sun4v_wdt.c b/drivers/watchdog/sun4v_wdt.c
new file mode 100644
index 000000000..8db86ad5e
--- /dev/null
+++ b/drivers/watchdog/sun4v_wdt.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * sun4v watchdog timer
+ * (c) Copyright 2016 Oracle Corporation
+ *
+ * Implement a simple watchdog driver using the built-in sun4v hypervisor
+ * watchdog support. If time expires, the hypervisor stops or bounces
+ * the guest domain.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/watchdog.h>
+#include <asm/hypervisor.h>
+#include <asm/mdesc.h>
+
+#define WDT_TIMEOUT 60
+#define WDT_MAX_TIMEOUT 31536000
+#define WDT_MIN_TIMEOUT 1
+#define WDT_DEFAULT_RESOLUTION_MS 1000 /* 1 second */
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
+ __MODULE_STRING(WDT_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, S_IRUGO);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int sun4v_wdt_stop(struct watchdog_device *wdd)
+{
+ sun4v_mach_set_watchdog(0, NULL);
+
+ return 0;
+}
+
+static int sun4v_wdt_ping(struct watchdog_device *wdd)
+{
+ int hverr;
+
+ /*
+ * HV watchdog timer will round up the timeout
+ * passed in to the nearest multiple of the
+ * watchdog resolution in milliseconds.
+ */
+ hverr = sun4v_mach_set_watchdog(wdd->timeout * 1000, NULL);
+ if (hverr == HV_EINVAL)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int sun4v_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+
+ return 0;
+}
+
+static const struct watchdog_info sun4v_wdt_ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .identity = "sun4v hypervisor watchdog",
+ .firmware_version = 0,
+};
+
+static const struct watchdog_ops sun4v_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sun4v_wdt_ping,
+ .stop = sun4v_wdt_stop,
+ .ping = sun4v_wdt_ping,
+ .set_timeout = sun4v_wdt_set_timeout,
+};
+
+static struct watchdog_device wdd = {
+ .info = &sun4v_wdt_ident,
+ .ops = &sun4v_wdt_ops,
+ .min_timeout = WDT_MIN_TIMEOUT,
+ .max_timeout = WDT_MAX_TIMEOUT,
+ .timeout = WDT_TIMEOUT,
+};
+
+static int __init sun4v_wdt_init(void)
+{
+ struct mdesc_handle *handle;
+ u64 node;
+ const u64 *value;
+ int err = 0;
+ unsigned long major = 1, minor = 1;
+
+ /*
+ * There are 2 properties that can be set from the control
+ * domain for the watchdog.
+ * watchdog-resolution
+ * watchdog-max-timeout
+ *
+ * We can expect a handle to be returned otherwise something
+ * serious is wrong. Correct to return -ENODEV here.
+ */
+
+ handle = mdesc_grab();
+ if (!handle)
+ return -ENODEV;
+
+ node = mdesc_node_by_name(handle, MDESC_NODE_NULL, "platform");
+ err = -ENODEV;
+ if (node == MDESC_NODE_NULL)
+ goto out_release;
+
+ /*
+ * This is a safe way to validate if we are on the right
+ * platform.
+ */
+ if (sun4v_hvapi_register(HV_GRP_CORE, major, &minor))
+ goto out_hv_unreg;
+
+ /* Allow value of watchdog-resolution up to 1s (default) */
+ value = mdesc_get_property(handle, node, "watchdog-resolution", NULL);
+ err = -EINVAL;
+ if (value) {
+ if (*value == 0 ||
+ *value > WDT_DEFAULT_RESOLUTION_MS)
+ goto out_hv_unreg;
+ }
+
+ value = mdesc_get_property(handle, node, "watchdog-max-timeout", NULL);
+ if (value) {
+ /*
+ * If the property value (in ms) is smaller than
+ * min_timeout, return -EINVAL.
+ */
+ if (*value < wdd.min_timeout * 1000)
+ goto out_hv_unreg;
+
+ /*
+ * If the property value is smaller than
+ * default max_timeout then set watchdog max_timeout to
+ * the value of the property in seconds.
+ */
+ if (*value < wdd.max_timeout * 1000)
+ wdd.max_timeout = *value / 1000;
+ }
+
+ watchdog_init_timeout(&wdd, timeout, NULL);
+
+ watchdog_set_nowayout(&wdd, nowayout);
+
+ err = watchdog_register_device(&wdd);
+ if (err)
+ goto out_hv_unreg;
+
+ pr_info("initialized (timeout=%ds, nowayout=%d)\n",
+ wdd.timeout, nowayout);
+
+ mdesc_release(handle);
+
+ return 0;
+
+out_hv_unreg:
+ sun4v_hvapi_unregister(HV_GRP_CORE);
+
+out_release:
+ mdesc_release(handle);
+ return err;
+}
+
+static void __exit sun4v_wdt_exit(void)
+{
+ sun4v_hvapi_unregister(HV_GRP_CORE);
+ watchdog_unregister_device(&wdd);
+}
+
+module_init(sun4v_wdt_init);
+module_exit(sun4v_wdt_exit);
+
+MODULE_AUTHOR("Wim Coekaerts <wim.coekaerts@oracle.com>");
+MODULE_DESCRIPTION("sun4v watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sunplus_wdt.c b/drivers/watchdog/sunplus_wdt.c
new file mode 100644
index 000000000..e2d8c532b
--- /dev/null
+++ b/drivers/watchdog/sunplus_wdt.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sunplus Watchdog Driver
+ *
+ * Copyright (C) 2021 Sunplus Technology Co., Ltd.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/watchdog.h>
+
+#define WDT_CTRL 0x00
+#define WDT_CNT 0x04
+
+#define WDT_STOP 0x3877
+#define WDT_RESUME 0x4A4B
+#define WDT_CLRIRQ 0x7482
+#define WDT_UNLOCK 0xAB00
+#define WDT_LOCK 0xAB01
+#define WDT_CONMAX 0xDEAF
+
+/* TIMEOUT_MAX = ffff0/90kHz =11.65, so longer than 11 seconds will time out. */
+#define SP_WDT_MAX_TIMEOUT 11U
+#define SP_WDT_DEFAULT_TIMEOUT 10
+
+#define STC_CLK 90000
+
+#define DEVICE_NAME "sunplus-wdt"
+
+static unsigned int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct sp_wdt_priv {
+ struct watchdog_device wdev;
+ void __iomem *base;
+ struct clk *clk;
+ struct reset_control *rstc;
+};
+
+static int sp_wdt_restart(struct watchdog_device *wdev,
+ unsigned long action, void *data)
+{
+ struct sp_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ void __iomem *base = priv->base;
+
+ writel(WDT_STOP, base + WDT_CTRL);
+ writel(WDT_UNLOCK, base + WDT_CTRL);
+ writel(0x0001, base + WDT_CNT);
+ writel(WDT_LOCK, base + WDT_CTRL);
+ writel(WDT_RESUME, base + WDT_CTRL);
+
+ return 0;
+}
+
+static int sp_wdt_ping(struct watchdog_device *wdev)
+{
+ struct sp_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ void __iomem *base = priv->base;
+ u32 count;
+
+ if (wdev->timeout > SP_WDT_MAX_TIMEOUT) {
+ /* WDT_CONMAX sets the count to the maximum (down-counting). */
+ writel(WDT_CONMAX, base + WDT_CTRL);
+ } else {
+ writel(WDT_UNLOCK, base + WDT_CTRL);
+ /*
+ * Watchdog timer is a 20-bit down-counting based on STC_CLK.
+ * This register bits[16:0] is from bit[19:4] of the watchdog
+ * timer counter.
+ */
+ count = (wdev->timeout * STC_CLK) >> 4;
+ writel(count, base + WDT_CNT);
+ writel(WDT_LOCK, base + WDT_CTRL);
+ }
+
+ return 0;
+}
+
+static int sp_wdt_stop(struct watchdog_device *wdev)
+{
+ struct sp_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ void __iomem *base = priv->base;
+
+ writel(WDT_STOP, base + WDT_CTRL);
+
+ return 0;
+}
+
+static int sp_wdt_start(struct watchdog_device *wdev)
+{
+ struct sp_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ void __iomem *base = priv->base;
+
+ writel(WDT_RESUME, base + WDT_CTRL);
+
+ return 0;
+}
+
+static unsigned int sp_wdt_get_timeleft(struct watchdog_device *wdev)
+{
+ struct sp_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ void __iomem *base = priv->base;
+ u32 val;
+
+ val = readl(base + WDT_CNT);
+ val &= 0xffff;
+ val = val << 4;
+
+ return val;
+}
+
+static const struct watchdog_info sp_wdt_info = {
+ .identity = DEVICE_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+};
+
+static const struct watchdog_ops sp_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sp_wdt_start,
+ .stop = sp_wdt_stop,
+ .ping = sp_wdt_ping,
+ .get_timeleft = sp_wdt_get_timeleft,
+ .restart = sp_wdt_restart,
+};
+
+static void sp_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static void sp_reset_control_assert(void *data)
+{
+ reset_control_assert(data);
+}
+
+static int sp_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sp_wdt_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->clk))
+ return dev_err_probe(dev, PTR_ERR(priv->clk), "Failed to get clock\n");
+
+ ret = clk_prepare_enable(priv->clk);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable clock\n");
+
+ ret = devm_add_action_or_reset(dev, sp_clk_disable_unprepare, priv->clk);
+ if (ret)
+ return ret;
+
+ /* The timer and watchdog shared the STC reset */
+ priv->rstc = devm_reset_control_get_shared(dev, NULL);
+ if (IS_ERR(priv->rstc))
+ return dev_err_probe(dev, PTR_ERR(priv->rstc), "Failed to get reset\n");
+
+ reset_control_deassert(priv->rstc);
+
+ ret = devm_add_action_or_reset(dev, sp_reset_control_assert, priv->rstc);
+ if (ret)
+ return ret;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->wdev.info = &sp_wdt_info;
+ priv->wdev.ops = &sp_wdt_ops;
+ priv->wdev.timeout = SP_WDT_DEFAULT_TIMEOUT;
+ priv->wdev.max_hw_heartbeat_ms = SP_WDT_MAX_TIMEOUT * 1000;
+ priv->wdev.min_timeout = 1;
+ priv->wdev.parent = dev;
+
+ watchdog_set_drvdata(&priv->wdev, priv);
+ watchdog_init_timeout(&priv->wdev, timeout, dev);
+ watchdog_set_nowayout(&priv->wdev, nowayout);
+ watchdog_stop_on_reboot(&priv->wdev);
+ watchdog_set_restart_priority(&priv->wdev, 128);
+
+ return devm_watchdog_register_device(dev, &priv->wdev);
+}
+
+static const struct of_device_id sp_wdt_of_match[] = {
+ {.compatible = "sunplus,sp7021-wdt", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sp_wdt_of_match);
+
+static struct platform_driver sp_wdt_driver = {
+ .probe = sp_wdt_probe,
+ .driver = {
+ .name = DEVICE_NAME,
+ .of_match_table = sp_wdt_of_match,
+ },
+};
+
+module_platform_driver(sp_wdt_driver);
+
+MODULE_AUTHOR("Xiantao Hu <xt.hu@cqplus1.com>");
+MODULE_DESCRIPTION("Sunplus Watchdog Timer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c
new file mode 100644
index 000000000..6cf82922d
--- /dev/null
+++ b/drivers/watchdog/sunxi_wdt.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * sunxi Watchdog Driver
+ *
+ * Copyright (c) 2013 Carlo Caione
+ * 2012 Henrik Nordstrom
+ *
+ * Based on xen_wdt.c
+ * (c) Copyright 2010 Novell, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#define WDT_MAX_TIMEOUT 16
+#define WDT_MIN_TIMEOUT 1
+#define WDT_TIMEOUT_MASK 0x0F
+
+#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1))
+
+#define WDT_MODE_EN (1 << 0)
+
+#define DRV_NAME "sunxi-wdt"
+#define DRV_VERSION "1.0"
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int timeout;
+
+/*
+ * This structure stores the register offsets for different variants
+ * of Allwinner's watchdog hardware.
+ */
+struct sunxi_wdt_reg {
+ u8 wdt_ctrl;
+ u8 wdt_cfg;
+ u8 wdt_mode;
+ u8 wdt_timeout_shift;
+ u8 wdt_reset_mask;
+ u8 wdt_reset_val;
+ u32 wdt_key_val;
+};
+
+struct sunxi_wdt_dev {
+ struct watchdog_device wdt_dev;
+ void __iomem *wdt_base;
+ const struct sunxi_wdt_reg *wdt_regs;
+};
+
+/*
+ * wdt_timeout_map maps the watchdog timer interval value in seconds to
+ * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
+ *
+ * [timeout seconds] = register value
+ *
+ */
+
+static const int wdt_timeout_map[] = {
+ [1] = 0x1, /* 1s */
+ [2] = 0x2, /* 2s */
+ [3] = 0x3, /* 3s */
+ [4] = 0x4, /* 4s */
+ [5] = 0x5, /* 5s */
+ [6] = 0x6, /* 6s */
+ [8] = 0x7, /* 8s */
+ [10] = 0x8, /* 10s */
+ [12] = 0x9, /* 12s */
+ [14] = 0xA, /* 14s */
+ [16] = 0xB, /* 16s */
+};
+
+
+static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
+ unsigned long action, void *data)
+{
+ struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = sunxi_wdt->wdt_base;
+ const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
+ u32 val;
+
+ /* Set system reset function */
+ val = readl(wdt_base + regs->wdt_cfg);
+ val &= ~(regs->wdt_reset_mask);
+ val |= regs->wdt_reset_val;
+ val |= regs->wdt_key_val;
+ writel(val, wdt_base + regs->wdt_cfg);
+
+ /* Set lowest timeout and enable watchdog */
+ val = readl(wdt_base + regs->wdt_mode);
+ val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
+ val |= WDT_MODE_EN;
+ val |= regs->wdt_key_val;
+ writel(val, wdt_base + regs->wdt_mode);
+
+ /*
+ * Restart the watchdog. The default (and lowest) interval
+ * value for the watchdog is 0.5s.
+ */
+ writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
+
+ while (1) {
+ mdelay(5);
+ val = readl(wdt_base + regs->wdt_mode);
+ val |= WDT_MODE_EN;
+ val |= regs->wdt_key_val;
+ writel(val, wdt_base + regs->wdt_mode);
+ }
+ return 0;
+}
+
+static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = sunxi_wdt->wdt_base;
+ const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
+
+ writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
+
+ return 0;
+}
+
+static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = sunxi_wdt->wdt_base;
+ const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
+ u32 reg;
+
+ if (wdt_timeout_map[timeout] == 0)
+ timeout++;
+
+ sunxi_wdt->wdt_dev.timeout = timeout;
+
+ reg = readl(wdt_base + regs->wdt_mode);
+ reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
+ reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
+ reg |= regs->wdt_key_val;
+ writel(reg, wdt_base + regs->wdt_mode);
+
+ sunxi_wdt_ping(wdt_dev);
+
+ return 0;
+}
+
+static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = sunxi_wdt->wdt_base;
+ const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
+
+ writel(regs->wdt_key_val, wdt_base + regs->wdt_mode);
+
+ return 0;
+}
+
+static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
+{
+ u32 reg;
+ struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
+ void __iomem *wdt_base = sunxi_wdt->wdt_base;
+ const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
+ int ret;
+
+ ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
+ sunxi_wdt->wdt_dev.timeout);
+ if (ret < 0)
+ return ret;
+
+ /* Set system reset function */
+ reg = readl(wdt_base + regs->wdt_cfg);
+ reg &= ~(regs->wdt_reset_mask);
+ reg |= regs->wdt_reset_val;
+ reg |= regs->wdt_key_val;
+ writel(reg, wdt_base + regs->wdt_cfg);
+
+ /* Enable watchdog */
+ reg = readl(wdt_base + regs->wdt_mode);
+ reg |= WDT_MODE_EN;
+ reg |= regs->wdt_key_val;
+ writel(reg, wdt_base + regs->wdt_mode);
+
+ return 0;
+}
+
+static const struct watchdog_info sunxi_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops sunxi_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = sunxi_wdt_start,
+ .stop = sunxi_wdt_stop,
+ .ping = sunxi_wdt_ping,
+ .set_timeout = sunxi_wdt_set_timeout,
+ .restart = sunxi_wdt_restart,
+};
+
+static const struct sunxi_wdt_reg sun4i_wdt_reg = {
+ .wdt_ctrl = 0x00,
+ .wdt_cfg = 0x04,
+ .wdt_mode = 0x04,
+ .wdt_timeout_shift = 3,
+ .wdt_reset_mask = 0x02,
+ .wdt_reset_val = 0x02,
+};
+
+static const struct sunxi_wdt_reg sun6i_wdt_reg = {
+ .wdt_ctrl = 0x10,
+ .wdt_cfg = 0x14,
+ .wdt_mode = 0x18,
+ .wdt_timeout_shift = 4,
+ .wdt_reset_mask = 0x03,
+ .wdt_reset_val = 0x01,
+};
+
+static const struct sunxi_wdt_reg sun20i_wdt_reg = {
+ .wdt_ctrl = 0x10,
+ .wdt_cfg = 0x14,
+ .wdt_mode = 0x18,
+ .wdt_timeout_shift = 4,
+ .wdt_reset_mask = 0x03,
+ .wdt_reset_val = 0x01,
+ .wdt_key_val = 0x16aa0000,
+};
+
+static const struct of_device_id sunxi_wdt_dt_ids[] = {
+ { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
+ { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
+ { .compatible = "allwinner,sun20i-d1-wdt", .data = &sun20i_wdt_reg },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
+
+static int sunxi_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sunxi_wdt_dev *sunxi_wdt;
+ int err;
+
+ sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
+ if (!sunxi_wdt)
+ return -ENOMEM;
+
+ sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
+ if (!sunxi_wdt->wdt_regs)
+ return -ENODEV;
+
+ sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(sunxi_wdt->wdt_base))
+ return PTR_ERR(sunxi_wdt->wdt_base);
+
+ sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
+ sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
+ sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
+ sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
+ sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
+ sunxi_wdt->wdt_dev.parent = dev;
+
+ watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
+ watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
+
+ watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
+
+ sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
+
+ watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
+ err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
+ if (unlikely(err))
+ return err;
+
+ dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
+ sunxi_wdt->wdt_dev.timeout, nowayout);
+
+ return 0;
+}
+
+static struct platform_driver sunxi_wdt_driver = {
+ .probe = sunxi_wdt_probe,
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = sunxi_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(sunxi_wdt_driver);
+
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
+
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+ "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
+MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
+MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
+MODULE_VERSION(DRV_VERSION);
diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c
new file mode 100644
index 000000000..d5de6c065
--- /dev/null
+++ b/drivers/watchdog/tegra_wdt.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+/* minimum and maximum watchdog trigger timeout, in seconds */
+#define MIN_WDT_TIMEOUT 1
+#define MAX_WDT_TIMEOUT 255
+
+/*
+ * Base of the WDT registers, from the timer base address. There are
+ * actually 5 watchdogs that can be configured (by pairing with an available
+ * timer), at bases 0x100 + (WDT ID) * 0x20, where WDT ID is 0 through 4.
+ * This driver only configures the first watchdog (WDT ID 0).
+ */
+#define WDT_BASE 0x100
+#define WDT_ID 0
+
+/*
+ * Register base of the timer that's selected for pairing with the watchdog.
+ * This driver arbitrarily uses timer 5, which is currently unused by
+ * other drivers (in particular, the Tegra clocksource driver). If this
+ * needs to change, take care that the new timer is not used by the
+ * clocksource driver.
+ */
+#define WDT_TIMER_BASE 0x60
+#define WDT_TIMER_ID 5
+
+/* WDT registers */
+#define WDT_CFG 0x0
+#define WDT_CFG_PERIOD_SHIFT 4
+#define WDT_CFG_PERIOD_MASK 0xff
+#define WDT_CFG_INT_EN (1 << 12)
+#define WDT_CFG_PMC2CAR_RST_EN (1 << 15)
+#define WDT_STS 0x4
+#define WDT_STS_COUNT_SHIFT 4
+#define WDT_STS_COUNT_MASK 0xff
+#define WDT_STS_EXP_SHIFT 12
+#define WDT_STS_EXP_MASK 0x3
+#define WDT_CMD 0x8
+#define WDT_CMD_START_COUNTER (1 << 0)
+#define WDT_CMD_DISABLE_COUNTER (1 << 1)
+#define WDT_UNLOCK (0xc)
+#define WDT_UNLOCK_PATTERN (0xc45a << 0)
+
+/* Timer registers */
+#define TIMER_PTV 0x0
+#define TIMER_EN (1 << 31)
+#define TIMER_PERIODIC (1 << 30)
+
+struct tegra_wdt {
+ struct watchdog_device wdd;
+ void __iomem *wdt_regs;
+ void __iomem *tmr_regs;
+};
+
+#define WDT_HEARTBEAT 120
+static int heartbeat = WDT_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeats in seconds. (default = "
+ __MODULE_STRING(WDT_HEARTBEAT) ")");
+
+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) ")");
+
+static int tegra_wdt_start(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 val;
+
+ /*
+ * This thing has a fixed 1MHz clock. Normally, we would set the
+ * period to 1 second by writing 1000000ul, but the watchdog system
+ * reset actually occurs on the 4th expiration of this counter,
+ * so we set the period to 1/4 of this amount.
+ */
+ val = 1000000ul / 4;
+ val |= (TIMER_EN | TIMER_PERIODIC);
+ writel(val, wdt->tmr_regs + TIMER_PTV);
+
+ /*
+ * Set number of periods and start counter.
+ *
+ * Interrupt handler is not required for user space
+ * WDT accesses, since the caller is responsible to ping the
+ * WDT to reset the counter before expiration, through ioctls.
+ */
+ val = WDT_TIMER_ID |
+ (wdd->timeout << WDT_CFG_PERIOD_SHIFT) |
+ WDT_CFG_PMC2CAR_RST_EN;
+ writel(val, wdt->wdt_regs + WDT_CFG);
+
+ writel(WDT_CMD_START_COUNTER, wdt->wdt_regs + WDT_CMD);
+
+ return 0;
+}
+
+static int tegra_wdt_stop(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDT_UNLOCK_PATTERN, wdt->wdt_regs + WDT_UNLOCK);
+ writel(WDT_CMD_DISABLE_COUNTER, wdt->wdt_regs + WDT_CMD);
+ writel(0, wdt->tmr_regs + TIMER_PTV);
+
+ return 0;
+}
+
+static int tegra_wdt_ping(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ writel(WDT_CMD_START_COUNTER, wdt->wdt_regs + WDT_CMD);
+
+ return 0;
+}
+
+static int tegra_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ wdd->timeout = timeout;
+
+ if (watchdog_active(wdd)) {
+ tegra_wdt_stop(wdd);
+ return tegra_wdt_start(wdd);
+ }
+
+ return 0;
+}
+
+static unsigned int tegra_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct tegra_wdt *wdt = watchdog_get_drvdata(wdd);
+ u32 val;
+ int count;
+ int exp;
+
+ val = readl(wdt->wdt_regs + WDT_STS);
+
+ /* Current countdown (from timeout) */
+ count = (val >> WDT_STS_COUNT_SHIFT) & WDT_STS_COUNT_MASK;
+
+ /* Number of expirations (we are waiting for the 4th expiration) */
+ exp = (val >> WDT_STS_EXP_SHIFT) & WDT_STS_EXP_MASK;
+
+ /*
+ * The entire thing is divided by 4 because we are ticking down 4 times
+ * faster due to needing to wait for the 4th expiration.
+ */
+ return (((3 - exp) * wdd->timeout) + count) / 4;
+}
+
+static const struct watchdog_info tegra_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 0,
+ .identity = "Tegra Watchdog",
+};
+
+static const struct watchdog_ops tegra_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = tegra_wdt_start,
+ .stop = tegra_wdt_stop,
+ .ping = tegra_wdt_ping,
+ .set_timeout = tegra_wdt_set_timeout,
+ .get_timeleft = tegra_wdt_get_timeleft,
+};
+
+static int tegra_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdd;
+ struct tegra_wdt *wdt;
+ void __iomem *regs;
+ int ret;
+
+ /* This is the timer base. */
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ /*
+ * Allocate our watchdog driver data, which has the
+ * struct watchdog_device nested within it.
+ */
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ /* Initialize struct tegra_wdt. */
+ wdt->wdt_regs = regs + WDT_BASE;
+ wdt->tmr_regs = regs + WDT_TIMER_BASE;
+
+ /* Initialize struct watchdog_device. */
+ wdd = &wdt->wdd;
+ wdd->timeout = heartbeat;
+ wdd->info = &tegra_wdt_info;
+ wdd->ops = &tegra_wdt_ops;
+ wdd->min_timeout = MIN_WDT_TIMEOUT;
+ wdd->max_timeout = MAX_WDT_TIMEOUT;
+ wdd->parent = dev;
+
+ watchdog_set_drvdata(wdd, wdt);
+
+ watchdog_set_nowayout(wdd, nowayout);
+
+ watchdog_stop_on_unregister(wdd);
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+
+ dev_info(dev, "initialized (heartbeat = %d sec, nowayout = %d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+}
+
+static int tegra_wdt_suspend(struct device *dev)
+{
+ struct tegra_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ tegra_wdt_stop(&wdt->wdd);
+
+ return 0;
+}
+
+static int tegra_wdt_resume(struct device *dev)
+{
+ struct tegra_wdt *wdt = dev_get_drvdata(dev);
+
+ if (watchdog_active(&wdt->wdd))
+ tegra_wdt_start(&wdt->wdd);
+
+ return 0;
+}
+
+static const struct of_device_id tegra_wdt_of_match[] = {
+ { .compatible = "nvidia,tegra30-timer", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, tegra_wdt_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(tegra_wdt_pm_ops,
+ tegra_wdt_suspend, tegra_wdt_resume);
+
+static struct platform_driver tegra_wdt_driver = {
+ .probe = tegra_wdt_probe,
+ .driver = {
+ .name = "tegra-wdt",
+ .pm = pm_sleep_ptr(&tegra_wdt_pm_ops),
+ .of_match_table = tegra_wdt_of_match,
+ },
+};
+module_platform_driver(tegra_wdt_driver);
+
+MODULE_AUTHOR("NVIDIA Corporation");
+MODULE_DESCRIPTION("Tegra Watchdog Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/tqmx86_wdt.c b/drivers/watchdog/tqmx86_wdt.c
new file mode 100644
index 000000000..83860e94c
--- /dev/null
+++ b/drivers/watchdog/tqmx86_wdt.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for TQMx86 PLD.
+ *
+ * The watchdog supports power of 2 timeouts from 1 to 4096sec.
+ * Once started, it cannot be stopped.
+ *
+ * Based on the vendor code written by Vadim V.Vlasov
+ * <vvlasov@dev.rtsoft.ru>
+ */
+
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/timer.h>
+#include <linux/watchdog.h>
+
+/* default timeout (secs) */
+#define WDT_TIMEOUT 32
+
+static unsigned int timeout;
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. (1<=timeout<=4096, default="
+ __MODULE_STRING(WDT_TIMEOUT) ")");
+struct tqmx86_wdt {
+ struct watchdog_device wdd;
+ void __iomem *io_base;
+};
+
+#define TQMX86_WDCFG 0x00 /* Watchdog Configuration Register */
+#define TQMX86_WDCS 0x01 /* Watchdog Config/Status Register */
+
+static int tqmx86_wdt_start(struct watchdog_device *wdd)
+{
+ struct tqmx86_wdt *priv = watchdog_get_drvdata(wdd);
+
+ iowrite8(0x81, priv->io_base + TQMX86_WDCS);
+
+ return 0;
+}
+
+static int tqmx86_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
+{
+ struct tqmx86_wdt *priv = watchdog_get_drvdata(wdd);
+ u8 val;
+
+ t = roundup_pow_of_two(t);
+ val = ilog2(t) | 0x90;
+ val += 3; /* values 0,1,2 correspond to 0.125,0.25,0.5s timeouts */
+ iowrite8(val, priv->io_base + TQMX86_WDCFG);
+
+ wdd->timeout = t;
+
+ return 0;
+}
+
+static const struct watchdog_info tqmx86_wdt_info = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING,
+ .identity = "TQMx86 Watchdog",
+};
+
+static const struct watchdog_ops tqmx86_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = tqmx86_wdt_start,
+ .set_timeout = tqmx86_wdt_set_timeout,
+};
+
+static int tqmx86_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tqmx86_wdt *priv;
+ struct resource *res;
+ int err;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res)
+ return -ENODEV;
+
+ priv->io_base = devm_ioport_map(dev, res->start, resource_size(res));
+ if (!priv->io_base)
+ return -ENOMEM;
+
+ watchdog_set_drvdata(&priv->wdd, priv);
+
+ priv->wdd.parent = dev;
+ priv->wdd.info = &tqmx86_wdt_info;
+ priv->wdd.ops = &tqmx86_wdt_ops;
+ priv->wdd.min_timeout = 1;
+ priv->wdd.max_timeout = 4096;
+ priv->wdd.max_hw_heartbeat_ms = 4096*1000;
+ priv->wdd.timeout = WDT_TIMEOUT;
+
+ watchdog_init_timeout(&priv->wdd, timeout, dev);
+ watchdog_set_nowayout(&priv->wdd, WATCHDOG_NOWAYOUT);
+
+ tqmx86_wdt_set_timeout(&priv->wdd, priv->wdd.timeout);
+
+ err = devm_watchdog_register_device(dev, &priv->wdd);
+ if (err)
+ return err;
+
+ dev_info(dev, "TQMx86 watchdog\n");
+
+ return 0;
+}
+
+static struct platform_driver tqmx86_wdt_driver = {
+ .driver = {
+ .name = "tqmx86-wdt",
+ },
+ .probe = tqmx86_wdt_probe,
+};
+
+module_platform_driver(tqmx86_wdt_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_DESCRIPTION("TQMx86 Watchdog");
+MODULE_ALIAS("platform:tqmx86-wdt");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/ts4800_wdt.c b/drivers/watchdog/ts4800_wdt.c
new file mode 100644
index 000000000..0ea554c7c
--- /dev/null
+++ b/drivers/watchdog/ts4800_wdt.c
@@ -0,0 +1,206 @@
+/*
+ * Watchdog driver for TS-4800 based boards
+ *
+ * Copyright (c) 2015 - Savoir-faire Linux
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+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) ")");
+
+/* possible feed values */
+#define TS4800_WDT_FEED_2S 0x1
+#define TS4800_WDT_FEED_10S 0x2
+#define TS4800_WDT_DISABLE 0x3
+
+struct ts4800_wdt {
+ struct watchdog_device wdd;
+ struct regmap *regmap;
+ u32 feed_offset;
+ u32 feed_val;
+};
+
+/*
+ * TS-4800 supports the following timeout values:
+ *
+ * value desc
+ * ---------------------
+ * 0 feed for 338ms
+ * 1 feed for 2.706s
+ * 2 feed for 10.824s
+ * 3 disable watchdog
+ *
+ * Keep the regmap/timeout map ordered by timeout
+ */
+static const struct {
+ const int timeout;
+ const int regval;
+} ts4800_wdt_map[] = {
+ { 2, TS4800_WDT_FEED_2S },
+ { 10, TS4800_WDT_FEED_10S },
+};
+
+#define MAX_TIMEOUT_INDEX (ARRAY_SIZE(ts4800_wdt_map) - 1)
+
+static void ts4800_write_feed(struct ts4800_wdt *wdt, u32 val)
+{
+ regmap_write(wdt->regmap, wdt->feed_offset, val);
+}
+
+static int ts4800_wdt_start(struct watchdog_device *wdd)
+{
+ struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ ts4800_write_feed(wdt, wdt->feed_val);
+ return 0;
+}
+
+static int ts4800_wdt_stop(struct watchdog_device *wdd)
+{
+ struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ ts4800_write_feed(wdt, TS4800_WDT_DISABLE);
+ return 0;
+}
+
+static int ts4800_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct ts4800_wdt *wdt = watchdog_get_drvdata(wdd);
+ int i;
+
+ for (i = 0; i < MAX_TIMEOUT_INDEX; i++) {
+ if (ts4800_wdt_map[i].timeout >= timeout)
+ break;
+ }
+
+ wdd->timeout = ts4800_wdt_map[i].timeout;
+ wdt->feed_val = ts4800_wdt_map[i].regval;
+
+ return 0;
+}
+
+static const struct watchdog_ops ts4800_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ts4800_wdt_start,
+ .stop = ts4800_wdt_stop,
+ .set_timeout = ts4800_wdt_set_timeout,
+};
+
+static const struct watchdog_info ts4800_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "TS-4800 Watchdog",
+};
+
+static int ts4800_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *syscon_np;
+ struct watchdog_device *wdd;
+ struct ts4800_wdt *wdt;
+ u32 reg;
+ int ret;
+
+ syscon_np = of_parse_phandle(np, "syscon", 0);
+ if (!syscon_np) {
+ dev_err(dev, "no syscon property\n");
+ return -ENODEV;
+ }
+
+ ret = of_property_read_u32_index(np, "syscon", 1, &reg);
+ if (ret < 0) {
+ dev_err(dev, "no offset in syscon\n");
+ of_node_put(syscon_np);
+ return ret;
+ }
+
+ /* allocate memory for watchdog struct */
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt) {
+ of_node_put(syscon_np);
+ return -ENOMEM;
+ }
+
+ /* set regmap and offset to know where to write */
+ wdt->feed_offset = reg;
+ wdt->regmap = syscon_node_to_regmap(syscon_np);
+ of_node_put(syscon_np);
+ if (IS_ERR(wdt->regmap)) {
+ dev_err(dev, "cannot get parent's regmap\n");
+ return PTR_ERR(wdt->regmap);
+ }
+
+ /* Initialize struct watchdog_device */
+ wdd = &wdt->wdd;
+ wdd->parent = dev;
+ wdd->info = &ts4800_wdt_info;
+ wdd->ops = &ts4800_wdt_ops;
+ wdd->min_timeout = ts4800_wdt_map[0].timeout;
+ wdd->max_timeout = ts4800_wdt_map[MAX_TIMEOUT_INDEX].timeout;
+
+ watchdog_set_drvdata(wdd, wdt);
+ watchdog_set_nowayout(wdd, nowayout);
+ watchdog_init_timeout(wdd, 0, dev);
+
+ /*
+ * As this watchdog supports only a few values, ts4800_wdt_set_timeout
+ * must be called to initialize timeout and feed_val with valid values.
+ * Default to maximum timeout if none, or an invalid one, is provided in
+ * device tree.
+ */
+ if (!wdd->timeout)
+ wdd->timeout = wdd->max_timeout;
+ ts4800_wdt_set_timeout(wdd, wdd->timeout);
+
+ /*
+ * The feed register is write-only, so it is not possible to determine
+ * watchdog's state. Disable it to be in a known state.
+ */
+ ts4800_wdt_stop(wdd);
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdt);
+
+ dev_info(dev, "initialized (timeout = %d sec, nowayout = %d)\n",
+ wdd->timeout, nowayout);
+
+ return 0;
+}
+
+static const struct of_device_id ts4800_wdt_of_match[] = {
+ { .compatible = "technologic,ts4800-wdt", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ts4800_wdt_of_match);
+
+static struct platform_driver ts4800_wdt_driver = {
+ .probe = ts4800_wdt_probe,
+ .driver = {
+ .name = "ts4800_wdt",
+ .of_match_table = ts4800_wdt_of_match,
+ },
+};
+
+module_platform_driver(ts4800_wdt_driver);
+
+MODULE_AUTHOR("Damien Riegel <damien.riegel@savoirfairelinux.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ts4800_wdt");
diff --git a/drivers/watchdog/ts72xx_wdt.c b/drivers/watchdog/ts72xx_wdt.c
new file mode 100644
index 000000000..bf918f5fa
--- /dev/null
+++ b/drivers/watchdog/ts72xx_wdt.c
@@ -0,0 +1,177 @@
+/*
+ * Watchdog driver for Technologic Systems TS-72xx based SBCs
+ * (TS-7200, TS-7250 and TS-7260). These boards have external
+ * glue logic CPLD chip, which includes programmable watchdog
+ * timer.
+ *
+ * Copyright (c) 2009 Mika Westerberg <mika.westerberg@iki.fi>
+ *
+ * This driver is based on ep93xx_wdt and wm831x_wdt drivers.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/io.h>
+
+#define TS72XX_WDT_DEFAULT_TIMEOUT 30
+
+static int timeout;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds.");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+/* priv->control_reg */
+#define TS72XX_WDT_CTRL_DISABLE 0x00
+#define TS72XX_WDT_CTRL_250MS 0x01
+#define TS72XX_WDT_CTRL_500MS 0x02
+#define TS72XX_WDT_CTRL_1SEC 0x03
+#define TS72XX_WDT_CTRL_RESERVED 0x04
+#define TS72XX_WDT_CTRL_2SEC 0x05
+#define TS72XX_WDT_CTRL_4SEC 0x06
+#define TS72XX_WDT_CTRL_8SEC 0x07
+
+/* priv->feed_reg */
+#define TS72XX_WDT_FEED_VAL 0x05
+
+struct ts72xx_wdt_priv {
+ void __iomem *control_reg;
+ void __iomem *feed_reg;
+ struct watchdog_device wdd;
+ unsigned char regval;
+};
+
+static int ts72xx_wdt_start(struct watchdog_device *wdd)
+{
+ struct ts72xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writeb(TS72XX_WDT_FEED_VAL, priv->feed_reg);
+ writeb(priv->regval, priv->control_reg);
+
+ return 0;
+}
+
+static int ts72xx_wdt_stop(struct watchdog_device *wdd)
+{
+ struct ts72xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writeb(TS72XX_WDT_FEED_VAL, priv->feed_reg);
+ writeb(TS72XX_WDT_CTRL_DISABLE, priv->control_reg);
+
+ return 0;
+}
+
+static int ts72xx_wdt_ping(struct watchdog_device *wdd)
+{
+ struct ts72xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writeb(TS72XX_WDT_FEED_VAL, priv->feed_reg);
+
+ return 0;
+}
+
+static int ts72xx_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
+{
+ struct ts72xx_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ if (to == 1) {
+ priv->regval = TS72XX_WDT_CTRL_1SEC;
+ } else if (to == 2) {
+ priv->regval = TS72XX_WDT_CTRL_2SEC;
+ } else if (to <= 4) {
+ priv->regval = TS72XX_WDT_CTRL_4SEC;
+ to = 4;
+ } else {
+ priv->regval = TS72XX_WDT_CTRL_8SEC;
+ if (to <= 8)
+ to = 8;
+ }
+
+ wdd->timeout = to;
+
+ if (watchdog_active(wdd)) {
+ ts72xx_wdt_stop(wdd);
+ ts72xx_wdt_start(wdd);
+ }
+
+ return 0;
+}
+
+static const struct watchdog_info ts72xx_wdt_ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "TS-72XX WDT",
+};
+
+static const struct watchdog_ops ts72xx_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ts72xx_wdt_start,
+ .stop = ts72xx_wdt_stop,
+ .ping = ts72xx_wdt_ping,
+ .set_timeout = ts72xx_wdt_settimeout,
+};
+
+static int ts72xx_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ts72xx_wdt_priv *priv;
+ struct watchdog_device *wdd;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->control_reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->control_reg))
+ return PTR_ERR(priv->control_reg);
+
+ priv->feed_reg = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(priv->feed_reg))
+ return PTR_ERR(priv->feed_reg);
+
+ wdd = &priv->wdd;
+ wdd->info = &ts72xx_wdt_ident;
+ wdd->ops = &ts72xx_wdt_ops;
+ wdd->min_timeout = 1;
+ wdd->max_hw_heartbeat_ms = 8000;
+ wdd->parent = dev;
+
+ watchdog_set_nowayout(wdd, nowayout);
+
+ wdd->timeout = TS72XX_WDT_DEFAULT_TIMEOUT;
+ watchdog_init_timeout(wdd, timeout, dev);
+
+ watchdog_set_drvdata(wdd, priv);
+
+ ret = devm_watchdog_register_device(dev, wdd);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "TS-72xx Watchdog driver\n");
+
+ return 0;
+}
+
+static struct platform_driver ts72xx_wdt_driver = {
+ .probe = ts72xx_wdt_probe,
+ .driver = {
+ .name = "ts72xx-wdt",
+ },
+};
+
+module_platform_driver(ts72xx_wdt_driver);
+
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>");
+MODULE_DESCRIPTION("TS-72xx SBC Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ts72xx-wdt");
diff --git a/drivers/watchdog/twl4030_wdt.c b/drivers/watchdog/twl4030_wdt.c
new file mode 100644
index 000000000..36b4a6609
--- /dev/null
+++ b/drivers/watchdog/twl4030_wdt.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) Nokia Corporation
+ *
+ * Written by Timo Kokkonen <timo.t.kokkonen at nokia.com>
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/twl.h>
+
+#define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3
+
+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) ")");
+
+static int twl4030_wdt_write(unsigned char val)
+{
+ return twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, val,
+ TWL4030_WATCHDOG_CFG_REG_OFFS);
+}
+
+static int twl4030_wdt_start(struct watchdog_device *wdt)
+{
+ return twl4030_wdt_write(wdt->timeout + 1);
+}
+
+static int twl4030_wdt_stop(struct watchdog_device *wdt)
+{
+ return twl4030_wdt_write(0);
+}
+
+static int twl4030_wdt_set_timeout(struct watchdog_device *wdt,
+ unsigned int timeout)
+{
+ wdt->timeout = timeout;
+ return 0;
+}
+
+static const struct watchdog_info twl4030_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "TWL4030 Watchdog",
+};
+
+static const struct watchdog_ops twl4030_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = twl4030_wdt_start,
+ .stop = twl4030_wdt_stop,
+ .set_timeout = twl4030_wdt_set_timeout,
+};
+
+static int twl4030_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct watchdog_device *wdt;
+
+ wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->info = &twl4030_wdt_info;
+ wdt->ops = &twl4030_wdt_ops;
+ wdt->status = 0;
+ wdt->timeout = 30;
+ wdt->min_timeout = 1;
+ wdt->max_timeout = 30;
+ wdt->parent = dev;
+
+ watchdog_set_nowayout(wdt, nowayout);
+ platform_set_drvdata(pdev, wdt);
+
+ twl4030_wdt_stop(wdt);
+
+ return devm_watchdog_register_device(dev, wdt);
+}
+
+#ifdef CONFIG_PM
+static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct watchdog_device *wdt = platform_get_drvdata(pdev);
+ if (watchdog_active(wdt))
+ return twl4030_wdt_stop(wdt);
+
+ return 0;
+}
+
+static int twl4030_wdt_resume(struct platform_device *pdev)
+{
+ struct watchdog_device *wdt = platform_get_drvdata(pdev);
+ if (watchdog_active(wdt))
+ return twl4030_wdt_start(wdt);
+
+ return 0;
+}
+#else
+#define twl4030_wdt_suspend NULL
+#define twl4030_wdt_resume NULL
+#endif
+
+static const struct of_device_id twl_wdt_of_match[] = {
+ { .compatible = "ti,twl4030-wdt", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, twl_wdt_of_match);
+
+static struct platform_driver twl4030_wdt_driver = {
+ .probe = twl4030_wdt_probe,
+ .suspend = twl4030_wdt_suspend,
+ .resume = twl4030_wdt_resume,
+ .driver = {
+ .name = "twl4030_wdt",
+ .of_match_table = twl_wdt_of_match,
+ },
+};
+
+module_platform_driver(twl4030_wdt_driver);
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:twl4030_wdt");
+
diff --git a/drivers/watchdog/txx9wdt.c b/drivers/watchdog/txx9wdt.c
new file mode 100644
index 000000000..89a54b664
--- /dev/null
+++ b/drivers/watchdog/txx9wdt.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * txx9wdt: A Hardware Watchdog Driver for TXx9 SoCs
+ *
+ * Copyright (C) 2007 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <asm/txx9tmr.h>
+
+#define WD_TIMER_CCD 7 /* 1/256 */
+#define WD_TIMER_CLK (clk_get_rate(txx9_imclk) / (2 << WD_TIMER_CCD))
+#define WD_MAX_TIMEOUT ((0xffffffff >> (32 - TXX9_TIMER_BITS)) / WD_TIMER_CLK)
+#define TIMER_MARGIN 60 /* Default is 60 seconds */
+
+static unsigned int timeout = TIMER_MARGIN; /* in seconds */
+module_param(timeout, uint, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. "
+ "(0<timeout<((2^" __MODULE_STRING(TXX9_TIMER_BITS) ")/(IMCLK/256)), "
+ "default=" __MODULE_STRING(TIMER_MARGIN) ")");
+
+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) ")");
+
+static struct txx9_tmr_reg __iomem *txx9wdt_reg;
+static struct clk *txx9_imclk;
+static DEFINE_SPINLOCK(txx9_lock);
+
+static int txx9wdt_ping(struct watchdog_device *wdt_dev)
+{
+ spin_lock(&txx9_lock);
+ __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr);
+ spin_unlock(&txx9_lock);
+ return 0;
+}
+
+static int txx9wdt_start(struct watchdog_device *wdt_dev)
+{
+ spin_lock(&txx9_lock);
+ __raw_writel(WD_TIMER_CLK * wdt_dev->timeout, &txx9wdt_reg->cpra);
+ __raw_writel(WD_TIMER_CCD, &txx9wdt_reg->ccdr);
+ __raw_writel(0, &txx9wdt_reg->tisr); /* clear pending interrupt */
+ __raw_writel(TXx9_TMTCR_TCE | TXx9_TMTCR_CCDE | TXx9_TMTCR_TMODE_WDOG,
+ &txx9wdt_reg->tcr);
+ __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr);
+ spin_unlock(&txx9_lock);
+ return 0;
+}
+
+static int txx9wdt_stop(struct watchdog_device *wdt_dev)
+{
+ spin_lock(&txx9_lock);
+ __raw_writel(TXx9_TMWTMR_WDIS, &txx9wdt_reg->wtmr);
+ __raw_writel(__raw_readl(&txx9wdt_reg->tcr) & ~TXx9_TMTCR_TCE,
+ &txx9wdt_reg->tcr);
+ spin_unlock(&txx9_lock);
+ return 0;
+}
+
+static int txx9wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ wdt_dev->timeout = new_timeout;
+ txx9wdt_stop(wdt_dev);
+ txx9wdt_start(wdt_dev);
+ return 0;
+}
+
+static const struct watchdog_info txx9wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "Hardware Watchdog for TXx9",
+};
+
+static const struct watchdog_ops txx9wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = txx9wdt_start,
+ .stop = txx9wdt_stop,
+ .ping = txx9wdt_ping,
+ .set_timeout = txx9wdt_set_timeout,
+};
+
+static struct watchdog_device txx9wdt = {
+ .info = &txx9wdt_info,
+ .ops = &txx9wdt_ops,
+};
+
+static int __init txx9wdt_probe(struct platform_device *dev)
+{
+ int ret;
+
+ txx9_imclk = clk_get(NULL, "imbus_clk");
+ if (IS_ERR(txx9_imclk)) {
+ ret = PTR_ERR(txx9_imclk);
+ txx9_imclk = NULL;
+ goto exit;
+ }
+ ret = clk_prepare_enable(txx9_imclk);
+ if (ret) {
+ clk_put(txx9_imclk);
+ txx9_imclk = NULL;
+ goto exit;
+ }
+
+ txx9wdt_reg = devm_platform_ioremap_resource(dev, 0);
+ if (IS_ERR(txx9wdt_reg)) {
+ ret = PTR_ERR(txx9wdt_reg);
+ goto exit;
+ }
+
+ if (timeout < 1 || timeout > WD_MAX_TIMEOUT)
+ timeout = TIMER_MARGIN;
+ txx9wdt.timeout = timeout;
+ txx9wdt.min_timeout = 1;
+ txx9wdt.max_timeout = WD_MAX_TIMEOUT;
+ txx9wdt.parent = &dev->dev;
+ watchdog_set_nowayout(&txx9wdt, nowayout);
+
+ ret = watchdog_register_device(&txx9wdt);
+ if (ret)
+ goto exit;
+
+ pr_info("Hardware Watchdog Timer: timeout=%d sec (max %ld) (nowayout= %d)\n",
+ timeout, WD_MAX_TIMEOUT, nowayout);
+
+ return 0;
+exit:
+ if (txx9_imclk) {
+ clk_disable_unprepare(txx9_imclk);
+ clk_put(txx9_imclk);
+ }
+ return ret;
+}
+
+static int __exit txx9wdt_remove(struct platform_device *dev)
+{
+ watchdog_unregister_device(&txx9wdt);
+ clk_disable_unprepare(txx9_imclk);
+ clk_put(txx9_imclk);
+ return 0;
+}
+
+static void txx9wdt_shutdown(struct platform_device *dev)
+{
+ txx9wdt_stop(&txx9wdt);
+}
+
+static struct platform_driver txx9wdt_driver = {
+ .remove = __exit_p(txx9wdt_remove),
+ .shutdown = txx9wdt_shutdown,
+ .driver = {
+ .name = "txx9wdt",
+ },
+};
+
+module_platform_driver_probe(txx9wdt_driver, txx9wdt_probe);
+
+MODULE_DESCRIPTION("TXx9 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:txx9wdt");
diff --git a/drivers/watchdog/uniphier_wdt.c b/drivers/watchdog/uniphier_wdt.c
new file mode 100644
index 000000000..8e9242c23
--- /dev/null
+++ b/drivers/watchdog/uniphier_wdt.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Watchdog driver for the UniPhier watchdog timer
+ *
+ * (c) Copyright 2014 Panasonic Corporation
+ * (c) Copyright 2016 Socionext Inc.
+ * All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+/* WDT timer setting register */
+#define WDTTIMSET 0x3004
+#define WDTTIMSET_PERIOD_MASK (0xf << 0)
+#define WDTTIMSET_PERIOD_1_SEC (0x3 << 0)
+
+/* WDT reset selection register */
+#define WDTRSTSEL 0x3008
+#define WDTRSTSEL_RSTSEL_MASK (0x3 << 0)
+#define WDTRSTSEL_RSTSEL_BOTH (0x0 << 0)
+#define WDTRSTSEL_RSTSEL_IRQ_ONLY (0x2 << 0)
+
+/* WDT control register */
+#define WDTCTRL 0x300c
+#define WDTCTRL_STATUS BIT(8)
+#define WDTCTRL_CLEAR BIT(1)
+#define WDTCTRL_ENABLE BIT(0)
+
+#define SEC_TO_WDTTIMSET_PRD(sec) \
+ (ilog2(sec) + WDTTIMSET_PERIOD_1_SEC)
+
+#define WDTST_TIMEOUT 1000 /* usec */
+
+#define WDT_DEFAULT_TIMEOUT 64 /* Default is 64 seconds */
+#define WDT_PERIOD_MIN 1
+#define WDT_PERIOD_MAX 128
+
+static unsigned int timeout = 0;
+static bool nowayout = WATCHDOG_NOWAYOUT;
+
+struct uniphier_wdt_dev {
+ struct watchdog_device wdt_dev;
+ struct regmap *regmap;
+};
+
+/*
+ * UniPhier Watchdog operations
+ */
+static int uniphier_watchdog_ping(struct watchdog_device *w)
+{
+ struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w);
+ unsigned int val;
+ int ret;
+
+ /* Clear counter */
+ ret = regmap_write_bits(wdev->regmap, WDTCTRL,
+ WDTCTRL_CLEAR, WDTCTRL_CLEAR);
+ if (!ret)
+ /*
+ * As SoC specification, after clear counter,
+ * it needs to wait until counter status is 1.
+ */
+ ret = regmap_read_poll_timeout(wdev->regmap, WDTCTRL, val,
+ (val & WDTCTRL_STATUS),
+ 0, WDTST_TIMEOUT);
+
+ return ret;
+}
+
+static int __uniphier_watchdog_start(struct regmap *regmap, unsigned int sec)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read_poll_timeout(regmap, WDTCTRL, val,
+ !(val & WDTCTRL_STATUS),
+ 0, WDTST_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* Setup period */
+ ret = regmap_write(regmap, WDTTIMSET,
+ SEC_TO_WDTTIMSET_PRD(sec));
+ if (ret)
+ return ret;
+
+ /* Enable and clear watchdog */
+ ret = regmap_write(regmap, WDTCTRL, WDTCTRL_ENABLE | WDTCTRL_CLEAR);
+ if (!ret)
+ /*
+ * As SoC specification, after clear counter,
+ * it needs to wait until counter status is 1.
+ */
+ ret = regmap_read_poll_timeout(regmap, WDTCTRL, val,
+ (val & WDTCTRL_STATUS),
+ 0, WDTST_TIMEOUT);
+
+ return ret;
+}
+
+static int __uniphier_watchdog_stop(struct regmap *regmap)
+{
+ /* Disable and stop watchdog */
+ return regmap_write_bits(regmap, WDTCTRL, WDTCTRL_ENABLE, 0);
+}
+
+static int __uniphier_watchdog_restart(struct regmap *regmap, unsigned int sec)
+{
+ int ret;
+
+ ret = __uniphier_watchdog_stop(regmap);
+ if (ret)
+ return ret;
+
+ return __uniphier_watchdog_start(regmap, sec);
+}
+
+static int uniphier_watchdog_start(struct watchdog_device *w)
+{
+ struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w);
+ unsigned int tmp_timeout;
+
+ tmp_timeout = roundup_pow_of_two(w->timeout);
+
+ return __uniphier_watchdog_start(wdev->regmap, tmp_timeout);
+}
+
+static int uniphier_watchdog_stop(struct watchdog_device *w)
+{
+ struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w);
+
+ return __uniphier_watchdog_stop(wdev->regmap);
+}
+
+static int uniphier_watchdog_set_timeout(struct watchdog_device *w,
+ unsigned int t)
+{
+ struct uniphier_wdt_dev *wdev = watchdog_get_drvdata(w);
+ unsigned int tmp_timeout;
+ int ret;
+
+ tmp_timeout = roundup_pow_of_two(t);
+ if (tmp_timeout == w->timeout)
+ return 0;
+
+ if (watchdog_active(w)) {
+ ret = __uniphier_watchdog_restart(wdev->regmap, tmp_timeout);
+ if (ret)
+ return ret;
+ }
+
+ w->timeout = tmp_timeout;
+
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+static const struct watchdog_info uniphier_wdt_info = {
+ .identity = "uniphier-wdt",
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE |
+ WDIOF_OVERHEAT,
+};
+
+static const struct watchdog_ops uniphier_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = uniphier_watchdog_start,
+ .stop = uniphier_watchdog_stop,
+ .ping = uniphier_watchdog_ping,
+ .set_timeout = uniphier_watchdog_set_timeout,
+};
+
+static int uniphier_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct uniphier_wdt_dev *wdev;
+ struct regmap *regmap;
+ struct device_node *parent;
+ int ret;
+
+ wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL);
+ if (!wdev)
+ return -ENOMEM;
+
+ parent = of_get_parent(dev->of_node); /* parent should be syscon node */
+ regmap = syscon_node_to_regmap(parent);
+ of_node_put(parent);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ wdev->regmap = regmap;
+ wdev->wdt_dev.info = &uniphier_wdt_info;
+ wdev->wdt_dev.ops = &uniphier_wdt_ops;
+ wdev->wdt_dev.max_timeout = WDT_PERIOD_MAX;
+ wdev->wdt_dev.min_timeout = WDT_PERIOD_MIN;
+ wdev->wdt_dev.timeout = WDT_DEFAULT_TIMEOUT;
+ wdev->wdt_dev.parent = dev;
+
+ watchdog_init_timeout(&wdev->wdt_dev, timeout, dev);
+ watchdog_set_nowayout(&wdev->wdt_dev, nowayout);
+ watchdog_stop_on_reboot(&wdev->wdt_dev);
+
+ watchdog_set_drvdata(&wdev->wdt_dev, wdev);
+
+ uniphier_watchdog_stop(&wdev->wdt_dev);
+ ret = regmap_write(wdev->regmap, WDTRSTSEL, WDTRSTSEL_RSTSEL_BOTH);
+ if (ret)
+ return ret;
+
+ ret = devm_watchdog_register_device(dev, &wdev->wdt_dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "watchdog driver (timeout=%d sec, nowayout=%d)\n",
+ wdev->wdt_dev.timeout, nowayout);
+
+ return 0;
+}
+
+static const struct of_device_id uniphier_wdt_dt_ids[] = {
+ { .compatible = "socionext,uniphier-wdt" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, uniphier_wdt_dt_ids);
+
+static struct platform_driver uniphier_wdt_driver = {
+ .probe = uniphier_wdt_probe,
+ .driver = {
+ .name = "uniphier-wdt",
+ .of_match_table = uniphier_wdt_dt_ids,
+ },
+};
+
+module_platform_driver(uniphier_wdt_driver);
+
+module_param(timeout, uint, 0000);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout seconds in power of 2. (0 < timeout < 128, default="
+ __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
+
+module_param(nowayout, bool, 0000);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>");
+MODULE_DESCRIPTION("UniPhier Watchdog Device Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c
new file mode 100644
index 000000000..eeb39f96e
--- /dev/null
+++ b/drivers/watchdog/via_wdt.c
@@ -0,0 +1,261 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VIA Chipset Watchdog Driver
+ *
+ * Copyright (C) 2011 Sigfox
+ * Author: Marc Vertes <marc.vertes@sigfox.com>
+ * Based on a preliminary version from Harald Welte <HaraldWelte@viatech.com>
+ * Timer code by Wim Van Sebroeck <wim@iguana.be>
+ *
+ * Caveat: PnP must be enabled in BIOS to allow full access to watchdog
+ * control registers. If not, the watchdog must be configured in BIOS manually.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/timer.h>
+#include <linux/watchdog.h>
+
+/* Configuration registers relative to the pci device */
+#define VIA_WDT_MMIO_BASE 0xe8 /* MMIO region base address */
+#define VIA_WDT_CONF 0xec /* watchdog enable state */
+
+/* Relevant bits for the VIA_WDT_CONF register */
+#define VIA_WDT_CONF_ENABLE 0x01 /* 1: enable watchdog */
+#define VIA_WDT_CONF_MMIO 0x02 /* 1: enable watchdog MMIO */
+
+/*
+ * The MMIO region contains the watchdog control register and the
+ * hardware timer counter.
+ */
+#define VIA_WDT_MMIO_LEN 8 /* MMIO region length in bytes */
+#define VIA_WDT_CTL 0 /* MMIO addr+0: state/control reg. */
+#define VIA_WDT_COUNT 4 /* MMIO addr+4: timer counter reg. */
+
+/* Bits for the VIA_WDT_CTL register */
+#define VIA_WDT_RUNNING 0x01 /* 0: stop, 1: running */
+#define VIA_WDT_FIRED 0x02 /* 1: restarted by expired watchdog */
+#define VIA_WDT_PWROFF 0x04 /* 0: reset, 1: poweroff */
+#define VIA_WDT_DISABLED 0x08 /* 1: timer is disabled */
+#define VIA_WDT_TRIGGER 0x80 /* 1: start a new countdown */
+
+/* Hardware heartbeat in seconds */
+#define WDT_HW_HEARTBEAT 1
+
+/* Timer heartbeat (500ms) */
+#define WDT_HEARTBEAT (HZ/2) /* should be <= ((WDT_HW_HEARTBEAT*HZ)/2) */
+
+/* User space timeout in seconds */
+#define WDT_TIMEOUT_MAX 1023 /* approx. 17 min. */
+#define WDT_TIMEOUT 60
+static int timeout = WDT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds, between 1 and 1023 "
+ "(default = " __MODULE_STRING(WDT_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) ")");
+
+static struct watchdog_device wdt_dev;
+static struct resource wdt_res;
+static void __iomem *wdt_mem;
+static unsigned int mmio;
+static void wdt_timer_tick(struct timer_list *unused);
+static DEFINE_TIMER(timer, wdt_timer_tick);
+ /* The timer that pings the watchdog */
+static unsigned long next_heartbeat; /* the next_heartbeat for the timer */
+
+static inline void wdt_reset(void)
+{
+ unsigned int ctl = readl(wdt_mem);
+
+ writel(ctl | VIA_WDT_TRIGGER, wdt_mem);
+}
+
+/*
+ * Timer tick: the timer will make sure that the watchdog timer hardware
+ * is being reset in time. The conditions to do this are:
+ * 1) the watchdog timer has been started and /dev/watchdog is open
+ * and there is still time left before userspace should send the
+ * next heartbeat/ping. (note: the internal heartbeat is much smaller
+ * then the external/userspace heartbeat).
+ * 2) the watchdog timer has been stopped by userspace.
+ */
+static void wdt_timer_tick(struct timer_list *unused)
+{
+ if (time_before(jiffies, next_heartbeat) ||
+ (!watchdog_active(&wdt_dev))) {
+ wdt_reset();
+ mod_timer(&timer, jiffies + WDT_HEARTBEAT);
+ } else
+ pr_crit("I will reboot your machine !\n");
+}
+
+static int wdt_ping(struct watchdog_device *wdd)
+{
+ /* calculate when the next userspace timeout will be */
+ next_heartbeat = jiffies + wdd->timeout * HZ;
+ return 0;
+}
+
+static int wdt_start(struct watchdog_device *wdd)
+{
+ unsigned int ctl = readl(wdt_mem);
+
+ writel(wdd->timeout, wdt_mem + VIA_WDT_COUNT);
+ writel(ctl | VIA_WDT_RUNNING | VIA_WDT_TRIGGER, wdt_mem);
+ wdt_ping(wdd);
+ mod_timer(&timer, jiffies + WDT_HEARTBEAT);
+ return 0;
+}
+
+static int wdt_stop(struct watchdog_device *wdd)
+{
+ unsigned int ctl = readl(wdt_mem);
+
+ writel(ctl & ~VIA_WDT_RUNNING, wdt_mem);
+ return 0;
+}
+
+static int wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int new_timeout)
+{
+ writel(new_timeout, wdt_mem + VIA_WDT_COUNT);
+ wdd->timeout = new_timeout;
+ return 0;
+}
+
+static const struct watchdog_info wdt_info = {
+ .identity = "VIA watchdog",
+ .options = WDIOF_CARDRESET |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+};
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .ping = wdt_ping,
+ .set_timeout = wdt_set_timeout,
+};
+
+static struct watchdog_device wdt_dev = {
+ .info = &wdt_info,
+ .ops = &wdt_ops,
+ .min_timeout = 1,
+ .max_timeout = WDT_TIMEOUT_MAX,
+};
+
+static int wdt_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ unsigned char conf;
+ int ret = -ENODEV;
+
+ if (pci_enable_device(pdev)) {
+ dev_err(&pdev->dev, "cannot enable PCI device\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Allocate a MMIO region which contains watchdog control register
+ * and counter, then configure the watchdog to use this region.
+ * This is possible only if PnP is properly enabled in BIOS.
+ * If not, the watchdog must be configured in BIOS manually.
+ */
+ if (allocate_resource(&iomem_resource, &wdt_res, VIA_WDT_MMIO_LEN,
+ 0xf0000000, 0xffffff00, 0xff, NULL, NULL)) {
+ dev_err(&pdev->dev, "MMIO allocation failed\n");
+ goto err_out_disable_device;
+ }
+
+ pci_write_config_dword(pdev, VIA_WDT_MMIO_BASE, wdt_res.start);
+ pci_read_config_byte(pdev, VIA_WDT_CONF, &conf);
+ conf |= VIA_WDT_CONF_ENABLE | VIA_WDT_CONF_MMIO;
+ pci_write_config_byte(pdev, VIA_WDT_CONF, conf);
+
+ pci_read_config_dword(pdev, VIA_WDT_MMIO_BASE, &mmio);
+ if (mmio) {
+ dev_info(&pdev->dev, "VIA Chipset watchdog MMIO: %x\n", mmio);
+ } else {
+ dev_err(&pdev->dev, "MMIO setting failed. Check BIOS.\n");
+ goto err_out_resource;
+ }
+
+ if (!request_mem_region(mmio, VIA_WDT_MMIO_LEN, "via_wdt")) {
+ dev_err(&pdev->dev, "MMIO region busy\n");
+ goto err_out_resource;
+ }
+
+ wdt_mem = ioremap(mmio, VIA_WDT_MMIO_LEN);
+ if (wdt_mem == NULL) {
+ dev_err(&pdev->dev, "cannot remap VIA wdt MMIO registers\n");
+ goto err_out_release;
+ }
+
+ if (timeout < 1 || timeout > WDT_TIMEOUT_MAX)
+ timeout = WDT_TIMEOUT;
+
+ wdt_dev.timeout = timeout;
+ wdt_dev.parent = &pdev->dev;
+ watchdog_set_nowayout(&wdt_dev, nowayout);
+ if (readl(wdt_mem) & VIA_WDT_FIRED)
+ wdt_dev.bootstatus |= WDIOF_CARDRESET;
+
+ ret = watchdog_register_device(&wdt_dev);
+ if (ret)
+ goto err_out_iounmap;
+
+ /* start triggering, in case of watchdog already enabled by BIOS */
+ mod_timer(&timer, jiffies + WDT_HEARTBEAT);
+ return 0;
+
+err_out_iounmap:
+ iounmap(wdt_mem);
+err_out_release:
+ release_mem_region(mmio, VIA_WDT_MMIO_LEN);
+err_out_resource:
+ release_resource(&wdt_res);
+err_out_disable_device:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void wdt_remove(struct pci_dev *pdev)
+{
+ watchdog_unregister_device(&wdt_dev);
+ del_timer_sync(&timer);
+ iounmap(wdt_mem);
+ release_mem_region(mmio, VIA_WDT_MMIO_LEN);
+ release_resource(&wdt_res);
+ pci_disable_device(pdev);
+}
+
+static const struct pci_device_id wdt_pci_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_CX700) },
+ { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX800) },
+ { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_VX855) },
+ { 0 }
+};
+
+static struct pci_driver wdt_driver = {
+ .name = "via_wdt",
+ .id_table = wdt_pci_table,
+ .probe = wdt_probe,
+ .remove = wdt_remove,
+};
+
+module_pci_driver(wdt_driver);
+
+MODULE_AUTHOR("Marc Vertes");
+MODULE_DESCRIPTION("Driver for watchdog timer on VIA chipset");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/visconti_wdt.c b/drivers/watchdog/visconti_wdt.c
new file mode 100644
index 000000000..83ef55e66
--- /dev/null
+++ b/drivers/watchdog/visconti_wdt.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 TOSHIBA CORPORATION
+ * Copyright (c) 2020 Toshiba Electronic Devices & Storage Corporation
+ * Copyright (c) 2020 Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp>
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+
+#define WDT_CNT 0x00
+#define WDT_MIN 0x04
+#define WDT_MAX 0x08
+#define WDT_CTL 0x0c
+#define WDT_CMD 0x10
+#define WDT_CMD_CLEAR 0x4352
+#define WDT_CMD_START_STOP 0x5354
+#define WDT_DIV 0x30
+
+#define VISCONTI_WDT_FREQ 2000000 /* 2MHz */
+#define WDT_DEFAULT_TIMEOUT 10U /* in seconds */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(
+ nowayout,
+ "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT)")");
+
+struct visconti_wdt_priv {
+ struct watchdog_device wdev;
+ void __iomem *base;
+ u32 div;
+};
+
+static int visconti_wdt_start(struct watchdog_device *wdev)
+{
+ struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ u32 timeout = wdev->timeout * VISCONTI_WDT_FREQ;
+
+ writel(priv->div, priv->base + WDT_DIV);
+ writel(0, priv->base + WDT_MIN);
+ writel(timeout, priv->base + WDT_MAX);
+ writel(0, priv->base + WDT_CTL);
+ writel(WDT_CMD_START_STOP, priv->base + WDT_CMD);
+
+ return 0;
+}
+
+static int visconti_wdt_stop(struct watchdog_device *wdev)
+{
+ struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ writel(1, priv->base + WDT_CTL);
+ writel(WDT_CMD_START_STOP, priv->base + WDT_CMD);
+
+ return 0;
+}
+
+static int visconti_wdt_ping(struct watchdog_device *wdd)
+{
+ struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+ writel(WDT_CMD_CLEAR, priv->base + WDT_CMD);
+
+ return 0;
+}
+
+static unsigned int visconti_wdt_get_timeleft(struct watchdog_device *wdev)
+{
+ struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
+ u32 timeout = wdev->timeout * VISCONTI_WDT_FREQ;
+ u32 cnt = readl(priv->base + WDT_CNT);
+
+ if (timeout <= cnt)
+ return 0;
+ timeout -= cnt;
+
+ return timeout / VISCONTI_WDT_FREQ;
+}
+
+static int visconti_wdt_set_timeout(struct watchdog_device *wdev, unsigned int timeout)
+{
+ u32 val;
+ struct visconti_wdt_priv *priv = watchdog_get_drvdata(wdev);
+
+ wdev->timeout = timeout;
+ val = wdev->timeout * VISCONTI_WDT_FREQ;
+
+ /* Clear counter before setting timeout because WDT expires */
+ writel(WDT_CMD_CLEAR, priv->base + WDT_CMD);
+ writel(val, priv->base + WDT_MAX);
+
+ return 0;
+}
+
+static const struct watchdog_info visconti_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "Visconti Watchdog",
+};
+
+static const struct watchdog_ops visconti_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = visconti_wdt_start,
+ .stop = visconti_wdt_stop,
+ .ping = visconti_wdt_ping,
+ .get_timeleft = visconti_wdt_get_timeleft,
+ .set_timeout = visconti_wdt_set_timeout,
+};
+
+static void visconti_clk_disable_unprepare(void *data)
+{
+ clk_disable_unprepare(data);
+}
+
+static int visconti_wdt_probe(struct platform_device *pdev)
+{
+ struct watchdog_device *wdev;
+ struct visconti_wdt_priv *priv;
+ struct device *dev = &pdev->dev;
+ struct clk *clk;
+ int ret;
+ unsigned long clk_freq;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Could not get clock\n");
+
+ ret = clk_prepare_enable(clk);
+ if (ret) {
+ dev_err(dev, "Could not enable clock\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, visconti_clk_disable_unprepare, clk);
+ if (ret)
+ return ret;
+
+ clk_freq = clk_get_rate(clk);
+ if (!clk_freq)
+ return -EINVAL;
+
+ priv->div = clk_freq / VISCONTI_WDT_FREQ;
+
+ /* Initialize struct watchdog_device. */
+ wdev = &priv->wdev;
+ wdev->info = &visconti_wdt_info;
+ wdev->ops = &visconti_wdt_ops;
+ wdev->parent = dev;
+ wdev->min_timeout = 1;
+ wdev->max_timeout = 0xffffffff / VISCONTI_WDT_FREQ;
+ wdev->timeout = min(wdev->max_timeout, WDT_DEFAULT_TIMEOUT);
+
+ watchdog_set_drvdata(wdev, priv);
+ watchdog_set_nowayout(wdev, nowayout);
+ watchdog_stop_on_unregister(wdev);
+
+ /* This overrides the default timeout only if DT configuration was found */
+ ret = watchdog_init_timeout(wdev, 0, dev);
+ if (ret)
+ dev_warn(dev, "Specified timeout value invalid, using default\n");
+
+ return devm_watchdog_register_device(dev, wdev);
+}
+
+static const struct of_device_id visconti_wdt_of_match[] = {
+ { .compatible = "toshiba,visconti-wdt", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, visconti_wdt_of_match);
+
+static struct platform_driver visconti_wdt_driver = {
+ .driver = {
+ .name = "visconti_wdt",
+ .of_match_table = visconti_wdt_of_match,
+ },
+ .probe = visconti_wdt_probe,
+};
+module_platform_driver(visconti_wdt_driver);
+
+MODULE_DESCRIPTION("TOSHIBA Visconti Watchdog Driver");
+MODULE_AUTHOR("Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c
new file mode 100644
index 000000000..bc33b63c5
--- /dev/null
+++ b/drivers/watchdog/w83627hf_wdt.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * w83627hf/thf WDT driver
+ *
+ * (c) Copyright 2013 Guenter Roeck
+ * converted to watchdog infrastructure
+ *
+ * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com>
+ * added support for W83627THF.
+ *
+ * (c) Copyright 2003,2007 Pádraig Brady <P@draigBrady.com>
+ *
+ * Based on advantechwdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/dmi.h>
+
+#define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT"
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+static int wdt_io;
+static int cr_wdt_timeout; /* WDT timeout register */
+static int cr_wdt_control; /* WDT control register */
+static int cr_wdt_csr; /* WDT control & status register */
+static int wdt_cfg_enter = 0x87;/* key to unlock configuration space */
+static int wdt_cfg_leave = 0xAA;/* key to lock configuration space */
+
+enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf,
+ w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p,
+ w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793,
+ nct6795, nct6796, nct6102, nct6116 };
+
+static int timeout; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
+ __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static int early_disable;
+module_param(early_disable, int, 0);
+MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)");
+
+/*
+ * Kernel methods.
+ */
+
+#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */
+#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register
+ (same as EFER) */
+#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */
+
+#define W83627HF_LD_WDT 0x08
+
+#define W83627HF_ID 0x52
+#define W83627S_ID 0x59
+#define W83697HF_ID 0x60
+#define W83697UG_ID 0x68
+#define W83637HF_ID 0x70
+#define W83627THF_ID 0x82
+#define W83687THF_ID 0x85
+#define W83627EHF_ID 0x88
+#define W83627DHG_ID 0xa0
+#define W83627UHG_ID 0xa2
+#define W83667HG_ID 0xa5
+#define W83627DHG_P_ID 0xb0
+#define W83667HG_B_ID 0xb3
+#define NCT6775_ID 0xb4
+#define NCT6776_ID 0xc3
+#define NCT6102_ID 0xc4
+#define NCT6116_ID 0xd2
+#define NCT6779_ID 0xc5
+#define NCT6791_ID 0xc8
+#define NCT6792_ID 0xc9
+#define NCT6793_ID 0xd1
+#define NCT6795_ID 0xd3
+#define NCT6796_ID 0xd4 /* also NCT9697D, NCT9698D */
+
+#define W83627HF_WDT_TIMEOUT 0xf6
+#define W83697HF_WDT_TIMEOUT 0xf4
+#define NCT6102D_WDT_TIMEOUT 0xf1
+
+#define W83627HF_WDT_CONTROL 0xf5
+#define W83697HF_WDT_CONTROL 0xf3
+#define NCT6102D_WDT_CONTROL 0xf0
+
+#define W836X7HF_WDT_CSR 0xf7
+#define NCT6102D_WDT_CSR 0xf2
+
+#define WDT_CSR_STATUS 0x10
+#define WDT_CSR_KBD 0x40
+#define WDT_CSR_MOUSE 0x80
+
+static void superio_outb(int reg, int val)
+{
+ outb(reg, WDT_EFER);
+ outb(val, WDT_EFDR);
+}
+
+static inline int superio_inb(int reg)
+{
+ outb(reg, WDT_EFER);
+ return inb(WDT_EFDR);
+}
+
+static int superio_enter(void)
+{
+ if (!request_muxed_region(wdt_io, 2, WATCHDOG_NAME))
+ return -EBUSY;
+
+ outb_p(wdt_cfg_enter, WDT_EFER); /* Enter extended function mode */
+ outb_p(wdt_cfg_enter, WDT_EFER); /* Again according to manual */
+
+ return 0;
+}
+
+static void superio_select(int ld)
+{
+ superio_outb(0x07, ld);
+}
+
+static void superio_exit(void)
+{
+ outb_p(wdt_cfg_leave, WDT_EFER); /* Leave extended function mode */
+ release_region(wdt_io, 2);
+}
+
+static int w83627hf_init(struct watchdog_device *wdog, enum chips chip)
+{
+ int ret;
+ unsigned char t;
+
+ ret = superio_enter();
+ if (ret)
+ return ret;
+
+ superio_select(W83627HF_LD_WDT);
+
+ /* set CR30 bit 0 to activate GPIO2 */
+ t = superio_inb(0x30);
+ if (!(t & 0x01))
+ superio_outb(0x30, t | 0x01);
+
+ switch (chip) {
+ case w83627hf:
+ case w83627s:
+ t = superio_inb(0x2B) & ~0x10;
+ superio_outb(0x2B, t); /* set GPIO24 to WDT0 */
+ break;
+ case w83697hf:
+ /* Set pin 119 to WDTO# mode (= CR29, WDT0) */
+ t = superio_inb(0x29) & ~0x60;
+ t |= 0x20;
+ superio_outb(0x29, t);
+ break;
+ case w83697ug:
+ /* Set pin 118 to WDTO# mode */
+ t = superio_inb(0x2b) & ~0x04;
+ superio_outb(0x2b, t);
+ break;
+ case w83627thf:
+ t = (superio_inb(0x2B) & ~0x08) | 0x04;
+ superio_outb(0x2B, t); /* set GPIO3 to WDT0 */
+ break;
+ case w83627dhg:
+ case w83627dhg_p:
+ t = superio_inb(0x2D) & ~0x01; /* PIN77 -> WDT0# */
+ superio_outb(0x2D, t); /* set GPIO5 to WDT0 */
+ t = superio_inb(cr_wdt_control);
+ t |= 0x02; /* enable the WDTO# output low pulse
+ * to the KBRST# pin */
+ superio_outb(cr_wdt_control, t);
+ break;
+ case w83637hf:
+ break;
+ case w83687thf:
+ t = superio_inb(0x2C) & ~0x80; /* PIN47 -> WDT0# */
+ superio_outb(0x2C, t);
+ break;
+ case w83627ehf:
+ case w83627uhg:
+ case w83667hg:
+ case w83667hg_b:
+ case nct6775:
+ case nct6776:
+ case nct6779:
+ case nct6791:
+ case nct6792:
+ case nct6793:
+ case nct6795:
+ case nct6796:
+ case nct6102:
+ case nct6116:
+ /*
+ * These chips have a fixed WDTO# output pin (W83627UHG),
+ * or support more than one WDTO# output pin.
+ * Don't touch its configuration, and hope the BIOS
+ * does the right thing.
+ */
+ t = superio_inb(cr_wdt_control);
+ t |= 0x02; /* enable the WDTO# output low pulse
+ * to the KBRST# pin */
+ superio_outb(cr_wdt_control, t);
+ break;
+ default:
+ break;
+ }
+
+ t = superio_inb(cr_wdt_timeout);
+ if (t != 0) {
+ if (early_disable) {
+ pr_warn("Stopping previously enabled watchdog until userland kicks in\n");
+ superio_outb(cr_wdt_timeout, 0);
+ } else {
+ pr_info("Watchdog already running. Resetting timeout to %d sec\n",
+ wdog->timeout);
+ superio_outb(cr_wdt_timeout, wdog->timeout);
+ }
+ }
+
+ /* set second mode & disable keyboard turning off watchdog */
+ t = superio_inb(cr_wdt_control) & ~0x0C;
+ superio_outb(cr_wdt_control, t);
+
+ t = superio_inb(cr_wdt_csr);
+ if (t & WDT_CSR_STATUS)
+ wdog->bootstatus |= WDIOF_CARDRESET;
+
+ /* reset status, disable keyboard & mouse turning off watchdog */
+ t &= ~(WDT_CSR_STATUS | WDT_CSR_KBD | WDT_CSR_MOUSE);
+ superio_outb(cr_wdt_csr, t);
+
+ superio_exit();
+
+ return 0;
+}
+
+static int wdt_set_time(unsigned int timeout)
+{
+ int ret;
+
+ ret = superio_enter();
+ if (ret)
+ return ret;
+
+ superio_select(W83627HF_LD_WDT);
+ superio_outb(cr_wdt_timeout, timeout);
+ superio_exit();
+
+ return 0;
+}
+
+static int wdt_start(struct watchdog_device *wdog)
+{
+ return wdt_set_time(wdog->timeout);
+}
+
+static int wdt_stop(struct watchdog_device *wdog)
+{
+ return wdt_set_time(0);
+}
+
+static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout)
+{
+ wdog->timeout = timeout;
+
+ return 0;
+}
+
+static unsigned int wdt_get_time(struct watchdog_device *wdog)
+{
+ unsigned int timeleft;
+ int ret;
+
+ ret = superio_enter();
+ if (ret)
+ return 0;
+
+ superio_select(W83627HF_LD_WDT);
+ timeleft = superio_inb(cr_wdt_timeout);
+ superio_exit();
+
+ return timeleft;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct watchdog_info wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "W83627HF Watchdog",
+};
+
+static const struct watchdog_ops wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdt_start,
+ .stop = wdt_stop,
+ .set_timeout = wdt_set_timeout,
+ .get_timeleft = wdt_get_time,
+};
+
+static struct watchdog_device wdt_dev = {
+ .info = &wdt_info,
+ .ops = &wdt_ops,
+ .timeout = WATCHDOG_TIMEOUT,
+ .min_timeout = 1,
+ .max_timeout = 255,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static int wdt_find(int addr)
+{
+ u8 val;
+ int ret;
+
+ cr_wdt_timeout = W83627HF_WDT_TIMEOUT;
+ cr_wdt_control = W83627HF_WDT_CONTROL;
+ cr_wdt_csr = W836X7HF_WDT_CSR;
+
+ ret = superio_enter();
+ if (ret)
+ return ret;
+ superio_select(W83627HF_LD_WDT);
+ val = superio_inb(0x20);
+ switch (val) {
+ case W83627HF_ID:
+ ret = w83627hf;
+ break;
+ case W83627S_ID:
+ ret = w83627s;
+ break;
+ case W83697HF_ID:
+ ret = w83697hf;
+ cr_wdt_timeout = W83697HF_WDT_TIMEOUT;
+ cr_wdt_control = W83697HF_WDT_CONTROL;
+ break;
+ case W83697UG_ID:
+ ret = w83697ug;
+ cr_wdt_timeout = W83697HF_WDT_TIMEOUT;
+ cr_wdt_control = W83697HF_WDT_CONTROL;
+ break;
+ case W83637HF_ID:
+ ret = w83637hf;
+ break;
+ case W83627THF_ID:
+ ret = w83627thf;
+ break;
+ case W83687THF_ID:
+ ret = w83687thf;
+ break;
+ case W83627EHF_ID:
+ ret = w83627ehf;
+ break;
+ case W83627DHG_ID:
+ ret = w83627dhg;
+ break;
+ case W83627DHG_P_ID:
+ ret = w83627dhg_p;
+ break;
+ case W83627UHG_ID:
+ ret = w83627uhg;
+ break;
+ case W83667HG_ID:
+ ret = w83667hg;
+ break;
+ case W83667HG_B_ID:
+ ret = w83667hg_b;
+ break;
+ case NCT6775_ID:
+ ret = nct6775;
+ break;
+ case NCT6776_ID:
+ ret = nct6776;
+ break;
+ case NCT6779_ID:
+ ret = nct6779;
+ break;
+ case NCT6791_ID:
+ ret = nct6791;
+ break;
+ case NCT6792_ID:
+ ret = nct6792;
+ break;
+ case NCT6793_ID:
+ ret = nct6793;
+ break;
+ case NCT6795_ID:
+ ret = nct6795;
+ break;
+ case NCT6796_ID:
+ ret = nct6796;
+ break;
+ case NCT6102_ID:
+ ret = nct6102;
+ cr_wdt_timeout = NCT6102D_WDT_TIMEOUT;
+ cr_wdt_control = NCT6102D_WDT_CONTROL;
+ cr_wdt_csr = NCT6102D_WDT_CSR;
+ break;
+ case NCT6116_ID:
+ ret = nct6116;
+ cr_wdt_timeout = NCT6102D_WDT_TIMEOUT;
+ cr_wdt_control = NCT6102D_WDT_CONTROL;
+ cr_wdt_csr = NCT6102D_WDT_CSR;
+ break;
+ case 0xff:
+ ret = -ENODEV;
+ break;
+ default:
+ ret = -ENODEV;
+ pr_err("Unsupported chip ID: 0x%02x\n", val);
+ break;
+ }
+ superio_exit();
+ return ret;
+}
+
+/*
+ * On some systems, the NCT6791D comes with a companion chip and the
+ * watchdog function is in this companion chip. We must use a different
+ * unlocking sequence to access the companion chip.
+ */
+static int __init wdt_use_alt_key(const struct dmi_system_id *d)
+{
+ wdt_cfg_enter = 0x88;
+ wdt_cfg_leave = 0xBB;
+
+ return 0;
+}
+
+static const struct dmi_system_id wdt_dmi_table[] __initconst = {
+ {
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "INVES"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CTS"),
+ DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "INVES"),
+ DMI_EXACT_MATCH(DMI_BOARD_NAME, "SHARKBAY"),
+ },
+ .callback = wdt_use_alt_key,
+ },
+ {}
+};
+
+static int __init wdt_init(void)
+{
+ int ret;
+ int chip;
+ static const char * const chip_name[] = {
+ "W83627HF",
+ "W83627S",
+ "W83697HF",
+ "W83697UG",
+ "W83637HF",
+ "W83627THF",
+ "W83687THF",
+ "W83627EHF",
+ "W83627DHG",
+ "W83627UHG",
+ "W83667HG",
+ "W83667DHG-P",
+ "W83667HG-B",
+ "NCT6775",
+ "NCT6776",
+ "NCT6779",
+ "NCT6791",
+ "NCT6792",
+ "NCT6793",
+ "NCT6795",
+ "NCT6796",
+ "NCT6102",
+ "NCT6116",
+ };
+
+ /* Apply system-specific quirks */
+ dmi_check_system(wdt_dmi_table);
+
+ wdt_io = 0x2e;
+ chip = wdt_find(0x2e);
+ if (chip < 0) {
+ wdt_io = 0x4e;
+ chip = wdt_find(0x4e);
+ if (chip < 0)
+ return chip;
+ }
+
+ pr_info("WDT driver for %s Super I/O chip initialising\n",
+ chip_name[chip]);
+
+ watchdog_init_timeout(&wdt_dev, timeout, NULL);
+ watchdog_set_nowayout(&wdt_dev, nowayout);
+ watchdog_stop_on_reboot(&wdt_dev);
+
+ ret = w83627hf_init(&wdt_dev, chip);
+ if (ret) {
+ pr_err("failed to initialize watchdog (err=%d)\n", ret);
+ return ret;
+ }
+
+ ret = watchdog_register_device(&wdt_dev);
+ if (ret)
+ return ret;
+
+ pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
+ wdt_dev.timeout, nowayout);
+
+ return ret;
+}
+
+static void __exit wdt_exit(void)
+{
+ watchdog_unregister_device(&wdt_dev);
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>");
+MODULE_DESCRIPTION("w83627hf/thf WDT driver");
diff --git a/drivers/watchdog/w83877f_wdt.c b/drivers/watchdog/w83877f_wdt.c
new file mode 100644
index 000000000..f2650863f
--- /dev/null
+++ b/drivers/watchdog/w83877f_wdt.c
@@ -0,0 +1,405 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * W83877F Computer 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>
+ *
+ * 4/19 - 2001 [Initial revision]
+ * 9/27 - 2001 Added spinlocking
+ * 4/12 - 2002 [rob@osinvestor.com] Eliminate extra comments
+ * Eliminate fop_read
+ * Eliminate extra spin_unlock
+ * Added KERN_* tags to printks
+ * add CONFIG_WATCHDOG_NOWAYOUT support
+ * fix possible wdt_is_open race
+ * changed watchdog_info to correctly reflect what
+ * the driver offers
+ * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS,
+ * WDIOC_SETTIMEOUT,
+ * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
+ * 09/8 - 2003 [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
+ *
+ * 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.
+ */
+
+#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>
+
+#define OUR_NAME "w83877f_wdt"
+
+#define ENABLE_W83877F_PORT 0x3F0
+#define ENABLE_W83877F 0x87
+#define DISABLE_W83877F 0xAA
+#define WDT_PING 0x443
+#define WDT_REGISTER 0x14
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+/*
+ * The W83877F seems to be fixed at 1.6s timeout (at least on the
+ * EMACS PC-104 board I'm using). 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) ")");
+
+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);
+
+ /* Ping the WDT by reading from WDT_PING */
+ inb_p(WDT_PING);
+
+ /* Re-set the timer interval */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+ spin_unlock(&wdt_spinlock);
+
+ } else
+ pr_warn("Heartbeat lost! Will not ping the watchdog\n");
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&wdt_spinlock, flags);
+
+ /* buy some time */
+ inb_p(WDT_PING);
+
+ /* make W83877F available */
+ outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT);
+ outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT);
+
+ /* enable watchdog */
+ outb_p(WDT_REGISTER, ENABLE_W83877F_PORT);
+ outb_p(writeval, ENABLE_W83877F_PORT+1);
+
+ /* lock the W8387FF away */
+ outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
+
+ spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static void wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* Start the timer */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+ wdt_change(WDT_ENABLE);
+
+ pr_info("Watchdog timer is now enabled\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer_sync(&timer);
+
+ wdt_change(WDT_DISABLE);
+
+ pr_info("Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /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;
+
+ /* scan to see whether or not we got the
+ magic character */
+ for (ofs = 0; ofs != count; ofs++) {
+ char c;
+ if (get_user(c, buf + ofs))
+ return -EFAULT;
+ if (c == 'V')
+ wdt_expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should restart timer */
+ 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;
+
+ /* 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 {
+ del_timer(&timer);
+ pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ 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 = "W83877F",
+ };
+
+ 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;
+
+ /* arbitrary upper limit */
+ if (new_timeout < 1 || new_timeout > 3600)
+ return -EINVAL;
+
+ timeout = new_timeout;
+ wdt_keepalive();
+ }
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .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 w83877f_wdt_unload(void)
+{
+ wdt_turnoff();
+
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+
+ unregister_reboot_notifier(&wdt_notifier);
+ release_region(WDT_PING, 1);
+ release_region(ENABLE_W83877F_PORT, 2);
+}
+
+static int __init w83877f_wdt_init(void)
+{
+ int rc = -EBUSY;
+
+ if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */
+ timeout = WATCHDOG_TIMEOUT;
+ pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
+ timeout);
+ }
+
+ if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) {
+ pr_err("I/O address 0x%04x already in use\n",
+ ENABLE_W83877F_PORT);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ if (!request_region(WDT_PING, 1, "W8387FF WDT")) {
+ pr_err("I/O address 0x%04x already in use\n", WDT_PING);
+ rc = -EIO;
+ goto err_out_region1;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out_region2;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_reboot;
+ }
+
+ pr_info("WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return 0;
+
+err_out_reboot:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out_region2:
+ release_region(WDT_PING, 1);
+err_out_region1:
+ release_region(ENABLE_W83877F_PORT, 2);
+err_out:
+ return rc;
+}
+
+module_init(w83877f_wdt_init);
+module_exit(w83877f_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/w83977f_wdt.c b/drivers/watchdog/w83977f_wdt.c
new file mode 100644
index 000000000..31bf21cea
--- /dev/null
+++ b/drivers/watchdog/w83977f_wdt.c
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * W83977F Watchdog Timer Driver for Winbond W83977F I/O Chip
+ *
+ * (c) Copyright 2005 Jose Goncalves <jose.goncalves@inov.pt>
+ *
+ * Based on w83877f_wdt.c by Scott Jennings,
+ * and wdt977.c by Woody Suwalski
+ *
+ * -----------------------
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+
+#define WATCHDOG_VERSION "1.00"
+#define WATCHDOG_NAME "W83977F WDT"
+
+#define IO_INDEX_PORT 0x3F0
+#define IO_DATA_PORT (IO_INDEX_PORT+1)
+
+#define UNLOCK_DATA 0x87
+#define LOCK_DATA 0xAA
+#define DEVICE_REGISTER 0x07
+
+#define DEFAULT_TIMEOUT 45 /* default timeout in seconds */
+
+static int timeout = DEFAULT_TIMEOUT;
+static int timeoutW; /* timeout in watchdog counter units */
+static unsigned long timer_alive;
+static int testmode;
+static char expect_close;
+static DEFINE_SPINLOCK(spinlock);
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds (15..7635), default="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+module_param(testmode, int, 0);
+MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0");
+
+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) ")");
+
+/*
+ * Start the watchdog
+ */
+
+static int wdt_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* Unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /*
+ * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4.
+ * F2 has the timeout in watchdog counter units.
+ * F3 is set to enable watchdog LED blink at timeout.
+ * F4 is used to just clear the TIMEOUT'ed state (bit 0).
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(timeoutW, IO_DATA_PORT);
+ outb_p(0xF3, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF4, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+
+ /* Set device Aux2 active */
+ outb_p(0x30, IO_INDEX_PORT);
+ outb_p(0x01, IO_DATA_PORT);
+
+ /*
+ * Select device Aux1 (dev=7) to set GP16 as the watchdog output
+ * (in reg E6) and GP13 as the watchdog LED output (in reg E3).
+ * Map GP16 at pin 119.
+ * In test mode watch the bit 0 on F4 to indicate "triggered" or
+ * check watchdog LED on SBC.
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x07, IO_DATA_PORT);
+ if (!testmode) {
+ unsigned pin_map;
+
+ outb_p(0xE6, IO_INDEX_PORT);
+ outb_p(0x0A, IO_DATA_PORT);
+ outb_p(0x2C, IO_INDEX_PORT);
+ pin_map = inb_p(IO_DATA_PORT);
+ pin_map |= 0x10;
+ pin_map &= ~(0x20);
+ outb_p(0x2C, IO_INDEX_PORT);
+ outb_p(pin_map, IO_DATA_PORT);
+ }
+ outb_p(0xE3, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+
+ /* Set device Aux1 active */
+ outb_p(0x30, IO_INDEX_PORT);
+ outb_p(0x01, IO_DATA_PORT);
+
+ /* Lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ pr_info("activated\n");
+
+ return 0;
+}
+
+/*
+ * Stop the watchdog
+ */
+
+static int wdt_stop(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* Unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /*
+ * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4.
+ * F2 is reset to its default value (watchdog timer disabled).
+ * F3 is reset to its default state.
+ * F4 clears the TIMEOUT'ed state (bit 0) - back to default.
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(0xFF, IO_DATA_PORT);
+ outb_p(0xF3, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+ outb_p(0xF4, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+
+ /*
+ * Select device Aux1 (dev=7) to set GP16 (in reg E6) and
+ * Gp13 (in reg E3) as inputs.
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x07, IO_DATA_PORT);
+ if (!testmode) {
+ outb_p(0xE6, IO_INDEX_PORT);
+ outb_p(0x01, IO_DATA_PORT);
+ }
+ outb_p(0xE3, IO_INDEX_PORT);
+ outb_p(0x01, IO_DATA_PORT);
+
+ /* Lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ pr_info("shutdown\n");
+
+ return 0;
+}
+
+/*
+ * Send a keepalive ping to the watchdog
+ * This is done by simply re-writing the timeout to reg. 0xF2
+ */
+
+static int wdt_keepalive(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* Unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* Select device Aux2 (device=8) to kick watchdog reg F2 */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(timeoutW, IO_DATA_PORT);
+
+ /* Lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ return 0;
+}
+
+/*
+ * Set the watchdog timeout value
+ */
+
+static int wdt_set_timeout(int t)
+{
+ unsigned int tmrval;
+
+ /*
+ * Convert seconds to watchdog counter time units, rounding up.
+ * On PCM-5335 watchdog units are 30 seconds/step with 15 sec startup
+ * value. This information is supplied in the PCM-5335 manual and was
+ * checked by me on a real board. This is a bit strange because W83977f
+ * datasheet says counter unit is in minutes!
+ */
+ if (t < 15)
+ return -EINVAL;
+
+ tmrval = ((t + 15) + 29) / 30;
+
+ if (tmrval > 255)
+ return -EINVAL;
+
+ /*
+ * timeout is the timeout in seconds,
+ * timeoutW is the timeout in watchdog counter units.
+ */
+ timeoutW = tmrval;
+ timeout = (timeoutW * 30) - 15;
+ return 0;
+}
+
+/*
+ * Get the watchdog status
+ */
+
+static int wdt_get_status(int *status)
+{
+ int new_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* Unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* Select device Aux2 (device=8) to read watchdog reg F4 */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF4, IO_INDEX_PORT);
+ new_status = inb_p(IO_DATA_PORT);
+
+ /* Lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ *status = 0;
+ if (new_status & 1)
+ *status |= WDIOF_CARDRESET;
+
+ return 0;
+}
+
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int wdt_open(struct inode *inode, struct file *file)
+{
+ /* If the watchdog is alive we don't need to start it again */
+ if (test_and_set_bit(0, &timer_alive))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ wdt_start();
+ return stream_open(inode, file);
+}
+
+static int wdt_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ * Lock it in if it's a module and we set nowayout
+ */
+ if (expect_close == 42) {
+ wdt_stop();
+ clear_bit(0, &timer_alive);
+ } else {
+ wdt_keepalive();
+ pr_crit("unexpected close, not stopping watchdog!\n");
+ }
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * wdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we don't define content meaning.
+ */
+
+static ssize_t wdt_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 long ago */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the
+ magic character */
+ for (ofs = 0; ofs != count; ofs++) {
+ char c;
+ if (get_user(c, buf + ofs))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should restart timer */
+ wdt_keepalive();
+ }
+ return count;
+}
+
+/*
+ * wdt_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+};
+
+static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int status;
+ int new_options, retval = -EINVAL;
+ int new_timeout;
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ uarg.i = (int __user *)arg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ wdt_get_status(&status);
+ return put_user(status, uarg.i);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, uarg.i);
+
+ case WDIOC_SETOPTIONS:
+ if (get_user(new_options, uarg.i))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ wdt_stop();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ wdt_start();
+ retval = 0;
+ }
+
+ return retval;
+
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, uarg.i))
+ return -EFAULT;
+
+ if (wdt_set_timeout(new_timeout))
+ return -EINVAL;
+
+ wdt_keepalive();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, uarg.i);
+
+ default:
+ return -ENOTTY;
+
+ }
+}
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wdt_stop();
+ return NOTIFY_DONE;
+}
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt_write,
+ .unlocked_ioctl = wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wdt_open,
+ .release = wdt_release,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+static int __init w83977f_wdt_init(void)
+{
+ int rc;
+
+ pr_info("driver v%s\n", WATCHDOG_VERSION);
+
+ /*
+ * Check that the timeout value is within it's range;
+ * if not reset to the default
+ */
+ if (wdt_set_timeout(timeout)) {
+ wdt_set_timeout(DEFAULT_TIMEOUT);
+ pr_info("timeout value must be 15 <= timeout <= 7635, using %d\n",
+ DEFAULT_TIMEOUT);
+ }
+
+ if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", IO_INDEX_PORT);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out_region;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_reboot;
+ }
+
+ pr_info("initialized. timeout=%d sec (nowayout=%d testmode=%d)\n",
+ timeout, nowayout, testmode);
+
+ return 0;
+
+err_out_reboot:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out_region:
+ release_region(IO_INDEX_PORT, 2);
+err_out:
+ return rc;
+}
+
+static void __exit w83977f_wdt_exit(void)
+{
+ wdt_stop();
+ misc_deregister(&wdt_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+ release_region(IO_INDEX_PORT, 2);
+}
+
+module_init(w83977f_wdt_init);
+module_exit(w83977f_wdt_exit);
+
+MODULE_AUTHOR("Jose Goncalves <jose.goncalves@inov.pt>");
+MODULE_DESCRIPTION("Driver for watchdog timer in W83977F I/O chip");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/wafer5823wdt.c b/drivers/watchdog/wafer5823wdt.c
new file mode 100644
index 000000000..a8a1ed215
--- /dev/null
+++ b/drivers/watchdog/wafer5823wdt.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ICP Wafer 5823 Single Board Computer WDT driver
+ * http://www.icpamerica.com/wafer_5823.php
+ * May also work on other similar models
+ *
+ * (c) Copyright 2002 Justin Cormack <justin@street-vision.com>
+ *
+ * Release 0.02
+ *
+ * Based on advantechwdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.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/spinlock.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#define WATCHDOG_NAME "Wafer 5823 WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WD_TIMO 60 /* 60 sec default timeout */
+
+static unsigned long wafwdt_is_open;
+static char expect_close;
+static DEFINE_SPINLOCK(wafwdt_lock);
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ *
+ * To enable, write the timeout value in seconds (1 to 255) to I/O
+ * port WDT_START, then read the port to start the watchdog. To pat
+ * the dog, read port WDT_STOP to stop the timer, then read WDT_START
+ * to restart it again.
+ */
+
+static int wdt_stop = 0x843;
+static int wdt_start = 0x443;
+
+static int timeout = WD_TIMO; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,
+ "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
+ __MODULE_STRING(WD_TIMO) ".");
+
+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) ")");
+
+static void wafwdt_ping(void)
+{
+ /* pat watchdog */
+ spin_lock(&wafwdt_lock);
+ inb_p(wdt_stop);
+ inb_p(wdt_start);
+ spin_unlock(&wafwdt_lock);
+}
+
+static void wafwdt_start(void)
+{
+ /* start up watchdog */
+ outb_p(timeout, wdt_start);
+ inb_p(wdt_start);
+}
+
+static void wafwdt_stop(void)
+{
+ /* stop watchdog */
+ inb_p(wdt_stop);
+}
+
+static ssize_t wafwdt_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 i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the magic
+ character */
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ /* Well, anyhow someone wrote to us, we should
+ return that favour */
+ wafwdt_ping();
+ }
+ return count;
+}
+
+static long wafwdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_timeout;
+ 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 = "Wafer 5823 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ wafwdt_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ wafwdt_start();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_KEEPALIVE:
+ wafwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if ((new_timeout < 1) || (new_timeout > 255))
+ return -EINVAL;
+ timeout = new_timeout;
+ wafwdt_stop();
+ wafwdt_start();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int wafwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &wafwdt_is_open))
+ return -EBUSY;
+
+ /*
+ * Activate
+ */
+ wafwdt_start();
+ return stream_open(inode, file);
+}
+
+static int wafwdt_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42)
+ wafwdt_stop();
+ else {
+ pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
+ wafwdt_ping();
+ }
+ clear_bit(0, &wafwdt_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wafwdt_stop();
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static const struct file_operations wafwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wafwdt_write,
+ .unlocked_ioctl = wafwdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wafwdt_open,
+ .release = wafwdt_close,
+};
+
+static struct miscdevice wafwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wafwdt_fops,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wafwdt_notifier = {
+ .notifier_call = wafwdt_notify_sys,
+};
+
+static int __init wafwdt_init(void)
+{
+ int ret;
+
+ pr_info("WDT driver for Wafer 5823 single board computer initialising\n");
+
+ if (timeout < 1 || timeout > 255) {
+ timeout = WD_TIMO;
+ pr_info("timeout value must be 1 <= x <= 255, using %d\n",
+ timeout);
+ }
+
+ if (wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_stop);
+ ret = -EIO;
+ goto error;
+ }
+ }
+
+ if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
+ pr_err("I/O address 0x%04x already in use\n", wdt_start);
+ ret = -EIO;
+ goto error2;
+ }
+
+ ret = register_reboot_notifier(&wafwdt_notifier);
+ if (ret != 0) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto error3;
+ }
+
+ ret = misc_register(&wafwdt_miscdev);
+ if (ret != 0) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto error4;
+ }
+
+ pr_info("initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return ret;
+error4:
+ unregister_reboot_notifier(&wafwdt_notifier);
+error3:
+ release_region(wdt_start, 1);
+error2:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+error:
+ return ret;
+}
+
+static void __exit wafwdt_exit(void)
+{
+ misc_deregister(&wafwdt_miscdev);
+ unregister_reboot_notifier(&wafwdt_notifier);
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+ release_region(wdt_start, 1);
+}
+
+module_init(wafwdt_init);
+module_exit(wafwdt_exit);
+
+MODULE_AUTHOR("Justin Cormack");
+MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");
+MODULE_LICENSE("GPL");
+
+/* end of wafer5823wdt.c */
diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c
new file mode 100644
index 000000000..c777a612d
--- /dev/null
+++ b/drivers/watchdog/watchdog_core.c
@@ -0,0 +1,485 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * watchdog_core.c
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h> /* For EXPORT_SYMBOL/module stuff/... */
+#include <linux/types.h> /* For standard types */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/reboot.h> /* For restart handler */
+#include <linux/watchdog.h> /* For watchdog specific items */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/idr.h> /* For ida_* macros */
+#include <linux/err.h> /* For IS_ERR macros */
+#include <linux/of.h> /* For of_get_timeout_sec */
+#include <linux/suspend.h>
+
+#include "watchdog_core.h" /* For watchdog_dev_register/... */
+
+#define CREATE_TRACE_POINTS
+#include <trace/events/watchdog.h>
+
+static DEFINE_IDA(watchdog_ida);
+
+static int stop_on_reboot = -1;
+module_param(stop_on_reboot, int, 0444);
+MODULE_PARM_DESC(stop_on_reboot, "Stop watchdogs on reboot (0=keep watching, 1=stop)");
+
+/*
+ * Deferred Registration infrastructure.
+ *
+ * Sometimes watchdog drivers needs to be loaded as soon as possible,
+ * for example when it's impossible to disable it. To do so,
+ * raising the initcall level of the watchdog driver is a solution.
+ * But in such case, the miscdev is maybe not ready (subsys_initcall), and
+ * watchdog_core need miscdev to register the watchdog as a char device.
+ *
+ * The deferred registration infrastructure offer a way for the watchdog
+ * subsystem to register a watchdog properly, even before miscdev is ready.
+ */
+
+static DEFINE_MUTEX(wtd_deferred_reg_mutex);
+static LIST_HEAD(wtd_deferred_reg_list);
+static bool wtd_deferred_reg_done;
+
+static void watchdog_deferred_registration_add(struct watchdog_device *wdd)
+{
+ list_add_tail(&wdd->deferred,
+ &wtd_deferred_reg_list);
+}
+
+static void watchdog_deferred_registration_del(struct watchdog_device *wdd)
+{
+ struct list_head *p, *n;
+ struct watchdog_device *wdd_tmp;
+
+ list_for_each_safe(p, n, &wtd_deferred_reg_list) {
+ wdd_tmp = list_entry(p, struct watchdog_device,
+ deferred);
+ if (wdd_tmp == wdd) {
+ list_del(&wdd_tmp->deferred);
+ break;
+ }
+ }
+}
+
+static void watchdog_check_min_max_timeout(struct watchdog_device *wdd)
+{
+ /*
+ * Check that we have valid min and max timeout values, if
+ * not reset them both to 0 (=not used or unknown)
+ */
+ if (!wdd->max_hw_heartbeat_ms && wdd->min_timeout > wdd->max_timeout) {
+ pr_info("Invalid min and max timeout values, resetting to 0!\n");
+ wdd->min_timeout = 0;
+ wdd->max_timeout = 0;
+ }
+}
+
+/**
+ * watchdog_init_timeout() - initialize the timeout field
+ * @wdd: watchdog device
+ * @timeout_parm: timeout module parameter
+ * @dev: Device that stores the timeout-sec property
+ *
+ * Initialize the timeout field of the watchdog_device struct with either the
+ * timeout module parameter (if it is valid value) or the timeout-sec property
+ * (only if it is a valid value and the timeout_parm is out of bounds).
+ * If none of them are valid then we keep the old value (which should normally
+ * be the default timeout value). Note that for the module parameter, '0' means
+ * 'use default' while it is an invalid value for the timeout-sec property.
+ * It should simply be dropped if you want to use the default value then.
+ *
+ * A zero is returned on success or -EINVAL if all provided values are out of
+ * bounds.
+ */
+int watchdog_init_timeout(struct watchdog_device *wdd,
+ unsigned int timeout_parm, struct device *dev)
+{
+ const char *dev_str = wdd->parent ? dev_name(wdd->parent) :
+ (const char *)wdd->info->identity;
+ unsigned int t = 0;
+ int ret = 0;
+
+ watchdog_check_min_max_timeout(wdd);
+
+ /* check the driver supplied value (likely a module parameter) first */
+ if (timeout_parm) {
+ if (!watchdog_timeout_invalid(wdd, timeout_parm)) {
+ wdd->timeout = timeout_parm;
+ return 0;
+ }
+ pr_err("%s: driver supplied timeout (%u) out of range\n",
+ dev_str, timeout_parm);
+ ret = -EINVAL;
+ }
+
+ /* try to get the timeout_sec property */
+ if (dev && dev->of_node &&
+ of_property_read_u32(dev->of_node, "timeout-sec", &t) == 0) {
+ if (t && !watchdog_timeout_invalid(wdd, t)) {
+ wdd->timeout = t;
+ return 0;
+ }
+ pr_err("%s: DT supplied timeout (%u) out of range\n", dev_str, t);
+ ret = -EINVAL;
+ }
+
+ if (ret < 0 && wdd->timeout)
+ pr_warn("%s: falling back to default timeout (%u)\n", dev_str,
+ wdd->timeout);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_init_timeout);
+
+static int watchdog_reboot_notifier(struct notifier_block *nb,
+ unsigned long code, void *data)
+{
+ struct watchdog_device *wdd;
+
+ wdd = container_of(nb, struct watchdog_device, reboot_nb);
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ if (watchdog_active(wdd) || watchdog_hw_running(wdd)) {
+ int ret;
+
+ ret = wdd->ops->stop(wdd);
+ trace_watchdog_stop(wdd, ret);
+ if (ret)
+ return NOTIFY_BAD;
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int watchdog_restart_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct watchdog_device *wdd = container_of(nb, struct watchdog_device,
+ restart_nb);
+
+ int ret;
+
+ ret = wdd->ops->restart(wdd, action, data);
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_DONE;
+}
+
+static int watchdog_pm_notifier(struct notifier_block *nb, unsigned long mode,
+ void *data)
+{
+ struct watchdog_device *wdd;
+ int ret = 0;
+
+ wdd = container_of(nb, struct watchdog_device, pm_nb);
+
+ switch (mode) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_RESTORE_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ ret = watchdog_dev_suspend(wdd);
+ break;
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ ret = watchdog_dev_resume(wdd);
+ break;
+ }
+
+ if (ret)
+ return NOTIFY_BAD;
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * watchdog_set_restart_priority - Change priority of restart handler
+ * @wdd: watchdog device
+ * @priority: priority of the restart handler, should follow these guidelines:
+ * 0: use watchdog's restart function as last resort, has limited restart
+ * capabilies
+ * 128: default restart handler, use if no other handler is expected to be
+ * available and/or if restart is sufficient to restart the entire system
+ * 255: preempt all other handlers
+ *
+ * If a wdd->ops->restart function is provided when watchdog_register_device is
+ * called, it will be registered as a restart handler with the priority given
+ * here.
+ */
+void watchdog_set_restart_priority(struct watchdog_device *wdd, int priority)
+{
+ wdd->restart_nb.priority = priority;
+}
+EXPORT_SYMBOL_GPL(watchdog_set_restart_priority);
+
+static int __watchdog_register_device(struct watchdog_device *wdd)
+{
+ int ret, id = -1;
+
+ if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL)
+ return -EINVAL;
+
+ /* Mandatory operations need to be supported */
+ if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms))
+ return -EINVAL;
+
+ watchdog_check_min_max_timeout(wdd);
+
+ /*
+ * Note: now that all watchdog_device data has been verified, we
+ * will not check this anymore in other functions. If data gets
+ * corrupted in a later stage then we expect a kernel panic!
+ */
+
+ /* Use alias for watchdog id if possible */
+ if (wdd->parent) {
+ ret = of_alias_get_id(wdd->parent->of_node, "watchdog");
+ if (ret >= 0)
+ id = ida_simple_get(&watchdog_ida, ret,
+ ret + 1, GFP_KERNEL);
+ }
+
+ if (id < 0)
+ id = ida_simple_get(&watchdog_ida, 0, MAX_DOGS, GFP_KERNEL);
+
+ if (id < 0)
+ return id;
+ wdd->id = id;
+
+ ret = watchdog_dev_register(wdd);
+ if (ret) {
+ ida_simple_remove(&watchdog_ida, id);
+ if (!(id == 0 && ret == -EBUSY))
+ return ret;
+
+ /* Retry in case a legacy watchdog module exists */
+ id = ida_simple_get(&watchdog_ida, 1, MAX_DOGS, GFP_KERNEL);
+ if (id < 0)
+ return id;
+ wdd->id = id;
+
+ ret = watchdog_dev_register(wdd);
+ if (ret) {
+ ida_simple_remove(&watchdog_ida, id);
+ return ret;
+ }
+ }
+
+ /* Module parameter to force watchdog policy on reboot. */
+ if (stop_on_reboot != -1) {
+ if (stop_on_reboot)
+ set_bit(WDOG_STOP_ON_REBOOT, &wdd->status);
+ else
+ clear_bit(WDOG_STOP_ON_REBOOT, &wdd->status);
+ }
+
+ if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status)) {
+ if (!wdd->ops->stop)
+ pr_warn("watchdog%d: stop_on_reboot not supported\n", wdd->id);
+ else {
+ wdd->reboot_nb.notifier_call = watchdog_reboot_notifier;
+
+ ret = register_reboot_notifier(&wdd->reboot_nb);
+ if (ret) {
+ pr_err("watchdog%d: Cannot register reboot notifier (%d)\n",
+ wdd->id, ret);
+ watchdog_dev_unregister(wdd);
+ ida_simple_remove(&watchdog_ida, id);
+ return ret;
+ }
+ }
+ }
+
+ if (wdd->ops->restart) {
+ wdd->restart_nb.notifier_call = watchdog_restart_notifier;
+
+ ret = register_restart_handler(&wdd->restart_nb);
+ if (ret)
+ pr_warn("watchdog%d: Cannot register restart handler (%d)\n",
+ wdd->id, ret);
+ }
+
+ if (test_bit(WDOG_NO_PING_ON_SUSPEND, &wdd->status)) {
+ wdd->pm_nb.notifier_call = watchdog_pm_notifier;
+
+ ret = register_pm_notifier(&wdd->pm_nb);
+ if (ret)
+ pr_warn("watchdog%d: Cannot register pm handler (%d)\n",
+ wdd->id, ret);
+ }
+
+ return 0;
+}
+
+/**
+ * watchdog_register_device() - register a watchdog device
+ * @wdd: watchdog device
+ *
+ * Register a watchdog device with the kernel so that the
+ * watchdog timer can be accessed from userspace.
+ *
+ * A zero is returned on success and a negative errno code for
+ * failure.
+ */
+
+int watchdog_register_device(struct watchdog_device *wdd)
+{
+ const char *dev_str;
+ int ret = 0;
+
+ mutex_lock(&wtd_deferred_reg_mutex);
+ if (wtd_deferred_reg_done)
+ ret = __watchdog_register_device(wdd);
+ else
+ watchdog_deferred_registration_add(wdd);
+ mutex_unlock(&wtd_deferred_reg_mutex);
+
+ if (ret) {
+ dev_str = wdd->parent ? dev_name(wdd->parent) :
+ (const char *)wdd->info->identity;
+ pr_err("%s: failed to register watchdog device (err = %d)\n",
+ dev_str, ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(watchdog_register_device);
+
+static void __watchdog_unregister_device(struct watchdog_device *wdd)
+{
+ if (wdd == NULL)
+ return;
+
+ if (wdd->ops->restart)
+ unregister_restart_handler(&wdd->restart_nb);
+
+ if (test_bit(WDOG_STOP_ON_REBOOT, &wdd->status))
+ unregister_reboot_notifier(&wdd->reboot_nb);
+
+ watchdog_dev_unregister(wdd);
+ ida_simple_remove(&watchdog_ida, wdd->id);
+}
+
+/**
+ * watchdog_unregister_device() - unregister a watchdog device
+ * @wdd: watchdog device to unregister
+ *
+ * Unregister a watchdog device that was previously successfully
+ * registered with watchdog_register_device().
+ */
+
+void watchdog_unregister_device(struct watchdog_device *wdd)
+{
+ mutex_lock(&wtd_deferred_reg_mutex);
+ if (wtd_deferred_reg_done)
+ __watchdog_unregister_device(wdd);
+ else
+ watchdog_deferred_registration_del(wdd);
+ mutex_unlock(&wtd_deferred_reg_mutex);
+}
+
+EXPORT_SYMBOL_GPL(watchdog_unregister_device);
+
+static void devm_watchdog_unregister_device(struct device *dev, void *res)
+{
+ watchdog_unregister_device(*(struct watchdog_device **)res);
+}
+
+/**
+ * devm_watchdog_register_device() - resource managed watchdog_register_device()
+ * @dev: device that is registering this watchdog device
+ * @wdd: watchdog device
+ *
+ * Managed watchdog_register_device(). For watchdog device registered by this
+ * function, watchdog_unregister_device() is automatically called on driver
+ * detach. See watchdog_register_device() for more information.
+ */
+int devm_watchdog_register_device(struct device *dev,
+ struct watchdog_device *wdd)
+{
+ struct watchdog_device **rcwdd;
+ int ret;
+
+ rcwdd = devres_alloc(devm_watchdog_unregister_device, sizeof(*rcwdd),
+ GFP_KERNEL);
+ if (!rcwdd)
+ return -ENOMEM;
+
+ ret = watchdog_register_device(wdd);
+ if (!ret) {
+ *rcwdd = wdd;
+ devres_add(dev, rcwdd);
+ } else {
+ devres_free(rcwdd);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(devm_watchdog_register_device);
+
+static int __init watchdog_deferred_registration(void)
+{
+ mutex_lock(&wtd_deferred_reg_mutex);
+ wtd_deferred_reg_done = true;
+ while (!list_empty(&wtd_deferred_reg_list)) {
+ struct watchdog_device *wdd;
+
+ wdd = list_first_entry(&wtd_deferred_reg_list,
+ struct watchdog_device, deferred);
+ list_del(&wdd->deferred);
+ __watchdog_register_device(wdd);
+ }
+ mutex_unlock(&wtd_deferred_reg_mutex);
+ return 0;
+}
+
+static int __init watchdog_init(void)
+{
+ int err;
+
+ err = watchdog_dev_init();
+ if (err < 0)
+ return err;
+
+ watchdog_deferred_registration();
+ return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+ watchdog_dev_exit();
+ ida_destroy(&watchdog_ida);
+}
+
+subsys_initcall_sync(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox <alan@lxorguk.ukuu.org.uk>");
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("WatchDog Timer Driver Core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/watchdog_core.h b/drivers/watchdog/watchdog_core.h
new file mode 100644
index 000000000..5b35a8439
--- /dev/null
+++ b/drivers/watchdog/watchdog_core.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * watchdog_core.h
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * (c) Copyright 2021 Hewlett Packard Enterprise Development LP.
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/kthread.h>
+
+#define MAX_DOGS 32 /* Maximum number of watchdog devices */
+
+/*
+ * struct watchdog_core_data - watchdog core internal data
+ * @dev: The watchdog's internal device
+ * @cdev: The watchdog's Character device.
+ * @wdd: Pointer to watchdog device.
+ * @lock: Lock for watchdog core.
+ * @status: Watchdog core internal status bits.
+ */
+struct watchdog_core_data {
+ struct device dev;
+ struct cdev cdev;
+ struct watchdog_device *wdd;
+ struct mutex lock;
+ ktime_t last_keepalive;
+ ktime_t last_hw_keepalive;
+ ktime_t open_deadline;
+ struct hrtimer timer;
+ struct kthread_work work;
+#if IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT)
+ struct hrtimer pretimeout_timer;
+#endif
+ unsigned long status; /* Internal status bits */
+#define _WDOG_DEV_OPEN 0 /* Opened ? */
+#define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */
+#define _WDOG_KEEPALIVE 2 /* Did we receive a keepalive ? */
+};
+
+/*
+ * Functions/procedures to be called by the core
+ */
+extern int watchdog_dev_register(struct watchdog_device *);
+extern void watchdog_dev_unregister(struct watchdog_device *);
+extern int __init watchdog_dev_init(void);
+extern void __exit watchdog_dev_exit(void);
+
+static inline bool watchdog_have_pretimeout(struct watchdog_device *wdd)
+{
+ return wdd->info->options & WDIOF_PRETIMEOUT ||
+ IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT);
+}
+
+#if IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT)
+void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd);
+void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd);
+void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd);
+#else
+static inline void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd) {}
+static inline void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd) {}
+static inline void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd) {}
+#endif
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
new file mode 100644
index 000000000..81684d89d
--- /dev/null
+++ b/drivers/watchdog/watchdog_dev.c
@@ -0,0 +1,1306 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * watchdog_dev.c
+ *
+ * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * (c) Copyright 2021 Hewlett Packard Enterprise Development LP.
+ *
+ * This source code is part of the generic code that can be used
+ * by all the watchdog timer drivers.
+ *
+ * This part of the generic code takes care of the following
+ * misc device: /dev/watchdog.
+ *
+ * Based on source code of the following authors:
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Rusty Lynch <rusty@linux.co.intel.com>
+ * Satyam Sharma <satyam@infradead.org>
+ * Randy Dunlap <randy.dunlap@oracle.com>
+ *
+ * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw.
+ * admit liability nor provide warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cdev.h> /* For character device */
+#include <linux/errno.h> /* For the -ENODEV/... values */
+#include <linux/fs.h> /* For file operations */
+#include <linux/init.h> /* For __init/__exit/... */
+#include <linux/hrtimer.h> /* For hrtimers */
+#include <linux/kernel.h> /* For printk/panic/... */
+#include <linux/kthread.h> /* For kthread_work */
+#include <linux/miscdevice.h> /* For handling misc devices */
+#include <linux/module.h> /* For module stuff/... */
+#include <linux/mutex.h> /* For mutexes */
+#include <linux/slab.h> /* For memory functions */
+#include <linux/types.h> /* For standard types (like size_t) */
+#include <linux/watchdog.h> /* For watchdog specific items */
+#include <linux/uaccess.h> /* For copy_to_user/put_user/... */
+
+#include "watchdog_core.h"
+#include "watchdog_pretimeout.h"
+
+#include <trace/events/watchdog.h>
+
+/* the dev_t structure to store the dynamically allocated watchdog devices */
+static dev_t watchdog_devt;
+/* Reference to watchdog device behind /dev/watchdog */
+static struct watchdog_core_data *old_wd_data;
+
+static struct kthread_worker *watchdog_kworker;
+
+static bool handle_boot_enabled =
+ IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED);
+
+static unsigned open_timeout = CONFIG_WATCHDOG_OPEN_TIMEOUT;
+
+static bool watchdog_past_open_deadline(struct watchdog_core_data *data)
+{
+ return ktime_after(ktime_get(), data->open_deadline);
+}
+
+static void watchdog_set_open_deadline(struct watchdog_core_data *data)
+{
+ data->open_deadline = open_timeout ?
+ ktime_get() + ktime_set(open_timeout, 0) : KTIME_MAX;
+}
+
+static inline bool watchdog_need_worker(struct watchdog_device *wdd)
+{
+ /* All variables in milli-seconds */
+ unsigned int hm = wdd->max_hw_heartbeat_ms;
+ unsigned int t = wdd->timeout * 1000;
+
+ /*
+ * A worker to generate heartbeat requests is needed if all of the
+ * following conditions are true.
+ * - Userspace activated the watchdog.
+ * - The driver provided a value for the maximum hardware timeout, and
+ * thus is aware that the framework supports generating heartbeat
+ * requests.
+ * - Userspace requests a longer timeout than the hardware can handle.
+ *
+ * Alternatively, if userspace has not opened the watchdog
+ * device, we take care of feeding the watchdog if it is
+ * running.
+ */
+ return (hm && watchdog_active(wdd) && t > hm) ||
+ (t && !watchdog_active(wdd) && watchdog_hw_running(wdd));
+}
+
+static ktime_t watchdog_next_keepalive(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ unsigned int timeout_ms = wdd->timeout * 1000;
+ ktime_t keepalive_interval;
+ ktime_t last_heartbeat, latest_heartbeat;
+ ktime_t virt_timeout;
+ unsigned int hw_heartbeat_ms;
+
+ if (watchdog_active(wdd))
+ virt_timeout = ktime_add(wd_data->last_keepalive,
+ ms_to_ktime(timeout_ms));
+ else
+ virt_timeout = wd_data->open_deadline;
+
+ hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms);
+ keepalive_interval = ms_to_ktime(hw_heartbeat_ms / 2);
+
+ /*
+ * To ensure that the watchdog times out wdd->timeout seconds
+ * after the most recent ping from userspace, the last
+ * worker ping has to come in hw_heartbeat_ms before this timeout.
+ */
+ last_heartbeat = ktime_sub(virt_timeout, ms_to_ktime(hw_heartbeat_ms));
+ latest_heartbeat = ktime_sub(last_heartbeat, ktime_get());
+ if (ktime_before(latest_heartbeat, keepalive_interval))
+ return latest_heartbeat;
+ return keepalive_interval;
+}
+
+static inline void watchdog_update_worker(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+
+ if (watchdog_need_worker(wdd)) {
+ ktime_t t = watchdog_next_keepalive(wdd);
+
+ if (t > 0)
+ hrtimer_start(&wd_data->timer, t,
+ HRTIMER_MODE_REL_HARD);
+ } else {
+ hrtimer_cancel(&wd_data->timer);
+ }
+}
+
+static int __watchdog_ping(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ ktime_t earliest_keepalive, now;
+ int err;
+
+ earliest_keepalive = ktime_add(wd_data->last_hw_keepalive,
+ ms_to_ktime(wdd->min_hw_heartbeat_ms));
+ now = ktime_get();
+
+ if (ktime_after(earliest_keepalive, now)) {
+ hrtimer_start(&wd_data->timer,
+ ktime_sub(earliest_keepalive, now),
+ HRTIMER_MODE_REL_HARD);
+ return 0;
+ }
+
+ wd_data->last_hw_keepalive = now;
+
+ if (wdd->ops->ping) {
+ err = wdd->ops->ping(wdd); /* ping the watchdog */
+ trace_watchdog_ping(wdd, err);
+ } else {
+ err = wdd->ops->start(wdd); /* restart watchdog */
+ trace_watchdog_start(wdd, err);
+ }
+
+ if (err == 0)
+ watchdog_hrtimer_pretimeout_start(wdd);
+
+ watchdog_update_worker(wdd);
+
+ return err;
+}
+
+/*
+ * watchdog_ping - ping the watchdog
+ * @wdd: The watchdog device to ping
+ *
+ * If the watchdog has no own ping operation then it needs to be
+ * restarted via the start operation. This wrapper function does
+ * exactly that.
+ * We only ping when the watchdog device is running.
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 on success, error otherwise.
+ */
+static int watchdog_ping(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+
+ if (!watchdog_active(wdd) && !watchdog_hw_running(wdd))
+ return 0;
+
+ set_bit(_WDOG_KEEPALIVE, &wd_data->status);
+
+ wd_data->last_keepalive = ktime_get();
+ return __watchdog_ping(wdd);
+}
+
+static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data)
+{
+ struct watchdog_device *wdd = wd_data->wdd;
+
+ if (!wdd)
+ return false;
+
+ if (watchdog_active(wdd))
+ return true;
+
+ return watchdog_hw_running(wdd) && !watchdog_past_open_deadline(wd_data);
+}
+
+static void watchdog_ping_work(struct kthread_work *work)
+{
+ struct watchdog_core_data *wd_data;
+
+ wd_data = container_of(work, struct watchdog_core_data, work);
+
+ mutex_lock(&wd_data->lock);
+ if (watchdog_worker_should_ping(wd_data))
+ __watchdog_ping(wd_data->wdd);
+ mutex_unlock(&wd_data->lock);
+}
+
+static enum hrtimer_restart watchdog_timer_expired(struct hrtimer *timer)
+{
+ struct watchdog_core_data *wd_data;
+
+ wd_data = container_of(timer, struct watchdog_core_data, timer);
+
+ kthread_queue_work(watchdog_kworker, &wd_data->work);
+ return HRTIMER_NORESTART;
+}
+
+/*
+ * watchdog_start - wrapper to start the watchdog
+ * @wdd: The watchdog device to start
+ *
+ * Start the watchdog if it is not active and mark it active.
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 on success or a negative errno code for failure.
+ */
+static int watchdog_start(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ ktime_t started_at;
+ int err;
+
+ if (watchdog_active(wdd))
+ return 0;
+
+ set_bit(_WDOG_KEEPALIVE, &wd_data->status);
+
+ started_at = ktime_get();
+ if (watchdog_hw_running(wdd) && wdd->ops->ping) {
+ err = __watchdog_ping(wdd);
+ if (err == 0) {
+ set_bit(WDOG_ACTIVE, &wdd->status);
+ watchdog_hrtimer_pretimeout_start(wdd);
+ }
+ } else {
+ err = wdd->ops->start(wdd);
+ trace_watchdog_start(wdd, err);
+ if (err == 0) {
+ set_bit(WDOG_ACTIVE, &wdd->status);
+ wd_data->last_keepalive = started_at;
+ wd_data->last_hw_keepalive = started_at;
+ watchdog_update_worker(wdd);
+ watchdog_hrtimer_pretimeout_start(wdd);
+ }
+ }
+
+ return err;
+}
+
+/*
+ * watchdog_stop - wrapper to stop the watchdog
+ * @wdd: The watchdog device to stop
+ *
+ * Stop the watchdog if it is still active and unmark it active.
+ * If the 'nowayout' feature was set, the watchdog cannot be stopped.
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 on success or a negative errno code for failure.
+ */
+static int watchdog_stop(struct watchdog_device *wdd)
+{
+ int err = 0;
+
+ if (!watchdog_active(wdd))
+ return 0;
+
+ if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) {
+ pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n",
+ wdd->id);
+ return -EBUSY;
+ }
+
+ if (wdd->ops->stop) {
+ clear_bit(WDOG_HW_RUNNING, &wdd->status);
+ err = wdd->ops->stop(wdd);
+ trace_watchdog_stop(wdd, err);
+ } else {
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+ }
+
+ if (err == 0) {
+ clear_bit(WDOG_ACTIVE, &wdd->status);
+ watchdog_update_worker(wdd);
+ watchdog_hrtimer_pretimeout_stop(wdd);
+ }
+
+ return err;
+}
+
+/*
+ * watchdog_get_status - wrapper to get the watchdog status
+ * @wdd: The watchdog device to get the status from
+ *
+ * Get the watchdog's status flags.
+ * The caller must hold wd_data->lock.
+ *
+ * Return: watchdog's status flags.
+ */
+static unsigned int watchdog_get_status(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ unsigned int status;
+
+ if (wdd->ops->status)
+ status = wdd->ops->status(wdd);
+ else
+ status = wdd->bootstatus & (WDIOF_CARDRESET |
+ WDIOF_OVERHEAT |
+ WDIOF_FANFAULT |
+ WDIOF_EXTERN1 |
+ WDIOF_EXTERN2 |
+ WDIOF_POWERUNDER |
+ WDIOF_POWEROVER);
+
+ if (test_bit(_WDOG_ALLOW_RELEASE, &wd_data->status))
+ status |= WDIOF_MAGICCLOSE;
+
+ if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status))
+ status |= WDIOF_KEEPALIVEPING;
+
+ if (IS_ENABLED(CONFIG_WATCHDOG_HRTIMER_PRETIMEOUT))
+ status |= WDIOF_PRETIMEOUT;
+
+ return status;
+}
+
+/*
+ * watchdog_set_timeout - set the watchdog timer timeout
+ * @wdd: The watchdog device to set the timeout for
+ * @timeout: Timeout to set in seconds
+ *
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ int err = 0;
+
+ if (!(wdd->info->options & WDIOF_SETTIMEOUT))
+ return -EOPNOTSUPP;
+
+ if (watchdog_timeout_invalid(wdd, timeout))
+ return -EINVAL;
+
+ if (wdd->ops->set_timeout) {
+ err = wdd->ops->set_timeout(wdd, timeout);
+ trace_watchdog_set_timeout(wdd, timeout, err);
+ } else {
+ wdd->timeout = timeout;
+ /* Disable pretimeout if it doesn't fit the new timeout */
+ if (wdd->pretimeout >= wdd->timeout)
+ wdd->pretimeout = 0;
+ }
+
+ watchdog_update_worker(wdd);
+
+ return err;
+}
+
+/*
+ * watchdog_set_pretimeout - set the watchdog timer pretimeout
+ * @wdd: The watchdog device to set the timeout for
+ * @timeout: pretimeout to set in seconds
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_set_pretimeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ int err = 0;
+
+ if (!watchdog_have_pretimeout(wdd))
+ return -EOPNOTSUPP;
+
+ if (watchdog_pretimeout_invalid(wdd, timeout))
+ return -EINVAL;
+
+ if (wdd->ops->set_pretimeout && (wdd->info->options & WDIOF_PRETIMEOUT))
+ err = wdd->ops->set_pretimeout(wdd, timeout);
+ else
+ wdd->pretimeout = timeout;
+
+ return err;
+}
+
+/*
+ * watchdog_get_timeleft - wrapper to get the time left before a reboot
+ * @wdd: The watchdog device to get the remaining time from
+ * @timeleft: The time that's left
+ *
+ * Get the time before a watchdog will reboot (if not pinged).
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_get_timeleft(struct watchdog_device *wdd,
+ unsigned int *timeleft)
+{
+ *timeleft = 0;
+
+ if (!wdd->ops->get_timeleft)
+ return -EOPNOTSUPP;
+
+ *timeleft = wdd->ops->get_timeleft(wdd);
+
+ return 0;
+}
+
+#ifdef CONFIG_WATCHDOG_SYSFS
+static ssize_t nowayout_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", !!test_bit(WDOG_NO_WAY_OUT,
+ &wdd->status));
+}
+
+static ssize_t nowayout_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ unsigned int value;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &value);
+ if (ret)
+ return ret;
+ if (value > 1)
+ return -EINVAL;
+ /* nowayout cannot be disabled once set */
+ if (test_bit(WDOG_NO_WAY_OUT, &wdd->status) && !value)
+ return -EPERM;
+ watchdog_set_nowayout(wdd, value);
+ return len;
+}
+static DEVICE_ATTR_RW(nowayout);
+
+static ssize_t status_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ unsigned int status;
+
+ mutex_lock(&wd_data->lock);
+ status = watchdog_get_status(wdd);
+ mutex_unlock(&wd_data->lock);
+
+ return sysfs_emit(buf, "0x%x\n", status);
+}
+static DEVICE_ATTR_RO(status);
+
+static ssize_t bootstatus_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->bootstatus);
+}
+static DEVICE_ATTR_RO(bootstatus);
+
+static ssize_t timeleft_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ ssize_t status;
+ unsigned int val;
+
+ mutex_lock(&wd_data->lock);
+ status = watchdog_get_timeleft(wdd, &val);
+ mutex_unlock(&wd_data->lock);
+ if (!status)
+ status = sysfs_emit(buf, "%u\n", val);
+
+ return status;
+}
+static DEVICE_ATTR_RO(timeleft);
+
+static ssize_t timeout_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->timeout);
+}
+static DEVICE_ATTR_RO(timeout);
+
+static ssize_t min_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->min_timeout);
+}
+static DEVICE_ATTR_RO(min_timeout);
+
+static ssize_t max_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->max_timeout);
+}
+static DEVICE_ATTR_RO(max_timeout);
+
+static ssize_t pretimeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%u\n", wdd->pretimeout);
+}
+static DEVICE_ATTR_RO(pretimeout);
+
+static ssize_t identity_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n", wdd->info->identity);
+}
+static DEVICE_ATTR_RO(identity);
+
+static ssize_t state_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ if (watchdog_active(wdd))
+ return sysfs_emit(buf, "active\n");
+
+ return sysfs_emit(buf, "inactive\n");
+}
+static DEVICE_ATTR_RO(state);
+
+static ssize_t pretimeout_available_governors_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return watchdog_pretimeout_available_governors_get(buf);
+}
+static DEVICE_ATTR_RO(pretimeout_available_governors);
+
+static ssize_t pretimeout_governor_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+
+ return watchdog_pretimeout_governor_get(wdd, buf);
+}
+
+static ssize_t pretimeout_governor_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ int ret = watchdog_pretimeout_governor_set(wdd, buf);
+
+ if (!ret)
+ ret = count;
+
+ return ret;
+}
+static DEVICE_ATTR_RW(pretimeout_governor);
+
+static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct watchdog_device *wdd = dev_get_drvdata(dev);
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft)
+ mode = 0;
+ else if (attr == &dev_attr_pretimeout.attr && !watchdog_have_pretimeout(wdd))
+ mode = 0;
+ else if ((attr == &dev_attr_pretimeout_governor.attr ||
+ attr == &dev_attr_pretimeout_available_governors.attr) &&
+ (!watchdog_have_pretimeout(wdd) || !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)))
+ mode = 0;
+
+ return mode;
+}
+static struct attribute *wdt_attrs[] = {
+ &dev_attr_state.attr,
+ &dev_attr_identity.attr,
+ &dev_attr_timeout.attr,
+ &dev_attr_min_timeout.attr,
+ &dev_attr_max_timeout.attr,
+ &dev_attr_pretimeout.attr,
+ &dev_attr_timeleft.attr,
+ &dev_attr_bootstatus.attr,
+ &dev_attr_status.attr,
+ &dev_attr_nowayout.attr,
+ &dev_attr_pretimeout_governor.attr,
+ &dev_attr_pretimeout_available_governors.attr,
+ NULL,
+};
+
+static const struct attribute_group wdt_group = {
+ .attrs = wdt_attrs,
+ .is_visible = wdt_is_visible,
+};
+__ATTRIBUTE_GROUPS(wdt);
+#else
+#define wdt_groups NULL
+#endif
+
+/*
+ * watchdog_ioctl_op - call the watchdog drivers ioctl op if defined
+ * @wdd: The watchdog device to do the ioctl on
+ * @cmd: Watchdog command
+ * @arg: Argument pointer
+ *
+ * The caller must hold wd_data->lock.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd,
+ unsigned long arg)
+{
+ if (!wdd->ops->ioctl)
+ return -ENOIOCTLCMD;
+
+ return wdd->ops->ioctl(wdd, cmd, arg);
+}
+
+/*
+ * watchdog_write - writes to the watchdog
+ * @file: File from VFS
+ * @data: User address of data
+ * @len: Length of data
+ * @ppos: Pointer to the file offset
+ *
+ * A write to a watchdog device is defined as a keepalive ping.
+ * Writing the magic 'V' sequence allows the next close to turn
+ * off the watchdog (if 'nowayout' is not set).
+ *
+ * Return: @len if successful, error otherwise.
+ */
+static ssize_t watchdog_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ struct watchdog_core_data *wd_data = file->private_data;
+ struct watchdog_device *wdd;
+ int err;
+ size_t i;
+ char c;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * Note: just in case someone wrote the magic character
+ * five months ago...
+ */
+ clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status);
+ }
+
+ /* someone wrote to us, so we send the watchdog a keepalive ping */
+
+ err = -ENODEV;
+ mutex_lock(&wd_data->lock);
+ wdd = wd_data->wdd;
+ if (wdd)
+ err = watchdog_ping(wdd);
+ mutex_unlock(&wd_data->lock);
+
+ if (err < 0)
+ return err;
+
+ return len;
+}
+
+/*
+ * watchdog_ioctl - handle the different ioctl's for the watchdog device
+ * @file: File handle to the device
+ * @cmd: Watchdog command
+ * @arg: Argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+
+static long watchdog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct watchdog_core_data *wd_data = file->private_data;
+ void __user *argp = (void __user *)arg;
+ struct watchdog_device *wdd;
+ int __user *p = argp;
+ unsigned int val;
+ int err;
+
+ mutex_lock(&wd_data->lock);
+
+ wdd = wd_data->wdd;
+ if (!wdd) {
+ err = -ENODEV;
+ goto out_ioctl;
+ }
+
+ err = watchdog_ioctl_op(wdd, cmd, arg);
+ if (err != -ENOIOCTLCMD)
+ goto out_ioctl;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ err = copy_to_user(argp, wdd->info,
+ sizeof(struct watchdog_info)) ? -EFAULT : 0;
+ break;
+ case WDIOC_GETSTATUS:
+ val = watchdog_get_status(wdd);
+ err = put_user(val, p);
+ break;
+ case WDIOC_GETBOOTSTATUS:
+ err = put_user(wdd->bootstatus, p);
+ break;
+ case WDIOC_SETOPTIONS:
+ if (get_user(val, p)) {
+ err = -EFAULT;
+ break;
+ }
+ if (val & WDIOS_DISABLECARD) {
+ err = watchdog_stop(wdd);
+ if (err < 0)
+ break;
+ }
+ if (val & WDIOS_ENABLECARD)
+ err = watchdog_start(wdd);
+ break;
+ case WDIOC_KEEPALIVE:
+ if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) {
+ err = -EOPNOTSUPP;
+ break;
+ }
+ err = watchdog_ping(wdd);
+ break;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(val, p)) {
+ err = -EFAULT;
+ break;
+ }
+ err = watchdog_set_timeout(wdd, val);
+ if (err < 0)
+ break;
+ /* If the watchdog is active then we send a keepalive ping
+ * to make sure that the watchdog keep's running (and if
+ * possible that it takes the new timeout) */
+ err = watchdog_ping(wdd);
+ if (err < 0)
+ break;
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ /* timeout == 0 means that we don't know the timeout */
+ if (wdd->timeout == 0) {
+ err = -EOPNOTSUPP;
+ break;
+ }
+ err = put_user(wdd->timeout, p);
+ break;
+ case WDIOC_GETTIMELEFT:
+ err = watchdog_get_timeleft(wdd, &val);
+ if (err < 0)
+ break;
+ err = put_user(val, p);
+ break;
+ case WDIOC_SETPRETIMEOUT:
+ if (get_user(val, p)) {
+ err = -EFAULT;
+ break;
+ }
+ err = watchdog_set_pretimeout(wdd, val);
+ break;
+ case WDIOC_GETPRETIMEOUT:
+ err = put_user(wdd->pretimeout, p);
+ break;
+ default:
+ err = -ENOTTY;
+ break;
+ }
+
+out_ioctl:
+ mutex_unlock(&wd_data->lock);
+ return err;
+}
+
+/*
+ * watchdog_open - open the /dev/watchdog* devices
+ * @inode: Inode of device
+ * @file: File handle to device
+ *
+ * When the /dev/watchdog* device gets opened, we start the watchdog.
+ * Watch out: the /dev/watchdog device is single open, so we make sure
+ * it can only be opened once.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_open(struct inode *inode, struct file *file)
+{
+ struct watchdog_core_data *wd_data;
+ struct watchdog_device *wdd;
+ bool hw_running;
+ int err;
+
+ /* Get the corresponding watchdog device */
+ if (imajor(inode) == MISC_MAJOR)
+ wd_data = old_wd_data;
+ else
+ wd_data = container_of(inode->i_cdev, struct watchdog_core_data,
+ cdev);
+
+ /* the watchdog is single open! */
+ if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status))
+ return -EBUSY;
+
+ wdd = wd_data->wdd;
+
+ /*
+ * If the /dev/watchdog device is open, we don't want the module
+ * to be unloaded.
+ */
+ hw_running = watchdog_hw_running(wdd);
+ if (!hw_running && !try_module_get(wdd->ops->owner)) {
+ err = -EBUSY;
+ goto out_clear;
+ }
+
+ err = watchdog_start(wdd);
+ if (err < 0)
+ goto out_mod;
+
+ file->private_data = wd_data;
+
+ if (!hw_running)
+ get_device(&wd_data->dev);
+
+ /*
+ * open_timeout only applies for the first open from
+ * userspace. Set open_deadline to infinity so that the kernel
+ * will take care of an always-running hardware watchdog in
+ * case the device gets magic-closed or WDIOS_DISABLECARD is
+ * applied.
+ */
+ wd_data->open_deadline = KTIME_MAX;
+
+ /* dev/watchdog is a virtual (and thus non-seekable) filesystem */
+ return stream_open(inode, file);
+
+out_mod:
+ module_put(wd_data->wdd->ops->owner);
+out_clear:
+ clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
+ return err;
+}
+
+static void watchdog_core_data_release(struct device *dev)
+{
+ struct watchdog_core_data *wd_data;
+
+ wd_data = container_of(dev, struct watchdog_core_data, dev);
+
+ kfree(wd_data);
+}
+
+/*
+ * watchdog_release - release the watchdog device
+ * @inode: Inode of device
+ * @file: File handle to device
+ *
+ * This is the code for when /dev/watchdog gets closed. We will only
+ * stop the watchdog when we have received the magic char (and nowayout
+ * was not set), else the watchdog will keep running.
+ *
+ * Always returns 0.
+ */
+static int watchdog_release(struct inode *inode, struct file *file)
+{
+ struct watchdog_core_data *wd_data = file->private_data;
+ struct watchdog_device *wdd;
+ int err = -EBUSY;
+ bool running;
+
+ mutex_lock(&wd_data->lock);
+
+ wdd = wd_data->wdd;
+ if (!wdd)
+ goto done;
+
+ /*
+ * We only stop the watchdog if we received the magic character
+ * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then
+ * watchdog_stop will fail.
+ */
+ if (!watchdog_active(wdd))
+ err = 0;
+ else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) ||
+ !(wdd->info->options & WDIOF_MAGICCLOSE))
+ err = watchdog_stop(wdd);
+
+ /* If the watchdog was not stopped, send a keepalive ping */
+ if (err < 0) {
+ pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id);
+ watchdog_ping(wdd);
+ }
+
+ watchdog_update_worker(wdd);
+
+ /* make sure that /dev/watchdog can be re-opened */
+ clear_bit(_WDOG_DEV_OPEN, &wd_data->status);
+
+done:
+ running = wdd && watchdog_hw_running(wdd);
+ mutex_unlock(&wd_data->lock);
+ /*
+ * Allow the owner module to be unloaded again unless the watchdog
+ * is still running. If the watchdog is still running, it can not
+ * be stopped, and its driver must not be unloaded.
+ */
+ if (!running) {
+ module_put(wd_data->cdev.owner);
+ put_device(&wd_data->dev);
+ }
+ return 0;
+}
+
+static const struct file_operations watchdog_fops = {
+ .owner = THIS_MODULE,
+ .write = watchdog_write,
+ .unlocked_ioctl = watchdog_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = watchdog_open,
+ .release = watchdog_release,
+};
+
+static struct miscdevice watchdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &watchdog_fops,
+};
+
+static struct class watchdog_class = {
+ .name = "watchdog",
+ .owner = THIS_MODULE,
+ .dev_groups = wdt_groups,
+};
+
+/*
+ * watchdog_cdev_register - register watchdog character device
+ * @wdd: Watchdog device
+ *
+ * Register a watchdog character device including handling the legacy
+ * /dev/watchdog node. /dev/watchdog is actually a miscdevice and
+ * thus we set it up like that.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+static int watchdog_cdev_register(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data;
+ int err;
+
+ wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL);
+ if (!wd_data)
+ return -ENOMEM;
+ mutex_init(&wd_data->lock);
+
+ wd_data->wdd = wdd;
+ wdd->wd_data = wd_data;
+
+ if (IS_ERR_OR_NULL(watchdog_kworker)) {
+ kfree(wd_data);
+ return -ENODEV;
+ }
+
+ device_initialize(&wd_data->dev);
+ wd_data->dev.devt = MKDEV(MAJOR(watchdog_devt), wdd->id);
+ wd_data->dev.class = &watchdog_class;
+ wd_data->dev.parent = wdd->parent;
+ wd_data->dev.groups = wdd->groups;
+ wd_data->dev.release = watchdog_core_data_release;
+ dev_set_drvdata(&wd_data->dev, wdd);
+ err = dev_set_name(&wd_data->dev, "watchdog%d", wdd->id);
+ if (err) {
+ put_device(&wd_data->dev);
+ return err;
+ }
+
+ kthread_init_work(&wd_data->work, watchdog_ping_work);
+ hrtimer_init(&wd_data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_HARD);
+ wd_data->timer.function = watchdog_timer_expired;
+ watchdog_hrtimer_pretimeout_init(wdd);
+
+ if (wdd->id == 0) {
+ old_wd_data = wd_data;
+ watchdog_miscdev.parent = wdd->parent;
+ err = misc_register(&watchdog_miscdev);
+ if (err != 0) {
+ pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n",
+ wdd->info->identity, WATCHDOG_MINOR, err);
+ if (err == -EBUSY)
+ pr_err("%s: a legacy watchdog module is probably present.\n",
+ wdd->info->identity);
+ old_wd_data = NULL;
+ put_device(&wd_data->dev);
+ return err;
+ }
+ }
+
+ /* Fill in the data structures */
+ cdev_init(&wd_data->cdev, &watchdog_fops);
+ wd_data->cdev.owner = wdd->ops->owner;
+
+ /* Add the device */
+ err = cdev_device_add(&wd_data->cdev, &wd_data->dev);
+ if (err) {
+ pr_err("watchdog%d unable to add device %d:%d\n",
+ wdd->id, MAJOR(watchdog_devt), wdd->id);
+ if (wdd->id == 0) {
+ misc_deregister(&watchdog_miscdev);
+ old_wd_data = NULL;
+ }
+ put_device(&wd_data->dev);
+ return err;
+ }
+
+ /* Record time of most recent heartbeat as 'just before now'. */
+ wd_data->last_hw_keepalive = ktime_sub(ktime_get(), 1);
+ watchdog_set_open_deadline(wd_data);
+
+ /*
+ * If the watchdog is running, prevent its driver from being unloaded,
+ * and schedule an immediate ping.
+ */
+ if (watchdog_hw_running(wdd)) {
+ __module_get(wdd->ops->owner);
+ get_device(&wd_data->dev);
+ if (handle_boot_enabled)
+ hrtimer_start(&wd_data->timer, 0,
+ HRTIMER_MODE_REL_HARD);
+ else
+ pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n",
+ wdd->id);
+ }
+
+ return 0;
+}
+
+/*
+ * watchdog_cdev_unregister - unregister watchdog character device
+ * @wdd: Watchdog device
+ *
+ * Unregister watchdog character device and if needed the legacy
+ * /dev/watchdog device.
+ */
+static void watchdog_cdev_unregister(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+
+ cdev_device_del(&wd_data->cdev, &wd_data->dev);
+ if (wdd->id == 0) {
+ misc_deregister(&watchdog_miscdev);
+ old_wd_data = NULL;
+ }
+
+ if (watchdog_active(wdd) &&
+ test_bit(WDOG_STOP_ON_UNREGISTER, &wdd->status)) {
+ watchdog_stop(wdd);
+ }
+
+ watchdog_hrtimer_pretimeout_stop(wdd);
+
+ mutex_lock(&wd_data->lock);
+ wd_data->wdd = NULL;
+ wdd->wd_data = NULL;
+ mutex_unlock(&wd_data->lock);
+
+ hrtimer_cancel(&wd_data->timer);
+ kthread_cancel_work_sync(&wd_data->work);
+
+ put_device(&wd_data->dev);
+}
+
+/**
+ * watchdog_dev_register - register a watchdog device
+ * @wdd: Watchdog device
+ *
+ * Register a watchdog device including handling the legacy
+ * /dev/watchdog node. /dev/watchdog is actually a miscdevice and
+ * thus we set it up like that.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int watchdog_dev_register(struct watchdog_device *wdd)
+{
+ int ret;
+
+ ret = watchdog_cdev_register(wdd);
+ if (ret)
+ return ret;
+
+ ret = watchdog_register_pretimeout(wdd);
+ if (ret)
+ watchdog_cdev_unregister(wdd);
+
+ return ret;
+}
+
+/**
+ * watchdog_dev_unregister - unregister a watchdog device
+ * @wdd: watchdog device
+ *
+ * Unregister watchdog device and if needed the legacy
+ * /dev/watchdog device.
+ */
+void watchdog_dev_unregister(struct watchdog_device *wdd)
+{
+ watchdog_unregister_pretimeout(wdd);
+ watchdog_cdev_unregister(wdd);
+}
+
+/**
+ * watchdog_set_last_hw_keepalive - set last HW keepalive time for watchdog
+ * @wdd: Watchdog device
+ * @last_ping_ms: Time since last HW heartbeat
+ *
+ * Adjusts the last known HW keepalive time for a watchdog timer.
+ * This is needed if the watchdog is already running when the probe
+ * function is called, and it can't be pinged immediately. This
+ * function must be called immediately after watchdog registration,
+ * and min_hw_heartbeat_ms must be set for this to be useful.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int watchdog_set_last_hw_keepalive(struct watchdog_device *wdd,
+ unsigned int last_ping_ms)
+{
+ struct watchdog_core_data *wd_data;
+ ktime_t now;
+
+ if (!wdd)
+ return -EINVAL;
+
+ wd_data = wdd->wd_data;
+
+ now = ktime_get();
+
+ wd_data->last_hw_keepalive = ktime_sub(now, ms_to_ktime(last_ping_ms));
+
+ if (watchdog_hw_running(wdd) && handle_boot_enabled)
+ return __watchdog_ping(wdd);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(watchdog_set_last_hw_keepalive);
+
+/**
+ * watchdog_dev_init - init dev part of watchdog core
+ *
+ * Allocate a range of chardev nodes to use for watchdog devices.
+ *
+ * Return: 0 if successful, error otherwise.
+ */
+int __init watchdog_dev_init(void)
+{
+ int err;
+
+ watchdog_kworker = kthread_create_worker(0, "watchdogd");
+ if (IS_ERR(watchdog_kworker)) {
+ pr_err("Failed to create watchdog kworker\n");
+ return PTR_ERR(watchdog_kworker);
+ }
+ sched_set_fifo(watchdog_kworker->task);
+
+ err = class_register(&watchdog_class);
+ if (err < 0) {
+ pr_err("couldn't register class\n");
+ goto err_register;
+ }
+
+ err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog");
+ if (err < 0) {
+ pr_err("watchdog: unable to allocate char dev region\n");
+ goto err_alloc;
+ }
+
+ return 0;
+
+err_alloc:
+ class_unregister(&watchdog_class);
+err_register:
+ kthread_destroy_worker(watchdog_kworker);
+ return err;
+}
+
+/**
+ * watchdog_dev_exit - exit dev part of watchdog core
+ *
+ * Release the range of chardev nodes used for watchdog devices.
+ */
+void __exit watchdog_dev_exit(void)
+{
+ unregister_chrdev_region(watchdog_devt, MAX_DOGS);
+ class_unregister(&watchdog_class);
+ kthread_destroy_worker(watchdog_kworker);
+}
+
+int watchdog_dev_suspend(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ int ret = 0;
+
+ if (!wdd->wd_data)
+ return -ENODEV;
+
+ /* ping for the last time before suspend */
+ mutex_lock(&wd_data->lock);
+ if (watchdog_worker_should_ping(wd_data))
+ ret = __watchdog_ping(wd_data->wdd);
+ mutex_unlock(&wd_data->lock);
+
+ if (ret)
+ return ret;
+
+ /*
+ * make sure that watchdog worker will not kick in when the wdog is
+ * suspended
+ */
+ hrtimer_cancel(&wd_data->timer);
+ kthread_cancel_work_sync(&wd_data->work);
+
+ return 0;
+}
+
+int watchdog_dev_resume(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+ int ret = 0;
+
+ if (!wdd->wd_data)
+ return -ENODEV;
+
+ /*
+ * __watchdog_ping will also retrigger hrtimer and therefore restore the
+ * ping worker if needed.
+ */
+ mutex_lock(&wd_data->lock);
+ if (watchdog_worker_should_ping(wd_data))
+ ret = __watchdog_ping(wd_data->wdd);
+ mutex_unlock(&wd_data->lock);
+
+ return ret;
+}
+
+module_param(handle_boot_enabled, bool, 0444);
+MODULE_PARM_DESC(handle_boot_enabled,
+ "Watchdog core auto-updates boot enabled watchdogs before userspace takes over (default="
+ __MODULE_STRING(IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED)) ")");
+
+module_param(open_timeout, uint, 0644);
+MODULE_PARM_DESC(open_timeout,
+ "Maximum time (in seconds, 0 means infinity) for userspace to take over a running watchdog (default="
+ __MODULE_STRING(CONFIG_WATCHDOG_OPEN_TIMEOUT) ")");
diff --git a/drivers/watchdog/watchdog_hrtimer_pretimeout.c b/drivers/watchdog/watchdog_hrtimer_pretimeout.c
new file mode 100644
index 000000000..940b53718
--- /dev/null
+++ b/drivers/watchdog/watchdog_hrtimer_pretimeout.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (c) Copyright 2021 Hewlett Packard Enterprise Development LP.
+ */
+
+#include <linux/hrtimer.h>
+#include <linux/watchdog.h>
+
+#include "watchdog_core.h"
+#include "watchdog_pretimeout.h"
+
+static enum hrtimer_restart watchdog_hrtimer_pretimeout(struct hrtimer *timer)
+{
+ struct watchdog_core_data *wd_data;
+
+ wd_data = container_of(timer, struct watchdog_core_data, pretimeout_timer);
+
+ watchdog_notify_pretimeout(wd_data->wdd);
+ return HRTIMER_NORESTART;
+}
+
+void watchdog_hrtimer_pretimeout_init(struct watchdog_device *wdd)
+{
+ struct watchdog_core_data *wd_data = wdd->wd_data;
+
+ hrtimer_init(&wd_data->pretimeout_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ wd_data->pretimeout_timer.function = watchdog_hrtimer_pretimeout;
+}
+
+void watchdog_hrtimer_pretimeout_start(struct watchdog_device *wdd)
+{
+ if (!(wdd->info->options & WDIOF_PRETIMEOUT) &&
+ !watchdog_pretimeout_invalid(wdd, wdd->pretimeout))
+ hrtimer_start(&wdd->wd_data->pretimeout_timer,
+ ktime_set(wdd->timeout - wdd->pretimeout, 0),
+ HRTIMER_MODE_REL);
+ else
+ hrtimer_cancel(&wdd->wd_data->pretimeout_timer);
+}
+
+void watchdog_hrtimer_pretimeout_stop(struct watchdog_device *wdd)
+{
+ hrtimer_cancel(&wdd->wd_data->pretimeout_timer);
+}
diff --git a/drivers/watchdog/watchdog_pretimeout.c b/drivers/watchdog/watchdog_pretimeout.c
new file mode 100644
index 000000000..376a495ab
--- /dev/null
+++ b/drivers/watchdog/watchdog_pretimeout.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015-2016 Mentor Graphics
+ */
+
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/watchdog.h>
+
+#include "watchdog_core.h"
+#include "watchdog_pretimeout.h"
+
+/* Default watchdog pretimeout governor */
+static struct watchdog_governor *default_gov;
+
+/* The spinlock protects default_gov, wdd->gov and pretimeout_list */
+static DEFINE_SPINLOCK(pretimeout_lock);
+
+/* List of watchdog devices, which can generate a pretimeout event */
+static LIST_HEAD(pretimeout_list);
+
+struct watchdog_pretimeout {
+ struct watchdog_device *wdd;
+ struct list_head entry;
+};
+
+/* The mutex protects governor list and serializes external interfaces */
+static DEFINE_MUTEX(governor_lock);
+
+/* List of the registered watchdog pretimeout governors */
+static LIST_HEAD(governor_list);
+
+struct governor_priv {
+ struct watchdog_governor *gov;
+ struct list_head entry;
+};
+
+static struct governor_priv *find_governor_by_name(const char *gov_name)
+{
+ struct governor_priv *priv;
+
+ list_for_each_entry(priv, &governor_list, entry)
+ if (sysfs_streq(gov_name, priv->gov->name))
+ return priv;
+
+ return NULL;
+}
+
+int watchdog_pretimeout_available_governors_get(char *buf)
+{
+ struct governor_priv *priv;
+ int count = 0;
+
+ mutex_lock(&governor_lock);
+
+ list_for_each_entry(priv, &governor_list, entry)
+ count += sysfs_emit_at(buf, count, "%s\n", priv->gov->name);
+
+ mutex_unlock(&governor_lock);
+
+ return count;
+}
+
+int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf)
+{
+ int count = 0;
+
+ spin_lock_irq(&pretimeout_lock);
+ if (wdd->gov)
+ count = sysfs_emit(buf, "%s\n", wdd->gov->name);
+ spin_unlock_irq(&pretimeout_lock);
+
+ return count;
+}
+
+int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
+ const char *buf)
+{
+ struct governor_priv *priv;
+
+ mutex_lock(&governor_lock);
+
+ priv = find_governor_by_name(buf);
+ if (!priv) {
+ mutex_unlock(&governor_lock);
+ return -EINVAL;
+ }
+
+ spin_lock_irq(&pretimeout_lock);
+ wdd->gov = priv->gov;
+ spin_unlock_irq(&pretimeout_lock);
+
+ mutex_unlock(&governor_lock);
+
+ return 0;
+}
+
+void watchdog_notify_pretimeout(struct watchdog_device *wdd)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pretimeout_lock, flags);
+ if (!wdd->gov) {
+ spin_unlock_irqrestore(&pretimeout_lock, flags);
+ return;
+ }
+
+ wdd->gov->pretimeout(wdd);
+ spin_unlock_irqrestore(&pretimeout_lock, flags);
+}
+EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout);
+
+int watchdog_register_governor(struct watchdog_governor *gov)
+{
+ struct watchdog_pretimeout *p;
+ struct governor_priv *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_lock(&governor_lock);
+
+ if (find_governor_by_name(gov->name)) {
+ mutex_unlock(&governor_lock);
+ kfree(priv);
+ return -EBUSY;
+ }
+
+ priv->gov = gov;
+ list_add(&priv->entry, &governor_list);
+
+ if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV,
+ WATCHDOG_GOV_NAME_MAXLEN)) {
+ spin_lock_irq(&pretimeout_lock);
+ default_gov = gov;
+
+ list_for_each_entry(p, &pretimeout_list, entry)
+ if (!p->wdd->gov)
+ p->wdd->gov = default_gov;
+ spin_unlock_irq(&pretimeout_lock);
+ }
+
+ mutex_unlock(&governor_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL(watchdog_register_governor);
+
+void watchdog_unregister_governor(struct watchdog_governor *gov)
+{
+ struct watchdog_pretimeout *p;
+ struct governor_priv *priv, *t;
+
+ mutex_lock(&governor_lock);
+
+ list_for_each_entry_safe(priv, t, &governor_list, entry) {
+ if (priv->gov == gov) {
+ list_del(&priv->entry);
+ kfree(priv);
+ break;
+ }
+ }
+
+ spin_lock_irq(&pretimeout_lock);
+ list_for_each_entry(p, &pretimeout_list, entry)
+ if (p->wdd->gov == gov)
+ p->wdd->gov = default_gov;
+ spin_unlock_irq(&pretimeout_lock);
+
+ mutex_unlock(&governor_lock);
+}
+EXPORT_SYMBOL(watchdog_unregister_governor);
+
+int watchdog_register_pretimeout(struct watchdog_device *wdd)
+{
+ struct watchdog_pretimeout *p;
+
+ if (!watchdog_have_pretimeout(wdd))
+ return 0;
+
+ p = kzalloc(sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+
+ spin_lock_irq(&pretimeout_lock);
+ list_add(&p->entry, &pretimeout_list);
+ p->wdd = wdd;
+ wdd->gov = default_gov;
+ spin_unlock_irq(&pretimeout_lock);
+
+ return 0;
+}
+
+void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
+{
+ struct watchdog_pretimeout *p, *t;
+
+ if (!watchdog_have_pretimeout(wdd))
+ return;
+
+ spin_lock_irq(&pretimeout_lock);
+ wdd->gov = NULL;
+
+ list_for_each_entry_safe(p, t, &pretimeout_list, entry) {
+ if (p->wdd == wdd) {
+ list_del(&p->entry);
+ break;
+ }
+ }
+ spin_unlock_irq(&pretimeout_lock);
+
+ kfree(p);
+}
diff --git a/drivers/watchdog/watchdog_pretimeout.h b/drivers/watchdog/watchdog_pretimeout.h
new file mode 100644
index 000000000..a3f1abc68
--- /dev/null
+++ b/drivers/watchdog/watchdog_pretimeout.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __WATCHDOG_PRETIMEOUT_H
+#define __WATCHDOG_PRETIMEOUT_H
+
+#define WATCHDOG_GOV_NAME_MAXLEN 20
+
+struct watchdog_device;
+
+struct watchdog_governor {
+ const char name[WATCHDOG_GOV_NAME_MAXLEN];
+ void (*pretimeout)(struct watchdog_device *wdd);
+};
+
+#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV)
+/* Interfaces to watchdog pretimeout governors */
+int watchdog_register_governor(struct watchdog_governor *gov);
+void watchdog_unregister_governor(struct watchdog_governor *gov);
+
+/* Interfaces to watchdog_dev.c */
+int watchdog_register_pretimeout(struct watchdog_device *wdd);
+void watchdog_unregister_pretimeout(struct watchdog_device *wdd);
+int watchdog_pretimeout_available_governors_get(char *buf);
+int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf);
+int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
+ const char *buf);
+
+#if IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_NOOP)
+#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "noop"
+#elif IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_DEFAULT_GOV_PANIC)
+#define WATCHDOG_PRETIMEOUT_DEFAULT_GOV "panic"
+#endif
+
+#else
+static inline int watchdog_register_pretimeout(struct watchdog_device *wdd)
+{
+ return 0;
+}
+
+static inline void watchdog_unregister_pretimeout(struct watchdog_device *wdd)
+{
+}
+
+static inline int watchdog_pretimeout_available_governors_get(char *buf)
+{
+ return -EINVAL;
+}
+
+static inline int watchdog_pretimeout_governor_get(struct watchdog_device *wdd,
+ char *buf)
+{
+ return -EINVAL;
+}
+
+static inline int watchdog_pretimeout_governor_set(struct watchdog_device *wdd,
+ const char *buf)
+{
+ return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/drivers/watchdog/wd501p.h b/drivers/watchdog/wd501p.h
new file mode 100644
index 000000000..43a4d88fd
--- /dev/null
+++ b/drivers/watchdog/wd501p.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-1.0+ */
+/*
+ * Industrial Computer Source WDT500/501 driver
+ *
+ * (c) Copyright 1995 CymruNET Ltd
+ * Innovation Centre
+ * Singleton Park
+ * Swansea
+ * Wales
+ * UK
+ * SA2 8PP
+ *
+ * http://www.cymru.net
+ *
+ * Release 0.04.
+ */
+
+
+#define WDT_COUNT0 (io+0)
+#define WDT_COUNT1 (io+1)
+#define WDT_COUNT2 (io+2)
+#define WDT_CR (io+3)
+#define WDT_SR (io+4) /* Start buzzer on PCI write */
+#define WDT_RT (io+5) /* Stop buzzer on PCI write */
+#define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */
+#define WDT_DC (io+7)
+
+/* The following are only on the PCI card, they're outside of I/O space on
+ * the ISA card: */
+#define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */
+/* inverted opto isolated reset output: */
+#define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */
+/* opto isolated reset output: */
+#define WDT_OPTORST (io+14) /* wr=enable, rd=disable */
+/* programmable outputs: */
+#define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */
+
+ /* FAN 501 500 */
+#define WDC_SR_WCCR 1 /* Active low */ /* X X X */
+#define WDC_SR_TGOOD 2 /* X X - */
+#define WDC_SR_ISOI0 4 /* X X X */
+#define WDC_SR_ISII1 8 /* X X X */
+#define WDC_SR_FANGOOD 16 /* X - - */
+#define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */
+#define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */
+#define WDC_SR_IRQ 128 /* Active low */ /* X X X */
+
diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c
new file mode 100644
index 000000000..ce7a4a9e4
--- /dev/null
+++ b/drivers/watchdog/wdat_wdt.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ACPI Hardware Watchdog (WDAT) driver.
+ *
+ * Copyright (C) 2016, Intel Corporation
+ * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/watchdog.h>
+
+#define MAX_WDAT_ACTIONS ACPI_WDAT_ACTION_RESERVED
+
+/**
+ * struct wdat_instruction - Single ACPI WDAT instruction
+ * @entry: Copy of the ACPI table instruction
+ * @reg: Register the instruction is accessing
+ * @node: Next instruction in action sequence
+ */
+struct wdat_instruction {
+ struct acpi_wdat_entry entry;
+ void __iomem *reg;
+ struct list_head node;
+};
+
+/**
+ * struct wdat_wdt - ACPI WDAT watchdog device
+ * @pdev: Parent platform device
+ * @wdd: Watchdog core device
+ * @period: How long is one watchdog period in ms
+ * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5
+ * @stopped: Was the watchdog stopped by the driver in suspend
+ * @instructions: An array of instruction lists indexed by an action number from
+ * the WDAT table. There can be %NULL entries for not implemented
+ * actions.
+ */
+struct wdat_wdt {
+ struct platform_device *pdev;
+ struct watchdog_device wdd;
+ unsigned int period;
+ bool stopped_in_sleep;
+ bool stopped;
+ struct list_head *instructions[MAX_WDAT_ACTIONS];
+};
+
+#define to_wdat_wdt(wdd) container_of(wdd, struct wdat_wdt, wdd)
+
+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 WDAT_DEFAULT_TIMEOUT 30
+
+static int timeout = WDAT_DEFAULT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default="
+ __MODULE_STRING(WDAT_DEFAULT_TIMEOUT) ")");
+
+static int wdat_wdt_read(struct wdat_wdt *wdat,
+ const struct wdat_instruction *instr, u32 *value)
+{
+ const struct acpi_generic_address *gas = &instr->entry.register_region;
+
+ switch (gas->access_width) {
+ case 1:
+ *value = ioread8(instr->reg);
+ break;
+ case 2:
+ *value = ioread16(instr->reg);
+ break;
+ case 3:
+ *value = ioread32(instr->reg);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&wdat->pdev->dev, "Read %#x from 0x%08llx\n", *value,
+ gas->address);
+
+ return 0;
+}
+
+static int wdat_wdt_write(struct wdat_wdt *wdat,
+ const struct wdat_instruction *instr, u32 value)
+{
+ const struct acpi_generic_address *gas = &instr->entry.register_region;
+
+ switch (gas->access_width) {
+ case 1:
+ iowrite8((u8)value, instr->reg);
+ break;
+ case 2:
+ iowrite16((u16)value, instr->reg);
+ break;
+ case 3:
+ iowrite32(value, instr->reg);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&wdat->pdev->dev, "Wrote %#x to 0x%08llx\n", value,
+ gas->address);
+
+ return 0;
+}
+
+static int wdat_wdt_run_action(struct wdat_wdt *wdat, unsigned int action,
+ u32 param, u32 *retval)
+{
+ struct wdat_instruction *instr;
+
+ if (action >= ARRAY_SIZE(wdat->instructions))
+ return -EINVAL;
+
+ if (!wdat->instructions[action])
+ return -EOPNOTSUPP;
+
+ dev_dbg(&wdat->pdev->dev, "Running action %#x\n", action);
+
+ /* Run each instruction sequentially */
+ list_for_each_entry(instr, wdat->instructions[action], node) {
+ const struct acpi_wdat_entry *entry = &instr->entry;
+ const struct acpi_generic_address *gas;
+ u32 flags, value, mask, x, y;
+ bool preserve;
+ int ret;
+
+ gas = &entry->register_region;
+
+ preserve = entry->instruction & ACPI_WDAT_PRESERVE_REGISTER;
+ flags = entry->instruction & ~ACPI_WDAT_PRESERVE_REGISTER;
+ value = entry->value;
+ mask = entry->mask;
+
+ switch (flags) {
+ case ACPI_WDAT_READ_VALUE:
+ ret = wdat_wdt_read(wdat, instr, &x);
+ if (ret)
+ return ret;
+ x >>= gas->bit_offset;
+ x &= mask;
+ if (retval)
+ *retval = x == value;
+ break;
+
+ case ACPI_WDAT_READ_COUNTDOWN:
+ ret = wdat_wdt_read(wdat, instr, &x);
+ if (ret)
+ return ret;
+ x >>= gas->bit_offset;
+ x &= mask;
+ if (retval)
+ *retval = x;
+ break;
+
+ case ACPI_WDAT_WRITE_VALUE:
+ x = value & mask;
+ x <<= gas->bit_offset;
+ if (preserve) {
+ ret = wdat_wdt_read(wdat, instr, &y);
+ if (ret)
+ return ret;
+ y = y & ~(mask << gas->bit_offset);
+ x |= y;
+ }
+ ret = wdat_wdt_write(wdat, instr, x);
+ if (ret)
+ return ret;
+ break;
+
+ case ACPI_WDAT_WRITE_COUNTDOWN:
+ x = param;
+ x &= mask;
+ x <<= gas->bit_offset;
+ if (preserve) {
+ ret = wdat_wdt_read(wdat, instr, &y);
+ if (ret)
+ return ret;
+ y = y & ~(mask << gas->bit_offset);
+ x |= y;
+ }
+ ret = wdat_wdt_write(wdat, instr, x);
+ if (ret)
+ return ret;
+ break;
+
+ default:
+ dev_err(&wdat->pdev->dev, "Unknown instruction: %u\n",
+ flags);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat)
+{
+ int ret;
+
+ /*
+ * WDAT specification says that the watchdog is required to reboot
+ * the system when it fires. However, it also states that it is
+ * recommended to make it configurable through hardware register. We
+ * enable reboot now if it is configurable, just in case.
+ */
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_REBOOT, 0, NULL);
+ if (ret && ret != -EOPNOTSUPP) {
+ dev_err(&wdat->pdev->dev,
+ "Failed to enable reboot when watchdog triggers\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void wdat_wdt_boot_status(struct wdat_wdt *wdat)
+{
+ u32 boot_status = 0;
+ int ret;
+
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_STATUS, 0, &boot_status);
+ if (ret && ret != -EOPNOTSUPP) {
+ dev_err(&wdat->pdev->dev, "Failed to read boot status\n");
+ return;
+ }
+
+ if (boot_status)
+ wdat->wdd.bootstatus = WDIOF_CARDRESET;
+
+ /* Clear the boot status in case BIOS did not do it */
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_STATUS, 0, NULL);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_err(&wdat->pdev->dev, "Failed to clear boot status\n");
+}
+
+static void wdat_wdt_set_running(struct wdat_wdt *wdat)
+{
+ u32 running = 0;
+ int ret;
+
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_GET_RUNNING_STATE, 0,
+ &running);
+ if (ret && ret != -EOPNOTSUPP)
+ dev_err(&wdat->pdev->dev, "Failed to read running state\n");
+
+ if (running)
+ set_bit(WDOG_HW_RUNNING, &wdat->wdd.status);
+}
+
+static int wdat_wdt_start(struct watchdog_device *wdd)
+{
+ return wdat_wdt_run_action(to_wdat_wdt(wdd),
+ ACPI_WDAT_SET_RUNNING_STATE, 0, NULL);
+}
+
+static int wdat_wdt_stop(struct watchdog_device *wdd)
+{
+ return wdat_wdt_run_action(to_wdat_wdt(wdd),
+ ACPI_WDAT_SET_STOPPED_STATE, 0, NULL);
+}
+
+static int wdat_wdt_ping(struct watchdog_device *wdd)
+{
+ return wdat_wdt_run_action(to_wdat_wdt(wdd), ACPI_WDAT_RESET, 0, NULL);
+}
+
+static int wdat_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct wdat_wdt *wdat = to_wdat_wdt(wdd);
+ unsigned int periods;
+ int ret;
+
+ periods = timeout * 1000 / wdat->period;
+ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_COUNTDOWN, periods, NULL);
+ if (!ret)
+ wdd->timeout = timeout;
+ return ret;
+}
+
+static unsigned int wdat_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct wdat_wdt *wdat = to_wdat_wdt(wdd);
+ u32 periods = 0;
+
+ wdat_wdt_run_action(wdat, ACPI_WDAT_GET_CURRENT_COUNTDOWN, 0, &periods);
+ return periods * wdat->period / 1000;
+}
+
+static const struct watchdog_info wdat_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "wdat_wdt",
+};
+
+static const struct watchdog_ops wdat_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wdat_wdt_start,
+ .stop = wdat_wdt_stop,
+ .ping = wdat_wdt_ping,
+ .set_timeout = wdat_wdt_set_timeout,
+ .get_timeleft = wdat_wdt_get_timeleft,
+};
+
+static int wdat_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct acpi_wdat_entry *entries;
+ const struct acpi_table_wdat *tbl;
+ struct wdat_wdt *wdat;
+ struct resource *res;
+ void __iomem **regs;
+ acpi_status status;
+ int i, ret;
+
+ status = acpi_get_table(ACPI_SIG_WDAT, 0,
+ (struct acpi_table_header **)&tbl);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL);
+ if (!wdat)
+ return -ENOMEM;
+
+ regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs),
+ GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
+
+ /* WDAT specification wants to have >= 1ms period */
+ if (tbl->timer_period < 1)
+ return -EINVAL;
+ if (tbl->min_count > tbl->max_count)
+ return -EINVAL;
+
+ wdat->period = tbl->timer_period;
+ wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000);
+ wdat->wdd.max_timeout = wdat->period * tbl->max_count / 1000;
+ wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED;
+ wdat->wdd.info = &wdat_wdt_info;
+ wdat->wdd.ops = &wdat_wdt_ops;
+ wdat->pdev = pdev;
+
+ /* Request and map all resources */
+ for (i = 0; i < pdev->num_resources; i++) {
+ void __iomem *reg;
+
+ res = &pdev->resource[i];
+ if (resource_type(res) == IORESOURCE_MEM) {
+ reg = devm_ioremap_resource(dev, res);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+ } else if (resource_type(res) == IORESOURCE_IO) {
+ reg = devm_ioport_map(dev, res->start, 1);
+ if (!reg)
+ return -ENOMEM;
+ } else {
+ dev_err(dev, "Unsupported resource\n");
+ return -EINVAL;
+ }
+
+ regs[i] = reg;
+ }
+
+ entries = (struct acpi_wdat_entry *)(tbl + 1);
+ for (i = 0; i < tbl->entries; i++) {
+ const struct acpi_generic_address *gas;
+ struct wdat_instruction *instr;
+ struct list_head *instructions;
+ unsigned int action;
+ struct resource r;
+ int j;
+
+ action = entries[i].action;
+ if (action >= MAX_WDAT_ACTIONS) {
+ dev_dbg(dev, "Skipping unknown action: %u\n", action);
+ continue;
+ }
+
+ instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL);
+ if (!instr)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&instr->node);
+ instr->entry = entries[i];
+
+ gas = &entries[i].register_region;
+
+ memset(&r, 0, sizeof(r));
+ r.start = gas->address;
+ r.end = r.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1;
+ if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ r.flags = IORESOURCE_MEM;
+ } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+ r.flags = IORESOURCE_IO;
+ } else {
+ dev_dbg(dev, "Unsupported address space: %d\n",
+ gas->space_id);
+ continue;
+ }
+
+ /* Find the matching resource */
+ for (j = 0; j < pdev->num_resources; j++) {
+ res = &pdev->resource[j];
+ if (resource_contains(res, &r)) {
+ instr->reg = regs[j] + r.start - res->start;
+ break;
+ }
+ }
+
+ if (!instr->reg) {
+ dev_err(dev, "I/O resource not found\n");
+ return -EINVAL;
+ }
+
+ instructions = wdat->instructions[action];
+ if (!instructions) {
+ instructions = devm_kzalloc(dev,
+ sizeof(*instructions),
+ GFP_KERNEL);
+ if (!instructions)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(instructions);
+ wdat->instructions[action] = instructions;
+ }
+
+ list_add_tail(&instr->node, instructions);
+ }
+
+ wdat_wdt_boot_status(wdat);
+ wdat_wdt_set_running(wdat);
+
+ ret = wdat_wdt_enable_reboot(wdat);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, wdat);
+
+ /*
+ * Set initial timeout so that userspace has time to configure the
+ * watchdog properly after it has opened the device. In some cases
+ * the BIOS default is too short and causes immediate reboot.
+ */
+ if (watchdog_timeout_invalid(&wdat->wdd, timeout)) {
+ dev_warn(dev, "Invalid timeout %d given, using %d\n",
+ timeout, WDAT_DEFAULT_TIMEOUT);
+ timeout = WDAT_DEFAULT_TIMEOUT;
+ }
+
+ ret = wdat_wdt_set_timeout(&wdat->wdd, timeout);
+ if (ret)
+ return ret;
+
+ watchdog_set_nowayout(&wdat->wdd, nowayout);
+ watchdog_stop_on_reboot(&wdat->wdd);
+ watchdog_stop_on_unregister(&wdat->wdd);
+ return devm_watchdog_register_device(dev, &wdat->wdd);
+}
+
+static int wdat_wdt_suspend_noirq(struct device *dev)
+{
+ struct wdat_wdt *wdat = dev_get_drvdata(dev);
+ int ret;
+
+ if (!watchdog_active(&wdat->wdd))
+ return 0;
+
+ /*
+ * We need to stop the watchdog if firmware is not doing it or if we
+ * are going suspend to idle (where firmware is not involved). If
+ * firmware is stopping the watchdog we kick it here one more time
+ * to give it some time.
+ */
+ wdat->stopped = false;
+ if (acpi_target_system_state() == ACPI_STATE_S0 ||
+ !wdat->stopped_in_sleep) {
+ ret = wdat_wdt_stop(&wdat->wdd);
+ if (!ret)
+ wdat->stopped = true;
+ } else {
+ ret = wdat_wdt_ping(&wdat->wdd);
+ }
+
+ return ret;
+}
+
+static int wdat_wdt_resume_noirq(struct device *dev)
+{
+ struct wdat_wdt *wdat = dev_get_drvdata(dev);
+ int ret;
+
+ if (!watchdog_active(&wdat->wdd))
+ return 0;
+
+ if (!wdat->stopped) {
+ /*
+ * Looks like the boot firmware reinitializes the watchdog
+ * before it hands off to the OS on resume from sleep so we
+ * stop and reprogram the watchdog here.
+ */
+ ret = wdat_wdt_stop(&wdat->wdd);
+ if (ret)
+ return ret;
+
+ ret = wdat_wdt_set_timeout(&wdat->wdd, wdat->wdd.timeout);
+ if (ret)
+ return ret;
+
+ ret = wdat_wdt_enable_reboot(wdat);
+ if (ret)
+ return ret;
+
+ ret = wdat_wdt_ping(&wdat->wdd);
+ if (ret)
+ return ret;
+ }
+
+ return wdat_wdt_start(&wdat->wdd);
+}
+
+static const struct dev_pm_ops wdat_wdt_pm_ops = {
+ NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, wdat_wdt_resume_noirq)
+};
+
+static struct platform_driver wdat_wdt_driver = {
+ .probe = wdat_wdt_probe,
+ .driver = {
+ .name = "wdat_wdt",
+ .pm = pm_sleep_ptr(&wdat_wdt_pm_ops),
+ },
+};
+
+module_platform_driver(wdat_wdt_driver);
+
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_DESCRIPTION("ACPI Hardware Watchdog (WDAT) driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:wdat_wdt");
diff --git a/drivers/watchdog/wdrtas.c b/drivers/watchdog/wdrtas.c
new file mode 100644
index 000000000..c00627825
--- /dev/null
+++ b/drivers/watchdog/wdrtas.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * FIXME: add wdrtas_get_status and wdrtas_get_boot_status as soon as
+ * RTAS calls are available
+ */
+
+/*
+ * RTAS watchdog driver
+ *
+ * (C) Copyright IBM Corp. 2005
+ * device driver to exploit watchdog RTAS functions
+ *
+ * Authors : Utz Bacher <utz.bacher@de.ibm.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+
+#include <asm/rtas.h>
+
+#define WDRTAS_MAGIC_CHAR 42
+#define WDRTAS_SUPPORTED_MASK (WDIOF_SETTIMEOUT | \
+ WDIOF_MAGICCLOSE)
+
+MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>");
+MODULE_DESCRIPTION("RTAS watchdog driver");
+MODULE_LICENSE("GPL");
+
+static bool wdrtas_nowayout = WATCHDOG_NOWAYOUT;
+static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0);
+static char wdrtas_expect_close;
+
+static int wdrtas_interval;
+
+#define WDRTAS_THERMAL_SENSOR 3
+static int wdrtas_token_get_sensor_state;
+#define WDRTAS_SURVEILLANCE_IND 9000
+static int wdrtas_token_set_indicator;
+#define WDRTAS_SP_SPI 28
+static int wdrtas_token_get_sp;
+static int wdrtas_token_event_scan;
+
+#define WDRTAS_DEFAULT_INTERVAL 300
+
+#define WDRTAS_LOGBUFFER_LEN 128
+static char wdrtas_logbuffer[WDRTAS_LOGBUFFER_LEN];
+
+
+/*** watchdog access functions */
+
+/**
+ * wdrtas_set_interval - sets the watchdog interval
+ * @interval: new interval
+ *
+ * returns 0 on success, <0 on failures
+ *
+ * wdrtas_set_interval sets the watchdog keepalive interval by calling the
+ * RTAS function set-indicator (surveillance). The unit of interval is
+ * seconds.
+ */
+
+static int wdrtas_set_interval(int interval)
+{
+ long result;
+ static int print_msg = 10;
+
+ /* rtas uses minutes */
+ interval = (interval + 59) / 60;
+
+ result = rtas_call(wdrtas_token_set_indicator, 3, 1, NULL,
+ WDRTAS_SURVEILLANCE_IND, 0, interval);
+ if (result < 0 && print_msg) {
+ pr_err("setting the watchdog to %i timeout failed: %li\n",
+ interval, result);
+ print_msg--;
+ }
+
+ return result;
+}
+
+#define WDRTAS_SP_SPI_LEN 4
+
+/**
+ * wdrtas_get_interval - returns the current watchdog interval
+ * @fallback_value: value (in seconds) to use, if the RTAS call fails
+ *
+ * returns the interval
+ *
+ * wdrtas_get_interval returns the current watchdog keepalive interval
+ * as reported by the RTAS function ibm,get-system-parameter. The unit
+ * of the return value is seconds.
+ */
+static int wdrtas_get_interval(int fallback_value)
+{
+ long result;
+ char value[WDRTAS_SP_SPI_LEN];
+
+ spin_lock(&rtas_data_buf_lock);
+ memset(rtas_data_buf, 0, WDRTAS_SP_SPI_LEN);
+ result = rtas_call(wdrtas_token_get_sp, 3, 1, NULL,
+ WDRTAS_SP_SPI, __pa(rtas_data_buf),
+ WDRTAS_SP_SPI_LEN);
+
+ memcpy(value, rtas_data_buf, WDRTAS_SP_SPI_LEN);
+ spin_unlock(&rtas_data_buf_lock);
+
+ if (value[0] != 0 || value[1] != 2 || value[3] != 0 || result < 0) {
+ pr_warn("could not get sp_spi watchdog timeout (%li). Continuing\n",
+ result);
+ return fallback_value;
+ }
+
+ /* rtas uses minutes */
+ return ((int)value[2]) * 60;
+}
+
+/**
+ * wdrtas_timer_start - starts watchdog
+ *
+ * wdrtas_timer_start starts the watchdog by calling the RTAS function
+ * set-interval (surveillance)
+ */
+static void wdrtas_timer_start(void)
+{
+ wdrtas_set_interval(wdrtas_interval);
+}
+
+/**
+ * wdrtas_timer_stop - stops watchdog
+ *
+ * wdrtas_timer_stop stops the watchdog timer by calling the RTAS function
+ * set-interval (surveillance)
+ */
+static void wdrtas_timer_stop(void)
+{
+ wdrtas_set_interval(0);
+}
+
+/**
+ * wdrtas_timer_keepalive - resets watchdog timer to keep system alive
+ *
+ * wdrtas_timer_keepalive restarts the watchdog timer by calling the
+ * RTAS function event-scan and repeats these calls as long as there are
+ * events available. All events will be dumped.
+ */
+static void wdrtas_timer_keepalive(void)
+{
+ long result;
+
+ do {
+ result = rtas_call(wdrtas_token_event_scan, 4, 1, NULL,
+ RTAS_EVENT_SCAN_ALL_EVENTS, 0,
+ (void *)__pa(wdrtas_logbuffer),
+ WDRTAS_LOGBUFFER_LEN);
+ if (result < 0)
+ pr_err("event-scan failed: %li\n", result);
+ if (result == 0)
+ print_hex_dump(KERN_INFO, "dumping event, data: ",
+ DUMP_PREFIX_OFFSET, 16, 1,
+ wdrtas_logbuffer, WDRTAS_LOGBUFFER_LEN, false);
+ } while (result == 0);
+}
+
+/**
+ * wdrtas_get_temperature - returns current temperature
+ *
+ * returns temperature or <0 on failures
+ *
+ * wdrtas_get_temperature returns the current temperature in Fahrenheit. It
+ * uses the RTAS call get-sensor-state, token 3 to do so
+ */
+static int wdrtas_get_temperature(void)
+{
+ int result;
+ int temperature = 0;
+
+ result = rtas_get_sensor(WDRTAS_THERMAL_SENSOR, 0, &temperature);
+
+ if (result < 0)
+ pr_warn("reading the thermal sensor failed: %i\n", result);
+ else
+ temperature = ((temperature * 9) / 5) + 32; /* fahrenheit */
+
+ return temperature;
+}
+
+/**
+ * wdrtas_get_status - returns the status of the watchdog
+ *
+ * returns a bitmask of defines WDIOF_... as defined in
+ * include/linux/watchdog.h
+ */
+static int wdrtas_get_status(void)
+{
+ return 0; /* TODO */
+}
+
+/**
+ * wdrtas_get_boot_status - returns the reason for the last boot
+ *
+ * returns a bitmask of defines WDIOF_... as defined in
+ * include/linux/watchdog.h, indicating why the watchdog rebooted the system
+ */
+static int wdrtas_get_boot_status(void)
+{
+ return 0; /* TODO */
+}
+
+/*** watchdog API and operations stuff */
+
+/* wdrtas_write - called when watchdog device is written to
+ * @file: file structure
+ * @buf: user buffer with data
+ * @len: amount to data written
+ * @ppos: position in file
+ *
+ * returns the number of successfully processed characters, which is always
+ * the number of bytes passed to this function
+ *
+ * wdrtas_write processes all the data given to it and looks for the magic
+ * character 'V'. This character allows the watchdog device to be closed
+ * properly.
+ */
+static ssize_t wdrtas_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ int i;
+ char c;
+
+ if (!len)
+ goto out;
+
+ if (!wdrtas_nowayout) {
+ wdrtas_expect_close = 0;
+ /* look for 'V' */
+ for (i = 0; i < len; i++) {
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ /* allow to close device */
+ if (c == 'V')
+ wdrtas_expect_close = WDRTAS_MAGIC_CHAR;
+ }
+ }
+
+ wdrtas_timer_keepalive();
+
+out:
+ return len;
+}
+
+/**
+ * wdrtas_ioctl - ioctl function for the watchdog device
+ * @file: file structure
+ * @cmd: command for ioctl
+ * @arg: argument pointer
+ *
+ * returns 0 on success, <0 on failure
+ *
+ * wdrtas_ioctl implements the watchdog API ioctls
+ */
+
+static long wdrtas_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int __user *argp = (void __user *)arg;
+ int i;
+ static const struct watchdog_info wdinfo = {
+ .options = WDRTAS_SUPPORTED_MASK,
+ .firmware_version = 0,
+ .identity = "wdrtas",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &wdinfo, sizeof(wdinfo)))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ i = wdrtas_get_status();
+ return put_user(i, argp);
+
+ case WDIOC_GETBOOTSTATUS:
+ i = wdrtas_get_boot_status();
+ return put_user(i, argp);
+
+ case WDIOC_GETTEMP:
+ if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE)
+ return -EOPNOTSUPP;
+
+ i = wdrtas_get_temperature();
+ return put_user(i, argp);
+
+ case WDIOC_SETOPTIONS:
+ if (get_user(i, argp))
+ return -EFAULT;
+ if (i & WDIOS_DISABLECARD)
+ wdrtas_timer_stop();
+ if (i & WDIOS_ENABLECARD) {
+ wdrtas_timer_keepalive();
+ wdrtas_timer_start();
+ }
+ /* not implemented. Done by H8
+ if (i & WDIOS_TEMPPANIC) {
+ } */
+ return 0;
+
+ case WDIOC_KEEPALIVE:
+ wdrtas_timer_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(i, argp))
+ return -EFAULT;
+
+ if (wdrtas_set_interval(i))
+ return -EINVAL;
+
+ wdrtas_timer_keepalive();
+
+ if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE)
+ wdrtas_interval = i;
+ else
+ wdrtas_interval = wdrtas_get_interval(i);
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(wdrtas_interval, argp);
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+/**
+ * wdrtas_open - open function of watchdog device
+ * @inode: inode structure
+ * @file: file structure
+ *
+ * returns 0 on success, -EBUSY if the file has been opened already, <0 on
+ * other failures
+ *
+ * function called when watchdog device is opened
+ */
+static int wdrtas_open(struct inode *inode, struct file *file)
+{
+ /* only open once */
+ if (atomic_inc_return(&wdrtas_miscdev_open) > 1) {
+ atomic_dec(&wdrtas_miscdev_open);
+ return -EBUSY;
+ }
+
+ wdrtas_timer_start();
+ wdrtas_timer_keepalive();
+
+ return stream_open(inode, file);
+}
+
+/**
+ * wdrtas_close - close function of watchdog device
+ * @inode: inode structure
+ * @file: file structure
+ *
+ * returns 0 on success
+ *
+ * close function. Always succeeds
+ */
+static int wdrtas_close(struct inode *inode, struct file *file)
+{
+ /* only stop watchdog, if this was announced using 'V' before */
+ if (wdrtas_expect_close == WDRTAS_MAGIC_CHAR)
+ wdrtas_timer_stop();
+ else {
+ pr_warn("got unexpected close. Watchdog not stopped.\n");
+ wdrtas_timer_keepalive();
+ }
+
+ wdrtas_expect_close = 0;
+ atomic_dec(&wdrtas_miscdev_open);
+ return 0;
+}
+
+/**
+ * wdrtas_temp_read - gives back the temperature in fahrenheit
+ * @file: file structure
+ * @buf: user buffer
+ * @count: number of bytes to be read
+ * @ppos: position in file
+ *
+ * returns always 1 or -EFAULT in case of user space copy failures, <0 on
+ * other failures
+ *
+ * wdrtas_temp_read gives the temperature to the users by copying this
+ * value as one byte into the user space buffer. The unit is Fahrenheit...
+ */
+static ssize_t wdrtas_temp_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int temperature = 0;
+
+ temperature = wdrtas_get_temperature();
+ if (temperature < 0)
+ return temperature;
+
+ if (copy_to_user(buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+/**
+ * wdrtas_temp_open - open function of temperature device
+ * @inode: inode structure
+ * @file: file structure
+ *
+ * returns 0 on success, <0 on failure
+ *
+ * function called when temperature device is opened
+ */
+static int wdrtas_temp_open(struct inode *inode, struct file *file)
+{
+ return stream_open(inode, file);
+}
+
+/**
+ * wdrtas_temp_close - close function of temperature device
+ * @inode: inode structure
+ * @file: file structure
+ *
+ * returns 0 on success
+ *
+ * close function. Always succeeds
+ */
+static int wdrtas_temp_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/**
+ * wdrtas_reboot - reboot notifier function
+ * @nb: notifier block structure
+ * @code: reboot code
+ * @ptr: unused
+ *
+ * returns NOTIFY_DONE
+ *
+ * wdrtas_reboot stops the watchdog in case of a reboot
+ */
+static int wdrtas_reboot(struct notifier_block *this,
+ unsigned long code, void *ptr)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wdrtas_timer_stop();
+
+ return NOTIFY_DONE;
+}
+
+/*** initialization stuff */
+
+static const struct file_operations wdrtas_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdrtas_write,
+ .unlocked_ioctl = wdrtas_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wdrtas_open,
+ .release = wdrtas_close,
+};
+
+static struct miscdevice wdrtas_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdrtas_fops,
+};
+
+static const struct file_operations wdrtas_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = wdrtas_temp_read,
+ .open = wdrtas_temp_open,
+ .release = wdrtas_temp_close,
+};
+
+static struct miscdevice wdrtas_tempdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &wdrtas_temp_fops,
+};
+
+static struct notifier_block wdrtas_notifier = {
+ .notifier_call = wdrtas_reboot,
+};
+
+/**
+ * wdrtas_get_tokens - reads in RTAS tokens
+ *
+ * returns 0 on success, <0 on failure
+ *
+ * wdrtas_get_tokens reads in the tokens for the RTAS calls used in
+ * this watchdog driver. It tolerates, if "get-sensor-state" and
+ * "ibm,get-system-parameter" are not available.
+ */
+static int wdrtas_get_tokens(void)
+{
+ wdrtas_token_get_sensor_state = rtas_token("get-sensor-state");
+ if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) {
+ pr_warn("couldn't get token for get-sensor-state. Trying to continue without temperature support.\n");
+ }
+
+ wdrtas_token_get_sp = rtas_token("ibm,get-system-parameter");
+ if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) {
+ pr_warn("couldn't get token for ibm,get-system-parameter. Trying to continue with a default timeout value of %i seconds.\n",
+ WDRTAS_DEFAULT_INTERVAL);
+ }
+
+ wdrtas_token_set_indicator = rtas_token("set-indicator");
+ if (wdrtas_token_set_indicator == RTAS_UNKNOWN_SERVICE) {
+ pr_err("couldn't get token for set-indicator. Terminating watchdog code.\n");
+ return -EIO;
+ }
+
+ wdrtas_token_event_scan = rtas_token("event-scan");
+ if (wdrtas_token_event_scan == RTAS_UNKNOWN_SERVICE) {
+ pr_err("couldn't get token for event-scan. Terminating watchdog code.\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * wdrtas_unregister_devs - unregisters the misc dev handlers
+ *
+ * wdrtas_register_devs unregisters the watchdog and temperature watchdog
+ * misc devs
+ */
+static void wdrtas_unregister_devs(void)
+{
+ misc_deregister(&wdrtas_miscdev);
+ if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE)
+ misc_deregister(&wdrtas_tempdev);
+}
+
+/**
+ * wdrtas_register_devs - registers the misc dev handlers
+ *
+ * returns 0 on success, <0 on failure
+ *
+ * wdrtas_register_devs registers the watchdog and temperature watchdog
+ * misc devs
+ */
+static int wdrtas_register_devs(void)
+{
+ int result;
+
+ result = misc_register(&wdrtas_miscdev);
+ if (result) {
+ pr_err("couldn't register watchdog misc device. Terminating watchdog code.\n");
+ return result;
+ }
+
+ if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) {
+ result = misc_register(&wdrtas_tempdev);
+ if (result) {
+ pr_warn("couldn't register watchdog temperature misc device. Continuing without temperature support.\n");
+ wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * wdrtas_init - init function of the watchdog driver
+ *
+ * returns 0 on success, <0 on failure
+ *
+ * registers the file handlers and the reboot notifier
+ */
+static int __init wdrtas_init(void)
+{
+ if (wdrtas_get_tokens())
+ return -ENODEV;
+
+ if (wdrtas_register_devs())
+ return -ENODEV;
+
+ if (register_reboot_notifier(&wdrtas_notifier)) {
+ pr_err("could not register reboot notifier. Terminating watchdog code.\n");
+ wdrtas_unregister_devs();
+ return -ENODEV;
+ }
+
+ if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE)
+ wdrtas_interval = WDRTAS_DEFAULT_INTERVAL;
+ else
+ wdrtas_interval = wdrtas_get_interval(WDRTAS_DEFAULT_INTERVAL);
+
+ return 0;
+}
+
+/**
+ * wdrtas_exit - exit function of the watchdog driver
+ *
+ * unregisters the file handlers and the reboot notifier
+ */
+static void __exit wdrtas_exit(void)
+{
+ if (!wdrtas_nowayout)
+ wdrtas_timer_stop();
+
+ wdrtas_unregister_devs();
+
+ unregister_reboot_notifier(&wdrtas_notifier);
+}
+
+module_init(wdrtas_init);
+module_exit(wdrtas_exit);
diff --git a/drivers/watchdog/wdt.c b/drivers/watchdog/wdt.c
new file mode 100644
index 000000000..183876156
--- /dev/null
+++ b/drivers/watchdog/wdt.c
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Industrial Computer Source WDT501 driver
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Release 0.10.
+ *
+ * Fixes
+ * Dave Gregorich : Modularisation and minor bugs
+ * Alan Cox : Added the watchdog ioctl() stuff
+ * Alan Cox : Fixed the reboot problem (as noted by
+ * Matt Crocker).
+ * Alan Cox : Added wdt= boot option
+ * Alan Cox : Cleaned up copy/user stuff
+ * Tim Hockin : Added insmod parameters, comment
+ * cleanup, parameterized timeout
+ * Tigran Aivazian : Restructured wdt_init() to handle
+ * failures
+ * Joel Becker : Added WDIOC_GET/SETTIMEOUT
+ * Matt Domsch : Added nowayout module option
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.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>
+
+#include "wd501p.h"
+
+static unsigned long wdt_is_open;
+static char expect_close;
+
+/*
+ * Module parameters
+ */
+
+#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default="
+ __MODULE_STRING(WD_TIMO) ")");
+
+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) ")");
+
+/* You must set these - there is no sane way to probe for this board. */
+static int io = 0x240;
+static int irq = 11;
+
+static DEFINE_SPINLOCK(wdt_lock);
+
+module_param_hw(io, int, ioport, 0);
+MODULE_PARM_DESC(io, "WDT io port (default=0x240)");
+module_param_hw(irq, int, irq, 0);
+MODULE_PARM_DESC(irq, "WDT irq (default=11)");
+
+/* Support for the Fan Tachometer on the WDT501-P */
+static int tachometer;
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer,
+ "WDT501-P Fan Tachometer support (0=disable, default=0)");
+
+static int type = 500;
+module_param(type, int, 0);
+MODULE_PARM_DESC(type,
+ "WDT501-P Card type (500 or 501, default=500)");
+
+/*
+ * Programming support
+ */
+
+static void wdt_ctr_mode(int ctr, int mode)
+{
+ ctr <<= 6;
+ ctr |= 0x30;
+ ctr |= (mode << 1);
+ outb_p(ctr, WDT_CR);
+}
+
+static void wdt_ctr_load(int ctr, int val)
+{
+ outb_p(val&0xFF, WDT_COUNT0+ctr);
+ outb_p(val>>8, WDT_COUNT0+ctr);
+}
+
+/**
+ * wdt_start:
+ *
+ * Start the watchdog driver.
+ */
+
+static int wdt_start(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&wdt_lock, flags);
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_mode(0, 3); /* Program CTR0 for Mode 3:
+ Square Wave Generator */
+ wdt_ctr_mode(1, 2); /* Program CTR1 for Mode 2:
+ Rate Generator */
+ wdt_ctr_mode(2, 0); /* Program CTR2 for Mode 0:
+ Pulse on Terminal Count */
+ wdt_ctr_load(0, 8948); /* Count at 100Hz */
+ wdt_ctr_load(1, wd_heartbeat); /* Heartbeat */
+ wdt_ctr_load(2, 65535); /* Length of reset pulse */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+ spin_unlock_irqrestore(&wdt_lock, flags);
+ return 0;
+}
+
+/**
+ * wdt_stop:
+ *
+ * Stop the watchdog driver.
+ */
+
+static int wdt_stop(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&wdt_lock, flags);
+ /* Turn the card off */
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_load(2, 0); /* 0 length reset pulses now */
+ spin_unlock_irqrestore(&wdt_lock, flags);
+ return 0;
+}
+
+/**
+ * wdt_ping:
+ *
+ * Reload counter one with the watchdog heartbeat. We don't bother
+ * reloading the cascade counter.
+ */
+
+static void wdt_ping(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&wdt_lock, flags);
+ /* Write a watchdog value */
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_mode(1, 2); /* Re-Program CTR1 for Mode 2:
+ Rate Generator */
+ wdt_ctr_load(1, wd_heartbeat); /* Heartbeat */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+ spin_unlock_irqrestore(&wdt_lock, flags);
+}
+
+/**
+ * wdt_set_heartbeat:
+ * @t: the new heartbeat value that needs to be set.
+ *
+ * Set a new heartbeat value for the watchdog device. If the heartbeat
+ * value is incorrect we keep the old value and return -EINVAL. If
+ * successful we return 0.
+ */
+
+static int wdt_set_heartbeat(int t)
+{
+ if (t < 1 || t > 65535)
+ return -EINVAL;
+
+ heartbeat = t;
+ wd_heartbeat = t * 100;
+ return 0;
+}
+
+/**
+ * wdt_get_status:
+ *
+ * Extract the status information from a WDT watchdog device. There are
+ * several board variants so we have to know which bits are valid. Some
+ * bits default to one and some to zero in order to be maximally painful.
+ *
+ * we then map the bits onto the status ioctl flags.
+ */
+
+static int wdt_get_status(void)
+{
+ unsigned char new_status;
+ int status = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt_lock, flags);
+ new_status = inb_p(WDT_SR);
+ spin_unlock_irqrestore(&wdt_lock, flags);
+
+ if (new_status & WDC_SR_ISOI0)
+ status |= WDIOF_EXTERN1;
+ if (new_status & WDC_SR_ISII1)
+ status |= WDIOF_EXTERN2;
+ if (type == 501) {
+ if (!(new_status & WDC_SR_TGOOD))
+ status |= WDIOF_OVERHEAT;
+ if (!(new_status & WDC_SR_PSUOVER))
+ status |= WDIOF_POWEROVER;
+ if (!(new_status & WDC_SR_PSUUNDR))
+ status |= WDIOF_POWERUNDER;
+ if (tachometer) {
+ if (!(new_status & WDC_SR_FANGOOD))
+ status |= WDIOF_FANFAULT;
+ }
+ }
+ return status;
+}
+
+/**
+ * wdt_get_temperature:
+ *
+ * Reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdt_get_temperature(void)
+{
+ unsigned short c;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdt_lock, flags);
+ c = inb_p(WDT_RT);
+ spin_unlock_irqrestore(&wdt_lock, flags);
+ return (c * 11 / 15) + 7;
+}
+
+static void wdt_decode_501(int status)
+{
+ if (!(status & WDC_SR_TGOOD))
+ pr_crit("Overheat alarm (%d)\n", inb_p(WDT_RT));
+ if (!(status & WDC_SR_PSUOVER))
+ pr_crit("PSU over voltage\n");
+ if (!(status & WDC_SR_PSUUNDR))
+ pr_crit("PSU under voltage\n");
+}
+
+/**
+ * wdt_interrupt:
+ * @irq: Interrupt number
+ * @dev_id: Unused as we don't allow multiple devices.
+ *
+ * Handle an interrupt from the board. These are raised when the status
+ * map changes in what the board considers an interesting way. That means
+ * a failure condition occurring.
+ */
+
+static irqreturn_t wdt_interrupt(int irq, void *dev_id)
+{
+ /*
+ * Read the status register see what is up and
+ * then printk it.
+ */
+ unsigned char status;
+
+ spin_lock(&wdt_lock);
+ status = inb_p(WDT_SR);
+
+ pr_crit("WDT status %d\n", status);
+
+ if (type == 501) {
+ wdt_decode_501(status);
+ if (tachometer) {
+ if (!(status & WDC_SR_FANGOOD))
+ pr_crit("Possible fan fault\n");
+ }
+ }
+ if (!(status & WDC_SR_WCCR)) {
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+ pr_crit("Would Reboot\n");
+#else
+ pr_crit("Initiating system reboot\n");
+ emergency_restart();
+#endif
+#else
+ pr_crit("Reset in 5ms\n");
+#endif
+ }
+ spin_unlock(&wdt_lock);
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * wdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ wdt_ping();
+ }
+ return count;
+}
+
+/**
+ * wdt_ioctl:
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features. We only actually usefully support
+ * querying capabilities and current status.
+ */
+
+static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_heartbeat;
+ int status;
+
+ struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT|
+ WDIOF_MAGICCLOSE|
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = "WDT500/501",
+ };
+
+ /* Add options according to the card we have */
+ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+ if (type == 501) {
+ ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|
+ WDIOF_POWEROVER);
+ if (tachometer)
+ ident.options |= WDIOF_FANFAULT;
+ }
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ status = wdt_get_status();
+ return put_user(status, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+ if (wdt_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+ wdt_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/**
+ * wdt_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The watchdog device has been opened. The watchdog device is single
+ * open and on opening we load the counters. Counter zero is a 100Hz
+ * cascade, into counter 1 which downcounts to reboot. When the counter
+ * triggers counter 2 downcounts the length of the reset pulse which
+ * set set to be as long as possible.
+ */
+
+static int wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+ wdt_start();
+ return stream_open(inode, file);
+}
+
+/**
+ * wdt_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wdt_stop();
+ clear_bit(0, &wdt_is_open);
+ } else {
+ pr_crit("WDT device closed unexpectedly. WDT will not stop!\n");
+ wdt_ping();
+ }
+ expect_close = 0;
+ return 0;
+}
+
+/**
+ * wdt_temp_read:
+ * @file: file handle to the watchdog board
+ * @buf: buffer to write 1 byte into
+ * @count: length of buffer
+ * @ptr: offset (no seek allowed)
+ *
+ * Temp_read reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdt_temp_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ptr)
+{
+ int temperature = wdt_get_temperature();
+
+ if (copy_to_user(buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+/**
+ * wdt_temp_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The temperature device has been opened.
+ */
+
+static int wdt_temp_open(struct inode *inode, struct file *file)
+{
+ return stream_open(inode, file);
+}
+
+/**
+ * wdt_temp_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The temperature device has been closed.
+ */
+
+static int wdt_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/**
+ * wdt_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wdt_stop();
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static const struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt_write,
+ .unlocked_ioctl = wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wdt_open,
+ .release = wdt_release,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+static const struct file_operations wdt_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = wdt_temp_read,
+ .open = wdt_temp_open,
+ .release = wdt_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &wdt_temp_fops,
+};
+
+/*
+ * The WDT card 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,
+};
+
+/**
+ * wdt_exit:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit wdt_exit(void)
+{
+ misc_deregister(&wdt_miscdev);
+ if (type == 501)
+ misc_deregister(&temp_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+ free_irq(irq, NULL);
+ release_region(io, 8);
+}
+
+/**
+ * wdt_init:
+ *
+ * Set up the WDT watchdog board. All we have to do is grab the
+ * resources we require and bitch if anyone beat us to them.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init wdt_init(void)
+{
+ int ret;
+
+ if (type != 500 && type != 501) {
+ pr_err("unknown card type '%d'\n", type);
+ return -ENODEV;
+ }
+
+ /* Check that the heartbeat value is within it's range;
+ if not reset to the default */
+ if (wdt_set_heartbeat(heartbeat)) {
+ wdt_set_heartbeat(WD_TIMO);
+ pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n",
+ WD_TIMO);
+ }
+
+ if (!request_region(io, 8, "wdt501p")) {
+ pr_err("I/O address 0x%04x already in use\n", io);
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = request_irq(irq, wdt_interrupt, 0, "wdt501p", NULL);
+ if (ret) {
+ pr_err("IRQ %d is not free\n", irq);
+ goto outreg;
+ }
+
+ ret = register_reboot_notifier(&wdt_notifier);
+ if (ret) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto outirq;
+ }
+
+ if (type == 501) {
+ ret = misc_register(&temp_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto outrbt;
+ }
+ }
+
+ ret = misc_register(&wdt_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto outmisc;
+ }
+
+ pr_info("WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n",
+ io, irq, heartbeat, nowayout);
+ if (type == 501)
+ pr_info("Fan Tachometer is %s\n",
+ tachometer ? "Enabled" : "Disabled");
+ return 0;
+
+outmisc:
+ if (type == 501)
+ misc_deregister(&temp_miscdev);
+outrbt:
+ unregister_reboot_notifier(&wdt_notifier);
+outirq:
+ free_irq(irq, NULL);
+outreg:
+ release_region(io, 8);
+out:
+ return ret;
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c
new file mode 100644
index 000000000..110249e5f
--- /dev/null
+++ b/drivers/watchdog/wdt285.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Intel 21285 watchdog driver
+ * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998
+ *
+ * based on
+ *
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <linux/irq.h>
+#include <mach/hardware.h>
+
+#include <asm/mach-types.h>
+#include <asm/system_info.h>
+#include <asm/hardware/dec21285.h>
+
+/*
+ * Define this to stop the watchdog actually rebooting the machine.
+ */
+#undef ONLY_TESTING
+
+static unsigned int soft_margin = 60; /* in seconds */
+static unsigned int reload;
+static unsigned long timer_alive;
+
+#ifdef ONLY_TESTING
+/*
+ * If the timer expires..
+ */
+static void watchdog_fire(int irq, void *dev_id)
+{
+ pr_crit("Would Reboot\n");
+ *CSR_TIMER4_CNTL = 0;
+ *CSR_TIMER4_CLR = 0;
+}
+#endif
+
+/*
+ * Refresh the timer.
+ */
+static void watchdog_ping(void)
+{
+ *CSR_TIMER4_LOAD = reload;
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int watchdog_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ if (*CSR_SA110_CNTL & (1 << 13))
+ return -EBUSY;
+
+ if (test_and_set_bit(1, &timer_alive))
+ return -EBUSY;
+
+ reload = soft_margin * (mem_fclk_21285 / 256);
+
+ *CSR_TIMER4_CLR = 0;
+ watchdog_ping();
+ *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD
+ | TIMER_CNTL_DIV256;
+
+#ifdef ONLY_TESTING
+ ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL);
+ if (ret) {
+ *CSR_TIMER4_CNTL = 0;
+ clear_bit(1, &timer_alive);
+ }
+#else
+ /*
+ * Setting this bit is irreversible; once enabled, there is
+ * no way to disable the watchdog.
+ */
+ *CSR_SA110_CNTL |= 1 << 13;
+
+ ret = 0;
+#endif
+ stream_open(inode, file);
+ return ret;
+}
+
+/*
+ * Shut off the timer.
+ * Note: if we really have enabled the watchdog, there
+ * is no way to turn off.
+ */
+static int watchdog_release(struct inode *inode, struct file *file)
+{
+#ifdef ONLY_TESTING
+ free_irq(IRQ_TIMER4, NULL);
+ clear_bit(1, &timer_alive);
+#endif
+ return 0;
+}
+
+static ssize_t watchdog_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if (len)
+ watchdog_ping();
+
+ return len;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT,
+ .identity = "Footbridge Watchdog",
+};
+
+static long watchdog_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int __user *int_arg = (int __user *)arg;
+ int new_margin, ret = -ENOTTY;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = 0;
+ if (copy_to_user((void __user *)arg, &ident, sizeof(ident)))
+ ret = -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, int_arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ watchdog_ping();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(new_margin, int_arg);
+ if (ret)
+ break;
+
+ /* Arbitrary, can't find the card's limits */
+ if (new_margin < 0 || new_margin > 60) {
+ ret = -EINVAL;
+ break;
+ }
+
+ soft_margin = new_margin;
+ reload = soft_margin * (mem_fclk_21285 / 256);
+ watchdog_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(soft_margin, int_arg);
+ break;
+ }
+ return ret;
+}
+
+static const struct file_operations watchdog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = watchdog_write,
+ .unlocked_ioctl = watchdog_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = watchdog_open,
+ .release = watchdog_release,
+};
+
+static struct miscdevice watchdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &watchdog_fops,
+};
+
+static int __init footbridge_watchdog_init(void)
+{
+ int retval;
+
+ if (machine_is_netwinder())
+ return -ENODEV;
+
+ retval = misc_register(&watchdog_miscdev);
+ if (retval < 0)
+ return retval;
+
+ pr_info("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n",
+ soft_margin);
+
+ if (machine_is_cats())
+ pr_warn("Warning: Watchdog reset may not work on this machine\n");
+ return 0;
+}
+
+static void __exit footbridge_watchdog_exit(void)
+{
+ misc_deregister(&watchdog_miscdev);
+}
+
+MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>");
+MODULE_DESCRIPTION("Footbridge watchdog driver");
+MODULE_LICENSE("GPL");
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds");
+
+module_init(footbridge_watchdog_init);
+module_exit(footbridge_watchdog_exit);
diff --git a/drivers/watchdog/wdt977.c b/drivers/watchdog/wdt977.c
new file mode 100644
index 000000000..c9b8e863f
--- /dev/null
+++ b/drivers/watchdog/wdt977.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Wdt977 0.04: A Watchdog Device for Netwinder W83977AF chip
+ *
+ * (c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>)
+ *
+ * -----------------------
+ *
+ * -----------------------
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * 19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface
+ * 06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts
+ * from minutes to seconds.
+ * 07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in
+ * nwwatchdog_init.
+ * 25-Oct-2005 Woody Suwalski: Convert addresses to #defs, add spinlocks
+ * remove limitiation to be used on
+ * Netwinders only
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <asm/mach-types.h>
+
+#define WATCHDOG_VERSION "0.04"
+#define WATCHDOG_NAME "Wdt977"
+
+#define IO_INDEX_PORT 0x370 /* on some systems it can be 0x3F0 */
+#define IO_DATA_PORT (IO_INDEX_PORT + 1)
+
+#define UNLOCK_DATA 0x87
+#define LOCK_DATA 0xAA
+#define DEVICE_REGISTER 0x07
+
+
+#define DEFAULT_TIMEOUT 60 /* default timeout in seconds */
+
+static int timeout = DEFAULT_TIMEOUT;
+static int timeoutM; /* timeout in minutes */
+static unsigned long timer_alive;
+static int testmode;
+static char expect_close;
+static DEFINE_SPINLOCK(spinlock);
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (60..15300, default="
+ __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+module_param(testmode, int, 0);
+MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0");
+
+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) ")");
+
+/*
+ * Start the watchdog
+ */
+
+static int wdt977_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4
+ * F2 has the timeout in minutes
+ * F3 could be set to the POWER LED blink (with GP17 set to PowerLed)
+ * at timeout, and to reset timer on kbd/mouse activity (not impl.)
+ * F4 is used to just clear the TIMEOUT'ed state (bit 0)
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(timeoutM, IO_DATA_PORT);
+ outb_p(0xF3, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT); /* another setting is 0E for
+ kbd/mouse/LED */
+ outb_p(0xF4, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+
+ /* At last select device Aux1 (dev=7) and set GP16 as a
+ * watchdog output. In test mode watch the bit 1 on F4 to
+ * indicate "triggered"
+ */
+ if (!testmode) {
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x07, IO_DATA_PORT);
+ outb_p(0xE6, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ }
+
+ /* lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+ pr_info("activated\n");
+
+ return 0;
+}
+
+/*
+ * Stop the watchdog
+ */
+
+static int wdt977_stop(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4
+ * F3 is reset to its default state
+ * F4 can clear the TIMEOUT'ed state (bit 0) - back to default
+ * We can not use GP17 as a PowerLed, as we use its usage as a RedLed
+ */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(0xFF, IO_DATA_PORT);
+ outb_p(0xF3, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+ outb_p(0xF4, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(0x00, IO_DATA_PORT);
+
+ /* at last select device Aux1 (dev=7) and set
+ GP16 as a watchdog output */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x07, IO_DATA_PORT);
+ outb_p(0xE6, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+
+ /* lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+ pr_info("shutdown\n");
+
+ return 0;
+}
+
+/*
+ * Send a keepalive ping to the watchdog
+ * This is done by simply re-writing the timeout to reg. 0xF2
+ */
+
+static int wdt977_keepalive(void)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* select device Aux2 (device=8) and kicks watchdog reg F2 */
+ /* F2 has the timeout in minutes */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF2, IO_INDEX_PORT);
+ outb_p(timeoutM, IO_DATA_PORT);
+
+ /* lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ return 0;
+}
+
+/*
+ * Set the watchdog timeout value
+ */
+
+static int wdt977_set_timeout(int t)
+{
+ int tmrval;
+
+ /* convert seconds to minutes, rounding up */
+ tmrval = (t + 59) / 60;
+
+ if (machine_is_netwinder()) {
+ /* we have a hw bug somewhere, so each 977 minute is actually
+ * only 30sec. This limits the max timeout to half of device
+ * max of 255 minutes...
+ */
+ tmrval += tmrval;
+ }
+
+ if (tmrval < 1 || tmrval > 255)
+ return -EINVAL;
+
+ /* timeout is the timeout in seconds, timeoutM is
+ the timeout in minutes) */
+ timeout = t;
+ timeoutM = tmrval;
+ return 0;
+}
+
+/*
+ * Get the watchdog status
+ */
+
+static int wdt977_get_status(int *status)
+{
+ int new_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&spinlock, flags);
+
+ /* unlock the SuperIO chip */
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+ outb_p(UNLOCK_DATA, IO_INDEX_PORT);
+
+ /* select device Aux2 (device=8) and read watchdog reg F4 */
+ outb_p(DEVICE_REGISTER, IO_INDEX_PORT);
+ outb_p(0x08, IO_DATA_PORT);
+ outb_p(0xF4, IO_INDEX_PORT);
+ new_status = inb_p(IO_DATA_PORT);
+
+ /* lock the SuperIO chip */
+ outb_p(LOCK_DATA, IO_INDEX_PORT);
+
+ spin_unlock_irqrestore(&spinlock, flags);
+
+ *status = 0;
+ if (new_status & 1)
+ *status |= WDIOF_CARDRESET;
+
+ return 0;
+}
+
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int wdt977_open(struct inode *inode, struct file *file)
+{
+ /* If the watchdog is alive we don't need to start it again */
+ if (test_and_set_bit(0, &timer_alive))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ wdt977_start();
+ return stream_open(inode, file);
+}
+
+static int wdt977_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ * Lock it in if it's a module and we set nowayout
+ */
+ if (expect_close == 42) {
+ wdt977_stop();
+ clear_bit(0, &timer_alive);
+ } else {
+ wdt977_keepalive();
+ pr_crit("Unexpected close, not stopping watchdog!\n");
+ }
+ expect_close = 0;
+ return 0;
+}
+
+
+/*
+ * wdt977_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt977_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should restart timer */
+ wdt977_keepalive();
+ }
+ return count;
+}
+
+static const struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = WATCHDOG_NAME,
+};
+
+/*
+ * wdt977_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static long wdt977_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int status;
+ int new_options, retval = -EINVAL;
+ int new_timeout;
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ uarg.i = (int __user *)arg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ wdt977_get_status(&status);
+ return put_user(status, uarg.i);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, uarg.i);
+
+ case WDIOC_SETOPTIONS:
+ if (get_user(new_options, uarg.i))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ wdt977_stop();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ wdt977_start();
+ retval = 0;
+ }
+
+ return retval;
+
+ case WDIOC_KEEPALIVE:
+ wdt977_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, uarg.i))
+ return -EFAULT;
+
+ if (wdt977_set_timeout(new_timeout))
+ return -EINVAL;
+
+ wdt977_keepalive();
+ fallthrough;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, uarg.i);
+
+ default:
+ return -ENOTTY;
+
+ }
+}
+
+static int wdt977_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wdt977_stop();
+ return NOTIFY_DONE;
+}
+
+static const struct file_operations wdt977_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt977_write,
+ .unlocked_ioctl = wdt977_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wdt977_open,
+ .release = wdt977_release,
+};
+
+static struct miscdevice wdt977_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt977_fops,
+};
+
+static struct notifier_block wdt977_notifier = {
+ .notifier_call = wdt977_notify_sys,
+};
+
+static int __init wd977_init(void)
+{
+ int rc;
+
+ pr_info("driver v%s\n", WATCHDOG_VERSION);
+
+ /* Check that the timeout value is within its range;
+ if not reset to the default */
+ if (wdt977_set_timeout(timeout)) {
+ wdt977_set_timeout(DEFAULT_TIMEOUT);
+ pr_info("timeout value must be 60 < timeout < 15300, using %d\n",
+ DEFAULT_TIMEOUT);
+ }
+
+ /* on Netwinder the IOports are already reserved by
+ * arch/arm/mach-footbridge/netwinder-hw.c
+ */
+ if (!machine_is_netwinder()) {
+ if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n",
+ IO_INDEX_PORT);
+ rc = -EIO;
+ goto err_out;
+ }
+ }
+
+ rc = register_reboot_notifier(&wdt977_notifier);
+ if (rc) {
+ pr_err("cannot register reboot notifier (err=%d)\n", rc);
+ goto err_out_region;
+ }
+
+ rc = misc_register(&wdt977_miscdev);
+ if (rc) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ wdt977_miscdev.minor, rc);
+ goto err_out_reboot;
+ }
+
+ pr_info("initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n",
+ timeout, nowayout, testmode);
+
+ return 0;
+
+err_out_reboot:
+ unregister_reboot_notifier(&wdt977_notifier);
+err_out_region:
+ if (!machine_is_netwinder())
+ release_region(IO_INDEX_PORT, 2);
+err_out:
+ return rc;
+}
+
+static void __exit wd977_exit(void)
+{
+ wdt977_stop();
+ misc_deregister(&wdt977_miscdev);
+ unregister_reboot_notifier(&wdt977_notifier);
+ release_region(IO_INDEX_PORT, 2);
+}
+
+module_init(wd977_init);
+module_exit(wd977_exit);
+
+MODULE_AUTHOR("Woody Suwalski <woodys@xandros.com>");
+MODULE_DESCRIPTION("W83977AF Watchdog driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c
new file mode 100644
index 000000000..d5e56b601
--- /dev/null
+++ b/drivers/watchdog/wdt_pci.c
@@ -0,0 +1,742 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Industrial Computer Source PCI-WDT500/501 driver
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>,
+ * All Rights Reserved.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Release 0.10.
+ *
+ * Fixes
+ * Dave Gregorich : Modularisation and minor bugs
+ * Alan Cox : Added the watchdog ioctl() stuff
+ * Alan Cox : Fixed the reboot problem (as noted by
+ * Matt Crocker).
+ * Alan Cox : Added wdt= boot option
+ * Alan Cox : Cleaned up copy/user stuff
+ * Tim Hockin : Added insmod parameters, comment cleanup
+ * Parameterized timeout
+ * JP Nollmann : Added support for PCI wdt501p
+ * Alan Cox : Split ISA and PCI cards into two drivers
+ * Jeff Garzik : PCI cleanups
+ * Tigran Aivazian : Restructured wdtpci_init_one() to handle
+ * failures
+ * Joel Becker : Added WDIOC_GET/SETTIMEOUT
+ * Zwane Mwaikambo : Magic char closing, locking changes,
+ * cleanups
+ * Matt Domsch : nowayout module option
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+
+#define WDT_IS_PCI
+#include "wd501p.h"
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int dev_count;
+
+static unsigned long open_lock;
+static DEFINE_SPINLOCK(wdtpci_lock);
+static char expect_close;
+
+static resource_size_t io;
+static int irq;
+
+/* Default timeout */
+#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat in seconds. (0<heartbeat<65536, default="
+ __MODULE_STRING(WD_TIMO) ")");
+
+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) ")");
+
+/* Support for the Fan Tachometer on the PCI-WDT501 */
+static int tachometer;
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer,
+ "PCI-WDT501 Fan Tachometer support (0=disable, default=0)");
+
+static int type = 500;
+module_param(type, int, 0);
+MODULE_PARM_DESC(type,
+ "PCI-WDT501 Card type (500 or 501 , default=500)");
+
+/*
+ * Programming support
+ */
+
+static void wdtpci_ctr_mode(int ctr, int mode)
+{
+ ctr <<= 6;
+ ctr |= 0x30;
+ ctr |= (mode << 1);
+ outb(ctr, WDT_CR);
+ udelay(8);
+}
+
+static void wdtpci_ctr_load(int ctr, int val)
+{
+ outb(val & 0xFF, WDT_COUNT0 + ctr);
+ udelay(8);
+ outb(val >> 8, WDT_COUNT0 + ctr);
+ udelay(8);
+}
+
+/**
+ * wdtpci_start:
+ *
+ * Start the watchdog driver.
+ */
+
+static int wdtpci_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdtpci_lock, flags);
+
+ /*
+ * "pet" the watchdog, as Access says.
+ * This resets the clock outputs.
+ */
+ inb(WDT_DC); /* Disable watchdog */
+ udelay(8);
+ wdtpci_ctr_mode(2, 0); /* Program CTR2 for Mode 0:
+ Pulse on Terminal Count */
+ outb(0, WDT_DC); /* Enable watchdog */
+ udelay(8);
+ inb(WDT_DC); /* Disable watchdog */
+ udelay(8);
+ outb(0, WDT_CLOCK); /* 2.0833MHz clock */
+ udelay(8);
+ inb(WDT_BUZZER); /* disable */
+ udelay(8);
+ inb(WDT_OPTONOTRST); /* disable */
+ udelay(8);
+ inb(WDT_OPTORST); /* disable */
+ udelay(8);
+ inb(WDT_PROGOUT); /* disable */
+ udelay(8);
+ wdtpci_ctr_mode(0, 3); /* Program CTR0 for Mode 3:
+ Square Wave Generator */
+ wdtpci_ctr_mode(1, 2); /* Program CTR1 for Mode 2:
+ Rate Generator */
+ wdtpci_ctr_mode(2, 1); /* Program CTR2 for Mode 1:
+ Retriggerable One-Shot */
+ wdtpci_ctr_load(0, 20833); /* count at 100Hz */
+ wdtpci_ctr_load(1, wd_heartbeat);/* Heartbeat */
+ /* DO NOT LOAD CTR2 on PCI card! -- JPN */
+ outb(0, WDT_DC); /* Enable watchdog */
+ udelay(8);
+
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_stop:
+ *
+ * Stop the watchdog driver.
+ */
+
+static int wdtpci_stop(void)
+{
+ unsigned long flags;
+
+ /* Turn the card off */
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ inb(WDT_DC); /* Disable watchdog */
+ udelay(8);
+ wdtpci_ctr_load(2, 0); /* 0 length reset pulses now */
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_ping:
+ *
+ * Reload counter one with the watchdog heartbeat. We don't bother
+ * reloading the cascade counter.
+ */
+
+static int wdtpci_ping(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ /* Write a watchdog value */
+ inb(WDT_DC); /* Disable watchdog */
+ udelay(8);
+ wdtpci_ctr_mode(1, 2); /* Re-Program CTR1 for Mode 2:
+ Rate Generator */
+ wdtpci_ctr_load(1, wd_heartbeat);/* Heartbeat */
+ outb(0, WDT_DC); /* Enable watchdog */
+ udelay(8);
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_set_heartbeat:
+ * @t: the new heartbeat value that needs to be set.
+ *
+ * Set a new heartbeat value for the watchdog device. If the heartbeat
+ * value is incorrect we keep the old value and return -EINVAL.
+ * If successful we return 0.
+ */
+static int wdtpci_set_heartbeat(int t)
+{
+ /* Arbitrary, can't find the card's limits */
+ if (t < 1 || t > 65535)
+ return -EINVAL;
+
+ heartbeat = t;
+ wd_heartbeat = t * 100;
+ return 0;
+}
+
+/**
+ * wdtpci_get_status:
+ * @status: the new status.
+ *
+ * Extract the status information from a WDT watchdog device. There are
+ * several board variants so we have to know which bits are valid. Some
+ * bits default to one and some to zero in order to be maximally painful.
+ *
+ * we then map the bits onto the status ioctl flags.
+ */
+
+static int wdtpci_get_status(int *status)
+{
+ unsigned char new_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ new_status = inb(WDT_SR);
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+
+ *status = 0;
+ if (new_status & WDC_SR_ISOI0)
+ *status |= WDIOF_EXTERN1;
+ if (new_status & WDC_SR_ISII1)
+ *status |= WDIOF_EXTERN2;
+ if (type == 501) {
+ if (!(new_status & WDC_SR_TGOOD))
+ *status |= WDIOF_OVERHEAT;
+ if (!(new_status & WDC_SR_PSUOVER))
+ *status |= WDIOF_POWEROVER;
+ if (!(new_status & WDC_SR_PSUUNDR))
+ *status |= WDIOF_POWERUNDER;
+ if (tachometer) {
+ if (!(new_status & WDC_SR_FANGOOD))
+ *status |= WDIOF_FANFAULT;
+ }
+ }
+ return 0;
+}
+
+/**
+ * wdtpci_get_temperature:
+ *
+ * Reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdtpci_get_temperature(int *temperature)
+{
+ unsigned short c;
+ unsigned long flags;
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ c = inb(WDT_RT);
+ udelay(8);
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ *temperature = (c * 11 / 15) + 7;
+ return 0;
+}
+
+/**
+ * wdtpci_interrupt:
+ * @irq: Interrupt number
+ * @dev_id: Unused as we don't allow multiple devices.
+ *
+ * Handle an interrupt from the board. These are raised when the status
+ * map changes in what the board considers an interesting way. That means
+ * a failure condition occurring.
+ */
+
+static irqreturn_t wdtpci_interrupt(int irq, void *dev_id)
+{
+ /*
+ * Read the status register see what is up and
+ * then printk it.
+ */
+ unsigned char status;
+
+ spin_lock(&wdtpci_lock);
+
+ status = inb(WDT_SR);
+ udelay(8);
+
+ pr_crit("status %d\n", status);
+
+ if (type == 501) {
+ if (!(status & WDC_SR_TGOOD)) {
+ pr_crit("Overheat alarm (%d)\n", inb(WDT_RT));
+ udelay(8);
+ }
+ if (!(status & WDC_SR_PSUOVER))
+ pr_crit("PSU over voltage\n");
+ if (!(status & WDC_SR_PSUUNDR))
+ pr_crit("PSU under voltage\n");
+ if (tachometer) {
+ if (!(status & WDC_SR_FANGOOD))
+ pr_crit("Possible fan fault\n");
+ }
+ }
+ if (!(status & WDC_SR_WCCR)) {
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+ pr_crit("Would Reboot\n");
+#else
+ pr_crit("Initiating system reboot\n");
+ emergency_restart();
+#endif
+#else
+ pr_crit("Reset in 5ms\n");
+#endif
+ }
+ spin_unlock(&wdtpci_lock);
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * wdtpci_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdtpci_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ wdtpci_ping();
+ }
+ return count;
+}
+
+/**
+ * wdtpci_ioctl:
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features. We only actually usefully support
+ * querying capabilities and current status.
+ */
+
+static long wdtpci_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_heartbeat;
+ int status;
+
+ struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT|
+ WDIOF_MAGICCLOSE|
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = "PCI-WDT500/501",
+ };
+
+ /* Add options according to the card we have */
+ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+ if (type == 501) {
+ ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|
+ WDIOF_POWEROVER);
+ if (tachometer)
+ ident.options |= WDIOF_FANFAULT;
+ }
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ wdtpci_get_status(&status);
+ return put_user(status, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdtpci_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+ if (wdtpci_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+ wdtpci_ping();
+ fallthrough;
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+/**
+ * wdtpci_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The watchdog device has been opened. The watchdog device is single
+ * open and on opening we load the counters. Counter zero is a 100Hz
+ * cascade, into counter 1 which downcounts to reboot. When the counter
+ * triggers counter 2 downcounts the length of the reset pulse which
+ * set set to be as long as possible.
+ */
+
+static int wdtpci_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &open_lock))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+ /*
+ * Activate
+ */
+ wdtpci_start();
+ return stream_open(inode, file);
+}
+
+/**
+ * wdtpci_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int wdtpci_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wdtpci_stop();
+ } else {
+ pr_crit("Unexpected close, not stopping timer!\n");
+ wdtpci_ping();
+ }
+ expect_close = 0;
+ clear_bit(0, &open_lock);
+ return 0;
+}
+
+/**
+ * wdtpci_temp_read:
+ * @file: file handle to the watchdog board
+ * @buf: buffer to write 1 byte into
+ * @count: length of buffer
+ * @ptr: offset (no seek allowed)
+ *
+ * Read reports the temperature in degrees Fahrenheit. The API is in
+ * fahrenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdtpci_temp_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ptr)
+{
+ int temperature;
+
+ if (wdtpci_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user(buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+/**
+ * wdtpci_temp_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The temperature device has been opened.
+ */
+
+static int wdtpci_temp_open(struct inode *inode, struct file *file)
+{
+ return stream_open(inode, file);
+}
+
+/**
+ * wdtpci_temp_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The temperature device has been closed.
+ */
+
+static int wdtpci_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/**
+ * wdtpci_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ wdtpci_stop();
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static const struct file_operations wdtpci_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdtpci_write,
+ .unlocked_ioctl = wdtpci_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = wdtpci_open,
+ .release = wdtpci_release,
+};
+
+static struct miscdevice wdtpci_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdtpci_fops,
+};
+
+static const struct file_operations wdtpci_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = wdtpci_temp_read,
+ .open = wdtpci_temp_open,
+ .release = wdtpci_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &wdtpci_temp_fops,
+};
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdtpci_notifier = {
+ .notifier_call = wdtpci_notify_sys,
+};
+
+
+static int wdtpci_init_one(struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ int ret = -EIO;
+
+ dev_count++;
+ if (dev_count > 1) {
+ pr_err("This driver only supports one device\n");
+ return -ENODEV;
+ }
+
+ if (type != 500 && type != 501) {
+ pr_err("unknown card type '%d'\n", type);
+ return -ENODEV;
+ }
+
+ if (pci_enable_device(dev)) {
+ pr_err("Not possible to enable PCI Device\n");
+ return -ENODEV;
+ }
+
+ if (pci_resource_start(dev, 2) == 0x0000) {
+ pr_err("No I/O-Address for card detected\n");
+ ret = -ENODEV;
+ goto out_pci;
+ }
+
+ if (pci_request_region(dev, 2, "wdt_pci")) {
+ pr_err("I/O address 0x%llx already in use\n",
+ (unsigned long long)pci_resource_start(dev, 2));
+ goto out_pci;
+ }
+
+ irq = dev->irq;
+ io = pci_resource_start(dev, 2);
+
+ if (request_irq(irq, wdtpci_interrupt, IRQF_SHARED,
+ "wdt_pci", &wdtpci_miscdev)) {
+ pr_err("IRQ %d is not free\n", irq);
+ goto out_reg;
+ }
+
+ pr_info("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%llx (Interrupt %d)\n",
+ (unsigned long long)io, irq);
+
+ /* Check that the heartbeat value is within its range;
+ if not reset to the default */
+ if (wdtpci_set_heartbeat(heartbeat)) {
+ wdtpci_set_heartbeat(WD_TIMO);
+ pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n",
+ WD_TIMO);
+ }
+
+ ret = register_reboot_notifier(&wdtpci_notifier);
+ if (ret) {
+ pr_err("cannot register reboot notifier (err=%d)\n", ret);
+ goto out_irq;
+ }
+
+ if (type == 501) {
+ ret = misc_register(&temp_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto out_rbt;
+ }
+ }
+
+ ret = misc_register(&wdtpci_miscdev);
+ if (ret) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto out_misc;
+ }
+
+ pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+ if (type == 501)
+ pr_info("Fan Tachometer is %s\n",
+ tachometer ? "Enabled" : "Disabled");
+
+ ret = 0;
+out:
+ return ret;
+
+out_misc:
+ if (type == 501)
+ misc_deregister(&temp_miscdev);
+out_rbt:
+ unregister_reboot_notifier(&wdtpci_notifier);
+out_irq:
+ free_irq(irq, &wdtpci_miscdev);
+out_reg:
+ pci_release_region(dev, 2);
+out_pci:
+ pci_disable_device(dev);
+ goto out;
+}
+
+
+static void wdtpci_remove_one(struct pci_dev *pdev)
+{
+ /* here we assume only one device will ever have
+ * been picked up and registered by probe function */
+ misc_deregister(&wdtpci_miscdev);
+ if (type == 501)
+ misc_deregister(&temp_miscdev);
+ unregister_reboot_notifier(&wdtpci_notifier);
+ free_irq(irq, &wdtpci_miscdev);
+ pci_release_region(pdev, 2);
+ pci_disable_device(pdev);
+ dev_count--;
+}
+
+
+static const struct pci_device_id wdtpci_pci_tbl[] = {
+ {
+ .vendor = PCI_VENDOR_ID_ACCESSIO,
+ .device = PCI_DEVICE_ID_ACCESSIO_WDG_CSM,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl);
+
+
+static struct pci_driver wdtpci_driver = {
+ .name = "wdt_pci",
+ .id_table = wdtpci_pci_tbl,
+ .probe = wdtpci_init_one,
+ .remove = wdtpci_remove_one,
+};
+
+module_pci_driver(wdtpci_driver);
+
+MODULE_AUTHOR("JP Nollmann, Alan Cox");
+MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/wm831x_wdt.c b/drivers/watchdog/wm831x_wdt.c
new file mode 100644
index 000000000..d96ad8f38
--- /dev/null
+++ b/drivers/watchdog/wm831x_wdt.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for the wm831x PMICs
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/watchdog.h>
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct wm831x_wdt_drvdata {
+ struct watchdog_device wdt;
+ struct wm831x *wm831x;
+ struct mutex lock;
+ int update_state;
+};
+
+/* We can't use the sub-second values here but they're included
+ * for completeness. */
+static struct {
+ unsigned int time; /* Seconds */
+ u16 val; /* WDOG_TO value */
+} wm831x_wdt_cfgs[] = {
+ { 1, 2 },
+ { 2, 3 },
+ { 4, 4 },
+ { 8, 5 },
+ { 16, 6 },
+ { 32, 7 },
+ { 33, 7 }, /* Actually 32.768s so include both, others round down */
+};
+
+static int wm831x_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct wm831x *wm831x = driver_data->wm831x;
+ int ret;
+
+ mutex_lock(&driver_data->lock);
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_ENA, WM831X_WDOG_ENA);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ mutex_unlock(&driver_data->lock);
+
+ return ret;
+}
+
+static int wm831x_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct wm831x *wm831x = driver_data->wm831x;
+ int ret;
+
+ mutex_lock(&driver_data->lock);
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_ENA, 0);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ mutex_unlock(&driver_data->lock);
+
+ return ret;
+}
+
+static int wm831x_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct wm831x *wm831x = driver_data->wm831x;
+ int ret;
+ u16 reg;
+
+ mutex_lock(&driver_data->lock);
+
+ reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+
+ if (!(reg & WM831X_WDOG_RST_SRC)) {
+ dev_err(wm831x->dev, "Hardware watchdog update unsupported\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ reg |= WM831X_WDOG_RESET;
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+out:
+ mutex_unlock(&driver_data->lock);
+
+ return ret;
+}
+
+static int wm831x_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct wm831x_wdt_drvdata *driver_data = watchdog_get_drvdata(wdt_dev);
+ struct wm831x *wm831x = driver_data->wm831x;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+ if (wm831x_wdt_cfgs[i].time == timeout)
+ break;
+ if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
+ return -EINVAL;
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_TO_MASK,
+ wm831x_wdt_cfgs[i].val);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ wdt_dev->timeout = timeout;
+
+ return ret;
+}
+
+static const struct watchdog_info wm831x_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "WM831x Watchdog",
+};
+
+static const struct watchdog_ops wm831x_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wm831x_wdt_start,
+ .stop = wm831x_wdt_stop,
+ .ping = wm831x_wdt_ping,
+ .set_timeout = wm831x_wdt_set_timeout,
+};
+
+static int wm831x_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct wm831x *wm831x = dev_get_drvdata(dev->parent);
+ struct wm831x_pdata *chip_pdata = dev_get_platdata(dev->parent);
+ struct wm831x_watchdog_pdata *pdata;
+ struct wm831x_wdt_drvdata *driver_data;
+ struct watchdog_device *wm831x_wdt;
+ int reg, ret, i;
+
+ ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read watchdog status: %d\n",
+ ret);
+ return ret;
+ }
+ reg = ret;
+
+ if (reg & WM831X_WDOG_DEBUG)
+ dev_warn(wm831x->dev, "Watchdog is paused\n");
+
+ driver_data = devm_kzalloc(dev, sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data)
+ return -ENOMEM;
+
+ mutex_init(&driver_data->lock);
+ driver_data->wm831x = wm831x;
+
+ wm831x_wdt = &driver_data->wdt;
+
+ wm831x_wdt->info = &wm831x_wdt_info;
+ wm831x_wdt->ops = &wm831x_wdt_ops;
+ wm831x_wdt->parent = dev;
+ watchdog_set_nowayout(wm831x_wdt, nowayout);
+ watchdog_set_drvdata(wm831x_wdt, driver_data);
+
+ reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+ reg &= WM831X_WDOG_TO_MASK;
+ for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+ if (wm831x_wdt_cfgs[i].val == reg)
+ break;
+ if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
+ dev_warn(wm831x->dev,
+ "Unknown watchdog timeout: %x\n", reg);
+ else
+ wm831x_wdt->timeout = wm831x_wdt_cfgs[i].time;
+
+ /* Apply any configuration */
+ if (chip_pdata)
+ pdata = chip_pdata->watchdog;
+ else
+ pdata = NULL;
+
+ if (pdata) {
+ reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
+ WM831X_WDOG_RST_SRC);
+
+ reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT;
+ reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT;
+ reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT;
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev,
+ "Failed to unlock security key: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return devm_watchdog_register_device(dev, &driver_data->wdt);
+}
+
+static struct platform_driver wm831x_wdt_driver = {
+ .probe = wm831x_wdt_probe,
+ .driver = {
+ .name = "wm831x-watchdog",
+ },
+};
+
+module_platform_driver(wm831x_wdt_driver);
+
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM831x Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-watchdog");
diff --git a/drivers/watchdog/wm8350_wdt.c b/drivers/watchdog/wm8350_wdt.c
new file mode 100644
index 000000000..33c62d51f
--- /dev/null
+++ b/drivers/watchdog/wm8350_wdt.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Watchdog driver for the wm8350
+ *
+ * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+#include <linux/mfd/wm8350/core.h>
+
+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) ")");
+
+static DEFINE_MUTEX(wdt_mutex);
+
+static struct {
+ unsigned int time; /* Seconds */
+ u16 val; /* To be set in WM8350_SYSTEM_CONTROL_2 */
+} wm8350_wdt_cfgs[] = {
+ { 1, 0x02 },
+ { 2, 0x04 },
+ { 4, 0x05 },
+};
+
+static int wm8350_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int timeout)
+{
+ struct wm8350 *wm8350 = watchdog_get_drvdata(wdt_dev);
+ int ret, i;
+ u16 reg;
+
+ for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++)
+ if (wm8350_wdt_cfgs[i].time == timeout)
+ break;
+ if (i == ARRAY_SIZE(wm8350_wdt_cfgs))
+ return -EINVAL;
+
+ mutex_lock(&wdt_mutex);
+ wm8350_reg_unlock(wm8350);
+
+ reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
+ reg &= ~WM8350_WDOG_TO_MASK;
+ reg |= wm8350_wdt_cfgs[i].val;
+ ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
+
+ wm8350_reg_lock(wm8350);
+ mutex_unlock(&wdt_mutex);
+
+ wdt_dev->timeout = timeout;
+ return ret;
+}
+
+static int wm8350_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct wm8350 *wm8350 = watchdog_get_drvdata(wdt_dev);
+ int ret;
+ u16 reg;
+
+ mutex_lock(&wdt_mutex);
+ wm8350_reg_unlock(wm8350);
+
+ reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
+ reg &= ~WM8350_WDOG_MODE_MASK;
+ reg |= 0x20;
+ ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
+
+ wm8350_reg_lock(wm8350);
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm8350_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct wm8350 *wm8350 = watchdog_get_drvdata(wdt_dev);
+ int ret;
+ u16 reg;
+
+ mutex_lock(&wdt_mutex);
+ wm8350_reg_unlock(wm8350);
+
+ reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
+ reg &= ~WM8350_WDOG_MODE_MASK;
+ ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
+
+ wm8350_reg_lock(wm8350);
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm8350_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ struct wm8350 *wm8350 = watchdog_get_drvdata(wdt_dev);
+ int ret;
+ u16 reg;
+
+ mutex_lock(&wdt_mutex);
+
+ reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2);
+ ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg);
+
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static const struct watchdog_info wm8350_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "WM8350 Watchdog",
+};
+
+static const struct watchdog_ops wm8350_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = wm8350_wdt_start,
+ .stop = wm8350_wdt_stop,
+ .ping = wm8350_wdt_ping,
+ .set_timeout = wm8350_wdt_set_timeout,
+};
+
+static struct watchdog_device wm8350_wdt = {
+ .info = &wm8350_wdt_info,
+ .ops = &wm8350_wdt_ops,
+ .timeout = 4,
+ .min_timeout = 1,
+ .max_timeout = 4,
+};
+
+static int wm8350_wdt_probe(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+
+ if (!wm8350) {
+ pr_err("No driver data supplied\n");
+ return -ENODEV;
+ }
+
+ watchdog_set_nowayout(&wm8350_wdt, nowayout);
+ watchdog_set_drvdata(&wm8350_wdt, wm8350);
+ wm8350_wdt.parent = &pdev->dev;
+
+ /* Default to 4s timeout */
+ wm8350_wdt_set_timeout(&wm8350_wdt, 4);
+
+ return watchdog_register_device(&wm8350_wdt);
+}
+
+static int wm8350_wdt_remove(struct platform_device *pdev)
+{
+ watchdog_unregister_device(&wm8350_wdt);
+ return 0;
+}
+
+static struct platform_driver wm8350_wdt_driver = {
+ .probe = wm8350_wdt_probe,
+ .remove = wm8350_wdt_remove,
+ .driver = {
+ .name = "wm8350-wdt",
+ },
+};
+
+module_platform_driver(wm8350_wdt_driver);
+
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM8350 Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8350-wdt");
diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c
new file mode 100644
index 000000000..b343f421d
--- /dev/null
+++ b/drivers/watchdog/xen_wdt.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Xen Watchdog Driver
+ *
+ * (c) Copyright 2010 Novell, Inc.
+ */
+
+#define DRV_NAME "xen_wdt"
+
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/hrtimer.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <xen/xen.h>
+#include <asm/xen/hypercall.h>
+#include <xen/interface/sched.h>
+
+static struct platform_device *platform_device;
+static struct sched_watchdog wdt;
+static time64_t wdt_expires;
+
+#define WATCHDOG_TIMEOUT 60 /* in seconds */
+static unsigned int timeout;
+module_param(timeout, uint, S_IRUGO);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
+ "(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, S_IRUGO);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
+ "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static inline time64_t set_timeout(struct watchdog_device *wdd)
+{
+ wdt.timeout = wdd->timeout;
+ return ktime_get_seconds() + wdd->timeout;
+}
+
+static int xen_wdt_start(struct watchdog_device *wdd)
+{
+ time64_t expires;
+ int err;
+
+ expires = set_timeout(wdd);
+ if (!wdt.id)
+ err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
+ else
+ err = -EBUSY;
+ if (err > 0) {
+ wdt.id = err;
+ wdt_expires = expires;
+ err = 0;
+ } else
+ BUG_ON(!err);
+
+ return err;
+}
+
+static int xen_wdt_stop(struct watchdog_device *wdd)
+{
+ int err = 0;
+
+ wdt.timeout = 0;
+ if (wdt.id)
+ err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
+ if (!err)
+ wdt.id = 0;
+
+ return err;
+}
+
+static int xen_wdt_kick(struct watchdog_device *wdd)
+{
+ time64_t expires;
+ int err;
+
+ expires = set_timeout(wdd);
+ if (wdt.id)
+ err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
+ else
+ err = -ENXIO;
+ if (!err)
+ wdt_expires = expires;
+
+ return err;
+}
+
+static unsigned int xen_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ return wdt_expires - ktime_get_seconds();
+}
+
+static struct watchdog_info xen_wdt_info = {
+ .identity = DRV_NAME,
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+};
+
+static const struct watchdog_ops xen_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = xen_wdt_start,
+ .stop = xen_wdt_stop,
+ .ping = xen_wdt_kick,
+ .get_timeleft = xen_wdt_get_timeleft,
+};
+
+static struct watchdog_device xen_wdt_dev = {
+ .info = &xen_wdt_info,
+ .ops = &xen_wdt_ops,
+ .timeout = WATCHDOG_TIMEOUT,
+};
+
+static int xen_wdt_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sched_watchdog wd = { .id = ~0 };
+ int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd);
+
+ if (ret == -ENOSYS) {
+ dev_err(dev, "watchdog not supported by hypervisor\n");
+ return -ENODEV;
+ }
+
+ if (ret != -EINVAL) {
+ dev_err(dev, "unexpected hypervisor error (%d)\n", ret);
+ return -ENODEV;
+ }
+
+ watchdog_init_timeout(&xen_wdt_dev, timeout, NULL);
+ watchdog_set_nowayout(&xen_wdt_dev, nowayout);
+ watchdog_stop_on_reboot(&xen_wdt_dev);
+ watchdog_stop_on_unregister(&xen_wdt_dev);
+
+ ret = devm_watchdog_register_device(dev, &xen_wdt_dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "initialized (timeout=%ds, nowayout=%d)\n",
+ xen_wdt_dev.timeout, nowayout);
+
+ return 0;
+}
+
+static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state)
+{
+ typeof(wdt.id) id = wdt.id;
+ int rc = xen_wdt_stop(&xen_wdt_dev);
+
+ wdt.id = id;
+ return rc;
+}
+
+static int xen_wdt_resume(struct platform_device *dev)
+{
+ if (!wdt.id)
+ return 0;
+ wdt.id = 0;
+ return xen_wdt_start(&xen_wdt_dev);
+}
+
+static struct platform_driver xen_wdt_driver = {
+ .probe = xen_wdt_probe,
+ .suspend = xen_wdt_suspend,
+ .resume = xen_wdt_resume,
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init xen_wdt_init_module(void)
+{
+ int err;
+
+ if (!xen_domain())
+ return -ENODEV;
+
+ err = platform_driver_register(&xen_wdt_driver);
+ if (err)
+ return err;
+
+ platform_device = platform_device_register_simple(DRV_NAME,
+ -1, NULL, 0);
+ if (IS_ERR(platform_device)) {
+ err = PTR_ERR(platform_device);
+ platform_driver_unregister(&xen_wdt_driver);
+ }
+
+ return err;
+}
+
+static void __exit xen_wdt_cleanup_module(void)
+{
+ platform_device_unregister(platform_device);
+ platform_driver_unregister(&xen_wdt_driver);
+}
+
+module_init(xen_wdt_init_module);
+module_exit(xen_wdt_cleanup_module);
+
+MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>");
+MODULE_DESCRIPTION("Xen WatchDog Timer Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c
new file mode 100644
index 000000000..d0e888754
--- /dev/null
+++ b/drivers/watchdog/ziirave_wdt.c
@@ -0,0 +1,744 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015 Zodiac Inflight Innovations
+ *
+ * Author: Martyn Welch <martyn.welch@collabora.co.uk>
+ *
+ * Based on twl4030_wdt.c by Timo Kokkonen <timo.t.kokkonen at nokia.com>:
+ *
+ * Copyright (C) Nokia Corporation
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/ihex.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#include <asm/unaligned.h>
+
+#define ZIIRAVE_TIMEOUT_MIN 3
+#define ZIIRAVE_TIMEOUT_MAX 255
+#define ZIIRAVE_TIMEOUT_DEFAULT 30
+
+#define ZIIRAVE_PING_VALUE 0x0
+
+#define ZIIRAVE_STATE_INITIAL 0x0
+#define ZIIRAVE_STATE_OFF 0x1
+#define ZIIRAVE_STATE_ON 0x2
+
+#define ZIIRAVE_FW_NAME "ziirave_wdt.fw"
+
+static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL,
+ "host request", NULL, "illegal configuration",
+ "illegal instruction", "illegal trap",
+ "unknown"};
+
+#define ZIIRAVE_WDT_FIRM_VER_MAJOR 0x1
+#define ZIIRAVE_WDT_BOOT_VER_MAJOR 0x3
+#define ZIIRAVE_WDT_RESET_REASON 0x5
+#define ZIIRAVE_WDT_STATE 0x6
+#define ZIIRAVE_WDT_TIMEOUT 0x7
+#define ZIIRAVE_WDT_TIME_LEFT 0x8
+#define ZIIRAVE_WDT_PING 0x9
+#define ZIIRAVE_WDT_RESET_DURATION 0xa
+
+#define ZIIRAVE_FIRM_PKT_TOTAL_SIZE 20
+#define ZIIRAVE_FIRM_PKT_DATA_SIZE 16
+#define ZIIRAVE_FIRM_FLASH_MEMORY_START (2 * 0x1600)
+#define ZIIRAVE_FIRM_FLASH_MEMORY_END (2 * 0x2bbf)
+#define ZIIRAVE_FIRM_PAGE_SIZE 128
+
+/* Received and ready for next Download packet. */
+#define ZIIRAVE_FIRM_DOWNLOAD_ACK 1
+
+/* Firmware commands */
+#define ZIIRAVE_CMD_DOWNLOAD_START 0x10
+#define ZIIRAVE_CMD_DOWNLOAD_END 0x11
+#define ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR 0x12
+#define ZIIRAVE_CMD_DOWNLOAD_READ_BYTE 0x13
+#define ZIIRAVE_CMD_RESET_PROCESSOR 0x0b
+#define ZIIRAVE_CMD_JUMP_TO_BOOTLOADER 0x0c
+#define ZIIRAVE_CMD_DOWNLOAD_PACKET 0x0e
+
+#define ZIIRAVE_CMD_JUMP_TO_BOOTLOADER_MAGIC 1
+#define ZIIRAVE_CMD_RESET_PROCESSOR_MAGIC 1
+
+struct ziirave_wdt_rev {
+ unsigned char major;
+ unsigned char minor;
+};
+
+struct ziirave_wdt_data {
+ struct mutex sysfs_mutex;
+ struct watchdog_device wdd;
+ struct ziirave_wdt_rev bootloader_rev;
+ struct ziirave_wdt_rev firmware_rev;
+ int reset_reason;
+};
+
+static int wdt_timeout;
+module_param(wdt_timeout, int, 0);
+MODULE_PARM_DESC(wdt_timeout, "Watchdog timeout in seconds");
+
+static int reset_duration;
+module_param(reset_duration, int, 0);
+MODULE_PARM_DESC(reset_duration,
+ "Watchdog reset pulse duration in milliseconds");
+
+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) ")");
+
+static int ziirave_wdt_revision(struct i2c_client *client,
+ struct ziirave_wdt_rev *rev, u8 command)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, command);
+ if (ret < 0)
+ return ret;
+
+ rev->major = ret;
+
+ ret = i2c_smbus_read_byte_data(client, command + 1);
+ if (ret < 0)
+ return ret;
+
+ rev->minor = ret;
+
+ return 0;
+}
+
+static int ziirave_wdt_set_state(struct watchdog_device *wdd, int state)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+
+ return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_STATE, state);
+}
+
+static int ziirave_wdt_start(struct watchdog_device *wdd)
+{
+ return ziirave_wdt_set_state(wdd, ZIIRAVE_STATE_ON);
+}
+
+static int ziirave_wdt_stop(struct watchdog_device *wdd)
+{
+ return ziirave_wdt_set_state(wdd, ZIIRAVE_STATE_OFF);
+}
+
+static int ziirave_wdt_ping(struct watchdog_device *wdd)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+
+ return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_PING,
+ ZIIRAVE_PING_VALUE);
+}
+
+static int ziirave_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_TIMEOUT, timeout);
+ if (!ret)
+ wdd->timeout = timeout;
+
+ return ret;
+}
+
+static unsigned int ziirave_wdt_get_timeleft(struct watchdog_device *wdd)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_TIME_LEFT);
+ if (ret < 0)
+ ret = 0;
+
+ return ret;
+}
+
+static int ziirave_firm_read_ack(struct watchdog_device *wdd)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ int ret;
+
+ ret = i2c_smbus_read_byte(client);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read status byte\n");
+ return ret;
+ }
+
+ return ret == ZIIRAVE_FIRM_DOWNLOAD_ACK ? 0 : -EIO;
+}
+
+static int ziirave_firm_set_read_addr(struct watchdog_device *wdd, u32 addr)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ const u16 addr16 = (u16)addr / 2;
+ u8 address[2];
+
+ put_unaligned_le16(addr16, address);
+
+ return i2c_smbus_write_block_data(client,
+ ZIIRAVE_CMD_DOWNLOAD_SET_READ_ADDR,
+ sizeof(address), address);
+}
+
+static bool ziirave_firm_addr_readonly(u32 addr)
+{
+ return addr < ZIIRAVE_FIRM_FLASH_MEMORY_START ||
+ addr > ZIIRAVE_FIRM_FLASH_MEMORY_END;
+}
+
+/*
+ * ziirave_firm_write_pkt() - Build and write a firmware packet
+ *
+ * A packet to send to the firmware is composed by following bytes:
+ * Length | Addr0 | Addr1 | Data0 .. Data15 | Checksum |
+ * Where,
+ * Length: A data byte containing the length of the data.
+ * Addr0: Low byte of the address.
+ * Addr1: High byte of the address.
+ * Data0 .. Data15: Array of 16 bytes of data.
+ * Checksum: Checksum byte to verify data integrity.
+ */
+static int __ziirave_firm_write_pkt(struct watchdog_device *wdd,
+ u32 addr, const u8 *data, u8 len)
+{
+ const u16 addr16 = (u16)addr / 2;
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ u8 i, checksum = 0, packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE];
+ int ret;
+
+ /* Check max data size */
+ if (len > ZIIRAVE_FIRM_PKT_DATA_SIZE) {
+ dev_err(&client->dev, "Firmware packet too long (%d)\n",
+ len);
+ return -EMSGSIZE;
+ }
+
+ /*
+ * Ignore packets that are targeting program memory outisde of
+ * app partition, since they will be ignored by the
+ * bootloader. At the same time, we need to make sure we'll
+ * allow zero length packet that will be sent as the last step
+ * of firmware update
+ */
+ if (len && ziirave_firm_addr_readonly(addr))
+ return 0;
+
+ /* Packet length */
+ packet[0] = len;
+ /* Packet address */
+ put_unaligned_le16(addr16, packet + 1);
+
+ memcpy(packet + 3, data, len);
+ memset(packet + 3 + len, 0, ZIIRAVE_FIRM_PKT_DATA_SIZE - len);
+
+ /* Packet checksum */
+ for (i = 0; i < len + 3; i++)
+ checksum += packet[i];
+ packet[ZIIRAVE_FIRM_PKT_TOTAL_SIZE - 1] = checksum;
+
+ ret = i2c_smbus_write_block_data(client, ZIIRAVE_CMD_DOWNLOAD_PACKET,
+ sizeof(packet), packet);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to send DOWNLOAD_PACKET: %d\n", ret);
+ return ret;
+ }
+
+ ret = ziirave_firm_read_ack(wdd);
+ if (ret)
+ dev_err(&client->dev,
+ "Failed to write firmware packet at address 0x%04x: %d\n",
+ addr, ret);
+
+ return ret;
+}
+
+static int ziirave_firm_write_pkt(struct watchdog_device *wdd,
+ u32 addr, const u8 *data, u8 len)
+{
+ const u8 max_write_len = ZIIRAVE_FIRM_PAGE_SIZE -
+ (addr - ALIGN_DOWN(addr, ZIIRAVE_FIRM_PAGE_SIZE));
+ int ret;
+
+ if (len > max_write_len) {
+ /*
+ * If data crossed page boundary we need to split this
+ * write in two
+ */
+ ret = __ziirave_firm_write_pkt(wdd, addr, data, max_write_len);
+ if (ret)
+ return ret;
+
+ addr += max_write_len;
+ data += max_write_len;
+ len -= max_write_len;
+ }
+
+ return __ziirave_firm_write_pkt(wdd, addr, data, len);
+}
+
+static int ziirave_firm_verify(struct watchdog_device *wdd,
+ const struct firmware *fw)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ const struct ihex_binrec *rec;
+ int i, ret;
+ u8 data[ZIIRAVE_FIRM_PKT_DATA_SIZE];
+
+ for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
+ const u16 len = be16_to_cpu(rec->len);
+ const u32 addr = be32_to_cpu(rec->addr);
+
+ if (ziirave_firm_addr_readonly(addr))
+ continue;
+
+ ret = ziirave_firm_set_read_addr(wdd, addr);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to send SET_READ_ADDR command: %d\n",
+ ret);
+ return ret;
+ }
+
+ for (i = 0; i < len; i++) {
+ ret = i2c_smbus_read_byte_data(client,
+ ZIIRAVE_CMD_DOWNLOAD_READ_BYTE);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to READ DATA: %d\n", ret);
+ return ret;
+ }
+ data[i] = ret;
+ }
+
+ if (memcmp(data, rec->data, len)) {
+ dev_err(&client->dev,
+ "Firmware mismatch at address 0x%04x\n", addr);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int ziirave_firm_upload(struct watchdog_device *wdd,
+ const struct firmware *fw)
+{
+ struct i2c_client *client = to_i2c_client(wdd->parent);
+ const struct ihex_binrec *rec;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client,
+ ZIIRAVE_CMD_JUMP_TO_BOOTLOADER,
+ ZIIRAVE_CMD_JUMP_TO_BOOTLOADER_MAGIC);
+ if (ret) {
+ dev_err(&client->dev, "Failed to jump to bootloader\n");
+ return ret;
+ }
+
+ msleep(500);
+
+ ret = i2c_smbus_write_byte(client, ZIIRAVE_CMD_DOWNLOAD_START);
+ if (ret) {
+ dev_err(&client->dev, "Failed to start download\n");
+ return ret;
+ }
+
+ ret = ziirave_firm_read_ack(wdd);
+ if (ret) {
+ dev_err(&client->dev, "No ACK for start download\n");
+ return ret;
+ }
+
+ msleep(500);
+
+ for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
+ ret = ziirave_firm_write_pkt(wdd, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len));
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Finish firmware download process by sending a zero length
+ * payload
+ */
+ ret = ziirave_firm_write_pkt(wdd, 0, NULL, 0);
+ if (ret) {
+ dev_err(&client->dev, "Failed to send EMPTY packet: %d\n", ret);
+ return ret;
+ }
+
+ /* This sleep seems to be required */
+ msleep(20);
+
+ /* Start firmware verification */
+ ret = ziirave_firm_verify(wdd, fw);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to verify firmware: %d\n", ret);
+ return ret;
+ }
+
+ /* End download operation */
+ ret = i2c_smbus_write_byte(client, ZIIRAVE_CMD_DOWNLOAD_END);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to end firmware download: %d\n", ret);
+ return ret;
+ }
+
+ /* Reset the processor */
+ ret = i2c_smbus_write_byte_data(client,
+ ZIIRAVE_CMD_RESET_PROCESSOR,
+ ZIIRAVE_CMD_RESET_PROCESSOR_MAGIC);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to reset the watchdog: %d\n", ret);
+ return ret;
+ }
+
+ msleep(500);
+
+ return 0;
+}
+
+static const struct watchdog_info ziirave_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
+ .identity = "RAVE Switch Watchdog",
+};
+
+static const struct watchdog_ops ziirave_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ziirave_wdt_start,
+ .stop = ziirave_wdt_stop,
+ .ping = ziirave_wdt_ping,
+ .set_timeout = ziirave_wdt_set_timeout,
+ .get_timeleft = ziirave_wdt_get_timeleft,
+};
+
+static ssize_t ziirave_wdt_sysfs_show_firm(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client);
+ int ret;
+
+ ret = mutex_lock_interruptible(&w_priv->sysfs_mutex);
+ if (ret)
+ return ret;
+
+ ret = sysfs_emit(buf, "02.%02u.%02u\n",
+ w_priv->firmware_rev.major,
+ w_priv->firmware_rev.minor);
+
+ mutex_unlock(&w_priv->sysfs_mutex);
+
+ return ret;
+}
+
+static DEVICE_ATTR(firmware_version, S_IRUGO, ziirave_wdt_sysfs_show_firm,
+ NULL);
+
+static ssize_t ziirave_wdt_sysfs_show_boot(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client);
+ int ret;
+
+ ret = mutex_lock_interruptible(&w_priv->sysfs_mutex);
+ if (ret)
+ return ret;
+
+ ret = sysfs_emit(buf, "01.%02u.%02u\n",
+ w_priv->bootloader_rev.major,
+ w_priv->bootloader_rev.minor);
+
+ mutex_unlock(&w_priv->sysfs_mutex);
+
+ return ret;
+}
+
+static DEVICE_ATTR(bootloader_version, S_IRUGO, ziirave_wdt_sysfs_show_boot,
+ NULL);
+
+static ssize_t ziirave_wdt_sysfs_show_reason(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client);
+ int ret;
+
+ ret = mutex_lock_interruptible(&w_priv->sysfs_mutex);
+ if (ret)
+ return ret;
+
+ ret = sysfs_emit(buf, "%s\n", ziirave_reasons[w_priv->reset_reason]);
+
+ mutex_unlock(&w_priv->sysfs_mutex);
+
+ return ret;
+}
+
+static DEVICE_ATTR(reset_reason, S_IRUGO, ziirave_wdt_sysfs_show_reason,
+ NULL);
+
+static ssize_t ziirave_wdt_sysfs_store_firm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client);
+ const struct firmware *fw;
+ int err;
+
+ err = request_ihex_firmware(&fw, ZIIRAVE_FW_NAME, dev);
+ if (err) {
+ dev_err(&client->dev, "Failed to request ihex firmware\n");
+ return err;
+ }
+
+ err = mutex_lock_interruptible(&w_priv->sysfs_mutex);
+ if (err)
+ goto release_firmware;
+
+ err = ziirave_firm_upload(&w_priv->wdd, fw);
+ if (err) {
+ dev_err(&client->dev, "The firmware update failed: %d\n", err);
+ goto unlock_mutex;
+ }
+
+ /* Update firmware version */
+ err = ziirave_wdt_revision(client, &w_priv->firmware_rev,
+ ZIIRAVE_WDT_FIRM_VER_MAJOR);
+ if (err) {
+ dev_err(&client->dev, "Failed to read firmware version: %d\n",
+ err);
+ goto unlock_mutex;
+ }
+
+ dev_info(&client->dev,
+ "Firmware updated to version 02.%02u.%02u\n",
+ w_priv->firmware_rev.major, w_priv->firmware_rev.minor);
+
+ /* Restore the watchdog timeout */
+ err = ziirave_wdt_set_timeout(&w_priv->wdd, w_priv->wdd.timeout);
+ if (err)
+ dev_err(&client->dev, "Failed to set timeout: %d\n", err);
+
+unlock_mutex:
+ mutex_unlock(&w_priv->sysfs_mutex);
+
+release_firmware:
+ release_firmware(fw);
+
+ return err ? err : count;
+}
+
+static DEVICE_ATTR(update_firmware, S_IWUSR, NULL,
+ ziirave_wdt_sysfs_store_firm);
+
+static struct attribute *ziirave_wdt_attrs[] = {
+ &dev_attr_firmware_version.attr,
+ &dev_attr_bootloader_version.attr,
+ &dev_attr_reset_reason.attr,
+ &dev_attr_update_firmware.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ziirave_wdt);
+
+static int ziirave_wdt_init_duration(struct i2c_client *client)
+{
+ int ret;
+
+ if (!reset_duration) {
+ /* See if the reset pulse duration is provided in an of_node */
+ if (!client->dev.of_node)
+ ret = -ENODEV;
+ else
+ ret = of_property_read_u32(client->dev.of_node,
+ "reset-duration-ms",
+ &reset_duration);
+ if (ret) {
+ dev_info(&client->dev,
+ "No reset pulse duration specified, using default\n");
+ return 0;
+ }
+ }
+
+ if (reset_duration < 1 || reset_duration > 255)
+ return -EINVAL;
+
+ dev_info(&client->dev, "Setting reset duration to %dms",
+ reset_duration);
+
+ return i2c_smbus_write_byte_data(client, ZIIRAVE_WDT_RESET_DURATION,
+ reset_duration);
+}
+
+static int ziirave_wdt_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct ziirave_wdt_data *w_priv;
+ int val;
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE |
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
+ return -ENODEV;
+
+ w_priv = devm_kzalloc(&client->dev, sizeof(*w_priv), GFP_KERNEL);
+ if (!w_priv)
+ return -ENOMEM;
+
+ mutex_init(&w_priv->sysfs_mutex);
+
+ w_priv->wdd.info = &ziirave_wdt_info;
+ w_priv->wdd.ops = &ziirave_wdt_ops;
+ w_priv->wdd.min_timeout = ZIIRAVE_TIMEOUT_MIN;
+ w_priv->wdd.max_timeout = ZIIRAVE_TIMEOUT_MAX;
+ w_priv->wdd.parent = &client->dev;
+ w_priv->wdd.groups = ziirave_wdt_groups;
+
+ watchdog_init_timeout(&w_priv->wdd, wdt_timeout, &client->dev);
+
+ /*
+ * The default value set in the watchdog should be perfectly valid, so
+ * pass that in if we haven't provided one via the module parameter or
+ * of property.
+ */
+ if (w_priv->wdd.timeout == 0) {
+ val = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_TIMEOUT);
+ if (val < 0) {
+ dev_err(&client->dev, "Failed to read timeout\n");
+ return val;
+ }
+
+ if (val > ZIIRAVE_TIMEOUT_MAX ||
+ val < ZIIRAVE_TIMEOUT_MIN)
+ val = ZIIRAVE_TIMEOUT_DEFAULT;
+
+ w_priv->wdd.timeout = val;
+ }
+
+ ret = ziirave_wdt_set_timeout(&w_priv->wdd, w_priv->wdd.timeout);
+ if (ret) {
+ dev_err(&client->dev, "Failed to set timeout\n");
+ return ret;
+ }
+
+ dev_info(&client->dev, "Timeout set to %ds\n", w_priv->wdd.timeout);
+
+ watchdog_set_nowayout(&w_priv->wdd, nowayout);
+
+ i2c_set_clientdata(client, w_priv);
+
+ /* If in unconfigured state, set to stopped */
+ val = i2c_smbus_read_byte_data(client, ZIIRAVE_WDT_STATE);
+ if (val < 0) {
+ dev_err(&client->dev, "Failed to read state\n");
+ return val;
+ }
+
+ if (val == ZIIRAVE_STATE_INITIAL)
+ ziirave_wdt_stop(&w_priv->wdd);
+
+ ret = ziirave_wdt_init_duration(client);
+ if (ret) {
+ dev_err(&client->dev, "Failed to init duration\n");
+ return ret;
+ }
+
+ ret = ziirave_wdt_revision(client, &w_priv->firmware_rev,
+ ZIIRAVE_WDT_FIRM_VER_MAJOR);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read firmware version\n");
+ return ret;
+ }
+
+ dev_info(&client->dev,
+ "Firmware version: 02.%02u.%02u\n",
+ w_priv->firmware_rev.major, w_priv->firmware_rev.minor);
+
+ ret = ziirave_wdt_revision(client, &w_priv->bootloader_rev,
+ ZIIRAVE_WDT_BOOT_VER_MAJOR);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read bootloader version\n");
+ return ret;
+ }
+
+ dev_info(&client->dev,
+ "Bootloader version: 01.%02u.%02u\n",
+ w_priv->bootloader_rev.major, w_priv->bootloader_rev.minor);
+
+ w_priv->reset_reason = i2c_smbus_read_byte_data(client,
+ ZIIRAVE_WDT_RESET_REASON);
+ if (w_priv->reset_reason < 0) {
+ dev_err(&client->dev, "Failed to read reset reason\n");
+ return w_priv->reset_reason;
+ }
+
+ if (w_priv->reset_reason >= ARRAY_SIZE(ziirave_reasons) ||
+ !ziirave_reasons[w_priv->reset_reason]) {
+ dev_err(&client->dev, "Invalid reset reason\n");
+ return -ENODEV;
+ }
+
+ ret = watchdog_register_device(&w_priv->wdd);
+
+ return ret;
+}
+
+static void ziirave_wdt_remove(struct i2c_client *client)
+{
+ struct ziirave_wdt_data *w_priv = i2c_get_clientdata(client);
+
+ watchdog_unregister_device(&w_priv->wdd);
+}
+
+static const struct i2c_device_id ziirave_wdt_id[] = {
+ { "rave-wdt", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ziirave_wdt_id);
+
+static const struct of_device_id zrv_wdt_of_match[] = {
+ { .compatible = "zii,rave-wdt", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, zrv_wdt_of_match);
+
+static struct i2c_driver ziirave_wdt_driver = {
+ .driver = {
+ .name = "ziirave_wdt",
+ .of_match_table = zrv_wdt_of_match,
+ },
+ .probe = ziirave_wdt_probe,
+ .remove = ziirave_wdt_remove,
+ .id_table = ziirave_wdt_id,
+};
+
+module_i2c_driver(ziirave_wdt_driver);
+
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk");
+MODULE_DESCRIPTION("Zodiac Aerospace RAVE Switch Watchdog Processor Driver");
+MODULE_LICENSE("GPL");