mv watchdog tree under drivers

move watchdog tree from drivers/char/watchdog to drivers/watchdog.

Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
This commit is contained in:
Wim Van Sebroeck
2007-08-17 08:38:02 +00:00
parent d85714d81c
commit b7e04f8c61
65 changed files with 3 additions and 3 deletions

853
drivers/watchdog/Kconfig Normal file
View File

@@ -0,0 +1,853 @@
#
# 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.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_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"
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.
# ALPHA Architecture
# ARM Architecture
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 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 S3C2410_WATCHDOG
tristate "S3C2410 Watchdog"
depends on ARCH_S3C2410
help
Watchdog timer block in the Samsung S3C2410 chips. 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 MPCORE_WATCHDOG
tristate "MPcore watchdog"
depends on ARM_MPCORE_PLATFORM && LOCAL_TIMERS
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
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_OMAP24XX
help
Support for TI OMAP1610/OMAP1710/OMAP2420 watchdog. Say 'Y' here to
enable the OMAP1610/OMAP1710 watchdog timer.
config PNX4008_WATCHDOG
tristate "PNX4008 Watchdog"
depends on ARCH_PNX4008
help
Say Y here if to include support for the watchdog timer
in the PNX4008 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.
# ARM26 Architecture
# AVR32 Architecture
config AT32AP700X_WDT
tristate "AT32AP700x watchdog"
depends on CPU_AT32AP7000
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 X86 && PCI
help
This is the driver for the hardware watchdog on the ALi M7101 PMU
as used in the x86 Cobalt servers.
To compile this driver as a module, choose M here: the
module will be called alim7101_wdt.
Most people will say N.
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 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 Wafer 5823 Single Board Computer Watchdog"
depends on X86
help
This is a driver for the hardware watchdog on the ICP Wafer 5823
Single Board Computer (and probably other similar models).
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 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 ICH8) and in the Intel 6300ESB
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 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 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.ko.
Most people will say N.
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 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 W83627HF_WDT
tristate "W83627HF 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). 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 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
# M68KNOMMU Architecture
# MIPS Architecture
config INDYDOG
tristate "Indy/I2 Hardware Watchdog"
depends on SGI_IP22
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 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 WDT_RM9K_GPI
tristate "RM9000/GPI hardware watchdog"
depends on CPU_RM9000
help
Watchdog implementation using the GPI hardware found on
PMC-Sierra RM9xxx CPUs.
To compile this driver as a module, choose M here: the
module will be called rm9k_wdt.
# PARISC Architecture
# POWERPC Architecture
config MPC5200_WDT
tristate "MPC5200 Watchdog Timer"
depends on PPC_MPC52xx
config 8xx_WDT
tristate "MPC8xx Watchdog Timer"
depends on 8xx
config 83xx_WDT
tristate "MPC83xx Watchdog Timer"
depends on PPC_83xx
config MV64X60_WDT
tristate "MV64X60 (Marvell Discovery) Watchdog Timer"
depends on MV64X60
config BOOKE_WDT
bool "PowerPC Book-E Watchdog Timer"
depends on BOOKE || 4xx
---help---
Please see Documentation/watchdog/watchdog-api.txt for
more information.
# 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.
config SH_WDT_MMAP
bool "Allow mmap of SH WDT"
default n
depends on SH_WDT
help
If you say Y here, user applications will be able to mmap the
WDT/CPG registers.
# SPARC Architecture
# SPARC64 Architecture
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.
# V850 Architecture
# XTENSA Architecture
#
# 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.
config WDT_501
bool "WDT501 features"
depends on WDT
help
Saying Y here and creating a character special file /dev/temperature
with major number 10 and minor number 131 ("man mknod") will give
you a thermometer inside your computer: reading from
/dev/temperature yields one byte, the temperature in degrees
Fahrenheit. This works only if you have a WDT501P watchdog board
installed.
If you want to enable the Fan Tachometer on the WDT501P, then you
can do this via the tachometer parameter. Only do this if you have a
fan tachometer actually set up.
#
# PCI-based Watchdog Cards
#
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.
To compile this driver as a module, choose M here: the
module will be called wdt_pci.
config WDT_501_PCI
bool "PCI-WDT501 features"
depends on WDTPCI
help
Saying Y here and creating a character special file /dev/temperature
with major number 10 and minor number 131 ("man mknod") will give
you a thermometer inside your computer: reading from
/dev/temperature yields one byte, the temperature in degrees
Fahrenheit. This works only if you have a PCI-WDT501 watchdog board
installed.
If you 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.
#
# 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

120
drivers/watchdog/Makefile Normal file
View File

@@ -0,0 +1,120 @@
#
# Makefile for the WatchDog device drivers.
#
# Only one watchdog can succeed. We probe the ISA/PCI/USB based
# watchdog-cards first, then the architecture specific watchdog
# drivers and then the architecture independant "softdog" driver.
# 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_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o
obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o
obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
obj-$(CONFIG_977_WATCHDOG) += wdt977.o
obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o
obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o
obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o
obj-$(CONFIG_EP93XX_WATCHDOG) += ep93xx_wdt.o
obj-$(CONFIG_PNX4008_WATCHDOG) += pnx4008_wdt.o
obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o
obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o
# ARM26 Architecture
# 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_SC520_WDT) += sc520_wdt.o
obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
obj-$(CONFIG_IB700_WDT) += ib700wdt.o
obj-$(CONFIG_IBMASR) += ibmasr.o
obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o
obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o
obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o iTCO_vendor_support.o
obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o
obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
obj-$(CONFIG_SBC8360_WDT) += sbc8360.o
obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o
obj-$(CONFIG_SMSC37B787_WDT) += smsc37b787_wdt.o
obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o
obj-$(CONFIG_W83697HF_WDT) += w83697hf_wdt.o
obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o
obj-$(CONFIG_W83977F_WDT) += w83977f_wdt.o
obj-$(CONFIG_MACHZ_WDT) += machzwd.o
obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o
# M32R Architecture
# M68K Architecture
# M68KNOMMU Architecture
# MIPS Architecture
obj-$(CONFIG_INDYDOG) += indydog.o
obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o
obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o
# PARISC Architecture
# POWERPC Architecture
obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o
obj-$(CONFIG_MPC5200_WDT) += mpc5200_wdt.o
obj-$(CONFIG_83xx_WDT) += mpc83xx_wdt.o
obj-$(CONFIG_MV64X60_WDT) += mv64x60_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
# V850 Architecture
# XTENSA Architecture
# Architecture Independant
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o

View File

@@ -0,0 +1,348 @@
/*
* Acquire Single Board Computer Watchdog Timer driver
*
* Based on wdt.c. Original copyright messages:
*
* (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>
*
* 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
* Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
* Can't add timeout - driver doesn't allow changing value
*/
/*
* Theory of Operation:
* The Watch-Dog Timer is provided to ensure that standalone
* Systems can always recover from catastrophic conditions that
* caused the CPU to crash. This condition may have occured by
* 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, ...
*/
/* 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 <asm/uaccess.h> /* For copy_to_user/put_user/... */
#include <asm/io.h> /* For inb/outb/... */
/* Module information */
#define DRV_NAME "acquirewdt"
#define PFX DRV_NAME ": "
#define WATCHDOG_NAME "Acquire WDT"
#define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */
/* internal variables */
static struct platform_device *acq_platform_device; /* the watchdog platform device */
static unsigned long acq_is_open;
static char expect_close;
/* module parameters */
static int wdt_stop = 0x43; /* You must set this - there is no sane way to probe for this board. */
module_param(wdt_stop, int, 0);
MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
static int wdt_start = 0x443; /* You must set this - there is no sane way to probe for this board. */
module_param(wdt_start, int, 0);
MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/*
* 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 int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int options, retval = -EINVAL;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident =
{
.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_KEEPALIVE:
acq_keepalive();
return 0;
case WDIOC_GETTIMEOUT:
return put_user(WATCHDOG_HEARTBEAT, 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;
}
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 {
printk(KERN_CRIT PFX "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,
.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)) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_stop);
ret = -EIO;
goto out;
}
}
if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_start);
ret = -EIO;
goto unreg_stop;
}
ret = misc_register(&acq_miscdev);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_regions;
}
printk (KERN_INFO PFX "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;
printk(KERN_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);
printk(KERN_INFO PFX "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);

View File

@@ -0,0 +1,362 @@
/*
* 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@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>
*
* 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.
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define DRV_NAME "advantechwdt"
#define PFX DRV_NAME ": "
#define WATCHDOG_NAME "Advantech WDT"
#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
static struct platform_device *advwdt_platform_device; /* the watchdog platform device */
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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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 int
advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int new_timeout;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.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_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);
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;
}
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 {
printk(KERN_CRIT PFX "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,
.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)) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_stop);
ret = -EIO;
goto out;
}
}
if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_start);
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);
printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n",
timeout);
}
ret = misc_register(&advwdt_miscdev);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_regions;
}
printk (KERN_INFO PFX "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;
printk(KERN_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);
printk(KERN_INFO PFX "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);

View File

@@ -0,0 +1,465 @@
/*
* 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.
*/
#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 <asm/uaccess.h>
#include <asm/io.h>
#define WATCHDOG_NAME "ALi_M1535"
#define PFX WATCHDOG_NAME ": "
#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
/* internal variables */
static unsigned long ali_is_open;
static char ali_expect_release;
static struct pci_dev *ali_pci;
static u32 ali_timeout_bits; /* stores the computed timeout */
static spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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
* @inode: VFS inode
* @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 int ali_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING |
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_KEEPALIVE:
ali_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if (get_user (new_options, p))
return -EFAULT;
if (new_options & WDIOS_DISABLECARD) {
ali_stop();
retval = 0;
}
if (new_options & WDIOS_ENABLECARD) {
ali_start();
retval = 0;
}
return retval;
}
case WDIOC_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 {
printk(KERN_CRIT PFX "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) {
/* Turn the WDT off */
ali_stop();
}
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 struct pci_device_id ali_pci_tbl[] = {
{ 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);
wdog &= ~0x3F; /* Timer bits */
wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */
wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */
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,
.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;
spin_lock_init(&ali_lock);
/* 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;
printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n",
timeout);
}
/* Calculate the watchdog's timeout */
ali_settimer(timeout);
ret = misc_register(&ali_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto out;
}
ret = register_reboot_notifier(&ali_notifier);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto unreg_miscdev;
}
printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
out:
return ret;
unreg_miscdev:
misc_deregister(&ali_miscdev);
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 */
unregister_reboot_notifier(&ali_notifier);
misc_deregister(&ali_miscdev);
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);

View File

@@ -0,0 +1,423 @@
/*
* 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>
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define OUR_NAME "alim7101_wdt"
#define PFX OUR_NAME ": "
#define WDT_ENABLE 0x9C
#define WDT_DISABLE 0x8C
#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 */
static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be 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 int use_gpio = 0; /* 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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 {
printk(KERN_WARNING PFX "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);
printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
}
static void wdt_turnoff(void)
{
/* Stop the timer */
del_timer_sync(&timer);
wdt_change(WDT_DISABLE);
printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
}
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); */
printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
}
clear_bit(0, &wdt_is_open);
wdt_expect_close = 0;
return 0;
}
static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static 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_KEEPALIVE:
wdt_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if(get_user(new_options, p))
return -EFAULT;
if(new_options & WDIOS_DISABLECARD) {
wdt_turnoff();
retval = 0;
}
if(new_options & WDIOS_ENABLECARD) {
wdt_startup();
retval = 0;
}
return retval;
}
case WDIOC_SETTIMEOUT:
{
int new_timeout;
if(get_user(new_timeout, p))
return -EFAULT;
if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
return -EINVAL;
timeout = new_timeout;
wdt_keepalive();
/* Fall through */
}
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
default:
return -ENOTTY;
}
}
static const struct file_operations wdt_fops = {
.owner= THIS_MODULE,
.llseek= no_llseek,
.write= fop_write,
.open= fop_open,
.release= fop_close,
.ioctl= fop_ioctl,
};
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);
printk(KERN_INFO PFX "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;
printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n");
alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,
NULL);
if (!alim7101_pmu) {
printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n");
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) {
printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n");
goto err_out;
}
pci_read_config_byte(ali1543_south, 0x5e, &tmp);
pci_dev_put(ali1543_south);
if ((tmp & 0x1e) == 0x00) {
if (!use_gpio) {
printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n");
goto err_out;
}
nowayout = 1;
} else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
goto err_out;
}
if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
{
timeout = WATCHDOG_TIMEOUT;
printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
timeout);
}
rc = misc_register(&wdt_miscdev);
if (rc) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
wdt_miscdev.minor, rc);
goto err_out;
}
rc = register_reboot_notifier(&wdt_notifier);
if (rc) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_miscdev;
}
if (nowayout) {
__module_get(THIS_MODULE);
}
printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
return 0;
err_out_miscdev:
misc_deregister(&wdt_miscdev);
err_out:
pci_dev_put(alim7101_pmu);
return rc;
}
module_init(alim7101_wdt_init);
module_exit(alim7101_wdt_unload);
static struct pci_device_id alim7101_pci_tbl[] __devinitdata = {
{ 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);

View File

@@ -0,0 +1,386 @@
/*
* 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.
*/
#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>
#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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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_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;
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;
}
static struct watchdog_info at32_wdt_info = {
.identity = "at32ap700x watchdog",
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
};
/*
* Handle commands from user-space.
*/
static int at32_wdt_ioctl(struct inode *inode, 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_KEEPALIVE:
at32_wdt_pat();
ret = 0;
break;
case WDIOC_GETSUPPORT:
ret = copy_to_user(argp, &at32_wdt_info,
sizeof(at32_wdt_info)) ? -EFAULT : 0;
break;
case WDIOC_SETTIMEOUT:
ret = get_user(time, p);
if (ret)
break;
ret = at32_wdt_settimeout(time);
if (ret)
break;
/* Enable new time value */
at32_wdt_start();
/* fall through */
case WDIOC_GETTIMEOUT:
ret = put_user(wdt->timeout, p);
break;
case WDIOC_GETSTATUS: /* fall through */
case WDIOC_GETBOOTSTATUS:
ret = put_user(0, 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;
}
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,
.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, regs->end - regs->start + 1);
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->users = 0;
wdt->miscdev.minor = WATCHDOG_MINOR;
wdt->miscdev.name = "watchdog";
wdt->miscdev.fops = &at32_wdt_fops;
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_iounmap;
}
platform_set_drvdata(pdev, wdt);
wdt->miscdev.parent = &pdev->dev;
dev_info(&pdev->dev,
"AT32AP700X WDT at 0x%p, timeout %d sec (nowayout=%d)\n",
wdt->regs, wdt->timeout, nowayout);
return 0;
err_iounmap:
iounmap(wdt->regs);
err_free:
kfree(wdt);
wdt = NULL;
return ret;
}
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
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 <hcegtvedt@atmel.com>");
MODULE_DESCRIPTION("Watchdog driver for Atmel AT32AP700X");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,288 @@
/*
* 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.
*/
#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 <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/arch/at91_st.h>
#define WDT_DEFAULT_TIME 5 /* seconds */
#define WDT_MAX_TIME 256 /* seconds */
static int wdt_time = WDT_DEFAULT_TIME;
static int nowayout = WATCHDOG_NOWAYOUT;
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, int, 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 void inline at91_wdt_stop(void)
{
at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN);
}
/*
* Enable and reset the watchdog.
*/
static void inline at91_wdt_start(void)
{
at91_sys_write(AT91_ST_WDMR, AT91_ST_EXTEN | AT91_ST_RSTEN | (((65536 * wdt_time) >> 8) & AT91_ST_WDV));
at91_sys_write(AT91_ST_CR, AT91_ST_WDRST);
}
/*
* Reload the watchdog timer. (ie, pat the watchdog)
*/
static void inline at91_wdt_reload(void)
{
at91_sys_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)
{
if (!nowayout)
at91_wdt_stop(); /* Disable the watchdog when file is closed */
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 = 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 at91_wdt_start() is called. */
wdt_time = new_time;
return 0;
}
static struct watchdog_info at91_wdt_info = {
.identity = "at91 watchdog",
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
};
/*
* Handle commands from user-space.
*/
static int at91_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_value;
switch(cmd) {
case WDIOC_KEEPALIVE:
at91_wdt_reload(); /* pat the watchdog */
return 0;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &at91_wdt_info, sizeof(at91_wdt_info)) ? -EFAULT : 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_value, p))
return -EFAULT;
if (at91_wdt_settimeout(new_value))
return -EINVAL;
/* Enable new time value */
at91_wdt_start();
/* Return current value */
return put_user(wdt_time, p);
case WDIOC_GETTIMEOUT:
return put_user(wdt_time, p);
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_SETOPTIONS:
if (get_user(new_value, p))
return -EFAULT;
if (new_value & WDIOS_DISABLECARD)
at91_wdt_stop();
if (new_value & WDIOS_ENABLECARD)
at91_wdt_start();
return 0;
default:
return -ENOTTY;
}
}
/*
* 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,
.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)
{
int res;
if (at91wdt_miscdev.parent)
return -EBUSY;
at91wdt_miscdev.parent = &pdev->dev;
res = misc_register(&at91wdt_miscdev);
if (res)
return res;
printk("AT91 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", 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 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 = __exit_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("at91_wdt: 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);

490
drivers/watchdog/bfin_wdt.c Normal file
View File

@@ -0,0 +1,490 @@
/*
* Blackfin On-Chip Watchdog Driver
* Supports BF53[123]/BF53[467]/BF54[2489]/BF561
*
* Originally based on softdog.c
* Copyright 2006-2007 Analog Devices Inc.
* Copyright 2006-2007 Michele d'Amico
* Copyright 1996 Alan Cox <alan@redhat.com>
*
* Enter bugs at http://blackfin.uclinux.org/
*
* Licensed under the GPL-2 or later.
*/
#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/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/blackfin.h>
#include <asm/uaccess.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"
#define PFX WATCHDOG_NAME ": "
/* The BF561 has two watchdogs (one per core), but since Linux
* only runs on core A, we'll just work with that one.
*/
#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
/* Bit in SWRST that indicates boot caused by watchdog */
#define SWRST_RESET_WDOG 0x4000
/* Bit in WDOG_CTL that indicates watchdog has expired (WDR0) */
#define WDOG_EXPIRED 0x8000
/* Masks for WDEV field in WDOG_CTL register */
#define ICTL_RESET 0x0
#define ICTL_NMI 0x2
#define ICTL_GPI 0x4
#define ICTL_NONE 0x6
#define ICTL_MASK 0x6
/* Masks for WDEN field in WDOG_CTL register */
#define WDEN_MASK 0x0FF0
#define WDEN_ENABLE 0x0000
#define WDEN_DISABLE 0x0AD0
/* some defaults */
#define WATCHDOG_TIMEOUT 20
static unsigned int timeout = WATCHDOG_TIMEOUT;
static int nowayout = WATCHDOG_NOWAYOUT;
static struct watchdog_info bfin_wdt_info;
static unsigned long open_check;
static char expect_close;
static spinlock_t bfin_wdt_spinlock = SPIN_LOCK_UNLOCKED;
/**
* 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;
unsigned long flags;
stampit();
cnt = t * get_sclk();
if (cnt < get_sclk()) {
printk(KERN_WARNING PFX "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 {
printk(KERN_CRIT PFX "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
* @inode: inode of device
* @file: file handle of device
* @cmd: watchdog command
* @arg: argument
*
* Query basic information from the device or ping it, as outlined by the
* watchdog API.
*/
static int bfin_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
stampit();
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &bfin_wdt_info, sizeof(bfin_wdt_info)))
return -EFAULT;
else
return 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(!!(_bfin_swrst & SWRST_RESET_WDOG), p);
case WDIOC_KEEPALIVE:
bfin_wdt_keepalive();
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);
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;
}
}
}
/**
* bfin_wdt_notify_sys - Notifier Handler
* @this: notifier block
* @code: notifier event
* @unused: unused
*
* Handles specific events, such as turning off the watchdog during a
* shutdown event.
*/
static int bfin_wdt_notify_sys(struct notifier_block *this, unsigned long code,
void *unused)
{
stampit();
if (code == SYS_DOWN || code == SYS_HALT)
bfin_wdt_stop();
return NOTIFY_DONE;
}
#ifdef CONFIG_PM
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 struct platform_device bfin_wdt_device = {
.name = WATCHDOG_NAME,
.id = -1,
};
static struct platform_driver bfin_wdt_driver = {
.driver = {
.name = WATCHDOG_NAME,
.owner = THIS_MODULE,
},
.suspend = bfin_wdt_suspend,
.resume = bfin_wdt_resume,
};
static struct file_operations bfin_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = bfin_wdt_write,
.ioctl = bfin_wdt_ioctl,
.open = bfin_wdt_open,
.release = bfin_wdt_release,
};
static struct miscdevice bfin_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &bfin_wdt_fops,
};
static struct watchdog_info bfin_wdt_info = {
.identity = "Blackfin Watchdog",
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
};
static struct notifier_block bfin_wdt_notifier = {
.notifier_call = bfin_wdt_notify_sys,
};
/**
* bfin_wdt_init - Initialize module
*
* Registers the device and notifier handler. Actual device
* initialization is handled by bfin_wdt_open().
*/
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_device_register(&bfin_wdt_device);
if (ret)
return ret;
ret = platform_driver_probe(&bfin_wdt_driver, NULL);
if (ret)
return ret;
ret = register_reboot_notifier(&bfin_wdt_notifier);
if (ret) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret);
return ret;
}
ret = misc_register(&bfin_wdt_miscdev);
if (ret) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&bfin_wdt_notifier);
return ret;
}
printk(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
return 0;
}
/**
* bfin_wdt_exit - Deinitialize module
*
* Unregisters the device and notifier handler. Actual device
* deinitialization is handled by bfin_wdt_close().
*/
static void __exit bfin_wdt_exit(void)
{
misc_deregister(&bfin_wdt_miscdev);
unregister_reboot_notifier(&bfin_wdt_notifier);
}
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, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

View File

@@ -0,0 +1,194 @@
/*
* drivers/char/watchdog/booke_wdt.c
*
* Watchdog timer for PowerPC Book-E systems
*
* Author: Matthew McClintock
* Maintainer: Kumar Gala <galak@kernel.crashing.org>
*
* Copyright 2005 Freescale Semiconductor Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/notifier.h>
#include <linux/watchdog.h>
#include <asm/reg_booke.h>
#include <asm/uaccess.h>
#include <asm/system.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.
*/
#ifdef CONFIG_FSL_BOOKE
#define WDT_PERIOD_DEFAULT 63 /* Ex. wdt_period=28 bus=333Mhz , reset=~40sec */
#else
#define WDT_PERIOD_DEFAULT 3 /* Refer to the PPC40x and PPC4xx manuals */
#endif /* for timing information */
u32 booke_wdt_enabled = 0;
u32 booke_wdt_period = WDT_PERIOD_DEFAULT;
#ifdef CONFIG_FSL_BOOKE
#define WDTP(x) ((((63-x)&0x3)<<30)|(((63-x)&0x3c)<<15))
#else
#define WDTP(x) (TCR_WP(x))
#endif
/*
* booke_wdt_ping:
*/
static __inline__ void booke_wdt_ping(void)
{
mtspr(SPRN_TSR, TSR_ENW|TSR_WIS);
}
/*
* booke_wdt_enable:
*/
static __inline__ void booke_wdt_enable(void)
{
u32 val;
/* clear status before enabling watchdog */
booke_wdt_ping();
val = mfspr(SPRN_TCR);
val |= (TCR_WIE|TCR_WRC(WRC_CHIP)|WDTP(booke_wdt_period));
mtspr(SPRN_TCR, val);
}
/*
* booke_wdt_write:
*/
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,
.firmware_version = 0,
.identity = "PowerPC Book-E Watchdog",
};
/*
* booke_wdt_ioctl:
*/
static int booke_wdt_ioctl (struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
u32 tmp = 0;
u32 __user *p = (u32 __user *)arg;
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user ((struct watchdog_info __user *) arg, &ident,
sizeof(struct watchdog_info)))
return -EFAULT;
case WDIOC_GETSTATUS:
return put_user(ident.options, p);
case WDIOC_GETBOOTSTATUS:
/* XXX: something is clearing TSR */
tmp = mfspr(SPRN_TSR) & TSR_WRS(3);
/* returns 1 if last reset was caused by the WDT */
return (tmp ? 1 : 0);
case WDIOC_KEEPALIVE:
booke_wdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(booke_wdt_period, p))
return -EFAULT;
mtspr(SPRN_TCR, (mfspr(SPRN_TCR)&~WDTP(0))|WDTP(booke_wdt_period));
return 0;
case WDIOC_GETTIMEOUT:
return put_user(booke_wdt_period, p);
case WDIOC_SETOPTIONS:
if (get_user(tmp, p))
return -EINVAL;
if (tmp == WDIOS_ENABLECARD) {
booke_wdt_ping();
break;
} else
return -EINVAL;
return 0;
default:
return -ENOTTY;
}
return 0;
}
/*
* booke_wdt_open:
*/
static int booke_wdt_open (struct inode *inode, struct file *file)
{
if (booke_wdt_enabled == 0) {
booke_wdt_enabled = 1;
booke_wdt_enable();
printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n",
booke_wdt_period);
}
return nonseekable_open(inode, file);
}
static const struct file_operations booke_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = booke_wdt_write,
.ioctl = booke_wdt_ioctl,
.open = booke_wdt_open,
};
static struct 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);
}
/*
* booke_wdt_init:
*/
static int __init booke_wdt_init(void)
{
int ret = 0;
printk (KERN_INFO "PowerPC Book-E Watchdog Timer Loaded\n");
ident.firmware_version = cur_cpu_spec->pvr_value;
ret = misc_register(&booke_wdt_miscdev);
if (ret) {
printk (KERN_CRIT "Cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
return ret;
}
if (booke_wdt_enabled == 1) {
printk (KERN_INFO "PowerPC Book-E Watchdog Timer Enabled (wdt_period=%d)\n",
booke_wdt_period);
booke_wdt_enable();
}
return ret;
}
device_initcall(booke_wdt_init);

304
drivers/watchdog/cpu5wdt.c Normal file
View File

@@ -0,0 +1,304 @@
/*
* 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.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/timer.h>
#include <linux/completion.h>
#include <linux/jiffies.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/watchdog.h>
/* adjustable parameters */
static int verbose = 0;
static int port = 0x91;
static int ticks = 10000;
#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;
volatile int running;
struct timer_list timer;
volatile int queue;
int default_ticks;
unsigned long inuse;
} cpu5wdt_device;
/* generic helper functions */
static void cpu5wdt_trigger(unsigned long unused)
{
if ( verbose > 2 )
printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks);
if( cpu5wdt_device.running )
ticks--;
/* 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);
}
}
static void cpu5wdt_reset(void)
{
ticks = cpu5wdt_device.default_ticks;
if ( verbose )
printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks);
}
static void cpu5wdt_start(void)
{
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++;
}
static int cpu5wdt_stop(void)
{
if ( cpu5wdt_device.running )
cpu5wdt_device.running = 0;
ticks = cpu5wdt_device.default_ticks;
if ( verbose )
printk(KERN_CRIT PFX "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 int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
unsigned int value;
static struct watchdog_info ident =
{
.options = WDIOF_CARDRESET,
.identity = "CPU5 WDT",
};
switch(cmd) {
case WDIOC_KEEPALIVE:
cpu5wdt_reset();
break;
case WDIOC_GETSTATUS:
value = inb(port + CPU5WDT_STATUS_REG);
value = (value >> 2) & 1;
if ( copy_to_user(argp, &value, sizeof(int)) )
return -EFAULT;
break;
case WDIOC_GETBOOTSTATUS:
if ( copy_to_user(argp, &value, sizeof(int)) )
return -EFAULT;
break;
case WDIOC_GETSUPPORT:
if ( copy_to_user(argp, &ident, sizeof(ident)) )
return -EFAULT;
break;
case WDIOC_SETOPTIONS:
if ( copy_from_user(&value, argp, sizeof(int)) )
return -EFAULT;
switch(value) {
case WDIOS_ENABLECARD:
cpu5wdt_start();
break;
case WDIOS_DISABLECARD:
return cpu5wdt_stop();
default:
return -EINVAL;
}
break;
default:
return -ENOTTY;
}
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,
.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 )
printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose);
if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) {
printk(KERN_ERR PFX "request_region failed\n");
err = -EBUSY;
goto no_port;
}
if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) {
printk(KERN_ERR PFX "misc_register failed\n");
goto no_misc;
}
/* watchdog reboot? */
val = inb(port + CPU5WDT_STATUS_REG);
val = (val >> 2) & 1;
if ( !val )
printk(KERN_INFO PFX "sorry, was my fault\n");
init_completion(&cpu5wdt_device.stop);
cpu5wdt_device.queue = 0;
clear_bit(0, &cpu5wdt_device.inuse);
setup_timer(&cpu5wdt_device.timer, cpu5wdt_trigger, 0);
cpu5wdt_device.default_ticks = ticks;
printk(KERN_INFO PFX "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");

View File

@@ -0,0 +1,281 @@
/*
* 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 <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.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 spinlock_t io_lock;
static unsigned long wdt_status;
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
#define WDT_REGION_INITED 2
#define WDT_DEVICE_INITED 3
static struct resource *wdt_mem;
static void __iomem *wdt_base;
static void wdt_service(void)
{
spin_lock(&io_lock);
/* put watchdog in service state */
davinci_writel(WDKEY_SEQ0, wdt_base + WDTCR);
/* put watchdog in active state */
davinci_writel(WDKEY_SEQ1, wdt_base + WDTCR);
spin_unlock(&io_lock);
}
static void wdt_enable(void)
{
u32 tgcr;
u32 timer_margin;
spin_lock(&io_lock);
/* disable, internal clock source */
davinci_writel(0, wdt_base + TCR);
/* reset timer, set mode to 64-bit watchdog, and unreset */
davinci_writel(0, wdt_base + TGCR);
tgcr = TIMMODE_64BIT_WDOG | TIM12RS_UNRESET | TIM34RS_UNRESET;
davinci_writel(tgcr, wdt_base + TGCR);
/* clear counter regs */
davinci_writel(0, wdt_base + TIM12);
davinci_writel(0, wdt_base + TIM34);
/* set timeout period */
timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) & 0xffffffff);
davinci_writel(timer_margin, wdt_base + PRD12);
timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) >> 32);
davinci_writel(timer_margin, wdt_base + PRD34);
/* enable run continuously */
davinci_writel(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 */
davinci_writel(WDKEY_SEQ0 | WDEN, wdt_base + WDTCR);
/* put watchdog in active state */
davinci_writel(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 struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING,
.identity = "DaVinci Watchdog",
};
static int
davinci_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOTTY;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user((struct watchdog_info *)arg, &ident,
sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
ret = put_user(0, (int *)arg);
break;
case WDIOC_GETTIMEOUT:
ret = put_user(heartbeat, (int *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_service();
ret = 0;
break;
}
return ret;
}
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,
.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 davinci_wdt_probe(struct platform_device *pdev)
{
int ret = 0, size;
struct resource *res;
spin_lock_init(&io_lock);
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
heartbeat = DEFAULT_HEARTBEAT;
printk(KERN_INFO MODULE_NAME
"DaVinci Watchdog Timer: heartbeat %d sec\n", heartbeat);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
printk(KERN_INFO MODULE_NAME
"failed to get memory region resource\n");
return -ENOENT;
}
size = res->end - res->start + 1;
wdt_mem = request_mem_region(res->start, size, pdev->name);
if (wdt_mem == NULL) {
printk(KERN_INFO MODULE_NAME "failed to get memory region\n");
return -ENOENT;
}
wdt_base = (void __iomem *)(res->start);
ret = misc_register(&davinci_wdt_miscdev);
if (ret < 0) {
printk(KERN_ERR MODULE_NAME "cannot register misc device\n");
release_resource(wdt_mem);
kfree(wdt_mem);
} else {
set_bit(WDT_DEVICE_INITED, &wdt_status);
}
return ret;
}
static int davinci_wdt_remove(struct platform_device *pdev)
{
misc_deregister(&davinci_wdt_miscdev);
if (wdt_mem) {
release_resource(wdt_mem);
kfree(wdt_mem);
wdt_mem = NULL;
}
return 0;
}
static struct platform_driver platform_wdt_driver = {
.driver = {
.name = "watchdog",
},
.probe = davinci_wdt_probe,
.remove = davinci_wdt_remove,
};
static int __init davinci_wdt_init(void)
{
return platform_driver_register(&platform_wdt_driver);
}
static void __exit davinci_wdt_exit(void)
{
return platform_driver_unregister(&platform_wdt_driver);
}
module_init(davinci_wdt_init);
module_exit(davinci_wdt_exit);
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);

View File

@@ -0,0 +1,253 @@
/*
* 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>
*
* 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/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/timer.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#define WDT_VERSION "0.3"
#define PFX "ep93xx_wdt: "
/* default timeout (secs) */
#define WDT_TIMEOUT 30
static int nowayout = WATCHDOG_NOWAYOUT;
static int timeout = WDT_TIMEOUT;
static struct timer_list timer;
static unsigned long next_heartbeat;
static unsigned long wdt_status;
static unsigned long boot_status;
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
#define EP93XX_WDT_REG(x) (EP93XX_WATCHDOG_BASE + (x))
#define EP93XX_WDT_WATCHDOG EP93XX_WDT_REG(0x00)
#define EP93XX_WDT_WDSTATUS EP93XX_WDT_REG(0x04)
/* reset the wdt every ~200ms */
#define WDT_INTERVAL (HZ/5)
static void wdt_enable(void)
{
__raw_writew(0xaaaa, EP93XX_WDT_WATCHDOG);
}
static void wdt_disable(void)
{
__raw_writew(0xaa55, EP93XX_WDT_WATCHDOG);
}
static inline void wdt_ping(void)
{
__raw_writew(0x5555, EP93XX_WDT_WATCHDOG);
}
static void wdt_startup(void)
{
next_heartbeat = jiffies + (timeout * HZ);
wdt_enable();
mod_timer(&timer, jiffies + WDT_INTERVAL);
}
static void wdt_shutdown(void)
{
del_timer_sync(&timer);
wdt_disable();
}
static void wdt_keepalive(void)
{
/* user land ping */
next_heartbeat = jiffies + (timeout * HZ);
}
static int ep93xx_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(WDT_IN_USE, &wdt_status))
return -EBUSY;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
wdt_startup();
return nonseekable_open(inode, file);
}
static ssize_t
ep93xx_wdt_write(struct file *file, const char __user *data, size_t len,
loff_t *ppos)
{
if (len) {
if (!nowayout) {
size_t i;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
set_bit(WDT_OK_TO_CLOSE, &wdt_status);
else
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
}
}
wdt_keepalive();
}
return len;
}
static struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE,
.identity = "EP93xx Watchdog",
};
static int
ep93xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOTTY;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user((struct watchdog_info __user *)arg, &ident,
sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int __user *)arg);
break;
case WDIOC_GETBOOTSTATUS:
ret = put_user(boot_status, (int __user *)arg);
break;
case WDIOC_GETTIMEOUT:
/* actually, it is 0.250 seconds.... */
ret = put_user(1, (int __user *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_keepalive();
ret = 0;
break;
}
return ret;
}
static int ep93xx_wdt_release(struct inode *inode, struct file *file)
{
if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
wdt_shutdown();
else
printk(KERN_CRIT PFX "Device closed unexpectedly - "
"timer will not stop\n");
clear_bit(WDT_IN_USE, &wdt_status);
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
return 0;
}
static const struct file_operations ep93xx_wdt_fops = {
.owner = THIS_MODULE,
.write = ep93xx_wdt_write,
.ioctl = ep93xx_wdt_ioctl,
.open = ep93xx_wdt_open,
.release = ep93xx_wdt_release,
};
static struct miscdevice ep93xx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &ep93xx_wdt_fops,
};
static void ep93xx_timer_ping(unsigned long data)
{
if (time_before(jiffies, next_heartbeat))
wdt_ping();
/* Re-set the timer interval */
mod_timer(&timer, jiffies + WDT_INTERVAL);
}
static int __init ep93xx_wdt_init(void)
{
int err;
err = misc_register(&ep93xx_wdt_miscdev);
boot_status = __raw_readl(EP93XX_WDT_WATCHDOG) & 0x01 ? 1 : 0;
printk(KERN_INFO PFX "EP93XX watchdog, driver version "
WDT_VERSION "%s\n",
(__raw_readl(EP93XX_WDT_WATCHDOG) & 0x08)
? " (nCS1 disable detected)" : "");
if (timeout < 1 || timeout > 3600) {
timeout = WDT_TIMEOUT;
printk(KERN_INFO PFX
"timeout value must be 1<=x<=3600, using %d\n",
timeout);
}
setup_timer(&timer, ep93xx_timer_ping, 1);
return err;
}
static void __exit ep93xx_wdt_exit(void)
{
wdt_shutdown();
misc_deregister(&ep93xx_wdt_miscdev);
}
module_init(ep93xx_wdt_init);
module_exit(ep93xx_wdt_exit);
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>,"
"Alessandro Zummo <a.zummo@towertech.it>");
MODULE_DESCRIPTION("EP93xx Watchdog");
MODULE_LICENSE("GPL");
MODULE_VERSION(WDT_VERSION);
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,473 @@
/*
* 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@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>*
*/
/* 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.
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
static unsigned long eurwdt_is_open;
static int eurwdt_timeout;
static char eur_expect_close;
/*
* You must set these - there is no sane way to probe for this board.
* You can use eurwdt=x,y to set these now.
*/
static int io = 0x3f0;
static int irq = 10;
static char *ev = "int";
#define WDT_TIMEOUT 60 /* 1 minute */
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/*
* 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) {
printk(KERN_ERR ": invalid irq number\n");
irq = 0; /* if invalid we disable interrupt */
}
if (irq == 0)
printk(KERN_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)
{
printk(KERN_CRIT "timeout WDT timeout\n");
#ifdef ONLY_TESTING
printk(KERN_CRIT "Would Reboot.\n");
#else
printk(KERN_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;
}
}
eurwdt_ping(); /* the default timeout */
}
return count;
}
/**
* eurwdt_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 int eurwdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "WDT Eurotech CPU-1220/1410",
};
int time;
int options, retval = -EINVAL;
switch(cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
eurwdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (copy_from_user(&time, p, sizeof(int)))
return -EFAULT;
/* Sanity check */
if (time < 0 || time > 255)
return -EINVAL;
eurwdt_timeout = time;
eurwdt_set_timeout(time);
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(eurwdt_timeout, p);
case WDIOC_SETOPTIONS:
if (get_user(options, p))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
eurwdt_disable_timer();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
eurwdt_activate_timer();
eurwdt_ping();
retval = 0;
}
return retval;
}
}
/**
* 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 {
printk(KERN_CRIT "eurwdt: 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) {
/* Turn the card off */
eurwdt_disable_timer();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations eurwdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = eurwdt_write,
.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, IRQF_DISABLED, "eurwdt", NULL);
if(ret) {
printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq);
goto out;
}
if (!request_region(io, 2, "eurwdt")) {
printk(KERN_ERR "eurwdt: IO %X is not free.\n", io);
ret = -EBUSY;
goto outirq;
}
ret = register_reboot_notifier(&eurwdt_notifier);
if (ret) {
printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret);
goto outreg;
}
ret = misc_register(&eurwdt_miscdev);
if (ret) {
printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n",
WATCHDOG_MINOR);
goto outreboot;
}
eurwdt_unlock_chip();
ret = 0;
printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)"
" - timeout event: %s\n",
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);

527
drivers/watchdog/i6300esb.c Normal file
View File

@@ -0,0 +1,527 @@
/*
* 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-003
*
* 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, ...
*/
#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/pci.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* Module and version information */
#define ESB_VERSION "0.03"
#define ESB_MODULE_NAME "i6300ESB timer"
#define ESB_DRIVER_NAME ESB_MODULE_NAME ", v" ESB_VERSION
#define PFX ESB_MODULE_NAME ": "
/* PCI configuration registers */
#define ESB_CONFIG_REG 0x60 /* Config register */
#define ESB_LOCK_REG 0x68 /* WDT lock register */
/* Memory mapped registers */
#define ESB_TIMER1_REG BASEADDR + 0x00 /* Timer1 value after each reset */
#define ESB_TIMER2_REG BASEADDR + 0x04 /* Timer2 value after each reset */
#define ESB_GINTSR_REG BASEADDR + 0x08 /* General Interrupt Status Register */
#define ESB_RELOAD_REG BASEADDR + 0x0c /* Reload register */
/* 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 ( 0x11 << 0 ) /* Interrupt type on timer1 timeout */
/* Reload register bits */
#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 spinlock_t 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;
/* module parameters */
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (1<heartbeat<2*1023) */
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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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) {
writeb(ESB_UNLOCK1, ESB_RELOAD_REG);
writeb(ESB_UNLOCK2, ESB_RELOAD_REG);
}
static void esb_timer_start(void)
{
u8 val;
/* Enable or Enable + Lock? */
val = 0x02 | (nowayout ? 0x01 : 0x00);
pci_write_config_byte(esb_pci, ESB_LOCK_REG, val);
}
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 & 0x01);
}
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;
}
static int esb_timer_read (void)
{
u32 count;
/* This isn't documented, and doesn't take into
* acount which stage is running, but it looks
* like a 20 bit count down, so we might as well report it.
*/
pci_read_config_dword(esb_pci, 0x64, &count);
return (int)count;
}
/*
* /dev/watchdog handling
*/
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_keepalive ();
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 {
printk(KERN_CRIT PFX "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 int esb_ioctl (struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_options, retval = -EINVAL;
int new_heartbeat;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
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 (esb_timer_read(), p);
case WDIOC_GETBOOTSTATUS:
return put_user (triggered, p);
case WDIOC_KEEPALIVE:
esb_timer_keepalive ();
return 0;
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_keepalive ();
esb_timer_start ();
retval = 0;
}
return retval;
}
case WDIOC_SETTIMEOUT:
{
if (get_user(new_heartbeat, p))
return -EFAULT;
if (esb_timer_set_heartbeat(new_heartbeat))
return -EINVAL;
esb_timer_keepalive ();
/* Fall */
}
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p);
default:
return -ENOTTY;
}
}
/*
* Notify system
*/
static int esb_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
{
if (code==SYS_DOWN || code==SYS_HALT) {
/* Turn the WDT off */
esb_timer_stop ();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations esb_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = esb_write,
.ioctl = esb_ioctl,
.open = esb_open,
.release = esb_release,
};
static struct miscdevice esb_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &esb_fops,
};
static struct notifier_block esb_notifier = {
.notifier_call = esb_notify_sys,
};
/*
* Data for PCI driver interface
*
* This data only exists for exporting the supported
* PCI ids via MODULE_DEVICE_TABLE. We do not actually
* register a pci_driver, because someone else might one day
* want to register another driver on the same PCI id.
*/
static struct pci_device_id esb_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_9), },
{ 0, }, /* End of list */
};
MODULE_DEVICE_TABLE (pci, esb_pci_tbl);
/*
* Init & exit routines
*/
static unsigned char __init esb_getdevice (void)
{
u8 val1;
unsigned short val2;
struct pci_dev *dev = NULL;
/*
* Find the PCI device
*/
for_each_pci_dev(dev) {
if (pci_match_id(esb_pci_tbl, dev)) {
esb_pci = dev;
break;
}
}
if (esb_pci) {
if (pci_enable_device(esb_pci)) {
printk (KERN_ERR PFX "failed to enable device\n");
goto err_devput;
}
if (pci_request_region(esb_pci, 0, ESB_MODULE_NAME)) {
printk (KERN_ERR PFX "failed to request region\n");
goto err_disable;
}
BASEADDR = ioremap(pci_resource_start(esb_pci, 0),
pci_resource_len(esb_pci, 0));
if (BASEADDR == NULL) {
/* Something's wrong here, BASEADDR has to be set */
printk (KERN_ERR PFX "failed to get BASEADDR\n");
goto err_release;
}
/*
* The watchdog has two timers, it can be setup so that the
* expiry of timer1 results in an interrupt and the expiry of
* timer2 results in a reboot. We set it to not generate
* any interrupts as there is not much we can do with it
* right now.
*
* We also enable reboots and set the timer frequency to
* the PCI clock divided by 2^15 (approx 1KHz).
*/
pci_write_config_word(esb_pci, ESB_CONFIG_REG, 0x0003);
/* Check that the WDT isn't already locked */
pci_read_config_byte(esb_pci, ESB_LOCK_REG, &val1);
if (val1 & ESB_WDT_LOCK)
printk (KERN_WARNING PFX "nowayout already set\n");
/* Set the timer to watchdog mode and disable it for now */
pci_write_config_byte(esb_pci, ESB_LOCK_REG, 0x00);
/* Check if the watchdog was previously triggered */
esb_unlock_registers();
val2 = readw(ESB_RELOAD_REG);
triggered = (val2 & (0x01 << 9) >> 9);
/* Reset trigger flag and timers */
esb_unlock_registers();
writew((0x11 << 8), ESB_RELOAD_REG);
/* Done */
return 1;
err_release:
pci_release_region(esb_pci, 0);
err_disable:
pci_disable_device(esb_pci);
err_devput:
pci_dev_put(esb_pci);
}
return 0;
}
static int __init watchdog_init (void)
{
int ret;
spin_lock_init(&esb_lock);
/* Check whether or not the hardware watchdog is there */
if (!esb_getdevice () || esb_pci == NULL)
return -ENODEV;
/* Check that the heartbeat value is within it's range ; if not reset to the default */
if (esb_timer_set_heartbeat (heartbeat)) {
esb_timer_set_heartbeat (WATCHDOG_HEARTBEAT);
printk(KERN_INFO PFX "heartbeat value must be 1<heartbeat<2046, using %d\n",
heartbeat);
}
ret = register_reboot_notifier(&esb_notifier);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto err_unmap;
}
ret = misc_register(&esb_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto err_notifier;
}
esb_timer_stop ();
printk (KERN_INFO PFX "initialized (0x%p). heartbeat=%d sec (nowayout=%d)\n",
BASEADDR, heartbeat, nowayout);
return 0;
err_notifier:
unregister_reboot_notifier(&esb_notifier);
err_unmap:
iounmap(BASEADDR);
/* err_release: */
pci_release_region(esb_pci, 0);
/* err_disable: */
pci_disable_device(esb_pci);
/* err_devput: */
pci_dev_put(esb_pci);
return ret;
}
static void __exit watchdog_cleanup (void)
{
/* Stop the timer before we leave */
if (!nowayout)
esb_timer_stop ();
/* Deregister */
misc_deregister(&esb_miscdev);
unregister_reboot_notifier(&esb_notifier);
iounmap(BASEADDR);
pci_release_region(esb_pci, 0);
pci_disable_device(esb_pci);
pci_dev_put(esb_pci);
}
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);

View File

@@ -0,0 +1,307 @@
/*
* intel TCO vendor specific watchdog driver support
*
* (c) Copyright 2006 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, ...
*/
/* Module and version information */
#define DRV_NAME "iTCO_vendor_support"
#define DRV_VERSION "1.01"
#define DRV_RELDATE "11-Nov-2006"
#define PFX DRV_NAME ": "
/* 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 <asm/io.h> /* For inb/outb/... */
/* iTCO defines */
#define SMI_EN acpibase + 0x30 /* SMI Control and Enable Register */
#define TCOBASE acpibase + 0x60 /* TCO base address */
#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */
/* List of vendor support modes */
#define SUPERMICRO_OLD_BOARD 1 /* SuperMicro Pentium 3 Era 370SSE+-OEM1/P3TSSE */
#define SUPERMICRO_NEW_BOARD 2 /* SuperMicro Pentium 4 / Xeon 4 / EMT64T Era Systems */
static int vendorsupport = 0;
module_param(vendorsupport, int, 0);
MODULE_PARM_DESC(vendorsupport, "iTCO vendor specific support mode, default=0 (none), 1=SuperMicro Pent3, 2=SuperMicro Pent4+");
/*
* 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;
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;
val32 = inl(SMI_EN);
val32 &= 0x00002000; /* Turn on SMI clearing watchdog */
outl(val32, SMI_EN); /* Needed to deactivate watchdog */
}
static void supermicro_old_pre_keepalive(unsigned long acpibase)
{
/* Reload TCO Timer (done in iTCO_wdt_keepalive) + */
/* Clear "Expire Flag" (Bit 3 of TC01_STS register) */
outb(0x08, TCO1_STS);
}
/*
* 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 435”). 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)
{
outb(SM_WATCHPAGE, SM_REGINDEX); /* Write 0x87 to port 0x2e twice */
outb(SM_WATCHPAGE, SM_REGINDEX);
outb(SM_CTLPAGESW, SM_REGINDEX); /* Switch to watchdog control page */
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();
}
/*
* Generic Support Functions
*/
void iTCO_vendor_pre_start(unsigned long acpibase,
unsigned int heartbeat)
{
if (vendorsupport == SUPERMICRO_OLD_BOARD)
supermicro_old_pre_start(acpibase);
else if (vendorsupport == SUPERMICRO_NEW_BOARD)
supermicro_new_pre_start(heartbeat);
}
EXPORT_SYMBOL(iTCO_vendor_pre_start);
void iTCO_vendor_pre_stop(unsigned long acpibase)
{
if (vendorsupport == SUPERMICRO_OLD_BOARD)
supermicro_old_pre_stop(acpibase);
else if (vendorsupport == SUPERMICRO_NEW_BOARD)
supermicro_new_pre_stop();
}
EXPORT_SYMBOL(iTCO_vendor_pre_stop);
void iTCO_vendor_pre_keepalive(unsigned long acpibase, unsigned int heartbeat)
{
if (vendorsupport == SUPERMICRO_OLD_BOARD)
supermicro_old_pre_keepalive(acpibase);
else 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)
{
printk (KERN_INFO PFX "vendor-support=%d\n", vendorsupport);
return 0;
}
static void __exit iTCO_vendor_exit_module(void)
{
printk (KERN_INFO PFX "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");

804
drivers/watchdog/iTCO_wdt.c Normal file
View File

@@ -0,0 +1,804 @@
/*
* intel TCO Watchdog Driver (Used in i82801 and i6300ESB chipsets)
*
* (c) Copyright 2006-2007 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.)
* 82801AA (ICH) : document number 290655-003, 290677-014,
* 82801AB (ICHO) : document number 290655-003, 290677-014,
* 82801BA (ICH2) : document number 290687-002, 298242-027,
* 82801BAM (ICH2-M) : document number 290687-002, 298242-027,
* 82801CA (ICH3-S) : document number 290733-003, 290739-013,
* 82801CAM (ICH3-M) : document number 290716-001, 290718-007,
* 82801DB (ICH4) : document number 290744-001, 290745-020,
* 82801DBM (ICH4-M) : document number 252337-001, 252663-005,
* 82801E (C-ICH) : document number 273599-001, 273645-002,
* 82801EB (ICH5) : document number 252516-001, 252517-003,
* 82801ER (ICH5R) : document number 252516-001, 252517-003,
* 82801FB (ICH6) : document number 301473-002, 301474-007,
* 82801FR (ICH6R) : document number 301473-002, 301474-007,
* 82801FBM (ICH6-M) : document number 301473-002, 301474-007,
* 82801FW (ICH6W) : document number 301473-001, 301474-007,
* 82801FRW (ICH6RW) : document number 301473-001, 301474-007,
* 82801GB (ICH7) : document number 307013-002, 307014-009,
* 82801GR (ICH7R) : document number 307013-002, 307014-009,
* 82801GDH (ICH7DH) : document number 307013-002, 307014-009,
* 82801GBM (ICH7-M) : document number 307013-002, 307014-009,
* 82801GHM (ICH7-M DH) : document number 307013-002, 307014-009,
* 82801HB (ICH8) : document number 313056-002, 313057-004,
* 82801HR (ICH8R) : document number 313056-002, 313057-004,
* 82801HH (ICH8DH) : document number 313056-002, 313057-004,
* 82801HO (ICH8DO) : document number 313056-002, 313057-004,
* 82801IB (ICH9) : document number 316972-001, 316973-001,
* 82801IR (ICH9R) : document number 316972-001, 316973-001,
* 82801IH (ICH9DH) : document number 316972-001, 316973-001,
* 6300ESB (6300ESB) : document number 300641-003, 300884-010,
* 631xESB (631xESB) : document number 313082-001, 313075-005,
* 632xESB (632xESB) : document number 313082-001, 313075-005
*/
/*
* Includes, defines, variables, module parameters, ...
*/
/* Module and version information */
#define DRV_NAME "iTCO_wdt"
#define DRV_VERSION "1.02"
#define DRV_RELDATE "26-Jul-2007"
#define PFX DRV_NAME ": "
/* 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 <asm/uaccess.h> /* For copy_to_user/put_user/... */
#include <asm/io.h> /* For inb/outb/... */
/* TCO related info */
enum iTCO_chipsets {
TCO_ICH = 0, /* ICH */
TCO_ICH0, /* ICH0 */
TCO_ICH2, /* ICH2 */
TCO_ICH2M, /* ICH2-M */
TCO_ICH3, /* ICH3-S */
TCO_ICH3M, /* ICH3-M */
TCO_ICH4, /* ICH4 */
TCO_ICH4M, /* ICH4-M */
TCO_CICH, /* C-ICH */
TCO_ICH5, /* ICH5 & ICH5R */
TCO_6300ESB, /* 6300ESB */
TCO_ICH6, /* ICH6 & ICH6R */
TCO_ICH6M, /* ICH6-M */
TCO_ICH6W, /* ICH6W & ICH6RW */
TCO_ICH7, /* ICH7 & ICH7R */
TCO_ICH7M, /* ICH7-M */
TCO_ICH7MDH, /* ICH7-M DH */
TCO_ICH8, /* ICH8 & ICH8R */
TCO_ICH8DH, /* ICH8DH */
TCO_ICH8DO, /* ICH8DO */
TCO_ICH9, /* ICH9 */
TCO_ICH9R, /* ICH9R */
TCO_ICH9DH, /* ICH9DH */
TCO_631XESB, /* 631xESB/632xESB */
};
static struct {
char *name;
unsigned int iTCO_version;
} iTCO_chipset_info[] __devinitdata = {
{"ICH", 1},
{"ICH0", 1},
{"ICH2", 1},
{"ICH2-M", 1},
{"ICH3-S", 1},
{"ICH3-M", 1},
{"ICH4", 1},
{"ICH4-M", 1},
{"C-ICH", 1},
{"ICH5 or ICH5R", 1},
{"6300ESB", 1},
{"ICH6 or ICH6R", 2},
{"ICH6-M", 2},
{"ICH6W or ICH6RW", 2},
{"ICH7 or ICH7R", 2},
{"ICH7-M", 2},
{"ICH7-M DH", 2},
{"ICH8 or ICH8R", 2},
{"ICH8DH", 2},
{"ICH8DO", 2},
{"ICH9", 2},
{"ICH9R", 2},
{"ICH9DH", 2},
{"631xESB/632xESB", 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 struct pci_device_id iTCO_wdt_pci_tbl[] = {
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH0 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH2 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH2M },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH3 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH3M },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH4 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH4M },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_CICH },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH5 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_6300ESB },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6M },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH6W },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7M },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_31, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH7MDH },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8 },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8DH },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH8DO },
{ PCI_VENDOR_ID_INTEL, 0x2918, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9 },
{ PCI_VENDOR_ID_INTEL, 0x2916, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9R },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_ICH9DH },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2671, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2672, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2673, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2674, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2675, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2676, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2677, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2678, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x2679, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ PCI_VENDOR_ID_INTEL, 0x267f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TCO_631XESB },
{ 0, }, /* End of list */
};
MODULE_DEVICE_TABLE (pci, iTCO_wdt_pci_tbl);
/* Address definitions for the TCO */
#define TCOBASE iTCO_wdt_private.ACPIBASE + 0x60 /* TCO base address */
#define SMI_EN iTCO_wdt_private.ACPIBASE + 0x30 /* SMI Control and Enable Register */
#define TCO_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */
#define TCOv1_TMR TCOBASE + 0x01 /* TCOv1 Timer Initial Value */
#define TCO_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */
#define TCO_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */
#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */
#define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */
#define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */
#define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */
#define TCOv2_TMR TCOBASE + 0x12 /* TCOv2 Timer Initial Value */
/* internal variables */
static unsigned long is_active;
static char expect_release;
static struct { /* this is private data for the iTCO_wdt device */
unsigned int iTCO_version; /* TCO version/generation */
unsigned long ACPIBASE; /* The cards ACPIBASE address (TCOBASE = ACPIBASE+0x60) */
unsigned long __iomem *gcs; /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2) */
spinlock_t io_lock; /* the lock for io operations */
struct pci_dev *pdev; /* the PCI-device */
} iTCO_wdt_private;
static struct platform_device *iTCO_wdt_platform_device; /* the watchdog platform device */
/* module parameters */
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39 (TCO v1) or 613 (TCO v2), default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/* iTCO Vendor Specific Support hooks */
#ifdef CONFIG_ITCO_VENDOR_SUPPORT
extern void iTCO_vendor_pre_start(unsigned long, unsigned int);
extern void iTCO_vendor_pre_stop(unsigned long);
extern void iTCO_vendor_pre_keepalive(unsigned long, unsigned int);
extern void iTCO_vendor_pre_set_heartbeat(unsigned int);
extern int iTCO_vendor_check_noreboot_on(void);
#else
#define iTCO_vendor_pre_start(acpibase, heartbeat) {}
#define iTCO_vendor_pre_stop(acpibase) {}
#define iTCO_vendor_pre_keepalive(acpibase,heartbeat) {}
#define iTCO_vendor_pre_set_heartbeat(heartbeat) {}
#define iTCO_vendor_check_noreboot_on() 1 /* 1=check noreboot; 0=don't check */
#endif
/*
* 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()) {
printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
return -EIO;
}
/* 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) {
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);
/* 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;
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_keepalive();
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 {
printk(KERN_CRIT PFX "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 int iTCO_wdt_ioctl (struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_options, retval = -EINVAL;
int new_heartbeat;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = DRV_NAME,
};
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident,
sizeof (ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
iTCO_wdt_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
if (get_user(new_options, p))
return -EFAULT;
if (new_options & WDIOS_DISABLECARD) {
iTCO_wdt_stop();
retval = 0;
}
if (new_options & WDIOS_ENABLECARD) {
iTCO_wdt_keepalive();
iTCO_wdt_start();
retval = 0;
}
return retval;
}
case WDIOC_SETTIMEOUT:
{
if (get_user(new_heartbeat, p))
return -EFAULT;
if (iTCO_wdt_set_heartbeat(new_heartbeat))
return -EINVAL;
iTCO_wdt_keepalive();
/* Fall */
}
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p);
case WDIOC_GETTIMELEFT:
{
int time_left;
if (iTCO_wdt_get_timeleft(&time_left))
return -EINVAL;
return put_user(time_left, p);
}
default:
return -ENOTTY;
}
}
/*
* Kernel Interfaces
*/
static const struct file_operations iTCO_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = iTCO_wdt_write,
.ioctl = iTCO_wdt_ioctl,
.open = iTCO_wdt_open,
.release = iTCO_wdt_release,
};
static struct miscdevice iTCO_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &iTCO_wdt_fops,
};
/*
* Init & exit routines
*/
static int 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 */
printk(KERN_ERR PFX "failed to get TCOBASE address\n");
pci_dev_put(pdev);
return -ENODEV;
}
iTCO_wdt_private.iTCO_version = iTCO_chipset_info[ent->driver_data].iTCO_version;
iTCO_wdt_private.ACPIBASE = base_address;
iTCO_wdt_private.pdev = pdev;
/* Get the Memory-Mapped GCS register, we need it for the NO_REBOOT flag (TCO v2) */
/* To get access to it you have to read RCBA from PCI Config space 0xf0
and use it as base. GCS = RCBA + ICH6_GCS(0x3410). */
if (iTCO_wdt_private.iTCO_version == 2) {
pci_read_config_dword(pdev, 0xf0, &base_address);
RCBA = base_address & 0xffffc000;
iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410),4);
}
/* Check chipset's NO_REBOOT bit */
if (iTCO_wdt_unset_NO_REBOOT_bit() && iTCO_vendor_check_noreboot_on()) {
printk(KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
ret = -ENODEV; /* Cannot reset NO_REBOOT bit */
goto out;
}
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
iTCO_wdt_set_NO_REBOOT_bit();
/* Set the TCO_EN bit in SMI_EN register */
if (!request_region(SMI_EN, 4, "iTCO_wdt")) {
printk(KERN_ERR PFX "I/O address 0x%04lx already in use\n",
SMI_EN );
ret = -EIO;
goto out;
}
val32 = inl(SMI_EN);
val32 &= 0xffffdfff; /* Turn off SMI clearing watchdog */
outl(val32, SMI_EN);
release_region(SMI_EN, 4);
/* The TCO I/O registers reside in a 32-byte range pointed to by the TCOBASE value */
if (!request_region (TCOBASE, 0x20, "iTCO_wdt")) {
printk (KERN_ERR PFX "I/O address 0x%04lx already in use\n",
TCOBASE);
ret = -EIO;
goto out;
}
printk(KERN_INFO PFX "Found a %s TCO device (Version=%d, TCOBASE=0x%04lx)\n",
iTCO_chipset_info[ent->driver_data].name,
iTCO_chipset_info[ent->driver_data].iTCO_version,
TCOBASE);
/* Clear out the (probably old) status */
outb(0, TCO1_STS);
outb(3, TCO2_STS);
/* 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);
printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39 (TCO v1) or 613 (TCO v2), using %d\n",
heartbeat);
}
ret = misc_register(&iTCO_wdt_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_region;
}
printk (KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
heartbeat, nowayout);
return 0;
unreg_region:
release_region (TCOBASE, 0x20);
out:
if (iTCO_wdt_private.iTCO_version == 2)
iounmap(iTCO_wdt_private.gcs);
pci_dev_put(iTCO_wdt_private.pdev);
iTCO_wdt_private.ACPIBASE = 0;
return ret;
}
static void iTCO_wdt_cleanup(void)
{
/* Stop the timer before we leave */
if (!nowayout)
iTCO_wdt_stop();
/* Deregister */
misc_deregister(&iTCO_wdt_miscdev);
release_region(TCOBASE, 0x20);
if (iTCO_wdt_private.iTCO_version == 2)
iounmap(iTCO_wdt_private.gcs);
pci_dev_put(iTCO_wdt_private.pdev);
iTCO_wdt_private.ACPIBASE = 0;
}
static int iTCO_wdt_probe(struct platform_device *dev)
{
int found = 0;
struct pci_dev *pdev = NULL;
const struct pci_device_id *ent;
spin_lock_init(&iTCO_wdt_private.io_lock);
for_each_pci_dev(pdev) {
ent = pci_match_id(iTCO_wdt_pci_tbl, pdev);
if (ent) {
if (!(iTCO_wdt_init(pdev, ent, dev))) {
found++;
break;
}
}
}
if (!found) {
printk(KERN_INFO PFX "No card detected\n");
return -ENODEV;
}
return 0;
}
static int 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();
}
#define iTCO_wdt_suspend NULL
#define iTCO_wdt_resume NULL
static struct platform_driver iTCO_wdt_driver = {
.probe = iTCO_wdt_probe,
.remove = iTCO_wdt_remove,
.shutdown = iTCO_wdt_shutdown,
.suspend = iTCO_wdt_suspend,
.resume = iTCO_wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = DRV_NAME,
},
};
static int __init iTCO_wdt_init_module(void)
{
int err;
printk(KERN_INFO PFX "Intel TCO WatchDog Timer Driver v%s (%s)\n",
DRV_VERSION, DRV_RELDATE);
err = platform_driver_register(&iTCO_wdt_driver);
if (err)
return err;
iTCO_wdt_platform_device = platform_device_register_simple(DRV_NAME, -1, NULL, 0);
if (IS_ERR(iTCO_wdt_platform_device)) {
err = PTR_ERR(iTCO_wdt_platform_device);
goto unreg_platform_driver;
}
return 0;
unreg_platform_driver:
platform_driver_unregister(&iTCO_wdt_driver);
return err;
}
static void __exit iTCO_wdt_cleanup_module(void)
{
platform_device_unregister(iTCO_wdt_platform_device);
platform_driver_unregister(&iTCO_wdt_driver);
printk(KERN_INFO PFX "Watchdog Module Unloaded.\n");
}
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);

408
drivers/watchdog/ib700wdt.c Normal file
View File

@@ -0,0 +1,408 @@
/*
* 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@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>
*
* 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
*
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
static struct platform_device *ibwdt_platform_device;
static unsigned long ibwdt_is_open;
static spinlock_t ibwdt_lock;
static char expect_close;
/* Module information */
#define DRV_NAME "ib700wdt"
#define PFX DRV_NAME ": "
/*
*
* 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
*
*/
static int wd_times[] = {
30, /* 0x0 */
28, /* 0x1 */
26, /* 0x2 */
24, /* 0x3 */
22, /* 0x4 */
20, /* 0x5 */
18, /* 0x6 */
16, /* 0x7 */
14, /* 0x8 */
12, /* 0x9 */
10, /* 0xA */
8, /* 0xB */
6, /* 0xC */
4, /* 0xD */
2, /* 0xE */
0, /* 0xF */
};
#define WDT_STOP 0x441
#define WDT_START 0x443
/* Default timeout */
#define WD_TIMO 0 /* 30 seconds +/- 20%, from table */
static int wd_margin = WD_TIMO;
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/*
* Watchdog Operations
*/
static void
ibwdt_ping(void)
{
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)
{
int i;
if ((t < 0) || (t > 30))
return -EINVAL;
for (i = 0x0F; i > -1; i--)
if (wd_times[i] > t)
break;
wd_margin = i;
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 int
ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int new_margin;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.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_KEEPALIVE:
ibwdt_ping();
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, p))
return -EFAULT;
if (ibwdt_set_heartbeat(new_margin))
return -EINVAL;
ibwdt_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(wd_times[wd_margin], p);
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;
}
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 {
printk(KERN_CRIT PFX "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,
.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;
spin_lock_init(&ibwdt_lock);
#if WDT_START != WDT_STOP
if (!request_region(WDT_STOP, 1, "IB700 WDT")) {
printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP);
res = -EIO;
goto out_nostopreg;
}
#endif
if (!request_region(WDT_START, 1, "IB700 WDT")) {
printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START);
res = -EIO;
goto out_nostartreg;
}
res = misc_register(&ibwdt_miscdev);
if (res) {
printk (KERN_ERR PFX "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;
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "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 */

403
drivers/watchdog/ibmasr.c Normal file
View File

@@ -0,0 +1,403 @@
/*
* 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.
*/
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/dmi.h>
#include <asm/io.h>
#include <asm/uaccess.h>
enum {
ASMTYPE_UNKNOWN,
ASMTYPE_TOPAZ,
ASMTYPE_JASPER,
ASMTYPE_PEARL,
ASMTYPE_JUNIPER,
ASMTYPE_SPRUCE,
};
#define PFX "ibmasr: "
#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 int 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 void asr_toggle(void)
{
unsigned char 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_enable(void)
{
unsigned char reg;
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);
}
static void asr_disable(void)
{
unsigned char 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);
}
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 ";
/* 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);
/*
* 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);*/
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")) {
printk(KERN_ERR PFX "address %#x already in use\n",
asr_base);
return -EBUSY;
}
printk(KERN_INFO PFX "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 int asr_ioctl(struct inode *inode, 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_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);
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;
}
}
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 {
printk(KERN_CRIT PFX "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,
.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);
printk(KERN_ERR PFX "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, int, 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);

215
drivers/watchdog/indydog.c Normal file
View File

@@ -0,0 +1,215 @@
/*
* 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>
*/
#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/uaccess.h>
#include <asm/sgi/mc.h>
#define PFX "indydog: "
static int indydog_alive;
#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static void indydog_start(void)
{
u32 mc_ctrl0 = sgimc->cpuctrl0;
mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG;
sgimc->cpuctrl0 = mc_ctrl0;
}
static void indydog_stop(void)
{
u32 mc_ctrl0 = sgimc->cpuctrl0;
mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
sgimc->cpuctrl0 = mc_ctrl0;
printk(KERN_INFO PFX "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 (indydog_alive)
return -EBUSY;
if (nowayout)
__module_get(THIS_MODULE);
/* Activate timer */
indydog_start();
indydog_ping();
indydog_alive = 1;
printk(KERN_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 */
indydog_alive = 0;
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 int indydog_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int options, retval = -EINVAL;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Hardware Watchdog for SGI IP22",
};
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
if (copy_to_user((struct watchdog_info *)arg,
&ident, sizeof(ident)))
return -EFAULT;
return 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0,(int *)arg);
case WDIOC_KEEPALIVE:
indydog_ping();
return 0;
case WDIOC_GETTIMEOUT:
return put_user(WATCHDOG_TIMEOUT,(int *)arg);
case WDIOC_SETOPTIONS:
{
if (get_user(options, (int *)arg))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
indydog_stop();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
indydog_start();
retval = 0;
}
return retval;
}
}
}
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,
.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 char banner[] __initdata =
KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n";
static int __init watchdog_init(void)
{
int ret;
ret = register_reboot_notifier(&indydog_notifier);
if (ret) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
return ret;
}
ret = misc_register(&indydog_miscdev);
if (ret) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&indydog_notifier);
return ret;
}
printk(banner);
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);

262
drivers/watchdog/iop_wdt.c Normal file
View File

@@ -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>
*/
#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 <asm/hardware.h>
static int nowayout = WATCHDOG_NOWAYOUT;
static unsigned long wdt_status;
static unsigned long boot_status;
#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
*/
write_wdtcr(IOP_WDTCR_EN_ARM);
write_wdtcr(IOP_WDTCR_EN);
}
/* returns 0 if the timer was successfully disabled */
static int wdt_disable(void)
{
/* Stop Counting */
if (wdt_supports_disable()) {
write_wdtcr(IOP_WDTCR_DIS_ARM);
write_wdtcr(IOP_WDTCR_DIS);
clear_bit(WDT_ENABLED, &wdt_status);
printk(KERN_INFO "WATCHDOG: 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 struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
.identity = "iop watchdog",
};
static int
iop_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int options;
int ret = -ENOTTY;
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user
((struct watchdog_info *)arg, &ident, sizeof ident))
ret = -EFAULT;
else
ret = 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_GETTIMEOUT:
ret = put_user(iop_watchdog_timeout(), (int *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_enable();
ret = 0;
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;
}
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 disbaled reload and notify that we are still
* going down
*/
if (state != 0) {
wdt_enable();
printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
"reset in %lu seconds\n", iop_watchdog_timeout());
}
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,
.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;
ret = misc_register(&iop_wdt_miscdev);
if (ret == 0)
printk("iop watchdog timer: timeout %lu sec\n",
iop_watchdog_timeout());
/* check if the reset was caused by the watchdog timer */
boot_status = (read_rcsr() & IOP_RCSR_WDT) ? WDIOF_CARDRESET : 0;
/* 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);
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, int, 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);

View File

@@ -0,0 +1,219 @@
/*
* drivers/char/watchdog/ixp2000_wdt.c
*
* Watchdog driver for Intel IXP2000 network processors
*
* Adapted from the IXP4xx watchdog driver by Lennert Buytenhek.
* The original version carries these notices:
*
* Author: Deepak Saxena <dsaxena@plexity.net>
*
* Copyright 2004 (c) MontaVista, Software, Inc.
* Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
static int nowayout = WATCHDOG_NOWAYOUT;
static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */
static unsigned long wdt_status;
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
static unsigned long wdt_tick_rate;
static void
wdt_enable(void)
{
ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE);
ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE);
ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE);
}
static void
wdt_disable(void)
{
ixp2000_reg_write(IXP2000_T4_CTL, 0);
}
static void
wdt_keepalive(void)
{
ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
}
static int
ixp2000_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(WDT_IN_USE, &wdt_status))
return -EBUSY;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
wdt_enable();
return nonseekable_open(inode, file);
}
static ssize_t
ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
if (len) {
if (!nowayout) {
size_t i;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
set_bit(WDT_OK_TO_CLOSE, &wdt_status);
}
}
wdt_keepalive();
}
return len;
}
static struct watchdog_info ident = {
.options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING,
.identity = "IXP2000 Watchdog",
};
static int
ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOTTY;
int time;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user((struct watchdog_info *)arg, &ident,
sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int *)arg);
break;
case WDIOC_GETBOOTSTATUS:
ret = put_user(0, (int *)arg);
break;
case WDIOC_SETTIMEOUT:
ret = get_user(time, (int *)arg);
if (ret)
break;
if (time <= 0 || time > 60) {
ret = -EINVAL;
break;
}
heartbeat = time;
wdt_keepalive();
/* Fall through */
case WDIOC_GETTIMEOUT:
ret = put_user(heartbeat, (int *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_enable();
ret = 0;
break;
}
return ret;
}
static int
ixp2000_wdt_release(struct inode *inode, struct file *file)
{
if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
wdt_disable();
} else {
printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
"timer will not stop\n");
}
clear_bit(WDT_IN_USE, &wdt_status);
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
return 0;
}
static const struct file_operations ixp2000_wdt_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = ixp2000_wdt_write,
.ioctl = ixp2000_wdt_ioctl,
.open = ixp2000_wdt_open,
.release = ixp2000_wdt_release,
};
static struct miscdevice ixp2000_wdt_miscdev =
{
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &ixp2000_wdt_fops,
};
static int __init ixp2000_wdt_init(void)
{
if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) {
printk(KERN_INFO "Unable to use IXP2000 watchdog due to IXP2800 erratum #25.\n");
return -EIO;
}
wdt_tick_rate = (*IXP2000_T1_CLD * HZ) / 256;
return misc_register(&ixp2000_wdt_miscdev);
}
static void __exit ixp2000_wdt_exit(void)
{
misc_deregister(&ixp2000_wdt_miscdev);
}
module_init(ixp2000_wdt_init);
module_exit(ixp2000_wdt_exit);
MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog");
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,225 @@
/*
* 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.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/init.h>
#include <linux/bitops.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
static int nowayout = WATCHDOG_NOWAYOUT;
static int heartbeat = 60; /* (secs) Default is 1 minute */
static unsigned long wdt_status;
static unsigned long boot_status;
#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)
{
*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;
}
static void
wdt_disable(void)
{
*IXP4XX_OSWK = IXP4XX_WDT_KEY;
*IXP4XX_OSWE = 0;
*IXP4XX_OSWK = 0;
}
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 struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "IXP4xx Watchdog",
};
static int
ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOTTY;
int time;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user((struct watchdog_info *)arg, &ident,
sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int *)arg);
break;
case WDIOC_GETBOOTSTATUS:
ret = put_user(boot_status, (int *)arg);
break;
case WDIOC_SETTIMEOUT:
ret = get_user(time, (int *)arg);
if (ret)
break;
if (time <= 0 || time > 60) {
ret = -EINVAL;
break;
}
heartbeat = time;
wdt_enable();
/* Fall through */
case WDIOC_GETTIMEOUT:
ret = put_user(heartbeat, (int *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_enable();
ret = 0;
break;
}
return ret;
}
static int
ixp4xx_wdt_release(struct inode *inode, struct file *file)
{
if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
wdt_disable();
} else {
printk(KERN_CRIT "WATCHDOG: Device closed unexpectedly - "
"timer will not stop\n");
}
clear_bit(WDT_IN_USE, &wdt_status);
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
return 0;
}
static const struct file_operations ixp4xx_wdt_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = ixp4xx_wdt_write,
.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;
unsigned long processor_id;
asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :);
if (!(processor_id & 0xf) && !cpu_is_ixp46x()) {
printk("IXP4XXX Watchdog: Rev. A0 IXP42x CPU detected - "
"watchdog disabled\n");
return -ENODEV;
}
ret = misc_register(&ixp4xx_wdt_miscdev);
if (ret == 0)
printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat);
boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ?
WDIOF_CARDRESET : 0;
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, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,308 @@
/*
* 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.
*/
#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 <asm/bitops.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-timer.h>
#define WDT_DEFAULT_TIME 5 /* seconds */
#define WDT_MAX_TIME 171 /* seconds */
static int wdt_time = WDT_DEFAULT_TIME;
static int 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, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#endif
static unsigned long ks8695wdt_busy;
/* ......................................................................... */
/*
* Disable the watchdog.
*/
static void inline ks8695_wdt_stop(void)
{
unsigned long tmcon;
/* disable timer0 */
tmcon = __raw_readl(KS8695_TMR_VA + KS8695_TMCON);
__raw_writel(tmcon & ~TMCON_T0EN, KS8695_TMR_VA + KS8695_TMCON);
}
/*
* Enable and reset the watchdog.
*/
static void inline ks8695_wdt_start(void)
{
unsigned long tmcon;
unsigned long tval = wdt_time * CLOCK_TICK_RATE;
/* 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);
}
/*
* Reload the watchdog timer. (ie, pat the watchdog)
*/
static void inline ks8695_wdt_reload(void)
{
unsigned long tmcon;
/* 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);
}
/*
* Change the watchdog time interval.
*/
static int ks8695_wdt_settimeout(int new_time)
{
/*
* All counting occurs at SLOW_CLOCK / 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)
{
if (!nowayout)
ks8695_wdt_stop(); /* Disable the watchdog when file is closed */
clear_bit(0, &ks8695wdt_busy);
return 0;
}
static struct watchdog_info ks8695_wdt_info = {
.identity = "ks8695 watchdog",
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
};
/*
* Handle commands from user-space.
*/
static int ks8695_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_value;
switch(cmd) {
case WDIOC_KEEPALIVE:
ks8695_wdt_reload(); /* pat the watchdog */
return 0;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ks8695_wdt_info, sizeof(ks8695_wdt_info)) ? -EFAULT : 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_value, p))
return -EFAULT;
if (ks8695_wdt_settimeout(new_value))
return -EINVAL;
/* Enable new time value */
ks8695_wdt_start();
/* Return current value */
return put_user(wdt_time, p);
case WDIOC_GETTIMEOUT:
return put_user(wdt_time, p);
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_SETOPTIONS:
if (get_user(new_value, p))
return -EFAULT;
if (new_value & WDIOS_DISABLECARD)
ks8695_wdt_stop();
if (new_value & WDIOS_ENABLECARD)
ks8695_wdt_start();
return 0;
default:
return -ENOTTY;
}
}
/*
* 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,
.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 __init 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;
printk("KS8695 Watchdog Timer enabled (%d seconds%s)\n", wdt_time, nowayout ? ", nowayout" : "");
return 0;
}
static int __exit 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 = __exit_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);

489
drivers/watchdog/machzwd.c Normal file
View File

@@ -0,0 +1,489 @@
/*
* 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 unconditionaly 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
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#define PFX "machzwd"
static 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 = 0;
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 spinlock_t zf_lock;
static spinlock_t zf_port_lock;
static DEFINE_TIMER(zf_timer, zf_ping, 0, 0);
static unsigned long next_heartbeat = 0;
/* 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...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __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);
printk(KERN_INFO PFX ": 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);
printk(KERN_INFO PFX ": 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{
printk(KERN_CRIT PFX ": 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 int zf_ioctl(struct inode *inode, 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)
{
spin_lock(&zf_lock);
if(test_and_set_bit(0, &zf_is_open)) {
spin_unlock(&zf_lock);
return -EBUSY;
}
if (nowayout)
__module_get(THIS_MODULE);
spin_unlock(&zf_lock);
zf_timer_on();
return nonseekable_open(inode, file);
}
static int zf_close(struct inode *inode, struct file *file)
{
if(zf_expect_close == 42){
zf_timer_off();
} else {
del_timer(&zf_timer);
printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n");
}
spin_lock(&zf_lock);
clear_bit(0, &zf_is_open);
spin_unlock(&zf_lock);
zf_expect_close = 0;
return 0;
}
/*
* 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,
.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)
{
char *str[] = { "RESET", "SMI", "NMI", "SCI" };
printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]);
}
static int __init zf_init(void)
{
int ret;
printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n");
ret = zf_get_ZFL_version();
if ((!ret) || (ret == 0xffff)) {
printk(KERN_WARNING PFX ": no ZF-Logic found\n");
return -ENODEV;
}
if((action <= 3) && (action >= 0)){
zf_action = zf_action>>action;
} else
action = 0;
zf_show_action(action);
spin_lock_init(&zf_lock);
spin_lock_init(&zf_port_lock);
if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){
printk(KERN_ERR "cannot reserve I/O ports at %d\n",
ZF_IOBASE);
ret = -EBUSY;
goto no_region;
}
ret = register_reboot_notifier(&zf_notifier);
if(ret){
printk(KERN_ERR "can't register reboot notifier (err=%d)\n",
ret);
goto no_reboot;
}
ret = misc_register(&zf_miscdev);
if (ret){
printk(KERN_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);

330
drivers/watchdog/mixcomwd.c Normal file
View File

@@ -0,0 +1,330 @@
/*
* 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 VERSION "0.6"
#define WATCHDOG_NAME "mixcomwd"
#define PFX WATCHDOG_NAME ": "
#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 <asm/uaccess.h>
#include <asm/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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static 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) {
printk(KERN_ERR PFX "release called while internal timer alive");
return -EBUSY;
}
mixcomwd_timer_alive=1;
mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
} else {
printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n");
}
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 int mixcomwd_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int status;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "MixCOM watchdog",
};
switch(cmd)
{
case WDIOC_GETSTATUS:
status=mixcomwd_opened;
if (!nowayout) {
status|=mixcomwd_timer_alive;
}
if (copy_to_user(p, &status, sizeof(int))) {
return -EFAULT;
}
break;
case WDIOC_GETBOOTSTATUS:
if (copy_to_user(p, &status, sizeof(int))) {
return -EFAULT;
}
break;
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &ident, sizeof(ident))) {
return -EFAULT;
}
break;
case WDIOC_KEEPALIVE:
mixcomwd_ping();
break;
default:
return -ENOTTY;
}
return 0;
}
static const struct file_operations mixcomwd_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = mixcomwd_write,
.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;
int ret;
int 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) {
printk(KERN_ERR PFX "No card detected, or port not available.\n");
return -ENODEV;
}
ret = misc_register(&mixcomwd_miscdev);
if (ret)
{
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto error_misc_register_watchdog;
}
printk(KERN_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) {
printk(KERN_WARNING PFX "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);

View File

@@ -0,0 +1,286 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <asm/of_platform.h>
#include <asm/uaccess.h>
#include <asm/mpc52xx.h>
#define GPT_MODE_WDT (1<<15)
#define GPT_MODE_CE (1<<12)
#define GPT_MODE_MS_TIMER (0x4)
struct mpc5200_wdt {
unsigned count; /* timer ticks before watchdog kicks in */
long ipb_freq;
struct miscdevice miscdev;
struct resource mem;
struct mpc52xx_gpt __iomem *regs;
spinlock_t io_lock;
};
/* is_active stores wether or not the /dev/watchdog device is opened */
static unsigned long is_active;
/* misc devices don't provide a way, to get back to 'dev' or 'miscdev' from
* file operations, which sucks. But there can be max 1 watchdog anyway, so...
*/
static struct mpc5200_wdt *wdt_global;
/* helper to calculate timeout in timer counts */
static void mpc5200_wdt_set_timeout(struct mpc5200_wdt *wdt, int timeout)
{
/* use biggest prescaler of 64k */
wdt->count = (wdt->ipb_freq + 0xffff) / 0x10000 * timeout;
if (wdt->count > 0xffff)
wdt->count = 0xffff;
}
/* return timeout in seconds (calculated from timer count) */
static int mpc5200_wdt_get_timeout(struct mpc5200_wdt *wdt)
{
return wdt->count * 0x10000 / wdt->ipb_freq;
}
/* watchdog operations */
static int mpc5200_wdt_start(struct mpc5200_wdt *wdt)
{
spin_lock(&wdt->io_lock);
/* disable */
out_be32(&wdt->regs->mode, 0);
/* set timeout, with maximum prescaler */
out_be32(&wdt->regs->count, 0x0 | wdt->count);
/* enable watchdog */
out_be32(&wdt->regs->mode, GPT_MODE_CE | GPT_MODE_WDT | GPT_MODE_MS_TIMER);
spin_unlock(&wdt->io_lock);
return 0;
}
static int mpc5200_wdt_ping(struct mpc5200_wdt *wdt)
{
spin_lock(&wdt->io_lock);
/* writing A5 to OCPW resets the watchdog */
out_be32(&wdt->regs->mode, 0xA5000000 | (0xffffff & in_be32(&wdt->regs->mode)));
spin_unlock(&wdt->io_lock);
return 0;
}
static int mpc5200_wdt_stop(struct mpc5200_wdt *wdt)
{
spin_lock(&wdt->io_lock);
/* disable */
out_be32(&wdt->regs->mode, 0);
spin_unlock(&wdt->io_lock);
return 0;
}
/* file operations */
static ssize_t mpc5200_wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
struct mpc5200_wdt *wdt = file->private_data;
mpc5200_wdt_ping(wdt);
return 0;
}
static struct watchdog_info mpc5200_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "mpc5200 watchdog on GPT0",
};
static int mpc5200_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct mpc5200_wdt *wdt = file->private_data;
int __user *data = (int __user *)arg;
int timeout;
int ret = 0;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user(data, &mpc5200_wdt_info,
sizeof(mpc5200_wdt_info));
if (ret)
ret = -EFAULT;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
ret = put_user(0, data);
break;
case WDIOC_KEEPALIVE:
mpc5200_wdt_ping(wdt);
break;
case WDIOC_SETTIMEOUT:
ret = get_user(timeout, data);
if (ret)
break;
mpc5200_wdt_set_timeout(wdt, timeout);
mpc5200_wdt_start(wdt);
/* fall through and return the timeout */
case WDIOC_GETTIMEOUT:
timeout = mpc5200_wdt_get_timeout(wdt);
ret = put_user(timeout, data);
break;
default:
ret = -ENOTTY;
}
return ret;
}
static int mpc5200_wdt_open(struct inode *inode, struct file *file)
{
/* /dev/watchdog can only be opened once */
if (test_and_set_bit(0, &is_active))
return -EBUSY;
/* Set and activate the watchdog */
mpc5200_wdt_set_timeout(wdt_global, 30);
mpc5200_wdt_start(wdt_global);
file->private_data = wdt_global;
return nonseekable_open(inode, file);
}
static int mpc5200_wdt_release(struct inode *inode, struct file *file)
{
#if WATCHDOG_NOWAYOUT == 0
struct mpc5200_wdt *wdt = file->private_data;
mpc5200_wdt_stop(wdt);
wdt->count = 0; /* == disabled */
#endif
clear_bit(0, &is_active);
return 0;
}
static struct file_operations mpc5200_wdt_fops = {
.owner = THIS_MODULE,
.write = mpc5200_wdt_write,
.ioctl = mpc5200_wdt_ioctl,
.open = mpc5200_wdt_open,
.release = mpc5200_wdt_release,
};
/* module operations */
static int mpc5200_wdt_probe(struct of_device *op, const struct of_device_id *match)
{
struct mpc5200_wdt *wdt;
int err;
const void *has_wdt;
int size;
has_wdt = of_get_property(op->node, "has-wdt", NULL);
if (!has_wdt)
return -ENODEV;
wdt = kzalloc(sizeof(*wdt), GFP_KERNEL);
if (!wdt)
return -ENOMEM;
wdt->ipb_freq = mpc52xx_find_ipb_freq(op->node);
err = of_address_to_resource(op->node, 0, &wdt->mem);
if (err)
goto out_free;
size = wdt->mem.end - wdt->mem.start + 1;
if (!request_mem_region(wdt->mem.start, size, "mpc5200_wdt")) {
err = -ENODEV;
goto out_free;
}
wdt->regs = ioremap(wdt->mem.start, size);
if (!wdt->regs) {
err = -ENODEV;
goto out_release;
}
dev_set_drvdata(&op->dev, wdt);
spin_lock_init(&wdt->io_lock);
wdt->miscdev = (struct miscdevice) {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &mpc5200_wdt_fops,
.parent = &op->dev,
};
wdt_global = wdt;
err = misc_register(&wdt->miscdev);
if (!err)
return 0;
iounmap(wdt->regs);
out_release:
release_mem_region(wdt->mem.start, size);
out_free:
kfree(wdt);
return err;
}
static int mpc5200_wdt_remove(struct of_device *op)
{
struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev);
mpc5200_wdt_stop(wdt);
misc_deregister(&wdt->miscdev);
iounmap(wdt->regs);
release_mem_region(wdt->mem.start, wdt->mem.end - wdt->mem.start + 1);
kfree(wdt);
return 0;
}
static int mpc5200_wdt_suspend(struct of_device *op, pm_message_t state)
{
struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev);
mpc5200_wdt_stop(wdt);
return 0;
}
static int mpc5200_wdt_resume(struct of_device *op)
{
struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev);
if (wdt->count)
mpc5200_wdt_start(wdt);
return 0;
}
static int mpc5200_wdt_shutdown(struct of_device *op)
{
struct mpc5200_wdt *wdt = dev_get_drvdata(&op->dev);
mpc5200_wdt_stop(wdt);
return 0;
}
static struct of_device_id mpc5200_wdt_match[] = {
{ .compatible = "mpc5200-gpt", },
{},
};
static struct of_platform_driver mpc5200_wdt_driver = {
.owner = THIS_MODULE,
.name = "mpc5200-gpt-wdt",
.match_table = mpc5200_wdt_match,
.probe = mpc5200_wdt_probe,
.remove = mpc5200_wdt_remove,
.suspend = mpc5200_wdt_suspend,
.resume = mpc5200_wdt_resume,
.shutdown = mpc5200_wdt_shutdown,
};
static int __init mpc5200_wdt_init(void)
{
return of_register_platform_driver(&mpc5200_wdt_driver);
}
static void __exit mpc5200_wdt_exit(void)
{
of_unregister_platform_driver(&mpc5200_wdt_driver);
}
module_init(mpc5200_wdt_init);
module_exit(mpc5200_wdt_exit);
MODULE_AUTHOR("Domen Puncer <domen.puncer@telargo.com>");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,231 @@
/*
* mpc83xx_wdt.c - MPC83xx watchdog userspace interface
*
* Authors: Dave Updegraff <dave@cray.org>
* Kumar Gala <galak@kernel.crashing.org>
* Attribution: from 83xx_wst: Florian Schirmer <jolt@tuxbox.org>
* ..and from sc520_wdt
*
* Note: it appears that you can only actually ENABLE or DISABLE the thing
* once after POR. Once enabled, you cannot disable, and vice versa.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/watchdog.h>
#include <asm/io.h>
#include <asm/uaccess.h>
struct mpc83xx_wdt {
__be32 res0;
__be32 swcrr; /* System watchdog control register */
#define SWCRR_SWTC 0xFFFF0000 /* Software Watchdog Time Count. */
#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
__be32 swcnr; /* System watchdog count register */
u8 res1[2];
__be16 swsrr; /* System watchdog service register */
u8 res2[0xF0];
};
static struct mpc83xx_wdt __iomem *wd_base;
static u16 timeout = 0xffff;
module_param(timeout, ushort, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in ticks. (0<timeout<65536, default=65535");
static int reset = 1;
module_param(reset, bool, 0);
MODULE_PARM_DESC(reset, "Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset");
/*
* We always prescale, but if someone really doesn't want to they can set this
* to 0
*/
static int prescale = 1;
static unsigned int timeout_sec;
static unsigned long wdt_is_open;
static spinlock_t wdt_spinlock;
static void mpc83xx_wdt_keepalive(void)
{
/* Ping the WDT */
spin_lock(&wdt_spinlock);
out_be16(&wd_base->swsrr, 0x556c);
out_be16(&wd_base->swsrr, 0xaa39);
spin_unlock(&wdt_spinlock);
}
static ssize_t mpc83xx_wdt_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
if (count)
mpc83xx_wdt_keepalive();
return count;
}
static int mpc83xx_wdt_open(struct inode *inode, struct file *file)
{
u32 tmp = SWCRR_SWEN;
if (test_and_set_bit(0, &wdt_is_open))
return -EBUSY;
/* Once we start the watchdog we can't stop it */
__module_get(THIS_MODULE);
/* Good, fire up the show */
if (prescale)
tmp |= SWCRR_SWPR;
if (reset)
tmp |= SWCRR_SWRI;
tmp |= timeout << 16;
out_be32(&wd_base->swcrr, tmp);
return nonseekable_open(inode, file);
}
static int mpc83xx_wdt_release(struct inode *inode, struct file *file)
{
printk(KERN_CRIT "Unexpected close, not stopping watchdog!\n");
mpc83xx_wdt_keepalive();
clear_bit(0, &wdt_is_open);
return 0;
}
static int mpc83xx_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING,
.firmware_version = 1,
.identity = "MPC83xx",
};
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
mpc83xx_wdt_keepalive();
return 0;
case WDIOC_GETTIMEOUT:
return put_user(timeout_sec, p);
default:
return -ENOTTY;
}
}
static const struct file_operations mpc83xx_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = mpc83xx_wdt_write,
.ioctl = mpc83xx_wdt_ioctl,
.open = mpc83xx_wdt_open,
.release = mpc83xx_wdt_release,
};
static struct miscdevice mpc83xx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &mpc83xx_wdt_fops,
};
static int __devinit mpc83xx_wdt_probe(struct platform_device *dev)
{
struct resource *r;
int ret;
unsigned int *freq = dev->dev.platform_data;
/* get a pointer to the register memory */
r = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (!r) {
ret = -ENODEV;
goto err_out;
}
wd_base = ioremap(r->start, sizeof (struct mpc83xx_wdt));
if (wd_base == NULL) {
ret = -ENOMEM;
goto err_out;
}
ret = misc_register(&mpc83xx_wdt_miscdev);
if (ret) {
printk(KERN_ERR "cannot register miscdev on minor=%d "
"(err=%d)\n",
WATCHDOG_MINOR, ret);
goto err_unmap;
}
/* Calculate the timeout in seconds */
if (prescale)
timeout_sec = (timeout * 0x10000) / (*freq);
else
timeout_sec = timeout / (*freq);
printk(KERN_INFO "WDT driver for MPC83xx initialized. "
"mode:%s timeout=%d (%d seconds)\n",
reset ? "reset":"interrupt", timeout, timeout_sec);
spin_lock_init(&wdt_spinlock);
return 0;
err_unmap:
iounmap(wd_base);
err_out:
return ret;
}
static int __devexit mpc83xx_wdt_remove(struct platform_device *dev)
{
misc_deregister(&mpc83xx_wdt_miscdev);
iounmap(wd_base);
return 0;
}
static struct platform_driver mpc83xx_wdt_driver = {
.probe = mpc83xx_wdt_probe,
.remove = __devexit_p(mpc83xx_wdt_remove),
.driver = {
.name = "mpc83xx_wdt",
},
};
static int __init mpc83xx_wdt_init(void)
{
return platform_driver_register(&mpc83xx_wdt_driver);
}
static void __exit mpc83xx_wdt_exit(void)
{
platform_driver_unregister(&mpc83xx_wdt_driver);
}
module_init(mpc83xx_wdt_init);
module_exit(mpc83xx_wdt_exit);
MODULE_AUTHOR("Dave Updegraff, Kumar Gala");
MODULE_DESCRIPTION("Driver for watchdog timer in MPC83xx uProcessor");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,169 @@
/*
* mpc8xx_wdt.c - MPC8xx watchdog userspace interface
*
* Author: Florian Schirmer <jolt@tuxbox.org>
*
* 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under
* the terms of the GNU General Public License version 2. This program
* is licensed "as is" without any warranty of any kind, whether express
* or implied.
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/watchdog.h>
#include <asm/8xx_immap.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <syslib/m8xx_wdt.h>
static unsigned long wdt_opened;
static int wdt_status;
static void mpc8xx_wdt_handler_disable(void)
{
volatile uint __iomem *piscr;
piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr;
if (!m8xx_has_internal_rtc)
m8xx_wdt_stop_timer();
else
out_be32(piscr, in_be32(piscr) & ~(PISCR_PIE | PISCR_PTE));
printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n");
}
static void mpc8xx_wdt_handler_enable(void)
{
volatile uint __iomem *piscr;
piscr = (uint *)&((immap_t*)IMAP_ADDR)->im_sit.sit_piscr;
if (!m8xx_has_internal_rtc)
m8xx_wdt_install_timer();
else
out_be32(piscr, in_be32(piscr) | PISCR_PIE | PISCR_PTE);
printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n");
}
static int mpc8xx_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(0, &wdt_opened))
return -EBUSY;
m8xx_wdt_reset();
mpc8xx_wdt_handler_disable();
return nonseekable_open(inode, file);
}
static int mpc8xx_wdt_release(struct inode *inode, struct file *file)
{
m8xx_wdt_reset();
#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
mpc8xx_wdt_handler_enable();
#endif
clear_bit(0, &wdt_opened);
return 0;
}
static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len,
loff_t * ppos)
{
if (len)
m8xx_wdt_reset();
return len;
}
static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int timeout;
static struct watchdog_info info = {
.options = WDIOF_KEEPALIVEPING,
.firmware_version = 0,
.identity = "MPC8xx watchdog",
};
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user((void *)arg, &info, sizeof(info)))
return -EFAULT;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
if (put_user(wdt_status, (int *)arg))
return -EFAULT;
wdt_status &= ~WDIOF_KEEPALIVEPING;
break;
case WDIOC_GETTEMP:
return -EOPNOTSUPP;
case WDIOC_SETOPTIONS:
return -EOPNOTSUPP;
case WDIOC_KEEPALIVE:
m8xx_wdt_reset();
wdt_status |= WDIOF_KEEPALIVEPING;
break;
case WDIOC_SETTIMEOUT:
return -EOPNOTSUPP;
case WDIOC_GETTIMEOUT:
timeout = m8xx_wdt_get_timeout();
if (put_user(timeout, (int *)arg))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static const struct file_operations mpc8xx_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = mpc8xx_wdt_write,
.ioctl = mpc8xx_wdt_ioctl,
.open = mpc8xx_wdt_open,
.release = mpc8xx_wdt_release,
};
static struct miscdevice mpc8xx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &mpc8xx_wdt_fops,
};
static int __init mpc8xx_wdt_init(void)
{
return misc_register(&mpc8xx_wdt_miscdev);
}
static void __exit mpc8xx_wdt_exit(void)
{
misc_deregister(&mpc8xx_wdt_miscdev);
m8xx_wdt_reset();
mpc8xx_wdt_handler_enable();
}
module_init(mpc8xx_wdt_init);
module_exit(mpc8xx_wdt_exit);
MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>");
MODULE_DESCRIPTION("MPC8xx watchdog driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,435 @@
/*
* Watchdog driver for the mpcore watchdog timer
*
* (c) Copyright 2004 ARM Limited
*
* Based on the SoftDog driver:
* (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <asm/hardware/arm_twd.h>
#include <asm/uaccess.h>
struct mpcore_wdt {
unsigned long timer_alive;
struct device *dev;
void __iomem *base;
int irq;
unsigned int perturb;
char expect_close;
};
static struct platform_device *mpcore_wdt_dev;
extern unsigned int mpcore_timer_rate;
#define TIMER_MARGIN 60
static int mpcore_margin = TIMER_MARGIN;
module_param(mpcore_margin, int, 0);
MODULE_PARM_DESC(mpcore_margin, "MPcore timer margin in seconds. (0<mpcore_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#define ONLY_TESTING 0
static int mpcore_noboot = ONLY_TESTING;
module_param(mpcore_noboot, int, 0);
MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")");
/*
* This is the interrupt handler. Note that we only use this
* in testing mode, so don't actually do a reboot here.
*/
static irqreturn_t mpcore_wdt_fire(int irq, void *arg)
{
struct mpcore_wdt *wdt = arg;
/* Check it really was our interrupt */
if (readl(wdt->base + TWD_WDOG_INTSTAT)) {
dev_printk(KERN_CRIT, wdt->dev, "Triggered - Reboot ignored.\n");
/* Clear the interrupt on the watchdog */
writel(1, wdt->base + TWD_WDOG_INTSTAT);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
/*
* mpcore_wdt_keepalive - reload the timer
*
* Note that the spec says a DIFFERENT value must be written to the reload
* register each time. The "perturb" variable deals with this by adding 1
* to the count every other time the function is called.
*/
static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt)
{
unsigned int count;
/* Assume prescale is set to 256 */
count = (mpcore_timer_rate / 256) * mpcore_margin;
/* Reload the counter */
writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD);
wdt->perturb = wdt->perturb ? 0 : 1;
}
static void mpcore_wdt_stop(struct mpcore_wdt *wdt)
{
writel(0x12345678, wdt->base + TWD_WDOG_DISABLE);
writel(0x87654321, wdt->base + TWD_WDOG_DISABLE);
writel(0x0, wdt->base + TWD_WDOG_CONTROL);
}
static void mpcore_wdt_start(struct mpcore_wdt *wdt)
{
dev_printk(KERN_INFO, wdt->dev, "enabling watchdog.\n");
/* This loads the count register but does NOT start the count yet */
mpcore_wdt_keepalive(wdt);
if (mpcore_noboot) {
/* Enable watchdog - prescale=256, watchdog mode=0, enable=1 */
writel(0x0000FF01, wdt->base + TWD_WDOG_CONTROL);
} else {
/* Enable watchdog - prescale=256, watchdog mode=1, enable=1 */
writel(0x0000FF09, wdt->base + TWD_WDOG_CONTROL);
}
}
static int mpcore_wdt_set_heartbeat(int t)
{
if (t < 0x0001 || t > 0xFFFF)
return -EINVAL;
mpcore_margin = t;
return 0;
}
/*
* /dev/watchdog handling
*/
static int mpcore_wdt_open(struct inode *inode, struct file *file)
{
struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev);
if (test_and_set_bit(0, &wdt->timer_alive))
return -EBUSY;
if (nowayout)
__module_get(THIS_MODULE);
file->private_data = wdt;
/*
* Activate timer
*/
mpcore_wdt_start(wdt);
return nonseekable_open(inode, file);
}
static int mpcore_wdt_release(struct inode *inode, struct file *file)
{
struct mpcore_wdt *wdt = file->private_data;
/*
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
if (wdt->expect_close == 42) {
mpcore_wdt_stop(wdt);
} else {
dev_printk(KERN_CRIT, wdt->dev, "unexpected close, not stopping watchdog!\n");
mpcore_wdt_keepalive(wdt);
}
clear_bit(0, &wdt->timer_alive);
wdt->expect_close = 0;
return 0;
}
static ssize_t mpcore_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
{
struct mpcore_wdt *wdt = file->private_data;
/*
* Refresh the timer.
*/
if (len) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
wdt->expect_close = 0;
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
wdt->expect_close = 42;
}
}
mpcore_wdt_keepalive(wdt);
}
return len;
}
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.identity = "MPcore Watchdog",
};
static int mpcore_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct mpcore_wdt *wdt = file->private_data;
int ret;
union {
struct watchdog_info ident;
int i;
} uarg;
if (_IOC_DIR(cmd) && _IOC_SIZE(cmd) > sizeof(uarg))
return -ENOTTY;
if (_IOC_DIR(cmd) & _IOC_WRITE) {
ret = copy_from_user(&uarg, (void __user *)arg, _IOC_SIZE(cmd));
if (ret)
return -EFAULT;
}
switch (cmd) {
case WDIOC_GETSUPPORT:
uarg.ident = ident;
ret = 0;
break;
case WDIOC_SETOPTIONS:
ret = -EINVAL;
if (uarg.i & WDIOS_DISABLECARD) {
mpcore_wdt_stop(wdt);
ret = 0;
}
if (uarg.i & WDIOS_ENABLECARD) {
mpcore_wdt_start(wdt);
ret = 0;
}
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
uarg.i = 0;
ret = 0;
break;
case WDIOC_KEEPALIVE:
mpcore_wdt_keepalive(wdt);
ret = 0;
break;
case WDIOC_SETTIMEOUT:
ret = mpcore_wdt_set_heartbeat(uarg.i);
if (ret)
break;
mpcore_wdt_keepalive(wdt);
/* Fall */
case WDIOC_GETTIMEOUT:
uarg.i = mpcore_margin;
ret = 0;
break;
default:
return -ENOTTY;
}
if (ret == 0 && _IOC_DIR(cmd) & _IOC_READ) {
ret = copy_to_user((void __user *)arg, &uarg, _IOC_SIZE(cmd));
if (ret)
ret = -EFAULT;
}
return ret;
}
/*
* System shutdown handler. Turn off the watchdog if we're
* restarting or halting the system.
*/
static void mpcore_wdt_shutdown(struct platform_device *dev)
{
struct mpcore_wdt *wdt = platform_get_drvdata(dev);
if (system_state == SYSTEM_RESTART || system_state == SYSTEM_HALT)
mpcore_wdt_stop(wdt);
}
/*
* Kernel Interfaces
*/
static const struct file_operations mpcore_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = mpcore_wdt_write,
.ioctl = mpcore_wdt_ioctl,
.open = mpcore_wdt_open,
.release = mpcore_wdt_release,
};
static struct miscdevice mpcore_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &mpcore_wdt_fops,
};
static int __devinit mpcore_wdt_probe(struct platform_device *dev)
{
struct mpcore_wdt *wdt;
struct resource *res;
int ret;
/* We only accept one device, and it must have an id of -1 */
if (dev->id != -1)
return -ENODEV;
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENODEV;
goto err_out;
}
wdt = kzalloc(sizeof(struct mpcore_wdt), GFP_KERNEL);
if (!wdt) {
ret = -ENOMEM;
goto err_out;
}
wdt->dev = &dev->dev;
wdt->irq = platform_get_irq(dev, 0);
if (wdt->irq < 0) {
ret = -ENXIO;
goto err_free;
}
wdt->base = ioremap(res->start, res->end - res->start + 1);
if (!wdt->base) {
ret = -ENOMEM;
goto err_free;
}
mpcore_wdt_miscdev.parent = &dev->dev;
ret = misc_register(&mpcore_wdt_miscdev);
if (ret) {
dev_printk(KERN_ERR, _dev, "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto err_misc;
}
ret = request_irq(wdt->irq, mpcore_wdt_fire, IRQF_DISABLED, "mpcore_wdt", wdt);
if (ret) {
dev_printk(KERN_ERR, _dev, "cannot register IRQ%d for watchdog\n", wdt->irq);
goto err_irq;
}
mpcore_wdt_stop(wdt);
platform_set_drvdata(&dev->dev, wdt);
mpcore_wdt_dev = dev;
return 0;
err_irq:
misc_deregister(&mpcore_wdt_miscdev);
err_misc:
iounmap(wdt->base);
err_free:
kfree(wdt);
err_out:
return ret;
}
static int __devexit mpcore_wdt_remove(struct platform_device *dev)
{
struct mpcore_wdt *wdt = platform_get_drvdata(dev);
platform_set_drvdata(dev, NULL);
misc_deregister(&mpcore_wdt_miscdev);
mpcore_wdt_dev = NULL;
free_irq(wdt->irq, wdt);
iounmap(wdt->base);
kfree(wdt);
return 0;
}
static struct platform_driver mpcore_wdt_driver = {
.probe = mpcore_wdt_probe,
.remove = __devexit_p(mpcore_wdt_remove),
.shutdown = mpcore_wdt_shutdown,
.driver = {
.owner = THIS_MODULE,
.name = "mpcore_wdt",
},
};
static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. mpcore_noboot=%d mpcore_margin=%d sec (nowayout= %d)\n";
static int __init mpcore_wdt_init(void)
{
/*
* Check that the margin value is within it's range;
* if not reset to the default
*/
if (mpcore_wdt_set_heartbeat(mpcore_margin)) {
mpcore_wdt_set_heartbeat(TIMER_MARGIN);
printk(KERN_INFO "mpcore_margin value must be 0<mpcore_margin<65536, using %d\n",
TIMER_MARGIN);
}
printk(banner, mpcore_noboot, mpcore_margin, nowayout);
return platform_driver_register(&mpcore_wdt_driver);
}
static void __exit mpcore_wdt_exit(void)
{
platform_driver_unregister(&mpcore_wdt_driver);
}
module_init(mpcore_wdt_init);
module_exit(mpcore_wdt_exit);
MODULE_AUTHOR("ARM Limited");
MODULE_DESCRIPTION("MPcore Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,239 @@
/*
* 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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach-au1x00/au1000.h>
#define MTX1_WDT_INTERVAL (5 * HZ)
static int ticks = 100 * HZ;
static struct {
struct completion stop;
volatile int running;
struct timer_list timer;
volatile int queue;
int default_ticks;
unsigned long inuse;
} mtx1_wdt_device;
static void mtx1_wdt_trigger(unsigned long unused)
{
u32 tmp;
if (mtx1_wdt_device.running)
ticks--;
/*
* toggle GPIO2_15
*/
tmp = au_readl(GPIO2_DIR);
tmp = (tmp & ~(1<<15)) | ((~tmp) & (1<<15));
au_writel (tmp, GPIO2_DIR);
if (mtx1_wdt_device.queue && ticks)
mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL);
else {
complete(&mtx1_wdt_device.stop);
}
}
static void mtx1_wdt_reset(void)
{
ticks = mtx1_wdt_device.default_ticks;
}
static void mtx1_wdt_start(void)
{
if (!mtx1_wdt_device.queue) {
mtx1_wdt_device.queue = 1;
au_writel (au_readl(GPIO2_DIR) | (u32)(1<<15), GPIO2_DIR);
mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL);
}
mtx1_wdt_device.running++;
}
static int mtx1_wdt_stop(void)
{
if (mtx1_wdt_device.queue) {
mtx1_wdt_device.queue = 0;
au_writel (au_readl(GPIO2_DIR) & ~((u32)(1<<15)), GPIO2_DIR);
}
ticks = mtx1_wdt_device.default_ticks;
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 int mtx1_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
unsigned int value;
static struct watchdog_info ident =
{
.options = WDIOF_CARDRESET,
.identity = "MTX-1 WDT",
};
switch(cmd) {
case WDIOC_KEEPALIVE:
mtx1_wdt_reset();
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
if ( copy_to_user(argp, &value, sizeof(int)) )
return -EFAULT;
break;
case WDIOC_GETSUPPORT:
if ( copy_to_user(argp, &ident, sizeof(ident)) )
return -EFAULT;
break;
case WDIOC_SETOPTIONS:
if ( copy_from_user(&value, argp, sizeof(int)) )
return -EFAULT;
switch(value) {
case WDIOS_ENABLECARD:
mtx1_wdt_start();
break;
case WDIOS_DISABLECARD:
return mtx1_wdt_stop();
default:
return -EINVAL;
}
break;
default:
return -ENOTTY;
}
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 struct file_operations mtx1_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.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 __init mtx1_wdt_init(void)
{
int ret;
if ((ret = misc_register(&mtx1_wdt_misc)) < 0) {
printk(KERN_ERR " mtx-1_wdt : failed to register\n");
return ret;
}
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;
mtx1_wdt_start();
printk(KERN_INFO "MTX-1 Watchdog driver\n");
return 0;
}
static void __exit mtx1_wdt_exit(void)
{
if (mtx1_wdt_device.queue) {
mtx1_wdt_device.queue = 0;
wait_for_completion(&mtx1_wdt_device.stop);
}
misc_deregister(&mtx1_wdt_misc);
}
module_init(mtx1_wdt_init);
module_exit(mtx1_wdt_exit);
MODULE_AUTHOR("Michael Stickel, Florian Fainelli");
MODULE_DESCRIPTION("Driver for the MTX-1 watchdog");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,326 @@
/*
* 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.
*/
#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 <asm/uaccess.h>
#include <asm/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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static 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();
printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n");
}
}
static void mv64x60_wdt_handler_disable(void)
{
if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE,
MV64x60_WDC_ENABLE_SHIFT))
printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n");
}
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 {
printk(KERN_CRIT
"mv64x60_wdt: 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 int mv64x60_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int timeout;
int options;
void __user *argp = (void __user *)arg;
static 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,
.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, r->end - r->start + 1);
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)
{
printk(KERN_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);

389
drivers/watchdog/omap_wdt.c Normal file
View File

@@ -0,0 +1,389 @@
/*
* linux/drivers/char/watchdog/omap_wdt.c
*
* Watchdog driver for the TI OMAP 16xx & 24xx 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@redhat.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/moduleparam.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/bitops.h>
#include <asm/arch/prcm.h>
#include "omap_wdt.h"
static unsigned timer_margin;
module_param(timer_margin, uint, 0);
MODULE_PARM_DESC(timer_margin, "initial watchdog timeout (in seconds)");
static int omap_wdt_users;
static struct clk *armwdt_ck = NULL;
static struct clk *mpu_wdt_ick = NULL;
static struct clk *mpu_wdt_fck = NULL;
static unsigned int wdt_trgr_pattern = 0x1234;
static void omap_wdt_ping(void)
{
/* wait for posted write to complete */
while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08)
cpu_relax();
wdt_trgr_pattern = ~wdt_trgr_pattern;
omap_writel(wdt_trgr_pattern, (OMAP_WATCHDOG_TGR));
/* wait for posted write to complete */
while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x08)
cpu_relax();
/* reloaded WCRR from WLDR */
}
static void omap_wdt_enable(void)
{
/* Sequence to enable the watchdog */
omap_writel(0xBBBB, OMAP_WATCHDOG_SPR);
while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10)
cpu_relax();
omap_writel(0x4444, OMAP_WATCHDOG_SPR);
while ((omap_readl(OMAP_WATCHDOG_WPS)) & 0x10)
cpu_relax();
}
static void omap_wdt_disable(void)
{
/* sequence required to disable watchdog */
omap_writel(0xAAAA, OMAP_WATCHDOG_SPR); /* TIMER_MODE */
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10)
cpu_relax();
omap_writel(0x5555, OMAP_WATCHDOG_SPR); /* TIMER_MODE */
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x10)
cpu_relax();
}
static void omap_wdt_adjust_timeout(unsigned new_timeout)
{
if (new_timeout < TIMER_MARGIN_MIN)
new_timeout = TIMER_MARGIN_DEFAULT;
if (new_timeout > TIMER_MARGIN_MAX)
new_timeout = TIMER_MARGIN_MAX;
timer_margin = new_timeout;
}
static void omap_wdt_set_timeout(void)
{
u32 pre_margin = GET_WLDR_VAL(timer_margin);
/* just count up at 32 KHz */
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04)
cpu_relax();
omap_writel(pre_margin, OMAP_WATCHDOG_LDR);
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x04)
cpu_relax();
}
/*
* Allow only one task to hold it open
*/
static int omap_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(1, (unsigned long *)&omap_wdt_users))
return -EBUSY;
if (cpu_is_omap16xx())
clk_enable(armwdt_ck); /* Enable the clock */
if (cpu_is_omap24xx()) {
clk_enable(mpu_wdt_ick); /* Enable the interface clock */
clk_enable(mpu_wdt_fck); /* Enable the functional clock */
}
/* initialize prescaler */
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01)
cpu_relax();
omap_writel((1 << 5) | (PTV << 2), OMAP_WATCHDOG_CNTRL);
while (omap_readl(OMAP_WATCHDOG_WPS) & 0x01)
cpu_relax();
omap_wdt_set_timeout();
omap_wdt_enable();
return nonseekable_open(inode, file);
}
static int omap_wdt_release(struct inode *inode, struct file *file)
{
/*
* Shut off the timer unless NOWAYOUT is defined.
*/
#ifndef CONFIG_WATCHDOG_NOWAYOUT
omap_wdt_disable();
if (cpu_is_omap16xx()) {
clk_disable(armwdt_ck); /* Disable the clock */
clk_put(armwdt_ck);
armwdt_ck = NULL;
}
if (cpu_is_omap24xx()) {
clk_disable(mpu_wdt_ick); /* Disable the clock */
clk_disable(mpu_wdt_fck); /* Disable the clock */
clk_put(mpu_wdt_ick);
clk_put(mpu_wdt_fck);
mpu_wdt_ick = NULL;
mpu_wdt_fck = NULL;
}
#else
printk(KERN_CRIT "omap_wdt: Unexpected close, not stopping!\n");
#endif
omap_wdt_users = 0;
return 0;
}
static ssize_t
omap_wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
/* Refresh LOAD_TIME. */
if (len)
omap_wdt_ping();
return len;
}
static int
omap_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_margin;
static struct watchdog_info ident = {
.identity = "OMAP Watchdog",
.options = WDIOF_SETTIMEOUT,
.firmware_version = 0,
};
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user((struct watchdog_info __user *)arg, &ident,
sizeof(ident));
case WDIOC_GETSTATUS:
return put_user(0, (int __user *)arg);
case WDIOC_GETBOOTSTATUS:
if (cpu_is_omap16xx())
return put_user(omap_readw(ARM_SYSST),
(int __user *)arg);
if (cpu_is_omap24xx())
return put_user(omap_prcm_get_reset_sources(),
(int __user *)arg);
case WDIOC_KEEPALIVE:
omap_wdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, (int __user *)arg))
return -EFAULT;
omap_wdt_adjust_timeout(new_margin);
omap_wdt_disable();
omap_wdt_set_timeout();
omap_wdt_enable();
omap_wdt_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(timer_margin, (int __user *)arg);
}
}
static const struct file_operations omap_wdt_fops = {
.owner = THIS_MODULE,
.write = omap_wdt_write,
.ioctl = omap_wdt_ioctl,
.open = omap_wdt_open,
.release = omap_wdt_release,
};
static struct miscdevice omap_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &omap_wdt_fops
};
static int __init omap_wdt_probe(struct platform_device *pdev)
{
struct resource *res, *mem;
int ret;
/* reserve static register mappings */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENOENT;
mem = request_mem_region(res->start, res->end - res->start + 1,
pdev->name);
if (mem == NULL)
return -EBUSY;
platform_set_drvdata(pdev, mem);
omap_wdt_users = 0;
if (cpu_is_omap16xx()) {
armwdt_ck = clk_get(&pdev->dev, "armwdt_ck");
if (IS_ERR(armwdt_ck)) {
ret = PTR_ERR(armwdt_ck);
armwdt_ck = NULL;
goto fail;
}
}
if (cpu_is_omap24xx()) {
mpu_wdt_ick = clk_get(&pdev->dev, "mpu_wdt_ick");
if (IS_ERR(mpu_wdt_ick)) {
ret = PTR_ERR(mpu_wdt_ick);
mpu_wdt_ick = NULL;
goto fail;
}
mpu_wdt_fck = clk_get(&pdev->dev, "mpu_wdt_fck");
if (IS_ERR(mpu_wdt_fck)) {
ret = PTR_ERR(mpu_wdt_fck);
mpu_wdt_fck = NULL;
goto fail;
}
}
omap_wdt_disable();
omap_wdt_adjust_timeout(timer_margin);
omap_wdt_miscdev.parent = &pdev->dev;
ret = misc_register(&omap_wdt_miscdev);
if (ret)
goto fail;
pr_info("OMAP Watchdog Timer: initial timeout %d sec\n", timer_margin);
/* autogate OCP interface clock */
omap_writel(0x01, OMAP_WATCHDOG_SYS_CONFIG);
return 0;
fail:
if (armwdt_ck)
clk_put(armwdt_ck);
if (mpu_wdt_ick)
clk_put(mpu_wdt_ick);
if (mpu_wdt_fck)
clk_put(mpu_wdt_fck);
release_resource(mem);
return ret;
}
static void omap_wdt_shutdown(struct platform_device *pdev)
{
omap_wdt_disable();
}
static int omap_wdt_remove(struct platform_device *pdev)
{
struct resource *mem = platform_get_drvdata(pdev);
misc_deregister(&omap_wdt_miscdev);
release_resource(mem);
if (armwdt_ck)
clk_put(armwdt_ck);
if (mpu_wdt_ick)
clk_put(mpu_wdt_ick);
if (mpu_wdt_fck)
clk_put(mpu_wdt_fck);
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)
{
if (omap_wdt_users)
omap_wdt_disable();
return 0;
}
static int omap_wdt_resume(struct platform_device *pdev)
{
if (omap_wdt_users) {
omap_wdt_enable();
omap_wdt_ping();
}
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 = omap_wdt_remove,
.shutdown = omap_wdt_shutdown,
.suspend = omap_wdt_suspend,
.resume = omap_wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = "omap_wdt",
},
};
static int __init omap_wdt_init(void)
{
return platform_driver_register(&omap_wdt_driver);
}
static void __exit omap_wdt_exit(void)
{
platform_driver_unregister(&omap_wdt_driver);
}
module_init(omap_wdt_init);
module_exit(omap_wdt_exit);
MODULE_AUTHOR("George G. Davis");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,64 @@
/*
* 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 OMAP1610_WATCHDOG_BASE 0xfffeb000
#define OMAP2420_WATCHDOG_BASE 0x48022000 /*WDT Timer 2 */
#ifdef CONFIG_ARCH_OMAP24XX
#define OMAP_WATCHDOG_BASE OMAP2420_WATCHDOG_BASE
#else
#define OMAP_WATCHDOG_BASE OMAP1610_WATCHDOG_BASE
#define RM_RSTST_WKUP 0
#endif
#define OMAP_WATCHDOG_REV (OMAP_WATCHDOG_BASE + 0x00)
#define OMAP_WATCHDOG_SYS_CONFIG (OMAP_WATCHDOG_BASE + 0x10)
#define OMAP_WATCHDOG_STATUS (OMAP_WATCHDOG_BASE + 0x14)
#define OMAP_WATCHDOG_CNTRL (OMAP_WATCHDOG_BASE + 0x24)
#define OMAP_WATCHDOG_CRR (OMAP_WATCHDOG_BASE + 0x28)
#define OMAP_WATCHDOG_LDR (OMAP_WATCHDOG_BASE + 0x2c)
#define OMAP_WATCHDOG_TGR (OMAP_WATCHDOG_BASE + 0x30)
#define OMAP_WATCHDOG_WPS (OMAP_WATCHDOG_BASE + 0x34)
#define OMAP_WATCHDOG_SPR (OMAP_WATCHDOG_BASE + 0x48)
/* 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 */

View File

@@ -0,0 +1,635 @@
/*
* 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
*/
#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/version.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
/* #define DEBUG 1 */
#define DEFAULT_TIMEOUT 1 /* 1 minute */
#define MAX_TIMEOUT 255
#define VERSION "1.1"
#define MODNAME "pc87413 WDT"
#define PFX MODNAME ": "
#define DPFX MODNAME " - DEBUG: "
#define WDT_INDEX_IO_PORT (io+0) /* I/O port base (index register) */
#define WDT_DATA_IO_PORT (WDT_INDEX_IO_PORT+1)
#define SWC_LDN 0x04
#define SIOCFG2 0x22 /* Serial IO register */
#define WDCTL 0x10 /* Watchdog-Timer-Controll-Register */
#define WDTO 0x11 /* Watchdog timeout register */
#define WDCFG 0x12 /* Watchdog config register */
static int io = 0x2E; /* Address used on Portwell Boards */
static int timeout = DEFAULT_TIMEOUT; /* timeout value */
static unsigned long timer_enabled = 0; /* is the timer enabled? */
static char expect_close; /* is the close expected? */
static spinlock_t io_lock; /* to guard the watchdog from io races */
static int 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
printk(KERN_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
printk(KERN_INFO DPFX "pc87413 - Enable SWC functions\n");
#endif
}
/* Read SWC I/O base address */
static inline unsigned int pc87413_get_swc_base(void)
{
unsigned int swc_base_addr = 0;
unsigned char addr_l, addr_h = 0;
/* Step 3: Read SWC I/O Base Address */
outb_p(0x60, WDT_INDEX_IO_PORT); /* Read Index 0x60 */
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
printk(KERN_INFO DPFX "Read SWC I/O Base Address: low %d, high %d,"
" res %d\n", addr_l, addr_h, swc_base_addr);
#endif
return swc_base_addr;
}
/* Select Bank 3 of SWC */
static inline void pc87413_swc_bank3(unsigned int swc_base_addr)
{
/* Step 4: Select Bank3 of SWC */
outb_p(inb(swc_base_addr + 0x0f) | 0x03, swc_base_addr + 0x0f);
#ifdef DEBUG
printk(KERN_INFO DPFX "Select Bank3 of SWC\n");
#endif
}
/* Set watchdog timeout to x minutes */
static inline void pc87413_programm_wdto(unsigned int swc_base_addr,
char pc87413_time)
{
/* Step 5: Programm WDTO, Twd. */
outb_p(pc87413_time, swc_base_addr + WDTO);
#ifdef DEBUG
printk(KERN_INFO DPFX "Set WDTO to %d minutes\n", pc87413_time);
#endif
}
/* Enable WDEN */
static inline void pc87413_enable_wden(unsigned int swc_base_addr)
{
/* Step 6: Enable WDEN */
outb_p(inb (swc_base_addr + WDCTL) | 0x01, swc_base_addr + WDCTL);
#ifdef DEBUG
printk(KERN_INFO DPFX "Enable WDEN\n");
#endif
}
/* Enable SW_WD_TREN */
static inline void pc87413_enable_sw_wd_tren(unsigned int swc_base_addr)
{
/* Enable SW_WD_TREN */
outb_p(inb (swc_base_addr + WDCFG) | 0x80, swc_base_addr + WDCFG);
#ifdef DEBUG
printk(KERN_INFO DPFX "Enable SW_WD_TREN\n");
#endif
}
/* Disable SW_WD_TREN */
static inline void pc87413_disable_sw_wd_tren(unsigned int swc_base_addr)
{
/* Disable SW_WD_TREN */
outb_p(inb (swc_base_addr + WDCFG) & 0x7f, swc_base_addr + WDCFG);
#ifdef DEBUG
printk(KERN_INFO DPFX "pc87413 - Disable SW_WD_TREN\n");
#endif
}
/* Enable SW_WD_TRG */
static inline void pc87413_enable_sw_wd_trg(unsigned int swc_base_addr)
{
/* Enable SW_WD_TRG */
outb_p(inb (swc_base_addr + WDCTL) | 0x80, swc_base_addr + WDCTL);
#ifdef DEBUG
printk(KERN_INFO DPFX "pc87413 - Enable SW_WD_TRG\n");
#endif
}
/* Disable SW_WD_TRG */
static inline void pc87413_disable_sw_wd_trg(unsigned int swc_base_addr)
{
/* Disable SW_WD_TRG */
outb_p(inb (swc_base_addr + WDCTL) & 0x7f, swc_base_addr + WDCTL);
#ifdef DEBUG
printk(KERN_INFO DPFX "Disable SW_WD_TRG\n");
#endif
}
/* -- Higher level functions ------------------------------------*/
/* Enable the watchdog */
static void pc87413_enable(void)
{
unsigned int swc_base_addr;
spin_lock(&io_lock);
pc87413_select_wdt_out();
pc87413_enable_swc();
swc_base_addr = pc87413_get_swc_base();
pc87413_swc_bank3(swc_base_addr);
pc87413_programm_wdto(swc_base_addr, timeout);
pc87413_enable_wden(swc_base_addr);
pc87413_enable_sw_wd_tren(swc_base_addr);
pc87413_enable_sw_wd_trg(swc_base_addr);
spin_unlock(&io_lock);
}
/* Disable the watchdog */
static void pc87413_disable(void)
{
unsigned int swc_base_addr;
spin_lock(&io_lock);
pc87413_select_wdt_out();
pc87413_enable_swc();
swc_base_addr = pc87413_get_swc_base();
pc87413_swc_bank3(swc_base_addr);
pc87413_disable_sw_wd_tren(swc_base_addr);
pc87413_disable_sw_wd_trg(swc_base_addr);
pc87413_programm_wdto(swc_base_addr, 0);
spin_unlock(&io_lock);
}
/* Refresh the watchdog */
static void pc87413_refresh(void)
{
unsigned int swc_base_addr;
spin_lock(&io_lock);
pc87413_select_wdt_out();
pc87413_enable_swc();
swc_base_addr = pc87413_get_swc_base();
pc87413_swc_bank3(swc_base_addr);
pc87413_disable_sw_wd_tren(swc_base_addr);
pc87413_disable_sw_wd_trg(swc_base_addr);
pc87413_programm_wdto(swc_base_addr, timeout);
pc87413_enable_wden(swc_base_addr);
pc87413_enable_sw_wd_tren(swc_base_addr);
pc87413_enable_sw_wd_trg(swc_base_addr);
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();
printk(KERN_INFO MODNAME "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();
printk(KERN_INFO MODNAME "Watchdog disabled,"
" sleeping again...\n");
} else {
printk(KERN_CRIT MODNAME "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:
* @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. We only actually usefully support
* querying capabilities and current status.
*/
static int pc87413_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_timeout;
union {
struct watchdog_info __user *ident;
int __user *i;
} uarg;
static 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) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(uarg.ident, &ident,
sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
return put_user(pc87413_status(), uarg.i);
case WDIOC_GETBOOTSTATUS:
return put_user(0, uarg.i);
case WDIOC_KEEPALIVE:
pc87413_refresh();
#ifdef DEBUG
printk(KERN_INFO DPFX "keepalive\n");
#endif
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, uarg.i))
return -EFAULT;
// the API states this is given in secs
new_timeout /= 60;
if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
return -EINVAL;
timeout = new_timeout;
pc87413_refresh();
// fall through and return the new timeout...
case WDIOC_GETTIMEOUT:
new_timeout = timeout * 60;
return put_user(new_timeout, uarg.i);
case WDIOC_SETOPTIONS:
{
int options, retval = -EINVAL;
if (get_user(options, uarg.i))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
pc87413_disable();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
pc87413_enable();
retval = 0;
}
return retval;
}
}
}
/* -- 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,
.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;
spin_lock_init(&io_lock);
printk(KERN_INFO PFX "Version " VERSION " at io 0x%X\n", WDT_INDEX_IO_PORT);
/* request_region(io, 2, "pc87413"); */
ret = register_reboot_notifier(&pc87413_notifier);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
}
ret = misc_register(&pc87413_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&pc87413_notifier);
return ret;
}
printk(KERN_INFO PFX "initialized. timeout=%d min \n", timeout);
pc87413_enable();
return 0;
}
/**
* 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();
printk(KERN_INFO MODNAME "Watchdog disabled.\n");
}
misc_deregister(&pc87413_miscdev);
unregister_reboot_notifier(&pc87413_notifier);
/* release_region(io,2); */
printk(MODNAME " 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) ").");
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes (default=" __MODULE_STRING(timeout) ").");
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

1013
drivers/watchdog/pcwd.c Normal file

File diff suppressed because it is too large Load Diff

832
drivers/watchdog/pcwd_pci.c Normal file
View File

@@ -0,0 +1,832 @@
/*
* 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@redhat.com>,
* 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, ...
*/
#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 <asm/uaccess.h> /* For copy_to_user/put_user/... */
#include <asm/io.h> /* For inb/outb/... */
/* Module and version information */
#define WATCHDOG_VERSION "1.03"
#define WATCHDOG_DATE "21 Jan 2007"
#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog"
#define WATCHDOG_NAME "pcwd_pci"
#define PFX WATCHDOG_NAME ": "
#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n"
/* 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;
static struct { /* this is private data for each PCI-PC watchdog card */
int supports_temp; /* Wether or not the card has a temperature device */
int boot_status; /* The card's boot status */
unsigned long io_addr; /* The cards I/O address */
spinlock_t io_lock; /* the lock for io operations */
struct pci_dev *pdev; /* the PCI-device */
} 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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)
printk(KERN_DEBUG PFX "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) {
printk(KERN_DEBUG PFX "time to process command was: %d ms\n",
count);
} else {
printk(KERN_DEBUG PFX "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)
printk(KERN_DEBUG PFX "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();
printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n",
(int) pcipcwd_private.io_addr, fw_ver_str,
(pcipcwd_private.supports_temp ? "with" : "without"));
printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
option_switches,
((option_switches & 0x10) ? "ON" : "OFF"),
((option_switches & 0x08) ? "ON" : "OFF"));
if (pcipcwd_private.boot_status & WDIOF_CARDRESET)
printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n");
if (pcipcwd_private.boot_status & WDIOF_OVERHEAT)
printk(KERN_INFO PFX "Card sensed a CPU Overheat\n");
if (pcipcwd_private.boot_status == 0)
printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n");
}
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) {
printk(KERN_ERR PFX "Card timer not enabled\n");
return -1;
}
if (debug >= VERBOSE)
printk(KERN_DEBUG PFX "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)) {
printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
return -1;
}
if (debug >= VERBOSE)
printk(KERN_DEBUG PFX "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)
printk(KERN_DEBUG PFX "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)
printk(KERN_DEBUG PFX "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(PFX "Temperature overheat trip!\n");
}
if (debug >= DEBUG)
printk(KERN_DEBUG PFX "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)
printk(KERN_INFO PFX "clearing watchdog trip status & LED\n");
control_status = inb_p(pcipcwd_private.io_addr + 1);
if (debug >= DEBUG) {
printk(KERN_DEBUG PFX "status was: 0x%02x\n", control_status);
printk(KERN_DEBUG PFX "sending: 0x%02x\n",
(control_status & WD_PCI_R2DS) | WD_PCI_WTRP);
}
/* 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) {
printk(KERN_DEBUG PFX "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) {
printk(KERN_DEBUG PFX "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)
printk(KERN_DEBUG PFX "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 int pcipcwd_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_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_KEEPALIVE:
pcipcwd_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if (get_user (new_options, p))
return -EFAULT;
if (new_options & WDIOS_DISABLECARD) {
if (pcipcwd_stop())
return -EIO;
retval = 0;
}
if (new_options & WDIOS_ENABLECARD) {
if (pcipcwd_start())
return -EIO;
retval = 0;
}
if (new_options & WDIOS_TEMPPANIC) {
temp_panic = 1;
retval = 0;
}
return retval;
}
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)
printk(KERN_ERR PFX "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 {
printk(KERN_CRIT PFX "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) {
/* Turn the WDT off */
pcipcwd_stop();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations pcipcwd_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = pcipcwd_write,
.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)
printk(KERN_INFO PFX DRIVER_VERSION);
if (cards_found > 1) {
printk(KERN_ERR PFX "This driver only supports 1 device\n");
return -ENODEV;
}
if (pci_enable_device(pdev)) {
printk(KERN_ERR PFX "Not possible to enable PCI Device\n");
return -ENODEV;
}
if (pci_resource_start(pdev, 0) == 0x0000) {
printk(KERN_ERR PFX "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)) {
printk(KERN_ERR PFX "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);
printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
WATCHDOG_HEARTBEAT);
}
ret = register_reboot_notifier(&pcipcwd_notifier);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto err_out_release_region;
}
if (pcipcwd_private.supports_temp) {
ret = misc_register(&pcipcwd_temp_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
TEMP_MINOR, ret);
goto err_out_unregister_reboot;
}
}
ret = misc_register(&pcipcwd_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto err_out_misc_deregister;
}
printk(KERN_INFO PFX "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 struct pci_device_id pcipcwd_pci_tbl[] = {
{ PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD,
PCI_ANY_ID, PCI_ANY_ID, },
{ 0 }, /* End of list */
};
MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl);
static struct pci_driver pcipcwd_driver = {
.name = WATCHDOG_NAME,
.id_table = pcipcwd_pci_tbl,
.probe = pcipcwd_card_init,
.remove = __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);
printk(KERN_INFO PFX "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);

824
drivers/watchdog/pcwd_usb.c Normal file
View File

@@ -0,0 +1,824 @@
/*
* 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@redhat.com>,
* 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/
*/
#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 <asm/uaccess.h> /* For copy_to_user/put_user/... */
#ifdef CONFIG_USB_DEBUG
static int debug = 1;
#else
static int debug;
#endif
/* Use our own dbg macro */
#undef dbg
#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0)
/* Module and Version Information */
#define DRIVER_VERSION "1.02"
#define DRIVER_DATE "21 Jan 2007"
#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
#define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
#define DRIVER_LICENSE "GPL"
#define DRIVER_NAME "pcwd_usb"
#define PFX DRIVER_NAME ": "
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE(DRIVER_LICENSE);
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
MODULE_ALIAS_MISCDEV(TEMP_MINOR);
/* Module Parameters */
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug enabled or not");
#define WATCHDOG_HEARTBEAT 0 /* default heartbeat = delay-time from dip-switches */
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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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 Watchdog Time */
#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 {
struct usb_device * udev; /* save off the usb device pointer */
struct usb_interface * interface; /* the interface for this device */
unsigned int interface_number; /* the interface number used for cmd's */
unsigned char * intr_buffer; /* the buffer to intr data */
dma_addr_t intr_dma; /* the dma address for the intr buffer */
size_t intr_size; /* the size of the intr buffer */
struct urb * intr_urb; /* the urb used for the intr pipe */
unsigned char cmd_command; /* The command that is reported back */
unsigned char cmd_data_msb; /* The data MSB that is reported back */
unsigned char cmd_data_lsb; /* The data LSB that is reported back */
atomic_t cmd_received; /* true if we received a report after a command */
int exists; /* Wether or not the device exists */
struct mutex mtx; /* locks this structure */
};
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", __FUNCTION__, urb->status);
return;
/* -EPIPE: should clear the halt */
default: /* error */
dbg("%s - nonzero urb status received: %d", __FUNCTION__, 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)
printk(KERN_ERR PFX "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)) {
printk(KERN_ERR PFX "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)) {
printk(KERN_ERR PFX "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 int usb_pcwd_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING |
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_KEEPALIVE:
usb_pcwd_keepalive(usb_pcwd_device);
return 0;
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_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 {
printk(KERN_CRIT PFX "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) {
/* Turn the WDT off */
usb_pcwd_stop(usb_pcwd_device);
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations usb_pcwd_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = usb_pcwd_write,
.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_buffer_free(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) {
printk(KERN_ERR PFX "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)) {
printk(KERN_ERR PFX "The device isn't a Human Interface Device\n");
return -ENODEV;
}
/* check out the endpoint: it has to be Interrupt & IN */
endpoint = &iface_desc->endpoint[0].desc;
if (!((endpoint->bEndpointAddress & USB_DIR_IN) &&
((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
== USB_ENDPOINT_XFER_INT))) {
/* we didn't find a Interrupt endpoint with direction IN */
printk(KERN_ERR PFX "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) {
printk(KERN_ERR PFX "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 */
if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, GFP_ATOMIC, &usb_pcwd->intr_dma))) {
printk(KERN_ERR PFX "Out of memory\n");
goto error;
}
/* allocate the urb's */
usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!usb_pcwd->intr_urb) {
printk(KERN_ERR PFX "Out of memory\n");
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)) {
printk(KERN_ERR PFX "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>");
}
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
WATCHDOG_HEARTBEAT);
}
retval = register_reboot_notifier(&usb_pcwd_notifier);
if (retval != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
retval);
goto error;
}
retval = misc_register(&usb_pcwd_temperature_miscdev);
if (retval != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
TEMP_MINOR, retval);
goto err_out_unregister_reboot;
}
retval = misc_register(&usb_pcwd_miscdev);
if (retval != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, retval);
goto err_out_misc_deregister;
}
/* we can register the device now, as it is ready */
usb_set_intfdata (interface, usb_pcwd);
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "USB PC Watchdog disconnected\n");
}
/**
* usb_pcwd_init
*/
static int __init usb_pcwd_init(void)
{
int result;
/* register this driver with the USB subsystem */
result = usb_register(&usb_pcwd_driver);
if (result) {
printk(KERN_ERR PFX "usb_register failed. Error number %d\n",
result);
return result;
}
printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n");
return 0;
}
/**
* usb_pcwd_exit
*/
static void __exit usb_pcwd_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&usb_pcwd_driver);
}
module_init (usb_pcwd_init);
module_exit (usb_pcwd_exit);

View File

@@ -0,0 +1,358 @@
/*
* 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. 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/ioport.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/spinlock.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MODULE_NAME "PNX4008-WDT: "
/* 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 int nowayout = WATCHDOG_NOWAYOUT;
static int heartbeat = DEFAULT_HEARTBEAT;
static spinlock_t io_lock;
static unsigned long wdt_status;
#define WDT_IN_USE 0
#define WDT_OK_TO_CLOSE 1
#define WDT_REGION_INITED 2
#define WDT_DEVICE_INITED 3
static unsigned long boot_status;
static struct resource *wdt_mem;
static void __iomem *wdt_base;
struct clk *wdt_clk;
static void wdt_enable(void)
{
spin_lock(&io_lock);
if (wdt_clk)
clk_set_rate(wdt_clk, 1);
/* stop counter, initiate counter reset */
__raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base));
/*wait for reset to complete. 100% guarantee event */
while (__raw_readl(WDTIM_COUNTER(wdt_base)))
cpu_relax();
/* internal and external reset, stop after that */
__raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0,
WDTIM_MCTRL(wdt_base));
/* configure match output */
__raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base));
/* clear interrupt, just in case */
__raw_writel(MATCH_INT, WDTIM_INT(wdt_base));
/* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */
__raw_writel(0xFFFF, WDTIM_PULSE(wdt_base));
__raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base));
/*enable counter, stop when debugger active */
__raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base));
spin_unlock(&io_lock);
}
static void wdt_disable(void)
{
spin_lock(&io_lock);
__raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */
if (wdt_clk)
clk_set_rate(wdt_clk, 0);
spin_unlock(&io_lock);
}
static int pnx4008_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(WDT_IN_USE, &wdt_status))
return -EBUSY;
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
wdt_enable();
return nonseekable_open(inode, file);
}
static ssize_t
pnx4008_wdt_write(struct file *file, const char *data, size_t len,
loff_t * ppos)
{
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 struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "PNX4008 Watchdog",
};
static int
pnx4008_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int ret = -ENOTTY;
int time;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user((struct watchdog_info *)arg, &ident,
sizeof(ident)) ? -EFAULT : 0;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int *)arg);
break;
case WDIOC_GETBOOTSTATUS:
ret = put_user(boot_status, (int *)arg);
break;
case WDIOC_SETTIMEOUT:
ret = get_user(time, (int *)arg);
if (ret)
break;
if (time <= 0 || time > MAX_HEARTBEAT) {
ret = -EINVAL;
break;
}
heartbeat = time;
wdt_enable();
/* Fall through */
case WDIOC_GETTIMEOUT:
ret = put_user(heartbeat, (int *)arg);
break;
case WDIOC_KEEPALIVE:
wdt_enable();
ret = 0;
break;
}
return ret;
}
static int pnx4008_wdt_release(struct inode *inode, struct file *file)
{
if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status))
printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n");
wdt_disable();
clear_bit(WDT_IN_USE, &wdt_status);
clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
return 0;
}
static const struct file_operations pnx4008_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = pnx4008_wdt_write,
.ioctl = pnx4008_wdt_ioctl,
.open = pnx4008_wdt_open,
.release = pnx4008_wdt_release,
};
static struct miscdevice pnx4008_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &pnx4008_wdt_fops,
};
static int pnx4008_wdt_probe(struct platform_device *pdev)
{
int ret = 0, size;
struct resource *res;
spin_lock_init(&io_lock);
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
heartbeat = DEFAULT_HEARTBEAT;
printk(KERN_INFO MODULE_NAME
"PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
printk(KERN_INFO MODULE_NAME
"failed to get memory region resouce\n");
return -ENOENT;
}
size = res->end - res->start + 1;
wdt_mem = request_mem_region(res->start, size, pdev->name);
if (wdt_mem == NULL) {
printk(KERN_INFO MODULE_NAME "failed to get memory region\n");
return -ENOENT;
}
wdt_base = (void __iomem *)IO_ADDRESS(res->start);
wdt_clk = clk_get(&pdev->dev, "wdt_ck");
if (IS_ERR(wdt_clk)) {
ret = PTR_ERR(wdt_clk);
release_resource(wdt_mem);
kfree(wdt_mem);
goto out;
} else
clk_set_rate(wdt_clk, 1);
ret = misc_register(&pnx4008_wdt_miscdev);
if (ret < 0) {
printk(KERN_ERR MODULE_NAME "cannot register misc device\n");
release_resource(wdt_mem);
kfree(wdt_mem);
clk_set_rate(wdt_clk, 0);
} else {
boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ?
WDIOF_CARDRESET : 0;
wdt_disable(); /*disable for now */
set_bit(WDT_DEVICE_INITED, &wdt_status);
}
out:
return ret;
}
static int pnx4008_wdt_remove(struct platform_device *pdev)
{
misc_deregister(&pnx4008_wdt_miscdev);
if (wdt_clk) {
clk_set_rate(wdt_clk, 0);
clk_put(wdt_clk);
wdt_clk = NULL;
}
if (wdt_mem) {
release_resource(wdt_mem);
kfree(wdt_mem);
wdt_mem = NULL;
}
return 0;
}
static struct platform_driver platform_wdt_driver = {
.driver = {
.name = "watchdog",
},
.probe = pnx4008_wdt_probe,
.remove = pnx4008_wdt_remove,
};
static int __init pnx4008_wdt_init(void)
{
return platform_driver_register(&platform_wdt_driver);
}
static void __exit pnx4008_wdt_exit(void)
{
return platform_driver_unregister(&platform_wdt_driver);
}
module_init(pnx4008_wdt_init);
module_exit(pnx4008_wdt_exit);
MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>");
MODULE_DESCRIPTION("PNX4008 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, int, 0);
MODULE_PARM_DESC(nowayout,
"Set to 1 to keep watchdog running after device release");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

420
drivers/watchdog/rm9k_wdt.c Normal file
View File

@@ -0,0 +1,420 @@
/*
* Watchdog implementation for GPI h/w found on PMC-Sierra RM9xxx
* chips.
*
* Copyright (C) 2004 by Basler Vision Technologies AG
* Author: Thomas Koeller <thomas.koeller@baslerweb.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/reboot.h>
#include <linux/notifier.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <asm/io.h>
#include <asm/atomic.h>
#include <asm/processor.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/rm9k-ocd.h>
#include <rm9k_wdt.h>
#define CLOCK 125000000
#define MAX_TIMEOUT_SECONDS 32
#define CPCCR 0x0080
#define CPGIG1SR 0x0044
#define CPGIG1ER 0x0054
/* Function prototypes */
static irqreturn_t wdt_gpi_irqhdl(int, void *);
static void wdt_gpi_start(void);
static void wdt_gpi_stop(void);
static void wdt_gpi_set_timeout(unsigned int);
static int wdt_gpi_open(struct inode *, struct file *);
static int wdt_gpi_release(struct inode *, struct file *);
static ssize_t wdt_gpi_write(struct file *, const char __user *, size_t, loff_t *);
static long wdt_gpi_ioctl(struct file *, unsigned int, unsigned long);
static int wdt_gpi_notify(struct notifier_block *, unsigned long, void *);
static const struct resource *wdt_gpi_get_resource(struct platform_device *, const char *, unsigned int);
static int __init wdt_gpi_probe(struct device *);
static int __exit wdt_gpi_remove(struct device *);
static const char wdt_gpi_name[] = "wdt_gpi";
static atomic_t opencnt;
static int expect_close;
static int locked;
/* These are set from device resources */
static void __iomem * wd_regs;
static unsigned int wd_irq, wd_ctr;
/* Module arguments */
static int timeout = MAX_TIMEOUT_SECONDS;
module_param(timeout, int, 0444);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
static unsigned long resetaddr = 0xbffdc200;
module_param(resetaddr, ulong, 0444);
MODULE_PARM_DESC(resetaddr, "Address to write to to force a reset");
static unsigned long flagaddr = 0xbffdc104;
module_param(flagaddr, ulong, 0444);
MODULE_PARM_DESC(flagaddr, "Address to write to boot flags to");
static int powercycle;
module_param(powercycle, bool, 0444);
MODULE_PARM_DESC(powercycle, "Cycle power if watchdog expires");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0444);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be disabled once started");
/* Kernel interfaces */
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = wdt_gpi_open,
.release = wdt_gpi_release,
.write = wdt_gpi_write,
.unlocked_ioctl = wdt_gpi_ioctl,
};
static struct miscdevice miscdev = {
.minor = WATCHDOG_MINOR,
.name = wdt_gpi_name,
.fops = &fops,
};
static struct notifier_block wdt_gpi_shutdown = {
.notifier_call = wdt_gpi_notify,
};
/* Interrupt handler */
static irqreturn_t wdt_gpi_irqhdl(int irq, void *ctxt)
{
if (!unlikely(__raw_readl(wd_regs + 0x0008) & 0x1))
return IRQ_NONE;
__raw_writel(0x1, wd_regs + 0x0008);
printk(KERN_CRIT "%s: watchdog expired - resetting system\n",
wdt_gpi_name);
*(volatile char *) flagaddr |= 0x01;
*(volatile char *) resetaddr = powercycle ? 0x01 : 0x2;
iob();
while (1)
cpu_relax();
}
/* Watchdog functions */
static void wdt_gpi_start(void)
{
u32 reg;
lock_titan_regs();
reg = titan_readl(CPGIG1ER);
titan_writel(reg | (0x100 << wd_ctr), CPGIG1ER);
iob();
unlock_titan_regs();
}
static void wdt_gpi_stop(void)
{
u32 reg;
lock_titan_regs();
reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4));
titan_writel(reg, CPCCR);
reg = titan_readl(CPGIG1ER);
titan_writel(reg & ~(0x100 << wd_ctr), CPGIG1ER);
iob();
unlock_titan_regs();
}
static void wdt_gpi_set_timeout(unsigned int to)
{
u32 reg;
const u32 wdval = (to * CLOCK) & ~0x0000000f;
lock_titan_regs();
reg = titan_readl(CPCCR) & ~(0xf << (wd_ctr * 4));
titan_writel(reg, CPCCR);
wmb();
__raw_writel(wdval, wd_regs + 0x0000);
wmb();
titan_writel(reg | (0x2 << (wd_ctr * 4)), CPCCR);
wmb();
titan_writel(reg | (0x5 << (wd_ctr * 4)), CPCCR);
iob();
unlock_titan_regs();
}
/* /dev/watchdog operations */
static int wdt_gpi_open(struct inode *inode, struct file *file)
{
int res;
if (unlikely(atomic_dec_if_positive(&opencnt) < 0))
return -EBUSY;
expect_close = 0;
if (locked) {
module_put(THIS_MODULE);
free_irq(wd_irq, &miscdev);
locked = 0;
}
res = request_irq(wd_irq, wdt_gpi_irqhdl, IRQF_SHARED | IRQF_DISABLED,
wdt_gpi_name, &miscdev);
if (unlikely(res))
return res;
wdt_gpi_set_timeout(timeout);
wdt_gpi_start();
printk(KERN_INFO "%s: watchdog started, timeout = %u seconds\n",
wdt_gpi_name, timeout);
return nonseekable_open(inode, file);
}
static int wdt_gpi_release(struct inode *inode, struct file *file)
{
if (nowayout) {
printk(KERN_INFO "%s: no way out - watchdog left running\n",
wdt_gpi_name);
__module_get(THIS_MODULE);
locked = 1;
} else {
if (expect_close) {
wdt_gpi_stop();
free_irq(wd_irq, &miscdev);
printk(KERN_INFO "%s: watchdog stopped\n", wdt_gpi_name);
} else {
printk(KERN_CRIT "%s: unexpected close() -"
" watchdog left running\n",
wdt_gpi_name);
wdt_gpi_set_timeout(timeout);
__module_get(THIS_MODULE);
locked = 1;
}
}
atomic_inc(&opencnt);
return 0;
}
static ssize_t
wdt_gpi_write(struct file *f, const char __user *d, size_t s, loff_t *o)
{
char val;
wdt_gpi_set_timeout(timeout);
expect_close = (s > 0) && !get_user(val, d) && (val == 'V');
return s ? 1 : 0;
}
static long
wdt_gpi_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
long res = -ENOTTY;
const long size = _IOC_SIZE(cmd);
int stat;
void __user *argp = (void __user *)arg;
static struct watchdog_info wdinfo = {
.identity = "RM9xxx/GPI watchdog",
.firmware_version = 0,
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
};
if (unlikely(_IOC_TYPE(cmd) != WATCHDOG_IOCTL_BASE))
return -ENOTTY;
if ((_IOC_DIR(cmd) & _IOC_READ)
&& !access_ok(VERIFY_WRITE, arg, size))
return -EFAULT;
if ((_IOC_DIR(cmd) & _IOC_WRITE)
&& !access_ok(VERIFY_READ, arg, size))
return -EFAULT;
expect_close = 0;
switch (cmd) {
case WDIOC_GETSUPPORT:
wdinfo.options = nowayout ?
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING :
WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE;
res = __copy_to_user(argp, &wdinfo, size) ? -EFAULT : size;
break;
case WDIOC_GETSTATUS:
break;
case WDIOC_GETBOOTSTATUS:
stat = (*(volatile char *) flagaddr & 0x01)
? WDIOF_CARDRESET : 0;
res = __copy_to_user(argp, &stat, size) ?
-EFAULT : size;
break;
case WDIOC_SETOPTIONS:
break;
case WDIOC_KEEPALIVE:
wdt_gpi_set_timeout(timeout);
res = size;
break;
case WDIOC_SETTIMEOUT:
{
int val;
if (unlikely(__copy_from_user(&val, argp, size))) {
res = -EFAULT;
break;
}
if (val > MAX_TIMEOUT_SECONDS)
val = MAX_TIMEOUT_SECONDS;
timeout = val;
wdt_gpi_set_timeout(val);
res = size;
printk(KERN_INFO "%s: timeout set to %u seconds\n",
wdt_gpi_name, timeout);
}
break;
case WDIOC_GETTIMEOUT:
res = __copy_to_user(argp, &timeout, size) ?
-EFAULT : size;
break;
}
return res;
}
/* Shutdown notifier */
static int
wdt_gpi_notify(struct notifier_block *this, unsigned long code, void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT)
wdt_gpi_stop();
return NOTIFY_DONE;
}
/* Init & exit procedures */
static const struct resource *
wdt_gpi_get_resource(struct platform_device *pdv, const char *name,
unsigned int type)
{
char buf[80];
if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf)
return NULL;
return platform_get_resource_byname(pdv, type, buf);
}
/* No hotplugging on the platform bus - use __init */
static int __init wdt_gpi_probe(struct device *dev)
{
int res;
struct platform_device * const pdv = to_platform_device(dev);
const struct resource
* const rr = wdt_gpi_get_resource(pdv, WDT_RESOURCE_REGS,
IORESOURCE_MEM),
* const ri = wdt_gpi_get_resource(pdv, WDT_RESOURCE_IRQ,
IORESOURCE_IRQ),
* const rc = wdt_gpi_get_resource(pdv, WDT_RESOURCE_COUNTER,
0);
if (unlikely(!rr || !ri || !rc))
return -ENXIO;
wd_regs = ioremap_nocache(rr->start, rr->end + 1 - rr->start);
if (unlikely(!wd_regs))
return -ENOMEM;
wd_irq = ri->start;
wd_ctr = rc->start;
res = misc_register(&miscdev);
if (res)
iounmap(wd_regs);
else
register_reboot_notifier(&wdt_gpi_shutdown);
return res;
}
static int __exit wdt_gpi_remove(struct device *dev)
{
int res;
unregister_reboot_notifier(&wdt_gpi_shutdown);
res = misc_deregister(&miscdev);
iounmap(wd_regs);
wd_regs = NULL;
return res;
}
/* Device driver init & exit */
static struct device_driver wdt_gpi_driver = {
.name = (char *) wdt_gpi_name,
.bus = &platform_bus_type,
.owner = THIS_MODULE,
.probe = wdt_gpi_probe,
.remove = __exit_p(wdt_gpi_remove),
.shutdown = NULL,
.suspend = NULL,
.resume = NULL,
};
static int __init wdt_gpi_init_module(void)
{
atomic_set(&opencnt, 1);
if (timeout > MAX_TIMEOUT_SECONDS)
timeout = MAX_TIMEOUT_SECONDS;
return driver_register(&wdt_gpi_driver);
}
static void __exit wdt_gpi_cleanup_module(void)
{
driver_unregister(&wdt_gpi_driver);
}
module_init(wdt_gpi_init_module);
module_exit(wdt_gpi_cleanup_module);
MODULE_AUTHOR("Thomas Koeller <thomas.koeller@baslerweb.com>");
MODULE_DESCRIPTION("Basler eXcite watchdog driver for gpi devices");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,563 @@
/* 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@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.
*
* 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
*
* Changelog:
* 05-Oct-2004 BJD Added semaphore init to stop crashes on open
* Fixed tmr_count / wdt_count confusion
* Added configurable debug
*
* 11-Jan-2005 BJD Fixed divide-by-2 in timeout code
*
* 25-Jan-2005 DA Added suspend/resume support
* Replaced reboot notifier with .shutdown method
*
* 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/map.h>
#undef S3C_VA_WATCHDOG
#define S3C_VA_WATCHDOG (0)
#include <asm/plat-s3c/regs-watchdog.h>
#define PFX "s3c2410-wdt: "
#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
static int nowayout = WATCHDOG_NOWAYOUT;
static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;
static int soft_noboot = 0;
static int debug = 0;
module_param(tmr_margin, int, 0);
module_param(tmr_atboot, int, 0);
module_param(nowayout, int, 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 depends on ONLY_TESTING)");
MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
typedef enum close_state {
CLOSE_STATE_NOT,
CLOSE_STATE_ALLOW=0x4021
} close_state_t;
static DECLARE_MUTEX(open_lock);
static struct device *wdt_dev; /* platform device attached to */
static struct resource *wdt_mem;
static struct resource *wdt_irq;
static struct clk *wdt_clock;
static void __iomem *wdt_base;
static unsigned int wdt_count;
static close_state_t allow_close;
/* watchdog control routines */
#define DBG(msg...) do { \
if (debug) \
printk(KERN_INFO msg); \
} while(0)
/* functions */
static int s3c2410wdt_keepalive(void)
{
writel(wdt_count, wdt_base + S3C2410_WTCNT);
return 0;
}
static int 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);
return 0;
}
static int s3c2410wdt_start(void)
{
unsigned long wtcon;
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",
__FUNCTION__, wdt_count, wtcon);
writel(wdt_count, wdt_base + S3C2410_WTDAT);
writel(wdt_count, wdt_base + S3C2410_WTCNT);
writel(wtcon, wdt_base + S3C2410_WTCON);
return 0;
}
static int s3c2410wdt_set_heartbeat(int timeout)
{
unsigned int 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=%d\n",
__FUNCTION__, 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;
}
}
tmr_margin = timeout;
DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
__FUNCTION__, 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);
return 0;
}
/*
* /dev/watchdog handling
*/
static int s3c2410wdt_open(struct inode *inode, struct file *file)
{
if(down_trylock(&open_lock))
return -EBUSY;
if (nowayout)
__module_get(THIS_MODULE);
allow_close = CLOSE_STATE_NOT;
/* start the timer */
s3c2410wdt_start();
return nonseekable_open(inode, file);
}
static int s3c2410wdt_release(struct inode *inode, struct file *file)
{
/*
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
if (allow_close == CLOSE_STATE_ALLOW) {
s3c2410wdt_stop();
} else {
dev_err(wdt_dev, "Unexpected close, not stopping watchdog\n");
s3c2410wdt_keepalive();
}
allow_close = CLOSE_STATE_NOT;
up(&open_lock);
return 0;
}
static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
/*
* Refresh the timer.
*/
if(len) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
allow_close = CLOSE_STATE_NOT;
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
allow_close = CLOSE_STATE_ALLOW;
}
}
s3c2410wdt_keepalive();
}
return len;
}
#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
static struct watchdog_info s3c2410_wdt_ident = {
.options = OPTIONS,
.firmware_version = 0,
.identity = "S3C2410 Watchdog",
};
static int s3c2410wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_margin;
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &s3c2410_wdt_ident,
sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
s3c2410wdt_keepalive();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, p))
return -EFAULT;
if (s3c2410wdt_set_heartbeat(new_margin))
return -EINVAL;
s3c2410wdt_keepalive();
return put_user(tmr_margin, p);
case WDIOC_GETTIMEOUT:
return put_user(tmr_margin, p);
}
}
/* kernel interface */
static const struct file_operations s3c2410wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = s3c2410wdt_write,
.ioctl = s3c2410wdt_ioctl,
.open = s3c2410wdt_open,
.release = s3c2410wdt_release,
};
static struct miscdevice s3c2410wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &s3c2410wdt_fops,
};
/* interrupt handler code */
static irqreturn_t s3c2410wdt_irq(int irqno, void *param)
{
dev_info(wdt_dev, "watchdog timer expired (irq)\n");
s3c2410wdt_keepalive();
return IRQ_HANDLED;
}
/* device interface */
static int s3c2410wdt_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev;
unsigned int wtcon;
int started = 0;
int ret;
int size;
DBG("%s: probe=%p\n", __FUNCTION__, pdev);
dev = &pdev->dev;
wdt_dev = &pdev->dev;
/* get the memory region for the watchdog timer */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(dev, "no memory resource specified\n");
return -ENOENT;
}
size = (res->end-res->start)+1;
wdt_mem = request_mem_region(res->start, size, pdev->name);
if (wdt_mem == NULL) {
dev_err(dev, "failed to get memory region\n");
ret = -ENOENT;
goto err_req;
}
wdt_base = ioremap(res->start, size);
if (wdt_base == 0) {
dev_err(dev, "failed to ioremap() region\n");
ret = -EINVAL;
goto err_req;
}
DBG("probe: mapped wdt_base=%p\n", wdt_base);
wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (wdt_irq == NULL) {
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err_map;
}
ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_map;
}
wdt_clock = clk_get(&pdev->dev, "watchdog");
if (IS_ERR(wdt_clock)) {
dev_err(dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock);
goto err_irq;
}
clk_enable(wdt_clock);
/* see if we can actually set the requested timer margin, and if
* not, try the default value */
if (s3c2410wdt_set_heartbeat(tmr_margin)) {
started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
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 = misc_register(&s3c2410wdt_miscdev);
if (ret) {
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
goto err_clk;
}
if (tmr_atboot && started == 0) {
dev_info(dev, "starting watchdog timer\n");
s3c2410wdt_start();
} 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();
}
/* 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) ? "" : "dis",
(wtcon & S3C2410_WTCON_INTEN) ? "" : "en");
return 0;
err_clk:
clk_disable(wdt_clock);
clk_put(wdt_clock);
err_irq:
free_irq(wdt_irq->start, pdev);
err_map:
iounmap(wdt_base);
err_req:
release_resource(wdt_mem);
kfree(wdt_mem);
return ret;
}
static int s3c2410wdt_remove(struct platform_device *dev)
{
release_resource(wdt_mem);
kfree(wdt_mem);
wdt_mem = NULL;
free_irq(wdt_irq->start, dev);
wdt_irq = NULL;
clk_disable(wdt_clock);
clk_put(wdt_clock);
wdt_clock = NULL;
iounmap(wdt_base);
misc_deregister(&s3c2410wdt_miscdev);
return 0;
}
static void s3c2410wdt_shutdown(struct platform_device *dev)
{
s3c2410wdt_stop();
}
#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();
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);
printk(KERN_INFO PFX "watchdog %sabled\n",
(wtcon_save & S3C2410_WTCON_ENABLE) ? "en" : "dis");
return 0;
}
#else
#define s3c2410wdt_suspend NULL
#define s3c2410wdt_resume NULL
#endif /* CONFIG_PM */
static struct platform_driver s3c2410wdt_driver = {
.probe = s3c2410wdt_probe,
.remove = s3c2410wdt_remove,
.shutdown = s3c2410wdt_shutdown,
.suspend = s3c2410wdt_suspend,
.resume = s3c2410wdt_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-wdt",
},
};
static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
static int __init watchdog_init(void)
{
printk(banner);
return platform_driver_register(&s3c2410wdt_driver);
}
static void __exit watchdog_exit(void)
{
platform_driver_unregister(&s3c2410wdt_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
MODULE_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);

View File

@@ -0,0 +1,190 @@
/*
* Watchdog driver for the SA11x0/PXA2xx
*
* (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
* Based on SoftDog driver by Alan Cox <alan@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 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
*/
#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>
#ifdef CONFIG_ARCH_PXA
#include <asm/arch/pxa-regs.h>
#endif
#include <asm/hardware.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#define OSCR_FREQ CLOCK_TICK_RATE
static unsigned long sa1100wdt_users;
static 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)
{
printk(KERN_CRIT "WATCHDOG: 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 struct watchdog_info ident = {
.options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = "SA1100/PXA255 Watchdog",
};
static int sa1100dog_ioctl(struct inode *inode, 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_SETTIMEOUT:
ret = get_user(time, p);
if (ret)
break;
if (time <= 0 || time > 255) {
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;
case WDIOC_KEEPALIVE:
OSMR3 = OSCR + pre_margin;
ret = 0;
break;
}
return ret;
}
static const struct file_operations sa1100dog_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = sa1100dog_write,
.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;
/*
* Read the reset status, and save it for later. If
* we suspend, RCSR will be cleared, and the watchdog
* reset reason will be lost.
*/
boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0;
pre_margin = OSCR_FREQ * margin;
ret = misc_register(&sa1100dog_miscdev);
if (ret == 0)
printk("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);

View File

@@ -0,0 +1,400 @@
/*
* 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.
*
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.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, will be 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static 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 {
printk(KERN_WARNING PFX "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);
printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
}
static void wdt_turnoff(void)
{
/* Stop the timer */
del_timer(&timer);
inb_p(wdt_stop);
printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
}
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);
printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
}
clear_bit(0, &wdt_is_open);
wdt_expect_close = 0;
return 0;
}
static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident=
{
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "SBC60xx",
};
switch(cmd)
{
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if(get_user(new_options, p))
return -EFAULT;
if(new_options & WDIOS_DISABLECARD) {
wdt_turnoff();
retval = 0;
}
if(new_options & WDIOS_ENABLECARD) {
wdt_startup();
retval = 0;
}
return retval;
}
case WDIOC_SETTIMEOUT:
{
int new_timeout;
if(get_user(new_timeout, p))
return -EFAULT;
if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
return -EINVAL;
timeout = new_timeout;
wdt_keepalive();
/* Fall through */
}
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
}
}
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = fop_write,
.open = fop_open,
.release = fop_close,
.ioctl = fop_ioctl,
};
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;
printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
timeout);
}
if (!request_region(wdt_start, 1, "SBC 60XX WDT"))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_start);
rc = -EIO;
goto err_out;
}
/* We cannot reserve 0x45 - the kernel already has! */
if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
{
if (!request_region(wdt_stop, 1, "SBC 60XX WDT"))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_stop);
rc = -EIO;
goto err_out_region1;
}
}
rc = misc_register(&wdt_miscdev);
if (rc)
{
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
wdt_miscdev.minor, rc);
goto err_out_region2;
}
rc = register_reboot_notifier(&wdt_notifier);
if (rc)
{
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_miscdev;
}
printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
return 0;
err_out_miscdev:
misc_deregister(&wdt_miscdev);
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);

413
drivers/watchdog/sbc8360.c Normal file
View File

@@ -0,0 +1,413 @@
/*
* 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@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>
*
* 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
*
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
static unsigned long sbc8360_is_open;
static spinlock_t sbc8360_lock;
static char expect_close;
#define PFX "sbc8360: "
/*
*
* 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))");
module_param(nowayout, int, 0);
MODULE_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);
}
/* 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)
{
spin_lock(&sbc8360_lock);
if (test_and_set_bit(0, &sbc8360_is_open)) {
spin_unlock(&sbc8360_lock);
return -EBUSY;
}
if (nowayout)
__module_get(THIS_MODULE);
/* Activate and ping once to start the countdown */
spin_unlock(&sbc8360_lock);
sbc8360_activate();
sbc8360_ping();
return nonseekable_open(inode, file);
}
static int sbc8360_close(struct inode *inode, struct file *file)
{
spin_lock(&sbc8360_lock);
if (expect_close == 42)
outb(0, SBC8360_ENABLE);
else
printk(KERN_CRIT PFX
"SBC8360 device closed unexpectedly. SBC8360 will not stop!\n");
clear_bit(0, &sbc8360_is_open);
expect_close = 0;
spin_unlock(&sbc8360_lock);
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) {
/* Disable the SBC8360 Watchdog */
outb(0, SBC8360_ENABLE);
}
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) {
printk(KERN_ERR PFX "Invalid timeout index (must be 0-63).\n");
res = -EINVAL;
goto out;
}
if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) {
printk(KERN_ERR PFX "ENABLE method I/O %X is not available.\n",
SBC8360_ENABLE);
res = -EIO;
goto out;
}
if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) {
printk(KERN_ERR PFX
"BASETIME method I/O %X is not available.\n",
SBC8360_BASETIME);
res = -EIO;
goto out_nobasetimereg;
}
res = register_reboot_notifier(&sbc8360_notifier);
if (res) {
printk(KERN_ERR PFX "Failed to register reboot notifier.\n");
goto out_noreboot;
}
spin_lock_init(&sbc8360_lock);
res = misc_register(&sbc8360_miscdev);
if (res) {
printk(KERN_ERR PFX "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! */
printk(KERN_INFO PFX "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 */

View File

@@ -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@redhat.com>
*/
#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 <asm/uaccess.h>
#include <asm/io.h>
#define PFX "epx_c3: "
static int epx_c3_alive;
#define WATCHDOG_TIMEOUT 1 /* 1 sec default timeout */
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#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);
printk(KERN_INFO PFX "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;
printk(KERN_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 int epx_c3_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int options, retval = -EINVAL;
int __user *argp = (void __user *)arg;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.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_KEEPALIVE:
epx_c3_pet();
return 0;
case WDIOC_GETTIMEOUT:
return put_user(WATCHDOG_TIMEOUT, argp);
case WDIOC_SETOPTIONS:
if (get_user(options, argp))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
epx_c3_stop();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
epx_c3_start();
retval = 0;
}
return retval;
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,
.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 const char banner[] __initdata =
KERN_INFO PFX "Hardware Watchdog Timer for Winsystems EPX-C3 SBC: 0.1\n";
static int __init watchdog_init(void)
{
int ret;
if (!request_region(EPXC3_WATCHDOG_CTL_REG, 2, "epxc3_watchdog"))
return -EBUSY;
ret = register_reboot_notifier(&epx_c3_notifier);
if (ret) {
printk(KERN_ERR PFX "cannot register reboot notifier "
"(err=%d)\n", ret);
goto out;
}
ret = misc_register(&epx_c3_miscdev);
if (ret) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d "
"(err=%d)\n", WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&epx_c3_notifier);
goto out;
}
printk(banner);
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 runnning on this specific SBC system from Winsystems! It writes to IO ports 0x1ee and 0x1ef!");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,463 @@
/*
* 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
*
*/
#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 <asm/semaphore.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define SC1200_MODULE_VER "build 20020303"
#define SC1200_MODULE_NAME "sc1200wdt"
#define PFX SC1200_MODULE_NAME ": "
#define MAX_TIMEOUT 255 /* 255 minutes */
#define PMIR (io) /* Power Management Index Register */
#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 char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER;
static int timeout = 1;
static int io = -1;
static int io_len = 2; /* for non plug and play */
static struct semaphore open_sem;
static char expect_close;
static spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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)
{
spin_lock(&sc1200wdt_lock);
outb_p(index, PMIR);
*data = inb(PMDR);
spin_unlock(&sc1200wdt_lock);
}
/* Write to Data Register */
static inline void sc1200wdt_write_data(unsigned char index, unsigned char data)
{
spin_lock(&sc1200wdt_lock);
outb_p(index, PMIR);
outb(data, PMDR);
spin_unlock(&sc1200wdt_lock);
}
static void sc1200wdt_start(void)
{
unsigned char reg;
sc1200wdt_read_data(WDCF, &reg);
/* assert WDO when any of the following interrupts are triggered too */
reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ);
sc1200wdt_write_data(WDCF, reg);
/* set the timeout and get the ball rolling */
sc1200wdt_write_data(WDTO, timeout);
}
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; /* bits 1 - 7 are undefined */
}
static int sc1200wdt_open(struct inode *inode, struct file *file)
{
/* allow one at a time */
if (down_trylock(&open_sem))
return -EBUSY;
if (timeout > MAX_TIMEOUT)
timeout = MAX_TIMEOUT;
sc1200wdt_start();
printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout);
return nonseekable_open(inode, file);
}
static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int new_timeout;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "PC87307/PC97307",
};
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &ident, sizeof ident))
return -EFAULT;
return 0;
case WDIOC_GETSTATUS:
return put_user(sc1200wdt_status(), p);
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
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);
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;
}
}
}
static int sc1200wdt_release(struct inode *inode, struct file *file)
{
if (expect_close == 42) {
sc1200wdt_stop();
printk(KERN_INFO PFX "Watchdog disabled\n");
} else {
sc1200wdt_write_data(WDTO, timeout);
printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout);
}
up(&open_sem);
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,
.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);
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)) {
printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
return -EBUSY;
}
printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len);
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;
printk("%s\n", banner);
spin_lock_init(&sc1200wdt_lock);
sema_init(&open_sem, 1);
#if defined CONFIG_PNP
if (isapnp) {
ret = pnp_register_driver(&scl200wdt_pnp_driver);
if (ret)
goto out_clean;
}
#endif
if (io == -1) {
printk(KERN_ERR PFX "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)) {
printk(KERN_ERR PFX "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) {
printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
goto out_io;
}
ret = misc_register(&sc1200wdt_miscdev);
if (ret) {
printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
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);

View File

@@ -0,0 +1,435 @@
/*
* 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.
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define OUR_NAME "sc520_wdt"
#define PFX OUR_NAME ": "
/*
* The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
*
* 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 */
static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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 spinlock_t 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 {
printk(KERN_WARNING PFX "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);
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "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 {
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
wdt_keepalive();
}
clear_bit(0, &wdt_is_open);
wdt_expect_close = 0;
return 0;
}
static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "SC520",
};
switch(cmd)
{
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if(get_user(new_options, p))
return -EFAULT;
if(new_options & WDIOS_DISABLECARD) {
wdt_turnoff();
retval = 0;
}
if(new_options & WDIOS_ENABLECARD) {
wdt_startup();
retval = 0;
}
return retval;
}
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);
}
}
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = fop_write,
.open = fop_open,
.release = fop_close,
.ioctl = fop_ioctl,
};
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;
spin_lock_init(&wdt_spinlock);
/* Check that the timeout value is within it's range ; if not reset to the default */
if (wdt_set_heartbeat(timeout)) {
wdt_set_heartbeat(WATCHDOG_TIMEOUT);
printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n",
WATCHDOG_TIMEOUT);
}
wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2);
if (!wdtmrctl) {
printk(KERN_ERR PFX "Unable to remap memory\n");
rc = -ENOMEM;
goto err_out_region2;
}
rc = register_reboot_notifier(&wdt_notifier);
if (rc) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_ioremap;
}
rc = misc_register(&wdt_miscdev);
if (rc) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, rc);
goto err_out_notifier;
}
printk(KERN_INFO PFX "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);

View File

@@ -0,0 +1,269 @@
/* 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. */
#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 <asm/uaccess.h>
#include <asm/io.h>
#define NAME "scx200_wdt"
MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
static int margin = 60; /* in seconds */
module_param(margin, int, 0);
MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
static u16 wdto_restart;
static struct semaphore open_semaphore;
static char expect_close;
/* 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)
{
outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
}
static void scx200_wdt_update_margin(void)
{
printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
wdto_restart = margin * W_SCALE;
}
static void scx200_wdt_enable(void)
{
printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n",
wdto_restart);
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);
scx200_wdt_ping();
}
static void scx200_wdt_disable(void)
{
printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
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);
}
static int scx200_wdt_open(struct inode *inode, struct file *file)
{
/* only allow one at a time */
if (down_trylock(&open_semaphore))
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) {
printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
} else if (!nowayout) {
scx200_wdt_disable();
}
expect_close = 0;
up(&open_semaphore);
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 int scx200_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.identity = "NatSemi SCx200 Watchdog",
.firmware_version = 1,
.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING),
};
int new_margin;
switch (cmd) {
default:
return -ENOTTY;
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;
}
}
static const struct file_operations scx200_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = scx200_wdt_write,
.ioctl = scx200_wdt_ioctl,
.open = scx200_wdt_open,
.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;
printk(KERN_DEBUG NAME ": 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")) {
printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
return -EBUSY;
}
scx200_wdt_update_margin();
scx200_wdt_disable();
sema_init(&open_semaphore, 1);
r = misc_register(&scx200_wdt_miscdev);
if (r) {
release_region(scx200_cb_base + SCx200_WDT_OFFSET,
SCx200_WDT_SIZE);
return r;
}
r = register_reboot_notifier(&scx200_wdt_notifier);
if (r) {
printk(KERN_ERR NAME ": unable to register reboot notifier");
misc_deregister(&scx200_wdt_miscdev);
release_region(scx200_cb_base + SCx200_WDT_OFFSET,
SCx200_WDT_SIZE);
return r;
}
return 0;
}
static void __exit scx200_wdt_cleanup(void)
{
unregister_reboot_notifier(&scx200_wdt_notifier);
misc_deregister(&scx200_wdt_miscdev);
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:
*/

485
drivers/watchdog/shwdt.c Normal file
View File

@@ -0,0 +1,485 @@
/*
* drivers/char/watchdog/shwdt.c
*
* Watchdog driver for integrated watchdog in the SuperH processors.
*
* Copyright (C) 2001, 2002, 2003 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
*/
#include <linux/module.h>
#include <linux/moduleparam.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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/watchdog.h>
#define PFX "shwdt: "
/*
* 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 yeild 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 seperate 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) msecs_to_jiffies(cks - 4)
static void sh_wdt_ping(unsigned long data);
static unsigned long shwdt_is_open;
static struct watchdog_info sh_wdt_info;
static char shwdt_expect_close;
static DEFINE_TIMER(timer, sh_wdt_ping, 0, 0);
static unsigned long next_heartbeat;
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
static int nowayout = WATCHDOG_NOWAYOUT;
/**
* sh_wdt_start - Start the Watchdog
*
* Starts the watchdog.
*/
static void sh_wdt_start(void)
{
__u8 csr;
next_heartbeat = jiffies + (heartbeat * HZ);
mod_timer(&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
/*
* Whoever came up with the RSTCSR semantics must've been smoking
* some of the good stuff, since in addition to the WTCSR/WTCNT write
* brain-damage, it's managed to fuck things up one step further..
*
* If we need to clear the WOVF bit, the upper byte has to be 0xa5..
* but if we want to touch RSTE or RSTS, the upper byte has to be
* 0x5a..
*/
csr = sh_wdt_read_rstcsr();
csr &= ~RSTCSR_RSTS;
sh_wdt_write_rstcsr(csr);
#endif
}
/**
* sh_wdt_stop - Stop the Watchdog
* Stops the watchdog.
*/
static void sh_wdt_stop(void)
{
__u8 csr;
del_timer(&timer);
csr = sh_wdt_read_csr();
csr &= ~WTCSR_TME;
sh_wdt_write_csr(csr);
}
/**
* sh_wdt_keepalive - Keep the Userspace Watchdog Alive
* The Userspace watchdog got a KeepAlive: schedule the next heartbeat.
*/
static inline void sh_wdt_keepalive(void)
{
next_heartbeat = jiffies + (heartbeat * HZ);
}
/**
* sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat
* Set the Userspace Watchdog heartbeat
*/
static int sh_wdt_set_heartbeat(int t)
{
if (unlikely((t < 1) || (t > 3600))) /* arbitrary upper limit */
return -EINVAL;
heartbeat = t;
return 0;
}
/**
* sh_wdt_ping - Ping the Watchdog
* @data: Unused
*
* Clears overflow bit, resets timer counter.
*/
static void sh_wdt_ping(unsigned long data)
{
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(&timer, next_ping_period(clock_division_ratio));
} else
printk(KERN_WARNING PFX "Heartbeat lost! Will not ping "
"the watchdog\n");
}
/**
* sh_wdt_open - Open the Device
* @inode: inode of device
* @file: file handle of device
*
* Watchdog device is opened and started.
*/
static int sh_wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(0, &shwdt_is_open))
return -EBUSY;
if (nowayout)
__module_get(THIS_MODULE);
sh_wdt_start();
return nonseekable_open(inode, file);
}
/**
* sh_wdt_close - Close the Device
* @inode: inode of device
* @file: file handle of device
*
* Watchdog device is closed and stopped.
*/
static int sh_wdt_close(struct inode *inode, struct file *file)
{
if (shwdt_expect_close == 42) {
sh_wdt_stop();
} else {
printk(KERN_CRIT PFX "Unexpected close, not "
"stopping watchdog!\n");
sh_wdt_keepalive();
}
clear_bit(0, &shwdt_is_open);
shwdt_expect_close = 0;
return 0;
}
/**
* sh_wdt_write - Write to Device
* @file: file handle of device
* @buf: buffer to write
* @count: length of buffer
* @ppos: offset
*
* Pings the watchdog on write.
*/
static ssize_t sh_wdt_write(struct file *file, const char *buf,
size_t count, loff_t *ppos)
{
if (count) {
if (!nowayout) {
size_t i;
shwdt_expect_close = 0;
for (i = 0; i != count; i++) {
char c;
if (get_user(c, buf + i))
return -EFAULT;
if (c == 'V')
shwdt_expect_close = 42;
}
}
sh_wdt_keepalive();
}
return count;
}
/**
* sh_wdt_mmap - map WDT/CPG registers into userspace
* @file: file structure for the device
* @vma: VMA to map the registers into
*
* A simple mmap() implementation for the corner cases where the counter
* needs to be mapped in userspace directly. Due to the relatively small
* size of the area, neighbouring registers not necessarily tied to the
* CPG will also be accessible through the register page, so this remains
* configurable for users that really know what they're doing.
*
* Additionaly, the register page maps in the CPG register base relative
* to the nearest page-aligned boundary, which requires that userspace do
* the appropriate CPU subtype math for calculating the page offset for
* the counter value.
*/
static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret = -ENOSYS;
#ifdef CONFIG_SH_WDT_MMAP
unsigned long addr;
/* Only support the simple cases where we map in a register page. */
if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff)
return -EINVAL;
/*
* Pick WTCNT as the start, it's usually the first register after the
* FRQCR, and neither one are generally page-aligned out of the box.
*/
addr = WTCNT & ~(PAGE_SIZE - 1);
vma->vm_flags |= VM_IO;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
PAGE_SIZE, vma->vm_page_prot)) {
printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n",
__FUNCTION__);
return -EAGAIN;
}
ret = 0;
#endif
return ret;
}
/**
* sh_wdt_ioctl - Query Device
* @inode: inode of device
* @file: file handle of device
* @cmd: watchdog command
* @arg: argument
*
* Query basic information from the device or ping it, as outlined by the
* watchdog API.
*/
static int sh_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_heartbeat;
int options, retval = -EINVAL;
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user((struct watchdog_info *)arg,
&sh_wdt_info,
sizeof(sh_wdt_info)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, (int *)arg);
case WDIOC_KEEPALIVE:
sh_wdt_keepalive();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_heartbeat, (int *)arg))
return -EFAULT;
if (sh_wdt_set_heartbeat(new_heartbeat))
return -EINVAL;
sh_wdt_keepalive();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, (int *)arg);
case WDIOC_SETOPTIONS:
if (get_user(options, (int *)arg))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
sh_wdt_stop();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
sh_wdt_start();
retval = 0;
}
return retval;
default:
return -ENOTTY;
}
return 0;
}
/**
* sh_wdt_notify_sys - Notifier Handler
* @this: notifier block
* @code: notifier event
* @unused: unused
*
* Handles specific events, such as turning off the watchdog during a
* shutdown event.
*/
static int sh_wdt_notify_sys(struct notifier_block *this,
unsigned long code, void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT)
sh_wdt_stop();
return NOTIFY_DONE;
}
static const struct file_operations sh_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = sh_wdt_write,
.ioctl = sh_wdt_ioctl,
.open = sh_wdt_open,
.release = sh_wdt_close,
.mmap = sh_wdt_mmap,
};
static 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,
};
/**
* sh_wdt_init - Initialize module
* Registers the device and notifier handler. Actual device
* initialization is handled by sh_wdt_open().
*/
static int __init sh_wdt_init(void)
{
int rc;
if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) {
clock_division_ratio = WTCSR_CKS_4096;
printk(KERN_INFO PFX "clock_division_ratio value must "
"be 0x5<=x<=0x7, using %d\n", clock_division_ratio);
}
rc = sh_wdt_set_heartbeat(heartbeat);
if (unlikely(rc)) {
heartbeat = WATCHDOG_HEARTBEAT;
printk(KERN_INFO PFX "heartbeat value must "
"be 1<=x<=3600, using %d\n", heartbeat);
}
rc = register_reboot_notifier(&sh_wdt_notifier);
if (unlikely(rc)) {
printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n",
rc);
return rc;
}
rc = misc_register(&sh_wdt_miscdev);
if (unlikely(rc)) {
printk(KERN_ERR PFX "Can't register miscdev on "
"minor=%d (err=%d)\n", sh_wdt_miscdev.minor, rc);
unregister_reboot_notifier(&sh_wdt_notifier);
return rc;
}
printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
heartbeat, nowayout);
return 0;
}
/**
* sh_wdt_exit - Deinitialize module
* Unregisters the device and notifier handler. Actual device
* deinitialization is handled by sh_wdt_close().
*/
static void __exit sh_wdt_exit(void)
{
misc_deregister(&sh_wdt_miscdev);
unregister_reboot_notifier(&sh_wdt_notifier);
}
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
MODULE_DESCRIPTION("SuperH watchdog driver");
MODULE_LICENSE("GPL");
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(clock_division_ratio) ")");
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
module_init(sh_wdt_init);
module_exit(sh_wdt_exit);

View File

@@ -0,0 +1,627 @@
/*
* SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x
*
* Based on acquirewdt.c by Alan Cox <alan@redhat.com>
* and some other existing drivers
*
* 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/watchdog.txt
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
/* enable support for minutes as units? */
/* (does not always work correctly, so disabled by default!) */
#define SMSC_SUPPORT_MINUTES
#undef SMSC_SUPPORT_MINUTES
#define MAX_TIMEOUT 255
#define UNIT_SECOND 0
#define UNIT_MINUTE 1
#define MODNAME "smsc37b787_wdt: "
#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 = 0; /* is the timer enabled? */
static char expect_close; /* is the close expected? */
static spinlock_t io_lock; /* to guard the watchdog from io races */
static int 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 occured
// Bit 1 Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz
// Bit 2 Force Timeout: 1 = Forces WD timeout event (self-cleaning)
// Bit 3 P20 Force Timeout enabled:
// 0 = P20 activity does not generate the WD timeout event
// 1 = P20 Allows rising edge of P20, from the keyboard
// controller, to force the WD timeout event.
// Bit 4 (Reserved)
// -- Soft power management --
// Bit 5 Stop Counter: 1 = Stop software power down counter
// set via register 0xB8, (self-cleaning)
// (Upon read: 0 = Counter running, 1 = Counter stopped)
// Bit 6 Restart Counter: 1 = Restart software power down counter
// set via register 0xB8, (self-cleaning)
// Bit 7 SPOFF: 1 = Force software power down (self-cleaning)
write_io_cr(0xF4, reg);
}
/* -- 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();
printk(KERN_INFO MODNAME "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();
printk(KERN_INFO MODNAME "Watchdog disabled, sleeping again...\n");
} else {
printk(KERN_CRIT MODNAME "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 int wb_smsc_wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_timeout;
union {
struct watchdog_info __user *ident;
int __user *i;
} uarg;
static 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) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(uarg.ident, &ident,
sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
return put_user(wb_smsc_wdt_status(), uarg.i);
case WDIOC_GETBOOTSTATUS:
return put_user(0, uarg.i);
case WDIOC_KEEPALIVE:
wb_smsc_wdt_reset_timer();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, uarg.i))
return -EFAULT;
// the API states this is given in secs
if (unit == UNIT_MINUTE)
new_timeout /= 60;
if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
return -EINVAL;
timeout = new_timeout;
wb_smsc_wdt_set_timeout(timeout);
// fall through and return the new timeout...
case WDIOC_GETTIMEOUT:
new_timeout = timeout;
if (unit == UNIT_MINUTE)
new_timeout *= 60;
return put_user(new_timeout, uarg.i);
case WDIOC_SETOPTIONS:
{
int options, retval = -EINVAL;
if (get_user(options, uarg.i))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
wb_smsc_wdt_disable();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
wb_smsc_wdt_enable();
retval = 0;
}
return retval;
}
}
}
/* -- 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,
.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;
spin_lock_init(&io_lock);
printk("SMsC 37B787 watchdog component driver " VERSION " initialising...\n");
if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) {
printk(KERN_ERR MODNAME "Unable to register IO port %#x\n", IOPORT);
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) {
printk(KERN_ERR MODNAME "Unable to register reboot notifier err = %d\n", ret);
goto out_io;
}
ret = misc_register(&wb_smsc_wdt_miscdev);
if (ret) {
printk(KERN_ERR MODNAME "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
goto out_rbt;
}
// output info
printk(KERN_INFO MODNAME "Timeout set to %d %s.\n", timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");
printk(KERN_INFO MODNAME "Watchdog initialized and sleeping (nowayout=%d)...\n", nowayout);
// ret = 0
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();
printk(KERN_INFO MODNAME "Watchdog disabled.\n");
}
misc_deregister(&wb_smsc_wdt_miscdev);
unregister_reboot_notifier(&wb_smsc_wdt_notifier);
release_region(IOPORT, IOPORT_SIZE);
printk("SMsC 37B787 watchdog component driver removed.\n");
}
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, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

310
drivers/watchdog/softdog.c Normal file
View File

@@ -0,0 +1,310 @@
/*
* SoftDog 0.07: A Software Watchdog Device
*
* (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
* 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.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <asm/uaccess.h>
#define PFX "SoftDog: "
#define TIMER_MARGIN 60 /* Default is 60 seconds */
static int soft_margin = TIMER_MARGIN; /* in seconds */
module_param(soft_margin, int, 0);
MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#ifdef ONLY_TESTING
static int soft_noboot = 1;
#else
static int soft_noboot = 0;
#endif /* ONLY_TESTING */
module_param(soft_noboot, int, 0);
MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
/*
* Our timer
*/
static void watchdog_fire(unsigned long);
static struct timer_list watchdog_ticktock =
TIMER_INITIALIZER(watchdog_fire, 0, 0);
static unsigned long driver_open, orphan_timer;
static char expect_close;
/*
* If the timer expires..
*/
static void watchdog_fire(unsigned long data)
{
if (test_and_clear_bit(0, &orphan_timer))
module_put(THIS_MODULE);
if (soft_noboot)
printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n");
else
{
printk(KERN_CRIT PFX "Initiating system reboot.\n");
emergency_restart();
printk(KERN_CRIT PFX "Reboot didn't ?????\n");
}
}
/*
* Softdog operations
*/
static int softdog_keepalive(void)
{
mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ));
return 0;
}
static int softdog_stop(void)
{
del_timer(&watchdog_ticktock);
return 0;
}
static int softdog_set_heartbeat(int t)
{
if ((t < 0x0001) || (t > 0xFFFF))
return -EINVAL;
soft_margin = t;
return 0;
}
/*
* /dev/watchdog handling
*/
static int softdog_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(0, &driver_open))
return -EBUSY;
if (!test_and_clear_bit(0, &orphan_timer))
__module_get(THIS_MODULE);
/*
* Activate timer
*/
softdog_keepalive();
return nonseekable_open(inode, file);
}
static int softdog_release(struct inode *inode, struct file *file)
{
/*
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
if (expect_close == 42) {
softdog_stop();
module_put(THIS_MODULE);
} else {
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
set_bit(0, &orphan_timer);
softdog_keepalive();
}
clear_bit(0, &driver_open);
expect_close = 0;
return 0;
}
static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
{
/*
* Refresh the timer.
*/
if(len) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
expect_close = 0;
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
expect_close = 42;
}
}
softdog_keepalive();
}
return len;
}
static int softdog_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_margin;
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
switch (cmd) {
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident,
sizeof(ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
softdog_keepalive();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, p))
return -EFAULT;
if (softdog_set_heartbeat(new_margin))
return -EINVAL;
softdog_keepalive();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(soft_margin, p);
}
}
/*
* Notifier for system down
*/
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();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations softdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = softdog_write,
.ioctl = softdog_ioctl,
.open = softdog_open,
.release = softdog_release,
};
static struct miscdevice softdog_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &softdog_fops,
};
static struct notifier_block softdog_notifier = {
.notifier_call = softdog_notify_sys,
};
static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n";
static int __init watchdog_init(void)
{
int ret;
/* Check that the soft_margin value is within it's range ; if not reset to the default */
if (softdog_set_heartbeat(soft_margin)) {
softdog_set_heartbeat(TIMER_MARGIN);
printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n",
TIMER_MARGIN);
}
ret = register_reboot_notifier(&softdog_notifier);
if (ret) {
printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
return ret;
}
ret = misc_register(&softdog_miscdev);
if (ret) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
unregister_reboot_notifier(&softdog_notifier);
return ret;
}
printk(banner, soft_noboot, soft_margin, nowayout);
return 0;
}
static void __exit watchdog_exit(void)
{
misc_deregister(&softdog_miscdev);
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);

View File

@@ -0,0 +1,390 @@
/*
* 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@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>
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define WATCHDOG_NAME "w83627hf/thf/hg WDT"
#define PFX WATCHDOG_NAME ": "
#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
static unsigned long wdt_is_open;
static char expect_close;
static spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/*
* Kernel methods.
*/
#define 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 */
}
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) {
printk (KERN_INFO PFX "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 */
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_ctrl(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_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 int
wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_timeout;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "W83627HF WDT",
};
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &ident, sizeof(ident)))
return -EFAULT;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_ping();
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, p))
return -EFAULT;
if (wdt_set_heartbeat(new_timeout))
return -EINVAL;
wdt_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
case WDIOC_SETOPTIONS:
{
int options, retval = -EINVAL;
if (get_user(options, p))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
wdt_disable();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
wdt_ping();
retval = 0;
}
return retval;
}
default:
return -ENOTTY;
}
return 0;
}
static int
wdt_open(struct inode *inode, struct file *file)
{
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 {
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
wdt_ping();
}
expect_close = 0;
clear_bit(0, &wdt_is_open);
return 0;
}
/*
* Notifier for system down
*/
static int
wdt_notify_sys(struct notifier_block *this, unsigned long code,
void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT) {
/* Turn the WDT off */
wdt_disable();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = wdt_write,
.ioctl = wdt_ioctl,
.open = wdt_open,
.release = wdt_close,
};
static struct miscdevice wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &wdt_fops,
};
/*
* The WDT needs to learn about soft shutdowns in order to
* turn the timebomb registers off.
*/
static struct notifier_block wdt_notifier = {
.notifier_call = wdt_notify_sys,
};
static int __init
wdt_init(void)
{
int ret;
spin_lock_init(&io_lock);
printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF/THF/HG Super I/O chip initialising.\n");
if (wdt_set_heartbeat(timeout)) {
wdt_set_heartbeat(WATCHDOG_TIMEOUT);
printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n",
WATCHDOG_TIMEOUT);
}
if (!request_region(wdt_io, 1, WATCHDOG_NAME)) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_io);
ret = -EIO;
goto out;
}
w83627hf_init();
ret = register_reboot_notifier(&wdt_notifier);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto unreg_regions;
}
ret = misc_register(&wdt_miscdev);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_reboot;
}
printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
out:
return ret;
unreg_reboot:
unregister_reboot_notifier(&wdt_notifier);
unreg_regions:
release_region(wdt_io, 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<EFBFBD>draig Brady <P@draigBrady.com>");
MODULE_DESCRIPTION("w83627hf/thf WDT driver");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,450 @@
/*
* w83697hf/hg WDT driver
*
* (c) Copyright 2006 Samuel Tardieu <sam@rfc1149.net>
* (c) Copyright 2006 Marcus Junker <junker@anduras.de>
*
* Based on w83627hf_wdt.c which is based on advantechwdt.c
* which is based on wdt.c.
* Original copyright messages:
*
* (c) Copyright 2003 P<>draig Brady <P@draigBrady.com>
*
* (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
*
* (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Marcus Junker nor ANDURAS AG admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define WATCHDOG_NAME "w83697hf/hg WDT"
#define PFX WATCHDOG_NAME ": "
#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
static unsigned long wdt_is_open;
static char expect_close;
static spinlock_t io_lock;
/* You must set this - there is no sane way to probe for this board. */
static int wdt_io = 0x2e;
module_param(wdt_io, int, 0);
MODULE_PARM_DESC(wdt_io, "w83697hf/hg WDT io port (default 0x2e, 0 = autodetect)");
static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
static int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
/*
* Kernel methods.
*/
#define W83697HF_EFER (wdt_io+0) /* Extended Function Enable Register */
#define W83697HF_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */
#define W83697HF_EFDR (wdt_io+1) /* Extended Function Data Register */
static inline void
w83697hf_unlock(void)
{
outb_p(0x87, W83697HF_EFER); /* Enter extended function mode */
outb_p(0x87, W83697HF_EFER); /* Again according to manual */
}
static inline void
w83697hf_lock(void)
{
outb_p(0xAA, W83697HF_EFER); /* Leave extended function mode */
}
/*
* The three functions w83697hf_get_reg(), w83697hf_set_reg() and
* w83697hf_write_timeout() must be called with the device unlocked.
*/
static unsigned char
w83697hf_get_reg(unsigned char reg)
{
outb_p(reg, W83697HF_EFIR);
return inb_p(W83697HF_EFDR);
}
static void
w83697hf_set_reg(unsigned char reg, unsigned char data)
{
outb_p(reg, W83697HF_EFIR);
outb_p(data, W83697HF_EFDR);
}
static void
w83697hf_write_timeout(int timeout)
{
w83697hf_set_reg(0xF4, timeout); /* Write Timeout counter to CRF4 */
}
static void
w83697hf_select_wdt(void)
{
w83697hf_unlock();
w83697hf_set_reg(0x07, 0x08); /* Switch to logic device 8 (GPIO2) */
}
static inline void
w83697hf_deselect_wdt(void)
{
w83697hf_lock();
}
static void
w83697hf_init(void)
{
unsigned char bbuf;
w83697hf_select_wdt();
bbuf = w83697hf_get_reg(0x29);
bbuf &= ~0x60;
bbuf |= 0x20;
w83697hf_set_reg(0x29, bbuf); /* Set pin 119 to WDTO# mode (= CR29, WDT0) */
bbuf = w83697hf_get_reg(0xF3);
bbuf &= ~0x04;
w83697hf_set_reg(0xF3, bbuf); /* Count mode is seconds */
w83697hf_deselect_wdt();
}
static int
wdt_ping(void)
{
spin_lock(&io_lock);
w83697hf_select_wdt();
w83697hf_write_timeout(timeout);
w83697hf_deselect_wdt();
spin_unlock(&io_lock);
return 0;
}
static int
wdt_enable(void)
{
spin_lock(&io_lock);
w83697hf_select_wdt();
w83697hf_write_timeout(timeout);
w83697hf_set_reg(0x30, 1); /* Enable timer */
w83697hf_deselect_wdt();
spin_unlock(&io_lock);
return 0;
}
static int
wdt_disable(void)
{
spin_lock(&io_lock);
w83697hf_select_wdt();
w83697hf_set_reg(0x30, 0); /* Disable timer */
w83697hf_write_timeout(0);
w83697hf_deselect_wdt();
spin_unlock(&io_lock);
return 0;
}
static int
wdt_set_heartbeat(int t)
{
if ((t < 1) || (t > 255))
return -EINVAL;
timeout = t;
return 0;
}
static ssize_t
wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
if (count) {
if (!nowayout) {
size_t i;
expect_close = 0;
for (i = 0; i != count; i++) {
char c;
if (get_user(c, buf+i))
return -EFAULT;
if (c == 'V')
expect_close = 42;
}
}
wdt_ping();
}
return count;
}
static int
wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_timeout;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "W83697HF WDT",
};
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user(argp, &ident, sizeof(ident)))
return -EFAULT;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_ping();
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, p))
return -EFAULT;
if (wdt_set_heartbeat(new_timeout))
return -EINVAL;
wdt_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
case WDIOC_SETOPTIONS:
{
int options, retval = -EINVAL;
if (get_user(options, p))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
wdt_disable();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
wdt_enable();
retval = 0;
}
return retval;
}
default:
return -ENOTTY;
}
return 0;
}
static int
wdt_open(struct inode *inode, struct file *file)
{
if (test_and_set_bit(0, &wdt_is_open))
return -EBUSY;
/*
* Activate
*/
wdt_enable();
return nonseekable_open(inode, file);
}
static int
wdt_close(struct inode *inode, struct file *file)
{
if (expect_close == 42) {
wdt_disable();
} else {
printk (KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
wdt_ping();
}
expect_close = 0;
clear_bit(0, &wdt_is_open);
return 0;
}
/*
* Notifier for system down
*/
static int
wdt_notify_sys(struct notifier_block *this, unsigned long code,
void *unused)
{
if (code == SYS_DOWN || code == SYS_HALT) {
/* Turn the WDT off */
wdt_disable();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = wdt_write,
.ioctl = wdt_ioctl,
.open = wdt_open,
.release = wdt_close,
};
static struct miscdevice wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &wdt_fops,
};
/*
* The WDT needs to learn about soft shutdowns in order to
* turn the timebomb registers off.
*/
static struct notifier_block wdt_notifier = {
.notifier_call = wdt_notify_sys,
};
static int
w83697hf_check_wdt(void)
{
if (!request_region(wdt_io, 2, WATCHDOG_NAME)) {
printk (KERN_ERR PFX "I/O address 0x%x already in use\n", wdt_io);
return -EIO;
}
printk (KERN_DEBUG PFX "Looking for watchdog at address 0x%x\n", wdt_io);
w83697hf_unlock();
if (w83697hf_get_reg(0x20) == 0x60) {
printk (KERN_INFO PFX "watchdog found at address 0x%x\n", wdt_io);
w83697hf_lock();
return 0;
}
w83697hf_lock(); /* Reprotect in case it was a compatible device */
printk (KERN_INFO PFX "watchdog not found at address 0x%x\n", wdt_io);
release_region(wdt_io, 2);
return -EIO;
}
static int w83697hf_ioports[] = { 0x2e, 0x4e, 0x00 };
static int __init
wdt_init(void)
{
int ret, i, found = 0;
spin_lock_init(&io_lock);
printk (KERN_INFO PFX "WDT driver for W83697HF/HG initializing\n");
if (wdt_io == 0) {
/* we will autodetect the W83697HF/HG watchdog */
for (i = 0; ((!found) && (w83697hf_ioports[i] != 0)); i++) {
wdt_io = w83697hf_ioports[i];
if (!w83697hf_check_wdt())
found++;
}
} else {
if (!w83697hf_check_wdt())
found++;
}
if (!found) {
printk (KERN_ERR PFX "No W83697HF/HG could be found\n");
ret = -EIO;
goto out;
}
w83697hf_init();
wdt_disable(); /* Disable watchdog until first use */
if (wdt_set_heartbeat(timeout)) {
wdt_set_heartbeat(WATCHDOG_TIMEOUT);
printk (KERN_INFO PFX "timeout value must be 1<=timeout<=255, using %d\n",
WATCHDOG_TIMEOUT);
}
ret = register_reboot_notifier(&wdt_notifier);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto unreg_regions;
}
ret = misc_register(&wdt_miscdev);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_reboot;
}
printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
out:
return ret;
unreg_reboot:
unregister_reboot_notifier(&wdt_notifier);
unreg_regions:
release_region(wdt_io, 2);
goto out;
}
static void __exit
wdt_exit(void)
{
misc_deregister(&wdt_miscdev);
unregister_reboot_notifier(&wdt_notifier);
release_region(wdt_io, 2);
}
module_init(wdt_init);
module_exit(wdt_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marcus Junker <junker@anduras.de>, Samuel Tardieu <sam@rfc1149.net>");
MODULE_DESCRIPTION("w83697hf/hg WDT driver");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);

View File

@@ -0,0 +1,415 @@
/*
* 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.
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define OUR_NAME "w83877f_wdt"
#define PFX OUR_NAME ": "
#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 */
static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static 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 spinlock_t 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 {
printk(KERN_WARNING PFX "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);
printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
}
static void wdt_turnoff(void)
{
/* Stop the timer */
del_timer(&timer);
wdt_change(WDT_DISABLE);
printk(KERN_INFO PFX "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);
printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
}
clear_bit(0, &wdt_is_open);
wdt_expect_close = 0;
return 0;
}
static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident=
{
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.firmware_version = 1,
.identity = "W83877F",
};
switch(cmd)
{
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_keepalive();
return 0;
case WDIOC_SETOPTIONS:
{
int new_options, retval = -EINVAL;
if(get_user(new_options, p))
return -EFAULT;
if(new_options & WDIOS_DISABLECARD) {
wdt_turnoff();
retval = 0;
}
if(new_options & WDIOS_ENABLECARD) {
wdt_startup();
retval = 0;
}
return retval;
}
case WDIOC_SETTIMEOUT:
{
int new_timeout;
if(get_user(new_timeout, p))
return -EFAULT;
if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
return -EINVAL;
timeout = new_timeout;
wdt_keepalive();
/* Fall through */
}
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
}
}
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = fop_write,
.open = fop_open,
.release = fop_close,
.ioctl = fop_ioctl,
};
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;
spin_lock_init(&wdt_spinlock);
if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
{
timeout = WATCHDOG_TIMEOUT;
printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
timeout);
}
if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT"))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
ENABLE_W83877F_PORT);
rc = -EIO;
goto err_out;
}
if (!request_region(WDT_PING, 1, "W8387FF WDT"))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
WDT_PING);
rc = -EIO;
goto err_out_region1;
}
rc = misc_register(&wdt_miscdev);
if (rc)
{
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
wdt_miscdev.minor, rc);
goto err_out_region2;
}
rc = register_reboot_notifier(&wdt_notifier);
if (rc)
{
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_miscdev;
}
printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
timeout, nowayout);
return 0;
err_out_miscdev:
misc_deregister(&wdt_miscdev);
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);

View File

@@ -0,0 +1,542 @@
/*
* 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.
*
*/
#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 <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#define WATCHDOG_VERSION "1.00"
#define WATCHDOG_NAME "W83977F WDT"
#define PFX WATCHDOG_NAME ": "
#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n"
#define IO_INDEX_PORT 0x3F0
#define IO_DATA_PORT (IO_INDEX_PORT+1)
#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 spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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);
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "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();
printk(KERN_CRIT PFX "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 struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
.firmware_version = 1,
.identity = WATCHDOG_NAME,
};
static int wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
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)
{
default:
return -ENOTTY;
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_KEEPALIVE:
wdt_keepalive();
return 0;
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_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);
}
}
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,
.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;
printk(KERN_INFO PFX DRIVER_VERSION);
spin_lock_init(&spinlock);
/*
* Check that the timeout value is within it's range ;
* if not reset to the default
*/
if (wdt_set_timeout(timeout)) {
wdt_set_timeout(DEFAULT_TIMEOUT);
printk(KERN_INFO PFX "timeout value must be 15<=timeout<=7635, using %d\n",
DEFAULT_TIMEOUT);
}
if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
IO_INDEX_PORT);
rc = -EIO;
goto err_out;
}
rc = misc_register(&wdt_miscdev);
if (rc)
{
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
wdt_miscdev.minor, rc);
goto err_out_region;
}
rc = register_reboot_notifier(&wdt_notifier);
if (rc)
{
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_miscdev;
}
printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d testmode=%d)\n",
timeout, nowayout, testmode);
return 0;
err_out_miscdev:
misc_deregister(&wdt_miscdev);
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);

View File

@@ -0,0 +1,325 @@
/*
* 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@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#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 spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static 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 int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int new_timeout;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static struct watchdog_info ident = {
.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
.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_KEEPALIVE:
wafwdt_ping();
break;
case WDIOC_SETTIMEOUT:
if (get_user(new_timeout, p))
return -EFAULT;
if ((new_timeout < 1) || (new_timeout > 255))
return -EINVAL;
timeout = new_timeout;
wafwdt_stop();
wafwdt_start();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(timeout, p);
case WDIOC_SETOPTIONS:
{
int options, retval = -EINVAL;
if (get_user(options, p))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
wafwdt_start();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
wafwdt_stop();
retval = 0;
}
return retval;
}
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 {
printk(KERN_CRIT PFX "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) {
/* Turn the WDT off */
wafwdt_stop();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations wafwdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = wafwdt_write,
.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;
printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n");
spin_lock_init(&wafwdt_lock);
if (timeout < 1 || timeout > 255) {
timeout = WD_TIMO;
printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n",
timeout);
}
if (wdt_stop != wdt_start) {
if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_stop);
ret = -EIO;
goto error;
}
}
if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
wdt_start);
ret = -EIO;
goto error2;
}
ret = register_reboot_notifier(&wafwdt_notifier);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto error3;
}
ret = misc_register(&wafwdt_miscdev);
if (ret != 0) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto error4;
}
printk (KERN_INFO PFX "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 */

51
drivers/watchdog/wd501p.h Normal file
View File

@@ -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 */

695
drivers/watchdog/wdrtas.c Normal file
View File

@@ -0,0 +1,695 @@
/*
* 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.
*/
#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 <asm/rtas.h>
#include <asm/uaccess.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);
#ifdef CONFIG_WATCHDOG_NOWAYOUT
static int wdrtas_nowayout = 1;
#else
static int wdrtas_nowayout = 0;
#endif
static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0);
static char wdrtas_expect_close = 0;
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) ) {
printk(KERN_ERR "wdrtas: setting the watchdog to %i "
"timeout failed: %li\n", interval, result);
print_msg--;
}
return result;
}
/**
* 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[4];
result = rtas_call(wdrtas_token_get_sp, 3, 1, NULL,
WDRTAS_SP_SPI, (void *)__pa(&value), 4);
if ( (value[0] != 0) || (value[1] != 2) || (value[3] != 0) ||
(result < 0) ) {
printk(KERN_WARNING "wdrtas: could not get sp_spi watchdog "
"timeout (%li). Continuing\n", result);
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)
printk(KERN_INFO "wdrtas: dumping event (line %i/%i), data = "
"%02x %02x %02x %02x %02x %02x %02x %02x "
"%02x %02x %02x %02x %02x %02x %02x %02x\n",
(i / 16) + 1, (WDRTAS_LOGBUFFER_LEN / 16),
wdrtas_logbuffer[i + 0], wdrtas_logbuffer[i + 1],
wdrtas_logbuffer[i + 2], wdrtas_logbuffer[i + 3],
wdrtas_logbuffer[i + 4], wdrtas_logbuffer[i + 5],
wdrtas_logbuffer[i + 6], wdrtas_logbuffer[i + 7],
wdrtas_logbuffer[i + 8], wdrtas_logbuffer[i + 9],
wdrtas_logbuffer[i + 10], wdrtas_logbuffer[i + 11],
wdrtas_logbuffer[i + 12], wdrtas_logbuffer[i + 13],
wdrtas_logbuffer[i + 14], wdrtas_logbuffer[i + 15]);
}
/**
* wdrtas_timer_keepalive - resets watchdog timer to keep system alive
*
* wdrtas_timer_keepalive restarts the watchdog timer by calling the
* RTAS function event-scan and repeats these calls as long as there are
* events available. All events will be dumped.
*/
static void
wdrtas_timer_keepalive(void)
{
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)
printk(KERN_ERR "wdrtas: 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)
{
long result;
int temperature = 0;
result = rtas_call(wdrtas_token_get_sensor_state, 2, 2,
(void *)__pa(&temperature),
WDRTAS_THERMAL_SENSOR, 0);
if (result < 0)
printk(KERN_WARNING "wdrtas: reading the thermal sensor "
"faild: %li\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
* @inode: inode structure
* @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 int
wdrtas_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int __user *argp = (void __user *)arg;
int i;
static 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();
}
if (i & WDIOS_TEMPPANIC) {
/* not implemented. Done by H8 */
}
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 {
printk(KERN_WARNING "wdrtas: 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,
.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 succes, <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) {
printk(KERN_WARNING "wdrtas: couldn't get token for "
"get-sensor-state. Trying to continue without "
"temperature support.\n");
}
wdrtas_token_get_sp = rtas_token("ibm,get-system-parameter");
if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) {
printk(KERN_WARNING "wdrtas: couldn't get token for "
"ibm,get-system-parameter. Trying to continue with "
"a default timeout value of %i seconds.\n",
WDRTAS_DEFAULT_INTERVAL);
}
wdrtas_token_set_indicator = rtas_token("set-indicator");
if (wdrtas_token_set_indicator == RTAS_UNKNOWN_SERVICE) {
printk(KERN_ERR "wdrtas: couldn't get token for "
"set-indicator. Terminating watchdog code.\n");
return -EIO;
}
wdrtas_token_event_scan = rtas_token("event-scan");
if (wdrtas_token_event_scan == RTAS_UNKNOWN_SERVICE) {
printk(KERN_ERR "wdrtas: couldn't get token for event-scan. "
"Terminating watchdog code.\n");
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 succes, <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) {
printk(KERN_ERR "wdrtas: couldn't register watchdog misc "
"device. Terminating watchdog code.\n");
return result;
}
if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) {
result = misc_register(&wdrtas_tempdev);
if (result) {
printk(KERN_WARNING "wdrtas: couldn't register "
"watchdog temperature misc device. Continuing "
"without temperature support.\n");
wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE;
}
}
return 0;
}
/**
* wdrtas_init - init function of the watchdog driver
*
* returns 0 on succes, <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)) {
printk(KERN_ERR "wdrtas: 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);

640
drivers/watchdog/wdt.c Normal file
View File

@@ -0,0 +1,640 @@
/*
* Industrial Computer Source WDT500/501 driver
*
* (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
* 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
*/
#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 <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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;
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)");
#ifdef CONFIG_WDT_501
/* Support for the Fan Tachometer on the WDT501-P */
static int tachometer;
module_param(tachometer, int, 0);
MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)");
#endif /* CONFIG_WDT_501 */
/*
* 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)
{
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 */
return 0;
}
/**
* wdt_stop:
*
* Stop the watchdog driver.
*/
static int wdt_stop (void)
{
/* Turn the card off */
inb_p(WDT_DC); /* Disable watchdog */
wdt_ctr_load(2,0); /* 0 length reset pulses now */
return 0;
}
/**
* wdt_ping:
*
* Reload counter one with the watchdog heartbeat. We don't bother reloading
* the cascade counter.
*/
static int wdt_ping(void)
{
/* 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 */
return 0;
}
/**
* wdt_set_heartbeat:
* @t: the new heartbeat value that needs to be set.
*
* Set a new heartbeat value for the watchdog device. If the heartbeat value is
* incorrect we keep the old value and return -EINVAL. If successfull we
* return 0.
*/
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:
* @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 wdt_get_status(int *status)
{
unsigned char new_status=inb_p(WDT_SR);
*status=0;
if (new_status & WDC_SR_ISOI0)
*status |= WDIOF_EXTERN1;
if (new_status & WDC_SR_ISII1)
*status |= WDIOF_EXTERN2;
#ifdef CONFIG_WDT_501
if (!(new_status & WDC_SR_TGOOD))
*status |= WDIOF_OVERHEAT;
if (!(new_status & WDC_SR_PSUOVER))
*status |= WDIOF_POWEROVER;
if (!(new_status & WDC_SR_PSUUNDR))
*status |= WDIOF_POWERUNDER;
if (tachometer) {
if (!(new_status & WDC_SR_FANGOOD))
*status |= WDIOF_FANFAULT;
}
#endif /* CONFIG_WDT_501 */
return 0;
}
#ifdef CONFIG_WDT_501
/**
* 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(int *temperature)
{
unsigned short c=inb_p(WDT_RT);
*temperature = (c * 11 / 15) + 7;
return 0;
}
#endif /* CONFIG_WDT_501 */
/**
* 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=inb_p(WDT_SR);
printk(KERN_CRIT "WDT status %d\n", status);
#ifdef CONFIG_WDT_501
if (!(status & WDC_SR_TGOOD))
printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT));
if (!(status & WDC_SR_PSUOVER))
printk(KERN_CRIT "PSU over voltage.\n");
if (!(status & WDC_SR_PSUUNDR))
printk(KERN_CRIT "PSU under voltage.\n");
if (tachometer) {
if (!(status & WDC_SR_FANGOOD))
printk(KERN_CRIT "Possible fan fault.\n");
}
#endif /* CONFIG_WDT_501 */
if (!(status & WDC_SR_WCCR))
#ifdef SOFTWARE_REBOOT
#ifdef ONLY_TESTING
printk(KERN_CRIT "Would Reboot.\n");
#else
printk(KERN_CRIT "Initiating system reboot.\n");
emergency_restart();
#endif
#else
printk(KERN_CRIT "Reset in 5ms.\n");
#endif
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:
* @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. We only actually usefully support
* querying capabilities and current status.
*/
static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_heartbeat;
int status;
static 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);
#ifdef CONFIG_WDT_501
ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
if (tachometer)
ident.options |= WDIOF_FANFAULT;
#endif /* CONFIG_WDT_501 */
switch(cmd)
{
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
case WDIOC_GETSTATUS:
wdt_get_status(&status);
return put_user(status, p);
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_heartbeat, p))
return -EFAULT;
if (wdt_set_heartbeat(new_heartbeat))
return -EINVAL;
wdt_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p);
}
}
/**
* 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 {
printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n");
wdt_ping();
}
expect_close = 0;
return 0;
}
#ifdef CONFIG_WDT_501
/**
* wdt_temp_read:
* @file: file handle to the watchdog board
* @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;
if (wdt_get_temperature(&temperature))
return -EFAULT;
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;
}
#endif /* CONFIG_WDT_501 */
/**
* 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) {
/* Turn the card off */
wdt_stop();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = wdt_write,
.ioctl = wdt_ioctl,
.open = wdt_open,
.release = wdt_release,
};
static struct miscdevice wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &wdt_fops,
};
#ifdef CONFIG_WDT_501
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,
};
#endif /* CONFIG_WDT_501 */
/*
* 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);
#ifdef CONFIG_WDT_501
misc_deregister(&temp_miscdev);
#endif /* CONFIG_WDT_501 */
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;
/* Check that the heartbeat value is within it's range ; if not reset to the default */
if (wdt_set_heartbeat(heartbeat)) {
wdt_set_heartbeat(WD_TIMO);
printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n",
WD_TIMO);
}
if (!request_region(io, 8, "wdt501p")) {
printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io);
ret = -EBUSY;
goto out;
}
ret = request_irq(irq, wdt_interrupt, IRQF_DISABLED, "wdt501p", NULL);
if(ret) {
printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq);
goto outreg;
}
ret = register_reboot_notifier(&wdt_notifier);
if(ret) {
printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret);
goto outirq;
}
#ifdef CONFIG_WDT_501
ret = misc_register(&temp_miscdev);
if (ret) {
printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
TEMP_MINOR, ret);
goto outrbt;
}
#endif /* CONFIG_WDT_501 */
ret = misc_register(&wdt_miscdev);
if (ret) {
printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto outmisc;
}
ret = 0;
printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n",
io, irq, heartbeat, nowayout);
#ifdef CONFIG_WDT_501
printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
#endif /* CONFIG_WDT_501 */
out:
return ret;
outmisc:
#ifdef CONFIG_WDT_501
misc_deregister(&temp_miscdev);
outrbt:
#endif /* CONFIG_WDT_501 */
unregister_reboot_notifier(&wdt_notifier);
outirq:
free_irq(irq, NULL);
outreg:
release_region(io,8);
goto out;
}
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");

229
drivers/watchdog/wdt285.c Normal file
View File

@@ -0,0 +1,229 @@
/*
* 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@redhat.com>, 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.
*
*/
#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 <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/hardware.h>
#include <asm/mach-types.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)
{
printk(KERN_CRIT "Watchdog: 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 *data, size_t len, loff_t *ppos)
{
/*
* Refresh the timer.
*/
if (len)
watchdog_ping();
return len;
}
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT,
.identity = "Footbridge Watchdog",
};
static int
watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
unsigned int new_margin;
int ret = -ENOTTY;
switch(cmd) {
case WDIOC_GETSUPPORT:
ret = 0;
if (copy_to_user((void *)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,
.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;
printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n",
soft_margin);
if (machine_is_cats())
printk("Warning: Watchdog reset may not work on this machine.\n");
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);

519
drivers/watchdog/wdt977.c Normal file
View File

@@ -0,0 +1,519 @@
/*
* 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
*/
#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 <asm/io.h>
#include <asm/system.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#define WATCHDOG_VERSION "0.04"
#define WATCHDOG_NAME "Wdt977"
#define PFX WATCHDOG_NAME ": "
#define DRIVER_VERSION WATCHDOG_NAME " driver, v" WATCHDOG_VERSION "\n"
#define IO_INDEX_PORT 0x370 /* on some systems it can be 0x3F0 */
#define IO_DATA_PORT (IO_INDEX_PORT+1)
#define 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 spinlock_t 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 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);
printk(KERN_INFO PFX "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);
printk(KERN_INFO PFX "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();
printk(KERN_CRIT PFX "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;
}
/*
* 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 struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING,
.firmware_version = 1,
.identity = WATCHDOG_NAME,
};
static int wdt977_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
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)
{
default:
return -ENOTTY;
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_KEEPALIVE:
wdt977_keepalive();
return 0;
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_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);
}
}
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,
.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;
//if (!machine_is_netwinder())
// return -ENODEV;
printk(KERN_INFO PFX DRIVER_VERSION);
spin_lock_init(&spinlock);
/* Check that the timeout value is within it's range ; if not reset to the default */
if (wdt977_set_timeout(timeout))
{
wdt977_set_timeout(DEFAULT_TIMEOUT);
printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n",
DEFAULT_TIMEOUT);
}
/* on Netwinder the IOports are already reserved by
* arch/arm/mach-footbridge/netwinder-hw.c
*/
if (!machine_is_netwinder())
{
if (!request_region(IO_INDEX_PORT, 2, WATCHDOG_NAME))
{
printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
IO_INDEX_PORT);
rc = -EIO;
goto err_out;
}
}
rc = misc_register(&wdt977_miscdev);
if (rc)
{
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
wdt977_miscdev.minor, rc);
goto err_out_region;
}
rc = register_reboot_notifier(&wdt977_notifier);
if (rc)
{
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
rc);
goto err_out_miscdev;
}
printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode=%i)\n",
timeout, nowayout, testmode);
return 0;
err_out_miscdev:
misc_deregister(&wdt977_miscdev);
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);

756
drivers/watchdog/wdt_pci.c Normal file
View File

@@ -0,0 +1,756 @@
/*
* Industrial Computer Source PCI-WDT500/501 driver
*
* (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
* http://www.redhat.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
* warranty for any of this software. This material is provided
* "AS-IS" and at no charge.
*
* (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
*
* 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
*/
#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/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#define WDT_IS_PCI
#include "wd501p.h"
#define PFX "wdt_pci: "
/*
* Until Access I/O gets their application for a PCI vendor ID approved,
* I don't think that it's appropriate to move these constants into the
* regular pci_ids.h file. -- JPN 2000/01/18
*/
#ifndef PCI_VENDOR_ID_ACCESSIO
#define PCI_VENDOR_ID_ACCESSIO 0x494f
#endif
#ifndef PCI_DEVICE_ID_WDG_CSM
#define PCI_DEVICE_ID_WDG_CSM 0x22c0
#endif
/* We can only use 1 card due to the /dev/watchdog restriction */
static int dev_count;
static struct semaphore open_sem;
static spinlock_t wdtpci_lock;
static char expect_close;
static int 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 int nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
#ifdef CONFIG_WDT_501_PCI
/* Support for the Fan Tachometer on the PCI-WDT501 */
static int tachometer;
module_param(tachometer, int, 0);
MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)");
#endif /* CONFIG_WDT_501_PCI */
/*
* Programming support
*/
static void wdtpci_ctr_mode(int ctr, int mode)
{
ctr<<=6;
ctr|=0x30;
ctr|=(mode<<1);
outb_p(ctr, WDT_CR);
}
static void wdtpci_ctr_load(int ctr, int val)
{
outb_p(val&0xFF, WDT_COUNT0+ctr);
outb_p(val>>8, WDT_COUNT0+ctr);
}
/**
* 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_p(WDT_DC); /* Disable watchdog */
wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */
outb_p(0, WDT_DC); /* Enable watchdog */
inb_p(WDT_DC); /* Disable watchdog */
outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */
inb_p(WDT_BUZZER); /* disable */
inb_p(WDT_OPTONOTRST); /* disable */
inb_p(WDT_OPTORST); /* disable */
inb_p(WDT_PROGOUT); /* disable */
wdtpci_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */
wdtpci_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */
wdtpci_ctr_mode(2,1); /* Program CTR2 for Mode 1: Retriggerable One-Shot */
wdtpci_ctr_load(0,20833); /* count at 100Hz */
wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
/* DO NOT LOAD CTR2 on PCI card! -- JPN */
outb_p(0, WDT_DC); /* Enable watchdog */
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_p(WDT_DC); /* Disable watchdog */
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;
/* Write a watchdog value */
spin_lock_irqsave(&wdtpci_lock, flags);
inb_p(WDT_DC); /* Disable watchdog */
wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */
wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
outb_p(0, WDT_DC); /* Enable watchdog */
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 successfull 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=inb_p(WDT_SR);
*status=0;
if (new_status & WDC_SR_ISOI0)
*status |= WDIOF_EXTERN1;
if (new_status & WDC_SR_ISII1)
*status |= WDIOF_EXTERN2;
#ifdef CONFIG_WDT_501_PCI
if (!(new_status & WDC_SR_TGOOD))
*status |= WDIOF_OVERHEAT;
if (!(new_status & WDC_SR_PSUOVER))
*status |= WDIOF_POWEROVER;
if (!(new_status & WDC_SR_PSUUNDR))
*status |= WDIOF_POWERUNDER;
if (tachometer) {
if (!(new_status & WDC_SR_FANGOOD))
*status |= WDIOF_FANFAULT;
}
#endif /* CONFIG_WDT_501_PCI */
return 0;
}
#ifdef CONFIG_WDT_501_PCI
/**
* 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=inb_p(WDT_RT);
*temperature = (c * 11 / 15) + 7;
return 0;
}
#endif /* CONFIG_WDT_501_PCI */
/**
* 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=inb_p(WDT_SR);
printk(KERN_CRIT PFX "status %d\n", status);
#ifdef CONFIG_WDT_501_PCI
if (!(status & WDC_SR_TGOOD))
printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT));
if (!(status & WDC_SR_PSUOVER))
printk(KERN_CRIT PFX "PSU over voltage.\n");
if (!(status & WDC_SR_PSUUNDR))
printk(KERN_CRIT PFX "PSU under voltage.\n");
if (tachometer) {
if (!(status & WDC_SR_FANGOOD))
printk(KERN_CRIT PFX "Possible fan fault.\n");
}
#endif /* CONFIG_WDT_501_PCI */
if (!(status&WDC_SR_WCCR))
#ifdef SOFTWARE_REBOOT
#ifdef ONLY_TESTING
printk(KERN_CRIT PFX "Would Reboot.\n");
#else
printk(KERN_CRIT PFX "Initiating system reboot.\n");
emergency_restart(NULL);
#endif
#else
printk(KERN_CRIT PFX "Reset in 5ms.\n");
#endif
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;
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:
* @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. We only actually usefully support
* querying capabilities and current status.
*/
static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
int new_heartbeat;
int status;
void __user *argp = (void __user *)arg;
int __user *p = argp;
static 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);
#ifdef CONFIG_WDT_501_PCI
ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
if (tachometer)
ident.options |= WDIOF_FANFAULT;
#endif /* CONFIG_WDT_501_PCI */
switch(cmd)
{
default:
return -ENOTTY;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
case WDIOC_GETSTATUS:
wdtpci_get_status(&status);
return put_user(status, p);
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
wdtpci_ping();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_heartbeat, p))
return -EFAULT;
if (wdtpci_set_heartbeat(new_heartbeat))
return -EINVAL;
wdtpci_ping();
/* Fall */
case WDIOC_GETTIMEOUT:
return put_user(heartbeat, p);
}
}
/**
* 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 (down_trylock(&open_sem))
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 {
printk(KERN_CRIT PFX "Unexpected close, not stopping timer!");
wdtpci_ping();
}
expect_close = 0;
up(&open_sem);
return 0;
}
#ifdef CONFIG_WDT_501_PCI
/**
* 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;
}
#endif /* CONFIG_WDT_501_PCI */
/**
* 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) {
/* Turn the card off */
wdtpci_stop();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static const struct file_operations wdtpci_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = wdtpci_write,
.ioctl = wdtpci_ioctl,
.open = wdtpci_open,
.release = wdtpci_release,
};
static struct miscdevice wdtpci_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &wdtpci_fops,
};
#ifdef CONFIG_WDT_501_PCI
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,
};
#endif /* CONFIG_WDT_501_PCI */
/*
* 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) {
printk (KERN_ERR PFX "this driver only supports 1 device\n");
return -ENODEV;
}
if (pci_enable_device (dev)) {
printk (KERN_ERR PFX "Not possible to enable PCI Device\n");
return -ENODEV;
}
if (pci_resource_start (dev, 2) == 0x0000) {
printk (KERN_ERR PFX "No I/O-Address for card detected\n");
ret = -ENODEV;
goto out_pci;
}
sema_init(&open_sem, 1);
spin_lock_init(&wdtpci_lock);
irq = dev->irq;
io = pci_resource_start (dev, 2);
if (request_region (io, 16, "wdt_pci") == NULL) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io);
goto out_pci;
}
if (request_irq (irq, wdtpci_interrupt, IRQF_DISABLED | IRQF_SHARED,
"wdt_pci", &wdtpci_miscdev)) {
printk (KERN_ERR PFX "IRQ %d is not free\n", irq);
goto out_reg;
}
printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n",
io, irq);
/* Check that the heartbeat value is within it's range ; if not reset to the default */
if (wdtpci_set_heartbeat(heartbeat)) {
wdtpci_set_heartbeat(WD_TIMO);
printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
WD_TIMO);
}
ret = register_reboot_notifier (&wdtpci_notifier);
if (ret) {
printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret);
goto out_irq;
}
#ifdef CONFIG_WDT_501_PCI
ret = misc_register (&temp_miscdev);
if (ret) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
TEMP_MINOR, ret);
goto out_rbt;
}
#endif /* CONFIG_WDT_501_PCI */
ret = misc_register (&wdtpci_miscdev);
if (ret) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto out_misc;
}
printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
heartbeat, nowayout);
#ifdef CONFIG_WDT_501_PCI
printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
#endif /* CONFIG_WDT_501_PCI */
ret = 0;
out:
return ret;
out_misc:
#ifdef CONFIG_WDT_501_PCI
misc_deregister(&temp_miscdev);
out_rbt:
#endif /* CONFIG_WDT_501_PCI */
unregister_reboot_notifier(&wdtpci_notifier);
out_irq:
free_irq(irq, &wdtpci_miscdev);
out_reg:
release_region (io, 16);
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);
#ifdef CONFIG_WDT_501_PCI
misc_deregister(&temp_miscdev);
#endif /* CONFIG_WDT_501_PCI */
unregister_reboot_notifier(&wdtpci_notifier);
free_irq(irq, &wdtpci_miscdev);
release_region(io, 16);
pci_disable_device(pdev);
dev_count--;
}
static struct pci_device_id wdtpci_pci_tbl[] = {
{
.vendor = PCI_VENDOR_ID_ACCESSIO,
.device = PCI_DEVICE_ID_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);