Delete all kernel chip drivers, documentation and references. We are

moving to a user-space-only package, all the drivers are maintained in
the Linux kernel tree itself.

I tried to update the documentation a bit, but there's still a lot to
go. In fact, a complete rewrite seems to be needed.


git-svn-id: http://lm-sensors.org/svn/lm-sensors/branches/lm-sensors-3.0.0@4397 7894878c-1315-0410-8ee3-d5d059ff63e0
This commit is contained in:
Jean Delvare committed 2007-05-09 13:00:30 +00:00
1 parent 448c4a70e9
commit 9232f4eb43
136 files changed
+1633 -55767

No files matched your search

+1
View File
@@ -3,6 +3,7 @@ lm_sensors CHANGES file
SVN HEAD
All bus modules and documentation: Delete
All chip modules and documentation: Delete
File doc/version-2: Delete, obsolete
File sensors.conf.eg: Add a dme1737 section
File lm_sensors.sysconfig: Delete, now generated by sensors-detect
+7 -182
View File
@@ -27,38 +27,6 @@
# I think so, but I have not tested it.
#SHELL := /usr/bin/bash
# The currently running kernel version. This is used right below to
# determine where the kernel sources can be found.
KERNELVERSION := $(shell uname -r)
# The location of linux itself. This is used to find the kernel headers
# and other things.
#LINUX := /usr/src/linux
LINUX := $(shell if [ -L "/lib/modules/$(KERNELVERSION)/build" ] ; \
then echo "/lib/modules/$(KERNELVERSION)/build" ; \
else echo "/usr/src/linux" ; fi)
LINUX_HEADERS := $(LINUX)/include
# If you have installed the i2c header at some other place (like
# /usr/local/include/linux), set that directory here. Please check this out
# if you get strange compilation errors; the default Linux i2c headers
# may be used mistakenly. Note: This should point to the directory
# *above* the linux/ subdirectory, so to /usr/local/include in the
# above example.
I2C_HEADERS := /usr/local/include
#I2C_HEADERS := $(LINUX_HEADERS)
ifneq ($(MAKECMDGOALS),user)
ifneq ($(MAKECMDGOALS),user_install)
ifneq ($(MAKECMDGOALS),user_uninstall)
SMP := $(shell if grep -q '^[[:space:]]*\#define[[:space:]]*CONFIG_SMP[[:space:]]*1' $(LINUX_HEADERS)/linux/autoconf.h ; \
then echo 1; else echo 0; fi)
MODVER := $(shell if grep -q '^[[:space:]]*\#define[[:space:]]*CONFIG_MODVERSIONS[[:space:]]*1' $(LINUX_HEADERS)/linux/autoconf.h ; \
then echo 1; else echo 0; fi)
endif
endif
endif
# Uncomment the second line if you are a developer. This will enable many
# additional warnings at compile-time
WARN := 0
@@ -80,34 +48,8 @@ PREFIX := /usr/local
# Your C compiler
CC := gcc
# This is the main modules directory into which the modules will be installed.
# The magic invocation will return something like this:
# /lib/modules/2.4.29
KERNELVERSION := $(shell $(CC) -I$(LINUX_HEADERS) -E etc/config.c | grep uts_release | cut -f 2 -d'"')
MODPREF := /lib/modules/$(KERNELVERSION)
# When building userspace for use with 2.4.x series kernels, we turn off
# sysfs support by default. You can override this (e.g. if you want
# to build binaries that work for both 2.4.x, and 2.6.x and above)
# by uncommenting the line after the next endif. Keep in mind, if and only
# if you do this: you will need to install the libsysfs libraries on your
# kernel 2.4.x systems also.
ifeq (,$(findstring /2.4., $(MODPREF)))
SYSFS_SUPPORT := 1
else
SYSFS_SUPPORT :=
endif
#SYSFS_SUPPORT := 1
# Prevent 2.6+ users from using improper targets, as this won't work.
ifeq (,$(findstring /2.4., $(MODPREF)))
ifeq (, $(MAKECMDGOALS))
$(error For 2.6 kernels and later, use "make user")
endif
ifeq (install, $(MAKECMDGOALS))
$(error For 2.6 kernels and later, use "make user_install")
endif
endif
# Build with sysfs support
SYSFS_SUPPORT := 1
# This is the directory where sensors.conf will be installed, if no other
# configuration file is found
@@ -170,19 +112,6 @@ MACHINE := $(shell uname -m)
# The subdirectories we need to build things in
SRCDIRS :=
ifneq ($(MAKECMDGOALS),user)
ifneq ($(MAKECMDGOALS),user_install)
ifneq ($(MAKECMDGOALS),user_uninstall)
ifneq ($(MAKECMDGOALS),package)
ifneq ($(MAKECMDGOALS),userpackage)
ifneq ($(MAKECMDGOALS),manhtml)
SRCDIRS += kernel kernel/chips
endif
endif
endif
endif
endif
endif
SRCDIRS += kernel/include
SRCDIRS += lib prog/detect prog/dump prog/eeprom prog/pwm \
prog/sensors prog/xeon ${PROG_EXTRA:%=prog/%} etc
@@ -204,11 +133,10 @@ SED := sed
# Determine the default compiler flags
# Set CFLAGS or CPPFLAGS above to add your own flags to all.
# ALLCPPFLAGS/ALLCFLAGS are common flags, plus any user-specified overrides from the environment or make command line.
# MODCPPFLAGS/MODCFLAGS is to create in-kernel object files (modules).
# PROGCPPFLAGS/PROGCFLAGS is to create non-kernel object files (which are linked into executables).
# PROGCPPFLAGS/PROGCFLAGS is to create regular object files (which are linked into executables).
# ARCPPFLAGS/ARCFLAGS are used to create archive object files (static libraries).
# LIBCPPFLAGS/LIBCFLAGS are for shared library objects.
ALL_CPPFLAGS := -I. -Ikernel/include -I$(I2C_HEADERS)
ALL_CPPFLAGS := -I. -Ikernel/include
ALL_CFLAGS := -Wall
ifeq ($(DEBUG),1)
@@ -226,47 +154,6 @@ endif
ALL_CPPFLAGS += $(CPPFLAGS)
ALL_CFLAGS += $(CFLAGS)
MODCPPFLAGS :=
MODCFLAGS := -fno-strict-aliasing
ifeq ($(MACHINE),alpha)
MODCFLAGS += -ffixed-8 -mno-fp-regs -mcpu=ev56
endif
ifeq ($(MACHINE),x86_64)
MODCFLAGS += -fno-common -fomit-frame-pointer -mno-red-zone \
-mcmodel=kernel -fno-reorder-blocks -finline-limit=2000 -fno-strength-reduce
endif
ifeq ($(MACHINE),mips)
MODCFLAGS += -mabi=32 -mips3 -Wa,-32 -Wa,-mips3 -Wa,--trap
endif
ifeq ($(MACHINE),sparc32)
MODCFLAGS += -m32 -pipe -mno-fpu -fcall-used-g5 -fcall-used-g7
endif
ifeq ($(MACHINE),sparc64)
MODCFLAGS += -m64 -pipe -mno-fpu -mcpu=ultrasparc -mcmodel=medlow \
-ffixed-g4 -fcall-used-g5 -fcall-used-g7 -Wno-sign-compare \
-Wa,--undeclared-regs
endif
ifeq ($(SMP),1)
MODCPPFLAGS += -D__SMP__
endif
ifeq ($(MODVER),1)
MODCPPFLAGS += -DMODVERSIONS -include $(LINUX_HEADERS)/linux/modversions.h
endif
# This magic is from the kernel Makefile.
# Extra cflags for kbuild 2.4. The default is to forbid includes by kernel code
# from user space headers.
kbuild_2_4_nostdinc := -nostdinc $(shell LC_ALL=C $(CC) -print-search-dirs | sed -ne 's/install: \(.*\)/-I \1include/gp')
MODCPPFLAGS += -D__KERNEL__ -DMODULE -DEXPORT_SYMTAB -fomit-frame-pointer $(ALL_CPPFLAGS) -I$(LINUX_HEADERS) $(kbuild_2_4_nostdinc)
MODCFLAGS += $(ALL_CFLAGS)
PROGCPPFLAGS := -DETCDIR="\"$(ETCDIR)\"" $(ALL_CPPFLAGS) -Wundef
PROGCFLAGS := $(ALL_CFLAGS)
ARCPPFLAGS := $(ALL_CPPFLAGS)
@@ -317,22 +204,6 @@ user_install::
@echo "*** to fit your needs again."
all :: user
install :: all user_install
ifeq ($(DESTDIR),)
-if [ -r $(MODPREF)/build/System.map -a -x /sbin/depmod ] ; then \
/sbin/depmod -a -F $(MODPREF)/build/System.map $(KERNELVERSION) ; \
fi
else
@echo "*** This is a \`staged' install using \"$(DESTDIR)\" as prefix."
@echo "***"
@echo "*** Once the modules have been moved to their final destination"
@echo "*** you must run the command \"/sbin/depmod -a\"."
@echo "***"
@echo "*** Alternatively, if you build a package (e.g. rpm), include the"
@echo "*** command \"/sbin/depmod -a\" in the post-(un)install procedure."
@echo "***"
@echo "*** The depmod command mentioned above may generate errors. We are"
@echo "*** aware of the problem and are working on a solution."
endif
clean::
$(RM) lm_sensors-* lex.backup
@@ -340,8 +211,6 @@ clean::
user_uninstall::
uninstall :: user_uninstall
@echo "*** Note:"
@echo "*** * Kernel modules were not uninstalled."
# This is tricky, but it works like a charm. It needs lots of utilities
# though: cut, find, gzip, ln, tail and tar.
@@ -359,23 +228,6 @@ package: version clean
$(RM) $$lmpackage.tar $$lmpackage
cat doc/developers/checklist
# doesn't work well yet... needs Makefile changes too
userpackage: version clean $(KERNELINCLUDEDIR)/sensors.h
lmversion=`tail -1 version.h|cut -f 2 -d \"`; \
lmpackage=lm_sensors-user-$$lmversion; \
ln -s . $$lmpackage; \
find $$lmpackage/ -type f | grep -v ^$$lmpackage/$$lmpackage$$ | \
grep -v ^$$lmpackage/$$lmpackage.tar$$ | \
grep -v ^$$lmpackage/doc/chips | \
grep -v ^$$lmpackage/kernel/chips | \
grep -v ^$$lmpackage/$$ | \
grep -v /CVS | \
grep -v /\\.# | \
tar rvf $$lmpackage.tar -T -; \
gzip -9 $$lmpackage.tar ;\
$(RM) $$lmpackage.tar $$lmpackage
cat doc/developers/checklist
version:
$(RM) version.h
echo '#define LM_DATE "'`date +'%Y%m%d'`\" > version.h
@@ -384,29 +236,12 @@ version:
help:
@echo 'Make targets are:'
@echo ' all (default): build modules and userspace programs'
@echo ' install: install modules and userspace programs'
@echo ' user: build userspace programs'
@echo ' user_install: install userspace programs'
@echo ' user_uninstall: remove userspace programs'
@echo ' all (default): build library and userspace programs'
@echo ' install: install library and userspace programs'
@echo ' uninstall: uninstall library and userspace programs'
@echo ' clean: cleanup'
@echo ' package: create a distribution package'
$(LINUX)/.config:
@echo
@echo "Error - missing file $(LINUX)/.config !! "
@echo " Verify kernel source is in $(LINUX) and then"
@echo " cd to $(LINUX) and run 'make config' !!"
@echo
@echo "Exception: if you're using a stock RedHat kernel..."
@echo " (1) Install the appropriate kernel-source RPM."
@echo " (2) Copy the appropriate config..."
@echo " from $(LINUX)/configs/<...>"
@echo " to $(LINUX)/.config"
@echo " (3) Do *NOT* 'make dep' or 'make config'."
@echo
@exit 1
# Generate html man pages to be copied to the lm_sensors website.
# This uses the man2html from here
# http://ftp.math.utah.edu/pub/sgml/
@@ -427,16 +262,6 @@ manhtml:
# We need to create dependency files. Tricky. The sed rule puts dir/file.d and
# dir/file.c in front of the dependency file rule.
# .o files are used for modules
# depend on the kernel config file!
%.o: %.c $(LINUX)/.config
$(CC) $(MODCPPFLAGS) $(MODCFLAGS) -c $< -o $@
%.d: %.c $(LINUX)/.config
$(CC) -M -MG $(MODCPPFLAGS) $(MODCFLAGS) $< | \
$(SED) -e 's@^\(.*\)\.o:@$*.d $*.o: Makefile '`dirname $*.d`/Module.mk' @' > $@
# .ro files are used for programs (as opposed to modules)
%.ro: %.c
+1 -53
View File
@@ -4,14 +4,11 @@ LM_SENSORS QUICK INSTALLATION INSTRUCTIONS
The instructions in this document will generally work if you have a standard
system, but not always!!
The first part of document is for 2.6 kernel series, second part is for
older 2.4 kernels.
Quickstart for Kernel 2.6
-------------------------
0) Just compile and install lm-sensors userspace tools. You can do that with:
'make user; make user_install'
'make ; make install'
1) Make sure your kernel has support for all I2C bus drivers and
'i2c device interface'. If you are using a distribution kernel, all is
already prepared, you don't need to recompile it.
@@ -26,52 +23,3 @@ http://www.lm-sensors.org/wiki/iwizard/1
We are aware that the documentation could be improved, please contact us if
you want to help us with that.
Quickstart for Kernel 2.4
-------------------------
NOTE: PLEASE SKIM THE INSTALL DOCUMENT, OR BETTER YET READ IT CAREFULLY!
Quickstart for i2c option 1 (modules) and lm_sensors option 1 (modules).
This is by far the EASIEST METHOD.
Other methods are described in INSTALL.
------------------------------------------
* Verify you are running a 2.4 kernel, 2.4.10 or later.
* Make sure you have the kernel tree corresponding to your current
kernel in /lib/modules/$(uname -r)/build.
Even if the source is there, it may not correspond to your running
kernel. If so, you will have to recompile your kernel.
* Verify that your i2c support in the kernel is configured as
modules or is disabled. If it is compiled-in, you obviously
cannot replace it with modules in the next step.
* Do `lsmod'. If there are any old i2c or sensors modules loaded,
you MUST remove them (as root) with `rmmod'.
* Download, build, and install the latest i2c package,
(i2c-2.9.0 or later). The i2c subsystem found in the Linux 2.4
kernel trees, even the latest ones, is outdated and won't work.
Quickstart for i2c: make; (as root) make install
* Go back to the lm_sensors directory. Do `make'.
The warnings about .d files at the start are harmless.
* Do (as root) `make install'.
* Verify that /usr/local/lib is listed in /etc/ld.so.conf;
if it isn't, add it, then run (as root) `ldconfig'.
* Run (as root) `prog/mkdev/mkdev.sh' to create the device files,
unless you already have them or are running devfs or udev.
* Run (as root) `prog/detect/sensors-detect' to detect your hardware,
put the lines it suggests into /etc/modules.conf, and run all
the commands it suggests (`modprobe X', `sensors -s').
* Run `sensors' to see your output.
* That's it! If you have problems, read our excellent FAQ
(doc/FAQ or doc/lm_sensors-FAQ.html).
+2 -2
View File
@@ -1,11 +1,11 @@
The directories within this package:
* doc
Documentation about the modules within this package, and all other things.
Documentation.
* etc
A sample configuration file for the 'sensors' program.
* kernel
The kernel modules: sensor chip drivers.
A few kernel-related header files.
* lib
The user-space sensors support library code.
* prog
-415
View File
@@ -1,415 +0,0 @@
Kernel driver `adm1021.o'
=========================
Status: Complete; tested for most devices except Xeon.
Supported chips:
* Analog Devices ADM1021
Prefix: 'adm1021'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Analog Devices website
* Analog Devices ADM1021A/ADM1023
Prefix: 'adm1023'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Analog Devices website
* Genesys Logic GL523SM
Prefix: 'gl523sm'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet:
* Intel Xeon Processor
Prefix - any other - may require 'force_adm1021' parameter -
see MODPARMS and xeontemp files for more information
Addresses scanned: none
Datasheet: Publicly available at Intel website
Status: Alpha, untested.
* Maxim MAX1617
Prefix: 'max1617'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Maxim website
* Maxim MAX1617A
Prefix: 'max1617a'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Maxim website
* National Semiconductor LM84
Prefix: 'lm84'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the National Semiconductor website
* Philips NE1617
Prefix: 'max1617' (probably detected as a max1617)
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Philips website
* Philips NE1617A
Prefix: 'max1617' (probably detected as a max1617)
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Philips website
* TI THMC10
Prefix: 'thmc10'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the TI website
* Onsemi MC1066
Prefix: 'mc1066'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the Onsemi website
Author: Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_adm1021: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1021' chip
* force_adm1023: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1023' chip
* force_max1617: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `max1617' chip
* force_max1617a: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `max1617a' chip
* force_thmc10: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `thmc10' chip
* force_lm84: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm84' chip
* force_gl523sm: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `gl523sm' chip
* force_mc1066: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `mc1066' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* read_only: int
Don't set any values, read only mode
Description
-----------
The chips supported by this driver are very similar. The Maxim MAX1617
is the oldest; it has the problem that it is not very well detectable.
The MAX1617A solves that. The ADM1021 is a straight clone of the MAX1617A.
Ditto for the THMC10.
From here on, we will refer to all these chips as ADM1021-clones.
ADM1021-clones implement two temperature sensors. One of them is internal,
and measures the temperature of the chip itself; the other is external
and is realised in the form of a transistor-like device. A special alarm
indicates whether the remote sensor is connected.
Each sensor has its own low and high limits. When they are crossed, the
corresponding alarm is set and remains on as long as the temperature stays
out of range. Temperatures are measured in degrees Celsius. Measurements
are possible between -65 and +127 degrees, with a resolution of one degree.
The ADM1021 reports a die code, which is a sort of revision code. This
can help us pinpoint problems; it is not very useful otherwise.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared!
This driver only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values. It is possible to make
ADM1021-clones do faster measurements, but there is really no good reason
for that.
Xeon support
------------
Some Xeon processors have real max1617, adm1021, or compatible chips
with in them, with two temperature sensors.
Other Xeons have chips with only one sensor.
If you have a Xeon, and the adm1021 module loads, and both
temperatures appear valid, then things are good.
If the adm1021 module doesn't load, you should try two things:
1) modprobe adm1021 force_adm1021=BUS,ADDRESS
ADDRESS can only be 0x18, 0x1a, 0x29, 0x2b, 0x4c, or 0x4e.
See if you have one or two valid temperatures
2) modprobe xeontemp
See if you have one valid temperature.
If you have dual Xeons you may have appear to have two separate
adm1021-compatible chips, or two single-temperature sensors,
at distinct addresses.
Pick the best module for your processor based on the results.
See the xeontemp documentation for further information.
Chip Features
-------------
Chip 'adm1021'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
die_code - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_ADM1021_TEMP temp1:3
temp_low SENSORS_ADM1021_TEMP_HYST temp1:2
temp_over SENSORS_ADM1021_TEMP_OVER temp1:1
remote_temp SENSORS_ADM1021_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_ADM1021_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_ADM1021_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_ADM1021_ALARMS alarms:1
die_code SENSORS_ADM1021_DIE_CODE die_code:1
Chip 'adm1023'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 3
remote_temp_low remote_temp remote_temp RW 3
remote_temp_over remote_temp remote_temp RW 3
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_ADM1021_TEMP temp1:3
temp_low SENSORS_ADM1021_TEMP_HYST temp1:2
temp_over SENSORS_ADM1021_TEMP_OVER temp1:1
remote_temp SENSORS_ADM1021_REMOTE_TEMP temp2:4
remote_temp_low SENSORS_ADM1021_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_ADM1021_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_ADM1021_ALARMS alarms:1
Chip 'max1617'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_MAX1617_TEMP temp1:3
temp_low SENSORS_MAX1617_TEMP_HYST temp1:2
temp_over SENSORS_MAX1617_TEMP_OVER temp1:1
remote_temp SENSORS_MAX1617_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_MAX1617_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_MAX1617_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_MAX1617_ALARMS alarms:1
Chip 'max1617a'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_MAX1617A_TEMP temp1:3
temp_low SENSORS_MAX1617A_TEMP_HYST temp1:2
temp_over SENSORS_MAX1617A_TEMP_OVER temp1:1
remote_temp SENSORS_MAX1617A_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_MAX1617A_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_MAX1617A_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_MAX1617A_ALARMS alarms:1
Chip 'thmc10'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
die_code - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_ADM1021_TEMP temp1:3
temp_low SENSORS_ADM1021_TEMP_HYST temp1:2
temp_over SENSORS_ADM1021_TEMP_OVER temp1:1
remote_temp SENSORS_ADM1021_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_ADM1021_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_ADM1021_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_ADM1021_ALARMS alarms:1
die_code SENSORS_ADM1021_DIE_CODE die_code:1
Chip 'lm84'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
die_code - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_ADM1021_TEMP temp1:3
temp_low SENSORS_ADM1021_TEMP_HYST temp1:2
temp_over SENSORS_ADM1021_TEMP_OVER temp1:1
remote_temp SENSORS_ADM1021_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_ADM1021_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_ADM1021_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_ADM1021_ALARMS alarms:1
die_code SENSORS_ADM1021_DIE_CODE die_code:1
Chip 'gl523'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
die_code - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_ADM1021_TEMP temp1:3
temp_low SENSORS_ADM1021_TEMP_HYST temp1:2
temp_over SENSORS_ADM1021_TEMP_OVER temp1:1
remote_temp SENSORS_ADM1021_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_ADM1021_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_ADM1021_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_ADM1021_ALARMS alarms:1
die_code SENSORS_ADM1021_DIE_CODE die_code:1
Chip 'mc1066'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_low temp temp RW 0
temp_over temp temp RW 0
remote_temp - - R- 0
remote_temp_low remote_temp remote_temp RW 0
remote_temp_over remote_temp remote_temp RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_MAX1617_TEMP temp1:3
temp_low SENSORS_MAX1617_TEMP_HYST temp1:2
temp_over SENSORS_MAX1617_TEMP_OVER temp1:1
remote_temp SENSORS_MAX1617_REMOTE_TEMP temp2:3
remote_temp_low SENSORS_MAX1617_REMOTE_TEMP_HYST temp2:2
remote_temp_over SENSORS_MAX1617_REMOTE_TEMP_OVER temp2:1
alarms SENSORS_MAX1617_ALARMS alarms:1
Old Documentation (may be outdated)
-----------------------------------
This file documents the adm1021 directories for the Analog Devices ADM1021
and MAX1617 temperature sensor chip. Note that this last chip should be
fully compatible; it is not yet well-tested, though.
There will be one directory created for each detected ADM1021 chip. As
ADM1021 chips can only be on a SMBus, they will be called adm1021-i2c-?-??, with
the first question mark being the number of the i2c bus (see /proc/bus/i2c
for a list of them, if i2c-proc is loaded), and the SMBus (I2C) address
of the chip at the end. An ADM1021 is uniquely characterised with these
two numbers. Likewise, for each MAX1617 chip, a directory max1617-i2c-?-??
will be created. /proc/sys/dev/sensors/chips contains the SYSCTL values for
all chip directories.
Within each ADM1021 or MAX1617 directory, you can find the following files:
* die_code (ADM1021_SYSCTL_DIE_CODE)
Silicon die (mask) revision code. This driver was written using a sample
device of die code 3. Normally this kind of information isn't reported
in its own proc file, but for a device this simple I didn't think it
would hurt.
* status (ADM1021_SYSCTL_STATUS)
This is an intergral number composed of 8-bits, which indicate the
different alarms and conversion status's of the chip. Here is what
each bit indicates:
7 BUSY 1 When ADC Converting.
6 LHIGH# 1 When Local High Temp Limit Tripped.
5 LLOW# 1 When Local Low Temp Limit Tripped.
4 RHIGH# 1 When Remote High Temp Limit Tripped.
3 RLOW# 1 When Remote Low Temp Limit Tripped.
2 OPEN# 1 When Remote Sensor Open-Circuit. (i.e., no connected remote
sensor)
1-0 Reserved.
Note: # - These flags stay high until the status register is read or they
are reset by POR.
* temp1 (ADM1021_SYSCTL_TEMP)
A list of three numbers. The first number is the Overtemperature Shutdown
value, the second number is Low limit value, and the third number is the
current temperature. The first two values can be modified. Each value can
be set and read to full-degree accuracy. All values are in degrees Celsius.
An alarm is issued (usually to a connected ADM9240) when the temperature
gets higher then the Overtemperature Shutdown value; it stays on until
the temperature falls back below the Overtemperature Shutdown value.
NOTE: This is different than the way most remote temperature sensors work
(e.g. the LM75), they usually use the lower limit to clear the alarm set
from initially going above the Overtemperature limit. Instead, the
ADM1021 acts much the way that voltage limits work on, say, the LM78 or
ADM9240. A disadvantage to this is that the alarm doesn't have a
hysteresis (like a shmidt-trigger or thermostat) type of function for
doing things like controlling over-heat fans and such. Instead, the
lower limit can be used to signal an alarm if (goodness forbid!) the
computer gets too cold.
If accessed through sysctl, this is a list of longs, each being the
temperature times 10.
* temp2 (ADM1021_SYSCTL_REMOTE_TEMP)
Works exactly the same way as 'temp1', but uses an external temperature
probe for the sensing. The temperature probe is of a silicon diode type
and not of a thermal-resistive type. A probe can easily be made from a
common transister (see the ADM1021 for qualification specs) by shorting
the base to the collector. From the docs: "Transistors such as 2N3904,
2N3906 or equivalents in SOT-23 package are suitable to use."
The data for each chip is updated each 1.5 seconds, but only if it is
actually read. And, any alarms triggered between reads which always be
present in the next read just after the alarm was triggered (regardless if
the alarm situation has been resolved or not).
This driver was written and is maintained by Philip Edelbrock
<phil@netroedge.com>. Please send corrections/comments to him. For
further technical information on this chip, please get the Acrobat PDF
documentation available from www.analog.com. We'd also like to thank
Analog Devices for helping us with correspondance and free samples to make
this driver (and other Analog Devices drivers) possible.
-12
View File
@@ -1,12 +0,0 @@
Kernel driver `adm1024.o'
=========================
Status: Complete; Alpha.
Supported chips:
* Analog Devices ADM1024
Prefix: 'adm1024'
Addresses scanned: I2C 0x2c - 0x2e
Datasheet: Publicly available at the Analog Devices website
Author: Ken Bowley <ken@opnix.com>
-135
View File
@@ -1,135 +0,0 @@
Kernel driver `adm1025.o'
=========================
Status: Complete, known to work but not heavily tested
Supported chips:
* Analog Devices ADM1025
Prefix: 'adm1025'
Addresses scanned: I2C 0x2c - 0x2e
Datasheet: Publicly available at the Analog Devices website
* Philips NE1619
Prefix: 'ne1619'
Addresses scanned: I2C 0x2c - 0x2d
Datasheet: Publicly available at the Philips website
The NE1619 presents some differences with the original ADM1025:
* Only two possible addresses (0x2c - 0x2d).
* No temperature offset register, but we don't use it anyway.
* No INT mode for pin 16. We don't play with it anyway.
Author: Chen-Yuan Wu <gwu@esoft.com> and Jean Delvare <khali@linux-fr.org>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_adm1025: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1025' chip
* force_ne1619: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `ne1619' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
(This is from Analog Devices.) The ADM1025 is a complete system hardware
monitor for microprocessor-based systems, providing measurement and limit
comparison of various system parameters. Five voltage measurement inputs
are provided, for monitoring +2.5V, +3.3V, +5V and +12V power supplies and
the processor core voltage. The ADM1025 can monitor a sixth power-supply
voltage by measuring its own VCC. One input (two pins) is dedicated to a
remote temperature-sensing diode and an on-chip temperature sensor allows
ambient temperature to be monitored.
One specificity of this chip is that the pin 11 can be hardwired in two
different manners. It can act as the +12V power-supply voltage analog
input, or as the a fifth digital entry for the VID reading (bit 4). It's
kind of strange since both are useful, and the reason for designing the
chip that way is obscure at least to me. The bit 5 of the configuration
register can be used to define how the chip is hardwired. Please note
that it is not a choice you have to make as the user. The choice was
already made by your motherboard's maker. If the configuration bit isn't
set properly, you'll have a wrong +12V reading or a wrong VID reading.
The way the driver handles that is to preserve this bit through the
initialization process, assuming that the BIOS set it up properly
beforehand. If it turns out not to be true in some cases, we'll provide
a module parameter to force modes.
Chip Features
-------------
Chips 'adm1025' and 'ne1619'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
in0 - - R- 2
in1 - - R- 2
in2 - - R- 2
in3 - - R- 2
in4 - - R- 2
in5 - - R- 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
temp1 - - R- 1
temp1_low temp1 temp1 RW 1
temp1_high temp1 temp1 RW 1
temp2 - - R- 1
temp2_low temp2 temp2 RW 1
temp2_high temp2 temp2 RW 1
vid - - R- 3
vrm - - RW 1
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
in0 SENSORS_ADM1025_IN0 in0:3
in1 SENSORS_ADM1025_IN1 in1:3
in2 SENSORS_ADM1025_IN2 in2:3
in3 SENSORS_ADM1025_IN3 in3:3
in4 SENSORS_ADM1025_IN4 in4:3
in5 SENSORS_ADM1025_IN5 in5:3
in0_min SENSORS_ADM1025_IN0_MIN in0:1
in1_min SENSORS_ADM1025_IN1_MIN in1:1
in2_min SENSORS_ADM1025_IN2_MIN in2:1
in3_min SENSORS_ADM1025_IN3_MIN in3:1
in4_min SENSORS_ADM1025_IN4_MIN in4:1
in5_min SENSORS_ADM1025_IN5_MIN in5:1
in0_max SENSORS_ADM1025_IN0_MAX in0:2
in1_max SENSORS_ADM1025_IN1_MAX in1:2
in2_max SENSORS_ADM1025_IN2_MAX in2:2
in3_max SENSORS_ADM1025_IN3_MAX in3:2
in4_max SENSORS_ADM1025_IN4_MAX in4:2
in5_max SENSORS_ADM1025_IN5_MAX in5:2
temp1 SENSORS_ADM1025_TEMP1 temp1:3
temp1_low SENSORS_ADM1025_TEMP1_LOW temp1:2
temp1_high SENSORS_ADM1025_TEMP1_HIGH temp1:1
temp2 SENSORS_ADM1025_TEMP2 temp2:3
temp2_low SENSORS_ADM1025_TEMP2_LOW temp2:2
temp2_high SENSORS_ADM1025_TEMP2_HIGH temp2:1
vid SENSORS_ADM1025_VID vid:1
vrm SENSORS_ADM1025_VRM vrm:1
alarms SENSORS_ADM1025_ALARMS alarms:1
-217
View File
@@ -1,217 +0,0 @@
Kernel driver `adm1026.o'
======================
Status: In development
Supported chips:
* Analog Devices ADM1026
Prefix: 'adm1026'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Author: Philip Pokorny <ppokorny@penguincomputing.com>
for Penguin Computing
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_adm1026: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
an `adm1026' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
The following only apply to the first ADM1026 detected. These are a
workaround for if your BIOS hasn't setup your chip.
* gpio_input: int array (min = 1, max = 17)
List of GPIO pins (0-16) to program as inputs
* gpio_output: int array (min = 1, max = 17)
List of GPIO pins (0-16) to program as outputs
* gpio_inverted: int array (min = 1, max = 17)
List of GPIO pins (0-16) to program as inverted
* gpio_normal: int array (min = 1, max = 17)
List of GPIO pins (0-16) to program as normal/non-inverted
* gpio_fan: int array (min = 1, max = 8)
List of GPIO pins (0-7) to program as fan tachs
Description
-----------
This driver implements support for the Analog Devices ADM1026. Analog
Devices calls it a "complete thermal system management controller."
The ADM1026 implements three (3) temperature sensors, 17 voltage
sensors, 16 general purpose digital I/O lines, eight (8) fan speed
sensors (8-bit), an analog output and a PWM output along with limit,
alarm and mask bits for all of the above. There is even 8k bytes of
EEPROM memory on chip.
Temperatures are measured in degrees Celsius. There are two external
sensor inputs and one internal sensor. Each sensor has a high and low
limit. If the limit is exceeded, an interrupt (#SMBALERT) can be
generated. The interrupts can be masked. In addition, there are
over-temp limits for each sensor. If this limit is exceeded, the
#THERM output will be asserted. The current temperature and limits
have a resolution of 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute) but
measured in counts of a 22.5kHz internal clock. Each fan has a high
limit which corresponds to a minimum fan speed. If the limit is
exceeded, an interrupt can be generated. Each fan can be programmed
to divide the reference clock by 1, 2, 4 or 8. Not all RPM values can
accurately be represented, so some rounding is done. With a divider of
8, the slowest measureable speed of a one pulse per rev fan is 1323
RPM. Two pulse per rev fans can be measured as slow as 661 RPM.
Voltage sensors (in0 to in16) report their values in volts. An alarm
is triggered if the voltage has crossed a programmable minimum or
maximum limit. Note that minimum in this case always means 'closest
to zero'; this is important for negative voltage measurements.
Several inputs have integrated attenuators so they can measure higher
voltages directly. 3.3V, 5V, 12V, -12V and battery voltage all have
dedicated inputs. There are several inputs scaled to 0-3V full-scale
range for SCSI terminator power. The remaining inputs are not scaled
and have a 0-2.5V full-scale range. A 2.5V or 1.82V reference voltage
is provided for negative voltage measurements.
If an alarm triggers, it will remain triggered until the hardware
register is read at least once. This means that the cause for the
alarm may already have disappeared! Note that in the current
implementation, all hardware registers are read whenever any data is
read (unless it is less than 2.0 seconds since the last update). This
means that you can easily miss once-only alarms.
The ADM1026 measures continuously. Analog inputs are measured about 4
times a second. Fan speed measurement time depends on fan speed and
divisor. It can take as long as 1.5 seconds to measure all fan
speeds.
The ADM1026 has the ability to automaticaly control fan speed based on
the temperature sensor inputs. Both the PWM output and the DAC output
can be used to control fan speed. Usually only one of these two
outputs will be used. Write the minimum PWM or DAC value to the
appropriate control register. Then set the low temperature limit in
the tmin values for each temperature sensor. The range of control is
fixed at 20 degC, and the largest difference between current and tmin
of the temperature sensors sets the control output. See the datasheet
for several example circuits for controlling fan speed with the PWM
and DAC outputs. The fan speed sensors do not have PWM compensation,
so it is probably best to control the fan voltage from the power
lead rather than on the ground lead.
The datasheet shows an example application with VID signals attached
to GPIO lines. Unfortunately, the chip may not be connected to the
VID lines in this way. The driver assumes that the chips *is*
connected this way to get a vid voltage. If the VID value is wrong or
not wired as shown in the example, then you will need to write the
correct VID value to the driver either based on the GPIO values or
setting it directly. The value written to VID should be the *raw* VID
value (0 to 0x3f) or -1 (to restore the default value). When read,
the vid interface returns the scaled voltage according to the VRM spec
selected.
Example sensors.conf
--------------------
Here is an example sensors.conf configuration section for the ADM1026.
--------- cut here ---------
chip "adm1026-*"
# Voltage inputs
label in0 "V2.25_0" # Scaled for "SCSI terminator"?
label in1 "V2.25_1" # supply voltage?
label in2 "V2.25_2"
label in3 "V2.25_3"
label in4 "V2.25_4"
label in5 "V2.25_5"
label in6 "V1.875_0" # Unscaled inputs
label in7 "V1.875_1"
# If temp3 is enabled, in8 and in9 are disabled.
# label in8 "V1.875_2"
# label in9 "V1.875_3"
ignore in8
ignore in9
# Dedicated voltage inputs
label in10 "Vbat"
label in11 "V3.3STBY"
label in12 "V3.3MAIN"
label in13 "V5"
label in14 "Vccp"
label in15 "V12"
label in16 "V-12"
# Temperature inputs
label temp1 "Board"
label temp2 "CPU0"
label temp3 "CPU1"
# Fan inputs
label fan0 "CPU_Fan"
label fan1 "Fan1"
label fan2 "Fan2"
label fan3 "Fan3"
label fan4 "Fan4"
label fan5 "Fan5"
label fan6 "Fan6"
label fan7 "Fan7"
# PWM Outputs
label pwm "PWM"
# Voltage scaling is done on-chip. No 'compute' directive
# should be necessary. If in0-in9 have external scaling,
# set it here.
# compute in0 @ * 2.5, @ / 2.5
# Adjust fans speeds for actual pulses per rev
# compute fan0 @ / 2, @ * 2 # 2 pulse per rev
# compute fan1 @ / 3, @ * 3 # 3 pulse per rev
# compute fan2 @ / 4, @ * 4 # 4 pulse per rev
# compute fan3 @ / 8, @ * 8 # 8 pulse per rev
# Set VRM version
set vrm 9.1
# set vid 1.580
# Set voltage limits
set in10_min 3.0 * 0.95
set in10_max 3.0 * 1.05
set in11_min 3.3 * 0.95
set in11_max 3.3 * 1.05
set in12_min 3.3 * 0.95
set in12_max 3.3 * 1.05
set in13_min 5.0 * 0.95
set in13_max 5.0 * 1.05
# Uncomment if VID is wired or set above
# set in14_min vid * 0.95
# set in14_max vid * 1.05
set in15_min 12 * 0.95
set in15_max 12 * 1.05
set in16_min -12 * 0.95
set in16_max -12 * 1.05
# Set Fan limits
set fan0_min 7000
set fan1_min 7000
set fan2_min 7000
set fan3_min 7000
set fan4_min 3000
set fan5_min 3000
set fan6_min 3000
set fan7_min 3000
-109
View File
@@ -1,109 +0,0 @@
Kernel driver `adm1031.o'
=========================
Status: Driver supports basic temp and fan readings, min/max/crit settings
alarms. This driver does not support auto fan control.
Supported chips:
* Analog Devices ADM1030
Prefix: 'adm1030'
Addresses scanned: I2C 0x2c to 0x2e
Datasheet: Publicly available at the Analog Devices website
http://products.analog.com/products/info.asp?product=ADM1030
* Analog Devices ADM1031
Prefix: 'adm1031'
Addresses scanned: I2C 0x2c to 0x2e
Datasheet: Publicly available at the Analog Devices website
http://products.analog.com/products/info.asp?product=ADM1031
Author: Alexandre d'Alton <alex@alexdalton.org>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_adm1030: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1030' chip
* force_adm1031: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1031' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
ADM1030 and ADM1031 are digital temperature sensors and fan controllers.
They sense their own temperature as well as the temperature of up to one
(ADM1030) or two (ADM1031) external diodes.
All temperature values are given in degrees Celsius. Resolution
is 0.5 degree for the local temperature, 0.125 degree for the remote
temperatures.
Each temperature channel has its own high and low limits, plus a critical
limit.
The ADM1030 monitors a single fan speed, while the ADM1031 monitors up to
two. Each fan channel has its own low speed limit.
Chip Features
-------------
Chip 'adm1030'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 3
temp1_high temp1 temp1 RW 3
temp1_low temp1 temp1 RW 3
temp1_crit temp1 temp1 RW 3
temp2 - - R- 3
temp2_high temp2 temp2 RW 3
temp2_low temp2 temp2 RW 3
temp2_crit temp2 temp2 RW 3
fan1_input - - R- 0
fan1_min fan1 fan1 RW 0
fan1_div fan1 - RW 0
alarms - - R- 0
Chip 'adm1031'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 3
temp1_high temp1 temp1 RW 3
temp1_low temp1 temp1 RW 3
temp1_crit temp1 temp1 RW 3
temp2 - - R- 3
temp2_high temp2 temp2 RW 3
temp2_low temp2 temp2 RW 3
temp2_crit temp2 temp2 RW 3
temp3 - - R- 3
temp3_high temp3 temp3 RW 3
temp3_low temp3 temp3 RW 3
temp3_crit temp3 temp3 RW 3
fan1_input - - R- 0
fan1_min fan1 fan1 RW 0
fan1_div fan1 - RW 0
fan2_input - - R- 0
fan2_min fan2 fan2 RW 0
fan2_div fan2 - RW 0
alarms - - R- 0
-424
View File
@@ -1,424 +0,0 @@
Kernel driver `adm9240.o'
=========================
Status: Complete and tested
Supported chips:
* Analog Devices ADM9240
Prefix: 'adm9240'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: Publicly available at the Analog Devices website
* Dallas Semiconductor DS1780
Prefix: 'ds1780'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: Publicly available at the Dallas Semiconductor website
* National Semiconductor LM81
Prefix: 'lm81'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: Publicly available at the National Semiconductor website
Author: Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_adm9240: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm9240' chip
* force_ds1780: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `ds1780' chip
* force_lm81: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm81' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the Analog Devices ADM9240,
Dallas Semiconductor DS1780, and National Semiconductor LM81 chips.
These chips appear to be completely
identical, except for the manufacturer IDs encoded in two registers, and
the speed with which they update their sensor readings.
The chips implement six voltage sensors, a temperature sensor,
two fan rotation speed sensors, VID lines and some miscellaneous stuff.
Temperatures are measured in degrees Celsius. An alarm is triggered whenever
the temperature is above the hot limit, and will continue to be generated
until the temperature has dropped beneath the hysteresis limit. Measurements
are guaranteed between -40 and +125 degrees. The current temperature
has a resolution of 0.5 degrees, the limits of 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements.
IMPORTANT: The current voltage measurement system is very probably broken.
Please report back to us if you have a ADM9240 or DS1780!
The VID lines encode the core voltage value: the voltage level your processor
should work with. This is hardcoded by the mainboard and/or processor itself.
It is a value in volts. When it is unconnected, you will often find the
value 3.50 V here.
The analog output can be programmed separatedly. It can take values
between 0 and 255. What it actually does is mainboard-dependent; it
might influence the fan speed. Please report to us whether this is
the case with your ADM9240.
In addition to the alarms described above, the CHAS alarm triggers
if your computer case is open.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
Thes ADM9240 only updates its values every 0.5 seconds; the DS1780 one
every 2 seconds; and the LM81 every 0.4 seconds.
Reading them more often will do no harm, but will return
'old' values. (NB: theoretically, this could be done much faster for the
ADM9240, but there is no real reason to do so, and this way the SMBus use
is kept within reasonable limits)
Chip Features
-------------
Chip `adm9240'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
2.5V NONE NONE R 2
Vccp1 NONE NONE R 2
3.3V NONE NONE R 2
5V NONE NONE R 2
12V NONE NONE R 2
Vccp2 NONE NONE R 2
2.5V_min 2.5V 2.5V RW 2
Vccp1_min Vccp1 Vccp1 RW 2
3.3V_min 3.3V 3.3V RW 2
5V_min 5V 5V RW 2
12V_min 12V 12V RW 2
Vccp2_min Vccp2 Vccp2 RW 2
2.5V_max 2.5V 2.5V RW 2
Vccp1_max Vccp1 Vccp1 RW 2
3.3V_max 3.3V 3.3V RW 2
5V_max 5V 5V RW 2
12V_max 12V 12V RW 2
Vccp2_max Vccp2 Vccp2 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
analog_out NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
2.5V SENSORS_ADM9240_IN0 2.5V:3
Vccp1 SENSORS_ADM9240_IN1 Vccp1:3
3.3V SENSORS_ADM9240_IN2 3.3V:3
5V SENSORS_ADM9240_IN3 5V:3
12V SENSORS_ADM9240_IN4 12V:3
Vccp2 SENSORS_ADM9240_IN5 Vccp2:3
2.5V_min SENSORS_ADM9240_IN0_MIN 2.5V:1
Vccp1_min SENSORS_ADM9240_IN1_MIN Vccp1:1
3.3V_min SENSORS_ADM9240_IN2_MIN 3.3V:1
5V_min SENSORS_ADM9240_IN3_MIN 5V:1
12V_min SENSORS_ADM9240_IN4_MIN 12V:1
Vccp2_min SENSORS_ADM9240_IN5_MIN Vccp2:1
2.5V_max SENSORS_ADM9240_IN0_MAX 2.5V:2
Vccp1_max SENSORS_ADM9240_IN1_MAX Vccp1:2
3.3V_max SENSORS_ADM9240_IN2_MAX 3.3V:2
5V_max SENSORS_ADM9240_IN3_MAX 5V:2
12V_max SENSORS_ADM9240_IN4_MAX 12V:2
Vccp2_max SENSORS_ADM9240_IN5_MAX Vccp2:2
fan1 SENSORS_ADM9240_FAN1 fan1:2
fan2 SENSORS_ADM9240_FAN2 fan2:2
fan1_min SENSORS_ADM9240_FAN1_MIN fan1:1
fan2_min SENSORS_ADM9240_FAN2_MIN fan2:1
temp SENSORS_ADM9240_TEMP temp:3
temp_hyst SENSORS_ADM9240_TEMP_HYST temp:2
temp_over SENSORS_ADM9240_TEMP_OVER temp:1
vid SENSORS_ADM9240_VID vid:1
fan1_div SENSORS_ADM9240_FAN1_DIV fan_div:1
fan2_div SENSORS_ADM9240_FAN2_DIV fan_div:2
alarms SENSORS_ADM9240_ALARMS alarms:1
analog_out SENSORS_ADM9240_ANALOG_OUT analog_out:1
Chip `ds1780'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
2.5V NONE NONE R 2
Vccp1 NONE NONE R 2
3.3V NONE NONE R 2
5V NONE NONE R 2
12V NONE NONE R 2
Vccp2 NONE NONE R 2
2.5V_min 2.5V 2.5V RW 2
Vccp1_min Vccp1 Vccp1 RW 2
3.3V_min 3.3V 3.3V RW 2
5V_min 5V 5V RW 2
12V_min 12V 12V RW 2
Vccp2_min Vccp2 Vccp2 RW 2
2.5V_max 2.5V 2.5V RW 2
Vccp1_max Vccp1 Vccp1 RW 2
3.3V_max 3.3V 3.3V RW 2
5V_max 5V 5V RW 2
12V_max 12V 12V RW 2
Vccp2_max Vccp2 Vccp2 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
analog_out NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
2.5V SENSORS_DS1780_IN0 2.5V:3
Vccp1 SENSORS_DS1780_IN1 Vccp1:3
3.3V SENSORS_DS1780_IN2 3.3V:3
5V SENSORS_DS1780_IN3 5V:3
12V SENSORS_DS1780_IN4 12V:3
Vccp2 SENSORS_DS1780_IN5 Vccp2:3
2.5V_min SENSORS_DS1780_IN0_MIN 2.5V:1
Vccp1_min SENSORS_DS1780_IN1_MIN Vccp1:1
3.3V_min SENSORS_DS1780_IN2_MIN 3.3V:1
5V_min SENSORS_DS1780_IN3_MIN 5V:1
12V_min SENSORS_DS1780_IN4_MIN 12V:1
Vccp2_min SENSORS_DS1780_IN5_MIN Vccp2:1
2.5V_max SENSORS_DS1780_IN0_MAX 2.5V:2
Vccp1_max SENSORS_DS1780_IN1_MAX Vccp1:2
3.3V_max SENSORS_DS1780_IN2_MAX 3.3V:2
5V_max SENSORS_DS1780_IN3_MAX 5V:2
12V_max SENSORS_DS1780_IN4_MAX 12V:2
Vccp2_max SENSORS_DS1780_IN5_MAX Vccp2:2
fan1 SENSORS_DS1780_FAN1 fan1:2
fan2 SENSORS_DS1780_FAN2 fan2:2
fan1_min SENSORS_DS1780_FAN1_MIN fan1:1
fan2_min SENSORS_DS1780_FAN2_MIN fan2:1
temp SENSORS_DS1780_TEMP temp:3
temp_hyst SENSORS_DS1780_TEMP_HYST temp:2
temp_over SENSORS_DS1780_TEMP_OVER temp:1
vid SENSORS_DS1780_VID vid:1
fan1_div SENSORS_DS1780_FAN1_DIV fan_div:1
fan2_div SENSORS_DS1780_FAN2_DIV fan_div:2
alarms SENSORS_DS1780_ALARMS alarms:1
analog_out SENSORS_DS1780_ANALOG_OUT analog_out:1
Chip `lm81'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
2.5V NONE NONE R 2
Vccp1 NONE NONE R 2
3.3V NONE NONE R 2
5V NONE NONE R 2
12V NONE NONE R 2
Vccp2 NONE NONE R 2
2.5V_min 2.5V 2.5V RW 2
Vccp1_min Vccp1 Vccp1 RW 2
3.3V_min 3.3V 3.3V RW 2
5V_min 5V 5V RW 2
12V_min 12V 12V RW 2
Vccp2_min Vccp2 Vccp2 RW 2
2.5V_max 2.5V 2.5V RW 2
Vccp1_max Vccp1 Vccp1 RW 2
3.3V_max 3.3V 3.3V RW 2
5V_max 5V 5V RW 2
12V_max 12V 12V RW 2
Vccp2_max Vccp2 Vccp2 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
analog_out NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
2.5V SENSORS_ADM9240_IN0 2.5V:3
Vccp1 SENSORS_ADM9240_IN1 Vccp1:3
3.3V SENSORS_ADM9240_IN2 3.3V:3
5V SENSORS_ADM9240_IN3 5V:3
12V SENSORS_ADM9240_IN4 12V:3
Vccp2 SENSORS_ADM9240_IN5 Vccp2:3
2.5V_min SENSORS_ADM9240_IN0_MIN 2.5V:1
Vccp1_min SENSORS_ADM9240_IN1_MIN Vccp1:1
3.3V_min SENSORS_ADM9240_IN2_MIN 3.3V:1
5V_min SENSORS_ADM9240_IN3_MIN 5V:1
12V_min SENSORS_ADM9240_IN4_MIN 12V:1
Vccp2_min SENSORS_ADM9240_IN5_MIN Vccp2:1
2.5V_max SENSORS_ADM9240_IN0_MAX 2.5V:2
Vccp1_max SENSORS_ADM9240_IN1_MAX Vccp1:2
3.3V_max SENSORS_ADM9240_IN2_MAX 3.3V:2
5V_max SENSORS_ADM9240_IN3_MAX 5V:2
12V_max SENSORS_ADM9240_IN4_MAX 12V:2
Vccp2_max SENSORS_ADM9240_IN5_MAX Vccp2:2
fan1 SENSORS_ADM9240_FAN1 fan1:2
fan2 SENSORS_ADM9240_FAN2 fan2:2
fan1_min SENSORS_ADM9240_FAN1_MIN fan1:1
fan2_min SENSORS_ADM9240_FAN2_MIN fan2:1
temp SENSORS_ADM9240_TEMP temp:3
temp_hyst SENSORS_ADM9240_TEMP_HYST temp:2
temp_over SENSORS_ADM9240_TEMP_OVER temp:1
vid SENSORS_ADM9240_VID vid:1
fan1_div SENSORS_ADM9240_FAN1_DIV fan_div:1
fan2_div SENSORS_ADM9240_FAN2_DIV fan_div:2
alarms SENSORS_ADM9240_ALARMS alarms:1
analog_out SENSORS_ADM9240_ANALOG_OUT analog_out:1
Old Documentation
-----------------
Supports the Analog Devices ADM9240 and the Dallas Semiconductor DS1780.
These chips are apparently identical except for the manufacturer ID
register.
The proc directory will be adm9240-i2c-x-xx for the adm9240 and
ds1780-i2c-x-xx for the ds1780.
-----------------------------------------------------------------------
This file documents the adm9240 directories for the Analog Devices
ADM9240 hardware health monitoring chip found on some Intel mainboards
(and others?).
There will be one directory created for each detected ADM9240 chip.
Each chip must be on the SMBus. Directories are called things
like adm9240-i2c-1-2c. /proc/sys/dev/sensors/chips contains the SYSCTL
values for all chip directories.
Within each ADM9240 directory, you can find the following files:
* alarms (ADM9240_SYSCTL_ALARMS)
A number which indicates which alarms are on. An alarm gets triggered when
some limit has been crossed. This number is composed from a 16 bit
value which is the combination of the Interrupt Status Register 1
(low byte) and 2 (high byte).
This file can not be written to.
The number is the logical OR of the following components (bit #
followed by a description):
0 +2.5 V_Error Read Only A "1" indicates a high or low limit has been exceeded.
1 VCCP_Error Read Only A "1" indicates a high or low limit has been exceeded.
2 +3.3 V_Error Read Only A "1" indicates a high or low limit has been exceeded.
3 +5 V_Error Read Only A "1" indicates a high or low limit has been exceeded.
4 Temp_Error Read Only A "1" indicates that a temperature interrupt has been set.
5 Reserved Read Only Undefined.
6 FAN1_Error Read Only A "1" indicates that a fan count limit has been exceeded.
7 FAN2_Error Read Only A "1" indicates that a fan count limit has been exceeded.
8 +12 V_Error Read Only A "1" indicates a high or low limit has been exceeded.
9 VCCP2_Error Read Only A "1" indicates a high or low limit has been exceeded.
10 Reserved Read Only Undefined.
11 Reserved Read Only Undefined.
12 Chassis_Error Read Only A "1" indicates chassis intrusion has gone high.
13 Reserved Read Only Undefined.
14 Reserved Read Only Undefined.
15 Reserved Read Only Undefined.
If accessed through sysctl, this value is a long.
* fan[1-3] (ADM9240_SYSCTL_FAN[1-2])
A list of two numbers. The first is the minimum fan rotation limit; the
second is the current fan rotation speed. Both are in RPM (rotations per
minute). An alarm is triggered if the rotation speed drops below the
limit. The first value can be changed. Not all RPM values can accurately
be represented, so some rounding is done.
If accessed through sysctl, this is a list of longs.
* fan_div (LM78_SYSCTL_FAN_DIV)
A list of two numbers, one for each fan. Each number can be either 1, 2,
4 or 8. It is the internal scaling factor used for the fan rotations. If
you change this number, the fan readings get more or less detailed, but
the range in which they can be read too. Higher numbers give better
resolution, but less range. The first two numbers can be changed, the
third not.
If accessed through sysctl, this is a list of longs.
* 2.5V, Vccp1, 3.3V, 5V, 12V, and Vccp2 (ADM9240_SYSCTL_IN[0-5] in the
order shown)
A list of three numbers. The first is the minimum limit, the second is the
maximum limit, and the third is the current value. Note that
minimum means 'closest to zero'; so if the normal value equals -10, a
minimum could equal -9, and a maximum -11. Vccp1 and Vccp2 reflect core
voltages; they are by default set from the vid values, or set to 2.80V
if the vid lines seem to be unconnected. Not all voltage values
can be accurately represented, so some rounding is done.
The first two numbers can be changed, the third not.
If accessed through sysctl, this is a list of longs, each being the voltage
times 100.
* temp
A list of three numbers. The first number is the Overtemperature Shutdown
value; the second is the Hysteresis value and the third number is the
current value. The first two values can be modified. All values are in
degrees Celsius. An alarm is issued when the temperature first gets above
the Overtemperature Shutdown value; no more alarms are issued until the
temperature gets below the Hysteresis value, at which moment another alarm
is issued, and the state is reset. A more useful behaviour can be found
by setting the Hysteresis value to +127 degrees Celsius; in this case,
alarms are issued during all the time when the actual temperature is
above the Overtemperature Shutdown value.
The first two numbers can be changed, the third not.
If accessed through sysctl, this is a list of longs, each being the
temperature in degrees Celsius times 10.
* vid
The core voltage value (the voltage level your processor should work with),
in volts. This is the value IN0 and IN1 are initialized to. If unconnected,
it usually reads 3.50V.
This number can not be changed.
If accessed through sysctl, this is a long, being the voltage times 100.
* analog_out
This is feature is specific to the ADM9240. It is a DAC voltage
control register for the Analog-Out pin. Valid values are from 255
to 0 (with 255 being the highest voltage, and 0 the lowest). From
the docs: "The ADM9240 has a single analog output from an unsigned
8-bit DAC which produces 0 V - 1.25 V."
The data for each ADM9240 is updated each 1.5 seconds,
but only if it is actually read.
Note: Much of this documentation is copied from the LM78 documenation
(the device from which most featuers of the ADM9240 is modelled from).
The maintainer of the ADM9240 is Philip Edelbrock
<phil@netroedge.com>; please forward corrections, suggestions, etc.
to him. For further technical information on this chip, I suggest
getting the Acrobat PDF documentation from www.analog.com.
Dallas documentation is at http://www.dalsemi.com.
-89
View File
@@ -1,89 +0,0 @@
Kernel driver `asb100.o'
========================
Status: Work in progress
Supported Chips:
* Asus ASB100 and ASB100-A "Bach"
Prefix: 'asb100'
Addresses scanned: I2C 0x2d
Datasheet: none released
Author: Mark M. Hoffman <mhoffman@lightlink.com>
Module Parameters
-----------------
<TODO: fill in here>
Asus' BIOS is often broken
--------------------------
If you have an Asus P4PE, you need to read prog/hotplug/README.p4b.
If you have any other (Asus) board for which sensors-detect finds neither
asb100 nor an I2C bus, please read prog/hotplug/README.p4b before sending
a bug report.
Description
-----------
This driver implements support for the Asus ASB100 and ASB100-A "Bach". These
are custom ASICs available only on Asus mainboards. Asus refuses to supply a
datasheet for these chips. Thanks go to many people who helped investigate
their hardware, including:
Vitaly V. Bursov
Alexander van Kaam (author of MBM for Windows)
Bertrik Sikken
The ASB100 implements seven voltage sensors, three fan rotation speed sensors,
four temperature sensors, VID lines and alarms. In addition to these, the
ASB100-A also implements a single PWM controller for fans 2 and 3 (i.e. one
setting controls both.) If you have a plain ASB100, the pwm controller will
simply not work (or maybe it will for you... it doesn't for me).
Temperatures are measured and reported in degrees Celsius.
Fan speeds are reported in RPM (rotations per minute). An alarm is triggered
if the rotation speed has dropped below a programmable limit.
Voltage sensors (also known as IN sensors) report values in volts.
The VID lines encode the core voltage value: the voltage level your processor
should work with. This is hardcoded by the mainboard and/or processor itself.
It is a value in volts.
Alarms: (TODO question marks indicate may or may not work)
0x0001 => in0 (?)
0x0002 => in1 (?)
0x0004 => in2
0x0008 => in3
0x0010 => temp1 (1)
0x0020 => temp2
0x0040 => fan1
0x0080 => fan2
0x0100 => in4
0x0200 => in5 (?) (2)
0x0400 => in6 (?) (2)
0x0800 => fan3
0x1000 => chassis switch
0x2000 => temp3
Alarm Notes:
(1) This alarm will only trigger if the hysteresis value is 127C.
I.e. it behaves the same as w83781d.
(2) The min and max registers for these values appear to
be read-only or otherwise stuck at 0x00.
TODO:
* Experiment with fan divisors > 8.
* Experiment with temp. sensor types.
* Are there really 13 voltage inputs? Probably not...
* Fix bogus client deregistration (like w83781d in 2.6).
* Fix fan divisor / fan minimum interaction (like lm78).
* Cleanups, no doubt...
-140
View File
@@ -1,140 +0,0 @@
Kernel driver `bmcsensors.o'
==========================
Status: Alpha
Supported chips:
* IPMI BMC
Prefix: 'bmc'
Requires dummy adapter i2c-ipmi;
Driver will be registered as bmc-ipmi-0000.
Author: M. Studebaker <mdsxyz123@yahoo.com>
License: GPL
Module Parameters
-----------------
None.
Detection
---------
Registers only with i2c-ipmi.
Description
-----------
Together with the i2c-ipmi module, this provides high-level
access to sensors known by the BMC. For low-level access to
IPMB (I2C/SMBus) busses behind the BMC, use the i2c-ipmb module
and individual chip drivers.
This module REQUIRES the IPMI kernel patch by Corey Minyard,
v13 or higher, available at http://openipmi.sourceforge.net .
Other IPMI drivers are not supported.
See http://www.intel.com/design/servers/ipmi/ for
IPMI standards information.
You must configure the following features in your kernel
(under Character Devices):
IPMI top-level message handler
IPMI KCS handler
The IPMI device interface and IPMI watchdog handler are not required.
You must modprobe i2c-ipmi separately, it will not
load automatically when you modprobe bmcsensors.
If the KCS handler (ipmi_kcs_drv) is configured as a module,
you must also modprobe it separately, it will not load automatically
when you modprobe i2c-ipmi or ipmi_msghandler (unless you set up
/etc/modules.conf to load ipmi_kcs_drv when ipmi_msghandler is loaded).
/proc entries
-------------
The BMC SDR's (Sensor Data Records) are scanned and
the /proc table is built dynamically. Sensor types and quantities
depend on what the BMC supports.
curr1-n Currents. Max, min, and reading in amps.
fan1-n Fan speeds. Min and reading in RPM.
in1-n Voltages. Max, min, and reading in volts.
temp1-n Temps. Max, hysteresis or min, and reading in degrees C.
Thresholds will appear as 0 if unsupported or unreadable, as indicated
in the SDR.
If the thresholds are not settable (as indicated in the SDR), the
/proc permissions for that sensor will be 444.
After the SDR's are scanned, the module will print
the sensor names and bindings to syslog/dmesg.
Cut these messages out and paste them to /etc/sensors.conf
so that 'sensors' has correct labeling.
Issues
------
The module will not register in /proc/sys/dev/sensors until
the SDR's are scanned. This may take several seconds.
The driver uses a very simple state machine. It only sends
one message at a time and then waits for a response.
This makes the driver relatively slow.
If a response is an error or does not arrive, the driver
will probably be stuck.
/proc access kicks off an update but returns the old data,
not the new; need to wait for update to finish and then return.
Probably there are some locking issues.
Every attempt is made to present the sensor data in /proc in a
manner consistent with lm_sensors /proc standards, as defined
in doc/developers/proc. Given the wide variation in possible SDR definitions,
this may not generate expected or useful data.
IPMI supports up to 4 max and 4 min thresholds (non-recoverable, critical,
non-critical, and hysteresis) for each sensor. This driver
tries to pick out the best one and exports it as the max and min
to /proc.
ins start at 1, other modules start at in0.
Alarms and thresholds are not yet supported.
Threshold-only sensors are not supported.
Threshold setting is not yet supported.
1's and 2's complement formats not yet supported.
Non-linear conversions not supported, except for 1/x.
Fan divs are not supported (or required) - should the driver
export a fan_div entry (1 1 1 ...) for compatibility?
Supports only sensor types 1-4 (temp, voltage, current, and fan).
Libsensors can't handle dynamic /proc entries so the table
in lib/chips.c must be static and contain the max number of
each sensor. Bmcsensors can register more of a sensor type
than libsensors supports in its static table.
Only one BMC is supported - shouldn't be a problem.
Alpha!
**********************
The lm_sensors project gratefully acknowledges the support of
Hewlett Packard (Ft. Collins) in the development of this driver.
-397
View File
@@ -1,397 +0,0 @@
Kernel driver `bt869.o'
======================
Status: 'Beta' and needs further development and testing
Supported chips:
* Brooktree BT869 Video Modulator chips
Prefix: 'bt869'
Addresses scanned: I2C 0x44 - 0x45
Datasheet: Publicly available at the brooktree web site
(www.brooktree.com)
Author: Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock
<phil@netroedge.com>, Ralph Metzler <rjkm@thp.uni-koeln.de>
Steve Davies <steve@daviesfam.org> added the 720x576 and
720x480 support, and the svideo configuration.
Main Contact: Philip Edelbrock <phil@netroedge.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_bt869: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `bt869' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver provides some access to the Brooktree bt869 found on some
video cards (like the 'Voodoo3 3000'). Typically, the output can be
either an s-video connection or composite video. Resolutions
supported using the chip's built in configurations are 640x480 or
800x600. In addition, a 720x576 PAL mode and a 720x480 NTSC mode,
both without overscan compensation, are supported - a convenient
configuration for video playback.
Other possible implementations may allow for double resolutions with some
decimations (e.g., a monitor running at 1280x960 but the TV out getting every
other pixel for 640x480).
Driver Features
---------------
Standards: (US) NTSC or PAL
Resolutions: 640x480, 800x600, 720x576 (PAL only), 720x480 (NTSC only)
Depths: 16, 24 or 32 bits
Colorbars: Switch on the built-in color bar generator. (FYI- using a blue
filter, you should see only alternating bright stripes of equal brightness.
Adjust your TV's color and saturation if this isn't the case.)
SVideo: composite or s-video output. Also, theoretically, RGB output, though
this isn't tested and will require that your card presents all three DAC outputs
(the 3dfx Voodoo3 doesn't)
In your XF86Config file, you should have these entries for NTSC output:
ModeLine "640x480NTSC" 28.195793 640 656 658 784 480 520 525 600
ModeLine "720x480NTSC" 27.5 720 744 800 872 480 483 485 525
ModeLine "800x600NTSC" 38.769241 800 812 814 880 600 646 649 735
or for PAL
ModeLine "640x480PAL" 29.50 640 675 678 944 480 530 535 625
ModeLine "720x576PAL" 27.50 720 744 800 880 576 581 583 625
ModeLine "800x600PAL" 36.00 800 818 820 960 600 653 655 750
Also, don't forget to add these modes to the resolutions list in the
'screen' sections of XF86Config. You can change modes on the fly in X by
pressing (usually) ctrl-alt-+ or ctrl-alt-(minus) with + and - being the
keys on your numeric 10-key pad.
BTW- My monitor doesn't like these entries very much, and as I've read:
don't run your monitor out of spec or else it will burst into fire. So,
having the monitor turned on while in any of these modes is at your own
risk. (I've never heard of a monitor breaking from this, but it seems to
be fashionable to warn about it.)
Chip Features
-------------
The chip is very flexible and customizable for many standards. Right
now the driver sticks with the chip's built in NTSC and PAL standards,
with the addition of the 720x576PAL and 720x480NTSC modes. It also
does some other wacky stuff like closed-captioning, Macro-vision
copy-protection, and other things. Check out the datasheet for more
info.
Issues
------
The depth register doesn't seem to do much? If you have troubles or
aren't sure the output is working, turn the color-bars on. If they display
OK, then the chip is syncing correctly. The next thing to try is the depth
setting. Lastly, turning off your computer and back on with your TV
connected but you monitor not, may force the video card to configure and
use the BT869 as your primary output device (all bets are off once X is
started). Let me know if you have problems which you can't figure out
(phil@netroedge.com).
--------------------------------
On May 3rd, 2001 (My birthday, as it happens to be :') Tomi Ollila sent me
a how-to from a user's perspective on using the BT869 with a 3Dfx card.
It should also provide insight for other video card users who have a
BT869 on board. Here is his document:
How-To
------
This text describes my successful attempt to eventually get the TV-Out
feature of 3DFX Voodoo 3 3000 (PAL) working in a Linux environment.
I've successfully configured the 800x600 mode -- And currently I am
not interested in 640x480 -- hardware scaling rules in all cases
I need the tv-out. My monitor works much less worse w/ 800x600PAL...
All the information is based on my best knowledge but there might be
some mistakes. In any case, THE ENTIRE RISK OF USING THIS INFORMATION
IS WITH YOU, AND NO ONE ELSE IS LIABLE OF ANY PROBLEMS ENCOUNTERED.
Please fix any mistakes, inaccuracies, writing errors, etc. you
notice in this document.
I was about to write briefly, but sorry due to limited resources
I did not have time to write briefly (too much thinking ;).
Well, browse through the text and skip all irrelevant parts
to proceed...
Tomi Ollila <too@iki.fi>
The first time I got the the tv-out working I was working w/ Linux kernel
2.4.3. Now I am redoing the work w/ Linux 2.4.4 and recording the steps
in this document. Description of my system and software used is as follows:
RedHat 7.0 linux distribution (probably any distribution will do, since
all of the needed software is compiled from packages that are not RH-pagkaged)
(maybe if anything is important, are modutils-2.3.14-3 and kernel-headers-2.4.0-0.26)
XFree-4.0.3. I don't see any reason anyone uses any older. HW acceleration
of Voodoo3 3000 works out of the box with this. Also Windowed!!!
lm_sensors-2.5.5: Get this or newer. I have not used any older.
i2c-2.5.5: Probably 2.4.x -kernel distributions has recent enough version
of i2c. Since I also have IOmega BUZ and it's (working) versions require
i2c-old, I cannot use the one's kernel provided. Since I cannot say anything
about not using this i2c as separate package I suggest you do the same
and compile this also.
I have used kgcc that comes in RedHat 7.0 -distribution to compile all
stuff. Probably all other recent gcc -compilers will produce good results
also: kgcc -v outputs:
Reading specs from /usr/lib/gcc-lib/i386-glibc21-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
--
1) The work starts w/ kernel compilation (or skip to step 2, I quess it will
work w/ any recent 2.2 and 2.4 -kernels)
# run make config, make menuconfig or make xconfig (I suggest 'xconfig')
For sure you need kernel module support and /proc -filesystem support
(XXX add more absolute requirements)
Now I cannot say for sure, if following things needs to be compiled
at kernel compilation time or does i2c-2.5.5 bring all these:
character devices->i2c support->i2c support
character devices->i2c support->i2c bit-banging interfaces (*)
character devices->i2c support->i2c devices (**)
character devices->/dev/agpart support
character devices->Direct Rendering Manager (XFree86 DRI support) (+)
character devices->3dfx banshee/Voodoo3+
make above as modules.
(*) I already forgot why I wanted some special attention for this
(**) It seems I never need this
(+) Hmm, It seems I have compiled this as part of the kernel binary
There is of course lot of other options you need to choose but these
are probably the ones needed...
# run make dep
# run make bzImage
# run make modules
# run make modules_install (as root)
# copy arch/i386/boot/bzImage to /boot/vmlinuz-2.4.4 (as root)
# edit /etc/lilo.conf and run lilo (as root)
2) In order to (the most easiest way) compile separate i2c and lm_sensors
-package, symlink your linux source tree to /usr/src/linux (unless it is
there already) and do the following
# tar zxvf i2c-2.5.5.tar.gz
# cd i2c-2.5.5
# make
# make install (as root)
# cd ..
# tar zxvf lm_sensors-2.5.5.tar.gz
# cd lm_sensors-2.5.5
# make
# make install (as root)
# cd ..
Now you have all necessary modules installed on the system (and
/usr/local/bin/sensors and /usr/local/sbin/sensors-detect).
But there is some configuration missing in order to activate
the needed functionality of the modules...
3) Now, before going any further, let's take a look the modifications that
is required in XFree86 -configuration so that X is ready for the
tv-out functionality next time it is started.
XFree86-4.x uses the configuration file /etc/X11/XF86Config-4. Older
configuration files needed "ModeLine"s for all display resolutions
that are needed, but XFree86 version 4 can figure out those by itself.
But what it cannot figure out, are some special ModeLines that may
be reguired in various cases... and this is one of these.
For Voodoo 3 3000 TV out feature, special modelines are needed for
the supported resolutions, 640x480 and 800x600 (there might be others
but I don't know enough of the issue. In this document, I only
activate the 800x600 mode.
Make backup copy of your /etc/X11/XF86Config-4 in case you accidentally
screw something up so you can then reuse the old file if this happens.
Open /etc/X11/XF86Config-4 in your favourite editor and find line
that contains `Section "Monitor"'. At the end of that section,
just before line `EndSection', add the following lines:
ModeLine "640x480PAL" 29.50 640 675 678 944 480 530 535 625
ModeLine "800x600PAL" 36.00 800 818 820 960 600 653 655 750
Then, find lines that starts with something like:
Section "Screen"
Identifier "Screen0"
Device "3Dfx Interactive, Inc.|Voodoo 3"
Monitor "<Your monitor>"
DefaultDepth 16
...
Subsection "Display"
Depth 16
# Modes "1024x768" "640x480" "800x600" "800x600PAL" #"400x300"
Modes "1024x768" "800x600PAL"
EndSubSection
Make sure that `DefaultDepth' is set 16 (I don't know if any other works)
Then you notice there my resolutions as `Modes' -lines. I have outcommented
the first `Modes' -line so that testing is easier. You can do what
you widh here, just make sure to add that `"800x600PAL"' -line there.
Save the file and exit editor.
4) It's is time to reboot (with new kernel), that is the easiest way to
continue.
5) After rebooting, and X startup `lsmod' in my machine output following:
Module Size Used by
tdfx 54536 1
es1371 28500 0
ac97_codec 8808 0 [es1371]
tdfx -module was loaded by X, and sound module by some RH -startup script.
Now you could run /usr/local/sbin/sensors-detect. After that was run in
my PII class -machine it suggested I add the following:
to /etc/modules.conf
alias char-major-89 i2c-dev
to some startup script (i choose append to /etc/rc.local)
# I2C adapter drivers
/sbin/modprobe i2c-piix4
/sbin/modprobe i2c-voodoo3
# I2C chip drivers
/sbin/modprobe w83781d
/sbin/modprobe eeprom
(you run the prg and add the stuff it suggests)
Let's now /sbin/modprobe -v i2c-voodoo3. Now I have:
Module Size Used by
i2c-voodoo3 3012 0 (unused)
i2c-algo-bit 7796 2 [i2c-voodoo3]
i2c-core 12844 0 [i2c-algo-bit]
tdfx 54536 1
es1371 28500 0
ac97_codec 8808 0 [es1371]
I think this is the only one of the above that is required for the task.
(If that is not true, I'll revise this text and you never know I though
such a thing)
Now, turn on your TV set (unless it is already open) and make sure it
is getting (either s-video or composite... I personally have only
tested composite) input from your voodoo3 card. Don't change any
screen resolution just yet...
You notice the tv set screen is still blank. Changing screen resolutions
won't do any change on that, don't bother trying.
So, how to continue from there. The answer was found looking through
all the files that are found with zsh command line in lm_sensors dir:
grep -i voodoo3 **/*(.) | less
The file doc/chips/bt869 contains interesting information, with it
and some other information I found somewhere else, don't remember
either, I did:
modprobe -v bt869 (or insmod /lib/modules/2.x.x/misc/bt869)
NOW! you should see some noise on your TV-Set screen.
and `lsmod' returns (on my machine)
Module Size Used by
bt869 5392 0 (unused)
i2c-voodoo3 3012 0 (unused)
sensors 6140 0 [bt869]
i2c-algo-bit 7796 2 [i2c-voodoo3]
i2c-core 12844 0 [bt869 sensors i2c-algo-bit]
tdfx 54536 1
es1371 28500 0
ac97_codec 8808 0 [es1371]
If you now try to change mode to `800x600PAL' The noise pattern just
changes. That's because there are various parameters for bt869 to
make it operate in different systems.
The configuration entries for bt869 live somewhere in `proc' filesystem
and since it is so hard to remember, the following commands helps out:
# find /proc -name "bt869*" -print
In my system that output: /proc/sys/dev/sensors/bt869-i2c-0-44. Since
the last few characters may be different on your system I'll use...
# ls /proc/sys/dev/sensors/bt869* ... and this outputs:
colorbars: Output colorbars instead of display data. Default `0'.
depth: Display depth. Seems to be `16' on my system.
half: No idea. Default `0'.
ntsc: 1: use NTSC, 0: use PAL. Default: `1'.
res; Screen resolution to use. Defaut: `640 480'.
status: Outputs some status info I do not know what that is.
For me, in order to get TV sync with incoming signal (or is it Voodoo3
that needs syncing), I first put colorbars on, and when I get colorbars
visible I shut colorbars off -- and then I have part of my 1024x768
displayed on TV-screen. Normal WIMP screen doesn't look so nice, but
games, pictures, etc. Looks just fine. And HW acceleration rules.
Do the following to achieve this:
# echo 1 > /proc/sys/dev/sensors/bt869*/colorbars
# echo 0 > /proc/sys/dev/sensors/bt869*/ntsc
# echo 800 600 > /proc/sys/dev/sensors/bt869*/res
Press ctrl-alt-num_keypad_+
Now you should see vertical colorbars on your TV-Set
# echo 0 > /proc/sys/dev/sensors/bt869*/colorbars
Now (part of) your computer display should be visible on your TV-Set.
... Ok, I did just that... after ctrl-alt-num_keypad_+ I did not get
synchronized colorbar output visible on my TV. So, at this time I
re-did...
# echo 1 > /proc/sys/dev/sensors/bt869*/colorbars
... and got colorbars, and ...
# echo 0 > /proc/sys/dev/sensors/bt869*/colorbars
... got my computer display visible on my TV.
#EOF
-272
View File
@@ -1,272 +0,0 @@
Kernel driver `ddcmon.o'
========================
Status: As complete as we want it to be, lightly tested
Supported chips:
* Any DDC Monitor EDID EEPROM chip at 0x50
Prefix: 'ddcmon'
Addresses scanned: I2C 0x50
Standards: available for purchase from VESA http://www.vesa.org
Datasheets: Publicly available from Atmel (www.atmel.com),
Fairchild (www.fairchildsemi.com), and
Microchip (www.microchip.com)
Chip Size (bits) Address
24C01 1K 0x50 (shadows at 0x51 - 0x57)
(Typical device in monitors)
24C01A 1K 0x50 - 0x57 (Typical device in monitors)
24C02 2K 0x50 - 0x57
24C04 4K 0x50, 0x52, 0x54, 0x56
(additional data at 0x51, 0x53, 0x55, 0x57)
24C08 8K 0x50, 0x54 (additional data at 0x51, 0x52,
0x53, 0x55, 0x56, 0x57)
24C16 16K 0x50 (additional data at 0x51 - 0x57)
Author: Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock
<phil@netroedge.com>, Mark Studebaker <mdsxyz123@yahoo.com> and
Jean Delvare <khali@linux-fr.org>
Special assistance on DDC provided by Petr Vandrovec <vandrove@vc.cvut.cz>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_ddcmon: short array (min = 1, max = 48)
Same as 'force'
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* checksum: int
Only accept eeproms whose checksum is correct
Description
-----------
This is a simple module for reading the EEPROM on a DDC-compliant monitor.
DDC (Display Data Channel) is the I2C-based communication channel
to the monitor. The EEPROM contains a 128-byte data structure defined
by the EDID (Extended Display Identification Data) standard.
Use:
After inserting the module (and any other required smbus/i2c modules), you
should have a directory in /proc/sys/dev/sensors/ with a name such
as "ddcmon-i2c-3-50". Inside each of these is a series of files which
represent interesting data from the DDC monitor.
/proc entries:
dpms: bitmask
DPMS support flags
0x20 Active Off
0x40 Suspend
0x80 Standby
edid: 2 integers
EDID version and revision
gamma: 1 float
gamma factor
id: 2 integers
manufacturer ID and product ID
Manufacturer ID is encoded, see 'sensors'
source ../../prog/sensors/chips.c.
maxclock: 1 integer
max pixel clock (also known as video bandwidth) (MHz)
0 if not available.
serial: 1 integer
serial number
Encoding depends on the manufacturer, don't expect the
real serial number here. 0 probably means "not available".
size: 2 integers
vertical monitor size (cm)
horizontal monitor size (cm)
sync: 4 integers
minimum vertical sync frequency (Hz)
maximum vertical sync frequency (Hz)
minimum horizontal sync frequency (KHz)
maximum horizontal sync frequency (KHz)
0 0 0 0 if not available.
time: 2 integers
Manufacture year and week
timing[1-8]: 3 integers
supported standard timing
horizontal resolution
vertical resolution
(vertical) refresh rate
0 0 0 if not used.
timings: bitmask
supported established timings
0x000001 720x400@70Hz (VGA 640x400, IBM)
0x000002 720x400@88Hz (XGA2)
0x000004 640x480@60Hz (VGA)
0x000008 640x480@67Hz (Mac II, Apple)
0x000010 640x480@72Hz (VESA)
0x000020 640x480@75Hz (VESA)
0x000040 800x600@56Hz (VESA)
0x000080 800x600@60Hz (VESA)
0x000100 800x600@72Hz (VESA)
0x000200 800x600@75Hz (VESA)
0x000400 832x624@75Hz (Mac II)
0x000800 1024x768@87Hz interlaced (8514A)
0x001000 1024x768@60Hz (VESA)
0x002000 1024x768@70Hz (VESA)
0x004000 1024x768@75Hz (VESA)
0x008000 1280x1024@75Hz (VESA)
0x010000 - 0x800000 Manufacturer reserved
0x800000 1152x870 @ 75 Hz (Mac II, Apple)?
Chip Features
-------------
Chip 'ddcmon'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
Manufacturer ID - - R- 0
Model Number - - R- 0
Monitor Size (cm) - - R- 0
hsize Monitor Size (cm) - R- 0
Vertical Sync (Hz) - - R- 0
v_sync_max Vertical Sync (Hz) - R- 0
Horizontal Sync (KHz) - - R- 0
h_sync_max Horizontal Sync (KHz) - R- 0
Established Timings - - R- 0
Serial Number - - R- 0
Manufacture Time - - R- 0
week Manufacture Time - R- 0
EDID Version - - R- 0
revision EDID Version - R- 0
Gamma Factor - - R- 2
DPMS Modes - - R- 0
Standard Timing 1 - - R- 0
vertical Standard Timing 1 - R- 0
refresh Standard Timing 1 - R- 0
Standard Timing 2 - - R- 0
vertical Standard Timing 2 - R- 0
refresh Standard Timing 2 - R- 0
Standard Timing 3 - - R- 0
vertical Standard Timing 3 - R- 0
refresh Standard Timing 3 - R- 0
Standard Timing 4 - - R- 0
vertical Standard Timing 4 - R- 0
refresh Standard Timing 4 - R- 0
Standard Timing 5 - - R- 0
vertical Standard Timing 5 - R- 0
refresh Standard Timing 5 - R- 0
Standard Timing 6 - - R- 0
vertical Standard Timing 6 - R- 0
refresh Standard Timing 6 - R- 0
Standard Timing 7 - - R- 0
vertical Standard Timing 7 - R- 0
refresh Standard Timing 7 - R- 0
Standard Timing 8 - - R- 0
vertical Standard Timing 8 - R- 0
refresh Standard Timing 8 - R- 0
Max Pixel Clock (MHz) - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
Manufacturer ID SENSORS_DDCMON_MAN_ID id:1
Model Number SENSORS_DDCMON_PROD_ID id:2
Monitor Size (cm) SENSORS_DDCMON_VERSIZE size:1
hsize SENSORS_DDCMON_HORSIZE size:2
Vertical Sync (Hz) SENSORS_DDCMON_VERSYNCMIN sync:1
v_sync_max SENSORS_DDCMON_VERSYNCMAX sync:2
Horizontal Sync (KHz) SENSORS_DDCMON_HORSYNCMIN sync:3
h_sync_max SENSORS_DDCMON_HORSYNCMAX sync:4
Established Timings SENSORS_DDCMON_TIMINGS timings:1
Serial Number SENSORS_DDCMON_SERIAL serial:1
Manufacture Time SENSORS_DDCMON_YEAR time:1
week SENSORS_DDCMON_WEEK time:2
EDID Version SENSORS_DDCMON_EDID_VER edid:1
revision SENSORS_DDCMON_EDID_REV edid:2
Gamma Factor SENSORS_DDCMON_GAMMA gamma:1
DPMS Modes SENSORS_DDCMON_DPMS dpms:1
Standard Timing 1 SENSORS_DDCMON_TIMING1_HOR timing1:1
vertical SENSORS_DDCMON_TIMING1_VER timing1:2
refresh SENSORS_DDCMON_TIMING1_REF timing1:3
Standard Timing 2 SENSORS_DDCMON_TIMING2_HOR timing2:1
vertical SENSORS_DDCMON_TIMING2_VER timing2:2
refresh SENSORS_DDCMON_TIMING2_REF timing2:3
Standard Timing 3 SENSORS_DDCMON_TIMING3_HOR timing3:1
vertical SENSORS_DDCMON_TIMING3_VER timing3:2
refresh SENSORS_DDCMON_TIMING3_REF timing3:3
Standard Timing 4 SENSORS_DDCMON_TIMING4_HOR timing4:1
vertical SENSORS_DDCMON_TIMING4_VER timing4:2
refresh SENSORS_DDCMON_TIMING4_REF timing4:3
Standard Timing 5 SENSORS_DDCMON_TIMING5_HOR timing5:1
vertical SENSORS_DDCMON_TIMING5_VER timing5:2
refresh SENSORS_DDCMON_TIMING5_REF timing5:3
Standard Timing 6 SENSORS_DDCMON_TIMING6_HOR timing6:1
vertical SENSORS_DDCMON_TIMING6_VER timing6:2
refresh SENSORS_DDCMON_TIMING6_REF timing6:3
Standard Timing 7 SENSORS_DDCMON_TIMING7_HOR timing7:1
vertical SENSORS_DDCMON_TIMING7_VER timing7:2
refresh SENSORS_DDCMON_TIMING7_REF timing7:3
Standard Timing 8 SENSORS_DDCMON_TIMING8_HOR timing8:1
vertical SENSORS_DDCMON_TIMING8_VER timing8:2
refresh SENSORS_DDCMON_TIMING8_REF timing8:3
Max Pixel Clock (MHz) SENSORS_DDCMON_MAXCLOCK maxclock:1
Notes
-----
- The eeprom.o module will also attach to a DDC monitor. Install the
ddcmon.o module before the eeprom.o module to prevent this.
Alternatively, give the eeprom.o module an ignore= parameter or
a checksum= parameter. The ddcmon.o module will not attach to non-DDC
eeproms because it checks for a DDC 'signature' at the beginning
of the DDC Monitor's eeprom. Additionally, you can give it a checksum=
parameter.
- If a DDC monitor eeprom lacks the 'signature', you may use the
'force' parameter (modprobe ddcmon force=x,0x50 where 'x' is the
i2c bus number).
- This module requires a bus driver for your video chip to access the
DDC bus. In this package, drivers exist for Voodoo3/Banshee chips.
See documentation for those drivers in ../busses. Other drivers
(Matrox and NVidia, for example) are available in the kernel or
elsewhere. See our 'Supported Devices' web page for details.
- There is a checksum over the 128 bytes in the eeprom. The driver
used not to check it, now it does (providing you give it a
checksum= parameter).
- More information is available in the eeprom which could be made
available by enhancing the driver.
To see the entire eeprom, use the eeprom.o module instead of ddcmon.o.
However, then you lose the DDC-specific formatting.
Also see ../../prog/eeprom/decode-edid.pl.
- This driver does not support the DDC/CI (DDC2Bi) bidirectional
communications channel to the monitor for controlling the monitor.
- The driver caches the data from the monitor and only rereads it
from the eeprom if the cache is more than 5 minutes old.
- X 4.0 also accesses the DDC monitor EEPROM to auto-configure itself.
There could possibly be conflicts from these multiple accesses, but
none were ever reported so far.
-25
View File
@@ -1,25 +0,0 @@
Kernel driver `ds1307.o'
========================
Status: Complete and well-tested
Supported chips:
* Dallas Semiconductor DS1307 Real-time clock
Author: Abraham vd Merwe <abraham@2d3d.co.za>
Module Parameters
-----------------
There are no module parameters. The driver only scans address 0x68 for
presence of the real-time clock chip (which is the only possible address).
Description
-----------
This module provides an API for drivers RTC and NVRAM drivers to use in
order to access the DS1307 real-time clock. Examples of drivers that uses
this API are the frodo-nvram and frodo-rtc drivers.
-237
View File
@@ -1,237 +0,0 @@
Kernel driver `ds1621.o'
========================
Status: Complete and tested; enhanced features experimental
Supported chips:
* Dallas Semiconductor DS1621
Prefix: 'ds1621'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Dallas Semiconductor website
http://www.dalsemi.com/
* Dallas Semiconductor DS1625
Prefix: 'ds1621'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Dallas Semiconductor website
http://www.dalsemi.com/
Author: Christian W. Zuckschwerdt <zany@triq.net>
valueable contributions by Jan M. Sendler <sendler@sendler.de>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_ds1621: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `ds1621' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The DS1621 is a (one instance) digital thermometer and thermostat. It
has both high and low temperature limits which can be user defined
(i. e. programmed into non-volatile on-chip registers).
Temperature range is -55 degree Celsius to +125 in 0.5 increments. You
may convert this into a Fahrenheit range of -67 to +257 degrees with
0.9 steps. Unfortunally though, you will need a hash table or even a
conversion routine for that.
Temperature readings are internally converted into a 9bit value to be
read straight via the i2c-bus, but higher resolutions may be obtained
with little effort via 'precise_temp' access or using a simple algorithm.
As for the thermostat, behavior can also be programmed using the
polarity toggle.
On the one hand ("heater"), the thermostat output of the chip, Tout,
will trigger when the low limit temperature is met or underrun and
stays high until the high limit is met or exceeded. On the other hand
("cooler"), vice versa.
That way "heater" equals "active low", whereas "conditioner" equals
"active high". Please note that the DS1621 data sheet is somewhat
misleading in this point since setting the polarity bit does not
simply invert Tout.
A second thing is that, during extensive testing, Tout showed a
tolerance of up to +/- 0.5 degrees even when compared against precise
temperature readings.
Be sure to have a high vs. low temperature limit gap of al least 1.0
degree Celsius to avoid Tout "bouncing", though!
As for alarms, you can read the alarm status of the DS1621 via the
'alarms' proc file interface. The result consits mainly of bit 6 and 5
of the configuration register of the chip; bit 6 (0x40 or 64) is the
high alarm bit and bit 5 (0x20 or 32) the low one. These bits are set
when the high or low limits are met or exceeded and are reset by the
module as soon as the respective temperature ranges are left.
The alarm registers are in no way suitable to find out about the
actual status of Tout. They will only tell you about its history,
whether or not any of the limits have ever been met or exceeded since
last power-up or reset. Be aware: When testing, it showed that the
status of Tout can change with neither of the alarms set.
Temperature conversion of the DS1621 takes up to 1000ms; internal
access to non-volatile registers may last for 10ms or below.
This driver also detects DS1625. As this chip is discontinued, you
should use a DS1621 instead. Detected 1625s also get the
"ds1621"-prefix, but the accuracy registers are not supported.
The latest modification of this driver was not checked against 1625s,
though, but should work well with them.
Default values written into the DS1621 upon detection are:
Temperature limits: High 60.0; low 0.0 degree Celsius;
Continuous mode (not enabled), Tout active high, alarms reset.
Accessing DS1621 via /proc interface (part I)
---------------------------------------------
On detection (i.e. insmod, modprobe et al.), directories are being
created for each detected DS1621:
/proc/sys/dev/sensors/ds1621-<0>-<1>/
where <0> is the bus the chip was detected on (e. g. i2c-0)
and <1> the chip address ([48..4f]): ./ds1621-i2c-0-48/
Inside these directories, there are five files each:
alarms, continuous, enable, polarity and temp.
The temp, alarms and enable are LM75-like for compatibility reasons:
The temp file is rw. Reading gives TEMP_HIGH:TEMP_HYST:TEMP, where
TEMP_HIGH is the upper limit temp, TEMP_HYST is the hysteresis (lower
limit) temp and TEMP is the actual temperature. (For things like
ONE_SHOT, see below.) - Writing takes two results (read: input
values): TEMP_HIGH:TEMP_HYST. This provides an easy way to set limits.
The alarms file is ro. Basically, it is 0x40 ("64") for the high alarm
bit set plus 0x20 ("32") for the low one. Since alarms are being reset
by the module when the limit conditions are no longer met, you should
first read the alarms before you do anything else (such as reading the
temperature), or the information may be lost.
The polarity file is rw. Reading gives "0" for active high, which
describes a need for cooling corresponds to Tout status. "1" means
active low, so you can connect a powerful heater directly to Tout
(DON'T TRY!). - Writing accepts "0" and "1" accordingly. Again, note
that the statements on page 5 of the DS1621 data sheet are misleading;
change the orientation of the vertical arrows in the sketch there
instead.
To understand the use of the other things, it is useful to have a
brief look at the internal DS1621 procedures:
High Accuracy Temperature Reading
---------------------------------
As said before, the temperature issued via the 9bit i2c-bus data is
somewhat arbitrary. Internally, the temperature conversion is of a
different kind that is explained (not so...) well in the DS1621 data
sheet. To cut the long story short: Inside the DS1621 there are two
oscillators, both of them biassed by a temperature coefficient.
Higher accuracy of the temperature reading can be achieved using the
internal projection, which means taking account of REG_COUNT and
REG_SLOPE (the driver manages them):
Taken from Dallas Semiconductors App Note 068: 'Increasing Temperature
Resolution on the DS1620' and App Note 105: 'High Resolution
Temperature Measurement with Dallas Direct-to-Digital Temperature
Sensors'
- Read the 9bit temperature and strip the LSB (Truncate the .5 degs)
- The resulting value is TEMP_READ.
- Then, read REG_COUNT.
- And then, REG_SLOPE.
TEMP = TEMP_READ - 0.25 + ((REG_SLOPE - REG_COUNT) / REG_SLOPE)
Note that this is what the DONE bit in the DS1621 configuration
register is good for: Internally, one temperature conversion takes up
to 1000ms. Before that conversion is complete you will not be able to
read valid things out of REG_COUNT and REG_SLOPE. The DONE bit, as you
may have guessed by now, tells you whether the conversion is complete
("done", in plain english) and thus, whether the values you read are
good or not.
The DS1621 has two modes of operation: "Continuous" conversion, which
can be understood as the default stand-alone mode where the chip gets
the temperature and controls external devices via its Tout pin or
tells other i2c's about it if they care. The other mode is called
"1SHOT", that means that it only figures out about the temperature
when it is explicitly told to do so; this can be seen as power saving
mode.
Now if you want to read REG_COUNT and REG_SLOPE, you have to either
stop the continuous conversions until the contents of these registers
are valid, or, in 1SHOT mode, you have to have one conversion made.
Accessing DS1621 via /proc interface (part II and end)
------------------------------------------------------
The continuous file is rw. Reading gives "1" when the chip is in
continuous mode and "0" for 1SHOT. - Writing accepts "1" to put it
into continuous mode or "0" for 1SHOT.
The enable file is rw. Reading gives "1" if in continuous mode the
DS1621 is "running" or if in 1SHOT mode it is "shooting". In fact, the
value returned here is the negated DONE bit, just like a "BUSY"
flag. This means, that the "1" tells you that the values in REG_COUNT
and REG_SLOPE are not valid. "0", on the other hand, means that the
continuous conversion had been stopped long enough to provide valid
readings and that one shot has successfully been fi- red,
respectively. - Writing accepts "1" to start continuous conversion or
to pull the trigger in 1SHOT mode, and "0" stops continuous conversion
to allow for valid readings. Writing "0" in 1SHOT doesn't make much
sense since you cannot catch a bullet, can you? No, the thing is that
DS1621 does only do one conversion then anyway, and after that,
there's nothing to stop.
Last, but best try writing "0" to the "continuous" file and after that
"1" to the "enable" file. This gives you the precise temperature with
two digits after the radix (decimal point).
The precise results are only updated if the DONE bit was set, thus you
have to stop continuous conversion early enough prior to reading,
i. e. 1000ms. And, on the other hand, you shouldn't forget to fire off
1SHOT in order to get the actual temperature rather than that of the
day before yesterday. So read enable before any of these.
Chip Features
-------------
Chip `ds1621'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
alarms NONE NONE R 0
enable NONE NONE RW 0
continuous NONE NONE RW 0
polarity NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
temp SENSORS_DS1621_TEMP temp:3
temp_hyst SENSORS_DS1621_TEMP_HYST temp:2
temp_over SENSORS_DS1621_TEMP_OVER temp:1
alarms SENSORS_DS1621_ALARMS alarms:1
enable SENSORS_DS1621_ENABLE enable:1
continuous SENSORS_DS1621_CONTINUOUS continuous:1
polarity SENSORS_DS1621_POLARITY polarity:1
-228
View File
@@ -1,228 +0,0 @@
Kernel driver `eeprom.o'
========================
Status: Complete and well-tested
Supported chips:
* Any EEPROM chip in the designated address range
Prefix: 'eeprom'
Addresses scanned: I2C 0x50 - 0x57
Datasheets: Publicly available from:
Atmel (www.atmel.com),
Catalyst (www.catsemi.com),
Fairchild (www.fairchildsemi.com),
Microchip (www.microchip.com),
Philips (www.semiconductor.philips.com),
Rohm (www.rohm.com),
ST (www.st.com),
Xicor (www.xicor.com),
and others.
Chip Size (bits) Address
24C01 1K 0x50 (shadows at 0x51 - 0x57)
24C01A 1K 0x50 - 0x57 (Typical device on DIMMs)
24C02 2K 0x50 - 0x57
24C04 4K 0x50, 0x52, 0x54, 0x56
(additional data at 0x51, 0x53, 0x55, 0x57)
24C08 8K 0x50, 0x54 (additional data at 0x51, 0x52,
0x53, 0x55, 0x56, 0x57)
24C16 16K 0x50 (additional data at 0x51 - 0x57)
Sony 2K 0x57
Atmel 34C02B 2K 0x50 - 0x57, SW write protect at 0x30-37
Catalyst 34FC02 2K 0x50 - 0x57, SW write protect at 0x30-37
Catalyst 34RC02 2K 0x50 - 0x57, SW write protect at 0x30-37
Fairchild 34W02 2K 0x50 - 0x57, SW write protect at 0x30-37
Microchip 24AA52 2K 0x50 - 0x57, SW write protect at 0x30-37
ST M34C02 2K 0x50 - 0x57, SW write protect at 0x30-37
Author: Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
Module Parameters
-----------------
* checksum: int
Only accept eeproms whose checksum is correct
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_eeprom: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `eeprom' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This is a simple EEPROM module meant to enable reading the first 256 bytes
of an EEPROM (on a SDRAM DIMM for example). However, it will access serial
EEPROMs on any I2C adapter. The supported devices are generically
called 24Cxx, and are listed above; however the numbering
for these industry-standard devices may vary by manufacturer.
This module was a programming exercise to get used to the new project
organization laid out by Frodo, but it should be at least completely
effective for decoding the contents of EEPROMs on DIMMs.
DIMMS will typically contain a 24C01A or 24C02, or the 34C02 variants.
The other devices will not be found on a DIMM because
they respond to more than one address.
DDC Monitors may contain any device. Often a 24C01, which responds
to all 8 addresses, is found. See the 'ddcmon' driver which has
specialized /proc entries for DDC monitors. If the eeprom driver is
loaded before the ddcmon driver and there is no 'ignore' line in the
/etc/sensors.conf file, the eeprom driver will latch onto the DDC monitor
eeprom.
Recent Sony Vaio laptops have an EEPROM at 0x57. We couldn't get the
specification, so it is guess work and far from being complete.
The Microchip 24AA52/24LCS52, ST M34C02, and others support an additional
software write protect register at 0x30 - 0x37 (0x20 less than the
memory location). The chip responds to "write quick" detection at this
address but does not respond to byte reads.
If this register is present, the lower 128 bytes of the memory
array are not write protected. Any byte data write to this address
will write protect the memory array permanently, and the device
will no longer respond at the 0x30-37 address.
The eeprom driver does not support this register.
Lacking functionality:
* Full support for larger devices (24C04, 24C08, 24C16). These are
not typically found on a PC. These devices will appear as separate
devices at multiple addresses.
* Support for really large devices (24C32, 24C64, 24C128, 24C256, 24C512).
These devices require two-byte address fields and are not supported.
* Enable Writing. Again, no technical reason why not, but making it easy
to change the contents of the EEPROMs (on DIMMs anyway) also makes it easy
to disable the DIMMs (potentially preventing the computer from booting)
until the values are restored somehow.
Use:
After inserting the module (and any other required smbus/i2c modules), you
should have some EEPROM directories in /proc/sys/dev/sensors/ of names such
as "eeprom-i2c-0-50". Inside each of these is a series of files which
represent 16 bytes blocks from the EEPROM. The data is in decimal (base
10) delimited by spaces.
Note:
EEPROMs reported are not nessesarily all from DIMMs. Xeon processors, for
example, have serial EEPROMs in them connected to the SMBus which will be
found by the module. Take care to ignore the output of decode-dimms.pl for
these EEPROMs. There is decode-xeon.pl for them. Same applies to Vaio
EEPROMs, which have decode-vaio.pl.
The driver caches the data from the monitor and only rereads it
from the eeprom if the cache is more than 5 minutes old.
Decode-dimms.pl
---------------
This Perl script attempts to make sense of the first 128 bytes of a SDRAM
PC-100 DIMM. Using the 'Serial Presence Detect (SPD)' Spec (Rev1.2a)** from
Intel. When finished, it will decode and report all the values defined in
the spec. Much of the information is technical timing and interfacing info
(probably not all used by the BIOS or clocking IC).
Note: During testing, we noticed that many DIMMs have trucated SPD records.
I'm not sure if these conform to an old spec, or if the manufacturers are
simply just taking shortcuts. But, many DIMMs have all zeros stored past
byte 21. I asked an Intel SDRAM tester, Sat Kolli (skolli@smartm.com),
about this and this is what he had to say:
"[...] Now in terms of SPD contents, you are right that people do all kinds
of things. The way to insure that any module works is to look for the most
basic information, such as module bank density, number of banks, and device
addresses. That will give you the module size and what devices are used. It
is very difficult to verify if the module is PC-100, because of
inconsistencies between spd data from different manufacturers. You could
read the timing information or the special Intel bytes (126 & 127) but I do
not know how many program that information. These inconsistencies may be
minimized if you or your customers stay with some of the better known
manufacturers." (Thanks goes to Sat Kolli for his comments and help.)
Use:
After inserting the necessary modules, run the script!
Notes:
The script assumes that Perl can be found at /usr/bin/perl. If different,
you will need to adjust the first line of the file accordingly, or else
you will get a strange "decode-dimms.pl: no such file or directory" error.
Also note that the script assumes that _all_ the eeproms belong to DIMMs,
which may not be the case.
Decode-vaio.pl
--------------
This Perl script looks for a Sony Vaio EEPROM at 0x57. If your system is not
a Sony Vaio laptop, you just don't care. If your system is an old Vaio, you
probably don't have an EEPROM on it and this script will be useless to you
as well.
Use:
After inserting the necessary modules, run the script!
Notes:
The script assumes that Perl can be found at /usr/bin/perl. If different,
you will need to adjust the first line of the file accordingly, or else
you will get a strange "decode-vaio.pl: no such file or directory" error.
Decode-edid.pl
This Perl script acts as an interface between the eeprom module and
parse-edid, which is part of read-edid. Read-edid is a tool for gathering
information on VESA PNP monitors. It is somewhat similar to what our
ddcmon driver does, except that ddcmon outputs user-oriented data while
read-edid generates a a computer oriented configuration file (primarily
for XFree86).
Read-edid is made of two components, get-edid which retrieves binary data
from the monitor, and parse-edid which decodes the data into useful
information. Get-edid uses low level functions, and relies on compliance
of both the video card and the monitor to some standard (DCC as it is
called). It may not work for everyone. On the other hand, some video
cards make this information available on an I2C bus and we are able to
get it using our eeprom module. That's why we wrote this script that
converts the data as exported by our eeprom module into what parse-edid
expects from get-edid. That way, users who can't get get-edid to work
still have a chance to be able to retrieve the wanted information thanks
to the lm_sensors modules and tools.
Use:
Unload the ddcmon if it was loaded, load the eeprom module instead. Then,
run the script. The script will try to figure out on which I2C bus and at
which address is the eeprom, but it may fail. In this case, you can pass
the parameters to the script. Examples:
decode-edid.pl 1
decode-edid.pl 2 0x50
You will find additional details in the script itself. Read-edid is avilable
there:
http://john.fremlin.de/programs/linux/read-edid/
-107
View File
@@ -1,107 +0,0 @@
Kernel driver `f71805f.o'
=========================
Status: Beta.
Supported chips:
* Fintek F71805F/FG
Addresses scanned: none, address read from Super I/O config space
Prefix: 'f71805f'
Datasheet: Provided by Fintek on request
Author: Jean Delvare <khali@linux-fr.org>
Thanks to Denis Kieft from Barracuda Networks for the donation of a
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
for providing initial documentation.
Thanks to Kris Chen from Fintek for answering technical questions and
providing additional documentation.
Thanks to Chris Lin from Jetway for providing wiring schematics and
answering technical questions.
Description
-----------
The Fintek F71805F/FG Super I/O chip includes complete hardware monitoring
capabilities. It can monitor up to 9 voltages (counting its own power
source), 3 fans and 3 temperature sensors.
This chip also has fan controlling features, using either DC or PWM, in
three different modes (one manual, two automatic). The driver doesn't
support these features yet.
The driver assumes that no more than one chip is present, which seems
reasonable.
Voltage Monitoring
------------------
Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
range is thus from 0 to 2.040 V. Voltage values outside of this range
need external resistors. An exception is in0, which is used to monitor
the chip's own power source (+3.3V), and is divided internally by a
factor 2.
The two LSB of the voltage limit registers are not used (always 0), so
you can only set the limits in steps of 32 mV (before scaling).
The wirings and resistor values suggested by Fintek are as follow:
pin expected
name use R1 R2 divider raw val.
in0 VCC VCC3.3V int. int. 2.00 1.65 V
in1 VIN1 VTT1.2V 10K - 1.00 1.20 V
in2 VIN2 VRAM 100K 100K 2.00 ~1.25 V (1)
in3 VIN3 VCHIPSET 47K 100K 1.47 2.24 V (2)
in4 VIN4 VCC5V 200K 47K 5.25 0.95 V
in5 VIN5 +12V 200K 20K 11.00 1.05 V
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
(1) Depends on your hardware setup.
(2) Obviously not correct, swapping R1 and R2 would make more sense.
These values can be used as hints at best, as motherboard manufacturers
are free to use a completely different setup. As a matter of fact, the
Jetway K8M8MS uses a significantly different setup. You will have to
find out documentation about your own motherboard, and edit sensors.conf
accordingly.
Each voltage measured has associated low and high limits, each of which
triggers an alarm when crossed.
Fan Monitoring
--------------
Fan rotation speeds are reported as 12-bit values from a gated clock
signal. Speeds down to 366 RPM can be measured. There is no theoretical
high limit, but values over 6000 RPM seem to cause problem. The effective
resolution is much lower than you would expect, the step between different
register values being 10 rather than 1.
The chip assumes 2 pulse-per-revolution fans.
An alarm is triggered if the rotation speed drops below a programmable
limit or is too low to be measured.
Temperature Monitoring
----------------------
Temperatures are reported in degrees Celsius. Each temperature measured
has a high limit, those crossing triggers an alarm. There is an associated
hysteresis value, below which the temperature has to drop before the
alarm is cleared.
All temperature channels are external, there is no embedded temperature
sensor. Each channel can be used for connecting either a thermal diode
or a thermistor. The driver reports the currently selected mode, but
doesn't allow changing it. In theory, the BIOS should have configured
everything properly.
-266
View File
@@ -1,266 +0,0 @@
Kernel driver `fscher.o'
=======================
Status: Stable
Tested with motherboard D1562
Think twice before you're using the write mode of some
variables, especially the watchdog
Supported chips:
* Fujitsu-Siemens Hermes chip
Prefix: 'fscher'
Addresses scanned: I2C 0x73
Author: Reinhard Nissl <rnissl@gmx.de> based on work from Hermann Jung
<hej@odn.de>, Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_fscher: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `fscher' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
This driver implements support for the Fujitsu-Siemens Hermes chip.
It is described in the 'Register Set Specification BMC Hermes based
Systemboard' from Fujitsu-Siemens.
The Hermes chip implements a hardware-based system management,
e.g. for controlling fan speed and core voltage.
There is also a watchdog counter on the chip which can trigger
an alarm and even shut the system down.
The chip provides three temperature values (CPU, motherboard and auxiliary),
three voltage values (+12V, +5V and battery) and three fans (power supply,
CPU and auxiliary).
Temperatures are measured in degrees Celsius. The resolution is 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute).
The value can be divided by a programmable divider (1, 2 or 4) which
is stored on the chip.
Voltage sensors (also known as "in" sensors) report their values in volts.
All values are reported as final values from the driver.
There is no need for further calculations.
Detailed description
--------------------
Below you'll find a single line description of all the bit values. With this
information, you're able to decode e. g. alarms, wdog, etc.
To make use of the watchdog, you'll need to set the watchdog time and enable
the watchdog. After that it is necessary to restart the watchdog time within
the specified period of time, or a system reset will occur.
* rev:1
READING & 0xff = 0x??: HERMES revision identification
* alarms:1
READING & 0x80 = 0x80: CPU throttling active
READING & 0x80 = 0x00: CPU running at full speed
READING & 0x10 = 0x10: software event (see control:1)
READING & 0x10 = 0x00: no software event
READING & 0x08 = 0x08: watchdog event (see wdog:2)
READING & 0x08 = 0x00: no watchdog event
READING & 0x02 = 0x02: thermal event (see temp*:1)
READING & 0x02 = 0x00: no thermal event
READING & 0x01 = 0x01: fan event (see fan*:1)
READING & 0x01 = 0x00: no fan event
READING & 0x13 ! 0x00: ALERT LED is flashing
* control:1
READING & 0x01 = 0x01: software event
READING & 0x01 = 0x00: no software event
WRITING & 0x01 = 0x01: set software event
WRITING & 0x01 = 0x00: clear software event
* wdog:3
READING & 0x80 = 0x80: power off on watchdog event while thermal event
READING & 0x80 = 0x00: watchdog power off disabled (just system reset enabled)
READING & 0x40 = 0x40: watchdog timebase 60 seconds (see also wdog:1)
READING & 0x40 = 0x00: watchdog timebase 2 seconds
READING & 0x10 = 0x10: watchdog enabled
READING & 0x10 = 0x00: watchdog disabled
WRITING & 0x80 = 0x80: enable "power off on watchdog event while thermal event"
WRITING & 0x80 = 0x00: disable "power off on watchdog event while thermal event"
WRITING & 0x40 = 0x40: set watchdog timebase to 60 seconds
WRITING & 0x40 = 0x00: set watchdog timebase to 2 seconds
WRITING & 0x20 = 0x20: disable watchdog
WRITING & 0x10 = 0x10: enable watchdog / restart watchdog time
* wdog:2
READING & 0x02 = 0x02: watchdog system reset occurred
READING & 0x02 = 0x00: no watchdog system reset occurred
WRITING & 0x02 = 0x02: clear watchdog event
* wdog:1
READING & 0xff = 0x??: configured watch dog time in units (see wdog:3 0x40)
WRITING & 0xff = 0x??: configure watch dog time in units
* in*:1 (0: +5V, 1: +12V, 2: onboard 3V battery)
READING: actual voltage value
* temp*:1 (1: CPU sensor, 2: onboard sensor, 3: auxiliary sensor)
READING & 0x02 = 0x02: thermal event (overtemperature)
READING & 0x02 = 0x00: no thermal event
READING & 0x01 = 0x01: sensor is working
READING & 0x01 = 0x00: sensor is faulty
WRITING & 0x02 = 0x02: clear thermal event
* temp*:2 (1: CPU sensor, 2: onboard sensor, 3: auxiliary sensor)
READING: actual temperature value
* fan*:1 (1: power supply fan, 2: CPU fan, 3: auxiliary fan)
READING & 0x04 = 0x04: fan event (fan fault)
READING & 0x04 = 0x00: no fan event
WRITING & 0x04 = 0x04: clear fan event
* fan*:2 (1: power supply fan, 2: CPU fan, 3: auxiliary fan)
READING & 0x03 = 0x03: 8 ripples are one fan turn
READING & 0x03 = 0x02: 4 ripples are one fan turn
READING & 0x03 = 0x01: 2 ripples are one fan turn
WRITING & 0x03 = 0x03: set prescaler to "8 ripples are one fan turn"
WRITING & 0x03 = 0x02: set prescaler to "4 ripples are one fan turn"
WRITING & 0x03 = 0x01: set prescaler to "2 ripples are one fan turn"
* fan*:3 (1: power supply fan, 2: CPU fan, 3: auxiliary fan)
READING & 0xff = 0x00: fan may be switched off
READING & 0xff = 0x01: fan must run at least at minimum speed (supply: 6V)
READING & 0xff = 0xff: fan must run at maximum speed (supply: 12V)
READING & 0xff = 0x??: fan must run at least at given speed (supply: 6V..12V)
WRITING & 0xff = 0x00: fan may be switched off
WRITING & 0xff = 0x01: fan must run at least at minimum speed (supply: 6V)
WRITING & 0xff = 0xff: fan must run at maximum speed (supply: 12V)
WRITING & 0xff = 0x??: fan must run at least at given speed (supply: 6V..12V)
* fan*:4 (1: power supply fan, 2: CPU fan, 3: auxiliary fan)
READING: actual RPM value
Limitations
-----------
* Measuring fan speed
It seems that the chip counts "ripples" (typical fans produce 2 ripples per
rotation while VERAX fans produce 18) in a 9-bit register. This register is
read out every second, then the ripple prescaler (2, 4 or 8) is applied and
the result is stored in the 8 bit output register. Due to the limitation of
the counting register to 9 bits, it is impossible to measure a VERAX fan
properly (even with a prescaler of 8). At its maximum speed of 3500 RPM the
fan produces 1080 ripples per second which causes the counting register to
overflow twice, leading to only 186 RPM.
* Measuring input voltages
in2 ("battery") reports the voltage of the onboard lithium battery and not
+3.3V from the power supply.
* Undocumented features
Fujitsu-Siemens Computers has not documented all features of the chip so far.
Their software, System Guard, shows that there are a still some features which
cannot be controled by this implementation.
Chip Features
-------------
Chip 'fscher'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
rev - - R- 0
alarms - - R- 0
control - - RW 0
in0 - - R- 2
in1 - - R- 2
in2 - - R- 2
temp1 - - R- 0
temp2 - - R- 0
temp3 - - R- 0
temp1_state temp1 temp1 RW 0
temp2_state temp2 temp2 RW 0
temp3_state temp3 temp3 RW 0
fan1 - - R- 0
fan2 - - R- 0
fan3 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan1_state fan1 fan1 RW 0
fan2_state fan2 fan2 RW 0
fan3_state fan3 fan3 RW 0
fan1_ripple fan1 fan1 RW 0
fan2_ripple fan2 fan2 RW 0
fan3_ripple fan3 fan3 RW 0
wdog_preset - - RW 0
wdog_state wdog_preset wdog_preset RW 0
wdog_control wdog_preset wdog_preset RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
rev SENSORS_FSCHER_REV rev:1
alarms SENSORS_FSCHER_EVENT alarms:1
control SENSORS_FSCHER_CONTROL control:1
in0 SENSORS_FSCHER_VOLTAGE1 in0:1
in1 SENSORS_FSCHER_VOLTAGE2 in1:1
in2 SENSORS_FSCHER_VOLTAGE3 in2:1
temp1 SENSORS_FSCHER_TEMP1 temp1:2
temp2 SENSORS_FSCHER_TEMP2 temp2:2
temp3 SENSORS_FSCHER_TEMP3 temp3:2
temp1_state SENSORS_FSCHER_TEMP1_STATE temp1:1
temp2_state SENSORS_FSCHER_TEMP2_STATE temp2:1
temp3_state SENSORS_FSCHER_TEMP3_STATE temp3:1
fan1 SENSORS_FSCHER_FAN1 fan1:4
fan2 SENSORS_FSCHER_FAN2 fan2:4
fan3 SENSORS_FSCHER_FAN3 fan3:4
fan1_min SENSORS_FSCHER_FAN1_MIN fan1:2
fan2_min SENSORS_FSCHER_FAN2_MIN fan2:2
fan3_min SENSORS_FSCHER_FAN3_MIN fan3:2
fan1_state SENSORS_FSCHER_FAN1_STATE fan1:1
fan2_state SENSORS_FSCHER_FAN2_STATE fan2:1
fan3_state SENSORS_FSCHER_FAN3_STATE fan3:1
fan1_ripple SENSORS_FSCHER_FAN1_RIPPLE fan1:3
fan2_ripple SENSORS_FSCHER_FAN2_RIPPLE fan2:3
fan3_ripple SENSORS_FSCHER_FAN3_RIPPLE fan3:3
wdog_preset SENSORS_FSCHER_WDOG_PRESET wdog:1
wdog_state SENSORS_FSCHER_WDOG_STATE wdog:2
wdog_control SENSORS_FSCHER_WDOG_CONTROL wdog:3
-122
View File
@@ -1,122 +0,0 @@
Kernel driver `fscpos.o'
======================
Status: Beta
Tested with motherboard d1107
Think twice before you're using the write mode of some
variables, especially the watchdog
Supported chips:
* Fujitsu Siemens Poseidon chip
Prefix: 'fscpos'
Addresses scanned: I2C 0x73
Author: Hermann Jung <hej@odn.de>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the Fujitsu Siemens Poseidon chip.
It is descibed in the 'Register Set Specification BMC Poseidon based
Systemboard' from Fujitsu Siemens.
The poseidon chip implements a hardware based system management,
e.g. for controlling fan speed and core voltage.
There is also a watchdog counter on the chip which can trigger
an alarm and even shutdown the system.
The chip provides three temperature values (cpu, motherboard and auxiliary),
three current values (12 volt, 5 volt and battery) and
three fans (power supply, cpu and auxiliary).
Temperatures are measured in degrees Celsius. The resolution is 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute).
The value can be divided by a programmable divider (1, 2, 4 or 8) which
is stored on the chip.
Voltage sensors (also known as IN sensors) report their values in volts.
All values are reported as final values from the driver.
There is no need for further calculations.
Chip Features
-------------
Chip `fscpos'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
rev NONE NONE R 0
event NONE NONE R 0
control NONE NONE RW 0
volt12 NONE NONE R 2
volt5 NONE NONE R 2
voltbatt NONE NONE R 2
temp1 NONE NONE R 0
temp2 NONE NONE R 0
temp3 NONE NONE R 0
temp1_state temp1 temp1 RW 0
temp2_state temp2 temp2 RW 0
temp3_state temp3 temp3 RW 0
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan1_state fan1 fan1 RW 0
fan2_state fan2 fan2 RW 0
fan3_state fan3 fan3 RW 0
fan1_ripple fan1 fan1 RW 0
fan2_ripple fan2 fan2 RW 0
fan3_ripple fan3 fan3 RW 0
wdog_preset NONE NONE RW 0
wdog_state wdog_preset wdog_preset RW 0
wdog_control wdog_preset wdog_preset RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
rev FSCPOS_SYSCTL_REV rev:1
event FSCPOS_SYSCTL_EVENT event:1
control FSCPOS_SYSCTL_CONTROL control:1
volt12 FSCPOS_SYSCTL_VOLTAGE1 volt0:1
volt5 FSCPOS_SYSCTL_VOLTAGE2 volt1:1
voltbatt FSCPOS_SYSCTL_VOLTAGE3 volt2:1
temp1 FSCPOS_SYSCTL_TEMP1 temp0:2
temp2 FSCPOS_SYSCTL_TEMP2 temp1:2
temp3 FSCPOS_SYSCTL_TEMP3 temp2:2
temp1_state FSCPOS_SYSCTL_TEMP1_STATE temp0:1
temp2_state FSCPOS_SYSCTL_TEMP2_STATE temp1:1
temp3_state FSCPOS_SYSCTL_TEMP3_STATE temp2:1
fan1 FSCPOS_SYSCTL_FAN1 fan0:4
fan2 FSCPOS_SYSCTL_FAN2 fan1:4
fan3 FSCPOS_SYSCTL_FAN3 fan2:4
fan1_min FSCPOS_SYSCTL_FAN1_MIN fan0:2
fan2_min FSCPOS_SYSCTL_FAN2_MIN fan1:2
fan1_state FSCPOS_SYSCTL_FAN1_STATE fan0:1
fan2_state FSCPOS_SYSCTL_FAN2_STATE fan1:1
fan3_state FSCPOS_SYSCTL_FAN3_STATE fan2:1
fan1_ripple FSCPOS_SYSCTL_FAN1_RIPPLE fan0:3
fan2_ripple FSCPOS_SYSCTL_FAN2_RIPPLE fan1:3
fan3_ripple FSCPOS_SYSCTL_FAN3_RIPPLE fan2:3
wdog_preset FSCPOS_SYSCTL_WDOG_PRESET wdog:1
wdog_state FSCPOS_SYSCTL_WDOG_STATE wdog:2
wdog_control FSCPOS_SYSCTL_WDOG_CONTROL wdog:3
-251
View File
@@ -1,251 +0,0 @@
Kernel driver `fscscy.o'
======================
Status: Beta
Tested with motherboard D1230
Should also work with D1204, D1306 and D1307
Think twice before you're using the write mode of some
variables, especially the watchdog (Not my comment, but it makes
good sense to observe :-)
Supported chips:
* Fujitsu Siemens Scylla chip
Prefix: 'fscscy'
Addresses scanned: I2C 0x73
Author: Martin Knoblauch <mkn@teraport.de>
Acknowledgement:
This code has been greatly inspired by Hermann Jung <hej@odn.de>
and his work on the Poseidon driver (fscpos)
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the Fujitsu Siemens Scylla chip.
The code is based on the "fscpos" driver for the FSC Poseidon chip.
In fact, it seems the Scylla is a superset of the "Poseidon", "Poseidon II"
and "Hydra" chips.
The register readings/setting have been deduced from the 'Register Set
Specification BMC Poseidon based Systemboard' by Fujitsu Siemens and the
"sbbmon.ini" file from the Fujitsu-Siemens ServerView software.
The scylla chip implements a hardware based system management,
e.g. for controlling fan speed and core voltage.
There is also a watchdog counter on the chip which can trigger
an alarm and even shutdown the system. See above for more details about using
the watchdog feature.
The chip provides four temperature values (cpu0/1, motherboard and auxiliary),
three current values (12 volt, 5 volt and battery) and up to
six fans (power supply, cpu and auxiliary).
Temperatures are measured in degrees Celsius. The resolution is 1 degree.
In addition to state and actual temperature, the temperature sensors also export
a LIMIT temperature for each sensor. The meaning of the information itself is
obvious. The rest is guesswork. It likely triggers an alarm/event as soon as it is
reached. The authors feeling is that it is even a writeable register. But beeing of
the cautios kind he did not implement writing the LIMIT resister. The driver itself
keeps track of the minimum and maximum temperatures since module load. The more often
the module is accessed, the more meaningful these values are.
Fan rotation speeds are reported in RPM (rotations per minute).
The value can be divided by a programmable divider (1, 2, 4 or 8) which
is stored on the chip.
Voltage sensors (also known as IN sensors) report their values in volts.
All values are reported as final values from the driver.
There is no need for further calculations.
Using The Watchdog
------------------
This part was mostly contributed by A'rpi <arpi@thot.banki.hu>. Thanks!
I (Jean Delvare) edited the text for greater clarity, mainly to integrate
the e-mail exchange I and A'rpi had as a sequel of the original report.
Here it goes.
I've spent few hours experiencing with the watchdog feature of the Fujitsu
Siemens server board (don't ask the model name, what I know is that it uses
Serverworks CSB5 chipset) using lm_sensors' fscscy driver.
OK, so there is /proc/sys/dev/sensors/fscscy-i2c-0-73/wdog, containing 3
8-bit values for the 3 watchdog registers.
The first one is the time counter, it counts backwards. It's in 2 seconds
base, so writting 30 there means 60 seconds delay. It seems the whole range
(0 to 255) is supported, so up to 510 seconds (8.5 minutes). Writting 0 means
immediate hardware reset. The value you can read back is the same as you wrote
there, not the current value of the counter (as I expected).
The second number is supposed to be a state register. It doesn't matter what
value I put there. It's always 0. Even if I write a non-zero value there, i got
0 back. Maybe it's non-zero at the moment of reset :)
The third is the control register, with 5th (value 16), 6th (value 32) and 8th
(value 128) bits used only. If I write 16 or 144 (128+16), it means system
reset when the counter reaches 0. If I add 32 (for a value of 48 or 176), no
reset will occur.
So, the world's simplest watchdog using this mainboard:
while true ; do
echo 30 0 16 > /proc/sys/dev/sensors/fscscy-i2c-0-73/wdog
sleep 10
done
It does hardware reset after 1 minute, if this script is killed or system
hang-up occurs. Adjust the 30 and 10 values according to you needs, but
remember you must sleep less than the reset countdown time (here, 30*2-10
gives us a 50 seconds margin, which is more than enough).
Also note that BIOS has a strange setting, named 'OS Boot Retry Count', set
to 0 by default, it changes watchdog behaviour to power off instead of reset.
(0=poweroff 1..7=reset). It took me a while to find this. This behavior is
probably related to the "Reset Limit" parameter defined by the ACPI
specification (3.3.24 System Reset (Type 23)). It sets how many unintentional
resets can be performed in a row before giving up (and shut down).
Please add the above to the documentation (doc/chips/fscscy), so i can save
a few hours of resetting for other people :)
Chip Features
-------------
Chip `fscscy'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
rev NONE NONE R 0
event NONE NONE R 0
control NONE NONE RW 0
volt12 NONE NONE R 2
volt5 NONE NONE R 2
voltbatt NONE NONE R 2
temp1 NONE NONE R 0
temp2 NONE NONE R 0
temp3 NONE NONE R 0
temp4 NONE NONE R 0
temp1_state temp1 temp1 RW 0
temp2_state temp2 temp2 RW 0
temp3_state temp3 temp3 RW 0
temp4_state temp4 temp4 RW 0
temp1_lim temp1 temp1 R* 0
temp2_lim temp2 temp2 R* 0
temp3_lim temp3 temp3 R* 0
temp4_lim temp4 temp4 R* 0
temp1_min temp1 temp1 R 0
temp2_min temp2 temp2 R 0
temp3_min temp3 temp3 R 0
temp4_min temp4 temp4 R 0
temp1_max temp1 temp1 R 0
temp2_max temp2 temp2 R 0
temp3_max temp3 temp3 R 0
temp4_max temp4 temp4 R 0
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan4 NONE NONE R 0
fan5 NONE NONE R 0
fan6 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan4_min fan4 fan4 RW 0
fan5_min fan5 fan5 RW 0
fan6_min fan6 fan6 RW 0
fan1_state fan1 fan1 RW 0
fan2_state fan2 fan2 RW 0
fan3_state fan3 fan3 RW 0
fan4_state fan4 fan4 RW 0
fan5_state fan5 fan5 RW 0
fan6_state fan6 fan6 RW 0
fan1_ripple fan1 fan1 RW 0
fan2_ripple fan2 fan2 RW 0
fan3_ripple fan3 fan3 RW 0
fan4_ripple fan4 fan4 RW 0
fan5_ripple fan5 fan5 RW 0
fan6_ripple fan6 fan6 RW 0
wdog_preset NONE NONE RW 0
wdog_state wdog_preset wdog_preset RW 0
wdog_control wdog_preset wdog_preset RW 0
R*: Maybe writable, but due to lack of documentation the author did not dare
to implement it :-)
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
rev FSCSCY_SYSCTL_REV rev:1
event FSCSCY_SYSCTL_EVENT event:1
control FSCSCY_SYSCTL_CONTROL control:1
volt12 FSCSCY_SYSCTL_VOLTAGE1 volt0:1
volt5 FSCSCY_SYSCTL_VOLTAGE2 volt1:1
voltbatt FSCSCY_SYSCTL_VOLTAGE3 volt2:1
temp1 FSCSCY_SYSCTL_TEMP1 temp0:2
temp2 FSCSCY_SYSCTL_TEMP2 temp1:2
temp3 FSCSCY_SYSCTL_TEMP3 temp2:2
temp4 FSCSCY_SYSCTL_TEMP4 temp3:2
temp1_state FSCSCY_SYSCTL_TEMP1_STATE temp0:1
temp2_state FSCSCY_SYSCTL_TEMP2_STATE temp1:1
temp3_state FSCSCY_SYSCTL_TEMP3_STATE temp2:1
temp4_state FSCSCY_SYSCTL_TEMP4_STATE temp3:1
temp1_lim FSCSCY_SYSCTL_TEMP1_LIM temp0:3
temp2_lim FSCSCY_SYSCTL_TEMP2_LIM temp1:3
temp3_lim FSCSCY_SYSCTL_TEMP3_LIM temp2:3
temp4_lim FSCSCY_SYSCTL_TEMP4_LIM temp3:3
temp1_min FSCSCY_SYSCTL_TEMP1_MIN temp0:4
temp2_min FSCSCY_SYSCTL_TEMP2_MIN temp1:4
temp3_min FSCSCY_SYSCTL_TEMP3_MIN temp2:4
temp4_min FSCSCY_SYSCTL_TEMP4_MIN temp3:4
temp1_max FSCSCY_SYSCTL_TEMP1_MIN temp0:5
temp2_max FSCSCY_SYSCTL_TEMP2_MIN temp1:5
temp3_max FSCSCY_SYSCTL_TEMP3_MIN temp2:5
temp4_max FSCSCY_SYSCTL_TEMP4_MIN temp3:5
fan1 FSCSCY_SYSCTL_FAN1 fan0:4
fan2 FSCSCY_SYSCTL_FAN2 fan1:4
fan3 FSCSCY_SYSCTL_FAN3 fan2:4
fan4 FSCSCY_SYSCTL_FAN4 fan3:4
fan5 FSCSCY_SYSCTL_FAN5 fan4:4
fan6 FSCSCY_SYSCTL_FAN6 fan5:4
fan1_min FSCSCY_SYSCTL_FAN1_MIN fan0:2
fan2_min FSCSCY_SYSCTL_FAN2_MIN fan1:2
fan3_min FSCSCY_SYSCTL_FAN3_MIN fan2:2
fan4_min FSCSCY_SYSCTL_FAN4_MIN fan3:2
fan5_min FSCSCY_SYSCTL_FAN5_MIN fan4:2
fan6_min FSCSCY_SYSCTL_FAN6_MIN fan5:2
fan1_state FSCSCY_SYSCTL_FAN1_STATE fan0:1
fan2_state FSCSCY_SYSCTL_FAN2_STATE fan1:1
fan3_state FSCSCY_SYSCTL_FAN3_STATE fan2:1
fan4_state FSCSCY_SYSCTL_FAN4_STATE fan3:1
fan5_state FSCSCY_SYSCTL_FAN5_STATE fan4:1
fan6_state FSCSCY_SYSCTL_FAN6_STATE fan5:1
fan1_ripple FSCSCY_SYSCTL_FAN1_RIPPLE fan0:3
fan2_ripple FSCSCY_SYSCTL_FAN2_RIPPLE fan1:3
fan3_ripple FSCSCY_SYSCTL_FAN3_RIPPLE fan2:3
fan4_ripple FSCSCY_SYSCTL_FAN3_RIPPLE fan3:3
fan5_ripple FSCSCY_SYSCTL_FAN3_RIPPLE fan4:3
fan6_ripple FSCSCY_SYSCTL_FAN3_RIPPLE fan5:3
wdog_preset FSCSCY_SYSCTL_WDOG_PRESET wdog:1
wdog_state FSCSCY_SYSCTL_WDOG_STATE wdog:2
wdog_control FSCSCY_SYSCTL_WDOG_CONTROL wdog:3
-184
View File
@@ -1,184 +0,0 @@
Kernel driver `gl518sm.o'
=========================
Status: Complete and well-tested
Supported chips:
* Genesys Logic GL518SM release 0x00
Prefix: 'gl518sm'
Addresses scanned: I2C 0x2c and 0x2d
Datasheet: http://www.genesyslogic.com/pdf
* Genesys Logic GL518SM release 0x80
Prefix: 'gl518sm'
Addresses scanned: I2C 0x2c and 0x2d
Datasheet: http://www.genesyslogic.com/pdf
Author: Frodo Looijaard <frodol@dds.nl>,
Kyösti Mälkki <kmalkki@cc.hut.fi>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_gl518sm_r00: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `gl518sm_r00' chip
* force_gl518sm_r80: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `gl518sm_r80' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
IMPORTANT: For the revision 0x00 chip, the
in0, in1, and in2 values (+5, +3, and +12) CANNOT be read
unless you use the slow 'iterate' method. Limits will still
work even when iterate=0. See below. This is a limitation
of the chip, not the driver.
Note that the 'iterate' method was trimmed while porting the
driver to Linux 2.6 as we considered it too ugly for the thin
benefit.
This driver supports the Genesys Logic GL518SM chip. There are at least
two revision of this chip, which we call revision 0x00 and 0x80. Revision
0x80 chips support the reading of voltages directly, 0x00 only for VIN3.
The GL518SM implements one temperature sensor, two fan rotation speed
sensors, and four voltage sensors. It can report alarms through the
computer speakers.
Temperatures are measured in degrees Celsius. An alarm goes off while the
temperature is above the over temperature limit, and has not yet dropped
below the hysteresis limit. The alarm always reflects the current situation.
Measurements are guaranteed between -10 degrees and +110 degrees, with
a resolution of +-3 degrees.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit.
In case when you have selected to turn Fan1 off, no Fan1 alarm is triggered.
Fan readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 1900 RPM.
Voltage sensors (also known as VIN sensors) report their values in volts. An
alarm is triggered if the voltage has crossed a programmable minimum or
maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. The VDD input
measures voltages between 0.000 and 5.865 volt, with a resolution of 0.023
volt. The other inputs measure voltages between 0.000 and 4.845 volt, with a
resolution of 0.019 volt. Note that revision 0x00 chips do not support
reading the current voltage of any input except for VIN3; limit setting and
alarms work fine, though. There is code to compute them by iterating.
When an alarm goes off, you can be warned by a beeping signal through
your computer speaker. It is possible to enable all beeping globally,
or only the beeping for some alarms.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once (except for temperature alarms). This means that the
cause for the alarm may already have disappeared! Note that in the current
implementation, all hardware registers are read whenever any data is read
(unless it is less than 1.5 seconds since the last update). This means that
you can easily miss once-only alarms.
The GL518SM only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Iteration Code
--------------
IMPORTANT: For the revision 0x00 chip only,
you may select between three modes to read the vin0-2 inputs with
the following lines in /etc/sensors.conf:
set iterate 0 Don't read in0-in2 inputs.
set iterate 1 Run the iteration loop, this takes up to 10 seconds.
set iterate 2 Show the results of the last iteration loop,
and start a new loop to run in the background.
You may also set the iteration type with
'echo n > /proc/sys/dev/sensors/gl518sm-i2c-x-y/iterate'.
If you have a rev 0x80 chip, the in0-in2 inputs are readable.
In this case, 'iterate' will be read-only and the read value is 3.
This does NOT apply to the Linux 2.6 kernel driver, where the 'iteration'
code was dropped for being too ugly.
Chip Features
-------------
Chip `gl518sm'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
vdd NONE NONE R 2
vin1 NONE NONE R 2
vin2 NONE NONE R 2
vin3 NONE NONE R 2
vdd_min vdd vdd RW 2
vin1_min vin1 vin1 RW 2
vin2_min vin2 vin2 RW 2
vin3_min vin3 vin3 RW 2
vdd_max vdd vdd RW 2
vin1_max vin1 vin1 RW 2
vin2_max vin2 vin2 RW 2
vin3_max vin3 vin3 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
beep_enable alarms NONE RW 0
beeps alarms alarms RW 0
fan1_off NONE NONE RW 0
fan1_off_pin NONE NONE RW 0
iterate NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
vdd SENSORS_GL518_VDD vdd:3
vin1 SENSORS_GL518_VIN1 vin1:3
vin2 SENSORS_GL518_VIN2 vin2:3
vin3 SENSORS_GL518_VIN3 vin3:3
vdd_min SENSORS_GL518_VDD_MIN vdd:1
vin1_min SENSORS_GL518_VIN1_MIN vin1:1
vin2_min SENSORS_GL518_VIN2_MIN vin2:1
vin3_min SENSORS_GL518_VIN3_MIN vin3:1
vdd_max SENSORS_GL518_VDD_MAX vdd:2
vin1_max SENSORS_GL518_VIN1_MAX vin1:2
vin2_max SENSORS_GL518_VIN2_MAX vin2:2
vin3_max SENSORS_GL518_VIN3_MAX vin3:2
fan1 SENSORS_GL518_FAN1 fan1:2
fan2 SENSORS_GL518_FAN2 fan2:2
fan1_min SENSORS_GL518_FAN1_MIN fan1:1
fan2_min SENSORS_GL518_FAN2_MIN fan2:1
temp SENSORS_GL518_TEMP temp:3
temp_hyst SENSORS_GL518_TEMP_HYST temp:2
temp_over SENSORS_GL518_TEMP_OVER temp:1
fan1_div SENSORS_GL518_FAN1_DIV fan_div:1
fan2_div SENSORS_GL518_FAN2_DIV fan_div:2
alarms SENSORS_GL518_ALARMS alarms:1
beep_enable SENSORS_GL518_BEEP_ENABLE beep:1
beeps SENSORS_GL518_BEEPS beep:2
fan1_off SENSORS_GL518_FAN1OFF fan1off:1
fan1_off_pin SENSORS_GL518_FAN1PIN fan1off:2
iterate SENSORS_GL518_ITERATE iterate:1
-146
View File
@@ -1,146 +0,0 @@
Kernel driver `gl520sm.o'
=========================
Status: Complete but experimental
Supported chips:
* Genesys Logic GL520SM release 0x00
Prefix: 'gl520sm'
Addresses scanned: I2C 0x2c and 0x2d
Datasheet: Not openly available
Author: Frodo Looijaard <frodol@dds.nl>,
Kyösti Mälkki <kmalkki@cc.hut.fi>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver supports the Genesys Logic GL520SM chip.
The GL520SM implements one temperature sensor, two fan rotation speed
sensors, four voltage sensors and the cpu VID lines. In addition, one input
can be configured as either a temperature or voltage sensor.
The sensor can report alarms through the computer speakers.
Temperatures are measured in degrees Celsius. An alarm goes off while the
temperature is above the over temperature limit, and has not yet dropped
below the hysteresis limit. The alarm always reflects the current situation.
Measurements are guaranteed between 0 degrees and +110 degrees, with
a resolution of +-3 degrees.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit.
In case when you have selected to turn Fan1 off, no Fan1 alarm is triggered
if measured temperature is below 'over temperature' limit.
Fan readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 1900 RPM.
Voltage sensors (also known as VIN sensors) report their values in volts. An
alarm is triggered if the voltage has crossed a programmable minimum or
maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. The VDD input
measures voltages between 0.000 and 5.865 volt, with a resolution of 0.023
volt. The other inputs measure voltages between 0.000 and 4.845 volt, with a
resolution of 0.019 volt.
When an alarm goes off, you can be warned by a beeping signal through
your computer speaker. It is possible to enable all beeping globally,
or only the beeping for some alarms.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once (except for temperature alarms). This means that the
cause for the alarm may already have disappeared! Note that in the current
implementation, all hardware registers are read whenever any data is read
(unless it is less than 1.5 seconds since the last update). This means that
you can easily miss once-only alarms.
The GL520SM only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Chip Features
-------------
Chip `gl520sm'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
vdd NONE NONE R 2
vin1 NONE NONE R 2
vin2 NONE NONE R 2
vin3 NONE NONE R 2
vin4 NONE NONE R 2
vdd_min vdd vdd RW 2
vin1_min vin1 vin1 RW 2
vin2_min vin2 vin2 RW 2
vin3_min vin3 vin3 RW 2
vin4_min vin4 vin4 RW 2
vdd_max vdd vdd RW 2
vin1_max vin1 vin1 RW 2
vin2_max vin2 vin2 RW 2
vin3_max vin3 vin3 RW 2
vin4_max vin4 vin4 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp1 NONE NONE R 1
temp2 NONE NONE R 1
temp1_hyst temp1 temp1 RW 1
temp2_hyst temp2 temp2 RW 1
temp1_over temp1 temp1 RW 1
temp2_over temp2 temp2 RW 1
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
beep_enable alarms NONE RW 0
beeps alarms alarms RW 0
two_temps NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
vdd SENSORS_GL520_VDD vdd:3
vin1 SENSORS_GL520_VIN1 vin1:3
vin2 SENSORS_GL520_VIN2 vin2:3
vin3 SENSORS_GL520_VIN3 vin3:3
vin4 SENSORS_GL520_VIN4 vin3:3
vdd_min SENSORS_GL520_VDD_MIN vdd:1
vin1_min SENSORS_GL520_VIN1_MIN vin1:1
vin2_min SENSORS_GL520_VIN2_MIN vin2:1
vin3_min SENSORS_GL520_VIN3_MIN vin3:1
vin4_min SENSORS_GL520_VIN4_MIN vin3:1
vdd_max SENSORS_GL520_VDD_MAX vdd:2
vin1_max SENSORS_GL520_VIN1_MAX vin1:2
vin2_max SENSORS_GL520_VIN2_MAX vin2:2
vin3_max SENSORS_GL520_VIN3_MAX vin3:2
vin4_max SENSORS_GL520_VIN4_MAX vin3:2
fan1 SENSORS_GL520_FAN1 fan1:2
fan2 SENSORS_GL520_FAN2 fan2:2
fan1_min SENSORS_GL520_FAN1_MIN fan1:1
fan2_min SENSORS_GL520_FAN2_MIN fan2:1
temp1 SENSORS_GL520_TEMP1 temp1:3
temp2 SENSORS_GL520_TEMP2 temp2:3
temp1_hyst SENSORS_GL520_TEMP1_HYST temp1:2
temp2_hyst SENSORS_GL520_TEMP2_HYST temp2:2
temp1_over SENSORS_GL520_TEMP1_OVER temp1:1
temp2_over SENSORS_GL520_TEMP2_OVER temp2:1
fan1_div SENSORS_GL520_FAN1_DIV fan_div:1
fan2_div SENSORS_GL520_FAN2_DIV fan_div:2
alarms SENSORS_GL520_ALARMS alarms:1
beep_enable SENSORS_GL520_BEEP_ENABLE beep:1
beeps SENSORS_GL520_BEEPS beep:2
two_temps SENSORS_GL520_TWOTEMPS config:1
-273
View File
@@ -1,273 +0,0 @@
Kernel driver `it87.o'
======================
Status: Complete, stable
Supported chips:
* IT8705F
Prefix: 'it87'
Addresses scanned: ISA autodetected
Datasheet: Publicly available at the ITE website
http://www.ite.com.tw/
* IT8712F
Prefix: 'it8712'
Addresses scanned: I2C 0x28 - 0x2f, ISA autodetected
Datasheet: Publicly available at the ITE website
http://www.ite.com.tw/
* SiS950 [clone of IT8705F]
Prefix: 'it87'
Addresses scanned: ISA autodetected
Datasheet: No longer publicly available
Author: Christophe Gauthron <chrisg@0-in.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* update_vbat: integer 0 if vbat should report power on value, 1 if
vbat should be updated after each read. Default is 0.
On some boards the battery voltage is provided by either the battery or
the onboard power supply. Only the first reading at power on will be
the actual battery voltage (which the chip does automatically). On other
boards the battery voltage is always fed to the chip so can be read at
any time. Excessive reading may decrease battery life but no information
is given in the datasheet.
* reset: integer 1 if the registers of the chip should be reset at
module load time, 0 to not reset the registers. Default is 0.
Description
-----------
This driver implements support for the IT8705F, IT8712F and SiS950 chips.
These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
joysticks and other miscellaneous stuff. For hardware monitoring,
they include an 'environmnent controller' with 3 temperature sensors,
3 fan rotation speed sensors, 8 voltage sensors, and associated alarms.
The IT8712F additionally features VID inputs.
Temperatures are measured in degrees Celsius. An alarm is triggered once
when the Overtemperature Shutdown limit is crossed.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 4.08 volts, with a resolution
of 0.016 volt. The battery voltage in8 does not have limit registers.
The VID lines (IT8712F only) encode the core voltage value: the voltage level
your processor should work with. This is hardcoded by the mainboard and/or
processor itself. It is a value in volts.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
The IT87xx only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
The sensor type can be set at runtime as of release 2.8.2.
To change sensor N to a thermistor, 'echo 2 > sensorN'
where N is 1, 2, or 3.
To change sensor N to a thermal diode, 'echo 3 > sensorN'.
Give 0 for unused sensor. Any other value is invalid.
To configure this at startup, put the following lines in the it87 section
of /etc/sensors.conf.
(2 = thermistor; 3 = thermal diode; 0 = unused)
set sensor1 3
set sensor2 3
set sensor3 3
If you set a sensor to 0, you'll also want to ignore its value:
ignore tempN
The fan speed control features can be enabled by setting the bits 0-2 of
the fan_ctl register to 1. (Note: bits 6-4 of this register must be set to
1 to enable the fan monitoring).
0x40 : enable fan speed monitoring for fan3
0x20 : enable fan speed monitoring for fan2
0x10 : enable fan speed monitoring for fan1
0x04 : enable fan smart guardian mode for fan3
0x02 : enable fan smart guardian mode for fan2
0x01 : enable fan smart guardian mode for fan1
If a fan's smart guardian bit is not set this fan is in on-off mode.
It can be switched on or off by setting/clearing the corresponding bit 0-2
of the second fan control register fan_on_off. This register has no effect if
a fan is in smart guardian mode.
0x04 : fan3 is on
0x02 : fan2 is on
0x01 : fan1 is on
Bit 7 of the pwm[1-3] registers enables/disables the chip's automatic
temperature control mode for the specified fan. If Bit 7 is set the bits
1 and 0 encode the number of the temperature sensor used for this feature.
Possible register values are:
0x00-0x7f direct power control for the fan speed
0x80 automatic smart guardian mode use temperature sensor 1
0x81 automatic smart guardian mode use temperature sensor 2
0x82 automatic smart guardian mode use temperature sensor 3
In automatic smart guardian mode pwm values for low, medium and high speed
can be set in sg_pwm[1-3]. The temperature limits for fan off, low, medium,
hi speed and over temperature are set in sg_tl[1-3]. While the sensor's
temperature is between the low and medium speed value the fan will run in
low speed. When the sensor's temperature is between the medium and high speed
value the fan spins in medium speed. When the sensor's temperature is between
the high speed and over temperature value the fan spins in high speed.
If the sensor's temperature is higher than the over temperature limit the
fan will spin with the highest possible speed.
Example:
The preconfigured (BIOS set) values of the Shuttle XPC SN41G2 Barebone.
The MB uses temp2 to monitor the CPU temperature and fan3 as the fan for their
special ICE heatpipe cooling system.
$ cat fan_ctl
116
$ cat tan_on_off
215
$ cat pwm3
129
$ cat sg_tl3
16.0 16.0 48.0 54.0 58.0
$ cat sg_pwm3
3 4 9
Explanation:
fan_ctl -> 116 = 0x74 = 01110100 : fan monitoring on for all fans,
and fan3 is in smart guardian mode.
fan_on_off -> 215 = 0xd7 =11010111 : all fans are on this setting is
ignored for fan3 which is in smart guardian mode
pwm3 -> 129 = 0x81 = 10000001 : fan3 is in automatic mode and uses
temp2 for speed control
sg_tl3 -> 16.0 16.0 48.0 54.0 58.0 : off, low, med, high, and
over temperature limits of fan3
sg_pwm3 -> 3 4 9 : pwm values for low, medium and high fan speed of fan3.
Chip features
-------------
Chip `it87`
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in7 NONE NONE R 2
in8 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in7_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
in7_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
temp1 NONE NONE R 1
temp2 NONE NONE R 1
temp3 NONE NONE R 1
temp1_low temp temp RW 1
temp1_over temp temp RW 1
temp2_low temp temp RW 1
temp2_over temp temp RW 1
temp3_low temp temp RW 1
temp3_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms_vin NONE NONE R 0
alarms_fan NONE NONE R 0
alarms_temp NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
in0 SENSORS_IT87_IN0 in0:3
in1 SENSORS_IT87_IN1 in1:3
in2 SENSORS_IT87_IN2 in2:3
in3 SENSORS_IT87_IN3 in3:3
in4 SENSORS_IT87_IN4 in4:3
in5 SENSORS_IT87_IN5 in5:3
in6 SENSORS_IT87_IN6 in6:3
in7 SENSORS_IT87_IN7 in7:3
in0_min SENSORS_IT87_IN0_MIN in0:1
in1_min SENSORS_IT87_IN1_MIN in1:1
in2_min SENSORS_IT87_IN2_MIN in2:1
in3_min SENSORS_IT87_IN3_MIN in3:1
in4_min SENSORS_IT87_IN4_MIN in4:1
in5_min SENSORS_IT87_IN5_MIN in5:1
in6_min SENSORS_IT87_IN6_MIN in6:1
in7_min SENSORS_IT87_IN7_MIN in7:1
in0_max SENSORS_IT87_IN0_MAX in0:2
in1_max SENSORS_IT87_IN1_MAX in1:2
in2_max SENSORS_IT87_IN2_MAX in2:2
in3_max SENSORS_IT87_IN3_MAX in3:2
in4_max SENSORS_IT87_IN4_MAX in4:2
in5_max SENSORS_IT87_IN5_MAX in5:2
in6_max SENSORS_IT87_IN6_MAX in6:2
in7_max SENSORS_IT87_IN7_MAX in7:2
fan1 SENSORS_IT87_FAN1 fan1:2
fan2 SENSORS_IT87_FAN2 fan2:2
fan3 SENSORS_IT87_FAN3 fan3:2
fan1_min SENSORS_IT87_FAN1_MIN fan1:1
fan2_min SENSORS_IT87_FAN2_MIN fan2:1
fan3_min SENSORS_IT87_FAN3_MIN fan3:1
temp1 SENSORS_IT87_TEMP1 temp1:3
temp2 SENSORS_IT87_TEMP2 temp2:3
temp3 SENSORS_IT87_TEMP3 temp3:3
temp1_low SENSORS_IT87_TEMP1_LOW temp1:2
temp1_over SENSORS_IT87_TEMP1_HIGH temp1:1
temp2_low SENSORS_IT87_TEMP2_LOW temp2:2
temp2_over SENSORS_IT87_TEMP2_HIGH temp2:1
temp3_low SENSORS_IT87_TEMP3_LOW temp3:2
temp3_over SENSORS_IT87_TEMP3_HIGH temp3:1
vid SENSORS_IT87_VID vid:1
fan1_div SENSORS_IT87_FAN1_DIV fan_div:1
fan2_div SENSORS_IT87_FAN2_DIV fan_div:2
fan3_div SENSORS_IT87_FAN3_DIV fan_div:3
alarms_fan SENSORS_IT87_ALARMS_FAN alarms_fan:1
alarms_vin SENSORS_IT87_ALARMS_VIN alarms_vin:1
alarms_temp SENSORS_IT87_ALARMS_TEMP alarms_temp:1
-102
View File
@@ -1,102 +0,0 @@
Kernel driver `lm63.o'
======================
Status: Complete, lightly tested
Supported chips:
* National Semiconductor LM63
Prefix: 'lm63'
Addresses scanned: I2C 0x4c
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM63.html
Author: Jean Delvare <khali@linux-fr.org>
Thanks go to Tyan and especially Alex Buckingham for setting up a remote
access to their S4882 test platform for this driver.
http://www.tyan.com/
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm63: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm63' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The LM63 is a digital temperature sensor with integrated fan monitoring
and control.
All temperature values are given in degrees Celsius. Resolution is 1.0
degree for the local temperature, 0.125 degree for the remote temperature.
The fan speed is measured using a tachometer. Contrary to most chips which
store the value in an 8-bit register and have a selectable clock divider
to make sure that the result will fit in the register, the LM63 uses 16-bit
value for measuring the speed of the fan. It can measure fan speeds down to
83 RPM, at least in theory.
Note that the pin used for fan monitoring is shared with an alert out
function. Depending on how the board designer wanted to use the chip, fan
speed monitoring will or will not be possible. The proper chip configuration
is left to the BIOS, and the driver will blindly trust it.
A PWM output can be used to control the speed of the fan. The LM63 has two
PWM modes: manual and automatic. Automatic mode is not fully implemented yet
(you cannot define your custom pwm/temperature curve), and mode change isn't
supported either.
The lm63 driver will not update its values more frequently than every
second; reading them more often will do no harm, but will return 'old'
values.
Chip Features
-------------
Chip 'lm63'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 0
temp1_high temp1 temp1 RW 0
temp2 - - R- 3
temp2_high temp2 temp2 RW 3
temp2_low temp2 temp2 RW 3
temp2_crit temp2 temp2 R- 0
temp2_crit_hyst temp2 temp2 RW 0
fan1 - - R- 0
fan1_low fan1 fan1 RW 0
pwm1 - - RW 0
pwm1_enable pwm1 - R- 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp1 SENSORS_LM63_LOCAL_TEMP temp1:2
temp1_high SENSORS_LM63_LOCAL_HIGH temp1:1
temp2 SENSORS_LM63_REMOTE_TEMP temp2:3
temp2_high SENSORS_LM63_REMOTE_HIGH temp2:1
temp2_low SENSORS_LM63_REMOTE_LOW temp2:2
temp2_crit SENSORS_LM63_REMOTE_TCRIT temp2_crit:1
temp2_crit_hyst SENSORS_LM63_REMOTE_TCRIT_HYST temp2_crit_hyst:1
fan1 SENSORS_LM63_FAN fan1:2
fan1_low SENSORS_LM63_FAN_LOW fan1:1
pwm1 SENSORS_LM63_PWM pwm1:1
pwm1_enable SENSORS_LM63_PWM_ENABLE pwm1:2
alarms SENSORS_LM63_ALARMS alarms:1
-107
View File
@@ -1,107 +0,0 @@
Kernel driver `lm75.o'
=====================
Status: Complete and well-tested
Supported chips:
* National Semiconductor LM75
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/
* Dallas Semiconductor DS75
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/
* Dallas Semiconductor DS1775
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/
* Maxim MAX6625, MAX6626
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4b
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/
* Microchip (TelCom) TCN75
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Microchip website
http://www.microchip.com/
* Texas Instruments TMP100, TMP101
Prefix: 'lm75'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the TI website
http://www.ti.com/
Author: Frodo Looijaard <frodol@dds.nl>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm75: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm75' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The LM75 implements one temperature sensor. Limits can be set through the
Overtemperature Shutdown register and Hysteresis register. Each value can be
set and read to half-degree accuracy.
An alarm is issued (usually to a connected LM78) when the temperature
gets higher then the Overtemperature Shutdown value; it stays on until
the temperature falls below the Hysteresis value.
All temperatures are in degrees Celsius, and are guaranteed within a
range of -55 to +125 degrees.
The LM75 only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
The LM75 is usually used in combination with LM78-like chips, to measure
the temperature of the processor(s).
The DS75, DS1775, MAX6625, MAX6626, and others are supported as well.
They are not distinguished from an LM75. While most of these chips
have three additional bits of accuracy (12 vs. 9 for the LM75),
the additional bits are not supported. Not only that, but these chips will
not be detected if not in 9-bit precision mode (use the force parameter if
needed).
The prefix in the /proc directory is still 'lm75'.
The LM75 is essentially an industry standard; there may be other
LM75 clones not listed here, with or without various enhancements,
that are supported.
The LM77 is not supported, contrary to what we pretended for a long time.
Both chips are simply not compatible, value encoding differs.
Chip Features
-------------
Chip `lm75'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
temp SENSORS_LM75_TEMP temp:3
temp_hyst SENSORS_LM75_TEMP_HYST temp:2
temp_over SENSORS_LM75_TEMP_OVER temp:1
-337
View File
@@ -1,337 +0,0 @@
Kernel driver `lm78.o'
======================
Status: Complete and well-tested
Supported chips:
* National Semiconductor LM78
Prefix: 'lm78'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: Publicly available at the National Semiconductor website
* National Semiconductor LM78-J
Prefix: 'lm78-j'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: Publicly available at the National Semiconductor website
* National Semiconductor LM79
Prefix: 'lm79'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: Publicly available at the National Semiconductor website
Author: Frodo Looijaard <frodol@dds.nl>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm78: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm78' chip
* force_lm78j: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm78j' chip
* force_lm79: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm79' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the National Semiconductor LM78, LM78-J
and LM79. They are described as `Microprocessor System Hardware Monitors'.
There is almost no difference between the three supported chips. Functionally,
the LM78 and LM78-J are exactly identical. The LM79 has one more VID line,
which is used to report the lower voltages newer Pentium processors use.
From here on, LM7* means either of these three types.
The LM7* implements one temperature sensor, three fan rotation speed sensors,
seven voltage sensors, VID lines, alarms, and some miscellaneous stuff.
Temperatures are measured in degrees Celsius. An alarm is triggered once
when the Overtemperature Shutdown limit is crossed; it is triggered again
as soon as it drops below the Hysteresis value. A more useful behaviour
can be found by setting the Hysteresis value to +127 degrees Celsius; in
this case, alarms are issued during all the time when the actual temperature
is above the Overtemperature Shutdown value. Measurements are guaranteed
between -55 and +125 degrees, with a resolution of 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 4.08 volts, with a resolution
of 0.016 volt.
The VID lines encode the core voltage value: the voltage level your processor
should work with. This is hardcoded by the mainboard and/or processor itself.
It is a value in volts. When it is unconnected, you will often find the
value 3.50 V here.
In addition to the alarms described above, there are a couple of additional
ones. There is a BTI alarm, which gets triggered when an external chip has
crossed its limits. Usually, this is connected to all LM75 chips; if at
least one crosses its limits, this bit gets set. The CHAS alarm triggers
if your computer case is open. The FIFO alarms should never trigger; it
indicates an internal error. The SMI_IN alarm indicates some other chip
has triggered an SMI interrupt. As we do not use SMI interrupts at all,
this condition usually indicates there is a problem with some other
device.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
The LM7* only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Chip features
-------------
Chip `lm78'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
in0 SENSORS_LM78_IN0 in0:3
in1 SENSORS_LM78_IN1 in1:3
in2 SENSORS_LM78_IN2 in2:3
in3 SENSORS_LM78_IN3 in3:3
in4 SENSORS_LM78_IN4 in4:3
in5 SENSORS_LM78_IN5 in5:3
in6 SENSORS_LM78_IN6 in6:3
in0_min SENSORS_LM78_IN0_MIN in0:1
in1_min SENSORS_LM78_IN1_MIN in1:1
in2_min SENSORS_LM78_IN2_MIN in2:1
in3_min SENSORS_LM78_IN3_MIN in3:1
in4_min SENSORS_LM78_IN4_MIN in4:1
in5_min SENSORS_LM78_IN5_MIN in5:1
in6_min SENSORS_LM78_IN6_MIN in6:1
in0_max SENSORS_LM78_IN0_MAX in0:2
in1_max SENSORS_LM78_IN1_MAX in1:2
in2_max SENSORS_LM78_IN2_MAX in2:2
in3_max SENSORS_LM78_IN3_MAX in3:2
in4_max SENSORS_LM78_IN4_MAX in4:2
in5_max SENSORS_LM78_IN5_MAX in5:2
in6_max SENSORS_LM78_IN6_MAX in6:2
fan1 SENSORS_LM78_FAN1 fan1:2
fan2 SENSORS_LM78_FAN2 fan2:2
fan3 SENSORS_LM78_FAN3 fan3:2
fan1_min SENSORS_LM78_FAN1_MIN fan1:1
fan2_min SENSORS_LM78_FAN2_MIN fan2:1
fan3_min SENSORS_LM78_FAN3_MIN fan3:1
temp SENSORS_LM78_TEMP temp:3
temp_hyst SENSORS_LM78_TEMP_HYST temp:2
temp_over SENSORS_LM78_TEMP_OVER temp:1
vid SENSORS_LM78_VID vid:1
fan1_div SENSORS_LM78_FAN1_DIV fan_div:1
fan2_div SENSORS_LM78_FAN2_DIV fan_div:2
fan3_div SENSORS_LM78_FAN3_DIV fan_div:3
alarms SENSORS_LM78_ALARMS alarms:1
Chip `lm78-j'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
in0 SENSORS_LM78J_IN0 in0:3
in1 SENSORS_LM78J_IN1 in1:3
in2 SENSORS_LM78J_IN2 in2:3
in3 SENSORS_LM78J_IN3 in3:3
in4 SENSORS_LM78J_IN4 in4:3
in5 SENSORS_LM78J_IN5 in5:3
in6 SENSORS_LM78J_IN6 in6:3
in0_min SENSORS_LM78J_IN0_MIN in0:1
in1_min SENSORS_LM78J_IN1_MIN in1:1
in2_min SENSORS_LM78J_IN2_MIN in2:1
in3_min SENSORS_LM78J_IN3_MIN in3:1
in4_min SENSORS_LM78J_IN4_MIN in4:1
in5_min SENSORS_LM78J_IN5_MIN in5:1
in6_min SENSORS_LM78J_IN6_MIN in6:1
in0_max SENSORS_LM78J_IN0_MAX in0:2
in1_max SENSORS_LM78J_IN1_MAX in1:2
in2_max SENSORS_LM78J_IN2_MAX in2:2
in3_max SENSORS_LM78J_IN3_MAX in3:2
in4_max SENSORS_LM78J_IN4_MAX in4:2
in5_max SENSORS_LM78J_IN5_MAX in5:2
in6_max SENSORS_LM78J_IN6_MAX in6:2
fan1 SENSORS_LM78J_FAN1 fan1:2
fan2 SENSORS_LM78J_FAN2 fan2:2
fan3 SENSORS_LM78J_FAN3 fan3:2
fan1_min SENSORS_LM78J_FAN1_MIN fan1:1
fan2_min SENSORS_LM78J_FAN2_MIN fan2:1
fan3_min SENSORS_LM78J_FAN3_MIN fan3:1
temp SENSORS_LM78J_TEMP temp:3
temp_hyst SENSORS_LM78J_TEMP_HYST temp:2
temp_over SENSORS_LM78J_TEMP_OVER temp:1
vid SENSORS_LM78J_VID vid:1
fan1_div SENSORS_LM78J_FAN1_DIV fan_div:1
fan2_div SENSORS_LM78J_FAN2_DIV fan_div:2
fan3_div SENSORS_LM78J_FAN3_DIV fan_div:3
alarms SENSORS_LM78J_ALARMS alarms:1
Chip `lm79'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
in0 SENSORS_LM79_IN0 in0:3
in1 SENSORS_LM79_IN1 in1:3
in2 SENSORS_LM79_IN2 in2:3
in3 SENSORS_LM79_IN3 in3:3
in4 SENSORS_LM79_IN4 in4:3
in5 SENSORS_LM79_IN5 in5:3
in6 SENSORS_LM79_IN6 in6:3
in0_min SENSORS_LM79_IN0_MIN in0:1
in1_min SENSORS_LM79_IN1_MIN in1:1
in2_min SENSORS_LM79_IN2_MIN in2:1
in3_min SENSORS_LM79_IN3_MIN in3:1
in4_min SENSORS_LM79_IN4_MIN in4:1
in5_min SENSORS_LM79_IN5_MIN in5:1
in6_min SENSORS_LM79_IN6_MIN in6:1
in0_max SENSORS_LM79_IN0_MAX in0:2
in1_max SENSORS_LM79_IN1_MAX in1:2
in2_max SENSORS_LM79_IN2_MAX in2:2
in3_max SENSORS_LM79_IN3_MAX in3:2
in4_max SENSORS_LM79_IN4_MAX in4:2
in5_max SENSORS_LM79_IN5_MAX in5:2
in6_max SENSORS_LM79_IN6_MAX in6:2
fan1 SENSORS_LM79_FAN1 fan1:2
fan2 SENSORS_LM79_FAN2 fan2:2
fan3 SENSORS_LM79_FAN3 fan3:2
fan1_min SENSORS_LM79_FAN1_MIN fan1:1
fan2_min SENSORS_LM79_FAN2_MIN fan2:1
fan3_min SENSORS_LM79_FAN3_MIN fan3:1
temp SENSORS_LM79_TEMP temp:3
temp_hyst SENSORS_LM79_TEMP_HYST temp:2
temp_over SENSORS_LM79_TEMP_OVER temp:1
vid SENSORS_LM79_VID vid:1
fan1_div SENSORS_LM79_FAN1_DIV fan_div:1
fan2_div SENSORS_LM79_FAN2_DIV fan_div:2
fan3_div SENSORS_LM79_FAN3_DIV fan_div:3
alarms SENSORS_LM79_ALARMS alarms:1
-149
View File
@@ -1,149 +0,0 @@
Kernel driver `lm80.o'
======================
Status: Complete and tested
Supported chips:
* National Semiconductor LM80
Prefix: 'lm80'
Addresses scanned: I2C 0x20 - 0x2f
Author: Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm80: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm80' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the National Semiconductor LM80.
It is descibed as a `Serial Interface ACPI-Compatible Microprocessor
System Hardware Monitor'.
The LM80 implements one temperature sensor, two fan rotation speed sensors,
seven voltage sensors, alarms, and some miscellaneous stuff.
Temperatures are measured in degrees Celsius. There are two sets of limits
which operate independently. When the HOT Temperature Limit is crossed,
this will cause an alarm that will be reasserted until the temperature
drops below the HOT Hysteresis. The Overtemperature Shotdown (OS) limits
should work in the same way (but this must be checked; the datasheet
is unclear about this). Measurements are guaranteed between -55 and
+125 degrees. The current temperature measurement has a resolution of
0.0625 degrees; the limits have a resolution of 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage ensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 2.55 volts, with a resolution
of 0.01 volt.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 2.0 seconds since the last update). This means that you can easily
miss once-only alarms.
The LM80 only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Chip Features
-------------
Chip `lm80'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 NONE RW 0
fan2_min fan2 NONE RW 0
temp NONE NONE R 2
temp_hot_hyst temp temp RW 2
temp_hot_max temp temp RW 2
temp_os_hyst temp temp RW 2
temp_os_max temp temp RW 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
in0 SENSORS_LM80_IN0 in0:3
in1 SENSORS_LM80_IN1 in1:3
in2 SENSORS_LM80_IN2 in2:3
in3 SENSORS_LM80_IN3 in3:3
in4 SENSORS_LM80_IN4 in4:3
in5 SENSORS_LM80_IN5 in5:3
in6 SENSORS_LM80_IN6 in6:3
in0_min SENSORS_LM80_IN0_MIN in0:1
in1_min SENSORS_LM80_IN1_MIN in1:1
in2_min SENSORS_LM80_IN2_MIN in2:1
in3_min SENSORS_LM80_IN3_MIN in3:1
in4_min SENSORS_LM80_IN4_MIN in4:1
in5_min SENSORS_LM80_IN5_MIN in5:1
in6_min SENSORS_LM80_IN6_MIN in6:1
in0_max SENSORS_LM80_IN0_MAX in0:2
in1_max SENSORS_LM80_IN1_MAX in1:2
in2_max SENSORS_LM80_IN2_MAX in2:2
in3_max SENSORS_LM80_IN3_MAX in3:2
in4_max SENSORS_LM80_IN4_MAX in4:2
in5_max SENSORS_LM80_IN5_MAX in5:2
in6_max SENSORS_LM80_IN6_MAX in6:2
fan1 SENSORS_LM80_FAN1 fan1:2
fan2 SENSORS_LM80_FAN2 fan2:2
fan1_min SENSORS_LM80_FAN1_MIN fan1:1
fan2_min SENSORS_LM80_FAN2_MIN fan2:1
temp SENSORS_LM80_TEMP temp:5
temp_hot_hyst SENSORS_LM80_TEMP_HOT_HYST temp:2
temp_hot_max SENSORS_LM80_TEMP_HOT_MAX temp:1
temp_os_hyst SENSORS_LM80_TEMP_OS_HYST temp:4
temp_os_max SENSORS_LM80_TEMP_OS_MAX temp:3
fan1_div SENSORS_LM80_FAN1_DIV fan_div:1
fan2_div SENSORS_LM80_FAN2_DIV fan_div:2
alarms SENSORS_LM80_ALARMS alarms:1
-156
View File
@@ -1,156 +0,0 @@
Kernel driver `lm83.o'
=====================
Status: Complete and lightly tested
Supported chips:
* National Semiconductor LM83
Prefix: 'lm83'
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM83.html
* National Semiconductor LM82
Addresses scanned: I2C 0x18 - 0x1a, 0x29 - 0x2b, 0x4c - 0x4e
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM82.html
Author: Jean Delvare <khali@linux-fr.org>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm83: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm83' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The LM83 is a digital temperature sensor. It senses its own temperature as
well as the temperature of up to three external diodes. The LM82 is
a stripped down version of the LM83 that only supports one external diode.
Both are compatible with many other devices such as the LM84 and all
other ADM1021 clones. The main difference between the LM83 and the LM84
in that the later can only sense the temperature of one external diode.
Using the adm1021 driver for a LM83 should work, but only two temperatures
will be reported instead of four.
The LM83 is only found on a handful of motherboards. Both a confirmed
list and an unconfirmed list follow. If you can confirm or infirm the
fact that any of these motherboards do actually have an LM83, please
contact us. Note that the LM90 can easily be misdetected as a LM83.
Confirmed motherboards:
SBS P014
SBS PSL09
Unconfirmed motherboards:
Gigabyte GA-8IK1100
Iwill MPX2
Soltek SL-75DRV5
The LM82 is confirmed to have been found on most AMD Geode reference
designs and test platforms.
The driver has been successfully tested by Magnus Forsström, who I'd
like to thank here. More testers will be of course welcome.
The fact that the LM83 is only scarcely used can be easily explained.
Most motherboards come with more than just temperature sensors for
health monitoring. They also have voltage and fan rotation speed
sensors. This means that temperature-only chips are usually used as
secondary chips coupled with another chip such as an IT8705F or similar
chip, which provides more features. Since systems usually need three
temperature sensors (motherboard, processor, power supply) and primary
chips provide some temperature sensors, the secondary chip, if needed,
won't have to handle more than two temperatures. Thus, ADM1021 clones
are sufficent, and there is no need for a four temperatures sensor
chip such as the LM83. The only case where using an LM83 would make
sense is on SMP systems, such as the above-mentioned Iwill MPX2,
because you want an additional temperature sensor for each additional
CPU.
On the SBS P014, this is different, since the LM83 is the only hardware
monitoring chipset. One temperature sensor is used for the motherboard
(actually measuring the LM83's own temperature), one is used for the
CPU. The two other sensors must be used to measure the temperature of
two other points of the motherboard. We suspect these points to be the
north and south bridges, but this couldn't be confirmed.
All temperature values are given in degrees Celsius. Local temperature
is given within a range of 0 to +85 degrees. Remote temperatures are
given within a range of 0 to +125 degrees. Resolution is 1.0 degree,
accuracy is guaranteed to 3.0 degrees (see the datasheet for more
details).
Each sensor has its own high limit, but the critical limit is common to
all four sensors. There is no hysteresis mechanism as found on most
recent temperature sensors.
The lm83 driver will not update its values more frequently than every
other second; reading them more often will do no harm, but will return
'old' values.
Chip Features
-------------
Chip 'lm83'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 0
temp1_high temp1 temp1 RW 0
temp2 - - R- 0
temp2_high temp2 temp2 RW 0
temp3 - - R- 0
temp3_high temp3 temp3 RW 0
temp4 - - R- 0
temp4_high temp4 temp4 RW 0
tcrit - - RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp1 SENSORS_LM83_LOCAL_TEMP temp1:2
temp1_high SENSORS_LM83_LOCAL_HIGH temp1:1
temp2 SENSORS_LM83_REMOTE1_TEMP temp2:2
temp2_high SENSORS_LM83_REMOTE1_HIGH temp2:1
temp3 SENSORS_LM83_REMOTE2_TEMP temp3:2
temp3_high SENSORS_LM83_REMOTE2_HIGH temp3:1
temp4 SENSORS_LM83_REMOTE3_TEMP temp4:2
temp4_high SENSORS_LM83_REMOTE3_HIGH temp4:1
tcrit SENSORS_LM83_TCRIT tcrit:1
alarms SENSORS_LM83_ALARMS alarms:1
Chip 'lm82'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 0
temp1_high temp1 temp1 RW 0
temp3 - - R- 0
temp3_high temp3 temp3 RW 0
tcrit - - RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp1 SENSORS_LM83_LOCAL_TEMP temp1:2
temp1_high SENSORS_LM83_LOCAL_HIGH temp1:1
temp3 SENSORS_LM83_REMOTE2_TEMP temp3:2
temp3_high SENSORS_LM83_REMOTE2_HIGH temp3:1
tcrit SENSORS_LM83_TCRIT tcrit:1
alarms SENSORS_LM83_ALARMS alarms:1
-527
View File
@@ -1,527 +0,0 @@
Kernel driver `lm85.o'
======================
Status: Complete and somewhat tested
Supported chips:
* National Semiconductor LM85 (B and C versions)
Prefix: 'lm85'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: http://www.national.com/pf/LM/LM85.html
* Analog Devices ADM1027
Prefix: 'adm1027'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: http://www.analog.com/en/prod/0,,766_825_ADM1027,00.html
* Analog Devices ADT7463
Prefix: 'adt7463'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: http://www.analog.com/en/prod/0,,766_825_ADT7463,00.html
* SMSC EMC6D100, SMSC EMC6D101
Prefix: 'emc6d100'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: http://www.smsc.com/main/tools/discontinued/6d100.pdf
* SMSC EMC6D102, SMSC EMC6D101
Prefix: 'emc6d102'
Addresses scanned: I2C 0x2c, 0x2d, 0x2e
Datasheet: http://www.smsc.com/main/catalog/emc6d102.html
Authors:
Philip Pokorny <ppokorny@penguincomputing.com>
Frodo Looijaard <frodol@dds.nl>
Richard Barrington <rich_b_nz@clear.net.nz>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to assume to be present. Autodetection
of the target device will still be attempted. Use one of the more
specific force directives below if this doesn't detect the device.
* force_lm85b: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm85-B' chip
* force_lm85c: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm85-C' chip
* force_adm1027: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1027' chip
* force_adt7463: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adt7463' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the National Semiconductor LM85
and compatible chips including the Analog Devices ADM1027 and ADT7463,
and SMSC EMC6D100, EMC6D101 and EMC6D102.
The LM85 uses the 2-wire interface compatible with the SMBUS 2.0
specification. Using an analog to digital converter it measures three
(3) temperatures and five (5) voltages. It has four (4) 16-bit
counters for measuring fan speed. Five (5) digital inputs are
provided for sampling the VID signals from the processor to the VRM.
Lastly, there are three (3) PWM outputs that can be used to control
fan speed.
The voltage inputs have internal scaling resistors so that the
following voltage can be measured without external resistors:
2.5V, 3.3V, 5V, 12V, and CPU core voltage (2.25V)
The temperatures measured are one internal diode, and two remote
diodes. Remote 1 is generally the CPU temperature. These inputs are
designed to measure a thermal diode like the one in a Pentium 4
processor in a socket 423 or socket 478 package. They can also
measure temperature using a transistor like the 2N3904.
A sophisticated control system for the PWM outputs is designed into
the LM85 that allows fan speed to be adjusted automatically based on
any of the three temperature sensors. Each PWM output is individually
adjustable and programmable. Once configured, the LM85 will adjust
the PWM outputs in response to the measured temperatures without
further host intervention. This feature can also be disabled for
manual control of the PWM's.
Each of the measured inputs (voltage, temperature, fan speed) has
corresponding high/low limit values. The LM85 will signal an ALARM if
any measured value exceeds either limit.
The LM85 samples all inputs continuously. The lm85 driver will not
read the registers more often than once a second. Further, configuration
data is only read once each 5 minutes. There is twice as much config
data as measurements, so this would seem to be a worthwhile
optimization.
Special Features
The LM85 has four fan speed monitoring modes. The ADM1027 has only
two. Both have special circuitry to compensate for PWM interactions
with the TACH signal from the fans. The ADM1027 can be configured to
measure the speed of a two wire fan, but the input conditioning
circuitry is different for 3-wire and 2-wire mode. For this reason,
the 2-wire fan modes are not exposed to user control. The BIOS should
initialize them to the correct mode. If you've designed your own
ADM1027, you'll have to modify the init_client function and add an
insmod parameter to set this up.
To smooth the response of fans to changes in temperature, the LM85 has
an optional filter for smoothing temperatures. The ADM1027 has the
same config option but uses it to rate limit the changes to fan speed
instead. In libsensors, we associate the smoothing feature with a
zone in the LM85 and with a PWM in the ADM1027.
The ADM1027 and ADT7463 have a 10-bit ADC and can therefore measure
temperatures with 0.25 degC resolution. They also provide an offset to
the temperature readings that is automatically applied during
measurement. This offset can be used to zero out any errors due to
traces and placement. The documentation says that the offset is in
0.25 degC steps, but in initial testing of the ADM1027 it was 1.00
degC steps. Analog Devices has confirmed this "bug". The ADT7463 is
reported to work as described in the documentation. Accordingly, the
driver currently reports 1.00 degC steps for the ADM1027 and 0.25 degC
steps for the ADT7463. If you have an ADT7463, please confirm to the
LM85 maintainer that the temperature offset works as implemented.
The ADT7463 has a THERM asserted counter. This counter has a 22.76ms
resolution and a range of 5.8 seconds. The driver implements a 31-bit
accumulator of the counter value to extend the range to over a year.
The counter will stay at it's max value until read. If you want to
insure no counts get lost, you need to trigger the driver to read the
counter every 5 seconds. You can do this by reading just the
therm_signal value. The driver implements reads of 'therm_signal'
without reading the other values for speed. A background shell script
like:
#!/bin/bash
while [ 1 ] ; do
cat /proc/sys/dev/sensors/adt7463-*/therm_signal > /dev/null
sleep 5
done
Or if you want to record the values:
#!/bin/bash
last=0
while [ 1 ] ; do
read tlim total ovfl < /proc/sys/dev/sensors/adt7463-*/therm_signal
if [ $total -gt $last ]; then
echo $(date) - $tlim $total $ovfl
last=$total
fi
sleep 5;
done
Either script should insure no counts are lost.
See the vendor datasheets for more information. There is application
note from National (AN-1260) with some additional information about
the LM85. The Analog Devices datasheet is very detailed and describes
a procedure for determining an optimal configuration for the automatic
PWM control.
Hardware Configurations
-----------------------
The LM85 can be jumpered for 3 different SMBus addresses. There are
no other hardware configuration options for the LM85.
The lm85 driver detects both LM85B and LM85C revisions of the chip.
See the datasheet for a complete description of the differences.
Other than identifying the chip, the driver behaves no differently
with regard to these two chips. The LM85B is recommended for new
designs.
The ADM1027 and ADT7463 chips have an optional SMBALERT output that
can be used to signal the chipset in case a limit is exceeded or the
temperature sensors fail. Individual sensor interrupts can be masked
with 'alarm_mask' so they won't trigger SMBALERT. The SMBALERT output
if configured replaces one of the other functions (PWM2 or IN0). The
driver will print a message if the BIOS has configured this option
when the chip is detected.
The ADT7463 also has an optional THERM output/input which can be
connected to the processor PROC_HOT output. If available, the autofan
control dynamic Tmin feature can be enabled to keep the system
temperature within spec (just?!) with the least possible fan noise.
Configuration Notes
-------------------
The voltage, temperature and fan inputs are available in libsensors
and the sensors executable. In addition, the automatic control of the
PWM outputs can also be configured via sensors.conf. This
configuration is described below:
* Temperatures and Zones
Each temperature sensor is associated with a Zone. There are three
sensors and therefore three zones. Each zone has the following
temperature configuration points:
Limit temp -- Temperature at which the fan will turn on
[ absolute deg C from 0.0 to 127.0 ]
[ sensors.conf: zone#_limit ]
Hysteresis -- Degrees below the limit that fan will remain
on before turning off.
[ relative C from 0.0 to -15.0 ]
[ sensors.conf: zone#_hyst ]
Range -- Degrees above the limit over which the fan
PWM will be adjusted (from PWM_MIN to 100%)
[ relative deg C from 2.0 to 80.0 ]
[ sensors.conf: zone#_range ]
Critical temp -- Temperature above which other fans will be
forced to 100% PWM
[ absolute deg C from 0.0 to 127.0 ]
[ sensors.conf: zone#_critical ]
** Temperature Range for a Zone
The RANGE value for a given zone can have one of 16 different values
in an approximately logrithmic spacing:
Selecting a RANGE not in this list will cause the next larger range to
be used.
Temperature ranges: 2.0, 2.5, 3.3, 4.0, 5.0, 6.6, 8.0, 10.0,
(in deg C) 13.3, 16.0, 20.0, 26.6, 32.0, 40.0, 53.3,
80.0
* Temperature Smoothing or Acoustic Enhancement
The LM85 and ADM1027 use this value in different ways. In the LM85
it's used to filter the temperature input of a zone. In this way, it
affects all PWM's controlled by that zone temperature. In the
ADM1027, it limits the rate of change in fan speed. Thus it is part
of the PWM control algorithm.
Smooth filter -- Optional filter constant to smooth the system
response to temperature spikes. It either
filters the measured temperatures or limits the
rate of change of fan speed. This improves
the system by limiting rapid changes in fan
speed.
[ seconds from 0.8 to 35.0 or 0.0 for OFF ]
[ sensors.conf: zone#_smooth or pwm#_smooth ]
The smoothing can be disabled, or enabled and set to one of the values
listed below. Selecting a filter time constant not on this list will
cause the next smaller value to be used. Setting the time constant to
0 will disable the filtering.
Filter time constant: 35.0, 17.6, 11.8, 7.0, 4.4, 3.0, 1.6, 0.8
(in seconds)
* PWM Control
There are three PWM outputs. The LM85 datasheet suggests that the
pwm3 output control both fan3 and fan4. Each PWM can be individually
configured and assigned to a zone for it's control value. Each PWM can be
configured individually according to the following options.
Spinup time -- When turning on a fan that was off, the PWM will
temporarily be set to 100% for this length of
time to insure the fan starts spinning.
[ seconds from 0.0 to 4.0 ]
[ sensors.conf: pwm#_spinup ]
Minimum PWM -- This is the PWM value at the limit temp of the
referenced zone.
[ PWM value from 0.0 to 255.0 ]
[ sensors.conf: pwm#_min ]
Frequency -- This is the base frequency of the PWM output.
[ Hz from 10.0 to 98.0 ]
[ sensors.conf: pwm#_freq ]
Min/Off flag -- This flag determines if the PWM will be off or
"min PWM" below the limit temperature.
[ 0.0 means PWM off (0%) below limit ]
[ sensors.conf: pwm#_min_ctl ]
Invert flag -- This flag causes the PWM output to be inverted.
If inverted, a PWM setting of 0 will be full on
and 100 will be off.
[ 0.0 means PWM is "normal" ]
[ sensors.conf: pwm#_invert ]
** Spinup Time
Spinup time can be one of eight (8) values. Selecting a spinup time
not on this list will result in the next larger spinup time being
selected. Note, the value 0.2 actually results in a setting of 0.250
seconds.
Spinup Time 0.0, 0.1, 0.2, 0.4, 0.7, 1.0, 2.0, 4.0
(in seconds)
** PWM Frequency
The base frequency of the PWM can be controlled. The frequency can be
set to one of the following 8 values. Setting the frequency to a
value not on this list, will result in the next higher frequency being
selected. The actual device frequency may vary slightly from this
specification as designed by the manufacturer. Consult the datasheet
for more details.
PWM Frequency 10.0, 15.0, 23.0, 30.0, 38.0, 47.0, 62.0, 98.0
(in Hz)
** Min/Off Flag
Below the temperature "Limit - Hysteresis" from the controlling zone,
the PWM can turn off (set to 0) or it can stay at the minimum PWM
value. Setting this flag to 1 will cause the PWM value to be the
"Minimum PWM" value below this limit. Setting it to 0 will set the
PWM to 0 (off) below that limit.
NOTE: It has been reported that there is a bug in the LM85 that causes
the Min/Off flag to be associated with the zones not the PWMs. This
contradicts all the published documentation. Setting pwm#_min_ctl in
this case actually affects all PWMs controlled by zone '#'.
** Invert Flag
The PWM output can be inverted. This may be useful if the fan power
circuitry is inverted. (A low output turns on power and a high output
turns off power to the fan). Alternatively, if a slower fan speed is
needed for higher temperatures, this could be set.
Setting this flag to 1 will invert the PWM output. A value of 0
does not invert the PWM output.
* PWM Controlling Zone selection
PWM's can be controlled by zones according to the following
configuration choices:
Value Meaning
------ ------------------------------------------------
1 Controlled by Zone 1
2 Controlled by Zone 2
3 Controlled by Zone 3
23 Controlled by higher temp of Zone 2 or 3
123 Controlled by highest temp of Zone 1, 2 or 3
0 PWM alwasy 0% (off)
-1 PWM always 100% (full on)
-2 Manual control (write to 'pwm#' to set)
These are specified in sensors.conf using 'pwm#_zone'
* /proc Interface
These values can also be set using the /proc interface. The following
files contain the above settings in the order listed below:
Name Values
---------- ----------------------------------------------------
zone# Limit, Hysteresis, Range, Critical
pwm#_cfg Spinup, PWM at low limit, Frequency, Min/Off, Invert
pwm#_zone Control Zone choice
smooth# Filtering
The National LM85's have two vendor specific configuration
features. Tach. mode and Spinup Control. For more details on these,
see the LM85 datasheet or Application Note AN-1260.
Name Values
------------ --------------------------------------------------
tach_mode For best fan speed measurement when PWM's are in use,
the Tach Mode should be set to 2 for all three (Fan's
3 and 4 share a config setting) config values. If
PWM's are not in use or not wired to control fan
speed, mode 0 or 1 may be better.
[ sensors.conf: fan#_tach_mode ]
spinup_ctl Setting spinup_ctl to 1 will modify the fan spinup
algorithm. Instead of setting the PWM to 100% for
the configured period of time, it will be set to 100%
until the measured RPM is greater than the RPM limit
register or the time period elapses.
[ sensors.conf: pwm#_spinup_ctl ]
The Analog Devices ADM1027 has several vendor specific enhancements.
The number of pulses-per-rev of the fans can be set, Tach monitoring
can be optimized for PWM operation, and an offset can be applied to
the temperatures to compensate for systemic errors in the
measurements.
Name Values
------------ --------------------------------------------------
tach_mode For best fan speed measurement when PWM's are in use,
the Tach Mode should be set to 0. If PWM's are not
in use or not wired to control fan speed, a mode of 1
can be selected. This will also increase the rate
of fan speed measurement from 1 per second to 4 per
second.
[ sensors.conf: fan#_tach_mode ]
fan_ppr You can set the pulses per rev to 1, 2, 3 or 4 so
that fan speeds don't have to be scaled.
[ sensors.conf: fan#_ppr ]
temp#_offset This is the value to be added or subtracted from the
measured temperature before reporting it. This also
affects the PWM control algorithms.
[ sensors.conf: temp#_offset ]
In addition to the ADM1027 features, the ADT7463 also has Tmin control
and THERM asserted counts. Automatic Tmin control acts to adjust the
Tmin value to maintain the measured temperature sensor at a specified
temperature. There isn't much documentation on this feature in the
ADT7463 data sheet. The following values control this feature.
(These are not available via sensors.conf)
Enable Flag -- When set to 1, the Tmin control is enabled. When
set to 0, Tmin will remain unchanged.
[ 1 means Tmin control is enabled ]
[ sensors.conf: none ]
Control Loop -- This value sets the cycle time of the control
loop which adjusts the Tmin value. The mapping
from raw value to loop period is non-linear.
[ Raw value from 0 to 7 (4sec) ]
[ sensors.conf: none ]
Set on THERM -- If enabled, this causes the operating point to be
set from the current temperature when THERM is
asserted. If THERM is connected to a PROC_HOT
signal, this can automatically set the operating
point to the highest value that is within spec.
allowing the lowest/quietest fan speed possible.
[ 1 means set op-point on THERM asserted ]
[ sensors.conf: none ]
Target Temp -- This is the target temperature. Tmin will be
adjusted to keep the measured temperature at this
value.
[ degC -127 to 127 degC ]
[ sensors.conf: none ]
The THERM signal monitoring has three parameters:
THERM Limit -- How long THERM can be asserted before an ALARM is
triggered.
[ 22.7 ms counts ]
[ sensors.conf: none ]
THERM Total -- How long THERM has been asserted since the driver
was loaded.
[ 22.7 ms counts ]
[ sensors.conf: none ]
Overflows -- How many times the THERM counter was read with a
max value indicating it overflowed.
[ count ]
[ sensors.conf: none ]
These values are available via /proc under the following names in the
listed order.
Name Values
------------ --------------------------------------------------
tmin_ctl# Enable, Control Period, Set on THREM, Target
therm_signal Limit, Total, Overflows
* Sample sensors.conf Config File
Here is a sample LM85 chip config for sensors.conf
--------- cut here ---------
chip "lm85c-*" "adm1027-*" "adt7463-*" "lm85-*" "lm85b-*"
# Voltage inputs
label in0 "V1.5" # AGP on Intel S845WD1-E
label in1 "VCore"
label in2 "V3.3"
label in3 "V5"
label in4 "V12"
# Temperature inputs
label temp1 "CPU"
label temp2 "Board"
label temp3 "Remote"
# Fan inputs
label fan1 "CPU_Fan"
label fan2 "Fan2"
label fan3 "Fan3"
label fan4 "Fan4"
# PWM Outputs
label pwm1 "CPU_PWM"
label pwm2 "Fan2_PWM"
label pwm3 "Fan3_PWM"
# Voltage scaling is done on-chip. No 'compute' directive
# should be necessary. If in0 has external scaling set
# it here.
# compute in0 @ * 2.5, @ / 2.5
# Adjust fans speeds for actual pulses per rev
compute fan1 @ * 2, @ / 2 # 1 pulse per rev
# set fan1_ppr 1 # ADM1027 or ADT7463
compute fan2 @ / 2, @ * 2 # 4 pulse per rev
# set fan2_ppr 4 # ADM1027 or ADT7463
# Set VRM version
set vrm 9.1 # Pentium 4
# Set voltage limits
set in0_min 1.5 * 0.95
set in0_max 1.5 * 1.05
set in1_min vid * 0.95
set in1_max vid * 1.05
set in2_min 3.3 * 0.95
set in2_max 3.3 * 1.05
set in3_min 5.0 * 0.95
set in3_max 5.0 * 1.05
set in4_min 12 * 0.95
set in4_max 12 * 1.05
# Set Fan limits
set fan1_min 3000
set fan2_min 3000
set fan3_min 3000
set fan4_min 3000
-150
View File
@@ -1,150 +0,0 @@
Kernel driver `lm87.o'
======================
Status: Complete and somewhat tested
Supported chips:
* National Semiconductor LM87
Prefix: 'lm87'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: http://www.national.com/pf/LM/LM87.html
Authors: Frodo Looijaard <frodol@dds.nl>,
Philip Edelbrock <phil@netroedge.com>,
Mark Studebaker <mdsxyz123@yahoo.com>,
and Stephen Rousset <stephen.rousset@rocketlogix.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm87: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm87' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver implements support for the National Semiconductor LM87.
The LM87 implements up to three temperature sensors, up to two fan
rotation speed sensors, up to seven voltage sensors, alarms, and some
miscellaneous stuff.
Temperatures are measured in degrees Celsius. Each input has a high
and low alarm settings. A high limit produces an alarm when the value
goes above it, and an alarm is also produced when the value goes below
the low limit.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in
volts. An alarm is triggered if the voltage has crossed a programmable
minimum or maximum limit. Note that minimum in this case always means
'closest to zero'; this is important for negative voltage
measurements.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.0 second since the last update). This means that you can easily
miss once-only alarms.
The lm87 driver only updates its values each 1.0 seconds; reading it more
often will do no harm, but will return 'old' values.
Hardware Configurations
-----------------------
The LM87 has four pins which can serve one of two possible functions,
depending on the hardware configuration. These pins are:
Vccp2/D2- (pin 17)
+2.5V(IN0)/D2+ (pin 18)
FAN1/AIN1 (pin 5)
FAN2/AIN2 (pin 6)
The most common configuration is to use the values shown here on the
left (Vccp2, 2.5V, FAN1, and FAN2). However, for a third temperature
sensor, it is possible to do away with Vccp2 and 2.5V (IN0) for the
second external temp diode inputs. Strangely, it seems that you can
monitor the core voltage of the second processor or its temperature,
but never both at the same time. Also, either/both FAN1 and FAN2
inputs can be substituted for aux analog inputs AIN1 and/or AIN2,
respectively.
To adjust the driver for a hardware configuration other than the
default, simply uncomment the appropriate #defines in the
kernel/chips/lm87.c driver header, and then recompile/reinstall.
Here is a copy (as of this writing) of the header:
--snip--
/* Chip configuration settings. These should be set to reflect the
HARDWARE configuration of your chip. By default (read: when all of
these are left commented out), this driver assumes that the
configuration is the same as National's defaults for the Channel Mode
register.
Set to '1' the appropriate defines, as nessesary:
- External temp sensors 2 (possible second CPU temp)
This will disable the 2.5V and Vccp2 readings.
Strangely, National decided that you can read the
temperature of a second CPU or its core voltage,
but not both! Comment out if FAULT is reported. */
/* #define LM87_EXT2 1 */
/* Aux analog input. When enabled, the Fan 1 reading
will be disabled */
/* #define LM87_AIN1 1 */
/* Aux analog input 2. When enabled, the Fan 2 reading
will be disabled */
/* #define LM87_AIN2 1 */
/* Internal Vcc is 5V instead of 3.3V */
/* #define LM87_5V_VCC 1 */
/* That's the end of the hardware config defines. I would have made
them insmod params, but it would be too much work. ;') */
--snip--
Please note: Only adjust the #defines to reflect your HARDWARE
configuration. Otherwise some, if not all, the readings will be
wrong. Simply enabling different driver configurations does not
provide the desired readings. You must make hardware changes (which
is hard) if you really wish to get a different set of readings!!
If you may suspect that your configuration may be different than the
default, try enabling the different #defines. If you get a 'FAULT'
when using D2, it is most likely that you don't have a second external
diode, so recomment-out the #define. If you get strange readings for
some/all of the other values when enabling a AIN, then recomment-out
that #define.
Unfortunately, it's tough to 'detect' all hardware config's
automaticly. And, I needed to resort to using #defines instead of
insmod params to make life easier on myself.
-223
View File
@@ -1,223 +0,0 @@
Kernel driver `lm90.o'
======================
Status: Complete and well tested
Supported chips:
* National Semiconductor LM90
Prefix: 'lm90'
Addresses scanned: I2C 0x4c
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM90.html
* National Semiconductor LM89
Prefix: 'lm99'
Addresses scanned: I2C 0x4c and 0x4d
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM89.html
* National Semiconductor LM99
Prefix: 'lm99'
Addresses scanned: I2C 0x4c and 0x4d
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM99.html
* National Semiconductor LM86
Prefix: 'lm86'
Addresses scanned: I2C 0x4c
Datasheet: Publicly available at the National Semiconductor website
http://www.national.com/pf/LM/LM86.html
* Analog Devices ADM1032
Prefix: 'adm1032'
Addresses scanned: I2C 0x4c and 0x4d
Datasheet: Publicly available at the Analog Devices website
http://products.analog.com/products/info.asp?product=ADM1032
* Analog Devices ADT7461
Prefix: 'adt7461'
Addresses scanned: I2C 0x4c and 0x4d
Datasheet: Publicly available at the Analog Devices website
http://products.analog.com/products/info.asp?product=ADT7461
Note: Only if in ADM1032 compatibility mode
* Maxim MAX6657
Prefix: 'max6657'
Addresses scanned: I2C 0x4c
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578
* Maxim MAX6658
Prefix: 'max6657'
Addresses scanned: I2C 0x4c
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578
* Maxim MAX6659
Prefix: 'max6657'
Addresses scanned: I2C 0x4c and 0x4d
Datasheet: Publicly available at the Maxim website
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578
Author: Jean Delvare <khali@linux-fr.org>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm90: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm90' chip
* force_adm1032: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adm1032' chip
* force_lm99: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm99' chip
* force_lm86: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm86' chip
* force_max6657: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `max6657' chip
* force_adt7461: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `adt7461' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The LM90 is a digital temperature sensor. It senses its own temperature as
well as the temperature of up to one external diode. It is compatible
with many other devices such as the LM86, the LM89, the LM99, the ADM1032,
the MAX6657, the MAX6658, and the MAX6659, all of which are supported by
this driver. Additionally, the ADT7461 is supported if found in ADM1032
compatibility mode.
The specificity of this family of chipsets over the ADM1021/LM84
family is that it features critical limits with hysteresis, and an
increased resolution of the remote temperature measurement.
The different chipsets of the family are not strictly identical, although
very similar. This driver doesn't handle any specific feature for now,
with the exception of SMBus PEC. For reference, here comes a non-exhaustive
list of specific features:
LM90:
* Filter and alert configuration register at 0xBF.
* ALERT is triggered by temperatures over critical limits.
LM86 and LM89:
* Same as LM90
* Better external channel accuracy
LM99:
* Same as LM89
* External temperature shifted by 16 degrees down
ADM1032:
* Consecutive alert register at 0x22.
* Conversion averaging.
* Up to 64 conversions/s.
* ALERT is triggered by open remote sensor.
* SMBus PEC support for Write Byte and Receive Byte transactions.
ADT7461
* Extended temperature range (breaks compatibility)
* Lower resolution for remote temperature
MAX6657 and MAX6658:
* Remote sensor type selecion
MAX6659
* Selectable address
* Second critical temperature limit
All temperature values are given in degrees Celsius. Resolution
is 1.0 degree for the local temperature, 0.125 degree for the remote
temperature.
Each sensor has its own high and low limits, plus a critical limit.
Additionally, there is a relative hysteresis value common to both critical
values. To make life easier to user-space applications, two absolute values
are exported, one for each channel, but these values are of course linked.
Only the local hysteresis can be set from user-space, and the same delta
applies to the remote hysteresis.
The lm90 driver will not update its values more frequently than every
other second; reading them more often will do no harm, but will return
'old' values.
PEC Support
-----------
The ADM1032 is the only chip of the family which supports PEC. It does
not support PEC on all transactions though, so some care must be taken.
When reading a register value, the PEC byte is computed and sent by the
ADM1032 chip. However, in the case of a combined transaction (SMBus Read
Byte), the ADM1032 computes the CRC value over only the second half of
the message rather than its entirety, because it thinks the first half
of the message belongs to a different transaction. As a result, the CRC
value differs from what the SMBus master expects, and all reads fail.
For this reason, the lm90 driver will enable PEC for the ADM1032 only if
the bus supports the SMBus Send Byte and Receive Byte transaction types.
These transactions will be used to read register values, instead of
SMBus Read Byte, and PEC will work properly.
Additionally, the ADM1032 doesn't support SMBus Send Byte with PEC.
Instead, it will try to write the PEC value to the register (because the
SMBus Send Byte transaction with PEC is similar to a Write Byte transaction
without PEC), which is not what we want. Thus, PEC is explicitly disabled
on SMBus Send Byte transactions in the lm90 driver.
PEC on byte data transactions represents a significant increase in bandwidth
usage (+33% for writes, +25% for reads) in normal conditions. With the need
to use two SMBus transaction for reads, this overhead jumps to +50%. Worse,
two transactions will typically mean twice as much delay waiting for
transaction completion, effectively doubling the register cache refresh time.
I guess reliability comes at a price, but it's quite expensive this time.
So, as not everyone might enjoy the slowdown, PEC can be disabled through
procfs. Just write 0 to the "pec" file and PEC will be disabled. Write 1
to that file to enable PEC again.
Chip Features
-------------
Chips 'lm90', adm1032', 'lm99', 'lm86', 'max6657' and 'adt7461'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 0
temp1_high temp1 temp1 RW 0
temp1_low temp1 temp1 RW 0
temp2 - - R- 1
temp2_high temp2 temp2 RW 1
temp2_low temp2 temp2 RW 1
tcrit1 temp1 temp1 RW 0
tcrit2 temp2 temp2 RW 0
hyst1 temp1 temp1 RW 0
hyst2 temp2 temp2 R- 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp1 SENSORS_LM90_LOCAL_TEMP temp1:3
temp1_high SENSORS_LM90_LOCAL_HIGH temp1:1
temp1_low SENSORS_LM90_LOCAL_LOW temp1:2
temp2 SENSORS_LM90_REMOTE_TEMP temp2:3
temp2_high SENSORS_LM90_REMOTE_HIGH temp2:1
temp2_low SENSORS_LM90_REMOTE_LOW temp2:2
tcrit1 SENSORS_LM90_LOCAL_TCRIT tcrit1:1
tcrit2 SENSORS_LM90_REMOTE_TCRIT tcrit2:1
hyst1 SENSORS_LM90_LOCAL_TCRIT_HYST hyst1:1
hyst2 SENSORS_LM90_REMOTE_TCRIT_HYST hyst2:1
alarms SENSORS_LM90_ALARMS alarms:1
-89
View File
@@ -1,89 +0,0 @@
Kernel driver `lm92.o'
======================
Status: Complete and somewhat tested
Supported chips:
* National Semiconductor LM92
Prefix: 'lm92'
Addresses scanned: I2C 0x48 - 0x4b
* National Semiconductor LM76
Prefix: 'lm92'
Addresses scanned: none, force parameter needed
* Maxim MAX6633/MAX6634/MAX6635
Prefix: 'lm92'
Addresses scanned: I2C 0x48 - 0x4f
MAX6633 with address in 0x40 - 0x47 needs force parameter
Author: Abraham van der Merwe <abraham@2d3d.co.za>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_lm92: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `lm92' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
This driver implements support for the National Semiconductor LM92
temperature sensor.
Each LM92 temperature sensor supports a single temperature sensor.
Temperatures are exported to proc in degrees Celsius * 10000 (in other
words temperatures are accurate to 4 decimal places). There are alarms
for high, low, and critical thresholds. There's also an hysteresis to
control the thresholds for resetting alarms.
At the moment the driver controls these sensors in comparator mode and the
interrupt pins (INT, T_CRIT_A) are ignored.
Support was added later for the LM76 and Maxim MAX6633/MAX6634/MAX6635,
which are mostly compatible. They have not all been tested, so you
may need to use the force parameter.
Hardware Configurations
-----------------------
The only thing that can be changed is enabling of the fault queue. Doing so
ensures that alarms will only be triggered if 4 consecutive readings all
cross the alarm thresholds. You can enable this behaviour by uncommenting
the #define in the driver source, and then recompile/reinstall.
Chip Features
-------------
Chip 'lm92'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 4
temp_high temp temp RW 4
temp_low temp temp RW 4
temp_crit temp temp RW 4
temp_hyst temp temp RW 4
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_LM92_TEMP temp:1
temp_high SENSORS_LM92_TEMP_HIGH temp:2
temp_low SENSORS_LM92_TEMP_LOW temp:3
temp_crit SENSORS_LM92_TEMP_CRIT temp:4
temp_hyst SENSORS_LM92_TEMP_HYST temp:5
alarms SENSORS_LM92_ALARMS alarms:1
-386
View File
@@ -1,386 +0,0 @@
Kernel driver 'lm93.o'
======================
Status: In progress
Supported chips:
* National Semiconductor LM93
Prefix: 'lm93'
Addresses scanned: I2C 0x2c-0x2e
Author:
Mark M. Hoffman <mhoffman@lightlink.com>
Module Parameters
-----------------
(specific to LM93)
* init: integer
Set to zero to disable some initializations (default is 1)
* disable_block: integer
A "0" allows SMBus block data transactions if the host supports them. A "1"
disables SMBus block data transactions. The default is 0.
* vccp_limit_type: integer array (2)
Configures in7 and in8 limit type, where 0 means absolute and non-zero
means relative. "Relative" here refers to "Dynamic Vccp Monitoring using
VID" from the datasheet. It greatly simplifies the interface to allow
only one set of limits (absolute or relative) to be in operation at a
time (even though the hardware is capable of enabling both). There's
not a compelling use case for enabling both at once, anyway. The default
is "0,0".
* vid_agtl: integer
A "0" configures the VID pins for V(ih) = 2.1V min, V(il) = 0.8V max.
A "1" configures the VID pins for V(ih) = 0.8V min, V(il) = 0.4V max.
(The latter setting is referred to as AGTL+ Compatible in the datasheet.)
I.e. this parameter controls the VID pin input thresholds; if your VID
inputs are not working, trying changing this. The default value is "0".
(common among sensor drivers)
* force: short array (min = 1, max = 48)
List of adapter,address pairs to assume to be present. Autodetection
of the target device will still be attempted. Use one of the more
specific force directives below if this doesn't detect the device.
* force_lm93: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a 'lm93' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Hardware Description
--------------------
(from the datasheet)
The LM93, hardware monitor, has a two wire digital interface compatible with
SMBus 2.0. Using an 8-bit ADC, the LM93 measures the temperature of two remote
diode connected transistors as well as its own die and 16 power supply
voltages. To set fan speed, the LM93 has two PWM outputs that are each
controlled by up to four temperature zones. The fancontrol algorithm is lookup
table based. The LM93 includes a digital filter that can be invoked to smooth
temperature readings for better control of fan speed. The LM93 has four
tachometer inputs to measure fan speed. Limit and status registers for all
measured values are included. The LM93 builds upon the functionality of
previous motherboard management ASICs and uses some of the LM85's features
(i.e. smart tachometer mode). It also adds measurement and control support
for dynamic Vccp monitoring and PROCHOT. It is designed to monitor a dual
processor Xeon class motherboard with a minimum of external components.
User Interface
--------------
#PROCHOT:
The LM93 can monitor two #PROCHOT signals. The results are found in the
/proc files prochot1 and prochot2. There are three values per file: the first
value is the user limit. The second value is the current reading for the
most recent complete time interval. The third value is something like a
2 period exponential moving average (but not quite - check the datasheet).
Note that this third value is calculated by the chip itself. All values
range from 0-255 where 0 indicates no throttling, and 255 indicates > 99.6%.
The monitoring intervals for the two #PROCHOT signals is also configurable.
These intervals can be found in the /proc file prochot_interval. There are
two values in this file: one each for #P1_PROCHOT and #P2_PROCHOT, res-
pectively. Selecting a value not in this list will cause the driver to
use the next largest interval. The available intervals are (in seconds):
#PROCHOT intervals: 0.73, 1.46, 2.9, 5.8, 11.7, 23.3, 46.6, 93.2, 186, 372
It is possible to configure the LM93 to logically short the two #PROCHOT
signals. I.e. when #P1_PROCHOT is asserted, the LM93 will automatically
assert #P2_PROCHOT, and vice-versa. This mode is enabled by writing a
non-zero integer to the /proc file prochot_short.
The LM93 can also override the #PROCHOT pins by driving a PWM signal onto
one or both of them. When overridden, the signal has a period of 3.56 ms,
a minimum pulse width of 5 clocks (at 22.5kHz => 6.25% duty cycle), and
a maximum pulse width of 80 clocks (at 22.5kHz => 99.88% duty cycle).
The /proc file prochot_override has three values. The first two are boolean
integers which enable/disable the override funtion for #P1_PROCHOT and
#P2_PROCHOT, the third value is 0-15 where 0 indicates minimum duty cycle
and 15 indicates maximum.
#VRD_HOT:
The LM93 can monitor two #VRD_HOT signals. The results are found in the
/proc files vrdhot1 and vrdhot2. There is one value per file: a boolean for
which 1 indicates #VRD_HOT is asserted and 0 indicates it is negated. These
files are read-only.
Smart Tach Mode:
(from the datasheet)
If a fan is driven using a low-side drive PWM, the tachometer
output of the fan is corrupted. The LM93 includes smart tachometer
circuitry that allows an accurate tachometer reading to be
achieved despite the signal corruption. In smart tach mode all
four signals are measured within 4 seconds.
Smart tach mode is enabled by the driver by writing 1 or 2 (associating the
the fan tachometer with a pwm) to the /proc file fan<n>_smart_tach. A zero
will disable the function for that fan. Note that Smart tach mode cannot be
enabled if the PWM output frequency is 22500 Hz (see below).
Manual PWM:
The LM93 has a fixed or override mode for the two PWM outputs (although, there
are still some conditions that will override even this mode - see section
15.10.6 of the datasheet for details.) The /proc files pwm1 and pwm2 are
used to enable and control this mode. The first value of the file is an
integer (0-255) where 0 is 0% duty cycle, and 255 is 100%. The second value
is a boolean integer where 0 disables and 1 enables the manual control mode.
Note that the duty cycle values are constrained by the hardware. Selecting
a value which is not available will cause the driver to use the next largest
value. Also note: when manual PWM mode is disabled, the first value of the
/proc files indicates the current duty cycle chosen by the h/w.
PWM Output Frequency:
The LM93 supports several different frequencies for the PWM output channels.
The /proc files pwm1_freq and pwm2_freq are used to select the frequency. The
frequency values are constrained by the hardware. Selecting a value which is
not available will cause the driver to use the next largest value. Also note
that this parameter has implications for the Smart Tach Mode (see above).
PWM Output Frequencies: 12, 36, 48, 60, 72, 84, 96, 22500 Hz (h/w default)
Automatic PWM:
The LM93 is capable of complex automatic fan control, with many different
points of configuration. To start, each PWM output can be bound to any
combination of eight control sources. The final PWM is the largest of all
individual control sources to which the PWM output is bound.
The eight control sources are: temp1-temp4 (aka "zones" in the datasheet),
#PROCHOT 1 & 2, and #VRDHOT 1 & 2. The bindings are expressed as a bitmask
in the /proc files pwm<n>_auto_channels, where a "1" enables the binding, and
a "0" disables it. The h/w default is 0x0f (all temperatures bound).
0x01 - Temp 1
0x02 - Temp 2
0x04 - Temp 3
0x08 - Temp 4
0x10 - #PROCHOT 1
0x20 - #PROCHOT 2
0x40 - #VRDHOT 1
0x80 - #VRDHOT 2
The function y = f(x) takes a source temperature x to a PWM output y. This
function of the LM93 is derived from a base temperature and a table of 12
temperature offsets. The base temperature is expressed in degrees C in the
/proc files temp<n>_auto_base. The offsets are expressed in cumulative
degrees C, with all 12 values in the files temp<n>_auto_offsets. E.g. if the
base temperature is 40C:
offset # temp<n>_auto_offsets range pwm
1 0 - 25.00%
2 0 - 28.57%
3 1 40C - 41C 32.14%
4 1 41C - 42C 35.71%
5 2 42C - 44C 39.29%
6 2 44C - 46C 42.86%
7 2 48C - 50C 46.43%
8 2 50C - 52C 50.00%
9 2 52C - 54C 53.57%
10 2 54C - 56C 57.14%
11 2 56C - 58C 71.43%
12 2 58C - 60C 85.71%
> 60C 100.00%
Valid offsets are in the range 0C <= x <= 7.5C in 0.5C increments.
There is an independent base temperature for each temperature channel. Note,
however, there are only two tables of offsets: one each for temp[12] and
temp[34]. Therefore, any change to e.g. temp1_auto_offsets will also affect
temp2_auto_offsets.
The LM93 can also apply hysteresis to the offset table, to prevent unwanted
oscillation between two steps in the offsets table. These values are found in
the /proc files temp<n>_auto_offset_hyst. There is just one value; it has the
same representation as in temp<n>_auto_offsets.
If a temperature reading falls below the base value for that channel, the LM93
will use the minimum PWM value. These values are found in the /proc files
temp<n>_auto_pwm_min. Note, there are only two minimums: one each for temp[12]
and temp[34]. Therefore, any change to e.g. temp1_auto_pwm_min will also
affect temp2_auto_pwm_min.
PWM Spin-Up Cycle:
A spin-up cycle occurs when a PWM output is commanded from 0% duty cycle to
some value > 0%. The LM93 supports a minimum duty cycle during spin-up. These
values are found in the /proc files pwm<n>_auto_spinup_min. There is one value
per file; it has the same representation as other PWM duty cycle values. The
duration of the spin-up cycle is also configurable. These values are found in
the /proc files pwm<n>_auto_spinup_time. There is one value per file; it is
the spin-up time in seconds. The available spin-up times are constrained by
the hardware. Selecting a value which is not available will cause the driver
to use the next largest value.
Spin-up Durations: 0 (disabled, h/w default), 0.1, 0.25, 0.4, 0.7, 1.0, 2.0, 4.0
#PROCHOT and #VRDHOT PWM Ramping:
If the #PROCHOT or #VRDHOT signals are asserted while bound to a PWM output
channel, the LM93 will ramp the PWM output up to 100% duty cycle in discrete
steps. The duration of each step is configurable. There are two files, with
one value each in seconds: pwm_auto_prochot_ramp and pwm_auto_vrdhot_ramp.
The available ramp times are constrained by the hardware. Selecting a value
which is not available will cause the driver to use the next largest value.
Ramp Times: 0 (disabled, h/w default) to 0.75 in 0.05 second intervals
Fan Boost:
For each temperature channel, there is a boost temperature: if the channel
exceeds this limit, the LM93 will immediately drive both PWM outputs to 100%.
This limit is expressed in degrees C in the /proc files temp<n>_auto_boost.
There is also a hysteresis temperature for this function: after the boost
limit is reached, the temperature channel must drop below this value before
the boost function is disabled. This temperature is also expressed in degrees
C in the /proc files temp<n>_auto_boost_hyst.
GPIO Pins:
The LM93 can monitor the logic level of four dedicated GPIO pins as well as the
four tach input pins. GPIO0-GPIO3 correspond to (fan) tach 1-4, respectively.
All eight GPIOs are read by reading the bitmask in the /proc file gpio. The
LSB is GPIO0, and the MSB is GPIO7.
LM93 Unique /proc Files
-----------------------
file description
-------------------------------------------------------------
prochot<n> limit, current, and moving average #PROCHOT %
prochot_short enable or disable logical #PROCHOT pin short
prochot_override force #PROCHOT assertion as PWM
prochot_interval #PROCHOT PWM sampling interval
vrdhot<n> 0 means negated, 1 means asserted
fan<n>_smart_tach enable or disable smart tach mode
pwm<n>_auto_spinup_min minimum duty cycle during spin-up
pwm<n>_auto_spinup_time duration of spin-up
pwm_auto_prochot_ramp ramp time per step when #PROCHOT asserted
pwm_auto_vrdhot_ramp ramp time per step when #VRDHOT asserted
temp<n>_auto_base temperature channel base
temp<n>_auto_offsets temperature channel offsets
temp<n>_auto_offset_hyst
temperature channel offset hysteresis
temp<n>_auto_boost temperature channel boost (PWMs to 100%) limit
temp<n>_auto_boost_hyst temperature channel boost hysteresis
gpio input state of 8 GPIO pins; read-only
Sample Configuration File
-------------------------
Here is a sample LM93 chip config for sensors.conf:
---------- cut here ----------
chip "lm93-*"
# VOLTAGE INPUTS
# labels and scaling based on datasheet recommendations
label in1 "+12V1"
compute in1 @ * 12.945, @ / 12.945
set in1_min 12 * 0.90
set in1_max 12 * 1.10
label in2 "+12V2"
compute in2 @ * 12.945, @ / 12.945
set in2_min 12 * 0.90
set in2_max 12 * 1.10
label in3 "+12V3"
compute in3 @ * 12.945, @ / 12.945
set in3_min 12 * 0.90
set in3_max 12 * 1.10
label in4 "FSB_Vtt"
label in5 "3GIO"
label in6 "ICH_Core"
label in7 "Vccp1"
label in8 "Vccp2"
label in9 "+3.3V"
set in9_min 3.3 * 0.90
set in9_max 3.3 * 1.10
label in10 "+5V"
set in10_min 5.0 * 0.90
set in10_max 5.0 * 1.10
label in11 "SCSI_Core"
label in12 "Mem_Core"
label in13 "Mem_Vtt"
label in14 "Gbit_Core"
# Assuming R1/R2 = 4.1143, and 3.3V reference
# -12V = (4.1143 + 1) * (@ - 3.3) + 3.3
label in15 "-12V"
compute in15 @ * 5.1143 - 13.57719, (@ + 13.57719) / 5.1143
set in15_min -12 * 0.90
set in15_max -12 * 1.10
label in16 "+3.3VSB"
set in16_min 3.3 * 0.90
set in16_max 3.3 * 1.10
# TEMPERATURE INPUTS
label temp1 "CPU1"
label temp2 "CPU2"
label temp3 "LM93"
# TACHOMETER INPUTS
label fan1 "Fan1"
set fan1_min 3000
label fan2 "Fan2"
set fan2_min 3000
label fan3 "Fan3"
set fan3_min 3000
label fan4 "Fan4"
set fan4_min 3000
# PWM OUTPUTS
label pwm1 "CPU1"
label pwm2 "CPU2"
-67
View File
@@ -1,67 +0,0 @@
Kernel driver `ltc1710.o'
=========================
Status: Complete and well-tested
Supported chips:
* Linear Technologies LTC1710
Prefix: 'ltc1710'
Addresses scanned: I2C 0x58 - 0x5a
Datasheet: Publicly available at the Linear Technologies website
Author: Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock
<phil@netroedge.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_ltc1710: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `ltc1710' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This is some documenation for the ltc1710 driver for the Linear
Technologies LTC1710.
The LTC1710 is a simple dual switch device with a SMBus interface. It can
switch a supply voltage to either of its two output pins independantly
from the built-in SMBus interface.
For each sensed LTC1710 there is a directory which looks something like:
ltc1710-i2c-1-59
For each directory there are the following files:
* switch1 (LTC1710_SYSCTL_SWITCH_1)
Simply contains either a 1 or 0 depending on whether the switch is
currently turned on or off.
* switch2 (LTC1710_SYSCTL_SWITCH_2)
Works the same way as switch1, but is for the second switch.
Some notes: The LTC1710 is a *very* simple I2C/SMBus device. It does not
support reads, so the status of the switches isn't polled, but rather
remembered. The LTC1710 has enough 'umph' to drive 300mA with only 0.4 Ohm
impedence (in an 'on' state). This works well for LEDs, or to drive other
devices (MOSFETs and such) which can multiply the switching effect. The
LTC1710 also contains an emergency over-temperature shut-off in the case
of too much current flow. Switch 2 switches the Vcc voltage on/off, but
Switch 1 switches the voltage present on pin 1 (0V to Vcc).
This driver was written and maintained by Philip Edelbrock
<phil@netroedge.com>. Please send comments/suggestions/corrections to him.
For more technical information on the LTC1710, please get the Acrobat PDF
from http://www.linear-tech.com/
-97
View File
@@ -1,97 +0,0 @@
Kernel driver `matorb.o'
=======================
Status: 'Beta', but it's so simple that it may be in its final state
Supported devices:
* Matrix-Orbital (www.matrix-orbital.com) displays. Developed and
tested on the LCD2041V, but should work with all their displays.
Prefix: 'matorb'
Addresses scanned: I2C 0x2E
Datasheet: Publicly available at the Matrix-Orbital web site
(www.matrix-orbital.com)
Author: Frodo Looijaard <frodol@dds.nl>, and Philip Edelbrock
<phil@netroege.com>
Main Contact: Philip Edelbrock <phil@netroedge.com>
Module Parameters
-----------------
(I'm not positive about these, so don't take this as granite)
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_matorb: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `bt869' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
This driver provides access to the Matrix-Orbital display devices. These
devices are usually in the form of a small LCD (20x4 is typical) screen
which is mounted in a carrier which is then mounted into a 5.25" drive bay
of a computer.
This driver allows the user to send any data to the display to control or
display anything which the display supports.
For more information on the displays or of particular features/command sets
go to:
http://www.matrix-orbital.com
Use
---
To use, just redirect the base-10 ordinal value of bytes (or chars) to the
display. For example, to clear the screen and write a simple message, you
could do this:
# To clear the screen, send byte 0xFE and the char 'X' (ordinal 88)
echo "254 88" > /proc/sys/dev/sensors/matorb*/disp
# To display "hi there"
echo "hi there" \
| perl -e '$_=<STDIN>; while (/(.)/gc) { print ord($1)," "; }' \
> /proc/sys/dev/sensors/matorb*/disp
I've written a simple sample program (in Perl) to display a full screen
of text (which is fed in on standard-input) to the display. It is in the
prog/matorb folder of this project.
I think the displays are very attractive and add a neat sort of 'status'
display to things like servers. It would be very simple to either
make a script daemon (or modify the healthd provided in this project)
to display a current status of health info (voltages, fans, temps, etc.)
on the display. Let's see Windoze do *THAT*! :')
Issues
------
Redirecting too long of a string (32 ordinals seem to be the max) to 'disp'
seems to cause problems. Keep things in small chunks (like lines).
Built in functions (screen clearing, etc.) were initially planned to be
added as other device files (other than just 'disp'), but these functions
are just as easily done by sending the command sequences to disp (as shown
above when clearing the screen). This also ensures that the driver will
be compatible with all the displays.
Detection is very poor (as of this writing, anyway). It looks for a device
at 0x2E and asumes it is a display. Unfortunately, the 'beta' firmware
the Matrix-Orbital people gave me doesn't have good detection abilities to
it (it ignores reads... it used to lock up the bus on reads, so at least
it doesn't do that any more! :'). If they provide an easy way to 'sense'
that it is a display (and what kind it is?) then I will add support for it.
Let me know if you have problems which you can't figure out
(phil@netroedge.com).
-73
View File
@@ -1,73 +0,0 @@
Kernel driver `max1619.o'
=========================
Status: New
Supported chips:
* Maxim MAX1619
Prefix: 'max1619'
Addresses scanned: I2C 0x18-0x1a, 0x29-0x2b, 0x4c-0x4e
Datasheet: Publicly available at the Maxim website
http://pdfserv.maxim-ic.com/en/ds/MAX1619.pdf
Author: Alexey Fisher <fishor@mail.ru>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_max1619: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `max1619' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The MAX1619 is a digital temperature sensor. It senses its own temperature as
well as the temperature of up to one external diode.
All temperature values are given in degrees Celsius. Resolution
is 1.0 degree for the local temperature and for the remote temperature.
Only the external sensor has high and low limits.
The max1619 driver will not update its values more frequently than every
other second; reading them more often will do no harm, but will return
'old' values.
Chip Features
-------------
Chips 'max1619'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp1 - - R- 0
temp2 - - R- 0
temp2_max temp2 temp2 RW 0
temp2_min temp2 temp2 RW 0
temp2_crit temp2 temp2 RW 0
temp2_hyst temp2 temp2 RW 0
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp1 SENSORS_MAX1619_LOCAL_TEMP temp1:1
temp2 SENSORS_MAX1619_REMOTE_TEMP temp2:3
temp2_max SENSORS_MAX1619_REMOTE_HIGH temp2:1
temp2_min SENSORS_MAX1619_REMOTE_LOW temp2:2
temp2_crit SENSORS_MAX1619_REMOTE_HYST temp2_crit:1
temp2_hyst SENSORS_MAX1619_REMOTE_MAX temp2_crit:2
alarms SENSORS_MAX1619_ALARMS alarms:1
-125
View File
@@ -1,125 +0,0 @@
Sensors chip driver for MAXIM MAX6650 / MAX6651 fan controllers
===============================================================
Chip notes
----------
The MAX6651 is a fan controller. It has two distinct functions:
1) Speed control, with feedback, for a single fan. The code can write
to a register requesting that the fan run at a certain speed, and the
chip will adjust the fan voltage until it reaches that speed. This
is called "closed loop" mode in the datasheet.
2) Speed measurement (tachometers) for up to four fans, including the
controlled one.
The MAX6650 is similar but only has one measurement tachometer.
Both chips have a few general purpose i/o pins, some of which can be
configured to have special functions. At the time of writing an overview
of the chip, with links to the datasheet, can be seen at:
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2296/ln/en
If that has moved searching for MAX6650 from Maxim's home page should
work.
Driver state
------------
Currently the driver provides the following entries in /proc/sys/
dev/sensors/max6650-xxx/:
fan1 through fan4 (read only): the current measured fan speeds, in
rpm.
speed (read/write): when written, sets the chip to closed loop mode and
sets the configured speed, in rpm, to the value written. When read returns
the configured speed, again in rpm.
xdump (read only): returns all of the chip register values, except the
tachometers. Used for debug, and should probably go away or be conditionally
compiled.
The MAX665x uses byte wide registers with various scaling factors and
some more or less complex equations (in the datasheet) relating register
values to real world values. The driver takes care of all of this.
Note that the measured speed ("fan1") never exactly matches the set speed
("speed") because of the coarse resolution of the underlying registers.
Assumptions
-----------
The driver assumes that:
o The MAX665x is running from its internal 254 kHz clock, not an
external clock.
o The monitored fans produce two pulses per rotation (This same
assumption is made by Maxim throughout the datasheet.)
o The tachometer count time (register 0x16) has been configured suitably
for the fans in use, perhaps by some boot code. The driver reads this
register to compute rpm, but never writes to it.
o The speed controller prescale (low three bits of register 0x04) has
been configured suitably for the fans in use, perhaps by some boot
code. The driver reads these bits to compute rpm, but never changes
them.
The driver currently never reads or writes any other configuration
(gpio config, alarms, 5V/12V setting).
Bugs
----
o The driver assumes too much: it should be possible to set more of the
configuration dynamically instead of assuming that some other code has
done it.
o It does not differentiate between a MAX6650 and a MAX6651, as I can
see no obvious way to do this. It has only been tested with a MAX6651
so if anyone gets a board with a MAX6650 to test with maybe something
will be revealed, such as the three missing tachometer registers
returning all ones or something. (OK, so maybe I should have called
this driver max6651, and ignored the max6650 totally...)
o The /proc names don't match the standards in doc/developers/proc,
as this chip doesn't map well to other fan controllers,
Detection notes
---------------
The module will autodetect the presence of a max6650 or max6651 at any
of the four possible I2C addresses for these devices. There is no ID
register, but there are several registers with all zero bits at the
high end. The driver checks that all of these are in fact zero before
declaring the chip to be a MAX6650/1.
sensors-detect
--------------
Support for MAX6650/1 has been added to sensors-detect. It has been
tested as correctly detecting a MAX6651 at I2C address 0x1B. Like the
driver, it cannot yet distinguish a MAX6650 and MAX6651.
prog/sensors
------------
Initial read-only support has been added to the "sensors" program.
Testing
-------
The driver and associated programs have been tested under Linux 2.4.18
running on an embedded Intel XScale processor (actually an IXP425 running
in big-endian mode) with a MAX6651 chip.
Author
------
John Morris (john.morris@spirentcom.com)
-433
View File
@@ -1,433 +0,0 @@
Kernel driver `maxilife.o'
==========================
Status: Mostly done and tested
Supported chips:
* Hewlett Packard Maxilife Revision AS 00.01 for Single Pentium II Slot 1
Prefix: 'maxilife-as'
Addresses scanned: I2C 0x10 - 0x14
Datasheet: Only available under NDA
* Hewlett Packard Maxilife Revision CO 00.03 for Dual Xeon Slot 2
Prefix: 'maxilife-co'
Addresses scanned: I2C 0x10 - 0x14
Datasheet: Only available under NDA
* Hewlett Packard Maxilife Revision CG 00.04 for Dual Pentium II Slot 1
Prefix: 'maxilife-cg'
Addresses scanned: I2C 0x10 - 0x14
Datasheet: Only available under NDA
* Hewlett Packard NBA (New BIOS Architecture) for newer machines,
for example Kayak XU800 and XM600.
Prefix: 'maxilife-nba'
Addresses scanned: I2C 0x10 - 0x14
Datasheet: Only available under NDA
Author: Fons Rademakers <Fons.Rademakers@cern.ch>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_maxilife: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `maxilife' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* maxi_version: int
MaxiLife firmware version
Description
-----------
The is the driver for the HP MaxiLife Health monitoring system
as used in the line of HP Kayak Workstation PC's.
The driver supports the following MaxiLife firmware versions:
0) HP KAYAK XU/XAs (Dual Pentium II Slot 1, Deschutes/Klamath)
1) HP KAYAK XU (Dual Xeon [Slot 2] 400/450 Mhz)
2) HP KAYAK XA (Pentium II Slot 1, monoprocessor)
Currently firmware auto detection is not implemented. To use the
driver load it with the correct option for you Kayak. For example:
insmod maxilife.o maxi_version=0 | 1 | 2
maxi_version=0 is the default
The lm75's are indeed accessable in the regular way via the lm75 module.
They worked out of the box. However, I don't use the "raw" lm75 info.
MaxiLife takes care of reading out the lm75's and converts the values to
temperatures. Compared to some of the other monitoring controllers MaxiLife
is quite advanced. It takes care of a lot of issues, like setting ranges,
raising alarms, etc. Machines with MaxiLife also have a little LCD display
on their front (lcd is at address 0x54). The idea is that later versions
of the driver will write health info on the lcd (a bit like lcdproc).
Note
----
sensors-detect does not support recognition of the maxilife chips.
Chip Features
-------------
Chip 'maxilife-as'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
fan1 - - R- 0
fan2 - - R- 0
fan3 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan1_div fan1 - R- 0
fan2_div fan2 - R- 0
fan3_div fan3 - R- 0
temp1 - - R- 1
temp2 - - R- 1
temp3 - - R- 1
temp4 - - R- 1
temp5 - - R- 1
temp1_max temp1 temp1 R- 1
temp2_max temp2 temp2 R- 1
temp3_max temp3 temp3 R- 1
temp4_max temp4 temp4 R- 1
temp5_max temp5 temp5 R- 1
temp1_hyst temp1 temp1 R- 1
temp2_hyst temp2 temp2 R- 1
temp3_hyst temp3 temp3 R- 1
temp4_hyst temp4 temp4 R- 1
temp5_hyst temp5 temp5 R- 1
pll - - R- 2
pll_min pll pll RW 2
pll_max pll pll RW 2
vid1 - - R- 4
vid2 - - R- 4
vid3 - - R- 4
vid4 - - R- 4
vid1_min vid1 vid1 RW 4
vid2_min vid2 vid2 RW 4
vid3_min vid3 vid3 RW 4
vid4_min vid4 vid4 RW 4
vid1_max vid1 vid1 RW 4
vid2_max vid2 vid2 RW 4
vid3_max vid3 vid3 RW 4
vid4_max vid4 vid4 RW 4
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
fan1 SENSORS_MAXI_AS_FAN1 fan1:3
fan2 SENSORS_MAXI_AS_FAN2 fan2:3
fan3 SENSORS_MAXI_AS_FAN3 fan3:3
fan1_min SENSORS_MAXI_AS_FAN1_MIN fan1:1
fan2_min SENSORS_MAXI_AS_FAN2_MIN fan2:1
fan3_min SENSORS_MAXI_AS_FAN3_MIN fan3:1
fan1_div SENSORS_MAXI_AS_FAN1_DIV fan1:2
fan2_div SENSORS_MAXI_AS_FAN2_DIV fan2:2
fan3_div SENSORS_MAXI_AS_FAN3_DIV fan3:2
temp1 SENSORS_MAXI_AS_TEMP1 temp1:3
temp2 SENSORS_MAXI_AS_TEMP2 temp2:3
temp3 SENSORS_MAXI_AS_TEMP3 temp3:3
temp4 SENSORS_MAXI_AS_TEMP4 temp4:3
temp5 SENSORS_MAXI_AS_TEMP5 temp5:3
temp1_max SENSORS_MAXI_AS_TEMP1_MAX temp1:1
temp2_max SENSORS_MAXI_AS_TEMP2_MAX temp2:1
temp3_max SENSORS_MAXI_AS_TEMP3_MAX temp3:1
temp4_max SENSORS_MAXI_AS_TEMP4_MAX temp4:1
temp5_max SENSORS_MAXI_AS_TEMP5_MAX temp5:1
temp1_hyst SENSORS_MAXI_AS_TEMP1_HYST temp1:2
temp2_hyst SENSORS_MAXI_AS_TEMP2_HYST temp2:2
temp3_hyst SENSORS_MAXI_AS_TEMP3_HYST temp3:2
temp4_hyst SENSORS_MAXI_AS_TEMP4_HYST temp4:2
temp5_hyst SENSORS_MAXI_AS_TEMP5_HYST temp5:2
pll SENSORS_MAXI_AS_PLL pll:3
pll_min SENSORS_MAXI_AS_PLL_MIN pll:1
pll_max SENSORS_MAXI_AS_PLL_MAX pll:2
vid1 SENSORS_MAXI_AS_VID1 vid1:3
vid2 SENSORS_MAXI_AS_VID2 vid2:3
vid3 SENSORS_MAXI_AS_VID3 vid3:3
vid4 SENSORS_MAXI_AS_VID4 vid4:3
vid1_min SENSORS_MAXI_AS_VID1_MIN vid1:1
vid2_min SENSORS_MAXI_AS_VID2_MIN vid2:1
vid3_min SENSORS_MAXI_AS_VID3_MIN vid3:1
vid4_min SENSORS_MAXI_AS_VID4_MIN vid4:1
vid1_max SENSORS_MAXI_AS_VID1_MAX vid1:2
vid2_max SENSORS_MAXI_AS_VID2_MAX vid2:2
vid3_max SENSORS_MAXI_AS_VID3_MAX vid3:2
vid4_max SENSORS_MAXI_AS_VID4_MAX vid4:2
alarms SENSORS_MAXI_AS_ALARMS alarms:1
Chip 'maxilife-cg'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
fan1 - - R- 0
fan2 - - R- 0
fan3 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan1_div fan1 - R- 0
fan2_div fan2 - R- 0
fan3_div fan3 - R- 0
temp1 - - R- 1
temp2 - - R- 1
temp3 - - R- 1
temp4 - - R- 1
temp5 - - R- 1
temp1_max temp1 temp1 R- 1
temp2_max temp2 temp2 R- 1
temp3_max temp3 temp3 R- 1
temp4_max temp4 temp4 R- 1
temp5_max temp5 temp5 R- 1
temp1_hyst temp1 temp1 R- 1
temp2_hyst temp2 temp2 R- 1
temp3_hyst temp3 temp3 R- 1
temp4_hyst temp4 temp4 R- 1
temp5_hyst temp5 temp5 R- 1
pll - - R- 2
pll_min pll pll RW 2
pll_max pll pll RW 2
vid1 - - R- 4
vid2 - - R- 4
vid3 - - R- 4
vid4 - - R- 4
vid1_min vid1 vid1 RW 4
vid2_min vid2 vid2 RW 4
vid3_min vid3 vid3 RW 4
vid4_min vid4 vid4 RW 4
vid1_max vid1 vid1 RW 4
vid2_max vid2 vid2 RW 4
vid3_max vid3 vid3 RW 4
vid4_max vid4 vid4 RW 4
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
fan1 SENSORS_MAXI_CG_FAN1 fan1:3
fan2 SENSORS_MAXI_CG_FAN2 fan2:3
fan3 SENSORS_MAXI_CG_FAN3 fan3:3
fan1_min SENSORS_MAXI_CG_FAN1_MIN fan1:1
fan2_min SENSORS_MAXI_CG_FAN2_MIN fan2:1
fan3_min SENSORS_MAXI_CG_FAN3_MIN fan3:1
fan1_div SENSORS_MAXI_CG_FAN1_DIV fan1:2
fan2_div SENSORS_MAXI_CG_FAN2_DIV fan2:2
fan3_div SENSORS_MAXI_CG_FAN3_DIV fan3:2
temp1 SENSORS_MAXI_CG_TEMP1 temp1:3
temp2 SENSORS_MAXI_CG_TEMP2 temp2:3
temp3 SENSORS_MAXI_CG_TEMP3 temp3:3
temp4 SENSORS_MAXI_CG_TEMP4 temp4:3
temp5 SENSORS_MAXI_CG_TEMP5 temp5:3
temp1_max SENSORS_MAXI_CG_TEMP1_MAX temp1:1
temp2_max SENSORS_MAXI_CG_TEMP2_MAX temp2:1
temp3_max SENSORS_MAXI_CG_TEMP3_MAX temp3:1
temp4_max SENSORS_MAXI_CG_TEMP4_MAX temp4:1
temp5_max SENSORS_MAXI_CG_TEMP5_MAX temp5:1
temp1_hyst SENSORS_MAXI_CG_TEMP1_HYST temp1:2
temp2_hyst SENSORS_MAXI_CG_TEMP2_HYST temp2:2
temp3_hyst SENSORS_MAXI_CG_TEMP3_HYST temp3:2
temp4_hyst SENSORS_MAXI_CG_TEMP4_HYST temp4:2
temp5_hyst SENSORS_MAXI_CG_TEMP5_HYST temp5:2
pll SENSORS_MAXI_CG_PLL pll:3
pll_min SENSORS_MAXI_CG_PLL_MIN pll:1
pll_max SENSORS_MAXI_CG_PLL_MAX pll:2
vid1 SENSORS_MAXI_CG_VID1 vid1:3
vid2 SENSORS_MAXI_CG_VID2 vid2:3
vid3 SENSORS_MAXI_CG_VID3 vid3:3
vid4 SENSORS_MAXI_CG_VID4 vid4:3
vid1_min SENSORS_MAXI_CG_VID1_MIN vid1:1
vid2_min SENSORS_MAXI_CG_VID2_MIN vid2:1
vid3_min SENSORS_MAXI_CG_VID3_MIN vid3:1
vid4_min SENSORS_MAXI_CG_VID4_MIN vid4:1
vid1_max SENSORS_MAXI_CG_VID1_MAX vid1:2
vid2_max SENSORS_MAXI_CG_VID2_MAX vid2:2
vid3_max SENSORS_MAXI_CG_VID3_MAX vid3:2
vid4_max SENSORS_MAXI_CG_VID4_MAX vid4:2
alarms SENSORS_MAXI_CG_ALARMS alarms:1
Chip 'maxilife-co'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
fan1 - - R- 0
fan2 - - R- 0
fan3 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan1_div fan1 - R- 0
fan2_div fan2 - R- 0
fan3_div fan3 - R- 0
temp1 - - R- 1
temp2 - - R- 1
temp3 - - R- 1
temp4 - - R- 1
temp5 - - R- 1
temp1_max temp1 temp1 R- 1
temp2_max temp2 temp2 R- 1
temp3_max temp3 temp3 R- 1
temp4_max temp4 temp4 R- 1
temp5_max temp5 temp5 R- 1
temp1_hyst temp1 temp1 R- 1
temp2_hyst temp2 temp2 R- 1
temp3_hyst temp3 temp3 R- 1
temp4_hyst temp4 temp4 R- 1
temp5_hyst temp5 temp5 R- 1
pll - - R- 2
pll_min pll pll RW 2
pll_max pll pll RW 2
vid1 - - R- 4
vid2 - - R- 4
vid3 - - R- 4
vid4 - - R- 4
vid1_min vid1 vid1 RW 4
vid2_min vid2 vid2 RW 4
vid3_min vid3 vid3 RW 4
vid4_min vid4 vid4 RW 4
vid1_max vid1 vid1 RW 4
vid2_max vid2 vid2 RW 4
vid3_max vid3 vid3 RW 4
vid4_max vid4 vid4 RW 4
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
fan1 SENSORS_MAXI_CO_FAN1 fan1:3
fan2 SENSORS_MAXI_CO_FAN2 fan2:3
fan3 SENSORS_MAXI_CO_FAN3 fan3:3
fan1_min SENSORS_MAXI_CO_FAN1_MIN fan1:1
fan2_min SENSORS_MAXI_CO_FAN2_MIN fan2:1
fan3_min SENSORS_MAXI_CO_FAN3_MIN fan3:1
fan1_div SENSORS_MAXI_CO_FAN1_DIV fan1:2
fan2_div SENSORS_MAXI_CO_FAN2_DIV fan2:2
fan3_div SENSORS_MAXI_CO_FAN3_DIV fan3:2
temp1 SENSORS_MAXI_CO_TEMP1 temp1:3
temp2 SENSORS_MAXI_CO_TEMP2 temp2:3
temp3 SENSORS_MAXI_CO_TEMP3 temp3:3
temp4 SENSORS_MAXI_CO_TEMP4 temp4:3
temp5 SENSORS_MAXI_CO_TEMP5 temp5:3
temp1_max SENSORS_MAXI_CO_TEMP1_MAX temp1:1
temp2_max SENSORS_MAXI_CO_TEMP2_MAX temp2:1
temp3_max SENSORS_MAXI_CO_TEMP3_MAX temp3:1
temp4_max SENSORS_MAXI_CO_TEMP4_MAX temp4:1
temp5_max SENSORS_MAXI_CO_TEMP5_MAX temp5:1
temp1_hyst SENSORS_MAXI_CO_TEMP1_HYST temp1:2
temp2_hyst SENSORS_MAXI_CO_TEMP2_HYST temp2:2
temp3_hyst SENSORS_MAXI_CO_TEMP3_HYST temp3:2
temp4_hyst SENSORS_MAXI_CO_TEMP4_HYST temp4:2
temp5_hyst SENSORS_MAXI_CO_TEMP5_HYST temp5:2
pll SENSORS_MAXI_CO_PLL pll:3
pll_min SENSORS_MAXI_CO_PLL_MIN pll:1
pll_max SENSORS_MAXI_CO_PLL_MAX pll:2
vid1 SENSORS_MAXI_CO_VID1 vid1:3
vid2 SENSORS_MAXI_CO_VID2 vid2:3
vid3 SENSORS_MAXI_CO_VID3 vid3:3
vid4 SENSORS_MAXI_CO_VID4 vid4:3
vid1_min SENSORS_MAXI_CO_VID1_MIN vid1:1
vid2_min SENSORS_MAXI_CO_VID2_MIN vid2:1
vid3_min SENSORS_MAXI_CO_VID3_MIN vid3:1
vid4_min SENSORS_MAXI_CO_VID4_MIN vid4:1
vid1_max SENSORS_MAXI_CO_VID1_MAX vid1:2
vid2_max SENSORS_MAXI_CO_VID2_MAX vid2:2
vid3_max SENSORS_MAXI_CG_VID3_MAX vid3:2
vid4_max SENSORS_MAXI_CO_VID4_MAX vid4:2
alarms SENSORS_MAXI_CO_ALARMS alarms:1
Chip 'maxilife-nba'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
fan1 - - R- 0
fan2 - - R- 0
fan3 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
fan3_min fan3 fan3 RW 0
fan1_div fan1 - R- 0
fan2_div fan2 - R- 0
fan3_div fan3 - R- 0
temp1 - - R- 1
temp2 - - R- 1
temp3 - - R- 1
temp4 - - R- 1
temp5 - - R- 1
temp1_max temp1 temp1 R- 1
temp2_max temp2 temp2 R- 1
temp3_max temp3 temp3 R- 1
temp4_max temp4 temp4 R- 1
temp5_max temp5 temp5 R- 1
temp1_hyst temp1 temp1 R- 1
temp2_hyst temp2 temp2 R- 1
temp3_hyst temp3 temp3 R- 1
temp4_hyst temp4 temp4 R- 1
temp5_hyst temp5 temp5 R- 1
pll - - R- 2
pll_min pll pll RW 2
pll_max pll pll RW 2
vid1 - - R- 4
vid2 - - R- 4
vid3 - - R- 4
vid4 - - R- 4
vid1_min vid1 vid1 RW 4
vid2_min vid2 vid2 RW 4
vid3_min vid3 vid3 RW 4
vid4_min vid4 vid4 RW 4
vid1_max vid1 vid1 RW 4
vid2_max vid2 vid2 RW 4
vid3_max vid3 vid3 RW 4
vid4_max vid4 vid4 RW 4
alarms - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
fan1 SENSORS_MAXI_AS_FAN1 fan1:3
fan2 SENSORS_MAXI_AS_FAN2 fan2:3
fan3 SENSORS_MAXI_AS_FAN3 fan3:3
fan1_min SENSORS_MAXI_AS_FAN1_MIN fan1:1
fan2_min SENSORS_MAXI_AS_FAN2_MIN fan2:1
fan3_min SENSORS_MAXI_AS_FAN3_MIN fan3:1
fan1_div SENSORS_MAXI_AS_FAN1_DIV fan1:2
fan2_div SENSORS_MAXI_AS_FAN2_DIV fan2:2
fan3_div SENSORS_MAXI_AS_FAN3_DIV fan3:2
temp1 SENSORS_MAXI_AS_TEMP1 temp1:3
temp2 SENSORS_MAXI_AS_TEMP2 temp2:3
temp3 SENSORS_MAXI_AS_TEMP3 temp3:3
temp4 SENSORS_MAXI_AS_TEMP4 temp4:3
temp5 SENSORS_MAXI_AS_TEMP5 temp5:3
temp1_max SENSORS_MAXI_AS_TEMP1_MAX temp1:1
temp2_max SENSORS_MAXI_AS_TEMP2_MAX temp2:1
temp3_max SENSORS_MAXI_AS_TEMP3_MAX temp3:1
temp4_max SENSORS_MAXI_AS_TEMP4_MAX temp4:1
temp5_max SENSORS_MAXI_AS_TEMP5_MAX temp5:1
temp1_hyst SENSORS_MAXI_AS_TEMP1_HYST temp1:2
temp2_hyst SENSORS_MAXI_AS_TEMP2_HYST temp2:2
temp3_hyst SENSORS_MAXI_AS_TEMP3_HYST temp3:2
temp4_hyst SENSORS_MAXI_AS_TEMP4_HYST temp4:2
temp5_hyst SENSORS_MAXI_AS_TEMP5_HYST temp5:2
pll SENSORS_MAXI_AS_PLL pll:3
pll_min SENSORS_MAXI_AS_PLL_MIN pll:1
pll_max SENSORS_MAXI_AS_PLL_MAX pll:2
vid1 SENSORS_MAXI_AS_VID1 vid1:3
vid2 SENSORS_MAXI_AS_VID2 vid2:3
vid3 SENSORS_MAXI_AS_VID3 vid3:3
vid4 SENSORS_MAXI_AS_VID4 vid4:3
vid1_min SENSORS_MAXI_AS_VID1_MIN vid1:1
vid2_min SENSORS_MAXI_AS_VID2_MIN vid2:1
vid3_min SENSORS_MAXI_AS_VID3_MIN vid3:1
vid4_min SENSORS_MAXI_AS_VID4_MIN vid4:1
vid1_max SENSORS_MAXI_AS_VID1_MAX vid1:2
vid2_max SENSORS_MAXI_AS_VID2_MAX vid2:2
vid3_max SENSORS_MAXI_AS_VID3_MAX vid3:2
vid4_max SENSORS_MAXI_AS_VID4_MAX vid4:2
alarms SENSORS_MAXI_AS_ALARMS alarms:1
-57
View File
@@ -1,57 +0,0 @@
Kernel driver `mic74.o'
=========================
Status: Beta
Supported chips:
* Micrel MIC74
Prefix: 'mic74'
Addresses scanned: I2C 0x20 - 0x27
Datasheet: Publicly available at the Micrel website
http://www.micrel.com/
Authors: zebo25
mds (cleanups and this doc)
Module Parameters
-----------------
Description
-----------
The MIC74 is an 8-bit I/O expander for the I2C bus.
Semiconductors. It is designed to provide a byte I2C interface to up to 8
separate devices.
This device consists of a quasi-bidirectionnal port. Each of the eight I/Os
can be independently used as an input or output.
The driver does not initialize the ports, they will be at their
power-up defaults, or however the BIOS programmed them.
For more information see the datasheet.
Accessing MIC74 via /proc interface
-------------------------------------
! Be careful !
The chip is plainly impossible to detect ! Stupid chip.
So every chip with adress in the interval [20..27] could be
detected as an MIC74 or a PCF8574. Don't load both drivers.
If you have other chips in this address
range, the workaround is to load this module after the one
for your others chips.
On detection, directories are created for each detected chip:
/proc/sys/dev/sensors/mic74-<0>-<1>/
where <0> is the bus the chip was detected on (e. g. i2c-0)
and <1> the chip address [20..27]:
./mic74-i2c-0-27/
Inside these directories, there are seven files each,
one for each of the seven registers in the device.
At this point you really need the datasheet.
It should be self-explanatory which file is for which register.
-73
View File
@@ -1,73 +0,0 @@
Kernel driver `mtp008.o'
=========================
Status: Complete; Beta.
This chip is quite configurable and many settings cannot
be done through the driver. Hopefully the BIOS settings will
be correct for most motherboards.
Supported chips:
* Myson MTP008
Prefix: 'mtp008'
Addresses scanned: I2C 0x2c - 0x2e
Datasheet: Publicly available at the Myson website
Author: Kris Van Hees <aedil@alchar.org>
Description
-----------
The Myson MTP008 is similar in function to the Winbond W83781D and the
National Semi LM78. See documentation for those chips for an overview.
The MTP008 chip has several pins that are configurable.
The three temperature sensors are configurable via the driver
using either the /proc entries or 'set sensorx y' statements in
/etc/sensors.conf.
One temperature input is always temp1, and the sensor type is configured
with the sensor1 entry in /proc.
One input is either in4 or temp2, and is configured with the sensor2 entry.
One input is either in5 or temp3, and is configured with the sensor3 entry.
For example, on the Tyan S1824D motherboard,
AIN4 is configured as PII temp sensor 2 and
AIN5 is hooked up to the -12V sensing circuit.
Other Tyan motherboards (the 2510 Thunder and the Tiger 200) appear to
have different configurations.
For the 2510, which has two MTP008 chips, each with a different
configuration, follow the instructions in the mtp008 section of
etc/sensors.conf.eg to configure 'sensors' correctly.
The PWM outputs are configurable via the driver.
PWM values are from 0 to 255. The first value in the /proc entry is
the pwm value; the second is the enable (0 or 1).
The fan3 and pwm1 functions share a pin.
If pwm1 is enabled than fan3 will read as 0.
The Fan input types (2 wire or 3 wire) are configurable but
not via the driver.
The driver now preserves pin settings that the BIOS configures.
If your BIOS did it right then the driver should work great.
The MTP008 has sensor type selection in /proc like the Winbond W83782D.
However, the appropriate values used to make the selection are different.
To select a sensor type, echo the value to the /proc entry, for example:
echo 1 > /proc/sys/dev/sensors/mtp008-i2c-0-2d/sensor1
Or, use /etc/sensors.conf and add a line 'set sensor1 1', for example.
Hopefully the BIOS has set these already.
Sensor selection is as follows:
Value Function Valid on sensors
----- -------- ----------------
1 Analog input (not for temperature sensing) 2,3
2 Thermistor 1,2,3
3 Pentium diode 1,2
-184
View File
@@ -1,184 +0,0 @@
Kernel driver `pc87360.o'
=========================
Status: Beta, PC87366 well tested, other chips untested.
Supported chips:
* National Semiconductor PC87360, PC87363, PC87364, PC87365 and PC87366
Addresses scanned: none, address read from Super I/O config space
Prefixes: 'pc87360', 'pc87363', 'pc87364', 'pc87365', 'pc87366'
Datasheets:
http://www.national.com/pf/PC/PC87360.html
http://www.national.com/pf/PC/PC87363.html
http://www.national.com/pf/PC/PC87364.html
http://www.national.com/pf/PC/PC87365.html
http://www.national.com/pf/PC/PC87366.html
Authors: Jean Delvare <khali@linux-fr.org>
Thanks to Sandeep Mehta, Tonko de Rooy and Daniel Ceregatti for testing.
Thanks to Rudolf Marek for helping me investigate conversion issues.
Module Parameters
-----------------
init (int):
Chip initialization level:
0: None
*1: Forcibly enable internal voltage and temperature channels, except in9
2: Forcibly enable all voltage and temperature channels, except in9
3: Forcibly enable all voltage and temperature channels, including in9
Note that this parameter has no effect for the PC87360, PC87363 and PC87364
chips.
Also note that for the PC87366, initialization levels 2 and 3 don't enable
all temperature channels, because some of them share pins with each other,
so they can't be used at the same time.
Description
-----------
The National Semiconductor PC87360 Super I/O chip contains monitoring and
PWM control circuitry for two fans. The PC87363 chip is similar, and the
PC87364 chip has monitoring and PWM control for a third fan.
The National Semiconductor PC87365 and PC87366 Super I/O chips are complete
hardware monitoring chipsets, not only controling and monitoring three fans,
but also monitoring eleven voltage inputs and two (PC87365) or up to four
(PC87366) temperatures.
The driver assumes that no more than one chip is present, and one of the
standard Super I/O addresses is used (0x2E/0x2F or 0x4E/0x4F).
Fan Monitoring
--------------
Fan rotation speeds are reported in RPM (revolutions per minute). An alarm
is triggered if the rotation speed has dropped below a programmable limit.
A different alarm is triggered if the fan speed is too low to be measured.
Fan readings are affected by a programmable clock divider, giving the
readings more range or accuracy. Usually, users have to learn how it works,
but this driver implements dynamic clock divider selection, so you don't
have to care no more.
For reference, here are a few values about clock dividers:
slowest accuracy highest
measurable around 3000 accurate
divider speed (RPM) RPM (RPM) speed (RPM)
1 1882 18 6928
2 941 37 4898
4 470 74 3464
8 235 150 2449
For the curious, here is how the values above were computed:
* slowest measurable speed: clock/(255*divider)
* accuracy around 3000 RPM: 3000^2/clock
* highest accurate speed: sqrt(clock*100)
The clock speed for the PC87360 family is 480 kHz. I arbitrarily chose 100
RPM as the lowest acceptable accuracy.
As mentioned above, you don't have to care about this no more.
Note that not all RPM values can be represented, even when the best clock
divider is selected. This is not only true for the measured speeds, but
also for the programmable low limits, so don't be surprised if you try to
set, say, fan1_min to 2900 and it finaly reads 2909.
Fan Control
-----------
PWM (pulse width modulation) values range from 0 to 255, with 0 meaning
that the fan is stopped, and 255 meaning that the fan goes at full speed.
Be extremely careful when changing PWM values. Low PWM values, even
non-zero, can stop the fan, which may cause irreversible damage to your
hardware if temperature increases too much. When changing PWM values, go
step by step and keep an eye on temperatures.
One user reported problems with PWM. Changing PWM values would break fan
speed readings. No explanation nor fix could be found.
Temperature Monitoring
----------------------
Temperatures are reported in degrees Celsius. Each temperature measured has
associated low, high and overtemperature limits, each of which triggers an
alarm when crossed.
The first two temperature channels are external. The third one (PC87366
only) is internal.
The PC87366 has three additional temperature channels, based on
thermistors (as opposed to thermal diodes for the first three temperature
channels). For technical reasons, these channels are held by the VLM
(voltage level monitor) logical device, not the TMS (temperature
measurement) one. As a consequence, these temperatures are exported as
voltages, and converted into temperatures in user-space.
Note that these three additional channels share their pins with the
external thermal diode channels, so you (physically) can't use them all at
the same time. Although it should be possible to mix the two sensor types,
the documents from National Semiconductor suggest that motherboard
manufacturers should choose one type and stick to it. So you will more
likely have either channels 1 to 3 (thermal diodes) or 3 to 6 (internal
thermal diode, and thermistors).
Voltage Monitoring
------------------
Voltages are reported relatively to a reference voltage, either internal or
external. Some of them (in7:Vsb, in8:Vdd and in10:AVdd) are divided by two
internally, you will have to compensate in sensors.conf. Others (in0 to in6)
are likely to be divided externally. The meaning of each of these inputs as
well as the values of the resistors used for division is left to the
motherboard manufacturers, so you will have to document yourself and edit
sensors.conf accordingly. National Semiconductor has a document with
recommended resistor values for some voltages, but this still leaves much
room for per motherboard specificities, unfortunately. Even worse,
motherboard manufacturers don't seem to care about National Semiconductor's
recommendations.
Each voltage measured has associated low and high limits, each of which
triggers an alarm when crossed.
When available, VID inputs are used to provide the nominal CPU Core voltage.
The driver will default to VRM 9.0, but this can be changed from user-space.
The chipsets can handle two sets of VID inputs (on dual-CPU systems), but
the driver will only export one for now. This may change later if there is
a need.
General Remarks
---------------
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may already
have disappeared! Note that all hardware registers are read whenever any
data is read (unless it is less than 2 seconds since the last update, in
which case cached values are returned instead). As a consequence, when
a once-only alarm triggers, it may take 2 seconds for it to show, and 2
more seconds for it to disappear.
Monitoring of in9 isn't enabled at lower init levels (<3) because that
channel measures the battery voltage (Vbat). It is a known fact that
repeatedly sampling the battery voltage reduces its lifetime. National
Semiconductor smartly designed their chipset so that in9 is sampled only
once every 1024 sampling cycles (that is every 34 minutes at the default
sampling rate), so the effect is attenuated, but still present.
Limitations
-----------
The datasheets suggests that some values (fan mins, fan dividers)
shouldn't be changed once the monitoring has started, but we ignore that
recommendation. We'll reconsider if it actually causes trouble.
-76
View File
@@ -1,76 +0,0 @@
Kernel driver `pca9540.o'
=========================
Status: Complete but untested
Supported chips:
* Philips PCA9540
Prefix: 'pca9540'
Addresses scanned: I2C 0x70
Datasheet: Publicly available at the Philips Semiconductors website
http://www.semiconductors.philips.com/pip/PCA9540.html
Authors: Jean Delvare <khali@linux-fr.org>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_pca9540: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `pca9540' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Note that the force and force_pca9540 parameters are equivalent here.
Description (from the Philips web site)
---------------------------------------
The PCA9540 is a 1-of-2 bi-directional translating multiplexer, controlled
via the I2C bus. The SCL/SDA upstream pair fans out to two SCL/SDA downstream
pairs, or channels. Only one SCL/SDA channel is selected at a time,
determined by the contents of the programmable control register.
Accessing the PCA9540
---------------------
The PCA9540 is almost impossible to detect. We are lucky that no other
known chip lives at address 0x70.
Beware that the PCA9540 has a single internal register. It does work with
READ BYTE and WRITE BYTE commands, contrary to regular chips which use
READ BYTE DATA and WRITE BYTE DATA. Using i2cdump on the PCA9540 will
actually *write* to it and leave its internal register to value 0x07 (i.e.
no downstream channel selected).
/proc Interface
---------------
There is a single read/write file, named "channel".
0 = no downstream channel selected
1 = first downstream channel selected
2 = second downstream channel selected
Chip Features
-------------
Chip `pca9540'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
channel NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
channel SENSORS_PCA9540_CHANNEL channel:1
-100
View File
@@ -1,100 +0,0 @@
Kernel driver `pcf8574.o'
=========================
Status: Complete
Supported chips:
* Philips PCF8574
Prefix: 'pcf8574'
Addresses scanned: I2C 0x20 - 0x27
Datasheet: Publicly available at the Philips Semiconductors website
http://www.semiconductors.philips.com/pip/PCF8574P.html
* Philips PCF8574A
Prefix: 'pcf8574a'
Addresses scanned: I2C 0x38 - 0x3f
Datasheet: Publicly available at the Philips Semiconductors website
http://www.semiconductors.philips.com/pip/PCF8574P.html
Authors: Frodo Looijaard <frodol@dds.nl>,
Philip Edelbrock <phil@netroedge.com>,
and Dan Eaton <dan.eaton@rocketlogix.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_pcf8574: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `pcf8574' chip
* force_pcf8574a: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `pcf8574a' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The PCF8574(A) is an 8-bit I/O expander for the I2C bus produced by Philips
Semiconductors. It is designed to provide a byte I2C interface to up to 16
separate devices (8 x PCF8574 and 8 x PCF8574A).
This device consists of a quasi-bidirectionnal port. Each of the eight I/Os
can be independently used as an input or output. To setup an I/O as an
input, you have to write a 1 to the corresponding output.
For more informations see the datasheet.
Accessing PCF8574(A) via /proc interface
-------------------------------------
! Be careful !
The PCF8574(A) is plainly impossible to detect ! Stupid chip.
So every chip with adress in the interval [20..27] and [38..3f] is
detected as PCF8574(A). If you have other chips in this address
range, the workaround is to load this module after the one
for your others chips.
On detection (i.e. insmod, modprobe et al.), directories are being
created for each detected PCF8574(A):
/proc/sys/dev/sensors/pcf8574-<0>-<1>/
where <0> is the bus the chip was detected on (e. g. i2c-0)
and <1> the chip address ([20..27] or [38..3f]):
./pcf8574-i2c-0-27/
Inside these directories, there are two files each:
read and write.
The read file is read-only. Reading gives you the current I/O input
if the corresponding output is set as 1, otherwise the current output
value, that is to say 0.
The write file is read/write. Writing a value outputs it on the I/O
port. Reading return the last written value.
On module initialization the chip is configured as eight inputs (all
outputs to 1), so you can connect any circuit to the PCF8574(A) without
being afraid of short-circuit.
Chip Features
-------------
Chip `pcf8574'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
read NONE NONE R 0
write NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
read SENSORS_PCF8591_READ read:1
write SENSORS_PCF8591_WRITE write:1
-127
View File
@@ -1,127 +0,0 @@
Kernel driver `pcf8591.o'
========================
Status: Status: Complete; Beta.
Supported chips:
* Philips PCF8591
Prefix: 'pcf8591'
Addresses scanned: I2C 0x48 - 0x4f
Datasheet: Publicly available at the Philips Semiconductors website
http://www.semiconductors.philips.com/pip/PCF8591P.html
Author: Aurelien Jarno <aurelien@aurel32.net>
valuable contributions by Jan M. Sendler <sendler@sendler.de>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_pcf8591: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `pcf8591' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The PCF8591 is an 8-bit A/D and D/A converter (4 analog inputs and one
analog ouput) for the I2C bus produced by Philips Semiconductors. It
is designed to provide a byte I2C interface to up to 4 separate devices.
The PCF8591 has 4 analog inputs programmable as single-ended or
differential inputs :
- mode 0 : four single ended inputs
Pins AIN0 to AIN3 are single ended inputs for channels 0 to 3
- mode 1 : three differential inputs
Pins AIN3 is the common negative differential input
Pins AIN0 to AIN2 are positive differential inputs for channels 0 to 2
- mode 2 : single ended and differential mixed
Pins AIN0 and AIN1 are single ended inputs for channels 0 and 1
Pins AIN2 is the positive differential input for channel 3
Pins AIN3 is the negative differential input for channel 3
- mode 3 : two differential inputs
Pins AIN0 is the positive differential input for channel 0
Pins AIN1 is the negative differential input for channel 0
Pins AIN2 is the positive differential input for channel 1
Pins AIN3 is the negative differential input for channel 1
For more informations see the datasheet.
Accessing PCF8591 via /proc interface
-------------------------------------
! Be careful !
The PCF8591 is plainly impossible to detect ! Stupid chip.
So every chip with adress in the interval [48..4f] is
detected as PCF8591. If you have other chips in this address
range, the workaround is to load this module after the one
for your others chips.
On detection (i.e. insmod, modprobe et al.), directories are being
created for each detected PCF8591:
/proc/sys/dev/sensors/pcf8591-<0>-<1>/
where <0> is the bus the chip was detected on (e. g. i2c-0)
and <1> the chip address ([48..4f]): ./pcf8591-i2c-0-48/
Inside these directories, there are five files each:
ain_conf, ch0, ch1, ch2, ch3, aout_enable, aout.
The ain_conf file is rw. Reading gives you the current analog inputs
configuration, and writing configure the analog inputs. It must be
a number between 0 and 3., other values are ignored. For the different
modes, see above in the description.
The ch0, ch1, ch2 and ch3 files are ro. Reading gives the value
of the corresponding channel. Depending on the current analog inputs
configuration, channel ch2 and/or ch3 may are not used by the chip
and so read as 0. Values range are from 0 to 255 for single
ended inputs and -128 to +127 for differential inputs (8-bit ADC).
The aout_enable is rw. Reading gives "1" for analog output enabled and
"0" for analog output disabled. Writing accepts "0" and "1" accordingly.
The aout is rw. Writing a number between 0 and 255 (8-bit DAC), send
the value to the digital-to-analog converter. Note that a voltage will
only appears on AOUT pin if aout_enable equals 1. Reading returns the last
value written.
On module initialization the chip is configured as followed :
- four single ended differential inputs
- analog output set to 0 and enabled
Chip Features
-------------
Chip `pcf8591'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
ain_conf NONE NONE RW 0
ch0 NONE NONE R 0
ch1 NONE NONE R 0
ch2 NONE NONE R 0
ch3 NONE NONE R 0
aout_enable NONE NONE RW 0
aout NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:OFFSET
ain_conf SENSORS_PCF8591_AIN_CONF ain_conf:1
ch0 SENSORS_PCF8591_CH0 ain_ch0:1
ch1 SENSORS_PCF8591_CH1 ain_ch1:1
ch2 SENSORS_PCF8591_CH2 ain_ch2:1
ch3 SENSORS_PCF8591_CH3 ain_ch3:1
aout_enable SENSORS_PCF8591_AOUT_ENABLE aout_enable:1
aout SENSORS_PCF8591_AOUT aout:1
-81
View File
@@ -1,81 +0,0 @@
Kernel driver `saa1064.o'
========================
Status: Testing;
Supported chips:
* Philips SAA1064
Prefix: 'saa1064'
Addresses scanned: I2C 0x38 - 0x3b
Datasheet: Publicly available at the Philips Semiconductors website
http://www.semiconductors.philips.com/
http://www.philipslogic.com/products/i2c/pdf/saa1064.pdf
Author: Sascha Volkenandt <sascha@akv-soft.de>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_saa1064: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to
contain a `saa1064' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The SAA1064 is a 4-digit LED driver chip for the I2C bus produced by Philips
Semiconductors. It is designed to provide a byte I2C interface to up to 4
separate 4-digit displays (16 digits).
This device has eight write-only registers, five are used for a control byte
and the four digit values.
For more informations see the datasheet.
Accessing SAA1064 via /proc interface
-------------------------------------
On detection (i.e. insmod, modprobe et al.), directories are being
created for each detected SAA1064:
/proc/sys/dev/sensors/saa1064-<0>-<1>/
where <0> is the bus the chip was detected on (e. g. i2c-0)
and <1> the chip address ([38..3b]):
./saa1064-i2c-0-38/
Inside these directories, there are four files each:
bright, test, disp and refresh
The bright, test and disp files are read-write.
Writing bright sets the output current to the LEDs connected to the
chip and thus the brightness. Range is from 0 to 7. Reading
gives you the last known value.
Writing test sets the segment test mode (all LEDs lit regardless of
the digit values) on/off (values 0/1). Reading gives you the last
known value.
Writing up to four values to disp sets the digits. Each value represents
one segment, while each bit represents one LED of the segment (seven bars
and one dot usually). Reading gives you the last known value.
The refresh file is read-only. You can't read anything from it, but
reading it will trigger a refresh in case a power-loss has occurred since
the last refresh.
On module initialization the chip is configured for segment test (test=1),
full brightness (bright=7), all digit values zeroed.
-165
View File
@@ -1,165 +0,0 @@
Kernel driver `sis5595.o'
=========================
Status: Complete and tested
Supported chips:
* Silicon Integrated Systems Corp. SiS5595 Southbridge Hardware Monitor
Prefix: sis5595
Addresses scanned: ISA in PCI-space encoded address
Datasheet: Publicly available at the Silicon Integrated Systems Corp. site.
Supports following revisions:
Version PCI ID PCI Revision
1 1039/0008 01
2 1039/0008 C0
Author: Kyösti Mälkki <kmalkki@cc.hut.fi>
Note: The chips below contain a 0008 device which is incompatible with the
5595. We recognize these by the presence of the listed
"blacklist" PCI ID and refuse to load.
NOT SUPPORTED PCI ID BLACKLIST PCI ID
540 0008 0540
550 0008 0550
5513 0008 5511
5581 0008 5597
5582 0008 5597
5597 0008 5597
5598 0008 5597/5598
630 0008 0630
645 0008 0645
730 0008 0730
735 0008 0735
Module Parameters
-----------------
force_addr=0xaddr Set the I/O base address. Useful for boards
that don't set the address in the BIOS. Does not do a
PCI force; the device must still be present in lspci.
Don't use this unless the driver complains that the
base address is not set.
Example: 'modprobe sis5595 force_addr=0x290'
Description
-----------
The Sis5595 southbridge has integrated hardware monitor functions.
It also has an I2C bus, but this driver only supports the hardware monitor.
For the I2C bus driver see i2c-sis5595.
The SiS5595 implements zero or one temperature sensor, two fan speed
sensors, four or five voltage sensors, and alarms.
On the first version of the chip, there are four voltage
sensors and one temperature sensor.
On the second version of the chip,
the temperature sensor (temp) and the fifth voltage sensor (in4) share a pin
which is configurable, but not through the driver. Sorry.
The driver senses the configuration of the pin, which was hopefully
set by the BIOS.
If the temperature function is enabled, in4 will return 0.
If the in4 function is enabled, temp will return 0.
Temperatures are measured in degrees Celsius. An alarm is triggered once
when the max is crossed; it is also triggered
when it drops below the min value.
Measurements are guaranteed
between -55 and +125 degrees, with a resolution of 1 degree.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 4.08 volts, with a resolution
of 0.016 volt.
In addition to the alarms described above, there is a BTI alarm, which gets
triggered when an external chip has crossed its limits. Usually, this is
connected to some LM75-like chip; if at least one crosses its limits, this
bit gets set.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
The Sis5595 only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Problems
--------
Some chips refuse to be enabled. We don't know why.
The driver will recognize this and print a message in dmesg.
Chip Features
-------------
Chip `sis5595'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_SIS5595_IN0 in0:3
in1 SENSORS_SIS5595_IN1 in1:3
in2 SENSORS_SIS5595_IN2 in2:3
in3 SENSORS_SIS5595_IN3 in3:3
in4 SENSORS_SIS5595_IN4 in3:3
in0_min SENSORS_SIS5595_IN0_MIN in0:1
in1_min SENSORS_SIS5595_IN1_MIN in1:1
in2_min SENSORS_SIS5595_IN2_MIN in2:1
in3_min SENSORS_SIS5595_IN3_MIN in3:1
in4_min SENSORS_SIS5595_IN4_MIN in4:1
in0_max SENSORS_SIS5595_IN0_MAX in0:2
in1_max SENSORS_SIS5595_IN1_MAX in1:2
in2_max SENSORS_SIS5595_IN2_MAX in2:2
in3_max SENSORS_SIS5595_IN3_MAX in3:2
in4_max SENSORS_SIS5595_IN4_MAX in4:2
fan1 SENSORS_SIS5595_FAN1 fan1:2
fan2 SENSORS_SIS5595_FAN2 fan2:2
fan1_min SENSORS_SIS5595_FAN1_MIN fan1:1
fan2_min SENSORS_SIS5595_FAN2_MIN fan2:1
temp SENSORS_SIS5595_TEMP temp:3
temp_hyst SENSORS_SIS5595_TEMP_HYST temp:2
temp_over SENSORS_SIS5595_TEMP_OVER temp:1
fan1_div SENSORS_SIS5595_FAN1_DIV fan_div:1
fan2_div SENSORS_SIS5595_FAN2_DIV fan_div:2
alarms SENSORS_SIS5595_ALARMS alarms:1
-66
View File
@@ -1,66 +0,0 @@
Kernel driver `smartbatt.o'
==========================
Status: Alpha
Supported chips:
* Smart batteries
Prefix: 'smartbatt'
Address scanned: 0x0b
Datasheet: Smart Battery Data Specification Revision 1.1
http://www.sbs-forum.org/
Author: M. Studebaker <mdsxyz123@yahoo.com>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_smartbatt: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `smartbatt' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Detection
---------
Very poor. Just looks for the presence of some registers.
Uses
----
Provides read-only access to battery voltage, current, temperature,
charge status, time remaining, and alarms.
/proc entries
-------------
Average readings are one minute rolling averages.
alarms bitmask
charge relative charge, absolute charge (%)
i average current, instantaneous current (A)
temp temperature (deg. C)
time avg. time to empty, avg. time to full,
instantaneous time to full (min)
v max voltage, current voltage (V)
To do
-----
More features.
-66
View File
@@ -1,66 +0,0 @@
SMBUS_ARP Driver
-------------
This is a demonstration driver which provides SMBus 2.0
ARP services. It isn't much use except for testing SMBus 2.0
ARP and PEC support in the i2c layer.
It is NOT a normal 'chip' driver for a particular sensor device.
If you don't know what SMBus ARP is you don't need this driver.
Stability
---------
Beta.
DETECTION
-------------
This driver requires SMBus 2.0 support in i2c-2.6.4 or higher.
If your kernel does not have i2c-2.6.4 or higher the module
will refuse to load.
This driver will register itself on each i2c bus which
supports SMBus Block Data transactions.
USES
----
The module ARPs each supported bus when it is inserted
(or after that when a i2c bus driver is inserted).
It does not periodically re-ARP, and the i2c stack does not
currently support SMBus Host Notify, so it will not detect
hot-plugged devices.
The smbus-arp module should be inserted before any chip modules
so that it has an opportunity to assign addresses.
/PROC ENTRIES
-------------
There are 8 entries in /proc, 0-7. Each is a 'slot'.
If a chip has responded to ARP, it will appear in a 'slot'
as follows. See the SMBus 2.0 Specification for details.
value function
1 SMBus address
2 UDID Device Capabilities
3 UDID Version / Revision
4 UDID Vendor ID
5 UDID Device ID
6 UDID Interface
7 UDID Subsystem Vendor ID
8 UDID Subsystem Device ID
9 UDID Vendor Specific ID
To reassign a device to a different address, write
that address to the slot. For example, 'echo 13 > 0'
to reassign the device in slot 0 to address 13 (0x0d).
TO DO
-----
lots
Author/Maintainer
-----------------
Mark D. Studebaker (mdsxyz123@yahoo.com)
-75
View File
@@ -1,75 +0,0 @@
Kernel driver `smsc47m1.o'
==========================
Status: Beta.
Supported chips:
* SMSC LPC47B27x, LPC47M112, LPC47M10x, LPC47M13x, LPC47M14x,
LPC47M15x and LPC47M192
Prefix: 'smsc47m1'
Addresses scanned: none, address read from Super I/O config space
Datasheets:
http://www.smsc.com/main/datasheets/47b27x.pdf
http://www.smsc.com/main/datasheets/47m10x.pdf
http://www.smsc.com/main/datasheets/47m112.pdf
http://www.smsc.com/main/tools/discontinued/47m13x.pdf
http://www.smsc.com/main/datasheets/47m14x.pdf
http://www.smsc.com/main/tools/discontinued/47m15x.pdf
http://www.smsc.com/main/datasheets/47m192.pdf
* SMSC LPC47M292
Prefix: 'smsc47m2'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Not public
* SMSC LPC47M997
Addresses scanned: none, address read from Super I/O config space
Prefix: 'smsc47m1'
Datasheet: none
Authors: Mark D. Studebaker <mdsxyz123@yahoo.com>
With assistance from Bruce Allen <ballen@uwm.edu> and his
fan.c program: http://www.lsc-group.phys.uwm.edu/%7Eballen/driver/
Module Parameters
-----------------
force_addr=0xaddr Set the I/O base address. Useful for boards
that don't set the address in the BIOS.
Don't use this unless the driver complains that the
base address is not set.
Example: 'modprobe smsc47m1 force_addr=0x680'
Description
-----------
The Standard Microsystems Corporation (SMSC) 47M1xx Super I/O chips
contain monitoring and PWM control circuitry for two fans.
The LPC47M15x, LPC47M192 and LPC47M292 chips contain a full 'hardware
monitoring block' in addition to the fan monitoring and control. The
hardware monitoring block is not supported by the driver.
No documentation is available for the 47M997, but it has the same device
ID as the 47M15x and 47M192 chips and seems to be compatible therewith.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
PWM values are from 0 to 255. The first value in the /proc entry is
the pwm value; the second is the enable (0 or 1).
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
**********************
The lm_sensors project gratefully acknowledges the support of
Intel in the development of this driver.
-96
View File
@@ -1,96 +0,0 @@
Kernel driver `thmc50.o'
=====================
Status: Complete and some-what tested
Supported chips:
* Analog Devices ADM1022
Prefix: 'adm1022'
Addresses scanned: I2C 0x2D - 0x2F
Datasheet: Publicly available at the Analog Devices website
* Texas Instruments THMC50
Prefix: 'thmc50'
Addresses scanned: I2C 0x2D - 0x2F
Datasheet: Publicly available at the Texas Instruments' website
Authors: Frodo Looijaard <frodol@dds.nl> and
Philip Edelbrock <phil@netroedge.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_thmc50: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `thmc50' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
Description
-----------
The THMC50 implements: an internal temperature sensor, support for an
external diode-type temperature sensor (compatible w/ the diode sensor inside
many processors), and a controllable fan/analog_out DAC. For the temperature
sensors, limits can be set through the appropriate Overtemperature Shutdown
register and Hysteresis register. Each value can be set and read to half-degree
accuracy. An alarm is issued (usually to a connected LM78) when the
temperature gets higher then the Overtemperature Shutdown value; it stays on
until the temperature falls below the Hysteresis value. All temperatures are in
degrees Celsius, and are guaranteed within a range of -55 to +125 degrees.
The THMC50 only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
The THMC50 is usually used in combination with LM78-like chips, to measure
the temperature of the processor(s).
Driver Features
---------------
These are the files which are provided by the driver (ro=read-only,
rw=read and writeable):
temp1 (internal) -- Three fields: [temp-os,rw] [temp-hyst,rw] [temp,ro]
temp2 (remote) -- Three fields: [temp-os,rw] [temp-hyst,rw] [temp,ro]
temp-os, temp-hyst, and temp are whole degree integers representing degrees
Celsius.
analog_out -- One field: [output-value,rw]
output-value is a number from 0 to 255 representing the voltage output
provided by the DAC from 0 to 2.5, respectively. Note: the max *current*
supplied is actually quite low. Some type of current amplification is needed
in order to control a fan or other current demanding device (see the
datasheet for more info).
die_code -- One field: [die-code,ro]
die-code is an integer representation of the die_code byte register.
According to the docs, the high nibble is the die version, and the lower
nibble is the stepping. (Don't ask me why or what this means! ;')
inter -- One field: [interrupts,rw]
interrupts is an integer representation of the interrupts byte. See the data
sheet for what bits mean what.
inter_mask -- One field: [inter-mask,rw]
inter-mask is an integer repressentation of the interrupt mask which is a
determination of which conditions can cause the /INT pin on the chip to
become asserted.
-155
View File
@@ -1,155 +0,0 @@
Kernel driver `via686a.o'
=========================
Status: Stable.
Supported chips:
* Via VT82C686A, VT82C686B Southbridge Integrated Hardware Monitor
Prefix: 'via686a'
Addresses scanned: ISA in PCI-space encoded address
Datasheet: On request through web form
(http://www.via.com.tw/en/support/datasheets/)
Authors: Kyösti Mälkki <kmalkki@cc.hut.fi>,
Mark D. Studebaker <mdsxyz123@yahoo.com>
Bob Dougherty <bobd@stanford.edu>
(Some conversion-factor data were contributed by
Jonathan Teh Soon Yew <j.teh@iname.com>
and Alex van Kaam <darkside@chello.nl>.)
Module Parameters
-----------------
force_addr=0xaddr Set the I/O base address. Useful for boards that
don't set the address in the BIOS. Look for a BIOS
upgrade before resorting to this. Does not do a
PCI force; the via686a must still be present in lspci.
Don't use this unless the driver complains that the
base address is not set.
Example: 'modprobe via686a force_addr=0x6000'
Description
-----------
The driver does not distinguish between the chips and reports
all as a 686A.
The Via 686a southbridge has integrated hardware monitor functionality.
It also has an I2C bus, but this driver only supports the hardware monitor.
For the I2C bus driver, see ../busses/i2c-viapro.
The Via 686a implements three temperature sensors, two fan rotation speed
sensors, five voltage sensors and alarms.
Temperatures are measured in degrees Celsius. An alarm is triggered once
when the Overtemperature Shutdown limit is crossed; it is triggered again
as soon as it drops below the hysteresis value.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Voltages are internally scalled, so each voltage channel
has a different resolution and range.
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
The driver only updates its values each 1.5 seconds; reading it more often
will do no harm, but will return 'old' values.
Known Issues
------------
This driver handles sensors integrated in some VIA south bridges. It is
possible that a motherboard maker used a VT82C686A/B chip as part of a
product design but was not interested in its hardware monitoring features,
in which case the sensor inputs will not be wired. This is the case of
the Asus K7V, A7V and A7V133 motherboards, to name only a few of them.
So, if you need the force_addr parameter, and end up with values which
don't seem to make any sense, don't look any further: your chip is simply
not wired for hardware monitoring.
Chip Features
-------------
Chip `via686a'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in3 in3 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in3 in3 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp NONE NONE R 1
temp_hyst temp temp RW 1
temp_over temp temp RW 1
temp2 NONE NONE R 1
temp2_hyst temp temp RW 1
temp2_over temp temp RW 1
temp3 NONE NONE R 1
temp3_hyst temp temp RW 1
temp3_over temp temp RW 1
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
alarms NONE NONE R 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_VIA686A_IN0 in0:3
in1 SENSORS_VIA686A_IN1 in1:3
in2 SENSORS_VIA686A_IN2 in2:3
in3 SENSORS_VIA686A_IN3 in3:3
in4 SENSORS_VIA686A_IN4 in4:3
in0_min SENSORS_VIA686A_IN0_MIN in0:1
in1_min SENSORS_VIA686A_IN1_MIN in1:1
in2_min SENSORS_VIA686A_IN2_MIN in2:1
in3_min SENSORS_VIA686A_IN3_MIN in3:1
in4_min SENSORS_VIA686A_IN4_MIN in4:1
in0_max SENSORS_VIA686A_IN0_MAX in0:2
in1_max SENSORS_VIA686A_IN1_MAX in1:2
in2_max SENSORS_VIA686A_IN2_MAX in2:2
in3_max SENSORS_VIA686A_IN3_MAX in3:2
in4_max SENSORS_VIA686A_IN4_MAX in4:2
fan1 SENSORS_VIA686A_FAN1 fan1:2
fan2 SENSORS_VIA686A_FAN2 fan2:2
fan1_min SENSORS_VIA686A_FAN1_MIN fan1:1
fan2_min SENSORS_VIA686A_FAN2_MIN fan2:1
temp SENSORS_VIA686A_TEMP temp:3
temp_hyst SENSORS_VIA686A_TEMP_HYST temp:2
temp_over SENSORS_VIA686A_TEMP_OVER temp:1
temp2 SENSORS_VIA686A_TEMP2 temp2:3
temp2_hyst SENSORS_VIA686A_TEMP2_HYST temp2:2
temp2_over SENSORS_VIA686A_TEMP2_OVER temp2:1
temp3 SENSORS_VIA686A_TEMP3 temp3:3
temp3_hyst SENSORS_VIA686A_TEMP3_HYST temp3:2
temp3_over SENSORS_VIA686A_TEMP3_OVER temp3:1
fan1_div SENSORS_VIA686A_FAN1_DIV fan_div:1
fan2_div SENSORS_VIA686A_FAN2_DIV fan_div:2
alarms SENSORS_VIA686A_ALARMS alarms:1
-70
View File
@@ -1,70 +0,0 @@
Kernel driver `vt1211.o'
=========================
Status: Beta.
Supported chips:
* Via VT1211 Super I/O Controller
Addresses scanned: none, address read from Super I/O config space
Authors: Mark D. Studebaker <mdsxyz123@yahoo.com>
Module Parameters
-----------------
force_addr=0xaddr Set the I/O base address. Useful for boards
that don't set the address in the BIOS.
Don't use this unless the driver complains that the
base address is not set.
Example: 'modprobe vt1211 force_addr=0x680'
Description
-----------
The Via VT1211 Super I/O chip
contains voltage/temperature monitoring and
PWM control circuitry for two fans.
We define the sensors as follows. Somewhat convoluted to minimize
changes from via686a.
Sensor Voltage Mode Temp Mode uch_config bit
-------- ------------ --------- --------------
Reading 1 temp3
Reading 3 temp1
UCH1/Reading2 in0 temp2 0x04 (4)
UCH2 in1 temp4 0x08 (8)
UCH3 in2 temp5 0x10 (16)
UCH4 in3 temp6 0x20 (32)
UCH5 in4 temp7 0x40 (64)
3.3V in5
As noted above, 5 of the sensors are "universal channels" and can
be set for either voltage or temperature.
Set the uch_config bit (in /proc) to 0 for voltage and 1 for temperature.
Voltages will read 0 if set for temperature.
Temperatures will read a really low number if set for voltage.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
PWM values are from 0 to 255. The first value in the /proc entry is
the pwm value; the second is the enable (0 or 1).
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
**********************
The lm_sensors project gratefully acknowledges the support of
Via in the development of this driver.
-184
View File
@@ -1,184 +0,0 @@
Kernel driver `vt8231.o'
========================
Status: Beta
Supported chips:
* VIA VT8231 South Bridge
Prefix: 'vt8231'
Addresses scanned: none, address read from PCI space
Author: Mark D. Studebaker <mdsxyz123@yahoo.com>
License: GPL
Module Parameters
-----------------
* force_addr: int
Initialize the base address of the sensors
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_vt8231: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `vt8231' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The VIA VT8231 South Bridge contains voltage/temperature monitoring and
PWM control circuitry for two fans.
We define the sensors as follows.
Sensor Voltage Mode Temp Mode uch_config bit
------ ------------ --------- --------------
Reading 1 temp1
UCH1 in0 temp2 0x04 (4)
UCH2 in1 temp3 0x08 (8)
UCH3 in2 temp4 0x10 (16)
UCH4 in3 temp5 0x20 (32)
UCH5 in4 temp6 0x40 (64)
3.3V in5
As noted above, 5 of the sensors are "universal channels" and can
be set for either voltage or temperature.
Set the uch_config bit (in /proc) to 0 for voltage and 1 for temperature.
Voltages will read 0 if set for temperature.
Temperatures will read a really low number if set for voltage.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
PWM values are from 0 to 255. The first value in the /proc entry is
the pwm value; the second is the enable (0 or 1).
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
Chip Features
-------------
Chip 'vt8231'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
in0 - - R- 2
in1 - - R- 2
in2 - - R- 2
in3 - - R- 2
in4 - - R- 2
in5 - - R- 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
fan1 - - R- 0
fan2 - - R- 0
fan1_min fan1 fan1 RW 0
fan2_min fan2 fan2 RW 0
temp1 - - R- 1
temp1_hyst temp1 temp1 RW 1
temp1_over temp1 temp1 RW 1
temp2 - - R- 2
temp2_hyst temp2 temp2 RW 2
temp2_over temp2 temp2 RW 2
temp3 - - R- 2
temp3_hyst temp3 temp3 RW 2
temp3_over temp3 temp3 RW 2
temp4 - - R- 2
temp4_hyst temp4 temp4 RW 2
temp4_over temp4 temp4 RW 2
temp5 - - R- 2
temp5_hyst temp5 temp5 RW 2
temp5_over temp5 temp5 RW 2
temp6 - - R- 2
temp6_hyst temp6 temp6 RW 2
temp6_over temp6 temp6 RW 2
fan1_div fan1 - RW 0
fan2_div fan2 - RW 0
alarms - - R- 0
vid - - R- 3
vrm - - RW 1
config - - R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
in0 SENSORS_VT8231_IN0 in0:3
in1 SENSORS_VT8231_IN1 in1:3
in2 SENSORS_VT8231_IN2 in2:3
in3 SENSORS_VT8231_IN3 in3:3
in4 SENSORS_VT8231_IN4 in4:3
in5 SENSORS_VT8231_IN5 in5:3
in0_min SENSORS_VT8231_IN0_MIN in0:1
in1_min SENSORS_VT8231_IN1_MIN in1:1
in2_min SENSORS_VT8231_IN2_MIN in2:1
in3_min SENSORS_VT8231_IN3_MIN in3:1
in4_min SENSORS_VT8231_IN4_MIN in4:1
in5_min SENSORS_VT8231_IN5_MIN in5:1
in0_max SENSORS_VT8231_IN0_MAX in0:2
in1_max SENSORS_VT8231_IN1_MAX in1:2
in2_max SENSORS_VT8231_IN2_MAX in2:2
in3_max SENSORS_VT8231_IN3_MAX in3:2
in4_max SENSORS_VT8231_IN4_MAX in4:2
in5_max SENSORS_VT8231_IN5_MAX in5:2
fan1 SENSORS_VT8231_FAN1 fan1:2
fan2 SENSORS_VT8231_FAN2 fan2:2
fan1_min SENSORS_VT8231_FAN1_MIN fan1:1
fan2_min SENSORS_VT8231_FAN2_MIN fan2:1
temp1 SENSORS_VT8231_TEMP temp1:3
temp1_hyst SENSORS_VT8231_TEMP_HYST temp1:2
temp1_over SENSORS_VT8231_TEMP_OVER temp1:1
temp2 SENSORS_VT8231_TEMP2 temp2:3
temp2_hyst SENSORS_VT8231_TEMP2_HYST temp2:2
temp2_over SENSORS_VT8231_TEMP2_OVER temp2:1
temp3 SENSORS_VT8231_TEMP3 temp3:3
temp3_hyst SENSORS_VT8231_TEMP3_HYST temp3:2
temp3_over SENSORS_VT8231_TEMP3_OVER temp3:1
temp4 SENSORS_VT8231_TEMP4 temp4:3
temp4_hyst SENSORS_VT8231_TEMP4_HYST temp4:2
temp4_over SENSORS_VT8231_TEMP4_OVER temp4:1
temp5 SENSORS_VT8231_TEMP5 temp5:3
temp5_hyst SENSORS_VT8231_TEMP5_HYST temp5:2
temp5_over SENSORS_VT8231_TEMP5_OVER temp5:1
temp6 SENSORS_VT8231_TEMP6 temp6:3
temp6_hyst SENSORS_VT8231_TEMP6_HYST temp6:2
temp6_over SENSORS_VT8231_TEMP6_OVER temp6:1
fan1_div SENSORS_VT8231_FAN1_DIV fan_div:1
fan2_div SENSORS_VT8231_FAN2_DIV fan_div:2
alarms SENSORS_VT8231_ALARMS alarms:1
vid SENSORS_VT8231_VID vid:1
vrm SENSORS_VT8231_VRM vrm:1
config SENSORS_VT8231_UCH uch_config:1
Notes
-----
The lm_sensors project gratefully acknowledges the support of VIA in the
development of this driver.
-103
View File
@@ -1,103 +0,0 @@
Kernel driver `w83627hf.o'
=========================
Status: Beta.
Supported chips:
* Winbond W83627HF (ISA accesses ONLY)
Prefix: 'w83627hf'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: http://www.winbond.com/PDF/sheet/w83627hf.pdf
* Winbond W83627THF
Prefix: 'w83627thf'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: http://www.winbond.com/PDF/sheet/w83627thf.pdf
* Winbond W83697HF
Prefix: 'w83697hf'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: http://www.winbond.com/PDF/sheet/w83697hf.pdf
* Winbond W83637HF
Prefix: 'w83637hf'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: http://www.winbond.com/PDF/sheet/w83637hf.pdf
* Winbond W83687THF
Prefix: 'w83687thf'
Addresses scanned: ISA address retrieved from Super I/O registers
Datasheet: Provided by Winbond on request
Authors: Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>,
and Mark Studebaker <mdsxyz123@yahoo.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_addr: int
Initialize the ISA address of the sensors
* force_i2c: int
Initialize the I2C address of the sensors
* force_w83627hf: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83627hf' chip
* force_w83627thf: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83627thf' chip
* force_w83697hf: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83697hf' chip
* force_w83637hf: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83637hf' chip
* force_w83687thf: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83687thf' chip
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* init=[0,1] (default 1)
Use 'init=0' to bypass initializing the chip.
Try this if your computer crashes when you load the module.
Description
-----------
This driver implements support for ISA accesses *only* for
the Winbond W83627HF, W83627THF, W83697HF, W83637HF and W83687THF
Super I/O chips. We will refer to them collectively as Winbond chips.
This driver supports ISA accesses, which should be more reliable
than i2c accesses. Also, for Tyan boards which contain both a
Super I/O chip and a second i2c-only Winbond chip (often a W83782D),
using this driver will avoid i2c address conflicts and complex
initialization that were required in the w83781d driver
(lm_sensors releases 2.7.0 and earlier).
If you really want i2c accesses for these Super I/O chips,
use the w83781d driver. However this is not the preferred method
now that this ISA driver has been developed.
The w83627_HF_ uses pins 110-106 as VID0-VID4. The w83627_THF_ uses the
same pins as GPIO[0:4]. Technically, the w83627_THF_ does not support a
VID reading. However the two chips have the identical 128 pin package. So,
it is possible or even likely for a w83627thf to have the VID signals routed
to these pins despite their not being labeled for that purpose. Therefore,
the w83627thf driver interprets these as VID. If the VID on your board
doesn't work, first see doc/vid. If that still doesn't help, you may just
ignore the bogus VID reading with no harm done.
For further information on this driver, /proc values, etc.
see the w83781d driver documentation.
Author/Maintainer
-----------------
Mark D. Studebaker (mdsxyz123@yahoo.com).
Derived from the w83781d driver.
-799
View File
@@ -1,799 +0,0 @@
Kernel driver `w83781d.o'
=========================
Status: W83781D support is complete and well-tested.
W83782D support is complete and well-tested.
W83783S support is complete but has not been well-tested.
W83791D support is BETA.
W83627HF support is complete but has not been well-tested.
AS99127F support is BETA and has known problems due to lack of a
chip datasheet. SEE BELOW.
Supported chips:
* Winbond W83781D
Prefix: 'w83781d'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/w83781d.pdf
* Winbond W83782D
Prefix: 'w83782d'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: http://www.winbond.com/PDF/sheet/w83782d.pdf
* Winbond W83783S
Prefix: 'w83783s'
Addresses scanned: I2C 0x2d
Datasheet: http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/w83783s.pdf
* Winbond W83791D
Prefix: 'w83791d'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/w83791d.pdf
* Winbond W83627HF
Prefix: 'w83627hf'
Addresses scanned: I2C 0x20 - 0x2f, ISA 0x290 (8 I/O ports)
Datasheet: http://www.winbond.com/PDF/sheet/w83627hf.pdf
* Asus AS99127F
Prefix: 'as99127f'
Addresses scanned: I2C 0x28 - 0x2f
Datasheet: Unavailable from Asus
Authors: Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>,
and Mark Studebaker <mdsxyz123@yahoo.com>
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_w83781d: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83781d' chip
* force_w83782d:
Ditto
* force_w83783s:
Ditto
* force_w83791d:
Ditto
* force_w83627hf:
Ditto
* force_as99127f:
Ditto
* force_subclients=bus,caddr,saddr,saddr
This is used to force the i2c addresses for subclients of
a certain chip. Typical usage is `force_subclients=0,0x2d,0x4a,0x4b'
to force the subclients of chip 0x2d on bus 0 to i2c addresses
0x4a and 0x4b.
This parameter is useful for certain Tyan boards.
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* init=[0,1] (default 1)
Use 'init=0' to bypass initializing the chip.
Try this if your computer crashes when you load the module.
Description
-----------
This driver implements support for the Winbond W83781D, W83782D,
W83783S, W83791D, W83627HF and W83698HF chips, and the Asus AS99127F chips.
We will refer to them collectively as W8378* chips.
There is quite some difference between these chips, but they are similar
enough that it was sensible to put them together in one driver.
The W83627HF chip is assumed to be identical to the ISA W83782D.
The Asus chips are similar to an I2C-only W83782D.
Detection of these chips can sometimes be foiled because they can be in
an internal state that allows no clean access. If you know the address
of the chip, use a 'force' parameter; this will put them into a more
well-behaved state first.
The W8378* implements temperature sensors (three on the W83781D and W83782D,
two on the W83783S), three fan rotation speed sensors, voltage sensors
(seven on the W83781D, nine on the W83782D and six on the W83783S), VID
lines, alarms with beep warnings, and some miscellaneous stuff.
Temperatures are measured in degrees Celsius. There is always one main
temperature sensor, and one (W83783S) or two (W83781D and W83782D) other
sensors. An alarm is triggered for the main sensor once when the
Overtemperature Shutdown limit is crossed; it is triggered again as soon as
it drops below the Hysteresis value. A more useful behaviour
can be found by setting the Hysteresis value to +127 degrees Celsius; in
this case, alarms are issued during all the time when the actual temperature
is above the Overtemperature Shutdown value. The driver sets the
hysteresis value for temp1 to 127 at initialization.
For the other temperature sensor(s), an
alarm is triggered when the temperature gets higher then the Overtemperature
Shutdown value; it stays on until the temperature falls below the Hysteresis
value. But on the W83781D, there is only one alarm that functions for both
other sensors! Temperatures are guaranteed within a range of -55 to +125
degrees. The main temperature sensors has a resolution of 1 degree; the other
sensor(s) of 0.5 degree.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4 or 8 for the
W83781D; 1, 2, 4, 8, 16, 32, 64 or 128 for the others) to give
the readings more range or accuracy. Not all RPM values can accurately be
represented, so some rounding is done. With a divider of 2, the lowest
representable value is around 2600 RPM.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit. Note that minimum in this case always means 'closest to
zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 4.08 volts, with a resolution
of 0.016 volt.
The VID lines encode the core voltage value: the voltage level your processor
should work with. This is hardcoded by the mainboard and/or processor itself.
It is a value in volts. When it is unconnected, you will often find the
value 3.50 V here.
The W83782D and W83783S temperature conversion machine understands about
several kinds of temperature probes. You can program the so-called
beta value in the sensor files. '1' is the PII/Celeron diode, '2' is the
TN3904 transistor, and 3435 the default thermistor value. Other values
are (not yet) supported. You can program your own values through the
rt interface, but this is not very useful, because these tables are
under NDA.
In addition to the alarms described above, there is a CHAS alarm on the
chips which triggers if your computer case is open.
When an alarm goes off, you can be warned by a beeping signal through
your computer speaker. It is possible to enable all beeping globally,
or only the beeping for some alarms.
Individual alarm and beep bits:
0x000001: in0
0x000002: in1
0x000004: in2
0x000008: in3
0x000010: temp1
0x000020: temp2 (+temp3 on W83781D)
0x000040: fan1
0x000080: fan2
0x000100: in4
0x000200: in5
0x000400: in6
0x000800: fan3
0x001000: chassis
0x002000: temp3 (W83782D and W83627HF only)
0x010000: in7 (W83782D and W83627HF only)
0x020000: in8 (W83782D and W83627HF only)
If an alarm triggers, it will remain triggered until the hardware register
is read at least once. This means that the cause for the alarm may
already have disappeared! Note that in the current implementation, all
hardware registers are read whenever any data is read (unless it is less
than 1.5 seconds since the last update). This means that you can easily
miss once-only alarms.
The chips only update values each 1.5 seconds; reading them more often
will do no harm, but will return 'old' values.
AS99127F PROBLEMS
-----------------
The as99127f support was developed without the benefit of a datasheet.
In most cases it is treated as a w83781d (although revision 2 of the
AS9917F looks more like a w83782d).
This support will be BETA until a datasheet is released.
One user has reported problems with fans stopping
occasionally.
Note that the individual beep bits are inverted from the other chips.
The driver now takes care of this so that user-space applications
don't have to know about it.
Known problems:
- Problems with diode/thermistor settings (supported?)
- One user reports fans stopping under high server load.
- Revision 2 seems to have 2 PWM registers but we don't know
how to handle them. More details below.
These will not be fixed unless we get a datasheet.
If you have problems, please lobby Asus to release a datasheet.
Unfortunately several others have without success.
Please do not send mail to us asking for better as99127f support.
We have done the best we can without a datasheet.
Please do not send mail to the author or the sensors group asking for
a datasheet or ideas on how to convince Asus. We can't help.
Chip Features
-------------
Chip `w83781d'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 NONE RW 0
fan2_min fan2 NONE RW 0
fan3_min fan3 NONE RW 0
temp1 NONE NONE R 1
temp1_hyst temp1 temp1 RW 1
temp1_over temp1 temp1 RW 1
temp2 NONE NONE R 1
temp2_hyst temp2 temp2 RW 1
temp2_over temp2 temp2 RW 1
temp3 NONE NONE R 1
temp3_hyst temp3 temp3 RW 1
temp3_over temp3 temp3 RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
beep_enable alarms NONE RW 0
beeps alarms alarms RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_W83781D_IN0 in0:3
in1 SENSORS_W83781D_IN1 in1:3
in2 SENSORS_W83781D_IN2 in2:3
in3 SENSORS_W83781D_IN3 in3:3
in4 SENSORS_W83781D_IN4 in4:3
in5 SENSORS_W83781D_IN5 in5:3
in6 SENSORS_W83781D_IN6 in6:3
in0_min SENSORS_W83781D_IN0_MIN in0:1
in1_min SENSORS_W83781D_IN1_MIN in1:1
in2_min SENSORS_W83781D_IN2_MIN in2:1
in3_min SENSORS_W83781D_IN3_MIN in3:1
in4_min SENSORS_W83781D_IN4_MIN in4:1
in5_min SENSORS_W83781D_IN5_MIN in5:1
in6_min SENSORS_W83781D_IN6_MIN in6:1
in0_max SENSORS_W83781D_IN0_MAX in0:2
in1_max SENSORS_W83781D_IN1_MAX in1:2
in2_max SENSORS_W83781D_IN2_MAX in2:2
in3_max SENSORS_W83781D_IN3_MAX in3:2
in4_max SENSORS_W83781D_IN4_MAX in4:2
in5_max SENSORS_W83781D_IN5_MAX in5:2
in6_max SENSORS_W83781D_IN6_MAX in6:2
fan1 SENSORS_W83781D_FAN1 fan1:2
fan2 SENSORS_W83781D_FAN2 fan2:2
fan3 SENSORS_W83781D_FAN3 fan3:2
fan1_min SENSORS_W83781D_FAN1_MIN fan1:1
fan2_min SENSORS_W83781D_FAN2_MIN fan2:1
fan3_min SENSORS_W83781D_FAN3_MIN fan3:1
temp1 SENSORS_W83781D_TEMP1 temp1:3
temp1_hyst SENSORS_W83781D_TEMP1_HYST temp1:2
temp1_over SENSORS_W83781D_TEMP1_OVER temp1:1
temp2 SENSORS_W83781D_TEMP2 temp2:3
temp2_hyst SENSORS_W83781D_TEMP2_HYST temp2:2
temp2_over SENSORS_W83781D_TEMP2_OVER temp2:1
temp3 SENSORS_W83781D_TEMP3 temp3:3
temp3_hyst SENSORS_W83781D_TEMP3_HYST temp3:2
temp3_over SENSORS_W83781D_TEMP3_OVER temp3:1
vid SENSORS_W83781D_VID vid:1
fan1_div SENSORS_W83781D_FAN1_DIV fan_div:1
fan2_div SENSORS_W83781D_FAN2_DIV fan_div:2
fan3_div SENSORS_W83781D_FAN3_DIV fan_div:3
alarms SENSORS_W83781D_ALARMS alarms:1
beep_enable SENSORS_W83781D_BEEP_ENABLE beep:1
beeps SENSORS_W83781D_BEEPS beep:2
Chip `w83782d'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in7 NONE NONE R 2
in8 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in7_min in7 in7 RW 2
in8_min in8 in8 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
in7_max in7 in7 RW 2
in8_max in8 in8 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 NONE RW 0
fan2_min fan2 NONE RW 0
fan3_min fan3 NONE RW 0
temp1 NONE NONE R 1
temp1_hyst temp1 temp1 RW 1
temp1_over temp1 temp1 RW 1
temp2 NONE NONE R 1
temp2_hyst temp2 temp2 RW 1
temp2_over temp2 temp2 RW 1
temp3 NONE NONE R 1
temp3_hyst temp3 temp3 RW 1
temp3_over temp3 temp3 RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
beep_enable alarms NONE RW 0
beeps alarms alarms RW 0
sensor1 NONE NONE RW 0
sensor2 NONE NONE RW 0
sensor3 NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_W83782D_IN0 in0:3
in1 SENSORS_W83782D_IN1 in1:3
in2 SENSORS_W83782D_IN2 in2:3
in3 SENSORS_W83782D_IN3 in3:3
in4 SENSORS_W83782D_IN4 in4:3
in5 SENSORS_W83782D_IN5 in5:3
in6 SENSORS_W83782D_IN6 in6:3
in7 SENSORS_W83782D_IN7 in7:3
in8 SENSORS_W83782D_IN8 in8:3
in0_min SENSORS_W83782D_IN0_MIN in0:1
in1_min SENSORS_W83782D_IN1_MIN in1:1
in2_min SENSORS_W83782D_IN2_MIN in2:1
in3_min SENSORS_W83782D_IN3_MIN in3:1
in4_min SENSORS_W83782D_IN4_MIN in4:1
in5_min SENSORS_W83782D_IN5_MIN in5:1
in6_min SENSORS_W83782D_IN6_MIN in6:1
in7_min SENSORS_W83782D_IN7_MIN in7:1
in8_min SENSORS_W83782D_IN8_MIN in8:1
in0_max SENSORS_W83782D_IN0_MAX in0:2
in1_max SENSORS_W83782D_IN1_MAX in1:2
in2_max SENSORS_W83782D_IN2_MAX in2:2
in3_max SENSORS_W83782D_IN3_MAX in3:2
in4_max SENSORS_W83782D_IN4_MAX in4:2
in5_max SENSORS_W83782D_IN5_MAX in5:2
in6_max SENSORS_W83782D_IN6_MAX in6:2
in7_max SENSORS_W83782D_IN7_MAX in7:2
in8_max SENSORS_W83782D_IN8_MAX in8:2
fan1 SENSORS_W83782D_FAN1 fan1:2
fan2 SENSORS_W83782D_FAN2 fan2:2
fan3 SENSORS_W83781D_FAN3 fan3:2
fan1_min SENSORS_W83782D_FAN1_MIN fan1:1
fan2_min SENSORS_W83782D_FAN2_MIN fan2:1
fan3_min SENSORS_W83781D_FAN3_MIN fan3:1
temp1 SENSORS_W83782D_TEMP1 temp1:3
temp1_hyst SENSORS_W83782D_TEMP1_HYST temp1:2
temp1_over SENSORS_W83782D_TEMP1_OVER temp1:1
temp2 SENSORS_W83782D_TEMP2 temp2:3
temp2_hyst SENSORS_W83782D_TEMP2_HYST temp2:2
temp2_over SENSORS_W83782D_TEMP2_OVER temp2:1
temp3 SENSORS_W83782D_TEMP3 temp3:3
temp3_hyst SENSORS_W83782D_TEMP3_HYST temp3:2
temp3_over SENSORS_W83782D_TEMP3_OVER temp3:1
vid SENSORS_W83782D_VID vid:1
fan1_div SENSORS_W83782D_FAN1_DIV fan_div:1
fan2_div SENSORS_W83782D_FAN2_DIV fan_div:2
fan3_div SENSORS_W83781D_FAN3_DIV fan_div:3
alarms SENSORS_W83782D_ALARMS alarms:1
beep_enable SENSORS_W83782D_BEEP_ENABLE beep:1
beeps SENSORS_W83782D_BEEPS beep:2
sensor1 SENSORS_W83782D_SENS1 sensor1:1
sensor2 SENSORS_W83782D_SENS2 sensor2:1
sensor3 SENSORS_W83782D_SENS3 sensor3:1
Chip `w83783s'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 2
in1 NONE NONE R 2
in2 NONE NONE R 2
in3 NONE NONE R 2
in4 NONE NONE R 2
in5 NONE NONE R 2
in6 NONE NONE R 2
in0_min in0 in0 RW 2
in1_min in1 in1 RW 2
in2_min in2 in2 RW 2
in3_min in3 in3 RW 2
in4_min in4 in4 RW 2
in5_min in5 in5 RW 2
in6_min in6 in6 RW 2
in0_max in0 in0 RW 2
in1_max in1 in1 RW 2
in2_max in2 in2 RW 2
in3_max in3 in3 RW 2
in4_max in4 in4 RW 2
in5_max in5 in5 RW 2
in6_max in6 in6 RW 2
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan1_min fan1 NONE RW 0
fan2_min fan2 NONE RW 0
fan3_min fan3 NONE RW 0
temp1 NONE NONE R 1
temp1_hyst temp1 temp1 RW 1
temp1_over temp1 temp1 RW 1
temp2 NONE NONE R 1
temp2_hyst temp2 temp2 RW 1
temp2_over temp2 temp2 RW 1
vid NONE NONE R 2
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE R 0
alarms NONE NONE R 0
beep_enable alarms NONE RW 0
beeps alarms alarms RW 0
sensor1 NONE NONE RW 0
sensor2 NONE NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_W83783S_IN0 in0:3
in1 SENSORS_W83783S_IN1 in1:3
in2 SENSORS_W83783S_IN2 in2:3
in3 SENSORS_W83783S_IN3 in3:3
in4 SENSORS_W83783S_IN4 in4:3
in5 SENSORS_W83783S_IN5 in5:3
in6 SENSORS_W83783S_IN6 in6:3
in0_min SENSORS_W83783S_IN0_MIN in0:1
in1_min SENSORS_W83783S_IN1_MIN in1:1
in2_min SENSORS_W83783S_IN2_MIN in2:1
in3_min SENSORS_W83783S_IN3_MIN in3:1
in4_min SENSORS_W83783S_IN4_MIN in4:1
in5_min SENSORS_W83783S_IN5_MIN in5:1
in6_min SENSORS_W83783S_IN6_MIN in6:1
in0_max SENSORS_W83783S_IN0_MAX in0:2
in1_max SENSORS_W83783S_IN1_MAX in1:2
in2_max SENSORS_W83783S_IN2_MAX in2:2
in3_max SENSORS_W83783S_IN3_MAX in3:2
in4_max SENSORS_W83783S_IN4_MAX in4:2
in5_max SENSORS_W83783S_IN5_MAX in5:2
in6_max SENSORS_W83783S_IN6_MAX in6:2
fan1 SENSORS_W83783S_FAN1 fan1:2
fan2 SENSORS_W83783S_FAN2 fan2:2
fan3 SENSORS_W83783S_FAN3 fan3:2
fan1_min SENSORS_W83783S_FAN1_MIN fan1:1
fan2_min SENSORS_W83783S_FAN2_MIN fan2:1
fan3_min SENSORS_W83783S_FAN3_MIN fan3:1
temp1 SENSORS_W83783S_TEMP1 temp1:3
temp1_hyst SENSORS_W83783S_TEMP1_HYST temp1:2
temp1_over SENSORS_W83783S_TEMP1_OVER temp1:1
temp2 SENSORS_W83783S_TEMP2 temp2:3
temp2_hyst SENSORS_W83783S_TEMP2_HYST temp2:2
temp2_over SENSORS_W83783S_TEMP2_OVER temp2:1
vid SENSORS_W83783S_VID vid:1
fan1_div SENSORS_W83783S_FAN1_DIV fan_div:1
fan2_div SENSORS_W83783S_FAN2_DIV fan_div:2
fan3_div SENSORS_W83783S_FAN3_DIV fan_div:3
alarms SENSORS_W83783S_ALARMS alarms:1
beep_enable SENSORS_W83783S_BEEP_ENABLE beep:1
beeps SENSORS_W83783S_BEEPS beep:2
sensor1 SENSORS_W83783S_SENS1 sensor1:1
sensor2 SENSORS_W83783S_SENS2 sensor2:1
Miscellaneous documentation
===========================
/PROC ENTRIES
-------------
Varies depending on chip type. Most are identical to the lm78 entries.
See the lm78 documentation for details.
Additional entries not in the lm78:
pwm[1-4]: (not on w83781d nor as99127f)
Controls the speed of the fans with PWM (Pulse Width Modulation)
Valid values:
0 - 255. 255 = max speed.
Second value is PWM enable 0/1.
sensor[1-3]: (not on w83781d nor as99127f)
Controls the sensor type. To change to a different
sensor type, for example, do 'echo 2 > sensor1'.
Valid values:
1: Pentium II / Celeron diode
2: 2N3904 Transistor in a diode configuration
3435: Thermistor with Beta = 3435. Beta is a measure
of sensitivity to temperature.
Thermistors with other Betas could in theory be supported
after correspondence with Winbond. See below.
rt[1-3]: (w83781d only)
32-byte tables controlling the conversion from
resistance to temperature for thermistors.
For the curious only. Writing to these entries will
probably render temperature sensing insane.
Only set at initialization; not updated every 1.5 seconds.
TO DO:
-----
782d programmable pins
NOTES:
-----
783s has no in1 so that in[2-6] are compatible with the 781d/782d.
783s pin is programmable for -5V or temp1; defaults to -5V,
no control in driver so temp1 doesn't work.
782d and 783s datasheets differ on which is pwm1 and which is pwm2.
We chose to follow 782d.
782d and 783s pin is programmable for fan3 input or pwm2 output;
defaults to fan3 input.
If pwm2 is enabled (with echo 255 1 > pwm2), then
fan3 will report 0.
782d has pwm1-2 for isa, pwm1-4 for i2c. (pwm3-4 share pins with
the isa pins)
Data sheet updates:
------------------
- PWM clock registers:
000: master / 512
001: master / 1024
010: master / 2048
011: master / 4096
100: master / 8192
Answers from Winbond tech support
---------------------------------
>
> 1) In the W83781D data sheet section 7.2 last paragraph, it talks about
> reprogramming the R-T table if the Beta of the thermistor is not
> 3435K. The R-T table is described briefly in section 8.20.
> What formulas do I use to program a new R-T table for a given Beta?
>
We are sorry that the calculation for R-T table value is
confidential. If you have another Beta value of thermistor, we can help
to calculate the R-T table for you. But you should give us real R-T
Table which can be gotten by thermistor vendor. Therefore we will calculate
them and obtain 32-byte data, and you can fill the 32-byte data to the
register in Bank0.CR51 of W83781D.
> 2) In the W83782D data sheet, it mentions that pins 38, 39, and 40 are
> programmable to be either thermistor or Pentium II diode inputs.
> How do I program them for diode inputs? I can't find any register
> to program these to be diode inputs.
--> You may program Bank0 CR[5Dh] and CR[59h] registers.
CR[5Dh] bit 1(VTIN1) bit 2(VTIN2) bit 3(VTIN3)
thermistor 0 0 0
diode 1 1 1
(error) CR[59h] bit 4(VTIN1) bit 2(VTIN2) bit 3(VTIN3)
(right) CR[59h] bit 4(VTIN1) bit 5(VTIN2) bit 6(VTIN3)
PII thermal diode 1 1 1
2N3904 diode 0 0 0
Author/Maintainer
-----------------
Original 781D support: Frodo and Phil
782D/783S support added by: Mark D. Studebaker (mdsxyz123@yahoo.com)
Asus Clones
-----------
We have no datasheets for the Asus clones (AS99127F and ASB100 Bach).
Here are some very useful information that were given to us by Alex Van
Kaam about how to detect these chips, and how to read their values. He
also gives advice for another Asus chipset, the Mozart-2 (which we
don't support yet). Thanks Alex!
I reworded some parts and added personal comments.
# Detection:
AS99127F rev.1, AS99127F rev.2 and ASB100:
- I2C address range: 0x29 - 0x2F
- If register 0x58 holds 0x31 then we have an Asus (either ASB100 or
AS99127F)
- Which one depends on register 0x4F (manufacturer ID):
0x06 or 0x94: ASB100
0x12 or 0xC3: AS99127F rev.1
0x5C or 0xA3: AS99127F rev.2
Note that 0x5CA3 is Winbond's ID (WEC), which let us think Asus get their
AS99127F rev.2 direct from Winbond. The other codes mean ATT and DVC,
respectively. ATT could stand for Asustek something (although it would be
very badly chosen IMHO), I don't know what DVC could stand for. Maybe
these codes simply aren't meant to be decoded that way.
Mozart-2:
- I2C address: 0x77
- If register 0x58 holds 0x56 or 0x10 then we have a Mozart-2
- Of the mozart there are 3 types:
0x58=0x56, 0x4E=0x94, 0x4F=0x36: Asus ASM58 Mozart-2
0x58=0x56, 0x4E=0x94, 0x4F=0x06: Asus AS2K129R Mozart-2
0x58=0x10, 0x4E=0x5C, 0x4F=0xA3: Asus ??? Mozart-2
You can handle all 3 the exact same way :)
# Temperature sensors:
ASB100:
- sensor 1: register 0x27
- sensor 2 & 3 are the 2 LM75's on the SMBus
- sensor 4: register 0x17
Remark: I noticed that on Intel boards sensor 2 is used for the CPU
and 4 is ignored/stuck, on AMD boards sensor 4 is the CPU and sensor 2 is
either ignored or a socket temperature.
AS99127F (rev.1 and 2 alike):
- sensor 1: register 0x27
- sensor 2 & 3 are the 2 LM75's on the SMBus
Remark: Register 0x5b is suspected to be temperature type selector. Bit 1
would control temp1, bit 3 temp2 and bit 5 temp3.
Mozart-2:
- sensor 1: register 0x27
- sensor 2: register 0x13
# Fan sensors:
ASB100, AS99127F (rev.1 and 2 alike):
- 3 fans, identical to the W83781D
Mozart-2:
- 2 fans only, 1350000/rpm/div
- fan 1: register 0x28, divisor on register 0xA1 (bits 4-5)
- fan 2: register 0x29, divisor on register 0xA1 (bits 6-7)
# Voltages:
This is where there is a difference between AS99127F rev.1 and 2.
Remark: The difference is similar to the difference between
W83781D and W83782D.
ASB100:
in0=r(0x20)*0.016
in1=r(0x21)*0.016
in2=r(0x22)*0.016
in3=r(0x23)*0.016*1.68
in4=r(0x24)*0.016*3.8
in5=r(0x25)*(-0.016)*3.97
in6=r(0x26)*(-0.016)*1.666
AS99127F rev.1:
in0=r(0x20)*0.016
in1=r(0x21)*0.016
in2=r(0x22)*0.016
in3=r(0x23)*0.016*1.68
in4=r(0x24)*0.016*3.8
in5=r(0x25)*(-0.016)*3.97
in6=r(0x26)*(-0.016)*1.503
AS99127F rev.2:
in0=r(0x20)*0.016
in1=r(0x21)*0.016
in2=r(0x22)*0.016
in3=r(0x23)*0.016*1.68
in4=r(0x24)*0.016*3.8
in5=(r(0x25)*0.016-3.6)*5.14+3.6
in6=(r(0x26)*0.016-3.6)*3.14+3.6
Mozart-2:
in0=r(0x20)*0.016
in1=255
in2=r(0x22)*0.016
in3=r(0x23)*0.016*1.68
in4=r(0x24)*0.016*4
in5=255
in6=255
# PWM
Additional info about PWM on the AS99127F (may apply to other Asus
chips as well) by Jean Delvare as of 2004-04-09:
AS99127F revision 2 seems to have two PWM registers at 0x59 and 0x5A,
and a temperature sensor type selector at 0x5B (which basically means
that they swapped registers 0x59 and 0x5B when you compare with Winbond
chips).
Revision 1 of the chip also has the temperature sensor type selector at
0x5B, but PWM registers have no effect.
We don't know exactly how the temperature sensor type selection works.
Looks like bits 1-0 are for temp1, bits 3-2 for temp2 and bits 5-4 for
temp3, although it is possible that only the most significant bit matters
each time. So far, values other than 0 always broke the readings.
PWM registers seem to be split in two parts: bit 7 is a mode selector,
while the other bits seem to define a value or threshold.
When bit 7 is clear, bits 6-0 seem to hold a threshold value. If the value
is below a given limit, the fan runs at low speed. If the value is above
the limit, the fan runs at full speed. We have no clue as to what the limit
represents. Note that there seem to be some inertia in this mode, speed
changes may need some time to trigger. Also, an hysteresis mechanism is
suspected since walking through all the values increasingly and then
decreasingly led to slightly different limits.
When bit 7 is set, bits 3-0 seem to hold a threshold value, while bits 6-4
would not be significant. If the value is below a given limit, the fan runs
at full speed, while if it is above the limit it runs at low speed (so this
is the contrary of the other mode, in a way). Here again, we don't know
what the limit is supposed to represent.
One remarkable thing is that the fans would only have two or three
different speeds (transitional states left apart), not a whole range as
you usually get with PWM.
As a conclusion, you can write 0x00 or 0x8F to the PWM registers to make
fans run at low speed, and 0x7F or 0x80 to make them run at full speed.
Please contact us if you can figure out how it is supposed to work. As
long as we don't know more, the w83781d driver doesn't handle PWM on
AS99127F chips at all.
Additional info about PWM on the AS99127F rev.1 by Hector Martin:
I've been fiddling around with the (in)famous 0x59 register and
found out the following values do work as a form of coarse pwm:
0x80 - seems to turn fans off after some time(1-2 minutes)... might be
some form of auto-fan-control based on temp? hmm (Qfan? this mobo is an
old ASUS, it isn't marketed as Qfan. Maybe some beta pre-attempt at Qfan
that was dropped at the BIOS)
0x81 - off
0x82 - slightly "on-ner" than off, but my fans do not get to move. I can
hear the high-pitched PWM sound that motors give off at too-low-pwm.
0x83 - now they do move. Estimate about 70% speed or so.
0x84-0x8f - full on
Changing the high nibble doesn't seem to do much except the high bit
(0x80) must be set for PWM to work, else the current pwm doesn't seem to
change.
My mobo is an ASUS A7V266-E. This behaviour is similar to what I got
with speedfan under Windows, where 0-15% would be off, 15-2x% (can't
remember the exact value) would be 70% and higher would be full on.
-298
View File
@@ -1,298 +0,0 @@
Kernel driver `w83792d.o'
=========================
Status: W83792D support is complete and well-tested.
Supported chips:
* Winbond W83792D
Prefix: 'w83792d'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: http://www.winbond.com.tw/E-WINBONDHTM/partner/PDFresult.asp?Pname=1035
Author: Chunhao Huang
Contact: DZShen <DZShen@Winbond.com.tw>
Module Parameters
-----------------
* init int
(default 1)
Use 'init=0' to bypass initializing the chip.
Try this if your computer crashes when you load the module.
* force_subclients=bus,caddr,saddr,saddr
This is used to force the i2c addresses for subclients of
a certain chip. Example usage is `force_subclients=0,0x2f,0x4a,0x4b'
to force the subclients of chip 0x2f on bus 0 to i2c addresses
0x4a and 0x4b.
Description
-----------
This driver implements support for the Winbond W83792AD/D.
Detection of the chip can sometimes be foiled because it can be in an
internal state that allows no clean access (Bank with ID register is not
currently selected). If you know the address of the chip, use a 'force'
parameter; this will put it into a more well-behaved state first.
The driver implements three temperature sensors, seven fan rotation speed
sensors, nine voltage sensors, and two automatic fan regulation
strategies called: Smart Fan I (Thermal Cruise mode) and Smart Fan II.
Automatic fan control mode is possible only for fan1-fan3. Fan4-fan7 can run
synchronized with selected fan (fan1-fan3). This functionality and manual PWM
control for fan4-fan7 is not yet implemented.
Temperatures are measured in degrees Celsius and measurement resolution is 1
degC for temp1 and 0.5 degC for temp2 and temp3. An alarm is triggered when
the temperature gets higher than the Overtemperature Shutdown value; it stays
on until the temperature falls below the Hysteresis value.
Fan rotation speeds are reported in RPM (rotations per minute). An alarm is
triggered if the rotation speed has dropped below a programmable limit. Fan
readings can be divided by a programmable divider (1, 2, 4, 8, 16, 32, 64 or
128) to give the readings more range or accuracy. This driver modifies the
fan divisor automatically to get the reasonable RPM rotation range.
Voltage sensors (also known as IN sensors) report their values in volts.
An alarm is triggered if the voltage has crossed a programmable minimum
or maximum limit.
Alarms are provided as output from "realtime status register". Following bits
are defined:
bit - alarm on:
0 - in0
1 - in1
2 - temp1
3 - temp2
4 - temp3
5 - fan1
6 - fan2
7 - fan3
8 - in2
9 - in3
10 - in4
11 - in5
12 - in6
13 - VID change
14 - chassis
15 - fan7
16 - tart1
17 - tart2
18 - tart3
19 - in7
20 - in8
21 - fan4
22 - fan5
23 - fan6
Tart will be asserted while target temperature cannot be achieved after 3 minutes
of full speed rotation of corresponding fan.
In addition to the alarms described above, there is a CHAS alarm on the chips
which triggers if your computer case is open (This one is latched, contrary
to realtime alarms).
The chips only update values each 3 seconds; reading them more often will
do no harm, but will return 'old' values.
W83792D PROBLEMS
----------------
Known problems:
- This driver is only for Winbond W83792D C version device, there
are also some motherboards with B version W83792D device. The
calculation method to in6-in7(measured value, limits) is a little
different between C and B version. C or B version can be identified
by CR[0x49h].
- The function of vid and vrm has not been finished, because I'm NOT
very familiar with them. Adding support is welcome.
- The function of chassis open detection needs more tests.
- If you have ASUS server board and chip was not found: Then you will
need to upgrade to latest (or beta) BIOS. If it does not help please
contact us.
Chip Features
-------------
Chip `w83792d'
LABEL LABEL CLASS COMPUTE CLASS ACCESS MAGNITUDE
in0 NONE NONE R 3
in1 NONE NONE R 3
in2 NONE NONE R 3
in3 NONE NONE R 3
in4 NONE NONE R 3
in5 NONE NONE R 3
in6 NONE NONE R 3
in7 NONE NONE R 3
in8 NONE NONE R 3
in0_min in0 in0 RW 3
in1_min in1 in1 RW 3
in2_min in2 in2 RW 3
in3_min in3 in3 RW 3
in4_min in4 in4 RW 3
in5_min in5 in5 RW 3
in6_min in6 in6 RW 3
in7_min in7 in7 RW 3
in8_min in8 in8 RW 3
in0_max in0 in0 RW 3
in1_max in1 in1 RW 3
in2_max in2 in2 RW 3
in3_max in3 in3 RW 3
in4_max in4 in4 RW 3
in5_max in5 in5 RW 3
in6_max in6 in6 RW 3
in7_max in7 in7 RW 3
in8_max in8 in8 RW 3
fan1 NONE NONE R 0
fan2 NONE NONE R 0
fan3 NONE NONE R 0
fan4 NONE NONE R 0
fan5 NONE NONE R 0
fan6 NONE NONE R 0
fan7 NONE NONE R 0
fan1_min fan1 NONE RW 0
fan2_min fan2 NONE RW 0
fan3_min fan3 NONE RW 0
fan4_min fan4 NONE RW 0
fan5_min fan5 NONE RW 0
fan6_min fan6 NONE RW 0
fan7_min fan7 NONE RW 0
temp1 NONE NONE R 1
temp1_hyst temp1 temp1 RW 1
temp1_over temp1 temp1 RW 1
temp2 NONE NONE R 1
temp2_hyst temp2 temp2 RW 1
temp2_over temp2 temp2 RW 1
temp3 NONE NONE R 1
temp3_hyst temp3 temp3 RW 1
temp3_over temp3 temp3 RW 1
fan1_div fan1 NONE RW 0
fan2_div fan2 NONE RW 0
fan3_div fan3 NONE RW 0
fan4_div fan4 NONE RW 0
fan5_div fan5 NONE RW 0
fan6_div fan6 NONE RW 0
fan7_div fan7 NONE RW 0
LABEL FEATURE SYMBOL SYSCTL FILE:NR
in0 SENSORS_W83792D_IN0 in0:3
in1 SENSORS_W83792D_IN1 in1:3
in2 SENSORS_W83792D_IN2 in2:3
in3 SENSORS_W83792D_IN3 in3:3
in4 SENSORS_W83792D_IN4 in4:3
in5 SENSORS_W83792D_IN5 in5:3
in6 SENSORS_W83792D_IN6 in6:3
in7 SENSORS_W83792D_IN7 in7:3
in8 SENSORS_W83792D_IN8 in8:3
in0_min SENSORS_W83792D_IN0_MIN in0:1
in1_min SENSORS_W83792D_IN1_MIN in1:1
in2_min SENSORS_W83792D_IN2_MIN in2:1
in3_min SENSORS_W83792D_IN3_MIN in3:1
in4_min SENSORS_W83792D_IN4_MIN in4:1
in5_min SENSORS_W83792D_IN5_MIN in5:1
in6_min SENSORS_W83792D_IN6_MIN in6:1
in7_min SENSORS_W83792D_IN7_MIN in7:1
in8_min SENSORS_W83792D_IN8_MIN in8:1
in0_max SENSORS_W83792D_IN0_MAX in0:2
in1_max SENSORS_W83792D_IN1_MAX in1:2
in2_max SENSORS_W83792D_IN2_MAX in2:2
in3_max SENSORS_W83792D_IN3_MAX in3:2
in4_max SENSORS_W83792D_IN4_MAX in4:2
in5_max SENSORS_W83792D_IN5_MAX in5:2
in6_max SENSORS_W83792D_IN6_MAX in6:2
in7_max SENSORS_W83792D_IN7_MAX in7:2
in8_max SENSORS_W83792D_IN8_MAX in8:2
fan1 SENSORS_W83792D_FAN1 fan1:2
fan2 SENSORS_W83792D_FAN2 fan2:2
fan3 SENSORS_W83792D_FAN3 fan3:2
fan4 SENSORS_W83792D_FAN4 fan4:2
fan5 SENSORS_W83792D_FAN5 fan5:2
fan6 SENSORS_W83792D_FAN6 fan6:2
fan7 SENSORS_W83792D_FAN7 fan7:2
fan1_min SENSORS_W83792D_FAN1_MIN fan1:1
fan2_min SENSORS_W83792D_FAN2_MIN fan2:1
fan3_min SENSORS_W83792D_FAN3_MIN fan3:1
fan4_min SENSORS_W83792D_FAN4_MIN fan4:1
fan5_min SENSORS_W83792D_FAN5_MIN fan5:1
fan6_min SENSORS_W83792D_FAN6_MIN fan6:1
fan7_min SENSORS_W83792D_FAN7_MIN fan7:1
temp1 SENSORS_W83792D_TEMP1 temp1:3
temp1_hyst SENSORS_W83792D_TEMP1_HYST temp1:2
temp1_over SENSORS_W83792D_TEMP1_OVER temp1:1
temp2 SENSORS_W83792D_TEMP2 temp2:3
temp2_hyst SENSORS_W83792D_TEMP2_HYST temp2:2
temp2_over SENSORS_W83792D_TEMP2_OVER temp2:1
temp3 SENSORS_W83792D_TEMP3 temp3:3
temp3_hyst SENSORS_W83792D_TEMP3_HYST temp3:2
temp3_over SENSORS_W83792D_TEMP3_OVER temp3:1
fan1_div SENSORS_W83792D_FAN1_DIV fan_div:1
fan2_div SENSORS_W83792D_FAN2_DIV fan_div:2
fan3_div SENSORS_W83792D_FAN3_DIV fan_div:3
fan4_div SENSORS_W83792D_FAN4_DIV fan_div:4
fan5_div SENSORS_W83792D_FAN5_DIV fan_div:5
fan6_div SENSORS_W83792D_FAN6_DIV fan_div:6
fan7_div SENSORS_W83792D_FAN7_DIV fan_div:7
/PROC ENTRIES
-------------
Most are identical to the w83781d entries. See the w83781d documentation for
details.
Most of the additional entries not in the w83781d are Winbond Smart Fan I/II:
Please read "7.7 FAN Speed Count and FAN Speed Control" of W83792AD/D datasheet
before you read the explanation to the /proc entries below.
fan_cfg: indicates the fan speed control mode: 0 means PWM/DC mode,
1 means Smart Fan I(Thermal Cruise) mode, 2 or 3 means Smart Fan II
mode. There are three items in this entry, the first one is the
configuration of Fan1, the rest may be deduced by analogy.
pwm_flag: valid under PWM/DC mode, 1 means PWM mode, 0 means DC mode.
There are three items in this entry, the first one is PWM/DC mode
of Fan1, the rest may be deduced by analogy.
thermal_cruise: valid under Smart Fan I mode, indicates the wanted
temperature. There are three items in this entry, the first one
is setting temperature of Fan1, the rest may be deduced by analogy.
fan_tolerance: valid under Smart Fan I or Smart Fan II mode, indicates
the interval temperature. There are also three items in this entry.
sf2_points_fan1: valid under Smart Fan II mode, there are four items in
this entry, indicates the Temp.[1-4] in "Figure 8. Smart Fan II
behavior" to Fan1.
sf2_points_fan2: almost same as the above one, except that this entry
is for Fan2.
sf2_points_fan3: almost same as the above one, except that this entry
is for Fan3.
sf2_levels_fan1: valid under Smart Fan II mode, there are four items in
this entry, indicates the "Non-Stop Duty Cycle" and "Fan Duty Cycle
Level [1-3]" in "Figure 8. Smart Fan II behavior" to Fan1. The
first item "Non-Stop Duty Cycle" is read only in this driver, while
the other three items can be modified.
sf2_levels_fan2: almost same as the above one, except that this entry
is for Fan2.
sf2_levels_fan3: almost same as the above one, except that this entry
is for Fan3.
additional entries for chassis intrusion detection:
chassis: There are two items in this entry, the first one indicats the
chassis status, 1 means chassis intrusion detected, while 1 means
normal. The second item in this entry is used to clear the
detected chassis intrusion interrupt and reset the chassis status
to normal.
-77
View File
@@ -1,77 +0,0 @@
Kernel driver `w83l785ts.o'
===========================
Status: Beta, slightly tested
Supported chips:
* Winbond W83L785TS-S
Prefix: 'w83l785ts'
Addresses scanned: I2C 0x2e
Datasheet: Publicly available at the Winbond USA website
http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83L785TS-S.pdf
Author: Jean Delvare <khali@linux-fr.org>
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_w83l785ts: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `w83l785ts' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
Description
-----------
The W83L785TS-S is a digital temperature sensor. It senses the
temperature of a single external diode. The high limit is
theoretically defined as 85 or 100 degrees C through a combination
of external resistors, so the user cannot change it. Values seen so
far suggest that the two possible limits are actually 95 and 110
degrees C. The datasheet is rather poor and obviously inaccurate
on several points including this one.
All temperature values are given in degrees Celsius. Resolution
is 1.0 degree. See the datasheet for details.
The w83l785ts driver will not update its values more frequently than
every other second; reading them more often will do no harm, but will
return 'old' values.
Known Issues
------------
On some systems (Asus), the BIOS is known to interfere with the driver
and cause read errors. The driver will retry a given number of times
(5 by default) and then give up, returning the old value (or 0 if
there is no old value). It seems to work well enough so that you should
not notice anything. Thanks to James Bolt for helping test this feature.
Chip Features
-------------
Chip 'w83l785ts'
LABEL LABEL CLASS COMPUTE CLASS MODE MAGN
temp - - R- 0
temp_high temp temp R- 0
LABEL FEATURE SYMBOL SYSCTL FILE:N
temp SENSORS_W83L785TS_TEMP temp:2
temp_high SENSORS_W83L785TS_TEMP_OVER temp:1
-108
View File
@@ -1,108 +0,0 @@
Kernel driver `xeontemp.o'
=========================
Status: Complete; tested for most devices except Xeon.
Supported chips:
* Intel Xeon and other processors
Prefix: 'xeontemp'
Addresses scanned: I2C 0x18, 0x1a, 0x29, 0x2b, 0x4c, 0x4e
Datasheets: Publicly available at the Intel website
See the adm1021 document for additional references
Authors: Frodo Looijaard <frodol@dds.nl>,Philip Edelbrock
<phil@netroedge.com>, and Mark D. Studebaker (mdsxyz123@yahoo.com)
License: GPL
Module Parameters
-----------------
* force: short array (min = 1, max = 48)
List of adapter,address pairs to boldly assume to be present
* force_xeontemp: short array (min = 1, max = 48)
List of adapter,address pairs which are unquestionably assumed to contain
a `xeontemp' chip
* probe: short array (min = 1, max = 48)
List of adapter,address pairs to scan additionally
* probe_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples to scan additionally
* ignore: short array (min = 1, max = 48)
List of adapter,address pairs not to scan
* ignore_range: short array (min = 1, max = 48)
List of adapter,start-addr,end-addr triples not to scan
* read_only: int
Don't set any values, read only mode
Description
-----------
Many Xeons and other Intel processors have an embedded i2c temperature
sensor that look very similar to an adm1021 or max1617.
Some Xeons have honest to goodness, real live, actual MAX1617 chips on
them. Others Xeons made around the same time have other ADM1021
compatible chips on them. They are mounted on the CPU substrate along
with the processor information EEPROM that is part of that generation of
Xeon CPU's.
So it's basically impossible to tell a Xeon TEMP sensor from a MAX1617
since they are in fact the same chip. The Xeon Temperature sensors spec
is just a stripped down register spec for the ADM1021 that says the
"on-board" or "built-in" sensor registers are "reserved". That way
Intel could substitute whatever ADM1021 compatible chip was cheapest at
the time when they built the CPU's.
One reason to use the xeontemp driver is if, when Intel built your
particular Xeon's, they didn't use any of the ADM1021 compatible chips
that the ADM1021 driver recognizes. In that case, using the xeontemp
driver will get you the temperatures you want without potentially
messing up something in the sensor.
Another reason is to avoid having 'sensors' report the second
temperature.
Unfortunately, the 533MHz FSB Xeon's, dropped the on-board sensor chip.
So these new Xeons require a sensor on the motherboard to be connected
to the thermal diode to read the temperature. But these 533MHz capable
motherboards can also accept 400MHz CPU's with the on-board sensor so it
gets confusing in a hurry for motherboard vendors and users.
If you have 400MHz FSB P4 Xeon CPU's, you should have an on-CPU ADM1021
compatible sensor at 0x18 or 0x19. In that case, use the ADM1021 driver
if it detects a chip at that address. The CPU DIE temperature (Tj) will
be the "REMOTE" temperature from this sensor. The "Board" or "built-in"
temperature from the ADM1021 will be the temperature of the ADM1021
compatible itself which is one of the chips you see on the side of the CPU.
If you have sensors at 0x18 and 0x19 that are not detected by the
ADM1021, and you have 400MHz Xeons, then you may want to try the
xeontemp driver.
If you have 533MHZ FSB Xeons, then you do *not* have an on-board thermal
sensor and you should look for CPU temperatures from the other sensors
on your motherboard. (Winbond chip for example).
If you have an Intel Xeon processor, and sensors-detect recommended
the adm1021 driver, and the address is one of
0x18, 0x1a, 0x29, 0x2b, 0x4c, or 0x4e, and when using the
adm1021 driver, temp1 is invalid but temp2 looks correct, then
it is likely you should use the xeontemp driver instead.
If you have dual Xeons you may have appear to have two separate
adm1021-compatible chips, or two single-temperature sensors,
at distinct addresses.
In summary, the xeontemp module supports one temperature and the
adm1021 module supports two. Use the module that works best for you.
See the adm1021 documentation for more information.
Author/Maintainer
-----------------
Mark D. Studebaker (mdsxyz123@yahoo.com).
Derived from the adm1021 driver.
Thanks to Philip Pokorny for help with this document.
-550
View File
@@ -1,550 +0,0 @@
This is a design for version 2 of our smbus and lm_sensors module. This
document is Copyright (c) 1998, 1999 by Frodo Looijaard. You may freely copy and
distribute it, as long as you recognize me as the author, and mark any
changes you make as being your own, and distribute this notice with it.
Document version 1.0, 19981101.
1.1, 19981111.
1.2, 19981118.
1.3, 19981126.
NOTE: THIS IS BY NOW SOMEWHAT OUTDATED. SORRY.
Object oriented approach
========================
The i2c module structs contain callback functions and data fields. In the
i2c module, these structures are only referenced by pointers. This makes
it easy to extend these structs to contain additional information or
callback functions. For those familiar with object oriented languages,
you can see the smbus and isa structures as an object extension of the i2c
structures.
To make this clearer, I will show in an example how this is done. Note
that I have simplified some things here, so this example does not
correspond to an actual struct found in one of the modules.
In the i2c module, you can find a struct somewhat like this:
struct i2c_adapter {
char name[32];
int (*detach_client) (struct i2c_client *);
struct i2c_adapter *next;
}
We have a plain data field (name), a call-back function which needs one
parameter (a pointer to a i2c_client struct), and a data field which is
a pointer to the next adapter.
Now we want to extend this structure. We need another data field,
containing a number of flags. We will call this new structure smbus_adapter.
A few other things change too:
struct smbus_adapter {
char name[32];
int (*detach_client) (struct smbus_client *);
struct smbus_adapter *next;
unsigned int flags;
}
So we copy all existing fields. The next field still points to the next
adapter - but it is now of type smbus_adapter, not i2c_adapter. And the
call-back function takes now a parameter of type pointer to smbus_client.
And there is a new data field, called flags.
Now examine this function definition:
int fully_detach_i2c_client (struct i2c_client * client)
{
res = 0;
struct i2c_adapter *current_adapter = first_adapter; /* a global var. */
while (current_adapter) {
res |= (current_adapter -> detach_client) (client);
current_adapter = current_adapter -> next;
}
return res;
}
This function detaches a client from all adapters. Nice. But now comes the
Big Trick(tm): we can still use this function for smbus_adapters!
int fully_detach_smbus_client (struct smbus_client * client)
{
return fully_detach_i2c_client( (struct i2c_client *) client);
}
All we needed here was a simple typecast. Datapointers remain datapointers,
so this can safely be done. And because we use call-back functions, the
actual function called to detach a client from a single adapter might
be the same, or different, depending on what function was put in
client->detach_client!
It gets even better. The i2c module stores internally all registered
adapters. But it stores pointers to the i2c_adapter struct, not the
structs themselves! So there is an array:
#define I2C_ADAPTER_MAX 32
struct i2c_adapter *adapters[I2C_ADAPTER_MAX];
/* this is an array of pointers to structs, not vice versa! */
So, an element of this array might in fact point to a smbus_adapter, instead
of an i2c_adapter! If you know this for sure, you might use a typecast and
access the additional fields. In the meantime, the i2c internal
administration remains valid.
We have to thank Simon Vogl and Gerd Knorr for the way they implemented
their i2c module. Any other way would have made this approach impossible,
and basing anything upon their module much more difficult.
Limitations
-----------
Extending the adapter and algorithm structures in this way is quite safe.
They are only allocated on places where the code knows that they are
'special'. Extending the driver or client structures depending on a
specific adapter/algorithm type is *very* *dangerous*. A driver/client
module would need to be aware of every special adapter/algorithm in
order to allocate itself! For the ISA bus, it has to be aware of this
anyway, so it is safe to do; on other places, think twice first!
Module overview
===============
All in all, lots of modules will be stacked on each other. Too bad, but
that is the only way to cleanly implement everything. Note that in a
specific situation, only a few modules may need to be loaded. isa.o,
for example, does not depend on smbus.o (in the sense that you can load
sensor.o without loading smbus.o). A specific bus driver, though, will
depend on many of them.
Generally:
isa.o depends on nothing (actually, on i2c.o, to keep the code small)
smbus.o depends on nothing (actually, on i2c.o, to keep the code small)
i2c.o depends on nothing.
A non-i2c SMBus bus driver depends only on smbus.o
A i2c bus driver depends only on i2c.o
A sensor chip driver depends either on isa.o or smbus.o, or both.
A SMBus chip driver depends only on smbus.o
A I2C chip driver depends only on i2c.o
We may need a sensor.o module, to act as a central point for sensor
modules. At this moment, it seems not really necessary, but this may
change.
We will need an enhanced i2c-dev.o module, to add SMBus access to I2C
/dev entries.
isa.o
ISA bus handling.
Encapsulates ISA bus access within the i2c structures.
Unites I2C adapters and the ISA bus.
Defines variables isa_algorithm and isa_adapter.
smbus.o
Main SMBus handling.
Encapsulates SMBus access within the smbus structures.
Unites I2C adapters and SMBus hosts (like the PIIX4).
Emulates SMBus access on pure I2C adapters.
Defines variable smbus_algorithm.
piix4.o
SMBus adapter driver for the PIIX4 SMBus host.
Defines variable piix4_adapter (based on smbus_algorithm).
FOO.o
Adapter driver for FOO SMBus host
Defines variable FOO_adapter (based on smbus_algorithm).
i2c-core.o (From Simon Vogl)
Main i2c handling.
????.o
I2C adapter driver
Implementing a class of I2C busses
A chip driver (typically defined in its own module) can be hooked on all
these levels:
* If it is a sensor chip, it should be hooked to isa.o or smbus.o
* A pure ISA chip should be hooked to isa.o
* A pure SMBus chip should be hooked to smbus.o
* An I2C chip should be hooked to i2c.o
It can be difficult to decide whether a specific chip should be hooked to
smbus.o or i2c.o. A good deciding question is, 'could it be connected to
a PIIX4?'
Module i2c.o
============
This is Simon Vogl's i2c module (this one is different from the i2c module
included in kernels around 2.1.120!).
A driver
--------
struct i2c_driver {
char name[32];
int id;
unsigned int flags;
int (* attach_adapter) (struct i2c_adapter *);
int (* detach_client) (struct i2c_client *);
int (* command) (struct i2c_client *, unsigned int cmd, void *arg);
void (* inc_use) (struct i2c_client *);
void (* dec_use) (struct i2c_client *);
}
A driver tells us how we should handle a specific type of i2c chip. An
instance of such a chip is called a 'client'. An example would be the LM75
module: it would contain only one driver, which tells us how to handle the
LM75; but each detected LM75 would be a separate client (which would be
dynamically allocated).
A description of the above struct:
name: The name of this driver
id: A unique driver identifier
flags: Flags to set certain kinds of behaviour. Most notably, DF_NOTIFY
will notify the driver when a new i2c bus is detected, so it can
try to detect chips on it.
attach_adapter: A call-back function which is called if a new adapter (bus)
is found. This allows us to do our detection stuff on the new adapter,
and register new clients.
detach_client: A call-back function which is called if a specific client
which uses this driver should be disallocated. If a specific sensor module
is removed, for instance, this function would be called for each registered
client.
command: A generic call-back function to communicate in a driver-dependent
way with a specific client. This should only be seldom needed.
inc_use: Can be called to add one to an internal 'use' counter. This can be
used to control when it is safe to remove this module, for example.
A client
--------
struct i2c_client {
char name[32];
int id;
unsigned int flags;
unsigned char addr;
struct i2c_adapter *adapter;
struct i2c_driver *driver;
void *data;
}
A client is a specific sensor chip. Its operation is controlled by a driver
(which describes a type of sensor chip), and it is connected to an adapter
(a bus).
A description of the above struct:
name: The name of this client
id: A unique client id
flags: Flags to set certain kinds of behaviour (not very important)
addr: The client address. 10-bit addresses are a bit of a kludge right now.
adapter: A pointer to the adapter this client is on.
driver: A pointer to the driver this client uses.
data: Additional, client-dependent data
An algorithm
------------
struct i2c_algorithm {
char name[32];
unsigned int id;
int (* master_xfer) (struct i2c_adapter *adap, struct i2c_msg msgs[],
int num);
int (* slave_send) (struct i2c_adapter *,char *, int);
int (* slave_recv) (struct i2c_adapter *,char *, int);
int (* algo_control) (struct i2c_adapter *, unsigned int, unsigned long);
int (* client_register) (struct i2c_client *);
int (* client_unregister) (struct i2c_client *);
}
An algorithm describes how a certain class of i2c busses can be accessed.
A description of the above struct:
name: The name of this algorithm
id: A unique algorithm id
master_xfer: Transfer a bunch of messages through a specific i2c bus.
slave_send: Send a message as if we are a slave device
slave_recv: Receive a message as if we are a slave device
client_register: Register a new client
client_unregister: Unregister a client
An adapter
----------
struct i2c_adapter {
char name[32];
unsigned int id;
struct i2c_algorithm *algo;
void *data;
#ifdef SPINLOCK
spinlock_t lock;
unsigned long lockflags;
#else
struct semaphore lock;
#endif
unsigned int flags;
struct i2c_client *clients[I2C_CLIENT_MAX];
int client_count;
int timeout;
int retries;
}
An adapter is a specific i2c bus. How to access it is defined in the
algorithm associated with it.
A description of the above struct:
name: The name of this adapter
id: A unique adapter id
algo: The algorithm through which this bus must be accessed
data: Adapter specific data
lock, lockflags: To lock out simultaneous adapter access
flags: Modifiers for adapter operation
clients: All currently connected clients
client_count: The number of currently connected clients
timeout, retries: Internal variables (unimportant here).
Access functions
----------------
All these functions are defined extern.
int i2c_master_send(struct i2c_client *,const char *, int);
int i2c_master_recv(struct i2c_client *,char *, int);
int i2c_transfer(struct i2c_adapter *,struct i2c_msg [], int num);
These function respectively send one message to a client, receive one message
from a client, or transmit a bunch of messages. struct i2c_msg contains
an i2c address to communicate with, and can both read from and write to this
address.
int i2c_slave_send(struct i2c_client *, char *, int);
int i2c_slave_recv(struct i2c_client *, char *, int);
Communicate with another master as if the normal master is a common slave
device.
Administration functions
------------------------
int i2c_add_algorithm(struct i2c_algorithm *);
int i2c_del_algorithm(struct i2c_algorithm *);
The i2c_{add,del}_algorithm functions must be called whenever a new module
is inserted with this driver in it, by the module initialization function.
int i2c_add_adapter(struct i2c_adapter *);
int i2c_del_adapter(struct i2c_adapter *);
The i2c_{add,del}_adapter functions must be called if you have detected
a specific bus. It triggers driver->attach_adapter (add, for each driver
present) or driver->detach_client (del, for each registered client on
this adapter).
int i2c_add_driver(struct i2c_driver *);
int i2c_del_driver(struct i2c_driver *);
The i2c_{add,del}_driver functions must be called whenever a new module is
inserted with a chip driver in it, by the module initialization function.
int i2c_attach_client(struct i2c_client *);
int i2c_detach_client(struct i2c_client *);
The i2c_{attach,detach}_client functions must be called if you have detected
a single chip.
Module smbus.o
==============
This module offers support for SMBus operation. An SMBus adapter can either
accept SMBus commands (like the PIIX4), or be in fact an I2C driver. In
the last case, all SMBus commands will be expressed through I2C primitives.
This means that any I2C adapter driver will automatically be a SMBus
driver.
At this point, it should be noted that there can only be one System
Management Bus in a given system (is this really true, by the way?). This
means there must be some way of selecting which of the many possible adapters
is in fact *the* SMBus. For now, I will ignore this problem. Later on,
we can add a hook somewhere in the i2c module to help us decide this.
This module consists in fact of three separate parts: first of all, it extends
all i2c structs to accomodate the new smbus fields. Second, it defines a
new algorithm (smbus_algorithm), that will be used by all non-i2c adapters.
Finally, it implements a new access function that sends or receives SMBus
commands; these are either translated into I2C commands or sent to the
SMBus driver.
A driver, client and algorithm
------------------------------
We will not need to extend i2c_driver, i2c_client or i2c_adapter. This means
that struct smbus_driver is exactly the same as struct i2c_driver, and
struct smbus_client is the same as struct i2c_client, and smbus_adapter is
the same as struct i2c_adapter. We *will* define the smbus_* variants, and
use them within this module, so it should be easy to extend them after all.
Note that at this moment, 10 bit SMBus addresses seem to be only
partially supported by the i2c module. We will ignore this issue for
now.
An adapter
------------
struct smbus_adapter {
char name[32];
unsigned int id;
struct smbus_algorithm *algo;
void *data;
#ifdef SPINLOCK
spinlock_t lock;
unsigned long lockflags;
#else
struct semaphore lock;
#endif
unsigned int flags;
struct smbus_client *clients[I2C_CLIENT_MAX];
int client_count;
int timeout;
int retries;
/* Here ended i2c_adapter */
s32 (* smbus_access) (__u8 addr, char read_write,
__u8 command, int size, union smbus_data * data);
}
A description of the above struct:
smbus_access: A function to access the SMBus. It is only used for non-i2c
smbus adapters.
Access functions
----------------
All these functions are defined extern.
The i2c access function should not be used within SMBus chip drivers, as
they might not be defined (for the PIIX4, for example). Instead, use the
following general access function, or one of the easier functions based
on it:
int smbus_access (struct i2c_adapter *, __u8 addr, char read_write,
__u8 command, int size, union smbus_data * data);
There are specific SMBus registration functions too, like the i2c ones.
They are fully compatiable with each other; just substitute 'smbus' for
'i2c' everywhere in the i2c description.
int i2c_is_smbus_client(struct i2c_client *);
int i2c_is_smbus_adapter(struct i2c_adapter *);
Decide whether this client, or adapter, is (on) a non-I2C SMBus. Usually
not needed, but it is nice anyway to be able to decide this.
Module isa.o
============
This module implements a new algorithm and a specific adapter for the
(single) ISA bus in your computer. This makes writing drivers for chips
that can be both on ISA and SMBus much easier.
Note that this module does *not* in any way depend on smbus.o (previous
versions of this document still assumed it would be build upon it; this
is no longer true).
A driver, adapter or algorithm
------------------------------
We will not need to extend i2c_driver, i2c_adapter or i2c_algorithm. This
means that struct isa_driver is exactly the same as struct i2c_driver,
struct isa_adapter is the same as struct i2c_adapter and struct isa_algorithm
is the same as struct isa_driver. We *will* define the isa_* variants, and
use them within this module, so it should be easy to extend them after all.
A client
--------
struct isa_client {
char name[32];
int id;
unsigned int flags;
unsigned char addr;
struct isa_adapter *adapter;
struct isa_driver *driver;
void *data;
unsigned int isa_addr;
}
A client is a specific sensor chip. Its operation is controlled by a driver
(which describes a type of sensor chip), and it is connected to an adapter
(a bus, the (single) ISA bus here).
A description of the above struct:
isa_addr: ISA addresses do not fit in the i2c-compatible addr field, so
we needed a new field.
Access functions
----------------
All these functions are defined extern.
In case of the ISA bus, the master_xfer, slave_send and slave_recv hooks
will be NULL, because these functions make no sense. It is regrettably
not easy to create an access abstraction in which both ISA bus access
and SMBus access are united. See below for examples how you can solve
this problem.
The most imporant additional access function:
int i2c_is_isa_client(struct i2c_client *);
int i2c_is_isa_adapter(struct i2c_adapter *);
Decide whether this client, or adapter, is (on) the ISA bus. This is
important, because it determines whether we can use the SMBus access
routines.
As an example, I will here implement our old LM78 access function:
/* The SMBus locks itself, but ISA access must be locked explicitly!
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
would slow down the LM78 access and should not be necessary.
There are some ugly typecasts here, but the good new is - they should
nowhere else be necessary! */
int lm78_read_value(struct i2c_client *client, u8 reg)
{
int res;
if (i2c_is_isa_client(client)) {
down((struct semaphore *) (client->data));
outb_p(reg,(((struct isa_client *) client)->isa_addr) +
LM78_ADDR_REG_OFFSET);
res = inb_p((((struct isa_client *) client)->isa_addr) +
LM78_DATA_REG_OFFSET);
up((struct semaphore *) (client->data));
return res;
} else
return smbus_read_byte_data(client->adapter,client->addr, reg);
}
+4 -16
View File
@@ -4,36 +4,28 @@ See doc/useful_addresses.html for a link to this article.
There is one big Makefile in the root of this package. It includes many
other files; one for each directory in which source code is found. These
included files are called 'Module.mk'. There is a separate set of Makefiles
in the i2c/ directories; these are Simon Vogl's original Makefiles, and
not referred by the main package Makefile.
included files are called 'Module.mk'.
There are several interesting targets defined through this Makefile:
* all
Create everything in all directories.
* all-i2c, all-src, ...
Create everything in the designated directory.
* install
Install everything from all directories.
* install-i2c, install-src, ...
Install everything in the designated directory.
* uninstall
Uninstall everything.
* clean
Remove anything which can be regenerated from all directories. A call
of 'make clean' (without any other targets) will ignore any .d files;
this is useful when they are out of date (and prevent the calling of
any other target).
* clean-i2c, clean-src, ...
Remove anything which can be regenerated from the designated directory.
* version
Regenerate version.h, using the current date for the date-stamp, and
a user-supplied version number.
* package
Create a .tar.gz file containing everything except the SVN directories.
* src/lm78.o, i2c/i2c-core.o, ...
You can of course also specify one or more targets to make.
The best way to understand the Module.mk subfiles is to examine one of them,
for example src/Module.mk. They are not too difficult to understand.
for example lib/Module.mk. They are not too difficult to understand.
There are several variables which can be set in the main Makefile. You can
also specify them on the command-line; this overrules any definitions
@@ -47,16 +39,12 @@ ones for developers:
only output more information, and never make the code mis-behave.
Several files are generated by Makefiles:
* .d
Dependency files for modules. Automatically generated.
* .rd
Dependency files for executables. Automatically generated.
* .ao
Dependency files for static libraries. Automatically generated.
* .lo
Dependency files for shared libraries. Automatically generated.
* .o
Generated modules
* .ro
Object files for executables. They will be linked together to create
the executable. This extension is used to distinguish them from
-166
View File
@@ -1,166 +0,0 @@
These are the steps you should follow to write a new driver and
submit it to us so that it will be applied cleanly to our package.
See at the bottom for adding new chip support to an existing driver.
Don't be intimidated by the list, it's just here so we don't
forget anything.
We are more than happy to help you with any part of the process.
There are two types of drivers, chip and bus.
Steps which apply to only one type of driver are noted.
These instructions are for 2.4-kernel-compatible drivers.
If you wish to write a driver for 2.6 only, not all of the following
steps apply.
---------------------------------------------------------------------
* Check our "New Drivers" page for status!!
* Contact us.
You are of course free to write your own drivers, but it is smart to
check first nobody else is already working on it, in case the
New Drivers page is out of date.
Tell us it's OK to put your name on the "New Drivers" page.
* Consider subscribing to the mailing list.
* Check out our latest code from SVN.
You should use this as a base for your development.
See instructions on our download page.
* Write the new driver.
If you write a driver for a device that is similar to one we already
support, use the other device driver as a template. If you don't know
which one to use, either ask us or use the following defaults.
For a chip driver, use lm78.c as template. For an SMBus-only adapter,
use i2c-piix4.c. Use i2c-via.c for an I2C-level adapter ('bit banger').
Usually, we make all prefixes start with the driver name. This is not
really necessary, as these symbols will not be exported anyway. But by
doing this, you are sure you do not shadow any global kernel names.
* A quick note about conversions. The conversions which are specified
in the sensor chip datasheet and which *cannot* vary from one board to
the next are performed by the driver. If other conversions are
necessary, they are performed in user-space.
* Meet Kernel coding standards.
See Documentation/CodingStyle in the kernel source.
Be sure and use 8 column tabs.
Also, please do not use // comments.
Also, please do not go past column 80.
Also, please use C99 struct initializers {.foo = blat}
Please do this _before_ you check in your driver.
* Meet /proc naming standards in the ctl_table (for chip drivers only).
See doc/developers/proc for information. Please do not deviate from
this without discussion.
* Add the driver to the Module.mk (the makefile).
Usually, you can just add it to KERNEL{CHIPS,DRIVERS}TARGETS in the
Module.mk file in the directory itself.
* Make sure it compiles cleanly.
Check compilation with 'make DEBUG=1' if you use debugging
information. Remember, things put between #ifdef DEBUG #endif may never
stop the driver from functioning; they should just output additional
information.
* Check for external symbols.
'nm --extern --defined' should only output symbols starting with __module,
cleanup_module, init_module and some kernel versioning symbols. Mark all
other symbols as static in your source file.
* Test the module.
Test with a recent 2.4 kernel.
For bus drivers, use i2cdetect and i2cdump. For i2cdump, test all supported
bus access modes (see i2cdump man page).
For chip drivers, cat all files in applicable /proc or /sys directory
and check for problems. Write all supported settings.
Check ALARM indications.
* Add detection information to prog/detect/sensors-detect.
This is a perl script that automatically detects what chips and adapters
are present. Contact us if you need help.
* Add chip information to lib/chips.{c,h} (for chip drivers only).
Until you have done this, the chip will be invisible for user-level
programs which use libsensors. Use standard names in lib/chips.c;
see also the comments in etc/sensors.conf.eg for help.
Contact us if you need more assistance.
* Be sure that SYSCTL and ALARM definitions in the driver are bracketed
by the special comments so that the kernel/include/sensors.h
file, which is generated automatically, contains your definitions.
(for chip drivers only)
* Add a procedure to prog/sensors/chips.[ch] (for chip drivers only).
This is a function specific for your driver that
makes the included 'sensors' program pretty-print your chip information.
* Add an line for the procedure in the matches[] table in
prog/sensors/main.c (for chip drivers only).
This tells sensors to call your new procedure when the chip name matches.
* Add entries to etc/sensors.conf.eg (for chip drivers only).
If needed, you can set defaults here.
* Test the userspace apps (sensors-detect and sensors).
Test setting limits with sensors -s.
* Add the name of the device to the README file.
* Add your name to the CONTRIBUTORS file.
* Add entries to the CHANGES file. Please keep in alphabetical order.
* Write a doc/chips/xxx or doc/busses/xxx file.
* Add entry to doc/chips/SUMMARY (for chip drivers only).
* Clearly specify licensing and copyright.
Make sure the GPL boilerplate and your name
(or if applicable your company's name) is at the top of the
new driver so we know you are giving it to us under the GPL.
* Submit the changes to us.
Do a 'svn update' to get in sync (things will have changed since
you started), then submit the changes to us as a patch against SVN.
* If you want to have your driver integrated to Linux 2.6, you have to
port your driver to the new sysfs interface. Once done and preferably
after testing, submit a patch to our mailing list: see
http://www.lm-sensors.org/wiki/AuthorsAndContributors
Make sure to check the current status for your driver first:
http://www.lm-sensors.org/wiki/Kernel2.6
http://www.lm-sensors.org/wiki/Devices
---------------------------------------------------------------------
Checklist for us to do on the webpage:
* Add entry to 'supported drivers' page
* Update entry on 'new drivers' page
---------------------------------------------------------------------
Checklist for _adding_ support for a chip to an _existing_ driver:
* Make the changes to the driver itself.
* Add detection information to prog/detect/sensors-detect.
* Add a strncmp() call for the new prefix in prog/sensors/main.c
(chip drivers only).
* Add entries for the new prefix to etc/sensors.conf.eg (chip drivers only).
* Update doc/[chips,busses]/xxx file
* Update doc/chips/SUMMARY file (chip drivers only)
* Update list in README
* Submit the changes as a patch or check them in
* Update entry on 'new drivers' page
-145
View File
@@ -1,145 +0,0 @@
Naming and data format standards under /proc
--------------------------------------------
The libsensors library offers an interface to the raw sensors data
through the sysctl interface. See the doc/developers/sysctl
and the libsensors source for further information.
An alternative method that some programs use is to access the /proc
files directly. This document briefly describes the standards that
the drivers follow, so that an application program can scan for
entries and access this data in a simple and consistent way.
Note that not all chip drivers adhere to this standard.
We will attempt to migrate older drivers to this standard,
and ensure that new drivers follow this standard wherever possible.
If you are developing a userspace application please send us
feedback on this standard.
Note that motherboards vary widely in the connections to sensor chips.
There is no standard that ensures, for example, that the second
temperature sensor is connected to the CPU, or that the second
fan is on the CPU. Therefore, programs must provide a facility
for the user to label or bind /proc entries for display.
Sensor chips often have unused inputs that should be ignored
by user programs.
Each chip gets its own directory under /proc/sys/dev/sensors.
The format is either chip-i2c-bus-hexaddress or chip-isa-hexaddress.
(Exception - chip lm78-j has a '-' in it - should this be changed??)
New dummy drivers may present chips as chip-busname-hexaddress.
All /proc values are integers or floating point numbers.
There is no facility in the lm_sensors package for drivers
to output general strings via /proc. This facility may be added
in the future.
Alarms are direct indications read from the chips. The drivers do NOT make
comparisons of readings to thresholds. This allows violations
between readings to be caught and alarmed. The exact definition of
an alarm (for example, whether a threshold must be met or must be
exceeded to cause an alarm) is chip-dependent.
Guidelines for new entries:
- If there are multiple values, some r/w and some r/o,
put the r/w ones first.
- Additional non-standard values should be after the standard values.
-------------------------------------------------------------------------
/proc entries are as follows:
Entry Values Function
----- ------ --------
alarms 1 Alarm bitmask.
Read only.
Integer representation of one to four bytes.
A '1' bit means an alarm.
Chips should be programmed for 'comparator' mode so that
the alarm will 'come back' after you read the register
if it is still valid.
Generally a direct representation of a chip's internal
alarm registers; there is no standard for the position
of individual bits.
Bits are defined in kernel/include/sensors.h.
beep 2 Beep/interrupt enable and bitmask.
The first value is 0 or 1 to globally disable
or enable the beeps.
The second value is a bitmask, with the
same format as 'alarms' with same bit locations.
curr[1-n] 3 Current max, min or hysteresis, and input value.
Floating point values XX.X or XX.XX in Amps.
The second value is preferably a hysteresis value,
reported as a absolute current, NOT a delta from
the max value.
First two values are read/write and third is read only.
fan[1-3] 2 Fan minimum and input value.
Two integers indicating RPM.
First value is read/write and second is read only.
fan_div 3 Fan divisor.
Integers in powers of two (1,2,4,8,16,32,64,128).
Some chips only support values 1,2,4,8.
Entries correspond to the fans[1-3].
See doc/fan-divisors for details.
in[0-8] 3 Voltage min, max, and input value.
Floating point values X.XX in volts from 0 to 4.01 volts.
Actual voltage depends on the scaling resistors on the
motherboard, as recommended in the chip datasheet.
This varies by chip and by motherboard.
Because of this variation, values are generally NOT scaled
by the chip driver, and must be done by the application.
However, some drivers (notably lm87 and via686a)
do scale, with various degrees of success.
These drivers will output the actual voltage.
First two values are read/write and third is read only.
Typical usage:
in0 CPU #1 voltage (not scaled)
in1 CPU #1 voltage (not scaled)
in2 3.3V nominal (not scaled)
in3 5.0V nominal (scaled)
in4 12.0V nominal (scaled)
in5 -12.0V nominal (scaled)
in6 -5.0V nominal (scaled)
in7 varies
in8 varies
pwm[1-3] 1-2 Pulse width modulation fan control.
Integer 0 - 255, read/write.
255 is max or 100%.
Corresponds to the fans 1-3.
Second value, if present, is an enable (0 or 1)
sensor[1-3] 1 Sensor type selection.
Integers 1,2,3, or thermistor Beta value (3435); read/write.
temp,
temp[1-4] 3 Temperature max, min or hysteresis, and input value.
Floating point values XXX.X or XXX.XX in degrees Celsius.
'temp' is used if there is only one temperature sensor on the
chip; for multiple temps. start with 'temp1'.
Temp1 is generally the sensor inside the chip itself,
generally reported as "motherboard temperature".
Temp2 to temp4 are generally sensors external to the chip
itself, for example the thermal diode inside the CPU or a
termistor nearby. The second value is preferably a hysteresis
value, reported as a absolute temperature, NOT a delta from
the max value.
First two values are read/write and third is read only.
vid 1 CPU core voltage.
Read only.
Floating point X.XX or X.XXX corresponding to CPU core voltage
as told to the sensor chip. Not always correct.
vrm 1 Voltage Regulator Module version number.
"Floating point" X.X. Default 8.2.
Affects the way the driver calculates the core voltage from
the vid pins. See doc/vid for details.
+5 -7
View File
@@ -2,18 +2,16 @@ CHECKLIST FOR NEW RELEASES
==========================
Release steps:
- Check out i2c and lm-sensors
- Check out lm-sensors
- Update if necessary, the version number of libsensors
(edit lib/Module.mk and change LIBMAINVER and/or LIBMINORVER)
Remember: update main number when interface changes, minor if new
functionality is added, and patch if only bugs are fixed.
`svn diff -r Vx-y-z lib' is your friend.
- Update both CHANGES files (add the release date)
- Verify i2c dependency instructions in README, QUICKSTART, and INSTALL,
update if necessary
- Update the CHANGES file (add the release date)
- Update lm_sensors.lsm and send it to the LSM by mailing it to
lsm@execpc.com with the subject `add' (no quotes).
- Update lm_sensors2/version.h and i2c/kernel/i2c.h
- Update version.h
- Commit
- Tag the release using SVN:
svn copy http://lm-sensors.org/svn/lm-sensors/trunk \
@@ -21,9 +19,9 @@ Release steps:
-m "Create tag 'V2-x-x'."
- Remove empty directories (e.g. rmdir `find . -type d`)
- Remove 'SVN' related files/directories (e.g. rm -fR `find . -name .svn`)
- Rename i2c and lm_sensors2 to i2c-#.#.# and lm_sensors-#.#.#
- Rename lm_sensors2 to lm_sensors-#.#.#
- Tar it up and post it (sign it)
- Make sure the file permissions on both the archives and the signatures
- Make sure the file permissions on both the archive and the signature
are correct (664)
After release, remember to:
-16
View File
@@ -1,16 +0,0 @@
You can access everything under /proc/sys through the sysctl interface. This
is mostly useful for C programs, which can use the sysctl function. Try
'man 2 sysctl' for more information.
As each chip gets its own directory under /proc/sys/dev/sensors, you must
somehow know the sysctl id of these directories to access the sensor data.
This is done through the file /proc/sys/dev/sensors/chips (see sensors.h
for the corresponding SYSCTL ids). You can only read this file; it
returns an array of struct sensors_chips_data, as defined in sensors.h.
Each chip will have its own entry here, with both its sysctl id and
the corresponding /proc name.
Generally, it is a better idea to use the new libsensors to access this
data. If you feel you need more than the library offers, please mail us,
so we can either add the new functionality to the library, or explain how
you can work around it.
-91
View File
@@ -1,91 +0,0 @@
This file documents the changes in struct file_operations throughout the 2.1
kernel series.
Kernels 2.0.x:
struct file_operations {
int (*lseek) (struct inode *, struct file *, off_t, int);
int (*read) (struct inode *, struct file *, char *, int);
int (*write) (struct inode *, struct file *, const char *, int);
int (*readdir) (struct inode *, struct file *, void *, filldir_t);
int (*select) (struct inode *, struct file *, int, select_table *);
int (*ioctl) (struct inode *, struct file *, unsigned int,
unsigned long);
int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
void (*release) (struct inode *, struct file *);
int (*fsync) (struct inode *, struct file *);
int (*fasync) (struct inode *, struct file *, int);
int (*check_media_change) (kdev_t dev);
int (*revalidate) (kdev_t dev);
};
Kernel 2.1.0:
- int (*lseek) (struct inode *, struct file *, off_t, int);
+ long long (*llseek) (struct inode *, struct file *, long long, int);
- int (*read) (struct inode *, struct file *, char *, int);
+ long (*read) (struct inode *, struct file *, char *, unsigned long);
- int (*write) (struct inode *, struct file *, const char *, int);
+ long (*write) (struct inode *, struct file *, const char *, unsigned lon
Kernel 2.1.23:
- int (*select) (struct inode *, struct file *, int, select_table *);
+ unsigned int (*poll) (struct file *, poll_table *);
[The lock field is added behind the revalidate field]
+ int (*lock) (struct inode *, struct file *, int, struct file_lock *);
Kernel 2.1.31:
- void (*release) (struct inode *, struct file *);
+ int (*release) (struct inode *, struct file *);
Kernel 2.1.55:
- int (*readdir) (struct inode *, struct file *, void *, filldir_t);
+ int (*readdir) (struct file *, void *, filldir_t);
Kernel 2.1.56:
- long long (*llseek) (struct inode *, struct file *, long long, int);
+ long long (*llseek) (struct file *, long long, int);
- int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);
+ int (*mmap) (struct file *, struct vm_area_struct *);
- int (*fsync) (struct inode *, struct file *);
+ int (*fsync) (struct file *, struct dentry *);
- int (*fasync) (struct inode *, struct file *, int);
+ int (*fasync) (struct file *, int);
- int (*lock) (struct inode *, struct file *, int, struct file_lock *);
+ int (*lock) (struct file *, int, struct file_lock *);
Kernel 2.1.70:
- long long (*llseek) (struct file *, long long, int);
+ loff_t (*llseek) (struct file *, loff_t, int);
- long (*read) (struct inode *, struct file *, char *, unsigned long);
+ ssize_t (*read) (struct file *, char *, size_t, loff_t *);
- long (*write) (struct inode *, struct file *, const char *,
unsigned long);
+ ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
Kernel 2.1.72:
- unsigned int (*poll) (struct file *, poll_table *);
+ unsigned int (*poll) (struct file *, struct poll_table_struct *);
Kernel 2.1.118:
[The flush field is added behind the open field -- stupid...]
+ int (*flush) (struct file *);
Last kernel checked: 2.2.0-pre4.
-145
View File
@@ -1,145 +0,0 @@
This file documents how to handle the callback function that returns the
information displayed in a (non-/proc/sys) /proc file.
For kernels before 2.1.29, you have to declare a proc_dir_entry, with
in the get_info field the procedure handler. For kernels 2.1.29 and later,
you can use the create_proc_entry function and assign a new value (the
procedure handler address) to the read_proc field:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,29)
static int read_bus_i2c(char *buf, char **start, off_t offset, int len,
int unused);
static struct proc_dir_entry proc_bus_i2c_dir =
{
/* low_ino */ 0, /* Set by proc_register_dynamic */
/* namelen */ 3, /* The length of the name field */
/* name */ "i2c",
/* mode */ S_IRUGO | S_IFREG,
/* nlink */ 1,
/* uid */ 0,
/* gid */ 0,
/* size */ 0,
/* ops */ NULL,
/* get_info */ &read_bus_i2c
};
#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,29)
static int read_bus_i2c(char *buf, char **start, off_t offset, int len,
int *eof , void *private);
#endif
/* This code fragment is part of the initialization code. You should do
several safety checks, like whether create_proc_entry does not return
NULL. */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,1,29)
proc_register_dynamic(&proc_root, &proc_bus_i2c_dir);
#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,29)
proc_bus_i2c = create_proc_entry("i2c",0,proc_root);
proc_bus_i2c->read_proc = &read_bus_i2c;
#endif
Now for the function itself.
LINUX KERNEL 2.0.x, and 2.1.x upto 2.1.28
=========================================
This function is known as the get_info function.
int get_info(char *buf, char **start, off_t offset, int len, int unused);
(INPUT)
buf: A kernel-space buffer where we can put the information.
len: The buffer length (we have a bit of slack here: if we write
'a little' beyond this, things will still work out)
(OUTPUT)
*buf: The complete contents of the /proc file, starting at the
beginning.
return value: the number of chars written to buf.
This is all you have to do if all information fits in the buffer (it is
about 3K in length). You can safely ignore offset and start in this case.
If your /proc entry gets larger, you have to do a bit more work.
(INPUT)
buf: See above
len: see above
offset: The offset from the beginning of the /proc file where the user
wants to start reading
(OUTPUT)
*buf: A part of the /proc file. The requested information does not
have to start at the beginning.
start: this is the place within buf where the information starts (ie.
you must use offset to compute this!).
return value: The number of valid characters written to buf. Begin
counting at start, not at buf!
You do not have to worry about not the whole information being in buf.
Another call will be done (with a higher offset value) if more is
needed.
LINUX KERNEL 2.1.x, from 2.1.29 upwards
=======================================
This function is known as the read_proc function.
int read_proc(char *buf, char **start, off_t offset, int len, int *eof,
void *private);
(INPUT)
buf: A kernel-space buffer where we can put the information.
len: The buffer length (we have a bit of slack here: if we write
'a little' beyond this, things will still work out)
(OUTPUT)
*buf: The complete contents of the /proc file, starting at the
beginning.
eof: You can set this to true (anything but 0) to indicate we have
reached the end of the file with this read. Not strictly
necessary, but it is more efficient (else this function may be
called again).
return value: The number of valid characters written to buf. Begin
counting at start, not at buf!
If your /proc entry gets larger, you have to do a bit more work.
(INPUT)
buf: See above
len: See above
offset: The offset from the beginning of the /proc file where the user
wants to start reading
(OUTPUT)
*buf: A part of the /proc file. The requested information does not
have to start at the beginning.
eof: See above
start: this is the place within buf where the information starts (ie.
you must use offset to compute this!).
return value: The number of valid characters written to buf.
You do not have to worry about not the whole information being in buf.
Another call will be done (with a higher offset value) if more is
needed. Ignore private.
A third way to do this:
(INPUT)
buf: See above
len: See above
offset: The offset from the beginning of the /proc file where the user
wants to start reading
(OUTPUT)
*buf: A part of the /proc file, with the requested information starting
at the beginning.
eof: See above
start: (long)start is added to the offset pointer after reading. This
allows you to play tricks with the file offset, independent of
the number of read bytes.
return value: the number of chars written to buf.
This document was written by Frodo Looijaard <frodol@dds.nl>. I do not
accept any responsibility for omissions or errors, but you are free to
drop me a note if something is wrong or if you miss something.
-14
View File
@@ -1,14 +0,0 @@
Installing Modules
==================
'make install' will install the kernel modules in the
subdirectory /lib/modules/$version/kernel/drivers/i2c/chips.
This puts the modules in the same location as a kernel
'make modules_install' does so that there will be no duplicate modules.
If 'make install' fails, particularly on Mandrake kernels,
see FAQ #3.8 (fix MODPREF in the Makefile) and #3.9.
For details on options that apply to most modules, see
doc/chips/MODPARMS. For details on options specific to particular
modules, see the documentation in doc/chips/.
+3 -7
View File
@@ -920,11 +920,6 @@ chip "as99127f-*"
chip "gl518sm-*"
# IMPORTANT: in0, in1, and in2 values (+5, +3, and +12) CANNOT be read
# unless you use the slow 'iterate' method. Limits will still
# work even when iterate=0. See doc/chips/gl518sm.
# Note that the 'iterate' method was trimmed while porting the
# driver to Linux 2.6 as we considered it too ugly for the thin
# benefit.
#
# Factors and labels taken from GL518SM datasheet, they seem to give
# reasonable values with EISCA connected Fan78
@@ -1854,13 +1849,14 @@ chip "fscher-*"
label in2 "Battery"
# Compute Voltages using mainboard dependant MRO-values
# (see doc/chips/fscher)
# (see the fscher driver documentation in the kernel tree)
# M R O O M R
compute in0 (@ * (49 * 33) / 255) + (0 / 100), (@ - (0 / 100)) * 255 / (49 * 33)
compute in1 (@ * (20 * 33) / 255) + (0 / 100), (@ - (0 / 100)) * 255 / (20 * 33)
compute in2 (@ * (10 * 33) / 255) + (0 / 100), (@ - (0 / 100)) * 255 / (10 * 33)
# Read documentation before enabling pwm settings (see doc/chips/fscher)
# Read documentation before enabling pwm settings
# (see the fscher driver documentation in the kernel tree)
# WARNING: IMPROPER VALUES MAY DAMAGE YOUR SYSTEM DUE TO OVERHEATING!
# Allow fans to turn off
-42
View File
@@ -1,42 +0,0 @@
# Module.mk - Makefile for a Linux module for reading sensor data.
# Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
#
# 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.
# Note that MODULE_DIR (the directory in which this file resides) is a
# 'simply expanded variable'. That means that its value is substituted
# verbatim in the rules, until it is redefined.
MODULE_DIR := kernel
KERNELDIR := $(MODULE_DIR)
# Regrettably, even 'simply expanded variables' will not put their currently
# defined value verbatim into the command-list of rules...
KERNELTARGETS :=
# Include all dependency files
INCLUDEFILES += $(KERNELTARGETS:.o=.d)
all-kernel: $(KERNELTARGETS)
all :: all-kernel
# Remove sensors.o possibly left from old versions
install-kernel: all-kernel
$(RM) $(DESTDIR)$(MODPREF)/misc/sensors.o
install :: install-kernel
clean-kernel:
$(RM) $(KERNELDIR)/*.o $(KERNELDIR)/*.d
clean :: clean-kernel
-210
View File
@@ -1,210 +0,0 @@
# Module.mk - Makefile for a Linux module for reading sensor data.
# Copyright (c) 1998 - 2001 Frodo Looijaard <frodol@dds.nl>
#
# 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.
# Note that MODULE_DIR (the directory in which this file resides) is a
# 'simply expanded variable'. That means that its value is substituted
# verbatim in the rules, until it is redefined.
MODULE_DIR := kernel/chips
KERNELCHIPSDIR := $(MODULE_DIR)
# Regrettably, even 'simply expanded variables' will not put their currently
# defined value verbatim into the command-list of rules...
KERNELCHIPSTARGETS :=
ifeq ($(shell if grep -q '^CONFIG_IPMI_HANDLER=' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/bmcsensors.o
endif
KERNELCHIPSTARGETS += $(MODULE_DIR)/ds1307.o
KERNELCHIPSTARGETS += $(MODULE_DIR)/ltc1710.o
KERNELCHIPSTARGETS += $(MODULE_DIR)/mic74.o
KERNELCHIPSTARGETS += $(MODULE_DIR)/saa1064.o
KERNELCHIPSTARGETS += $(MODULE_DIR)/smartbatt.o
KERNELCHIPSTARGETS += $(MODULE_DIR)/smbus-arp.o
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM1021=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1021.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM1024=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1024.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM1025=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1025.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM1026=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1026.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM1031=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm1031.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ADM9240=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/adm9240.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_ASB100=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/asb100.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_BT869=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/bt869.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_DDCMON=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/ddcmon.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_DS1621=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/ds1621.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_EEPROM=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/eeprom.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_F71805F=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/f71805f.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_FSCHER=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/fscher.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_FSCPOS=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/fscpos.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_FSCSCY=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/fscscy.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_GL518SM=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/gl518sm.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_GL520SM=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/gl520sm.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_IT87=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/it87.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM63=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm63.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM75=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm75.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM78=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm78.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM80=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm80.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM83=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm83.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM85=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm85.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM87=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm87.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM90=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm90.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM92=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm92.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_LM93=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/lm93.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_MATORB=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/matorb.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_MAX1619=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/max1619.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_MAX6650=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/max6650.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_MAXILIFE=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/maxilife.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_MTP008=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/mtp008.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_PC87360=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/pc87360.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_PCA9540=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/pca9540.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_PCF8574=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/pcf8574.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_PCF8591=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/pcf8591.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_SIS5595=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/sis5595.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_SMSC47M1=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/smsc47m1.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_THMC50=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/thmc50.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83781D=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/w83781d.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_VIA686A=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/via686a.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_VT1211=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/vt1211.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_VT8231=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/vt8231.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83792D=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/w83792d.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83627HF=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/w83627hf.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83L785TS=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/w83l785ts.o
endif
ifneq ($(shell if grep -q '^CONFIG_SENSORS_XEONTEMP=y' $(LINUX)/.config; then echo 1; fi),1)
KERNELCHIPSTARGETS += $(MODULE_DIR)/xeontemp.o
endif
# Include all dependency files
INCLUDEFILES += $(KERNELCHIPSTARGETS:.o=.d)
all-kernel-chips: $(KERNELCHIPSTARGETS)
all :: all-kernel-chips
#
# If $MODPREF/kernel exists, we presume the new (2.4.0) /lib/modules/x.y.z directory
# layout, so we install in kernel/drivers/i2c/chips/ and remove old versions in misc/
# and kernel/drivers/sensors/ . Otherwise we install in misc/ as before.
#
install-kernel-chips: all-kernel-chips
if [ -n "$(KERNELCHIPSTARGETS)" ] ; then \
$(MKDIR) $(DESTDIR)$(MODPREF)/kernel/drivers/i2c/chips ; \
$(INSTALL) -m 644 $(KERNELCHIPSTARGETS) $(DESTDIR)$(MODPREF)/kernel/drivers/i2c/chips ; \
for i in $(KERNELCHIPSTARGETS) ; do \
$(RM) $(DESTDIR)$(MODPREF)/misc/`basename $$i` $(DESTDIR)$(MODPREF)/kernel/drivers/sensors/`basename $$i` \
$(DESTDIR)$(MODPREF)/kernel/drivers/sensors/`basename $$i`.gz $(DESTDIR)$(MODPREF)/kernel/drivers/chips/`basename $$i`.gz ; \
done ; \
$(RMDIR) $(DESTDIR)$(MODPREF)/misc $(DESTDIR)$(MODPREF)/kernel/drivers/sensors 2> /dev/null || true ; \
fi
install :: install-kernel-chips
clean-kernel-chips:
$(RM) $(KERNELCHIPSDIR)/*.o $(KERNELCHIPSDIR)/*.d
clean :: clean-kernel-chips
-587
View File
@@ -1,587 +0,0 @@
/*
adm1021.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
Philip Edelbrock <phil@netroedge.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b,
0x4c, 0x4e, SENSORS_I2C_END
};
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_8(adm1021, adm1023, max1617, max1617a, thmc10, lm84, gl523sm, mc1066);
/* adm1021 constants specified below */
/* The adm1021 registers */
/* Read-only */
#define ADM1021_REG_TEMP 0x00
#define ADM1021_REG_REMOTE_TEMP 0x01
#define ADM1021_REG_STATUS 0x02
#define ADM1021_REG_MAN_ID 0x0FE /* 0x41 = Analog Devices, 0x49 = TI,
0x4D = Maxim, 0x23 = Genesys , 0x54 = Onsemi*/
#define ADM1021_REG_DEV_ID 0x0FF /* ADM1021 = 0x0X, ADM1021A/ADM1023 = 0x3X */
#define ADM1021_REG_DIE_CODE 0x0FF /* MAX1617A */
/* These use different addresses for reading/writing */
#define ADM1021_REG_CONFIG_R 0x03
#define ADM1021_REG_CONFIG_W 0x09
#define ADM1021_REG_CONV_RATE_R 0x04
#define ADM1021_REG_CONV_RATE_W 0x0A
/* These are for the ADM1023's additional precision on the remote temp sensor */
#define ADM1021_REG_REM_TEMP_PREC 0x010
#define ADM1021_REG_REM_OFFSET 0x011
#define ADM1021_REG_REM_OFFSET_PREC 0x012
#define ADM1021_REG_REM_TOS_PREC 0x013
#define ADM1021_REG_REM_THYST_PREC 0x014
/* limits */
#define ADM1021_REG_TOS_R 0x05
#define ADM1021_REG_TOS_W 0x0B
#define ADM1021_REG_REMOTE_TOS_R 0x07
#define ADM1021_REG_REMOTE_TOS_W 0x0D
#define ADM1021_REG_THYST_R 0x06
#define ADM1021_REG_THYST_W 0x0C
#define ADM1021_REG_REMOTE_THYST_R 0x08
#define ADM1021_REG_REMOTE_THYST_W 0x0E
/* write-only */
#define ADM1021_REG_ONESHOT 0x0F
#define ADM1021_ALARM_TEMP (ADM1021_ALARM_TEMP_HIGH | ADM1021_ALARM_TEMP_LOW)
#define ADM1021_ALARM_RTEMP (ADM1021_ALARM_RTEMP_HIGH | ADM1021_ALARM_RTEMP_LOW\
| ADM1021_ALARM_RTEMP_NA)
#define ADM1021_ALARM_ALL (ADM1021_ALARM_TEMP | ADM1021_ALARM_RTEMP)
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
/* Conversions note: 1021 uses normal integer signed-byte format*/
#define TEMP_FROM_REG(val) (val > 127 ? val-256 : val)
#define TEMP_TO_REG(val) (SENSORS_LIMIT((val < 0 ? val+256 : val),0,255))
/* Each client has this additional data */
struct adm1021_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 temp, temp_os, temp_hyst; /* Register values */
u8 remote_temp, remote_temp_os, remote_temp_hyst, alarms, die_code;
u8 fail;
/* Special values for ADM1023 only */
u8 remote_temp_prec, remote_temp_os_prec, remote_temp_hyst_prec,
remote_temp_offset, remote_temp_offset_prec;
};
static int adm1021_attach_adapter(struct i2c_adapter *adapter);
static int adm1021_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void adm1021_init_client(struct i2c_client *client);
static int adm1021_detach_client(struct i2c_client *client);
static int adm1021_read_value(struct i2c_client *client, u8 reg);
static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask);
static int adm1021_write_value(struct i2c_client *client, u8 reg,
u16 value);
static void adm1021_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1021_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag,
long *results);
static void adm1021_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1021_die_code(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1021_update_client(struct i2c_client *client);
/* (amalysh) read only mode, otherwise any limit's writing confuse BIOS */
static int read_only = 0;
/* This is the driver that will be inserted */
static struct i2c_driver adm1021_driver = {
.name = "ADM1021, MAX1617 sensor driver",
.id = I2C_DRIVERID_ADM1021,
.flags = I2C_DF_NOTIFY,
.attach_adapter = adm1021_attach_adapter,
.detach_client = adm1021_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define ADM1021_SYSCTL_TEMP 1200
#define ADM1021_SYSCTL_REMOTE_TEMP 1201
#define ADM1021_SYSCTL_DIE_CODE 1202
#define ADM1021_SYSCTL_ALARMS 1203
#define ADM1021_ALARM_TEMP_HIGH 0x40
#define ADM1021_ALARM_TEMP_LOW 0x20
#define ADM1021_ALARM_RTEMP_HIGH 0x10
#define ADM1021_ALARM_RTEMP_LOW 0x08
#define ADM1021_ALARM_RTEMP_NA 0x04
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected adm1021. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table adm1021_dir_table_template[] = {
{ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_temp},
{ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_remote_temp},
{ADM1021_SYSCTL_DIE_CODE, "die_code", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_die_code},
{ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_alarms},
{0}
};
static ctl_table adm1021_max_dir_table_template[] = {
{ADM1021_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_temp},
{ADM1021_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_remote_temp},
{ADM1021_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1021_alarms},
{0}
};
static int adm1021_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, adm1021_detect);
}
static int adm1021_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct adm1021_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("adm1021.o: adm1021_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto error0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access adm1021_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct adm1021_data), GFP_KERNEL))) {
err = -ENOMEM;
goto error0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &adm1021_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if ((adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0x03) != 0x00
|| (adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x3F) != 0x00
|| (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) & 0xF8) != 0x00)
goto error1;
}
/* Determine the chip type. */
if (kind <= 0) {
i = adm1021_read_value(new_client, ADM1021_REG_MAN_ID);
if (i == 0x41)
if ((adm1021_read_value (new_client, ADM1021_REG_DEV_ID) & 0xF0) == 0x30)
kind = adm1023;
else
kind = adm1021;
else if (i == 0x49)
kind = thmc10;
else if (i == 0x23)
kind = gl523sm;
else if ((i == 0x4d) &&
(adm1021_read_value
(new_client, ADM1021_REG_DEV_ID) == 0x01))
kind = max1617a;
else if (i == 0x54)
kind = mc1066;
/* LM84 Mfr ID in a different place, and it has more unused bits */
else if (adm1021_read_value(new_client, ADM1021_REG_CONV_RATE_R) == 0x00
&& (kind == 0 /* skip extra detection */
|| ((adm1021_read_value(new_client, ADM1021_REG_CONFIG_R) & 0x7F) == 0x00
&& (adm1021_read_value(new_client, ADM1021_REG_STATUS) & 0xAB) == 0x00)))
kind = lm84;
else
kind = max1617;
}
if (kind == max1617) {
type_name = "max1617";
client_name = "MAX1617 chip";
} else if (kind == max1617a) {
type_name = "max1617a";
client_name = "MAX1617A chip";
} else if (kind == adm1021) {
type_name = "adm1021";
client_name = "ADM1021 chip";
} else if (kind == adm1023) {
type_name = "adm1023";
client_name = "ADM1023 chip";
} else if (kind == thmc10) {
type_name = "thmc10";
client_name = "THMC10 chip";
} else if (kind == lm84) {
type_name = "lm84";
client_name = "LM84 chip";
} else if (kind == gl523sm) {
type_name = "gl523sm";
client_name = "GL523SM chip";
} else if (kind == mc1066) {
type_name = "mc1066";
client_name = "MC1066 chip";
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto error3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
data->type == adm1021 ? adm1021_dir_table_template :
adm1021_max_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto error4;
}
data->sysctl_id = i;
/* Initialize the ADM1021 chip */
if (kind != lm84)
adm1021_init_client(new_client);
return 0;
error4:
i2c_detach_client(new_client);
error3:
error1:
kfree(data);
error0:
return err;
}
static void adm1021_init_client(struct i2c_client *client)
{
/* Enable ADC and disable suspend mode */
adm1021_write_value(client, ADM1021_REG_CONFIG_W,
adm1021_read_value(client, ADM1021_REG_CONFIG_R) & 0xBF);
/* Set Conversion rate to 1/sec (this can be tinkered with) */
adm1021_write_value(client, ADM1021_REG_CONV_RATE_W, 0x04);
}
static int adm1021_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct adm1021_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("adm1021.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* All registers are byte-sized */
static int adm1021_read_value(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
/* only update value if read succeeded; set fail bit if failed */
static int adm1021_rd_good(u8 *val, struct i2c_client *client, u8 reg, u8 mask)
{
int i;
struct adm1021_data *data = client->data;
i = i2c_smbus_read_byte_data(client, reg);
if (i < 0) {
data->fail |= mask;
return i;
}
*val = i;
return 0;
}
static int adm1021_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if (read_only > 0)
return 0;
return i2c_smbus_write_byte_data(client, reg, value);
}
static void adm1021_update_client(struct i2c_client *client)
{
struct adm1021_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting adm1021 update\n");
#endif
data->fail = 0;
adm1021_rd_good(&(data->temp), client, ADM1021_REG_TEMP,
ADM1021_ALARM_TEMP);
adm1021_rd_good(&(data->temp_os), client, ADM1021_REG_TOS_R,
ADM1021_ALARM_TEMP);
adm1021_rd_good(&(data->temp_hyst), client,
ADM1021_REG_THYST_R, ADM1021_ALARM_TEMP);
adm1021_rd_good(&(data->remote_temp), client,
ADM1021_REG_REMOTE_TEMP, ADM1021_ALARM_RTEMP);
adm1021_rd_good(&(data->remote_temp_os), client,
ADM1021_REG_REMOTE_TOS_R, ADM1021_ALARM_RTEMP);
adm1021_rd_good(&(data->remote_temp_hyst), client,
ADM1021_REG_REMOTE_THYST_R,
ADM1021_ALARM_RTEMP);
data->alarms = ADM1021_ALARM_ALL;
if (!adm1021_rd_good(&(data->alarms), client,
ADM1021_REG_STATUS, 0))
data->alarms &= ADM1021_ALARM_ALL;
if (data->type == adm1021)
adm1021_rd_good(&(data->die_code), client,
ADM1021_REG_DIE_CODE, 0);
if (data->type == adm1023) {
adm1021_rd_good(&(data->remote_temp_prec), client,
ADM1021_REG_REM_TEMP_PREC,
ADM1021_ALARM_TEMP);
adm1021_rd_good(&(data->remote_temp_os_prec), client,
ADM1021_REG_REM_TOS_PREC,
ADM1021_ALARM_RTEMP);
adm1021_rd_good(&(data->remote_temp_hyst_prec), client,
ADM1021_REG_REM_THYST_PREC,
ADM1021_ALARM_RTEMP);
adm1021_rd_good(&(data->remote_temp_offset), client,
ADM1021_REG_REM_OFFSET,
ADM1021_ALARM_RTEMP);
adm1021_rd_good(&(data->remote_temp_offset_prec),
client, ADM1021_REG_REM_OFFSET_PREC,
ADM1021_ALARM_RTEMP);
}
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
void adm1021_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1021_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1021_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_os);
results[1] = TEMP_FROM_REG(data->temp_hyst);
results[2] = TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_os = TEMP_TO_REG(results[0]);
adm1021_write_value(client, ADM1021_REG_TOS_W,
data->temp_os);
}
if (*nrels_mag >= 2) {
data->temp_hyst = TEMP_TO_REG(results[1]);
adm1021_write_value(client, ADM1021_REG_THYST_W,
data->temp_hyst);
}
}
}
void adm1021_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1021_data *data = client->data;
int prec = 0;
if (operation == SENSORS_PROC_REAL_INFO)
if (data->type == adm1023) { *nrels_mag = 3; }
else { *nrels_mag = 0; }
else if (operation == SENSORS_PROC_REAL_READ) {
adm1021_update_client(client);
results[0] = TEMP_FROM_REG(data->remote_temp_os);
results[1] = TEMP_FROM_REG(data->remote_temp_hyst);
results[2] = TEMP_FROM_REG(data->remote_temp);
if (data->type == adm1023) {
results[0]=results[0]*1000 +
((data->remote_temp_os_prec >> 5) * 125);
results[1]=results[1]*1000 +
((data->remote_temp_hyst_prec >> 5) * 125);
results[2]=(TEMP_FROM_REG(data->remote_temp_offset)*1000) +
((data->remote_temp_offset_prec >> 5) * 125);
results[3]=TEMP_FROM_REG(data->remote_temp)*1000 +
((data->remote_temp_prec >> 5) * 125);
*nrels_mag = 4;
} else {
*nrels_mag = 3;
}
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (data->type == adm1023) {
prec=((results[0]-((results[0]/1000)*1000))/125)<<5;
adm1021_write_value(client,
ADM1021_REG_REM_TOS_PREC,
prec);
results[0]=results[0]/1000;
data->remote_temp_os_prec=prec;
}
data->remote_temp_os = TEMP_TO_REG(results[0]);
adm1021_write_value(client,
ADM1021_REG_REMOTE_TOS_W,
data->remote_temp_os);
}
if (*nrels_mag >= 2) {
if (data->type == adm1023) {
prec=((results[1]-((results[1]/1000)*1000))/125)<<5;
adm1021_write_value(client,
ADM1021_REG_REM_THYST_PREC,
prec);
results[1]=results[1]/1000;
data->remote_temp_hyst_prec=prec;
}
data->remote_temp_hyst = TEMP_TO_REG(results[1]);
adm1021_write_value(client,
ADM1021_REG_REMOTE_THYST_W,
data->remote_temp_hyst);
}
if (*nrels_mag >= 3) {
if (data->type == adm1023) {
prec=((results[2]-((results[2]/1000)*1000))/125)<<5;
adm1021_write_value(client,
ADM1021_REG_REM_OFFSET_PREC,
prec);
results[2]=results[2]/1000;
data->remote_temp_offset_prec=prec;
data->remote_temp_offset=results[2];
adm1021_write_value(client,
ADM1021_REG_REM_OFFSET,
data->remote_temp_offset);
}
}
}
}
void adm1021_die_code(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1021_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1021_update_client(client);
results[0] = data->die_code;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
/* Can't write to it */
}
}
void adm1021_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1021_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1021_update_client(client);
results[0] = data->alarms | data->fail;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
/* Can't write to it */
}
}
static int __init sm_adm1021_init(void)
{
printk(KERN_INFO "adm1021.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&adm1021_driver);
}
static void __exit sm_adm1021_exit(void)
{
i2c_del_driver(&adm1021_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("adm1021 driver");
MODULE_LICENSE("GPL");
MODULE_PARM(read_only, "i");
MODULE_PARM_DESC(read_only, "Don't set any values, read only mode");
module_init(sm_adm1021_init)
module_exit(sm_adm1021_exit)
-776
View File
@@ -1,776 +0,0 @@
/*
adm1024.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Add by Ken Bowley <ken@opnix.com> from the adm1025.c written by
Gordon Wu <gwu@esoft.com> and from adm9240.c written by
Copyright (c) 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Supports the Analog Devices ADM1024. See doc/chips/adm1024 for details */
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(adm1024);
/* Many ADM1024 constants specified below */
#define ADM1024_REG_IN_MAX(nr) (0x2b + (nr) * 2)
#define ADM1024_REG_IN_MIN(nr) (0x2c + (nr) * 2)
#define ADM1024_REG_IN(nr) (0x20 + (nr))
/* The ADM1024 registers */
#define ADM1024_REG_INT_TEMP_TRIP_SET 0x13
#define ADM1024_REG_EXT_TEMP_TRIP_SET 0x14
#define ADM1024_REG_TEST 0x15
#define ADM1024_REG_CHANNEL_MODE 0x16
#define ADM1024_REG_INT_TEMP_TRIP 0x17 /* read only */
#define ADM1024_REG_EXT_TEMP_TRIP 0x18 /* read only */
#define ADM1024_REG_ANALOG_OUT 0x19
#define ADM1024_REG_AIN1_LOW_LIMIT 0x1A
#define ADM1024_REG_AIN2_LOW_LIMIT 0x1B
/* These are all read-only */
#define ADM1024_REG_2_5V 0x20 /* 2.5V Measured Value/EXT Temp 2 */
#define ADM1024_REG_VCCP1 0x21
#define ADM1024_REG_3_3V 0x22 /* VCC Measured Value */
#define ADM1024_REG_5V 0x23
#define ADM1024_REG_12V 0x24
#define ADM1024_REG_VCCP2 0x25
#define ADM1024_REG_EXT_TEMP1 0x26
#define ADM1024_REG_TEMP 0x27
#define ADM1024_REG_FAN1 0x28 /* FAN1/AIN1 Value */
#define ADM1024_REG_FAN2 0x29 /* FAN2/AIN2 Value */
#define ADM1024_REG_COMPANY_ID 0x3E /* 0x41 for ADM1024 */
#define ADM1024_REG_DIE_REV 0x3F
/* These are read/write */
#define ADM1024_REG_2_5V_HIGH 0x2B /* 2.5V/Ext Temp2 High Limit */
#define ADM1024_REG_2_5V_LOW 0x2C /* 2.5V/Ext Temp2 Low Limit */
#define ADM1024_REG_VCCP1_HIGH 0x2D
#define ADM1024_REG_VCCP1_LOW 0x2E
#define ADM1024_REG_3_3V_HIGH 0x2F /* VCC High Limit */
#define ADM1024_REG_3_3V_LOW 0x30 /* VCC Low Limit */
#define ADM1024_REG_5V_HIGH 0x31
#define ADM1024_REG_5V_LOW 0x32
#define ADM1024_REG_12V_HIGH 0x33
#define ADM1024_REG_12V_LOW 0x34
#define ADM1024_REG_VCCP2_HIGH 0x35
#define ADM1024_REG_VCCP2_LOW 0x36
#define ADM1024_REG_EXT_TEMP1_HIGH 0x37
#define ADM1024_REG_EXT_TEMP1_LOW 0x38
#define ADM1024_REG_TOS 0x39
#define ADM1024_REG_THYST 0x3A
#define ADM1024_REG_FAN1_MIN 0x3B
#define ADM1024_REG_FAN2_MIN 0x3C
#define ADM1024_REG_CONFIG 0x40
#define ADM1024_REG_INT1_STAT 0x41
#define ADM1024_REG_INT2_STAT 0x42
#define ADM1024_REG_INT1_MASK 0x43
#define ADM1024_REG_INT2_MASK 0x44
#define ADM1024_REG_CHASSIS_CLEAR 0x46
#define ADM1024_REG_VID_FAN_DIV 0x47
#define ADM1024_REG_I2C_ADDR 0x48
#define ADM1024_REG_VID4 0x49
#define ADM1024_REG_CONFIG2 0x4A
#define ADM1024_REG_TEMP_CONFIG 0x4B
#define ADM1024_REG_EXTMODE1 0x4C /* Interupt Status Register Mirror No. 1 */
#define ADM1024_REG_EXTMODE2 0x4D /* Interupt Status Register Mirror No. 2 */
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val,nr) (SENSORS_LIMIT(((val) & 0xff),0,255))
#define IN_FROM_REG(val,nr) (val)
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) ((val)==0?-1:\
(val)==255?0:1350000/((div)*(val)))
#define TEMP_FROM_REG(temp) \
((temp)<256?((((temp)&0x1fe) >> 1) * 10) + ((temp) & 1) * 5: \
((((temp)&0x1fe) >> 1) -255) * 10 - ((temp) & 1) * 5) \
#define EXT_TEMP_FROM_REG(temp) (((temp)>0x80?(temp)-0x100:(temp))*10)
#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*10)
#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\
((val)+5)/10), \
0,255)
#define ALARMS_FROM_REG(val) (val)
#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1)))
#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\
205-(val)*5)
/* For each registered ADM1024, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new adm1024 client is
allocated. */
struct adm1024_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[6]; /* Register value */
u8 in_max[6]; /* Register value */
u8 in_min[6]; /* Register value */
u8 fan[2]; /* Register value */
u8 fan_min[2]; /* Register value */
u8 fan_div[2]; /* Register encoding, shifted right */
int temp; /* Temp, shifted right */
u8 temp_os_max; /* Register value */
u8 temp_os_hyst; /* Register value */
int temp1; /* Ext Temp 1 */
u8 temp1_os_max;
u8 temp1_os_hyst;
int temp2; /* Ext Temp 2 */
u8 temp2_os_max;
u8 temp2_os_hyst;
u16 alarms; /* Register encoding, combined */
u8 analog_out; /* Register value */
u8 vid; /* Register value combined */
};
static int adm1024_attach_adapter(struct i2c_adapter *adapter);
static int adm1024_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int adm1024_detach_client(struct i2c_client *client);
static int adm1024_read_value(struct i2c_client *client, u8 register);
static int adm1024_write_value(struct i2c_client *client, u8 register,
u8 value);
static void adm1024_update_client(struct i2c_client *client);
static void adm1024_init_client(struct i2c_client *client);
static void adm1024_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_temp1(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_temp2(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1024_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag,
long *results);
static void adm1024_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver adm1024_driver = {
.name = "ADM1024 sensor driver",
.id = I2C_DRIVERID_ADM1024,
.flags = I2C_DF_NOTIFY,
.attach_adapter = adm1024_attach_adapter,
.detach_client = adm1024_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define ADM1024_SYSCTL_IN0 1000 /* Volts * 100 */
#define ADM1024_SYSCTL_IN1 1001
#define ADM1024_SYSCTL_IN2 1002
#define ADM1024_SYSCTL_IN3 1003
#define ADM1024_SYSCTL_IN4 1004
#define ADM1024_SYSCTL_IN5 1005
#define ADM1024_SYSCTL_FAN1 1101 /* Rotations/min */
#define ADM1024_SYSCTL_FAN2 1102
#define ADM1024_SYSCTL_TEMP 1250 /* Degrees Celsius * 100 */
#define ADM1024_SYSCTL_TEMP1 1290 /* Degrees Celsius */
#define ADM1024_SYSCTL_TEMP2 1295 /* Degrees Celsius */
#define ADM1024_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define ADM1024_SYSCTL_ALARMS 2001 /* bitvector */
#define ADM1024_SYSCTL_ANALOG_OUT 2002
#define ADM1024_SYSCTL_VID 2003
#define ADM1024_ALARM_IN0 0x0001
#define ADM1024_ALARM_IN1 0x0002
#define ADM1024_ALARM_IN2 0x0004
#define ADM1024_ALARM_IN3 0x0008
#define ADM1024_ALARM_IN4 0x0100
#define ADM1024_ALARM_IN5 0x0200
#define ADM1024_ALARM_FAN1 0x0040
#define ADM1024_ALARM_FAN2 0x0080
#define ADM1024_ALARM_TEMP 0x0010
#define ADM1024_ALARM_TEMP1 0x0020
#define ADM1024_ALARM_TEMP2 0x0001
#define ADM1024_ALARM_CHAS 0x1000
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected ADM1024. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table adm1024_dir_table_template[] = {
{ADM1024_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_in},
{ADM1024_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_fan},
{ADM1024_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_fan},
{ADM1024_SYSCTL_TEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_temp},
{ADM1024_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_temp1},
{ADM1024_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_temp2},
{ADM1024_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_fan_div},
{ADM1024_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_alarms},
{ADM1024_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_analog_out},
{ADM1024_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1024_vid},
{0}
};
static int adm1024_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, adm1024_detect);
}
static int adm1024_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct adm1024_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("adm1024.o: adm1024_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access adm1024_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct adm1024_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &adm1024_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if((adm1024_read_value(new_client, ADM1024_REG_CONFIG) & 0x80) != 0x00)
goto ERROR1;
}
/* Determine the chip type. */
if (kind <= 0) {
i = adm1024_read_value(new_client, ADM1024_REG_COMPANY_ID);
if (i == 0x41)
kind = adm1024;
else {
if (kind == 0)
printk
("adm1024.o: Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
goto ERROR1;
}
}
if (kind == adm1024) {
type_name = "adm1024";
client_name = "ADM1024 chip";
} else {
#ifdef DEBUG
printk("adm1024.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
adm1024_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the ADM1024 chip */
adm1024_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int adm1024_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct adm1024_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("adm1024.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static int adm1024_read_value(struct i2c_client *client, u8 reg)
{
return 0xFF & i2c_smbus_read_byte_data(client, reg);
}
static int adm1024_write_value(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
static void adm1024_init_client(struct i2c_client *client)
{
/* Enable temperature channel 2 */
adm1024_write_value(client, ADM1024_REG_CHANNEL_MODE, adm1024_read_value(client, ADM1024_REG_CHANNEL_MODE) | 0x04);
/* Start monitoring */
adm1024_write_value(client, ADM1024_REG_CONFIG, 0x07);
}
static void adm1024_update_client(struct i2c_client *client)
{
struct adm1024_data *data = client->data;
u8 i;
down(&data->update_lock);
if (
(jiffies - data->last_updated >
(data->type == adm1024 ? HZ / 2 : HZ * 2))
|| (jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting adm1024 update\n");
#endif
for (i = 0; i <= 5; i++) {
data->in[i] =
adm1024_read_value(client, ADM1024_REG_IN(i));
data->in_min[i] =
adm1024_read_value(client,
ADM1024_REG_IN_MIN(i));
data->in_max[i] =
adm1024_read_value(client,
ADM1024_REG_IN_MAX(i));
}
data->fan[0] =
adm1024_read_value(client, ADM1024_REG_FAN1);
data->fan_min[0] =
adm1024_read_value(client, ADM1024_REG_FAN1_MIN);
data->fan[1] =
adm1024_read_value(client, ADM1024_REG_FAN2);
data->fan_min[1] =
adm1024_read_value(client, ADM1024_REG_FAN2_MIN);
data->temp =
(adm1024_read_value(client, ADM1024_REG_TEMP) << 1) +
((adm1024_read_value
(client, ADM1024_REG_TEMP_CONFIG) & 0x80) >> 7);
data->temp_os_max =
adm1024_read_value(client, ADM1024_REG_TOS);
data->temp_os_hyst =
adm1024_read_value(client, ADM1024_REG_THYST);
data->temp1 =
adm1024_read_value(client, ADM1024_REG_EXT_TEMP1);
data->temp1_os_max =
adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_HIGH);
data->temp1_os_hyst =
adm1024_read_value(client, ADM1024_REG_EXT_TEMP1_LOW);
data->temp2 =
adm1024_read_value(client, ADM1024_REG_2_5V);
data->temp2_os_max =
adm1024_read_value(client, ADM1024_REG_2_5V_HIGH);
data->temp2_os_hyst =
adm1024_read_value(client, ADM1024_REG_2_5V_LOW);
i = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV);
data->fan_div[0] = (i >> 4) & 0x03;
data->fan_div[1] = (i >> 6) & 0x03;
data->vid = i & 0x0f;
data->vid |=
(adm1024_read_value(client, ADM1024_REG_VID4) & 0x01)
<< 4;
data->alarms =
adm1024_read_value(client,
ADM1024_REG_INT1_STAT) +
(adm1024_read_value(client, ADM1024_REG_INT2_STAT) <<
8);
data->analog_out =
adm1024_read_value(client, ADM1024_REG_ANALOG_OUT);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void adm1024_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int scales[6] = { 250, 225, 330, 500, 1200, 270 };
struct adm1024_data *data = client->data;
int nr = ctl_name - ADM1024_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] =
IN_FROM_REG(data->in_min[nr], nr) * scales[nr] / 192;
results[1] =
IN_FROM_REG(data->in_max[nr], nr) * scales[nr] / 192;
results[2] =
IN_FROM_REG(data->in[nr], nr) * scales[nr] / 192;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] =
IN_TO_REG((results[0] * 192) / scales[nr], nr);
adm1024_write_value(client, ADM1024_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] =
IN_TO_REG((results[1] * 192) / scales[nr], nr);
adm1024_write_value(client, ADM1024_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
void adm1024_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
int nr = ctl_name - ADM1024_SYSCTL_FAN1 + 1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr - 1],
DIV_FROM_REG(data->
fan_div[nr - 1]));
results[1] =
FAN_FROM_REG(data->fan[nr - 1],
DIV_FROM_REG(data->fan_div[nr - 1]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr - 1] = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->
fan_div[nr -
1]));
adm1024_write_value(client,
nr ==
1 ? ADM1024_REG_FAN1_MIN :
ADM1024_REG_FAN2_MIN,
data->fan_min[nr - 1]);
}
}
}
void adm1024_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = TEMP_LIMIT_FROM_REG(data->temp_os_max);
results[1] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst);
results[2] = TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_os_max = TEMP_LIMIT_TO_REG(results[0]);
adm1024_write_value(client, ADM1024_REG_TOS,
data->temp_os_max);
}
if (*nrels_mag >= 2) {
data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]);
adm1024_write_value(client, ADM1024_REG_THYST,
data->temp_os_hyst);
}
}
}
void adm1024_temp1(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = TEMP_LIMIT_FROM_REG(data->temp1_os_max);
results[1] = TEMP_LIMIT_FROM_REG(data->temp1_os_hyst);
results[2] = EXT_TEMP_FROM_REG(data->temp1);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp1_os_max = TEMP_LIMIT_TO_REG(results[0]);
adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_HIGH,
data->temp1_os_max);
}
if (*nrels_mag >= 2) {
data->temp1_os_hyst = TEMP_LIMIT_TO_REG(results[1]);
adm1024_write_value(client, ADM1024_REG_EXT_TEMP1_LOW,
data->temp1_os_hyst);
}
}
}
void adm1024_temp2(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = TEMP_LIMIT_FROM_REG(data->temp2_os_max);
results[1] = TEMP_LIMIT_FROM_REG(data->temp2_os_hyst);
results[2] = EXT_TEMP_FROM_REG(data->temp2);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp2_os_max = TEMP_LIMIT_TO_REG(results[0]);
adm1024_write_value(client, ADM1024_REG_2_5V_HIGH,
data->temp2_os_max);
}
if (*nrels_mag >= 2) {
data->temp2_os_hyst = TEMP_LIMIT_TO_REG(results[1]);
adm1024_write_value(client, ADM1024_REG_2_5V_LOW,
data->temp2_os_hyst);
}
}
}
void adm1024_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = ALARMS_FROM_REG(data->alarms);
*nrels_mag = 1;
}
}
void adm1024_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = adm1024_read_value(client, ADM1024_REG_VID_FAN_DIV);
if (*nrels_mag >= 2) {
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0x3f) | (data->fan_div[1] << 6);
}
if (*nrels_mag >= 1) {
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0xcf) | (data->fan_div[0] << 4);
adm1024_write_value(client,
ADM1024_REG_VID_FAN_DIV, old);
}
}
}
void adm1024_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = data->analog_out;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->analog_out = results[0];
adm1024_write_value(client, ADM1024_REG_ANALOG_OUT,
data->analog_out);
}
}
}
void adm1024_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1024_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1024_update_client(client);
results[0] = VID_FROM_REG(data->vid);
*nrels_mag = 1;
}
}
static int __init sm_adm1024_init(void)
{
printk("adm1024.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&adm1024_driver);
}
static void __exit sm_adm1024_exit(void)
{
i2c_del_driver(&adm1024_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("ADM1024 driver");
MODULE_LICENSE("GPL");
module_init(sm_adm1024_init);
module_exit(sm_adm1024_exit);
-588
View File
@@ -1,588 +0,0 @@
/*
adm1025.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2000 Chen-Yuan Wu <gwu@esoft.com>
Copyright (c) 2003-2004 Jean Delvare <khali@linux-fr.org>
Based on the adm9240 driver.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Supports the Analog Devices ADM1025 and the Philips NE1619.
See doc/chips/adm1025 for details */
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
#include "sensors_vid.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_2(adm1025, ne1619);
/* Many ADM1025 constants specified below */
/* The ADM1025 registers */
/* These are all read-only */
#define ADM1025_REG_2_5V 0x20 /* not used directly, see */
#define ADM1025_REG_VCCP1 0x21 /* ADM1025_REG_IN(nr) below */
#define ADM1025_REG_3_3V 0x22
#define ADM1025_REG_5V 0x23
#define ADM1025_REG_12V 0x24
#define ADM1025_REG_VCC 0x25
#define ADM1025_REG_RTEMP 0x26 /* not used directly, see */
#define ADM1025_REG_LTEMP 0x27 /* ADM1025_REG_TEMP(nr) below */
#define ADM1025_REG_COMPANY_ID 0x3E /* 0x41 for Analog Devices,
0xA1 for Philips */
#define ADM1025_REG_DIE_REV 0x3F /* 0x20-0x2F for ADM1025 and compatible */
#define ADM1025_REG_STATUS1 0x41
#define ADM1025_REG_STATUS2 0x42
#define ADM1025_REG_VID 0x47
#define ADM1025_REG_VID4 0x49 /* actually R/W
but we don't write to it */
/* These are read/write */
#define ADM1025_REG_2_5V_HIGH 0x2B /* not used directly, see */
#define ADM1025_REG_2_5V_LOW 0x2C /* ADM1025_REG_IN_MAX(nr) and */
#define ADM1025_REG_VCCP1_HIGH 0x2D /* ADM1025_REG_IN_MIN(nr) below */
#define ADM1025_REG_VCCP1_LOW 0x2E
#define ADM1025_REG_3_3V_HIGH 0x2F
#define ADM1025_REG_3_3V_LOW 0x30
#define ADM1025_REG_5V_HIGH 0x31
#define ADM1025_REG_5V_LOW 0x32
#define ADM1025_REG_12V_HIGH 0x33
#define ADM1025_REG_12V_LOW 0x34
#define ADM1025_REG_VCC_HIGH 0x35
#define ADM1025_REG_VCC_LOW 0x36
#define ADM1025_REG_RTEMP_HIGH 0x37 /* not used directly, see */
#define ADM1025_REG_RTEMP_LOW 0x38 /* ADM1025_REG_TEMP_MAX(nr) and */
#define ADM1025_REG_LTEMP_HIGH 0x39 /* ADM1025_REG_TEMP_MIN(nr) below */
#define ADM1025_REG_LTEMP_LOW 0x3A
#define ADM1025_REG_CONFIG 0x40
/* Useful macros */
#define ADM1025_REG_IN(nr) (ADM1025_REG_2_5V + (nr))
#define ADM1025_REG_IN_MAX(nr) (ADM1025_REG_2_5V_HIGH + (nr) * 2)
#define ADM1025_REG_IN_MIN(nr) (ADM1025_REG_2_5V_LOW + (nr) * 2)
#define ADM1025_REG_TEMP(nr) (ADM1025_REG_RTEMP + (nr))
#define ADM1025_REG_TEMP_HIGH(nr) (ADM1025_REG_RTEMP_HIGH + (nr) * 2)
#define ADM1025_REG_TEMP_LOW(nr) (ADM1025_REG_RTEMP_LOW + (nr) * 2)
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val) SENSORS_LIMIT(val, 0, 255)
#define IN_FROM_REG(val) (val)
#define TEMP_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10)
#define TEMP_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10):\
((val)+5)/10),-128,127)
#define ALARMS_FROM_REG(val) (val)
/* For each registered ADM1025, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new adm1025 client is
allocated. */
struct adm1025_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[6]; /* Register value */
u8 in_max[6]; /* Register value */
u8 in_min[6]; /* Register value */
u8 temp[2]; /* Register value */
u8 temp_high[2]; /* Register value */
u8 temp_low[2]; /* Register value */
u16 alarms; /* Register encoding, combined */
u8 vid; /* Register value combined */
u8 vrm;
};
static int adm1025_attach_adapter(struct i2c_adapter *adapter);
static int adm1025_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int adm1025_detach_client(struct i2c_client *client);
static void adm1025_update_client(struct i2c_client *client);
static void adm1025_init_client(struct i2c_client *client);
static void adm1025_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1025_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1025_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1025_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1025_vrm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver adm1025_driver = {
.name = "ADM1025 sensor driver",
.id = I2C_DRIVERID_ADM1025,
.flags = I2C_DF_NOTIFY,
.attach_adapter = adm1025_attach_adapter,
.detach_client = adm1025_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define ADM1025_SYSCTL_IN0 1000 /* Volts * 100 */
#define ADM1025_SYSCTL_IN1 1001
#define ADM1025_SYSCTL_IN2 1002
#define ADM1025_SYSCTL_IN3 1003
#define ADM1025_SYSCTL_IN4 1004
#define ADM1025_SYSCTL_IN5 1005
#define ADM1025_SYSCTL_RTEMP 1250 /* Degrees Celsius * 10 */
#define ADM1025_SYSCTL_TEMP 1251
#define ADM1025_SYSCTL_ALARMS 2001 /* bitvector */
#define ADM1025_SYSCTL_VID 2003 /* Volts * 1000 */
#define ADM1025_SYSCTL_VRM 2004
#define ADM1025_ALARM_IN0 0x0001
#define ADM1025_ALARM_IN1 0x0002
#define ADM1025_ALARM_IN2 0x0004
#define ADM1025_ALARM_IN3 0x0008
#define ADM1025_ALARM_IN4 0x0100
#define ADM1025_ALARM_IN5 0x0200
#define ADM1025_ALARM_RTEMP 0x0020
#define ADM1025_ALARM_TEMP 0x0010
#define ADM1025_ALARM_RFAULT 0x4000
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected ADM1025. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table adm1025_dir_table_template[] = {
{ADM1025_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_in},
{ADM1025_SYSCTL_RTEMP, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_temp},
{ADM1025_SYSCTL_TEMP, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_temp},
{ADM1025_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_alarms},
{ADM1025_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_vid},
{ADM1025_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1025_vrm},
{0}
};
static int adm1025_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, adm1025_detect);
}
static int adm1025_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct adm1025_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("adm1025.o: adm1025_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access adm1025_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct adm1025_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &adm1025_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if ((i2c_smbus_read_byte_data(new_client,
ADM1025_REG_CONFIG) & 0x80) != 0x00
|| (i2c_smbus_read_byte_data(new_client,
ADM1025_REG_STATUS1) & 0xC0) != 0x00
|| (i2c_smbus_read_byte_data(new_client,
ADM1025_REG_STATUS2) & 0xBC) != 0x00)
goto ERROR1;
}
/* Determine the chip type. */
if (kind <= 0) {
u8 man_id, chip_id;
man_id = i2c_smbus_read_byte_data(new_client,
ADM1025_REG_COMPANY_ID);
chip_id = i2c_smbus_read_byte_data(new_client,
ADM1025_REG_DIE_REV);
if (man_id == 0x41) { /* Analog Devices */
if ((chip_id & 0xF0) == 0x20) /* ADM1025 */
kind = adm1025;
} else if (man_id == 0xA1) { /* Philips */
if (address != 0x2E
&& (chip_id & 0xF0) == 0x20) /* NE1619 */
kind = ne1619;
}
}
if (kind <= 0) { /* Identification failed */
printk("adm1025.o: Unsupported chip.\n");
goto ERROR1;
}
if (kind == adm1025) {
type_name = "adm1025";
client_name = "ADM1025 chip";
} else if (kind == ne1619) {
type_name = "ne1619";
client_name = "NE1619 chip";
} else {
#ifdef DEBUG
printk("adm1025.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
adm1025_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the ADM1025 chip */
adm1025_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int adm1025_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct adm1025_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("adm1025.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* Called when we have found a new ADM1025. */
static void adm1025_init_client(struct i2c_client *client)
{
struct adm1025_data *data = client->data;
u8 reg;
int i;
data->vrm = 82;
/* Set high limits
Usually we avoid setting limits on driver init, but it happens
that the ADM1025 comes with stupid default limits (all registers
set to 0). In case the chip has not gone through any limit
setting yet, we better set the high limits to the max so that
no alarm triggers. */
for (i=0; i<6; i++) {
reg = i2c_smbus_read_byte_data(client,
ADM1025_REG_IN_MAX(i));
if (reg == 0)
i2c_smbus_write_byte_data(client,
ADM1025_REG_IN_MAX(i),
0xFF);
}
for (i=0; i<2; i++) {
reg = i2c_smbus_read_byte_data(client,
ADM1025_REG_TEMP_HIGH(i));
if (reg == 0)
i2c_smbus_write_byte_data(client,
ADM1025_REG_TEMP_HIGH(i),
0x7F);
}
/* Start monitoring */
reg = i2c_smbus_read_byte_data(client, ADM1025_REG_CONFIG);
i2c_smbus_write_byte_data(client, ADM1025_REG_CONFIG, (reg|0x01)&0x7F);
}
static void adm1025_update_client(struct i2c_client *client)
{
struct adm1025_data *data = client->data;
u8 nr;
down(&data->update_lock);
if ((jiffies - data->last_updated > 2 * HZ)
|| (jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting adm1025 update\n");
#endif
/* Voltages */
for (nr = 0; nr < 6; nr++) {
data->in[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN(nr));
data->in_min[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MIN(nr));
data->in_max[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_IN_MAX(nr));
}
/* Temperatures */
for (nr = 0; nr < 2; nr++) {
data->temp[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP(nr));
data->temp_high[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_HIGH(nr));
data->temp_low[nr] = i2c_smbus_read_byte_data(client, ADM1025_REG_TEMP_LOW(nr));
}
/* VID */
data->vid = (i2c_smbus_read_byte_data(client, ADM1025_REG_VID) & 0x0f)
+ ((i2c_smbus_read_byte_data(client, ADM1025_REG_VID4) & 0x01) << 4);
/* Alarms */
data->alarms = (i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS1) & 0x3f)
+ ((i2c_smbus_read_byte_data(client, ADM1025_REG_STATUS2) & 0x43) << 8);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the data
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void adm1025_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int scales[6] = { 250, 225, 330, 500, 1200, 330 };
struct adm1025_data *data = client->data;
int nr = ctl_name - ADM1025_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1025_update_client(client);
results[0] = (IN_FROM_REG(data->in_min[nr]) * scales[nr] + 96) / 192;
results[1] = (IN_FROM_REG(data->in_max[nr]) * scales[nr] + 96) / 192;
results[2] = (IN_FROM_REG(data->in[nr]) * scales[nr] + 96) / 192;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] = IN_TO_REG((results[0] * 192 + scales[nr] / 2)
/ scales[nr]);
i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] = IN_TO_REG((results[1] * 192 + scales[nr] / 2)
/ scales[nr]);
i2c_smbus_write_byte_data(client, ADM1025_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
void adm1025_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1025_data *data = client->data;
int nr = ctl_name - ADM1025_SYSCTL_RTEMP;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1025_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_high[nr]);
results[1] = TEMP_FROM_REG(data->temp_low[nr]);
results[2] = TEMP_FROM_REG(data->temp[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_high[nr] = TEMP_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_HIGH(nr),
data->temp_high[nr]);
}
if (*nrels_mag >= 2) {
data->temp_low[nr] = TEMP_TO_REG(results[1]);
i2c_smbus_write_byte_data(client, ADM1025_REG_TEMP_LOW(nr),
data->temp_low[nr]);
}
}
}
void adm1025_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1025_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1025_update_client(client);
results[0] = ALARMS_FROM_REG(data->alarms);
*nrels_mag = 1;
}
}
void adm1025_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1025_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1025_update_client(client);
results[0] = vid_from_reg(data->vid, data->vrm);
*nrels_mag = 1;
}
}
void adm1025_vrm(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm1025_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = data->vrm;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1)
data->vrm = results[0];
}
}
static int __init sm_adm1025_init(void)
{
printk("adm1025.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&adm1025_driver);
}
static void __exit sm_adm1025_exit(void)
{
i2c_del_driver(&adm1025_driver);
}
MODULE_AUTHOR("Chen-Yuan Wu <gwu@esoft.com>"
" and Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("ADM1025 driver");
module_init(sm_adm1025_init);
module_exit(sm_adm1025_exit);
File diff suppressed because it is too large. Load diff
-643
View File
@@ -1,643 +0,0 @@
/*
adm1031.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Based on lm75.c and lm85.c
Supports Analog Devices ADM1030 and ADM1031
Copyright (C) 2004 Alexandre d'Alton <alex@alexdalton.org> and
Jean Delvare <khali@linux-fr.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.
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/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include "version.h"
/* Following macros take channel parameter starting from 0 */
#define ADM1031_REG_FAN_SPEED(nr) (0x08 + (nr))
#define ADM1031_REG_FAN_DIV(nr) (0x20 + (nr))
#define ADM1031_REG_FAN_MIN(nr) (0x10 + (nr))
#define ADM1031_REG_TEMP_MAX(nr) (0x14 + 4 * (nr))
#define ADM1031_REG_TEMP_MIN(nr) (0x15 + 4 * (nr))
#define ADM1031_REG_TEMP_CRIT(nr) (0x16 + 4 * (nr))
#define ADM1031_REG_TEMP(nr) (0x0a + (nr))
#define ADM1031_REG_AUTO_TEMP(nr) (0x24 + (nr))
#define ADM1031_REG_STATUS(nr) (0x02 + (nr))
#define ADM1031_REG_FAN_PWM 0x22
#define ADM1031_REG_CONF1 0x00
#define ADM1031_REG_CONF2 0x01
#define ADM1031_REG_EXT_TEMP 0x06
#define ADM1031_CONF1_MONITOR_ENABLE 0x01 /* Monitoring enable */
#define ADM1031_CONF1_PWM_INVERT 0x08 /* PWM Invert (unused) */
#define ADM1031_CONF1_AUTO_MODE 0x80 /* Auto fan mode */
#define ADM1031_CONF2_PWM1_ENABLE 0x01
#define ADM1031_CONF2_PWM2_ENABLE 0x02
#define ADM1031_CONF2_TACH1_ENABLE 0x04
#define ADM1031_CONF2_TACH2_ENABLE 0x08
#define ADM1031_CONF2_TEMP_ENABLE(chan) (0x10 << (chan))
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_2(adm1030, adm1031);
/*
* Proc entries
* These files are created for each detected ADM1031.
*/
/* -- SENSORS SYSCTL START -- */
#define ADM1031_SYSCTL_TEMP1 1200
#define ADM1031_SYSCTL_TEMP2 1201
#define ADM1031_SYSCTL_TEMP3 1202
#define ADM1031_SYSCTL_FAN1 1210
#define ADM1031_SYSCTL_FAN2 1211
#define ADM1031_SYSCTL_FAN_DIV 1220
#define ADM1031_SYSCTL_ALARMS 1250
#define ADM1031_ALARM_FAN1_MIN 0x0001
#define ADM1031_ALARM_FAN1_FLT 0x0002
#define ADM1031_ALARM_TEMP2_HIGH 0x0004
#define ADM1031_ALARM_TEMP2_LOW 0x0008
#define ADM1031_ALARM_TEMP2_CRIT 0x0010
#define ADM1031_ALARM_TEMP2_DIODE 0x0020
#define ADM1031_ALARM_TEMP1_HIGH 0x0040
#define ADM1031_ALARM_TEMP1_LOW 0x0080
#define ADM1031_ALARM_FAN2_MIN 0x0100
#define ADM1031_ALARM_FAN2_FLT 0x0200
#define ADM1031_ALARM_TEMP3_HIGH 0x0400
#define ADM1031_ALARM_TEMP3_LOW 0x0800
#define ADM1031_ALARM_TEMP3_CRIT 0x1000
#define ADM1031_ALARM_TEMP3_DIODE 0x2000
#define ADM1031_ALARM_TEMP1_CRIT 0x4000
#define ADM1031_ALARM_THERMAL 0x8000
/* -- SENSORS SYSCTL END -- */
static void adm1031_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1031_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1031_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1031_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static ctl_table adm1031_dir_table_template[] = {
{ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_temp},
{ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_temp},
{ADM1031_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_temp},
{ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_fan},
{ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_fan_div},
{ADM1031_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_fan},
{ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_alarms},
{0}
};
static ctl_table adm1030_dir_table_template[] = {
{ADM1031_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_temp},
{ADM1031_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_temp},
{ADM1031_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_fan},
{ADM1031_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_fan_div},
{ADM1031_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real,
&i2c_sysctl_real, NULL, &adm1031_alarms},
{0}
};
/* Each client has this additional data */
struct adm1031_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
int chip_type;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u16 alarms;
u8 conf1;
u8 conf2;
u8 fan[2];
u8 fan_min[2];
u8 fan_pwm[2];
u8 fan_div[2];
s8 temp[3];
u8 auto_temp[3];
u8 ext_temp[3];
s8 temp_min[3];
s8 temp_max[3];
s8 temp_crit[3];
};
static int adm1031_attach_adapter(struct i2c_adapter *adapter);
static int adm1031_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void adm1031_init_client(struct i2c_client *client);
static int adm1031_detach_client(struct i2c_client *client);
static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg);
static inline int adm1031_write_value(struct i2c_client *client, u8 reg,
unsigned int value);
static void adm1031_update_client(struct i2c_client *client);
/* This is the driver that will be inserted */
static struct i2c_driver adm1031_driver = {
.name = "ADM1031/ADM1030 sensor driver",
.flags = I2C_DF_NOTIFY,
.attach_adapter = adm1031_attach_adapter,
.detach_client = adm1031_detach_client,
};
#define TEMP_TO_REG(val) ((val) < 0 ? (((val) - 500) / 1000) : \
(((val) + 500) / 1000))
#define TEMP_FROM_REG(reg) ((reg) * 1000)
#define TEMP_FROM_REG_EXT(val,ext) (TEMP_FROM_REG(val) + (ext) * 125)
#define FAN_FROM_REG(reg,div) ((reg) ? 675000 / ((reg) * (div)) : 0)
#define FAN_TO_REG(val,div) ((val) <= 0 ? 0 : \
(val) * (div) >= 675000 ? 1 : \
(val) * (div) <= 2647 ? 255 : \
675000 / ((val) * (div)))
#define FAN_DIV_TO_REG(val) ((val) == 8 ? 0xc0 : \
(val) == 4 ? 0x80 : \
(val) == 1 ? 0x00 : 0x40)
#define FAN_DIV_FROM_REG(reg) (1 << (((reg)&0xc0) >> 6))
#define AUTO_TEMP_MIN_FROM_REG(reg) (((reg) >> 3) * 4)
static int adm1031_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, adm1031_detect);
}
/* This function is called by i2c_detect */
static int
adm1031_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
struct i2c_client *new_client;
struct adm1031_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto exit;
if (!(data = kmalloc(sizeof(struct adm1031_data), GFP_KERNEL))) {
err = -ENOMEM;
goto exit;
}
memset(data, 0, sizeof(struct adm1031_data));
new_client = &data->client;
new_client->data = data;
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &adm1031_driver;
new_client->flags = 0;
if (kind < 0) {
int id, co;
id = i2c_smbus_read_byte_data(new_client, 0x3d);
co = i2c_smbus_read_byte_data(new_client, 0x3e);
if (((id != 0x31) || (id != 0x30)) && (co != 0x41))
goto exit_free;
kind = (id == 0x30) ? adm1030 : adm1031;
}
if (kind <= 0)
kind = adm1031;
/* Given the detected chip type, set the chip name and the
* auto fan control helper table. */
if (kind == adm1030) {
type_name = "adm1030";
client_name = "ADM1030 chip";
} else if (kind == adm1031) {
type_name = "adm1031";
client_name = "ADM1031 chip";
}
data->chip_type = kind;
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto exit_free;
if (kind == adm1030) {
if ((err = i2c_register_entry(new_client, type_name,
adm1030_dir_table_template,
THIS_MODULE)) < 0)
goto exit_detach;
} else if (kind == adm1031) {
if ((err = i2c_register_entry(new_client, type_name,
adm1031_dir_table_template,
THIS_MODULE)) < 0)
goto exit_detach;
}
data->sysctl_id = err;
/* Initialize the ADM1031 chip */
adm1031_init_client(new_client);
return 0;
exit_detach:
i2c_detach_client(new_client);
exit_free:
kfree(data);
exit:
return err;
}
static int adm1031_detach_client(struct i2c_client *client)
{
int ret;
struct adm1031_data *data = client->data;
i2c_deregister_entry(data->sysctl_id);
if ((ret = i2c_detach_client(client)) != 0) {
return ret;
}
kfree(data);
return 0;
}
static inline u8 adm1031_read_value(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
static inline int adm1031_write_value(struct i2c_client *client, u8 reg,
unsigned int value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
static void adm1031_init_client(struct i2c_client *client)
{
unsigned int read_val;
unsigned int mask;
struct adm1031_data *data = client->data;
mask = (ADM1031_CONF2_PWM1_ENABLE | ADM1031_CONF2_TACH1_ENABLE);
if (data->chip_type == adm1031) {
mask |= (ADM1031_CONF2_PWM2_ENABLE |
ADM1031_CONF2_TACH2_ENABLE);
}
/* Initialize the ADM1031 chip (enable fan speed reading) */
read_val = adm1031_read_value(client, ADM1031_REG_CONF2);
if ((read_val | mask) != read_val) {
adm1031_write_value(client, ADM1031_REG_CONF2,
read_val | mask);
}
read_val = adm1031_read_value(client, ADM1031_REG_CONF1);
if ((read_val | ADM1031_CONF1_MONITOR_ENABLE) != read_val) {
adm1031_write_value(client, ADM1031_REG_CONF1, read_val |
ADM1031_CONF1_MONITOR_ENABLE);
}
}
static void adm1031_update_client(struct i2c_client *client)
{
struct adm1031_data *data = client->data;
int chan;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk(KERN_INFO "adm1031.o: Starting chip update\n");
#endif
for (chan = 0;
chan < ((data->chip_type == adm1031) ? 3 : 2);
chan++) {
u8 oldh, newh;
oldh = adm1031_read_value(client,
ADM1031_REG_TEMP(chan));
data->ext_temp[chan] = adm1031_read_value(client,
ADM1031_REG_EXT_TEMP);
newh = adm1031_read_value(client,
ADM1031_REG_TEMP(chan));
if (newh != oldh) {
data->ext_temp[chan] = adm1031_read_value(client,
ADM1031_REG_EXT_TEMP);
#ifdef DEBUG
oldh = adm1031_read_value(client,
ADM1031_REG_TEMP(chan));
/* oldh is actually newer */
if (newh != oldh)
printk(KERN_INFO "adm1031.o: Remote "
"temperature may be wrong.\n");
#endif
}
data->temp[chan] = newh;
data->temp_min[chan] = adm1031_read_value(client,
ADM1031_REG_TEMP_MIN(chan));
data->temp_max[chan] = adm1031_read_value(client,
ADM1031_REG_TEMP_MAX(chan));
data->temp_crit[chan] = adm1031_read_value(client,
ADM1031_REG_TEMP_CRIT(chan));
data->auto_temp[chan] = adm1031_read_value(client,
ADM1031_REG_AUTO_TEMP(chan));
}
data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1);
data->alarms = adm1031_read_value(client, ADM1031_REG_STATUS(0))
| (adm1031_read_value(client, ADM1031_REG_STATUS(1))
<< 8);
if (data->chip_type == adm1030) {
data->alarms &= 0xc0ff;
}
for (chan = 0;
chan < (data->chip_type == adm1030 ? 1 : 2);
chan++) {
data->fan_div[chan] = adm1031_read_value(client,
ADM1031_REG_FAN_DIV(chan));
data->fan_min[chan] = adm1031_read_value(client,
ADM1031_REG_FAN_MIN(chan));
data->fan[chan] = adm1031_read_value(client,
ADM1031_REG_FAN_SPEED(chan));
data->fan_pwm[chan] = (adm1031_read_value(client,
ADM1031_REG_FAN_PWM) >> (4 * chan))
& 0x0f;
}
data->conf1 = adm1031_read_value(client, ADM1031_REG_CONF1);
data->conf2 = adm1031_read_value(client, ADM1031_REG_CONF2);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
static int __init sensors_adm1031_init(void)
{
printk(KERN_INFO "adm1031.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&adm1031_driver);
}
static void __exit sensors_adm1031_exit(void)
{
i2c_del_driver(&adm1031_driver);
}
static void
adm1031_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int ext;
struct adm1031_data *data = client->data;
int tmp;
int nr = ctl_name - ADM1031_SYSCTL_TEMP1;
ext = nr == 0 ?
((data->ext_temp[nr] >> 6) & 0x3) * 2 :
(((data->ext_temp[nr] >> ((nr - 1) * 3)) & 7));
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1031_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_max[nr]);
results[1] = TEMP_FROM_REG(data->temp_min[nr]);
results[2] = TEMP_FROM_REG(data->temp_crit[nr]);
results[3] = TEMP_FROM_REG_EXT(data->temp[nr], ext);
*nrels_mag = 4;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
tmp = SENSORS_LIMIT(results[0], -55000, 127000);
data->temp_max[nr] = TEMP_TO_REG(tmp);
adm1031_write_value(client,
ADM1031_REG_TEMP_MAX(nr),
data->temp_max[nr]);
}
if (*nrels_mag >= 2) {
tmp = SENSORS_LIMIT(results[1], -55000, 127000);
data->temp_min[nr] = TEMP_TO_REG(tmp);
adm1031_write_value(client,
ADM1031_REG_TEMP_MIN(nr),
data->temp_min[nr]);
}
if (*nrels_mag >= 3) {
tmp = SENSORS_LIMIT(results[2], -55000, 127000);
data->temp_crit[nr] = TEMP_TO_REG(tmp);
adm1031_write_value(client,
ADM1031_REG_TEMP_CRIT(nr),
data->temp_crit[nr]);
}
}
}
/*
* That function checks the cases where the fan reading is not
* relevant. It is used to provide 0 as fan reading when the fan is
* not supposed to run.
*/
static int trust_fan_readings(struct adm1031_data * data, int chan)
{
int res = 0;
if (data->conf1 & ADM1031_CONF1_AUTO_MODE) {
switch(data->conf1 & 0x60) {
case 0x00: /* temp2 controls fan1, temp3 controls fan2 */
res = data->temp[chan+1] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[chan+1]);
break;
case 0x20: /* temp2 controls both fans */
res = data->temp[1] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[1]);
break;
case 0x40: /* temp3 controls both fans */
res = data->temp[2] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[2]);
break;
case 0x60: /* max of temp1, temp2 and temp3 controls both
fans */
res = data->temp[0] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[0])
|| data->temp[1] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[1])
|| (data->chip_type == adm1031
&& data->temp[2] >=
AUTO_TEMP_MIN_FROM_REG(data->auto_temp[2]));
break;
}
} else {
res = data->fan_pwm[chan] > 0;
}
return res;
}
static void adm1031_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1031_data *data = client->data;
int nr = ctl_name - ADM1031_SYSCTL_FAN1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1031_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr],
FAN_DIV_FROM_REG(data->fan_div[nr]));
results[1] = trust_fan_readings(data, nr) ?
FAN_FROM_REG(data->fan[nr],
FAN_DIV_FROM_REG(data->fan_div[nr])) : 0;
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr] = FAN_TO_REG(results[0],
FAN_DIV_FROM_REG(data->fan_div[nr]));
adm1031_write_value(client,
ADM1031_REG_FAN_MIN(nr),
data->fan_min[nr]);
}
}
}
static void adm1031_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1031_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1031_update_client(client);
results[0] = FAN_DIV_FROM_REG(data->fan_div[0]);
*nrels_mag = 1;
if(data->chip_type == adm1031) {
results[1] = FAN_DIV_FROM_REG(data->fan_div[1]);
*nrels_mag = 2;
}
} else if (operation == SENSORS_PROC_REAL_WRITE) {
int old_div, new_min;
if (*nrels_mag >= 1) {
old_div = FAN_DIV_FROM_REG(data->fan_div[0]);
data->fan_div[0] = FAN_DIV_TO_REG(results[0]);
adm1031_write_value(client,
ADM1031_REG_FAN_DIV(0),
data->fan_div[0]);
new_min = data->fan_min[0] * old_div /
FAN_DIV_FROM_REG(data->fan_div[0]);
data->fan_min[0] = new_min > 0xff ? 0xff : new_min;
adm1031_write_value(client,
ADM1031_REG_FAN_MIN(0),
data->fan_min[0]);
}
if (*nrels_mag >= 2) {
old_div = FAN_DIV_FROM_REG(data->fan_div[1]);
data->fan_div[1] = FAN_DIV_TO_REG(results[1]);
adm1031_write_value(client,
ADM1031_REG_FAN_DIV(1),
data->fan_div[1]);
new_min = data->fan_min[1] * old_div /
FAN_DIV_FROM_REG(data->fan_div[1]);
data->fan_min[1] = new_min > 0xff ? 0xff : new_min;
adm1031_write_value(client,
ADM1031_REG_FAN_MIN(1),
data->fan_min[1]);
}
}
}
static void adm1031_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm1031_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm1031_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
MODULE_AUTHOR("Alexandre d'Alton <alex@alexdalton.org>");
MODULE_DESCRIPTION("ADM1031/ADM1030 driver");
MODULE_LICENSE("GPL");
module_init(sensors_adm1031_init);
module_exit(sensors_adm1031_exit);
-700
View File
@@ -1,700 +0,0 @@
/*
adm9240.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (C) 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Supports ADM9240, DS1780, and LM81. See doc/chips/adm9240 for details */
/*
A couple notes about the ADM9240:
* It claims to be 'LM7x' register compatible. This must be in reference
to only the LM78, because it is missing stuff to emulate LM75's as well.
(like the Winbond W83781 does)
* This driver was written from rev. 0 of the PDF, but it seems well
written and complete (unlike the W83781 which is horrible and has
supposedly gone through a few revisions.. rev 0 of that one must
have been in crayon on construction paper...)
* All analog inputs can range from 0 to 2.5, eventhough some inputs are
marked as being 5V, 12V, etc. I don't have any real voltages going
into my prototype, so I'm not sure that things are computed right,
but at least the limits seem to be working OK.
* Another curiousity is that the fan_div seems to be read-only. I.e.,
any written value to it doesn't seem to make any difference. The
fan_div seems to be 'stuck' at 2 (which isn't a bad value in most cases).
--Phil
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x2c, 0x2f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_3(adm9240, ds1780, lm81);
/* Many ADM9240 constants specified below */
#define ADM9240_REG_IN_MAX(nr) (0x2b + (nr) * 2)
#define ADM9240_REG_IN_MIN(nr) (0x2c + (nr) * 2)
#define ADM9240_REG_IN(nr) (0x20 + (nr))
/* The ADM9240 registers */
#define ADM9240_REG_TEST 0x15
#define ADM9240_REG_ANALOG_OUT 0x19
/* These are all read-only */
#define ADM9240_REG_2_5V 0x20
#define ADM9240_REG_VCCP1 0x21
#define ADM9240_REG_3_3V 0x22
#define ADM9240_REG_5V 0x23
#define ADM9240_REG_12V 0x24
#define ADM9240_REG_VCCP2 0x25
#define ADM9240_REG_TEMP 0x27
#define ADM9240_REG_FAN1 0x28
#define ADM9240_REG_FAN2 0x29
#define ADM9240_REG_COMPANY_ID 0x3E /* 0x23 for ADM9240; 0xDA for DS1780 */
/* 0x01 for LM81 */
#define ADM9240_REG_DIE_REV 0x3F
/* These are read/write */
#define ADM9240_REG_2_5V_HIGH 0x2B
#define ADM9240_REG_2_5V_LOW 0x2C
#define ADM9240_REG_VCCP1_HIGH 0x2D
#define ADM9240_REG_VCCP1_LOW 0x2E
#define ADM9240_REG_3_3V_HIGH 0x2F
#define ADM9240_REG_3_3V_LOW 0x30
#define ADM9240_REG_5V_HIGH 0x31
#define ADM9240_REG_5V_LOW 0x32
#define ADM9240_REG_12V_HIGH 0x33
#define ADM9240_REG_12V_LOW 0x34
#define ADM9240_REG_VCCP2_HIGH 0x35
#define ADM9240_REG_VCCP2_LOW 0x36
#define ADM9240_REG_TCRIT_LIMIT 0x37 /* LM81 only - not supported */
#define ADM9240_REG_LOW_LIMIT 0x38 /* LM81 only - not supported */
#define ADM9240_REG_TOS 0x39
#define ADM9240_REG_THYST 0x3A
#define ADM9240_REG_FAN1_MIN 0x3B
#define ADM9240_REG_FAN2_MIN 0x3C
#define ADM9240_REG_CONFIG 0x40
#define ADM9240_REG_INT1_STAT 0x41
#define ADM9240_REG_INT2_STAT 0x42
#define ADM9240_REG_INT1_MASK 0x43
#define ADM9240_REG_INT2_MASK 0x44
#define ADM9240_REG_COMPAT 0x45 /* dummy compat. register for other drivers? */
#define ADM9240_REG_CHASSIS_CLEAR 0x46
#define ADM9240_REG_VID_FAN_DIV 0x47
#define ADM9240_REG_I2C_ADDR 0x48
#define ADM9240_REG_VID4 0x49
#define ADM9240_REG_TEMP_CONFIG 0x4B
#define ADM9240_REG_EXTMODE1 0x4C /* LM81 only - not supported */
#define ADM9240_REG_EXTMODE2 0x4D /* LM81 only - not supported */
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val), 0, 255))
#define IN_FROM_REG(val,nr) (val)
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) ((val)==0?-1:\
(val)==255?0:1350000/((div)*(val)))
#define TEMP_FROM_REG(temp) ((temp)<256 ? (temp) * 5 : \
((temp) - 512) * 5)
#define TEMP_LIMIT_FROM_REG(val) (((val)>=0x80?(val)-0x100:(val))*10)
#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-5)/10)+256:\
((val)+5)/10), \
0,255)
#define ALARMS_FROM_REG(val) (val)
#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1)))
#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\
205-(val)*5)
/* For each registered ADM9240, we need to keep some data in memory. */
struct adm9240_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[6]; /* Register value */
u8 in_max[6]; /* Register value */
u8 in_min[6]; /* Register value */
u8 fan[2]; /* Register value */
u8 fan_min[2]; /* Register value */
u8 fan_div[2]; /* Register encoding, shifted right */
int temp; /* Temp, shifted right */
u8 temp_os_max; /* Register value */
u8 temp_os_hyst; /* Register value */
u16 alarms; /* Register encoding, combined */
u8 analog_out; /* Register value */
u8 vid; /* Register value combined */
};
static int adm9240_attach_adapter(struct i2c_adapter *adapter);
static int adm9240_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int adm9240_detach_client(struct i2c_client *client);
static void adm9240_update_client(struct i2c_client *client);
static void adm9240_init_client(struct i2c_client *client);
static void adm9240_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm9240_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm9240_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm9240_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm9240_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm9240_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag,
long *results);
static void adm9240_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver adm9240_driver = {
.name = "ADM9240 sensor driver",
.id = I2C_DRIVERID_ADM9240,
.flags = I2C_DF_NOTIFY,
.attach_adapter = adm9240_attach_adapter,
.detach_client = adm9240_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define ADM9240_SYSCTL_IN0 1000 /* Volts * 100 */
#define ADM9240_SYSCTL_IN1 1001
#define ADM9240_SYSCTL_IN2 1002
#define ADM9240_SYSCTL_IN3 1003
#define ADM9240_SYSCTL_IN4 1004
#define ADM9240_SYSCTL_IN5 1005
#define ADM9240_SYSCTL_FAN1 1101 /* Rotations/min */
#define ADM9240_SYSCTL_FAN2 1102
#define ADM9240_SYSCTL_TEMP 1250 /* Degrees Celsius * 100 */
#define ADM9240_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define ADM9240_SYSCTL_ALARMS 2001 /* bitvector */
#define ADM9240_SYSCTL_ANALOG_OUT 2002
#define ADM9240_SYSCTL_VID 2003
#define ADM9240_ALARM_IN0 0x0001
#define ADM9240_ALARM_IN1 0x0002
#define ADM9240_ALARM_IN2 0x0004
#define ADM9240_ALARM_IN3 0x0008
#define ADM9240_ALARM_IN4 0x0100
#define ADM9240_ALARM_IN5 0x0200
#define ADM9240_ALARM_FAN1 0x0040
#define ADM9240_ALARM_FAN2 0x0080
#define ADM9240_ALARM_TEMP 0x0010
#define ADM9240_ALARM_CHAS 0x1000
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected ADM9240. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table adm9240_dir_table_template[] = {
{ADM9240_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_in},
{ADM9240_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_fan},
{ADM9240_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_fan},
{ADM9240_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_temp},
{ADM9240_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_fan_div},
{ADM9240_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_alarms},
{ADM9240_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_analog_out},
{ADM9240_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &adm9240_vid},
{0}
};
static int adm9240_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, adm9240_detect);
}
static int adm9240_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct adm9240_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access adm9240_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct adm9240_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &adm9240_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if (
((i2c_smbus_read_byte_data
(new_client, ADM9240_REG_CONFIG) & 0x80) != 0x00)
||
(i2c_smbus_read_byte_data(new_client, ADM9240_REG_I2C_ADDR)
!= address))
goto ERROR1;
}
/* Determine the chip type. */
if (kind <= 0) {
i = i2c_smbus_read_byte_data(new_client,
ADM9240_REG_COMPANY_ID);
if (i == 0x23)
kind = adm9240;
else if (i == 0xda)
kind = ds1780;
else if (i == 0x01)
kind = lm81;
else {
if (kind == 0)
printk("adm9240.o: Ignoring 'force' "
"parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
goto ERROR1;
}
}
if (kind == adm9240) {
type_name = "adm9240";
client_name = "ADM9240 chip";
} else if (kind == ds1780) {
type_name = "ds1780";
client_name = "DS1780 chip";
} else if (kind == lm81) {
type_name = "lm81";
client_name = "LM81 chip";
} else {
#ifdef DEBUG
printk("adm9240.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global
list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
adm9240_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the ADM9240 chip */
adm9240_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int adm9240_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct adm9240_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk("adm9240.o: Client deregistration failed, "
"client not detached\n");
return err;
}
kfree(client->data);
return 0;
}
/* Called when we have found a new ADM9240. */
static void adm9240_init_client(struct i2c_client *client)
{
/* Start monitoring */
i2c_smbus_write_byte_data(client, ADM9240_REG_CONFIG, 0x01);
}
static void adm9240_update_client(struct i2c_client *client)
{
struct adm9240_data *data = client->data;
u8 i;
down(&data->update_lock);
if (
(jiffies - data->last_updated >
(data->type == adm9240 ? HZ / 2 : HZ * 2))
|| (jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting adm9240 update\n");
#endif
for (i = 0; i <= 5; i++) {
data->in[i] =
i2c_smbus_read_byte_data(client,
ADM9240_REG_IN(i));
data->in_min[i] =
i2c_smbus_read_byte_data(client,
ADM9240_REG_IN_MIN(i));
data->in_max[i] =
i2c_smbus_read_byte_data(client,
ADM9240_REG_IN_MAX(i));
}
data->fan[0] =
i2c_smbus_read_byte_data(client, ADM9240_REG_FAN1);
data->fan_min[0] =
i2c_smbus_read_byte_data(client, ADM9240_REG_FAN1_MIN);
data->fan[1] =
i2c_smbus_read_byte_data(client, ADM9240_REG_FAN2);
data->fan_min[1] =
i2c_smbus_read_byte_data(client, ADM9240_REG_FAN2_MIN);
data->temp =
(i2c_smbus_read_byte_data(client, ADM9240_REG_TEMP) << 1) +
((i2c_smbus_read_byte_data
(client, ADM9240_REG_TEMP_CONFIG) & 0x80) >> 7);
data->temp_os_max =
i2c_smbus_read_byte_data(client, ADM9240_REG_TOS);
data->temp_os_hyst =
i2c_smbus_read_byte_data(client, ADM9240_REG_THYST);
i = i2c_smbus_read_byte_data(client, ADM9240_REG_VID_FAN_DIV);
data->fan_div[0] = (i >> 4) & 0x03;
data->fan_div[1] = (i >> 6) & 0x03;
data->vid = i & 0x0f;
data->vid |=
(i2c_smbus_read_byte_data(client, ADM9240_REG_VID4) & 0x01)
<< 4;
data->alarms =
i2c_smbus_read_byte_data(client,
ADM9240_REG_INT1_STAT) +
(i2c_smbus_read_byte_data(client, ADM9240_REG_INT2_STAT) <<
8);
data->analog_out =
i2c_smbus_read_byte_data(client, ADM9240_REG_ANALOG_OUT);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void adm9240_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int scales[6] = { 250, 270, 330, 500, 1200, 270 };
struct adm9240_data *data = client->data;
int nr = ctl_name - ADM9240_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] =
IN_FROM_REG(data->in_min[nr], nr) * scales[nr] / 192;
results[1] =
IN_FROM_REG(data->in_max[nr], nr) * scales[nr] / 192;
results[2] =
IN_FROM_REG(data->in[nr], nr) * scales[nr] / 192;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] =
IN_TO_REG((results[0] * 192) / scales[nr], nr);
i2c_smbus_write_byte_data(client,
ADM9240_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] =
IN_TO_REG((results[1] * 192) / scales[nr], nr);
i2c_smbus_write_byte_data(client,
ADM9240_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
void adm9240_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
int nr = ctl_name - ADM9240_SYSCTL_FAN1 + 1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr - 1],
DIV_FROM_REG(data->
fan_div[nr - 1]));
results[1] =
FAN_FROM_REG(data->fan[nr - 1],
DIV_FROM_REG(data->fan_div[nr - 1]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr - 1] = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->
fan_div[nr -
1]));
i2c_smbus_write_byte_data(client, nr == 1 ?
ADM9240_REG_FAN1_MIN :
ADM9240_REG_FAN2_MIN,
data->fan_min[nr - 1]);
}
}
}
void adm9240_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = TEMP_LIMIT_FROM_REG(data->temp_os_max);
results[1] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst);
results[2] = TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_os_max = TEMP_LIMIT_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, ADM9240_REG_TOS,
data->temp_os_max);
}
if (*nrels_mag >= 2) {
data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[1]);
i2c_smbus_write_byte_data(client, ADM9240_REG_THYST,
data->temp_os_hyst);
}
}
}
void adm9240_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = ALARMS_FROM_REG(data->alarms);
*nrels_mag = 1;
}
}
void adm9240_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = i2c_smbus_read_byte_data(client,
ADM9240_REG_VID_FAN_DIV);
if (*nrels_mag >= 2) {
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0x3f) | (data->fan_div[1] << 6);
}
if (*nrels_mag >= 1) {
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0xcf) | (data->fan_div[0] << 4);
i2c_smbus_write_byte_data(client,
ADM9240_REG_VID_FAN_DIV,
old);
}
}
}
void adm9240_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = data->analog_out;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->analog_out = results[0];
i2c_smbus_write_byte_data(client,
ADM9240_REG_ANALOG_OUT,
data->analog_out);
}
}
}
void adm9240_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct adm9240_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
adm9240_update_client(client);
results[0] = VID_FROM_REG(data->vid);
*nrels_mag = 1;
}
}
static int __init sm_adm9240_init(void)
{
printk("adm9240.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&adm9240_driver);
}
static void __exit sm_adm9240_exit(void)
{
i2c_del_driver(&adm9240_driver);
}
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> "
"and Philip Edelbrock <phil@netroedge.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ADM9240 driver");
module_init(sm_adm9240_init);
module_exit(sm_adm9240_exit);
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-886
View File
@@ -1,886 +0,0 @@
/*
bt869.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
Copyright (c) 2001, 2002 Stephen Davies <steve@daviesfam.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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#define DEBUG 1
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
/* found only at 0x44 or 0x45 */
static unsigned short normal_i2c_range[] = { 0x44, 0x45, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(bt869);
/* Many bt869 constants specified below */
/* The bt869 registers */
/* Coming soon: Many, many registers */
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
/*none */
/* Initial values */
/*none*/
/* Each client has this additional data */
struct bt869_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 status[3]; /* Register values */
u16 res[2]; /* Resolution XxY */
u8 ntsc; /* 1=NTSC, 0=PAL */
u8 half; /* go half res */
u8 depth; /* screen depth */
u8 colorbars; /* turn on/off colorbar calibration screen */
u8 svideo; /* output format: (2=RGB) 1=SVIDEO, 0=Composite */
};
static int bt869_attach_adapter(struct i2c_adapter *adapter);
static int bt869_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void bt869_init_client(struct i2c_client *client);
static int bt869_detach_client(struct i2c_client *client);
static int bt869_read_value(struct i2c_client *client, u8 reg);
static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value);
static void bt869_write_values(struct i2c_client *client, u16 *values);
static void bt869_status(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_ntsc(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_res(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_half(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_colorbars(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_svideo(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_depth(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void bt869_update_client(struct i2c_client *client);
/* This is the driver that will be inserted */
static struct i2c_driver bt869_driver = {
.name = "BT869 video-output chip driver",
.id = I2C_DRIVERID_BT869,
.flags = I2C_DF_NOTIFY,
.attach_adapter = bt869_attach_adapter,
.detach_client = bt869_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define BT869_SYSCTL_STATUS 1000
#define BT869_SYSCTL_NTSC 1001
#define BT869_SYSCTL_HALF 1002
#define BT869_SYSCTL_RES 1003
#define BT869_SYSCTL_COLORBARS 1004
#define BT869_SYSCTL_DEPTH 1005
#define BT869_SYSCTL_SVIDEO 1006
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected bt869. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table bt869_dir_table_template[] = {
{BT869_SYSCTL_STATUS, "status", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_status},
{BT869_SYSCTL_NTSC, "ntsc", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_ntsc},
{BT869_SYSCTL_RES, "res", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_res},
{BT869_SYSCTL_HALF, "half", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_half},
{BT869_SYSCTL_COLORBARS, "colorbars", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_colorbars},
{BT869_SYSCTL_DEPTH, "depth", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_depth},
{BT869_SYSCTL_SVIDEO, "svideo", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &bt869_svideo},
{0}
};
/* ******************
720x576, 27.5MHz, PAL, no overscan compensation.
This mode should be use for digital video, DVD playback etc.
NOTE: This mode for PAL, see 720x480 for an equivalent NTSC mode
NOTE: -- Steve Davies <steve@daviesfam.org>
Compatible X modeline:
Mode "720x576-BT869"
DotClock 27.5
HTimings 720 744 800 880
VTimings 576 581 583 625
EndMode
625LINE=1 625 line output format
BST_AMP[7:0]=x57 87 Burst ampl. multiplication factor (PAL std??)
BY_PLL=0 Use the PLL
CATTENUATE[2:0]=0 No chroma attenuation
CCF1B1[7:0]=0 close caption stuff
CCF1B2[7:0]=0 close caption stuff
CCF2B1[7:0]=0 close caption stuff
CCF2B2[7:0]=0 close caption stuff
CCORING[2:0]=0 Bypass chroma coring
CCR_START[8:0]=0 [CCR_START[8]=0; CCR_START[7:0]=0] Close-caption clock runin start from hsync
CC_ADD[11:0]=xD2 210 [CC_ADD[11:8]=0; CC_ADD[7:0]=xD2] Close-caption DTO increment
CHECK_STAT=0 Don't check monitor status
CLPF[1:0]=0 Hoz chroma lowpass filter=Bypass
DACDISA=1 Disable DACA
DACDISB=0 Don't disable DACB
DACDISC=0 Don't disable DACC
DACOFF=0 Don't disable the DACs
DATDLY = 0 normal
DATSWP=0 normal
DCHROMA=0 Don't blank chroma
DIS_FFILT=1 Disable flickerfilter
DIS_GMSHC=1 Disable chroma psuedo-gamma removal
DIS_GMSHY=1 Disable luma pseudo gamma removal
DIS_GMUSHC=1 Disable chroma anti-pseudo gamma removal
DIS_GMUSHY=1 Disable luma anti-pseudo gamma removal
DIS_SCRESET=0 Normal subcarrier phase resets
DIS_YFLPF=0 Disable Luma initial hoz low pass filter
DIV2=0 Input pixel rate not divided by 2
ECBAR=0 No colour bars
ECCF1=0 Disable closed caption
ECCF2=0 Disable closed caption
ECCGATE=0 Normal close caption encoding
ECLIP=0 0=disable clipping
EN_ASYNC=0 set to 0 for normal operation
EN_BLANKO=0 BLANK is an input
EN_DOT=0 Disables dot clock sync on BLANK pin
EN_OUT=1 Allows outputs to be enabled
EN_XCLK=1 Use CLKI pin as clock source
ESTATUS[1:0]=0 Used to select readback register
FIELDI=0 Logical 1 on FIELD indicates even field
F_SELC[2:0]=0 5 line chroma flickerfilter
F_SELY[2:0]=0 5 line luma flickerfilter
HBURST_BEGIN[7:0]=x98 152 Chroma burst start point in clocks
HBURST_END[7:0]=x58 88 Chroma burst end point in clocks - 128
HSYNCI=0 Active low HSYNC
HSYNC_WIDTH[7:0]=x80 128 Analogue sync width in clocks
HSYNOFFSET[9:0]=0 [HSYNOFFSET[9:8]=0; HSYNOFFSET[7:0]=0] hsync in "standard position"
HSYNWIDTH[5:0]=2 2 pixel hsync width
H_ACTIVE[9:0]=x2D0 720 [H_ACTIVE[9:8]=2; H_ACTIVE[7:0]=xD0] Active pixels per line
H_BLANKI[8:0]=x84 132 [H_BLANKI[8]=0; H_BLANKI[7:0]=x84] End of blanking of input video
H_BLANKO[9:0]=x120 288 [H_BLANKO[9:8]=1; H_BLANKO[7:0]=x20] End of blanking from hoz sync leading edge
H_CLKI[10:0]=x378 888 [H_CLKI[10:8]=3; H_CLKI[7:0]=x78] Input line length total in clocks
H_CLKO[11:0]=x6e0 1760 [H_CLKO[11:8]=6; H_CLKO[7:0]=xe0] Output clocks per line
H_FRACT[7:0]=0 0 fractional input clocks per line
IN_MODE[2:0]=0 24Bit RGB muxed
LUMADLY[1:0]=0 0 pixel delay on Y_DLY luma
MCB[7:0]=x49 73 Mult factor for CB prior to subcarrier mod.
MCR[7:0]=x82 130 Mult factor for CR prior to subcarrier mod.
MODE2X=0 Don't divide clock input by 2
MSC[31:0]=x2945E0B4 692445365 [MSC[31:24]=x29; MSC[23:16]=x45; MSC[15:8]=xE0; MSC[7:0]=xB4] Subcarrier incr.
MY[7:0]=x8C 140 Mult factor for Y
NI_OUT=0 Normal interlaced output
OUT_MODE[1:0]=0 video0-3 is CVBS, Y, C, Y_DLY
OUT_MUXA[1:0]=0 Don't care as DACA is disabled
OUT_MUXB[1:0]=1 Output video[1] (Y) on DACB
OUT_MUXC[1:0]=2 Output video[2] (C) on DACC
PAL_MD=1 Video output in PAL mode
PHASE_OFF[7:0]=0 Subcarrier phase offset
PLL_FRACT[15:0]=x30 48 [PLL_FRACT[15:8]=0x0; PLL_FRACT[7:0]=x30] frac portion of pll multiplier
PLL_INT[5:0]=0x0C 12 Int portion of pll multiplier
SETUP=0 7.5-IRE setup disabled
SLAVER=1
SRESET=0 Don't do a software reset
SYNC_AMP[7:0]=xF0 240 Sync amp mult. factor (PAL std???)
VBLANKDLY=0 Extra line of blanking in 2nd field?
VSYNCI=0 Active low VSYNC
VSYNC_DUR=0 2.5line VSYNC duration on output
VSYNCOFFSET[10:0]=0 [VSYNOFFSET[10:8]=0; VSYNOFFSET[7:0]=0] VSYNC in standard position
VSYNWIDTH[2:0]=1 1 line of vsync width
V_ACTIVEI[9:0]=x240 576 [V_ACTIVEI[9:0]=2; V_ACTIVEI[7:0]=x40] Active input lines
V_ACTIVEO[8:0]=x122 290 [V_ACTIVE0[8]=1; V_ACTIVEO[7:0]=x22]
V_BLANKI[7:0]=x2A 42 Input lines from vsync to first active line
V_BLANKO[7:0]=x16 22
V_LINESI[9:0]=x271 625 [V_LINESI[9:8]=2; V_LINESI[7:0]=x71] Number of input lines
V_SCALE[13:0]=x1000 4096 [V_SCALE[13:8]=x10; V_SCALE[7:0]=0] Vert scale coefficient="none"?
YATTENUATE[2:0]=0 no luma attenuation
YCORING[2:0]=0 Luma-coring bypass
YLPF[1:0]=0 Luma hoz low pass filter=bypass
***************** */
static u16 registers_720_576[] =
{
0x6e, 0x00, /* HSYNOFFSET[7:0]=0 */
0x70, 0x02, /* HSYNOFFSET[9:8]=0; HSYNWIDTH[5:0]=2 */
0x72, 0x00, /* VSYNOFFSET[7:0]=0 */
0x74, 0x01, /* DATDLY = 0; DATSWP=0; VSYNOFFSET[10:8]=0; VSYNWIDTH[2:0]=1 */
0x76, 0xe0, /* H_CLKO[7:0]=xe0 */
0x78, 0xd0, /* H_ACTIVE[7:0]=xD0 */
0x7a, 0x80, /* HSYNC_WIDTH[7:0]=x80 */
0x7c, 0x98, /* HBURST_BEGIN[7:0]=x98 */
0x7e, 0x58, /* HBURST_END[7:0]=x58 */
0x80, 0x20, /* H_BLANKO[7:0]=x20 */
0x82, 0x16, /* V_BLANKO[7:0]=x16 */
0x84, 0x22, /* V_ACTIVEO[7:0]=x22 */
0x86, 0xa6, /* V_ACTIVE0[8]=1; H_ACTIVE[9:8]=2; H_CLKO[11:8]=6 */
0x88, 0x00, /* H_FRACT[7:0]=0 */
0x8a, 0x78, /* H_CLKI[7:0]=x78 */
0x8c, 0x80, /* H_BLANKI[7:0]=x84 */
0x8e, 0x03, /* VBLANKDLY=0; H_BLANKI[8]=0; H_CLKI[10:8]=3 */
0x90, 0x71, /* V_LINESI[7:0]=x71 */
0x92, 0x2a, /* V_BLANKI[7:0]=x2A */
0x94, 0x40, /* V_ACTIVEI[7:0]=x40 */
0x96, 0x0a, /* CLPF[1:0]=0; YLPF[1:0]=0; V_ACTIVEI[9:0]=2; V_LINESI[9:8]=2 */
0x98, 0x00, /* V_SCALE[7:0]=0 */
0x9a, 0x50, /* H_BLANKO[9:8]=1; V_SCALE[13:8]=x10 */
0x9c, 0x30, /* PLL_FRACT[7:0]=x30 */
0x9e, 0x0, /* PLL_FRACT[15:8]=0x0 */
0xa0, 0x8c, /* EN_XCLK=1; BY_PLL=0; PLL_INT[5:0]=0x0C */
0xa2, 0x24, /* ECLIP=0; PAL_MD=1; DIS_SCRESET=0; VSYNC_DUR=0; 625LINE=1; SETUP=0; NI_OUT=0 */
0xa4, 0xf0, /* SYNC_AMP[7:0]=xF0 */
0xa6, 0x57, /* BST_AMP[7:0]=x57 */
0xa8, 0x82, /* MCR[7:0]=x82 */
0xaa, 0x49, /* MCB[7:0]=x49 */
0xac, 0x8c, /* MY[7:0]=x8C */
0xae, 0xb4, /* MSC[7:0]=xb4 */
0xb0, 0xe0, /* MSC[15:8]=xe0 */
0xb2, 0x45, /* MSC[23:16]=x45 */
0xb4, 0x29, /* MSC[31:24]=x29 */
0xb6, 0x00, /* PHASE_OFF[7:0]=0 */
//0xba, 0x21, /* SRESET=0; CHECK_STAT=0; SLAVER=1; DACOFF=0; DACDISC=0; DACDISB=0; DACDISA=1 */
0xc4, 0x01, /* ESTATUS[1:0]=0; ECCF2=0; ECCF1=0; ECCGATE=0; ECBAR=0; DCHROMA=0; EN_OUT=1 */
0xc6, 0x00, /* EN_BLANKO=0; EN_DOT=0; FIELDI=0; VSYNCI=0; HSYNCI=0; IN_MODE[2:0]=0(24bRGB) */
0xc8, 0x40, /* DIS_YFLPF=0; DIS_FFILT=1; F_SELC[2:0]=0; F_SELY[2:0]=0 */
0xca, 0xc0, /* DIS_GMUSHY=1; DIS_GMSHY=1; YCORING[2:0]=0; YATTENUATE[2:0]=0 */
0xcc, 0xc0, /* DIS_GMUSHC=1; DIS_GMSHC=1; CCORING[2:0]=0; CATTENUATE[2:0]=0 */
//0xce, 0x24, /* OUT_MUXC=2 [C]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/
//0xce, 0x04, /* OUT_MUXC=0 [CVBS]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/
0xd6, 0x00, /* OUT_MODE[1:0]=0; LUMADLY[1:0]=0 */
0, 0
};
/* ******************
720x480, 27.5MHz, NTSC no overscan compensation.
This mode should be use for digital video, DVD playback etc.
NOTE: This mode for NTSC, see 720x576 for an equivalent PAL mode
NOTE: -- Steve Davies <steve@daviesfam.org>
Compatible X modeline:
Mode "720x480-BT869"
DotClock 27.5
HTimings 720 744 800 872
VTimings 480 483 485 525
EndMode
625LINE=0 not 625 line output format
BST_AMP[7:0]=x74 116 Burst ampl. multiplication factor (NTSC std??)
BY_PLL=0 Use the PLL
CATTENUATE[2:0]=0 No chroma attenuation
CCF1B1[7:0]=0 close caption stuff
CCF1B2[7:0]=0 close caption stuff
CCF2B1[7:0]=0 close caption stuff
CCF2B2[7:0]=0 close caption stuff
CCORING[2:0]=0 Bypass chroma coring
CCR_START[8:0]=0 [CCR_START[8]=0; CCR_START[7:0]=0] Close-caption clock runin start from hsync
CC_ADD[11:0]=xD2 210 [CC_ADD[11:8]=0; CC_ADD[7:0]=xD2] Close-caption DTO increment
CHECK_STAT=0 Don't check monitor status
CLPF[1:0]=0 Hoz chroma lowpass filter=Bypass
DACDISA=1 Disable DACA
DACDISB=0 Don't disable DACB
DACDISC=0 Don't disable DACC
DACOFF=0 Don't disable the DACs
DATDLY = 0 normal
DATSWP=0 normal
DCHROMA=0 Don't blank chroma
DIS_FFILT=1 Disable flickerfilter
DIS_GMSHC=1 Disable chroma psuedo-gamma removal
DIS_GMSHY=1 Disable luma pseudo gamma removal
DIS_GMUSHC=1 Disable chroma anti-pseudo gamma removal
DIS_GMUSHY=1 Disable luma anti-pseudo gamma removal
DIS_SCRESET=0 Normal subcarrier phase resets
DIS_YFLPF=0 Disable Luma initial hoz low pass filter
DIV2=0 Input pixel rate not divided by 2
ECBAR=0 No colour bars
ECCF1=0 Disable closed caption
ECCF2=0 Disable closed caption
ECCGATE=0 Normal close caption encoding
ECLIP=0 0=disable clipping
EN_ASYNC=0 set to 0 for normal operation
EN_BLANKO=0 BLANK is an input
EN_DOT=0 Disables dot clock sync on BLANK pin
EN_OUT=1 Allows outputs to be enabled
EN_XCLK=1 Use CLKI pin as clock source
ESTATUS[1:0]=0 Used to select readback register
FIELDI=0 Logical 1 on FIELD indicates even field
F_SELC[2:0]=0 5 line chroma flickerfilter
F_SELY[2:0]=0 5 line luma flickerfilter
HBURST_BEGIN[7:0]=x92 146 Chroma burst start point in clocks
HBURST_END[7:0]=x57 87 Chroma burst end point in clocks - 128
HSYNCI=0 Active low HSYNC
HSYNC_WIDTH[7:0]=x80 128 Analogue sync width in clocks
HSYNOFFSET[9:0]=0 [HSYNOFFSET[9:8]=0; HSYNOFFSET[7:0]=0] hsync in "standard position"
HSYNWIDTH[5:0]=2 2 pixel hsync width
H_ACTIVE[9:0]=x2D0 720 [H_ACTIVE[9:8]=2; H_ACTIVE[7:0]=xD0] Active pixels per line
H_BLANKI[8:0]=x80 128 [H_BLANKI[8]=0; H_BLANKI[7:0]=x80] End of blanking of input video
H_BLANKO[9:0]=x102 258 [H_BLANKO[9:8]=1; H_BLANKO[7:0]=x2] End of blanking from hoz sync leading edge
H_CLKI[10:0]=x368 872 [H_CLKI[10:8]=3; H_CLKI[7:0]=x68] Input line length total in clocks
H_CLKO[11:0]=x6d0 1744 [H_CLKO[11:8]=6; H_CLKO[7:0]=xD0] Output clocks per line
H_FRACT[7:0]=0 0 fractional input clocks per line
IN_MODE[2:0]=0 24Bit RGB muxed
LUMADLY[1:0]=0 0 pixel delay on Y_DLY luma
MCB[7:0]=x43 67 Mult factor for CB prior to subcarrier mod.
MCR[7:0]=x77 119 Mult factor for CR prior to subcarrier mod.
MODE2X=0 Don't divide clock input by 2
MSC[31:0]=x215282E5 559055589 [MSC[31:24]=x21; MSC[23:16]=x52; MSC[15:8]=x82; MSC[7:0]=xE5] Subcarrier incr.
MY[7:0]=x85 133 Mult factor for Y
NI_OUT=0 Normal interlaced output
OUT_MODE[1:0]=0 video0-3 is CVBS, Y, C, Y_DLY
OUT_MUXA[1:0]=0 Don't care as DACA is disabled
OUT_MUXB[1:0]=1 Output video[1] (Y) on DACB
OUT_MUXC[1:0]=2 Output video[2] (C) on DACC
PAL_MD=0 Video output in PAL mode? No.
PHASE_OFF[7:0]=0 Subcarrier phase offset
PLL_FRACT[15:0]=x30 48 [PLL_FRACT[15:8]=0x0; PLL_FRACT[7:0]=x30] frac portion of pll multiplier
PLL_INT[5:0]=0x0C 12 Int portion of pll multiplier
SETUP=1 7.5-IRE enabled for NTSC
SLAVER=1
SRESET=0 Don't do a software reset
SYNC_AMP[7:0]=xE5 229 Sync amp mult. factor (PAL std???)
VBLANKDLY=0 Extra line of blanking in 2nd field?
VSYNCI=0 Active low VSYNC
VSYNC_DUR=1 2.5line VSYNC duration on output (Yes for NTSC)
VSYNCOFFSET[10:0]=0 [VSYNOFFSET[10:8]=0; VSYNOFFSET[7:0]=0] VSYNC in standard position
VSYNWIDTH[2:0]=1 1 line of vsync width
V_ACTIVEI[9:0]=x1E0 480 [V_ACTIVEI[9:0]=1; V_ACTIVEI[7:0]=xE0] Active input lines
V_ACTIVEO[8:0]=xF0 240 [V_ACTIVE0[8]=0; V_ACTIVEO[7:0]=xF0]
V_BLANKI[7:0]=x2A 42 Input lines from vsync to first active line
V_BLANKO[7:0]=x16 22
V_LINESI[9:0]=x20D 525 [V_LINESI[9:8]=2; V_LINESI[7:0]=x0D] Number of input lines
V_SCALE[13:0]=x1000 4096 [V_SCALE[13:8]=x10; V_SCALE[7:0]=0] Vert scale coefficient="none"?
YATTENUATE[2:0]=0 no luma attenuation
YCORING[2:0]=0 Luma-coring bypass
YLPF[1:0]=0 Luma hoz low pass filter=bypass
***************** */
static u16 registers_720_480[] =
{
0x6e, 0x00, /* HSYNOFFSET[7:0]=0 */
0x70, 0x02, /* HSYNOFFSET[9:8]=0; HSYNWIDTH[5:0]=2 */
0x72, 0x00, /* VSYNOFFSET[7:0]=0 */
0x74, 0x01, /* DATDLY = 0; DATSWP=0; VSYNOFFSET[10:8]=0; VSYNWIDTH[2:0]=1 */
0x76, 0xD0, /* H_CLKO[7:0]=xD0 */
0x78, 0xD0, /* H_ACTIVE[7:0]=xD0 */
0x7a, 0x80, /* HSYNC_WIDTH[7:0]=x80 */
0x7c, 0x92, /* HBURST_BEGIN[7:0]=x92 */
0x7e, 0x57, /* HBURST_END[7:0]=x57 */
0x80, 0x02, /* H_BLANKO[7:0]=x2 */
0x82, 0x16, /* V_BLANKO[7:0]=x16 */
0x84, 0xF0, /* V_ACTIVEO[7:0]=xF0 */
0x86, 0x26, /* V_ACTIVE0[8]=0; H_ACTIVE[9:8]=2; H_CLKO[11:8]=6 */
0x88, 0x00, /* H_FRACT[7:0]=0 */
0x8a, 0xD0, /* H_CLKI[7:0]=xD0 */
0x8c, 0x80, /* H_BLANKI[7:0]=x80 */
0x8e, 0x03, /* VBLANKDLY=0; H_BLANKI[8]=0; H_CLKI[10:8]=3 */
0x90, 0x0D, /* V_LINESI[7:0]=x0D */
0x92, 0x2A, /* V_BLANKI[7:0]=x2A */
0x94, 0xE0, /* V_ACTIVEI[7:0]=xE0 */
0x96, 0x06, /* CLPF[1:0]=0; YLPF[1:0]=0; V_ACTIVEI[9:8]=1; V_LINESI[9:8]=2 */
0x98, 0x00, /* V_SCALE[7:0]=0 */
0x9a, 0x50, /* H_BLANKO[9:8]=1; V_SCALE[13:8]=x10 */
0x9c, 0x30, /* PLL_FRACT[7:0]=x30 */
0x9e, 0x0, /* PLL_FRACT[15:8]=0x0 */
0xa0, 0x8c, /* EN_XCLK=1; BY_PLL=0; PLL_INT[5:0]=0x0C */
0xa2, 0x0A, /* ECLIP=0; PAL_MD=0; DIS_SCRESET=0; VSYNC_DUR=1; 625LINE=0; SETUP=1; NI_OUT=0 */
0xa4, 0xE5, /* SYNC_AMP[7:0]=xE5 */
0xa6, 0x74, /* BST_AMP[7:0]=x74 */
0xa8, 0x77, /* MCR[7:0]=x77 */
0xaa, 0x43, /* MCB[7:0]=x43 */
0xac, 0x85, /* MY[7:0]=x85 */
0xae, 0xE5, /* MSC[7:0]=xE5 */
0xb0, 0x82, /* MSC[15:8]=x82 */
0xb2, 0x52, /* MSC[23:16]=x52 */
0xb4, 0x21, /* MSC[31:24]=x21 */
0xb6, 0x00, /* PHASE_OFF[7:0]=0 */
//0xba, 0x21, /* SRESET=0; CHECK_STAT=0; SLAVER=1; DACOFF=0; DACDISC=0; DACDISB=0; DACDISA=1 */
0xc4, 0x01, /* ESTATUS[1:0]=0; ECCF2=0; ECCF1=0; ECCGATE=0; ECBAR=0; DCHROMA=0; EN_OUT=1 */
0xc6, 0x00, /* EN_BLANKO=0; EN_DOT=0; FIELDI=0; VSYNCI=0; HSYNCI=0; IN_MODE[2:0]=0(24bRGB) */
0xc8, 0x40, /* DIS_YFLPF=0; DIS_FFILT=1; F_SELC[2:0]=0; F_SELY[2:0]=0 */
0xca, 0xc0, /* DIS_GMUSHY=1; DIS_GMSHY=1; YCORING[2:0]=0; YATTENUATE[2:0]=0 */
0xcc, 0xc0, /* DIS_GMUSHC=1; DIS_GMSHC=1; CCORING[2:0]=0; CATTENUATE[2:0]=0 */
//0xce, 0x24, /* OUT_MUXC=2 [C]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/
//0xce, 0x04, /* OUT_MUXC=0 [CVBS]; OUT_MUXB=1 [Y]; OUT_MUXA=0 [CVBS, but disabled]*/
0xd6, 0x00, /* OUT_MODE[1:0]=0; LUMADLY[1:0]=0 */
0, 0
};
static int bt869_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, bt869_detect);
}
/* This function is called by i2c_detect */
int bt869_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i, cur;
struct i2c_client *new_client;
struct bt869_data *data;
int err = 0;
const char *type_name, *client_name;
printk("bt869.o: probing address %d .\n", address);
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("bt869.o: bt869_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE |
I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access bt869_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct bt869_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &bt869_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. It is lousy. */
i2c_smbus_write_byte_data(new_client, 0xC4, 0); /* set status bank 0 */
cur = i2c_smbus_read_byte(new_client);
printk("bt869.o: address 0x%X testing-->0x%X\n", address, cur);
if ((cur & 0xE0) != 0x20)
goto ERROR1;
/* Determine the chip type */
kind = ((cur & 0x20) >> 5);
if (kind) {
type_name = "bt869";
client_name = "bt869 chip";
printk("bt869.o: BT869 detected\n");
} else {
type_name = "bt868";
client_name = "bt868 chip";
printk("bt869.o: BT868 detected\n");
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
bt869_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
bt869_init_client((struct i2c_client *) new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int bt869_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct bt869_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("bt869.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* All registers are byte-sized.
bt869 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int bt869_read_value(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte(client);
}
/* All registers are byte-sized.
bt869 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int bt869_write_value(struct i2c_client *client, u8 reg, u16 value)
{
#ifdef DEBUG
printk("bt869.o: write_value(0x%X, 0x%X)\n", reg, value);
#endif
return i2c_smbus_write_byte_data(client, reg, value);
}
static void bt869_write_values(struct i2c_client *client, u16 *values)
{
/* writes set of registers from array. 0,0 marks end of table */
while (*values) {
bt869_write_value(client, values[0], values[1]);
values += 2;
}
}
static void bt869_init_client(struct i2c_client *client)
{
struct bt869_data *data = client->data;
/* Initialize the bt869 chip */
bt869_write_value(client, 0x0ba, 0x80);
// bt869_write_value(client,0x0D6, 0x00);
/* Be a slave to the clock on the Voodoo3 */
bt869_write_value(client, 0xa0, 0x80);
bt869_write_value(client, 0xba, 0x20);
/* depth =16bpp */
bt869_write_value(client, 0x0C6, 0x001);
bt869_write_value(client, 0xC4, 1);
/* Flicker free enable and config */
bt869_write_value(client, 0xC8, 0);
data->res[0] = 640;
data->res[1] = 480;
data->ntsc = 1;
data->half = 0;
data->colorbars = 0;
data->svideo = 0;
data->depth = 16;
}
static void bt869_update_client(struct i2c_client *client)
{
struct bt869_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting bt869 update\n");
#endif
if ((data->res[0] == 800) && (data->res[1] == 600)) {
/* 800x600 built-in mode */
bt869_write_value(client, 0xB8,
(2 + (!data->ntsc)));
bt869_write_value(client, 0xa0, 0x80 + 0x11);
printk("bt869.o: writing into config -->0x%X\n",
(2 + (!data->ntsc)));
}
else if ((data->res[0] == 720) && (data->res[1] == 576)) {
/* 720x576 no-overscan-compensation mode suitable for PAL DVD playback */
data->ntsc = 0; /* This mode always PAL */
bt869_write_values(client, registers_720_576);
}
else if ((data->res[0] == 720) && (data->res[1] == 480)) {
/* 720x480 no-overscan-compensation mode suitable for NTSC DVD playback */
data->ntsc = 1; /* This mode always NTSC */
bt869_write_values(client, registers_720_480);
}
else {
/* 640x480 built-in mode */
bt869_write_value(client, 0xB8, (!data->ntsc));
bt869_write_value(client, 0xa0, 0x80 + 0x0C);
printk("bt869.o: writing into config -->0x%X\n",
(0 + (!data->ntsc)));
if ((data->res[0] != 640) || (data->res[1] != 480)) {
printk
("bt869.o: Warning: arbitrary resolutions not supported yet. Using 640x480.\n");
data->res[0] = 640;
data->res[1] = 480;
}
}
/* Set colour depth */
if ((data->depth != 24) && (data->depth != 16))
data->depth = 16;
if (data->depth == 16)
bt869_write_value(client, 0x0C6, 0x001);
if (data->depth == 24)
bt869_write_value(client, 0x0C6, 0x000);
/* set "half" resolution mode */
bt869_write_value(client, 0xd4, data->half << 6);
/* Set composite/svideo mode, also enable the right dacs */
switch (data->svideo) {
case 2: /* RGB */
/* requires hardware mod on Voodoo3 to get all outputs,
untested in practice... Feedback to steve@daviesfam.org please */
bt869_write_value(client, 0xd6, 0x0c);
bt869_write_value(client, 0xce, 0x24);
bt869_write_value(client, 0xba, 0x20);
break;
case 1: /* Svideo*/
bt869_write_value(client, 0xce, 0x24);
bt869_write_value(client, 0xba, 0x21);
break;
default: /* Composite */
bt869_write_value(client, 0xce, 0x0);
bt869_write_value(client, 0xba, 0x21);
break;
}
/* Enable outputs */
bt869_write_value(client, 0xC4, 1);
/* Issue timing reset */
bt869_write_value(client, 0x6c, 0x80);
/* Read back status registers */
bt869_write_value(client, 0xC4,
1 | (data->colorbars << 2));
data->status[0] = bt869_read_value(client, 1);
bt869_write_value(client, 0xC4,
0x41 | (data->colorbars << 2));
data->status[1] = bt869_read_value(client, 1);
bt869_write_value(client, 0xC4,
0x81 | (data->colorbars << 2));
data->status[2] = bt869_read_value(client, 1);
bt869_write_value(client, 0xC4,
0x0C1 | (data->colorbars << 2));
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
void bt869_status(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->status[0];
results[1] = data->status[1];
results[2] = data->status[2];
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
printk
("bt869.o: Warning: write was requested on read-only proc file: status\n");
}
}
void bt869_ntsc(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->ntsc;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->ntsc = (results[0] > 0);
}
bt869_update_client(client);
}
}
void bt869_svideo(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->svideo;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->svideo = results[0];
}
bt869_update_client(client);
}
}
void bt869_res(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->res[0];
results[1] = data->res[1];
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->res[0] = results[0];
}
if (*nrels_mag >= 2) {
data->res[1] = results[1];
}
bt869_update_client(client);
}
}
void bt869_half(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->half;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->half = (results[0] > 0);
bt869_update_client(client);
}
}
}
void bt869_colorbars(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->colorbars;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->colorbars = (results[0] > 0);
bt869_update_client(client);
}
}
}
void bt869_depth(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct bt869_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
bt869_update_client(client);
results[0] = data->depth;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->depth = results[0];
bt869_update_client(client);
}
}
}
static int __init sm_bt869_init(void)
{
printk("bt869.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&bt869_driver);
}
static void __exit sm_bt869_exit(void)
{
i2c_del_driver(&bt869_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, Stephen Davies <steve@daviesfam.org>");
MODULE_DESCRIPTION("bt869 driver");
module_init(sm_bt869_init);
module_exit(sm_bt869_exit);
-586
View File
@@ -1,586 +0,0 @@
/*
ddcmon.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999, 2000 Frodo Looijaard <frodol@dds.nl>,
Philip Edelbrock <phil@netroedge.com>,
and Mark Studebaker <mdsxyz123@yahoo.com>
Copyright (c) 2003 Jean Delvare <khali@linux-fr.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.
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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x50, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(ddcmon);
static int checksum = 0;
MODULE_PARM(checksum, "i");
MODULE_PARM_DESC(checksum, "Only accept eeproms whose checksum is correct");
/* Many constants specified below */
/* DDCMON registers */
/* vendor section */
#define DDCMON_REG_MAN_ID 0x08
#define DDCMON_REG_PROD_ID 0x0A
#define DDCMON_REG_SERIAL 0x0C
#define DDCMON_REG_WEEK 0x10
#define DDCMON_REG_YEAR 0x11
/* EDID version */
#define DDCMON_REG_EDID_VER 0x12
#define DDCMON_REG_EDID_REV 0x13
/* display information */
#define DDCMON_REG_HORSIZE 0x15
#define DDCMON_REG_VERSIZE 0x16
#define DDCMON_REG_GAMMA 0x17
#define DDCMON_REG_DPMS_FLAGS 0x18
/* supported timings */
#define DDCMON_REG_ESTABLISHED_TIMINGS 0x23
#define DDCMON_REG_STANDARD_TIMINGS 0x26
#define DDCMON_REG_TIMBASE 0x36
#define DDCMON_REG_TIMINCR 18
#define DDCMON_REG_TIMNUM 4
#define DDCMON_REG_CHECKSUM 0x7f
/* Size of DDCMON in bytes */
#define DDCMON_SIZE 128
/* Each client has this additional data */
struct ddcmon_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 data[DDCMON_SIZE]; /* Register values */
};
static int ddcmon_attach_adapter(struct i2c_adapter *adapter);
static int ddcmon_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int ddcmon_detach_client(struct i2c_client *client);
static void ddcmon_idcall(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_size(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_sync(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_maxclock(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_timings(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_serial(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_time(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_edid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_gamma(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_dpms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_standard_timing(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ddcmon_update_client(struct i2c_client *client);
/* This is the driver that will be inserted */
static struct i2c_driver ddcmon_driver = {
.name = "DDCMON READER",
.id = I2C_DRIVERID_DDCMON,
.flags = I2C_DF_NOTIFY,
.attach_adapter = ddcmon_attach_adapter,
.detach_client = ddcmon_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define DDCMON_SYSCTL_ID 1010
#define DDCMON_SYSCTL_SIZE 1011
#define DDCMON_SYSCTL_SYNC 1012
#define DDCMON_SYSCTL_TIMINGS 1013
#define DDCMON_SYSCTL_SERIAL 1014
#define DDCMON_SYSCTL_TIME 1015
#define DDCMON_SYSCTL_EDID 1016
#define DDCMON_SYSCTL_GAMMA 1017
#define DDCMON_SYSCTL_DPMS 1018
#define DDCMON_SYSCTL_TIMING1 1021
#define DDCMON_SYSCTL_TIMING2 1022
#define DDCMON_SYSCTL_TIMING3 1023
#define DDCMON_SYSCTL_TIMING4 1024
#define DDCMON_SYSCTL_TIMING5 1025
#define DDCMON_SYSCTL_TIMING6 1026
#define DDCMON_SYSCTL_TIMING7 1027
#define DDCMON_SYSCTL_TIMING8 1028
#define DDCMON_SYSCTL_MAXCLOCK 1029
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected DDCMON. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table ddcmon_dir_table_template[] = {
{DDCMON_SYSCTL_ID, "id", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &ddcmon_idcall},
{DDCMON_SYSCTL_SIZE, "size", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &ddcmon_size},
{DDCMON_SYSCTL_SYNC, "sync", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_sync},
{DDCMON_SYSCTL_TIMINGS, "timings", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_timings},
{DDCMON_SYSCTL_SERIAL, "serial", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_serial},
{DDCMON_SYSCTL_TIME, "time", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_time},
{DDCMON_SYSCTL_EDID, "edid", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_edid},
{DDCMON_SYSCTL_GAMMA, "gamma", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_gamma},
{DDCMON_SYSCTL_DPMS, "dpms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_dpms},
{DDCMON_SYSCTL_TIMING1, "timing1", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING2, "timing2", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING3, "timing3", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING4, "timing4", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING5, "timing5", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING6, "timing6", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING7, "timing7", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_TIMING8, "timing8", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_standard_timing},
{DDCMON_SYSCTL_MAXCLOCK, "maxclock", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ddcmon_maxclock},
{0}
};
static int ddcmon_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, ddcmon_detect);
}
/* This function is called by i2c_detect */
int ddcmon_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i, cs;
struct i2c_client *new_client;
struct ddcmon_data *data;
int err = 0;
const char *type_name, *client_name;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access ddcmon_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct ddcmon_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
memset(data->data, 0xff, DDCMON_SIZE);
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &ddcmon_driver;
new_client->flags = 0;
/* prevent 24RF08 corruption (just in case) */
i2c_smbus_write_quick(new_client, 0);
/* Now, we do the remaining detection. */
if (checksum) {
int cs = 0;
for (i = 0; i < 0x80; i++)
cs += i2c_smbus_read_byte_data(new_client, i);
if ((cs & 0xff) != 0)
goto ERROR1;
}
/* Verify the first 8 locations 0x00FFFFFFFFFFFF00 */
/* Allow force and force_ddcmon arguments */
if(kind < 0)
{
for(i = 0; i < 8; i++) {
cs = i2c_smbus_read_byte_data(new_client, i);
if(i == 0 || i == 7) {
if(cs != 0)
goto ERROR1;
} else if(cs != 0xff)
goto ERROR1;
}
}
type_name = "ddcmon";
client_name = "DDC Monitor";
/* Fill in the remaining client fields and put it in the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
ddcmon_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
return 0;
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int ddcmon_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct ddcmon_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("ddcmon.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static void ddcmon_update_client(struct i2c_client *client)
{
struct ddcmon_data *data = client->data;
int i, j;
down(&data->update_lock);
if ((jiffies - data->last_updated > 300 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_I2C_BLOCK))
{
for (i=0; i<DDCMON_SIZE; i+=I2C_SMBUS_I2C_BLOCK_MAX)
if (i2c_smbus_read_i2c_block_data(client,
i, data->data + i)
!= I2C_SMBUS_I2C_BLOCK_MAX) {
printk(KERN_WARNING "ddcmon.o: block read fail at 0x%.2x!\n", i);
goto DONE;
}
} else {
if (i2c_smbus_write_byte(client, 0)) {
printk(KERN_WARNING "ddcmon.o: read start fail at 0!\n");
goto DONE;
}
for (i = 0; i < DDCMON_SIZE; i++) {
j = i2c_smbus_read_byte(client);
if (j < 0) {
printk(KERN_WARNING "eeprom.o: read fail at 0x%.2x!\n", i);
goto DONE;
}
data->data[i] = (u8) j;
}
}
data->last_updated = jiffies;
data->valid = 1;
}
DONE:
up(&data->update_lock);
}
void ddcmon_idcall(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_MAN_ID + 1] |
(data->data[DDCMON_REG_MAN_ID] << 8);
results[1] = data->data[DDCMON_REG_PROD_ID + 1] |
(data->data[DDCMON_REG_PROD_ID] << 8);
*nrels_mag = 2;
}
}
void ddcmon_size(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_VERSIZE];
results[1] = data->data[DDCMON_REG_HORSIZE];
*nrels_mag = 2;
}
}
void ddcmon_sync(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
int i, j;
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
*nrels_mag = 4;
/* look for monitor limits entry */
for(i = DDCMON_REG_TIMBASE;
i < DDCMON_REG_TIMBASE +
(DDCMON_REG_TIMNUM * DDCMON_REG_TIMINCR);
i += DDCMON_REG_TIMINCR) {
if (data->data[i] == 0x00
&& data->data[i + 1] == 0x00
&& data->data[i + 2] == 0x00
&& data->data[i + 3] == 0xfd) {
for(j = 0; j < 4; j++)
results[j] = data->data[i + j + 5];
return;
}
}
for(j = 0; j < 4; j++)
results[j] = 0;
}
}
void ddcmon_maxclock(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
int i;
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
*nrels_mag = 1;
/* look for monitor limits entry */
for(i = DDCMON_REG_TIMBASE;
i < DDCMON_REG_TIMBASE +
(DDCMON_REG_TIMNUM * DDCMON_REG_TIMINCR);
i += DDCMON_REG_TIMINCR) {
if (data->data[i] == 0x00
&& data->data[i + 1] == 0x00
&& data->data[i + 2] == 0x00
&& data->data[i + 3] == 0xfd) {
results[0] = (data->data[i + 9] == 0xff ?
0 : data->data[i + 9] * 10);
return;
}
}
results[0] = 0;
}
}
void ddcmon_timings(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_ESTABLISHED_TIMINGS] |
(data->data[DDCMON_REG_ESTABLISHED_TIMINGS + 1] << 8) |
(data->data[DDCMON_REG_ESTABLISHED_TIMINGS + 2] << 16);
*nrels_mag = 1;
}
}
void ddcmon_serial(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_SERIAL] |
(data->data[DDCMON_REG_SERIAL + 1] << 8) |
(data->data[DDCMON_REG_SERIAL + 2] << 16) |
(data->data[DDCMON_REG_SERIAL + 3] << 24);
*nrels_mag = 1;
}
}
void ddcmon_time(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_YEAR] + 1990;
results[1] = data->data[DDCMON_REG_WEEK];
*nrels_mag = 2;
}
}
void ddcmon_edid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_EDID_VER];
results[1] = data->data[DDCMON_REG_EDID_REV];
*nrels_mag = 2;
}
}
void ddcmon_gamma(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = 100 + data->data[DDCMON_REG_GAMMA];
*nrels_mag = 1;
}
}
void ddcmon_dpms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
results[0] = data->data[DDCMON_REG_DPMS_FLAGS];
*nrels_mag = 1;
}
}
void ddcmon_standard_timing(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct ddcmon_data *data = client->data;
int nr = ctl_name - DDCMON_SYSCTL_TIMING1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ddcmon_update_client(client);
/* If both bytes of the timing are 0x00 or 0x01, then the timing
slot is unused. */
if ((data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2]
| data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1]) & 0xfe) {
results[0] = (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2] + 31) * 8;
switch (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1] >> 6) {
/* We don't care about rounding issues there, it really
should be OK without it. */
case 0x00:
results[1] = results[0]; /* unconfirmed */
break;
case 0x01:
results[1] = results[0] * 3 / 4;
break;
case 0x02:
results[1] = results[0] * 4 / 5;
break;
case 0x03:
results[1] = results[0] * 9 / 16;
break;
}
results[2] = (data->data[DDCMON_REG_STANDARD_TIMINGS + nr * 2 + 1] & 0x3f) + 60;
} else {
results[0] = 0;
results[1] = 0;
results[2] = 0;
}
*nrels_mag = 3;
}
}
static int __init sm_ddcmon_init(void)
{
printk("ddcmon.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&ddcmon_driver);
}
static void __exit sm_ddcmon_exit(void)
{
i2c_del_driver(&ddcmon_driver);
}
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>, "
"Philip Edelbrock <phil@netroedge.com>, "
"Mark Studebaker <mdsxyz123@yahoo.com> "
"and Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("DDCMON driver");
module_init(sm_ddcmon_init);
module_exit(sm_ddcmon_exit);
-345
View File
@@ -1,345 +0,0 @@
/*
* linux/drivers/i2c/ds1307.c
*
* Author: Abraham van der Merwe <abraham@2d3d.co.za>
*
* Linux support for the Dallas Semiconductor DS1307 Serial Real-Time
* Clock.
*
* Based on code from the lm-sensors project which is available
* at http://www.lm-sensors.org/ and Russell King's PCF8583 Real-Time
* Clock driver (linux/drivers/acorn/char/pcf8583.c).
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <asm/semaphore.h>
#include "version.h"
#include "ds1307.h"
#define BCD_TO_BIN(x) (((x) & 15) + ((x) >> 4) * 10)
#define BIN_TO_BCD(x) ((((x) / 10) << 4) + (x) % 10)
static struct i2c_driver ds1307;
static DECLARE_MUTEX (mutex);
/*
* The DS1307 Real-Time Clock wants the address in a different
* message, so we can't use the normal i2c_master_recv() routine
* for receiving data.
*/
static int ds1307_i2c_recv (struct i2c_client *client,char *buf,char addr,int count)
{
struct i2c_msg msg[] = {
{ addr: client->addr, flags: 0, len: 1, buf: &addr },
{ addr: client->addr, flags: I2C_M_RD, len: count, buf: buf }
};
int result = 0;
if (down_interruptible (&mutex))
return (-ERESTARTSYS);
if (i2c_transfer (client->adapter,msg,2) != 2)
result = -EIO;
up (&mutex);
return (result);
}
/*
* Would've been nice to specify the address to this as well, but then we
* would need to copy the buffer twice - not worth it...
*/
static int ds1307_i2c_send (struct i2c_client *client,const char *buf,int count)
{
int result = 0;
if (down_interruptible (&mutex))
return (-ERESTARTSYS);
if (i2c_master_send (client,(const char *) buf,count) != count)
result = -EIO;
up (&mutex);
return (result);
}
static int ds1307_attach (struct i2c_adapter *adapter,int addr,unsigned short flags,int kind)
{
struct i2c_client *client;
int result;
if ((client = (struct i2c_client *) kmalloc (sizeof (struct i2c_client),GFP_KERNEL)) == NULL)
return (-ENOMEM);
strcpy (client->name,ds1307.name);
client->flags = I2C_CLIENT_ALLOW_USE | I2C_CLIENT_ALLOW_MULTIPLE_USE;
client->addr = addr;
client->adapter = adapter;
client->driver = &ds1307;
client->data = NULL;
if ((result = i2c_attach_client (client))) {
kfree (client);
return (result);
}
return (0);
}
static int ds1307_attach_adapter (struct i2c_adapter *adapter)
{
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short addr[] = { 0x68, I2C_CLIENT_END };
static struct i2c_client_address_data ds1307_addr_data = {
normal_i2c: addr,
normal_i2c_range: ignore,
probe: ignore,
probe_range: ignore,
ignore: ignore,
ignore_range: ignore,
force: ignore
};
return (i2c_probe (adapter,&ds1307_addr_data,ds1307_attach));
}
static int ds1307_detach_client (struct i2c_client *client)
{
int result;
if ((result = i2c_detach_client (client)))
return (result);
kfree (client);
return (0);
}
static int ds1307_getdate (struct i2c_client *client,void *arg)
{
struct ds1307_date *date = (struct ds1307_date *) arg;
u8 buf[7];
/* this also enables the oscillator */
memset (buf,0,7);
/* enable 24-hour mode */
buf[2] = 0x40;
if (ds1307_i2c_recv (client,(char *) buf,0,7) < 0)
return (-EIO);
date->tm_sec = BCD_TO_BIN (buf[0] & ~0x80);
date->tm_min = BCD_TO_BIN (buf[1]);
date->tm_hour = BCD_TO_BIN (buf[2] & 0x3f);
date->tm_wday = BCD_TO_BIN (buf[3]) - 1;
date->tm_mday = BCD_TO_BIN (buf[4]);
date->tm_mon = BCD_TO_BIN (buf[5]) - 1;
date->tm_year = BCD_TO_BIN (buf[6]) + 100;
return (0);
}
static int ds1307_setdate (struct i2c_client *client,void *arg)
{
struct ds1307_date *date = (struct ds1307_date *) arg;
u8 buf[8];
/* select address 0 */
buf[0] = 0;
buf[1] = BIN_TO_BCD (date->tm_sec);
buf[2] = BIN_TO_BCD (date->tm_min);
buf[3] = BIN_TO_BCD (date->tm_hour) | 0x40;
buf[4] = BIN_TO_BCD (date->tm_wday + 1);
buf[5] = BIN_TO_BCD (date->tm_mday);
buf[6] = BIN_TO_BCD (date->tm_mon + 1);
buf[7] = BIN_TO_BCD (date->tm_year - 100);
return (ds1307_i2c_send (client,(const char *) buf,8));
}
static int ds1307_enable (struct i2c_client *client,void *arg)
{
u8 buf[2];
if (ds1307_i2c_recv (client,(char *) buf + 1,0,1) < 0)
return (-EIO);
if ((buf[1] & 0x80)) {
buf[0] = 0, buf[1] &= ~0x80;
return (ds1307_i2c_send (client,(const char *) buf,2));
}
return (0);
}
static int ds1307_irqon (struct i2c_client *client,void *arg)
{
u8 buf[2];
if (ds1307_i2c_recv (client,(char *) buf + 1,7,1) < 0)
return (-EIO);
buf[0] = 7;
buf[1] |= 0x10;
return (ds1307_i2c_send (client,(const char *) buf,2));
}
static int ds1307_irqoff (struct i2c_client *client,void *arg)
{
u8 buf[2];
if (ds1307_i2c_recv (client,(char *) buf + 1,7,1) < 0)
return (-EIO);
buf[0] = 7;
buf[1] &= ~0x10;
return (ds1307_i2c_send (client,(const char *) buf,2));
}
static int ds1307_getfreq (struct i2c_client *client,void *arg)
{
u16 *freq = (u16 *) arg;
u8 buf;
static const u16 table[] = {
DS1307_FREQ_1HZ,
DS1307_FREQ_4KHZ,
DS1307_FREQ_8KHZ,
DS1307_FREQ_32KHZ
};
if (ds1307_i2c_recv (client,(char *) &buf,7,1) < 0)
return (-EIO);
*freq = table[buf & 3];
return (0);
}
static int ds1307_setfreq (struct i2c_client *client,void *arg)
{
u16 *freq = (u16 *) arg;
u8 buf[2];
/* select address 7 */
buf[0] = 7;
/* default to 1HZ */
buf[1] = 0;
switch (*freq) {
case DS1307_FREQ_32KHZ: buf[1]++;
case DS1307_FREQ_8KHZ: buf[1]++;
case DS1307_FREQ_4KHZ: buf[1]++;
case DS1307_FREQ_1HZ: break;
default:
return (-EINVAL);
}
return (ds1307_i2c_send (client,(const char *) buf,2));
}
static int ds1307_read (struct i2c_client *client,void *arg)
{
struct ds1307_memory *mem = (struct ds1307_memory *) arg;
u8 buf[DS1307_SIZE];
if (mem->offset >= DS1307_SIZE || mem->offset + mem->length > DS1307_SIZE)
return (-EINVAL);
if (ds1307_i2c_recv (client,(char *) buf,mem->offset + 8,mem->length) < 0)
return (-EIO);
memcpy (mem->buf,buf,mem->length);
return (0);
}
static int ds1307_write (struct i2c_client *client,void *arg)
{
struct ds1307_memory *mem = (struct ds1307_memory *) arg;
u8 buf[DS1307_SIZE + 1];
if (mem->offset >= DS1307_SIZE || mem->offset + mem->length > DS1307_SIZE)
return (-EINVAL);
buf[0] = mem->offset + 8;
memcpy (buf + 1,mem->buf,mem->length);
return (ds1307_i2c_send (client,(const char *) buf,mem->length + 1));
}
static int ds1307_command (struct i2c_client *client,unsigned int cmd,void *arg)
{
static const struct {
int cmd;
int (*function) (struct i2c_client *,void *arg);
} ioctl[] = {
{ DS1307_ENABLE, ds1307_enable },
{ DS1307_GET_DATE, ds1307_getdate },
{ DS1307_SET_DATE, ds1307_setdate },
{ DS1307_IRQ_ON, ds1307_irqon },
{ DS1307_IRQ_OFF, ds1307_irqoff },
{ DS1307_GET_FREQ, ds1307_getfreq },
{ DS1307_SET_FREQ, ds1307_setfreq },
{ DS1307_READ, ds1307_read },
{ DS1307_WRITE, ds1307_write }
};
int i;
for (i = 0; i < sizeof (ioctl) / sizeof (ioctl[0]); i++)
if (ioctl[i].cmd == cmd)
return (ioctl[i].function (client,arg));
return (-EINVAL);
}
static struct i2c_driver ds1307 = {
.name = "ds1307",
.id = I2C_DRIVERID_DS1307,
.flags = I2C_DF_NOTIFY,
.attach_adapter = ds1307_attach_adapter,
.detach_client = ds1307_detach_client,
.command = ds1307_command,
};
static int __init sm_ds1307_init(void)
{
printk("ds1307.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&ds1307);
}
static void __exit sm_ds1307_exit(void)
{
i2c_del_driver(&ds1307);
}
MODULE_AUTHOR ("Abraham van der Merwe <abraham@2d3d.co.za>");
MODULE_DESCRIPTION ("Linux support for DS1307 Real-Time Clock");
MODULE_LICENSE ("GPL");
module_init(sm_ds1307_init);
module_exit(sm_ds1307_exit);
-537
View File
@@ -1,537 +0,0 @@
/*
ds1621.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Christian W. Zuckschwerdt <zany@triq.net> 2000-11-23
based on lm75.c by Frodo Looijaard <frodol@dds.nl>
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.
*/
/* Supports DS1621. See doc/chips/ds1621 for details */
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(ds1621);
/* Many DS1621 constants specified below */
/* Config register used for detection */
/* 7 6 5 4 3 2 1 0 */
/* |Done|THF |TLF |NVB | X | X |POL |1SHOT| */
#define DS1621_REG_CONFIG_NVB 0x10
#define DS1621_REG_CONFIG_POLARITY 0x02
#define DS1621_REG_CONFIG_1SHOT 0x01
#define DS1621_REG_CONFIG_DONE 0x80
/* Note: the done bit is always unset if continuous conversion is in progress.
We need to stop the continuous conversion or switch to single shot
before this bit becomes available!
*/
/* The DS1621 registers */
#define DS1621_REG_TEMP 0xAA /* word, RO */
#define DS1621_REG_TEMP_OVER 0xA1 /* word, RW */
#define DS1621_REG_TEMP_HYST 0xA2 /* word, RW -- it's a low temp trigger */
#define DS1621_REG_CONF 0xAC /* byte, RW */
#define DS1621_REG_TEMP_COUNTER 0xA8 /* byte, RO */
#define DS1621_REG_TEMP_SLOPE 0xA9 /* byte, RO */
#define DS1621_COM_START 0xEE /* no data */
#define DS1621_COM_STOP 0x22 /* no data */
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define TEMP_FROM_REG(val) ((((val & 0x7fff) >> 7) * 5) | \
((val & 0x8000)?-256:0))
#define TEMP_TO_REG(val) (SENSORS_LIMIT((val<0 ? (0x200+((val)/5))<<7 : \
(((val) + 2) / 5) << 7),0,0xffff))
#define ALARMS_FROM_REG(val) ((val) & \
(DS1621_ALARM_TEMP_HIGH | DS1621_ALARM_TEMP_LOW))
#define ITEMP_FROM_REG(val) ((((val & 0x7fff) >> 8)) | \
((val & 0x8000)?-256:0))
/* Each client has this additional data */
struct ds1621_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u16 temp, temp_over, temp_hyst; /* Register values, word */
u8 conf; /* Register encoding, combined */
char enable; /* !=0 if we're expected to restart the conversion */
u8 temp_int, temp_counter, temp_slope; /* Register values, byte */
};
static int ds1621_attach_adapter(struct i2c_adapter *adapter);
static int ds1621_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void ds1621_init_client(struct i2c_client *client);
static int ds1621_detach_client(struct i2c_client *client);
static int ds1621_read_value(struct i2c_client *client, u8 reg);
static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value);
static void ds1621_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ds1621_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ds1621_enable(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ds1621_continuous(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ds1621_polarity(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void ds1621_update_client(struct i2c_client *client);
/* This is the driver that will be inserted */
static struct i2c_driver ds1621_driver = {
.name = "DS1621 sensor driver",
.id = I2C_DRIVERID_DS1621,
.flags = I2C_DF_NOTIFY,
.attach_adapter = ds1621_attach_adapter,
.detach_client = ds1621_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define DS1621_SYSCTL_TEMP 1200 /* Degrees Celsius * 10 */
#define DS1621_SYSCTL_ALARMS 2001 /* bitvector */
#define DS1621_ALARM_TEMP_HIGH 0x40
#define DS1621_ALARM_TEMP_LOW 0x20
#define DS1621_SYSCTL_ENABLE 2002
#define DS1621_SYSCTL_CONTINUOUS 2003
#define DS1621_SYSCTL_POLARITY 2004
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected DS1621. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table ds1621_dir_table_template[] = {
{DS1621_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_temp},
{DS1621_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_alarms},
{DS1621_SYSCTL_ENABLE, "enable", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_enable},
{DS1621_SYSCTL_CONTINUOUS, "continuous", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_continuous},
{DS1621_SYSCTL_POLARITY, "polarity", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &ds1621_polarity},
{0}
};
static int ds1621_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, ds1621_detect);
}
/* This function is called by i2c_detect */
static int ds1621_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i, conf, temp;
struct i2c_client *new_client;
struct ds1621_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("ds1621.o: ds1621_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_WRITE_BYTE))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access ds1621_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct ds1621_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &ds1621_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. It is lousy. */
if (kind < 0) {
/* The NVB bit should be low if no EEPROM write has been
requested during the latest 10ms, which is highly
improbable in our case. */
conf = i2c_smbus_read_byte_data(new_client,
DS1621_REG_CONF);
if (conf & DS1621_REG_CONFIG_NVB)
goto ERROR1;
/* The 7 lowest bits of a temperature should always be 0. */
temp = ds1621_read_value(new_client,
DS1621_REG_TEMP);
if (temp & 0x007f)
goto ERROR1;
temp = ds1621_read_value(new_client,
DS1621_REG_TEMP_HYST);
if (temp & 0x007f)
goto ERROR1;
temp = ds1621_read_value(new_client,
DS1621_REG_TEMP_OVER);
if (temp & 0x007f)
goto ERROR1;
}
/* Determine the chip type - only one kind supported! */
if (kind <= 0)
kind = ds1621;
if (kind == ds1621) {
type_name = "ds1621";
client_name = "DS1621 chip";
} else {
#ifdef DEBUG
printk("ds1621.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
ds1621_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
ds1621_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int ds1621_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct ds1621_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("ds1621.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* All registers are word-sized, except for the configuration register.
DS1621 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int ds1621_read_value(struct i2c_client *client, u8 reg)
{
if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER)
|| (reg == DS1621_REG_TEMP_SLOPE))
return i2c_smbus_read_byte_data(client, reg);
else
return swab16(i2c_smbus_read_word_data(client, reg));
}
/* All registers are word-sized, except for the configuration register.
DS1621 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int ds1621_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if ( (reg == DS1621_COM_START) || (reg == DS1621_COM_STOP) )
return i2c_smbus_write_byte(client, reg);
else
if ((reg == DS1621_REG_CONF) || (reg == DS1621_REG_TEMP_COUNTER)
|| (reg == DS1621_REG_TEMP_SLOPE))
return i2c_smbus_write_byte_data(client, reg, value);
else
return i2c_smbus_write_word_data(client, reg, swab16(value));
}
static void ds1621_init_client(struct i2c_client *client)
{
int reg;
reg = ds1621_read_value(client, DS1621_REG_CONF);
/* start the continuous conversion */
if(reg & 0x01)
ds1621_write_value(client, DS1621_REG_CONF, reg & 0xfe);
}
static void ds1621_update_client(struct i2c_client *client)
{
struct ds1621_data *data = client->data;
u8 new_conf;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting ds1621 update\n");
#endif
data->conf = ds1621_read_value(client, DS1621_REG_CONF);
data->temp = ds1621_read_value(client,
DS1621_REG_TEMP);
data->temp_over = ds1621_read_value(client,
DS1621_REG_TEMP_OVER);
data->temp_hyst = ds1621_read_value(client,
DS1621_REG_TEMP_HYST);
/* wait for the DONE bit before reading extended values */
if (data->conf & DS1621_REG_CONFIG_DONE) {
data->temp_counter = ds1621_read_value(client,
DS1621_REG_TEMP_COUNTER);
data->temp_slope = ds1621_read_value(client,
DS1621_REG_TEMP_SLOPE);
data->temp_int = ITEMP_FROM_REG(data->temp);
/* restart the conversion */
if (data->enable)
ds1621_write_value(client, DS1621_COM_START, 0);
}
/* reset alarms if necessary */
new_conf = data->conf;
if (data->temp < data->temp_over)
new_conf &= ~DS1621_ALARM_TEMP_HIGH;
if (data->temp > data->temp_hyst)
new_conf &= ~DS1621_ALARM_TEMP_LOW;
if (data->conf != new_conf)
ds1621_write_value(client, DS1621_REG_CONF,
new_conf);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
void ds1621_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct ds1621_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
if (!(data->conf & DS1621_REG_CONFIG_DONE) ||
(data->temp_counter > data->temp_slope) ||
(data->temp_slope == 0)) {
*nrels_mag = 1;
} else {
*nrels_mag = 2;
}
else if (operation == SENSORS_PROC_REAL_READ) {
ds1621_update_client(client);
/* decide wether to calculate more precise temp */
if (!(data->conf & DS1621_REG_CONFIG_DONE) ||
(data->temp_counter > data->temp_slope) ||
(data->temp_slope == 0)) {
results[0] = TEMP_FROM_REG(data->temp_over);
results[1] = TEMP_FROM_REG(data->temp_hyst);
results[2] = TEMP_FROM_REG(data->temp);
} else {
results[0] = TEMP_FROM_REG(data->temp_over)*10;
results[1] = TEMP_FROM_REG(data->temp_hyst)*10;
results[2] = data->temp_int * 100 - 25 +
((data->temp_slope - data->temp_counter) *
100 / data->temp_slope);
}
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_over = TEMP_TO_REG(results[0]);
ds1621_write_value(client, DS1621_REG_TEMP_OVER,
data->temp_over);
}
if (*nrels_mag >= 2) {
data->temp_hyst = TEMP_TO_REG(results[1]);
ds1621_write_value(client, DS1621_REG_TEMP_HYST,
data->temp_hyst);
}
}
}
void ds1621_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct ds1621_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ds1621_update_client(client);
results[0] = ALARMS_FROM_REG(data->conf);
*nrels_mag = 1;
}
}
void ds1621_enable(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
/* If you really screw up your chip (like I did) this is */
/* sometimes needed to (re)start the continuous conversion */
/* there is no data to read so this might hang your SMBus! */
struct ds1621_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ds1621_update_client(client);
results[0] = !(data->conf & DS1621_REG_CONFIG_DONE);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (results[0]) {
ds1621_write_value(client, DS1621_COM_START, 0);
data->enable=1;
} else {
ds1621_write_value(client, DS1621_COM_STOP, 0);
data->enable=0;
}
} else {
ds1621_write_value(client, DS1621_COM_START, 0);
data->enable=1;
}
}
}
void ds1621_continuous(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct ds1621_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ds1621_update_client(client);
results[0] = !(data->conf & DS1621_REG_CONFIG_1SHOT);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
ds1621_update_client(client);
if (*nrels_mag >= 1) {
if (results[0]) {
ds1621_write_value(client, DS1621_REG_CONF,
data->conf & ~DS1621_REG_CONFIG_1SHOT);
} else {
ds1621_write_value(client, DS1621_REG_CONF,
data->conf | DS1621_REG_CONFIG_1SHOT);
}
} else {
ds1621_write_value(client, DS1621_REG_CONF,
data->conf & ~DS1621_REG_CONFIG_1SHOT);
}
}
}
void ds1621_polarity(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct ds1621_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
ds1621_update_client(client);
results[0] = !(!(data->conf & DS1621_REG_CONFIG_POLARITY));
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
ds1621_update_client(client);
if (*nrels_mag >= 1) {
if (results[0]) {
ds1621_write_value(client, DS1621_REG_CONF,
data->conf | DS1621_REG_CONFIG_POLARITY);
} else {
ds1621_write_value(client, DS1621_REG_CONF,
data->conf & ~DS1621_REG_CONFIG_POLARITY);
}
}
}
}
static int __init sm_ds1621_init(void)
{
printk("ds1621.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&ds1621_driver);
}
static void __exit sm_ds1621_exit(void)
{
i2c_del_driver(&ds1621_driver);
}
MODULE_AUTHOR("Christian W. Zuckschwerdt <zany@triq.net>");
MODULE_DESCRIPTION("DS1621 driver");
module_init(sm_ds1621_init);
module_exit(sm_ds1621_exit);
-370
View File
@@ -1,370 +0,0 @@
/*
eeprom.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl> and
Philip Edelbrock <phil@netroedge.com>
2003-08-18 Jean Delvare <khali@linux-fr.org>
Divide the eeprom in 2-row (arbitrary) slices. This significantly
speeds sensors up, as well as various scripts using the eeprom
module.
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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include <linux/sched.h> /* for capable() */
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x50, 0x57, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(eeprom);
/* possible natures */
#define NATURE_UNKNOWN 0
#define NATURE_VAIO 1
/* Size of EEPROM in bytes */
#define EEPROM_SIZE 256
/* Each client has this additional data */
struct eeprom_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
u8 valid; /* bitfield, bit!=0 if slice is valid */
unsigned long last_updated[8]; /* In jiffies, 8 slices */
u8 data[EEPROM_SIZE]; /* Register values */
u8 nature;
};
static int eeprom_attach_adapter(struct i2c_adapter *adapter);
static int eeprom_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int eeprom_detach_client(struct i2c_client *client);
#if 0
static int eeprom_write_value(struct i2c_client *client, u8 reg,
u8 value);
#endif
static void eeprom_contents(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void eeprom_update_client(struct i2c_client *client, u8 slice);
/* This is the driver that will be inserted */
static struct i2c_driver eeprom_driver = {
.name = "EEPROM READER",
.id = I2C_DRIVERID_EEPROM,
.flags = I2C_DF_NOTIFY,
.attach_adapter = eeprom_attach_adapter,
.detach_client = eeprom_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define EEPROM_SYSCTL1 1000
#define EEPROM_SYSCTL2 1001
#define EEPROM_SYSCTL3 1002
#define EEPROM_SYSCTL4 1003
#define EEPROM_SYSCTL5 1004
#define EEPROM_SYSCTL6 1005
#define EEPROM_SYSCTL7 1006
#define EEPROM_SYSCTL8 1007
#define EEPROM_SYSCTL9 1008
#define EEPROM_SYSCTL10 1009
#define EEPROM_SYSCTL11 1010
#define EEPROM_SYSCTL12 1011
#define EEPROM_SYSCTL13 1012
#define EEPROM_SYSCTL14 1013
#define EEPROM_SYSCTL15 1014
#define EEPROM_SYSCTL16 1015
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected EEPROM. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table eeprom_dir_table_template[] = {
{EEPROM_SYSCTL1, "00", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL2, "10", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL3, "20", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL4, "30", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL5, "40", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL6, "50", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL7, "60", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL8, "70", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL9, "80", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL10, "90", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL11, "a0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL12, "b0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL13, "c0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL14, "d0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL15, "e0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{EEPROM_SYSCTL16, "f0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &eeprom_contents},
{0}
};
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, eeprom_detect);
}
/* This function is called by i2c_detect */
static int eeprom_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct eeprom_data *data;
int err = 0;
const char *type_name;
/* There are three ways we can read the EEPROM data:
(1) I2C block reads (faster, but unsupported by most adapters)
(2) Consecutive byte reads (100% overhead)
(3) Regular byte data reads (200% overhead)
The third method is not implemented by this driver because all
known adapters support at least the second. */
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA
| I2C_FUNC_SMBUS_BYTE))
goto ERROR0;
if (!(data = kmalloc(sizeof(struct eeprom_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
memset(data->data, 0xff, EEPROM_SIZE);
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &eeprom_driver;
new_client->flags = 0;
/* prevent 24RF08 corruption */
i2c_smbus_write_quick(new_client, 0);
type_name = "eeprom";
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, "EEPROM chip");
data->valid = 0;
init_MUTEX(&data->update_lock);
data->nature = NATURE_UNKNOWN;
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Detect the Vaio nature of EEPROMs.
We use the "PCG-" prefix as the signature. */
if (address == 0x57) {
if (i2c_smbus_read_byte_data(new_client, 0x80) == 'P'
&& i2c_smbus_read_byte(new_client) == 'C'
&& i2c_smbus_read_byte(new_client) == 'G'
&& i2c_smbus_read_byte(new_client) == '-') {
printk(KERN_INFO "Vaio EEPROM detected, "
"enabling password protection\n");
data->nature = NATURE_VAIO;
}
}
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
eeprom_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
return 0;
ERROR4:
i2c_detach_client(new_client);
ERROR3:
kfree(data);
ERROR0:
return err;
}
static int eeprom_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct eeprom_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("eeprom.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
#if 0
/* No writes yet (PAE) */
static int eeprom_write_value(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
#endif
static void eeprom_update_client(struct i2c_client *client, u8 slice)
{
struct eeprom_data *data = client->data;
int i, j;
down(&data->update_lock);
if (!(data->valid & (1 << slice))
|| (jiffies - data->last_updated[slice] > 300 * HZ)
|| (jiffies < data->last_updated[slice])) {
#ifdef DEBUG
printk(KERN_DEBUG "eeprom.o: Starting update, slice %u\n", slice);
#endif
if (i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_I2C_BLOCK))
{
for (i = slice << 5; i < (slice + 1) << 5;
i += I2C_SMBUS_I2C_BLOCK_MAX)
if (i2c_smbus_read_i2c_block_data(client,
i, data->data + i)
!= I2C_SMBUS_I2C_BLOCK_MAX) {
printk(KERN_WARNING "eeprom.o: block read fail at 0x%.2x!\n", i);
goto DONE;
}
} else {
if (i2c_smbus_write_byte(client, slice << 5)) {
printk(KERN_WARNING "eeprom.o: read start fail at 0x%.2x!\n", slice << 5);
goto DONE;
}
for (i = slice << 5; i < (slice + 1) << 5; i++) {
j = i2c_smbus_read_byte(client);
if (j < 0) {
printk(KERN_WARNING "eeprom.o: read fail at 0x%.2x!\n", i);
goto DONE;
}
data->data[i] = (u8) j;
}
}
data->last_updated[slice] = jiffies;
data->valid |= (1 << slice);
}
DONE:
up(&data->update_lock);
}
void eeprom_contents(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
int i;
int nr = ctl_name - EEPROM_SYSCTL1;
struct eeprom_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
eeprom_update_client(client, nr >> 1);
/* Hide Vaio security settings to regular users */
if (nr == 0 && data->nature == NATURE_VAIO
&& !capable(CAP_SYS_ADMIN))
for (i = 0; i < 16; i++)
results[i] = 0;
else
for (i = 0; i < 16; i++)
results[i] = data->data[i + nr * 16];
#ifdef DEBUG
printk(KERN_DEBUG "eeprom.o: 0x%X EEPROM contents (row %d):",
client->addr, nr + 1);
if (nr == 0 && data->nature == NATURE_VAIO)
printk(" <hidden for security reasons>\n");
else {
for (i = 0; i < 16; i++)
printk(" 0x%02X", data->data[i + nr * 16]);
printk("\n");
}
#endif
*nrels_mag = 16;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
/* No writes to the EEPROM (yet, anyway) (PAE) */
printk("eeprom.o: No writes to EEPROMs supported!\n");
}
}
static int __init sm_eeprom_init(void)
{
printk("eeprom.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&eeprom_driver);
}
static void __exit sm_eeprom_exit(void)
{
i2c_del_driver(&eeprom_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("EEPROM driver");
module_init(sm_eeprom_init);
module_exit(sm_eeprom_exit);
-757
View File
@@ -1,757 +0,0 @@
/*
* f71805f.c - driver for the Fintek F71805F Super-I/O chip integrated
* hardware monitoring features
* Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
*
* The F71805F is a LPC Super-I/O chip made by Fintek. It integrates
* complete hardware monitoring features: voltage, fan and temperature
* sensors, and manual and automatic fan speed control.
*
* 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/init.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <asm/io.h>
#include "version.h"
/* ISA address is read from Super-I/O configuration space */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { 0, SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
SENSORS_INSMOD_1(f71805f);
#define DRVNAME "f71805f"
/*
* Super-I/O constants and functions
*/
#define F71805F_LD_HWM 0x04
#define SIO_REG_LDSEL 0x07 /* Logical device select */
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
#define SIO_REG_DEVREV 0x22 /* Device revision */
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
#define SIO_FINTEK_ID 0x1934
#define SIO_F71805F_ID 0x0406
static inline int
superio_inb(int base, int reg)
{
outb(reg, base);
return inb(base + 1);
}
static int
superio_inw(int base, int reg)
{
int val;
outb(reg++, base);
val = inb(base + 1) << 8;
outb(reg, base);
val |= inb(base + 1);
return val;
}
static inline void
superio_select(int base, int ld)
{
outb(SIO_REG_LDSEL, base);
outb(ld, base + 1);
}
static inline void
superio_enter(int base)
{
outb(0x87, base);
outb(0x87, base);
}
static inline void
superio_exit(int base)
{
outb(0xaa, base);
}
/*
* ISA constants
*/
#define REGION_LENGTH 8
#define ADDR_REG_OFFSET 5
#define DATA_REG_OFFSET 6
/*
* Registers
*/
/* in nr from 0 to 8 (8-bit values) */
#define F71805F_REG_IN(nr) (0x10 + (nr))
#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr))
#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr))
/* fan nr from 0 to 2 (12-bit values, two registers) */
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr))
/* temp nr from 0 to 2 (8-bit values) */
#define F71805F_REG_TEMP(nr) (0x1B + (nr))
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
#define F71805F_REG_TEMP_HYST(nr) (0x55 + 2 * (nr))
#define F71805F_REG_TEMP_MODE 0x01
#define F71805F_REG_START 0x00
/* status nr from 0 to 2 */
#define F71805F_REG_STATUS(nr) (0x36 + (nr))
/*
* Data structures and manipulation thereof
*/
struct f71805f_data {
struct i2c_client client;
struct semaphore lock;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
unsigned long last_limits; /* In jiffies */
/* Register values */
u8 in[9];
u8 in_high[9];
u8 in_low[9];
u16 fan[3];
u16 fan_low[3];
u8 fan_enabled; /* Read once at init time */
u8 temp[3];
u8 temp_high[3];
u8 temp_hyst[3];
u8 temp_mode;
u8 alarms[3];
};
static inline long in_from_reg(u8 reg)
{
return (reg * 8);
}
/* The 2 least significant bits are not used */
static inline u8 in_to_reg(long val)
{
if (val <= 0)
return 0;
if (val >= 2016)
return 0xfc;
return (((val + 16) / 32) << 2);
}
/* in0 is downscaled by a factor 2 internally */
static inline long in0_from_reg(u8 reg)
{
return (reg * 16);
}
static inline u8 in0_to_reg(long val)
{
if (val <= 0)
return 0;
if (val >= 4032)
return 0xfc;
return (((val + 32) / 64) << 2);
}
/* The 4 most significant bits are not used */
static inline long fan_from_reg(u16 reg)
{
reg &= 0xfff;
if (!reg || reg == 0xfff)
return 0;
return (1500000 / reg);
}
static inline u16 fan_to_reg(long rpm)
{
/* If the low limit is set below what the chip can measure,
store the largest possible 12-bit value in the registers,
so that no alarm will ever trigger. */
if (rpm < 367)
return 0xfff;
return (1500000 / rpm);
}
static inline u8 temp_to_reg(long val)
{
if (val < 0)
val = 0;
else if (val > 0xff)
val = 0xff;
return val;
}
/*
* Driver and client management
*/
static u8 f71805f_read8(struct i2c_client *client, u8 reg)
{
struct f71805f_data *data = client->data;
u8 val;
down(&data->lock);
outb_p(reg, client->addr + ADDR_REG_OFFSET);
val = inb_p(client->addr + DATA_REG_OFFSET);
up(&data->lock);
return val;
}
static void f71805f_write8(struct i2c_client *client, u8 reg, u8 val)
{
struct f71805f_data *data = client->data;
down(&data->lock);
outb_p(reg, client->addr + ADDR_REG_OFFSET);
outb_p(val, client->addr + DATA_REG_OFFSET);
up(&data->lock);
}
/* It is important to read the MSB first, because doing so latches the
value of the LSB, so we are sure both bytes belong to the same value. */
static u16 f71805f_read16(struct i2c_client *client, u8 reg)
{
struct f71805f_data *data = client->data;
u16 val;
down(&data->lock);
outb_p(reg, client->addr + ADDR_REG_OFFSET);
val = inb_p(client->addr + DATA_REG_OFFSET) << 8;
outb_p(++reg, client->addr + ADDR_REG_OFFSET);
val |= inb_p(client->addr + DATA_REG_OFFSET);
up(&data->lock);
return val;
}
static void f71805f_write16(struct i2c_client *client, u8 reg, u16 val)
{
struct f71805f_data *data = client->data;
down(&data->lock);
outb_p(reg, client->addr + ADDR_REG_OFFSET);
outb_p(val >> 8, client->addr + DATA_REG_OFFSET);
outb_p(++reg, client->addr + ADDR_REG_OFFSET);
outb_p(val & 0xff, client->addr + DATA_REG_OFFSET);
up(&data->lock);
}
static struct i2c_driver f71805f_driver;
static ctl_table f71805f_dir_table_template[];
static void f71805f_init_client(struct i2c_client *client)
{
struct f71805f_data *data = client->data;
u8 reg;
int i;
reg = f71805f_read8(client, F71805F_REG_START);
if ((reg & 0x41) != 0x01) {
printk(KERN_DEBUG DRVNAME ": Starting monitoring "
"operations\n");
f71805f_write8(client,
F71805F_REG_START, (reg | 0x01) & ~0x40);
}
/* Fan monitoring can be disabled. If it is, we won't be polling
the register values, and instead set the cache to return 0 RPM. */
for (i = 0; i < 3; i++) {
reg = f71805f_read8(client, F71805F_REG_FAN_CTRL(i));
if (!(reg & 0x80))
data->fan_enabled |= (1 << i);
else
data->fan[i] = data->fan_low[i] = 0xfff;
}
}
static int f71805f_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
struct i2c_client *client;
struct f71805f_data *data;
int err;
if (!request_region(address, REGION_LENGTH, f71805f_driver.name)) {
err = -EBUSY;
goto exit;
}
if (!(data = kmalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
err = -ENOMEM;
goto exit_release;
}
memset(data, 0, sizeof(struct f71805f_data));
/* Fill in the client fields */
client = &data->client;
client->addr = address;
client->data = data;
client->adapter = adapter;
client->driver = &f71805f_driver;
strcpy(client->name, "F71805F chip");
init_MUTEX(&data->lock);
init_MUTEX(&data->update_lock);
/* Tell the i2c core a new client has arrived */
if ((err = i2c_attach_client(client)))
goto exit_free;
/* Initialize the F71805F chip */
f71805f_init_client(client);
/* Register a new directory entry in /proc */
err = i2c_register_entry(client, "f71805f",
f71805f_dir_table_template, THIS_MODULE);
if (err < 0)
goto exit_detach;
data->sysctl_id = err;
return 0;
exit_detach:
i2c_detach_client(client);
exit_free:
kfree(data);
exit_release:
release_region(address, REGION_LENGTH);
exit:
return err;
}
static int f71805f_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, f71805f_detect);
}
static int f71805f_detach_client(struct i2c_client *client)
{
int err;
struct f71805f_data *data = client->data;
i2c_deregister_entry(data->sysctl_id);
if ((err = i2c_detach_client(client))) {
printk(KERN_ERR DRVNAME ": Client deregistration failed, "
"client not detached\n");
return err;
}
release_region(client->addr, REGION_LENGTH);
kfree(client->data);
return 0;
}
static void f71805f_update_client(struct i2c_client *client)
{
struct f71805f_data *data = client->data;
int nr;
down(&data->update_lock);
/* Limit registers cache is refreshed after 60 seconds */
if ((jiffies - data->last_limits > 60 * HZ)
|| (jiffies < data->last_limits)
|| !data->valid) {
for (nr = 0; nr < 9; nr++) {
data->in_high[nr] = f71805f_read8(client,
F71805F_REG_IN_HIGH(nr));
data->in_low[nr] = f71805f_read8(client,
F71805F_REG_IN_LOW(nr));
}
for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr))
data->fan_low[nr] = f71805f_read16(client,
F71805F_REG_FAN_LOW(nr));
}
for (nr = 0; nr < 3; nr++) {
data->temp_high[nr] = f71805f_read8(client,
F71805F_REG_TEMP_HIGH(nr));
data->temp_hyst[nr] = f71805f_read8(client,
F71805F_REG_TEMP_HYST(nr));
}
data->temp_mode = f71805f_read8(client, F71805F_REG_TEMP_MODE);
data->last_limits = jiffies;
}
/* Measurement registers cache is refreshed after 1 second */
if ((jiffies - data->last_updated > HZ)
|| (jiffies < data->last_updated)
|| !data->valid) {
for (nr = 0; nr < 9; nr++) {
data->in[nr] = f71805f_read8(client,
F71805F_REG_IN(nr));
}
for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr))
data->fan[nr] = f71805f_read16(client,
F71805F_REG_FAN(nr));
}
for (nr = 0; nr < 3; nr++) {
data->temp[nr] = f71805f_read8(client,
F71805F_REG_TEMP(nr));
}
for (nr = 0; nr < 3; nr++) {
data->alarms[nr] = f71805f_read8(client,
F71805F_REG_STATUS(nr));
}
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* -- SENSORS SYSCTL START -- */
#define F71805F_SYSCTL_IN0 1000
#define F71805F_SYSCTL_IN1 1001
#define F71805F_SYSCTL_IN2 1002
#define F71805F_SYSCTL_IN3 1003
#define F71805F_SYSCTL_IN4 1004
#define F71805F_SYSCTL_IN5 1005
#define F71805F_SYSCTL_IN6 1006
#define F71805F_SYSCTL_IN7 1007
#define F71805F_SYSCTL_IN8 1008
#define F71805F_SYSCTL_FAN1 1101
#define F71805F_SYSCTL_FAN2 1102
#define F71805F_SYSCTL_FAN3 1103
#define F71805F_SYSCTL_TEMP1 1201
#define F71805F_SYSCTL_TEMP2 1202
#define F71805F_SYSCTL_TEMP3 1203
#define F71805F_SYSCTL_SENSOR1 1211
#define F71805F_SYSCTL_SENSOR2 1212
#define F71805F_SYSCTL_SENSOR3 1213
#define F71805F_SYSCTL_ALARMS_IN 1090
#define F71805F_SYSCTL_ALARMS_FAN 1190
#define F71805F_SYSCTL_ALARMS_TEMP 1290
/* -- SENSORS SYSCTL END -- */
static void f71805f_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
int nr = ctl_name - F71805F_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = in_from_reg(data->in_low[nr]);
results[1] = in_from_reg(data->in_high[nr]);
results[2] = in_from_reg(data->in[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag < 1)
return;
down(&data->update_lock);
data->in_low[nr] = in_to_reg(results[0]);
f71805f_write8(client, F71805F_REG_IN_LOW(nr),
data->in_low[nr]);
if (*nrels_mag >= 2) {
data->in_high[nr] = in_to_reg(results[1]);
f71805f_write8(client, F71805F_REG_IN_HIGH(nr),
data->in_high[nr]);
}
up(&data->update_lock);
}
}
static void f71805f_in0(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = in0_from_reg(data->in_low[0]);
results[1] = in0_from_reg(data->in_high[0]);
results[2] = in0_from_reg(data->in[0]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag < 1)
return;
down(&data->update_lock);
data->in_low[0] = in0_to_reg(results[0]);
f71805f_write8(client, F71805F_REG_IN_LOW(0),
data->in_low[0]);
if (*nrels_mag >= 2) {
data->in_high[0] = in0_to_reg(results[1]);
f71805f_write8(client, F71805F_REG_IN_HIGH(0),
data->in_high[0]);
}
up(&data->update_lock);
}
}
static void f71805f_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
int nr = ctl_name - F71805F_SYSCTL_FAN1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = fan_from_reg(data->fan_low[nr]);
results[1] = fan_from_reg(data->fan[nr]);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag < 1)
return;
down(&data->update_lock);
data->fan_low[nr] = fan_to_reg(results[0]);
f71805f_write16(client, F71805F_REG_FAN_LOW(nr),
data->fan_low[nr]);
up(&data->update_lock);
}
}
static void f71805f_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
int nr = ctl_name - F71805F_SYSCTL_TEMP1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = data->temp_high[nr];
results[1] = data->temp_hyst[nr];
results[2] = data->temp[nr];
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag < 1)
return;
down(&data->update_lock);
data->temp_high[nr] = temp_to_reg(results[0]);
f71805f_write8(client, F71805F_REG_TEMP_HIGH(nr),
data->temp_high[nr]);
if (*nrels_mag >= 2) {
data->temp_hyst[nr] = temp_to_reg(results[1]);
f71805f_write8(client, F71805F_REG_TEMP_HYST(nr),
data->temp_hyst[nr]);
}
up(&data->update_lock);
}
}
static void f71805f_sensor(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
int nr = ctl_name - F71805F_SYSCTL_SENSOR1;
f71805f_update_client(client);
results[0] = (data->temp_mode & (1 << nr)) ? 3 : 4;
*nrels_mag = 1;
}
}
static void f71805f_alarms_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = data->alarms[0]
| ((data->alarms[1] & 0x01) << 8);
*nrels_mag = 1;
}
}
static void f71805f_alarms_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = data->alarms[2] & 0x07;
*nrels_mag = 1;
}
}
static void f71805f_alarms_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct f71805f_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
f71805f_update_client(client);
results[0] = (data->alarms[1] >> 3) & 0x07;
*nrels_mag = 1;
}
}
static ctl_table f71805f_dir_table_template[] = {
{ F71805F_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in0 },
{ F71805F_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_in },
{ F71805F_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan },
{ F71805F_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan },
{ F71805F_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_fan },
{ F71805F_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp },
{ F71805F_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp },
{ F71805F_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_temp },
{ F71805F_SYSCTL_SENSOR1, "sensor1", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor },
{ F71805F_SYSCTL_SENSOR2, "sensor2", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor },
{ F71805F_SYSCTL_SENSOR3, "sensor3", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_sensor },
{ F71805F_SYSCTL_ALARMS_IN, "alarms_in", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_in },
{ F71805F_SYSCTL_ALARMS_FAN, "alarms_fan", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_fan },
{ F71805F_SYSCTL_ALARMS_TEMP, "alarms_temp", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &f71805f_alarms_temp },
{ 0 }
};
static struct i2c_driver f71805f_driver = {
.name = "F71805F sensor driver",
.flags = I2C_DF_NOTIFY,
.attach_adapter = f71805f_attach_adapter,
.detach_client = f71805f_detach_client,
};
static int __init f71805f_find(int sioaddr, unsigned int *address)
{
int err = -ENODEV;
u16 devid;
superio_enter(sioaddr);
devid = superio_inw(sioaddr, SIO_REG_MANID);
if (devid != SIO_FINTEK_ID)
goto exit;
devid = superio_inw(sioaddr, SIO_REG_DEVID);
if (devid != SIO_F71805F_ID) {
printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
"skipping\n");
goto exit;
}
superio_select(sioaddr, F71805F_LD_HWM);
if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) {
printk(KERN_WARNING DRVNAME ": Device not activated, "
"skipping\n");
goto exit;
}
*address = superio_inw(sioaddr, SIO_REG_ADDR);
if (*address == 0) {
printk(KERN_WARNING DRVNAME ": Base address not set, "
"skipping\n");
goto exit;
}
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
err = 0;
printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n",
*address, superio_inb(sioaddr, SIO_REG_DEVREV));
exit:
superio_exit(sioaddr);
return err;
}
static int __init f71805f_init(void)
{
printk("%s: Driver version %s (%s)\n", DRVNAME, LM_VERSION, LM_DATE);
if (f71805f_find(0x2e, &normal_isa[0])
&& f71805f_find(0x4e, &normal_isa[0]))
return -ENODEV;
return i2c_add_driver(&f71805f_driver);
}
static void __exit f71805f_exit(void)
{
i2c_del_driver(&f71805f_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("F71805F hardware monitoring driver");
module_init(f71805f_init);
module_exit(f71805f_exit);
-729
View File
@@ -1,729 +0,0 @@
/*
fscher.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (C) 2003, 2004 Reinhard Nissl <rnissl@gmx.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.
*/
/*
fujitsu siemens hermes chip,
module based on fscpos.c
Copyright (C) 2000 Hermann Jung <hej@odn.de>
Copyright (C) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.com>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
#ifndef I2C_DRIVERID_FSCHER
#define I2C_DRIVERID_FSCHER 1046
#endif
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(fscher);
/* The FSCHER registers */
/* chip identification */
#define FSCHER_REG_IDENT_0 0x00
#define FSCHER_REG_IDENT_1 0x01
#define FSCHER_REG_IDENT_2 0x02
#define FSCHER_REG_REVISION 0x03
/* global control and status */
#define FSCHER_REG_EVENT_STATE 0x04
#define FSCHER_REG_CONTROL 0x05
/* watchdog */
#define FSCHER_REG_WDOG_PRESET 0x28
#define FSCHER_REG_WDOG_STATE 0x23
#define FSCHER_REG_WDOG_CONTROL 0x21
/* fan 0 */
#define FSCHER_REG_FAN0_MIN 0x55
#define FSCHER_REG_FAN0_ACT 0x0e
#define FSCHER_REG_FAN0_STATE 0x0d
#define FSCHER_REG_FAN0_RIPPLE 0x0f
/* fan 1 */
#define FSCHER_REG_FAN1_MIN 0x65
#define FSCHER_REG_FAN1_ACT 0x6b
#define FSCHER_REG_FAN1_STATE 0x62
#define FSCHER_REG_FAN1_RIPPLE 0x6f
/* fan 2 */
#define FSCHER_REG_FAN2_MIN 0xb5
#define FSCHER_REG_FAN2_ACT 0xbb
#define FSCHER_REG_FAN2_STATE 0xb2
#define FSCHER_REG_FAN2_RIPPLE 0xbf
/* voltage supervision */
#define FSCHER_REG_VOLT_12 0x45
#define FSCHER_REG_VOLT_5 0x42
#define FSCHER_REG_VOLT_BATT 0x48
/* temperatures */
/* sensor 0 */
#define FSCHER_REG_TEMP0_ACT 0x64
#define FSCHER_REG_TEMP0_STATE 0x71
/* sensor 1 */
#define FSCHER_REG_TEMP1_ACT 0x32
#define FSCHER_REG_TEMP1_STATE 0x81
/* sensor 2 */
#define FSCHER_REG_TEMP2_ACT 0x35
#define FSCHER_REG_TEMP2_STATE 0x91
/* Initial limits */
/* For each registered FSCHER, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new fscher client is
allocated. */
struct fscher_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 revision; /* revision of chip */
u8 global_event; /* global event status */
u8 global_control; /* global control register */
u8 watchdog[3]; /* watchdog */
u8 volt[3]; /* 12, 5, battery current */
u8 temp_act[3]; /* temperature */
u8 temp_status[3]; /* status of sensor */
u8 fan_act[3]; /* fans revolutions per second */
u8 fan_status[3]; /* fan status */
u8 fan_min[3]; /* fan min value for rps */
u8 fan_ripple[3]; /* divider for rps */
};
static int fscher_attach_adapter(struct i2c_adapter *adapter);
static int fscher_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int fscher_detach_client(struct i2c_client *client);
static int fscher_read_value(struct i2c_client *client, u8 register);
static int fscher_write_value(struct i2c_client *client, u8 register,
u8 value);
static void fscher_update_client(struct i2c_client *client);
static void fscher_init_client(struct i2c_client *client);
static void fscher_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results);
static void fscher_pwm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscher_pwm_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_min);
static void fscher_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscher_fan_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_state, int res_ripple);
static void fscher_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscher_volt(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscher_wdog(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver fscher_driver = {
.name = "FSCHER sensor driver",
.id = I2C_DRIVERID_FSCHER,
.flags = I2C_DF_NOTIFY,
.attach_adapter = fscher_attach_adapter,
.detach_client = fscher_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define FSCHER_SYSCTL_VOLT0 1000 /* 12 volt supply */
#define FSCHER_SYSCTL_VOLT1 1001 /* 5 volt supply */
#define FSCHER_SYSCTL_VOLT2 1002 /* batterie voltage */
#define FSCHER_SYSCTL_FAN0 1101 /* state, ripple, actual value
fan 0 */
#define FSCHER_SYSCTL_FAN1 1102 /* state, ripple, actual value
fan 1 */
#define FSCHER_SYSCTL_FAN2 1103 /* state, ripple, actual value
fan 2 */
#define FSCHER_SYSCTL_TEMP0 1201 /* state and value of sensor 0,
cpu die */
#define FSCHER_SYSCTL_TEMP1 1202 /* state and value of sensor 1,
motherboard */
#define FSCHER_SYSCTL_TEMP2 1203 /* state and value of sensor 2,
chassis */
#define FSCHER_SYSCTL_PWM0 1301 /* min fan 0 */
#define FSCHER_SYSCTL_PWM1 1302 /* min fan 1 */
#define FSCHER_SYSCTL_PWM2 1303 /* min fan 2 */
#define FSCHER_SYSCTL_REV 2000 /* revision */
#define FSCHER_SYSCTL_EVENT 2001 /* global event status */
#define FSCHER_SYSCTL_CONTROL 2002 /* global control byte */
#define FSCHER_SYSCTL_WDOG 2003 /* watch dog preset, state and
control */
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected FSCHER. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table fscher_dir_table_template[] = {
{FSCHER_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_in},
{FSCHER_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_in},
{FSCHER_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_in},
{FSCHER_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_temp},
{FSCHER_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_temp},
{FSCHER_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_temp},
{FSCHER_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_volt},
{FSCHER_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_volt},
{FSCHER_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_volt},
{FSCHER_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_fan},
{FSCHER_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_fan},
{FSCHER_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_fan},
{FSCHER_SYSCTL_PWM0, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_pwm},
{FSCHER_SYSCTL_PWM1, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_pwm},
{FSCHER_SYSCTL_PWM2, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_pwm},
{FSCHER_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscher_wdog},
{0}
};
static int fscher_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, fscher_detect);
}
int fscher_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct fscher_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety
check at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk("fscher.o: fscher_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access fscher_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct fscher_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &fscher_driver;
new_client->flags = 0;
/* Do the remaining detection unless force or force_fscher parameter */
if (kind < 0) {
if (fscher_read_value(new_client, FSCHER_REG_IDENT_0) != 0x48) /* 'H' */
goto ERROR1;
if (fscher_read_value(new_client, FSCHER_REG_IDENT_1) != 0x45) /* 'E' */
goto ERROR1;
if (fscher_read_value(new_client, FSCHER_REG_IDENT_2) != 0x52) /* 'R' */
goto ERROR1;
}
kind = fscher;
type_name = "fscher";
client_name = "fsc hermes chip";
/* Fill in the remaining client fields and put it into the
global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
fscher_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
fscher_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int fscher_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct fscher_data *) (client->data))->sysctl_id);
if ((err = i2c_detach_client(client))) {
printk("fscher.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static int fscher_read_value(struct i2c_client *client, u8 reg)
{
#ifdef DEBUG
printk("fscher: read reg 0x%02x\n",reg);
#endif
return i2c_smbus_read_byte_data(client, reg);
}
static int fscher_write_value(struct i2c_client *client, u8 reg, u8 value)
{
#ifdef DEBUG
printk("fscher: write reg 0x%02x, val 0x%02x\n",reg, value);
#endif
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new FSCHER. */
static void fscher_init_client(struct i2c_client *client)
{
struct fscher_data *data = client->data;
/* read revision from chip */
data->revision = fscher_read_value(client,FSCHER_REG_REVISION);
}
static void fscher_update_client(struct i2c_client *client)
{
struct fscher_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > 2 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting fscher update\n");
#endif
data->temp_act[0] = fscher_read_value(client, FSCHER_REG_TEMP0_ACT);
data->temp_act[1] = fscher_read_value(client, FSCHER_REG_TEMP1_ACT);
data->temp_act[2] = fscher_read_value(client, FSCHER_REG_TEMP2_ACT);
data->temp_status[0] = fscher_read_value(client, FSCHER_REG_TEMP0_STATE);
data->temp_status[1] = fscher_read_value(client, FSCHER_REG_TEMP1_STATE);
data->temp_status[2] = fscher_read_value(client, FSCHER_REG_TEMP2_STATE);
data->volt[0] = fscher_read_value(client, FSCHER_REG_VOLT_12);
data->volt[1] = fscher_read_value(client, FSCHER_REG_VOLT_5);
data->volt[2] = fscher_read_value(client, FSCHER_REG_VOLT_BATT);
data->fan_act[0] = fscher_read_value(client, FSCHER_REG_FAN0_ACT);
data->fan_act[1] = fscher_read_value(client, FSCHER_REG_FAN1_ACT);
data->fan_act[2] = fscher_read_value(client, FSCHER_REG_FAN2_ACT);
data->fan_status[0] = fscher_read_value(client, FSCHER_REG_FAN0_STATE);
data->fan_status[1] = fscher_read_value(client, FSCHER_REG_FAN1_STATE);
data->fan_status[2] = fscher_read_value(client, FSCHER_REG_FAN2_STATE);
data->fan_min[0] = fscher_read_value(client, FSCHER_REG_FAN0_MIN);
data->fan_min[1] = fscher_read_value(client, FSCHER_REG_FAN1_MIN);
data->fan_min[2] = fscher_read_value(client, FSCHER_REG_FAN2_MIN);
data->fan_ripple[0] = fscher_read_value(client, FSCHER_REG_FAN0_RIPPLE);
data->fan_ripple[1] = fscher_read_value(client, FSCHER_REG_FAN1_RIPPLE);
data->fan_ripple[2] = fscher_read_value(client, FSCHER_REG_FAN2_RIPPLE);
data->watchdog[0] = fscher_read_value(client, FSCHER_REG_WDOG_PRESET);
data->watchdog[1] = fscher_read_value(client, FSCHER_REG_WDOG_STATE);
data->watchdog[2] = fscher_read_value(client, FSCHER_REG_WDOG_CONTROL);
data->global_event = fscher_read_value(client, FSCHER_REG_EVENT_STATE);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void fscher_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
switch(ctl_name) {
case FSCHER_SYSCTL_REV:
results[0] = data->revision ;
break;
case FSCHER_SYSCTL_EVENT:
/* bits 6, 5 and 2 are reserved => mask with 0x9b */
results[0] = data->global_event & 0x9b;
break;
case FSCHER_SYSCTL_CONTROL:
results[0] = data->global_control & 0x01;
break;
default:
printk("fscher: ctl_name %d not supported\n", ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if((ctl_name == FSCHER_SYSCTL_CONTROL) && (*nrels_mag >= 1)) {
data->global_control = (results[0] & 0x01);
printk("fscher: writing 0x%02x to global_control\n",
data->global_control);
fscher_write_value(client,FSCHER_REG_CONTROL,
data->global_control);
}
else
printk("fscher: writing to chip not supported\n");
}
}
#define TEMP_FROM_REG(val) (val-128)
void fscher_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
switch(ctl_name) {
case FSCHER_SYSCTL_TEMP0:
results[0] = data->temp_status[0] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[0]);
break;
case FSCHER_SYSCTL_TEMP1:
results[0] = data->temp_status[1] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[1]);
break;
case FSCHER_SYSCTL_TEMP2:
results[0] = data->temp_status[2] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[2]);
break;
default:
printk("fscher: ctl_name %d not supported\n", ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
switch(ctl_name) {
case FSCHER_SYSCTL_TEMP0:
data->temp_status[0] = (data->temp_status[0] & ~0x02)
| (results[0] & 0x02);
printk("fscher: writing value 0x%02x to temp0_status\n",
data->temp_status[0]);
fscher_write_value(client, FSCHER_REG_TEMP0_STATE,
data->temp_status[0] & 0x02);
break;
case FSCHER_SYSCTL_TEMP1:
data->temp_status[1] = (data->temp_status[1] & ~0x02)
| (results[0] & 0x02);
printk("fscher: writing value 0x%02x to temp1_status\n",
data->temp_status[1]);
fscher_write_value(client, FSCHER_REG_TEMP1_STATE,
data->temp_status[1] & 0x02);
break;
case FSCHER_SYSCTL_TEMP2:
data->temp_status[2] = (data->temp_status[2] & ~0x02)
| (results[0] & 0x02);
printk("fscher: writing value 0x%02x to temp2_status\n",
data->temp_status[2]);
fscher_write_value(client, FSCHER_REG_TEMP2_STATE,
data->temp_status[2] & 0x02);
break;
default:
printk("fscher: ctl_name %d not supported\n",ctl_name);
}
}
else
printk("fscher: writing to chip not supported\n");
}
}
/*
* The final conversion is specified in sensors.conf, as it depends on
* mainboard specific values. We export the registers contents as
* pseudo-hundredths-of-Volts (range 0V - 2.55V). Not that it makes much
* sense per se, but it minimizes the conversions count and keeps the
* values within a usual range.
*/
void fscher_volt(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
switch(ctl_name) {
case FSCHER_SYSCTL_VOLT0:
results[0] = data->volt[0];
break;
case FSCHER_SYSCTL_VOLT1:
results[0] = data->volt[1];
break;
case FSCHER_SYSCTL_VOLT2:
results[0] = data->volt[2];
break;
default:
printk("fscher: ctl_name %d not supported\n", ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
printk("fscher: writing to chip not supported\n");
}
}
void fscher_pwm(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
switch(ctl_name) {
case FSCHER_SYSCTL_PWM0:
fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results,
0,FSCHER_REG_FAN0_MIN);
break;
case FSCHER_SYSCTL_PWM1:
fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results,
1,FSCHER_REG_FAN1_MIN);
break;
case FSCHER_SYSCTL_PWM2:
fscher_pwm_internal(client,operation,ctl_name,nrels_mag,results,
2,FSCHER_REG_FAN2_MIN);
break;
default:
printk("fscher: illegal pwm nr %d\n",ctl_name);
}
}
void fscher_pwm_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_min)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
results[0] = data->fan_min[nr];
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
data->fan_min[nr] = results[0];
printk("fscher: writing value 0x%02x to fan%d_min\n",
data->fan_min[nr],nr);
fscher_write_value(client,reg_min,data->fan_min[nr]);
}
}
}
void fscher_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
switch(ctl_name) {
case FSCHER_SYSCTL_FAN0:
fscher_fan_internal(client,operation,ctl_name,nrels_mag,results,
0,FSCHER_REG_FAN0_STATE, FSCHER_REG_FAN0_RIPPLE);
break;
case FSCHER_SYSCTL_FAN1:
fscher_fan_internal(client,operation,ctl_name,nrels_mag,results,
1,FSCHER_REG_FAN1_STATE, FSCHER_REG_FAN1_RIPPLE);
break;
case FSCHER_SYSCTL_FAN2:
fscher_fan_internal(client,operation,ctl_name,nrels_mag,results,
2,FSCHER_REG_FAN2_STATE, FSCHER_REG_FAN2_RIPPLE);
break;
default:
printk("fscher: illegal fan nr %d\n",ctl_name);
}
}
#define RPM_FROM_REG(val) (val*60)
void fscher_fan_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_state, int reg_ripple)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
results[0] = data->fan_status[nr] & 0x04;
results[1] = data->fan_ripple[nr] & 0x03;
results[2] = RPM_FROM_REG(data->fan_act[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
data->fan_status[nr] = results[0] & 0x04;
printk("fscher: writing value 0x%02x to fan%d_status\n",
data->fan_status[nr],nr);
fscher_write_value(client,reg_state,data->fan_status[nr]);
}
if(*nrels_mag >= 2) {
if((results[1] & 0x03) == 0) {
printk("fscher: fan%d ripple 0 not allowed\n",nr);
return;
}
data->fan_ripple[nr] = results[1] & 0x03;
printk("fscher: writing value 0x%02x to fan%d_ripple\n",
data->fan_ripple[nr],nr);
fscher_write_value(client,reg_ripple,data->fan_ripple[nr]);
}
}
}
void fscher_wdog(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscher_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscher_update_client(client);
results[0] = data->watchdog[0] ;
results[1] = data->watchdog[1] & 0x02;
results[2] = data->watchdog[2] & 0xd0;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->watchdog[0] = results[0] & 0xff;
printk("fscher: writing value 0x%02x to wdog_preset\n",
data->watchdog[0]);
fscher_write_value(client,FSCHER_REG_WDOG_PRESET,data->watchdog[0]);
}
if (*nrels_mag >= 2) {
data->watchdog[1] = results[1] & 0x02;
printk("fscher: writing value 0x%02x to wdog_state\n",
data->watchdog[1]);
fscher_write_value(client,FSCHER_REG_WDOG_STATE,data->watchdog[1]);
}
if (*nrels_mag >= 3) {
data->watchdog[2] = results[2] & 0xf0;
printk("fscher: writing value 0x%02x to wdog_control\n",
data->watchdog[2]);
fscher_write_value(client,FSCHER_REG_WDOG_CONTROL,data->watchdog[2]);
}
}
}
static int __init sm_fscher_init(void)
{
printk("fscher.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&fscher_driver);
}
static void __exit sm_fscher_exit(void)
{
i2c_del_driver(&fscher_driver);
}
MODULE_AUTHOR("Reinhard Nissl <rnissl@gmx.de> based on work from Hermann"
" Jung <hej@odn.de>, Frodo Looijaard <frodol@dds.nl> and"
" Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("fujitsu siemens hermes chip driver");
MODULE_LICENSE("GPL");
module_init(sm_fscher_init);
module_exit(sm_fscher_exit);
-684
View File
@@ -1,684 +0,0 @@
/*
fscpos.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2001 Hermann Jung <hej@odn.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.
*/
/*
fujitsu siemens poseidon chip,
module based on lm80.c
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.com>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(fscpos);
/* The FSCPOS registers */
/* chip identification */
#define FSCPOS_REG_IDENT_0 0x00
#define FSCPOS_REG_IDENT_1 0x01
#define FSCPOS_REG_IDENT_2 0x02
#define FSCPOS_REG_REVISION 0x03
/* global control and status */
#define FSCPOS_REG_EVENT_STATE 0x04
#define FSCPOS_REG_CONTROL 0x05
/* watchdog */
#define FSCPOS_REG_WDOG_PRESET 0x28
#define FSCPOS_REG_WDOG_STATE 0x23
#define FSCPOS_REG_WDOG_CONTROL 0x21
/* fan 0 */
#define FSCPOS_REG_FAN0_MIN 0x55
#define FSCPOS_REG_FAN0_ACT 0x0e
#define FSCPOS_REG_FAN0_STATE 0x0d
#define FSCPOS_REG_FAN0_RIPPLE 0x0f
/* fan 1 */
#define FSCPOS_REG_FAN1_MIN 0x65
#define FSCPOS_REG_FAN1_ACT 0x6b
#define FSCPOS_REG_FAN1_STATE 0x62
#define FSCPOS_REG_FAN1_RIPPLE 0x6f
/* fan 2 */
/* min speed fan2 not supported */
#define FSCPOS_REG_FAN2_ACT 0xab
#define FSCPOS_REG_FAN2_STATE 0xa2
#define FSCPOS_REG_FAN2_RIPPLE 0x0af
/* voltage supervision */
#define FSCPOS_REG_VOLT_12 0x45
#define FSCPOS_REG_VOLT_5 0x42
#define FSCPOS_REG_VOLT_BATT 0x48
/* temperatures */
/* sensor 0 */
#define FSCPOS_REG_TEMP0_ACT 0x64
#define FSCPOS_REG_TEMP0_STATE 0x71
/* sensor 1 */
#define FSCPOS_REG_TEMP1_ACT 0x32
#define FSCPOS_REG_TEMP1_STATE 0x81
/* sensor 2 */
#define FSCPOS_REG_TEMP2_ACT 0x35
#define FSCPOS_REG_TEMP2_STATE 0x91
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255))
#define IN_FROM_REG(val,nr) (val)
/* Initial limits */
/* For each registered FSCPOS, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new fscpos client is
allocated. */
struct fscpos_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 revision; /* revision of chip */
u8 global_event; /* global event status */
u8 global_control; /* global control register */
u8 watchdog[3]; /* watchdog */
u8 volt[3]; /* 12, 5, battery current */
u8 temp_act[3]; /* temperature */
u8 temp_status[3]; /* status of sensor */
u8 fan_act[3]; /* fans revolutions per second */
u8 fan_status[3]; /* fan status */
u8 fan_min[3]; /* fan min value for rps */
u8 fan_ripple[3]; /* divider for rps */
};
static int fscpos_attach_adapter(struct i2c_adapter *adapter);
static int fscpos_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int fscpos_detach_client(struct i2c_client *client);
static int fscpos_read_value(struct i2c_client *client, u8 register);
static int fscpos_write_value(struct i2c_client *client, u8 register,
u8 value);
static void fscpos_update_client(struct i2c_client *client);
static void fscpos_init_client(struct i2c_client *client);
static void fscpos_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results);
static void fscpos_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscpos_fan_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_state, int reg_min, int res_ripple);
static void fscpos_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscpos_volt(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscpos_wdog(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver fscpos_driver = {
.name = "FSCPOS sensor driver",
.id = I2C_DRIVERID_FSCPOS,
.flags = I2C_DF_NOTIFY,
.attach_adapter = fscpos_attach_adapter,
.detach_client = fscpos_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define FSCPOS_SYSCTL_VOLT0 1000 /* 12 volt supply */
#define FSCPOS_SYSCTL_VOLT1 1001 /* 5 volt supply */
#define FSCPOS_SYSCTL_VOLT2 1002 /* batterie voltage*/
#define FSCPOS_SYSCTL_FAN0 1101 /* state, min, ripple, actual value fan 0 */
#define FSCPOS_SYSCTL_FAN1 1102 /* state, min, ripple, actual value fan 1 */
#define FSCPOS_SYSCTL_FAN2 1103 /* state, min, ripple, actual value fan 2 */
#define FSCPOS_SYSCTL_TEMP0 1201 /* state and value of sensor 0, cpu die */
#define FSCPOS_SYSCTL_TEMP1 1202 /* state and value of sensor 1, motherboard */
#define FSCPOS_SYSCTL_TEMP2 1203 /* state and value of sensor 2, chassis */
#define FSCPOS_SYSCTL_REV 2000 /* Revision */
#define FSCPOS_SYSCTL_EVENT 2001 /* global event status */
#define FSCPOS_SYSCTL_CONTROL 2002 /* global control byte */
#define FSCPOS_SYSCTL_WDOG 2003 /* state, min, ripple, actual value fan 2 */
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected FSCPOS. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table fscpos_dir_table_template[] = {
{FSCPOS_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_in},
{FSCPOS_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_in},
{FSCPOS_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_in},
{FSCPOS_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_temp},
{FSCPOS_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_temp},
{FSCPOS_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_temp},
{FSCPOS_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_volt},
{FSCPOS_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_volt},
{FSCPOS_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_volt},
{FSCPOS_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_fan},
{FSCPOS_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_fan},
{FSCPOS_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_fan},
{FSCPOS_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscpos_wdog},
{0}
};
static int fscpos_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, fscpos_detect);
}
static int fscpos_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct fscpos_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("fscpos.o: fscpos_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access fscpos_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct fscpos_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &fscpos_driver;
new_client->flags = 0;
/* Do the remaining detection unless force or force_fscpos parameter */
if (kind < 0) {
if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_0) != 0x50)
goto ERROR1;
if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_1) != 0x45)
goto ERROR1;
if (fscpos_read_value(new_client, FSCPOS_REG_IDENT_2) != 0x47)
goto ERROR1;
}
kind = fscpos;
type_name = "fscpos";
client_name = "fsc poseidon chip";
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
fscpos_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
fscpos_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int fscpos_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct fscpos_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("fscpos.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static int fscpos_read_value(struct i2c_client *client, u8 reg)
{
#ifdef DEBUG
printk("fscpos: read reg 0x%02x\n",reg);
#endif
return i2c_smbus_read_byte_data(client, reg);
}
static int fscpos_write_value(struct i2c_client *client, u8 reg, u8 value)
{
#ifdef DEBUG
printk("fscpos: write reg 0x%02x, val 0x%02x\n",reg, value);
#endif
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new FSCPOS. */
static void fscpos_init_client(struct i2c_client *client)
{
struct fscpos_data *data = client->data;
/* read revision from chip */
data->revision = fscpos_read_value(client,FSCPOS_REG_REVISION);
/* setup missing fan2_min value */
data->fan_min[2] = 0xff;
}
static void fscpos_update_client(struct i2c_client *client)
{
struct fscpos_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > 2 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting fscpos update\n");
#endif
data->temp_act[0] = fscpos_read_value(client, FSCPOS_REG_TEMP0_ACT);
data->temp_act[1] = fscpos_read_value(client, FSCPOS_REG_TEMP1_ACT);
data->temp_act[2] = fscpos_read_value(client, FSCPOS_REG_TEMP2_ACT);
data->temp_status[0] = fscpos_read_value(client, FSCPOS_REG_TEMP0_STATE);
data->temp_status[1] = fscpos_read_value(client, FSCPOS_REG_TEMP1_STATE);
data->temp_status[2] = fscpos_read_value(client, FSCPOS_REG_TEMP2_STATE);
data->volt[0] = fscpos_read_value(client, FSCPOS_REG_VOLT_12);
data->volt[1] = fscpos_read_value(client, FSCPOS_REG_VOLT_5);
data->volt[2] = fscpos_read_value(client, FSCPOS_REG_VOLT_BATT);
data->fan_act[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_ACT);
data->fan_act[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_ACT);
data->fan_act[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_ACT);
data->fan_status[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_STATE);
data->fan_status[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_STATE);
data->fan_status[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_STATE);
data->fan_min[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_MIN);
data->fan_min[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_MIN);
/* fan2_min is not supported */
data->fan_ripple[0] = fscpos_read_value(client, FSCPOS_REG_FAN0_RIPPLE);
data->fan_ripple[1] = fscpos_read_value(client, FSCPOS_REG_FAN1_RIPPLE);
data->fan_ripple[2] = fscpos_read_value(client, FSCPOS_REG_FAN2_RIPPLE);
data->watchdog[0] = fscpos_read_value(client, FSCPOS_REG_WDOG_PRESET);
data->watchdog[1] = fscpos_read_value(client, FSCPOS_REG_WDOG_STATE);
data->watchdog[2] = fscpos_read_value(client, FSCPOS_REG_WDOG_CONTROL);
data->global_event = fscpos_read_value(client, FSCPOS_REG_EVENT_STATE);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void fscpos_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscpos_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscpos_update_client(client);
switch(ctl_name) {
case FSCPOS_SYSCTL_REV:
results[0] = data->revision ;
break;
case FSCPOS_SYSCTL_EVENT:
results[0] = data->global_event & 0x1f;
break;
case FSCPOS_SYSCTL_CONTROL:
results[0] = data->global_control & 0x01;
break;
default:
printk("fscpos: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if((ctl_name == FSCPOS_SYSCTL_CONTROL) && (*nrels_mag >= 1)) {
data->global_control = (results[0] & 0x01);
printk("fscpos: writing 0x%02x to global_control\n",
data->global_control);
fscpos_write_value(client,FSCPOS_REG_CONTROL,
data->global_control);
}
else
printk("fscpos: writing to chip not supported\n");
}
}
#define TEMP_FROM_REG(val) (val-128)
void fscpos_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscpos_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscpos_update_client(client);
switch(ctl_name) {
case FSCPOS_SYSCTL_TEMP0:
results[0] = data->temp_status[0] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[0]);
break;
case FSCPOS_SYSCTL_TEMP1:
results[0] = data->temp_status[1] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[1]);
break;
case FSCPOS_SYSCTL_TEMP2:
results[0] = data->temp_status[2] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[2]);
break;
default:
printk("fscpos: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
switch(ctl_name) {
case FSCPOS_SYSCTL_TEMP0:
data->temp_status[0] =
(data->temp_status[0] & ~0x02)
| (results[0] & 0x02);
printk("fscpos: writing value 0x%02x "
"to temp0_status\n",
data->temp_status[0]);
fscpos_write_value(client,
FSCPOS_REG_TEMP0_STATE,
data->temp_status[0] & 0x02);
break;
case FSCPOS_SYSCTL_TEMP1:
data->temp_status[1] = (data->temp_status[1] & ~0x02) | (results[0] & 0x02);
printk("fscpos: writing value 0x%02x to temp1_status\n", data->temp_status[1]);
fscpos_write_value(client,FSCPOS_REG_TEMP1_STATE,
data->temp_status[1] & 0x02);
break;
case FSCPOS_SYSCTL_TEMP2:
data->temp_status[2] = (data->temp_status[2] & ~0x02) | (results[0] & 0x02);
printk("fscpos: writing value 0x%02x to temp2_status\n", data->temp_status[2]);
fscpos_write_value(client,FSCPOS_REG_TEMP2_STATE,
data->temp_status[2] & 0x02);
break;
default:
printk("fscpos: ctl_name %d not supported\n",ctl_name);
}
}
else
printk("fscpos: writing to chip not supported\n");
}
}
#define VOLT_FROM_REG(val,mult) (val*mult/255)
void fscpos_volt(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscpos_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
fscpos_update_client(client);
switch(ctl_name) {
case FSCPOS_SYSCTL_VOLT0:
results[0] = VOLT_FROM_REG(data->volt[0],1420);
break;
case FSCPOS_SYSCTL_VOLT1:
results[0] = VOLT_FROM_REG(data->volt[1],660);
break;
case FSCPOS_SYSCTL_VOLT2:
results[0] = VOLT_FROM_REG(data->volt[2],330);
break;
default:
printk("fscpos: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
printk("fscpos: writing to chip not supported\n");
}
}
void fscpos_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
switch(ctl_name) {
case FSCPOS_SYSCTL_FAN0:
fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results,
0,FSCPOS_REG_FAN0_STATE,FSCPOS_REG_FAN0_MIN,
FSCPOS_REG_FAN0_RIPPLE);
break;
case FSCPOS_SYSCTL_FAN1:
fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results,
1,FSCPOS_REG_FAN1_STATE,FSCPOS_REG_FAN1_MIN,
FSCPOS_REG_FAN1_RIPPLE);
break;
case FSCPOS_SYSCTL_FAN2:
fscpos_fan_internal(client,operation,ctl_name,nrels_mag,results,
2,FSCPOS_REG_FAN2_STATE,0xff,
FSCPOS_REG_FAN2_RIPPLE);
break;
default:
printk("fscpos: illegal fan nr %d\n",ctl_name);
}
}
#define RPM_FROM_REG(val) (val*60)
void fscpos_fan_internal(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results, int nr,
int reg_state, int reg_min, int reg_ripple )
{
struct fscpos_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscpos_update_client(client);
results[0] = data->fan_status[nr] & 0x04;
results[1] = data->fan_min[nr];
results[2] = data->fan_ripple[nr] & 0x03;
results[3] = RPM_FROM_REG(data->fan_act[nr]);
*nrels_mag = 4;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
data->fan_status[nr] = results[0] & 0x04;
printk("fscpos: writing value 0x%02x to fan%d_status\n",
data->fan_status[nr],nr);
fscpos_write_value(client,reg_state,
data->fan_status[nr]);
}
if((*nrels_mag >= 2) && (nr < 2)) {
/* minimal speed for fan2 not supported */
data->fan_min[nr] = results[1];
printk("fscpos: writing value 0x%02x to fan%d_min\n",
data->fan_min[nr],nr);
fscpos_write_value(client,reg_min,
data->fan_min[nr]);
}
if(*nrels_mag >= 3) {
if((results[2] & 0x03) == 0) {
printk("fscpos: fan%d ripple 0 not allowed\n",nr);
return;
}
data->fan_ripple[nr] = results[2] & 0x03;
printk("fscpos: writing value 0x%02x to fan%d_ripple\n",
data->fan_ripple[nr],nr);
fscpos_write_value(client,reg_ripple,
data->fan_ripple[nr]);
}
}
}
void fscpos_wdog(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscpos_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscpos_update_client(client);
results[0] = data->watchdog[0] ;
results[1] = data->watchdog[1] & 0x02;
results[2] = data->watchdog[2] & 0xb0;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->watchdog[0] = results[0] & 0xff;
printk("fscpos: writing value 0x%02x to wdog_preset\n",
data->watchdog[0]);
fscpos_write_value(client,FSCPOS_REG_WDOG_PRESET,
data->watchdog[0]);
}
if (*nrels_mag >= 2) {
data->watchdog[1] = results[1] & 0x02;
printk("fscpos: writing value 0x%02x to wdog_state\n",
data->watchdog[1]);
fscpos_write_value(client,FSCPOS_REG_WDOG_STATE,
data->watchdog[1]);
}
if (*nrels_mag >= 3) {
data->watchdog[2] = results[2] & 0xb0;
printk("fscpos: writing value 0x%02x to wdog_control\n",
data->watchdog[2]);
fscpos_write_value(client,FSCPOS_REG_WDOG_CONTROL,
data->watchdog[2]);
}
}
}
static int __init sm_fscpos_init(void)
{
printk("fscpos.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&fscpos_driver);
}
static void __exit sm_fscpos_exit(void)
{
i2c_del_driver(&fscpos_driver);
}
MODULE_AUTHOR
("Hermann Jung <hej@odn.de> based on work from Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("fujitsu siemens poseidon chip driver");
MODULE_LICENSE("GPL");
module_init(sm_fscpos_init);
module_exit(sm_fscpos_exit);
-909
View File
@@ -1,909 +0,0 @@
/*
fscscy.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2001 Martin Knoblauch <mkn@teraport.de, knobi@knobisoft.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.
*/
/*
fujitsu siemens scylla chip,
module based on lm80.c, fscpos.c
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.com>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x73, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(fscscy);
/* The FSCSCY registers */
/* chip identification */
#define FSCSCY_REG_IDENT_0 0x00
#define FSCSCY_REG_IDENT_1 0x01
#define FSCSCY_REG_IDENT_2 0x02
#define FSCSCY_REG_REVISION 0x03
/* global control and status */
#define FSCSCY_REG_EVENT_STATE 0x04
#define FSCSCY_REG_CONTROL 0x05
/* watchdog */
#define FSCSCY_REG_WDOG_PRESET 0x28
#define FSCSCY_REG_WDOG_STATE 0x23
#define FSCSCY_REG_WDOG_CONTROL 0x21
/*
** Fan definitions
**
** _RPMMIN: Minimum speed. Can be set via interface, but only for three of the fans
** FAN1_RPMMIN is wired to Fan 0 (CPU Fans)
** FAN4_RPMMIN is wired to Fan 2 (PS Fans ??)
** FAN5_RPMMIN is wired to Fan 3 (AUX Fans ??)
** _ACT: Actual Fan Speed
** _STATE: Fan status register
** _RIPPLE: Fan speed multiplier
*/
/* fan 0 */
#define FSCSCY_REG_FAN0_RPMMIN 0x65
#define FSCSCY_REG_FAN0_ACT 0x6b
#define FSCSCY_REG_FAN0_STATE 0x62
#define FSCSCY_REG_FAN0_RIPPLE 0x6f
/* fan 1 */
#define FSCSCY_REG_FAN1_RPMMIN FSCSCY_REG_FAN0_RPMMIN
#define FSCSCY_REG_FAN1_ACT 0x6c
#define FSCSCY_REG_FAN1_STATE 0x61
#define FSCSCY_REG_FAN1_RIPPLE 0x6f
/* fan 2 */
#define FSCSCY_REG_FAN2_RPMMIN 0x55
#define FSCSCY_REG_FAN2_ACT 0x0e
#define FSCSCY_REG_FAN2_STATE 0x0d
#define FSCSCY_REG_FAN2_RIPPLE 0x0f
/* fan 3 */
#define FSCSCY_REG_FAN3_RPMMIN 0xa5
#define FSCSCY_REG_FAN3_ACT 0xab
#define FSCSCY_REG_FAN3_STATE 0xa2
#define FSCSCY_REG_FAN3_RIPPLE 0xaf
/* fan 4 */
#define FSCSCY_REG_FAN4_RPMMIN FSCSCY_REG_FAN2_RPMMIN
#define FSCSCY_REG_FAN4_ACT 0x5c
#define FSCSCY_REG_FAN4_STATE 0x52
#define FSCSCY_REG_FAN4_RIPPLE 0x0f
/* fan 5 */
#define FSCSCY_REG_FAN5_RPMMIN FSCSCY_REG_FAN3_RPMMIN
#define FSCSCY_REG_FAN5_ACT 0xbb
#define FSCSCY_REG_FAN5_STATE 0xb2
#define FSCSCY_REG_FAN5_RIPPLE 0xbf
/* voltage supervision */
#define FSCSCY_REG_VOLT_12 0x45
#define FSCSCY_REG_VOLT_5 0x42
#define FSCSCY_REG_VOLT_BATT 0x48
/* temperatures */
/* sensor 0 */
#define FSCSCY_REG_TEMP0_ACT 0x64
#define FSCSCY_REG_TEMP0_STATE 0x71
#define FSCSCY_REG_TEMP0_LIM 0x76
/* sensor 1 */
#define FSCSCY_REG_TEMP1_ACT 0xD0
#define FSCSCY_REG_TEMP1_STATE 0xD1
#define FSCSCY_REG_TEMP1_LIM 0xD6
/* sensor 2 */
#define FSCSCY_REG_TEMP2_ACT 0x32
#define FSCSCY_REG_TEMP2_STATE 0x81
#define FSCSCY_REG_TEMP2_LIM 0x86
/* sensor3 */
#define FSCSCY_REG_TEMP3_ACT 0x35
#define FSCSCY_REG_TEMP3_STATE 0x91
#define FSCSCY_REG_TEMP3_LIM 0x96
/* PCI Load */
#define FSCSCY_REG_PCILOAD 0x1a
/* Intrusion Sensor */
#define FSCSCY_REG_INTR_STATE 0x13
#define FSCSCY_REG_INTR_CTRL 0x12
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val,nr) (SENSORS_LIMIT((val),0,255))
#define IN_FROM_REG(val,nr) (val)
/* Initial limits */
/* For each registered FSCSCY, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new fscscy client is
allocated. */
struct fscscy_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 revision; /* revision of chip */
u8 global_event; /* global event status */
u8 global_control; /* global control register */
u8 watchdog[3]; /* watchdog */
u8 volt[3]; /* 12, 5, battery current */
u8 volt_min[3]; /* minimum voltages over module "lifetime" */
u8 volt_max[3]; /* maximum voltages over module "lifetime" */
u8 temp_act[4]; /* temperature */
u8 temp_status[4]; /* status of temp. sensor */
u8 temp_lim[4]; /* limit temperature of temp. sensor */
u8 temp_min[4]; /* minimum of temp. sensor, this is just calculated by the module */
u8 temp_max[4]; /* maximum of temp. sensor, this is just calculsted by the module */
u8 fan_act[6]; /* fans revolutions per second */
u8 fan_status[6]; /* fan status */
u8 fan_rpmmin[6]; /* fan min value for rps */
u8 fan_ripple[6]; /* divider for rps */
u8 fan_min[6]; /* minimum RPM over module "lifetime" */
u8 fan_max[6]; /* maximum RPM over module "lifetime" */
u8 pciload; /* PCILoad value */
u8 intr_status; /* Intrusion Status */
u8 intr_control; /* Intrusion Control */
};
static int fscscy_attach_adapter(struct i2c_adapter *adapter);
static int fscscy_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int fscscy_detach_client(struct i2c_client *client);
static int fscscy_read_value(struct i2c_client *client, u8 register);
static int fscscy_write_value(struct i2c_client *client, u8 register,
u8 value);
static void fscscy_update_client(struct i2c_client *client);
static void fscscy_init_client(struct i2c_client *client);
static void fscscy_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results);
static void fscscy_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscscy_fan_internal(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results,
int nr, int reg_state, int reg_min, int res_ripple);
static void fscscy_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscscy_volt(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscscy_wdog(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscscy_pciload(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void fscscy_intrusion(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver fscscy_driver = {
.name = "FSCSCY sensor driver",
.id = I2C_DRIVERID_FSCSCY,
.flags = I2C_DF_NOTIFY,
.attach_adapter = fscscy_attach_adapter,
.detach_client = fscscy_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define FSCSCY_SYSCTL_VOLT0 1000 /* 12 volt supply */
#define FSCSCY_SYSCTL_VOLT1 1001 /* 5 volt supply */
#define FSCSCY_SYSCTL_VOLT2 1002 /* batterie voltage*/
#define FSCSCY_SYSCTL_FAN0 1101 /* state, min, ripple, actual value fan 0 */
#define FSCSCY_SYSCTL_FAN1 1102 /* state, min, ripple, actual value fan 1 */
#define FSCSCY_SYSCTL_FAN2 1103 /* state, min, ripple, actual value fan 2 */
#define FSCSCY_SYSCTL_FAN3 1104 /* state, min, ripple, actual value fan 3 */
#define FSCSCY_SYSCTL_FAN4 1105 /* state, min, ripple, actual value fan 4 */
#define FSCSCY_SYSCTL_FAN5 1106 /* state, min, ripple, actual value fan 5 */
#define FSCSCY_SYSCTL_TEMP0 1201 /* state and value of sensor 0, cpu die */
#define FSCSCY_SYSCTL_TEMP1 1202 /* state and value of sensor 1, motherboard */
#define FSCSCY_SYSCTL_TEMP2 1203 /* state and value of sensor 2, chassis */
#define FSCSCY_SYSCTL_TEMP3 1204 /* state and value of sensor 3, chassis */
#define FSCSCY_SYSCTL_REV 2000 /* Revision */
#define FSCSCY_SYSCTL_EVENT 2001 /* global event status */
#define FSCSCY_SYSCTL_CONTROL 2002 /* global control byte */
#define FSCSCY_SYSCTL_WDOG 2003 /* state, min, ripple, actual value fan 2 */
#define FSCSCY_SYSCTL_PCILOAD 2004 /* PCILoad value */
#define FSCSCY_SYSCTL_INTRUSION 2005 /* state, control for intrusion sensor */
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected FSCSCY. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table fscscy_dir_table_template[] = {
{FSCSCY_SYSCTL_REV, "rev", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_in},
{FSCSCY_SYSCTL_EVENT, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_in},
{FSCSCY_SYSCTL_CONTROL, "control", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_in},
{FSCSCY_SYSCTL_TEMP0, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_temp},
{FSCSCY_SYSCTL_TEMP1, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_temp},
{FSCSCY_SYSCTL_TEMP2, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_temp},
{FSCSCY_SYSCTL_TEMP3, "temp4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_temp},
{FSCSCY_SYSCTL_VOLT0, "in0", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_volt},
{FSCSCY_SYSCTL_VOLT1, "in1", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_volt},
{FSCSCY_SYSCTL_VOLT2, "in2", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_volt},
{FSCSCY_SYSCTL_FAN0, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_FAN1, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_FAN2, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_FAN3, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_FAN4, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_FAN5, "fan6", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_fan},
{FSCSCY_SYSCTL_WDOG, "wdog", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_wdog},
{FSCSCY_SYSCTL_PCILOAD, "pciload", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_pciload},
{FSCSCY_SYSCTL_INTRUSION, "intrusion", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &fscscy_intrusion},
{0}
};
static int fscscy_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, fscscy_detect);
}
int fscscy_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct fscscy_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("fscscy.o: fscscy_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access fscscy_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct fscscy_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &fscscy_driver;
new_client->flags = 0;
/* Do the remaining detection unless force or force_fscscy parameter */
if (kind < 0) {
if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_0) != 0x53)
goto ERROR1;
if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_1) != 0x43)
goto ERROR1;
if (fscscy_read_value(new_client, FSCSCY_REG_IDENT_2) != 0x59)
goto ERROR1;
}
kind = fscscy;
type_name = "fscscy";
client_name = "fsc scylla chip";
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
fscscy_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
fscscy_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int fscscy_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct fscscy_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("fscscy.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static int fscscy_read_value(struct i2c_client *client, u8 reg)
{
#ifdef DEBUG
printk("fscscy: read reg 0x%02x\n",reg);
#endif
return i2c_smbus_read_byte_data(client, reg);
}
static int fscscy_write_value(struct i2c_client *client, u8 reg, u8 value)
{
#ifdef DEBUG
printk("fscscy: write reg 0x%02x, val 0x%02x\n",reg, value);
#endif
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new FSCSCY. */
static void fscscy_init_client(struct i2c_client *client)
{
struct fscscy_data *data = client->data;
/* read revision from chip */
data->revision = fscscy_read_value(client,FSCSCY_REG_REVISION);
/* Initialize min/max values from chip */
data->fan_min[0] = data->fan_max[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_ACT);
data->fan_min[1] = data->fan_max[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_ACT);
data->fan_min[2] = data->fan_max[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_ACT);
data->fan_min[3] = data->fan_max[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_ACT);
data->fan_min[4] = data->fan_max[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_ACT);
data->fan_min[4] = data->fan_max[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_ACT);
data->temp_min[0] = data->temp_max[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_ACT);
data->temp_min[1] = data->temp_max[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_ACT);
data->temp_min[2] = data->temp_max[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_ACT);
data->temp_min[3] = data->temp_max[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_ACT);
data->volt_min[0] = data->volt_max[0] = fscscy_read_value(client, FSCSCY_REG_VOLT_12);
data->volt_min[1] = data->volt_max[1] = fscscy_read_value(client, FSCSCY_REG_VOLT_5);
data->volt_min[2] = data->volt_max[2] = fscscy_read_value(client, FSCSCY_REG_VOLT_BATT);
}
static void fscscy_update_client(struct i2c_client *client)
{
struct fscscy_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > 2 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting fscscy update\n");
#endif
data->temp_act[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_ACT);
if (data->temp_min[0] > data->temp_act[0]) data->temp_min[0] = data->temp_act[0];
if (data->temp_max[0] < data->temp_act[0]) data->temp_max[0] = data->temp_act[0];
data->temp_act[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_ACT);
if (data->temp_min[1] > data->temp_act[1]) data->temp_min[1] = data->temp_act[1];
if (data->temp_max[1] < data->temp_act[1]) data->temp_max[1] = data->temp_act[1];
data->temp_act[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_ACT);
if (data->temp_min[2] > data->temp_act[2]) data->temp_min[2] = data->temp_act[2];
if (data->temp_max[2] < data->temp_act[2]) data->temp_max[2] = data->temp_act[2];
data->temp_act[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_ACT);
if (data->temp_min[3] > data->temp_act[3]) data->temp_min[3] = data->temp_act[3];
if (data->temp_max[3] < data->temp_act[3]) data->temp_max[3] = data->temp_act[3];
data->temp_status[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_STATE);
data->temp_status[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_STATE);
data->temp_status[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_STATE);
data->temp_status[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_STATE);
data->temp_lim[0] = fscscy_read_value(client, FSCSCY_REG_TEMP0_LIM);
data->temp_lim[1] = fscscy_read_value(client, FSCSCY_REG_TEMP1_LIM);
data->temp_lim[2] = fscscy_read_value(client, FSCSCY_REG_TEMP2_LIM);
data->temp_lim[3] = fscscy_read_value(client, FSCSCY_REG_TEMP3_LIM);
data->volt[0] = fscscy_read_value(client, FSCSCY_REG_VOLT_12);
if (data->volt_min[0] > data->volt[0]) data->volt_min[0] = data->volt[0];
if (data->volt_max[0] < data->volt[0]) data->volt_max[0] = data->volt[0];
data->volt[1] = fscscy_read_value(client, FSCSCY_REG_VOLT_5);
if (data->volt_min[1] > data->volt[1]) data->volt_min[1] = data->volt[1];
if (data->volt_max[1] < data->volt[1]) data->volt_max[1] = data->volt[1];
data->volt[2] = fscscy_read_value(client, FSCSCY_REG_VOLT_BATT);
if (data->volt_min[2] > data->volt[2]) data->volt_min[2] = data->volt[2];
if (data->volt_max[2] < data->volt[2]) data->volt_max[2] = data->volt[2];
data->fan_act[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_ACT);
if (data->fan_min[0] > data->fan_act[0]) data->fan_min[0] = data->fan_act[0];
if (data->fan_max[0] < data->fan_act[0]) data->fan_max[0] = data->fan_act[0];
data->fan_act[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_ACT);
if (data->fan_min[1] > data->fan_act[1]) data->fan_min[1] = data->fan_act[1];
if (data->fan_max[1] < data->fan_act[1]) data->fan_max[1] = data->fan_act[1];
data->fan_act[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_ACT);
if (data->fan_min[2] > data->fan_act[2]) data->fan_min[2] = data->fan_act[2];
if (data->fan_max[2] < data->fan_act[2]) data->fan_max[2] = data->fan_act[2];
data->fan_act[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_ACT);
if (data->fan_min[3] > data->fan_act[3]) data->fan_min[3] = data->fan_act[3];
if (data->fan_max[3] < data->fan_act[3]) data->fan_max[3] = data->fan_act[3];
data->fan_act[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_ACT);
if (data->fan_min[4] > data->fan_act[4]) data->fan_min[4] = data->fan_act[4];
if (data->fan_max[4] < data->fan_act[4]) data->fan_max[4] = data->fan_act[4];
data->fan_act[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_ACT);
if (data->fan_min[5] > data->fan_act[5]) data->fan_min[5] = data->fan_act[5];
if (data->fan_max[5] < data->fan_act[5]) data->fan_max[5] = data->fan_act[5];
data->fan_status[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_STATE);
data->fan_status[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_STATE);
data->fan_status[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_STATE);
data->fan_status[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_STATE);
data->fan_status[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_STATE);
data->fan_status[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_STATE);
data->fan_rpmmin[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_RPMMIN);
data->fan_rpmmin[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_RPMMIN);
data->fan_rpmmin[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_RPMMIN);
data->fan_rpmmin[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_RPMMIN);
data->fan_rpmmin[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_RPMMIN);
data->fan_rpmmin[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_RPMMIN);
data->fan_ripple[0] = fscscy_read_value(client, FSCSCY_REG_FAN0_RIPPLE);
data->fan_ripple[1] = fscscy_read_value(client, FSCSCY_REG_FAN1_RIPPLE);
data->fan_ripple[2] = fscscy_read_value(client, FSCSCY_REG_FAN2_RIPPLE);
data->fan_ripple[3] = fscscy_read_value(client, FSCSCY_REG_FAN3_RIPPLE);
data->fan_ripple[4] = fscscy_read_value(client, FSCSCY_REG_FAN4_RIPPLE);
data->fan_ripple[5] = fscscy_read_value(client, FSCSCY_REG_FAN5_RIPPLE);
data->watchdog[0] = fscscy_read_value(client, FSCSCY_REG_WDOG_PRESET);
data->watchdog[1] = fscscy_read_value(client, FSCSCY_REG_WDOG_STATE);
data->watchdog[2] = fscscy_read_value(client, FSCSCY_REG_WDOG_CONTROL);
data->global_event = fscscy_read_value(client, FSCSCY_REG_EVENT_STATE);
data->global_control = fscscy_read_value(client, FSCSCY_REG_CONTROL);
data->pciload = fscscy_read_value(client, FSCSCY_REG_PCILOAD);
data->intr_status = fscscy_read_value(client, FSCSCY_REG_INTR_STATE);
data->intr_control = fscscy_read_value(client, FSCSCY_REG_INTR_CTRL);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void fscscy_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
switch(ctl_name) {
case FSCSCY_SYSCTL_REV:
results[0] = data->revision ;
break;
case FSCSCY_SYSCTL_EVENT:
results[0] = data->global_event & 0x9f; /* MKN */
break;
case FSCSCY_SYSCTL_CONTROL:
results[0] = data->global_control & 0x19; /* MKN */
break;
default:
printk("fscscy: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if((ctl_name == FSCSCY_SYSCTL_CONTROL) && (*nrels_mag >= 1)) {
data->global_control = (data->global_control & 0x18) | (results[0] & 0x01); /* MKN */
printk("fscscy: writing 0x%02x to global_control\n",
data->global_control);
fscscy_write_value(client,FSCSCY_REG_CONTROL,
data->global_control);
}
else
printk("fscscy: writing to chip not supported\n");
}
}
#define TEMP_FROM_REG(val) (val-128)
void fscscy_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
switch(ctl_name) {
case FSCSCY_SYSCTL_TEMP0:
results[0] = data->temp_status[0] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[0]);
results[2] = TEMP_FROM_REG(data->temp_lim[0]);
results[3] = TEMP_FROM_REG(data->temp_min[0]);
results[4] = TEMP_FROM_REG(data->temp_max[0]);
break;
case FSCSCY_SYSCTL_TEMP1:
results[0] = data->temp_status[1] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[1]);
results[2] = TEMP_FROM_REG(data->temp_lim[1]);
results[3] = TEMP_FROM_REG(data->temp_min[1]);
results[4] = TEMP_FROM_REG(data->temp_max[1]);
break;
case FSCSCY_SYSCTL_TEMP2:
results[0] = data->temp_status[2] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[2]);
results[2] = TEMP_FROM_REG(data->temp_lim[2]);
results[3] = TEMP_FROM_REG(data->temp_min[2]);
results[4] = TEMP_FROM_REG(data->temp_max[2]);
break;
case FSCSCY_SYSCTL_TEMP3:
results[0] = data->temp_status[3] & 0x03;
results[1] = TEMP_FROM_REG(data->temp_act[3]);
results[2] = TEMP_FROM_REG(data->temp_lim[3]);
results[3] = TEMP_FROM_REG(data->temp_min[3]);
results[4] = TEMP_FROM_REG(data->temp_max[3]);
break;
default:
printk("fscscy: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 5;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
switch(ctl_name) {
case FSCSCY_SYSCTL_TEMP0:
data->temp_status[0] =
(data->temp_status[0] & ~0x02)
| (results[0] & 0x02);
printk("fscscy: writing value 0x%02x "
"to temp0_status\n",
data->temp_status[0]);
fscscy_write_value(client,
FSCSCY_REG_TEMP0_STATE,
data->temp_status[0] & 0x02);
break;
case FSCSCY_SYSCTL_TEMP1:
data->temp_status[1] = (data->temp_status[1] & ~0x02) | (results[0] & 0x02);
printk("fscscy: writing value 0x%02x to temp1_status\n", data->temp_status[1]);
fscscy_write_value(client,FSCSCY_REG_TEMP1_STATE,
data->temp_status[1] & 0x02);
break;
case FSCSCY_SYSCTL_TEMP2:
data->temp_status[2] = (data->temp_status[2] & ~0x02) | (results[0] & 0x02);
printk("fscscy: writing value 0x%02x to temp2_status\n", data->temp_status[2]);
fscscy_write_value(client,FSCSCY_REG_TEMP2_STATE,
data->temp_status[2] & 0x02);
break;
case FSCSCY_SYSCTL_TEMP3:
data->temp_status[3] = (data->temp_status[3] & ~0x02) | (results[0] & 0x02);
printk("fscscy: writing value 0x%02x to temp3_status\n", data->temp_status[3]);
fscscy_write_value(client,FSCSCY_REG_TEMP3_STATE,
data->temp_status[3] & 0x02);
break;
default:
printk("fscscy: ctl_name %d not supported\n",ctl_name);
}
}
else
printk("fscscy: writing to chip not supported\n");
}
}
#define VOLT_FROM_REG(val,mult) (val*mult/255)
void fscscy_volt(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
switch(ctl_name) {
case FSCSCY_SYSCTL_VOLT0:
results[0] = VOLT_FROM_REG(data->volt[0],1420);
results[1] = VOLT_FROM_REG(data->volt_min[0],1420);
results[2] = VOLT_FROM_REG(data->volt_max[0],1420);
break;
case FSCSCY_SYSCTL_VOLT1:
results[0] = VOLT_FROM_REG(data->volt[1],660);
results[1] = VOLT_FROM_REG(data->volt_min[1],660);
results[2] = VOLT_FROM_REG(data->volt_max[1],660);
break;
case FSCSCY_SYSCTL_VOLT2:
results[0] = VOLT_FROM_REG(data->volt[2],330);
results[1] = VOLT_FROM_REG(data->volt_min[2],330);
results[2] = VOLT_FROM_REG(data->volt_max[2],330);
break;
default:
printk("fscscy: ctl_name %d not supported\n",
ctl_name);
*nrels_mag = 0;
return;
}
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
printk("fscscy: writing to chip not supported\n");
}
}
void fscscy_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
switch(ctl_name) {
case FSCSCY_SYSCTL_FAN0:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
0,FSCSCY_REG_FAN0_STATE,FSCSCY_REG_FAN0_RPMMIN,
FSCSCY_REG_FAN0_RIPPLE);
break;
case FSCSCY_SYSCTL_FAN1:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
1,FSCSCY_REG_FAN1_STATE,FSCSCY_REG_FAN1_RPMMIN,
FSCSCY_REG_FAN1_RIPPLE);
break;
case FSCSCY_SYSCTL_FAN2:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
2,FSCSCY_REG_FAN2_STATE,FSCSCY_REG_FAN2_RPMMIN,
FSCSCY_REG_FAN2_RIPPLE);
break;
case FSCSCY_SYSCTL_FAN3:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
3,FSCSCY_REG_FAN3_STATE,FSCSCY_REG_FAN3_RPMMIN,
FSCSCY_REG_FAN3_RIPPLE);
break;
case FSCSCY_SYSCTL_FAN4:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
4,FSCSCY_REG_FAN4_STATE,FSCSCY_REG_FAN4_RPMMIN,
FSCSCY_REG_FAN4_RIPPLE);
break;
case FSCSCY_SYSCTL_FAN5:
fscscy_fan_internal(client,operation,ctl_name,nrels_mag,results,
5,FSCSCY_REG_FAN5_STATE,FSCSCY_REG_FAN5_RPMMIN,
FSCSCY_REG_FAN5_RIPPLE);
break;
default:
printk("fscscy: illegal fan nr %d\n",ctl_name);
}
}
#define RPM_FROM_REG(val) (val*60)
void fscscy_fan_internal(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results, int nr,
int reg_state, int reg_min, int reg_ripple )
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
results[0] = data->fan_status[nr] & 0x0f; /* MKN */
results[1] = data->fan_rpmmin[nr];
results[2] = data->fan_ripple[nr] & 0x03;
results[3] = RPM_FROM_REG(data->fan_act[nr]);
results[4] = RPM_FROM_REG(data->fan_min[nr]);
results[5] = RPM_FROM_REG(data->fan_max[nr]);
*nrels_mag = 6;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if(*nrels_mag >= 1) {
data->fan_status[nr] = (data->fan_status[nr] & 0x0b) | (results[0] & 0x04); /* MKN */
printk("fscscy: writing value 0x%02x to fan%d_status\n",
data->fan_status[nr],nr);
fscscy_write_value(client,reg_state,
data->fan_status[nr]);
}
if(*nrels_mag >= 2) {
if((results[1] & 0xff) == 0) {
printk("fscscy: fan%d rpmmin 0 not allowed for safety reasons\n",nr);
return;
}
data->fan_rpmmin[nr] = results[1];
printk("fscscy: writing value 0x%02x to fan%d_min\n",
data->fan_rpmmin[nr],nr);
fscscy_write_value(client,reg_min,
data->fan_rpmmin[nr]);
}
if(*nrels_mag >= 3) {
if((results[2] & 0x03) == 0) {
printk("fscscy: fan%d ripple 0 is nonsense/not allowed\n",nr);
return;
}
data->fan_ripple[nr] = results[2] & 0x03;
printk("fscscy: writing value 0x%02x to fan%d_ripple\n",
data->fan_ripple[nr],nr);
fscscy_write_value(client,reg_ripple,
data->fan_ripple[nr]);
}
}
}
void fscscy_wdog(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
results[0] = data->watchdog[0] ;
results[1] = data->watchdog[1] & 0x02;
results[2] = data->watchdog[2] & 0xb0;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->watchdog[0] = results[0] & 0xff;
printk("fscscy: writing value 0x%02x to wdog_preset\n",
data->watchdog[0]);
fscscy_write_value(client,FSCSCY_REG_WDOG_PRESET,
data->watchdog[0]);
}
if (*nrels_mag >= 2) {
data->watchdog[1] = results[1] & 0x02;
printk("fscscy: writing value 0x%02x to wdog_state\n",
data->watchdog[1]);
fscscy_write_value(client,FSCSCY_REG_WDOG_STATE,
data->watchdog[1]);
}
if (*nrels_mag >= 3) {
data->watchdog[2] = results[2] & 0xb0;
printk("fscscy: writing value 0x%02x to wdog_control\n",
data->watchdog[2]);
fscscy_write_value(client,FSCSCY_REG_WDOG_CONTROL,
data->watchdog[2]);
}
}
}
void fscscy_pciload(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
results[0] = data->pciload;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
printk("fscscy: writing PCILOAD to chip not supported\n");
}
}
void fscscy_intrusion(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct fscscy_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
fscscy_update_client(client);
results[0] = data->intr_control & 0x80;
results[1] = data->intr_status & 0xc0;
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->intr_control = results[0] & 0x80;
printk("fscscy: writing value 0x%02x to intr_control\n",
data->intr_control);
fscscy_write_value(client,FSCSCY_REG_INTR_CTRL,
data->intr_control);
}
}
}
static int __init sm_fscscy_init(void)
{
printk("fscscy.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&fscscy_driver);
}
static void __exit sm_fscscy_exit(void)
{
i2c_del_driver(&fscscy_driver);
}
MODULE_AUTHOR
("Martin Knoblauch <mkn@teraport.de> based on work (fscpos) from Hermann Jung <hej@odn.de>");
MODULE_DESCRIPTION("fujitsu siemens scylla chip driver");
module_init(sm_fscscy_init);
module_exit(sm_fscscy_exit);
-987
View File
@@ -1,987 +0,0 @@
/*
gl518sm.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>,
Kyösti Mälkki <kmalkki@cc.hut.fi>
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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#ifdef __SMP__
#include <linux/smp_lock.h>
#endif
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_2(gl518sm_r00, gl518sm_r80);
/* Defining this will enable debug messages for the voltage iteration
code used with rev 0 ICs */
#undef DEBUG_VIN
/* Many GL518 constants specified below */
/* The GL518 registers */
#define GL518_REG_CHIP_ID 0x00
#define GL518_REG_REVISION 0x01
#define GL518_REG_VENDOR_ID 0x02
#define GL518_REG_CONF 0x03
#define GL518_REG_TEMP 0x04
#define GL518_REG_TEMP_OVER 0x05
#define GL518_REG_TEMP_HYST 0x06
#define GL518_REG_FAN_COUNT 0x07
#define GL518_REG_FAN_LIMIT 0x08
#define GL518_REG_VIN1_LIMIT 0x09
#define GL518_REG_VIN2_LIMIT 0x0a
#define GL518_REG_VIN3_LIMIT 0x0b
#define GL518_REG_VDD_LIMIT 0x0c
#define GL518_REG_VIN3 0x0d
#define GL518_REG_MISC 0x0f
#define GL518_REG_ALARM 0x10
#define GL518_REG_MASK 0x11
#define GL518_REG_INT 0x12
#define GL518_REG_VIN2 0x13
#define GL518_REG_VIN1 0x14
#define GL518_REG_VDD 0x15
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+119),\
0,255))
#define TEMP_FROM_REG(val) (((val) - 119) * 10)
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) \
( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) )
#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255))
#define IN_FROM_REG(val) (((val)*19)/10)
#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255))
#define VDD_FROM_REG(val) (((val)*23)/10)
#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1)
#define DIV_FROM_REG(val) (1 << (val))
#define ALARMS_FROM_REG(val) val
#define BEEP_ENABLE_TO_REG(val) ((val)?0:1)
#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1)
#define BEEPS_TO_REG(val) ((val) & 0x7f)
#define BEEPS_FROM_REG(val) ((val) & 0x7f)
/* Each client has this additional data */
struct gl518_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
int iterate_lock;
int quit_thread;
struct task_struct *thread;
wait_queue_head_t wq;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
unsigned long last_updated_v00;
/* In jiffies (used only by rev00 chips) */
u8 voltage[4]; /* Register values; [0] = VDD */
u8 voltage_min[4]; /* Register values; [0] = VDD */
u8 voltage_max[4]; /* Register values; [0] = VDD */
u8 iter_voltage[4]; /* Register values; [0] = VDD */
u8 fan[2];
u8 fan_min[2];
u8 temp; /* Register values */
u8 temp_over; /* Register values */
u8 temp_hyst; /* Register values */
u8 alarms, beeps; /* Register value */
u8 alarm_mask; /* Register value */
u8 fan_div[2]; /* Register encoding, shifted right */
u8 beep_enable; /* Boolean */
u8 iterate; /* Voltage iteration mode */
};
static int gl518_attach_adapter(struct i2c_adapter *adapter);
static int gl518_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void gl518_init_client(struct i2c_client *client);
static int gl518_detach_client(struct i2c_client *client);
static int gl518_read_value(struct i2c_client *client, u8 reg);
static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value);
static void gl518_update_client(struct i2c_client *client);
static void gl518_update_client_rev00(struct i2c_client *client);
static void gl518_update_iterate(struct i2c_client *client);
static void gl518_vin(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_beep(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_fan1off(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl518_iterate(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
/* This is the driver that will be inserted */
static struct i2c_driver gl518_driver = {
.name = "GL518SM sensor chip driver",
.id = I2C_DRIVERID_GL518,
.flags = I2C_DF_NOTIFY,
.attach_adapter = gl518_attach_adapter,
.detach_client = gl518_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define GL518_SYSCTL_VDD 1000 /* Volts * 100 */
#define GL518_SYSCTL_VIN1 1001
#define GL518_SYSCTL_VIN2 1002
#define GL518_SYSCTL_VIN3 1003
#define GL518_SYSCTL_FAN1 1101 /* RPM */
#define GL518_SYSCTL_FAN2 1102
#define GL518_SYSCTL_TEMP 1200 /* Degrees Celsius * 10 */
#define GL518_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define GL518_SYSCTL_ALARMS 2001 /* bitvector */
#define GL518_SYSCTL_BEEP 2002 /* bitvector */
#define GL518_SYSCTL_FAN1OFF 2003
#define GL518_SYSCTL_ITERATE 2004
#define GL518_ALARM_VDD 0x01
#define GL518_ALARM_VIN1 0x02
#define GL518_ALARM_VIN2 0x04
#define GL518_ALARM_VIN3 0x08
#define GL518_ALARM_TEMP 0x10
#define GL518_ALARM_FAN1 0x20
#define GL518_ALARM_FAN2 0x40
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected GL518. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table gl518_dir_table_template[] = {
{GL518_SYSCTL_VIN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_vin},
{GL518_SYSCTL_VIN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_vin},
{GL518_SYSCTL_VIN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_vin},
{GL518_SYSCTL_VDD, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_vin},
{GL518_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_fan},
{GL518_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_fan},
{GL518_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_temp},
{GL518_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_fan_div},
{GL518_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_alarms},
{GL518_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_beep},
{GL518_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_fan1off},
{GL518_SYSCTL_ITERATE, "iterate", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl518_iterate},
{0}
};
/* I choose here for semi-static GL518SM allocation. Complete dynamic
allocation could also be used; the code needed for this would probably
take more memory than the datastructure takes now. */
#define MAX_GL518_NR 4
static struct i2c_client *gl518_list[MAX_GL518_NR];
static int gl518_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, gl518_detect);
}
static int gl518_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct gl518_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("gl518sm.o: gl518_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access gl518_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct gl518_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &gl518_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if (
(gl518_read_value(new_client, GL518_REG_CHIP_ID) !=
0x80)
|| (gl518_read_value(new_client, GL518_REG_CONF) &
0x80)) goto ERROR1;
}
/* Determine the chip type. */
if (kind <= 0) {
i = gl518_read_value(new_client, GL518_REG_REVISION);
if (i == 0x00)
kind = gl518sm_r00;
else if (i == 0x80)
kind = gl518sm_r80;
else {
if (kind == 0)
printk
("gl518sm.o: Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
goto ERROR1;
}
}
type_name = "gl518sm";
if (kind == gl518sm_r00) {
client_name = "GL518SM Revision 0x00 chip";
} else if (kind == gl518sm_r80) {
client_name = "GL518SM Revision 0x80 chip";
} else {
#ifdef DEBUG
printk("gl518sm.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
for (i = 0; i < MAX_GL518_NR; i++)
if (!gl518_list[i])
break;
if (i == MAX_GL518_NR) {
printk
("gl518sm.o: No empty slots left, recompile and heighten "
"MAX_GL518_NR!\n");
err = -ENOMEM;
goto ERROR2;
}
gl518_list[i] = new_client;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry((struct i2c_client *) new_client,
type_name,
gl518_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the GL518SM chip */
if (kind == gl518sm_r00)
data->iterate = 0;
else
data->iterate = 3;
data->iterate_lock = 0;
data->quit_thread = 0;
data->thread = NULL;
data->alarm_mask = 0xff;
data->voltage[0]=data->voltage[1]=data->voltage[2]=0;
gl518_init_client((struct i2c_client *) new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
for (i = 0; i < MAX_GL518_NR; i++)
if (new_client == gl518_list[i])
gl518_list[i] = NULL;
ERROR2:
ERROR1:
kfree(data);
ERROR0:
return err;
}
/* Called when we have found a new GL518SM. */
static void gl518_init_client(struct i2c_client *client)
{
/* Power-on defaults (bit 7=1) */
gl518_write_value(client, GL518_REG_CONF, 0x80);
/* No noisy output (bit 2=1), Comparator mode (bit 3=0), two fans (bit4=0),
standby mode (bit6=0) */
gl518_write_value(client, GL518_REG_CONF, 0x04);
/* Never interrupts */
gl518_write_value(client, GL518_REG_MASK, 0x00);
/* Clear status register (bit 5=1), start (bit6=1) */
gl518_write_value(client, GL518_REG_CONF, 0x24);
gl518_write_value(client, GL518_REG_CONF, 0x44);
}
static int gl518_detach_client(struct i2c_client *client)
{
int err, i;
struct gl518_data *data = client->data;
i2c_deregister_entry(((struct gl518_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("gl518sm.o: Client deregistration failed, client not detached.\n");
return err;
}
for (i = 0; i < MAX_GL518_NR; i++)
if (client == gl518_list[i])
break;
if ((i == MAX_GL518_NR)) {
printk("gl518sm.o: Client to detach not found.\n");
return -ENOENT;
}
gl518_list[i] = NULL;
if (data->thread) {
data->quit_thread = 1;
wake_up_interruptible(&data->wq);
}
kfree(client->data);
return 0;
}
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
GL518 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int gl518_read_value(struct i2c_client *client, u8 reg)
{
if ((reg >= 0x07) && (reg <= 0x0c))
return swab16(i2c_smbus_read_word_data(client, reg));
else
return i2c_smbus_read_byte_data(client, reg);
}
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
GL518 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int gl518_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if ((reg >= 0x07) && (reg <= 0x0c))
return i2c_smbus_write_word_data(client, reg, swab16(value));
else
return i2c_smbus_write_byte_data(client, reg, value);
}
static void gl518_update_client(struct i2c_client *client)
{
struct gl518_data *data = client->data;
int val;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting gl518 update\n");
#endif
data->alarms = gl518_read_value(client, GL518_REG_INT);
data->beeps = gl518_read_value(client, GL518_REG_ALARM);
val = gl518_read_value(client, GL518_REG_VDD_LIMIT);
data->voltage_min[0] = val & 0xff;
data->voltage_max[0] = (val >> 8) & 0xff;
val = gl518_read_value(client, GL518_REG_VIN1_LIMIT);
data->voltage_min[1] = val & 0xff;
data->voltage_max[1] = (val >> 8) & 0xff;
val = gl518_read_value(client, GL518_REG_VIN2_LIMIT);
data->voltage_min[2] = val & 0xff;
data->voltage_max[2] = (val >> 8) & 0xff;
val = gl518_read_value(client, GL518_REG_VIN3_LIMIT);
data->voltage_min[3] = val & 0xff;
data->voltage_max[3] = (val >> 8) & 0xff;
val = gl518_read_value(client, GL518_REG_FAN_COUNT);
data->fan[0] = (val >> 8) & 0xff;
data->fan[1] = val & 0xff;
val = gl518_read_value(client, GL518_REG_FAN_LIMIT);
data->fan_min[0] = (val >> 8) & 0xff;
data->fan_min[1] = val & 0xff;
data->temp = gl518_read_value(client, GL518_REG_TEMP);
data->temp_over =
gl518_read_value(client, GL518_REG_TEMP_OVER);
data->temp_hyst =
gl518_read_value(client, GL518_REG_TEMP_HYST);
val = gl518_read_value(client, GL518_REG_MISC);
data->fan_div[0] = (val >> 6) & 0x03;
data->fan_div[1] = (val >> 4) & 0x03;
data->alarms &= data->alarm_mask;
val = gl518_read_value(client, GL518_REG_CONF);
data->beep_enable = (val >> 2) & 1;
#ifndef DEBUG_VIN
if (data->type != gl518sm_r00) {
data->voltage[0] =
gl518_read_value(client, GL518_REG_VDD);
data->voltage[1] =
gl518_read_value(client, GL518_REG_VIN1);
data->voltage[2] =
gl518_read_value(client, GL518_REG_VIN2);
data->voltage[3] =
gl518_read_value(client, GL518_REG_VIN3);
} else
gl518_update_client_rev00(client);
#else
gl518_update_client_rev00(client);
#endif
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* Here we decide how to run the iteration code.
When called, we trigger the iteration and report the last
measured voltage. No delay for user apps */
static void gl518_update_client_rev00(struct i2c_client *client)
{
struct gl518_data *data = client->data;
int i;
if (data->iterate == 1) { /* 10 sec delay */
/* as that update is slow, we consider the data valid for 30 seconds */
if (
((jiffies - data->last_updated_v00 > 30 * HZ)
|| (data->alarms & 7)
|| (!data->valid)) && (!data->iterate_lock)) {
data->iterate_lock = 1;
gl518_update_iterate(client);
data->iterate_lock = 0;
}
for (i = 0; i < 4; i++)
data->voltage[i] = data->iter_voltage[i];
} else if (data->iterate == 2) { /* show results of last iteration */
for (i = 0; i < 4; i++)
data->voltage[i] = data->iter_voltage[i];
wake_up_interruptible(&data->wq);
} else { /* no iteration */
data->voltage[3] =
gl518_read_value(client, GL518_REG_VIN3);
}
}
static int gl518_update_thread(void *c)
{
struct i2c_client *client = c;
struct gl518_data *data = client->data;
#ifdef __SMP__
lock_kernel();
#endif
exit_mm(current);
current->session = 1;
current->pgrp = 1;
sigfillset(&current->blocked);
current->fs->umask = 0;
strcpy(current->comm, "gl518sm");
init_waitqueue_head(&(data->wq));
data->thread = current;
#ifdef __SMP__
unlock_kernel();
#endif
for (;;) {
if (!data->iterate_lock) {
data->iterate_lock = 1;
gl518_update_iterate(client);
data->iterate_lock = 0;
}
if ((data->quit_thread) || signal_pending(current))
break;
interruptible_sleep_on(&data->wq);
}
data->thread = NULL;
data->quit_thread = 0;
return 0;
}
/* This updates vdd, vin1, vin2 values by doing slow and multiple
comparisons for the GL518SM rev 00 that lacks support for direct
reading of these values. Values are kept in iter_voltage */
static void gl518_update_iterate(struct i2c_client *client)
{
struct gl518_data *data = client->data;
int i, j, loop_more = 1, min[3], max[3], delta[3];
int alarm, beeps, irqs;
#define VIN_REG(c) c==0?GL518_REG_VDD_LIMIT:\
c==1?GL518_REG_VIN1_LIMIT:\
GL518_REG_VIN2_LIMIT
/* disable beeps & irqs for vin0-2 */
beeps = gl518_read_value(client, GL518_REG_ALARM);
irqs = gl518_read_value(client, GL518_REG_MASK);
gl518_write_value(client, GL518_REG_ALARM, beeps & ~0x7);
gl518_write_value(client, GL518_REG_MASK, irqs & ~0x7);
alarm = data->alarms;
for (i = 0; i < 3; i++) {
if (alarm & (1 << i)) {
min[i] = 0;
max[i] = 127;
} else {
min[i] = data->voltage_min[i];
max[i] =
(data->voltage_max[i] +
data->voltage_min[i]) / 2;
}
delta[i] = (max[i] - min[i]) / 2;
}
for (j = 0; (j < 10 && loop_more); j++) {
for (i = 0; i < 3; i++)
gl518_write_value(client, VIN_REG(i),
max[i] << 8 | min[i]);
if ((data->thread) &&
((data->quit_thread) || signal_pending(current)))
goto finish;
/* we wait now 1.5 seconds before comparing */
current->state = TASK_INTERRUPTIBLE;
schedule_timeout(HZ + HZ / 2);
alarm = gl518_read_value(client, GL518_REG_INT);
#ifdef DEBUG_VIN
printk("gl518sm: iteration %2d: %4d%c %4d%c %4d%c\n", j,
max[0], (alarm & 1) ? '!' : ' ',
max[1], (alarm & 2) ? '!' : ' ',
max[2], (alarm & 4) ? '!' : ' ');
#endif
for (loop_more = 0, i = 0; i < 3; i++) {
if (alarm & (1 << i))
max[i] += delta[i];
else
max[i] -= delta[i];
if (delta[i])
loop_more++;
delta[i] >>= 1;
}
}
for (i = 0; i < 3; i++)
if (alarm & (1 << i))
max[i]++;
#ifdef DEBUG_VIN
printk("gl518sm: final :%5d %5d %5d\n", max[0], max[1],
max[2]);
printk("gl518sm: meter :%5d %5d %5d\n", data->voltage[0],
data->voltage[1], data->voltage[2]);
#endif
/* update values, including vin3 */
for (i = 0; i < 3; i++) {
data->iter_voltage[i] = max[i];
}
data->iter_voltage[3] = gl518_read_value(client, GL518_REG_VIN3);
data->last_updated_v00 = jiffies;
finish:
/* reset values */
for (i = 0; i < 3; i++) {
gl518_write_value(client, VIN_REG(i),
data->voltage_max[i] << 8 | data->
voltage_min[i]);
}
gl518_write_value(client, GL518_REG_ALARM, beeps);
gl518_write_value(client, GL518_REG_MASK, irqs);
#undef VIN_REG
}
void gl518_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_over);
results[1] = TEMP_FROM_REG(data->temp_hyst);
results[2] = TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_over = TEMP_TO_REG(results[0]);
gl518_write_value(client, GL518_REG_TEMP_OVER,
data->temp_over);
}
if (*nrels_mag >= 2) {
data->temp_hyst = TEMP_TO_REG(results[1]);
gl518_write_value(client, GL518_REG_TEMP_HYST,
data->temp_hyst);
}
}
}
void gl518_vin(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
int nr = ctl_name - GL518_SYSCTL_VDD;
int regnr, old = 0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) :
VDD_FROM_REG(data->voltage_min[nr]);
results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) :
VDD_FROM_REG(data->voltage_max[nr]);
results[2] = nr ? IN_FROM_REG(data->voltage[nr]) :
VDD_FROM_REG(data->voltage[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
regnr =
nr == 0 ? GL518_REG_VDD_LIMIT : nr ==
1 ? GL518_REG_VIN1_LIMIT : nr ==
2 ? GL518_REG_VIN2_LIMIT : GL518_REG_VIN3_LIMIT;
if (*nrels_mag == 1)
old = gl518_read_value(client, regnr) & 0xff00;
if (*nrels_mag >= 2) {
data->voltage_max[nr] =
nr ? IN_TO_REG(results[1]) :
VDD_TO_REG(results[1]);
old = data->voltage_max[nr] << 8;
}
if (*nrels_mag >= 1) {
data->voltage_min[nr] =
nr ? IN_TO_REG(results[0]) :
VDD_TO_REG(results[0]);
old |= data->voltage_min[nr];
gl518_write_value(client, regnr, old);
}
}
}
void gl518_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
int nr = ctl_name - GL518_SYSCTL_FAN1;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr],
DIV_FROM_REG(data->fan_div[nr]));
results[1] =
FAN_FROM_REG(data->fan[nr],
DIV_FROM_REG(data->fan_div[nr]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr] = FAN_TO_REG(results[0],
DIV_FROM_REG(data->
fan_div
[nr]));
old =
gl518_read_value(client, GL518_REG_FAN_LIMIT);
if (nr == 0) {
old =
(old & 0x00ff) | (data->
fan_min[0] << 8);
if (results[0] == 0)
data->alarm_mask &= ~0x20;
else
data->alarm_mask |= 0x20;
} else {
old = (old & 0xff00) | data->fan_min[1];
if (results[0] == 0)
data->alarm_mask &= ~0x40;
else
data->alarm_mask |= 0x40;
}
gl518_write_value(client, GL518_REG_FAN_LIMIT,
old);
}
}
}
void gl518_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = ALARMS_FROM_REG(data->alarms);
*nrels_mag = 1;
}
}
void gl518_beep(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable);
results[1] = BEEPS_FROM_REG(data->beeps);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->beep_enable = BEEP_ENABLE_TO_REG(results[0]);
gl518_write_value(client, GL518_REG_CONF,
(gl518_read_value(client,
GL518_REG_CONF)
& 0xfb) | (data->
beep_enable << 2));
}
if (*nrels_mag >= 2) {
data->beeps =
BEEPS_TO_REG(results[1]) & data->alarm_mask;
gl518_write_value(client, GL518_REG_ALARM,
data->beeps);
}
}
}
void gl518_fan_div(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl518_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = gl518_read_value(client, GL518_REG_MISC);
if (*nrels_mag >= 2) {
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0xcf) | (data->fan_div[1] << 4);
}
if (*nrels_mag >= 1) {
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0x3f) | (data->fan_div[0] << 6);
}
gl518_write_value(client, GL518_REG_MISC, old);
}
}
void gl518_fan1off(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] =
((gl518_read_value(client, GL518_REG_MISC) & 0x08) !=
0);
results[1] =
((gl518_read_value(client, GL518_REG_CONF) & 0x10) !=
0);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
old =
gl518_read_value(client,
GL518_REG_MISC) & 0xf7;
if (results[0])
old |= 0x08;
gl518_write_value(client, GL518_REG_MISC, old);
}
if (*nrels_mag >= 2) {
old =
gl518_read_value(client,
GL518_REG_CONF) & 0xef;
if (results[1])
old |= 0x10;
gl518_write_value(client, GL518_REG_CONF, old);
}
}
}
void gl518_iterate(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl518_data *data = client->data;
int i;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = data->iterate;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE &&
data->type == gl518sm_r00 ) {
if ((*nrels_mag >= 1) && (data->iterate != results[0])) {
data->iterate = results[0];
for (i = 0; i < 4; i++) {
data->voltage[i] = 0;
data->iter_voltage[i] = 0;
}
data->valid = 0;
if ((data->iterate != 2) && (data->thread)) {
data->quit_thread = 1;
wake_up_interruptible(&data->wq);
} else if ((data->iterate == 2) && (!data->thread)) {
init_waitqueue_head(&(data->wq));
kernel_thread(gl518_update_thread,
(void *) client, 0);
}
}
}
}
static int __init sm_gl518sm_init(void)
{
printk("gl518sm.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&gl518_driver);
}
static void __exit sm_gl518sm_exit(void)
{
i2c_del_driver(&gl518_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Kyösti Mälkki <kmalkki@cc.hut.fi>");
MODULE_DESCRIPTION("GL518SM driver");
module_init(sm_gl518sm_init);
module_exit(sm_gl518sm_exit);
-804
View File
@@ -1,804 +0,0 @@
/*
gl520sm.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>,
Kyösti Mälkki <kmalkki@cc.hut.fi>
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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x2c, 0x2d, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(gl520sm);
/* Many GL520 constants specified below
One of the inputs can be configured as either temp or voltage.
That's why _TEMP2 and _VIN4 access the same register
*/
/* The GL520 registers */
#define GL520_REG_CHIP_ID 0x00
#define GL520_REG_REVISION 0x01
#define GL520_REG_VID 0x02
#define GL520_REG_CONF 0x03
#define GL520_REG_TEMP1 0x04
#define GL520_REG_TEMP1_OVER 0x05
#define GL520_REG_TEMP1_HYST 0x06
#define GL520_REG_FAN_COUNT 0x07
#define GL520_REG_FAN_LIMIT 0x08
#define GL520_REG_VIN1_LIMIT 0x09
#define GL520_REG_VIN2_LIMIT 0x0a
#define GL520_REG_VIN3_LIMIT 0x0b
#define GL520_REG_VDD_LIMIT 0x0c
#define GL520_REG_VIN3 0x0d
#define GL520_REG_VIN4 0x0e
#define GL520_REG_TEMP2 0x0e
#define GL520_REG_MISC 0x0f
#define GL520_REG_ALARM 0x10
#define GL520_REG_MASK 0x11
#define GL520_REG_INT 0x12
#define GL520_REG_VIN2 0x13
#define GL520_REG_VIN1 0x14
#define GL520_REG_VDD 0x15
#define GL520_REG_TEMP2_OVER 0x17
#define GL520_REG_VIN4_MAX 0x17
#define GL520_REG_TEMP2_HYST 0x18
#define GL520_REG_VIN4_MIN 0x18
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((((val)<0?(val)-5:(val)+5) / 10)+130),\
0,255))
#define TEMP_FROM_REG(val) (((val) - 130) * 10)
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((960000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) \
( (val)==0 ? 0 : (val)==255 ? 0 : (960000/((val)*(div))) )
#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*10+8)/19),0,255))
#define IN_FROM_REG(val) (((val)*19)/10)
#define VDD_TO_REG(val) (SENSORS_LIMIT((((val)*10+11)/23),0,255))
#define VDD_FROM_REG(val) (((val)*23)/10)
#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1)
#define DIV_FROM_REG(val) (1 << (val))
#define ALARMS_FROM_REG(val) val
#define BEEP_ENABLE_TO_REG(val) ((val)?0:1)
#define BEEP_ENABLE_FROM_REG(val) ((val)?0:1)
#define BEEPS_TO_REG(val) (val)
#define BEEPS_FROM_REG(val) (val)
#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\
205-(val)*5)
/* Each client has this additional data */
struct gl520_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 voltage[5]; /* Register values; [0] = VDD */
u8 voltage_min[5]; /* Register values; [0] = VDD */
u8 voltage_max[5]; /* Register values; [0] = VDD */
u8 fan[2];
u8 fan_min[2];
u8 temp[2]; /* Register values */
u8 temp_over[2]; /* Register values */
u8 temp_hyst[2]; /* Register values */
u8 alarms, beeps, vid; /* Register value */
u8 alarm_mask; /* Register value */
u8 fan_div[2]; /* Register encoding, shifted right */
u8 beep_enable; /* Boolean */
u8 two_temps; /* Boolean */
};
static int gl520_attach_adapter(struct i2c_adapter *adapter);
static int gl520_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void gl520_init_client(struct i2c_client *client);
static int gl520_detach_client(struct i2c_client *client);
static int gl520_read_value(struct i2c_client *client, u8 reg);
static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value);
static void gl520_update_client(struct i2c_client *client);
static void gl520_vin(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_beep(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_fan1off(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void gl520_config(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
/* This is the driver that will be inserted */
static struct i2c_driver gl520_driver = {
.name = "GL520SM sensor chip driver",
.id = I2C_DRIVERID_GL520,
.flags = I2C_DF_NOTIFY,
.attach_adapter = gl520_attach_adapter,
.detach_client = gl520_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define GL520_SYSCTL_VDD 1000 /* Volts * 100 */
#define GL520_SYSCTL_VIN1 1001
#define GL520_SYSCTL_VIN2 1002
#define GL520_SYSCTL_VIN3 1003
#define GL520_SYSCTL_VIN4 1004
#define GL520_SYSCTL_FAN1 1101 /* RPM */
#define GL520_SYSCTL_FAN2 1102
#define GL520_SYSCTL_TEMP1 1200 /* Degrees Celsius * 10 */
#define GL520_SYSCTL_TEMP2 1201 /* Degrees Celsius * 10 */
#define GL520_SYSCTL_VID 1300
#define GL520_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define GL520_SYSCTL_ALARMS 2001 /* bitvector */
#define GL520_SYSCTL_BEEP 2002 /* bitvector */
#define GL520_SYSCTL_FAN1OFF 2003
#define GL520_SYSCTL_CONFIG 2004
#define GL520_ALARM_VDD 0x01
#define GL520_ALARM_VIN1 0x02
#define GL520_ALARM_VIN2 0x04
#define GL520_ALARM_VIN3 0x08
#define GL520_ALARM_TEMP1 0x10
#define GL520_ALARM_FAN1 0x20
#define GL520_ALARM_FAN2 0x40
#define GL520_ALARM_TEMP2 0x80
#define GL520_ALARM_VIN4 0x80
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected GL520. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table gl520_dir_table_template[] = {
{GL520_SYSCTL_VIN1, "vin1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vin},
{GL520_SYSCTL_VIN2, "vin2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vin},
{GL520_SYSCTL_VIN3, "vin3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vin},
{GL520_SYSCTL_VIN4, "vin4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vin},
{GL520_SYSCTL_VDD, "vdd", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vin},
{GL520_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_vid},
{GL520_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_fan},
{GL520_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_fan},
{GL520_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_temp},
{GL520_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_temp},
{GL520_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_fan_div},
{GL520_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_alarms},
{GL520_SYSCTL_BEEP, "beep", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_beep},
{GL520_SYSCTL_FAN1OFF, "fan1off", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_fan1off},
{GL520_SYSCTL_CONFIG, "config", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &gl520_config},
{0}
};
static int gl520_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, gl520_detect);
}
static int gl520_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct gl520_data *data;
int err = 0;
const char *type_name = "";
char client_name[32];
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("gl520sm.o: gl520_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access gl520_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct gl520_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &gl520_driver;
new_client->flags = 0;
/* Determine the chip type. */
if (gl520_read_value(new_client, GL520_REG_CHIP_ID) != 0x20) {
printk
("gl520sm.o: Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
goto ERROR1;
} else {
kind = gl520sm;
}
i = gl520_read_value(new_client, GL520_REG_REVISION);
if (kind == gl520sm) {
type_name = "gl520sm";
sprintf(client_name, "GL520SM Revision %02x chip", i);
} else {
#ifdef DEBUG
printk("gl520sm.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
gl520_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the GL520SM chip */
data->alarm_mask = 0xff;
gl520_init_client(new_client);
if (data->two_temps)
data->voltage_max[4] = data->voltage_min[4] =
data->voltage[4] = 0;
else
data->temp_hyst[1] = data->temp_over[1] =
data->temp[1] = 0;
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
/* Called when we have found a new GL520SM. */
static void gl520_init_client(struct i2c_client *client)
{
struct gl520_data *data = (struct gl520_data *)(client->data);
u8 oldconf, conf;
conf = oldconf = gl520_read_value(client, GL520_REG_CONF);
data->two_temps = !(conf & 0x10);
/* If IRQ# is disabled, we can safely force comparator mode */
if (!(conf & 0x20))
conf &= 0xf7;
/* Enable monitoring if needed */
conf |= 0x40;
if (conf != oldconf)
gl520_write_value(client, GL520_REG_CONF, conf);
}
static int gl520_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct gl520_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("gl520sm.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
GL520 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int gl520_read_value(struct i2c_client *client, u8 reg)
{
if ((reg >= 0x07) && (reg <= 0x0c))
return swab16(i2c_smbus_read_word_data(client, reg));
else
return i2c_smbus_read_byte_data(client, reg);
}
/* Registers 0x07 to 0x0c are word-sized, others are byte-sized
GL520 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int gl520_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if ((reg >= 0x07) && (reg <= 0x0c))
return i2c_smbus_write_word_data(client, reg, swab16(value));
else
return i2c_smbus_write_byte_data(client, reg, value);
}
static void gl520_update_client(struct i2c_client *client)
{
struct gl520_data *data = client->data;
int val;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting gl520 update\n");
#endif
data->alarms = gl520_read_value(client, GL520_REG_INT);
data->beeps = gl520_read_value(client, GL520_REG_ALARM);
data->vid = gl520_read_value(client, GL520_REG_VID) & 0x1f;
val = gl520_read_value(client, GL520_REG_VDD_LIMIT);
data->voltage_min[0] = val & 0xff;
data->voltage_max[0] = (val >> 8) & 0xff;
val = gl520_read_value(client, GL520_REG_VIN1_LIMIT);
data->voltage_min[1] = val & 0xff;
data->voltage_max[1] = (val >> 8) & 0xff;
val = gl520_read_value(client, GL520_REG_VIN2_LIMIT);
data->voltage_min[2] = val & 0xff;
data->voltage_max[2] = (val >> 8) & 0xff;
val = gl520_read_value(client, GL520_REG_VIN3_LIMIT);
data->voltage_min[3] = val & 0xff;
data->voltage_max[3] = (val >> 8) & 0xff;
val = gl520_read_value(client, GL520_REG_FAN_COUNT);
data->fan[0] = (val >> 8) & 0xff;
data->fan[1] = val & 0xff;
val = gl520_read_value(client, GL520_REG_FAN_LIMIT);
data->fan_min[0] = (val >> 8) & 0xff;
data->fan_min[1] = val & 0xff;
data->temp[0] = gl520_read_value(client, GL520_REG_TEMP1);
data->temp_over[0] =
gl520_read_value(client, GL520_REG_TEMP1_OVER);
data->temp_hyst[0] =
gl520_read_value(client, GL520_REG_TEMP1_HYST);
val = gl520_read_value(client, GL520_REG_MISC);
data->fan_div[0] = (val >> 6) & 0x03;
data->fan_div[1] = (val >> 4) & 0x03;
data->alarms &= data->alarm_mask;
val = gl520_read_value(client, GL520_REG_CONF);
data->beep_enable = (val >> 2) & 1;
data->voltage[0] = gl520_read_value(client, GL520_REG_VDD);
data->voltage[1] =
gl520_read_value(client, GL520_REG_VIN1);
data->voltage[2] =
gl520_read_value(client, GL520_REG_VIN2);
data->voltage[3] =
gl520_read_value(client, GL520_REG_VIN3);
/* Temp1 and Vin4 are the same input */
if (data->two_temps) {
data->temp[1] =
gl520_read_value(client, GL520_REG_TEMP2);
data->temp_over[1] =
gl520_read_value(client, GL520_REG_TEMP2_OVER);
data->temp_hyst[1] =
gl520_read_value(client, GL520_REG_TEMP2_HYST);
} else {
data->voltage[4] =
gl520_read_value(client, GL520_REG_VIN4);
data->voltage_min[4] =
gl520_read_value(client, GL520_REG_VIN4_MIN);
data->voltage_max[4] =
gl520_read_value(client, GL520_REG_VIN4_MAX);
}
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
void gl520_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
int nr = ctl_name - GL520_SYSCTL_TEMP1;
int regnr;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_over[nr]);
results[1] = TEMP_FROM_REG(data->temp_hyst[nr]);
results[2] = TEMP_FROM_REG(data->temp[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if ((nr == 1) && (!data->two_temps))
return;
regnr =
nr == 0 ? GL520_REG_TEMP1_OVER : GL520_REG_TEMP2_OVER;
if (*nrels_mag >= 1) {
data->temp_over[nr] = TEMP_TO_REG(results[0]);
gl520_write_value(client, regnr,
data->temp_over[nr]);
}
regnr =
nr == 0 ? GL520_REG_TEMP1_HYST : GL520_REG_TEMP2_HYST;
if (*nrels_mag >= 2) {
data->temp_hyst[nr] = TEMP_TO_REG(results[1]);
gl520_write_value(client, regnr,
data->temp_hyst[nr]);
}
}
}
void gl520_vin(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
int nr = ctl_name - GL520_SYSCTL_VDD;
int regnr, old = 0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = nr ? IN_FROM_REG(data->voltage_min[nr]) :
VDD_FROM_REG(data->voltage_min[nr]);
results[1] = nr ? IN_FROM_REG(data->voltage_max[nr]) :
VDD_FROM_REG(data->voltage_max[nr]);
results[2] = nr ? IN_FROM_REG(data->voltage[nr]) :
VDD_FROM_REG(data->voltage[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (nr != 4) {
regnr =
nr == 0 ? GL520_REG_VDD_LIMIT : nr ==
1 ? GL520_REG_VIN1_LIMIT : nr ==
2 ? GL520_REG_VIN2_LIMIT :
GL520_REG_VIN3_LIMIT;
if (*nrels_mag == 1)
old =
gl520_read_value(client,
regnr) & 0xff00;
if (*nrels_mag >= 2) {
data->voltage_max[nr] =
nr ? IN_TO_REG(results[1]) :
VDD_TO_REG(results[1]);
old = data->voltage_max[nr] << 8;
}
if (*nrels_mag >= 1) {
data->voltage_min[nr] =
nr ? IN_TO_REG(results[0]) :
VDD_TO_REG(results[0]);
old |= data->voltage_min[nr];
gl520_write_value(client, regnr, old);
}
} else if (!data->two_temps) {
if (*nrels_mag == 1)
gl520_write_value(client,
GL520_REG_VIN4_MIN,
IN_TO_REG(results[0]));
if (*nrels_mag >= 2)
gl520_write_value(client,
GL520_REG_VIN4_MAX,
IN_TO_REG(results[1]));
}
}
}
void gl520_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
int nr = ctl_name - GL520_SYSCTL_FAN1;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr],
DIV_FROM_REG(data->fan_div[nr]));
results[1] =
FAN_FROM_REG(data->fan[nr],
DIV_FROM_REG(data->fan_div[nr]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr] = FAN_TO_REG(results[0],
DIV_FROM_REG(data->
fan_div
[nr]));
old =
gl520_read_value(client, GL520_REG_FAN_LIMIT);
if (nr == 0) {
old =
(old & 0x00ff) | (data->
fan_min[nr] << 8);
if (results[0] == 0)
data->alarm_mask &= ~0x20;
else
data->alarm_mask |= 0x20;
} else {
old = (old & 0xff00) | data->fan_min[nr];
if (results[0] == 0)
data->alarm_mask &= ~0x40;
else
data->alarm_mask |= 0x40;
}
gl520_write_value(client, GL520_REG_FAN_LIMIT,
old);
}
}
}
void gl520_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = ALARMS_FROM_REG(data->alarms);
*nrels_mag = 1;
}
}
void gl520_beep(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = BEEP_ENABLE_FROM_REG(data->beep_enable);
results[1] = BEEPS_FROM_REG(data->beeps);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->beep_enable = BEEP_ENABLE_TO_REG(results[0]);
gl520_write_value(client, GL520_REG_CONF,
(gl520_read_value(client,
GL520_REG_CONF)
& 0xfb) | (data->
beep_enable << 2));
}
if (*nrels_mag >= 2) {
data->beeps =
BEEPS_TO_REG(results[1]) & data->alarm_mask;
gl520_write_value(client, GL520_REG_ALARM,
data->beeps);
}
}
}
void gl520_fan_div(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = gl520_read_value(client, GL520_REG_MISC);
if (*nrels_mag >= 2) {
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0xcf) | (data->fan_div[1] << 4);
}
if (*nrels_mag >= 1) {
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0x3f) | (data->fan_div[0] << 6);
}
gl520_write_value(client, GL520_REG_MISC, old);
}
}
void gl520_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
gl520_update_client(client);
results[0] = VID_FROM_REG(data->vid);
*nrels_mag = 1;
}
}
void gl520_fan1off(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] =
((gl520_read_value(client, GL520_REG_MISC) & 0x04) !=
0);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
old =
gl520_read_value(client,
GL520_REG_MISC) & 0xfb;
if (results[0])
old |= 0x04;
gl520_write_value(client, GL520_REG_MISC, old);
}
}
}
void gl520_config(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct gl520_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] =
((gl520_read_value(client, GL520_REG_CONF) & 0x10) ==
0);
data->two_temps = results[0];
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
old =
gl520_read_value(client,
GL520_REG_CONF) & 0xef;
if (!results[1]) {
old |= 0x10;
data->two_temps = 0;
data->temp_hyst[1] = data->temp_over[1] =
data->temp[1] = 0;
} else {
data->two_temps = 1;
data->voltage_max[4] = data->voltage_min[4] =
data->voltage[4] = 0;
}
gl520_write_value(client, GL520_REG_CONF, old);
}
}
}
static int __init sm_gl520sm_init(void)
{
printk("gl520sm.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&gl520_driver);
}
static void __exit sm_gl520sm_exit(void)
{
i2c_del_driver(&gl520_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Kyösti Mälkki <kmalkki@cc.hut.fi>");
MODULE_DESCRIPTION("GL520SM driver");
module_init(sm_gl520sm_init);
module_exit(sm_gl520sm_exit);
-1130
View File
File diff suppressed because it is too large. Load diff
-645
View File
@@ -1,645 +0,0 @@
/*
* lm63.c - driver for the National Semiconductor LM63 temperature sensor
* with integrated fan control
* Copyright (C) 2004 Jean Delvare <khali@linux-fr.org>
* Based on the lm90 driver.
*
* The LM63 is a sensor chip made by National Semiconductor. It measures
* two temperatures (its own and one external one) and the speed of one
* fan, those speed it can additionally control. Complete datasheet can be
* obtained from National's website at:
* http://www.national.com/pf/LM/LM63.html
*
* The LM63 is basically an LM86 with fan speed monitoring and control
* capabilities added. It misses some of the LM86 features though:
* - No low limit for local temperature.
* - No critical limit for local temperature.
* - Critical limit for remote temperature can be changed only once. We
* will consider that the critical limit is read-only.
*
* The datasheet isn't very clear about what the tachometer reading is.
* I had a explanation from National Semiconductor though. The two lower
* bits of the read value have to be masked out. The value is still 16 bit
* in width.
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/*
* Addresses to scan
* Address is fully defined internally and cannot be changed.
*/
static unsigned short normal_i2c[] = { 0x4c, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/*
* Insmod parameters
*/
SENSORS_INSMOD_1(lm63);
/*
* The LM63 registers
*/
#define LM63_REG_CONFIG1 0x03
#define LM63_REG_CONFIG2 0xBF
#define LM63_REG_CONFIG_FAN 0x4A
#define LM63_REG_TACH_COUNT_MSB 0x47
#define LM63_REG_TACH_COUNT_LSB 0x46
#define LM63_REG_TACH_LIMIT_MSB 0x49
#define LM63_REG_TACH_LIMIT_LSB 0x48
#define LM63_REG_PWM_VALUE 0x4C
#define LM63_REG_PWM_FREQ 0x4D
#define LM63_REG_LOCAL_TEMP 0x00
#define LM63_REG_LOCAL_HIGH 0x05
#define LM63_REG_REMOTE_TEMP_MSB 0x01
#define LM63_REG_REMOTE_TEMP_LSB 0x10
#define LM63_REG_REMOTE_OFFSET_MSB 0x11
#define LM63_REG_REMOTE_OFFSET_LSB 0x12
#define LM63_REG_REMOTE_HIGH_MSB 0x07
#define LM63_REG_REMOTE_HIGH_LSB 0x13
#define LM63_REG_REMOTE_LOW_MSB 0x08
#define LM63_REG_REMOTE_LOW_LSB 0x14
#define LM63_REG_REMOTE_TCRIT 0x19
#define LM63_REG_REMOTE_TCRIT_HYST 0x21
#define LM63_REG_ALERT_STATUS 0x02
#define LM63_REG_ALERT_MASK 0x16
#define LM63_REG_MAN_ID 0xFE
#define LM63_REG_CHIP_ID 0xFF
/*
* Conversions and various macros
* For tachometer counts, the LM63 uses 16-bit values.
* For local temperature and high limit, remote critical limit and hysteresis
* value, it uses signed 8-bit values with LSB = 1 degree Celsius.
* For remote temperature, low and high limits, it uses signed 11-bit values
* with LSB = 0.125 degree Celsius, left-justified in 16-bit registers.
*/
#define FAN_FROM_REG(reg) ((reg) == 0xFFFC || (reg) == 0 ? 0 : \
5400000 / (reg))
#define FAN_TO_REG(val) ((val) <= 82 ? 0xFFFF : \
(5400000 / (val)) & 0xFFFC)
#define TEMP8_FROM_REG(reg) (reg)
#define TEMP8_TO_REG(val) ((val) <= -128 ? -128 : \
(val) >= 127 ? 127 : \
val)
#define TEMP11_FROM_REG(reg) ((reg) / 32 * 125)
#define TEMP11_TO_REG(val) ((val) <= -128000 ? 0x8000 : \
(val) >= 127875 ? 0x7FE0 : \
(val) < 0 ? ((val) - 62) / 125 * 32 : \
((val) + 62) / 125 * 32)
#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \
(val) >= 127 ? 127 : \
val)
/*
* Functions declaration
*/
static int lm63_attach_adapter(struct i2c_adapter *adapter);
static int lm63_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void lm63_init_client(struct i2c_client *client);
static int lm63_detach_client(struct i2c_client *client);
static void lm63_local_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_remote_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_remote_tcrit_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm63_pwm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
/*
* Driver data (common to all clients)
*/
static struct i2c_driver lm63_driver = {
.name = "LM63 sensor driver",
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm63_attach_adapter,
.detach_client = lm63_detach_client,
};
/*
* Client data (each client gets its own)
*/
struct lm63_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
/* registers values */
u8 config, config_fan;
u16 fan1_input;
u16 fan1_low;
u8 pwm1_freq;
u8 pwm1_value;
s8 temp1_input;
s8 temp1_high;
s16 temp2_input;
s16 temp2_high;
s16 temp2_low;
s8 temp2_crit;
u8 temp2_crit_hyst;
u8 alarms;
};
/*
* Proc entries
* These files are created for each detected LM63.
*/
/* -- SENSORS SYSCTL START -- */
#define LM63_SYSCTL_TEMP1 1200
#define LM63_SYSCTL_TEMP2 1201
#define LM63_SYSCTL_TEMP2_TCRIT 1205
#define LM63_SYSCTL_TEMP2_TCRIT_HYST 1208
#define LM63_SYSCTL_ALARMS 1210
#define LM63_SYSCTL_FAN1 1220
#define LM63_SYSCTL_PWM1 1230
#define LM63_ALARM_LOCAL_HIGH 0x40
#define LM63_ALARM_REMOTE_HIGH 0x10
#define LM63_ALARM_REMOTE_LOW 0x08
#define LM63_ALARM_REMOTE_CRIT 0x02
#define LM63_ALARM_REMOTE_OPEN 0x04
#define LM63_ALARM_FAN_LOW 0x01
/* -- SENSORS SYSCTL END -- */
static ctl_table lm63_dir_table_template[] =
{
{LM63_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_local_temp},
{LM63_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_temp},
{LM63_SYSCTL_TEMP2_TCRIT, "temp2_crit", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_tcrit},
{LM63_SYSCTL_TEMP2_TCRIT_HYST, "temp2_crit_hyst", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_remote_tcrit_hyst},
{LM63_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_alarms},
{LM63_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_fan},
{LM63_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm63_pwm},
{0}
};
/*
* Real code
*/
static int lm63_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm63_detect);
}
/*
* The following function does more than just detection. If detection
* succeeds, it also registers the new chip.
*/
static int lm63_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
struct i2c_client *new_client;
struct lm63_data *data;
int err = 0;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
#ifdef DEBUG
printk(KERN_DEBUG "lm63: adapter doesn't support SMBus byte "
"data mode, skipping.\n");
#endif
return 0;
}
if (!(data = kmalloc(sizeof(struct lm63_data), GFP_KERNEL))) {
printk(KERN_ERR "lm63: Out of memory in lm63_detect\n");
return -ENOMEM;
}
/*
* The common I2C client data is placed right before the
* LM63-specific data. The LM63-specific data is pointed to by the
* data field from the i2c_client structure.
*/
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm63_driver;
new_client->flags = 0;
/* Default to an LM63 if forced */
if (kind == 0)
kind = lm63;
if (kind < 0) { /* must identify */
u8 man_id, chip_id, reg_config1, reg_config2;
u8 reg_alert_status, reg_alert_mask;
man_id = i2c_smbus_read_byte_data(new_client,
LM63_REG_MAN_ID);
chip_id = i2c_smbus_read_byte_data(new_client,
LM63_REG_CHIP_ID);
reg_config1 = i2c_smbus_read_byte_data(new_client,
LM63_REG_CONFIG1);
reg_config2 = i2c_smbus_read_byte_data(new_client,
LM63_REG_CONFIG2);
reg_alert_status = i2c_smbus_read_byte_data(new_client,
LM63_REG_ALERT_STATUS);
reg_alert_mask = i2c_smbus_read_byte_data(new_client,
LM63_REG_ALERT_MASK);
if (man_id == 0x01 /* National Semiconductor */
&& chip_id == 0x41 /* LM63 */
&& (reg_config1 & 0x18) == 0x00
&& (reg_config2 & 0xF8) == 0x00
&& (reg_alert_status & 0x20) == 0x00
&& (reg_alert_mask & 0xA4) == 0xA4) {
kind = lm63;
} else {
#ifdef DEBUG
printk(KERN_DEBUG "lm63: Unsupported chip "
"(man_id=0x%02X, chip_id=0x%02X)\n",
man_id, chip_id);
#endif
goto ERROR1;
}
}
/* Fill in the remaining client fields */
strcpy(new_client->name, "LM63 chip");
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client))) {
printk(KERN_ERR "lm63: Failed attaching client\n");
goto ERROR1;
}
/* Register a new directory entry */
if ((err = i2c_register_entry(new_client, "lm63",
lm63_dir_table_template, THIS_MODULE)) < 0) {
printk(KERN_ERR "lm63: Failed registering directory entry\n");
goto ERROR2;
}
data->sysctl_id = err;
/* Initialize the LM63 chip */
lm63_init_client(new_client);
return 0;
ERROR2:
i2c_detach_client(new_client);
ERROR1:
kfree(data);
return err;
}
/* Idealy we shouldn't have to initialize anything, since the BIOS
should have taken care of everything */
static void lm63_init_client(struct i2c_client *client)
{
struct lm63_data *data = client->data;
data->config = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG1);
data->config_fan = i2c_smbus_read_byte_data(client,
LM63_REG_CONFIG_FAN);
/* Start converting if needed */
if (data->config & 0x40) { /* standby */
#ifdef DEBUG
printk(KERN_DEBUG "lm63: Switching to operational mode");
#endif
data->config &= 0xA7;
i2c_smbus_write_byte_data(client, LM63_REG_CONFIG1,
data->config);
}
/* We may need pwm1_freq before ever updating the client data */
data->pwm1_freq = i2c_smbus_read_byte_data(client, LM63_REG_PWM_FREQ);
if (data->pwm1_freq == 0)
data->pwm1_freq = 1;
#ifdef DEBUG
/* Show some debug info about the LM63 configuration */
printk(KERN_DEBUG "lm63: Alert/tach pin configured for %s\n",
(data->config & 0x04) ? "tachometer input" :
"alert output");
printk(KERN_DEBUG "lm63: PWM clock %s kHz, output frequency %u Hz\n",
(data->config_fan & 0x08) ? "1.4" : "360",
((data->config_fan & 0x08) ? 700 : 180000) / data->pwm1_freq);
printk(KERN_DEBUG "lm63: PWM output active %s, %s mode\n",
(data->config_fan & 0x10) ? "low" : "high",
(data->config_fan & 0x20) ? "manual" : "auto");
#endif
}
static int lm63_detach_client(struct i2c_client *client)
{
struct lm63_data *data = client->data;
int err;
i2c_deregister_entry(data->sysctl_id);
if ((err = i2c_detach_client(client))) {
printk(KERN_ERR "lm63: Client deregistration failed, client "
"not detached.\n");
return err;
}
kfree(data);
return 0;
}
static void lm63_update_client(struct i2c_client *client)
{
struct lm63_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ) ||
(jiffies < data->last_updated) ||
!data->valid) {
if (data->config & 0x04) { /* tachometer enabled */
/* order matters for fan1_input */
data->fan1_input = i2c_smbus_read_byte_data(client,
LM63_REG_TACH_COUNT_LSB) & 0xFC;
data->fan1_input |= i2c_smbus_read_byte_data(client,
LM63_REG_TACH_COUNT_MSB) << 8;
data->fan1_low = (i2c_smbus_read_byte_data(client,
LM63_REG_TACH_LIMIT_LSB) & 0xFC)
| (i2c_smbus_read_byte_data(client,
LM63_REG_TACH_LIMIT_MSB) << 8);
}
data->pwm1_freq = i2c_smbus_read_byte_data(client,
LM63_REG_PWM_FREQ);
if (data->pwm1_freq == 0)
data->pwm1_freq = 1;
data->pwm1_value = i2c_smbus_read_byte_data(client,
LM63_REG_PWM_VALUE);
data->temp1_input = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_TEMP);
data->temp1_high = i2c_smbus_read_byte_data(client,
LM63_REG_LOCAL_HIGH);
/* order matters for temp2_input */
data->temp2_input = i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_TEMP_MSB) << 8;
data->temp2_input |= i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_TEMP_LSB);
data->temp2_high = (i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_HIGH_MSB) << 8)
| i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_HIGH_LSB);
data->temp2_low = (i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_LOW_MSB) << 8)
| i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_LOW_LSB);
data->temp2_crit = i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_TCRIT);
data->temp2_crit_hyst = i2c_smbus_read_byte_data(client,
LM63_REG_REMOTE_TCRIT_HYST);
/* Mask out Busy bit in status register */
data->alarms = i2c_smbus_read_byte_data(client,
LM63_REG_ALERT_STATUS) & 0x7F;
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
static void lm63_local_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = TEMP8_FROM_REG(data->temp1_high);
results[1] = TEMP8_FROM_REG(data->temp1_input);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp1_high = TEMP8_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM63_REG_LOCAL_HIGH,
data->temp1_high);
}
}
}
static void lm63_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = TEMP11_FROM_REG(data->temp2_high);
results[1] = TEMP11_FROM_REG(data->temp2_low);
results[2] = TEMP11_FROM_REG(data->temp2_input);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp2_high = TEMP11_TO_REG(results[0]);
i2c_smbus_write_byte_data(client,
LM63_REG_REMOTE_HIGH_MSB,
data->temp2_high >> 8);
i2c_smbus_write_byte_data(client,
LM63_REG_REMOTE_HIGH_LSB,
data->temp2_high & 0xFF);
}
if (*nrels_mag >= 2) {
data->temp2_low = TEMP11_TO_REG(results[1]);
i2c_smbus_write_byte_data(client,
LM63_REG_REMOTE_LOW_MSB,
data->temp2_low >> 8);
i2c_smbus_write_byte_data(client,
LM63_REG_REMOTE_LOW_LSB,
data->temp2_low & 0xFF);
}
}
}
static void lm63_remote_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = TEMP8_FROM_REG(data->temp2_crit);
*nrels_mag = 1;
}
}
static void lm63_remote_tcrit_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = TEMP8_FROM_REG(data->temp2_crit) -
TEMP8_FROM_REG(data->temp2_crit_hyst);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp2_crit_hyst = HYST_TO_REG(data->temp2_crit -
results[0]);
i2c_smbus_write_byte_data(client,
LM63_REG_REMOTE_TCRIT_HYST,
data->temp2_crit_hyst);
}
}
}
static void lm63_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
static void lm63_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
if (!(data->config & 0x04)) { /* tachometer disabled */
results[0] = 0;
*nrels_mag = 1;
return;
}
lm63_update_client(client);
results[0] = FAN_FROM_REG(data->fan1_low);
results[1] = FAN_FROM_REG(data->fan1_input);
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (!(data->config & 0x04)) /* tachometer disabled */
return;
if (*nrels_mag >= 1) {
data->fan1_low = FAN_TO_REG(results[0]);
i2c_smbus_write_byte_data(client,
LM63_REG_TACH_LIMIT_LSB,
data->fan1_low & 0xFF);
i2c_smbus_write_byte_data(client,
LM63_REG_TACH_LIMIT_MSB,
data->fan1_low >> 8);
}
}
}
static void lm63_pwm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm63_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm63_update_client(client);
results[0] = data->pwm1_value >= 2 * data->pwm1_freq ? 255 :
(data->pwm1_value * 255 + data->pwm1_freq) /
(2 * data->pwm1_freq);
results[1] = data->config_fan & 0x20 ? 1 : 2;
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1 && (data->config_fan & 0x20)) {
data->pwm1_value = results[0] <= 0 ? 0 :
results[0] >= 255 ? 2 * data->pwm1_freq :
(results[0] * data->pwm1_freq * 2 + 127) / 255;
i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE,
data->pwm1_value);
}
}
}
static int __init lm63_init(void)
{
printk(KERN_INFO "lm63 version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&lm63_driver);
}
static void __exit lm63_exit(void)
{
i2c_del_driver(&lm63_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("LM63 sensor driver");
MODULE_LICENSE("GPL");
module_init(lm63_init);
module_exit(lm63_exit);
-330
View File
@@ -1,330 +0,0 @@
/*
lm75.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
#include "lm75.h"
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x48, 0x4f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(lm75);
/* Many LM75 constants specified below */
/* The LM75 registers */
#define LM75_REG_TEMP 0x00
#define LM75_REG_CONF 0x01
#define LM75_REG_TEMP_HYST 0x02
#define LM75_REG_TEMP_OS 0x03
/* Each client has this additional data */
struct lm75_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u16 temp, temp_os, temp_hyst; /* Register values */
};
static int lm75_attach_adapter(struct i2c_adapter *adapter);
static int lm75_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void lm75_init_client(struct i2c_client *client);
static int lm75_detach_client(struct i2c_client *client);
static int lm75_read_value(struct i2c_client *client, u8 reg);
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value);
static void lm75_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm75_update_client(struct i2c_client *client);
/* This is the driver that will be inserted */
static struct i2c_driver lm75_driver = {
.name = "LM75 sensor chip driver",
.id = I2C_DRIVERID_LM75,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm75_attach_adapter,
.detach_client = lm75_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define LM75_SYSCTL_TEMP 1200 /* Degrees Celsius * 10 */
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected LM75. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table lm75_dir_table_template[] = {
{LM75_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm75_temp},
{0}
};
static int lm75_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm75_detect);
}
/* This function is called by i2c_detect */
static int lm75_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct lm75_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("lm75.o: lm75_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA))
goto error0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access lm75_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct lm75_data), GFP_KERNEL))) {
err = -ENOMEM;
goto error0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm75_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. There is no identification-
dedicated register so we have to rely on several tricks:
unused bits, registers cycling over 8-address boundaries,
addresses 0x04-0x07 returning the last read value.
The cycling+unused addresses combination is not tested,
since it would significantly slow the detection down and would
hardly add any value. */
if (kind < 0) {
int cur, conf, hyst, os;
/* Unused addresses */
cur = i2c_smbus_read_word_data(new_client, 0);
conf = i2c_smbus_read_byte_data(new_client, 1);
hyst = i2c_smbus_read_word_data(new_client, 2);
if (i2c_smbus_read_word_data(new_client, 4) != hyst
|| i2c_smbus_read_word_data(new_client, 5) != hyst
|| i2c_smbus_read_word_data(new_client, 6) != hyst
|| i2c_smbus_read_word_data(new_client, 7) != hyst)
goto error1;
os = i2c_smbus_read_word_data(new_client, 3);
if (i2c_smbus_read_word_data(new_client, 4) != os
|| i2c_smbus_read_word_data(new_client, 5) != os
|| i2c_smbus_read_word_data(new_client, 6) != os
|| i2c_smbus_read_word_data(new_client, 7) != os)
goto error1;
/* Unused bits */
if (conf & 0xe0)
goto error1;
/* Addresses cycling */
for (i = 8; i < 0xff; i += 8)
if (i2c_smbus_read_byte_data(new_client, i + 1) != conf
|| i2c_smbus_read_word_data(new_client, i + 2) != hyst
|| i2c_smbus_read_word_data(new_client, i + 3) != os)
goto error1;
}
/* Determine the chip type - only one kind supported! */
if (kind <= 0)
kind = lm75;
if (kind == lm75) {
type_name = "lm75";
client_name = "LM75 chip";
} else {
pr_debug("lm75.o: Internal error: unknown kind (%d)?!?", kind);
goto error1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto error3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
lm75_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto error4;
}
data->sysctl_id = i;
lm75_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
error4:
i2c_detach_client(new_client);
error3:
error1:
kfree(data);
error0:
return err;
}
static int lm75_detach_client(struct i2c_client *client)
{
struct lm75_data *data = client->data;
i2c_deregister_entry(data->sysctl_id);
i2c_detach_client(client);
kfree(client->data);
return 0;
}
/* All registers are word-sized, except for the configuration register.
LM75 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int lm75_read_value(struct i2c_client *client, u8 reg)
{
if (reg == LM75_REG_CONF)
return i2c_smbus_read_byte_data(client, reg);
else
return swab16(i2c_smbus_read_word_data(client, reg));
}
/* All registers are word-sized, except for the configuration register.
LM75 uses a high-byte first convention, which is exactly opposite to
the usual practice. */
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if (reg == LM75_REG_CONF)
return i2c_smbus_write_byte_data(client, reg, value);
else
return i2c_smbus_write_word_data(client, reg, swab16(value));
}
static void lm75_init_client(struct i2c_client *client)
{
int i;
/* Enable if in shutdown mode */
i = lm75_read_value(client, LM75_REG_CONF);
if (i >= 0 && (i & 0x01))
lm75_write_value(client, LM75_REG_CONF, i & 0xfe);
}
static void lm75_update_client(struct i2c_client *client)
{
struct lm75_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
pr_debug("Starting lm75 update\n");
data->temp = lm75_read_value(client, LM75_REG_TEMP);
data->temp_os = lm75_read_value(client, LM75_REG_TEMP_OS);
data->temp_hyst =
lm75_read_value(client, LM75_REG_TEMP_HYST);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
void lm75_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm75_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
lm75_update_client(client);
results[0] = LM75_TEMP_FROM_REG(data->temp_os);
results[1] = LM75_TEMP_FROM_REG(data->temp_hyst);
results[2] = LM75_TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_os = LM75_TEMP_TO_REG(results[0]);
lm75_write_value(client, LM75_REG_TEMP_OS,
data->temp_os);
}
if (*nrels_mag >= 2) {
data->temp_hyst = LM75_TEMP_TO_REG(results[1]);
lm75_write_value(client, LM75_REG_TEMP_HYST,
data->temp_hyst);
}
}
}
static int __init sm_lm75_init(void)
{
printk(KERN_INFO "lm75.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&lm75_driver);
}
static void __exit sm_lm75_exit(void)
{
i2c_del_driver(&lm75_driver);
}
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_DESCRIPTION("LM75 driver");
MODULE_LICENSE("GPL");
module_init(sm_lm75_init);
module_exit(sm_lm75_exit);
-49
View File
@@ -1,49 +0,0 @@
/*
lm75.h - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
This file contains common code for encoding/decoding LM75 type
temperature readings, which are emulated by many of the chips
we support. As the user is unlikely to load more than one driver
which contains this code, we don't worry about the wasted space.
*/
#include <linux/i2c-proc.h>
/* straight from the datasheet */
#define LM75_TEMP_MIN (-550)
#define LM75_TEMP_MAX 1250
/* TEMP: 0.1C/bit (-55C to +125C)
REG: (0.5C/bit, two's complement) << 7 */
static inline u16 LM75_TEMP_TO_REG(int temp)
{
int ntemp = SENSORS_LIMIT(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
ntemp += (ntemp<0 ? -2 : 2);
return (u16)((ntemp / 5) << 7);
}
static inline int LM75_TEMP_FROM_REG(u16 reg)
{
/* use integer division instead of equivalent right shift to
guarantee arithmetic shift and preserve the sign */
return ((s16)reg / 128) * 5;
}
-721
View File
@@ -1,721 +0,0 @@
/*
lm78.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
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/slab.h>
#include <linux/ioport.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include <asm/io.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { 0x0290, SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_3(lm78, lm78j, lm79);
/* Many LM78 constants specified below */
/* Length of ISA address segment */
#define LM78_EXTENT 8
/* Where are the ISA address/data registers relative to the base address */
#define LM78_ADDR_REG_OFFSET 5
#define LM78_DATA_REG_OFFSET 6
/* The LM78 registers */
#define LM78_REG_IN_MAX(nr) (0x2b + (nr) * 2)
#define LM78_REG_IN_MIN(nr) (0x2c + (nr) * 2)
#define LM78_REG_IN(nr) (0x20 + (nr))
#define LM78_REG_FAN_MIN(nr) (0x3a + (nr))
#define LM78_REG_FAN(nr) (0x27 + (nr))
#define LM78_REG_TEMP 0x27
#define LM78_REG_TEMP_OVER 0x39
#define LM78_REG_TEMP_HYST 0x3a
#define LM78_REG_ALARM1 0x41
#define LM78_REG_ALARM2 0x42
#define LM78_REG_VID_FANDIV 0x47
#define LM78_REG_CONFIG 0x40
#define LM78_REG_CHIPID 0x49
#define LM78_REG_I2C_ADDR 0x48
/* Conversions. Limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val) (SENSORS_LIMIT((((val) * 10 + 8)/16),0,255))
#define IN_FROM_REG(val) (((val) * 16 + 5) / 10)
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm <= 0)
return 255;
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) ((val)==0?-1:(val)==255?0:1350000/((val)*(div)))
#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)<0?(((val)-5)/10):\
((val)+5)/10), -128, 127))
#define TEMP_FROM_REG(val) ((val)*10)
#define VID_FROM_REG(val) ((val)==0x1f?0:(val)>=0x10?510-(val)*10:\
205-(val)*5)
#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1)
#define DIV_FROM_REG(val) (1 << (val))
/* There are some complications in a module like this. First off, LM78 chips
may be both present on the SMBus and the ISA bus, and we have to handle
those cases separately at some places. Second, there might be several
LM78 chips available (well, actually, that is probably never done; but
it is a clean illustration of how to handle a case like that). Finally,
a specific chip may be attached to *both* ISA and SMBus, and we would
not like to detect it double. Fortunately, in the case of the LM78 at
least, a register tells us what SMBus address we are on, so that helps
a bit - except if there could be more than one SMBus. Groan. No solution
for this yet. */
/* This module may seem overly long and complicated. In fact, it is not so
bad. Quite a lot of bookkeeping is done. A real driver can often cut
some corners. */
/* For each registered LM78, we need to keep some data in memory. It is
dynamically allocated, at the same time when a new lm78 client is
allocated. */
struct lm78_data {
struct i2c_client client;
struct semaphore lock;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[7]; /* Register value */
u8 in_max[7]; /* Register value */
u8 in_min[7]; /* Register value */
u8 fan[3]; /* Register value */
u8 fan_min[3]; /* Register value */
s8 temp; /* Register value */
s8 temp_over; /* Register value */
s8 temp_hyst; /* Register value */
u8 fan_div[3]; /* Register encoding, shifted right */
u8 vid; /* Register encoding, combined */
u16 alarms; /* Register encoding, combined */
};
static int lm78_attach_adapter(struct i2c_adapter *adapter);
static int lm78_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int lm78_detach_client(struct i2c_client *client);
static int lm78_read_value(struct i2c_client *client, u8 register);
static int lm78_write_value(struct i2c_client *client, u8 register,
u8 value);
static void lm78_update_client(struct i2c_client *client);
static void lm78_init_client(struct i2c_client *client);
static void lm78_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results);
static void lm78_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm78_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm78_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm78_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm78_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver lm78_driver = {
.name = "LM78(-J) and LM79 sensor driver",
.id = I2C_DRIVERID_LM78,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm78_attach_adapter,
.detach_client = lm78_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define LM78_SYSCTL_IN0 1000 /* Volts * 100 */
#define LM78_SYSCTL_IN1 1001
#define LM78_SYSCTL_IN2 1002
#define LM78_SYSCTL_IN3 1003
#define LM78_SYSCTL_IN4 1004
#define LM78_SYSCTL_IN5 1005
#define LM78_SYSCTL_IN6 1006
#define LM78_SYSCTL_FAN1 1101 /* Rotations/min */
#define LM78_SYSCTL_FAN2 1102
#define LM78_SYSCTL_FAN3 1103
#define LM78_SYSCTL_TEMP 1200 /* Degrees Celsius * 10 */
#define LM78_SYSCTL_VID 1300 /* Volts * 100 */
#define LM78_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define LM78_SYSCTL_ALARMS 2001 /* bitvector */
#define LM78_ALARM_IN0 0x0001
#define LM78_ALARM_IN1 0x0002
#define LM78_ALARM_IN2 0x0004
#define LM78_ALARM_IN3 0x0008
#define LM78_ALARM_IN4 0x0100
#define LM78_ALARM_IN5 0x0200
#define LM78_ALARM_IN6 0x0400
#define LM78_ALARM_FAN1 0x0040
#define LM78_ALARM_FAN2 0x0080
#define LM78_ALARM_FAN3 0x0800
#define LM78_ALARM_TEMP 0x0010
#define LM78_ALARM_BTI 0x0020
#define LM78_ALARM_CHAS 0x1000
#define LM78_ALARM_FIFO 0x2000
#define LM78_ALARM_SMI_IN 0x4000
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected LM78. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table lm78_dir_table_template[] = {
{LM78_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_in},
{LM78_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_fan},
{LM78_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_fan},
{LM78_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_fan},
{LM78_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_temp},
{LM78_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_vid},
{LM78_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_fan_div},
{LM78_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm78_alarms},
{0}
};
/* This function is called when:
* lm78_driver is inserted (when this module is loaded), for each
available adapter
* when a new adapter is inserted (and lm78_driver is still present) */
static int lm78_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm78_detect);
}
/* This function is called by i2c_detect */
static int lm78_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct lm78_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
int is_isa = i2c_is_isa_adapter(adapter);
if (!is_isa
&& !i2c_check_functionality(adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) goto
ERROR0;
if (is_isa) {
if (check_region(address, LM78_EXTENT))
goto ERROR0;
}
/* Probe whether there is anything available on this address. Already
done for SMBus clients */
if (kind < 0) {
if (is_isa) {
#define REALLY_SLOW_IO
/* We need the timeouts for at least some LM78-like chips. But only
if we read 'undefined' registers. */
i = inb_p(address + 1);
if (inb_p(address + 2) != i)
goto ERROR0;
if (inb_p(address + 3) != i)
goto ERROR0;
if (inb_p(address + 7) != i)
goto ERROR0;
#undef REALLY_SLOW_IO
/* Let's just hope nothing breaks here */
i = inb_p(address + 5) & 0x7f;
outb_p(~i & 0x7f, address + 5);
if ((inb_p(address + 5) & 0x7f) != (~i & 0x7f)) {
outb_p(i, address + 5);
return 0;
}
}
}
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access lm78_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct lm78_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
if (is_isa)
init_MUTEX(&data->lock);
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm78_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if (lm78_read_value(new_client, LM78_REG_CONFIG) & 0x80)
goto ERROR1;
if (!is_isa
&& (lm78_read_value(new_client, LM78_REG_I2C_ADDR) !=
address)) goto ERROR1;
}
/* Determine the chip type. */
if (kind <= 0) {
i = lm78_read_value(new_client, LM78_REG_CHIPID);
if (i == 0x00 || i == 0x20)
kind = lm78;
else if (i == 0x40)
kind = lm78j;
else if ((i & 0xfe) == 0xc0)
kind = lm79;
else {
if (kind == 0)
printk
("lm78.o: Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
goto ERROR1;
}
}
if (kind == lm78) {
type_name = "lm78";
client_name = "LM78 chip";
} else if (kind == lm78j) {
type_name = "lm78-j";
client_name = "LM78-J chip";
} else if (kind == lm79) {
type_name = "lm79";
client_name = "LM79 chip";
} else {
#ifdef DEBUG
printk("lm78.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Reserve the ISA region */
if (is_isa)
request_region(address, LM78_EXTENT, type_name);
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
lm78_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the LM78 chip */
lm78_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
if (is_isa)
release_region(address, LM78_EXTENT);
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int lm78_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct lm78_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("lm78.o: Client deregistration failed, client not detached.\n");
return err;
}
if(i2c_is_isa_client(client))
release_region(client->addr, LM78_EXTENT);
kfree(client->data);
return 0;
}
/* The SMBus locks itself, but ISA access must be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
would slow down the LM78 access and should not be necessary.
There are some ugly typecasts here, but the good new is - they should
nowhere else be necessary! */
static int lm78_read_value(struct i2c_client *client, u8 reg)
{
int res;
if (i2c_is_isa_client(client)) {
down(&(((struct lm78_data *) (client->data))->lock));
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
res = inb_p(client->addr + LM78_DATA_REG_OFFSET);
up(&(((struct lm78_data *) (client->data))->lock));
return res;
} else
return i2c_smbus_read_byte_data(client, reg);
}
/* The SMBus locks itself, but ISA access muse be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks,
would slow down the LM78 access and should not be necessary.
There are some ugly typecasts here, but the good new is - they should
nowhere else be necessary! */
static int lm78_write_value(struct i2c_client *client, u8 reg, u8 value)
{
if (i2c_is_isa_client(client)) {
down(&(((struct lm78_data *) (client->data))->lock));
outb_p(reg, client->addr + LM78_ADDR_REG_OFFSET);
outb_p(value, client->addr + LM78_DATA_REG_OFFSET);
up(&(((struct lm78_data *) (client->data))->lock));
return 0;
} else
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new LM78. */
static void lm78_init_client(struct i2c_client *client)
{
u8 config = lm78_read_value(client, LM78_REG_CONFIG);
/* Start monitoring */
if (!(config & 0x01))
lm78_write_value(client, LM78_REG_CONFIG,
(config & 0xf7) | 0x01);
}
static void lm78_update_client(struct i2c_client *client)
{
struct lm78_data *data = client->data;
int i;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ + HZ / 2) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting lm78 update\n");
#endif
for (i = 0; i <= 6; i++) {
data->in[i] =
lm78_read_value(client, LM78_REG_IN(i));
data->in_min[i] =
lm78_read_value(client, LM78_REG_IN_MIN(i));
data->in_max[i] =
lm78_read_value(client, LM78_REG_IN_MAX(i));
}
for (i = 1; i <= 3; i++) {
data->fan[i - 1] =
lm78_read_value(client, LM78_REG_FAN(i));
data->fan_min[i - 1] =
lm78_read_value(client, LM78_REG_FAN_MIN(i));
}
data->temp = lm78_read_value(client, LM78_REG_TEMP);
data->temp_over =
lm78_read_value(client, LM78_REG_TEMP_OVER);
data->temp_hyst =
lm78_read_value(client, LM78_REG_TEMP_HYST);
i = lm78_read_value(client, LM78_REG_VID_FANDIV);
data->vid = i & 0x0f;
if (data->type == lm79)
data->vid |=
(lm78_read_value(client, LM78_REG_CHIPID) &
0x01) << 4;
else
data->vid |= 0x10;
data->fan_div[0] = (i >> 4) & 0x03;
data->fan_div[1] = i >> 6;
data->alarms = lm78_read_value(client, LM78_REG_ALARM1) +
(lm78_read_value(client, LM78_REG_ALARM2) << 8);
data->last_updated = jiffies;
data->valid = 1;
data->fan_div[2] = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void lm78_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
int nr = ctl_name - LM78_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = IN_FROM_REG(data->in_min[nr]);
results[1] = IN_FROM_REG(data->in_max[nr]);
results[2] = IN_FROM_REG(data->in[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] = IN_TO_REG(results[0]);
lm78_write_value(client, LM78_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] = IN_TO_REG(results[1]);
lm78_write_value(client, LM78_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
void lm78_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
int nr = ctl_name - LM78_SYSCTL_FAN1 + 1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr - 1],
DIV_FROM_REG(data->
fan_div[nr - 1]));
results[1] =
FAN_FROM_REG(data->fan[nr - 1],
DIV_FROM_REG(data->fan_div[nr - 1]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr - 1] = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->
fan_div[nr -
1]));
lm78_write_value(client, LM78_REG_FAN_MIN(nr),
data->fan_min[nr - 1]);
}
}
}
void lm78_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_over);
results[1] = TEMP_FROM_REG(data->temp_hyst);
results[2] = TEMP_FROM_REG(data->temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_over = TEMP_TO_REG(results[0]);
lm78_write_value(client, LM78_REG_TEMP_OVER,
data->temp_over);
}
if (*nrels_mag >= 2) {
data->temp_hyst = TEMP_TO_REG(results[1]);
lm78_write_value(client, LM78_REG_TEMP_HYST,
data->temp_hyst);
}
}
}
void lm78_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = VID_FROM_REG(data->vid);
*nrels_mag = 1;
}
}
void lm78_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
/* Note: we save and restore the fan minimum here, because its value is
determined in part by the fan divisor. This follows the principle of
least surprise: the user doesn't expect the fan minimum to change just
because the divisor changed. */
void lm78_fan_div(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm78_data *data = client->data;
int old, min;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm78_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
results[2] = 2;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = lm78_read_value(client, LM78_REG_VID_FANDIV);
if (*nrels_mag >= 2) {
min = FAN_FROM_REG(data->fan_min[1],
DIV_FROM_REG(data->fan_div[1]));
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0x3f) | (data->fan_div[1] << 6);
data->fan_min[1] = FAN_TO_REG(min,
DIV_FROM_REG(data->fan_div[1]));
lm78_write_value(client, LM78_REG_FAN_MIN(2),
data->fan_min[1]);
}
if (*nrels_mag >= 1) {
min = FAN_FROM_REG(data->fan_min[0],
DIV_FROM_REG(data->fan_div[0]));
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0xcf) | (data->fan_div[0] << 4);
data->fan_min[0] = FAN_TO_REG(min,
DIV_FROM_REG(data->fan_div[0]));
lm78_write_value(client, LM78_REG_FAN_MIN(1),
data->fan_min[0]);
lm78_write_value(client, LM78_REG_VID_FANDIV, old);
}
}
}
static int __init sm_lm78_init(void)
{
printk("lm78.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&lm78_driver);
}
static void __exit sm_lm78_exit(void)
{
i2c_del_driver(&lm78_driver);
}
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
MODULE_DESCRIPTION("LM78, LM78-J and LM79 driver");
module_init(sm_lm78_init);
module_exit(sm_lm78_exit);
-599
View File
@@ -1,599 +0,0 @@
/*
lm80.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 1998, 1999 Frodo Looijaard <frodol@dds.nl>
and Philip Edelbrock <phil@netroedge.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
MODULE_LICENSE("GPL");
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x20, 0x2f, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(lm80);
/* Many LM80 constants specified below */
/* The LM80 registers */
#define LM80_REG_IN_MAX(nr) (0x2a + (nr) * 2)
#define LM80_REG_IN_MIN(nr) (0x2b + (nr) * 2)
#define LM80_REG_IN(nr) (0x20 + (nr))
#define LM80_REG_FAN1_MIN 0x3c
#define LM80_REG_FAN2_MIN 0x3d
#define LM80_REG_FAN1 0x28
#define LM80_REG_FAN2 0x29
#define LM80_REG_TEMP 0x27
#define LM80_REG_TEMP_HOT_MAX 0x38
#define LM80_REG_TEMP_HOT_HYST 0x39
#define LM80_REG_TEMP_OS_MAX 0x3a
#define LM80_REG_TEMP_OS_HYST 0x3b
#define LM80_REG_CONFIG 0x00
#define LM80_REG_ALARM1 0x01
#define LM80_REG_ALARM2 0x02
#define LM80_REG_MASK1 0x03
#define LM80_REG_MASK2 0x04
#define LM80_REG_FANDIV 0x05
#define LM80_REG_RES 0x06
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
#define IN_TO_REG(val) (SENSORS_LIMIT((val),0,255))
#define IN_FROM_REG(val) (val)
static inline unsigned char FAN_TO_REG(unsigned rpm, unsigned div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) ((val)==0?-1:\
(val)==255?0:1350000/((div)*(val)))
static inline long TEMP_FROM_REG(u16 temp)
{
long res;
temp >>= 4;
if (temp < 0x0800)
res = 625 * (long) temp;
else
res = ((long) temp - 0x01000) * 625;
return res / 100;
}
#define TEMP_LIMIT_FROM_REG(val) (((val)>0x80?(val)-0x100:(val))*100)
#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(((val)-50)/100):\
((val)+50)/100), \
0,255)
#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val)==8?3:(val)==4?2:(val)==1?0:1)
struct lm80_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[7]; /* Register value */
u8 in_max[7]; /* Register value */
u8 in_min[7]; /* Register value */
u8 fan[2]; /* Register value */
u8 fan_min[2]; /* Register value */
u8 fan_div[2]; /* Register encoding, shifted right */
u16 temp; /* Register values, shifted right */
u8 temp_hot_max; /* Register value */
u8 temp_hot_hyst; /* Register value */
u8 temp_os_max; /* Register value */
u8 temp_os_hyst; /* Register value */
u16 alarms; /* Register encoding, combined */
};
static int lm80_attach_adapter(struct i2c_adapter *adapter);
static int lm80_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int lm80_detach_client(struct i2c_client *client);
static int lm80_read_value(struct i2c_client *client, u8 reg);
static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value);
static void lm80_update_client(struct i2c_client *client);
static void lm80_init_client(struct i2c_client *client);
static void lm80_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results);
static void lm80_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm80_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm80_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm80_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver lm80_driver = {
.name = "LM80 sensor driver",
.id = I2C_DRIVERID_LM80,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm80_attach_adapter,
.detach_client = lm80_detach_client,
};
/* The /proc/sys entries */
/* -- SENSORS SYSCTL START -- */
#define LM80_SYSCTL_IN0 1000 /* Volts * 100 */
#define LM80_SYSCTL_IN1 1001
#define LM80_SYSCTL_IN2 1002
#define LM80_SYSCTL_IN3 1003
#define LM80_SYSCTL_IN4 1004
#define LM80_SYSCTL_IN5 1005
#define LM80_SYSCTL_IN6 1006
#define LM80_SYSCTL_FAN1 1101 /* Rotations/min */
#define LM80_SYSCTL_FAN2 1102
#define LM80_SYSCTL_TEMP 1250 /* Degrees Celsius * 100 */
#define LM80_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define LM80_SYSCTL_ALARMS 2001 /* bitvector */
#define LM80_ALARM_IN0 0x0001
#define LM80_ALARM_IN1 0x0002
#define LM80_ALARM_IN2 0x0004
#define LM80_ALARM_IN3 0x0008
#define LM80_ALARM_IN4 0x0010
#define LM80_ALARM_IN5 0x0020
#define LM80_ALARM_IN6 0x0040
#define LM80_ALARM_FAN1 0x0400
#define LM80_ALARM_FAN2 0x0800
#define LM80_ALARM_TEMP_HOT 0x0100
#define LM80_ALARM_TEMP_OS 0x2000
#define LM80_ALARM_CHAS 0x1000
#define LM80_ALARM_BTI 0x0200
#define LM80_ALARM_INT_IN 0x0080
/* -- SENSORS SYSCTL END -- */
/* These files are created for each detected LM80. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table lm80_dir_table_template[] = {
{LM80_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_in},
{LM80_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_fan},
{LM80_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_fan},
{LM80_SYSCTL_TEMP, "temp", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_temp},
{LM80_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_fan_div},
{LM80_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm80_alarms},
{0}
};
static int lm80_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm80_detect);
}
static int lm80_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i, cur;
struct i2c_client *new_client;
struct lm80_data *data;
int err = 0;
const char *type_name, *client_name;
/* Make sure we aren't probing the ISA bus!! This is just a safety check
at this moment; i2c_detect really won't call us. */
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter)) {
printk
("lm80.o: lm80_detect called for an ISA bus adapter?!?\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access lm80_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct lm80_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm80_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. It is lousy. */
if (lm80_read_value(new_client, LM80_REG_ALARM2) & 0xc0)
goto ERROR1;
for (i = 0x2a; i <= 0x3d; i++) {
cur = i2c_smbus_read_byte_data(new_client, i);
if ((i2c_smbus_read_byte_data(new_client, i + 0x40) != cur)
|| (i2c_smbus_read_byte_data(new_client, i + 0x80) !=
cur)
|| (i2c_smbus_read_byte_data(new_client, i + 0xc0) !=
cur)) goto ERROR1;
}
/* Determine the chip type - only one kind supported! */
if (kind <= 0)
kind = lm80;
if (kind == lm80) {
type_name = "lm80";
client_name = "LM80 chip";
} else {
#ifdef DEBUG
printk("lm80.o: Internal error: unknown kind (%d)?!?",
kind);
#endif
goto ERROR1;
}
/* Fill in the remaining client fields and put it into the global list */
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client, type_name,
lm80_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
lm80_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int lm80_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct lm80_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk
("lm80.o: Client deregistration failed, client not detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static int lm80_read_value(struct i2c_client *client, u8 reg)
{
return i2c_smbus_read_byte_data(client, reg);
}
static int lm80_write_value(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new LM80. */
static void lm80_init_client(struct i2c_client *client)
{
/* Reset all except Watchdog values and last conversion values
This sets fan-divs to 2, among others. This makes most other
initializations unnecessary */
lm80_write_value(client, LM80_REG_CONFIG, 0x80);
/* Set 11-bit temperature resolution */
lm80_write_value(client, LM80_REG_RES, 0x08);
/* Start monitoring */
lm80_write_value(client, LM80_REG_CONFIG, 0x01);
}
static void lm80_update_client(struct i2c_client *client)
{
struct lm80_data *data = client->data;
int i;
down(&data->update_lock);
if ((jiffies - data->last_updated > 2 * HZ) ||
(jiffies < data->last_updated) || !data->valid) {
#ifdef DEBUG
printk("Starting lm80 update\n");
#endif
for (i = 0; i <= 6; i++) {
data->in[i] =
lm80_read_value(client, LM80_REG_IN(i));
data->in_min[i] =
lm80_read_value(client, LM80_REG_IN_MIN(i));
data->in_max[i] =
lm80_read_value(client, LM80_REG_IN_MAX(i));
}
data->fan[0] = lm80_read_value(client, LM80_REG_FAN1);
data->fan_min[0] =
lm80_read_value(client, LM80_REG_FAN1_MIN);
data->fan[1] = lm80_read_value(client, LM80_REG_FAN2);
data->fan_min[1] =
lm80_read_value(client, LM80_REG_FAN2_MIN);
data->temp =
(lm80_read_value(client, LM80_REG_TEMP) << 8) |
(lm80_read_value(client, LM80_REG_RES) & 0xf0);
data->temp_os_max =
lm80_read_value(client, LM80_REG_TEMP_OS_MAX);
data->temp_os_hyst =
lm80_read_value(client, LM80_REG_TEMP_OS_HYST);
data->temp_hot_max =
lm80_read_value(client, LM80_REG_TEMP_HOT_MAX);
data->temp_hot_hyst =
lm80_read_value(client, LM80_REG_TEMP_HOT_HYST);
i = lm80_read_value(client, LM80_REG_FANDIV);
data->fan_div[0] = (i >> 2) & 0x03;
data->fan_div[1] = (i >> 4) & 0x03;
data->alarms = lm80_read_value(client, LM80_REG_ALARM1) +
(lm80_read_value(client, LM80_REG_ALARM2) << 8);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void lm80_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm80_data *data = client->data;
int nr = ctl_name - LM80_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
lm80_update_client(client);
results[0] = IN_FROM_REG(data->in_min[nr]);
results[1] = IN_FROM_REG(data->in_max[nr]);
results[2] = IN_FROM_REG(data->in[nr]);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] = IN_TO_REG(results[0]);
lm80_write_value(client, LM80_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] = IN_TO_REG(results[1]);
lm80_write_value(client, LM80_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
void lm80_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm80_data *data = client->data;
int nr = ctl_name - LM80_SYSCTL_FAN1 + 1;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm80_update_client(client);
results[0] = FAN_FROM_REG(data->fan_min[nr - 1],
DIV_FROM_REG(data->
fan_div[nr - 1]));
results[1] =
FAN_FROM_REG(data->fan[nr - 1],
DIV_FROM_REG(data->fan_div[nr - 1]));
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->fan_min[nr - 1] = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->
fan_div[nr -
1]));
lm80_write_value(client,
nr ==
1 ? LM80_REG_FAN1_MIN :
LM80_REG_FAN2_MIN,
data->fan_min[nr - 1]);
}
}
}
void lm80_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm80_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
lm80_update_client(client);
results[0] = TEMP_LIMIT_FROM_REG(data->temp_hot_max);
results[1] = TEMP_LIMIT_FROM_REG(data->temp_hot_hyst);
results[2] = TEMP_LIMIT_FROM_REG(data->temp_os_max);
results[3] = TEMP_LIMIT_FROM_REG(data->temp_os_hyst);
results[4] = TEMP_FROM_REG(data->temp);
*nrels_mag = 5;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->temp_hot_max = TEMP_LIMIT_TO_REG(results[0]);
lm80_write_value(client, LM80_REG_TEMP_HOT_MAX,
data->temp_hot_max);
}
if (*nrels_mag >= 2) {
data->temp_hot_hyst =
TEMP_LIMIT_TO_REG(results[1]);
lm80_write_value(client, LM80_REG_TEMP_HOT_HYST,
data->temp_hot_hyst);
}
if (*nrels_mag >= 3) {
data->temp_os_max = TEMP_LIMIT_TO_REG(results[2]);
lm80_write_value(client, LM80_REG_TEMP_OS_MAX,
data->temp_os_max);
}
if (*nrels_mag >= 4) {
data->temp_os_hyst = TEMP_LIMIT_TO_REG(results[3]);
lm80_write_value(client, LM80_REG_TEMP_OS_HYST,
data->temp_os_hyst);
}
}
}
void lm80_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm80_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm80_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
void lm80_fan_div(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm80_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm80_update_client(client);
results[0] = DIV_FROM_REG(data->fan_div[0]);
results[1] = DIV_FROM_REG(data->fan_div[1]);
results[2] = 2;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = lm80_read_value(client, LM80_REG_FANDIV);
if (*nrels_mag >= 2) {
data->fan_div[1] = DIV_TO_REG(results[1]);
old = (old & 0xcf) | (data->fan_div[1] << 4);
}
if (*nrels_mag >= 1) {
data->fan_div[0] = DIV_TO_REG(results[0]);
old = (old & 0xf3) | (data->fan_div[0] << 2);
lm80_write_value(client, LM80_REG_FANDIV, old);
}
}
}
static int __init sm_lm80_init(void)
{
printk("lm80.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&lm80_driver);
}
static void __exit sm_lm80_exit(void)
{
i2c_del_driver(&lm80_driver);
}
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl> and Philip Edelbrock <phil@netroedge.com>");
MODULE_DESCRIPTION("LM80 driver");
module_init(sm_lm80_init);
module_exit(sm_lm80_exit);
-528
View File
@@ -1,528 +0,0 @@
/*
* lm83.c - Part of lm_sensors, Linux kernel modules for hardware
* monitoring
* Copyright (C) 2003 Jean Delvare <khali@linux-fr.org>
*
* Heavily inspired from the lm78, lm75 and adm1021 drivers. The LM83 is
* a sensor chip made by National Semiconductor. It reports up to four
* temperatures (its own plus up to three external ones) with a 1 deg
* resolution and a 3-4 deg accuracy. Complete datasheet can be obtained
* from National's website at:
* http://www.national.com/pf/LM/LM83.html
* Since the datasheet omits to give the chip stepping code, I give it
* here: 0x03 (at register 0xff).
*
* Also supports the LM82 temp sensor, which is basically a stripped down
* model of the LM83. Datasheet is here:
* http://www.national.com/pf/LM/LM82.html
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
/*
* Addresses to scan
* Address is selected using 2 three-level pins, resulting in 9 possible
* addresses.
*/
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x18, 0x1a, 0x29, 0x2b,
0x4c, 0x4e, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/*
* Insmod parameters
*/
SENSORS_INSMOD_2(lm83, lm82);
/*
* The LM83 registers
* Manufacturer ID is 0x01 for National Semiconductor.
*/
#define LM83_REG_R_MAN_ID 0xFE
#define LM83_REG_R_CHIP_ID 0xFF
#define LM83_REG_R_CONFIG 0x03
#define LM83_REG_W_CONFIG 0x09
#define LM83_REG_R_STATUS1 0x02
#define LM83_REG_R_STATUS2 0x35
#define LM83_REG_R_LOCAL_TEMP 0x00
#define LM83_REG_R_LOCAL_HIGH 0x05
#define LM83_REG_W_LOCAL_HIGH 0x0B
#define LM83_REG_R_REMOTE1_TEMP 0x30
#define LM83_REG_R_REMOTE1_HIGH 0x38
#define LM83_REG_W_REMOTE1_HIGH 0x50
#define LM83_REG_R_REMOTE2_TEMP 0x01
#define LM83_REG_R_REMOTE2_HIGH 0x07
#define LM83_REG_W_REMOTE2_HIGH 0x0D
#define LM83_REG_R_REMOTE3_TEMP 0x31
#define LM83_REG_R_REMOTE3_HIGH 0x3A
#define LM83_REG_W_REMOTE3_HIGH 0x52
#define LM83_REG_R_TCRIT 0x42
#define LM83_REG_W_TCRIT 0x5A
/*
* Conversions and various macros
* The LM83 uses signed 8-bit values with LSB = 1 degree Celsius.
*/
#define TEMP_FROM_REG(val) (val)
#define TEMP_TO_REG(val) ((val) <= -50 ? -50 : \
(val) >= 127 ? 127 : (val))
static const u8 LM83_REG_R_TEMP[] = {
LM83_REG_R_LOCAL_TEMP,
LM83_REG_R_REMOTE1_TEMP,
LM83_REG_R_REMOTE2_TEMP,
LM83_REG_R_REMOTE3_TEMP
};
static const u8 LM83_REG_R_HIGH[] = {
LM83_REG_R_LOCAL_HIGH,
LM83_REG_R_REMOTE1_HIGH,
LM83_REG_R_REMOTE2_HIGH,
LM83_REG_R_REMOTE3_HIGH
};
static const u8 LM83_REG_W_HIGH[] = {
LM83_REG_W_LOCAL_HIGH,
LM83_REG_W_REMOTE1_HIGH,
LM83_REG_W_REMOTE2_HIGH,
LM83_REG_W_REMOTE3_HIGH
};
/*
* Functions declaration
*/
static int lm83_attach_adapter(struct i2c_adapter *adapter);
static int lm83_detect(struct i2c_adapter *adapter, int address, unsigned
short flags, int kind);
static int lm83_detach_client(struct i2c_client *client);
static void lm83_update_client(struct i2c_client *client);
static void lm83_temp(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results);
static void lm83_tcrit(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results);
static void lm83_alarms(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results);
/*
* Driver data (common to all clients)
*/
static struct i2c_driver lm83_driver = {
.name = "LM83 sensor driver",
.id = I2C_DRIVERID_LM83,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm83_attach_adapter,
.detach_client = lm83_detach_client,
};
/*
* Client data (each client gets its own)
*/
struct lm83_data
{
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
/* registers values */
s8 temp[4], temp_high[4], tcrit;
u16 alarms; /* bitvector, combined */
};
/*
* Proc entries
* These files are created for each detected LM83.
*/
/* -- SENSORS SYSCTL START -- */
#define LM83_SYSCTL_LOCAL_TEMP 1200
#define LM83_SYSCTL_REMOTE1_TEMP 1201
#define LM83_SYSCTL_REMOTE2_TEMP 1202
#define LM83_SYSCTL_REMOTE3_TEMP 1203
#define LM83_SYSCTL_TCRIT 1208
#define LM83_SYSCTL_ALARMS 1210
#define LM83_ALARM_LOCAL_HIGH 0x0040
#define LM83_ALARM_LOCAL_CRIT 0x0001
#define LM83_ALARM_REMOTE1_HIGH 0x8000
#define LM83_ALARM_REMOTE1_CRIT 0x0100
#define LM83_ALARM_REMOTE1_OPEN 0x2000
#define LM83_ALARM_REMOTE2_HIGH 0x0010
#define LM83_ALARM_REMOTE2_CRIT 0x0002
#define LM83_ALARM_REMOTE2_OPEN 0x0004
#define LM83_ALARM_REMOTE3_HIGH 0x1000
#define LM83_ALARM_REMOTE3_CRIT 0x0200
#define LM83_ALARM_REMOTE3_OPEN 0x0400
/* -- SENSORS SYSCTL END -- */
static ctl_table lm83_dir_table_template[] =
{
{LM83_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_REMOTE1_TEMP, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_REMOTE2_TEMP, "temp3", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_REMOTE3_TEMP, "temp4", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_TCRIT, "tcrit", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_tcrit},
{LM83_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_alarms},
{0}
};
static ctl_table lm82_dir_table_template[] =
{
{LM83_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_REMOTE2_TEMP, "temp3", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_temp},
{LM83_SYSCTL_TCRIT, "tcrit", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_tcrit},
{LM83_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm83_alarms},
{0}
};
/*
* Real code
*/
static int lm83_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm83_detect);
}
/*
* The following function does more than just detection. If detection
* succeeds, it also registers the new chip.
*/
static int lm83_detect(struct i2c_adapter *adapter, int address, unsigned
short flags, int kind)
{
struct i2c_client *new_client;
struct lm83_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
#ifdef DEBUG
if (i2c_is_isa_adapter(adapter))
{
printk("lm83.o: Called for an ISA bus adapter, aborting.\n");
return 0;
}
#endif
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
{
#ifdef DEBUG
printk("lm83.o: I2C bus doesn't support byte read mode, "
"skipping.\n");
#endif
return 0;
}
if (!(data = kmalloc(sizeof(struct lm83_data), GFP_KERNEL)))
{
printk("lm83.o: Out of memory in lm83_detect (new_client).\n");
return -ENOMEM;
}
/*
* The common I2C client data is placed right before the
* LM83-specific data. The LM83-specific data is pointed to by the
* data field from the I2C client data.
*/
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm83_driver;
new_client->flags = 0;
/*
* Now we do the remaining detection. A negative kind means that
* the driver was loaded with no force parameter (default), so we
* must both detect and identify the chip (actually there is only
* one possible kind of chip for now, LM83). A zero kind means that
* the driver was loaded with the force parameter, the detection
* step shall be skipped. A positive kind means that the driver
* was loaded with the force parameter and a given kind of chip is
* requested, so both the detection and the identification steps
* are skipped.
*/
/* Default to an LM83 if forced */
if (kind == 0)
kind = lm83;
if (kind < 0) /* detection */
{
if (((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS1)
& 0xA8) != 0x00)
|| ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_STATUS2)
& 0x48) != 0x00)
|| ((i2c_smbus_read_byte_data(new_client, LM83_REG_R_CONFIG)
& 0x41) != 0x00))
{
#ifdef DEBUG
printk(KERN_DEBUG "lm83.o: Detection failed at 0x%02x.\n",
address);
#endif
goto ERROR1;
}
}
if (kind <= 0) /* identification */
{
u8 man_id, chip_id;
man_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_MAN_ID);
chip_id = i2c_smbus_read_byte_data(new_client, LM83_REG_R_CHIP_ID);
if (man_id == 0x01) /* National Semiconductor */
{
if (chip_id == 0x03)
kind = lm83;
else if (chip_id == 0x01)
kind = lm82;
}
}
if (kind <= 0) /* identification failed */
{
printk("lm83.o: Unsupported chip.\n");
goto ERROR1;
}
if (kind == lm83)
{
type_name = "lm83";
client_name = "LM83 chip";
}
else if (kind == lm82)
{
type_name = "lm82";
client_name = "LM82 chip";
}
else
{
printk("lm83.o: Unknown kind %d.\n", kind);
goto ERROR1;
}
/*
* OK, we got a valid chip so we can fill in the remaining client
* fields.
*/
strcpy(new_client->name, client_name);
data->valid = 0;
init_MUTEX(&data->update_lock);
/*
* Tell the I2C layer a new client has arrived.
*/
if ((err = i2c_attach_client(new_client)))
{
#ifdef DEBUG
printk("lm83.o: Failed attaching client.\n");
#endif
goto ERROR1;
}
/*
* Register a new directory entry.
*/
if ((kind == lm83 && (err = i2c_register_entry(new_client, type_name,
lm83_dir_table_template, THIS_MODULE)) < 0) ||
(kind == lm82 && (err = i2c_register_entry(new_client, type_name,
lm82_dir_table_template, THIS_MODULE)) < 0))
{
#ifdef DEBUG
printk("lm83.o: Failed registering directory entry.\n");
#endif
goto ERROR2;
}
data->sysctl_id = err;
/*
* Initialize the LM83 chip
* (Nothing to do for this one.)
*/
return 0;
ERROR2:
i2c_detach_client(new_client);
ERROR1:
kfree(data);
return err;
}
static int lm83_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct lm83_data *) (client->data))->sysctl_id);
if ((err = i2c_detach_client(client)))
{
printk("lm83.o: Client deregistration failed, client not "
"detached.\n");
return err;
}
kfree(client->data);
return 0;
}
static void lm83_update_client(struct i2c_client *client)
{
struct lm83_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ * 2) ||
(jiffies < data->last_updated) || !data->valid)
{
int nr;
#ifdef DEBUG
printk("lm83.o: Updating LM83 data.\n");
#endif
for (nr = 0; nr < 4 ; nr++)
{
data->temp[nr] =
i2c_smbus_read_byte_data(client, LM83_REG_R_TEMP[nr]);
data->temp_high[nr] =
i2c_smbus_read_byte_data(client, LM83_REG_R_HIGH[nr]);
}
data->tcrit = i2c_smbus_read_byte_data(client, LM83_REG_R_TCRIT);
data->alarms =
i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS1) +
(i2c_smbus_read_byte_data(client, LM83_REG_R_STATUS2) << 8);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
static void lm83_temp(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results)
{
struct lm83_data *data = client->data;
int nr = ctl_name - LM83_SYSCTL_LOCAL_TEMP;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ)
{
lm83_update_client(client);
results[0] = TEMP_FROM_REG(data->temp_high[nr]);
results[1] = TEMP_FROM_REG(data->temp[nr]);
*nrels_mag = 2;
}
else if (operation == SENSORS_PROC_REAL_WRITE)
{
if (*nrels_mag >= 1)
{
data->temp_high[nr] = TEMP_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM83_REG_W_HIGH[nr],
data->temp_high[nr]);
}
}
}
static void lm83_tcrit(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results)
{
struct lm83_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ)
{
lm83_update_client(client);
results[0] = TEMP_FROM_REG(data->tcrit);
*nrels_mag = 1;
}
else if (operation == SENSORS_PROC_REAL_WRITE)
{
if (*nrels_mag >= 1)
{
data->tcrit = TEMP_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM83_REG_W_TCRIT,
data->tcrit);
}
}
}
static void lm83_alarms(struct i2c_client *client, int operation, int
ctl_name, int *nrels_mag, long *results)
{
struct lm83_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ)
{
lm83_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
static int __init sm_lm83_init(void)
{
printk(KERN_INFO "lm83.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&lm83_driver);
}
static void __exit sm_lm83_exit(void)
{
i2c_del_driver(&lm83_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("LM83 sensor driver");
MODULE_LICENSE("GPL");
module_init(sm_lm83_init);
module_exit(sm_lm83_exit);
-2037
View File
File diff suppressed because it is too large. Load diff
-982
View File
@@ -1,982 +0,0 @@
/*
LM87.c - Part of lm_sensors, Linux kernel modules for hardware
monitoring
Copyright (c) 2000 Frodo Looijaard <frodol@dds.nl>
Philip Edelbrock <phil@netroedge.com>
Stephen Rousset <stephen.rousset@rocketlogix.com>
Dan Eaton <dan.eaton@rocketlogix.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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include <linux/delay.h>
#include "version.h"
#include "sensors_vid.h"
/* Chip configuration settings. These should be set to reflect the
HARDWARE configuration of your chip. By default (read: when all of
these are left commented out), this driver assumes that the
configuration is the same as National's defaults for the Channel Mode
register.
Set to '1' the appropriate defines, as nessesary:
- External temp sensors 2 (possible second CPU temp)
This will disable the 2.5V and Vccp2 readings.
Ironically, National decided that you can read the
temperature of a second CPU or it's core voltage,
but not both! Comment out if FAULT is reported. */
/* #define LM87_EXT2 1 */
/* Aux analog input. When enabled, the Fan 1 reading
will be disabled */
/* #define LM87_AIN1 1 */
/* Aux analog input 2. When enabled, the Fan 2 reading
will be disabled */
/* #define LM87_AIN2 1 */
/* Internal Vcc is 5V instead of 3.3V */
/* #define LM87_5V_VCC 1 */
/* That's the end of the hardware config defines. I would have made
them insmod params, but it would be too much work. ;') */
/* Addresses to scan */
static unsigned short normal_i2c[] = { SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { 0x2c, 0x2e, SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/* Insmod parameters */
SENSORS_INSMOD_1(lm87);
/* The following is the calculation for the register offset
* for the monitored items minimum and maximum locations.
*/
#define LM87_REG_IN_MAX(nr) (0x2b + ((nr) * 2))
#define LM87_REG_IN_MIN(nr) (0x2c + ((nr) * 2))
#define LM87_REG_IN(nr) (0x20 + (nr))
/* Initial limits */
/*
* LM87 register definition
*
*/
/* The LM87 registers */
#define LM87_INT_TEMP_HI_LIMIT_LOCKABLE 0x13
#define LM87_EXT_TEMP_HI_LIMIT_LOCKABLE 0x14
#define LM87_REG_TEST 0x15
#define LM87_REG_CHANNEL_MODE 0x16
#define LM87_REG_INT_TEMP_HI_LIMIT 0x17
#define LM87_REG_EXT_TEMP_HI_LIMIT 0x18
#define LM87_REG_ANALOG_OUT 0x19
/* These are all read-only */
#define LM87_REG_2_5V_EXT_TEMP_2 0x20
#define LM87_REG_VCCP1 0x21
#define LM87_REG_3_3V 0x22
#define LM87_REG_5V 0x23
#define LM87_REG_12V 0x24
#define LM87_REG_VCCP2 0x25
#define LM87_REG_EXT_TEMP_1 0x26
#define LM87_REG_INT_TEMP 0x27 /* LM87 temp. */
#define LM87_REG_FAN1_AIN1 0x28
#define LM87_REG_FAN2_AIN2 0x29
/* These are read/write */
#define LM87_REG_AIN1_LOW 0x1A
#define LM87_REG_AIN2_LOW 0x1B
#define LM87_REG_2_5V_EXT_TEMP_2_HIGH 0x2B
#define LM87_REG_2_5V_EXT_TEMP_2_LOW 0x2C
#define LM87_REG_VCCP1_HIGH 0x2D
#define LM87_REG_VCCP1_LOW 0x2E
#define LM87_REG_3_3V_HIGH 0x2F
#define LM87_REG_3_3V_LOW 0x30
#define LM87_REG_5V_HIGH 0x31
#define LM87_REG_5V_LOW 0x32
#define LM87_REG_12V_HIGH 0x33
#define LM87_REG_12V_LOW 0x34
#define LM87_REG_VCCP2_HIGH 0x35
#define LM87_REG_VCCP2_LOW 0x36
#define LM87_REG_EXT_TEMP_1_HIGH 0x37
#define LM87_REG_EXT_TEMP_1_LOW 0x38
#define LM87_REG_INT_TEMP_HIGH 0x39
#define LM87_REG_INT_TEMP_LOW 0x3A
#define LM87_REG_FAN1_AIN1_LIMIT 0x3B
#define LM87_REG_FAN2_AIN2_LIMIT 0x3C
#define LM87_REG_COMPANY_ID 0x3E
#define LM87_REG_DIE_REV 0x3F
#define LM87_REG_CONFIG 0x40
#define LM87_REG_INT1_STAT 0x41
#define LM87_REG_INT2_STAT 0x42
#define LM87_REG_INT1_MASK 0x43
#define LM87_REG_INT2_MASK 0x44
#define LM87_REG_CHASSIS_CLEAR 0x46
#define LM87_REG_VID_FAN_DIV 0x47
#define LM87_REG_VID4 0x49
#define LM87_REG_CONFIG_2 0x4A
#define LM87_REG_INTRPT_STATUS_1_MIRROR 0x4C
#define LM87_REG_INTRPT_STATUS_2_MIRROR 0x4D
#define LM87_REG_SMBALERT_NUM_ENABLE 0x80
/* Conversions. Rounding and limit checking is only done on the TO_REG
variants. Note that you should be a bit careful with which arguments
these macros are called: arguments may be evaluated more than once.
Fixing this is just not worth it. */
static inline u8 FAN_TO_REG(long rpm, int div)
{
if (rpm == 0)
return 255;
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1,
254);
}
#define FAN_FROM_REG(val,div) ((val)==0?-1:\
(val)==255?0:1350000/((div)*(val)))
#define TEMP_FROM_REG(val) ((val)*10)
#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT(((val)<0?(val)-5:\
(val)+5)/10,-128,127)
#define DIV_FROM_REG(val) (1 << (val))
#define DIV_TO_REG(val) ((val)==1?0:((val)==8?3:((val)==4?2:1)))
/* For each registered LM87, we need to keep some data in memory. The
structure is dynamically allocated whenever a new LM87 client is
found. */
struct lm87_data {
struct i2c_client client;
int sysctl_id;
enum chips type;
struct semaphore update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
u8 in[6]; /* Scaled Register value */
u8 in_max[6]; /* Scaled Register value */
u8 in_min[6]; /* Scaled Register value */
u8 ain1; /* Register value */
u8 ain1_min; /* Register value */
u8 ain1_max; /* Register value */
u8 ain2; /* Register value */
u8 ain2_min; /* Register value */
u8 ain2_max; /* Register value */
u8 fan; /* Register value */
u8 fan_min; /* Register value */
u8 fan_div; /* Register encoding, shifted right */
u8 fan2; /* Register value */
u8 fan2_min; /* Register value */
u8 fan2_div; /* Register encoding, shifted right */
s8 ext2_temp; /* Register value */
s8 ext_temp; /* Register value */
s8 int_temp; /* Register value */
u8 ext_temp_max; /* Register value */
u8 ext_temp_min; /* Register value */
u8 ext2_temp_max; /* Register value */
u8 ext2_temp_min; /* Register value */
u8 int_temp_max; /* Register value */
u8 int_temp_min; /* Register value */
u16 alarms; /* Register encoding, combined */
u8 analog_out; /* Register value */
u8 vid; /* Register value combined */
u8 vrm; /* VRM version * 10 */
};
static int lm87_attach_adapter(struct i2c_adapter *adapter);
static int lm87_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static int lm87_detach_client(struct i2c_client *client);
static int lm87_read_value(struct i2c_client *client, u8 register);
static int lm87_write_value(struct i2c_client *client, u8 register,
u8 value);
static void lm87_update_client(struct i2c_client *client);
static void lm87_init_client(struct i2c_client *client);
static void lm87_in(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
#if defined (LM87_AIN1) || defined (LM87_AIN2)
static void lm87_ain(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
#endif
static void lm87_fan(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm87_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm87_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm87_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm87_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag,
long *results);
static void lm87_vid(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm87_vrm(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static struct i2c_driver LM87_driver = {
.name = "LM87 sensor driver",
.id = I2C_DRIVERID_LM87,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm87_attach_adapter,
.detach_client = lm87_detach_client,
};
/* -- SENSORS SYSCTL START -- */
#define LM87_SYSCTL_IN0 1000 /* Volts * 100 */
#define LM87_SYSCTL_IN1 1001
#define LM87_SYSCTL_IN2 1002
#define LM87_SYSCTL_IN3 1003
#define LM87_SYSCTL_IN4 1004
#define LM87_SYSCTL_IN5 1005
#define LM87_SYSCTL_AIN1 1006
#define LM87_SYSCTL_AIN2 1007
#define LM87_SYSCTL_FAN1 1102
#define LM87_SYSCTL_FAN2 1103
#define LM87_SYSCTL_TEMP1 1250 /* Degrees Celsius * 10 */
#define LM87_SYSCTL_TEMP2 1251 /* Degrees Celsius * 10 */
#define LM87_SYSCTL_TEMP3 1252 /* Degrees Celsius * 10 */
#define LM87_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
#define LM87_SYSCTL_ALARMS 2001 /* bitvector */
#define LM87_SYSCTL_ANALOG_OUT 2002
#define LM87_SYSCTL_VID 2003
#define LM87_SYSCTL_VRM 2004
#define LM87_ALARM_IN0 0x0001
#define LM87_ALARM_IN1 0x0002
#define LM87_ALARM_IN2 0x0004
#define LM87_ALARM_IN3 0x0008
#define LM87_ALARM_TEMP1 0x0010
#define LM87_ALARM_TEMP2 0x0020
#define LM87_ALARM_TEMP3 0x0020 /* same?? */
#define LM87_ALARM_FAN1 0x0040
#define LM87_ALARM_FAN2 0x0080
#define LM87_ALARM_IN4 0x0100
#define LM87_ALARM_IN5 0x0200
#define LM87_ALARM_RESERVED1 0x0400
#define LM87_ALARM_RESERVED2 0x0800
#define LM87_ALARM_CHAS 0x1000
#define LM87_ALARM_THERM_SIG 0x2000
#define LM87_ALARM_TEMP2_FAULT 0x4000
#define LM87_ALARM_TEMP3_FAULT 0x08000
/* -- SENSORS SYSCTL END -- */
/* The /proc/sys entries */
/* These files are created for each detected LM87. This is just a template;
though at first sight, you might think we could use a statically
allocated list, we need some way to get back to the parent - which
is done through one of the 'extra' fields which are initialized
when a new copy is allocated. */
static ctl_table LM87_dir_table_template[] = {
#ifdef LM87_AIN1
{LM87_SYSCTL_AIN1, "in6", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_ain},
#endif
#ifdef LM87_AIN2
{LM87_SYSCTL_AIN2, "in7", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_ain},
#endif
#ifndef LM87_EXT2
{LM87_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
{LM87_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
#endif
{LM87_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
{LM87_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
{LM87_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
{LM87_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_in},
#ifndef LM87_AIN1
{LM87_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_fan},
{LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_fan_div},
#define LM87_FANDIV_FLAG
#endif
#ifndef LM87_AIN2
{LM87_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_fan},
#ifndef LM87_FANDIV_FLAG
{LM87_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_fan_div},
#endif /* LM87_FANDIV_FLAG */
#endif /* LM87_AIN2 */
#ifdef LM87_EXT2
{LM87_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_temp},
#endif
{LM87_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_temp},
{LM87_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_temp},
{LM87_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_alarms},
{LM87_SYSCTL_ANALOG_OUT, "analog_out", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_analog_out},
{LM87_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_vid},
{LM87_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real,
&i2c_sysctl_real, NULL, &lm87_vrm},
{0}
};
static int lm87_attach_adapter(struct i2c_adapter *adapter)
{
int error;
struct i2c_client_address_data lm87_client_data;
lm87_client_data.normal_i2c = addr_data.normal_i2c;
lm87_client_data.normal_i2c_range = addr_data.normal_i2c_range;
lm87_client_data.probe = addr_data.probe;
lm87_client_data.probe_range = addr_data.probe_range;
lm87_client_data.ignore = addr_data.ignore;
lm87_client_data.ignore_range = addr_data.ignore_range;
lm87_client_data.force = addr_data.forces->force;
error = i2c_probe(adapter, &lm87_client_data, lm87_detect);
i2c_detect(adapter, &addr_data, lm87_detect);
return error;
}
static int lm87_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
int i;
struct i2c_client *new_client;
struct lm87_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access LM87_{read,write}_value. */
if (!(data = kmalloc(sizeof(struct lm87_data), GFP_KERNEL))) {
err = -ENOMEM;
goto ERROR0;
}
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &LM87_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */
if (kind < 0) {
if (((lm87_read_value(new_client, LM87_REG_CONFIG) & 0x80)
!= 0x00) ||
(lm87_read_value(new_client, LM87_REG_COMPANY_ID) != 0x02))
goto ERROR1;
}
/* Fill in the remaining client fields and put into the global list */
type_name = "lm87";
client_name = "LM87 chip";
strcpy(new_client->name, client_name);
data->type = kind;
data->valid = 0;
init_MUTEX(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client)))
goto ERROR3;
/* Register a new directory entry with module sensors */
if ((i = i2c_register_entry(new_client,
type_name,
LM87_dir_table_template,
THIS_MODULE)) < 0) {
err = i;
goto ERROR4;
}
data->sysctl_id = i;
/* Initialize the LM87 chip */
lm87_init_client(new_client);
return 0;
/* OK, this is not exactly good programming practice, usually. But it is
very code-efficient in this case. */
ERROR4:
i2c_detach_client(new_client);
ERROR3:
ERROR1:
kfree(data);
ERROR0:
return err;
}
static int lm87_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct lm87_data *) (client->data))->
sysctl_id);
if ((err = i2c_detach_client(client))) {
printk(KERN_ERR "lm87.o: Client deregistration failed, "
"client not detached\n");
return err;
}
kfree(client->data);
return 0;
}
#define MAX_RETRIES 5
static int lm87_read_value(struct i2c_client *client, u8 reg)
{
int value, i;
/* Retry in case of read errors */
for (i = 1; i <= MAX_RETRIES; i++) {
if ((value = i2c_smbus_read_byte_data(client, reg)) >= 0)
return value;
printk(KERN_WARNING "lm87.o: Read byte data failed, "
"address 0x%02x\n", reg);
mdelay(i + 3);
}
/* <TODO> what to return in case of error? */
printk(KERN_ERR "lm87.o: All read byte retries failed!!\n");
return 0;
}
static int lm87_write_value(struct i2c_client *client, u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(client, reg, value);
}
/* Called when we have found a new LM87. */
static void lm87_init_client(struct i2c_client *client)
{
struct lm87_data *data = client->data;
u8 reg;
/* Setup Channel Mode register for configuration of monitoring
* Default is 00000000b
* bit 0 - Configures Fan 1/AIN 1 input (1 = AIN)
* bit 1 - Configures Fan 2/AIN 2 input (1 = AIN)
* bit 2 - Configures 2.5V&Vccp2/D2 input (1 = 2nd Therm.)
* bit 3 - Configures Vcc for 5V/3.3V reading (0 = 3.3V)
* bit 4 - Configures IRQ0 Enable if = 1
* bit 5 - Configures IRQ1 Enable if = 1
* bit 6 - Configures IRQ2 Enable if = 1
* bit 7 - Configures VID/IRQ input as interrupts if = 1
*/
/* Preserve 4 MSB */
reg = lm87_read_value(client, LM87_REG_CHANNEL_MODE);
/* I know, not clean, but it works. :'p */
lm87_write_value(client, LM87_REG_CHANNEL_MODE, (reg & 0xf0) |
#ifdef LM87_AIN1
0x01
#else
0
#endif
|
#ifdef LM87_AIN2
0x02
#else
0
#endif
|
#ifdef LM87_EXT2
0x04
#else
0
#endif
|
#ifdef LM87_5V_VCC
0x08
#else
0
#endif
);
data->vrm = 82;
/* Start monitoring */
reg = lm87_read_value(client, LM87_REG_CONFIG);
if (!(reg & 0x01)) {
printk(KERN_INFO "lm87.o: Monitoring starts\n");
lm87_write_value(client, LM87_REG_CONFIG,
(reg & 0x7e) | 0x01);
}
}
static void lm87_update_client(struct i2c_client *client)
{
struct lm87_data *data = client->data;
int i;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ) || /* 1 sec cache */
(jiffies < data->last_updated) ||
!data->valid) {
for (i = 0; i <= 5; i++) {
data->in[i] =
lm87_read_value(client,LM87_REG_IN(i));
data->in_min[i] =
lm87_read_value(client,LM87_REG_IN_MIN(i));
data->in_max[i] =
lm87_read_value(client,LM87_REG_IN_MAX(i));
}
data->ain1 =
lm87_read_value(client,LM87_REG_FAN1_AIN1);
data->ain1_min =
lm87_read_value(client,LM87_REG_AIN1_LOW);
data->ain1_max =
lm87_read_value(client,LM87_REG_FAN1_AIN1_LIMIT);
data->ain2 =
lm87_read_value(client,LM87_REG_FAN2_AIN2);
data->ain2_min =
lm87_read_value(client,LM87_REG_AIN2_LOW);
data->ain2_max =
lm87_read_value(client,LM87_REG_FAN2_AIN2_LIMIT);
data->fan =
lm87_read_value(client, LM87_REG_FAN1_AIN1);
data->fan_min =
lm87_read_value(client, LM87_REG_FAN1_AIN1_LIMIT);
data->fan2 =
lm87_read_value(client, LM87_REG_FAN2_AIN2);
data->fan2_min =
lm87_read_value(client, LM87_REG_FAN2_AIN2_LIMIT);
data->ext2_temp =
lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2);
data->ext_temp =
lm87_read_value(client, LM87_REG_EXT_TEMP_1);
data->int_temp =
lm87_read_value(client, LM87_REG_INT_TEMP);
data->ext2_temp_max =
lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2_HIGH);
data->ext2_temp_min =
lm87_read_value(client, LM87_REG_2_5V_EXT_TEMP_2_LOW);
data->ext_temp_max =
lm87_read_value(client, LM87_REG_EXT_TEMP_1_HIGH);
data->ext_temp_min =
lm87_read_value(client, LM87_REG_EXT_TEMP_1_LOW);
data->int_temp_max =
lm87_read_value(client, LM87_REG_INT_TEMP_HIGH);
data->int_temp_min =
lm87_read_value(client, LM87_REG_INT_TEMP_LOW);
i = lm87_read_value(client, LM87_REG_VID_FAN_DIV);
data->fan_div = (i >> 4) & 0x03;
data->fan2_div = (i >> 6) & 0x03;
data->vid = i & 0x0f;
data->vid |=
(lm87_read_value(client, LM87_REG_VID4) & 0x01)
<< 4;
data->alarms =
lm87_read_value(client, LM87_REG_INT1_STAT) +
(lm87_read_value(client, LM87_REG_INT2_STAT) << 8);
data->analog_out =
lm87_read_value(client, LM87_REG_ANALOG_OUT);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
/* The next few functions are the call-back functions of the /proc/sys and
sysctl files. Which function is used is defined in the ctl_table in
the extra1 field.
Each function must return the magnitude (power of 10 to divide the date
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
put a maximum of *nrels elements in results reflecting the data of this
file, and set *nrels to the number it actually put in it, if operation==
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
large enough (by checking the incoming value of *nrels). This is not very
good practice, but as long as you put less than about 5 values in results,
you can assume it is large enough. */
void lm87_in(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
long scales[6] = { 250, 270,
#ifdef LM87_5V_VCC
500,
#else
330,
#endif
500, 1200, 270 };
struct lm87_data *data = client->data;
int nr = ctl_name - LM87_SYSCTL_IN0;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 2;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
results[0] = (data->in_min[nr] * scales[nr] + 96) / 192;
results[1] = (data->in_max[nr] * scales[nr] + 96) / 192;
results[2] = (data->in[nr] * scales[nr] + 96) / 192;
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->in_min[nr] = (results[0] * 192 + scales[nr] / 2)
/ scales[nr];
lm87_write_value(client, LM87_REG_IN_MIN(nr),
data->in_min[nr]);
}
if (*nrels_mag >= 2) {
data->in_max[nr] = (results[1] * 192 + scales[nr] / 2)
/ scales[nr];
lm87_write_value(client, LM87_REG_IN_MAX(nr),
data->in_max[nr]);
}
}
}
#if defined (LM87_AIN1) || defined (LM87_AIN2)
void lm87_ain(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
if (ctl_name == LM87_SYSCTL_AIN1) {
results[0] = data->ain1_min;
results[1] = data->ain1_max;
results[2] = data->ain1;
} else {
results[0] = data->ain2_min;
results[1] = data->ain2_max;
results[2] = data->ain2;
}
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (ctl_name == LM87_SYSCTL_AIN1) {
data->ain1_min = results[0];
lm87_write_value(client, LM87_REG_AIN1_LOW,
data->ain1_min);
} else {
data->ain2_min = results[0];
lm87_write_value(client, LM87_REG_AIN2_LOW,
data->ain2_min);
}
}
if (*nrels_mag >= 2) {
if (ctl_name == LM87_SYSCTL_AIN1) {
data->ain1_max = results[1];
lm87_write_value(client, LM87_REG_FAN1_AIN1_LIMIT,
data->ain1_max);
} else {
data->ain2_max = results[1];
lm87_write_value(client, LM87_REG_FAN2_AIN2_LIMIT,
data->ain2_max);
}
}
}
}
#endif
void lm87_fan(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
if (ctl_name == LM87_SYSCTL_FAN1) {
results[0] = FAN_FROM_REG(data->fan_min,
DIV_FROM_REG(data->fan_div));
results[1] = FAN_FROM_REG(data->fan,
DIV_FROM_REG(data->fan_div));
} else {
results[0] = FAN_FROM_REG(data->fan2_min,
DIV_FROM_REG(data->fan2_div));
results[1] = FAN_FROM_REG(data->fan2,
DIV_FROM_REG(data->fan2_div));
}
*nrels_mag = 2;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 0) {
if (ctl_name == LM87_SYSCTL_FAN1) {
data->fan_min = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->fan_div));
lm87_write_value(client, LM87_REG_FAN1_AIN1_LIMIT,
data->fan_min);
} else {
data->fan2_min = FAN_TO_REG(results[0],
DIV_FROM_REG
(data->fan2_div));
lm87_write_value(client, LM87_REG_FAN2_AIN2_LIMIT,
data->fan2_min);
}
}
}
}
void lm87_temp(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ)
{
lm87_update_client(client);
/* find out which temp. is being requested */
if (ctl_name == LM87_SYSCTL_TEMP3)
{
results[0] = TEMP_FROM_REG(data->ext2_temp_max);
results[1] = TEMP_FROM_REG(data->ext2_temp_min);
results[2] = TEMP_FROM_REG(data->ext2_temp);
}
else if(ctl_name == LM87_SYSCTL_TEMP2)
{
results[0] = TEMP_FROM_REG(data->ext_temp_max);
results[1] = TEMP_FROM_REG(data->ext_temp_min);
results[2] = TEMP_FROM_REG(data->ext_temp);
}
else if(ctl_name == LM87_SYSCTL_TEMP1)
{
results[0] = TEMP_FROM_REG(data->int_temp_max);
results[1] = TEMP_FROM_REG(data->int_temp_min);
results[2] = TEMP_FROM_REG(data->int_temp);
}
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (ctl_name == LM87_SYSCTL_TEMP3) {
data->ext2_temp_max = TEMP_LIMIT_TO_REG(results[0]);
lm87_write_value(client, LM87_REG_2_5V_EXT_TEMP_2_HIGH,
data->ext2_temp_max);
}
if (ctl_name == LM87_SYSCTL_TEMP2) {
data->ext_temp_max = TEMP_LIMIT_TO_REG(results[0]);
lm87_write_value(client, LM87_REG_EXT_TEMP_1_HIGH,
data->ext_temp_max);
}
if (ctl_name == LM87_SYSCTL_TEMP1) {
data->int_temp_max = TEMP_LIMIT_TO_REG(results[0]);
lm87_write_value(client, LM87_REG_INT_TEMP_HIGH,
data->int_temp_max);
}
}
if (*nrels_mag >= 2) {
if (ctl_name == LM87_SYSCTL_TEMP3) {
data->ext2_temp_min = TEMP_LIMIT_TO_REG(results[1]);
lm87_write_value(client, LM87_REG_2_5V_EXT_TEMP_2_LOW,
data->ext2_temp_min);
}
if (ctl_name == LM87_SYSCTL_TEMP2) {
data->ext_temp_min = TEMP_LIMIT_TO_REG(results[1]);
lm87_write_value(client, LM87_REG_EXT_TEMP_1_LOW,
data->ext_temp_min);
}
if (ctl_name == LM87_SYSCTL_TEMP1) {
data->int_temp_min = TEMP_LIMIT_TO_REG(results[1]);
lm87_write_value(client, LM87_REG_INT_TEMP_LOW,
data->int_temp_min);
}
}
}
}
void lm87_alarms(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
void lm87_fan_div(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
/* This gets a little hairy depending on the hardware config */
struct lm87_data *data = client->data;
int old;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
#ifndef LM87_AIN1
results[0] = DIV_FROM_REG(data->fan_div);
# ifndef LM87_AIN2
results[1] = DIV_FROM_REG(data->fan2_div);
*nrels_mag = 2;
# else
*nrels_mag = 1;
# endif
#else /* Must be referring to fan 2 */
results[0] = DIV_FROM_REG(data->fan2_div);
*nrels_mag = 1;
#endif
} else if (operation == SENSORS_PROC_REAL_WRITE) {
old = lm87_read_value(client, LM87_REG_VID_FAN_DIV);
/* Note: it's OK to change fan2 div even if fan2 isn't enabled */
#ifndef LM87_AIN1
if (*nrels_mag >= 2) {
data->fan2_div = DIV_TO_REG(results[1]);
old = (old & 0x3f) | (data->fan2_div << 6);
}
if (*nrels_mag >= 1) {
data->fan_div = DIV_TO_REG(results[0]);
old = (old & 0xcf) | (data->fan_div << 4);
lm87_write_value(client, LM87_REG_VID_FAN_DIV, old);
}
#else /* Must be referring to fan 2 */
if (*nrels_mag >= 1) {
data->fan2_div = DIV_TO_REG(results[0]);
old = (old & 0xcf) | (data->fan2_div << 6);
lm87_write_value(client, LM87_REG_VID_FAN_DIV, old);
}
#endif
}
}
void lm87_analog_out(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
results[0] = data->analog_out;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->analog_out = results[0];
lm87_write_value(client, LM87_REG_ANALOG_OUT,
data->analog_out);
}
}
}
void lm87_vid(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 3;
else if (operation == SENSORS_PROC_REAL_READ) {
lm87_update_client(client);
results[0] = vid_from_reg(data->vid, data->vrm);
*nrels_mag = 1;
}
}
void lm87_vrm(struct i2c_client *client, int operation, int ctl_name,
int *nrels_mag, long *results)
{
struct lm87_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1;
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = data->vrm;
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1)
data->vrm = results[0];
}
}
static int __init sm_lm87_init(void)
{
printk(KERN_INFO "lm87.o version %s (%s)\n", LM_VERSION, LM_DATE);
return i2c_add_driver(&LM87_driver);
}
static void __exit sm_lm87_exit(void)
{
i2c_del_driver(&LM87_driver);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR
("Frodo Looijaard <frodol@dds.nl>, Philip Edelbrock <phil@netroedge.com>, "
"Mark Studebaker <mdsxyz123@yahoo.com>, and Stephen Rousset <stephen.rousset@rocketlogix.com>");
MODULE_DESCRIPTION("LM87 driver");
module_init(sm_lm87_init);
module_exit(sm_lm87_exit);
-858
View File
@@ -1,858 +0,0 @@
/*
* lm90.c - Part of lm_sensors, Linux kernel modules for hardware
* monitoring
* Copyright (C) 2003-2005 Jean Delvare <khali@linux-fr.org>
*
* Based on the lm83 driver. The LM90 is a sensor chip made by National
* Semiconductor. It reports up to two temperatures (its own plus up to
* one external one) with a 0.125 deg resolution (1 deg for local
* temperature) and a 3-4 deg accuracy. Complete datasheet can be
* obtained from National's website at:
* http://www.national.com/pf/LM/LM90.html
*
* This driver also supports the LM89 and LM99, two other sensor chips
* made by National Semiconductor. Both have an increased remote
* temperature measurement accuracy (1 degree), and the LM99
* additionally shifts remote temperatures (measured and limits) by 16
* degrees, which allows for higher temperatures measurement. The
* driver doesn't handle it since it can be done easily in user-space.
* Complete datasheets can be obtained from National's website at:
* http://www.national.com/pf/LM/LM89.html
* http://www.national.com/pf/LM/LM99.html
* Note that there is no way to differentiate between both chips.
*
* This driver also supports the LM86, another sensor chip made by
* National Semiconductor. It is exactly similar to the LM90 except it
* has a higher accuracy.
* Complete datasheet can be obtained from National's website at:
* http://www.national.com/pf/LM/LM86.html
*
* This driver also supports the ADM1032, a sensor chip made by Analog
* Devices. That chip is similar to the LM90, with a few differences
* that are not handled by this driver. Complete datasheet can be
* obtained from Analog's website at:
* http://products.analog.com/products/info.asp?product=ADM1032
* Among others, it has a higher accuracy than the LM90, much like the
* LM86 does.
*
* This driver also supports the MAX6657, MAX6658 and MAX6659 sensor
* chips made by Maxim. These chips are similar to the LM86. Complete
* datasheet can be obtained at Maxim's website at:
* http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2578
* Note that there is no easy way to differentiate between the three
* variants. The extra address and features of the MAX6659 are not
* supported by this driver.
*
* This driver also supports the ADT7461 chip from Analog Devices but
* only in its "compatability mode". If an ADT7461 chip is found but
* is configured in non-compatible mode (where its temperature
* register values are decoded differently) it is ignored by this
* driver. Complete datasheet can be obtained from Analog's website
* at:
* http://products.analog.com/products/info.asp?product=ADT7461
*
* Since the LM90 was the first chipset supported by this driver, most
* comments will refer to this chipset, but are actually general and
* concern all supported chipsets, unless mentioned otherwise.
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/i2c-proc.h>
#include <linux/init.h>
#include "version.h"
#ifndef I2C_DRIVERID_LM90
#define I2C_DRIVERID_LM90 1042
#endif
/*
* Addresses to scan
* Address is fully defined internally and cannot be changed except for
* MAX6659.
* LM86, LM89, LM90, LM99, ADM1032, ADM1032-1, ADT7461, MAX6657 and MAX6658
* have address 0x4c.
* ADM1032-2, ADT7461-2, LM89-1, and LM99-1 have address 0x4d.
* MAX6659 can have address 0x4c, 0x4d or 0x4e (unsupported).
*/
static unsigned short normal_i2c[] = { 0x4c, 0x4d, SENSORS_I2C_END };
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
static unsigned int normal_isa[] = { SENSORS_ISA_END };
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
/*
* Insmod parameters
*/
SENSORS_INSMOD_6(lm90, adm1032, lm99, lm86, max6657, adt7461);
/*
* The LM90 registers
*/
#define LM90_REG_R_MAN_ID 0xFE
#define LM90_REG_R_CHIP_ID 0xFF
#define LM90_REG_R_CONFIG1 0x03
#define LM90_REG_W_CONFIG1 0x09
#define LM90_REG_R_CONFIG2 0xBF
#define LM90_REG_W_CONFIG2 0xBF
#define LM90_REG_R_CONVRATE 0x04
#define LM90_REG_W_CONVRATE 0x0A
#define LM90_REG_R_STATUS 0x02
#define LM90_REG_R_LOCAL_TEMP 0x00
#define LM90_REG_R_LOCAL_HIGH 0x05
#define LM90_REG_W_LOCAL_HIGH 0x0B
#define LM90_REG_R_LOCAL_LOW 0x06
#define LM90_REG_W_LOCAL_LOW 0x0C
#define LM90_REG_R_LOCAL_CRIT 0x20
#define LM90_REG_W_LOCAL_CRIT 0x20
#define LM90_REG_R_REMOTE_TEMPH 0x01
#define LM90_REG_R_REMOTE_TEMPL 0x10
#define LM90_REG_R_REMOTE_OFFSH 0x11
#define LM90_REG_W_REMOTE_OFFSH 0x11
#define LM90_REG_R_REMOTE_OFFSL 0x12
#define LM90_REG_W_REMOTE_OFFSL 0x12
#define LM90_REG_R_REMOTE_HIGHH 0x07
#define LM90_REG_W_REMOTE_HIGHH 0x0D
#define LM90_REG_R_REMOTE_HIGHL 0x13
#define LM90_REG_W_REMOTE_HIGHL 0x13
#define LM90_REG_R_REMOTE_LOWH 0x08
#define LM90_REG_W_REMOTE_LOWH 0x0E
#define LM90_REG_R_REMOTE_LOWL 0x14
#define LM90_REG_W_REMOTE_LOWL 0x14
#define LM90_REG_R_REMOTE_CRIT 0x19
#define LM90_REG_W_REMOTE_CRIT 0x19
#define LM90_REG_R_TCRIT_HYST 0x21
#define LM90_REG_W_TCRIT_HYST 0x21
/*
* Conversions and various macros
* For local temperatures and limits, critical limits and the hysteresis
* value, the LM90 uses signed 8-bit values with LSB = 1 degree Celsius.
* For remote temperatures and limits, it uses signed 11-bit values with
* LSB = 0.125 degree Celsius, left-justified in 16-bit registers.
*/
#define TEMP1_FROM_REG(val) (val)
#define TEMP1_TO_REG(val) ((val) <= -128 ? -128 : \
(val) >= 127 ? 127 : (val))
#define TEMP2_FROM_REG(val) ((val) / 32 * 125 / 100)
#define TEMP2_TO_REG(val) ((val) <= -1280 ? 0x8000 : \
(val) >= 1270 ? 0x7FE0 : \
((val) * 100 / 125 * 32))
#define HYST_TO_REG(val) ((val) <= 0 ? 0 : \
(val) >= 31 ? 31 : (val))
/*
* ADT7461 is almost identical to LM90 except that attempts to write
* values that are outside the range 0 < temp < 127 are treated as
* the boundary value.
*/
#define TEMP1_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \
(val) >= 127 ? 127 : (val))
#define TEMP2_TO_REG_ADT7461(val) ((val) <= 0 ? 0 : \
(val) >= 1277 ? 0x7FC0 : \
((val) * 100 / 250 * 64))
/*
* Functions declaration
*/
static int lm90_attach_adapter(struct i2c_adapter *adapter);
static int lm90_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind);
static void lm90_init_client(struct i2c_client *client);
static int lm90_detach_client(struct i2c_client *client);
static void lm90_local_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_local_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_remote_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_local_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_remote_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void lm90_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
static void adm1032_pec(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results);
/*
* Driver data (common to all clients)
*/
static struct i2c_driver lm90_driver = {
.name = "LM90/ADM1032 sensor driver",
.id = I2C_DRIVERID_LM90,
.flags = I2C_DF_NOTIFY,
.attach_adapter = lm90_attach_adapter,
.detach_client = lm90_detach_client
};
/*
* Client data (each client gets its own)
*/
struct lm90_data {
struct i2c_client client;
int sysctl_id;
struct semaphore update_lock;
char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */
int kind;
/* registers values */
s8 local_temp, local_high, local_low;
s16 remote_temp, remote_high, remote_low; /* combined */
s8 local_crit, remote_crit;
u8 hyst; /* linked to two sysctl files (hyst1 RW, hyst2 RO) */
u8 alarms; /* bitvector */
};
/*
* Proc entries
* These files are created for each detected LM90.
*/
/* -- SENSORS SYSCTL START -- */
#define LM90_SYSCTL_LOCAL_TEMP 1200
#define LM90_SYSCTL_REMOTE_TEMP 1201
#define LM90_SYSCTL_LOCAL_TCRIT 1204
#define LM90_SYSCTL_REMOTE_TCRIT 1205
#define LM90_SYSCTL_LOCAL_HYST 1207
#define LM90_SYSCTL_REMOTE_HYST 1208
#define LM90_SYSCTL_ALARMS 1210
#define LM90_SYSCTL_PEC 1214
#define LM90_ALARM_LOCAL_HIGH 0x40
#define LM90_ALARM_LOCAL_LOW 0x20
#define LM90_ALARM_LOCAL_CRIT 0x01
#define LM90_ALARM_REMOTE_HIGH 0x10
#define LM90_ALARM_REMOTE_LOW 0x08
#define LM90_ALARM_REMOTE_CRIT 0x02
#define LM90_ALARM_REMOTE_OPEN 0x04
/* -- SENSORS SYSCTL END -- */
static ctl_table lm90_dir_table_template[] =
{
{LM90_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_temp},
{LM90_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_temp},
{LM90_SYSCTL_LOCAL_TCRIT, "tcrit1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_tcrit},
{LM90_SYSCTL_REMOTE_TCRIT, "tcrit2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_tcrit},
{LM90_SYSCTL_LOCAL_HYST, "hyst1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_hyst},
{LM90_SYSCTL_REMOTE_HYST, "hyst2", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_hyst},
{LM90_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_alarms},
{0}
};
static ctl_table adm1032_dir_table_template[] =
{
{LM90_SYSCTL_LOCAL_TEMP, "temp1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_temp},
{LM90_SYSCTL_REMOTE_TEMP, "temp2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_temp},
{LM90_SYSCTL_LOCAL_TCRIT, "tcrit1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_tcrit},
{LM90_SYSCTL_REMOTE_TCRIT, "tcrit2", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_tcrit},
{LM90_SYSCTL_LOCAL_HYST, "hyst1", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_local_hyst},
{LM90_SYSCTL_REMOTE_HYST, "hyst2", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_remote_hyst},
{LM90_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &lm90_alarms},
{LM90_SYSCTL_PEC, "pec", NULL, 0, 0644, NULL,
&i2c_proc_real, &i2c_sysctl_real, NULL, &adm1032_pec},
{0}
};
/*
* Real code
*/
/* The ADM1032 supports PEC but not on write byte transactions, so we need
to explicitly ask for a transaction without PEC. */
static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value)
{
return i2c_smbus_xfer(client->adapter, client->addr,
client->flags & ~I2C_CLIENT_PEC,
I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
}
/* It is assumed that client->update_lock is held (unless we are in
detection or initialization steps). This matters when PEC is enabled,
because we don't want the address pointer to change between the write
byte and the read byte transactions. */
static int lm90_read_reg(struct i2c_client* client, u8 reg, u8 *value)
{
int err;
if (client->flags & I2C_CLIENT_PEC) {
err = adm1032_write_byte(client, reg);
if (err >= 0)
err = i2c_smbus_read_byte(client);
} else
err = i2c_smbus_read_byte_data(client, reg);
if (err < 0) {
printk(KERN_WARNING "lm90: Register 0x%02x read failed (%d)\n",
reg, err);
return err;
}
*value = err;
return 0;
}
static int lm90_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data, lm90_detect);
}
/*
* The following function does more than just detection. If detection
* succeeds, it also registers the new chip.
*/
static int lm90_detect(struct i2c_adapter *adapter, int address,
unsigned short flags, int kind)
{
struct i2c_client *new_client;
struct lm90_data *data;
int err = 0;
const char *type_name = "";
const char *client_name = "";
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
#ifdef DEBUG
printk(KERN_DEBUG "lm90: adapter doesn't support byte mode, "
"skipping\n");
#endif
return 0;
}
if (!(data = kmalloc(sizeof(struct lm90_data), GFP_KERNEL))) {
printk(KERN_ERR "lm90: Out of memory in lm90_detect\n");
return -ENOMEM;
}
/*
* The common I2C client data is placed right before the
* LM90-specific data. The LM90-specific data is pointed to by the
* data field from the I2C client data.
*/
new_client = &data->client;
new_client->addr = address;
new_client->data = data;
new_client->adapter = adapter;
new_client->driver = &lm90_driver;
new_client->flags = 0;
/*
* Now we do the remaining detection. A negative kind means that
* the driver was loaded with no force parameter (default), so we
* must both detect and identify the chip. A zero kind means that
* the driver was loaded with the force parameter, the detection
* step shall be skipped. A positive kind means that the driver
* was loaded with the force parameter and a given kind of chip is
* requested, so both the detection and the identification steps
* are skipped.
*/
/* Default to an LM90 if forced */
if (kind == 0)
kind = lm90;
if (kind < 0) { /* detection and identification */
u8 man_id, chip_id, reg_config1, reg_convrate;
if (lm90_read_reg(new_client, LM90_REG_R_MAN_ID,
&man_id) < 0
|| lm90_read_reg(new_client, LM90_REG_R_CHIP_ID,
&chip_id) < 0
|| lm90_read_reg(new_client, LM90_REG_R_CONFIG1,
&reg_config1) < 0
|| lm90_read_reg(new_client, LM90_REG_R_CONVRATE,
&reg_convrate) < 0)
goto exit_free;
if (man_id == 0x01) { /* National Semiconductor */
u8 reg_config2;
if (lm90_read_reg(new_client, LM90_REG_R_CONFIG2,
&reg_config2) < 0)
goto exit_free;
if ((reg_config1 & 0x2A) == 0x00
&& (reg_config2 & 0xF8) == 0x00
&& reg_convrate <= 0x09) {
if (address == 0x4C
&& (chip_id & 0xF0) == 0x20) /* LM90 */
kind = lm90;
else if ((chip_id & 0xF0) == 0x30) /* LM89/LM99 */
kind = lm99;
else if (address == 0x4C
&& (chip_id & 0xF0) == 0x10) /* LM86 */
kind = lm99;
}
} else
if (man_id == 0x41) { /* Analog Devices */
if ((chip_id & 0xF0) == 0x40 /* ADM1032 */
&& (reg_config1 & 0x3F) == 0x00
&& reg_convrate <= 0x0A)
kind = adm1032;
else
if (chip_id == 0x51 /* ADT7461 */
&& (reg_config1 & 0x1F) == 0x00 /* check compat mode */
&& reg_convrate <= 0x0A)
kind = adt7461;
} else
if (man_id == 0x4D) { /* Maxim */
/*
* The Maxim variants do NOT have a chip_id register.
* Reading from that address will return the last read
* value, which in our case is those of the man_id
* register. Likewise, the config1 register seems to
* lack a low nibble, so the value will be those of the
* previous read, so in our case those of the man_id
* register.
*/
if (chip_id == man_id
&& (reg_config1 & 0x1F) == (man_id & 0x0F)
&& reg_convrate <= 0x09)
kind = max6657;
}
}
if (kind <= 0) { /* identification failed */
printk(KERN_INFO "lm90: Unsupported chip\n");
goto exit_free;
}
if (kind == lm90) {
type_name = "lm90";
client_name = "LM90 chip";
} else if (kind == adm1032) {
type_name = "adm1032";
client_name = "ADM1032 chip";
/* The ADM1032 supports PEC, but only if combined
transactions are not used. */
if (i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) {
new_client->flags |= I2C_CLIENT_PEC;
printk(KERN_DEBUG "lm90: Enabling PEC for ADM1032\n");
}
} else if (kind == lm99) {
type_name = "lm99";
client_name = "LM99 chip";
} else if (kind == lm86) {
type_name = "lm86";
client_name = "LM86 chip";
} else if (kind == max6657) {
type_name = "max6657";
client_name = "MAX6657 chip";
} else if (kind == adt7461) {
type_name = "adt7461";
client_name = "ADT7561 chip";
} else {
printk(KERN_ERR "lm90: Unknown kind %d\n", kind);
goto exit_free;
}
/*
* OK, we got a valid chip so we can fill in the remaining client
* fields.
*/
strcpy(new_client->name, client_name);
data->valid = 0;
data->kind = kind;
init_MUTEX(&data->update_lock);
/*
* Tell the I2C layer a new client has arrived.
*/
if ((err = i2c_attach_client(new_client))) {
printk(KERN_ERR "lm90: Failed to attach client (%d)\n", err);
goto exit_free;
}
/*
* Register a new directory entry.
*/
if ((err = i2c_register_entry(new_client, type_name,
(new_client->flags & I2C_CLIENT_PEC) ?
adm1032_dir_table_template :
lm90_dir_table_template, THIS_MODULE)) < 0) {
printk(KERN_ERR "lm90: Failed to register directory entry "
"(%d)\n", err);
goto exit_detach;
}
data->sysctl_id = err;
/*
* Initialize the LM90 chip.
*/
lm90_init_client(new_client);
return 0;
exit_detach:
i2c_detach_client(new_client);
exit_free:
kfree(data);
return err;
}
static void lm90_init_client(struct i2c_client *client)
{
u8 config;
/*
* Start the conversions.
*/
i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE,
5); /* 2 Hz */
if (lm90_read_reg(client, LM90_REG_R_CONFIG1, &config) < 0) {
printk(KERN_ERR "lm90: Initialization failed!\n");
return;
}
if (config & 0x40)
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
config & 0xBF); /* run */
}
static int lm90_detach_client(struct i2c_client *client)
{
int err;
i2c_deregister_entry(((struct lm90_data *) (client->data))->sysctl_id);
if ((err = i2c_detach_client(client))) {
printk(KERN_ERR "lm90: Client deregistration failed, client "
"not detached (%d)\n", err);
return err;
}
kfree(client->data);
return 0;
}
static void lm90_update_client(struct i2c_client *client)
{
struct lm90_data *data = client->data;
down(&data->update_lock);
if ((jiffies - data->last_updated > HZ * 2) ||
(jiffies < data->last_updated) || !data->valid) {
u8 oldh, newh, l;
#ifdef DEBUG
printk(KERN_DEBUG "lm90: Updating register values\n");
#endif
lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP,
&data->local_temp);
lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH,
&data->local_high);
lm90_read_reg(client, LM90_REG_R_LOCAL_LOW,
&data->local_low);
lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT,
&data->local_crit);
lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT,
&data->remote_crit);
lm90_read_reg(client, LM90_REG_R_TCRIT_HYST,
&data->hyst);
/*
* There is a trick here. We have to read two registers to
* have the remote sensor temperature, but we have to beware
* a conversion could occur inbetween the readings. The
* datasheet says we should either use the one-shot
* conversion register, which we don't want to do (disables
* hardware monitoring) or monitor the busy bit, which is
* impossible (we can't read the values and monitor that bit
* at the exact same time). So the solution used here is to
* read the high byte once, then the low byte, then the high
* byte again. If the new high byte matches the old one,
* then we have a valid reading. Else we have to read the low
* byte again, and now we believe we have a correct reading.
*/
if (lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPH, &oldh) == 0
&& lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPL, &l) == 0
&& lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPH, &newh) == 0
&& (newh == oldh
|| lm90_read_reg(client, LM90_REG_R_REMOTE_TEMPL, &l) == 0))
data->remote_temp = (newh << 8) | l;
if (lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &newh) == 0
&& lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL, &l) == 0)
data->remote_low = (newh << 8) | l;
if (lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &newh) == 0
&& lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL, &l) == 0)
data->remote_high = (newh << 8) | l;
lm90_read_reg(client, LM90_REG_R_STATUS, &data->alarms);
data->last_updated = jiffies;
data->valid = 1;
}
up(&data->update_lock);
}
static void lm90_local_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP1_FROM_REG(data->local_high);
results[1] = TEMP1_FROM_REG(data->local_low);
results[2] = TEMP1_FROM_REG(data->local_temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (data->kind == adt7461)
data->local_high = TEMP1_TO_REG_ADT7461(results[0]);
else
data->local_high = TEMP1_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_HIGH,
data->local_high);
}
if (*nrels_mag >= 2) {
if (data->kind == adt7461)
data->local_low = TEMP1_TO_REG_ADT7461(results[1]);
else
data->local_low = TEMP1_TO_REG(results[1]);
i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_LOW,
data->local_low);
}
}
}
static void lm90_remote_temp(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 1; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP2_FROM_REG(data->remote_high);
results[1] = TEMP2_FROM_REG(data->remote_low);
results[2] = TEMP2_FROM_REG(data->remote_temp);
*nrels_mag = 3;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (data->kind == adt7461)
data->remote_high = TEMP2_TO_REG_ADT7461(results[0]);
else
data->remote_high = TEMP2_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_HIGHH,
data->remote_high >> 8);
i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_HIGHL,
data->remote_high & 0xFF);
}
if (*nrels_mag >= 2) {
if (data->kind == adt7461)
data->remote_low = TEMP2_TO_REG_ADT7461(results[1]);
else
data->remote_low = TEMP2_TO_REG(results[1]);
i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_LOWH,
data->remote_low >> 8);
i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_LOWL,
data->remote_low & 0xFF);
}
}
}
static void lm90_local_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP1_FROM_REG(data->local_crit);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (data->kind == adt7461)
data->local_crit = TEMP1_TO_REG_ADT7461(results[0]);
else
data->local_crit = TEMP1_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM90_REG_W_LOCAL_CRIT,
data->local_crit);
}
}
}
static void lm90_remote_tcrit(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP1_FROM_REG(data->remote_crit);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
if (data->kind == adt7461)
data->remote_crit = TEMP1_TO_REG_ADT7461(results[0]);
else
data->remote_crit = TEMP1_TO_REG(results[0]);
i2c_smbus_write_byte_data(client, LM90_REG_W_REMOTE_CRIT,
data->remote_crit);
}
}
}
/*
* One quick note about hysteresis. Internally, the hysteresis value
* is held in a single register by the LM90, as a relative value.
* This relative value applies to both the local critical temperature
* and the remote critical temperature. Since all temperatures exported
* through procfs have to be absolute, we have to do some conversions.
* The solution retained here is to export two absolute values, one for
* each critical temperature. In order not to confuse the users too
* much, only one file is writable. Would we fail to do so, users
* would probably attempt to write to both files, as if they were
* independant, and since they aren't, they wouldn't understand why
* setting one affects the other one (and would probably claim there's
* a bug in the driver).
*/
static void lm90_local_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP1_FROM_REG(data->local_crit) -
TEMP1_FROM_REG(data->hyst);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
data->hyst = HYST_TO_REG(data->local_crit - results[0]);
i2c_smbus_write_byte_data(client, LM90_REG_W_TCRIT_HYST,
data->hyst);
}
}
}
static void lm90_remote_hyst(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = TEMP1_FROM_REG(data->remote_crit) -
TEMP1_FROM_REG(data->hyst);
*nrels_mag = 1;
}
}
static void lm90_alarms(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
struct lm90_data *data = client->data;
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
lm90_update_client(client);
results[0] = data->alarms;
*nrels_mag = 1;
}
}
/* pec used for ADM1032 only */
static void adm1032_pec(struct i2c_client *client, int operation,
int ctl_name, int *nrels_mag, long *results)
{
if (operation == SENSORS_PROC_REAL_INFO)
*nrels_mag = 0; /* magnitude */
else if (operation == SENSORS_PROC_REAL_READ) {
results[0] = !!(client->flags & I2C_CLIENT_PEC);
*nrels_mag = 1;
} else if (operation == SENSORS_PROC_REAL_WRITE) {
if (*nrels_mag >= 1) {
switch (results[0]) {
case 0:
client->flags &= ~I2C_CLIENT_PEC;
break;
case 1:
client->flags |= I2C_CLIENT_PEC;
break;
}
}
}
}
static int __init sm_lm90_init(void)
{
printk(KERN_INFO "lm90 driver version %s (%s)\n", LM_VERSION,
LM_DATE);
return i2c_add_driver(&lm90_driver);
}
static void __exit sm_lm90_exit(void)
{
i2c_del_driver(&lm90_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("LM90/ADM1032 sensor driver");
MODULE_LICENSE("GPL");
module_init(sm_lm90_init);
module_exit(sm_lm90_exit);
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff. Show more