diff options
author | Linus Torvalds | 2010-03-06 11:33:09 -0800 |
---|---|---|
committer | Linus Torvalds | 2010-03-06 11:33:09 -0800 |
commit | dff6d1c5ef9116a4478908001d72ee67127ecf01 (patch) | |
tree | 43c6e7ff8e8059f8fd733efd45b51f84b510d305 | |
parent | 66ce3cf84deba6cc71dcf43c9d56a4278e5f712d (diff) | |
parent | a0a5e3488a51c46f383c5faa86b53828e30ce153 (diff) |
Merge branch 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging
* 'hwmon-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jdelvare/staging: (23 commits)
hwmon: Remove the deprecated adt7473 driver
hwmon: Fix off-by-one kind values
hwmon: (tmp421) Fix temperature conversions
hwmon: (tmp421) Restore missing inputs
hwmon: Driver for Andigilog aSC7621 family monitoring chips
hwmon: (adt7411) Improve locking
hwmon: Add driver for ADT7411 voltage and temperature sensor
hwmon: (w83793) Add watchdog functionality
hwmon: (g760a) Make rpm_from_cnt static
hwmon: (it87) Validate auto pwm settings
hwmon: (it87) Add support for old automatic fan speed control
hwmon: (it87) Drop dead web links in documentation
hwmon: (it87) Add an entry in MAINTAINERS
hwmon: (it87) Use strict_strtol instead of simple_strtol
hwmon: (it87) Fix many checkpatch errors and warnings
hwmon: (it87) Add support for beep on alarm
hwmon: (it87) Create vid attributes by group
hwmon: (it87) Refactor attributes creation and removal
hwmon: (it87) Expose the PWM/temperature mappings
hwmon: (it87) Display fan outputs in automatic mode as such
...
-rw-r--r-- | Documentation/feature-removal-schedule.txt | 6 | ||||
-rw-r--r-- | Documentation/hwmon/adt7411 | 42 | ||||
-rw-r--r-- | Documentation/hwmon/adt7473 | 74 | ||||
-rw-r--r-- | Documentation/hwmon/asc7621 | 296 | ||||
-rw-r--r-- | Documentation/hwmon/it87 | 53 | ||||
-rw-r--r-- | Documentation/hwmon/lm90 | 22 | ||||
-rw-r--r-- | MAINTAINERS | 14 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 45 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 3 | ||||
-rw-r--r-- | drivers/hwmon/adt7411.c | 366 | ||||
-rw-r--r-- | drivers/hwmon/adt7473.c | 1180 | ||||
-rw-r--r-- | drivers/hwmon/asc7621.c | 1255 | ||||
-rw-r--r-- | drivers/hwmon/fschmd.c | 15 | ||||
-rw-r--r-- | drivers/hwmon/g760a.c | 2 | ||||
-rw-r--r-- | drivers/hwmon/it87.c | 939 | ||||
-rw-r--r-- | drivers/hwmon/lm90.c | 89 | ||||
-rw-r--r-- | drivers/hwmon/tmp401.c | 7 | ||||
-rw-r--r-- | drivers/hwmon/tmp421.c | 24 | ||||
-rw-r--r-- | drivers/hwmon/w83793.c | 482 |
19 files changed, 3335 insertions, 1579 deletions
diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 31575e220f3b..a5cc0db63d7a 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -449,12 +449,6 @@ Who: Alok N Kataria <akataria@vmware.com> ---------------------------- -What: adt7473 hardware monitoring driver -When: February 2010 -Why: Obsoleted by the adt7475 driver. -Who: Jean Delvare <khali@linux-fr.org> - ---------------------------- What: Support for lcd_switch and display_get in asus-laptop driver When: March 2010 Why: These two features use non-standard interfaces. There are the diff --git a/Documentation/hwmon/adt7411 b/Documentation/hwmon/adt7411 new file mode 100644 index 000000000000..1632960f9745 --- /dev/null +++ b/Documentation/hwmon/adt7411 @@ -0,0 +1,42 @@ +Kernel driver adt7411 +===================== + +Supported chips: + * Analog Devices ADT7411 + Prefix: 'adt7411' + Addresses scanned: 0x48, 0x4a, 0x4b + Datasheet: Publicly available at the Analog Devices website + +Author: Wolfram Sang (based on adt7470 by Darrick J. Wong) + +Description +----------- + +This driver implements support for the Analog Devices ADT7411 chip. There may +be other chips that implement this interface. + +The ADT7411 can use an I2C/SMBus compatible 2-wire interface or an +SPI-compatible 4-wire interface. It provides a 10-bit analog to digital +converter which measures 1 temperature, vdd and 8 input voltages. It has an +internal temperature sensor, but an external one can also be connected (one +loses 2 inputs then). There are high- and low-limit registers for all inputs. + +Check the datasheet for details. + +sysfs-Interface +--------------- + +in0_input - vdd voltage input +in[1-8]_input - analog 1-8 input +temp1_input - temperature input + +Besides standard interfaces, this driver adds (0 = off, 1 = on): + + adc_ref_vdd - Use vdd as reference instead of 2.25 V + fast_sampling - Sample at 22.5 kHz instead of 1.4 kHz, but drop filters + no_average - Turn off averaging over 16 samples + +Notes +----- + +SPI, external temperature sensor and limit registers are not supported yet. diff --git a/Documentation/hwmon/adt7473 b/Documentation/hwmon/adt7473 deleted file mode 100644 index 446612bd1fb9..000000000000 --- a/Documentation/hwmon/adt7473 +++ /dev/null @@ -1,74 +0,0 @@ -Kernel driver adt7473 -====================== - -Supported chips: - * Analog Devices ADT7473 - Prefix: 'adt7473' - Addresses scanned: I2C 0x2C, 0x2D, 0x2E - Datasheet: Publicly available at the Analog Devices website - -Author: Darrick J. Wong - -This driver is depreacted, please use the adt7475 driver instead. - -Description ------------ - -This driver implements support for the Analog Devices ADT7473 chip family. - -The ADT7473 uses the 2-wire interface compatible with the SMBUS 2.0 -specification. Using an analog to digital converter it measures three (3) -temperatures and two (2) voltages. It has four (4) 16-bit counters for -measuring fan speed. There are three (3) PWM outputs that can be used -to control fan speed. - -A sophisticated control system for the PWM outputs is designed into the -ADT7473 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 ADT7473 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 ADT7473 will signal an ALARM if -any measured value exceeds either limit. - -The ADT7473 samples all inputs continuously. The driver will not read -the registers more often than once every other second. Further, -configuration data is only read once per minute. - -Special Features ----------------- - -The ADT7473 have a 10-bit ADC and can therefore measure temperatures -with 0.25 degC resolution. Temperature readings can be configured either -for twos complement format or "Offset 64" format, wherein 63 is subtracted -from the raw value to get the temperature value. - -The Analog Devices datasheet is very detailed and describes a procedure for -determining an optimal configuration for the automatic PWM control. - -Configuration Notes -------------------- - -Besides standard interfaces driver adds the following: - -* PWM Control - -* pwm#_auto_point1_pwm and temp#_auto_point1_temp and -* pwm#_auto_point2_pwm and temp#_auto_point2_temp - - -point1: Set the pwm speed at a lower temperature bound. -point2: Set the pwm speed at a higher temperature bound. - -The ADT7473 will scale the pwm between the lower and higher pwm speed when -the temperature is between the two temperature boundaries. PWM values range -from 0 (off) to 255 (full speed). Fan speed will be set to maximum when the -temperature sensor associated with the PWM control exceeds temp#_max. - -Notes ------ - -The NVIDIA binary driver presents an ADT7473 chip via an on-card i2c bus. -Unfortunately, they fail to set the i2c adapter class, so this driver may -fail to find the chip until the nvidia driver is patched. diff --git a/Documentation/hwmon/asc7621 b/Documentation/hwmon/asc7621 new file mode 100644 index 000000000000..7287be7e1f21 --- /dev/null +++ b/Documentation/hwmon/asc7621 @@ -0,0 +1,296 @@ +Kernel driver asc7621 +================== + +Supported chips: + Andigilog aSC7621 and aSC7621a + Prefix: 'asc7621' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: http://www.fairview5.com/linux/asc7621/asc7621.pdf + +Author: + George Joseph + +Description provided by Dave Pivin @ Andigilog: + +Andigilog has both the PECI and pre-PECI versions of the Heceta-6, as +Intel calls them. Heceta-6e has high frequency PWM and Heceta-6p has +added PECI and a 4th thermal zone. The Andigilog aSC7611 is the +Heceta-6e part and aSC7621 is the Heceta-6p part. They are both in +volume production, shipping to Intel and their subs. + +We have enhanced both parts relative to the governing Intel +specification. First enhancement is temperature reading resolution. We +have used registers below 20h for vendor-specific functions in addition +to those in the Intel-specified vendor range. + +Our conversion process produces a result that is reported as two bytes. +The fan speed control uses this finer value to produce a "step-less" fan +PWM output. These two bytes are "read-locked" to guarantee that once a +high or low byte is read, the other byte is locked-in until after the +next read of any register. So to get an atomic reading, read high or low +byte, then the very next read should be the opposite byte. Our data +sheet says 10-bits of resolution, although you may find the lower bits +are active, they are not necessarily reliable or useful externally. We +chose not to mask them. + +We employ significant filtering that is user tunable as described in the +data sheet. Our temperature reports and fan PWM outputs are very smooth +when compared to the competition, in addition to the higher resolution +temperature reports. The smoother PWM output does not require user +intervention. + +We offer GPIO features on the former VID pins. These are open-drain +outputs or inputs and may be used as general purpose I/O or as alarm +outputs that are based on temperature limits. These are in 19h and 1Ah. + +We offer flexible mapping of temperature readings to thermal zones. Any +temperature may be mapped to any zone, which has a default assignment +that follows Intel's specs. + +Since there is a fan to zone assignment that allows for the "hotter" of +a set of zones to control the PWM of an individual fan, but there is no +indication to the user, we have added an indicator that shows which zone +is currently controlling the PWM for a given fan. This is in register +00h. + +Both remote diode temperature readings may be given an offset value such +that the reported reading as well as the temperature used to determine +PWM may be offset for system calibration purposes. + +PECI Extended configuration allows for having more than two domains per +PECI address and also provides an enabling function for each PECI +address. One could use our flexible zone assignment to have a zone +assigned to up to 4 PECI addresses. This is not possible in the default +Intel configuration. This would be useful in multi-CPU systems with +individual fans on each that would benefit from individual fan control. +This is in register 0Eh. + +The tachometer measurement system is flexible and able to adapt to many +fan types. We can also support pulse-stretched PWM so that 3-wire fans +may be used. These characteristics are in registers 04h to 07h. + +Finally, we have added a tach disable function that turns off the tach +measurement system for individual tachs in order to save power. That is +in register 75h. + +-- +aSC7621 Product Description + +The aSC7621 has a two wire digital interface compatible with SMBus 2.0. +Using a 10-bit ADC, the aSC7621 measures the temperature of two remote diode +connected transistors as well as its own die. Support for Platform +Environmental Control Interface (PECI) is included. + +Using temperature information from these four zones, an automatic fan speed +control algorithm is employed to minimize acoustic impact while achieving +recommended CPU temperature under varying operational loads. + +To set fan speed, the aSC7621 has three independent pulse width modulation +(PWM) outputs that are controlled by one, or a combination of three, +temperature zones. Both high- and low-frequency PWM ranges are supported. + +The aSC7621 also includes a digital filter that can be invoked to smooth +temperature readings for better control of fan speed and minimum acoustic +impact. + +The aSC7621 has tachometer inputs to measure fan speed on up to four fans. +Limit and status registers for all measured values are included to alert +the system host that any measurements are outside of programmed limits +via status registers. + +System voltages of VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard power are +monitored efficiently with internal scaling resistors. + +Features +- Supports PECI interface and monitors internal and remote thermal diodes +- 2-wire, SMBus 2.0 compliant, serial interface +- 10-bit ADC +- Monitors VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard/processor supplies +- Programmable autonomous fan control based on temperature readings +- Noise filtering of temperature reading for fan speed control +- 0.25C digital temperature sensor resolution +- 3 PWM fan speed control outputs for 2-, 3- or 4-wire fans and up to 4 fan + tachometer inputs +- Enhanced measured temperature to Temperature Zone assignment. +- Provides high and low PWM frequency ranges +- 3 GPIO pins for custom use +- 24-Lead QSOP package + +Configuration Notes +=================== + +Except where noted below, the sysfs entries created by this driver follow +the standards defined in "sysfs-interface". + +temp1_source + 0 (default) peci_legacy = 0, Remote 1 Temperature + peci_legacy = 1, PECI Processor Temperature 0 + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp2_source + 0 (default) Internal Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp3_source + 0 (default) Remote 2 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp4_source + 0 (default) peci_legacy = 0, PECI Processor Temperature 0 + peci_legacy = 1, Remote 1 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp[1-4]_smoothing_enable +temp[1-4]_smoothing_time + Smooths spikes in temp readings caused by noise. + Valid values in milliseconds are: + 35000 + 17600 + 11800 + 7000 + 4400 + 3000 + 1600 + 800 + +temp[1-4]_crit + When the corresponding zone temperature reaches this value, + ALL pwm outputs will got to 100%. + +temp[5-8]_input +temp[5-8]_enable + The aSC7621 can also read temperatures provided by the processor + via the PECI bus. Usually these are "core" temps and are relative + to the point where the automatic thermal control circuit starts + throttling. This means that these are usually negative numbers. + +pwm[1-3]_enable + 0 Fan off. + 1 Fan on manual control. + 2 Fan on automatic control and will run at the minimum pwm + if the temperature for the zone is below the minimum. + 3 Fan on automatic control but will be off if the temperature + for the zone is below the minimum. + 4-254 Ignored. + 255 Fan on full. + +pwm[1-3]_auto_channels + Bitmap as described in sysctl-interface with the following + exceptions... + Only the following combination of zones (and their corresponding masks) + are valid: + 1 + 2 + 3 + 2,3 + 1,2,3 + 4 + 1,2,3,4 + + Special values: + 0 Disabled. + 16 Fan on manual control. + 31 Fan on full. + + +pwm[1-3]_invert + When set, inverts the meaning of pwm[1-3]. + i.e. when pwm = 0, the fan will be on full and + when pwm = 255 the fan will be off. + +pwm[1-3]_freq + PWM frequency in Hz + Valid values in Hz are: + + 10 + 15 + 23 + 30 (default) + 38 + 47 + 62 + 94 + 23000 + 24000 + 25000 + 26000 + 27000 + 28000 + 29000 + 30000 + + Setting any other value will be ignored. + +peci_enable + Enables or disables PECI + +peci_avg + Input filter average time. + + 0 0 Sec. (no Smoothing) (default) + 1 0.25 Sec. + 2 0.5 Sec. + 3 1.0 Sec. + 4 2.0 Sec. + 5 4.0 Sec. + 6 8.0 Sec. + 7 0.0 Sec. + +peci_legacy + + 0 Standard Mode (default) + Remote Diode 1 reading is associated with + Temperature Zone 1, PECI is associated with + Zone 4 + + 1 Legacy Mode + PECI is associated with Temperature Zone 1, + Remote Diode 1 is associated with Zone 4 + +peci_diode + Diode filter + + 0 0.25 Sec. + 1 1.1 Sec. + 2 2.4 Sec. (default) + 3 3.4 Sec. + 4 5.0 Sec. + 5 6.8 Sec. + 6 10.2 Sec. + 7 16.4 Sec. + +peci_4domain + Four domain enable + + 0 1 or 2 Domains for enabled processors (default) + 1 3 or 4 Domains for enabled processors + +peci_domain + Domain + + 0 Processor contains a single domain (0) (default) + 1 Processor contains two domains (0,1) diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index f9ba96c0ac4a..8d08bf0d38ed 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -5,31 +5,23 @@ Supported chips: * IT8705F Prefix: 'it87' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8705F_V.0.4.1.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8712F Prefix: 'it8712' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8712F_V0.9.1.pdf - http://www.ite.com.tw/product_info/file/pc/Errata%20V0.1%20for%20IT8712F%20V0.9.1.pdf - http://www.ite.com.tw/product_info/file/pc/IT8712F_V0.9.3.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8716F/IT8726F Prefix: 'it8716' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8716F_V0.3.ZIP - http://www.ite.com.tw/product_info/file/pc/IT8726F_V0.3.pdf + Datasheet: Once publicly available at the ITE website, but no longer * IT8718F Prefix: 'it8718' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Publicly available at the ITE website - http://www.ite.com.tw/product_info/file/pc/IT8718F_V0.2.zip - http://www.ite.com.tw/product_info/file/pc/IT8718F_V0%203_(for%20C%20version).zip + Datasheet: Once publicly available at the ITE website, but no longer * IT8720F Prefix: 'it8720' Addresses scanned: from Super I/O config space (8 I/O ports) - Datasheet: Not yet publicly available. + Datasheet: Not publicly available * SiS950 [clone of IT8705F] Prefix: 'it87' Addresses scanned: from Super I/O config space (8 I/O ports) @@ -136,6 +128,10 @@ registers are read whenever any data is read (unless it is less than 1.5 seconds since the last update). This means that you can easily miss once-only alarms. +Out-of-limit readings can also result in beeping, if the chip is properly +wired and configured. Beeping can be enabled or disabled per sensor type +(temperatures, voltages and fans.) + The IT87xx only updates its values each 1.5 seconds; reading it more often will do no harm, but will return 'old' values. @@ -150,11 +146,38 @@ Fan speed control ----------------- The fan speed control features are limited to manual PWM mode. Automatic -"Smart Guardian" mode control handling is not implemented. However -if you want to go for "manual mode" just write 1 to pwmN_enable. +"Smart Guardian" mode control handling is only implemented for older chips +(see below.) However if you want to go for "manual mode" just write 1 to +pwmN_enable. If you are only able to control the fan speed with very small PWM values, try lowering the PWM base frequency (pwm1_freq). Depending on the fan, it may give you a somewhat greater control range. The same frequency is used to drive all fan outputs, which is why pwm2_freq and pwm3_freq are read-only. + + +Automatic fan speed control (old interface) +------------------------------------------- + +The driver supports the old interface to automatic fan speed control +which is implemented by IT8705F chips up to revision F and IT8712F +chips up to revision G. + +This interface implements 4 temperature vs. PWM output trip points. +The PWM output of trip point 4 is always the maximum value (fan running +at full speed) while the PWM output of the other 3 trip points can be +freely chosen. The temperature of all 4 trip points can be freely chosen. +Additionally, trip point 1 has an hysteresis temperature attached, to +prevent fast switching between fan on and off. + +The chip automatically computes the PWM output value based on the input +temperature, based on this simple rule: if the temperature value is +between trip point N and trip point N+1 then the PWM output value is +the one of trip point N. The automatic control mode is less flexible +than the manual control mode, but it reacts faster, is more robust and +doesn't use CPU cycles. + +Trip points must be set properly before switching to automatic fan speed +control mode. The driver will perform basic integrity checks before +actually switching to automatic control mode. diff --git a/Documentation/hwmon/lm90 b/Documentation/hwmon/lm90 index 93d8e3d55150..6a03dd4bcc94 100644 --- a/Documentation/hwmon/lm90 +++ b/Documentation/hwmon/lm90 @@ -84,6 +84,10 @@ Supported chips: Addresses scanned: I2C 0x4c Datasheet: Publicly available at the Maxim website http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3500 + * Winbond/Nuvoton W83L771AWG/ASG + Prefix: 'w83l771' + Addresses scanned: I2C 0x4c + Datasheet: Not publicly available, can be requested from Nuvoton Author: Jean Delvare <khali@linux-fr.org> @@ -147,6 +151,12 @@ MAX6680 and MAX6681: * Selectable address * Remote sensor type selection +W83L771AWG/ASG + * The AWG and ASG variants only differ in package format. + * Filter and alert configuration register at 0xBF + * Diode ideality factor configuration (remote sensor) at 0xE3 + * Moving average (depending on conversion rate) + All temperature values are given in degrees Celsius. Resolution is 1.0 degree for the local temperature, 0.125 degree for the remote temperature, except for the MAX6657, MAX6658 and MAX6659 which have a @@ -163,6 +173,18 @@ The lm90 driver will not update its values more frequently than every other second; reading them more often will do no harm, but will return 'old' values. +SMBus Alert Support +------------------- + +This driver has basic support for SMBus alert. When an alert is received, +the status register is read and the faulty temperature channel is logged. + +The Analog Devices chips (ADM1032 and ADT7461) do not implement the SMBus +alert protocol properly so additional care is needed: the ALERT output is +disabled when an alert is received, and is re-enabled only when the alarm +is gone. Otherwise the chip would block alerts from other chips in the bus +as long as the alarm is active. + PEC Support ----------- diff --git a/MAINTAINERS b/MAINTAINERS index e0d5c1a949d9..bfa4fd1f3c03 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -968,6 +968,13 @@ W: http://www.arm.linux.org.uk/ S: Maintained F: arch/arm/vfp/ +ASC7621 HARDWARE MONITOR DRIVER +M: George Joseph <george.joseph@fairview5.com> +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/asc7621 +F: drivers/hwmon/asc7621.c + ASUS ACPI EXTRAS DRIVER M: Corentin Chary <corentincj@iksaif.net> M: Karol Kozimor <sziwan@users.sourceforge.net> @@ -3048,6 +3055,13 @@ W: http://www.melware.de S: Maintained F: drivers/isdn/hardware/eicon/ +IT87 HARDWARE MONITORING DRIVER +M: Jean Delvare <khali@linux-fr.org> +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/it87 +F: drivers/hwmon/it87.c + IVTV VIDEO4LINUX DRIVER M: Andy Walls <awalls@radix.net> L: ivtv-devel@ivtvdriver.org (moderated for non-subscribers) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 68cf87749a42..e4595e6147b4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -170,6 +170,16 @@ config SENSORS_ADM9240 This driver can also be built as a module. If so, the module will be called adm9240. +config SENSORS_ADT7411 + tristate "Analog Devices ADT7411" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Analog Devices + ADT7411 voltage and temperature monitoring chip. + + This driver can also be built as a module. If so, the module + will be called adt7411. + config SENSORS_ADT7462 tristate "Analog Devices ADT7462" depends on I2C && EXPERIMENTAL @@ -190,20 +200,6 @@ config SENSORS_ADT7470 This driver can also be built as a module. If so, the module will be called adt7470. -config SENSORS_ADT7473 - tristate "Analog Devices ADT7473 (DEPRECATED)" - depends on I2C && EXPERIMENTAL - select SENSORS_ADT7475 - help - If you say yes here you get support for the Analog Devices - ADT7473 temperature monitoring chips. - - This driver is deprecated, you should use the adt7475 driver - instead. - - This driver can also be built as a module. If so, the module - will be called adt7473. - config SENSORS_ADT7475 tristate "Analog Devices ADT7473, ADT7475, ADT7476 and ADT7490" depends on I2C && EXPERIMENTAL @@ -216,6 +212,19 @@ config SENSORS_ADT7475 This driver can also be build as a module. If so, the module will be called adt7475. +config SENSORS_ASC7621 + tristate "Andigilog aSC7621" + depends on HWMON && I2C + help + If you say yes here you get support for the aSC7621 + family of SMBus sensors chip found on most Intel X48, X38, 975, + 965 and 945 desktop boards. Currently supported chips: + aSC7621 + aSC7621a + + This driver can also be built as a module. If so, the module + will be called asc7621. + config SENSORS_K8TEMP tristate "AMD Athlon64/FX or Opteron temperature sensor" depends on X86 && PCI && EXPERIMENTAL @@ -563,9 +572,10 @@ config SENSORS_LM90 depends on I2C help If you say yes here you get support for National Semiconductor LM90, - LM86, LM89 and LM99, Analog Devices ADM1032 and ADT7461, and Maxim + LM86, LM89 and LM99, Analog Devices ADM1032 and ADT7461, Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, - MAX6680, MAX6681 and MAX6692 sensor chips. + MAX6680, MAX6681 and MAX6692, and Winbond/Nuvoton W83L771AWG/ASG + sensor chips. This driver can also be built as a module. If so, the module will be called lm90. @@ -909,7 +919,8 @@ config SENSORS_W83793 select HWMON_VID help If you say yes here you get support for the Winbond W83793 - hardware monitoring chip. + hardware monitoring chip, including support for the integrated + watchdog. This driver can also be built as a module. If so, the module will be called w83793. diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 4bc215c0953f..4aa1a3d112ad 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -29,12 +29,13 @@ obj-$(CONFIG_SENSORS_ADM1029) += adm1029.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o obj-$(CONFIG_SENSORS_ADS7828) += ads7828.o +obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o -obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ +obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c new file mode 100644 index 000000000000..3471884e42d2 --- /dev/null +++ b/drivers/hwmon/adt7411.c @@ -0,0 +1,366 @@ +/* + * Driver for the ADT7411 (I2C/SPI 8 channel 10 bit ADC & temperature-sensor) + * + * Copyright (C) 2008, 2010 Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * TODO: SPI, support for external temperature sensor + * use power-down mode for suspend?, interrupt handling? + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#define ADT7411_REG_INT_TEMP_VDD_LSB 0x03 +#define ADT7411_REG_EXT_TEMP_AIN14_LSB 0x04 +#define ADT7411_REG_VDD_MSB 0x06 +#define ADT7411_REG_INT_TEMP_MSB 0x07 +#define ADT7411_REG_EXT_TEMP_AIN1_MSB 0x08 + +#define ADT7411_REG_CFG1 0x18 +#define ADT7411_CFG1_START_MONITOR (1 << 0) + +#define ADT7411_REG_CFG2 0x19 +#define ADT7411_CFG2_DISABLE_AVG (1 << 5) + +#define ADT7411_REG_CFG3 0x1a +#define ADT7411_CFG3_ADC_CLK_225 (1 << 0) +#define ADT7411_CFG3_REF_VDD (1 << 4) + +#define ADT7411_REG_DEVICE_ID 0x4d +#define ADT7411_REG_MANUFACTURER_ID 0x4e + +#define ADT7411_DEVICE_ID 0x2 +#define ADT7411_MANUFACTURER_ID 0x41 + +static const unsigned short normal_i2c[] = { 0x48, 0x4a, 0x4b, I2C_CLIENT_END }; + +struct adt7411_data { + struct mutex device_lock; /* for "atomic" device accesses */ + struct mutex update_lock; + unsigned long next_update; + int vref_cached; + struct device *hwmon_dev; +}; + +/* + * When reading a register containing (up to 4) lsb, all associated + * msb-registers get locked by the hardware. After _one_ of those msb is read, + * _all_ are unlocked. In order to use this locking correctly, reading lsb/msb + * is protected here with a mutex, too. + */ +static int adt7411_read_10_bit(struct i2c_client *client, u8 lsb_reg, + u8 msb_reg, u8 lsb_shift) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int val, tmp; + + mutex_lock(&data->device_lock); + + val = i2c_smbus_read_byte_data(client, lsb_reg); + if (val < 0) + goto exit_unlock; + + tmp = (val >> lsb_shift) & 3; + val = i2c_smbus_read_byte_data(client, msb_reg); + + if (val >= 0) + val = (val << 2) | tmp; + + exit_unlock: + mutex_unlock(&data->device_lock); + + return val; +} + +static int adt7411_modify_bit(struct i2c_client *client, u8 reg, u8 bit, + bool flag) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + int ret, val; + + mutex_lock(&data->device_lock); + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + goto exit_unlock; + + if (flag) + val = ret | bit; + else + val = ret & ~bit; + + ret = i2c_smbus_write_byte_data(client, reg, val); + + exit_unlock: + mutex_unlock(&data->device_lock); + return ret; +} + +static ssize_t adt7411_show_vdd(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + + return ret < 0 ? ret : sprintf(buf, "%u\n", ret * 7000 / 1024); +} + +static ssize_t adt7411_show_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int val = adt7411_read_10_bit(client, ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_INT_TEMP_MSB, 0); + + if (val < 0) + return val; + + val = val & 0x200 ? val - 0x400 : val; /* 10 bit signed */ + + return sprintf(buf, "%d\n", val * 250); +} + +static ssize_t adt7411_show_input(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int nr = to_sensor_dev_attr(attr)->index; + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int val; + u8 lsb_reg, lsb_shift; + + mutex_lock(&data->update_lock); + if (time_after_eq(jiffies, data->next_update)) { + val = i2c_smbus_read_byte_data(client, ADT7411_REG_CFG3); + if (val < 0) + goto exit_unlock; + + if (val & ADT7411_CFG3_REF_VDD) { + val = adt7411_read_10_bit(client, + ADT7411_REG_INT_TEMP_VDD_LSB, + ADT7411_REG_VDD_MSB, 2); + if (val < 0) + goto exit_unlock; + + data->vref_cached = val * 7000 / 1024; + } else { + data->vref_cached = 2250; + } + + data->next_update = jiffies + HZ; + } + + lsb_reg = ADT7411_REG_EXT_TEMP_AIN14_LSB + (nr >> 2); + lsb_shift = 2 * (nr & 0x03); + val = adt7411_read_10_bit(client, lsb_reg, + ADT7411_REG_EXT_TEMP_AIN1_MSB + nr, lsb_shift); + if (val < 0) + goto exit_unlock; + + val = sprintf(buf, "%u\n", val * data->vref_cached / 1024); + exit_unlock: + mutex_unlock(&data->update_lock); + return val; +} + +static ssize_t adt7411_show_bit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute_2 *attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + int ret = i2c_smbus_read_byte_data(client, attr2->index); + + return ret < 0 ? ret : sprintf(buf, "%u\n", !!(ret & attr2->nr)); +} + +static ssize_t adt7411_set_bit(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct sensor_device_attribute_2 *s_attr2 = to_sensor_dev_attr_2(attr); + struct i2c_client *client = to_i2c_client(dev); + struct adt7411_data *data = i2c_get_clientdata(client); + int ret; + unsigned long flag; + + ret = strict_strtoul(buf, 0, &flag); + if (ret || flag > 1) + return -EINVAL; + + ret = adt7411_modify_bit(client, s_attr2->index, s_attr2->nr, flag); + + /* force update */ + mutex_lock(&data->update_lock); + data->next_update = jiffies; + mutex_unlock(&data->update_lock); + + return ret < 0 ? ret : count; +} + +#define ADT7411_BIT_ATTR(__name, __reg, __bit) \ + SENSOR_DEVICE_ATTR_2(__name, S_IRUGO | S_IWUSR, adt7411_show_bit, \ + adt7411_set_bit, __bit, __reg) + +static DEVICE_ATTR(temp1_input, S_IRUGO, adt7411_show_temp, NULL); +static DEVICE_ATTR(in0_input, S_IRUGO, adt7411_show_vdd, NULL); +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, adt7411_show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, adt7411_show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, adt7411_show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, adt7411_show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, adt7411_show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, adt7411_show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, adt7411_show_input, NULL, 6); +static SENSOR_DEVICE_ATTR(in8_input, S_IRUGO, adt7411_show_input, NULL, 7); +static ADT7411_BIT_ATTR(no_average, ADT7411_REG_CFG2, ADT7411_CFG2_DISABLE_AVG); +static ADT7411_BIT_ATTR(fast_sampling, ADT7411_REG_CFG3, ADT7411_CFG3_ADC_CLK_225); +static ADT7411_BIT_ATTR(adc_ref_vdd, ADT7411_REG_CFG3, ADT7411_CFG3_REF_VDD); + +static struct attribute *adt7411_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_in0_input.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_no_average.dev_attr.attr, + &sensor_dev_attr_fast_sampling.dev_attr.attr, + &sensor_dev_attr_adc_ref_vdd.dev_attr.attr, + NULL +}; + +static const struct attribute_group adt7411_attr_grp = { + .attrs = adt7411_attrs, +}; + +static int adt7411_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int val; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_MANUFACTURER_ID); + if (val < 0 || val != ADT7411_MANUFACTURER_ID) { + dev_dbg(&client->dev, "Wrong manufacturer ID. Got %d, " + "expected %d\n", val, ADT7411_MANUFACTURER_ID); + return -ENODEV; + } + + val = i2c_smbus_read_byte_data(client, ADT7411_REG_DEVICE_ID); + if (val < 0 || val != ADT7411_DEVICE_ID) { + dev_dbg(&client->dev, "Wrong device ID. Got %d, " + "expected %d\n", val, ADT7411_DEVICE_ID); + return -ENODEV; + } + + strlcpy(info->type, "adt7411", I2C_NAME_SIZE); + + return 0; +} + +static int __devinit adt7411_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adt7411_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->device_lock); + mutex_init(&data->update_lock); + + ret = adt7411_modify_bit(client, ADT7411_REG_CFG1, + ADT7411_CFG1_START_MONITOR, 1); + if (ret < 0) + goto exit_free; + + /* force update on first occasion */ + data->next_update = jiffies; + + ret = sysfs_create_group(&client->dev.kobj, &adt7411_attr_grp); + if (ret) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + dev_info(&client->dev, "successfully registered\n"); + + return 0; + + exit_remove: + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + exit_free: + i2c_set_clientdata(client, NULL); + kfree(data); + return ret; +} + +static int __devexit adt7411_remove(struct i2c_client *client) +{ + struct adt7411_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &adt7411_attr_grp); + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id adt7411_id[] = { + { "adt7411", 0 }, + { } +}; + +static struct i2c_driver adt7411_driver = { + .driver = { + .name = "adt7411", + }, + .probe = adt7411_probe, + .remove = __devexit_p(adt7411_remove), + .id_table = adt7411_id, + .detect = adt7411_detect, + .address_list = normal_i2c, + .class = I2C_CLASS_HWMON, +}; + +static int __init sensors_adt7411_init(void) +{ + return i2c_add_driver(&adt7411_driver); +} +module_init(sensors_adt7411_init) + +static void __exit sensors_adt7411_exit(void) +{ + i2c_del_driver(&adt7411_driver); +} +module_exit(sensors_adt7411_exit) + +MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and " + "Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_DESCRIPTION("ADT7411 driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hwmon/adt7473.c b/drivers/hwmon/adt7473.c deleted file mode 100644 index 434576f61c84..000000000000 --- a/drivers/hwmon/adt7473.c +++ /dev/null @@ -1,1180 +0,0 @@ -/* - * A hwmon driver for the Analog Devices ADT7473 - * Copyright (C) 2007 IBM - * - * Author: Darrick J. Wong <djwong@us.ibm.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/module.h> -#include <linux/jiffies.h> -#include <linux/i2c.h> -#include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> -#include <linux/err.h> -#include <linux/mutex.h> -#include <linux/delay.h> -#include <linux/log2.h> - -/* Addresses to scan */ -static const unsigned short normal_i2c[] = { 0x2C, 0x2D, 0x2E, I2C_CLIENT_END }; - -/* ADT7473 registers */ -#define ADT7473_REG_BASE_ADDR 0x20 - -#define ADT7473_REG_VOLT_BASE_ADDR 0x21 -#define ADT7473_REG_VOLT_MIN_BASE_ADDR 0x46 - -#define ADT7473_REG_TEMP_BASE_ADDR 0x25 -#define ADT7473_REG_TEMP_LIMITS_BASE_ADDR 0x4E -#define ADT7473_REG_TEMP_TMIN_BASE_ADDR 0x67 -#define ADT7473_REG_TEMP_TMAX_BASE_ADDR 0x6A - -#define ADT7473_REG_FAN_BASE_ADDR 0x28 -#define ADT7473_REG_FAN_MIN_BASE_ADDR 0x54 - -#define ADT7473_REG_PWM_BASE_ADDR 0x30 -#define ADT7473_REG_PWM_MIN_BASE_ADDR 0x64 -#define ADT7473_REG_PWM_MAX_BASE_ADDR 0x38 -#define ADT7473_REG_PWM_BHVR_BASE_ADDR 0x5C -#define ADT7473_PWM_BHVR_MASK 0xE0 -#define ADT7473_PWM_BHVR_SHIFT 5 - -#define ADT7473_REG_CFG1 0x40 -#define ADT7473_CFG1_START 0x01 -#define ADT7473_CFG1_READY 0x04 -#define ADT7473_REG_CFG2 0x73 -#define ADT7473_REG_CFG3 0x78 -#define ADT7473_REG_CFG4 0x7D -#define ADT7473_CFG4_MAX_DUTY_AT_OVT 0x08 -#define ADT7473_REG_CFG5 0x7C -#define ADT7473_CFG5_TEMP_TWOS 0x01 -#define ADT7473_CFG5_TEMP_OFFSET 0x02 - -#define ADT7473_REG_DEVICE 0x3D -#define ADT7473_VENDOR 0x41 -#define ADT7473_REG_VENDOR 0x3E -#define ADT7473_DEVICE 0x73 -#define ADT7473_REG_REVISION 0x3F -#define ADT7473_REV_68 0x68 -#define ADT7473_REV_69 0x69 - -#define ADT7473_REG_ALARM1 0x41 -#define ADT7473_VCCP_ALARM 0x02 -#define ADT7473_VCC_ALARM 0x04 -#define ADT7473_R1T_ALARM 0x10 -#define ADT7473_LT_ALARM 0x20 -#define ADT7473_R2T_ALARM 0x40 -#define ADT7473_OOL 0x80 -#define ADT7473_REG_ALARM2 0x42 -#define ADT7473_OVT_ALARM 0x02 -#define ADT7473_FAN1_ALARM 0x04 -#define ADT7473_FAN2_ALARM 0x08 -#define ADT7473_FAN3_ALARM 0x10 -#define ADT7473_FAN4_ALARM 0x20 -#define ADT7473_R1T_SHORT 0x40 -#define ADT7473_R2T_SHORT 0x80 - -#define ALARM2(x) ((x) << 8) - -#define ADT7473_VOLT_COUNT 2 -#define ADT7473_REG_VOLT(x) (ADT7473_REG_VOLT_BASE_ADDR + (x)) -#define ADT7473_REG_VOLT_MIN(x) (ADT7473_REG_VOLT_MIN_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_VOLT_MAX(x) (ADT7473_REG_VOLT_MIN_BASE_ADDR + \ - ((x) * 2) + 1) - -#define ADT7473_TEMP_COUNT 3 -#define ADT7473_REG_TEMP(x) (ADT7473_REG_TEMP_BASE_ADDR + (x)) -#define ADT7473_REG_TEMP_MIN(x) (ADT7473_REG_TEMP_LIMITS_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_TEMP_MAX(x) (ADT7473_REG_TEMP_LIMITS_BASE_ADDR + \ - ((x) * 2) + 1) -#define ADT7473_REG_TEMP_TMIN(x) (ADT7473_REG_TEMP_TMIN_BASE_ADDR + (x)) -#define ADT7473_REG_TEMP_TMAX(x) (ADT7473_REG_TEMP_TMAX_BASE_ADDR + (x)) - -#define ADT7473_FAN_COUNT 4 -#define ADT7473_REG_FAN(x) (ADT7473_REG_FAN_BASE_ADDR + ((x) * 2)) -#define ADT7473_REG_FAN_MIN(x) (ADT7473_REG_FAN_MIN_BASE_ADDR + ((x) * 2)) - -#define ADT7473_PWM_COUNT 3 -#define ADT7473_REG_PWM(x) (ADT7473_REG_PWM_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_MAX(x) (ADT7473_REG_PWM_MAX_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_MIN(x) (ADT7473_REG_PWM_MIN_BASE_ADDR + (x)) -#define ADT7473_REG_PWM_BHVR(x) (ADT7473_REG_PWM_BHVR_BASE_ADDR + (x)) - -/* How often do we reread sensors values? (In jiffies) */ -#define SENSOR_REFRESH_INTERVAL (2 * HZ) - -/* How often do we reread sensor limit values? (In jiffies) */ -#define LIMIT_REFRESH_INTERVAL (60 * HZ) - -/* datasheet says to divide this number by the fan reading to get fan rpm */ -#define FAN_PERIOD_TO_RPM(x) ((90000 * 60) / (x)) -#define FAN_RPM_TO_PERIOD FAN_PERIOD_TO_RPM -#define FAN_PERIOD_INVALID 65535 -#define FAN_DATA_VALID(x) ((x) && (x) != FAN_PERIOD_INVALID) - -struct adt7473_data { - struct device *hwmon_dev; - struct attribute_group attrs; - struct mutex lock; - char sensors_valid; - char limits_valid; - unsigned long sensors_last_updated; /* In jiffies */ - unsigned long limits_last_updated; /* In jiffies */ - - u8 volt[ADT7473_VOLT_COUNT]; - s8 volt_min[ADT7473_VOLT_COUNT]; - s8 volt_max[ADT7473_VOLT_COUNT]; - - s8 temp[ADT7473_TEMP_COUNT]; - s8 temp_min[ADT7473_TEMP_COUNT]; - s8 temp_max[ADT7473_TEMP_COUNT]; - s8 temp_tmin[ADT7473_TEMP_COUNT]; - /* This is called the !THERM limit in the datasheet */ - s8 temp_tmax[ADT7473_TEMP_COUNT]; - - u16 fan[ADT7473_FAN_COUNT]; - u16 fan_min[ADT7473_FAN_COUNT]; - - u8 pwm[ADT7473_PWM_COUNT]; - u8 pwm_max[ADT7473_PWM_COUNT]; - u8 pwm_min[ADT7473_PWM_COUNT]; - u8 pwm_behavior[ADT7473_PWM_COUNT]; - - u8 temp_twos_complement; - u8 temp_offset; - - u16 alarm; - u8 max_duty_at_overheat; -}; - -static int adt7473_probe(struct i2c_client *client, - const struct i2c_device_id *id); -static int adt7473_detect(struct i2c_client *client, - struct i2c_board_info *info); -static int adt7473_remove(struct i2c_client *client); - -static const struct i2c_device_id adt7473_id[] = { - { "adt7473", 0 }, - { } -}; - -static struct i2c_driver adt7473_driver = { - .class = I2C_CLASS_HWMON, - .driver = { - .name = "adt7473", - }, - .probe = adt7473_probe, - .remove = adt7473_remove, - .id_table = adt7473_id, - .detect = adt7473_detect, - .address_list = normal_i2c, -}; - -/* - * 16-bit registers on the ADT7473 are low-byte first. The data sheet says - * that the low byte must be read before the high byte. - */ -static inline int adt7473_read_word_data(struct i2c_client *client, u8 reg) -{ - u16 foo; - foo = i2c_smbus_read_byte_data(client, reg); - foo |= ((u16)i2c_smbus_read_byte_data(client, reg + 1) << 8); - return foo; -} - -static inline int adt7473_write_word_data(struct i2c_client *client, u8 reg, - u16 value) -{ - return i2c_smbus_write_byte_data(client, reg, value & 0xFF) - && i2c_smbus_write_byte_data(client, reg + 1, value >> 8); -} - -static void adt7473_init_client(struct i2c_client *client) -{ - int reg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG1); - - if (!(reg & ADT7473_CFG1_READY)) { - dev_err(&client->dev, "Chip not ready.\n"); - } else { - /* start monitoring */ - i2c_smbus_write_byte_data(client, ADT7473_REG_CFG1, - reg | ADT7473_CFG1_START); - } -} - -static struct adt7473_data *adt7473_update_device(struct device *dev) -{ - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - unsigned long local_jiffies = jiffies; - u8 cfg; - int i; - - mutex_lock(&data->lock); - if (time_before(local_jiffies, data->sensors_last_updated + - SENSOR_REFRESH_INTERVAL) - && data->sensors_valid) - goto no_sensor_update; - - for (i = 0; i < ADT7473_VOLT_COUNT; i++) - data->volt[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT(i)); - - /* Determine temperature encoding */ - cfg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG5); - data->temp_twos_complement = (cfg & ADT7473_CFG5_TEMP_TWOS); - - /* - * What does this do? it implies a variable temperature sensor - * offset, but the datasheet doesn't say anything about this bit - * and other parts of the datasheet imply that "offset64" mode - * means that you shift temp values by -64 if the above bit was set. - */ - data->temp_offset = (cfg & ADT7473_CFG5_TEMP_OFFSET); - - for (i = 0; i < ADT7473_TEMP_COUNT; i++) - data->temp[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP(i)); - - for (i = 0; i < ADT7473_FAN_COUNT; i++) - data->fan[i] = adt7473_read_word_data(client, - ADT7473_REG_FAN(i)); - - for (i = 0; i < ADT7473_PWM_COUNT; i++) - data->pwm[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM(i)); - - data->alarm = i2c_smbus_read_byte_data(client, ADT7473_REG_ALARM1); - if (data->alarm & ADT7473_OOL) - data->alarm |= ALARM2(i2c_smbus_read_byte_data(client, - ADT7473_REG_ALARM2)); - - data->sensors_last_updated = local_jiffies; - data->sensors_valid = 1; - -no_sensor_update: - if (time_before(local_jiffies, data->limits_last_updated + - LIMIT_REFRESH_INTERVAL) - && data->limits_valid) - goto out; - - for (i = 0; i < ADT7473_VOLT_COUNT; i++) { - data->volt_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT_MIN(i)); - data->volt_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_VOLT_MAX(i)); - } - - for (i = 0; i < ADT7473_TEMP_COUNT; i++) { - data->temp_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_MIN(i)); - data->temp_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_MAX(i)); - data->temp_tmin[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_TMIN(i)); - data->temp_tmax[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_TEMP_TMAX(i)); - } - - for (i = 0; i < ADT7473_FAN_COUNT; i++) - data->fan_min[i] = adt7473_read_word_data(client, - ADT7473_REG_FAN_MIN(i)); - - for (i = 0; i < ADT7473_PWM_COUNT; i++) { - data->pwm_max[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_MAX(i)); - data->pwm_min[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_MIN(i)); - data->pwm_behavior[i] = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(i)); - } - - i = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG4); - data->max_duty_at_overheat = !!(i & ADT7473_CFG4_MAX_DUTY_AT_OVT); - - data->limits_last_updated = local_jiffies; - data->limits_valid = 1; - -out: - mutex_unlock(&data->lock); - return data; -} - -/* - * Conversions - */ - -/* IN are scaled acording to built-in resistors */ -static const int adt7473_scaling[] = { /* .001 Volts */ - 2250, 3300 -}; -#define SCALE(val, from, to) (((val) * (to) + ((from) / 2)) / (from)) - -static int decode_volt(int volt_index, u8 raw) -{ - return SCALE(raw, 192, adt7473_scaling[volt_index]); -} - -static u8 encode_volt(int volt_index, int cooked) -{ - int raw = SCALE(cooked, adt7473_scaling[volt_index], 192); - return SENSORS_LIMIT(raw, 0, 255); -} - -static ssize_t show_volt_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt_min[attr->index])); -} - -static ssize_t set_volt_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long volt; - - if (strict_strtol(buf, 10, &volt)) - return -EINVAL; - - volt = encode_volt(attr->index, volt); - - mutex_lock(&data->lock); - data->volt_min[attr->index] = volt; - i2c_smbus_write_byte_data(client, ADT7473_REG_VOLT_MIN(attr->index), - volt); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_volt_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt_max[attr->index])); -} - -static ssize_t set_volt_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long volt; - - if (strict_strtol(buf, 10, &volt)) - return -EINVAL; - - volt = encode_volt(attr->index, volt); - - mutex_lock(&data->lock); - data->volt_max[attr->index] = volt; - i2c_smbus_write_byte_data(client, ADT7473_REG_VOLT_MAX(attr->index), - volt); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_volt(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - return sprintf(buf, "%d\n", - decode_volt(attr->index, data->volt[attr->index])); -} - -/* - * This chip can report temperature data either as a two's complement - * number in the range -128 to 127, or as an unsigned number that must - * be offset by 64. - */ -static int decode_temp(u8 twos_complement, u8 raw) -{ - return twos_complement ? (s8)raw : raw - 64; -} - -static u8 encode_temp(u8 twos_complement, int cooked) -{ - u8 ret = twos_complement ? cooked & 0xFF : cooked + 64; - return SENSORS_LIMIT(ret, 0, 255); -} - -static ssize_t show_temp_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_min[attr->index])); -} - -static ssize_t set_temp_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_MIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_max[attr->index])); -} - -static ssize_t set_temp_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_MAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp[attr->index])); -} - -static ssize_t show_fan_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (FAN_DATA_VALID(data->fan_min[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan_min[attr->index])); - else - return sprintf(buf, "0\n"); -} - -static ssize_t set_fan_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp) || !temp) - return -EINVAL; - - temp = FAN_RPM_TO_PERIOD(temp); - temp = SENSORS_LIMIT(temp, 1, 65534); - - mutex_lock(&data->lock); - data->fan_min[attr->index] = temp; - adt7473_write_word_data(client, ADT7473_REG_FAN_MIN(attr->index), temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (FAN_DATA_VALID(data->fan[attr->index])) - return sprintf(buf, "%d\n", - FAN_PERIOD_TO_RPM(data->fan[attr->index])); - else - return sprintf(buf, "0\n"); -} - -static ssize_t show_max_duty_at_crit(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->max_duty_at_overheat); -} - -static ssize_t set_max_duty_at_crit(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - mutex_lock(&data->lock); - data->max_duty_at_overheat = !!temp; - reg = i2c_smbus_read_byte_data(client, ADT7473_REG_CFG4); - if (temp) - reg |= ADT7473_CFG4_MAX_DUTY_AT_OVT; - else - reg &= ~ADT7473_CFG4_MAX_DUTY_AT_OVT; - i2c_smbus_write_byte_data(client, ADT7473_REG_CFG4, reg); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm[attr->index]); -} - -static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM(attr->index), temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_max(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm_max[attr->index]); -} - -static ssize_t set_pwm_max(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm_max[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_MAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_min(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", data->pwm_min[attr->index]); -} - -static ssize_t set_pwm_min(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = SENSORS_LIMIT(temp, 0, 255); - - mutex_lock(&data->lock); - data->pwm_min[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_MIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_tmax(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_tmax[attr->index])); -} - -static ssize_t set_temp_tmax(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_tmax[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_TMAX(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_tmin(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - return sprintf(buf, "%d\n", 1000 * decode_temp( - data->temp_twos_complement, - data->temp_tmin[attr->index])); -} - -static ssize_t set_temp_tmin(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - temp = DIV_ROUND_CLOSEST(temp, 1000); - temp = encode_temp(data->temp_twos_complement, temp); - - mutex_lock(&data->lock); - data->temp_tmin[attr->index] = temp; - i2c_smbus_write_byte_data(client, ADT7473_REG_TEMP_TMIN(attr->index), - temp); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_enable(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - switch (data->pwm_behavior[attr->index] >> ADT7473_PWM_BHVR_SHIFT) { - case 3: - return sprintf(buf, "0\n"); - case 7: - return sprintf(buf, "1\n"); - default: - return sprintf(buf, "2\n"); - } -} - -static ssize_t set_pwm_enable(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - switch (temp) { - case 0: - temp = 3; - break; - case 1: - temp = 7; - break; - case 2: - /* Enter automatic mode with fans off */ - temp = 4; - break; - default: - return -EINVAL; - } - - mutex_lock(&data->lock); - reg = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(attr->index)); - reg = (temp << ADT7473_PWM_BHVR_SHIFT) | - (reg & ~ADT7473_PWM_BHVR_MASK); - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_BHVR(attr->index), - reg); - data->pwm_behavior[attr->index] = reg; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_pwm_auto_temp(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - int bhvr = data->pwm_behavior[attr->index] >> ADT7473_PWM_BHVR_SHIFT; - - switch (bhvr) { - case 3: - case 4: - case 7: - return sprintf(buf, "0\n"); - case 0: - case 1: - case 5: - case 6: - return sprintf(buf, "%d\n", bhvr + 1); - case 2: - return sprintf(buf, "4\n"); - } - /* shouldn't ever get here */ - BUG(); -} - -static ssize_t set_pwm_auto_temp(struct device *dev, - struct device_attribute *devattr, - const char *buf, - size_t count) -{ - u8 reg; - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct i2c_client *client = to_i2c_client(dev); - struct adt7473_data *data = i2c_get_clientdata(client); - long temp; - - if (strict_strtol(buf, 10, &temp)) - return -EINVAL; - - switch (temp) { - case 1: - case 2: - case 6: - case 7: - temp--; - break; - case 0: - temp = 4; - break; - default: - return -EINVAL; - } - - mutex_lock(&data->lock); - reg = i2c_smbus_read_byte_data(client, - ADT7473_REG_PWM_BHVR(attr->index)); - reg = (temp << ADT7473_PWM_BHVR_SHIFT) | - (reg & ~ADT7473_PWM_BHVR_MASK); - i2c_smbus_write_byte_data(client, ADT7473_REG_PWM_BHVR(attr->index), - reg); - data->pwm_behavior[attr->index] = reg; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_alarm(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - struct adt7473_data *data = adt7473_update_device(dev); - - if (data->alarm & attr->index) - return sprintf(buf, "1\n"); - else - return sprintf(buf, "0\n"); -} - - -static SENSOR_DEVICE_ATTR(in1_max, S_IWUSR | S_IRUGO, show_volt_max, - set_volt_max, 0); -static SENSOR_DEVICE_ATTR(in2_max, S_IWUSR | S_IRUGO, show_volt_max, - set_volt_max, 1); - -static SENSOR_DEVICE_ATTR(in1_min, S_IWUSR | S_IRUGO, show_volt_min, - set_volt_min, 0); -static SENSOR_DEVICE_ATTR(in2_min, S_IWUSR | S_IRUGO, show_volt_min, - set_volt_min, 1); - -static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_volt, NULL, 0); -static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_volt, NULL, 1); - -static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_VCCP_ALARM); -static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_VCC_ALARM); - -static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 0); -static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 1); -static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_temp_max, - set_temp_max, 2); - -static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 0); -static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 1); -static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_temp_min, - set_temp_min, 2); - -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); - -static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_R1T_ALARM | ALARM2(ADT7473_R1T_SHORT)); -static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_LT_ALARM); -static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, - ADT7473_R2T_ALARM | ALARM2(ADT7473_R2T_SHORT)); - -static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 0); -static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 1); -static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 2); -static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO, show_fan_min, - set_fan_min, 3); - -static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); -static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); -static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); -static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); - -static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN1_ALARM)); -static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN2_ALARM)); -static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN3_ALARM)); -static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_alarm, NULL, - ALARM2(ADT7473_FAN4_ALARM)); - -static SENSOR_DEVICE_ATTR(pwm_use_point2_pwm_at_crit, S_IWUSR | S_IRUGO, - show_max_duty_at_crit, set_max_duty_at_crit, 0); - -static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); -static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); -static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IWUSR | S_IRUGO, - show_pwm_min, set_pwm_min, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IWUSR | S_IRUGO, - show_pwm_max, set_pwm_max, 2); - -static SENSOR_DEVICE_ATTR(temp1_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 0); -static SENSOR_DEVICE_ATTR(temp2_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 1); -static SENSOR_DEVICE_ATTR(temp3_auto_point1_temp, S_IWUSR | S_IRUGO, - show_temp_tmin, set_temp_tmin, 2); - -static SENSOR_DEVICE_ATTR(temp1_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 0); -static SENSOR_DEVICE_ATTR(temp2_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 1); -static SENSOR_DEVICE_ATTR(temp3_auto_point2_temp, S_IWUSR | S_IRUGO, - show_temp_tmax, set_temp_tmax, 2); - -static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 0); -static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 1); -static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, show_pwm_enable, - set_pwm_enable, 2); - -static SENSOR_DEVICE_ATTR(pwm1_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 0); -static SENSOR_DEVICE_ATTR(pwm2_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 1); -static SENSOR_DEVICE_ATTR(pwm3_auto_channels_temp, S_IWUSR | S_IRUGO, - show_pwm_auto_temp, set_pwm_auto_temp, 2); - -static struct attribute *adt7473_attr[] = -{ - &sensor_dev_attr_in1_max.dev_attr.attr, - &sensor_dev_attr_in2_max.dev_attr.attr, - &sensor_dev_attr_in1_min.dev_attr.attr, - &sensor_dev_attr_in2_min.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in1_alarm.dev_attr.attr, - &sensor_dev_attr_in2_alarm.dev_attr.attr, - - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp1_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp2_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp3_auto_point1_temp.dev_attr.attr, - &sensor_dev_attr_temp1_auto_point2_temp.dev_attr.attr, - &sensor_dev_attr_temp2_auto_point2_temp.dev_attr.attr, - &sensor_dev_attr_temp3_auto_point2_temp.dev_attr.attr, - - &sensor_dev_attr_fan1_min.dev_attr.attr, - &sensor_dev_attr_fan2_min.dev_attr.attr, - &sensor_dev_attr_fan3_min.dev_attr.attr, - &sensor_dev_attr_fan4_min.dev_attr.attr, - &sensor_dev_attr_fan1_input.dev_attr.attr, - &sensor_dev_attr_fan2_input.dev_attr.attr, - &sensor_dev_attr_fan3_input.dev_attr.attr, - &sensor_dev_attr_fan4_input.dev_attr.attr, - &sensor_dev_attr_fan1_alarm.dev_attr.attr, - &sensor_dev_attr_fan2_alarm.dev_attr.attr, - &sensor_dev_attr_fan3_alarm.dev_attr.attr, - &sensor_dev_attr_fan4_alarm.dev_attr.attr, - - &sensor_dev_attr_pwm_use_point2_pwm_at_crit.dev_attr.attr, - - &sensor_dev_attr_pwm1.dev_attr.attr, - &sensor_dev_attr_pwm2.dev_attr.attr, - &sensor_dev_attr_pwm3.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, - - &sensor_dev_attr_pwm1_enable.dev_attr.attr, - &sensor_dev_attr_pwm2_enable.dev_attr.attr, - &sensor_dev_attr_pwm3_enable.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, - - NULL -}; - -/* Return 0 if detection is successful, -ENODEV otherwise */ -static int adt7473_detect(struct i2c_client *client, - struct i2c_board_info *info) -{ - struct i2c_adapter *adapter = client->adapter; - int vendor, device, revision; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) - return -ENODEV; - - vendor = i2c_smbus_read_byte_data(client, ADT7473_REG_VENDOR); - if (vendor != ADT7473_VENDOR) - return -ENODEV; - - device = i2c_smbus_read_byte_data(client, ADT7473_REG_DEVICE); - if (device != ADT7473_DEVICE) - return -ENODEV; - - revision = i2c_smbus_read_byte_data(client, ADT7473_REG_REVISION); - if (revision != ADT7473_REV_68 && revision != ADT7473_REV_69) - return -ENODEV; - - strlcpy(info->type, "adt7473", I2C_NAME_SIZE); - - return 0; -} - -static int adt7473_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct adt7473_data *data; - int err; - - data = kzalloc(sizeof(struct adt7473_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } - - i2c_set_clientdata(client, data); - mutex_init(&data->lock); - - dev_info(&client->dev, "%s chip found\n", client->name); - - /* Initialize the ADT7473 chip */ - adt7473_init_client(client); - - /* Register sysfs hooks */ - data->attrs.attrs = adt7473_attr; - err = sysfs_create_group(&client->dev.kobj, &data->attrs); - if (err) - goto exit_free; - - data->hwmon_dev = hwmon_device_register(&client->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - goto exit_remove; - } - - return 0; - -exit_remove: - sysfs_remove_group(&client->dev.kobj, &data->attrs); -exit_free: - kfree(data); -exit: - return err; -} - -static int adt7473_remove(struct i2c_client *client) -{ - struct adt7473_data *data = i2c_get_clientdata(client); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &data->attrs); - kfree(data); - return 0; -} - -static int __init adt7473_init(void) -{ - pr_notice("The adt7473 driver is deprecated, please use the adt7475 " - "driver instead\n"); - return i2c_add_driver(&adt7473_driver); -} - -static void __exit adt7473_exit(void) -{ - i2c_del_driver(&adt7473_driver); -} - -MODULE_AUTHOR("Darrick J. Wong <djwong@us.ibm.com>"); -MODULE_DESCRIPTION("ADT7473 driver"); -MODULE_LICENSE("GPL"); - -module_init(adt7473_init); -module_exit(adt7473_exit); diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c new file mode 100644 index 000000000000..7f948105d8ad --- /dev/null +++ b/drivers/hwmon/asc7621.c @@ -0,0 +1,1255 @@ +/* + * asc7621.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * Copyright (c) 2007, 2010 George Joseph <george.joseph@fairview5.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + 0x2c, 0x2d, 0x2e, I2C_CLIENT_END +}; + +enum asc7621_type { + asc7621, + asc7621a +}; + +#define INTERVAL_HIGH (HZ + HZ / 2) +#define INTERVAL_LOW (1 * 60 * HZ) +#define PRI_NONE 0 +#define PRI_LOW 1 +#define PRI_HIGH 2 +#define FIRST_CHIP asc7621 +#define LAST_CHIP asc7621a + +struct asc7621_chip { + char *name; + enum asc7621_type chip_type; + u8 company_reg; + u8 company_id; + u8 verstep_reg; + u8 verstep_id; + unsigned short *addresses; +}; + +static struct asc7621_chip asc7621_chips[] = { + { + .name = "asc7621", + .chip_type = asc7621, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6c, + .addresses = normal_i2c, + }, + { + .name = "asc7621a", + .chip_type = asc7621a, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6d, + .addresses = normal_i2c, + }, +}; + +/* + * Defines the highest register to be used, not the count. + * The actual count will probably be smaller because of gaps + * in the implementation (unused register locations). + * This define will safely set the array size of both the parameter + * and data arrays. + * This comes from the data sheet register description table. + */ +#define LAST_REGISTER 0xff + +struct asc7621_data { + struct i2c_client client; + struct device *class_dev; + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_high_reading; /* In jiffies */ + unsigned long last_low_reading; /* In jiffies */ + /* + * Registers we care about occupy the corresponding index + * in the array. Registers we don't care about are left + * at 0. + */ + u8 reg[LAST_REGISTER + 1]; +}; + +/* + * Macro to get the parent asc7621_param structure + * from a sensor_device_attribute passed into the + * show/store functions. + */ +#define to_asc7621_param(_sda) \ + container_of(_sda, struct asc7621_param, sda) + +/* + * Each parameter to be retrieved needs an asc7621_param structure + * allocated. It contains the sensor_device_attribute structure + * and the control info needed to retrieve the value from the register map. + */ +struct asc7621_param { + struct sensor_device_attribute sda; + u8 priority; + u8 msb[3]; + u8 lsb[3]; + u8 mask[3]; + u8 shift[3]; +}; + +/* + * This is the map that ultimately indicates whether we'll be + * retrieving a register value or not, and at what frequency. + */ +static u8 asc7621_register_priorities[255]; + +static struct asc7621_data *asc7621_update_device(struct device *dev); + +static inline u8 read_byte(struct i2c_client *client, u8 reg) +{ + int res = i2c_smbus_read_byte_data(client, reg); + if (res < 0) { + dev_err(&client->dev, + "Unable to read from register 0x%02x.\n", reg); + return 0; + }; + return res & 0xff; +} + +static inline int write_byte(struct i2c_client *client, u8 reg, u8 data) +{ + int res = i2c_smbus_write_byte_data(client, reg, data); + if (res < 0) { + dev_err(&client->dev, + "Unable to write value 0x%02x to register 0x%02x.\n", + data, reg); + }; + return res; +} + +/* + * Data Handlers + * Each function handles the formatting, storage + * and retrieval of like parameters. + */ + +#define SETUP_SHOW_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct asc7621_data *data = asc7621_update_device(d); \ + struct asc7621_param *param = to_asc7621_param(sda) + +#define SETUP_STORE_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct i2c_client *client = to_i2c_client(d); \ + struct asc7621_data *data = i2c_get_clientdata(client); \ + struct asc7621_param *param = to_asc7621_param(sda) + +/* + * u8 is just what it sounds like...an unsigned byte with no + * special formatting. + */ +static ssize_t show_u8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", data->reg[param->msb[0]]); +} + +static ssize_t store_u8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, 255); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Many of the config values occupy only a few bits of a register. + */ +static ssize_t show_bitmask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", + (data->reg[param->msb[0]] >> param-> + shift[0]) & param->mask[0]); +} + +static ssize_t store_bitmask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, param->mask[0]); + + reqval = (reqval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + reqval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * 16 bit fan rpm values + * reported by the device as the number of 11.111us periods (90khz) + * between full fan rotations. Therefore... + * RPM = (90000 * 60) / register value + */ +static ssize_t show_fan16(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", + (regval == 0 ? -1 : (regval) == + 0xffff ? 0 : 5400000 / regval)); +} + +static ssize_t store_fan16(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = + (SENSORS_LIMIT((reqval) <= 0 ? 0 : 5400000 / (reqval), 0, 65534)); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = (reqval >> 8) & 0xff; + data->reg[param->lsb[0]] = reqval & 0xff; + write_byte(client, param->msb[0], data->reg[param->msb[0]]); + write_byte(client, param->lsb[0], data->reg[param->lsb[0]]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Voltages are scaled in the device so that the nominal voltage + * is 3/4ths of the 0-255 range (i.e. 192). + * If all voltages are 'normal' then all voltage registers will + * read 0xC0. This doesn't help us if we don't have a point of refernce. + * The data sheet however provides us with the full scale value for each + * which is stored in in_scaling. The sda->index parameter value provides + * the index into in_scaling. + * + * NOTE: The chip expects the first 2 inputs be 2.5 and 2.25 volts + * respectively. That doesn't mean that's what the motherboard provides. :) + */ + +static int asc7621_in_scaling[] = { + 3320, 3000, 4380, 6640, 16000 +}; + +static ssize_t show_in10(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + u8 nr = sda->index; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] * asc7621_in_scaling[nr]) / 256; + + /* The LSB value is a 2-bit scaling of the MSB's LSbit value. + * I.E. If the maximim voltage for this input is 6640 millivolts then + * a MSB register value of 0 = 0mv and 255 = 6640mv. + * A 1 step change therefore represents 25.9mv (6640 / 256). + * The extra 2-bits therefore represent increments of 6.48mv. + */ + regval += ((asc7621_in_scaling[nr] / 256) / 4) * + (data->reg[param->lsb[0]] >> 6); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", regval); +} + +/* 8 bit voltage values (the mins and maxs) */ +static ssize_t show_in8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 nr = sda->index; + + return sprintf(buf, "%u\n", + ((data->reg[param->msb[0]] * + asc7621_in_scaling[nr]) / 256)); +} + +static ssize_t store_in8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 nr = sda->index; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, asc7621_in_scaling[nr]); + + reqval = (reqval * 255 + 128) / asc7621_in_scaling[nr]; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp8(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000); +} + +static ssize_t store_temp8(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -127000, 127000); + + temp = reqval / 1000; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Temperatures that occupy 2 bytes always have the whole + * number of degrees in the MSB with some part of the LSB + * indicating fractional degrees. + */ + +/* mmmmmmmm.llxxxxxx */ +static ssize_t show_temp10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 msb, lsb; + int temp; + + mutex_lock(&data->update_lock); + msb = data->reg[param->msb[0]]; + lsb = (data->reg[param->lsb[0]] >> 6) & 0x03; + temp = (((s8) msb) * 1000) + (lsb * 250); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); +} + +/* mmmmmm.ll */ +static ssize_t show_temp62(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = data->reg[param->msb[0]]; + int temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp62(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, i, f; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -32000, 31750); + i = reqval / 1000; + f = reqval - (i * 1000); + temp = i << 2; + temp |= f / 250; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * The aSC7621 doesn't provide an "auto_point2". Instead, you + * specify the auto_point1 and a range. To keep with the sysfs + * hwmon specs, we synthesize the auto_point_2 from them. + */ + +static u32 asc7621_range_map[] = { + 2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000, + 13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000, +}; + +static ssize_t show_ap2_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + long auto_point1; + u8 regval; + int temp; + + mutex_lock(&data->update_lock); + auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000; + regval = + ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]); + temp = auto_point1 + asc7621_range_map[SENSORS_LIMIT(regval, 0, 15)]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); + +} + +static ssize_t store_ap2_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, auto_point1; + int i; + u8 currval, newval = 0; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + mutex_lock(&data->update_lock); + auto_point1 = data->reg[param->msb[1]] * 1000; + reqval = SENSORS_LIMIT(reqval, auto_point1 + 2000, auto_point1 + 80000); + + for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) { + if (reqval >= auto_point1 + asc7621_range_map[i]) { + newval = i; + break; + } + } + + newval = (newval & param->mask[0]) << param->shift[0]; + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_ac(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, regval; + u8 map[] = { + 0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10, + 0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f + }; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + regval = config | (altbit << 3); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", map[SENSORS_LIMIT(regval, 0, 15)]); +} + +static ssize_t store_pwm_ac(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, config, altbit, newval; + u16 map[] = { + 0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + }; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + if (reqval > 31) + return -EINVAL; + + reqval = map[reqval]; + if (reqval == 0xff) + return -EINVAL; + + config = reqval & 0x07; + altbit = (reqval >> 3) & 0x01; + + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, minoff, val, newval; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + minoff = (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2]; + mutex_unlock(&data->update_lock); + + val = config | (altbit << 3); + newval = 0; + + if (val == 3 || val >= 10) + newval = 255; + else if (val == 4) + newval = 0; + else if (val == 7) + newval = 1; + else if (minoff == 1) + newval = 2; + else + newval = 3; + + return sprintf(buf, "%u\n", newval); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, config, altbit, newval, minoff = 255; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + switch (reqval) { + case 0: + newval = 0x04; + break; + case 1: + newval = 0x07; + break; + case 2: + newval = 0x00; + minoff = 1; + break; + case 3: + newval = 0x00; + minoff = 0; + break; + case 255: + newval = 0x03; + break; + default: + return -EINVAL; + } + + config = newval & 0x07; + altbit = (newval >> 3) & 0x01; + + mutex_lock(&data->update_lock); + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + if (minoff < 255) { + minoff = (minoff & param->mask[2]) << param->shift[2]; + currval = read_byte(client, param->msb[2]); + newval = + minoff | (currval & ~(param->mask[2] << param->shift[2])); + data->reg[param->msb[2]] = newval; + write_byte(client, param->msb[2], newval); + } + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_freq_map[] = { + 10, 15, 23, 30, 38, 47, 62, 94, + 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000 +}; + +static ssize_t show_pwm_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 15); + + return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, newval = 255; + int i; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) { + if (reqval == asc7621_pwm_freq_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_auto_spinup_map[] = { + 0, 100, 250, 400, 700, 1000, 2000, 4000 +}; + +static ssize_t show_pwm_ast(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]); + +} + +static ssize_t store_pwm_ast(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) { + if (reqval == asc7621_pwm_auto_spinup_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_temp_smoothing_time_map[] = { + 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 +}; + +static ssize_t show_temp_st(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]); +} + +static ssize_t store_temp_st(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_temp_smoothing_time_map); i++) { + if (reqval == asc7621_temp_smoothing_time_map[i]) { + newval = i; + break; + } + } + + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * End of data handlers + * + * These defines do nothing more than make the table easier + * to read when wrapped at column 80. + */ + +/* + * Creates a variable length array inititalizer. + * VAA(1,3,5,7) would produce {1,3,5,7} + */ +#define VAA(args...) {args} + +#define PREAD(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +#define PWRITE(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +/* + * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift + * were created using the VAA macro. + */ +#define PWRITEM(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb = rm, .lsb = rl, .mask = m, .shift = s,} + +static struct asc7621_param asc7621_params[] = { + PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10), + PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10), + PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10), + PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10), + PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10), + + PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8), + PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8), + PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8), + PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8), + PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8), + + PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8), + PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8), + PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8), + PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8), + PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8), + + PREAD(in0_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 0, bitmask), + PREAD(in1_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 1, bitmask), + PREAD(in2_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 2, bitmask), + PREAD(in3_alarm, 3, PRI_LOW, 0x41, 0, 0x01, 3, bitmask), + PREAD(in4_alarm, 4, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + + PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16), + PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16), + PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16), + PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16), + + PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16), + PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16), + PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16), + PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16), + + PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 1, bitmask), + PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 2, bitmask), + PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 3, bitmask), + + PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10), + PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10), + PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10), + PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10), + PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10), + PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10), + PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10), + PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10), + + PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8), + PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8), + PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8), + PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8), + + PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8), + PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8), + PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8), + PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8), + + PREAD(temp1_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 4, bitmask), + PREAD(temp2_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 5, bitmask), + PREAD(temp3_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 6, bitmask), + PREAD(temp4_alarm, 3, PRI_LOW, 0x43, 0, 0x01, 0, bitmask), + + PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask), + PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask), + PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask), + PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask), + + PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask), + PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask), + PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask), + PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask), + + PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st), + PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st), + PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st), + PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st), + + PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8), + PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8), + PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8), + PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8), + + PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + + PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8), + PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8), + PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8), + PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8), + + PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask), + PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask), + PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask), + PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask), + + PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62), + PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62), + + PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8), + PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8), + PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8), + + PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask), + PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask), + PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask), + + PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable), + PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable), + PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable), + + PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + + PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8), + PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8), + PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8), + + PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8), + PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8), + PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8), + + PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq), + + PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask), + PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask), + PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask), + + PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast), + PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast), + PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast), + + PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask), + PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask), + PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask), + PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask), + PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask), + PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask), + +}; + +static struct asc7621_data *asc7621_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + +/* + * The asc7621 chips guarantee consistent reads of multi-byte values + * regardless of the order of the reads. No special logic is needed + * so we can just read the registers in whatever order they appear + * in the asc7621_params array. + */ + + mutex_lock(&data->update_lock); + + /* Read all the high priority registers */ + + if (!data->valid || + time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) { + if (asc7621_register_priorities[i] == PRI_HIGH) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_high_reading = jiffies; + }; /* last_reading */ + + /* Read all the low priority registers. */ + + if (!data->valid || + time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + if (asc7621_register_priorities[i] == PRI_LOW) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_low_reading = jiffies; + }; /* last_reading */ + + data->valid = 1; + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Standard detection and initialization below + * + * Helper function that checks if an address is valid + * for a particular chip. + */ + +static inline int valid_address_for_chip(int chip_type, int address) +{ + int i; + + for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END; + i++) { + if (asc7621_chips[chip_type].addresses[i] == address) + return 1; + } + return 0; +} + +static void asc7621_init_client(struct i2c_client *client) +{ + int value; + + /* Warn if part was not "READY" */ + + value = read_byte(client, 0x40); + + if (value & 0x02) { + dev_err(&client->dev, + "Client (%d,0x%02x) config is locked.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + if (!(value & 0x04)) { + dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + +/* + * Start monitoring + * + * Try to clear LOCK, Set START, save everything else + */ + value = (value & ~0x02) | 0x01; + write_byte(client, 0x40, value & 0xff); + +} + +static int +asc7621_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct asc7621_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Initialize the asc7621 chip */ + asc7621_init_client(client); + + /* Create the sysfs entries */ + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + err = + device_create_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + if (err) + goto exit_remove; + } + + data->class_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return err; +} + +static int asc7621_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int company, verstep, chip_index; + struct device *dev; + + dev = &client->dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP; chip_index++) { + + if (!valid_address_for_chip(chip_index, client->addr)) + continue; + + company = read_byte(client, + asc7621_chips[chip_index].company_reg); + verstep = read_byte(client, + asc7621_chips[chip_index].verstep_reg); + + if (company == asc7621_chips[chip_index].company_id && + verstep == asc7621_chips[chip_index].verstep_id) { + strlcpy(client->name, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + strlcpy(info->type, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + + dev_info(&adapter->dev, "Matched %s\n", + asc7621_chips[chip_index].name); + return 0; + } + } + + return -ENODEV; +} + +static int asc7621_remove(struct i2c_client *client) +{ + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + + hwmon_device_unregister(data->class_dev); + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id asc7621_id[] = { + {"asc7621", asc7621}, + {"asc7621a", asc7621a}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, asc7621_id); + +static struct i2c_driver asc7621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "asc7621", + }, + .probe = asc7621_probe, + .remove = asc7621_remove, + .id_table = asc7621_id, + .detect = asc7621_detect, + .address_list = normal_i2c, +}; + +static int __init sm_asc7621_init(void) +{ + int i, j; +/* + * Collect all the registers needed into a single array. + * This way, if a register isn't actually used for anything, + * we don't retrieve it. + */ + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) + asc7621_register_priorities[asc7621_params[i].msb[j]] = + asc7621_params[i].priority; + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) + asc7621_register_priorities[asc7621_params[i].lsb[j]] = + asc7621_params[i].priority; + } + return i2c_add_driver(&asc7621_driver); +} + +static void __exit sm_asc7621_exit(void) +{ + i2c_del_driver(&asc7621_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("George Joseph"); +MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver"); + +module_init(sm_asc7621_init); +module_exit(sm_asc7621_exit); diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c index fa0728232e71..0627f7a5b9b8 100644 --- a/drivers/hwmon/fschmd.c +++ b/drivers/hwmon/fschmd.c @@ -267,7 +267,7 @@ struct fschmd_data { struct list_head list; /* member of the watchdog_data_list */ struct kref kref; struct miscdevice watchdog_miscdev; - int kind; + enum chips kind; unsigned long watchdog_is_open; char watchdog_expect_close; char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ @@ -325,8 +325,7 @@ static ssize_t show_in_value(struct device *dev, int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev); - /* fscher / fschrc - 1 as data->kind is an array index, not a chips */ - if (data->kind == (fscher - 1) || data->kind >= (fschrc - 1)) + if (data->kind == fscher || data->kind >= fschrc) return sprintf(buf, "%d\n", (data->volt[index] * dmi_vref * dmi_mult[index]) / 255 + dmi_offset[index]); else @@ -492,7 +491,7 @@ static ssize_t show_pwm_auto_point1_pwm(struct device *dev, int val = data->fan_min[index]; /* 0 = allow turning off (except on the syl), 1-255 = 50-100% */ - if (val || data->kind == fscsyl - 1) + if (val || data->kind == fscsyl) val = val / 2 + 128; return sprintf(buf, "%d\n", val); @@ -506,7 +505,7 @@ static ssize_t store_pwm_auto_point1_pwm(struct device *dev, unsigned long v = simple_strtoul(buf, NULL, 10); /* reg: 0 = allow turning off (except on the syl), 1-255 = 50-100% */ - if (v || data->kind == fscsyl - 1) { + if (v || data->kind == fscsyl) { v = SENSORS_LIMIT(v, 128, 255); v = (v - 128) * 2 + 1; } @@ -1037,7 +1036,7 @@ static int fschmd_detect(struct i2c_client *client, else return -ENODEV; - strlcpy(info->type, fschmd_id[kind - 1].name, I2C_NAME_SIZE); + strlcpy(info->type, fschmd_id[kind].name, I2C_NAME_SIZE); return 0; } @@ -1065,6 +1064,7 @@ static int fschmd_probe(struct i2c_client *client, (where the client is found through a data ptr instead of the otherway around) */ data->client = client; + data->kind = kind; if (kind == fscpos) { /* The Poseidon has hardwired temp limits, fill these @@ -1085,9 +1085,6 @@ static int fschmd_probe(struct i2c_client *client, } } - /* i2c kind goes from 1-6, we want from 0-5 to address arrays */ - data->kind = kind - 1; - /* Read in some never changing registers */ data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); data->global_control = i2c_smbus_read_byte_data(client, diff --git a/drivers/hwmon/g760a.c b/drivers/hwmon/g760a.c index 19c01a49f6be..09ea12e0a551 100644 --- a/drivers/hwmon/g760a.c +++ b/drivers/hwmon/g760a.c @@ -68,7 +68,7 @@ struct g760a_data { #define PWM_FROM_CNT(cnt) (0xff-(cnt)) #define PWM_TO_CNT(pwm) (0xff-(pwm)) -unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) +static inline unsigned int rpm_from_cnt(u8 val, u32 clk, u16 div) { return ((val == 0x00) ? 0 : ((clk*30)/(val*div))); } diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 0ffe84d190bb..1002befd87d5 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -1,40 +1,40 @@ /* - it87.c - Part of lm_sensors, Linux kernel modules for hardware - monitoring. - - The IT8705F is an LPC-based Super I/O part that contains UARTs, a - parallel port, an IR port, a MIDI port, a floppy controller, etc., in - addition to an Environment Controller (Enhanced Hardware Monitor and - Fan Controller) - - This driver supports only the Environment Controller in the IT8705F and - similar parts. The other devices are supported by different drivers. - - Supports: IT8705F Super I/O chip w/LPC interface - IT8712F Super I/O chip w/LPC interface - IT8716F Super I/O chip w/LPC interface - IT8718F Super I/O chip w/LPC interface - IT8720F Super I/O chip w/LPC interface - IT8726F Super I/O chip w/LPC interface - Sis950 A clone of the IT8705F - - Copyright (C) 2001 Chris Gauthron - Copyright (C) 2005-2007 Jean Delvare <khali@linux-fr.org> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*/ + * it87.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring. + * + * The IT8705F is an LPC-based Super I/O part that contains UARTs, a + * parallel port, an IR port, a MIDI port, a floppy controller, etc., in + * addition to an Environment Controller (Enhanced Hardware Monitor and + * Fan Controller) + * + * This driver supports only the Environment Controller in the IT8705F and + * similar parts. The other devices are supported by different drivers. + * + * Supports: IT8705F Super I/O chip w/LPC interface + * IT8712F Super I/O chip w/LPC interface + * IT8716F Super I/O chip w/LPC interface + * IT8718F Super I/O chip w/LPC interface + * IT8720F Super I/O chip w/LPC interface + * IT8726F Super I/O chip w/LPC interface + * Sis950 A clone of the IT8705F + * + * Copyright (C) 2001 Chris Gauthron + * Copyright (C) 2005-2010 Jean Delvare <khali@linux-fr.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ #include <linux/module.h> #include <linux/init.h> @@ -128,6 +128,7 @@ superio_exit(void) #define IT87_SIO_GPIO5_REG 0x29 #define IT87_SIO_PINX2_REG 0x2c /* Pin selection */ #define IT87_SIO_VID_REG 0xfc /* VID value */ +#define IT87_SIO_BEEP_PIN_REG 0xf6 /* Beep pin mapping */ /* Update battery voltage after every reading if true */ static int update_vbat; @@ -187,9 +188,13 @@ static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 }; #define IT87_REG_VIN_ENABLE 0x50 #define IT87_REG_TEMP_ENABLE 0x51 +#define IT87_REG_BEEP_ENABLE 0x5c #define IT87_REG_CHIPID 0x58 +#define IT87_REG_AUTO_TEMP(nr, i) (0x60 + (nr) * 8 + (i)) +#define IT87_REG_AUTO_PWM(nr, i) (0x65 + (nr) * 8 + (i)) + #define IN_TO_REG(val) (SENSORS_LIMIT((((val) + 8)/16),0,255)) #define IN_FROM_REG(val) ((val) * 16) @@ -246,6 +251,7 @@ struct it87_sio_data { /* Values read from Super-I/O config space */ u8 revision; u8 vid_value; + u8 beep_pin; /* Features skipped based on config or DMI */ u8 skip_vid; u8 skip_fan; @@ -279,9 +285,21 @@ struct it87_data { u8 vid; /* Register encoding, combined */ u8 vrm; u32 alarms; /* Register encoding, combined */ + u8 beeps; /* Register encoding */ u8 fan_main_ctrl; /* Register value */ u8 fan_ctl; /* Register value */ - u8 manual_pwm_ctl[3]; /* manual PWM value set by user */ + + /* The following 3 arrays correspond to the same registers. The + * meaning of bits 6-0 depends on the value of bit 7, and we want + * to preserve settings on mode changes, so we have to track all + * values separately. */ + u8 pwm_ctrl[3]; /* Register value */ + u8 pwm_duty[3]; /* Manual PWM value set by user (bit 6-0) */ + u8 pwm_temp_map[3]; /* PWM to temp. chan. mapping (bits 1-0) */ + + /* Automatic fan speed control registers */ + u8 auto_pwm[3][4]; /* [nr][3] is hard-coded */ + s8 auto_temp[3][5]; /* [nr][0] is point1_temp_hyst */ }; static inline int has_16bit_fans(const struct it87_data *data) @@ -296,6 +314,15 @@ static inline int has_16bit_fans(const struct it87_data *data) || data->type == it8720; } +static inline int has_old_autopwm(const struct it87_data *data) +{ + /* The old automatic fan speed control interface is implemented + by IT8705F chips up to revision F and IT8712F chips up to + revision G. */ + return (data->type == it87 && data->revision < 0x03) + || (data->type == it8712 && data->revision < 0x08); +} + static int it87_probe(struct platform_device *pdev); static int __devexit it87_remove(struct platform_device *pdev); @@ -352,7 +379,10 @@ static ssize_t set_in_min(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - unsigned long val = simple_strtoul(buf, NULL, 10); + unsigned long val; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); data->in_min[nr] = IN_TO_REG(val); @@ -368,7 +398,10 @@ static ssize_t set_in_max(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - unsigned long val = simple_strtoul(buf, NULL, 10); + unsigned long val; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); data->in_max[nr] = IN_TO_REG(val); @@ -441,7 +474,10 @@ static ssize_t set_temp_max(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); data->temp_high[nr] = TEMP_TO_REG(val); @@ -456,7 +492,10 @@ static ssize_t set_temp_min(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); data->temp_low[nr] = TEMP_TO_REG(val); @@ -483,8 +522,9 @@ static ssize_t show_sensor(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - u8 reg = data->sensor; /* In case the value is updated while we use it */ - + u8 reg = data->sensor; /* In case the value is updated while + we use it */ + if (reg & (1 << nr)) return sprintf(buf, "3\n"); /* thermal diode */ if (reg & (8 << nr)) @@ -498,7 +538,10 @@ static ssize_t set_sensor(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); @@ -511,9 +554,9 @@ static ssize_t set_sensor(struct device *dev, struct device_attribute *attr, } /* 3 = thermal diode; 4 = thermistor; 0 = disabled */ if (val == 3) - data->sensor |= 1 << nr; + data->sensor |= 1 << nr; else if (val == 4) - data->sensor |= 8 << nr; + data->sensor |= 8 << nr; else if (val != 0) { mutex_unlock(&data->update_lock); return -EINVAL; @@ -531,6 +574,19 @@ show_sensor_offset(2); show_sensor_offset(3); /* 3 Fans */ + +static int pwm_mode(const struct it87_data *data, int nr) +{ + int ctrl = data->fan_main_ctrl & (1 << nr); + + if (ctrl == 0) /* Full speed */ + return 0; + if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */ + return 2; + else /* Manual mode */ + return 1; +} + static ssize_t show_fan(struct device *dev, struct device_attribute *attr, char *buf) { @@ -538,7 +594,7 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - return sprintf(buf,"%d\n", FAN_FROM_REG(data->fan[nr], + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[nr], DIV_FROM_REG(data->fan_div[nr]))); } static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, @@ -548,8 +604,8 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - return sprintf(buf,"%d\n", - FAN_FROM_REG(data->fan_min[nr], DIV_FROM_REG(data->fan_div[nr]))); + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[nr], + DIV_FROM_REG(data->fan_div[nr]))); } static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, char *buf) @@ -560,14 +616,14 @@ static ssize_t show_fan_div(struct device *dev, struct device_attribute *attr, struct it87_data *data = it87_update_device(dev); return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr])); } -static ssize_t show_pwm_enable(struct device *dev, struct device_attribute *attr, - char *buf) +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - return sprintf(buf,"%d\n", (data->fan_main_ctrl & (1 << nr)) ? 1 : 0); + return sprintf(buf, "%d\n", pwm_mode(data, nr)); } static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, char *buf) @@ -576,7 +632,7 @@ static ssize_t show_pwm(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = it87_update_device(dev); - return sprintf(buf,"%d\n", data->manual_pwm_ctl[nr]); + return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm_duty[nr])); } static ssize_t show_pwm_freq(struct device *dev, struct device_attribute *attr, char *buf) @@ -593,15 +649,24 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; u8 reg; + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; + mutex_lock(&data->update_lock); reg = it87_read_value(data, IT87_REG_FAN_DIV); switch (nr) { - case 0: data->fan_div[nr] = reg & 0x07; break; - case 1: data->fan_div[nr] = (reg >> 3) & 0x07; break; - case 2: data->fan_div[nr] = (reg & 0x40) ? 3 : 1; break; + case 0: + data->fan_div[nr] = reg & 0x07; + break; + case 1: + data->fan_div[nr] = (reg >> 3) & 0x07; + break; + case 2: + data->fan_div[nr] = (reg & 0x40) ? 3 : 1; + break; } data->fan_min[nr] = FAN_TO_REG(val, DIV_FROM_REG(data->fan_div[nr])); @@ -616,10 +681,13 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - unsigned long val = simple_strtoul(buf, NULL, 10); + unsigned long val; int min; u8 old; + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + mutex_lock(&data->update_lock); old = it87_read_value(data, IT87_REG_FAN_DIV); @@ -651,6 +719,32 @@ static ssize_t set_fan_div(struct device *dev, struct device_attribute *attr, mutex_unlock(&data->update_lock); return count; } + +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct device *dev, int nr) +{ + const struct it87_data *data = dev_get_drvdata(dev); + int i, err = 0; + + if (has_old_autopwm(data)) { + for (i = 0; i < 3; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + err = -EINVAL; + } + for (i = 0; i < 2; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + err = -EINVAL; + } + } + + if (err) { + dev_err(dev, "Inconsistent trip points, not switching to " + "automatic mode\n"); + dev_err(dev, "Adjust the trip points and try again\n"); + } + return err; +} + static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -658,7 +752,16 @@ static ssize_t set_pwm_enable(struct device *dev, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; + + if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 2) + return -EINVAL; + + /* Check trip points before switching to automatic mode */ + if (val == 2) { + if (check_trip_points(dev, nr) < 0) + return -EINVAL; + } mutex_lock(&data->update_lock); @@ -669,16 +772,18 @@ static ssize_t set_pwm_enable(struct device *dev, it87_write_value(data, IT87_REG_FAN_CTL, tmp | (1 << nr)); /* set on/off mode */ data->fan_main_ctrl &= ~(1 << nr); - it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); - } else if (val == 1) { + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + data->fan_main_ctrl); + } else { + if (val == 1) /* Manual mode */ + data->pwm_ctrl[nr] = data->pwm_duty[nr]; + else /* Automatic mode */ + data->pwm_ctrl[nr] = 0x80 | data->pwm_temp_map[nr]; + it87_write_value(data, IT87_REG_PWM(nr), data->pwm_ctrl[nr]); /* set SmartGuardian mode */ data->fan_main_ctrl |= (1 << nr); - it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); - /* set saved pwm value, clear FAN_CTLX PWM mode bit */ - it87_write_value(data, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); - } else { - mutex_unlock(&data->update_lock); - return -EINVAL; + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + data->fan_main_ctrl); } mutex_unlock(&data->update_lock); @@ -691,15 +796,19 @@ static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; - if (val < 0 || val > 255) + if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 255) return -EINVAL; mutex_lock(&data->update_lock); - data->manual_pwm_ctl[nr] = val; - if (data->fan_main_ctrl & (1 << nr)) - it87_write_value(data, IT87_REG_PWM(nr), PWM_TO_REG(data->manual_pwm_ctl[nr])); + data->pwm_duty[nr] = PWM_TO_REG(val); + /* If we are in manual mode, write the duty cycle immediately; + * otherwise, just store it for later use. */ + if (!(data->pwm_ctrl[nr] & 0x80)) { + data->pwm_ctrl[nr] = data->pwm_duty[nr]; + it87_write_value(data, IT87_REG_PWM(nr), data->pwm_ctrl[nr]); + } mutex_unlock(&data->update_lock); return count; } @@ -707,9 +816,12 @@ static ssize_t set_pwm_freq(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct it87_data *data = dev_get_drvdata(dev); - unsigned long val = simple_strtoul(buf, NULL, 10); + unsigned long val; int i; + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + /* Search for the nearest available frequency */ for (i = 0; i < 7; i++) { if (val > (pwm_freq[i] + pwm_freq[i+1]) / 2) @@ -724,6 +836,132 @@ static ssize_t set_pwm_freq(struct device *dev, return count; } +static ssize_t show_pwm_temp_map(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + + struct it87_data *data = it87_update_device(dev); + int map; + + if (data->pwm_temp_map[nr] < 3) + map = 1 << data->pwm_temp_map[nr]; + else + map = 0; /* Should never happen */ + return sprintf(buf, "%d\n", map); +} +static ssize_t set_pwm_temp_map(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); + int nr = sensor_attr->index; + + struct it87_data *data = dev_get_drvdata(dev); + long val; + u8 reg; + + /* This check can go away if we ever support automatic fan speed + control on newer chips. */ + if (!has_old_autopwm(data)) { + dev_notice(dev, "Mapping change disabled for safety reasons\n"); + return -EINVAL; + } + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; + + switch (val) { + case (1 << 0): + reg = 0x00; + break; + case (1 << 1): + reg = 0x01; + break; + case (1 << 2): + reg = 0x02; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->pwm_temp_map[nr] = reg; + /* If we are in automatic mode, write the temp mapping immediately; + * otherwise, just store it for later use. */ + if (data->pwm_ctrl[nr] & 0x80) { + data->pwm_ctrl[nr] = 0x80 | data->pwm_temp_map[nr]; + it87_write_value(data, IT87_REG_PWM(nr), data->pwm_ctrl[nr]); + } + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + + return sprintf(buf, "%d\n", PWM_FROM_REG(data->auto_pwm[nr][point])); +} + +static ssize_t set_auto_pwm(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + long val; + + if (strict_strtol(buf, 10, &val) < 0 || val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = PWM_TO_REG(val); + it87_write_value(data, IT87_REG_AUTO_PWM(nr, point), + data->auto_pwm[nr][point]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_auto_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct it87_data *data = it87_update_device(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + + return sprintf(buf, "%d\n", TEMP_FROM_REG(data->auto_temp[nr][point])); +} + +static ssize_t set_auto_temp(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct it87_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sensor_attr = + to_sensor_dev_attr_2(attr); + int nr = sensor_attr->nr; + int point = sensor_attr->index; + long val; + + if (strict_strtol(buf, 10, &val) < 0 || val < -128000 || val > 127000) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_temp[nr][point] = TEMP_TO_REG(val); + it87_write_value(data, IT87_REG_AUTO_TEMP(nr, point), + data->auto_temp[nr][point]); + mutex_unlock(&data->update_lock); + return count; +} #define show_fan_offset(offset) \ static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ @@ -744,7 +982,36 @@ static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR, \ show_pwm, set_pwm, offset - 1); \ static DEVICE_ATTR(pwm##offset##_freq, \ (offset == 1 ? S_IRUGO | S_IWUSR : S_IRUGO), \ - show_pwm_freq, (offset == 1 ? set_pwm_freq : NULL)); + show_pwm_freq, (offset == 1 ? set_pwm_freq : NULL)); \ +static SENSOR_DEVICE_ATTR(pwm##offset##_auto_channels_temp, \ + S_IRUGO | S_IWUSR, show_pwm_temp_map, set_pwm_temp_map, \ + offset - 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 0); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_pwm, \ + S_IRUGO | S_IWUSR, show_auto_pwm, set_auto_pwm, \ + offset - 1, 2); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_pwm, \ + S_IRUGO, show_auto_pwm, NULL, offset - 1, 3); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 1); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point1_temp_hyst, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 0); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point2_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 2); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point3_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 3); \ +static SENSOR_DEVICE_ATTR_2(pwm##offset##_auto_point4_temp, \ + S_IRUGO | S_IWUSR, show_auto_temp, set_auto_temp, \ + offset - 1, 4); show_pwm_offset(1); show_pwm_offset(2); @@ -775,7 +1042,10 @@ static ssize_t set_fan16_min(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sensor_attr = to_sensor_dev_attr(attr); int nr = sensor_attr->index; struct it87_data *data = dev_get_drvdata(dev); - int val = simple_strtol(buf, NULL, 10); + long val; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; mutex_lock(&data->update_lock); data->fan_min[nr] = FAN16_TO_REG(val); @@ -805,7 +1075,8 @@ show_fan16_offset(4); show_fan16_offset(5); /* Alarms */ -static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t show_alarms(struct device *dev, struct device_attribute *attr, + char *buf) { struct it87_data *data = it87_update_device(dev); return sprintf(buf, "%u\n", data->alarms); @@ -836,27 +1107,78 @@ static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 16); static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 17); static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 18); -static ssize_t -show_vrm_reg(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t show_beep(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct it87_data *data = it87_update_device(dev); + return sprintf(buf, "%u\n", (data->beeps >> bitnr) & 1); +} +static ssize_t set_beep(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int bitnr = to_sensor_dev_attr(attr)->index; + struct it87_data *data = dev_get_drvdata(dev); + long val; + + if (strict_strtol(buf, 10, &val) < 0 + || (val != 0 && val != 1)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); + if (val) + data->beeps |= (1 << bitnr); + else + data->beeps &= ~(1 << bitnr); + it87_write_value(data, IT87_REG_BEEP_ENABLE, data->beeps); + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(in0_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 1); +static SENSOR_DEVICE_ATTR(in1_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in2_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in3_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in4_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in5_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in6_beep, S_IRUGO, show_beep, NULL, 1); +static SENSOR_DEVICE_ATTR(in7_beep, S_IRUGO, show_beep, NULL, 1); +/* fanX_beep writability is set later */ +static SENSOR_DEVICE_ATTR(fan1_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan2_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan3_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan4_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(fan5_beep, S_IRUGO, show_beep, set_beep, 0); +static SENSOR_DEVICE_ATTR(temp1_beep, S_IRUGO | S_IWUSR, + show_beep, set_beep, 2); +static SENSOR_DEVICE_ATTR(temp2_beep, S_IRUGO, show_beep, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_beep, S_IRUGO, show_beep, NULL, 2); + +static ssize_t show_vrm_reg(struct device *dev, struct device_attribute *attr, + char *buf) { struct it87_data *data = dev_get_drvdata(dev); return sprintf(buf, "%u\n", data->vrm); } -static ssize_t -store_vrm_reg(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +static ssize_t store_vrm_reg(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { struct it87_data *data = dev_get_drvdata(dev); - u32 val; + unsigned long val; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; - val = simple_strtoul(buf, NULL, 10); data->vrm = val; return count; } static DEVICE_ATTR(vrm, S_IRUGO | S_IWUSR, show_vrm_reg, store_vrm_reg); -static ssize_t -show_vid_reg(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr, + char *buf) { struct it87_data *data = it87_update_device(dev); return sprintf(buf, "%ld\n", (long) vid_from_reg(data->vid, data->vrm)); @@ -931,51 +1253,176 @@ static const struct attribute_group it87_group = { .attrs = it87_attributes, }; -static struct attribute *it87_attributes_opt[] = { +static struct attribute *it87_attributes_beep[] = { + &sensor_dev_attr_in0_beep.dev_attr.attr, + &sensor_dev_attr_in1_beep.dev_attr.attr, + &sensor_dev_attr_in2_beep.dev_attr.attr, + &sensor_dev_attr_in3_beep.dev_attr.attr, + &sensor_dev_attr_in4_beep.dev_attr.attr, + &sensor_dev_attr_in5_beep.dev_attr.attr, + &sensor_dev_attr_in6_beep.dev_attr.attr, + &sensor_dev_attr_in7_beep.dev_attr.attr, + + &sensor_dev_attr_temp1_beep.dev_attr.attr, + &sensor_dev_attr_temp2_beep.dev_attr.attr, + &sensor_dev_attr_temp3_beep.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_beep = { + .attrs = it87_attributes_beep, +}; + +static struct attribute *it87_attributes_fan16[5][3+1] = { { &sensor_dev_attr_fan1_input16.dev_attr.attr, &sensor_dev_attr_fan1_min16.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan2_input16.dev_attr.attr, &sensor_dev_attr_fan2_min16.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan3_input16.dev_attr.attr, &sensor_dev_attr_fan3_min16.dev_attr.attr, + &sensor_dev_attr_fan3_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan4_input16.dev_attr.attr, &sensor_dev_attr_fan4_min16.dev_attr.attr, + &sensor_dev_attr_fan4_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan5_input16.dev_attr.attr, &sensor_dev_attr_fan5_min16.dev_attr.attr, + &sensor_dev_attr_fan5_alarm.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_fan16[5] = { + { .attrs = it87_attributes_fan16[0] }, + { .attrs = it87_attributes_fan16[1] }, + { .attrs = it87_attributes_fan16[2] }, + { .attrs = it87_attributes_fan16[3] }, + { .attrs = it87_attributes_fan16[4] }, +}; +static struct attribute *it87_attributes_fan[3][4+1] = { { &sensor_dev_attr_fan1_input.dev_attr.attr, &sensor_dev_attr_fan1_min.dev_attr.attr, &sensor_dev_attr_fan1_div.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan2_input.dev_attr.attr, &sensor_dev_attr_fan2_min.dev_attr.attr, &sensor_dev_attr_fan2_div.dev_attr.attr, + &sensor_dev_attr_fan2_alarm.dev_attr.attr, + NULL +}, { &sensor_dev_attr_fan3_input.dev_attr.attr, &sensor_dev_attr_fan3_min.dev_attr.attr, &sensor_dev_attr_fan3_div.dev_attr.attr, - - &sensor_dev_attr_fan1_alarm.dev_attr.attr, - &sensor_dev_attr_fan2_alarm.dev_attr.attr, &sensor_dev_attr_fan3_alarm.dev_attr.attr, - &sensor_dev_attr_fan4_alarm.dev_attr.attr, - &sensor_dev_attr_fan5_alarm.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_fan[3] = { + { .attrs = it87_attributes_fan[0] }, + { .attrs = it87_attributes_fan[1] }, + { .attrs = it87_attributes_fan[2] }, +}; + +static const struct attribute_group * +it87_get_fan_group(const struct it87_data *data) +{ + return has_16bit_fans(data) ? it87_group_fan16 : it87_group_fan; +} +static struct attribute *it87_attributes_pwm[3][4+1] = { { &sensor_dev_attr_pwm1_enable.dev_attr.attr, - &sensor_dev_attr_pwm2_enable.dev_attr.attr, - &sensor_dev_attr_pwm3_enable.dev_attr.attr, &sensor_dev_attr_pwm1.dev_attr.attr, - &sensor_dev_attr_pwm2.dev_attr.attr, - &sensor_dev_attr_pwm3.dev_attr.attr, &dev_attr_pwm1_freq.attr, + &sensor_dev_attr_pwm1_auto_channels_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, &dev_attr_pwm2_freq.attr, + &sensor_dev_attr_pwm2_auto_channels_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, &dev_attr_pwm3_freq.attr, + &sensor_dev_attr_pwm3_auto_channels_temp.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_pwm[3] = { + { .attrs = it87_attributes_pwm[0] }, + { .attrs = it87_attributes_pwm[1] }, + { .attrs = it87_attributes_pwm[2] }, +}; +static struct attribute *it87_attributes_autopwm[3][9+1] = { { + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point1_temp_hyst.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_autopwm[3] = { + { .attrs = it87_attributes_autopwm[0] }, + { .attrs = it87_attributes_autopwm[1] }, + { .attrs = it87_attributes_autopwm[2] }, +}; + +static struct attribute *it87_attributes_fan_beep[] = { + &sensor_dev_attr_fan1_beep.dev_attr.attr, + &sensor_dev_attr_fan2_beep.dev_attr.attr, + &sensor_dev_attr_fan3_beep.dev_attr.attr, + &sensor_dev_attr_fan4_beep.dev_attr.attr, + &sensor_dev_attr_fan5_beep.dev_attr.attr, +}; + +static struct attribute *it87_attributes_vid[] = { &dev_attr_vrm.attr, &dev_attr_cpu0_vid.attr, NULL }; -static const struct attribute_group it87_group_opt = { - .attrs = it87_attributes_opt, +static const struct attribute_group it87_group_vid = { + .attrs = it87_attributes_vid, }; /* SuperIO detection - will change isa_address if a chip is found */ @@ -1035,6 +1482,10 @@ static int __init it87_find(unsigned short *address, if (sio_data->type == it87) { /* The IT8705F doesn't have VID pins at all */ sio_data->skip_vid = 1; + + /* The IT8705F has a different LD number for GPIO */ + superio_select(5); + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } else { int reg; @@ -1068,7 +1519,11 @@ static int __init it87_find(unsigned short *address, pr_info("it87: in3 is VCC (+5V)\n"); if (reg & (1 << 1)) pr_info("it87: in7 is VCCH (+5V Stand-By)\n"); + + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } + if (sio_data->beep_pin) + pr_info("it87: Beeping is supported\n"); /* Disable specific features based on DMI strings */ board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); @@ -1093,14 +1548,46 @@ exit: return err; } +static void it87_remove_files(struct device *dev) +{ + struct it87_data *data = platform_get_drvdata(pdev); + struct it87_sio_data *sio_data = dev->platform_data; + const struct attribute_group *fan_group = it87_get_fan_group(data); + int i; + + sysfs_remove_group(&dev->kobj, &it87_group); + if (sio_data->beep_pin) + sysfs_remove_group(&dev->kobj, &it87_group_beep); + for (i = 0; i < 5; i++) { + if (!(data->has_fan & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &fan_group[i]); + if (sio_data->beep_pin) + sysfs_remove_file(&dev->kobj, + it87_attributes_fan_beep[i]); + } + for (i = 0; i < 3; i++) { + if (sio_data->skip_pwm & (1 << 0)) + continue; + sysfs_remove_group(&dev->kobj, &it87_group_pwm[i]); + if (has_old_autopwm(data)) + sysfs_remove_group(&dev->kobj, + &it87_group_autopwm[i]); + } + if (!sio_data->skip_vid) + sysfs_remove_group(&dev->kobj, &it87_group_vid); +} + static int __devinit it87_probe(struct platform_device *pdev) { struct it87_data *data; struct resource *res; struct device *dev = &pdev->dev; struct it87_sio_data *sio_data = dev->platform_data; - int err = 0; + const struct attribute_group *fan_group; + int err = 0, i; int enable_pwm_interface; + int fan_beep_need_rw; static const char *names[] = { "it87", "it8712", @@ -1118,7 +1605,8 @@ static int __devinit it87_probe(struct platform_device *pdev) goto ERROR0; } - if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) { + data = kzalloc(sizeof(struct it87_data), GFP_KERNEL); + if (!data) { err = -ENOMEM; goto ERROR1; } @@ -1146,120 +1634,60 @@ static int __devinit it87_probe(struct platform_device *pdev) it87_init_device(pdev); /* Register sysfs hooks */ - if ((err = sysfs_create_group(&dev->kobj, &it87_group))) + err = sysfs_create_group(&dev->kobj, &it87_group); + if (err) goto ERROR2; + if (sio_data->beep_pin) { + err = sysfs_create_group(&dev->kobj, &it87_group_beep); + if (err) + goto ERROR4; + } + /* Do not create fan files for disabled fans */ - if (has_16bit_fans(data)) { - /* 16-bit tachometers */ - if (data->has_fan & (1 << 0)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan1_input16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan1_min16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan1_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 1)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan2_input16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan2_min16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan2_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 2)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan3_input16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan3_min16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan3_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 3)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan4_input16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan4_min16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan4_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 4)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan5_input16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan5_min16.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan5_alarm.dev_attr))) - goto ERROR4; - } - } else { - /* 8-bit tachometers with clock divider */ - if (data->has_fan & (1 << 0)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan1_input.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan1_min.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan1_div.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan1_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 1)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan2_input.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan2_min.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan2_div.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan2_alarm.dev_attr))) - goto ERROR4; - } - if (data->has_fan & (1 << 2)) { - if ((err = device_create_file(dev, - &sensor_dev_attr_fan3_input.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan3_min.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan3_div.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_fan3_alarm.dev_attr))) + fan_group = it87_get_fan_group(data); + fan_beep_need_rw = 1; + for (i = 0; i < 5; i++) { + if (!(data->has_fan & (1 << i))) + continue; + err = sysfs_create_group(&dev->kobj, &fan_group[i]); + if (err) + goto ERROR4; + + if (sio_data->beep_pin) { + err = sysfs_create_file(&dev->kobj, + it87_attributes_fan_beep[i]); + if (err) goto ERROR4; + if (!fan_beep_need_rw) + continue; + + /* As we have a single beep enable bit for all fans, + * only the first enabled fan has a writable attribute + * for it. */ + if (sysfs_chmod_file(&dev->kobj, + it87_attributes_fan_beep[i], + S_IRUGO | S_IWUSR)) + dev_dbg(dev, "chmod +w fan%d_beep failed\n", + i + 1); + fan_beep_need_rw = 0; } } if (enable_pwm_interface) { - if (!(sio_data->skip_pwm & (1 << 0))) { - if ((err = device_create_file(dev, - &sensor_dev_attr_pwm1_enable.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_pwm1.dev_attr)) - || (err = device_create_file(dev, - &dev_attr_pwm1_freq))) - goto ERROR4; - } - if (!(sio_data->skip_pwm & (1 << 1))) { - if ((err = device_create_file(dev, - &sensor_dev_attr_pwm2_enable.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_pwm2.dev_attr)) - || (err = device_create_file(dev, - &dev_attr_pwm2_freq))) + for (i = 0; i < 3; i++) { + if (sio_data->skip_pwm & (1 << i)) + continue; + err = sysfs_create_group(&dev->kobj, + &it87_group_pwm[i]); + if (err) goto ERROR4; - } - if (!(sio_data->skip_pwm & (1 << 2))) { - if ((err = device_create_file(dev, - &sensor_dev_attr_pwm3_enable.dev_attr)) - || (err = device_create_file(dev, - &sensor_dev_attr_pwm3.dev_attr)) - || (err = device_create_file(dev, - &dev_attr_pwm3_freq))) + + if (!has_old_autopwm(data)) + continue; + err = sysfs_create_group(&dev->kobj, + &it87_group_autopwm[i]); + if (err) goto ERROR4; } } @@ -1268,10 +1696,8 @@ static int __devinit it87_probe(struct platform_device *pdev) data->vrm = vid_which_vrm(); /* VID reading from Super-I/O config space if available */ data->vid = sio_data->vid_value; - if ((err = device_create_file(dev, - &dev_attr_vrm)) - || (err = device_create_file(dev, - &dev_attr_cpu0_vid))) + err = sysfs_create_group(&dev->kobj, &it87_group_vid); + if (err) goto ERROR4; } @@ -1284,8 +1710,7 @@ static int __devinit it87_probe(struct platform_device *pdev) return 0; ERROR4: - sysfs_remove_group(&dev->kobj, &it87_group); - sysfs_remove_group(&dev->kobj, &it87_group_opt); + it87_remove_files(dev); ERROR2: platform_set_drvdata(pdev, NULL); kfree(data); @@ -1300,8 +1725,7 @@ static int __devexit it87_remove(struct platform_device *pdev) struct it87_data *data = platform_get_drvdata(pdev); hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &it87_group); - sysfs_remove_group(&pdev->dev.kobj, &it87_group_opt); + it87_remove_files(&pdev->dev); release_region(data->addr, IT87_EC_EXTENT); platform_set_drvdata(pdev, NULL); @@ -1387,15 +1811,18 @@ static void __devinit it87_init_device(struct platform_device *pdev) int tmp, i; u8 mask; - /* initialize to sane defaults: - * - if the chip is in manual pwm mode, this will be overwritten with - * the actual settings on the chip (so in this case, initialization - * is not needed) - * - if in automatic or on/off mode, we could switch to manual mode, - * read the registers and set manual_pwm_ctl accordingly, but currently - * this is not implemented, so we initialize to something sane */ + /* For each PWM channel: + * - If it is in automatic mode, setting to manual mode should set + * the fan to full speed by default. + * - If it is in manual mode, we need a mapping to temperature + * channels to use when later setting to automatic mode later. + * Use a 1:1 mapping by default (we are clueless.) + * In both cases, the value can (and should) be changed by the user + * prior to switching to a different mode. */ for (i = 0; i < 3; i++) { - data->manual_pwm_ctl[i] = 0xff; + data->pwm_temp_map[i] = i; + data->pwm_duty[i] = 0x7f; /* Full speed */ + data->auto_pwm[i][3] = 0x7f; /* Full speed, hard-coded */ } /* Some chips seem to have default value 0xff for all limit @@ -1436,7 +1863,8 @@ static void __devinit it87_init_device(struct platform_device *pdev) if ((data->fan_main_ctrl & mask) == 0) { /* Enable all fan tachometers */ data->fan_main_ctrl |= mask; - it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, data->fan_main_ctrl); + it87_write_value(data, IT87_REG_FAN_MAIN_CTRL, + data->fan_main_ctrl); } data->has_fan = (data->fan_main_ctrl >> 4) & 0x07; @@ -1461,30 +1889,32 @@ static void __devinit it87_init_device(struct platform_device *pdev) /* Fan input pins may be used for alternative functions */ data->has_fan &= ~sio_data->skip_fan; - /* Set current fan mode registers and the default settings for the - * other mode registers */ - for (i = 0; i < 3; i++) { - if (data->fan_main_ctrl & (1 << i)) { - /* pwm mode */ - tmp = it87_read_value(data, IT87_REG_PWM(i)); - if (tmp & 0x80) { - /* automatic pwm - not yet implemented, but - * leave the settings made by the BIOS alone - * until a change is requested via the sysfs - * interface */ - } else { - /* manual pwm */ - data->manual_pwm_ctl[i] = PWM_FROM_REG(tmp); - } - } - } - /* Start monitoring */ it87_write_value(data, IT87_REG_CONFIG, (it87_read_value(data, IT87_REG_CONFIG) & 0x36) | (update_vbat ? 0x41 : 0x01)); } +static void it87_update_pwm_ctrl(struct it87_data *data, int nr) +{ + data->pwm_ctrl[nr] = it87_read_value(data, IT87_REG_PWM(nr)); + if (data->pwm_ctrl[nr] & 0x80) /* Automatic mode */ + data->pwm_temp_map[nr] = data->pwm_ctrl[nr] & 0x03; + else /* Manual mode */ + data->pwm_duty[nr] = data->pwm_ctrl[nr] & 0x7f; + + if (has_old_autopwm(data)) { + int i; + + for (i = 0; i < 5 ; i++) + data->auto_temp[nr][i] = it87_read_value(data, + IT87_REG_AUTO_TEMP(nr, i)); + for (i = 0; i < 3 ; i++) + data->auto_pwm[nr][i] = it87_read_value(data, + IT87_REG_AUTO_PWM(nr, i)); + } +} + static struct it87_data *it87_update_device(struct device *dev) { struct it87_data *data = dev_get_drvdata(dev); @@ -1494,24 +1924,22 @@ static struct it87_data *it87_update_device(struct device *dev) if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { - if (update_vbat) { /* Cleared after each update, so reenable. Value - returned by this read will be previous value */ + returned by this read will be previous value */ it87_write_value(data, IT87_REG_CONFIG, - it87_read_value(data, IT87_REG_CONFIG) | 0x40); + it87_read_value(data, IT87_REG_CONFIG) | 0x40); } for (i = 0; i <= 7; i++) { data->in[i] = - it87_read_value(data, IT87_REG_VIN(i)); + it87_read_value(data, IT87_REG_VIN(i)); data->in_min[i] = - it87_read_value(data, IT87_REG_VIN_MIN(i)); + it87_read_value(data, IT87_REG_VIN_MIN(i)); data->in_max[i] = - it87_read_value(data, IT87_REG_VIN_MAX(i)); + it87_read_value(data, IT87_REG_VIN_MAX(i)); } /* in8 (battery) has no limit registers */ - data->in[8] = - it87_read_value(data, IT87_REG_VIN(8)); + data->in[8] = it87_read_value(data, IT87_REG_VIN(8)); for (i = 0; i < 5; i++) { /* Skip disabled fans */ @@ -1519,7 +1947,7 @@ static struct it87_data *it87_update_device(struct device *dev) continue; data->fan_min[i] = - it87_read_value(data, IT87_REG_FAN_MIN[i]); + it87_read_value(data, IT87_REG_FAN_MIN[i]); data->fan[i] = it87_read_value(data, IT87_REG_FAN[i]); /* Add high byte if in 16-bit mode */ @@ -1532,11 +1960,11 @@ static struct it87_data *it87_update_device(struct device *dev) } for (i = 0; i < 3; i++) { data->temp[i] = - it87_read_value(data, IT87_REG_TEMP(i)); + it87_read_value(data, IT87_REG_TEMP(i)); data->temp_high[i] = - it87_read_value(data, IT87_REG_TEMP_HIGH(i)); + it87_read_value(data, IT87_REG_TEMP_HIGH(i)); data->temp_low[i] = - it87_read_value(data, IT87_REG_TEMP_LOW(i)); + it87_read_value(data, IT87_REG_TEMP_LOW(i)); } /* Newer chips don't have clock dividers */ @@ -1551,9 +1979,13 @@ static struct it87_data *it87_update_device(struct device *dev) it87_read_value(data, IT87_REG_ALARM1) | (it87_read_value(data, IT87_REG_ALARM2) << 8) | (it87_read_value(data, IT87_REG_ALARM3) << 16); + data->beeps = it87_read_value(data, IT87_REG_BEEP_ENABLE); + data->fan_main_ctrl = it87_read_value(data, IT87_REG_FAN_MAIN_CTRL); data->fan_ctl = it87_read_value(data, IT87_REG_FAN_CTL); + for (i = 0; i < 3; i++) + it87_update_pwm_ctrl(data, i); data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); /* The 8705 does not have VID capability. @@ -1628,7 +2060,7 @@ exit: static int __init sm_it87_init(void) { int err; - unsigned short isa_address=0; + unsigned short isa_address = 0; struct it87_sio_data sio_data; memset(&sio_data, 0, sizeof(struct it87_sio_data)); @@ -1640,7 +2072,7 @@ static int __init sm_it87_init(void) return err; err = it87_device_add(isa_address, &sio_data); - if (err){ + if (err) { platform_driver_unregister(&it87_driver); return err; } @@ -1661,7 +2093,8 @@ MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8720F/8726F, SiS950 driver"); module_param(update_vbat, bool, 0); MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); module_param(fix_pwm_polarity, bool, 0); -MODULE_PARM_DESC(fix_pwm_polarity, "Force PWM polarity to active high (DANGEROUS)"); +MODULE_PARM_DESC(fix_pwm_polarity, + "Force PWM polarity to active high (DANGEROUS)"); MODULE_LICENSE("GPL"); module_init(sm_it87_init); diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 7c9bdc167426..7cc2708871ab 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -1,7 +1,7 @@ /* * lm90.c - Part of lm_sensors, Linux kernel modules for hardware * monitoring - * Copyright (C) 2003-2009 Jean Delvare <khali@linux-fr.org> + * Copyright (C) 2003-2010 Jean Delvare <khali@linux-fr.org> * * Based on the lm83 driver. The LM90 is a sensor chip made by National * Semiconductor. It reports up to two temperatures (its own plus up to @@ -93,7 +93,8 @@ static const unsigned short normal_i2c[] = { 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, 0x4c, 0x4d, 0x4e, I2C_CLIENT_END }; -enum chips { lm90, adm1032, lm99, lm86, max6657, adt7461, max6680, max6646 }; +enum chips { lm90, adm1032, lm99, lm86, max6657, adt7461, max6680, max6646, + w83l771 }; /* * The LM90 registers @@ -151,6 +152,7 @@ static int lm90_detect(struct i2c_client *client, struct i2c_board_info *info); static int lm90_probe(struct i2c_client *client, const struct i2c_device_id *id); static void lm90_init_client(struct i2c_client *client); +static void lm90_alert(struct i2c_client *client, unsigned int flag); static int lm90_remove(struct i2c_client *client); static struct lm90_data *lm90_update_device(struct device *dev); @@ -173,6 +175,7 @@ static const struct i2c_device_id lm90_id[] = { { "max6659", max6657 }, { "max6680", max6680 }, { "max6681", max6680 }, + { "w83l771", w83l771 }, { } }; MODULE_DEVICE_TABLE(i2c, lm90_id); @@ -184,6 +187,7 @@ static struct i2c_driver lm90_driver = { }, .probe = lm90_probe, .remove = lm90_remove, + .alert = lm90_alert, .id_table = lm90_id, .detect = lm90_detect, .address_list = normal_i2c, @@ -201,6 +205,9 @@ struct lm90_data { int kind; int flags; + u8 config_orig; /* Original configuration register value */ + u8 alert_alarms; /* Which alarm bits trigger ALERT# */ + /* registers values */ s8 temp8[4]; /* 0: local low limit 1: local high limit @@ -758,6 +765,14 @@ static int lm90_detect(struct i2c_client *new_client, && reg_convrate <= 0x07) { name = "max6646"; } + } else + if (address == 0x4C + && man_id == 0x5C) { /* Winbond/Nuvoton */ + if ((chip_id & 0xFE) == 0x10 /* W83L771AWG/ASG */ + && (reg_config1 & 0x2A) == 0x00 + && reg_convrate <= 0x08) { + name = "w83l771"; + } } if (!name) { /* identification failed */ @@ -794,6 +809,19 @@ static int lm90_probe(struct i2c_client *new_client, new_client->flags &= ~I2C_CLIENT_PEC; } + /* Different devices have different alarm bits triggering the + * ALERT# output */ + switch (data->kind) { + case lm90: + case lm99: + case lm86: + data->alert_alarms = 0x7b; + break; + default: + data->alert_alarms = 0x7c; + break; + } + /* Initialize the LM90 chip */ lm90_init_client(new_client); @@ -830,7 +858,7 @@ exit: static void lm90_init_client(struct i2c_client *client) { - u8 config, config_orig; + u8 config; struct lm90_data *data = i2c_get_clientdata(client); /* @@ -842,7 +870,7 @@ static void lm90_init_client(struct i2c_client *client) dev_warn(&client->dev, "Initialization failed!\n"); return; } - config_orig = config; + data->config_orig = config; /* Check Temperature Range Select */ if (data->kind == adt7461) { @@ -860,7 +888,7 @@ static void lm90_init_client(struct i2c_client *client) } config &= 0xBF; /* run */ - if (config != config_orig) /* Only write if changed */ + if (config != data->config_orig) /* Only write if changed */ i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config); } @@ -875,10 +903,46 @@ static int lm90_remove(struct i2c_client *client) device_remove_file(&client->dev, &sensor_dev_attr_temp2_offset.dev_attr); + /* Restore initial configuration */ + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + data->config_orig); + kfree(data); return 0; } +static void lm90_alert(struct i2c_client *client, unsigned int flag) +{ + struct lm90_data *data = i2c_get_clientdata(client); + u8 config, alarms; + + lm90_read_reg(client, LM90_REG_R_STATUS, &alarms); + if ((alarms & 0x7f) == 0) { + dev_info(&client->dev, "Everything OK\n"); + } else { + if (alarms & 0x61) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 1); + if (alarms & 0x1a) + dev_warn(&client->dev, + "temp%d out of range, please check!\n", 2); + if (alarms & 0x04) + dev_warn(&client->dev, + "temp%d diode open, please check!\n", 2); + + /* Disable ALERT# output, because these chips don't implement + SMBus alert correctly; they should only hold the alert line + low briefly. */ + if ((data->kind == adm1032 || data->kind == adt7461) + && (alarms & data->alert_alarms)) { + dev_dbg(&client->dev, "Disabling ALERT#\n"); + lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); + i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, + config | 0x80); + } + } +} + static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value) { int err; @@ -966,6 +1030,21 @@ static struct lm90_data *lm90_update_device(struct device *dev) } lm90_read_reg(client, LM90_REG_R_STATUS, &data->alarms); + /* Re-enable ALERT# output if it was originally enabled and + * relevant alarms are all clear */ + if ((data->config_orig & 0x80) == 0 + && (data->alarms & data->alert_alarms) == 0) { + u8 config; + + lm90_read_reg(client, LM90_REG_R_CONFIG1, &config); + if (config & 0x80) { + dev_dbg(&client->dev, "Re-enabling ALERT#\n"); + i2c_smbus_write_byte_data(client, + LM90_REG_W_CONFIG1, + config & ~0x80); + } + } + data->last_updated = jiffies; data->valid = 1; } diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index a13b30e8d8d8..d14a1af9f550 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -134,7 +134,7 @@ struct tmp401_data { struct mutex update_lock; char valid; /* zero until following fields are valid */ unsigned long last_updated; /* in jiffies */ - int kind; + enum chips kind; /* register values */ u8 status; @@ -524,7 +524,7 @@ static int tmp401_detect(struct i2c_client *client, if (reg > 15) return -ENODEV; - strlcpy(info->type, tmp401_id[kind - 1].name, I2C_NAME_SIZE); + strlcpy(info->type, tmp401_id[kind].name, I2C_NAME_SIZE); return 0; } @@ -572,8 +572,7 @@ static int tmp401_probe(struct i2c_client *client, goto exit_remove; } - dev_info(&client->dev, "Detected TI %s chip\n", - names[data->kind - 1]); + dev_info(&client->dev, "Detected TI %s chip\n", names[data->kind]); return 0; diff --git a/drivers/hwmon/tmp421.c b/drivers/hwmon/tmp421.c index 4f7c051e2d7b..738c472ece27 100644 --- a/drivers/hwmon/tmp421.c +++ b/drivers/hwmon/tmp421.c @@ -61,9 +61,9 @@ static const u8 TMP421_TEMP_LSB[4] = { 0x10, 0x11, 0x12, 0x13 }; #define TMP423_DEVICE_ID 0x23 static const struct i2c_device_id tmp421_id[] = { - { "tmp421", tmp421 }, - { "tmp422", tmp422 }, - { "tmp423", tmp423 }, + { "tmp421", 2 }, + { "tmp422", 3 }, + { "tmp423", 4 }, { } }; MODULE_DEVICE_TABLE(i2c, tmp421_id); @@ -73,21 +73,23 @@ struct tmp421_data { struct mutex update_lock; char valid; unsigned long last_updated; - int kind; + int channels; u8 config; s16 temp[4]; }; static int temp_from_s16(s16 reg) { - int temp = reg; + /* Mask out status bits */ + int temp = reg & ~0xf; return (temp * 1000 + 128) / 256; } static int temp_from_u16(u16 reg) { - int temp = reg; + /* Mask out status bits */ + int temp = reg & ~0xf; /* Add offset for extended temperature range. */ temp -= 64 * 256; @@ -107,7 +109,7 @@ static struct tmp421_data *tmp421_update_device(struct device *dev) data->config = i2c_smbus_read_byte_data(client, TMP421_CONFIG_REG_1); - for (i = 0; i <= data->kind; i++) { + for (i = 0; i < data->channels; i++) { data->temp[i] = i2c_smbus_read_byte_data(client, TMP421_TEMP_MSB[i]) << 8; data->temp[i] |= i2c_smbus_read_byte_data(client, @@ -166,7 +168,7 @@ static mode_t tmp421_is_visible(struct kobject *kobj, struct attribute *a, devattr = container_of(a, struct device_attribute, attr); index = to_sensor_dev_attr(devattr)->index; - if (data->kind > index) + if (index < data->channels) return a->mode; return 0; @@ -252,9 +254,9 @@ static int tmp421_detect(struct i2c_client *client, return -ENODEV; } - strlcpy(info->type, tmp421_id[kind - 1].name, I2C_NAME_SIZE); + strlcpy(info->type, tmp421_id[kind].name, I2C_NAME_SIZE); dev_info(&adapter->dev, "Detected TI %s chip at 0x%02x\n", - names[kind - 1], client->addr); + names[kind], client->addr); return 0; } @@ -271,7 +273,7 @@ static int tmp421_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); - data->kind = id->driver_data; + data->channels = id->driver_data; err = tmp421_init_client(client); if (err) diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c index 9a2022b67495..9de81a4c15a2 100644 --- a/drivers/hwmon/w83793.c +++ b/drivers/hwmon/w83793.c @@ -3,6 +3,10 @@ Copyright (C) 2006 Winbond Electronics Corp. Yuan Mu Rudolf Marek <r.marek@assembler.cz> + Copyright (C) 2009-2010 Sven Anders <anders@anduras.de>, ANDURAS AG. + Watchdog driver part + (Based partially on fschmd driver, + Copyright 2007-2008 by Hans de Goede) 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 @@ -35,6 +39,16 @@ #include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/fs.h> +#include <linux/watchdog.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/kref.h> +#include <linux/notifier.h> +#include <linux/reboot.h> + +/* Default values */ +#define WATCHDOG_TIMEOUT 2 /* 2 minute default timeout */ /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, @@ -51,6 +65,18 @@ static int reset; module_param(reset, bool, 0); MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); +static int timeout = WATCHDOG_TIMEOUT; /* default timeout in minutes */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in minutes. 2<= timeout <=255 (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + /* Address 0x00, 0x0d, 0x0e, 0x0f in all three banks are reserved as ID, Bank Select registers @@ -72,6 +98,11 @@ MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended"); #define W83793_REG_VID_LATCHB 0x08 #define W83793_REG_VID_CTRL 0x59 +#define W83793_REG_WDT_LOCK 0x01 +#define W83793_REG_WDT_ENABLE 0x02 +#define W83793_REG_WDT_STATUS 0x03 +#define W83793_REG_WDT_TIMEOUT 0x04 + static u16 W83793_REG_TEMP_MODE[2] = { 0x5e, 0x5f }; #define TEMP_READ 0 @@ -223,8 +254,37 @@ struct w83793_data { u8 tolerance[3]; /* Temp tolerance(Smart Fan I/II) */ u8 sf2_pwm[6][7]; /* Smart FanII: Fan duty cycle */ u8 sf2_temp[6][7]; /* Smart FanII: Temp level point */ + + /* watchdog */ + struct i2c_client *client; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; + unsigned long watchdog_is_open; + char watchdog_expect_close; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ + unsigned int watchdog_caused_reboot; + int watchdog_timeout; /* watchdog timeout in minutes */ }; +/* Somewhat ugly :( global data pointer list with all devices, so that + we can find our device data as when using misc_register. There is no + other method to get to one's device data from the open file-op and + for usage in the reboot notifier callback. */ +static LIST_HEAD(watchdog_data_list); + +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + +/* Release our data struct when we're detached from the i2c client *and* all + references to our watchdog device are released */ +static void w83793_release_resources(struct kref *ref) +{ + struct w83793_data *data = container_of(ref, struct w83793_data, kref); + kfree(data); +} + static u8 w83793_read_value(struct i2c_client *client, u16 reg); static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value); static int w83793_probe(struct i2c_client *client, @@ -1063,14 +1123,349 @@ static void w83793_init_client(struct i2c_client *client) /* Start monitoring */ w83793_write_value(client, W83793_REG_CONFIG, w83793_read_value(client, W83793_REG_CONFIG) | 0x01); +} + +/* + * Watchdog routines + */ + +static int watchdog_set_timeout(struct w83793_data *data, int timeout) +{ + int ret, mtimeout; + + mtimeout = DIV_ROUND_UP(timeout, 60); + + if (mtimeout > 255) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_timeout = mtimeout; + + /* Set Timeout value (in Minutes) */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + + ret = mtimeout * 60; + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct w83793_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + timeout = data->watchdog_timeout * 60; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_trigger(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Set Timeout value (in Minutes) */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_enable(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Set initial timeout */ + w83793_write_value(data->client, W83793_REG_WDT_TIMEOUT, + data->watchdog_timeout); + + /* Enable Soft Watchdog */ + w83793_write_value(data->client, W83793_REG_WDT_LOCK, 0x55); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_disable(struct w83793_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + /* Disable Soft Watchdog */ + w83793_write_value(data->client, W83793_REG_WDT_LOCK, 0xAA); + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct w83793_data *pos, *data = NULL; + int watchdog_is_open; + + /* We get called from drivers/char/misc.c with misc_mtx hold, and we + call misc_register() from w83793_probe() with watchdog_data_mutex + hold, as misc_register() takes the misc_mtx lock, this is a possible + deadlock, so we use mutex_trylock here. */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + + /* Check, if device is already open */ + watchdog_is_open = test_and_set_bit(0, &data->watchdog_is_open); + + /* Increase data reference counter (if not already done). + Note we can never not have found data, so we don't check for this */ + if (!watchdog_is_open) + kref_get(&data->kref); + + mutex_unlock(&watchdog_data_mutex); + + /* Check, if device is already open and possibly issue error */ + if (watchdog_is_open) + return -EBUSY; + + /* Enable Soft Watchdog */ + watchdog_enable(data); + + /* Store pointer to data into filp's private data */ + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int watchdog_close(struct inode *inode, struct file *filp) +{ + struct w83793_data *data = filp->private_data; + if (data->watchdog_expect_close) { + watchdog_disable(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + dev_crit(&data->client->dev, + "unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + /* Decrease data reference counter */ + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + size_t ret; + struct w83793_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret < 0) + return ret; + } + return count; +} + +static int watchdog_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_CARDRESET, + .identity = "w83793 watchdog" + }; + + int val, ret = 0; + struct w83793_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + val = data->watchdog_caused_reboot ? WDIOF_CARDRESET : 0; + ret = put_user(val, (int __user *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + val = watchdog_get_timeout(data); + ret = put_user(val, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(val, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, val); + if (ret > 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(val, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (val & WDIOS_DISABLECARD) + ret = watchdog_disable(data); + else if (val & WDIOS_ENABLECARD) + ret = watchdog_enable(data); + else + ret = -EINVAL; + + break; + default: + ret = -ENOTTY; + } + + return ret; +} + +static const struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_close, + .write = watchdog_write, + .ioctl = watchdog_ioctl, +}; + +/* + * Notifier for system down + */ + +static int watchdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct w83793_data *data = NULL; + + if (code == SYS_DOWN || code == SYS_HALT) { + + /* Disable each registered watchdog */ + mutex_lock(&watchdog_data_mutex); + list_for_each_entry(data, &watchdog_data_list, list) { + if (data->watchdog_miscdev.minor) + watchdog_disable(data); + } + mutex_unlock(&watchdog_data_mutex); + } + + return NOTIFY_DONE; } +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block watchdog_notifier = { + .notifier_call = watchdog_notify_sys, +}; + +/* + * Init / remove routines + */ + static int w83793_remove(struct i2c_client *client) { struct w83793_data *data = i2c_get_clientdata(client); struct device *dev = &client->dev; - int i; + int i, tmp; + + /* Unregister the watchdog (if registered) */ + if (data->watchdog_miscdev.minor) { + misc_deregister(&data->watchdog_miscdev); + + if (data->watchdog_is_open) { + dev_warn(&client->dev, + "i2c client detached with watchdog open! " + "Stopping watchdog.\n"); + watchdog_disable(data); + } + + mutex_lock(&watchdog_data_mutex); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + + /* Tell the watchdog code the client is gone */ + mutex_lock(&data->watchdog_lock); + data->client = NULL; + mutex_unlock(&data->watchdog_lock); + } + + /* Reset Configuration Register to Disable Watch Dog Registers */ + tmp = w83793_read_value(client, W83793_REG_CONFIG); + w83793_write_value(client, W83793_REG_CONFIG, tmp & ~0x04); + + unregister_reboot_notifier(&watchdog_notifier); hwmon_device_unregister(data->hwmon_dev); @@ -1099,7 +1494,10 @@ static int w83793_remove(struct i2c_client *client) if (data->lm75[1] != NULL) i2c_unregister_device(data->lm75[1]); - kfree(data); + /* Decrease data reference counter */ + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, w83793_release_resources); + mutex_unlock(&watchdog_data_mutex); return 0; } @@ -1203,6 +1601,7 @@ static int w83793_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct device *dev = &client->dev; + const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; struct w83793_data *data; int i, tmp, val, err; int files_fan = ARRAY_SIZE(w83793_left_fan) / 7; @@ -1218,6 +1617,14 @@ static int w83793_probe(struct i2c_client *client, i2c_set_clientdata(client, data); data->bank = i2c_smbus_read_byte_data(client, W83793_REG_BANKSEL); mutex_init(&data->update_lock); + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + + /* Store client pointer in our data struct for watchdog usage + (where the client is found through a data ptr instead of the + otherway around) */ + data->client = client; err = w83793_detect_subclients(client); if (err) @@ -1380,8 +1787,77 @@ static int w83793_probe(struct i2c_client *client, goto exit_remove; } + /* Watchdog initialization */ + + /* Register boot notifier */ + err = register_reboot_notifier(&watchdog_notifier); + if (err != 0) { + dev_err(&client->dev, + "cannot register reboot notifier (err=%d)\n", err); + goto exit_devunreg; + } + + /* Enable Watchdog registers. + Set Configuration Register to Enable Watch Dog Registers + (Bit 2) = XXXX, X1XX. */ + tmp = w83793_read_value(client, W83793_REG_CONFIG); + w83793_write_value(client, W83793_REG_CONFIG, tmp | 0x04); + + /* Set the default watchdog timeout */ + data->watchdog_timeout = timeout; + + /* Check, if last reboot was caused by watchdog */ + data->watchdog_caused_reboot = + w83793_read_value(data->client, W83793_REG_WDT_STATUS) & 0x01; + + /* Disable Soft Watchdog during initialiation */ + watchdog_disable(data); + + /* We take the data_mutex lock early so that watchdog_open() cannot + run when misc_register() has completed, but we've not yet added + our data to the watchdog_data_list (and set the default timeout) */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) { + data->watchdog_miscdev.minor = 0; + dev_err(&client->dev, + "Registering watchdog chardev: %d\n", err); + break; + } + + list_add(&data->list, &watchdog_data_list); + + dev_info(&client->dev, + "Registered watchdog chardev major 10, minor: %d\n", + watchdog_minors[i]); + break; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + data->watchdog_miscdev.minor = 0; + dev_warn(&client->dev, "Couldn't register watchdog chardev " + "(due to no free minor)\n"); + } + + mutex_unlock(&watchdog_data_mutex); + return 0; + /* Unregister hwmon device */ + +exit_devunreg: + + hwmon_device_unregister(data->hwmon_dev); + /* Unregister sysfs hooks */ exit_remove: @@ -1628,7 +2104,7 @@ static void __exit sensors_w83793_exit(void) i2c_del_driver(&w83793_driver); } -MODULE_AUTHOR("Yuan Mu"); +MODULE_AUTHOR("Yuan Mu, Sven Anders"); MODULE_DESCRIPTION("w83793 driver"); MODULE_LICENSE("GPL"); |