Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
786
drivers/char/watchdog/Kconfig
Normal file
786
drivers/char/watchdog/Kconfig
Normal file
@@ -0,0 +1,786 @@
|
||||
#
|
||||
# Watchdog device configuration
|
||||
#
|
||||
|
||||
menu "Watchdog Cards"
|
||||
|
||||
config 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.
|
||||
|
||||
config WATCHDOG_NOWAYOUT
|
||||
bool "Disable watchdog shutdown on close"
|
||||
depends on WATCHDOG
|
||||
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"
|
||||
depends on WATCHDOG
|
||||
|
||||
# Architecture Independent
|
||||
|
||||
config SOFT_WATCHDOG
|
||||
tristate "Software watchdog"
|
||||
depends on 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.
|
||||
|
||||
# ARM Architecture
|
||||
|
||||
config AT91RM9200_WATCHDOG
|
||||
tristate "AT91RM9200 watchdog"
|
||||
depends on WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 S3C2410_WATCHDOG
|
||||
tristate "S3C2410 Watchdog"
|
||||
depends on WATCHDOG && 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 WATCHDOG && ( 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && (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 WATCHDOG && 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.
|
||||
|
||||
# X86 (i386 + ia64 + x86_64) Architecture
|
||||
|
||||
config ACQUIRE_WDT
|
||||
tristate "Acquire SBC Watchdog Timer"
|
||||
depends on WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 I8XX_TCO
|
||||
tristate "Intel i8xx TCO Timer/Watchdog"
|
||||
depends on WATCHDOG && (X86 || IA64) && PCI
|
||||
default n
|
||||
---help---
|
||||
Hardware driver for the TCO timer built into the Intel 82801
|
||||
I/O Controller Hub family. 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 i8xx_tco.
|
||||
|
||||
Note: This driver will be removed in the near future. Please
|
||||
use the Intel TCO Timer/Watchdog driver.
|
||||
|
||||
config ITCO_WDT
|
||||
tristate "Intel TCO Timer/Watchdog"
|
||||
depends on WATCHDOG && (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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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.
|
||||
|
||||
# PowerPC Architecture
|
||||
|
||||
config 8xx_WDT
|
||||
tristate "MPC8xx Watchdog Timer"
|
||||
depends on WATCHDOG && 8xx
|
||||
|
||||
config 83xx_WDT
|
||||
tristate "MPC83xx Watchdog Timer"
|
||||
depends on WATCHDOG && PPC_83xx
|
||||
|
||||
config MV64X60_WDT
|
||||
tristate "MV64X60 (Marvell Discovery) Watchdog Timer"
|
||||
depends on WATCHDOG && MV64X60
|
||||
|
||||
config BOOKE_WDT
|
||||
tristate "PowerPC Book-E Watchdog Timer"
|
||||
depends on WATCHDOG && (BOOKE || 4xx)
|
||||
---help---
|
||||
Please see Documentation/watchdog/watchdog-api.txt for
|
||||
more information.
|
||||
|
||||
# PPC64 Architecture
|
||||
|
||||
config WATCHDOG_RTAS
|
||||
tristate "RTAS watchdog"
|
||||
depends on WATCHDOG && 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.
|
||||
|
||||
# MIPS Architecture
|
||||
|
||||
config INDYDOG
|
||||
tristate "Indy/I2 Hardware Watchdog"
|
||||
depends on WATCHDOG && 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_RM9K_GPI
|
||||
tristate "RM9000/GPI hardware watchdog"
|
||||
depends on WATCHDOG && 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.
|
||||
|
||||
# S390 Architecture
|
||||
|
||||
config ZVM_WATCHDOG
|
||||
tristate "z/VM Watchdog Timer"
|
||||
depends on WATCHDOG && 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 Architecture
|
||||
|
||||
config SH_WDT
|
||||
tristate "SuperH Watchdog"
|
||||
depends on WATCHDOG && SUPERH
|
||||
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.
|
||||
|
||||
# SPARC64 Architecture
|
||||
|
||||
config WATCHDOG_CP1XXX
|
||||
tristate "CP1XXX Hardware Watchdog support"
|
||||
depends on WATCHDOG && 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 WATCHDOG && 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.
|
||||
|
||||
#
|
||||
# ISA-based Watchdog Cards
|
||||
#
|
||||
|
||||
comment "ISA-based Watchdog Cards"
|
||||
depends on WATCHDOG && ISA
|
||||
|
||||
config PCWATCHDOG
|
||||
tristate "Berkshire Products ISA-PC Watchdog"
|
||||
depends on WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && PCI
|
||||
|
||||
config PCIPCWATCHDOG
|
||||
tristate "Berkshire Products PCI-PC Watchdog"
|
||||
depends on WATCHDOG && 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 WATCHDOG && 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 WATCHDOG && USB
|
||||
|
||||
config USBPCWATCHDOG
|
||||
tristate "Berkshire Products USB-PC Watchdog"
|
||||
depends on WATCHDOG && 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.
|
||||
|
||||
endmenu
|
||||
86
drivers/char/watchdog/Makefile
Normal file
86
drivers/char/watchdog/Makefile
Normal file
@@ -0,0 +1,86 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
# 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_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
|
||||
|
||||
# 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_I8XX_TCO) += i8xx_tco.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
|
||||
|
||||
# PowerPC Architecture
|
||||
obj-$(CONFIG_8xx_WDT) += mpc8xx_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
|
||||
|
||||
# MIPS Architecture
|
||||
obj-$(CONFIG_INDYDOG) += indydog.o
|
||||
obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o
|
||||
|
||||
# S390 Architecture
|
||||
|
||||
# SUPERH Architecture
|
||||
obj-$(CONFIG_SH_WDT) += shwdt.o
|
||||
|
||||
# SPARC64 Architecture
|
||||
|
||||
# Architecture Independant
|
||||
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
|
||||
348
drivers/char/watchdog/acquirewdt.c
Normal file
348
drivers/char/watchdog/acquirewdt.c
Normal 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);
|
||||
362
drivers/char/watchdog/advantechwdt.c
Normal file
362
drivers/char/watchdog/advantechwdt.c
Normal 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);
|
||||
462
drivers/char/watchdog/alim1535_wdt.c
Normal file
462
drivers/char/watchdog/alim1535_wdt.c
Normal file
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
* 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, 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 1535 series bridge */
|
||||
pdev = pci_get_device(PCI_VENDOR_ID_AL, 0x1535, 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);
|
||||
423
drivers/char/watchdog/alim7101_wdt.c
Normal file
423
drivers/char/watchdog/alim7101_wdt.c
Normal 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);
|
||||
288
drivers/char/watchdog/at91rm9200_wdt.c
Normal file
288
drivers/char/watchdog/at91rm9200_wdt.c
Normal 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);
|
||||
194
drivers/char/watchdog/booke_wdt.c
Normal file
194
drivers/char/watchdog/booke_wdt.c
Normal 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_enable=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 0;
|
||||
}
|
||||
|
||||
static const struct file_operations booke_wdt_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.write = booke_wdt_write,
|
||||
.ioctl = booke_wdt_ioctl,
|
||||
.open = booke_wdt_open,
|
||||
};
|
||||
|
||||
static struct 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);
|
||||
300
drivers/char/watchdog/cpu5wdt.c
Normal file
300
drivers/char/watchdog/cpu5wdt.c
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* sma cpu5 watchdog driver
|
||||
*
|
||||
* Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#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_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 ( (err = misc_register(&cpu5wdt_misc)) < 0 ) {
|
||||
printk(KERN_ERR PFX "misc_register failed\n");
|
||||
goto no_misc;
|
||||
}
|
||||
|
||||
if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) {
|
||||
printk(KERN_ERR PFX "request_region failed\n");
|
||||
err = -EBUSY;
|
||||
goto no_port;
|
||||
}
|
||||
|
||||
/* 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_port:
|
||||
misc_deregister(&cpu5wdt_misc);
|
||||
no_misc:
|
||||
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");
|
||||
257
drivers/char/watchdog/ep93xx_wdt.c
Normal file
257
drivers/char/watchdog/ep93xx_wdt.c
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
/* Can't seek (pwrite) on this device */
|
||||
if (*ppos != file->f_pos)
|
||||
return -ESPIPE;
|
||||
|
||||
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);
|
||||
468
drivers/char/watchdog/eurotechwdt.c
Normal file
468
drivers/char/watchdog/eurotechwdt.c
Normal file
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* Eurotech CPU-1220/1410 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:
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 2001 - Rodolfo Giometti
|
||||
* Initial release
|
||||
*
|
||||
* 2002.05.30 - Joel Becker <joel.becker@oracle.com>
|
||||
* Added Matt Domsch's 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>
|
||||
|
||||
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 = misc_register(&eurwdt_miscdev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n",
|
||||
WATCHDOG_MINOR);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = request_irq(irq, eurwdt_interrupt, IRQF_DISABLED, "eurwdt", NULL);
|
||||
if(ret) {
|
||||
printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq);
|
||||
goto outmisc;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
outreg:
|
||||
release_region(io, 2);
|
||||
|
||||
outirq:
|
||||
free_irq(irq, NULL);
|
||||
|
||||
outmisc:
|
||||
misc_deregister(&eurwdt_miscdev);
|
||||
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/char/watchdog/i6300esb.c
Normal file
527
drivers/char/watchdog/i6300esb.c
Normal 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);
|
||||
571
drivers/char/watchdog/i8xx_tco.c
Normal file
571
drivers/char/watchdog/i8xx_tco.c
Normal file
@@ -0,0 +1,571 @@
|
||||
/*
|
||||
* i8xx_tco: TCO timer driver for i8xx chipsets
|
||||
*
|
||||
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
|
||||
* http://www.kernelconcepts.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* Neither kernel concepts nor Nils Faerber admit liability nor provide
|
||||
* warranty for any of this software. This material is provided
|
||||
* "AS-IS" and at no charge.
|
||||
*
|
||||
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
|
||||
* developed for
|
||||
* Jentro AG, Haar/Munich (Germany)
|
||||
*
|
||||
* TCO timer driver for i8xx chipsets
|
||||
* based on softdog.c by Alan Cox <alan@redhat.com>
|
||||
*
|
||||
* The TCO timer 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,
|
||||
*
|
||||
* 20000710 Nils Faerber
|
||||
* Initial Version 0.01
|
||||
* 20000728 Nils Faerber
|
||||
* 0.02 Fix for SMI_EN->TCO_EN bit, some cleanups
|
||||
* 20011214 Matt Domsch <Matt_Domsch@dell.com>
|
||||
* 0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
|
||||
* Didn't add timeout option as i810_margin already exists.
|
||||
* 20020224 Joel Becker, Wim Van Sebroeck
|
||||
* 0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3,
|
||||
* add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT.
|
||||
* 20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck
|
||||
* 0.05 Fix possible timer_alive race, add expect close support,
|
||||
* clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and
|
||||
* WDIOC_SETOPTIONS), made i810tco_getdevice __init,
|
||||
* removed boot_status, removed tco_timer_read,
|
||||
* added support for 82801DB and 82801E chipset,
|
||||
* added support for 82801EB and 8280ER chipset,
|
||||
* general cleanup.
|
||||
* 20030921 Wim Van Sebroeck <wim@iguana.be>
|
||||
* 0.06 change i810_margin to heartbeat, use module_param,
|
||||
* added notify system support, renamed module to i8xx_tco.
|
||||
* 20050128 Wim Van Sebroeck <wim@iguana.be>
|
||||
* 0.07 Added support for the ICH4-M, ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW
|
||||
* chipsets. Also added support for the "undocumented" ICH7 chipset.
|
||||
* 20050807 Wim Van Sebroeck <wim@iguana.be>
|
||||
* 0.08 Make sure that the watchdog is only "armed" when started.
|
||||
* (Kernel Bug 4251)
|
||||
* 20060416 Wim Van Sebroeck <wim@iguana.be>
|
||||
* 0.09 Remove support for the ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW and
|
||||
* ICH7 chipsets. (See Kernel Bug 6031 - other code will support these
|
||||
* chipsets)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Includes, defines, variables, module parameters, ...
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/watchdog.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/ioport.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#include "i8xx_tco.h"
|
||||
|
||||
/* Module and version information */
|
||||
#define TCO_VERSION "0.09"
|
||||
#define TCO_MODULE_NAME "i8xx TCO timer"
|
||||
#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
|
||||
#define PFX TCO_MODULE_NAME ": "
|
||||
|
||||
/* internal variables */
|
||||
static unsigned int ACPIBASE;
|
||||
static spinlock_t tco_lock; /* Guards the hardware */
|
||||
static unsigned long timer_alive;
|
||||
static char tco_expect_close;
|
||||
static struct pci_dev *i8xx_tco_pci;
|
||||
|
||||
/* module parameters */
|
||||
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
|
||||
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
|
||||
module_param(heartbeat, int, 0);
|
||||
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
|
||||
|
||||
static 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 TCO specific functions
|
||||
*/
|
||||
|
||||
static inline unsigned char seconds_to_ticks(int seconds)
|
||||
{
|
||||
/* the internal timer is stored as ticks which decrement
|
||||
* every 0.6 seconds */
|
||||
return (seconds * 10) / 6;
|
||||
}
|
||||
|
||||
static int tco_timer_start (void)
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
spin_lock(&tco_lock);
|
||||
|
||||
/* disable chipset's NO_REBOOT bit */
|
||||
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val);
|
||||
val &= 0xfd;
|
||||
pci_write_config_byte (i8xx_tco_pci, 0xd4, val);
|
||||
|
||||
/* Bit 11: TCO Timer Halt -> 0 = The TCO timer is enabled to count */
|
||||
val = inb (TCO1_CNT + 1);
|
||||
val &= 0xf7;
|
||||
outb (val, TCO1_CNT + 1);
|
||||
val = inb (TCO1_CNT + 1);
|
||||
|
||||
spin_unlock(&tco_lock);
|
||||
|
||||
if (val & 0x08)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_stop (void)
|
||||
{
|
||||
unsigned char val, val1;
|
||||
|
||||
spin_lock(&tco_lock);
|
||||
/* Bit 11: TCO Timer Halt -> 1 = The TCO timer is disabled */
|
||||
val = inb (TCO1_CNT + 1);
|
||||
val |= 0x08;
|
||||
outb (val, TCO1_CNT + 1);
|
||||
val = inb (TCO1_CNT + 1);
|
||||
|
||||
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
|
||||
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
|
||||
val1 |= 0x02;
|
||||
pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
|
||||
|
||||
spin_unlock(&tco_lock);
|
||||
|
||||
if ((val & 0x08) == 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_keepalive (void)
|
||||
{
|
||||
spin_lock(&tco_lock);
|
||||
/* Reload the timer by writing to the TCO Timer Reload register */
|
||||
outb (0x01, TCO1_RLD);
|
||||
spin_unlock(&tco_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_set_heartbeat (int t)
|
||||
{
|
||||
unsigned char val;
|
||||
unsigned char tmrval;
|
||||
|
||||
tmrval = seconds_to_ticks(t);
|
||||
/* from the specs: */
|
||||
/* "Values of 0h-3h are ignored and should not be attempted" */
|
||||
if (tmrval > 0x3f || tmrval < 0x04)
|
||||
return -EINVAL;
|
||||
|
||||
/* Write new heartbeat to watchdog */
|
||||
spin_lock(&tco_lock);
|
||||
val = inb (TCO1_TMR);
|
||||
val &= 0xc0;
|
||||
val |= tmrval;
|
||||
outb (val, TCO1_TMR);
|
||||
val = inb (TCO1_TMR);
|
||||
spin_unlock(&tco_lock);
|
||||
|
||||
if ((val & 0x3f) != tmrval)
|
||||
return -EINVAL;
|
||||
|
||||
heartbeat = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tco_timer_get_timeleft (int *time_left)
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
spin_lock(&tco_lock);
|
||||
|
||||
/* read the TCO Timer */
|
||||
val = inb (TCO1_RLD);
|
||||
val &= 0x3f;
|
||||
|
||||
spin_unlock(&tco_lock);
|
||||
|
||||
*time_left = (int)((val * 6) / 10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* /dev/watchdog handling
|
||||
*/
|
||||
|
||||
static int i8xx_tco_open (struct inode *inode, struct file *file)
|
||||
{
|
||||
/* /dev/watchdog can only be opened once */
|
||||
if (test_and_set_bit(0, &timer_alive))
|
||||
return -EBUSY;
|
||||
|
||||
/*
|
||||
* Reload and activate timer
|
||||
*/
|
||||
tco_timer_keepalive ();
|
||||
tco_timer_start ();
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static int i8xx_tco_release (struct inode *inode, struct file *file)
|
||||
{
|
||||
/*
|
||||
* Shut off the timer.
|
||||
*/
|
||||
if (tco_expect_close == 42) {
|
||||
tco_timer_stop ();
|
||||
} else {
|
||||
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
|
||||
tco_timer_keepalive ();
|
||||
}
|
||||
clear_bit(0, &timer_alive);
|
||||
tco_expect_close = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t i8xx_tco_write (struct file *file, const char __user *data,
|
||||
size_t len, loff_t * ppos)
|
||||
{
|
||||
/* See if we got the magic character 'V' and reload the timer */
|
||||
if (len) {
|
||||
if (!nowayout) {
|
||||
size_t i;
|
||||
|
||||
/* note: just in case someone wrote the magic character
|
||||
* five months ago... */
|
||||
tco_expect_close = 0;
|
||||
|
||||
/* scan to see whether or not we got the magic character */
|
||||
for (i = 0; i != len; i++) {
|
||||
char c;
|
||||
if(get_user(c, data+i))
|
||||
return -EFAULT;
|
||||
if (c == 'V')
|
||||
tco_expect_close = 42;
|
||||
}
|
||||
}
|
||||
|
||||
/* someone wrote to us, we should reload the timer */
|
||||
tco_timer_keepalive ();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int i8xx_tco_ioctl (struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int new_options, retval = -EINVAL;
|
||||
int new_heartbeat;
|
||||
int time_left;
|
||||
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 = TCO_MODULE_NAME,
|
||||
};
|
||||
|
||||
switch (cmd) {
|
||||
case WDIOC_GETSUPPORT:
|
||||
return copy_to_user(argp, &ident,
|
||||
sizeof (ident)) ? -EFAULT : 0;
|
||||
|
||||
case WDIOC_GETSTATUS:
|
||||
case WDIOC_GETBOOTSTATUS:
|
||||
return put_user (0, p);
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
tco_timer_keepalive ();
|
||||
return 0;
|
||||
|
||||
case WDIOC_SETOPTIONS:
|
||||
{
|
||||
if (get_user (new_options, p))
|
||||
return -EFAULT;
|
||||
|
||||
if (new_options & WDIOS_DISABLECARD) {
|
||||
tco_timer_stop ();
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
if (new_options & WDIOS_ENABLECARD) {
|
||||
tco_timer_keepalive ();
|
||||
tco_timer_start ();
|
||||
retval = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
{
|
||||
if (get_user(new_heartbeat, p))
|
||||
return -EFAULT;
|
||||
|
||||
if (tco_timer_set_heartbeat(new_heartbeat))
|
||||
return -EINVAL;
|
||||
|
||||
tco_timer_keepalive ();
|
||||
/* Fall */
|
||||
}
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
return put_user(heartbeat, p);
|
||||
|
||||
case WDIOC_GETTIMELEFT:
|
||||
{
|
||||
if (tco_timer_get_timeleft(&time_left))
|
||||
return -EINVAL;
|
||||
|
||||
return put_user(time_left, p);
|
||||
}
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Notify system
|
||||
*/
|
||||
|
||||
static int i8xx_tco_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
|
||||
{
|
||||
if (code==SYS_DOWN || code==SYS_HALT) {
|
||||
/* Turn the WDT off */
|
||||
tco_timer_stop ();
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kernel Interfaces
|
||||
*/
|
||||
|
||||
static const struct file_operations i8xx_tco_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.llseek = no_llseek,
|
||||
.write = i8xx_tco_write,
|
||||
.ioctl = i8xx_tco_ioctl,
|
||||
.open = i8xx_tco_open,
|
||||
.release = i8xx_tco_release,
|
||||
};
|
||||
|
||||
static struct miscdevice i8xx_tco_miscdev = {
|
||||
.minor = WATCHDOG_MINOR,
|
||||
.name = "watchdog",
|
||||
.fops = &i8xx_tco_fops,
|
||||
};
|
||||
|
||||
static struct notifier_block i8xx_tco_notifier = {
|
||||
.notifier_call = i8xx_tco_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 i8xx_tco_pci_tbl[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0) },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1) },
|
||||
{ }, /* End of list */
|
||||
};
|
||||
MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl);
|
||||
|
||||
/*
|
||||
* Init & exit routines
|
||||
*/
|
||||
|
||||
static unsigned char __init i8xx_tco_getdevice (void)
|
||||
{
|
||||
struct pci_dev *dev = NULL;
|
||||
u8 val1, val2;
|
||||
u16 badr;
|
||||
/*
|
||||
* Find the PCI device
|
||||
*/
|
||||
|
||||
for_each_pci_dev(dev)
|
||||
if (pci_match_id(i8xx_tco_pci_tbl, dev)) {
|
||||
i8xx_tco_pci = dev;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i8xx_tco_pci) {
|
||||
/*
|
||||
* Find the ACPI 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_byte (i8xx_tco_pci, 0x40, &val1);
|
||||
pci_read_config_byte (i8xx_tco_pci, 0x41, &val2);
|
||||
badr = ((val2 << 1) | (val1 >> 7)) << 7;
|
||||
ACPIBASE = badr;
|
||||
/* Something's wrong here, ACPIBASE has to be set */
|
||||
if (badr == 0x0001 || badr == 0x0000) {
|
||||
printk (KERN_ERR PFX "failed to get TCOBASE address\n");
|
||||
pci_dev_put(i8xx_tco_pci);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check chipset's NO_REBOOT bit */
|
||||
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
|
||||
if (val1 & 0x02) {
|
||||
val1 &= 0xfd;
|
||||
pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
|
||||
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
|
||||
if (val1 & 0x02) {
|
||||
printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
|
||||
pci_dev_put(i8xx_tco_pci);
|
||||
return 0; /* Cannot reset NO_REBOOT bit */
|
||||
}
|
||||
}
|
||||
/* Disable reboots untill the watchdog starts */
|
||||
val1 |= 0x02;
|
||||
pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
|
||||
|
||||
/* Set the TCO_EN bit in SMI_EN register */
|
||||
if (!request_region (SMI_EN + 1, 1, "i8xx TCO")) {
|
||||
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
|
||||
SMI_EN + 1);
|
||||
pci_dev_put(i8xx_tco_pci);
|
||||
return 0;
|
||||
}
|
||||
val1 = inb (SMI_EN + 1);
|
||||
val1 &= 0xdf;
|
||||
outb (val1, SMI_EN + 1);
|
||||
release_region (SMI_EN + 1, 1);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init watchdog_init (void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_lock_init(&tco_lock);
|
||||
|
||||
/* Check whether or not the hardware watchdog is there */
|
||||
if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (!request_region (TCOBASE, 0x10, "i8xx TCO")) {
|
||||
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
|
||||
TCOBASE);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Clear out the (probably old) status */
|
||||
outb (0, TCO1_STS);
|
||||
outb (3, TCO2_STS);
|
||||
|
||||
/* Check that the heartbeat value is within it's range ; if not reset to the default */
|
||||
if (tco_timer_set_heartbeat (heartbeat)) {
|
||||
heartbeat = WATCHDOG_HEARTBEAT;
|
||||
tco_timer_set_heartbeat (heartbeat);
|
||||
printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, using %d\n",
|
||||
heartbeat);
|
||||
}
|
||||
|
||||
ret = register_reboot_notifier(&i8xx_tco_notifier);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
|
||||
ret);
|
||||
goto unreg_region;
|
||||
}
|
||||
|
||||
ret = misc_register(&i8xx_tco_miscdev);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
|
||||
WATCHDOG_MINOR, ret);
|
||||
goto unreg_notifier;
|
||||
}
|
||||
|
||||
tco_timer_stop ();
|
||||
|
||||
printk (KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
|
||||
TCOBASE, heartbeat, nowayout);
|
||||
|
||||
return 0;
|
||||
|
||||
unreg_notifier:
|
||||
unregister_reboot_notifier(&i8xx_tco_notifier);
|
||||
unreg_region:
|
||||
release_region (TCOBASE, 0x10);
|
||||
out:
|
||||
pci_dev_put(i8xx_tco_pci);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit watchdog_cleanup (void)
|
||||
{
|
||||
/* Stop the timer before we leave */
|
||||
if (!nowayout)
|
||||
tco_timer_stop ();
|
||||
|
||||
/* Deregister */
|
||||
misc_deregister (&i8xx_tco_miscdev);
|
||||
unregister_reboot_notifier(&i8xx_tco_notifier);
|
||||
release_region (TCOBASE, 0x10);
|
||||
|
||||
pci_dev_put(i8xx_tco_pci);
|
||||
}
|
||||
|
||||
module_init(watchdog_init);
|
||||
module_exit(watchdog_cleanup);
|
||||
|
||||
MODULE_AUTHOR("Nils Faerber");
|
||||
MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|
||||
42
drivers/char/watchdog/i8xx_tco.h
Normal file
42
drivers/char/watchdog/i8xx_tco.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* i8xx_tco: TCO timer driver for i8xx chipsets
|
||||
*
|
||||
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
|
||||
* http://www.kernelconcepts.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* Neither kernel concepts nor Nils Faerber admit liability nor provide
|
||||
* warranty for any of this software. This material is provided
|
||||
* "AS-IS" and at no charge.
|
||||
*
|
||||
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
|
||||
* developed for
|
||||
* Jentro AG, Haar/Munich (Germany)
|
||||
*
|
||||
* TCO timer driver for i8xx chipsets
|
||||
* based on softdog.c by Alan Cox <alan@redhat.com>
|
||||
*
|
||||
* For history and the complete list of supported I/O Controller Hub's
|
||||
* see i8xx_tco.c
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Some address definitions for the TCO
|
||||
*/
|
||||
|
||||
#define TCOBASE ACPIBASE + 0x60 /* TCO base address */
|
||||
#define TCO1_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */
|
||||
#define TCO1_TMR TCOBASE + 0x01 /* TCO Timer Initial Value */
|
||||
#define TCO1_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */
|
||||
#define TCO1_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 SMI_EN ACPIBASE + 0x30 /* SMI Control and Enable Register */
|
||||
307
drivers/char/watchdog/iTCO_vendor_support.c
Normal file
307
drivers/char/watchdog/iTCO_vendor_support.c
Normal 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 4’35”). 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");
|
||||
|
||||
772
drivers/char/watchdog/iTCO_wdt.c
Normal file
772
drivers/char/watchdog/iTCO_wdt.c
Normal file
@@ -0,0 +1,772 @@
|
||||
/*
|
||||
* 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,
|
||||
* 6300ESB (6300ESB) : document number 300641-003
|
||||
*/
|
||||
|
||||
/*
|
||||
* Includes, defines, variables, module parameters, ...
|
||||
*/
|
||||
|
||||
/* Module and version information */
|
||||
#define DRV_NAME "iTCO_wdt"
|
||||
#define DRV_VERSION "1.01"
|
||||
#define DRV_RELDATE "21-Jan-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 */
|
||||
};
|
||||
|
||||
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},
|
||||
{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 },
|
||||
{ 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 &= 0x00007f80;
|
||||
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/char/watchdog/ib700wdt.c
Normal file
408
drivers/char/watchdog/ib700wdt.c
Normal 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 */
|
||||
404
drivers/char/watchdog/ibmasr.c
Normal file
404
drivers/char/watchdog/ibmasr.c
Normal file
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* 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 = misc_register(&asr_miscdev);
|
||||
if (rc < 0) {
|
||||
printk(KERN_ERR PFX "failed to register misc device\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = asr_get_base_address();
|
||||
if (rc) {
|
||||
misc_deregister(&asr_miscdev);
|
||||
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/char/watchdog/indydog.c
Normal file
215
drivers/char/watchdog/indydog.c
Normal 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);
|
||||
219
drivers/char/watchdog/ixp2000_wdt.c
Normal file
219
drivers/char/watchdog/ixp2000_wdt.c
Normal 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);
|
||||
|
||||
225
drivers/char/watchdog/ixp4xx_wdt.c
Normal file
225
drivers/char/watchdog/ixp4xx_wdt.c
Normal 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);
|
||||
|
||||
488
drivers/char/watchdog/machzwd.c
Normal file
488
drivers/char/watchdog/machzwd.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/*
|
||||
* 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:
|
||||
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);
|
||||
|
||||
ret = misc_register(&zf_miscdev);
|
||||
if (ret){
|
||||
printk(KERN_ERR "can't misc_register on minor=%d\n",
|
||||
WATCHDOG_MINOR);
|
||||
goto out;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
zf_set_status(0);
|
||||
zf_set_control(0);
|
||||
|
||||
return 0;
|
||||
|
||||
no_reboot:
|
||||
release_region(ZF_IOBASE, 3);
|
||||
no_region:
|
||||
misc_deregister(&zf_miscdev);
|
||||
out:
|
||||
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);
|
||||
300
drivers/char/watchdog/mixcomwd.c
Normal file
300
drivers/char/watchdog/mixcomwd.c
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#define VERSION "0.5"
|
||||
|
||||
#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>
|
||||
|
||||
static int mixcomwd_ioports[] = { 0x180, 0x280, 0x380, 0x000 };
|
||||
|
||||
#define MIXCOM_WATCHDOG_OFFSET 0xc10
|
||||
#define MIXCOM_ID 0x11
|
||||
#define FLASHCOM_WATCHDOG_OFFSET 0x4
|
||||
#define FLASHCOM_ID 0x18
|
||||
|
||||
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 "mixcomwd: release called while internal timer alive");
|
||||
return -EBUSY;
|
||||
}
|
||||
mixcomwd_timer_alive=1;
|
||||
mod_timer(&mixcomwd_timer, jiffies + 5 * HZ);
|
||||
} else {
|
||||
printk(KERN_CRIT "mixcomwd: 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_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 mixcomwd_checkcard(int port)
|
||||
{
|
||||
int id;
|
||||
|
||||
port += MIXCOM_WATCHDOG_OFFSET;
|
||||
if (!request_region(port, 1, "MixCOM watchdog")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
id=inb_p(port) & 0x3f;
|
||||
if(id!=MIXCOM_ID) {
|
||||
release_region(port, 1);
|
||||
return 0;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
static int __init flashcom_checkcard(int port)
|
||||
{
|
||||
int id;
|
||||
|
||||
port += FLASHCOM_WATCHDOG_OFFSET;
|
||||
if (!request_region(port, 1, "MixCOM watchdog")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
id=inb_p(port);
|
||||
if(id!=FLASHCOM_ID) {
|
||||
release_region(port, 1);
|
||||
return 0;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
static int __init mixcomwd_init(void)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
int found=0;
|
||||
|
||||
for (i = 0; !found && mixcomwd_ioports[i] != 0; i++) {
|
||||
watchdog_port = mixcomwd_checkcard(mixcomwd_ioports[i]);
|
||||
if (watchdog_port) {
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* The FlashCOM card can be set up at 0x300 -> 0x378, in 0x8 jumps */
|
||||
for (i = 0x300; !found && i < 0x380; i+=0x8) {
|
||||
watchdog_port = flashcom_checkcard(i);
|
||||
if (watchdog_port) {
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
printk("mixcomwd: No card detected, or port not available.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = misc_register(&mixcomwd_miscdev);
|
||||
if (ret)
|
||||
{
|
||||
release_region(watchdog_port, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",VERSION,watchdog_port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit mixcomwd_exit(void)
|
||||
{
|
||||
if (!nowayout) {
|
||||
if(mixcomwd_timer_alive) {
|
||||
printk(KERN_WARNING "mixcomwd: I quit now, hardware will"
|
||||
" probably reboot!\n");
|
||||
del_timer_sync(&mixcomwd_timer);
|
||||
mixcomwd_timer_alive=0;
|
||||
}
|
||||
}
|
||||
release_region(watchdog_port,1);
|
||||
misc_deregister(&mixcomwd_miscdev);
|
||||
}
|
||||
|
||||
module_init(mixcomwd_init);
|
||||
module_exit(mixcomwd_exit);
|
||||
|
||||
MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
|
||||
MODULE_DESCRIPTION("MixCom Watchdog driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|
||||
228
drivers/char/watchdog/mpc83xx_wdt.c
Normal file
228
drivers/char/watchdog/mpc83xx_wdt.c
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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_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);
|
||||
169
drivers/char/watchdog/mpc8xx_wdt.c
Normal file
169
drivers/char/watchdog/mpc8xx_wdt.c
Normal 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 0;
|
||||
}
|
||||
|
||||
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);
|
||||
436
drivers/char/watchdog/mpcore_wdt.c
Normal file
436
drivers/char/watchdog/mpcore_wdt.c
Normal file
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* 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 = kmalloc(sizeof(struct mpcore_wdt), GFP_KERNEL);
|
||||
if (!wdt) {
|
||||
ret = -ENOMEM;
|
||||
goto err_out;
|
||||
}
|
||||
memset(wdt, 0, sizeof(struct mpcore_wdt));
|
||||
|
||||
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);
|
||||
264
drivers/char/watchdog/mv64x60_wdt.c
Normal file
264
drivers/char/watchdog/mv64x60_wdt.c
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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 <asm/mv64x60.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
/* MV64x60 WDC (config) register access definitions */
|
||||
#define MV64x60_WDC_CTL1_MASK (3 << 24)
|
||||
#define MV64x60_WDC_CTL1(val) ((val & 3) << 24)
|
||||
#define MV64x60_WDC_CTL2_MASK (3 << 26)
|
||||
#define MV64x60_WDC_CTL2(val) ((val & 3) << 26)
|
||||
|
||||
/* Flags bits */
|
||||
#define MV64x60_WDOG_FLAG_OPENED 0
|
||||
#define MV64x60_WDOG_FLAG_ENABLED 1
|
||||
|
||||
static unsigned long wdt_flags;
|
||||
static int wdt_status;
|
||||
static void __iomem *mv64x60_regs;
|
||||
static int mv64x60_wdt_timeout;
|
||||
|
||||
static void mv64x60_wdt_reg_write(u32 val)
|
||||
{
|
||||
/* Allow write only to CTL1 / CTL2 fields, retaining values in
|
||||
* other fields.
|
||||
*/
|
||||
u32 data = readl(mv64x60_regs + MV64x60_WDT_WDC);
|
||||
data &= ~(MV64x60_WDC_CTL1_MASK | MV64x60_WDC_CTL2_MASK);
|
||||
data |= val;
|
||||
writel(data, mv64x60_regs + MV64x60_WDT_WDC);
|
||||
}
|
||||
|
||||
static void mv64x60_wdt_service(void)
|
||||
{
|
||||
/* Write 01 followed by 10 to CTL2 */
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x01));
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL2(0x02));
|
||||
}
|
||||
|
||||
static void mv64x60_wdt_handler_disable(void)
|
||||
{
|
||||
if (test_and_clear_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) {
|
||||
/* Write 01 followed by 10 to CTL1 */
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01));
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02));
|
||||
printk(KERN_NOTICE "mv64x60_wdt: watchdog deactivated\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void mv64x60_wdt_handler_enable(void)
|
||||
{
|
||||
if (!test_and_set_bit(MV64x60_WDOG_FLAG_ENABLED, &wdt_flags)) {
|
||||
/* Write 01 followed by 10 to CTL1 */
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x01));
|
||||
mv64x60_wdt_reg_write(MV64x60_WDC_CTL1(0x02));
|
||||
printk(KERN_NOTICE "mv64x60_wdt: watchdog activated\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int mv64x60_wdt_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags))
|
||||
return -EBUSY;
|
||||
|
||||
mv64x60_wdt_service();
|
||||
mv64x60_wdt_handler_enable();
|
||||
|
||||
nonseekable_open(inode, file);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mv64x60_wdt_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
mv64x60_wdt_service();
|
||||
|
||||
#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
|
||||
mv64x60_wdt_handler_disable();
|
||||
#endif
|
||||
|
||||
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)
|
||||
mv64x60_wdt_service();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int mv64x60_wdt_ioctl(struct inode *inode, struct file *file,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
int timeout;
|
||||
void __user *argp = (void __user *)arg;
|
||||
static struct watchdog_info info = {
|
||||
.options = 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:
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case WDIOC_KEEPALIVE:
|
||||
mv64x60_wdt_service();
|
||||
wdt_status |= WDIOF_KEEPALIVEPING;
|
||||
break;
|
||||
|
||||
case WDIOC_SETTIMEOUT:
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
case WDIOC_GETTIMEOUT:
|
||||
timeout = mv64x60_wdt_timeout * HZ;
|
||||
if (put_user(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;
|
||||
int bus_clk = 133;
|
||||
|
||||
mv64x60_wdt_timeout = 10;
|
||||
if (pdata) {
|
||||
mv64x60_wdt_timeout = pdata->timeout;
|
||||
bus_clk = pdata->bus_clk;
|
||||
}
|
||||
|
||||
mv64x60_regs = mv64x60_get_bridge_vbase();
|
||||
|
||||
writel((mv64x60_wdt_timeout * (bus_clk * 1000000)) >> 8,
|
||||
mv64x60_regs + MV64x60_WDT_WDC);
|
||||
|
||||
return misc_register(&mv64x60_wdt_miscdev);
|
||||
}
|
||||
|
||||
static int __devexit mv64x60_wdt_remove(struct platform_device *dev)
|
||||
{
|
||||
misc_deregister(&mv64x60_wdt_miscdev);
|
||||
|
||||
mv64x60_wdt_service();
|
||||
mv64x60_wdt_handler_disable();
|
||||
|
||||
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 struct platform_device *mv64x60_wdt_dev;
|
||||
|
||||
static int __init mv64x60_wdt_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printk(KERN_INFO "MV64x60 watchdog driver\n");
|
||||
|
||||
mv64x60_wdt_dev = platform_device_alloc(MV64x60_WDT_NAME, -1);
|
||||
if (!mv64x60_wdt_dev) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = platform_device_add(mv64x60_wdt_dev);
|
||||
if (ret) {
|
||||
platform_device_put(mv64x60_wdt_dev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&mv64x60_wdt_driver);
|
||||
if (ret) {
|
||||
platform_device_unregister(mv64x60_wdt_dev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit mv64x60_wdt_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&mv64x60_wdt_driver);
|
||||
platform_device_unregister(mv64x60_wdt_dev);
|
||||
}
|
||||
|
||||
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);
|
||||
390
drivers/char/watchdog/omap_wdt.c
Normal file
390
drivers/char/watchdog/omap_wdt.c
Normal file
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* 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/smp_lock.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 0;
|
||||
}
|
||||
|
||||
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 -ENOIOCTLCMD;
|
||||
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);
|
||||
64
drivers/char/watchdog/omap_wdt.h
Normal file
64
drivers/char/watchdog/omap_wdt.h
Normal 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 */
|
||||
635
drivers/char/watchdog/pc87413_wdt.c
Normal file
635
drivers/char/watchdog/pc87413_wdt.c
Normal 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) ")");
|
||||
|
||||
1015
drivers/char/watchdog/pcwd.c
Normal file
1015
drivers/char/watchdog/pcwd.c
Normal file
File diff suppressed because it is too large
Load Diff
832
drivers/char/watchdog/pcwd_pci.c
Normal file
832
drivers/char/watchdog/pcwd_pci.c
Normal 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);
|
||||
825
drivers/char/watchdog/pcwd_usb.c
Normal file
825
drivers/char/watchdog/pcwd_usb.c
Normal file
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
* 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 semaphore sem; /* 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 = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL);
|
||||
if (usb_pcwd == NULL) {
|
||||
printk(KERN_ERR PFX "Out of memory\n");
|
||||
goto error;
|
||||
}
|
||||
memset (usb_pcwd, 0x00, sizeof (*usb_pcwd));
|
||||
|
||||
usb_pcwd_device = usb_pcwd;
|
||||
|
||||
init_MUTEX (&usb_pcwd->sem);
|
||||
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);
|
||||
|
||||
down (&usb_pcwd->sem);
|
||||
|
||||
/* 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);
|
||||
|
||||
up (&usb_pcwd->sem);
|
||||
|
||||
/* 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);
|
||||
362
drivers/char/watchdog/pnx4008_wdt.c
Normal file
362
drivers/char/watchdog/pnx4008_wdt.c
Normal file
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
/* Can't seek (pwrite) on this device */
|
||||
if (ppos != &file->f_pos)
|
||||
return -ESPIPE;
|
||||
|
||||
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/char/watchdog/rm9k_wdt.c
Normal file
420
drivers/char/watchdog/rm9k_wdt.c
Normal 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);
|
||||
|
||||
550
drivers/char/watchdog/s3c2410_wdt.c
Normal file
550
drivers/char/watchdog/s3c2410_wdt.c
Normal file
@@ -0,0 +1,550 @@
|
||||
/* 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 S3C24XX_VA_WATCHDOG
|
||||
#define S3C24XX_VA_WATCHDOG (0)
|
||||
|
||||
#include <asm/arch/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 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) {
|
||||
printk(KERN_ERR PFX "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 {
|
||||
printk(KERN_CRIT PFX "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)
|
||||
{
|
||||
printk(KERN_INFO PFX "Watchdog timer expired!\n");
|
||||
|
||||
s3c2410wdt_keepalive();
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
/* device interface */
|
||||
|
||||
static int s3c2410wdt_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int started = 0;
|
||||
int ret;
|
||||
int size;
|
||||
|
||||
DBG("%s: probe=%p\n", __FUNCTION__, pdev);
|
||||
|
||||
/* get the memory region for the watchdog timer */
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (res == NULL) {
|
||||
printk(KERN_INFO PFX "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 PFX "failed to get memory region\n");
|
||||
ret = -ENOENT;
|
||||
goto err_req;
|
||||
}
|
||||
|
||||
wdt_base = ioremap(res->start, size);
|
||||
|
||||
if (wdt_base == 0) {
|
||||
printk(KERN_INFO PFX "failed to ioremap() region\n");
|
||||
ret = -EINVAL;
|
||||
goto err_req;
|
||||
}
|
||||
|
||||
DBG("probe: mapped wdt_base=%p\n", wdt_base);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
||||
if (res == NULL) {
|
||||
printk(KERN_INFO PFX "failed to get irq resource\n");
|
||||
ret = -ENOENT;
|
||||
goto err_map;
|
||||
}
|
||||
|
||||
ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, pdev);
|
||||
if (ret != 0) {
|
||||
printk(KERN_INFO PFX "failed to install irq (%d)\n", ret);
|
||||
goto err_map;
|
||||
}
|
||||
|
||||
wdt_clock = clk_get(&pdev->dev, "watchdog");
|
||||
if (IS_ERR(wdt_clock)) {
|
||||
printk(KERN_INFO PFX "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) {
|
||||
printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n",
|
||||
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
|
||||
} else {
|
||||
printk(KERN_INFO PFX "default timer value is out of range, cannot start\n");
|
||||
}
|
||||
}
|
||||
|
||||
ret = misc_register(&s3c2410wdt_miscdev);
|
||||
if (ret) {
|
||||
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n",
|
||||
WATCHDOG_MINOR, ret);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
if (tmr_atboot && started == 0) {
|
||||
printk(KERN_INFO PFX "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();
|
||||
}
|
||||
|
||||
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);
|
||||
191
drivers/char/watchdog/sa1100_wdt.c
Normal file
191
drivers/char/watchdog/sa1100_wdt.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
nonseekable_open(inode, 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 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
402
drivers/char/watchdog/sbc60xxwdt.c
Normal file
402
drivers/char/watchdog/sbc60xxwdt.c
Normal file
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
nonseekable_open(inode, 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 0;
|
||||
}
|
||||
|
||||
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/char/watchdog/sbc8360.c
Normal file
413
drivers/char/watchdog/sbc8360.c
Normal 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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_noenablereg;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (timeout < 0 || timeout > 63) {
|
||||
printk(KERN_ERR PFX "Invalid timeout index (must be 0-63).\n");
|
||||
res = -EINVAL;
|
||||
goto out_noreboot;
|
||||
}
|
||||
|
||||
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_noreboot:
|
||||
release_region(SBC8360_ENABLE, 1);
|
||||
release_region(SBC8360_BASETIME, 1);
|
||||
out_noenablereg:
|
||||
out_nobasetimereg:
|
||||
misc_deregister(&sbc8360_miscdev);
|
||||
out_nomisc:
|
||||
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 */
|
||||
223
drivers/char/watchdog/sbc_epx_c3.c
Normal file
223
drivers/char/watchdog/sbc_epx_c3.c
Normal 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);
|
||||
466
drivers/char/watchdog/sc1200wdt.c
Normal file
466
drivers/char/watchdog/sc1200wdt.c
Normal file
@@ -0,0 +1,466 @@
|
||||
/*
|
||||
* 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 <linux/pci.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, ®);
|
||||
/* 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)
|
||||
{
|
||||
nonseekable_open(inode, 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 0;
|
||||
}
|
||||
|
||||
|
||||
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 &= 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);
|
||||
437
drivers/char/watchdog/sc520_wdt.c
Normal file
437
drivers/char/watchdog/sc520_wdt.c
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
nonseekable_open(inode, 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 0;
|
||||
}
|
||||
|
||||
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);
|
||||
269
drivers/char/watchdog/scx200_wdt.c
Normal file
269
drivers/char/watchdog/scx200_wdt.c
Normal 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/pci.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/char/watchdog/shwdt.c
Normal file
485
drivers/char/watchdog/shwdt.c
Normal 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);
|
||||
627
drivers/char/watchdog/smsc37b787_wdt.c
Normal file
627
drivers/char/watchdog/smsc37b787_wdt.c
Normal 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/char/watchdog/softdog.c
Normal file
310
drivers/char/watchdog/softdog.c
Normal 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);
|
||||
371
drivers/char/watchdog/w83627hf_wdt.c
Normal file
371
drivers/char/watchdog/w83627hf_wdt.c
Normal file
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* w83627hf WDT driver
|
||||
*
|
||||
* (c) Copyright 2003 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 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 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 <=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) ")");
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
outb_p(0x87, WDT_EFER); /* Enter extended function mode */
|
||||
outb_p(0x87, WDT_EFER); /* Again according to manual */
|
||||
|
||||
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 */
|
||||
|
||||
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 > 63))
|
||||
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 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<=63, 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 WDT driver");
|
||||
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
|
||||
450
drivers/char/watchdog/w83697hf_wdt.c
Normal file
450
drivers/char/watchdog/w83697hf_wdt.c
Normal 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);
|
||||
415
drivers/char/watchdog/w83877f_wdt.c
Normal file
415
drivers/char/watchdog/w83877f_wdt.c
Normal 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);
|
||||
542
drivers/char/watchdog/w83977f_wdt.c
Normal file
542
drivers/char/watchdog/w83977f_wdt.c
Normal 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);
|
||||
325
drivers/char/watchdog/wafer5823wdt.c
Normal file
325
drivers/char/watchdog/wafer5823wdt.c
Normal 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/char/watchdog/wd501p.h
Normal file
51
drivers/char/watchdog/wd501p.h
Normal 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/char/watchdog/wdrtas.c
Normal file
695
drivers/char/watchdog/wdrtas.c
Normal 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/char/watchdog/wdt.c
Normal file
640
drivers/char/watchdog/wdt.c
Normal 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/char/watchdog/wdt285.c
Normal file
229
drivers/char/watchdog/wdt285.c
Normal 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/char/watchdog/wdt977.c
Normal file
519
drivers/char/watchdog/wdt977.c
Normal 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/char/watchdog/wdt_pci.c
Normal file
756
drivers/char/watchdog/wdt_pci.c
Normal 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);
|
||||
Reference in New Issue
Block a user