mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-10-26 12:24:33 +00:00
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:
1 parent
448c4a70e9
commit
9232f4eb43
136 files changed
+1633
-55767
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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...
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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/
|
||||
@@ -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).
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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
@@ -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);
|
||||
@@ -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
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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(¤t->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);
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
@@ -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,
|
||||
®_config1) < 0
|
||||
|| lm90_read_reg(new_client, LM90_REG_R_CONVRATE,
|
||||
®_convrate) < 0)
|
||||
goto exit_free;
|
||||
|
||||
if (man_id == 0x01) { /* National Semiconductor */
|
||||
u8 reg_config2;
|
||||
|
||||
if (lm90_read_reg(new_client, LM90_REG_R_CONFIG2,
|
||||
®_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
Reference in new issue
Block a user