From bd256e9552f0e5f9c48355d6f7446f53648b8940 Mon Sep 17 00:00:00 2001 From: "Mark D. Studebaker" Date: Mon, 17 Mar 2003 02:34:10 +0000 Subject: [PATCH] lm85 patch from Philip Pokorny git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@1726 7894878c-1315-0410-8ee3-d5d059ff63e0 --- CONTRIBUTORS | 3 +- README | 7 +- doc/chips/SUMMARY | 5 + doc/chips/lm85 | 515 ++++++++++ kernel/chips/lm85.c | 1921 ++++++++++++++++++++++++++++++++++++ kernel/include/sensors.h | 69 +- lib/chips.c | 435 ++++++++ lib/chips.h | 100 ++ mkpatch/Config.in | 1 + mkpatch/FILES | 1 + mkpatch/mkpatch.pl | 1 + prog/detect/sensors-detect | 21 +- prog/sensors/chips.c | 238 +++++ prog/sensors/chips.h | 1 + prog/sensors/main.c | 6 + 15 files changed, 3313 insertions(+), 11 deletions(-) create mode 100644 doc/chips/lm85 create mode 100644 kernel/chips/lm85.c diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 57bbdc76..a1e6e06d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -83,4 +83,5 @@ problems. * Vojtech Pavlik Author of the i2c-amd8111 bus driver. Added support for amd8111 SMBus 1.0 controller to i2c-amd756. - +* Philip Pokorny + Author of the LM85 chip driver. diff --git a/README b/README index df4277f4..e12152ba 100644 --- a/README +++ b/README @@ -60,8 +60,9 @@ At least the following I2C/SMBus adapters are supported: At least the following hardware sensor chips are supported: - Analog Devices ADM1021, ADM1021A, ADM1022, - ADM1023, ADM1024, ADM1025, and ADM9240 + Analog Devices ADM1021, ADM1021A, ADM1022, ADM1023 + ADM1024, ADM1025, ADM1027 and ADM9240 + ADT7463 Asus AS99127F, ASB100 Bach Dallas Semiconductor DS75, DS1621, DS1625, DS1775, and DS1780 Hewlett Packard Maxilife (several revisions including '99 NBA) @@ -73,7 +74,7 @@ At least the following hardware sensor chips are supported: Microchip TC1068, TCM1617, TCN75 Myson MTP008 National Semiconductor LM75, LM77, LM78, LM78-J, LM79, - LM80, LM81, LM84, LM87, and LM92 + LM80, LM81, LM84, LM85, LM87, and LM92 Philips NE1617, NE1617A SiS 5595, 950 embedded sensors SMSC 47M1xx embedded sensors diff --git a/doc/chips/SUMMARY b/doc/chips/SUMMARY index 5c264311..047efa7a 100644 --- a/doc/chips/SUMMARY +++ b/doc/chips/SUMMARY @@ -111,6 +111,11 @@ lm78 lm80 lm80 1 7 2 - yes no +lm85 + lm85 3 5 4 3 pwm yes no + adm1027 3 5 4 3 pwm yes no + adt7463 3 5 4 3 pwm yes no + lm87 lm87 2-3 6-8 0-2 1 pwm yes no diff --git a/doc/chips/lm85 b/doc/chips/lm85 new file mode 100644 index 00000000..08fd1daf --- /dev/null +++ b/doc/chips/lm85 @@ -0,0 +1,515 @@ +Kernel driver `lm85.o' +====================== + +Status: Complete and somewhat tested + +Supported chips: + * National Semiconductors LM85 (B and C versions) + Prefix `lm85' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + * Analog Devices ADM1027 + Prefix `adm1027' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + * Analog Devices ADT7463 + Prefix 'adt7463' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + +Authors: + Philip Pokorny + Frodo Looijaard + Richard Barrington + + +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 Semiconductors LM85 +and compatible chips including the Analog Devices ADM1027 and ADT7463. + +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 lm85 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 LM85C 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 in4_max 5.0 * 1.05 + set in4_min 12 * 0.95 + set in3_max 12 * 1.05 + +# Set Fan limits + set fan1_min 3000 + set fan2_min 3000 + set fan3_min 3000 + set fan4_min 3000 + diff --git a/kernel/chips/lm85.c b/kernel/chips/lm85.c new file mode 100644 index 00000000..70937d5f --- /dev/null +++ b/kernel/chips/lm85.c @@ -0,0 +1,1921 @@ +#define DEBUG 1 +/* + lm85.c - Part of lm_sensors, Linux kernel modules for hardware + monitoring + Copyright (c) 1998, 1999 Frodo Looijaard + Copyright (c) 2002, 2003 Philip Pokorny + Copyright (c) 2003 Margit Schubert-While + + 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. + + CHANGELOG + + 2002-11-13 First patch for LM85 functionality + 2002-11-18 LM85 functionality mostly done + 2002-12-02 Adding ADM1027 functionality + 2002-12-06 Adding ADT7463 functionality + 2003-01-09 Code cleanup. + Save reserved bits in case they are implemented + in a future chip. (Solve problem with lockups + on ADM1027 due to chip initialization) + Added chip initialization bypass option + 2003-02-12 Add THERM asserted counts for ADT7463 + Added #ifdef so we can compile against 2.6.5 + without updating i2c-ids.h + 2003-02-17 Prepare for switch to 2.7.0 development + Implement tmin_control for ADT7463 + Expose THERM asserted counts to /proc + Code cleanup + 2003-02-19 Working with Margit and LM_SENSORS developers + 2003-02-23 Removed chip initialization entirely + Scale voltages in driver at Margit's request + Change PWM from 0-100% to 0-255 per LM sensors standard + 2003-02-27 Documentation and code cleanups + Added this CHANGELOG + Print additional precision for temperatures and voltages + Many thanks to Margit Schubert-While and Brandt xxxxxx + for help testing this version + 2003-02-28 More diagnostic messages regarding BIOS setup + 2003-03-01 Added Interrupt mask register support. + 2003-03-08 Fixed problem with pseudo 16-bit registers + Cleaned up some compiler warnings. + Fixed problem with Operating Point and THERM counting +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "sensors.h" +#include "sensors_vid.h" + +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif + +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,18)) || \ + (LINUX_VERSION_CODE == KERNEL_VERSION(2,3,0)) +#define init_MUTEX(s) do { *(s) = MUTEX; } while(0) +#endif + +#ifndef THIS_MODULE +#define THIS_MODULE NULL +#endif + +#ifndef I2C_DRIVERID_LM85 +/* i2c-id.h hasn't been updated to assign us an ID. + * So... Use a local ID from 0xf000 to 0xffff as + * documented in i2c-id.h + * But, for the record, LM85 was assigned I2C ID 0x1039 around Jan 2003. + */ +#define I2C_DRIVERID_LM85 (0xf185) +#endif + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 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_4(lm85b, lm85c, adm1027, adt7463); + +/* Many LM85 constants specified below */ + +/* The LM85 registers */ +#define LM85_REG_IN(nr) (0x20 + (nr)) +#define LM85_REG_IN_MIN(nr) (0x44 + (nr) * 2) +#define LM85_REG_IN_MAX(nr) (0x45 + (nr) * 2) + +#define LM85_REG_TEMP(nr) (0x25 + (nr)) +#define LM85_REG_TEMP_MIN(nr) (0x4e + (nr) * 2) +#define LM85_REG_TEMP_MAX(nr) (0x4f + (nr) * 2) + +/* Fan speeds are LSB, MSB (2 bytes) */ +#define LM85_REG_FAN(nr) (0x28 + (nr) *2) +#define LM85_REG_FAN_MIN(nr) (0x54 + (nr) *2) + +#define LM85_REG_PWM(nr) (0x30 + (nr)) + +#define ADT7463_REG_OPPOINT(nr) (0x33 + (nr)) + +#define ADT7463_REG_TMIN_CTL1 0x36 +#define ADT7463_REG_TMIN_CTL2 0x37 +#define ADT7463_REG_TMIN_CTL 0x0136 + +#define LM85_REG_DEVICE 0x3d +#define LM85_REG_COMPANY 0x3e +#define LM85_REG_VERSTEP 0x3f +/* These are the recognized values for the above regs */ +#define LM85_DEVICE_ADX 0x27 +#define LM85_COMPANY_NATIONAL 0x01 +#define LM85_COMPANY_ANALOG_DEV 0x41 +#define LM85_VERSTEP_GENERIC 0x60 +#define LM85_VERSTEP_LM85C 0x60 +#define LM85_VERSTEP_LM85B 0x62 +#define LM85_VERSTEP_ADM1027 0x60 +#define LM85_VERSTEP_ADT7463 0x62 + +#define LM85_REG_CONFIG 0x40 + +#define LM85_REG_ALARM1 0x41 +#define LM85_REG_ALARM2 0x42 +#define LM85_REG_ALARM 0x0141 + +#define LM85_REG_VID 0x43 + +/* Automated FAN control */ +#define LM85_REG_AFAN_CONFIG(nr) (0x5c + (nr)) +#define LM85_REG_AFAN_RANGE(nr) (0x5f + (nr)) +#define LM85_REG_AFAN_SPIKE1 0x62 +#define LM85_REG_AFAN_SPIKE2 0x63 +#define LM85_REG_AFAN_MINPWM(nr) (0x64 + (nr)) +#define LM85_REG_AFAN_LIMIT(nr) (0x67 + (nr)) +#define LM85_REG_AFAN_CRITICAL(nr) (0x6a + (nr)) +#define LM85_REG_AFAN_HYST1 0x6d +#define LM85_REG_AFAN_HYST2 0x6e + +#define LM85_REG_TACH_MODE 0x74 +#define LM85_REG_SPINUP_CTL 0x75 + +#define ADM1027_REG_TEMP_OFFSET(nr) (0x70 + (nr)) +#define ADM1027_REG_CONFIG2 0x73 +#define ADM1027_REG_INTMASK1 0x74 +#define ADM1027_REG_INTMASK2 0x75 +#define ADM1027_REG_INTMASK 0x0174 +#define ADM1027_REG_EXTEND_ADC1 0x76 +#define ADM1027_REG_EXTEND_ADC2 0x77 +#define ADM1027_REG_EXTEND_ADC 0x0176 +#define ADM1027_REG_CONFIG3 0x78 +#define ADM1027_REG_FAN_PPR 0x7b + +#define ADT7463_REG_THERM 0x79 +#define ADT7463_REG_THERM_LIMIT 0x7A +#define ADT7463_REG_CONFIG4 0x7D + +/* 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. + */ + +/* IN are scaled 1.000 == 0xc0, mag = 3 */ +#define IN_TO_REG(val) (SENSORS_LIMIT((((val)*0xc0+500)/1000),0,255)) +#define INEXT_FROM_REG(val,ext) (((val)*1000 + (ext)*250 + 96)/0xc0) +#define IN_FROM_REG(val) (INEXT_FROM_REG(val,0)) + +/* IN are scaled acording to built-in resistors */ +static int lm85_scaling[] = { /* .001 Volts */ + 2500, 2250, 3300, 5000, 12000 + }; +#define SCALE(val,from,to) (((val)*(to) + ((from)/2))/(from)) +#define INS_TO_REG(n,val) (SENSORS_LIMIT(SCALE(val,lm85_scaling[n],192),0,255)) +#define INSEXT_FROM_REG(n,val,ext) (SCALE((val)*4 + (ext),192*4,lm85_scaling[n])) +#define INS_FROM_REG(n,val) (INSEXT_FROM_REG(n,val,0)) + +/* FAN speed is measured using 90kHz clock */ +#define FAN_TO_REG(val) (SENSORS_LIMIT( (val)<=0?0: 5400000/(val),0,65534)) +#define FAN_FROM_REG(val) ((val)==0?-1:(val)==0xffff?0:5400000/(val)) + +/* Temperature is reported in .01 degC increments */ +#define TEMP_TO_REG(val) (SENSORS_LIMIT(((val)+50)/100,-127,127)) +#define TEMPEXT_FROM_REG(val,ext) ((val)*100 + (ext)*25) +#define TEMP_FROM_REG(val) (TEMPEXT_FROM_REG(val,0)) +#define EXTTEMP_TO_REG(val) (SENSORS_LIMIT((val)/25,-127,127)) +#define OPPOINT_TO_REG(val) (SENSORS_LIMIT(val,-127,127)) +#define OPPOINT_FROM_REG(val) (val) + +#define PWM_TO_REG(val) (SENSORS_LIMIT(val,0,255)) +#define PWM_FROM_REG(val) (val) + +#define EXT_FROM_REG(val,sensor) (((val)>>(sensor * 2))&0x03) + +/* ZONEs have the following parameters: + * Limit (low) temp, 1. degC + * Hysteresis (below limit), 1. degC (0-15) + * Range of speed control, .1 degC (2-80) + * Critical (high) temp, 1. degC + * + * FAN PWMs have the following parameters: + * Reference Zone, 1, 2, 3, etc. + * Spinup time, .05 sec + * PWM value at limit/low temp, 1 count + * PWM Frequency, 1. Hz + * PWM is Min or OFF below limit, flag + * Invert PWM output, flag + * + * Some chips filter the temp, others the fan. + * Filter constant (or disabled) .1 seconds + */ + +/* These are the zone temperature range encodings */ +static int lm85_range_map[] = { /* .1 degC */ + 20, 25, 33, 40, 50, 66, + 80, 100, 133, 160, 200, 266, + 320, 400, 533, 800 + }; +static int RANGE_TO_REG( int range ) +{ + int i; + + if( range >= lm85_range_map[15] ) { return 15 ; } + for( i = 0 ; i < 15 ; ++i ) + if( range <= lm85_range_map[i] ) + break ; + return( i & 0x0f ); +} +#define RANGE_FROM_REG(val) (lm85_range_map[(val)&0x0f]) + +/* These are the Acoustic Enhancement, or Temperature smoothing encodings + * NOTE: The enable/disable bit is INCLUDED in these encodings as the + * MSB (bit 3, value 8). If the enable bit is 0, the encoded value + * is ignored, or set to 0. + */ +static int lm85_smooth_map[] = { /* .1 sec */ + 350, 176, 118, 70, 44, 30, 16, 8 +/* 35.4 * 1/1, 1/2, 1/3, 1/5, 1/8, 1/12, 1/24, 1/48 */ + }; +static int SMOOTH_TO_REG( int smooth ) +{ + int i; + + if( smooth <= 0 ) { return 0 ; } /* Disabled */ + for( i = 0 ; i < 7 ; ++i ) + if( smooth >= lm85_smooth_map[i] ) + break ; + return( (i & 0x07) | 0x08 ); +} +#define SMOOTH_FROM_REG(val) ((val)&0x08?lm85_smooth_map[(val)&0x07]:0) + +/* These are the fan spinup delay time encodings */ +static int lm85_spinup_map[] = { /* .1 sec */ + 0, 1, 2, 4, 7, 10, 20, 40 + }; +static int SPINUP_TO_REG( int spinup ) +{ + int i; + + if( spinup >= lm85_spinup_map[7] ) { return 7 ; } + for( i = 0 ; i < 7 ; ++i ) + if( spinup <= lm85_spinup_map[i] ) + break ; + return( i & 0x07 ); +} +#define SPINUP_FROM_REG(val) (lm85_spinup_map[(val)&0x07]) + +/* These are the PWM frequency encodings */ +static int lm85_freq_map[] = { /* .1 Hz */ + 100, 150, 230, 300, 380, 470, 620, 980 + }; +static int FREQ_TO_REG( int freq ) +{ + int i; + + if( freq >= lm85_freq_map[7] ) { return 7 ; } + for( i = 0 ; i < 7 ; ++i ) + if( freq <= lm85_freq_map[i] ) + break ; + return( i & 0x07 ); +} +#define FREQ_FROM_REG(val) (lm85_freq_map[(val)&0x07]) + +/* Since we can't use strings, I'm abusing these numbers + * to stand in for the following meanings: + * 1 -- PWM responds to Zone 1 + * 2 -- PWM responds to Zone 2 + * 3 -- PWM responds to Zone 3 + * 23 -- PWM responds to the higher temp of Zone 2 or 3 + * 123 -- PWM responds to highest of Zone 1, 2, or 3 + * 0 -- PWM is always at 0% (ie, off) + * -1 -- PWM is always at 100% + * -2 -- PWM responds to manual control + */ +static int lm85_zone_map[] = { 1, 2, 3, -1, 0, 23, 123, -2 }; +static int ZONE_TO_REG( int zone ) +{ + int i; + + for( i = 0 ; i <= 7 ; ++i ) + if( zone == lm85_zone_map[i] ) + break ; + if( i > 7 ) /* Not found. */ + i = 3; /* Always 100% */ + return( (i & 0x07)<<5 ); +} +#define ZONE_FROM_REG(val) (lm85_zone_map[((val)>>5)&0x07]) + +#define HYST_TO_REG(val) (SENSORS_LIMIT((-(val)+5)/10,0,15)) +#define HYST_FROM_REG(val) (-(val)*10) + +#define OFFSET_TO_REG(val) (SENSORS_LIMIT((val)/25,-127,127)) +#define OFFSET_FROM_REG(val) ((val)*25) + +#define PPR_MASK(fan) (0x03<<(fan *2)) +#define PPR_TO_REG(val,fan) (SENSORS_LIMIT((val)-1,0,3)<<(fan *2)) +#define PPR_FROM_REG(val,fan) ((((val)>>(fan * 2))&0x03)+1) + +/* sensors_vid.h defines vid_from_reg() */ +#define VID_FROM_REG(val,vrm) (vid_from_reg((val),(vrm))) + +#define ALARMS_FROM_REG(val) (val) + +/* When converting to REG, we need to fixup the carry-over bit */ +#define INTMASK_FROM_REG(val) (val) +#define INTMASK_TO_REG(val) (SENSORS_LIMIT((val)|((val)&0xff00?0x80:0),0,65535)) + +/* Unlike some other drivers we DO NOT set initial limits. Use + * the config file to set limits. Some users have reported + * motherboards shutting down when we set limits in a previous + * version of this driver. This may be caused by APM/ACPI + * detecting an out-of-limit condition when we had the wrong + * limits set. + */ + +/* Typically used with Pentium 4 systems v9.1 VRM spec */ +#define LM85_INIT_VRM 91 + +/* Chip sampling rates + * + * Some sensors are not updated more frequently than once per second + * so it doesn't make sense to read them more often than that. + * We cache the results and return the saved data if the driver + * is called again before a second has elapsed. + * + * Also, there is significant configuration data for this chip + * given the automatic PWM fan control that is possible. There + * are about 47 bytes of config data to only 22 bytes of actual + * readings. So, we keep the config data up to date in the cache + * when it is written and only sample it once every 5 *minutes* + */ +#define LM85_DATA_INTERVAL (1 * HZ) +#define LM85_CONFIG_INTERVAL (5 * 60 * HZ) + +#ifdef MODULE +extern int init_module(void); +extern int cleanup_module(void); +#endif /* MODULE */ + +/* There are some complications in a module like this. There might be + several LM85 chips available (well, actually, that is probably never + done; but it is a clean illustration of how to handle a case like + that). */ + +/* For each registered LM85, we need to keep some data in memory. That + data is pointed to by lm85_list[NR]->data. The structure itself is + dynamically allocated, at the same time when a new lm85 client is + allocated. */ + +/* LM85 can automatically adjust fan speeds based on temperature + * This structure encapsulates an entire Zone config. There are + * three zones (one for each temperature input) on the lm85 + */ +struct lm85_zone { + s8 limit; /* Low temp limit */ + u8 hyst; /* Low limit hysteresis. (0-15) */ + u8 range; /* Temp range, encoded */ + s8 critical; /* "All fans ON" temp limit */ +}; + +struct lm85_autofan { + u8 config; /* Register value */ + u8 freq; /* PWM frequency, encoded */ + u8 min_pwm; /* Minimum PWM value, encoded */ + u8 min_off; /* Min PWM or OFF below "limit", flag */ +}; + +struct lm85_data { + struct semaphore lock; + int sysctl_id; + enum chips type; + + struct semaphore update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_reading; /* In jiffies */ + unsigned long last_config; /* In jiffies */ + + u8 in[5]; /* Register value */ + u8 in_max[5]; /* Register value */ + u8 in_min[5]; /* Register value */ + s8 temp[3]; /* Register value */ + s8 temp_min[3]; /* Register value */ + s8 temp_max[3]; /* Register value */ + s8 temp_offset[3]; /* Register value */ + u16 fan[4]; /* Register value */ + u16 fan_min[4]; /* Register value */ + u8 pwm[3]; /* Register value */ + u8 spinup_ctl; /* Register encoding, combined */ + u8 tach_mode; /* Register encoding, combined */ + u16 extend_adc; /* Register value */ + u8 fan_ppr; /* Register value */ + u8 smooth[3]; /* Register encoding */ + u8 vid; /* Register value */ + u8 vrm; /* VRM version */ + u8 syncpwm3; /* Saved PWM3 for TACH 2,3,4 config */ + s8 oppoint[3]; /* Register value */ + u16 tmin_ctl; /* Register value */ + long therm_total; /* Cummulative therm count */ + long therm_ovfl; /* Count of therm overflows */ + u8 therm_limit; /* Register value */ + u16 alarms; /* Register encoding, combined */ + u16 alarm_mask; /* Register encoding, combined */ + struct lm85_autofan autofan[3]; + struct lm85_zone zone[3]; +}; + + +#ifdef MODULE +static +#else +extern +#endif +int __init sensors_lm85_init(void); +static int __init lm85_cleanup(void); + +static int lm85_attach_adapter(struct i2c_adapter *adapter); +static int lm85_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int lm85_detach_client(struct i2c_client *client); +static int lm85_command(struct i2c_client *client, unsigned int cmd, + void *arg); +static void lm85_inc_use(struct i2c_client *client); +static void lm85_dec_use(struct i2c_client *client); + +static int lm85_read_value(struct i2c_client *client, u16 register); +static int lm85_write_value(struct i2c_client *client, u16 register, int value); +static void lm85_update_client(struct i2c_client *client); +static void lm85_init_client(struct i2c_client *client); + + +static void lm85_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results); +static void lm85_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_vid(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_vrm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_zone(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm_config(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_pwm_zone(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_smooth(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void lm85_spinup_ctl(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void lm85_tach_mode(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void adm1027_tach_mode(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_temp_offset(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_fan_ppr(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adm1027_alarm_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static void adt7463_tmin_ctl(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void adt7463_therm_signal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); + +static struct i2c_driver lm85_driver = { + /* name */ "LM85 compatible sensor driver", + /* id */ I2C_DRIVERID_LM85, + /* flags */ I2C_DF_NOTIFY, + /* attach_adapter */ &lm85_attach_adapter, + /* detach_client */ &lm85_detach_client, + /* command */ &lm85_command, + /* inc_use */ &lm85_inc_use, + /* dec_use */ &lm85_dec_use +}; + +/* Unique ID assigned to each LM85 detected */ +static int lm85_id = 0; + +/* The /proc/sys entries */ +/* These files are created for each detected LM85. This is just a template; + * The actual list is built from this and additional per-chip + * custom lists below. Note the XXX_LEN macros. These must be + * compile time constants because they will be used to allocate + * space for the final template passed to i2c_register_entry. + * We depend on the ability of GCC to evaluate expressions at + * compile time to turn these expressions into compile time + * constants, but this can generate a warning. + */ +static ctl_table lm85_common[] = { + {LM85_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_in}, + {LM85_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_in}, + {LM85_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_in}, + {LM85_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_in}, + {LM85_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_in}, + {LM85_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_fan}, + {LM85_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_fan}, + {LM85_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_fan}, + {LM85_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_fan}, + {LM85_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_temp}, + {LM85_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_temp}, + {LM85_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_temp}, + {LM85_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_vid}, + {LM85_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_vrm}, + {LM85_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_alarms}, + {LM85_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm}, + {LM85_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm}, + {LM85_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm}, + {LM85_SYSCTL_PWM_CFG1, "pwm1_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm_config}, + {LM85_SYSCTL_PWM_CFG2, "pwm2_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm_config}, + {LM85_SYSCTL_PWM_CFG3, "pwm3_cfg", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_pwm_config}, + {LM85_SYSCTL_PWM_ZONE1, "pwm1_zone", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, + {LM85_SYSCTL_PWM_ZONE2, "pwm2_zone", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, + {LM85_SYSCTL_PWM_ZONE3, "pwm3_zone", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_pwm_zone}, + {LM85_SYSCTL_ZONE1, "zone1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_zone}, + {LM85_SYSCTL_ZONE2, "zone2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_zone}, + {LM85_SYSCTL_ZONE3, "zone3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_zone}, + {LM85_SYSCTL_SMOOTH1, "smooth1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_smooth}, + {LM85_SYSCTL_SMOOTH2, "smooth2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_smooth}, + {LM85_SYSCTL_SMOOTH3, "smooth3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &lm85_smooth}, + {0} +}; +#define CTLTBL_COMMON (sizeof(lm85_common)/sizeof(lm85_common[0])) + +/* NOTE: tach_mode is a shared name, but implemented with + * different functions + */ +static ctl_table lm85_specific[] = { + {LM85_SYSCTL_SPINUP_CTL, "spinup_ctl", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_spinup_ctl}, + {LM85_SYSCTL_TACH_MODE, "tach_mode", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &lm85_tach_mode}, +}; +#define CTLTBL_LM85 (sizeof(lm85_specific)/sizeof(lm85_specific[0])) + +static ctl_table adm1027_specific[] = { + {ADM1027_SYSCTL_TACH_MODE, "tach_mode", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_tach_mode}, + {ADM1027_SYSCTL_TEMP_OFFSET1, "temp1_offset", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, + {ADM1027_SYSCTL_TEMP_OFFSET2, "temp2_offset", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, + {ADM1027_SYSCTL_TEMP_OFFSET3, "temp3_offset", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_temp_offset}, + {ADM1027_SYSCTL_FAN_PPR, "fan_ppr", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_fan_ppr}, + {ADM1027_SYSCTL_ALARM_MASK, "alarm_mask", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adm1027_alarm_mask}, +}; +#define CTLTBL_ADM1027 (sizeof(adm1027_specific)/sizeof(adm1027_specific[0])) + +static ctl_table adt7463_specific[] = { + {ADT7463_SYSCTL_TMIN_CTL1, "tmin_ctl1", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, + {ADT7463_SYSCTL_TMIN_CTL2, "tmin_ctl2", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, + {ADT7463_SYSCTL_TMIN_CTL3, "tmin_ctl3", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_tmin_ctl}, + {ADT7463_SYSCTL_THERM_SIGNAL, "therm_signal", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &adt7463_therm_signal}, +}; +#define CTLTBL_ADT7463 (sizeof(adt7463_specific)/sizeof(adt7463_specific[0])) + + +#define MAX2(a,b) ((a)>(b)?(a):(b)) +#define MAX3(a,b,c) ((a)>(b)?MAX2((a),(c)):MAX2((b),(c))) +#define MAX4(a,b,c,d) ((a)>(b)?MAX3((a),(c),(d)):MAX3((b),(c),(d))) + +#define CTLTBL_MAX (CTLTBL_COMMON + MAX2(CTLTBL_LM85, CTLTBL_ADM1027+CTLTBL_ADT7463)) + +/* This function is called when: + * lm85_driver is inserted (when this module is loaded), for each + available adapter + * when a new adapter is inserted (and lm85_driver is still present) */ +int lm85_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, lm85_detect); +} + +/* This function is called by i2c_detect */ +int lm85_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i; + int company, verstep ; + struct i2c_client *new_client; + struct lm85_data *data; + int err = 0; + const char *type_name = ""; + struct ctl_table template[CTLTBL_MAX] ; + int template_used ; + + if (i2c_is_isa_adapter(adapter)) { + /* This chip has no ISA interface */ + goto ERROR0 ; + }; + + if (!i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) { + /* We need to be able to do byte I/O */ + 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 lm85_{read,write}_value. */ + + if (!(new_client = kmalloc((sizeof(struct i2c_client)) + + sizeof(struct lm85_data), + GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + + data = (struct lm85_data *) (new_client + 1); + new_client->addr = address; + new_client->data = data; + new_client->adapter = adapter; + new_client->driver = &lm85_driver; + new_client->flags = 0; + + /* Now, we do the remaining detection. */ + + company = lm85_read_value(new_client, LM85_REG_COMPANY); + verstep = lm85_read_value(new_client, LM85_REG_VERSTEP); + +#ifdef DEBUG + printk("lm85: Detecting device at %d,0x%02x with" + " COMPANY: 0x%02x and VERSTEP: 0x%02x\n", + i2c_adapter_id(new_client->adapter), new_client->addr, + company, verstep + ); +#endif + + /* If auto-detecting, Determine the chip type. */ + if (kind <= 0) { +#ifdef DEBUG + printk("lm85: Autodetecting device at %d,0x%02x ...\n", + i2c_adapter_id(adapter), address ); +#endif + if( company == LM85_COMPANY_NATIONAL + && verstep == LM85_VERSTEP_LM85C ) { + kind = lm85c ; + } else if( company == LM85_COMPANY_NATIONAL + && verstep == LM85_VERSTEP_LM85B ) { + kind = lm85b ; + } else if( company == LM85_COMPANY_NATIONAL + && (verstep & 0xf0) == LM85_VERSTEP_GENERIC ) { + printk("lm85: Unrecgonized version/stepping 0x%02x" + " Defaulting to LM85.\n", verstep ); + kind = any_chip ; + } else if( company == LM85_COMPANY_ANALOG_DEV + && verstep == LM85_VERSTEP_ADM1027 ) { + kind = adm1027 ; + } else if( company == LM85_COMPANY_ANALOG_DEV + && verstep == LM85_VERSTEP_ADT7463 ) { + kind = adt7463 ; + } else if( company == LM85_COMPANY_ANALOG_DEV + && (verstep & 0xf0) == LM85_VERSTEP_GENERIC ) { + printk("lm85: Unrecgonized version/stepping 0x%02x" + " Defaulting to ADM1027.\n", verstep ); + kind = adm1027 ; + } else if( kind == 0 && (verstep & 0xf0) == 0x60) { + printk("lm85: Generic LM85 Version 6 detected\n"); + /* Leave kind as "any_chip" */ + } else { +#ifdef DEBUG + printk("lm85: Autodetection failed\n"); +#endif + /* Not an LM85 ... */ + if( kind == 0 ) { /* User used force=x,y */ + printk("lm85: Generic LM85 Version 6 not" + " found at %d,0x%02x. Try force_lm85c.\n", + i2c_adapter_id(adapter), address ); + } + err = 0 ; + goto ERROR1; + } + } + + /* Fill in the chip specific driver values */ + switch (kind) { + case any_chip : + type_name = "lm85"; + strcpy(new_client->name, "Generic LM85"); + template_used = 0 ; + break ; + case lm85b : + type_name = "lm85b"; + strcpy(new_client->name, "National LM85-B"); + memcpy( template, lm85_specific, sizeof(lm85_specific) ); + template_used = CTLTBL_LM85 ; + break ; + case lm85c : + type_name = "lm85c"; + strcpy(new_client->name, "National LM85-C"); + memcpy( template, lm85_specific, sizeof(lm85_specific) ); + template_used = CTLTBL_LM85 ; + break ; + case adm1027 : + type_name = "adm1027"; + strcpy(new_client->name, "Analog Devices ADM1027"); + memcpy( template, adm1027_specific, sizeof(adm1027_specific) ); + template_used = CTLTBL_ADM1027 ; + break ; + case adt7463 : + type_name = "adt7463"; + strcpy(new_client->name, "Analog Devices ADT7463"); + memcpy( template, adt7463_specific, sizeof(adt7463_specific) ); + template_used = CTLTBL_ADT7463 ; + memcpy( template+template_used, adm1027_specific, sizeof(adm1027_specific) ); + template_used += CTLTBL_ADM1027 ; + break ; + default : + printk("lm85: Internal error, invalid kind (%d)!", kind); + err = -EFAULT ; + goto ERROR1; + } + + /* Fill in the remaining client fields */ + new_client->id = lm85_id++; +#ifdef DEBUG + printk("lm85: Assigning ID %d to %s at %d,0x%02x\n", + new_client->id, new_client->name, + i2c_adapter_id(new_client->adapter), + new_client->addr + ); +#endif + + /* Housekeeping values */ + data->type = kind; + data->valid = 0; + + /* Set the VRM version */ + data->vrm = LM85_INIT_VRM ; + + /* Zero the accumulators */ + data->therm_total = 0; + data->therm_ovfl = 0; + + init_MUTEX(&data->update_lock); + + /* Initialize the LM85 chip */ + lm85_init_client(new_client); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(new_client))) + goto ERROR1; + + /* Finish out the template */ + memcpy( template + template_used, lm85_common, sizeof(lm85_common) ); + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(new_client, + type_name, + template, + THIS_MODULE)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + return 0; + + /* Error out and cleanup code */ + ERROR2: + i2c_detach_client(new_client); + ERROR1: + kfree(new_client); + ERROR0: + return err; +} + +int lm85_detach_client(struct i2c_client *client) +{ + int err; + int id ; + + id = client->id; + i2c_deregister_entry(((struct lm85_data *)(client->data))->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk("lm85(%d): Client deregistration failed," + " client not detached.\n", id ); + return err; + } + + kfree(client); + + return 0; +} + +/* No commands defined yet */ +int lm85_command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + return 0; +} + +/* Nothing here yet */ +void lm85_inc_use(struct i2c_client *client) +{ + MOD_INC_USE_COUNT; +} + +/* Nothing here yet */ +void lm85_dec_use(struct i2c_client *client) +{ + MOD_DEC_USE_COUNT; +} + + +int lm85_read_value(struct i2c_client *client, u16 reg) +{ + int res; + + /* What size location is it? */ + switch( reg ) { + case LM85_REG_FAN(0) : /* Read WORD data */ + case LM85_REG_FAN(1) : + case LM85_REG_FAN(2) : + case LM85_REG_FAN(3) : + case LM85_REG_FAN_MIN(0) : + case LM85_REG_FAN_MIN(1) : + case LM85_REG_FAN_MIN(2) : + case LM85_REG_FAN_MIN(3) : + case LM85_REG_ALARM : /* Read ALARM1 and ALARM2 */ + case ADM1027_REG_INTMASK : /* Read MASK1 and MASK2 */ + case ADM1027_REG_EXTEND_ADC : /* Read ADC1 and ADC2 */ + reg &= 0xff ; /* Pseudo words have address + 0x0100 */ + res = i2c_smbus_read_byte_data(client, reg) & 0xff ; + res |= i2c_smbus_read_byte_data(client, reg+1) << 8 ; + break ; + case ADT7463_REG_TMIN_CTL : /* Read WORD MSB, LSB */ + reg &= 0xff ; /* Pseudo words have address + 0x0100 */ + res = i2c_smbus_read_byte_data(client, reg) << 8 ; + res |= i2c_smbus_read_byte_data(client, reg+1) & 0xff ; + break ; + default: /* Read BYTE data */ + res = i2c_smbus_read_byte_data(client, reg & 0xff); + break ; + } + + return res ; +} + +int lm85_write_value(struct i2c_client *client, u16 reg, int value) +{ + int res ; + + switch( reg ) { + case LM85_REG_FAN(0) : /* Write WORD data */ + case LM85_REG_FAN(1) : + case LM85_REG_FAN(2) : + case LM85_REG_FAN(3) : + case LM85_REG_FAN_MIN(0) : + case LM85_REG_FAN_MIN(1) : + case LM85_REG_FAN_MIN(2) : + case LM85_REG_FAN_MIN(3) : + case ADM1027_REG_INTMASK : + /* NOTE: ALARM and ADC are read only, so not included here */ + reg &= 0xff ; /* Pseudo words have address + 0x0100 */ + res = i2c_smbus_write_byte_data(client, reg, value & 0xff) ; + res |= i2c_smbus_write_byte_data(client, reg+1, (value>>8) & 0xff) ; + break ; + case ADT7463_REG_TMIN_CTL : /* Write WORD MSB, LSB */ + reg &= 0xff ; /* Pseudo words have address + 0x0100 */ + res = i2c_smbus_write_byte_data(client, reg, (value>>8) & 0xff); + res |= i2c_smbus_write_byte_data(client, reg+1, value & 0xff) ; + break ; + default: /* Write BYTE data */ + res = i2c_smbus_write_byte_data(client, reg & 0xff, value); + break ; + } + + return res ; +} + +/* Called when we have found a new LM85. It should set limits, etc. */ +void lm85_init_client(struct i2c_client *client) +{ + int value; + struct lm85_data *data = client->data; + +#ifdef DEBUG + printk("lm85(%d): Initializing device\n", client->id); +#endif + + /* Warn if part was not "READY" */ + value = lm85_read_value(client, LM85_REG_CONFIG); +#ifdef DEBUG + printk("lm85(%d): LM85_REG_CONFIG is: 0x%02x\n", client->id, value ); +#endif + if( value & 0x02 ) { + printk("lm85(%d): Client (%d,0x%02x) config is locked.\n", + client->id, + i2c_adapter_id(client->adapter), client->addr ); + }; + if( ! (value & 0x04) ) { + printk("lm85(%d): Client (%d,0x%02x) is not ready.\n", + client->id, + i2c_adapter_id(client->adapter), client->addr ); + }; + if( (data->type == adm1027 || data->type == adt7463) + && (value & 0x10) + ) { + printk("lm85(%d): Client (%d,0x%02x) VxI mode is set. " + "Please report this to the lm85 maintainer.\n", + client->id, + i2c_adapter_id(client->adapter), client->addr ); + }; + + /* See if SYNC to PWM3 is set */ + if( data->type == adt7463 + && (lm85_read_value(client, LM85_REG_AFAN_SPIKE1) & 0x10) + ) { + printk("lm85(%d): Sync to PWM3 is set. Expect PWM3 " + "to control fans 2, 3, and 4\n", + client->id ); + }; + + /* See if PWM2 is #SMBALERT */ + if( (data->type == adm1027 || data->type == adt7463) + && (lm85_read_value(client, ADM1027_REG_CONFIG3) & 0x01) + ) { + printk("lm85(%d): PWM2 is SMBALERT. PWM2 not available.\n", + client->id ); + }; + + /* Check if 2.5V and 5V inputs are reconfigured */ + if( data->type == adt7463 ) { + value = lm85_read_value(client, ADT7463_REG_CONFIG4); + if( value & 0x01 ) { + printk("lm85(%d): 2.5V input (in0) is SMBALERT. " + "in0 not available.\n", client->id ); + }; + if( value & 0x02 ) { + printk("lm85(%d): 5V input (in3) is THERM. " + "in3 not available.\n", client->id ); + } + }; + + /* WE INTENTIONALLY make no changes to the limits, + * offsets, pwms, fans and zones. If they were + * configured, we don't want to mess with them. + * If they weren't, the default is 100% PWM, no + * control and will suffice until 'sensors -s' + * can be run by the user. + */ + + /* Start monitoring */ + value = lm85_read_value(client, LM85_REG_CONFIG); + /* Try to clear LOCK, Set START, save everything else */ + value = ((value & ~ 0x02) | 0x01) & 0xff ; +#ifdef DEBUG + printk("lm85(%d): Setting CONFIG to: 0x%02x\n", client->id, value ); +#endif + lm85_write_value(client, LM85_REG_CONFIG, value); + +} + +void lm85_update_client(struct i2c_client *client) +{ + struct lm85_data *data = client->data; + int i; + + down(&data->update_lock); + + if (!data->valid + || (jiffies - data->last_reading > LM85_DATA_INTERVAL )) { + /* Things that change quickly */ + +#ifdef DEBUG + printk("lm85(%d): Reading sensor values\n", client->id); +#endif + /* Have to read extended bits first to "freeze" the + * more significant bits that are read later. + */ + switch( data->type ) { + case adm1027 : + case adt7463 : + data->extend_adc = + lm85_read_value(client, ADM1027_REG_EXTEND_ADC); + break ; + default : + data->extend_adc = 0 ; + break ; + } + + for (i = 0; i <= 4; ++i) { + data->in[i] = + lm85_read_value(client, LM85_REG_IN(i)); + } + + for (i = 0; i <= 3; ++i) { + data->fan[i] = + lm85_read_value(client, LM85_REG_FAN(i)); + } + + for (i = 0; i <= 2; ++i) { + data->temp[i] = + lm85_read_value(client, LM85_REG_TEMP(i)); + } + + for (i = 0; i <= 2; ++i) { + data->pwm[i] = + lm85_read_value(client, LM85_REG_PWM(i)); + } + + switch( ((struct lm85_data *)(client->data))->type ) { + case adt7463 : + /* REG_THERM code duplicated in therm_signal() */ + i = lm85_read_value(client, ADT7463_REG_THERM); + if( data->therm_total < LONG_MAX - 256 ) { + data->therm_total += i ; + } + if( i >= 255 ) { + ++data->therm_ovfl ; + } + break ; + default : break ; /* no warnings */ + } + + data->alarms = lm85_read_value(client, LM85_REG_ALARM); + + data->last_reading = jiffies ; + }; /* last_reading */ + + if (!data->valid + || (jiffies - data->last_config > LM85_CONFIG_INTERVAL) ) { + /* Things that don't change often */ + +#ifdef DEBUG + printk("lm85(%d): Reading config values\n", client->id); +#endif + for (i = 0; i <= 4; ++i) { + data->in_min[i] = + lm85_read_value(client, LM85_REG_IN_MIN(i)); + data->in_max[i] = + lm85_read_value(client, LM85_REG_IN_MAX(i)); + } + + for (i = 0; i <= 3; ++i) { + data->fan_min[i] = + lm85_read_value(client, LM85_REG_FAN_MIN(i)); + } + + for (i = 0; i <= 2; ++i) { + data->temp_min[i] = + lm85_read_value(client, LM85_REG_TEMP_MIN(i)); + data->temp_max[i] = + lm85_read_value(client, LM85_REG_TEMP_MAX(i)); + } + + data->vid = lm85_read_value(client, LM85_REG_VID); + + for (i = 0; i <= 2; ++i) { + int val ; + data->autofan[i].config = + lm85_read_value(client, LM85_REG_AFAN_CONFIG(i)); + val = lm85_read_value(client, LM85_REG_AFAN_RANGE(i)); + data->autofan[i].freq = val & 0x07 ; + data->zone[i].range = (val >> 4) & 0x0f ; + data->autofan[i].min_pwm = + lm85_read_value(client, LM85_REG_AFAN_MINPWM(i)); + data->zone[i].limit = + lm85_read_value(client, LM85_REG_AFAN_LIMIT(i)); + data->zone[i].critical = + lm85_read_value(client, LM85_REG_AFAN_CRITICAL(i)); + } + + i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); + data->smooth[0] = i & 0x0f ; + data->syncpwm3 = i & 0x10 ; /* Save PWM3 config */ + data->autofan[0].min_off = i & 0x20 ; + data->autofan[1].min_off = i & 0x40 ; + data->autofan[2].min_off = i & 0x80 ; + i = lm85_read_value(client, LM85_REG_AFAN_SPIKE2); + data->smooth[1] = (i>>4) & 0x0f ; + data->smooth[2] = i & 0x0f ; + + i = lm85_read_value(client, LM85_REG_AFAN_HYST1); + data->zone[0].hyst = (i>>4) & 0x0f ; + data->zone[1].hyst = i & 0x0f ; + + i = lm85_read_value(client, LM85_REG_AFAN_HYST2); + data->zone[2].hyst = (i>>4) & 0x0f ; + + switch( ((struct lm85_data *)(client->data))->type ) { + case lm85b : + case lm85c : + data->tach_mode = lm85_read_value(client, + LM85_REG_TACH_MODE ); + data->spinup_ctl = lm85_read_value(client, + LM85_REG_SPINUP_CTL ); + break ; + case adt7463 : + for (i = 0; i <= 2; ++i) { + data->oppoint[i] = lm85_read_value(client, + ADT7463_REG_OPPOINT(i) ); + } + data->tmin_ctl = lm85_read_value(client, + ADT7463_REG_TMIN_CTL ); + data->therm_limit = lm85_read_value(client, + ADT7463_REG_THERM_LIMIT ); + /* FALL THROUGH */ + case adm1027 : + for (i = 0; i <= 2; ++i) { + data->temp_offset[i] = lm85_read_value(client, + ADM1027_REG_TEMP_OFFSET(i) ); + } + data->tach_mode = lm85_read_value(client, + ADM1027_REG_CONFIG3 ); + data->fan_ppr = lm85_read_value(client, + ADM1027_REG_FAN_PPR ); + data->alarm_mask = lm85_read_value(client, + ADM1027_REG_INTMASK ); + break ; + default : break ; /* no warnings */ + } + + data->last_config = jiffies; + }; /* last_config */ + + data->valid = 1; + + up(&data->update_lock); +} + + +/* The next 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. + */ +void lm85_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_IN0; + + if (nr < 0 || nr > 4) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; /* 1.000 */ + else if (operation == SENSORS_PROC_REAL_READ) { + int ext = 0 ; + lm85_update_client(client); + ext = EXT_FROM_REG(data->extend_adc, nr); + results[0] = INS_FROM_REG(nr,data->in_min[nr]); + results[1] = INS_FROM_REG(nr,data->in_max[nr]); + results[2] = INSEXT_FROM_REG(nr,data->in[nr],ext); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 1) { + data->in_max[nr] = INS_TO_REG(nr,results[1]); + lm85_write_value(client, LM85_REG_IN_MAX(nr), + data->in_max[nr]); + } + if (*nrels_mag > 0) { + data->in_min[nr] = INS_TO_REG(nr,results[0]); + lm85_write_value(client, LM85_REG_IN_MIN(nr), + data->in_min[nr]); + } + } +} + +void lm85_fan(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_FAN1 ; + + if (nr < 0 || nr > 3) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = FAN_FROM_REG(data->fan_min[nr]); + results[1] = FAN_FROM_REG(data->fan[nr]); + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 0) { + data->fan_min[nr] = FAN_TO_REG(results[0]); + lm85_write_value(client, LM85_REG_FAN_MIN(nr), + data->fan_min[nr]); + } + } +} + + +void lm85_temp(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_TEMP1 ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + int ext = 0 ; + lm85_update_client(client); + + /* +5 for offset of temp data in ext reg */ + ext = EXT_FROM_REG(data->extend_adc, nr+5); + + results[0] = TEMP_FROM_REG(data->temp_min[nr]); + results[1] = TEMP_FROM_REG(data->temp_max[nr]); + results[2] = TEMPEXT_FROM_REG(data->temp[nr],ext); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 1) { + data->temp_max[nr] = TEMP_TO_REG(results[1]); + lm85_write_value(client, LM85_REG_TEMP_MAX(nr), + data->temp_max[nr]); + } + if (*nrels_mag > 0) { + data->temp_min[nr] = TEMP_TO_REG(results[0]); + lm85_write_value(client, LM85_REG_TEMP_MIN(nr), + data->temp_min[nr]); + } + } +} + +void lm85_pwm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_PWM1 ; + int pwm_zone ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = PWM_FROM_REG(data->pwm[nr]); + pwm_zone = ZONE_FROM_REG(data->autofan[nr].config); + /* PWM "enabled" if not off (0) nor on (-1) */ + results[1] = pwm_zone != 0 && pwm_zone != -1 ; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + /* PWM enable is read-only */ + if (*nrels_mag > 0) { + data->pwm[nr] = PWM_TO_REG(results[0]); + lm85_write_value(client, LM85_REG_PWM(nr), + data->pwm[nr]); + } + } +} + +void lm85_vid(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + + if( ctl_name != LM85_SYSCTL_VID ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = VID_FROM_REG((data->vid)&0x3f,data->vrm); + *nrels_mag = 1; + } +} + +void lm85_vrm(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + + if( ctl_name != LM85_SYSCTL_VRM ) + return ; /* ERROR */ + + 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 > 0) { + data->vrm = results[0] ; + } + } +} + +void lm85_alarms(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + + if( ctl_name != LM85_SYSCTL_ALARMS ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = ALARMS_FROM_REG(data->alarms); + *nrels_mag = 1; + } +} + +void lm85_spinup_ctl(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int old; + + if( ctl_name != LM85_SYSCTL_SPINUP_CTL ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = (data->spinup_ctl & 1) != 0 ; + results[1] = (data->spinup_ctl & 2) != 0 ; + results[2] = (data->spinup_ctl & 4) != 0 ; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = data->spinup_ctl ; + if (*nrels_mag > 2) { + old = (old & (~4)) | (results[2]?4:0) ; + } + if (*nrels_mag > 1) { + old = (old & (~2)) | (results[1]?2:0) ; + } + if (*nrels_mag > 0) { + old = (old & (~1)) | (results[0]?1:0) ; + lm85_write_value(client, LM85_REG_SPINUP_CTL, old); + data->spinup_ctl = old ; + } + } +} + +void lm85_tach_mode(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int old; + + /* Tach Mode 1, Tach Mode 2, Tach Mode 3 & 4 */ + + if( ctl_name != LM85_SYSCTL_TACH_MODE ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = (data->tach_mode & 0x03) ; + results[1] = (data->tach_mode & 0x0c) >> 2 ; + results[2] = (data->tach_mode & 0x30) >> 4 ; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = data->tach_mode ; + if (*nrels_mag > 2) { + old = (old & (~0x30)) | ((results[2]&3) << 4) ; + } + if (*nrels_mag > 1) { + old = (old & (~0x0c)) | ((results[1]&3) << 2) ; + } + if (*nrels_mag > 0) { + old = (old & (~0x03)) | (results[0]&3) ; + lm85_write_value(client, LM85_REG_TACH_MODE, old); + data->tach_mode = old ; + } + } +} + +void adm1027_tach_mode(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int old; + + /* Tach/DC 1, Tach/DC 2, Tach/DC 3, Tach/DC 4 */ + + if( ctl_name != ADM1027_SYSCTL_TACH_MODE ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = (data->tach_mode & 0x10) != 0 ; + results[1] = (data->tach_mode & 0x20) != 0 ; + results[2] = (data->tach_mode & 0x40) != 0 ; + results[3] = (data->tach_mode & 0x80) != 0 ; + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = data->tach_mode ; + if (*nrels_mag > 3) { + old = (old & (~0x80)) | (results[3] ? 0x80 : 0) ; + } + if (*nrels_mag > 2) { + old = (old & (~0x40)) | (results[2] ? 0x40 : 0) ; + } + if (*nrels_mag > 1) { + old = (old & (~0x20)) | (results[1] ? 0x20 : 0) ; + } + if (*nrels_mag > 0) { + old = (old & (~0x10)) | (results[0] ? 0x10 : 0) ; + + /* Enable fast measurements if any TACH's are DC */ + old = (old & (~0x08)) | ((old&0xf0) ? 0x08 : 0) ; + + lm85_write_value(client, ADM1027_REG_CONFIG3, old); + data->tach_mode = old ; + } + } +} + +void lm85_pwm_config(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_PWM_CFG1 ; + + /* Spinup, min PWM, PWM Frequency, min below limit, Invert */ + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + + results[0] = SPINUP_FROM_REG(data->autofan[nr].config); + results[1] = PWM_FROM_REG(data->autofan[nr].min_pwm)*10; + results[2] = FREQ_FROM_REG(data->autofan[nr].freq); + results[3] = data->autofan[nr].min_off ? 10 : 0 ; + results[4] = (data->autofan[nr].config & 0x10) ? 10 : 0 ; + *nrels_mag = 5; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + int old_config ; + old_config = data->autofan[nr].config ; + if (*nrels_mag > 4) { + old_config = (old_config & (~0x10)) | (results[4]?0x10:0) ; + } + if (*nrels_mag > 3) { + data->autofan[nr].min_off = results[3] != 0 ; + lm85_write_value(client, LM85_REG_AFAN_SPIKE1, + data->smooth[0] + | data->syncpwm3 + | (data->autofan[0].min_off ? 0x20 : 0) + | (data->autofan[1].min_off ? 0x40 : 0) + | (data->autofan[2].min_off ? 0x80 : 0) + ); + } + if (*nrels_mag > 2) { + data->autofan[nr].freq = FREQ_TO_REG(results[2]) ; + lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), + (data->zone[nr].range << 4) + | data->autofan[nr].freq + ); + } + if (*nrels_mag > 1) { + data->autofan[nr].min_pwm = PWM_TO_REG((results[1]+5)/10); + lm85_write_value(client, LM85_REG_AFAN_MINPWM(nr), + data->autofan[nr].min_pwm + ); + } + if (*nrels_mag > 0) { + old_config = (old_config & (~0x07)) | SPINUP_TO_REG(results[0]) ; + lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), old_config); + data->autofan[nr].config = old_config ; + } + } +} + +void lm85_smooth(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_SMOOTH1 ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = SMOOTH_FROM_REG(data->smooth[nr]); + *nrels_mag = 1; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if( *nrels_mag > 0 ) { + data->smooth[nr] = SMOOTH_TO_REG(results[0]); + } + if( nr == 0 ) { + lm85_write_value(client, LM85_REG_AFAN_SPIKE1, + data->smooth[0] + | data->syncpwm3 + | (data->autofan[0].min_off ? 0x20 : 0) + | (data->autofan[1].min_off ? 0x40 : 0) + | (data->autofan[2].min_off ? 0x80 : 0) + ); + } else { + lm85_write_value(client, LM85_REG_AFAN_SPIKE2, + (data->smooth[1] << 4) | data->smooth[2]); + } + } +} + +void lm85_zone(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_ZONE1 ; + + /* Limit, Hysteresis (neg), Range, Critical */ + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + + results[0] = TEMP_FROM_REG(data->zone[nr].limit) / 10; + results[1] = HYST_FROM_REG(data->zone[nr].hyst); + results[2] = RANGE_FROM_REG(data->zone[nr].range); + results[3] = TEMP_FROM_REG(data->zone[nr].critical) / 10; + *nrels_mag = 4; + + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 3) { + data->zone[nr].critical = TEMP_TO_REG(results[3]*10); + lm85_write_value(client, LM85_REG_AFAN_CRITICAL(nr), + data->zone[nr].critical ); + } + if (*nrels_mag > 2) { + data->zone[nr].range = RANGE_TO_REG(results[2]); + lm85_write_value(client, LM85_REG_AFAN_RANGE(nr), + (data->zone[nr].range << 4) + | data->autofan[nr].freq + ); + } + if (*nrels_mag > 1) { + data->zone[nr].hyst = HYST_TO_REG(results[1]); + if( nr == 0 || nr == 1 ) { + lm85_write_value(client, LM85_REG_AFAN_HYST1, + (data->zone[0].hyst << 4) + | data->zone[1].hyst + ); + } else { + lm85_write_value(client, LM85_REG_AFAN_HYST2, + (data->zone[2].hyst << 4) + ); + } + } + if (*nrels_mag > 0) { + data->zone[nr].limit = TEMP_TO_REG(results[0]*10); + lm85_write_value(client, LM85_REG_AFAN_LIMIT(nr), + data->zone[nr].limit + ); + } + } +} + +void lm85_pwm_zone(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - LM85_SYSCTL_PWM_ZONE1 ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = ZONE_FROM_REG(data->autofan[nr].config); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 0) { + data->autofan[nr].config = + (data->autofan[nr].config & (~0xe0)) + | ZONE_TO_REG(results[0]) ; + lm85_write_value(client, LM85_REG_AFAN_CONFIG(nr), + data->autofan[nr].config); + } + } +} + +void adm1027_temp_offset(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - ADM1027_SYSCTL_TEMP_OFFSET1 ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 2; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + switch( data->type ) { + case adm1027 : + default : + results[0] = TEMP_FROM_REG(data->temp_offset[nr]); + break ; + case adt7463 : + results[0] = TEMPEXT_FROM_REG(0,data->temp_offset[nr]); + break ; + } + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 0) { + switch( data->type ) { + case adm1027 : + default : + data->temp_offset[nr] = TEMP_TO_REG(results[0]); + break ; + case adt7463 : + data->temp_offset[nr] = EXTTEMP_TO_REG(results[0]); + break ; + }; + lm85_write_value(client, ADM1027_REG_TEMP_OFFSET(nr), + data->temp_offset[nr]); + } + } +} + +void adm1027_fan_ppr(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int old ; + + if (ctl_name != ADM1027_SYSCTL_FAN_PPR) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = PPR_FROM_REG(data->fan_ppr,0); + results[1] = PPR_FROM_REG(data->fan_ppr,1); + results[2] = PPR_FROM_REG(data->fan_ppr,2); + results[3] = PPR_FROM_REG(data->fan_ppr,3); + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = data->fan_ppr ; + if (*nrels_mag > 3) { + old = (old & ~PPR_MASK(3)) | PPR_TO_REG(results[3],3); + }; + if (*nrels_mag > 2) { + old = (old & ~PPR_MASK(2)) | PPR_TO_REG(results[2],2); + }; + if (*nrels_mag > 1) { + old = (old & ~PPR_MASK(1)) | PPR_TO_REG(results[1],1); + }; + if (*nrels_mag > 0) { + old = (old & ~PPR_MASK(0)) | PPR_TO_REG(results[0],0); + lm85_write_value(client, ADM1027_REG_FAN_PPR, old); + data->fan_ppr = old ; + } + } +} + +void adm1027_alarm_mask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + + if( ctl_name != ADM1027_SYSCTL_ALARM_MASK ) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + lm85_update_client(client); + results[0] = INTMASK_FROM_REG(data->alarm_mask); + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag > 0) { + data->alarm_mask = INTMASK_TO_REG(results[0]); + lm85_write_value(client, ADM1027_REG_INTMASK, + data->alarm_mask); + } + } +} + +void adt7463_tmin_ctl(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int nr = ctl_name - ADT7463_SYSCTL_TMIN_CTL1 ; + u16 old ; + + if (nr < 0 || nr > 2) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + old = data->tmin_ctl ; + results[0] = (old & ( 0x2000 << nr )) != 0 ; + results[1] = (old >> (nr*3)) & 0x07 ; + results[2] = (old & ( 0x0400 << nr )) != 0 ; + results[3] = OPPOINT_FROM_REG(data->oppoint[nr]); + *nrels_mag = 4; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + old = data->tmin_ctl ; + if (*nrels_mag > 3) { + data->oppoint[nr] = OPPOINT_TO_REG(results[3]); + lm85_write_value(client, ADT7463_REG_OPPOINT(nr), + data->oppoint[nr]); + }; + if (*nrels_mag > 2) { + if( results[2] ) { + old |= (0x0400 << nr) ; + } else { + old &= ~(0x0400 << nr) ; + } + }; + if (*nrels_mag > 1) { + old &= ~(0x07 << (nr*3)) ; + old |= (results[1] & 0x07) << (nr*3) ; + }; + if (*nrels_mag > 0) { + if( results[0] ) { + old |= 0x2000 << nr ; + } else { + old &= ~(0x2000 << nr) ; + } + lm85_write_value(client, ADT7463_REG_TMIN_CTL, old); + data->tmin_ctl = old ; + } + } +} + +void adt7463_therm_signal(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct lm85_data *data = client->data; + int counts ; + + if (ctl_name != ADT7463_SYSCTL_THERM_SIGNAL) + return ; /* ERROR */ + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + /* Don't call update_client here because + * ADT7463_REG_THERM has to be read every + * 5 seconds to prevent lost counts + */ + down(&data->update_lock); + counts = lm85_read_value(client, ADT7463_REG_THERM) & 0xff; + if( data->therm_total < LONG_MAX - 256 ) { + data->therm_total += counts ; + } + if( counts >= 255 ) { + ++data->therm_ovfl ; + } + up(&data->update_lock); + + results[0] = data->therm_limit ; + results[1] = data->therm_total ; + results[2] = data->therm_ovfl ; + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + /* therm_total and therm_ovfl are read only */ + if (*nrels_mag > 0) { + data->therm_limit = SENSORS_LIMIT(results[0],0,255); + lm85_write_value(client, ADT7463_REG_THERM_LIMIT, + data->therm_limit); + }; + } +} + +static int __initdata lm85_initialized = 0; + +int __init sensors_lm85_init(void) +{ + int res; + + printk("lm85: Version %s (%s)\n", LM_VERSION, LM_DATE); + lm85_initialized = 0; + + if ((res = i2c_add_driver(&lm85_driver))) { + printk("lm85: Driver registration failed, module not inserted.\n"); + lm85_cleanup(); + return res; + } + lm85_initialized++; + return 0; +} + +int __init lm85_cleanup(void) +{ + int res; + + if (lm85_initialized >= 1) { + if ((res = i2c_del_driver(&lm85_driver))) { + printk("lm85: Driver deregistration failed, module not removed.\n"); + return res; + } + lm85_initialized--; + } + return 0; +} + + +#ifdef MODULE + +/* Thanks to Richard Barrington for adding the LM85 to sensors-detect. + * Thanks to Margit Schubert-While for help with + * post 2.7.0 CVS changes + */ +MODULE_AUTHOR("Philip Pokorny "National Semiconductor LM85", - driver => "Unwritten: lm85", + driver => "lm85", i2c_addrs => [0x2c..0x2e], - i2c_detect => sub { lm85_detect @_}, + i2c_detect => sub { lm85_detect 0x01, @_}, + }, + { + name => "Analog Devices ADM1027 or ADT7463", + driver => "lm85", + i2c_addrs => [0x2c..0x2e], + i2c_detect => sub { lm85_detect 0x41, @_}, }, { name => "National Semiconductor LM87", @@ -2219,18 +2225,21 @@ sub lm80_detect return (3); } -# $_[0]: A reference to the file descriptor to access this chip. -# #_[1]: Base address. +# $_[0]: Vendor to check for +# (0x01 = National Semi, 0x41 = Analog Dev) +# $_[1]: A reference to the file descriptor to access this chip. +# #_[2]: Base address. # Returns: undef if not detected, (7) if detected. # Registers used: 0x3e == Vendor register. # 0x3f == Version/Stepping register. # Constants used: 0x01 == National Semiconductor Vendor Id. +# 0x41 == Analog Devices Vendor Id. # 0x60 == Version number. The lower 4 stepping # bits are masked and ignored. sub lm85_detect { - my ($file,$addr) = @_; - return if (i2c_smbus_read_byte_data($file,0x3e)) != 0x01; + my ($vendor,$file,$addr) = @_; + return if (i2c_smbus_read_byte_data($file,0x3e)) != $vendor ; return if (i2c_smbus_read_byte_data($file,0x3f) & 0xf0) != 0x60; return (7); } diff --git a/prog/sensors/chips.c b/prog/sensors/chips.c index e2a0342b..89f7defa 100644 --- a/prog/sensors/chips.c +++ b/prog/sensors/chips.c @@ -1468,6 +1468,244 @@ void print_lm80(const sensors_chip_name *name) free_the_label(&label); } +void print_lm85(const sensors_chip_name *name) +{ + char *label = NULL; + double cur, min, max; + int alarms, alarm_mask, valid; + int is85, is1027; + + is85 = !strcmp(name->prefix,"lm85") + || !strcmp(name->prefix,"lm85b") + || !strcmp(name->prefix,"lm85c") ; + is1027 = !strcmp(name->prefix,"adm1027") + || !strcmp(name->prefix,"adt7463") ; + + if (!sensors_get_feature(*name,SENSORS_LM85_ALARMS,&cur)) + alarms = cur + 0.5; + else { + printf("ERROR: Can't get alarm data!\n"); + alarms = 0; + } + + if (!sensors_get_feature(*name,SENSORS_ADM1027_ALARM_MASK,&cur)) + alarm_mask = cur + 0.5; + else { + printf("ERROR: Can't get alarm mask data!\n"); + alarm_mask = 0; + } + + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_IN0,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_IN0,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_IN0_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_IN0_MAX,&max)) { + if (valid) { + print_label(label,10); + printf("%+7.*f V (min = %+6.2f V, max = %+6.2f V) %s", + (is1027?3:2),cur,min,max,alarms&LM85_ALARM_IN0?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_IN0?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get IN0 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_IN1,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_IN1,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_IN1_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_IN1_MAX,&max)) { + if (valid) { + print_label(label,10); + printf("%+7.*f V (min = %+6.2f V, max = %+6.2f V) %s", + (is1027?3:2),cur,min,max,alarms&LM85_ALARM_IN1?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_IN1?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get IN1 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_IN2,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_IN2,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_IN2_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_IN2_MAX,&max)) { + if (valid) { + print_label(label,10); + printf("%+7.*f V (min = %+6.2f V, max = %+6.2f V) %s", + (is1027?3:2),cur,min,max,alarms&LM85_ALARM_IN2?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_IN2?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get IN2 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_IN3,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_IN3,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_IN3_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_IN3_MAX,&max)) { + if (valid) { + print_label(label,10); + printf("%+6.*f V (min = %+6.2f V, max = %+6.2f V) %s", + (is1027?3:2),cur,min,max,alarms&LM85_ALARM_IN3?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_IN3?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get IN3 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_IN4,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_IN4,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_IN4_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_IN4_MAX,&max)) { + if (valid) { + print_label(label,10); + printf("%+6.*f V (min = %+6.2f V, max = %+6.2f V) %s", + (is1027?3:2),cur,min,max,alarms&LM85_ALARM_IN4?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_IN4?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get IN4 data!\n"); + free_the_label(&label); + + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_FAN1,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_FAN1,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_FAN1_MIN,&min)) { + if (valid) { + print_label(label,10); + printf("%5.0f RPM (min = %4.0f RPM) %s", + cur,min, alarms&LM85_ALARM_FAN1?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_FAN1?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get FAN1 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_FAN2,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_FAN2,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_FAN2_MIN,&min)) { + if (valid) { + print_label(label,10); + printf("%5.0f RPM (min = %4.0f RPM) %s", + cur,min, alarms&LM85_ALARM_FAN2?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_FAN2?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get FAN2 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_FAN3,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_FAN3,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_FAN3_MIN,&min)) { + if (valid) { + print_label(label,10); + printf("%5.0f RPM (min = %4.0f RPM) %s", + cur,min, alarms&LM85_ALARM_FAN3?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_FAN3?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get FAN3 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_FAN4,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_FAN4,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_FAN4_MIN,&min)) { + if (valid) { + print_label(label,10); + printf("%5.0f RPM (min = %4.0f RPM) %s", + cur,min, alarms&LM85_ALARM_FAN4?"ALARM":""); + if (is1027) { printf(alarm_mask&LM85_ALARM_FAN4?" MASKED":""); } + putchar( '\n' ); + } + } else + printf("ERROR: Can't get FAN4 data!\n"); + free_the_label(&label); + + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_TEMP1,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP1,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP1_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP1_MAX,&max)) { + if (valid) { + print_label(label,10); + print_temp_info( cur, max, min, MINMAX, (is1027 ? 2 : 0), 0); + printf( " %s %s %s\n", + alarms&LM85_ALARM_TEMP1?"ALARM":"", + alarms&LM85_ALARM_TEMP1_FAULT?"FAULT":"", + is1027&&(alarm_mask&LM85_ALARM_TEMP1)?"MASKED":"" + ); + } + } else + printf("ERROR: Can't get TEMP1 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_TEMP2,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP2,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP2_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP2_MAX,&max)) { + if (valid) { + print_label(label,10); + print_temp_info( cur, max, min, MINMAX, (is1027 ? 2 : 0), 0); + printf( " %s %s\n", + alarms&LM85_ALARM_TEMP2?"ALARM":"", + is1027&&(alarm_mask&LM85_ALARM_TEMP2)?"MASKED":"" + ); + } + } else + printf("ERROR: Can't get TEMP2 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_TEMP3,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP3,&cur) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP3_MIN,&min) && + !sensors_get_feature(*name,SENSORS_LM85_TEMP3_MAX,&max)) { + if (valid) { + print_label(label,10); + print_temp_info( cur, max, min, MINMAX, (is1027 ? 2 : 0), 0); + printf( " %s %s %s\n", + alarms&LM85_ALARM_TEMP3?"ALARM":"", + alarms&LM85_ALARM_TEMP3_FAULT?"FAULT":"", + is1027&&(alarm_mask&LM85_ALARM_TEMP3)?"MASKED":"" + ); + } + } else + printf("ERROR: Can't get TEMP3 data!\n"); + free_the_label(&label); + + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_PWM1,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_PWM1,&cur)) { + if (valid) { + print_label(label,10); + printf("%4.0f\n", cur); + } + } else + printf("ERROR: Can't get PWM1 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_PWM2,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_PWM2,&cur)) { + if (valid) { + print_label(label,10); + printf("%4.0f\n", cur); + } + } else + printf("ERROR: Can't get PWM2 data!\n"); + free_the_label(&label); + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_PWM3,&label,&valid) && + !sensors_get_feature(*name,SENSORS_LM85_PWM3,&cur)) { + if (valid) { + print_label(label,10); + printf("%4.0f\n", cur); + } + } else + printf("ERROR: Can't get PWM3 data!\n"); + free_the_label(&label); + + if (!sensors_get_label_and_valid(*name,SENSORS_LM85_VID,&label,&valid) + && !sensors_get_feature(*name,SENSORS_LM85_VID,&cur) + && !sensors_get_feature(*name,SENSORS_LM85_VRM,&min) ) { + if (valid) { + print_label(label,10); + printf("%+6.3f V (VRM Version %4.1f)\n",cur,min); + } + } + free_the_label(&label); +} + void print_lm87(const sensors_chip_name *name) { char *label = NULL; diff --git a/prog/sensors/chips.h b/prog/sensors/chips.h index e102299b..68173ce1 100644 --- a/prog/sensors/chips.h +++ b/prog/sensors/chips.h @@ -36,6 +36,7 @@ extern void print_sis5595(const sensors_chip_name *name); extern void print_via686a(const sensors_chip_name *name); extern void print_gl518(const sensors_chip_name *name); extern void print_lm80(const sensors_chip_name *name); +extern void print_lm85(const sensors_chip_name *name); extern void print_w83781d(const sensors_chip_name *name); extern void print_maxilife(const sensors_chip_name *name); extern void print_ddcmon(const sensors_chip_name *name); diff --git a/prog/sensors/main.c b/prog/sensors/main.c index da8c9633..4acf268b 100644 --- a/prog/sensors/main.c +++ b/prog/sensors/main.c @@ -320,6 +320,12 @@ void do_a_print(sensors_chip_name name) print_via686a(&name); else if (!strcmp(name.prefix,"lm80")) print_lm80(&name); + else if (!strcmp(name.prefix,"lm85") || + !strcmp(name.prefix,"lm85b") || + !strcmp(name.prefix,"lm85c") || + !strcmp(name.prefix,"adm1027") || + !strcmp(name.prefix,"adt7463") ) + print_lm85(&name); else if (!strcmp(name.prefix,"lm87")) print_lm87(&name); else if (!strcmp(name.prefix,"gl518sm"))