diff options
Diffstat (limited to 'drivers/watchdog')
133 files changed, 28054 insertions, 9962 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 254d115cafa..76dd54122f7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -28,6 +28,14 @@ menuconfig WATCHDOG if WATCHDOG +config WATCHDOG_CORE + bool "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 @@ -47,6 +55,7 @@ comment "Watchdog Device Drivers" 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 @@ -55,10 +64,74 @@ config SOFT_WATCHDOG To compile this driver as a module, choose M here: the module will be called softdog. +config DA9052_WATCHDOG + tristate "Dialog DA9052 Watchdog" + depends on PMIC_DA9052 + 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 + 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 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 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" + 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. + # ALPHA Architecture # ARM Architecture +config ARM_SP805_WATCHDOG + tristate "ARM SP805 Watchdog" + depends on (ARM || ARM64) && ARM_AMBA + select WATCHDOG_CORE + help + ARM Primecell SP805 Watchdog timer. This will reboot your system when + the timeout is reached. + config AT91RM9200_WATCHDOG tristate "AT91RM9200 watchdog" depends on ARCH_AT91RM9200 @@ -66,6 +139,14 @@ config AT91RM9200_WATCHDOG 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 && !ARCH_AT91RM9200 + select WATCHDOG_CORE + help + Watchdog timer embedded into AT91SAM9X and AT91CAP9 chips. This will + reboot your system when the timeout is reached. + config 21285_WATCHDOG tristate "DC21285 watchdog" depends on FOOTBRIDGE @@ -90,17 +171,6 @@ config 977_WATCHDOG Not sure? It's safe to say N. -config IXP2000_WATCHDOG - tristate "IXP2000 Watchdog" - depends on ARCH_IXP2000 - help - Say Y here if to include support for the watchdog timer - in the Intel IXP2000(2400, 2800, 2850) network processors. - This driver can be built as a module by choosing M. The module - will be called ixp2000_wdt. - - Say N if you are unsure. - config IXP4XX_WATCHDOG tristate "IXP4xx Watchdog" depends on ARCH_IXP4XX @@ -124,13 +194,21 @@ config KS8695_WATCHDOG Watchdog timer embedded into KS8695 processor. This will reboot your system when the timeout is reached. +config HAVE_S3C2410_WATCHDOG + bool + help + This will include watchdog timer support for Samsung SoCs. If + you want to include watchdog support for any machine, kindly + select this in the respective mach-XXXX/Kconfig file. + config S3C2410_WATCHDOG tristate "S3C2410 Watchdog" - depends on ARCH_S3C2410 + depends on HAVE_S3C2410_WATCHDOG + select WATCHDOG_CORE + select MFD_SYSCON if ARCH_EXYNOS5 help - Watchdog timer block in the Samsung S3C2410 chips. This will - reboot the system when the timer expires with the watchdog - enabled. + Watchdog timer block in the Samsung 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) @@ -152,18 +230,19 @@ config SA1100_WATCHDOG To compile this driver as a module, choose M here: the module will be called sa1100_wdt. -config MPCORE_WATCHDOG - tristate "MPcore watchdog" - depends on ARM_MPCORE_PLATFORM && LOCAL_TIMERS +config DW_WATCHDOG + tristate "Synopsys DesignWare watchdog" + depends on HAS_IOMEM help - Watchdog timer embedded into the MPcore system. - + 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 mpcore_wdt. + module will be called dw_wdt. config EP93XX_WATCHDOG tristate "EP93xx Watchdog" depends on ARCH_EP93XX + select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer embedded in the Cirrus Logic EP93xx family of devices. @@ -173,17 +252,19 @@ config EP93XX_WATCHDOG config OMAP_WATCHDOG tristate "OMAP Watchdog" - depends on ARCH_OMAP16XX || ARCH_OMAP24XX + depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS + select WATCHDOG_CORE help - Support for TI OMAP1610/OMAP1710/OMAP2420 watchdog. Say 'Y' here to - enable the OMAP1610/OMAP1710 watchdog timer. + 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 "PNX4008 Watchdog" - depends on ARCH_PNX4008 + tristate "LPC32XX Watchdog" + depends on ARCH_LPC32XX + select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer - in the PNX4008 processor. + in the LPC32XX processor. This driver can be built as a module by choosing M. The module will be called pnx4008_wdt. @@ -191,7 +272,7 @@ config PNX4008_WATCHDOG config IOP_WATCHDOG tristate "IOP Watchdog" - depends on PLAT_IOP + depends on ARCH_IOP13XX select WATCHDOG_NOWAYOUT if (ARCH_IOP32X || ARCH_IOP33X) help Say Y here if to include support for the watchdog timer @@ -207,17 +288,160 @@ config IOP_WATCHDOG config DAVINCI_WATCHDOG tristate "DaVinci watchdog" - depends on ARCH_DAVINCI + depends on ARCH_DAVINCI || ARCH_KEYSTONE + select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer - in the DaVinci DM644x/DM646x processors. + 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. -# ARM26 Architecture +config ORION_WATCHDOG + tristate "Orion watchdog" + depends on ARCH_ORION5X || ARCH_KIRKWOOD || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU + 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 SUNXI_WATCHDOG + tristate "Allwinner SoCs watchdog support" + depends on ARCH_SUNXI + 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 COH901327_WATCHDOG + bool "ST-Ericsson COH 901 327 watchdog" + depends on ARCH_U300 + default y if MACH_U300 + select WATCHDOG_CORE + help + Say Y here to include Watchdog timer support for the + watchdog embedded into the ST-Ericsson U300 series platforms. + 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 + 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 NUC900_WATCHDOG + tristate "Nuvoton NUC900 watchdog" + depends on ARCH_W90X900 + help + Say Y here if to include support for the watchdog timer + for the Nuvoton NUC900 series SoCs. + To compile this driver as a module, choose M here: the + module will be called nuc900_wdt. + +config TS72XX_WATCHDOG + tristate "TS-72XX SBC Watchdog" + depends on MACH_TS72XX + 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 ARM && HAS_IOMEM + select WATCHDOG_CORE + help + Support for memory mapped max63{69,70,71,72,73,74} watchdog timer. + +config IMX2_WDT + tristate "IMX2+ Watchdog" + depends on ARCH_MXC + 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 UX500_WATCHDOG + tristate "ST-Ericsson Ux500 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 Ux500 series platforms. + + To compile this driver as a module, choose M here: the + module will be called ux500_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 + 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 SIRFSOC_WATCHDOG + tristate "SiRFSOC watchdog" + depends on ARCH_SIRF + select WATCHDOG_CORE + default y + help + Support for CSR SiRFprimaII and SiRFatlasVI watchdog. When + the watchdog triggers the system will be reset. + +config TEGRA_WATCHDOG + tristate "Tegra watchdog" + depends on ARCH_TEGRA || COMPILE_TEST + 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. # AVR32 Architecture @@ -245,8 +469,6 @@ config BFIN_WDT # FRV Architecture -# H8300 Architecture - # X86 (i386 + ia64 + x86_64) Architecture config ACQUIRE_WDT @@ -285,19 +507,55 @@ config ALIM1535_WDT config ALIM7101_WDT tristate "ALi M7101 PMU Computer Watchdog" - depends on X86 && PCI + depends on PCI help This is the driver for the hardware watchdog on the ALi M7101 PMU - as used in the x86 Cobalt servers. + 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 F71808E_WDT + tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" + depends on X86 + help + This is the driver for the hardware watchdog on the Fintek + F71808E, F71862FG, F71869, F71882FG and F71889FG 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 + ---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 X86 + depends on MELAN help This is the driver for the hardware watchdog built in to the AMD "Elan" SC520 microcomputer commonly used in embedded systems. @@ -308,6 +566,28 @@ config SC520_WDT 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 watchdog timer either from BIOS (F2) or from booted Linux. + When "Watchdog Timer Value" enabled one can set 31-255 s operational range. + + Entering BIOS setup temporary disables watchdog operation regardless to current state, + so system will not be restarted while user in BIOS setup. + + Once watchdog was 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 @@ -355,7 +635,7 @@ config WAFER_WDT config I6300ESB_WDT tristate "Intel 6300ESB Timer/Watchdog" - depends on X86 && PCI + depends on PCI ---help--- Hardware driver for the watchdog timer built into the Intel 6300ESB controller hub. @@ -363,13 +643,50 @@ config I6300ESB_WDT 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_SCU_WATCHDOG + bool "Intel SCU Watchdog for Mobile Platforms" + depends on X86_INTEL_MID + ---help--- + Hardware driver for the watchdog time built into the Intel SCU + for Intel Mobile Platforms. + + To compile this driver as a module, choose M here. + +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 + select LPC_ICH ---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 ICH8) and in the Intel 6300ESB + 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 @@ -399,20 +716,58 @@ config IT8712F_WDT 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 HP_WATCHDOG - tristate "HP Proliant iLO 2 Hardware Watchdog Timer" +config IT87_WDT + tristate "IT87 Watchdog Timer" depends on X86 + ---help--- + This is the driver for the hardware watchdog on the ITE IT8702, + IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 and IT8728 + Super I/O chips. + + If the driver does not work, then make sure that the game port in + the BIOS is enabled. + + 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" + depends on X86 && PCI help A software monitoring watchdog and NMI sourcing driver. This driver - will detect lockups and provide stack trace. Also, when an NMI - occurs this driver will make the necessary BIOS calls to log - the cause of the NMI. This is a driver that will only load on a - 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. + 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 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 HPWDT_NMI_DECODING + bool "NMI decoding support for the HP ProLiant iLO2+ Hardware Watchdog Timer" + depends on HP_WATCHDOG + default y + help + When an NMI occurs this feature will make the necessary BIOS calls to + log the cause of the NMI. config SC1200_WDT tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" @@ -450,7 +805,35 @@ config PC87413_WDT 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 + 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 @@ -467,20 +850,20 @@ config 60XX_WDT config SBC8360_WDT tristate "SBC8360 Watchdog Timer" - depends on X86 + 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.ko. + module will be called sbc8360. Most people will say N. config SBC7240_WDT tristate "SBC Nano 7240 Watchdog Timer" - depends on X86_32 + 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 @@ -499,6 +882,18 @@ config CPU5_WDT 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 @@ -519,33 +914,42 @@ config SMSC37B787_WDT Most people will say N. -config W83627HF_WDT - tristate "W83627HF Watchdog Timer" - depends on X86 +config VIA_WDT + tristate "VIA Watchdog Timer" + depends on X86 && PCI + select WATCHDOG_CORE ---help--- - This is the driver for the hardware watchdog on the W83627HF chipset - as used in Advantech PC-9578 and Tyan S2721-533 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. + 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 w83627hf_wdt. + To compile this driver as a module, choose M here; the module + will be called via_wdt. - Most people will say N. + Most people will say N. -config W83697HF_WDT - tristate "W83697HF/W83697HG Watchdog Timer" +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 W83697HF/HG - chipset as used in Dedibox/VIA motherboards (and likely others). + 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 + 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 w83697hf_wdt. + module will be called w83627hf_wdt. Most people will say N. @@ -615,10 +1019,41 @@ config SBC_EPX_C3_WATCHDOG # M68K Architecture -# M68KNOMMU 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 + 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 + 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 @@ -628,6 +1063,13 @@ config INDYDOG timer expired and no process has written to /dev/watchdog during that time. +config JZ4740_WDT + tristate "Ingenic jz4740 SoC hardware watchdog" + depends on MACH_JZ4740 + select WATCHDOG_CORE + 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 @@ -635,15 +1077,14 @@ config WDT_MTX1 Hardware driver for the MTX-1 boards. This is a watchdog timer that will reboot the machine after a 100 seconds timer expired. -config WDT_RM9K_GPI - tristate "RM9000/GPI hardware watchdog" - depends on CPU_RM9000 +config PNX833X_WDT + tristate "PNX833x Hardware Watchdog" + depends on SOC_PNX8335 help - Watchdog implementation using the GPI hardware found on - PMC-Sierra RM9xxx CPUs. - - To compile this driver as a module, choose M here: the - module will be called rm9k_wdt. + Hardware driver for the PNX833x's watchdog. This is a + watchdog timer that will reboot the machine after a programmable + timer has expired and no process has written to /dev/watchdog during + that time. config SIBYTE_WDOG tristate "Sibyte SoC hardware watchdog" @@ -667,36 +1108,165 @@ config AR7_WDT config TXX9_WDT tristate "Toshiba TXx9 Watchdog Timer" depends on CPU_TX39XX || CPU_TX49XX + 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 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 BCM63XX_WDT + tristate "Broadcom BCM63xx hardware watchdog" + depends on BCM63XX + help + Watchdog driver for the built in watchdog hardware in Broadcom + BCM63xx SoC. + + To compile this driver as a loadable module, choose M here. + The module will be called bcm63xx_wdt. + +config BCM2835_WDT + tristate "Broadcom BCM2835 hardware watchdog" + depends on ARCH_BCM2835 + 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 + 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 LANTIQ_WDT + tristate "Lantiq SoC watchdog" + depends on LANTIQ + help + Hardware driver for the Lantiq 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. + # 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 - tristate "MPC5200 Watchdog Timer" + bool "MPC52xx Watchdog Timer" depends on PPC_MPC52xx + help + Use General Purpose Timer (GPT) 0 on the MPC5200 as Watchdog. -config 8xx_WDT - tristate "MPC8xx Watchdog Timer" - depends on 8xx +config 8xxx_WDT + tristate "MPC8xxx Platform Watchdog Timer" + depends on PPC_8xx || PPC_83xx || PPC_86xx + 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 -config 83xx_WDT - tristate "MPC83xx Watchdog Timer" - depends on PPC_83xx + For BookE processors (MPC85xx) use the BOOKE_WDT driver instead. config MV64X60_WDT tristate "MV64X60 (Marvell Discovery) Watchdog Timer" depends on MV64X60 +config PIKA_WDT + tristate "PIKA FPGA Watchdog" + depends on WARP + default y + help + This enables the watchdog in the PIKA FPGA. Currently used on + the Warp platform. + config BOOKE_WDT - bool "PowerPC Book-E Watchdog Timer" + 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.txt for more information. +config BOOKE_WDT_DEFAULT_TIMEOUT + int "PowerPC Book-E Watchdog Timer Default Timeout" + depends on BOOKE_WDT + default 38 if PPC_FSL_BOOK3E + range 0 63 if PPC_FSL_BOOK3E + default 3 if !PPC_FSL_BOOK3E + range 0 3 if !PPC_FSL_BOOK3E + 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 + 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 WATCHDOG_RTAS @@ -710,14 +1280,17 @@ config WATCHDOG_RTAS # S390 Architecture -config ZVM_WATCHDOG - tristate "z/VM Watchdog Timer" +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 vmwatchdog. @@ -727,6 +1300,7 @@ config ZVM_WATCHDOG config SH_WDT tristate "SuperH Watchdog" depends on SUPERH && (CPU_SH3 || CPU_SH4) + 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 @@ -741,14 +1315,6 @@ config SH_WDT To compile this driver as a module, choose M here: the module will be called shwdt. -config SH_WDT_MMAP - bool "Allow mmap of SH WDT" - default n - depends on SH_WDT - help - If you say Y here, user applications will be able to mmap the - WDT/CPG registers. - # SPARC Architecture # SPARC64 Architecture @@ -775,10 +1341,22 @@ config WATCHDOG_RIO machines. The watchdog timeout period is normally one minute but can be changed with a boot-time parameter. -# V850 Architecture - # XTENSA Architecture +# Xen Architecture + +config XEN_WDT + tristate "Xen Watchdog support" + depends on XEN + 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 + # # ISA-based Watchdog Cards # @@ -828,21 +1406,6 @@ config WDT To compile this driver as a module, choose M here: the module will be called wdt. -config WDT_501 - bool "WDT501 features" - depends on WDT - help - Saying Y here and creating a character special file /dev/temperature - with major number 10 and minor number 131 ("man mknod") will give - you a thermometer inside your computer: reading from - /dev/temperature yields one byte, the temperature in degrees - Fahrenheit. This works only if you have a WDT501P watchdog board - installed. - - If you want to enable the Fan Tachometer on the WDT501P, then you - can do this via the tachometer parameter. Only do this if you have a - fan tachometer actually set up. - # # PCI-based Watchdog Cards # @@ -871,24 +1434,16 @@ config WDTPCI ---help--- If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. - To compile this driver as a module, choose M here: the - module will be called wdt_pci. - -config WDT_501_PCI - bool "PCI-WDT501 features" - depends on WDTPCI - help - Saying Y here and creating a character special file /dev/temperature - with major number 10 and minor number 131 ("man mknod") will give - you a thermometer inside your computer: reading from - /dev/temperature yields one byte, the temperature in degrees - Fahrenheit. This works only if you have a PCI-WDT501 watchdog board - installed. + 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 # diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f3fb170fe5c..468c3204c3b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -2,9 +2,13 @@ # Makefile for the WatchDog device drivers. # +# The WatchDog Timer Driver Core. +watchdog-objs += watchdog_core.o watchdog_dev.o +obj-$(CONFIG_WATCHDOG_CORE) += watchdog.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 independant "softdog" driver. +# 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 @@ -25,22 +29,36 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ALPHA Architecture # ARM Architecture +obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o +obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_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_IXP2000_WATCHDOG) += ixp2000_wdt.o obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o -obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_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_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o - -# ARM26 Architecture +obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o +obj-$(CONFIG_SUNXI_WATCHDOG) += sunxi_wdt.o +obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o +obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o +obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o +obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o +obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o +obj-$(CONFIG_UX500_WATCHDOG) += ux500_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_SIRFSOC_WATCHDOG) += sirfsoc_wdt.o +obj-$(CONFIG_BCM_KONA_WDT) += bcm_kona_wdt.o +obj-$(CONFIG_TEGRA_WATCHDOG) += tegra_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o @@ -52,64 +70,90 @@ obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o # FRV Architecture -# H8300 Architecture - # 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_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_ITCO_WDT) += iTCO_wdt.o iTCO_vendor_support.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_VIA_WDT) += via_wdt.o obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o -obj-$(CONFIG_W83697HF_WDT) += w83697hf_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_SCU_WATCHDOG) += intel_scu_watchdog.o +obj-$(CONFIG_INTEL_MID_WATCHDOG) += intel-mid_wdt.o # M32R Architecture # M68K Architecture +obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o -# M68KNOMMU Architecture +# 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_BCM63XX_WDT) += bcm63xx_wdt.o +obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o obj-$(CONFIG_INDYDOG) += indydog.o -obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o -obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o +obj-$(CONFIG_JZ4740_WDT) += jz4740_wdt.o +obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o +obj-$(CONFIG_PNX833X_WDT) += pnx833x_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_RALINK_WDT) += rt2880_wdt.o # PARISC Architecture # POWERPC Architecture -obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o -obj-$(CONFIG_MPC5200_WDT) += mpc5200_wdt.o -obj-$(CONFIG_83xx_WDT) += mpc83xx_wdt.o +obj-$(CONFIG_GEF_WDT) += gef_wdt.o +obj-$(CONFIG_8xxx_WDT) += mpc8xxx_wdt.o obj-$(CONFIG_MV64X60_WDT) += mv64x60_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_WATCHDOG_RTAS) += wdrtas.o # S390 Architecture +obj-$(CONFIG_DIAG288_WATCHDOG) += diag288_wdt.o # SUPERH (sh + sh64) Architecture obj-$(CONFIG_SH_WDT) += shwdt.o @@ -118,9 +162,19 @@ obj-$(CONFIG_SH_WDT) += shwdt.o # SPARC64 Architecture -# V850 Architecture +obj-$(CONFIG_WATCHDOG_RIO) += riowd.o +obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o # XTENSA Architecture -# Architecture Independant +# Xen +obj-$(CONFIG_XEN_WDT) += xen_wdt.o + +# Architecture Independent +obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o +obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o +obj-$(CONFIG_GPIO_WATCHDOG) += gpio_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_SOFT_WATCHDOG) += softdog.o diff --git a/drivers/watchdog/acquirewdt.c b/drivers/watchdog/acquirewdt.c index 85269c365a1..5614416f103 100644 --- a/drivers/watchdog/acquirewdt.c +++ b/drivers/watchdog/acquirewdt.c @@ -1,10 +1,10 @@ /* * Acquire Single Board Computer Watchdog Timer driver * - * Based on wdt.c. Original copyright messages: + * Based on wdt.c. Original copyright messages: * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -15,18 +15,18 @@ * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * - * (c) Copyright 1995 Alan Cox <alan@redhat.com> + * (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 + * 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 occured by + * 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 @@ -52,45 +52,51 @@ * 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 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#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 <asm/uaccess.h> /* For copy_to_user/put_user/... */ -#include <asm/io.h> /* For inb/outb/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <linux/io.h> /* For inb/outb/... */ /* Module information */ #define DRV_NAME "acquirewdt" -#define PFX DRV_NAME ": " #define WATCHDOG_NAME "Acquire WDT" -#define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */ +/* There is no way to see what the correct time-out period is */ +#define WATCHDOG_HEARTBEAT 0 /* internal variables */ -static struct platform_device *acq_platform_device; /* the watchdog platform device */ +/* the watchdog platform device */ +static struct platform_device *acq_platform_device; static unsigned long acq_is_open; static char expect_close; /* module parameters */ -static int wdt_stop = 0x43; /* You must set this - there is no sane way to probe for this board. */ +/* 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)"); -static int wdt_start = 0x443; /* You must set this - there is no sane way to probe for this board. */ +/* 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 int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -112,18 +118,18 @@ static void acq_stop(void) * /dev/watchdog handling */ -static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +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 (count) { if (!nowayout) { size_t i; - /* note: just in case someone wrote the magic character - * five months ago... */ + five months ago... */ expect_close = 0; - - /* scan to see whether or not we got the magic character */ + /* 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)) @@ -132,64 +138,55 @@ static ssize_t acq_write(struct file *file, const char __user *buf, size_t count expect_close = 42; } } - - /* Well, anyhow someone wrote to us, we should return that favour */ + /* Well, anyhow someone wrote to us, we should + return that favour */ acq_keepalive(); } return count; } -static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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 struct watchdog_info ident = - { + static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = WATCHDOG_NAME, }; - switch(cmd) - { + switch (cmd) { case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - - case WDIOC_KEEPALIVE: - acq_keepalive(); - return 0; - - case WDIOC_GETTIMEOUT: - return put_user(WATCHDOG_HEARTBEAT, p); + 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; + 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; + return -ENOTTY; } } @@ -211,7 +208,7 @@ static int acq_close(struct inode *inode, struct file *file) if (expect_close == 42) { acq_stop(); } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); acq_keepalive(); } clear_bit(0, &acq_is_open); @@ -227,7 +224,7 @@ static const struct file_operations acq_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = acq_write, - .ioctl = acq_ioctl, + .unlocked_ioctl = acq_ioctl, .open = acq_open, .release = acq_close, }; @@ -242,38 +239,32 @@ static struct miscdevice acq_miscdev = { * Init & exit routines */ -static int __devinit acq_probe(struct platform_device *dev) +static int __init acq_probe(struct platform_device *dev) { int ret; if (wdt_stop != wdt_start) { if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_stop); + 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)) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_start); + 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) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto unreg_regions; } - - printk (KERN_INFO PFX "initialized. (nowayout=%d)\n", - nowayout); + pr_info("initialized. (nowayout=%d)\n", nowayout); return 0; - unreg_regions: release_region(wdt_start, 1); unreg_stop: @@ -283,12 +274,12 @@ out: return ret; } -static int __devexit acq_remove(struct platform_device *dev) +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); + release_region(wdt_start, 1); + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); return 0; } @@ -300,8 +291,7 @@ static void acq_shutdown(struct platform_device *dev) } static struct platform_driver acquirewdt_driver = { - .probe = acq_probe, - .remove = __devexit_p(acq_remove), + .remove = acq_remove, .shutdown = acq_shutdown, .driver = { .owner = THIS_MODULE, @@ -313,22 +303,20 @@ static int __init acq_init(void) { int err; - printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n"); - - err = platform_driver_register(&acquirewdt_driver); - if (err) - return 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)) { - err = PTR_ERR(acq_platform_device); - goto unreg_platform_driver; - } + 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_driver: - platform_driver_unregister(&acquirewdt_driver); +unreg_platform_device: + platform_device_unregister(acq_platform_device); return err; } @@ -336,7 +324,7 @@ static void __exit acq_exit(void) { platform_device_unregister(acq_platform_device); platform_driver_unregister(&acquirewdt_driver); - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); + pr_info("Watchdog Module Unloaded\n"); } module_init(acq_init); @@ -345,4 +333,3 @@ module_exit(acq_exit); MODULE_AUTHOR("David Woodhouse"); MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c index 8121cc24734..7796db7fa6e 100644 --- a/drivers/watchdog/advantechwdt.c +++ b/drivers/watchdog/advantechwdt.c @@ -6,8 +6,8 @@ * Based on acquirewdt.c which is based on wdt.c. * Original copyright messages: * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -18,7 +18,7 @@ * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * - * (c) Copyright 1995 Alan Cox <alan@redhat.com> + * (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 @@ -28,6 +28,8 @@ * 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> @@ -37,17 +39,16 @@ #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> #define DRV_NAME "advantechwdt" -#define PFX DRV_NAME ": " #define WATCHDOG_NAME "Advantech WDT" #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ -static struct platform_device *advwdt_platform_device; /* the watchdog platform device */ +/* the watchdog platform device */ +static struct platform_device *advwdt_platform_device; static unsigned long advwdt_is_open; static char adv_expect_close; @@ -72,35 +73,35 @@ 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) "."); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1<= timeout <=63, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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) +static void advwdt_ping(void) { /* Write a watchdog value */ outb_p(timeout, wdt_start); } -static void -advwdt_disable(void) +static void advwdt_disable(void) { inb_p(wdt_stop); } -static int -advwdt_set_heartbeat(int t) +static int advwdt_set_heartbeat(int t) { - if ((t < 1) || (t > 63)) + if (t < 1 || t > 63) return -EINVAL; - timeout = t; return 0; } @@ -109,8 +110,8 @@ advwdt_set_heartbeat(int t) * /dev/watchdog handling */ -static ssize_t -advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +static ssize_t advwdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { if (count) { if (!nowayout) { @@ -120,7 +121,7 @@ advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *pp for (i = 0; i != count; i++) { char c; - if (get_user(c, buf+i)) + if (get_user(c, buf + i)) return -EFAULT; if (c == 'V') adv_expect_close = 42; @@ -131,72 +132,65 @@ advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *pp return count; } -static int -advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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 struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + 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; + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - - 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(); - /* Fall */ - - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); + 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; - } + int options, retval = -EINVAL; - return retval; + 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(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); default: - return -ENOTTY; + return -ENOTTY; } return 0; } -static int -advwdt_open(struct inode *inode, struct file *file) +static int advwdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &advwdt_is_open)) return -EBUSY; @@ -208,13 +202,12 @@ advwdt_open(struct inode *inode, struct file *file) return nonseekable_open(inode, file); } -static int -advwdt_close(struct inode *inode, struct file *file) +static int advwdt_close(struct inode *inode, struct file *file) { if (adv_expect_close == 42) { advwdt_disable(); } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); advwdt_ping(); } clear_bit(0, &advwdt_is_open); @@ -230,7 +223,7 @@ static const struct file_operations advwdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = advwdt_write, - .ioctl = advwdt_ioctl, + .unlocked_ioctl = advwdt_ioctl, .open = advwdt_open, .release = advwdt_close, }; @@ -245,44 +238,40 @@ static struct miscdevice advwdt_miscdev = { * Init & exit routines */ -static int __devinit -advwdt_probe(struct platform_device *dev) +static int __init advwdt_probe(struct platform_device *dev) { int ret; if (wdt_stop != wdt_start) { if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_stop); + 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)) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_start); + 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 */ + /* 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); - printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n", - timeout); + pr_info("timeout value must be 1<=x<=63, using %d\n", timeout); } ret = misc_register(&advwdt_miscdev); if (ret != 0) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto unreg_regions; } - - printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); - out: return ret; unreg_regions: @@ -293,27 +282,24 @@ unreg_stop: goto out; } -static int __devexit -advwdt_remove(struct platform_device *dev) +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); + 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) +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 = { - .probe = advwdt_probe, - .remove = __devexit_p(advwdt_remove), + .remove = advwdt_remove, .shutdown = advwdt_shutdown, .driver = { .owner = THIS_MODULE, @@ -321,36 +307,33 @@ static struct platform_driver advwdt_driver = { }, }; -static int __init -advwdt_init(void) +static int __init advwdt_init(void) { int err; - printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n"); + pr_info("WDT driver for Advantech single board computer initialising\n"); - err = platform_driver_register(&advwdt_driver); - if (err) - return err; + advwdt_platform_device = platform_device_register_simple(DRV_NAME, + -1, NULL, 0); + if (IS_ERR(advwdt_platform_device)) + return PTR_ERR(advwdt_platform_device); - advwdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); - if (IS_ERR(advwdt_platform_device)) { - err = PTR_ERR(advwdt_platform_device); - goto unreg_platform_driver; - } + err = platform_driver_probe(&advwdt_driver, advwdt_probe); + if (err) + goto unreg_platform_device; return 0; -unreg_platform_driver: - platform_driver_unregister(&advwdt_driver); +unreg_platform_device: + platform_device_unregister(advwdt_platform_device); return err; } -static void __exit -advwdt_exit(void) +static void __exit advwdt_exit(void) { platform_device_unregister(advwdt_platform_device); platform_driver_unregister(&advwdt_driver); - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); + pr_info("Watchdog Module Unloaded\n"); } module_init(advwdt_init); @@ -359,4 +342,3 @@ module_exit(advwdt_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>"); MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c index 2b1fbdb2fcf..3a17fbd39f8 100644 --- a/drivers/watchdog/alim1535_wdt.c +++ b/drivers/watchdog/alim1535_wdt.c @@ -7,6 +7,8 @@ * 2 of the License, or (at your option) any later version. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -18,29 +20,31 @@ #include <linux/init.h> #include <linux/fs.h> #include <linux/pci.h> - -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/uaccess.h> +#include <linux/io.h> #define WATCHDOG_NAME "ALi_M1535" -#define PFX WATCHDOG_NAME ": " #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 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) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (0 < timeout < 18000, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -57,7 +61,7 @@ static void ali_start(void) pci_read_config_dword(ali_pci, 0xCC, &val); val &= ~0x3F; /* Mask count */ - val |= (1<<25) | ali_timeout_bits; + val |= (1 << 25) | ali_timeout_bits; pci_write_config_dword(ali_pci, 0xCC, val); spin_unlock(&ali_lock); @@ -76,8 +80,8 @@ static void ali_stop(void) 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 */ + 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); @@ -86,7 +90,7 @@ static void ali_stop(void) /* * ali_keepalive - send a keepalive to the watchdog * - * Send a keepalive to the timer (actually we restart the timer). + * Send a keepalive to the timer (actually we restart the timer). */ static void ali_keepalive(void) @@ -103,15 +107,16 @@ static void ali_keepalive(void) static int ali_settimer(int t) { - if(t < 0) + 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; - 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; @@ -134,21 +139,22 @@ static int ali_settimer(int t) */ static ssize_t ali_write(struct file *file, const char __user *data, - size_t len, loff_t * ppos) + 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... */ + /* 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 */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') ali_expect_release = 42; @@ -163,7 +169,6 @@ static ssize_t ali_write(struct file *file, const char __user *data, /* * ali_ioctl - handle watchdog ioctls - * @inode: VFS inode * @file: VFS file pointer * @cmd: ioctl number * @arg: arguments to the ioctl @@ -172,12 +177,11 @@ static ssize_t ali_write(struct file *file, const char __user *data, * we want an extension to enable irq ack monitoring and the like */ -static int ali_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, @@ -186,57 +190,45 @@ static int ali_ioctl(struct inode *inode, struct file *file, }; 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_KEEPALIVE: - ali_keepalive(); - return 0; - - 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_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; } - - case WDIOC_SETTIMEOUT: - { - int new_timeout; - - if (get_user(new_timeout, p)) - return -EFAULT; - - if (ali_settimer(new_timeout)) - return -EINVAL; - - ali_keepalive(); - /* Fall */ + if (new_options & WDIOS_ENABLECARD) { + ali_start(); + retval = 0; } - - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - - default: - return -ENOTTY; + 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(); + /* Fall */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; } } @@ -274,10 +266,10 @@ static int ali_release(struct inode *inode, struct file *file) /* * Shut off the timer. */ - if (ali_expect_release == 42) { + if (ali_expect_release == 42) ali_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); ali_keepalive(); } clear_bit(0, &ali_is_open); @@ -292,13 +284,11 @@ static int ali_release(struct inode *inode, struct file *file) */ -static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +static int ali_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) { - if (code==SYS_DOWN || code==SYS_HALT) { - /* Turn the WDT off */ - ali_stop(); - } - + if (code == SYS_DOWN || code == SYS_HALT) + ali_stop(); /* Turn the WDT off */ return NOTIFY_DONE; } @@ -311,7 +301,7 @@ static int ali_notify_sys(struct notifier_block *this, unsigned long code, void * want to register another driver on the same PCI id. */ -static struct pci_device_id ali_pci_tbl[] = { +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, }, @@ -340,10 +330,10 @@ static int __init ali_find_watchdog(void) /* Check for the a 7101 PMU */ pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x7101, NULL); - if(pdev == NULL) + if (pdev == NULL) return -ENODEV; - if(pci_enable_device(pdev)) { + if (pci_enable_device(pdev)) { pci_dev_put(pdev); return -EIO; } @@ -355,9 +345,12 @@ static int __init ali_find_watchdog(void) */ pci_read_config_dword(pdev, 0xCC, &wdog); - wdog &= ~0x3F; /* Timer bits */ - wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */ - wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */ + /* 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); @@ -369,12 +362,12 @@ static int __init ali_find_watchdog(void) */ static const struct file_operations ali_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = ali_write, - .ioctl = ali_ioctl, - .open = ali_open, - .release = ali_release, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ali_write, + .unlocked_ioctl = ali_ioctl, + .open = ali_open, + .release = ali_release, }; static struct miscdevice ali_miscdev = { @@ -399,14 +392,14 @@ static int __init watchdog_init(void) int ret; /* Check whether or not the hardware watchdog is there */ - if (ali_find_watchdog() != 0) { + if (ali_find_watchdog() != 0) return -ENODEV; - } - /* Check that the timeout value is within it's range ; if not reset to the default */ + /* Check that the timeout value is within it's range; + if not reset to the default */ if (timeout < 1 || timeout >= 18000) { timeout = WATCHDOG_TIMEOUT; - printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n", + pr_info("timeout value must be 0 < timeout < 18000, using %d\n", timeout); } @@ -415,19 +408,18 @@ static int __init watchdog_init(void) ret = register_reboot_notifier(&ali_notifier); if (ret != 0) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); goto out; } ret = misc_register(&ali_miscdev); if (ret != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto unreg_reboot; } - printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); out: @@ -460,4 +452,3 @@ module_exit(watchdog_exit); MODULE_AUTHOR("Alan Cox"); MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/alim7101_wdt.c b/drivers/watchdog/alim7101_wdt.c index 238273c9865..996b2f7d330 100644 --- a/drivers/watchdog/alim7101_wdt.c +++ b/drivers/watchdog/alim7101_wdt.c @@ -19,6 +19,8 @@ * -- 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> @@ -31,13 +33,9 @@ #include <linux/init.h> #include <linux/fs.h> #include <linux/pci.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> - -#define OUR_NAME "alim7101_wdt" -#define PFX OUR_NAME ": " #define WDT_ENABLE 0x9C #define WDT_DISABLE 0x8C @@ -60,13 +58,17 @@ */ #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +/* 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) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); -static int use_gpio = 0; /* Use the pic (for a1d revision alim7101) */ +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)"); +MODULE_PARM_DESC(use_gpio, + "Use the gpio watchdog (required by old cobalt boards)."); static void wdt_timer_ping(unsigned long); static DEFINE_TIMER(timer, wdt_timer_ping, 0, 1); @@ -75,10 +77,11 @@ static unsigned long wdt_is_open; static char wdt_expect_close; static struct pci_dev *alim7101_pmu; -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -89,23 +92,25 @@ static void wdt_timer_ping(unsigned long data) /* If we got a heartbeat pulse within the WDT_US_INTERVAL * we agree to ping the WDT */ - char tmp; + char tmp; - if(time_before(jiffies, next_heartbeat)) - { + 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)); + 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); + 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 { - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); } /* Re-set the timer interval */ mod_timer(&timer, jiffies + WDT_INTERVAL); @@ -117,21 +122,27 @@ static void wdt_timer_ping(unsigned long data) static void wdt_change(int writeval) { - char tmp; + 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)); + 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_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)); + 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_read_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, tmp | 0x20); } } } @@ -148,7 +159,7 @@ static void wdt_startup(void) /* Start the timer */ mod_timer(&timer, jiffies + WDT_INTERVAL); - printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); + pr_info("Watchdog timer is now enabled\n"); } static void wdt_turnoff(void) @@ -156,7 +167,7 @@ static void wdt_turnoff(void) /* Stop the timer */ del_timer_sync(&timer); wdt_change(WDT_DISABLE); - printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); + pr_info("Watchdog timer is now disabled...\n"); } static void wdt_keepalive(void) @@ -169,10 +180,11 @@ static void wdt_keepalive(void) * /dev/watchdog handling */ -static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +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 (count) { if (!nowayout) { size_t ofs; @@ -183,7 +195,7 @@ static ssize_t fop_write(struct file * file, const char __user * buf, size_t cou /* now scan */ for (ofs = 0; ofs != count; ofs++) { char c; - if (get_user(c, buf+ofs)) + if (get_user(c, buf + ofs)) return -EFAULT; if (c == 'V') wdt_expect_close = 42; @@ -195,122 +207,118 @@ static ssize_t fop_write(struct file * file, const char __user * buf, size_t cou return count; } -static int fop_open(struct inode * inode, struct file * file) +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)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; /* Good, fire up the show */ wdt_startup(); return nonseekable_open(inode, file); } -static int fop_close(struct inode * inode, struct file * file) +static int fop_close(struct inode *inode, struct file *file) { - if(wdt_expect_close == 42) + if (wdt_expect_close == 42) wdt_turnoff(); else { /* wim: shouldn't there be a: del_timer(&timer); */ - printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + 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 int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident = - { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "ALiM7101", }; - switch(cmd) + 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: { - 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_KEEPALIVE: - wdt_keepalive(); - return 0; - 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; - } + int new_options, retval = -EINVAL; - return retval; + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; } - case WDIOC_SETTIMEOUT: - { - int new_timeout; - - if(get_user(new_timeout, p)) - return -EFAULT; - - if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ - return -EINVAL; - - timeout = new_timeout; - wdt_keepalive(); - /* Fall through */ + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; } - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - default: - return -ENOTTY; + 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(); + /* Fall through */ + } + 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, - .ioctl= fop_ioctl, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fop_write, + .open = fop_open, + .release = fop_close, + .unlocked_ioctl = fop_ioctl, }; static struct miscdevice wdt_miscdev = { - .minor=WATCHDOG_MINOR, - .name="watchdog", - .fops=&wdt_fops, + .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) +static int wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) { - if (code==SYS_DOWN || code==SYS_HALT) + if (code == SYS_DOWN || code == SYS_HALT) wdt_turnoff(); - if (code==SYS_RESTART) { + if (code == SYS_RESTART) { /* - * 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 + * 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); - printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n"); + pr_info("Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second\n"); } return NOTIFY_DONE; } @@ -320,8 +328,7 @@ static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void * turn the timebomb registers off. */ -static struct notifier_block wdt_notifier= -{ +static struct notifier_block wdt_notifier = { .notifier_call = wdt_notify_sys, }; @@ -340,11 +347,11 @@ static int __init alim7101_wdt_init(void) struct pci_dev *ali1543_south; char tmp; - printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n"); + 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) { - printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n"); + pr_info("ALi M7101 PMU not present - WDT not set\n"); return -EBUSY; } @@ -354,48 +361,46 @@ static int __init alim7101_wdt_init(void) ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL); if (!ali1543_south) { - printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n"); + 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) { - printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n"); + 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) { - printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n"); + 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 */ - { + if (timeout < 1 || timeout > 3600) { + /* arbitrary upper limit */ timeout = WATCHDOG_TIMEOUT; - printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", timeout); } rc = register_reboot_notifier(&wdt_notifier); if (rc) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out; } rc = misc_register(&wdt_miscdev); if (rc) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - wdt_miscdev.minor, rc); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); goto err_out_reboot; } - if (nowayout) { + if (nowayout) __module_get(THIS_MODULE); - } - printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", + pr_info("WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); return 0; @@ -409,7 +414,7 @@ err_out: module_init(alim7101_wdt_init); module_exit(alim7101_wdt_unload); -static struct pci_device_id alim7101_pci_tbl[] __devinitdata = { +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) }, { } @@ -420,4 +425,3 @@ MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl); MODULE_AUTHOR("Steve Hill"); MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ar7_wdt.c b/drivers/watchdog/ar7_wdt.c index 2eb48c0df32..ae6c287a49c 100644 --- a/drivers/watchdog/ar7_wdt.c +++ b/drivers/watchdog/ar7_wdt.c @@ -23,36 +23,35 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/errno.h> -#include <linux/init.h> #include <linux/miscdevice.h> +#include <linux/platform_device.h> #include <linux/watchdog.h> -#include <linux/notifier.h> -#include <linux/reboot.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/ar7/ar7.h> +#include <asm/mach-ar7/ar7.h> -#define DRVNAME "ar7_wdt" #define LONGNAME "TI AR7 Watchdog Timer" MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>"); MODULE_DESCRIPTION(LONGNAME); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); static int margin = 60; module_param(margin, int, 0); MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); +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)) @@ -69,30 +68,19 @@ struct ar7_wdt { u32 prescale; }; -static struct semaphore open_semaphore; +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 -/* Offset of the WDT registers */ -static unsigned long ar7_regs_wdt; +/* Resource of the WDT registers */ +static struct resource *ar7_regs_wdt; /* Pointer to the remapped WDT IO space */ static struct ar7_wdt *ar7_wdt; -static void ar7_wdt_get_regs(void) -{ - u16 chip_id = ar7_chip_id(); - switch (chip_id) { - case AR7_CHIP_7100: - case AR7_CHIP_7200: - ar7_regs_wdt = AR7_REGS_WDT; - break; - default: - ar7_regs_wdt = UR8_REGS_WDT; - break; - } -} +static struct clk *vbus_clk; static void ar7_wdt_kick(u32 value) { @@ -104,7 +92,7 @@ static void ar7_wdt_kick(u32 value) return; } } - printk(KERN_ERR DRVNAME ": failed to unlock WDT kick reg\n"); + pr_err("failed to unlock WDT kick reg\n"); } static void ar7_wdt_prescale(u32 value) @@ -117,7 +105,7 @@ static void ar7_wdt_prescale(u32 value) return; } } - printk(KERN_ERR DRVNAME ": failed to unlock WDT prescale reg\n"); + pr_err("failed to unlock WDT prescale reg\n"); } static void ar7_wdt_change(u32 value) @@ -130,7 +118,7 @@ static void ar7_wdt_change(u32 value) return; } } - printk(KERN_ERR DRVNAME ": failed to unlock WDT change reg\n"); + pr_err("failed to unlock WDT change reg\n"); } static void ar7_wdt_disable(u32 value) @@ -146,40 +134,43 @@ static void ar7_wdt_disable(u32 value) } } } - printk(KERN_ERR DRVNAME ": failed to unlock WDT disable reg\n"); + pr_err("failed to unlock WDT disable reg\n"); } static void ar7_wdt_update_margin(int new_margin) { u32 change; - - change = new_margin * (ar7_vbus_freq() / prescale_value); - if (change < 1) change = 1; - if (change > 0xffff) change = 0xffff; + 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 / ar7_vbus_freq(); - printk(KERN_INFO DRVNAME - ": timer margin %d seconds (prescale %d, change %d, freq %d)\n", - margin, prescale_value, change, ar7_vbus_freq()); + 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) { - printk(KERN_DEBUG DRVNAME ": enabling watchdog timer\n"); + pr_debug("enabling watchdog timer\n"); ar7_wdt_disable(1); ar7_wdt_kick(1); } static void ar7_wdt_disable_wdt(void) { - printk(KERN_DEBUG DRVNAME ": disabling watchdog timer\n"); + 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 (down_trylock(&open_semaphore)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; ar7_wdt_enable_wdt(); expect_close = 0; @@ -190,31 +181,13 @@ static int ar7_wdt_open(struct inode *inode, struct file *file) static int ar7_wdt_release(struct inode *inode, struct file *file) { if (!expect_close) - printk(KERN_WARNING DRVNAME - ": watchdog device closed unexpectedly," - "will not disable the watchdog timer\n"); + pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); else if (!nowayout) ar7_wdt_disable_wdt(); - - up(&open_semaphore); - + clear_bit(0, &wdt_is_open); return 0; } -static int ar7_wdt_notify_sys(struct notifier_block *this, - unsigned long code, void *unused) -{ - if (code == SYS_HALT || code == SYS_POWER_OFF) - if (!nowayout) - ar7_wdt_disable_wdt(); - - return NOTIFY_DONE; -} - -static struct notifier_block ar7_wdt_notifier = { - .notifier_call = ar7_wdt_notify_sys -}; - static ssize_t ar7_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) { @@ -222,12 +195,14 @@ static ssize_t ar7_wdt_write(struct file *file, const char *data, 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 1; @@ -237,19 +212,18 @@ static ssize_t ar7_wdt_write(struct file *file, const char *data, return len; } -static int ar7_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long ar7_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { - static struct watchdog_info ident = { + static const struct watchdog_info ident = { .identity = LONGNAME, .firmware_version = 1, - .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), + .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE), }; int new_margin; switch (cmd) { - default: - return -ENOTTY; case WDIOC_GETSUPPORT: if (copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))) @@ -269,22 +243,27 @@ static int ar7_wdt_ioctl(struct inode *inode, struct file *file, if (new_margin < 1) return -EINVAL; + spin_lock(&wdt_lock); ar7_wdt_update_margin(new_margin); ar7_wdt_kick(1); + spin_unlock(&wdt_lock); 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, - .ioctl = ar7_wdt_ioctl, + .unlocked_ioctl = ar7_wdt_ioctl, .open = ar7_wdt_open, .release = ar7_wdt_release, + .llseek = no_llseek, }; static struct miscdevice ar7_wdt_miscdev = { @@ -293,57 +272,61 @@ static struct miscdevice ar7_wdt_miscdev = { .fops = &ar7_wdt_fops, }; -static int __init ar7_wdt_init(void) +static int ar7_wdt_probe(struct platform_device *pdev) { int rc; - ar7_wdt_get_regs(); + ar7_regs_wdt = + platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + ar7_wdt = devm_ioremap_resource(&pdev->dev, ar7_regs_wdt); + if (IS_ERR(ar7_wdt)) + return PTR_ERR(ar7_wdt); - if (!request_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt), - LONGNAME)) { - printk(KERN_WARNING DRVNAME ": watchdog I/O region busy\n"); - return -EBUSY; + 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 = (struct ar7_wdt *) - ioremap(ar7_regs_wdt, sizeof(struct ar7_wdt)); - ar7_wdt_disable_wdt(); ar7_wdt_prescale(prescale_value); ar7_wdt_update_margin(margin); - sema_init(&open_semaphore, 1); - - rc = register_reboot_notifier(&ar7_wdt_notifier); - if (rc) { - printk(KERN_ERR DRVNAME - ": unable to register reboot notifier\n"); - goto out_alloc; - } - rc = misc_register(&ar7_wdt_miscdev); if (rc) { - printk(KERN_ERR DRVNAME ": unable to register misc device\n"); - goto out_register; + pr_err("unable to register misc device\n"); + goto out; } - goto out; + return 0; -out_register: - unregister_reboot_notifier(&ar7_wdt_notifier); -out_alloc: - iounmap(ar7_wdt); - release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt)); out: + clk_put(vbus_clk); + vbus_clk = NULL; return rc; } -static void __exit ar7_wdt_cleanup(void) +static int ar7_wdt_remove(struct platform_device *pdev) { misc_deregister(&ar7_wdt_miscdev); - unregister_reboot_notifier(&ar7_wdt_notifier); - iounmap(ar7_wdt); - release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt)); + clk_put(vbus_clk); + vbus_clk = NULL; + return 0; +} + +static void ar7_wdt_shutdown(struct platform_device *pdev) +{ + if (!nowayout) + ar7_wdt_disable_wdt(); } -module_init(ar7_wdt_init); -module_exit(ar7_wdt_cleanup); +static struct platform_driver ar7_wdt_driver = { + .probe = ar7_wdt_probe, + .remove = ar7_wdt_remove, + .shutdown = ar7_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "ar7_wdt", + }, +}; + +module_platform_driver(ar7_wdt_driver); diff --git a/drivers/watchdog/at32ap700x_wdt.c b/drivers/watchdog/at32ap700x_wdt.c index fb5ed6478f7..25b5c67d3af 100644 --- a/drivers/watchdog/at32ap700x_wdt.c +++ b/drivers/watchdog/at32ap700x_wdt.c @@ -32,6 +32,7 @@ #include <linux/uaccess.h> #include <linux/io.h> #include <linux/spinlock.h> +#include <linux/slab.h> #define TIMEOUT_MIN 1 #define TIMEOUT_MAX 2 @@ -44,8 +45,8 @@ MODULE_PARM_DESC(timeout, "Timeout value. Limited to be 1 or 2 seconds. (default=" __MODULE_STRING(TIMEOUT_DEFAULT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, 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) ")"); @@ -202,7 +203,7 @@ static int at32_wdt_get_status(void) return status; } -static struct watchdog_info at32_wdt_info = { +static const struct watchdog_info at32_wdt_info = { .identity = "at32ap700x watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | @@ -212,8 +213,8 @@ static struct watchdog_info at32_wdt_info = { /* * Handle commands from user-space. */ -static int at32_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long at32_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { int ret = -ENOTTY; int time; @@ -221,27 +222,10 @@ static int at32_wdt_ioctl(struct inode *inode, struct file *file, int __user *p = argp; switch (cmd) { - case WDIOC_KEEPALIVE: - at32_wdt_pat(); - ret = 0; - break; case WDIOC_GETSUPPORT: ret = copy_to_user(argp, &at32_wdt_info, sizeof(at32_wdt_info)) ? -EFAULT : 0; break; - case WDIOC_SETTIMEOUT: - ret = get_user(time, p); - if (ret) - break; - ret = at32_wdt_settimeout(time); - if (ret) - break; - /* Enable new time value */ - at32_wdt_start(); - /* fall through */ - case WDIOC_GETTIMEOUT: - ret = put_user(wdt->timeout, p); - break; case WDIOC_GETSTATUS: ret = put_user(0, p); break; @@ -258,6 +242,23 @@ static int at32_wdt_ioctl(struct inode *inode, struct file *file, at32_wdt_start(); ret = 0; break; + case WDIOC_KEEPALIVE: + at32_wdt_pat(); + ret = 0; + break; + case WDIOC_SETTIMEOUT: + ret = get_user(time, p); + if (ret) + break; + ret = at32_wdt_settimeout(time); + if (ret) + break; + /* Enable new time value */ + at32_wdt_start(); + /* fall through */ + case WDIOC_GETTIMEOUT: + ret = put_user(wdt->timeout, p); + break; } return ret; @@ -283,7 +284,7 @@ static ssize_t at32_wdt_write(struct file *file, const char __user *data, */ for (i = 0; i != len; i++) { char c; - if (get_user(c, data+i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_release = 42; @@ -298,7 +299,7 @@ static ssize_t at32_wdt_write(struct file *file, const char __user *data, static const struct file_operations at32_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = at32_wdt_ioctl, + .unlocked_ioctl = at32_wdt_ioctl, .open = at32_wdt_open, .release = at32_wdt_close, .write = at32_wdt_write, @@ -320,13 +321,12 @@ static int __init at32_wdt_probe(struct platform_device *pdev) return -ENXIO; } - wdt = kzalloc(sizeof(struct wdt_at32ap700x), GFP_KERNEL); - if (!wdt) { - dev_dbg(&pdev->dev, "no memory for wdt structure\n"); + wdt = devm_kzalloc(&pdev->dev, sizeof(struct wdt_at32ap700x), + GFP_KERNEL); + if (!wdt) return -ENOMEM; - } - wdt->regs = ioremap(regs->start, regs->end - regs->start + 1); + wdt->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs)); if (!wdt->regs) { ret = -ENOMEM; dev_dbg(&pdev->dev, "could not map I/O memory\n"); @@ -341,13 +341,17 @@ static int __init at32_wdt_probe(struct platform_device *pdev) dev_info(&pdev->dev, "CPU must be reset with external " "reset or POR due to silicon errata.\n"); ret = -EIO; - goto err_iounmap; + goto err_free; } else { wdt->users = 0; } - wdt->miscdev.minor = WATCHDOG_MINOR; - wdt->miscdev.name = "watchdog"; - wdt->miscdev.fops = &at32_wdt_fops; + + wdt->miscdev.minor = WATCHDOG_MINOR; + wdt->miscdev.name = "watchdog"; + wdt->miscdev.fops = &at32_wdt_fops; + wdt->miscdev.parent = &pdev->dev; + + platform_set_drvdata(pdev, wdt); if (at32_wdt_settimeout(timeout)) { at32_wdt_settimeout(TIMEOUT_DEFAULT); @@ -359,21 +363,16 @@ static int __init at32_wdt_probe(struct platform_device *pdev) ret = misc_register(&wdt->miscdev); if (ret) { dev_dbg(&pdev->dev, "failed to register wdt miscdev\n"); - goto err_iounmap; + goto err_free; } - platform_set_drvdata(pdev, wdt); - wdt->miscdev.parent = &pdev->dev; dev_info(&pdev->dev, "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n", wdt->regs, wdt->timeout, nowayout); return 0; -err_iounmap: - iounmap(wdt->regs); err_free: - kfree(wdt); wdt = NULL; return ret; } @@ -386,12 +385,8 @@ static int __exit at32_wdt_remove(struct platform_device *pdev) at32_wdt_stop(); misc_deregister(&wdt->miscdev); - iounmap(wdt->regs); - kfree(wdt); wdt = NULL; - platform_set_drvdata(pdev, NULL); } - return 0; } @@ -418,6 +413,9 @@ static int at32_wdt_resume(struct platform_device *pdev) #define at32_wdt_resume NULL #endif +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:at32_wdt"); + static struct platform_driver at32_wdt_driver = { .remove = __exit_p(at32_wdt_remove), .suspend = at32_wdt_suspend, @@ -429,19 +427,8 @@ static struct platform_driver at32_wdt_driver = { .shutdown = at32_wdt_shutdown, }; -static int __init at32_wdt_init(void) -{ - return platform_driver_probe(&at32_wdt_driver, at32_wdt_probe); -} -module_init(at32_wdt_init); - -static void __exit at32_wdt_exit(void) -{ - platform_driver_unregister(&at32_wdt_driver); -} -module_exit(at32_wdt_exit); +module_platform_driver_probe(at32_wdt_driver, at32_wdt_probe); -MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); +MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/at91rm9200_wdt.c b/drivers/watchdog/at91rm9200_wdt.c index a684b1e8737..dee6cc21d27 100644 --- a/drivers/watchdog/at91rm9200_wdt.c +++ b/drivers/watchdog/at91rm9200_wdt.c @@ -9,10 +9,13 @@ * 2 of the License, or (at your option) any later version. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/bitops.h> #include <linux/errno.h> #include <linux/fs.h> #include <linux/init.h> +#include <linux/io.h> #include <linux/kernel.h> #include <linux/miscdevice.h> #include <linux/module.h> @@ -20,22 +23,26 @@ #include <linux/platform_device.h> #include <linux/types.h> #include <linux/watchdog.h> -#include <asm/uaccess.h> -#include <asm/arch/at91_st.h> - +#include <linux/uaccess.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <mach/at91_st.h> #define WDT_DEFAULT_TIME 5 /* seconds */ #define WDT_MAX_TIME 256 /* seconds */ static int wdt_time = WDT_DEFAULT_TIME; -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; module_param(wdt_time, int, 0); -MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="__MODULE_STRING(WDT_DEFAULT_TIME) ")"); +MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" + __MODULE_STRING(WDT_DEFAULT_TIME) ")"); #ifdef CONFIG_WATCHDOG_NOWAYOUT -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); #endif @@ -46,26 +53,27 @@ static unsigned long at91wdt_busy; /* * Disable the watchdog. */ -static void inline at91_wdt_stop(void) +static inline void at91_wdt_stop(void) { - at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN); + at91_st_write(AT91_ST_WDMR, AT91_ST_EXTEN); } /* * Enable and reset the watchdog. */ -static void inline at91_wdt_start(void) +static inline void at91_wdt_start(void) { - at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | (((65536 * wdt_time) >> 8) & AT91_ST_WDV)); - at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); + at91_st_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | + (((65536 * wdt_time) >> 8) & AT91_ST_WDV)); + at91_st_write(AT91_ST_CR, AT91_ST_WDRST); } /* * Reload the watchdog timer. (ie, pat the watchdog) */ -static void inline at91_wdt_reload(void) +static inline void at91_wdt_reload(void) { - at91_sys_write(AT91_ST_CR, AT91_ST_WDRST); + at91_st_write(AT91_ST_CR, AT91_ST_WDRST); } /* ......................................................................... */ @@ -89,8 +97,9 @@ static int at91_wdt_open(struct inode *inode, struct file *file) */ static int at91_wdt_close(struct inode *inode, struct file *file) { + /* Disable the watchdog when file is closed */ if (!nowayout) - at91_wdt_stop(); /* Disable the watchdog when file is closed */ + at91_wdt_stop(); clear_bit(0, &at91wdt_busy); return 0; @@ -102,20 +111,21 @@ static int at91_wdt_close(struct inode *inode, struct file *file) static int at91_wdt_settimeout(int new_time) { /* - * All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz + * All counting occurs at SLOW_CLOCK / 128 = 256 Hz * * Since WDV is a 16-bit counter, the maximum period is - * 65536 / 0.256 = 256 seconds. + * 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. */ + /* Set new watchdog time. It will be used when + at91_wdt_start() is called. */ wdt_time = new_time; return 0; } -static struct watchdog_info at91_wdt_info = { +static const struct watchdog_info at91_wdt_info = { .identity = "at91 watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, }; @@ -123,60 +133,52 @@ static struct watchdog_info at91_wdt_info = { /* * Handle commands from user-space. */ -static int at91_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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_KEEPALIVE: - at91_wdt_reload(); /* pat the watchdog */ - return 0; - - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &at91_wdt_info, sizeof(at91_wdt_info)) ? -EFAULT : 0; - - case WDIOC_SETTIMEOUT: - if (get_user(new_value, p)) - return -EFAULT; - - if (at91_wdt_settimeout(new_value)) - return -EINVAL; - - /* Enable new time 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 current value */ - return put_user(wdt_time, p); - - case WDIOC_GETTIMEOUT: - return put_user(wdt_time, p); - - 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; - - default: - return -ENOTTY; + 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) +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; @@ -187,7 +189,7 @@ static ssize_t at91_wdt_write(struct file *file, const char *data, size_t len, l static const struct file_operations at91wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = at91_wdt_ioctl, + .unlocked_ioctl = at91_wdt_ioctl, .open = at91_wdt_open, .release = at91_wdt_close, .write = at91_wdt_write, @@ -199,7 +201,7 @@ static struct miscdevice at91wdt_miscdev = { .fops = &at91wdt_fops, }; -static int __init at91wdt_probe(struct platform_device *pdev) +static int at91wdt_probe(struct platform_device *pdev) { int res; @@ -211,11 +213,12 @@ static int __init at91wdt_probe(struct platform_device *pdev) if (res) return res; - printk("AT91 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", nowayout" : ""); + pr_info("AT91 Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); return 0; } -static int __exit at91wdt_remove(struct platform_device *pdev) +static int at91wdt_remove(struct platform_device *pdev) { int res; @@ -243,7 +246,7 @@ static int at91wdt_resume(struct platform_device *pdev) { if (at91wdt_busy) at91_wdt_start(); - return 0; + return 0; } #else @@ -251,24 +254,33 @@ static int at91wdt_resume(struct platform_device *pdev) #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 = __exit_p(at91wdt_remove), + .remove = at91wdt_remove, .shutdown = at91wdt_shutdown, .suspend = at91wdt_suspend, .resume = at91wdt_resume, .driver = { .name = "at91_wdt", .owner = THIS_MODULE, + .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 */ + /* 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("at91_wdt: wdt_time value must be 1 <= wdt_time <= 256, using %d\n", wdt_time); + pr_info("wdt_time value must be 1 <= wdt_time <= 256, using %d\n", + wdt_time); } return platform_driver_register(&at91wdt_driver); @@ -285,4 +297,4 @@ module_exit(at91_wdt_exit); MODULE_AUTHOR("Andrew Victor"); MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:at91_wdt"); diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c new file mode 100644 index 00000000000..489729b2629 --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c @@ -0,0 +1,405 @@ +/* + * Watchdog driver for Atmel AT91SAM9x processors. + * + * Copyright (C) 2008 Renaud CERRATO r.cerrato@til-technologies.fr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* + * 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/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) \ + __raw_readl((wdt)->base + (field)) +#define wdt_write(wtd, field, val) \ + __raw_writel((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; +}; + +/* ......................................................................... */ + +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(unsigned long data) +{ + struct at91wdt *wdt = (struct at91wdt *)data; + 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 = request_irq(wdt->irq, wdt_interrupt, + IRQF_SHARED | IRQF_IRQPOLL, + 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); + + setup_timer(&wdt->timer, at91_ping, (unsigned long)wdt); + + /* + * 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) +{ + struct resource *r; + 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; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + if (pdev->dev.of_node) { + err = of_at91wdt_init(pdev->dev.of_node, wdt); + if (err) + return err; + } + + err = at91_wdt_init(pdev, wdt); + if (err) + return err; + + platform_set_drvdata(pdev, wdt); + + pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", + wdt->wdd.timeout, wdt->nowayout); + + return 0; +} + +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); + + 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", + .owner = THIS_MODULE, + .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 00000000000..c6fbb2e6c41 --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.h @@ -0,0 +1,37 @@ +/* + * drivers/watchdog/at91sam9_wdt.h + * + * Copyright (C) 2007 Andrew Victor + * Copyright (C) 2007 Atmel Corporation. + * + * Watchdog Timer (WDT) - System peripherals regsters. + * Based on AT91SAM9261 datasheet revision D. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef AT91_WDT_H +#define AT91_WDT_H + +#define AT91_WDT_CR 0x00 /* Watchdog Control Register */ +#define AT91_WDT_WDRSTT (1 << 0) /* Restart */ +#define AT91_WDT_KEY (0xa5 << 24) /* KEY Password */ + +#define AT91_WDT_MR 0x04 /* Watchdog Mode Register */ +#define AT91_WDT_WDV (0xfff << 0) /* Counter Value */ +#define AT91_WDT_WDFIEN (1 << 12) /* Fault Interrupt Enable */ +#define AT91_WDT_WDRSTEN (1 << 13) /* Reset Processor */ +#define AT91_WDT_WDRPROC (1 << 14) /* Timer Restart */ +#define AT91_WDT_WDDIS (1 << 15) /* Watchdog Disable */ +#define AT91_WDT_WDD (0xfff << 16) /* Delta Value */ +#define AT91_WDT_WDDBGHLT (1 << 28) /* Debug Halt */ +#define AT91_WDT_WDIDLEHLT (1 << 29) /* Idle Halt */ + +#define AT91_WDT_SR 0x08 /* Watchdog Status Register */ +#define AT91_WDT_WDUNF (1 << 0) /* Watchdog Underflow */ +#define AT91_WDT_WDERR (1 << 1) /* Watchdog Error */ + +#endif diff --git a/drivers/watchdog/ath79_wdt.c b/drivers/watchdog/ath79_wdt.c new file mode 100644 index 00000000000..41ac4660fb8 --- /dev/null +++ b/drivers/watchdog/ath79_wdt.c @@ -0,0 +1,340 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#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> + +#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 nonseekable_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, + .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) +{ + struct resource *res; + u32 ctrl; + int err; + + if (wdt_base) + return -EBUSY; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt_base = devm_ioremap_resource(&pdev->dev, res); + 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 ath97_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 = ath97_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .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 00000000000..8df450c090a --- /dev/null +++ b/drivers/watchdog/bcm2835_wdt.c @@ -0,0 +1,185 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> + +#define PM_RSTC 0x1c +#define PM_WDOG 0x24 + +#define PM_PASSWORD 0x5a000000 + +#define PM_WDOG_TIME_SET 0x000fffff +#define PM_RSTC_WRCFG_CLR 0xffffffcf +#define PM_RSTC_WRCFG_SET 0x00000030 +#define PM_RSTC_WRCFG_FULL_RESET 0x00000020 +#define PM_RSTC_RESET 0x00000102 + +#define SECS_TO_WDOG_TICKS(x) ((x) << 16) +#define WDOG_TICKS_TO_SECS(x) ((x) >> 16) + +struct bcm2835_wdt { + void __iomem *base; + spinlock_t lock; +}; + +static unsigned int heartbeat; +static bool nowayout = WATCHDOG_NOWAYOUT; + +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); + dev_info(wdog->dev, "Watchdog timer stopped"); + return 0; +} + +static int bcm2835_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) +{ + wdog->timeout = t; + 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 struct watchdog_ops bcm2835_wdt_ops = { + .owner = THIS_MODULE, + .start = bcm2835_wdt_start, + .stop = bcm2835_wdt_stop, + .set_timeout = bcm2835_wdt_set_timeout, + .get_timeleft = bcm2835_wdt_get_timeleft, +}; + +static 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_timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), + .timeout = WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), +}; + +static int bcm2835_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct bcm2835_wdt *wdt; + int err; + + wdt = devm_kzalloc(dev, sizeof(struct bcm2835_wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + platform_set_drvdata(pdev, wdt); + + spin_lock_init(&wdt->lock); + + wdt->base = of_iomap(np, 0); + if (!wdt->base) { + dev_err(dev, "Failed to remap watchdog regs"); + return -ENODEV; + } + + watchdog_set_drvdata(&bcm2835_wdt_wdd, wdt); + watchdog_init_timeout(&bcm2835_wdt_wdd, heartbeat, dev); + watchdog_set_nowayout(&bcm2835_wdt_wdd, nowayout); + err = watchdog_register_device(&bcm2835_wdt_wdd); + if (err) { + dev_err(dev, "Failed to register watchdog device"); + iounmap(wdt->base); + return err; + } + + dev_info(dev, "Broadcom BCM2835 watchdog timer"); + return 0; +} + +static int bcm2835_wdt_remove(struct platform_device *pdev) +{ + struct bcm2835_wdt *wdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(&bcm2835_wdt_wdd); + iounmap(wdt->base); + + return 0; +} + +static void bcm2835_wdt_shutdown(struct platform_device *pdev) +{ + bcm2835_wdt_stop(&bcm2835_wdt_wdd); +} + +static const struct of_device_id bcm2835_wdt_of_match[] = { + { .compatible = "brcm,bcm2835-pm-wdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm2835_wdt_of_match); + +static struct platform_driver bcm2835_wdt_driver = { + .probe = bcm2835_wdt_probe, + .remove = bcm2835_wdt_remove, + .shutdown = bcm2835_wdt_shutdown, + .driver = { + .name = "bcm2835-wdt", + .owner = THIS_MODULE, + .of_match_table = bcm2835_wdt_of_match, + }, +}; +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_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 00000000000..b61fcc53597 --- /dev/null +++ b/drivers/watchdog/bcm47xx_wdt.c @@ -0,0 +1,257 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#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/reboot.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 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, +}; + +static void bcm47xx_wdt_soft_timer_tick(unsigned long data) +{ + struct bcm47xx_wdt *wdt = (struct bcm47xx_wdt *)data; + 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((unsigned long)wdt); + + 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 int bcm47xx_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + struct bcm47xx_wdt *wdt; + + wdt = container_of(this, struct bcm47xx_wdt, notifier); + if (code == SYS_DOWN || code == SYS_HALT) + wdt->wdd.ops->stop(&wdt->wdd); + return NOTIFY_DONE; +} + +static 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, +}; + +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; + setup_timer(&wdt->soft_timer, bcm47xx_wdt_soft_timer_tick, + (long unsigned int)wdt); + } else { + wdt->wdd.ops = &bcm47xx_wdt_hard_ops; + } + + wdt->wdd.info = &bcm47xx_wdt_info; + wdt->wdd.timeout = WDT_DEFAULT_TIME; + ret = wdt->wdd.ops->set_timeout(&wdt->wdd, timeout); + if (ret) + goto err_timer; + watchdog_set_nowayout(&wdt->wdd, nowayout); + + wdt->notifier.notifier_call = &bcm47xx_wdt_notify_sys; + + ret = register_reboot_notifier(&wdt->notifier); + if (ret) + goto err_timer; + + ret = watchdog_register_device(&wdt->wdd); + if (ret) + goto err_notifier; + + dev_info(&pdev->dev, "BCM47xx Watchdog Timer enabled (%d seconds%s%s)\n", + timeout, nowayout ? ", nowayout" : "", + soft ? ", Software Timer" : ""); + return 0; + +err_notifier: + unregister_reboot_notifier(&wdt->notifier); +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); + + if (!wdt) + return -ENXIO; + + watchdog_unregister_device(&wdt->wdd); + unregister_reboot_notifier(&wdt->notifier); + + return 0; +} + +static struct platform_driver bcm47xx_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .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/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c new file mode 100644 index 00000000000..5a8e879a430 --- /dev/null +++ b/drivers/watchdog/bcm63xx_wdt.c @@ -0,0 +1,318 @@ +/* + * Broadcom BCM63xx SoC watchdog driver + * + * Copyright (C) 2007, Miguel Gaio <miguel.gaio@efixo.com> + * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitops.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/types.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/resource.h> +#include <linux/platform_device.h> + +#include <bcm63xx_cpu.h> +#include <bcm63xx_io.h> +#include <bcm63xx_regs.h> +#include <bcm63xx_timer.h> + +#define PFX KBUILD_MODNAME + +#define WDT_HZ 50000000 /* Fclk */ +#define WDT_DEFAULT_TIME 30 /* seconds */ +#define WDT_MAX_TIME 256 /* seconds */ + +static struct { + void __iomem *regs; + struct timer_list timer; + unsigned long inuse; + atomic_t ticks; +} bcm63xx_wdt_device; + +static int expect_close; + +static int wdt_time = WDT_DEFAULT_TIME; +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) ")"); + +/* HW functions */ +static void bcm63xx_wdt_hw_start(void) +{ + bcm_writel(0xfffffffe, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG); + bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); + bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); +} + +static void bcm63xx_wdt_hw_stop(void) +{ + bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); + bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); +} + +static void bcm63xx_wdt_isr(void *data) +{ + struct pt_regs *regs = get_irq_regs(); + + die(PFX " fire", regs); +} + +static void bcm63xx_timer_tick(unsigned long unused) +{ + if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) { + bcm63xx_wdt_hw_start(); + mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ); + } else + pr_crit("watchdog will restart system\n"); +} + +static void bcm63xx_wdt_pet(void) +{ + atomic_set(&bcm63xx_wdt_device.ticks, wdt_time); +} + +static void bcm63xx_wdt_start(void) +{ + bcm63xx_wdt_pet(); + bcm63xx_timer_tick(0); +} + +static void bcm63xx_wdt_pause(void) +{ + del_timer_sync(&bcm63xx_wdt_device.timer); + bcm63xx_wdt_hw_stop(); +} + +static int bcm63xx_wdt_settimeout(int new_time) +{ + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) + return -EINVAL; + + wdt_time = new_time; + + return 0; +} + +static int bcm63xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse)) + return -EBUSY; + + bcm63xx_wdt_start(); + return nonseekable_open(inode, file); +} + +static int bcm63xx_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + bcm63xx_wdt_pause(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + bcm63xx_wdt_start(); + } + clear_bit(0, &bcm63xx_wdt_device.inuse); + expect_close = 0; + return 0; +} + +static ssize_t bcm63xx_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; + } + } + bcm63xx_wdt_pet(); + } + return len; +} + +static struct watchdog_info bcm63xx_wdt_info = { + .identity = PFX, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + + +static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value, retval = -EINVAL; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &bcm63xx_wdt_info, + sizeof(bcm63xx_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) { + bcm63xx_wdt_pause(); + retval = 0; + } + if (new_value & WDIOS_ENABLECARD) { + bcm63xx_wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + bcm63xx_wdt_pet(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + if (bcm63xx_wdt_settimeout(new_value)) + return -EINVAL; + + bcm63xx_wdt_pet(); + + case WDIOC_GETTIMEOUT: + return put_user(wdt_time, p); + + default: + return -ENOTTY; + + } +} + +static const struct file_operations bcm63xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = bcm63xx_wdt_write, + .unlocked_ioctl = bcm63xx_wdt_ioctl, + .open = bcm63xx_wdt_open, + .release = bcm63xx_wdt_release, +}; + +static struct miscdevice bcm63xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bcm63xx_wdt_fops, +}; + + +static int bcm63xx_wdt_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + + setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "failed to get resources\n"); + return -ENODEV; + } + + bcm63xx_wdt_device.regs = devm_ioremap_nocache(&pdev->dev, r->start, + resource_size(r)); + if (!bcm63xx_wdt_device.regs) { + dev_err(&pdev->dev, "failed to remap I/O resources\n"); + return -ENXIO; + } + + ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register wdt timer isr\n"); + return ret; + } + + if (bcm63xx_wdt_settimeout(wdt_time)) { + bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME); + dev_info(&pdev->dev, + ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n", + wdt_time); + } + + ret = misc_register(&bcm63xx_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register watchdog device\n"); + goto unregister_timer; + } + + dev_info(&pdev->dev, " started, timer margin: %d sec\n", + WDT_DEFAULT_TIME); + + return 0; + +unregister_timer: + bcm63xx_timer_unregister(TIMER_WDT_ID); + return ret; +} + +static int bcm63xx_wdt_remove(struct platform_device *pdev) +{ + if (!nowayout) + bcm63xx_wdt_pause(); + + misc_deregister(&bcm63xx_wdt_miscdev); + bcm63xx_timer_unregister(TIMER_WDT_ID); + return 0; +} + +static void bcm63xx_wdt_shutdown(struct platform_device *pdev) +{ + bcm63xx_wdt_pause(); +} + +static struct platform_driver bcm63xx_wdt_driver = { + .probe = bcm63xx_wdt_probe, + .remove = bcm63xx_wdt_remove, + .shutdown = bcm63xx_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "bcm63xx-wdt", + } +}; + +module_platform_driver(bcm63xx_wdt_driver); + +MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:bcm63xx-wdt"); diff --git a/drivers/watchdog/bcm_kona_wdt.c b/drivers/watchdog/bcm_kona_wdt.c new file mode 100644 index 00000000000..9c248099f4a --- /dev/null +++ b/drivers/watchdog/bcm_kona_wdt.c @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2013 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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_wdt_dbg_show(struct seq_file *s, void *data) +{ + int ctl_val, cur_val, ret; + unsigned long flags; + struct bcm_kona_wdt *wdt = s->private; + + if (!wdt) + return seq_puts(s, "No device pointer\n"); + + 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) { + ret = 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); + ret = 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 ret; +} + +static int bcm_kona_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, bcm_kona_wdt_dbg_show, inode->i_private); +} + +static const struct file_operations bcm_kona_dbg_operations = { + .open = bcm_kona_dbg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +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); + if (IS_ERR_OR_NULL(dir)) + return; + + if (debugfs_create_file("info", S_IFREG | S_IRUGO, dir, wdt, + &bcm_kona_dbg_operations)) + wdt->debugfs = dir; + else + debugfs_remove_recursive(dir); +} + +static void bcm_kona_wdt_debug_exit(struct platform_device *pdev) +{ + struct bcm_kona_wdt *wdt = platform_get_drvdata(pdev); + + if (wdt && wdt->debugfs) { + debugfs_remove_recursive(wdt->debugfs); + wdt->debugfs = NULL; + } +} + +#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 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 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 void bcm_kona_wdt_shutdown(struct platform_device *pdev) +{ + bcm_kona_wdt_stop(&bcm_kona_wdt_wdd); +} + +static int bcm_kona_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_kona_wdt *wdt; + struct resource *res; + int ret; + + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->base = devm_ioremap_resource(dev, res); + if (IS_ERR(wdt->base)) + return -ENODEV; + + 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; + } + + spin_lock_init(&wdt->lock); + platform_set_drvdata(pdev, wdt); + watchdog_set_drvdata(&bcm_kona_wdt_wdd, wdt); + + ret = bcm_kona_wdt_set_timeout_reg(&bcm_kona_wdt_wdd, 0); + if (ret) { + dev_err(dev, "Failed set watchdog timeout"); + return ret; + } + + ret = watchdog_register_device(&bcm_kona_wdt_wdd); + if (ret) { + dev_err(dev, "Failed to register watchdog device"); + 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); + bcm_kona_wdt_shutdown(pdev); + watchdog_unregister_device(&bcm_kona_wdt_wdd); + 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, + .owner = THIS_MODULE, + .of_match_table = bcm_kona_wdt_of_match, + }, + .probe = bcm_kona_wdt_probe, + .remove = bcm_kona_wdt_remove, + .shutdown = bcm_kona_wdt_shutdown, +}; + +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/bfin_wdt.c b/drivers/watchdog/bfin_wdt.c index 1237113dc14..a3b6a5b30f9 100644 --- a/drivers/watchdog/bfin_wdt.c +++ b/drivers/watchdog/bfin_wdt.c @@ -1,17 +1,18 @@ /* * Blackfin On-Chip Watchdog Driver - * Supports BF53[123]/BF53[467]/BF54[2489]/BF561 * * Originally based on softdog.c - * Copyright 2006-2007 Analog Devices Inc. + * Copyright 2006-2010 Analog Devices Inc. * Copyright 2006-2007 Michele d'Amico - * Copyright 1996 Alan Cox <alan@redhat.com> + * Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk> * * Enter bugs at http://blackfin.uclinux.org/ * * Licensed under the GPL-2 or later. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/platform_device.h> #include <linux/module.h> #include <linux/moduleparam.h> @@ -20,19 +21,17 @@ #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/fs.h> -#include <linux/notifier.h> -#include <linux/reboot.h> #include <linux/init.h> #include <linux/interrupt.h> +#include <linux/uaccess.h> #include <asm/blackfin.h> -#include <asm/uaccess.h> +#include <asm/bfin_watchdog.h> -#define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) +#define stamp(fmt, args...) \ + pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) #define stampit() stamp("here i am") -#define pr_init(fmt, args...) ({ static const __initdata char __fmt[] = fmt; printk(__fmt, ## args); }) #define WATCHDOG_NAME "bfin-wdt" -#define PFX WATCHDOG_NAME ": " /* The BF561 has two watchdogs (one per core), but since Linux * only runs on core A, we'll just work with that one. @@ -46,30 +45,12 @@ # define bfin_write_WDOG_STAT(x) bfin_write_WDOGA_STAT(x) #endif -/* Bit in SWRST that indicates boot caused by watchdog */ -#define SWRST_RESET_WDOG 0x4000 - -/* Bit in WDOG_CTL that indicates watchdog has expired (WDR0) */ -#define WDOG_EXPIRED 0x8000 - -/* Masks for WDEV field in WDOG_CTL register */ -#define ICTL_RESET 0x0 -#define ICTL_NMI 0x2 -#define ICTL_GPI 0x4 -#define ICTL_NONE 0x6 -#define ICTL_MASK 0x6 - -/* Masks for WDEN field in WDOG_CTL register */ -#define WDEN_MASK 0x0FF0 -#define WDEN_ENABLE 0x0000 -#define WDEN_DISABLE 0x0AD0 - /* some defaults */ #define WATCHDOG_TIMEOUT 20 static unsigned int timeout = WATCHDOG_TIMEOUT; -static int nowayout = WATCHDOG_NOWAYOUT; -static struct watchdog_info bfin_wdt_info; +static bool nowayout = WATCHDOG_NOWAYOUT; +static const struct watchdog_info bfin_wdt_info; static unsigned long open_check; static char expect_close; static DEFINE_SPINLOCK(bfin_wdt_spinlock); @@ -77,7 +58,7 @@ static DEFINE_SPINLOCK(bfin_wdt_spinlock); /** * bfin_wdt_keepalive - Keep the Userspace Watchdog Alive * - * The Userspace watchdog got a KeepAlive: schedule the next timeout. + * The Userspace watchdog got a KeepAlive: schedule the next timeout. */ static int bfin_wdt_keepalive(void) { @@ -131,14 +112,16 @@ static int bfin_wdt_running(void) */ static int bfin_wdt_set_timeout(unsigned long t) { - u32 cnt; + u32 cnt, max_t, sclk; unsigned long flags; - stampit(); + sclk = get_sclk(); + max_t = -1 / sclk; + cnt = t * sclk; + stamp("maxtimeout=%us newtimeout=%lus (cnt=%#x)", max_t, t, cnt); - cnt = t * get_sclk(); - if (cnt < get_sclk()) { - printk(KERN_WARNING PFX "timeout value is too large\n"); + if (t > max_t) { + pr_warn("timeout value is too large\n"); return -EINVAL; } @@ -147,7 +130,8 @@ static int bfin_wdt_set_timeout(unsigned long t) int run = bfin_wdt_running(); bfin_wdt_stop(); bfin_write_WDOG_CNT(cnt); - if (run) bfin_wdt_start(); + if (run) + bfin_wdt_start(); } spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); @@ -190,16 +174,14 @@ static int bfin_wdt_release(struct inode *inode, struct file *file) { stampit(); - if (expect_close == 42) { + if (expect_close == 42) bfin_wdt_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); bfin_wdt_keepalive(); } - expect_close = 0; clear_bit(0, &open_check); - return 0; } @@ -213,7 +195,7 @@ static int bfin_wdt_release(struct inode *inode, struct file *file) * Pings the watchdog on write. */ static ssize_t bfin_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) + size_t len, loff_t *ppos) { stampit(); @@ -240,7 +222,6 @@ static ssize_t bfin_wdt_write(struct file *file, const char __user *data, /** * bfin_wdt_ioctl - Query Device - * @inode: inode of device * @file: file handle of device * @cmd: watchdog command * @arg: argument @@ -248,8 +229,8 @@ static ssize_t bfin_wdt_write(struct file *file, const char __user *data, * Query basic information from the device or ping it, as outlined by the * watchdog API. */ -static int bfin_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long bfin_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; @@ -257,80 +238,50 @@ static int bfin_wdt_ioctl(struct inode *inode, struct file *file, stampit(); switch (cmd) { - default: - return -ENOTTY; - - case WDIOC_GETSUPPORT: - if (copy_to_user(argp, &bfin_wdt_info, sizeof(bfin_wdt_info))) - return -EFAULT; - else - return 0; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(!!(_bfin_swrst & SWRST_RESET_WDOG), p); - - case WDIOC_KEEPALIVE: - bfin_wdt_keepalive(); + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &bfin_wdt_info, sizeof(bfin_wdt_info))) + return -EFAULT; + else return 0; - - case WDIOC_SETTIMEOUT: { - int new_timeout; - - if (get_user(new_timeout, p)) - return -EFAULT; - - if (bfin_wdt_set_timeout(new_timeout)) - return -EINVAL; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(!!(_bfin_swrst & SWRST_RESET_WDOG), p); + case WDIOC_SETOPTIONS: { + unsigned long flags; + int options, ret = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + spin_lock_irqsave(&bfin_wdt_spinlock, flags); + if (options & WDIOS_DISABLECARD) { + bfin_wdt_stop(); + ret = 0; } - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - - case WDIOC_SETOPTIONS: { - unsigned long flags; - int options, ret = -EINVAL; - - if (get_user(options, p)) - return -EFAULT; - - spin_lock_irqsave(&bfin_wdt_spinlock, flags); - - if (options & WDIOS_DISABLECARD) { - bfin_wdt_stop(); - ret = 0; - } - - if (options & WDIOS_ENABLECARD) { - bfin_wdt_start(); - ret = 0; - } - - spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); - - return ret; + if (options & WDIOS_ENABLECARD) { + bfin_wdt_start(); + ret = 0; } + spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); + return ret; + } + case WDIOC_KEEPALIVE: + bfin_wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; + if (bfin_wdt_set_timeout(new_timeout)) + return -EINVAL; + } + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; } -} - -/** - * bfin_wdt_notify_sys - Notifier Handler - * @this: notifier block - * @code: notifier event - * @unused: unused - * - * Handles specific events, such as turning off the watchdog during a - * shutdown event. - */ -static int bfin_wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - stampit(); - - if (code == SYS_DOWN || code == SYS_HALT) - bfin_wdt_stop(); - - return NOTIFY_DONE; } #ifdef CONFIG_PM @@ -377,27 +328,13 @@ static int bfin_wdt_resume(struct platform_device *pdev) # define bfin_wdt_resume NULL #endif -static struct platform_device bfin_wdt_device = { - .name = WATCHDOG_NAME, - .id = -1, -}; - -static struct platform_driver bfin_wdt_driver = { - .driver = { - .name = WATCHDOG_NAME, - .owner = THIS_MODULE, - }, - .suspend = bfin_wdt_suspend, - .resume = bfin_wdt_resume, -}; - static const struct file_operations bfin_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = bfin_wdt_write, - .ioctl = bfin_wdt_ioctl, - .open = bfin_wdt_open, - .release = bfin_wdt_release, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = bfin_wdt_write, + .unlocked_ioctl = bfin_wdt_ioctl, + .open = bfin_wdt_open, + .release = bfin_wdt_release, }; static struct miscdevice bfin_wdt_miscdev = { @@ -406,22 +343,79 @@ static struct miscdevice bfin_wdt_miscdev = { .fops = &bfin_wdt_fops, }; -static struct watchdog_info bfin_wdt_info = { +static const struct watchdog_info bfin_wdt_info = { .identity = "Blackfin Watchdog", .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, }; -static struct notifier_block bfin_wdt_notifier = { - .notifier_call = bfin_wdt_notify_sys, +/** + * bfin_wdt_probe - Initialize module + * + * Registers the misc device. Actual device + * initialization is handled by bfin_wdt_open(). + */ +static int bfin_wdt_probe(struct platform_device *pdev) +{ + int ret; + + ret = misc_register(&bfin_wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + return ret; + } + + pr_info("initialized: timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; +} + +/** + * bfin_wdt_remove - Initialize module + * + * Unregisters the misc device. Actual device + * deinitialization is handled by bfin_wdt_close(). + */ +static int bfin_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&bfin_wdt_miscdev); + return 0; +} + +/** + * bfin_wdt_shutdown - Soft Shutdown Handler + * + * Handles the soft shutdown event. + */ +static void bfin_wdt_shutdown(struct platform_device *pdev) +{ + stampit(); + + bfin_wdt_stop(); +} + +static struct platform_device *bfin_wdt_device; + +static struct platform_driver bfin_wdt_driver = { + .probe = bfin_wdt_probe, + .remove = bfin_wdt_remove, + .shutdown = bfin_wdt_shutdown, + .suspend = bfin_wdt_suspend, + .resume = bfin_wdt_resume, + .driver = { + .name = WATCHDOG_NAME, + .owner = THIS_MODULE, + }, }; /** * bfin_wdt_init - Initialize module * - * Registers the device and notifier handler. Actual device - * initialization is handled by bfin_wdt_open(). + * Checks the module params and registers the platform device & driver. + * Real work is in the platform probe function. */ static int __init bfin_wdt_init(void) { @@ -436,44 +430,33 @@ static int __init bfin_wdt_init(void) /* Since this is an on-chip device and needs no board-specific * resources, we'll handle all the platform device stuff here. */ - ret = platform_device_register(&bfin_wdt_device); - if (ret) - return ret; - - ret = platform_driver_probe(&bfin_wdt_driver, NULL); - if (ret) - return ret; - - ret = register_reboot_notifier(&bfin_wdt_notifier); + ret = platform_driver_register(&bfin_wdt_driver); if (ret) { - pr_init(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); + pr_err("unable to register driver\n"); return ret; } - ret = misc_register(&bfin_wdt_miscdev); - if (ret) { - pr_init(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - unregister_reboot_notifier(&bfin_wdt_notifier); - return ret; + bfin_wdt_device = platform_device_register_simple(WATCHDOG_NAME, + -1, NULL, 0); + if (IS_ERR(bfin_wdt_device)) { + pr_err("unable to register device\n"); + platform_driver_unregister(&bfin_wdt_driver); + return PTR_ERR(bfin_wdt_device); } - pr_init(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n", - timeout, nowayout); - return 0; } /** * bfin_wdt_exit - Deinitialize module * - * Unregisters the device and notifier handler. Actual device - * deinitialization is handled by bfin_wdt_close(). + * Back out the platform device & driver steps. Real work is in the + * platform remove function. */ static void __exit bfin_wdt_exit(void) { - misc_deregister(&bfin_wdt_miscdev); - unregister_reboot_notifier(&bfin_wdt_notifier); + platform_device_unregister(bfin_wdt_device); + platform_driver_unregister(&bfin_wdt_driver); } module_init(bfin_wdt_init); @@ -482,10 +465,13 @@ module_exit(bfin_wdt_exit); MODULE_AUTHOR("Michele d'Amico, Mike Frysinger <vapier@gentoo.org>"); MODULE_DESCRIPTION("Blackfin Watchdog Device Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); module_param(timeout, uint, 0); -MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=((2^32)/SCLK), default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); - -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=((2^32)/SCLK), default=" + __MODULE_STRING(WATCHDOG_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/booke_wdt.c b/drivers/watchdog/booke_wdt.c index d362f5bf658..08a785398ea 100644 --- a/drivers/watchdog/booke_wdt.c +++ b/drivers/watchdog/booke_wdt.c @@ -1,12 +1,10 @@ /* - * drivers/char/watchdog/booke_wdt.c - * * Watchdog timer for PowerPC Book-E systems * * Author: Matthew McClintock * Maintainer: Kumar Gala <galak@kernel.crashing.org> * - * Copyright 2005 Freescale Semiconductor Inc. + * Copyright 2005, 2008, 2010-2011 Freescale Semiconductor Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -14,15 +12,15 @@ * option) any later version. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> -#include <linux/fs.h> -#include <linux/miscdevice.h> -#include <linux/notifier.h> +#include <linux/smp.h> #include <linux/watchdog.h> #include <asm/reg_booke.h> -#include <asm/uaccess.h> -#include <asm/system.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. @@ -32,163 +30,234 @@ * occur, and the final time the board will reset. */ -#ifdef CONFIG_FSL_BOOKE -#define WDT_PERIOD_DEFAULT 63 /* Ex. wdt_period=28 bus=333Mhz , reset=~40sec */ -#else -#define WDT_PERIOD_DEFAULT 3 /* Refer to the PPC40x and PPC4xx manuals */ -#endif /* for timing information */ - -u32 booke_wdt_enabled = 0; -u32 booke_wdt_period = WDT_PERIOD_DEFAULT; +u32 booke_wdt_enabled; +u32 booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT; -#ifdef CONFIG_FSL_BOOKE -#define WDTP(x) ((((63-x)&0x3)<<30)|(((63-x)&0x3c)<<15)) +#ifdef CONFIG_PPC_FSL_BOOK3E +#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 -/* - * booke_wdt_ping: +/* Checks wdt=x and wdt_period=xx command-line option */ +notrace int __init early_parse_wdt(char *p) +{ + if (p && strncmp(p, "0", 1) != 0) + booke_wdt_enabled = 1; + + return 0; +} +early_param("wdt", early_parse_wdt); + +int __init early_parse_wdt_period(char *p) +{ + unsigned long ret; + if (p) { + if (!kstrtol(p, 0, &ret)) + booke_wdt_period = ret; + } + + return 0; +} +early_param("wdt_period", early_parse_wdt_period); + +#ifdef CONFIG_PPC_FSL_BOOK3E + +/* 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 __inline__ void booke_wdt_ping(void) +static unsigned long long period_to_sec(unsigned int period) { - mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); + 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; } /* - * booke_wdt_enable: + * 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 + * and a parameter of 2 secs, then this procedure will return a value of 38. */ -static __inline__ void booke_wdt_enable(void) +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_FSL_BOOK3E */ + +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_FSL_BOOK3E */ + +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(); + __booke_wdt_ping(NULL); val = mfspr(SPRN_TCR); - val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(booke_wdt_period)); + val &= ~WDTP_MASK; + val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(sec_to_period(wdog->timeout))); mtspr(SPRN_TCR, val); } -/* - * booke_wdt_write: +/** + * 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 ssize_t booke_wdt_write (struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static void __booke_wdt_disable(void *data) { - booke_wdt_ping(); - return count; + 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 struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .firmware_version = 0, - .identity = "PowerPC Book-E Watchdog", -}; +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); -/* - * booke_wdt_ioctl: - */ -static int booke_wdt_ioctl (struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - u32 tmp = 0; - u32 __user *p = (u32 __user *)arg; - - switch (cmd) { - case WDIOC_GETSUPPORT: - if (copy_to_user ((struct watchdog_info __user *) arg, &ident, - sizeof(struct watchdog_info))) - return -EFAULT; - case WDIOC_GETSTATUS: - return put_user(ident.options, p); - case WDIOC_GETBOOTSTATUS: - /* XXX: something is clearing TSR */ - tmp = mfspr(SPRN_TSR) & TSR_WRS(3); - /* returns 1 if last reset was caused by the WDT */ - return (tmp ? 1 : 0); - case WDIOC_KEEPALIVE: - booke_wdt_ping(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(booke_wdt_period, p)) - return -EFAULT; - mtspr(SPRN_TCR, (mfspr(SPRN_TCR)&~WDTP(0))|WDTP(booke_wdt_period)); - return 0; - case WDIOC_GETTIMEOUT: - return put_user(booke_wdt_period, p); - case WDIOC_SETOPTIONS: - if (get_user(tmp, p)) - return -EINVAL; - if (tmp == WDIOS_ENABLECARD) { - booke_wdt_ping(); - break; - } else - return -EINVAL; - return 0; - default: - return -ENOTTY; - } + 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; } -/* - * booke_wdt_open: - */ -static int booke_wdt_open (struct inode *inode, struct file *file) + +static int booke_wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) { - if (booke_wdt_enabled == 0) { - booke_wdt_enabled = 1; - booke_wdt_enable(); - printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n", - booke_wdt_period); - } + if (timeout > MAX_WDT_TIMEOUT) + return -EINVAL; + wdt_dev->timeout = timeout; + booke_wdt_set(wdt_dev); - return nonseekable_open(inode, file); + return 0; } -static const struct file_operations booke_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = booke_wdt_write, - .ioctl = booke_wdt_ioctl, - .open = booke_wdt_open, +static struct watchdog_info booke_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "PowerPC Book-E Watchdog", +}; + +static 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 miscdevice booke_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &booke_wdt_fops, +static struct watchdog_device booke_wdt_dev = { + .info = &booke_wdt_info, + .ops = &booke_wdt_ops, + .min_timeout = 1, + .max_timeout = 0xFFFF }; static void __exit booke_wdt_exit(void) { - misc_deregister(&booke_wdt_miscdev); + watchdog_unregister_device(&booke_wdt_dev); } -/* - * booke_wdt_init: - */ static int __init booke_wdt_init(void) { int ret = 0; + bool nowayout = WATCHDOG_NOWAYOUT; - printk (KERN_INFO "PowerPC Book-E Watchdog Timer Loaded\n"); - ident.firmware_version = cur_cpu_spec->pvr_value; + 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); + if (booke_wdt_enabled) + booke_wdt_start(&booke_wdt_dev); - ret = misc_register(&booke_wdt_miscdev); - if (ret) { - printk (KERN_CRIT "Cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - return ret; - } - - if (booke_wdt_enabled == 1) { - printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n", - booke_wdt_period); - booke_wdt_enable(); - } + ret = watchdog_register_device(&booke_wdt_dev); return ret; } -device_initcall(booke_wdt_init); + +module_init(booke_wdt_init); +module_exit(booke_wdt_exit); + +MODULE_DESCRIPTION("PowerPC Book-E watchdog driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c new file mode 100644 index 00000000000..4bd070f524e --- /dev/null +++ b/drivers/watchdog/coh901327_wdt.c @@ -0,0 +1,469 @@ +/* + * coh901327_wdt.c + * + * Copyright (C) 2008-2009 ST-Ericsson AB + * License terms: GNU General Public License (GPL) version 2 + * Watchdog driver for the ST-Ericsson AB COH 901 327 IP core + * Author: Linus Walleij <linus.walleij@stericsson.com> + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> + +#define DRV_NAME "WDOG COH 901 327" + +/* + * COH 901 327 register definitions + */ + +/* WDOG_FEED Register 32bit (-/W) */ +#define U300_WDOG_FR 0x00 +#define U300_WDOG_FR_FEED_RESTART_TIMER 0xFEEDU +/* WDOG_TIMEOUT Register 32bit (R/W) */ +#define U300_WDOG_TR 0x04 +#define U300_WDOG_TR_TIMEOUT_MASK 0x7FFFU +/* WDOG_DISABLE1 Register 32bit (-/W) */ +#define U300_WDOG_D1R 0x08 +#define U300_WDOG_D1R_DISABLE1_DISABLE_TIMER 0x2BADU +/* WDOG_DISABLE2 Register 32bit (R/W) */ +#define U300_WDOG_D2R 0x0C +#define U300_WDOG_D2R_DISABLE2_DISABLE_TIMER 0xCAFEU +#define U300_WDOG_D2R_DISABLE_STATUS_DISABLED 0xDABEU +#define U300_WDOG_D2R_DISABLE_STATUS_ENABLED 0x0000U +/* WDOG_STATUS Register 32bit (R/W) */ +#define U300_WDOG_SR 0x10 +#define U300_WDOG_SR_STATUS_TIMED_OUT 0xCFE8U +#define U300_WDOG_SR_STATUS_NORMAL 0x0000U +#define U300_WDOG_SR_RESET_STATUS_RESET 0xE8B4U +/* WDOG_COUNT Register 32bit (R/-) */ +#define U300_WDOG_CR 0x14 +#define U300_WDOG_CR_VALID_IND 0x8000U +#define U300_WDOG_CR_VALID_STABLE 0x0000U +#define U300_WDOG_CR_COUNT_VALUE_MASK 0x7FFFU +/* WDOG_JTAGOVR Register 32bit (R/W) */ +#define U300_WDOG_JOR 0x18 +#define U300_WDOG_JOR_JTAG_MODE_IND 0x0002U +#define U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE 0x0001U +/* WDOG_RESTART Register 32bit (-/W) */ +#define U300_WDOG_RR 0x1C +#define U300_WDOG_RR_RESTART_VALUE_RESUME 0xACEDU +/* WDOG_IRQ_EVENT Register 32bit (R/W) */ +#define U300_WDOG_IER 0x20 +#define U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND 0x0001U +#define U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE 0x0001U +/* WDOG_IRQ_MASK Register 32bit (R/W) */ +#define U300_WDOG_IMR 0x24 +#define U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE 0x0001U +/* WDOG_IRQ_FORCE Register 32bit (R/W) */ +#define U300_WDOG_IFR 0x28 +#define U300_WDOG_IFR_WILL_BARK_IRQ_FORCE_ENABLE 0x0001U + +/* Default timeout in seconds = 1 minute */ +static unsigned int margin = 60; +static resource_size_t phybase; +static resource_size_t physize; +static int irq; +static void __iomem *virtbase; +static struct device *parent; + +/* + * The watchdog block is of course always clocked, the + * clk_enable()/clk_disable() calls are mainly for performing reference + * counting higher up in the clock hierarchy. + */ +static struct clk *clk; + +/* + * Enabling and disabling functions. + */ +static void coh901327_enable(u16 timeout) +{ + u16 val; + unsigned long freq; + unsigned long delay_ns; + + clk_enable(clk); + /* Restart timer if it is disabled */ + val = readw(virtbase + U300_WDOG_D2R); + if (val == U300_WDOG_D2R_DISABLE_STATUS_DISABLED) + writew(U300_WDOG_RR_RESTART_VALUE_RESUME, + virtbase + U300_WDOG_RR); + /* Acknowledge any pending interrupt so it doesn't just fire off */ + writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, + virtbase + U300_WDOG_IER); + /* + * The interrupt is cleared in the 32 kHz clock domain. + * Wait 3 32 kHz cycles for it to take effect + */ + freq = clk_get_rate(clk); + delay_ns = DIV_ROUND_UP(1000000000, freq); /* Freq to ns and round up */ + delay_ns = 3 * delay_ns; /* Wait 3 cycles */ + ndelay(delay_ns); + /* Enable the watchdog interrupt */ + writew(U300_WDOG_IMR_WILL_BARK_IRQ_ENABLE, virtbase + U300_WDOG_IMR); + /* Activate the watchdog timer */ + writew(timeout, virtbase + U300_WDOG_TR); + /* Start the watchdog timer */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, virtbase + U300_WDOG_FR); + /* + * Extra read so that this change propagate in the watchdog. + */ + (void) readw(virtbase + U300_WDOG_CR); + val = readw(virtbase + U300_WDOG_D2R); + clk_disable(clk); + if (val != U300_WDOG_D2R_DISABLE_STATUS_ENABLED) + dev_err(parent, + "%s(): watchdog not enabled! D2R value %04x\n", + __func__, val); +} + +static void coh901327_disable(void) +{ + u16 val; + + clk_enable(clk); + /* Disable the watchdog interrupt if it is active */ + writew(0x0000U, virtbase + U300_WDOG_IMR); + /* If the watchdog is currently enabled, attempt to disable it */ + val = readw(virtbase + U300_WDOG_D2R); + if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) { + writew(U300_WDOG_D1R_DISABLE1_DISABLE_TIMER, + virtbase + U300_WDOG_D1R); + writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, + virtbase + U300_WDOG_D2R); + /* Write this twice (else problems occur) */ + writew(U300_WDOG_D2R_DISABLE2_DISABLE_TIMER, + virtbase + U300_WDOG_D2R); + } + val = readw(virtbase + U300_WDOG_D2R); + clk_disable(clk); + if (val != U300_WDOG_D2R_DISABLE_STATUS_DISABLED) + dev_err(parent, + "%s(): watchdog not disabled! D2R value %04x\n", + __func__, val); +} + +static int coh901327_start(struct watchdog_device *wdt_dev) +{ + coh901327_enable(wdt_dev->timeout * 100); + return 0; +} + +static int coh901327_stop(struct watchdog_device *wdt_dev) +{ + coh901327_disable(); + return 0; +} + +static int coh901327_ping(struct watchdog_device *wdd) +{ + clk_enable(clk); + /* Feed the watchdog */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + clk_disable(clk); + return 0; +} + +static int coh901327_settimeout(struct watchdog_device *wdt_dev, + unsigned int time) +{ + wdt_dev->timeout = time; + clk_enable(clk); + /* Set new timeout value */ + writew(time * 100, virtbase + U300_WDOG_TR); + /* Feed the dog */ + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + clk_disable(clk); + return 0; +} + +static unsigned int coh901327_gettimeleft(struct watchdog_device *wdt_dev) +{ + u16 val; + + clk_enable(clk); + /* Read repeatedly until the value is stable! */ + val = readw(virtbase + U300_WDOG_CR); + while (val & U300_WDOG_CR_VALID_IND) + val = readw(virtbase + U300_WDOG_CR); + val &= U300_WDOG_CR_COUNT_VALUE_MASK; + clk_disable(clk); + if (val != 0) + val /= 100; + + return val; +} + +/* + * This interrupt occurs 10 ms before the watchdog WILL bark. + */ +static irqreturn_t coh901327_interrupt(int irq, void *data) +{ + u16 val; + + /* + * Ack IRQ? If this occurs we're FUBAR anyway, so + * just acknowledge, disable the interrupt and await the imminent end. + * If you at some point need a host of callbacks to be called + * when the system is about to watchdog-reset, add them here! + * + * NOTE: on future versions of this IP-block, it will be possible + * to prevent a watchdog reset by feeding the watchdog at this + * point. + */ + clk_enable(clk); + val = readw(virtbase + U300_WDOG_IER); + if (val == U300_WDOG_IER_WILL_BARK_IRQ_EVENT_IND) + writew(U300_WDOG_IER_WILL_BARK_IRQ_ACK_ENABLE, + virtbase + U300_WDOG_IER); + writew(0x0000U, virtbase + U300_WDOG_IMR); + clk_disable(clk); + dev_crit(parent, "watchdog is barking!\n"); + return IRQ_HANDLED; +} + +static const struct watchdog_info coh901327_ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = DRV_NAME, +}; + +static struct watchdog_ops coh901327_ops = { + .owner = THIS_MODULE, + .start = coh901327_start, + .stop = coh901327_stop, + .ping = coh901327_ping, + .set_timeout = coh901327_settimeout, + .get_timeleft = coh901327_gettimeleft, +}; + +static struct watchdog_device coh901327_wdt = { + .info = &coh901327_ident, + .ops = &coh901327_ops, + /* + * Max timeout is 327 since the 10ms + * timeout register is max + * 0x7FFF = 327670ms ~= 327s. + */ + .min_timeout = 0, + .max_timeout = 327, +}; + +static int __exit coh901327_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&coh901327_wdt); + coh901327_disable(); + free_irq(irq, pdev); + clk_unprepare(clk); + clk_put(clk); + iounmap(virtbase); + release_mem_region(phybase, physize); + return 0; +} + +static int __init coh901327_probe(struct platform_device *pdev) +{ + int ret; + u16 val; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + parent = &pdev->dev; + physize = resource_size(res); + phybase = res->start; + + if (request_mem_region(phybase, physize, DRV_NAME) == NULL) { + ret = -EBUSY; + goto out; + } + + virtbase = ioremap(phybase, physize); + if (!virtbase) { + ret = -ENOMEM; + goto out_no_remap; + } + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&pdev->dev, "could not get clock\n"); + goto out_no_clk; + } + ret = clk_prepare_enable(clk); + if (ret) { + dev_err(&pdev->dev, "could not prepare and enable clock\n"); + goto out_no_clk_enable; + } + + val = readw(virtbase + U300_WDOG_SR); + switch (val) { + case U300_WDOG_SR_STATUS_TIMED_OUT: + dev_info(&pdev->dev, + "watchdog timed out since last chip reset!\n"); + coh901327_wdt.bootstatus |= WDIOF_CARDRESET; + /* Status will be cleared below */ + break; + case U300_WDOG_SR_STATUS_NORMAL: + dev_info(&pdev->dev, + "in normal status, no timeouts have occurred.\n"); + break; + default: + dev_info(&pdev->dev, + "contains an illegal status code (%08x)\n", val); + break; + } + + val = readw(virtbase + U300_WDOG_D2R); + switch (val) { + case U300_WDOG_D2R_DISABLE_STATUS_DISABLED: + dev_info(&pdev->dev, "currently disabled.\n"); + break; + case U300_WDOG_D2R_DISABLE_STATUS_ENABLED: + dev_info(&pdev->dev, + "currently enabled! (disabling it now)\n"); + coh901327_disable(); + break; + default: + dev_err(&pdev->dev, + "contains an illegal enable/disable code (%08x)\n", + val); + break; + } + + /* Reset the watchdog */ + writew(U300_WDOG_SR_RESET_STATUS_RESET, virtbase + U300_WDOG_SR); + + irq = platform_get_irq(pdev, 0); + if (request_irq(irq, coh901327_interrupt, 0, + DRV_NAME " Bark", pdev)) { + ret = -EIO; + goto out_no_irq; + } + + clk_disable(clk); + + ret = watchdog_init_timeout(&coh901327_wdt, margin, &pdev->dev); + if (ret < 0) + coh901327_wdt.timeout = 60; + + ret = watchdog_register_device(&coh901327_wdt); + if (ret == 0) + dev_info(&pdev->dev, + "initialized. timer margin=%d sec\n", margin); + else + goto out_no_wdog; + + return 0; + +out_no_wdog: + free_irq(irq, pdev); +out_no_irq: + clk_disable_unprepare(clk); +out_no_clk_enable: + clk_put(clk); +out_no_clk: + iounmap(virtbase); +out_no_remap: + release_mem_region(phybase, SZ_4K); +out: + return ret; +} + +#ifdef CONFIG_PM + +static u16 wdogenablestore; +static u16 irqmaskstore; + +static int coh901327_suspend(struct platform_device *pdev, pm_message_t state) +{ + irqmaskstore = readw(virtbase + U300_WDOG_IMR) & 0x0001U; + wdogenablestore = readw(virtbase + U300_WDOG_D2R); + /* If watchdog is on, disable it here and now */ + if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) + coh901327_disable(); + return 0; +} + +static int coh901327_resume(struct platform_device *pdev) +{ + /* Restore the watchdog interrupt */ + writew(irqmaskstore, virtbase + U300_WDOG_IMR); + if (wdogenablestore == U300_WDOG_D2R_DISABLE_STATUS_ENABLED) { + /* Restart the watchdog timer */ + writew(U300_WDOG_RR_RESTART_VALUE_RESUME, + virtbase + U300_WDOG_RR); + writew(U300_WDOG_FR_FEED_RESTART_TIMER, + virtbase + U300_WDOG_FR); + } + return 0; +} +#else +#define coh901327_suspend NULL +#define coh901327_resume NULL +#endif + +/* + * Mistreating the watchdog is the only way to perform a software reset of the + * system on EMP platforms. So we implement this and export a symbol for it. + */ +void coh901327_watchdog_reset(void) +{ + /* Enable even if on JTAG too */ + writew(U300_WDOG_JOR_JTAG_WATCHDOG_ENABLE, + virtbase + U300_WDOG_JOR); + /* + * Timeout = 5s, we have to wait for the watchdog reset to + * actually take place: the watchdog will be reloaded with the + * default value immediately, so we HAVE to reboot and get back + * into the kernel in 30s, or the device will reboot again! + * The boot loader will typically deactivate the watchdog, so we + * need time enough for the boot loader to get to the point of + * deactivating the watchdog before it is shut down by it. + * + * NOTE: on future versions of the watchdog, this restriction is + * gone: the watchdog will be reloaded with a default value (1 min) + * instead of last value, and you can conveniently set the watchdog + * timeout to 10ms (value = 1) without any problems. + */ + coh901327_enable(500); + /* Return and await doom */ +} + +static const struct of_device_id coh901327_dt_match[] = { + { .compatible = "stericsson,coh901327" }, + {}, +}; + +static struct platform_driver coh901327_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "coh901327_wdog", + .of_match_table = coh901327_dt_match, + }, + .remove = __exit_p(coh901327_remove), + .suspend = coh901327_suspend, + .resume = coh901327_resume, +}; + +module_platform_driver_probe(coh901327_driver, coh901327_probe); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("COH 901 327 Watchdog"); + +module_param(margin, uint, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:coh901327-watchdog"); diff --git a/drivers/watchdog/cpu5wdt.c b/drivers/watchdog/cpu5wdt.c index df72f90123d..6d03e8e30f8 100644 --- a/drivers/watchdog/cpu5wdt.c +++ b/drivers/watchdog/cpu5wdt.c @@ -19,27 +19,28 @@ * */ +#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/init.h> #include <linux/ioport.h> #include <linux/timer.h> #include <linux/completion.h> #include <linux/jiffies.h> -#include <asm/io.h> -#include <asm/uaccess.h> - +#include <linux/io.h> +#include <linux/uaccess.h> #include <linux/watchdog.h> /* adjustable parameters */ -static int verbose = 0; +static int verbose; static int port = 0x91; static int ticks = 10000; +static DEFINE_SPINLOCK(cpu5wdt_lock); #define PFX "cpu5wdt: " @@ -70,12 +71,13 @@ static struct { static void cpu5wdt_trigger(unsigned long unused) { - if ( verbose > 2 ) - printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks); + if (verbose > 2) + pr_debug("trigger at %i ticks\n", ticks); - if( cpu5wdt_device.running ) + if (cpu5wdt_device.running) ticks--; + spin_lock(&cpu5wdt_lock); /* keep watchdog alive */ outb(1, port + CPU5WDT_TRIGGER_REG); @@ -86,6 +88,7 @@ static void cpu5wdt_trigger(unsigned long unused) /* ticks doesn't matter anyway */ complete(&cpu5wdt_device.stop); } + spin_unlock(&cpu5wdt_lock); } @@ -93,14 +96,17 @@ static void cpu5wdt_reset(void) { ticks = cpu5wdt_device.default_ticks; - if ( verbose ) - printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks); + if (verbose) + pr_debug("reset (%i ticks)\n", (int) ticks); } static void cpu5wdt_start(void) { - if ( !cpu5wdt_device.queue ) { + 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); @@ -111,18 +117,20 @@ static void cpu5wdt_start(void) } /* if process dies, counter is not decremented */ cpu5wdt_device.running++; + spin_unlock_irqrestore(&cpu5wdt_lock, flags); } static int cpu5wdt_stop(void) { - if ( cpu5wdt_device.running ) - cpu5wdt_device.running = 0; + unsigned long flags; + spin_lock_irqsave(&cpu5wdt_lock, flags); + if (cpu5wdt_device.running) + cpu5wdt_device.running = 0; ticks = cpu5wdt_device.default_ticks; - - if ( verbose ) - printk(KERN_CRIT PFX "stop not possible\n"); - + spin_unlock_irqrestore(&cpu5wdt_lock, flags); + if (verbose) + pr_crit("stop not possible\n"); return -EIO; } @@ -130,9 +138,8 @@ static int cpu5wdt_stop(void) static int cpu5wdt_open(struct inode *inode, struct file *file) { - if ( test_and_set_bit(0, &cpu5wdt_device.inuse) ) + if (test_and_set_bit(0, &cpu5wdt_device.inuse)) return -EBUSY; - return nonseekable_open(inode, file); } @@ -142,67 +149,58 @@ static int cpu5wdt_release(struct inode *inode, struct file *file) return 0; } -static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = - { + static const struct watchdog_info ident = { .options = WDIOF_CARDRESET, .identity = "CPU5 WDT", }; - switch(cmd) { - case WDIOC_KEEPALIVE: - cpu5wdt_reset(); - break; - case WDIOC_GETSTATUS: - value = inb(port + CPU5WDT_STATUS_REG); - value = (value >> 2) & 1; - if ( copy_to_user(argp, &value, sizeof(int)) ) - return -EFAULT; - break; - case WDIOC_GETBOOTSTATUS: - if ( copy_to_user(argp, &value, sizeof(int)) ) - 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: - cpu5wdt_start(); - break; - case WDIOS_DISABLECARD: - return cpu5wdt_stop(); - default: - return -EINVAL; - } - break; - default: - return -ENOTTY; + 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) +static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { - if ( !count ) + if (!count) return -EIO; - cpu5wdt_reset(); - return count; } static const struct file_operations cpu5wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = cpu5wdt_ioctl, + .unlocked_ioctl = cpu5wdt_ioctl, .open = cpu5wdt_open, .write = cpu5wdt_write, .release = cpu5wdt_release, @@ -216,42 +214,39 @@ static struct miscdevice cpu5wdt_misc = { /* init/exit function */ -static int __devinit cpu5wdt_init(void) +static int cpu5wdt_init(void) { unsigned int val; int err; - if ( verbose ) - printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose); + if (verbose) + pr_debug("port=0x%x, verbose=%i\n", port, verbose); + + init_completion(&cpu5wdt_device.stop); + cpu5wdt_device.queue = 0; + setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0); + cpu5wdt_device.default_ticks = ticks; - if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) { - printk(KERN_ERR PFX "request_region failed\n"); + if (!request_region(port, CPU5WDT_EXTENT, PFX)) { + pr_err("request_region failed\n"); err = -EBUSY; goto no_port; } - if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) { - printk(KERN_ERR PFX "misc_register failed\n"); - goto no_misc; - } - /* watchdog reboot? */ val = inb(port + CPU5WDT_STATUS_REG); val = (val >> 2) & 1; - if ( !val ) - printk(KERN_INFO PFX "sorry, was my fault\n"); - - init_completion(&cpu5wdt_device.stop); - cpu5wdt_device.queue = 0; + if (!val) + pr_info("sorry, was my fault\n"); - clear_bit(0, &cpu5wdt_device.inuse); - - setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0); - - cpu5wdt_device.default_ticks = ticks; + err = misc_register(&cpu5wdt_misc); + if (err < 0) { + pr_err("misc_register failed\n"); + goto no_misc; + } - printk(KERN_INFO PFX "init success\n"); + pr_info("init success\n"); return 0; no_misc: @@ -260,16 +255,17 @@ no_port: return err; } -static int __devinit cpu5wdt_init_module(void) +static int cpu5wdt_init_module(void) { return cpu5wdt_init(); } -static void __devexit cpu5wdt_exit(void) +static void cpu5wdt_exit(void) { - if ( cpu5wdt_device.queue ) { + if (cpu5wdt_device.queue) { cpu5wdt_device.queue = 0; wait_for_completion(&cpu5wdt_device.stop); + del_timer(&cpu5wdt_device.timer); } misc_deregister(&cpu5wdt_misc); @@ -278,7 +274,7 @@ static void __devexit cpu5wdt_exit(void) } -static void __devexit cpu5wdt_exit_module(void) +static void cpu5wdt_exit_module(void) { cpu5wdt_exit(); } @@ -292,7 +288,6 @@ MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>"); MODULE_DESCRIPTION("sma cpu5 watchdog driver"); MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); module_param(port, int, 0); MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91"); diff --git a/drivers/watchdog/cpwd.c b/drivers/watchdog/cpwd.c new file mode 100644 index 00000000000..e55ed702209 --- /dev/null +++ b/drivers/watchdog/cpwd.c @@ -0,0 +1,689 @@ +/* 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/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"); +MODULE_SUPPORTED_DEVICE("watchdog"); + +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(unsigned long data) +{ + struct cpwd *p = (struct cpwd *) data; + 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((unsigned long) p); + } + } +} + +/* 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 nonseekable_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) +{ + int rval = -ENOIOCTLCMD; + + switch (cmd) { + /* solaris ioctls are specific to this driver */ + case WIOCSTART: + case WIOCSTOP: + case WIOCGSTAT: + mutex_lock(&cpwd_mutex); + rval = cpwd_ioctl(file, cmd, arg); + mutex_unlock(&cpwd_mutex); + break; + + /* everything else is handled by the generic compat layer */ + default: + break; + } + + return rval; +} + +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 = kzalloc(sizeof(*p), GFP_KERNEL); + err = -ENOMEM; + if (!p) { + pr_err("Unable to allocate struct cpwd\n"); + goto out; + } + + 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"); + goto out_free; + } + + options = of_find_node_by_path("/options"); + err = -ENODEV; + if (!options) { + 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); + + /* 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) { + init_timer(&cpwd_timer); + cpwd_timer.function = cpwd_brokentimer; + cpwd_timer.data = (unsigned long) p; + 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; + err = 0; + +out: + return err; + +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); + +out_free: + kfree(p); + goto out; +} + +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); + kfree(p); + + 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, + .owner = THIS_MODULE, + .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 00000000000..2e9589652e1 --- /dev/null +++ b/drivers/watchdog/da9052_wdt.c @@ -0,0 +1,245 @@ +/* + * System monitoring driver for DA9052 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: Anthony Olech <Anthony.Olech@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#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; + struct kref kref; + 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 void da9052_wdt_release_resources(struct kref *r) +{ +} + +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 void da9052_wdt_ref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_get(&driver_data->kref); +} + +static void da9052_wdt_unref(struct watchdog_device *wdt_dev) +{ + struct da9052_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_put(&driver_data->kref, da9052_wdt_release_resources); +} + +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) + goto err_strobe; + + /* + * FIXME: Reset the watchdog core, in general PMIC + * is supposed to do this + */ + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_WATCHDOG, 0 << 7); +err_strobe: + return ret; +} + +static 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, + .ref = da9052_wdt_ref, + .unref = da9052_wdt_unref, +}; + + +static int da9052_wdt_probe(struct platform_device *pdev) +{ + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + struct da9052_wdt_data *driver_data; + struct watchdog_device *da9052_wdt; + int ret; + + driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), + GFP_KERNEL); + if (!driver_data) { + ret = -ENOMEM; + goto err; + } + 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; + watchdog_set_drvdata(da9052_wdt, driver_data); + + kref_init(&driver_data->kref); + + ret = da9052_reg_update(da9052, DA9052_CONTROL_D_REG, + DA9052_CONTROLD_TWDSCALE, 0); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to disable watchdog bits, %d\n", + ret); + goto err; + } + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) { + dev_err(da9052->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, driver_data); +err: + return ret; +} + +static int da9052_wdt_remove(struct platform_device *pdev) +{ + struct da9052_wdt_data *driver_data = platform_get_drvdata(pdev); + + watchdog_unregister_device(&driver_data->wdt); + kref_put(&driver_data->kref, da9052_wdt_release_resources); + + return 0; +} + +static struct platform_driver da9052_wdt_driver = { + .probe = da9052_wdt_probe, + .remove = da9052_wdt_remove, + .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 00000000000..495089d8dbf --- /dev/null +++ b/drivers/watchdog/da9055_wdt.c @@ -0,0 +1,209 @@ +/* + * System monitoring driver for DA9055 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#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; + struct kref kref; +}; + +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 void da9055_wdt_release_resources(struct kref *r) +{ +} + +static void da9055_wdt_ref(struct watchdog_device *wdt_dev) +{ + struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_get(&driver_data->kref); +} + +static void da9055_wdt_unref(struct watchdog_device *wdt_dev) +{ + struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev); + + kref_put(&driver_data->kref, da9055_wdt_release_resources); +} + +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 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, + .ref = da9055_wdt_ref, + .unref = da9055_wdt_unref, +}; + +static int da9055_wdt_probe(struct platform_device *pdev) +{ + struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent); + struct da9055_wdt_data *driver_data; + struct watchdog_device *da9055_wdt; + int ret; + + driver_data = devm_kzalloc(&pdev->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; + watchdog_set_nowayout(da9055_wdt, nowayout); + watchdog_set_drvdata(da9055_wdt, driver_data); + + kref_init(&driver_data->kref); + + ret = da9055_wdt_stop(da9055_wdt); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to stop watchdog, %d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, driver_data); + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) + dev_err(da9055->dev, "watchdog_register_device() failed: %d\n", + ret); + +err: + return ret; +} + +static int da9055_wdt_remove(struct platform_device *pdev) +{ + struct da9055_wdt_data *driver_data = platform_get_drvdata(pdev); + + watchdog_unregister_device(&driver_data->wdt); + kref_put(&driver_data->kref, da9055_wdt_release_resources); + + return 0; +} + +static struct platform_driver da9055_wdt_driver = { + .probe = da9055_wdt_probe, + .remove = da9055_wdt_remove, + .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/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c index a61cbd48dc0..d09ad2254b5 100644 --- a/drivers/watchdog/davinci_wdt.c +++ b/drivers/watchdog/davinci_wdt.c @@ -3,7 +3,7 @@ * * Watchdog driver for DaVinci DM644x/DM646x processors * - * Copyright (C) 2006 Texas Instruments. + * Copyright (C) 2006-2013 Texas Instruments. * * 2007 (c) MontaVista Software, Inc. This file is licensed under * the terms of the GNU General Public License version 2. This program @@ -15,17 +15,12 @@ #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/platform_device.h> -#include <linux/spinlock.h> - -#include <asm/hardware.h> -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/err.h> #define MODULE_NAME "DAVINCI-WDT: " @@ -59,212 +54,178 @@ #define WDKEY_SEQ0 (0xa5c6 << 16) #define WDKEY_SEQ1 (0xda7e << 16) -static int heartbeat = DEFAULT_HEARTBEAT; - -static DEFINE_SPINLOCK(io_lock); -static unsigned long wdt_status; -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 -#define WDT_REGION_INITED 2 -#define WDT_DEVICE_INITED 3 - -static struct resource *wdt_mem; -static void __iomem *wdt_base; - -static void wdt_service(void) -{ - spin_lock(&io_lock); - - /* put watchdog in service state */ - davinci_writel(WDKEY_SEQ0, wdt_base + WDTCR); - /* put watchdog in active state */ - davinci_writel(WDKEY_SEQ1, wdt_base + WDTCR); +static int heartbeat; - spin_unlock(&io_lock); -} +/* + * 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 void wdt_enable(void) +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); - spin_lock(&io_lock); + wdt_freq = clk_get_rate(davinci_wdt->clk); /* disable, internal clock source */ - davinci_writel(0, wdt_base + TCR); + iowrite32(0, davinci_wdt->base + TCR); /* reset timer, set mode to 64-bit watchdog, and unreset */ - davinci_writel(0, wdt_base + TGCR); + iowrite32(0, davinci_wdt->base + TGCR); tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET; - davinci_writel(tgcr, wdt_base + TGCR); + iowrite32(tgcr, davinci_wdt->base + TGCR); /* clear counter regs */ - davinci_writel(0, wdt_base + TIM12); - davinci_writel(0, wdt_base + TIM34); + iowrite32(0, davinci_wdt->base + TIM12); + iowrite32(0, davinci_wdt->base + TIM34); /* set timeout period */ - timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) & 0xffffffff); - davinci_writel(timer_margin, wdt_base + PRD12); - timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) >> 32); - davinci_writel(timer_margin, wdt_base + PRD34); + 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 */ - davinci_writel(ENAMODE12_PERIODIC, wdt_base + TCR); + 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 */ - davinci_writel(WDKEY_SEQ0 | WDEN, wdt_base + WDTCR); + iowrite32(WDKEY_SEQ0 | WDEN, davinci_wdt->base + WDTCR); /* put watchdog in active state */ - davinci_writel(WDKEY_SEQ1 | WDEN, wdt_base + WDTCR); - - spin_unlock(&io_lock); + iowrite32(WDKEY_SEQ1 | WDEN, davinci_wdt->base + WDTCR); + return 0; } -static int davinci_wdt_open(struct inode *inode, struct file *file) +static int davinci_wdt_ping(struct watchdog_device *wdd) { - if (test_and_set_bit(WDT_IN_USE, &wdt_status)) - return -EBUSY; - - wdt_enable(); + struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd); - return nonseekable_open(inode, file); + /* 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 ssize_t -davinci_wdt_write(struct file *file, const char *data, size_t len, - loff_t *ppos) +static unsigned int davinci_wdt_get_timeleft(struct watchdog_device *wdd) { - if (len) - wdt_service(); + u64 timer_counter; + unsigned long freq; + u32 val; + struct davinci_wdt_device *davinci_wdt = watchdog_get_drvdata(wdd); - return len; -} + /* if timeout has occured then return 0 */ + val = ioread32(davinci_wdt->base + WDTCR); + if (val & WDFLAG) + return 0; -static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING, - .identity = "DaVinci Watchdog", -}; + freq = clk_get_rate(davinci_wdt->clk); -static int -davinci_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) -{ - int ret = -ENOTTY; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user((struct watchdog_info *)arg, &ident, - sizeof(ident)) ? -EFAULT : 0; - break; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_GETTIMEOUT: - ret = put_user(heartbeat, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_service(); - ret = 0; - break; - } - return ret; -} + if (!freq) + return 0; -static int davinci_wdt_release(struct inode *inode, struct file *file) -{ - wdt_service(); - clear_bit(WDT_IN_USE, &wdt_status); + timer_counter = ioread32(davinci_wdt->base + TIM12); + timer_counter |= ((u64)ioread32(davinci_wdt->base + TIM34) << 32); - return 0; + do_div(timer_counter, freq); + + return wdd->timeout - timer_counter; } -static const struct file_operations davinci_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = davinci_wdt_write, - .ioctl = davinci_wdt_ioctl, - .open = davinci_wdt_open, - .release = davinci_wdt_release, +static const struct watchdog_info davinci_wdt_info = { + .options = WDIOF_KEEPALIVEPING, + .identity = "DaVinci/Keystone Watchdog", }; -static struct miscdevice davinci_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &davinci_wdt_fops, +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, }; static int davinci_wdt_probe(struct platform_device *pdev) { - int ret = 0, size; - struct resource *res; - - if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) - heartbeat = DEFAULT_HEARTBEAT; - - printk(KERN_INFO MODULE_NAME - "DaVinci Watchdog Timer: heartbeat %d sec\n", heartbeat); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - printk(KERN_INFO MODULE_NAME - "failed to get memory region resource\n"); - return -ENOENT; - } - - size = res->end - res->start + 1; - wdt_mem = request_mem_region(res->start, size, pdev->name); - - if (wdt_mem == NULL) { - printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); - return -ENOENT; - } - wdt_base = (void __iomem *)(res->start); - - ret = misc_register(&davinci_wdt_miscdev); - if (ret < 0) { - printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); - release_resource(wdt_mem); - kfree(wdt_mem); - } else { - set_bit(WDT_DEVICE_INITED, &wdt_status); - } + int ret = 0; + struct device *dev = &pdev->dev; + struct resource *wdt_mem; + 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 (WARN_ON(IS_ERR(davinci_wdt->clk))) + return PTR_ERR(davinci_wdt->clk); + + clk_prepare_enable(davinci_wdt->clk); + + 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; + + 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); + + wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + davinci_wdt->base = devm_ioremap_resource(dev, wdt_mem); + if (IS_ERR(davinci_wdt->base)) + return PTR_ERR(davinci_wdt->base); + + ret = watchdog_register_device(wdd); + if (ret < 0) + dev_err(dev, "cannot register watchdog device\n"); return ret; } static int davinci_wdt_remove(struct platform_device *pdev) { - misc_deregister(&davinci_wdt_miscdev); - if (wdt_mem) { - release_resource(wdt_mem); - kfree(wdt_mem); - wdt_mem = NULL; - } + struct davinci_wdt_device *davinci_wdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(&davinci_wdt->wdd); + clk_disable_unprepare(davinci_wdt->clk); + return 0; } +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 = "watchdog", + .name = "davinci-wdt", + .owner = THIS_MODULE, + .of_match_table = davinci_wdt_of_match, }, .probe = davinci_wdt_probe, .remove = davinci_wdt_remove, }; -static int __init davinci_wdt_init(void) -{ - return platform_driver_register(&platform_wdt_driver); -} - -static void __exit davinci_wdt_exit(void) -{ - platform_driver_unregister(&platform_wdt_driver); -} - -module_init(davinci_wdt_init); -module_exit(davinci_wdt_exit); +module_platform_driver(platform_wdt_driver); MODULE_AUTHOR("Texas Instruments"); MODULE_DESCRIPTION("DaVinci Watchdog Driver"); @@ -276,4 +237,4 @@ MODULE_PARM_DESC(heartbeat, __MODULE_STRING(DEFAULT_HEARTBEAT)); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:davinci-wdt"); diff --git a/drivers/watchdog/diag288_wdt.c b/drivers/watchdog/diag288_wdt.c new file mode 100644 index 00000000000..429494b6c82 --- /dev/null +++ b/drivers/watchdog/diag288_wdt.c @@ -0,0 +1,316 @@ +/* + * 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/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/suspend.h> +#include <asm/ebcdic.h> +#include <linux/io.h> +#include <linux/uaccess.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_MISCDEV(WATCHDOG_MINOR); +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"); + return err; +} + +static int __diag288_vm(unsigned int func, unsigned int timeout, + char *cmd, size_t len) +{ + return __diag288(func, timeout, virt_to_phys(cmd), len); +} + +static int __diag288_lpar(unsigned int func, unsigned int timeout, + unsigned long action) +{ + return __diag288(func, timeout, action, 0); +} + +static int wdt_start(struct watchdog_device *dev) +{ + char *ebc_cmd; + size_t len; + int ret; + unsigned int func; + + ret = -ENODEV; + + 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); + + 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); + } + + if (MACHINE_IS_LPAR) { + ret = __diag288_lpar(WDT_FUNC_INIT, + dev->timeout, LPARWDT_RESTART); + } + + if (ret) { + pr_err("The watchdog cannot be activated\n"); + return ret; + } + pr_info("The watchdog was activated\n"); + return 0; +} + +static int wdt_stop(struct watchdog_device *dev) +{ + int ret; + + ret = __diag288(WDT_FUNC_CANCEL, 0, 0, 0); + pr_info("The watchdog was deactivated\n"); + return ret; +} + +static int wdt_ping(struct watchdog_device *dev) +{ + char *ebc_cmd; + size_t len; + int ret; + unsigned int func; + + ret = -ENODEV; + + 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); + } + + if (MACHINE_IS_LPAR) + 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 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_info wdt_info = { + .options = WDIOF_SETTIMEOUT | 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. + * We reuse the open flag to ensure that suspend and watchdog open are + * exclusive operations + */ +static int wdt_suspend(void) +{ + if (test_and_set_bit(WDOG_DEV_OPEN, &wdt_dev.status)) { + pr_err("Linux cannot be suspended while the watchdog is in use\n"); + return notifier_from_errno(-EBUSY); + } + if (test_bit(WDOG_ACTIVE, &wdt_dev.status)) { + clear_bit(WDOG_DEV_OPEN, &wdt_dev.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(WDOG_DEV_OPEN, &wdt_dev.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 + }; + + watchdog_set_nowayout(&wdt_dev, nowayout_info); + + if (MACHINE_IS_VM) { + pr_info("The watchdog device driver detected a z/VM environment\n"); + if (__diag288_vm(WDT_FUNC_INIT, 15, + ebc_begin, sizeof(ebc_begin)) != 0) { + pr_err("The watchdog cannot be initialized\n"); + return -EINVAL; + } + } else if (MACHINE_IS_LPAR) { + pr_info("The watchdog device driver detected an LPAR environment\n"); + if (__diag288_lpar(WDT_FUNC_INIT, 30, LPARWDT_RESTART)) { + pr_err("The watchdog cannot be initialized\n"); + return -EINVAL; + } + } else { + pr_err("Linux runs in an environment that does not support the diag288 watchdog\n"); + return -ENODEV; + } + + 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/dw_wdt.c b/drivers/watchdog/dw_wdt.c new file mode 100644 index 00000000000..ee4f86ba83e --- /dev/null +++ b/drivers/watchdog/dw_wdt.c @@ -0,0 +1,364 @@ +/* + * Copyright 2010-2011 Picochip Ltd., Jamie Iles + * http://www.picochip.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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 + * use a software timer to implement a ping that will keep the watchdog alive. + * If we receive an expected close for the watchdog then we keep the timer + * running, otherwise the timer is stopped and the watchdog will expire. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.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/of.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> + +#define WDOG_CONTROL_REG_OFFSET 0x00 +#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 +#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 +#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 +#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c +#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 + +/* The maximum TOP (timeout period) value that can be set in the watchdog. */ +#define DW_WDT_MAX_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) ")"); + +#define WDT_TIMEOUT (HZ / 2) + +static struct { + spinlock_t lock; + void __iomem *regs; + struct clk *clk; + unsigned long in_use; + unsigned long next_heartbeat; + struct timer_list timer; + int expect_close; +} dw_wdt; + +static inline int dw_wdt_is_enabled(void) +{ + return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) & + WDOG_CONTROL_REG_WDT_EN_MASK; +} + +static inline int dw_wdt_top_in_seconds(unsigned top) +{ + /* + * There are 16 possible timeout values in 0..15 where the number of + * cycles is 2 ^ (16 + i) and the watchdog counts down. + */ + return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk); +} + +static int dw_wdt_get_top(void) +{ + int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; + + return dw_wdt_top_in_seconds(top); +} + +static inline void dw_wdt_set_next_heartbeat(void) +{ + dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ; +} + +static int dw_wdt_set_top(unsigned top_s) +{ + int i, top_val = DW_WDT_MAX_TOP; + + /* + * Iterate over the timeout values until we find the closest match. We + * always look for >=. + */ + for (i = 0; i <= DW_WDT_MAX_TOP; ++i) + if (dw_wdt_top_in_seconds(i) >= top_s) { + top_val = i; + break; + } + + /* Set the new value in the watchdog. */ + writel(top_val, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + + dw_wdt_set_next_heartbeat(); + + return dw_wdt_top_in_seconds(top_val); +} + +static void dw_wdt_keepalive(void) +{ + writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + + WDOG_COUNTER_RESTART_REG_OFFSET); +} + +static void dw_wdt_ping(unsigned long data) +{ + if (time_before(jiffies, dw_wdt.next_heartbeat) || + (!nowayout && !dw_wdt.in_use)) { + dw_wdt_keepalive(); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + } else + pr_crit("keepalive missed, machine will reset\n"); +} + +static int dw_wdt_open(struct inode *inode, struct file *filp) +{ + if (test_and_set_bit(0, &dw_wdt.in_use)) + return -EBUSY; + + /* Make sure we don't get unloaded. */ + __module_get(THIS_MODULE); + + spin_lock(&dw_wdt.lock); + if (!dw_wdt_is_enabled()) { + /* + * The watchdog is not currently enabled. Set the timeout to + * the maximum and then start it. + */ + dw_wdt_set_top(DW_WDT_MAX_TOP); + writel(WDOG_CONTROL_REG_WDT_EN_MASK, + dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + } + + dw_wdt_set_next_heartbeat(); + + spin_unlock(&dw_wdt.lock); + + return nonseekable_open(inode, filp); +} + +static ssize_t dw_wdt_write(struct file *filp, const char __user *buf, + size_t len, loff_t *offset) +{ + if (!len) + return 0; + + if (!nowayout) { + size_t i; + + dw_wdt.expect_close = 0; + + for (i = 0; i < len; ++i) { + char c; + + if (get_user(c, buf + i)) + return -EFAULT; + + if (c == 'V') { + dw_wdt.expect_close = 1; + break; + } + } + } + + dw_wdt_set_next_heartbeat(); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + + return len; +} + +static u32 dw_wdt_time_left(void) +{ + return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) / + clk_get_rate(dw_wdt.clk); +} + +static const struct watchdog_info dw_wdt_ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .identity = "Synopsys DesignWare Watchdog", +}; + +static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + unsigned long val; + int timeout; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user((void __user *)arg, &dw_wdt_ident, + sizeof(dw_wdt_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int __user *)arg); + + case WDIOC_KEEPALIVE: + dw_wdt_set_next_heartbeat(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + timeout = dw_wdt_set_top(val); + return put_user(timeout , (int __user *)arg); + + case WDIOC_GETTIMEOUT: + return put_user(dw_wdt_get_top(), (int __user *)arg); + + case WDIOC_GETTIMELEFT: + /* Get the time left until expiry. */ + if (get_user(val, (int __user *)arg)) + return -EFAULT; + return put_user(dw_wdt_time_left(), (int __user *)arg); + + default: + return -ENOTTY; + } +} + +static int dw_wdt_release(struct inode *inode, struct file *filp) +{ + clear_bit(0, &dw_wdt.in_use); + + if (!dw_wdt.expect_close) { + del_timer(&dw_wdt.timer); + + if (!nowayout) + pr_crit("unexpected close, system will reboot soon\n"); + else + pr_crit("watchdog cannot be disabled, system will reboot soon\n"); + } + + dw_wdt.expect_close = 0; + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dw_wdt_suspend(struct device *dev) +{ + clk_disable_unprepare(dw_wdt.clk); + + return 0; +} + +static int dw_wdt_resume(struct device *dev) +{ + int err = clk_prepare_enable(dw_wdt.clk); + + if (err) + return err; + + dw_wdt_keepalive(); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume); + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = dw_wdt_open, + .write = dw_wdt_write, + .unlocked_ioctl = dw_wdt_ioctl, + .release = dw_wdt_release +}; + +static struct miscdevice dw_wdt_miscdev = { + .fops = &wdt_fops, + .name = "watchdog", + .minor = WATCHDOG_MINOR, +}; + +static int dw_wdt_drv_probe(struct platform_device *pdev) +{ + int ret; + struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!mem) + return -EINVAL; + + dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(dw_wdt.regs)) + return PTR_ERR(dw_wdt.regs); + + dw_wdt.clk = devm_clk_get(&pdev->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; + + spin_lock_init(&dw_wdt.lock); + + ret = misc_register(&dw_wdt_miscdev); + if (ret) + goto out_disable_clk; + + dw_wdt_set_next_heartbeat(); + setup_timer(&dw_wdt.timer, dw_wdt_ping, 0); + mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); + + return 0; + +out_disable_clk: + clk_disable_unprepare(dw_wdt.clk); + + return ret; +} + +static int dw_wdt_drv_remove(struct platform_device *pdev) +{ + misc_deregister(&dw_wdt_miscdev); + + 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", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(dw_wdt_of_match), + .pm = &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/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c index 0e4787a0bb8..5f54e1e5819 100644 --- a/drivers/watchdog/ep93xx_wdt.c +++ b/drivers/watchdog/ep93xx_wdt.c @@ -8,6 +8,9 @@ * 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. @@ -23,231 +26,148 @@ * - Add a few missing ioctls */ +#include <linux/platform_device.h> #include <linux/module.h> -#include <linux/fs.h> -#include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/timer.h> +#include <linux/io.h> -#include <asm/hardware.h> -#include <asm/uaccess.h> - -#define WDT_VERSION "0.3" -#define PFX "ep93xx_wdt: " +#define WDT_VERSION "0.4" /* default timeout (secs) */ #define WDT_TIMEOUT 30 -static int nowayout = WATCHDOG_NOWAYOUT; -static int timeout = WDT_TIMEOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +static unsigned int timeout = WDT_TIMEOUT; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WDT_TIMEOUT) ")"); +static void __iomem *mmio_base; static struct timer_list timer; static unsigned long next_heartbeat; -static unsigned long wdt_status; -static unsigned long boot_status; -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 +#define EP93XX_WATCHDOG 0x00 +#define EP93XX_WDSTATUS 0x04 -#define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x)) -#define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00) -#define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04) - -/* reset the wdt every ~200ms */ +/* reset the wdt every ~200ms - the heartbeat of the device is 0.250 seconds*/ #define WDT_INTERVAL (HZ/5) -static void wdt_enable(void) -{ - __raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG); -} - -static void wdt_disable(void) +static void ep93xx_wdt_timer_ping(unsigned long data) { - __raw_writew(0xaa55, EP93XX_WDT_WATCHDOG); -} + if (time_before(jiffies, next_heartbeat)) + writel(0x5555, mmio_base + EP93XX_WATCHDOG); -static inline void wdt_ping(void) -{ - __raw_writew(0x5555, EP93XX_WDT_WATCHDOG); + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); } -static void wdt_startup(void) +static int ep93xx_wdt_start(struct watchdog_device *wdd) { next_heartbeat = jiffies + (timeout * HZ); - wdt_enable(); + writel(0xaaaa, mmio_base + EP93XX_WATCHDOG); mod_timer(&timer, jiffies + WDT_INTERVAL); + + return 0; } -static void wdt_shutdown(void) +static int ep93xx_wdt_stop(struct watchdog_device *wdd) { del_timer_sync(&timer); - wdt_disable(); + writel(0xaa55, mmio_base + EP93XX_WATCHDOG); + + return 0; } -static void wdt_keepalive(void) +static int ep93xx_wdt_keepalive(struct watchdog_device *wdd) { /* user land ping */ next_heartbeat = jiffies + (timeout * HZ); -} - -static int ep93xx_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_startup(); - - return nonseekable_open(inode, file); -} - -static ssize_t -ep93xx_wdt_write(struct file *file, const char __user *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); - else - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - } - } - wdt_keepalive(); - } - return len; + return 0; } -static struct watchdog_info ident = { - .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE, - .identity = "EP93xx Watchdog", +static const struct watchdog_info ep93xx_wdt_ident = { + .options = WDIOF_CARDRESET | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .identity = "EP93xx Watchdog", }; -static int -ep93xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) -{ - int ret = -ENOTTY; - - 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(boot_status, (int __user *)arg); - break; - - case WDIOC_GETTIMEOUT: - /* actually, it is 0.250 seconds.... */ - ret = put_user(1, (int __user *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_keepalive(); - ret = 0; - break; - } - return ret; -} - -static int ep93xx_wdt_release(struct inode *inode, struct file *file) -{ - if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) - wdt_shutdown(); - else - printk(KERN_CRIT PFX "Device closed unexpectedly - " - "timer will not stop\n"); - - clear_bit(WDT_IN_USE, &wdt_status); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - return 0; -} - -static const struct file_operations ep93xx_wdt_fops = { +static struct watchdog_ops ep93xx_wdt_ops = { .owner = THIS_MODULE, - .write = ep93xx_wdt_write, - .ioctl = ep93xx_wdt_ioctl, - .open = ep93xx_wdt_open, - .release = ep93xx_wdt_release, + .start = ep93xx_wdt_start, + .stop = ep93xx_wdt_stop, + .ping = ep93xx_wdt_keepalive, }; -static struct miscdevice ep93xx_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &ep93xx_wdt_fops, +static struct watchdog_device ep93xx_wdt_wdd = { + .info = &ep93xx_wdt_ident, + .ops = &ep93xx_wdt_ops, }; -static void ep93xx_timer_ping(unsigned long data) -{ - if (time_before(jiffies, next_heartbeat)) - wdt_ping(); - - /* Re-set the timer interval */ - mod_timer(&timer, jiffies + WDT_INTERVAL); -} - -static int __init ep93xx_wdt_init(void) +static int ep93xx_wdt_probe(struct platform_device *pdev) { + struct resource *res; + unsigned long val; int err; - err = misc_register(&ep93xx_wdt_miscdev); - - boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0; - - printk(KERN_INFO PFX "EP93XX watchdog, driver version " - WDT_VERSION "%s\n", - (__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08) - ? " (nCS1 disable detected)" : ""); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mmio_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mmio_base)) + return PTR_ERR(mmio_base); if (timeout < 1 || timeout > 3600) { timeout = WDT_TIMEOUT; - printk(KERN_INFO PFX + dev_warn(&pdev->dev, "timeout value must be 1<=x<=3600, using %d\n", timeout); } - setup_timer(&timer, ep93xx_timer_ping, 1); - return err; + val = readl(mmio_base + EP93XX_WATCHDOG); + ep93xx_wdt_wdd.bootstatus = (val & 0x01) ? WDIOF_CARDRESET : 0; + ep93xx_wdt_wdd.timeout = timeout; + + watchdog_set_nowayout(&ep93xx_wdt_wdd, nowayout); + + setup_timer(&timer, ep93xx_wdt_timer_ping, 1); + + err = watchdog_register_device(&ep93xx_wdt_wdd); + if (err) + return err; + + dev_info(&pdev->dev, + "EP93XX watchdog, driver version " WDT_VERSION "%s\n", + (val & 0x08) ? " (nCS1 disable detected)" : ""); + + return 0; } -static void __exit ep93xx_wdt_exit(void) +static int ep93xx_wdt_remove(struct platform_device *pdev) { - wdt_shutdown(); - misc_deregister(&ep93xx_wdt_miscdev); + watchdog_unregister_device(&ep93xx_wdt_wdd); + return 0; } -module_init(ep93xx_wdt_init); -module_exit(ep93xx_wdt_exit); - -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); +static struct platform_driver ep93xx_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ep93xx-wdt", + }, + .probe = ep93xx_wdt_probe, + .remove = ep93xx_wdt_remove, +}; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); +module_platform_driver(ep93xx_wdt_driver); -MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," - "Alessandro Zummo <a.zummo@towertech.it>"); +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"); MODULE_VERSION(WDT_VERSION); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/eurotechwdt.c b/drivers/watchdog/eurotechwdt.c index b14e9d1f164..23ee53240c4 100644 --- a/drivers/watchdog/eurotechwdt.c +++ b/drivers/watchdog/eurotechwdt.c @@ -8,19 +8,19 @@ * Based on wdt.c. * Original copyright messages: * - * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (c) Copyright 1996-1997 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 - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. * - * 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. + * 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>* + * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>* */ /* Changelog: @@ -37,7 +37,7 @@ * add expect_close support * * 2002.05.30 - Joel Becker <joel.becker@oracle.com> - * Added Matt Domsch's nowayout module option. + * Added Matt Domsch's nowayout module option. */ /* @@ -45,6 +45,8 @@ * 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> @@ -56,18 +58,17 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.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. - * You can use eurwdt=x,y to set these now. */ static int io = 0x3f0; @@ -76,9 +77,11 @@ static char *ev = "int"; #define WDT_TIMEOUT 60 /* 1 minute */ -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -137,17 +140,18 @@ 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); + 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) { - printk(KERN_ERR ": invalid irq number\n"); + pr_err("invalid irq number\n"); irq = 0; /* if invalid we disable interrupt */ } if (irq == 0) - printk(KERN_INFO ": interrupt disabled\n"); + pr_info("interrupt disabled\n"); - eurwdt_write_reg(WDT_TIMER_CFG, irq<<4); + 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 */ @@ -160,12 +164,12 @@ static void eurwdt_activate_timer(void) static irqreturn_t eurwdt_interrupt(int irq, void *dev_id) { - printk(KERN_CRIT "timeout WDT timeout\n"); + pr_crit("timeout WDT timeout\n"); #ifdef ONLY_TESTING - printk(KERN_CRIT "Would Reboot.\n"); + pr_crit("Would Reboot\n"); #else - printk(KERN_CRIT "Initiating system reboot.\n"); + pr_crit("Initiating system reboot\n"); emergency_restart(); #endif return IRQ_HANDLED; @@ -198,7 +202,7 @@ static void eurwdt_ping(void) static ssize_t eurwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - if (count) { + if (count) { if (!nowayout) { size_t i; @@ -206,21 +210,21 @@ size_t count, loff_t *ppos) for (i = 0; i != count; i++) { char c; - if(get_user(c, buf+i)) + 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: - * @inode: inode of the device * @file: file handle to the device * @cmd: watchdog command * @arg: argument pointer @@ -229,13 +233,14 @@ size_t count, loff_t *ppos) * according to their available features. */ -static int eurwdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long eurwdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "WDT Eurotech CPU-1220/1410", }; @@ -243,10 +248,7 @@ static int eurwdt_ioctl(struct inode *inode, struct file *file, int time; int options, retval = -EINVAL; - switch(cmd) { - default: - return -ENOTTY; - + switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; @@ -254,8 +256,26 @@ static int eurwdt_ioctl(struct inode *inode, struct file *file, 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: @@ -266,26 +286,17 @@ static int eurwdt_ioctl(struct inode *inode, struct file *file, if (time < 0 || time > 255) return -EINVAL; + spin_lock(&eurwdt_lock); eurwdt_timeout = time; eurwdt_set_timeout(time); + spin_unlock(&eurwdt_lock); /* Fall */ case WDIOC_GETTIMEOUT: return put_user(eurwdt_timeout, p); - case WDIOC_SETOPTIONS: - if (get_user(options, p)) - return -EFAULT; - if (options & WDIOS_DISABLECARD) { - eurwdt_disable_timer(); - retval = 0; - } - if (options & WDIOS_ENABLECARD) { - eurwdt_activate_timer(); - eurwdt_ping(); - retval = 0; - } - return retval; + default: + return -ENOTTY; } } @@ -322,10 +333,10 @@ static int eurwdt_open(struct inode *inode, struct file *file) static int eurwdt_release(struct inode *inode, struct file *file) { - if (eur_expect_close == 42) { + if (eur_expect_close == 42) eurwdt_disable_timer(); - } else { - printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n"); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); eurwdt_ping(); } clear_bit(0, &eurwdt_is_open); @@ -348,10 +359,8 @@ static int eurwdt_release(struct inode *inode, struct file *file) static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if (code == SYS_DOWN || code == SYS_HALT) { - /* Turn the card off */ - eurwdt_disable_timer(); - } + if (code == SYS_DOWN || code == SYS_HALT) + eurwdt_disable_timer(); /* Turn the card off */ return NOTIFY_DONE; } @@ -362,11 +371,11 @@ static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, static const struct file_operations eurwdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = eurwdt_write, - .ioctl = eurwdt_ioctl, - .open = eurwdt_open, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = eurwdt_write, + .unlocked_ioctl = eurwdt_ioctl, + .open = eurwdt_open, .release = eurwdt_release, }; @@ -418,36 +427,34 @@ static int __init eurwdt_init(void) { int ret; - ret = request_irq(irq, eurwdt_interrupt, IRQF_DISABLED, "eurwdt", NULL); - if(ret) { - printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq); + 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")) { - printk(KERN_ERR "eurwdt: IO %X is not free.\n", io); + pr_err("IO %X is not free\n", io); ret = -EBUSY; goto outirq; } ret = register_reboot_notifier(&eurwdt_notifier); if (ret) { - printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret); + pr_err("can't register reboot notifier (err=%d)\n", ret); goto outreg; } ret = misc_register(&eurwdt_miscdev); if (ret) { - printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n", - WATCHDOG_MINOR); + pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); goto outreboot; } eurwdt_unlock_chip(); ret = 0; - printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)" - " - timeout event: %s\n", + pr_info("Eurotech WDT driver 0.01 at %X (Interrupt %d) - timeout event: %s\n", io, irq, (!strcmp("int", ev) ? "int" : "reboot")); out: @@ -470,4 +477,3 @@ module_exit(eurwdt_exit); MODULE_AUTHOR("Rodolfo Giometti"); MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c new file mode 100644 index 00000000000..016bd935519 --- /dev/null +++ b/drivers/watchdog/f71808e_wdt.c @@ -0,0 +1,820 @@ +/*************************************************************************** + * 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> * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/uaccess.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 diasble 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_ROM_ADDR_SEL 0x27 /* ROM address 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_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_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 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 5 +#define F71808FG_FLAG_WD_EN 5 +#define F71808FG_FLAG_WD_PULSE 4 +#define F71808FG_FLAG_WD_UNIT 3 + +/* 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 const int max_timeout = WATCHDOG_MAX_TIMEOUT; +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 ms, 25 ms, 125 ms or 5000 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, f71869, f71882fg, f71889fg }; + +static const char *f71808e_names[] = { + "f71808fg", + "f71858fg", + "f71862fg", + "f71869", + "f71882fg", + "f71889fg", +}; + +/* 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 watchdog_data { + unsigned short sioaddr; + enum chips type; + unsigned long opened; + struct mutex lock; + char expect_close; + struct watchdog_info ident; + + unsigned short timeout; + 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? */ + char caused_reboot; /* last reboot was by the watchdog */ +}; + +static struct watchdog_data watchdog = { + .lock = __MUTEX_INITIALIZER(watchdog.lock), +}; + +/* 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 watchdog_set_timeout(int timeout) +{ + if (timeout <= 0 + || timeout > max_timeout) { + pr_err("watchdog timeout out of range\n"); + return -EINVAL; + } + + mutex_lock(&watchdog.lock); + + watchdog.timeout = timeout; + if (timeout > 0xff) { + watchdog.timer_val = DIV_ROUND_UP(timeout, 60); + watchdog.minutes_mode = true; + } else { + watchdog.timer_val = timeout; + watchdog.minutes_mode = false; + } + + mutex_unlock(&watchdog.lock); + + return 0; +} + +static int watchdog_set_pulse_width(unsigned int pw) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + + if (pw <= 1) { + watchdog.pulse_val = 0; + } else if (pw <= 25) { + watchdog.pulse_val = 1; + } else if (pw <= 125) { + watchdog.pulse_val = 2; + } else if (pw <= 5000) { + watchdog.pulse_val = 3; + } else { + pr_err("pulse width out of range\n"); + err = -EINVAL; + goto exit_unlock; + } + + watchdog.pulse_mode = pw; + +exit_unlock: + mutex_unlock(&watchdog.lock); + return err; +} + +static int watchdog_keepalive(void) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + if (watchdog.minutes_mode) + /* select minutes for timer units */ + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + else + /* select seconds for timer units */ + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + + /* Set timer value */ + superio_outb(watchdog.sioaddr, F71808FG_REG_WD_TIME, + watchdog.timer_val); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + return err; +} + +static int f71862fg_pin_configure(unsigned short ioaddr) +{ + /* When ioaddr is non-zero the calling function has to take care of + mutex handling and superio preparation! */ + + if (f71862fg_pin == 63) { + if (ioaddr) { + /* SPI must be disabled first to use this pin! */ + superio_clear_bit(ioaddr, SIO_REG_ROM_ADDR_SEL, 6); + superio_set_bit(ioaddr, SIO_REG_MFUNCT3, 4); + } + } else if (f71862fg_pin == 56) { + if (ioaddr) + superio_set_bit(ioaddr, SIO_REG_MFUNCT1, 1); + } else { + pr_err("Invalid argument f71862fg_pin=%d\n", f71862fg_pin); + return -EINVAL; + } + return 0; +} + +static int watchdog_start(void) +{ + /* Make sure we don't die as soon as the watchdog is enabled below */ + int err = watchdog_keepalive(); + if (err) + return err; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + /* Watchdog pin configuration */ + switch (watchdog.type) { + case f71808fg: + /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ + superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT2, 3); + superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT3, 3); + break; + + case f71862fg: + err = f71862fg_pin_configure(watchdog.sioaddr); + if (err) + goto exit_superio; + break; + + case f71869: + /* GPIO14 --> WDTRST# */ + superio_clear_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 4); + break; + + case f71882fg: + /* Set pin 56 to WDTRST# */ + superio_set_bit(watchdog.sioaddr, SIO_REG_MFUNCT1, 1); + break; + + case f71889fg: + /* set pin 40 to WDTRST# */ + superio_outb(watchdog.sioaddr, SIO_REG_MFUNCT3, + superio_inb(watchdog.sioaddr, SIO_REG_MFUNCT3) & 0xcf); + break; + + default: + /* + * 'default' label to shut up the compiler and catch + * programmer errors + */ + err = -ENODEV; + goto exit_superio; + } + + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + superio_set_bit(watchdog.sioaddr, SIO_REG_ENABLE, 0); + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDO_CONF, + F71808FG_FLAG_WDOUT_EN); + + superio_set_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + if (watchdog.pulse_mode) { + /* Select "pulse" output mode with given duration */ + u8 wdt_conf = superio_inb(watchdog.sioaddr, + F71808FG_REG_WDT_CONF); + + /* Set WD_PSWIDTH bits (1:0) */ + wdt_conf = (wdt_conf & 0xfc) | (watchdog.pulse_val & 0x03); + /* Set WD_PULSE to "pulse" mode */ + wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE); + + superio_outb(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + wdt_conf); + } else { + /* Select "level" output mode */ + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_PULSE); + } + +exit_superio: + superio_exit(watchdog.sioaddr); +exit_unlock: + mutex_unlock(&watchdog.lock); + + return err; +} + +static int watchdog_stop(void) +{ + int err = 0; + + mutex_lock(&watchdog.lock); + err = superio_enter(watchdog.sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + superio_clear_bit(watchdog.sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + + return err; +} + +static int watchdog_get_status(void) +{ + int status = 0; + + mutex_lock(&watchdog.lock); + status = (watchdog.caused_reboot) ? WDIOF_CARDRESET : 0; + mutex_unlock(&watchdog.lock); + + return status; +} + +static bool watchdog_is_running(void) +{ + /* + * if we fail to determine the watchdog's status assume it to be + * running to be on the safe side + */ + bool is_running = true; + + mutex_lock(&watchdog.lock); + if (superio_enter(watchdog.sioaddr)) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + is_running = (superio_inb(watchdog.sioaddr, SIO_REG_ENABLE) & BIT(0)) + && (superio_inb(watchdog.sioaddr, F71808FG_REG_WDT_CONF) + & F71808FG_FLAG_WD_EN); + + superio_exit(watchdog.sioaddr); + +exit_unlock: + mutex_unlock(&watchdog.lock); + return is_running; +} + +/* /dev/watchdog api */ + +static int watchdog_open(struct inode *inode, struct file *file) +{ + int err; + + /* If the watchdog is alive we don't need to start it again */ + if (test_and_set_bit(0, &watchdog.opened)) + return -EBUSY; + + err = watchdog_start(); + if (err) { + clear_bit(0, &watchdog.opened); + return err; + } + + if (nowayout) + __module_get(THIS_MODULE); + + watchdog.expect_close = 0; + return nonseekable_open(inode, file); +} + +static int watchdog_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &watchdog.opened); + + if (!watchdog.expect_close) { + watchdog_keepalive(); + pr_crit("Unexpected close, not stopping watchdog!\n"); + } else if (!nowayout) { + watchdog_stop(); + } + return 0; +} + +/* + * watchdog_write: + * @file: file handle to the watchdog + * @buf: buffer to write + * @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 watchdog_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 */ + bool expect_close = false; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + expect_close = (c == 'V'); + } + + /* Properly order writes across fork()ed processes */ + mutex_lock(&watchdog.lock); + watchdog.expect_close = expect_close; + mutex_unlock(&watchdog.lock); + } + + /* someone wrote to us, we should restart timer */ + watchdog_keepalive(); + } + return count; +} + +/* + * watchdog_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 watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int status; + int new_options; + 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, &watchdog.ident, + sizeof(watchdog.ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + status = watchdog_get_status(); + if (status < 0) + return 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) + watchdog_stop(); + + if (new_options & WDIOS_ENABLECARD) + return watchdog_start(); + + + case WDIOC_KEEPALIVE: + watchdog_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (watchdog_set_timeout(new_timeout)) + return -EINVAL; + + watchdog_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(watchdog.timeout, uarg.i); + + default: + return -ENOTTY; + + } +} + +static int watchdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + watchdog_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, +}; + +static struct miscdevice watchdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &watchdog_fops, +}; + +static struct notifier_block watchdog_notifier = { + .notifier_call = watchdog_notify_sys, +}; + +static int __init watchdog_init(int sioaddr) +{ + int wdt_conf, err = 0; + + /* No need to lock watchdog.lock here because no entry points + * into the module have been registered yet. + */ + watchdog.sioaddr = sioaddr; + watchdog.ident.options = WDIOC_SETTIMEOUT + | WDIOF_MAGICCLOSE + | WDIOF_KEEPALIVEPING; + + snprintf(watchdog.ident.identity, + sizeof(watchdog.ident.identity), "%s watchdog", + f71808e_names[watchdog.type]); + + err = superio_enter(sioaddr); + if (err) + return err; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + wdt_conf = superio_inb(sioaddr, F71808FG_REG_WDT_CONF); + watchdog.caused_reboot = wdt_conf & F71808FG_FLAG_WDTMOUT_STS; + + superio_exit(sioaddr); + + err = watchdog_set_timeout(timeout); + if (err) + return err; + err = watchdog_set_pulse_width(pulse_width); + if (err) + return err; + + err = register_reboot_notifier(&watchdog_notifier); + if (err) + return err; + + err = misc_register(&watchdog_miscdev); + if (err) { + pr_err("cannot register miscdev on minor=%d\n", + watchdog_miscdev.minor); + goto exit_reboot; + } + + if (start_withtimeout) { + if (start_withtimeout <= 0 + || start_withtimeout > max_timeout) { + pr_err("starting timeout out of range\n"); + err = -EINVAL; + goto exit_miscdev; + } + + err = watchdog_start(); + if (err) { + pr_err("cannot start watchdog timer\n"); + goto exit_miscdev; + } + + mutex_lock(&watchdog.lock); + err = superio_enter(sioaddr); + if (err) + goto exit_unlock; + superio_select(watchdog.sioaddr, SIO_F71808FG_LD_WDT); + + if (start_withtimeout > 0xff) { + /* select minutes for timer units */ + superio_set_bit(sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + superio_outb(sioaddr, F71808FG_REG_WD_TIME, + DIV_ROUND_UP(start_withtimeout, 60)); + } else { + /* select seconds for timer units */ + superio_clear_bit(sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + superio_outb(sioaddr, F71808FG_REG_WD_TIME, + start_withtimeout); + } + + superio_exit(sioaddr); + mutex_unlock(&watchdog.lock); + + if (nowayout) + __module_get(THIS_MODULE); + + pr_info("watchdog started with initial timeout of %u sec\n", + start_withtimeout); + } + + return 0; + +exit_unlock: + mutex_unlock(&watchdog.lock); +exit_miscdev: + misc_deregister(&watchdog_miscdev); +exit_reboot: + unregister_reboot_notifier(&watchdog_notifier); + + return err; +} + +static int __init f71808e_find(int sioaddr) +{ + 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: + watchdog.type = f71808fg; + break; + case SIO_F71862_ID: + watchdog.type = f71862fg; + err = f71862fg_pin_configure(0); /* validate module parameter */ + break; + case SIO_F71869_ID: + case SIO_F71869A_ID: + watchdog.type = f71869; + break; + case SIO_F71882_ID: + watchdog.type = f71882fg; + break; + case SIO_F71889_ID: + watchdog.type = f71889fg; + break; + case SIO_F71858_ID: + /* Confirmed (by datasheet) not to have a watchdog. */ + err = -ENODEV; + goto exit; + default: + pr_info("Unrecognized Fintek device: %04x\n", + (unsigned int)devid); + err = -ENODEV; + goto exit; + } + + pr_info("Found %s watchdog chip, revision %d\n", + f71808e_names[watchdog.type], + (int)superio_inb(sioaddr, SIO_REG_DEVREV)); +exit: + superio_exit(sioaddr); + return err; +} + +static int __init f71808e_init(void) +{ + static const unsigned short addrs[] = { 0x2e, 0x4e }; + int err = -ENODEV; + int i; + + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + err = f71808e_find(addrs[i]); + if (err == 0) + break; + } + if (i == ARRAY_SIZE(addrs)) + return err; + + return watchdog_init(addrs[i]); +} + +static void __exit f71808e_exit(void) +{ + if (watchdog_is_running()) { + pr_warn("Watchdog timer still running, stopping it\n"); + watchdog_stop(); + } + misc_deregister(&watchdog_miscdev); + unregister_reboot_notifier(&watchdog_notifier); +} + +MODULE_DESCRIPTION("F71808E Watchdog Driver"); +MODULE_AUTHOR("Giel van Schijndel <me@mortis.eu>"); +MODULE_LICENSE("GPL"); + +module_init(f71808e_init); +module_exit(f71808e_exit); diff --git a/drivers/watchdog/gef_wdt.c b/drivers/watchdog/gef_wdt.c new file mode 100644 index 00000000000..25beb30878d --- /dev/null +++ b/drivers/watchdog/gef_wdt.c @@ -0,0 +1,334 @@ +/* + * GE watchdog userspace interface + * + * Author: Martyn Welch <martyn.welch@ge.com> + * + * Copyright 2008 GE Intelligent Platforms Embedded Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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); + /* Fall through */ + + 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 nonseekable_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, + .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", + }, + {}, +}; + +static struct platform_driver gef_wdt_driver = { + .driver = { + .name = "gef_wdt", + .owner = THIS_MODULE, + .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 00000000000..4c43e3fa8bd --- /dev/null +++ b/drivers/watchdog/geodewdt.c @@ -0,0 +1,296 @@ +/* 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#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 nonseekable_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; + break; + + 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; + /* Fall through */ + 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, + .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 = { + .owner = THIS_MODULE, + .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 00000000000..220a9e07cfd --- /dev/null +++ b/drivers/watchdog/gpio_wdt.c @@ -0,0 +1,254 @@ +/* + * Driver for watchdog device controlled through GPIO-line + * + * Author: 2013, Alexander Shiyan <shc_work@mail.ru> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/watchdog.h> + +#define SOFT_TIMEOUT_MIN 1 +#define SOFT_TIMEOUT_DEF 60 +#define SOFT_TIMEOUT_MAX 0xffff + +enum { + HW_ALGO_TOGGLE, + HW_ALGO_LEVEL, +}; + +struct gpio_wdt_priv { + int gpio; + bool active_low; + bool state; + unsigned int hw_algo; + unsigned int hw_margin; + unsigned long last_jiffies; + struct notifier_block notifier; + struct timer_list timer; + struct watchdog_device wdd; +}; + +static void gpio_wdt_disable(struct gpio_wdt_priv *priv) +{ + gpio_set_value_cansleep(priv->gpio, !priv->active_low); + + /* Put GPIO back to tristate */ + if (priv->hw_algo == HW_ALGO_TOGGLE) + gpio_direction_input(priv->gpio); +} + +static int gpio_wdt_start(struct watchdog_device *wdd) +{ + struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); + + priv->state = priv->active_low; + gpio_direction_output(priv->gpio, priv->state); + priv->last_jiffies = jiffies; + mod_timer(&priv->timer, priv->last_jiffies + priv->hw_margin); + + return 0; +} + +static int gpio_wdt_stop(struct watchdog_device *wdd) +{ + struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); + + mod_timer(&priv->timer, 0); + gpio_wdt_disable(priv); + + return 0; +} + +static int gpio_wdt_ping(struct watchdog_device *wdd) +{ + struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); + + priv->last_jiffies = jiffies; + + return 0; +} + +static int gpio_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) +{ + wdd->timeout = t; + + return gpio_wdt_ping(wdd); +} + +static void gpio_wdt_hwping(unsigned long data) +{ + struct watchdog_device *wdd = (struct watchdog_device *)data; + struct gpio_wdt_priv *priv = watchdog_get_drvdata(wdd); + + if (time_after(jiffies, priv->last_jiffies + + msecs_to_jiffies(wdd->timeout * 1000))) { + dev_crit(wdd->dev, "Timer expired. System will reboot soon!\n"); + return; + } + + /* Restart timer */ + mod_timer(&priv->timer, jiffies + priv->hw_margin); + + switch (priv->hw_algo) { + case HW_ALGO_TOGGLE: + /* Toggle output pin */ + priv->state = !priv->state; + gpio_set_value_cansleep(priv->gpio, priv->state); + break; + case HW_ALGO_LEVEL: + /* Pulse */ + gpio_set_value_cansleep(priv->gpio, !priv->active_low); + udelay(1); + gpio_set_value_cansleep(priv->gpio, priv->active_low); + break; + } +} + +static int gpio_wdt_notify_sys(struct notifier_block *nb, unsigned long code, + void *unused) +{ + struct gpio_wdt_priv *priv = container_of(nb, struct gpio_wdt_priv, + notifier); + + mod_timer(&priv->timer, 0); + + switch (code) { + case SYS_HALT: + case SYS_POWER_OFF: + gpio_wdt_disable(priv); + break; + default: + break; + } + + return NOTIFY_DONE; +} + +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, + .set_timeout = gpio_wdt_set_timeout, +}; + +static int gpio_wdt_probe(struct platform_device *pdev) +{ + struct gpio_wdt_priv *priv; + enum of_gpio_flags flags; + unsigned int hw_margin; + unsigned long f = 0; + const char *algo; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->gpio = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); + if (!gpio_is_valid(priv->gpio)) + return priv->gpio; + + priv->active_low = flags & OF_GPIO_ACTIVE_LOW; + + ret = of_property_read_string(pdev->dev.of_node, "hw_algo", &algo); + if (ret) + return ret; + if (!strncmp(algo, "toggle", 6)) { + priv->hw_algo = HW_ALGO_TOGGLE; + f = GPIOF_IN; + } else if (!strncmp(algo, "level", 5)) { + priv->hw_algo = HW_ALGO_LEVEL; + f = priv->active_low ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW; + } else { + return -EINVAL; + } + + ret = devm_gpio_request_one(&pdev->dev, priv->gpio, f, + dev_name(&pdev->dev)); + if (ret) + return ret; + + ret = of_property_read_u32(pdev->dev.of_node, + "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; + + /* Use safe value (1/2 of real timeout) */ + priv->hw_margin = msecs_to_jiffies(hw_margin / 2); + + 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_timeout = SOFT_TIMEOUT_MAX; + + if (watchdog_init_timeout(&priv->wdd, 0, &pdev->dev) < 0) + priv->wdd.timeout = SOFT_TIMEOUT_DEF; + + setup_timer(&priv->timer, gpio_wdt_hwping, (unsigned long)&priv->wdd); + + ret = watchdog_register_device(&priv->wdd); + if (ret) + return ret; + + priv->notifier.notifier_call = gpio_wdt_notify_sys; + ret = register_reboot_notifier(&priv->notifier); + if (ret) + watchdog_unregister_device(&priv->wdd); + + return ret; +} + +static int gpio_wdt_remove(struct platform_device *pdev) +{ + struct gpio_wdt_priv *priv = platform_get_drvdata(pdev); + + del_timer_sync(&priv->timer); + unregister_reboot_notifier(&priv->notifier); + watchdog_unregister_device(&priv->wdd); + + return 0; +} + +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", + .owner = THIS_MODULE, + .of_match_table = gpio_wdt_dt_ids, + }, + .probe = gpio_wdt_probe, + .remove = gpio_wdt_remove, +}; +module_platform_driver(gpio_wdt_driver); + +MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); +MODULE_DESCRIPTION("GPIO Watchdog"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 6483d1066b9..75d2243b94f 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -13,36 +13,55 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/device.h> #include <linux/fs.h> -#include <linux/init.h> -#include <linux/interrupt.h> #include <linux/io.h> -#include <linux/irq.h> +#include <linux/bitops.h> #include <linux/kernel.h> #include <linux/miscdevice.h> -#include <linux/mm.h> #include <linux/module.h> -#include <linux/kdebug.h> #include <linux/moduleparam.h> -#include <linux/notifier.h> #include <linux/pci.h> #include <linux/pci_ids.h> -#include <linux/reboot.h> -#include <linux/sched.h> -#include <linux/timer.h> #include <linux/types.h> #include <linux/uaccess.h> #include <linux/watchdog.h> +#ifdef CONFIG_HPWDT_NMI_DECODING #include <linux/dmi.h> -#include <linux/efi.h> -#include <linux/string.h> -#include <linux/bootmem.h> -#include <linux/slab.h> -#include <asm/dmi.h> -#include <asm/desc.h> -#include <asm/kdebug.h> +#include <linux/spinlock.h> +#include <linux/nmi.h> +#include <linux/kdebug.h> +#include <linux/notifier.h> +#include <asm/cacheflush.h> +#endif /* CONFIG_HPWDT_NMI_DECODING */ +#include <asm/nmi.h> + +#define HPWDT_VERSION "1.3.3" +#define SECS_TO_TICKS(secs) ((secs) * 1000 / 128) +#define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000) +#define HPWDT_MAX_TIMER TICKS_TO_SECS(65535) +#define DEFAULT_MARGIN 30 + +static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ +static unsigned int reload; /* the computed soft_margin */ +static bool nowayout = WATCHDOG_NOWAYOUT; +static char expect_release; +static unsigned long hpwdt_is_open; +static void __iomem *pci_mem_addr; /* the PCI-memory address */ +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 */ + {0}, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, hpwdt_devices); + +#ifdef CONFIG_HPWDT_NMI_DECODING #define PCI_BIOS32_SD_VALUE 0x5F32335F /* "_32_" */ #define CRU_BIOS_SIGNATURE_VALUE 0x55524324 #define PCI_BIOS32_PARAGRAPH_LEN 16 @@ -70,6 +89,19 @@ struct smbios_cru64_info { }; #define SMBIOS_CRU64_INFORMATION 212 +/* type 219 */ +struct smbios_proliant_info { + u8 type; + u8 byte_length; + u16 handle; + u32 power_features; + u32 omega_features; + u32 reserved; + u32 misc_features; +}; +#define SMBIOS_ICRU_INFORMATION 219 + + struct cmn_registers { union { struct { @@ -112,77 +144,63 @@ struct cmn_registers { u32 reflags; } __attribute__((packed)); -#define DEFAULT_MARGIN 30 -static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ -static unsigned int reload; /* the computed soft_margin */ -static int nowayout = WATCHDOG_NOWAYOUT; -static char expect_release; -static unsigned long hpwdt_is_open; - -static void __iomem *pci_mem_addr; /* the PCI-memory address */ -static unsigned long __iomem *hpwdt_timer_reg; -static unsigned long __iomem *hpwdt_timer_con; - +static unsigned int hpwdt_nmi_decoding; +static unsigned int allow_kdump = 1; +static unsigned int is_icru; +static unsigned int is_uefi; static DEFINE_SPINLOCK(rom_lock); - static void *cru_rom_addr; - static struct cmn_registers cmn_regs; -static struct pci_device_id hpwdt_devices[] = { - { - .vendor = PCI_VENDOR_ID_COMPAQ, - .device = 0xB203, - .subvendor = PCI_ANY_ID, - .subdevice = PCI_ANY_ID, - }, - {0}, /* terminate list */ -}; -MODULE_DEVICE_TABLE(pci, hpwdt_devices); +extern asmlinkage void asminline_call(struct cmn_registers *pi86Regs, + unsigned long *pRomEntry); -#ifndef CONFIG_X86_64 +#ifdef CONFIG_X86_32 /* --32 Bit Bios------------------------------------------------------------ */ #define HPWDT_ARCH 32 -static void asminline_call(struct cmn_registers *pi86Regs, - unsigned long *pRomEntry) -{ - asm("pushl %ebp \n\t" - "movl %esp, %ebp \n\t" - "pusha \n\t" - "pushf \n\t" - "push %es \n\t" - "push %ds \n\t" - "pop %es \n\t" - "movl 8(%ebp),%eax \n\t" - "movl 4(%eax),%ebx \n\t" - "movl 8(%eax),%ecx \n\t" - "movl 12(%eax),%edx \n\t" - "movl 16(%eax),%esi \n\t" - "movl 20(%eax),%edi \n\t" - "movl (%eax),%eax \n\t" - "push %cs \n\t" - "call *12(%ebp) \n\t" - "pushf \n\t" - "pushl %eax \n\t" - "movl 8(%ebp),%eax \n\t" - "movl %ebx,4(%eax) \n\t" - "movl %ecx,8(%eax) \n\t" - "movl %edx,12(%eax) \n\t" - "movl %esi,16(%eax) \n\t" - "movl %edi,20(%eax) \n\t" - "movw %ds,24(%eax) \n\t" - "movw %es,26(%eax) \n\t" - "popl %ebx \n\t" - "movl %ebx,(%eax) \n\t" - "popl %ebx \n\t" - "movl %ebx,28(%eax) \n\t" - "pop %es \n\t" - "popf \n\t" - "popa \n\t" - "leave \n\t" "ret"); -} +asm(".text \n\t" + ".align 4 \n\t" + ".globl asminline_call \n" + "asminline_call: \n\t" + "pushl %ebp \n\t" + "movl %esp, %ebp \n\t" + "pusha \n\t" + "pushf \n\t" + "push %es \n\t" + "push %ds \n\t" + "pop %es \n\t" + "movl 8(%ebp),%eax \n\t" + "movl 4(%eax),%ebx \n\t" + "movl 8(%eax),%ecx \n\t" + "movl 12(%eax),%edx \n\t" + "movl 16(%eax),%esi \n\t" + "movl 20(%eax),%edi \n\t" + "movl (%eax),%eax \n\t" + "push %cs \n\t" + "call *12(%ebp) \n\t" + "pushf \n\t" + "pushl %eax \n\t" + "movl 8(%ebp),%eax \n\t" + "movl %ebx,4(%eax) \n\t" + "movl %ecx,8(%eax) \n\t" + "movl %edx,12(%eax) \n\t" + "movl %esi,16(%eax) \n\t" + "movl %edi,20(%eax) \n\t" + "movw %ds,24(%eax) \n\t" + "movw %es,26(%eax) \n\t" + "popl %ebx \n\t" + "movl %ebx,(%eax) \n\t" + "popl %ebx \n\t" + "movl %ebx,28(%eax) \n\t" + "pop %es \n\t" + "popf \n\t" + "popa \n\t" + "leave \n\t" + "ret \n\t" + ".previous"); + /* * cru_detect @@ -195,7 +213,7 @@ static void asminline_call(struct cmn_registers *pi86Regs, * 0 : SUCCESS * <0 : FAILURE */ -static int __devinit cru_detect(unsigned long map_entry, +static int cru_detect(unsigned long map_entry, unsigned long map_offset) { void *bios32_map; @@ -215,35 +233,34 @@ static int __devinit cru_detect(unsigned long map_entry, cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE; + set_memory_x((unsigned long)bios32_map, 2); asminline_call(&cmn_regs, bios32_entrypoint); if (cmn_regs.u1.ral != 0) { - printk(KERN_WARNING - "hpwdt: Call succeeded but with an error: 0x%x\n", - cmn_regs.u1.ral); + pr_warn("Call succeeded but with an error: 0x%x\n", + cmn_regs.u1.ral); } else { physical_bios_base = cmn_regs.u2.rebx; physical_bios_offset = cmn_regs.u4.redx; cru_length = cmn_regs.u3.recx; cru_physical_address = - physical_bios_base + physical_bios_offset; + physical_bios_base + physical_bios_offset; /* If the values look OK, then map it in. */ if ((physical_bios_base + physical_bios_offset)) { cru_rom_addr = - ioremap(cru_physical_address, cru_length); - if (cru_rom_addr) + ioremap(cru_physical_address, cru_length); + if (cru_rom_addr) { + set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, + (cru_length + PAGE_SIZE - 1) >> PAGE_SHIFT); retval = 0; + } } - printk(KERN_DEBUG "hpwdt: CRU Base Address: 0x%lx\n", - physical_bios_base); - printk(KERN_DEBUG "hpwdt: CRU Offset Address: 0x%lx\n", - physical_bios_offset); - printk(KERN_DEBUG "hpwdt: CRU Length: 0x%lx\n", - cru_length); - printk(KERN_DEBUG "hpwdt: CRU Mapped Address: 0x%x\n", - (unsigned int)&cru_rom_addr); + pr_debug("CRU Base Address: 0x%lx\n", physical_bios_base); + pr_debug("CRU Offset Address: 0x%lx\n", physical_bios_offset); + pr_debug("CRU Length: 0x%lx\n", cru_length); + pr_debug("CRU Mapped Address: %p\n", &cru_rom_addr); } iounmap(bios32_map); return retval; @@ -252,7 +269,7 @@ static int __devinit cru_detect(unsigned long map_entry, /* * bios_checksum */ -static int __devinit bios_checksum(const char __iomem *ptr, int len) +static int bios_checksum(const char __iomem *ptr, int len) { char sum = 0; int i; @@ -277,7 +294,7 @@ static int __devinit bios_checksum(const char __iomem *ptr, int len) * 0 : SUCCESS * <0 : FAILURE */ -static int __devinit bios32_present(const char __iomem *p) +static int bios32_present(const char __iomem *p) { struct bios32_service_dir *bios_32_ptr; int length; @@ -307,7 +324,7 @@ static int __devinit bios32_present(const char __iomem *p) return -ENODEV; } -static int __devinit detect_cru_service(void) +static int detect_cru_service(void) { char __iomem *p, *q; int rc = -1; @@ -327,49 +344,51 @@ static int __devinit detect_cru_service(void) iounmap(p); return rc; } - -#else +/* ------------------------------------------------------------------------- */ +#endif /* CONFIG_X86_32 */ +#ifdef CONFIG_X86_64 /* --64 Bit Bios------------------------------------------------------------ */ #define HPWDT_ARCH 64 -static void asminline_call(struct cmn_registers *pi86Regs, - unsigned long *pRomEntry) -{ - asm("pushq %rbp \n\t" - "movq %rsp, %rbp \n\t" - "pushq %rax \n\t" - "pushq %rbx \n\t" - "pushq %rdx \n\t" - "pushq %r12 \n\t" - "pushq %r9 \n\t" - "movq %rsi, %r12 \n\t" - "movq %rdi, %r9 \n\t" - "movl 4(%r9),%ebx \n\t" - "movl 8(%r9),%ecx \n\t" - "movl 12(%r9),%edx \n\t" - "movl 16(%r9),%esi \n\t" - "movl 20(%r9),%edi \n\t" - "movl (%r9),%eax \n\t" - "call *%r12 \n\t" - "pushfq \n\t" - "popq %r12 \n\t" - "popfq \n\t" - "movl %eax, (%r9) \n\t" - "movl %ebx, 4(%r9) \n\t" - "movl %ecx, 8(%r9) \n\t" - "movl %edx, 12(%r9) \n\t" - "movl %esi, 16(%r9) \n\t" - "movl %edi, 20(%r9) \n\t" - "movq %r12, %rax \n\t" - "movl %eax, 28(%r9) \n\t" - "popq %r9 \n\t" - "popq %r12 \n\t" - "popq %rdx \n\t" - "popq %rbx \n\t" - "popq %rax \n\t" - "leave \n\t" "ret"); -} +asm(".text \n\t" + ".align 4 \n\t" + ".globl asminline_call \n" + "asminline_call: \n\t" + "pushq %rbp \n\t" + "movq %rsp, %rbp \n\t" + "pushq %rax \n\t" + "pushq %rbx \n\t" + "pushq %rdx \n\t" + "pushq %r12 \n\t" + "pushq %r9 \n\t" + "movq %rsi, %r12 \n\t" + "movq %rdi, %r9 \n\t" + "movl 4(%r9),%ebx \n\t" + "movl 8(%r9),%ecx \n\t" + "movl 12(%r9),%edx \n\t" + "movl 16(%r9),%esi \n\t" + "movl 20(%r9),%edi \n\t" + "movl (%r9),%eax \n\t" + "call *%r12 \n\t" + "pushfq \n\t" + "popq %r12 \n\t" + "movl %eax, (%r9) \n\t" + "movl %ebx, 4(%r9) \n\t" + "movl %ecx, 8(%r9) \n\t" + "movl %edx, 12(%r9) \n\t" + "movl %esi, 16(%r9) \n\t" + "movl %edi, 20(%r9) \n\t" + "movq %r12, %rax \n\t" + "movl %eax, 28(%r9) \n\t" + "popq %r9 \n\t" + "popq %r12 \n\t" + "popq %rdx \n\t" + "popq %rbx \n\t" + "popq %rax \n\t" + "leave \n\t" + "ret \n\t" + ".previous"); /* * dmi_find_cru @@ -378,7 +397,7 @@ static void asminline_call(struct cmn_registers *pi86Regs, * This function checks whether or not a SMBIOS/DMI record is * the 64bit CRU info or not */ -static void __devinit dmi_find_cru(const struct dmi_header *dm) +static void dmi_find_cru(const struct dmi_header *dm, void *dummy) { struct smbios_cru64_info *smbios_cru64_ptr; unsigned long cru_physical_address; @@ -387,73 +406,46 @@ static void __devinit dmi_find_cru(const struct dmi_header *dm) smbios_cru64_ptr = (struct smbios_cru64_info *) dm; if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) { cru_physical_address = - smbios_cru64_ptr->physical_address + - smbios_cru64_ptr->double_offset; + smbios_cru64_ptr->physical_address + + smbios_cru64_ptr->double_offset; cru_rom_addr = ioremap(cru_physical_address, - smbios_cru64_ptr->double_length); + smbios_cru64_ptr->double_length); + set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, + smbios_cru64_ptr->double_length >> PAGE_SHIFT); } } } -static int __devinit detect_cru_service(void) +static int detect_cru_service(void) { cru_rom_addr = NULL; - dmi_walk(dmi_find_cru); + dmi_walk(dmi_find_cru, NULL); /* if cru_rom_addr has been set then we found a CRU service */ - return ((cru_rom_addr != NULL)? 0: -ENODEV); + return ((cru_rom_addr != NULL) ? 0 : -ENODEV); } - /* ------------------------------------------------------------------------- */ - -#endif - -/* - * NMI Handler - */ -static int hpwdt_pretimeout(struct notifier_block *nb, unsigned long ulReason, - void *data) -{ - static unsigned long rom_pl; - static int die_nmi_called; - - if (ulReason != DIE_NMI && ulReason != DIE_NMI_IPI) - return NOTIFY_OK; - - spin_lock_irqsave(&rom_lock, rom_pl); - if (!die_nmi_called) - asminline_call(&cmn_regs, cru_rom_addr); - die_nmi_called = 1; - spin_unlock_irqrestore(&rom_lock, rom_pl); - if (cmn_regs.u1.ral == 0) { - printk(KERN_WARNING "hpwdt: An NMI occurred, " - "but unable to determine source.\n"); - } else { - panic("An NMI occurred, please see the Integrated " - "Management Log for details.\n"); - } - - return NOTIFY_STOP; -} +#endif /* CONFIG_X86_64 */ +#endif /* CONFIG_HPWDT_NMI_DECODING */ /* * Watchdog operations */ static void hpwdt_start(void) { - reload = (soft_margin * 1000) / 128; + reload = SECS_TO_TICKS(soft_margin); iowrite16(reload, hpwdt_timer_reg); - iowrite16(0x85, hpwdt_timer_con); + iowrite8(0x85, hpwdt_timer_con); } static void hpwdt_stop(void) { unsigned long data; - data = ioread16(hpwdt_timer_con); + data = ioread8(hpwdt_timer_con); data &= 0xFE; - iowrite16(data, hpwdt_timer_con); + iowrite8(data, hpwdt_timer_con); } static void hpwdt_ping(void) @@ -463,23 +455,64 @@ static void hpwdt_ping(void) static int hpwdt_change_timer(int new_margin) { - /* Arbitrary, can't find the card's limits */ - if (new_margin < 30 || new_margin > 600) { - printk(KERN_WARNING - "hpwdt: New value passed in is invalid: %d seconds.\n", + if (new_margin < 1 || new_margin > HPWDT_MAX_TIMER) { + pr_warn("New value passed in is invalid: %d seconds\n", new_margin); return -EINVAL; } soft_margin = new_margin; - printk(KERN_DEBUG - "hpwdt: New timer passed in is %d seconds.\n", - new_margin); - reload = (soft_margin * 1000) / 128; + pr_debug("New timer passed in is %d seconds\n", new_margin); + reload = SECS_TO_TICKS(soft_margin); return 0; } +static int hpwdt_time_left(void) +{ + return TICKS_TO_SECS(ioread16(hpwdt_timer_reg)); +} + +#ifdef CONFIG_HPWDT_NMI_DECODING +/* + * NMI Handler + */ +static int hpwdt_pretimeout(unsigned int ulReason, struct pt_regs *regs) +{ + unsigned long rom_pl; + static int die_nmi_called; + + if (!hpwdt_nmi_decoding) + goto out; + + spin_lock_irqsave(&rom_lock, rom_pl); + if (!die_nmi_called && !is_icru && !is_uefi) + asminline_call(&cmn_regs, cru_rom_addr); + die_nmi_called = 1; + spin_unlock_irqrestore(&rom_lock, rom_pl); + + if (allow_kdump) + hpwdt_stop(); + + if (!is_icru && !is_uefi) { + if (cmn_regs.u1.ral == 0) { + panic("An NMI occurred, " + "but unable to determine source.\n"); + } + } + panic("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"); + +out: + return NMI_DONE; +} +#endif /* CONFIG_HPWDT_NMI_DECODING */ + /* * /dev/watchdog handling */ @@ -502,8 +535,7 @@ static int hpwdt_release(struct inode *inode, struct file *file) if (expect_release == 42) { hpwdt_stop(); } else { - printk(KERN_CRIT - "hpwdt: Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); hpwdt_ping(); } @@ -530,7 +562,7 @@ static ssize_t hpwdt_write(struct file *file, const char __user *data, /* scan to see whether or not we got the magic char. */ for (i = 0; i != len; i++) { char c; - if (get_user(c, data+i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_release = 42; @@ -544,11 +576,11 @@ static ssize_t hpwdt_write(struct file *file, const char __user *data, return len; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .identity = "HP iLO2 HW Watchdog Timer", + .identity = "HP iLO2+ HW Watchdog Timer", }; static long hpwdt_ioctl(struct file *file, unsigned int cmd, @@ -590,6 +622,10 @@ static long hpwdt_ioctl(struct file *file, unsigned int cmd, case WDIOC_GETTIMEOUT: ret = put_user(soft_margin, p); break; + + case WDIOC_GETTIMELEFT: + ret = put_user(hpwdt_time_left(), p); + break; } return ret; } @@ -597,7 +633,7 @@ static long hpwdt_ioctl(struct file *file, unsigned int cmd, /* * Kernel interfaces */ -static struct file_operations hpwdt_fops = { +static const struct file_operations hpwdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = hpwdt_write, @@ -612,30 +648,170 @@ static struct miscdevice hpwdt_miscdev = { .fops = &hpwdt_fops, }; -static struct notifier_block die_notifier = { - .notifier_call = hpwdt_pretimeout, - .priority = 0x7FFFFFFF, -}; - /* * Init & Exit */ -static int __devinit hpwdt_init_one(struct pci_dev *dev, - const struct pci_device_id *ent) +#ifdef CONFIG_HPWDT_NMI_DECODING +#ifdef CONFIG_X86_LOCAL_APIC +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) +{ + /* + * If nmi_watchdog is turned off then we can turn on + * our nmi decoding capability. + */ + hpwdt_nmi_decoding = 1; +} +#else +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) +{ + dev_warn(&dev->dev, "NMI decoding is disabled. " + "Your kernel does not support a NMI Watchdog.\n"); +} +#endif /* CONFIG_X86_LOCAL_APIC */ + +/* + * dmi_find_icru + * + * Routine Description: + * This function checks whether or not we are on an iCRU-based server. + * This check is independent of architecture and needs to be made for + * any ProLiant system. + */ +static void dmi_find_icru(const struct dmi_header *dm, void *dummy) +{ + struct smbios_proliant_info *smbios_proliant_ptr; + + if (dm->type == SMBIOS_ICRU_INFORMATION) { + smbios_proliant_ptr = (struct smbios_proliant_info *) dm; + if (smbios_proliant_ptr->misc_features & 0x01) + is_icru = 1; + if (smbios_proliant_ptr->misc_features & 0x408) + is_uefi = 1; + } +} + +static int hpwdt_init_nmi_decoding(struct pci_dev *dev) { int retval; /* - * First let's find out if we are on an iLO2 server. We will + * On typical CRU-based systems we need to map that service in + * the BIOS. For 32 bit Operating Systems we need to go through + * the 32 Bit BIOS Service Directory. For 64 bit Operating + * Systems we get that service through SMBIOS. + * + * On systems that support the new iCRU service all we need to + * do is call dmi_walk to get the supported flag value and skip + * the old cru detect code. + */ + dmi_walk(dmi_find_icru, NULL); + if (!is_icru && !is_uefi) { + + /* + * We need to map the ROM to get the CRU service. + * For 32 bit Operating Systems we need to go through the 32 Bit + * BIOS Service Directory + * For 64 bit Operating Systems we get that service through SMBIOS. + */ + retval = detect_cru_service(); + if (retval < 0) { + dev_warn(&dev->dev, + "Unable to detect the %d Bit CRU Service.\n", + HPWDT_ARCH); + return retval; + } + + /* + * We know this is the only CRU call we need to make so lets keep as + * few instructions as possible once the NMI comes in. + */ + cmn_regs.u1.rah = 0x0D; + cmn_regs.u1.ral = 0x02; + } + + /* + * 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, + "HP Watchdog Timer Driver: NMI decoding initialized" + ", allow kernel dump: %s (default = 0/OFF)\n", + (allow_kdump == 0) ? "OFF" : "ON"); + 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); + if (cru_rom_addr) + iounmap(cru_rom_addr); + return retval; +} + +static void hpwdt_exit_nmi_decoding(void) +{ + unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); + unregister_nmi_handler(NMI_SERR, "hpwdt"); + unregister_nmi_handler(NMI_IO_CHECK, "hpwdt"); + if (cru_rom_addr) + iounmap(cru_rom_addr); +} +#else /* !CONFIG_HPWDT_NMI_DECODING */ +static void hpwdt_check_nmi_decoding(struct pci_dev *dev) +{ +} + +static int hpwdt_init_nmi_decoding(struct pci_dev *dev) +{ + return 0; +} + +static void hpwdt_exit_nmi_decoding(void) +{ +} +#endif /* CONFIG_HPWDT_NMI_DECODING */ + +static int hpwdt_init_one(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + int retval; + + /* + * Check if we can do NMI decoding or not + */ + hpwdt_check_nmi_decoding(dev); + + /* + * 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_warn(&dev->dev, - "This server does not have an iLO2 ASIC.\n"); + "This server does not have an iLO2+ ASIC.\n"); return -ENODEV; } + /* + * Ignore all auxilary iLO devices with the following PCI ID + */ + if (dev->subsystem_device == 0x1979) + return -ENODEV; + if (pci_enable_device(dev)) { dev_warn(&dev->dev, "Not possible to enable PCI Device: 0x%x:0x%x.\n", @@ -646,45 +822,24 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, pci_mem_addr = pci_iomap(dev, 1, 0x80); if (!pci_mem_addr) { dev_warn(&dev->dev, - "Unable to detect the iLO2 server memory.\n"); + "Unable to detect the iLO2+ server memory.\n"); retval = -ENOMEM; goto error_pci_iomap; } hpwdt_timer_reg = pci_mem_addr + 0x70; hpwdt_timer_con = pci_mem_addr + 0x72; + /* Make sure that timer is disabled until /dev/watchdog is opened */ + hpwdt_stop(); + /* Make sure that we have a valid soft_margin */ if (hpwdt_change_timer(soft_margin)) hpwdt_change_timer(DEFAULT_MARGIN); - /* - * We need to map the ROM to get the CRU service. - * For 32 bit Operating Systems we need to go through the 32 Bit - * BIOS Service Directory - * For 64 bit Operating Systems we get that service through SMBIOS. - */ - retval = detect_cru_service(); - if (retval < 0) { - dev_warn(&dev->dev, - "Unable to detect the %d Bit CRU Service.\n", - HPWDT_ARCH); - goto error_get_cru; - } - - /* - * We know this is the only CRU call we need to make so lets keep as - * few instructions as possible once the NMI comes in. - */ - cmn_regs.u1.rah = 0x0D; - cmn_regs.u1.ral = 0x02; - - retval = register_die_notifier(&die_notifier); - if (retval != 0) { - dev_warn(&dev->dev, - "Unable to register a die notifier (err=%d).\n", - retval); - goto error_die_notifier; - } + /* Initialize NMI Decoding functionality */ + retval = hpwdt_init_nmi_decoding(dev); + if (retval != 0) + goto error_init_nmi_decoding; retval = misc_register(&hpwdt_miscdev); if (retval < 0) { @@ -694,35 +849,27 @@ static int __devinit hpwdt_init_one(struct pci_dev *dev, goto error_misc_register; } - printk(KERN_INFO - "hp Watchdog Timer Driver: 1.00" - ", timer margin: %d seconds( nowayout=%d).\n", - soft_margin, nowayout); - + dev_info(&dev->dev, "HP Watchdog Timer Driver: %s" + ", timer margin: %d seconds (nowayout=%d).\n", + HPWDT_VERSION, soft_margin, nowayout); return 0; error_misc_register: - unregister_die_notifier(&die_notifier); -error_die_notifier: - if (cru_rom_addr) - iounmap(cru_rom_addr); -error_get_cru: + 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 __devexit hpwdt_exit(struct pci_dev *dev) +static void hpwdt_exit(struct pci_dev *dev) { if (!nowayout) hpwdt_stop(); misc_deregister(&hpwdt_miscdev); - unregister_die_notifier(&die_notifier); - - if (cru_rom_addr) - iounmap(cru_rom_addr); + hpwdt_exit_nmi_decoding(); pci_iounmap(dev, pci_mem_addr); pci_disable_device(dev); } @@ -731,30 +878,24 @@ static struct pci_driver hpwdt_driver = { .name = "hpwdt", .id_table = hpwdt_devices, .probe = hpwdt_init_one, - .remove = __devexit_p(hpwdt_exit), + .remove = hpwdt_exit, }; -static void __exit hpwdt_cleanup(void) -{ - pci_unregister_driver(&hpwdt_driver); -} - -static int __init hpwdt_init(void) -{ - return pci_register_driver(&hpwdt_driver); -} - MODULE_AUTHOR("Tom Mingarelli"); MODULE_DESCRIPTION("hp watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_VERSION(HPWDT_VERSION); module_param(soft_margin, int, 0); MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -module_init(hpwdt_init); -module_exit(hpwdt_cleanup); +#ifdef CONFIG_HPWDT_NMI_DECODING +module_param(allow_kdump, int, 0); +MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); +#endif /* !CONFIG_HPWDT_NMI_DECODING */ + +module_pci_driver(hpwdt_driver); diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index ca44fd9b19b..d7befd58b39 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -9,24 +9,26 @@ * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * - * based on i810-tco.c which is in turn based on softdog.c + * 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-003 + * 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 + * Version 0.02 * 20050210 David Härdeman <david@2gen.com> - * Ported driver to kernel 2.6 + * Ported driver to kernel 2.6 */ /* * Includes, defines, variables, module parameters, ... */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> @@ -34,42 +36,39 @@ #include <linux/mm.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> -#include <linux/reboot.h> -#include <linux/init.h> #include <linux/pci.h> #include <linux/ioport.h> - -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/uaccess.h> +#include <linux/io.h> /* Module and version information */ -#define ESB_VERSION "0.03" +#define ESB_VERSION "0.05" #define ESB_MODULE_NAME "i6300ESB timer" #define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION -#define PFX ESB_MODULE_NAME ": " /* 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 BASEADDR + 0x00 /* Timer1 value after each reset */ -#define ESB_TIMER2_REG BASEADDR + 0x04 /* Timer2 value after each reset */ -#define ESB_GINTSR_REG BASEADDR + 0x08 /* General Interrupt Status Register */ -#define ESB_RELOAD_REG BASEADDR + 0x0c /* Reload register */ +#define ESB_TIMER1_REG (BASEADDR + 0x00)/* Timer1 value after each reset */ +#define ESB_TIMER2_REG (BASEADDR + 0x04)/* Timer2 value after each reset */ +#define ESB_GINTSR_REG (BASEADDR + 0x08)/* General Interrupt Status Register */ +#define ESB_RELOAD_REG (BASEADDR + 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) */ +#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 ( 0x11 << 0 ) /* Interrupt type on timer1 timeout */ +#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_RELOAD ( 0x01 << 8 ) /* prevent timeout */ +#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 */ @@ -83,15 +82,23 @@ static struct pci_dev *esb_pci; static unsigned short triggered; /* The status of the watchdog upon boot */ static char esb_expect_close; +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + /* module parameters */ -#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (1<heartbeat<2*1023) */ +/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */ +#define WATCHDOG_HEARTBEAT 30 static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<heartbeat<2046, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (1<heartbeat<2046, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 i6300ESB specific functions @@ -103,19 +110,24 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" _ * 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(void) { - writeb(ESB_UNLOCK1, ESB_RELOAD_REG); - writeb(ESB_UNLOCK2, ESB_RELOAD_REG); +static inline void esb_unlock_registers(void) +{ + writew(ESB_UNLOCK1, ESB_RELOAD_REG); + writew(ESB_UNLOCK2, ESB_RELOAD_REG); } -static void esb_timer_start(void) +static int esb_timer_start(void) { u8 val; + spin_lock(&esb_lock); + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); /* Enable or Enable + Lock? */ - val = 0x02 | (nowayout ? 0x01 : 0x00); - - pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); + val = ESB_WDT_ENABLE | (nowayout ? ESB_WDT_LOCK : 0x00); + pci_write_config_byte(esb_pci, ESB_LOCK_REG, val); + spin_unlock(&esb_lock); + return 0; } static int esb_timer_stop(void) @@ -132,7 +144,7 @@ static int esb_timer_stop(void) spin_unlock(&esb_lock); /* Returns 0 if the timer was disabled, non-zero otherwise */ - return (val & 0x01); + return val & ESB_WDT_ENABLE; } static void esb_timer_keepalive(void) @@ -140,7 +152,7 @@ static void esb_timer_keepalive(void) spin_lock(&esb_lock); esb_unlock_registers(); writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); - /* FIXME: Do we need to flush anything here? */ + /* FIXME: Do we need to flush anything here? */ spin_unlock(&esb_lock); } @@ -165,9 +177,9 @@ static int esb_timer_set_heartbeat(int time) /* Write timer 2 */ esb_unlock_registers(); - writel(val, ESB_TIMER2_REG); + writel(val, ESB_TIMER2_REG); - /* Reload */ + /* Reload */ esb_unlock_registers(); writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); @@ -179,54 +191,41 @@ static int esb_timer_set_heartbeat(int time) return 0; } -static int esb_timer_read (void) -{ - u32 count; - - /* This isn't documented, and doesn't take into - * acount which stage is running, but it looks - * like a 20 bit count down, so we might as well report it. - */ - pci_read_config_dword(esb_pci, 0x64, &count); - return (int)count; -} - /* - * /dev/watchdog handling + * /dev/watchdog handling */ -static int esb_open (struct inode *inode, struct file *file) +static int esb_open(struct inode *inode, struct file *file) { - /* /dev/watchdog can only be opened once */ - if (test_and_set_bit(0, &timer_alive)) - return -EBUSY; + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; - /* Reload and activate timer */ - esb_timer_keepalive (); - esb_timer_start (); + /* Reload and activate timer */ + esb_timer_start(); return nonseekable_open(inode, file); } -static int esb_release (struct inode *inode, struct file *file) +static int esb_release(struct inode *inode, struct file *file) { - /* Shut off the timer. */ - if (esb_expect_close == 42) { - esb_timer_stop (); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - esb_timer_keepalive (); - } - clear_bit(0, &timer_alive); - esb_expect_close = 0; - return 0; + /* Shut off the timer. */ + if (esb_expect_close == 42) + esb_timer_stop(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + esb_timer_keepalive(); + } + clear_bit(0, &timer_alive); + esb_expect_close = 0; + return 0; } -static ssize_t esb_write (struct file *file, const char __user *data, - size_t len, loff_t * ppos) +static ssize_t esb_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 (len) { if (!nowayout) { size_t i; @@ -234,10 +233,11 @@ static ssize_t esb_write (struct file *file, const char __user *data, * five months ago... */ esb_expect_close = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') esb_expect_close = 42; @@ -245,92 +245,70 @@ static ssize_t esb_write (struct file *file, const char __user *data, } /* someone wrote to us, we should reload the timer */ - esb_timer_keepalive (); + esb_timer_keepalive(); } return len; } -static int esb_ioctl (struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long esb_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 struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = ESB_MODULE_NAME, + .firmware_version = 0, + .identity = ESB_MODULE_NAME, }; switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, - sizeof (ident)) ? -EFAULT : 0; - - case WDIOC_GETSTATUS: - return put_user (esb_timer_read(), p); - - case WDIOC_GETBOOTSTATUS: - return put_user (triggered, p); - - case WDIOC_KEEPALIVE: - esb_timer_keepalive (); - return 0; + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; - case WDIOC_SETOPTIONS: - { - if (get_user (new_options, p)) - return -EFAULT; + case WDIOC_GETSTATUS: + return put_user(0, p); - if (new_options & WDIOS_DISABLECARD) { - esb_timer_stop (); - retval = 0; - } + case WDIOC_GETBOOTSTATUS: + return put_user(triggered, p); - if (new_options & WDIOS_ENABLECARD) { - esb_timer_keepalive (); - esb_timer_start (); - retval = 0; - } + case WDIOC_SETOPTIONS: + { + if (get_user(new_options, p)) + return -EFAULT; - return retval; - } - - case WDIOC_SETTIMEOUT: - { - if (get_user(new_heartbeat, p)) - return -EFAULT; - - if (esb_timer_set_heartbeat(new_heartbeat)) - return -EINVAL; - - esb_timer_keepalive (); - /* Fall */ - } - - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - - default: - return -ENOTTY; - } -} - -/* - * Notify system - */ - -static int esb_notify_sys (struct notifier_block *this, unsigned long code, void *unused) -{ - if (code==SYS_DOWN || code==SYS_HALT) { - /* Turn the WDT off */ - esb_timer_stop (); - } + if (new_options & WDIOS_DISABLECARD) { + esb_timer_stop(); + retval = 0; + } - return NOTIFY_DONE; + if (new_options & WDIOS_ENABLECARD) { + esb_timer_start(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + esb_timer_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + { + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (esb_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + esb_timer_keepalive(); + /* Fall */ + } + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } } /* @@ -338,188 +316,183 @@ static int esb_notify_sys (struct notifier_block *this, unsigned long code, void */ static const struct file_operations esb_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = esb_write, - .ioctl = esb_ioctl, - .open = esb_open, - .release = esb_release, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = esb_write, + .unlocked_ioctl = esb_ioctl, + .open = esb_open, + .release = esb_release, }; static struct miscdevice esb_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &esb_fops, -}; - -static struct notifier_block esb_notifier = { - .notifier_call = esb_notify_sys, + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &esb_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 struct pci_device_id esb_pci_tbl[] = { - { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), }, - { 0, }, /* End of list */ +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); +MODULE_DEVICE_TABLE(pci, esb_pci_tbl); /* * Init & exit routines */ -static unsigned char __init esb_getdevice (void) +static unsigned char esb_getdevice(struct pci_dev *pdev) { - u8 val1; - unsigned short val2; - - struct pci_dev *dev = NULL; - /* - * Find the PCI device - */ - - for_each_pci_dev(dev) { - if (pci_match_id(esb_pci_tbl, dev)) { - esb_pci = dev; - break; - } + if (pci_enable_device(pdev)) { + pr_err("failed to enable device\n"); + goto err_devput; } - if (esb_pci) { - if (pci_enable_device(esb_pci)) { - printk (KERN_ERR PFX "failed to enable device\n"); - goto err_devput; - } + if (pci_request_region(pdev, 0, ESB_MODULE_NAME)) { + pr_err("failed to request region\n"); + goto err_disable; + } - if (pci_request_region(esb_pci, 0, ESB_MODULE_NAME)) { - printk (KERN_ERR PFX "failed to request region\n"); - goto err_disable; - } + BASEADDR = pci_ioremap_bar(pdev, 0); + if (BASEADDR == NULL) { + /* Something's wrong here, BASEADDR has to be set */ + pr_err("failed to get BASEADDR\n"); + goto err_release; + } - BASEADDR = ioremap(pci_resource_start(esb_pci, 0), - pci_resource_len(esb_pci, 0)); - if (BASEADDR == NULL) { - /* Something's wrong here, BASEADDR has to be set */ - printk (KERN_ERR PFX "failed to get BASEADDR\n"); - goto err_release; - } - - /* - * 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. - * - * We also enable reboots and set the timer frequency to - * the PCI clock divided by 2^15 (approx 1KHz). - */ - pci_write_config_word(esb_pci, ESB_CONFIG_REG, 0x0003); - - /* Check that the WDT isn't already locked */ - pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1); - if (val1 & ESB_WDT_LOCK) - printk (KERN_WARNING PFX "nowayout already set\n"); - - /* Set the timer to watchdog mode and disable it for now */ - pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00); - - /* Check if the watchdog was previously triggered */ - esb_unlock_registers(); - val2 = readw(ESB_RELOAD_REG); - triggered = (val2 & (0x01 << 9) >> 9); - - /* Reset trigger flag and timers */ - esb_unlock_registers(); - writew((0x11 << 8), ESB_RELOAD_REG); - - /* Done */ - return 1; + /* Done */ + esb_pci = pdev; + return 1; err_release: - pci_release_region(esb_pci, 0); + pci_release_region(pdev, 0); err_disable: - pci_disable_device(esb_pci); + pci_disable_device(pdev); err_devput: - pci_dev_put(esb_pci); - } return 0; } -static int __init watchdog_init (void) +static void esb_initdevice(void) +{ + 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(esb_pci, ESB_CONFIG_REG, 0x0003); + + /* Check that the WDT isn't already locked */ + pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1); + if (val1 & ESB_WDT_LOCK) + pr_warn("nowayout already set\n"); + + /* Set the timer to watchdog mode and disable it for now */ + pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00); + + /* Check if the watchdog was previously triggered */ + esb_unlock_registers(); + val2 = readw(ESB_RELOAD_REG); + if (val2 & ESB_WDT_TIMEOUT) + triggered = WDIOF_CARDRESET; + + /* Reset WDT_TIMEOUT flag and timers */ + esb_unlock_registers(); + writew((ESB_WDT_TIMEOUT | ESB_WDT_RELOAD), ESB_RELOAD_REG); + + /* And set the correct timeout value */ + esb_timer_set_heartbeat(heartbeat); +} + +static int esb_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) { - int ret; - - /* Check whether or not the hardware watchdog is there */ - if (!esb_getdevice () || esb_pci == NULL) - return -ENODEV; - - /* Check that the heartbeat value is within it's range ; if not reset to the default */ - if (esb_timer_set_heartbeat (heartbeat)) { - esb_timer_set_heartbeat (WATCHDOG_HEARTBEAT); - printk(KERN_INFO PFX "heartbeat value must be 1<heartbeat<2046, using %d\n", - heartbeat); - } - - ret = register_reboot_notifier(&esb_notifier); - if (ret != 0) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); - goto err_unmap; - } - - ret = misc_register(&esb_miscdev); - if (ret != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto err_notifier; - } - - esb_timer_stop (); - - printk (KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", - BASEADDR, heartbeat, nowayout); - - return 0; - -err_notifier: - unregister_reboot_notifier(&esb_notifier); + int ret; + + cards_found++; + if (cards_found == 1) + pr_info("Intel 6300ESB WatchDog Timer Driver v%s\n", + ESB_VERSION); + + if (cards_found > 1) { + pr_err("This driver only supports 1 device\n"); + return -ENODEV; + } + + /* Check whether or not the hardware watchdog is there */ + if (!esb_getdevice(pdev) || esb_pci == NULL) + return -ENODEV; + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ + if (heartbeat < 0x1 || heartbeat > 2 * 0x03ff) { + heartbeat = WATCHDOG_HEARTBEAT; + pr_info("heartbeat value must be 1<heartbeat<2046, using %d\n", + heartbeat); + } + + /* Initialize the watchdog and make sure it does not run */ + esb_initdevice(); + + /* Register the watchdog so that userspace has access to it */ + ret = misc_register(&esb_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto err_unmap; + } + pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", + BASEADDR, heartbeat, nowayout); + return 0; + err_unmap: iounmap(BASEADDR); -/* err_release: */ pci_release_region(esb_pci, 0); -/* err_disable: */ pci_disable_device(esb_pci); -/* err_devput: */ - pci_dev_put(esb_pci); - return ret; + esb_pci = NULL; + return ret; } -static void __exit watchdog_cleanup (void) +static void esb_remove(struct pci_dev *pdev) { /* Stop the timer before we leave */ if (!nowayout) - esb_timer_stop (); + esb_timer_stop(); /* Deregister */ misc_deregister(&esb_miscdev); - unregister_reboot_notifier(&esb_notifier); iounmap(BASEADDR); pci_release_region(esb_pci, 0); pci_disable_device(esb_pci); - pci_dev_put(esb_pci); + esb_pci = NULL; } -module_init(watchdog_init); -module_exit(watchdog_cleanup); +static void esb_shutdown(struct pci_dev *pdev) +{ + esb_timer_stop(); +} + +static struct pci_driver esb_driver = { + .name = ESB_MODULE_NAME, + .id_table = esb_pci_tbl, + .probe = esb_probe, + .remove = esb_remove, + .shutdown = esb_shutdown, +}; + +module_pci_driver(esb_driver); MODULE_AUTHOR("Ross Biro and David Härdeman"); MODULE_DESCRIPTION("Watchdog driver for Intel 6300ESB chipsets"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/iTCO_vendor.h b/drivers/watchdog/iTCO_vendor.h new file mode 100644 index 00000000000..3c57b45537a --- /dev/null +++ b/drivers/watchdog/iTCO_vendor.h @@ -0,0 +1,15 @@ +/* iTCO Vendor Specific Support hooks */ +#ifdef CONFIG_ITCO_VENDOR_SUPPORT +extern void iTCO_vendor_pre_start(struct resource *, unsigned int); +extern void iTCO_vendor_pre_stop(struct resource *); +extern void iTCO_vendor_pre_keepalive(struct resource *, unsigned int); +extern void iTCO_vendor_pre_set_heartbeat(unsigned int); +extern int iTCO_vendor_check_noreboot_on(void); +#else +#define iTCO_vendor_pre_start(acpibase, heartbeat) {} +#define iTCO_vendor_pre_stop(acpibase) {} +#define iTCO_vendor_pre_keepalive(acpibase, heartbeat) {} +#define iTCO_vendor_pre_set_heartbeat(heartbeat) {} +#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 index cafc465f2ae..b6b2f90b5d4 100644 --- a/drivers/watchdog/iTCO_vendor_support.c +++ b/drivers/watchdog/iTCO_vendor_support.c @@ -1,7 +1,7 @@ /* * intel TCO vendor specific watchdog driver support * - * (c) Copyright 2006 Wim Van Sebroeck <wim@iguana.be>. + * (c) Copyright 2006-2009 Wim Van Sebroeck <wim@iguana.be>. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,11 +17,11 @@ * 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.01" -#define DRV_RELDATE "11-Nov-2006" -#define PFX DRV_NAME ": " +#define DRV_NAME "iTCO_vendor_support" +#define DRV_VERSION "1.04" /* Includes */ #include <linux/module.h> /* For module specific items */ @@ -31,21 +31,23 @@ #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 <asm/io.h> /* For inb/outb/... */ - -/* iTCO defines */ -#define SMI_EN acpibase + 0x30 /* SMI Control and Enable Register */ -#define TCOBASE acpibase + 0x60 /* TCO base address */ -#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ +#include "iTCO_vendor.h" /* List of vendor support modes */ -#define SUPERMICRO_OLD_BOARD 1 /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ -#define SUPERMICRO_NEW_BOARD 2 /* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */ - -static int vendorsupport = 0; +/* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ +#define SUPERMICRO_OLD_BOARD 1 +/* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */ +#define SUPERMICRO_NEW_BOARD 2 +/* Broken BIOS */ +#define BROKEN_BIOS 911 + +static int vendorsupport; module_param(vendorsupport, int, 0); -MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=0 (none), 1=SuperMicro Pent3, 2=SuperMicro Pent4+"); +MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=" + "0 (none), 1=SuperMicro Pent3, 2=SuperMicro Pent4+, " + "911=Broken SMI BIOS"); /* * Vendor Specific Support @@ -75,29 +77,24 @@ MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=0 (n * 20.6 seconds. */ -static void supermicro_old_pre_start(unsigned long acpibase) +static void supermicro_old_pre_start(struct resource *smires) { unsigned long val32; - val32 = inl(SMI_EN); + /* Bit 13: TCO_EN -> 0 = Disables TCO logic generating an SMI# */ + val32 = inl(smires->start); val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ - outl(val32, SMI_EN); /* Needed to activate watchdog */ + outl(val32, smires->start); /* Needed to activate watchdog */ } -static void supermicro_old_pre_stop(unsigned long acpibase) +static void supermicro_old_pre_stop(struct resource *smires) { unsigned long val32; - val32 = inl(SMI_EN); - val32 &= 0x00002000; /* Turn on SMI clearing watchdog */ - outl(val32, SMI_EN); /* Needed to deactivate watchdog */ -} - -static void supermicro_old_pre_keepalive(unsigned long acpibase) -{ - /* Reload TCO Timer (done in iTCO_wdt_keepalive) + */ - /* Clear "Expire Flag" (Bit 3 of TC01_STS register) */ - outb(0x08, TCO1_STS); + /* 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 */ } /* @@ -143,34 +140,35 @@ static void supermicro_old_pre_keepalive(unsigned long acpibase) */ /* I/O Port's */ -#define SM_REGINDEX 0x2e /* SuperMicro ICH4+ Register Index */ -#define SM_DATAIO 0x2f /* SuperMicro ICH4+ Register Data I/O */ +#define SM_REGINDEX 0x2e /* SuperMicro ICH4+ Register Index */ +#define SM_DATAIO 0x2f /* SuperMicro ICH4+ Register Data I/O */ /* Control Register's */ -#define SM_CTLPAGESW 0x07 /* SuperMicro ICH4+ Control Page Switch */ -#define SM_CTLPAGE 0x08 /* SuperMicro ICH4+ Control Page Num */ +#define SM_CTLPAGESW 0x07 /* SuperMicro ICH4+ Control Page Switch */ +#define SM_CTLPAGE 0x08 /* SuperMicro ICH4+ Control Page Num */ -#define SM_WATCHENABLE 0x30 /* Watchdog enable: Bit 0: 0=off, 1=on */ +#define SM_WATCHENABLE 0x30 /* Watchdog enable: Bit 0: 0=off, 1=on */ -#define SM_WATCHPAGE 0x87 /* Watchdog unlock control page */ +#define SM_WATCHPAGE 0x87 /* Watchdog unlock control page */ -#define SM_ENDWATCH 0xAA /* Watchdog lock control page */ +#define SM_ENDWATCH 0xAA /* Watchdog lock control page */ -#define SM_COUNTMODE 0xf5 /* Watchdog count mode select */ - /* (Bit 3: 0 = seconds, 1 = minutes */ +#define SM_COUNTMODE 0xf5 /* Watchdog count mode select */ + /* (Bit 3: 0 = seconds, 1 = minutes */ -#define SM_WATCHTIMER 0xf6 /* 8-bits, Watchdog timer counter (RW) */ +#define SM_WATCHTIMER 0xf6 /* 8-bits, Watchdog timer counter (RW) */ -#define SM_RESETCONTROL 0xf7 /* Watchdog reset control */ - /* Bit 6: timer is reset by kbd interrupt */ - /* Bit 7: timer is reset by mouse interrupt */ +#define SM_RESETCONTROL 0xf7 /* Watchdog reset control */ + /* Bit 6: timer is reset by kbd interrupt */ + /* Bit 7: timer is reset by mouse interrupt */ static void supermicro_new_unlock_watchdog(void) { - outb(SM_WATCHPAGE, SM_REGINDEX); /* Write 0x87 to port 0x2e twice */ + /* Write 0x87 to port 0x2e twice */ outb(SM_WATCHPAGE, SM_REGINDEX); - - outb(SM_CTLPAGESW, SM_REGINDEX); /* Switch to watchdog control page */ + outb(SM_WATCHPAGE, SM_REGINDEX); + /* Switch to watchdog control page */ + outb(SM_CTLPAGESW, SM_REGINDEX); outb(SM_CTLPAGE, SM_DATAIO); } @@ -192,7 +190,7 @@ static void supermicro_new_pre_start(unsigned int heartbeat) outb(val, SM_DATAIO); /* Write heartbeat interval to WDOG */ - outb (SM_WATCHTIMER, SM_REGINDEX); + outb(SM_WATCHTIMER, SM_REGINDEX); outb((heartbeat & 255), SM_DATAIO); /* Make sure keyboard/mouse interrupts don't interfere */ @@ -237,33 +235,98 @@ static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat) } /* + * 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(unsigned long acpibase, +void iTCO_vendor_pre_start(struct resource *smires, unsigned int heartbeat) { - if (vendorsupport == SUPERMICRO_OLD_BOARD) - supermicro_old_pre_start(acpibase); - else if (vendorsupport == SUPERMICRO_NEW_BOARD) + switch (vendorsupport) { + case SUPERMICRO_OLD_BOARD: + supermicro_old_pre_start(smires); + break; + case SUPERMICRO_NEW_BOARD: supermicro_new_pre_start(heartbeat); + break; + case BROKEN_BIOS: + broken_bios_start(smires); + break; + } } EXPORT_SYMBOL(iTCO_vendor_pre_start); -void iTCO_vendor_pre_stop(unsigned long acpibase) +void iTCO_vendor_pre_stop(struct resource *smires) { - if (vendorsupport == SUPERMICRO_OLD_BOARD) - supermicro_old_pre_stop(acpibase); - else if (vendorsupport == SUPERMICRO_NEW_BOARD) + switch (vendorsupport) { + case SUPERMICRO_OLD_BOARD: + supermicro_old_pre_stop(smires); + break; + case SUPERMICRO_NEW_BOARD: supermicro_new_pre_stop(); + break; + case BROKEN_BIOS: + broken_bios_stop(smires); + break; + } } EXPORT_SYMBOL(iTCO_vendor_pre_stop); -void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat) +void iTCO_vendor_pre_keepalive(struct resource *smires, unsigned int heartbeat) { - if (vendorsupport == SUPERMICRO_OLD_BOARD) - supermicro_old_pre_keepalive(acpibase); - else if (vendorsupport == SUPERMICRO_NEW_BOARD) + if (vendorsupport == SUPERMICRO_NEW_BOARD) supermicro_new_pre_set_heartbeat(heartbeat); } EXPORT_SYMBOL(iTCO_vendor_pre_keepalive); @@ -277,7 +340,7 @@ EXPORT_SYMBOL(iTCO_vendor_pre_set_heartbeat); int iTCO_vendor_check_noreboot_on(void) { - switch(vendorsupport) { + switch (vendorsupport) { case SUPERMICRO_OLD_BOARD: return 0; default: @@ -288,19 +351,20 @@ EXPORT_SYMBOL(iTCO_vendor_check_noreboot_on); static int __init iTCO_vendor_init_module(void) { - printk (KERN_INFO PFX "vendor-support=%d\n", vendorsupport); + pr_info("vendor-support=%d\n", vendorsupport); return 0; } static void __exit iTCO_vendor_exit_module(void) { - printk (KERN_INFO PFX "Module Unloaded\n"); + 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_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 index a0e6809e369..0ba1b7c9976 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -1,7 +1,7 @@ /* - * intel TCO Watchdog Driver (Used in i82801 and i6300ESB chipsets) + * intel TCO Watchdog Driver * - * (c) Copyright 2006-2007 Wim Van Sebroeck <wim@iguana.be>. + * (c) Copyright 2006-2011 Wim Van Sebroeck <wim@iguana.be>. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -14,50 +14,41 @@ * * The TCO watchdog is implemented in the following I/O controller hubs: * (See the intel documentation on http://developer.intel.com.) - * 82801AA (ICH) : document number 290655-003, 290677-014, - * 82801AB (ICHO) : document number 290655-003, 290677-014, - * 82801BA (ICH2) : document number 290687-002, 298242-027, - * 82801BAM (ICH2-M) : document number 290687-002, 298242-027, - * 82801CA (ICH3-S) : document number 290733-003, 290739-013, - * 82801CAM (ICH3-M) : document number 290716-001, 290718-007, - * 82801DB (ICH4) : document number 290744-001, 290745-020, - * 82801DBM (ICH4-M) : document number 252337-001, 252663-005, - * 82801E (C-ICH) : document number 273599-001, 273645-002, - * 82801EB (ICH5) : document number 252516-001, 252517-003, - * 82801ER (ICH5R) : document number 252516-001, 252517-003, - * 82801FB (ICH6) : document number 301473-002, 301474-007, - * 82801FR (ICH6R) : document number 301473-002, 301474-007, - * 82801FBM (ICH6-M) : document number 301473-002, 301474-007, - * 82801FW (ICH6W) : document number 301473-001, 301474-007, - * 82801FRW (ICH6RW) : document number 301473-001, 301474-007, - * 82801GB (ICH7) : document number 307013-002, 307014-009, - * 82801GR (ICH7R) : document number 307013-002, 307014-009, - * 82801GDH (ICH7DH) : document number 307013-002, 307014-009, - * 82801GBM (ICH7-M) : document number 307013-002, 307014-009, - * 82801GHM (ICH7-M DH) : document number 307013-002, 307014-009, - * 82801HB (ICH8) : document number 313056-003, 313057-009, - * 82801HR (ICH8R) : document number 313056-003, 313057-009, - * 82801HBM (ICH8M) : document number 313056-003, 313057-009, - * 82801HH (ICH8DH) : document number 313056-003, 313057-009, - * 82801HO (ICH8DO) : document number 313056-003, 313057-009, - * 82801HEM (ICH8M-E) : document number 313056-003, 313057-009, - * 82801IB (ICH9) : document number 316972-001, 316973-001, - * 82801IR (ICH9R) : document number 316972-001, 316973-001, - * 82801IH (ICH9DH) : document number 316972-001, 316973-001, - * 6300ESB (6300ESB) : document number 300641-003, 300884-010, - * 631xESB (631xESB) : document number 313082-001, 313075-005, - * 632xESB (632xESB) : document number 313082-001, 313075-005 + * 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, ... */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + /* Module and version information */ -#define DRV_NAME "iTCO_wdt" -#define DRV_VERSION "1.02" -#define DRV_RELDATE "26-Jul-2007" -#define PFX DRV_NAME ": " +#define DRV_NAME "iTCO_wdt" +#define DRV_VERSION "1.11" /* Includes */ #include <linux/module.h> /* For module specific items */ @@ -65,7 +56,6 @@ #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 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ #include <linux/watchdog.h> /* For the watchdog specific items */ #include <linux/init.h> /* For __init/__exit/... */ #include <linux/fs.h> /* For file operations */ @@ -73,195 +63,84 @@ #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/mfd/core.h> +#include <linux/mfd/lpc_ich.h> -#include <asm/uaccess.h> /* For copy_to_user/put_user/... */ -#include <asm/io.h> /* For inb/outb/... */ - -/* TCO related info */ -enum iTCO_chipsets { - TCO_ICH = 0, /* ICH */ - TCO_ICH0, /* ICH0 */ - TCO_ICH2, /* ICH2 */ - TCO_ICH2M, /* ICH2-M */ - TCO_ICH3, /* ICH3-S */ - TCO_ICH3M, /* ICH3-M */ - TCO_ICH4, /* ICH4 */ - TCO_ICH4M, /* ICH4-M */ - TCO_CICH, /* C-ICH */ - TCO_ICH5, /* ICH5 & ICH5R */ - TCO_6300ESB, /* 6300ESB */ - TCO_ICH6, /* ICH6 & ICH6R */ - TCO_ICH6M, /* ICH6-M */ - TCO_ICH6W, /* ICH6W & ICH6RW */ - TCO_ICH7, /* ICH7 & ICH7R */ - TCO_ICH7M, /* ICH7-M */ - TCO_ICH7MDH, /* ICH7-M DH */ - TCO_ICH8, /* ICH8 & ICH8R */ - TCO_ICH8ME, /* ICH8M-E */ - TCO_ICH8DH, /* ICH8DH */ - TCO_ICH8DO, /* ICH8DO */ - TCO_ICH8M, /* ICH8M */ - TCO_ICH9, /* ICH9 */ - TCO_ICH9R, /* ICH9R */ - TCO_ICH9DH, /* ICH9DH */ - TCO_631XESB, /* 631xESB/632xESB */ -}; - -static struct { - char *name; - unsigned int iTCO_version; -} iTCO_chipset_info[] __devinitdata = { - {"ICH", 1}, - {"ICH0", 1}, - {"ICH2", 1}, - {"ICH2-M", 1}, - {"ICH3-S", 1}, - {"ICH3-M", 1}, - {"ICH4", 1}, - {"ICH4-M", 1}, - {"C-ICH", 1}, - {"ICH5 or ICH5R", 1}, - {"6300ESB", 1}, - {"ICH6 or ICH6R", 2}, - {"ICH6-M", 2}, - {"ICH6W or ICH6RW", 2}, - {"ICH7 or ICH7R", 2}, - {"ICH7-M", 2}, - {"ICH7-M DH", 2}, - {"ICH8 or ICH8R", 2}, - {"ICH8M-E", 2}, - {"ICH8DH", 2}, - {"ICH8DO", 2}, - {"ICH8M", 2}, - {"ICH9", 2}, - {"ICH9R", 2}, - {"ICH9DH", 2}, - {"631xESB/632xESB", 2}, - {NULL,0} -}; - -#define ITCO_PCI_DEVICE(dev, data) \ - .vendor = PCI_VENDOR_ID_INTEL, \ - .device = dev, \ - .subvendor = PCI_ANY_ID, \ - .subdevice = PCI_ANY_ID, \ - .class = 0, \ - .class_mask = 0, \ - .driver_data = data - -/* - * This data only exists for exporting the supported PCI ids - * via MODULE_DEVICE_TABLE. We do not actually register a - * pci_driver, because the I/O Controller Hub has also other - * functions that probably will be registered by other drivers. - */ -static struct pci_device_id iTCO_wdt_pci_tbl[] = { - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801AA_0, TCO_ICH )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801AB_0, TCO_ICH0 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801BA_0, TCO_ICH2 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801BA_10, TCO_ICH2M )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801CA_0, TCO_ICH3 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801CA_12, TCO_ICH3M )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801DB_0, TCO_ICH4 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801DB_12, TCO_ICH4M )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801E_0, TCO_CICH )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_82801EB_0, TCO_ICH5 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ESB_1, TCO_6300ESB)}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_0, TCO_ICH6 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_1, TCO_ICH6M )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH6_2, TCO_ICH6W )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_0, TCO_ICH7 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_1, TCO_ICH7M )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH7_31, TCO_ICH7MDH)}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_0, TCO_ICH8 )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_1, TCO_ICH8ME )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_2, TCO_ICH8DH )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_3, TCO_ICH8DO )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH8_4, TCO_ICH8M )}, - { ITCO_PCI_DEVICE(0x2918, TCO_ICH9 )}, - { ITCO_PCI_DEVICE(0x2916, TCO_ICH9R )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ICH9_2, TCO_ICH9DH )}, - { ITCO_PCI_DEVICE(PCI_DEVICE_ID_INTEL_ESB2_0, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2671, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2672, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2673, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2674, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2675, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2676, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2677, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2678, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x2679, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267a, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267b, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267c, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267d, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267e, TCO_631XESB)}, - { ITCO_PCI_DEVICE(0x267f, TCO_631XESB)}, - { 0, }, /* End of list */ -}; -MODULE_DEVICE_TABLE (pci, iTCO_wdt_pci_tbl); +#include "iTCO_vendor.h" /* Address definitions for the TCO */ -#define TCOBASE iTCO_wdt_private.ACPIBASE + 0x60 /* TCO base address */ -#define SMI_EN iTCO_wdt_private.ACPIBASE + 0x30 /* SMI Control and Enable Register */ - -#define TCO_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */ -#define TCOv1_TMR TCOBASE + 0x01 /* TCOv1 Timer Initial Value */ -#define TCO_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ -#define TCO_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */ -#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ -#define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */ -#define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */ -#define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */ -#define TCOv2_TMR TCOBASE + 0x12 /* TCOv2 Timer Initial Value */ +/* TCO base address */ +#define TCOBASE (iTCO_wdt_private.tco_res->start) +/* SMI Control and Enable Register */ +#define SMI_EN (iTCO_wdt_private.smi_res->start) + +#define TCO_RLD (TCOBASE + 0x00) /* TCO Timer Reload and Curr. Value */ +#define TCOv1_TMR (TCOBASE + 0x01) /* TCOv1 Timer Initial Value */ +#define TCO_DAT_IN (TCOBASE + 0x02) /* TCO Data In Register */ +#define TCO_DAT_OUT (TCOBASE + 0x03) /* TCO Data Out Register */ +#define TCO1_STS (TCOBASE + 0x04) /* TCO1 Status Register */ +#define TCO2_STS (TCOBASE + 0x06) /* TCO2 Status Register */ +#define TCO1_CNT (TCOBASE + 0x08) /* TCO1 Control Register */ +#define TCO2_CNT (TCOBASE + 0x0a) /* TCO2 Control Register */ +#define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */ /* internal variables */ -static unsigned long is_active; -static char expect_release; -static struct { /* this is private data for the iTCO_wdt device */ - unsigned int iTCO_version; /* TCO version/generation */ - unsigned long ACPIBASE; /* The cards ACPIBASE address (TCOBASE = ACPIBASE+0x60) */ - unsigned long __iomem *gcs; /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2) */ - spinlock_t io_lock; /* the lock for io operations */ - struct pci_dev *pdev; /* the PCI-device */ +static struct { /* this is private data for the iTCO_wdt device */ + /* 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). + */ + struct resource *gcs_pmc_res; + unsigned long __iomem *gcs_pmc; + /* the lock for io operations */ + spinlock_t io_lock; + struct platform_device *dev; + /* the PCI-device */ + struct pci_dev *pdev; } iTCO_wdt_private; -static struct platform_device *iTCO_wdt_platform_device; /* the watchdog platform device */ - /* module parameters */ -#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ -static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +#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 heartbeat in seconds. (2<heartbeat<39 (TCO v1) or 613 (TCO v2), default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); - -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -/* iTCO Vendor Specific Support hooks */ -#ifdef CONFIG_ITCO_VENDOR_SUPPORT -extern void iTCO_vendor_pre_start(unsigned long, unsigned int); -extern void iTCO_vendor_pre_stop(unsigned long); -extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int); -extern void iTCO_vendor_pre_set_heartbeat(unsigned int); -extern int iTCO_vendor_check_noreboot_on(void); -#else -#define iTCO_vendor_pre_start(acpibase, heartbeat) {} -#define iTCO_vendor_pre_stop(acpibase) {} -#define iTCO_vendor_pre_keepalive(acpibase,heartbeat) {} -#define iTCO_vendor_pre_set_heartbeat(heartbeat) {} -#define iTCO_vendor_check_noreboot_on() 1 /* 1=check noreboot; 0=don't check */ -#endif +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 */ -static inline unsigned int seconds_to_ticks(int seconds) +/* + * 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(int secs) +{ + return iTCO_wdt_private.iTCO_version == 3 ? secs : (secs * 10) / 6; +} + +static inline unsigned int ticks_to_seconds(int ticks) { - /* the internal timer is stored as ticks which decrement - * every 0.6 seconds */ - return (seconds * 10) / 6; + return iTCO_wdt_private.iTCO_version == 3 ? ticks : (ticks * 6) / 10; } static void iTCO_wdt_set_NO_REBOOT_bit(void) @@ -269,10 +148,14 @@ static void iTCO_wdt_set_NO_REBOOT_bit(void) u32 val32; /* Set the NO_REBOOT bit: this disables reboots */ - if (iTCO_wdt_private.iTCO_version == 2) { - val32 = readl(iTCO_wdt_private.gcs); + if (iTCO_wdt_private.iTCO_version == 3) { + val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 |= 0x00000010; + writel(val32, iTCO_wdt_private.gcs_pmc); + } else if (iTCO_wdt_private.iTCO_version == 2) { + val32 = readl(iTCO_wdt_private.gcs_pmc); val32 |= 0x00000020; - writel(val32, iTCO_wdt_private.gcs); + writel(val32, iTCO_wdt_private.gcs_pmc); } else if (iTCO_wdt_private.iTCO_version == 1) { pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); val32 |= 0x00000002; @@ -286,12 +169,20 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) u32 val32; /* Unset the NO_REBOOT bit: this enables reboots */ - if (iTCO_wdt_private.iTCO_version == 2) { - val32 = readl(iTCO_wdt_private.gcs); + if (iTCO_wdt_private.iTCO_version == 3) { + val32 = readl(iTCO_wdt_private.gcs_pmc); + val32 &= 0xffffffef; + writel(val32, iTCO_wdt_private.gcs_pmc); + + val32 = readl(iTCO_wdt_private.gcs_pmc); + if (val32 & 0x00000010) + ret = -EIO; + } else if (iTCO_wdt_private.iTCO_version == 2) { + val32 = readl(iTCO_wdt_private.gcs_pmc); val32 &= 0xffffffdf; - writel(val32, iTCO_wdt_private.gcs); + writel(val32, iTCO_wdt_private.gcs_pmc); - val32 = readl(iTCO_wdt_private.gcs); + val32 = readl(iTCO_wdt_private.gcs_pmc); if (val32 & 0x00000020) ret = -EIO; } else if (iTCO_wdt_private.iTCO_version == 1) { @@ -307,21 +198,28 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) return ret; /* returns: 0 = OK, -EIO = Error */ } -static int iTCO_wdt_start(void) +static int iTCO_wdt_start(struct watchdog_device *wd_dev) { unsigned int val; spin_lock(&iTCO_wdt_private.io_lock); - iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); + iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, wd_dev->timeout); /* disable chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit()) { spin_unlock(&iTCO_wdt_private.io_lock); - printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); + pr_err("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 (iTCO_wdt_private.iTCO_version >= 2) + outw(0x01, TCO_RLD); + else if (iTCO_wdt_private.iTCO_version == 1) + outb(0x01, TCO_RLD); + /* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */ val = inw(TCO1_CNT); val &= 0xf7ff; @@ -334,13 +232,13 @@ static int iTCO_wdt_start(void) return 0; } -static int iTCO_wdt_stop(void) +static int iTCO_wdt_stop(struct watchdog_device *wd_dev) { unsigned int val; spin_lock(&iTCO_wdt_private.io_lock); - iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); + iTCO_vendor_pre_stop(iTCO_wdt_private.smi_res); /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ val = inw(TCO1_CNT); @@ -358,16 +256,20 @@ static int iTCO_wdt_stop(void) return 0; } -static int iTCO_wdt_keepalive(void) +static int iTCO_wdt_ping(struct watchdog_device *wd_dev) { spin_lock(&iTCO_wdt_private.io_lock); - iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); + iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout); /* Reload the timer by writing to the TCO Timer Counter register */ - if (iTCO_wdt_private.iTCO_version == 2) { + if (iTCO_wdt_private.iTCO_version >= 2) { outw(0x01, TCO_RLD); } else if (iTCO_wdt_private.iTCO_version == 1) { + /* Reset the timeout status bit so that the timer + * needs to count down twice again before rebooting */ + outw(0x0008, TCO1_STS); /* write 1 to clear bit */ + outb(0x01, TCO_RLD); } @@ -375,25 +277,30 @@ static int iTCO_wdt_keepalive(void) return 0; } -static int iTCO_wdt_set_heartbeat(int t) +static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t) { unsigned int val16; unsigned char val8; unsigned int tmrval; tmrval = seconds_to_ticks(t); + + /* For TCO v1 the timer counts down twice before rebooting */ + if (iTCO_wdt_private.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 (((iTCO_wdt_private.iTCO_version == 2) && (tmrval > 0x3ff)) || + if (((iTCO_wdt_private.iTCO_version >= 2) && (tmrval > 0x3ff)) || ((iTCO_wdt_private.iTCO_version == 1) && (tmrval > 0x03f))) return -EINVAL; iTCO_vendor_pre_set_heartbeat(tmrval); /* Write new heartbeat to watchdog */ - if (iTCO_wdt_private.iTCO_version == 2) { + if (iTCO_wdt_private.iTCO_version >= 2) { spin_lock(&iTCO_wdt_private.io_lock); val16 = inw(TCOv2_TMR); val16 &= 0xfc00; @@ -417,345 +324,243 @@ static int iTCO_wdt_set_heartbeat(int t) return -EINVAL; } - heartbeat = t; + wd_dev->timeout = t; return 0; } -static int iTCO_wdt_get_timeleft (int *time_left) +static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev) { unsigned int val16; unsigned char val8; + unsigned int time_left = 0; /* read the TCO Timer */ - if (iTCO_wdt_private.iTCO_version == 2) { + if (iTCO_wdt_private.iTCO_version >= 2) { spin_lock(&iTCO_wdt_private.io_lock); val16 = inw(TCO_RLD); val16 &= 0x3ff; spin_unlock(&iTCO_wdt_private.io_lock); - *time_left = (val16 * 6) / 10; + time_left = ticks_to_seconds(val16); } else if (iTCO_wdt_private.iTCO_version == 1) { spin_lock(&iTCO_wdt_private.io_lock); val8 = inb(TCO_RLD); val8 &= 0x3f; + if (!(inw(TCO1_STS) & 0x0008)) + val8 += (inb(TCOv1_TMR) & 0x3f); spin_unlock(&iTCO_wdt_private.io_lock); - *time_left = (val8 * 6) / 10; - } else - return -EINVAL; - return 0; + time_left = ticks_to_seconds(val8); + } + return time_left; } /* - * /dev/watchdog handling + * Kernel Interfaces */ -static int iTCO_wdt_open (struct inode *inode, struct file *file) -{ - /* /dev/watchdog can only be opened once */ - if (test_and_set_bit(0, &is_active)) - return -EBUSY; +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = DRV_NAME, +}; - /* - * Reload and activate timer - */ - iTCO_wdt_keepalive(); - iTCO_wdt_start(); - return nonseekable_open(inode, file); -} +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, +}; -static int iTCO_wdt_release (struct inode *inode, struct file *file) -{ - /* - * Shut off the timer. - */ - if (expect_release == 42) { - iTCO_wdt_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - iTCO_wdt_keepalive(); - } - clear_bit(0, &is_active); - expect_release = 0; - return 0; -} +static struct watchdog_device iTCO_wdt_watchdog_dev = { + .info = &ident, + .ops = &iTCO_wdt_ops, +}; + +/* + * Init & exit routines + */ -static ssize_t iTCO_wdt_write (struct file *file, const char __user *data, - size_t len, loff_t * ppos) +static void iTCO_wdt_cleanup(void) { - /* 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; - } - } + /* Stop the timer before we leave */ + if (!nowayout) + iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); - /* someone wrote to us, we should reload the timer */ - iTCO_wdt_keepalive(); + /* Deregister */ + watchdog_unregister_device(&iTCO_wdt_watchdog_dev); + + /* release resources */ + release_region(iTCO_wdt_private.tco_res->start, + resource_size(iTCO_wdt_private.tco_res)); + release_region(iTCO_wdt_private.smi_res->start, + resource_size(iTCO_wdt_private.smi_res)); + if (iTCO_wdt_private.iTCO_version >= 2) { + iounmap(iTCO_wdt_private.gcs_pmc); + release_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); } - return len; + + iTCO_wdt_private.tco_res = NULL; + iTCO_wdt_private.smi_res = NULL; + iTCO_wdt_private.gcs_pmc_res = NULL; + iTCO_wdt_private.gcs_pmc = NULL; } -static int iTCO_wdt_ioctl (struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static int iTCO_wdt_probe(struct platform_device *dev) { - int new_options, retval = -EINVAL; - int new_heartbeat; - void __user *argp = (void __user *)arg; - int __user *p = argp; - static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = DRV_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_KEEPALIVE: - iTCO_wdt_keepalive(); - return 0; - - case WDIOC_SETOPTIONS: - { - if (get_user(new_options, p)) - return -EFAULT; - - if (new_options & WDIOS_DISABLECARD) { - iTCO_wdt_stop(); - retval = 0; - } - - if (new_options & WDIOS_ENABLECARD) { - iTCO_wdt_keepalive(); - iTCO_wdt_start(); - retval = 0; - } - - return retval; - } - - case WDIOC_SETTIMEOUT: - { - if (get_user(new_heartbeat, p)) - return -EFAULT; - - if (iTCO_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - - iTCO_wdt_keepalive(); - /* Fall */ - } - - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - - case WDIOC_GETTIMELEFT: - { - int time_left; - - if (iTCO_wdt_get_timeleft(&time_left)) - return -EINVAL; - - return put_user(time_left, p); - } - - default: - return -ENOTTY; - } -} + int ret = -ENODEV; + unsigned long val32; + struct lpc_ich_info *ich_info = dev_get_platdata(&dev->dev); -/* - * Kernel Interfaces - */ + if (!ich_info) + goto out; -static const struct file_operations iTCO_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = iTCO_wdt_write, - .ioctl = iTCO_wdt_ioctl, - .open = iTCO_wdt_open, - .release = iTCO_wdt_release, -}; + spin_lock_init(&iTCO_wdt_private.io_lock); -static struct miscdevice iTCO_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &iTCO_wdt_fops, -}; + iTCO_wdt_private.tco_res = + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_TCO); + if (!iTCO_wdt_private.tco_res) + goto out; -/* - * Init & exit routines - */ + iTCO_wdt_private.smi_res = + platform_get_resource(dev, IORESOURCE_IO, ICH_RES_IO_SMI); + if (!iTCO_wdt_private.smi_res) + goto out; -static int __devinit iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device_id *ent, struct platform_device *dev) -{ - int ret; - u32 base_address; - unsigned long RCBA; - unsigned long val32; + iTCO_wdt_private.iTCO_version = ich_info->iTCO_version; + iTCO_wdt_private.dev = dev; + iTCO_wdt_private.pdev = to_pci_dev(dev->dev.parent); /* - * Find the ACPI/PM base I/O address which is the base - * for the TCO registers (TCOBASE=ACPIBASE + 0x60) - * ACPIBASE is bits [15:7] from 0x40-0x43 + * Get the Memory-Mapped GCS or PMC register, we need it for the + * NO_REBOOT flag (TCO v2 and v3). */ - pci_read_config_dword(pdev, 0x40, &base_address); - base_address &= 0x0000ff80; - if (base_address == 0x00000000) { - /* Something's wrong here, ACPIBASE has to be set */ - printk(KERN_ERR PFX "failed to get TCOBASE address\n"); - pci_dev_put(pdev); - return -ENODEV; - } - iTCO_wdt_private.iTCO_version = iTCO_chipset_info[ent->driver_data].iTCO_version; - iTCO_wdt_private.ACPIBASE = base_address; - iTCO_wdt_private.pdev = pdev; - - /* Get the Memory-Mapped GCS register, we need it for the NO_REBOOT flag (TCO v2) */ - /* To get access to it you have to read RCBA from PCI Config space 0xf0 - and use it as base. GCS = RCBA + ICH6_GCS(0x3410). */ - if (iTCO_wdt_private.iTCO_version == 2) { - pci_read_config_dword(pdev, 0xf0, &base_address); - RCBA = base_address & 0xffffc000; - iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410),4); + if (iTCO_wdt_private.iTCO_version >= 2) { + iTCO_wdt_private.gcs_pmc_res = platform_get_resource(dev, + IORESOURCE_MEM, + ICH_RES_MEM_GCS_PMC); + + if (!iTCO_wdt_private.gcs_pmc_res) + goto out; + + if (!request_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res), dev->name)) { + ret = -EBUSY; + goto out; + } + iTCO_wdt_private.gcs_pmc = ioremap(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); + if (!iTCO_wdt_private.gcs_pmc) { + ret = -EIO; + goto unreg_gcs_pmc; + } } /* Check chipset's NO_REBOOT bit */ if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { - printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); + pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ - goto out; + goto unmap_gcs_pmc; } /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ iTCO_wdt_set_NO_REBOOT_bit(); - /* Set the TCO_EN bit in SMI_EN register */ - if (!request_region(SMI_EN, 4, "iTCO_wdt")) { - printk(KERN_ERR PFX "I/O address 0x%04lx already in use\n", - SMI_EN ); - ret = -EIO; - goto out; + /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ + if (!request_region(iTCO_wdt_private.smi_res->start, + resource_size(iTCO_wdt_private.smi_res), dev->name)) { + pr_err("I/O address 0x%04llx already in use, device disabled\n", + (u64)SMI_EN); + ret = -EBUSY; + goto unmap_gcs_pmc; } - val32 = inl(SMI_EN); - val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ - outl(val32, SMI_EN); - release_region(SMI_EN, 4); - - /* The TCO I/O registers reside in a 32-byte range pointed to by the TCOBASE value */ - if (!request_region (TCOBASE, 0x20, "iTCO_wdt")) { - printk (KERN_ERR PFX "I/O address 0x%04lx already in use\n", - TCOBASE); - ret = -EIO; - goto out; + if (turn_SMI_watchdog_clear_off >= iTCO_wdt_private.iTCO_version) { + /* + * Bit 13: TCO_EN -> 0 + * Disables TCO logic generating an SMI# + */ + val32 = inl(SMI_EN); + val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */ + outl(val32, SMI_EN); } - printk(KERN_INFO PFX "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n", - iTCO_chipset_info[ent->driver_data].name, - iTCO_chipset_info[ent->driver_data].iTCO_version, - TCOBASE); + if (!request_region(iTCO_wdt_private.tco_res->start, + resource_size(iTCO_wdt_private.tco_res), dev->name)) { + pr_err("I/O address 0x%04llx already in use, device disabled\n", + (u64)TCOBASE); + ret = -EBUSY; + goto unreg_smi; + } + + pr_info("Found a %s TCO device (Version=%d, TCOBASE=0x%04llx)\n", + ich_info->name, ich_info->iTCO_version, (u64)TCOBASE); /* Clear out the (probably old) status */ - outb(0, TCO1_STS); - outb(3, TCO2_STS); + if (iTCO_wdt_private.iTCO_version == 3) { + outl(0x20008, TCO1_STS); + } else { + outw(0x0008, TCO1_STS); /* Clear the Time Out Status bit */ + outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ + outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ + } - /* Make sure the watchdog is not running */ - iTCO_wdt_stop(); + iTCO_wdt_watchdog_dev.bootstatus = 0; + iTCO_wdt_watchdog_dev.timeout = WATCHDOG_TIMEOUT; + watchdog_set_nowayout(&iTCO_wdt_watchdog_dev, nowayout); + iTCO_wdt_watchdog_dev.parent = &dev->dev; - /* Check that the heartbeat value is within it's range ; if not reset to the default */ - if (iTCO_wdt_set_heartbeat(heartbeat)) { - iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); - printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39 (TCO v1) or 613 (TCO v2), using %d\n", - heartbeat); + /* Make sure the watchdog is not running */ + iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ + if (iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, heartbeat)) { + iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, WATCHDOG_TIMEOUT); + pr_info("timeout value out of range, using %d\n", + WATCHDOG_TIMEOUT); } - ret = misc_register(&iTCO_wdt_miscdev); + ret = watchdog_register_device(&iTCO_wdt_watchdog_dev); if (ret != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto unreg_region; + pr_err("cannot register watchdog device (err=%d)\n", ret); + goto unreg_tco; } - printk (KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); return 0; -unreg_region: - release_region (TCOBASE, 0x20); +unreg_tco: + release_region(iTCO_wdt_private.tco_res->start, + resource_size(iTCO_wdt_private.tco_res)); +unreg_smi: + release_region(iTCO_wdt_private.smi_res->start, + resource_size(iTCO_wdt_private.smi_res)); +unmap_gcs_pmc: + if (iTCO_wdt_private.iTCO_version >= 2) + iounmap(iTCO_wdt_private.gcs_pmc); +unreg_gcs_pmc: + if (iTCO_wdt_private.iTCO_version >= 2) + release_mem_region(iTCO_wdt_private.gcs_pmc_res->start, + resource_size(iTCO_wdt_private.gcs_pmc_res)); out: - if (iTCO_wdt_private.iTCO_version == 2) - iounmap(iTCO_wdt_private.gcs); - pci_dev_put(iTCO_wdt_private.pdev); - iTCO_wdt_private.ACPIBASE = 0; - return ret; -} - -static void __devexit iTCO_wdt_cleanup(void) -{ - /* Stop the timer before we leave */ - if (!nowayout) - iTCO_wdt_stop(); - - /* Deregister */ - misc_deregister(&iTCO_wdt_miscdev); - release_region(TCOBASE, 0x20); - if (iTCO_wdt_private.iTCO_version == 2) - iounmap(iTCO_wdt_private.gcs); - pci_dev_put(iTCO_wdt_private.pdev); - iTCO_wdt_private.ACPIBASE = 0; -} - -static int __devinit iTCO_wdt_probe(struct platform_device *dev) -{ - int found = 0; - struct pci_dev *pdev = NULL; - const struct pci_device_id *ent; - - spin_lock_init(&iTCO_wdt_private.io_lock); - - for_each_pci_dev(pdev) { - ent = pci_match_id(iTCO_wdt_pci_tbl, pdev); - if (ent) { - if (!(iTCO_wdt_init(pdev, ent, dev))) { - found++; - break; - } - } - } - - if (!found) { - printk(KERN_INFO PFX "No card detected\n"); - return -ENODEV; - } + iTCO_wdt_private.tco_res = NULL; + iTCO_wdt_private.smi_res = NULL; + iTCO_wdt_private.gcs_pmc_res = NULL; + iTCO_wdt_private.gcs_pmc = NULL; - return 0; + return ret; } -static int __devexit iTCO_wdt_remove(struct platform_device *dev) +static int iTCO_wdt_remove(struct platform_device *dev) { - if (iTCO_wdt_private.ACPIBASE) + if (iTCO_wdt_private.tco_res || iTCO_wdt_private.smi_res) iTCO_wdt_cleanup(); return 0; @@ -763,18 +568,13 @@ static int __devexit iTCO_wdt_remove(struct platform_device *dev) static void iTCO_wdt_shutdown(struct platform_device *dev) { - iTCO_wdt_stop(); + iTCO_wdt_stop(NULL); } -#define iTCO_wdt_suspend NULL -#define iTCO_wdt_resume NULL - static struct platform_driver iTCO_wdt_driver = { .probe = iTCO_wdt_probe, - .remove = __devexit_p(iTCO_wdt_remove), + .remove = iTCO_wdt_remove, .shutdown = iTCO_wdt_shutdown, - .suspend = iTCO_wdt_suspend, - .resume = iTCO_wdt_resume, .driver = { .owner = THIS_MODULE, .name = DRV_NAME, @@ -785,31 +585,19 @@ static int __init iTCO_wdt_init_module(void) { int err; - printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s (%s)\n", - DRV_VERSION, DRV_RELDATE); + pr_info("Intel TCO WatchDog Timer Driver v%s\n", DRV_VERSION); err = platform_driver_register(&iTCO_wdt_driver); if (err) return err; - iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); - if (IS_ERR(iTCO_wdt_platform_device)) { - err = PTR_ERR(iTCO_wdt_platform_device); - goto unreg_platform_driver; - } - return 0; - -unreg_platform_driver: - platform_driver_unregister(&iTCO_wdt_driver); - return err; } static void __exit iTCO_wdt_cleanup_module(void) { - platform_device_unregister(iTCO_wdt_platform_device); platform_driver_unregister(&iTCO_wdt_driver); - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); + pr_info("Watchdog Module Unloaded\n"); } module_init(iTCO_wdt_init_module); @@ -819,4 +607,4 @@ MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); MODULE_DESCRIPTION("Intel TCO WatchDog Timer Driver"); MODULE_VERSION(DRV_VERSION); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c index 4b89f401691..4247c498ee7 100644 --- a/drivers/watchdog/ib700wdt.c +++ b/drivers/watchdog/ib700wdt.c @@ -11,8 +11,8 @@ * Based on acquirewdt.c which is based on wdt.c. * Original copyright messages: * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -23,7 +23,7 @@ * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * - * (c) Copyright 1995 Alan Cox <alan@redhat.com> + * (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 @@ -31,6 +31,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/types.h> #include <linux/miscdevice.h> @@ -41,10 +43,9 @@ #include <linux/spinlock.h> #include <linux/moduleparam.h> #include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> static struct platform_device *ibwdt_platform_device; static unsigned long ibwdt_is_open; @@ -53,7 +54,6 @@ static char expect_close; /* Module information */ #define DRV_NAME "ib700wdt" -#define PFX DRV_NAME ": " /* * @@ -91,45 +91,32 @@ static char expect_close; * */ -static int wd_times[] = { - 30, /* 0x0 */ - 28, /* 0x1 */ - 26, /* 0x2 */ - 24, /* 0x3 */ - 22, /* 0x4 */ - 20, /* 0x5 */ - 18, /* 0x6 */ - 16, /* 0x7 */ - 14, /* 0x8 */ - 12, /* 0x9 */ - 10, /* 0xA */ - 8, /* 0xB */ - 6, /* 0xC */ - 4, /* 0xD */ - 2, /* 0xE */ - 0, /* 0xF */ -}; - #define WDT_STOP 0x441 #define WDT_START 0x443 /* Default timeout */ -#define WD_TIMO 0 /* 30 seconds +/- 20%, from table */ - -static int wd_margin = WD_TIMO; +#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 int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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) +static void ibwdt_ping(void) { + int wd_margin = 15 - ((timeout + 1) / 2); + spin_lock(&ibwdt_lock); /* Write a watchdog value */ @@ -138,26 +125,19 @@ ibwdt_ping(void) spin_unlock(&ibwdt_lock); } -static void -ibwdt_disable(void) +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) +static int ibwdt_set_heartbeat(int t) { - int i; - - if ((t < 0) || (t > 30)) + if (t < 0 || t > 30) return -EINVAL; - for (i = 0x0F; i > -1; i--) - if (wd_times[i] > t) - break; - wd_margin = i; + timeout = t; return 0; } @@ -165,8 +145,8 @@ ibwdt_set_heartbeat(int t) * /dev/watchdog handling */ -static ssize_t -ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +static ssize_t ibwdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { if (count) { if (!nowayout) { @@ -188,77 +168,71 @@ ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppo return count; } -static int -ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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 struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + 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; + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - - 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(); - /* Fall */ - - case WDIOC_GETTIMEOUT: - return put_user(wd_times[wd_margin], p); + return put_user(0, p); case WDIOC_SETOPTIONS: { - int options, retval = -EINVAL; + int options, retval = -EINVAL; - if (get_user(options, p)) - return -EFAULT; + if (get_user(options, p)) + return -EFAULT; - if (options & WDIOS_DISABLECARD) { - ibwdt_disable(); - retval = 0; - } + if (options & WDIOS_DISABLECARD) { + ibwdt_disable(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + ibwdt_ping(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + ibwdt_ping(); + break; - if (options & WDIOS_ENABLECARD) { - ibwdt_ping(); - retval = 0; - } + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (ibwdt_set_heartbeat(new_margin)) + return -EINVAL; + ibwdt_ping(); + /* Fall */ - return retval; - } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); default: - return -ENOTTY; + return -ENOTTY; } return 0; } -static int -ibwdt_open(struct inode *inode, struct file *file) +static int ibwdt_open(struct inode *inode, struct file *file) { - if (test_and_set_bit(0, &ibwdt_is_open)) { + if (test_and_set_bit(0, &ibwdt_is_open)) return -EBUSY; - } if (nowayout) __module_get(THIS_MODULE); @@ -267,13 +241,12 @@ ibwdt_open(struct inode *inode, struct file *file) return nonseekable_open(inode, file); } -static int -ibwdt_close(struct inode *inode, struct file *file) +static int ibwdt_close(struct inode *inode, struct file *file) { if (expect_close == 42) { ibwdt_disable(); } else { - printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); ibwdt_ping(); } clear_bit(0, &ibwdt_is_open); @@ -289,7 +262,7 @@ static const struct file_operations ibwdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = ibwdt_write, - .ioctl = ibwdt_ioctl, + .unlocked_ioctl = ibwdt_ioctl, .open = ibwdt_open, .release = ibwdt_close, }; @@ -304,27 +277,34 @@ static struct miscdevice ibwdt_miscdev = { * Init & exit routines */ -static int __devinit ibwdt_probe(struct platform_device *dev) +static int __init ibwdt_probe(struct platform_device *dev) { int res; #if WDT_START != WDT_STOP if (!request_region(WDT_STOP, 1, "IB700 WDT")) { - printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP); + 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")) { - printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START); + 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) { - printk (KERN_ERR PFX "failed to register misc device\n"); + pr_err("failed to register misc device\n"); goto out_nomisc; } return 0; @@ -339,12 +319,12 @@ out_nostopreg: return res; } -static int __devexit ibwdt_remove(struct platform_device *dev) +static int ibwdt_remove(struct platform_device *dev) { misc_deregister(&ibwdt_miscdev); - release_region(WDT_START,1); + release_region(WDT_START, 1); #if WDT_START != WDT_STOP - release_region(WDT_STOP,1); + release_region(WDT_STOP, 1); #endif return 0; } @@ -356,8 +336,7 @@ static void ibwdt_shutdown(struct platform_device *dev) } static struct platform_driver ibwdt_driver = { - .probe = ibwdt_probe, - .remove = __devexit_p(ibwdt_remove), + .remove = ibwdt_remove, .shutdown = ibwdt_shutdown, .driver = { .owner = THIS_MODULE, @@ -369,22 +348,21 @@ static int __init ibwdt_init(void) { int err; - printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n"); + pr_info("WDT driver for IB700 single board computer initialising\n"); - err = platform_driver_register(&ibwdt_driver); - if (err) - return err; + ibwdt_platform_device = platform_device_register_simple(DRV_NAME, + -1, NULL, 0); + if (IS_ERR(ibwdt_platform_device)) + return PTR_ERR(ibwdt_platform_device); - ibwdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0); - if (IS_ERR(ibwdt_platform_device)) { - err = PTR_ERR(ibwdt_platform_device); - goto unreg_platform_driver; - } + err = platform_driver_probe(&ibwdt_driver, ibwdt_probe); + if (err) + goto unreg_platform_device; return 0; -unreg_platform_driver: - platform_driver_unregister(&ibwdt_driver); +unreg_platform_device: + platform_device_unregister(ibwdt_platform_device); return err; } @@ -392,7 +370,7 @@ static void __exit ibwdt_exit(void) { platform_device_unregister(ibwdt_platform_device); platform_driver_unregister(&ibwdt_driver); - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); + pr_info("Watchdog Module Unloaded\n"); } module_init(ibwdt_init); @@ -401,6 +379,5 @@ module_exit(ibwdt_exit); MODULE_AUTHOR("Charles Howes <chowes@vsol.net>"); MODULE_DESCRIPTION("IB700 SBC watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); /* end of ib700wdt.c */ diff --git a/drivers/watchdog/ibmasr.c b/drivers/watchdog/ibmasr.c index 94155f6136c..366b0474f27 100644 --- a/drivers/watchdog/ibmasr.c +++ b/drivers/watchdog/ibmasr.c @@ -10,18 +10,18 @@ * 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/slab.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 <asm/io.h> -#include <asm/uaccess.h> +#include <linux/io.h> +#include <linux/uaccess.h> enum { @@ -33,8 +33,6 @@ enum { ASMTYPE_SPRUCE, }; -#define PFX "ibmasr: " - #define TOPAZ_ASR_REG_OFFSET 4 #define TOPAZ_ASR_TOGGLE 0x40 #define TOPAZ_ASR_DISABLE 0x80 @@ -62,7 +60,7 @@ enum { #define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; static unsigned long asr_is_open; static char asr_expect_close; @@ -70,10 +68,13 @@ 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) +static void __asr_toggle(void) { - unsigned char reg = inb(asr_read_addr); + unsigned char reg; + + reg = inb(asr_read_addr); outb(reg & ~asr_toggle_mask, asr_write_addr); reg = inb(asr_read_addr); @@ -85,10 +86,18 @@ static void asr_toggle(void) 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); @@ -99,17 +108,21 @@ static void asr_enable(void) * First make sure the hardware timer is reset by toggling * ASR hardware timer line. */ - asr_toggle(); + __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 = inb(asr_read_addr); + unsigned char reg; + + spin_lock(&asr_lock); + reg = inb(asr_read_addr); if (asr_type == ASMTYPE_TOPAZ) /* asr_write_addr == asr_read_addr */ @@ -122,6 +135,7 @@ static void asr_disable(void) 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) @@ -133,7 +147,8 @@ static int __init asr_get_base_address(void) switch (asr_type) { case ASMTYPE_TOPAZ: - /* SELECT SuperIO CHIP FOR QUERYING (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ + /* SELECT SuperIO CHIP FOR QUERYING + (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ outb(0x07, 0x2e); outb(0x07, 0x2f); @@ -154,14 +169,26 @@ static int __init asr_get_base_address(void) case ASMTYPE_JASPER: type = "Jaspers "; - - /* FIXME: need to use pci_config_lock here, but it's not exported */ +#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 @@ -170,7 +197,7 @@ static int __init asr_get_base_address(void) 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; @@ -207,12 +234,11 @@ static int __init asr_get_base_address(void) } if (!request_region(asr_base, asr_length, "ibmasr")) { - printk(KERN_ERR PFX "address %#x already in use\n", - asr_base); + pr_err("address %#x already in use\n", asr_base); return -EBUSY; } - printk(KERN_INFO PFX "found %sASR @ addr %#x\n", type, asr_base); + pr_info("found %sASR @ addr %#x\n", type, asr_base); return 0; } @@ -241,66 +267,57 @@ static ssize_t asr_write(struct file *file, const char __user *buf, return count; } -static int asr_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long asr_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { static const struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, - .identity = "IBM ASR" + .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_KEEPALIVE: + 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(); - 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); - - 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; + 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; } - - return -ENOTTY; } static int asr_open(struct inode *inode, struct file *file) { - if(test_and_set_bit(0, &asr_is_open)) + if (test_and_set_bit(0, &asr_is_open)) return -EBUSY; asr_toggle(); @@ -314,7 +331,7 @@ static int asr_release(struct inode *inode, struct file *file) if (asr_expect_close == 42) asr_disable(); else { - printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); + pr_crit("unexpected close, not stopping watchdog!\n"); asr_toggle(); } clear_bit(0, &asr_is_open); @@ -323,12 +340,12 @@ static int asr_release(struct inode *inode, struct file *file) } static const struct file_operations asr_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = asr_write, - .ioctl = asr_ioctl, - .open = asr_open, - .release = asr_release, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = asr_write, + .unlocked_ioctl = asr_ioctl, + .open = asr_open, + .release = asr_release, }; static struct miscdevice asr_miscdev = { @@ -343,7 +360,7 @@ struct ibmasr_id { int type; }; -static struct ibmasr_id __initdata ibmasr_id_table[] = { +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 }, @@ -374,7 +391,7 @@ static int __init ibmasr_init(void) rc = misc_register(&asr_miscdev); if (rc < 0) { release_region(asr_base, asr_length); - printk(KERN_ERR PFX "failed to register misc device\n"); + pr_err("failed to register misc device\n"); return rc; } @@ -394,10 +411,11 @@ static void __exit ibmasr_exit(void) module_init(ibmasr_init); module_exit(ibmasr_exit); -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ie6xx_wdt.c b/drivers/watchdog/ie6xx_wdt.c new file mode 100644 index 00000000000..07f88f54e5c --- /dev/null +++ b/drivers/watchdog/ie6xx_wdt.c @@ -0,0 +1,346 @@ +/* + * Intel Atom E6xx Watchdog driver + * + * Copyright (C) 2011 Alexander Stein + * <alexander.stein@systec-electronic.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General + * Public License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + * + */ + +#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; + struct spinlock 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_dbg_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; +} + +static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ie6xx_wdt_dbg_show, NULL); +} + +static const struct file_operations ie6xx_wdt_dbg_operations = { + .open = ie6xx_wdt_dbg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +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_dbg_operations); +} + +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); + + 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) { + dev_err(&pdev->dev, + "Watchdog timer: cannot register device (err =%d)\n", + 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, + .owner = THIS_MODULE, + }, +}; + +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/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c new file mode 100644 index 00000000000..9d4874f0994 --- /dev/null +++ b/drivers/watchdog/imx2_wdt.c @@ -0,0 +1,312 @@ +/* + * Watchdog driver for IMX2 and later processors + * + * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. <w.sang@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. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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/init.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/timer.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_WRE (1 << 3) /* -> WDOG Reset Enable */ +#define IMX2_WDT_WCR_WDE (1 << 2) /* -> Watchdog Enable */ +#define IMX2_WDT_WCR_WDZST (1 << 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 (1 << 1) /* -> Reset due to Timeout */ + +#define IMX2_WDT_MAX_TIME 128 +#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 timer_list timer; /* Pings the watchdog when closed */ + struct watchdog_device wdog; +}; + +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 = IMX2_WDT_DEFAULT_TIME; +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 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 reset if WDOG times out */ + 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); + + 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_timer_ping(unsigned long arg) +{ + struct watchdog_device *wdog = (struct watchdog_device *)arg; + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); + + /* ping it every wdog->timeout / 2 seconds to prevent reboot */ + imx2_wdt_ping(wdog); + mod_timer(&wdev->timer, jiffies + wdog->timeout * HZ / 2); +} + +static int 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)); + return 0; +} + +static int imx2_wdt_start(struct watchdog_device *wdog) +{ + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); + + if (imx2_wdt_is_running(wdev)) { + /* delete the timer that pings the watchdog after close */ + del_timer_sync(&wdev->timer); + imx2_wdt_set_timeout(wdog, wdog->timeout); + } else + imx2_wdt_setup(wdog); + + return imx2_wdt_ping(wdog); +} + +static int imx2_wdt_stop(struct watchdog_device *wdog) +{ + /* + * We don't need a clk_disable, it cannot be disabled once started. + * We use a timer to ping the watchdog while /dev/watchdog is closed + */ + imx2_wdt_timer_ping((unsigned long)wdog); + return 0; +} + +static inline void imx2_wdt_ping_if_active(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); + imx2_wdt_timer_ping((unsigned long)wdog); + } +} + +static struct watchdog_ops imx2_wdt_ops = { + .owner = THIS_MODULE, + .start = imx2_wdt_start, + .stop = imx2_wdt_stop, + .ping = imx2_wdt_ping, + .set_timeout = imx2_wdt_set_timeout, +}; + +static struct regmap_config imx2_wdt_regmap_config = { + .reg_bits = 16, + .reg_stride = 2, + .val_bits = 16, + .max_register = 0x8, +}; + +static int __init imx2_wdt_probe(struct platform_device *pdev) +{ + struct imx2_wdt_device *wdev; + struct watchdog_device *wdog; + struct resource *res; + void __iomem *base; + int ret; + u32 val; + + wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + wdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, NULL, base, + &imx2_wdt_regmap_config); + if (IS_ERR(wdev->regmap)) { + dev_err(&pdev->dev, "regmap init failed\n"); + return PTR_ERR(wdev->regmap); + } + + wdev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdev->clk)) { + dev_err(&pdev->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->max_timeout = IMX2_WDT_MAX_TIME; + + clk_prepare_enable(wdev->clk); + + regmap_read(wdev->regmap, IMX2_WDT_WRSR, &val); + wdog->bootstatus = val & IMX2_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0; + + wdog->timeout = clamp_t(unsigned, timeout, 1, IMX2_WDT_MAX_TIME); + if (wdog->timeout != timeout) + dev_warn(&pdev->dev, "Initial timeout out of range! Clamped from %u to %u\n", + timeout, wdog->timeout); + + platform_set_drvdata(pdev, wdog); + watchdog_set_drvdata(wdog, wdev); + watchdog_set_nowayout(wdog, nowayout); + watchdog_init_timeout(wdog, timeout, &pdev->dev); + + setup_timer(&wdev->timer, imx2_wdt_timer_ping, (unsigned long)wdog); + + imx2_wdt_ping_if_active(wdog); + + ret = watchdog_register_device(wdog); + if (ret) { + dev_err(&pdev->dev, "cannot register watchdog device\n"); + return ret; + } + + dev_info(&pdev->dev, "timeout %d sec (nowayout=%d)\n", + wdog->timeout, nowayout); + + return 0; +} + +static int __exit imx2_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); + + watchdog_unregister_device(wdog); + + if (imx2_wdt_is_running(wdev)) { + del_timer_sync(&wdev->timer); + imx2_wdt_ping(wdog); + dev_crit(&pdev->dev, "Device removed: Expect reboot!\n"); + } + return 0; +} + +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, we need to delete the timer but will + * give max timeout before reboot will take place + */ + del_timer_sync(&wdev->timer); + imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); + imx2_wdt_ping(wdog); + dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n"); + } +} + +static const struct of_device_id imx2_wdt_dt_ids[] = { + { .compatible = "fsl,imx21-wdt", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx2_wdt_dt_ids); + +static struct platform_driver imx2_wdt_driver = { + .remove = __exit_p(imx2_wdt_remove), + .shutdown = imx2_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .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/indydog.c b/drivers/watchdog/indydog.c index 788245bdaa7..5d20cdd30ef 100644 --- a/drivers/watchdog/indydog.c +++ b/drivers/watchdog/indydog.c @@ -1,16 +1,19 @@ /* * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22 * - * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, All Rights Reserved. + * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, + * 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 * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * - * based on softdog.c by Alan Cox <alan@redhat.com> + * 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> @@ -22,34 +25,34 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> -#include <asm/uaccess.h> +#include <linux/uaccess.h> #include <asm/sgi/mc.h> -#define PFX "indydog: " -static int indydog_alive; +static unsigned long indydog_alive; +static DEFINE_SPINLOCK(indydog_lock); #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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) { - u32 mc_ctrl0 = sgimc->cpuctrl0; - - mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG; - sgimc->cpuctrl0 = mc_ctrl0; + spin_lock(&indydog_lock); + sgimc->cpuctrl0 |= SGIMC_CCTRL0_WDOG; + spin_unlock(&indydog_lock); } static void indydog_stop(void) { - u32 mc_ctrl0 = sgimc->cpuctrl0; - - mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG; - sgimc->cpuctrl0 = mc_ctrl0; + spin_lock(&indydog_lock); + sgimc->cpuctrl0 &= ~SGIMC_CCTRL0_WDOG; + spin_unlock(&indydog_lock); - printk(KERN_INFO PFX "Stopped watchdog timer.\n"); + pr_info("Stopped watchdog timer\n"); } static void indydog_ping(void) @@ -62,7 +65,7 @@ static void indydog_ping(void) */ static int indydog_open(struct inode *inode, struct file *file) { - if (indydog_alive) + if (test_and_set_bit(0, &indydog_alive)) return -EBUSY; if (nowayout) @@ -72,8 +75,7 @@ static int indydog_open(struct inode *inode, struct file *file) indydog_start(); indydog_ping(); - indydog_alive = 1; - printk(KERN_INFO "Started watchdog timer.\n"); + pr_info("Started watchdog timer\n"); return nonseekable_open(inode, file); } @@ -84,69 +86,64 @@ static int indydog_release(struct inode *inode, struct file *file) * Lock it in if it's a module and we defined ...NOWAYOUT */ if (!nowayout) indydog_stop(); /* Turn the WDT off */ - - indydog_alive = 0; - + clear_bit(0, &indydog_alive); return 0; } -static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos) +static ssize_t indydog_write(struct file *file, const char *data, + size_t len, loff_t *ppos) { /* Refresh the timer. */ - if (len) { + if (len) indydog_ping(); - } return len; } -static int indydog_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long indydog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int options, retval = -EINVAL; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, .firmware_version = 0, .identity = "Hardware Watchdog for SGI IP22", }; switch (cmd) { - default: - return -ENOTTY; - 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_KEEPALIVE: - indydog_ping(); - return 0; - case WDIOC_GETTIMEOUT: - return put_user(WATCHDOG_TIMEOUT,(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_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) +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 */ @@ -158,7 +155,7 @@ static const struct file_operations indydog_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = indydog_write, - .ioctl = indydog_ioctl, + .unlocked_ioctl = indydog_ioctl, .open = indydog_open, .release = indydog_release, }; @@ -173,29 +170,25 @@ static struct notifier_block indydog_notifier = { .notifier_call = indydog_notify_sys, }; -static char banner[] __initdata = - KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n"; - static int __init watchdog_init(void) { int ret; ret = register_reboot_notifier(&indydog_notifier); if (ret) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); return ret; } ret = misc_register(&indydog_miscdev); if (ret) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); unregister_reboot_notifier(&indydog_notifier); return ret; } - printk(banner); + pr_info("Hardware Watchdog Timer for SGI IP22: 0.3\n"); return 0; } @@ -212,4 +205,3 @@ module_exit(watchdog_exit); MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/intel-mid_wdt.c b/drivers/watchdog/intel-mid_wdt.c new file mode 100644 index 00000000000..ca66e8e7463 --- /dev/null +++ b/drivers/watchdog/intel-mid_wdt.c @@ -0,0 +1,184 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General + * Public License as published by the Free Software Foundation. + */ + +#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, +}; + +static inline int wdt_command(int sub, u32 *in, int inlen) +{ + return intel_scu_ipc_command(IPC_WATCHDOG, sub, in, inlen, NULL, 0); +} + +static int wdt_start(struct watchdog_device *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 based on 4 bytes + */ + in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4); + + ret = wdt_command(SCU_WATCHDOG_START, (u32 *)&ipc_wd_start, in_size); + if (ret) { + struct device *dev = watchdog_get_drvdata(wd); + dev_crit(dev, "error starting watchdog: %d\n", ret); + } + + return ret; +} + +static int wdt_ping(struct watchdog_device *wd) +{ + int ret; + + ret = wdt_command(SCU_WATCHDOG_KEEPALIVE, NULL, 0); + if (ret) { + struct device *dev = watchdog_get_drvdata(wd); + dev_crit(dev, "Error executing keepalive: 0x%x\n", ret); + } + + return ret; +} + +static int wdt_stop(struct watchdog_device *wd) +{ + int ret; + + ret = wdt_command(SCU_WATCHDOG_STOP, NULL, 0); + if (ret) { + struct device *dev = watchdog_get_drvdata(wd); + dev_crit(dev, "Error stopping watchdog: 0x%x\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, +}; + +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 watchdog_device *wdt_dev; + struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data; + int ret; + + if (!pdata) { + dev_err(&pdev->dev, "missing platform data\n"); + return -EINVAL; + } + + if (pdata->probe) { + ret = pdata->probe(pdev); + if (ret) + return ret; + } + + wdt_dev = devm_kzalloc(&pdev->dev, sizeof(*wdt_dev), GFP_KERNEL); + if (!wdt_dev) + return -ENOMEM; + + 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; + + watchdog_set_drvdata(wdt_dev, &pdev->dev); + platform_set_drvdata(pdev, wdt_dev); + + ret = devm_request_irq(&pdev->dev, pdata->irq, mid_wdt_irq, + IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog", + wdt_dev); + if (ret) { + dev_err(&pdev->dev, "error requesting warning irq %d\n", + pdata->irq); + return ret; + } + + ret = watchdog_register_device(wdt_dev); + if (ret) { + dev_err(&pdev->dev, "error registering watchdog device\n"); + return ret; + } + + dev_info(&pdev->dev, "Intel MID watchdog device probed\n"); + + return 0; +} + +static int mid_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wd = platform_get_drvdata(pdev); + watchdog_unregister_device(wd); + return 0; +} + +static struct platform_driver mid_wdt_driver = { + .probe = mid_wdt_probe, + .remove = mid_wdt_remove, + .driver = { + .owner = THIS_MODULE, + .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"); diff --git a/drivers/watchdog/intel_scu_watchdog.c b/drivers/watchdog/intel_scu_watchdog.c new file mode 100644 index 00000000000..0caab6241eb --- /dev/null +++ b/drivers/watchdog/intel_scu_watchdog.c @@ -0,0 +1,565 @@ +/* + * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device + * for Intel part #(s): + * - AF82MP20 PCH + * + * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General + * Public License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/compiler.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/sfi.h> +#include <asm/irq.h> +#include <linux/atomic.h> +#include <asm/intel_scu_ipc.h> +#include <asm/apb_timer.h> +#include <asm/intel-mid.h> + +#include "intel_scu_watchdog.h" + +/* Bounds number of times we will retry loading time count */ +/* This retry is a work around for a silicon bug. */ +#define MAX_RETRY 16 + +#define IPC_SET_WATCHDOG_TIMER 0xF8 + +static int timer_margin = DEFAULT_SOFT_TO_HARD_MARGIN; +module_param(timer_margin, int, 0); +MODULE_PARM_DESC(timer_margin, + "Watchdog timer margin" + "Time between interrupt and resetting the system" + "The range is from 1 to 160" + "This is the time for all keep alives to arrive"); + +static int timer_set = DEFAULT_TIME; +module_param(timer_set, int, 0); +MODULE_PARM_DESC(timer_set, + "Default Watchdog timer setting" + "Complete cycle time" + "The range is from 1 to 170" + "This is the time for all keep alives to arrive"); + +/* After watchdog device is closed, check force_boot. If: + * force_boot == 0, then force boot on next watchdog interrupt after close, + * force_boot == 1, then force boot immediately when device is closed. + */ +static int force_boot; +module_param(force_boot, int, 0); +MODULE_PARM_DESC(force_boot, + "A value of 1 means that the driver will reboot" + "the system immediately if the /dev/watchdog device is closed" + "A value of 0 means that when /dev/watchdog device is closed" + "the watchdog timer will be refreshed for one more interval" + "of length: timer_set. At the end of this interval, the" + "watchdog timer will reset the system." + ); + +/* there is only one device in the system now; this can be made into + * an array in the future if we have more than one device */ + +static struct intel_scu_watchdog_dev watchdog_device; + +/* Forces restart, if force_reboot is set */ +static void watchdog_fire(void) +{ + if (force_boot) { + pr_crit("Initiating system reboot\n"); + emergency_restart(); + pr_crit("Reboot didn't ?????\n"); + } + + else { + pr_crit("Immediate Reboot Disabled\n"); + pr_crit("System will reset when watchdog timer times out!\n"); + } +} + +static int check_timer_margin(int new_margin) +{ + if ((new_margin < MIN_TIME_CYCLE) || + (new_margin > MAX_TIME - timer_set)) { + pr_debug("value of new_margin %d is out of the range %d to %d\n", + new_margin, MIN_TIME_CYCLE, MAX_TIME - timer_set); + return -EINVAL; + } + return 0; +} + +/* + * IPC operations + */ +static int watchdog_set_ipc(int soft_threshold, int threshold) +{ + u32 *ipc_wbuf; + u8 cbuf[16] = { '\0' }; + int ipc_ret = 0; + + ipc_wbuf = (u32 *)&cbuf; + ipc_wbuf[0] = soft_threshold; + ipc_wbuf[1] = threshold; + + ipc_ret = intel_scu_ipc_command( + IPC_SET_WATCHDOG_TIMER, + 0, + ipc_wbuf, + 2, + NULL, + 0); + + if (ipc_ret != 0) + pr_err("Error setting SCU watchdog timer: %x\n", ipc_ret); + + return ipc_ret; +}; + +/* + * Intel_SCU operations + */ + +/* timer interrupt handler */ +static irqreturn_t watchdog_timer_interrupt(int irq, void *dev_id) +{ + int int_status; + int_status = ioread32(watchdog_device.timer_interrupt_status_addr); + + pr_debug("irq, int_status: %x\n", int_status); + + if (int_status != 0) + return IRQ_NONE; + + /* has the timer been started? If not, then this is spurious */ + if (watchdog_device.timer_started == 0) { + pr_debug("spurious interrupt received\n"); + return IRQ_HANDLED; + } + + /* temporarily disable the timer */ + iowrite32(0x00000002, watchdog_device.timer_control_addr); + + /* set the timer to the threshold */ + iowrite32(watchdog_device.threshold, + watchdog_device.timer_load_count_addr); + + /* allow the timer to run */ + iowrite32(0x00000003, watchdog_device.timer_control_addr); + + return IRQ_HANDLED; +} + +static int intel_scu_keepalive(void) +{ + + /* read eoi register - clears interrupt */ + ioread32(watchdog_device.timer_clear_interrupt_addr); + + /* temporarily disable the timer */ + iowrite32(0x00000002, watchdog_device.timer_control_addr); + + /* set the timer to the soft_threshold */ + iowrite32(watchdog_device.soft_threshold, + watchdog_device.timer_load_count_addr); + + /* allow the timer to run */ + iowrite32(0x00000003, watchdog_device.timer_control_addr); + + return 0; +} + +static int intel_scu_stop(void) +{ + iowrite32(0, watchdog_device.timer_control_addr); + return 0; +} + +static int intel_scu_set_heartbeat(u32 t) +{ + int ipc_ret; + int retry_count; + u32 soft_value; + u32 hw_value; + + watchdog_device.timer_set = t; + watchdog_device.threshold = + timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; + watchdog_device.soft_threshold = + (watchdog_device.timer_set - timer_margin) + * watchdog_device.timer_tbl_ptr->freq_hz; + + pr_debug("set_heartbeat: timer freq is %d\n", + watchdog_device.timer_tbl_ptr->freq_hz); + pr_debug("set_heartbeat: timer_set is %x (hex)\n", + watchdog_device.timer_set); + pr_debug("set_hearbeat: timer_margin is %x (hex)\n", timer_margin); + pr_debug("set_heartbeat: threshold is %x (hex)\n", + watchdog_device.threshold); + pr_debug("set_heartbeat: soft_threshold is %x (hex)\n", + watchdog_device.soft_threshold); + + /* Adjust thresholds by FREQ_ADJUSTMENT factor, to make the */ + /* watchdog timing come out right. */ + watchdog_device.threshold = + watchdog_device.threshold / FREQ_ADJUSTMENT; + watchdog_device.soft_threshold = + watchdog_device.soft_threshold / FREQ_ADJUSTMENT; + + /* temporarily disable the timer */ + iowrite32(0x00000002, watchdog_device.timer_control_addr); + + /* send the threshold and soft_threshold via IPC to the processor */ + ipc_ret = watchdog_set_ipc(watchdog_device.soft_threshold, + watchdog_device.threshold); + + if (ipc_ret != 0) { + /* Make sure the watchdog timer is stopped */ + intel_scu_stop(); + return ipc_ret; + } + + /* Soft Threshold set loop. Early versions of silicon did */ + /* not always set this count correctly. This loop checks */ + /* the value and retries if it was not set correctly. */ + + retry_count = 0; + soft_value = watchdog_device.soft_threshold & 0xFFFF0000; + do { + + /* Make sure timer is stopped */ + intel_scu_stop(); + + if (MAX_RETRY < retry_count++) { + /* Unable to set timer value */ + pr_err("Unable to set timer\n"); + return -ENODEV; + } + + /* set the timer to the soft threshold */ + iowrite32(watchdog_device.soft_threshold, + watchdog_device.timer_load_count_addr); + + /* read count value before starting timer */ + ioread32(watchdog_device.timer_load_count_addr); + + /* Start the timer */ + iowrite32(0x00000003, watchdog_device.timer_control_addr); + + /* read the value the time loaded into its count reg */ + hw_value = ioread32(watchdog_device.timer_load_count_addr); + hw_value = hw_value & 0xFFFF0000; + + + } while (soft_value != hw_value); + + watchdog_device.timer_started = 1; + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int intel_scu_open(struct inode *inode, struct file *file) +{ + + /* Set flag to indicate that watchdog device is open */ + if (test_and_set_bit(0, &watchdog_device.driver_open)) + return -EBUSY; + + /* Check for reopen of driver. Reopens are not allowed */ + if (watchdog_device.driver_closed) + return -EPERM; + + return nonseekable_open(inode, file); +} + +static int intel_scu_release(struct inode *inode, struct file *file) +{ + /* + * This watchdog should not be closed, after the timer + * is started with the WDIPC_SETTIMEOUT ioctl + * If force_boot is set watchdog_fire() will cause an + * immediate reset. If force_boot is not set, the watchdog + * timer is refreshed for one more interval. At the end + * of that interval, the watchdog timer will reset the system. + */ + + if (!test_and_clear_bit(0, &watchdog_device.driver_open)) { + pr_debug("intel_scu_release, without open\n"); + return -ENOTTY; + } + + if (!watchdog_device.timer_started) { + /* Just close, since timer has not been started */ + pr_debug("closed, without starting timer\n"); + return 0; + } + + pr_crit("Unexpected close of /dev/watchdog!\n"); + + /* Since the timer was started, prevent future reopens */ + watchdog_device.driver_closed = 1; + + /* Refresh the timer for one more interval */ + intel_scu_keepalive(); + + /* Reboot system (if force_boot is set) */ + watchdog_fire(); + + /* We should only reach this point if force_boot is not set */ + return 0; +} + +static ssize_t intel_scu_write(struct file *file, + char const *data, + size_t len, + loff_t *ppos) +{ + + if (watchdog_device.timer_started) + /* Watchdog already started, keep it alive */ + intel_scu_keepalive(); + else + /* Start watchdog with timer value set by init */ + intel_scu_set_heartbeat(watchdog_device.timer_set); + + return len; +} + +static long intel_scu_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + u32 __user *p = argp; + u32 new_margin; + + + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT + | WDIOF_KEEPALIVEPING, + .firmware_version = 0, /* @todo Get from SCU via + ipc_get_scu_fw_version()? */ + .identity = "Intel_SCU IOH Watchdog" /* len < 32 */ + }; + + 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_KEEPALIVE: + intel_scu_keepalive(); + + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + + if (check_timer_margin(new_margin)) + return -EINVAL; + + if (intel_scu_set_heartbeat(new_margin)) + return -EINVAL; + return 0; + case WDIOC_GETTIMEOUT: + return put_user(watchdog_device.soft_threshold, p); + + default: + return -ENOTTY; + } +} + +/* + * Notifier for system down + */ +static int intel_scu_notify_sys(struct notifier_block *this, + unsigned long code, + void *another_unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + /* Turn off the watchdog timer. */ + intel_scu_stop(); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ +static const struct file_operations intel_scu_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = intel_scu_write, + .unlocked_ioctl = intel_scu_ioctl, + .open = intel_scu_open, + .release = intel_scu_release, +}; + +static int __init intel_scu_watchdog_init(void) +{ + int ret; + u32 __iomem *tmp_addr; + + /* + * We don't really need to check this as the SFI timer get will fail + * but if we do so we can exit with a clearer reason and no noise. + * + * If it isn't an intel MID device then it doesn't have this watchdog + */ + if (!intel_mid_identify_cpu()) + return -ENODEV; + + /* Check boot parameters to verify that their initial values */ + /* are in range. */ + /* Check value of timer_set boot parameter */ + if ((timer_set < MIN_TIME_CYCLE) || + (timer_set > MAX_TIME - MIN_TIME_CYCLE)) { + pr_err("value of timer_set %x (hex) is out of range from %x to %x (hex)\n", + timer_set, MIN_TIME_CYCLE, MAX_TIME - MIN_TIME_CYCLE); + return -EINVAL; + } + + /* Check value of timer_margin boot parameter */ + if (check_timer_margin(timer_margin)) + return -EINVAL; + + watchdog_device.timer_tbl_ptr = sfi_get_mtmr(sfi_mtimer_num-1); + + if (watchdog_device.timer_tbl_ptr == NULL) { + pr_debug("timer is not available\n"); + return -ENODEV; + } + /* make sure the timer exists */ + if (watchdog_device.timer_tbl_ptr->phys_addr == 0) { + pr_debug("timer %d does not have valid physical memory\n", + sfi_mtimer_num); + return -ENODEV; + } + + if (watchdog_device.timer_tbl_ptr->irq == 0) { + pr_debug("timer %d invalid irq\n", sfi_mtimer_num); + return -ENODEV; + } + + tmp_addr = ioremap_nocache(watchdog_device.timer_tbl_ptr->phys_addr, + 20); + + if (tmp_addr == NULL) { + pr_debug("timer unable to ioremap\n"); + return -ENOMEM; + } + + watchdog_device.timer_load_count_addr = tmp_addr++; + watchdog_device.timer_current_value_addr = tmp_addr++; + watchdog_device.timer_control_addr = tmp_addr++; + watchdog_device.timer_clear_interrupt_addr = tmp_addr++; + watchdog_device.timer_interrupt_status_addr = tmp_addr++; + + /* Set the default time values in device structure */ + + watchdog_device.timer_set = timer_set; + watchdog_device.threshold = + timer_margin * watchdog_device.timer_tbl_ptr->freq_hz; + watchdog_device.soft_threshold = + (watchdog_device.timer_set - timer_margin) + * watchdog_device.timer_tbl_ptr->freq_hz; + + + watchdog_device.intel_scu_notifier.notifier_call = + intel_scu_notify_sys; + + ret = register_reboot_notifier(&watchdog_device.intel_scu_notifier); + if (ret) { + pr_err("cannot register notifier %d)\n", ret); + goto register_reboot_error; + } + + watchdog_device.miscdev.minor = WATCHDOG_MINOR; + watchdog_device.miscdev.name = "watchdog"; + watchdog_device.miscdev.fops = &intel_scu_fops; + + ret = misc_register(&watchdog_device.miscdev); + if (ret) { + pr_err("cannot register miscdev %d err =%d\n", + WATCHDOG_MINOR, ret); + goto misc_register_error; + } + + ret = request_irq((unsigned int)watchdog_device.timer_tbl_ptr->irq, + watchdog_timer_interrupt, + IRQF_SHARED, "watchdog", + &watchdog_device.timer_load_count_addr); + if (ret) { + pr_err("error requesting irq %d\n", ret); + goto request_irq_error; + } + /* Make sure timer is disabled before returning */ + intel_scu_stop(); + return 0; + +/* error cleanup */ + +request_irq_error: + misc_deregister(&watchdog_device.miscdev); +misc_register_error: + unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); +register_reboot_error: + intel_scu_stop(); + iounmap(watchdog_device.timer_load_count_addr); + return ret; +} + +static void __exit intel_scu_watchdog_exit(void) +{ + + misc_deregister(&watchdog_device.miscdev); + unregister_reboot_notifier(&watchdog_device.intel_scu_notifier); + /* disable the timer */ + iowrite32(0x00000002, watchdog_device.timer_control_addr); + iounmap(watchdog_device.timer_load_count_addr); +} + +late_initcall(intel_scu_watchdog_init); +module_exit(intel_scu_watchdog_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel SCU Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(WDT_VER); diff --git a/drivers/watchdog/intel_scu_watchdog.h b/drivers/watchdog/intel_scu_watchdog.h new file mode 100644 index 00000000000..f3ac608deb6 --- /dev/null +++ b/drivers/watchdog/intel_scu_watchdog.h @@ -0,0 +1,65 @@ +/* + * Intel_SCU 0.2: An Intel SCU IOH Based Watchdog Device + * for Intel part #(s): + * - AF82MP20 PCH + * + * Copyright (C) 2009-2010 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General + * Public License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + * + */ + +#ifndef __INTEL_SCU_WATCHDOG_H +#define __INTEL_SCU_WATCHDOG_H + +#define WDT_VER "0.3" + +/* minimum time between interrupts */ +#define MIN_TIME_CYCLE 1 + +/* Time from warning to reboot is 2 seconds */ +#define DEFAULT_SOFT_TO_HARD_MARGIN 2 + +#define MAX_TIME 170 + +#define DEFAULT_TIME 5 + +#define MAX_SOFT_TO_HARD_MARGIN (MAX_TIME-MIN_TIME_CYCLE) + +/* Ajustment to clock tick frequency to make timing come out right */ +#define FREQ_ADJUSTMENT 8 + +struct intel_scu_watchdog_dev { + ulong driver_open; + ulong driver_closed; + u32 timer_started; + u32 timer_set; + u32 threshold; + u32 soft_threshold; + u32 __iomem *timer_load_count_addr; + u32 __iomem *timer_current_value_addr; + u32 __iomem *timer_control_addr; + u32 __iomem *timer_clear_interrupt_addr; + u32 __iomem *timer_interrupt_status_addr; + struct sfi_timer_table_entry *timer_tbl_ptr; + struct notifier_block intel_scu_notifier; + struct miscdevice miscdev; +}; + +extern int sfi_mtimer_num; + +/* extern struct sfi_timer_table_entry *sfi_get_mtmr(int hint); */ +#endif /* __INTEL_SCU_WATCHDOG_H */ diff --git a/drivers/watchdog/iop_wdt.c b/drivers/watchdog/iop_wdt.c index bbbd91af754..b16013ffacc 100644 --- a/drivers/watchdog/iop_wdt.c +++ b/drivers/watchdog/iop_wdt.c @@ -24,6 +24,8 @@ * Dan Williams <dan.j.williams@intel.com> */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> @@ -32,11 +34,12 @@ #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/uaccess.h> -#include <asm/hardware.h> +#include <mach/hardware.h> -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; static unsigned long wdt_status; static unsigned long boot_status; +static DEFINE_SPINLOCK(wdt_lock); #define WDT_IN_USE 0 #define WDT_OK_TO_CLOSE 1 @@ -68,8 +71,10 @@ static void wdt_enable(void) /* Arm and enable the Timer to starting counting down from 0xFFFF.FFFF * Takes approx. 10.7s to timeout */ + spin_lock(&wdt_lock); write_wdtcr(IOP_WDTCR_EN_ARM); write_wdtcr(IOP_WDTCR_EN); + spin_unlock(&wdt_lock); } /* returns 0 if the timer was successfully disabled */ @@ -77,10 +82,12 @@ static int wdt_disable(void) { /* Stop Counting */ if (wdt_supports_disable()) { + spin_lock(&wdt_lock); write_wdtcr(IOP_WDTCR_DIS_ARM); write_wdtcr(IOP_WDTCR_DIS); clear_bit(WDT_ENABLED, &wdt_status); - printk(KERN_INFO "WATCHDOG: Disabled\n"); + spin_unlock(&wdt_lock); + pr_info("Disabled\n"); return 0; } else return 1; @@ -92,16 +99,12 @@ static int iop_wdt_open(struct inode *inode, struct file *file) return -EBUSY; clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - wdt_enable(); - set_bit(WDT_ENABLED, &wdt_status); - return nonseekable_open(inode, file); } -static ssize_t -iop_wdt_write(struct file *file, const char *data, size_t len, +static ssize_t iop_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) { if (len) { @@ -121,46 +124,35 @@ iop_wdt_write(struct file *file, const char *data, size_t len, } wdt_enable(); } - return len; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .identity = "iop watchdog", }; -static int -iop_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long iop_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { int options; int ret = -ENOTTY; + int __user *argp = (int __user *)arg; switch (cmd) { case WDIOC_GETSUPPORT: - if (copy_to_user - ((struct watchdog_info *)arg, &ident, sizeof ident)) + if (copy_to_user(argp, &ident, sizeof(ident))) ret = -EFAULT; else ret = 0; break; case WDIOC_GETSTATUS: - ret = put_user(0, (int *)arg); + ret = put_user(0, argp); break; case WDIOC_GETBOOTSTATUS: - ret = put_user(boot_status, (int *)arg); - break; - - case WDIOC_GETTIMEOUT: - ret = put_user(iop_watchdog_timeout(), (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; + ret = put_user(boot_status, argp); break; case WDIOC_SETOPTIONS: @@ -177,14 +169,21 @@ iop_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, } else ret = 0; } - if (options & WDIOS_ENABLECARD) { wdt_enable(); ret = 0; } break; - } + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_GETTIMEOUT: + ret = put_user(iop_watchdog_timeout(), argp); + break; + } return ret; } @@ -195,13 +194,13 @@ static int iop_wdt_release(struct inode *inode, struct file *file) if (test_bit(WDT_ENABLED, &wdt_status)) state = wdt_disable(); - /* if the timer is not disbaled reload and notify that we are still + /* if the timer is not disabled reload and notify that we are still * going down */ if (state != 0) { wdt_enable(); - printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " - "reset in %lu seconds\n", iop_watchdog_timeout()); + pr_crit("Device closed unexpectedly - reset in %lu seconds\n", + iop_watchdog_timeout()); } clear_bit(WDT_IN_USE, &wdt_status); @@ -214,7 +213,7 @@ static const struct file_operations iop_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = iop_wdt_write, - .ioctl = iop_wdt_ioctl, + .unlocked_ioctl = iop_wdt_ioctl, .open = iop_wdt_open, .release = iop_wdt_release, }; @@ -229,11 +228,6 @@ static int __init iop_wdt_init(void) { int ret; - ret = misc_register(&iop_wdt_miscdev); - if (ret == 0) - printk("iop watchdog timer: timeout %lu sec\n", - iop_watchdog_timeout()); - /* check if the reset was caused by the watchdog timer */ boot_status = (read_rcsr() & IOP_RCSR_WDT) ? WDIOF_CARDRESET : 0; @@ -242,6 +236,12 @@ static int __init iop_wdt_init(void) */ write_wdtsr(IOP13XX_WDTCR_IB_RESET); + /* Register after we have the device set up so we cannot race + with an open */ + ret = misc_register(&iop_wdt_miscdev); + if (ret == 0) + pr_info("timeout %lu sec\n", iop_watchdog_timeout()); + return ret; } @@ -253,10 +253,9 @@ static void __exit iop_wdt_exit(void) module_init(iop_wdt_init); module_exit(iop_wdt_exit); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); MODULE_AUTHOR("Curt E Bruns <curt.e.bruns@intel.com>"); MODULE_DESCRIPTION("iop watchdog timer driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/it8712f_wdt.c b/drivers/watchdog/it8712f_wdt.c index 445b7e81211..41b3979a9d8 100644 --- a/drivers/watchdog/it8712f_wdt.c +++ b/drivers/watchdog/it8712f_wdt.c @@ -20,6 +20,8 @@ * 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> @@ -28,31 +30,29 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/fs.h> -#include <linux/pci.h> #include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/ioport.h> -#include <asm/uaccess.h> -#include <asm/io.h> - +#define DEBUG #define NAME "it8712f_wdt" MODULE_AUTHOR("Jorge Boncompte - DTI2 <jorge@dti2.net>"); MODULE_DESCRIPTION("IT8712F Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 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 int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); -static struct semaphore it8712f_wdt_sem; +static unsigned long wdt_open; static unsigned expect_close; -static spinlock_t io_lock; static unsigned char revision; /* Dog Food address - We use the game port address */ @@ -70,38 +70,43 @@ static unsigned short address; #define IT8712F_DEVID 0x8712 #define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */ -#define LDN_GAME 0x09 /* Game Port */ +#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 -#define WDT_RESET_KBD 0x20 -#define WDT_RESET_MOUSE 0x40 -#define WDT_RESET_CIR 0x80 +#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 -#define WDT_OUT_KRST 0x40 +#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) +static int superio_inb(int reg) { outb(reg, REG); return inb(VAL); } -static void -superio_outb(int val, int reg) +static void superio_outb(int val, int reg) { outb(reg, REG); outb(val, VAL); } -static int -superio_inw(int reg) +static int superio_inw(int reg) { int val; outb(reg++, REG); @@ -111,39 +116,41 @@ superio_inw(int reg) return val; } -static inline void -superio_select(int ldn) +static inline void superio_select(int ldn) { outb(LDN, REG); outb(ldn, VAL); } -static inline void -superio_enter(void) +static inline int superio_enter(void) { - spin_lock(&io_lock); + /* + * 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) +static inline void superio_exit(void) { outb(0x02, REG); outb(0x02, VAL); - spin_unlock(&io_lock); + release_region(REG, 2); } -static inline void -it8712f_wdt_ping(void) +static inline void it8712f_wdt_ping(void) { - inb(address); + if (wdt_control_reg & WDT_RESET_GAME) + inb(address); } -static void -it8712f_wdt_update_margin(void) +static void it8712f_wdt_update_margin(void) { int config = WDT_OUT_KRST | WDT_OUT_PWROK; int units = margin; @@ -153,10 +160,10 @@ it8712f_wdt_update_margin(void) */ if (units <= max_units) { config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */ - printk(KERN_INFO NAME ": timer margin %d seconds\n", units); + pr_info("timer margin %d seconds\n", units); } else { units /= 60; - printk(KERN_INFO NAME ": timer margin %d minutes\n", units); + pr_info("timer margin %d minutes\n", units); } superio_outb(config, WDT_CONFIG); @@ -165,8 +172,7 @@ it8712f_wdt_update_margin(void) superio_outb(units, WDT_TIMEOUT); } -static int -it8712f_wdt_get_status(void) +static int it8712f_wdt_get_status(void) { if (superio_inb(WDT_CONTROL) & 0x01) return WDIOF_CARDRESET; @@ -174,28 +180,33 @@ it8712f_wdt_get_status(void) return 0; } -static void -it8712f_wdt_enable(void) +static int it8712f_wdt_enable(void) { - printk(KERN_DEBUG NAME ": enabling watchdog timer\n"); - superio_enter(); + int ret = superio_enter(); + if (ret) + return ret; + + pr_debug("enabling watchdog timer\n"); superio_select(LDN_GPIO); - superio_outb(WDT_RESET_GAME, WDT_CONTROL); + superio_outb(wdt_control_reg, WDT_CONTROL); it8712f_wdt_update_margin(); superio_exit(); it8712f_wdt_ping(); + + return 0; } -static void -it8712f_wdt_disable(void) +static int it8712f_wdt_disable(void) { - printk(KERN_DEBUG NAME ": disabling watchdog timer\n"); + int ret = superio_enter(); + if (ret) + return ret; - superio_enter(); + pr_debug("disabling watchdog timer\n"); superio_select(LDN_GPIO); superio_outb(0, WDT_CONFIG); @@ -205,10 +216,10 @@ it8712f_wdt_disable(void) superio_outb(0, WDT_TIMEOUT); superio_exit(); + return 0; } -static int -it8712f_wdt_notify(struct notifier_block *this, +static int it8712f_wdt_notify(struct notifier_block *this, unsigned long code, void *unused) { if (code == SYS_HALT || code == SYS_POWER_OFF) @@ -222,9 +233,8 @@ 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) +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) { @@ -235,7 +245,7 @@ it8712f_wdt_write(struct file *file, const char __user *data, expect_close = 0; for (i = 0; i < len; ++i) { char c; - if (get_user(c, data+i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -245,28 +255,29 @@ it8712f_wdt_write(struct file *file, const char __user *data, return len; } -static int -it8712f_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { + static const struct watchdog_info ident = { .identity = "IT8712F Watchdog", .firmware_version = 1, - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, }; int value; + int ret; switch (cmd) { - default: - return -ENOTTY; case WDIOC_GETSUPPORT: if (copy_to_user(argp, &ident, sizeof(ident))) return -EFAULT; return 0; case WDIOC_GETSTATUS: - superio_enter(); + ret = superio_enter(); + if (ret) + return ret; superio_select(LDN_GPIO); value = it8712f_wdt_get_status(); @@ -287,7 +298,9 @@ it8712f_wdt_ioctl(struct inode *inode, struct file *file, if (value > (max_units * 60)) return -EINVAL; margin = value; - superio_enter(); + ret = superio_enter(); + if (ret) + return ret; superio_select(LDN_GPIO); it8712f_wdt_update_margin(); @@ -299,32 +312,34 @@ it8712f_wdt_ioctl(struct inode *inode, struct file *file, if (put_user(margin, p)) return -EFAULT; return 0; + default: + return -ENOTTY; } } -static int -it8712f_wdt_open(struct inode *inode, struct file *file) +static int it8712f_wdt_open(struct inode *inode, struct file *file) { + int ret; /* only allow one at a time */ - if (down_trylock(&it8712f_wdt_sem)) + if (test_and_set_bit(0, &wdt_open)) return -EBUSY; - it8712f_wdt_enable(); + ret = it8712f_wdt_enable(); + if (ret) + return ret; return nonseekable_open(inode, file); } -static int -it8712f_wdt_release(struct inode *inode, struct file *file) +static int it8712f_wdt_release(struct inode *inode, struct file *file) { if (expect_close != 42) { - printk(KERN_WARNING NAME - ": watchdog device closed unexpectedly, will not" - " disable the watchdog timer\n"); + pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); } else if (!nowayout) { - it8712f_wdt_disable(); + if (it8712f_wdt_disable()) + pr_warn("Watchdog disable failed\n"); } expect_close = 0; - up(&it8712f_wdt_sem); + clear_bit(0, &wdt_open); return 0; } @@ -333,7 +348,7 @@ static const struct file_operations it8712f_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = it8712f_wdt_write, - .ioctl = it8712f_wdt_ioctl, + .unlocked_ioctl = it8712f_wdt_ioctl, .open = it8712f_wdt_open, .release = it8712f_wdt_release, }; @@ -344,13 +359,14 @@ static struct miscdevice it8712f_wdt_miscdev = { .fops = &it8712f_wdt_fops, }; -static int __init -it8712f_wdt_find(unsigned short *address) +static int __init it8712f_wdt_find(unsigned short *address) { int err = -ENODEV; int chip_type; + int ret = superio_enter(); + if (ret) + return ret; - superio_enter(); chip_type = superio_inw(DEVID); if (chip_type != IT8712F_DEVID) goto exit; @@ -358,13 +374,13 @@ it8712f_wdt_find(unsigned short *address) superio_select(LDN_GAME); superio_outb(1, ACT_REG); if (!(superio_inb(ACT_REG) & 0x01)) { - printk(KERN_ERR NAME ": Device not activated, skipping\n"); + pr_err("Device not activated, skipping\n"); goto exit; } *address = superio_inw(BASE_REG); if (*address == 0) { - printk(KERN_ERR NAME ": Base address not set, skipping\n"); + pr_err("Base address not set, skipping\n"); goto exit; } @@ -378,8 +394,7 @@ it8712f_wdt_find(unsigned short *address) if (margin > (max_units * 60)) margin = (max_units * 60); - printk(KERN_INFO NAME ": Found IT%04xF chip revision %d - " - "using DogFood address 0x%x\n", + pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n", chip_type, revision, *address); exit: @@ -387,36 +402,34 @@ exit: return err; } -static int __init -it8712f_wdt_init(void) +static int __init it8712f_wdt_init(void) { int err = 0; - spin_lock_init(&io_lock); - if (it8712f_wdt_find(&address)) return -ENODEV; if (!request_region(address, 1, "IT8712F Watchdog")) { - printk(KERN_WARNING NAME ": watchdog I/O region busy\n"); + pr_warn("watchdog I/O region busy\n"); return -EBUSY; } - it8712f_wdt_disable(); - - sema_init(&it8712f_wdt_sem, 1); + 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) { - printk(KERN_ERR NAME ": unable to register reboot notifier\n"); + pr_err("unable to register reboot notifier\n"); goto out; } err = misc_register(&it8712f_wdt_miscdev); if (err) { - printk(KERN_ERR NAME - ": cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, err); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, err); goto reboot_out; } @@ -430,8 +443,7 @@ out: return err; } -static void __exit -it8712f_wdt_exit(void) +static void __exit it8712f_wdt_exit(void) { misc_deregister(&it8712f_wdt_miscdev); unregister_reboot_notifier(&it8712f_wdt_notifier); diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c new file mode 100644 index 00000000000..0b93739c010 --- /dev/null +++ b/drivers/watchdog/it87_wdt.c @@ -0,0 +1,795 @@ +/* + * 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 + * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 + * and IT8728. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#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.14" +#define WATCHDOG_NAME "IT87 WDT" +#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" +#define WD_MAGIC 'V' + +/* Defaults for Module Parameter */ +#define DEFAULT_NOGAMEPORT 0 +#define DEFAULT_NOCIR 0 +#define DEFAULT_EXCLUSIVE 1 +#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 +#define GAMEPORT 0x09 +#define CIR 0x0a + +/* Configuration Registers and Functions */ +#define LDNREG 0x07 +#define CHIPID 0x20 +#define CHIPREV 0x22 +#define ACTREG 0x30 +#define BASEREG 0x60 + +/* Chip Id numbers */ +#define NO_DEV_ID 0xffff +#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 + +/* GPIO Configuration Registers LDN=0x07 */ +#define WDTCTRL 0x71 +#define WDTCFG 0x72 +#define WDTVALLSB 0x73 +#define WDTVALMSB 0x74 + +/* GPIO Bits WDTCTRL */ +#define WDT_CIRINT 0x80 +#define WDT_MOUSEINT 0x40 +#define WDT_KYBINT 0x20 +#define WDT_GAMEPORT 0x10 /* not in it8718, it8720, it8721, it8728 */ +#define WDT_FORCE 0x02 +#define WDT_ZERO 0x01 + +/* 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 + +/* CIR Configuration Register LDN=0x0a */ +#define CIR_ILS 0x70 + +/* The default Base address is not always available, we use this */ +#define CIR_BASE 0x0208 + +/* CIR Controller */ +#define CIR_DR(b) (b) +#define CIR_IER(b) (b + 1) +#define CIR_RCR(b) (b + 2) +#define CIR_TCR1(b) (b + 3) +#define CIR_TCR2(b) (b + 4) +#define CIR_TSR(b) (b + 5) +#define CIR_RSR(b) (b + 6) +#define CIR_BDLR(b) (b + 5) +#define CIR_BDHR(b) (b + 6) +#define CIR_IIR(b) (b + 7) + +/* Default Base address of Game port */ +#define GP_BASE_DEFAULT 0x0201 + +/* wdt_status */ +#define WDTS_TIMER_RUN 0 +#define WDTS_DEV_OPEN 1 +#define WDTS_KEEPALIVE 2 +#define WDTS_LOCKED 3 +#define WDTS_USE_GP 4 +#define WDTS_EXPECTED 5 +#define WDTS_USE_CIR 6 + +static unsigned int base, gpact, ciract, max_units, chip_type; +static unsigned long wdt_status; + +static int nogameport = DEFAULT_NOGAMEPORT; +static int nocir = DEFAULT_NOCIR; +static int exclusive = DEFAULT_EXCLUSIVE; +static int timeout = DEFAULT_TIMEOUT; +static int testmode = DEFAULT_TESTMODE; +static bool nowayout = DEFAULT_NOWAYOUT; + +module_param(nogameport, int, 0); +MODULE_PARM_DESC(nogameport, "Forbid the activation of game port, default=" + __MODULE_STRING(DEFAULT_NOGAMEPORT)); +module_param(nocir, int, 0); +MODULE_PARM_DESC(nocir, "Forbid the use of Consumer IR interrupts to reset timer, default=" + __MODULE_STRING(DEFAULT_NOCIR)); +module_param(exclusive, int, 0); +MODULE_PARM_DESC(exclusive, "Watchdog exclusive device open, default=" + __MODULE_STRING(DEFAULT_EXCLUSIVE)); +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; +} + +static inline void superio_outw(int val, int reg) +{ + outb(reg++, REG); + outb(val >> 8, VAL); + outb(reg, REG); + outb(val, VAL); +} + +/* Internal function, should be called after superio_select(GPIO) */ +static void wdt_update_timeout(void) +{ + unsigned char cfg = WDT_KRST; + int tm = timeout; + + if (testmode) + cfg = 0; + + if (tm <= max_units) + cfg |= WDT_TOV1; + else + tm /= 60; + + if (chip_type != IT8721_ID) + cfg |= WDT_PWROK; + + superio_outb(cfg, WDTCFG); + superio_outb(tm, WDTVALLSB); + if (max_units > 255) + superio_outb(tm>>8, WDTVALMSB); +} + +static int wdt_round_time(int t) +{ + t += 59; + t -= t % 60; + return t; +} + +/* watchdog timer handling */ + +static void wdt_keepalive(void) +{ + if (test_bit(WDTS_USE_GP, &wdt_status)) + inb(base); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) + /* The timer reloads with around 5 msec delay */ + outb(0x55, CIR_DR(base)); + else { + if (superio_enter()) + return; + + superio_select(GPIO); + wdt_update_timeout(); + superio_exit(); + } + set_bit(WDTS_KEEPALIVE, &wdt_status); +} + +static int wdt_start(void) +{ + int ret = superio_enter(); + if (ret) + return ret; + + superio_select(GPIO); + if (test_bit(WDTS_USE_GP, &wdt_status)) + superio_outb(WDT_GAMEPORT, WDTCTRL); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) + superio_outb(WDT_CIRINT, WDTCTRL); + wdt_update_timeout(); + + superio_exit(); + + return 0; +} + +static int wdt_stop(void) +{ + int ret = superio_enter(); + if (ret) + return ret; + + superio_select(GPIO); + superio_outb(0x00, WDTCTRL); + superio_outb(WDT_TOV1, WDTCFG); + superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); + + superio_exit(); + return 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(int t) +{ + if (t < 1 || t > max_units * 60) + return -EINVAL; + + if (t > max_units) + timeout = wdt_round_time(t); + else + timeout = t; + + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + int ret = superio_enter(); + if (ret) + return ret; + + superio_select(GPIO); + wdt_update_timeout(); + superio_exit(); + } + return 0; +} + +/** + * wdt_get_status - determines the status supported by watchdog ioctl + * @status: status returned to user space + * + * The status bit of the device does not allow to distinguish + * between a regular system reset and a watchdog forced reset. + * But, in test mode it is useful, so it is supported through + * WDIOC_GETSTATUS watchdog ioctl. Additionally the driver + * reports the keepalive signal and the acception of the magic. + * + * Used within WDIOC_GETSTATUS watchdog device ioctl. + */ + +static int wdt_get_status(int *status) +{ + *status = 0; + if (testmode) { + int ret = superio_enter(); + if (ret) + return ret; + + superio_select(GPIO); + if (superio_inb(WDTCTRL) & WDT_ZERO) { + superio_outb(0x00, WDTCTRL); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + *status |= WDIOF_CARDRESET; + } + + superio_exit(); + } + if (test_and_clear_bit(WDTS_KEEPALIVE, &wdt_status)) + *status |= WDIOF_KEEPALIVEPING; + if (test_bit(WDTS_EXPECTED, &wdt_status)) + *status |= WDIOF_MAGICCLOSE; + return 0; +} + +/* /dev/watchdog handling */ + +/** + * wdt_open - watchdog file_operations .open + * @inode: inode of the device + * @file: file handle to the device + * + * The watchdog timer starts by opening the device. + * + * Used within the file operation of the watchdog device. + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + if (exclusive && test_and_set_bit(WDTS_DEV_OPEN, &wdt_status)) + return -EBUSY; + if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { + int ret; + if (nowayout && !test_and_set_bit(WDTS_LOCKED, &wdt_status)) + __module_get(THIS_MODULE); + + ret = wdt_start(); + if (ret) { + clear_bit(WDTS_LOCKED, &wdt_status); + clear_bit(WDTS_TIMER_RUN, &wdt_status); + clear_bit(WDTS_DEV_OPEN, &wdt_status); + return ret; + } + } + return nonseekable_open(inode, file); +} + +/** + * wdt_release - watchdog file_operations .release + * @inode: inode of the device + * @file: file handle to the device + * + * Closing the watchdog device either stops the watchdog timer + * or in the case, that nowayout is set or the magic character + * wasn't written, a critical warning about an running watchdog + * timer is given. + * + * Used within the file operation of the watchdog device. + */ + +static int wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + if (test_and_clear_bit(WDTS_EXPECTED, &wdt_status)) { + int ret = wdt_stop(); + if (ret) { + /* + * Stop failed. Just keep the watchdog alive + * and hope nothing bad happens. + */ + set_bit(WDTS_EXPECTED, &wdt_status); + wdt_keepalive(); + return ret; + } + clear_bit(WDTS_TIMER_RUN, &wdt_status); + } else { + wdt_keepalive(); + pr_crit("unexpected close, not stopping watchdog!\n"); + } + } + clear_bit(WDTS_DEV_OPEN, &wdt_status); + return 0; +} + +/** + * wdt_write - watchdog file_operations .write + * @file: file handle to the watchdog + * @buf: buffer to write + * @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. + * + * Used within the file operation of the watchdog device. + */ + +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + clear_bit(WDTS_EXPECTED, &wdt_status); + wdt_keepalive(); + } + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character long ago */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == WD_MAGIC) + set_bit(WDTS_EXPECTED, &wdt_status); + } + } + return count; +} + +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +/** + * wdt_ioctl - watchdog file_operations .unlocked_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. + * + * Used within the file operation of the watchdog device. + */ + +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rc = 0, status, new_options, 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: + rc = wdt_get_status(&status); + if (rc) + return rc; + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + + case WDIOC_SETOPTIONS: + if (get_user(new_options, uarg.i)) + return -EFAULT; + + switch (new_options) { + case WDIOS_DISABLECARD: + if (test_bit(WDTS_TIMER_RUN, &wdt_status)) { + rc = wdt_stop(); + if (rc) + return rc; + } + clear_bit(WDTS_TIMER_RUN, &wdt_status); + return 0; + + case WDIOS_ENABLECARD: + if (!test_and_set_bit(WDTS_TIMER_RUN, &wdt_status)) { + rc = wdt_start(); + if (rc) { + clear_bit(WDTS_TIMER_RUN, &wdt_status); + return rc; + } + } + return 0; + + default: + return -EFAULT; + } + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + rc = wdt_set_timeout(new_timeout); + case WDIOC_GETTIMEOUT: + if (put_user(timeout, uarg.i)) + return -EFAULT; + return rc; + + 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, + .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 it87_wdt_init(void) +{ + int rc = 0; + int try_gameport = !nogameport; + u8 chip_rev; + int gp_rreq_fail = 0; + + wdt_status = 0; + + 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 IT8718_ID: + case IT8720_ID: + case IT8721_ID: + case IT8728_ID: + max_units = 65535; + try_gameport = 0; + 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); + + /* First try to get Gameport support */ + if (try_gameport) { + superio_select(GAMEPORT); + base = superio_inw(BASEREG); + if (!base) { + base = GP_BASE_DEFAULT; + superio_outw(base, BASEREG); + } + gpact = superio_inb(ACTREG); + superio_outb(0x01, ACTREG); + if (request_region(base, 1, WATCHDOG_NAME)) + set_bit(WDTS_USE_GP, &wdt_status); + else + gp_rreq_fail = 1; + } + + /* If we haven't Gameport support, try to get CIR support */ + if (!nocir && !test_bit(WDTS_USE_GP, &wdt_status)) { + if (!request_region(CIR_BASE, 8, WATCHDOG_NAME)) { + if (gp_rreq_fail) + pr_err("I/O Address 0x%04x and 0x%04x already in use\n", + base, CIR_BASE); + else + pr_err("I/O Address 0x%04x already in use\n", + CIR_BASE); + rc = -EIO; + goto err_out; + } + base = CIR_BASE; + + superio_select(CIR); + superio_outw(base, BASEREG); + superio_outb(0x00, CIR_ILS); + ciract = superio_inb(ACTREG); + superio_outb(0x01, ACTREG); + if (gp_rreq_fail) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } + set_bit(WDTS_USE_CIR, &wdt_status); + } + + 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); + + 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; + } + + /* Initialize CIR to use it as keepalive source */ + if (test_bit(WDTS_USE_CIR, &wdt_status)) { + outb(0x00, CIR_RCR(base)); + outb(0xc0, CIR_TCR1(base)); + outb(0x5c, CIR_TCR2(base)); + outb(0x10, CIR_IER(base)); + outb(0x00, CIR_BDHR(base)); + outb(0x01, CIR_BDLR(base)); + outb(0x09, CIR_IER(base)); + } + + pr_info("Chip IT%04x revision %d initialized. timeout=%d sec (nowayout=%d testmode=%d exclusive=%d nogameport=%d nocir=%d)\n", + chip_type, chip_rev, timeout, + nowayout, testmode, exclusive, nogameport, nocir); + + superio_exit(); + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region: + if (test_bit(WDTS_USE_GP, &wdt_status)) + release_region(base, 1); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) { + release_region(base, 8); + superio_select(CIR); + superio_outb(ciract, ACTREG); + } +err_out: + if (try_gameport) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } + + superio_exit(); + return rc; +} + +static void __exit it87_wdt_exit(void) +{ + if (superio_enter() == 0) { + superio_select(GPIO); + superio_outb(0x00, WDTCTRL); + superio_outb(0x00, WDTCFG); + superio_outb(0x00, WDTVALLSB); + if (max_units > 255) + superio_outb(0x00, WDTVALMSB); + if (test_bit(WDTS_USE_GP, &wdt_status)) { + superio_select(GAMEPORT); + superio_outb(gpact, ACTREG); + } else if (test_bit(WDTS_USE_CIR, &wdt_status)) { + superio_select(CIR); + superio_outb(ciract, ACTREG); + } + superio_exit(); + } + + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + + if (test_bit(WDTS_USE_GP, &wdt_status)) + release_region(base, 1); + else if (test_bit(WDTS_USE_CIR, &wdt_status)) + release_region(base, 8); +} + +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/ixp2000_wdt.c b/drivers/watchdog/ixp2000_wdt.c deleted file mode 100644 index dc7548dcaf3..00000000000 --- a/drivers/watchdog/ixp2000_wdt.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * drivers/char/watchdog/ixp2000_wdt.c - * - * Watchdog driver for Intel IXP2000 network processors - * - * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek. - * The original version 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. - */ - -#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 <asm/hardware.h> -#include <asm/uaccess.h> - -static int nowayout = WATCHDOG_NOWAYOUT; -static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ -static unsigned long wdt_status; - -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 - -static unsigned long wdt_tick_rate; - -static void -wdt_enable(void) -{ - ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); - ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); - ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); - ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); -} - -static void -wdt_disable(void) -{ - ixp2000_reg_write(IXP2000_T4_CTL, 0); -} - -static void -wdt_keepalive(void) -{ - ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); -} - -static int -ixp2000_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 nonseekable_open(inode, file); -} - -static ssize_t -ixp2000_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 struct watchdog_info ident = { - .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING, - .identity = "IXP2000 Watchdog", -}; - -static int -ixp2000_wdt_ioctl(struct inode *inode, 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_SETTIMEOUT: - ret = get_user(time, (int *)arg); - if (ret) - break; - - if (time <= 0 || time > 60) { - ret = -EINVAL; - break; - } - - heartbeat = time; - wdt_keepalive(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(heartbeat, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - } - - return ret; -} - -static int -ixp2000_wdt_release(struct inode *inode, struct file *file) -{ - if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { - wdt_disable(); - } else { - printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " - "timer will not stop\n"); - } - - clear_bit(WDT_IN_USE, &wdt_status); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - return 0; -} - - -static const struct file_operations ixp2000_wdt_fops = -{ - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = ixp2000_wdt_write, - .ioctl = ixp2000_wdt_ioctl, - .open = ixp2000_wdt_open, - .release = ixp2000_wdt_release, -}; - -static struct miscdevice ixp2000_wdt_miscdev = -{ - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &ixp2000_wdt_fops, -}; - -static int __init ixp2000_wdt_init(void) -{ - if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) { - printk(KERN_INFO "Unable to use IXP2000 watchdog due to IXP2800 erratum #25.\n"); - return -EIO; - } - - wdt_tick_rate = (*IXP2000_T1_CLD * HZ) / 256; - - return misc_register(&ixp2000_wdt_miscdev); -} - -static void __exit ixp2000_wdt_exit(void) -{ - misc_deregister(&ixp2000_wdt_miscdev); -} - -module_init(ixp2000_wdt_init); -module_exit(ixp2000_wdt_exit); - -MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); -MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog"); - -module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); - -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); - -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - diff --git a/drivers/watchdog/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c index 5864bb865cf..f20cc53ff71 100644 --- a/drivers/watchdog/ixp4xx_wdt.c +++ b/drivers/watchdog/ixp4xx_wdt.c @@ -13,6 +13,8 @@ * 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> @@ -22,48 +24,47 @@ #include <linux/watchdog.h> #include <linux/init.h> #include <linux/bitops.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> -#include <asm/hardware.h> -#include <asm/uaccess.h> - -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; static int heartbeat = 60; /* (secs) Default is 1 minute */ static unsigned long wdt_status; static unsigned long boot_status; +static DEFINE_SPINLOCK(wdt_lock); #define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) #define WDT_IN_USE 0 #define WDT_OK_TO_CLOSE 1 -static void -wdt_enable(void) +static void wdt_enable(void) { + spin_lock(&wdt_lock); *IXP4XX_OSWK = IXP4XX_WDT_KEY; *IXP4XX_OSWE = 0; *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; *IXP4XX_OSWK = 0; + spin_unlock(&wdt_lock); } -static void -wdt_disable(void) +static void wdt_disable(void) { + spin_lock(&wdt_lock); *IXP4XX_OSWK = IXP4XX_WDT_KEY; *IXP4XX_OSWE = 0; *IXP4XX_OSWK = 0; + spin_unlock(&wdt_lock); } -static int -ixp4xx_wdt_open(struct inode *inode, struct file *file) +static int ixp4xx_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 nonseekable_open(inode, file); } @@ -87,20 +88,18 @@ ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) } wdt_enable(); } - return len; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, .identity = "IXP4xx Watchdog", }; -static int -ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long ixp4xx_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int ret = -ENOTTY; int time; @@ -119,6 +118,11 @@ ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, ret = put_user(boot_status, (int *)arg); break; + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + case WDIOC_SETTIMEOUT: ret = get_user(time, (int *)arg); if (ret) @@ -136,25 +140,16 @@ ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, case WDIOC_GETTIMEOUT: ret = put_user(heartbeat, (int *)arg); break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; } return ret; } -static int -ixp4xx_wdt_release(struct inode *inode, struct file *file) +static int ixp4xx_wdt_release(struct inode *inode, struct file *file) { - if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) wdt_disable(); - } else { - printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - " - "timer will not stop\n"); - } - + else + pr_crit("Device closed unexpectedly - timer will not stop\n"); clear_bit(WDT_IN_USE, &wdt_status); clear_bit(WDT_OK_TO_CLOSE, &wdt_status); @@ -162,18 +157,16 @@ ixp4xx_wdt_release(struct inode *inode, struct file *file) } -static const struct file_operations ixp4xx_wdt_fops = -{ +static const struct file_operations ixp4xx_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = ixp4xx_wdt_write, - .ioctl = ixp4xx_wdt_ioctl, + .unlocked_ioctl = ixp4xx_wdt_ioctl, .open = ixp4xx_wdt_open, .release = ixp4xx_wdt_release, }; -static struct miscdevice ixp4xx_wdt_miscdev = -{ +static struct miscdevice ixp4xx_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &ixp4xx_wdt_fops, @@ -182,23 +175,17 @@ static struct miscdevice ixp4xx_wdt_miscdev = static int __init ixp4xx_wdt_init(void) { int ret; - unsigned long processor_id; - asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :); - if (!(processor_id & 0xf) && !cpu_is_ixp46x()) { - printk("IXP4XXX Watchdog: Rev. A0 IXP42x CPU detected - " - "watchdog disabled\n"); + if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { + pr_err("Rev. A0 IXP42x CPU detected - watchdog disabled\n"); return -ENODEV; } - - ret = misc_register(&ixp4xx_wdt_miscdev); - if (ret == 0) - printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat); - boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? WDIOF_CARDRESET : 0; - + ret = misc_register(&ixp4xx_wdt_miscdev); + if (ret == 0) + pr_info("timer heartbeat %d sec\n", heartbeat); return ret; } @@ -217,9 +204,7 @@ MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - diff --git a/drivers/watchdog/jz4740_wdt.c b/drivers/watchdog/jz4740_wdt.c new file mode 100644 index 00000000000..91e45ca589e --- /dev/null +++ b/drivers/watchdog/jz4740_wdt.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> + * JZ4740 Watchdog driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#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 <asm/mach-jz4740/timer.h> + +#define JZ_REG_WDT_TIMER_DATA 0x0 +#define JZ_REG_WDT_COUNTER_ENABLE 0x4 +#define JZ_REG_WDT_TIMER_COUNTER 0x8 +#define JZ_REG_WDT_TIMER_CONTROL 0xC + +#define JZ_WDT_CLOCK_PCLK 0x1 +#define JZ_WDT_CLOCK_RTC 0x2 +#define JZ_WDT_CLOCK_EXT 0x4 + +#define JZ_WDT_CLOCK_DIV_SHIFT 3 + +#define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) +#define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) +#define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) +#define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) +#define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) +#define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) + +#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; + void __iomem *base; + struct clk *rtc_clk; +}; + +static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) +{ + struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + + writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); + 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); + unsigned int rtc_clk_rate; + unsigned int timeout_value; + unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; + + rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); + + timeout_value = rtc_clk_rate * new_timeout; + while (timeout_value > 0xffff) { + if (clock_div == JZ_WDT_CLOCK_DIV_1024) { + /* Requested timeout too high; + * use highest possible value. */ + timeout_value = 0xffff; + break; + } + timeout_value >>= 2; + clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); + } + + writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); + writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); + + writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); + writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); + writew(clock_div | JZ_WDT_CLOCK_RTC, + drvdata->base + JZ_REG_WDT_TIMER_CONTROL); + + writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); + + wdt_dev->timeout = new_timeout; + return 0; +} + +static int jz4740_wdt_start(struct watchdog_device *wdt_dev) +{ + jz4740_timer_enable_watchdog(); + jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); + + return 0; +} + +static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) +{ + struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); + + jz4740_timer_disable_watchdog(); + writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); + + 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, +}; + +static int jz4740_wdt_probe(struct platform_device *pdev) +{ + struct jz4740_wdt_drvdata *drvdata; + struct watchdog_device *jz4740_wdt; + struct resource *res; + int ret; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), + GFP_KERNEL); + if (!drvdata) { + dev_err(&pdev->dev, "Unable to alloacate watchdog device\n"); + return -ENOMEM; + } + + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) + heartbeat = DEFAULT_HEARTBEAT; + + jz4740_wdt = &drvdata->wdt; + jz4740_wdt->info = &jz4740_wdt_info; + jz4740_wdt->ops = &jz4740_wdt_ops; + jz4740_wdt->timeout = heartbeat; + jz4740_wdt->min_timeout = 1; + jz4740_wdt->max_timeout = MAX_HEARTBEAT; + watchdog_set_nowayout(jz4740_wdt, nowayout); + watchdog_set_drvdata(jz4740_wdt, drvdata); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drvdata->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drvdata->base)) { + ret = PTR_ERR(drvdata->base); + goto err_out; + } + + drvdata->rtc_clk = clk_get(&pdev->dev, "rtc"); + if (IS_ERR(drvdata->rtc_clk)) { + dev_err(&pdev->dev, "cannot find RTC clock\n"); + ret = PTR_ERR(drvdata->rtc_clk); + goto err_out; + } + + ret = watchdog_register_device(&drvdata->wdt); + if (ret < 0) + goto err_disable_clk; + + platform_set_drvdata(pdev, drvdata); + return 0; + +err_disable_clk: + clk_put(drvdata->rtc_clk); +err_out: + return ret; +} + +static int jz4740_wdt_remove(struct platform_device *pdev) +{ + struct jz4740_wdt_drvdata *drvdata = platform_get_drvdata(pdev); + + jz4740_wdt_stop(&drvdata->wdt); + watchdog_unregister_device(&drvdata->wdt); + clk_put(drvdata->rtc_clk); + + return 0; +} + +static struct platform_driver jz4740_wdt_driver = { + .probe = jz4740_wdt_probe, + .remove = jz4740_wdt_remove, + .driver = { + .name = "jz4740-wdt", + .owner = THIS_MODULE, + }, +}; + +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/kempld_wdt.c b/drivers/watchdog/kempld_wdt.c new file mode 100644 index 00000000000..d9c1a160192 --- /dev/null +++ b/drivers/watchdog/kempld_wdt.c @@ -0,0 +1,579 @@ +/* + * Kontron PLD watchdog driver + * + * Copyright (c) 2010-2013 Kontron Europe GmbH + * Author: Michael Brunner <michael.brunner@kontron.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 = kempld_prescaler[PRESCALER_21]; + u64 stage_timeout64; + u32 stage_timeout; + u32 remainder; + u8 stage_cfg; + + 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 struct watchdog_info kempld_wdt_info = { + .identity = "KEMPLD Watchdog", + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT +}; + +static 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); + ret = watchdog_register_device(wdd); + if (ret) + return ret; + + dev_info(dev, "Watchdog registered with %ds timeout\n", wdd->timeout); + + return 0; +} + +static void kempld_wdt_shutdown(struct platform_device *pdev) +{ + struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); + + kempld_wdt_stop(&wdt_data->wdd); +} + +static int kempld_wdt_remove(struct platform_device *pdev) +{ + struct kempld_wdt_data *wdt_data = platform_get_drvdata(pdev); + struct watchdog_device *wdd = &wdt_data->wdd; + int ret = 0; + + if (!nowayout) + ret = kempld_wdt_stop(wdd); + watchdog_unregister_device(wdd); + + return ret; +} + +#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", + .owner = THIS_MODULE, + }, + .probe = kempld_wdt_probe, + .remove = kempld_wdt_remove, + .shutdown = kempld_wdt_shutdown, + .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/ks8695_wdt.c b/drivers/watchdog/ks8695_wdt.c index e3a29c30230..40ca5594a33 100644 --- a/drivers/watchdog/ks8695_wdt.c +++ b/drivers/watchdog/ks8695_wdt.c @@ -8,6 +8,8 @@ * published by the Free Software Foundation. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/bitops.h> #include <linux/errno.h> #include <linux/fs.h> @@ -19,50 +21,68 @@ #include <linux/platform_device.h> #include <linux/types.h> #include <linux/watchdog.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/arch/regs-timer.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> + +#define KS8695_TMR_OFFSET (0xF0000 + 0xE400) +#define KS8695_TMR_VA (KS8695_IO_VA + KS8695_TMR_OFFSET) + +/* + * Timer registers + */ +#define KS8695_TMCON (0x00) /* Timer Control Register */ +#define KS8695_T0TC (0x08) /* Timer 0 Timeout Count Register */ +#define TMCON_T0EN (1 << 0) /* Timer 0 Enable */ +/* Timer0 Timeout Counter Register */ +#define T0TC_WATCHDOG (0xff) /* Enable watchdog mode */ #define WDT_DEFAULT_TIME 5 /* seconds */ #define WDT_MAX_TIME 171 /* seconds */ static int wdt_time = WDT_DEFAULT_TIME; -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; module_param(wdt_time, int, 0); -MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default="__MODULE_STRING(WDT_DEFAULT_TIME) ")"); +MODULE_PARM_DESC(wdt_time, "Watchdog time in seconds. (default=" + __MODULE_STRING(WDT_DEFAULT_TIME) ")"); #ifdef CONFIG_WATCHDOG_NOWAYOUT -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(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 ks8695wdt_busy; +static DEFINE_SPINLOCK(ks8695_lock); /* ......................................................................... */ /* * Disable the watchdog. */ -static void inline ks8695_wdt_stop(void) +static inline void ks8695_wdt_stop(void) { unsigned long tmcon; + spin_lock(&ks8695_lock); /* disable timer0 */ tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); + spin_unlock(&ks8695_lock); } /* * Enable and reset the watchdog. */ -static void inline ks8695_wdt_start(void) +static inline void ks8695_wdt_start(void) { unsigned long tmcon; - unsigned long tval = wdt_time * CLOCK_TICK_RATE; + unsigned long tval = wdt_time * KS8695_CLOCK_RATE; + spin_lock(&ks8695_lock); /* disable timer0 */ tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); @@ -73,19 +93,22 @@ static void inline ks8695_wdt_start(void) /* re-enable timer0 */ tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); + spin_unlock(&ks8695_lock); } /* * Reload the watchdog timer. (ie, pat the watchdog) */ -static void inline ks8695_wdt_reload(void) +static inline void ks8695_wdt_reload(void) { unsigned long tmcon; + spin_lock(&ks8695_lock); /* disable, then re-enable timer0 */ tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON); __raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); __raw_writel(tmcon | TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON); + spin_unlock(&ks8695_lock); } /* @@ -94,7 +117,7 @@ static void inline ks8695_wdt_reload(void) static int ks8695_wdt_settimeout(int new_time) { /* - * All counting occurs at SLOW_CLOCK / 128 = 0.256 Hz + * All counting occurs at KS8695_CLOCK_RATE / 128 = 0.256 Hz * * Since WDV is a 16-bit counter, the maximum period is * 65536 / 0.256 = 256 seconds. @@ -102,7 +125,8 @@ static int ks8695_wdt_settimeout(int new_time) if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) return -EINVAL; - /* Set new watchdog time. It will be used when ks8695_wdt_start() is called. */ + /* Set new watchdog time. It will be used when + ks8695_wdt_start() is called. */ wdt_time = new_time; return 0; } @@ -128,14 +152,14 @@ static int ks8695_wdt_open(struct inode *inode, struct file *file) */ static int ks8695_wdt_close(struct inode *inode, struct file *file) { + /* Disable the watchdog when file is closed */ if (!nowayout) - ks8695_wdt_stop(); /* Disable the watchdog when file is closed */ - + ks8695_wdt_stop(); clear_bit(0, &ks8695wdt_busy); return 0; } -static struct watchdog_info ks8695_wdt_info = { +static const struct watchdog_info ks8695_wdt_info = { .identity = "ks8695 watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, }; @@ -143,60 +167,52 @@ static struct watchdog_info ks8695_wdt_info = { /* * Handle commands from user-space. */ -static int ks8695_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long ks8695_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_KEEPALIVE: - ks8695_wdt_reload(); /* pat the watchdog */ - return 0; - - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ks8695_wdt_info, sizeof(ks8695_wdt_info)) ? -EFAULT : 0; - - case WDIOC_SETTIMEOUT: - if (get_user(new_value, p)) - return -EFAULT; - - if (ks8695_wdt_settimeout(new_value)) - return -EINVAL; - - /* Enable new time value */ + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ks8695_wdt_info, + sizeof(ks8695_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) + ks8695_wdt_stop(); + if (new_value & WDIOS_ENABLECARD) ks8695_wdt_start(); - - /* Return current value */ - return put_user(wdt_time, p); - - case WDIOC_GETTIMEOUT: - return put_user(wdt_time, p); - - 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) - ks8695_wdt_stop(); - if (new_value & WDIOS_ENABLECARD) - ks8695_wdt_start(); - return 0; - - default: - return -ENOTTY; + return 0; + case WDIOC_KEEPALIVE: + ks8695_wdt_reload(); /* pat the watchdog */ + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + if (ks8695_wdt_settimeout(new_value)) + return -EINVAL; + /* Enable new time value */ + ks8695_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 ks8695_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +static ssize_t ks8695_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) { ks8695_wdt_reload(); /* pat the watchdog */ return len; @@ -207,7 +223,7 @@ static ssize_t ks8695_wdt_write(struct file *file, const char *data, size_t len, static const struct file_operations ks8695wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = ks8695_wdt_ioctl, + .unlocked_ioctl = ks8695_wdt_ioctl, .open = ks8695_wdt_open, .release = ks8695_wdt_close, .write = ks8695_wdt_write, @@ -219,7 +235,7 @@ static struct miscdevice ks8695wdt_miscdev = { .fops = &ks8695wdt_fops, }; -static int __init ks8695wdt_probe(struct platform_device *pdev) +static int ks8695wdt_probe(struct platform_device *pdev) { int res; @@ -231,11 +247,12 @@ static int __init ks8695wdt_probe(struct platform_device *pdev) if (res) return res; - printk("KS8695 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", nowayout" : ""); + pr_info("KS8695 Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); return 0; } -static int __exit ks8695wdt_remove(struct platform_device *pdev) +static int ks8695wdt_remove(struct platform_device *pdev) { int res; @@ -273,7 +290,7 @@ static int ks8695wdt_resume(struct platform_device *pdev) static struct platform_driver ks8695wdt_driver = { .probe = ks8695wdt_probe, - .remove = __exit_p(ks8695wdt_remove), + .remove = ks8695wdt_remove, .shutdown = ks8695wdt_shutdown, .suspend = ks8695wdt_suspend, .resume = ks8695wdt_resume, @@ -285,12 +302,13 @@ static struct platform_driver ks8695wdt_driver = { static int __init ks8695_wdt_init(void) { - /* Check that the heartbeat value is within range; if not reset to the default */ + /* Check that the heartbeat value is within range; + if not reset to the default */ if (ks8695_wdt_settimeout(wdt_time)) { ks8695_wdt_settimeout(WDT_DEFAULT_TIME); - pr_info("ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i, using %d\n", wdt_time, WDT_MAX_TIME); + pr_info("ks8695_wdt: wdt_time value must be 1 <= wdt_time <= %i" + ", using %d\n", wdt_time, WDT_MAX_TIME); } - return platform_driver_register(&ks8695wdt_driver); } @@ -305,4 +323,4 @@ module_exit(ks8695_wdt_exit); MODULE_AUTHOR("Andrew Victor"); MODULE_DESCRIPTION("Watchdog driver for KS8695"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:ks8695_wdt"); diff --git a/drivers/watchdog/lantiq_wdt.c b/drivers/watchdog/lantiq_wdt.c new file mode 100644 index 00000000000..3b3148c764a --- /dev/null +++ b/drivers/watchdog/lantiq_wdt.c @@ -0,0 +1,251 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Copyright (C) 2010 John Crispin <blogic@openwrt.org> + * Based on EP93xx wdt driver + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/of_platform.h> +#include <linux/uaccess.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <lantiq_soc.h> + +/* + * 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_PW1 0x00BE0000 +#define LTQ_WDT_PW2 0x00DC0000 + +#define LTQ_WDT_CR 0x0 /* watchdog control register */ +#define LTQ_WDT_SR 0x8 /* watchdog status register */ + +#define LTQ_WDT_SR_EN (0x1 << 31) /* enable bit */ +#define LTQ_WDT_SR_PWD (0x3 << 26) /* turn on power */ +#define LTQ_WDT_SR_CLKDIV (0x3 << 24) /* turn on clock and set */ + /* divider to 0x40000 */ +#define LTQ_WDT_DIVIDER 0x40000 +#define LTQ_MAX_TIMEOUT ((1 << 16) - 1) /* the reload field is 16 bit */ + +static bool nowayout = WATCHDOG_NOWAYOUT; + +static void __iomem *ltq_wdt_membase; +static unsigned long ltq_io_region_clk_rate; + +static unsigned long ltq_wdt_bootstatus; +static unsigned long ltq_wdt_in_use; +static int ltq_wdt_timeout = 30; +static int ltq_wdt_ok_to_close; + +static void +ltq_wdt_enable(void) +{ + unsigned long int timeout = ltq_wdt_timeout * + (ltq_io_region_clk_rate / LTQ_WDT_DIVIDER) + 0x1000; + if (timeout > LTQ_MAX_TIMEOUT) + timeout = LTQ_MAX_TIMEOUT; + + /* write the first password magic */ + ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); + /* write the second magic plus the configuration and new timeout */ + ltq_w32(LTQ_WDT_SR_EN | LTQ_WDT_SR_PWD | LTQ_WDT_SR_CLKDIV | + LTQ_WDT_PW2 | timeout, ltq_wdt_membase + LTQ_WDT_CR); +} + +static void +ltq_wdt_disable(void) +{ + /* write the first password magic */ + ltq_w32(LTQ_WDT_PW1, ltq_wdt_membase + LTQ_WDT_CR); + /* + * write the second password magic with no config + * this turns the watchdog off + */ + ltq_w32(LTQ_WDT_PW2, ltq_wdt_membase + LTQ_WDT_CR); +} + +static ssize_t +ltq_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + ltq_wdt_ok_to_close = 0; + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + ltq_wdt_ok_to_close = 1; + else + ltq_wdt_ok_to_close = 0; + } + } + ltq_wdt_enable(); + } + + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_CARDRESET, + .identity = "ltq_wdt", +}; + +static long +ltq_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = -ENOTTY; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info __user *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(ltq_wdt_bootstatus, (int __user *)arg); + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(ltq_wdt_timeout, (int __user *)arg); + if (!ret) + ltq_wdt_enable(); + /* intentional drop through */ + case WDIOC_GETTIMEOUT: + ret = put_user(ltq_wdt_timeout, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ltq_wdt_enable(); + ret = 0; + break; + } + return ret; +} + +static int +ltq_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, <q_wdt_in_use)) + return -EBUSY; + ltq_wdt_in_use = 1; + ltq_wdt_enable(); + + return nonseekable_open(inode, file); +} + +static int +ltq_wdt_release(struct inode *inode, struct file *file) +{ + if (ltq_wdt_ok_to_close) + ltq_wdt_disable(); + else + pr_err("watchdog closed without warning\n"); + ltq_wdt_ok_to_close = 0; + clear_bit(0, <q_wdt_in_use); + + return 0; +} + +static const struct file_operations ltq_wdt_fops = { + .owner = THIS_MODULE, + .write = ltq_wdt_write, + .unlocked_ioctl = ltq_wdt_ioctl, + .open = ltq_wdt_open, + .release = ltq_wdt_release, + .llseek = no_llseek, +}; + +static struct miscdevice ltq_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = <q_wdt_fops, +}; + +static int +ltq_wdt_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct clk *clk; + + if (!res) { + dev_err(&pdev->dev, "cannot obtain I/O memory region"); + return -ENOENT; + } + + ltq_wdt_membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ltq_wdt_membase)) + return PTR_ERR(ltq_wdt_membase); + + /* we do not need to enable the clock as it is always running */ + clk = clk_get_io(); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + return -ENOENT; + } + ltq_io_region_clk_rate = clk_get_rate(clk); + clk_put(clk); + + /* find out if the watchdog caused the last reboot */ + if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST) + ltq_wdt_bootstatus = WDIOF_CARDRESET; + + dev_info(&pdev->dev, "Init done\n"); + return misc_register(<q_wdt_miscdev); +} + +static int +ltq_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(<q_wdt_miscdev); + + return 0; +} + +static const struct of_device_id ltq_wdt_match[] = { + { .compatible = "lantiq,wdt" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ltq_wdt_match); + +static struct platform_driver ltq_wdt_driver = { + .probe = ltq_wdt_probe, + .remove = ltq_wdt_remove, + .driver = { + .name = "wdt", + .owner = THIS_MODULE, + .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 <blogic@openwrt.org>"); +MODULE_DESCRIPTION("Lantiq SoC Watchdog"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/m54xx_wdt.c b/drivers/watchdog/m54xx_wdt.c new file mode 100644 index 00000000000..da6fa2b6807 --- /dev/null +++ b/drivers/watchdog/m54xx_wdt.c @@ -0,0 +1,225 @@ +/* + * 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 <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 nonseekable_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(); + /* Fall through */ + + 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, + .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 index 6905135a776..9826b59ef73 100644 --- a/drivers/watchdog/machzwd.c +++ b/drivers/watchdog/machzwd.c @@ -21,13 +21,15 @@ * 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 unconditionaly will RESET + * 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> @@ -40,10 +42,9 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> /* ports */ #define ZF_IOBASE 0x218 @@ -54,7 +55,7 @@ /* indexes */ /* size */ #define ZFL_VERSION 0x02 /* 16 */ -#define CONTROL 0x10 /* 16 */ +#define CONTROL 0x10 /* 16 */ #define STATUS 0x12 /* 8 */ #define COUNTER_1 0x0C /* 16 */ #define COUNTER_2 0x0E /* 8 */ @@ -91,15 +92,16 @@ static unsigned short zf_readw(unsigned char port) MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 struct watchdog_info zf_info = { +static const struct watchdog_info zf_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "ZF-Logic watchdog", @@ -114,19 +116,19 @@ static struct watchdog_info zf_info = { * 3 = GEN_SCI * defaults to GEN_RESET (0) */ -static int action = 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"); +MODULE_PARM_DESC(action, "after watchdog resets, generate: " + "0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); static void zf_ping(unsigned long data); static int zf_action = GEN_RESET; static unsigned long zf_is_open; static char zf_expect_close; -static DEFINE_SPINLOCK(zf_lock); static DEFINE_SPINLOCK(zf_port_lock); static DEFINE_TIMER(zf_timer, zf_ping, 0, 0); -static unsigned long next_heartbeat = 0; +static unsigned long next_heartbeat; /* timeout for user land heart beat (10 seconds) */ @@ -139,9 +141,10 @@ static unsigned long next_heartbeat = 0; #define ZF_CTIMEOUT 0xffff #ifndef ZF_DEBUG -# define dprintk(format, args...) +#define dprintk(format, args...) #else -# define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __func__, __LINE__ , ## args) +#define dprintk(format, args...) \ + pr_debug(":%s:%d: " format, __func__, __LINE__ , ## args) #endif @@ -171,13 +174,13 @@ static inline void zf_set_control(unsigned short new) static inline void zf_set_timer(unsigned short new, unsigned char n) { - switch(n){ - case WD1: - zf_writew(COUNTER_1, new); - case WD2: - zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); - default: - return; + switch (n) { + case WD1: + zf_writew(COUNTER_1, new); + case WD2: + zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); + default: + return; } } @@ -200,7 +203,7 @@ static void zf_timer_off(void) zf_set_control(ctrl_reg); spin_unlock_irqrestore(&zf_port_lock, flags); - printk(KERN_INFO PFX ": Watchdog timer is now disabled\n"); + pr_info("Watchdog timer is now disabled\n"); } @@ -230,7 +233,7 @@ static void zf_timer_on(void) zf_set_control(ctrl_reg); spin_unlock_irqrestore(&zf_port_lock, flags); - printk(KERN_INFO PFX ": Watchdog timer is now enabled\n"); + pr_info("Watchdog timer is now enabled\n"); } @@ -241,10 +244,8 @@ static void zf_ping(unsigned long data) zf_writeb(COUNTER_2, 0xff); - if(time_before(jiffies, next_heartbeat)){ - + 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... @@ -261,24 +262,21 @@ static void zf_ping(unsigned long data) spin_unlock_irqrestore(&zf_port_lock, flags); mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); - }else{ - printk(KERN_CRIT PFX ": I will reset your machine\n"); - } + } 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){ - + 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... @@ -286,11 +284,11 @@ static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, zf_expect_close = 0; /* now scan */ - for (ofs = 0; ofs != count; ofs++){ + for (ofs = 0; ofs != count; ofs++) { char c; if (get_user(c, buf + ofs)) return -EFAULT; - if (c == 'V'){ + if (c == 'V') { zf_expect_close = 42; dprintk("zf_expect_close = 42\n"); } @@ -303,14 +301,11 @@ static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, */ next_heartbeat = jiffies + ZF_USER_TIMEO; dprintk("user ping at %ld\n", jiffies); - } - return count; } -static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; @@ -319,55 +314,38 @@ static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd, 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(0); break; - default: return -ENOTTY; } - return 0; } static int zf_open(struct inode *inode, struct file *file) { - spin_lock(&zf_lock); - if(test_and_set_bit(0, &zf_is_open)) { - spin_unlock(&zf_lock); + if (test_and_set_bit(0, &zf_is_open)) return -EBUSY; - } - if (nowayout) __module_get(THIS_MODULE); - - spin_unlock(&zf_lock); - zf_timer_on(); - return nonseekable_open(inode, file); } static int zf_close(struct inode *inode, struct file *file) { - if(zf_expect_close == 42){ + if (zf_expect_close == 42) zf_timer_off(); - } else { + else { del_timer(&zf_timer); - printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n"); + pr_err("device file closed unexpectedly. Will not stop the WDT!\n"); } - - spin_lock(&zf_lock); clear_bit(0, &zf_is_open); - spin_unlock(&zf_lock); - zf_expect_close = 0; - return 0; } @@ -378,23 +356,18 @@ static int zf_close(struct inode *inode, struct file *file) static int zf_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code == SYS_DOWN || code == SYS_HALT){ + 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, - .ioctl = zf_ioctl, - .open = zf_open, - .release = zf_close, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = zf_write, + .unlocked_ioctl = zf_ioctl, + .open = zf_open, + .release = zf_close, }; static struct miscdevice zf_miscdev = { @@ -402,7 +375,7 @@ static struct miscdevice zf_miscdev = { .name = "watchdog", .fops = &zf_fops, }; - + /* * The device needs to learn about soft shutdowns in order to @@ -414,48 +387,45 @@ static struct notifier_block zf_notifier = { static void __init zf_show_action(int act) { - char *str[] = { "RESET", "SMI", "NMI", "SCI" }; + static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" }; - printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]); + pr_info("Watchdog using action = %s\n", str[act]); } static int __init zf_init(void) { int ret; - printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n"); + pr_info("MachZ ZF-Logic Watchdog driver initializing\n"); ret = zf_get_ZFL_version(); - if ((!ret) || (ret == 0xffff)) { - printk(KERN_WARNING PFX ": no ZF-Logic found\n"); + if (!ret || ret == 0xffff) { + pr_warn("no ZF-Logic found\n"); return -ENODEV; } - if((action <= 3) && (action >= 0)){ - zf_action = zf_action>>action; - } else + 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")){ - printk(KERN_ERR "cannot reserve I/O ports at %d\n", - ZF_IOBASE); + 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){ - printk(KERN_ERR "can't register reboot notifier (err=%d)\n", - ret); + if (ret) { + pr_err("can't register reboot notifier (err=%d)\n", ret); goto no_reboot; } ret = misc_register(&zf_miscdev); - if (ret){ - printk(KERN_ERR "can't misc_register on minor=%d\n", - WATCHDOG_MINOR); + if (ret) { + pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); goto no_misc; } diff --git a/drivers/watchdog/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c new file mode 100644 index 00000000000..0e9cc6f5a91 --- /dev/null +++ b/drivers/watchdog/max63xx_wdt.c @@ -0,0 +1,258 @@ +/* + * 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/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> + +#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) + +static DEFINE_SPINLOCK(io_lock); + +static int nodelay; +static void __iomem *wdt_base; + +/* + * 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 { + u8 wdset; + u8 tdelay; + u8 twd; +}; + +static struct max63xx_timeout max6369_table[] = { + { 5, 1, 1 }, + { 6, 10, 10 }, + { 7, 60, 60 }, + { }, +}; + +static struct max63xx_timeout max6371_table[] = { + { 6, 60, 3 }, + { 7, 60, 60 }, + { }, +}; + +static struct max63xx_timeout max6373_table[] = { + { 2, 60, 1 }, + { 5, 0, 1 }, + { 1, 3, 3 }, + { 7, 60, 10 }, + { 6, 0, 10 }, + { }, +}; + +static struct max63xx_timeout *current_timeout; + +static struct max63xx_timeout * +max63xx_select_timeout(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) +{ + u8 val; + + spin_lock(&io_lock); + + val = __raw_readb(wdt_base); + + __raw_writeb(val | MAX6369_WDI, wdt_base); + __raw_writeb(val & ~MAX6369_WDI, wdt_base); + + spin_unlock(&io_lock); + return 0; +} + +static int max63xx_wdt_start(struct watchdog_device *wdd) +{ + struct max63xx_timeout *entry = watchdog_get_drvdata(wdd); + u8 val; + + spin_lock(&io_lock); + + val = __raw_readb(wdt_base); + val &= ~MAX6369_WDSET; + val |= entry->wdset; + __raw_writeb(val, wdt_base); + + spin_unlock(&io_lock); + + /* check for a edge triggered startup */ + if (entry->tdelay == 0) + max63xx_wdt_ping(wdd); + return 0; +} + +static int max63xx_wdt_stop(struct watchdog_device *wdd) +{ + u8 val; + + spin_lock(&io_lock); + + val = __raw_readb(wdt_base); + val &= ~MAX6369_WDSET; + val |= 3; + __raw_writeb(val, wdt_base); + + spin_unlock(&io_lock); + return 0; +} + +static const struct watchdog_info max63xx_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "max63xx Watchdog", +}; + +static const struct watchdog_ops max63xx_wdt_ops = { + .owner = THIS_MODULE, + .start = max63xx_wdt_start, + .stop = max63xx_wdt_stop, + .ping = max63xx_wdt_ping, +}; + +static struct watchdog_device max63xx_wdt_dev = { + .info = &max63xx_wdt_info, + .ops = &max63xx_wdt_ops, +}; + +static int max63xx_wdt_probe(struct platform_device *pdev) +{ + struct resource *wdt_mem; + struct max63xx_timeout *table; + + table = (struct max63xx_timeout *)pdev->id_entry->driver_data; + + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) + heartbeat = DEFAULT_HEARTBEAT; + + dev_info(&pdev->dev, "requesting %ds heartbeat\n", heartbeat); + current_timeout = max63xx_select_timeout(table, heartbeat); + + if (!current_timeout) { + dev_err(&pdev->dev, "unable to satisfy heartbeat request\n"); + return -EINVAL; + } + + dev_info(&pdev->dev, "using %ds heartbeat with %ds initial delay\n", + current_timeout->twd, current_timeout->tdelay); + + heartbeat = current_timeout->twd; + + wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt_base = devm_ioremap_resource(&pdev->dev, wdt_mem); + if (IS_ERR(wdt_base)) + return PTR_ERR(wdt_base); + + max63xx_wdt_dev.timeout = heartbeat; + watchdog_set_nowayout(&max63xx_wdt_dev, nowayout); + watchdog_set_drvdata(&max63xx_wdt_dev, current_timeout); + + return watchdog_register_device(&max63xx_wdt_dev); +} + +static int max63xx_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&max63xx_wdt_dev); + return 0; +} + +static 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 struct platform_driver max63xx_wdt_driver = { + .probe = max63xx_wdt_probe, + .remove = max63xx_wdt_remove, + .id_table = max63xx_id_table, + .driver = { + .name = "max63xx_wdt", + .owner = THIS_MODULE, + }, +}; + +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"); diff --git a/drivers/watchdog/mena21_wdt.c b/drivers/watchdog/mena21_wdt.c new file mode 100644 index 00000000000..96dbba98057 --- /dev/null +++ b/drivers/watchdog/mena21_wdt.c @@ -0,0 +1,270 @@ +/* + * Watchdog driver for the A21 VME CPU Boards + * + * Copyright (C) 2013 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation + */ +#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.h> +#include <linux/of_gpio.h> +#include <linux/delay.h> +#include <linux/bitops.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 mutex lock; + unsigned 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 |= gpio_get_value(drv->gpios[GPIO_WD_RST0]) ? (1 << 0) : 0; + reset |= gpio_get_value(drv->gpios[GPIO_WD_RST1]) ? (1 << 1) : 0; + reset |= gpio_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); + + mutex_lock(&drv->lock); + + gpio_set_value(drv->gpios[GPIO_WD_ENAB], 1); + + mutex_unlock(&drv->lock); + + return 0; +} + +static int a21_wdt_stop(struct watchdog_device *wdt) +{ + struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); + + mutex_lock(&drv->lock); + + gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0); + + mutex_unlock(&drv->lock); + + return 0; +} + +static int a21_wdt_ping(struct watchdog_device *wdt) +{ + struct a21_wdt_drv *drv = watchdog_get_drvdata(wdt); + + mutex_lock(&drv->lock); + + gpio_set_value(drv->gpios[GPIO_WD_TRIG], 0); + ndelay(10); + gpio_set_value(drv->gpios[GPIO_WD_TRIG], 1); + + mutex_unlock(&drv->lock); + + 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->dev, "Only 1 and 30 allowed as timeout\n"); + return -EINVAL; + } + + if (timeout == 30 && wdt->timeout == 1) { + dev_err(wdt->dev, + "Transition from fast to slow mode not allowed\n"); + return -EINVAL; + } + + mutex_lock(&drv->lock); + + if (timeout == 1) + gpio_set_value(drv->gpios[GPIO_WD_FAST], 1); + else + gpio_set_value(drv->gpios[GPIO_WD_FAST], 0); + + wdt->timeout = timeout; + + mutex_unlock(&drv->lock); + + 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_node *node; + struct a21_wdt_drv *drv; + unsigned int reset = 0; + int num_gpios; + int ret; + int i; + + drv = devm_kzalloc(&pdev->dev, sizeof(struct a21_wdt_drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + /* Fill GPIO pin array */ + node = pdev->dev.of_node; + + num_gpios = of_gpio_count(node); + if (num_gpios != NUM_GPIOS) { + dev_err(&pdev->dev, "gpios DT property wrong, got %d want %d", + num_gpios, NUM_GPIOS); + return -ENODEV; + } + + for (i = 0; i < num_gpios; i++) { + int val; + + val = of_get_gpio(node, i); + if (val < 0) + return val; + + drv->gpios[i] = val; + } + + /* Request the used GPIOs */ + for (i = 0; i < num_gpios; i++) { + ret = devm_gpio_request(&pdev->dev, drv->gpios[i], + "MEN A21 Watchdog"); + if (ret) + return ret; + + if (i < GPIO_WD_RST0) + ret = gpio_direction_output(drv->gpios[i], + gpio_get_value(drv->gpios[i])); + else /* GPIO_WD_RST[0..2] are inputs */ + ret = gpio_direction_input(drv->gpios[i]); + if (ret) + return ret; + } + + mutex_init(&drv->lock); + watchdog_init_timeout(&a21_wdt, 30, &pdev->dev); + watchdog_set_nowayout(&a21_wdt, nowayout); + watchdog_set_drvdata(&a21_wdt, drv); + + 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; + + ret = watchdog_register_device(&a21_wdt); + if (ret) { + dev_err(&pdev->dev, "Cannot register watchdog device\n"); + goto err_register_wd; + } + + dev_set_drvdata(&pdev->dev, drv); + + dev_info(&pdev->dev, "MEN A21 watchdog timer driver enabled\n"); + + return 0; + +err_register_wd: + mutex_destroy(&drv->lock); + + return ret; +} + +static int a21_wdt_remove(struct platform_device *pdev) +{ + struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev); + + dev_warn(&pdev->dev, + "Unregistering A21 watchdog driver, board may reboot\n"); + + watchdog_unregister_device(&drv->wdt); + + mutex_destroy(&drv->lock); + + return 0; +} + +static void a21_wdt_shutdown(struct platform_device *pdev) +{ + struct a21_wdt_drv *drv = dev_get_drvdata(&pdev->dev); + + gpio_set_value(drv->gpios[GPIO_WD_ENAB], 0); +} + +static const struct of_device_id a21_wdt_ids[] = { + { .compatible = "men,a021-wdt" }, + { }, +}; + +static struct platform_driver a21_wdt_driver = { + .probe = a21_wdt_probe, + .remove = a21_wdt_remove, + .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/mixcomwd.c b/drivers/watchdog/mixcomwd.c index 1adf1d56027..be86ea359ee 100644 --- a/drivers/watchdog/mixcomwd.c +++ b/drivers/watchdog/mixcomwd.c @@ -29,7 +29,8 @@ * - 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 + * - added nowayout module option to override + * CONFIG_WATCHDOG_NOWAYOUT * * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com> * - make mixcomwd_opened unsigned, @@ -38,9 +39,10 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #define VERSION "0.6" #define WATCHDOG_NAME "mixcomwd" -#define PFX WATCHDOG_NAME ": " #include <linux/module.h> #include <linux/moduleparam.h> @@ -53,8 +55,8 @@ #include <linux/init.h> #include <linux/jiffies.h> #include <linux/timer.h> -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/uaccess.h> +#include <linux/io.h> /* * We have two types of cards that can be probed: @@ -71,7 +73,7 @@ static struct { int ioport; int id; -} mixcomwd_io_info[] __devinitdata = { +} mixcomwd_io_info[] = { /* The Mixcom cards */ {0x0d90, MIXCOM_ID}, {0x0e90, MIXCOM_ID}, @@ -106,20 +108,21 @@ static int mixcomwd_timer_alive; static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun, 0, 0); static char expect_close; -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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); + outb_p(55, watchdog_port); return; } static void mixcomwd_timerfun(unsigned long d) { mixcomwd_ping(); - mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); } @@ -129,22 +132,22 @@ static void mixcomwd_timerfun(unsigned long d) static int mixcomwd_open(struct inode *inode, struct file *file) { - if(test_and_set_bit(0,&mixcomwd_opened)) { + if (test_and_set_bit(0, &mixcomwd_opened)) return -EBUSY; - } + mixcomwd_ping(); - if (nowayout) { + 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) { + else { + if (mixcomwd_timer_alive) { del_timer(&mixcomwd_timer); - mixcomwd_timer_alive=0; + mixcomwd_timer_alive = 0; } } return nonseekable_open(inode, file); @@ -153,26 +156,25 @@ static int mixcomwd_open(struct inode *inode, struct file *file) static int mixcomwd_release(struct inode *inode, struct file *file) { if (expect_close == 42) { - if(mixcomwd_timer_alive) { - printk(KERN_ERR PFX "release called while internal timer alive"); + if (mixcomwd_timer_alive) { + pr_err("release called while internal timer alive\n"); return -EBUSY; } - mixcomwd_timer_alive=1; + mixcomwd_timer_alive = 1; mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); - } else { - printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); - } + } else + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); - clear_bit(0,&mixcomwd_opened); - expect_close=0; + 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) +static ssize_t mixcomwd_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) { - if(len) - { + if (len) { if (!nowayout) { size_t i; @@ -192,44 +194,35 @@ static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t return len; } -static int mixcomwd_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "MixCOM watchdog", }; - switch(cmd) - { - case WDIOC_GETSTATUS: - status=mixcomwd_opened; - if (!nowayout) { - status|=mixcomwd_timer_alive; - } - if (copy_to_user(p, &status, sizeof(int))) { - return -EFAULT; - } - break; - case WDIOC_GETBOOTSTATUS: - if (copy_to_user(p, &status, sizeof(int))) { - return -EFAULT; - } - break; - case WDIOC_GETSUPPORT: - if (copy_to_user(argp, &ident, sizeof(ident))) { - return -EFAULT; - } - break; - case WDIOC_KEEPALIVE: - mixcomwd_ping(); - break; - default: - return -ENOTTY; + 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; } @@ -238,7 +231,7 @@ static const struct file_operations mixcomwd_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = mixcomwd_write, - .ioctl = mixcomwd_ioctl, + .unlocked_ioctl = mixcomwd_ioctl, .open = mixcomwd_open, .release = mixcomwd_release, }; @@ -253,15 +246,14 @@ static int __init checkcard(int port, int card_id) { int id; - if (!request_region(port, 1, "MixCOM watchdog")) { + if (!request_region(port, 1, "MixCOM watchdog")) return 0; - } - id=inb_p(port); - if (card_id==MIXCOM_ID) + id = inb_p(port); + if (card_id == MIXCOM_ID) id &= 0x3f; - if (id!=card_id) { + if (id != card_id) { release_region(port, 1); return 0; } @@ -270,9 +262,7 @@ static int __init checkcard(int port, int card_id) static int __init mixcomwd_init(void) { - int i; - int ret; - int found=0; + int i, ret, found = 0; for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) { if (checkcard(mixcomwd_io_info[i].ioport, @@ -283,19 +273,18 @@ static int __init mixcomwd_init(void) } if (!found) { - printk(KERN_ERR PFX "No card detected, or port not available.\n"); + pr_err("No card detected, or port not available\n"); return -ENODEV; } ret = misc_register(&mixcomwd_miscdev); - if (ret) - { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto error_misc_register_watchdog; } - printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n", + pr_info("MixCOM watchdog driver v%s, watchdog port at 0x%3x\n", VERSION, watchdog_port); return 0; @@ -309,15 +298,14 @@ error_misc_register_watchdog: static void __exit mixcomwd_exit(void) { if (!nowayout) { - if(mixcomwd_timer_alive) { - printk(KERN_WARNING PFX "I quit now, hardware will" - " probably reboot!\n"); + if (mixcomwd_timer_alive) { + pr_warn("I quit now, hardware will probably reboot!\n"); del_timer_sync(&mixcomwd_timer); - mixcomwd_timer_alive=0; + mixcomwd_timer_alive = 0; } } misc_deregister(&mixcomwd_miscdev); - release_region(watchdog_port,1); + release_region(watchdog_port, 1); } module_init(mixcomwd_init); @@ -327,4 +315,3 @@ MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>"); MODULE_DESCRIPTION("MixCom Watchdog driver"); MODULE_VERSION(VERSION); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/moxart_wdt.c b/drivers/watchdog/moxart_wdt.c new file mode 100644 index 00000000000..4aa3a8a876f --- /dev/null +++ b/drivers/watchdog/moxart_wdt.c @@ -0,0 +1,180 @@ +/* + * 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/err.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> +#include <linux/moduleparam.h> + +#include <asm/system_misc.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 struct moxart_wdt_dev *moxart_restart_ctx; + +static int heartbeat; + +static void moxart_wdt_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + writel(1, moxart_restart_ctx->base + REG_COUNT); + writel(0x5ab9, moxart_restart_ctx->base + REG_MODE); + writel(0x03, moxart_restart_ctx->base + REG_ENABLE); +} + +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, +}; + +static int moxart_wdt_probe(struct platform_device *pdev) +{ + struct moxart_wdt_dev *moxart_wdt; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct resource *res; + 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); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + moxart_wdt->base = devm_ioremap_resource(dev, res); + if (IS_ERR(moxart_wdt->base)) + return PTR_ERR(moxart_wdt->base); + + clk = of_clk_get(node, 0); + 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_drvdata(&moxart_wdt->dev, moxart_wdt); + + err = watchdog_register_device(&moxart_wdt->dev); + if (err) + return err; + + moxart_restart_ctx = moxart_wdt; + arm_pm_restart = moxart_wdt_restart; + + dev_dbg(dev, "Watchdog enabled (heartbeat=%d sec, nowayout=%d)\n", + moxart_wdt->dev.timeout, nowayout); + + return 0; +} + +static int moxart_wdt_remove(struct platform_device *pdev) +{ + struct moxart_wdt_dev *moxart_wdt = platform_get_drvdata(pdev); + + arm_pm_restart = NULL; + moxart_wdt_stop(&moxart_wdt->dev); + watchdog_unregister_device(&moxart_wdt->dev); + + return 0; +} + +static const struct of_device_id moxart_watchdog_match[] = { + { .compatible = "moxa,moxart-watchdog" }, + { }, +}; + +static struct platform_driver moxart_wdt_driver = { + .probe = moxart_wdt_probe, + .remove = moxart_wdt_remove, + .driver = { + .name = "moxart-watchdog", + .owner = THIS_MODULE, + .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/mpc5200_wdt.c b/drivers/watchdog/mpc5200_wdt.c deleted file mode 100644 index 80a91d4cea1..00000000000 --- a/drivers/watchdog/mpc5200_wdt.c +++ /dev/null @@ -1,289 +0,0 @@ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <linux/io.h> -#include <linux/spinlock.h> -#include <asm/of_platform.h> -#include <asm/uaccess.h> -#include <asm/mpc52xx.h> - - -#define GPT_MODE_WDT (1<<15) -#define GPT_MODE_CE (1<<12) -#define GPT_MODE_MS_TIMER (0x4) - - -struct mpc5200_wdt { - unsigned count; /* timer ticks before watchdog kicks in */ - long ipb_freq; - struct miscdevice miscdev; - struct resource mem; - struct mpc52xx_gpt __iomem *regs; - spinlock_t io_lock; -}; - -/* is_active stores wether or not the /dev/watchdog device is opened */ -static unsigned long is_active; - -/* misc devices don't provide a way, to get back to 'dev' or 'miscdev' from - * file operations, which sucks. But there can be max 1 watchdog anyway, so... - */ -static struct mpc5200_wdt *wdt_global; - - -/* helper to calculate timeout in timer counts */ -static void mpc5200_wdt_set_timeout(struct mpc5200_wdt *wdt, int timeout) -{ - /* use biggest prescaler of 64k */ - wdt->count = (wdt->ipb_freq + 0xffff) / 0x10000 * timeout; - - if (wdt->count > 0xffff) - wdt->count = 0xffff; -} -/* return timeout in seconds (calculated from timer count) */ -static int mpc5200_wdt_get_timeout(struct mpc5200_wdt *wdt) -{ - return wdt->count * 0x10000 / wdt->ipb_freq; -} - - -/* watchdog operations */ -static int mpc5200_wdt_start(struct mpc5200_wdt *wdt) -{ - spin_lock(&wdt->io_lock); - /* disable */ - out_be32(&wdt->regs->mode, 0); - /* set timeout, with maximum prescaler */ - out_be32(&wdt->regs->count, 0x0 | wdt->count); - /* enable watchdog */ - out_be32(&wdt->regs->mode, GPT_MODE_CE | GPT_MODE_WDT | GPT_MODE_MS_TIMER); - spin_unlock(&wdt->io_lock); - - return 0; -} -static int mpc5200_wdt_ping(struct mpc5200_wdt *wdt) -{ - spin_lock(&wdt->io_lock); - /* writing A5 to OCPW resets the watchdog */ - out_be32(&wdt->regs->mode, 0xA5000000 | (0xffffff & in_be32(&wdt->regs->mode))); - spin_unlock(&wdt->io_lock); - return 0; -} -static int mpc5200_wdt_stop(struct mpc5200_wdt *wdt) -{ - spin_lock(&wdt->io_lock); - /* disable */ - out_be32(&wdt->regs->mode, 0); - spin_unlock(&wdt->io_lock); - return 0; -} - - -/* file operations */ -static ssize_t mpc5200_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - struct mpc5200_wdt *wdt = file->private_data; - mpc5200_wdt_ping(wdt); - return 0; -} -static struct watchdog_info mpc5200_wdt_info = { - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, - .identity = "mpc5200 watchdog on GPT0", -}; -static int mpc5200_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - struct mpc5200_wdt *wdt = file->private_data; - int __user *data = (int __user *)arg; - int timeout; - int ret = 0; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user(data, &mpc5200_wdt_info, - sizeof(mpc5200_wdt_info)); - if (ret) - ret = -EFAULT; - break; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - ret = put_user(0, data); - break; - - case WDIOC_KEEPALIVE: - mpc5200_wdt_ping(wdt); - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(timeout, data); - if (ret) - break; - mpc5200_wdt_set_timeout(wdt, timeout); - mpc5200_wdt_start(wdt); - /* fall through and return the timeout */ - - case WDIOC_GETTIMEOUT: - timeout = mpc5200_wdt_get_timeout(wdt); - ret = put_user(timeout, data); - break; - - default: - ret = -ENOTTY; - } - return ret; -} -static int mpc5200_wdt_open(struct inode *inode, struct file *file) -{ - /* /dev/watchdog can only be opened once */ - if (test_and_set_bit(0, &is_active)) - return -EBUSY; - - /* Set and activate the watchdog */ - mpc5200_wdt_set_timeout(wdt_global, 30); - mpc5200_wdt_start(wdt_global); - file->private_data = wdt_global; - return nonseekable_open(inode, file); -} -static int mpc5200_wdt_release(struct inode *inode, struct file *file) -{ -#if WATCHDOG_NOWAYOUT == 0 - struct mpc5200_wdt *wdt = file->private_data; - mpc5200_wdt_stop(wdt); - wdt->count = 0; /* == disabled */ -#endif - clear_bit(0, &is_active); - return 0; -} - -static const struct file_operations mpc5200_wdt_fops = { - .owner = THIS_MODULE, - .write = mpc5200_wdt_write, - .ioctl = mpc5200_wdt_ioctl, - .open = mpc5200_wdt_open, - .release = mpc5200_wdt_release, -}; - -/* module operations */ -static int mpc5200_wdt_probe(struct of_device *op, const struct of_device_id *match) -{ - struct mpc5200_wdt *wdt; - int err; - const void *has_wdt; - int size; - - has_wdt = of_get_property(op->node, "has-wdt", NULL); - if (!has_wdt) - has_wdt = of_get_property(op->node, "fsl,has-wdt", NULL); - if (!has_wdt) - return -ENODEV; - - wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); - if (!wdt) - return -ENOMEM; - - wdt->ipb_freq = mpc52xx_find_ipb_freq(op->node); - - err = of_address_to_resource(op->node, 0, &wdt->mem); - if (err) - goto out_free; - size = wdt->mem.end - wdt->mem.start + 1; - if (!request_mem_region(wdt->mem.start, size, "mpc5200_wdt")) { - err = -ENODEV; - goto out_free; - } - wdt->regs = ioremap(wdt->mem.start, size); - if (!wdt->regs) { - err = -ENODEV; - goto out_release; - } - - dev_set_drvdata(&op->dev, wdt); - spin_lock_init(&wdt->io_lock); - - wdt->miscdev = (struct miscdevice) { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &mpc5200_wdt_fops, - .parent = &op->dev, - }; - wdt_global = wdt; - err = misc_register(&wdt->miscdev); - if (!err) - return 0; - - iounmap(wdt->regs); - out_release: - release_mem_region(wdt->mem.start, size); - out_free: - kfree(wdt); - return err; -} - -static int mpc5200_wdt_remove(struct of_device *op) -{ - struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); - - mpc5200_wdt_stop(wdt); - misc_deregister(&wdt->miscdev); - iounmap(wdt->regs); - release_mem_region(wdt->mem.start, wdt->mem.end - wdt->mem.start + 1); - kfree(wdt); - - return 0; -} -static int mpc5200_wdt_suspend(struct of_device *op, pm_message_t state) -{ - struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); - mpc5200_wdt_stop(wdt); - return 0; -} -static int mpc5200_wdt_resume(struct of_device *op) -{ - struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); - if (wdt->count) - mpc5200_wdt_start(wdt); - return 0; -} -static int mpc5200_wdt_shutdown(struct of_device *op) -{ - struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev); - mpc5200_wdt_stop(wdt); - return 0; -} - -static struct of_device_id mpc5200_wdt_match[] = { - { .compatible = "mpc5200-gpt", }, - { .compatible = "fsl,mpc5200-gpt", }, - {}, -}; -static struct of_platform_driver mpc5200_wdt_driver = { - .owner = THIS_MODULE, - .name = "mpc5200-gpt-wdt", - .match_table = mpc5200_wdt_match, - .probe = mpc5200_wdt_probe, - .remove = mpc5200_wdt_remove, - .suspend = mpc5200_wdt_suspend, - .resume = mpc5200_wdt_resume, - .shutdown = mpc5200_wdt_shutdown, -}; - - -static int __init mpc5200_wdt_init(void) -{ - return of_register_platform_driver(&mpc5200_wdt_driver); -} - -static void __exit mpc5200_wdt_exit(void) -{ - of_unregister_platform_driver(&mpc5200_wdt_driver); -} - -module_init(mpc5200_wdt_init); -module_exit(mpc5200_wdt_exit); - -MODULE_AUTHOR("Domen Puncer <domen.puncer@telargo.com>"); -MODULE_LICENSE("Dual BSD/GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mpc83xx_wdt.c b/drivers/watchdog/mpc83xx_wdt.c deleted file mode 100644 index 6369f569517..00000000000 --- a/drivers/watchdog/mpc83xx_wdt.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * mpc83xx_wdt.c - MPC83xx 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 - * - * Note: it appears that you can only actually ENABLE or DISABLE the thing - * once after POR. Once enabled, you cannot disable, and vice versa. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/fs.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/miscdevice.h> -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/watchdog.h> -#include <asm/io.h> -#include <asm/uaccess.h> - -struct mpc83xx_wdt { - __be32 res0; - __be32 swcrr; /* System watchdog control register */ -#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ -#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]; -}; - -static struct mpc83xx_wdt __iomem *wd_base; - -static u16 timeout = 0xffff; -module_param(timeout, ushort, 0); -MODULE_PARM_DESC(timeout, "Watchdog timeout in ticks. (0<timeout<65536, default=65535"); - -static int reset = 1; -module_param(reset, bool, 0); -MODULE_PARM_DESC(reset, "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset"); - -/* - * We always prescale, but if someone really doesn't want to they can set this - * to 0 - */ -static int prescale = 1; -static unsigned int timeout_sec; - -static unsigned long wdt_is_open; -static DEFINE_SPINLOCK(wdt_spinlock); - -static void mpc83xx_wdt_keepalive(void) -{ - /* Ping the WDT */ - spin_lock(&wdt_spinlock); - out_be16(&wd_base->swsrr, 0x556c); - out_be16(&wd_base->swsrr, 0xaa39); - spin_unlock(&wdt_spinlock); -} - -static ssize_t mpc83xx_wdt_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - if (count) - mpc83xx_wdt_keepalive(); - return count; -} - -static int mpc83xx_wdt_open(struct inode *inode, struct file *file) -{ - u32 tmp = SWCRR_SWEN; - if (test_and_set_bit(0, &wdt_is_open)) - return -EBUSY; - - /* Once we start the watchdog we can't stop it */ - __module_get(THIS_MODULE); - - /* Good, fire up the show */ - if (prescale) - tmp |= SWCRR_SWPR; - if (reset) - tmp |= SWCRR_SWRI; - - tmp |= timeout << 16; - - out_be32(&wd_base->swcrr, tmp); - - return nonseekable_open(inode, file); -} - -static int mpc83xx_wdt_release(struct inode *inode, struct file *file) -{ - printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n"); - mpc83xx_wdt_keepalive(); - clear_bit(0, &wdt_is_open); - return 0; -} - -static int mpc83xx_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING, - .firmware_version = 1, - .identity = "MPC83xx", - }; - - 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_KEEPALIVE: - mpc83xx_wdt_keepalive(); - return 0; - case WDIOC_GETTIMEOUT: - return put_user(timeout_sec, p); - default: - return -ENOTTY; - } -} - -static const struct file_operations mpc83xx_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = mpc83xx_wdt_write, - .ioctl = mpc83xx_wdt_ioctl, - .open = mpc83xx_wdt_open, - .release = mpc83xx_wdt_release, -}; - -static struct miscdevice mpc83xx_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &mpc83xx_wdt_fops, -}; - -static int __devinit mpc83xx_wdt_probe(struct platform_device *dev) -{ - struct resource *r; - int ret; - unsigned int *freq = dev->dev.platform_data; - - /* get a pointer to the register memory */ - r = platform_get_resource(dev, IORESOURCE_MEM, 0); - - if (!r) { - ret = -ENODEV; - goto err_out; - } - - wd_base = ioremap(r->start, sizeof (struct mpc83xx_wdt)); - - if (wd_base == NULL) { - ret = -ENOMEM; - goto err_out; - } - - ret = misc_register(&mpc83xx_wdt_miscdev); - if (ret) { - printk(KERN_ERR "cannot register miscdev on minor=%d " - "(err=%d)\n", - WATCHDOG_MINOR, ret); - goto err_unmap; - } - - /* Calculate the timeout in seconds */ - if (prescale) - timeout_sec = (timeout * 0x10000) / (*freq); - else - timeout_sec = timeout / (*freq); - - printk(KERN_INFO "WDT driver for MPC83xx initialized. " - "mode:%s timeout=%d (%d seconds)\n", - reset ? "reset":"interrupt", timeout, timeout_sec); - return 0; - -err_unmap: - iounmap(wd_base); -err_out: - return ret; -} - -static int __devexit mpc83xx_wdt_remove(struct platform_device *dev) -{ - misc_deregister(&mpc83xx_wdt_miscdev); - iounmap(wd_base); - - return 0; -} - -static struct platform_driver mpc83xx_wdt_driver = { - .probe = mpc83xx_wdt_probe, - .remove = __devexit_p(mpc83xx_wdt_remove), - .driver = { - .name = "mpc83xx_wdt", - }, -}; - -static int __init mpc83xx_wdt_init(void) -{ - return platform_driver_register(&mpc83xx_wdt_driver); -} - -static void __exit mpc83xx_wdt_exit(void) -{ - platform_driver_unregister(&mpc83xx_wdt_driver); -} - -module_init(mpc83xx_wdt_init); -module_exit(mpc83xx_wdt_exit); - -MODULE_AUTHOR("Dave Updegraff, Kumar Gala"); -MODULE_DESCRIPTION("Driver for watchdog timer in MPC83xx uProcessor"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mpc8xx_wdt.c b/drivers/watchdog/mpc8xx_wdt.c deleted file mode 100644 index 85b5734403a..00000000000 --- a/drivers/watchdog/mpc8xx_wdt.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * mpc8xx_wdt.c - MPC8xx watchdog userspace interface - * - * Author: Florian Schirmer <jolt@tuxbox.org> - * - * 2002 (c) Florian Schirmer <jolt@tuxbox.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. - */ - -#include <linux/fs.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/miscdevice.h> -#include <linux/module.h> -#include <linux/watchdog.h> -#include <asm/8xx_immap.h> -#include <asm/uaccess.h> -#include <asm/io.h> -#include <syslib/m8xx_wdt.h> - -static unsigned long wdt_opened; -static int wdt_status; - -static void mpc8xx_wdt_handler_disable(void) -{ - volatile uint __iomem *piscr; - piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr; - - if (!m8xx_has_internal_rtc) - m8xx_wdt_stop_timer(); - else - out_be32(piscr, in_be32(piscr) & ~(PISCR_PIE | PISCR_PTE)); - - printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n"); -} - -static void mpc8xx_wdt_handler_enable(void) -{ - volatile uint __iomem *piscr; - piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr; - - if (!m8xx_has_internal_rtc) - m8xx_wdt_install_timer(); - else - out_be32(piscr, in_be32(piscr) | PISCR_PIE | PISCR_PTE); - - printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n"); -} - -static int mpc8xx_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &wdt_opened)) - return -EBUSY; - - m8xx_wdt_reset(); - mpc8xx_wdt_handler_disable(); - - return nonseekable_open(inode, file); -} - -static int mpc8xx_wdt_release(struct inode *inode, struct file *file) -{ - m8xx_wdt_reset(); - -#if !defined(CONFIG_WATCHDOG_NOWAYOUT) - mpc8xx_wdt_handler_enable(); -#endif - - clear_bit(0, &wdt_opened); - - return 0; -} - -static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len, - loff_t * ppos) -{ - if (len) - m8xx_wdt_reset(); - - return len; -} - -static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - int timeout; - static struct watchdog_info info = { - .options = WDIOF_KEEPALIVEPING, - .firmware_version = 0, - .identity = "MPC8xx watchdog", - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - if (copy_to_user((void *)arg, &info, sizeof(info))) - return -EFAULT; - break; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - if (put_user(wdt_status, (int *)arg)) - return -EFAULT; - wdt_status &= ~WDIOF_KEEPALIVEPING; - break; - - case WDIOC_GETTEMP: - return -EOPNOTSUPP; - - case WDIOC_SETOPTIONS: - return -EOPNOTSUPP; - - case WDIOC_KEEPALIVE: - m8xx_wdt_reset(); - wdt_status |= WDIOF_KEEPALIVEPING; - break; - - case WDIOC_SETTIMEOUT: - return -EOPNOTSUPP; - - case WDIOC_GETTIMEOUT: - timeout = m8xx_wdt_get_timeout(); - if (put_user(timeout, (int *)arg)) - return -EFAULT; - break; - - default: - return -ENOTTY; - } - - return 0; -} - -static const struct file_operations mpc8xx_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = mpc8xx_wdt_write, - .ioctl = mpc8xx_wdt_ioctl, - .open = mpc8xx_wdt_open, - .release = mpc8xx_wdt_release, -}; - -static struct miscdevice mpc8xx_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &mpc8xx_wdt_fops, -}; - -static int __init mpc8xx_wdt_init(void) -{ - return misc_register(&mpc8xx_wdt_miscdev); -} - -static void __exit mpc8xx_wdt_exit(void) -{ - misc_deregister(&mpc8xx_wdt_miscdev); - - m8xx_wdt_reset(); - mpc8xx_wdt_handler_enable(); -} - -module_init(mpc8xx_wdt_init); -module_exit(mpc8xx_wdt_exit); - -MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>"); -MODULE_DESCRIPTION("MPC8xx watchdog driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c new file mode 100644 index 00000000000..7831955cd9e --- /dev/null +++ b/drivers/watchdog/mpc8xxx_wdt.c @@ -0,0 +1,297 @@ +/* + * 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. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/miscdevice.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> + +struct mpc8xxx_wdt { + __be32 res0; + __be32 swcrr; /* System watchdog control register */ +#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */ +#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; +}; + +static struct mpc8xxx_wdt __iomem *wd_base; +static int mpc8xxx_wdt_init_late(void); + +static u16 timeout = 0xffff; +module_param(timeout, ushort, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in ticks. (0<timeout<65536, default=65535)"); + +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) ")"); + +/* + * We always prescale, but if someone really doesn't want to they can set this + * to 0 + */ +static int prescale = 1; + +static DEFINE_SPINLOCK(wdt_spinlock); + +static void mpc8xxx_wdt_keepalive(void) +{ + /* Ping the WDT */ + spin_lock(&wdt_spinlock); + out_be16(&wd_base->swsrr, 0x556c); + out_be16(&wd_base->swsrr, 0xaa39); + spin_unlock(&wdt_spinlock); +} + +static struct watchdog_device mpc8xxx_wdt_dev; +static void mpc8xxx_wdt_timer_ping(unsigned long arg); +static DEFINE_TIMER(wdt_timer, mpc8xxx_wdt_timer_ping, 0, + (unsigned long)&mpc8xxx_wdt_dev); + +static void mpc8xxx_wdt_timer_ping(unsigned long arg) +{ + struct watchdog_device *w = (struct watchdog_device *)arg; + + mpc8xxx_wdt_keepalive(); + /* We're pinging it twice faster than needed, just to be sure. */ + mod_timer(&wdt_timer, jiffies + HZ * w->timeout / 2); +} + +static int mpc8xxx_wdt_start(struct watchdog_device *w) +{ + u32 tmp = SWCRR_SWEN; + + /* Good, fire up the show */ + if (prescale) + tmp |= SWCRR_SWPR; + if (reset) + tmp |= SWCRR_SWRI; + + tmp |= timeout << 16; + + out_be32(&wd_base->swcrr, tmp); + + del_timer_sync(&wdt_timer); + + return 0; +} + +static int mpc8xxx_wdt_ping(struct watchdog_device *w) +{ + mpc8xxx_wdt_keepalive(); + return 0; +} + +static int mpc8xxx_wdt_stop(struct watchdog_device *w) +{ + mod_timer(&wdt_timer, jiffies); + return 0; +} + +static struct watchdog_info mpc8xxx_wdt_info = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "MPC8xxx", +}; + +static struct watchdog_ops mpc8xxx_wdt_ops = { + .owner = THIS_MODULE, + .start = mpc8xxx_wdt_start, + .ping = mpc8xxx_wdt_ping, + .stop = mpc8xxx_wdt_stop, +}; + +static struct watchdog_device mpc8xxx_wdt_dev = { + .info = &mpc8xxx_wdt_info, + .ops = &mpc8xxx_wdt_ops, +}; + +static const struct of_device_id mpc8xxx_wdt_match[]; +static int mpc8xxx_wdt_probe(struct platform_device *ofdev) +{ + int ret; + const struct of_device_id *match; + struct device_node *np = ofdev->dev.of_node; + const struct mpc8xxx_wdt_type *wdt_type; + u32 freq = fsl_get_sys_freq(); + bool enabled; + unsigned int timeout_sec; + + match = of_match_device(mpc8xxx_wdt_match, &ofdev->dev); + if (!match) + return -EINVAL; + wdt_type = match->data; + + if (!freq || freq == -1) + return -EINVAL; + + wd_base = of_iomap(np, 0); + if (!wd_base) + return -ENOMEM; + + enabled = in_be32(&wd_base->swcrr) & SWCRR_SWEN; + if (!enabled && wdt_type->hw_enabled) { + pr_info("could not be enabled in software\n"); + ret = -ENOSYS; + goto err_unmap; + } + + /* Calculate the timeout in seconds */ + if (prescale) + timeout_sec = (timeout * wdt_type->prescaler) / freq; + else + timeout_sec = timeout / freq; + + mpc8xxx_wdt_dev.timeout = timeout_sec; +#ifdef MODULE + ret = mpc8xxx_wdt_init_late(); + if (ret) + goto err_unmap; +#endif + + pr_info("WDT driver for MPC8xxx initialized. mode:%s timeout=%d (%d seconds)\n", + reset ? "reset" : "interrupt", timeout, timeout_sec); + + /* + * 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) + mod_timer(&wdt_timer, jiffies); + return 0; +err_unmap: + iounmap(wd_base); + wd_base = NULL; + return ret; +} + +static int mpc8xxx_wdt_remove(struct platform_device *ofdev) +{ + pr_crit("Watchdog removed, expect the %s soon!\n", + reset ? "reset" : "machine check exception"); + del_timer_sync(&wdt_timer); + watchdog_unregister_device(&mpc8xxx_wdt_dev); + iounmap(wd_base); + + return 0; +} + +static const struct of_device_id mpc8xxx_wdt_match[] = { + { + .compatible = "mpc83xx_wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x10000, + }, + }, + { + .compatible = "fsl,mpc8610-wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x10000, + .hw_enabled = true, + }, + }, + { + .compatible = "fsl,mpc823-wdt", + .data = &(struct mpc8xxx_wdt_type) { + .prescaler = 0x800, + .hw_enabled = true, + }, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc8xxx_wdt_match); + +static struct platform_driver mpc8xxx_wdt_driver = { + .probe = mpc8xxx_wdt_probe, + .remove = mpc8xxx_wdt_remove, + .driver = { + .name = "mpc8xxx_wdt", + .owner = THIS_MODULE, + .of_match_table = mpc8xxx_wdt_match, + }, +}; + +/* + * We do wdt initialization in two steps: arch_initcall probes the wdt + * very early to start pinging the watchdog (misc devices are not yet + * available), and later module_init() just registers the misc device. + */ +static int mpc8xxx_wdt_init_late(void) +{ + int ret; + + if (!wd_base) + return -ENODEV; + + watchdog_set_nowayout(&mpc8xxx_wdt_dev, nowayout); + + ret = watchdog_register_device(&mpc8xxx_wdt_dev); + if (ret) { + pr_err("cannot register watchdog device (err=%d)\n", ret); + return ret; + } + return 0; +} +#ifndef MODULE +module_init(mpc8xxx_wdt_init_late); +#endif + +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/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c deleted file mode 100644 index 0d2b2773541..00000000000 --- a/drivers/watchdog/mpcore_wdt.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Watchdog driver for the mpcore watchdog timer - * - * (c) Copyright 2004 ARM Limited - * - * Based on the SoftDog driver: - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * 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> - * - */ -#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/reboot.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> - -#include <asm/hardware/arm_twd.h> -#include <asm/uaccess.h> - -struct mpcore_wdt { - unsigned long timer_alive; - struct device *dev; - void __iomem *base; - int irq; - unsigned int perturb; - char expect_close; -}; - -static struct platform_device *mpcore_wdt_dev; - -extern unsigned int mpcore_timer_rate; - -#define TIMER_MARGIN 60 -static int mpcore_margin = TIMER_MARGIN; -module_param(mpcore_margin, int, 0); -MODULE_PARM_DESC(mpcore_margin, "MPcore timer margin in seconds. (0<mpcore_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); - -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -#define ONLY_TESTING 0 -static int mpcore_noboot = ONLY_TESTING; -module_param(mpcore_noboot, int, 0); -MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); - -/* - * This is the interrupt handler. Note that we only use this - * in testing mode, so don't actually do a reboot here. - */ -static irqreturn_t mpcore_wdt_fire(int irq, void *arg) -{ - struct mpcore_wdt *wdt = arg; - - /* Check it really was our interrupt */ - if (readl(wdt->base + TWD_WDOG_INTSTAT)) { - dev_printk(KERN_CRIT, wdt->dev, "Triggered - Reboot ignored.\n"); - - /* Clear the interrupt on the watchdog */ - writel(1, wdt->base + TWD_WDOG_INTSTAT); - - return IRQ_HANDLED; - } - - return IRQ_NONE; -} - -/* - * mpcore_wdt_keepalive - reload the timer - * - * Note that the spec says a DIFFERENT value must be written to the reload - * register each time. The "perturb" variable deals with this by adding 1 - * to the count every other time the function is called. - */ -static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) -{ - unsigned int count; - - /* Assume prescale is set to 256 */ - count = (mpcore_timer_rate / 256) * mpcore_margin; - - /* Reload the counter */ - writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); - - wdt->perturb = wdt->perturb ? 0 : 1; -} - -static void mpcore_wdt_stop(struct mpcore_wdt *wdt) -{ - writel(0x12345678, wdt->base + TWD_WDOG_DISABLE); - writel(0x87654321, wdt->base + TWD_WDOG_DISABLE); - writel(0x0, wdt->base + TWD_WDOG_CONTROL); -} - -static void mpcore_wdt_start(struct mpcore_wdt *wdt) -{ - dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n"); - - /* This loads the count register but does NOT start the count yet */ - mpcore_wdt_keepalive(wdt); - - if (mpcore_noboot) { - /* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */ - writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL); - } else { - /* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */ - writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL); - } -} - -static int mpcore_wdt_set_heartbeat(int t) -{ - if (t < 0x0001 || t > 0xFFFF) - return -EINVAL; - - mpcore_margin = t; - return 0; -} - -/* - * /dev/watchdog handling - */ -static int mpcore_wdt_open(struct inode *inode, struct file *file) -{ - struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); - - if (test_and_set_bit(0, &wdt->timer_alive)) - return -EBUSY; - - if (nowayout) - __module_get(THIS_MODULE); - - file->private_data = wdt; - - /* - * Activate timer - */ - mpcore_wdt_start(wdt); - - return nonseekable_open(inode, file); -} - -static int mpcore_wdt_release(struct inode *inode, struct file *file) -{ - struct mpcore_wdt *wdt = file->private_data; - - /* - * Shut off the timer. - * Lock it in if it's a module and we set nowayout - */ - if (wdt->expect_close == 42) { - mpcore_wdt_stop(wdt); - } else { - dev_printk(KERN_CRIT, wdt->dev, "unexpected close, not stopping watchdog!\n"); - mpcore_wdt_keepalive(wdt); - } - clear_bit(0, &wdt->timer_alive); - wdt->expect_close = 0; - return 0; -} - -static ssize_t mpcore_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) -{ - struct mpcore_wdt *wdt = file->private_data; - - /* - * Refresh the timer. - */ - if (len) { - if (!nowayout) { - size_t i; - - /* In case it was set long ago */ - wdt->expect_close = 0; - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - wdt->expect_close = 42; - } - } - mpcore_wdt_keepalive(wdt); - } - return len; -} - -static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .identity = "MPcore Watchdog", -}; - -static int mpcore_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - struct mpcore_wdt *wdt = file->private_data; - int ret; - union { - struct watchdog_info ident; - int i; - } uarg; - - if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg)) - return -ENOTTY; - - if (_IOC_DIR(cmd) & _IOC_WRITE) { - ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd)); - if (ret) - return -EFAULT; - } - - switch (cmd) { - case WDIOC_GETSUPPORT: - uarg.ident = ident; - ret = 0; - break; - - case WDIOC_SETOPTIONS: - ret = -EINVAL; - if (uarg.i & WDIOS_DISABLECARD) { - mpcore_wdt_stop(wdt); - ret = 0; - } - if (uarg.i & WDIOS_ENABLECARD) { - mpcore_wdt_start(wdt); - ret = 0; - } - break; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - uarg.i = 0; - ret = 0; - break; - - case WDIOC_KEEPALIVE: - mpcore_wdt_keepalive(wdt); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = mpcore_wdt_set_heartbeat(uarg.i); - if (ret) - break; - - mpcore_wdt_keepalive(wdt); - /* Fall */ - case WDIOC_GETTIMEOUT: - uarg.i = mpcore_margin; - ret = 0; - break; - - default: - return -ENOTTY; - } - - if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) { - ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd)); - if (ret) - ret = -EFAULT; - } - return ret; -} - -/* - * System shutdown handler. Turn off the watchdog if we're - * restarting or halting the system. - */ -static void mpcore_wdt_shutdown(struct platform_device *dev) -{ - struct mpcore_wdt *wdt = platform_get_drvdata(dev); - - if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT) - mpcore_wdt_stop(wdt); -} - -/* - * Kernel Interfaces - */ -static const struct file_operations mpcore_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = mpcore_wdt_write, - .ioctl = mpcore_wdt_ioctl, - .open = mpcore_wdt_open, - .release = mpcore_wdt_release, -}; - -static struct miscdevice mpcore_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &mpcore_wdt_fops, -}; - -static int __devinit mpcore_wdt_probe(struct platform_device *dev) -{ - struct mpcore_wdt *wdt; - struct resource *res; - int ret; - - /* We only accept one device, and it must have an id of -1 */ - if (dev->id != -1) - return -ENODEV; - - res = platform_get_resource(dev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENODEV; - goto err_out; - } - - wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL); - if (!wdt) { - ret = -ENOMEM; - goto err_out; - } - - wdt->dev = &dev->dev; - wdt->irq = platform_get_irq(dev, 0); - if (wdt->irq < 0) { - ret = -ENXIO; - goto err_free; - } - wdt->base = ioremap(res->start, res->end - res->start + 1); - if (!wdt->base) { - ret = -ENOMEM; - goto err_free; - } - - mpcore_wdt_miscdev.parent = &dev->dev; - ret = misc_register(&mpcore_wdt_miscdev); - if (ret) { - dev_printk(KERN_ERR, _dev, "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto err_misc; - } - - ret = request_irq(wdt->irq, mpcore_wdt_fire, IRQF_DISABLED, "mpcore_wdt", wdt); - if (ret) { - dev_printk(KERN_ERR, _dev, "cannot register IRQ%d for watchdog\n", wdt->irq); - goto err_irq; - } - - mpcore_wdt_stop(wdt); - platform_set_drvdata(&dev->dev, wdt); - mpcore_wdt_dev = dev; - - return 0; - - err_irq: - misc_deregister(&mpcore_wdt_miscdev); - err_misc: - iounmap(wdt->base); - err_free: - kfree(wdt); - err_out: - return ret; -} - -static int __devexit mpcore_wdt_remove(struct platform_device *dev) -{ - struct mpcore_wdt *wdt = platform_get_drvdata(dev); - - platform_set_drvdata(dev, NULL); - - misc_deregister(&mpcore_wdt_miscdev); - - mpcore_wdt_dev = NULL; - - free_irq(wdt->irq, wdt); - iounmap(wdt->base); - kfree(wdt); - return 0; -} - -static struct platform_driver mpcore_wdt_driver = { - .probe = mpcore_wdt_probe, - .remove = __devexit_p(mpcore_wdt_remove), - .shutdown = mpcore_wdt_shutdown, - .driver = { - .owner = THIS_MODULE, - .name = "mpcore_wdt", - }, -}; - -static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n"; - -static int __init mpcore_wdt_init(void) -{ - /* - * Check that the margin value is within it's range; - * if not reset to the default - */ - if (mpcore_wdt_set_heartbeat(mpcore_margin)) { - mpcore_wdt_set_heartbeat(TIMER_MARGIN); - printk(KERN_INFO "mpcore_margin value must be 0<mpcore_margin<65536, using %d\n", - TIMER_MARGIN); - } - - printk(banner, mpcore_noboot, mpcore_margin, nowayout); - - return platform_driver_register(&mpcore_wdt_driver); -} - -static void __exit mpcore_wdt_exit(void) -{ - platform_driver_unregister(&mpcore_wdt_driver); -} - -module_init(mpcore_wdt_init); -module_exit(mpcore_wdt_exit); - -MODULE_AUTHOR("ARM Limited"); -MODULE_DESCRIPTION("MPcore Watchdog Device Driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mtx-1_wdt.c b/drivers/watchdog/mtx-1_wdt.c index 10b89f2703b..ff27c4ac96e 100644 --- a/drivers/watchdog/mtx-1_wdt.c +++ b/drivers/watchdog/mtx-1_wdt.c @@ -1,10 +1,11 @@ /* * Driver for the MTX-1 Watchdog. * - * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, All Rights Reserved. + * (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 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -39,19 +40,17 @@ #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/fs.h> -#include <linux/init.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 <asm/io.h> -#include <asm/uaccess.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/gpio.h> #include <asm/mach-au1x00/au1000.h> -#include <asm/gpio.h> #define MTX1_WDT_INTERVAL (5 * HZ) @@ -59,33 +58,31 @@ 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; unsigned gpio; + unsigned int gstate; } mtx1_wdt_device; static void mtx1_wdt_trigger(unsigned long unused) { - u32 tmp; - + spin_lock(&mtx1_wdt_device.lock); if (mtx1_wdt_device.running) ticks--; - /* - * toggle GPIO2_15 - */ - tmp = au_readl(GPIO2_DIR); - tmp = (tmp & ~(1 << mtx1_wdt_device.gpio)) | - ((~tmp) & (1 << mtx1_wdt_device.gpio)); - au_writel (tmp, GPIO2_DIR); + + /* toggle wdt gpio */ + mtx1_wdt_device.gstate = !mtx1_wdt_device.gstate; + gpio_set_value(mtx1_wdt_device.gpio, mtx1_wdt_device.gstate); if (mtx1_wdt_device.queue && ticks) mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); - else { + else complete(&mtx1_wdt_device.stop); - } + spin_unlock(&mtx1_wdt_device.lock); } static void mtx1_wdt_reset(void) @@ -96,23 +93,31 @@ static void mtx1_wdt_reset(void) 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; gpio_set_value(mtx1_wdt_device.gpio, 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; gpio_set_value(mtx1_wdt_device.gpio, 0); } - ticks = mtx1_wdt_device.default_ticks; - + spin_unlock_irqrestore(&mtx1_wdt_device.lock, flags); return 0; } @@ -122,7 +127,6 @@ static int mtx1_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) return -EBUSY; - return nonseekable_open(inode, file); } @@ -133,72 +137,69 @@ static int mtx1_wdt_release(struct inode *inode, struct file *file) return 0; } -static int mtx1_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = - { + static const struct watchdog_info ident = { .options = WDIOF_CARDRESET, .identity = "MTX-1 WDT", }; - switch(cmd) { - case WDIOC_KEEPALIVE: - mtx1_wdt_reset(); - break; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - if ( copy_to_user(argp, &value, sizeof(int)) ) - 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: - mtx1_wdt_start(); - break; - case WDIOS_DISABLECARD: - return mtx1_wdt_stop(); - default: - return -EINVAL; - } - break; - default: - return -ENOTTY; + 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) +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, + .owner = THIS_MODULE, .llseek = no_llseek, - .ioctl = mtx1_wdt_ioctl, - .open = mtx1_wdt_open, - .write = mtx1_wdt_write, - .release = mtx1_wdt_release + .unlocked_ioctl = mtx1_wdt_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 + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mtx1_wdt_fops, }; @@ -207,58 +208,52 @@ static int mtx1_wdt_probe(struct platform_device *pdev) int ret; mtx1_wdt_device.gpio = pdev->resource[0].start; - - if ((ret = misc_register(&mtx1_wdt_misc)) < 0) { - printk(KERN_ERR " mtx-1_wdt : failed to register\n"); + ret = devm_gpio_request_one(&pdev->dev, mtx1_wdt_device.gpio, + GPIOF_OUT_INIT_HIGH, "mtx1-wdt"); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request gpio"); return ret; } + 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); - setup_timer(&mtx1_wdt_device.timer, mtx1_wdt_trigger, 0L); - 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(); - - printk(KERN_INFO "MTX-1 Watchdog driver\n"); - + 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 = { +static struct platform_driver mtx1_wdt_driver = { .probe = mtx1_wdt_probe, .remove = mtx1_wdt_remove, .driver.name = "mtx1-wdt", + .driver.owner = THIS_MODULE, }; -static int __init mtx1_wdt_init(void) -{ - return platform_driver_register(&mtx1_wdt); -} - -static void __exit mtx1_wdt_exit(void) -{ - platform_driver_unregister(&mtx1_wdt); -} - -module_init(mtx1_wdt_init); -module_exit(mtx1_wdt_exit); +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_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:mtx1-wdt"); diff --git a/drivers/watchdog/mv64x60_wdt.c b/drivers/watchdog/mv64x60_wdt.c index 0365c317f7e..f9fa5840939 100644 --- a/drivers/watchdog/mv64x60_wdt.c +++ b/drivers/watchdog/mv64x60_wdt.c @@ -8,13 +8,15 @@ * and services the watchdog. * * Derived from mpc8xx_wdt.c, with the following copyright. - * + * * 2002 (c) Florian Schirmer <jolt@tuxbox.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. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/fs.h> #include <linux/init.h> #include <linux/kernel.h> @@ -22,10 +24,9 @@ #include <linux/module.h> #include <linux/watchdog.h> #include <linux/platform_device.h> - #include <linux/mv643xx.h> -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/uaccess.h> +#include <linux/io.h> #define MV64x60_WDT_WDC_OFFSET 0 @@ -59,9 +60,11 @@ static unsigned int bus_clk; static char expect_close; static DEFINE_SPINLOCK(mv64x60_wdt_spinlock); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift) { @@ -99,7 +102,7 @@ static void mv64x60_wdt_handler_enable(void) if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE, MV64x60_WDC_ENABLE_SHIFT)) { mv64x60_wdt_service(); - printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n"); + pr_notice("watchdog activated\n"); } } @@ -107,7 +110,7 @@ static void mv64x60_wdt_handler_disable(void) { if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, MV64x60_WDC_ENABLE_SHIFT)) - printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n"); + pr_notice("watchdog deactivated\n"); } static void mv64x60_wdt_set_timeout(unsigned int timeout) @@ -138,8 +141,7 @@ static int mv64x60_wdt_release(struct inode *inode, struct file *file) if (expect_close == 42) mv64x60_wdt_handler_disable(); else { - printk(KERN_CRIT - "mv64x60_wdt: unexpected close, not stopping timer!\n"); + pr_crit("unexpected close, not stopping timer!\n"); mv64x60_wdt_service(); } expect_close = 0; @@ -150,7 +152,7 @@ static int mv64x60_wdt_release(struct inode *inode, struct file *file) } static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t * ppos) + size_t len, loff_t *ppos) { if (len) { if (!nowayout) { @@ -160,7 +162,7 @@ static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, for (i = 0; i != len; i++) { char c; - if(get_user(c, data + i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -172,13 +174,13 @@ static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, return len; } -static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long mv64x60_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { int timeout; int options; void __user *argp = (void __user *)arg; - static struct watchdog_info info = { + static const struct watchdog_info info = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, @@ -240,7 +242,7 @@ static const struct file_operations mv64x60_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = mv64x60_wdt_write, - .ioctl = mv64x60_wdt_ioctl, + .unlocked_ioctl = mv64x60_wdt_ioctl, .open = mv64x60_wdt_open, .release = mv64x60_wdt_release, }; @@ -251,9 +253,9 @@ static struct miscdevice mv64x60_wdt_miscdev = { .fops = &mv64x60_wdt_fops, }; -static int __devinit mv64x60_wdt_probe(struct platform_device *dev) +static int mv64x60_wdt_probe(struct platform_device *dev) { - struct mv64x60_wdt_pdata *pdata = dev->dev.platform_data; + struct mv64x60_wdt_pdata *pdata = dev_get_platdata(&dev->dev); struct resource *r; int timeout = 10; @@ -274,7 +276,7 @@ static int __devinit mv64x60_wdt_probe(struct platform_device *dev) if (!r) return -ENODEV; - mv64x60_wdt_regs = ioremap(r->start, r->end - r->start + 1); + mv64x60_wdt_regs = devm_ioremap(&dev->dev, r->start, resource_size(r)); if (mv64x60_wdt_regs == NULL) return -ENOMEM; @@ -285,20 +287,18 @@ static int __devinit mv64x60_wdt_probe(struct platform_device *dev) return misc_register(&mv64x60_wdt_miscdev); } -static int __devexit mv64x60_wdt_remove(struct platform_device *dev) +static int mv64x60_wdt_remove(struct platform_device *dev) { misc_deregister(&mv64x60_wdt_miscdev); mv64x60_wdt_handler_disable(); - iounmap(mv64x60_wdt_regs); - return 0; } static struct platform_driver mv64x60_wdt_driver = { .probe = mv64x60_wdt_probe, - .remove = __devexit_p(mv64x60_wdt_remove), + .remove = mv64x60_wdt_remove, .driver = { .owner = THIS_MODULE, .name = MV64x60_WDT_NAME, @@ -307,7 +307,7 @@ static struct platform_driver mv64x60_wdt_driver = { static int __init mv64x60_wdt_init(void) { - printk(KERN_INFO "MV64x60 watchdog driver\n"); + pr_info("MV64x60 watchdog driver\n"); return platform_driver_register(&mv64x60_wdt_driver); } @@ -323,4 +323,4 @@ module_exit(mv64x60_wdt_exit); MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); MODULE_DESCRIPTION("MV64x60 watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" MV64x60_WDT_NAME); diff --git a/drivers/watchdog/nuc900_wdt.c b/drivers/watchdog/nuc900_wdt.c new file mode 100644 index 00000000000..7135803ca1a --- /dev/null +++ b/drivers/watchdog/nuc900_wdt.c @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2009 Nuvoton technology corporation. + * + * Wan ZongShun <mcuos.com@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation;version 2 of the License. + * + */ + +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> + +#define REG_WTCR 0x1c +#define WTCLK (0x01 << 10) +#define WTE (0x01 << 7) /*wdt enable*/ +#define WTIS (0x03 << 4) +#define WTIF (0x01 << 3) +#define WTRF (0x01 << 2) +#define WTRE (0x01 << 1) +#define WTR (0x01 << 0) +/* + * The watchdog time interval can be calculated via following formula: + * WTIS real time interval (formula) + * 0x00 ((2^ 14 ) * ((external crystal freq) / 256))seconds + * 0x01 ((2^ 16 ) * ((external crystal freq) / 256))seconds + * 0x02 ((2^ 18 ) * ((external crystal freq) / 256))seconds + * 0x03 ((2^ 20 ) * ((external crystal freq) / 256))seconds + * + * The external crystal freq is 15Mhz in the nuc900 evaluation board. + * So 0x00 = +-0.28 seconds, 0x01 = +-1.12 seconds, 0x02 = +-4.48 seconds, + * 0x03 = +- 16.92 seconds.. + */ +#define WDT_HW_TIMEOUT 0x02 +#define WDT_TIMEOUT (HZ/2) +#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) ")"); + +struct nuc900_wdt { + struct clk *wdt_clock; + struct platform_device *pdev; + void __iomem *wdt_base; + char expect_close; + struct timer_list timer; + spinlock_t wdt_lock; + unsigned long next_heartbeat; +}; + +static unsigned long nuc900wdt_busy; +static struct nuc900_wdt *nuc900_wdt; + +static inline void nuc900_wdt_keepalive(void) +{ + unsigned int val; + + spin_lock(&nuc900_wdt->wdt_lock); + + val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); + val |= (WTR | WTIF); + __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); + + spin_unlock(&nuc900_wdt->wdt_lock); +} + +static inline void nuc900_wdt_start(void) +{ + unsigned int val; + + spin_lock(&nuc900_wdt->wdt_lock); + + val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); + val |= (WTRE | WTE | WTR | WTCLK | WTIF); + val &= ~WTIS; + val |= (WDT_HW_TIMEOUT << 0x04); + __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); + + spin_unlock(&nuc900_wdt->wdt_lock); + + nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ; + mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT); +} + +static inline void nuc900_wdt_stop(void) +{ + unsigned int val; + + del_timer(&nuc900_wdt->timer); + + spin_lock(&nuc900_wdt->wdt_lock); + + val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR); + val &= ~WTE; + __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR); + + spin_unlock(&nuc900_wdt->wdt_lock); +} + +static inline void nuc900_wdt_ping(void) +{ + nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ; +} + +static int nuc900_wdt_open(struct inode *inode, struct file *file) +{ + + if (test_and_set_bit(0, &nuc900wdt_busy)) + return -EBUSY; + + nuc900_wdt_start(); + + return nonseekable_open(inode, file); +} + +static int nuc900_wdt_close(struct inode *inode, struct file *file) +{ + if (nuc900_wdt->expect_close == 42) + nuc900_wdt_stop(); + else { + dev_crit(&nuc900_wdt->pdev->dev, + "Unexpected close, not stopping watchdog!\n"); + nuc900_wdt_ping(); + } + + nuc900_wdt->expect_close = 0; + clear_bit(0, &nuc900wdt_busy); + return 0; +} + +static const struct watchdog_info nuc900_wdt_info = { + .identity = "nuc900 watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +static long nuc900_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, &nuc900_wdt_info, + sizeof(nuc900_wdt_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + nuc900_wdt_ping(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + heartbeat = new_value; + nuc900_wdt_ping(); + + return put_user(new_value, p); + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +static ssize_t nuc900_wdt_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; + + nuc900_wdt->expect_close = 0; + + for (i = 0; i < len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') { + nuc900_wdt->expect_close = 42; + break; + } + } + } + + nuc900_wdt_ping(); + return len; +} + +static void nuc900_wdt_timer_ping(unsigned long data) +{ + if (time_before(jiffies, nuc900_wdt->next_heartbeat)) { + nuc900_wdt_keepalive(); + mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT); + } else + dev_warn(&nuc900_wdt->pdev->dev, "Will reset the machine !\n"); +} + +static const struct file_operations nuc900wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = nuc900_wdt_ioctl, + .open = nuc900_wdt_open, + .release = nuc900_wdt_close, + .write = nuc900_wdt_write, +}; + +static struct miscdevice nuc900wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &nuc900wdt_fops, +}; + +static int nuc900wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret = 0; + + nuc900_wdt = devm_kzalloc(&pdev->dev, sizeof(*nuc900_wdt), + GFP_KERNEL); + if (!nuc900_wdt) + return -ENOMEM; + + nuc900_wdt->pdev = pdev; + + spin_lock_init(&nuc900_wdt->wdt_lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + nuc900_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(nuc900_wdt->wdt_base)) + return PTR_ERR(nuc900_wdt->wdt_base); + + nuc900_wdt->wdt_clock = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_wdt->wdt_clock)) { + dev_err(&pdev->dev, "failed to find watchdog clock source\n"); + return PTR_ERR(nuc900_wdt->wdt_clock); + } + + clk_enable(nuc900_wdt->wdt_clock); + + setup_timer(&nuc900_wdt->timer, nuc900_wdt_timer_ping, 0); + + ret = misc_register(&nuc900wdt_miscdev); + if (ret) { + dev_err(&pdev->dev, "err register miscdev on minor=%d (%d)\n", + WATCHDOG_MINOR, ret); + goto err_clk; + } + + return 0; + +err_clk: + clk_disable(nuc900_wdt->wdt_clock); + return ret; +} + +static int nuc900wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&nuc900wdt_miscdev); + + clk_disable(nuc900_wdt->wdt_clock); + + return 0; +} + +static struct platform_driver nuc900wdt_driver = { + .probe = nuc900wdt_probe, + .remove = nuc900wdt_remove, + .driver = { + .name = "nuc900-wdt", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(nuc900wdt_driver); + +MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); +MODULE_DESCRIPTION("Watchdog driver for NUC900"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:nuc900-wdt"); diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c new file mode 100644 index 00000000000..0b9ec61e131 --- /dev/null +++ b/drivers/watchdog/nv_tco.c @@ -0,0 +1,515 @@ +/* + * 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. + * http://www.kernelconcepts.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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 nonseekable_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(); + /* Fall through */ + 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, + .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, }, + { 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 = { + .owner = THIS_MODULE, + .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 00000000000..c2d1d04e055 --- /dev/null +++ b/drivers/watchdog/nv_tco.h @@ -0,0 +1,64 @@ +/* + * 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. + * http://www.kernelconcepts.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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 00000000000..4baf2d78892 --- /dev/null +++ b/drivers/watchdog/octeon-wdt-main.c @@ -0,0 +1,755 @@ +/* + * Octeon Watchdog driver + * + * Copyright (C) 2007, 2008, 2009, 2010 Cavium Networks + * + * Some parts derived from wdt.c + * + * (c) Copyright 1996-1997 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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> + * + * 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. + * + * + * 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/miscdevice.h> +#include <linux/interrupt.h> +#include <linux/watchdog.h> +#include <linux/cpumask.h> +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/cpu.h> +#include <linux/smp.h> +#include <linux/fs.h> +#include <linux/irq.h> + +#include <asm/mipsregs.h> +#include <asm/uasm.h> + +#include <asm/octeon/octeon.h> + +/* 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 int do_coundown; +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 */ + +static int heartbeat = WD_TIMO; +module_param(heartbeat, int, S_IRUGO); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0 < heartbeat, default=" + __MODULE_STRING(WD_TIMO) ")"); + +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 unsigned long octeon_wdt_is_open; +static char expect_close; + +static u32 __initdata nmi_stage1_insns[64]; +/* We need one branch and therefore one relocation per target label. */ +static struct uasm_label __initdata labels[5]; +static struct uasm_reloc __initdata relocs[5]; + +enum lable_id { + label_enter_bootloader = 1 +}; + +/* Some CP0 registers */ +#define K0 26 +#define C0_CVMMEMCTL 11, 7 +#define C0_STATUS 12, 0 +#define C0_EBASE 15, 1 +#define C0_DESAVE 31, 0 + +void octeon_wdt_nmi_stage2(void); + +static void __init octeon_wdt_build_stage1(void) +{ + int i; + int len; + u32 *p = nmi_stage1_insns; +#ifdef CONFIG_HOTPLUG_CPU + struct uasm_label *l = labels; + struct uasm_reloc *r = relocs; +#endif + + /* + * For the next few instructions running the debugger may + * cause corruption of k0 in the saved registers. Since we're + * about to crash, nobody probably cares. + * + * Save K0 into the debug scratch register + */ + uasm_i_dmtc0(&p, K0, C0_DESAVE); + + uasm_i_mfc0(&p, K0, C0_STATUS); +#ifdef CONFIG_HOTPLUG_CPU + uasm_il_bbit0(&p, &r, K0, ilog2(ST0_NMI), label_enter_bootloader); +#endif + /* Force 64-bit addressing enabled */ + uasm_i_ori(&p, K0, K0, ST0_UX | ST0_SX | ST0_KX); + uasm_i_mtc0(&p, K0, C0_STATUS); + +#ifdef CONFIG_HOTPLUG_CPU + uasm_i_mfc0(&p, K0, C0_EBASE); + /* Coreid number in K0 */ + uasm_i_andi(&p, K0, K0, 0xf); + /* 8 * coreid in bits 16-31 */ + uasm_i_dsll_safe(&p, K0, K0, 3 + 16); + uasm_i_ori(&p, K0, K0, 0x8001); + uasm_i_dsll_safe(&p, K0, K0, 16); + uasm_i_ori(&p, K0, K0, 0x0700); + uasm_i_drotr_safe(&p, K0, K0, 32); + /* + * Should result in: 0x8001,0700,0000,8*coreid which is + * CVMX_CIU_WDOGX(coreid) - 0x0500 + * + * Now ld K0, CVMX_CIU_WDOGX(coreid) + */ + uasm_i_ld(&p, K0, 0x500, K0); + /* + * If bit one set handle the NMI as a watchdog event. + * otherwise transfer control to bootloader. + */ + uasm_il_bbit0(&p, &r, K0, 1, label_enter_bootloader); + uasm_i_nop(&p); +#endif + + /* Clear Dcache so cvmseg works right. */ + uasm_i_cache(&p, 1, 0, 0); + + /* Use K0 to do a read/modify/write of CVMMEMCTL */ + uasm_i_dmfc0(&p, K0, C0_CVMMEMCTL); + /* Clear out the size of CVMSEG */ + uasm_i_dins(&p, K0, 0, 0, 6); + /* Set CVMSEG to its largest value */ + uasm_i_ori(&p, K0, K0, 0x1c0 | 54); + /* Store the CVMMEMCTL value */ + uasm_i_dmtc0(&p, K0, C0_CVMMEMCTL); + + /* Load the address of the second stage handler */ + UASM_i_LA(&p, K0, (long)octeon_wdt_nmi_stage2); + uasm_i_jr(&p, K0); + uasm_i_dmfc0(&p, K0, C0_DESAVE); + +#ifdef CONFIG_HOTPLUG_CPU + uasm_build_label(&l, p, label_enter_bootloader); + /* Jump to the bootloader and restore K0 */ + UASM_i_LA(&p, K0, (long)octeon_bootloader_entry_addr); + uasm_i_jr(&p, K0); + uasm_i_dmfc0(&p, K0, C0_DESAVE); +#endif + uasm_resolve_relocs(relocs, labels); + + len = (int)(p - nmi_stage1_insns); + pr_debug("Synthesized NMI stage 1 handler (%d instructions)\n", len); + + pr_debug("\t.set push\n"); + pr_debug("\t.set noreorder\n"); + for (i = 0; i < len; i++) + pr_debug("\t.word 0x%08x\n", nmi_stage1_insns[i]); + pr_debug("\t.set pop\n"); + + if (len > 32) + panic("NMI stage 1 handler exceeds 32 instructions, was %d\n", len); +} + +static int cpu2core(int cpu) +{ +#ifdef CONFIG_SMP + return cpu_logical_map(cpu); +#else + return cvmx_get_core_num(); +#endif +} + +static int core2cpu(int coreid) +{ +#ifdef CONFIG_SMP + return cpu_number_map(coreid); +#else + return 0; +#endif +} + +/** + * Poke the watchdog when an interrupt is received + * + * @cpl: + * @dev_id: + * + * Returns + */ +static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id) +{ + unsigned int core = cvmx_get_core_num(); + int cpu = core2cpu(core); + + if (do_coundown) { + if (per_cpu_countdown[cpu] > 0) { + /* We're alive, poke the watchdog */ + cvmx_write_csr(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(CVMX_CIU_PP_POKEX(core), 1); + } + return IRQ_HANDLED; +} + +/* From setup.c */ +extern int prom_putchar(char c); + +/** + * 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++); +} + +/** + * 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); + } +} + +const char *reg_name[] = { + "$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" +}; + +/** + * 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. */ + __delay(100000000ull * coreid); + + octeon_wdt_write_string("\r\n*** NMI Watchdog interrupt on Core 0x"); + octeon_wdt_write_hex(coreid, 1); + 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"); + + 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"); +} + +static void octeon_wdt_disable_interrupt(int cpu) +{ + unsigned int core; + unsigned int irq; + union cvmx_ciu_wdogx ciu_wdog; + + core = cpu2core(cpu); + + irq = OCTEON_IRQ_WDOG0 + core; + + /* Poke the watchdog to clear out its state */ + cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + + /* Disable the hardware. */ + ciu_wdog.u64 = 0; + cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + + free_irq(irq, octeon_wdt_poke_irq); +} + +static void octeon_wdt_setup_interrupt(int cpu) +{ + unsigned int core; + unsigned int irq; + union cvmx_ciu_wdogx ciu_wdog; + + core = cpu2core(cpu); + + /* Disable it before doing anything with the interrupts. */ + ciu_wdog.u64 = 0; + cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + + per_cpu_countdown[cpu] = countdown_reset; + + 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); + + cpumask_set_cpu(cpu, &irq_enabled_cpus); + + /* Poke the watchdog to clear out its state */ + cvmx_write_csr(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(CVMX_CIU_WDOGX(core), ciu_wdog.u64); +} + +static int octeon_wdt_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + + switch (action) { + case CPU_DOWN_PREPARE: + octeon_wdt_disable_interrupt(cpu); + break; + case CPU_ONLINE: + case CPU_DOWN_FAILED: + octeon_wdt_setup_interrupt(cpu); + break; + default: + break; + } + return NOTIFY_OK; +} + +static void octeon_wdt_ping(void) +{ + int cpu; + int coreid; + + for_each_online_cpu(cpu) { + coreid = cpu2core(cpu); + cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + per_cpu_countdown[cpu] = countdown_reset; + if ((countdown_reset || !do_coundown) && + !cpumask_test_cpu(cpu, &irq_enabled_cpus)) { + /* We have to enable the irq */ + int irq = OCTEON_IRQ_WDOG0 + coreid; + enable_irq(irq); + cpumask_set_cpu(cpu, &irq_enabled_cpus); + } + } +} + +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() >> 8) * timeout_sec) >> 8; +} + +static int octeon_wdt_set_heartbeat(int t) +{ + int cpu; + int coreid; + union cvmx_ciu_wdogx ciu_wdog; + + if (t <= 0) + return -1; + + octeon_wdt_calc_parameters(t); + + for_each_online_cpu(cpu) { + coreid = cpu2core(cpu); + cvmx_write_csr(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(CVMX_CIU_WDOGX(coreid), ciu_wdog.u64); + cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + } + octeon_wdt_ping(); /* Get the irqs back on. */ + return 0; +} + +/** + * octeon_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 octeon_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 = 1; + } + } + octeon_wdt_ping(); + } + return count; +} + +/** + * octeon_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 setting + * the timeout. + */ + +static long octeon_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "OCTEON", + }; + + 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_KEEPALIVE: + octeon_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (octeon_wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + /* Fall through. */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/** + * octeon_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 do a ping to reset the counters. + */ + +static int octeon_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &octeon_wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + octeon_wdt_ping(); + do_coundown = 1; + return nonseekable_open(inode, file); +} + +/** + * octeon_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 octeon_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close) { + do_coundown = 0; + octeon_wdt_ping(); + } else { + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); + } + clear_bit(0, &octeon_wdt_is_open); + expect_close = 0; + return 0; +} + +static const struct file_operations octeon_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = octeon_wdt_write, + .unlocked_ioctl = octeon_wdt_ioctl, + .open = octeon_wdt_open, + .release = octeon_wdt_release, +}; + +static struct miscdevice octeon_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &octeon_wdt_fops, +}; + +static struct notifier_block octeon_wdt_cpu_notifier = { + .notifier_call = octeon_wdt_cpu_callback, +}; + + +/** + * Module/ driver initialization. + * + * Returns Zero on success + */ +static int __init octeon_wdt_init(void) +{ + int i; + int ret; + int cpu; + u64 *ptr; + + /* + * Watchdog time expiration length = The 16 bits of LEN + * represent the most significant bits of a 24 bit decrementer + * that decrements every 256 cycles. + * + * 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() >> 8) * 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); + + ret = misc_register(&octeon_wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out; + } + + /* Build the NMI handler ... */ + octeon_wdt_build_stage1(); + + /* ... and install it. */ + ptr = (u64 *) nmi_stage1_insns; + for (i = 0; i < 16; i++) { + cvmx_write_csr(CVMX_MIO_BOOT_LOC_ADR, i * 8); + cvmx_write_csr(CVMX_MIO_BOOT_LOC_DAT, ptr[i]); + } + cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0x81fc0000); + + cpumask_clear(&irq_enabled_cpus); + + cpu_notifier_register_begin(); + for_each_online_cpu(cpu) + octeon_wdt_setup_interrupt(cpu); + + __register_hotcpu_notifier(&octeon_wdt_cpu_notifier); + cpu_notifier_register_done(); + +out: + return ret; +} + +/** + * Module / driver shutdown + */ +static void __exit octeon_wdt_cleanup(void) +{ + int cpu; + + misc_deregister(&octeon_wdt_miscdev); + + cpu_notifier_register_begin(); + __unregister_hotcpu_notifier(&octeon_wdt_cpu_notifier); + + for_each_online_cpu(cpu) { + int core = cpu2core(cpu); + /* Disable the watchdog */ + cvmx_write_csr(CVMX_CIU_WDOGX(core), 0); + /* Free the interrupt handler */ + free_irq(OCTEON_IRQ_WDOG0 + core, octeon_wdt_poke_irq); + } + + cpu_notifier_register_done(); + + /* + * 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 Networks <support@caviumnetworks.com>"); +MODULE_DESCRIPTION("Cavium Networks 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 00000000000..8a900a5e323 --- /dev/null +++ b/drivers/watchdog/octeon-wdt-nmi.S @@ -0,0 +1,64 @@ +/* + * 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 Cavium Networks + */ +#include <asm/asm.h> +#include <asm/regdef.h> + +#define SAVE_REG(r) sd $r, -32768+6912-(32-r)*8($0) + + NESTED(octeon_wdt_nmi_stage2, 0, sp) + .set push + .set noreorder + .set noat + /* 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) + /* Set the stack to begin right below the registers */ + li sp, -32768+6912-32*8 + /* Load the address of the third stage handler */ + dla a0, octeon_wdt_nmi_stage3 + /* Call the third stage handler */ + jal a0 + /* a0 is the address of the saved registers */ + move a0, sp + /* Loop forvever if we get here. */ +1: b 1b + 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 00000000000..1e6e28df5d7 --- /dev/null +++ b/drivers/watchdog/of_xilinx_wdt.c @@ -0,0 +1,249 @@ +/* + * Watchdog Device Driver for Xilinx axi/xps_timebase_wdt + * + * (C) Copyright 2013 - 2014 Xilinx, Inc. + * (C) Copyright 2011 (Alejandro Cabrera <aldaya@gmail.com>) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#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 0x00000008 /* Reset status */ +#define XWT_CSR0_WDS_MASK 0x00000004 /* Timer state */ +#define XWT_CSR0_EWDT1_MASK 0x00000002 /* Enable bit 1 */ + +/* Control/Status Register 0/1 bits */ +#define XWT_CSRX_EWDT2_MASK 0x00000001 /* 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; + struct watchdog_device xilinx_wdt_wdd; +}; + +static int xilinx_wdt_start(struct watchdog_device *wdd) +{ + u32 control_status_reg; + struct xwdt_device *xdev = watchdog_get_drvdata(wdd); + + 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); + + 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); + pr_info("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 int xwdt_probe(struct platform_device *pdev) +{ + int rc; + u32 pfreq = 0, enable_once = 0; + struct resource *res; + struct xwdt_device *xdev; + struct watchdog_device *xilinx_wdt_wdd; + + xdev = devm_kzalloc(&pdev->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 = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + xdev->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(xdev->base)) + return PTR_ERR(xdev->base); + + rc = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &pfreq); + if (rc) + dev_warn(&pdev->dev, + "The watchdog clock frequency cannot be obtained\n"); + + rc = of_property_read_u32(pdev->dev.of_node, "xlnx,wdt-interval", + &xdev->wdt_interval); + if (rc) + dev_warn(&pdev->dev, + "Parameter \"xlnx,wdt-interval\" not found\n"); + + rc = of_property_read_u32(pdev->dev.of_node, "xlnx,wdt-enable-once", + &enable_once); + if (rc) + dev_warn(&pdev->dev, + "Parameter \"xlnx,wdt-enable-once\" not found\n"); + + watchdog_set_nowayout(xilinx_wdt_wdd, enable_once); + + /* + * 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(&pdev->dev, "SelfTest routine error\n"); + return rc; + } + + rc = watchdog_register_device(xilinx_wdt_wdd); + if (rc) { + dev_err(&pdev->dev, "Cannot register watchdog (err=%d)\n", rc); + return rc; + } + + dev_info(&pdev->dev, "Xilinx Watchdog Timer at %p with timeout %ds\n", + xdev->base, xilinx_wdt_wdd->timeout); + + platform_set_drvdata(pdev, xdev); + + return 0; +} + +static int xwdt_remove(struct platform_device *pdev) +{ + struct xwdt_device *xdev = platform_get_drvdata(pdev); + + watchdog_unregister_device(&xdev->xilinx_wdt_wdd); + + return 0; +} + +/* 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, + .remove = xwdt_remove, + .driver = { + .owner = THIS_MODULE, + .name = WATCHDOG_NAME, + .of_match_table = xwdt_of_match, + }, +}; + +module_platform_driver(xwdt_driver); + +MODULE_AUTHOR("Alejandro Cabrera <aldaya@gmail.com>"); +MODULE_DESCRIPTION("Xilinx Watchdog driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c index 635ca454f56..3691b157516 100644 --- a/drivers/watchdog/omap_wdt.c +++ b/drivers/watchdog/omap_wdt.c @@ -1,7 +1,7 @@ /* - * linux/drivers/char/watchdog/omap_wdt.c + * omap_wdt.c * - * Watchdog driver for the TI OMAP 16xx & 24xx 32KHz (non-secure) watchdog + * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog * * Author: MontaVista Software, Inc. * <gdavis@mvista.com> or <source@mvista.com> @@ -16,7 +16,7 @@ * 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@redhat.com> + * 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 @@ -26,307 +26,273 @@ * Use the driver model and standard identifiers; handle bigger timeouts. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #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/reboot.h> -#include <linux/init.h> #include <linux/err.h> #include <linux/platform_device.h> #include <linux/moduleparam.h> -#include <linux/clk.h> -#include <linux/bitops.h> - -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/hardware.h> - -#include <asm/arch/prcm.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)"); -static int omap_wdt_users; -static struct clk *armwdt_ck = NULL; -static struct clk *mpu_wdt_ick = NULL; -static struct clk *mpu_wdt_fck = NULL; - -static unsigned int wdt_trgr_pattern = 0x1234; +struct omap_wdt_dev { + 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_ping(void) +static void omap_wdt_reload(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* wait for posted write to complete */ - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) + while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x08) cpu_relax(); - wdt_trgr_pattern = ~wdt_trgr_pattern; - omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR)); + + 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 ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08) + while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x08) cpu_relax(); /* reloaded WCRR from WLDR */ } -static void omap_wdt_enable(void) +static void omap_wdt_enable(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* Sequence to enable the watchdog */ - omap_writel(0xBBBB, OMAP_WATCHDOG_SPR); - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) + writel_relaxed(0xBBBB, base + OMAP_WATCHDOG_SPR); + while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x10) cpu_relax(); - omap_writel(0x4444, OMAP_WATCHDOG_SPR); - while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10) + + writel_relaxed(0x4444, base + OMAP_WATCHDOG_SPR); + while ((readl_relaxed(base + OMAP_WATCHDOG_WPS)) & 0x10) cpu_relax(); } -static void omap_wdt_disable(void) +static void omap_wdt_disable(struct omap_wdt_dev *wdev) { + void __iomem *base = wdev->base; + /* sequence required to disable watchdog */ - omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) + writel_relaxed(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x10) cpu_relax(); - omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10) - cpu_relax(); -} -static void omap_wdt_adjust_timeout(unsigned new_timeout) -{ - if (new_timeout < TIMER_MARGIN_MIN) - new_timeout = TIMER_MARGIN_DEFAULT; - if (new_timeout > TIMER_MARGIN_MAX) - new_timeout = TIMER_MARGIN_MAX; - timer_margin = new_timeout; + writel_relaxed(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x10) + cpu_relax(); } -static void omap_wdt_set_timeout(void) +static void omap_wdt_set_timer(struct omap_wdt_dev *wdev, + unsigned int timeout) { - u32 pre_margin = GET_WLDR_VAL(timer_margin); + u32 pre_margin = GET_WLDR_VAL(timeout); + void __iomem *base = wdev->base; /* just count up at 32 KHz */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x04) cpu_relax(); - omap_writel(pre_margin, OMAP_WATCHDOG_LDR); - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04) + + writel_relaxed(pre_margin, base + OMAP_WATCHDOG_LDR); + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x04) cpu_relax(); } -/* - * Allow only one task to hold it open - */ - -static int omap_wdt_open(struct inode *inode, struct file *file) +static int omap_wdt_start(struct watchdog_device *wdog) { - if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users)) - return -EBUSY; + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + void __iomem *base = wdev->base; - if (cpu_is_omap16xx()) - clk_enable(armwdt_ck); /* Enable the clock */ + mutex_lock(&wdev->lock); - if (cpu_is_omap24xx()) { - clk_enable(mpu_wdt_ick); /* Enable the interface clock */ - clk_enable(mpu_wdt_fck); /* Enable the functional clock */ - } + wdev->omap_wdt_users = true; + + pm_runtime_get_sync(wdev->dev); /* initialize prescaler */ - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x01) cpu_relax(); - omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL); - while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01) + + writel_relaxed((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); + while (readl_relaxed(base + OMAP_WATCHDOG_WPS) & 0x01) cpu_relax(); - omap_wdt_set_timeout(); - omap_wdt_enable(); - return nonseekable_open(inode, file); + 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_release(struct inode *inode, struct file *file) +static int omap_wdt_stop(struct watchdog_device *wdog) { - /* - * Shut off the timer unless NOWAYOUT is defined. - */ -#ifndef CONFIG_WATCHDOG_NOWAYOUT - omap_wdt_disable(); - - if (cpu_is_omap16xx()) { - clk_disable(armwdt_ck); /* Disable the clock */ - clk_put(armwdt_ck); - armwdt_ck = NULL; - } + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); - if (cpu_is_omap24xx()) { - clk_disable(mpu_wdt_ick); /* Disable the clock */ - clk_disable(mpu_wdt_fck); /* Disable the clock */ - clk_put(mpu_wdt_ick); - clk_put(mpu_wdt_fck); - mpu_wdt_ick = NULL; - mpu_wdt_fck = NULL; - } -#else - printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n"); -#endif - omap_wdt_users = 0; + 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 ssize_t -omap_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) +static int omap_wdt_ping(struct watchdog_device *wdog) { - /* Refresh LOAD_TIME. */ - if (len) - omap_wdt_ping(); - return len; + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + mutex_lock(&wdev->lock); + omap_wdt_reload(wdev); + mutex_unlock(&wdev->lock); + + return 0; } -static int -omap_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static int omap_wdt_set_timeout(struct watchdog_device *wdog, + unsigned int timeout) { - int new_margin; - static struct watchdog_info ident = { - .identity = "OMAP Watchdog", - .options = WDIOF_SETTIMEOUT, - .firmware_version = 0, - }; - - switch (cmd) { - default: - return -ENOTTY; - case WDIOC_GETSUPPORT: - return copy_to_user((struct watchdog_info __user *)arg, &ident, - sizeof(ident)); - case WDIOC_GETSTATUS: - return put_user(0, (int __user *)arg); - case WDIOC_GETBOOTSTATUS: - if (cpu_is_omap16xx()) - return put_user(omap_readw(ARM_SYSST), - (int __user *)arg); - if (cpu_is_omap24xx()) - return put_user(omap_prcm_get_reset_sources(), - (int __user *)arg); - case WDIOC_KEEPALIVE: - omap_wdt_ping(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_margin, (int __user *)arg)) - return -EFAULT; - omap_wdt_adjust_timeout(new_margin); - - omap_wdt_disable(); - omap_wdt_set_timeout(); - omap_wdt_enable(); - - omap_wdt_ping(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(timer_margin, (int __user *)arg); - } + struct omap_wdt_dev *wdev = watchdog_get_drvdata(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 const struct file_operations omap_wdt_fops = { - .owner = THIS_MODULE, - .write = omap_wdt_write, - .ioctl = omap_wdt_ioctl, - .open = omap_wdt_open, - .release = omap_wdt_release, +static const struct watchdog_info omap_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "OMAP Watchdog", }; -static struct miscdevice omap_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &omap_wdt_fops +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, }; -static int __init omap_wdt_probe(struct platform_device *pdev) +static int omap_wdt_probe(struct platform_device *pdev) { - struct resource *res, *mem; + struct omap_wd_timer_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct watchdog_device *omap_wdt; + struct resource *res; + struct omap_wdt_dev *wdev; + u32 rs; int ret; - /* reserve static register mappings */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENOENT; - - mem = request_mem_region(res->start, res->end - res->start + 1, - pdev->name); - if (mem == NULL) - return -EBUSY; - - platform_set_drvdata(pdev, mem); + omap_wdt = devm_kzalloc(&pdev->dev, sizeof(*omap_wdt), GFP_KERNEL); + if (!omap_wdt) + return -ENOMEM; - omap_wdt_users = 0; + wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return -ENOMEM; - if (cpu_is_omap16xx()) { - armwdt_ck = clk_get(&pdev->dev, "armwdt_ck"); - if (IS_ERR(armwdt_ck)) { - ret = PTR_ERR(armwdt_ck); - armwdt_ck = NULL; - goto fail; - } - } + wdev->omap_wdt_users = false; + wdev->dev = &pdev->dev; + wdev->wdt_trgr_pattern = 0x1234; + mutex_init(&wdev->lock); - if (cpu_is_omap24xx()) { - mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick"); - if (IS_ERR(mpu_wdt_ick)) { - ret = PTR_ERR(mpu_wdt_ick); - mpu_wdt_ick = NULL; - goto fail; - } - mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck"); - if (IS_ERR(mpu_wdt_fck)) { - ret = PTR_ERR(mpu_wdt_fck); - mpu_wdt_fck = NULL; - goto fail; - } + /* reserve static register mappings */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdev->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdev->base)) + return PTR_ERR(wdev->base); + + omap_wdt->info = &omap_wdt_info; + omap_wdt->ops = &omap_wdt_ops; + omap_wdt->min_timeout = TIMER_MARGIN_MIN; + omap_wdt->max_timeout = TIMER_MARGIN_MAX; + + if (timer_margin >= TIMER_MARGIN_MIN && + timer_margin <= TIMER_MARGIN_MAX) + omap_wdt->timeout = timer_margin; + else + omap_wdt->timeout = TIMER_MARGIN_DEFAULT; + + watchdog_set_drvdata(omap_wdt, wdev); + watchdog_set_nowayout(omap_wdt, nowayout); + + platform_set_drvdata(pdev, omap_wdt); + + pm_runtime_enable(wdev->dev); + pm_runtime_get_sync(wdev->dev); + + if (pdata && pdata->read_reset_sources) + rs = pdata->read_reset_sources(); + else + rs = 0; + omap_wdt->bootstatus = (rs & (1 << OMAP_MPU_WD_RST_SRC_ID_SHIFT)) ? + WDIOF_CARDRESET : 0; + + omap_wdt_disable(wdev); + + ret = watchdog_register_device(omap_wdt); + if (ret) { + pm_runtime_disable(wdev->dev); + return ret; } - omap_wdt_disable(); - omap_wdt_adjust_timeout(timer_margin); - - omap_wdt_miscdev.parent = &pdev->dev; - ret = misc_register(&omap_wdt_miscdev); - if (ret) - goto fail; + pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", + readl_relaxed(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, + omap_wdt->timeout); - pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin); + pm_runtime_put_sync(wdev->dev); - /* autogate OCP interface clock */ - omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG); return 0; - -fail: - if (armwdt_ck) - clk_put(armwdt_ck); - if (mpu_wdt_ick) - clk_put(mpu_wdt_ick); - if (mpu_wdt_fck) - clk_put(mpu_wdt_fck); - release_resource(mem); - return ret; } static void omap_wdt_shutdown(struct platform_device *pdev) { - omap_wdt_disable(); + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + 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 resource *mem = platform_get_drvdata(pdev); - misc_deregister(&omap_wdt_miscdev); - release_resource(mem); - if (armwdt_ck) - clk_put(armwdt_ck); - if (mpu_wdt_ick) - clk_put(mpu_wdt_ick); - if (mpu_wdt_fck) - clk_put(mpu_wdt_fck); + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + pm_runtime_disable(wdev->dev); + watchdog_unregister_device(wdog); + return 0; } @@ -340,17 +306,32 @@ static int omap_wdt_remove(struct platform_device *pdev) static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) { - if (omap_wdt_users) - omap_wdt_disable(); + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + 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) { - if (omap_wdt_users) { - omap_wdt_enable(); - omap_wdt_ping(); + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct omap_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + 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; } @@ -359,6 +340,12 @@ static int omap_wdt_resume(struct platform_device *pdev) #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, @@ -368,22 +355,12 @@ static struct platform_driver omap_wdt_driver = { .driver = { .owner = THIS_MODULE, .name = "omap_wdt", + .of_match_table = omap_wdt_of_match, }, }; -static int __init omap_wdt_init(void) -{ - return platform_driver_register(&omap_wdt_driver); -} - -static void __exit omap_wdt_exit(void) -{ - platform_driver_unregister(&omap_wdt_driver); -} - -module_init(omap_wdt_init); -module_exit(omap_wdt_exit); +module_platform_driver(omap_wdt_driver); MODULE_AUTHOR("George G. Davis"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:omap_wdt"); diff --git a/drivers/watchdog/omap_wdt.h b/drivers/watchdog/omap_wdt.h index 52a532a5114..09b774cf75b 100644 --- a/drivers/watchdog/omap_wdt.h +++ b/drivers/watchdog/omap_wdt.h @@ -30,31 +30,21 @@ #ifndef _OMAP_WATCHDOG_H #define _OMAP_WATCHDOG_H -#define OMAP1610_WATCHDOG_BASE 0xfffeb000 -#define OMAP2420_WATCHDOG_BASE 0x48022000 /*WDT Timer 2 */ - -#ifdef CONFIG_ARCH_OMAP24XX -#define OMAP_WATCHDOG_BASE OMAP2420_WATCHDOG_BASE -#else -#define OMAP_WATCHDOG_BASE OMAP1610_WATCHDOG_BASE -#define RM_RSTST_WKUP 0 -#endif - -#define OMAP_WATCHDOG_REV (OMAP_WATCHDOG_BASE + 0x00) -#define OMAP_WATCHDOG_SYS_CONFIG (OMAP_WATCHDOG_BASE + 0x10) -#define OMAP_WATCHDOG_STATUS (OMAP_WATCHDOG_BASE + 0x14) -#define OMAP_WATCHDOG_CNTRL (OMAP_WATCHDOG_BASE + 0x24) -#define OMAP_WATCHDOG_CRR (OMAP_WATCHDOG_BASE + 0x28) -#define OMAP_WATCHDOG_LDR (OMAP_WATCHDOG_BASE + 0x2c) -#define OMAP_WATCHDOG_TGR (OMAP_WATCHDOG_BASE + 0x30) -#define OMAP_WATCHDOG_WPS (OMAP_WATCHDOG_BASE + 0x34) -#define OMAP_WATCHDOG_SPR (OMAP_WATCHDOG_BASE + 0x48) +#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_MAX (24 * 60 * 60) /* 1 day */ #define TIMER_MARGIN_DEFAULT 60 /* 60 secs */ #define TIMER_MARGIN_MIN 1 diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c new file mode 100644 index 00000000000..00d0741228f --- /dev/null +++ b/drivers/watchdog/orion_wdt.c @@ -0,0 +1,615 @@ +/* + * 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 TIMER_A370_STATUS 0x04 + +#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) + +#define WDT_AXP_FIXED_ENABLE_BIT BIT(10) +#define WDT_A370_EXPIRED BIT(31) + +static bool nowayout = WATCHDOG_NOWAYOUT; +static int heartbeat = -1; /* 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 armadaxp_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)) + return PTR_ERR(dev->clk); + ret = clk_prepare_enable(dev->clk); + if (ret) { + clk_put(dev->clk); + return ret; + } + + /* Enable the fixed watchdog clock input */ + 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; +} + +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); + 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); + + /* Clear the watchdog expiration bit */ + atomic_io_modify(dev->reg + TIMER_A370_STATUS, WDT_A370_EXPIRED, 0); + + /* Enable watchdog timer */ + atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit, + dev->data->wdt_enable_bit); + + /* 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 */ + atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit, + dev->data->wdt_enable_bit); + + /* 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; + + /* 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 */ + atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit, 0); + + return 0; +} + +static int armada370_stop(struct watchdog_device *wdt_dev) +{ + struct orion_watchdog *dev = watchdog_get_drvdata(wdt_dev); + u32 reg; + + /* Disable reset on watchdog */ + reg = readl(dev->rstout); + reg &= ~dev->data->rstout_enable_bit; + writel(reg, dev->rstout); + + /* Disable watchdog timer */ + atomic_io_modify(dev->reg + TIMER_CTRL, dev->data->wdt_enable_bit, 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 int orion_wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int timeout) +{ + wdt_dev->timeout = timeout; + return 0; +} + +static const 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, + .set_timeout = orion_wdt_set_timeout, + .get_timeleft = orion_wdt_get_timeleft, +}; + +static irqreturn_t orion_wdt_irq(int irq, void *devid) +{ + panic("Watchdog Timeout"); + 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 harcoded 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 = armada370_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. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + dev->rstout = devm_ioremap_resource(&pdev->dev, res); + 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. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + dev->rstout = devm_ioremap_resource(&pdev->dev, res); + 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; + 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-insterted, or if the bootloader explicitly + * set a running watchdog before booting the kernel. + */ + if (!orion_wdt_enabled(&dev->wdt)) + orion_wdt_stop(&dev->wdt); + + /* Request the IRQ only after the watchdog is disabled */ + irq = platform_get_irq(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; + } + } + + 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 = { + .owner = THIS_MODULE, + .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"); +MODULE_ALIAS("platform:orion_wdt"); diff --git a/drivers/watchdog/pc87413_wdt.c b/drivers/watchdog/pc87413_wdt.c index 15e4f8887a9..9f15dd9435d 100644 --- a/drivers/watchdog/pc87413_wdt.c +++ b/drivers/watchdog/pc87413_wdt.c @@ -18,6 +18,8 @@ * Release 1.1 */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/types.h> #include <linux/miscdevice.h> @@ -30,46 +32,46 @@ #include <linux/init.h> #include <linux/spinlock.h> #include <linux/moduleparam.h> -#include <linux/version.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> /* #define DEBUG 1 */ -#define DEFAULT_TIMEOUT 1 /* 1 minute */ +#define DEFAULT_TIMEOUT 1 /* 1 minute */ #define MAX_TIMEOUT 255 #define VERSION "1.1" #define MODNAME "pc87413 WDT" -#define PFX MODNAME ": " #define DPFX MODNAME " - DEBUG: " -#define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */ +#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-Controll-Register */ -#define WDTO 0x11 /* Watchdog timeout register */ -#define WDCFG 0x12 /* Watchdog config register */ +#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 = 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 = 0; /* is the timer enabled? */ +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 char expect_close; /* is the close expected? */ -static DEFINE_SPINLOCK(io_lock);/* to guard the watchdog from io races */ +static DEFINE_SPINLOCK(io_lock); /* to guard us from io races */ -static int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; /* -- Low level function ----------------------------------------*/ /* Select pins for Watchdog output */ -static inline void pc87413_select_wdt_out (void) +static inline void pc87413_select_wdt_out(void) { unsigned int cr_data = 0; @@ -77,7 +79,7 @@ static inline void pc87413_select_wdt_out (void) outb_p(SIOCFG2, WDT_INDEX_IO_PORT); - cr_data = inb (WDT_DATA_IO_PORT); + cr_data = inb(WDT_DATA_IO_PORT); cr_data |= 0x80; /* Set Bit7 to 1*/ outb_p(SIOCFG2, WDT_INDEX_IO_PORT); @@ -85,8 +87,9 @@ static inline void pc87413_select_wdt_out (void) outb_p(cr_data, WDT_DATA_IO_PORT); #ifdef DEBUG - printk(KERN_INFO DPFX "Select multiple pin,pin55,as WDT output:" - " Bit7 to 1: %d\n", cr_data); + pr_info(DPFX + "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n", + cr_data); #endif } @@ -94,138 +97,120 @@ static inline void pc87413_select_wdt_out (void) static inline void pc87413_enable_swc(void) { - unsigned int cr_data=0; + unsigned int cr_data = 0; /* Step 2: Enable SWC functions */ - outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */ + 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 */ + 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 */ + cr_data |= 0x01; /* Set Bit0 to 1 */ outb_p(0x30, WDT_INDEX_IO_PORT); - outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ + outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ #ifdef DEBUG - printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n"); + pr_info(DPFX "pc87413 - Enable SWC functions\n"); #endif } /* Read SWC I/O base address */ -static inline unsigned int pc87413_get_swc_base(void) +static void pc87413_get_swc_base_addr(void) { - unsigned int swc_base_addr = 0; 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 */ + 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 */ + 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 - printk(KERN_INFO DPFX "Read SWC I/O Base Address: low %d, high %d," - " res %d\n", addr_l, addr_h, swc_base_addr); + pr_info(DPFX + "Read SWC I/O Base Address: low %d, high %d, res %d\n", + addr_l, addr_h, swc_base_addr); #endif - - return swc_base_addr; } /* Select Bank 3 of SWC */ -static inline void pc87413_swc_bank3(unsigned int swc_base_addr) +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 - printk(KERN_INFO DPFX "Select Bank3 of SWC\n"); + pr_info(DPFX "Select Bank3 of SWC\n"); #endif } /* Set watchdog timeout to x minutes */ -static inline void pc87413_programm_wdto(unsigned int swc_base_addr, - char pc87413_time) +static inline void pc87413_programm_wdto(char pc87413_time) { /* Step 5: Programm WDTO, Twd. */ - outb_p(pc87413_time, swc_base_addr + WDTO); - #ifdef DEBUG - printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time); + pr_info(DPFX "Set WDTO to %d minutes\n", pc87413_time); #endif } /* Enable WDEN */ -static inline void pc87413_enable_wden(unsigned int swc_base_addr) +static inline void pc87413_enable_wden(void) { /* Step 6: Enable WDEN */ - - outb_p(inb (swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); - + outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); #ifdef DEBUG - printk(KERN_INFO DPFX "Enable WDEN\n"); + pr_info(DPFX "Enable WDEN\n"); #endif } /* Enable SW_WD_TREN */ -static inline void pc87413_enable_sw_wd_tren(unsigned int swc_base_addr) +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); - + outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG); #ifdef DEBUG - printk(KERN_INFO DPFX "Enable SW_WD_TREN\n"); + pr_info(DPFX "Enable SW_WD_TREN\n"); #endif } /* Disable SW_WD_TREN */ -static inline void pc87413_disable_sw_wd_tren(unsigned int swc_base_addr) +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); - + outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG); #ifdef DEBUG - printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n"); + pr_info(DPFX "pc87413 - Disable SW_WD_TREN\n"); #endif } /* Enable SW_WD_TRG */ -static inline void pc87413_enable_sw_wd_trg(unsigned int swc_base_addr) +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); - + outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL); #ifdef DEBUG - printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n"); + pr_info(DPFX "pc87413 - Enable SW_WD_TRG\n"); #endif } /* Disable SW_WD_TRG */ -static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr) +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); - + outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL); #ifdef DEBUG - printk(KERN_INFO DPFX "Disable SW_WD_TRG\n"); + pr_info(DPFX "Disable SW_WD_TRG\n"); #endif } @@ -235,18 +220,13 @@ static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr) static void pc87413_enable(void) { - unsigned int swc_base_addr; - spin_lock(&io_lock); - pc87413_select_wdt_out(); - pc87413_enable_swc(); - swc_base_addr = pc87413_get_swc_base(); - pc87413_swc_bank3(swc_base_addr); - pc87413_programm_wdto(swc_base_addr, timeout); - pc87413_enable_wden(swc_base_addr); - pc87413_enable_sw_wd_tren(swc_base_addr); - pc87413_enable_sw_wd_trg(swc_base_addr); + pc87413_swc_bank3(); + pc87413_programm_wdto(timeout); + pc87413_enable_wden(); + pc87413_enable_sw_wd_tren(); + pc87413_enable_sw_wd_trg(); spin_unlock(&io_lock); } @@ -255,17 +235,12 @@ static void pc87413_enable(void) static void pc87413_disable(void) { - unsigned int swc_base_addr; - spin_lock(&io_lock); - pc87413_select_wdt_out(); - pc87413_enable_swc(); - swc_base_addr = pc87413_get_swc_base(); - pc87413_swc_bank3(swc_base_addr); - pc87413_disable_sw_wd_tren(swc_base_addr); - pc87413_disable_sw_wd_trg(swc_base_addr); - pc87413_programm_wdto(swc_base_addr, 0); + pc87413_swc_bank3(); + pc87413_disable_sw_wd_tren(); + pc87413_disable_sw_wd_trg(); + pc87413_programm_wdto(0); spin_unlock(&io_lock); } @@ -274,20 +249,15 @@ static void pc87413_disable(void) static void pc87413_refresh(void) { - unsigned int swc_base_addr; - spin_lock(&io_lock); - pc87413_select_wdt_out(); - pc87413_enable_swc(); - swc_base_addr = pc87413_get_swc_base(); - pc87413_swc_bank3(swc_base_addr); - pc87413_disable_sw_wd_tren(swc_base_addr); - pc87413_disable_sw_wd_trg(swc_base_addr); - pc87413_programm_wdto(swc_base_addr, timeout); - pc87413_enable_wden(swc_base_addr); - pc87413_enable_sw_wd_tren(swc_base_addr); - pc87413_enable_sw_wd_trg(swc_base_addr); + 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); } @@ -314,8 +284,7 @@ static int pc87413_open(struct inode *inode, struct file *file) /* Reload and activate timer */ pc87413_refresh(); - printk(KERN_INFO MODNAME "Watchdog enabled. Timeout set to" - " %d minute(s).\n", timeout); + pr_info("Watchdog enabled. Timeout set to %d minute(s).\n", timeout); return nonseekable_open(inode, file); } @@ -338,17 +307,13 @@ static int pc87413_release(struct inode *inode, struct file *file) if (expect_close == 42) { pc87413_disable(); - printk(KERN_INFO MODNAME "Watchdog disabled," - " sleeping again...\n"); + pr_info("Watchdog disabled, sleeping again...\n"); } else { - printk(KERN_CRIT MODNAME "Unexpected close, not stopping" - " watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); pc87413_refresh(); } - clear_bit(0, &timer_enabled); expect_close = 0; - return 0; } @@ -386,10 +351,11 @@ static ssize_t pc87413_write(struct file *file, const char __user *data, /* reset expect flag */ expect_close = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -404,7 +370,6 @@ static ssize_t pc87413_write(struct file *file, const char __user *data, /** * pc87413_ioctl: - * @inode: inode of the device * @file: file handle to the device * @cmd: watchdog command * @arg: argument pointer @@ -414,8 +379,8 @@ static ssize_t pc87413_write(struct file *file, const char __user *data, * querying capabilities and current status. */ -static int pc87413_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long pc87413_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int new_timeout; @@ -424,77 +389,60 @@ static int pc87413_ioctl(struct inode *inode, struct file *file, int __user *i; } uarg; - static struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | - WDIOF_SETTIMEOUT | - WDIOF_MAGICCLOSE, + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, .firmware_version = 1, - .identity = "PC87413(HF/F) watchdog" + .identity = "PC87413(HF/F) watchdog", }; uarg.i = (int __user *)arg; - switch(cmd) { - default: - return -ENOTTY; - - 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_KEEPALIVE: - pc87413_refresh(); + 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 - printk(KERN_INFO DPFX "keepalive\n"); + 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(); - - // fall through and return the new timeout... - - case WDIOC_GETTIMEOUT: - - new_timeout = timeout * 60; - - return put_user(new_timeout, 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; - } + 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(); + /* fall through and return the new timeout... */ + case WDIOC_GETTIMEOUT: + new_timeout = timeout * 60; + return put_user(new_timeout, uarg.i); + default: + return -ENOTTY; } } @@ -517,10 +465,8 @@ static int pc87413_notify_sys(struct notifier_block *this, void *unused) { if (code == SYS_DOWN || code == SYS_HALT) - { /* Turn the card off */ pc87413_disable(); - } return NOTIFY_DONE; } @@ -530,27 +476,25 @@ static const struct file_operations pc87413_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = pc87413_write, - .ioctl = pc87413_ioctl, + .unlocked_ioctl = pc87413_ioctl, .open = pc87413_open, .release = pc87413_release, }; -static struct notifier_block pc87413_notifier = -{ +static struct notifier_block pc87413_notifier = { .notifier_call = pc87413_notify_sys, }; -static struct miscdevice pc87413_miscdev= -{ +static struct miscdevice pc87413_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", - .fops = &pc87413_fops + .fops = &pc87413_fops, }; /* -- Module init functions -------------------------------------*/ /** - * pc87413_init: module's "constructor" + * 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. @@ -561,30 +505,45 @@ static int __init pc87413_init(void) { int ret; - printk(KERN_INFO PFX "Version " VERSION " at io 0x%X\n", WDT_INDEX_IO_PORT); + pr_info("Version " VERSION " at io 0x%X\n", + WDT_INDEX_IO_PORT); - /* request_region(io, 2, "pc87413"); */ + if (!request_muxed_region(io, 2, MODNAME)) + return -EBUSY; ret = register_reboot_notifier(&pc87413_notifier); - if (ret != 0) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); - } + if (ret != 0) + pr_err("cannot register reboot notifier (err=%d)\n", ret); ret = misc_register(&pc87413_miscdev); - if (ret != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - unregister_reboot_notifier(&pc87413_notifier); - return ret; + 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(); - printk(KERN_INFO PFX "initialized. timeout=%d min \n", timeout); + 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; } /** @@ -600,34 +559,37 @@ static int __init pc87413_init(void) static void __exit pc87413_exit(void) { /* Stop the timer before we leave */ - if (!nowayout) - { + if (!nowayout) { pc87413_disable(); - printk(KERN_INFO MODNAME "Watchdog disabled.\n"); + pr_info("Watchdog disabled\n"); } misc_deregister(&pc87413_miscdev); unregister_reboot_notifier(&pc87413_notifier); - /* release_region(io,2); */ + release_region(swc_base_addr, 0x20); - printk(MODNAME " watchdog component driver removed.\n"); + pr_info("watchdog component driver removed\n"); } module_init(pc87413_init); module_exit(pc87413_exit); -MODULE_AUTHOR("Sven Anders <anders@anduras.de>, Marcus Junker <junker@anduras.de>,"); +MODULE_AUTHOR("Sven Anders <anders@anduras.de>"); +MODULE_AUTHOR("Marcus Junker <junker@anduras.de>"); MODULE_DESCRIPTION("PC87413 WDT driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - module_param(io, int, 0); -MODULE_PARM_DESC(io, MODNAME " I/O port (default: " __MODULE_STRING(io) ")."); +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(timeout) ")."); - -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 index 7b41434fac8..e936f15dc7c 100644 --- a/drivers/watchdog/pcwd.c +++ b/drivers/watchdog/pcwd.c @@ -24,31 +24,35 @@ * 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. + * 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. + * 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>] + * 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 + * 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/ + * 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) */ @@ -57,7 +61,7 @@ #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 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#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/... */ @@ -65,18 +69,15 @@ #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 <asm/uaccess.h> /* For copy_to_user/put_user/... */ -#include <asm/io.h> /* For inb/outb/... */ +#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 PFX WATCHDOG_NAME ": " -#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" -#define WD_VER WATCHDOG_VERSION " (" WATCHDOG_DATE ")" +#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 @@ -111,14 +112,16 @@ static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; #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_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_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 */ @@ -142,7 +145,7 @@ static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; #define CMD_ISA_RESET_RELAYS 0x0D /* Watchdog's Dip Switch heartbeat values */ -static const int heartbeat_tbl [] = { +static const int heartbeat_tbl[] = { 20, /* OFF-OFF-OFF = 20 Sec */ 40, /* OFF-OFF-ON = 40 Sec */ 60, /* OFF-ON-OFF = 1 Min */ @@ -165,14 +168,18 @@ static const int heartbeat_tbl [] = { static int cards_found; /* internal variables */ -static atomic_t open_allowed = ATOMIC_INIT(1); +static unsigned long open_allowed; static char expect_close; static int temp_panic; -static struct { /* this is private data for each ISA-PC watchdog card */ + +/* 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; /* Wether or not the card has a temperature device */ - int command_mode; /* Wether or not the card is in command mode */ + 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 */ @@ -186,16 +193,22 @@ static struct { /* this is private data for each ISA-PC watchdog card */ #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)"); +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 */ +/* 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) ")"); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(2 <= heartbeat <= 7200 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -208,8 +221,7 @@ static int send_isa_command(int cmd) int port0, last_port0; /* Double read for stabilising */ if (debug >= DEBUG) - printk(KERN_DEBUG PFX "sending following data cmd=0x%02x\n", - cmd); + 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; @@ -224,19 +236,19 @@ static int send_isa_command(int cmd) if (port0 == last_port0) break; /* Data is stable */ - udelay (250); + udelay(250); } if (debug >= DEBUG) - printk(KERN_DEBUG PFX "received following data for cmd=0x%02x: port0=0x%02x last_port0=0x%02x\n", - cmd, port0, last_port0); + 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; + int i, found = 0, count = 0; /* Set the card into command mode */ spin_lock(&pcwd_private.io_lock); @@ -258,10 +270,9 @@ static int set_command_mode(void) pcwd_private.command_mode = found; if (debug >= DEBUG) - printk(KERN_DEBUG PFX "command_mode=%d\n", - pcwd_private.command_mode); + pr_debug("command_mode=%d\n", pcwd_private.command_mode); - return(found); + return found; } static void unset_command_mode(void) @@ -275,8 +286,7 @@ static void unset_command_mode(void) pcwd_private.command_mode = 0; if (debug >= DEBUG) - printk(KERN_DEBUG PFX "command_mode=%d\n", - pcwd_private.command_mode); + pr_debug("command_mode=%d\n", pcwd_private.command_mode); } static inline void pcwd_check_temperature_support(void) @@ -296,7 +306,8 @@ static inline void pcwd_get_firmware(void) 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); + sprintf(pcwd_private.fw_ver_str, "%c.%c%c%c", + one, ten, hund, minor); } unset_command_mode(); @@ -305,7 +316,7 @@ static inline void pcwd_get_firmware(void) static inline int pcwd_get_option_switches(void) { - int option_switches=0; + int option_switches = 0; if (set_command_mode()) { /* Get switch settings */ @@ -313,7 +324,7 @@ static inline int pcwd_get_option_switches(void) } unset_command_mode(); - return(option_switches); + return option_switches; } static void pcwd_show_card_info(void) @@ -322,13 +333,14 @@ static void pcwd_show_card_info(void) /* Get some extra info from the hardware (in command/debug/diag mode) */ if (pcwd_private.revision == PCWD_REVISION_A) - printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", pcwd_private.io_addr); + 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(); - printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n", + 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(); - printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + 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")); @@ -341,18 +353,18 @@ static void pcwd_show_card_info(void) } if (pcwd_private.supports_temp) - printk(KERN_INFO PFX "Temperature Option Detected\n"); + pr_info("Temperature Option Detected\n"); if (pcwd_private.boot_status & WDIOF_CARDRESET) - printk(KERN_INFO PFX "Previous reboot was caused by the card\n"); + pr_info("Previous reboot was caused by the card\n"); if (pcwd_private.boot_status & WDIOF_OVERHEAT) { - printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n"); - printk(KERN_EMERG PFX "CPU Overheat\n"); + pr_emerg("Card senses a CPU Overheat. Panicking!\n"); + pr_emerg("CPU Overheat\n"); } if (pcwd_private.boot_status == 0) - printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); + pr_info("No previous trip detected - Cold boot or reset\n"); } static void pcwd_timer_ping(unsigned long data) @@ -361,11 +373,12 @@ static void pcwd_timer_ping(unsigned long data) /* If we got a heartbeat pulse within the WDT_INTERVAL * we agree to ping the WDT */ - if(time_before(jiffies, pcwd_private.next_heartbeat)) { + 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 */ + /* 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; @@ -381,7 +394,7 @@ static void pcwd_timer_ping(unsigned long data) spin_unlock(&pcwd_private.io_lock); } else { - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); } } @@ -402,13 +415,13 @@ static int pcwd_start(void) stat_reg = inb_p(pcwd_private.io_addr + 2); spin_unlock(&pcwd_private.io_lock); if (stat_reg & WD_WDIS) { - printk(KERN_INFO PFX "Could not start watchdog\n"); + pr_info("Could not start watchdog\n"); return -EIO; } } if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "Watchdog started\n"); + pr_debug("Watchdog started\n"); return 0; } @@ -430,13 +443,13 @@ static int pcwd_stop(void) stat_reg = inb_p(pcwd_private.io_addr + 2); spin_unlock(&pcwd_private.io_lock); if ((stat_reg & WD_WDIS) == 0) { - printk(KERN_INFO PFX "Could not stop watchdog\n"); + pr_info("Could not stop watchdog\n"); return -EIO; } } if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "Watchdog stopped\n"); + pr_debug("Watchdog stopped\n"); return 0; } @@ -447,21 +460,20 @@ static int pcwd_keepalive(void) pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ); if (debug >= DEBUG) - printk(KERN_DEBUG PFX "Watchdog keepalive signal send\n"); + pr_debug("Watchdog keepalive signal send\n"); return 0; } static int pcwd_set_heartbeat(int t) { - if ((t < 2) || (t > 7200)) /* arbitrary upper limit */ + if (t < 2 || t > 7200) /* arbitrary upper limit */ return -EINVAL; heartbeat = t; if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "New heartbeat: %d\n", - heartbeat); + pr_debug("New heartbeat: %d\n", heartbeat); return 0; } @@ -470,7 +482,7 @@ static int pcwd_get_status(int *status) { int control_status; - *status=0; + *status = 0; spin_lock(&pcwd_private.io_lock); if (pcwd_private.revision == PCWD_REVISION_A) /* Rev A cards return status information from @@ -494,9 +506,8 @@ static int pcwd_get_status(int *status) if (control_status & WD_T110) { *status |= WDIOF_OVERHEAT; if (temp_panic) { - printk(KERN_INFO PFX "Temperature overheat trip!\n"); + pr_info("Temperature overheat trip!\n"); kernel_power_off(); - /* or should we just do a: panic(PFX "Temperature overheat trip!\n"); */ } } } else { @@ -506,9 +517,8 @@ static int pcwd_get_status(int *status) if (control_status & WD_REVC_TTRP) { *status |= WDIOF_OVERHEAT; if (temp_panic) { - printk(KERN_INFO PFX "Temperature overheat trip!\n"); + pr_info("Temperature overheat trip!\n"); kernel_power_off(); - /* or should we just do a: panic(PFX "Temperature overheat trip!\n"); */ } } } @@ -524,18 +534,19 @@ static int pcwd_clear_status(void) spin_lock(&pcwd_private.io_lock); if (debug >= VERBOSE) - printk(KERN_INFO PFX "clearing watchdog trip status\n"); + pr_info("clearing watchdog trip status\n"); control_status = inb_p(pcwd_private.io_addr + 1); if (debug >= DEBUG) { - printk(KERN_DEBUG PFX "status was: 0x%02x\n", control_status); - printk(KERN_DEBUG PFX "sending: 0x%02x\n", - (control_status & WD_REVC_R2DS)); + 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); + outb_p((control_status & WD_REVC_R2DS), + pcwd_private.io_addr + 1); spin_unlock(&pcwd_private.io_lock); } @@ -561,8 +572,7 @@ static int pcwd_get_temperature(int *temperature) spin_unlock(&pcwd_private.io_lock); if (debug >= DEBUG) { - printk(KERN_DEBUG PFX "temperature is: %d F\n", - *temperature); + pr_debug("temperature is: %d F\n", *temperature); } return 0; @@ -572,15 +582,14 @@ static int pcwd_get_temperature(int *temperature) * /dev/watchdog handling */ -static int pcwd_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_KEEPALIVEPING | @@ -590,12 +599,9 @@ static int pcwd_ioctl(struct inode *inode, struct file *file, .identity = "PCWD", }; - switch(cmd) { - default: - return -ENOTTY; - + switch (cmd) { case WDIOC_GETSUPPORT: - if(copy_to_user(argp, &ident, sizeof(ident))) + if (copy_to_user(argp, &ident, sizeof(ident))) return -EFAULT; return 0; @@ -613,25 +619,22 @@ static int pcwd_ioctl(struct inode *inode, struct file *file, return put_user(temperature, argp); case WDIOC_SETOPTIONS: - if (pcwd_private.revision == PCWD_REVISION_C) - { - if(copy_from_user(&rv, argp, sizeof(int))) + if (pcwd_private.revision == PCWD_REVISION_C) { + if (get_user(rv, argp)) return -EFAULT; - if (rv & WDIOS_DISABLECARD) - { - return pcwd_stop(); + if (rv & WDIOS_DISABLECARD) { + status = pcwd_stop(); + if (status < 0) + return status; } - - if (rv & WDIOS_ENABLECARD) - { - return pcwd_start(); + if (rv & WDIOS_ENABLECARD) { + status = pcwd_start(); + if (status < 0) + return status; } - if (rv & WDIOS_TEMPPANIC) - { temp_panic = 1; - } } return -EINVAL; @@ -651,6 +654,9 @@ static int pcwd_ioctl(struct inode *inode, struct file *file, case WDIOC_GETTIMEOUT: return put_user(heartbeat, argp); + + default: + return -ENOTTY; } return 0; @@ -682,16 +688,10 @@ static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len, static int pcwd_open(struct inode *inode, struct file *file) { - if (!atomic_dec_and_test(&open_allowed) ) { - if (debug >= VERBOSE) - printk(KERN_ERR PFX "Attempt to open already opened device.\n"); - atomic_inc( &open_allowed ); + if (test_and_set_bit(0, &open_allowed)) return -EBUSY; - } - if (nowayout) __module_get(THIS_MODULE); - /* Activate */ pcwd_start(); pcwd_keepalive(); @@ -700,14 +700,14 @@ static int pcwd_open(struct inode *inode, struct file *file) static int pcwd_close(struct inode *inode, struct file *file) { - if (expect_close == 42) { + if (expect_close == 42) pcwd_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); pcwd_keepalive(); } expect_close = 0; - atomic_inc( &open_allowed ); + clear_bit(0, &open_allowed); return 0; } @@ -750,7 +750,7 @@ static const struct file_operations pcwd_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = pcwd_write, - .ioctl = pcwd_ioctl, + .unlocked_ioctl = pcwd_ioctl, .open = pcwd_open, .release = pcwd_close, }; @@ -788,7 +788,7 @@ static inline int get_revision(void) * 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; + r = PCWD_REVISION_A; spin_unlock(&pcwd_private.io_lock); return r; @@ -801,20 +801,19 @@ static inline int get_revision(void) * The initial rate is once per second at board start up, then twice * per second for normal operation. */ -static int __devinit pcwd_isa_match(struct device *dev, unsigned int id) +static int pcwd_isa_match(struct device *dev, unsigned int id) { - int base_addr=pcwd_ioports[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) - printk(KERN_DEBUG PFX "pcwd_isa_match id=%d\n", - id); + pr_debug("pcwd_isa_match id=%d\n", id); - if (!request_region (base_addr, 4, "PCWD")) { - printk(KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr); + if (!request_region(base_addr, 4, "PCWD")) { + pr_info("Port 0x%04x unavailable\n", base_addr); return 0; } @@ -842,30 +841,30 @@ static int __devinit pcwd_isa_match(struct device *dev, unsigned int id) } } } - release_region (base_addr, 4); + release_region(base_addr, 4); return retval; } -static int __devinit pcwd_isa_probe(struct device *dev, unsigned int id) +static int pcwd_isa_probe(struct device *dev, unsigned int id) { int ret; if (debug >= DEBUG) - printk(KERN_DEBUG PFX "pcwd_isa_probe id=%d\n", - id); + pr_debug("pcwd_isa_probe id=%d\n", id); cards_found++; if (cards_found == 1) - printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER); + pr_info("v%s Ken Hollis (kenji@bitgate.com)\n", + WATCHDOG_VERSION); if (cards_found > 1) { - printk(KERN_ERR PFX "This driver only supports 1 device\n"); + pr_err("This driver only supports 1 device\n"); return -ENODEV; } if (pcwd_ioports[id] == 0x0000) { - printk(KERN_ERR PFX "No I/O-Address for card detected\n"); + pr_err("No I/O-Address for card detected\n"); return -ENODEV; } pcwd_private.io_addr = pcwd_ioports[id]; @@ -875,10 +874,11 @@ static int __devinit pcwd_isa_probe(struct device *dev, unsigned int id) /* 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")) { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - pcwd_private.io_addr); - ret=-EIO; + 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; } @@ -908,30 +908,31 @@ static int __devinit pcwd_isa_probe(struct device *dev, unsigned int id) 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 */ + /* 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); - printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n", + 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) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - TEMP_MINOR, 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) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto error_misc_register_watchdog; } - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); return 0; @@ -940,18 +941,18 @@ 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); + 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 int __devexit pcwd_isa_remove(struct device *dev, unsigned int id) +static int pcwd_isa_remove(struct device *dev, unsigned int id) { if (debug >= DEBUG) - printk(KERN_DEBUG PFX "pcwd_isa_remove id=%d\n", - id); + pr_debug("pcwd_isa_remove id=%d\n", id); if (!pcwd_private.io_addr) return 1; @@ -964,7 +965,8 @@ static int __devexit pcwd_isa_remove(struct device *dev, unsigned int id) 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); + release_region(pcwd_private.io_addr, + (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4); pcwd_private.io_addr = 0x0000; cards_found--; @@ -974,8 +976,7 @@ static int __devexit pcwd_isa_remove(struct device *dev, unsigned int id) static void pcwd_isa_shutdown(struct device *dev, unsigned int id) { if (debug >= DEBUG) - printk(KERN_DEBUG PFX "pcwd_isa_shutdown id=%d\n", - id); + pr_debug("pcwd_isa_shutdown id=%d\n", id); pcwd_stop(); } @@ -983,7 +984,7 @@ static void pcwd_isa_shutdown(struct device *dev, unsigned int id) static struct isa_driver pcwd_isa_driver = { .match = pcwd_isa_match, .probe = pcwd_isa_probe, - .remove = __devexit_p(pcwd_isa_remove), + .remove = pcwd_isa_remove, .shutdown = pcwd_isa_shutdown, .driver = { .owner = THIS_MODULE, @@ -999,15 +1000,14 @@ static int __init pcwd_init_module(void) static void __exit pcwd_cleanup_module(void) { isa_unregister_driver(&pcwd_isa_driver); - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); + pr_info("Watchdog Module Unloaded\n"); } module_init(pcwd_init_module); module_exit(pcwd_cleanup_module); -MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>, Wim Van Sebroeck <wim@iguana.be>"); +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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c index 61a89e95964..c0d07eef264 100644 --- a/drivers/watchdog/pcwd_pci.c +++ b/drivers/watchdog/pcwd_pci.c @@ -6,7 +6,7 @@ * Based on source code of the following authors: * Ken Hollis <kenji@bitgate.com>, * Lindsay Harris <lindsay@bluegum.com>, - * Alan Cox <alan@redhat.com>, + * Alan Cox <alan@lxorguk.ukuu.org.uk>, * Matt Domsch <Matt_Domsch@dell.com>, * Rob Radez <rob@osinvestor.com> * @@ -24,20 +24,23 @@ * 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/ + * 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 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#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 */ @@ -46,17 +49,14 @@ #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 <asm/uaccess.h> /* For copy_to_user/put_user/... */ -#include <asm/io.h> /* For inb/outb/... */ +#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_DATE "21 Jan 2007" #define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" #define WATCHDOG_NAME "pcwd_pci" -#define PFX WATCHDOG_NAME ": " -#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" +#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION /* Stuff for the PCI ID's */ #ifndef PCI_VENDOR_ID_QUICKLOGIC @@ -77,7 +77,8 @@ #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_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 */ @@ -97,7 +98,7 @@ #define CMD_GET_CLEAR_RESET_COUNT 0x84 /* Watchdog's Dip Switch heartbeat values */ -static const int heartbeat_tbl [] = { +static const int heartbeat_tbl[] = { 5, /* OFF-OFF-OFF = 5 Sec */ 10, /* OFF-OFF-ON = 10 Sec */ 30, /* OFF-ON-OFF = 30 Sec */ @@ -115,12 +116,18 @@ static int cards_found; static int temp_panic; static unsigned long is_active; static char expect_release; -static struct { /* this is private data for each PCI-PC watchdog card */ - int supports_temp; /* Wether or not the card has a temperature device */ - int boot_status; /* The card's boot status */ - unsigned long io_addr; /* The cards I/O address */ - spinlock_t io_lock; /* the lock for io operations */ - struct pci_dev *pdev; /* the PCI-device */ +/* 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 */ @@ -131,14 +138,18 @@ 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 */ +#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) ")"); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(0<heartbeat<65536 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -149,8 +160,8 @@ static int send_command(int cmd, int *msb, int *lsb) int got_response, count; if (debug >= DEBUG) - printk(KERN_DEBUG PFX "sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x\n", - cmd, *msb, *lsb); + 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. @@ -167,17 +178,18 @@ static int send_command(int cmd, int *msb, int *lsb) * 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++) { + 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) { - printk(KERN_DEBUG PFX "time to process command was: %d ms\n", - count); + pr_debug("time to process command was: %d ms\n", + count); } else { - printk(KERN_DEBUG PFX "card did not respond on command!\n"); + pr_debug("card did not respond on command!\n"); } } @@ -190,8 +202,8 @@ static int send_command(int cmd, int *msb, int *lsb) inb_p(pcipcwd_private.io_addr + 6); if (debug >= DEBUG) - printk(KERN_DEBUG PFX "received following data for cmd=0x%02x: msb=0x%02x lsb=0x%02x\n", - cmd, *msb, *lsb); + 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); @@ -219,33 +231,33 @@ static void pcipcwd_show_card_info(void) 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) { + 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 { + else sprintf(fw_ver_str, "<card no answer>"); - } /* Get switch settings */ option_switches = pcipcwd_get_option_switches(); - printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n", + 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")); - printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + 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) - printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n"); + pr_info("Previous reset was caused by the Watchdog card\n"); if (pcipcwd_private.boot_status & WDIOF_OVERHEAT) - printk(KERN_INFO PFX "Card sensed a CPU Overheat\n"); + pr_info("Card sensed a CPU Overheat\n"); if (pcipcwd_private.boot_status == 0) - printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); + pr_info("No previous trip detected - Cold boot or reset\n"); } static int pcipcwd_start(void) @@ -260,12 +272,12 @@ static int pcipcwd_start(void) spin_unlock(&pcipcwd_private.io_lock); if (stat_reg & WD_PCI_WDIS) { - printk(KERN_ERR PFX "Card timer not enabled\n"); + pr_err("Card timer not enabled\n"); return -1; } if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "Watchdog started\n"); + pr_debug("Watchdog started\n"); return 0; } @@ -285,12 +297,12 @@ static int pcipcwd_stop(void) spin_unlock(&pcipcwd_private.io_lock); if (!(stat_reg & WD_PCI_WDIS)) { - printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); + pr_err("Card did not acknowledge disable attempt\n"); return -1; } if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "Watchdog stopped\n"); + pr_debug("Watchdog stopped\n"); return 0; } @@ -303,7 +315,7 @@ static int pcipcwd_keepalive(void) spin_unlock(&pcipcwd_private.io_lock); if (debug >= DEBUG) - printk(KERN_DEBUG PFX "Watchdog keepalive signal send\n"); + pr_debug("Watchdog keepalive signal send\n"); return 0; } @@ -321,8 +333,7 @@ static int pcipcwd_set_heartbeat(int t) heartbeat = t; if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "New heartbeat: %d\n", - heartbeat); + pr_debug("New heartbeat: %d\n", heartbeat); return 0; } @@ -331,19 +342,18 @@ static int pcipcwd_get_status(int *status) { int control_status; - *status=0; + *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(PFX "Temperature overheat trip!\n"); + panic(KBUILD_MODNAME ": Temperature overheat trip!\n"); } if (debug >= DEBUG) - printk(KERN_DEBUG PFX "Control Status #1: 0x%02x\n", - control_status); + pr_debug("Control Status #1: 0x%02x\n", control_status); return 0; } @@ -355,27 +365,27 @@ static int pcipcwd_clear_status(void) int reset_counter; if (debug >= VERBOSE) - printk(KERN_INFO PFX "clearing watchdog trip status & LED\n"); + pr_info("clearing watchdog trip status & LED\n"); control_status = inb_p(pcipcwd_private.io_addr + 1); if (debug >= DEBUG) { - printk(KERN_DEBUG PFX "status was: 0x%02x\n", control_status); - printk(KERN_DEBUG PFX "sending: 0x%02x\n", - (control_status & WD_PCI_R2DS) | WD_PCI_WTRP); + 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); + outb_p((control_status & WD_PCI_R2DS) | WD_PCI_WTRP, + pcipcwd_private.io_addr + 1); /* clear reset counter */ - msb=0; - reset_counter=0xff; + msb = 0; + reset_counter = 0xff; send_command(CMD_GET_CLEAR_RESET_COUNT, &msb, &reset_counter); if (debug >= DEBUG) { - printk(KERN_DEBUG PFX "reset count was: 0x%02x\n", - reset_counter); + pr_debug("reset count was: 0x%02x\n", reset_counter); } return 0; @@ -398,8 +408,7 @@ static int pcipcwd_get_temperature(int *temperature) *temperature = (*temperature * 9 / 5) + 32; if (debug >= DEBUG) { - printk(KERN_DEBUG PFX "temperature is: %d F\n", - *temperature); + pr_debug("temperature is: %d F\n", *temperature); } return 0; @@ -417,8 +426,7 @@ static int pcipcwd_get_timeleft(int *time_left) *time_left = (msb << 8) + lsb; if (debug >= VERBOSE) - printk(KERN_DEBUG PFX "Time left before next reboot: %d\n", - *time_left); + pr_debug("Time left before next reboot: %d\n", *time_left); return 0; } @@ -439,10 +447,11 @@ static ssize_t pcipcwd_write(struct file *file, const char __user *data, * five months ago... */ expect_release = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_release = 42; @@ -455,12 +464,12 @@ static ssize_t pcipcwd_write(struct file *file, const char __user *data, return len; } -static int pcipcwd_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long pcipcwd_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_KEEPALIVEPING | @@ -471,92 +480,89 @@ static int pcipcwd_ioctl(struct inode *inode, struct file *file, }; switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, - sizeof (ident)) ? -EFAULT : 0; + 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_GETSTATUS: - { - int status; + case WDIOC_GETBOOTSTATUS: + return put_user(pcipcwd_private.boot_status, p); - pcipcwd_get_status(&status); + case WDIOC_GETTEMP: + { + int temperature; - return put_user(status, p); - } + if (pcipcwd_get_temperature(&temperature)) + return -EFAULT; - case WDIOC_GETBOOTSTATUS: - return put_user(pcipcwd_private.boot_status, p); + return put_user(temperature, p); + } - case WDIOC_GETTEMP: - { - int temperature; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; - if (pcipcwd_get_temperature(&temperature)) - return -EFAULT; + if (get_user(new_options, p)) + return -EFAULT; - return put_user(temperature, p); + if (new_options & WDIOS_DISABLECARD) { + if (pcipcwd_stop()) + return -EIO; + retval = 0; } - case WDIOC_KEEPALIVE: - pcipcwd_keepalive(); - return 0; - - 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_ENABLECARD) { - if (pcipcwd_start()) - return -EIO; - retval = 0; - } + if (new_options & WDIOS_TEMPPANIC) { + temp_panic = 1; + retval = 0; + } - if (new_options & WDIOS_TEMPPANIC) { - temp_panic = 1; - retval = 0; - } + return retval; + } - return retval; - } + case WDIOC_KEEPALIVE: + pcipcwd_keepalive(); + return 0; - case WDIOC_SETTIMEOUT: - { - int new_heartbeat; + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; - if (get_user(new_heartbeat, p)) - return -EFAULT; + if (get_user(new_heartbeat, p)) + return -EFAULT; - if (pcipcwd_set_heartbeat(new_heartbeat)) - return -EINVAL; + if (pcipcwd_set_heartbeat(new_heartbeat)) + return -EINVAL; - pcipcwd_keepalive(); - /* Fall */ - } + pcipcwd_keepalive(); + /* Fall */ + } - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); - case WDIOC_GETTIMELEFT: - { - int time_left; + case WDIOC_GETTIMELEFT: + { + int time_left; - if (pcipcwd_get_timeleft(&time_left)) - return -EFAULT; + if (pcipcwd_get_timeleft(&time_left)) + return -EFAULT; - return put_user(time_left, p); - } + return put_user(time_left, p); + } - default: - return -ENOTTY; + default: + return -ENOTTY; } } @@ -565,7 +571,7 @@ 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) - printk(KERN_ERR PFX "Attempt to open already opened device.\n"); + pr_err("Attempt to open already opened device\n"); return -EBUSY; } @@ -583,7 +589,7 @@ static int pcipcwd_release(struct inode *inode, struct file *file) if (expect_release == 42) { pcipcwd_stop(); } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); pcipcwd_keepalive(); } expect_release = 0; @@ -603,7 +609,7 @@ static ssize_t pcipcwd_temp_read(struct file *file, char __user *data, if (pcipcwd_get_temperature(&temperature)) return -EFAULT; - if (copy_to_user (data, &temperature, 1)) + if (copy_to_user(data, &temperature, 1)) return -EFAULT; return 1; @@ -626,12 +632,11 @@ static int pcipcwd_temp_release(struct inode *inode, struct file *file) * Notify system */ -static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) { - if (code==SYS_DOWN || code==SYS_HALT) { - /* Turn the WDT off */ - pcipcwd_stop(); - } + if (code == SYS_DOWN || code == SYS_HALT) + pcipcwd_stop(); /* Turn the WDT off */ return NOTIFY_DONE; } @@ -644,7 +649,7 @@ static const struct file_operations pcipcwd_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = pcipcwd_write, - .ioctl = pcipcwd_ioctl, + .unlocked_ioctl = pcipcwd_ioctl, .open = pcipcwd_open, .release = pcipcwd_release, }; @@ -677,37 +682,38 @@ static struct notifier_block pcipcwd_notifier = { * Init & exit routines */ -static int __devinit pcipcwd_card_init(struct pci_dev *pdev, +static int pcipcwd_card_init(struct pci_dev *pdev, const struct pci_device_id *ent) { int ret = -EIO; cards_found++; if (cards_found == 1) - printk(KERN_INFO PFX DRIVER_VERSION); + pr_info("%s\n", DRIVER_VERSION); if (cards_found > 1) { - printk(KERN_ERR PFX "This driver only supports 1 device\n"); + pr_err("This driver only supports 1 device\n"); return -ENODEV; } if (pci_enable_device(pdev)) { - printk(KERN_ERR PFX "Not possible to enable PCI Device\n"); + pr_err("Not possible to enable PCI Device\n"); return -ENODEV; } if (pci_resource_start(pdev, 0) == 0x0000) { - printk(KERN_ERR PFX "No I/O-Address for card detected\n"); + 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)) { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - (int) pcipcwd_private.io_addr); + pr_err("I/O address 0x%04x already in use\n", + (int) pcipcwd_private.io_addr); ret = -EIO; goto err_out_disable_device; } @@ -729,39 +735,40 @@ static int __devinit pcipcwd_card_init(struct pci_dev *pdev, /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ if (heartbeat == 0) - heartbeat = heartbeat_tbl[(pcipcwd_get_option_switches() & 0x07)]; + heartbeat = + heartbeat_tbl[(pcipcwd_get_option_switches() & 0x07)]; - /* Check that the heartbeat value is within it's range ; if not reset to the default */ + /* 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); - printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n", WATCHDOG_HEARTBEAT); } ret = register_reboot_notifier(&pcipcwd_notifier); if (ret != 0) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); + 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) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - TEMP_MINOR, ret); + 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) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto err_out_misc_deregister; } - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); return 0; @@ -778,7 +785,7 @@ err_out_disable_device: return ret; } -static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) +static void pcipcwd_card_exit(struct pci_dev *pdev) { /* Stop the timer before we leave */ if (!nowayout) @@ -794,7 +801,7 @@ static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) cards_found--; } -static struct pci_device_id pcipcwd_pci_tbl[] = { +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 */ @@ -805,28 +812,11 @@ static struct pci_driver pcipcwd_driver = { .name = WATCHDOG_NAME, .id_table = pcipcwd_pci_tbl, .probe = pcipcwd_card_init, - .remove = __devexit_p(pcipcwd_card_exit), + .remove = pcipcwd_card_exit, }; -static int __init pcipcwd_init_module(void) -{ - spin_lock_init(&pcipcwd_private.io_lock); - - return pci_register_driver(&pcipcwd_driver); -} - -static void __exit pcipcwd_cleanup_module(void) -{ - pci_unregister_driver(&pcipcwd_driver); - - printk(KERN_INFO PFX "Watchdog Module Unloaded.\n"); -} - -module_init(pcipcwd_init_module); -module_exit(pcipcwd_cleanup_module); +module_pci_driver(pcipcwd_driver); MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); diff --git a/drivers/watchdog/pcwd_usb.c b/drivers/watchdog/pcwd_usb.c index bf443d077a1..1a11aedc4fe 100644 --- a/drivers/watchdog/pcwd_usb.c +++ b/drivers/watchdog/pcwd_usb.c @@ -5,7 +5,7 @@ * * Based on source code of the following authors: * Ken Hollis <kenji@bitgate.com>, - * Alan Cox <alan@redhat.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> @@ -24,13 +24,15 @@ * 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 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#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 */ @@ -40,77 +42,62 @@ #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 <asm/uaccess.h> /* For copy_to_user/put_user/... */ - - -#ifdef CONFIG_USB_DEBUG - static int debug = 1; -#else - static int debug; -#endif - -/* Use our own dbg macro */ -#undef dbg -#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0) +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ /* Module and Version Information */ #define DRIVER_VERSION "1.02" -#define DRIVER_DATE "21 Jan 2007" #define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>" #define DRIVER_DESC "Berkshire USB-PC Watchdog driver" #define DRIVER_LICENSE "GPL" #define DRIVER_NAME "pcwd_usb" -#define PFX DRIVER_NAME ": " MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE(DRIVER_LICENSE); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); - -/* Module Parameters */ -module_param(debug, int, 0); -MODULE_PARM_DESC(debug, "Debug enabled or not"); -#define WATCHDOG_HEARTBEAT 0 /* default heartbeat = delay-time from dip-switches */ +#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) ")"); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(0<heartbeat<65536 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 struct usb_device_id usb_pcwd_table [] = { +static 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); +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_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 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 [] = { +static const int heartbeat_tbl[] = { 5, /* OFF-OFF-OFF = 5 Sec */ 10, /* OFF-OFF-ON = 10 Sec */ 30, /* OFF-ON-OFF = 30 Sec */ @@ -130,23 +117,36 @@ static char expect_release; /* Structure to hold all of our device specific stuff */ struct usb_pcwd_private { - struct usb_device * udev; /* save off the usb device pointer */ - struct usb_interface * interface; /* the interface for this device */ - - unsigned int interface_number; /* the interface number used for cmd's */ - - unsigned char * intr_buffer; /* the buffer to intr data */ - dma_addr_t intr_dma; /* the dma address for the intr buffer */ - size_t intr_size; /* the size of the intr buffer */ - struct urb * intr_urb; /* the urb used for the intr pipe */ - - unsigned char cmd_command; /* The command that is reported back */ - unsigned char cmd_data_msb; /* The data MSB that is reported back */ - unsigned char cmd_data_lsb; /* The data LSB that is reported back */ - atomic_t cmd_received; /* true if we received a report after a command */ - - int exists; /* Wether or not the device exists */ - struct mutex mtx; /* locks this structure */ + /* 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; @@ -154,8 +154,9 @@ static struct usb_pcwd_private *usb_pcwd_device; 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); +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 = { @@ -168,8 +169,10 @@ static struct usb_driver usb_pcwd_driver = { static void usb_pcwd_intr_done(struct urb *urb) { - struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context; + 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) { @@ -179,15 +182,17 @@ static void usb_pcwd_intr_done(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ - dbg("%s - urb shutting down with status: %d", __func__, urb->status); + dev_dbg(dev, "%s - urb shutting down with status: %d", + __func__, urb->status); return; /* -EPIPE: should clear the halt */ default: /* error */ - dbg("%s - nonzero urb status received: %d", __func__, urb->status); + dev_dbg(dev, "%s - nonzero urb status received: %d", + __func__, urb->status); goto resubmit; } - dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x", + 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]; @@ -195,49 +200,58 @@ static void usb_pcwd_intr_done(struct urb *urb) usb_pcwd->cmd_data_lsb = data[2]; /* notify anyone waiting that the cmd has finished */ - atomic_set (&usb_pcwd->cmd_received, 1); + atomic_set(&usb_pcwd->cmd_received, 1); resubmit: - retval = usb_submit_urb (urb, GFP_ATOMIC); + retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) - printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n", - 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) +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[6]; + unsigned char *buf; - /* We will not send any commands if the USB PCWD device does not exist */ + /* We will not send any commands if the USB PCWD device does + * not exist */ if ((!usb_pcwd) || (!usb_pcwd->exists)) return -1; - /* The USB PC Watchdog uses a 6 byte report format. The board currently uses - * only 3 of the six bytes of the report. */ + 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 */ - dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x", + 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); + 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, sizeof(buf), - USB_COMMAND_TIMEOUT) != sizeof(buf)) { - dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb); + 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++) { + for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); + count++) { mdelay(1); - if (atomic_read (&usb_pcwd->cmd_received)) + if (atomic_read(&usb_pcwd->cmd_received)) got_response = 1; } @@ -247,6 +261,8 @@ static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned cha *lsb = usb_pcwd->cmd_data_lsb; } + kfree(buf); + return got_response; } @@ -257,10 +273,11 @@ static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd) int retval; /* Enable Watchdog */ - retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb); + retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, + &msb, &lsb); if ((retval == 0) || (lsb == 0)) { - printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n"); + pr_err("Card did not acknowledge enable attempt\n"); return -1; } @@ -274,10 +291,11 @@ static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd) int retval; /* Disable Watchdog */ - retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb); + retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, + &msb, &lsb); if ((retval == 0) || (lsb != 0)) { - printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); + pr_err("Card did not acknowledge disable attempt\n"); return -1; } @@ -309,7 +327,8 @@ static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t) return 0; } -static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature) +static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, + int *temperature) { unsigned char msb, lsb; @@ -324,7 +343,8 @@ static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temp return 0; } -static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, int *time_left) +static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, + int *time_left) { unsigned char msb, lsb; @@ -342,7 +362,7 @@ static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, int *time_le */ static ssize_t usb_pcwd_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) + size_t len, loff_t *ppos) { /* See if we got the magic character 'V' and reload the timer */ if (len) { @@ -353,10 +373,11 @@ static ssize_t usb_pcwd_write(struct file *file, const char __user *data, * five months ago... */ expect_release = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_release = 42; @@ -369,12 +390,12 @@ static ssize_t usb_pcwd_write(struct file *file, const char __user *data, return len; } -static int usb_pcwd_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { + static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, @@ -383,77 +404,76 @@ static int usb_pcwd_ioctl(struct inode *inode, struct file *file, }; switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, - sizeof (ident)) ? -EFAULT : 0; + 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_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); - case WDIOC_GETTEMP: - { - int temperature; + case WDIOC_GETTEMP: + { + int temperature; - if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) - return -EFAULT; + if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) + return -EFAULT; - return put_user(temperature, p); - } + return put_user(temperature, p); + } - case WDIOC_KEEPALIVE: - usb_pcwd_keepalive(usb_pcwd_device); - return 0; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; - case WDIOC_SETOPTIONS: - { - int new_options, retval = -EINVAL; + if (get_user(new_options, p)) + return -EFAULT; - 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_DISABLECARD) { - usb_pcwd_stop(usb_pcwd_device); - retval = 0; - } + if (new_options & WDIOS_ENABLECARD) { + usb_pcwd_start(usb_pcwd_device); + retval = 0; + } - if (new_options & WDIOS_ENABLECARD) { - usb_pcwd_start(usb_pcwd_device); - retval = 0; - } + return retval; + } - return retval; - } + case WDIOC_KEEPALIVE: + usb_pcwd_keepalive(usb_pcwd_device); + return 0; - case WDIOC_SETTIMEOUT: - { - int new_heartbeat; + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; - if (get_user(new_heartbeat, p)) - return -EFAULT; + if (get_user(new_heartbeat, p)) + return -EFAULT; - if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) - return -EINVAL; + if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) + return -EINVAL; - usb_pcwd_keepalive(usb_pcwd_device); - /* Fall */ - } + usb_pcwd_keepalive(usb_pcwd_device); + /* Fall */ + } - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); - case WDIOC_GETTIMELEFT: - { - int time_left; + case WDIOC_GETTIMELEFT: + { + int time_left; - if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left)) - return -EFAULT; + if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left)) + return -EFAULT; - return put_user(time_left, p); - } + return put_user(time_left, p); + } - default: - return -ENOTTY; + default: + return -ENOTTY; } } @@ -477,7 +497,7 @@ static int usb_pcwd_release(struct inode *inode, struct file *file) if (expect_release == 42) { usb_pcwd_stop(usb_pcwd_device); } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); usb_pcwd_keepalive(usb_pcwd_device); } expect_release = 0; @@ -517,12 +537,11 @@ static int usb_pcwd_temperature_release(struct inode *inode, struct file *file) * Notify system */ -static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) { - if (code==SYS_DOWN || code==SYS_HALT) { - /* Turn the WDT off */ - usb_pcwd_stop(usb_pcwd_device); - } + if (code == SYS_DOWN || code == SYS_HALT) + usb_pcwd_stop(usb_pcwd_device); /* Turn the WDT off */ return NOTIFY_DONE; } @@ -535,7 +554,7 @@ static const struct file_operations usb_pcwd_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = usb_pcwd_write, - .ioctl = usb_pcwd_ioctl, + .unlocked_ioctl = usb_pcwd_ioctl, .open = usb_pcwd_open, .release = usb_pcwd_release, }; @@ -567,13 +586,13 @@ static struct notifier_block usb_pcwd_notifier = { /** * usb_pcwd_delete */ -static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd) +static inline void usb_pcwd_delete(struct usb_pcwd_private *usb_pcwd) { usb_free_urb(usb_pcwd->intr_urb); if (usb_pcwd->intr_buffer != NULL) - usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size, - usb_pcwd->intr_buffer, usb_pcwd->intr_dma); - kfree (usb_pcwd); + usb_free_coherent(usb_pcwd->udev, usb_pcwd->intr_size, + usb_pcwd->intr_buffer, usb_pcwd->intr_dma); + kfree(usb_pcwd); } /** @@ -582,7 +601,8 @@ static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd) * 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) +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; @@ -597,7 +617,7 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi cards_found++; if (cards_found > 1) { - printk(KERN_ERR PFX "This driver only supports 1 device\n"); + pr_err("This driver only supports 1 device\n"); return -ENODEV; } @@ -606,18 +626,16 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi /* check out that we have a HID device */ if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) { - printk(KERN_ERR PFX "The device isn't a Human Interface Device\n"); + pr_err("The device isn't a Human Interface Device\n"); return -ENODEV; } /* check out the endpoint: it has to be Interrupt & IN */ endpoint = &iface_desc->endpoint[0].desc; - if (!((endpoint->bEndpointAddress & USB_DIR_IN) && - ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) - == USB_ENDPOINT_XFER_INT))) { + if (!usb_endpoint_is_int_in(endpoint)) { /* we didn't find a Interrupt endpoint with direction IN */ - printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n"); + pr_err("Couldn't find an INTR & IN endpoint\n"); return -ENODEV; } @@ -626,11 +644,9 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); /* allocate memory for our device and initialize it */ - usb_pcwd = kzalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); - if (usb_pcwd == NULL) { - printk(KERN_ERR PFX "Out of memory\n"); + usb_pcwd = kzalloc(sizeof(struct usb_pcwd_private), GFP_KERNEL); + if (usb_pcwd == NULL) goto error; - } usb_pcwd_device = usb_pcwd; @@ -638,18 +654,21 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi 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); + usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? + le16_to_cpu(endpoint->wMaxPacketSize) : 8); /* set up the memory buffer's */ - if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, GFP_ATOMIC, &usb_pcwd->intr_dma))) { - printk(KERN_ERR PFX "Out of memory\n"); + usb_pcwd->intr_buffer = usb_alloc_coherent(udev, usb_pcwd->intr_size, + GFP_ATOMIC, &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) { - printk(KERN_ERR PFX "Out of memory\n"); + pr_err("Out of memory\n"); goto error; } @@ -662,7 +681,7 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi /* register our interrupt URB with the USB system */ if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) { - printk(KERN_ERR PFX "Problem registering interrupt URB\n"); + pr_err("Problem registering interrupt URB\n"); retval = -EIO; /* failure */ goto error; } @@ -674,20 +693,20 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi 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) { + 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 { + else sprintf(fw_ver_str, "<card no answer>"); - } - printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n", - fw_ver_str); + 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); + usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, + &option_switches); - printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + 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")); @@ -696,38 +715,38 @@ static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_devi 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 */ + /* 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); - printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n", WATCHDOG_HEARTBEAT); } retval = register_reboot_notifier(&usb_pcwd_notifier); if (retval != 0) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - retval); + pr_err("cannot register reboot notifier (err=%d)\n", retval); goto error; } retval = misc_register(&usb_pcwd_temperature_miscdev); if (retval != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - TEMP_MINOR, retval); + 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) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, retval); + 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); + usb_set_intfdata(interface, usb_pcwd); - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); return 0; @@ -759,8 +778,8 @@ static void usb_pcwd_disconnect(struct usb_interface *interface) /* prevent races with open() */ mutex_lock(&disconnect_mutex); - usb_pcwd = usb_get_intfdata (interface); - usb_set_intfdata (interface, NULL); + usb_pcwd = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); mutex_lock(&usb_pcwd->mtx); @@ -785,40 +804,7 @@ static void usb_pcwd_disconnect(struct usb_interface *interface) mutex_unlock(&disconnect_mutex); - printk(KERN_INFO PFX "USB PC Watchdog disconnected\n"); -} - - - -/** - * usb_pcwd_init - */ -static int __init usb_pcwd_init(void) -{ - int result; - - /* register this driver with the USB subsystem */ - result = usb_register(&usb_pcwd_driver); - if (result) { - printk(KERN_ERR PFX "usb_register failed. Error number %d\n", - result); - return result; - } - - printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n"); - return 0; -} - - -/** - * usb_pcwd_exit - */ -static void __exit usb_pcwd_exit(void) -{ - /* deregister this driver with the USB subsystem */ - usb_deregister(&usb_pcwd_driver); + pr_info("USB PC Watchdog disconnected\n"); } - -module_init (usb_pcwd_init); -module_exit (usb_pcwd_exit); +module_usb_driver(usb_pcwd_driver); diff --git a/drivers/watchdog/pika_wdt.c b/drivers/watchdog/pika_wdt.c new file mode 100644 index 00000000000..0cdfee26669 --- /dev/null +++ b/drivers/watchdog/pika_wdt.c @@ -0,0 +1,301 @@ +/* + * 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 = { + .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(unsigned long data) +{ + 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 nonseekable_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, +}; + +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; + static 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); + + setup_timer(&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/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index b04aa096a10..15fb316e943 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -4,36 +4,34 @@ * Watchdog driver for PNX4008 board * * Authors: Dmitry Chigirev <source@mvista.com>, - * Vitaly Wool <vitalywool@gmail.com> + * Vitaly Wool <vitalywool@gmail.com> * Based on sa1100 driver, * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> * - * 2005-2006 (c) MontaVista Software, Inc. 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. + * 2005-2006 (c) MontaVista Software, Inc. + * + * (C) 2012 Wolfram Sang, Pengutronix + * + * 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/device.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/spinlock.h> - -#include <asm/hardware.h> -#include <asm/uaccess.h> -#include <asm/io.h> - -#define MODULE_NAME "PNX4008-WDT: " +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/of.h> +#include <mach/hardware.h> /* WatchDog Timer - Chapter 23 Page 207 */ @@ -55,302 +53,182 @@ /* WDTIM_CTRL bit definitions */ #define COUNT_ENAB 1 -#define RESET_COUNT (1<<1) -#define DEBUG_EN (1<<2) +#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) +#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 */ +#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 int nowayout = WATCHDOG_NOWAYOUT; -static int heartbeat = DEFAULT_HEARTBEAT; +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned int heartbeat = DEFAULT_HEARTBEAT; static DEFINE_SPINLOCK(io_lock); -static unsigned long wdt_status; -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 -#define WDT_REGION_INITED 2 -#define WDT_DEVICE_INITED 3 - -static unsigned long boot_status; - -static struct resource *wdt_mem; static void __iomem *wdt_base; struct clk *wdt_clk; -static void wdt_enable(void) +static int pnx4008_wdt_start(struct watchdog_device *wdd) { spin_lock(&io_lock); - if (wdt_clk) - clk_set_rate(wdt_clk, 1); - /* stop counter, initiate counter reset */ - __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); + writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); /*wait for reset to complete. 100% guarantee event */ - while (__raw_readl(WDTIM_COUNTER(wdt_base))) + while (readl(WDTIM_COUNTER(wdt_base))) cpu_relax(); /* internal and external reset, stop after that */ - __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, - WDTIM_MCTRL(wdt_base)); + writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base)); /* configure match output */ - __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); + writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); /* clear interrupt, just in case */ - __raw_writel(MATCH_INT, WDTIM_INT(wdt_base)); + writel(MATCH_INT, WDTIM_INT(wdt_base)); /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ - __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base)); - __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); + writel(0xFFFF, WDTIM_PULSE(wdt_base)); + writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); /*enable counter, stop when debugger active */ - __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); + writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); spin_unlock(&io_lock); + return 0; } -static void wdt_disable(void) +static int pnx4008_wdt_stop(struct watchdog_device *wdd) { spin_lock(&io_lock); - __raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ - if (wdt_clk) - clk_set_rate(wdt_clk, 0); + writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ spin_unlock(&io_lock); + return 0; } -static int pnx4008_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 nonseekable_open(inode, file); -} - -static ssize_t -pnx4008_wdt_write(struct file *file, const char *data, size_t len, - loff_t * ppos) +static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int new_timeout) { - 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_enable(); - } - - return len; + wdd->timeout = new_timeout; + return 0; } -static struct watchdog_info ident = { +static const struct watchdog_info pnx4008_wdt_ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, .identity = "PNX4008 Watchdog", }; -static int -pnx4008_wdt_ioctl(struct inode *inode, 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(boot_status, (int *)arg); - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(time, (int *)arg); - if (ret) - break; - - if (time <= 0 || time > MAX_HEARTBEAT) { - ret = -EINVAL; - break; - } - - heartbeat = time; - wdt_enable(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(heartbeat, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - } - return ret; -} - -static int pnx4008_wdt_release(struct inode *inode, struct file *file) -{ - if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) - printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n"); - - wdt_disable(); - clear_bit(WDT_IN_USE, &wdt_status); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - return 0; -} - -static const struct file_operations pnx4008_wdt_fops = { +static const struct watchdog_ops pnx4008_wdt_ops = { .owner = THIS_MODULE, - .llseek = no_llseek, - .write = pnx4008_wdt_write, - .ioctl = pnx4008_wdt_ioctl, - .open = pnx4008_wdt_open, - .release = pnx4008_wdt_release, + .start = pnx4008_wdt_start, + .stop = pnx4008_wdt_stop, + .set_timeout = pnx4008_wdt_set_timeout, }; -static struct miscdevice pnx4008_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &pnx4008_wdt_fops, +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 int pnx4008_wdt_probe(struct platform_device *pdev) { - int ret = 0, size; - struct resource *res; + struct resource *r; + int ret = 0; - if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) - heartbeat = DEFAULT_HEARTBEAT; + watchdog_init_timeout(&pnx4008_wdd, heartbeat, &pdev->dev); - printk(KERN_INFO MODULE_NAME - "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(wdt_base)) + return PTR_ERR(wdt_base); - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - printk(KERN_INFO MODULE_NAME - "failed to get memory region resouce\n"); - return -ENOENT; - } + wdt_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt_clk)) + return PTR_ERR(wdt_clk); - size = res->end - res->start + 1; - wdt_mem = request_mem_region(res->start, size, pdev->name); + ret = clk_enable(wdt_clk); + if (ret) + return ret; - if (wdt_mem == NULL) { - printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); - return -ENOENT; - } - wdt_base = (void __iomem *)IO_ADDRESS(res->start); - - wdt_clk = clk_get(&pdev->dev, "wdt_ck"); - if (IS_ERR(wdt_clk)) { - ret = PTR_ERR(wdt_clk); - release_resource(wdt_mem); - kfree(wdt_mem); - goto out; - } else - clk_set_rate(wdt_clk, 1); - - ret = misc_register(&pnx4008_wdt_miscdev); + pnx4008_wdd.bootstatus = (readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? + WDIOF_CARDRESET : 0; + watchdog_set_nowayout(&pnx4008_wdd, nowayout); + + pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */ + + ret = watchdog_register_device(&pnx4008_wdd); if (ret < 0) { - printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); - release_resource(wdt_mem); - kfree(wdt_mem); - clk_set_rate(wdt_clk, 0); - } else { - boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? - WDIOF_CARDRESET : 0; - wdt_disable(); /*disable for now */ - set_bit(WDT_DEVICE_INITED, &wdt_status); + dev_err(&pdev->dev, "cannot register watchdog device\n"); + goto disable_clk; } -out: + dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", + pnx4008_wdd.timeout); + + return 0; + +disable_clk: + clk_disable(wdt_clk); return ret; } static int pnx4008_wdt_remove(struct platform_device *pdev) { - misc_deregister(&pnx4008_wdt_miscdev); - if (wdt_clk) { - clk_set_rate(wdt_clk, 0); - clk_put(wdt_clk); - wdt_clk = NULL; - } - if (wdt_mem) { - release_resource(wdt_mem); - kfree(wdt_mem); - wdt_mem = NULL; - } + watchdog_unregister_device(&pnx4008_wdd); + + clk_disable(wdt_clk); + 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 = "watchdog", + .name = "pnx4008-watchdog", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pnx4008_wdt_match), }, .probe = pnx4008_wdt_probe, .remove = pnx4008_wdt_remove, }; -static int __init pnx4008_wdt_init(void) -{ - return platform_driver_register(&platform_wdt_driver); -} - -static void __exit pnx4008_wdt_exit(void) -{ - platform_driver_unregister(&platform_wdt_driver); -} - -module_init(pnx4008_wdt_init); -module_exit(pnx4008_wdt_exit); +module_platform_driver(platform_wdt_driver); MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); +MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); -module_param(heartbeat, int, 0); +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, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Set to 1 to keep watchdog running after device release"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:pnx4008-watchdog"); diff --git a/drivers/watchdog/pnx833x_wdt.c b/drivers/watchdog/pnx833x_wdt.c new file mode 100644 index 00000000000..882fdcb46ad --- /dev/null +++ b/drivers/watchdog/pnx833x_wdt.c @@ -0,0 +1,280 @@ +/* + * PNX833x Hardware Watchdog Driver + * Copyright 2008 NXP Semiconductors + * Daniel Laird <daniel.j.laird@nxp.com> + * Andre McCurdy <andre.mccurdy@nxp.com> + * + * Heavily based upon - IndyDog 0.3 + * A Hardware Watchdog Device for SGI IP22 + * + * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * based on softdog.c by Alan Cox <alan@redhat.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/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 <asm/mach-pnx833x/pnx833x.h> + +#define WATCHDOG_TIMEOUT 30 /* 30 sec Maximum timeout */ +#define WATCHDOG_COUNT_FREQUENCY 68000000U /* Watchdog counts at 68MHZ. */ +#define PNX_WATCHDOG_TIMEOUT (WATCHDOG_TIMEOUT * WATCHDOG_COUNT_FREQUENCY) +#define PNX_TIMEOUT_VALUE 2040000000U + +/** CONFIG block */ +#define PNX833X_CONFIG (0x07000U) +#define PNX833X_CONFIG_CPU_WATCHDOG (0x54) +#define PNX833X_CONFIG_CPU_WATCHDOG_COMPARE (0x58) +#define PNX833X_CONFIG_CPU_COUNTERS_CONTROL (0x1c) + +/** RESET block */ +#define PNX833X_RESET (0x08000U) +#define PNX833X_RESET_CONFIG (0x08) + +static int pnx833x_wdt_alive; + +/* Set default timeout in MHZ.*/ +static int pnx833x_wdt_timeout = PNX_WATCHDOG_TIMEOUT; +module_param(pnx833x_wdt_timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in Mhz. (68Mhz clock), default=" + __MODULE_STRING(PNX_TIMEOUT_VALUE) "(30 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) ")"); + +#define START_DEFAULT 1 +static int start_enabled = START_DEFAULT; +module_param(start_enabled, int, 0); +MODULE_PARM_DESC(start_enabled, "Watchdog is started on module insertion " + "(default=" __MODULE_STRING(START_DEFAULT) ")"); + +static void pnx833x_wdt_start(void) +{ + /* Enable watchdog causing reset. */ + PNX833X_REG(PNX833X_RESET + PNX833X_RESET_CONFIG) |= 0x1; + /* Set timeout.*/ + PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_WATCHDOG_COMPARE) = pnx833x_wdt_timeout; + /* Enable watchdog. */ + PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_COUNTERS_CONTROL) |= 0x1; + + pr_info("Started watchdog timer\n"); +} + +static void pnx833x_wdt_stop(void) +{ + /* Disable watchdog causing reset. */ + PNX833X_REG(PNX833X_RESET + PNX833X_CONFIG) &= 0xFFFFFFFE; + /* Disable watchdog.*/ + PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_COUNTERS_CONTROL) &= 0xFFFFFFFE; + + pr_info("Stopped watchdog timer\n"); +} + +static void pnx833x_wdt_ping(void) +{ + PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_WATCHDOG_COMPARE) = pnx833x_wdt_timeout; +} + +/* + * Allow only one person to hold it open + */ +static int pnx833x_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &pnx833x_wdt_alive)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate timer */ + if (!start_enabled) + pnx833x_wdt_start(); + + pnx833x_wdt_ping(); + + pr_info("Started watchdog timer\n"); + + return nonseekable_open(inode, file); +} + +static int pnx833x_wdt_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) + pnx833x_wdt_stop(); /* Turn the WDT off */ + + clear_bit(0, &pnx833x_wdt_alive); + return 0; +} + +static ssize_t pnx833x_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + /* Refresh the timer. */ + if (len) + pnx833x_wdt_ping(); + + return len; +} + +static long pnx833x_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int options, new_timeout = 0; + uint32_t timeout, timeout_left = 0; + + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, + .firmware_version = 0, + .identity = "Hardware Watchdog for PNX833x", + }; + + switch (cmd) { + default: + return -ENOTTY; + + 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) + pnx833x_wdt_stop(); + + if (options & WDIOS_ENABLECARD) + pnx833x_wdt_start(); + + return 0; + + case WDIOC_KEEPALIVE: + pnx833x_wdt_ping(); + return 0; + + case WDIOC_SETTIMEOUT: + { + if (get_user(new_timeout, (int *)arg)) + return -EFAULT; + + pnx833x_wdt_timeout = new_timeout; + PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_WATCHDOG_COMPARE) = new_timeout; + return put_user(new_timeout, (int *)arg); + } + + case WDIOC_GETTIMEOUT: + timeout = PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_WATCHDOG_COMPARE); + return put_user(timeout, (int *)arg); + + case WDIOC_GETTIMELEFT: + timeout_left = PNX833X_REG(PNX833X_CONFIG + + PNX833X_CONFIG_CPU_WATCHDOG); + return put_user(timeout_left, (int *)arg); + + } +} + +static int pnx833x_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + pnx833x_wdt_stop(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +static const struct file_operations pnx833x_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pnx833x_wdt_write, + .unlocked_ioctl = pnx833x_wdt_ioctl, + .open = pnx833x_wdt_open, + .release = pnx833x_wdt_release, +}; + +static struct miscdevice pnx833x_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pnx833x_wdt_fops, +}; + +static struct notifier_block pnx833x_wdt_notifier = { + .notifier_call = pnx833x_wdt_notify_sys, +}; + +static int __init watchdog_init(void) +{ + int ret, cause; + + /* Lets check the reason for the reset.*/ + cause = PNX833X_REG(PNX833X_RESET); + /*If bit 31 is set then watchdog was cause of reset.*/ + if (cause & 0x80000000) { + pr_info("The system was previously reset due to the watchdog firing - please investigate...\n"); + } + + ret = register_reboot_notifier(&pnx833x_wdt_notifier); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + return ret; + } + + ret = misc_register(&pnx833x_wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&pnx833x_wdt_notifier); + return ret; + } + + pr_info("Hardware Watchdog Timer for PNX833x: Version 0.1\n"); + + if (start_enabled) + pnx833x_wdt_start(); + + return 0; +} + +static void __exit watchdog_exit(void) +{ + misc_deregister(&pnx833x_wdt_miscdev); + unregister_reboot_notifier(&pnx833x_wdt_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Daniel Laird/Andre McCurdy"); +MODULE_DESCRIPTION("Hardware Watchdog Device for PNX833x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c new file mode 100644 index 00000000000..71e78ef4b73 --- /dev/null +++ b/drivers/watchdog/rc32434_wdt.c @@ -0,0 +1,330 @@ +/* + * 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. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#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_nocache */ + +#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 nonseekable_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; + /* Fall through */ + case WDIOC_GETTIMEOUT: + return copy_to_user(argp, &timeout, sizeof(int)); + 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, + .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_nocache(&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 00000000000..29cf4dcbc59 --- /dev/null +++ b/drivers/watchdog/rdc321x_wdt.c @@ -0,0 +1,299 @@ +/* + * RDC321x watchdog driver + * + * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org> + * + * This driver is highly inspired from the cpu5_wdt driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#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(unsigned long 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 nonseekable_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, + .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; + + 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); + rdc321x_wdt_device.queue = 0; + + clear_bit(0, &rdc321x_wdt_device.inuse); + + setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0); + + rdc321x_wdt_device.default_ticks = ticks; + + 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 = { + .owner = THIS_MODULE, + .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/retu_wdt.c b/drivers/watchdog/retu_wdt.c new file mode 100644 index 00000000000..a7a0695971e --- /dev/null +++ b/drivers/watchdog/retu_wdt.c @@ -0,0 +1,177 @@ +/* + * Retu watchdog driver + * + * Copyright (C) 2004, 2005 Nokia Corporation + * + * Based on code written by Amit Kucheria and Michael Buesch. + * Rewritten by Aaro Koskinen. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#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_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; + + watchdog_set_drvdata(retu_wdt, wdev); + watchdog_set_nowayout(retu_wdt, nowayout); + + wdev->rdev = rdev; + wdev->dev = &pdev->dev; + + INIT_DELAYED_WORK(&wdev->ping_work, retu_wdt_ping_work); + + ret = watchdog_register_device(retu_wdt); + if (ret < 0) + return ret; + + if (nowayout) + retu_wdt_ping(retu_wdt); + else + retu_wdt_ping_enable(wdev); + + platform_set_drvdata(pdev, retu_wdt); + + return 0; +} + +static int retu_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct retu_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + watchdog_unregister_device(wdog); + cancel_delayed_work_sync(&wdev->ping_work); + + return 0; +} + +static struct platform_driver retu_wdt_driver = { + .probe = retu_wdt_probe, + .remove = retu_wdt_remove, + .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 00000000000..cfed0fe264d --- /dev/null +++ b/drivers/watchdog/riowd.c @@ -0,0 +1,247 @@ +/* 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_SUPPORTED_DEVICE("watchdog"); +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) +{ + nonseekable_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); + /* Fall */ + + 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, + .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, + .owner = THIS_MODULE, + .of_match_table = riowd_match, + }, + .probe = riowd_probe, + .remove = riowd_remove, +}; + +module_platform_driver(riowd_driver); diff --git a/drivers/watchdog/rm9k_wdt.c b/drivers/watchdog/rm9k_wdt.c deleted file mode 100644 index 5c921e47156..00000000000 --- a/drivers/watchdog/rm9k_wdt.c +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx - * chips. - * - * Copyright (C) 2004 by Basler Vision Technologies AG - * Author: Thomas Koeller <thomas.koeller@baslerweb.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/platform_device.h> -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/interrupt.h> -#include <linux/fs.h> -#include <linux/reboot.h> -#include <linux/notifier.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <asm/io.h> -#include <asm/atomic.h> -#include <asm/processor.h> -#include <asm/uaccess.h> -#include <asm/system.h> -#include <asm/rm9k-ocd.h> - -#include <rm9k_wdt.h> - - -#define CLOCK 125000000 -#define MAX_TIMEOUT_SECONDS 32 -#define CPCCR 0x0080 -#define CPGIG1SR 0x0044 -#define CPGIG1ER 0x0054 - - -/* Function prototypes */ -static irqreturn_t wdt_gpi_irqhdl(int, void *); -static void wdt_gpi_start(void); -static void wdt_gpi_stop(void); -static void wdt_gpi_set_timeout(unsigned int); -static int wdt_gpi_open(struct inode *, struct file *); -static int wdt_gpi_release(struct inode *, struct file *); -static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *); -static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long); -static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *); -static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int); -static int __init wdt_gpi_probe(struct device *); -static int __exit wdt_gpi_remove(struct device *); - - -static const char wdt_gpi_name[] = "wdt_gpi"; -static atomic_t opencnt; -static int expect_close; -static int locked; - - -/* These are set from device resources */ -static void __iomem * wd_regs; -static unsigned int wd_irq, wd_ctr; - - -/* Module arguments */ -static int timeout = MAX_TIMEOUT_SECONDS; -module_param(timeout, int, 0444); -MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); - -static unsigned long resetaddr = 0xbffdc200; -module_param(resetaddr, ulong, 0444); -MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset"); - -static unsigned long flagaddr = 0xbffdc104; -module_param(flagaddr, ulong, 0444); -MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to"); - -static int powercycle; -module_param(powercycle, bool, 0444); -MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires"); - -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, 0444); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started"); - - -/* Kernel interfaces */ -static const struct file_operations fops = { - .owner = THIS_MODULE, - .open = wdt_gpi_open, - .release = wdt_gpi_release, - .write = wdt_gpi_write, - .unlocked_ioctl = wdt_gpi_ioctl, -}; - -static struct miscdevice miscdev = { - .minor = WATCHDOG_MINOR, - .name = wdt_gpi_name, - .fops = &fops, -}; - -static struct notifier_block wdt_gpi_shutdown = { - .notifier_call = wdt_gpi_notify, -}; - - -/* Interrupt handler */ -static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt) -{ - if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1)) - return IRQ_NONE; - __raw_writel(0x1, wd_regs + 0x0008); - - - printk(KERN_CRIT "%s: watchdog expired - resetting system\n", - wdt_gpi_name); - - *(volatile char *) flagaddr |= 0x01; - *(volatile char *) resetaddr = powercycle ? 0x01 : 0x2; - iob(); - while (1) - cpu_relax(); -} - - -/* Watchdog functions */ -static void wdt_gpi_start(void) -{ - u32 reg; - - lock_titan_regs(); - reg = titan_readl(CPGIG1ER); - titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER); - iob(); - unlock_titan_regs(); -} - -static void wdt_gpi_stop(void) -{ - u32 reg; - - lock_titan_regs(); - reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); - titan_writel(reg, CPCCR); - reg = titan_readl(CPGIG1ER); - titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER); - iob(); - unlock_titan_regs(); -} - -static void wdt_gpi_set_timeout(unsigned int to) -{ - u32 reg; - const u32 wdval = (to * CLOCK) & ~0x0000000f; - - lock_titan_regs(); - reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4)); - titan_writel(reg, CPCCR); - wmb(); - __raw_writel(wdval, wd_regs + 0x0000); - wmb(); - titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR); - wmb(); - titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR); - iob(); - unlock_titan_regs(); -} - - -/* /dev/watchdog operations */ -static int wdt_gpi_open(struct inode *inode, struct file *file) -{ - int res; - - if (unlikely(atomic_dec_if_positive(&opencnt) < 0)) - return -EBUSY; - - expect_close = 0; - if (locked) { - module_put(THIS_MODULE); - free_irq(wd_irq, &miscdev); - locked = 0; - } - - res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED, - wdt_gpi_name, &miscdev); - if (unlikely(res)) - return res; - - wdt_gpi_set_timeout(timeout); - wdt_gpi_start(); - - printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n", - wdt_gpi_name, timeout); - return nonseekable_open(inode, file); -} - -static int wdt_gpi_release(struct inode *inode, struct file *file) -{ - if (nowayout) { - printk(KERN_INFO "%s: no way out - watchdog left running\n", - wdt_gpi_name); - __module_get(THIS_MODULE); - locked = 1; - } else { - if (expect_close) { - wdt_gpi_stop(); - free_irq(wd_irq, &miscdev); - printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name); - } else { - printk(KERN_CRIT "%s: unexpected close() -" - " watchdog left running\n", - wdt_gpi_name); - wdt_gpi_set_timeout(timeout); - __module_get(THIS_MODULE); - locked = 1; - } - } - - atomic_inc(&opencnt); - return 0; -} - -static ssize_t -wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o) -{ - char val; - - wdt_gpi_set_timeout(timeout); - expect_close = (s > 0) && !get_user(val, d) && (val == 'V'); - return s ? 1 : 0; -} - -static long -wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg) -{ - long res = -ENOTTY; - const long size = _IOC_SIZE(cmd); - int stat; - void __user *argp = (void __user *)arg; - static struct watchdog_info wdinfo = { - .identity = "RM9xxx/GPI watchdog", - .firmware_version = 0, - .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING - }; - - if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE)) - return -ENOTTY; - - if ((_IOC_DIR(cmd) & _IOC_READ) - && !access_ok(VERIFY_WRITE, arg, size)) - return -EFAULT; - - if ((_IOC_DIR(cmd) & _IOC_WRITE) - && !access_ok(VERIFY_READ, arg, size)) - return -EFAULT; - - expect_close = 0; - - switch (cmd) { - case WDIOC_GETSUPPORT: - wdinfo.options = nowayout ? - WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING : - WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE; - res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size; - break; - - case WDIOC_GETSTATUS: - break; - - case WDIOC_GETBOOTSTATUS: - stat = (*(volatile char *) flagaddr & 0x01) - ? WDIOF_CARDRESET : 0; - res = __copy_to_user(argp, &stat, size) ? - -EFAULT : size; - break; - - case WDIOC_SETOPTIONS: - break; - - case WDIOC_KEEPALIVE: - wdt_gpi_set_timeout(timeout); - res = size; - break; - - case WDIOC_SETTIMEOUT: - { - int val; - if (unlikely(__copy_from_user(&val, argp, size))) { - res = -EFAULT; - break; - } - - if (val > MAX_TIMEOUT_SECONDS) - val = MAX_TIMEOUT_SECONDS; - timeout = val; - wdt_gpi_set_timeout(val); - res = size; - printk(KERN_INFO "%s: timeout set to %u seconds\n", - wdt_gpi_name, timeout); - } - break; - - case WDIOC_GETTIMEOUT: - res = __copy_to_user(argp, &timeout, size) ? - -EFAULT : size; - break; - } - - return res; -} - - -/* Shutdown notifier */ -static int -wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - wdt_gpi_stop(); - - return NOTIFY_DONE; -} - - -/* Init & exit procedures */ -static const struct resource * -wdt_gpi_get_resource(struct platform_device *pdv, const char *name, - unsigned int type) -{ - char buf[80]; - if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf) - return NULL; - return platform_get_resource_byname(pdv, type, buf); -} - -/* No hotplugging on the platform bus - use __init */ -static int __init wdt_gpi_probe(struct device *dev) -{ - int res; - struct platform_device * const pdv = to_platform_device(dev); - const struct resource - * const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS, - IORESOURCE_MEM), - * const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ, - IORESOURCE_IRQ), - * const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER, - 0); - - if (unlikely(!rr || !ri || !rc)) - return -ENXIO; - - wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start); - if (unlikely(!wd_regs)) - return -ENOMEM; - wd_irq = ri->start; - wd_ctr = rc->start; - res = misc_register(&miscdev); - if (res) - iounmap(wd_regs); - else - register_reboot_notifier(&wdt_gpi_shutdown); - return res; -} - -static int __exit wdt_gpi_remove(struct device *dev) -{ - int res; - - unregister_reboot_notifier(&wdt_gpi_shutdown); - res = misc_deregister(&miscdev); - iounmap(wd_regs); - wd_regs = NULL; - return res; -} - - -/* Device driver init & exit */ -static struct device_driver wdt_gpi_driver = { - .name = (char *) wdt_gpi_name, - .bus = &platform_bus_type, - .owner = THIS_MODULE, - .probe = wdt_gpi_probe, - .remove = __exit_p(wdt_gpi_remove), - .shutdown = NULL, - .suspend = NULL, - .resume = NULL, -}; - -static int __init wdt_gpi_init_module(void) -{ - atomic_set(&opencnt, 1); - if (timeout > MAX_TIMEOUT_SECONDS) - timeout = MAX_TIMEOUT_SECONDS; - return driver_register(&wdt_gpi_driver); -} - -static void __exit wdt_gpi_cleanup_module(void) -{ - driver_unregister(&wdt_gpi_driver); -} - -module_init(wdt_gpi_init_module); -module_exit(wdt_gpi_cleanup_module); - -MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>"); -MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices"); -MODULE_VERSION("0.1"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - diff --git a/drivers/watchdog/rt2880_wdt.c b/drivers/watchdog/rt2880_wdt.c new file mode 100644 index 00000000000..d92c2d5859c --- /dev/null +++ b/drivers/watchdog/rt2880_wdt.c @@ -0,0 +1,206 @@ +/* + * Ralink RT288x/RT3xxx/MT76xx built-in hardware watchdog timer + * + * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2013 John Crispin <blogic@openwrt.org> + * + * This driver was based on: drivers/watchdog/softdog.c + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#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 <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 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 struct watchdog_info rt288x_wdt_info = { + .identity = "Ralink Watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, +}; + +static 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 resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rt288x_wdt_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rt288x_wdt_base)) + return PTR_ERR(rt288x_wdt_base); + + rt288x_wdt_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rt288x_wdt_clk)) + return PTR_ERR(rt288x_wdt_clk); + + device_reset(&pdev->dev); + + rt288x_wdt_freq = clk_get_rate(rt288x_wdt_clk) / RALINK_WDT_PRESCALE; + + rt288x_wdt_dev.dev = &pdev->dev; + rt288x_wdt_dev.bootstatus = rt288x_wdt_bootcause(); + + rt288x_wdt_dev.max_timeout = (0xfffful / rt288x_wdt_freq); + rt288x_wdt_dev.timeout = rt288x_wdt_dev.max_timeout; + + watchdog_set_nowayout(&rt288x_wdt_dev, nowayout); + + ret = watchdog_register_device(&rt288x_wdt_dev); + if (!ret) + dev_info(&pdev->dev, "Initialized\n"); + + return 0; +} + +static int rt288x_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&rt288x_wdt_dev); + + return 0; +} + +static void rt288x_wdt_shutdown(struct platform_device *pdev) +{ + rt288x_wdt_stop(&rt288x_wdt_dev); +} + +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, + .remove = rt288x_wdt_remove, + .shutdown = rt288x_wdt_shutdown, + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + .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/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 7645e881215..7c6ccd071ba 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -6,7 +6,7 @@ * S3C2410 Watchdog Timer Support * * Based on, softdog.c by Alan Cox, - * (c) Copyright 1996 Alan Cox <alan@redhat.com> + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,118 +21,246 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Changelog: - * 05-Oct-2004 BJD Added semaphore init to stop crashes on open - * Fixed tmr_count / wdt_count confusion - * Added configurable debug - * - * 11-Jan-2005 BJD Fixed divide-by-2 in timeout code - * - * 25-Jan-2005 DA Added suspend/resume support - * Replaced reboot notifier with .shutdown method - * - * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA */ +#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/init.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/clk.h> - -#include <asm/uaccess.h> -#include <asm/io.h> - -#include <asm/arch/map.h> - -#undef S3C_VA_WATCHDOG -#define S3C_VA_WATCHDOG (0) - -#include <asm/plat-s3c/regs-watchdog.h> - -#define PFX "s3c2410-wdt: " +#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/mfd/syscon.h> +#include <linux/regmap.h> + +#define S3C2410_WTCON 0x00 +#define S3C2410_WTDAT 0x04 +#define S3C2410_WTCNT 0x08 + +#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_PRESCALE(x) ((x) << 8) +#define S3C2410_WTCON_PRESCALE_MASK (0xff << 8) #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) -static int nowayout = WATCHDOG_NOWAYOUT; -static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; +#define EXYNOS5_RST_STAT_REG_OFFSET 0x0404 +#define EXYNOS5_WDT_DISABLE_REG_OFFSET 0x0408 +#define EXYNOS5_WDT_MASK_RESET_REG_OFFSET 0x040c +#define QUIRK_HAS_PMU_CONFIG (1 << 0) +#define QUIRK_HAS_RST_STAT (1 << 1) + +/* These quirks require that we have a PMU register map */ +#define QUIRKS_HAVE_PMUREG (QUIRK_HAS_PMU_CONFIG | \ + QUIRK_HAS_RST_STAT) + +static bool nowayout = WATCHDOG_NOWAYOUT; +static int tmr_margin; static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; -static int soft_noboot = 0; -static int debug = 0; +static int soft_noboot; +static int debug; module_param(tmr_margin, int, 0); module_param(tmr_atboot, int, 0); -module_param(nowayout, int, 0); +module_param(nowayout, bool, 0); module_param(soft_noboot, int, 0); module_param(debug, int, 0); -MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); - -MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); +MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" + __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); +MODULE_PARM_DESC(tmr_atboot, + "Watchdog is started at boot time if set to 1, default=" + __MODULE_STRING(CONFIG_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)"); +MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug (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_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. + * @quirks: A bitfield of quirks. + */ -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +struct s3c2410_wdt_variant { + int disable_reg; + int mask_reset_reg; + int mask_bit; + int rst_stat_reg; + int rst_stat_bit; + u32 quirks; +}; -MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); +struct s3c2410_wdt { + struct device *dev; + struct clk *clock; + 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; + struct s3c2410_wdt_variant *drv_data; + struct regmap *pmureg; +}; -MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); +static const struct s3c2410_wdt_variant drv_data_s3c2410 = { + .quirks = 0 +}; +#ifdef CONFIG_OF +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_PMU_CONFIG | QUIRK_HAS_RST_STAT, +}; -typedef enum close_state { - CLOSE_STATE_NOT, - CLOSE_STATE_ALLOW=0x4021 -} close_state_t; +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_PMU_CONFIG | QUIRK_HAS_RST_STAT, +}; -static DECLARE_MUTEX(open_lock); +static const struct of_device_id s3c2410_wdt_match[] = { + { .compatible = "samsung,s3c2410-wdt", + .data = &drv_data_s3c2410 }, + { .compatible = "samsung,exynos5250-wdt", + .data = &drv_data_exynos5250 }, + { .compatible = "samsung,exynos5420-wdt", + .data = &drv_data_exynos5420 }, + {}, +}; +MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); +#endif -static struct device *wdt_dev; /* platform device attached to */ -static struct resource *wdt_mem; -static struct resource *wdt_irq; -static struct clk *wdt_clock; -static void __iomem *wdt_base; -static unsigned int wdt_count; -static close_state_t allow_close; +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); /* watchdog control routines */ -#define DBG(msg...) do { \ - if (debug) \ - printk(KERN_INFO msg); \ - } while(0) +#define DBG(fmt, ...) \ +do { \ + if (debug) \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) /* functions */ -static int s3c2410wdt_keepalive(void) +static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb) +{ + return container_of(nb, struct s3c2410_wdt, freq_transition); +} + +static int s3c2410wdt_mask_and_disable_reset(struct s3c2410_wdt *wdt, bool mask) { - writel(wdt_count, wdt_base + S3C2410_WTCNT); + int ret; + u32 mask_val = 1 << wdt->drv_data->mask_bit; + u32 val = 0; + + /* No need to do anything if no PMU CONFIG needed */ + if (!(wdt->drv_data->quirks & QUIRK_HAS_PMU_CONFIG)) + return 0; + + if (mask) + val = mask_val; + + ret = regmap_update_bits(wdt->pmureg, + wdt->drv_data->disable_reg, + mask_val, val); + if (ret < 0) + goto error; + + ret = regmap_update_bits(wdt->pmureg, + wdt->drv_data->mask_reset_reg, + mask_val, val); + error: + if (ret < 0) + dev_err(wdt->dev, "failed to update reg(%d)\n", ret); + + return ret; +} + +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 int s3c2410wdt_stop(void) +static void __s3c2410wdt_stop(struct s3c2410_wdt *wdt) { unsigned long wtcon; - wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon = readl(wdt->reg_base + S3C2410_WTCON); wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); - writel(wtcon, wdt_base + S3C2410_WTCON); + 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(void) +static int s3c2410wdt_start(struct watchdog_device *wdd) { unsigned long wtcon; + struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); - s3c2410wdt_stop(); + spin_lock(&wdt->lock); - wtcon = readl(wdt_base + S3C2410_WTCON); + __s3c2410wdt_stop(wdt); + + wtcon = readl(wdt->reg_base + S3C2410_WTCON); wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; if (soft_noboot) { @@ -143,19 +271,26 @@ static int s3c2410wdt_start(void) wtcon |= S3C2410_WTCON_RSTEN; } - DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", - __func__, wdt_count, wtcon); + DBG("%s: count=0x%08x, wtcon=%08lx\n", + __func__, wdt->count, wtcon); - writel(wdt_count, wdt_base + S3C2410_WTDAT); - writel(wdt_count, wdt_base + S3C2410_WTCNT); - writel(wtcon, wdt_base + S3C2410_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 int s3c2410wdt_set_heartbeat(int timeout) +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 timeout) { - unsigned int freq = clk_get_rate(wdt_clock); + struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); + unsigned long freq = clk_get_rate(wdt->clock); unsigned int count; unsigned int divisor = 1; unsigned long wtcon; @@ -163,10 +298,10 @@ static int s3c2410wdt_set_heartbeat(int timeout) if (timeout < 1) return -EINVAL; - freq /= 128; + freq = DIV_ROUND_UP(freq, 128); count = timeout * freq; - DBG("%s: count=%d, timeout=%d, freq=%d\n", + DBG("%s: count=%d, timeout=%d, freq=%lu\n", __func__, count, timeout, freq); /* if the count is bigger than the watchdog register, @@ -175,389 +310,400 @@ static int s3c2410wdt_set_heartbeat(int timeout) */ if (count >= 0x10000) { - for (divisor = 1; divisor <= 0x100; divisor++) { - if ((count / divisor) < 0x10000) - break; - } + divisor = DIV_ROUND_UP(count, 0xffff); - if ((count / divisor) >= 0x10000) { - dev_err(wdt_dev, "timeout %d too big\n", timeout); + if (divisor > 0x100) { + dev_err(wdt->dev, "timeout %d too big\n", timeout); return -EINVAL; } } - tmr_margin = timeout; - DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", - __func__, timeout, divisor, count, count/divisor); + __func__, timeout, divisor, count, DIV_ROUND_UP(count, divisor)); - count /= divisor; - wdt_count = count; + count = DIV_ROUND_UP(count, divisor); + wdt->count = count; /* update the pre-scaler */ - wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon = readl(wdt->reg_base + S3C2410_WTCON); wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); - writel(count, wdt_base + S3C2410_WTDAT); - writel(wtcon, wdt_base + S3C2410_WTCON); + writel(count, wdt->reg_base + S3C2410_WTDAT); + writel(wtcon, wdt->reg_base + S3C2410_WTCON); + + wdd->timeout = (count * divisor) / freq; return 0; } -/* - * /dev/watchdog handling - */ +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) -static int s3c2410wdt_open(struct inode *inode, struct file *file) -{ - if(down_trylock(&open_lock)) - return -EBUSY; +static const struct watchdog_info s3c2410_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "S3C2410 Watchdog", +}; - if (nowayout) - __module_get(THIS_MODULE); +static struct watchdog_ops s3c2410wdt_ops = { + .owner = THIS_MODULE, + .start = s3c2410wdt_start, + .stop = s3c2410wdt_stop, + .ping = s3c2410wdt_keepalive, + .set_timeout = s3c2410wdt_set_heartbeat, +}; - allow_close = CLOSE_STATE_NOT; +static struct watchdog_device s3c2410_wdd = { + .info = &s3c2410_wdt_ident, + .ops = &s3c2410wdt_ops, + .timeout = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME, +}; - /* start the timer */ - s3c2410wdt_start(); - return nonseekable_open(inode, file); -} +/* interrupt handler code */ -static int s3c2410wdt_release(struct inode *inode, struct file *file) +static irqreturn_t s3c2410wdt_irq(int irqno, void *param) { - /* - * Shut off the timer. - * Lock it in if it's a module and we set nowayout - */ + struct s3c2410_wdt *wdt = platform_get_drvdata(param); - if (allow_close == CLOSE_STATE_ALLOW) { - s3c2410wdt_stop(); - } else { - dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n"); - s3c2410wdt_keepalive(); - } + dev_info(wdt->dev, "watchdog timer expired (irq)\n"); - allow_close = CLOSE_STATE_NOT; - up(&open_lock); - return 0; + s3c2410wdt_keepalive(&wdt->wdt_device); + return IRQ_HANDLED; } -static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) +#ifdef CONFIG_ARM_S3C24XX_CPUFREQ + +static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) { - /* - * Refresh the timer. - */ - if(len) { - if (!nowayout) { - size_t i; - - /* In case it was set long ago */ - allow_close = CLOSE_STATE_NOT; - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - allow_close = CLOSE_STATE_ALLOW; - } - } + int ret; + struct s3c2410_wdt *wdt = freq_to_wdt(nb); - s3c2410wdt_keepalive(); - } - return len; -} + if (!s3c2410wdt_is_running(wdt)) + goto done; -#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE + 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. + */ -static struct watchdog_info s3c2410_wdt_ident = { - .options = OPTIONS, - .firmware_version = 0, - .identity = "S3C2410 Watchdog", -}; + 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); -static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_margin; + if (ret >= 0) + s3c2410wdt_start(&wdt->wdt_device); + else + goto err; + } - switch (cmd) { - default: - return -ENOTTY; +done: + return 0; - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &s3c2410_wdt_ident, - sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; + err: + dev_err(wdt->dev, "cannot set new value for timeout %d\n", + wdt->wdt_device.timeout); + return ret; +} - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); +static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt) +{ + wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition; - case WDIOC_KEEPALIVE: - s3c2410wdt_keepalive(); - return 0; + return cpufreq_register_notifier(&wdt->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} - case WDIOC_SETTIMEOUT: - if (get_user(new_margin, p)) - return -EFAULT; +static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) +{ + wdt->freq_transition.notifier_call = s3c2410wdt_cpufreq_transition; - if (s3c2410wdt_set_heartbeat(new_margin)) - return -EINVAL; + cpufreq_unregister_notifier(&wdt->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +} - s3c2410wdt_keepalive(); - return put_user(tmr_margin, p); +#else - case WDIOC_GETTIMEOUT: - return put_user(tmr_margin, p); - } +static inline int s3c2410wdt_cpufreq_register(struct s3c2410_wdt *wdt) +{ + return 0; } -/* kernel interface */ +static inline void s3c2410wdt_cpufreq_deregister(struct s3c2410_wdt *wdt) +{ +} +#endif -static const struct file_operations s3c2410wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = s3c2410wdt_write, - .ioctl = s3c2410wdt_ioctl, - .open = s3c2410wdt_open, - .release = s3c2410wdt_release, -}; +static inline unsigned int s3c2410wdt_get_bootstatus(struct s3c2410_wdt *wdt) +{ + unsigned int rst_stat; + int ret; -static struct miscdevice s3c2410wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &s3c2410wdt_fops, -}; + if (!(wdt->drv_data->quirks & QUIRK_HAS_RST_STAT)) + return 0; -/* interrupt handler code */ + 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; -static irqreturn_t s3c2410wdt_irq(int irqno, void *param) -{ - dev_info(wdt_dev, "watchdog timer expired (irq)\n"); + return 0; +} - s3c2410wdt_keepalive(); - return IRQ_HANDLED; +/* s3c2410_get_wdt_driver_data */ +static inline struct s3c2410_wdt_variant * +get_wdt_drv_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(s3c2410_wdt_match, pdev->dev.of_node); + return (struct s3c2410_wdt_variant *)match->data; + } else { + return (struct s3c2410_wdt_variant *) + platform_get_device_id(pdev)->driver_data; + } } -/* device interface */ static int s3c2410wdt_probe(struct platform_device *pdev) { - struct resource *res; struct device *dev; + struct s3c2410_wdt *wdt; + struct resource *wdt_mem; + struct resource *wdt_irq; unsigned int wtcon; int started = 0; int ret; - int size; DBG("%s: probe=%p\n", __func__, pdev); dev = &pdev->dev; - wdt_dev = &pdev->dev; - /* get the memory region for the watchdog timer */ - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(dev, "no memory resource specified\n"); - return -ENOENT; + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->dev = &pdev->dev; + spin_lock_init(&wdt->lock); + wdt->wdt_device = s3c2410_wdd; + + wdt->drv_data = get_wdt_drv_data(pdev); + 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); + } } - size = (res->end-res->start)+1; - wdt_mem = request_mem_region(res->start, size, pdev->name); - if (wdt_mem == NULL) { - dev_err(dev, "failed to get memory region\n"); + wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (wdt_irq == NULL) { + dev_err(dev, "no irq resource specified\n"); ret = -ENOENT; - goto err_req; + goto err; } - wdt_base = ioremap(res->start, size); - if (wdt_base == 0) { - dev_err(dev, "failed to ioremap() region\n"); - ret = -EINVAL; - goto err_req; + /* get the memory region for the watchdog timer */ + wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->reg_base = devm_ioremap_resource(dev, wdt_mem); + if (IS_ERR(wdt->reg_base)) { + ret = PTR_ERR(wdt->reg_base); + goto err; } - DBG("probe: mapped wdt_base=%p\n", wdt_base); + DBG("probe: mapped reg_base=%p\n", wdt->reg_base); - wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (wdt_irq == NULL) { - dev_err(dev, "no irq resource specified\n"); - ret = -ENOENT; - goto err_map; + wdt->clock = devm_clk_get(dev, "watchdog"); + if (IS_ERR(wdt->clock)) { + dev_err(dev, "failed to find watchdog clock source\n"); + ret = PTR_ERR(wdt->clock); + goto err; } - ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev); - if (ret != 0) { - dev_err(dev, "failed to install irq (%d)\n", ret); - goto err_map; + ret = clk_prepare_enable(wdt->clock); + if (ret < 0) { + dev_err(dev, "failed to enable clock\n"); + return ret; } - wdt_clock = clk_get(&pdev->dev, "watchdog"); - if (IS_ERR(wdt_clock)) { - dev_err(dev, "failed to find watchdog clock source\n"); - ret = PTR_ERR(wdt_clock); - goto err_irq; + ret = s3c2410wdt_cpufreq_register(wdt); + if (ret < 0) { + dev_err(dev, "failed to register cpufreq\n"); + goto err_clk; } - clk_enable(wdt_clock); + watchdog_set_drvdata(&wdt->wdt_device, wdt); /* see if we can actually set the requested timer margin, and if * not, try the default value */ - if (s3c2410wdt_set_heartbeat(tmr_margin)) { - started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + watchdog_init_timeout(&wdt->wdt_device, tmr_margin, &pdev->dev); + ret = s3c2410wdt_set_heartbeat(&wdt->wdt_device, + wdt->wdt_device.timeout); + if (ret) { + started = s3c2410wdt_set_heartbeat(&wdt->wdt_device, + CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); - if (started == 0) { - dev_info(dev,"tmr_margin value out of range, default %d used\n", + if (started == 0) + dev_info(dev, + "tmr_margin value out of range, default %d used\n", CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); - } else { - dev_info(dev, "default timer value is out of range, cannot start\n"); - } + else + dev_info(dev, "default timer value is out of range, " + "cannot start\n"); + } + + ret = devm_request_irq(dev, wdt_irq->start, s3c2410wdt_irq, 0, + pdev->name, pdev); + if (ret != 0) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err_cpufreq; } - ret = misc_register(&s3c2410wdt_miscdev); + watchdog_set_nowayout(&wdt->wdt_device, nowayout); + + wdt->wdt_device.bootstatus = s3c2410wdt_get_bootstatus(wdt); + + ret = watchdog_register_device(&wdt->wdt_device); if (ret) { - dev_err(dev, "cannot register miscdev on minor=%d (%d)\n", - WATCHDOG_MINOR, ret); - goto err_clk; + dev_err(dev, "cannot register watchdog (%d)\n", ret); + goto err_cpufreq; } + ret = s3c2410wdt_mask_and_disable_reset(wdt, false); + if (ret < 0) + goto err_unregister; + if (tmr_atboot && started == 0) { dev_info(dev, "starting watchdog timer\n"); - s3c2410wdt_start(); + s3c2410wdt_start(&wdt->wdt_device); } else if (!tmr_atboot) { /* if we're not enabling the watchdog, then ensure it is * disabled if it has been left running from the bootloader * or other source */ - s3c2410wdt_stop(); + s3c2410wdt_stop(&wdt->wdt_device); } + platform_set_drvdata(pdev, wdt); + /* print out a statement of readiness */ - wtcon = readl(wdt_base + S3C2410_WTCON); + 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) ? "" : "dis", - (wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); - - return 0; + (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis", + (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); - err_clk: - clk_disable(wdt_clock); - clk_put(wdt_clock); + return 0; - err_irq: - free_irq(wdt_irq->start, pdev); + err_unregister: + watchdog_unregister_device(&wdt->wdt_device); - err_map: - iounmap(wdt_base); + err_cpufreq: + s3c2410wdt_cpufreq_deregister(wdt); - err_req: - release_resource(wdt_mem); - kfree(wdt_mem); + err_clk: + clk_disable_unprepare(wdt->clock); + err: return ret; } static int s3c2410wdt_remove(struct platform_device *dev) { - release_resource(wdt_mem); - kfree(wdt_mem); - wdt_mem = NULL; + int ret; + struct s3c2410_wdt *wdt = platform_get_drvdata(dev); + + ret = s3c2410wdt_mask_and_disable_reset(wdt, true); + if (ret < 0) + return ret; - free_irq(wdt_irq->start, dev); - wdt_irq = NULL; + watchdog_unregister_device(&wdt->wdt_device); - clk_disable(wdt_clock); - clk_put(wdt_clock); - wdt_clock = NULL; + s3c2410wdt_cpufreq_deregister(wdt); + + clk_disable_unprepare(wdt->clock); - iounmap(wdt_base); - misc_deregister(&s3c2410wdt_miscdev); return 0; } static void s3c2410wdt_shutdown(struct platform_device *dev) { - s3c2410wdt_stop(); -} + struct s3c2410_wdt *wdt = platform_get_drvdata(dev); -#ifdef CONFIG_PM + s3c2410wdt_mask_and_disable_reset(wdt, true); -static unsigned long wtcon_save; -static unsigned long wtdat_save; + s3c2410wdt_stop(&wdt->wdt_device); +} -static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) +#ifdef CONFIG_PM_SLEEP + +static int s3c2410wdt_suspend(struct device *dev) { + int ret; + struct s3c2410_wdt *wdt = dev_get_drvdata(dev); + /* Save watchdog state, and turn it off. */ - wtcon_save = readl(wdt_base + S3C2410_WTCON); - wtdat_save = readl(wdt_base + S3C2410_WTDAT); + wdt->wtcon_save = readl(wdt->reg_base + S3C2410_WTCON); + wdt->wtdat_save = readl(wdt->reg_base + S3C2410_WTDAT); + + ret = s3c2410wdt_mask_and_disable_reset(wdt, true); + if (ret < 0) + return ret; /* Note that WTCNT doesn't need to be saved. */ - s3c2410wdt_stop(); + s3c2410wdt_stop(&wdt->wdt_device); return 0; } -static int s3c2410wdt_resume(struct platform_device *dev) +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); - writel(wtdat_save, wdt_base + S3C2410_WTDAT); - writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ - writel(wtcon_save, wdt_base + S3C2410_WTCON); + ret = s3c2410wdt_mask_and_disable_reset(wdt, false); + if (ret < 0) + return ret; - printk(KERN_INFO PFX "watchdog %sabled\n", - (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); + dev_info(dev, "watchdog %sabled\n", + (wdt->wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); return 0; } +#endif -#else -#define s3c2410wdt_suspend NULL -#define s3c2410wdt_resume NULL -#endif /* CONFIG_PM */ - +static 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, - .suspend = s3c2410wdt_suspend, - .resume = s3c2410wdt_resume, + .id_table = s3c2410_wdt_ids, .driver = { .owner = THIS_MODULE, .name = "s3c2410-wdt", + .pm = &s3c2410wdt_pm_ops, + .of_match_table = of_match_ptr(s3c2410_wdt_match), }, }; - -static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; - -static int __init watchdog_init(void) -{ - printk(banner); - return platform_driver_register(&s3c2410wdt_driver); -} - -static void __exit watchdog_exit(void) -{ - platform_driver_unregister(&s3c2410wdt_driver); -} - -module_init(watchdog_init); -module_exit(watchdog_exit); +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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sa1100_wdt.c b/drivers/watchdog/sa1100_wdt.c index 34a2b3b8180..e1d39a1e962 100644 --- a/drivers/watchdog/sa1100_wdt.c +++ b/drivers/watchdog/sa1100_wdt.c @@ -1,8 +1,8 @@ /* * Watchdog driver for the SA11x0/PXA2xx * - * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> - * Based on SoftDog driver by Alan Cox <alan@redhat.com> + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,8 +15,11 @@ * * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> * - * 27/11/2000 Initial release + * 27/11/2000 Initial release */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -25,19 +28,21 @@ #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> #ifdef CONFIG_ARCH_PXA -#include <asm/arch/pxa-regs.h> +#include <mach/regs-ost.h> #endif -#include <asm/hardware.h> -#include <asm/uaccess.h> - -#define OSCR_FREQ CLOCK_TICK_RATE +#include <mach/reset.h> +#include <mach/hardware.h> +static unsigned long oscr_freq; static unsigned long sa1100wdt_users; -static int pre_margin; +static unsigned int pre_margin; static int boot_status; /* @@ -45,14 +50,14 @@ static int boot_status; */ static int sa1100dog_open(struct inode *inode, struct file *file) { - if (test_and_set_bit(1,&sa1100wdt_users)) + if (test_and_set_bit(1, &sa1100wdt_users)) return -EBUSY; /* Activate SA1100 Watchdog timer */ - OSMR3 = OSCR + pre_margin; - OSSR = OSSR_M3; - OWER = OWER_WME; - OIER |= OIER_E3; + writel_relaxed(readl_relaxed(OSCR) + pre_margin, OSMR3); + writel_relaxed(OSSR_M3, OSSR); + writel_relaxed(OWER_WME, OWER); + writel_relaxed(readl_relaxed(OIER) | OIER_E3, OIER); return nonseekable_open(inode, file); } @@ -65,29 +70,29 @@ static int sa1100dog_open(struct inode *inode, struct file *file) */ static int sa1100dog_release(struct inode *inode, struct file *file) { - printk(KERN_CRIT "WATCHDOG: Device closed - timer will not stop\n"); - + 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) +static ssize_t sa1100dog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) { if (len) /* Refresh OSMR3 timer. */ - OSMR3 = OSCR + pre_margin; - + writel_relaxed(readl_relaxed(OSCR) + pre_margin, OSMR3); return len; } -static struct watchdog_info ident = { - .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT + | WDIOF_KEEPALIVEPING, .identity = "SA1100/PXA255 Watchdog", + .firmware_version = 1, }; -static int sa1100dog_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long sa1100dog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int ret = -ENOTTY; int time; @@ -108,44 +113,42 @@ static int sa1100dog_ioctl(struct inode *inode, struct file *file, ret = put_user(boot_status, p); break; + case WDIOC_KEEPALIVE: + writel_relaxed(readl_relaxed(OSCR) + pre_margin, OSMR3); + ret = 0; + break; + case WDIOC_SETTIMEOUT: ret = get_user(time, p); if (ret) break; - if (time <= 0 || time > 255) { + if (time <= 0 || (oscr_freq * (long long)time >= 0xffffffff)) { ret = -EINVAL; break; } - pre_margin = OSCR_FREQ * time; - OSMR3 = OSCR + pre_margin; + pre_margin = oscr_freq * time; + writel_relaxed(readl_relaxed(OSCR) + pre_margin, OSMR3); /*fall through*/ case WDIOC_GETTIMEOUT: - ret = put_user(pre_margin / OSCR_FREQ, p); - break; - - case WDIOC_KEEPALIVE: - OSMR3 = OSCR + pre_margin; - ret = 0; + ret = put_user(pre_margin / oscr_freq, p); break; } return ret; } -static const struct file_operations sa1100dog_fops = -{ +static const struct file_operations sa1100dog_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = sa1100dog_write, - .ioctl = sa1100dog_ioctl, + .unlocked_ioctl = sa1100dog_ioctl, .open = sa1100dog_open, .release = sa1100dog_release, }; -static struct miscdevice sa1100dog_miscdev = -{ +static struct miscdevice sa1100dog_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &sa1100dog_fops, @@ -157,18 +160,21 @@ static int __init sa1100dog_init(void) { int ret; + oscr_freq = get_clock_tick_rate(); + /* * Read the reset status, and save it for later. If * we suspend, RCSR will be cleared, and the watchdog * reset reason will be lost. */ - boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0; - pre_margin = OSCR_FREQ * margin; + boot_status = (reset_status & RESET_STATUS_WATCHDOG) ? + WDIOF_CARDRESET : 0; + pre_margin = oscr_freq * margin; ret = misc_register(&sa1100dog_miscdev); if (ret == 0) - printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", - margin); + pr_info("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", + margin); return ret; } @@ -187,4 +193,3 @@ module_param(margin, int, 0); MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sb_wdog.c b/drivers/watchdog/sb_wdog.c index b9443143369..3abae50773b 100644 --- a/drivers/watchdog/sb_wdog.c +++ b/drivers/watchdog/sb_wdog.c @@ -1,7 +1,7 @@ /* * Watchdog driver for SiByte SB1 SoCs * - * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@onstor.com> + * 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 @@ -35,14 +35,17 @@ * 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@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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> @@ -57,6 +60,7 @@ #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 @@ -65,8 +69,10 @@ */ void sbwdog_set(char __iomem *wdog, unsigned long t) { - __raw_writeb(0, wdog - 0x10); - __raw_writeq(t & 0x7fffffUL, wdog); + spin_lock(&sbwd_lock); + __raw_writeb(0, wdog); + __raw_writeq(t & 0x7fffffUL, wdog - 0x10); + spin_unlock(&sbwd_lock); } /* @@ -77,7 +83,9 @@ void sbwdog_set(char __iomem *wdog, unsigned long t) */ 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 */ @@ -86,8 +94,9 @@ 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 struct watchdog_info ident = { - .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .identity = "SiByte Watchdog", }; @@ -97,9 +106,8 @@ static struct watchdog_info ident = { static int sbwdog_open(struct inode *inode, struct file *file) { nonseekable_open(inode, file); - if (test_and_set_bit(0, &sbwdog_gate)) { + if (test_and_set_bit(0, &sbwdog_gate)) return -EBUSY; - } __module_get(THIS_MODULE); /* @@ -120,7 +128,7 @@ static int sbwdog_release(struct inode *inode, struct file *file) __raw_writeb(0, user_dog); module_put(THIS_MODULE); } else { - printk(KERN_CRIT "%s: Unexpected close, not stopping watchdog!\n", + pr_crit("%s: Unexpected close, not stopping watchdog!\n", ident.identity); sbwdog_pet(user_dog); } @@ -147,12 +155,10 @@ static ssize_t sbwdog_write(struct file *file, const char __user *data, for (i = 0; i != len; i++) { char c; - if (get_user(c, data + i)) { + if (get_user(c, data + i)) return -EFAULT; - } - if (c == 'V') { + if (c == 'V') expect_close = 42; - } } sbwdog_pet(user_dog); } @@ -160,8 +166,8 @@ static ssize_t sbwdog_write(struct file *file, const char __user *data, return len; } -static int sbwdog_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long sbwdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int ret = -ENOTTY; unsigned long time; @@ -178,11 +184,15 @@ static int sbwdog_ioctl(struct inode *inode, struct file *file, 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) { + if (ret) break; - } time *= 1000000; if (time > 0x7fffffUL) { @@ -198,12 +208,7 @@ static int sbwdog_ioctl(struct inode *inode, struct file *file, * get the remaining count from the ... count register * which is 1*8 before the config register */ - ret = put_user(__raw_readq(user_dog - 8) / 1000000, p); - break; - - case WDIOC_KEEPALIVE: - sbwdog_pet(user_dog); - ret = 0; + ret = put_user((u32)__raw_readq(user_dog - 8) / 1000000, p); break; } return ret; @@ -212,8 +217,8 @@ static int sbwdog_ioctl(struct inode *inode, struct file *file, /* * Notifier for system down */ -static int -sbwdog_notify_sys(struct notifier_block *this, unsigned long code, void *erf) +static int sbwdog_notify_sys(struct notifier_block *this, unsigned long code, + void *erf) { if (code == SYS_DOWN || code == SYS_HALT) { /* @@ -226,18 +231,16 @@ sbwdog_notify_sys(struct notifier_block *this, unsigned long code, void *erf) return NOTIFY_DONE; } -static const struct file_operations sbwdog_fops = -{ +static const struct file_operations sbwdog_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = sbwdog_write, - .ioctl = sbwdog_ioctl, + .unlocked_ioctl = sbwdog_ioctl, .open = sbwdog_open, .release = sbwdog_release, }; -static struct miscdevice sbwdog_miscdev = -{ +static struct miscdevice sbwdog_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &sbwdog_fops, @@ -267,13 +270,13 @@ irqreturn_t sbwdog_interrupt(int irq, void *addr) /* * if it's the second watchdog timer, it's for those users */ - if (wd_cfg_reg == user_dog) { - printk(KERN_CRIT - "%s in danger of initiating system reset in %ld.%01ld seconds\n", - ident.identity, wd_init / 1000000, (wd_init / 100000) % 10); - } else { + 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); @@ -289,27 +292,33 @@ static int __init sbwdog_init(void) */ ret = register_reboot_notifier(&sbwdog_notifier); if (ret) { - printk (KERN_ERR "%s: cannot register reboot notifier (err=%d)\n", - ident.identity, ret); + pr_err("%s: cannot register reboot notifier (err=%d)\n", + ident.identity, ret); return ret; } /* * get the resources */ - ret = misc_register(&sbwdog_miscdev); - if (ret == 0) { - printk(KERN_INFO "%s: timeout is %ld.%ld secs\n", ident.identity, - timeout / 1000000, (timeout / 100000) % 10); - } - ret = request_irq(1, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, + ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED, ident.identity, (void *)user_dog); if (ret) { - printk(KERN_ERR "%s: failed to request irq 1 - %d\n", ident.identity, - ret); - misc_deregister(&sbwdog_miscdev); + 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; } @@ -317,35 +326,34 @@ static int __init sbwdog_init(void) 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@onstor.com>"); +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)"); + "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); /* * 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) +void platform_wd_setup(void) { int ret; - ret = request_irq(0, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, + ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED, "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0)); if (ret) { - printk(KERN_CRIT "Watchdog IRQ zero(0) failed to be requested - %d\n", - 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 index ef76f01625e..2eef58a0cf0 100644 --- a/drivers/watchdog/sbc60xxwdt.c +++ b/drivers/watchdog/sbc60xxwdt.c @@ -1,7 +1,7 @@ /* * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x * - * Based on acquirewdt.c by Alan Cox. + * Based on acquirewdt.c by Alan Cox. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -16,19 +16,23 @@ * * 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 + * 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 + * 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 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 @@ -44,6 +48,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -56,10 +62,9 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> #define OUR_NAME "sbc60xxwdt" #define PFX OUR_NAME ": " @@ -94,13 +99,18 @@ MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)"); */ #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +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) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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(unsigned long); static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); @@ -117,15 +127,13 @@ static void wdt_timer_ping(unsigned long data) /* If we got a heartbeat pulse within the WDT_US_INTERVAL * we agree to ping the WDT */ - if(time_before(jiffies, next_heartbeat)) - { + 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 { - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); - } + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); } /* @@ -138,7 +146,7 @@ static void wdt_startup(void) /* Start the timer */ mod_timer(&timer, jiffies + WDT_INTERVAL); - printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); + pr_info("Watchdog timer is now enabled\n"); } static void wdt_turnoff(void) @@ -146,7 +154,7 @@ static void wdt_turnoff(void) /* Stop the timer */ del_timer(&timer); inb_p(wdt_stop); - printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); + pr_info("Watchdog timer is now disabled...\n"); } static void wdt_keepalive(void) @@ -159,40 +167,40 @@ static void wdt_keepalive(void) * /dev/watchdog handling */ -static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +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) - { + if (count) { + if (!nowayout) { size_t ofs; - /* note: just in case someone wrote the magic character - * five months ago... */ + /* 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++) - { + /* 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)) + if (get_user(c, buf + ofs)) return -EFAULT; - if(c == 'V') + if (c == 'V') wdt_expect_close = 42; } } - /* Well, anyhow someone wrote to us, we should return that favour */ + /* 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) +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)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; if (nowayout) @@ -203,78 +211,71 @@ static int fop_open(struct inode * inode, struct file * file) return nonseekable_open(inode, file); } -static int fop_close(struct inode * inode, struct file * file) +static int fop_close(struct inode *inode, struct file *file) { - if(wdt_expect_close == 42) + if (wdt_expect_close == 42) wdt_turnoff(); else { del_timer(&timer); - printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + 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 int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident= - { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "SBC60xx", }; - switch(cmd) + 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: { - default: - return -ENOTTY; - 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_KEEPALIVE: - wdt_keepalive(); - return 0; - 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; + int new_options, retval = -EINVAL; + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; } - case WDIOC_SETTIMEOUT: - { - int new_timeout; - - if(get_user(new_timeout, p)) - return -EFAULT; - - if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ - return -EINVAL; - - timeout = new_timeout; - wdt_keepalive(); - /* Fall through */ + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; } - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); + 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(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; } } @@ -284,7 +285,7 @@ static const struct file_operations wdt_fops = { .write = fop_write, .open = fop_open, .release = fop_close, - .ioctl = fop_ioctl, + .unlocked_ioctl = fop_ioctl, }; static struct miscdevice wdt_miscdev = { @@ -300,7 +301,7 @@ static struct miscdevice wdt_miscdev = { static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code==SYS_DOWN || code==SYS_HALT) + if (code == SYS_DOWN || code == SYS_HALT) wdt_turnoff(); return NOTIFY_DONE; } @@ -310,8 +311,7 @@ static int wdt_notify_sys(struct notifier_block *this, unsigned long code, * turn the timebomb registers off. */ -static struct notifier_block wdt_notifier= -{ +static struct notifier_block wdt_notifier = { .notifier_call = wdt_notify_sys, }; @@ -324,58 +324,48 @@ static void __exit sbc60xxwdt_unload(void) unregister_reboot_notifier(&wdt_notifier); if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) - release_region(wdt_stop,1); - release_region(wdt_start,1); + 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 */ - { + if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */ timeout = WATCHDOG_TIMEOUT; - printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", timeout); - } + } - if (!request_region(wdt_start, 1, "SBC 60XX WDT")) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_start); + 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")) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_stop); + 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) - { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_region2; } rc = misc_register(&wdt_miscdev); - if (rc) - { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - wdt_miscdev.minor, rc); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); goto err_out_reboot; } - - printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", + pr_info("WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); return 0; @@ -383,10 +373,10 @@ static int __init sbc60xxwdt_init(void) err_out_reboot: unregister_reboot_notifier(&wdt_notifier); err_out_region2: - if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) - release_region(wdt_stop,1); + if (wdt_stop != 0x45 && wdt_stop != wdt_start) + release_region(wdt_stop, 1); err_out_region1: - release_region(wdt_start,1); + release_region(wdt_start, 1); err_out: return rc; } @@ -397,4 +387,3 @@ module_exit(sbc60xxwdt_unload); MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>"); MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sbc7240_wdt.c b/drivers/watchdog/sbc7240_wdt.c index 4c8cefbd862..5f268add17c 100644 --- a/drivers/watchdog/sbc7240_wdt.c +++ b/drivers/watchdog/sbc7240_wdt.c @@ -16,6 +16,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/fs.h> #include <linux/init.h> #include <linux/ioport.h> @@ -27,12 +29,9 @@ #include <linux/reboot.h> #include <linux/types.h> #include <linux/watchdog.h> -#include <asm/atomic.h> -#include <asm/io.h> -#include <asm/system.h> -#include <asm/uaccess.h> - -#define SBC7240_PREFIX "sbc7240_wdt: " +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/atomic.h> #define SBC7240_ENABLE_PORT 0x443 #define SBC7240_DISABLE_PORT 0x043 @@ -47,8 +46,8 @@ MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=" __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default=" __MODULE_STRING(SBC7240_TIMEOUT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); +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 @@ -65,8 +64,7 @@ static void wdt_disable(void) /* disable the watchdog */ if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { inb_p(SBC7240_DISABLE_PORT); - printk(KERN_INFO SBC7240_PREFIX - "Watchdog timer is now disabled.\n"); + pr_info("Watchdog timer is now disabled\n"); } } @@ -75,23 +73,20 @@ static void wdt_enable(void) /* enable the watchdog */ if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { inb_p(SBC7240_ENABLE_PORT); - printk(KERN_INFO SBC7240_PREFIX - "Watchdog timer is now enabled.\n"); + pr_info("Watchdog timer is now enabled\n"); } } static int wdt_set_timeout(int t) { if (t < 1 || t > SBC7240_MAX_TIMEOUT) { - printk(KERN_ERR SBC7240_PREFIX - "timeout value must be 1<=x<=%d\n", - 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; - printk(KERN_INFO SBC7240_PREFIX "timeout set to %d seconds\n", t); + pr_info("timeout set to %d seconds\n", t); return 0; } @@ -150,8 +145,7 @@ static int fop_close(struct inode *inode, struct file *file) || !nowayout) { wdt_disable(); } else { - printk(KERN_CRIT SBC7240_PREFIX - "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); wdt_keepalive(); } @@ -159,7 +153,7 @@ static int fop_close(struct inode *inode, struct file *file) return 0; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING| WDIOF_SETTIMEOUT| WDIOF_MAGICCLOSE, @@ -168,50 +162,50 @@ static struct watchdog_info ident = { }; -static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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; + 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_KEEPALIVE: - wdt_keepalive(); - return 0; - case WDIOC_SETOPTIONS:{ - int options; - int retval = -EINVAL; + case WDIOC_SETOPTIONS: + { + int options; + int retval = -EINVAL; - if (get_user(options, (int __user *)arg)) - return -EFAULT; + 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; - } + if (options & WDIOS_DISABLECARD) { + wdt_disable(); + retval = 0; + } - return retval; + if (options & WDIOS_ENABLECARD) { + wdt_enable(); + retval = 0; } - case WDIOC_SETTIMEOUT:{ - int new_timeout; - if (get_user(new_timeout, (int __user *)arg)) - return -EFAULT; + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; - if (wdt_set_timeout(new_timeout)) - return -EINVAL; + if (get_user(new_timeout, (int __user *)arg)) + return -EFAULT; - /* Fall through */ - } + if (wdt_set_timeout(new_timeout)) + return -EINVAL; + + /* Fall through */ + } case WDIOC_GETTIMEOUT: return put_user(timeout, (int __user *)arg); default: @@ -225,7 +219,7 @@ static const struct file_operations wdt_fops = { .write = fop_write, .open = fop_open, .release = fop_close, - .ioctl = fop_ioctl, + .unlocked_ioctl = fop_ioctl, }; static struct miscdevice wdt_miscdev = { @@ -252,7 +246,7 @@ static struct notifier_block wdt_notifier = { static void __exit sbc7240_wdt_unload(void) { - printk(KERN_INFO SBC7240_PREFIX "Removing watchdog\n"); + pr_info("Removing watchdog\n"); misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); @@ -264,8 +258,7 @@ static int __init sbc7240_wdt_init(void) int rc = -EBUSY; if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) { - printk(KERN_ERR SBC7240_PREFIX - "I/O address 0x%04x already in use\n", + pr_err("I/O address 0x%04x already in use\n", SBC7240_ENABLE_PORT); rc = -EIO; goto err_out; @@ -273,35 +266,31 @@ static int __init sbc7240_wdt_init(void) /* The IO port 0x043 used to disable the watchdog * is already claimed by the system timer, so we - * cant request_region() it ...*/ + * can't request_region() it ...*/ if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) { timeout = SBC7240_TIMEOUT; - printk(KERN_INFO SBC7240_PREFIX - "timeout value must be 1<=x<=%d, using %d\n", - SBC7240_MAX_TIMEOUT, 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) { - printk(KERN_ERR SBC7240_PREFIX - "cannot register reboot notifier (err=%d)\n", rc); + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_region; } rc = misc_register(&wdt_miscdev); if (rc) { - printk(KERN_ERR SBC7240_PREFIX - "cannot register miscdev on minor=%d (err=%d)\n", + pr_err("cannot register miscdev on minor=%d (err=%d)\n", wdt_miscdev.minor, rc); goto err_out_reboot_notifier; } - printk(KERN_INFO SBC7240_PREFIX - "Watchdog driver for SBC7240 initialised (nowayout=%d)\n", - nowayout); + pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n", + nowayout); return 0; @@ -320,5 +309,3 @@ MODULE_AUTHOR("Gilles Gigan"); MODULE_DESCRIPTION("Watchdog device driver for single board" " computers EPIC Nano 7240 from iEi"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - diff --git a/drivers/watchdog/sbc8360.c b/drivers/watchdog/sbc8360.c index 2ee2677f364..da60560ca44 100644 --- a/drivers/watchdog/sbc8360.c +++ b/drivers/watchdog/sbc8360.c @@ -4,20 +4,20 @@ * (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. + * 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. + * 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@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -28,14 +28,16 @@ * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * - * (c) Copyright 1995 Alan Cox <alan@redhat.com> + * (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 + * 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> @@ -48,17 +50,13 @@ #include <linux/init.h> #include <linux/spinlock.h> #include <linux/moduleparam.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> static unsigned long sbc8360_is_open; -static DEFINE_SPINLOCK(sbc8360_lock); static char expect_close; -#define PFX "sbc8360: " - /* * * Watchdog Timer Configuration @@ -115,7 +113,7 @@ static char expect_close; * C | 6.5s 65s 650s 1300s * D | 7s 70s 700s 1400s * E | 7.5s 75s 750s 1500s - * F | 8s 80s 800s 1600s + * F | 8s 80s 800s 1600s * * Another way to say the same things is: * For N=1, Timeout = (M+1) * 0.5s @@ -198,13 +196,14 @@ static int wd_times[64][2] = { static int timeout = 27; static int wd_margin = 0xB; static int wd_multiplier = 2; -static int nowayout = WATCHDOG_NOWAYOUT; +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, int, 0); +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, - "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); /* * Kernel methods. @@ -231,9 +230,16 @@ static void sbc8360_ping(void) 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) +static ssize_t sbc8360_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { if (count) { if (!nowayout) { @@ -257,16 +263,12 @@ static ssize_t sbc8360_write(struct file *file, const char __user * buf, static int sbc8360_open(struct inode *inode, struct file *file) { - spin_lock(&sbc8360_lock); - if (test_and_set_bit(0, &sbc8360_is_open)) { - spin_unlock(&sbc8360_lock); + 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 */ - spin_unlock(&sbc8360_lock); sbc8360_activate(); sbc8360_ping(); return nonseekable_open(inode, file); @@ -274,16 +276,13 @@ static int sbc8360_open(struct inode *inode, struct file *file) static int sbc8360_close(struct inode *inode, struct file *file) { - spin_lock(&sbc8360_lock); if (expect_close == 42) - outb(0, SBC8360_ENABLE); + sbc8360_stop(); else - printk(KERN_CRIT PFX - "SBC8360 device closed unexpectedly. SBC8360 will not stop!\n"); + pr_crit("SBC8360 device closed unexpectedly. SBC8360 will not stop!\n"); clear_bit(0, &sbc8360_is_open); expect_close = 0; - spin_unlock(&sbc8360_lock); return 0; } @@ -294,10 +293,9 @@ static int sbc8360_close(struct inode *inode, struct file *file) static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if (code == SYS_DOWN || code == SYS_HALT) { - /* Disable the SBC8360 Watchdog */ - outb(0, SBC8360_ENABLE); - } + if (code == SYS_DOWN || code == SYS_HALT) + sbc8360_stop(); /* Disable the SBC8360 Watchdog */ + return NOTIFY_DONE; } @@ -334,20 +332,19 @@ static int __init sbc8360_init(void) unsigned long int mseconds = 60000; if (timeout < 0 || timeout > 63) { - printk(KERN_ERR PFX "Invalid timeout index (must be 0-63).\n"); + pr_err("Invalid timeout index (must be 0-63)\n"); res = -EINVAL; goto out; } if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) { - printk(KERN_ERR PFX "ENABLE method I/O %X is not available.\n", + pr_err("ENABLE method I/O %X is not available\n", SBC8360_ENABLE); res = -EIO; goto out; } if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) { - printk(KERN_ERR PFX - "BASETIME method I/O %X is not available.\n", + pr_err("BASETIME method I/O %X is not available\n", SBC8360_BASETIME); res = -EIO; goto out_nobasetimereg; @@ -355,13 +352,13 @@ static int __init sbc8360_init(void) res = register_reboot_notifier(&sbc8360_notifier); if (res) { - printk(KERN_ERR PFX "Failed to register reboot notifier.\n"); + pr_err("Failed to register reboot notifier\n"); goto out_noreboot; } res = misc_register(&sbc8360_miscdev); if (res) { - printk(KERN_ERR PFX "failed to register misc device\n"); + pr_err("failed to register misc device\n"); goto out_nomisc; } @@ -378,17 +375,17 @@ static int __init sbc8360_init(void) mseconds = (wd_margin + 1) * 100000; /* My kingdom for the ability to print "0.5 seconds" in the kernel! */ - printk(KERN_INFO PFX "Timeout set at %ld ms.\n", mseconds); + pr_info("Timeout set at %ld ms\n", mseconds); return 0; - out_nomisc: +out_nomisc: unregister_reboot_notifier(&sbc8360_notifier); - out_noreboot: +out_noreboot: release_region(SBC8360_BASETIME, 1); - out_nobasetimereg: +out_nobasetimereg: release_region(SBC8360_ENABLE, 1); - out: +out: return res; } @@ -407,6 +404,5 @@ MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>"); MODULE_DESCRIPTION("SBC8360 watchdog driver"); MODULE_LICENSE("GPL"); MODULE_VERSION("1.01"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); /* end of sbc8360.c */ diff --git a/drivers/watchdog/sbc_epx_c3.c b/drivers/watchdog/sbc_epx_c3.c index 82cbd8809a6..a1c502e0d8e 100644 --- a/drivers/watchdog/sbc_epx_c3.c +++ b/drivers/watchdog/sbc_epx_c3.c @@ -10,9 +10,11 @@ * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * - * based on softdog.c by Alan Cox <alan@redhat.com> + * 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> @@ -25,17 +27,17 @@ #include <linux/reboot.h> #include <linux/init.h> #include <linux/ioport.h> -#include <asm/uaccess.h> -#include <asm/io.h> +#include <linux/uaccess.h> +#include <linux/io.h> -#define PFX "epx_c3: " static int epx_c3_alive; #define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */ -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 */ @@ -50,7 +52,7 @@ static void epx_c3_stop(void) outb(0, EPXC3_WATCHDOG_CTL_REG); - printk(KERN_INFO PFX "Stopped watchdog timer.\n"); + pr_info("Stopped watchdog timer\n"); } static void epx_c3_pet(void) @@ -74,7 +76,7 @@ static int epx_c3_open(struct inode *inode, struct file *file) epx_c3_pet(); epx_c3_alive = 1; - printk(KERN_INFO "Started watchdog timer.\n"); + pr_info("Started watchdog timer\n"); return nonseekable_open(inode, file); } @@ -100,14 +102,13 @@ static ssize_t epx_c3_write(struct file *file, const char __user *data, return len; } -static int epx_c3_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, .firmware_version = 0, .identity = "Winsystems EPX-C3 H/W Watchdog", }; @@ -120,11 +121,6 @@ static int epx_c3_ioctl(struct inode *inode, struct file *file, case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, argp); - case WDIOC_KEEPALIVE: - epx_c3_pet(); - return 0; - case WDIOC_GETTIMEOUT: - return put_user(WATCHDOG_TIMEOUT, argp); case WDIOC_SETOPTIONS: if (get_user(options, argp)) return -EFAULT; @@ -140,6 +136,11 @@ static int epx_c3_ioctl(struct inode *inode, struct file *file, } return retval; + case WDIOC_KEEPALIVE: + epx_c3_pet(); + return 0; + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_TIMEOUT, argp); default: return -ENOTTY; } @@ -158,7 +159,7 @@ static const struct file_operations epx_c3_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = epx_c3_write, - .ioctl = epx_c3_ioctl, + .unlocked_ioctl = epx_c3_ioctl, .open = epx_c3_open, .release = epx_c3_release, }; @@ -173,9 +174,6 @@ static struct notifier_block epx_c3_notifier = { .notifier_call = epx_c3_notify_sys, }; -static const char banner[] __initdata = - KERN_INFO PFX "Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n"; - static int __init watchdog_init(void) { int ret; @@ -185,20 +183,19 @@ static int __init watchdog_init(void) ret = register_reboot_notifier(&epx_c3_notifier); if (ret) { - printk(KERN_ERR PFX "cannot register reboot notifier " - "(err=%d)\n", ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); goto out; } ret = misc_register(&epx_c3_miscdev); if (ret) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d " - "(err=%d)\n", WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); unregister_reboot_notifier(&epx_c3_notifier); goto out; } - printk(banner); + pr_info("Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n"); return 0; @@ -218,6 +215,8 @@ 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 runnning on this specific SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!"); +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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sbc_fitpc2_wdt.c b/drivers/watchdog/sbc_fitpc2_wdt.c new file mode 100644 index 00000000000..a517d8bae75 --- /dev/null +++ b/drivers/watchdog/sbc_fitpc2_wdt.c @@ -0,0 +1,265 @@ +/* + * 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 nonseekable_open(inode, file); +} + +static ssize_t fitpc2_wdt_write(struct file *file, const char *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 *)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_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time < 31 || time > 255) { + ret = -EINVAL; + break; + } + + margin = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(margin, (int *)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, + .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/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c index 32ccd7c89c7..131193a7acd 100644 --- a/drivers/watchdog/sc1200wdt.c +++ b/drivers/watchdog/sc1200wdt.c @@ -15,18 +15,24 @@ * * Changelog: * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware. - * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik and Alan Cox. + * 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 + * <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 + * 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> @@ -38,14 +44,12 @@ #include <linux/init.h> #include <linux/pnp.h> #include <linux/fs.h> - -#include <asm/semaphore.h> -#include <asm/io.h> -#include <asm/uaccess.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 PFX SC1200_MODULE_NAME ": " #define MAX_TIMEOUT 255 /* 255 minutes */ #define PMIR (io) /* Power Management Index Register */ @@ -68,11 +72,10 @@ #define UART2_IRQ 0x04 /* Serial1 */ /* 5 -7 are reserved */ -static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER; static int timeout = 1; static int io = -1; static int io_len = 2; /* for non plug and play */ -static struct semaphore open_sem; +static unsigned long open_flag; static char expect_close; static DEFINE_SPINLOCK(sc1200wdt_lock); /* io port access serialisation */ @@ -81,7 +84,8 @@ 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"); +MODULE_PARM_DESC(isapnp, + "When set to 0 driver ISA PnP support will be disabled"); #endif module_param(io, int, 0); @@ -89,28 +93,42 @@ MODULE_PARM_DESC(io, "io port"); module_param(timeout, int, 0); MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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) +static inline void __sc1200wdt_read_data(unsigned char index, + unsigned char *data) { - spin_lock(&sc1200wdt_lock); outb_p(index, PMIR); *data = inb(PMDR); - spin_unlock(&sc1200wdt_lock); } +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) +static inline void __sc1200wdt_write_data(unsigned char index, + unsigned char data) { - spin_lock(&sc1200wdt_lock); 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); } @@ -118,22 +136,23 @@ static inline void sc1200wdt_write_data(unsigned char index, unsigned char data) static void sc1200wdt_start(void) { unsigned char reg; + spin_lock(&sc1200wdt_lock); - sc1200wdt_read_data(WDCF, ®); + __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); + __sc1200wdt_write_data(WDCF, reg); /* set the timeout and get the ball rolling */ - sc1200wdt_write_data(WDTO, timeout); -} + __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) { @@ -144,91 +163,89 @@ static inline int sc1200wdt_status(void) * KEEPALIVEPING which is a bit of a kludge because there's nothing * else for enabled/disabled status */ - return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; /* bits 1 - 7 are undefined */ + return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; } - static int sc1200wdt_open(struct inode *inode, struct file *file) { /* allow one at a time */ - if (down_trylock(&open_sem)) + if (test_and_set_bit(0, &open_flag)) return -EBUSY; if (timeout > MAX_TIMEOUT) timeout = MAX_TIMEOUT; sc1200wdt_start(); - printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout); + pr_info("Watchdog enabled, timeout = %d min(s)", timeout); return nonseekable_open(inode, file); } -static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, .firmware_version = 0, .identity = "PC87307/PC97307", }; switch (cmd) { - default: - return -ENOTTY; + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; - 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_GETSTATUS: - return put_user(sc1200wdt_status(), p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; - case WDIOC_KEEPALIVE: - sc1200wdt_write_data(WDTO, timeout); - return 0; + if (get_user(options, p)) + return -EFAULT; - 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); - /* fall through and return the new timeout */ - - case WDIOC_GETTIMEOUT: - return put_user(timeout * 60, p); + if (options & WDIOS_DISABLECARD) { + sc1200wdt_stop(); + retval = 0; + } - case WDIOC_SETOPTIONS: - { - int options, retval = -EINVAL; + if (options & WDIOS_ENABLECARD) { + sc1200wdt_start(); + retval = 0; + } - if (get_user(options, p)) - return -EFAULT; + 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); + /* fall through and return the new timeout */ - if (options & WDIOS_DISABLECARD) { - sc1200wdt_stop(); - retval = 0; - } + case WDIOC_GETTIMEOUT: + return put_user(timeout * 60, p); - if (options & WDIOS_ENABLECARD) { - sc1200wdt_start(); - retval = 0; - } - - return retval; - } + default: + return -ENOTTY; } } @@ -237,19 +254,20 @@ static int sc1200wdt_release(struct inode *inode, struct file *file) { if (expect_close == 42) { sc1200wdt_stop(); - printk(KERN_INFO PFX "Watchdog disabled\n"); + pr_info("Watchdog disabled\n"); } else { sc1200wdt_write_data(WDTO, timeout); - printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout); + pr_crit("Unexpected close!, timeout = %d min(s)\n", timeout); } - up(&open_sem); + 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) +static ssize_t sc1200wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) { if (len) { if (!nowayout) { @@ -260,7 +278,7 @@ static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_ for (i = 0; i != len; i++) { char c; - if (get_user(c, data+i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -275,7 +293,8 @@ static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_ } -static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +static int sc1200wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) { if (code == SYS_DOWN || code == SYS_HALT) sc1200wdt_stop(); @@ -284,23 +303,20 @@ static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, } -static struct notifier_block sc1200wdt_notifier = -{ +static struct notifier_block sc1200wdt_notifier = { .notifier_call = sc1200wdt_notify_sys, }; -static const struct file_operations sc1200wdt_fops = -{ +static const struct file_operations sc1200wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = sc1200wdt_write, - .ioctl = sc1200wdt_ioctl, + .unlocked_ioctl = sc1200wdt_ioctl, .open = sc1200wdt_open, .release = sc1200wdt_release, }; -static struct miscdevice sc1200wdt_miscdev = -{ +static struct miscdevice sc1200wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &sc1200wdt_fops, @@ -312,14 +328,14 @@ 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. + * 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 */ + reg &= 0x0f; /* we don't want the UART busy bits */ return (reg == 0x0e) ? 0 : -ENODEV; } @@ -332,7 +348,8 @@ static struct pnp_device_id scl200wdt_pnp_devices[] = { {.id = ""}, }; -static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_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) @@ -343,17 +360,17 @@ static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id io_len = pnp_port_len(wdt_dev, 0); if (!request_region(io, io_len, SC1200_MODULE_NAME)) { - printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); + pr_err("Unable to register IO port %#x\n", io); return -EBUSY; } - printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len); + 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) +static void scl200wdt_pnp_remove(struct pnp_dev *dev) { - if (wdt_dev){ + if (wdt_dev) { release_region(io, io_len); wdt_dev = NULL; } @@ -373,9 +390,7 @@ static int __init sc1200wdt_init(void) { int ret; - printk("%s\n", banner); - - sema_init(&open_sem, 1); + pr_info("%s\n", SC1200_MODULE_VER); #if defined CONFIG_PNP if (isapnp) { @@ -386,7 +401,7 @@ static int __init sc1200wdt_init(void) #endif if (io == -1) { - printk(KERN_ERR PFX "io parameter must be specified\n"); + pr_err("io parameter must be specified\n"); ret = -EINVAL; goto out_pnp; } @@ -394,12 +409,13 @@ static int __init sc1200wdt_init(void) #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; - pnp_unregister_driver(&scl200wdt_pnp_driver); #endif if (!request_region(io, io_len, SC1200_MODULE_NAME)) { - printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); + pr_err("Unable to register IO port %#x\n", io); ret = -EBUSY; goto out_pnp; } @@ -410,13 +426,14 @@ static int __init sc1200wdt_init(void) ret = register_reboot_notifier(&sc1200wdt_notifier); if (ret) { - printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret); + pr_err("Unable to register reboot notifier err = %d\n", ret); goto out_io; } ret = misc_register(&sc1200wdt_miscdev); if (ret) { - printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); + pr_err("Unable to register miscdev on minor %d\n", + WATCHDOG_MINOR); goto out_rbt; } @@ -446,7 +463,7 @@ static void __exit sc1200wdt_exit(void) unregister_reboot_notifier(&sc1200wdt_notifier); #if defined CONFIG_PNP - if(isapnp) + if (isapnp) pnp_unregister_driver(&scl200wdt_pnp_driver); else #endif @@ -457,6 +474,6 @@ 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_DESCRIPTION( + "Driver for National Semiconductor PC87307/PC97307 watchdog component"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c index 2847324a2be..1cfd3f6a13d 100644 --- a/drivers/watchdog/sc520_wdt.c +++ b/drivers/watchdog/sc520_wdt.c @@ -1,8 +1,8 @@ /* * AMD Elan SC520 processor Watchdog Timer driver * - * Based on acquirewdt.c by Alan Cox, - * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> + * Based on acquirewdt.c by Alan Cox, + * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -11,7 +11,7 @@ * * 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. + * the hope that it may be useful for others. * * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> * 9/27 - 2001 [Initial release] @@ -52,6 +52,8 @@ * 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> @@ -64,13 +66,9 @@ #include <linux/reboot.h> #include <linux/init.h> #include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> - -#define OUR_NAME "sc520_wdt" -#define PFX OUR_NAME ": " /* * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) @@ -91,13 +89,18 @@ */ #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +/* 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) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1 <= timeout <= 3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -136,8 +139,7 @@ static void wdt_timer_ping(unsigned long data) /* If we got a heartbeat pulse within the WDT_US_INTERVAL * we agree to ping the WDT */ - if(time_before(jiffies, next_heartbeat)) - { + if (time_before(jiffies, next_heartbeat)) { /* Ping the WDT */ spin_lock(&wdt_spinlock); writew(0xAAAA, wdtmrctl); @@ -146,9 +148,8 @@ static void wdt_timer_ping(unsigned long data) /* Re-set the timer interval */ mod_timer(&timer, jiffies + WDT_INTERVAL); - } else { - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); - } + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); } /* @@ -157,12 +158,11 @@ static void wdt_timer_ping(unsigned long data) static void wdt_config(int writeval) { - __u16 dummy; unsigned long flags; /* buy some time (ping) */ spin_lock_irqsave(&wdt_spinlock, flags); - dummy=readw(wdtmrctl); /* ensure write synchronization */ + readw(wdtmrctl); /* ensure write synchronization */ writew(0xAAAA, wdtmrctl); writew(0x5555, wdtmrctl); /* unlock WDT = make WDT configuration register writable one time */ @@ -183,7 +183,7 @@ static int wdt_startup(void) /* Start the watchdog */ wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); - printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); + pr_info("Watchdog timer is now enabled\n"); return 0; } @@ -195,7 +195,7 @@ static int wdt_turnoff(void) /* Stop the watchdog */ wdt_config(0); - printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); + pr_info("Watchdog timer is now disabled...\n"); return 0; } @@ -219,10 +219,11 @@ static int wdt_set_heartbeat(int t) * /dev/watchdog handling */ -static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +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 (count) { if (!nowayout) { size_t ofs; @@ -231,25 +232,26 @@ static ssize_t fop_write(struct file * file, const char __user * buf, size_t cou wdt_expect_close = 0; /* now scan */ - for(ofs = 0; ofs != count; ofs++) { + for (ofs = 0; ofs != count; ofs++) { char c; if (get_user(c, buf + ofs)) return -EFAULT; - if(c == 'V') + if (c == 'V') wdt_expect_close = 42; } } - /* Well, anyhow someone wrote to us, we should return that favour */ + /* 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) +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)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; if (nowayout) __module_get(THIS_MODULE); @@ -259,12 +261,12 @@ static int fop_open(struct inode * inode, struct file * file) return nonseekable_open(inode, file); } -static int fop_close(struct inode * inode, struct file * file) +static int fop_close(struct inode *inode, struct file *file) { - if(wdt_expect_close == 42) { + if (wdt_expect_close == 42) wdt_turnoff(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); wdt_keepalive(); } clear_bit(0, &wdt_is_open); @@ -272,63 +274,62 @@ static int fop_close(struct inode * inode, struct file * file) return 0; } -static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "SC520", }; - switch(cmd) + 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: { - default: - return -ENOTTY; - 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_KEEPALIVE: - wdt_keepalive(); - return 0; - 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; - } + int new_options, retval = -EINVAL; - if(new_options & WDIOS_ENABLECARD) { - wdt_startup(); - retval = 0; - } + if (get_user(new_options, p)) + return -EFAULT; - return retval; + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; } - case WDIOC_SETTIMEOUT: - { - int new_timeout; - if(get_user(new_timeout, p)) - return -EFAULT; + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } - if(wdt_set_heartbeat(new_timeout)) - return -EINVAL; + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; - wdt_keepalive(); - /* Fall through */ - } - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); + if (get_user(new_timeout, p)) + return -EFAULT; + + if (wdt_set_heartbeat(new_timeout)) + return -EINVAL; + + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; } } @@ -338,7 +339,7 @@ static const struct file_operations wdt_fops = { .write = fop_write, .open = fop_open, .release = fop_close, - .ioctl = fop_ioctl, + .unlocked_ioctl = fop_ioctl, }; static struct miscdevice wdt_miscdev = { @@ -354,7 +355,7 @@ static struct miscdevice wdt_miscdev = { static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code==SYS_DOWN || code==SYS_HALT) + if (code == SYS_DOWN || code == SYS_HALT) wdt_turnoff(); return NOTIFY_DONE; } @@ -383,36 +384,36 @@ 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 */ + /* 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); - printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n", + pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n", WATCHDOG_TIMEOUT); } - wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2); + wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2); if (!wdtmrctl) { - printk(KERN_ERR PFX "Unable to remap memory\n"); + pr_err("Unable to remap memory\n"); rc = -ENOMEM; goto err_out_region2; } rc = register_reboot_notifier(&wdt_notifier); if (rc) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_ioremap; } rc = misc_register(&wdt_miscdev); if (rc) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, rc); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, rc); goto err_out_notifier; } - printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", - timeout,nowayout); + pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); return 0; @@ -428,6 +429,6 @@ 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_DESCRIPTION( + "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sch311x_wdt.c b/drivers/watchdog/sch311x_wdt.c new file mode 100644 index 00000000000..b96127ea3de --- /dev/null +++ b/drivers/watchdog/sch311x_wdt.c @@ -0,0 +1,546 @@ +/* + * sch311x_wdt.c - Driver for the SCH311x Super-I/O chips + * integrated watchdog. + * + * (c) Copyright 2008 Wim Van Sebroeck <wim@iguana.be>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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(); + /* Fall */ + 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 nonseekable_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, + .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 = { + .owner = THIS_MODULE, + .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 index d55882bca31..836377cf927 100644 --- a/drivers/watchdog/scx200_wdt.c +++ b/drivers/watchdog/scx200_wdt.c @@ -17,6 +17,8 @@ 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> @@ -27,28 +29,27 @@ #include <linux/fs.h> #include <linux/ioport.h> #include <linux/scx200.h> +#include <linux/uaccess.h> +#include <linux/io.h> -#include <asm/uaccess.h> -#include <asm/io.h> - -#define NAME "scx200_wdt" +#define DEBUG MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); static int margin = 60; /* in seconds */ module_param(margin, int, 0); MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); static u16 wdto_restart; -static struct semaphore open_semaphore; 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 */ @@ -59,40 +60,45 @@ static char expect_close; 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) { - printk(KERN_INFO NAME ": timer margin %d seconds\n", margin); + pr_info("timer margin %d seconds\n", margin); wdto_restart = margin * W_SCALE; } static void scx200_wdt_enable(void) { - printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n", - wdto_restart); + 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) { - printk(KERN_DEBUG NAME ": disabling watchdog timer\n"); + 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 (down_trylock(&open_semaphore)) + if (test_and_set_bit(0, &open_lock)) return -EBUSY; scx200_wdt_enable(); @@ -101,13 +107,12 @@ static int scx200_wdt_open(struct inode *inode, struct file *file) static int scx200_wdt_release(struct inode *inode, struct file *file) { - if (expect_close != 42) { - printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n"); - } else if (!nowayout) { + 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; - up(&open_semaphore); + clear_bit(0, &open_lock); return 0; } @@ -122,8 +127,7 @@ static int scx200_wdt_notify_sys(struct notifier_block *this, return NOTIFY_DONE; } -static struct notifier_block scx200_wdt_notifier = -{ +static struct notifier_block scx200_wdt_notifier = { .notifier_call = scx200_wdt_notify_sys, }; @@ -131,8 +135,7 @@ 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) - { + if (len) { size_t i; scx200_wdt_ping(); @@ -140,7 +143,7 @@ static ssize_t scx200_wdt_write(struct file *file, const char __user *data, expect_close = 0; for (i = 0; i < len; ++i) { char c; - if (get_user(c, data+i)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -152,23 +155,22 @@ static ssize_t scx200_wdt_write(struct file *file, const char __user *data, return 0; } -static int scx200_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +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 struct watchdog_info ident = { + static const struct watchdog_info ident = { .identity = "NatSemi SCx200 Watchdog", .firmware_version = 1, - .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, }; int new_margin; switch (cmd) { - default: - return -ENOTTY; case WDIOC_GETSUPPORT: - if(copy_to_user(argp, &ident, sizeof(ident))) + if (copy_to_user(argp, &ident, sizeof(ident))) return -EFAULT; return 0; case WDIOC_GETSTATUS: @@ -191,29 +193,31 @@ static int scx200_wdt_ioctl(struct inode *inode, struct file *file, 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, - .ioctl = scx200_wdt_ioctl, - .open = scx200_wdt_open, + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = scx200_wdt_write, + .unlocked_ioctl = scx200_wdt_ioctl, + .open = scx200_wdt_open, .release = scx200_wdt_release, }; static struct miscdevice scx200_wdt_miscdev = { .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &scx200_wdt_fops, + .name = "watchdog", + .fops = &scx200_wdt_fops, }; static int __init scx200_wdt_init(void) { int r; - printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n"); + pr_debug("NatSemi SCx200 Watchdog Driver\n"); /* check that we have found the configuration block */ if (!scx200_cb_present()) @@ -222,18 +226,16 @@ static int __init scx200_wdt_init(void) if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, SCx200_WDT_SIZE, "NatSemi SCx200 Watchdog")) { - printk(KERN_WARNING NAME ": watchdog I/O region busy\n"); + pr_warn("watchdog I/O region busy\n"); return -EBUSY; } scx200_wdt_update_margin(); scx200_wdt_disable(); - sema_init(&open_semaphore, 1); - r = register_reboot_notifier(&scx200_wdt_notifier); if (r) { - printk(KERN_ERR NAME ": unable to register reboot notifier"); + pr_err("unable to register reboot notifier\n"); release_region(scx200_cb_base + SCx200_WDT_OFFSET, SCx200_WDT_SIZE); return r; @@ -263,7 +265,7 @@ module_exit(scx200_wdt_cleanup); /* Local variables: - compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" - c-basic-offset: 8 + compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" + c-basic-offset: 8 End: */ diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c index 1277f7e9cc5..061756e36cf 100644 --- a/drivers/watchdog/shwdt.c +++ b/drivers/watchdog/shwdt.c @@ -1,9 +1,9 @@ /* - * drivers/char/watchdog/shwdt.c + * drivers/watchdog/shwdt.c * * Watchdog driver for integrated watchdog in the SuperH processors. * - * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org> + * Copyright (C) 2001 - 2012 Paul Mundt <lethal@linux-sh.org> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -17,22 +17,26 @@ * 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/miscdevice.h> +#include <linux/spinlock.h> #include <linux/watchdog.h> -#include <linux/reboot.h> -#include <linux/notifier.h> -#include <linux/ioport.h> +#include <linux/pm_runtime.h> #include <linux/fs.h> #include <linux/mm.h> -#include <asm/io.h> -#include <asm/uaccess.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> #include <asm/watchdog.h> -#define PFX "shwdt: " +#define DRV_NAME "sh-wdt" /* * Default clock division ratio is 5.25 msecs. For an additional table of @@ -48,7 +52,7 @@ * necssary. * * As a result of this timing problem, the only modes that are particularly - * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms + * 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 @@ -62,33 +66,35 @@ * 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) msecs_to_jiffies(cks - 4) - -static void sh_wdt_ping(unsigned long data); - -static unsigned long shwdt_is_open; -static struct watchdog_info sh_wdt_info; -static char shwdt_expect_close; -static DEFINE_TIMER(timer, sh_wdt_ping, 0, 0); -static unsigned long next_heartbeat; +#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; -static int nowayout = WATCHDOG_NOWAYOUT; +struct sh_wdt { + void __iomem *base; + struct device *dev; + struct clk *clk; + spinlock_t lock; -/** - * sh_wdt_start - Start the Watchdog - * - * Starts the watchdog. - */ -static void sh_wdt_start(void) + struct timer_list timer; +}; + +static int sh_wdt_start(struct watchdog_device *wdt_dev) { - __u8 csr; + 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(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); csr = sh_wdt_read_csr(); csr |= WTCSR_WT | clock_division_ratio; @@ -110,68 +116,73 @@ static void sh_wdt_start(void) sh_wdt_write_csr(csr); #ifdef CONFIG_CPU_SH2 - /* - * Whoever came up with the RSTCSR semantics must've been smoking - * some of the good stuff, since in addition to the WTCSR/WTCNT write - * brain-damage, it's managed to fuck things up one step further.. - * - * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. - * but if we want to touch RSTE or RSTS, the upper byte has to be - * 0x5a.. - */ csr = sh_wdt_read_rstcsr(); csr &= ~RSTCSR_RSTS; sh_wdt_write_rstcsr(csr); #endif + spin_unlock_irqrestore(&wdt->lock, flags); + + return 0; } -/** - * sh_wdt_stop - Stop the Watchdog - * Stops the watchdog. - */ -static void sh_wdt_stop(void) +static int sh_wdt_stop(struct watchdog_device *wdt_dev) { - __u8 csr; + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); + unsigned long flags; + u8 csr; - del_timer(&timer); + 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; } -/** - * sh_wdt_keepalive - Keep the Userspace Watchdog Alive - * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. - */ -static inline void sh_wdt_keepalive(void) +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; } -/** - * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat - * Set the Userspace Watchdog heartbeat - */ -static int sh_wdt_set_heartbeat(int t) +static int sh_wdt_set_heartbeat(struct watchdog_device *wdt_dev, unsigned t) { - if (unlikely((t < 1) || (t > 3600))) /* arbitrary upper limit */ + 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; } -/** - * sh_wdt_ping - Ping the Watchdog - * @data: Unused - * - * Clears overflow bit, resets timer counter. - */ static void sh_wdt_ping(unsigned long data) { + struct sh_wdt *wdt = (struct sh_wdt *)data; + unsigned long flags; + + spin_lock_irqsave(&wdt->lock, flags); if (time_before(jiffies, next_heartbeat)) { - __u8 csr; + u8 csr; csr = sh_wdt_read_csr(); csr &= ~WTCSR_IOVF; @@ -179,307 +190,166 @@ static void sh_wdt_ping(unsigned long data) sh_wdt_write_cnt(0); - mod_timer(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); } else - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping " - "the watchdog\n"); + dev_warn(wdt->dev, "Heartbeat lost! Will not ping " + "the watchdog\n"); + spin_unlock_irqrestore(&wdt->lock, flags); } -/** - * sh_wdt_open - Open the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is opened and started. - */ -static int sh_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &shwdt_is_open)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); +static const struct watchdog_info sh_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SH WDT", +}; - sh_wdt_start(); +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, +}; - return nonseekable_open(inode, file); -} +static struct watchdog_device sh_wdt_dev = { + .info = &sh_wdt_info, + .ops = &sh_wdt_ops, +}; -/** - * sh_wdt_close - Close the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is closed and stopped. - */ -static int sh_wdt_close(struct inode *inode, struct file *file) +static int sh_wdt_probe(struct platform_device *pdev) { - if (shwdt_expect_close == 42) { - sh_wdt_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not " - "stopping watchdog!\n"); - sh_wdt_keepalive(); - } + struct sh_wdt *wdt; + struct resource *res; + int rc; - clear_bit(0, &shwdt_is_open); - shwdt_expect_close = 0; + /* + * As this driver only covers the global watchdog case, reject + * any attempts to register per-CPU watchdogs. + */ + if (pdev->id != -1) + return -EINVAL; - return 0; -} + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) + return -EINVAL; -/** - * sh_wdt_write - Write to Device - * @file: file handle of device - * @buf: buffer to write - * @count: length of buffer - * @ppos: offset - * - * Pings the watchdog on write. - */ -static ssize_t sh_wdt_write(struct file *file, const char *buf, - size_t count, loff_t *ppos) -{ - if (count) { - if (!nowayout) { - size_t i; - - shwdt_expect_close = 0; - - for (i = 0; i != count; i++) { - char c; - if (get_user(c, buf + i)) - return -EFAULT; - if (c == 'V') - shwdt_expect_close = 42; - } - } - sh_wdt_keepalive(); + 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; } - return count; -} + wdt->base = devm_ioremap_resource(wdt->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); -/** - * sh_wdt_mmap - map WDT/CPG registers into userspace - * @file: file structure for the device - * @vma: VMA to map the registers into - * - * A simple mmap() implementation for the corner cases where the counter - * needs to be mapped in userspace directly. Due to the relatively small - * size of the area, neighbouring registers not necessarily tied to the - * CPG will also be accessible through the register page, so this remains - * configurable for users that really know what they're doing. - * - * Additionaly, the register page maps in the CPG register base relative - * to the nearest page-aligned boundary, which requires that userspace do - * the appropriate CPU subtype math for calculating the page offset for - * the counter value. - */ -static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma) -{ - int ret = -ENOSYS; + watchdog_set_nowayout(&sh_wdt_dev, nowayout); + watchdog_set_drvdata(&sh_wdt_dev, wdt); -#ifdef CONFIG_SH_WDT_MMAP - unsigned long addr; + spin_lock_init(&wdt->lock); - /* Only support the simple cases where we map in a register page. */ - if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff) - return -EINVAL; + 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); - /* - * Pick WTCNT as the start, it's usually the first register after the - * FRQCR, and neither one are generally page-aligned out of the box. - */ - addr = WTCNT & ~(PAGE_SIZE - 1); + dev_warn(&pdev->dev, + "heartbeat value must be 1<=x<=3600, using %d\n", + sh_wdt_dev.timeout); + } - vma->vm_flags |= VM_IO; - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + dev_info(&pdev->dev, "configured with heartbeat=%d sec (nowayout=%d)\n", + sh_wdt_dev.timeout, nowayout); - if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT, - PAGE_SIZE, vma->vm_page_prot)) { - printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n", - __func__); - return -EAGAIN; + rc = watchdog_register_device(&sh_wdt_dev); + if (unlikely(rc)) { + dev_err(&pdev->dev, "Can't register watchdog (err=%d)\n", rc); + return rc; } - ret = 0; -#endif + init_timer(&wdt->timer); + wdt->timer.function = sh_wdt_ping; + wdt->timer.data = (unsigned long)wdt; + wdt->timer.expires = next_ping_period(clock_division_ratio); - return ret; -} + dev_info(&pdev->dev, "initialized.\n"); -/** - * sh_wdt_ioctl - Query Device - * @inode: inode of device - * @file: file handle of device - * @cmd: watchdog command - * @arg: argument - * - * Query basic information from the device or ping it, as outlined by the - * watchdog API. - */ -static int sh_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - int new_heartbeat; - int options, retval = -EINVAL; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user((struct watchdog_info *)arg, - &sh_wdt_info, - sizeof(sh_wdt_info)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, (int *)arg); - case WDIOC_KEEPALIVE: - sh_wdt_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_heartbeat, (int *)arg)) - return -EFAULT; - - if (sh_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - - sh_wdt_keepalive(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, (int *)arg); - case WDIOC_SETOPTIONS: - if (get_user(options, (int *)arg)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - sh_wdt_stop(); - retval = 0; - } - - if (options & WDIOS_ENABLECARD) { - sh_wdt_start(); - retval = 0; - } - - return retval; - default: - return -ENOTTY; - } + pm_runtime_enable(&pdev->dev); return 0; } -/** - * sh_wdt_notify_sys - Notifier Handler - * @this: notifier block - * @code: notifier event - * @unused: unused - * - * Handles specific events, such as turning off the watchdog during a - * shutdown event. - */ -static int sh_wdt_notify_sys(struct notifier_block *this, - unsigned long code, void *unused) +static int sh_wdt_remove(struct platform_device *pdev) { - if (code == SYS_DOWN || code == SYS_HALT) - sh_wdt_stop(); + watchdog_unregister_device(&sh_wdt_dev); - return NOTIFY_DONE; -} + pm_runtime_disable(&pdev->dev); -static const struct file_operations sh_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sh_wdt_write, - .ioctl = sh_wdt_ioctl, - .open = sh_wdt_open, - .release = sh_wdt_close, - .mmap = sh_wdt_mmap, -}; + return 0; +} -static struct watchdog_info sh_wdt_info = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | - WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "SH WDT", -}; +static void sh_wdt_shutdown(struct platform_device *pdev) +{ + sh_wdt_stop(&sh_wdt_dev); +} -static struct notifier_block sh_wdt_notifier = { - .notifier_call = sh_wdt_notify_sys, -}; +static struct platform_driver sh_wdt_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, -static struct miscdevice sh_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sh_wdt_fops, + .probe = sh_wdt_probe, + .remove = sh_wdt_remove, + .shutdown = sh_wdt_shutdown, }; -/** - * sh_wdt_init - Initialize module - * Registers the device and notifier handler. Actual device - * initialization is handled by sh_wdt_open(). - */ static int __init sh_wdt_init(void) { - int rc; - - if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) { + if (unlikely(clock_division_ratio < 0x5 || + clock_division_ratio > 0x7)) { clock_division_ratio = WTCSR_CKS_4096; - printk(KERN_INFO PFX "clock_division_ratio value must " - "be 0x5<=x<=0x7, using %d\n", clock_division_ratio); - } - rc = sh_wdt_set_heartbeat(heartbeat); - if (unlikely(rc)) { - heartbeat = WATCHDOG_HEARTBEAT; - printk(KERN_INFO PFX "heartbeat value must " - "be 1<=x<=3600, using %d\n", heartbeat); + pr_info("divisor must be 0x5<=x<=0x7, using %d\n", + clock_division_ratio); } - rc = register_reboot_notifier(&sh_wdt_notifier); - if (unlikely(rc)) { - printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", - rc); - return rc; - } - - rc = misc_register(&sh_wdt_miscdev); - if (unlikely(rc)) { - printk(KERN_ERR PFX "Can't register miscdev on " - "minor=%d (err=%d)\n", sh_wdt_miscdev.minor, rc); - unregister_reboot_notifier(&sh_wdt_notifier); - return rc; - } - - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", - heartbeat, nowayout); - - return 0; + return platform_driver_register(&sh_wdt_driver); } -/** - * sh_wdt_exit - Deinitialize module - * Unregisters the device and notifier handler. Actual device - * deinitialization is handled by sh_wdt_close(). - */ static void __exit sh_wdt_exit(void) { - misc_deregister(&sh_wdt_miscdev); - unregister_reboot_notifier(&sh_wdt_notifier); + 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_MISCDEV(WATCHDOG_MINOR); +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(clock_division_ratio) ")"); +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, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -module_init(sh_wdt_init); -module_exit(sh_wdt_exit); +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/sirfsoc_wdt.c b/drivers/watchdog/sirfsoc_wdt.c new file mode 100644 index 00000000000..702d0787080 --- /dev/null +++ b/drivers/watchdog/sirfsoc_wdt.c @@ -0,0 +1,226 @@ +/* + * Watchdog driver for CSR SiRFprimaII and SiRFatlasVI + * + * Copyright (c) 2013 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/module.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/moduleparam.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#define CLOCK_FREQ 1000000 + +#define SIRFSOC_TIMER_COUNTER_LO 0x0000 +#define SIRFSOC_TIMER_MATCH_0 0x0008 +#define SIRFSOC_TIMER_INT_EN 0x0024 +#define SIRFSOC_TIMER_WATCHDOG_EN 0x0028 +#define SIRFSOC_TIMER_LATCH 0x0030 +#define SIRFSOC_TIMER_LATCHED_LO 0x0034 + +#define SIRFSOC_TIMER_WDT_INDEX 5 + +#define SIRFSOC_WDT_MIN_TIMEOUT 30 /* 30 secs */ +#define SIRFSOC_WDT_MAX_TIMEOUT (10 * 60) /* 10 mins */ +#define SIRFSOC_WDT_DEFAULT_TIMEOUT 30 /* 30 secs */ + +static unsigned int timeout = SIRFSOC_WDT_DEFAULT_TIMEOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; + +module_param(timeout, uint, 0); +module_param(nowayout, bool, 0); + +MODULE_PARM_DESC(timeout, "Default watchdog timeout (in seconds)"); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static unsigned int sirfsoc_wdt_gettimeleft(struct watchdog_device *wdd) +{ + u32 counter, match; + void __iomem *wdt_base; + int time_left; + + wdt_base = watchdog_get_drvdata(wdd); + counter = readl(wdt_base + SIRFSOC_TIMER_COUNTER_LO); + match = readl(wdt_base + + SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); + + time_left = match - counter; + + return time_left / CLOCK_FREQ; +} + +static int sirfsoc_wdt_updatetimeout(struct watchdog_device *wdd) +{ + u32 counter, timeout_ticks; + void __iomem *wdt_base; + + timeout_ticks = wdd->timeout * CLOCK_FREQ; + wdt_base = watchdog_get_drvdata(wdd); + + /* Enable the latch before reading the LATCH_LO register */ + writel(1, wdt_base + SIRFSOC_TIMER_LATCH); + + /* Set the TO value */ + counter = readl(wdt_base + SIRFSOC_TIMER_LATCHED_LO); + + counter += timeout_ticks; + + writel(counter, wdt_base + + SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); + + return 0; +} + +static int sirfsoc_wdt_enable(struct watchdog_device *wdd) +{ + void __iomem *wdt_base = watchdog_get_drvdata(wdd); + sirfsoc_wdt_updatetimeout(wdd); + + /* + * NOTE: If interrupt is not enabled + * then WD-Reset doesn't get generated at all. + */ + writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) + | (1 << SIRFSOC_TIMER_WDT_INDEX), + wdt_base + SIRFSOC_TIMER_INT_EN); + writel(1, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); + + return 0; +} + +static int sirfsoc_wdt_disable(struct watchdog_device *wdd) +{ + void __iomem *wdt_base = watchdog_get_drvdata(wdd); + + writel(0, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); + writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) + & (~(1 << SIRFSOC_TIMER_WDT_INDEX)), + wdt_base + SIRFSOC_TIMER_INT_EN); + + return 0; +} + +static int sirfsoc_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) +{ + wdd->timeout = to; + sirfsoc_wdt_updatetimeout(wdd); + + return 0; +} + +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) + +static const struct watchdog_info sirfsoc_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "SiRFSOC Watchdog", +}; + +static struct watchdog_ops sirfsoc_wdt_ops = { + .owner = THIS_MODULE, + .start = sirfsoc_wdt_enable, + .stop = sirfsoc_wdt_disable, + .get_timeleft = sirfsoc_wdt_gettimeleft, + .ping = sirfsoc_wdt_updatetimeout, + .set_timeout = sirfsoc_wdt_settimeout, +}; + +static struct watchdog_device sirfsoc_wdd = { + .info = &sirfsoc_wdt_ident, + .ops = &sirfsoc_wdt_ops, + .timeout = SIRFSOC_WDT_DEFAULT_TIMEOUT, + .min_timeout = SIRFSOC_WDT_MIN_TIMEOUT, + .max_timeout = SIRFSOC_WDT_MAX_TIMEOUT, +}; + +static int sirfsoc_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + void __iomem *base; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + watchdog_set_drvdata(&sirfsoc_wdd, base); + + watchdog_init_timeout(&sirfsoc_wdd, timeout, &pdev->dev); + watchdog_set_nowayout(&sirfsoc_wdd, nowayout); + + ret = watchdog_register_device(&sirfsoc_wdd); + if (ret) + return ret; + + platform_set_drvdata(pdev, &sirfsoc_wdd); + + return 0; +} + +static void sirfsoc_wdt_shutdown(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + + sirfsoc_wdt_disable(wdd); +} + +static int sirfsoc_wdt_remove(struct platform_device *pdev) +{ + sirfsoc_wdt_shutdown(pdev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int sirfsoc_wdt_suspend(struct device *dev) +{ + return 0; +} + +static int sirfsoc_wdt_resume(struct device *dev) +{ + struct watchdog_device *wdd = dev_get_drvdata(dev); + + /* + * NOTE: Since timer controller registers settings are saved + * and restored back by the timer-prima2.c, so we need not + * update WD settings except refreshing timeout. + */ + sirfsoc_wdt_updatetimeout(wdd); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(sirfsoc_wdt_pm_ops, + sirfsoc_wdt_suspend, sirfsoc_wdt_resume); + +static const struct of_device_id sirfsoc_wdt_of_match[] = { + { .compatible = "sirf,prima2-tick"}, + {}, +}; +MODULE_DEVICE_TABLE(of, sirfsoc_wdt_of_match); + +static struct platform_driver sirfsoc_wdt_driver = { + .driver = { + .name = "sirfsoc-wdt", + .owner = THIS_MODULE, + .pm = &sirfsoc_wdt_pm_ops, + .of_match_table = sirfsoc_wdt_of_match, + }, + .probe = sirfsoc_wdt_probe, + .remove = sirfsoc_wdt_remove, + .shutdown = sirfsoc_wdt_shutdown, +}; +module_platform_driver(sirfsoc_wdt_driver); + +MODULE_DESCRIPTION("SiRF SoC watchdog driver"); +MODULE_AUTHOR("Xianglong Du <Xianglong.Du@csr.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sirfsoc-wdt"); diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c index 5d2b5ba6141..445ea1ad1fa 100644 --- a/drivers/watchdog/smsc37b787_wdt.c +++ b/drivers/watchdog/smsc37b787_wdt.c @@ -1,8 +1,8 @@ /* * SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x * - * Based on acquirewdt.c by Alan Cox <alan@redhat.com> - * and some other existing drivers + * Based on acquirewdt.c by Alan Cox <alan@lxorguk.ukuu.org.uk> + * and some other existing drivers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -11,38 +11,40 @@ * * 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. + * 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 + * 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. + * 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. + * 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/watchdog.txt + * Documentation/watchdog/wdt.txt */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -55,10 +57,9 @@ #include <linux/reboot.h> #include <linux/init.h> #include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> /* enable support for minutes as units? */ /* (does not always work correctly, so disabled by default!) */ @@ -70,22 +71,21 @@ #define UNIT_SECOND 0 #define UNIT_MINUTE 1 -#define MODNAME "smsc37b787_wdt: " -#define VERSION "1.1" +#define VERSION "1.1" -#define IOPORT 0x3F0 +#define IOPORT 0x3F0 #define IOPORT_SIZE 2 -#define IODEV_NO 8 +#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 = 0; /* is the timer enabled? */ +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 int nowayout = WATCHDOG_NOWAYOUT; +static bool nowayout = WATCHDOG_NOWAYOUT; /* -- Low level function ----------------------------------------*/ @@ -93,114 +93,121 @@ static int nowayout = WATCHDOG_NOWAYOUT; static inline void open_io_config(void) { - outb(0x55, IOPORT); + outb(0x55, IOPORT); mdelay(1); - outb(0x55, IOPORT); + outb(0x55, IOPORT); } /* lock the IO chip */ static inline void close_io_config(void) { - outb(0xAA, IOPORT); + outb(0xAA, IOPORT); } /* select the IO device */ static inline void select_io_device(unsigned char devno) { - outb(0x07, IOPORT); - outb(devno, IOPORT+1); + 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); + 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); + 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); + /* -- 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); + /* -- 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); + /* -- 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); + /* -- 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); + /* -- 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 occured - // 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); + /* -- 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 ------------------------------------*/ @@ -209,33 +216,34 @@ static inline void wdt_timer_ctrl(unsigned char reg) static void wb_smsc_wdt_initialize(void) { - unsigned char old; + 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 + open_io_config(); + select_io_device(IODEV_NO); - // disable the timeout - wdt_timeout_value(0); + /* 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 control register */ + wdt_timer_ctrl(0x00); - // reset configuration register + /* 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 + /* 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); + /* set the watchdog timer units */ + wdt_timer_units(old); - close_io_config(); + close_io_config(); spin_unlock(&io_lock); } @@ -244,23 +252,23 @@ static void wb_smsc_wdt_initialize(void) static void wb_smsc_wdt_shutdown(void) { spin_lock(&io_lock); - open_io_config(); - select_io_device(IODEV_NO); + open_io_config(); + select_io_device(IODEV_NO); - // disable the watchdog - gpio_bit13(0x09); - gpio_bit12(0x09); + /* disable the watchdog */ + gpio_bit13(0x09); + gpio_bit12(0x09); - // reset watchdog config register + /* reset watchdog config register */ wdt_timer_conf(0x00); - // reset watchdog control register - wdt_timer_ctrl(0x00); + /* reset watchdog control register */ + wdt_timer_ctrl(0x00); - // disable timeout - wdt_timeout_value(0x00); + /* disable timeout */ + wdt_timeout_value(0x00); - close_io_config(); + close_io_config(); spin_unlock(&io_lock); } @@ -269,16 +277,16 @@ static void wb_smsc_wdt_shutdown(void) static void wb_smsc_wdt_set_timeout(unsigned char new_timeout) { spin_lock(&io_lock); - open_io_config(); - select_io_device(IODEV_NO); + 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 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); + /* set timeout value */ + wdt_timeout_value(new_timeout); - close_io_config(); + close_io_config(); spin_unlock(&io_lock); } @@ -286,32 +294,32 @@ static void wb_smsc_wdt_set_timeout(unsigned char new_timeout) static unsigned char wb_smsc_wdt_get_timeout(void) { - unsigned char set_timeout; + 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(); + open_io_config(); + select_io_device(IODEV_NO); + set_timeout = read_io_cr(0xF2); + close_io_config(); spin_unlock(&io_lock); - return set_timeout; + 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); + /* 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); + /* set the current timeout... */ + wb_smsc_wdt_set_timeout(timeout); } /* reset the timer */ @@ -319,14 +327,14 @@ static void wb_smsc_wdt_enable(void) static void wb_smsc_wdt_reset_timer(void) { spin_lock(&io_lock); - open_io_config(); - select_io_device(IODEV_NO); + open_io_config(); + select_io_device(IODEV_NO); - // reset the timer + /* reset the timer */ wdt_timeout_value(timeout); wdt_timer_conf(0x08); - close_io_config(); + close_io_config(); spin_unlock(&io_lock); } @@ -355,7 +363,8 @@ static int wb_smsc_wdt_open(struct inode *inode, struct file *file) /* Reload and activate timer */ wb_smsc_wdt_enable(); - printk(KERN_INFO MODNAME "Watchdog enabled. Timeout set to %d %s.\n", timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); + pr_info("Watchdog enabled. Timeout set to %d %s\n", + timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); return nonseekable_open(inode, file); } @@ -367,10 +376,10 @@ static int wb_smsc_wdt_release(struct inode *inode, struct file *file) /* Shut off the timer. */ if (expect_close == 42) { - wb_smsc_wdt_disable(); - printk(KERN_INFO MODNAME "Watchdog disabled, sleeping again...\n"); + wb_smsc_wdt_disable(); + pr_info("Watchdog disabled, sleeping again...\n"); } else { - printk(KERN_CRIT MODNAME "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); wb_smsc_wdt_reset_timer(); } @@ -392,10 +401,11 @@ static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data, /* reset expect flag */ expect_close = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) + if (get_user(c, data + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -410,8 +420,8 @@ static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data, /* ioctl => control interface */ -static int wb_smsc_wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long wb_smsc_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { int new_timeout; @@ -420,89 +430,73 @@ static int wb_smsc_wdt_ioctl(struct inode *inode, struct file *file, int __user *i; } uarg; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | - WDIOF_SETTIMEOUT | + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, .firmware_version = 0, - .identity = "SMsC 37B787 Watchdog" + .identity = "SMsC 37B787 Watchdog", }; uarg.i = (int __user *)arg; switch (cmd) { - default: - return -ENOTTY; - - 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_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); - - // fall through 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); - - 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; - } + 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 (options & WDIOS_ENABLECARD) { - wb_smsc_wdt_enable(); - retval = 0; - } + if (get_user(options, uarg.i)) + return -EFAULT; - return retval; + 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); + /* fall through 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) +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; + 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; @@ -510,23 +504,20 @@ static int wb_smsc_wdt_notify_sys(struct notifier_block *this, unsigned long cod /* -- Module's structures ---------------------------------------*/ -static const struct file_operations wb_smsc_wdt_fops = -{ - .owner = THIS_MODULE, +static const struct file_operations wb_smsc_wdt_fops = { + .owner = THIS_MODULE, .llseek = no_llseek, .write = wb_smsc_wdt_write, - .ioctl = wb_smsc_wdt_ioctl, + .unlocked_ioctl = wb_smsc_wdt_ioctl, .open = wb_smsc_wdt_open, .release = wb_smsc_wdt_release, }; -static struct notifier_block wb_smsc_wdt_notifier = -{ +static struct notifier_block wb_smsc_wdt_notifier = { .notifier_call = wb_smsc_wdt_notify_sys, }; -static struct miscdevice wb_smsc_wdt_miscdev = -{ +static struct miscdevice wb_smsc_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &wb_smsc_wdt_fops, @@ -540,39 +531,40 @@ static int __init wb_smsc_wdt_init(void) { int ret; - printk("SMsC 37B787 watchdog component driver " VERSION " initialising...\n"); + pr_info("SMsC 37B787 watchdog component driver " + VERSION " initialising...\n"); if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) { - printk(KERN_ERR MODNAME "Unable to register IO port %#x\n", IOPORT); + 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; + /* set new maximum, if it's too big */ + if (timeout > MAX_TIMEOUT) + timeout = MAX_TIMEOUT; - // init the watchdog timer - wb_smsc_wdt_initialize(); + /* init the watchdog timer */ + wb_smsc_wdt_initialize(); ret = register_reboot_notifier(&wb_smsc_wdt_notifier); if (ret) { - printk(KERN_ERR MODNAME "Unable to register reboot notifier err = %d\n", ret); + pr_err("Unable to register reboot notifier err = %d\n", ret); goto out_io; } ret = misc_register(&wb_smsc_wdt_miscdev); if (ret) { - printk(KERN_ERR MODNAME "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); + pr_err("Unable to register miscdev on minor %d\n", + WATCHDOG_MINOR); goto out_rbt; } - // output info - printk(KERN_INFO MODNAME "Timeout set to %d %s.\n", timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); - printk(KERN_INFO MODNAME "Watchdog initialized and sleeping (nowayout=%d)...\n", nowayout); - - // ret = 0 - + /* 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; @@ -591,35 +583,36 @@ out_pnp: static void __exit wb_smsc_wdt_exit(void) { /* Stop the timer before we leave */ - if (!nowayout) - { + if (!nowayout) { wb_smsc_wdt_shutdown(); - printk(KERN_INFO MODNAME "Watchdog disabled.\n"); + pr_info("Watchdog disabled\n"); } misc_deregister(&wb_smsc_wdt_miscdev); unregister_reboot_notifier(&wb_smsc_wdt_notifier); release_region(IOPORT, IOPORT_SIZE); - printk("SMsC 37B787 watchdog component driver removed.\n"); + 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_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version " + VERSION ")"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - #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"); +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, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 index 9c369490924..0dc5e323d59 100644 --- a/drivers/watchdog/softdog.c +++ b/drivers/watchdog/softdog.c @@ -1,8 +1,8 @@ /* - * SoftDog 0.07: A Software Watchdog Device + * SoftDog: A Software Watchdog Device * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -33,42 +33,44 @@ * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. * * 20020530 Joel Becker <joel.becker@oracle.com> - * Added Matt Domsch's nowayout module option. + * Added Matt Domsch's nowayout module option. */ +#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/notifier.h> #include <linux/reboot.h> #include <linux/init.h> #include <linux/jiffies.h> - -#include <asm/uaccess.h> - -#define PFX "SoftDog: " +#include <linux/kernel.h> #define TIMER_MARGIN 60 /* Default is 60 seconds */ -static int soft_margin = TIMER_MARGIN; /* in seconds */ -module_param(soft_margin, int, 0); -MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); - -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -#ifdef ONLY_TESTING -static int soft_noboot = 1; -#else -static int soft_noboot = 0; -#endif /* ONLY_TESTING */ - +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 depends on ONLY_TESTING)"); +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)"); /* * Our timer @@ -78,9 +80,6 @@ static void watchdog_fire(unsigned long); static struct timer_list watchdog_ticktock = TIMER_INITIALIZER(watchdog_fire, 0, 0); -static unsigned long driver_open, orphan_timer; -static char expect_close; - /* * If the timer expires.. @@ -88,16 +87,15 @@ static char expect_close; static void watchdog_fire(unsigned long data) { - if (test_and_clear_bit(0, &orphan_timer)) - module_put(THIS_MODULE); - if (soft_noboot) - printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n"); - else - { - printk(KERN_CRIT PFX "Initiating system reboot.\n"); + 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"); emergency_restart(); - printk(KERN_CRIT PFX "Reboot didn't ?????\n"); + pr_crit("Reboot didn't ?????\n"); } } @@ -105,126 +103,24 @@ static void watchdog_fire(unsigned long data) * Softdog operations */ -static int softdog_keepalive(void) +static int softdog_ping(struct watchdog_device *w) { - mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ)); + mod_timer(&watchdog_ticktock, jiffies+(w->timeout*HZ)); return 0; } -static int softdog_stop(void) +static int softdog_stop(struct watchdog_device *w) { del_timer(&watchdog_ticktock); return 0; } -static int softdog_set_heartbeat(int t) -{ - if ((t < 0x0001) || (t > 0xFFFF)) - return -EINVAL; - - soft_margin = t; - return 0; -} - -/* - * /dev/watchdog handling - */ - -static int softdog_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &driver_open)) - return -EBUSY; - if (!test_and_clear_bit(0, &orphan_timer)) - __module_get(THIS_MODULE); - /* - * Activate timer - */ - softdog_keepalive(); - return nonseekable_open(inode, file); -} - -static int softdog_release(struct inode *inode, struct file *file) +static int softdog_set_timeout(struct watchdog_device *w, unsigned int t) { - /* - * Shut off the timer. - * Lock it in if it's a module and we set nowayout - */ - if (expect_close == 42) { - softdog_stop(); - module_put(THIS_MODULE); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - set_bit(0, &orphan_timer); - softdog_keepalive(); - } - clear_bit(0, &driver_open); - expect_close = 0; + w->timeout = t; return 0; } -static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) -{ - /* - * Refresh the timer. - */ - 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; - } - } - softdog_keepalive(); - } - return len; -} - -static int softdog_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_margin; - static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = "Software Watchdog", - }; - switch (cmd) { - default: - return -ENOTTY; - 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_KEEPALIVE: - softdog_keepalive(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_margin, p)) - return -EFAULT; - if (softdog_set_heartbeat(new_margin)) - return -EINVAL; - softdog_keepalive(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(soft_margin, p); - } -} - /* * Notifier for system down */ @@ -232,10 +128,9 @@ static int softdog_ioctl(struct inode *inode, struct file *file, static int softdog_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code==SYS_DOWN || code==SYS_HALT) { + if (code == SYS_DOWN || code == SYS_HALT) /* Turn the WDT off */ - softdog_stop(); - } + softdog_stop(NULL); return NOTIFY_DONE; } @@ -243,61 +138,65 @@ static int softdog_notify_sys(struct notifier_block *this, unsigned long code, * Kernel Interfaces */ -static const struct file_operations softdog_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = softdog_write, - .ioctl = softdog_ioctl, - .open = softdog_open, - .release = softdog_release, +static struct notifier_block softdog_notifier = { + .notifier_call = softdog_notify_sys, }; -static struct miscdevice softdog_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &softdog_fops, +static struct watchdog_info softdog_info = { + .identity = "Software Watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; -static struct notifier_block softdog_notifier = { - .notifier_call = softdog_notify_sys, +static struct watchdog_ops softdog_ops = { + .owner = THIS_MODULE, + .start = softdog_ping, + .stop = softdog_stop, + .set_timeout = softdog_set_timeout, }; -static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n"; +static struct watchdog_device softdog_dev = { + .info = &softdog_info, + .ops = &softdog_ops, + .min_timeout = 1, + .max_timeout = 0xFFFF +}; static int __init watchdog_init(void) { int ret; - /* Check that the soft_margin value is within it's range ; if not reset to the default */ - if (softdog_set_heartbeat(soft_margin)) { - softdog_set_heartbeat(TIMER_MARGIN); - printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n", + /* Check that the soft_margin value is within it's range; + if not reset to the default */ + if (soft_margin < 1 || soft_margin > 65535) { + pr_info("soft_margin must be 0 < soft_margin < 65536, using %d\n", TIMER_MARGIN); + return -EINVAL; } + softdog_dev.timeout = soft_margin; + + watchdog_set_nowayout(&softdog_dev, nowayout); ret = register_reboot_notifier(&softdog_notifier); if (ret) { - printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); return ret; } - ret = misc_register(&softdog_miscdev); + ret = watchdog_register_device(&softdog_dev); if (ret) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); unregister_reboot_notifier(&softdog_notifier); return ret; } - printk(banner, soft_noboot, soft_margin, nowayout); + pr_info("Software Watchdog Timer: 0.08 initialized. soft_noboot=%d soft_margin=%d sec soft_panic=%d (nowayout=%d)\n", + soft_noboot, soft_margin, soft_panic, nowayout); return 0; } static void __exit watchdog_exit(void) { - misc_deregister(&softdog_miscdev); + watchdog_unregister_device(&softdog_dev); unregister_reboot_notifier(&softdog_notifier); } @@ -307,4 +206,3 @@ module_exit(watchdog_exit); MODULE_AUTHOR("Alan Cox"); MODULE_DESCRIPTION("Software Watchdog Device Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c new file mode 100644 index 00000000000..5cca9cddb87 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.c @@ -0,0 +1,582 @@ +/* + * 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. + * http://www.kernelconcepts.de + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * See AMD Publication 43009 "AMD SB700/710/750 Register Reference Guide", + * AMD Publication 45482 "AMD SB800-Series Southbridges Register + * Reference Guide" + */ + +/* + * 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/platform_device.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "sp5100_tco.h" + +/* Module and version information */ +#define TCO_VERSION "0.05" +#define TCO_MODULE_NAME "SP5100 TCO timer" +#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION + +/* internal variables */ +static u32 tcobase_phys; +static u32 tco_wdt_fired; +static void __iomem *tcobase; +static unsigned int pm_iobase; +static DEFINE_SPINLOCK(tco_lock); /* Guards the hardware */ +static unsigned long timer_alive; +static char tco_expect_close; +static struct pci_dev *sp5100_tco_pci; + +/* the watchdog platform device */ +static struct platform_device *sp5100_tco_platform_device; + +/* module parameters */ + +#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 void tco_timer_start(void) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&tco_lock, flags); + val = readl(SP5100_WDT_CONTROL(tcobase)); + val |= SP5100_WDT_START_STOP_BIT; + writel(val, SP5100_WDT_CONTROL(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 = readl(SP5100_WDT_CONTROL(tcobase)); + val &= ~SP5100_WDT_START_STOP_BIT; + writel(val, SP5100_WDT_CONTROL(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); +} + +static void tco_timer_keepalive(void) +{ + u32 val; + unsigned long flags; + + spin_lock_irqsave(&tco_lock, flags); + val = readl(SP5100_WDT_CONTROL(tcobase)); + val |= SP5100_WDT_TRIGGER_BIT; + writel(val, SP5100_WDT_CONTROL(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); +} + +static int tco_timer_set_heartbeat(int t) +{ + unsigned long flags; + + if (t < 0 || t > 0xffff) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + spin_lock_irqsave(&tco_lock, flags); + writel(t, SP5100_WDT_COUNT(tcobase)); + spin_unlock_irqrestore(&tco_lock, flags); + + heartbeat = t; + return 0; +} + +static void tco_timer_enable(void) +{ + int val; + + if (sp5100_tco_pci->revision >= 0x40) { + /* For SB800 or later */ + /* Set the Watchdog timer resolution to 1 sec */ + outb(SB800_PM_WATCHDOG_CONFIG, SB800_IO_PM_INDEX_REG); + val = inb(SB800_IO_PM_DATA_REG); + val |= SB800_PM_WATCHDOG_SECOND_RES; + outb(val, SB800_IO_PM_DATA_REG); + + /* Enable watchdog decode bit and watchdog timer */ + outb(SB800_PM_WATCHDOG_CONTROL, SB800_IO_PM_INDEX_REG); + val = inb(SB800_IO_PM_DATA_REG); + val |= SB800_PCI_WATCHDOG_DECODE_EN; + val &= ~SB800_PM_WATCHDOG_DISABLE; + outb(val, SB800_IO_PM_DATA_REG); + } else { + /* 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 */ + outb(SP5100_PM_WATCHDOG_CONTROL, SP5100_IO_PM_INDEX_REG); + val = inb(SP5100_IO_PM_DATA_REG); + val |= SP5100_PM_WATCHDOG_SECOND_RES; + val &= ~SP5100_PM_WATCHDOG_DISABLE; + outb(val, SP5100_IO_PM_DATA_REG); + } +} + +/* + * /dev/watchdog handling + */ + +static int sp5100_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_start(); + tco_timer_keepalive(); + return nonseekable_open(inode, file); +} + +static int sp5100_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 sp5100_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 sp5100_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_start(); + tco_timer_keepalive(); + 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(); + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations sp5100_tco_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sp5100_tco_write, + .unlocked_ioctl = sp5100_tco_ioctl, + .open = sp5100_tco_open, + .release = sp5100_tco_release, +}; + +static struct miscdevice sp5100_tco_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sp5100_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 + * 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, }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, sp5100_tco_pci_tbl); + +/* + * Init & exit routines + */ +static unsigned char sp5100_tco_setupdevice(void) +{ + struct pci_dev *dev = NULL; + const char *dev_name = NULL; + u32 val; + u32 index_reg, data_reg, base_addr; + + /* 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 0; + + pr_info("PCI Revision ID: 0x%x\n", sp5100_tco_pci->revision); + + /* + * Determine type of southbridge chipset. + */ + if (sp5100_tco_pci->revision >= 0x40) { + dev_name = SB800_DEVNAME; + index_reg = SB800_IO_PM_INDEX_REG; + data_reg = SB800_IO_PM_DATA_REG; + base_addr = SB800_PM_WATCHDOG_BASE; + } else { + dev_name = SP5100_DEVNAME; + index_reg = SP5100_IO_PM_INDEX_REG; + data_reg = SP5100_IO_PM_DATA_REG; + base_addr = SP5100_PM_WATCHDOG_BASE; + } + + /* Request the IO ports used by this driver */ + pm_iobase = SP5100_IO_PM_INDEX_REG; + if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, dev_name)) { + pr_err("I/O address 0x%04x already in use\n", pm_iobase); + goto exit; + } + + /* + * First, Find the watchdog timer MMIO address from indirect I/O. + */ + outb(base_addr+3, index_reg); + val = inb(data_reg); + outb(base_addr+2, index_reg); + val = val << 8 | inb(data_reg); + outb(base_addr+1, index_reg); + val = val << 8 | inb(data_reg); + outb(base_addr+0, index_reg); + /* Low three bits of BASE are reserved */ + val = val << 8 | (inb(data_reg) & 0xf8); + + pr_debug("Got 0x%04x from indirect I/O\n", val); + + /* Check MMIO address conflict */ + if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) + goto setup_wdt; + else + pr_debug("MMIO address 0x%04x already in use\n", val); + + /* + * Secondly, Find the watchdog timer MMIO address + * from SBResource_MMIO register. + */ + if (sp5100_tco_pci->revision >= 0x40) { + /* Read SBResource_MMIO from AcpiMmioEn(PM_Reg: 24h) */ + outb(SB800_PM_ACPI_MMIO_EN+3, SB800_IO_PM_INDEX_REG); + val = inb(SB800_IO_PM_DATA_REG); + outb(SB800_PM_ACPI_MMIO_EN+2, SB800_IO_PM_INDEX_REG); + val = val << 8 | inb(SB800_IO_PM_DATA_REG); + outb(SB800_PM_ACPI_MMIO_EN+1, SB800_IO_PM_INDEX_REG); + val = val << 8 | inb(SB800_IO_PM_DATA_REG); + outb(SB800_PM_ACPI_MMIO_EN+0, SB800_IO_PM_INDEX_REG); + val = val << 8 | inb(SB800_IO_PM_DATA_REG); + } else { + /* Read SBResource_MMIO from PCI config(PCI_Reg: 9Ch) */ + pci_read_config_dword(sp5100_tco_pci, + SP5100_SB_RESOURCE_MMIO_BASE, &val); + } + + /* The SBResource_MMIO is enabled and mapped memory space? */ + if ((val & (SB800_ACPI_MMIO_DECODE_EN | SB800_ACPI_MMIO_SEL)) == + SB800_ACPI_MMIO_DECODE_EN) { + /* Clear unnecessary the low twelve bits */ + val &= ~0xFFF; + /* Add the Watchdog Timer offset to base address. */ + val += SB800_PM_WDT_MMIO_OFFSET; + /* Check MMIO address conflict */ + if (request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, + dev_name)) { + pr_debug("Got 0x%04x from SBResource_MMIO register\n", + val); + goto setup_wdt; + } else + pr_debug("MMIO address 0x%04x already in use\n", val); + } else + pr_debug("SBResource_MMIO is disabled(0x%04x)\n", val); + + pr_notice("failed to find MMIO address, giving up.\n"); + goto unreg_region; + +setup_wdt: + tcobase_phys = val; + + tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); + if (!tcobase) { + pr_err("failed to get tcobase address\n"); + goto unreg_mem_region; + } + + pr_info("Using 0x%04x for watchdog MMIO address\n", val); + + /* Setup the watchdog timer */ + tco_timer_enable(); + + /* Check that the watchdog action is set to reset the system */ + val = readl(SP5100_WDT_CONTROL(tcobase)); + /* + * Save WatchDogFired status, because WatchDogFired flag is + * cleared here. + */ + tco_wdt_fired = val & SP5100_PM_WATCHDOG_FIRED; + val &= ~SP5100_PM_WATCHDOG_ACTION_RESET; + writel(val, SP5100_WDT_CONTROL(tcobase)); + + /* Set a reasonable heartbeat before we stop the timer */ + tco_timer_set_heartbeat(heartbeat); + + /* + * Stop the TCO before we change anything so we don't race with + * a zeroed timer. + */ + tco_timer_stop(); + + /* Done */ + return 1; + +unreg_mem_region: + release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); +unreg_region: + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); +exit: + return 0; +} + +static int sp5100_tco_init(struct platform_device *dev) +{ + int ret; + + /* + * Check whether or not the hardware watchdog is there. If found, then + * set it up. + */ + if (!sp5100_tco_setupdevice()) + return -ENODEV; + + /* Check to see if last reboot was due to watchdog timeout */ + pr_info("Last reboot was %striggered by watchdog.\n", + tco_wdt_fired ? "" : "not "); + + /* + * 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); + } + + ret = misc_register(&sp5100_tco_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto exit; + } + + clear_bit(0, &timer_alive); + + /* Show module parameters */ + pr_info("initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n", + tcobase, heartbeat, nowayout); + + return 0; + +exit: + iounmap(tcobase); + release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); + return ret; +} + +static void sp5100_tco_cleanup(void) +{ + /* Stop the timer before we leave */ + if (!nowayout) + tco_timer_stop(); + + /* Deregister */ + misc_deregister(&sp5100_tco_miscdev); + iounmap(tcobase); + release_mem_region(tcobase_phys, SP5100_WDT_MEM_MAP_SIZE); + release_region(pm_iobase, SP5100_PM_IOPORTS_SIZE); +} + +static int sp5100_tco_remove(struct platform_device *dev) +{ + if (tcobase) + sp5100_tco_cleanup(); + return 0; +} + +static void sp5100_tco_shutdown(struct platform_device *dev) +{ + tco_timer_stop(); +} + +static struct platform_driver sp5100_tco_driver = { + .probe = sp5100_tco_init, + .remove = sp5100_tco_remove, + .shutdown = sp5100_tco_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = TCO_MODULE_NAME, + }, +}; + +static int __init sp5100_tco_init_module(void) +{ + int err; + + pr_info("SP5100/SB800 TCO WatchDog Timer Driver v%s\n", TCO_VERSION); + + err = platform_driver_register(&sp5100_tco_driver); + if (err) + return err; + + sp5100_tco_platform_device = platform_device_register_simple( + TCO_MODULE_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_cleanup_module(void) +{ + platform_device_unregister(sp5100_tco_platform_device); + platform_driver_unregister(&sp5100_tco_driver); + pr_info("SP5100/SB800 TCO Watchdog Module Unloaded\n"); +} + +module_init(sp5100_tco_init_module); +module_exit(sp5100_tco_cleanup_module); + +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 00000000000..2b28c00da0d --- /dev/null +++ b/drivers/watchdog/sp5100_tco.h @@ -0,0 +1,65 @@ +/* + * sp5100_tco: TCO timer driver for sp5100 chipsets. + * + * (c) Copyright 2009 Google Inc., All Rights Reserved. + * + * TCO timer driver for sp5100 chipsets + */ + +/* + * 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 (1 << 0) +#define SP5100_WDT_TRIGGER_BIT (1 << 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 chipset */ +#define SP5100_IO_PM_INDEX_REG 0xCD6 +#define SP5100_IO_PM_DATA_REG 0xCD7 + +#define SP5100_SB_RESOURCE_MMIO_BASE 0x9C + +#define SP5100_PM_WATCHDOG_CONTROL 0x69 +#define SP5100_PM_WATCHDOG_BASE 0x6C + +#define SP5100_PM_WATCHDOG_FIRED (1 << 1) +#define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) + +#define SP5100_PCI_WATCHDOG_MISC_REG 0x41 +#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3) + +#define SP5100_PM_WATCHDOG_DISABLE (1 << 0) +#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) + +#define SP5100_DEVNAME "SP5100 TCO" + + +/* For SB8x0(or later) chipset */ +#define SB800_IO_PM_INDEX_REG 0xCD6 +#define SB800_IO_PM_DATA_REG 0xCD7 + +#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 (1 << 0) +#define SB800_PM_WATCHDOG_DISABLE (1 << 2) +#define SB800_PM_WATCHDOG_SECOND_RES (3 << 0) +#define SB800_ACPI_MMIO_DECODE_EN (1 << 0) +#define SB800_ACPI_MMIO_SEL (1 << 1) + + +#define SB800_PM_WDT_MMIO_OFFSET 0xB00 + +#define SB800_DEVNAME "SB800 TCO" diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c new file mode 100644 index 00000000000..c1b03f4235b --- /dev/null +++ b/drivers/watchdog/sp805_wdt.c @@ -0,0 +1,308 @@ +/* + * drivers/char/watchdog/sp805-wdt.c + * + * Watchdog driver for ARM SP805 watchdog module + * + * Copyright (C) 2010 ST Microelectronics + * Viresh Kumar <viresh.linux@gmail.com> + * + * 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/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 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: clock structure of wdt + * @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; + 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"); + +/* This routine finds load value that will reset system in required timout */ +static int wdt_setload(struct watchdog_device *wdd, unsigned int timeout) +{ + struct sp805_wdt *wdt = watchdog_get_drvdata(wdd); + u64 load, rate; + + rate = clk_get_rate(wdt->clk); + + /* + * 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, rate; + + rate = clk_get_rate(wdt->clk); + + 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, rate); +} + +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); + + if (!ping) { + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + 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, +}; + +static int +sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) +{ + struct sp805_wdt *wdt; + 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); + + wdt->clk = devm_clk_get(&adev->dev, NULL); + if (IS_ERR(wdt->clk)) { + dev_warn(&adev->dev, "Clock not found\n"); + ret = PTR_ERR(wdt->clk); + goto err; + } + + wdt->adev = adev; + wdt->wdd.info = &wdt_info; + wdt->wdd.ops = &wdt_ops; + + spin_lock_init(&wdt->lock); + watchdog_set_nowayout(&wdt->wdd, nowayout); + watchdog_set_drvdata(&wdt->wdd, wdt); + wdt_setload(&wdt->wdd, DEFAULT_TIMEOUT); + + ret = watchdog_register_device(&wdt->wdd); + if (ret) { + dev_err(&adev->dev, "watchdog_register_device() failed: %d\n", + 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 int 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); + + return 0; +} + +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 struct amba_id sp805_wdt_ids[] = { + { + .id = 0x00141805, + .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 <viresh.linux@gmail.com>"); +MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/stmp3xxx_rtc_wdt.c b/drivers/watchdog/stmp3xxx_rtc_wdt.c new file mode 100644 index 00000000000..3804d5e9bae --- /dev/null +++ b/drivers/watchdog/stmp3xxx_rtc_wdt.c @@ -0,0 +1,108 @@ +/* + * Watchdog driver for the RTC based watchdog in STMP3xxx and i.MX23/28 + * + * Author: Wolfram Sang <w.sang@pengutronix.de> + * + * Copyright (C) 2011-12 Wolfram Sang, Pengutronix + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/stmp3xxx_rtc_wdt.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 stmp3xxx_wdt_probe(struct platform_device *pdev) +{ + int ret; + + watchdog_set_drvdata(&stmp3xxx_wdd, &pdev->dev); + + stmp3xxx_wdd.timeout = clamp_t(unsigned, heartbeat, 1, STMP3XXX_MAX_TIMEOUT); + + ret = watchdog_register_device(&stmp3xxx_wdd); + if (ret < 0) { + dev_err(&pdev->dev, "cannot register watchdog device\n"); + return ret; + } + + dev_info(&pdev->dev, "initialized watchdog with heartbeat %ds\n", + stmp3xxx_wdd.timeout); + return 0; +} + +static int stmp3xxx_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&stmp3xxx_wdd); + return 0; +} + +static struct platform_driver stmp3xxx_wdt_driver = { + .driver = { + .name = "stmp3xxx_rtc_wdt", + }, + .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 <w.sang@pengutronix.de>"); diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c new file mode 100644 index 00000000000..693b9d2c6e3 --- /dev/null +++ b/drivers/watchdog/sunxi_wdt.c @@ -0,0 +1,237 @@ +/* + * sunxi Watchdog Driver + * + * Copyright (c) 2013 Carlo Caione + * 2012 Henrik Nordstrom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Based on xen_wdt.c + * (c) Copyright 2010 Novell, Inc. + */ + +#include <linux/clk.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/platform_device.h> +#include <linux/types.h> +#include <linux/watchdog.h> + +#define WDT_MAX_TIMEOUT 16 +#define WDT_MIN_TIMEOUT 1 +#define WDT_MODE_TIMEOUT(n) ((n) << 3) +#define WDT_TIMEOUT_MASK WDT_MODE_TIMEOUT(0x0F) + +#define WDT_CTRL 0x00 +#define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) + +#define WDT_MODE 0x04 +#define WDT_MODE_EN (1 << 0) +#define WDT_MODE_RST_EN (1 << 1) + +#define DRV_NAME "sunxi-wdt" +#define DRV_VERSION "1.0" + +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned int timeout = WDT_MAX_TIMEOUT; + +struct sunxi_wdt_dev { + struct watchdog_device wdt_dev; + void __iomem *wdt_base; +}; + +/* + * wdt_timeout_map maps the watchdog timer interval value in seconds to + * the value of the register WDT_MODE bit 3:6 + * + * [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_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; + + iowrite32(WDT_CTRL_RELOAD, wdt_base + 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; + u32 reg; + + if (wdt_timeout_map[timeout] == 0) + timeout++; + + sunxi_wdt->wdt_dev.timeout = timeout; + + reg = ioread32(wdt_base + WDT_MODE); + reg &= ~WDT_TIMEOUT_MASK; + reg |= WDT_MODE_TIMEOUT(wdt_timeout_map[timeout]); + iowrite32(reg, wdt_base + 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; + + iowrite32(0, wdt_base + 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; + int ret; + + ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev, + sunxi_wdt->wdt_dev.timeout); + if (ret < 0) + return ret; + + reg = ioread32(wdt_base + WDT_MODE); + reg |= (WDT_MODE_RST_EN | WDT_MODE_EN); + iowrite32(reg, wdt_base + 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, +}; + +static int sunxi_wdt_probe(struct platform_device *pdev) +{ + struct sunxi_wdt_dev *sunxi_wdt; + struct resource *res; + int err; + + sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL); + if (!sunxi_wdt) + return -EINVAL; + + platform_set_drvdata(pdev, sunxi_wdt); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); + 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 = &pdev->dev; + + watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); + watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); + + watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); + + sunxi_wdt_stop(&sunxi_wdt->wdt_dev); + + err = watchdog_register_device(&sunxi_wdt->wdt_dev); + if (unlikely(err)) + return err; + + dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", + sunxi_wdt->wdt_dev.timeout, nowayout); + + return 0; +} + +static int sunxi_wdt_remove(struct platform_device *pdev) +{ + struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(&sunxi_wdt->wdt_dev); + watchdog_set_drvdata(&sunxi_wdt->wdt_dev, NULL); + + return 0; +} + +static void sunxi_wdt_shutdown(struct platform_device *pdev) +{ + struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev); + + sunxi_wdt_stop(&sunxi_wdt->wdt_dev); +} + +static const struct of_device_id sunxi_wdt_dt_ids[] = { + { .compatible = "allwinner,sun4i-a10-wdt" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); + +static struct platform_driver sunxi_wdt_driver = { + .probe = sunxi_wdt_probe, + .remove = sunxi_wdt_remove, + .shutdown = sunxi_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .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 00000000000..750e2a26cb1 --- /dev/null +++ b/drivers/watchdog/tegra_wdt.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#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)) + 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 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 watchdog_device *wdd; + struct tegra_wdt *wdt; + struct resource *res; + void __iomem *regs; + int ret; + + /* This is the timer base. */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(&pdev->dev, res); + 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(&pdev->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; + + watchdog_set_drvdata(wdd, wdt); + + watchdog_set_nowayout(wdd, nowayout); + + ret = watchdog_register_device(wdd); + if (ret) { + dev_err(&pdev->dev, + "failed to register watchdog device\n"); + return ret; + } + + platform_set_drvdata(pdev, wdt); + + dev_info(&pdev->dev, + "initialized (heartbeat = %d sec, nowayout = %d)\n", + heartbeat, nowayout); + + return 0; +} + +static int tegra_wdt_remove(struct platform_device *pdev) +{ + struct tegra_wdt *wdt = platform_get_drvdata(pdev); + + tegra_wdt_stop(&wdt->wdd); + + watchdog_unregister_device(&wdt->wdd); + + dev_info(&pdev->dev, "removed wdt\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra_wdt_runtime_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_runtime_resume(struct device *dev) +{ + struct tegra_wdt *wdt = dev_get_drvdata(dev); + + if (watchdog_active(&wdt->wdd)) + tegra_wdt_start(&wdt->wdd); + + return 0; +} +#endif + +static const struct of_device_id tegra_wdt_of_match[] = { + { .compatible = "nvidia,tegra30-timer", }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_wdt_of_match); + +static const struct dev_pm_ops tegra_wdt_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tegra_wdt_runtime_suspend, + tegra_wdt_runtime_resume) +}; + +static struct platform_driver tegra_wdt_driver = { + .probe = tegra_wdt_probe, + .remove = tegra_wdt_remove, + .driver = { + .owner = THIS_MODULE, + .name = "tegra-wdt", + .pm = &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/ts72xx_wdt.c b/drivers/watchdog/ts72xx_wdt.c new file mode 100644 index 00000000000..afa9d6ef353 --- /dev/null +++ b/drivers/watchdog/ts72xx_wdt.c @@ -0,0 +1,452 @@ +/* + * 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/fs.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> + +#define TS72XX_WDT_FEED_VAL 0x05 +#define TS72XX_WDT_DEFAULT_TIMEOUT 8 + +static int timeout = TS72XX_WDT_DEFAULT_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. " + "(1 <= timeout <= 8, default=" + __MODULE_STRING(TS72XX_WDT_DEFAULT_TIMEOUT) + ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +/** + * struct ts72xx_wdt - watchdog control structure + * @lock: lock that protects this structure + * @regval: watchdog timeout value suitable for control register + * @flags: flags controlling watchdog device state + * @control_reg: watchdog control register + * @feed_reg: watchdog feed register + * @pdev: back pointer to platform dev + */ +struct ts72xx_wdt { + struct mutex lock; + int regval; + +#define TS72XX_WDT_BUSY_FLAG 1 +#define TS72XX_WDT_EXPECT_CLOSE_FLAG 2 + int flags; + + void __iomem *control_reg; + void __iomem *feed_reg; + + struct platform_device *pdev; +}; + +static struct platform_device *ts72xx_wdt_pdev; + +/* + * TS-72xx Watchdog supports following timeouts (value written + * to control register): + * value description + * ------------------------- + * 0x00 watchdog disabled + * 0x01 250ms + * 0x02 500ms + * 0x03 1s + * 0x04 reserved + * 0x05 2s + * 0x06 4s + * 0x07 8s + * + * Timeouts below 1s are not very usable so we don't + * allow them at all. + * + * We provide two functions that convert between these: + * timeout_to_regval() and regval_to_timeout(). + */ +static const struct { + int timeout; + int regval; +} ts72xx_wdt_map[] = { + { 1, 3 }, + { 2, 5 }, + { 4, 6 }, + { 8, 7 }, +}; + +/** + * timeout_to_regval() - converts given timeout to control register value + * @new_timeout: timeout in seconds to be converted + * + * Function converts given @new_timeout into valid value that can + * be programmed into watchdog control register. When conversion is + * not possible, function returns %-EINVAL. + */ +static int timeout_to_regval(int new_timeout) +{ + int i; + + /* first limit it to 1 - 8 seconds */ + new_timeout = clamp_val(new_timeout, 1, 8); + + for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { + if (ts72xx_wdt_map[i].timeout >= new_timeout) + return ts72xx_wdt_map[i].regval; + } + + return -EINVAL; +} + +/** + * regval_to_timeout() - converts control register value to timeout + * @regval: control register value to be converted + * + * Function converts given @regval to timeout in seconds (1, 2, 4 or 8). + * If @regval cannot be converted, function returns %-EINVAL. + */ +static int regval_to_timeout(int regval) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { + if (ts72xx_wdt_map[i].regval == regval) + return ts72xx_wdt_map[i].timeout; + } + + return -EINVAL; +} + +/** + * ts72xx_wdt_kick() - kick the watchdog + * @wdt: watchdog to be kicked + * + * Called with @wdt->lock held. + */ +static inline void ts72xx_wdt_kick(struct ts72xx_wdt *wdt) +{ + __raw_writeb(TS72XX_WDT_FEED_VAL, wdt->feed_reg); +} + +/** + * ts72xx_wdt_start() - starts the watchdog timer + * @wdt: watchdog to be started + * + * This function programs timeout to watchdog timer + * and starts it. + * + * Called with @wdt->lock held. + */ +static void ts72xx_wdt_start(struct ts72xx_wdt *wdt) +{ + /* + * To program the wdt, it first must be "fed" and + * only after that (within 30 usecs) the configuration + * can be changed. + */ + ts72xx_wdt_kick(wdt); + __raw_writeb((u8)wdt->regval, wdt->control_reg); +} + +/** + * ts72xx_wdt_stop() - stops the watchdog timer + * @wdt: watchdog to be stopped + * + * Called with @wdt->lock held. + */ +static void ts72xx_wdt_stop(struct ts72xx_wdt *wdt) +{ + ts72xx_wdt_kick(wdt); + __raw_writeb(0, wdt->control_reg); +} + +static int ts72xx_wdt_open(struct inode *inode, struct file *file) +{ + struct ts72xx_wdt *wdt = platform_get_drvdata(ts72xx_wdt_pdev); + int regval; + + /* + * Try to convert default timeout to valid register + * value first. + */ + regval = timeout_to_regval(timeout); + if (regval < 0) { + dev_err(&wdt->pdev->dev, + "failed to convert timeout (%d) to register value\n", + timeout); + return regval; + } + + if (mutex_lock_interruptible(&wdt->lock)) + return -ERESTARTSYS; + + if ((wdt->flags & TS72XX_WDT_BUSY_FLAG) != 0) { + mutex_unlock(&wdt->lock); + return -EBUSY; + } + + wdt->flags = TS72XX_WDT_BUSY_FLAG; + wdt->regval = regval; + file->private_data = wdt; + + ts72xx_wdt_start(wdt); + + mutex_unlock(&wdt->lock); + return nonseekable_open(inode, file); +} + +static int ts72xx_wdt_release(struct inode *inode, struct file *file) +{ + struct ts72xx_wdt *wdt = file->private_data; + + if (mutex_lock_interruptible(&wdt->lock)) + return -ERESTARTSYS; + + if ((wdt->flags & TS72XX_WDT_EXPECT_CLOSE_FLAG) != 0) { + ts72xx_wdt_stop(wdt); + } else { + dev_warn(&wdt->pdev->dev, + "TS-72XX WDT device closed unexpectly. " + "Watchdog timer will not stop!\n"); + /* + * Kick it one more time, to give userland some time + * to recover (for example, respawning the kicker + * daemon). + */ + ts72xx_wdt_kick(wdt); + } + + wdt->flags = 0; + + mutex_unlock(&wdt->lock); + return 0; +} + +static ssize_t ts72xx_wdt_write(struct file *file, + const char __user *data, + size_t len, + loff_t *ppos) +{ + struct ts72xx_wdt *wdt = file->private_data; + + if (!len) + return 0; + + if (mutex_lock_interruptible(&wdt->lock)) + return -ERESTARTSYS; + + ts72xx_wdt_kick(wdt); + + /* + * Support for magic character closing. User process + * writes 'V' into the device, just before it is closed. + * This means that we know that the wdt timer can be + * stopped after user closes the device. + */ + if (!nowayout) { + int i; + + for (i = 0; i < len; i++) { + char c; + + /* In case it was set long ago */ + wdt->flags &= ~TS72XX_WDT_EXPECT_CLOSE_FLAG; + + if (get_user(c, data + i)) { + mutex_unlock(&wdt->lock); + return -EFAULT; + } + if (c == 'V') { + wdt->flags |= TS72XX_WDT_EXPECT_CLOSE_FLAG; + break; + } + } + } + + mutex_unlock(&wdt->lock); + return len; +} + +static const struct watchdog_info winfo = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "TS-72XX WDT", +}; + +static long ts72xx_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct ts72xx_wdt *wdt = file->private_data; + void __user *argp = (void __user *)arg; + int __user *p = (int __user *)argp; + int error = 0; + + if (mutex_lock_interruptible(&wdt->lock)) + return -ERESTARTSYS; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &winfo, sizeof(winfo))) + error = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + error = put_user(0, p); + break; + + case WDIOC_KEEPALIVE: + ts72xx_wdt_kick(wdt); + break; + + case WDIOC_SETOPTIONS: { + int options; + + error = get_user(options, p); + if (error) + break; + + error = -EINVAL; + + if ((options & WDIOS_DISABLECARD) != 0) { + ts72xx_wdt_stop(wdt); + error = 0; + } + if ((options & WDIOS_ENABLECARD) != 0) { + ts72xx_wdt_start(wdt); + error = 0; + } + + break; + } + + case WDIOC_SETTIMEOUT: { + int new_timeout; + int regval; + + error = get_user(new_timeout, p); + if (error) + break; + + regval = timeout_to_regval(new_timeout); + if (regval < 0) { + error = regval; + break; + } + ts72xx_wdt_stop(wdt); + wdt->regval = regval; + ts72xx_wdt_start(wdt); + + /*FALLTHROUGH*/ + } + + case WDIOC_GETTIMEOUT: + error = put_user(regval_to_timeout(wdt->regval), p); + break; + + default: + error = -ENOTTY; + break; + } + + mutex_unlock(&wdt->lock); + return error; +} + +static const struct file_operations ts72xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = ts72xx_wdt_open, + .release = ts72xx_wdt_release, + .write = ts72xx_wdt_write, + .unlocked_ioctl = ts72xx_wdt_ioctl, +}; + +static struct miscdevice ts72xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ts72xx_wdt_fops, +}; + +static int ts72xx_wdt_probe(struct platform_device *pdev) +{ + struct ts72xx_wdt *wdt; + struct resource *r1, *r2; + int error = 0; + + wdt = devm_kzalloc(&pdev->dev, sizeof(struct ts72xx_wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt->control_reg = devm_ioremap_resource(&pdev->dev, r1); + if (IS_ERR(wdt->control_reg)) + return PTR_ERR(wdt->control_reg); + + r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + wdt->feed_reg = devm_ioremap_resource(&pdev->dev, r2); + if (IS_ERR(wdt->feed_reg)) + return PTR_ERR(wdt->feed_reg); + + platform_set_drvdata(pdev, wdt); + ts72xx_wdt_pdev = pdev; + wdt->pdev = pdev; + mutex_init(&wdt->lock); + + /* make sure that the watchdog is disabled */ + ts72xx_wdt_stop(wdt); + + error = misc_register(&ts72xx_wdt_miscdev); + if (error) { + dev_err(&pdev->dev, "failed to register miscdev\n"); + return error; + } + + dev_info(&pdev->dev, "TS-72xx Watchdog driver\n"); + + return 0; +} + +static int ts72xx_wdt_remove(struct platform_device *pdev) +{ + int error; + + error = misc_deregister(&ts72xx_wdt_miscdev); + + return error; +} + +static struct platform_driver ts72xx_wdt_driver = { + .probe = ts72xx_wdt_probe, + .remove = ts72xx_wdt_remove, + .driver = { + .name = "ts72xx-wdt", + .owner = THIS_MODULE, + }, +}; + +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 00000000000..2d4535dc267 --- /dev/null +++ b/drivers/watchdog/twl4030_wdt.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) Nokia Corporation + * + * Written by Timo Kokkonen <timo.t.kokkonen at nokia.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/i2c/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_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) +{ + int ret = 0; + struct watchdog_device *wdt; + + wdt = devm_kzalloc(&pdev->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; + + watchdog_set_nowayout(wdt, nowayout); + platform_set_drvdata(pdev, wdt); + + twl4030_wdt_stop(wdt); + + ret = watchdog_register_device(wdt); + if (ret) + return ret; + + return 0; +} + +static int twl4030_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(wdt); + + return 0; +} + +#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, + .remove = twl4030_wdt_remove, + .suspend = twl4030_wdt_suspend, + .resume = twl4030_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .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 index 328b3c7211e..6a447e321dd 100644 --- a/drivers/watchdog/txx9wdt.c +++ b/drivers/watchdog/txx9wdt.c @@ -7,183 +7,98 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ + +#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/reboot.h> #include <linux/init.h> -#include <linux/uaccess.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 int timeout = TIMER_MARGIN; /* in seconds */ -module_param(timeout, int, 0); +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 int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, 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) ")"); -#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) - -static unsigned long txx9wdt_alive; -static int expect_close; static struct txx9_tmr_reg __iomem *txx9wdt_reg; static struct clk *txx9_imclk; +static DEFINE_SPINLOCK(txx9_lock); -static void txx9wdt_ping(void) +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 void txx9wdt_start(void) +static int txx9wdt_start(struct watchdog_device *wdt_dev) { - __raw_writel(WD_TIMER_CLK * timeout, &txx9wdt_reg->cpra); + 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 void txx9wdt_stop(void) +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); -} - -static int txx9wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &txx9wdt_alive)) - return -EBUSY; - - if (__raw_readl(&txx9wdt_reg->tcr) & TXx9_TMTCR_TCE) { - clear_bit(0, &txx9wdt_alive); - return -EBUSY; - } - - if (nowayout) - __module_get(THIS_MODULE); - - txx9wdt_start(); - return nonseekable_open(inode, file); -} - -static int txx9wdt_release(struct inode *inode, struct file *file) -{ - if (expect_close) - txx9wdt_stop(); - else { - printk(KERN_CRIT "txx9wdt: " - "Unexpected close, not stopping watchdog!\n"); - txx9wdt_ping(); - } - clear_bit(0, &txx9wdt_alive); - expect_close = 0; + spin_unlock(&txx9_lock); return 0; } -static ssize_t txx9wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) +static int txx9wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int new_timeout) { - 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 = 1; - } - } - txx9wdt_ping(); - } - return len; -} - -static int txx9wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_timeout; - static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .firmware_version = 0, - .identity = "Hardware Watchdog for TXx9", - }; - - switch (cmd) { - default: - return -ENOTTY; - 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_KEEPALIVE: - txx9wdt_ping(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_timeout, p)) - return -EFAULT; - if (new_timeout < 1 || new_timeout > WD_MAX_TIMEOUT) - return -EINVAL; - timeout = new_timeout; - txx9wdt_stop(); - txx9wdt_start(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - } -} - -static int txx9wdt_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) -{ - if (code == SYS_DOWN || code == SYS_HALT) - txx9wdt_stop(); - return NOTIFY_DONE; + wdt_dev->timeout = new_timeout; + txx9wdt_stop(wdt_dev); + txx9wdt_start(wdt_dev); + return 0; } -static const struct file_operations txx9wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = txx9wdt_write, - .ioctl = txx9wdt_ioctl, - .open = txx9wdt_open, - .release = txx9wdt_release, +static const struct watchdog_info txx9wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Hardware Watchdog for TXx9", }; -static struct miscdevice txx9wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &txx9wdt_fops, +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 notifier_block txx9wdt_notifier = { - .notifier_call = txx9wdt_notify_sys +static struct watchdog_device txx9wdt = { + .info = &txx9wdt_info, + .ops = &txx9wdt_ops, }; static int __init txx9wdt_probe(struct platform_device *dev) @@ -205,34 +120,27 @@ static int __init txx9wdt_probe(struct platform_device *dev) } res = platform_get_resource(dev, IORESOURCE_MEM, 0); - if (!res) - goto exit_busy; - if (!devm_request_mem_region(&dev->dev, - res->start, res->end - res->start + 1, - "txx9wdt")) - goto exit_busy; - txx9wdt_reg = devm_ioremap(&dev->dev, - res->start, res->end - res->start + 1); - if (!txx9wdt_reg) - goto exit_busy; - - ret = register_reboot_notifier(&txx9wdt_notifier); - if (ret) + txx9wdt_reg = devm_ioremap_resource(&dev->dev, res); + if (IS_ERR(txx9wdt_reg)) { + ret = PTR_ERR(txx9wdt_reg); goto exit; + } - ret = misc_register(&txx9wdt_miscdev); - if (ret) { - unregister_reboot_notifier(&txx9wdt_notifier); + if (timeout < 1 || timeout > WD_MAX_TIMEOUT) + timeout = TIMER_MARGIN; + txx9wdt.timeout = timeout; + txx9wdt.min_timeout = 1; + txx9wdt.max_timeout = WD_MAX_TIMEOUT; + watchdog_set_nowayout(&txx9wdt, nowayout); + + ret = watchdog_register_device(&txx9wdt); + if (ret) goto exit; - } - printk(KERN_INFO "Hardware Watchdog Timer for TXx9: " - "timeout=%d sec (max %ld) (nowayout= %d)\n", - timeout, WD_MAX_TIMEOUT, nowayout); + pr_info("Hardware Watchdog Timer: timeout=%d sec (max %ld) (nowayout= %d)\n", + timeout, WD_MAX_TIMEOUT, nowayout); return 0; -exit_busy: - ret = -EBUSY; exit: if (txx9_imclk) { clk_disable(txx9_imclk); @@ -243,34 +151,28 @@ exit: static int __exit txx9wdt_remove(struct platform_device *dev) { - misc_deregister(&txx9wdt_miscdev); - unregister_reboot_notifier(&txx9wdt_notifier); + watchdog_unregister_device(&txx9wdt); clk_disable(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", .owner = THIS_MODULE, }, }; -static int __init watchdog_init(void) -{ - return platform_driver_probe(&txx9wdt_driver, txx9wdt_probe); -} - -static void __exit watchdog_exit(void) -{ - platform_driver_unregister(&txx9wdt_driver); -} - -module_init(watchdog_init); -module_exit(watchdog_exit); +module_platform_driver_probe(txx9wdt_driver, txx9wdt_probe); MODULE_DESCRIPTION("TXx9 Watchdog Driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:txx9wdt"); diff --git a/drivers/watchdog/ux500_wdt.c b/drivers/watchdog/ux500_wdt.c new file mode 100644 index 00000000000..5aed9d7ad47 --- /dev/null +++ b/drivers/watchdog/ux500_wdt.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) ST-Ericsson SA 2011-2013 + * + * License Terms: GNU General Public License v2 + * + * 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/platform_data/ux500_wdt.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 */ +#define WATCHDOG_MAX32 4294967 /* 32 bit resolution in ms == 4294967.295 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 ux500_wdt_start(struct watchdog_device *wdd) +{ + return prcmu_enable_a9wdog(PRCMU_WDOG_ALL); +} + +static int ux500_wdt_stop(struct watchdog_device *wdd) +{ + return prcmu_disable_a9wdog(PRCMU_WDOG_ALL); +} + +static int ux500_wdt_keepalive(struct watchdog_device *wdd) +{ + return prcmu_kick_a9wdog(PRCMU_WDOG_ALL); +} + +static int ux500_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + ux500_wdt_stop(wdd); + prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + ux500_wdt_start(wdd); + + return 0; +} + +static const struct watchdog_info ux500_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Ux500 WDT", + .firmware_version = 1, +}; + +static const struct watchdog_ops ux500_wdt_ops = { + .owner = THIS_MODULE, + .start = ux500_wdt_start, + .stop = ux500_wdt_stop, + .ping = ux500_wdt_keepalive, + .set_timeout = ux500_wdt_set_timeout, +}; + +static struct watchdog_device ux500_wdt = { + .info = &ux500_wdt_info, + .ops = &ux500_wdt_ops, + .min_timeout = WATCHDOG_MIN, + .max_timeout = WATCHDOG_MAX32, +}; + +static int ux500_wdt_probe(struct platform_device *pdev) +{ + int ret; + struct ux500_wdt_data *pdata = dev_get_platdata(&pdev->dev); + + if (pdata) { + if (pdata->timeout > 0) + timeout = pdata->timeout; + if (pdata->has_28_bits_resolution) + ux500_wdt.max_timeout = WATCHDOG_MAX28; + } + + watchdog_set_nowayout(&ux500_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 = watchdog_register_device(&ux500_wdt); + if (ret) + return ret; + + dev_info(&pdev->dev, "initialized\n"); + + return 0; +} + +static int ux500_wdt_remove(struct platform_device *dev) +{ + watchdog_unregister_device(&ux500_wdt); + + return 0; +} + +#ifdef CONFIG_PM +static int ux500_wdt_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (watchdog_active(&ux500_wdt)) { + ux500_wdt_stop(&ux500_wdt); + prcmu_config_a9wdog(PRCMU_WDOG_CPU1, true); + + prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + ux500_wdt_start(&ux500_wdt); + } + return 0; +} + +static int ux500_wdt_resume(struct platform_device *pdev) +{ + if (watchdog_active(&ux500_wdt)) { + ux500_wdt_stop(&ux500_wdt); + prcmu_config_a9wdog(PRCMU_WDOG_CPU1, false); + + prcmu_load_a9wdog(PRCMU_WDOG_ALL, timeout * 1000); + ux500_wdt_start(&ux500_wdt); + } + return 0; +} +#else +#define ux500_wdt_suspend NULL +#define ux500_wdt_resume NULL +#endif + +static struct platform_driver ux500_wdt_driver = { + .probe = ux500_wdt_probe, + .remove = ux500_wdt_remove, + .suspend = ux500_wdt_suspend, + .resume = ux500_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "ux500_wdt", + }, +}; + +module_platform_driver(ux500_wdt_driver); + +MODULE_AUTHOR("Jonas Aaberg <jonas.aberg@stericsson.com>"); +MODULE_DESCRIPTION("Ux500 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ux500_wdt"); diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c new file mode 100644 index 00000000000..56369c4f196 --- /dev/null +++ b/drivers/watchdog/via_wdt.c @@ -0,0 +1,260 @@ +/* + * VIA Chipset Watchdog Driver + * + * Copyright (C) 2011 Sigfox + * License terms: GNU General Public License (GPL) version 2 + * 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 watchog 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(unsigned long data); +static DEFINE_TIMER(timer, wdt_timer_tick, 0, 0); + /* 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 watchog 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(unsigned long data) +{ + 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; + 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/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index 386492821fc..7165704a3e3 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c @@ -1,18 +1,21 @@ /* * 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> + * (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@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (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 @@ -23,275 +26,272 @@ * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * - * (c) Copyright 1995 Alan Cox <alan@redhat.com> + * (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/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 <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> +#include <linux/io.h> -#define WATCHDOG_NAME "w83627hf/thf/hg WDT" -#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_NAME "w83627hf/thf/hg/dhg WDT" #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ -static unsigned long wdt_is_open; -static char expect_close; -static DEFINE_SPINLOCK(io_lock); +static int wdt_io; +static int cr_wdt_timeout; /* WDT timeout register */ +static int cr_wdt_control; /* WDT control register */ -/* You must set this - there is no sane way to probe for this board. */ -static int wdt_io = 0x2E; -module_param(wdt_io, int, 0); -MODULE_PARM_DESC(wdt_io, "w83627hf/thf WDT io port (default 0x2E)"); +enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf, + w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p, + w83667hg_b, nct6775, nct6776, nct6779 }; -static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +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) "."); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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_EFIR (wdt_io+0) /* Extended Function Index Register + (same as EFER) */ #define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ -static void -w83627hf_select_wd_register(void) +#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 NCT6779_ID 0xc5 + +#define W83627HF_WDT_TIMEOUT 0xf6 +#define W83697HF_WDT_TIMEOUT 0xf4 + +#define W83627HF_WDT_CONTROL 0xf5 +#define W83697HF_WDT_CONTROL 0xf3 + +static void superio_outb(int reg, int val) { - unsigned char c; + 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(0x87, WDT_EFER); /* Enter extended function mode */ outb_p(0x87, WDT_EFER); /* Again according to manual */ - outb(0x20, WDT_EFER); /* check chip version */ - c = inb(WDT_EFDR); - if (c == 0x82) { /* W83627THF */ - outb_p(0x2b, WDT_EFER); /* select GPIO3 */ - c = ((inb_p(WDT_EFDR) & 0xf7) | 0x04); /* select WDT0 */ - outb_p(0x2b, WDT_EFER); - outb_p(c, WDT_EFDR); /* set GPIO3 to WDT0 */ - } + return 0; +} - outb_p(0x07, WDT_EFER); /* point to logical device number reg */ - outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ - outb_p(0x30, WDT_EFER); /* select CR30 */ - outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ +static void superio_select(int ld) +{ + superio_outb(0x07, ld); } -static void -w83627hf_unselect_wd_register(void) +static void superio_exit(void) { outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ + release_region(wdt_io, 2); } -/* tyan motherboards seem to set F5 to 0x4C ? - * So explicitly init to appropriate value. */ -static void -w83627hf_init(void) +static int w83627hf_init(struct watchdog_device *wdog, enum chips chip) { + int ret; unsigned char t; - w83627hf_select_wd_register(); + 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: + /* + * 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; + } - outb_p(0xF6, WDT_EFER); /* Select CRF6 */ - t=inb_p(WDT_EFDR); /* read CRF6 */ + t = superio_inb(cr_wdt_timeout); if (t != 0) { - printk (KERN_INFO PFX "Watchdog already running. Resetting timeout to %d sec\n", timeout); - outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ + 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); + } } - outb_p(0xF5, WDT_EFER); /* Select CRF5 */ - t=inb_p(WDT_EFDR); /* read CRF5 */ - t&=~0x0C; /* set second mode & disable keyboard turning off watchdog */ - outb_p(t, WDT_EFDR); /* Write back to CRF5 */ + /* set second mode & disable keyboard turning off watchdog */ + t = superio_inb(cr_wdt_control) & ~0x0C; + superio_outb(cr_wdt_control, t); - outb_p(0xF7, WDT_EFER); /* Select CRF7 */ - t=inb_p(WDT_EFDR); /* read CRF7 */ - t&=~0xC0; /* disable keyboard & mouse turning off watchdog */ - outb_p(t, WDT_EFDR); /* Write back to CRF7 */ + /* reset trigger, disable keyboard & mouse turning off watchdog */ + t = superio_inb(0xF7) & ~0xD0; + superio_outb(0xF7, t); - w83627hf_unselect_wd_register(); + superio_exit(); + + return 0; } -static void -wdt_ctrl(int timeout) +static int wdt_set_time(unsigned int timeout) { - spin_lock(&io_lock); - - w83627hf_select_wd_register(); - - outb_p(0xF6, WDT_EFER); /* Select CRF6 */ - outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */ + int ret; - w83627hf_unselect_wd_register(); + ret = superio_enter(); + if (ret) + return ret; - spin_unlock(&io_lock); -} + superio_select(W83627HF_LD_WDT); + superio_outb(cr_wdt_timeout, timeout); + superio_exit(); -static int -wdt_ping(void) -{ - wdt_ctrl(timeout); return 0; } -static int -wdt_disable(void) +static int wdt_start(struct watchdog_device *wdog) { - wdt_ctrl(0); - return 0; + return wdt_set_time(wdog->timeout); } -static int -wdt_set_heartbeat(int t) +static int wdt_stop(struct watchdog_device *wdog) { - if ((t < 1) || (t > 255)) - return -EINVAL; - - timeout = t; - return 0; + return wdt_set_time(0); } -static ssize_t -wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) { - if (count) { - if (!nowayout) { - size_t i; - - 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; -} + wdog->timeout = timeout; -static int -wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_timeout; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "W83627HF 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_KEEPALIVE: - wdt_ping(); - break; - - case WDIOC_SETTIMEOUT: - if (get_user(new_timeout, p)) - return -EFAULT; - if (wdt_set_heartbeat(new_timeout)) - return -EINVAL; - wdt_ping(); - /* Fall */ - - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - - case WDIOC_SETOPTIONS: - { - int options, retval = -EINVAL; - - if (get_user(options, p)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - wdt_disable(); - retval = 0; - } - - if (options & WDIOS_ENABLECARD) { - wdt_ping(); - retval = 0; - } - - return retval; - } - - default: - return -ENOTTY; - } return 0; } -static int -wdt_open(struct inode *inode, struct file *file) +static unsigned int wdt_get_time(struct watchdog_device *wdog) { - if (test_and_set_bit(0, &wdt_is_open)) - return -EBUSY; - /* - * Activate - */ + unsigned int timeleft; + int ret; - wdt_ping(); - return nonseekable_open(inode, file); -} + ret = superio_enter(); + if (ret) + return 0; -static int -wdt_close(struct inode *inode, struct file *file) -{ - if (expect_close == 42) { - wdt_disable(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - wdt_ping(); - } - expect_close = 0; - clear_bit(0, &wdt_is_open); - return 0; + superio_select(W83627HF_LD_WDT); + timeleft = superio_inb(cr_wdt_timeout); + superio_exit(); + + return timeleft; } /* * Notifier for system down */ - -static int -wdt_notify_sys(struct notifier_block *this, unsigned long code, +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if (code == SYS_DOWN || code == SYS_HALT) { - /* Turn the WDT off */ - wdt_disable(); - } + if (code == SYS_DOWN || code == SYS_HALT) + wdt_set_time(0); /* Turn the WDT off */ + return NOTIFY_DONE; } @@ -299,19 +299,25 @@ wdt_notify_sys(struct notifier_block *this, unsigned long code, * Kernel Interfaces */ -static const struct file_operations wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = wdt_write, - .ioctl = wdt_ioctl, - .open = wdt_open, - .release = wdt_close, +static struct watchdog_info wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "W83627HF Watchdog", }; -static struct miscdevice wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &wdt_fops, +static 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, }; /* @@ -323,66 +329,157 @@ static struct notifier_block wdt_notifier = { .notifier_call = wdt_notify_sys, }; -static int __init -wdt_init(void) +static int wdt_find(int addr) { + u8 val; int ret; - printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n"); - - if (wdt_set_heartbeat(timeout)) { - wdt_set_heartbeat(WATCHDOG_TIMEOUT); - printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n", - WATCHDOG_TIMEOUT); + cr_wdt_timeout = W83627HF_WDT_TIMEOUT; + cr_wdt_control = W83627HF_WDT_CONTROL; + + 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 0xff: + ret = -ENODEV; + break; + default: + ret = -ENODEV; + pr_err("Unsupported chip ID: 0x%02x\n", val); + break; } + superio_exit(); + return ret; +} + +static int __init wdt_init(void) +{ + int ret; + int chip; + const char * const chip_name[] = { + "W83627HF", + "W83627S", + "W83697HF", + "W83697UG", + "W83637HF", + "W83627THF", + "W83687THF", + "W83627EHF", + "W83627DHG", + "W83627UHG", + "W83667HG", + "W83667DHG-P", + "W83667HG-B", + "NCT6775", + "NCT6776", + "NCT6779", + }; - if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_io); - ret = -EIO; - goto out; + wdt_io = 0x2e; + chip = wdt_find(0x2e); + if (chip < 0) { + wdt_io = 0x4e; + chip = wdt_find(0x4e); + if (chip < 0) + return chip; } - w83627hf_init(); + 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); + + ret = w83627hf_init(&wdt_dev, chip); + if (ret) { + pr_err("failed to initialize watchdog (err=%d)\n", ret); + return ret; + } ret = register_reboot_notifier(&wdt_notifier); if (ret != 0) { - printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); - goto unreg_regions; + pr_err("cannot register reboot notifier (err=%d)\n", ret); + return ret; } - ret = misc_register(&wdt_miscdev); - if (ret != 0) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + ret = watchdog_register_device(&wdt_dev); + if (ret) goto unreg_reboot; - } - printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", - timeout, nowayout); + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + wdt_dev.timeout, nowayout); -out: return ret; + unreg_reboot: unregister_reboot_notifier(&wdt_notifier); -unreg_regions: - release_region(wdt_io, 1); - goto out; + return ret; } -static void __exit -wdt_exit(void) +static void __exit wdt_exit(void) { - misc_deregister(&wdt_miscdev); + watchdog_unregister_device(&wdt_dev); unregister_reboot_notifier(&wdt_notifier); - release_region(wdt_io,1); } module_init(wdt_init); module_exit(wdt_exit); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>"); +MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>"); MODULE_DESCRIPTION("w83627hf/thf WDT driver"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83697hf_wdt.c b/drivers/watchdog/w83697hf_wdt.c deleted file mode 100644 index c622a0e6c9a..00000000000 --- a/drivers/watchdog/w83697hf_wdt.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * w83697hf/hg WDT driver - * - * (c) Copyright 2006 Samuel Tardieu <sam@rfc1149.net> - * (c) Copyright 2006 Marcus Junker <junker@anduras.de> - * - * Based on w83627hf_wdt.c which is based on advantechwdt.c - * which is based on wdt.c. - * Original copyright messages: - * - * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> - * - * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> - * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * Neither 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. - */ - -#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/spinlock.h> - -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> - -#define WATCHDOG_NAME "w83697hf/hg WDT" -#define PFX WATCHDOG_NAME ": " -#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ - -static unsigned long wdt_is_open; -static char expect_close; -static DEFINE_SPINLOCK(io_lock); - -/* You must set this - there is no sane way to probe for this board. */ -static int wdt_io = 0x2e; -module_param(wdt_io, int, 0); -MODULE_PARM_DESC(wdt_io, "w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)"); - -static int timeout = WATCHDOG_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 int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -/* - * Kernel methods. - */ - -#define W83697HF_EFER (wdt_io+0) /* Extended Function Enable Register */ -#define W83697HF_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */ -#define W83697HF_EFDR (wdt_io+1) /* Extended Function Data Register */ - -static inline void -w83697hf_unlock(void) -{ - outb_p(0x87, W83697HF_EFER); /* Enter extended function mode */ - outb_p(0x87, W83697HF_EFER); /* Again according to manual */ -} - -static inline void -w83697hf_lock(void) -{ - outb_p(0xAA, W83697HF_EFER); /* Leave extended function mode */ -} - -/* - * The three functions w83697hf_get_reg(), w83697hf_set_reg() and - * w83697hf_write_timeout() must be called with the device unlocked. - */ - -static unsigned char -w83697hf_get_reg(unsigned char reg) -{ - outb_p(reg, W83697HF_EFIR); - return inb_p(W83697HF_EFDR); -} - -static void -w83697hf_set_reg(unsigned char reg, unsigned char data) -{ - outb_p(reg, W83697HF_EFIR); - outb_p(data, W83697HF_EFDR); -} - -static void -w83697hf_write_timeout(int timeout) -{ - w83697hf_set_reg(0xF4, timeout); /* Write Timeout counter to CRF4 */ -} - -static void -w83697hf_select_wdt(void) -{ - w83697hf_unlock(); - w83697hf_set_reg(0x07, 0x08); /* Switch to logic device 8 (GPIO2) */ -} - -static inline void -w83697hf_deselect_wdt(void) -{ - w83697hf_lock(); -} - -static void -w83697hf_init(void) -{ - unsigned char bbuf; - - w83697hf_select_wdt(); - - bbuf = w83697hf_get_reg(0x29); - bbuf &= ~0x60; - bbuf |= 0x20; - w83697hf_set_reg(0x29, bbuf); /* Set pin 119 to WDTO# mode (= CR29, WDT0) */ - - bbuf = w83697hf_get_reg(0xF3); - bbuf &= ~0x04; - w83697hf_set_reg(0xF3, bbuf); /* Count mode is seconds */ - - w83697hf_deselect_wdt(); -} - -static int -wdt_ping(void) -{ - spin_lock(&io_lock); - w83697hf_select_wdt(); - - w83697hf_write_timeout(timeout); - - w83697hf_deselect_wdt(); - spin_unlock(&io_lock); - return 0; -} - -static int -wdt_enable(void) -{ - spin_lock(&io_lock); - w83697hf_select_wdt(); - - w83697hf_write_timeout(timeout); - w83697hf_set_reg(0x30, 1); /* Enable timer */ - - w83697hf_deselect_wdt(); - spin_unlock(&io_lock); - return 0; -} - -static int -wdt_disable(void) -{ - spin_lock(&io_lock); - w83697hf_select_wdt(); - - w83697hf_set_reg(0x30, 0); /* Disable timer */ - w83697hf_write_timeout(0); - - w83697hf_deselect_wdt(); - spin_unlock(&io_lock); - return 0; -} - -static int -wdt_set_heartbeat(int t) -{ - if ((t < 1) || (t > 255)) - return -EINVAL; - - timeout = t; - return 0; -} - -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; - - 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; -} - -static int -wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_timeout; - static struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "W83697HF 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_KEEPALIVE: - wdt_ping(); - break; - - case WDIOC_SETTIMEOUT: - if (get_user(new_timeout, p)) - return -EFAULT; - if (wdt_set_heartbeat(new_timeout)) - return -EINVAL; - wdt_ping(); - /* Fall */ - - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - - case WDIOC_SETOPTIONS: - { - int options, retval = -EINVAL; - - if (get_user(options, p)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - wdt_disable(); - retval = 0; - } - - if (options & WDIOS_ENABLECARD) { - wdt_enable(); - retval = 0; - } - - return retval; - } - - default: - return -ENOTTY; - } - return 0; -} - -static int -wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(0, &wdt_is_open)) - return -EBUSY; - /* - * Activate - */ - - wdt_enable(); - return nonseekable_open(inode, file); -} - -static int -wdt_close(struct inode *inode, struct file *file) -{ - if (expect_close == 42) { - wdt_disable(); - } else { - printk (KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - wdt_ping(); - } - expect_close = 0; - clear_bit(0, &wdt_is_open); - return 0; -} - -/* - * 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) { - /* Turn the WDT off */ - wdt_disable(); - } - return NOTIFY_DONE; -} - -/* - * Kernel Interfaces - */ - -static const struct file_operations wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = wdt_write, - .ioctl = wdt_ioctl, - .open = wdt_open, - .release = wdt_close, -}; - -static struct miscdevice wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &wdt_fops, -}; - -/* - * 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 int -w83697hf_check_wdt(void) -{ - if (!request_region(wdt_io, 2, WATCHDOG_NAME)) { - printk (KERN_ERR PFX "I/O address 0x%x already in use\n", wdt_io); - return -EIO; - } - - printk (KERN_DEBUG PFX "Looking for watchdog at address 0x%x\n", wdt_io); - w83697hf_unlock(); - if (w83697hf_get_reg(0x20) == 0x60) { - printk (KERN_INFO PFX "watchdog found at address 0x%x\n", wdt_io); - w83697hf_lock(); - return 0; - } - w83697hf_lock(); /* Reprotect in case it was a compatible device */ - - printk (KERN_INFO PFX "watchdog not found at address 0x%x\n", wdt_io); - release_region(wdt_io, 2); - return -EIO; -} - -static int w83697hf_ioports[] = { 0x2e, 0x4e, 0x00 }; - -static int __init -wdt_init(void) -{ - int ret, i, found = 0; - - printk (KERN_INFO PFX "WDT driver for W83697HF/HG initializing\n"); - - if (wdt_io == 0) { - /* we will autodetect the W83697HF/HG watchdog */ - for (i = 0; ((!found) && (w83697hf_ioports[i] != 0)); i++) { - wdt_io = w83697hf_ioports[i]; - if (!w83697hf_check_wdt()) - found++; - } - } else { - if (!w83697hf_check_wdt()) - found++; - } - - if (!found) { - printk (KERN_ERR PFX "No W83697HF/HG could be found\n"); - ret = -EIO; - goto out; - } - - w83697hf_init(); - wdt_disable(); /* Disable watchdog until first use */ - - if (wdt_set_heartbeat(timeout)) { - wdt_set_heartbeat(WATCHDOG_TIMEOUT); - printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n", - WATCHDOG_TIMEOUT); - } - - ret = register_reboot_notifier(&wdt_notifier); - if (ret != 0) { - printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); - goto unreg_regions; - } - - ret = misc_register(&wdt_miscdev); - if (ret != 0) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto unreg_reboot; - } - - printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", - timeout, nowayout); - -out: - return ret; -unreg_reboot: - unregister_reboot_notifier(&wdt_notifier); -unreg_regions: - release_region(wdt_io, 2); - goto out; -} - -static void __exit -wdt_exit(void) -{ - misc_deregister(&wdt_miscdev); - unregister_reboot_notifier(&wdt_notifier); - release_region(wdt_io, 2); -} - -module_init(wdt_init); -module_exit(wdt_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, Samuel Tardieu <sam@rfc1149.net>"); -MODULE_DESCRIPTION("w83697hf/hg WDT driver"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83877f_wdt.c b/drivers/watchdog/w83877f_wdt.c index f510a3a595e..f0483c75ed3 100644 --- a/drivers/watchdog/w83877f_wdt.c +++ b/drivers/watchdog/w83877f_wdt.c @@ -23,13 +23,16 @@ * 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, + * 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 timeout (the emulated heartbeat) a + * module_param * made the keepalive ping an internal subroutine * * This WDT driver is different from most other Linux WDT @@ -39,6 +42,8 @@ * 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> @@ -51,12 +56,10 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> +#include <linux/io.h> +#include <linux/uaccess.h> #define OUR_NAME "w83877f_wdt" -#define PFX OUR_NAME ": " #define ENABLE_W83877F_PORT 0x3F0 #define ENABLE_W83877F 0x87 @@ -80,14 +83,19 @@ */ #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ -static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +/* 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) ")"); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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(unsigned long); static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); @@ -105,8 +113,7 @@ static void wdt_timer_ping(unsigned long data) /* If we got a heartbeat pulse within the WDT_US_INTERVAL * we agree to ping the WDT */ - if(time_before(jiffies, next_heartbeat)) - { + if (time_before(jiffies, next_heartbeat)) { /* Ping the WDT */ spin_lock(&wdt_spinlock); @@ -118,9 +125,8 @@ static void wdt_timer_ping(unsigned long data) spin_unlock(&wdt_spinlock); - } else { - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); - } + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); } /* @@ -158,7 +164,7 @@ static void wdt_startup(void) wdt_change(WDT_ENABLE); - printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); + pr_info("Watchdog timer is now enabled\n"); } static void wdt_turnoff(void) @@ -168,7 +174,7 @@ static void wdt_turnoff(void) wdt_change(WDT_DISABLE); - printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); + pr_info("Watchdog timer is now disabled...\n"); } static void wdt_keepalive(void) @@ -181,22 +187,21 @@ static void wdt_keepalive(void) * /dev/watchdog handling */ -static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +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) - { + if (count) { + if (!nowayout) { size_t ofs; - /* note: just in case someone wrote the magic character - * five months ago... */ + /* 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++) - { + /* 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; @@ -211,10 +216,10 @@ static ssize_t fop_write(struct file * file, const char __user * buf, size_t cou return count; } -static int fop_open(struct inode * inode, struct file * file) +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)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; /* Good, fire up the show */ @@ -222,78 +227,77 @@ static int fop_open(struct inode * inode, struct file * file) return nonseekable_open(inode, file); } -static int fop_close(struct inode * inode, struct file * file) +static int fop_close(struct inode *inode, struct file *file) { - if(wdt_expect_close == 42) + if (wdt_expect_close == 42) wdt_turnoff(); else { del_timer(&timer); - printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + 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 int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; - static struct watchdog_info ident= - { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "W83877F", }; - switch(cmd) + 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: { - default: - return -ENOTTY; - 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_KEEPALIVE: - wdt_keepalive(); - return 0; - 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; - } + int new_options, retval = -EINVAL; - if(new_options & WDIOS_ENABLECARD) { - wdt_startup(); - retval = 0; - } + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } - return retval; + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; } - case WDIOC_SETTIMEOUT: - { - int new_timeout; - if(get_user(new_timeout, p)) - return -EFAULT; + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; - if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ - return -EINVAL; + /* arbitrary upper limit */ + if (new_timeout < 1 || new_timeout > 3600) + return -EINVAL; - timeout = new_timeout; - wdt_keepalive(); - /* Fall through */ - } - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); + timeout = new_timeout; + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; } } @@ -303,7 +307,7 @@ static const struct file_operations wdt_fops = { .write = fop_write, .open = fop_open, .release = fop_close, - .ioctl = fop_ioctl, + .unlocked_ioctl = fop_ioctl, }; static struct miscdevice wdt_miscdev = { @@ -319,7 +323,7 @@ static struct miscdevice wdt_miscdev = { static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code==SYS_DOWN || code==SYS_HALT) + if (code == SYS_DOWN || code == SYS_HALT) wdt_turnoff(); return NOTIFY_DONE; } @@ -329,8 +333,7 @@ static int wdt_notify_sys(struct notifier_block *this, unsigned long code, * turn the timebomb registers off. */ -static struct notifier_block wdt_notifier= -{ +static struct notifier_block wdt_notifier = { .notifier_call = wdt_notify_sys, }; @@ -342,54 +345,47 @@ static void __exit w83877f_wdt_unload(void) misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); - release_region(WDT_PING,1); - release_region(ENABLE_W83877F_PORT,2); + 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 */ - { + if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */ timeout = WATCHDOG_TIMEOUT; - printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", timeout); } - if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - ENABLE_W83877F_PORT); + 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")) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - WDT_PING); + 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) - { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_region2; } rc = misc_register(&wdt_miscdev); - if (rc) - { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - wdt_miscdev.minor, rc); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); goto err_out_reboot; } - printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", + pr_info("WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); return 0; @@ -397,9 +393,9 @@ static int __init w83877f_wdt_init(void) err_out_reboot: unregister_reboot_notifier(&wdt_notifier); err_out_region2: - release_region(WDT_PING,1); + release_region(WDT_PING, 1); err_out_region1: - release_region(ENABLE_W83877F_PORT,2); + release_region(ENABLE_W83877F_PORT, 2); err_out: return rc; } @@ -410,4 +406,3 @@ module_exit(w83877f_wdt_unload); MODULE_AUTHOR("Scott and Bill Jennings"); MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83977f_wdt.c b/drivers/watchdog/w83977f_wdt.c index b209bcd7f78..91bf55a2002 100644 --- a/drivers/watchdog/w83977f_wdt.c +++ b/drivers/watchdog/w83977f_wdt.c @@ -15,6 +15,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -26,15 +28,12 @@ #include <linux/watchdog.h> #include <linux/notifier.h> #include <linux/reboot.h> +#include <linux/uaccess.h> +#include <linux/io.h> -#include <asm/io.h> -#include <asm/system.h> -#include <asm/uaccess.h> #define WATCHDOG_VERSION "1.00" #define WATCHDOG_NAME "W83977F WDT" -#define PFX WATCHDOG_NAME ": " -#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" #define IO_INDEX_PORT 0x3F0 #define IO_DATA_PORT (IO_INDEX_PORT+1) @@ -53,13 +52,17 @@ 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_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"); +MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -72,8 +75,8 @@ static int wdt_start(void) spin_lock_irqsave(&spinlock, flags); /* Unlock the SuperIO chip */ - outb_p(UNLOCK_DATA,IO_INDEX_PORT); - outb_p(UNLOCK_DATA,IO_INDEX_PORT); + 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. @@ -81,54 +84,53 @@ static int wdt_start(void) * 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); + 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); + 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) - { + 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); + 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(0x2C, IO_INDEX_PORT); + outb_p(pin_map, IO_DATA_PORT); } - outb_p(0xE3,IO_INDEX_PORT); - outb_p(0x08,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); + outb_p(0x30, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); /* Lock the SuperIO chip */ - outb_p(LOCK_DATA,IO_INDEX_PORT); + outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); - printk(KERN_INFO PFX "activated.\n"); + pr_info("activated\n"); return 0; } @@ -144,46 +146,45 @@ static int wdt_stop(void) spin_lock_irqsave(&spinlock, flags); /* Unlock the SuperIO chip */ - outb_p(UNLOCK_DATA,IO_INDEX_PORT); - outb_p(UNLOCK_DATA,IO_INDEX_PORT); + 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); + 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 + * 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(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); + outb_p(0xE3, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); /* Lock the SuperIO chip */ - outb_p(LOCK_DATA,IO_INDEX_PORT); + outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); - printk(KERN_INFO PFX "shutdown.\n"); + pr_info("shutdown\n"); return 0; } @@ -200,17 +201,17 @@ static int wdt_keepalive(void) spin_lock_irqsave(&spinlock, flags); /* Unlock the SuperIO chip */ - outb_p(UNLOCK_DATA,IO_INDEX_PORT); - outb_p(UNLOCK_DATA,IO_INDEX_PORT); + 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); + 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); + outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); @@ -227,7 +228,7 @@ static int wdt_set_timeout(int t) /* * Convert seconds to watchdog counter time units, rounding up. - * On PCM-5335 watchdog units are 30 seconds/step with 15 sec startup + * 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! @@ -241,7 +242,7 @@ static int wdt_set_timeout(int t) return -EINVAL; /* - * timeout is the timeout in seconds, + * timeout is the timeout in seconds, * timeoutW is the timeout in watchdog counter units. */ timeoutW = tmrval; @@ -261,17 +262,17 @@ static int wdt_get_status(int *status) spin_lock_irqsave(&spinlock, flags); /* Unlock the SuperIO chip */ - outb_p(UNLOCK_DATA,IO_INDEX_PORT); - outb_p(UNLOCK_DATA,IO_INDEX_PORT); + 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); + 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); + outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); @@ -290,7 +291,7 @@ static int wdt_get_status(int *status) 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) ) + if (test_and_set_bit(0, &timer_alive)) return -EBUSY; if (nowayout) @@ -306,13 +307,12 @@ 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) - { + if (expect_close == 42) { wdt_stop(); clear_bit(0, &timer_alive); } else { wdt_keepalive(); - printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n"); + pr_crit("unexpected close, not stopping watchdog!\n"); } expect_close = 0; return 0; @@ -333,24 +333,22 @@ 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) - { + if (count) { + if (!nowayout) { size_t ofs; - /* note: just in case someone wrote the magic character long ago */ + /* 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++) - { + /* 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') { + if (c == 'V') expect_close = 42; - } } } @@ -371,14 +369,13 @@ static ssize_t wdt_write(struct file *file, const char __user *buf, * according to their available features. */ -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .firmware_version = 1, .identity = WATCHDOG_NAME, }; -static int wdt_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int status; int new_options, retval = -EINVAL; @@ -390,13 +387,10 @@ static int wdt_ioctl(struct inode *inode, struct file *file, uarg.i = (int __user *)arg; - switch(cmd) - { - default: - return -ENOTTY; - + switch (cmd) { case WDIOC_GETSUPPORT: - return copy_to_user(uarg.ident, &ident, sizeof(ident)) ? -EFAULT : 0; + return copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: wdt_get_status(&status); @@ -405,12 +399,8 @@ static int wdt_ioctl(struct inode *inode, struct file *file, case WDIOC_GETBOOTSTATUS: return put_user(0, uarg.i); - case WDIOC_KEEPALIVE: - wdt_keepalive(); - return 0; - case WDIOC_SETOPTIONS: - if (get_user (new_options, uarg.i)) + if (get_user(new_options, uarg.i)) return -EFAULT; if (new_options & WDIOS_DISABLECARD) { @@ -425,12 +415,16 @@ static int wdt_ioctl(struct inode *inode, struct file *file, 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; + return -EINVAL; wdt_keepalive(); /* Fall */ @@ -438,29 +432,30 @@ static int wdt_ioctl(struct inode *inode, struct file *file, 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) + if (code == SYS_DOWN || code == SYS_HALT) wdt_stop(); return NOTIFY_DONE; } -static const struct file_operations wdt_fops= -{ +static const struct file_operations wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wdt_write, - .ioctl = wdt_ioctl, + .unlocked_ioctl = wdt_ioctl, .open = wdt_open, .release = wdt_release, }; -static struct miscdevice wdt_miscdev= -{ +static struct miscdevice wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &wdt_fops, @@ -474,43 +469,38 @@ static int __init w83977f_wdt_init(void) { int rc; - printk(KERN_INFO PFX DRIVER_VERSION); + pr_info("driver v%s\n", WATCHDOG_VERSION); /* - * Check that the timeout value is within it's range ; + * 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); - printk(KERN_INFO PFX "timeout value must be 15<=timeout<=7635, using %d\n", + pr_info("timeout value must be 15 <= timeout <= 7635, using %d\n", DEFAULT_TIMEOUT); } - if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - IO_INDEX_PORT); + 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) - { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_region; } rc = misc_register(&wdt_miscdev); - if (rc) - { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - wdt_miscdev.minor, rc); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); goto err_out_reboot; } - printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d testmode=%d)\n", + pr_info("initialized. timeout=%d sec (nowayout=%d testmode=%d)\n", timeout, nowayout, testmode); return 0; @@ -518,7 +508,7 @@ static int __init w83977f_wdt_init(void) err_out_reboot: unregister_reboot_notifier(&wdt_notifier); err_out_region: - release_region(IO_INDEX_PORT,2); + release_region(IO_INDEX_PORT, 2); err_out: return rc; } @@ -528,7 +518,7 @@ static void __exit w83977f_wdt_exit(void) wdt_stop(); misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); - release_region(IO_INDEX_PORT,2); + release_region(IO_INDEX_PORT, 2); } module_init(w83977f_wdt_init); @@ -537,4 +527,3 @@ 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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/wafer5823wdt.c b/drivers/watchdog/wafer5823wdt.c index 9e368091f79..db0da7ea4fd 100644 --- a/drivers/watchdog/wafer5823wdt.c +++ b/drivers/watchdog/wafer5823wdt.c @@ -1,17 +1,17 @@ /* * ICP Wafer 5823 Single Board Computer WDT driver - * http://www.icpamerica.com/wafer_5823.php - * May also work on other similar models + * 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 + * Release 0.02 * * Based on advantechwdt.c which is based on wdt.c. * Original copyright messages: * - * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (c) Copyright 1996-1997 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 @@ -26,6 +26,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/miscdevice.h> @@ -36,8 +38,8 @@ #include <linux/reboot.h> #include <linux/init.h> #include <linux/spinlock.h> -#include <asm/io.h> -#include <asm/uaccess.h> +#include <linux/io.h> +#include <linux/uaccess.h> #define WATCHDOG_NAME "Wafer 5823 WDT" #define PFX WATCHDOG_NAME ": " @@ -50,10 +52,10 @@ 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. + * 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; @@ -61,11 +63,15 @@ 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) "."); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" + __MODULE_STRING(WD_TIMO) "."); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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) { @@ -83,14 +89,14 @@ static void wafwdt_start(void) inb_p(wdt_start); } -static void -wafwdt_stop(void) +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) +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) { @@ -100,7 +106,8 @@ static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t co /* In case it was set long ago */ expect_close = 0; - /* scan to see whether or not we got the magic character */ + /* 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)) @@ -109,27 +116,29 @@ static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t co expect_close = 42; } } - /* Well, anyhow someone wrote to us, we should return that favour */ + /* Well, anyhow someone wrote to us, we should + return that favour */ wafwdt_ping(); } return count; } -static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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 struct watchdog_info ident = { - .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + 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))) + if (copy_to_user(argp, &ident, sizeof(ident))) return -EFAULT; break; @@ -137,22 +146,6 @@ static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd case WDIOC_GETBOOTSTATUS: return put_user(0, p); - 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(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - case WDIOC_SETOPTIONS: { int options, retval = -EINVAL; @@ -161,18 +154,34 @@ static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd return -EFAULT; if (options & WDIOS_DISABLECARD) { - wafwdt_start(); + wafwdt_stop(); retval = 0; } if (options & WDIOS_ENABLECARD) { - wafwdt_stop(); + 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(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: return -ENOTTY; } @@ -191,13 +200,12 @@ static int wafwdt_open(struct inode *inode, struct file *file) return nonseekable_open(inode, file); } -static int -wafwdt_close(struct inode *inode, struct file *file) +static int wafwdt_close(struct inode *inode, struct file *file) { - if (expect_close == 42) { + if (expect_close == 42) wafwdt_stop(); - } else { - printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); + else { + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); wafwdt_ping(); } clear_bit(0, &wafwdt_is_open); @@ -209,12 +217,11 @@ wafwdt_close(struct inode *inode, struct file *file) * Notifier for system down */ -static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) { - if (code == SYS_DOWN || code == SYS_HALT) { - /* Turn the WDT off */ + if (code == SYS_DOWN || code == SYS_HALT) wafwdt_stop(); - } return NOTIFY_DONE; } @@ -226,7 +233,7 @@ static const struct file_operations wafwdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wafwdt_write, - .ioctl = wafwdt_ioctl, + .unlocked_ioctl = wafwdt_ioctl, .open = wafwdt_open, .release = wafwdt_close, }; @@ -250,45 +257,42 @@ static int __init wafwdt_init(void) { int ret; - printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n"); + pr_info("WDT driver for Wafer 5823 single board computer initialising\n"); if (timeout < 1 || timeout > 255) { timeout = WD_TIMO; - printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n", + 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")) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_stop); + 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")) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", - wdt_start); + 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) { - printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); goto error3; } ret = misc_register(&wafwdt_miscdev); if (ret != 0) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto error4; } - printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", timeout, nowayout); return ret; @@ -307,7 +311,7 @@ static void __exit wafwdt_exit(void) { misc_deregister(&wafwdt_miscdev); unregister_reboot_notifier(&wafwdt_notifier); - if(wdt_stop != wdt_start) + if (wdt_stop != wdt_start) release_region(wdt_stop, 1); release_region(wdt_start, 1); } @@ -318,6 +322,5 @@ module_exit(wafwdt_exit); MODULE_AUTHOR("Justin Cormack"); MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); /* end of wafer5823wdt.c */ diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c new file mode 100644 index 00000000000..cec9b559647 --- /dev/null +++ b/drivers/watchdog/watchdog_core.c @@ -0,0 +1,226 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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/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 "watchdog_core.h" /* For watchdog_dev_register/... */ + +static DEFINE_IDA(watchdog_ida); +static struct class *watchdog_class; + +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->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 + * @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. + * + * A zero is returned on success and -EINVAL for failure. + */ +int watchdog_init_timeout(struct watchdog_device *wdd, + unsigned int timeout_parm, struct device *dev) +{ + unsigned int t = 0; + int ret = 0; + + watchdog_check_min_max_timeout(wdd); + + /* try to get the timeout module parameter first */ + if (!watchdog_timeout_invalid(wdd, timeout_parm) && timeout_parm) { + wdd->timeout = timeout_parm; + return ret; + } + if (timeout_parm) + ret = -EINVAL; + + /* try to get the timeout_sec property */ + if (dev == NULL || dev->of_node == NULL) + return ret; + of_property_read_u32(dev->of_node, "timeout-sec", &t); + if (!watchdog_timeout_invalid(wdd, t) && t) + wdd->timeout = t; + else + ret = -EINVAL; + + return ret; +} +EXPORT_SYMBOL_GPL(watchdog_init_timeout); + +/** + * 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) +{ + int ret, id, devno; + + if (wdd == NULL || wdd->info == NULL || wdd->ops == NULL) + return -EINVAL; + + /* Mandatory operations need to be supported */ + if (wdd->ops->start == NULL || wdd->ops->stop == NULL) + 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! + */ + + mutex_init(&wdd->lock); + 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; + } + } + + devno = wdd->cdev.dev; + wdd->dev = device_create(watchdog_class, wdd->parent, devno, + NULL, "watchdog%d", wdd->id); + if (IS_ERR(wdd->dev)) { + watchdog_dev_unregister(wdd); + ida_simple_remove(&watchdog_ida, id); + ret = PTR_ERR(wdd->dev); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(watchdog_register_device); + +/** + * 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) +{ + int ret; + int devno; + + if (wdd == NULL) + return; + + devno = wdd->cdev.dev; + ret = watchdog_dev_unregister(wdd); + if (ret) + pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); + device_destroy(watchdog_class, devno); + ida_simple_remove(&watchdog_ida, wdd->id); + wdd->dev = NULL; +} +EXPORT_SYMBOL_GPL(watchdog_unregister_device); + +static int __init watchdog_init(void) +{ + int err; + + watchdog_class = class_create(THIS_MODULE, "watchdog"); + if (IS_ERR(watchdog_class)) { + pr_err("couldn't create class\n"); + return PTR_ERR(watchdog_class); + } + + err = watchdog_dev_init(); + if (err < 0) { + class_destroy(watchdog_class); + return err; + } + + return 0; +} + +static void __exit watchdog_exit(void) +{ + watchdog_dev_exit(); + class_destroy(watchdog_class); + ida_destroy(&watchdog_ida); +} + +subsys_initcall(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 00000000000..6c951418fca --- /dev/null +++ b/drivers/watchdog/watchdog_core.h @@ -0,0 +1,37 @@ +/* + * 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>. + * + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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 MAX_DOGS 32 /* Maximum number of watchdog devices */ + +/* + * Functions/procedures to be called by the core + */ +extern int watchdog_dev_register(struct watchdog_device *); +extern int watchdog_dev_unregister(struct watchdog_device *); +extern int __init watchdog_dev_init(void); +extern void __exit watchdog_dev_exit(void); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c new file mode 100644 index 00000000000..6aaefbad303 --- /dev/null +++ b/drivers/watchdog/watchdog_dev.c @@ -0,0 +1,604 @@ +/* + * 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>. + * + * + * 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> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * 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 module stuff/... */ +#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/watchdog.h> /* For watchdog specific items */ +#include <linux/miscdevice.h> /* For handling misc devices */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ + +#include "watchdog_core.h" + +/* the dev_t structure to store the dynamically allocated watchdog devices */ +static dev_t watchdog_devt; +/* the watchdog device behind /dev/watchdog */ +static struct watchdog_device *old_wdd; + +/* + * watchdog_ping: ping the watchdog. + * @wddev: 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. + */ + +static int watchdog_ping(struct watchdog_device *wddev) +{ + int err = 0; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_ping; + } + + if (!watchdog_active(wddev)) + goto out_ping; + + if (wddev->ops->ping) + err = wddev->ops->ping(wddev); /* ping the watchdog */ + else + err = wddev->ops->start(wddev); /* restart watchdog */ + +out_ping: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_start: wrapper to start the watchdog. + * @wddev: the watchdog device to start + * + * Start the watchdog if it is not active and mark it active. + * This function returns zero on success or a negative errno code for + * failure. + */ + +static int watchdog_start(struct watchdog_device *wddev) +{ + int err = 0; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_start; + } + + if (watchdog_active(wddev)) + goto out_start; + + err = wddev->ops->start(wddev); + if (err == 0) + set_bit(WDOG_ACTIVE, &wddev->status); + +out_start: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_stop: wrapper to stop the watchdog. + * @wddev: the watchdog device to stop + * + * Stop the watchdog if it is still active and unmark it active. + * This function returns zero on success or a negative errno code for + * failure. + * If the 'nowayout' feature was set, the watchdog cannot be stopped. + */ + +static int watchdog_stop(struct watchdog_device *wddev) +{ + int err = 0; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_stop; + } + + if (!watchdog_active(wddev)) + goto out_stop; + + if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { + dev_info(wddev->dev, "nowayout prevents watchdog being stopped!\n"); + err = -EBUSY; + goto out_stop; + } + + err = wddev->ops->stop(wddev); + if (err == 0) + clear_bit(WDOG_ACTIVE, &wddev->status); + +out_stop: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_get_status: wrapper to get the watchdog status + * @wddev: the watchdog device to get the status from + * @status: the status of the watchdog device + * + * Get the watchdog's status flags. + */ + +static int watchdog_get_status(struct watchdog_device *wddev, + unsigned int *status) +{ + int err = 0; + + *status = 0; + if (!wddev->ops->status) + return -EOPNOTSUPP; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_status; + } + + *status = wddev->ops->status(wddev); + +out_status: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_set_timeout: set the watchdog timer timeout + * @wddev: the watchdog device to set the timeout for + * @timeout: timeout to set in seconds + */ + +static int watchdog_set_timeout(struct watchdog_device *wddev, + unsigned int timeout) +{ + int err; + + if ((wddev->ops->set_timeout == NULL) || + !(wddev->info->options & WDIOF_SETTIMEOUT)) + return -EOPNOTSUPP; + + if (watchdog_timeout_invalid(wddev, timeout)) + return -EINVAL; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_timeout; + } + + err = wddev->ops->set_timeout(wddev, timeout); + +out_timeout: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_get_timeleft: wrapper to get the time left before a reboot + * @wddev: 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). + */ + +static int watchdog_get_timeleft(struct watchdog_device *wddev, + unsigned int *timeleft) +{ + int err = 0; + + *timeleft = 0; + if (!wddev->ops->get_timeleft) + return -EOPNOTSUPP; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_timeleft; + } + + *timeleft = wddev->ops->get_timeleft(wddev); + +out_timeleft: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined + * @wddev: the watchdog device to do the ioctl on + * @cmd: watchdog command + * @arg: argument pointer + */ + +static int watchdog_ioctl_op(struct watchdog_device *wddev, unsigned int cmd, + unsigned long arg) +{ + int err; + + if (!wddev->ops->ioctl) + return -ENOIOCTLCMD; + + mutex_lock(&wddev->lock); + + if (test_bit(WDOG_UNREGISTERED, &wddev->status)) { + err = -ENODEV; + goto out_ioctl; + } + + err = wddev->ops->ioctl(wddev, cmd, arg); + +out_ioctl: + mutex_unlock(&wddev->lock); + return err; +} + +/* + * 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). + */ + +static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + struct watchdog_device *wdd = file->private_data; + 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, &wdd->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, &wdd->status); + } + + /* someone wrote to us, so we send the watchdog a keepalive ping */ + watchdog_ping(wdd); + + 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. + */ + +static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct watchdog_device *wdd = file->private_data; + void __user *argp = (void __user *)arg; + int __user *p = argp; + unsigned int val; + int err; + + err = watchdog_ioctl_op(wdd, cmd, arg); + if (err != -ENOIOCTLCMD) + return err; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, wdd->info, + sizeof(struct watchdog_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + err = watchdog_get_status(wdd, &val); + if (err == -ENODEV) + return err; + return put_user(val, p); + case WDIOC_GETBOOTSTATUS: + return put_user(wdd->bootstatus, p); + case WDIOC_SETOPTIONS: + if (get_user(val, p)) + return -EFAULT; + if (val & WDIOS_DISABLECARD) { + err = watchdog_stop(wdd); + if (err < 0) + return err; + } + if (val & WDIOS_ENABLECARD) { + err = watchdog_start(wdd); + if (err < 0) + return err; + } + return 0; + case WDIOC_KEEPALIVE: + if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) + return -EOPNOTSUPP; + watchdog_ping(wdd); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(val, p)) + return -EFAULT; + err = watchdog_set_timeout(wdd, val); + if (err < 0) + return err; + /* 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) */ + watchdog_ping(wdd); + /* Fall */ + case WDIOC_GETTIMEOUT: + /* timeout == 0 means that we don't know the timeout */ + if (wdd->timeout == 0) + return -EOPNOTSUPP; + return put_user(wdd->timeout, p); + case WDIOC_GETTIMELEFT: + err = watchdog_get_timeleft(wdd, &val); + if (err) + return err; + return put_user(val, p); + default: + return -ENOTTY; + } +} + +/* + * 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. + */ + +static int watchdog_open(struct inode *inode, struct file *file) +{ + int err = -EBUSY; + struct watchdog_device *wdd; + + /* Get the corresponding watchdog device */ + if (imajor(inode) == MISC_MAJOR) + wdd = old_wdd; + else + wdd = container_of(inode->i_cdev, struct watchdog_device, cdev); + + /* the watchdog is single open! */ + if (test_and_set_bit(WDOG_DEV_OPEN, &wdd->status)) + return -EBUSY; + + /* + * If the /dev/watchdog device is open, we don't want the module + * to be unloaded. + */ + if (!try_module_get(wdd->ops->owner)) + goto out; + + err = watchdog_start(wdd); + if (err < 0) + goto out_mod; + + file->private_data = wdd; + + if (wdd->ops->ref) + wdd->ops->ref(wdd); + + /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ + return nonseekable_open(inode, file); + +out_mod: + module_put(wdd->ops->owner); +out: + clear_bit(WDOG_DEV_OPEN, &wdd->status); + return err; +} + +/* + * 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. + */ + +static int watchdog_release(struct inode *inode, struct file *file) +{ + struct watchdog_device *wdd = file->private_data; + int err = -EBUSY; + + /* + * 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 (!test_bit(WDOG_ACTIVE, &wdd->status)) + err = 0; + else if (test_and_clear_bit(WDOG_ALLOW_RELEASE, &wdd->status) || + !(wdd->info->options & WDIOF_MAGICCLOSE)) + err = watchdog_stop(wdd); + + /* If the watchdog was not stopped, send a keepalive ping */ + if (err < 0) { + mutex_lock(&wdd->lock); + if (!test_bit(WDOG_UNREGISTERED, &wdd->status)) + dev_crit(wdd->dev, "watchdog did not stop!\n"); + mutex_unlock(&wdd->lock); + watchdog_ping(wdd); + } + + /* Allow the owner module to be unloaded again */ + module_put(wdd->ops->owner); + + /* make sure that /dev/watchdog can be re-opened */ + clear_bit(WDOG_DEV_OPEN, &wdd->status); + + /* Note wdd may be gone after this, do not use after this! */ + if (wdd->ops->unref) + wdd->ops->unref(wdd); + + return 0; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, + .open = watchdog_open, + .release = watchdog_release, +}; + +static struct miscdevice watchdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &watchdog_fops, +}; + +/* + * watchdog_dev_register: register a watchdog device + * @watchdog: 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. + */ + +int watchdog_dev_register(struct watchdog_device *watchdog) +{ + int err, devno; + + if (watchdog->id == 0) { + old_wdd = watchdog; + watchdog_miscdev.parent = watchdog->parent; + err = misc_register(&watchdog_miscdev); + if (err != 0) { + pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n", + watchdog->info->identity, WATCHDOG_MINOR, err); + if (err == -EBUSY) + pr_err("%s: a legacy watchdog module is probably present.\n", + watchdog->info->identity); + old_wdd = NULL; + return err; + } + } + + /* Fill in the data structures */ + devno = MKDEV(MAJOR(watchdog_devt), watchdog->id); + cdev_init(&watchdog->cdev, &watchdog_fops); + watchdog->cdev.owner = watchdog->ops->owner; + + /* Add the device */ + err = cdev_add(&watchdog->cdev, devno, 1); + if (err) { + pr_err("watchdog%d unable to add device %d:%d\n", + watchdog->id, MAJOR(watchdog_devt), watchdog->id); + if (watchdog->id == 0) { + misc_deregister(&watchdog_miscdev); + old_wdd = NULL; + } + } + return err; +} + +/* + * watchdog_dev_unregister: unregister a watchdog device + * @watchdog: watchdog device + * + * Unregister the watchdog and if needed the legacy /dev/watchdog device. + */ + +int watchdog_dev_unregister(struct watchdog_device *watchdog) +{ + mutex_lock(&watchdog->lock); + set_bit(WDOG_UNREGISTERED, &watchdog->status); + mutex_unlock(&watchdog->lock); + + cdev_del(&watchdog->cdev); + if (watchdog->id == 0) { + misc_deregister(&watchdog_miscdev); + old_wdd = NULL; + } + return 0; +} + +/* + * watchdog_dev_init: init dev part of watchdog core + * + * Allocate a range of chardev nodes to use for watchdog devices + */ + +int __init watchdog_dev_init(void) +{ + int err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); + if (err < 0) + pr_err("watchdog: unable to allocate char dev region\n"); + 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); +} diff --git a/drivers/watchdog/wd501p.h b/drivers/watchdog/wd501p.h index a4504f40394..0e3a497d562 100644 --- a/drivers/watchdog/wd501p.h +++ b/drivers/watchdog/wd501p.h @@ -11,9 +11,9 @@ * * http://www.cymru.net * - * This driver is provided under the GNU General Public License, incorporated - * herein by reference. The driver is provided without warranty or - * support. + * This driver is provided under the GNU General Public License, + * incorporated herein by reference. The driver is provided without + * warranty or support. * * Release 0.04. * @@ -39,13 +39,13 @@ /* 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 */ + /* 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/wdrtas.c b/drivers/watchdog/wdrtas.c index 1d64e277567..0240c60d14e 100644 --- a/drivers/watchdog/wdrtas.c +++ b/drivers/watchdog/wdrtas.c @@ -26,6 +26,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/fs.h> #include <linux/init.h> #include <linux/kernel.h> @@ -35,9 +37,9 @@ #include <linux/reboot.h> #include <linux/types.h> #include <linux/watchdog.h> +#include <linux/uaccess.h> #include <asm/rtas.h> -#include <asm/uaccess.h> #define WDRTAS_MAGIC_CHAR 42 #define WDRTAS_SUPPORTED_MASK (WDIOF_SETTIMEOUT | \ @@ -46,17 +48,10 @@ MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); MODULE_DESCRIPTION("RTAS watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); - -#ifdef CONFIG_WATCHDOG_NOWAYOUT -static int wdrtas_nowayout = 1; -#else -static int wdrtas_nowayout = 0; -#endif +static bool wdrtas_nowayout = WATCHDOG_NOWAYOUT; static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0); -static char wdrtas_expect_close = 0; +static char wdrtas_expect_close; static int wdrtas_interval; @@ -86,8 +81,8 @@ static char wdrtas_logbuffer[WDRTAS_LOGBUFFER_LEN]; * RTAS function set-indicator (surveillance). The unit of interval is * seconds. */ -static int -wdrtas_set_interval(int interval) + +static int wdrtas_set_interval(int interval) { long result; static int print_msg = 10; @@ -97,15 +92,17 @@ wdrtas_set_interval(int interval) result = rtas_call(wdrtas_token_set_indicator, 3, 1, NULL, WDRTAS_SURVEILLANCE_IND, 0, interval); - if ( (result < 0) && (print_msg) ) { - printk(KERN_ERR "wdrtas: setting the watchdog to %i " - "timeout failed: %li\n", interval, result); + 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 @@ -116,18 +113,23 @@ wdrtas_set_interval(int 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) +static int wdrtas_get_interval(int fallback_value) { long result; - char value[4]; + 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, (void *)__pa(&value), 4); - if ( (value[0] != 0) || (value[1] != 2) || (value[3] != 0) || - (result < 0) ) { - printk(KERN_WARNING "wdrtas: could not get sp_spi watchdog " - "timeout (%li). Continuing\n", result); + 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; } @@ -141,8 +143,7 @@ wdrtas_get_interval(int fallback_value) * wdrtas_timer_start starts the watchdog by calling the RTAS function * set-interval (surveillance) */ -static void -wdrtas_timer_start(void) +static void wdrtas_timer_start(void) { wdrtas_set_interval(wdrtas_interval); } @@ -153,47 +154,19 @@ wdrtas_timer_start(void) * wdrtas_timer_stop stops the watchdog timer by calling the RTAS function * set-interval (surveillance) */ -static void -wdrtas_timer_stop(void) +static void wdrtas_timer_stop(void) { wdrtas_set_interval(0); } /** - * wdrtas_log_scanned_event - logs an event we received during keepalive - * - * wdrtas_log_scanned_event prints a message to the log buffer dumping - * the results of the last event-scan call - */ -static void -wdrtas_log_scanned_event(void) -{ - int i; - - for (i = 0; i < WDRTAS_LOGBUFFER_LEN; i += 16) - printk(KERN_INFO "wdrtas: dumping event (line %i/%i), data = " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x\n", - (i / 16) + 1, (WDRTAS_LOGBUFFER_LEN / 16), - wdrtas_logbuffer[i + 0], wdrtas_logbuffer[i + 1], - wdrtas_logbuffer[i + 2], wdrtas_logbuffer[i + 3], - wdrtas_logbuffer[i + 4], wdrtas_logbuffer[i + 5], - wdrtas_logbuffer[i + 6], wdrtas_logbuffer[i + 7], - wdrtas_logbuffer[i + 8], wdrtas_logbuffer[i + 9], - wdrtas_logbuffer[i + 10], wdrtas_logbuffer[i + 11], - wdrtas_logbuffer[i + 12], wdrtas_logbuffer[i + 13], - wdrtas_logbuffer[i + 14], wdrtas_logbuffer[i + 15]); -} - -/** * 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) +static void wdrtas_timer_keepalive(void) { long result; @@ -203,10 +176,11 @@ wdrtas_timer_keepalive(void) (void *)__pa(wdrtas_logbuffer), WDRTAS_LOGBUFFER_LEN); if (result < 0) - printk(KERN_ERR "wdrtas: event-scan failed: %li\n", - result); + pr_err("event-scan failed: %li\n", result); if (result == 0) - wdrtas_log_scanned_event(); + print_hex_dump(KERN_INFO, "dumping event, data: ", + DUMP_PREFIX_OFFSET, 16, 1, + wdrtas_logbuffer, WDRTAS_LOGBUFFER_LEN, false); } while (result == 0); } @@ -218,19 +192,15 @@ wdrtas_timer_keepalive(void) * 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) +static int wdrtas_get_temperature(void) { - long result; + int result; int temperature = 0; - result = rtas_call(wdrtas_token_get_sensor_state, 2, 2, - (void *)__pa(&temperature), - WDRTAS_THERMAL_SENSOR, 0); + result = rtas_get_sensor(WDRTAS_THERMAL_SENSOR, 0, &temperature); if (result < 0) - printk(KERN_WARNING "wdrtas: reading the thermal sensor " - "faild: %li\n", result); + pr_warn("reading the thermal sensor failed: %i\n", result); else temperature = ((temperature * 9) / 5) + 32; /* fahrenheit */ @@ -243,8 +213,7 @@ wdrtas_get_temperature(void) * returns a bitmask of defines WDIOF_... as defined in * include/linux/watchdog.h */ -static int -wdrtas_get_status(void) +static int wdrtas_get_status(void) { return 0; /* TODO */ } @@ -255,8 +224,7 @@ wdrtas_get_status(void) * 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) +static int wdrtas_get_boot_status(void) { return 0; /* TODO */ } @@ -276,8 +244,7 @@ wdrtas_get_boot_status(void) * character 'V'. This character allows the watchdog device to be closed * properly. */ -static ssize_t -wdrtas_write(struct file *file, const char __user *buf, +static ssize_t wdrtas_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { int i; @@ -306,7 +273,6 @@ out: /** * wdrtas_ioctl - ioctl function for the watchdog device - * @inode: inode structure * @file: file structure * @cmd: command for ioctl * @arg: argument pointer @@ -315,16 +281,16 @@ out: * * wdrtas_ioctl implements the watchdog API ioctls */ -static int -wdrtas_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) + +static long wdrtas_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int __user *argp = (void __user *)arg; int i; - static struct watchdog_info wdinfo = { + static const struct watchdog_info wdinfo = { .options = WDRTAS_SUPPORTED_MASK, .firmware_version = 0, - .identity = "wdrtas" + .identity = "wdrtas", }; switch (cmd) { @@ -357,9 +323,9 @@ wdrtas_ioctl(struct inode *inode, struct file *file, wdrtas_timer_keepalive(); wdrtas_timer_start(); } + /* not implemented. Done by H8 if (i & WDIOS_TEMPPANIC) { - /* not implemented. Done by H8 */ - } + } */ return 0; case WDIOC_KEEPALIVE: @@ -399,8 +365,7 @@ wdrtas_ioctl(struct inode *inode, struct file *file, * * function called when watchdog device is opened */ -static int -wdrtas_open(struct inode *inode, struct file *file) +static int wdrtas_open(struct inode *inode, struct file *file) { /* only open once */ if (atomic_inc_return(&wdrtas_miscdev_open) > 1) { @@ -423,15 +388,13 @@ wdrtas_open(struct inode *inode, struct file *file) * * close function. Always succeeds */ -static int -wdrtas_close(struct inode *inode, struct file *file) +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 { - printk(KERN_WARNING "wdrtas: got unexpected close. Watchdog " - "not stopped.\n"); + pr_warn("got unexpected close. Watchdog not stopped.\n"); wdrtas_timer_keepalive(); } @@ -453,8 +416,7 @@ wdrtas_close(struct inode *inode, struct file *file) * 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, +static ssize_t wdrtas_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int temperature = 0; @@ -478,8 +440,7 @@ wdrtas_temp_read(struct file *file, char __user *buf, * * function called when temperature device is opened */ -static int -wdrtas_temp_open(struct inode *inode, struct file *file) +static int wdrtas_temp_open(struct inode *inode, struct file *file) { return nonseekable_open(inode, file); } @@ -493,8 +454,7 @@ wdrtas_temp_open(struct inode *inode, struct file *file) * * close function. Always succeeds */ -static int -wdrtas_temp_close(struct inode *inode, struct file *file) +static int wdrtas_temp_close(struct inode *inode, struct file *file) { return 0; } @@ -509,10 +469,10 @@ wdrtas_temp_close(struct inode *inode, struct file *file) * * wdrtas_reboot stops the watchdog in case of a reboot */ -static int -wdrtas_reboot(struct notifier_block *this, unsigned long code, void *ptr) +static int wdrtas_reboot(struct notifier_block *this, + unsigned long code, void *ptr) { - if ( (code==SYS_DOWN) || (code==SYS_HALT) ) + if (code == SYS_DOWN || code == SYS_HALT) wdrtas_timer_stop(); return NOTIFY_DONE; @@ -524,7 +484,7 @@ static const struct file_operations wdrtas_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wdrtas_write, - .ioctl = wdrtas_ioctl, + .unlocked_ioctl = wdrtas_ioctl, .open = wdrtas_open, .release = wdrtas_close, }; @@ -556,41 +516,34 @@ static struct notifier_block wdrtas_notifier = { /** * wdrtas_get_tokens - reads in RTAS tokens * - * returns 0 on succes, <0 on failure + * 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) +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) { - printk(KERN_WARNING "wdrtas: couldn't get token for " - "get-sensor-state. Trying to continue without " - "temperature support.\n"); + 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) { - printk(KERN_WARNING "wdrtas: couldn't get token for " - "ibm,get-system-parameter. Trying to continue with " - "a default timeout value of %i seconds.\n", - WDRTAS_DEFAULT_INTERVAL); + 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) { - printk(KERN_ERR "wdrtas: couldn't get token for " - "set-indicator. Terminating watchdog code.\n"); + 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) { - printk(KERN_ERR "wdrtas: couldn't get token for event-scan. " - "Terminating watchdog code.\n"); + pr_err("couldn't get token for event-scan. Terminating watchdog code.\n"); return -EIO; } @@ -603,8 +556,7 @@ wdrtas_get_tokens(void) * wdrtas_register_devs unregisters the watchdog and temperature watchdog * misc devs */ -static void -wdrtas_unregister_devs(void) +static void wdrtas_unregister_devs(void) { misc_deregister(&wdrtas_miscdev); if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) @@ -614,29 +566,25 @@ wdrtas_unregister_devs(void) /** * wdrtas_register_devs - registers the misc dev handlers * - * returns 0 on succes, <0 on failure + * returns 0 on success, <0 on failure * * wdrtas_register_devs registers the watchdog and temperature watchdog * misc devs */ -static int -wdrtas_register_devs(void) +static int wdrtas_register_devs(void) { int result; result = misc_register(&wdrtas_miscdev); if (result) { - printk(KERN_ERR "wdrtas: couldn't register watchdog misc " - "device. Terminating watchdog code.\n"); + 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) { - printk(KERN_WARNING "wdrtas: couldn't register " - "watchdog temperature misc device. Continuing " - "without temperature support.\n"); + pr_warn("couldn't register watchdog temperature misc device. Continuing without temperature support.\n"); wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE; } } @@ -647,12 +595,11 @@ wdrtas_register_devs(void) /** * wdrtas_init - init function of the watchdog driver * - * returns 0 on succes, <0 on failure + * returns 0 on success, <0 on failure * * registers the file handlers and the reboot notifier */ -static int __init -wdrtas_init(void) +static int __init wdrtas_init(void) { if (wdrtas_get_tokens()) return -ENODEV; @@ -661,8 +608,7 @@ wdrtas_init(void) return -ENODEV; if (register_reboot_notifier(&wdrtas_notifier)) { - printk(KERN_ERR "wdrtas: could not register reboot notifier. " - "Terminating watchdog code.\n"); + pr_err("could not register reboot notifier. Terminating watchdog code.\n"); wdrtas_unregister_devs(); return -ENODEV; } @@ -680,8 +626,7 @@ wdrtas_init(void) * * unregisters the file handlers and the reboot notifier */ -static void __exit -wdrtas_exit(void) +static void __exit wdrtas_exit(void) { if (!wdrtas_nowayout) wdrtas_timer_stop(); diff --git a/drivers/watchdog/wdt.c b/drivers/watchdog/wdt.c index 756fb15fdce..e0206b5b7d8 100644 --- a/drivers/watchdog/wdt.c +++ b/drivers/watchdog/wdt.c @@ -1,8 +1,8 @@ /* - * Industrial Computer Source WDT500/501 driver + * Industrial Computer Source WDT501 driver * - * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (c) Copyright 1996-1997 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 @@ -24,13 +24,16 @@ * 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 + * 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> @@ -42,10 +45,9 @@ #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> #include "wd501p.h" static unsigned long wdt_is_open; @@ -60,15 +62,19 @@ static char expect_close; 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) ")"); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default=" + __MODULE_STRING(WD_TIMO) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 int io = 0x240; +static int irq = 11; static DEFINE_SPINLOCK(wdt_lock); @@ -77,13 +83,16 @@ MODULE_PARM_DESC(io, "WDT io port (default=0x240)"); module_param(irq, int, 0); MODULE_PARM_DESC(irq, "WDT irq (default=11)"); -#ifdef CONFIG_WDT_501 /* 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)"); -#endif /* CONFIG_WDT_501 */ +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 @@ -91,9 +100,9 @@ MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, defaul static void wdt_ctr_mode(int ctr, int mode) { - ctr<<=6; - ctr|=0x30; - ctr|=(mode<<1); + ctr <<= 6; + ctr |= 0x30; + ctr |= (mode << 1); outb_p(ctr, WDT_CR); } @@ -114,12 +123,15 @@ 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_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 */ + 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; @@ -131,13 +143,13 @@ static int wdt_start(void) * Stop the watchdog driver. */ -static int wdt_stop (void) +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 */ + wdt_ctr_load(2, 0); /* 0 length reset pulses now */ spin_unlock_irqrestore(&wdt_lock, flags); return 0; } @@ -145,34 +157,35 @@ static int wdt_stop (void) /** * wdt_ping: * - * Reload counter one with the watchdog heartbeat. We don't bother reloading - * the cascade counter. + * Reload counter one with the watchdog heartbeat. We don't bother + * reloading the cascade counter. */ -static int wdt_ping(void) +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 */ + 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); - return 0; } /** * 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 successfull we - * return 0. + * 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)) + if (t < 1 || t > 65535) return -EINVAL; heartbeat = t; @@ -182,7 +195,6 @@ static int wdt_set_heartbeat(int t) /** * wdt_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 @@ -191,36 +203,35 @@ static int wdt_set_heartbeat(int t) * we then map the bits onto the status ioctl flags. */ -static int wdt_get_status(int *status) +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); - *status=0; if (new_status & WDC_SR_ISOI0) - *status |= WDIOF_EXTERN1; + status |= WDIOF_EXTERN1; if (new_status & WDC_SR_ISII1) - *status |= WDIOF_EXTERN2; -#ifdef CONFIG_WDT_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; + 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; + } } -#endif /* CONFIG_WDT_501 */ - return 0; + return status; } -#ifdef CONFIG_WDT_501 /** * wdt_get_temperature: * @@ -228,7 +239,7 @@ static int wdt_get_status(int *status) * farenheit. It was designed by an imperial measurement luddite. */ -static int wdt_get_temperature(int *temperature) +static int wdt_get_temperature(void) { unsigned short c; unsigned long flags; @@ -236,10 +247,18 @@ static int wdt_get_temperature(int *temperature) spin_lock_irqsave(&wdt_lock, flags); c = inb_p(WDT_RT); spin_unlock_irqrestore(&wdt_lock, flags); - *temperature = (c * 11 / 15) + 7; - return 0; + 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"); } -#endif /* CONFIG_WDT_501 */ /** * wdt_interrupt: @@ -262,30 +281,25 @@ static irqreturn_t wdt_interrupt(int irq, void *dev_id) spin_lock(&wdt_lock); status = inb_p(WDT_SR); - printk(KERN_CRIT "WDT status %d\n", status); + pr_crit("WDT status %d\n", status); -#ifdef CONFIG_WDT_501 - if (!(status & WDC_SR_TGOOD)) - printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT)); - if (!(status & WDC_SR_PSUOVER)) - printk(KERN_CRIT "PSU over voltage.\n"); - if (!(status & WDC_SR_PSUUNDR)) - printk(KERN_CRIT "PSU under voltage.\n"); - if (tachometer) { - if (!(status & WDC_SR_FANGOOD)) - printk(KERN_CRIT "Possible fan fault.\n"); + if (type == 501) { + wdt_decode_501(status); + if (tachometer) { + if (!(status & WDC_SR_FANGOOD)) + pr_crit("Possible fan fault\n"); + } } -#endif /* CONFIG_WDT_501 */ if (!(status & WDC_SR_WCCR)) { #ifdef SOFTWARE_REBOOT #ifdef ONLY_TESTING - printk(KERN_CRIT "Would Reboot.\n"); + pr_crit("Would Reboot\n"); #else - printk(KERN_CRIT "Initiating system reboot.\n"); + pr_crit("Initiating system reboot\n"); emergency_restart(); #endif #else - printk(KERN_CRIT "Reset in 5ms.\n"); + pr_crit("Reset in 5ms\n"); #endif } spin_unlock(&wdt_lock); @@ -304,9 +318,10 @@ static irqreturn_t wdt_interrupt(int irq, void *dev_id) * 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) +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) { - if(count) { + if (count) { if (!nowayout) { size_t i; @@ -328,7 +343,6 @@ static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count /** * wdt_ioctl: - * @inode: inode of the device * @file: file handle to the device * @cmd: watchdog command * @arg: argument pointer @@ -338,15 +352,14 @@ static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count * querying capabilities and current status. */ -static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +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; - static struct watchdog_info ident = { + struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT| WDIOF_MAGICCLOSE| WDIOF_KEEPALIVEPING, @@ -356,38 +369,35 @@ static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, /* Add options according to the card we have */ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); -#ifdef CONFIG_WDT_501 - ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); - if (tachometer) - ident.options |= WDIOF_FANFAULT; -#endif /* CONFIG_WDT_501 */ - - switch(cmd) - { - default: - return -ENOTTY; - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; - - case WDIOC_GETSTATUS: - wdt_get_status(&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(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); + 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(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; } } @@ -405,7 +415,7 @@ static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, static int wdt_open(struct inode *inode, struct file *file) { - if(test_and_set_bit(0, &wdt_is_open)) + if (test_and_set_bit(0, &wdt_is_open)) return -EBUSY; /* * Activate @@ -432,14 +442,13 @@ static int wdt_release(struct inode *inode, struct file *file) wdt_stop(); clear_bit(0, &wdt_is_open); } else { - printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); wdt_ping(); } expect_close = 0; return 0; } -#ifdef CONFIG_WDT_501 /** * wdt_temp_read: * @file: file handle to the watchdog board @@ -451,14 +460,12 @@ static int wdt_release(struct inode *inode, struct file *file) * 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) +static ssize_t wdt_temp_read(struct file *file, char __user *buf, + size_t count, loff_t *ptr) { - int temperature; - - if (wdt_get_temperature(&temperature)) - return -EFAULT; + int temperature = wdt_get_temperature(); - if (copy_to_user (buf, &temperature, 1)) + if (copy_to_user(buf, &temperature, 1)) return -EFAULT; return 1; @@ -489,7 +496,6 @@ static int wdt_temp_release(struct inode *inode, struct file *file) { return 0; } -#endif /* CONFIG_WDT_501 */ /** * notify_sys: @@ -506,10 +512,8 @@ static int wdt_temp_release(struct inode *inode, struct file *file) static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { - if(code==SYS_DOWN || code==SYS_HALT) { - /* Turn the card off */ + if (code == SYS_DOWN || code == SYS_HALT) wdt_stop(); - } return NOTIFY_DONE; } @@ -522,7 +526,7 @@ static const struct file_operations wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wdt_write, - .ioctl = wdt_ioctl, + .unlocked_ioctl = wdt_ioctl, .open = wdt_open, .release = wdt_release, }; @@ -533,7 +537,6 @@ static struct miscdevice wdt_miscdev = { .fops = &wdt_fops, }; -#ifdef CONFIG_WDT_501 static const struct file_operations wdt_temp_fops = { .owner = THIS_MODULE, .llseek = no_llseek, @@ -547,7 +550,6 @@ static struct miscdevice temp_miscdev = { .name = "temperature", .fops = &wdt_temp_fops, }; -#endif /* CONFIG_WDT_501 */ /* * The WDT card needs to learn about soft shutdowns in order to @@ -571,16 +573,15 @@ static struct notifier_block wdt_notifier = { static void __exit wdt_exit(void) { misc_deregister(&wdt_miscdev); -#ifdef CONFIG_WDT_501 - misc_deregister(&temp_miscdev); -#endif /* CONFIG_WDT_501 */ + if (type == 501) + misc_deregister(&temp_miscdev); unregister_reboot_notifier(&wdt_notifier); free_irq(irq, NULL); - release_region(io,8); + release_region(io, 8); } /** - * wdt_init: + * 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. @@ -591,68 +592,71 @@ static int __init wdt_init(void) { int ret; - /* Check that the heartbeat value is within it's range ; if not reset to the default */ + 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); - printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n", + pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n", WD_TIMO); } if (!request_region(io, 8, "wdt501p")) { - printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io); + pr_err("I/O address 0x%04x already in use\n", io); ret = -EBUSY; goto out; } - ret = request_irq(irq, wdt_interrupt, IRQF_DISABLED, "wdt501p", NULL); - if(ret) { - printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq); + 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) { - printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); goto outirq; } -#ifdef CONFIG_WDT_501 - ret = misc_register(&temp_miscdev); - if (ret) { - printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", - TEMP_MINOR, ret); - goto outrbt; + 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; + } } -#endif /* CONFIG_WDT_501 */ ret = misc_register(&wdt_miscdev); if (ret) { - printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto outmisc; } - ret = 0; - printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", + pr_info("WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", io, irq, heartbeat, nowayout); -#ifdef CONFIG_WDT_501 - printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); -#endif /* CONFIG_WDT_501 */ - -out: - return ret; + if (type == 501) + pr_info("Fan Tachometer is %s\n", + tachometer ? "Enabled" : "Disabled"); + return 0; outmisc: -#ifdef CONFIG_WDT_501 - misc_deregister(&temp_miscdev); + if (type == 501) + misc_deregister(&temp_miscdev); outrbt: -#endif /* CONFIG_WDT_501 */ unregister_reboot_notifier(&wdt_notifier); outirq: free_irq(irq, NULL); outreg: - release_region(io,8); - goto out; + release_region(io, 8); +out: + return ret; } module_init(wdt_init); @@ -660,6 +664,4 @@ module_exit(wdt_exit); MODULE_AUTHOR("Alan Cox"); MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c index e4cf661dc89..ebbb183be61 100644 --- a/drivers/watchdog/wdt285.c +++ b/drivers/watchdog/wdt285.c @@ -6,7 +6,8 @@ * * SoftDog 0.05: A Software Watchdog Device * - * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * (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 @@ -15,6 +16,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> @@ -26,11 +29,12 @@ #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/irq.h> -#include <asm/uaccess.h> -#include <asm/hardware.h> #include <asm/mach-types.h> +#include <asm/system_info.h> #include <asm/hardware/dec21285.h> /* @@ -48,7 +52,7 @@ static unsigned long timer_alive; */ static void watchdog_fire(int irq, void *dev_id) { - printk(KERN_CRIT "Watchdog: Would Reboot.\n"); + pr_crit("Would Reboot\n"); *CSR_TIMER4_CNTL = 0; *CSR_TIMER4_CLR = 0; } @@ -115,8 +119,8 @@ static int watchdog_release(struct inode *inode, struct file *file) return 0; } -static ssize_t -watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos) +static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) { /* * Refresh the timer. @@ -127,28 +131,27 @@ watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos) return len; } -static struct watchdog_info ident = { +static const struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT, .identity = "Footbridge Watchdog", }; -static int -watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { - unsigned int new_margin; - int ret = -ENOTTY; + int __user *int_arg = (int __user *)arg; + int new_margin, ret = -ENOTTY; - switch(cmd) { + switch (cmd) { case WDIOC_GETSUPPORT: ret = 0; - if (copy_to_user((void *)arg, &ident, sizeof(ident))) + 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); + ret = put_user(0, int_arg); break; case WDIOC_KEEPALIVE: @@ -157,7 +160,7 @@ watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd, break; case WDIOC_SETTIMEOUT: - ret = get_user(new_margin, (int *)arg); + ret = get_user(new_margin, int_arg); if (ret) break; @@ -172,7 +175,7 @@ watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd, watchdog_ping(); /* Fall */ case WDIOC_GETTIMEOUT: - ret = put_user(soft_margin, (int *)arg); + ret = put_user(soft_margin, int_arg); break; } return ret; @@ -182,7 +185,7 @@ static const struct file_operations watchdog_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = watchdog_write, - .ioctl = watchdog_ioctl, + .unlocked_ioctl = watchdog_ioctl, .open = watchdog_open, .release = watchdog_release, }; @@ -204,11 +207,11 @@ static int __init footbridge_watchdog_init(void) if (retval < 0) return retval; - printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n", - soft_margin); + pr_info("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n", + soft_margin); if (machine_is_cats()) - printk("Warning: Watchdog reset may not work on this machine.\n"); + pr_warn("Warning: Watchdog reset may not work on this machine\n"); return 0; } @@ -220,10 +223,9 @@ static void __exit footbridge_watchdog_exit(void) MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>"); MODULE_DESCRIPTION("Footbridge watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); module_param(soft_margin, int, 0); -MODULE_PARM_DESC(soft_margin,"Watchdog timeout in seconds"); +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 index fb4b876c9fd..a8e6f87f60c 100644 --- a/drivers/watchdog/wdt977.c +++ b/drivers/watchdog/wdt977.c @@ -19,9 +19,12 @@ * 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 + * 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> @@ -33,19 +36,16 @@ #include <linux/watchdog.h> #include <linux/notifier.h> #include <linux/reboot.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/system.h> #include <asm/mach-types.h> -#include <asm/uaccess.h> #define WATCHDOG_VERSION "0.04" #define WATCHDOG_NAME "Wdt977" -#define PFX WATCHDOG_NAME ": " -#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n" #define IO_INDEX_PORT 0x370 /* on some systems it can be 0x3F0 */ -#define IO_DATA_PORT (IO_INDEX_PORT+1) +#define IO_DATA_PORT (IO_INDEX_PORT + 1) #define UNLOCK_DATA 0x87 #define LOCK_DATA 0xAA @@ -62,13 +62,16 @@ 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_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"); +MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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 @@ -95,14 +98,16 @@ static int wdt977_start(void) 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(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) - { + /* 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); @@ -113,7 +118,7 @@ static int wdt977_start(void) outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); - printk(KERN_INFO PFX "activated.\n"); + pr_info("activated\n"); return 0; } @@ -147,7 +152,8 @@ static int wdt977_stop(void) 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 */ + /* 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); @@ -157,7 +163,7 @@ static int wdt977_stop(void) outb_p(LOCK_DATA, IO_INDEX_PORT); spin_unlock_irqrestore(&spinlock, flags); - printk(KERN_INFO PFX "shutdown.\n"); + pr_info("shutdown\n"); return 0; } @@ -202,16 +208,18 @@ static int wdt977_set_timeout(int t) 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... + /* 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)) + if (tmrval < 1 || tmrval > 255) return -EINVAL; - /* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */ + /* timeout is the timeout in seconds, timeoutM is + the timeout in minutes) */ timeout = t; timeoutM = tmrval; return 0; @@ -243,7 +251,7 @@ static int wdt977_get_status(int *status) spin_unlock_irqrestore(&spinlock, flags); - *status=0; + *status = 0; if (new_status & 1) *status |= WDIOF_CARDRESET; @@ -258,7 +266,7 @@ static int wdt977_get_status(int *status) 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) ) + if (test_and_set_bit(0, &timer_alive)) return -EBUSY; if (nowayout) @@ -272,15 +280,14 @@ 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 + * Lock it in if it's a module and we set nowayout */ - if (expect_close == 42) - { + if (expect_close == 42) { wdt977_stop(); - clear_bit(0,&timer_alive); + clear_bit(0, &timer_alive); } else { wdt977_keepalive(); - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pr_crit("Unexpected close, not stopping watchdog!\n"); } expect_close = 0; return 0; @@ -301,17 +308,14 @@ static int wdt977_release(struct inode *inode, struct file *file) static ssize_t wdt977_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - if (count) - { - if (!nowayout) - { + if (count) { + if (!nowayout) { size_t i; /* In case it was set long ago */ expect_close = 0; - for (i = 0; i != count; i++) - { + for (i = 0; i != count; i++) { char c; if (get_user(c, buf + i)) return -EFAULT; @@ -326,6 +330,14 @@ static ssize_t wdt977_write(struct file *file, const char __user *buf, 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 @@ -337,16 +349,8 @@ static ssize_t wdt977_write(struct file *file, const char __user *buf, * according to their available features. */ -static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_MAGICCLOSE | - WDIOF_KEEPALIVEPING, - .firmware_version = 1, - .identity = WATCHDOG_NAME, -}; - -static int wdt977_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long wdt977_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { int status; int new_options, retval = -EINVAL; @@ -358,11 +362,7 @@ static int wdt977_ioctl(struct inode *inode, struct file *file, uarg.i = (int __user *)arg; - switch(cmd) - { - default: - return -ENOTTY; - + switch (cmd) { case WDIOC_GETSUPPORT: return copy_to_user(uarg.ident, &ident, sizeof(ident)) ? -EFAULT : 0; @@ -374,12 +374,8 @@ static int wdt977_ioctl(struct inode *inode, struct file *file, case WDIOC_GETBOOTSTATUS: return put_user(0, uarg.i); - case WDIOC_KEEPALIVE: - wdt977_keepalive(); - return 0; - case WDIOC_SETOPTIONS: - if (get_user (new_options, uarg.i)) + if (get_user(new_options, uarg.i)) return -EFAULT; if (new_options & WDIOS_DISABLECARD) { @@ -394,12 +390,16 @@ static int wdt977_ioctl(struct inode *inode, struct file *file, 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; + return -EINVAL; wdt977_keepalive(); /* Fall */ @@ -407,29 +407,30 @@ static int wdt977_ioctl(struct inode *inode, struct file *file, 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) + if (code == SYS_DOWN || code == SYS_HALT) wdt977_stop(); return NOTIFY_DONE; } -static const struct file_operations wdt977_fops= -{ +static const struct file_operations wdt977_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wdt977_write, - .ioctl = wdt977_ioctl, + .unlocked_ioctl = wdt977_ioctl, .open = wdt977_open, .release = wdt977_release, }; -static struct miscdevice wdt977_miscdev= -{ +static struct miscdevice wdt977_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &wdt977_fops, @@ -443,50 +444,42 @@ static int __init wd977_init(void) { int rc; - //if (!machine_is_netwinder()) - // return -ENODEV; + pr_info("driver v%s\n", WATCHDOG_VERSION); - printk(KERN_INFO PFX DRIVER_VERSION); - - /* Check that the timeout value is within it's range ; if not reset to the default */ - if (wdt977_set_timeout(timeout)) - { + /* 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); - printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n", + 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)) - { - printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", - IO_INDEX_PORT); + 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) - { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", - rc); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); goto err_out_region; } rc = misc_register(&wdt977_miscdev); - if (rc) - { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - wdt977_miscdev.minor, rc); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt977_miscdev.minor, rc); goto err_out_reboot; } - printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n", + pr_info("initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n", timeout, nowayout, testmode); return 0; @@ -495,7 +488,7 @@ err_out_reboot: unregister_reboot_notifier(&wdt977_notifier); err_out_region: if (!machine_is_netwinder()) - release_region(IO_INDEX_PORT,2); + release_region(IO_INDEX_PORT, 2); err_out: return rc; } @@ -505,7 +498,7 @@ static void __exit wd977_exit(void) wdt977_stop(); misc_deregister(&wdt977_miscdev); unregister_reboot_notifier(&wdt977_notifier); - release_region(IO_INDEX_PORT,2); + release_region(IO_INDEX_PORT, 2); } module_init(wd977_init); @@ -514,4 +507,3 @@ module_exit(wd977_exit); MODULE_AUTHOR("Woody Suwalski <woodys@xandros.com>"); MODULE_DESCRIPTION("W83977AF Watchdog driver"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c index 1355608683e..48b2c058b00 100644 --- a/drivers/watchdog/wdt_pci.c +++ b/drivers/watchdog/wdt_pci.c @@ -1,8 +1,8 @@ /* * Industrial Computer Source PCI-WDT500/501 driver * - * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. - * http://www.redhat.com + * (c) Copyright 1996-1997 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 @@ -29,12 +29,16 @@ * 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 + * 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> @@ -42,42 +46,26 @@ #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/init.h> #include <linux/fs.h> #include <linux/pci.h> +#include <linux/io.h> +#include <linux/uaccess.h> -#include <asm/io.h> -#include <asm/uaccess.h> -#include <asm/system.h> #define WDT_IS_PCI #include "wd501p.h" -#define PFX "wdt_pci: " - -/* - * Until Access I/O gets their application for a PCI vendor ID approved, - * I don't think that it's appropriate to move these constants into the - * regular pci_ids.h file. -- JPN 2000/01/18 - */ - -#ifndef PCI_VENDOR_ID_ACCESSIO -#define PCI_VENDOR_ID_ACCESSIO 0x494f -#endif -#ifndef PCI_DEVICE_ID_WDG_CSM -#define PCI_DEVICE_ID_WDG_CSM 0x22c0 -#endif - /* We can only use 1 card due to the /dev/watchdog restriction */ static int dev_count; -static struct semaphore open_sem; +static unsigned long open_lock; static DEFINE_SPINLOCK(wdtpci_lock); static char expect_close; -static int io; +static resource_size_t io; static int irq; /* Default timeout */ @@ -86,19 +74,26 @@ static int irq; 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) ")"); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" + __MODULE_STRING(WD_TIMO) ")"); -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +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_WDT_501_PCI /* 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)"); -#endif /* CONFIG_WDT_501_PCI */ +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 @@ -106,16 +101,19 @@ MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, defa static void wdtpci_ctr_mode(int ctr, int mode) { - ctr<<=6; - ctr|=0x30; - ctr|=(mode<<1); - outb_p(ctr, WDT_CR); + ctr <<= 6; + ctr |= 0x30; + ctr |= (mode << 1); + outb(ctr, WDT_CR); + udelay(8); } static void wdtpci_ctr_load(int ctr, int val) { - outb_p(val&0xFF, WDT_COUNT0+ctr); - outb_p(val>>8, WDT_COUNT0+ctr); + outb(val & 0xFF, WDT_COUNT0 + ctr); + udelay(8); + outb(val >> 8, WDT_COUNT0 + ctr); + udelay(8); } /** @@ -134,23 +132,35 @@ static int wdtpci_start(void) * "pet" the watchdog, as Access says. * This resets the clock outputs. */ - inb_p(WDT_DC); /* Disable watchdog */ - wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ - outb_p(0, WDT_DC); /* Enable watchdog */ - - inb_p(WDT_DC); /* Disable watchdog */ - outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */ - inb_p(WDT_BUZZER); /* disable */ - inb_p(WDT_OPTONOTRST); /* disable */ - inb_p(WDT_OPTORST); /* disable */ - inb_p(WDT_PROGOUT); /* disable */ - 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 */ + 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_p(0, WDT_DC); /* Enable watchdog */ + outb(0, WDT_DC); /* Enable watchdog */ + udelay(8); spin_unlock_irqrestore(&wdtpci_lock, flags); return 0; @@ -162,14 +172,15 @@ static int wdtpci_start(void) * Stop the watchdog driver. */ -static int wdtpci_stop (void) +static int wdtpci_stop(void) { unsigned long flags; /* Turn the card off */ spin_lock_irqsave(&wdtpci_lock, flags); - inb_p(WDT_DC); /* Disable watchdog */ - wdtpci_ctr_load(2,0); /* 0 length reset pulses now */ + 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; } @@ -177,20 +188,23 @@ static int wdtpci_stop (void) /** * wdtpci_ping: * - * Reload counter one with the watchdog heartbeat. We don't bother reloading - * the cascade counter. + * Reload counter one with the watchdog heartbeat. We don't bother + * reloading the cascade counter. */ static int wdtpci_ping(void) { unsigned long flags; - /* Write a watchdog value */ spin_lock_irqsave(&wdtpci_lock, flags); - inb_p(WDT_DC); /* Disable watchdog */ - wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ - wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ - outb_p(0, WDT_DC); /* Enable watchdog */ + /* 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; } @@ -199,14 +213,14 @@ static int wdtpci_ping(void) * 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 successfull we - * return 0. + * 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)) + if (t < 1 || t > 65535) return -EINVAL; heartbeat = t; @@ -227,29 +241,33 @@ static int wdtpci_set_heartbeat(int t) static int wdtpci_get_status(int *status) { - unsigned char new_status=inb_p(WDT_SR); + unsigned char new_status; + unsigned long flags; - *status=0; + 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; -#ifdef CONFIG_WDT_501_PCI - 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; + 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; + } } -#endif /* CONFIG_WDT_501_PCI */ return 0; } -#ifdef CONFIG_WDT_501_PCI /** * wdtpci_get_temperature: * @@ -259,12 +277,15 @@ static int wdtpci_get_status(int *status) static int wdtpci_get_temperature(int *temperature) { - unsigned short c=inb_p(WDT_RT); - + 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; } -#endif /* CONFIG_WDT_501_PCI */ /** * wdtpci_interrupt: @@ -282,34 +303,42 @@ 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=inb_p(WDT_SR); - - printk(KERN_CRIT PFX "status %d\n", status); - -#ifdef CONFIG_WDT_501_PCI - if (!(status & WDC_SR_TGOOD)) - printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT)); - if (!(status & WDC_SR_PSUOVER)) - printk(KERN_CRIT PFX "PSU over voltage.\n"); - if (!(status & WDC_SR_PSUUNDR)) - printk(KERN_CRIT PFX "PSU under voltage.\n"); - if (tachometer) { - if (!(status & WDC_SR_FANGOOD)) - printk(KERN_CRIT PFX "Possible fan fault.\n"); + 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"); + } } -#endif /* CONFIG_WDT_501_PCI */ - if (!(status&WDC_SR_WCCR)) { + if (!(status & WDC_SR_WCCR)) { #ifdef SOFTWARE_REBOOT #ifdef ONLY_TESTING - printk(KERN_CRIT PFX "Would Reboot.\n"); + pr_crit("Would Reboot\n"); #else - printk(KERN_CRIT PFX "Initiating system reboot.\n"); + pr_crit("Initiating system reboot\n"); emergency_restart(NULL); #endif #else - printk(KERN_CRIT PFX "Reset in 5ms.\n"); + pr_crit("Reset in 5ms\n"); #endif } + spin_unlock(&wdtpci_lock); return IRQ_HANDLED; } @@ -325,17 +354,19 @@ static irqreturn_t wdtpci_interrupt(int irq, void *dev_id) * 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) +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)) + if (get_user(c, buf + i)) return -EFAULT; if (c == 'V') expect_close = 42; @@ -343,13 +374,11 @@ static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t co } wdtpci_ping(); } - return count; } /** * wdtpci_ioctl: - * @inode: inode of the device * @file: file handle to the device * @cmd: watchdog command * @arg: argument pointer @@ -359,15 +388,15 @@ static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t co * querying capabilities and current status. */ -static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd, - unsigned long arg) +static long wdtpci_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) { - int new_heartbeat; - int status; void __user *argp = (void __user *)arg; int __user *p = argp; + int new_heartbeat; + int status; - static struct watchdog_info ident = { + struct watchdog_info ident = { .options = WDIOF_SETTIMEOUT| WDIOF_MAGICCLOSE| WDIOF_KEEPALIVEPING, @@ -377,38 +406,35 @@ static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd /* Add options according to the card we have */ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); -#ifdef CONFIG_WDT_501_PCI - ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); - if (tachometer) - ident.options |= WDIOF_FANFAULT; -#endif /* CONFIG_WDT_501_PCI */ + if (type == 501) { + ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER| + WDIOF_POWEROVER); + if (tachometer) + ident.options |= WDIOF_FANFAULT; + } - switch(cmd) - { - default: - return -ENOTTY; - 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(); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); + 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(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; } } @@ -426,12 +452,11 @@ static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd static int wdtpci_open(struct inode *inode, struct file *file) { - if (down_trylock(&open_sem)) + if (test_and_set_bit(0, &open_lock)) return -EBUSY; - if (nowayout) { + if (nowayout) __module_get(THIS_MODULE); - } /* * Activate */ @@ -456,15 +481,14 @@ static int wdtpci_release(struct inode *inode, struct file *file) if (expect_close == 42) { wdtpci_stop(); } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping timer!"); + pr_crit("Unexpected close, not stopping timer!\n"); wdtpci_ping(); } expect_close = 0; - up(&open_sem); + clear_bit(0, &open_lock); return 0; } -#ifdef CONFIG_WDT_501_PCI /** * wdtpci_temp_read: * @file: file handle to the watchdog board @@ -476,14 +500,15 @@ static int wdtpci_release(struct inode *inode, struct file *file) * 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) +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)) + if (copy_to_user(buf, &temperature, 1)) return -EFAULT; return 1; @@ -514,7 +539,6 @@ static int wdtpci_temp_release(struct inode *inode, struct file *file) { return 0; } -#endif /* CONFIG_WDT_501_PCI */ /** * notify_sys: @@ -529,12 +553,10 @@ static int wdtpci_temp_release(struct inode *inode, struct file *file) */ static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code, - void *unused) + void *unused) { - if (code==SYS_DOWN || code==SYS_HALT) { - /* Turn the card off */ + if (code == SYS_DOWN || code == SYS_HALT) wdtpci_stop(); - } return NOTIFY_DONE; } @@ -547,7 +569,7 @@ static const struct file_operations wdtpci_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .write = wdtpci_write, - .ioctl = wdtpci_ioctl, + .unlocked_ioctl = wdtpci_ioctl, .open = wdtpci_open, .release = wdtpci_release, }; @@ -558,7 +580,6 @@ static struct miscdevice wdtpci_miscdev = { .fops = &wdtpci_fops, }; -#ifdef CONFIG_WDT_501_PCI static const struct file_operations wdtpci_temp_fops = { .owner = THIS_MODULE, .llseek = no_llseek, @@ -572,7 +593,6 @@ static struct miscdevice temp_miscdev = { .name = "temperature", .fops = &wdtpci_temp_fops, }; -#endif /* CONFIG_WDT_501_PCI */ /* * The WDT card needs to learn about soft shutdowns in order to @@ -584,122 +604,125 @@ static struct notifier_block wdtpci_notifier = { }; -static int __devinit wdtpci_init_one (struct pci_dev *dev, - const struct pci_device_id *ent) +static int wdtpci_init_one(struct pci_dev *dev, + const struct pci_device_id *ent) { int ret = -EIO; dev_count++; if (dev_count > 1) { - printk (KERN_ERR PFX "this driver only supports 1 device\n"); + 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)) { - printk (KERN_ERR PFX "Not possible to enable PCI Device\n"); + if (pci_enable_device(dev)) { + pr_err("Not possible to enable PCI Device\n"); return -ENODEV; } - if (pci_resource_start (dev, 2) == 0x0000) { - printk (KERN_ERR PFX "No I/O-Address for card detected\n"); + if (pci_resource_start(dev, 2) == 0x0000) { + pr_err("No I/O-Address for card detected\n"); ret = -ENODEV; goto out_pci; } - sema_init(&open_sem, 1); - - irq = dev->irq; - io = pci_resource_start (dev, 2); - - if (request_region (io, 16, "wdt_pci") == NULL) { - printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io); + 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; } - if (request_irq (irq, wdtpci_interrupt, IRQF_DISABLED | IRQF_SHARED, + irq = dev->irq; + io = pci_resource_start(dev, 2); + + if (request_irq(irq, wdtpci_interrupt, IRQF_SHARED, "wdt_pci", &wdtpci_miscdev)) { - printk (KERN_ERR PFX "IRQ %d is not free\n", irq); + pr_err("IRQ %d is not free\n", irq); goto out_reg; } - printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n", - io, irq); + 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 it's range ; if not reset to the default */ + /* 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); - printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n", WD_TIMO); } - ret = register_reboot_notifier (&wdtpci_notifier); + ret = register_reboot_notifier(&wdtpci_notifier); if (ret) { - printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); + pr_err("cannot register reboot notifier (err=%d)\n", ret); goto out_irq; } -#ifdef CONFIG_WDT_501_PCI - ret = misc_register (&temp_miscdev); - if (ret) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - TEMP_MINOR, ret); - goto out_rbt; + 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; + } } -#endif /* CONFIG_WDT_501_PCI */ - ret = misc_register (&wdtpci_miscdev); + ret = misc_register(&wdtpci_miscdev); if (ret) { - printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); goto out_misc; } - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", heartbeat, nowayout); -#ifdef CONFIG_WDT_501_PCI - printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); -#endif /* CONFIG_WDT_501_PCI */ + if (type == 501) + pr_info("Fan Tachometer is %s\n", + tachometer ? "Enabled" : "Disabled"); ret = 0; out: return ret; out_misc: -#ifdef CONFIG_WDT_501_PCI - misc_deregister(&temp_miscdev); + if (type == 501) + misc_deregister(&temp_miscdev); out_rbt: -#endif /* CONFIG_WDT_501_PCI */ unregister_reboot_notifier(&wdtpci_notifier); out_irq: free_irq(irq, &wdtpci_miscdev); out_reg: - release_region (io, 16); + pci_release_region(dev, 2); out_pci: pci_disable_device(dev); goto out; } -static void __devexit wdtpci_remove_one (struct pci_dev *pdev) +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); -#ifdef CONFIG_WDT_501_PCI - misc_deregister(&temp_miscdev); -#endif /* CONFIG_WDT_501_PCI */ + if (type == 501) + misc_deregister(&temp_miscdev); unregister_reboot_notifier(&wdtpci_notifier); free_irq(irq, &wdtpci_miscdev); - release_region(io, 16); + pci_release_region(pdev, 2); pci_disable_device(pdev); dev_count--; } -static struct pci_device_id wdtpci_pci_tbl[] = { +static const struct pci_device_id wdtpci_pci_tbl[] = { { .vendor = PCI_VENDOR_ID_ACCESSIO, - .device = PCI_DEVICE_ID_WDG_CSM, + .device = PCI_DEVICE_ID_ACCESSIO_WDG_CSM, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, }, @@ -712,45 +735,11 @@ static struct pci_driver wdtpci_driver = { .name = "wdt_pci", .id_table = wdtpci_pci_tbl, .probe = wdtpci_init_one, - .remove = __devexit_p(wdtpci_remove_one), + .remove = wdtpci_remove_one, }; - -/** - * wdtpci_cleanup: - * - * 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 xx seconds or reboot. - */ - -static void __exit wdtpci_cleanup(void) -{ - pci_unregister_driver (&wdtpci_driver); -} - - -/** - * wdtpci_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 wdtpci_init(void) -{ - return pci_register_driver (&wdtpci_driver); -} - - -module_init(wdtpci_init); -module_exit(wdtpci_cleanup); +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"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -MODULE_ALIAS_MISCDEV(TEMP_MINOR); diff --git a/drivers/watchdog/wm831x_wdt.c b/drivers/watchdog/wm831x_wdt.c new file mode 100644 index 00000000000..2fa17e746ff --- /dev/null +++ b/drivers/watchdog/wm831x_wdt.c @@ -0,0 +1,312 @@ +/* + * Watchdog driver for the wm831x PMICs + * + * Copyright (C) 2009 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation + */ + +#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.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_gpio; + 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); + + if (driver_data->update_gpio) { + gpio_set_value_cansleep(driver_data->update_gpio, + driver_data->update_state); + driver_data->update_state = !driver_data->update_state; + ret = 0; + goto out; + } + + 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 wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *chip_pdata = dev_get_platdata(pdev->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); + goto err; + } + reg = ret; + + if (reg & WM831X_WDOG_DEBUG) + dev_warn(wm831x->dev, "Watchdog is paused\n"); + + driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data), + GFP_KERNEL); + if (!driver_data) { + ret = -ENOMEM; + goto err; + } + + 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; + 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; + + if (pdata->update_gpio) { + ret = devm_gpio_request_one(&pdev->dev, + pdata->update_gpio, + GPIOF_OUT_INIT_LOW, + "Watchdog update"); + if (ret < 0) { + dev_err(wm831x->dev, + "Failed to request update GPIO: %d\n", + ret); + goto err; + } + + driver_data->update_gpio = pdata->update_gpio; + + /* Make sure the watchdog takes hardware updates */ + reg |= WM831X_WDOG_RST_SRC; + } + + 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); + goto err; + } + } + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) { + dev_err(wm831x->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err; + } + + platform_set_drvdata(pdev, driver_data); + + return 0; + +err: + return ret; +} + +static int wm831x_wdt_remove(struct platform_device *pdev) +{ + struct wm831x_wdt_drvdata *driver_data = platform_get_drvdata(pdev); + + watchdog_unregister_device(&driver_data->wdt); + + return 0; +} + +static struct platform_driver wm831x_wdt_driver = { + .probe = wm831x_wdt_probe, + .remove = wm831x_wdt_remove, + .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 00000000000..34d272ada23 --- /dev/null +++ b/drivers/watchdog/wm8350_wdt.c @@ -0,0 +1,180 @@ +/* + * Watchdog driver for the wm8350 + * + * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation + */ + +#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); + + /* 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 00000000000..7a42dffd39e --- /dev/null +++ b/drivers/watchdog/xen_wdt.c @@ -0,0 +1,364 @@ +/* + * Xen Watchdog Driver + * + * (c) Copyright 2010 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define DRV_NAME "wdt" +#define DRV_VERSION "0.01" + +#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/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/uaccess.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 DEFINE_SPINLOCK(wdt_lock); +static struct sched_watchdog wdt; +static __kernel_time_t wdt_expires; +static bool is_active, expect_release; + +#define WATCHDOG_TIMEOUT 60 /* in seconds */ +static unsigned int timeout = WATCHDOG_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 __kernel_time_t set_timeout(void) +{ + wdt.timeout = timeout; + return ktime_to_timespec(ktime_get()).tv_sec + timeout; +} + +static int xen_wdt_start(void) +{ + __kernel_time_t expires; + int err; + + spin_lock(&wdt_lock); + + expires = set_timeout(); + 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); + + spin_unlock(&wdt_lock); + + return err; +} + +static int xen_wdt_stop(void) +{ + int err = 0; + + spin_lock(&wdt_lock); + + wdt.timeout = 0; + if (wdt.id) + err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); + if (!err) + wdt.id = 0; + + spin_unlock(&wdt_lock); + + return err; +} + +static int xen_wdt_kick(void) +{ + __kernel_time_t expires; + int err; + + spin_lock(&wdt_lock); + + expires = set_timeout(); + if (wdt.id) + err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt); + else + err = -ENXIO; + if (!err) + wdt_expires = expires; + + spin_unlock(&wdt_lock); + + return err; +} + +static int xen_wdt_open(struct inode *inode, struct file *file) +{ + int err; + + /* /dev/watchdog can only be opened once */ + if (xchg(&is_active, true)) + return -EBUSY; + + err = xen_wdt_start(); + if (err == -EBUSY) + err = xen_wdt_kick(); + return err ?: nonseekable_open(inode, file); +} + +static int xen_wdt_release(struct inode *inode, struct file *file) +{ + int err = 0; + + if (expect_release) + err = xen_wdt_stop(); + else { + pr_crit("unexpected close, not stopping watchdog!\n"); + xen_wdt_kick(); + } + is_active = err; + expect_release = false; + return err; +} + +static ssize_t xen_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; + + /* in case it was set long ago */ + expect_release = false; + + /* 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 = true; + } + } + + /* someone wrote to us, we should reload the timer */ + xen_wdt_kick(); + } + return len; +} + +static long xen_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_options, retval = -EINVAL; + int new_timeout; + int __user *argp = (void __user *)arg; + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = DRV_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, argp); + + case WDIOC_SETOPTIONS: + if (get_user(new_options, argp)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) + retval = xen_wdt_stop(); + if (new_options & WDIOS_ENABLECARD) { + retval = xen_wdt_start(); + if (retval == -EBUSY) + retval = xen_wdt_kick(); + } + return retval; + + case WDIOC_KEEPALIVE: + xen_wdt_kick(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, argp)) + return -EFAULT; + if (!new_timeout) + return -EINVAL; + timeout = new_timeout; + xen_wdt_kick(); + /* fall through */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, argp); + + case WDIOC_GETTIMELEFT: + retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec; + return put_user(retval, argp); + } + + return -ENOTTY; +} + +static const struct file_operations xen_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = xen_wdt_write, + .unlocked_ioctl = xen_wdt_ioctl, + .open = xen_wdt_open, + .release = xen_wdt_release, +}; + +static struct miscdevice xen_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &xen_wdt_fops, +}; + +static int xen_wdt_probe(struct platform_device *dev) +{ + struct sched_watchdog wd = { .id = ~0 }; + int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd); + + switch (ret) { + case -EINVAL: + if (!timeout) { + timeout = WATCHDOG_TIMEOUT; + pr_info("timeout value invalid, using %d\n", timeout); + } + + ret = misc_register(&xen_wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (%d)\n", + WATCHDOG_MINOR, ret); + break; + } + + pr_info("initialized (timeout=%ds, nowayout=%d)\n", + timeout, nowayout); + break; + + case -ENOSYS: + pr_info("not supported\n"); + ret = -ENODEV; + break; + + default: + pr_info("bogus return value %d\n", ret); + break; + } + + return ret; +} + +static int xen_wdt_remove(struct platform_device *dev) +{ + /* Stop the timer before we leave */ + if (!nowayout) + xen_wdt_stop(); + + misc_deregister(&xen_wdt_miscdev); + + return 0; +} + +static void xen_wdt_shutdown(struct platform_device *dev) +{ + xen_wdt_stop(); +} + +static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state) +{ + typeof(wdt.id) id = wdt.id; + int rc = xen_wdt_stop(); + + 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(); +} + +static struct platform_driver xen_wdt_driver = { + .probe = xen_wdt_probe, + .remove = xen_wdt_remove, + .shutdown = xen_wdt_shutdown, + .suspend = xen_wdt_suspend, + .resume = xen_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init xen_wdt_init_module(void) +{ + int err; + + if (!xen_domain()) + return -ENODEV; + + pr_info("Xen WatchDog Timer Driver v%s\n", DRV_VERSION); + + 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); + pr_info("module unloaded\n"); +} + +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_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); |
