diff options
Diffstat (limited to 'drivers/watchdog')
114 files changed, 44121 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig new file mode 100644 index 00000000..4253c958 --- /dev/null +++ b/drivers/watchdog/Kconfig @@ -0,0 +1,1316 @@ +# +# Watchdog device configuration +# + +menuconfig WATCHDOG + bool "Watchdog Timer Support" + ---help--- + If you say Y here (and to one of the following options) and create a + character special file /dev/watchdog with major number 10 and minor + number 130 using mknod ("man mknod"), you will get a watchdog, i.e.: + subsequently opening the file and then failing to write to it for + longer than 1 minute will result in rebooting the machine. This + could be useful for a networked machine that needs to come back + on-line as fast as possible after a lock-up. There's both a watchdog + implementation entirely in software (which can sometimes fail to + reboot the machine) and a driver for hardware watchdog boards, which + are more robust and can also keep track of the temperature inside + your computer. For details, read + <file:Documentation/watchdog/watchdog-api.txt> in the kernel source. + + The watchdog is usually used together with the watchdog daemon + which is available from + <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can + also monitor NFS connections and can reboot the machine when the process + table is full. + + If unsure, say N. + +if WATCHDOG + +config WATCHDOG_CORE + 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 + The default watchdog behaviour (which you get if you say N here) is + to stop the timer if the process managing it closes the file + /dev/watchdog. It's always remotely possible that this process might + get killed. If you say Y here, the watchdog cannot be stopped once + it has been started. + +# +# General Watchdog drivers +# + +comment "Watchdog Device Drivers" + +# Architecture Independent + +config SOFT_WATCHDOG + tristate "Software watchdog" + select WATCHDOG_CORE + help + A software monitoring watchdog. This will fail to reboot your system + from some situations that the hardware watchdog will recover + from. Equally it's a lot cheaper to install. + + To compile this driver as a module, choose M here: the + module will be called softdog. + +config 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. + +# ALPHA Architecture + +# ARM Architecture + +config ARM_SP805_WATCHDOG + tristate "ARM SP805 Watchdog" + depends on ARM_AMBA + 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 + help + Watchdog timer embedded into AT91RM9200 chips. This will reboot your + system when the timeout is reached. + +config AT91SAM9X_WATCHDOG + tristate "AT91SAM9X / AT91CAP9 watchdog" + depends on ARCH_AT91 && !ARCH_AT91RM9200 + 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 + help + The Intel Footbridge chip contains a built-in watchdog circuit. Say Y + here if you wish to use this. Alternatively say M to compile the + driver as a module, which will be called wdt285. + + This driver does not work on all machines. In particular, early CATS + boards have hardware problems that will cause the machine to simply + lock up if the watchdog fires. + + "If in doubt, leave it out" - say N. + +config 977_WATCHDOG + tristate "NetWinder WB83C977 watchdog" + depends on FOOTBRIDGE && ARCH_NETWINDER + help + Say Y here to include support for the WB977 watchdog included in + NetWinder machines. Alternatively say M to compile the driver as + a module, which will be called wdt977. + + Not sure? It's safe to say N. + +config 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 + help + Say Y here if to include support for the watchdog timer + in the Intel IXP4xx network processors. This driver can + be built as a module by choosing M. The module will + be called ixp4xx_wdt. + + Note: The internal IXP4xx watchdog does a soft CPU reset + which doesn't reset any peripherals. There are circumstances + where the watchdog will fail to reset the board correctly + (e.g., if the boot ROM is in an unreadable state). + + Say N if you are unsure. + +config KS8695_WATCHDOG + tristate "KS8695 watchdog" + depends on ARCH_KS8695 + help + 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 HAVE_S3C2410_WATCHDOG + select WATCHDOG_CORE + help + 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) + then watchdog intervals of over approximately 20seconds are + unavailable. + + The driver can be built as a module by choosing M, and will + be called s3c2410_wdt + +config SA1100_WATCHDOG + tristate "SA1100/PXA2xx watchdog" + depends on ARCH_SA1100 || ARCH_PXA + help + Watchdog timer embedded into SA11x0 and PXA2xx chips. This will + reboot your system when timeout is reached. + + NOTE: once enabled, this timer cannot be disabled. + + To compile this driver as a module, choose M here: the + module will be called sa1100_wdt. + +config DW_WATCHDOG + tristate "Synopsys DesignWare watchdog" + depends on ARM && HAVE_CLK + help + Say Y here if to include support for the Synopsys DesignWare + watchdog timer found in many ARM chips. + To compile this driver as a module, choose M here: the + module will be called dw_wdt. + +config MPCORE_WATCHDOG + tristate "MPcore watchdog" + depends on HAVE_ARM_TWD + help + Watchdog timer embedded into the MPcore system. + + To compile this driver as a module, choose M here: the + module will be called mpcore_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. + + To compile this driver as a module, choose M here: the + module will be called ep93xx_wdt. + +config OMAP_WATCHDOG + tristate "OMAP Watchdog" + depends on ARCH_OMAP16XX || ARCH_OMAP2PLUS + help + Support for TI OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog. Say 'Y' + here to enable the OMAP1610/OMAP1710/OMAP2420/OMAP3430/OMAP4430 watchdog timer. + +config PNX4008_WATCHDOG + tristate "PNX4008 and LPC32XX Watchdog" + depends on ARCH_PNX4008 || ARCH_LPC32XX + select WATCHDOG_CORE + help + Say Y here if to include support for the watchdog timer + in the PNX4008 or LPC32XX processor. + This driver can be built as a module by choosing M. The module + will be called pnx4008_wdt. + + Say N if you are unsure. + +config IOP_WATCHDOG + tristate "IOP Watchdog" + depends on PLAT_IOP + select WATCHDOG_NOWAYOUT if (ARCH_IOP32X || ARCH_IOP33X) + help + Say Y here if to include support for the watchdog timer + in the Intel IOP3XX & IOP13XX I/O Processors. This driver can + be built as a module by choosing M. The module will + be called iop_wdt. + + Note: The IOP13XX watchdog does an Internal Bus Reset which will + affect both cores and the peripherals of the IOP. The ATU-X + and/or ATUe configuration registers will remain intact, but if + operating as an Root Complex and/or Central Resource, the PCI-X + and/or PCIe busses will also be reset. THIS IS A VERY BIG HAMMER. + +config DAVINCI_WATCHDOG + tristate "DaVinci watchdog" + depends on ARCH_DAVINCI + help + Say Y here if to include support for the watchdog timer + in the DaVinci DM644x/DM646x processors. + To compile this driver as a module, choose M here: the + module will be called davinci_wdt. + + NOTE: once enabled, this timer cannot be disabled. + Say N if you are unsure. + +config ORION_WATCHDOG + tristate "Orion watchdog" + depends on ARCH_ORION5X || ARCH_KIRKWOOD + 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 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 + help + Support for TI TWL4030 watchdog. Say 'Y' here to enable the + watchdog timer support for TWL4030 chips. + +config STMP3XXX_WATCHDOG + tristate "Freescale STMP3XXX watchdog" + depends on ARCH_STMP3XXX + help + Say Y here if to include support for the watchdog timer + for the Sigmatel STMP37XX/378X SoC. + To compile this driver as a module, choose M here: the + module will be called stmp3xxx_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 IMX_HAVE_PLATFORM_IMX2_WDT + 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 WMT_WDT + tristate "WMT Watchdog support" + depends on ARCH_WMT + default y + ---help--- + This is the driver for the hardware watchdog + Say Y here if you want to enable WMT Watchdog . + + To compile this driver as a module, choose M here + +# AVR32 Architecture + +config AT32AP700X_WDT + tristate "AT32AP700x watchdog" + depends on CPU_AT32AP700X + help + Watchdog timer embedded into AT32AP700x devices. This will reboot + your system when the timeout is reached. + +# BLACKFIN Architecture + +config BFIN_WDT + tristate "Blackfin On-Chip Watchdog Timer" + depends on BLACKFIN + ---help--- + If you say yes here you will get support for the Blackfin On-Chip + Watchdog Timer. 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 bfin_wdt. + +# CRIS Architecture + +# FRV Architecture + +# H8300 Architecture + +# X86 (i386 + ia64 + x86_64) Architecture + +config ACQUIRE_WDT + tristate "Acquire SBC Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on Single Board + Computers produced by Acquire Inc (and others). This watchdog + simply watches your kernel to make sure it doesn't freeze, and if + it does, it reboots your computer after a certain amount of time. + + To compile this driver as a module, choose M here: the + module will be called acquirewdt. + + Most people will say N. + +config ADVANTECH_WDT + tristate "Advantech SBC Watchdog Timer" + depends on X86 + help + If you are configuring a Linux kernel for the Advantech single-board + computer, say `Y' here to support its built-in watchdog timer + feature. More information can be found at + <http://www.advantech.com.tw/products/> + +config ALIM1535_WDT + tristate "ALi M1535 PMU Watchdog Timer" + depends on X86 && PCI + ---help--- + This is the driver for the hardware watchdog on the ALi M1535 PMU. + + To compile this driver as a module, choose M here: the + module will be called alim1535_wdt. + + Most people will say N. + +config ALIM7101_WDT + tristate "ALi M7101 PMU Computer Watchdog" + depends on PCI + help + This is the driver for the hardware watchdog on the ALi M7101 PMU + as used in the x86 Cobalt servers and also found in some + SPARC Netra servers too. + + To compile this driver as a module, choose M here: the + module will be called alim7101_wdt. + + Most people will say N. + +config F71808E_WDT + tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" + depends on X86 && EXPERIMENTAL + 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 + help + This is the driver for the hardware watchdog built in to the + AMD "Elan" SC520 microcomputer commonly used in embedded systems. + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + You can compile this driver directly into the kernel, or use + it as a module. The module will be called sc520_wdt. + +config SBC_FITPC2_WATCHDOG + tristate "Compulab SBC-FITPC2 watchdog" + depends on X86 + ---help--- + This is the driver for the built-in watchdog timer on the fit-PC2, + fit-PC2i, CM-iAM single-board computers made by Compulab. + + It`s possible to enable 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 + help + Enable support for the watchdog timer on the Eurotech CPU-1220 and + CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product + information are at <http://www.eurotech.it/>. + +config IB700_WDT + tristate "IB700 SBC Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the IB700 Single + Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog + simply watches your kernel to make sure it doesn't freeze, and if + it does, it reboots your computer after a certain amount of time. + + This driver is like the WDT501 driver but for slightly different hardware. + + To compile this driver as a module, choose M here: the + module will be called ib700wdt. + + Most people will say N. + +config IBMASR + tristate "IBM Automatic Server Restart" + depends on X86 + help + This is the driver for the IBM Automatic Server Restart watchdog + timer built-in into some eServer xSeries machines. + + To compile this driver as a module, choose M here: the + module will be called ibmasr. + +config WAFER_WDT + tristate "ICP Single Board Computer Watchdog Timer" + depends on X86 + help + This is a driver for the hardware watchdog on the ICP Single + Board Computer. This driver is working on (at least) the following + IPC SBC's: Wafer 5823, Rocky 4783, Rocky 3703 and Rocky 3782. + + To compile this driver as a module, choose M here: the + module will be called wafer5823wdt. + +config I6300ESB_WDT + tristate "Intel 6300ESB Timer/Watchdog" + depends on X86 && PCI + ---help--- + Hardware driver for the watchdog timer built into the Intel + 6300ESB controller hub. + + To compile this driver as a module, choose M here: the + module will be called i6300esb. + +config INTEL_SCU_WATCHDOG + bool "Intel SCU Watchdog for Mobile Platforms" + depends on X86_MRST + ---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 ITCO_WDT + tristate "Intel TCO Timer/Watchdog" + depends on (X86 || IA64) && PCI + ---help--- + Hardware driver for the intel TCO timer based watchdog devices. + These drivers are included in the Intel 82801 I/O Controller + Hub family (from ICH0 up to ICH10) and in the Intel 63xxESB + controller hub. + + The TCO (Total Cost of Ownership) timer is a watchdog timer + that will reboot the machine after its second expiration. The + expiration time can be configured with the "heartbeat" parameter. + + On some motherboards the driver may fail to reset the chipset's + NO_REBOOT flag which prevents the watchdog from rebooting the + machine. If this is the case you will get a kernel message like + "failed to reset NO_REBOOT flag, reboot disabled by hardware". + + To compile this driver as a module, choose M here: the + module will be called iTCO_wdt. + +config ITCO_VENDOR_SUPPORT + bool "Intel TCO Timer/Watchdog Specific Vendor Support" + depends on ITCO_WDT + ---help--- + Add vendor specific support to the intel TCO timer based watchdog + devices. At this moment we only have additional support for some + SuperMicro Inc. motherboards. + +config IT8712F_WDT + tristate "IT8712F (Smart Guardian) Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the built-in watchdog timer on the IT8712F + Super I/0 chipset used on many motherboards. + + If the driver does not work, then make sure that the game port in + the BIOS is enabled. + + To compile this driver as a module, choose M here: the + module will be called it8712f_wdt. + +config IT87_WDT + tristate "IT87 Watchdog Timer" + depends on X86 && EXPERIMENTAL + ---help--- + This is the driver for the hardware watchdog on the ITE IT8702, + IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 Super I/O chips. + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + To compile this driver as a module, choose M here: the module will + be called it87_wdt. + +config HP_WATCHDOG + tristate "HP ProLiant iLO2+ Hardware Watchdog Timer" + depends on X86 && PCI + help + A software monitoring watchdog and NMI sourcing driver. This driver + will detect lockups and provide a stack trace. This is a driver that + will only load on an HP ProLiant system with a minimum of iLO2 support. + To compile this driver as a module, choose M here: the module will be + called hpwdt. + +config HPWDT_NMI_DECODING + bool "NMI 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" + depends on X86 + help + This is a driver for National Semiconductor PC87307/PC97307 hardware + watchdog cards as found on the SC1200. This watchdog is mainly used + for power management purposes and can be used to power down the device + during inactivity periods (includes interrupt activity monitoring). + + To compile this driver as a module, choose M here: the + module will be called sc1200wdt. + + Most people will say N. + +config SCx200_WDT + tristate "National Semiconductor SCx200 Watchdog" + depends on SCx200 && PCI + help + Enable the built-in watchdog timer support on the National + Semiconductor SCx200 processors. + + If compiled as a module, it will be called scx200_wdt. + +config PC87413_WDT + tristate "NS PC87413 watchdog" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the PC87413 chipset + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + To compile this driver as a module, choose M here: the + module will be called pc87413_wdt. + + Most people will say N. + +config NV_TCO + tristate "nVidia TCO Timer/Watchdog" + depends on X86 && PCI + ---help--- + Hardware driver for the TCO timer built into the nVidia Hub family + (such as the MCP51). The TCO (Total Cost of Ownership) timer is a + watchdog timer that will reboot the machine after its second + expiration. The expiration time can be configured with the + "heartbeat" parameter. + + On some motherboards the driver may fail to reset the chipset's + NO_REBOOT flag which prevents the watchdog from rebooting the + machine. If this is the case you will get a kernel message like + "failed to reset NO_REBOOT flag, reboot disabled by hardware". + + To compile this driver as a module, choose M here: the + module will be called nv_tco. + +config RDC321X_WDT + tristate "RDC R-321x SoC watchdog" + depends on X86_RDC321X + help + This is the driver for the built in hardware watchdog + in the RDC R-321x SoC. + + To compile this driver as a module, choose M here: the + module will be called rdc321x_wdt. + +config 60XX_WDT + tristate "SBC-60XX Watchdog Timer" + depends on X86 + help + This driver can be used with the watchdog timer found on some + single board computers, namely the 6010 PII based computer. + It may well work with other cards. It reads port 0x443 to enable + and re-set the watchdog timer, and reads port 0x45 to disable + the watchdog. If you have a card that behave in similar ways, + you can probably make this driver work with your card as well. + + You can compile this driver directly into the kernel, or use + it as a module. The module will be called sbc60xxwdt. + +config SBC8360_WDT + tristate "SBC8360 Watchdog Timer" + depends on X86 + ---help--- + + This is the driver for the hardware watchdog on the SBC8360 Single + Board Computer produced by Axiomtek Co., Ltd. (www.axiomtek.com). + + To compile this driver as a module, choose M here: the + module will be called sbc8360. + + Most people will say N. + +config SBC7240_WDT + tristate "SBC Nano 7240 Watchdog Timer" + depends on X86_32 && !UML + ---help--- + This is the driver for the hardware watchdog found on the IEI + single board computers EPIC Nano 7240 (and likely others). This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called sbc7240_wdt. + +config CPU5_WDT + tristate "SMA CPU5 Watchdog" + depends on X86 + ---help--- + TBD. + To compile this driver as a module, choose M here: the + module will be called cpu5wdt. + +config SMSC_SCH311X_WDT + tristate "SMSC SCH311X Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog timer on the + SMSC SCH3112, SCH3114 and SCH3116 Super IO chipset + (LPC IO with 8042 KBC, Reset Generation, HWM and multiple + serial ports). + + To compile this driver as a module, choose M here: the + module will be called sch311x_wdt. + +config SMSC37B787_WDT + tristate "Winbond SMsC37B787 Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog component on the + Winbond SMsC37B787 chipset as used on the NetRunner Mainboard + from Vision Systems and maybe others. + + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + Usually a userspace daemon will notify the kernel WDT driver that + userspace is still alive, at regular intervals. + + To compile this driver as a module, choose M here: the + module will be called smsc37b787_wdt. + + Most people will say N. + +config VIA_WDT + tristate "VIA Watchdog Timer" + depends on X86 + select WATCHDOG_CORE + ---help--- + This is the driver for the hardware watchdog timer on VIA + southbridge chipset CX700, VX800/VX820 or VX855/VX875. + + To compile this driver as a module, choose M here; the module + will be called via_wdt. + + Most people will say N. + +config W83627HF_WDT + tristate "W83627HF/W83627DHG Watchdog Timer" + depends on X86 + ---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). The driver also supports the W83627DHG chip. + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + To compile this driver as a module, choose M here: the + module will be called w83627hf_wdt. + + Most people will say N. + +config W83697HF_WDT + tristate "W83697HF/W83697HG Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the W83697HF/HG + chipset as used in Dedibox/VIA motherboards (and likely others). + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + To compile this driver as a module, choose M here: the + module will be called w83697hf_wdt. + + Most people will say N. + +config W83697UG_WDT + tristate "W83697UG/W83697UF Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the W83697UG/UF + chipset as used in MSI Fuzzy CX700 VIA motherboards (and likely others). + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + To compile this driver as a module, choose M here: the + module will be called w83697ug_wdt. + + Most people will say N. + +config W83877F_WDT + tristate "W83877F (EMACS) Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the W83877F chipset + as used in EMACS PC-104 motherboards (and likely others). This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called w83877f_wdt. + + Most people will say N. + +config W83977F_WDT + tristate "W83977F (PCM-5335) Watchdog Timer" + depends on X86 + ---help--- + This is the driver for the hardware watchdog on the W83977F I/O chip + as used in AAEON's PCM-5335 SBC (and likely others). This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called w83977f_wdt. + +config MACHZ_WDT + tristate "ZF MachZ Watchdog" + depends on X86 + ---help--- + If you are using a ZF Micro MachZ processor, say Y here, otherwise + N. This is the driver for the watchdog timer built-in on that + processor using ZF-Logic interface. This watchdog simply watches + your kernel to make sure it doesn't freeze, and if it does, it + reboots your computer after a certain amount of time. + + To compile this driver as a module, choose M here: the + module will be called machzwd. + +config SBC_EPX_C3_WATCHDOG + tristate "Winsystems SBC EPX-C3 watchdog" + depends on X86 + ---help--- + This is the driver for the built-in watchdog timer on the EPX-C3 + Single-board computer made by Winsystems, Inc. + + *Note*: This hardware watchdog is not probeable and thus there + is no way to know if writing to its IO address will corrupt + your system or have any real effect. The only way to be sure + that this driver does what you want is to make sure you + are running it on an EPX-C3 from Winsystems with the watchdog + timer at IO address 0x1ee and 0x1ef. It will write to both those + IO ports. Basically, the assumption is made that if you compile + this driver into your kernel and/or load it as a module, that you + know what you are doing and that you are in fact running on an + EPX-C3 board! + + To compile this driver as a module, choose M here: the + module will be called sbc_epx_c3. + +# M32R Architecture + +# M68K Architecture + +config M54xx_WATCHDOG + tristate "MCF54xx watchdog support" + depends on M548x + help + To compile this driver as a module, choose M here: the + module will be called m54xx_wdt. + +# MicroBlaze Architecture + +config XILINX_WATCHDOG + tristate "Xilinx Watchdog timer" + depends on MICROBLAZE + ---help--- + Watchdog driver for the xps_timebase_wdt ip core. + + IMPORTANT: The xps_timebase_wdt parent must have the property + "clock-frequency" at device tree. + + To compile this driver as a module, choose M here: the + module will be called of_xilinx_wdt. + +# 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 + help + Hardware driver for the Broadcom BCM47xx Watchog Timer. + +config RC32434_WDT + tristate "IDT RC32434 SoC Watchdog Timer" + depends on MIKROTIK_RB532 + help + Hardware driver for the IDT RC32434 SoC built-in + watchdog timer. + + To compile this driver as a module, choose M here: the + module will be called rc32434_wdt. + +config INDYDOG + tristate "Indy/I2 Hardware Watchdog" + depends on SGI_HAS_INDYDOG + help + Hardware driver for the Indy's/I2's watchdog. This is a + watchdog timer that will reboot the machine after a 60 second + timer expired and no process has written to /dev/watchdog during + that time. + +config JZ4740_WDT + tristate "Ingenic jz4740 SoC hardware watchdog" + depends on 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 + help + Hardware driver for the MTX-1 boards. This is a watchdog timer that + will reboot the machine after a 100 seconds timer expired. + +config PNX833X_WDT + tristate "PNX833x Hardware Watchdog" + depends on SOC_PNX8335 + help + 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" + depends on CPU_SB1 + help + Watchdog driver for the built in watchdog hardware in Sibyte + SoC processors. There are apparently two watchdog timers + on such processors; this driver supports only the first one, + because currently Linux only supports exporting one watchdog + to userspace. + + To compile this driver as a loadable module, choose M here. + The module will be called sb_wdog. + +config AR7_WDT + tristate "TI AR7 Watchdog Timer" + depends on AR7 + help + Hardware driver for the TI AR7 Watchdog Timer. + +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 CPU_CAVIUM_OCTEON + 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 LANTIQ_WDT + tristate "Lantiq SoC watchdog" + depends on LANTIQ + help + Hardware driver for the Lantiq 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 + bool "MPC52xx Watchdog Timer" + depends on PPC_MPC52xx + help + Use General Purpose Timer (GPT) 0 on the MPC5200 as Watchdog. + +config 8xxx_WDT + tristate "MPC8xxx Platform Watchdog Timer" + depends on PPC_8xx || PPC_83xx || PPC_86xx + help + This driver is for a SoC level watchdog that exists on some + Freescale PowerPC processors. So far this driver supports: + - MPC8xx watchdogs + - MPC83xx watchdogs + - MPC86xx watchdogs + + For BookE processors (MPC85xx) use the BOOKE_WDT driver instead. + +config 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 + tristate "PowerPC Book-E Watchdog Timer" + depends on BOOKE || 4xx + ---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 FSL_BOOKE + range 0 63 if FSL_BOOKE + default 3 if !FSL_BOOKE + range 0 3 if !FSL_BOOKE + 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. + +# PPC64 Architecture + +config WATCHDOG_RTAS + tristate "RTAS watchdog" + depends on PPC_RTAS + help + This driver adds watchdog support for the RTAS watchdog. + + To compile this driver as a module, choose M here. The module + will be called wdrtas. + +# S390 Architecture + +config ZVM_WATCHDOG + tristate "z/VM Watchdog Timer" + depends on S390 + 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. + + To compile this driver as a module, choose M here. The module + will be called vmwatchdog. + +# SUPERH (sh + sh64) Architecture + +config SH_WDT + tristate "SuperH Watchdog" + depends on SUPERH && (CPU_SH3 || CPU_SH4) + help + This driver adds watchdog support for the integrated watchdog in the + SuperH processors. If you have one of these processors and wish + to have watchdog support enabled, say Y, otherwise say N. + + As a side note, saying Y here will automatically boost HZ to 1000 + so that the timer has a chance to clear the overflow counter. On + slower systems (such as the SH-2 and SH-3) this will likely yield + some performance issues. As such, the WDT should be avoided here + unless it is absolutely necessary. + + To compile this driver as a module, choose M here: the + module will be called shwdt. + +# SPARC Architecture + +# SPARC64 Architecture + +config WATCHDOG_CP1XXX + tristate "CP1XXX Hardware Watchdog support" + depends on SPARC64 && PCI + ---help--- + This is the driver for the hardware watchdog timers present on + Sun Microsystems CompactPCI models CP1400 and CP1500. + + To compile this driver as a module, choose M here: the + module will be called cpwatchdog. + + If you do not have a CompactPCI model CP1400 or CP1500, or + another UltraSPARC-IIi-cEngine boardset with hardware watchdog, + you should say N to this option. + +config WATCHDOG_RIO + tristate "RIO Hardware Watchdog support" + depends on SPARC64 && PCI + help + Say Y here to support the hardware watchdog capability on Sun RIO + machines. The watchdog timeout period is normally one minute but + can be changed with a boot-time parameter. + +# 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 +# + +comment "ISA-based Watchdog Cards" + depends on ISA + +config PCWATCHDOG + tristate "Berkshire Products ISA-PC Watchdog" + depends on ISA + ---help--- + This is the driver for the Berkshire Products ISA-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. This driver is like the WDT501 driver but for different + hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC + watchdog cards can be ordered from <http://www.berkprod.com/>. + + To compile this driver as a module, choose M here: the + module will be called pcwd. + + Most people will say N. + +config MIXCOMWD + tristate "Mixcom Watchdog" + depends on ISA + ---help--- + This is a driver for the Mixcom hardware watchdog cards. This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called mixcomwd. + + Most people will say N. + +config WDT + tristate "WDT Watchdog timer" + depends on ISA + ---help--- + If you have a WDT500P or WDT501P watchdog board, say Y here, + otherwise N. It is not possible to probe for this board, which means + that you have to inform the kernel about the IO port and IRQ that + is needed (you can do this via the io and irq parameters) + + To compile this driver as a module, choose M here: the + module will be called wdt. + +# +# PCI-based Watchdog Cards +# + +comment "PCI-based Watchdog Cards" + depends on PCI + +config PCIPCWATCHDOG + tristate "Berkshire Products PCI-PC Watchdog" + depends on PCI + ---help--- + This is the driver for the Berkshire Products PCI-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. The card can also monitor the internal temperature of the PC. + More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>. + + To compile this driver as a module, choose M here: the + module will be called pcwd_pci. + + Most people will say N. + +config WDTPCI + tristate "PCI-WDT500/501 Watchdog timer" + depends on PCI + ---help--- + If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. + + If you have a PCI-WDT501 watchdog board then you can enable the + temperature sensor by setting the type parameter to 501. + + If you want to enable the Fan Tachometer on the PCI-WDT501, then you + can do this via the tachometer parameter. Only do this if you have a + fan tachometer actually set up. + + To compile this driver as a module, choose M here: the + module will be called wdt_pci. + +# +# USB-based Watchdog Cards +# + +comment "USB-based Watchdog Cards" + depends on USB + +config USBPCWATCHDOG + tristate "Berkshire Products USB-PC Watchdog" + depends on USB + ---help--- + This is the driver for the Berkshire Products USB-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. The card can also monitor the internal temperature of the PC. + More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. + + To compile this driver as a module, choose M here: the + module will be called pcwd_usb. + + Most people will say N. + +endif # WATCHDOG diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile new file mode 100644 index 00000000..66046d6a --- /dev/null +++ b/drivers/watchdog/Makefile @@ -0,0 +1,171 @@ +# +# 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 independent "softdog" driver. +# This means that if your ISA/PCI/USB card isn't detected that +# you can fall back to an architecture specific driver and if +# that also fails then you can fall back to the software watchdog +# to give you some cover. + +# ISA-based Watchdog Cards +obj-$(CONFIG_PCWATCHDOG) += pcwd.o +obj-$(CONFIG_MIXCOMWD) += mixcomwd.o +obj-$(CONFIG_WDT) += wdt.o + +# PCI-based Watchdog Cards +obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o +obj-$(CONFIG_WDTPCI) += wdt_pci.o + +# USB-based Watchdog Cards +obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o + +# ALPHA Architecture + +# ARM Architecture +obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o +obj-$(CONFIG_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_DW_WATCHDOG) += dw_wdt.o +obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_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 +obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o +obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o +obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_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_WMT_WDT) += wmt_wdt.o + + +# AVR32 Architecture +obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o + +# BLACKFIN Architecture +obj-$(CONFIG_BFIN_WDT) += bfin_wdt.o + +# CRIS Architecture + +# 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 +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_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_W83697UG_WDT) += w83697ug_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 + +# M32R Architecture + +# M68K Architecture +obj-$(CONFIG_M54xx_WATCHDOG) += m54xx_wdt.o + +# MicroBlaze Architecture +obj-$(CONFIG_XILINX_WATCHDOG) += of_xilinx_wdt.o + +# MIPS Architecture +obj-$(CONFIG_ATH79_WDT) += ath79_wdt.o +obj-$(CONFIG_BCM47XX_WDT) += bcm47xx_wdt.o +obj-$(CONFIG_BCM63XX_WDT) += bcm63xx_wdt.o +obj-$(CONFIG_RC32434_WDT) += rc32434_wdt.o +obj-$(CONFIG_INDYDOG) += indydog.o +obj-$(CONFIG_JZ4740_WDT) += jz4740_wdt.o +obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o +obj-$(CONFIG_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 + +# PARISC Architecture + +# POWERPC Architecture +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 + +# PPC64 Architecture +obj-$(CONFIG_WATCHDOG_RTAS) += wdrtas.o + +# S390 Architecture + +# SUPERH (sh + sh64) Architecture +obj-$(CONFIG_SH_WDT) += shwdt.o + +# SPARC Architecture + +# SPARC64 Architecture + +obj-$(CONFIG_WATCHDOG_RIO) += riowd.o +obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o + +# XTENSA Architecture + +# Xen +obj-$(CONFIG_XEN_WDT) += xen_wdt.o + +# Architecture Independent +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 new file mode 100644 index 00000000..4397881c --- /dev/null +++ b/drivers/watchdog/acquirewdt.c @@ -0,0 +1,340 @@ +/* + * Acquire Single Board Computer Watchdog Timer driver + * + * Based on wdt.c. Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Can't add timeout - driver doesn't allow changing value + */ + +/* + * Theory of Operation: + * The Watch-Dog Timer is provided to ensure that standalone + * Systems can always recover from catastrophic conditions that + * caused the CPU to crash. This condition may have occurred by + * external EMI or a software bug. When the CPU stops working + * correctly, hardware on the board will either perform a hardware + * reset (cold boot) or a non-maskable interrupt (NMI) to bring the + * system back to a known state. + * + * The Watch-Dog Timer is controlled by two I/O Ports. + * 443 hex - Read - Enable or refresh the Watch-Dog Timer + * 043 hex - Read - Disable the Watch-Dog Timer + * + * To enable the Watch-Dog Timer, a read from I/O port 443h must + * be performed. This will enable and activate the countdown timer + * which will eventually time out and either reset the CPU or cause + * an NMI depending on the setting of a jumper. To ensure that this + * reset condition does not occur, the Watch-Dog Timer must be + * periodically refreshed by reading the same I/O port 443h. + * The Watch-Dog Timer is disabled by reading I/O port 043h. + * + * The Watch-Dog Timer Time-Out Period is set via jumpers. + * It can be 1, 2, 10, 20, 110 or 220 seconds. + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* Includes */ +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV + (WATCHDOG_MINOR) */ +#include <linux/watchdog.h> /* For the watchdog specific items */ +#include <linux/fs.h> /* For file operations */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/platform_device.h> /* For platform_driver framework */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <linux/io.h> /* For inb/outb/... */ + +/* Module information */ +#define DRV_NAME "acquirewdt" +#define WATCHDOG_NAME "Acquire WDT" +/* There is no way to see what the correct time-out period is */ +#define WATCHDOG_HEARTBEAT 0 + +/* internal variables */ +/* the watchdog platform device */ +static struct platform_device *acq_platform_device; +static unsigned long acq_is_open; +static char expect_close; + +/* module parameters */ +/* You must set this - there is no sane way to probe for this board. */ +static int wdt_stop = 0x43; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)"); + +/* You must set this - there is no sane way to probe for this board. */ +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Watchdog Operations + */ + +static void acq_keepalive(void) +{ + /* Write a watchdog value */ + inb_p(wdt_start); +} + +static void acq_stop(void) +{ + /* Turn the card off */ + inb_p(wdt_stop); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t acq_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t i; + /* note: just in case someone wrote the magic character + five months ago... */ + expect_close = 0; + /* scan to see whether or not we got the + magic character */ + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + /* Well, anyhow someone wrote to us, we should + return that favour */ + acq_keepalive(); + } + return count; +} + +static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int options, retval = -EINVAL; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = WATCHDOG_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + if (get_user(options, p)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) { + acq_stop(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + acq_keepalive(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + acq_keepalive(); + return 0; + + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_HEARTBEAT, p); + + default: + return -ENOTTY; + } +} + +static int acq_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &acq_is_open)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate */ + acq_keepalive(); + return nonseekable_open(inode, file); +} + +static int acq_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + acq_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + acq_keepalive(); + } + clear_bit(0, &acq_is_open); + expect_close = 0; + return 0; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations acq_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = acq_write, + .unlocked_ioctl = acq_ioctl, + .open = acq_open, + .release = acq_close, +}; + +static struct miscdevice acq_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &acq_fops, +}; + +/* + * Init & exit routines + */ + +static int __devinit acq_probe(struct platform_device *dev) +{ + int ret; + + if (wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", wdt_stop); + ret = -EIO; + goto out; + } + } + + if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", wdt_start); + ret = -EIO; + goto unreg_stop; + } + ret = misc_register(&acq_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_regions; + } + pr_info("initialized. (nowayout=%d)\n", nowayout); + + return 0; +unreg_regions: + release_region(wdt_start, 1); +unreg_stop: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); +out: + return ret; +} + +static int __devexit acq_remove(struct platform_device *dev) +{ + misc_deregister(&acq_miscdev); + release_region(wdt_start, 1); + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); + + return 0; +} + +static void acq_shutdown(struct platform_device *dev) +{ + /* Turn the WDT off if we have a soft shutdown */ + acq_stop(); +} + +static struct platform_driver acquirewdt_driver = { + .probe = acq_probe, + .remove = __devexit_p(acq_remove), + .shutdown = acq_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init acq_init(void) +{ + int err; + + pr_info("WDT driver for Acquire single board computer initialising\n"); + + err = platform_driver_register(&acquirewdt_driver); + if (err) + return err; + + 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; + } + return 0; + +unreg_platform_driver: + platform_driver_unregister(&acquirewdt_driver); + return err; +} + +static void __exit acq_exit(void) +{ + platform_device_unregister(acq_platform_device); + platform_driver_unregister(&acquirewdt_driver); + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(acq_init); +module_exit(acq_exit); + +MODULE_AUTHOR("David Woodhouse"); +MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/advantechwdt.c b/drivers/watchdog/advantechwdt.c new file mode 100644 index 00000000..64ae9e9f --- /dev/null +++ b/drivers/watchdog/advantechwdt.c @@ -0,0 +1,348 @@ +/* + * Advantech Single Board Computer WDT driver + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * + * 16-Oct-2002 Rob Radez <rob@osinvestor.com> + * Clean up ioctls, clean up init + exit, add expect close support, + * add wdt_start and wdt_stop as parameters. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +#define DRV_NAME "advantechwdt" +#define WATCHDOG_NAME "Advantech WDT" +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +/* the watchdog platform device */ +static struct platform_device *advwdt_platform_device; +static unsigned long advwdt_is_open; +static char adv_expect_close; + +/* + * You must set these - there is no sane way to probe for this board. + * + * To enable or restart, write the timeout value in seconds (1 to 63) + * to I/O port wdt_start. To disable, read I/O port wdt_stop. + * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but + * check your manual (at least the PCA-6159 seems to be different - + * the manual says wdt_stop is 0x43, not 0x443). + * (0x43 is also a write-only control register for the 8254 timer!) + */ + +static int wdt_stop = 0x443; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)"); + +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)"); + +static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1<= timeout <=63, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Watchdog Operations + */ + +static void advwdt_ping(void) +{ + /* Write a watchdog value */ + outb_p(timeout, wdt_start); +} + +static void advwdt_disable(void) +{ + inb_p(wdt_stop); +} + +static int advwdt_set_heartbeat(int t) +{ + if (t < 1 || t > 63) + return -EINVAL; + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t advwdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + adv_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + adv_expect_close = 42; + } + } + advwdt_ping(); + } + return count; +} + +static long advwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = WATCHDOG_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) { + advwdt_disable(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + advwdt_ping(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + advwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if (advwdt_set_heartbeat(new_timeout)) + return -EINVAL; + advwdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; + } + return 0; +} + +static int advwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &advwdt_is_open)) + return -EBUSY; + /* + * Activate + */ + + advwdt_ping(); + return nonseekable_open(inode, file); +} + +static int advwdt_close(struct inode *inode, struct file *file) +{ + if (adv_expect_close == 42) { + advwdt_disable(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + advwdt_ping(); + } + clear_bit(0, &advwdt_is_open); + adv_expect_close = 0; + return 0; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations advwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = advwdt_write, + .unlocked_ioctl = advwdt_ioctl, + .open = advwdt_open, + .release = advwdt_close, +}; + +static struct miscdevice advwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &advwdt_fops, +}; + +/* + * Init & exit routines + */ + +static int __devinit advwdt_probe(struct platform_device *dev) +{ + int ret; + + if (wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", + wdt_stop); + ret = -EIO; + goto out; + } + } + + if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", wdt_start); + ret = -EIO; + goto unreg_stop; + } + + /* Check that the heartbeat value is within it's range ; + * if not reset to the default */ + if (advwdt_set_heartbeat(timeout)) { + advwdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 1<=x<=63, using %d\n", timeout); + } + + ret = misc_register(&advwdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_regions; + } + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); +out: + return ret; +unreg_regions: + release_region(wdt_start, 1); +unreg_stop: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); + goto out; +} + +static int __devexit advwdt_remove(struct platform_device *dev) +{ + misc_deregister(&advwdt_miscdev); + release_region(wdt_start, 1); + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); + + return 0; +} + +static void advwdt_shutdown(struct platform_device *dev) +{ + /* Turn the WDT off if we have a soft shutdown */ + advwdt_disable(); +} + +static struct platform_driver advwdt_driver = { + .probe = advwdt_probe, + .remove = __devexit_p(advwdt_remove), + .shutdown = advwdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init advwdt_init(void) +{ + int err; + + 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)) { + err = PTR_ERR(advwdt_platform_device); + goto unreg_platform_driver; + } + + return 0; + +unreg_platform_driver: + platform_driver_unregister(&advwdt_driver); + return err; +} + +static void __exit advwdt_exit(void) +{ + platform_device_unregister(advwdt_platform_device); + platform_driver_unregister(&advwdt_driver); + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(advwdt_init); +module_exit(advwdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>"); +MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/alim1535_wdt.c b/drivers/watchdog/alim1535_wdt.c new file mode 100644 index 00000000..41b84936 --- /dev/null +++ b/drivers/watchdog/alim1535_wdt.c @@ -0,0 +1,455 @@ +/* + * Watchdog for the 7101 PMU version found in the ALi M1535 chipsets + * + * 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/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#define WATCHDOG_NAME "ALi_M1535" +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +/* internal variables */ +static unsigned long ali_is_open; +static char ali_expect_release; +static struct pci_dev *ali_pci; +static u32 ali_timeout_bits; /* stores the computed timeout */ +static DEFINE_SPINLOCK(ali_lock); /* Guards the hardware */ + +/* module parameters */ +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (0 < timeout < 18000, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * ali_start - start watchdog countdown + * + * Starts the timer running providing the timer has a counter + * configuration set. + */ + +static void ali_start(void) +{ + u32 val; + + spin_lock(&ali_lock); + + pci_read_config_dword(ali_pci, 0xCC, &val); + val &= ~0x3F; /* Mask count */ + val |= (1 << 25) | ali_timeout_bits; + pci_write_config_dword(ali_pci, 0xCC, val); + + spin_unlock(&ali_lock); +} + +/* + * ali_stop - stop the timer countdown + * + * Stop the ALi watchdog countdown + */ + +static void ali_stop(void) +{ + u32 val; + + spin_lock(&ali_lock); + + pci_read_config_dword(ali_pci, 0xCC, &val); + val &= ~0x3F; /* Mask count to zero (disabled) */ + val &= ~(1 << 25); /* and for safety mask the reset enable */ + pci_write_config_dword(ali_pci, 0xCC, val); + + spin_unlock(&ali_lock); +} + +/* + * ali_keepalive - send a keepalive to the watchdog + * + * Send a keepalive to the timer (actually we restart the timer). + */ + +static void ali_keepalive(void) +{ + ali_start(); +} + +/* + * ali_settimer - compute the timer reload value + * @t: time in seconds + * + * Computes the timeout values needed + */ + +static int ali_settimer(int t) +{ + if (t < 0) + return -EINVAL; + else if (t < 60) + ali_timeout_bits = t|(1 << 6); + else if (t < 3600) + ali_timeout_bits = (t / 60)|(1 << 7); + else if (t < 18000) + ali_timeout_bits = (t / 300)|(1 << 6)|(1 << 7); + else + return -EINVAL; + + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +/* + * ali_write - writes to ALi watchdog + * @file: file from VFS + * @data: user address of data + * @len: length of data + * @ppos: pointer to the file offset + * + * Handle a write to the ALi watchdog. Writing to the file pings + * the watchdog and resets it. Writing the magic 'V' sequence allows + * the next close to turn off the watchdog. + */ + +static ssize_t ali_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the + magic character five months ago... */ + ali_expect_release = 0; + + /* scan to see whether or not we got + the magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + ali_expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + ali_start(); + } + return len; +} + +/* + * ali_ioctl - handle watchdog ioctls + * @file: VFS file pointer + * @cmd: ioctl number + * @arg: arguments to the ioctl + * + * Handle the watchdog ioctls supported by the ALi driver. Really + * we want an extension to enable irq ack monitoring and the like + */ + +static long ali_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "ALi M1535 WatchDog Timer", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + ali_stop(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + ali_start(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + ali_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + if (get_user(new_timeout, p)) + return -EFAULT; + if (ali_settimer(new_timeout)) + return -EINVAL; + ali_keepalive(); + /* Fall */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOTTY; + } +} + +/* + * ali_open - handle open of ali watchdog + * @inode: inode from VFS + * @file: file from VFS + * + * Open the ALi watchdog device. Ensure only one person opens it + * at a time. Also start the watchdog running. + */ + +static int ali_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &ali_is_open)) + return -EBUSY; + + /* Activate */ + ali_start(); + return nonseekable_open(inode, file); +} + +/* + * ali_release - close an ALi watchdog + * @inode: inode from VFS + * @file: file from VFS + * + * Close the ALi watchdog device. Actual shutdown of the timer + * only occurs if the magic sequence has been set. + */ + +static int ali_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (ali_expect_release == 42) + ali_stop(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + ali_keepalive(); + } + clear_bit(0, &ali_is_open); + ali_expect_release = 0; + return 0; +} + +/* + * ali_notify_sys - System down notifier + * + * Notifier for system down + */ + + +static int ali_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + ali_stop(); /* Turn the WDT off */ + return NOTIFY_DONE; +} + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * register a pci_driver, because someone else might one day + * want to register another driver on the same PCI id. + */ + +static DEFINE_PCI_DEVICE_TABLE(ali_pci_tbl) __used = { + { PCI_VENDOR_ID_AL, 0x1533, PCI_ANY_ID, PCI_ANY_ID,}, + { PCI_VENDOR_ID_AL, 0x1535, PCI_ANY_ID, PCI_ANY_ID,}, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, ali_pci_tbl); + +/* + * ali_find_watchdog - find a 1535 and 7101 + * + * Scans the PCI hardware for a 1535 series bridge and matching 7101 + * watchdog device. This may be overtight but it is better to be safe + */ + +static int __init ali_find_watchdog(void) +{ + struct pci_dev *pdev; + u32 wdog; + + /* Check for a 1533/1535 series bridge */ + pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1535, NULL); + if (pdev == NULL) + pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1533, NULL); + if (pdev == NULL) + return -ENODEV; + pci_dev_put(pdev); + + /* Check for the a 7101 PMU */ + pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x7101, NULL); + if (pdev == NULL) + return -ENODEV; + + if (pci_enable_device(pdev)) { + pci_dev_put(pdev); + return -EIO; + } + + ali_pci = pdev; + + /* + * Initialize the timer bits + */ + pci_read_config_dword(pdev, 0xCC, &wdog); + + /* Timer bits */ + wdog &= ~0x3F; + /* Issued events */ + wdog &= ~((1 << 27)|(1 << 26)|(1 << 25)|(1 << 24)); + /* No monitor bits */ + wdog &= ~((1 << 16)|(1 << 13)|(1 << 12)|(1 << 11)|(1 << 10)|(1 << 9)); + + pci_write_config_dword(pdev, 0xCC, wdog); + + return 0; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations ali_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ali_write, + .unlocked_ioctl = ali_ioctl, + .open = ali_open, + .release = ali_release, +}; + +static struct miscdevice ali_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ali_fops, +}; + +static struct notifier_block ali_notifier = { + .notifier_call = ali_notify_sys, +}; + +/* + * watchdog_init - module initialiser + * + * Scan for a suitable watchdog and if so initialize it. Return an error + * if we cannot, the error causes the module to unload + */ + +static int __init watchdog_init(void) +{ + int ret; + + /* Check whether or not the hardware watchdog is there */ + if (ali_find_watchdog() != 0) + return -ENODEV; + + /* Check that the timeout value is within it's range; + if not reset to the default */ + if (timeout < 1 || timeout >= 18000) { + timeout = WATCHDOG_TIMEOUT; + pr_info("timeout value must be 0 < timeout < 18000, using %d\n", + timeout); + } + + /* Calculate the watchdog's timeout */ + ali_settimer(timeout); + + ret = register_reboot_notifier(&ali_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto out; + } + + ret = misc_register(&ali_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&ali_notifier); + goto out; +} + +/* + * watchdog_exit - module de-initialiser + * + * Called while unloading a successfully installed watchdog module. + */ + +static void __exit watchdog_exit(void) +{ + /* Stop the timer before we leave */ + ali_stop(); + + /* Deregister */ + misc_deregister(&ali_miscdev); + unregister_reboot_notifier(&ali_notifier); + pci_dev_put(ali_pci); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/alim7101_wdt.c b/drivers/watchdog/alim7101_wdt.c new file mode 100644 index 00000000..5eee5501 --- /dev/null +++ b/drivers/watchdog/alim7101_wdt.c @@ -0,0 +1,428 @@ +/* + * ALi M7101 PMU Computer Watchdog Timer driver + * + * Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net> + * and the Cobalt kernel WDT timer driver by Tim Hockin + * <thockin@cobaltnet.com> + * + * (c)2002 Steve Hill <steve@navaho.co.uk> + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + * + * Additions: + * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs + * found on very old cobalt hardware. + * -- Mike Waychison <michael.waychison@sun.com> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +#define WDT_ENABLE 0x9C +#define WDT_DISABLE 0x8C + +#define ALI_7101_WDT 0x92 +#define ALI_7101_GPIO 0x7D +#define ALI_7101_GPIO_O 0x7E +#define ALI_WDT_ARM 0x01 + +/* + * We're going to use a 1 second timeout. + * If we reset the watchdog every ~250ms we should be safe. */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static int use_gpio; /* Use the pic (for a1d revision alim7101) */ +module_param(use_gpio, int, 0); +MODULE_PARM_DESC(use_gpio, + "Use the gpio watchdog (required by old cobalt boards)."); + +static void wdt_timer_ping(unsigned long); +static DEFINE_TIMER(timer, wdt_timer_ping, 0, 1); +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static struct pci_dev *alim7101_pmu; + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Whack the dog + */ + +static void wdt_timer_ping(unsigned long data) +{ + /* If we got a heartbeat pulse within the WDT_US_INTERVAL + * we agree to ping the WDT + */ + char tmp; + + if (time_before(jiffies, next_heartbeat)) { + /* Ping the WDT (this is actually a disarm/arm sequence) */ + pci_read_config_byte(alim7101_pmu, 0x92, &tmp); + pci_write_config_byte(alim7101_pmu, + ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); + pci_write_config_byte(alim7101_pmu, + ALI_7101_WDT, (tmp | ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, tmp | 0x20); + pci_write_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, tmp & ~0x20); + } + } else { + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); + } + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); +} + +/* + * Utility routines + */ + +static void wdt_change(int writeval) +{ + char tmp; + + pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp); + if (writeval == WDT_ENABLE) { + pci_write_config_byte(alim7101_pmu, + ALI_7101_WDT, (tmp | ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, tmp & ~0x20); + } + + } else { + pci_write_config_byte(alim7101_pmu, + ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, + ALI_7101_GPIO_O, tmp | 0x20); + } + } +} + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* We must enable before we kick off the timer in case the timer + occurs as we ping it */ + + wdt_change(WDT_ENABLE); + + /* Start the timer */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + pr_info("Watchdog timer is now enabled\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer_sync(&timer); + wdt_change(WDT_DISABLE); + pr_info("Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* now scan */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode *inode, struct file *file) +{ + /* Just in case we're already talking to someone... */ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode *inode, struct file *file) +{ + if (wdt_expect_close == 42) + wdt_turnoff(); + else { + /* wim: shouldn't there be a: del_timer(&timer); */ + pr_crit("device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "ALiM7101", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; + /* arbitrary upper limit */ + if (new_timeout < 1 || new_timeout > 3600) + return -EINVAL; + timeout = new_timeout; + wdt_keepalive(); + /* 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, + .unlocked_ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_turnoff(); + + 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 + */ + wdt_change(WDT_ENABLE); + pr_info("Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second\n"); + } + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit alim7101_wdt_unload(void) +{ + wdt_turnoff(); + /* Deregister */ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + pci_dev_put(alim7101_pmu); +} + +static int __init alim7101_wdt_init(void) +{ + int rc = -EBUSY; + struct pci_dev *ali1543_south; + char tmp; + + pr_info("Steve Hill <steve@navaho.co.uk>\n"); + alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, + NULL); + if (!alim7101_pmu) { + pr_info("ALi M7101 PMU not present - WDT not set\n"); + return -EBUSY; + } + + /* Set the WDT in the PMU to 1 second */ + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02); + + ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, + NULL); + if (!ali1543_south) { + pr_info("ALi 1543 South-Bridge not present - WDT not set\n"); + goto err_out; + } + pci_read_config_byte(ali1543_south, 0x5e, &tmp); + pci_dev_put(ali1543_south); + if ((tmp & 0x1e) == 0x00) { + if (!use_gpio) { + pr_info("Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n"); + goto err_out; + } + nowayout = 1; + } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) { + pr_info("ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n"); + goto err_out; + } + + if (timeout < 1 || timeout > 3600) { + /* arbitrary upper limit */ + timeout = WATCHDOG_TIMEOUT; + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", + timeout); + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out; + } + + rc = 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; + } + + if (nowayout) + __module_get(THIS_MODULE); + + pr_info("WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out: + pci_dev_put(alim7101_pmu); + return rc; +} + +module_init(alim7101_wdt_init); +module_exit(alim7101_wdt_unload); + +static DEFINE_PCI_DEVICE_TABLE(alim7101_pci_tbl) __used = { + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) }, + { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl); + +MODULE_AUTHOR("Steve Hill"); +MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ar7_wdt.c b/drivers/watchdog/ar7_wdt.c new file mode 100644 index 00000000..639ae9a2 --- /dev/null +++ b/drivers/watchdog/ar7_wdt.c @@ -0,0 +1,354 @@ +/* + * drivers/watchdog/ar7_wdt.c + * + * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org> + * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org> + * + * Some code taken from: + * National Semiconductor SCx200 Watchdog support + * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + * + * 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., 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/fs.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/clk.h> + +#include <asm/addrspace.h> +#include <asm/mach-ar7/ar7.h> + +#define LONGNAME "TI AR7 Watchdog Timer" + +MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>"); +MODULE_DESCRIPTION(LONGNAME); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +static int margin = 60; +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +#define READ_REG(x) readl((void __iomem *)&(x)) +#define WRITE_REG(x, v) writel((v), (void __iomem *)&(x)) + +struct ar7_wdt { + u32 kick_lock; + u32 kick; + u32 change_lock; + u32 change; + u32 disable_lock; + u32 disable; + u32 prescale_lock; + u32 prescale; +}; + +static unsigned long wdt_is_open; +static unsigned expect_close; +static DEFINE_SPINLOCK(wdt_lock); + +/* XXX currently fixed, allows max margin ~68.72 secs */ +#define prescale_value 0xffff + +/* 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 struct clk *vbus_clk; + +static void ar7_wdt_kick(u32 value) +{ + WRITE_REG(ar7_wdt->kick_lock, 0x5555); + if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) { + WRITE_REG(ar7_wdt->kick_lock, 0xaaaa); + if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) { + WRITE_REG(ar7_wdt->kick, value); + return; + } + } + pr_err("failed to unlock WDT kick reg\n"); +} + +static void ar7_wdt_prescale(u32 value) +{ + WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a); + if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) { + WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5); + if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) { + WRITE_REG(ar7_wdt->prescale, value); + return; + } + } + pr_err("failed to unlock WDT prescale reg\n"); +} + +static void ar7_wdt_change(u32 value) +{ + WRITE_REG(ar7_wdt->change_lock, 0x6666); + if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) { + WRITE_REG(ar7_wdt->change_lock, 0xbbbb); + if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) { + WRITE_REG(ar7_wdt->change, value); + return; + } + } + pr_err("failed to unlock WDT change reg\n"); +} + +static void ar7_wdt_disable(u32 value) +{ + WRITE_REG(ar7_wdt->disable_lock, 0x7777); + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) { + WRITE_REG(ar7_wdt->disable_lock, 0xcccc); + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) { + WRITE_REG(ar7_wdt->disable_lock, 0xdddd); + if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) { + WRITE_REG(ar7_wdt->disable, value); + return; + } + } + } + pr_err("failed to unlock WDT disable reg\n"); +} + +static void ar7_wdt_update_margin(int new_margin) +{ + u32 change; + u32 vbus_rate; + + vbus_rate = clk_get_rate(vbus_clk); + change = new_margin * (vbus_rate / prescale_value); + if (change < 1) + change = 1; + if (change > 0xffff) + change = 0xffff; + ar7_wdt_change(change); + margin = change * prescale_value / vbus_rate; + pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n", + margin, prescale_value, change, vbus_rate); +} + +static void ar7_wdt_enable_wdt(void) +{ + pr_debug("enabling watchdog timer\n"); + ar7_wdt_disable(1); + ar7_wdt_kick(1); +} + +static void ar7_wdt_disable_wdt(void) +{ + pr_debug("disabling watchdog timer\n"); + ar7_wdt_disable(0); +} + +static int ar7_wdt_open(struct inode *inode, struct file *file) +{ + /* only allow one at a time */ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + ar7_wdt_enable_wdt(); + expect_close = 0; + + return nonseekable_open(inode, file); +} + +static int ar7_wdt_release(struct inode *inode, struct file *file) +{ + if (!expect_close) + pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); + else if (!nowayout) + ar7_wdt_disable_wdt(); + clear_bit(0, &wdt_is_open); + return 0; +} + +static ssize_t ar7_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + /* check for a magic close character */ + if (len) { + size_t i; + + spin_lock(&wdt_lock); + ar7_wdt_kick(1); + spin_unlock(&wdt_lock); + + expect_close = 0; + for (i = 0; i < len; ++i) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 1; + } + + } + return len; +} + +static long ar7_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + static const struct watchdog_info ident = { + .identity = LONGNAME, + .firmware_version = 1, + .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE), + }; + int new_margin; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(0, (int *)arg)) + return -EFAULT; + return 0; + case WDIOC_KEEPALIVE: + ar7_wdt_kick(1); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int *)arg)) + return -EFAULT; + if (new_margin < 1) + return -EINVAL; + + spin_lock(&wdt_lock); + ar7_wdt_update_margin(new_margin); + ar7_wdt_kick(1); + spin_unlock(&wdt_lock); + + case WDIOC_GETTIMEOUT: + if (put_user(margin, (int *)arg)) + return -EFAULT; + return 0; + default: + return -ENOTTY; + } +} + +static const struct file_operations ar7_wdt_fops = { + .owner = THIS_MODULE, + .write = ar7_wdt_write, + .unlocked_ioctl = ar7_wdt_ioctl, + .open = ar7_wdt_open, + .release = ar7_wdt_release, + .llseek = no_llseek, +}; + +static struct miscdevice ar7_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ar7_wdt_fops, +}; + +static int __devinit ar7_wdt_probe(struct platform_device *pdev) +{ + int rc; + + ar7_regs_wdt = + platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + if (!ar7_regs_wdt) { + pr_err("could not get registers resource\n"); + rc = -ENODEV; + goto out; + } + + if (!request_mem_region(ar7_regs_wdt->start, + resource_size(ar7_regs_wdt), LONGNAME)) { + pr_warn("watchdog I/O region busy\n"); + rc = -EBUSY; + goto out; + } + + ar7_wdt = ioremap(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); + if (!ar7_wdt) { + pr_err("could not ioremap registers\n"); + rc = -ENXIO; + goto out_mem_region; + } + + vbus_clk = clk_get(NULL, "vbus"); + if (IS_ERR(vbus_clk)) { + pr_err("could not get vbus clock\n"); + rc = PTR_ERR(vbus_clk); + goto out_mem_region; + } + + ar7_wdt_disable_wdt(); + ar7_wdt_prescale(prescale_value); + ar7_wdt_update_margin(margin); + + rc = misc_register(&ar7_wdt_miscdev); + if (rc) { + pr_err("unable to register misc device\n"); + goto out_alloc; + } + goto out; + +out_alloc: + iounmap(ar7_wdt); +out_mem_region: + release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); +out: + return rc; +} + +static int __devexit ar7_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&ar7_wdt_miscdev); + iounmap(ar7_wdt); + release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); + + return 0; +} + +static void ar7_wdt_shutdown(struct platform_device *pdev) +{ + if (!nowayout) + ar7_wdt_disable_wdt(); +} + +static struct platform_driver ar7_wdt_driver = { + .probe = ar7_wdt_probe, + .remove = __devexit_p(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 new file mode 100644 index 00000000..2896430c --- /dev/null +++ b/drivers/watchdog/at32ap700x_wdt.c @@ -0,0 +1,454 @@ +/* + * Watchdog driver for Atmel AT32AP700X devices + * + * Copyright (C) 2005-2006 Atmel Corporation + * + * 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. + * + * + * Errata: WDT Clear is blocked after WDT Reset + * + * A watchdog timer event will, after reset, block writes to the WDT_CLEAR + * register, preventing the program to clear the next Watchdog Timer Reset. + * + * If you still want to use the WDT after a WDT reset a small code can be + * insterted at the startup checking the AVR32_PM.rcause register for WDT reset + * and use a GPIO pin to reset the system. This method requires that one of the + * GPIO pins are available and connected externally to the RESET_N pin. After + * the GPIO pin has pulled down the reset line the GPIO will be reset and leave + * the pin tristated with pullup. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <linux/slab.h> + +#define TIMEOUT_MIN 1 +#define TIMEOUT_MAX 2 +#define TIMEOUT_DEFAULT TIMEOUT_MAX + +/* module parameters */ +static int timeout = TIMEOUT_DEFAULT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Timeout value. Limited to be 1 or 2 seconds. (default=" + __MODULE_STRING(TIMEOUT_DEFAULT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* Watchdog registers and write/read macro */ +#define WDT_CTRL 0x00 +#define WDT_CTRL_EN 0 +#define WDT_CTRL_PSEL 8 +#define WDT_CTRL_KEY 24 + +#define WDT_CLR 0x04 + +#define WDT_RCAUSE 0x10 +#define WDT_RCAUSE_POR 0 +#define WDT_RCAUSE_EXT 2 +#define WDT_RCAUSE_WDT 3 +#define WDT_RCAUSE_JTAG 4 +#define WDT_RCAUSE_SERP 5 + +#define WDT_BIT(name) (1 << WDT_##name) +#define WDT_BF(name, value) ((value) << WDT_##name) + +#define wdt_readl(dev, reg) \ + __raw_readl((dev)->regs + WDT_##reg) +#define wdt_writel(dev, reg, value) \ + __raw_writel((value), (dev)->regs + WDT_##reg) + +struct wdt_at32ap700x { + void __iomem *regs; + spinlock_t io_lock; + int timeout; + int boot_status; + unsigned long users; + struct miscdevice miscdev; +}; + +static struct wdt_at32ap700x *wdt; +static char expect_release; + +/* + * Disable the watchdog. + */ +static inline void at32_wdt_stop(void) +{ + unsigned long psel; + + spin_lock(&wdt->io_lock); + psel = wdt_readl(wdt, CTRL) & WDT_BF(CTRL_PSEL, 0x0f); + wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0x55)); + wdt_writel(wdt, CTRL, psel | WDT_BF(CTRL_KEY, 0xaa)); + spin_unlock(&wdt->io_lock); +} + +/* + * Enable and reset the watchdog. + */ +static inline void at32_wdt_start(void) +{ + /* 0xf is 2^16 divider = 2 sec, 0xe is 2^15 divider = 1 sec */ + unsigned long psel = (wdt->timeout > 1) ? 0xf : 0xe; + + spin_lock(&wdt->io_lock); + wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) + | WDT_BF(CTRL_PSEL, psel) + | WDT_BF(CTRL_KEY, 0x55)); + wdt_writel(wdt, CTRL, WDT_BIT(CTRL_EN) + | WDT_BF(CTRL_PSEL, psel) + | WDT_BF(CTRL_KEY, 0xaa)); + spin_unlock(&wdt->io_lock); +} + +/* + * Pat the watchdog timer. + */ +static inline void at32_wdt_pat(void) +{ + spin_lock(&wdt->io_lock); + wdt_writel(wdt, CLR, 0x42); + spin_unlock(&wdt->io_lock); +} + +/* + * Watchdog device is opened, and watchdog starts running. + */ +static int at32_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(1, &wdt->users)) + return -EBUSY; + + at32_wdt_start(); + return nonseekable_open(inode, file); +} + +/* + * Close the watchdog device. + */ +static int at32_wdt_close(struct inode *inode, struct file *file) +{ + if (expect_release == 42) { + at32_wdt_stop(); + } else { + dev_dbg(wdt->miscdev.parent, + "unexpected close, not stopping watchdog!\n"); + at32_wdt_pat(); + } + clear_bit(1, &wdt->users); + expect_release = 0; + return 0; +} + +/* + * Change the watchdog time interval. + */ +static int at32_wdt_settimeout(int time) +{ + /* + * All counting occurs at 1 / SLOW_CLOCK (32 kHz) and max prescaler is + * 2 ^ 16 allowing up to 2 seconds timeout. + */ + if ((time < TIMEOUT_MIN) || (time > TIMEOUT_MAX)) + return -EINVAL; + + /* + * Set new watchdog time. It will be used when at32_wdt_start() is + * called. + */ + wdt->timeout = time; + return 0; +} + +/* + * Get the watchdog status. + */ +static int at32_wdt_get_status(void) +{ + int rcause; + int status = 0; + + rcause = wdt_readl(wdt, RCAUSE); + + switch (rcause) { + case WDT_BIT(RCAUSE_EXT): + status = WDIOF_EXTERN1; + break; + case WDT_BIT(RCAUSE_WDT): + status = WDIOF_CARDRESET; + break; + case WDT_BIT(RCAUSE_POR): /* fall through */ + case WDT_BIT(RCAUSE_JTAG): /* fall through */ + case WDT_BIT(RCAUSE_SERP): /* fall through */ + default: + break; + } + + return status; +} + +static const struct watchdog_info at32_wdt_info = { + .identity = "at32ap700x watchdog", + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +/* + * Handle commands from user-space. + */ +static long at32_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = -ENOTTY; + int time; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(argp, &at32_wdt_info, + sizeof(at32_wdt_info)) ? -EFAULT : 0; + break; + case WDIOC_GETSTATUS: + ret = put_user(0, p); + break; + case WDIOC_GETBOOTSTATUS: + ret = put_user(wdt->boot_status, p); + break; + case WDIOC_SETOPTIONS: + ret = get_user(time, p); + if (ret) + break; + if (time & WDIOS_DISABLECARD) + at32_wdt_stop(); + if (time & WDIOS_ENABLECARD) + 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; +} + +static ssize_t at32_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; + + /* + * note: just in case someone wrote the magic + * character five months ago... + */ + expect_release = 0; + + /* + * scan to see whether or not we got the magic + * character + */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + /* someone wrote to us, we should pat the watchdog */ + at32_wdt_pat(); + } + return len; +} + +static const struct file_operations at32_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = at32_wdt_ioctl, + .open = at32_wdt_open, + .release = at32_wdt_close, + .write = at32_wdt_write, +}; + +static int __init at32_wdt_probe(struct platform_device *pdev) +{ + struct resource *regs; + int ret; + + if (wdt) { + dev_dbg(&pdev->dev, "only 1 wdt instance supported.\n"); + return -EBUSY; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!regs) { + dev_dbg(&pdev->dev, "missing mmio resource\n"); + return -ENXIO; + } + + wdt = kzalloc(sizeof(struct wdt_at32ap700x), GFP_KERNEL); + if (!wdt) { + dev_dbg(&pdev->dev, "no memory for wdt structure\n"); + return -ENOMEM; + } + + wdt->regs = ioremap(regs->start, resource_size(regs)); + if (!wdt->regs) { + ret = -ENOMEM; + dev_dbg(&pdev->dev, "could not map I/O memory\n"); + goto err_free; + } + + spin_lock_init(&wdt->io_lock); + wdt->boot_status = at32_wdt_get_status(); + + /* Work-around for watchdog silicon errata. */ + if (wdt->boot_status & WDIOF_CARDRESET) { + dev_info(&pdev->dev, "CPU must be reset with external " + "reset or POR due to silicon errata.\n"); + ret = -EIO; + goto err_iounmap; + } else { + wdt->users = 0; + } + + 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); + dev_dbg(&pdev->dev, + "default timeout invalid, set to %d sec.\n", + TIMEOUT_DEFAULT); + } + + ret = misc_register(&wdt->miscdev); + if (ret) { + dev_dbg(&pdev->dev, "failed to register wdt miscdev\n"); + goto err_register; + } + + dev_info(&pdev->dev, + "AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n", + wdt->regs, wdt->timeout, nowayout); + + return 0; + +err_register: + platform_set_drvdata(pdev, NULL); +err_iounmap: + iounmap(wdt->regs); +err_free: + kfree(wdt); + wdt = NULL; + return ret; +} + +static int __exit at32_wdt_remove(struct platform_device *pdev) +{ + if (wdt && platform_get_drvdata(pdev) == wdt) { + /* Stop the timer before we leave */ + if (!nowayout) + at32_wdt_stop(); + + misc_deregister(&wdt->miscdev); + iounmap(wdt->regs); + kfree(wdt); + wdt = NULL; + platform_set_drvdata(pdev, NULL); + } + return 0; +} + +static void at32_wdt_shutdown(struct platform_device *pdev) +{ + at32_wdt_stop(); +} + +#ifdef CONFIG_PM +static int at32_wdt_suspend(struct platform_device *pdev, pm_message_t message) +{ + at32_wdt_stop(); + return 0; +} + +static int at32_wdt_resume(struct platform_device *pdev) +{ + if (wdt->users) + at32_wdt_start(); + return 0; +} +#else +#define at32_wdt_suspend NULL +#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, + .resume = at32_wdt_resume, + .driver = { + .name = "at32_wdt", + .owner = THIS_MODULE, + }, + .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_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 new file mode 100644 index 00000000..7ef99a16 --- /dev/null +++ b/drivers/watchdog/at91rm9200_wdt.c @@ -0,0 +1,292 @@ +/* + * Watchdog driver for Atmel AT91RM9200 (Thunder) + * + * Copyright (C) 2003 SAN People (Pty) Ltd + * + * 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/init.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/uaccess.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 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) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif + + +static unsigned long at91wdt_busy; + +/* ......................................................................... */ + +/* + * Disable the watchdog. + */ +static inline void at91_wdt_stop(void) +{ + at91_st_write(AT91_ST_WDMR, AT91_ST_EXTEN); +} + +/* + * Enable and reset the watchdog. + */ +static inline void at91_wdt_start(void) +{ + 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 inline void at91_wdt_reload(void) +{ + at91_st_write(AT91_ST_CR, AT91_ST_WDRST); +} + +/* ......................................................................... */ + +/* + * Watchdog device is opened, and watchdog starts running. + */ +static int at91_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &at91wdt_busy)) + return -EBUSY; + + at91_wdt_start(); + return nonseekable_open(inode, file); +} + +/* + * Close the watchdog device. + * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also + * disabled. + */ +static int at91_wdt_close(struct inode *inode, struct file *file) +{ + /* Disable the watchdog when file is closed */ + if (!nowayout) + at91_wdt_stop(); + + clear_bit(0, &at91wdt_busy); + return 0; +} + +/* + * Change the watchdog time interval. + */ +static int at91_wdt_settimeout(int new_time) +{ + /* + * All counting occurs at SLOW_CLOCK / 128 = 256 Hz + * + * Since WDV is a 16-bit counter, the maximum period is + * 65536 / 256 = 256 seconds. + */ + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) + return -EINVAL; + + /* Set new watchdog time. It will be used when + at91_wdt_start() is called. */ + wdt_time = new_time; + return 0; +} + +static const struct watchdog_info at91_wdt_info = { + .identity = "at91 watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, +}; + +/* + * Handle commands from user-space. + */ +static long at91_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &at91_wdt_info, + sizeof(at91_wdt_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + if (get_user(new_value, p)) + return -EFAULT; + if (new_value & WDIOS_DISABLECARD) + at91_wdt_stop(); + if (new_value & WDIOS_ENABLECARD) + at91_wdt_start(); + return 0; + case WDIOC_KEEPALIVE: + at91_wdt_reload(); /* pat the watchdog */ + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + if (at91_wdt_settimeout(new_value)) + return -EINVAL; + /* Enable new time value */ + at91_wdt_start(); + /* Return current value */ + return put_user(wdt_time, p); + case WDIOC_GETTIMEOUT: + return put_user(wdt_time, p); + default: + return -ENOTTY; + } +} + +/* + * Pat the watchdog whenever device is written to. + */ +static ssize_t at91_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + at91_wdt_reload(); /* pat the watchdog */ + return len; +} + +/* ......................................................................... */ + +static const struct file_operations at91wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = at91_wdt_ioctl, + .open = at91_wdt_open, + .release = at91_wdt_close, + .write = at91_wdt_write, +}; + +static struct miscdevice at91wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &at91wdt_fops, +}; + +static int __devinit at91wdt_probe(struct platform_device *pdev) +{ + int res; + + if (at91wdt_miscdev.parent) + return -EBUSY; + at91wdt_miscdev.parent = &pdev->dev; + + res = misc_register(&at91wdt_miscdev); + if (res) + return res; + + pr_info("AT91 Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); + return 0; +} + +static int __devexit at91wdt_remove(struct platform_device *pdev) +{ + int res; + + res = misc_deregister(&at91wdt_miscdev); + if (!res) + at91wdt_miscdev.parent = NULL; + + return res; +} + +static void at91wdt_shutdown(struct platform_device *pdev) +{ + at91_wdt_stop(); +} + +#ifdef CONFIG_PM + +static int at91wdt_suspend(struct platform_device *pdev, pm_message_t message) +{ + at91_wdt_stop(); + return 0; +} + +static int at91wdt_resume(struct platform_device *pdev) +{ + if (at91wdt_busy) + at91_wdt_start(); + return 0; +} + +#else +#define at91wdt_suspend NULL +#define at91wdt_resume NULL +#endif + +static struct platform_driver at91wdt_driver = { + .probe = at91wdt_probe, + .remove = __devexit_p(at91wdt_remove), + .shutdown = at91wdt_shutdown, + .suspend = at91wdt_suspend, + .resume = at91wdt_resume, + .driver = { + .name = "at91_wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init at91_wdt_init(void) +{ + /* Check that the heartbeat value is within range; + if not reset to the default */ + if (at91_wdt_settimeout(wdt_time)) { + at91_wdt_settimeout(WDT_DEFAULT_TIME); + pr_info("wdt_time value must be 1 <= wdt_time <= 256, using %d\n", + wdt_time); + } + + return platform_driver_register(&at91wdt_driver); +} + +static void __exit at91_wdt_exit(void) +{ + platform_driver_unregister(&at91wdt_driver); +} + +module_init(at91_wdt_init); +module_exit(at91_wdt_exit); + +MODULE_AUTHOR("Andrew Victor"); +MODULE_DESCRIPTION("Watchdog driver for Atmel AT91RM9200"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_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 00000000..05e1be85 --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c @@ -0,0 +1,329 @@ +/* + * 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/fs.h> +#include <linux/init.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/jiffies.h> +#include <linux/timer.h> +#include <linux/bitops.h> +#include <linux/uaccess.h> + +#include "at91sam9_wdt.h" + +#define DRV_NAME "AT91SAM9 Watchdog" + +#define wdt_read(field) \ + __raw_readl(at91wdt_private.base + field) +#define wdt_write(field, val) \ + __raw_writel((val), at91wdt_private.base + field) + +/* AT91SAM9 watchdog runs a 12bit counter @ 256Hz, + * use this to convert a watchdog + * value from/to milliseconds. + */ +#define ms_to_ticks(t) (((t << 8) / 1000) - 1) +#define ticks_to_ms(t) (((t + 1) * 1000) >> 8) + +/* 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 void at91_ping(unsigned long data); + +static struct { + void __iomem *base; + unsigned long next_heartbeat; /* the next_heartbeat for the timer */ + unsigned long open; + char expect_close; + struct timer_list timer; /* The timer that pings the watchdog */ +} at91wdt_private; + +/* ......................................................................... */ + + +/* + * Reload the watchdog timer. (ie, pat the watchdog) + */ +static inline void at91_wdt_reset(void) +{ + wdt_write(AT91_WDT_CR, AT91_WDT_KEY | AT91_WDT_WDRSTT); +} + +/* + * Timer tick + */ +static void at91_ping(unsigned long data) +{ + if (time_before(jiffies, at91wdt_private.next_heartbeat) || + (!nowayout && !at91wdt_private.open)) { + at91_wdt_reset(); + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + } else + pr_crit("I will reset your machine !\n"); +} + +/* + * Watchdog device is opened, and watchdog starts running. + */ +static int at91_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &at91wdt_private.open)) + return -EBUSY; + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + + return nonseekable_open(inode, file); +} + +/* + * Close the watchdog device. + */ +static int at91_wdt_close(struct inode *inode, struct file *file) +{ + clear_bit(0, &at91wdt_private.open); + + /* stop internal ping */ + if (!at91wdt_private.expect_close) + del_timer(&at91wdt_private.timer); + + at91wdt_private.expect_close = 0; + return 0; +} + +/* + * Set the watchdog time interval in 1/256Hz (write-once) + * Counter is 12 bit. + */ +static int at91_wdt_settimeout(unsigned int timeout) +{ + unsigned int reg; + unsigned int mr; + + /* Check if disabled */ + mr = wdt_read(AT91_WDT_MR); + if (mr & AT91_WDT_WDDIS) { + pr_err("sorry, watchdog is disabled\n"); + return -EIO; + } + + /* + * All counting occurs at SLOW_CLOCK / 128 = 256 Hz + * + * Since WDV is a 12-bit counter, the maximum period is + * 4096 / 256 = 16 seconds. + */ + reg = AT91_WDT_WDRSTEN /* causes watchdog reset */ + /* | AT91_WDT_WDRPROC causes processor reset only */ + | AT91_WDT_WDDBGHLT /* disabled in debug mode */ + | AT91_WDT_WDD /* restart at any time */ + | (timeout & AT91_WDT_WDV); /* timer value */ + wdt_write(AT91_WDT_MR, reg); + + return 0; +} + +static const struct watchdog_info at91_wdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +/* + * Handle commands from user-space. + */ +static long at91_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &at91_wdt_info, + sizeof(at91_wdt_info)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + heartbeat = new_value; + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + + return put_user(new_value, p); /* return current value */ + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + } + 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) +{ + if (!len) + return 0; + + /* Scan for magic character */ + if (!nowayout) { + size_t i; + + at91wdt_private.expect_close = 0; + + for (i = 0; i < len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') { + at91wdt_private.expect_close = 42; + break; + } + } + } + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + + return len; +} + +/* ......................................................................... */ + +static const struct file_operations at91wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = at91_wdt_ioctl, + .open = at91_wdt_open, + .release = at91_wdt_close, + .write = at91_wdt_write, +}; + +static struct miscdevice at91wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &at91wdt_fops, +}; + +static int __init at91wdt_probe(struct platform_device *pdev) +{ + struct resource *r; + int res; + + if (at91wdt_miscdev.parent) + return -EBUSY; + at91wdt_miscdev.parent = &pdev->dev; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -ENODEV; + at91wdt_private.base = ioremap(r->start, resource_size(r)); + if (!at91wdt_private.base) { + dev_err(&pdev->dev, "failed to map registers, aborting.\n"); + return -ENOMEM; + } + + /* Set watchdog */ + res = at91_wdt_settimeout(ms_to_ticks(WDT_HW_TIMEOUT * 1000)); + if (res) + return res; + + res = misc_register(&at91wdt_miscdev); + if (res) + return res; + + at91wdt_private.next_heartbeat = jiffies + heartbeat * HZ; + setup_timer(&at91wdt_private.timer, at91_ping, 0); + mod_timer(&at91wdt_private.timer, jiffies + WDT_TIMEOUT); + + pr_info("enabled (heartbeat=%d sec, nowayout=%d)\n", + heartbeat, nowayout); + + return 0; +} + +static int __exit at91wdt_remove(struct platform_device *pdev) +{ + int res; + + res = misc_deregister(&at91wdt_miscdev); + if (!res) + at91wdt_miscdev.parent = NULL; + + return res; +} + +static struct platform_driver at91wdt_driver = { + .remove = __exit_p(at91wdt_remove), + .driver = { + .name = "at91_wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init at91sam_wdt_init(void) +{ + return platform_driver_probe(&at91wdt_driver, at91wdt_probe); +} + +static void __exit at91sam_wdt_exit(void) +{ + platform_driver_unregister(&at91wdt_driver); +} + +module_init(at91sam_wdt_init); +module_exit(at91sam_wdt_exit); + +MODULE_AUTHOR("Renaud CERRATO <r.cerrato@til-technologies.fr>"); +MODULE_DESCRIPTION("Watchdog driver for Atmel AT91SAM9x processors"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/at91sam9_wdt.h b/drivers/watchdog/at91sam9_wdt.h new file mode 100644 index 00000000..c6fbb2e6 --- /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 00000000..1f9371f4 --- /dev/null +++ b/drivers/watchdog/ath79_wdt.c @@ -0,0 +1,312 @@ +/* + * 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/errno.h> +#include <linux/fs.h> +#include <linux/init.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 <asm/mach-ath79/ath79.h> +#include <asm/mach-ath79/ar71xx_regs.h> + +#define DRIVER_NAME "ath79-wdt" + +#define WDT_TIMEOUT 15 /* seconds */ + +#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 inline void ath79_wdt_keepalive(void) +{ + ath79_reset_wr(AR71XX_RESET_REG_WDOG, wdt_freq * timeout); + /* flush write */ + ath79_reset_rr(AR71XX_RESET_REG_WDOG); +} + +static inline void ath79_wdt_enable(void) +{ + ath79_wdt_keepalive(); + ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_FCR); + /* flush write */ + ath79_reset_rr(AR71XX_RESET_REG_WDOG_CTRL); +} + +static inline void ath79_wdt_disable(void) +{ + ath79_reset_wr(AR71XX_RESET_REG_WDOG_CTRL, WDOG_CTRL_ACTION_NONE); + /* flush write */ + ath79_reset_rr(AR71XX_RESET_REG_WDOG_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 __devinit ath79_wdt_probe(struct platform_device *pdev) +{ + u32 ctrl; + int err; + + wdt_clk = clk_get(&pdev->dev, "wdt"); + if (IS_ERR(wdt_clk)) + return PTR_ERR(wdt_clk); + + err = clk_enable(wdt_clk); + if (err) + goto err_clk_put; + + 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_reset_rr(AR71XX_RESET_REG_WDOG_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(wdt_clk); +err_clk_put: + clk_put(wdt_clk); + return err; +} + +static int __devexit ath79_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&ath79_wdt_miscdev); + clk_disable(wdt_clk); + clk_put(wdt_clk); + return 0; +} + +static void ath97_wdt_shutdown(struct platform_device *pdev) +{ + ath79_wdt_disable(); +} + +static struct platform_driver ath79_wdt_driver = { + .remove = __devexit_p(ath79_wdt_remove), + .shutdown = ath97_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ath79_wdt_init(void) +{ + return platform_driver_probe(&ath79_wdt_driver, ath79_wdt_probe); +} +module_init(ath79_wdt_init); + +static void __exit ath79_wdt_exit(void) +{ + platform_driver_unregister(&ath79_wdt_driver); +} +module_exit(ath79_wdt_exit); + +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); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c new file mode 100644 index 00000000..bc0e91e7 --- /dev/null +++ b/drivers/watchdog/bcm47xx_wdt.c @@ -0,0 +1,309 @@ +/* + * Watchdog driver for Broadcom BCM47XX + * + * Copyright (C) 2008 Aleksandar Radovanovic <biblbroks@sezampro.rs> + * Copyright (C) 2009 Matthieu CASTET <castet.matthieu@free.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. + */ + +#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/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/reboot.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/ssb/ssb_embedded.h> +#include <asm/mach-bcm47xx/bcm47xx.h> + +#define DRV_NAME "bcm47xx_wdt" + +#define WDT_DEFAULT_TIME 30 /* seconds */ +#define WDT_MAX_TIME 255 /* seconds */ + +static int wdt_time = WDT_DEFAULT_TIME; +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) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif + +static unsigned long bcm47xx_wdt_busy; +static char expect_release; +static struct timer_list wdt_timer; +static atomic_t ticks; + +static inline void bcm47xx_wdt_hw_start(void) +{ + /* this is 2,5s on 100Mhz clock and 2s on 133 Mhz */ + switch (bcm47xx_bus_type) { +#ifdef CONFIG_BCM47XX_SSB + case BCM47XX_BUS_TYPE_SSB: + ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0xfffffff); + break; +#endif +#ifdef CONFIG_BCM47XX_BCMA + case BCM47XX_BUS_TYPE_BCMA: + bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, + 0xfffffff); + break; +#endif + } +} + +static inline int bcm47xx_wdt_hw_stop(void) +{ + switch (bcm47xx_bus_type) { +#ifdef CONFIG_BCM47XX_SSB + case BCM47XX_BUS_TYPE_SSB: + return ssb_watchdog_timer_set(&bcm47xx_bus.ssb, 0); +#endif +#ifdef CONFIG_BCM47XX_BCMA + case BCM47XX_BUS_TYPE_BCMA: + bcma_chipco_watchdog_timer_set(&bcm47xx_bus.bcma.bus.drv_cc, 0); + return 0; +#endif + } + return -EINVAL; +} + +static void bcm47xx_timer_tick(unsigned long unused) +{ + if (!atomic_dec_and_test(&ticks)) { + bcm47xx_wdt_hw_start(); + mod_timer(&wdt_timer, jiffies + HZ); + } else { + pr_crit("Watchdog will fire soon!!!\n"); + } +} + +static inline void bcm47xx_wdt_pet(void) +{ + atomic_set(&ticks, wdt_time); +} + +static void bcm47xx_wdt_start(void) +{ + bcm47xx_wdt_pet(); + bcm47xx_timer_tick(0); +} + +static void bcm47xx_wdt_pause(void) +{ + del_timer_sync(&wdt_timer); + bcm47xx_wdt_hw_stop(); +} + +static void bcm47xx_wdt_stop(void) +{ + bcm47xx_wdt_pause(); +} + +static int bcm47xx_wdt_settimeout(int new_time) +{ + if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) + return -EINVAL; + + wdt_time = new_time; + return 0; +} + +static int bcm47xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &bcm47xx_wdt_busy)) + return -EBUSY; + + bcm47xx_wdt_start(); + return nonseekable_open(inode, file); +} + +static int bcm47xx_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_release == 42) { + bcm47xx_wdt_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + bcm47xx_wdt_start(); + } + + clear_bit(0, &bcm47xx_wdt_busy); + expect_release = 0; + return 0; +} + +static ssize_t bcm47xx_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + expect_release = 0; + + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + bcm47xx_wdt_pet(); + } + return len; +} + +static const struct watchdog_info bcm47xx_wdt_info = { + .identity = DRV_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +static long bcm47xx_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, &bcm47xx_wdt_info, + sizeof(bcm47xx_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) { + bcm47xx_wdt_stop(); + retval = 0; + } + + if (new_value & WDIOS_ENABLECARD) { + bcm47xx_wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + bcm47xx_wdt_pet(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + + if (bcm47xx_wdt_settimeout(new_value)) + return -EINVAL; + + bcm47xx_wdt_pet(); + + case WDIOC_GETTIMEOUT: + return put_user(wdt_time, p); + + default: + return -ENOTTY; + } +} + +static int bcm47xx_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + bcm47xx_wdt_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations bcm47xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = bcm47xx_wdt_ioctl, + .open = bcm47xx_wdt_open, + .release = bcm47xx_wdt_release, + .write = bcm47xx_wdt_write, +}; + +static struct miscdevice bcm47xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bcm47xx_wdt_fops, +}; + +static struct notifier_block bcm47xx_wdt_notifier = { + .notifier_call = bcm47xx_wdt_notify_sys, +}; + +static int __init bcm47xx_wdt_init(void) +{ + int ret; + + if (bcm47xx_wdt_hw_stop() < 0) + return -ENODEV; + + setup_timer(&wdt_timer, bcm47xx_timer_tick, 0L); + + if (bcm47xx_wdt_settimeout(wdt_time)) { + bcm47xx_wdt_settimeout(WDT_DEFAULT_TIME); + pr_info("wdt_time value must be 0 < wdt_time < %d, using %d\n", + (WDT_MAX_TIME + 1), wdt_time); + } + + ret = register_reboot_notifier(&bcm47xx_wdt_notifier); + if (ret) + return ret; + + ret = misc_register(&bcm47xx_wdt_miscdev); + if (ret) { + unregister_reboot_notifier(&bcm47xx_wdt_notifier); + return ret; + } + + pr_info("BCM47xx Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); + return 0; +} + +static void __exit bcm47xx_wdt_exit(void) +{ + if (!nowayout) + bcm47xx_wdt_stop(); + + misc_deregister(&bcm47xx_wdt_miscdev); + + unregister_reboot_notifier(&bcm47xx_wdt_notifier); +} + +module_init(bcm47xx_wdt_init); +module_exit(bcm47xx_wdt_exit); + +MODULE_AUTHOR("Aleksandar Radovanovic"); +MODULE_DESCRIPTION("Watchdog driver for Broadcom BCM47xx"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/bcm63xx_wdt.c b/drivers/watchdog/bcm63xx_wdt.c new file mode 100644 index 00000000..8379dc32 --- /dev/null +++ b/drivers/watchdog/bcm63xx_wdt.c @@ -0,0 +1,322 @@ +/* + * 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/init.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; + int default_ticks; + 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 __devinit 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 = ioremap_nocache(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"); + goto unmap; + } + + 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); +unmap: + iounmap(bcm63xx_wdt_device.regs); + return ret; +} + +static int __devexit bcm63xx_wdt_remove(struct platform_device *pdev) +{ + if (!nowayout) + bcm63xx_wdt_pause(); + + misc_deregister(&bcm63xx_wdt_miscdev); + bcm63xx_timer_unregister(TIMER_WDT_ID); + iounmap(bcm63xx_wdt_device.regs); + return 0; +} + +static void bcm63xx_wdt_shutdown(struct platform_device *pdev) +{ + bcm63xx_wdt_pause(); +} + +static struct platform_driver bcm63xx_wdt = { + .probe = bcm63xx_wdt_probe, + .remove = __devexit_p(bcm63xx_wdt_remove), + .shutdown = bcm63xx_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "bcm63xx-wdt", + } +}; + +module_platform_driver(bcm63xx_wdt); + +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_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:bcm63xx-wdt"); diff --git a/drivers/watchdog/bfin_wdt.c b/drivers/watchdog/bfin_wdt.c new file mode 100644 index 00000000..38bc383e --- /dev/null +++ b/drivers/watchdog/bfin_wdt.c @@ -0,0 +1,478 @@ +/* + * Blackfin On-Chip Watchdog Driver + * + * Originally based on softdog.c + * Copyright 2006-2010 Analog Devices Inc. + * Copyright 2006-2007 Michele d'Amico + * 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> +#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/interrupt.h> +#include <linux/uaccess.h> +#include <asm/blackfin.h> +#include <asm/bfin_watchdog.h> + +#define stamp(fmt, args...) \ + pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) +#define stampit() stamp("here i am") + +#define WATCHDOG_NAME "bfin-wdt" + +/* The BF561 has two watchdogs (one per core), but since Linux + * only runs on core A, we'll just work with that one. + */ +#ifdef BF561_FAMILY +# define bfin_read_WDOG_CTL() bfin_read_WDOGA_CTL() +# define bfin_read_WDOG_CNT() bfin_read_WDOGA_CNT() +# define bfin_read_WDOG_STAT() bfin_read_WDOGA_STAT() +# define bfin_write_WDOG_CTL(x) bfin_write_WDOGA_CTL(x) +# define bfin_write_WDOG_CNT(x) bfin_write_WDOGA_CNT(x) +# define bfin_write_WDOG_STAT(x) bfin_write_WDOGA_STAT(x) +#endif + +/* some defaults */ +#define WATCHDOG_TIMEOUT 20 + +static unsigned int timeout = WATCHDOG_TIMEOUT; +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); + +/** + * bfin_wdt_keepalive - Keep the Userspace Watchdog Alive + * + * The Userspace watchdog got a KeepAlive: schedule the next timeout. + */ +static int bfin_wdt_keepalive(void) +{ + stampit(); + bfin_write_WDOG_STAT(0); + return 0; +} + +/** + * bfin_wdt_stop - Stop the Watchdog + * + * Stops the on-chip watchdog. + */ +static int bfin_wdt_stop(void) +{ + stampit(); + bfin_write_WDOG_CTL(WDEN_DISABLE); + return 0; +} + +/** + * bfin_wdt_start - Start the Watchdog + * + * Starts the on-chip watchdog. Automatically loads WDOG_CNT + * into WDOG_STAT for us. + */ +static int bfin_wdt_start(void) +{ + stampit(); + bfin_write_WDOG_CTL(WDEN_ENABLE | ICTL_RESET); + return 0; +} + +/** + * bfin_wdt_running - Check Watchdog status + * + * See if the watchdog is running. + */ +static int bfin_wdt_running(void) +{ + stampit(); + return ((bfin_read_WDOG_CTL() & WDEN_MASK) != WDEN_DISABLE); +} + +/** + * bfin_wdt_set_timeout - Set the Userspace Watchdog timeout + * @t: new timeout value (in seconds) + * + * Translate the specified timeout in seconds into System Clock + * terms which is what the on-chip Watchdog requires. + */ +static int bfin_wdt_set_timeout(unsigned long t) +{ + u32 cnt, max_t, sclk; + unsigned long flags; + + sclk = get_sclk(); + max_t = -1 / sclk; + cnt = t * sclk; + stamp("maxtimeout=%us newtimeout=%lus (cnt=%#x)", max_t, t, cnt); + + if (t > max_t) { + pr_warn("timeout value is too large\n"); + return -EINVAL; + } + + spin_lock_irqsave(&bfin_wdt_spinlock, flags); + { + int run = bfin_wdt_running(); + bfin_wdt_stop(); + bfin_write_WDOG_CNT(cnt); + if (run) + bfin_wdt_start(); + } + spin_unlock_irqrestore(&bfin_wdt_spinlock, flags); + + timeout = t; + + return 0; +} + +/** + * bfin_wdt_open - Open the Device + * @inode: inode of device + * @file: file handle of device + * + * Watchdog device is opened and started. + */ +static int bfin_wdt_open(struct inode *inode, struct file *file) +{ + stampit(); + + if (test_and_set_bit(0, &open_check)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + bfin_wdt_keepalive(); + bfin_wdt_start(); + + return nonseekable_open(inode, file); +} + +/** + * bfin_wdt_close - Close the Device + * @inode: inode of device + * @file: file handle of device + * + * Watchdog device is closed and stopped. + */ +static int bfin_wdt_release(struct inode *inode, struct file *file) +{ + stampit(); + + if (expect_close == 42) + bfin_wdt_stop(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + bfin_wdt_keepalive(); + } + expect_close = 0; + clear_bit(0, &open_check); + return 0; +} + +/** + * bfin_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 bfin_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + stampit(); + + 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; + } + } + bfin_wdt_keepalive(); + } + + return len; +} + +/** + * bfin_wdt_ioctl - Query 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 long bfin_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + + stampit(); + + switch (cmd) { + 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_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; + } + 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; + } +} + +#ifdef CONFIG_PM +static int state_before_suspend; + +/** + * bfin_wdt_suspend - suspend the watchdog + * @pdev: device being suspended + * @state: requested suspend state + * + * Remember if the watchdog was running and stop it. + * TODO: is this even right? Doesn't seem to be any + * standard in the watchdog world ... + */ +static int bfin_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + stampit(); + + state_before_suspend = bfin_wdt_running(); + bfin_wdt_stop(); + + return 0; +} + +/** + * bfin_wdt_resume - resume the watchdog + * @pdev: device being resumed + * + * If the watchdog was running, turn it back on. + */ +static int bfin_wdt_resume(struct platform_device *pdev) +{ + stampit(); + + if (state_before_suspend) { + bfin_wdt_set_timeout(timeout); + bfin_wdt_start(); + } + + return 0; +} +#else +# define bfin_wdt_suspend NULL +# define bfin_wdt_resume NULL +#endif + +static const struct file_operations bfin_wdt_fops = { + .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 = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &bfin_wdt_fops, +}; + +static const struct watchdog_info bfin_wdt_info = { + .identity = "Blackfin Watchdog", + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, +}; + +/** + * bfin_wdt_probe - Initialize module + * + * Registers the misc device. Actual device + * initialization is handled by bfin_wdt_open(). + */ +static int __devinit 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 __devexit 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 = __devexit_p(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 + * + * 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) +{ + int ret; + + stampit(); + + /* Check that the timeout value is within range */ + if (bfin_wdt_set_timeout(timeout)) + return -EINVAL; + + /* Since this is an on-chip device and needs no board-specific + * resources, we'll handle all the platform device stuff here. + */ + ret = platform_driver_register(&bfin_wdt_driver); + if (ret) { + pr_err("unable to register driver\n"); + 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); + } + + return 0; +} + +/** + * bfin_wdt_exit - Deinitialize module + * + * Back out the platform device & driver steps. Real work is in the + * platform remove function. + */ +static void __exit bfin_wdt_exit(void) +{ + platform_device_unregister(bfin_wdt_device); + platform_driver_unregister(&bfin_wdt_driver); +} + +module_init(bfin_wdt_init); +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, 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 new file mode 100644 index 00000000..ce0ab441 --- /dev/null +++ b/drivers/watchdog/booke_wdt.c @@ -0,0 +1,304 @@ +/* + * Watchdog timer for PowerPC Book-E systems + * + * Author: Matthew McClintock + * Maintainer: Kumar Gala <galak@kernel.crashing.org> + * + * Copyright 2005, 2008, 2010-2011 Freescale Semiconductor Inc. + * + * 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/fs.h> +#include <linux/smp.h> +#include <linux/miscdevice.h> +#include <linux/notifier.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> + +#include <asm/reg_booke.h> +#include <asm/time.h> +#include <asm/div64.h> + +/* If the kernel parameter wdt=1, the watchdog will be enabled at boot. + * Also, the wdt_period sets the watchdog timer period timeout. + * For E500 cpus the wdt_period sets which bit changing from 0->1 will + * trigger a watchog timeout. This watchdog timeout will occur 3 times, the + * first time nothing will happen, the second time a watchdog exception will + * occur, and the final time the board will reset. + */ + +u32 booke_wdt_enabled; +u32 booke_wdt_period = CONFIG_BOOKE_WDT_DEFAULT_TIMEOUT; + +#ifdef CONFIG_FSL_BOOKE +#define WDTP(x) ((((x)&0x3)<<30)|(((x)&0x3c)<<15)) +#define WDTP_MASK (WDTP(0x3f)) +#else +#define WDTP(x) (TCR_WP(x)) +#define WDTP_MASK (TCR_WP_MASK) +#endif + +static DEFINE_SPINLOCK(booke_wdt_lock); + +/* For the specified period, determine the number of seconds + * corresponding to the reset time. There will be a watchdog + * exception at approximately 3/5 of this time. + * + * The formula to calculate this is given by: + * 2.5 * (2^(63-period+1)) / timebase_freq + * + * In order to simplify things, we assume that period is + * at least 1. This will still result in a very long timeout. + */ +static unsigned long long period_to_sec(unsigned int period) +{ + unsigned long long tmp = 1ULL << (64 - period); + unsigned long tmp2 = ppc_tb_freq; + + /* tmp may be a very large number and we don't want to overflow, + * so divide the timebase freq instead of multiplying tmp + */ + tmp2 = tmp2 / 5 * 2; + + do_div(tmp, tmp2); + return tmp; +} + +/* + * This procedure will find the highest period which will give a timeout + * greater than the one required. e.g. for a bus speed of 66666666 and + * and a parameter of 2 secs, then this procedure will return a value of 38. + */ +static unsigned int sec_to_period(unsigned int secs) +{ + unsigned int period; + for (period = 63; period > 0; period--) { + if (period_to_sec(period) >= secs) + return period; + } + return 0; +} + +static void __booke_wdt_set(void *data) +{ + u32 val; + + val = mfspr(SPRN_TCR); + val &= ~WDTP_MASK; + val |= WDTP(booke_wdt_period); + + mtspr(SPRN_TCR, val); +} + +static void booke_wdt_set(void) +{ + on_each_cpu(__booke_wdt_set, NULL, 0); +} + +static void __booke_wdt_ping(void *data) +{ + mtspr(SPRN_TSR, TSR_ENW|TSR_WIS); +} + +static void booke_wdt_ping(void) +{ + on_each_cpu(__booke_wdt_ping, NULL, 0); +} + +static void __booke_wdt_enable(void *data) +{ + u32 val; + + /* clear status before enabling watchdog */ + __booke_wdt_ping(NULL); + val = mfspr(SPRN_TCR); + val &= ~WDTP_MASK; + val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(booke_wdt_period)); + + mtspr(SPRN_TCR, val); +} + +/** + * booke_wdt_disable - disable the watchdog on the given CPU + * + * This function is called on each CPU. It disables the watchdog on that CPU. + * + * TCR[WRC] cannot be changed once it has been set to non-zero, but we can + * effectively disable the watchdog by setting its period to the maximum value. + */ +static void __booke_wdt_disable(void *data) +{ + u32 val; + + val = mfspr(SPRN_TCR); + val &= ~(TCR_WIE | WDTP_MASK); + mtspr(SPRN_TCR, val); + + /* clear status to make sure nothing is pending */ + __booke_wdt_ping(NULL); + +} + +static ssize_t booke_wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + booke_wdt_ping(); + return count; +} + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "PowerPC Book-E Watchdog", +}; + +static long booke_wdt_ioctl(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((void *)arg, &ident, sizeof(ident))) + return -EFAULT; + case WDIOC_GETSTATUS: + return put_user(0, p); + case WDIOC_GETBOOTSTATUS: + /* XXX: something is clearing TSR */ + tmp = mfspr(SPRN_TSR) & TSR_WRS(3); + /* returns CARDRESET if last reset was caused by the WDT */ + return (tmp ? WDIOF_CARDRESET : 0); + case WDIOC_SETOPTIONS: + if (get_user(tmp, p)) + return -EINVAL; + if (tmp == WDIOS_ENABLECARD) { + booke_wdt_ping(); + break; + } else + return -EINVAL; + return 0; + case WDIOC_KEEPALIVE: + booke_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(tmp, p)) + return -EFAULT; +#ifdef CONFIG_FSL_BOOKE + /* period of 1 gives the largest possible timeout */ + if (tmp > period_to_sec(1)) + return -EINVAL; + booke_wdt_period = sec_to_period(tmp); +#else + booke_wdt_period = tmp; +#endif + booke_wdt_set(); + /* Fall */ + case WDIOC_GETTIMEOUT: +#ifdef CONFIG_FSL_BOOKE + return put_user(period_to_sec(booke_wdt_period), p); +#else + return put_user(booke_wdt_period, p); +#endif + default: + return -ENOTTY; + } + + return 0; +} + +/* wdt_is_active stores wether or not the /dev/watchdog device is opened */ +static unsigned long wdt_is_active; + +static int booke_wdt_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &wdt_is_active)) + return -EBUSY; + + spin_lock(&booke_wdt_lock); + if (booke_wdt_enabled == 0) { + booke_wdt_enabled = 1; + on_each_cpu(__booke_wdt_enable, NULL, 0); + pr_debug("watchdog enabled (timeout = %llu sec)\n", + period_to_sec(booke_wdt_period)); + } + spin_unlock(&booke_wdt_lock); + + return nonseekable_open(inode, file); +} + +static int booke_wdt_release(struct inode *inode, struct file *file) +{ +#ifndef CONFIG_WATCHDOG_NOWAYOUT + /* Normally, the watchdog is disabled when /dev/watchdog is closed, but + * if CONFIG_WATCHDOG_NOWAYOUT is defined, then it means that the + * watchdog should remain enabled. So we disable it only if + * CONFIG_WATCHDOG_NOWAYOUT is not defined. + */ + on_each_cpu(__booke_wdt_disable, NULL, 0); + booke_wdt_enabled = 0; + pr_debug("watchdog disabled\n"); +#endif + + clear_bit(0, &wdt_is_active); + + return 0; +} + +static const struct file_operations booke_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = booke_wdt_write, + .unlocked_ioctl = booke_wdt_ioctl, + .open = booke_wdt_open, + .release = booke_wdt_release, +}; + +static struct miscdevice booke_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &booke_wdt_fops, +}; + +static void __exit booke_wdt_exit(void) +{ + misc_deregister(&booke_wdt_miscdev); +} + +static int __init booke_wdt_init(void) +{ + int ret = 0; + + pr_info("powerpc book-e watchdog driver loaded\n"); + ident.firmware_version = cur_cpu_spec->pvr_value; + + ret = misc_register(&booke_wdt_miscdev); + if (ret) { + pr_err("cannot register device (minor=%u, ret=%i)\n", + WATCHDOG_MINOR, ret); + return ret; + } + + spin_lock(&booke_wdt_lock); + if (booke_wdt_enabled == 1) { + pr_info("watchdog enabled (timeout = %llu sec)\n", + period_to_sec(booke_wdt_period)); + on_each_cpu(__booke_wdt_enable, NULL, 0); + } + spin_unlock(&booke_wdt_lock); + + return ret; +} + +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 00000000..6876430a --- /dev/null +++ b/drivers/watchdog/coh901327_wdt.c @@ -0,0 +1,472 @@ +/* + * 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_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_enable(clk); + if (ret) { + dev_err(&pdev->dev, "could not 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); + + if (margin < 1 || margin > 327) + margin = 60; + coh901327_wdt.timeout = margin; + + 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(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 struct platform_driver coh901327_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "coh901327_wdog", + }, + .remove = __exit_p(coh901327_remove), + .suspend = coh901327_suspend, + .resume = coh901327_resume, +}; + +static int __init coh901327_init(void) +{ + return platform_driver_probe(&coh901327_driver, coh901327_probe); +} +module_init(coh901327_init); + +static void __exit coh901327_exit(void) +{ + platform_driver_unregister(&coh901327_driver); +} +module_exit(coh901327_exit); + +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 new file mode 100644 index 00000000..7e888393 --- /dev/null +++ b/drivers/watchdog/cpu5wdt.c @@ -0,0 +1,300 @@ +/* + * sma cpu5 watchdog driver + * + * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.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. + * + * 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/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/io.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> + +/* adjustable parameters */ + +static int verbose; +static int port = 0x91; +static int ticks = 10000; +static DEFINE_SPINLOCK(cpu5wdt_lock); + +#define PFX "cpu5wdt: " + +#define CPU5WDT_EXTENT 0x0A + +#define CPU5WDT_STATUS_REG 0x00 +#define CPU5WDT_TIME_A_REG 0x02 +#define CPU5WDT_TIME_B_REG 0x03 +#define CPU5WDT_MODE_REG 0x04 +#define CPU5WDT_TRIGGER_REG 0x07 +#define CPU5WDT_ENABLE_REG 0x08 +#define CPU5WDT_RESET_REG 0x09 + +#define CPU5WDT_INTERVAL (HZ/10+1) + +/* some device data */ + +static struct { + struct completion stop; + int running; + struct timer_list timer; + int queue; + int default_ticks; + unsigned long inuse; +} cpu5wdt_device; + +/* generic helper functions */ + +static void cpu5wdt_trigger(unsigned long unused) +{ + if (verbose > 2) + pr_debug("trigger at %i ticks\n", ticks); + + if (cpu5wdt_device.running) + ticks--; + + spin_lock(&cpu5wdt_lock); + /* keep watchdog alive */ + outb(1, port + CPU5WDT_TRIGGER_REG); + + /* requeue?? */ + if (cpu5wdt_device.queue && ticks) + mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL); + else { + /* ticks doesn't matter anyway */ + complete(&cpu5wdt_device.stop); + } + spin_unlock(&cpu5wdt_lock); + +} + +static void cpu5wdt_reset(void) +{ + ticks = cpu5wdt_device.default_ticks; + + if (verbose) + pr_debug("reset (%i ticks)\n", (int) ticks); + +} + +static void cpu5wdt_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&cpu5wdt_lock, flags); + if (!cpu5wdt_device.queue) { + cpu5wdt_device.queue = 1; + outb(0, port + CPU5WDT_TIME_A_REG); + outb(0, port + CPU5WDT_TIME_B_REG); + outb(1, port + CPU5WDT_MODE_REG); + outb(0, port + CPU5WDT_RESET_REG); + outb(0, port + CPU5WDT_ENABLE_REG); + mod_timer(&cpu5wdt_device.timer, jiffies + CPU5WDT_INTERVAL); + } + /* if process dies, counter is not decremented */ + cpu5wdt_device.running++; + spin_unlock_irqrestore(&cpu5wdt_lock, flags); +} + +static int cpu5wdt_stop(void) +{ + unsigned long flags; + + spin_lock_irqsave(&cpu5wdt_lock, flags); + if (cpu5wdt_device.running) + cpu5wdt_device.running = 0; + ticks = cpu5wdt_device.default_ticks; + spin_unlock_irqrestore(&cpu5wdt_lock, flags); + if (verbose) + pr_crit("stop not possible\n"); + return -EIO; +} + +/* filesystem operations */ + +static int cpu5wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &cpu5wdt_device.inuse)) + return -EBUSY; + return nonseekable_open(inode, file); +} + +static int cpu5wdt_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &cpu5wdt_device.inuse); + return 0; +} + +static long cpu5wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + unsigned int value; + static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET, + .identity = "CPU5 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + case WDIOC_GETSTATUS: + value = inb(port + CPU5WDT_STATUS_REG); + value = (value >> 2) & 1; + return put_user(value, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + if (get_user(value, p)) + return -EFAULT; + if (value & WDIOS_ENABLECARD) + cpu5wdt_start(); + if (value & WDIOS_DISABLECARD) + cpu5wdt_stop(); + break; + case WDIOC_KEEPALIVE: + cpu5wdt_reset(); + break; + default: + return -ENOTTY; + } + return 0; +} + +static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (!count) + return -EIO; + cpu5wdt_reset(); + return count; +} + +static const struct file_operations cpu5wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = cpu5wdt_ioctl, + .open = cpu5wdt_open, + .write = cpu5wdt_write, + .release = cpu5wdt_release, +}; + +static struct miscdevice cpu5wdt_misc = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &cpu5wdt_fops, +}; + +/* init/exit function */ + +static int __devinit cpu5wdt_init(void) +{ + unsigned int val; + int err; + + if (verbose) + pr_debug("port=0x%x, verbose=%i\n", port, verbose); + + init_completion(&cpu5wdt_device.stop); + cpu5wdt_device.queue = 0; + setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0); + cpu5wdt_device.default_ticks = ticks; + + if (!request_region(port, CPU5WDT_EXTENT, PFX)) { + pr_err("request_region failed\n"); + err = -EBUSY; + goto no_port; + } + + /* watchdog reboot? */ + val = inb(port + CPU5WDT_STATUS_REG); + val = (val >> 2) & 1; + if (!val) + pr_info("sorry, was my fault\n"); + + err = misc_register(&cpu5wdt_misc); + if (err < 0) { + pr_err("misc_register failed\n"); + goto no_misc; + } + + + pr_info("init success\n"); + return 0; + +no_misc: + release_region(port, CPU5WDT_EXTENT); +no_port: + return err; +} + +static int __devinit cpu5wdt_init_module(void) +{ + return cpu5wdt_init(); +} + +static void __devexit cpu5wdt_exit(void) +{ + if (cpu5wdt_device.queue) { + cpu5wdt_device.queue = 0; + wait_for_completion(&cpu5wdt_device.stop); + } + + misc_deregister(&cpu5wdt_misc); + + release_region(port, CPU5WDT_EXTENT); + +} + +static void __devexit cpu5wdt_exit_module(void) +{ + cpu5wdt_exit(); +} + +/* module entry points */ + +module_init(cpu5wdt_init_module); +module_exit(cpu5wdt_exit_module); + +MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>"); +MODULE_DESCRIPTION("sma cpu5 watchdog driver"); +MODULE_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"); + +module_param(verbose, int, 0); +MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)"); + +module_param(ticks, int, 0); +MODULE_PARM_DESC(ticks, "count down ticks, default is 10000"); diff --git a/drivers/watchdog/cpwd.c b/drivers/watchdog/cpwd.c new file mode 100644 index 00000000..95b1b954 --- /dev/null +++ b/drivers/watchdog/cpwd.c @@ -0,0 +1,690 @@ +/* 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/init.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->f_path.dentry->d_inode; + 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->f_path.dentry->d_inode; + 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 __devinit 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); + } + + dev_set_drvdata(&op->dev, 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 __devexit cpwd_remove(struct platform_device *op) +{ + struct cpwd *p = dev_get_drvdata(&op->dev); + 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 = __devexit_p(cpwd_remove), +}; + +module_platform_driver(cpwd_driver); diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c new file mode 100644 index 00000000..c8c5c803 --- /dev/null +++ b/drivers/watchdog/davinci_wdt.c @@ -0,0 +1,287 @@ +/* + * drivers/char/watchdog/davinci_wdt.c + * + * Watchdog driver for DaVinci DM644x/DM646x processors + * + * Copyright (C) 2006 Texas Instruments. + * + * 2007 (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. + */ + +#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/platform_device.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/slab.h> + +#define MODULE_NAME "DAVINCI-WDT: " + +#define DEFAULT_HEARTBEAT 60 +#define MAX_HEARTBEAT 600 /* really the max margin is 264/27MHz*/ + +/* Timer register set definition */ +#define PID12 (0x0) +#define EMUMGT (0x4) +#define TIM12 (0x10) +#define TIM34 (0x14) +#define PRD12 (0x18) +#define PRD34 (0x1C) +#define TCR (0x20) +#define TGCR (0x24) +#define WDTCR (0x28) + +/* TCR bit definitions */ +#define ENAMODE12_DISABLED (0 << 6) +#define ENAMODE12_ONESHOT (1 << 6) +#define ENAMODE12_PERIODIC (2 << 6) + +/* TGCR bit definitions */ +#define TIM12RS_UNRESET (1 << 0) +#define TIM34RS_UNRESET (1 << 1) +#define TIMMODE_64BIT_WDOG (2 << 2) + +/* WDTCR bit definitions */ +#define WDEN (1 << 14) +#define WDFLAG (1 << 15) +#define WDKEY_SEQ0 (0xa5c6 << 16) +#define WDKEY_SEQ1 (0xda7e << 16) + +static int heartbeat = 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; +struct clk *wdt_clk; + +static void wdt_service(void) +{ + spin_lock(&io_lock); + + /* put watchdog in service state */ + iowrite32(WDKEY_SEQ0, wdt_base + WDTCR); + /* put watchdog in active state */ + iowrite32(WDKEY_SEQ1, wdt_base + WDTCR); + + spin_unlock(&io_lock); +} + +static void wdt_enable(void) +{ + u32 tgcr; + u32 timer_margin; + unsigned long wdt_freq; + + wdt_freq = clk_get_rate(wdt_clk); + + spin_lock(&io_lock); + + /* disable, internal clock source */ + iowrite32(0, wdt_base + TCR); + /* reset timer, set mode to 64-bit watchdog, and unreset */ + iowrite32(0, wdt_base + TGCR); + tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET; + iowrite32(tgcr, wdt_base + TGCR); + /* clear counter regs */ + iowrite32(0, wdt_base + TIM12); + iowrite32(0, wdt_base + TIM34); + /* set timeout period */ + timer_margin = (((u64)heartbeat * wdt_freq) & 0xffffffff); + iowrite32(timer_margin, wdt_base + PRD12); + timer_margin = (((u64)heartbeat * wdt_freq) >> 32); + iowrite32(timer_margin, wdt_base + PRD34); + /* enable run continuously */ + iowrite32(ENAMODE12_PERIODIC, wdt_base + TCR); + /* Once the WDT is in pre-active state write to + * TIM12, TIM34, PRD12, PRD34, TCR, TGCR, WDTCR are + * write protected (except for the WDKEY field) + */ + /* put watchdog in pre-active state */ + iowrite32(WDKEY_SEQ0 | WDEN, wdt_base + WDTCR); + /* put watchdog in active state */ + iowrite32(WDKEY_SEQ1 | WDEN, wdt_base + WDTCR); + + spin_unlock(&io_lock); +} + +static int davinci_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) + return -EBUSY; + + wdt_enable(); + + return nonseekable_open(inode, file); +} + +static ssize_t +davinci_wdt_write(struct file *file, const char *data, size_t len, + loff_t *ppos) +{ + if (len) + wdt_service(); + + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, + .identity = "DaVinci Watchdog", +}; + +static long davinci_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 *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_service(); + ret = 0; + break; + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + } + return ret; +} + +static int davinci_wdt_release(struct inode *inode, struct file *file) +{ + wdt_service(); + clear_bit(WDT_IN_USE, &wdt_status); + + return 0; +} + +static const struct file_operations davinci_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = davinci_wdt_write, + .unlocked_ioctl = davinci_wdt_ioctl, + .open = davinci_wdt_open, + .release = davinci_wdt_release, +}; + +static struct miscdevice davinci_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &davinci_wdt_fops, +}; + +static int __devinit davinci_wdt_probe(struct platform_device *pdev) +{ + int ret = 0, size; + struct device *dev = &pdev->dev; + + wdt_clk = clk_get(dev, NULL); + if (WARN_ON(IS_ERR(wdt_clk))) + return PTR_ERR(wdt_clk); + + clk_enable(wdt_clk); + + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) + heartbeat = DEFAULT_HEARTBEAT; + + dev_info(dev, "heartbeat %d sec\n", heartbeat); + + wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (wdt_mem == NULL) { + dev_err(dev, "failed to get memory region resource\n"); + return -ENOENT; + } + + size = resource_size(wdt_mem); + if (!request_mem_region(wdt_mem->start, size, pdev->name)) { + dev_err(dev, "failed to get memory region\n"); + return -ENOENT; + } + + wdt_base = ioremap(wdt_mem->start, size); + if (!wdt_base) { + dev_err(dev, "failed to map memory region\n"); + release_mem_region(wdt_mem->start, size); + wdt_mem = NULL; + return -ENOMEM; + } + + ret = misc_register(&davinci_wdt_miscdev); + if (ret < 0) { + dev_err(dev, "cannot register misc device\n"); + release_mem_region(wdt_mem->start, size); + wdt_mem = NULL; + } else { + set_bit(WDT_DEVICE_INITED, &wdt_status); + } + + iounmap(wdt_base); + return ret; +} + +static int __devexit davinci_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&davinci_wdt_miscdev); + if (wdt_mem) { + release_mem_region(wdt_mem->start, resource_size(wdt_mem)); + wdt_mem = NULL; + } + + clk_disable(wdt_clk); + clk_put(wdt_clk); + + return 0; +} + +static struct platform_driver platform_wdt_driver = { + .driver = { + .name = "watchdog", + .owner = THIS_MODULE, + }, + .probe = davinci_wdt_probe, + .remove = __devexit_p(davinci_wdt_remove), +}; + +module_platform_driver(platform_wdt_driver); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("DaVinci Watchdog Driver"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat period in seconds from 1 to " + __MODULE_STRING(MAX_HEARTBEAT) ", default " + __MODULE_STRING(DEFAULT_HEARTBEAT)); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:watchdog"); diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c new file mode 100644 index 00000000..06de1211 --- /dev/null +++ b/drivers/watchdog/dw_wdt.c @@ -0,0 +1,363 @@ +/* + * 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 ARM 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/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); +} + +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((struct watchdog_info *)arg, &dw_wdt_ident, + sizeof(dw_wdt_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int *)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 +static int dw_wdt_suspend(struct device *dev) +{ + clk_disable(dw_wdt.clk); + + return 0; +} + +static int dw_wdt_resume(struct device *dev) +{ + int err = clk_enable(dw_wdt.clk); + + if (err) + return err; + + dw_wdt_keepalive(); + + return 0; +} + +static const struct dev_pm_ops dw_wdt_pm_ops = { + .suspend = dw_wdt_suspend, + .resume = dw_wdt_resume, +}; +#endif /* CONFIG_PM */ + +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 __devinit 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_request_and_ioremap(&pdev->dev, mem); + if (!dw_wdt.regs) + return -ENOMEM; + + dw_wdt.clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(dw_wdt.clk)) + return PTR_ERR(dw_wdt.clk); + + ret = clk_enable(dw_wdt.clk); + if (ret) + goto out_put_clk; + + 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(dw_wdt.clk); +out_put_clk: + clk_put(dw_wdt.clk); + + return ret; +} + +static int __devexit dw_wdt_drv_remove(struct platform_device *pdev) +{ + misc_deregister(&dw_wdt_miscdev); + + clk_disable(dw_wdt.clk); + clk_put(dw_wdt.clk); + + return 0; +} + +static struct platform_driver dw_wdt_driver = { + .probe = dw_wdt_drv_probe, + .remove = __devexit_p(dw_wdt_drv_remove), + .driver = { + .name = "dw_wdt", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &dw_wdt_pm_ops, +#endif /* CONFIG_PM */ + }, +}; + +module_platform_driver(dw_wdt_driver); + +MODULE_AUTHOR("Jamie Iles"); +MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c new file mode 100644 index 00000000..77050037 --- /dev/null +++ b/drivers/watchdog/ep93xx_wdt.c @@ -0,0 +1,182 @@ +/* + * Watchdog driver for Cirrus Logic EP93xx family of devices. + * + * Copyright (c) 2004 Ray Lehtiniemi + * Copyright (c) 2006 Tower Technologies + * Based on ep93xx driver, bits from alim7101_wdt.c + * + * Authors: Ray Lehtiniemi <rayl@mail.com>, + * Alessandro Zummo <a.zummo@towertech.it> + * + * Copyright (c) 2012 H Hartley Sweeten <hsweeten@visionengravers.com> + * Convert to a platform device and use the watchdog framework API + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + * This watchdog fires after 250msec, which is a too short interval + * for us to rely on the user space daemon alone. So we ping the + * wdt each ~200msec and eventually stop doing it if the user space + * daemon dies. + * + * TODO: + * + * - Test last reset from watchdog status + * - Add a few missing ioctls + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/io.h> + +#define WDT_VERSION "0.4" + +/* default timeout (secs) */ +#define WDT_TIMEOUT 30 + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +static unsigned int timeout = 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; + +#define EP93XX_WATCHDOG 0x00 +#define EP93XX_WDSTATUS 0x04 + +/* reset the wdt every ~200ms - the heartbeat of the device is 0.250 seconds*/ +#define WDT_INTERVAL (HZ/5) + +static void ep93xx_wdt_timer_ping(unsigned long data) +{ + if (time_before(jiffies, next_heartbeat)) + writel(0x5555, mmio_base + EP93XX_WATCHDOG); + + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); +} + +static int ep93xx_wdt_start(struct watchdog_device *wdd) +{ + next_heartbeat = jiffies + (timeout * HZ); + + writel(0xaaaa, mmio_base + EP93XX_WATCHDOG); + mod_timer(&timer, jiffies + WDT_INTERVAL); + + return 0; +} + +static int ep93xx_wdt_stop(struct watchdog_device *wdd) +{ + del_timer_sync(&timer); + writel(0xaa55, mmio_base + EP93XX_WATCHDOG); + + return 0; +} + +static int ep93xx_wdt_keepalive(struct watchdog_device *wdd) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); + + return 0; +} + +static const struct watchdog_info ep93xx_wdt_ident = { + .options = WDIOF_CARDRESET | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .identity = "EP93xx Watchdog", +}; + +static struct watchdog_ops ep93xx_wdt_ops = { + .owner = THIS_MODULE, + .start = ep93xx_wdt_start, + .stop = ep93xx_wdt_stop, + .ping = ep93xx_wdt_keepalive, +}; + +static struct watchdog_device ep93xx_wdt_wdd = { + .info = &ep93xx_wdt_ident, + .ops = &ep93xx_wdt_ops, +}; + +static int __devinit ep93xx_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + unsigned long val; + int err; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + mmio_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!mmio_base) + return -ENXIO; + + if (timeout < 1 || timeout > 3600) { + timeout = WDT_TIMEOUT; + dev_warn(&pdev->dev, + "timeout value must be 1<=x<=3600, using %d\n", + timeout); + } + + 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 int __devexit ep93xx_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&ep93xx_wdt_wdd); + return 0; +} + +static struct platform_driver ep93xx_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ep93xx-wdt", + }, + .probe = ep93xx_wdt_probe, + .remove = __devexit_p(ep93xx_wdt_remove), +}; + +module_platform_driver(ep93xx_wdt_driver); + +MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," + "Alessandro Zummo <a.zummo@towertech.it>," + "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 new file mode 100644 index 00000000..cd31b8a2 --- /dev/null +++ b/drivers/watchdog/eurotechwdt.c @@ -0,0 +1,480 @@ +/* + * Eurotech CPU-1220/1410/1420 on board WDT driver + * + * (c) Copyright 2001 Ascensit <support@ascensit.com> + * (c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com> + * (c) Copyright 2002 Rob Radez <rob@osinvestor.com> + * + * Based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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>* + */ + +/* Changelog: + * + * 2001 - Rodolfo Giometti + * Initial release + * + * 2002/04/25 - Rob Radez + * clean up #includes + * clean up locking + * make __setup param unique + * proper options in watchdog_info + * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls + * add expect_close support + * + * 2002.05.30 - Joel Becker <joel.becker@oracle.com> + * Added Matt Domsch's nowayout module option. + */ + +/* + * The eurotech CPU-1220/1410/1420's watchdog is a part + * of the on-board SUPER I/O device SMSC FDC 37B782. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +static unsigned long eurwdt_is_open; +static int eurwdt_timeout; +static char eur_expect_close; +static DEFINE_SPINLOCK(eurwdt_lock); + +/* + * You must set these - there is no sane way to probe for this board. + */ + +static int io = 0x3f0; +static int irq = 10; +static char *ev = "int"; + +#define WDT_TIMEOUT 60 /* 1 minute */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Some symbolic names + */ + +#define WDT_CTRL_REG 0x30 +#define WDT_OUTPIN_CFG 0xe2 +#define WDT_EVENT_INT 0x00 +#define WDT_EVENT_REBOOT 0x08 +#define WDT_UNIT_SEL 0xf1 +#define WDT_UNIT_SECS 0x80 +#define WDT_TIMEOUT_VAL 0xf2 +#define WDT_TIMER_CFG 0xf3 + + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)"); +module_param(irq, int, 0); +MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)"); +module_param(ev, charp, 0); +MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')"); + + +/* + * Programming support + */ + +static inline void eurwdt_write_reg(u8 index, u8 data) +{ + outb(index, io); + outb(data, io+1); +} + +static inline void eurwdt_lock_chip(void) +{ + outb(0xaa, io); +} + +static inline void eurwdt_unlock_chip(void) +{ + outb(0x55, io); + eurwdt_write_reg(0x07, 0x08); /* set the logical device */ +} + +static inline void eurwdt_set_timeout(int timeout) +{ + eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout); +} + +static inline void eurwdt_disable_timer(void) +{ + eurwdt_set_timeout(0); +} + +static void eurwdt_activate_timer(void) +{ + eurwdt_disable_timer(); + eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */ + eurwdt_write_reg(WDT_OUTPIN_CFG, + !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT); + + /* Setting interrupt line */ + if (irq == 2 || irq > 15 || irq < 0) { + pr_err("invalid irq number\n"); + irq = 0; /* if invalid we disable interrupt */ + } + if (irq == 0) + pr_info("interrupt disabled\n"); + + eurwdt_write_reg(WDT_TIMER_CFG, irq << 4); + + eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */ + eurwdt_set_timeout(0); /* the default timeout */ +} + + +/* + * Kernel methods. + */ + +static irqreturn_t eurwdt_interrupt(int irq, void *dev_id) +{ + pr_crit("timeout WDT timeout\n"); + +#ifdef ONLY_TESTING + pr_crit("Would Reboot\n"); +#else + pr_crit("Initiating system reboot\n"); + emergency_restart(); +#endif + return IRQ_HANDLED; +} + + +/** + * eurwdt_ping: + * + * Reload counter one with the watchdog timeout. + */ + +static void eurwdt_ping(void) +{ + /* Write the watchdog default value */ + eurwdt_set_timeout(eurwdt_timeout); +} + +/** + * eurwdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t eurwdt_write(struct file *file, const char __user *buf, +size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + eur_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + eur_expect_close = 42; + } + } + spin_lock(&eurwdt_lock); + eurwdt_ping(); /* the default timeout */ + spin_unlock(&eurwdt_lock); + } + return count; +} + +/** + * eurwdt_ioctl: + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. + */ + +static long eurwdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "WDT Eurotech CPU-1220/1410", + }; + + int time; + int options, retval = -EINVAL; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + if (get_user(options, p)) + return -EFAULT; + spin_lock(&eurwdt_lock); + if (options & WDIOS_DISABLECARD) { + eurwdt_disable_timer(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + eurwdt_activate_timer(); + eurwdt_ping(); + retval = 0; + } + spin_unlock(&eurwdt_lock); + return retval; + + case WDIOC_KEEPALIVE: + spin_lock(&eurwdt_lock); + eurwdt_ping(); + spin_unlock(&eurwdt_lock); + return 0; + + case WDIOC_SETTIMEOUT: + if (copy_from_user(&time, p, sizeof(int))) + return -EFAULT; + + /* Sanity check */ + if (time < 0 || time > 255) + return -EINVAL; + + spin_lock(&eurwdt_lock); + eurwdt_timeout = time; + eurwdt_set_timeout(time); + spin_unlock(&eurwdt_lock); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(eurwdt_timeout, p); + + default: + return -ENOTTY; + } +} + +/** + * eurwdt_open: + * @inode: inode of device + * @file: file handle to device + * + * The misc device has been opened. The watchdog device is single + * open and on opening we load the counter. + */ + +static int eurwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &eurwdt_is_open)) + return -EBUSY; + eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */ + /* Activate the WDT */ + eurwdt_activate_timer(); + return nonseekable_open(inode, file); +} + +/** + * eurwdt_release: + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int eurwdt_release(struct inode *inode, struct file *file) +{ + if (eur_expect_close == 42) + eurwdt_disable_timer(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + eurwdt_ping(); + } + clear_bit(0, &eurwdt_is_open); + eur_expect_close = 0; + return 0; +} + +/** + * eurwdt_notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + eurwdt_disable_timer(); /* Turn the card off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static const struct file_operations eurwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = eurwdt_write, + .unlocked_ioctl = eurwdt_ioctl, + .open = eurwdt_open, + .release = eurwdt_release, +}; + +static struct miscdevice eurwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &eurwdt_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block eurwdt_notifier = { + .notifier_call = eurwdt_notify_sys, +}; + +/** + * cleanup_module: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit eurwdt_exit(void) +{ + eurwdt_lock_chip(); + + misc_deregister(&eurwdt_miscdev); + + unregister_reboot_notifier(&eurwdt_notifier); + release_region(io, 2); + free_irq(irq, NULL); +} + +/** + * eurwdt_init: + * + * Set up the WDT watchdog board. After grabbing the resources + * we require we need also to unlock the device. + * The open() function will actually kick the board off. + */ + +static int __init eurwdt_init(void) +{ + int ret; + + ret = request_irq(irq, eurwdt_interrupt, 0, "eurwdt", NULL); + if (ret) { + pr_err("IRQ %d is not free\n", irq); + goto out; + } + + if (!request_region(io, 2, "eurwdt")) { + pr_err("IO %X is not free\n", io); + ret = -EBUSY; + goto outirq; + } + + ret = register_reboot_notifier(&eurwdt_notifier); + if (ret) { + pr_err("can't register reboot notifier (err=%d)\n", ret); + goto outreg; + } + + ret = misc_register(&eurwdt_miscdev); + if (ret) { + pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); + goto outreboot; + } + + eurwdt_unlock_chip(); + + ret = 0; + pr_info("Eurotech WDT driver 0.01 at %X (Interrupt %d) - timeout event: %s\n", + io, irq, (!strcmp("int", ev) ? "int" : "reboot")); + +out: + return ret; + +outreboot: + unregister_reboot_notifier(&eurwdt_notifier); + +outreg: + release_region(io, 2); + +outirq: + free_irq(irq, NULL); + goto out; +} + +module_init(eurwdt_init); +module_exit(eurwdt_exit); + +MODULE_AUTHOR("Rodolfo Giometti"); +MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c new file mode 100644 index 00000000..c65b0a5a --- /dev/null +++ b/drivers/watchdog/f71808e_wdt.c @@ -0,0 +1,818 @@ +/*************************************************************************** + * 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_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 send 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: + 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 00000000..17f4cae7 --- /dev/null +++ b/drivers/watchdog/gef_wdt.c @@ -0,0 +1,333 @@ +/* + * 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_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 __devinit 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 __devexit 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, +}; + +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_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:gef_wdt"); diff --git a/drivers/watchdog/geodewdt.c b/drivers/watchdog/geodewdt.c new file mode 100644 index 00000000..dc563b68 --- /dev/null +++ b/drivers/watchdog/geodewdt.c @@ -0,0 +1,300 @@ +/* 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 __devinit 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 __devexit 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 = { + .probe = geodewdt_probe, + .remove = __devexit_p(geodewdt_remove), + .shutdown = geodewdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init geodewdt_init(void) +{ + int ret; + + ret = platform_driver_register(&geodewdt_driver); + if (ret) + return ret; + + geodewdt_platform_device = platform_device_register_simple(DRV_NAME, + -1, NULL, 0); + if (IS_ERR(geodewdt_platform_device)) { + ret = PTR_ERR(geodewdt_platform_device); + goto err; + } + + return 0; +err: + platform_driver_unregister(&geodewdt_driver); + 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c new file mode 100644 index 00000000..9f13b897 --- /dev/null +++ b/drivers/watchdog/hpwdt.c @@ -0,0 +1,891 @@ +/* + * HP WatchDog Driver + * based on + * + * SoftDog 0.05: A Software Watchdog Device + * + * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. + * Thomas Mingarelli <thomas.mingarelli@hp.com> + * + * 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/device.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/pci_ids.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/watchdog.h> +#ifdef CONFIG_HPWDT_NMI_DECODING +#include <linux/dmi.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.0" +#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 DEFINE_PCI_DEVICE_TABLE(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 +#define PCI_ROM_BASE1 0x000F0000 +#define ROM_SIZE 0x10000 + +struct bios32_service_dir { + u32 signature; + u32 entry_point; + u8 revision; + u8 length; + u8 checksum; + u8 reserved[5]; +}; + +/* type 212 */ +struct smbios_cru64_info { + u8 type; + u8 byte_length; + u16 handle; + u32 signature; + u64 physical_address; + u32 double_length; + u32 double_offset; +}; +#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 { + u8 ral; + u8 rah; + u16 rea2; + }; + u32 reax; + } u1; + union { + struct { + u8 rbl; + u8 rbh; + u8 reb2l; + u8 reb2h; + }; + u32 rebx; + } u2; + union { + struct { + u8 rcl; + u8 rch; + u16 rec2; + }; + u32 recx; + } u3; + union { + struct { + u8 rdl; + u8 rdh; + u16 red2; + }; + u32 redx; + } u4; + + u32 resi; + u32 redi; + u16 rds; + u16 res; + u32 reflags; +} __attribute__((packed)); + +static unsigned int hpwdt_nmi_decoding; +static unsigned int allow_kdump; +static unsigned int priority; /* hpwdt at end of die_notify list */ +static unsigned int is_icru; +static DEFINE_SPINLOCK(rom_lock); +static void *cru_rom_addr; +static struct cmn_registers cmn_regs; + +extern asmlinkage void asminline_call(struct cmn_registers *pi86Regs, + unsigned long *pRomEntry); + +#ifdef CONFIG_X86_32 +/* --32 Bit Bios------------------------------------------------------------ */ + +#define HPWDT_ARCH 32 + +asm(".text \n\t" + ".align 4 \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 + * + * Routine Description: + * This function uses the 32-bit BIOS Service Directory record to + * search for a $CRU record. + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int __devinit cru_detect(unsigned long map_entry, + unsigned long map_offset) +{ + void *bios32_map; + unsigned long *bios32_entrypoint; + unsigned long cru_physical_address; + unsigned long cru_length; + unsigned long physical_bios_base = 0; + unsigned long physical_bios_offset = 0; + int retval = -ENODEV; + + bios32_map = ioremap(map_entry, (2 * PAGE_SIZE)); + + if (bios32_map == NULL) + return -ENODEV; + + bios32_entrypoint = bios32_map + map_offset; + + 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) { + 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; + + /* 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) { + set_memory_x((unsigned long)cru_rom_addr & PAGE_MASK, + (cru_length + PAGE_SIZE - 1) >> PAGE_SHIFT); + retval = 0; + } + } + + 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; +} + +/* + * bios_checksum + */ +static int __devinit bios_checksum(const char __iomem *ptr, int len) +{ + char sum = 0; + int i; + + /* + * calculate checksum of size bytes. This should add up + * to zero if we have a valid header. + */ + for (i = 0; i < len; i++) + sum += ptr[i]; + + return ((sum == 0) && (len > 0)); +} + +/* + * bios32_present + * + * Routine Description: + * This function finds the 32-bit BIOS Service Directory + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int __devinit bios32_present(const char __iomem *p) +{ + struct bios32_service_dir *bios_32_ptr; + int length; + unsigned long map_entry, map_offset; + + bios_32_ptr = (struct bios32_service_dir *) p; + + /* + * Search for signature by checking equal to the swizzled value + * instead of calling another routine to perform a strcmp. + */ + if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) { + length = bios_32_ptr->length * PCI_BIOS32_PARAGRAPH_LEN; + if (bios_checksum(p, length)) { + /* + * According to the spec, we're looking for the + * first 4KB-aligned address below the entrypoint + * listed in the header. The Service Directory code + * is guaranteed to occupy no more than 2 4KB pages. + */ + map_entry = bios_32_ptr->entry_point & ~(PAGE_SIZE - 1); + map_offset = bios_32_ptr->entry_point - map_entry; + + return cru_detect(map_entry, map_offset); + } + } + return -ENODEV; +} + +static int __devinit detect_cru_service(void) +{ + char __iomem *p, *q; + int rc = -1; + + /* + * Search from 0x0f0000 through 0x0fffff, inclusive. + */ + p = ioremap(PCI_ROM_BASE1, ROM_SIZE); + if (p == NULL) + return -ENOMEM; + + for (q = p; q < p + ROM_SIZE; q += 16) { + rc = bios32_present(q); + if (!rc) + break; + } + iounmap(p); + return rc; +} +/* ------------------------------------------------------------------------- */ +#endif /* CONFIG_X86_32 */ +#ifdef CONFIG_X86_64 +/* --64 Bit Bios------------------------------------------------------------ */ + +#define HPWDT_ARCH 64 + +asm(".text \n\t" + ".align 4 \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 + * + * Routine Description: + * 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, void *dummy) +{ + struct smbios_cru64_info *smbios_cru64_ptr; + unsigned long cru_physical_address; + + if (dm->type == SMBIOS_CRU64_INFORMATION) { + 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; + cru_rom_addr = ioremap(cru_physical_address, + 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) +{ + cru_rom_addr = NULL; + + 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); +} +/* ------------------------------------------------------------------------- */ +#endif /* CONFIG_X86_64 */ +#endif /* CONFIG_HPWDT_NMI_DECODING */ + +/* + * Watchdog operations + */ +static void hpwdt_start(void) +{ + reload = SECS_TO_TICKS(soft_margin); + iowrite16(reload, hpwdt_timer_reg); + iowrite8(0x85, hpwdt_timer_con); +} + +static void hpwdt_stop(void) +{ + unsigned long data; + + data = ioread8(hpwdt_timer_con); + data &= 0xFE; + iowrite8(data, hpwdt_timer_con); +} + +static void hpwdt_ping(void) +{ + iowrite16(reload, hpwdt_timer_reg); +} + +static int hpwdt_change_timer(int new_margin) +{ + 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; + 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) + 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) { + if (cmn_regs.u1.ral == 0) { + panic("An NMI occurred, " + "but unable to determine source.\n"); + } + } + panic("An NMI occurred, please see the Integrated " + "Management Log for details.\n"); + +out: + return NMI_DONE; +} +#endif /* CONFIG_HPWDT_NMI_DECODING */ + +/* + * /dev/watchdog handling + */ +static int hpwdt_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &hpwdt_is_open)) + return -EBUSY; + + /* Start the watchdog */ + hpwdt_start(); + hpwdt_ping(); + + return nonseekable_open(inode, file); +} + +static int hpwdt_release(struct inode *inode, struct file *file) +{ + /* Stop the watchdog */ + if (expect_release == 42) { + hpwdt_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + hpwdt_ping(); + } + + expect_release = 0; + + /* /dev/watchdog is being closed, make sure it can be re-opened */ + clear_bit(0, &hpwdt_is_open); + + return 0; +} + +static ssize_t hpwdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + expect_release = 0; + + /* scan to see whether or not we got the magic char. */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + hpwdt_ping(); + } + + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "HP iLO2+ HW Watchdog Timer", +}; + +static long hpwdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + int ret = -ENOTTY; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = 0; + if (copy_to_user(argp, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_KEEPALIVE: + hpwdt_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(new_margin, p); + if (ret) + break; + + ret = hpwdt_change_timer(new_margin); + if (ret) + break; + + hpwdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + ret = put_user(soft_margin, p); + break; + + case WDIOC_GETTIMELEFT: + ret = put_user(hpwdt_time_left(), p); + break; + } + return ret; +} + +/* + * Kernel interfaces + */ +static const struct file_operations hpwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = hpwdt_write, + .unlocked_ioctl = hpwdt_ioctl, + .open = hpwdt_open, + .release = hpwdt_release, +}; + +static struct miscdevice hpwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &hpwdt_fops, +}; + +/* + * Init & Exit + */ + +#ifdef CONFIG_HPWDT_NMI_DECODING +#ifdef CONFIG_X86_LOCAL_APIC +static void __devinit 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 __devinit 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 __devinit 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; + } +} + +static int __devinit hpwdt_init_nmi_decoding(struct pci_dev *dev) +{ + int retval; + + /* + * 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) { + + /* + * 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; + } + + /* + * If the priority is set to 1, then we will be put first on the + * die notify list to handle a critical NMI. The default is to + * be last so other users of the NMI signal can function. + */ + retval = register_nmi_handler(NMI_UNKNOWN, hpwdt_pretimeout, + (priority) ? NMI_FLAG_FIRST : 0, + "hpwdt"); + if (retval != 0) { + dev_warn(&dev->dev, + "Unable to register a die notifier (err=%d).\n", + retval); + if (cru_rom_addr) + iounmap(cru_rom_addr); + } + + dev_info(&dev->dev, + "HP Watchdog Timer Driver: NMI decoding initialized" + ", allow kernel dump: %s (default = 0/OFF)" + ", priority: %s (default = 0/LAST).\n", + (allow_kdump == 0) ? "OFF" : "ON", + (priority == 0) ? "LAST" : "FIRST"); + return 0; +} + +static void hpwdt_exit_nmi_decoding(void) +{ + unregister_nmi_handler(NMI_UNKNOWN, "hpwdt"); + if (cru_rom_addr) + iounmap(cru_rom_addr); +} +#else /* !CONFIG_HPWDT_NMI_DECODING */ +static void __devinit hpwdt_check_nmi_decoding(struct pci_dev *dev) +{ +} + +static int __devinit hpwdt_init_nmi_decoding(struct pci_dev *dev) +{ + return 0; +} + +static void hpwdt_exit_nmi_decoding(void) +{ +} +#endif /* CONFIG_HPWDT_NMI_DECODING */ + +static int __devinit 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"); + return -ENODEV; + } + + if (pci_enable_device(dev)) { + dev_warn(&dev->dev, + "Not possible to enable PCI Device: 0x%x:0x%x.\n", + ent->vendor, ent->device); + return -ENODEV; + } + + pci_mem_addr = pci_iomap(dev, 1, 0x80); + if (!pci_mem_addr) { + dev_warn(&dev->dev, + "Unable to detect the iLO2+ server memory.\n"); + retval = -ENOMEM; + goto error_pci_iomap; + } + hpwdt_timer_reg = pci_mem_addr + 0x70; + hpwdt_timer_con = pci_mem_addr + 0x72; + + /* Make sure that we have a valid soft_margin */ + if (hpwdt_change_timer(soft_margin)) + hpwdt_change_timer(DEFAULT_MARGIN); + + /* 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) { + dev_warn(&dev->dev, + "Unable to register miscdev on minor=%d (err=%d).\n", + WATCHDOG_MINOR, retval); + goto error_misc_register; + } + + 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: + 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) +{ + if (!nowayout) + hpwdt_stop(); + + misc_deregister(&hpwdt_miscdev); + hpwdt_exit_nmi_decoding(); + pci_iounmap(dev, pci_mem_addr); + pci_disable_device(dev); +} + +static struct pci_driver hpwdt_driver = { + .name = "hpwdt", + .id_table = hpwdt_devices, + .probe = hpwdt_init_one, + .remove = __devexit_p(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_VERSION(HPWDT_VERSION); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(soft_margin, int, 0); +MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#ifdef CONFIG_HPWDT_NMI_DECODING +module_param(allow_kdump, int, 0); +MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); + +module_param(priority, int, 0); +MODULE_PARM_DESC(priority, "The hpwdt driver handles NMIs first or last" + " (default = 0/Last)\n"); +#endif /* !CONFIG_HPWDT_NMI_DECODING */ + +module_init(hpwdt_init); +module_exit(hpwdt_cleanup); diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c new file mode 100644 index 00000000..738032a3 --- /dev/null +++ b/drivers/watchdog/i6300esb.c @@ -0,0 +1,512 @@ +/* + * i6300esb: Watchdog timer driver for Intel 6300ESB chipset + * + * (c) Copyright 2004 Google Inc. + * (c) Copyright 2005 David Härdeman <david@2gen.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. + * + * based on i810-tco.c which is in turn based on softdog.c + * + * The timer is implemented in the following I/O controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * 6300ESB chip : document number 300641-004 + * + * 2004YYZZ Ross Biro + * Initial version 0.01 + * 2004YYZZ Ross Biro + * Version 0.02 + * 20050210 David Härdeman <david@2gen.com> + * Ported driver to kernel 2.6 + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#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/init.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +/* Module and version information */ +#define ESB_VERSION "0.05" +#define ESB_MODULE_NAME "i6300ESB timer" +#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION + +/* 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 */ + +/* Lock register bits */ +#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */ +#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */ +#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */ + +/* Config register bits */ +#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */ +#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */ +#define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */ + +/* Reload register bits */ +#define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */ +#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */ + +/* Magic constants */ +#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ +#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ + +/* internal variables */ +static void __iomem *BASEADDR; +static DEFINE_SPINLOCK(esb_lock); /* Guards the hardware */ +static unsigned long timer_alive; +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 */ +/* 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) ")"); + +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 + */ + +/* + * Prepare for reloading the timer by unlocking the proper registers. + * This is performed by first writing 0x80 followed by 0x86 to the + * reload register. After this the appropriate registers can be written + * to once before they need to be unlocked again. + */ +static inline void esb_unlock_registers(void) +{ + writew(ESB_UNLOCK1, ESB_RELOAD_REG); + writew(ESB_UNLOCK2, ESB_RELOAD_REG); +} + +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 = 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) +{ + u8 val; + + spin_lock(&esb_lock); + /* First, reset timers as suggested by the docs */ + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); + /* Then disable the WDT */ + pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x0); + pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val); + spin_unlock(&esb_lock); + + /* Returns 0 if the timer was disabled, non-zero otherwise */ + return val & ESB_WDT_ENABLE; +} + +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? */ + spin_unlock(&esb_lock); +} + +static int esb_timer_set_heartbeat(int time) +{ + u32 val; + + if (time < 0x1 || time > (2 * 0x03ff)) + return -EINVAL; + + spin_lock(&esb_lock); + + /* We shift by 9, so if we are passed a value of 1 sec, + * val will be 1 << 9 = 512, then write that to two + * timers => 2 * 512 = 1024 (which is decremented at 1KHz) + */ + val = time << 9; + + /* Write timer 1 */ + esb_unlock_registers(); + writel(val, ESB_TIMER1_REG); + + /* Write timer 2 */ + esb_unlock_registers(); + writel(val, ESB_TIMER2_REG); + + /* Reload */ + esb_unlock_registers(); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG); + + /* FIXME: Do we need to flush everything out? */ + + /* Done */ + heartbeat = time; + spin_unlock(&esb_lock); + return 0; +} + +/* + * /dev/watchdog handling + */ + +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; + + /* Reload and activate timer */ + esb_timer_start(); + + return nonseekable_open(inode, file); +} + +static int esb_release(struct inode *inode, struct file *file) +{ + /* 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) +{ + /* 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... */ + esb_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') + esb_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + esb_timer_keepalive(); + } + return len; +} + +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 const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .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(0, p); + + case WDIOC_GETBOOTSTATUS: + return put_user(triggered, p); + + case WDIOC_SETOPTIONS: + { + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + esb_timer_stop(); + retval = 0; + } + + 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; + } +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations esb_fops = { + .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, +}; + +/* + * Data for PCI driver interface + */ +static DEFINE_PCI_DEVICE_TABLE(esb_pci_tbl) = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, esb_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __devinit esb_getdevice(struct pci_dev *pdev) +{ + if (pci_enable_device(pdev)) { + pr_err("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; + } + + 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; + } + + /* Done */ + esb_pci = pdev; + return 1; + +err_release: + pci_release_region(pdev, 0); +err_disable: + pci_disable_device(pdev); +err_devput: + return 0; +} + +static void __devinit 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 __devinit esb_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + 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); + pci_release_region(esb_pci, 0); + pci_disable_device(esb_pci); + esb_pci = NULL; + return ret; +} + +static void __devexit esb_remove(struct pci_dev *pdev) +{ + /* Stop the timer before we leave */ + if (!nowayout) + esb_timer_stop(); + + /* Deregister */ + misc_deregister(&esb_miscdev); + iounmap(BASEADDR); + pci_release_region(esb_pci, 0); + pci_disable_device(esb_pci); + esb_pci = NULL; +} + +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 = __devexit_p(esb_remove), + .shutdown = esb_shutdown, +}; + +static int __init watchdog_init(void) +{ + return pci_register_driver(&esb_driver); +} + +static void __exit watchdog_cleanup(void) +{ + pci_unregister_driver(&esb_driver); + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(watchdog_init); +module_exit(watchdog_cleanup); + +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 00000000..9e27e642 --- /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(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 diff --git a/drivers/watchdog/iTCO_vendor_support.c b/drivers/watchdog/iTCO_vendor_support.c new file mode 100644 index 00000000..2721d29c --- /dev/null +++ b/drivers/watchdog/iTCO_vendor_support.c @@ -0,0 +1,376 @@ +/* + * intel TCO vendor specific watchdog driver support + * + * (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 + * 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 + +/* Module and version information */ +#define DRV_NAME "iTCO_vendor_support" +#define DRV_VERSION "1.04" + +/* Includes */ +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/io.h> /* For inb/outb/... */ + +#include "iTCO_vendor.h" + +/* 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 */ + +/* List of vendor support modes */ +/* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */ +#define SUPERMICRO_OLD_BOARD 1 +/* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */ +#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+, " + "911=Broken SMI BIOS"); + +/* + * Vendor Specific Support + */ + +/* + * Vendor Support: 1 + * Board: Super Micro Computer Inc. 370SSE+-OEM1/P3TSSE + * iTCO chipset: ICH2 + * + * Code contributed by: R. Seretny <lkpatches@paypc.com> + * Documentation obtained by R. Seretny from SuperMicro Technical Support + * + * To enable Watchdog function: + * BIOS setup -> Power -> TCO Logic SMI Enable -> Within5Minutes + * This setting enables SMI to clear the watchdog expired flag. + * If BIOS or CPU fail which may cause SMI hang, then system will + * reboot. When application starts to use watchdog function, + * application has to take over the control from SMI. + * + * For P3TSSE, J36 jumper needs to be removed to enable the Watchdog + * function. + * + * Note: The system will reboot when Expire Flag is set TWICE. + * So, if the watchdog timer is 20 seconds, then the maximum hang + * time is about 40 seconds, and the minimum hang time is about + * 20.6 seconds. + */ + +static void supermicro_old_pre_start(unsigned long acpibase) +{ + unsigned long val32; + + /* 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); /* Needed to activate watchdog */ +} + +static void supermicro_old_pre_stop(unsigned long acpibase) +{ + unsigned long val32; + + /* Bit 13: TCO_EN -> 1 = Enables the TCO logic to generate SMI# */ + val32 = inl(SMI_EN); + val32 |= 0x00002000; /* Turn on SMI clearing watchdog */ + outl(val32, SMI_EN); /* Needed to deactivate watchdog */ +} + +/* + * Vendor Support: 2 + * Board: Super Micro Computer Inc. P4SBx, P4DPx + * iTCO chipset: ICH4 + * + * Code contributed by: R. Seretny <lkpatches@paypc.com> + * Documentation obtained by R. Seretny from SuperMicro Technical Support + * + * To enable Watchdog function: + * 1. BIOS + * For P4SBx: + * BIOS setup -> Advanced -> Integrated Peripherals -> Watch Dog Feature + * For P4DPx: + * BIOS setup -> Advanced -> I/O Device Configuration -> Watch Dog + * This setting enables or disables Watchdog function. When enabled, the + * default watchdog timer is set to be 5 minutes (about 4m35s). It is + * enough to load and run the OS. The application (service or driver) has + * to take over the control once OS is running up and before watchdog + * expires. + * + * 2. JUMPER + * For P4SBx: JP39 + * For P4DPx: JP37 + * This jumper is used for safety. Closed is enabled. This jumper + * prevents user enables watchdog in BIOS by accident. + * + * To enable Watch Dog function, both BIOS and JUMPER must be enabled. + * + * The documentation lists motherboards P4SBx and P4DPx series as of + * 20-March-2002. However, this code works flawlessly with much newer + * motherboards, such as my X6DHR-8G2 (SuperServer 6014H-82). + * + * The original iTCO driver as written does not actually reset the + * watchdog timer on these machines, as a result they reboot after five + * minutes. + * + * NOTE: You may leave the Watchdog function disabled in the SuperMicro + * BIOS to avoid a "boot-race"... This driver will enable watchdog + * functionality even if it's disabled in the BIOS once the /dev/watchdog + * file is opened. + */ + +/* I/O Port's */ +#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_WATCHENABLE 0x30 /* Watchdog enable: Bit 0: 0=off, 1=on */ + +#define SM_WATCHPAGE 0x87 /* Watchdog unlock 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_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 */ + +static void supermicro_new_unlock_watchdog(void) +{ + /* Write 0x87 to port 0x2e twice */ + outb(SM_WATCHPAGE, SM_REGINDEX); + outb(SM_WATCHPAGE, SM_REGINDEX); + /* Switch to watchdog control page */ + outb(SM_CTLPAGESW, SM_REGINDEX); + outb(SM_CTLPAGE, SM_DATAIO); +} + +static void supermicro_new_lock_watchdog(void) +{ + outb(SM_ENDWATCH, SM_REGINDEX); +} + +static void supermicro_new_pre_start(unsigned int heartbeat) +{ + unsigned int val; + + supermicro_new_unlock_watchdog(); + + /* Watchdog timer setting needs to be in seconds*/ + outb(SM_COUNTMODE, SM_REGINDEX); + val = inb(SM_DATAIO); + val &= 0xF7; + outb(val, SM_DATAIO); + + /* Write heartbeat interval to WDOG */ + outb(SM_WATCHTIMER, SM_REGINDEX); + outb((heartbeat & 255), SM_DATAIO); + + /* Make sure keyboard/mouse interrupts don't interfere */ + outb(SM_RESETCONTROL, SM_REGINDEX); + val = inb(SM_DATAIO); + val &= 0x3f; + outb(val, SM_DATAIO); + + /* enable watchdog by setting bit 0 of Watchdog Enable to 1 */ + outb(SM_WATCHENABLE, SM_REGINDEX); + val = inb(SM_DATAIO); + val |= 0x01; + outb(val, SM_DATAIO); + + supermicro_new_lock_watchdog(); +} + +static void supermicro_new_pre_stop(void) +{ + unsigned int val; + + supermicro_new_unlock_watchdog(); + + /* disable watchdog by setting bit 0 of Watchdog Enable to 0 */ + outb(SM_WATCHENABLE, SM_REGINDEX); + val = inb(SM_DATAIO); + val &= 0xFE; + outb(val, SM_DATAIO); + + supermicro_new_lock_watchdog(); +} + +static void supermicro_new_pre_set_heartbeat(unsigned int heartbeat) +{ + supermicro_new_unlock_watchdog(); + + /* reset watchdog timeout to heartveat value */ + outb(SM_WATCHTIMER, SM_REGINDEX); + outb((heartbeat & 255), SM_DATAIO); + + supermicro_new_lock_watchdog(); +} + +/* + * Vendor Support: 911 + * Board: Some Intel ICHx based motherboards + * iTCO chipset: ICH7+ + * + * Some Intel motherboards have a broken BIOS implementation: i.e. + * the SMI handler clear's the TIMEOUT bit in the TC01_STS register + * and does not reload the time. Thus the TCO watchdog does not reboot + * the system. + * + * These are the conclusions of Andriy Gapon <avg@icyb.net.ua> after + * debugging: the SMI handler is quite simple - it tests value in + * TCO1_CNT against 0x800, i.e. checks TCO_TMR_HLT. If the bit is set + * the handler goes into an infinite loop, apparently to allow the + * second timeout and reboot. Otherwise it simply clears TIMEOUT bit + * in TCO1_STS and that's it. + * So the logic seems to be reversed, because it is hard to see how + * TIMEOUT can get set to 1 and SMI generated when TCO_TMR_HLT is set + * (other than a transitional effect). + * + * The only fix found to get the motherboard(s) to reboot is to put + * the glb_smi_en bit to 0. This is a dirty hack that bypasses the + * broken code by disabling Global SMI. + * + * WARNING: globally disabling SMI could possibly lead to dramatic + * problems, especially on laptops! I.e. various ACPI things where + * SMI is used for communication between OS and firmware. + * + * Don't use this fix if you don't need to!!! + */ + +static void broken_bios_start(unsigned long acpibase) +{ + unsigned long val32; + + val32 = inl(SMI_EN); + /* 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, SMI_EN); +} + +static void broken_bios_stop(unsigned long acpibase) +{ + unsigned long val32; + + val32 = inl(SMI_EN); + /* 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, SMI_EN); +} + +/* + * Generic Support Functions + */ + +void iTCO_vendor_pre_start(unsigned long acpibase, + unsigned int heartbeat) +{ + switch (vendorsupport) { + case SUPERMICRO_OLD_BOARD: + supermicro_old_pre_start(acpibase); + break; + case SUPERMICRO_NEW_BOARD: + supermicro_new_pre_start(heartbeat); + break; + case BROKEN_BIOS: + broken_bios_start(acpibase); + break; + } +} +EXPORT_SYMBOL(iTCO_vendor_pre_start); + +void iTCO_vendor_pre_stop(unsigned long acpibase) +{ + switch (vendorsupport) { + case SUPERMICRO_OLD_BOARD: + supermicro_old_pre_stop(acpibase); + break; + case SUPERMICRO_NEW_BOARD: + supermicro_new_pre_stop(); + break; + case BROKEN_BIOS: + broken_bios_stop(acpibase); + break; + } +} +EXPORT_SYMBOL(iTCO_vendor_pre_stop); + +void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat) +{ + if (vendorsupport == SUPERMICRO_NEW_BOARD) + supermicro_new_pre_set_heartbeat(heartbeat); +} +EXPORT_SYMBOL(iTCO_vendor_pre_keepalive); + +void iTCO_vendor_pre_set_heartbeat(unsigned int heartbeat) +{ + if (vendorsupport == SUPERMICRO_NEW_BOARD) + supermicro_new_pre_set_heartbeat(heartbeat); +} +EXPORT_SYMBOL(iTCO_vendor_pre_set_heartbeat); + +int iTCO_vendor_check_noreboot_on(void) +{ + switch (vendorsupport) { + case SUPERMICRO_OLD_BOARD: + return 0; + default: + return 1; + } +} +EXPORT_SYMBOL(iTCO_vendor_check_noreboot_on); + +static int __init iTCO_vendor_init_module(void) +{ + pr_info("vendor-support=%d\n", vendorsupport); + return 0; +} + +static void __exit iTCO_vendor_exit_module(void) +{ + pr_info("Module Unloaded\n"); +} + +module_init(iTCO_vendor_init_module); +module_exit(iTCO_vendor_exit_module); + +MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>, " + "R. Seretny <lkpatches@paypc.com>"); +MODULE_DESCRIPTION("Intel TCO Vendor Specific WatchDog Timer Driver Support"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c new file mode 100644 index 00000000..9fecb956 --- /dev/null +++ b/drivers/watchdog/iTCO_wdt.c @@ -0,0 +1,1008 @@ +/* + * intel TCO Watchdog Driver + * + * (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 + * 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. + * + * The TCO watchdog is implemented in the following I/O controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * document number 290655-003, 290677-014: 82801AA (ICH), 82801AB (ICHO) + * document number 290687-002, 298242-027: 82801BA (ICH2) + * document number 290733-003, 290739-013: 82801CA (ICH3-S) + * document number 290716-001, 290718-007: 82801CAM (ICH3-M) + * document number 290744-001, 290745-025: 82801DB (ICH4) + * document number 252337-001, 252663-008: 82801DBM (ICH4-M) + * document number 273599-001, 273645-002: 82801E (C-ICH) + * document number 252516-001, 252517-028: 82801EB (ICH5), 82801ER (ICH5R) + * document number 300641-004, 300884-013: 6300ESB + * document number 301473-002, 301474-026: 82801F (ICH6) + * document number 313082-001, 313075-006: 631xESB, 632xESB + * document number 307013-003, 307014-024: 82801G (ICH7) + * document number 322896-001, 322897-001: NM10 + * document number 313056-003, 313057-017: 82801H (ICH8) + * document number 316972-004, 316973-012: 82801I (ICH9) + * document number 319973-002, 319974-002: 82801J (ICH10) + * document number 322169-001, 322170-003: 5 Series, 3400 Series (PCH) + * document number 320066-003, 320257-008: EP80597 (IICH) + * document number 324645-001, 324646-001: Cougar Point (CPT) + * document number TBD : Patsburg (PBG) + * document number TBD : DH89xxCC + * document number TBD : Panther Point + * document number TBD : Lynx Point + */ + +/* + * 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.07" + +/* 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/watchdog.h> /* For the watchdog specific items */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/fs.h> /* For file operations */ +#include <linux/platform_device.h> /* For platform_driver framework */ +#include <linux/pci.h> /* For pci functions */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <linux/io.h> /* For inb/outb/... */ + +#include "iTCO_vendor.h" + +/* 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_631XESB, /* 631xESB/632xESB */ + TCO_ICH7, /* ICH7 & ICH7R */ + TCO_ICH7DH, /* ICH7DH */ + TCO_ICH7M, /* ICH7-M & ICH7-U */ + TCO_ICH7MDH, /* ICH7-M DH */ + TCO_NM10, /* NM10 */ + TCO_ICH8, /* ICH8 & ICH8R */ + TCO_ICH8DH, /* ICH8DH */ + TCO_ICH8DO, /* ICH8DO */ + TCO_ICH8M, /* ICH8M */ + TCO_ICH8ME, /* ICH8M-E */ + TCO_ICH9, /* ICH9 */ + TCO_ICH9R, /* ICH9R */ + TCO_ICH9DH, /* ICH9DH */ + TCO_ICH9DO, /* ICH9DO */ + TCO_ICH9M, /* ICH9M */ + TCO_ICH9ME, /* ICH9M-E */ + TCO_ICH10, /* ICH10 */ + TCO_ICH10R, /* ICH10R */ + TCO_ICH10D, /* ICH10D */ + TCO_ICH10DO, /* ICH10DO */ + TCO_PCH, /* PCH Desktop Full Featured */ + TCO_PCHM, /* PCH Mobile Full Featured */ + TCO_P55, /* P55 */ + TCO_PM55, /* PM55 */ + TCO_H55, /* H55 */ + TCO_QM57, /* QM57 */ + TCO_H57, /* H57 */ + TCO_HM55, /* HM55 */ + TCO_Q57, /* Q57 */ + TCO_HM57, /* HM57 */ + TCO_PCHMSFF, /* PCH Mobile SFF Full Featured */ + TCO_QS57, /* QS57 */ + TCO_3400, /* 3400 */ + TCO_3420, /* 3420 */ + TCO_3450, /* 3450 */ + TCO_EP80579, /* EP80579 */ + TCO_CPT, /* Cougar Point */ + TCO_CPTD, /* Cougar Point Desktop */ + TCO_CPTM, /* Cougar Point Mobile */ + TCO_PBG, /* Patsburg */ + TCO_DH89XXCC, /* DH89xxCC */ + TCO_PPT, /* Panther Point */ + TCO_LPT, /* Lynx Point */ +}; + +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}, + {"631xESB/632xESB", 2}, + {"ICH7 or ICH7R", 2}, + {"ICH7DH", 2}, + {"ICH7-M or ICH7-U", 2}, + {"ICH7-M DH", 2}, + {"NM10", 2}, + {"ICH8 or ICH8R", 2}, + {"ICH8DH", 2}, + {"ICH8DO", 2}, + {"ICH8M", 2}, + {"ICH8M-E", 2}, + {"ICH9", 2}, + {"ICH9R", 2}, + {"ICH9DH", 2}, + {"ICH9DO", 2}, + {"ICH9M", 2}, + {"ICH9M-E", 2}, + {"ICH10", 2}, + {"ICH10R", 2}, + {"ICH10D", 2}, + {"ICH10DO", 2}, + {"PCH Desktop Full Featured", 2}, + {"PCH Mobile Full Featured", 2}, + {"P55", 2}, + {"PM55", 2}, + {"H55", 2}, + {"QM57", 2}, + {"H57", 2}, + {"HM55", 2}, + {"Q57", 2}, + {"HM57", 2}, + {"PCH Mobile SFF Full Featured", 2}, + {"QS57", 2}, + {"3400", 2}, + {"3420", 2}, + {"3450", 2}, + {"EP80579", 2}, + {"Cougar Point", 2}, + {"Cougar Point Desktop", 2}, + {"Cougar Point Mobile", 2}, + {"Patsburg", 2}, + {"DH89xxCC", 2}, + {"Panther Point", 2}, + {"Lynx Point", 2}, + {NULL, 0} +}; + +/* + * 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 DEFINE_PCI_DEVICE_TABLE(iTCO_wdt_pci_tbl) = { + { PCI_VDEVICE(INTEL, 0x2410), TCO_ICH}, + { PCI_VDEVICE(INTEL, 0x2420), TCO_ICH0}, + { PCI_VDEVICE(INTEL, 0x2440), TCO_ICH2}, + { PCI_VDEVICE(INTEL, 0x244c), TCO_ICH2M}, + { PCI_VDEVICE(INTEL, 0x2480), TCO_ICH3}, + { PCI_VDEVICE(INTEL, 0x248c), TCO_ICH3M}, + { PCI_VDEVICE(INTEL, 0x24c0), TCO_ICH4}, + { PCI_VDEVICE(INTEL, 0x24cc), TCO_ICH4M}, + { PCI_VDEVICE(INTEL, 0x2450), TCO_CICH}, + { PCI_VDEVICE(INTEL, 0x24d0), TCO_ICH5}, + { PCI_VDEVICE(INTEL, 0x25a1), TCO_6300ESB}, + { PCI_VDEVICE(INTEL, 0x2640), TCO_ICH6}, + { PCI_VDEVICE(INTEL, 0x2641), TCO_ICH6M}, + { PCI_VDEVICE(INTEL, 0x2642), TCO_ICH6W}, + { PCI_VDEVICE(INTEL, 0x2670), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2671), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2672), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2673), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2674), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2675), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2676), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2677), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2678), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x2679), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267a), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267b), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267c), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267d), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267e), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x267f), TCO_631XESB}, + { PCI_VDEVICE(INTEL, 0x27b8), TCO_ICH7}, + { PCI_VDEVICE(INTEL, 0x27b0), TCO_ICH7DH}, + { PCI_VDEVICE(INTEL, 0x27b9), TCO_ICH7M}, + { PCI_VDEVICE(INTEL, 0x27bd), TCO_ICH7MDH}, + { PCI_VDEVICE(INTEL, 0x27bc), TCO_NM10}, + { PCI_VDEVICE(INTEL, 0x2810), TCO_ICH8}, + { PCI_VDEVICE(INTEL, 0x2812), TCO_ICH8DH}, + { PCI_VDEVICE(INTEL, 0x2814), TCO_ICH8DO}, + { PCI_VDEVICE(INTEL, 0x2815), TCO_ICH8M}, + { PCI_VDEVICE(INTEL, 0x2811), TCO_ICH8ME}, + { PCI_VDEVICE(INTEL, 0x2918), TCO_ICH9}, + { PCI_VDEVICE(INTEL, 0x2916), TCO_ICH9R}, + { PCI_VDEVICE(INTEL, 0x2912), TCO_ICH9DH}, + { PCI_VDEVICE(INTEL, 0x2914), TCO_ICH9DO}, + { PCI_VDEVICE(INTEL, 0x2919), TCO_ICH9M}, + { PCI_VDEVICE(INTEL, 0x2917), TCO_ICH9ME}, + { PCI_VDEVICE(INTEL, 0x3a18), TCO_ICH10}, + { PCI_VDEVICE(INTEL, 0x3a16), TCO_ICH10R}, + { PCI_VDEVICE(INTEL, 0x3a1a), TCO_ICH10D}, + { PCI_VDEVICE(INTEL, 0x3a14), TCO_ICH10DO}, + { PCI_VDEVICE(INTEL, 0x3b00), TCO_PCH}, + { PCI_VDEVICE(INTEL, 0x3b01), TCO_PCHM}, + { PCI_VDEVICE(INTEL, 0x3b02), TCO_P55}, + { PCI_VDEVICE(INTEL, 0x3b03), TCO_PM55}, + { PCI_VDEVICE(INTEL, 0x3b06), TCO_H55}, + { PCI_VDEVICE(INTEL, 0x3b07), TCO_QM57}, + { PCI_VDEVICE(INTEL, 0x3b08), TCO_H57}, + { PCI_VDEVICE(INTEL, 0x3b09), TCO_HM55}, + { PCI_VDEVICE(INTEL, 0x3b0a), TCO_Q57}, + { PCI_VDEVICE(INTEL, 0x3b0b), TCO_HM57}, + { PCI_VDEVICE(INTEL, 0x3b0d), TCO_PCHMSFF}, + { PCI_VDEVICE(INTEL, 0x3b0f), TCO_QS57}, + { PCI_VDEVICE(INTEL, 0x3b12), TCO_3400}, + { PCI_VDEVICE(INTEL, 0x3b14), TCO_3420}, + { PCI_VDEVICE(INTEL, 0x3b16), TCO_3450}, + { PCI_VDEVICE(INTEL, 0x5031), TCO_EP80579}, + { PCI_VDEVICE(INTEL, 0x1c41), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c42), TCO_CPTD}, + { PCI_VDEVICE(INTEL, 0x1c43), TCO_CPTM}, + { PCI_VDEVICE(INTEL, 0x1c44), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c45), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c46), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c47), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c48), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c49), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4a), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4b), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4c), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4d), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4e), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c4f), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c50), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c51), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c52), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c53), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c54), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c55), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c56), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c57), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c58), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c59), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5a), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5b), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5c), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5d), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5e), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1c5f), TCO_CPT}, + { PCI_VDEVICE(INTEL, 0x1d40), TCO_PBG}, + { PCI_VDEVICE(INTEL, 0x1d41), TCO_PBG}, + { PCI_VDEVICE(INTEL, 0x2310), TCO_DH89XXCC}, + { PCI_VDEVICE(INTEL, 0x1e40), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e41), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e42), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e43), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e44), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e45), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e46), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e47), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e48), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e49), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4a), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4b), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4c), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4d), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4e), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e4f), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e50), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e51), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e52), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e53), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e54), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e55), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e56), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e57), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e58), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e59), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5a), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5b), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5c), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5d), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5e), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x1e5f), TCO_PPT}, + { PCI_VDEVICE(INTEL, 0x8c40), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c41), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c42), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c43), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c44), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c45), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c46), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c47), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c48), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c49), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4a), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4b), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4c), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4d), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4e), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c4f), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c50), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c51), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c52), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c53), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c54), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c55), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c56), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c57), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c58), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c59), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5a), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5b), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5c), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5d), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5e), TCO_LPT}, + { PCI_VDEVICE(INTEL, 0x8c5f), TCO_LPT}, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, iTCO_wdt_pci_tbl); + +/* Address definitions for the TCO */ +/* TCO base address */ +#define TCOBASE (iTCO_wdt_private.ACPIBASE + 0x60) +/* SMI Control and Enable Register */ +#define SMI_EN (iTCO_wdt_private.ACPIBASE + 0x30) + +#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 */ + /* TCO version/generation */ + unsigned int iTCO_version; + /* The device's ACPIBASE address (TCOBASE = ACPIBASE+0x60) */ + unsigned long ACPIBASE; + /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2)*/ + unsigned long __iomem *gcs; + /* the lock for io operations */ + spinlock_t io_lock; + /* the PCI-device */ + struct pci_dev *pdev; +} iTCO_wdt_private; + +/* the watchdog platform device */ +static struct platform_device *iTCO_wdt_platform_device; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. " + "5..76 (TCO v1) or 3..614 (TCO v2), default=" + __MODULE_STRING(WATCHDOG_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 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 internal timer is stored as ticks which decrement + * every 0.6 seconds */ + return (seconds * 10) / 6; +} + +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); + val32 |= 0x00000020; + writel(val32, iTCO_wdt_private.gcs); + } else if (iTCO_wdt_private.iTCO_version == 1) { + pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); + val32 |= 0x00000002; + pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); + } +} + +static int iTCO_wdt_unset_NO_REBOOT_bit(void) +{ + int ret = 0; + u32 val32; + + /* Unset the NO_REBOOT bit: this enables reboots */ + if (iTCO_wdt_private.iTCO_version == 2) { + val32 = readl(iTCO_wdt_private.gcs); + val32 &= 0xffffffdf; + writel(val32, iTCO_wdt_private.gcs); + + val32 = readl(iTCO_wdt_private.gcs); + if (val32 & 0x00000020) + ret = -EIO; + } else if (iTCO_wdt_private.iTCO_version == 1) { + pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); + val32 &= 0xfffffffd; + pci_write_config_dword(iTCO_wdt_private.pdev, 0xd4, val32); + + pci_read_config_dword(iTCO_wdt_private.pdev, 0xd4, &val32); + if (val32 & 0x00000002) + ret = -EIO; + } + + return ret; /* returns: 0 = OK, -EIO = Error */ +} + +static int iTCO_wdt_start(void) +{ + unsigned int val; + + spin_lock(&iTCO_wdt_private.io_lock); + + iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); + + /* disable chipset's NO_REBOOT bit */ + if (iTCO_wdt_unset_NO_REBOOT_bit()) { + spin_unlock(&iTCO_wdt_private.io_lock); + 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; + outw(val, TCO1_CNT); + val = inw(TCO1_CNT); + spin_unlock(&iTCO_wdt_private.io_lock); + + if (val & 0x0800) + return -1; + return 0; +} + +static int iTCO_wdt_stop(void) +{ + unsigned int val; + + spin_lock(&iTCO_wdt_private.io_lock); + + iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); + + /* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */ + val = inw(TCO1_CNT); + val |= 0x0800; + outw(val, TCO1_CNT); + val = inw(TCO1_CNT); + + /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ + iTCO_wdt_set_NO_REBOOT_bit(); + + spin_unlock(&iTCO_wdt_private.io_lock); + + if ((val & 0x0800) == 0) + return -1; + return 0; +} + +static int iTCO_wdt_keepalive(void) +{ + spin_lock(&iTCO_wdt_private.io_lock); + + iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); + + /* Reload the timer by writing to the TCO Timer Counter register */ + 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); + } + + spin_unlock(&iTCO_wdt_private.io_lock); + return 0; +} + +static int iTCO_wdt_set_heartbeat(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)) || + ((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) { + spin_lock(&iTCO_wdt_private.io_lock); + val16 = inw(TCOv2_TMR); + val16 &= 0xfc00; + val16 |= tmrval; + outw(val16, TCOv2_TMR); + val16 = inw(TCOv2_TMR); + spin_unlock(&iTCO_wdt_private.io_lock); + + if ((val16 & 0x3ff) != tmrval) + return -EINVAL; + } else if (iTCO_wdt_private.iTCO_version == 1) { + spin_lock(&iTCO_wdt_private.io_lock); + val8 = inb(TCOv1_TMR); + val8 &= 0xc0; + val8 |= (tmrval & 0xff); + outb(val8, TCOv1_TMR); + val8 = inb(TCOv1_TMR); + spin_unlock(&iTCO_wdt_private.io_lock); + + if ((val8 & 0x3f) != tmrval) + return -EINVAL; + } + + heartbeat = t; + return 0; +} + +static int iTCO_wdt_get_timeleft(int *time_left) +{ + unsigned int val16; + unsigned char val8; + + /* read the TCO Timer */ + 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; + } 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; +} + +/* + * /dev/watchdog handling + */ + +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; + + /* + * Reload and activate timer + */ + iTCO_wdt_start(); + return nonseekable_open(inode, file); +} + +static int iTCO_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (expect_release == 42) { + iTCO_wdt_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + iTCO_wdt_keepalive(); + } + clear_bit(0, &is_active); + expect_release = 0; + return 0; +} + +static ssize_t iTCO_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; + + /* note: just in case someone wrote the magic + character five months ago... */ + expect_release = 0; + + /* scan to see whether or not we got the + magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + iTCO_wdt_keepalive(); + } + return len; +} + +static long iTCO_wdt_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 = 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_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_KEEPALIVE: + iTCO_wdt_keepalive(); + return 0; + + 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; + } +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations iTCO_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = iTCO_wdt_write, + .unlocked_ioctl = iTCO_wdt_ioctl, + .open = iTCO_wdt_open, + .release = iTCO_wdt_release, +}; + +static struct miscdevice iTCO_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &iTCO_wdt_fops, +}; + +/* + * Init & exit routines + */ + +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; + + /* + * 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 + */ + pci_read_config_dword(pdev, 0x40, &base_address); + base_address &= 0x0000ff80; + if (base_address == 0x00000000) { + /* Something's wrong here, ACPIBASE has to be set */ + pr_err("failed to get TCOBASE address, device disabled by hardware/BIOS\n"); + 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); + if ((base_address & 1) == 0) { + pr_err("RCBA is disabled by hardware/BIOS, device disabled\n"); + ret = -ENODEV; + goto out; + } + RCBA = base_address & 0xffffc000; + iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4); + } + + /* Check chipset's NO_REBOOT bit */ + if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) { + pr_info("unable to reset NO_REBOOT flag, device disabled by hardware/BIOS\n"); + ret = -ENODEV; /* Cannot reset NO_REBOOT bit */ + goto out_unmap; + } + + /* Set the NO_REBOOT bit to prevent later reboots, just for sure */ + iTCO_wdt_set_NO_REBOOT_bit(); + + /* The TCO logic uses the TCO_EN bit in the SMI_EN register */ + if (!request_region(SMI_EN, 4, "iTCO_wdt")) { + pr_err("I/O address 0x%04lx already in use, device disabled\n", + SMI_EN); + ret = -EIO; + goto out_unmap; + } + 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); + } + + /* The TCO I/O registers reside in a 32-byte range pointed to + by the TCOBASE value */ + if (!request_region(TCOBASE, 0x20, "iTCO_wdt")) { + pr_err("I/O address 0x%04lx already in use, device disabled\n", + TCOBASE); + ret = -EIO; + goto unreg_smi_en; + } + + pr_info("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); + + /* Clear out the (probably old) status */ + 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(); + + /* 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); + pr_info("timeout value out of range, using %d\n", heartbeat); + } + + ret = misc_register(&iTCO_wdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_region; + } + + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +unreg_region: + release_region(TCOBASE, 0x20); +unreg_smi_en: + release_region(SMI_EN, 4); +out_unmap: + if (iTCO_wdt_private.iTCO_version == 2) + iounmap(iTCO_wdt_private.gcs); +out: + 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); + release_region(SMI_EN, 4); + 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 ret = -ENODEV; + 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) { + found++; + ret = iTCO_wdt_init(pdev, ent, dev); + if (!ret) + break; + } + } + + if (!found) + pr_info("No device detected\n"); + + return ret; +} + +static int __devexit iTCO_wdt_remove(struct platform_device *dev) +{ + if (iTCO_wdt_private.ACPIBASE) + iTCO_wdt_cleanup(); + + return 0; +} + +static void iTCO_wdt_shutdown(struct platform_device *dev) +{ + iTCO_wdt_stop(); +} + +static struct platform_driver iTCO_wdt_driver = { + .probe = iTCO_wdt_probe, + .remove = __devexit_p(iTCO_wdt_remove), + .shutdown = iTCO_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init iTCO_wdt_init_module(void) +{ + int err; + + 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); + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(iTCO_wdt_init_module); +module_exit(iTCO_wdt_cleanup_module); + +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); diff --git a/drivers/watchdog/ib700wdt.c b/drivers/watchdog/ib700wdt.c new file mode 100644 index 00000000..184c0bfc --- /dev/null +++ b/drivers/watchdog/ib700wdt.c @@ -0,0 +1,387 @@ +/* + * IB700 Single Board Computer WDT driver + * + * (c) Copyright 2001 Charles Howes <chowes@vsol.net> + * + * Based on advantechwdt.c which is based on acquirewdt.c which + * is based on wdt.c. + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Added timeout module option to override default + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +static struct platform_device *ibwdt_platform_device; +static unsigned long ibwdt_is_open; +static DEFINE_SPINLOCK(ibwdt_lock); +static char expect_close; + +/* Module information */ +#define DRV_NAME "ib700wdt" + +/* + * + * Watchdog Timer Configuration + * + * The function of the watchdog timer is to reset the system + * automatically and is defined at I/O port 0443H. To enable the + * watchdog timer and allow the system to reset, write I/O port 0443H. + * To disable the timer, write I/O port 0441H for the system to stop the + * watchdog function. The timer has a tolerance of 20% for its + * intervals. + * + * The following describes how the timer should be programmed. + * + * Enabling Watchdog: + * MOV AX,000FH (Choose the values from 0 to F) + * MOV DX,0443H + * OUT DX,AX + * + * Disabling Watchdog: + * MOV AX,000FH (Any value is fine.) + * MOV DX,0441H + * OUT DX,AX + * + * Watchdog timer control table: + * Level Value Time/sec | Level Value Time/sec + * 1 F 0 | 9 7 16 + * 2 E 2 | 10 6 18 + * 3 D 4 | 11 5 20 + * 4 C 6 | 12 4 22 + * 5 B 8 | 13 3 24 + * 6 A 10 | 14 2 26 + * 7 9 12 | 15 1 28 + * 8 8 14 | 16 0 30 + * + */ + +#define WDT_STOP 0x441 +#define WDT_START 0x443 + +/* Default timeout */ +#define WATCHDOG_TIMEOUT 30 /* 30 seconds +/- 20% */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 0<= timeout <=30, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + + +/* + * Watchdog Operations + */ + +static void ibwdt_ping(void) +{ + int wd_margin = 15 - ((timeout + 1) / 2); + + spin_lock(&ibwdt_lock); + + /* Write a watchdog value */ + outb_p(wd_margin, WDT_START); + + spin_unlock(&ibwdt_lock); +} + +static void ibwdt_disable(void) +{ + spin_lock(&ibwdt_lock); + outb_p(0, WDT_STOP); + spin_unlock(&ibwdt_lock); +} + +static int ibwdt_set_heartbeat(int t) +{ + if (t < 0 || t > 30) + return -EINVAL; + + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t ibwdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + ibwdt_ping(); + } + return count; +} + +static long ibwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int new_margin; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "IB700 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + ibwdt_disable(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + ibwdt_ping(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + ibwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (ibwdt_set_heartbeat(new_margin)) + return -EINVAL; + ibwdt_ping(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + return 0; +} + +static int ibwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &ibwdt_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate */ + ibwdt_ping(); + return nonseekable_open(inode, file); +} + +static int ibwdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + ibwdt_disable(); + } else { + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); + ibwdt_ping(); + } + clear_bit(0, &ibwdt_is_open); + expect_close = 0; + return 0; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations ibwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ibwdt_write, + .unlocked_ioctl = ibwdt_ioctl, + .open = ibwdt_open, + .release = ibwdt_close, +}; + +static struct miscdevice ibwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ibwdt_fops, +}; + +/* + * Init & exit routines + */ + +static int __devinit ibwdt_probe(struct platform_device *dev) +{ + int res; + +#if WDT_START != WDT_STOP + if (!request_region(WDT_STOP, 1, "IB700 WDT")) { + pr_err("STOP method I/O %X is not available\n", WDT_STOP); + res = -EIO; + goto out_nostopreg; + } +#endif + + if (!request_region(WDT_START, 1, "IB700 WDT")) { + pr_err("START method I/O %X is not available\n", WDT_START); + res = -EIO; + goto out_nostartreg; + } + + /* Check that the heartbeat value is within it's range ; + * if not reset to the default */ + if (ibwdt_set_heartbeat(timeout)) { + ibwdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 0<=x<=30, using %d\n", timeout); + } + + res = misc_register(&ibwdt_miscdev); + if (res) { + pr_err("failed to register misc device\n"); + goto out_nomisc; + } + return 0; + +out_nomisc: + release_region(WDT_START, 1); +out_nostartreg: +#if WDT_START != WDT_STOP + release_region(WDT_STOP, 1); +#endif +out_nostopreg: + return res; +} + +static int __devexit ibwdt_remove(struct platform_device *dev) +{ + misc_deregister(&ibwdt_miscdev); + release_region(WDT_START, 1); +#if WDT_START != WDT_STOP + release_region(WDT_STOP, 1); +#endif + return 0; +} + +static void ibwdt_shutdown(struct platform_device *dev) +{ + /* Turn the WDT off if we have a soft shutdown */ + ibwdt_disable(); +} + +static struct platform_driver ibwdt_driver = { + .probe = ibwdt_probe, + .remove = __devexit_p(ibwdt_remove), + .shutdown = ibwdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME, + }, +}; + +static int __init ibwdt_init(void) +{ + int err; + + 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)) { + err = PTR_ERR(ibwdt_platform_device); + goto unreg_platform_driver; + } + + return 0; + +unreg_platform_driver: + platform_driver_unregister(&ibwdt_driver); + return err; +} + +static void __exit ibwdt_exit(void) +{ + platform_device_unregister(ibwdt_platform_device); + platform_driver_unregister(&ibwdt_driver); + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(ibwdt_init); +module_exit(ibwdt_exit); + +MODULE_AUTHOR("Charles Howes <chowes@vsol.net>"); +MODULE_DESCRIPTION("IB700 SBC watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +/* end of ib700wdt.c */ diff --git a/drivers/watchdog/ibmasr.c b/drivers/watchdog/ibmasr.c new file mode 100644 index 00000000..bc3fb8fe --- /dev/null +++ b/drivers/watchdog/ibmasr.c @@ -0,0 +1,422 @@ +/* + * IBM Automatic Server Restart driver. + * + * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru> + * + * Based on driver written by Pete Reynolds. + * Copyright (c) IBM Corporation, 1998-2004. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/dmi.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +enum { + ASMTYPE_UNKNOWN, + ASMTYPE_TOPAZ, + ASMTYPE_JASPER, + ASMTYPE_PEARL, + ASMTYPE_JUNIPER, + ASMTYPE_SPRUCE, +}; + +#define TOPAZ_ASR_REG_OFFSET 4 +#define TOPAZ_ASR_TOGGLE 0x40 +#define TOPAZ_ASR_DISABLE 0x80 + +/* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */ +#define PEARL_BASE 0xe04 +#define PEARL_WRITE 0xe06 +#define PEARL_READ 0xe07 + +#define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */ +#define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */ + +/* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */ +#define JASPER_ASR_REG_OFFSET 0x38 + +#define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */ +#define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ + +#define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */ +#define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */ +#define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */ + +#define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */ +#define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */ +#define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */ + + +static bool nowayout = WATCHDOG_NOWAYOUT; + +static unsigned long asr_is_open; +static char asr_expect_close; + +static unsigned int asr_type, asr_base, asr_length; +static unsigned int asr_read_addr, asr_write_addr; +static unsigned char asr_toggle_mask, asr_disable_mask; +static DEFINE_SPINLOCK(asr_lock); + +static void __asr_toggle(void) +{ + unsigned char reg; + + reg = inb(asr_read_addr); + + outb(reg & ~asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg | asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg & ~asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); +} + +static void asr_toggle(void) +{ + spin_lock(&asr_lock); + __asr_toggle(); + spin_unlock(&asr_lock); +} + +static void asr_enable(void) +{ + unsigned char reg; + + spin_lock(&asr_lock); + if (asr_type == ASMTYPE_TOPAZ) { + /* asr_write_addr == asr_read_addr */ + reg = inb(asr_read_addr); + outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE), + asr_read_addr); + } else { + /* + * First make sure the hardware timer is reset by toggling + * ASR hardware timer line. + */ + __asr_toggle(); + + reg = inb(asr_read_addr); + outb(reg & ~asr_disable_mask, asr_write_addr); + } + reg = inb(asr_read_addr); + spin_unlock(&asr_lock); +} + +static void asr_disable(void) +{ + unsigned char reg; + + spin_lock(&asr_lock); + reg = inb(asr_read_addr); + + if (asr_type == ASMTYPE_TOPAZ) + /* asr_write_addr == asr_read_addr */ + outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE, + asr_read_addr); + else { + outb(reg | asr_toggle_mask, asr_write_addr); + reg = inb(asr_read_addr); + + outb(reg | asr_disable_mask, asr_write_addr); + } + reg = inb(asr_read_addr); + spin_unlock(&asr_lock); +} + +static int __init asr_get_base_address(void) +{ + unsigned char low, high; + const char *type = ""; + + asr_length = 1; + + switch (asr_type) { + case ASMTYPE_TOPAZ: + /* SELECT SuperIO CHIP FOR QUERYING + (WRITE 0x07 TO BOTH 0x2E and 0x2F) */ + outb(0x07, 0x2e); + outb(0x07, 0x2f); + + /* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */ + outb(0x60, 0x2e); + high = inb(0x2f); + + /* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */ + outb(0x61, 0x2e); + low = inb(0x2f); + + asr_base = (high << 16) | low; + asr_read_addr = asr_write_addr = + asr_base + TOPAZ_ASR_REG_OFFSET; + asr_length = 5; + + break; + + case ASMTYPE_JASPER: + type = "Jaspers "; +#if 0 + u32 r; + /* Suggested fix */ + pdev = pci_get_bus_and_slot(0, DEVFN(0x1f, 0)); + if (pdev == NULL) + return -ENODEV; + pci_read_config_dword(pdev, 0x58, &r); + asr_base = r & 0xFFFE; + pci_dev_put(pdev); +#else + /* FIXME: need to use pci_config_lock here, + but it's not exported */ + +/* spin_lock_irqsave(&pci_config_lock, flags);*/ + + /* Select the SuperIO chip in the PCI I/O port register */ + outl(0x8000f858, 0xcf8); + + /* BUS 0, Slot 1F, fnc 0, offset 58 */ + + /* + * Read the base address for the SuperIO chip. + * Only the lower 16 bits are valid, but the address is word + * aligned so the last bit must be masked off. + */ + asr_base = inl(0xcfc) & 0xfffe; + +/* spin_unlock_irqrestore(&pci_config_lock, flags);*/ +#endif + asr_read_addr = asr_write_addr = + asr_base + JASPER_ASR_REG_OFFSET; + asr_toggle_mask = JASPER_ASR_TOGGLE_MASK; + asr_disable_mask = JASPER_ASR_DISABLE_MASK; + asr_length = JASPER_ASR_REG_OFFSET + 1; + + break; + + case ASMTYPE_PEARL: + type = "Pearls "; + asr_base = PEARL_BASE; + asr_read_addr = PEARL_READ; + asr_write_addr = PEARL_WRITE; + asr_toggle_mask = PEARL_ASR_TOGGLE_MASK; + asr_disable_mask = PEARL_ASR_DISABLE_MASK; + asr_length = 4; + break; + + case ASMTYPE_JUNIPER: + type = "Junipers "; + asr_base = JUNIPER_BASE_ADDRESS; + asr_read_addr = asr_write_addr = asr_base; + asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK; + asr_disable_mask = JUNIPER_ASR_DISABLE_MASK; + break; + + case ASMTYPE_SPRUCE: + type = "Spruce's "; + asr_base = SPRUCE_BASE_ADDRESS; + asr_read_addr = asr_write_addr = asr_base; + asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK; + asr_disable_mask = SPRUCE_ASR_DISABLE_MASK; + break; + } + + if (!request_region(asr_base, asr_length, "ibmasr")) { + pr_err("address %#x already in use\n", asr_base); + return -EBUSY; + } + + pr_info("found %sASR @ addr %#x\n", type, asr_base); + + return 0; +} + + +static ssize_t asr_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + asr_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + asr_expect_close = 42; + } + } + asr_toggle(); + } + return count; +} + +static long asr_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "IBM ASR", + }; + void __user *argp = (void __user *)arg; + int __user *p = argp; + int heartbeat; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + asr_disable(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + asr_enable(); + asr_toggle(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + asr_toggle(); + return 0; + /* + * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT + * and WDIOC_GETTIMEOUT always returns 256. + */ + case WDIOC_GETTIMEOUT: + heartbeat = 256; + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +static int asr_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &asr_is_open)) + return -EBUSY; + + asr_toggle(); + asr_enable(); + + return nonseekable_open(inode, file); +} + +static int asr_release(struct inode *inode, struct file *file) +{ + if (asr_expect_close == 42) + asr_disable(); + else { + pr_crit("unexpected close, not stopping watchdog!\n"); + asr_toggle(); + } + clear_bit(0, &asr_is_open); + asr_expect_close = 0; + return 0; +} + +static const struct file_operations asr_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = asr_write, + .unlocked_ioctl = asr_ioctl, + .open = asr_open, + .release = asr_release, +}; + +static struct miscdevice asr_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &asr_fops, +}; + + +struct ibmasr_id { + const char *desc; + int type; +}; + +static struct ibmasr_id __initdata ibmasr_id_table[] = { + { "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ }, + { "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL }, + { "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER }, + { "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER }, + { "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE }, + { NULL } +}; + +static int __init ibmasr_init(void) +{ + struct ibmasr_id *id; + int rc; + + for (id = ibmasr_id_table; id->desc; id++) { + if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) { + asr_type = id->type; + break; + } + } + + if (!asr_type) + return -ENODEV; + + rc = asr_get_base_address(); + if (rc) + return rc; + + rc = misc_register(&asr_miscdev); + if (rc < 0) { + release_region(asr_base, asr_length); + pr_err("failed to register misc device\n"); + return rc; + } + + return 0; +} + +static void __exit ibmasr_exit(void) +{ + if (!nowayout) + asr_disable(); + + misc_deregister(&asr_miscdev); + + release_region(asr_base, asr_length); +} + +module_init(ibmasr_init); +module_exit(ibmasr_exit); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +MODULE_DESCRIPTION("IBM Automatic Server Restart driver"); +MODULE_AUTHOR("Andrey Panin"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c new file mode 100644 index 00000000..7a2b734f --- /dev/null +++ b/drivers/watchdog/imx2_wdt.c @@ -0,0 +1,362 @@ +/* + * Watchdog driver for IMX2 and later processors + * + * Copyright (C) 2010 Wolfram Sang, Pengutronix e.K. <w.sang@pengutronix.de> + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> +#include <linux/clk.h> +#include <linux/fs.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <mach/hardware.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_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) + +#define IMX2_WDT_STATUS_OPEN 0 +#define IMX2_WDT_STATUS_STARTED 1 +#define IMX2_WDT_EXPECT_CLOSE 2 + +static struct { + struct clk *clk; + void __iomem *base; + unsigned timeout; + unsigned long status; + struct timer_list timer; /* Pings the watchdog when closed */ +} imx2_wdt; + +static struct miscdevice imx2_wdt_miscdev; + +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(void) +{ + u16 val = __raw_readw(imx2_wdt.base + IMX2_WDT_WCR); + + /* 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(imx2_wdt.timeout); + + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); + + /* enable the watchdog */ + val |= IMX2_WDT_WCR_WDE; + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); +} + +static inline void imx2_wdt_ping(void) +{ + __raw_writew(IMX2_WDT_SEQ1, imx2_wdt.base + IMX2_WDT_WSR); + __raw_writew(IMX2_WDT_SEQ2, imx2_wdt.base + IMX2_WDT_WSR); +} + +static void imx2_wdt_timer_ping(unsigned long arg) +{ + /* ping it every imx2_wdt.timeout / 2 seconds to prevent reboot */ + imx2_wdt_ping(); + mod_timer(&imx2_wdt.timer, jiffies + imx2_wdt.timeout * HZ / 2); +} + +static void imx2_wdt_start(void) +{ + if (!test_and_set_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + /* at our first start we enable clock and do initialisations */ + clk_enable(imx2_wdt.clk); + + imx2_wdt_setup(); + } else /* delete the timer that pings the watchdog after close */ + del_timer_sync(&imx2_wdt.timer); + + /* Watchdog is enabled - time to reload the timeout value */ + imx2_wdt_ping(); +} + +static void imx2_wdt_stop(void) +{ + /* 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(0); +} + +static void imx2_wdt_set_timeout(int new_timeout) +{ + u16 val = __raw_readw(imx2_wdt.base + IMX2_WDT_WCR); + + /* set the new timeout value in the WSR */ + val &= ~IMX2_WDT_WCR_WT; + val |= WDOG_SEC_TO_COUNT(new_timeout); + __raw_writew(val, imx2_wdt.base + IMX2_WDT_WCR); +} + +static int imx2_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(IMX2_WDT_STATUS_OPEN, &imx2_wdt.status)) + return -EBUSY; + + imx2_wdt_start(); + return nonseekable_open(inode, file); +} + +static int imx2_wdt_close(struct inode *inode, struct file *file) +{ + if (test_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status) && !nowayout) + imx2_wdt_stop(); + else { + dev_crit(imx2_wdt_miscdev.parent, + "Unexpected close: Expect reboot!\n"); + imx2_wdt_ping(); + } + + clear_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status); + clear_bit(IMX2_WDT_STATUS_OPEN, &imx2_wdt.status); + return 0; +} + +static long imx2_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_value; + u16 val; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &imx2_wdt_info, + sizeof(struct watchdog_info)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + return put_user(0, p); + + case WDIOC_GETBOOTSTATUS: + val = __raw_readw(imx2_wdt.base + IMX2_WDT_WRSR); + new_value = val & IMX2_WDT_WRSR_TOUT ? WDIOF_CARDRESET : 0; + return put_user(new_value, p); + + case WDIOC_KEEPALIVE: + imx2_wdt_ping(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_value, p)) + return -EFAULT; + if ((new_value < 1) || (new_value > IMX2_WDT_MAX_TIME)) + return -EINVAL; + imx2_wdt_set_timeout(new_value); + imx2_wdt.timeout = new_value; + imx2_wdt_ping(); + + /* Fallthrough to return current value */ + case WDIOC_GETTIMEOUT: + return put_user(imx2_wdt.timeout, p); + + default: + return -ENOTTY; + } +} + +static ssize_t imx2_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + size_t i; + char c; + + if (len == 0) /* Can we see this even ? */ + return 0; + + clear_bit(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.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(IMX2_WDT_EXPECT_CLOSE, &imx2_wdt.status); + } + + imx2_wdt_ping(); + return len; +} + +static const struct file_operations imx2_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = imx2_wdt_ioctl, + .open = imx2_wdt_open, + .release = imx2_wdt_close, + .write = imx2_wdt_write, +}; + +static struct miscdevice imx2_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &imx2_wdt_fops, +}; + +static int __init imx2_wdt_probe(struct platform_device *pdev) +{ + int ret; + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "can't get device resources\n"); + return -ENODEV; + } + + imx2_wdt.base = devm_request_and_ioremap(&pdev->dev, res); + if (!imx2_wdt.base) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + imx2_wdt.clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(imx2_wdt.clk)) { + dev_err(&pdev->dev, "can't get Watchdog clock\n"); + return PTR_ERR(imx2_wdt.clk); + } + + imx2_wdt.timeout = clamp_t(unsigned, timeout, 1, IMX2_WDT_MAX_TIME); + if (imx2_wdt.timeout != timeout) + dev_warn(&pdev->dev, "Initial timeout out of range! " + "Clamped from %u to %u\n", timeout, imx2_wdt.timeout); + + setup_timer(&imx2_wdt.timer, imx2_wdt_timer_ping, 0); + + imx2_wdt_miscdev.parent = &pdev->dev; + ret = misc_register(&imx2_wdt_miscdev); + if (ret) + goto fail; + + dev_info(&pdev->dev, + "IMX2+ Watchdog Timer enabled. timeout=%ds (nowayout=%d)\n", + imx2_wdt.timeout, nowayout); + return 0; + +fail: + imx2_wdt_miscdev.parent = NULL; + clk_put(imx2_wdt.clk); + return ret; +} + +static int __exit imx2_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&imx2_wdt_miscdev); + + if (test_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + del_timer_sync(&imx2_wdt.timer); + + dev_crit(imx2_wdt_miscdev.parent, + "Device removed: Expect reboot!\n"); + } else + clk_put(imx2_wdt.clk); + + imx2_wdt_miscdev.parent = NULL; + return 0; +} + +static void imx2_wdt_shutdown(struct platform_device *pdev) +{ + if (test_bit(IMX2_WDT_STATUS_STARTED, &imx2_wdt.status)) { + /* we are running, we need to delete the timer but will give + * max timeout before reboot will take place */ + del_timer_sync(&imx2_wdt.timer); + imx2_wdt_set_timeout(IMX2_WDT_MAX_TIME); + imx2_wdt_ping(); + + dev_crit(imx2_wdt_miscdev.parent, + "Device shutdown: Expect reboot!\n"); + } +} + +static const struct of_device_id imx2_wdt_dt_ids[] = { + { .compatible = "fsl,imx21-wdt", }, + { /* sentinel */ } +}; + +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, + }, +}; + +static int __init imx2_wdt_init(void) +{ + return platform_driver_probe(&imx2_wdt_driver, imx2_wdt_probe); +} +module_init(imx2_wdt_init); + +static void __exit imx2_wdt_exit(void) +{ + platform_driver_unregister(&imx2_wdt_driver); +} +module_exit(imx2_wdt_exit); + +MODULE_AUTHOR("Wolfram Sang"); +MODULE_DESCRIPTION("Watchdog driver for IMX2 and later"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/watchdog/indydog.c b/drivers/watchdog/indydog.c new file mode 100644 index 00000000..6d90f7a2 --- /dev/null +++ b/drivers/watchdog/indydog.c @@ -0,0 +1,217 @@ +/* + * 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@lxorguk.ukuu.org.uk> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/uaccess.h> +#include <asm/sgi/mc.h> + +static unsigned long indydog_alive; +static DEFINE_SPINLOCK(indydog_lock); + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void indydog_start(void) +{ + u32 mc_ctrl0; + + spin_lock(&indydog_lock); + mc_ctrl0 = sgimc->cpuctrl0; + mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG; + sgimc->cpuctrl0 = mc_ctrl0; + spin_unlock(&indydog_lock); +} + +static void indydog_stop(void) +{ + u32 mc_ctrl0; + + spin_lock(&indydog_lock); + + mc_ctrl0 = sgimc->cpuctrl0; + mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG; + sgimc->cpuctrl0 = mc_ctrl0; + spin_unlock(&indydog_lock); + + pr_info("Stopped watchdog timer\n"); +} + +static void indydog_ping(void) +{ + sgimc->watchdogt = 0; +} + +/* + * Allow only one person to hold it open + */ +static int indydog_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &indydog_alive)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate timer */ + indydog_start(); + indydog_ping(); + + pr_info("Started watchdog timer\n"); + + return nonseekable_open(inode, file); +} + +static int indydog_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. + * Lock it in if it's a module and we defined ...NOWAYOUT */ + if (!nowayout) + indydog_stop(); /* Turn the WDT off */ + clear_bit(0, &indydog_alive); + return 0; +} + +static ssize_t indydog_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + /* Refresh the timer. */ + if (len) + indydog_ping(); + return len; +} + +static long indydog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int options, retval = -EINVAL; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 0, + .identity = "Hardware Watchdog for SGI IP22", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user((struct watchdog_info *)arg, + &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int *)arg); + case WDIOC_SETOPTIONS: + { + if (get_user(options, (int *)arg)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) { + indydog_stop(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + indydog_start(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + indydog_ping(); + return 0; + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_TIMEOUT, (int *)arg); + default: + return -ENOTTY; + } +} + +static int indydog_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + indydog_stop(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +static const struct file_operations indydog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = indydog_write, + .unlocked_ioctl = indydog_ioctl, + .open = indydog_open, + .release = indydog_release, +}; + +static struct miscdevice indydog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &indydog_fops, +}; + +static struct notifier_block indydog_notifier = { + .notifier_call = indydog_notify_sys, +}; + +static int __init watchdog_init(void) +{ + int ret; + + ret = register_reboot_notifier(&indydog_notifier); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + return ret; + } + + ret = misc_register(&indydog_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&indydog_notifier); + return ret; + } + + pr_info("Hardware Watchdog Timer for SGI IP22: 0.3\n"); + + return 0; +} + +static void __exit watchdog_exit(void) +{ + misc_deregister(&indydog_miscdev); + unregister_reboot_notifier(&indydog_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); +MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/intel_scu_watchdog.c b/drivers/watchdog/intel_scu_watchdog.c new file mode 100644 index 00000000..9dda2d08 --- /dev/null +++ b/drivers/watchdog/intel_scu_watchdog.c @@ -0,0 +1,568 @@ +/* + * 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/mrst.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_pre_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 */ + hw_pre_value = ioread32(watchdog_device.timer_load_count_addr); + hw_pre_value = hw_pre_value & 0xFFFF0000; + + /* 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 (!mrst_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_ALIAS_MISCDEV(WATCHDOG_MINOR); +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 00000000..f3ac608d --- /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 new file mode 100644 index 00000000..d964faf1 --- /dev/null +++ b/drivers/watchdog/iop_wdt.c @@ -0,0 +1,262 @@ +/* + * drivers/char/watchdog/iop_wdt.c + * + * WDT driver for Intel I/O Processors + * Copyright (C) 2005, Intel Corporation. + * + * Based on ixp4xx driver, Copyright 2004 (c) MontaVista, Software, Inc. + * + * 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. + * + * 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. + * + * Curt E Bruns <curt.e.bruns@intel.com> + * Peter Milne <peter.milne@d-tacq.com> + * 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> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> + +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 +#define WDT_ENABLED 2 + +static unsigned long iop_watchdog_timeout(void) +{ + return (0xffffffffUL / get_iop_tick_rate()); +} + +/** + * wdt_supports_disable - determine if we are accessing a iop13xx watchdog + * or iop3xx by whether it has a disable command + */ +static int wdt_supports_disable(void) +{ + int can_disable; + + if (IOP_WDTCR_EN_ARM != IOP_WDTCR_DIS_ARM) + can_disable = 1; + else + can_disable = 0; + + return can_disable; +} + +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 */ +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); + spin_unlock(&wdt_lock); + pr_info("Disabled\n"); + return 0; + } else + return 1; +} + +static int iop_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(); + 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, + 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_enable(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "iop watchdog", +}; + +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(argp, &ident, sizeof(ident))) + ret = -EFAULT; + else + ret = 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, argp); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, argp); + break; + + case WDIOC_SETOPTIONS: + if (get_user(options, (int *)arg)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + if (!nowayout) { + if (wdt_disable() == 0) { + set_bit(WDT_OK_TO_CLOSE, &wdt_status); + ret = 0; + } else + ret = -ENXIO; + } 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; +} + +static int iop_wdt_release(struct inode *inode, struct file *file) +{ + int state = 1; + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) + if (test_bit(WDT_ENABLED, &wdt_status)) + state = wdt_disable(); + + /* if the timer is not disabled reload and notify that we are still + * going down + */ + if (state != 0) { + wdt_enable(); + pr_crit("Device closed unexpectedly - reset in %lu seconds\n", + iop_watchdog_timeout()); + } + + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + +static const struct file_operations iop_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = iop_wdt_write, + .unlocked_ioctl = iop_wdt_ioctl, + .open = iop_wdt_open, + .release = iop_wdt_release, +}; + +static struct miscdevice iop_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &iop_wdt_fops, +}; + +static int __init iop_wdt_init(void) +{ + int ret; + + /* check if the reset was caused by the watchdog timer */ + boot_status = (read_rcsr() & IOP_RCSR_WDT) ? WDIOF_CARDRESET : 0; + + /* Configure Watchdog Timeout to cause an Internal Bus (IB) Reset + * NOTE: An IB Reset will Reset both cores in the IOP342 + */ + 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; +} + +static void __exit iop_wdt_exit(void) +{ + misc_deregister(&iop_wdt_miscdev); +} + +module_init(iop_wdt_init); +module_exit(iop_wdt_exit); + +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 new file mode 100644 index 00000000..f4cce6d6 --- /dev/null +++ b/drivers/watchdog/it8712f_wdt.c @@ -0,0 +1,455 @@ +/* + * IT8712F "Smart Guardian" Watchdog support + * + * Copyright (c) 2006-2007 Jorge Boncompte - DTI2 <jorge@dti2.net> + * + * Based on info and code taken from: + * + * drivers/char/watchdog/scx200_wdt.c + * drivers/hwmon/it87.c + * IT8712F EC-LPC I/O Preliminary Specification 0.8.2 + * IT8712F EC-LPC I/O Preliminary Specification 0.9.3 + * + * 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 author(s) of this software shall not be held liable for damages + * of any nature resulting due to the use of this software. This + * software is provided AS-IS with no warranties. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/fs.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/ioport.h> + +#define 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 bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +static unsigned long wdt_open; +static unsigned expect_close; +static unsigned char revision; + +/* Dog Food address - We use the game port address */ +static unsigned short address; + +#define REG 0x2e /* The register to read/write */ +#define VAL 0x2f /* The value to read/write */ + +#define LDN 0x07 /* Register: Logical device select */ +#define DEVID 0x20 /* Register: Device ID */ +#define DEVREV 0x22 /* Register: Device Revision */ +#define ACT_REG 0x30 /* LDN Register: Activation */ +#define BASE_REG 0x60 /* LDN Register: Base address */ + +#define IT8712F_DEVID 0x8712 + +#define LDN_GPIO 0x07 /* GPIO and Watch Dog Timer */ +#define LDN_GAME 0x09 /* Game Port */ + +#define WDT_CONTROL 0x71 /* WDT Register: Control */ +#define WDT_CONFIG 0x72 /* WDT Register: Configuration */ +#define WDT_TIMEOUT 0x73 /* WDT Register: Timeout Value */ + +#define WDT_RESET_GAME 0x10 /* Reset timer on read or write to game port */ +#define WDT_RESET_KBD 0x20 /* Reset timer on keyboard interrupt */ +#define WDT_RESET_MOUSE 0x40 /* Reset timer on mouse interrupt */ +#define WDT_RESET_CIR 0x80 /* Reset timer on consumer IR interrupt */ + +#define WDT_UNIT_SEC 0x80 /* If 0 in MINUTES */ + +#define WDT_OUT_PWROK 0x10 /* Pulse PWROK on timeout */ +#define WDT_OUT_KRST 0x40 /* Pulse reset on timeout */ + +static int wdt_control_reg = WDT_RESET_GAME; +module_param(wdt_control_reg, int, 0); +MODULE_PARM_DESC(wdt_control_reg, "Value to write to watchdog control " + "register. The default WDT_RESET_GAME resets the timer on " + "game port reads that this driver generates. You can also " + "use KBD, MOUSE or CIR if you have some external way to " + "generate those interrupts."); + +static int superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static void superio_outb(int val, int reg) +{ + outb(reg, REG); + outb(val, VAL); +} + +static int superio_inw(int reg) +{ + int val; + outb(reg++, REG); + val = inb(VAL) << 8; + outb(reg, REG); + val |= inb(VAL); + return val; +} + +static inline void superio_select(int ldn) +{ + outb(LDN, REG); + outb(ldn, VAL); +} + +static inline int superio_enter(void) +{ + /* + * Try to reserve REG and REG + 1 for exclusive access. + */ + if (!request_muxed_region(REG, 2, NAME)) + return -EBUSY; + + outb(0x87, REG); + outb(0x01, REG); + outb(0x55, REG); + outb(0x55, REG); + return 0; +} + +static inline void superio_exit(void) +{ + outb(0x02, REG); + outb(0x02, VAL); + release_region(REG, 2); +} + +static inline void it8712f_wdt_ping(void) +{ + if (wdt_control_reg & WDT_RESET_GAME) + inb(address); +} + +static void it8712f_wdt_update_margin(void) +{ + int config = WDT_OUT_KRST | WDT_OUT_PWROK; + int units = margin; + + /* Switch to minutes precision if the configured margin + * value does not fit within the register width. + */ + if (units <= max_units) { + config |= WDT_UNIT_SEC; /* else UNIT is MINUTES */ + pr_info("timer margin %d seconds\n", units); + } else { + units /= 60; + pr_info("timer margin %d minutes\n", units); + } + superio_outb(config, WDT_CONFIG); + + if (revision >= 0x08) + superio_outb(units >> 8, WDT_TIMEOUT + 1); + superio_outb(units, WDT_TIMEOUT); +} + +static int it8712f_wdt_get_status(void) +{ + if (superio_inb(WDT_CONTROL) & 0x01) + return WDIOF_CARDRESET; + else + return 0; +} + +static int it8712f_wdt_enable(void) +{ + int ret = superio_enter(); + if (ret) + return ret; + + pr_debug("enabling watchdog timer\n"); + superio_select(LDN_GPIO); + + superio_outb(wdt_control_reg, WDT_CONTROL); + + it8712f_wdt_update_margin(); + + superio_exit(); + + it8712f_wdt_ping(); + + return 0; +} + +static int it8712f_wdt_disable(void) +{ + int ret = superio_enter(); + if (ret) + return ret; + + pr_debug("disabling watchdog timer\n"); + superio_select(LDN_GPIO); + + superio_outb(0, WDT_CONFIG); + superio_outb(0, WDT_CONTROL); + if (revision >= 0x08) + superio_outb(0, WDT_TIMEOUT + 1); + superio_outb(0, WDT_TIMEOUT); + + superio_exit(); + return 0; +} + +static int it8712f_wdt_notify(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_HALT || code == SYS_POWER_OFF) + if (!nowayout) + it8712f_wdt_disable(); + + return NOTIFY_DONE; +} + +static struct notifier_block it8712f_wdt_notifier = { + .notifier_call = it8712f_wdt_notify, +}; + +static ssize_t it8712f_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* check for a magic close character */ + if (len) { + size_t i; + + it8712f_wdt_ping(); + + expect_close = 0; + for (i = 0; i < len; ++i) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + return len; +} + +static long it8712f_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .identity = "IT8712F Watchdog", + .firmware_version = 1, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + }; + int value; + int ret; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + ret = superio_enter(); + if (ret) + return ret; + superio_select(LDN_GPIO); + + value = it8712f_wdt_get_status(); + + superio_exit(); + + return put_user(value, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + it8712f_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(value, p)) + return -EFAULT; + if (value < 1) + return -EINVAL; + if (value > (max_units * 60)) + return -EINVAL; + margin = value; + ret = superio_enter(); + if (ret) + return ret; + superio_select(LDN_GPIO); + + it8712f_wdt_update_margin(); + + superio_exit(); + it8712f_wdt_ping(); + /* Fall through */ + case WDIOC_GETTIMEOUT: + if (put_user(margin, p)) + return -EFAULT; + return 0; + default: + return -ENOTTY; + } +} + +static int it8712f_wdt_open(struct inode *inode, struct file *file) +{ + int ret; + /* only allow one at a time */ + if (test_and_set_bit(0, &wdt_open)) + return -EBUSY; + + ret = it8712f_wdt_enable(); + if (ret) + return ret; + return nonseekable_open(inode, file); +} + +static int it8712f_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close != 42) { + pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); + } else if (!nowayout) { + if (it8712f_wdt_disable()) + pr_warn("Watchdog disable failed\n"); + } + expect_close = 0; + clear_bit(0, &wdt_open); + + return 0; +} + +static const struct file_operations it8712f_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = it8712f_wdt_write, + .unlocked_ioctl = it8712f_wdt_ioctl, + .open = it8712f_wdt_open, + .release = it8712f_wdt_release, +}; + +static struct miscdevice it8712f_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &it8712f_wdt_fops, +}; + +static int __init it8712f_wdt_find(unsigned short *address) +{ + int err = -ENODEV; + int chip_type; + int ret = superio_enter(); + if (ret) + return ret; + + chip_type = superio_inw(DEVID); + if (chip_type != IT8712F_DEVID) + goto exit; + + superio_select(LDN_GAME); + superio_outb(1, ACT_REG); + if (!(superio_inb(ACT_REG) & 0x01)) { + pr_err("Device not activated, skipping\n"); + goto exit; + } + + *address = superio_inw(BASE_REG); + if (*address == 0) { + pr_err("Base address not set, skipping\n"); + goto exit; + } + + err = 0; + revision = superio_inb(DEVREV) & 0x0f; + + /* Later revisions have 16-bit values per datasheet 0.9.1 */ + if (revision >= 0x08) + max_units = 65535; + + if (margin > (max_units * 60)) + margin = (max_units * 60); + + pr_info("Found IT%04xF chip revision %d - using DogFood address 0x%x\n", + chip_type, revision, *address); + +exit: + superio_exit(); + return err; +} + +static int __init it8712f_wdt_init(void) +{ + int err = 0; + + if (it8712f_wdt_find(&address)) + return -ENODEV; + + if (!request_region(address, 1, "IT8712F Watchdog")) { + pr_warn("watchdog I/O region busy\n"); + return -EBUSY; + } + + err = it8712f_wdt_disable(); + if (err) { + pr_err("unable to disable watchdog timer\n"); + goto out; + } + + err = register_reboot_notifier(&it8712f_wdt_notifier); + if (err) { + pr_err("unable to register reboot notifier\n"); + goto out; + } + + err = misc_register(&it8712f_wdt_miscdev); + if (err) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, err); + goto reboot_out; + } + + return 0; + + +reboot_out: + unregister_reboot_notifier(&it8712f_wdt_notifier); +out: + release_region(address, 1); + return err; +} + +static void __exit it8712f_wdt_exit(void) +{ + misc_deregister(&it8712f_wdt_miscdev); + unregister_reboot_notifier(&it8712f_wdt_notifier); + release_region(address, 1); +} + +module_init(it8712f_wdt_init); +module_exit(it8712f_wdt_exit); diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c new file mode 100644 index 00000000..8a741bcb --- /dev/null +++ b/drivers/watchdog/it87_wdt.c @@ -0,0 +1,772 @@ +/* + * 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 and IT8726. + * + * 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_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 */ + +/* 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 */ +#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 + +static unsigned int base, gpact, ciract, max_units, chip_type; +static unsigned long wdt_status; + +static int nogameport = DEFAULT_NOGAMEPORT; +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(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 + /* The timer reloads with around 5 msec delay */ + outb(0x55, CIR_DR(base)); + 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 + 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: + 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 (!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); + } + } + + 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_GP, &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)\n", + chip_type, chip_rev, timeout, + nowayout, testmode, exclusive, nogameport); + + superio_exit(); + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region: + release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 8); + if (!test_bit(WDTS_USE_GP, &wdt_status)) { + 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 { + superio_select(CIR); + superio_outb(ciract, ACTREG); + } + superio_exit(); + } + + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(base, test_bit(WDTS_USE_GP, &wdt_status) ? 1 : 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ixp2000_wdt.c b/drivers/watchdog/ixp2000_wdt.c new file mode 100644 index 00000000..3f047a58 --- /dev/null +++ b/drivers/watchdog/ixp2000_wdt.c @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#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/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> + +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ +static unsigned long wdt_status; +static DEFINE_SPINLOCK(wdt_lock); + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static unsigned long wdt_tick_rate; + +static void wdt_enable(void) +{ + spin_lock(&wdt_lock); + 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); + spin_unlock(&wdt_lock); +} + +static void wdt_disable(void) +{ + spin_lock(&wdt_lock); + ixp2000_reg_write(IXP2000_T4_CTL, 0); + spin_unlock(&wdt_lock); +} + +static void wdt_keepalive(void) +{ + spin_lock(&wdt_lock); + ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); + spin_unlock(&wdt_lock); +} + +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 const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "IXP2000 Watchdog", +}; + +static long ixp2000_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 <= 0 || time > 60) { + ret = -EINVAL; + break; + } + + heartbeat = time; + wdt_keepalive(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + 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 + 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); + + return 0; +} + + +static const struct file_operations ixp2000_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ixp2000_wdt_write, + .unlocked_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) { + pr_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, 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/ixp4xx_wdt.c b/drivers/watchdog/ixp4xx_wdt.c new file mode 100644 index 00000000..5580b4ff --- /dev/null +++ b/drivers/watchdog/ixp4xx_wdt.c @@ -0,0 +1,212 @@ +/* + * drivers/char/watchdog/ixp4xx_wdt.c + * + * Watchdog driver for Intel IXP4xx network processors + * + * 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/uaccess.h> +#include <mach/hardware.h> + +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) +{ + 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) +{ + 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) +{ + 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 +ixp4xx_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_enable(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "IXP4xx Watchdog", +}; + + +static long ixp4xx_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(boot_status, (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 <= 0 || time > 60) { + ret = -EINVAL; + break; + } + + heartbeat = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + } + return ret; +} + +static int ixp4xx_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"); + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static const struct file_operations ixp4xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ixp4xx_wdt_write, + .unlocked_ioctl = ixp4xx_wdt_ioctl, + .open = ixp4xx_wdt_open, + .release = ixp4xx_wdt_release, +}; + +static struct miscdevice ixp4xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ixp4xx_wdt_fops, +}; + +static int __init ixp4xx_wdt_init(void) +{ + int ret; + + if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { + pr_err("Rev. A0 IXP42x CPU detected - watchdog disabled\n"); + + return -ENODEV; + } + 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; +} + +static void __exit ixp4xx_wdt_exit(void) +{ + misc_deregister(&ixp4xx_wdt_miscdev); +} + + +module_init(ixp4xx_wdt_init); +module_exit(ixp4xx_wdt_exit); + +MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); +MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); + +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 00000000..978615ef --- /dev/null +++ b/drivers/watchdog/jz4740_wdt.c @@ -0,0 +1,226 @@ +/* + * 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/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.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 __devinit 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_request_and_ioremap(&pdev->dev, res); + if (drvdata->base == NULL) { + ret = -EBUSY; + goto err_out; + } + + drvdata->rtc_clk = clk_get(NULL, "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 __devexit 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 = __devexit_p(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_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:jz4740-wdt"); diff --git a/drivers/watchdog/ks8695_wdt.c b/drivers/watchdog/ks8695_wdt.c new file mode 100644 index 00000000..59e75d9a --- /dev/null +++ b/drivers/watchdog/ks8695_wdt.c @@ -0,0 +1,315 @@ +/* + * Watchdog driver for Kendin/Micrel KS8695. + * + * (C) 2007 Andrew Victor + * + * 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/errno.h> +#include <linux/fs.h> +#include <linux/init.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/io.h> +#include <linux/uaccess.h> +#include <mach/hardware.h> +#include <mach/regs-timer.h> + +#define WDT_DEFAULT_TIME 5 /* seconds */ +#define WDT_MAX_TIME 171 /* seconds */ + +static int wdt_time = WDT_DEFAULT_TIME; +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) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif + + +static unsigned long ks8695wdt_busy; +static DEFINE_SPINLOCK(ks8695_lock); + +/* ......................................................................... */ + +/* + * Disable the watchdog. + */ +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 inline void ks8695_wdt_start(void) +{ + unsigned long tmcon; + 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); + + /* program timer0 */ + __raw_writel(tval | T0TC_WATCHDOG, KS8695_TMR_VA + KS8695_T0TC); + + /* 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 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); +} + +/* + * Change the watchdog time interval. + */ +static int ks8695_wdt_settimeout(int new_time) +{ + /* + * 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. + */ + 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. */ + wdt_time = new_time; + return 0; +} + +/* ......................................................................... */ + +/* + * Watchdog device is opened, and watchdog starts running. + */ +static int ks8695_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &ks8695wdt_busy)) + return -EBUSY; + + ks8695_wdt_start(); + return nonseekable_open(inode, file); +} + +/* + * Close the watchdog device. + * If CONFIG_WATCHDOG_NOWAYOUT is NOT defined then the watchdog is also + * disabled. + */ +static int ks8695_wdt_close(struct inode *inode, struct file *file) +{ + /* Disable the watchdog when file is closed */ + if (!nowayout) + ks8695_wdt_stop(); + clear_bit(0, &ks8695wdt_busy); + return 0; +} + +static const struct watchdog_info ks8695_wdt_info = { + .identity = "ks8695 watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, +}; + +/* + * Handle commands from user-space. + */ +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_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 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) +{ + ks8695_wdt_reload(); /* pat the watchdog */ + return len; +} + +/* ......................................................................... */ + +static const struct file_operations ks8695wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = ks8695_wdt_ioctl, + .open = ks8695_wdt_open, + .release = ks8695_wdt_close, + .write = ks8695_wdt_write, +}; + +static struct miscdevice ks8695wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ks8695wdt_fops, +}; + +static int __devinit ks8695wdt_probe(struct platform_device *pdev) +{ + int res; + + if (ks8695wdt_miscdev.parent) + return -EBUSY; + ks8695wdt_miscdev.parent = &pdev->dev; + + res = misc_register(&ks8695wdt_miscdev); + if (res) + return res; + + pr_info("KS8695 Watchdog Timer enabled (%d seconds%s)\n", + wdt_time, nowayout ? ", nowayout" : ""); + return 0; +} + +static int __devexit ks8695wdt_remove(struct platform_device *pdev) +{ + int res; + + res = misc_deregister(&ks8695wdt_miscdev); + if (!res) + ks8695wdt_miscdev.parent = NULL; + + return res; +} + +static void ks8695wdt_shutdown(struct platform_device *pdev) +{ + ks8695_wdt_stop(); +} + +#ifdef CONFIG_PM + +static int ks8695wdt_suspend(struct platform_device *pdev, pm_message_t message) +{ + ks8695_wdt_stop(); + return 0; +} + +static int ks8695wdt_resume(struct platform_device *pdev) +{ + if (ks8695wdt_busy) + ks8695_wdt_start(); + return 0; +} + +#else +#define ks8695wdt_suspend NULL +#define ks8695wdt_resume NULL +#endif + +static struct platform_driver ks8695wdt_driver = { + .probe = ks8695wdt_probe, + .remove = __devexit_p(ks8695wdt_remove), + .shutdown = ks8695wdt_shutdown, + .suspend = ks8695wdt_suspend, + .resume = ks8695wdt_resume, + .driver = { + .name = "ks8695_wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init ks8695_wdt_init(void) +{ + /* 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); + } + return platform_driver_register(&ks8695wdt_driver); +} + +static void __exit ks8695_wdt_exit(void) +{ + platform_driver_unregister(&ks8695wdt_driver); +} + +module_init(ks8695_wdt_init); +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 00000000..a9593a3a --- /dev/null +++ b/drivers/watchdog/lantiq_wdt.c @@ -0,0 +1,260 @@ +/* + * 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/platform_device.h> +#include <linux/uaccess.h> +#include <linux/clk.h> +#include <linux/io.h> + +#include <lantiq.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 __init +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; + } + res = devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), dev_name(&pdev->dev)); + if (!res) { + dev_err(&pdev->dev, "cannot request I/O memory region"); + return -EBUSY; + } + ltq_wdt_membase = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!ltq_wdt_membase) { + dev_err(&pdev->dev, "cannot remap I/O memory region\n"); + return -ENOMEM; + } + + /* we do not need to enable the clock as it is always running */ + clk = clk_get(&pdev->dev, "io"); + WARN_ON(!clk); + ltq_io_region_clk_rate = clk_get_rate(clk); + clk_put(clk); + + if (ltq_reset_cause() == LTQ_RST_CAUSE_WDTRST) + ltq_wdt_bootstatus = WDIOF_CARDRESET; + + return misc_register(<q_wdt_miscdev); +} + +static int __devexit +ltq_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(<q_wdt_miscdev); + + return 0; +} + + +static struct platform_driver ltq_wdt_driver = { + .remove = __devexit_p(ltq_wdt_remove), + .driver = { + .name = "ltq_wdt", + .owner = THIS_MODULE, + }, +}; + +static int __init +init_ltq_wdt(void) +{ + return platform_driver_probe(<q_wdt_driver, ltq_wdt_probe); +} + +static void __exit +exit_ltq_wdt(void) +{ + return platform_driver_unregister(<q_wdt_driver); +} + +module_init(init_ltq_wdt); +module_exit(exit_ltq_wdt); + +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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/m54xx_wdt.c b/drivers/watchdog/m54xx_wdt.c new file mode 100644 index 00000000..663cad86 --- /dev/null +++ b/drivers/watchdog/m54xx_wdt.c @@ -0,0 +1,227 @@ +/* + * drivers/watchdog/m54xx_wdt.c + * + * Watchdog driver for ColdFire MCF547x & MCF548x processors + * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be> + * + * Adapted from the IXP4xx watchdog driver, which carries these notices: + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2004 (c) MontaVista, Software, Inc. + * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/bitops.h> +#include <linux/ioport.h> +#include <linux/uaccess.h> + +#include <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_MBAR + 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_MBAR + MCF_GPT_GMS0); + __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) | + MCF_GPT_GCIR_CNT(0xffff), MCF_MBAR + MCF_GPT_GCIR0); + gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE; + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); +} + +static void wdt_disable(void) +{ + unsigned int gms0; + + /* disable watchdog */ + gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); + gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE); + __raw_writel(gms0, MCF_MBAR + MCF_GPT_GMS0); +} + +static void wdt_keepalive(void) +{ + unsigned int gms0; + + gms0 = __raw_readl(MCF_MBAR + MCF_GPT_GMS0); + gms0 |= MCF_GPT_GMS_OCPW(0xA5); + __raw_writel(gms0, MCF_MBAR + 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_MBAR + 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_MBAR + 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/machzwd.c b/drivers/watchdog/machzwd.c new file mode 100644 index 00000000..bf84f788 --- /dev/null +++ b/drivers/watchdog/machzwd.c @@ -0,0 +1,457 @@ +/* + * MachZ ZF-Logic Watchdog Timer driver for Linux + * + * + * 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 author does NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * Author: Fernando Fuganti <fuganti@conectiva.com.br> + * + * Based on sbc60xxwdt.c by Jakob Oestergaard + * + * + * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the + * following periods: + * wd#1 - 2 seconds; + * wd#2 - 7.2 ms; + * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or + * a system RESET and it starts wd#2 that unconditionally will RESET + * the system when the counter reaches zero. + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +/* ports */ +#define ZF_IOBASE 0x218 +#define INDEX 0x218 +#define DATA_B 0x219 +#define DATA_W 0x21A +#define DATA_D 0x21A + +/* indexes */ /* size */ +#define ZFL_VERSION 0x02 /* 16 */ +#define CONTROL 0x10 /* 16 */ +#define STATUS 0x12 /* 8 */ +#define COUNTER_1 0x0C /* 16 */ +#define COUNTER_2 0x0E /* 8 */ +#define PULSE_LEN 0x0F /* 8 */ + +/* controls */ +#define ENABLE_WD1 0x0001 +#define ENABLE_WD2 0x0002 +#define RESET_WD1 0x0010 +#define RESET_WD2 0x0020 +#define GEN_SCI 0x0100 +#define GEN_NMI 0x0200 +#define GEN_SMI 0x0400 +#define GEN_RESET 0x0800 + + +/* utilities */ + +#define WD1 0 +#define WD2 1 + +#define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } +#define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } +#define zf_get_ZFL_version() zf_readw(ZFL_VERSION) + + +static unsigned short zf_readw(unsigned char port) +{ + outb(port, INDEX); + return inw(DATA_W); +} + + +MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); +MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define PFX "machzwd" + +static const struct watchdog_info zf_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "ZF-Logic watchdog", +}; + + +/* + * action refers to action taken when watchdog resets + * 0 = GEN_RESET + * 1 = GEN_SMI + * 2 = GEN_NMI + * 3 = GEN_SCI + * defaults to GEN_RESET (0) + */ +static int action; +module_param(action, int, 0); +MODULE_PARM_DESC(action, "after watchdog resets, generate: " + "0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); + +static void zf_ping(unsigned long data); + +static int zf_action = GEN_RESET; +static unsigned long zf_is_open; +static char zf_expect_close; +static DEFINE_SPINLOCK(zf_port_lock); +static DEFINE_TIMER(zf_timer, zf_ping, 0, 0); +static unsigned long next_heartbeat; + + +/* timeout for user land heart beat (10 seconds) */ +#define ZF_USER_TIMEO (HZ*10) + +/* timeout for hardware watchdog (~500ms) */ +#define ZF_HW_TIMEO (HZ/2) + +/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ +#define ZF_CTIMEOUT 0xffff + +#ifndef ZF_DEBUG +#define dprintk(format, args...) +#else +#define dprintk(format, args...) \ + pr_debug(":%s:%d: " format, __func__, __LINE__ , ## args) +#endif + + +static inline void zf_set_status(unsigned char new) +{ + zf_writeb(STATUS, new); +} + + +/* CONTROL register functions */ + +static inline unsigned short zf_get_control(void) +{ + return zf_readw(CONTROL); +} + +static inline void zf_set_control(unsigned short new) +{ + zf_writew(CONTROL, new); +} + + +/* WD#? counter functions */ +/* + * Just set counter value + */ + +static inline void zf_set_timer(unsigned short new, unsigned char n) +{ + switch (n) { + case WD1: + zf_writew(COUNTER_1, new); + case WD2: + zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); + default: + return; + } +} + +/* + * stop hardware timer + */ +static void zf_timer_off(void) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + /* stop internal ping */ + del_timer_sync(&zf_timer); + + spin_lock_irqsave(&zf_port_lock, flags); + /* stop watchdog timer */ + ctrl_reg = zf_get_control(); + ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ + ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + pr_info("Watchdog timer is now disabled\n"); +} + + +/* + * start hardware timer + */ +static void zf_timer_on(void) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + spin_lock_irqsave(&zf_port_lock, flags); + + zf_writeb(PULSE_LEN, 0xff); + + zf_set_timer(ZF_CTIMEOUT, WD1); + + /* user land ping */ + next_heartbeat = jiffies + ZF_USER_TIMEO; + + /* start the timer for internal ping */ + mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); + + /* start watchdog timer */ + ctrl_reg = zf_get_control(); + ctrl_reg |= (ENABLE_WD1|zf_action); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + pr_info("Watchdog timer is now enabled\n"); +} + + +static void zf_ping(unsigned long data) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + zf_writeb(COUNTER_2, 0xff); + + if (time_before(jiffies, next_heartbeat)) { + dprintk("time_before: %ld\n", next_heartbeat - jiffies); + /* + * reset event is activated by transition from 0 to 1 on + * RESET_WD1 bit and we assume that it is already zero... + */ + + spin_lock_irqsave(&zf_port_lock, flags); + ctrl_reg = zf_get_control(); + ctrl_reg |= RESET_WD1; + zf_set_control(ctrl_reg); + + /* ...and nothing changes until here */ + ctrl_reg &= ~(RESET_WD1); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + mod_timer(&zf_timer, jiffies + ZF_HW_TIMEO); + } else + pr_crit("I will reset your machine\n"); +} + +static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, + loff_t *ppos) +{ + /* See if we got the magic character */ + if (count) { + /* + * no need to check for close confirmation + * no way to disable watchdog ;) + */ + if (!nowayout) { + size_t ofs; + /* + * note: just in case someone wrote the magic character + * five months ago... + */ + zf_expect_close = 0; + + /* now scan */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') { + zf_expect_close = 42; + dprintk("zf_expect_close = 42\n"); + } + } + } + + /* + * Well, anyhow someone wrote to us, + * we should return that favour + */ + next_heartbeat = jiffies + ZF_USER_TIMEO; + dprintk("user ping at %ld\n", jiffies); + } + return count; +} + +static long zf_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &zf_info, sizeof(zf_info))) + return -EFAULT; + break; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + zf_ping(0); + break; + default: + return -ENOTTY; + } + return 0; +} + +static int zf_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &zf_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + zf_timer_on(); + return nonseekable_open(inode, file); +} + +static int zf_close(struct inode *inode, struct file *file) +{ + if (zf_expect_close == 42) + zf_timer_off(); + else { + del_timer(&zf_timer); + pr_err("device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &zf_is_open); + zf_expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int zf_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + zf_timer_off(); + return NOTIFY_DONE; +} + +static const struct file_operations zf_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = zf_write, + .unlocked_ioctl = zf_ioctl, + .open = zf_open, + .release = zf_close, +}; + +static struct miscdevice zf_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &zf_fops, +}; + + +/* + * The device needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ +static struct notifier_block zf_notifier = { + .notifier_call = zf_notify_sys, +}; + +static void __init zf_show_action(int act) +{ + static const char * const str[] = { "RESET", "SMI", "NMI", "SCI" }; + + pr_info("Watchdog using action = %s\n", str[act]); +} + +static int __init zf_init(void) +{ + int ret; + + pr_info("MachZ ZF-Logic Watchdog driver initializing\n"); + + ret = zf_get_ZFL_version(); + if (!ret || ret == 0xffff) { + pr_warn("no ZF-Logic found\n"); + return -ENODEV; + } + + if (action <= 3 && action >= 0) + zf_action = zf_action >> action; + else + action = 0; + + zf_show_action(action); + + if (!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")) { + pr_err("cannot reserve I/O ports at %d\n", ZF_IOBASE); + ret = -EBUSY; + goto no_region; + } + + ret = register_reboot_notifier(&zf_notifier); + if (ret) { + pr_err("can't register reboot notifier (err=%d)\n", ret); + goto no_reboot; + } + + ret = misc_register(&zf_miscdev); + if (ret) { + pr_err("can't misc_register on minor=%d\n", WATCHDOG_MINOR); + goto no_misc; + } + + zf_set_status(0); + zf_set_control(0); + + return 0; + +no_misc: + unregister_reboot_notifier(&zf_notifier); +no_reboot: + release_region(ZF_IOBASE, 3); +no_region: + return ret; +} + + +static void __exit zf_exit(void) +{ + zf_timer_off(); + + misc_deregister(&zf_miscdev); + unregister_reboot_notifier(&zf_notifier); + release_region(ZF_IOBASE, 3); +} + +module_init(zf_init); +module_exit(zf_exit); diff --git a/drivers/watchdog/max63xx_wdt.c b/drivers/watchdog/max63xx_wdt.c new file mode 100644 index 00000000..8f4a74e9 --- /dev/null +++ b/drivers/watchdog/max63xx_wdt.c @@ -0,0 +1,260 @@ +/* + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.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 <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 __devinit 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_request_and_ioremap(&pdev->dev, wdt_mem); + if (!wdt_base) + return -ENOMEM; + + 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 __devexit 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mixcomwd.c b/drivers/watchdog/mixcomwd.c new file mode 100644 index 00000000..37e4b52d --- /dev/null +++ b/drivers/watchdog/mixcomwd.c @@ -0,0 +1,318 @@ +/* + * MixCom Watchdog: A Simple Hardware Watchdog Device + * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis + * + * Author: Gergely Madarasz <gorgo@itc.hu> + * + * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu> + * + * 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. + * + * Version 0.1 (99/04/15): + * - first version + * + * Version 0.2 (99/06/16): + * - added kernel timer watchdog ping after close + * since the hardware does not support watchdog shutdown + * + * Version 0.3 (99/06/21): + * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls + * + * Version 0.3.1 (99/06/22): + * - allow module removal while internal timer is active, + * print warning about probable reset + * + * Version 0.4 (99/11/15): + * - support for one more type board + * + * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com> + * - added nowayout module option to override + * CONFIG_WATCHDOG_NOWAYOUT + * + * Version 0.6 (2002/04/12): Rob Radez <rob@osinvestor.com> + * - make mixcomwd_opened unsigned, + * removed lock_kernel/unlock_kernel from mixcomwd_release, + * modified ioctl a bit to conform to API + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define VERSION "0.6" +#define WATCHDOG_NAME "mixcomwd" + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/timer.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +/* + * We have two types of cards that can be probed: + * 1) The Mixcom cards: these cards can be found at addresses + * 0x180, 0x280, 0x380 with an additional offset of 0xc10. + * (Or 0xd90, 0xe90, 0xf90). + * 2) The FlashCOM cards: these cards can be set up at + * 0x300 -> 0x378, in 0x8 jumps with an offset of 0x04. + * (Or 0x304 -> 0x37c in 0x8 jumps). + * Each card has it's own ID. + */ +#define MIXCOM_ID 0x11 +#define FLASHCOM_ID 0x18 +static struct { + int ioport; + int id; +} mixcomwd_io_info[] __devinitdata = { + /* The Mixcom cards */ + {0x0d90, MIXCOM_ID}, + {0x0e90, MIXCOM_ID}, + {0x0f90, MIXCOM_ID}, + /* The FlashCOM cards */ + {0x0304, FLASHCOM_ID}, + {0x030c, FLASHCOM_ID}, + {0x0314, FLASHCOM_ID}, + {0x031c, FLASHCOM_ID}, + {0x0324, FLASHCOM_ID}, + {0x032c, FLASHCOM_ID}, + {0x0334, FLASHCOM_ID}, + {0x033c, FLASHCOM_ID}, + {0x0344, FLASHCOM_ID}, + {0x034c, FLASHCOM_ID}, + {0x0354, FLASHCOM_ID}, + {0x035c, FLASHCOM_ID}, + {0x0364, FLASHCOM_ID}, + {0x036c, FLASHCOM_ID}, + {0x0374, FLASHCOM_ID}, + {0x037c, FLASHCOM_ID}, + /* The end of the list */ + {0x0000, 0}, +}; + +static void mixcomwd_timerfun(unsigned long d); + +static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */ + +static int watchdog_port; +static int mixcomwd_timer_alive; +static DEFINE_TIMER(mixcomwd_timer, mixcomwd_timerfun, 0, 0); +static char expect_close; + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void mixcomwd_ping(void) +{ + outb_p(55, watchdog_port); + return; +} + +static void mixcomwd_timerfun(unsigned long d) +{ + mixcomwd_ping(); + mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); +} + +/* + * Allow only one person to hold it open + */ + +static int mixcomwd_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &mixcomwd_opened)) + return -EBUSY; + + mixcomwd_ping(); + + if (nowayout) + /* + * fops_get() code via open() has already done + * a try_module_get() so it is safe to do the + * __module_get(). + */ + __module_get(THIS_MODULE); + else { + if (mixcomwd_timer_alive) { + del_timer(&mixcomwd_timer); + mixcomwd_timer_alive = 0; + } + } + return nonseekable_open(inode, file); +} + +static int mixcomwd_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + if (mixcomwd_timer_alive) { + pr_err("release called while internal timer alive\n"); + return -EBUSY; + } + mixcomwd_timer_alive = 1; + mod_timer(&mixcomwd_timer, jiffies + 5 * HZ); + } else + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); + + clear_bit(0, &mixcomwd_opened); + expect_close = 0; + return 0; +} + + +static ssize_t mixcomwd_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + mixcomwd_ping(); + } + return len; +} + +static long mixcomwd_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int status; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "MixCOM watchdog", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + case WDIOC_GETSTATUS: + status = mixcomwd_opened; + if (!nowayout) + status |= mixcomwd_timer_alive; + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + mixcomwd_ping(); + break; + default: + return -ENOTTY; + } + return 0; +} + +static const struct file_operations mixcomwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mixcomwd_write, + .unlocked_ioctl = mixcomwd_ioctl, + .open = mixcomwd_open, + .release = mixcomwd_release, +}; + +static struct miscdevice mixcomwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mixcomwd_fops, +}; + +static int __init checkcard(int port, int card_id) +{ + int id; + + if (!request_region(port, 1, "MixCOM watchdog")) + return 0; + + id = inb_p(port); + if (card_id == MIXCOM_ID) + id &= 0x3f; + + if (id != card_id) { + release_region(port, 1); + return 0; + } + return 1; +} + +static int __init mixcomwd_init(void) +{ + int i, ret, found = 0; + + for (i = 0; !found && mixcomwd_io_info[i].ioport != 0; i++) { + if (checkcard(mixcomwd_io_info[i].ioport, + mixcomwd_io_info[i].id)) { + found = 1; + watchdog_port = mixcomwd_io_info[i].ioport; + } + } + + if (!found) { + pr_err("No card detected, or port not available\n"); + return -ENODEV; + } + + ret = misc_register(&mixcomwd_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto error_misc_register_watchdog; + } + + pr_info("MixCOM watchdog driver v%s, watchdog port at 0x%3x\n", + VERSION, watchdog_port); + + return 0; + +error_misc_register_watchdog: + release_region(watchdog_port, 1); + watchdog_port = 0x0000; + return ret; +} + +static void __exit mixcomwd_exit(void) +{ + if (!nowayout) { + if (mixcomwd_timer_alive) { + pr_warn("I quit now, hardware will probably reboot!\n"); + del_timer_sync(&mixcomwd_timer); + mixcomwd_timer_alive = 0; + } + } + misc_deregister(&mixcomwd_miscdev); + release_region(watchdog_port, 1); +} + +module_init(mixcomwd_init); +module_exit(mixcomwd_exit); + +MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>"); +MODULE_DESCRIPTION("MixCom Watchdog driver"); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mpc8xxx_wdt.c b/drivers/watchdog/mpc8xxx_wdt.c new file mode 100644 index 00000000..40f7bf1f --- /dev/null +++ b/drivers/watchdog/mpc8xxx_wdt.c @@ -0,0 +1,332 @@ +/* + * 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_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 unsigned int timeout_sec; + +static unsigned long wdt_is_open; +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 void mpc8xxx_wdt_timer_ping(unsigned long arg); +static DEFINE_TIMER(wdt_timer, mpc8xxx_wdt_timer_ping, 0, 0); + +static void mpc8xxx_wdt_timer_ping(unsigned long arg) +{ + mpc8xxx_wdt_keepalive(); + /* We're pinging it twice faster than needed, just to be sure. */ + mod_timer(&wdt_timer, jiffies + HZ * timeout_sec / 2); +} + +static void mpc8xxx_wdt_pr_warn(const char *msg) +{ + pr_crit("%s, expect the %s soon!\n", msg, + reset ? "reset" : "machine check exception"); +} + +static ssize_t mpc8xxx_wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) + mpc8xxx_wdt_keepalive(); + return count; +} + +static int mpc8xxx_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 */ + if (nowayout) + __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); + + del_timer_sync(&wdt_timer); + + return nonseekable_open(inode, file); +} + +static int mpc8xxx_wdt_release(struct inode *inode, struct file *file) +{ + if (!nowayout) + mpc8xxx_wdt_timer_ping(0); + else + mpc8xxx_wdt_pr_warn("watchdog closed"); + clear_bit(0, &wdt_is_open); + return 0; +} + +static long mpc8xxx_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "MPC8xxx", + }; + + 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: + mpc8xxx_wdt_keepalive(); + return 0; + case WDIOC_GETTIMEOUT: + return put_user(timeout_sec, p); + default: + return -ENOTTY; + } +} + +static const struct file_operations mpc8xxx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mpc8xxx_wdt_write, + .unlocked_ioctl = mpc8xxx_wdt_ioctl, + .open = mpc8xxx_wdt_open, + .release = mpc8xxx_wdt_release, +}; + +static struct miscdevice mpc8xxx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mpc8xxx_wdt_fops, +}; + +static const struct of_device_id mpc8xxx_wdt_match[]; +static int __devinit mpc8xxx_wdt_probe(struct platform_device *ofdev) +{ + int ret; + const struct of_device_id *match; + struct device_node *np = ofdev->dev.of_node; + struct mpc8xxx_wdt_type *wdt_type; + u32 freq = fsl_get_sys_freq(); + bool enabled; + + 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; + +#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) + mpc8xxx_wdt_timer_ping(0); + return 0; +err_unmap: + iounmap(wd_base); + wd_base = NULL; + return ret; +} + +static int __devexit mpc8xxx_wdt_remove(struct platform_device *ofdev) +{ + mpc8xxx_wdt_pr_warn("watchdog removed"); + del_timer_sync(&wdt_timer); + misc_deregister(&mpc8xxx_wdt_miscdev); + 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, + }, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc8xxx_wdt_match); + +static struct platform_driver mpc8xxx_wdt_driver = { + .probe = mpc8xxx_wdt_probe, + .remove = __devexit_p(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; + + ret = misc_register(&mpc8xxx_wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c new file mode 100644 index 00000000..7c741dc9 --- /dev/null +++ b/drivers/watchdog/mpcore_wdt.c @@ -0,0 +1,457 @@ +/* + * Watchdog driver for the mpcore watchdog timer + * + * (c) Copyright 2004 ARM Limited + * + * Based on the SoftDog driver: + * (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. + * + * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/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 <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <asm/smp_twd.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_pdev; +static DEFINE_SPINLOCK(wdt_lock); + +#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 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 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 long count; + + spin_lock(&wdt_lock); + /* Assume prescale is set to 256 */ + count = __raw_readl(wdt->base + TWD_WDOG_COUNTER); + count = (0xFFFFFFFFU - count) * (HZ / 5); + count = (count / 256) * mpcore_margin; + + /* Reload the counter */ + writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); + wdt->perturb = wdt->perturb ? 0 : 1; + spin_unlock(&wdt_lock); +} + +static void mpcore_wdt_stop(struct mpcore_wdt *wdt) +{ + spin_lock(&wdt_lock); + writel(0x12345678, wdt->base + TWD_WDOG_DISABLE); + writel(0x87654321, wdt->base + TWD_WDOG_DISABLE); + writel(0x0, wdt->base + TWD_WDOG_CONTROL); + spin_unlock(&wdt_lock); +} + +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_pdev); + + 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 const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "MPcore Watchdog", +}; + +static long mpcore_wdt_ioctl(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_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + uarg.i = 0; + 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_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 *pdev) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(pdev); + + 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, + .unlocked_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 *pdev) +{ + struct mpcore_wdt *wdt; + struct resource *res; + int ret; + + /* We only accept one device, and it must have an id of -1 */ + if (pdev->id != -1) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + wdt = devm_kzalloc(&pdev->dev, sizeof(struct mpcore_wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->dev = &pdev->dev; + wdt->irq = platform_get_irq(pdev, 0); + if (wdt->irq >= 0) { + ret = devm_request_irq(wdt->dev, wdt->irq, mpcore_wdt_fire, 0, + "mpcore_wdt", wdt); + if (ret) { + dev_printk(KERN_ERR, wdt->dev, + "cannot register IRQ%d for watchdog\n", + wdt->irq); + return ret; + } + } + + wdt->base = devm_ioremap(wdt->dev, res->start, resource_size(res)); + if (!wdt->base) + return -ENOMEM; + + mpcore_wdt_miscdev.parent = &pdev->dev; + ret = misc_register(&mpcore_wdt_miscdev); + if (ret) { + dev_printk(KERN_ERR, wdt->dev, + "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + return ret; + } + + mpcore_wdt_stop(wdt); + platform_set_drvdata(pdev, wdt); + mpcore_wdt_pdev = pdev; + + return 0; +} + +static int __devexit mpcore_wdt_remove(struct platform_device *pdev) +{ + platform_set_drvdata(pdev, NULL); + + misc_deregister(&mpcore_wdt_miscdev); + + mpcore_wdt_pdev = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static int mpcore_wdt_suspend(struct platform_device *pdev, pm_message_t msg) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(pdev); + mpcore_wdt_stop(wdt); /* Turn the WDT off */ + return 0; +} + +static int mpcore_wdt_resume(struct platform_device *pdev) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(pdev); + /* re-activate timer */ + if (test_bit(0, &wdt->timer_alive)) + mpcore_wdt_start(wdt); + return 0; +} +#else +#define mpcore_wdt_suspend NULL +#define mpcore_wdt_resume NULL +#endif + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:mpcore_wdt"); + +static struct platform_driver mpcore_wdt_driver = { + .probe = mpcore_wdt_probe, + .remove = __devexit_p(mpcore_wdt_remove), + .suspend = mpcore_wdt_suspend, + .resume = mpcore_wdt_resume, + .shutdown = mpcore_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "mpcore_wdt", + }, +}; + +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); + pr_info("mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", + TIMER_MARGIN); + } + + pr_info("MPcore Watchdog Timer: 0.1. mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n", + 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 new file mode 100644 index 00000000..c29e31d9 --- /dev/null +++ b/drivers/watchdog/mtx-1_wdt.c @@ -0,0 +1,262 @@ +/* + * Driver for the MTX-1 Watchdog. + * + * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, + * All Rights Reserved. + * http://www.4g-systems.biz + * + * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> + * + * 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 Michael Stickel nor 4G Systems admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2005 4G Systems <info@4g-systems.biz> + * + * Release 0.01. + * Author: Michael Stickel michael.stickel@4g-systems.biz + * + * Release 0.02. + * Author: Florian Fainelli florian@openwrt.org + * use the Linux watchdog/timer APIs + * + * The Watchdog is configured to reset the MTX-1 + * if it is not triggered for 100 seconds. + * It should not be triggered more often than 1.6 seconds. + * + * A timer triggers the watchdog every 5 seconds, until + * it is opened for the first time. After the first open + * it MUST be triggered every 2..95 seconds. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/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 <linux/io.h> +#include <linux/uaccess.h> +#include <linux/gpio.h> + +#include <asm/mach-au1x00/au1000.h> + +#define MTX1_WDT_INTERVAL (5 * HZ) + +static int ticks = 100 * HZ; + +static struct { + struct completion stop; + spinlock_t lock; + int running; + struct timer_list timer; + int queue; + int default_ticks; + unsigned long inuse; + unsigned gpio; + unsigned int gstate; +} mtx1_wdt_device; + +static void mtx1_wdt_trigger(unsigned long unused) +{ + spin_lock(&mtx1_wdt_device.lock); + if (mtx1_wdt_device.running) + ticks--; + + /* 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 + complete(&mtx1_wdt_device.stop); + spin_unlock(&mtx1_wdt_device.lock); +} + +static void mtx1_wdt_reset(void) +{ + ticks = mtx1_wdt_device.default_ticks; +} + + +static void mtx1_wdt_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&mtx1_wdt_device.lock, flags); + if (!mtx1_wdt_device.queue) { + mtx1_wdt_device.queue = 1; + mtx1_wdt_device.gstate = 1; + 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; +} + +/* Filesystem functions */ + +static int mtx1_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &mtx1_wdt_device.inuse)) + return -EBUSY; + return nonseekable_open(inode, file); +} + + +static int mtx1_wdt_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &mtx1_wdt_device.inuse); + return 0; +} + +static long mtx1_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = (int __user *)argp; + unsigned int value; + static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET, + .identity = "MTX-1 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + put_user(0, p); + break; + case WDIOC_SETOPTIONS: + if (get_user(value, p)) + return -EFAULT; + if (value & WDIOS_ENABLECARD) + mtx1_wdt_start(); + else if (value & WDIOS_DISABLECARD) + mtx1_wdt_stop(); + else + return -EINVAL; + return 0; + case WDIOC_KEEPALIVE: + mtx1_wdt_reset(); + break; + default: + return -ENOTTY; + } + return 0; +} + + +static ssize_t mtx1_wdt_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + if (!count) + return -EIO; + mtx1_wdt_reset(); + return count; +} + +static const struct file_operations mtx1_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = mtx1_wdt_ioctl, + .open = mtx1_wdt_open, + .write = mtx1_wdt_write, + .release = mtx1_wdt_release, +}; + + +static struct miscdevice mtx1_wdt_misc = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mtx1_wdt_fops, +}; + + +static int __devinit mtx1_wdt_probe(struct platform_device *pdev) +{ + int ret; + + mtx1_wdt_device.gpio = pdev->resource[0].start; + ret = gpio_request_one(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(); + dev_info(&pdev->dev, "MTX-1 Watchdog driver\n"); + return 0; +} + +static int __devexit 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); + } + + gpio_free(mtx1_wdt_device.gpio); + misc_deregister(&mtx1_wdt_misc); + return 0; +} + +static struct platform_driver mtx1_wdt_driver = { + .probe = mtx1_wdt_probe, + .remove = __devexit_p(mtx1_wdt_remove), + .driver.name = "mtx1-wdt", + .driver.owner = THIS_MODULE, +}; + +module_platform_driver(mtx1_wdt_driver); + +MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); +MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:mtx1-wdt"); diff --git a/drivers/watchdog/mv64x60_wdt.c b/drivers/watchdog/mv64x60_wdt.c new file mode 100644 index 00000000..c53d025e --- /dev/null +++ b/drivers/watchdog/mv64x60_wdt.c @@ -0,0 +1,329 @@ +/* + * mv64x60_wdt.c - MV64X60 (Marvell Discovery) watchdog userspace interface + * + * Author: James Chapman <jchapman@katalix.com> + * + * Platform-specific setup code should configure the dog to generate + * interrupt or reset as required. This code only enables/disables + * 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> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/mv643xx.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#define MV64x60_WDT_WDC_OFFSET 0 + +/* + * 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 MV64x60_WDC_ENABLE_SHIFT 24 +#define MV64x60_WDC_SERVICE_SHIFT 26 +#define MV64x60_WDC_ENABLED_SHIFT 31 + +#define MV64x60_WDC_ENABLED_TRUE 1 +#define MV64x60_WDC_ENABLED_FALSE 0 + +/* Flags bits */ +#define MV64x60_WDOG_FLAG_OPENED 0 + +static unsigned long wdt_flags; +static int wdt_status; +static void __iomem *mv64x60_wdt_regs; +static int mv64x60_wdt_timeout; +static int mv64x60_wdt_count; +static unsigned int bus_clk; +static char expect_close; +static DEFINE_SPINLOCK(mv64x60_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 mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift) +{ + u32 data; + u32 enabled; + int ret = 0; + + spin_lock(&mv64x60_wdt_spinlock); + data = readl(mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + enabled = (data >> MV64x60_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) | mv64x60_wdt_count; + writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + + data = (2 << field_shift) | mv64x60_wdt_count; + writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); + ret = 1; + } + spin_unlock(&mv64x60_wdt_spinlock); + + return ret; +} + +static void mv64x60_wdt_service(void) +{ + mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, + MV64x60_WDC_SERVICE_SHIFT); +} + +static void mv64x60_wdt_handler_enable(void) +{ + if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE, + MV64x60_WDC_ENABLE_SHIFT)) { + mv64x60_wdt_service(); + pr_notice("watchdog activated\n"); + } +} + +static void mv64x60_wdt_handler_disable(void) +{ + if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, + MV64x60_WDC_ENABLE_SHIFT)) + pr_notice("watchdog deactivated\n"); +} + +static void mv64x60_wdt_set_timeout(unsigned int timeout) +{ + /* maximum bus cycle count is 0xFFFFFFFF */ + if (timeout > 0xFFFFFFFF / bus_clk) + timeout = 0xFFFFFFFF / bus_clk; + + mv64x60_wdt_count = timeout * bus_clk >> 8; + mv64x60_wdt_timeout = timeout; +} + +static int mv64x60_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + mv64x60_wdt_handler_enable(); + + return nonseekable_open(inode, file); +} + +static int mv64x60_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + mv64x60_wdt_handler_disable(); + else { + pr_crit("unexpected close, not stopping timer!\n"); + mv64x60_wdt_service(); + } + expect_close = 0; + + clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags); + + return 0; +} + +static ssize_t mv64x60_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; + } + } + mv64x60_wdt_service(); + } + + return len; +} + +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 const struct watchdog_info info = { + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .firmware_version = 0, + .identity = "MV64x60 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_GETTEMP: + return -EOPNOTSUPP; + + case WDIOC_SETOPTIONS: + if (get_user(options, (int __user *)argp)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) + mv64x60_wdt_handler_disable(); + + if (options & WDIOS_ENABLECARD) + mv64x60_wdt_handler_enable(); + break; + + case WDIOC_KEEPALIVE: + mv64x60_wdt_service(); + wdt_status |= WDIOF_KEEPALIVEPING; + break; + + case WDIOC_SETTIMEOUT: + if (get_user(timeout, (int __user *)argp)) + return -EFAULT; + mv64x60_wdt_set_timeout(timeout); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + if (put_user(mv64x60_wdt_timeout, (int __user *)argp)) + return -EFAULT; + break; + + default: + return -ENOTTY; + } + + return 0; +} + +static const struct file_operations mv64x60_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mv64x60_wdt_write, + .unlocked_ioctl = mv64x60_wdt_ioctl, + .open = mv64x60_wdt_open, + .release = mv64x60_wdt_release, +}; + +static struct miscdevice mv64x60_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mv64x60_wdt_fops, +}; + +static int __devinit mv64x60_wdt_probe(struct platform_device *dev) +{ + struct mv64x60_wdt_pdata *pdata = dev->dev.platform_data; + struct resource *r; + int timeout = 10; + + bus_clk = 133; /* in MHz */ + if (pdata) { + timeout = pdata->timeout; + bus_clk = pdata->bus_clk; + } + + /* Since bus_clk is truncated MHz, actual frequency could be + * up to 1MHz higher. Round up, since it's better to time out + * too late than too soon. + */ + bus_clk++; + bus_clk *= 1000000; /* convert to Hz */ + + r = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!r) + return -ENODEV; + + mv64x60_wdt_regs = ioremap(r->start, resource_size(r)); + if (mv64x60_wdt_regs == NULL) + return -ENOMEM; + + mv64x60_wdt_set_timeout(timeout); + + mv64x60_wdt_handler_disable(); /* in case timer was already running */ + + return misc_register(&mv64x60_wdt_miscdev); +} + +static int __devexit 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), + .driver = { + .owner = THIS_MODULE, + .name = MV64x60_WDT_NAME, + }, +}; + +static int __init mv64x60_wdt_init(void) +{ + pr_info("MV64x60 watchdog driver\n"); + + return platform_driver_register(&mv64x60_wdt_driver); +} + +static void __exit mv64x60_wdt_exit(void) +{ + platform_driver_unregister(&mv64x60_wdt_driver); +} + +module_init(mv64x60_wdt_init); +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 00000000..ea4c7448 --- /dev/null +++ b/drivers/watchdog/nuc900_wdt.c @@ -0,0 +1,344 @@ +/* + * 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/init.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 resource *res; + 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 __devinit nuc900wdt_probe(struct platform_device *pdev) +{ + int ret = 0; + + nuc900_wdt = kzalloc(sizeof(struct nuc900_wdt), GFP_KERNEL); + if (!nuc900_wdt) + return -ENOMEM; + + nuc900_wdt->pdev = pdev; + + spin_lock_init(&nuc900_wdt->wdt_lock); + + nuc900_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (nuc900_wdt->res == NULL) { + dev_err(&pdev->dev, "no memory resource specified\n"); + ret = -ENOENT; + goto err_get; + } + + if (!request_mem_region(nuc900_wdt->res->start, + resource_size(nuc900_wdt->res), pdev->name)) { + dev_err(&pdev->dev, "failed to get memory region\n"); + ret = -ENOENT; + goto err_get; + } + + nuc900_wdt->wdt_base = ioremap(nuc900_wdt->res->start, + resource_size(nuc900_wdt->res)); + if (nuc900_wdt->wdt_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() region\n"); + ret = -EINVAL; + goto err_req; + } + + nuc900_wdt->wdt_clock = clk_get(&pdev->dev, NULL); + if (IS_ERR(nuc900_wdt->wdt_clock)) { + dev_err(&pdev->dev, "failed to find watchdog clock source\n"); + ret = PTR_ERR(nuc900_wdt->wdt_clock); + goto err_map; + } + + 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); + clk_put(nuc900_wdt->wdt_clock); +err_map: + iounmap(nuc900_wdt->wdt_base); +err_req: + release_mem_region(nuc900_wdt->res->start, + resource_size(nuc900_wdt->res)); +err_get: + kfree(nuc900_wdt); + return ret; +} + +static int __devexit nuc900wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&nuc900wdt_miscdev); + + clk_disable(nuc900_wdt->wdt_clock); + clk_put(nuc900_wdt->wdt_clock); + + iounmap(nuc900_wdt->wdt_base); + + release_mem_region(nuc900_wdt->res->start, + resource_size(nuc900_wdt->res)); + + kfree(nuc900_wdt); + + return 0; +} + +static struct platform_driver nuc900wdt_driver = { + .probe = nuc900wdt_probe, + .remove = __devexit_p(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_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:nuc900-wdt"); diff --git a/drivers/watchdog/nv_tco.c b/drivers/watchdog/nv_tco.c new file mode 100644 index 00000000..6bbb9efc --- /dev/null +++ b/drivers/watchdog/nv_tco.c @@ -0,0 +1,516 @@ +/* + * 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 DEFINE_PCI_DEVICE_TABLE(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 __devinit 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 __devinit 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 __devexit 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 __devexit 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/nv_tco.h b/drivers/watchdog/nv_tco.h new file mode 100644 index 00000000..c2d1d04e --- /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 00000000..46120883 --- /dev/null +++ b/drivers/watchdog/octeon-wdt-main.c @@ -0,0 +1,748 @@ +/* + * 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); + + for_each_online_cpu(cpu) + octeon_wdt_setup_interrupt(cpu); + + register_hotcpu_notifier(&octeon_wdt_cpu_notifier); +out: + return ret; +} + +/** + * Module / driver shutdown + */ +static void __exit octeon_wdt_cleanup(void) +{ + int cpu; + + misc_deregister(&octeon_wdt_miscdev); + + 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); + } + /* + * 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 00000000..8a900a5e --- /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 00000000..55d2f66d --- /dev/null +++ b/drivers/watchdog/of_xilinx_wdt.c @@ -0,0 +1,417 @@ +/* +* of_xilinx_wdt.c 1.01 A Watchdog Device Driver for Xilinx xps_timebase_wdt +* +* (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. +* +* ----------------------- +* 30-May-2011 Alejandro Cabrera <aldaya@gmail.com> +* - If "xlnx,wdt-enable-once" wasn't found on device tree the +* module will use CONFIG_WATCHDOG_NOWAYOUT +* - If the device tree parameters ("clock-frequency" and +* "xlnx,wdt-interval") wasn't found the driver won't +* know the wdt reset interval +*/ + +#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/miscdevice.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/io.h> +#include <linux/uaccess.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" +#define PFX WATCHDOG_NAME ": " + +struct xwdt_device { + struct resource res; + void __iomem *base; + u32 nowayout; + u32 wdt_interval; + u32 boot_status; +}; + +static struct xwdt_device xdev; + +static u32 timeout; +static u32 control_status_reg; +static u8 expect_close; +static u8 no_timeout; +static unsigned long driver_open; + +static DEFINE_SPINLOCK(spinlock); + +static void xwdt_start(void) +{ + spin_lock(&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(&spinlock); +} + +static void xwdt_stop(void) +{ + spin_lock(&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(&spinlock); + pr_info("Stopped!\n"); +} + +static void xwdt_keepalive(void) +{ + spin_lock(&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(&spinlock); +} + +static void xwdt_get_status(int *status) +{ + int new_status; + + spin_lock(&spinlock); + + control_status_reg = ioread32(xdev.base + XWT_TWCSR0_OFFSET); + new_status = ((control_status_reg & + (XWT_CSR0_WRS_MASK | XWT_CSR0_WDS_MASK)) != 0); + spin_unlock(&spinlock); + + *status = 0; + if (new_status & 1) + *status |= WDIOF_CARDRESET; +} + +static u32 xwdt_selftest(void) +{ + int i; + u32 timer_value1; + u32 timer_value2; + + spin_lock(&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(&spinlock); + + if (timer_value2 != timer_value1) + return ~XWT_TIMER_FAILED; + else + return XWT_TIMER_FAILED; +} + +static int xwdt_open(struct inode *inode, struct file *file) +{ + /* Only one process can handle the wdt at a time */ + if (test_and_set_bit(0, &driver_open)) + return -EBUSY; + + /* Make sure that the module are always loaded...*/ + if (xdev.nowayout) + __module_get(THIS_MODULE); + + xwdt_start(); + pr_info("Started...\n"); + + return nonseekable_open(inode, file); +} + +static int xwdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + xwdt_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + xwdt_keepalive(); + } + + clear_bit(0, &driver_open); + expect_close = 0; + return 0; +} + +/* + * xwdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we don't define content meaning. + */ +static ssize_t xwdt_write(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + if (len) { + if (!xdev.nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + xwdt_keepalive(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +/* + * xwdt_ioctl: + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. + */ +static long xwdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int status; + + 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_GETBOOTSTATUS: + return put_user(xdev.boot_status, uarg.i); + + case WDIOC_GETSTATUS: + xwdt_get_status(&status); + return put_user(status, uarg.i); + + case WDIOC_KEEPALIVE: + xwdt_keepalive(); + return 0; + + case WDIOC_GETTIMEOUT: + if (no_timeout) + return -ENOTTY; + else + return put_user(timeout, uarg.i); + + default: + return -ENOTTY; + } +} + +static const struct file_operations xwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = xwdt_write, + .open = xwdt_open, + .release = xwdt_release, + .unlocked_ioctl = xwdt_ioctl, +}; + +static struct miscdevice xwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &xwdt_fops, +}; + +static int __devinit xwdt_probe(struct platform_device *pdev) +{ + int rc; + u32 *tmptr; + u32 *pfreq; + + no_timeout = 0; + + pfreq = (u32 *)of_get_property(pdev->dev.of_node->parent, + "clock-frequency", NULL); + + if (pfreq == NULL) { + pr_warn("The watchdog clock frequency cannot be obtained!\n"); + no_timeout = 1; + } + + rc = of_address_to_resource(pdev->dev.of_node, 0, &xdev.res); + if (rc) { + pr_warn("invalid address!\n"); + return rc; + } + + tmptr = (u32 *)of_get_property(pdev->dev.of_node, + "xlnx,wdt-interval", NULL); + if (tmptr == NULL) { + pr_warn("Parameter \"xlnx,wdt-interval\" not found in device tree!\n"); + no_timeout = 1; + } else { + xdev.wdt_interval = *tmptr; + } + + tmptr = (u32 *)of_get_property(pdev->dev.of_node, + "xlnx,wdt-enable-once", NULL); + if (tmptr == NULL) { + pr_warn("Parameter \"xlnx,wdt-enable-once\" not found in device tree!\n"); + xdev.nowayout = WATCHDOG_NOWAYOUT; + } + +/* + * Twice of the 2^wdt_interval / freq because the first wdt overflow is + * ignored (interrupt), reset is only generated at second wdt overflow + */ + if (!no_timeout) + timeout = 2 * ((1<<xdev.wdt_interval) / *pfreq); + + if (!request_mem_region(xdev.res.start, + xdev.res.end - xdev.res.start + 1, WATCHDOG_NAME)) { + rc = -ENXIO; + pr_err("memory request failure!\n"); + goto err_out; + } + + xdev.base = ioremap(xdev.res.start, xdev.res.end - xdev.res.start + 1); + if (xdev.base == NULL) { + rc = -ENOMEM; + pr_err("ioremap failure!\n"); + goto release_mem; + } + + rc = xwdt_selftest(); + if (rc == XWT_TIMER_FAILED) { + pr_err("SelfTest routine error!\n"); + goto unmap_io; + } + + xwdt_get_status(&xdev.boot_status); + + rc = misc_register(&xwdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + xwdt_miscdev.minor, rc); + goto unmap_io; + } + + if (no_timeout) + pr_info("driver loaded (timeout=? sec, nowayout=%d)\n", + xdev.nowayout); + else + pr_info("driver loaded (timeout=%d sec, nowayout=%d)\n", + timeout, xdev.nowayout); + + expect_close = 0; + clear_bit(0, &driver_open); + + return 0; + +unmap_io: + iounmap(xdev.base); +release_mem: + release_mem_region(xdev.res.start, resource_size(&xdev.res)); +err_out: + return rc; +} + +static int __devexit xwdt_remove(struct platform_device *dev) +{ + misc_deregister(&xwdt_miscdev); + iounmap(xdev.base); + release_mem_region(xdev.res.start, resource_size(&xdev.res)); + + return 0; +} + +/* Match table for of_platform binding */ +static struct of_device_id __devinitdata xwdt_of_match[] = { + { .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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/omap_wdt.c b/drivers/watchdog/omap_wdt.c new file mode 100644 index 00000000..8285d65c --- /dev/null +++ b/drivers/watchdog/omap_wdt.c @@ -0,0 +1,450 @@ +/* + * omap_wdt.c + * + * Watchdog driver for the TI OMAP 16xx & 24xx/34xx 32KHz (non-secure) watchdog + * + * Author: MontaVista Software, Inc. + * <gdavis@mvista.com> or <source@mvista.com> + * + * 2003 (c) MontaVista Software, Inc. 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. + * + * History: + * + * 20030527: George G. Davis <gdavis@mvista.com> + * Initially based on linux-2.4.19-rmk7-pxa1/drivers/char/sa1100_wdt.c + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * Copyright (c) 2004 Texas Instruments. + * 1. Modified to support OMAP1610 32-KHz watchdog timer + * 2. Ported to 2.6 kernel + * + * Copyright (c) 2005 David Brownell + * Use the driver model and standard identifiers; handle bigger timeouts. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/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/bitops.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <mach/hardware.h> +#include <plat/prcm.h> + +#include "omap_wdt.h" + +static struct platform_device *omap_wdt_dev; + +static unsigned timer_margin; +module_param(timer_margin, uint, 0); +MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)"); + +static unsigned int wdt_trgr_pattern = 0x1234; +static DEFINE_SPINLOCK(wdt_lock); + +struct omap_wdt_dev { + void __iomem *base; /* physical */ + struct device *dev; + int omap_wdt_users; + struct resource *mem; + struct miscdevice omap_wdt_miscdev; +}; + +static void omap_wdt_ping(struct omap_wdt_dev *wdev) +{ + void __iomem *base = wdev->base; + + /* wait for posted write to complete */ + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) + cpu_relax(); + + wdt_trgr_pattern = ~wdt_trgr_pattern; + __raw_writel(wdt_trgr_pattern, (base + OMAP_WATCHDOG_TGR)); + + /* wait for posted write to complete */ + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x08) + cpu_relax(); + /* reloaded WCRR from WLDR */ +} + +static void omap_wdt_enable(struct omap_wdt_dev *wdev) +{ + void __iomem *base = wdev->base; + + /* Sequence to enable the watchdog */ + __raw_writel(0xBBBB, base + OMAP_WATCHDOG_SPR); + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) + cpu_relax(); + + __raw_writel(0x4444, base + OMAP_WATCHDOG_SPR); + while ((__raw_readl(base + OMAP_WATCHDOG_WPS)) & 0x10) + cpu_relax(); +} + +static void omap_wdt_disable(struct omap_wdt_dev *wdev) +{ + void __iomem *base = wdev->base; + + /* sequence required to disable watchdog */ + __raw_writel(0xAAAA, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x10) + cpu_relax(); + + __raw_writel(0x5555, base + OMAP_WATCHDOG_SPR); /* TIMER_MODE */ + while (__raw_readl(base + 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; +} + +static void omap_wdt_set_timeout(struct omap_wdt_dev *wdev) +{ + u32 pre_margin = GET_WLDR_VAL(timer_margin); + void __iomem *base = wdev->base; + + pm_runtime_get_sync(wdev->dev); + + /* just count up at 32 KHz */ + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) + cpu_relax(); + + __raw_writel(pre_margin, base + OMAP_WATCHDOG_LDR); + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x04) + cpu_relax(); + + pm_runtime_put_sync(wdev->dev); +} + +/* + * Allow only one task to hold it open + */ +static int omap_wdt_open(struct inode *inode, struct file *file) +{ + struct omap_wdt_dev *wdev = platform_get_drvdata(omap_wdt_dev); + void __iomem *base = wdev->base; + + if (test_and_set_bit(1, (unsigned long *)&(wdev->omap_wdt_users))) + return -EBUSY; + + pm_runtime_get_sync(wdev->dev); + + /* initialize prescaler */ + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) + cpu_relax(); + + __raw_writel((1 << 5) | (PTV << 2), base + OMAP_WATCHDOG_CNTRL); + while (__raw_readl(base + OMAP_WATCHDOG_WPS) & 0x01) + cpu_relax(); + + file->private_data = (void *) wdev; + + omap_wdt_set_timeout(wdev); + omap_wdt_ping(wdev); /* trigger loading of new timeout value */ + omap_wdt_enable(wdev); + + pm_runtime_put_sync(wdev->dev); + + return nonseekable_open(inode, file); +} + +static int omap_wdt_release(struct inode *inode, struct file *file) +{ + struct omap_wdt_dev *wdev = file->private_data; + + /* + * Shut off the timer unless NOWAYOUT is defined. + */ +#ifndef CONFIG_WATCHDOG_NOWAYOUT + pm_runtime_get_sync(wdev->dev); + + omap_wdt_disable(wdev); + + pm_runtime_put_sync(wdev->dev); +#else + pr_crit("Unexpected close, not stopping!\n"); +#endif + wdev->omap_wdt_users = 0; + + return 0; +} + +static ssize_t omap_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + struct omap_wdt_dev *wdev = file->private_data; + + /* Refresh LOAD_TIME. */ + if (len) { + pm_runtime_get_sync(wdev->dev); + spin_lock(&wdt_lock); + omap_wdt_ping(wdev); + spin_unlock(&wdt_lock); + pm_runtime_put_sync(wdev->dev); + } + return len; +} + +static long omap_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct omap_wdt_dev *wdev; + int new_margin; + static const struct watchdog_info ident = { + .identity = "OMAP Watchdog", + .options = WDIOF_SETTIMEOUT, + .firmware_version = 0, + }; + + wdev = file->private_data; + + switch (cmd) { + 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(__raw_readw(ARM_SYSST), + (int __user *)arg); + if (cpu_is_omap24xx()) + return put_user(omap_prcm_get_reset_sources(), + (int __user *)arg); + return put_user(0, (int __user *)arg); + case WDIOC_KEEPALIVE: + pm_runtime_get_sync(wdev->dev); + spin_lock(&wdt_lock); + omap_wdt_ping(wdev); + spin_unlock(&wdt_lock); + pm_runtime_put_sync(wdev->dev); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, (int __user *)arg)) + return -EFAULT; + omap_wdt_adjust_timeout(new_margin); + + pm_runtime_get_sync(wdev->dev); + spin_lock(&wdt_lock); + omap_wdt_disable(wdev); + omap_wdt_set_timeout(wdev); + omap_wdt_enable(wdev); + + omap_wdt_ping(wdev); + spin_unlock(&wdt_lock); + pm_runtime_put_sync(wdev->dev); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timer_margin, (int __user *)arg); + default: + return -ENOTTY; + } +} + +static const struct file_operations omap_wdt_fops = { + .owner = THIS_MODULE, + .write = omap_wdt_write, + .unlocked_ioctl = omap_wdt_ioctl, + .open = omap_wdt_open, + .release = omap_wdt_release, + .llseek = no_llseek, +}; + +static int __devinit omap_wdt_probe(struct platform_device *pdev) +{ + struct resource *res, *mem; + struct omap_wdt_dev *wdev; + int ret; + + /* reserve static register mappings */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOENT; + goto err_get_resource; + } + + if (omap_wdt_dev) { + ret = -EBUSY; + goto err_busy; + } + + mem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!mem) { + ret = -EBUSY; + goto err_busy; + } + + wdev = kzalloc(sizeof(struct omap_wdt_dev), GFP_KERNEL); + if (!wdev) { + ret = -ENOMEM; + goto err_kzalloc; + } + + wdev->omap_wdt_users = 0; + wdev->mem = mem; + wdev->dev = &pdev->dev; + + wdev->base = ioremap(res->start, resource_size(res)); + if (!wdev->base) { + ret = -ENOMEM; + goto err_ioremap; + } + + platform_set_drvdata(pdev, wdev); + + pm_runtime_enable(wdev->dev); + pm_runtime_get_sync(wdev->dev); + + omap_wdt_disable(wdev); + omap_wdt_adjust_timeout(timer_margin); + + wdev->omap_wdt_miscdev.parent = &pdev->dev; + wdev->omap_wdt_miscdev.minor = WATCHDOG_MINOR; + wdev->omap_wdt_miscdev.name = "watchdog"; + wdev->omap_wdt_miscdev.fops = &omap_wdt_fops; + + ret = misc_register(&(wdev->omap_wdt_miscdev)); + if (ret) + goto err_misc; + + pr_info("OMAP Watchdog Timer Rev 0x%02x: initial timeout %d sec\n", + __raw_readl(wdev->base + OMAP_WATCHDOG_REV) & 0xFF, + timer_margin); + + pm_runtime_put_sync(wdev->dev); + + omap_wdt_dev = pdev; + + return 0; + +err_misc: + pm_runtime_disable(wdev->dev); + platform_set_drvdata(pdev, NULL); + iounmap(wdev->base); + +err_ioremap: + wdev->base = NULL; + kfree(wdev); + +err_kzalloc: + release_mem_region(res->start, resource_size(res)); + +err_busy: +err_get_resource: + + return ret; +} + +static void omap_wdt_shutdown(struct platform_device *pdev) +{ + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) { + pm_runtime_get_sync(wdev->dev); + omap_wdt_disable(wdev); + pm_runtime_put_sync(wdev->dev); + } +} + +static int __devexit omap_wdt_remove(struct platform_device *pdev) +{ + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + pm_runtime_disable(wdev->dev); + if (!res) + return -ENOENT; + + misc_deregister(&(wdev->omap_wdt_miscdev)); + release_mem_region(res->start, resource_size(res)); + platform_set_drvdata(pdev, NULL); + + iounmap(wdev->base); + + kfree(wdev); + omap_wdt_dev = NULL; + + return 0; +} + +#ifdef CONFIG_PM + +/* REVISIT ... not clear this is the best way to handle system suspend; and + * it's very inappropriate for selective device suspend (e.g. suspending this + * through sysfs rather than by stopping the watchdog daemon). Also, this + * may not play well enough with NOWAYOUT... + */ + +static int omap_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) { + pm_runtime_get_sync(wdev->dev); + omap_wdt_disable(wdev); + pm_runtime_put_sync(wdev->dev); + } + + return 0; +} + +static int omap_wdt_resume(struct platform_device *pdev) +{ + struct omap_wdt_dev *wdev = platform_get_drvdata(pdev); + + if (wdev->omap_wdt_users) { + pm_runtime_get_sync(wdev->dev); + omap_wdt_enable(wdev); + omap_wdt_ping(wdev); + pm_runtime_put_sync(wdev->dev); + } + + return 0; +} + +#else +#define omap_wdt_suspend NULL +#define omap_wdt_resume NULL +#endif + +static struct platform_driver omap_wdt_driver = { + .probe = omap_wdt_probe, + .remove = __devexit_p(omap_wdt_remove), + .shutdown = omap_wdt_shutdown, + .suspend = omap_wdt_suspend, + .resume = omap_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "omap_wdt", + }, +}; + +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 new file mode 100644 index 00000000..09b774cf --- /dev/null +++ b/drivers/watchdog/omap_wdt.h @@ -0,0 +1,54 @@ +/* + * linux/drivers/char/watchdog/omap_wdt.h + * + * BRIEF MODULE DESCRIPTION + * OMAP Watchdog timer register definitions + * + * Copyright (C) 2004 Texas Instruments. + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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. + */ + +#ifndef _OMAP_WATCHDOG_H +#define _OMAP_WATCHDOG_H + +#define OMAP_WATCHDOG_REV (0x00) +#define OMAP_WATCHDOG_SYS_CONFIG (0x10) +#define OMAP_WATCHDOG_STATUS (0x14) +#define OMAP_WATCHDOG_CNTRL (0x24) +#define OMAP_WATCHDOG_CRR (0x28) +#define OMAP_WATCHDOG_LDR (0x2c) +#define OMAP_WATCHDOG_TGR (0x30) +#define OMAP_WATCHDOG_WPS (0x34) +#define OMAP_WATCHDOG_SPR (0x48) + +/* Using the prescaler, the OMAP watchdog could go for many + * months before firing. These limits work without scaling, + * with the 60 second default assumed by most tools and docs. + */ +#define TIMER_MARGIN_MAX (24 * 60 * 60) /* 1 day */ +#define TIMER_MARGIN_DEFAULT 60 /* 60 secs */ +#define TIMER_MARGIN_MIN 1 + +#define PTV 0 /* prescale */ +#define GET_WLDR_VAL(secs) (0xffffffff - ((secs) * (32768/(1<<PTV))) + 1) + +#endif /* _OMAP_WATCHDOG_H */ diff --git a/drivers/watchdog/orion_wdt.c b/drivers/watchdog/orion_wdt.c new file mode 100644 index 00000000..788aa158 --- /dev/null +++ b/drivers/watchdog/orion_wdt.c @@ -0,0 +1,317 @@ +/* + * 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/fs.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/spinlock.h> +#include <mach/bridge-regs.h> +#include <plat/orion_wdt.h> + +/* + * Watchdog timer block registers. + */ +#define TIMER_CTRL 0x0000 +#define WDT_EN 0x0010 +#define WDT_VAL 0x0024 + +#define WDT_MAX_CYCLE_COUNT 0xffffffff +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static bool nowayout = WATCHDOG_NOWAYOUT; +static int heartbeat = -1; /* module parameter (seconds) */ +static unsigned int wdt_max_duration; /* (seconds) */ +static unsigned int wdt_tclk; +static void __iomem *wdt_reg; +static unsigned long wdt_status; +static DEFINE_SPINLOCK(wdt_lock); + +static void orion_wdt_ping(void) +{ + spin_lock(&wdt_lock); + + /* Reload watchdog duration */ + writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); + + spin_unlock(&wdt_lock); +} + +static void orion_wdt_enable(void) +{ + u32 reg; + + spin_lock(&wdt_lock); + + /* Set watchdog duration */ + writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); + + /* Clear watchdog timer interrupt */ + reg = readl(BRIDGE_CAUSE); + reg &= ~WDT_INT_REQ; + writel(reg, BRIDGE_CAUSE); + + /* Enable watchdog timer */ + reg = readl(wdt_reg + TIMER_CTRL); + reg |= WDT_EN; + writel(reg, wdt_reg + TIMER_CTRL); + + /* Enable reset on watchdog */ + reg = readl(RSTOUTn_MASK); + reg |= WDT_RESET_OUT_EN; + writel(reg, RSTOUTn_MASK); + + spin_unlock(&wdt_lock); +} + +static void orion_wdt_disable(void) +{ + u32 reg; + + spin_lock(&wdt_lock); + + /* Disable reset on watchdog */ + reg = readl(RSTOUTn_MASK); + reg &= ~WDT_RESET_OUT_EN; + writel(reg, RSTOUTn_MASK); + + /* Disable watchdog timer */ + reg = readl(wdt_reg + TIMER_CTRL); + reg &= ~WDT_EN; + writel(reg, wdt_reg + TIMER_CTRL); + + spin_unlock(&wdt_lock); +} + +static int orion_wdt_get_timeleft(int *time_left) +{ + spin_lock(&wdt_lock); + *time_left = readl(wdt_reg + WDT_VAL) / wdt_tclk; + spin_unlock(&wdt_lock); + return 0; +} + +static int orion_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); + orion_wdt_enable(); + return nonseekable_open(inode, file); +} + +static ssize_t orion_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); + } + } + orion_wdt_ping(); + } + return len; +} + +static int orion_wdt_settimeout(int new_time) +{ + if ((new_time <= 0) || (new_time > wdt_max_duration)) + return -EINVAL; + + /* Set new watchdog time to be used when + * orion_wdt_enable() or orion_wdt_ping() is called. */ + heartbeat = new_time; + return 0; +} + +static const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "Orion Watchdog", +}; + +static long orion_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: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + orion_wdt_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (orion_wdt_settimeout(time)) { + ret = -EINVAL; + break; + } + orion_wdt_ping(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + + case WDIOC_GETTIMELEFT: + if (orion_wdt_get_timeleft(&time)) { + ret = -EINVAL; + break; + } + ret = put_user(time, (int *)arg); + break; + } + return ret; +} + +static int orion_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) + orion_wdt_disable(); + 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); + + return 0; +} + + +static const struct file_operations orion_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = orion_wdt_write, + .unlocked_ioctl = orion_wdt_ioctl, + .open = orion_wdt_open, + .release = orion_wdt_release, +}; + +static struct miscdevice orion_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &orion_wdt_fops, +}; + +static int __devinit orion_wdt_probe(struct platform_device *pdev) +{ + struct orion_wdt_platform_data *pdata = pdev->dev.platform_data; + struct resource *res; + int ret; + + if (pdata) { + wdt_tclk = pdata->tclk; + } else { + pr_err("misses platform data\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + wdt_reg = ioremap(res->start, resource_size(res)); + + if (orion_wdt_miscdev.parent) + return -EBUSY; + orion_wdt_miscdev.parent = &pdev->dev; + + wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; + if (orion_wdt_settimeout(heartbeat)) + heartbeat = wdt_max_duration; + + ret = misc_register(&orion_wdt_miscdev); + if (ret) + return ret; + + pr_info("Initial timeout %d sec%s\n", + heartbeat, nowayout ? ", nowayout" : ""); + return 0; +} + +static int __devexit orion_wdt_remove(struct platform_device *pdev) +{ + int ret; + + if (test_bit(WDT_IN_USE, &wdt_status)) { + orion_wdt_disable(); + clear_bit(WDT_IN_USE, &wdt_status); + } + + ret = misc_deregister(&orion_wdt_miscdev); + if (!ret) + orion_wdt_miscdev.parent = NULL; + + return ret; +} + +static void orion_wdt_shutdown(struct platform_device *pdev) +{ + if (test_bit(WDT_IN_USE, &wdt_status)) + orion_wdt_disable(); +} + +static struct platform_driver orion_wdt_driver = { + .probe = orion_wdt_probe, + .remove = __devexit_p(orion_wdt_remove), + .shutdown = orion_wdt_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = "orion_wdt", + }, +}; + +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_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/pc87413_wdt.c b/drivers/watchdog/pc87413_wdt.c new file mode 100644 index 00000000..5afb89b4 --- /dev/null +++ b/drivers/watchdog/pc87413_wdt.c @@ -0,0 +1,598 @@ +/* + * NS pc87413-wdt Watchdog Timer driver for Linux 2.6.x.x + * + * This code is based on wdt.c with original copyright. + * + * (C) Copyright 2006 Sven Anders, <anders@anduras.de> + * and Marcus Junker, <junker@anduras.de> + * + * 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 Sven Anders, Marcus Junker nor ANDURAS AG + * admit liability nor provide warranty for any of this software. + * This material is provided "AS-IS" and at no charge. + * + * Release 1.1 + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/notifier.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +/* #define DEBUG 1 */ + +#define DEFAULT_TIMEOUT 1 /* 1 minute */ +#define MAX_TIMEOUT 255 + +#define VERSION "1.1" +#define MODNAME "pc87413 WDT" +#define DPFX MODNAME " - DEBUG: " + +#define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */ +#define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1) +#define SWC_LDN 0x04 +#define SIOCFG2 0x22 /* Serial IO register */ +#define WDCTL 0x10 /* Watchdog-Timer-Control-Register */ +#define WDTO 0x11 /* Watchdog timeout register */ +#define WDCFG 0x12 /* Watchdog config register */ + +#define IO_DEFAULT 0x2E /* Address used on Portwell Boards */ + +static int io = IO_DEFAULT; +static int swc_base_addr = -1; + +static int timeout = DEFAULT_TIMEOUT; /* timeout value */ +static unsigned long timer_enabled; /* is the timer enabled? */ + +static char expect_close; /* is the close expected? */ + +static DEFINE_SPINLOCK(io_lock); /* to guard us from io races */ + +static bool nowayout = WATCHDOG_NOWAYOUT; + +/* -- Low level function ----------------------------------------*/ + +/* Select pins for Watchdog output */ + +static inline void pc87413_select_wdt_out(void) +{ + unsigned int cr_data = 0; + + /* Step 1: Select multiple pin,pin55,as WDT output */ + + outb_p(SIOCFG2, WDT_INDEX_IO_PORT); + + cr_data = inb(WDT_DATA_IO_PORT); + + cr_data |= 0x80; /* Set Bit7 to 1*/ + outb_p(SIOCFG2, WDT_INDEX_IO_PORT); + + outb_p(cr_data, WDT_DATA_IO_PORT); + +#ifdef DEBUG + pr_info(DPFX + "Select multiple pin,pin55,as WDT output: Bit7 to 1: %d\n", + cr_data); +#endif +} + +/* Enable SWC functions */ + +static inline void pc87413_enable_swc(void) +{ + unsigned int cr_data = 0; + + /* Step 2: Enable SWC functions */ + + outb_p(0x07, WDT_INDEX_IO_PORT); /* Point SWC_LDN (LDN=4) */ + outb_p(SWC_LDN, WDT_DATA_IO_PORT); + + outb_p(0x30, WDT_INDEX_IO_PORT); /* Read Index 0x30 First */ + cr_data = inb(WDT_DATA_IO_PORT); + cr_data |= 0x01; /* Set Bit0 to 1 */ + outb_p(0x30, WDT_INDEX_IO_PORT); + outb_p(cr_data, WDT_DATA_IO_PORT); /* Index0x30_bit0P1 */ + +#ifdef DEBUG + pr_info(DPFX "pc87413 - Enable SWC functions\n"); +#endif +} + +/* Read SWC I/O base address */ + +static void pc87413_get_swc_base_addr(void) +{ + unsigned char addr_l, addr_h = 0; + + /* Step 3: Read SWC I/O Base Address */ + + outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */ + addr_h = inb(WDT_DATA_IO_PORT); + + outb_p(0x61, WDT_INDEX_IO_PORT); /* Read Index 0x61 */ + + addr_l = inb(WDT_DATA_IO_PORT); + + swc_base_addr = (addr_h << 8) + addr_l; +#ifdef DEBUG + pr_info(DPFX + "Read SWC I/O Base Address: low %d, high %d, res %d\n", + addr_l, addr_h, swc_base_addr); +#endif +} + +/* Select Bank 3 of SWC */ + +static inline void pc87413_swc_bank3(void) +{ + /* Step 4: Select Bank3 of SWC */ + outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f); +#ifdef DEBUG + pr_info(DPFX "Select Bank3 of SWC\n"); +#endif +} + +/* Set watchdog timeout to x minutes */ + +static inline void pc87413_programm_wdto(char pc87413_time) +{ + /* Step 5: Programm WDTO, Twd. */ + outb_p(pc87413_time, swc_base_addr + WDTO); +#ifdef DEBUG + pr_info(DPFX "Set WDTO to %d minutes\n", pc87413_time); +#endif +} + +/* Enable WDEN */ + +static inline void pc87413_enable_wden(void) +{ + /* Step 6: Enable WDEN */ + outb_p(inb(swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL); +#ifdef DEBUG + pr_info(DPFX "Enable WDEN\n"); +#endif +} + +/* Enable SW_WD_TREN */ +static inline void pc87413_enable_sw_wd_tren(void) +{ + /* Enable SW_WD_TREN */ + outb_p(inb(swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG); +#ifdef DEBUG + pr_info(DPFX "Enable SW_WD_TREN\n"); +#endif +} + +/* Disable SW_WD_TREN */ + +static inline void pc87413_disable_sw_wd_tren(void) +{ + /* Disable SW_WD_TREN */ + outb_p(inb(swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG); +#ifdef DEBUG + pr_info(DPFX "pc87413 - Disable SW_WD_TREN\n"); +#endif +} + +/* Enable SW_WD_TRG */ + +static inline void pc87413_enable_sw_wd_trg(void) +{ + /* Enable SW_WD_TRG */ + outb_p(inb(swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL); +#ifdef DEBUG + pr_info(DPFX "pc87413 - Enable SW_WD_TRG\n"); +#endif +} + +/* Disable SW_WD_TRG */ + +static inline void pc87413_disable_sw_wd_trg(void) +{ + /* Disable SW_WD_TRG */ + outb_p(inb(swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL); +#ifdef DEBUG + pr_info(DPFX "Disable SW_WD_TRG\n"); +#endif +} + +/* -- Higher level functions ------------------------------------*/ + +/* Enable the watchdog */ + +static void pc87413_enable(void) +{ + spin_lock(&io_lock); + + pc87413_swc_bank3(); + pc87413_programm_wdto(timeout); + pc87413_enable_wden(); + pc87413_enable_sw_wd_tren(); + pc87413_enable_sw_wd_trg(); + + spin_unlock(&io_lock); +} + +/* Disable the watchdog */ + +static void pc87413_disable(void) +{ + spin_lock(&io_lock); + + pc87413_swc_bank3(); + pc87413_disable_sw_wd_tren(); + pc87413_disable_sw_wd_trg(); + pc87413_programm_wdto(0); + + spin_unlock(&io_lock); +} + +/* Refresh the watchdog */ + +static void pc87413_refresh(void) +{ + spin_lock(&io_lock); + + pc87413_swc_bank3(); + pc87413_disable_sw_wd_tren(); + pc87413_disable_sw_wd_trg(); + pc87413_programm_wdto(timeout); + pc87413_enable_wden(); + pc87413_enable_sw_wd_tren(); + pc87413_enable_sw_wd_trg(); + + spin_unlock(&io_lock); +} + +/* -- File operations -------------------------------------------*/ + +/** + * pc87413_open: + * @inode: inode of device + * @file: file handle to device + * + */ + +static int pc87413_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + + if (test_and_set_bit(0, &timer_enabled)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Reload and activate timer */ + pc87413_refresh(); + + pr_info("Watchdog enabled. Timeout set to %d minute(s).\n", timeout); + + return nonseekable_open(inode, file); +} + +/** + * pc87413_release: + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int pc87413_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. */ + + if (expect_close == 42) { + pc87413_disable(); + pr_info("Watchdog disabled, sleeping again...\n"); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + pc87413_refresh(); + } + clear_bit(0, &timer_enabled); + expect_close = 0; + return 0; +} + +/** + * pc87413_status: + * + * return, if the watchdog is enabled (timeout is set...) + */ + + +static int pc87413_status(void) +{ + return 0; /* currently not supported */ +} + +/** + * pc87413_write: + * @file: file handle to the watchdog + * @data: data buffer to write + * @len: length in bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t pc87413_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* reset expect flag */ + expect_close = 0; + + /* scan to see whether or not we got the + magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + pc87413_refresh(); + } + return len; +} + +/** + * pc87413_ioctl: + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ + +static long pc87413_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_timeout; + + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "PC87413(HF/F) watchdog", + }; + + uarg.i = (int __user *)arg; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + return put_user(pc87413_status(), uarg.i); + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + if (get_user(options, uarg.i)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) { + pc87413_disable(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + pc87413_enable(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + pc87413_refresh(); +#ifdef DEBUG + pr_info(DPFX "keepalive\n"); +#endif + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + /* the API states this is given in secs */ + new_timeout /= 60; + if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) + return -EINVAL; + timeout = new_timeout; + pc87413_refresh(); + /* fall through and return the new timeout... */ + case WDIOC_GETTIMEOUT: + new_timeout = timeout * 60; + return put_user(new_timeout, uarg.i); + default: + return -ENOTTY; + } +} + +/* -- Notifier funtions -----------------------------------------*/ + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int pc87413_notify_sys(struct notifier_block *this, + unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + /* Turn the card off */ + pc87413_disable(); + return NOTIFY_DONE; +} + +/* -- Module's structures ---------------------------------------*/ + +static const struct file_operations pc87413_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pc87413_write, + .unlocked_ioctl = pc87413_ioctl, + .open = pc87413_open, + .release = pc87413_release, +}; + +static struct notifier_block pc87413_notifier = { + .notifier_call = pc87413_notify_sys, +}; + +static struct miscdevice pc87413_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pc87413_fops, +}; + +/* -- Module init functions -------------------------------------*/ + +/** + * pc87413_init: module's "constructor" + * + * Set up the WDT watchdog board. All we have to do is grab the + * resources we require and bitch if anyone beat us to them. + * The open() function will actually kick the board off. + */ + +static int __init pc87413_init(void) +{ + int ret; + + pr_info("Version " VERSION " at io 0x%X\n", + WDT_INDEX_IO_PORT); + + if (!request_muxed_region(io, 2, MODNAME)) + return -EBUSY; + + ret = register_reboot_notifier(&pc87413_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + } + + ret = misc_register(&pc87413_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto reboot_unreg; + } + pr_info("initialized. timeout=%d min\n", timeout); + + pc87413_select_wdt_out(); + pc87413_enable_swc(); + pc87413_get_swc_base_addr(); + + if (!request_region(swc_base_addr, 0x20, MODNAME)) { + pr_err("cannot request SWC region at 0x%x\n", swc_base_addr); + ret = -EBUSY; + goto misc_unreg; + } + + pc87413_enable(); + + release_region(io, 2); + return 0; + +misc_unreg: + misc_deregister(&pc87413_miscdev); +reboot_unreg: + unregister_reboot_notifier(&pc87413_notifier); + release_region(io, 2); + return ret; +} + +/** + * pc87413_exit: module's "destructor" + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit pc87413_exit(void) +{ + /* Stop the timer before we leave */ + if (!nowayout) { + pc87413_disable(); + pr_info("Watchdog disabled\n"); + } + + misc_deregister(&pc87413_miscdev); + unregister_reboot_notifier(&pc87413_notifier); + release_region(swc_base_addr, 0x20); + + pr_info("watchdog component driver removed\n"); +} + +module_init(pc87413_init); +module_exit(pc87413_exit); + +MODULE_AUTHOR("Sven Anders <anders@anduras.de>, " + "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_DEFAULT) ")."); + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in minutes (default=" + __MODULE_STRING(DEFAULT_TIMEOUT) ")."); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + diff --git a/drivers/watchdog/pcwd.c b/drivers/watchdog/pcwd.c new file mode 100644 index 00000000..75694cf2 --- /dev/null +++ b/drivers/watchdog/pcwd.c @@ -0,0 +1,1015 @@ +/* + * PC Watchdog Driver + * by Ken Hollis (khollis@bitgate.com) + * + * Permission granted from Simon Machell (smachell@berkprod.com) + * Written for the Linux Kernel, and GPLed by Ken Hollis + * + * 960107 Added request_region routines, modulized the whole thing. + * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added + * WD_TIMEOUT define. + * 960216 Added eof marker on the file, and changed verbose messages. + * 960716 Made functional and cosmetic changes to the source for + * inclusion in Linux 2.0.x kernels, thanks to Alan Cox. + * 960717 Removed read/seek routines, replaced with ioctl. Also, added + * check_region command due to Alan's suggestion. + * 960821 Made changes to compile in newer 2.0.x kernels. Added + * "cold reboot sense" entry. + * 960825 Made a few changes to code, deleted some defines and made + * typedefs to replace them. Made heartbeat reset only available + * via ioctl, and removed the write routine. + * 960828 Added new items for PC Watchdog Rev.C card. + * 960829 Changed around all of the IOCTLs, added new features, + * added watchdog disable/re-enable routines. Added firmware + * version reporting. Added read routine for temperature. + * Removed some extra defines, added an autodetect Revision + * routine. + * 961006 Revised some documentation, fixed some cosmetic bugs. Made + * drivers to panic the system if it's overheating at bootup. + * 961118 Changed some verbiage on some of the output, tidied up + * code bits, and added compatibility to 2.1.x. + * 970912 Enabled board on open and disable on close. + * 971107 Took account of recent VFS changes (broke read). + * 971210 Disable board on initialisation in case board already ticking. + * 971222 Changed open/close for temperature handling + * Michael Meskes <meskes@debian.org>. + * 980112 Used minor numbers from include/linux/miscdevice.h + * 990403 Clear reset status after reading control status register in + * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>] + * 990605 Made changes to code to support Firmware 1.22a, added + * fairly useless proc entry. + * 990610 removed said useless proc code for the merge <alan> + * 000403 Removed last traces of proc code. <davej> + * 011214 Added nowayout module option to override + * CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com> + * Added timeout module option to override default + */ + +/* + * A bells and whistles driver is available from http://www.pcwd.de/ + * More info available at http://www.berkprod.com/ or + * http://www.pcwatchdog.com/ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/delay.h> /* For mdelay function */ +#include <linux/timer.h> /* For timer related operations */ +#include <linux/jiffies.h> /* For jiffies stuff */ +#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#include <linux/watchdog.h> /* For the watchdog specific items */ +#include <linux/reboot.h> /* For kernel_power_off() */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/fs.h> /* For file operations */ +#include <linux/isa.h> /* For isa devices */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <linux/io.h> /* For inb/outb/... */ + +/* Module and version information */ +#define WATCHDOG_VERSION "1.20" +#define WATCHDOG_DATE "18 Feb 2007" +#define WATCHDOG_DRIVER_NAME "ISA-PC Watchdog" +#define WATCHDOG_NAME "pcwd" +#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION "\n" + +/* + * It should be noted that PCWD_REVISION_B was removed because A and B + * are essentially the same types of card, with the exception that B + * has temperature reporting. Since I didn't receive a Rev.B card, + * the Rev.B card is not supported. (It's a good thing too, as they + * are no longer in production.) + */ +#define PCWD_REVISION_A 1 +#define PCWD_REVISION_C 2 + +/* + * These are the auto-probe addresses available. + * + * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350. + * Revision A has an address range of 2 addresses, while Revision C has 4. + */ +#define PCWD_ISA_NR_CARDS 3 +static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; + +/* + * These are the defines that describe the control status bits for the + * PCI-PC Watchdog card. +*/ +/* Port 1 : Control Status #1 for the PC Watchdog card, revision A. */ +#define WD_WDRST 0x01 /* Previously reset state */ +#define WD_T110 0x02 /* Temperature overheat sense */ +#define WD_HRTBT 0x04 /* Heartbeat sense */ +#define WD_RLY2 0x08 /* External relay triggered */ +#define WD_SRLY2 0x80 /* Software external relay triggered */ +/* Port 1 : Control Status #1 for the PC Watchdog card, revision C. */ +#define WD_REVC_WTRP 0x01 /* Watchdog Trip status */ +#define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */ +#define WD_REVC_TTRP 0x04 /* Temperature Trip status */ +#define WD_REVC_RL2A 0x08 /* Relay 2 activated by + on-board processor */ +#define WD_REVC_RL1A 0x10 /* Relay 1 active */ +#define WD_REVC_R2DS 0x40 /* Relay 2 disable */ +#define WD_REVC_RLY2 0x80 /* Relay 2 activated? */ +/* Port 2 : Control Status #2 */ +#define WD_WDIS 0x10 /* Watchdog Disabled */ +#define WD_ENTP 0x20 /* Watchdog Enable Temperature Trip */ +#define WD_SSEL 0x40 /* Watchdog Switch Select + (1:SW1 <-> 0:SW2) */ +#define WD_WCMD 0x80 /* Watchdog Command Mode */ + +/* max. time we give an ISA watchdog card to process a command */ +/* 500ms for each 4 bit response (according to spec.) */ +#define ISA_COMMAND_TIMEOUT 1000 + +/* Watchdog's internal commands */ +#define CMD_ISA_IDLE 0x00 +#define CMD_ISA_VERSION_INTEGER 0x01 +#define CMD_ISA_VERSION_TENTH 0x02 +#define CMD_ISA_VERSION_HUNDRETH 0x03 +#define CMD_ISA_VERSION_MINOR 0x04 +#define CMD_ISA_SWITCH_SETTINGS 0x05 +#define CMD_ISA_RESET_PC 0x06 +#define CMD_ISA_ARM_0 0x07 +#define CMD_ISA_ARM_30 0x08 +#define CMD_ISA_ARM_60 0x09 +#define CMD_ISA_DELAY_TIME_2SECS 0x0A +#define CMD_ISA_DELAY_TIME_4SECS 0x0B +#define CMD_ISA_DELAY_TIME_8SECS 0x0C +#define CMD_ISA_RESET_RELAYS 0x0D + +/* Watchdog's Dip Switch heartbeat values */ +static const int heartbeat_tbl[] = { + 20, /* OFF-OFF-OFF = 20 Sec */ + 40, /* OFF-OFF-ON = 40 Sec */ + 60, /* OFF-ON-OFF = 1 Min */ + 300, /* OFF-ON-ON = 5 Min */ + 600, /* ON-OFF-OFF = 10 Min */ + 1800, /* ON-OFF-ON = 30 Min */ + 3600, /* ON-ON-OFF = 1 Hour */ + 7200, /* ON-ON-ON = 2 hour */ +}; + +/* + * We are using an kernel timer to do the pinging of the watchdog + * every ~500ms. We try to set the internal heartbeat of the + * watchdog to 2 ms. + */ + +#define WDT_INTERVAL (HZ/2+1) + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* internal variables */ +static unsigned long open_allowed; +static char expect_close; +static int temp_panic; + +/* this is private data for each ISA-PC watchdog card */ +static struct { + char fw_ver_str[6]; /* The cards firmware version */ + int revision; /* The card's revision */ + int supports_temp; /* Whether or not the card has + a temperature device */ + int command_mode; /* Whether or not the card is in + command mode */ + int boot_status; /* The card's boot status */ + int io_addr; /* The cards I/O address */ + spinlock_t io_lock; /* the lock for io operations */ + struct timer_list timer; /* The timer that pings the watchdog */ + unsigned long next_heartbeat; /* the next_heartbeat for the timer */ +} pcwd_private; + +/* module parameters */ +#define QUIET 0 /* Default */ +#define VERBOSE 1 /* Verbose */ +#define DEBUG 2 /* print fancy stuff too */ +static int debug = QUIET; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, + "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)"); + +/* default heartbeat = delay-time from dip-switches */ +#define WATCHDOG_HEARTBEAT 0 +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(2 <= heartbeat <= 7200 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Internal functions + */ + +static int send_isa_command(int cmd) +{ + int i; + int control_status; + int port0, last_port0; /* Double read for stabilising */ + + if (debug >= DEBUG) + pr_debug("sending following data cmd=0x%02x\n", cmd); + + /* The WCMD bit must be 1 and the command is only 4 bits in size */ + control_status = (cmd & 0x0F) | WD_WCMD; + outb_p(control_status, pcwd_private.io_addr + 2); + udelay(ISA_COMMAND_TIMEOUT); + + port0 = inb_p(pcwd_private.io_addr); + for (i = 0; i < 25; ++i) { + last_port0 = port0; + port0 = inb_p(pcwd_private.io_addr); + + if (port0 == last_port0) + break; /* Data is stable */ + + udelay(250); + } + + if (debug >= DEBUG) + pr_debug("received following data for cmd=0x%02x: port0=0x%02x last_port0=0x%02x\n", + cmd, port0, last_port0); + + return port0; +} + +static int set_command_mode(void) +{ + int i, found = 0, count = 0; + + /* Set the card into command mode */ + spin_lock(&pcwd_private.io_lock); + while ((!found) && (count < 3)) { + i = send_isa_command(CMD_ISA_IDLE); + + if (i == 0x00) + found = 1; + else if (i == 0xF3) { + /* Card does not like what we've done to it */ + outb_p(0x00, pcwd_private.io_addr + 2); + udelay(1200); /* Spec says wait 1ms */ + outb_p(0x00, pcwd_private.io_addr + 2); + udelay(ISA_COMMAND_TIMEOUT); + } + count++; + } + spin_unlock(&pcwd_private.io_lock); + pcwd_private.command_mode = found; + + if (debug >= DEBUG) + pr_debug("command_mode=%d\n", pcwd_private.command_mode); + + return found; +} + +static void unset_command_mode(void) +{ + /* Set the card into normal mode */ + spin_lock(&pcwd_private.io_lock); + outb_p(0x00, pcwd_private.io_addr + 2); + udelay(ISA_COMMAND_TIMEOUT); + spin_unlock(&pcwd_private.io_lock); + + pcwd_private.command_mode = 0; + + if (debug >= DEBUG) + pr_debug("command_mode=%d\n", pcwd_private.command_mode); +} + +static inline void pcwd_check_temperature_support(void) +{ + if (inb(pcwd_private.io_addr) != 0xF0) + pcwd_private.supports_temp = 1; +} + +static inline void pcwd_get_firmware(void) +{ + int one, ten, hund, minor; + + strcpy(pcwd_private.fw_ver_str, "ERROR"); + + if (set_command_mode()) { + one = send_isa_command(CMD_ISA_VERSION_INTEGER); + ten = send_isa_command(CMD_ISA_VERSION_TENTH); + hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH); + minor = send_isa_command(CMD_ISA_VERSION_MINOR); + sprintf(pcwd_private.fw_ver_str, "%c.%c%c%c", + one, ten, hund, minor); + } + unset_command_mode(); + + return; +} + +static inline int pcwd_get_option_switches(void) +{ + int option_switches = 0; + + if (set_command_mode()) { + /* Get switch settings */ + option_switches = send_isa_command(CMD_ISA_SWITCH_SETTINGS); + } + + unset_command_mode(); + return option_switches; +} + +static void pcwd_show_card_info(void) +{ + int option_switches; + + /* Get some extra info from the hardware (in command/debug/diag mode) */ + if (pcwd_private.revision == PCWD_REVISION_A) + pr_info("ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", + pcwd_private.io_addr); + else if (pcwd_private.revision == PCWD_REVISION_C) { + pcwd_get_firmware(); + pr_info("ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n", + pcwd_private.io_addr, pcwd_private.fw_ver_str); + option_switches = pcwd_get_option_switches(); + pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + /* Reprogram internal heartbeat to 2 seconds */ + if (set_command_mode()) { + send_isa_command(CMD_ISA_DELAY_TIME_2SECS); + unset_command_mode(); + } + } + + if (pcwd_private.supports_temp) + pr_info("Temperature Option Detected\n"); + + if (pcwd_private.boot_status & WDIOF_CARDRESET) + pr_info("Previous reboot was caused by the card\n"); + + if (pcwd_private.boot_status & WDIOF_OVERHEAT) { + pr_emerg("Card senses a CPU Overheat. Panicking!\n"); + pr_emerg("CPU Overheat\n"); + } + + if (pcwd_private.boot_status == 0) + pr_info("No previous trip detected - Cold boot or reset\n"); +} + +static void pcwd_timer_ping(unsigned long data) +{ + int wdrst_stat; + + /* If we got a heartbeat pulse within the WDT_INTERVAL + * we agree to ping the WDT */ + if (time_before(jiffies, pcwd_private.next_heartbeat)) { + /* Ping the watchdog */ + spin_lock(&pcwd_private.io_lock); + if (pcwd_private.revision == PCWD_REVISION_A) { + /* Rev A cards are reset by setting the + WD_WDRST bit in register 1 */ + wdrst_stat = inb_p(pcwd_private.io_addr); + wdrst_stat &= 0x0F; + wdrst_stat |= WD_WDRST; + + outb_p(wdrst_stat, pcwd_private.io_addr + 1); + } else { + /* Re-trigger watchdog by writing to port 0 */ + outb_p(0x00, pcwd_private.io_addr); + } + + /* Re-set the timer interval */ + mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL); + + spin_unlock(&pcwd_private.io_lock); + } else { + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); + } +} + +static int pcwd_start(void) +{ + int stat_reg; + + pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ); + + /* Start the timer */ + mod_timer(&pcwd_private.timer, jiffies + WDT_INTERVAL); + + /* Enable the port */ + if (pcwd_private.revision == PCWD_REVISION_C) { + spin_lock(&pcwd_private.io_lock); + outb_p(0x00, pcwd_private.io_addr + 3); + udelay(ISA_COMMAND_TIMEOUT); + stat_reg = inb_p(pcwd_private.io_addr + 2); + spin_unlock(&pcwd_private.io_lock); + if (stat_reg & WD_WDIS) { + pr_info("Could not start watchdog\n"); + return -EIO; + } + } + + if (debug >= VERBOSE) + pr_debug("Watchdog started\n"); + + return 0; +} + +static int pcwd_stop(void) +{ + int stat_reg; + + /* Stop the timer */ + del_timer(&pcwd_private.timer); + + /* Disable the board */ + if (pcwd_private.revision == PCWD_REVISION_C) { + spin_lock(&pcwd_private.io_lock); + outb_p(0xA5, pcwd_private.io_addr + 3); + udelay(ISA_COMMAND_TIMEOUT); + outb_p(0xA5, pcwd_private.io_addr + 3); + udelay(ISA_COMMAND_TIMEOUT); + stat_reg = inb_p(pcwd_private.io_addr + 2); + spin_unlock(&pcwd_private.io_lock); + if ((stat_reg & WD_WDIS) == 0) { + pr_info("Could not stop watchdog\n"); + return -EIO; + } + } + + if (debug >= VERBOSE) + pr_debug("Watchdog stopped\n"); + + return 0; +} + +static int pcwd_keepalive(void) +{ + /* user land ping */ + pcwd_private.next_heartbeat = jiffies + (heartbeat * HZ); + + if (debug >= DEBUG) + pr_debug("Watchdog keepalive signal send\n"); + + return 0; +} + +static int pcwd_set_heartbeat(int t) +{ + if (t < 2 || t > 7200) /* arbitrary upper limit */ + return -EINVAL; + + heartbeat = t; + + if (debug >= VERBOSE) + pr_debug("New heartbeat: %d\n", heartbeat); + + return 0; +} + +static int pcwd_get_status(int *status) +{ + int control_status; + + *status = 0; + spin_lock(&pcwd_private.io_lock); + if (pcwd_private.revision == PCWD_REVISION_A) + /* Rev A cards return status information from + * the base register, which is used for the + * temperature in other cards. */ + control_status = inb(pcwd_private.io_addr); + else { + /* Rev C cards return card status in the base + * address + 1 register. And use different bits + * to indicate a card initiated reset, and an + * over-temperature condition. And the reboot + * status can be reset. */ + control_status = inb(pcwd_private.io_addr + 1); + } + spin_unlock(&pcwd_private.io_lock); + + if (pcwd_private.revision == PCWD_REVISION_A) { + if (control_status & WD_WDRST) + *status |= WDIOF_CARDRESET; + + if (control_status & WD_T110) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) { + pr_info("Temperature overheat trip!\n"); + kernel_power_off(); + } + } + } else { + if (control_status & WD_REVC_WTRP) + *status |= WDIOF_CARDRESET; + + if (control_status & WD_REVC_TTRP) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) { + pr_info("Temperature overheat trip!\n"); + kernel_power_off(); + } + } + } + + return 0; +} + +static int pcwd_clear_status(void) +{ + int control_status; + + if (pcwd_private.revision == PCWD_REVISION_C) { + spin_lock(&pcwd_private.io_lock); + + if (debug >= VERBOSE) + pr_info("clearing watchdog trip status\n"); + + control_status = inb_p(pcwd_private.io_addr + 1); + + if (debug >= DEBUG) { + pr_debug("status was: 0x%02x\n", control_status); + pr_debug("sending: 0x%02x\n", + (control_status & WD_REVC_R2DS)); + } + + /* clear reset status & Keep Relay 2 disable state as it is */ + outb_p((control_status & WD_REVC_R2DS), + pcwd_private.io_addr + 1); + + spin_unlock(&pcwd_private.io_lock); + } + return 0; +} + +static int pcwd_get_temperature(int *temperature) +{ + /* check that port 0 gives temperature info and no command results */ + if (pcwd_private.command_mode) + return -1; + + *temperature = 0; + if (!pcwd_private.supports_temp) + return -ENODEV; + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + spin_lock(&pcwd_private.io_lock); + *temperature = ((inb(pcwd_private.io_addr)) * 9 / 5) + 32; + spin_unlock(&pcwd_private.io_lock); + + if (debug >= DEBUG) { + pr_debug("temperature is: %d F\n", *temperature); + } + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static long pcwd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int rv; + int status; + int temperature; + int new_heartbeat; + int __user *argp = (int __user *)arg; + static const struct watchdog_info ident = { + .options = WDIOF_OVERHEAT | + WDIOF_CARDRESET | + WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "PCWD", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + + case WDIOC_GETSTATUS: + pcwd_get_status(&status); + return put_user(status, argp); + + case WDIOC_GETBOOTSTATUS: + return put_user(pcwd_private.boot_status, argp); + + case WDIOC_GETTEMP: + if (pcwd_get_temperature(&temperature)) + return -EFAULT; + + return put_user(temperature, argp); + + case WDIOC_SETOPTIONS: + if (pcwd_private.revision == PCWD_REVISION_C) { + if (get_user(rv, argp)) + return -EFAULT; + + if (rv & WDIOS_DISABLECARD) { + status = pcwd_stop(); + if (status < 0) + return status; + } + if (rv & WDIOS_ENABLECARD) { + status = pcwd_start(); + if (status < 0) + return status; + } + if (rv & WDIOS_TEMPPANIC) + temp_panic = 1; + } + return -EINVAL; + + case WDIOC_KEEPALIVE: + pcwd_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, argp)) + return -EFAULT; + + if (pcwd_set_heartbeat(new_heartbeat)) + return -EINVAL; + + pcwd_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, argp); + + default: + return -ENOTTY; + } + + return 0; +} + +static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len, + loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + pcwd_keepalive(); + } + return len; +} + +static int pcwd_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &open_allowed)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + /* Activate */ + pcwd_start(); + pcwd_keepalive(); + return nonseekable_open(inode, file); +} + +static int pcwd_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + pcwd_stop(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + pcwd_keepalive(); + } + expect_close = 0; + clear_bit(0, &open_allowed); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int temperature; + + if (pcwd_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user(buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int pcwd_temp_open(struct inode *inode, struct file *file) +{ + if (!pcwd_private.supports_temp) + return -ENODEV; + + return nonseekable_open(inode, file); +} + +static int pcwd_temp_close(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations pcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pcwd_write, + .unlocked_ioctl = pcwd_ioctl, + .open = pcwd_open, + .release = pcwd_close, +}; + +static struct miscdevice pcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pcwd_fops, +}; + +static const struct file_operations pcwd_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = pcwd_temp_read, + .open = pcwd_temp_open, + .release = pcwd_temp_close, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &pcwd_temp_fops, +}; + +/* + * Init & exit routines + */ + +static inline int get_revision(void) +{ + int r = PCWD_REVISION_C; + + spin_lock(&pcwd_private.io_lock); + /* REV A cards use only 2 io ports; test + * presumes a floating bus reads as 0xff. */ + if ((inb(pcwd_private.io_addr + 2) == 0xFF) || + (inb(pcwd_private.io_addr + 3) == 0xFF)) + r = PCWD_REVISION_A; + spin_unlock(&pcwd_private.io_lock); + + return r; +} + +/* + * The ISA cards have a heartbeat bit in one of the registers, which + * register is card dependent. The heartbeat bit is monitored, and if + * found, is considered proof that a Berkshire card has been found. + * The initial rate is once per second at board start up, then twice + * per second for normal operation. + */ +static int __devinit pcwd_isa_match(struct device *dev, unsigned int id) +{ + int base_addr = pcwd_ioports[id]; + int port0, last_port0; /* Reg 0, in case it's REV A */ + int port1, last_port1; /* Register 1 for REV C cards */ + int i; + int retval; + + if (debug >= DEBUG) + pr_debug("pcwd_isa_match id=%d\n", id); + + if (!request_region(base_addr, 4, "PCWD")) { + pr_info("Port 0x%04x unavailable\n", base_addr); + return 0; + } + + retval = 0; + + port0 = inb_p(base_addr); /* For REV A boards */ + port1 = inb_p(base_addr + 1); /* For REV C boards */ + if (port0 != 0xff || port1 != 0xff) { + /* Not an 'ff' from a floating bus, so must be a card! */ + for (i = 0; i < 4; ++i) { + + msleep(500); + + last_port0 = port0; + last_port1 = port1; + + port0 = inb_p(base_addr); + port1 = inb_p(base_addr + 1); + + /* Has either hearbeat bit changed? */ + if ((port0 ^ last_port0) & WD_HRTBT || + (port1 ^ last_port1) & WD_REVC_HRBT) { + retval = 1; + break; + } + } + } + release_region(base_addr, 4); + + return retval; +} + +static int __devinit pcwd_isa_probe(struct device *dev, unsigned int id) +{ + int ret; + + if (debug >= DEBUG) + pr_debug("pcwd_isa_probe id=%d\n", id); + + cards_found++; + if (cards_found == 1) + pr_info("v%s Ken Hollis (kenji@bitgate.com)\n", + WATCHDOG_VERSION); + + if (cards_found > 1) { + pr_err("This driver only supports 1 device\n"); + return -ENODEV; + } + + if (pcwd_ioports[id] == 0x0000) { + pr_err("No I/O-Address for card detected\n"); + return -ENODEV; + } + pcwd_private.io_addr = pcwd_ioports[id]; + + spin_lock_init(&pcwd_private.io_lock); + + /* Check card's revision */ + pcwd_private.revision = get_revision(); + + if (!request_region(pcwd_private.io_addr, + (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) { + pr_err("I/O address 0x%04x already in use\n", + pcwd_private.io_addr); + ret = -EIO; + goto error_request_region; + } + + /* Initial variables */ + pcwd_private.supports_temp = 0; + temp_panic = 0; + pcwd_private.boot_status = 0x0000; + + /* get the boot_status */ + pcwd_get_status(&pcwd_private.boot_status); + + /* clear the "card caused reboot" flag */ + pcwd_clear_status(); + + setup_timer(&pcwd_private.timer, pcwd_timer_ping, 0); + + /* Disable the board */ + pcwd_stop(); + + /* Check whether or not the card supports the temperature device */ + pcwd_check_temperature_support(); + + /* Show info about the card itself */ + pcwd_show_card_info(); + + /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ + if (heartbeat == 0) + heartbeat = heartbeat_tbl[(pcwd_get_option_switches() & 0x07)]; + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ + if (pcwd_set_heartbeat(heartbeat)) { + pcwd_set_heartbeat(WATCHDOG_HEARTBEAT); + pr_info("heartbeat value must be 2 <= heartbeat <= 7200, using %d\n", + WATCHDOG_HEARTBEAT); + } + + if (pcwd_private.supports_temp) { + ret = misc_register(&temp_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto error_misc_register_temp; + } + } + + ret = misc_register(&pcwd_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto error_misc_register_watchdog; + } + + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +error_misc_register_watchdog: + if (pcwd_private.supports_temp) + misc_deregister(&temp_miscdev); +error_misc_register_temp: + release_region(pcwd_private.io_addr, + (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4); +error_request_region: + pcwd_private.io_addr = 0x0000; + cards_found--; + return ret; +} + +static int __devexit pcwd_isa_remove(struct device *dev, unsigned int id) +{ + if (debug >= DEBUG) + pr_debug("pcwd_isa_remove id=%d\n", id); + + if (!pcwd_private.io_addr) + return 1; + + /* Disable the board */ + if (!nowayout) + pcwd_stop(); + + /* Deregister */ + misc_deregister(&pcwd_miscdev); + if (pcwd_private.supports_temp) + misc_deregister(&temp_miscdev); + release_region(pcwd_private.io_addr, + (pcwd_private.revision == PCWD_REVISION_A) ? 2 : 4); + pcwd_private.io_addr = 0x0000; + cards_found--; + + return 0; +} + +static void pcwd_isa_shutdown(struct device *dev, unsigned int id) +{ + if (debug >= DEBUG) + pr_debug("pcwd_isa_shutdown id=%d\n", id); + + pcwd_stop(); +} + +static struct isa_driver pcwd_isa_driver = { + .match = pcwd_isa_match, + .probe = pcwd_isa_probe, + .remove = __devexit_p(pcwd_isa_remove), + .shutdown = pcwd_isa_shutdown, + .driver = { + .owner = THIS_MODULE, + .name = WATCHDOG_NAME, + }, +}; + +static int __init pcwd_init_module(void) +{ + return isa_register_driver(&pcwd_isa_driver, PCWD_ISA_NR_CARDS); +} + +static void __exit pcwd_cleanup_module(void) +{ + isa_unregister_driver(&pcwd_isa_driver); + 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_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 new file mode 100644 index 00000000..c891399b --- /dev/null +++ b/drivers/watchdog/pcwd_pci.c @@ -0,0 +1,838 @@ +/* + * Berkshire PCI-PC Watchdog Card Driver + * + * (c) Copyright 2003-2007 Wim Van Sebroeck <wim@iguana.be>. + * + * Based on source code of the following authors: + * Ken Hollis <kenji@bitgate.com>, + * Lindsay Harris <lindsay@bluegum.com>, + * Alan Cox <alan@lxorguk.ukuu.org.uk>, + * Matt Domsch <Matt_Domsch@dell.com>, + * Rob Radez <rob@osinvestor.com> + * + * 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. + */ + +/* + * A bells and whistles driver is available from: + * http://www.kernel.org/pub/linux/kernel/people/wim/pcwd/pcwd_pci/ + * + * More info available at + * http://www.berkprod.com/ or http://www.pcwatchdog.com/ + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/delay.h> /* For mdelay function */ +#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#include <linux/watchdog.h> /* For the watchdog specific items */ +#include <linux/notifier.h> /* For notifier support */ +#include <linux/reboot.h> /* For reboot_notifier stuff */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/fs.h> /* For file operations */ +#include <linux/pci.h> /* For pci functions */ +#include <linux/ioport.h> /* For io-port access */ +#include <linux/spinlock.h> /* For spin_lock/spin_unlock/... */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ +#include <linux/io.h> /* For inb/outb/... */ + +/* Module and version information */ +#define WATCHDOG_VERSION "1.03" +#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" +#define WATCHDOG_NAME "pcwd_pci" +#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION + +/* Stuff for the PCI ID's */ +#ifndef PCI_VENDOR_ID_QUICKLOGIC +#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3 +#endif + +#ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD +#define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030 +#endif + +/* + * These are the defines that describe the control status bits for the + * PCI-PC Watchdog card. + */ +/* Port 1 : Control Status #1 */ +#define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ +#define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */ +#define WD_PCI_TTRP 0x04 /* Temperature Trip status */ +#define WD_PCI_RL2A 0x08 /* Relay 2 Active */ +#define WD_PCI_RL1A 0x10 /* Relay 1 Active */ +#define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temperature-trip / + reset */ +#define WD_PCI_RLY2 0x80 /* Activate Relay 2 on the board */ +/* Port 2 : Control Status #2 */ +#define WD_PCI_WDIS 0x10 /* Watchdog Disable */ +#define WD_PCI_ENTP 0x20 /* Enable Temperature Trip Reset */ +#define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ +#define WD_PCI_PCMD 0x80 /* PC has sent command */ + +/* according to documentation max. time to process a command for the pci + * watchdog card is 100 ms, so we give it 150 ms to do it's job */ +#define PCI_COMMAND_TIMEOUT 150 + +/* Watchdog's internal commands */ +#define CMD_GET_STATUS 0x04 +#define CMD_GET_FIRMWARE_VERSION 0x08 +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 +#define CMD_GET_CLEAR_RESET_COUNT 0x84 + +/* Watchdog's Dip Switch heartbeat values */ +static const int heartbeat_tbl[] = { + 5, /* OFF-OFF-OFF = 5 Sec */ + 10, /* OFF-OFF-ON = 10 Sec */ + 30, /* OFF-ON-OFF = 30 Sec */ + 60, /* OFF-ON-ON = 1 Min */ + 300, /* ON-OFF-OFF = 5 Min */ + 600, /* ON-OFF-ON = 10 Min */ + 1800, /* ON-ON-OFF = 30 Min */ + 3600, /* ON-ON-ON = 1 hour */ +}; + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* internal variables */ +static int temp_panic; +static unsigned long is_active; +static char expect_release; +/* this is private data for each PCI-PC watchdog card */ +static struct { + /* Wether or not the card has a temperature device */ + int supports_temp; + /* The card's boot status */ + int boot_status; + /* The cards I/O address */ + unsigned long io_addr; + /* the lock for io operations */ + spinlock_t io_lock; + /* the PCI-device */ + struct pci_dev *pdev; +} pcipcwd_private; + +/* module parameters */ +#define QUIET 0 /* Default */ +#define VERBOSE 1 /* Verbose */ +#define DEBUG 2 /* print fancy stuff too */ +static int debug = QUIET; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level: 0=Quiet, 1=Verbose, 2=Debug (default=0)"); + +#define WATCHDOG_HEARTBEAT 0 /* default heartbeat = + delay-time from dip-switches */ +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(0<heartbeat<65536 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Internal functions + */ + +static int send_command(int cmd, int *msb, int *lsb) +{ + int got_response, count; + + if (debug >= DEBUG) + pr_debug("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x\n", + cmd, *msb, *lsb); + + spin_lock(&pcipcwd_private.io_lock); + /* If a command requires data it should be written first. + * Data for commands with 8 bits of data should be written to port 4. + * Commands with 16 bits of data, should be written as LSB to port 4 + * and MSB to port 5. + * After the required data has been written then write the command to + * port 6. */ + outb_p(*lsb, pcipcwd_private.io_addr + 4); + outb_p(*msb, pcipcwd_private.io_addr + 5); + outb_p(cmd, pcipcwd_private.io_addr + 6); + + /* wait till the pci card processed the command, signaled by + * the WRSP bit in port 2 and give it a max. timeout of + * PCI_COMMAND_TIMEOUT to process */ + got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP; + for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); + count++) { + mdelay(1); + got_response = inb_p(pcipcwd_private.io_addr + 2) & WD_PCI_WRSP; + } + + if (debug >= DEBUG) { + if (got_response) { + pr_debug("time to process command was: %d ms\n", + count); + } else { + pr_debug("card did not respond on command!\n"); + } + } + + if (got_response) { + /* read back response */ + *lsb = inb_p(pcipcwd_private.io_addr + 4); + *msb = inb_p(pcipcwd_private.io_addr + 5); + + /* clear WRSP bit */ + inb_p(pcipcwd_private.io_addr + 6); + + if (debug >= DEBUG) + pr_debug("received following data for cmd=0x%02x: msb=0x%02x lsb=0x%02x\n", + cmd, *msb, *lsb); + } + + spin_unlock(&pcipcwd_private.io_lock); + + return got_response; +} + +static inline void pcipcwd_check_temperature_support(void) +{ + if (inb_p(pcipcwd_private.io_addr) != 0xF0) + pcipcwd_private.supports_temp = 1; +} + +static int pcipcwd_get_option_switches(void) +{ + int option_switches; + + option_switches = inb_p(pcipcwd_private.io_addr + 3); + return option_switches; +} + +static void pcipcwd_show_card_info(void) +{ + int got_fw_rev, fw_rev_major, fw_rev_minor; + char fw_ver_str[20]; /* The cards firmware version */ + int option_switches; + + got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, + &fw_rev_minor); + if (got_fw_rev) + sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); + else + sprintf(fw_ver_str, "<card no answer>"); + + /* Get switch settings */ + option_switches = pcipcwd_get_option_switches(); + + pr_info("Found card at port 0x%04x (Firmware: %s) %s temp option\n", + (int) pcipcwd_private.io_addr, fw_ver_str, + (pcipcwd_private.supports_temp ? "with" : "without")); + + pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + if (pcipcwd_private.boot_status & WDIOF_CARDRESET) + pr_info("Previous reset was caused by the Watchdog card\n"); + + if (pcipcwd_private.boot_status & WDIOF_OVERHEAT) + pr_info("Card sensed a CPU Overheat\n"); + + if (pcipcwd_private.boot_status == 0) + pr_info("No previous trip detected - Cold boot or reset\n"); +} + +static int pcipcwd_start(void) +{ + int stat_reg; + + spin_lock(&pcipcwd_private.io_lock); + outb_p(0x00, pcipcwd_private.io_addr + 3); + udelay(1000); + + stat_reg = inb_p(pcipcwd_private.io_addr + 2); + spin_unlock(&pcipcwd_private.io_lock); + + if (stat_reg & WD_PCI_WDIS) { + pr_err("Card timer not enabled\n"); + return -1; + } + + if (debug >= VERBOSE) + pr_debug("Watchdog started\n"); + + return 0; +} + +static int pcipcwd_stop(void) +{ + int stat_reg; + + spin_lock(&pcipcwd_private.io_lock); + outb_p(0xA5, pcipcwd_private.io_addr + 3); + udelay(1000); + + outb_p(0xA5, pcipcwd_private.io_addr + 3); + udelay(1000); + + stat_reg = inb_p(pcipcwd_private.io_addr + 2); + spin_unlock(&pcipcwd_private.io_lock); + + if (!(stat_reg & WD_PCI_WDIS)) { + pr_err("Card did not acknowledge disable attempt\n"); + return -1; + } + + if (debug >= VERBOSE) + pr_debug("Watchdog stopped\n"); + + return 0; +} + +static int pcipcwd_keepalive(void) +{ + /* Re-trigger watchdog by writing to port 0 */ + spin_lock(&pcipcwd_private.io_lock); + outb_p(0x42, pcipcwd_private.io_addr); /* send out any data */ + spin_unlock(&pcipcwd_private.io_lock); + + if (debug >= DEBUG) + pr_debug("Watchdog keepalive signal send\n"); + + return 0; +} + +static int pcipcwd_set_heartbeat(int t) +{ + int t_msb = t / 256; + int t_lsb = t % 256; + + if ((t < 0x0001) || (t > 0xFFFF)) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb); + + heartbeat = t; + if (debug >= VERBOSE) + pr_debug("New heartbeat: %d\n", heartbeat); + + return 0; +} + +static int pcipcwd_get_status(int *status) +{ + int control_status; + + *status = 0; + control_status = inb_p(pcipcwd_private.io_addr + 1); + if (control_status & WD_PCI_WTRP) + *status |= WDIOF_CARDRESET; + if (control_status & WD_PCI_TTRP) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) + panic(KBUILD_MODNAME ": Temperature overheat trip!\n"); + } + + if (debug >= DEBUG) + pr_debug("Control Status #1: 0x%02x\n", control_status); + + return 0; +} + +static int pcipcwd_clear_status(void) +{ + int control_status; + int msb; + int reset_counter; + + if (debug >= VERBOSE) + pr_info("clearing watchdog trip status & LED\n"); + + control_status = inb_p(pcipcwd_private.io_addr + 1); + + if (debug >= DEBUG) { + pr_debug("status was: 0x%02x\n", control_status); + pr_debug("sending: 0x%02x\n", + (control_status & WD_PCI_R2DS) | WD_PCI_WTRP); + } + + /* clear trip status & LED and keep mode of relay 2 */ + outb_p((control_status & WD_PCI_R2DS) | WD_PCI_WTRP, + pcipcwd_private.io_addr + 1); + + /* clear reset counter */ + msb = 0; + reset_counter = 0xff; + send_command(CMD_GET_CLEAR_RESET_COUNT, &msb, &reset_counter); + + if (debug >= DEBUG) { + pr_debug("reset count was: 0x%02x\n", reset_counter); + } + + return 0; +} + +static int pcipcwd_get_temperature(int *temperature) +{ + *temperature = 0; + if (!pcipcwd_private.supports_temp) + return -ENODEV; + + spin_lock(&pcipcwd_private.io_lock); + *temperature = inb_p(pcipcwd_private.io_addr); + spin_unlock(&pcipcwd_private.io_lock); + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + *temperature = (*temperature * 9 / 5) + 32; + + if (debug >= DEBUG) { + pr_debug("temperature is: %d F\n", *temperature); + } + + return 0; +} + +static int pcipcwd_get_timeleft(int *time_left) +{ + int msb; + int lsb; + + /* Read the time that's left before rebooting */ + /* Note: if the board is not yet armed then we will read 0xFFFF */ + send_command(CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb); + + *time_left = (msb << 8) + lsb; + + if (debug >= VERBOSE) + pr_debug("Time left before next reboot: %d\n", *time_left); + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t pcipcwd_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + expect_release = 0; + + /* scan to see whether or not we got the + * magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + pcipcwd_keepalive(); + } + return len; +} + +static long pcipcwd_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_OVERHEAT | + WDIOF_CARDRESET | + WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = WATCHDOG_DRIVER_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + { + int status; + pcipcwd_get_status(&status); + return put_user(status, p); + } + + case WDIOC_GETBOOTSTATUS: + return put_user(pcipcwd_private.boot_status, p); + + case WDIOC_GETTEMP: + { + int temperature; + + if (pcipcwd_get_temperature(&temperature)) + return -EFAULT; + + return put_user(temperature, p); + } + + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + if (pcipcwd_stop()) + return -EIO; + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + if (pcipcwd_start()) + return -EIO; + retval = 0; + } + + if (new_options & WDIOS_TEMPPANIC) { + temp_panic = 1; + retval = 0; + } + + return retval; + } + + case WDIOC_KEEPALIVE: + pcipcwd_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; + + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (pcipcwd_set_heartbeat(new_heartbeat)) + return -EINVAL; + + pcipcwd_keepalive(); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + case WDIOC_GETTIMELEFT: + { + int time_left; + + if (pcipcwd_get_timeleft(&time_left)) + return -EFAULT; + + return put_user(time_left, p); + } + + default: + return -ENOTTY; + } +} + +static int pcipcwd_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &is_active)) { + if (debug >= VERBOSE) + pr_err("Attempt to open already opened device\n"); + return -EBUSY; + } + + /* Activate */ + pcipcwd_start(); + pcipcwd_keepalive(); + return nonseekable_open(inode, file); +} + +static int pcipcwd_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (expect_release == 42) { + pcipcwd_stop(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + pcipcwd_keepalive(); + } + expect_release = 0; + clear_bit(0, &is_active); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t pcipcwd_temp_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + int temperature; + + if (pcipcwd_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user(data, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int pcipcwd_temp_open(struct inode *inode, struct file *file) +{ + if (!pcipcwd_private.supports_temp) + return -ENODEV; + + return nonseekable_open(inode, file); +} + +static int pcipcwd_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Notify system + */ + +static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + pcipcwd_stop(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations pcipcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pcipcwd_write, + .unlocked_ioctl = pcipcwd_ioctl, + .open = pcipcwd_open, + .release = pcipcwd_release, +}; + +static struct miscdevice pcipcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pcipcwd_fops, +}; + +static const struct file_operations pcipcwd_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = pcipcwd_temp_read, + .open = pcipcwd_temp_open, + .release = pcipcwd_temp_release, +}; + +static struct miscdevice pcipcwd_temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &pcipcwd_temp_fops, +}; + +static struct notifier_block pcipcwd_notifier = { + .notifier_call = pcipcwd_notify_sys, +}; + +/* + * Init & exit routines + */ + +static int __devinit pcipcwd_card_init(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int ret = -EIO; + + cards_found++; + if (cards_found == 1) + pr_info("%s\n", DRIVER_VERSION); + + if (cards_found > 1) { + pr_err("This driver only supports 1 device\n"); + return -ENODEV; + } + + if (pci_enable_device(pdev)) { + pr_err("Not possible to enable PCI Device\n"); + return -ENODEV; + } + + if (pci_resource_start(pdev, 0) == 0x0000) { + pr_err("No I/O-Address for card detected\n"); + ret = -ENODEV; + goto err_out_disable_device; + } + + pcipcwd_private.pdev = pdev; + pcipcwd_private.io_addr = pci_resource_start(pdev, 0); + + if (pci_request_regions(pdev, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", + (int) pcipcwd_private.io_addr); + ret = -EIO; + goto err_out_disable_device; + } + + /* get the boot_status */ + pcipcwd_get_status(&pcipcwd_private.boot_status); + + /* clear the "card caused reboot" flag */ + pcipcwd_clear_status(); + + /* disable card */ + pcipcwd_stop(); + + /* Check whether or not the card supports the temperature device */ + pcipcwd_check_temperature_support(); + + /* Show info about the card itself */ + pcipcwd_show_card_info(); + + /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ + if (heartbeat == 0) + heartbeat = + heartbeat_tbl[(pcipcwd_get_option_switches() & 0x07)]; + + /* Check that the heartbeat value is within it's range ; + * if not reset to the default */ + if (pcipcwd_set_heartbeat(heartbeat)) { + pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT); + pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n", + WATCHDOG_HEARTBEAT); + } + + ret = register_reboot_notifier(&pcipcwd_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto err_out_release_region; + } + + if (pcipcwd_private.supports_temp) { + ret = misc_register(&pcipcwd_temp_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto err_out_unregister_reboot; + } + } + + ret = misc_register(&pcipcwd_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto err_out_misc_deregister; + } + + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +err_out_misc_deregister: + if (pcipcwd_private.supports_temp) + misc_deregister(&pcipcwd_temp_miscdev); +err_out_unregister_reboot: + unregister_reboot_notifier(&pcipcwd_notifier); +err_out_release_region: + pci_release_regions(pdev); +err_out_disable_device: + pci_disable_device(pdev); + return ret; +} + +static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) +{ + /* Stop the timer before we leave */ + if (!nowayout) + pcipcwd_stop(); + + /* Deregister */ + misc_deregister(&pcipcwd_miscdev); + if (pcipcwd_private.supports_temp) + misc_deregister(&pcipcwd_temp_miscdev); + unregister_reboot_notifier(&pcipcwd_notifier); + pci_release_regions(pdev); + pci_disable_device(pdev); + cards_found--; +} + +static DEFINE_PCI_DEVICE_TABLE(pcipcwd_pci_tbl) = { + { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0 }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl); + +static struct pci_driver pcipcwd_driver = { + .name = WATCHDOG_NAME, + .id_table = pcipcwd_pci_tbl, + .probe = pcipcwd_card_init, + .remove = __devexit_p(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); + + pr_info("Watchdog Module Unloaded\n"); +} + +module_init(pcipcwd_init_module); +module_exit(pcipcwd_cleanup_module); + +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 new file mode 100644 index 00000000..7b14d184 --- /dev/null +++ b/drivers/watchdog/pcwd_usb.c @@ -0,0 +1,826 @@ +/* + * Berkshire USB-PC Watchdog Card Driver + * + * (c) Copyright 2004-2007 Wim Van Sebroeck <wim@iguana.be>. + * + * Based on source code of the following authors: + * Ken Hollis <kenji@bitgate.com>, + * Alan Cox <alan@lxorguk.ukuu.org.uk>, + * Matt Domsch <Matt_Domsch@dell.com>, + * Rob Radez <rob@osinvestor.com>, + * Greg Kroah-Hartman <greg@kroah.com> + * + * 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. + * + * Thanks also to Simon Machell at Berkshire Products Inc. for + * providing the test hardware. More info is available at + * http://www.berkprod.com/ or http://www.pcwatchdog.com/ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> /* For module specific items */ +#include <linux/moduleparam.h> /* For new moduleparam's */ +#include <linux/types.h> /* For standard types (like size_t) */ +#include <linux/errno.h> /* For the -ENODEV/... values */ +#include <linux/kernel.h> /* For printk/panic/... */ +#include <linux/delay.h> /* For mdelay function */ +#include <linux/miscdevice.h> /* For MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) */ +#include <linux/watchdog.h> /* For the watchdog specific items */ +#include <linux/notifier.h> /* For notifier support */ +#include <linux/reboot.h> /* For reboot_notifier stuff */ +#include <linux/init.h> /* For __init/__exit/... */ +#include <linux/fs.h> /* For file operations */ +#include <linux/usb.h> /* For USB functions */ +#include <linux/slab.h> /* For kmalloc, ... */ +#include <linux/mutex.h> /* For mutex locking */ +#include <linux/hid.h> /* For HID_REQ_SET_REPORT & HID_DT_REPORT */ +#include <linux/uaccess.h> /* For copy_to_user/put_user/... */ + +#ifdef CONFIG_USB_DEBUG +static int debug = 1; +#else +static int debug; +#endif + +/* Use our own dbg macro */ + +#undef dbg +#ifndef DEBUG +#define DEBUG +#endif +#define dbg(format, ...) \ +do { \ + if (debug) \ + pr_debug(format "\n", ##__VA_ARGS__); \ +} while (0) + +/* Module and Version Information */ +#define DRIVER_VERSION "1.02" +#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>" +#define DRIVER_DESC "Berkshire USB-PC Watchdog driver" +#define DRIVER_LICENSE "GPL" +#define DRIVER_NAME "pcwd_usb" + +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 */ +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. " + "(0<heartbeat<65536 or 0=delay-time from dip-switches, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* The vendor and product id's for the USB-PC Watchdog card */ +#define USB_PCWD_VENDOR_ID 0x0c98 +#define USB_PCWD_PRODUCT_ID 0x1140 + +/* table of devices that work with this driver */ +static struct usb_device_id usb_pcwd_table[] = { + { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, usb_pcwd_table); + +/* according to documentation max. time to process a command for the USB + * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ +#define USB_COMMAND_TIMEOUT 250 + +/* Watchdog's internal commands */ +#define CMD_READ_TEMP 0x02 /* Read Temperature; + Re-trigger Watchdog */ +#define CMD_TRIGGER CMD_READ_TEMP +#define CMD_GET_STATUS 0x04 /* Get Status Information */ +#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ +#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current WatchdogTime */ +#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ +#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG + +/* Watchdog's Dip Switch heartbeat values */ +static const int heartbeat_tbl[] = { + 5, /* OFF-OFF-OFF = 5 Sec */ + 10, /* OFF-OFF-ON = 10 Sec */ + 30, /* OFF-ON-OFF = 30 Sec */ + 60, /* OFF-ON-ON = 1 Min */ + 300, /* ON-OFF-OFF = 5 Min */ + 600, /* ON-OFF-ON = 10 Min */ + 1800, /* ON-ON-OFF = 30 Min */ + 3600, /* ON-ON-ON = 1 hour */ +}; + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* some internal variables */ +static unsigned long is_active; +static char expect_release; + +/* Structure to hold all of our device specific stuff */ +struct usb_pcwd_private { + /* save off the usb device pointer */ + struct usb_device *udev; + /* the interface for this device */ + struct usb_interface *interface; + + /* the interface number used for cmd's */ + unsigned int interface_number; + + /* the buffer to intr data */ + unsigned char *intr_buffer; + /* the dma address for the intr buffer */ + dma_addr_t intr_dma; + /* the size of the intr buffer */ + size_t intr_size; + /* the urb used for the intr pipe */ + struct urb *intr_urb; + + /* The command that is reported back */ + unsigned char cmd_command; + /* The data MSB that is reported back */ + unsigned char cmd_data_msb; + /* The data LSB that is reported back */ + unsigned char cmd_data_lsb; + /* true if we received a report after a command */ + atomic_t cmd_received; + + /* Wether or not the device exists */ + int exists; + /* locks this structure */ + struct mutex mtx; +}; +static struct usb_pcwd_private *usb_pcwd_device; + +/* prevent races between open() and disconnect() */ +static DEFINE_MUTEX(disconnect_mutex); + +/* local function prototypes */ +static int usb_pcwd_probe(struct usb_interface *interface, + const struct usb_device_id *id); +static void usb_pcwd_disconnect(struct usb_interface *interface); + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver usb_pcwd_driver = { + .name = DRIVER_NAME, + .probe = usb_pcwd_probe, + .disconnect = usb_pcwd_disconnect, + .id_table = usb_pcwd_table, +}; + + +static void usb_pcwd_intr_done(struct urb *urb) +{ + struct usb_pcwd_private *usb_pcwd = + (struct usb_pcwd_private *)urb->context; + unsigned char *data = usb_pcwd->intr_buffer; + int retval; + + switch (urb->status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%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); + goto resubmit; + } + + dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x", + data[0], data[1], data[2]); + + usb_pcwd->cmd_command = data[0]; + usb_pcwd->cmd_data_msb = data[1]; + usb_pcwd->cmd_data_lsb = data[2]; + + /* notify anyone waiting that the cmd has finished */ + atomic_set(&usb_pcwd->cmd_received, 1); + +resubmit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + pr_err("can't resubmit intr, usb_submit_urb failed with result %d\n", + retval); +} + +static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, + unsigned char cmd, unsigned char *msb, unsigned char *lsb) +{ + int got_response, count; + unsigned char buf[6]; + + /* 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[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", + buf[0], buf[1], buf[2]); + + atomic_set(&usb_pcwd->cmd_received, 0); + + if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0), + HID_REQ_SET_REPORT, HID_DT_REPORT, + 0x0200, usb_pcwd->interface_number, buf, 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); + } + /* wait till the usb card processed the command, + * with a max. timeout of USB_COMMAND_TIMEOUT */ + got_response = 0; + for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); + count++) { + mdelay(1); + if (atomic_read(&usb_pcwd->cmd_received)) + got_response = 1; + } + + if ((got_response) && (cmd == usb_pcwd->cmd_command)) { + /* read back response */ + *msb = usb_pcwd->cmd_data_msb; + *lsb = usb_pcwd->cmd_data_lsb; + } + + return got_response; +} + +static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char msb = 0x00; + unsigned char lsb = 0x00; + int retval; + + /* Enable Watchdog */ + retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, + &msb, &lsb); + + if ((retval == 0) || (lsb == 0)) { + pr_err("Card did not acknowledge enable attempt\n"); + return -1; + } + + return 0; +} + +static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char msb = 0xA5; + unsigned char lsb = 0xC3; + int retval; + + /* Disable Watchdog */ + retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, + &msb, &lsb); + + if ((retval == 0) || (lsb != 0)) { + pr_err("Card did not acknowledge disable attempt\n"); + return -1; + } + + return 0; +} + +static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char dummy; + + /* Re-trigger Watchdog */ + usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy); + + return 0; +} + +static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t) +{ + unsigned char msb = t / 256; + unsigned char lsb = t % 256; + + if ((t < 0x0001) || (t > 0xFFFF)) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb); + + heartbeat = t; + return 0; +} + +static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, + int *temperature) +{ + unsigned char msb, lsb; + + usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb); + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + *temperature = (lsb * 9 / 5) + 32; + + return 0; +} + +static int usb_pcwd_get_timeleft(struct usb_pcwd_private *usb_pcwd, + int *time_left) +{ + unsigned char msb, lsb; + + /* Read the time that's left before rebooting */ + /* Note: if the board is not yet armed then we will read 0xFFFF */ + usb_pcwd_send_command(usb_pcwd, CMD_READ_WATCHDOG_TIMEOUT, &msb, &lsb); + + *time_left = (msb << 8) + lsb; + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t usb_pcwd_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + expect_release = 0; + + /* scan to see whether or not we got the + * magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + usb_pcwd_keepalive(usb_pcwd_device); + } + return len; +} + +static long usb_pcwd_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = DRIVER_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_GETTEMP: + { + int temperature; + + if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) + return -EFAULT; + + return put_user(temperature, p); + } + + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + usb_pcwd_stop(usb_pcwd_device); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + usb_pcwd_start(usb_pcwd_device); + retval = 0; + } + + return retval; + } + + case WDIOC_KEEPALIVE: + usb_pcwd_keepalive(usb_pcwd_device); + return 0; + + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; + + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) + return -EINVAL; + + usb_pcwd_keepalive(usb_pcwd_device); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + case WDIOC_GETTIMELEFT: + { + int time_left; + + if (usb_pcwd_get_timeleft(usb_pcwd_device, &time_left)) + return -EFAULT; + + return put_user(time_left, p); + } + + default: + return -ENOTTY; + } +} + +static int usb_pcwd_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &is_active)) + return -EBUSY; + + /* Activate */ + usb_pcwd_start(usb_pcwd_device); + usb_pcwd_keepalive(usb_pcwd_device); + return nonseekable_open(inode, file); +} + +static int usb_pcwd_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (expect_release == 42) { + usb_pcwd_stop(usb_pcwd_device); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + usb_pcwd_keepalive(usb_pcwd_device); + } + expect_release = 0; + clear_bit(0, &is_active); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + int temperature; + + if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) + return -EFAULT; + + if (copy_to_user(data, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int usb_pcwd_temperature_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static int usb_pcwd_temperature_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Notify system + */ + +static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + usb_pcwd_stop(usb_pcwd_device); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations usb_pcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = usb_pcwd_write, + .unlocked_ioctl = usb_pcwd_ioctl, + .open = usb_pcwd_open, + .release = usb_pcwd_release, +}; + +static struct miscdevice usb_pcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &usb_pcwd_fops, +}; + +static const struct file_operations usb_pcwd_temperature_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = usb_pcwd_temperature_read, + .open = usb_pcwd_temperature_open, + .release = usb_pcwd_temperature_release, +}; + +static struct miscdevice usb_pcwd_temperature_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &usb_pcwd_temperature_fops, +}; + +static struct notifier_block usb_pcwd_notifier = { + .notifier_call = usb_pcwd_notify_sys, +}; + +/** + * usb_pcwd_delete + */ +static inline void usb_pcwd_delete(struct usb_pcwd_private *usb_pcwd) +{ + usb_free_urb(usb_pcwd->intr_urb); + if (usb_pcwd->intr_buffer != NULL) + usb_free_coherent(usb_pcwd->udev, usb_pcwd->intr_size, + usb_pcwd->intr_buffer, usb_pcwd->intr_dma); + kfree(usb_pcwd); +} + +/** + * usb_pcwd_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int usb_pcwd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_pcwd_private *usb_pcwd = NULL; + int pipe, maxp; + int retval = -ENOMEM; + int got_fw_rev; + unsigned char fw_rev_major, fw_rev_minor; + char fw_ver_str[20]; + unsigned char option_switches, dummy; + + cards_found++; + if (cards_found > 1) { + pr_err("This driver only supports 1 device\n"); + return -ENODEV; + } + + /* get the active interface descriptor */ + iface_desc = interface->cur_altsetting; + + /* check out that we have a HID device */ + if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) { + pr_err("The device isn't a Human Interface Device\n"); + return -ENODEV; + } + + /* check out the endpoint: it has to be Interrupt & IN */ + endpoint = &iface_desc->endpoint[0].desc; + + if (!usb_endpoint_is_int_in(endpoint)) { + /* we didn't find a Interrupt endpoint with direction IN */ + pr_err("Couldn't find an INTR & IN endpoint\n"); + return -ENODEV; + } + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + 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) { + pr_err("Out of memory\n"); + goto error; + } + + usb_pcwd_device = usb_pcwd; + + mutex_init(&usb_pcwd->mtx); + usb_pcwd->udev = udev; + usb_pcwd->interface = interface; + usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber; + usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? + le16_to_cpu(endpoint->wMaxPacketSize) : 8); + + /* set up the memory buffer's */ + usb_pcwd->intr_buffer = usb_alloc_coherent(udev, usb_pcwd->intr_size, + GFP_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) { + pr_err("Out of memory\n"); + goto error; + } + + /* initialise the intr urb's */ + usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe, + usb_pcwd->intr_buffer, usb_pcwd->intr_size, + usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval); + usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma; + usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* register our interrupt URB with the USB system */ + if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) { + pr_err("Problem registering interrupt URB\n"); + retval = -EIO; /* failure */ + goto error; + } + + /* The device exists and can be communicated with */ + usb_pcwd->exists = 1; + + /* disable card */ + usb_pcwd_stop(usb_pcwd); + + /* Get the Firmware Version */ + got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, + &fw_rev_major, &fw_rev_minor); + if (got_fw_rev) + sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); + else + sprintf(fw_ver_str, "<card no answer>"); + + pr_info("Found card (Firmware: %s) with temp option\n", fw_ver_str); + + /* Get switch settings */ + usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, + &option_switches); + + pr_info("Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + /* If heartbeat = 0 then we use the heartbeat from the dip-switches */ + if (heartbeat == 0) + heartbeat = heartbeat_tbl[(option_switches & 0x07)]; + + /* Check that the heartbeat value is within it's range ; + * if not reset to the default */ + if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) { + usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT); + pr_info("heartbeat value must be 0<heartbeat<65536, using %d\n", + WATCHDOG_HEARTBEAT); + } + + retval = register_reboot_notifier(&usb_pcwd_notifier); + if (retval != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", retval); + goto error; + } + + retval = misc_register(&usb_pcwd_temperature_miscdev); + if (retval != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, retval); + goto err_out_unregister_reboot; + } + + retval = misc_register(&usb_pcwd_miscdev); + if (retval != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, retval); + goto err_out_misc_deregister; + } + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, usb_pcwd); + + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +err_out_misc_deregister: + misc_deregister(&usb_pcwd_temperature_miscdev); +err_out_unregister_reboot: + unregister_reboot_notifier(&usb_pcwd_notifier); +error: + if (usb_pcwd) + usb_pcwd_delete(usb_pcwd); + usb_pcwd_device = NULL; + return retval; +} + + +/** + * usb_pcwd_disconnect + * + * Called by the usb core when the device is removed from the system. + * + * This routine guarantees that the driver will not submit any more urbs + * by clearing dev->udev. + */ +static void usb_pcwd_disconnect(struct usb_interface *interface) +{ + struct usb_pcwd_private *usb_pcwd; + + /* prevent races with open() */ + mutex_lock(&disconnect_mutex); + + usb_pcwd = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + mutex_lock(&usb_pcwd->mtx); + + /* Stop the timer before we leave */ + if (!nowayout) + usb_pcwd_stop(usb_pcwd); + + /* We should now stop communicating with the USB PCWD device */ + usb_pcwd->exists = 0; + + /* Deregister */ + misc_deregister(&usb_pcwd_miscdev); + misc_deregister(&usb_pcwd_temperature_miscdev); + unregister_reboot_notifier(&usb_pcwd_notifier); + + mutex_unlock(&usb_pcwd->mtx); + + /* Delete the USB PCWD device */ + usb_pcwd_delete(usb_pcwd); + + cards_found--; + + mutex_unlock(&disconnect_mutex); + + pr_info("USB PC Watchdog disconnected\n"); +} + +module_usb_driver(usb_pcwd_driver); diff --git a/drivers/watchdog/pika_wdt.c b/drivers/watchdog/pika_wdt.c new file mode 100644 index 00000000..7d3d471f --- /dev/null +++ b/drivers/watchdog/pika_wdt.c @@ -0,0 +1,302 @@ +/* + * 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_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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c new file mode 100644 index 00000000..6b8432f6 --- /dev/null +++ b/drivers/watchdog/pnx4008_wdt.c @@ -0,0 +1,231 @@ +/* + * drivers/char/watchdog/pnx4008_wdt.c + * + * Watchdog driver for PNX4008 board + * + * Authors: Dmitry Chigirev <source@mvista.com>, + * Vitaly Wool <vitalywool@gmail.com> + * Based on sa1100 driver, + * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> + * + * 2005-2006 (c) MontaVista Software, Inc. + * + * (C) 2012 Wolfram Sang, Pengutronix + * + * 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/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <mach/hardware.h> + +/* WatchDog Timer - Chapter 23 Page 207 */ + +#define DEFAULT_HEARTBEAT 19 +#define MAX_HEARTBEAT 60 + +/* Watchdog timer register set definition */ +#define WDTIM_INT(p) ((p) + 0x0) +#define WDTIM_CTRL(p) ((p) + 0x4) +#define WDTIM_COUNTER(p) ((p) + 0x8) +#define WDTIM_MCTRL(p) ((p) + 0xC) +#define WDTIM_MATCH0(p) ((p) + 0x10) +#define WDTIM_EMR(p) ((p) + 0x14) +#define WDTIM_PULSE(p) ((p) + 0x18) +#define WDTIM_RES(p) ((p) + 0x1C) + +/* WDTIM_INT bit definitions */ +#define MATCH_INT 1 + +/* WDTIM_CTRL bit definitions */ +#define COUNT_ENAB 1 +#define RESET_COUNT (1 << 1) +#define DEBUG_EN (1 << 2) + +/* WDTIM_MCTRL bit definitions */ +#define MR0_INT 1 +#undef RESET_COUNT0 +#define RESET_COUNT0 (1 << 2) +#define STOP_COUNT0 (1 << 2) +#define M_RES1 (1 << 3) +#define M_RES2 (1 << 4) +#define RESFRC1 (1 << 5) +#define RESFRC2 (1 << 6) + +/* WDTIM_EMR bit definitions */ +#define EXT_MATCH0 1 +#define MATCH_OUTPUT_HIGH (2 << 4) /*a MATCH_CTRL setting */ + +/* WDTIM_RES bit definitions */ +#define WDOG_RESET 1 /* read only */ + +#define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned int heartbeat = DEFAULT_HEARTBEAT; + +static DEFINE_SPINLOCK(io_lock); +static void __iomem *wdt_base; +struct clk *wdt_clk; + +static int pnx4008_wdt_start(struct watchdog_device *wdd) +{ + spin_lock(&io_lock); + + /* stop counter, initiate counter reset */ + writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); + /*wait for reset to complete. 100% guarantee event */ + while (readl(WDTIM_COUNTER(wdt_base))) + cpu_relax(); + /* internal and external reset, stop after that */ + writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, WDTIM_MCTRL(wdt_base)); + /* configure match output */ + writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); + /* clear interrupt, just in case */ + writel(MATCH_INT, WDTIM_INT(wdt_base)); + /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ + writel(0xFFFF, WDTIM_PULSE(wdt_base)); + writel(wdd->timeout * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); + /*enable counter, stop when debugger active */ + writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); + + spin_unlock(&io_lock); + return 0; +} + +static int pnx4008_wdt_stop(struct watchdog_device *wdd) +{ + spin_lock(&io_lock); + + writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ + + spin_unlock(&io_lock); + return 0; +} + +static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int new_timeout) +{ + wdd->timeout = new_timeout; + return 0; +} + +static const struct watchdog_info pnx4008_wdt_ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "PNX4008 Watchdog", +}; + +static const struct watchdog_ops pnx4008_wdt_ops = { + .owner = THIS_MODULE, + .start = pnx4008_wdt_start, + .stop = pnx4008_wdt_stop, + .set_timeout = pnx4008_wdt_set_timeout, +}; + +static struct watchdog_device pnx4008_wdd = { + .info = &pnx4008_wdt_ident, + .ops = &pnx4008_wdt_ops, + .min_timeout = 1, + .max_timeout = MAX_HEARTBEAT, +}; + +static int __devinit pnx4008_wdt_probe(struct platform_device *pdev) +{ + struct resource *r; + int ret = 0; + + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) + heartbeat = DEFAULT_HEARTBEAT; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + wdt_base = devm_request_and_ioremap(&pdev->dev, r); + if (!wdt_base) + return -EADDRINUSE; + + wdt_clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt_clk)) + return PTR_ERR(wdt_clk); + + ret = clk_enable(wdt_clk); + if (ret) + goto out; + + pnx4008_wdd.timeout = heartbeat; + 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) { + dev_err(&pdev->dev, "cannot register watchdog device\n"); + goto disable_clk; + } + + dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", + heartbeat); + + return 0; + +disable_clk: + clk_disable(wdt_clk); +out: + clk_put(wdt_clk); + return ret; +} + +static int __devexit pnx4008_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&pnx4008_wdd); + + clk_disable(wdt_clk); + clk_put(wdt_clk); + + return 0; +} + +static struct platform_driver platform_wdt_driver = { + .driver = { + .name = "pnx4008-watchdog", + .owner = THIS_MODULE, + }, + .probe = pnx4008_wdt_probe, + .remove = __devexit_p(pnx4008_wdt_remove), +}; + +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, uint, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat period in seconds from 1 to " + __MODULE_STRING(MAX_HEARTBEAT) ", default " + __MODULE_STRING(DEFAULT_HEARTBEAT)); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_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 00000000..1b62a7df --- /dev/null +++ b/drivers/watchdog/pnx833x_wdt.c @@ -0,0 +1,281 @@ +/* + * 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c new file mode 100644 index 00000000..547353a5 --- /dev/null +++ b/drivers/watchdog/rc32434_wdt.c @@ -0,0 +1,336 @@ +/* + * 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 MODULE_ALIAS_MISCDEV + (WATCHDOG_MINOR) */ +#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 <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 __devinit 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 = ioremap_nocache(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"); + goto unmap; + } + + pr_info("Watchdog Timer version " VERSION ", timer margin: %d sec\n", + timeout); + + return 0; + +unmap: + iounmap(wdt_reg); + return ret; +} + +static int __devexit rc32434_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&rc32434_wdt_miscdev); + iounmap(wdt_reg); + 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/rdc321x_wdt.c b/drivers/watchdog/rdc321x_wdt.c new file mode 100644 index 00000000..042ccc56 --- /dev/null +++ b/drivers/watchdog/rdc321x_wdt.c @@ -0,0 +1,301 @@ +/* + * 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/init.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 __devinit rdc321x_wdt_probe(struct platform_device *pdev) +{ + int err; + struct resource *r; + struct rdc321x_wdt_pdata *pdata; + + pdata = pdev->dev.platform_data; + 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 __devexit 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/riowd.c b/drivers/watchdog/riowd.c new file mode 100644 index 00000000..49e1b1c2 --- /dev/null +++ b/drivers/watchdog/riowd.c @@ -0,0 +1,252 @@ +/* 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/init.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 __devinit riowd_probe(struct platform_device *op) +{ + struct riowd *p; + int err = -EINVAL; + + if (riowd_device) + goto out; + + err = -ENOMEM; + p = kzalloc(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_free; + } + /* 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); + + dev_set_drvdata(&op->dev, p); + return 0; + +out_iounmap: + riowd_device = NULL; + of_iounmap(&op->resource[0], p->regs, 2); + +out_free: + kfree(p); + +out: + return err; +} + +static int __devexit riowd_remove(struct platform_device *op) +{ + struct riowd *p = dev_get_drvdata(&op->dev); + + misc_deregister(&riowd_miscdev); + of_iounmap(&op->resource[0], p->regs, 2); + kfree(p); + + 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 = __devexit_p(riowd_remove), +}; + +module_platform_driver(riowd_driver); diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c new file mode 100644 index 00000000..04e5a6de --- /dev/null +++ b/drivers/watchdog/s3c2410_wdt.c @@ -0,0 +1,544 @@ +/* linux/drivers/char/watchdog/s3c2410_wdt.c + * + * Copyright (c) 2004 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * S3C2410 Watchdog Timer Support + * + * Based on, softdog.c by Alan Cox, + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> /* for MODULE_ALIAS_MISCDEV */ +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/uaccess.h> +#include <linux/io.h> +#include <linux/cpufreq.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <mach/map.h> + +#undef S3C_VA_WATCHDOG +#define S3C_VA_WATCHDOG (0) + +#include <plat/regs-watchdog.h> + +#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) +#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) + +static bool nowayout = WATCHDOG_NOWAYOUT; +static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; +static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; +static int soft_noboot; +static int debug; + +module_param(tmr_margin, int, 0); +module_param(tmr_atboot, 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(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)"); + +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 DEFINE_SPINLOCK(wdt_lock); + +/* watchdog control routines */ + +#define DBG(fmt, ...) \ +do { \ + if (debug) \ + pr_info(fmt, ##__VA_ARGS__); \ +} while (0) + +/* functions */ + +static int s3c2410wdt_keepalive(struct watchdog_device *wdd) +{ + spin_lock(&wdt_lock); + writel(wdt_count, wdt_base + S3C2410_WTCNT); + spin_unlock(&wdt_lock); + + return 0; +} + +static void __s3c2410wdt_stop(void) +{ + unsigned long wtcon; + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); + writel(wtcon, wdt_base + S3C2410_WTCON); +} + +static int s3c2410wdt_stop(struct watchdog_device *wdd) +{ + spin_lock(&wdt_lock); + __s3c2410wdt_stop(); + spin_unlock(&wdt_lock); + + return 0; +} + +static int s3c2410wdt_start(struct watchdog_device *wdd) +{ + unsigned long wtcon; + + spin_lock(&wdt_lock); + + __s3c2410wdt_stop(); + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; + + if (soft_noboot) { + wtcon |= S3C2410_WTCON_INTEN; + wtcon &= ~S3C2410_WTCON_RSTEN; + } else { + wtcon &= ~S3C2410_WTCON_INTEN; + wtcon |= S3C2410_WTCON_RSTEN; + } + + DBG("%s: wdt_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); + spin_unlock(&wdt_lock); + + return 0; +} + +static inline int s3c2410wdt_is_running(void) +{ + return readl(wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE; +} + +static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeout) +{ + unsigned long freq = clk_get_rate(wdt_clock); + unsigned int count; + unsigned int divisor = 1; + unsigned long wtcon; + + if (timeout < 1) + return -EINVAL; + + freq /= 128; + count = timeout * freq; + + DBG("%s: count=%d, timeout=%d, freq=%lu\n", + __func__, count, timeout, freq); + + /* if the count is bigger than the watchdog register, + then work out what we need to do (and if) we can + actually make this value + */ + + if (count >= 0x10000) { + for (divisor = 1; divisor <= 0x100; divisor++) { + if ((count / divisor) < 0x10000) + break; + } + + if ((count / divisor) >= 0x10000) { + dev_err(wdt_dev, "timeout %d too big\n", timeout); + return -EINVAL; + } + } + + DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", + __func__, timeout, divisor, count, count/divisor); + + count /= divisor; + wdt_count = count; + + /* update the pre-scaler */ + wtcon = readl(wdt_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); + + wdd->timeout = timeout; + + return 0; +} + +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) + +static const struct watchdog_info s3c2410_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "S3C2410 Watchdog", +}; + +static struct watchdog_ops s3c2410wdt_ops = { + .owner = THIS_MODULE, + .start = s3c2410wdt_start, + .stop = s3c2410wdt_stop, + .ping = s3c2410wdt_keepalive, + .set_timeout = s3c2410wdt_set_heartbeat, +}; + +static struct watchdog_device s3c2410_wdd = { + .info = &s3c2410_wdt_ident, + .ops = &s3c2410wdt_ops, +}; + +/* interrupt handler code */ + +static irqreturn_t s3c2410wdt_irq(int irqno, void *param) +{ + dev_info(wdt_dev, "watchdog timer expired (irq)\n"); + + s3c2410wdt_keepalive(&s3c2410_wdd); + return IRQ_HANDLED; +} + + +#ifdef CONFIG_CPU_FREQ + +static int s3c2410wdt_cpufreq_transition(struct notifier_block *nb, + unsigned long val, void *data) +{ + int ret; + + if (!s3c2410wdt_is_running()) + goto done; + + if (val == CPUFREQ_PRECHANGE) { + /* To ensure that over the change we don't cause the + * watchdog to trigger, we perform an keep-alive if + * the watchdog is running. + */ + + s3c2410wdt_keepalive(&s3c2410_wdd); + } else if (val == CPUFREQ_POSTCHANGE) { + s3c2410wdt_stop(&s3c2410_wdd); + + ret = s3c2410wdt_set_heartbeat(&s3c2410_wdd, s3c2410_wdd.timeout); + + if (ret >= 0) + s3c2410wdt_start(&s3c2410_wdd); + else + goto err; + } + +done: + return 0; + + err: + dev_err(wdt_dev, "cannot set new value for timeout %d\n", + s3c2410_wdd.timeout); + return ret; +} + +static struct notifier_block s3c2410wdt_cpufreq_transition_nb = { + .notifier_call = s3c2410wdt_cpufreq_transition, +}; + +static inline int s3c2410wdt_cpufreq_register(void) +{ + return cpufreq_register_notifier(&s3c2410wdt_cpufreq_transition_nb, + CPUFREQ_TRANSITION_NOTIFIER); +} + +static inline void s3c2410wdt_cpufreq_deregister(void) +{ + cpufreq_unregister_notifier(&s3c2410wdt_cpufreq_transition_nb, + CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +static inline int s3c2410wdt_cpufreq_register(void) +{ + return 0; +} + +static inline void s3c2410wdt_cpufreq_deregister(void) +{ +} +#endif + +static int __devinit s3c2410wdt_probe(struct platform_device *pdev) +{ + struct device *dev; + unsigned int wtcon; + int started = 0; + int ret; + int size; + + DBG("%s: probe=%p\n", __func__, pdev); + + dev = &pdev->dev; + wdt_dev = &pdev->dev; + + wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (wdt_mem == NULL) { + dev_err(dev, "no memory resource specified\n"); + return -ENOENT; + } + + 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; + } + + /* get the memory region for the watchdog timer */ + + size = resource_size(wdt_mem); + if (!request_mem_region(wdt_mem->start, size, pdev->name)) { + dev_err(dev, "failed to get memory region\n"); + ret = -EBUSY; + goto err; + } + + wdt_base = ioremap(wdt_mem->start, size); + if (wdt_base == NULL) { + dev_err(dev, "failed to ioremap() region\n"); + ret = -EINVAL; + goto err_req; + } + + DBG("probe: mapped wdt_base=%p\n", wdt_base); + + 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_map; + } + + clk_enable(wdt_clock); + + ret = s3c2410wdt_cpufreq_register(); + if (ret < 0) { + pr_err("failed to register cpufreq\n"); + goto err_clk; + } + + /* see if we can actually set the requested timer margin, and if + * not, try the default value */ + + if (s3c2410wdt_set_heartbeat(&s3c2410_wdd, tmr_margin)) { + started = s3c2410wdt_set_heartbeat(&s3c2410_wdd, + CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + + 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"); + } + + 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_cpufreq; + } + + watchdog_set_nowayout(&s3c2410_wdd, nowayout); + + ret = watchdog_register_device(&s3c2410_wdd); + if (ret) { + dev_err(dev, "cannot register watchdog (%d)\n", ret); + goto err_irq; + } + + if (tmr_atboot && started == 0) { + dev_info(dev, "starting watchdog timer\n"); + s3c2410wdt_start(&s3c2410_wdd); + } 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(&s3c2410_wdd); + } + + /* print out a statement of readiness */ + + wtcon = readl(wdt_base + S3C2410_WTCON); + + dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n", + (wtcon & S3C2410_WTCON_ENABLE) ? "" : "in", + (wtcon & S3C2410_WTCON_RSTEN) ? "en" : "dis", + (wtcon & S3C2410_WTCON_INTEN) ? "en" : "dis"); + + return 0; + + err_irq: + free_irq(wdt_irq->start, pdev); + + err_cpufreq: + s3c2410wdt_cpufreq_deregister(); + + err_clk: + clk_disable(wdt_clock); + clk_put(wdt_clock); + wdt_clock = NULL; + + err_map: + iounmap(wdt_base); + + err_req: + release_mem_region(wdt_mem->start, size); + + err: + wdt_irq = NULL; + wdt_mem = NULL; + return ret; +} + +static int __devexit s3c2410wdt_remove(struct platform_device *dev) +{ + watchdog_unregister_device(&s3c2410_wdd); + + free_irq(wdt_irq->start, dev); + + s3c2410wdt_cpufreq_deregister(); + + clk_disable(wdt_clock); + clk_put(wdt_clock); + wdt_clock = NULL; + + iounmap(wdt_base); + + release_mem_region(wdt_mem->start, resource_size(wdt_mem)); + wdt_irq = NULL; + wdt_mem = NULL; + return 0; +} + +static void s3c2410wdt_shutdown(struct platform_device *dev) +{ + s3c2410wdt_stop(&s3c2410_wdd); +} + +#ifdef CONFIG_PM + +static unsigned long wtcon_save; +static unsigned long wtdat_save; + +static int s3c2410wdt_suspend(struct platform_device *dev, pm_message_t state) +{ + /* Save watchdog state, and turn it off. */ + wtcon_save = readl(wdt_base + S3C2410_WTCON); + wtdat_save = readl(wdt_base + S3C2410_WTDAT); + + /* Note that WTCNT doesn't need to be saved. */ + s3c2410wdt_stop(&s3c2410_wdd); + + return 0; +} + +static int s3c2410wdt_resume(struct platform_device *dev) +{ + /* Restore watchdog state. */ + + writel(wtdat_save, wdt_base + S3C2410_WTDAT); + writel(wtdat_save, wdt_base + S3C2410_WTCNT); /* Reset count */ + writel(wtcon_save, wdt_base + S3C2410_WTCON); + + pr_info("watchdog %sabled\n", + (wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis"); + + return 0; +} + +#else +#define s3c2410wdt_suspend NULL +#define s3c2410wdt_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_OF +static const struct of_device_id s3c2410_wdt_match[] = { + { .compatible = "samsung,s3c2410-wdt" }, + {}, +}; +MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); +#else +#define s3c2410_wdt_match NULL +#endif + +static struct platform_driver s3c2410wdt_driver = { + .probe = s3c2410wdt_probe, + .remove = __devexit_p(s3c2410wdt_remove), + .shutdown = s3c2410wdt_shutdown, + .suspend = s3c2410wdt_suspend, + .resume = s3c2410wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c2410-wdt", + .of_match_table = s3c2410_wdt_match, + }, +}; + + +static int __init watchdog_init(void) +{ + pr_info("S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"); + + 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_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); +MODULE_ALIAS("platform:s3c2410-wdt"); diff --git a/drivers/watchdog/sa1100_wdt.c b/drivers/watchdog/sa1100_wdt.c new file mode 100644 index 00000000..54984deb --- /dev/null +++ b/drivers/watchdog/sa1100_wdt.c @@ -0,0 +1,196 @@ +/* + * Watchdog driver for the SA11x0/PXA2xx + * + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * 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 Oleg Drokin nor iXcelerator.com admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * + * 27/11/2000 Initial release + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/uaccess.h> +#include <linux/timex.h> + +#ifdef CONFIG_ARCH_PXA +#include <mach/regs-ost.h> +#endif + +#include <mach/reset.h> +#include <mach/hardware.h> + +static unsigned long oscr_freq; +static unsigned long sa1100wdt_users; +static unsigned int pre_margin; +static int boot_status; + +/* + * Allow only one person to hold it open + */ +static int sa1100dog_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(1, &sa1100wdt_users)) + return -EBUSY; + + /* Activate SA1100 Watchdog timer */ + OSMR3 = OSCR + pre_margin; + OSSR = OSSR_M3; + OWER = OWER_WME; + OIER |= OIER_E3; + return nonseekable_open(inode, file); +} + +/* + * The watchdog cannot be disabled. + * + * Previous comments suggested that turning off the interrupt by + * clearing OIER[E3] would prevent the watchdog timing out but this + * does not appear to be true (at least on the PXA255). + */ +static int sa1100dog_release(struct inode *inode, struct file *file) +{ + pr_crit("Device closed - timer will not stop\n"); + clear_bit(1, &sa1100wdt_users); + return 0; +} + +static ssize_t sa1100dog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) + /* Refresh OSMR3 timer. */ + OSMR3 = OSCR + pre_margin; + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT + | WDIOF_KEEPALIVEPING, + .identity = "SA1100/PXA255 Watchdog", + .firmware_version = 1, +}; + +static long sa1100dog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + int time; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, p); + break; + + case WDIOC_KEEPALIVE: + OSMR3 = OSCR + pre_margin; + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, p); + if (ret) + break; + + if (time <= 0 || (oscr_freq * (long long)time >= 0xffffffff)) { + ret = -EINVAL; + break; + } + + pre_margin = oscr_freq * time; + OSMR3 = OSCR + pre_margin; + /*fall through*/ + + case WDIOC_GETTIMEOUT: + ret = put_user(pre_margin / oscr_freq, p); + break; + } + return ret; +} + +static const struct file_operations sa1100dog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sa1100dog_write, + .unlocked_ioctl = sa1100dog_ioctl, + .open = sa1100dog_open, + .release = sa1100dog_release, +}; + +static struct miscdevice sa1100dog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sa1100dog_fops, +}; + +static int margin __initdata = 60; /* (secs) Default is 1 minute */ + +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 = (reset_status & RESET_STATUS_WATCHDOG) ? + WDIOF_CARDRESET : 0; + pre_margin = oscr_freq * margin; + + ret = misc_register(&sa1100dog_miscdev); + if (ret == 0) + pr_info("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", + margin); + return ret; +} + +static void __exit sa1100dog_exit(void) +{ + misc_deregister(&sa1100dog_miscdev); +} + +module_init(sa1100dog_init); +module_exit(sa1100dog_exit); + +MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>"); +MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog"); + +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sb_wdog.c b/drivers/watchdog/sb_wdog.c new file mode 100644 index 00000000..25c7a3f9 --- /dev/null +++ b/drivers/watchdog/sb_wdog.c @@ -0,0 +1,362 @@ +/* + * Watchdog driver for SiByte SB1 SoCs + * + * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@lsi.com> + * + * This driver is intended to make the second of two hardware watchdogs + * on the Sibyte 12XX and 11XX SoCs available to the user. There are two + * such devices available on the SoC, but it seems that there isn't an + * enumeration class for watchdogs in Linux like there is for RTCs. + * The second is used rather than the first because it uses IRQ 1, + * thereby avoiding all that IRQ 0 problematic nonsense. + * + * I have not tried this driver on a 1480 processor; it might work + * just well enough to really screw things up. + * + * It is a simple timer, and there is an interrupt that is raised the + * first time the timer expires. The second time it expires, the chip + * is reset and there is no way to redirect that NMI. Which could + * be problematic in some cases where this chip is sitting on the HT + * bus and has just taken responsibility for providing a cache block. + * Since the reset can't be redirected to the external reset pin, it is + * possible that other HT connected processors might hang and not reset. + * For Linux, a soft reset would probably be even worse than a hard reset. + * There you have it. + * + * The timer takes 23 bits of a 64 bit register (?) as a count value, + * and decrements the count every microsecond, for a max value of + * 0x7fffff usec or about 8.3ish seconds. + * + * This watchdog borrows some user semantics from the softdog driver, + * in that if you close the fd, it leaves the watchdog running, unless + * you previously wrote a 'V' to the fd, in which case it disables + * the watchdog when you close the fd like some other drivers. + * + * Based on various other watchdog drivers, which are probably all + * loosely based on something Alan Cox wrote years ago. + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 1 or 2 as published by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/interrupt.h> + +#include <asm/sibyte/sb1250.h> +#include <asm/sibyte/sb1250_regs.h> +#include <asm/sibyte/sb1250_int.h> +#include <asm/sibyte/sb1250_scd.h> + +static DEFINE_SPINLOCK(sbwd_lock); + +/* + * set the initial count value of a timer + * + * wdog is the iomem address of the cfg register + */ +void sbwdog_set(char __iomem *wdog, unsigned long t) +{ + spin_lock(&sbwd_lock); + __raw_writeb(0, wdog); + __raw_writeq(t & 0x7fffffUL, wdog - 0x10); + spin_unlock(&sbwd_lock); +} + +/* + * cause the timer to [re]load it's initial count and start counting + * all over again + * + * wdog is the iomem address of the cfg register + */ +void sbwdog_pet(char __iomem *wdog) +{ + spin_lock(&sbwd_lock); + __raw_writeb(__raw_readb(wdog) | 1, wdog); + spin_unlock(&sbwd_lock); +} + +static unsigned long sbwdog_gate; /* keeps it to one thread only */ +static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0)); +static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1)); +static unsigned long timeout = 0x7fffffUL; /* useconds: 8.3ish secs. */ +static int expect_close; + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "SiByte Watchdog", +}; + +/* + * Allow only a single thread to walk the dog + */ +static int sbwdog_open(struct inode *inode, struct file *file) +{ + nonseekable_open(inode, file); + if (test_and_set_bit(0, &sbwdog_gate)) + return -EBUSY; + __module_get(THIS_MODULE); + + /* + * Activate the timer + */ + sbwdog_set(user_dog, timeout); + __raw_writeb(1, user_dog); + + return 0; +} + +/* + * Put the dog back in the kennel. + */ +static int sbwdog_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + __raw_writeb(0, user_dog); + module_put(THIS_MODULE); + } else { + pr_crit("%s: Unexpected close, not stopping watchdog!\n", + ident.identity); + sbwdog_pet(user_dog); + } + clear_bit(0, &sbwdog_gate); + expect_close = 0; + + return 0; +} + +/* + * 42 - the answer + */ +static ssize_t sbwdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + int i; + + if (len) { + /* + * restart the timer + */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + sbwdog_pet(user_dog); + } + + return len; +} + +static long sbwdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + unsigned long time; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_KEEPALIVE: + sbwdog_pet(user_dog); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, p); + if (ret) + break; + + time *= 1000000; + if (time > 0x7fffffUL) { + ret = -EINVAL; + break; + } + timeout = time; + sbwdog_set(user_dog, timeout); + sbwdog_pet(user_dog); + + case WDIOC_GETTIMEOUT: + /* + * 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; + } + return ret; +} + +/* + * Notifier for system down + */ +static int sbwdog_notify_sys(struct notifier_block *this, unsigned long code, + void *erf) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* + * sit and sit + */ + __raw_writeb(0, user_dog); + __raw_writeb(0, kern_dog); + } + + return NOTIFY_DONE; +} + +static const struct file_operations sbwdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sbwdog_write, + .unlocked_ioctl = sbwdog_ioctl, + .open = sbwdog_open, + .release = sbwdog_release, +}; + +static struct miscdevice sbwdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sbwdog_fops, +}; + +static struct notifier_block sbwdog_notifier = { + .notifier_call = sbwdog_notify_sys, +}; + +/* + * interrupt handler + * + * doesn't do a whole lot for user, but oh so cleverly written so kernel + * code can use it to re-up the watchdog, thereby saving the kernel from + * having to create and maintain a timer, just to tickle another timer, + * which is just so wrong. + */ +irqreturn_t sbwdog_interrupt(int irq, void *addr) +{ + unsigned long wd_init; + char *wd_cfg_reg = (char *)addr; + u8 cfg; + + cfg = __raw_readb(wd_cfg_reg); + wd_init = __raw_readq(wd_cfg_reg - 8) & 0x7fffff; + + /* + * if it's the second watchdog timer, it's for those users + */ + if (wd_cfg_reg == user_dog) + pr_crit("%s in danger of initiating system reset " + "in %ld.%01ld seconds\n", + ident.identity, + wd_init / 1000000, (wd_init / 100000) % 10); + else + cfg |= 1; + + __raw_writeb(cfg, wd_cfg_reg); + + return IRQ_HANDLED; +} + +static int __init sbwdog_init(void) +{ + int ret; + + /* + * register a reboot notifier + */ + ret = register_reboot_notifier(&sbwdog_notifier); + if (ret) { + pr_err("%s: cannot register reboot notifier (err=%d)\n", + ident.identity, ret); + return ret; + } + + /* + * get the resources + */ + + ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED, + ident.identity, (void *)user_dog); + if (ret) { + pr_err("%s: failed to request irq 1 - %d\n", + ident.identity, ret); + goto out; + } + + ret = misc_register(&sbwdog_miscdev); + if (ret == 0) { + pr_info("%s: timeout is %ld.%ld secs\n", + ident.identity, + timeout / 1000000, (timeout / 100000) % 10); + return 0; + } + free_irq(1, (void *)user_dog); +out: + unregister_reboot_notifier(&sbwdog_notifier); + + return ret; +} + +static void __exit sbwdog_exit(void) +{ + misc_deregister(&sbwdog_miscdev); + free_irq(1, (void *)user_dog); + unregister_reboot_notifier(&sbwdog_notifier); +} + +module_init(sbwdog_init); +module_exit(sbwdog_exit); + +MODULE_AUTHOR("Andrew Sharp <andy.sharp@lsi.com>"); +MODULE_DESCRIPTION("SiByte Watchdog"); + +module_param(timeout, ulong, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)"); + +MODULE_LICENSE("GPL"); +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) +{ + int ret; + + ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED, + "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0)); + if (ret) { + pr_crit("Watchdog IRQ zero(0) failed to be requested - %d\n", ret); + } +} + + + */ diff --git a/drivers/watchdog/sbc60xxwdt.c b/drivers/watchdog/sbc60xxwdt.c new file mode 100644 index 00000000..63632ec8 --- /dev/null +++ b/drivers/watchdog/sbc60xxwdt.c @@ -0,0 +1,390 @@ +/* + * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x + * + * 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * The author does NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2000 Jakob Oestergaard <jakob@unthought.net> + * + * 12/4 - 2000 [Initial revision] + * 25/4 - 2000 Added /dev/watchdog support + * 09/5 - 2001 [smj@oro.net] fixed fop_write to "return 1" + * on success + * 12/4 - 2002 [rob@osinvestor.com] eliminate fop_read + * fix possible wdt_is_open race + * add CONFIG_WATCHDOG_NOWAYOUT support + * remove lock_kernel/unlock_kernel pairs + * added KERN_* to printk's + * got rid of extraneous comments + * changed watchdog_info to correctly reflect what + * the driver offers + * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, + * WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, and + * WDIOC_SETOPTIONS ioctls + * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces + * use module_param + * made timeout (the emulated heartbeat) a + * module_param + * made the keepalive ping an internal subroutine + * made wdt_stop and wdt_start module params + * added extra printk's for startup problems + * added MODULE_AUTHOR and MODULE_DESCRIPTION info + * + * + * This WDT driver is different from the other Linux WDT + * drivers in the following ways: + * *) The driver will ping the watchdog by itself, because this + * particular WDT has a very short timeout (one second) and it + * would be insane to count on any userspace daemon always + * getting scheduled within that time frame. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +#define OUR_NAME "sbc60xxwdt" +#define PFX OUR_NAME ": " + +/* + * You must set these - The driver cannot probe for the settings + */ + +static int wdt_stop = 0x45; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)"); + +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)"); + +/* + * The 60xx board can use watchdog timeout values from one second + * to several minutes. The default is one second, so if we reset + * the watchdog every ~250ms we should be safe. + */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + * If the daemon pulses us every 25 seconds, we can still afford + * a 5 second scheduling delay on the (high priority) daemon. That + * should be sufficient for a box under any load. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds, multiplied by HZ to + get seconds to wait for a ping */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void wdt_timer_ping(unsigned long); +static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; + +/* + * Whack the dog + */ + +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)) { + /* Ping the WDT by reading from wdt_start */ + inb_p(wdt_start); + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); +} + +/* + * Utility routines + */ + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + pr_info("Watchdog timer is now enabled\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + inb_p(wdt_stop); + pr_info("Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the + magic character five months ago... */ + wdt_expect_close = 0; + + /* scan to see whether or not we got the + magic character */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + + /* Well, anyhow someone wrote to us, we should + return that favour */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode *inode, struct file *file) +{ + /* Just in case we're already talking to someone... */ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode *inode, struct file *file) +{ + if (wdt_expect_close == 42) + wdt_turnoff(); + else { + del_timer(&timer); + pr_crit("device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SBC60xx", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + if (get_user(new_options, p)) + return -EFAULT; + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + if (get_user(new_timeout, p)) + return -EFAULT; + /* arbitrary upper limit */ + if (new_timeout < 1 || new_timeout > 3600) + return -EINVAL; + + timeout = new_timeout; + wdt_keepalive(); + /* 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, + .unlocked_ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit sbc60xxwdt_unload(void) +{ + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + + unregister_reboot_notifier(&wdt_notifier); + if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) + release_region(wdt_stop, 1); + release_region(wdt_start, 1); +} + +static int __init sbc60xxwdt_init(void) +{ + int rc = -EBUSY; + + if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */ + timeout = WATCHDOG_TIMEOUT; + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", + timeout); + } + + if (!request_region(wdt_start, 1, "SBC 60XX WDT")) { + pr_err("I/O address 0x%04x already in use\n", wdt_start); + rc = -EIO; + goto err_out; + } + + /* We cannot reserve 0x45 - the kernel already has! */ + if (wdt_stop != 0x45 && wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) { + pr_err("I/O address 0x%04x already in use\n", wdt_stop); + rc = -EIO; + goto err_out_region1; + } + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region2; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_reboot; + } + pr_info("WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region2: + if (wdt_stop != 0x45 && wdt_stop != wdt_start) + release_region(wdt_stop, 1); +err_out_region1: + release_region(wdt_start, 1); +err_out: + return rc; +} + +module_init(sbc60xxwdt_init); +module_exit(sbc60xxwdt_unload); + +MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>"); +MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sbc7240_wdt.c b/drivers/watchdog/sbc7240_wdt.c new file mode 100644 index 00000000..719edc8f --- /dev/null +++ b/drivers/watchdog/sbc7240_wdt.c @@ -0,0 +1,313 @@ +/* + * NANO7240 SBC Watchdog device driver + * + * Based on w83877f.c by Scott Jennings, + * + * 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; + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * (c) Copyright 2007 Gilles GIGAN <gilles.gigan@jcu.edu.au> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <linux/atomic.h> + +#define SBC7240_ENABLE_PORT 0x443 +#define SBC7240_DISABLE_PORT 0x043 +#define SBC7240_SET_TIMEOUT_PORT SBC7240_ENABLE_PORT +#define SBC7240_MAGIC_CHAR 'V' + +#define SBC7240_TIMEOUT 30 +#define SBC7240_MAX_TIMEOUT 255 +static int timeout = SBC7240_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=" + __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default=" + __MODULE_STRING(SBC7240_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file"); + +#define SBC7240_OPEN_STATUS_BIT 0 +#define SBC7240_ENABLED_STATUS_BIT 1 +#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2 +static unsigned long wdt_status; + +/* + * Utility routines + */ + +static void wdt_disable(void) +{ + /* disable the watchdog */ + if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { + inb_p(SBC7240_DISABLE_PORT); + pr_info("Watchdog timer is now disabled\n"); + } +} + +static void wdt_enable(void) +{ + /* enable the watchdog */ + if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) { + inb_p(SBC7240_ENABLE_PORT); + pr_info("Watchdog timer is now enabled\n"); + } +} + +static int wdt_set_timeout(int t) +{ + if (t < 1 || t > SBC7240_MAX_TIMEOUT) { + pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT); + return -1; + } + /* set the timeout */ + outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT); + timeout = t; + pr_info("timeout set to %d seconds\n", t); + return 0; +} + +/* Whack the dog */ +static inline void wdt_keepalive(void) +{ + if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) + inb_p(SBC7240_ENABLE_PORT); +} + +/* + * /dev/watchdog handling + */ +static ssize_t fop_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + size_t i; + char c; + + if (count) { + if (!nowayout) { + clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, + &wdt_status); + + /* is there a magic char ? */ + for (i = 0; i != count; i++) { + if (get_user(c, buf + i)) + return -EFAULT; + if (c == SBC7240_MAGIC_CHAR) { + set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, + &wdt_status); + break; + } + } + } + + wdt_keepalive(); + } + + return count; +} + +static int fop_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status)) + return -EBUSY; + + wdt_enable(); + + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode *inode, struct file *file) +{ + if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status) + || !nowayout) { + wdt_disable(); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + wdt_keepalive(); + } + + clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status); + return 0; +} + +static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING| + WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SBC7240", +}; + + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user((void __user *)arg, &ident, sizeof(ident)) + ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int __user *)arg); + case WDIOC_SETOPTIONS: + { + int options; + int retval = -EINVAL; + + if (get_user(options, (int __user *)arg)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wdt_disable(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wdt_enable(); + retval = 0; + } + + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, (int __user *)arg)) + return -EFAULT; + + if (wdt_set_timeout(new_timeout)) + return -EINVAL; + + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, (int __user *)arg); + default: + return -ENOTTY; + } +} + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fop_write, + .open = fop_open, + .release = fop_close, + .unlocked_ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_disable(); + return NOTIFY_DONE; +} + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit sbc7240_wdt_unload(void) +{ + pr_info("Removing watchdog\n"); + misc_deregister(&wdt_miscdev); + + unregister_reboot_notifier(&wdt_notifier); + release_region(SBC7240_ENABLE_PORT, 1); +} + +static int __init sbc7240_wdt_init(void) +{ + int rc = -EBUSY; + + if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) { + pr_err("I/O address 0x%04x already in use\n", + SBC7240_ENABLE_PORT); + rc = -EIO; + goto err_out; + } + + /* The IO port 0x043 used to disable the watchdog + * is already claimed by the system timer, so we + * can't request_region() it ...*/ + + if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) { + timeout = SBC7240_TIMEOUT; + pr_info("timeout value must be 1<=x<=%d, using %d\n", + SBC7240_MAX_TIMEOUT, timeout); + } + wdt_set_timeout(timeout); + wdt_disable(); + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_reboot_notifier; + } + + pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n", + nowayout); + + return 0; + +err_out_reboot_notifier: + unregister_reboot_notifier(&wdt_notifier); +err_out_region: + release_region(SBC7240_ENABLE_PORT, 1); +err_out: + return rc; +} + +module_init(sbc7240_wdt_init); +module_exit(sbc7240_wdt_unload); + +MODULE_AUTHOR("Gilles Gigan"); +MODULE_DESCRIPTION("Watchdog device driver for single board" + " computers EPIC Nano 7240 from iEi"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/watchdog/sbc8360.c b/drivers/watchdog/sbc8360.c new file mode 100644 index 00000000..d4781e05 --- /dev/null +++ b/drivers/watchdog/sbc8360.c @@ -0,0 +1,409 @@ +/* + * SBC8360 Watchdog driver + * + * (c) Copyright 2005 Webcon, Inc. + * + * Based on ib700wdt.c, which is based on advantechwdt.c which is based + * on acquirewdt.c which is based on wdt.c. + * + * (c) Copyright 2001 Charles Howes <chowes@vsol.net> + * + * Based on advantechwdt.c which is based on acquirewdt.c which + * is based on wdt.c. + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Added timeout module option to override default + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/notifier.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +static unsigned long sbc8360_is_open; +static char expect_close; + +/* + * + * Watchdog Timer Configuration + * + * The function of the watchdog timer is to reset the system automatically + * and is defined at I/O port 0120H and 0121H. To enable the watchdog timer + * and allow the system to reset, write appropriate values from the table + * below to I/O port 0120H and 0121H. To disable the timer, write a zero + * value to I/O port 0121H for the system to stop the watchdog function. + * + * The following describes how the timer should be programmed (according to + * the vendor documentation) + * + * Enabling Watchdog: + * MOV AX,000AH (enable, phase I) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000BH (enable, phase II) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000nH (set multiplier n, from 1-4) + * MOV DX,0120H + * OUT DX,AX + * MOV AX,000mH (set base timer m, from 0-F) + * MOV DX,0121H + * OUT DX,AX + * + * Reset timer: + * MOV AX,000mH (same as set base timer, above) + * MOV DX,0121H + * OUT DX,AX + * + * Disabling Watchdog: + * MOV AX,0000H (a zero value) + * MOV DX,0120H + * OUT DX,AX + * + * Watchdog timeout configuration values: + * N + * M | 1 2 3 4 + * --|---------------------------------- + * 0 | 0.5s 5s 50s 100s + * 1 | 1s 10s 100s 200s + * 2 | 1.5s 15s 150s 300s + * 3 | 2s 20s 200s 400s + * 4 | 2.5s 25s 250s 500s + * 5 | 3s 30s 300s 600s + * 6 | 3.5s 35s 350s 700s + * 7 | 4s 40s 400s 800s + * 8 | 4.5s 45s 450s 900s + * 9 | 5s 50s 500s 1000s + * A | 5.5s 55s 550s 1100s + * B | 6s 60s 600s 1200s + * C | 6.5s 65s 650s 1300s + * D | 7s 70s 700s 1400s + * E | 7.5s 75s 750s 1500s + * F | 8s 80s 800s 1600s + * + * Another way to say the same things is: + * For N=1, Timeout = (M+1) * 0.5s + * For N=2, Timeout = (M+1) * 5s + * For N=3, Timeout = (M+1) * 50s + * For N=4, Timeout = (M+1) * 100s + * + */ + +static int wd_times[64][2] = { + {0, 1}, /* 0 = 0.5s */ + {1, 1}, /* 1 = 1s */ + {2, 1}, /* 2 = 1.5s */ + {3, 1}, /* 3 = 2s */ + {4, 1}, /* 4 = 2.5s */ + {5, 1}, /* 5 = 3s */ + {6, 1}, /* 6 = 3.5s */ + {7, 1}, /* 7 = 4s */ + {8, 1}, /* 8 = 4.5s */ + {9, 1}, /* 9 = 5s */ + {0xA, 1}, /* 10 = 5.5s */ + {0xB, 1}, /* 11 = 6s */ + {0xC, 1}, /* 12 = 6.5s */ + {0xD, 1}, /* 13 = 7s */ + {0xE, 1}, /* 14 = 7.5s */ + {0xF, 1}, /* 15 = 8s */ + {0, 2}, /* 16 = 5s */ + {1, 2}, /* 17 = 10s */ + {2, 2}, /* 18 = 15s */ + {3, 2}, /* 19 = 20s */ + {4, 2}, /* 20 = 25s */ + {5, 2}, /* 21 = 30s */ + {6, 2}, /* 22 = 35s */ + {7, 2}, /* 23 = 40s */ + {8, 2}, /* 24 = 45s */ + {9, 2}, /* 25 = 50s */ + {0xA, 2}, /* 26 = 55s */ + {0xB, 2}, /* 27 = 60s */ + {0xC, 2}, /* 28 = 65s */ + {0xD, 2}, /* 29 = 70s */ + {0xE, 2}, /* 30 = 75s */ + {0xF, 2}, /* 31 = 80s */ + {0, 3}, /* 32 = 50s */ + {1, 3}, /* 33 = 100s */ + {2, 3}, /* 34 = 150s */ + {3, 3}, /* 35 = 200s */ + {4, 3}, /* 36 = 250s */ + {5, 3}, /* 37 = 300s */ + {6, 3}, /* 38 = 350s */ + {7, 3}, /* 39 = 400s */ + {8, 3}, /* 40 = 450s */ + {9, 3}, /* 41 = 500s */ + {0xA, 3}, /* 42 = 550s */ + {0xB, 3}, /* 43 = 600s */ + {0xC, 3}, /* 44 = 650s */ + {0xD, 3}, /* 45 = 700s */ + {0xE, 3}, /* 46 = 750s */ + {0xF, 3}, /* 47 = 800s */ + {0, 4}, /* 48 = 100s */ + {1, 4}, /* 49 = 200s */ + {2, 4}, /* 50 = 300s */ + {3, 4}, /* 51 = 400s */ + {4, 4}, /* 52 = 500s */ + {5, 4}, /* 53 = 600s */ + {6, 4}, /* 54 = 700s */ + {7, 4}, /* 55 = 800s */ + {8, 4}, /* 56 = 900s */ + {9, 4}, /* 57 = 1000s */ + {0xA, 4}, /* 58 = 1100s */ + {0xB, 4}, /* 59 = 1200s */ + {0xC, 4}, /* 60 = 1300s */ + {0xD, 4}, /* 61 = 1400s */ + {0xE, 4}, /* 62 = 1500s */ + {0xF, 4} /* 63 = 1600s */ +}; + +#define SBC8360_ENABLE 0x120 +#define SBC8360_BASETIME 0x121 + +static int timeout = 27; +static int wd_margin = 0xB; +static int wd_multiplier = 2; +static bool nowayout = WATCHDOG_NOWAYOUT; + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))"); +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Kernel methods. + */ + +/* Activate and pre-configure watchdog */ +static void sbc8360_activate(void) +{ + /* Enable the watchdog */ + outb(0x0A, SBC8360_ENABLE); + msleep_interruptible(100); + outb(0x0B, SBC8360_ENABLE); + msleep_interruptible(100); + /* Set timeout multiplier */ + outb(wd_multiplier, SBC8360_ENABLE); + msleep_interruptible(100); + /* Nothing happens until first sbc8360_ping() */ +} + +/* Kernel pings watchdog */ +static void sbc8360_ping(void) +{ + /* Write the base timer register */ + outb(wd_margin, SBC8360_BASETIME); +} + +/* stop watchdog */ +static void sbc8360_stop(void) +{ + /* De-activate the watchdog */ + outb(0, SBC8360_ENABLE); +} + +/* Userspace pings kernel driver, or requests clean close */ +static ssize_t sbc8360_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + sbc8360_ping(); + } + return count; +} + +static int sbc8360_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &sbc8360_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate and ping once to start the countdown */ + sbc8360_activate(); + sbc8360_ping(); + return nonseekable_open(inode, file); +} + +static int sbc8360_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + sbc8360_stop(); + else + pr_crit("SBC8360 device closed unexpectedly. SBC8360 will not stop!\n"); + + clear_bit(0, &sbc8360_is_open); + expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + sbc8360_stop(); /* Disable the SBC8360 Watchdog */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations sbc8360_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sbc8360_write, + .open = sbc8360_open, + .release = sbc8360_close, +}; + +static struct miscdevice sbc8360_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sbc8360_fops, +}; + +/* + * The SBC8360 needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block sbc8360_notifier = { + .notifier_call = sbc8360_notify_sys, +}; + +static int __init sbc8360_init(void) +{ + int res; + unsigned long int mseconds = 60000; + + if (timeout < 0 || timeout > 63) { + pr_err("Invalid timeout index (must be 0-63)\n"); + res = -EINVAL; + goto out; + } + + if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) { + pr_err("ENABLE method I/O %X is not available\n", + SBC8360_ENABLE); + res = -EIO; + goto out; + } + if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) { + pr_err("BASETIME method I/O %X is not available\n", + SBC8360_BASETIME); + res = -EIO; + goto out_nobasetimereg; + } + + res = register_reboot_notifier(&sbc8360_notifier); + if (res) { + pr_err("Failed to register reboot notifier\n"); + goto out_noreboot; + } + + res = misc_register(&sbc8360_miscdev); + if (res) { + pr_err("failed to register misc device\n"); + goto out_nomisc; + } + + wd_margin = wd_times[timeout][0]; + wd_multiplier = wd_times[timeout][1]; + + if (wd_multiplier == 1) + mseconds = (wd_margin + 1) * 500; + else if (wd_multiplier == 2) + mseconds = (wd_margin + 1) * 5000; + else if (wd_multiplier == 3) + mseconds = (wd_margin + 1) * 50000; + else if (wd_multiplier == 4) + mseconds = (wd_margin + 1) * 100000; + + /* My kingdom for the ability to print "0.5 seconds" in the kernel! */ + pr_info("Timeout set at %ld ms\n", mseconds); + + return 0; + +out_nomisc: + unregister_reboot_notifier(&sbc8360_notifier); +out_noreboot: + release_region(SBC8360_BASETIME, 1); +out_nobasetimereg: + release_region(SBC8360_ENABLE, 1); +out: + return res; +} + +static void __exit sbc8360_exit(void) +{ + misc_deregister(&sbc8360_miscdev); + unregister_reboot_notifier(&sbc8360_notifier); + release_region(SBC8360_ENABLE, 1); + release_region(SBC8360_BASETIME, 1); +} + +module_init(sbc8360_init); +module_exit(sbc8360_exit); + +MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>"); +MODULE_DESCRIPTION("SBC8360 watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.01"); +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 new file mode 100644 index 00000000..0c3e9f66 --- /dev/null +++ b/drivers/watchdog/sbc_epx_c3.c @@ -0,0 +1,223 @@ +/* + * SBC EPX C3 0.1 A Hardware Watchdog Device for the Winsystems EPX-C3 + * single board computer + * + * (c) Copyright 2006 Calin A. Culianu <calin@ajvar.org>, All Rights + * Reserved. + * + * 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@lxorguk.ukuu.org.uk> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +static int epx_c3_alive; + +#define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define EPXC3_WATCHDOG_CTL_REG 0x1ee /* write 1 to enable, 0 to disable */ +#define EPXC3_WATCHDOG_PET_REG 0x1ef /* write anything to pet once enabled */ + +static void epx_c3_start(void) +{ + outb(1, EPXC3_WATCHDOG_CTL_REG); +} + +static void epx_c3_stop(void) +{ + + outb(0, EPXC3_WATCHDOG_CTL_REG); + + pr_info("Stopped watchdog timer\n"); +} + +static void epx_c3_pet(void) +{ + outb(1, EPXC3_WATCHDOG_PET_REG); +} + +/* + * Allow only one person to hold it open + */ +static int epx_c3_open(struct inode *inode, struct file *file) +{ + if (epx_c3_alive) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate timer */ + epx_c3_start(); + epx_c3_pet(); + + epx_c3_alive = 1; + pr_info("Started watchdog timer\n"); + + return nonseekable_open(inode, file); +} + +static int epx_c3_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. + * Lock it in if it's a module and we defined ...NOWAYOUT */ + if (!nowayout) + epx_c3_stop(); /* Turn the WDT off */ + + epx_c3_alive = 0; + + return 0; +} + +static ssize_t epx_c3_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* Refresh the timer. */ + if (len) + epx_c3_pet(); + return len; +} + +static long epx_c3_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int options, retval = -EINVAL; + int __user *argp = (void __user *)arg; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 0, + .identity = "Winsystems EPX-C3 H/W Watchdog", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, argp); + case WDIOC_SETOPTIONS: + if (get_user(options, argp)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + epx_c3_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + epx_c3_start(); + retval = 0; + } + + return retval; + case WDIOC_KEEPALIVE: + epx_c3_pet(); + return 0; + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_TIMEOUT, argp); + default: + return -ENOTTY; + } +} + +static int epx_c3_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + epx_c3_stop(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +static const struct file_operations epx_c3_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = epx_c3_write, + .unlocked_ioctl = epx_c3_ioctl, + .open = epx_c3_open, + .release = epx_c3_release, +}; + +static struct miscdevice epx_c3_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &epx_c3_fops, +}; + +static struct notifier_block epx_c3_notifier = { + .notifier_call = epx_c3_notify_sys, +}; + +static int __init watchdog_init(void) +{ + int ret; + + if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog")) + return -EBUSY; + + ret = register_reboot_notifier(&epx_c3_notifier); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto out; + } + + ret = misc_register(&epx_c3_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&epx_c3_notifier); + goto out; + } + + pr_info("Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n"); + + return 0; + +out: + release_region(EPXC3_WATCHDOG_CTL_REG, 2); + return ret; +} + +static void __exit watchdog_exit(void) +{ + misc_deregister(&epx_c3_miscdev); + unregister_reboot_notifier(&epx_c3_notifier); + release_region(EPXC3_WATCHDOG_CTL_REG, 2); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Calin A. Culianu <calin@ajvar.org>"); +MODULE_DESCRIPTION("Hardware Watchdog Device for Winsystems EPX-C3 SBC. " + "Note that there is no way to probe for this device -- " + "so only use it if you are *sure* you are running on this specific " + "SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!"); +MODULE_LICENSE("GPL"); +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 00000000..90d5527c --- /dev/null +++ b/drivers/watchdog/sbc_fitpc2_wdt.c @@ -0,0 +1,267 @@ +/* + * 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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/watchdog/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c new file mode 100644 index 00000000..3fb83b0c --- /dev/null +++ b/drivers/watchdog/sc1200wdt.c @@ -0,0 +1,479 @@ +/* + * National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver + * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>, + * All Rights Reserved. + * Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively. + * + * 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 author(s) of this software shall not be held liable for damages + * of any nature resulting due to the use of this software. This + * software is provided AS-IS with no warranties. + * + * Changelog: + * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware. + * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik + * and Alan Cox. + * 20020222 Zwane Mwaikambo Added probing. + * 20020225 Zwane Mwaikambo Added ISAPNP support. + * 20020412 Rob Radez Broke out start/stop functions + * <rob@osinvestor.com> Return proper status instead of + * temperature warning + * Add WDIOC_GETBOOTSTATUS and + * WDIOC_SETOPTIONS ioctls + * Fix CONFIG_WATCHDOG_NOWAYOUT + * 20020530 Joel Becker Add Matt Domsch's nowayout module + * option + * 20030116 Adam Belay Updated to the latest pnp code + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/pnp.h> +#include <linux/fs.h> +#include <linux/semaphore.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#define SC1200_MODULE_VER "build 20020303" +#define SC1200_MODULE_NAME "sc1200wdt" + +#define MAX_TIMEOUT 255 /* 255 minutes */ +#define PMIR (io) /* Power Management Index Register */ +#define PMDR (io+1) /* Power Management Data Register */ + +/* Data Register indexes */ +#define FER1 0x00 /* Function enable register 1 */ +#define FER2 0x01 /* Function enable register 2 */ +#define PMC1 0x02 /* Power Management Ctrl 1 */ +#define PMC2 0x03 /* Power Management Ctrl 2 */ +#define PMC3 0x04 /* Power Management Ctrl 3 */ +#define WDTO 0x05 /* Watchdog timeout register */ +#define WDCF 0x06 /* Watchdog config register */ +#define WDST 0x07 /* Watchdog status register */ + +/* WDCF bitfields - which devices assert WDO */ +#define KBC_IRQ 0x01 /* Keyboard Controller */ +#define MSE_IRQ 0x02 /* Mouse */ +#define UART1_IRQ 0x03 /* Serial0 */ +#define UART2_IRQ 0x04 /* Serial1 */ +/* 5 -7 are reserved */ + +static int timeout = 1; +static int io = -1; +static int io_len = 2; /* for non plug and play */ +static unsigned long open_flag; +static char expect_close; +static DEFINE_SPINLOCK(sc1200wdt_lock); /* io port access serialisation */ + +#if defined CONFIG_PNP +static int isapnp = 1; +static struct pnp_dev *wdt_dev; + +module_param(isapnp, int, 0); +MODULE_PARM_DESC(isapnp, + "When set to 0 driver ISA PnP support will be disabled"); +#endif + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "io port"); +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + + + +/* Read from Data Register */ +static inline void __sc1200wdt_read_data(unsigned char index, + unsigned char *data) +{ + outb_p(index, PMIR); + *data = inb(PMDR); +} + +static void sc1200wdt_read_data(unsigned char index, unsigned char *data) +{ + spin_lock(&sc1200wdt_lock); + __sc1200wdt_read_data(index, data); + spin_unlock(&sc1200wdt_lock); +} + +/* Write to Data Register */ +static inline void __sc1200wdt_write_data(unsigned char index, + unsigned char data) +{ + outb_p(index, PMIR); + outb(data, PMDR); +} + +static inline void sc1200wdt_write_data(unsigned char index, + unsigned char data) +{ + spin_lock(&sc1200wdt_lock); + __sc1200wdt_write_data(index, data); + spin_unlock(&sc1200wdt_lock); +} + + +static void sc1200wdt_start(void) +{ + unsigned char reg; + spin_lock(&sc1200wdt_lock); + + __sc1200wdt_read_data(WDCF, ®); + /* assert WDO when any of the following interrupts are triggered too */ + reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ); + __sc1200wdt_write_data(WDCF, reg); + /* set the timeout and get the ball rolling */ + __sc1200wdt_write_data(WDTO, timeout); + + spin_unlock(&sc1200wdt_lock); +} + +static void sc1200wdt_stop(void) +{ + sc1200wdt_write_data(WDTO, 0); +} + +/* This returns the status of the WDO signal, inactive high. */ +static inline int sc1200wdt_status(void) +{ + unsigned char ret; + + sc1200wdt_read_data(WDST, &ret); + /* If the bit is inactive, the watchdog is enabled, so return + * KEEPALIVEPING which is a bit of a kludge because there's nothing + * else for enabled/disabled status + */ + return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; +} + +static int sc1200wdt_open(struct inode *inode, struct file *file) +{ + /* allow one at a time */ + if (test_and_set_bit(0, &open_flag)) + return -EBUSY; + + if (timeout > MAX_TIMEOUT) + timeout = MAX_TIMEOUT; + + sc1200wdt_start(); + pr_info("Watchdog enabled, timeout = %d min(s)", timeout); + + return nonseekable_open(inode, file); +} + + +static long sc1200wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "PC87307/PC97307", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + + case WDIOC_GETSTATUS: + return put_user(sc1200wdt_status(), p); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + sc1200wdt_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + sc1200wdt_start(); + retval = 0; + } + + return retval; + } + case WDIOC_KEEPALIVE: + sc1200wdt_write_data(WDTO, timeout); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + /* the API states this is given in secs */ + new_timeout /= 60; + if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) + return -EINVAL; + timeout = new_timeout; + sc1200wdt_write_data(WDTO, timeout); + /* fall through and return the new timeout */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout * 60, p); + + default: + return -ENOTTY; + } +} + + +static int sc1200wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + sc1200wdt_stop(); + pr_info("Watchdog disabled\n"); + } else { + sc1200wdt_write_data(WDTO, timeout); + pr_crit("Unexpected close!, timeout = %d min(s)\n", timeout); + } + clear_bit(0, &open_flag); + expect_close = 0; + + return 0; +} + + +static ssize_t sc1200wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + sc1200wdt_write_data(WDTO, timeout); + return len; + } + + return 0; +} + + +static int sc1200wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + sc1200wdt_stop(); + + return NOTIFY_DONE; +} + + +static struct notifier_block sc1200wdt_notifier = { + .notifier_call = sc1200wdt_notify_sys, +}; + +static const struct file_operations sc1200wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sc1200wdt_write, + .unlocked_ioctl = sc1200wdt_ioctl, + .open = sc1200wdt_open, + .release = sc1200wdt_release, +}; + +static struct miscdevice sc1200wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sc1200wdt_fops, +}; + + +static int __init sc1200wdt_probe(void) +{ + /* The probe works by reading the PMC3 register's default value of 0x0e + * there is one caveat, if the device disables the parallel port or any + * of the UARTs we won't be able to detect it. + * NB. This could be done with accuracy by reading the SID registers, + * but we don't have access to those io regions. + */ + + unsigned char reg; + + sc1200wdt_read_data(PMC3, ®); + reg &= 0x0f; /* we don't want the UART busy bits */ + return (reg == 0x0e) ? 0 : -ENODEV; +} + + +#if defined CONFIG_PNP + +static struct pnp_device_id scl200wdt_pnp_devices[] = { + /* National Semiconductor PC87307/PC97307 watchdog component */ + {.id = "NSC0800", .driver_data = 0}, + {.id = ""}, +}; + +static int scl200wdt_pnp_probe(struct pnp_dev *dev, + const struct pnp_device_id *dev_id) +{ + /* this driver only supports one card at a time */ + if (wdt_dev || !isapnp) + return -EBUSY; + + wdt_dev = dev; + io = pnp_port_start(wdt_dev, 0); + io_len = pnp_port_len(wdt_dev, 0); + + if (!request_region(io, io_len, SC1200_MODULE_NAME)) { + pr_err("Unable to register IO port %#x\n", io); + return -EBUSY; + } + + pr_info("PnP device found at io port %#x/%d\n", io, io_len); + return 0; +} + +static void scl200wdt_pnp_remove(struct pnp_dev *dev) +{ + if (wdt_dev) { + release_region(io, io_len); + wdt_dev = NULL; + } +} + +static struct pnp_driver scl200wdt_pnp_driver = { + .name = "scl200wdt", + .id_table = scl200wdt_pnp_devices, + .probe = scl200wdt_pnp_probe, + .remove = scl200wdt_pnp_remove, +}; + +#endif /* CONFIG_PNP */ + + +static int __init sc1200wdt_init(void) +{ + int ret; + + pr_info("%s\n", SC1200_MODULE_VER); + +#if defined CONFIG_PNP + if (isapnp) { + ret = pnp_register_driver(&scl200wdt_pnp_driver); + if (ret) + goto out_clean; + } +#endif + + if (io == -1) { + pr_err("io parameter must be specified\n"); + ret = -EINVAL; + goto out_pnp; + } + +#if defined CONFIG_PNP + /* now that the user has specified an IO port and we haven't detected + * any devices, disable pnp support */ + isapnp = 0; + pnp_unregister_driver(&scl200wdt_pnp_driver); +#endif + + if (!request_region(io, io_len, SC1200_MODULE_NAME)) { + pr_err("Unable to register IO port %#x\n", io); + ret = -EBUSY; + goto out_pnp; + } + + ret = sc1200wdt_probe(); + if (ret) + goto out_io; + + ret = register_reboot_notifier(&sc1200wdt_notifier); + if (ret) { + pr_err("Unable to register reboot notifier err = %d\n", ret); + goto out_io; + } + + ret = misc_register(&sc1200wdt_miscdev); + if (ret) { + pr_err("Unable to register miscdev on minor %d\n", + WATCHDOG_MINOR); + goto out_rbt; + } + + /* ret = 0 */ + +out_clean: + return ret; + +out_rbt: + unregister_reboot_notifier(&sc1200wdt_notifier); + +out_io: + release_region(io, io_len); + +out_pnp: +#if defined CONFIG_PNP + if (isapnp) + pnp_unregister_driver(&scl200wdt_pnp_driver); +#endif + goto out_clean; +} + + +static void __exit sc1200wdt_exit(void) +{ + misc_deregister(&sc1200wdt_miscdev); + unregister_reboot_notifier(&sc1200wdt_notifier); + +#if defined CONFIG_PNP + if (isapnp) + pnp_unregister_driver(&scl200wdt_pnp_driver); + else +#endif + release_region(io, io_len); +} + +module_init(sc1200wdt_init); +module_exit(sc1200wdt_exit); + +MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>"); +MODULE_DESCRIPTION( + "Driver for National Semiconductor PC87307/PC97307 watchdog component"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c new file mode 100644 index 00000000..707e027e --- /dev/null +++ b/drivers/watchdog/sc520_wdt.c @@ -0,0 +1,436 @@ +/* + * AMD Elan SC520 processor Watchdog Timer driver + * + * 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * The authors do NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> + * 9/27 - 2001 [Initial release] + * + * Additional fixes Alan Cox + * - Fixed formatting + * - Removed debug printks + * - Fixed SMP built kernel deadlock + * - Switched to private locks not lock_kernel + * - Used ioremap/writew/readw + * - Added NOWAYOUT support + * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> + * - Change comments + * - Eliminate fop_llseek + * - Change CONFIG_WATCHDOG_NOWAYOUT semantics + * - Add KERN_* tags to printks + * - fix possible wdt_is_open race + * - Report proper capabilities in watchdog_info + * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, + * GETTIMEOUT, SETOPTIONS} ioctls + * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> + * - cleanup of trailing spaces + * - added extra printk's for startup problems + * - use module_param + * - made timeout (the emulated heartbeat) a module_param + * - made the keepalive ping an internal subroutine + * 3/27 - 2004 Changes by Sean Young <sean@mess.org> + * - set MMCR_BASE to 0xfffef000 + * - CBAR does not need to be read + * - removed debugging printks + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + * + * This driver uses memory mapped IO, and spinlock. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +/* + * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) + * + * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s + * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s + * + * We will program the SC520 watchdog for a timeout of 2.01s. + * If we reset the watchdog every ~250ms we should be safe. + */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1 <= timeout <= 3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * AMD Elan SC520 - Watchdog Timer Registers + */ +#define MMCR_BASE 0xfffef000 /* The default base address */ +#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ + +/* WDT Control Register bit definitions */ +#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ +#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ +#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ +#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ +#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ +#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ + +static __u16 __iomem *wdtmrctl; + +static void wdt_timer_ping(unsigned long); +static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static DEFINE_SPINLOCK(wdt_spinlock); + +/* + * Whack the dog + */ + +static void wdt_timer_ping(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)) { + /* Ping the WDT */ + spin_lock(&wdt_spinlock); + writew(0xAAAA, wdtmrctl); + writew(0x5555, wdtmrctl); + spin_unlock(&wdt_spinlock); + + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); +} + +/* + * Utility routines + */ + +static void wdt_config(int writeval) +{ + __u16 dummy; + unsigned long flags; + + /* buy some time (ping) */ + spin_lock_irqsave(&wdt_spinlock, flags); + dummy = readw(wdtmrctl); /* ensure write synchronization */ + writew(0xAAAA, wdtmrctl); + writew(0x5555, wdtmrctl); + /* unlock WDT = make WDT configuration register writable one time */ + writew(0x3333, wdtmrctl); + writew(0xCCCC, wdtmrctl); + /* write WDT configuration register */ + writew(writeval, wdtmrctl); + spin_unlock_irqrestore(&wdt_spinlock, flags); +} + +static int wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + /* Start the watchdog */ + wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); + + pr_info("Watchdog timer is now enabled\n"); + return 0; +} + +static int wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + + /* Stop the watchdog */ + wdt_config(0); + + pr_info("Watchdog timer is now disabled...\n"); + return 0; +} + +static int wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); + return 0; +} + +static int wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ + return -EINVAL; + + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* now scan */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + + /* Well, anyhow someone wrote to us, we should + return that favour */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode *inode, struct file *file) +{ + /* Just in case we're already talking to someone... */ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode *inode, struct file *file) +{ + if (wdt_expect_close == 42) + wdt_turnoff(); + else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + wdt_keepalive(); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SC520", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; + + if (wdt_set_heartbeat(new_timeout)) + return -EINVAL; + + wdt_keepalive(); + /* 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, + .unlocked_ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit sc520_wdt_unload(void) +{ + if (!nowayout) + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + iounmap(wdtmrctl); +} + +static int __init sc520_wdt_init(void) +{ + int rc = -EBUSY; + + /* Check that the timeout value is within it's range ; + if not reset to the default */ + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n", + WATCHDOG_TIMEOUT); + } + + wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2); + if (!wdtmrctl) { + pr_err("Unable to remap memory\n"); + rc = -ENOMEM; + goto err_out_region2; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_ioremap; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, rc); + goto err_out_notifier; + } + + pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +err_out_notifier: + unregister_reboot_notifier(&wdt_notifier); +err_out_ioremap: + iounmap(wdtmrctl); +err_out_region2: + return rc; +} + +module_init(sc520_wdt_init); +module_exit(sc520_wdt_unload); + +MODULE_AUTHOR("Scott and Bill Jennings"); +MODULE_DESCRIPTION( + "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sch311x_wdt.c b/drivers/watchdog/sch311x_wdt.c new file mode 100644 index 00000000..bd86f32d --- /dev/null +++ b/drivers/watchdog/sch311x_wdt.c @@ -0,0 +1,572 @@ +/* + * 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 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 */ +#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 RESGEN 0x1d +#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"); + +static unsigned short therm_trip; +module_param(therm_trip, ushort, 0); +MODULE_PARM_DESC(therm_trip, "Should a ThermTrip trigger the reset generator"); + +#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) +{ + 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 + */ + outb(0x0e, sch311x_wdt_data.runtime_reg + GP60); + + spin_unlock(&sch311x_wdt_data.io_lock); + +} + +static void sch311x_wdt_stop(void) +{ + spin_lock(&sch311x_wdt_data.io_lock); + + /* stop the watchdog */ + outb(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 __devinit sch311x_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned char val; + int err; + + spin_lock_init(&sch311x_wdt_data.io_lock); + + if (!request_region(sch311x_wdt_data.runtime_reg + RESGEN, 1, + DRV_NAME)) { + dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n", + sch311x_wdt_data.runtime_reg + RESGEN, + sch311x_wdt_data.runtime_reg + RESGEN); + err = -EBUSY; + goto exit; + } + + 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_release_region; + } + + 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_region2; + } + + /* 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); + + /* enable watchdog */ + /* -- Reset Generator -- + * Bit 0 Enable Watchdog Timer Generation: 0* = Enabled, 1 = Disabled + * Bit 1 Thermtrip Source Select: O* = No Source, 1 = Source + * Bit 2 WDT2_CTL: WDT input bit + * Bit 3-7 Reserved + */ + outb(0, sch311x_wdt_data.runtime_reg + RESGEN); + val = therm_trip ? 0x06 : 0x04; + outb(val, sch311x_wdt_data.runtime_reg + RESGEN); + + 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_region3; + } + + dev_info(dev, + "SMSC SCH311x WDT initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +exit_release_region3: + release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); +exit_release_region2: + release_region(sch311x_wdt_data.runtime_reg + GP60, 1); +exit_release_region: + release_region(sch311x_wdt_data.runtime_reg + RESGEN, 1); + sch311x_wdt_data.runtime_reg = 0; +exit: + return err; +} + +static int __devexit 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); + release_region(sch311x_wdt_data.runtime_reg + RESGEN, 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/watchdog/scx200_wdt.c b/drivers/watchdog/scx200_wdt.c new file mode 100644 index 00000000..8ae7c282 --- /dev/null +++ b/drivers/watchdog/scx200_wdt.c @@ -0,0 +1,272 @@ +/* drivers/char/watchdog/scx200_wdt.c + + National Semiconductor SCx200 Watchdog support + + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + + Some code taken from: + National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver + (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com> + + 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 author(s) of this software shall not be held liable for damages + of any nature resulting due to the use of this software. This + software is provided AS-IS with no warranties. */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/scx200.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#define DEBUG + +MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); +MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); +MODULE_LICENSE("GPL"); +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 bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +static u16 wdto_restart; +static char expect_close; +static unsigned long open_lock; +static DEFINE_SPINLOCK(scx_lock); + +/* Bits of the WDCNFG register */ +#define W_ENABLE 0x00fa /* Enable watchdog */ +#define W_DISABLE 0x0000 /* Disable watchdog */ + +/* The scaling factor for the timer, this depends on the value of W_ENABLE */ +#define W_SCALE (32768/1024) + +static void scx200_wdt_ping(void) +{ + spin_lock(&scx_lock); + outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO); + spin_unlock(&scx_lock); +} + +static void scx200_wdt_update_margin(void) +{ + pr_info("timer margin %d seconds\n", margin); + wdto_restart = margin * W_SCALE; +} + +static void scx200_wdt_enable(void) +{ + pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart); + + spin_lock(&scx_lock); + outw(0, scx200_cb_base + SCx200_WDT_WDTO); + outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); + outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG); + spin_unlock(&scx_lock); + + scx200_wdt_ping(); +} + +static void scx200_wdt_disable(void) +{ + pr_debug("disabling watchdog timer\n"); + + spin_lock(&scx_lock); + outw(0, scx200_cb_base + SCx200_WDT_WDTO); + outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); + outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG); + spin_unlock(&scx_lock); +} + +static int scx200_wdt_open(struct inode *inode, struct file *file) +{ + /* only allow one at a time */ + if (test_and_set_bit(0, &open_lock)) + return -EBUSY; + scx200_wdt_enable(); + + return nonseekable_open(inode, file); +} + +static int scx200_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close != 42) + pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n"); + else if (!nowayout) + scx200_wdt_disable(); + expect_close = 0; + clear_bit(0, &open_lock); + + return 0; +} + +static int scx200_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_HALT || code == SYS_POWER_OFF) + if (!nowayout) + scx200_wdt_disable(); + + return NOTIFY_DONE; +} + +static struct notifier_block scx200_wdt_notifier = { + .notifier_call = scx200_wdt_notify_sys, +}; + +static ssize_t scx200_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* check for a magic close character */ + if (len) { + size_t i; + + scx200_wdt_ping(); + + expect_close = 0; + for (i = 0; i < len; ++i) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + + return len; + } + + return 0; +} + +static long scx200_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .identity = "NatSemi SCx200 Watchdog", + .firmware_version = 1, + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + }; + int new_margin; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(0, p)) + return -EFAULT; + return 0; + case WDIOC_KEEPALIVE: + scx200_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (new_margin < 1) + return -EINVAL; + margin = new_margin; + scx200_wdt_update_margin(); + scx200_wdt_ping(); + case WDIOC_GETTIMEOUT: + if (put_user(margin, p)) + return -EFAULT; + return 0; + default: + return -ENOTTY; + } +} + +static const struct file_operations scx200_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = scx200_wdt_write, + .unlocked_ioctl = scx200_wdt_ioctl, + .open = scx200_wdt_open, + .release = scx200_wdt_release, +}; + +static struct miscdevice scx200_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &scx200_wdt_fops, +}; + +static int __init scx200_wdt_init(void) +{ + int r; + + pr_debug("NatSemi SCx200 Watchdog Driver\n"); + + /* check that we have found the configuration block */ + if (!scx200_cb_present()) + return -ENODEV; + + if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE, + "NatSemi SCx200 Watchdog")) { + pr_warn("watchdog I/O region busy\n"); + return -EBUSY; + } + + scx200_wdt_update_margin(); + scx200_wdt_disable(); + + r = register_reboot_notifier(&scx200_wdt_notifier); + if (r) { + pr_err("unable to register reboot notifier\n"); + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); + return r; + } + + r = misc_register(&scx200_wdt_miscdev); + if (r) { + unregister_reboot_notifier(&scx200_wdt_notifier); + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); + return r; + } + + return 0; +} + +static void __exit scx200_wdt_cleanup(void) +{ + misc_deregister(&scx200_wdt_miscdev); + unregister_reboot_notifier(&scx200_wdt_notifier); + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); +} + +module_init(scx200_wdt_init); +module_exit(scx200_wdt_cleanup); + +/* + Local variables: + 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 new file mode 100644 index 00000000..93958a77 --- /dev/null +++ b/drivers/watchdog/shwdt.c @@ -0,0 +1,490 @@ +/* + * drivers/watchdog/shwdt.c + * + * Watchdog driver for integrated watchdog in the SuperH processors. + * + * Copyright (C) 2001 - 2010 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * + * 19-Apr-2002 Rob Radez <rob@osinvestor.com> + * Added expect close support, made emulated timeout runtime changeable + * general cleanups, add some ioctls + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/ioport.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/uaccess.h> +#include <asm/watchdog.h> + +#define DRV_NAME "sh-wdt" + +/* + * Default clock division ratio is 5.25 msecs. For an additional table of + * values, consult the asm-sh/watchdog.h. Overload this at module load + * time. + * + * In order for this to work reliably we need to have HZ set to 1000 or + * something quite higher than 100 (or we need a proper high-res timer + * implementation that will deal with this properly), otherwise the 10ms + * resolution of a jiffy is enough to trigger the overflow. For things like + * the SH-4 and SH-5, this isn't necessarily that big of a problem, though + * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely + * necssary. + * + * As a result of this timing problem, the only modes that are particularly + * feasible are the 4096 and the 2048 divisors, which yield 5.25 and 2.62ms + * overflow periods respectively. + * + * Also, since we can't really expect userspace to be responsive enough + * before the overflow happens, we maintain two separate timers .. One in + * the kernel for clearing out WOVF every 2ms or so (again, this depends on + * HZ == 1000), and another for monitoring userspace writes to the WDT device. + * + * As such, we currently use a configurable heartbeat interval which defaults + * to 30s. In this case, the userspace daemon is only responsible for periodic + * writes to the device before the next heartbeat is scheduled. If the daemon + * misses its deadline, the kernel timer will allow the WDT to overflow. + */ +static int clock_division_ratio = WTCSR_CKS_4096; +#define next_ping_period(cks) (jiffies + msecs_to_jiffies(cks - 4)) + +static const struct watchdog_info sh_wdt_info; +static struct platform_device *sh_wdt_dev; +static DEFINE_SPINLOCK(shwdt_lock); + +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ +static bool nowayout = WATCHDOG_NOWAYOUT; +static unsigned long next_heartbeat; + +struct sh_wdt { + void __iomem *base; + struct device *dev; + + struct timer_list timer; + + unsigned long enabled; + char expect_close; +}; + +static void sh_wdt_start(struct sh_wdt *wdt) +{ + unsigned long flags; + u8 csr; + + spin_lock_irqsave(&shwdt_lock, flags); + + next_heartbeat = jiffies + (heartbeat * HZ); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); + + csr = sh_wdt_read_csr(); + csr |= WTCSR_WT | clock_division_ratio; + sh_wdt_write_csr(csr); + + sh_wdt_write_cnt(0); + + /* + * These processors have a bit of an inconsistent initialization + * process.. starting with SH-3, RSTS was moved to WTCSR, and the + * RSTCSR register was removed. + * + * On the SH-2 however, in addition with bits being in different + * locations, we must deal with RSTCSR outright.. + */ + csr = sh_wdt_read_csr(); + csr |= WTCSR_TME; + csr &= ~WTCSR_RSTS; + sh_wdt_write_csr(csr); + +#ifdef CONFIG_CPU_SH2 + csr = sh_wdt_read_rstcsr(); + csr &= ~RSTCSR_RSTS; + sh_wdt_write_rstcsr(csr); +#endif + spin_unlock_irqrestore(&shwdt_lock, flags); +} + +static void sh_wdt_stop(struct sh_wdt *wdt) +{ + unsigned long flags; + u8 csr; + + spin_lock_irqsave(&shwdt_lock, flags); + + del_timer(&wdt->timer); + + csr = sh_wdt_read_csr(); + csr &= ~WTCSR_TME; + sh_wdt_write_csr(csr); + + spin_unlock_irqrestore(&shwdt_lock, flags); +} + +static inline void sh_wdt_keepalive(struct sh_wdt *wdt) +{ + unsigned long flags; + + spin_lock_irqsave(&shwdt_lock, flags); + next_heartbeat = jiffies + (heartbeat * HZ); + spin_unlock_irqrestore(&shwdt_lock, flags); +} + +static int sh_wdt_set_heartbeat(int t) +{ + unsigned long flags; + + if (unlikely(t < 1 || t > 3600)) /* arbitrary upper limit */ + return -EINVAL; + + spin_lock_irqsave(&shwdt_lock, flags); + heartbeat = t; + spin_unlock_irqrestore(&shwdt_lock, flags); + return 0; +} + +static void sh_wdt_ping(unsigned long data) +{ + struct sh_wdt *wdt = (struct sh_wdt *)data; + unsigned long flags; + + spin_lock_irqsave(&shwdt_lock, flags); + if (time_before(jiffies, next_heartbeat)) { + u8 csr; + + csr = sh_wdt_read_csr(); + csr &= ~WTCSR_IOVF; + sh_wdt_write_csr(csr); + + sh_wdt_write_cnt(0); + + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); + } else + dev_warn(wdt->dev, "Heartbeat lost! Will not ping " + "the watchdog\n"); + spin_unlock_irqrestore(&shwdt_lock, flags); +} + +static int sh_wdt_open(struct inode *inode, struct file *file) +{ + struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); + + if (test_and_set_bit(0, &wdt->enabled)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + file->private_data = wdt; + + sh_wdt_start(wdt); + + return nonseekable_open(inode, file); +} + +static int sh_wdt_close(struct inode *inode, struct file *file) +{ + struct sh_wdt *wdt = file->private_data; + + if (wdt->expect_close == 42) { + sh_wdt_stop(wdt); + } else { + dev_crit(wdt->dev, "Unexpected close, not " + "stopping watchdog!\n"); + sh_wdt_keepalive(wdt); + } + + clear_bit(0, &wdt->enabled); + wdt->expect_close = 0; + + return 0; +} + +static ssize_t sh_wdt_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct sh_wdt *wdt = file->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + wdt->expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + wdt->expect_close = 42; + } + } + sh_wdt_keepalive(wdt); + } + + return count; +} + +static long sh_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct sh_wdt *wdt = file->private_data; + 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_SETOPTIONS: + if (get_user(options, (int *)arg)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + sh_wdt_stop(wdt); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + sh_wdt_start(wdt); + retval = 0; + } + + return retval; + case WDIOC_KEEPALIVE: + sh_wdt_keepalive(wdt); + 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(wdt); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, (int *)arg); + default: + return -ENOTTY; + } + return 0; +} + +static int sh_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); + + if (code == SYS_DOWN || code == SYS_HALT) + sh_wdt_stop(wdt); + + return NOTIFY_DONE; +} + +static const struct file_operations sh_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sh_wdt_write, + .unlocked_ioctl = sh_wdt_ioctl, + .open = sh_wdt_open, + .release = sh_wdt_close, +}; + +static const struct watchdog_info sh_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SH WDT", +}; + +static struct notifier_block sh_wdt_notifier = { + .notifier_call = sh_wdt_notify_sys, +}; + +static struct miscdevice sh_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sh_wdt_fops, +}; + +static int __devinit sh_wdt_probe(struct platform_device *pdev) +{ + struct sh_wdt *wdt; + struct resource *res; + int rc; + + /* + * As this driver only covers the global watchdog case, reject + * any attempts to register per-CPU watchdogs. + */ + if (pdev->id != -1) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) + return -EINVAL; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), DRV_NAME)) + return -EBUSY; + + wdt = devm_kzalloc(&pdev->dev, sizeof(struct sh_wdt), GFP_KERNEL); + if (unlikely(!wdt)) { + rc = -ENOMEM; + goto out_release; + } + + wdt->dev = &pdev->dev; + + wdt->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (unlikely(!wdt->base)) { + rc = -ENXIO; + goto out_err; + } + + rc = register_reboot_notifier(&sh_wdt_notifier); + if (unlikely(rc)) { + dev_err(&pdev->dev, + "Can't register reboot notifier (err=%d)\n", rc); + goto out_unmap; + } + + sh_wdt_miscdev.parent = wdt->dev; + + rc = misc_register(&sh_wdt_miscdev); + if (unlikely(rc)) { + dev_err(&pdev->dev, + "Can't register miscdev on minor=%d (err=%d)\n", + sh_wdt_miscdev.minor, rc); + goto out_unreg; + } + + 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); + + platform_set_drvdata(pdev, wdt); + sh_wdt_dev = pdev; + + dev_info(&pdev->dev, "initialized.\n"); + + return 0; + +out_unreg: + unregister_reboot_notifier(&sh_wdt_notifier); +out_unmap: + devm_iounmap(&pdev->dev, wdt->base); +out_err: + devm_kfree(&pdev->dev, wdt); +out_release: + devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); + + return rc; +} + +static int __devexit sh_wdt_remove(struct platform_device *pdev) +{ + struct sh_wdt *wdt = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + platform_set_drvdata(pdev, NULL); + + misc_deregister(&sh_wdt_miscdev); + + sh_wdt_dev = NULL; + + unregister_reboot_notifier(&sh_wdt_notifier); + devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); + devm_iounmap(&pdev->dev, wdt->base); + devm_kfree(&pdev->dev, wdt); + + return 0; +} + +static struct platform_driver sh_wdt_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + + .probe = sh_wdt_probe, + .remove = __devexit_p(sh_wdt_remove), +}; + +static int __init sh_wdt_init(void) +{ + int rc; + + if (unlikely(clock_division_ratio < 0x5 || + clock_division_ratio > 0x7)) { + clock_division_ratio = WTCSR_CKS_4096; + + pr_info("divisor must be 0x5<=x<=0x7, using %d\n", + clock_division_ratio); + } + + rc = sh_wdt_set_heartbeat(heartbeat); + if (unlikely(rc)) { + heartbeat = WATCHDOG_HEARTBEAT; + + pr_info("heartbeat value must be 1<=x<=3600, using %d\n", + heartbeat); + } + + pr_info("configured with heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return platform_driver_register(&sh_wdt_driver); +} + +static void __exit sh_wdt_exit(void) +{ + platform_driver_unregister(&sh_wdt_driver); +} +module_init(sh_wdt_init); +module_exit(sh_wdt_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("SuperH watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(clock_division_ratio, int, 0); +MODULE_PARM_DESC(clock_division_ratio, + "Clock division ratio. Valid ranges are from 0x5 (1.31ms) " + "to 0x7 (5.25ms). (default=" __MODULE_STRING(WTCSR_CKS_4096) ")"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (1 <= heartbeat <= 3600, default=" + __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); diff --git a/drivers/watchdog/smsc37b787_wdt.c b/drivers/watchdog/smsc37b787_wdt.c new file mode 100644 index 00000000..6d665f9c --- /dev/null +++ b/drivers/watchdog/smsc37b787_wdt.c @@ -0,0 +1,620 @@ +/* + * SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x + * + * Based on acquirewdt.c by Alan Cox <alan@lxorguk.ukuu.org.uk> + * and some other existing drivers + * + * 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 authors do NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (C) Copyright 2003-2006 Sven Anders <anders@anduras.de> + * + * History: + * 2003 - Created version 1.0 for Linux 2.4.x. + * 2006 - Ported to Linux 2.6, added nowayout and MAGICCLOSE + * features. Released version 1.1 + * + * Theory of operation: + * + * A Watchdog Timer (WDT) is a hardware circuit that can + * reset the computer system in case of a software fault. + * You probably knew that already. + * + * Usually a userspace daemon will notify the kernel WDT driver + * via the /dev/watchdog special device file that userspace is + * still alive, at regular intervals. When such a notification + * occurs, the driver will usually tell the hardware watchdog + * that everything is in order, and that the watchdog should wait + * for yet another little while to reset the system. + * If userspace fails (RAM error, kernel bug, whatever), the + * notifications cease to occur, and the hardware watchdog will + * reset the system (causing a reboot) after the timeout occurs. + * + * Create device with: + * mknod /dev/watchdog c 10 130 + * + * For an example userspace keep-alive daemon, see: + * Documentation/watchdog/wdt.txt + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +/* enable support for minutes as units? */ +/* (does not always work correctly, so disabled by default!) */ +#define SMSC_SUPPORT_MINUTES +#undef SMSC_SUPPORT_MINUTES + +#define MAX_TIMEOUT 255 + +#define UNIT_SECOND 0 +#define UNIT_MINUTE 1 + +#define VERSION "1.1" + +#define IOPORT 0x3F0 +#define IOPORT_SIZE 2 +#define IODEV_NO 8 + +static int unit = UNIT_SECOND; /* timer's unit */ +static int timeout = 60; /* timeout value: default is 60 "units" */ +static unsigned long timer_enabled; /* is the timer enabled? */ + +static char expect_close; /* is the close expected? */ + +static DEFINE_SPINLOCK(io_lock);/* to guard the watchdog from io races */ + +static bool nowayout = WATCHDOG_NOWAYOUT; + +/* -- Low level function ----------------------------------------*/ + +/* unlock the IO chip */ + +static inline void open_io_config(void) +{ + outb(0x55, IOPORT); + mdelay(1); + outb(0x55, IOPORT); +} + +/* lock the IO chip */ +static inline void close_io_config(void) +{ + outb(0xAA, IOPORT); +} + +/* select the IO device */ +static inline void select_io_device(unsigned char devno) +{ + outb(0x07, IOPORT); + outb(devno, IOPORT+1); +} + +/* write to the control register */ +static inline void write_io_cr(unsigned char reg, unsigned char data) +{ + outb(reg, IOPORT); + outb(data, IOPORT+1); +} + +/* read from the control register */ +static inline char read_io_cr(unsigned char reg) +{ + outb(reg, IOPORT); + return inb(IOPORT+1); +} + +/* -- Medium level functions ------------------------------------*/ + +static inline void gpio_bit12(unsigned char reg) +{ + /* -- General Purpose I/O Bit 1.2 -- + * Bit 0, In/Out: 0 = Output, 1 = Input + * Bit 1, Polarity: 0 = No Invert, 1 = Invert + * Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable + * Bit 3/4, Function select: 00 = GPI/O, 01 = WDT, 10 = P17, + * 11 = Either Edge Triggered Intr. 2 + * Bit 5/6 (Reserved) + * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain + */ + write_io_cr(0xE2, reg); +} + +static inline void gpio_bit13(unsigned char reg) +{ + /* -- General Purpose I/O Bit 1.3 -- + * Bit 0, In/Out: 0 = Output, 1 = Input + * Bit 1, Polarity: 0 = No Invert, 1 = Invert + * Bit 2, Group Enable Intr.: 0 = Disable, 1 = Enable + * Bit 3, Function select: 0 = GPI/O, 1 = LED + * Bit 4-6 (Reserved) + * Bit 7, Output Type: 0 = Push Pull Bit, 1 = Open Drain + */ + write_io_cr(0xE3, reg); +} + +static inline void wdt_timer_units(unsigned char new_units) +{ + /* -- Watchdog timer units -- + * Bit 0-6 (Reserved) + * Bit 7, WDT Time-out Value Units Select + * (0 = Minutes, 1 = Seconds) + */ + write_io_cr(0xF1, new_units); +} + +static inline void wdt_timeout_value(unsigned char new_timeout) +{ + /* -- Watchdog Timer Time-out Value -- + * Bit 0-7 Binary coded units (0=Disabled, 1..255) + */ + write_io_cr(0xF2, new_timeout); +} + +static inline void wdt_timer_conf(unsigned char conf) +{ + /* -- Watchdog timer configuration -- + * Bit 0 Joystick enable: 0* = No Reset, 1 = Reset WDT upon + * Gameport I/O + * Bit 1 Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr. + * Bit 2 Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr + * Bit 3 Reset the timer + * (Wrong in SMsC documentation? Given as: PowerLED Timout + * Enabled) + * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled, + * 0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15) + */ + write_io_cr(0xF3, conf); +} + +static inline void wdt_timer_ctrl(unsigned char reg) +{ + /* -- Watchdog timer control -- + * Bit 0 Status Bit: 0 = Timer counting, 1 = Timeout occurred + * Bit 1 Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz + * Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning) + * Bit 3 P20 Force Timeout enabled: + * 0 = P20 activity does not generate the WD timeout event + * 1 = P20 Allows rising edge of P20, from the keyboard + * controller, to force the WD timeout event. + * Bit 4 (Reserved) + * -- Soft power management -- + * Bit 5 Stop Counter: 1 = Stop software power down counter + * set via register 0xB8, (self-cleaning) + * (Upon read: 0 = Counter running, 1 = Counter stopped) + * Bit 6 Restart Counter: 1 = Restart software power down counter + * set via register 0xB8, (self-cleaning) + * Bit 7 SPOFF: 1 = Force software power down (self-cleaning) + */ + write_io_cr(0xF4, reg); +} + +/* -- Higher level functions ------------------------------------*/ + +/* initialize watchdog */ + +static void wb_smsc_wdt_initialize(void) +{ + unsigned char old; + + spin_lock(&io_lock); + open_io_config(); + select_io_device(IODEV_NO); + + /* enable the watchdog */ + gpio_bit13(0x08); /* Select pin 80 = LED not GPIO */ + gpio_bit12(0x0A); /* Set pin 79 = WDT not + GPIO/Output/Polarity=Invert */ + /* disable the timeout */ + wdt_timeout_value(0); + + /* reset control register */ + wdt_timer_ctrl(0x00); + + /* reset configuration register */ + wdt_timer_conf(0x00); + + /* read old (timer units) register */ + old = read_io_cr(0xF1) & 0x7F; + if (unit == UNIT_SECOND) + old |= 0x80; /* set to seconds */ + + /* set the watchdog timer units */ + wdt_timer_units(old); + + close_io_config(); + spin_unlock(&io_lock); +} + +/* shutdown the watchdog */ + +static void wb_smsc_wdt_shutdown(void) +{ + spin_lock(&io_lock); + open_io_config(); + select_io_device(IODEV_NO); + + /* disable the watchdog */ + gpio_bit13(0x09); + gpio_bit12(0x09); + + /* reset watchdog config register */ + wdt_timer_conf(0x00); + + /* reset watchdog control register */ + wdt_timer_ctrl(0x00); + + /* disable timeout */ + wdt_timeout_value(0x00); + + close_io_config(); + spin_unlock(&io_lock); +} + +/* set timeout => enable watchdog */ + +static void wb_smsc_wdt_set_timeout(unsigned char new_timeout) +{ + spin_lock(&io_lock); + open_io_config(); + select_io_device(IODEV_NO); + + /* set Power LED to blink, if we enable the timeout */ + wdt_timer_ctrl((new_timeout == 0) ? 0x00 : 0x02); + + /* set timeout value */ + wdt_timeout_value(new_timeout); + + close_io_config(); + spin_unlock(&io_lock); +} + +/* get timeout */ + +static unsigned char wb_smsc_wdt_get_timeout(void) +{ + unsigned char set_timeout; + + spin_lock(&io_lock); + open_io_config(); + select_io_device(IODEV_NO); + set_timeout = read_io_cr(0xF2); + close_io_config(); + spin_unlock(&io_lock); + + return set_timeout; +} + +/* disable watchdog */ + +static void wb_smsc_wdt_disable(void) +{ + /* set the timeout to 0 to disable the watchdog */ + wb_smsc_wdt_set_timeout(0); +} + +/* enable watchdog by setting the current timeout */ + +static void wb_smsc_wdt_enable(void) +{ + /* set the current timeout... */ + wb_smsc_wdt_set_timeout(timeout); +} + +/* reset the timer */ + +static void wb_smsc_wdt_reset_timer(void) +{ + spin_lock(&io_lock); + open_io_config(); + select_io_device(IODEV_NO); + + /* reset the timer */ + wdt_timeout_value(timeout); + wdt_timer_conf(0x08); + + close_io_config(); + spin_unlock(&io_lock); +} + +/* return, if the watchdog is enabled (timeout is set...) */ + +static int wb_smsc_wdt_status(void) +{ + return (wb_smsc_wdt_get_timeout() == 0) ? 0 : WDIOF_KEEPALIVEPING; +} + + +/* -- File operations -------------------------------------------*/ + +/* open => enable watchdog and set initial timeout */ + +static int wb_smsc_wdt_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + + if (test_and_set_bit(0, &timer_enabled)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Reload and activate timer */ + wb_smsc_wdt_enable(); + + pr_info("Watchdog enabled. Timeout set to %d %s\n", + timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); + + return nonseekable_open(inode, file); +} + +/* close => shut off the timer */ + +static int wb_smsc_wdt_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. */ + + if (expect_close == 42) { + wb_smsc_wdt_disable(); + pr_info("Watchdog disabled, sleeping again...\n"); + } else { + pr_crit("Unexpected close, not stopping watchdog!\n"); + wb_smsc_wdt_reset_timer(); + } + + clear_bit(0, &timer_enabled); + expect_close = 0; + return 0; +} + +/* write => update the timer to keep the machine alive */ + +static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* reset expect flag */ + expect_close = 0; + + /* scan to see whether or not we got the + magic character */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + wb_smsc_wdt_reset_timer(); + } + return len; +} + +/* ioctl => control interface */ + +static long wb_smsc_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_timeout; + + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "SMsC 37B787 Watchdog", + }; + + uarg.i = (int __user *)arg; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, sizeof(ident)) + ? -EFAULT : 0; + case WDIOC_GETSTATUS: + return put_user(wb_smsc_wdt_status(), uarg.i); + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, uarg.i)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wb_smsc_wdt_disable(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + wb_smsc_wdt_enable(); + retval = 0; + } + return retval; + } + case WDIOC_KEEPALIVE: + wb_smsc_wdt_reset_timer(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + /* the API states this is given in secs */ + if (unit == UNIT_MINUTE) + new_timeout /= 60; + if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) + return -EINVAL; + timeout = new_timeout; + wb_smsc_wdt_set_timeout(timeout); + /* 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) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* set timeout to 0, to avoid possible race-condition */ + timeout = 0; + wb_smsc_wdt_disable(); + } + return NOTIFY_DONE; +} + +/* -- Module's structures ---------------------------------------*/ + +static const struct file_operations wb_smsc_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wb_smsc_wdt_write, + .unlocked_ioctl = wb_smsc_wdt_ioctl, + .open = wb_smsc_wdt_open, + .release = wb_smsc_wdt_release, +}; + +static struct notifier_block wb_smsc_wdt_notifier = { + .notifier_call = wb_smsc_wdt_notify_sys, +}; + +static struct miscdevice wb_smsc_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wb_smsc_wdt_fops, +}; + +/* -- Module init functions -------------------------------------*/ + +/* module's "constructor" */ + +static int __init wb_smsc_wdt_init(void) +{ + int ret; + + pr_info("SMsC 37B787 watchdog component driver " + VERSION " initialising...\n"); + + if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) { + pr_err("Unable to register IO port %#x\n", IOPORT); + ret = -EBUSY; + goto out_pnp; + } + + /* set new maximum, if it's too big */ + if (timeout > MAX_TIMEOUT) + timeout = MAX_TIMEOUT; + + /* init the watchdog timer */ + wb_smsc_wdt_initialize(); + + ret = register_reboot_notifier(&wb_smsc_wdt_notifier); + if (ret) { + pr_err("Unable to register reboot notifier err = %d\n", ret); + goto out_io; + } + + ret = misc_register(&wb_smsc_wdt_miscdev); + if (ret) { + pr_err("Unable to register miscdev on minor %d\n", + WATCHDOG_MINOR); + goto out_rbt; + } + + /* output info */ + pr_info("Timeout set to %d %s\n", + timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)"); + pr_info("Watchdog initialized and sleeping (nowayout=%d)...\n", + nowayout); +out_clean: + return ret; + +out_rbt: + unregister_reboot_notifier(&wb_smsc_wdt_notifier); + +out_io: + release_region(IOPORT, IOPORT_SIZE); + +out_pnp: + goto out_clean; +} + +/* module's "destructor" */ + +static void __exit wb_smsc_wdt_exit(void) +{ + /* Stop the timer before we leave */ + if (!nowayout) { + wb_smsc_wdt_shutdown(); + pr_info("Watchdog disabled\n"); + } + + misc_deregister(&wb_smsc_wdt_miscdev); + unregister_reboot_notifier(&wb_smsc_wdt_notifier); + release_region(IOPORT, IOPORT_SIZE); + + pr_info("SMsC 37B787 watchdog component driver removed\n"); +} + +module_init(wb_smsc_wdt_init); +module_exit(wb_smsc_wdt_exit); + +MODULE_AUTHOR("Sven Anders <anders@anduras.de>"); +MODULE_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version " + VERSION ")"); +MODULE_LICENSE("GPL"); + +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"); +#endif + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "range is 1-255 units, default is 60"); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); diff --git a/drivers/watchdog/softdog.c b/drivers/watchdog/softdog.c new file mode 100644 index 00000000..fe83beb8 --- /dev/null +++ b/drivers/watchdog/softdog.c @@ -0,0 +1,211 @@ +/* + * SoftDog: 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. + * + * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> + * + * Software only watchdog driver. Unlike its big brother the WDT501P + * driver this won't always recover a failed machine. + * + * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : + * Modularised. + * Added soft_margin; use upon insmod to change the timer delay. + * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate + * minors. + * + * 19980911 Alan Cox + * Made SMP safe for 2.3.x + * + * 20011127 Joel Becker (jlbec@evilplan.org> + * Added soft_noboot; Allows testing the softdog trigger without + * requiring a recompile. + * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. + * + * 20020530 Joel Becker <joel.becker@oracle.com> + * 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/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> + +#define TIMER_MARGIN 60 /* Default is 60 seconds */ +static unsigned int soft_margin = TIMER_MARGIN; /* in seconds */ +module_param(soft_margin, uint, 0); +MODULE_PARM_DESC(soft_margin, + "Watchdog soft_margin in seconds. (0 < soft_margin < 65536, default=" + __MODULE_STRING(TIMER_MARGIN) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static int soft_noboot = 0; +module_param(soft_noboot, int, 0); +MODULE_PARM_DESC(soft_noboot, + "Softdog action, set to 1 to ignore reboots, 0 to reboot (default=0)"); + +static int soft_panic; +module_param(soft_panic, int, 0); +MODULE_PARM_DESC(soft_panic, + "Softdog action, set to 1 to panic, 0 to reboot (default=0)"); + +/* + * Our timer + */ + +static void watchdog_fire(unsigned long); + +static struct timer_list watchdog_ticktock = + TIMER_INITIALIZER(watchdog_fire, 0, 0); + +/* + * If the timer expires.. + */ + +static void watchdog_fire(unsigned long data) +{ + if (soft_noboot) + pr_crit("Triggered - Reboot ignored\n"); + else if (soft_panic) { + pr_crit("Initiating panic\n"); + panic("Software Watchdog Timer expired"); + } else { + pr_crit("Initiating system reboot\n"); + emergency_restart(); + pr_crit("Reboot didn't ?????\n"); + } +} + +/* + * Softdog operations + */ + +static int softdog_ping(struct watchdog_device *w) +{ + mod_timer(&watchdog_ticktock, jiffies+(w->timeout*HZ)); + return 0; +} + +static int softdog_stop(struct watchdog_device *w) +{ + del_timer(&watchdog_ticktock); + return 0; +} + +static int softdog_set_timeout(struct watchdog_device *w, unsigned int t) +{ + w->timeout = t; + return 0; +} + +/* + * Notifier for system down + */ + +static int softdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + /* Turn the WDT off */ + softdog_stop(NULL); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct notifier_block softdog_notifier = { + .notifier_call = softdog_notify_sys, +}; + +static struct watchdog_info softdog_info = { + .identity = "Software Watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, +}; + +static struct watchdog_ops softdog_ops = { + .owner = THIS_MODULE, + .start = softdog_ping, + .stop = softdog_stop, + .ping = softdog_ping, + .set_timeout = softdog_set_timeout, +}; + +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 (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) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + return ret; + } + + ret = watchdog_register_device(&softdog_dev); + if (ret) { + unregister_reboot_notifier(&softdog_notifier); + return ret; + } + + 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) +{ + watchdog_unregister_device(&softdog_dev); + unregister_reboot_notifier(&softdog_notifier); +} + +module_init(watchdog_init); +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 00000000..59108e48 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.c @@ -0,0 +1,487 @@ +/* + * 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" + */ + +/* + * 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.01" +#define TCO_MODULE_NAME "SP5100 TCO timer" +#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION + +/* internal variables */ +static u32 tcobase_phys; +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; +} + +/* + * /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 DEFINE_PCI_DEVICE_TABLE(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 __devinit sp5100_tco_setupdevice(void) +{ + struct pci_dev *dev = NULL; + u32 val; + + /* 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; + + /* Request the IO ports used by this driver */ + pm_iobase = SP5100_IO_PM_INDEX_REG; + if (!request_region(pm_iobase, SP5100_PM_IOPORTS_SIZE, "SP5100 TCO")) { + pr_err("I/O address 0x%04x already in use\n", pm_iobase); + goto exit; + } + + /* Find the watchdog base address. */ + outb(SP5100_PM_WATCHDOG_BASE3, SP5100_IO_PM_INDEX_REG); + val = inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE2, SP5100_IO_PM_INDEX_REG); + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE1, SP5100_IO_PM_INDEX_REG); + val = val << 8 | inb(SP5100_IO_PM_DATA_REG); + outb(SP5100_PM_WATCHDOG_BASE0, SP5100_IO_PM_INDEX_REG); + /* Low three bits of BASE0 are reserved. */ + val = val << 8 | (inb(SP5100_IO_PM_DATA_REG) & 0xf8); + + if (!request_mem_region_exclusive(val, SP5100_WDT_MEM_MAP_SIZE, + "SP5100 TCO")) { + pr_err("mmio address 0x%04x already in use\n", val); + goto unreg_region; + } + tcobase_phys = val; + + tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); + if (tcobase == 0) { + pr_err("failed to get tcobase address\n"); + goto unreg_mem_region; + } + + /* 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); + + /* Check that the watchdog action is set to reset the system. */ + val = readl(SP5100_WDT_CONTROL(tcobase)); + 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 __devinit sp5100_tco_init(struct platform_device *dev) +{ + int ret; + u32 val; + + /* 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("Watchdog reboot %sdetected\n", + readl(SP5100_WDT_CONTROL(tcobase)) & SP5100_PM_WATCHDOG_FIRED ? + "" : "not "); + + /* Clear out the old status */ + val = readl(SP5100_WDT_CONTROL(tcobase)); + val &= ~SP5100_PM_WATCHDOG_FIRED; + writel(val, SP5100_WDT_CONTROL(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); + } + + 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); + + 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 __devexit 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 __devexit 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 = __devexit_p(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 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 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 chipset"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sp5100_tco.h b/drivers/watchdog/sp5100_tco.h new file mode 100644 index 00000000..a5a16cc9 --- /dev/null +++ b/drivers/watchdog/sp5100_tco.h @@ -0,0 +1,41 @@ +/* + * 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 +#define SP5100_WDT_TRIGGER_BIT (1 << 7) + +#define SP5100_PCI_WATCHDOG_MISC_REG 0x41 +#define SP5100_PCI_WATCHDOG_DECODE_EN (1 << 3) + +#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. + */ +#define SP5100_IO_PM_INDEX_REG 0xCD6 +#define SP5100_IO_PM_DATA_REG 0xCD7 + +#define SP5100_PM_WATCHDOG_CONTROL 0x69 +#define SP5100_PM_WATCHDOG_BASE0 0x6C +#define SP5100_PM_WATCHDOG_BASE1 0x6D +#define SP5100_PM_WATCHDOG_BASE2 0x6E +#define SP5100_PM_WATCHDOG_BASE3 0x6F + +#define SP5100_PM_WATCHDOG_FIRED (1 << 1) +#define SP5100_PM_WATCHDOG_ACTION_RESET (1 << 2) + +#define SP5100_PM_WATCHDOG_DISABLE 1 +#define SP5100_PM_WATCHDOG_SECOND_RES (3 << 1) diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c new file mode 100644 index 00000000..bbb170e5 --- /dev/null +++ b/drivers/watchdog/sp805_wdt.c @@ -0,0 +1,405 @@ +/* + * drivers/char/watchdog/sp805-wdt.c + * + * Watchdog driver for ARM SP805 watchdog module + * + * Copyright (C) 2010 ST Microelectronics + * Viresh Kumar<viresh.kumar@st.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/fs.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/math64.h> +#include <linux/miscdevice.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/uaccess.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 + * @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 + * @timeout: current programmed timeout + */ +struct sp805_wdt { + spinlock_t lock; + void __iomem *base; + struct clk *clk; + struct amba_device *adev; + unsigned long status; + #define WDT_BUSY 0 + #define WDT_CAN_BE_CLOSED 1 + unsigned int load_val; + unsigned int timeout; +}; + +/* local variables */ +static struct sp805_wdt *wdt; +static bool nowayout = WATCHDOG_NOWAYOUT; + +/* This routine finds load value that will reset system in required timout */ +static void wdt_setload(unsigned int timeout) +{ + 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 */ + wdt->timeout = div_u64((load + 1) * 2 + (rate / 2), rate); + spin_unlock(&wdt->lock); +} + +/* returns number of seconds left for reset to occur */ +static u32 wdt_timeleft(void) +{ + 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); +} + +/* enables watchdog timers reset */ +static void wdt_enable(void) +{ + spin_lock(&wdt->lock); + + writel_relaxed(UNLOCK, wdt->base + WDTLOCK); + writel_relaxed(wdt->load_val, wdt->base + WDTLOAD); + writel_relaxed(INT_MASK, wdt->base + WDTINTCLR); + 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); +} + +/* disables watchdog timers reset */ +static void wdt_disable(void) +{ + 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); +} + +static ssize_t sp805_wdt_write(struct file *file, const char *data, + size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + /* Check for Magic Close character */ + if (c == 'V') { + set_bit(WDT_CAN_BE_CLOSED, + &wdt->status); + break; + } + } + } + wdt_enable(); + } + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = MODULE_NAME, +}; + +static long sp805_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOTTY; + unsigned int timeout; + + 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_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(timeout, (unsigned int *)arg); + if (ret) + break; + + wdt_setload(timeout); + + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(wdt->timeout, (unsigned int *)arg); + break; + case WDIOC_GETTIMELEFT: + ret = put_user(wdt_timeleft(), (unsigned int *)arg); + break; + } + return ret; +} + +static int sp805_wdt_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + if (test_and_set_bit(WDT_BUSY, &wdt->status)) + return -EBUSY; + + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(&wdt->adev->dev, "clock enable fail"); + goto err; + } + + wdt_enable(); + + /* can not be closed, once enabled */ + clear_bit(WDT_CAN_BE_CLOSED, &wdt->status); + return nonseekable_open(inode, file); + +err: + clear_bit(WDT_BUSY, &wdt->status); + return ret; +} + +static int sp805_wdt_release(struct inode *inode, struct file *file) +{ + if (!test_bit(WDT_CAN_BE_CLOSED, &wdt->status)) { + clear_bit(WDT_BUSY, &wdt->status); + dev_warn(&wdt->adev->dev, "Device closed unexpectedly\n"); + return 0; + } + + wdt_disable(); + clk_disable(wdt->clk); + clear_bit(WDT_BUSY, &wdt->status); + + return 0; +} + +static const struct file_operations sp805_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sp805_wdt_write, + .unlocked_ioctl = sp805_wdt_ioctl, + .open = sp805_wdt_open, + .release = sp805_wdt_release, +}; + +static struct miscdevice sp805_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sp805_wdt_fops, +}; + +static int __devinit +sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) +{ + int ret = 0; + + if (!devm_request_mem_region(&adev->dev, adev->res.start, + resource_size(&adev->res), "sp805_wdt")) { + dev_warn(&adev->dev, "Failed to get memory region resource\n"); + ret = -ENOENT; + goto err; + } + + wdt = devm_kzalloc(&adev->dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) { + dev_warn(&adev->dev, "Kzalloc failed\n"); + ret = -ENOMEM; + goto err; + } + + wdt->base = devm_ioremap(&adev->dev, adev->res.start, + resource_size(&adev->res)); + if (!wdt->base) { + ret = -ENOMEM; + dev_warn(&adev->dev, "ioremap fail\n"); + goto err; + } + + wdt->clk = 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; + spin_lock_init(&wdt->lock); + wdt_setload(DEFAULT_TIMEOUT); + + ret = misc_register(&sp805_wdt_miscdev); + if (ret < 0) { + dev_warn(&adev->dev, "cannot register misc device\n"); + goto err_misc_register; + } + + dev_info(&adev->dev, "registration successful\n"); + return 0; + +err_misc_register: + clk_put(wdt->clk); +err: + dev_err(&adev->dev, "Probe Failed!!!\n"); + return ret; +} + +static int __devexit sp805_wdt_remove(struct amba_device *adev) +{ + misc_deregister(&sp805_wdt_miscdev); + clk_put(wdt->clk); + + return 0; +} + +#ifdef CONFIG_PM +static int sp805_wdt_suspend(struct device *dev) +{ + if (test_bit(WDT_BUSY, &wdt->status)) { + wdt_disable(); + clk_disable(wdt->clk); + } + + return 0; +} + +static int sp805_wdt_resume(struct device *dev) +{ + int ret = 0; + + if (test_bit(WDT_BUSY, &wdt->status)) { + ret = clk_enable(wdt->clk); + if (ret) { + dev_err(dev, "clock enable fail"); + return ret; + } + wdt_enable(); + } + + return ret; +} +#endif /* CONFIG_PM */ + +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 = __devexit_p(sp805_wdt_remove), +}; + +module_amba_driver(sp805_wdt_driver); + +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Set to 1 to keep watchdog running after device release"); + +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@st.com>"); +MODULE_DESCRIPTION("ARM SP805 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/stmp3xxx_wdt.c b/drivers/watchdog/stmp3xxx_wdt.c new file mode 100644 index 00000000..21d96b92 --- /dev/null +++ b/drivers/watchdog/stmp3xxx_wdt.c @@ -0,0 +1,288 @@ +/* + * Watchdog driver for Freescale STMP37XX/STMP378X + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/module.h> + +#include <mach/platform.h> +#include <mach/regs-rtc.h> + +#define DEFAULT_HEARTBEAT 19 +#define MAX_HEARTBEAT (0x10000000 >> 6) + +/* missing bitmask in headers */ +#define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER 0x80000000 + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +#define WDOG_COUNTER_RATE 1000 /* 1 kHz clock */ + +static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock); +static unsigned long wdt_status; +static const bool nowayout = WATCHDOG_NOWAYOUT; +static int heartbeat = DEFAULT_HEARTBEAT; +static unsigned long boot_status; + +static void wdt_enable(u32 value) +{ + spin_lock(&stmp3xxx_wdt_io_lock); + __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG); + stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); + stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, + REGS_RTC_BASE + HW_RTC_PERSISTENT1); + spin_unlock(&stmp3xxx_wdt_io_lock); +} + +static void wdt_disable(void) +{ + spin_lock(&stmp3xxx_wdt_io_lock); + stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, + REGS_RTC_BASE + HW_RTC_PERSISTENT1); + stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); + spin_unlock(&stmp3xxx_wdt_io_lock); +} + +static void wdt_ping(void) +{ + wdt_enable(heartbeat * WDOG_COUNTER_RATE); +} + +static int stmp3xxx_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_ping(); + + return nonseekable_open(inode, file); +} + +static ssize_t stmp3xxx_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); + } + } + wdt_ping(); + } + + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_CARDRESET | + WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "STMP3XXX Watchdog", +}; + +static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat, opts; + int ret = -ENOTTY; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, p); + break; + + case WDIOC_SETOPTIONS: + if (get_user(opts, p)) { + ret = -EFAULT; + break; + } + if (opts & WDIOS_DISABLECARD) + wdt_disable(); + else if (opts & WDIOS_ENABLECARD) + wdt_ping(); + else { + pr_debug("%s: unknown option 0x%x\n", __func__, opts); + ret = -EINVAL; + break; + } + ret = 0; + break; + + case WDIOC_KEEPALIVE: + wdt_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) { + ret = -EFAULT; + break; + } + if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) { + ret = -EINVAL; + break; + } + + heartbeat = new_heartbeat; + wdt_ping(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, p); + break; + } + return ret; +} + +static int stmp3xxx_wdt_release(struct inode *inode, struct file *file) +{ + int ret = 0; + + if (!nowayout) { + if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { + wdt_ping(); + pr_debug("%s: Device closed unexpectedly\n", __func__); + ret = -EINVAL; + } else { + wdt_disable(); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + } + } + clear_bit(WDT_IN_USE, &wdt_status); + + return ret; +} + +static const struct file_operations stmp3xxx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = stmp3xxx_wdt_write, + .unlocked_ioctl = stmp3xxx_wdt_ioctl, + .open = stmp3xxx_wdt_open, + .release = stmp3xxx_wdt_release, +}; + +static struct miscdevice stmp3xxx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &stmp3xxx_wdt_fops, +}; + +static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) + heartbeat = DEFAULT_HEARTBEAT; + + boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) & + BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER; + boot_status = !!boot_status; + stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, + REGS_RTC_BASE + HW_RTC_PERSISTENT1); + wdt_disable(); /* disable for now */ + + ret = misc_register(&stmp3xxx_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "cannot register misc device\n"); + return ret; + } + + pr_info("initialized, heartbeat %d sec\n", heartbeat); + + return ret; +} + +static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev) +{ + misc_deregister(&stmp3xxx_wdt_miscdev); + return 0; +} + +#ifdef CONFIG_PM +static int wdt_suspended; +static u32 wdt_saved_time; + +static int stmp3xxx_wdt_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) & + BM_RTC_CTRL_WATCHDOGEN) { + wdt_suspended = 1; + wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG); + wdt_disable(); + } + return 0; +} + +static int stmp3xxx_wdt_resume(struct platform_device *pdev) +{ + if (wdt_suspended) { + wdt_enable(wdt_saved_time); + wdt_suspended = 0; + } + return 0; +} +#else +#define stmp3xxx_wdt_suspend NULL +#define stmp3xxx_wdt_resume NULL +#endif + +static struct platform_driver platform_wdt_driver = { + .driver = { + .name = "stmp3xxx_wdt", + }, + .probe = stmp3xxx_wdt_probe, + .remove = __devexit_p(stmp3xxx_wdt_remove), + .suspend = stmp3xxx_wdt_suspend, + .resume = stmp3xxx_wdt_resume, +}; + +module_platform_driver(platform_wdt_driver); + +MODULE_DESCRIPTION("STMP3XXX Watchdog Driver"); +MODULE_LICENSE("GPL"); + +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_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/ts72xx_wdt.c b/drivers/watchdog/ts72xx_wdt.c new file mode 100644 index 00000000..8df050d8 --- /dev/null +++ b/drivers/watchdog/ts72xx_wdt.c @@ -0,0 +1,514 @@ +/* + * 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; +}; + +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 -EINVAL; + } + + 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: + error = copy_to_user(argp, &winfo, sizeof(winfo)); + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + ts72xx_wdt_kick(wdt); + break; + + case WDIOC_SETOPTIONS: { + int options; + + if (get_user(options, p)) { + error = -EFAULT; + 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; + + if (get_user(new_timeout, p)) { + error = -EFAULT; + } else { + int regval; + + regval = timeout_to_regval(new_timeout); + if (regval < 0) { + error = -EINVAL; + } else { + ts72xx_wdt_stop(wdt); + wdt->regval = regval; + ts72xx_wdt_start(wdt); + } + } + if (error) + break; + + /*FALLTHROUGH*/ + } + + case WDIOC_GETTIMEOUT: + if (put_user(regval_to_timeout(wdt->regval), p)) + error = -EFAULT; + 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 __devinit int ts72xx_wdt_probe(struct platform_device *pdev) +{ + struct ts72xx_wdt *wdt; + struct resource *r1, *r2; + int error = 0; + + wdt = kzalloc(sizeof(struct ts72xx_wdt), GFP_KERNEL); + if (!wdt) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r1) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + error = -ENODEV; + goto fail; + } + + r1 = request_mem_region(r1->start, resource_size(r1), pdev->name); + if (!r1) { + dev_err(&pdev->dev, "cannot request memory region\n"); + error = -EBUSY; + goto fail; + } + + wdt->control_reg = ioremap(r1->start, resource_size(r1)); + if (!wdt->control_reg) { + dev_err(&pdev->dev, "failed to map memory\n"); + error = -ENODEV; + goto fail_free_control; + } + + r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!r2) { + dev_err(&pdev->dev, "failed to get memory resource\n"); + error = -ENODEV; + goto fail_unmap_control; + } + + r2 = request_mem_region(r2->start, resource_size(r2), pdev->name); + if (!r2) { + dev_err(&pdev->dev, "cannot request memory region\n"); + error = -EBUSY; + goto fail_unmap_control; + } + + wdt->feed_reg = ioremap(r2->start, resource_size(r2)); + if (!wdt->feed_reg) { + dev_err(&pdev->dev, "failed to map memory\n"); + error = -ENODEV; + goto fail_free_feed; + } + + 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"); + goto fail_unmap_feed; + } + + dev_info(&pdev->dev, "TS-72xx Watchdog driver\n"); + + return 0; + +fail_unmap_feed: + platform_set_drvdata(pdev, NULL); + iounmap(wdt->feed_reg); +fail_free_feed: + release_mem_region(r2->start, resource_size(r2)); +fail_unmap_control: + iounmap(wdt->control_reg); +fail_free_control: + release_mem_region(r1->start, resource_size(r1)); +fail: + kfree(wdt); + return error; +} + +static __devexit int ts72xx_wdt_remove(struct platform_device *pdev) +{ + struct ts72xx_wdt *wdt = platform_get_drvdata(pdev); + struct resource *res; + int error; + + error = misc_deregister(&ts72xx_wdt_miscdev); + platform_set_drvdata(pdev, NULL); + + iounmap(wdt->feed_reg); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + release_mem_region(res->start, resource_size(res)); + + iounmap(wdt->control_reg); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(wdt); + return error; +} + +static struct platform_driver ts72xx_wdt_driver = { + .probe = ts72xx_wdt_probe, + .remove = __devexit_p(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 00000000..249f1130 --- /dev/null +++ b/drivers/watchdog/twl4030_wdt.c @@ -0,0 +1,265 @@ +/* + * 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/fs.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/i2c/twl.h> + +#define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3 + +#define TWL4030_WDT_STATE_OPEN 0x1 +#define TWL4030_WDT_STATE_ACTIVE 0x8 + +static struct platform_device *twl4030_wdt_dev; + +struct twl4030_wdt { + struct miscdevice miscdev; + int timer_margin; + unsigned long state; +}; + +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(TWL4030_MODULE_PM_RECEIVER, val, + TWL4030_WATCHDOG_CFG_REG_OFFS); +} + +static int twl4030_wdt_enable(struct twl4030_wdt *wdt) +{ + return twl4030_wdt_write(wdt->timer_margin + 1); +} + +static int twl4030_wdt_disable(struct twl4030_wdt *wdt) +{ + return twl4030_wdt_write(0); +} + +static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout) +{ + if (timeout < 0 || timeout > 30) { + dev_warn(wdt->miscdev.parent, + "Timeout can only be in the range [0-30] seconds"); + return -EINVAL; + } + wdt->timer_margin = timeout; + return twl4030_wdt_enable(wdt); +} + +static ssize_t twl4030_wdt_write_fop(struct file *file, + const char __user *data, size_t len, loff_t *ppos) +{ + struct twl4030_wdt *wdt = file->private_data; + + if (len) + twl4030_wdt_enable(wdt); + + return len; +} + +static long twl4030_wdt_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + struct twl4030_wdt *wdt = file->private_data; + + static const struct watchdog_info twl4030_wd_ident = { + .identity = "TWL4030 Watchdog", + .options = WDIOF_SETTIMEOUT, + .firmware_version = 0, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &twl4030_wd_ident, + sizeof(twl4030_wd_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + twl4030_wdt_enable(wdt); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (twl4030_wdt_set_timeout(wdt, new_margin)) + return -EINVAL; + return put_user(wdt->timer_margin, p); + + case WDIOC_GETTIMEOUT: + return put_user(wdt->timer_margin, p); + + default: + return -ENOTTY; + } + + return 0; +} + +static int twl4030_wdt_open(struct inode *inode, struct file *file) +{ + struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev); + + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &wdt->state)) + return -EBUSY; + + wdt->state |= TWL4030_WDT_STATE_ACTIVE; + file->private_data = (void *) wdt; + + twl4030_wdt_enable(wdt); + return nonseekable_open(inode, file); +} + +static int twl4030_wdt_release(struct inode *inode, struct file *file) +{ + struct twl4030_wdt *wdt = file->private_data; + if (nowayout) { + dev_alert(wdt->miscdev.parent, + "Unexpected close, watchdog still running!\n"); + twl4030_wdt_enable(wdt); + } else { + if (twl4030_wdt_disable(wdt)) + return -EFAULT; + wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; + } + + clear_bit(0, &wdt->state); + return 0; +} + +static const struct file_operations twl4030_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = twl4030_wdt_open, + .release = twl4030_wdt_release, + .unlocked_ioctl = twl4030_wdt_ioctl, + .write = twl4030_wdt_write_fop, +}; + +static int __devinit twl4030_wdt_probe(struct platform_device *pdev) +{ + int ret = 0; + struct twl4030_wdt *wdt; + + wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->state = 0; + wdt->timer_margin = 30; + wdt->miscdev.parent = &pdev->dev; + wdt->miscdev.fops = &twl4030_wdt_fops; + wdt->miscdev.minor = WATCHDOG_MINOR; + wdt->miscdev.name = "watchdog"; + + platform_set_drvdata(pdev, wdt); + + twl4030_wdt_dev = pdev; + + twl4030_wdt_disable(wdt); + + ret = misc_register(&wdt->miscdev); + if (ret) { + dev_err(wdt->miscdev.parent, + "Failed to register misc device\n"); + platform_set_drvdata(pdev, NULL); + kfree(wdt); + twl4030_wdt_dev = NULL; + return ret; + } + return 0; +} + +static int __devexit twl4030_wdt_remove(struct platform_device *pdev) +{ + struct twl4030_wdt *wdt = platform_get_drvdata(pdev); + + if (wdt->state & TWL4030_WDT_STATE_ACTIVE) + if (twl4030_wdt_disable(wdt)) + return -EFAULT; + + wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; + misc_deregister(&wdt->miscdev); + + platform_set_drvdata(pdev, NULL); + kfree(wdt); + twl4030_wdt_dev = NULL; + + return 0; +} + +#ifdef CONFIG_PM +static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct twl4030_wdt *wdt = platform_get_drvdata(pdev); + if (wdt->state & TWL4030_WDT_STATE_ACTIVE) + return twl4030_wdt_disable(wdt); + + return 0; +} + +static int twl4030_wdt_resume(struct platform_device *pdev) +{ + struct twl4030_wdt *wdt = platform_get_drvdata(pdev); + if (wdt->state & TWL4030_WDT_STATE_ACTIVE) + return twl4030_wdt_enable(wdt); + + return 0; +} +#else +#define twl4030_wdt_suspend NULL +#define twl4030_wdt_resume NULL +#endif + +static struct platform_driver twl4030_wdt_driver = { + .probe = twl4030_wdt_probe, + .remove = __devexit_p(twl4030_wdt_remove), + .suspend = twl4030_wdt_suspend, + .resume = twl4030_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "twl4030_wdt", + }, +}; + +module_platform_driver(twl4030_wdt_driver); + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:twl4030_wdt"); + diff --git a/drivers/watchdog/txx9wdt.c b/drivers/watchdog/txx9wdt.c new file mode 100644 index 00000000..98e16373 --- /dev/null +++ b/drivers/watchdog/txx9wdt.c @@ -0,0 +1,191 @@ +/* + * txx9wdt: A Hardware Watchdog Driver for TXx9 SoCs + * + * Copyright (C) 2007 Atsushi Nemoto <anemo@mba.ocn.ne.jp> + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <asm/txx9tmr.h> + +#define WD_TIMER_CCD 7 /* 1/256 */ +#define WD_TIMER_CLK (clk_get_rate(txx9_imclk) / (2 << WD_TIMER_CCD)) +#define WD_MAX_TIMEOUT ((0xffffffff >> (32 - TXX9_TIMER_BITS)) / WD_TIMER_CLK) +#define TIMER_MARGIN 60 /* Default is 60 seconds */ + +static unsigned int timeout = TIMER_MARGIN; /* in seconds */ +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. " + "(0<timeout<((2^" __MODULE_STRING(TXX9_TIMER_BITS) ")/(IMCLK/256)), " + "default=" __MODULE_STRING(TIMER_MARGIN) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started " + "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static struct txx9_tmr_reg __iomem *txx9wdt_reg; +static struct clk *txx9_imclk; +static DEFINE_SPINLOCK(txx9_lock); + +static int txx9wdt_ping(struct watchdog_device *wdt_dev) +{ + spin_lock(&txx9_lock); + __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr); + spin_unlock(&txx9_lock); + return 0; +} + +static int txx9wdt_start(struct watchdog_device *wdt_dev) +{ + spin_lock(&txx9_lock); + __raw_writel(WD_TIMER_CLK * wdt_dev->timeout, &txx9wdt_reg->cpra); + __raw_writel(WD_TIMER_CCD, &txx9wdt_reg->ccdr); + __raw_writel(0, &txx9wdt_reg->tisr); /* clear pending interrupt */ + __raw_writel(TXx9_TMTCR_TCE | TXx9_TMTCR_CCDE | TXx9_TMTCR_TMODE_WDOG, + &txx9wdt_reg->tcr); + __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr); + spin_unlock(&txx9_lock); + return 0; +} + +static int txx9wdt_stop(struct watchdog_device *wdt_dev) +{ + spin_lock(&txx9_lock); + __raw_writel(TXx9_TMWTMR_WDIS, &txx9wdt_reg->wtmr); + __raw_writel(__raw_readl(&txx9wdt_reg->tcr) & ~TXx9_TMTCR_TCE, + &txx9wdt_reg->tcr); + spin_unlock(&txx9_lock); + return 0; +} + +static int txx9wdt_set_timeout(struct watchdog_device *wdt_dev, + unsigned int new_timeout) +{ + wdt_dev->timeout = new_timeout; + txx9wdt_stop(wdt_dev); + txx9wdt_start(wdt_dev); + return 0; +} + +static const struct watchdog_info txx9wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Hardware Watchdog for TXx9", +}; + +static const struct watchdog_ops txx9wdt_ops = { + .owner = THIS_MODULE, + .start = txx9wdt_start, + .stop = txx9wdt_stop, + .ping = txx9wdt_ping, + .set_timeout = txx9wdt_set_timeout, +}; + +static struct watchdog_device txx9wdt = { + .info = &txx9wdt_info, + .ops = &txx9wdt_ops, +}; + +static int __init txx9wdt_probe(struct platform_device *dev) +{ + struct resource *res; + int ret; + + txx9_imclk = clk_get(NULL, "imbus_clk"); + if (IS_ERR(txx9_imclk)) { + ret = PTR_ERR(txx9_imclk); + txx9_imclk = NULL; + goto exit; + } + ret = clk_enable(txx9_imclk); + if (ret) { + clk_put(txx9_imclk); + txx9_imclk = NULL; + goto exit; + } + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + txx9wdt_reg = devm_request_and_ioremap(&dev->dev, res); + if (!txx9wdt_reg) { + ret = -EBUSY; + goto exit; + } + + if (timeout < 1 || timeout > WD_MAX_TIMEOUT) + timeout = TIMER_MARGIN; + txx9wdt.timeout = timeout; + txx9wdt.min_timeout = 1; + txx9wdt.max_timeout = WD_MAX_TIMEOUT; + watchdog_set_nowayout(&txx9wdt, nowayout); + + ret = watchdog_register_device(&txx9wdt); + if (ret) + goto exit; + + pr_info("Hardware Watchdog Timer: timeout=%d sec (max %ld) (nowayout= %d)\n", + timeout, WD_MAX_TIMEOUT, nowayout); + + return 0; +exit: + if (txx9_imclk) { + clk_disable(txx9_imclk); + clk_put(txx9_imclk); + } + return ret; +} + +static int __exit txx9wdt_remove(struct platform_device *dev) +{ + 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_DESCRIPTION("TXx9 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:txx9wdt"); diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c new file mode 100644 index 00000000..465e0827 --- /dev/null +++ b/drivers/watchdog/via_wdt.c @@ -0,0 +1,270 @@ +/* + * 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) || + (!test_bit(WDOG_ACTIVE, &wdt_dev.status))) { + 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 __devinit 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; + } + + 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 __devexit wdt_remove(struct pci_dev *pdev) +{ + watchdog_unregister_device(&wdt_dev); + del_timer(&timer); + iounmap(wdt_mem); + release_mem_region(mmio, VIA_WDT_MMIO_LEN); + release_resource(&wdt_res); + pci_disable_device(pdev); +} + +static DEFINE_PCI_DEVICE_TABLE(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 = __devexit_p(wdt_remove), +}; + +static int __init wdt_init(void) +{ + if (timeout < 1 || timeout > WDT_TIMEOUT_MAX) + timeout = WDT_TIMEOUT; + return pci_register_driver(&wdt_driver); +} + +static void __exit wdt_exit(void) +{ + pci_unregister_driver(&wdt_driver); +} + +module_init(wdt_init); +module_exit(wdt_exit); + +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 new file mode 100644 index 00000000..92f1326f --- /dev/null +++ b/drivers/watchdog/w83627hf_wdt.c @@ -0,0 +1,399 @@ +/* + * w83627hf/thf WDT driver + * + * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> + * added support for W83627THF. + * + * (c) Copyright 2003,2007 Pádraig Brady <P@draigBrady.com> + * + * Based on advantechwdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + */ + +#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 <linux/io.h> +#include <linux/uaccess.h> + + +#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); + +/* 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)"); + +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 bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Kernel methods. + */ + +#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ +#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register + (same as EFER) */ +#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ + +static void w83627hf_select_wd_register(void) +{ + unsigned char c; + 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 */ + } else if (c == 0x88 || c == 0xa0) { /* W83627EHF / W83627DHG */ + outb_p(0x2d, WDT_EFER); /* select GPIO5 */ + c = inb_p(WDT_EFDR) & ~0x01; /* PIN77 -> WDT0# */ + outb_p(0x2d, WDT_EFER); + outb_p(c, WDT_EFDR); /* set GPIO5 to WDT0 */ + } + + 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 w83627hf_unselect_wd_register(void) +{ + outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ +} + +/* tyan motherboards seem to set F5 to 0x4C ? + * So explicitly init to appropriate value. */ + +static void w83627hf_init(void) +{ + unsigned char t; + + w83627hf_select_wd_register(); + + outb_p(0xF6, WDT_EFER); /* Select CRF6 */ + t = inb_p(WDT_EFDR); /* read CRF6 */ + if (t != 0) { + pr_info("Watchdog already running. Resetting timeout to %d sec\n", + timeout); + outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ + } + + outb_p(0xF5, WDT_EFER); /* Select CRF5 */ + t = inb_p(WDT_EFDR); /* read CRF5 */ + t &= ~0x0C; /* set second mode & disable keyboard + turning off watchdog */ + t |= 0x02; /* enable the WDTO# output low pulse + to the KBRST# pin (PIN60) */ + outb_p(t, WDT_EFDR); /* Write back to CRF5 */ + + 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 */ + + w83627hf_unselect_wd_register(); +} + +static void wdt_set_time(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 */ + + w83627hf_unselect_wd_register(); + + spin_unlock(&io_lock); +} + +static int wdt_ping(void) +{ + wdt_set_time(timeout); + return 0; +} + +static int wdt_disable(void) +{ + wdt_set_time(0); + return 0; +} + +static int wdt_set_heartbeat(int t) +{ + if (t < 1 || t > 255) + return -EINVAL; + timeout = t; + return 0; +} + +static int wdt_get_time(void) +{ + int timeleft; + + spin_lock(&io_lock); + + w83627hf_select_wd_register(); + + outb_p(0xF6, WDT_EFER); /* Select CRF6 */ + timeleft = inb_p(WDT_EFDR); /* Read Timeout counter to CRF6 */ + + w83627hf_unselect_wd_register(); + + spin_unlock(&io_lock); + + return timeleft; +} + +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 long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int timeval; + static const 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_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; + } + case WDIOC_KEEPALIVE: + wdt_ping(); + break; + case WDIOC_SETTIMEOUT: + if (get_user(timeval, p)) + return -EFAULT; + if (wdt_set_heartbeat(timeval)) + return -EINVAL; + wdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + case WDIOC_GETTIMELEFT: + timeval = wdt_get_time(); + return put_user(timeval, p); + 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_ping(); + return nonseekable_open(inode, file); +} + +static int wdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + wdt_disable(); + else { + pr_crit("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) + wdt_disable(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .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 __init wdt_init(void) +{ + int ret; + + pr_info("WDT driver for the Winbond(TM) W83627HF/THF/HG/DHG Super I/O chip initialising\n"); + + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 1 <= timeout <= 255, using %d\n", + WATCHDOG_TIMEOUT); + } + + if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", wdt_io); + ret = -EIO; + goto out; + } + + w83627hf_init(); + + ret = register_reboot_notifier(&wdt_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto unreg_regions; + } + + ret = misc_register(&wdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&wdt_notifier); +unreg_regions: + release_region(wdt_io, 1); + goto out; +} + +static void __exit wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); + 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_DESCRIPTION("w83627hf/thf WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83697hf_wdt.c b/drivers/watchdog/w83697hf_wdt.c new file mode 100644 index 00000000..cd9f3c1e --- /dev/null +++ b/drivers/watchdog/w83697hf_wdt.c @@ -0,0 +1,461 @@ +/* + * 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@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 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. + */ + +#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 <linux/io.h> +#include <linux/uaccess.h> + + +#define WATCHDOG_NAME "w83697hf/hg WDT" +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ +#define WATCHDOG_EARLY_DISABLE 1 /* Disable until userland kicks in */ + +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 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 = WATCHDOG_EARLY_DISABLE; +module_param(early_disable, int, 0); +MODULE_PARM_DESC(early_disable, + "Watchdog gets disabled at boot time (default=" + __MODULE_STRING(WATCHDOG_EARLY_DISABLE) ")"); + +/* + * 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) +{ + /* Write Timeout counter to CRF4 */ + w83697hf_set_reg(0xF4, timeout); +} + +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; + + /* Set pin 119 to WDTO# mode (= CR29, WDT0) */ + w83697hf_set_reg(0x29, bbuf); + + bbuf = w83697hf_get_reg(0xF3); + bbuf &= ~0x04; + w83697hf_set_reg(0xF3, bbuf); /* Count mode is seconds */ + + w83697hf_deselect_wdt(); +} + +static void wdt_ping(void) +{ + spin_lock(&io_lock); + w83697hf_select_wdt(); + + w83697hf_write_timeout(timeout); + + w83697hf_deselect_wdt(); + spin_unlock(&io_lock); +} + +static void 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); +} + +static void 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); +} + +static unsigned char wdt_running(void) +{ + unsigned char t; + + spin_lock(&io_lock); + w83697hf_select_wdt(); + + t = w83697hf_get_reg(0xF4); /* Read timer */ + + w83697hf_deselect_wdt(); + spin_unlock(&io_lock); + + return t; +} + +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 long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_timeout; + static const 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_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; + } + + 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); + + 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 { + pr_crit("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) + wdt_disable(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .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)) { + pr_err("I/O address 0x%x already in use\n", wdt_io); + return -EIO; + } + + pr_debug("Looking for watchdog at address 0x%x\n", wdt_io); + w83697hf_unlock(); + if (w83697hf_get_reg(0x20) == 0x60) { + pr_info("watchdog found at address 0x%x\n", wdt_io); + w83697hf_lock(); + return 0; + } + /* Reprotect in case it was a compatible device */ + w83697hf_lock(); + + pr_info("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; + + pr_info("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) { + pr_err("No W83697HF/HG could be found\n"); + ret = -EIO; + goto out; + } + + w83697hf_init(); + if (early_disable) { + if (wdt_running()) + pr_warn("Stopping previously enabled watchdog until userland kicks in\n"); + wdt_disable(); + } + + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 1 <= timeout <= 255, using %d\n", + WATCHDOG_TIMEOUT); + } + + ret = register_reboot_notifier(&wdt_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto unreg_regions; + } + + ret = misc_register(&wdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&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/w83697ug_wdt.c b/drivers/watchdog/w83697ug_wdt.c new file mode 100644 index 00000000..274be0bf --- /dev/null +++ b/drivers/watchdog/w83697ug_wdt.c @@ -0,0 +1,398 @@ +/* + * w83697ug/uf WDT driver + * + * (c) Copyright 2008 Flemming Fransen <ff@nrvissing.net> + * reused original code to support w83697ug/uf. + * + * Based on w83627hf_wdt.c which is based on advantechwdt.c + * which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 2007 Vlad Drukker <vlad@storewiz.com> + * added support for W83627THF. + * + * (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 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@redhat.com> + */ + +#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 <linux/io.h> +#include <linux/uaccess.h> + + +#define WATCHDOG_NAME "w83697ug/uf 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 = 0x2e; +module_param(wdt_io, int, 0); +MODULE_PARM_DESC(wdt_io, "w83697ug/uf WDT io port (default 0x2e)"); + +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 bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Kernel methods. + */ + +#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ +#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register + (same as EFER) */ +#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ + +static int w83697ug_select_wd_register(void) +{ + unsigned char c; + unsigned char version; + + 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 */ + version = inb(WDT_EFDR); + + if (version == 0x68) { /* W83697UG */ + pr_info("Watchdog chip version 0x%02x = W83697UG/UF found at 0x%04x\n", + version, wdt_io); + + outb_p(0x2b, WDT_EFER); + c = inb_p(WDT_EFDR); /* select WDT0 */ + c &= ~0x04; + outb_p(0x2b, WDT_EFER); + outb_p(c, WDT_EFDR); /* set pin118 to WDT0 */ + + } else { + pr_err("No W83697UG/UF could be found\n"); + return -ENODEV; + } + + 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 */ + c = inb_p(WDT_EFDR); + outb_p(c | 0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ + + return 0; +} + +static void w83697ug_unselect_wd_register(void) +{ + outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ +} + +static int w83697ug_init(void) +{ + int ret; + unsigned char t; + + ret = w83697ug_select_wd_register(); + if (ret != 0) + return ret; + + outb_p(0xF6, WDT_EFER); /* Select CRF6 */ + t = inb_p(WDT_EFDR); /* read CRF6 */ + if (t != 0) { + pr_info("Watchdog already running. Resetting timeout to %d sec\n", + timeout); + outb_p(timeout, WDT_EFDR); /* Write back to CRF6 */ + } + 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 */ + + w83697ug_unselect_wd_register(); + return 0; +} + +static void wdt_ctrl(int timeout) +{ + spin_lock(&io_lock); + + if (w83697ug_select_wd_register() < 0) { + spin_unlock(&io_lock); + return; + } + + outb_p(0xF4, WDT_EFER); /* Select CRF4 */ + outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF4 */ + + w83697ug_unselect_wd_register(); + + spin_unlock(&io_lock); +} + +static int wdt_ping(void) +{ + wdt_ctrl(timeout); + return 0; +} + +static int wdt_disable(void) +{ + wdt_ctrl(0); + 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 long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_timeout; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "W83697UG WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wdt_disable(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wdt_ping(); + retval = 0; + } + + return retval; + } + + 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); + + 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_ping(); + return nonseekable_open(inode, file); +} + +static int wdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + wdt_disable(); + else { + pr_crit("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) + wdt_disable(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .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 __init wdt_init(void) +{ + int ret; + + pr_info("WDT driver for the Winbond(TM) W83697UG/UF Super I/O chip initialising\n"); + + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + pr_info("timeout value must be 1<=timeout<=255, using %d\n", + WATCHDOG_TIMEOUT); + } + + if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", wdt_io); + ret = -EIO; + goto out; + } + + ret = w83697ug_init(); + if (ret != 0) + goto unreg_regions; + + ret = register_reboot_notifier(&wdt_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto unreg_regions; + } + + ret = misc_register(&wdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&wdt_notifier); +unreg_regions: + release_region(wdt_io, 1); + goto out; +} + +static void __exit wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(wdt_io, 1); +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Flemming Frandsen <ff@nrvissing.net>"); +MODULE_DESCRIPTION("w83697ug/uf WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83877f_wdt.c b/drivers/watchdog/w83877f_wdt.c new file mode 100644 index 00000000..7874ae06 --- /dev/null +++ b/drivers/watchdog/w83877f_wdt.c @@ -0,0 +1,409 @@ +/* + * W83877F Computer Watchdog Timer driver + * + * 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 + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * The authors do NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> + * + * 4/19 - 2001 [Initial revision] + * 9/27 - 2001 Added spinlocking + * 4/12 - 2002 [rob@osinvestor.com] Eliminate extra comments + * Eliminate fop_read + * Eliminate extra spin_unlock + * Added KERN_* tags to printks + * add CONFIG_WATCHDOG_NOWAYOUT support + * fix possible wdt_is_open race + * changed watchdog_info to correctly reflect what + * the driver offers + * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, + * WDIOC_SETTIMEOUT, + * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls + * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces + * added extra printk's for startup problems + * use module_param + * made timeout (the emulated heartbeat) a + * module_param + * made the keepalive ping an internal subroutine + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#define OUR_NAME "w83877f_wdt" + +#define ENABLE_W83877F_PORT 0x3F0 +#define ENABLE_W83877F 0x87 +#define DISABLE_W83877F 0xAA +#define WDT_PING 0x443 +#define WDT_REGISTER 0x14 +#define WDT_ENABLE 0x9C +#define WDT_DISABLE 0x8C + +/* + * The W83877F seems to be fixed at 1.6s timeout (at least on the + * EMACS PC-104 board I'm using). If we reset the watchdog every + * ~250ms we should be safe. */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void wdt_timer_ping(unsigned long); +static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static DEFINE_SPINLOCK(wdt_spinlock); + +/* + * Whack the dog + */ + +static void wdt_timer_ping(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)) { + /* Ping the WDT */ + spin_lock(&wdt_spinlock); + + /* Ping the WDT by reading from WDT_PING */ + inb_p(WDT_PING); + + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + spin_unlock(&wdt_spinlock); + + } else + pr_warn("Heartbeat lost! Will not ping the watchdog\n"); +} + +/* + * Utility routines + */ + +static void wdt_change(int writeval) +{ + unsigned long flags; + spin_lock_irqsave(&wdt_spinlock, flags); + + /* buy some time */ + inb_p(WDT_PING); + + /* make W83877F available */ + outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); + outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); + + /* enable watchdog */ + outb_p(WDT_REGISTER, ENABLE_W83877F_PORT); + outb_p(writeval, ENABLE_W83877F_PORT+1); + + /* lock the W8387FF away */ + outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT); + + spin_unlock_irqrestore(&wdt_spinlock, flags); +} + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + wdt_change(WDT_ENABLE); + + pr_info("Watchdog timer is now enabled\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + + wdt_change(WDT_DISABLE); + + pr_info("Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic + character five months ago... */ + wdt_expect_close = 0; + + /* scan to see whether or not we got the + magic character */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode *inode, struct file *file) +{ + /* Just in case we're already talking to someone... */ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode *inode, struct file *file) +{ + if (wdt_expect_close == 42) + wdt_turnoff(); + else { + del_timer(&timer); + pr_crit("device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT + | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "W83877F", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user(new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; + + /* arbitrary upper limit */ + if (new_timeout < 1 || new_timeout > 3600) + return -EINVAL; + + timeout = new_timeout; + wdt_keepalive(); + /* 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, + .unlocked_ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit w83877f_wdt_unload(void) +{ + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + + unregister_reboot_notifier(&wdt_notifier); + release_region(WDT_PING, 1); + release_region(ENABLE_W83877F_PORT, 2); +} + +static int __init w83877f_wdt_init(void) +{ + int rc = -EBUSY; + + if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */ + timeout = WATCHDOG_TIMEOUT; + pr_info("timeout value must be 1 <= x <= 3600, using %d\n", + timeout); + } + + if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) { + pr_err("I/O address 0x%04x already in use\n", + ENABLE_W83877F_PORT); + rc = -EIO; + goto err_out; + } + + if (!request_region(WDT_PING, 1, "W8387FF WDT")) { + pr_err("I/O address 0x%04x already in use\n", WDT_PING); + rc = -EIO; + goto err_out_region1; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region2; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_reboot; + } + + pr_info("WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region2: + release_region(WDT_PING, 1); +err_out_region1: + release_region(ENABLE_W83877F_PORT, 2); +err_out: + return rc; +} + +module_init(w83877f_wdt_init); +module_exit(w83877f_wdt_unload); + +MODULE_AUTHOR("Scott and Bill Jennings"); +MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/w83977f_wdt.c b/drivers/watchdog/w83977f_wdt.c new file mode 100644 index 00000000..5d2c9028 --- /dev/null +++ b/drivers/watchdog/w83977f_wdt.c @@ -0,0 +1,530 @@ +/* + * W83977F Watchdog Timer Driver for Winbond W83977F I/O Chip + * + * (c) Copyright 2005 Jose Goncalves <jose.goncalves@inov.pt> + * + * Based on w83877f_wdt.c by Scott Jennings, + * and wdt977.c by Woody Suwalski + * + * ----------------------- + * + * 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/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/uaccess.h> +#include <linux/io.h> + + +#define WATCHDOG_VERSION "1.00" +#define WATCHDOG_NAME "W83977F WDT" + +#define IO_INDEX_PORT 0x3F0 +#define IO_DATA_PORT (IO_INDEX_PORT+1) + +#define UNLOCK_DATA 0x87 +#define LOCK_DATA 0xAA +#define DEVICE_REGISTER 0x07 + +#define DEFAULT_TIMEOUT 45 /* default timeout in seconds */ + +static int timeout = DEFAULT_TIMEOUT; +static int timeoutW; /* timeout in watchdog counter units */ +static unsigned long timer_alive; +static int testmode; +static char expect_close; +static DEFINE_SPINLOCK(spinlock); + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds (15..7635), default=" + __MODULE_STRING(DEFAULT_TIMEOUT) ")"); +module_param(testmode, int, 0); +MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Start the watchdog + */ + +static int wdt_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* + * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. + * F2 has the timeout in watchdog counter units. + * F3 is set to enable watchdog LED blink at timeout. + * F4 is used to just clear the TIMEOUT'ed state (bit 0). + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(timeoutW, IO_DATA_PORT); + outb_p(0xF3, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF4, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + + /* Set device Aux2 active */ + outb_p(0x30, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); + + /* + * Select device Aux1 (dev=7) to set GP16 as the watchdog output + * (in reg E6) and GP13 as the watchdog LED output (in reg E3). + * Map GP16 at pin 119. + * In test mode watch the bit 0 on F4 to indicate "triggered" or + * check watchdog LED on SBC. + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x07, IO_DATA_PORT); + if (!testmode) { + unsigned pin_map; + + outb_p(0xE6, IO_INDEX_PORT); + outb_p(0x0A, IO_DATA_PORT); + outb_p(0x2C, IO_INDEX_PORT); + pin_map = inb_p(IO_DATA_PORT); + pin_map |= 0x10; + pin_map &= ~(0x20); + outb_p(0x2C, IO_INDEX_PORT); + outb_p(pin_map, IO_DATA_PORT); + } + outb_p(0xE3, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + + /* Set device Aux1 active */ + outb_p(0x30, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + pr_info("activated\n"); + + return 0; +} + +/* + * Stop the watchdog + */ + +static int wdt_stop(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* + * Select device Aux2 (device=8) to set watchdog regs F2, F3 and F4. + * F2 is reset to its default value (watchdog timer disabled). + * F3 is reset to its default state. + * F4 clears the TIMEOUT'ed state (bit 0) - back to default. + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(0xFF, IO_DATA_PORT); + outb_p(0xF3, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + outb_p(0xF4, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + + /* + * Select device Aux1 (dev=7) to set GP16 (in reg E6) and + * Gp13 (in reg E3) as inputs. + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x07, IO_DATA_PORT); + if (!testmode) { + outb_p(0xE6, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); + } + outb_p(0xE3, IO_INDEX_PORT); + outb_p(0x01, IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + pr_info("shutdown\n"); + + return 0; +} + +/* + * Send a keepalive ping to the watchdog + * This is done by simply re-writing the timeout to reg. 0xF2 + */ + +static int wdt_keepalive(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* Select device Aux2 (device=8) to kick watchdog reg F2 */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(timeoutW, IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + return 0; +} + +/* + * Set the watchdog timeout value + */ + +static int wdt_set_timeout(int t) +{ + int tmrval; + + /* + * Convert seconds to watchdog counter time units, rounding up. + * On PCM-5335 watchdog units are 30 seconds/step with 15 sec startup + * value. This information is supplied in the PCM-5335 manual and was + * checked by me on a real board. This is a bit strange because W83977f + * datasheet says counter unit is in minutes! + */ + if (t < 15) + return -EINVAL; + + tmrval = ((t + 15) + 29) / 30; + + if (tmrval > 255) + return -EINVAL; + + /* + * timeout is the timeout in seconds, + * timeoutW is the timeout in watchdog counter units. + */ + timeoutW = tmrval; + timeout = (timeoutW * 30) - 15; + return 0; +} + +/* + * Get the watchdog status + */ + +static int wdt_get_status(int *status) +{ + int new_status; + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* Unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* Select device Aux2 (device=8) to read watchdog reg F4 */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF4, IO_INDEX_PORT); + new_status = inb_p(IO_DATA_PORT); + + /* Lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + *status = 0; + if (new_status & 1) + *status |= WDIOF_CARDRESET; + + return 0; +} + + +/* + * /dev/watchdog handling + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + /* If the watchdog is alive we don't need to start it again */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + wdt_start(); + return nonseekable_open(inode, file); +} + +static int wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (expect_close == 42) { + wdt_stop(); + clear_bit(0, &timer_alive); + } else { + wdt_keepalive(); + pr_crit("unexpected close, not stopping watchdog!\n"); + } + expect_close = 0; + return 0; +} + +/* + * wdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the + magic character long ago */ + expect_close = 0; + + /* scan to see whether or not we got the + magic character */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + return count; +} + +/* + * wdt_ioctl: + * @inode: inode of the device + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. + */ + +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int status; + int new_options, retval = -EINVAL; + int new_timeout; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + uarg.i = (int __user *)arg; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + wdt_get_status(&status); + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_SETOPTIONS: + if (get_user(new_options, uarg.i)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + wdt_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (wdt_set_timeout(new_timeout)) + return -EINVAL; + + wdt_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, uarg.i); + + default: + return -ENOTTY; + + } +} + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_release, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static int __init w83977f_wdt_init(void) +{ + int rc; + + pr_info("driver v%s\n", WATCHDOG_VERSION); + + /* + * Check that the timeout value is within it's range; + * if not reset to the default + */ + if (wdt_set_timeout(timeout)) { + wdt_set_timeout(DEFAULT_TIMEOUT); + pr_info("timeout value must be 15 <= timeout <= 7635, using %d\n", + DEFAULT_TIMEOUT); + } + + if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", IO_INDEX_PORT); + rc = -EIO; + goto err_out; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d testmode=%d)\n", + timeout, nowayout, testmode); + + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt_notifier); +err_out_region: + release_region(IO_INDEX_PORT, 2); +err_out: + return rc; +} + +static void __exit w83977f_wdt_exit(void) +{ + wdt_stop(); + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(IO_INDEX_PORT, 2); +} + +module_init(w83977f_wdt_init); +module_exit(w83977f_wdt_exit); + +MODULE_AUTHOR("Jose Goncalves <jose.goncalves@inov.pt>"); +MODULE_DESCRIPTION("Driver for watchdog timer in W83977F I/O chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/wafer5823wdt.c b/drivers/watchdog/wafer5823wdt.c new file mode 100644 index 00000000..25aba6e0 --- /dev/null +++ b/drivers/watchdog/wafer5823wdt.c @@ -0,0 +1,327 @@ +/* + * ICP Wafer 5823 Single Board Computer WDT driver + * http://www.icpamerica.com/wafer_5823.php + * May also work on other similar models + * + * (c) Copyright 2002 Justin Cormack <justin@street-vision.com> + * + * Release 0.02 + * + * Based on advantechwdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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> + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#define WATCHDOG_NAME "Wafer 5823 WDT" +#define PFX WATCHDOG_NAME ": " +#define WD_TIMO 60 /* 60 sec default timeout */ + +static unsigned long wafwdt_is_open; +static char expect_close; +static DEFINE_SPINLOCK(wafwdt_lock); + +/* + * You must set these - there is no sane way to probe for this board. + * + * To enable, write the timeout value in seconds (1 to 255) to I/O + * port WDT_START, then read the port to start the watchdog. To pat + * the dog, read port WDT_STOP to stop the timer, then read WDT_START + * to restart it again. + */ + +static int wdt_stop = 0x843; +static int wdt_start = 0x443; + +static int timeout = WD_TIMO; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" + __MODULE_STRING(WD_TIMO) "."); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void wafwdt_ping(void) +{ + /* pat watchdog */ + spin_lock(&wafwdt_lock); + inb_p(wdt_stop); + inb_p(wdt_start); + spin_unlock(&wafwdt_lock); +} + +static void wafwdt_start(void) +{ + /* start up watchdog */ + outb_p(timeout, wdt_start); + inb_p(wdt_start); +} + +static void wafwdt_stop(void) +{ + /* stop watchdog */ + inb_p(wdt_stop); +} + +static ssize_t wafwdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + /* scan to see whether or not we got the magic + character */ + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + /* Well, anyhow someone wrote to us, we should + return that favour */ + wafwdt_ping(); + } + return count; +} + +static long wafwdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static const struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "Wafer 5823 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wafwdt_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wafwdt_start(); + retval = 0; + } + + return retval; + } + + case WDIOC_KEEPALIVE: + wafwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if ((new_timeout < 1) || (new_timeout > 255)) + return -EINVAL; + timeout = new_timeout; + wafwdt_stop(); + wafwdt_start(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + return 0; +} + +static int wafwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wafwdt_is_open)) + return -EBUSY; + + /* + * Activate + */ + wafwdt_start(); + return nonseekable_open(inode, file); +} + +static int wafwdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) + wafwdt_stop(); + else { + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); + wafwdt_ping(); + } + clear_bit(0, &wafwdt_is_open); + expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wafwdt_stop(); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static const struct file_operations wafwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wafwdt_write, + .unlocked_ioctl = wafwdt_ioctl, + .open = wafwdt_open, + .release = wafwdt_close, +}; + +static struct miscdevice wafwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wafwdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wafwdt_notifier = { + .notifier_call = wafwdt_notify_sys, +}; + +static int __init wafwdt_init(void) +{ + int ret; + + pr_info("WDT driver for Wafer 5823 single board computer initialising\n"); + + if (timeout < 1 || timeout > 255) { + timeout = WD_TIMO; + pr_info("timeout value must be 1 <= x <= 255, using %d\n", + timeout); + } + + if (wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, "Wafer 5823 WDT")) { + pr_err("I/O address 0x%04x already in use\n", wdt_stop); + ret = -EIO; + goto error; + } + } + + if (!request_region(wdt_start, 1, "Wafer 5823 WDT")) { + pr_err("I/O address 0x%04x already in use\n", wdt_start); + ret = -EIO; + goto error2; + } + + ret = register_reboot_notifier(&wafwdt_notifier); + if (ret != 0) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto error3; + } + + ret = misc_register(&wafwdt_miscdev); + if (ret != 0) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto error4; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return ret; +error4: + unregister_reboot_notifier(&wafwdt_notifier); +error3: + release_region(wdt_start, 1); +error2: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); +error: + return ret; +} + +static void __exit wafwdt_exit(void) +{ + misc_deregister(&wafwdt_miscdev); + unregister_reboot_notifier(&wafwdt_notifier); + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); + release_region(wdt_start, 1); +} + +module_init(wafwdt_init); +module_exit(wafwdt_exit); + +MODULE_AUTHOR("Justin Cormack"); +MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); +MODULE_LICENSE("GPL"); +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 00000000..14d768bf --- /dev/null +++ b/drivers/watchdog/watchdog_core.c @@ -0,0 +1,111 @@ +/* + * 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 "watchdog_dev.h" /* For watchdog_dev_register/... */ + +/** + * 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; + + 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; + + /* + * 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; + } + + /* + * 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! + */ + + /* We only support 1 watchdog device via the /dev/watchdog interface */ + ret = watchdog_dev_register(wdd); + if (ret) { + pr_err("error registering /dev/watchdog (err=%d)\n", ret); + 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; + + if (wdd == NULL) + return; + + ret = watchdog_dev_unregister(wdd); + if (ret) + pr_err("error unregistering /dev/watchdog (err=%d)\n", ret); +} +EXPORT_SYMBOL_GPL(watchdog_unregister_device); + +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_dev.c b/drivers/watchdog/watchdog_dev.c new file mode 100644 index 00000000..8558da91 --- /dev/null +++ b/drivers/watchdog/watchdog_dev.c @@ -0,0 +1,399 @@ +/* + * 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/... */ + +/* make sure we only register one /dev/watchdog device */ +static unsigned long watchdog_dev_busy; +/* the watchdog device behind /dev/watchdog */ +static struct watchdog_device *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) +{ + if (test_bit(WDOG_ACTIVE, &wddev->status)) { + if (wddev->ops->ping) + return wddev->ops->ping(wddev); /* ping the watchdog */ + else + return wddev->ops->start(wddev); /* restart watchdog */ + } + return 0; +} + +/* + * 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; + + if (!test_bit(WDOG_ACTIVE, &wddev->status)) { + err = wddev->ops->start(wddev); + if (err < 0) + return err; + + set_bit(WDOG_ACTIVE, &wddev->status); + } + return 0; +} + +/* + * 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 = -EBUSY; + + if (test_bit(WDOG_NO_WAY_OUT, &wddev->status)) { + pr_info("%s: nowayout prevents watchdog to be stopped!\n", + wddev->info->identity); + return err; + } + + if (test_bit(WDOG_ACTIVE, &wddev->status)) { + err = wddev->ops->stop(wddev); + if (err < 0) + return err; + + clear_bit(WDOG_ACTIVE, &wddev->status); + } + return 0; +} + +/* + * 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) +{ + 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) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + unsigned int val; + int err; + + if (wdd->ops->ioctl) { + err = wdd->ops->ioctl(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: + val = wdd->ops->status ? wdd->ops->status(wdd) : 0; + 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 ((wdd->ops->set_timeout == NULL) || + !(wdd->info->options & WDIOF_SETTIMEOUT)) + return -EOPNOTSUPP; + if (get_user(val, p)) + return -EFAULT; + if ((wdd->max_timeout != 0) && + (val < wdd->min_timeout || val > wdd->max_timeout)) + return -EINVAL; + err = wdd->ops->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: + if (!wdd->ops->get_timeleft) + return -EOPNOTSUPP; + + return put_user(wdd->ops->get_timeleft(wdd), p); + default: + return -ENOTTY; + } +} + +/* + * watchdog_open: open the /dev/watchdog device. + * @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; + + /* 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; + + /* 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 /dev/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) +{ + 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_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) { + pr_crit("%s: watchdog did not stop!\n", wdd->info->identity); + 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); + + 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: + * @watchdog: watchdog device + * + * Register a watchdog device as /dev/watchdog. /dev/watchdog + * is actually a miscdevice and thus we set it up like that. + */ + +int watchdog_dev_register(struct watchdog_device *watchdog) +{ + int err; + + /* Only one device can register for /dev/watchdog */ + if (test_and_set_bit(0, &watchdog_dev_busy)) { + pr_err("only one watchdog can use /dev/watchdog\n"); + return -EBUSY; + } + + wdd = watchdog; + + 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); + goto out; + } + + return 0; + +out: + wdd = NULL; + clear_bit(0, &watchdog_dev_busy); + return err; +} + +/* + * watchdog_dev_unregister: + * @watchdog: watchdog device + * + * Deregister the /dev/watchdog device. + */ + +int watchdog_dev_unregister(struct watchdog_device *watchdog) +{ + /* Check that a watchdog device was registered in the past */ + if (!test_bit(0, &watchdog_dev_busy) || !wdd) + return -ENODEV; + + /* We can only unregister the watchdog device that was registered */ + if (watchdog != wdd) { + pr_err("%s: watchdog was not registered as /dev/watchdog\n", + watchdog->info->identity); + return -ENODEV; + } + + misc_deregister(&watchdog_miscdev); + wdd = NULL; + clear_bit(0, &watchdog_dev_busy); + return 0; +} diff --git a/drivers/watchdog/watchdog_dev.h b/drivers/watchdog/watchdog_dev.h new file mode 100644 index 00000000..bc7612be --- /dev/null +++ b/drivers/watchdog/watchdog_dev.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +/* + * Functions/procedures to be called by the core + */ +int watchdog_dev_register(struct watchdog_device *); +int watchdog_dev_unregister(struct watchdog_device *); diff --git a/drivers/watchdog/wd501p.h b/drivers/watchdog/wd501p.h new file mode 100644 index 00000000..0e3a497d --- /dev/null +++ b/drivers/watchdog/wd501p.h @@ -0,0 +1,51 @@ +/* + * Industrial Computer Source WDT500/501 driver + * + * (c) Copyright 1995 CymruNET Ltd + * Innovation Centre + * Singleton Park + * Swansea + * Wales + * UK + * SA2 8PP + * + * 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. + * + * Release 0.04. + * + */ + + +#define WDT_COUNT0 (io+0) +#define WDT_COUNT1 (io+1) +#define WDT_COUNT2 (io+2) +#define WDT_CR (io+3) +#define WDT_SR (io+4) /* Start buzzer on PCI write */ +#define WDT_RT (io+5) /* Stop buzzer on PCI write */ +#define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */ +#define WDT_DC (io+7) + +/* The following are only on the PCI card, they're outside of I/O space on + * the ISA card: */ +#define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */ +/* inverted opto isolated reset output: */ +#define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */ +/* opto isolated reset output: */ +#define WDT_OPTORST (io+14) /* wr=enable, rd=disable */ +/* programmable outputs: */ +#define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */ + + /* FAN 501 500 */ +#define WDC_SR_WCCR 1 /* Active low */ /* X X X */ +#define WDC_SR_TGOOD 2 /* X X - */ +#define WDC_SR_ISOI0 4 /* X X X */ +#define WDC_SR_ISII1 8 /* X X X */ +#define WDC_SR_FANGOOD 16 /* X - - */ +#define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */ +#define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */ +#define WDC_SR_IRQ 128 /* Active low */ /* X X X */ + diff --git a/drivers/watchdog/wdrtas.c b/drivers/watchdog/wdrtas.c new file mode 100644 index 00000000..0a77655c --- /dev/null +++ b/drivers/watchdog/wdrtas.c @@ -0,0 +1,665 @@ +/* + * FIXME: add wdrtas_get_status and wdrtas_get_boot_status as soon as + * RTAS calls are available + */ + +/* + * RTAS watchdog driver + * + * (C) Copyright IBM Corp. 2005 + * device driver to exploit watchdog RTAS functions + * + * Authors : Utz Bacher <utz.bacher@de.ibm.com> + * + * 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, 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/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/uaccess.h> + +#include <asm/rtas.h> + +#define WDRTAS_MAGIC_CHAR 42 +#define WDRTAS_SUPPORTED_MASK (WDIOF_SETTIMEOUT | \ + WDIOF_MAGICCLOSE) + +MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>"); +MODULE_DESCRIPTION("RTAS watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); + +static bool wdrtas_nowayout = WATCHDOG_NOWAYOUT; +static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0); +static char wdrtas_expect_close; + +static int wdrtas_interval; + +#define WDRTAS_THERMAL_SENSOR 3 +static int wdrtas_token_get_sensor_state; +#define WDRTAS_SURVEILLANCE_IND 9000 +static int wdrtas_token_set_indicator; +#define WDRTAS_SP_SPI 28 +static int wdrtas_token_get_sp; +static int wdrtas_token_event_scan; + +#define WDRTAS_DEFAULT_INTERVAL 300 + +#define WDRTAS_LOGBUFFER_LEN 128 +static char wdrtas_logbuffer[WDRTAS_LOGBUFFER_LEN]; + + +/*** watchdog access functions */ + +/** + * wdrtas_set_interval - sets the watchdog interval + * @interval: new interval + * + * returns 0 on success, <0 on failures + * + * wdrtas_set_interval sets the watchdog keepalive interval by calling the + * RTAS function set-indicator (surveillance). The unit of interval is + * seconds. + */ + +static int wdrtas_set_interval(int interval) +{ + long result; + static int print_msg = 10; + + /* rtas uses minutes */ + interval = (interval + 59) / 60; + + result = rtas_call(wdrtas_token_set_indicator, 3, 1, NULL, + WDRTAS_SURVEILLANCE_IND, 0, interval); + if (result < 0 && print_msg) { + pr_err("setting the watchdog to %i timeout failed: %li\n", + interval, result); + print_msg--; + } + + return result; +} + +#define WDRTAS_SP_SPI_LEN 4 + +/** + * wdrtas_get_interval - returns the current watchdog interval + * @fallback_value: value (in seconds) to use, if the RTAS call fails + * + * returns the interval + * + * wdrtas_get_interval returns the current watchdog keepalive interval + * as reported by the RTAS function ibm,get-system-parameter. The unit + * of the return value is seconds. + */ +static int wdrtas_get_interval(int fallback_value) +{ + long result; + char value[WDRTAS_SP_SPI_LEN]; + + spin_lock(&rtas_data_buf_lock); + memset(rtas_data_buf, 0, WDRTAS_SP_SPI_LEN); + result = rtas_call(wdrtas_token_get_sp, 3, 1, NULL, + WDRTAS_SP_SPI, __pa(rtas_data_buf), + WDRTAS_SP_SPI_LEN); + + memcpy(value, rtas_data_buf, WDRTAS_SP_SPI_LEN); + spin_unlock(&rtas_data_buf_lock); + + if (value[0] != 0 || value[1] != 2 || value[3] != 0 || result < 0) { + pr_warn("could not get sp_spi watchdog timeout (%li). Continuing\n", + result); + return fallback_value; + } + + /* rtas uses minutes */ + return ((int)value[2]) * 60; +} + +/** + * wdrtas_timer_start - starts watchdog + * + * wdrtas_timer_start starts the watchdog by calling the RTAS function + * set-interval (surveillance) + */ +static void wdrtas_timer_start(void) +{ + wdrtas_set_interval(wdrtas_interval); +} + +/** + * wdrtas_timer_stop - stops watchdog + * + * wdrtas_timer_stop stops the watchdog timer by calling the RTAS function + * set-interval (surveillance) + */ +static void wdrtas_timer_stop(void) +{ + wdrtas_set_interval(0); +} + +/** + * wdrtas_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) + pr_info("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) +{ + long result; + + do { + result = rtas_call(wdrtas_token_event_scan, 4, 1, NULL, + RTAS_EVENT_SCAN_ALL_EVENTS, 0, + (void *)__pa(wdrtas_logbuffer), + WDRTAS_LOGBUFFER_LEN); + if (result < 0) + pr_err("event-scan failed: %li\n", result); + if (result == 0) + wdrtas_log_scanned_event(); + } while (result == 0); +} + +/** + * wdrtas_get_temperature - returns current temperature + * + * returns temperature or <0 on failures + * + * wdrtas_get_temperature returns the current temperature in Fahrenheit. It + * uses the RTAS call get-sensor-state, token 3 to do so + */ +static int wdrtas_get_temperature(void) +{ + int result; + int temperature = 0; + + result = rtas_get_sensor(WDRTAS_THERMAL_SENSOR, 0, &temperature); + + if (result < 0) + pr_warn("reading the thermal sensor failed: %i\n", result); + else + temperature = ((temperature * 9) / 5) + 32; /* fahrenheit */ + + return temperature; +} + +/** + * wdrtas_get_status - returns the status of the watchdog + * + * returns a bitmask of defines WDIOF_... as defined in + * include/linux/watchdog.h + */ +static int wdrtas_get_status(void) +{ + return 0; /* TODO */ +} + +/** + * wdrtas_get_boot_status - returns the reason for the last boot + * + * returns a bitmask of defines WDIOF_... as defined in + * include/linux/watchdog.h, indicating why the watchdog rebooted the system + */ +static int wdrtas_get_boot_status(void) +{ + return 0; /* TODO */ +} + +/*** watchdog API and operations stuff */ + +/* wdrtas_write - called when watchdog device is written to + * @file: file structure + * @buf: user buffer with data + * @len: amount to data written + * @ppos: position in file + * + * returns the number of successfully processed characters, which is always + * the number of bytes passed to this function + * + * wdrtas_write processes all the data given to it and looks for the magic + * character 'V'. This character allows the watchdog device to be closed + * properly. + */ +static ssize_t wdrtas_write(struct file *file, const char __user *buf, + size_t len, loff_t *ppos) +{ + int i; + char c; + + if (!len) + goto out; + + if (!wdrtas_nowayout) { + wdrtas_expect_close = 0; + /* look for 'V' */ + for (i = 0; i < len; i++) { + if (get_user(c, buf + i)) + return -EFAULT; + /* allow to close device */ + if (c == 'V') + wdrtas_expect_close = WDRTAS_MAGIC_CHAR; + } + } + + wdrtas_timer_keepalive(); + +out: + return len; +} + +/** + * wdrtas_ioctl - ioctl function for the watchdog device + * @file: file structure + * @cmd: command for ioctl + * @arg: argument pointer + * + * returns 0 on success, <0 on failure + * + * wdrtas_ioctl implements the watchdog API ioctls + */ + +static long wdrtas_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int __user *argp = (void __user *)arg; + int i; + static const struct watchdog_info wdinfo = { + .options = WDRTAS_SUPPORTED_MASK, + .firmware_version = 0, + .identity = "wdrtas", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &wdinfo, sizeof(wdinfo))) + return -EFAULT; + return 0; + + case WDIOC_GETSTATUS: + i = wdrtas_get_status(); + return put_user(i, argp); + + case WDIOC_GETBOOTSTATUS: + i = wdrtas_get_boot_status(); + return put_user(i, argp); + + case WDIOC_GETTEMP: + if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) + return -EOPNOTSUPP; + + i = wdrtas_get_temperature(); + return put_user(i, argp); + + case WDIOC_SETOPTIONS: + if (get_user(i, argp)) + return -EFAULT; + if (i & WDIOS_DISABLECARD) + wdrtas_timer_stop(); + if (i & WDIOS_ENABLECARD) { + wdrtas_timer_keepalive(); + wdrtas_timer_start(); + } + /* not implemented. Done by H8 + if (i & WDIOS_TEMPPANIC) { + } */ + return 0; + + case WDIOC_KEEPALIVE: + wdrtas_timer_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(i, argp)) + return -EFAULT; + + if (wdrtas_set_interval(i)) + return -EINVAL; + + wdrtas_timer_keepalive(); + + if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) + wdrtas_interval = i; + else + wdrtas_interval = wdrtas_get_interval(i); + /* fallthrough */ + + case WDIOC_GETTIMEOUT: + return put_user(wdrtas_interval, argp); + + default: + return -ENOTTY; + } +} + +/** + * wdrtas_open - open function of watchdog device + * @inode: inode structure + * @file: file structure + * + * returns 0 on success, -EBUSY if the file has been opened already, <0 on + * other failures + * + * function called when watchdog device is opened + */ +static int wdrtas_open(struct inode *inode, struct file *file) +{ + /* only open once */ + if (atomic_inc_return(&wdrtas_miscdev_open) > 1) { + atomic_dec(&wdrtas_miscdev_open); + return -EBUSY; + } + + wdrtas_timer_start(); + wdrtas_timer_keepalive(); + + return nonseekable_open(inode, file); +} + +/** + * wdrtas_close - close function of watchdog device + * @inode: inode structure + * @file: file structure + * + * returns 0 on success + * + * close function. Always succeeds + */ +static int wdrtas_close(struct inode *inode, struct file *file) +{ + /* only stop watchdog, if this was announced using 'V' before */ + if (wdrtas_expect_close == WDRTAS_MAGIC_CHAR) + wdrtas_timer_stop(); + else { + pr_warn("got unexpected close. Watchdog not stopped.\n"); + wdrtas_timer_keepalive(); + } + + wdrtas_expect_close = 0; + atomic_dec(&wdrtas_miscdev_open); + return 0; +} + +/** + * wdrtas_temp_read - gives back the temperature in fahrenheit + * @file: file structure + * @buf: user buffer + * @count: number of bytes to be read + * @ppos: position in file + * + * returns always 1 or -EFAULT in case of user space copy failures, <0 on + * other failures + * + * wdrtas_temp_read gives the temperature to the users by copying this + * value as one byte into the user space buffer. The unit is Fahrenheit... + */ +static ssize_t wdrtas_temp_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int temperature = 0; + + temperature = wdrtas_get_temperature(); + if (temperature < 0) + return temperature; + + if (copy_to_user(buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +/** + * wdrtas_temp_open - open function of temperature device + * @inode: inode structure + * @file: file structure + * + * returns 0 on success, <0 on failure + * + * function called when temperature device is opened + */ +static int wdrtas_temp_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +/** + * wdrtas_temp_close - close function of temperature device + * @inode: inode structure + * @file: file structure + * + * returns 0 on success + * + * close function. Always succeeds + */ +static int wdrtas_temp_close(struct inode *inode, struct file *file) +{ + return 0; +} + +/** + * wdrtas_reboot - reboot notifier function + * @nb: notifier block structure + * @code: reboot code + * @ptr: unused + * + * returns NOTIFY_DONE + * + * wdrtas_reboot stops the watchdog in case of a reboot + */ +static int wdrtas_reboot(struct notifier_block *this, + unsigned long code, void *ptr) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdrtas_timer_stop(); + + return NOTIFY_DONE; +} + +/*** initialization stuff */ + +static const struct file_operations wdrtas_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdrtas_write, + .unlocked_ioctl = wdrtas_ioctl, + .open = wdrtas_open, + .release = wdrtas_close, +}; + +static struct miscdevice wdrtas_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdrtas_fops, +}; + +static const struct file_operations wdrtas_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = wdrtas_temp_read, + .open = wdrtas_temp_open, + .release = wdrtas_temp_close, +}; + +static struct miscdevice wdrtas_tempdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &wdrtas_temp_fops, +}; + +static struct notifier_block wdrtas_notifier = { + .notifier_call = wdrtas_reboot, +}; + +/** + * wdrtas_get_tokens - reads in RTAS tokens + * + * returns 0 on success, <0 on failure + * + * wdrtas_get_tokens reads in the tokens for the RTAS calls used in + * this watchdog driver. It tolerates, if "get-sensor-state" and + * "ibm,get-system-parameter" are not available. + */ +static int wdrtas_get_tokens(void) +{ + wdrtas_token_get_sensor_state = rtas_token("get-sensor-state"); + if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) { + pr_warn("couldn't get token for get-sensor-state. Trying to continue without temperature support.\n"); + } + + wdrtas_token_get_sp = rtas_token("ibm,get-system-parameter"); + if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) { + pr_warn("couldn't get token for ibm,get-system-parameter. Trying to continue with a default timeout value of %i seconds.\n", + WDRTAS_DEFAULT_INTERVAL); + } + + wdrtas_token_set_indicator = rtas_token("set-indicator"); + if (wdrtas_token_set_indicator == RTAS_UNKNOWN_SERVICE) { + pr_err("couldn't get token for set-indicator. Terminating watchdog code.\n"); + return -EIO; + } + + wdrtas_token_event_scan = rtas_token("event-scan"); + if (wdrtas_token_event_scan == RTAS_UNKNOWN_SERVICE) { + pr_err("couldn't get token for event-scan. Terminating watchdog code.\n"); + return -EIO; + } + + return 0; +} + +/** + * wdrtas_unregister_devs - unregisters the misc dev handlers + * + * wdrtas_register_devs unregisters the watchdog and temperature watchdog + * misc devs + */ +static void wdrtas_unregister_devs(void) +{ + misc_deregister(&wdrtas_miscdev); + if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) + misc_deregister(&wdrtas_tempdev); +} + +/** + * wdrtas_register_devs - registers the misc dev handlers + * + * returns 0 on success, <0 on failure + * + * wdrtas_register_devs registers the watchdog and temperature watchdog + * misc devs + */ +static int wdrtas_register_devs(void) +{ + int result; + + result = misc_register(&wdrtas_miscdev); + if (result) { + pr_err("couldn't register watchdog misc device. Terminating watchdog code.\n"); + return result; + } + + if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) { + result = misc_register(&wdrtas_tempdev); + if (result) { + pr_warn("couldn't register watchdog temperature misc device. Continuing without temperature support.\n"); + wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE; + } + } + + return 0; +} + +/** + * wdrtas_init - init function of the watchdog driver + * + * returns 0 on success, <0 on failure + * + * registers the file handlers and the reboot notifier + */ +static int __init wdrtas_init(void) +{ + if (wdrtas_get_tokens()) + return -ENODEV; + + if (wdrtas_register_devs()) + return -ENODEV; + + if (register_reboot_notifier(&wdrtas_notifier)) { + pr_err("could not register reboot notifier. Terminating watchdog code.\n"); + wdrtas_unregister_devs(); + return -ENODEV; + } + + if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) + wdrtas_interval = WDRTAS_DEFAULT_INTERVAL; + else + wdrtas_interval = wdrtas_get_interval(WDRTAS_DEFAULT_INTERVAL); + + return 0; +} + +/** + * wdrtas_exit - exit function of the watchdog driver + * + * unregisters the file handlers and the reboot notifier + */ +static void __exit wdrtas_exit(void) +{ + if (!wdrtas_nowayout) + wdrtas_timer_stop(); + + wdrtas_unregister_devs(); + + unregister_reboot_notifier(&wdrtas_notifier); +} + +module_init(wdrtas_init); +module_exit(wdrtas_exit); diff --git a/drivers/watchdog/wdt.c b/drivers/watchdog/wdt.c new file mode 100644 index 00000000..ee4333c0 --- /dev/null +++ b/drivers/watchdog/wdt.c @@ -0,0 +1,669 @@ +/* + * Industrial Computer Source WDT501 driver + * + * (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> + * + * Release 0.10. + * + * Fixes + * Dave Gregorich : Modularisation and minor bugs + * Alan Cox : Added the watchdog ioctl() stuff + * Alan Cox : Fixed the reboot problem (as noted by + * Matt Crocker). + * Alan Cox : Added wdt= boot option + * Alan Cox : Cleaned up copy/user stuff + * Tim Hockin : Added insmod parameters, comment + * cleanup, parameterized timeout + * Tigran Aivazian : Restructured wdt_init() to handle + * failures + * Joel Becker : Added WDIOC_GET/SETTIMEOUT + * Matt Domsch : Added nowayout module option + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#include "wd501p.h" + +static unsigned long wdt_is_open; +static char expect_close; + +/* + * Module parameters + */ + +#define WD_TIMO 60 /* Default heartbeat = 60 seconds */ + +static int heartbeat = WD_TIMO; +static int wd_heartbeat; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default=" + __MODULE_STRING(WD_TIMO) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* You must set these - there is no sane way to probe for this board. */ +static int io = 0x240; +static int irq = 11; + +static DEFINE_SPINLOCK(wdt_lock); + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "WDT io port (default=0x240)"); +module_param(irq, int, 0); +MODULE_PARM_DESC(irq, "WDT irq (default=11)"); + +/* Support for the Fan Tachometer on the WDT501-P */ +static int tachometer; +module_param(tachometer, int, 0); +MODULE_PARM_DESC(tachometer, + "WDT501-P Fan Tachometer support (0=disable, default=0)"); + +static int type = 500; +module_param(type, int, 0); +MODULE_PARM_DESC(type, + "WDT501-P Card type (500 or 501, default=500)"); + +/* + * Programming support + */ + +static void wdt_ctr_mode(int ctr, int mode) +{ + ctr <<= 6; + ctr |= 0x30; + ctr |= (mode << 1); + outb_p(ctr, WDT_CR); +} + +static void wdt_ctr_load(int ctr, int val) +{ + outb_p(val&0xFF, WDT_COUNT0+ctr); + outb_p(val>>8, WDT_COUNT0+ctr); +} + +/** + * wdt_start: + * + * Start the watchdog driver. + */ + +static int wdt_start(void) +{ + unsigned long flags; + spin_lock_irqsave(&wdt_lock, flags); + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_mode(0, 3); /* Program CTR0 for Mode 3: + Square Wave Generator */ + wdt_ctr_mode(1, 2); /* Program CTR1 for Mode 2: + Rate Generator */ + wdt_ctr_mode(2, 0); /* Program CTR2 for Mode 0: + Pulse on Terminal Count */ + wdt_ctr_load(0, 8948); /* Count at 100Hz */ + wdt_ctr_load(1, wd_heartbeat); /* Heartbeat */ + wdt_ctr_load(2, 65535); /* Length of reset pulse */ + outb_p(0, WDT_DC); /* Enable watchdog */ + spin_unlock_irqrestore(&wdt_lock, flags); + return 0; +} + +/** + * wdt_stop: + * + * Stop the watchdog driver. + */ + +static int wdt_stop(void) +{ + unsigned long flags; + spin_lock_irqsave(&wdt_lock, flags); + /* Turn the card off */ + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_load(2, 0); /* 0 length reset pulses now */ + spin_unlock_irqrestore(&wdt_lock, flags); + return 0; +} + +/** + * wdt_ping: + * + * Reload counter one with the watchdog heartbeat. We don't bother + * reloading the cascade counter. + */ + +static void wdt_ping(void) +{ + unsigned long flags; + spin_lock_irqsave(&wdt_lock, flags); + /* Write a watchdog value */ + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_mode(1, 2); /* Re-Program CTR1 for Mode 2: + Rate Generator */ + wdt_ctr_load(1, wd_heartbeat); /* Heartbeat */ + outb_p(0, WDT_DC); /* Enable watchdog */ + spin_unlock_irqrestore(&wdt_lock, flags); +} + +/** + * wdt_set_heartbeat: + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat + * value is incorrect we keep the old value and return -EINVAL. If + * successful we return 0. + */ + +static int wdt_set_heartbeat(int t) +{ + if (t < 1 || t > 65535) + return -EINVAL; + + heartbeat = t; + wd_heartbeat = t * 100; + return 0; +} + +/** + * wdt_get_status: + * + * Extract the status information from a WDT watchdog device. There are + * several board variants so we have to know which bits are valid. Some + * bits default to one and some to zero in order to be maximally painful. + * + * we then map the bits onto the status ioctl flags. + */ + +static int wdt_get_status(void) +{ + unsigned char new_status; + int status = 0; + unsigned long flags; + + spin_lock_irqsave(&wdt_lock, flags); + new_status = inb_p(WDT_SR); + spin_unlock_irqrestore(&wdt_lock, flags); + + if (new_status & WDC_SR_ISOI0) + status |= WDIOF_EXTERN1; + if (new_status & WDC_SR_ISII1) + status |= WDIOF_EXTERN2; + if (type == 501) { + if (!(new_status & WDC_SR_TGOOD)) + status |= WDIOF_OVERHEAT; + if (!(new_status & WDC_SR_PSUOVER)) + status |= WDIOF_POWEROVER; + if (!(new_status & WDC_SR_PSUUNDR)) + status |= WDIOF_POWERUNDER; + if (tachometer) { + if (!(new_status & WDC_SR_FANGOOD)) + status |= WDIOF_FANFAULT; + } + } + return status; +} + +/** + * wdt_get_temperature: + * + * Reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static int wdt_get_temperature(void) +{ + unsigned short c; + unsigned long flags; + + spin_lock_irqsave(&wdt_lock, flags); + c = inb_p(WDT_RT); + spin_unlock_irqrestore(&wdt_lock, flags); + return (c * 11 / 15) + 7; +} + +static void wdt_decode_501(int status) +{ + if (!(status & WDC_SR_TGOOD)) + pr_crit("Overheat alarm (%d)\n", inb_p(WDT_RT)); + if (!(status & WDC_SR_PSUOVER)) + pr_crit("PSU over voltage\n"); + if (!(status & WDC_SR_PSUUNDR)) + pr_crit("PSU under voltage\n"); +} + +/** + * wdt_interrupt: + * @irq: Interrupt number + * @dev_id: Unused as we don't allow multiple devices. + * + * Handle an interrupt from the board. These are raised when the status + * map changes in what the board considers an interesting way. That means + * a failure condition occurring. + */ + +static irqreturn_t wdt_interrupt(int irq, void *dev_id) +{ + /* + * Read the status register see what is up and + * then printk it. + */ + unsigned char status; + + spin_lock(&wdt_lock); + status = inb_p(WDT_SR); + + pr_crit("WDT status %d\n", status); + + if (type == 501) { + wdt_decode_501(status); + if (tachometer) { + if (!(status & WDC_SR_FANGOOD)) + pr_crit("Possible fan fault\n"); + } + } + if (!(status & WDC_SR_WCCR)) { +#ifdef SOFTWARE_REBOOT +#ifdef ONLY_TESTING + pr_crit("Would Reboot\n"); +#else + pr_crit("Initiating system reboot\n"); + emergency_restart(); +#endif +#else + pr_crit("Reset in 5ms\n"); +#endif + } + spin_unlock(&wdt_lock); + return IRQ_HANDLED; +} + + +/** + * wdt_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t wdt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdt_ping(); + } + return count; +} + +/** + * wdt_ioctl: + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ + +static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + int status; + + struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "WDT500/501", + }; + + /* Add options according to the card we have */ + ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); + if (type == 501) { + ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER| + WDIOF_POWEROVER); + if (tachometer) + ident.options |= WDIOF_FANFAULT; + } + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + status = wdt_get_status(); + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + wdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/** + * wdt_open: + * @inode: inode of device + * @file: file handle to device + * + * The watchdog device has been opened. The watchdog device is single + * open and on opening we load the counters. Counter zero is a 100Hz + * cascade, into counter 1 which downcounts to reboot. When the counter + * triggers counter 2 downcounts the length of the reset pulse which + * set set to be as long as possible. + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + wdt_start(); + return nonseekable_open(inode, file); +} + +/** + * wdt_release: + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdt_stop(); + clear_bit(0, &wdt_is_open); + } else { + pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); + wdt_ping(); + } + expect_close = 0; + return 0; +} + +/** + * wdt_temp_read: + * @file: file handle to the watchdog board + * @buf: buffer to write 1 byte into + * @count: length of buffer + * @ptr: offset (no seek allowed) + * + * Temp_read reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static ssize_t wdt_temp_read(struct file *file, char __user *buf, + size_t count, loff_t *ptr) +{ + int temperature = wdt_get_temperature(); + + if (copy_to_user(buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +/** + * wdt_temp_open: + * @inode: inode of device + * @file: file handle to device + * + * The temperature device has been opened. + */ + +static int wdt_temp_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +/** + * wdt_temp_release: + * @inode: inode to board + * @file: file handle to board + * + * The temperature device has been closed. + */ + +static int wdt_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt_stop(); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static const struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .unlocked_ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_release, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +static const struct file_operations wdt_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = wdt_temp_read, + .open = wdt_temp_open, + .release = wdt_temp_release, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &wdt_temp_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +/** + * cleanup_module: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); + if (type == 501) + misc_deregister(&temp_miscdev); + unregister_reboot_notifier(&wdt_notifier); + free_irq(irq, NULL); + release_region(io, 8); +} + +/** + * wdt_init: + * + * Set up the WDT watchdog board. All we have to do is grab the + * resources we require and bitch if anyone beat us to them. + * The open() function will actually kick the board off. + */ + +static int __init wdt_init(void) +{ + int ret; + + if (type != 500 && type != 501) { + pr_err("unknown card type '%d'\n", type); + return -ENODEV; + } + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ + if (wdt_set_heartbeat(heartbeat)) { + wdt_set_heartbeat(WD_TIMO); + pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n", + WD_TIMO); + } + + if (!request_region(io, 8, "wdt501p")) { + pr_err("I/O address 0x%04x already in use\n", io); + ret = -EBUSY; + goto out; + } + + ret = request_irq(irq, wdt_interrupt, 0, "wdt501p", NULL); + if (ret) { + pr_err("IRQ %d is not free\n", irq); + goto outreg; + } + + ret = register_reboot_notifier(&wdt_notifier); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto outirq; + } + + if (type == 501) { + ret = misc_register(&temp_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto outrbt; + } + } + + ret = misc_register(&wdt_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto outmisc; + } + + pr_info("WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", + io, irq, heartbeat, nowayout); + if (type == 501) + pr_info("Fan Tachometer is %s\n", + tachometer ? "Enabled" : "Disabled"); + return 0; + +outmisc: + if (type == 501) + misc_deregister(&temp_miscdev); +outrbt: + unregister_reboot_notifier(&wdt_notifier); +outirq: + free_irq(irq, NULL); +outreg: + release_region(io, 8); +out: + return ret; +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c new file mode 100644 index 00000000..5eec7405 --- /dev/null +++ b/drivers/watchdog/wdt285.c @@ -0,0 +1,233 @@ +/* + * Intel 21285 watchdog driver + * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998 + * + * based on + * + * SoftDog 0.05: A Software Watchdog Device + * + * (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>, + * All Rights Reserved. + * + * 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/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/uaccess.h> +#include <linux/irq.h> +#include <mach/hardware.h> + +#include <asm/mach-types.h> +#include <asm/system_info.h> +#include <asm/hardware/dec21285.h> + +/* + * Define this to stop the watchdog actually rebooting the machine. + */ +#undef ONLY_TESTING + +static unsigned int soft_margin = 60; /* in seconds */ +static unsigned int reload; +static unsigned long timer_alive; + +#ifdef ONLY_TESTING +/* + * If the timer expires.. + */ +static void watchdog_fire(int irq, void *dev_id) +{ + pr_crit("Would Reboot\n"); + *CSR_TIMER4_CNTL = 0; + *CSR_TIMER4_CLR = 0; +} +#endif + +/* + * Refresh the timer. + */ +static void watchdog_ping(void) +{ + *CSR_TIMER4_LOAD = reload; +} + +/* + * Allow only one person to hold it open + */ +static int watchdog_open(struct inode *inode, struct file *file) +{ + int ret; + + if (*CSR_SA110_CNTL & (1 << 13)) + return -EBUSY; + + if (test_and_set_bit(1, &timer_alive)) + return -EBUSY; + + reload = soft_margin * (mem_fclk_21285 / 256); + + *CSR_TIMER4_CLR = 0; + watchdog_ping(); + *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD + | TIMER_CNTL_DIV256; + +#ifdef ONLY_TESTING + ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL); + if (ret) { + *CSR_TIMER4_CNTL = 0; + clear_bit(1, &timer_alive); + } +#else + /* + * Setting this bit is irreversible; once enabled, there is + * no way to disable the watchdog. + */ + *CSR_SA110_CNTL |= 1 << 13; + + ret = 0; +#endif + nonseekable_open(inode, file); + return ret; +} + +/* + * Shut off the timer. + * Note: if we really have enabled the watchdog, there + * is no way to turn off. + */ +static int watchdog_release(struct inode *inode, struct file *file) +{ +#ifdef ONLY_TESTING + free_irq(IRQ_TIMER4, NULL); + clear_bit(1, &timer_alive); +#endif + return 0; +} + +static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if (len) + watchdog_ping(); + + return len; +} + +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT, + .identity = "Footbridge Watchdog", +}; + +static long watchdog_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned int new_margin; + int __user *int_arg = (int __user *)arg; + int ret = -ENOTTY; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = 0; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, int_arg); + break; + + case WDIOC_KEEPALIVE: + watchdog_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(new_margin, int_arg); + if (ret) + break; + + /* Arbitrary, can't find the card's limits */ + if (new_margin < 0 || new_margin > 60) { + ret = -EINVAL; + break; + } + + soft_margin = new_margin; + reload = soft_margin * (mem_fclk_21285 / 256); + watchdog_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + ret = put_user(soft_margin, int_arg); + break; + } + return ret; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = watchdog_write, + .unlocked_ioctl = watchdog_ioctl, + .open = watchdog_open, + .release = watchdog_release, +}; + +static struct miscdevice watchdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &watchdog_fops, +}; + +static int __init footbridge_watchdog_init(void) +{ + int retval; + + if (machine_is_netwinder()) + return -ENODEV; + + retval = misc_register(&watchdog_miscdev); + if (retval < 0) + return retval; + + pr_info("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n", + soft_margin); + + if (machine_is_cats()) + pr_warn("Warning: Watchdog reset may not work on this machine\n"); + return 0; +} + +static void __exit footbridge_watchdog_exit(void) +{ + misc_deregister(&watchdog_miscdev); +} + +MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>"); +MODULE_DESCRIPTION("Footbridge watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(soft_margin, int, 0); +MODULE_PARM_DESC(soft_margin, "Watchdog timeout in seconds"); + +module_init(footbridge_watchdog_init); +module_exit(footbridge_watchdog_exit); diff --git a/drivers/watchdog/wdt977.c b/drivers/watchdog/wdt977.c new file mode 100644 index 00000000..65a40234 --- /dev/null +++ b/drivers/watchdog/wdt977.c @@ -0,0 +1,510 @@ +/* + * Wdt977 0.04: A Watchdog Device for Netwinder W83977AF chip + * + * (c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.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. + * + * ----------------------- + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * 19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface + * 06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts + * from minutes to seconds. + * 07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in + * nwwatchdog_init. + * 25-Oct-2005 Woody Suwalski: Convert addresses to #defs, add spinlocks + * remove limitiation to be used on + * Netwinders only + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/io.h> +#include <linux/uaccess.h> + +#include <asm/mach-types.h> + +#define WATCHDOG_VERSION "0.04" +#define WATCHDOG_NAME "Wdt977" + +#define IO_INDEX_PORT 0x370 /* on some systems it can be 0x3F0 */ +#define IO_DATA_PORT (IO_INDEX_PORT + 1) + +#define UNLOCK_DATA 0x87 +#define LOCK_DATA 0xAA +#define DEVICE_REGISTER 0x07 + + +#define DEFAULT_TIMEOUT 60 /* default timeout in seconds */ + +static int timeout = DEFAULT_TIMEOUT; +static int timeoutM; /* timeout in minutes */ +static unsigned long timer_alive; +static int testmode; +static char expect_close; +static DEFINE_SPINLOCK(spinlock); + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (60..15300, default=" + __MODULE_STRING(DEFAULT_TIMEOUT) ")"); +module_param(testmode, int, 0); +MODULE_PARM_DESC(testmode, "Watchdog testmode (1 = no reboot), default=0"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * Start the watchdog + */ + +static int wdt977_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4 + * F2 has the timeout in minutes + * F3 could be set to the POWER LED blink (with GP17 set to PowerLed) + * at timeout, and to reset timer on kbd/mouse activity (not impl.) + * F4 is used to just clear the TIMEOUT'ed state (bit 0) + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(timeoutM, IO_DATA_PORT); + outb_p(0xF3, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); /* another setting is 0E for + kbd/mouse/LED */ + outb_p(0xF4, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + + /* At last select device Aux1 (dev=7) and set GP16 as a + * watchdog output. In test mode watch the bit 1 on F4 to + * indicate "triggered" + */ + if (!testmode) { + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x07, IO_DATA_PORT); + outb_p(0xE6, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + } + + /* lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + pr_info("activated\n"); + + return 0; +} + +/* + * Stop the watchdog + */ + +static int wdt977_stop(void) +{ + unsigned long flags; + spin_lock_irqsave(&spinlock, flags); + + /* unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4 + * F3 is reset to its default state + * F4 can clear the TIMEOUT'ed state (bit 0) - back to default + * We can not use GP17 as a PowerLed, as we use its usage as a RedLed + */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(0xFF, IO_DATA_PORT); + outb_p(0xF3, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + outb_p(0xF4, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(0x00, IO_DATA_PORT); + + /* at last select device Aux1 (dev=7) and set + GP16 as a watchdog output */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x07, IO_DATA_PORT); + outb_p(0xE6, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + + /* lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + pr_info("shutdown\n"); + + return 0; +} + +/* + * Send a keepalive ping to the watchdog + * This is done by simply re-writing the timeout to reg. 0xF2 + */ + +static int wdt977_keepalive(void) +{ + unsigned long flags; + spin_lock_irqsave(&spinlock, flags); + + /* unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* select device Aux2 (device=8) and kicks watchdog reg F2 */ + /* F2 has the timeout in minutes */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF2, IO_INDEX_PORT); + outb_p(timeoutM, IO_DATA_PORT); + + /* lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + spin_unlock_irqrestore(&spinlock, flags); + + return 0; +} + +/* + * Set the watchdog timeout value + */ + +static int wdt977_set_timeout(int t) +{ + int tmrval; + + /* convert seconds to minutes, rounding up */ + tmrval = (t + 59) / 60; + + if (machine_is_netwinder()) { + /* we have a hw bug somewhere, so each 977 minute is actually + * only 30sec. This limits the max timeout to half of device + * max of 255 minutes... + */ + tmrval += tmrval; + } + + if (tmrval < 1 || tmrval > 255) + return -EINVAL; + + /* timeout is the timeout in seconds, timeoutM is + the timeout in minutes) */ + timeout = t; + timeoutM = tmrval; + return 0; +} + +/* + * Get the watchdog status + */ + +static int wdt977_get_status(int *status) +{ + int new_status; + unsigned long flags; + + spin_lock_irqsave(&spinlock, flags); + + /* unlock the SuperIO chip */ + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + outb_p(UNLOCK_DATA, IO_INDEX_PORT); + + /* select device Aux2 (device=8) and read watchdog reg F4 */ + outb_p(DEVICE_REGISTER, IO_INDEX_PORT); + outb_p(0x08, IO_DATA_PORT); + outb_p(0xF4, IO_INDEX_PORT); + new_status = inb_p(IO_DATA_PORT); + + /* lock the SuperIO chip */ + outb_p(LOCK_DATA, IO_INDEX_PORT); + + spin_unlock_irqrestore(&spinlock, flags); + + *status = 0; + if (new_status & 1) + *status |= WDIOF_CARDRESET; + + return 0; +} + + +/* + * /dev/watchdog handling + */ + +static int wdt977_open(struct inode *inode, struct file *file) +{ + /* If the watchdog is alive we don't need to start it again */ + if (test_and_set_bit(0, &timer_alive)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + wdt977_start(); + return nonseekable_open(inode, file); +} + +static int wdt977_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (expect_close == 42) { + wdt977_stop(); + clear_bit(0, &timer_alive); + } else { + wdt977_keepalive(); + pr_crit("Unexpected close, not stopping watchdog!\n"); + } + expect_close = 0; + return 0; +} + + +/* + * wdt977_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t wdt977_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + /* someone wrote to us, we should restart timer */ + wdt977_keepalive(); + } + return count; +} + +static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = WATCHDOG_NAME, +}; + +/* + * wdt977_ioctl: + * @inode: inode of the device + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. + */ + +static long wdt977_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int status; + int new_options, retval = -EINVAL; + int new_timeout; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + uarg.i = (int __user *)arg; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + wdt977_get_status(&status); + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_SETOPTIONS: + if (get_user(new_options, uarg.i)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt977_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt977_start(); + retval = 0; + } + + return retval; + + case WDIOC_KEEPALIVE: + wdt977_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (wdt977_set_timeout(new_timeout)) + return -EINVAL; + + wdt977_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, uarg.i); + + default: + return -ENOTTY; + + } +} + +static int wdt977_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdt977_stop(); + return NOTIFY_DONE; +} + +static const struct file_operations wdt977_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt977_write, + .unlocked_ioctl = wdt977_ioctl, + .open = wdt977_open, + .release = wdt977_release, +}; + +static struct miscdevice wdt977_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt977_fops, +}; + +static struct notifier_block wdt977_notifier = { + .notifier_call = wdt977_notify_sys, +}; + +static int __init wd977_init(void) +{ + int rc; + + pr_info("driver v%s\n", WATCHDOG_VERSION); + + /* Check that the timeout value is within its range; + if not reset to the default */ + if (wdt977_set_timeout(timeout)) { + wdt977_set_timeout(DEFAULT_TIMEOUT); + pr_info("timeout value must be 60 < timeout < 15300, using %d\n", + DEFAULT_TIMEOUT); + } + + /* on Netwinder the IOports are already reserved by + * arch/arm/mach-footbridge/netwinder-hw.c + */ + if (!machine_is_netwinder()) { + if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME)) { + pr_err("I/O address 0x%04x already in use\n", + IO_INDEX_PORT); + rc = -EIO; + goto err_out; + } + } + + rc = register_reboot_notifier(&wdt977_notifier); + if (rc) { + pr_err("cannot register reboot notifier (err=%d)\n", rc); + goto err_out_region; + } + + rc = misc_register(&wdt977_miscdev); + if (rc) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + wdt977_miscdev.minor, rc); + goto err_out_reboot; + } + + pr_info("initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n", + timeout, nowayout, testmode); + + return 0; + +err_out_reboot: + unregister_reboot_notifier(&wdt977_notifier); +err_out_region: + if (!machine_is_netwinder()) + release_region(IO_INDEX_PORT, 2); +err_out: + return rc; +} + +static void __exit wd977_exit(void) +{ + wdt977_stop(); + misc_deregister(&wdt977_miscdev); + unregister_reboot_notifier(&wdt977_notifier); + release_region(IO_INDEX_PORT, 2); +} + +module_init(wd977_init); +module_exit(wd977_exit); + +MODULE_AUTHOR("Woody Suwalski <woodys@xandros.com>"); +MODULE_DESCRIPTION("W83977AF Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c new file mode 100644 index 00000000..1c888c7d --- /dev/null +++ b/drivers/watchdog/wdt_pci.c @@ -0,0 +1,780 @@ +/* + * Industrial Computer Source PCI-WDT500/501 driver + * + * (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> + * + * Release 0.10. + * + * Fixes + * Dave Gregorich : Modularisation and minor bugs + * Alan Cox : Added the watchdog ioctl() stuff + * Alan Cox : Fixed the reboot problem (as noted by + * Matt Crocker). + * Alan Cox : Added wdt= boot option + * Alan Cox : Cleaned up copy/user stuff + * Tim Hockin : Added insmod parameters, comment cleanup + * Parameterized timeout + * JP Nollmann : Added support for PCI wdt501p + * Alan Cox : Split ISA and PCI cards into two drivers + * Jeff Garzik : PCI cleanups + * Tigran Aivazian : Restructured wdtpci_init_one() to handle + * failures + * Joel Becker : Added WDIOC_GET/SETTIMEOUT + * Zwane Mwaikambo : Magic char closing, locking changes, + * cleanups + * Matt Domsch : nowayout module option + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/io.h> +#include <linux/uaccess.h> + + +#define WDT_IS_PCI +#include "wd501p.h" + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int dev_count; + +static unsigned long open_lock; +static DEFINE_SPINLOCK(wdtpci_lock); +static char expect_close; + +static resource_size_t io; +static int irq; + +/* Default timeout */ +#define WD_TIMO 60 /* Default heartbeat = 60 seconds */ + +static int heartbeat = WD_TIMO; +static int wd_heartbeat; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" + __MODULE_STRING(WD_TIMO) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* Support for the Fan Tachometer on the PCI-WDT501 */ +static int tachometer; +module_param(tachometer, int, 0); +MODULE_PARM_DESC(tachometer, + "PCI-WDT501 Fan Tachometer support (0=disable, default=0)"); + +static int type = 500; +module_param(type, int, 0); +MODULE_PARM_DESC(type, + "PCI-WDT501 Card type (500 or 501 , default=500)"); + +/* + * Programming support + */ + +static void wdtpci_ctr_mode(int ctr, int mode) +{ + ctr <<= 6; + ctr |= 0x30; + ctr |= (mode << 1); + outb(ctr, WDT_CR); + udelay(8); +} + +static void wdtpci_ctr_load(int ctr, int val) +{ + outb(val & 0xFF, WDT_COUNT0 + ctr); + udelay(8); + outb(val >> 8, WDT_COUNT0 + ctr); + udelay(8); +} + +/** + * wdtpci_start: + * + * Start the watchdog driver. + */ + +static int wdtpci_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&wdtpci_lock, flags); + + /* + * "pet" the watchdog, as Access says. + * This resets the clock outputs. + */ + inb(WDT_DC); /* Disable watchdog */ + udelay(8); + wdtpci_ctr_mode(2, 0); /* Program CTR2 for Mode 0: + Pulse on Terminal Count */ + outb(0, WDT_DC); /* Enable watchdog */ + udelay(8); + inb(WDT_DC); /* Disable watchdog */ + udelay(8); + outb(0, WDT_CLOCK); /* 2.0833MHz clock */ + udelay(8); + inb(WDT_BUZZER); /* disable */ + udelay(8); + inb(WDT_OPTONOTRST); /* disable */ + udelay(8); + inb(WDT_OPTORST); /* disable */ + udelay(8); + inb(WDT_PROGOUT); /* disable */ + udelay(8); + wdtpci_ctr_mode(0, 3); /* Program CTR0 for Mode 3: + Square Wave Generator */ + wdtpci_ctr_mode(1, 2); /* Program CTR1 for Mode 2: + Rate Generator */ + wdtpci_ctr_mode(2, 1); /* Program CTR2 for Mode 1: + Retriggerable One-Shot */ + wdtpci_ctr_load(0, 20833); /* count at 100Hz */ + wdtpci_ctr_load(1, wd_heartbeat);/* Heartbeat */ + /* DO NOT LOAD CTR2 on PCI card! -- JPN */ + outb(0, WDT_DC); /* Enable watchdog */ + udelay(8); + + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_stop: + * + * Stop the watchdog driver. + */ + +static int wdtpci_stop(void) +{ + unsigned long flags; + + /* Turn the card off */ + spin_lock_irqsave(&wdtpci_lock, flags); + inb(WDT_DC); /* Disable watchdog */ + udelay(8); + wdtpci_ctr_load(2, 0); /* 0 length reset pulses now */ + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_ping: + * + * Reload counter one with the watchdog heartbeat. We don't bother + * reloading the cascade counter. + */ + +static int wdtpci_ping(void) +{ + unsigned long flags; + + spin_lock_irqsave(&wdtpci_lock, flags); + /* Write a watchdog value */ + inb(WDT_DC); /* Disable watchdog */ + udelay(8); + wdtpci_ctr_mode(1, 2); /* Re-Program CTR1 for Mode 2: + Rate Generator */ + wdtpci_ctr_load(1, wd_heartbeat);/* Heartbeat */ + outb(0, WDT_DC); /* Enable watchdog */ + udelay(8); + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_set_heartbeat: + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat + * value is incorrect we keep the old value and return -EINVAL. + * If successful we return 0. + */ +static int wdtpci_set_heartbeat(int t) +{ + /* Arbitrary, can't find the card's limits */ + if (t < 1 || t > 65535) + return -EINVAL; + + heartbeat = t; + wd_heartbeat = t * 100; + return 0; +} + +/** + * wdtpci_get_status: + * @status: the new status. + * + * Extract the status information from a WDT watchdog device. There are + * several board variants so we have to know which bits are valid. Some + * bits default to one and some to zero in order to be maximally painful. + * + * we then map the bits onto the status ioctl flags. + */ + +static int wdtpci_get_status(int *status) +{ + unsigned char new_status; + unsigned long flags; + + spin_lock_irqsave(&wdtpci_lock, flags); + new_status = inb(WDT_SR); + spin_unlock_irqrestore(&wdtpci_lock, flags); + + *status = 0; + if (new_status & WDC_SR_ISOI0) + *status |= WDIOF_EXTERN1; + if (new_status & WDC_SR_ISII1) + *status |= WDIOF_EXTERN2; + if (type == 501) { + if (!(new_status & WDC_SR_TGOOD)) + *status |= WDIOF_OVERHEAT; + if (!(new_status & WDC_SR_PSUOVER)) + *status |= WDIOF_POWEROVER; + if (!(new_status & WDC_SR_PSUUNDR)) + *status |= WDIOF_POWERUNDER; + if (tachometer) { + if (!(new_status & WDC_SR_FANGOOD)) + *status |= WDIOF_FANFAULT; + } + } + return 0; +} + +/** + * wdtpci_get_temperature: + * + * Reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static int wdtpci_get_temperature(int *temperature) +{ + unsigned short c; + unsigned long flags; + spin_lock_irqsave(&wdtpci_lock, flags); + c = inb(WDT_RT); + udelay(8); + spin_unlock_irqrestore(&wdtpci_lock, flags); + *temperature = (c * 11 / 15) + 7; + return 0; +} + +/** + * wdtpci_interrupt: + * @irq: Interrupt number + * @dev_id: Unused as we don't allow multiple devices. + * + * Handle an interrupt from the board. These are raised when the status + * map changes in what the board considers an interesting way. That means + * a failure condition occurring. + */ + +static irqreturn_t wdtpci_interrupt(int irq, void *dev_id) +{ + /* + * Read the status register see what is up and + * then printk it. + */ + unsigned char status; + + spin_lock(&wdtpci_lock); + + status = inb(WDT_SR); + udelay(8); + + pr_crit("status %d\n", status); + + if (type == 501) { + if (!(status & WDC_SR_TGOOD)) { + pr_crit("Overheat alarm (%d)\n", inb(WDT_RT)); + udelay(8); + } + if (!(status & WDC_SR_PSUOVER)) + pr_crit("PSU over voltage\n"); + if (!(status & WDC_SR_PSUUNDR)) + pr_crit("PSU under voltage\n"); + if (tachometer) { + if (!(status & WDC_SR_FANGOOD)) + pr_crit("Possible fan fault\n"); + } + } + if (!(status & WDC_SR_WCCR)) { +#ifdef SOFTWARE_REBOOT +#ifdef ONLY_TESTING + pr_crit("Would Reboot\n"); +#else + pr_crit("Initiating system reboot\n"); + emergency_restart(NULL); +#endif +#else + pr_crit("Reset in 5ms\n"); +#endif + } + spin_unlock(&wdtpci_lock); + return IRQ_HANDLED; +} + + +/** + * wdtpci_write: + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. Any + * write of data will do, as we we don't define content meaning. + */ + +static ssize_t wdtpci_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdtpci_ping(); + } + return count; +} + +/** + * wdtpci_ioctl: + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ + +static long wdtpci_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + int status; + + struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "PCI-WDT500/501", + }; + + /* Add options according to the card we have */ + ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); + if (type == 501) { + ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER| + WDIOF_POWEROVER); + if (tachometer) + ident.options |= WDIOF_FANFAULT; + } + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + wdtpci_get_status(&status); + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdtpci_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (wdtpci_set_heartbeat(new_heartbeat)) + return -EINVAL; + wdtpci_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + default: + return -ENOTTY; + } +} + +/** + * wdtpci_open: + * @inode: inode of device + * @file: file handle to device + * + * The watchdog device has been opened. The watchdog device is single + * open and on opening we load the counters. Counter zero is a 100Hz + * cascade, into counter 1 which downcounts to reboot. When the counter + * triggers counter 2 downcounts the length of the reset pulse which + * set set to be as long as possible. + */ + +static int wdtpci_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &open_lock)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + /* + * Activate + */ + wdtpci_start(); + return nonseekable_open(inode, file); +} + +/** + * wdtpci_release: + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int wdtpci_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdtpci_stop(); + } else { + pr_crit("Unexpected close, not stopping timer!\n"); + wdtpci_ping(); + } + expect_close = 0; + clear_bit(0, &open_lock); + return 0; +} + +/** + * wdtpci_temp_read: + * @file: file handle to the watchdog board + * @buf: buffer to write 1 byte into + * @count: length of buffer + * @ptr: offset (no seek allowed) + * + * Read reports the temperature in degrees Fahrenheit. The API is in + * fahrenheit. It was designed by an imperial measurement luddite. + */ + +static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, + size_t count, loff_t *ptr) +{ + int temperature; + + if (wdtpci_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user(buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +/** + * wdtpci_temp_open: + * @inode: inode of device + * @file: file handle to device + * + * The temperature device has been opened. + */ + +static int wdtpci_temp_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +/** + * wdtpci_temp_release: + * @inode: inode to board + * @file: file handle to board + * + * The temperature device has been closed. + */ + +static int wdtpci_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdtpci_stop(); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static const struct file_operations wdtpci_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdtpci_write, + .unlocked_ioctl = wdtpci_ioctl, + .open = wdtpci_open, + .release = wdtpci_release, +}; + +static struct miscdevice wdtpci_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdtpci_fops, +}; + +static const struct file_operations wdtpci_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = wdtpci_temp_read, + .open = wdtpci_temp_open, + .release = wdtpci_temp_release, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &wdtpci_temp_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdtpci_notifier = { + .notifier_call = wdtpci_notify_sys, +}; + + +static int __devinit wdtpci_init_one(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + int ret = -EIO; + + dev_count++; + if (dev_count > 1) { + pr_err("This driver only supports one device\n"); + return -ENODEV; + } + + if (type != 500 && type != 501) { + pr_err("unknown card type '%d'\n", type); + return -ENODEV; + } + + if (pci_enable_device(dev)) { + pr_err("Not possible to enable PCI Device\n"); + return -ENODEV; + } + + if (pci_resource_start(dev, 2) == 0x0000) { + pr_err("No I/O-Address for card detected\n"); + ret = -ENODEV; + goto out_pci; + } + + if (pci_request_region(dev, 2, "wdt_pci")) { + pr_err("I/O address 0x%llx already in use\n", + (unsigned long long)pci_resource_start(dev, 2)); + goto out_pci; + } + + irq = dev->irq; + io = pci_resource_start(dev, 2); + + if (request_irq(irq, wdtpci_interrupt, IRQF_SHARED, + "wdt_pci", &wdtpci_miscdev)) { + pr_err("IRQ %d is not free\n", irq); + goto out_reg; + } + + pr_info("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%llx (Interrupt %d)\n", + (unsigned long long)io, irq); + + /* Check that the heartbeat value is within its range; + if not reset to the default */ + if (wdtpci_set_heartbeat(heartbeat)) { + wdtpci_set_heartbeat(WD_TIMO); + pr_info("heartbeat value must be 0 < heartbeat < 65536, using %d\n", + WD_TIMO); + } + + ret = register_reboot_notifier(&wdtpci_notifier); + if (ret) { + pr_err("cannot register reboot notifier (err=%d)\n", ret); + goto out_irq; + } + + if (type == 501) { + ret = misc_register(&temp_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto out_rbt; + } + } + + ret = misc_register(&wdtpci_miscdev); + if (ret) { + pr_err("cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out_misc; + } + + pr_info("initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + if (type == 501) + pr_info("Fan Tachometer is %s\n", + tachometer ? "Enabled" : "Disabled"); + + ret = 0; +out: + return ret; + +out_misc: + if (type == 501) + misc_deregister(&temp_miscdev); +out_rbt: + unregister_reboot_notifier(&wdtpci_notifier); +out_irq: + free_irq(irq, &wdtpci_miscdev); +out_reg: + pci_release_region(dev, 2); +out_pci: + pci_disable_device(dev); + goto out; +} + + +static void __devexit wdtpci_remove_one(struct pci_dev *pdev) +{ + /* here we assume only one device will ever have + * been picked up and registered by probe function */ + misc_deregister(&wdtpci_miscdev); + if (type == 501) + misc_deregister(&temp_miscdev); + unregister_reboot_notifier(&wdtpci_notifier); + free_irq(irq, &wdtpci_miscdev); + pci_release_region(pdev, 2); + pci_disable_device(pdev); + dev_count--; +} + + +static DEFINE_PCI_DEVICE_TABLE(wdtpci_pci_tbl) = { + { + .vendor = PCI_VENDOR_ID_ACCESSIO, + .device = PCI_DEVICE_ID_ACCESSIO_WDG_CSM, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl); + + +static struct pci_driver wdtpci_driver = { + .name = "wdt_pci", + .id_table = wdtpci_pci_tbl, + .probe = wdtpci_init_one, + .remove = __devexit_p(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_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 00000000..b1815c5e --- /dev/null +++ b/drivers/watchdog/wm831x_wdt.c @@ -0,0 +1,327 @@ +/* + * 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 __devinit wm831x_wdt_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_pdata *chip_pdata; + 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) { + dev_err(wm831x->dev, "Unable to alloacate watchdog device\n"); + 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 (pdev->dev.parent->platform_data) { + chip_pdata = pdev->dev.parent->platform_data; + 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 = gpio_request(pdata->update_gpio, + "Watchdog update"); + if (ret < 0) { + dev_err(wm831x->dev, + "Failed to request update GPIO: %d\n", + ret); + goto err; + } + + ret = gpio_direction_output(pdata->update_gpio, 0); + if (ret != 0) { + dev_err(wm831x->dev, + "gpio_direction_output returned: %d\n", + ret); + goto err_gpio; + } + + 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_gpio; + } + } + + ret = watchdog_register_device(&driver_data->wdt); + if (ret != 0) { + dev_err(wm831x->dev, "watchdog_register_device() failed: %d\n", + ret); + goto err_gpio; + } + + dev_set_drvdata(&pdev->dev, driver_data); + + return 0; + +err_gpio: + if (driver_data->update_gpio) + gpio_free(driver_data->update_gpio); +err: + return ret; +} + +static int __devexit wm831x_wdt_remove(struct platform_device *pdev) +{ + struct wm831x_wdt_drvdata *driver_data = dev_get_drvdata(&pdev->dev); + + watchdog_unregister_device(&driver_data->wdt); + + if (driver_data->update_gpio) + gpio_free(driver_data->update_gpio); + + return 0; +} + +static struct platform_driver wm831x_wdt_driver = { + .probe = wm831x_wdt_probe, + .remove = __devexit_p(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 00000000..3c766934 --- /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 __devinit 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 __devexit 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 = __devexit_p(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/wmt_wdt.c b/drivers/watchdog/wmt_wdt.c new file mode 100644 index 00000000..3d1b44e9 --- /dev/null +++ b/drivers/watchdog/wmt_wdt.c @@ -0,0 +1,419 @@ +/* + * linux/drivers/char/wmt_gpio.c + * + * Copyright (c) 2008 WonderMedia Technologies, 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. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + */ + +#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/platform_device.h> +#include <linux/uaccess.h> +#include <linux/types.h> +#include <mach/hardware.h> + +#define OSCR_FREQ 3000000 /* 3 MHz tick rate */ + +static unsigned long wmt_wdt_users; +static int expect_close; +static int pre_margin; + +static unsigned int wdt_tcr; +static unsigned int wdt_tmr0; +static unsigned int wdt_enable; + +#define WDT_NS_RSR (PM_CTRL_BASE_ADDR+0x8050) //NS Reset Status Register +#define WDT_NS_TMR4 (PM_CTRL_BASE_ADDR+0x80f0) //NS OS Timer Match Register 4 +#define WDT_NS_TMR5 (PM_CTRL_BASE_ADDR+0x80f4) //NS OS Timer Match Register 5 +#define WDT_NS_TMR6 (PM_CTRL_BASE_ADDR+0x80f8) //NS OS Timer Match Register 6 +#define WDT_NS_TMR7 (PM_CTRL_BASE_ADDR+0x80fc) //NS OS Timer Match Register 7 +#define WDT_NS_TMR0 (PM_CTRL_BASE_ADDR+0x8100) //NS OS Timer Match Register 0 +#define WDT_NS_TMR1 (PM_CTRL_BASE_ADDR+0x8104) //NS OS Timer Match Register 1 +#define WDT_NS_TMR2 (PM_CTRL_BASE_ADDR+0x8108) //NS OS Timer Match Register 2 +#define WDT_NS_TMR3 (PM_CTRL_BASE_ADDR+0x810c) //NS OS Timer Match Register 3 +#define WDT_NS_TCR (PM_CTRL_BASE_ADDR+0x8110) //NS OS Timer Count Register +#define WDT_NS_TSR (PM_CTRL_BASE_ADDR+0x8114) //NS OS Timer Status register +#define WDT_NS_TWER (PM_CTRL_BASE_ADDR+0x8118) //NS OS Timer Watch Dog Enable Register +#define WDT_NS_IER (PM_CTRL_BASE_ADDR+0x811c) //NS OS Timer Interrupt Enable Register +#define WDT_NS_CTRL (PM_CTRL_BASE_ADDR+0x8120) //NS OS Timer Control Register +#define WDT_NS_TASR (PM_CTRL_BASE_ADDR+0x8124) //NS OS Timer Access Status Register +#define WMT_NS_EN (1<<4) +#define WMT_NS_RESET (1<<3) + + +unsigned int wmt_read_ns_oscr(void) +{ + // + // Request the reading of OS Timer Count Register. + // + REG32_VAL(WDT_NS_CTRL) |= OSTC_RDREQ; + + // + // Polling until free to reading. + // + while ( REG32_VAL(WDT_NS_TASR) & OSTA_RCA ); + + return REG32_VAL(WDT_NS_TCR); +} + + +void wmt_write_ns_oscr(unsigned int val) +{ + // + // Polling until free to reading. + // + while ( REG32_VAL(WDT_NS_TASR) & OSTA_CWA ); + + REG32_VAL(WDT_NS_TCR) = val; +} + +/* wmt_wdt_open() + * + * There is a simple mutex to protect only one user could use it. + */ +static int wmt_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(1, &wmt_wdt_users)) + return -EBUSY; + + /* + * 1. Set timer match value. + * 2. Activate watchdog timer. + */ + REG32_VAL(WDT_NS_TMR0) = pre_margin + wmt_read_ns_oscr(); + REG32_VAL(WDT_NS_TWER) = OSTW_WE; + + OSTW_VAL |= WMT_NS_EN; + REG32_VAL(WDT_NS_CTRL) = OSTC_ENABLE; + + return 0; +} + +/* wmt_wdt_release() + * + * Shut off the watchdog timer. + */ +static int wmt_wdt_release(struct inode *inode, struct file *file) +{ + /* + * Release watchdog timer depend on NOWAYOUT option. + */ +#ifndef CONFIG_WATCHDOG_NOWAYOUT + if (expect_close) + REG32_VAL(WDT_NS_TWER) = 0; + else + printk("CONFIG_WATCHDOG_NOWAYOUT\n"); + +#else + printk(KERN_CRIT "Watchdog: WDT device closed unexpectedly. " \ + "WDT will not stop!\n"); +#endif + clear_bit(1, &wmt_wdt_users); + expect_close = 0; + + return 0; +} + +/* wmt_wdt_write() + * + */ +static ssize_t wmt_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + int ret; + + /* + * Check if we can seek (pwrite) on this device. + */ + + if ( len ) { +#ifndef CONFIG_WATCHDOG_NOWAYOUT + + size_t i; + + expect_close = 0; + + for ( i=0; i!=len; i++ ) { + char c; + + if ( get_user(c, data + i) ) + return -EFAULT; + + /* + * The magic character 'V' is for shotdown + * watchdog. + */ + if ( c == 'V' ) + expect_close = 1; + } +#endif + + /* + * Refresh watchdog timer. + */ + //printk("Refresh %d to ",REG32_VAL(WDT_NS_TMR0)); + REG32_VAL(WDT_NS_TMR0) = pre_margin + wmt_read_ns_oscr(); + //printk(" to %d\n",REG32_VAL(WDT_NS_TMR0)); + + } + + ret = len ? 1 : 0; + return ret; +} + +static struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "WMT Watchdog", + .firmware_version = 0, + +}; + +/* wmt_wdt_ioctl() + * + */ +static long wmt_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +//static int wmt_wdt_ioctl(struct inode *inode, struct file *file, +// unsigned int cmd, unsigned long arg) +{ + int ret = 0, time; + + switch ( cmd ) { + case WDIOC_GETSUPPORT: + { + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + } + case WDIOC_GETSTATUS: + return put_user(0, (int __user *)arg); + case WDIOC_GETBOOTSTATUS: + { + /* TODO */ + break; + } + + case WDIOC_SETTIMEOUT: + { + if (get_user(time, (int __user *)arg)) + return -EFAULT; + + /* Sanity check */ + if ( time <= 0 || time > 255 ) { + ret = -EINVAL; + break; + } + pre_margin = OSCR_FREQ * time; + REG32_VAL(WDT_NS_TMR0) = pre_margin + wmt_read_ns_oscr(); + /* Falling down */ + } + case WDIOC_GETTIMEOUT: + { + ret = put_user(pre_margin / OSCR_FREQ, (int *)arg); + break; + } + case WDIOC_KEEPALIVE: + { + REG32_VAL(WDT_NS_TMR0) = pre_margin + wmt_read_ns_oscr(); + ret = 0; + break; + } + default: + { + /* No this ioctl command */ + ret = -ENOIOCTLCMD; + } + } + + return ret; +} + +static struct file_operations wmt_dog_fops = +{ + .owner = THIS_MODULE, + .write = wmt_wdt_write, + .unlocked_ioctl = wmt_wdt_ioctl, + .open = wmt_wdt_open, + .release = wmt_wdt_release, +}; + +static struct miscdevice wmt_dog_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wmt_dog_fops, +}; + +static int margin __initdata = 60; /* (secs) Default using one minute. */ + +static int wmt_watchdog_probe(struct platform_device *pdev) +{ + int ret; + unsigned int val; + + /* + * Check if there is an enabled watchdog timer + * due to IO_RESET or SW_RESET. + * + */ + if ( REG32_VAL(WDT_NS_RSR) & WMT_NS_RESET) + REG32_VAL(WDT_NS_RSR) = WMT_NS_RESET; + + val = OSTW_VAL; + if (OSTW_VAL&WMT_NS_EN) { + val &= ~WMT_NS_EN; + OSTW_VAL = val; + } + + if ( REG32_VAL(WDT_NS_CTRL) & OSTC_ENABLE) + REG32_VAL(WDT_NS_CTRL) = 0; + + pre_margin = OSCR_FREQ * margin; + + ret = misc_register(&wmt_dog_miscdev); + if (ret) { + printk("Error: Can not register Watchdog driver\n"); + return ret; + } + + printk(KERN_INFO "Watchdog: timer margin %d sec\n", margin); + + return ret; +} + +static int __devexit wmt_wdt_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_PM +static int wmt_watchdog_suspend +( + struct device *pdev +) +{ + + wdt_enable = 0; + if ( REG32_VAL(WDT_NS_CTRL) & OSTC_ENABLE) { + wdt_tcr = wmt_read_ns_oscr(); + wdt_tmr0 = REG32_VAL(WDT_NS_TMR0); + wdt_enable = 1; + printk("wdt_tcr = 0x%x , wdt_tmr0 = 0x%x \n",wdt_tcr,wdt_tmr0); + } + + return 0; +} + +static int wmt_watchdog_resume +( + struct device *pdev +) +{ + if (wdt_enable) { + + REG32_VAL(WDT_NS_TMR0) = wdt_tmr0; + wmt_write_ns_oscr(wdt_tcr); + REG32_VAL(WDT_NS_TWER) = OSTW_WE; + + OSTW_VAL |= WMT_NS_EN; + REG32_VAL(WDT_NS_CTRL) = OSTC_ENABLE; + printk("resume : wdt_tcr = 0x%x , wdt_tmr0 = 0x%x \n",wmt_read_ns_oscr(),REG32_VAL(WDT_NS_TMR0)); + + } + return 0; +} + +static int wmt_watchdog_freeze(struct device *dev) +{ + return 0; +} + +static int wmt_watchdog_restore(struct device *dev) +{ + return 0; +} +#else + +#define wmt_watchdog_suspend NULL +#define wmt_watchdog_resume NULL +#define wmt_watchdog_freeze NULL +#define wmt_watchdog_restore NULL + +#endif + +static struct dev_pm_ops wmt_watchdog_pm_ops = { + .suspend = wmt_watchdog_suspend, + .resume = wmt_watchdog_resume, + .freeze = wmt_watchdog_freeze, + .thaw = wmt_watchdog_restore, + .restore = wmt_watchdog_restore, +}; + + +static struct platform_device wmt_watchdog_device = { + .name = "wmt-watchdog", + .id = 0, + +}; + +static struct platform_driver wmt_watchdog_driver = { + .driver = { + .name = "wmt-watchdog", + .owner = THIS_MODULE, + }, + .probe = wmt_watchdog_probe, + .remove = __devexit_p(wmt_wdt_remove), + .driver.pm = &wmt_watchdog_pm_ops +}; + +static int __init wmt_watchdog_init(void) +{ + int ret; + + ret = platform_device_register(&wmt_watchdog_device); + if(ret) { + printk("Error: Can not register Watchdog platform\n"); + return ret; + } + ret = platform_driver_register(&wmt_watchdog_driver); + if(ret) { + printk("Error: Can not register Watchdog driver\n"); + platform_device_unregister(&wmt_watchdog_device); + return ret; + } + return 0; +} +module_init(wmt_watchdog_init); + +static void __exit wmt_watchdog_exit(void) +{ + platform_driver_unregister(&wmt_watchdog_driver); + platform_device_unregister(&wmt_watchdog_device); +} +module_exit(wmt_watchdog_exit); + + +MODULE_DESCRIPTION("WMT Watchdog Driver"); + +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/watchdog/xen_wdt.c b/drivers/watchdog/xen_wdt.c new file mode 100644 index 00000000..e4a25b51 --- /dev/null +++ b/drivers/watchdog/xen_wdt.c @@ -0,0 +1,365 @@ +/* + * 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 __devinit 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 __devexit 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 = __devexit_p(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"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |