diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/watchdog | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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, ®); + 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 = <q_wdt_info; + wdt->ops = <q_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 = <q_wdt_xrx100 }, + { .compatible = "lantiq,falcon-wdt", .data = <q_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, ®val); + 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, ®val); + 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, ®val); + 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, ®val); + } 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, ®val); + } + } + + /* 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, ®); + /* 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 &= 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, ®); + 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"); |