Commit 2f2408a8 authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6

* 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6: (29 commits)
  hwmon: Fix various typos
  hwmon: Check for ACPI resource conflicts
  hwmon: (lm70) Add TI TMP121 support
  hwmon: (lm70) Code streamlining and cleanup
  hwmon: Deprecate the fscher and fscpos drivers
  hwmon: (fschmd) Add watchdog support
  hwmon: (fschmd) Cleanups for watchdog support
  hwmon: (i5k_amb) Load automatically on all 5000/5400 chipsets
  hwmon: (it87) Add support for the ITE IT8720F
  hwmon: Don't overuse I2C_CLIENT_MODULE_PARM
  hwmon: Add LTC4245 driver
  hwmon: (f71882fg) Fix fan_to/from_reg prototypes
  hwmon: (f71882fg) Printout fan modes
  hwmon: (f71882fg) Add documentation
  hwmon: (f71882fg) Fix auto_channels_temp temp numbering with f8000
  hwmon: (f71882fg) Add missing pwm3 attr for f71862fg
  hwmon: (f71882fg) Add F8000 support
  hwmon: (f71882fg) Remove the fan_mode module option
  hwmon: (f71882fg) Separate max and crit alarm and beep
  hwmon: (f71882fg) Check for hwmon powerdown state
  ...
parents fa7b906e 77fa49d9
...@@ -318,6 +318,14 @@ Who: Jean Delvare <khali@linux-fr.org> ...@@ -318,6 +318,14 @@ Who: Jean Delvare <khali@linux-fr.org>
--------------------------- ---------------------------
What: fscher and fscpos drivers
When: June 2009
Why: Deprecated by the new fschmd driver.
Who: Hans de Goede <hdegoede@redhat.com>
Jean Delvare <khali@linux-fr.org>
---------------------------
What: SELinux "compat_net" functionality What: SELinux "compat_net" functionality
When: 2.6.30 at the earliest When: 2.6.30 at the earliest
Why: In 2.6.18 the Secmark concept was introduced to replace the "compat_net" Why: In 2.6.18 the Secmark concept was introduced to replace the "compat_net"
......
Kernel driver f71882fg
======================
Supported chips:
* Fintek F71882FG and F71883FG
Prefix: 'f71882fg'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Available from the Fintek website
* Fintek F71862FG and F71863FG
Prefix: 'f71862fg'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Available from the Fintek website
* Fintek F8000
Prefix: 'f8000'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Not public
Author: Hans de Goede <hdegoede@redhat.com>
Description
-----------
Fintek F718xxFG/F8000 Super I/O chips include complete hardware monitoring
capabilities. They can monitor up to 9 voltages (3 for the F8000), 4 fans and
3 temperature sensors.
These chips also have fan controlling features, using either DC or PWM, in
three different modes (one manual, two automatic).
The driver assumes that no more than one chip is present, which seems
reasonable.
Monitoring
----------
The Voltage, Fan and Temperature Monitoring uses the standard sysfs
interface as documented in sysfs-interface, without any exceptions.
Fan Control
-----------
Both PWM (pulse-width modulation) and DC fan speed control methods are
supported. The right one to use depends on external circuitry on the
motherboard, so the driver assumes that the BIOS set the method
properly.
There are 2 modes to specify the speed of the fan, PWM duty cycle (or DC
voltage) mode, where 0-100% duty cycle (0-100% of 12V) is specified. And RPM
mode where the actual RPM of the fan (as measured) is controlled and the speed
gets specified as 0-100% of the fan#_full_speed file.
Since both modes work in a 0-100% (mapped to 0-255) scale, there isn't a
whole lot of a difference when modifying fan control settings. The only
important difference is that in RPM mode the 0-100% controls the fan speed
between 0-100% of fan#_full_speed. It is assumed that if the BIOS programs
RPM mode, it will also set fan#_full_speed properly, if it does not then
fan control will not work properly, unless you set a sane fan#_full_speed
value yourself.
Switching between these modes requires re-initializing a whole bunch of
registers, so the mode which the BIOS has set is kept. The mode is
printed when loading the driver.
Three different fan control modes are supported; the mode number is written
to the pwm#_enable file. Note that not all modes are supported on all
chips, and some modes may only be available in RPM / PWM mode on the F8000.
Writing an unsupported mode will result in an invalid parameter error.
* 1: Manual mode
You ask for a specific PWM duty cycle / DC voltage or a specific % of
fan#_full_speed by writing to the pwm# file. This mode is only
available on the F8000 if the fan channel is in RPM mode.
* 2: Normal auto mode
You can define a number of temperature/fan speed trip points, which % the
fan should run at at this temp and which temp a fan should follow using the
standard sysfs interface. The number and type of trip points is chip
depended, see which files are available in sysfs.
Fan/PWM channel 3 of the F8000 is always in this mode!
* 3: Thermostat mode (Only available on the F8000 when in duty cycle mode)
The fan speed is regulated to keep the temp the fan is mapped to between
temp#_auto_point2_temp and temp#_auto_point3_temp.
Both of the automatic modes require that pwm1 corresponds to fan1, pwm2 to
fan2 and pwm3 to fan3.
...@@ -26,6 +26,10 @@ Supported chips: ...@@ -26,6 +26,10 @@ Supported chips:
Datasheet: Publicly available at the ITE website 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.2.zip
http://www.ite.com.tw/product_info/file/pc/IT8718F_V0%203_(for%20C%20version).zip http://www.ite.com.tw/product_info/file/pc/IT8718F_V0%203_(for%20C%20version).zip
* IT8720F
Prefix: 'it8720'
Addresses scanned: from Super I/O config space (8 I/O ports)
Datasheet: Not yet publicly available.
* SiS950 [clone of IT8705F] * SiS950 [clone of IT8705F]
Prefix: 'it87' Prefix: 'it87'
Addresses scanned: from Super I/O config space (8 I/O ports) Addresses scanned: from Super I/O config space (8 I/O ports)
...@@ -71,7 +75,7 @@ Description ...@@ -71,7 +75,7 @@ Description
----------- -----------
This driver implements support for the IT8705F, IT8712F, IT8716F, This driver implements support for the IT8705F, IT8712F, IT8716F,
IT8718F, IT8726F and SiS950 chips. IT8718F, IT8720F, IT8726F and SiS950 chips.
These chips are 'Super I/O chips', supporting floppy disks, infrared ports, These chips are 'Super I/O chips', supporting floppy disks, infrared ports,
joysticks and other miscellaneous stuff. For hardware monitoring, they joysticks and other miscellaneous stuff. For hardware monitoring, they
...@@ -84,19 +88,19 @@ the IT8716F and late IT8712F have 6. They are shared with other functions ...@@ -84,19 +88,19 @@ the IT8716F and late IT8712F have 6. They are shared with other functions
though, so the functionality may not be available on a given system. though, so the functionality may not be available on a given system.
The driver dumbly assume it is there. The driver dumbly assume it is there.
The IT8718F also features VID inputs (up to 8 pins) but the value is The IT8718F and IT8720F also features VID inputs (up to 8 pins) but the value
stored in the Super-I/O configuration space. Due to technical limitations, is stored in the Super-I/O configuration space. Due to technical limitations,
this value can currently only be read once at initialization time, so this value can currently only be read once at initialization time, so
the driver won't notice and report changes in the VID value. The two the driver won't notice and report changes in the VID value. The two
upper VID bits share their pins with voltage inputs (in5 and in6) so you upper VID bits share their pins with voltage inputs (in5 and in6) so you
can't have both on a given board. can't have both on a given board.
The IT8716F, IT8718F and later IT8712F revisions have support for The IT8716F, IT8718F, IT8720F and later IT8712F revisions have support for
2 additional fans. The additional fans are supported by the driver. 2 additional fans. The additional fans are supported by the driver.
The IT8716F and IT8718F, and late IT8712F and IT8705F also have optional The IT8716F, IT8718F and IT8720F, and late IT8712F and IT8705F also have
16-bit tachometer counters for fans 1 to 3. This is better (no more fan optional 16-bit tachometer counters for fans 1 to 3. This is better (no more
clock divider mess) but not compatible with the older chips and fan clock divider mess) but not compatible with the older chips and
revisions. The 16-bit tachometer mode is enabled by the driver when one revisions. The 16-bit tachometer mode is enabled by the driver when one
of the above chips is detected. of the above chips is detected.
...@@ -122,7 +126,7 @@ zero'; this is important for negative voltage measurements. All voltage ...@@ -122,7 +126,7 @@ zero'; this is important for negative voltage measurements. All voltage
inputs can measure voltages between 0 and 4.08 volts, with a resolution of inputs can measure voltages between 0 and 4.08 volts, with a resolution of
0.016 volt. The battery voltage in8 does not have limit registers. 0.016 volt. The battery voltage in8 does not have limit registers.
The VID lines (IT8712F/IT8716F/IT8718F) encode the core voltage value: The VID lines (IT8712F/IT8716F/IT8718F/IT8720F) encode the core voltage value:
the voltage level your processor should work with. This is hardcoded by the voltage level your processor should work with. This is hardcoded by
the mainboard and/or processor itself. It is a value in volts. the mainboard and/or processor itself. It is a value in volts.
......
Kernel driver lm70 Kernel driver lm70
================== ==================
Supported chip: Supported chips:
* National Semiconductor LM70 * National Semiconductor LM70
Datasheet: http://www.national.com/pf/LM/LM70.html Datasheet: http://www.national.com/pf/LM/LM70.html
* Texas Instruments TMP121/TMP123
Information: http://focus.ti.com/docs/prod/folders/print/tmp121.html
Author: Author:
Kaiwan N Billimoria <kaiwan@designergraphix.com> Kaiwan N Billimoria <kaiwan@designergraphix.com>
...@@ -25,6 +27,14 @@ complement digital temperature (sent via the SIO line), is available in the ...@@ -25,6 +27,14 @@ complement digital temperature (sent via the SIO line), is available in the
driver for interpretation. This driver makes use of the kernel's in-core driver for interpretation. This driver makes use of the kernel's in-core
SPI support. SPI support.
As a real (in-tree) example of this "SPI protocol driver" interfacing
with a "SPI master controller driver", see drivers/spi/spi_lm70llp.c
and its associated documentation.
The TMP121/TMP123 are very similar; main differences are 4 wire SPI inter-
face (read only) and 13-bit temperature data (0.0625 degrees celsius reso-
lution).
Thanks to Thanks to
--------- ---------
Jean Delvare <khali@linux-fr.org> for mentoring the hwmon-side driver Jean Delvare <khali@linux-fr.org> for mentoring the hwmon-side driver
......
...@@ -164,7 +164,7 @@ configured individually according to the following options. ...@@ -164,7 +164,7 @@ configured individually according to the following options.
temperature. (PWM value from 0 to 255) temperature. (PWM value from 0 to 255)
* pwm#_auto_pwm_minctl - this flags selects for temp#_auto_temp_off temperature * pwm#_auto_pwm_minctl - this flags selects for temp#_auto_temp_off temperature
the bahaviour of fans. Write 1 to let fans spinning at the behaviour of fans. Write 1 to let fans spinning at
pwm#_auto_pwm_min or write 0 to let them off. pwm#_auto_pwm_min or write 0 to let them off.
NOTE: It has been reported that there is a bug in the LM85 that causes the flag NOTE: It has been reported that there is a bug in the LM85 that causes the flag
......
Kernel driver ltc4245
=====================
Supported chips:
* Linear Technology LTC4245
Prefix: 'ltc4245'
Addresses scanned: 0x20-0x3f
Datasheet:
http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517
Author: Ira W. Snyder <iws@ovro.caltech.edu>
Description
-----------
The LTC4245 controller allows a board to be safely inserted and removed
from a live backplane in multiple supply systems such as CompactPCI and
PCI Express.
Usage Notes
-----------
This driver does not probe for LTC4245 devices, due to the fact that some
of the possible addresses are unfriendly to probing. You will need to use
the "force" parameter to tell the driver where to find the device.
Example: the following will load the driver for an LTC4245 at address 0x23
on I2C bus #1:
$ modprobe ltc4245 force=1,0x23
Sysfs entries
-------------
The LTC4245 has built-in limits for over and under current warnings. This
makes it very likely that the reference circuit will be used.
This driver uses the values in the datasheet to change the register values
into the values specified in the sysfs-interface document. The current readings
rely on the sense resistors listed in Table 2: "Sense Resistor Values".
in1_input 12v input voltage (mV)
in2_input 5v input voltage (mV)
in3_input 3v input voltage (mV)
in4_input Vee (-12v) input voltage (mV)
in1_min_alarm 12v input undervoltage alarm
in2_min_alarm 5v input undervoltage alarm
in3_min_alarm 3v input undervoltage alarm
in4_min_alarm Vee (-12v) input undervoltage alarm
curr1_input 12v current (mA)
curr2_input 5v current (mA)
curr3_input 3v current (mA)
curr4_input Vee (-12v) current (mA)
curr1_max_alarm 12v overcurrent alarm
curr2_max_alarm 5v overcurrent alarm
curr3_max_alarm 3v overcurrent alarm
curr4_max_alarm Vee (-12v) overcurrent alarm
in5_input 12v output voltage (mV)
in6_input 5v output voltage (mV)
in7_input 3v output voltage (mV)
in8_input Vee (-12v) output voltage (mV)
in5_min_alarm 12v output undervoltage alarm
in6_min_alarm 5v output undervoltage alarm
in7_min_alarm 3v output undervoltage alarm
in8_min_alarm Vee (-12v) output undervoltage alarm
in9_input GPIO #1 voltage data
in10_input GPIO #2 voltage data
in11_input GPIO #3 voltage data
power1_input 12v power usage (mW)
power2_input 5v power usage (mW)
power3_input 3v power usage (mW)
power4_input Vee (-12v) power usage (mW)
...@@ -13,10 +13,20 @@ Description ...@@ -13,10 +13,20 @@ Description
This driver provides glue code connecting a National Semiconductor LM70 LLP This driver provides glue code connecting a National Semiconductor LM70 LLP
temperature sensor evaluation board to the kernel's SPI core subsystem. temperature sensor evaluation board to the kernel's SPI core subsystem.
This is a SPI master controller driver. It can be used in conjunction with
(layered under) the LM70 logical driver (a "SPI protocol driver").
In effect, this driver turns the parallel port interface on the eval board In effect, this driver turns the parallel port interface on the eval board
into a SPI bus with a single device, which will be driven by the generic into a SPI bus with a single device, which will be driven by the generic
LM70 driver (drivers/hwmon/lm70.c). LM70 driver (drivers/hwmon/lm70.c).
Hardware Interfacing
--------------------
The schematic for this particular board (the LM70EVAL-LLP) is
available (on page 4) here:
http://www.national.com/appinfo/tempsensors/files/LM70LLPEVALmanual.pdf
The hardware interfacing on the LM70 LLP eval board is as follows: The hardware interfacing on the LM70 LLP eval board is as follows:
Parallel LM70 LLP Parallel LM70 LLP
......
...@@ -284,11 +284,12 @@ config SENSORS_F71805F ...@@ -284,11 +284,12 @@ config SENSORS_F71805F
will be called f71805f. will be called f71805f.
config SENSORS_F71882FG config SENSORS_F71882FG
tristate "Fintek F71882FG and F71883FG" tristate "Fintek F71862FG, F71882FG and F8000"
depends on EXPERIMENTAL depends on EXPERIMENTAL
help help
If you say yes here you get support for hardware monitoring If you say yes here you get support for hardware monitoring
features of the Fintek F71882FG and F71883FG Super-I/O chips. features of the Fintek F71882FG/F71883FG, F71862FG/71863FG
and F8000 Super-I/O chips.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called f71882fg. will be called f71882fg.
...@@ -304,9 +305,13 @@ config SENSORS_F75375S ...@@ -304,9 +305,13 @@ config SENSORS_F75375S
will be called f75375s. will be called f75375s.
config SENSORS_FSCHER config SENSORS_FSCHER
tristate "FSC Hermes" tristate "FSC Hermes (DEPRECATED)"
depends on X86 && I2C depends on X86 && I2C
help help
This driver is DEPRECATED please use the new merged fschmd
("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver
instead.
If you say yes here you get support for Fujitsu Siemens If you say yes here you get support for Fujitsu Siemens
Computers Hermes sensor chips. Computers Hermes sensor chips.
...@@ -314,9 +319,13 @@ config SENSORS_FSCHER ...@@ -314,9 +319,13 @@ config SENSORS_FSCHER
will be called fscher. will be called fscher.
config SENSORS_FSCPOS config SENSORS_FSCPOS
tristate "FSC Poseidon" tristate "FSC Poseidon (DEPRECATED)"
depends on X86 && I2C depends on X86 && I2C
help help
This driver is DEPRECATED please use the new merged fschmd
("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver
instead.
If you say yes here you get support for Fujitsu Siemens If you say yes here you get support for Fujitsu Siemens
Computers Poseidon sensor chips. Computers Poseidon sensor chips.
...@@ -325,14 +334,15 @@ config SENSORS_FSCPOS ...@@ -325,14 +334,15 @@ config SENSORS_FSCPOS
config SENSORS_FSCHMD config SENSORS_FSCHMD
tristate "FSC Poseidon, Scylla, Hermes, Heimdall and Heracles" tristate "FSC Poseidon, Scylla, Hermes, Heimdall and Heracles"
depends on X86 && I2C && EXPERIMENTAL depends on X86 && I2C
help help
If you say yes here you get support for various Fujitsu Siemens If you say yes here you get support for various Fujitsu Siemens
Computers sensor chips. Computers sensor chips, including support for the integrated
watchdog.
This is a new merged driver for FSC sensor chips which is intended This is a merged driver for FSC sensor chips replacing the fscpos,
as a replacment for the fscpos, fscscy and fscher drivers and adds fscscy and fscher drivers and adding support for several other FSC
support for several other FCS sensor chips. sensor chips.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called fschmd. will be called fschmd.
...@@ -399,7 +409,8 @@ config SENSORS_IT87 ...@@ -399,7 +409,8 @@ config SENSORS_IT87
select HWMON_VID select HWMON_VID
help help
If you say yes here you get support for ITE IT8705F, IT8712F, If you say yes here you get support for ITE IT8705F, IT8712F,
IT8716F, IT8718F and IT8726F sensor chips, and the SiS960 clone. IT8716F, IT8718F, IT8720F and IT8726F sensor chips, and the
SiS960 clone.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called it87. will be called it87.
...@@ -417,11 +428,12 @@ config SENSORS_LM63 ...@@ -417,11 +428,12 @@ config SENSORS_LM63
will be called lm63. will be called lm63.
config SENSORS_LM70 config SENSORS_LM70
tristate "National Semiconductor LM70" tristate "National Semiconductor LM70 / Texas Instruments TMP121"
depends on SPI_MASTER && EXPERIMENTAL depends on SPI_MASTER && EXPERIMENTAL
help help
If you say yes here you get support for the National Semiconductor If you say yes here you get support for the National Semiconductor
LM70 digital temperature sensor chip. LM70 and Texas Instruments TMP121/TMP123 digital temperature
sensor chips.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called lm70. will be called lm70.
...@@ -548,6 +560,17 @@ config SENSORS_LM93 ...@@ -548,6 +560,17 @@ config SENSORS_LM93
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called lm93. will be called lm93.
config SENSORS_LTC4245
tristate "Linear Technology LTC4245"
depends on I2C && EXPERIMENTAL
default n
help
If you say yes here you get support for Linear Technology LTC4245
Multiple Supply Hot Swap Controller I2C interface.
This driver can also be built as a module. If so, the module will
be called ltc4245.
config SENSORS_MAX1111 config SENSORS_MAX1111
tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip" tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip"
depends on SPI_MASTER depends on SPI_MASTER
......
...@@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o ...@@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o
obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM92) += lm92.o
obj-$(CONFIG_SENSORS_LM93) += lm93.o obj-$(CONFIG_SENSORS_LM93) += lm93.o
obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o
obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o
......
...@@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END }; ...@@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
/* Insmod parameters */ /* Insmod parameters */
I2C_CLIENT_INSMOD_1(asb100); I2C_CLIENT_INSMOD_1(asb100);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
static unsigned short force_subclients[4];
module_param_array(force_subclients, short, NULL, 0);
MODULE_PARM_DESC(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}"); "{bus, clientaddr, subclientaddr1, subclientaddr2}");
/* Voltage IN registers 0-6 */ /* Voltage IN registers 0-6 */
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include <linux/hwmon-vid.h> #include <linux/hwmon-vid.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
/* ISA device, if found */ /* ISA device, if found */
...@@ -2361,6 +2362,10 @@ static int __init dme1737_isa_device_add(unsigned short addr) ...@@ -2361,6 +2362,10 @@ static int __init dme1737_isa_device_add(unsigned short addr)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
if (!(pdev = platform_device_alloc("dme1737", addr))) { if (!(pdev = platform_device_alloc("dme1737", addr))) {
printk(KERN_ERR "dme1737: Failed to allocate device.\n"); printk(KERN_ERR "dme1737: Failed to allocate device.\n");
err = -ENOMEM; err = -ENOMEM;
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static unsigned short force_id; static unsigned short force_id;
...@@ -1455,6 +1456,10 @@ static int __init f71805f_device_add(unsigned short address, ...@@ -1455,6 +1456,10 @@ static int __init f71805f_device_add(unsigned short address,
} }
res.name = pdev->name; res.name = pdev->name;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit_device_put;
err = platform_device_add_resources(pdev, &res, 1); err = platform_device_add_resources(pdev, &res, 1);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device resource addition failed " printk(KERN_ERR DRVNAME ": Device resource addition failed "
......
/*************************************************************************** /***************************************************************************
* Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> * * Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> *
* Copyright (C) 2007 by Hans de Goede <j.w.r.degoede@hhs.nl> * * Copyright (C) 2007,2008 by Hans de Goede <hdegoede@redhat.com> *
* * * *
* This program is free software; you can redistribute it and/or modify * * 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 * * it under the terms of the GNU General Public License as published by *
...@@ -27,11 +27,12 @@ ...@@ -27,11 +27,12 @@
#include <linux/hwmon-sysfs.h> #include <linux/hwmon-sysfs.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <asm/io.h> #include <linux/io.h>
#include <linux/acpi.h>
#define DRVNAME "f71882fg" #define DRVNAME "f71882fg"
#define SIO_F71882FG_LD_HWM 0x04 /* Hardware monitor logical device*/ #define SIO_F71882FG_LD_HWM 0x04 /* Hardware monitor logical device */
#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ #define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */
#define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */ #define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */
...@@ -43,7 +44,9 @@ ...@@ -43,7 +44,9 @@
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
#define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */
#define SIO_F71862_ID 0x0601 /* Chipset ID */
#define SIO_F71882_ID 0x0541 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */
#define SIO_F8000_ID 0x0581 /* Chipset ID */
#define REGION_LENGTH 8 #define REGION_LENGTH 8
#define ADDR_REG_OFFSET 5 #define ADDR_REG_OFFSET 5
...@@ -51,25 +54,36 @@ ...@@ -51,25 +54,36 @@
#define F71882FG_REG_PECI 0x0A #define F71882FG_REG_PECI 0x0A
#define F71882FG_REG_IN_STATUS 0x12 #define F71882FG_REG_IN_STATUS 0x12 /* f71882fg only */
#define F71882FG_REG_IN_BEEP 0x13 #define F71882FG_REG_IN_BEEP 0x13 /* f71882fg only */
#define F71882FG_REG_IN(nr) (0x20 + (nr)) #define F71882FG_REG_IN(nr) (0x20 + (nr))
#define F71882FG_REG_IN1_HIGH 0x32 #define F71882FG_REG_IN1_HIGH 0x32 /* f71882fg only */
#define F71882FG_REG_FAN(nr) (0xA0 + (16 * (nr))) #define F71882FG_REG_FAN(nr) (0xA0 + (16 * (nr)))
#define F71882FG_REG_FAN_TARGET(nr) (0xA2 + (16 * (nr)))
#define F71882FG_REG_FAN_FULL_SPEED(nr) (0xA4 + (16 * (nr)))
#define F71882FG_REG_FAN_STATUS 0x92 #define F71882FG_REG_FAN_STATUS 0x92
#define F71882FG_REG_FAN_BEEP 0x93 #define F71882FG_REG_FAN_BEEP 0x93
#define F71882FG_REG_TEMP(nr) (0x72 + 2 * (nr)) #define F71882FG_REG_TEMP(nr) (0x70 + 2 * (nr))
#define F71882FG_REG_TEMP_OVT(nr) (0x82 + 2 * (nr)) #define F71882FG_REG_TEMP_OVT(nr) (0x80 + 2 * (nr))
#define F71882FG_REG_TEMP_HIGH(nr) (0x83 + 2 * (nr)) #define F71882FG_REG_TEMP_HIGH(nr) (0x81 + 2 * (nr))
#define F71882FG_REG_TEMP_STATUS 0x62 #define F71882FG_REG_TEMP_STATUS 0x62
#define F71882FG_REG_TEMP_BEEP 0x63 #define F71882FG_REG_TEMP_BEEP 0x63
#define F71882FG_REG_TEMP_HYST1 0x6C #define F71882FG_REG_TEMP_HYST(nr) (0x6C + (nr))
#define F71882FG_REG_TEMP_HYST23 0x6D
#define F71882FG_REG_TEMP_TYPE 0x6B #define F71882FG_REG_TEMP_TYPE 0x6B
#define F71882FG_REG_TEMP_DIODE_OPEN 0x6F #define F71882FG_REG_TEMP_DIODE_OPEN 0x6F
#define F71882FG_REG_PWM(nr) (0xA3 + (16 * (nr)))
#define F71882FG_REG_PWM_TYPE 0x94
#define F71882FG_REG_PWM_ENABLE 0x96
#define F71882FG_REG_FAN_HYST(nr) (0x98 + (nr))
#define F71882FG_REG_POINT_PWM(pwm, point) (0xAA + (point) + (16 * (pwm)))
#define F71882FG_REG_POINT_TEMP(pwm, point) (0xA6 + (point) + (16 * (pwm)))
#define F71882FG_REG_POINT_MAPPING(nr) (0xAF + 16 * (nr))
#define F71882FG_REG_START 0x01 #define F71882FG_REG_START 0x01
#define FAN_MIN_DETECT 366 /* Lowest detectable fanspeed */ #define FAN_MIN_DETECT 366 /* Lowest detectable fanspeed */
...@@ -78,7 +92,15 @@ static unsigned short force_id; ...@@ -78,7 +92,15 @@ static unsigned short force_id;
module_param(force_id, ushort, 0); module_param(force_id, ushort, 0);
MODULE_PARM_DESC(force_id, "Override the detected device ID"); MODULE_PARM_DESC(force_id, "Override the detected device ID");
static struct platform_device *f71882fg_pdev = NULL; enum chips { f71862fg, f71882fg, f8000 };
static const char *f71882fg_names[] = {
"f71862fg",
"f71882fg",
"f8000",
};
static struct platform_device *f71882fg_pdev;
/* Super-I/O Function prototypes */ /* Super-I/O Function prototypes */
static inline int superio_inb(int base, int reg); static inline int superio_inb(int base, int reg);
...@@ -87,8 +109,13 @@ static inline void superio_enter(int base); ...@@ -87,8 +109,13 @@ static inline void superio_enter(int base);
static inline void superio_select(int base, int ld); static inline void superio_select(int base, int ld);
static inline void superio_exit(int base); static inline void superio_exit(int base);
struct f71882fg_sio_data {
enum chips type;
};
struct f71882fg_data { struct f71882fg_data {
unsigned short addr; unsigned short addr;
enum chips type;
struct device *hwmon_dev; struct device *hwmon_dev;
struct mutex update_lock; struct mutex update_lock;
...@@ -102,19 +129,30 @@ struct f71882fg_data { ...@@ -102,19 +129,30 @@ struct f71882fg_data {
u8 in_status; u8 in_status;
u8 in_beep; u8 in_beep;
u16 fan[4]; u16 fan[4];
u16 fan_target[4];
u16 fan_full_speed[4];
u8 fan_status; u8 fan_status;
u8 fan_beep; u8 fan_beep;
u8 temp[3]; /* Note: all models have only 3 temperature channels, but on some
u8 temp_ovt[3]; they are addressed as 0-2 and on others as 1-3, so for coding
u8 temp_high[3]; convenience we reserve space for 4 channels */
u8 temp_hyst[3]; u8 temp[4];
u8 temp_type[3]; u8 temp_ovt[4];
u8 temp_high[4];
u8 temp_hyst[2]; /* 2 hysts stored per reg */
u8 temp_type[4];
u8 temp_status; u8 temp_status;
u8 temp_beep; u8 temp_beep;
u8 temp_diode_open; u8 temp_diode_open;
u8 pwm[4];
u8 pwm_enable;
u8 pwm_auto_point_hyst[2];
u8 pwm_auto_point_mapping[4];
u8 pwm_auto_point_pwm[4][5];
u8 pwm_auto_point_temp[4][4];
}; };
/* Sysfs in*/ /* Sysfs in */
static ssize_t show_in(struct device *dev, struct device_attribute *devattr, static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
char *buf); char *buf);
static ssize_t show_in_max(struct device *dev, struct device_attribute static ssize_t show_in_max(struct device *dev, struct device_attribute
...@@ -130,6 +168,10 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute ...@@ -130,6 +168,10 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute
/* Sysfs Fan */ /* Sysfs Fan */
static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
char *buf); char *buf);
static ssize_t show_fan_full_speed(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_fan_full_speed(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_fan_beep(struct device *dev, struct device_attribute static ssize_t show_fan_beep(struct device *dev, struct device_attribute
*devattr, char *buf); *devattr, char *buf);
static ssize_t store_fan_beep(struct device *dev, struct device_attribute static ssize_t store_fan_beep(struct device *dev, struct device_attribute
...@@ -163,16 +205,41 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute ...@@ -163,16 +205,41 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
*devattr, char *buf); *devattr, char *buf);
static ssize_t show_temp_fault(struct device *dev, struct device_attribute static ssize_t show_temp_fault(struct device *dev, struct device_attribute
*devattr, char *buf); *devattr, char *buf);
/* PWM and Auto point control */
static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
char *buf);
static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count);
static ssize_t show_pwm_enable(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_enable(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_interpolate(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_interpolate(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_auto_point_channel(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_auto_point_channel(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_auto_point_pwm(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_auto_point_pwm(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
static ssize_t show_pwm_auto_point_temp(struct device *dev,
struct device_attribute *devattr, char *buf);
static ssize_t store_pwm_auto_point_temp(struct device *dev,
struct device_attribute *devattr, const char *buf, size_t count);
/* Sysfs misc */ /* Sysfs misc */
static ssize_t show_name(struct device *dev, struct device_attribute *devattr, static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
char *buf); char *buf);
static int __devinit f71882fg_probe(struct platform_device * pdev); static int __devinit f71882fg_probe(struct platform_device * pdev);
static int __devexit f71882fg_remove(struct platform_device *pdev); static int f71882fg_remove(struct platform_device *pdev);
static int __init f71882fg_init(void);
static int __init f71882fg_find(int sioaddr, unsigned short *address);
static int __init f71882fg_device_add(unsigned short address);
static void __exit f71882fg_exit(void);
static struct platform_driver f71882fg_driver = { static struct platform_driver f71882fg_driver = {
.driver = { .driver = {
...@@ -183,86 +250,531 @@ static struct platform_driver f71882fg_driver = { ...@@ -183,86 +250,531 @@ static struct platform_driver f71882fg_driver = {
.remove = __devexit_p(f71882fg_remove), .remove = __devexit_p(f71882fg_remove),
}; };
static struct device_attribute f71882fg_dev_attr[] = static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
{
__ATTR( name, S_IRUGO, show_name, NULL ), /* Temp and in attr common to both the f71862fg and f71882fg */
static struct sensor_device_attribute_2 f718x2fg_in_temp_attr[] = {
SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3),
SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4),
SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5),
SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6),
SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7),
SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8),
SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1),
SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0, 1),
SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 0, 1),
/* Should really be temp1_max_alarm, but older versions did not handle
the max and crit alarms separately and lm_sensors v2 depends on the
presence of temp#_alarm files. The same goes for temp2/3 _alarm. */
SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1),
SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 1),
SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 0, 1),
SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
0, 1),
SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5),
SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 5),
SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 1),
SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 1),
SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 2),
SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0, 2),
SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 0, 2),
/* Should be temp2_max_alarm, see temp1_alarm note */
SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2),
SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 2),
SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 0, 2),
SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
0, 2),
SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6),
SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 6),
SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2),
SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2),
SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 3),
SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0, 3),
SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 0, 3),
/* Should be temp3_max_alarm, see temp1_alarm note */
SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 3),
SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 3),
SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 0, 3),
SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL,
0, 3),
SENSOR_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 7),
SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0, 7),
SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 3),
SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3),
}; };
static struct sensor_device_attribute f71882fg_in_temp_attr[] = /* Temp and in attr found only on the f71882fg */
{ static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] = {
SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max,
SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), 0, 1),
SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1), SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep,
SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1), 0, 1),
SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1), SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1),
SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2),
SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3),
SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4),
SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5),
SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6),
SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7),
SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8),
SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0),
SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0),
SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 0),
SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 0),
SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0),
SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0),
SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 0),
SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0),
SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0),
SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1),
SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 1),
SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 1),
SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 1),
SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1),
SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1),
SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 1),
SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1),
SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1),
SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2),
SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 2),
SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst,
store_temp_max_hyst, 2),
SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 2),
SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2),
SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2),
SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep,
store_temp_beep, 2),
SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2),
SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2)
}; };
static struct sensor_device_attribute f71882fg_fan_attr[] = /* Temp and in attr for the f8000
{ Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max)
SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), is used as hysteresis value to clear alarms
SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep, */
store_fan_beep, 0), static struct sensor_device_attribute_2 f8000_in_temp_attr[] = {
SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0), SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0),
SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1),
SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep, SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2),
store_fan_beep, 1), SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0),
SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_crit,
SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2), store_temp_crit, 0, 0),
SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep, SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max,
store_fan_beep, 2), store_temp_max, 0, 0),
SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2), SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 4),
SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3), SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1),
SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep, SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_crit,
store_fan_beep, 3), store_temp_crit, 0, 1),
SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3) SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0, 1),
SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5),
SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1),
SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2),
SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_crit,
store_temp_crit, 0, 2),
SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max,
store_temp_max, 0, 2),
SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6),
}; };
/* Fan / PWM attr common to all models */
static struct sensor_device_attribute_2 fxxxx_fan_attr[] = {
SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0),
SENSOR_ATTR_2(fan1_full_speed, S_IRUGO|S_IWUSR,
show_fan_full_speed,
store_fan_full_speed, 0, 0),
SENSOR_ATTR_2(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 0),
SENSOR_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 0, 1),
SENSOR_ATTR_2(fan2_full_speed, S_IRUGO|S_IWUSR,
show_fan_full_speed,
store_fan_full_speed, 0, 1),
SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 1),
SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2),
SENSOR_ATTR_2(fan3_full_speed, S_IRUGO|S_IWUSR,
show_fan_full_speed,
store_fan_full_speed, 0, 2),
SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2),
SENSOR_ATTR_2(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 0),
SENSOR_ATTR_2(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
store_pwm_enable, 0, 0),
SENSOR_ATTR_2(pwm1_interpolate, S_IRUGO|S_IWUSR,
show_pwm_interpolate, store_pwm_interpolate, 0, 0),
SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_channel,
store_pwm_auto_point_channel, 0, 0),
SENSOR_ATTR_2(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 1),
SENSOR_ATTR_2(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
store_pwm_enable, 0, 1),
SENSOR_ATTR_2(pwm2_interpolate, S_IRUGO|S_IWUSR,
show_pwm_interpolate, store_pwm_interpolate, 0, 1),
SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_channel,
store_pwm_auto_point_channel, 0, 1),
SENSOR_ATTR_2(pwm3_interpolate, S_IRUGO|S_IWUSR,
show_pwm_interpolate, store_pwm_interpolate, 0, 2),
SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_channel,
store_pwm_auto_point_channel, 0, 2),
};
/* Fan / PWM attr for the f71862fg, less pwms and less zones per pwm than the
f71882fg */
static struct sensor_device_attribute_2 f71862fg_fan_attr[] = {
SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 0),
SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 1),
SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 2),
SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 0),
SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 0),
SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 0),
SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 0),
SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 0),
SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 1),
SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 1),
SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 1),
SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 1),
SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 1),
SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2),
SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
store_pwm_enable, 0, 2),
SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 2),
SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 2),
SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 2),
SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 2),
SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 2),
SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
};
/* Fan / PWM attr for the f71882fg */
static struct sensor_device_attribute_2 f71882fg_fan_attr[] = {
SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 0),
SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 1),
SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 2),
SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3),
SENSOR_ATTR_2(fan4_full_speed, S_IRUGO|S_IWUSR,
show_fan_full_speed,
store_fan_full_speed, 0, 3),
SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep,
store_fan_beep, 0, 3),
SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 3),
SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 0),
SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 0),
SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 0),
SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 0),
SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 0),
SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 0),
SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 0),
SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 0),
SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 0),
SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 0),
SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 0),
SENSOR_ATTR_2(pwm1_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 0),
SENSOR_ATTR_2(pwm1_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 1),
SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 1),
SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 1),
SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 1),
SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 1),
SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 1),
SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 1),
SENSOR_ATTR_2(pwm2_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 1),
SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 1),
SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 1),
SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 1),
SENSOR_ATTR_2(pwm2_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 1),
SENSOR_ATTR_2(pwm2_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2),
SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
store_pwm_enable, 0, 2),
SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 2),
SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 2),
SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 2),
SENSOR_ATTR_2(pwm3_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 2),
SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 2),
SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 2),
SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 2),
SENSOR_ATTR_2(pwm3_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 2),
SENSOR_ATTR_2(pwm3_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 2),
SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 2),
SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 2),
SENSOR_ATTR_2(pwm3_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 2),
SENSOR_ATTR_2(pwm3_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
SENSOR_ATTR_2(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 3),
SENSOR_ATTR_2(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable,
store_pwm_enable, 0, 3),
SENSOR_ATTR_2(pwm4_interpolate, S_IRUGO|S_IWUSR,
show_pwm_interpolate, store_pwm_interpolate, 0, 3),
SENSOR_ATTR_2(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_channel,
store_pwm_auto_point_channel, 0, 3),
SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 3),
SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 3),
SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 3),
SENSOR_ATTR_2(pwm4_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 3),
SENSOR_ATTR_2(pwm4_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 3),
SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 3),
SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 3),
SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 3),
SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 3),
SENSOR_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 3),
SENSOR_ATTR_2(pwm4_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 3),
SENSOR_ATTR_2(pwm4_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 3),
SENSOR_ATTR_2(pwm4_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 3),
};
/* Fan / PWM attr for the f8000, zones mapped to temp instead of to pwm!
Also the register block at offset A0 maps to TEMP1 (so our temp2, as the
F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 */
static struct sensor_device_attribute_2 f8000_fan_attr[] = {
SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3),
SENSOR_ATTR_2(pwm3, S_IRUGO, show_pwm, NULL, 0, 2),
SENSOR_ATTR_2(temp1_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 2),
SENSOR_ATTR_2(temp1_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 2),
SENSOR_ATTR_2(temp1_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 2),
SENSOR_ATTR_2(temp1_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 2),
SENSOR_ATTR_2(temp1_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 2),
SENSOR_ATTR_2(temp1_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 2),
SENSOR_ATTR_2(temp1_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 2),
SENSOR_ATTR_2(temp1_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 2),
SENSOR_ATTR_2(temp1_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 2),
SENSOR_ATTR_2(temp1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 2),
SENSOR_ATTR_2(temp1_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 2),
SENSOR_ATTR_2(temp1_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 2),
SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 2),
SENSOR_ATTR_2(temp2_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 0),
SENSOR_ATTR_2(temp2_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 0),
SENSOR_ATTR_2(temp2_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 0),
SENSOR_ATTR_2(temp2_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 0),
SENSOR_ATTR_2(temp2_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 0),
SENSOR_ATTR_2(temp2_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 0),
SENSOR_ATTR_2(temp2_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 0),
SENSOR_ATTR_2(temp2_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 0),
SENSOR_ATTR_2(temp2_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 0),
SENSOR_ATTR_2(temp2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 0),
SENSOR_ATTR_2(temp2_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 0),
SENSOR_ATTR_2(temp2_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 0),
SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 0),
SENSOR_ATTR_2(temp3_auto_point1_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
0, 1),
SENSOR_ATTR_2(temp3_auto_point2_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
1, 1),
SENSOR_ATTR_2(temp3_auto_point3_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
2, 1),
SENSOR_ATTR_2(temp3_auto_point4_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
3, 1),
SENSOR_ATTR_2(temp3_auto_point5_pwm, S_IRUGO|S_IWUSR,
show_pwm_auto_point_pwm, store_pwm_auto_point_pwm,
4, 1),
SENSOR_ATTR_2(temp3_auto_point1_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
0, 1),
SENSOR_ATTR_2(temp3_auto_point2_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
1, 1),
SENSOR_ATTR_2(temp3_auto_point3_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
2, 1),
SENSOR_ATTR_2(temp3_auto_point4_temp, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp, store_pwm_auto_point_temp,
3, 1),
SENSOR_ATTR_2(temp3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR,
show_pwm_auto_point_temp_hyst,
store_pwm_auto_point_temp_hyst,
0, 1),
SENSOR_ATTR_2(temp3_auto_point2_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 1, 1),
SENSOR_ATTR_2(temp3_auto_point3_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 2, 1),
SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO,
show_pwm_auto_point_temp_hyst, NULL, 3, 1),
};
/* Super I/O functions */ /* Super I/O functions */
static inline int superio_inb(int base, int reg) static inline int superio_inb(int base, int reg)
...@@ -299,11 +811,16 @@ static inline void superio_exit(int base) ...@@ -299,11 +811,16 @@ static inline void superio_exit(int base)
outb(SIO_LOCK_KEY, base); outb(SIO_LOCK_KEY, base);
} }
static inline u16 fan_from_reg(u16 reg) static inline int fan_from_reg(u16 reg)
{ {
return reg ? (1500000 / reg) : 0; return reg ? (1500000 / reg) : 0;
} }
static inline u16 fan_to_reg(int fan)
{
return fan ? (1500000 / fan) : 0;
}
static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg) static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg)
{ {
u8 val; u8 val;
...@@ -332,52 +849,111 @@ static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val) ...@@ -332,52 +849,111 @@ static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val)
outb(val, data->addr + DATA_REG_OFFSET); outb(val, data->addr + DATA_REG_OFFSET);
} }
static struct f71882fg_data *f71882fg_update_device(struct device * dev) static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val)
{
outb(reg++, data->addr + ADDR_REG_OFFSET);
outb(val >> 8, data->addr + DATA_REG_OFFSET);
outb(reg, data->addr + ADDR_REG_OFFSET);
outb(val & 255, data->addr + DATA_REG_OFFSET);
}
static struct f71882fg_data *f71882fg_update_device(struct device *dev)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr, reg, reg2; int nr, reg = 0, reg2;
int nr_fans = (data->type == f71882fg) ? 4 : 3;
int nr_ins = (data->type == f8000) ? 3 : 9;
int temp_start = (data->type == f8000) ? 0 : 1;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
/* Update once every 60 seconds */ /* Update once every 60 seconds */
if ( time_after(jiffies, data->last_limits + 60 * HZ ) || if ( time_after(jiffies, data->last_limits + 60 * HZ ) ||
!data->valid) { !data->valid) {
data->in1_max = f71882fg_read8(data, F71882FG_REG_IN1_HIGH); if (data->type == f71882fg) {
data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP); data->in1_max =
f71882fg_read8(data, F71882FG_REG_IN1_HIGH);
data->in_beep =
f71882fg_read8(data, F71882FG_REG_IN_BEEP);
}
/* Get High & boundary temps*/ /* Get High & boundary temps*/
for (nr = 0; nr < 3; nr++) { for (nr = temp_start; nr < 3 + temp_start; nr++) {
data->temp_ovt[nr] = f71882fg_read8(data, data->temp_ovt[nr] = f71882fg_read8(data,
F71882FG_REG_TEMP_OVT(nr)); F71882FG_REG_TEMP_OVT(nr));
data->temp_high[nr] = f71882fg_read8(data, data->temp_high[nr] = f71882fg_read8(data,
F71882FG_REG_TEMP_HIGH(nr)); F71882FG_REG_TEMP_HIGH(nr));
} }
/* Have to hardcode hyst*/ if (data->type != f8000) {
data->fan_beep = f71882fg_read8(data,
F71882FG_REG_FAN_BEEP);
data->temp_beep = f71882fg_read8(data,
F71882FG_REG_TEMP_BEEP);
data->temp_hyst[0] = f71882fg_read8(data, data->temp_hyst[0] = f71882fg_read8(data,
F71882FG_REG_TEMP_HYST1) >> 4; F71882FG_REG_TEMP_HYST(0));
/* Hyst temps 2 & 3 stored in same register */ data->temp_hyst[1] = f71882fg_read8(data,
reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST23); F71882FG_REG_TEMP_HYST(1));
data->temp_hyst[1] = reg & 0x0F;
data->temp_hyst[2] = reg >> 4;
/* Have to hardcode type, because temp1 is special */ /* Have to hardcode type, because temp1 is special */
reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE);
data->temp_type[2] = (reg & 0x04) ? 2 : 4;
data->temp_type[3] = (reg & 0x08) ? 2 : 4;
}
reg2 = f71882fg_read8(data, F71882FG_REG_PECI); reg2 = f71882fg_read8(data, F71882FG_REG_PECI);
if ((reg2 & 0x03) == 0x01) if ((reg2 & 0x03) == 0x01)
data->temp_type[0] = 6 /* PECI */; data->temp_type[1] = 6 /* PECI */;
else if ((reg2 & 0x03) == 0x02) else if ((reg2 & 0x03) == 0x02)
data->temp_type[0] = 5 /* AMDSI */; data->temp_type[1] = 5 /* AMDSI */;
else if (data->type != f8000)
data->temp_type[1] = (reg & 0x02) ? 2 : 4;
else else
data->temp_type[0] = (reg & 0x02) ? 2 : 4; data->temp_type[1] = 2; /* F8000 only supports BJT */
data->temp_type[1] = (reg & 0x04) ? 2 : 4; data->pwm_enable = f71882fg_read8(data,
data->temp_type[2] = (reg & 0x08) ? 2 : 4; F71882FG_REG_PWM_ENABLE);
data->pwm_auto_point_hyst[0] =
data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); f71882fg_read8(data, F71882FG_REG_FAN_HYST(0));
data->pwm_auto_point_hyst[1] =
data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP); f71882fg_read8(data, F71882FG_REG_FAN_HYST(1));
for (nr = 0; nr < nr_fans; nr++) {
data->pwm_auto_point_mapping[nr] =
f71882fg_read8(data,
F71882FG_REG_POINT_MAPPING(nr));
if (data->type != f71862fg) {
int point;
for (point = 0; point < 5; point++) {
data->pwm_auto_point_pwm[nr][point] =
f71882fg_read8(data,
F71882FG_REG_POINT_PWM
(nr, point));
}
for (point = 0; point < 4; point++) {
data->pwm_auto_point_temp[nr][point] =
f71882fg_read8(data,
F71882FG_REG_POINT_TEMP
(nr, point));
}
} else {
data->pwm_auto_point_pwm[nr][1] =
f71882fg_read8(data,
F71882FG_REG_POINT_PWM
(nr, 1));
data->pwm_auto_point_pwm[nr][4] =
f71882fg_read8(data,
F71882FG_REG_POINT_PWM
(nr, 4));
data->pwm_auto_point_temp[nr][0] =
f71882fg_read8(data,
F71882FG_REG_POINT_TEMP
(nr, 0));
data->pwm_auto_point_temp[nr][3] =
f71882fg_read8(data,
F71882FG_REG_POINT_TEMP
(nr, 3));
}
}
data->last_limits = jiffies; data->last_limits = jiffies;
} }
...@@ -387,19 +963,32 @@ static struct f71882fg_data *f71882fg_update_device(struct device * dev) ...@@ -387,19 +963,32 @@ static struct f71882fg_data *f71882fg_update_device(struct device * dev)
F71882FG_REG_TEMP_STATUS); F71882FG_REG_TEMP_STATUS);
data->temp_diode_open = f71882fg_read8(data, data->temp_diode_open = f71882fg_read8(data,
F71882FG_REG_TEMP_DIODE_OPEN); F71882FG_REG_TEMP_DIODE_OPEN);
for (nr = 0; nr < 3; nr++) for (nr = temp_start; nr < 3 + temp_start; nr++)
data->temp[nr] = f71882fg_read8(data, data->temp[nr] = f71882fg_read8(data,
F71882FG_REG_TEMP(nr)); F71882FG_REG_TEMP(nr));
data->fan_status = f71882fg_read8(data, data->fan_status = f71882fg_read8(data,
F71882FG_REG_FAN_STATUS); F71882FG_REG_FAN_STATUS);
for (nr = 0; nr < 4; nr++) for (nr = 0; nr < nr_fans; nr++) {
data->fan[nr] = f71882fg_read16(data, data->fan[nr] = f71882fg_read16(data,
F71882FG_REG_FAN(nr)); F71882FG_REG_FAN(nr));
data->fan_target[nr] =
f71882fg_read16(data, F71882FG_REG_FAN_TARGET(nr));
data->fan_full_speed[nr] =
f71882fg_read16(data,
F71882FG_REG_FAN_FULL_SPEED(nr));
data->pwm[nr] =
f71882fg_read8(data, F71882FG_REG_PWM(nr));
}
/* The f8000 can monitor 1 more fan, but has no pwm for it */
if (data->type == f8000)
data->fan[3] = f71882fg_read16(data,
F71882FG_REG_FAN(3));
if (data->type == f71882fg)
data->in_status = f71882fg_read8(data, data->in_status = f71882fg_read8(data,
F71882FG_REG_IN_STATUS); F71882FG_REG_IN_STATUS);
for (nr = 0; nr < 9; nr++) for (nr = 0; nr < nr_ins; nr++)
data->in[nr] = f71882fg_read8(data, data->in[nr] = f71882fg_read8(data,
F71882FG_REG_IN(nr)); F71882FG_REG_IN(nr));
...@@ -417,7 +1006,7 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, ...@@ -417,7 +1006,7 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int speed = fan_from_reg(data->fan[nr]); int speed = fan_from_reg(data->fan[nr]);
if (speed == FAN_MIN_DETECT) if (speed == FAN_MIN_DETECT)
...@@ -426,11 +1015,39 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, ...@@ -426,11 +1015,39 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr,
return sprintf(buf, "%d\n", speed); return sprintf(buf, "%d\n", speed);
} }
static ssize_t show_fan_full_speed(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
int speed = fan_from_reg(data->fan_full_speed[nr]);
return sprintf(buf, "%d\n", speed);
}
static ssize_t store_fan_full_speed(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
long val = simple_strtol(buf, NULL, 10);
val = SENSORS_LIMIT(val, 23, 1500000);
val = fan_to_reg(val);
mutex_lock(&data->update_lock);
f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), val);
data->fan_full_speed[nr] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_fan_beep(struct device *dev, struct device_attribute static ssize_t show_fan_beep(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->fan_beep & (1 << nr)) if (data->fan_beep & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
...@@ -442,10 +1059,11 @@ static ssize_t store_fan_beep(struct device *dev, struct device_attribute ...@@ -442,10 +1059,11 @@ static ssize_t store_fan_beep(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10); unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP);
if (val) if (val)
data->fan_beep |= 1 << nr; data->fan_beep |= 1 << nr;
else else
...@@ -461,7 +1079,7 @@ static ssize_t show_fan_alarm(struct device *dev, struct device_attribute ...@@ -461,7 +1079,7 @@ static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->fan_status & (1 << nr)) if (data->fan_status & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
...@@ -473,7 +1091,7 @@ static ssize_t show_in(struct device *dev, struct device_attribute *devattr, ...@@ -473,7 +1091,7 @@ static ssize_t show_in(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
return sprintf(buf, "%d\n", data->in[nr] * 8); return sprintf(buf, "%d\n", data->in[nr] * 8);
} }
...@@ -490,10 +1108,8 @@ static ssize_t store_in_max(struct device *dev, struct device_attribute ...@@ -490,10 +1108,8 @@ static ssize_t store_in_max(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int val = simple_strtoul(buf, NULL, 10) / 8; long val = simple_strtol(buf, NULL, 10) / 8;
val = SENSORS_LIMIT(val, 0, 255);
if (val > 255)
val = 255;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
f71882fg_write8(data, F71882FG_REG_IN1_HIGH, val); f71882fg_write8(data, F71882FG_REG_IN1_HIGH, val);
...@@ -507,7 +1123,7 @@ static ssize_t show_in_beep(struct device *dev, struct device_attribute ...@@ -507,7 +1123,7 @@ static ssize_t show_in_beep(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->in_beep & (1 << nr)) if (data->in_beep & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
...@@ -519,10 +1135,11 @@ static ssize_t store_in_beep(struct device *dev, struct device_attribute ...@@ -519,10 +1135,11 @@ static ssize_t store_in_beep(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10); unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP);
if (val) if (val)
data->in_beep |= 1 << nr; data->in_beep |= 1 << nr;
else else
...@@ -538,7 +1155,7 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute ...@@ -538,7 +1155,7 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->in_status & (1 << nr)) if (data->in_status & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
...@@ -550,7 +1167,7 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, ...@@ -550,7 +1167,7 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
return sprintf(buf, "%d\n", data->temp[nr] * 1000); return sprintf(buf, "%d\n", data->temp[nr] * 1000);
} }
...@@ -559,7 +1176,7 @@ static ssize_t show_temp_max(struct device *dev, struct device_attribute ...@@ -559,7 +1176,7 @@ static ssize_t show_temp_max(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
return sprintf(buf, "%d\n", data->temp_high[nr] * 1000); return sprintf(buf, "%d\n", data->temp_high[nr] * 1000);
} }
...@@ -568,11 +1185,9 @@ static ssize_t store_temp_max(struct device *dev, struct device_attribute ...@@ -568,11 +1185,9 @@ static ssize_t store_temp_max(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10) / 1000; long val = simple_strtol(buf, NULL, 10) / 1000;
val = SENSORS_LIMIT(val, 0, 255);
if (val > 255)
val = 255;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
f71882fg_write8(data, F71882FG_REG_TEMP_HIGH(nr), val); f71882fg_write8(data, F71882FG_REG_TEMP_HIGH(nr), val);
...@@ -586,48 +1201,46 @@ static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute ...@@ -586,48 +1201,46 @@ static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int temp_max_hyst;
mutex_lock(&data->update_lock);
if (nr & 1)
temp_max_hyst = data->temp_hyst[nr / 2] >> 4;
else
temp_max_hyst = data->temp_hyst[nr / 2] & 0x0f;
temp_max_hyst = (data->temp_high[nr] - temp_max_hyst) * 1000;
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", return sprintf(buf, "%d\n", temp_max_hyst);
(data->temp_high[nr] - data->temp_hyst[nr]) * 1000);
} }
static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10) / 1000; long val = simple_strtol(buf, NULL, 10) / 1000;
ssize_t ret = count; ssize_t ret = count;
u8 reg;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
/* convert abs to relative and check */ /* convert abs to relative and check */
data->temp_high[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_HIGH(nr));
val = SENSORS_LIMIT(val, data->temp_high[nr] - 15,
data->temp_high[nr]);
val = data->temp_high[nr] - val; val = data->temp_high[nr] - val;
if (val < 0 || val > 15) {
ret = -EINVAL;
goto store_temp_max_hyst_exit;
}
data->temp_hyst[nr] = val;
/* convert value to register contents */ /* convert value to register contents */
switch (nr) { reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST(nr / 2));
case 0: if (nr & 1)
val = val << 4; reg = (reg & 0x0f) | (val << 4);
break; else
case 1: reg = (reg & 0xf0) | val;
val = val | (data->temp_hyst[2] << 4); f71882fg_write8(data, F71882FG_REG_TEMP_HYST(nr / 2), reg);
break; data->temp_hyst[nr / 2] = reg;
case 2:
val = data->temp_hyst[1] | (val << 4);
break;
}
f71882fg_write8(data, nr ? F71882FG_REG_TEMP_HYST23 :
F71882FG_REG_TEMP_HYST1, val);
store_temp_max_hyst_exit:
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
return ret; return ret;
} }
...@@ -636,7 +1249,7 @@ static ssize_t show_temp_crit(struct device *dev, struct device_attribute ...@@ -636,7 +1249,7 @@ static ssize_t show_temp_crit(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000); return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000);
} }
...@@ -645,11 +1258,9 @@ static ssize_t store_temp_crit(struct device *dev, struct device_attribute ...@@ -645,11 +1258,9 @@ static ssize_t store_temp_crit(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10) / 1000; long val = simple_strtol(buf, NULL, 10) / 1000;
val = SENSORS_LIMIT(val, 0, 255);
if (val > 255)
val = 255;
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
f71882fg_write8(data, F71882FG_REG_TEMP_OVT(nr), val); f71882fg_write8(data, F71882FG_REG_TEMP_OVT(nr), val);
...@@ -663,17 +1274,25 @@ static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute ...@@ -663,17 +1274,25 @@ static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int temp_crit_hyst;
return sprintf(buf, "%d\n", mutex_lock(&data->update_lock);
(data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000); if (nr & 1)
temp_crit_hyst = data->temp_hyst[nr / 2] >> 4;
else
temp_crit_hyst = data->temp_hyst[nr / 2] & 0x0f;
temp_crit_hyst = (data->temp_ovt[nr] - temp_crit_hyst) * 1000;
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", temp_crit_hyst);
} }
static ssize_t show_temp_type(struct device *dev, struct device_attribute static ssize_t show_temp_type(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
return sprintf(buf, "%d\n", data->temp_type[nr]); return sprintf(buf, "%d\n", data->temp_type[nr]);
} }
...@@ -682,9 +1301,9 @@ static ssize_t show_temp_beep(struct device *dev, struct device_attribute ...@@ -682,9 +1301,9 @@ static ssize_t show_temp_beep(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->temp_beep & (1 << (nr + 1))) if (data->temp_beep & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
...@@ -694,14 +1313,15 @@ static ssize_t store_temp_beep(struct device *dev, struct device_attribute ...@@ -694,14 +1313,15 @@ static ssize_t store_temp_beep(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71882fg_data *data = dev_get_drvdata(dev); struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
int val = simple_strtoul(buf, NULL, 10); unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP);
if (val) if (val)
data->temp_beep |= 1 << (nr + 1); data->temp_beep |= 1 << nr;
else else
data->temp_beep &= ~(1 << (nr + 1)); data->temp_beep &= ~(1 << nr);
f71882fg_write8(data, F71882FG_REG_TEMP_BEEP, data->temp_beep); f71882fg_write8(data, F71882FG_REG_TEMP_BEEP, data->temp_beep);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
...@@ -713,9 +1333,9 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute ...@@ -713,9 +1333,9 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->temp_status & (1 << (nr + 1))) if (data->temp_status & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
...@@ -725,113 +1345,528 @@ static ssize_t show_temp_fault(struct device *dev, struct device_attribute ...@@ -725,113 +1345,528 @@ static ssize_t show_temp_fault(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71882fg_data *data = f71882fg_update_device(dev); struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr(devattr)->index; int nr = to_sensor_dev_attr_2(devattr)->index;
if (data->temp_diode_open & (1 << (nr + 1))) if (data->temp_diode_open & (1 << nr))
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
} }
static ssize_t show_pwm(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct f71882fg_data *data = f71882fg_update_device(dev);
int val, nr = to_sensor_dev_attr_2(devattr)->index;
mutex_lock(&data->update_lock);
if (data->pwm_enable & (1 << (2 * nr)))
/* PWM mode */
val = data->pwm[nr];
else {
/* RPM mode */
val = 255 * fan_from_reg(data->fan_target[nr])
/ fan_from_reg(data->fan_full_speed[nr]);
}
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", val);
}
static ssize_t store_pwm(struct device *dev,
struct device_attribute *devattr, const char *buf,
size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
long val = simple_strtol(buf, NULL, 10);
val = SENSORS_LIMIT(val, 0, 255);
mutex_lock(&data->update_lock);
data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
if ((data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 3) != 2) ||
(data->type != f8000 && !((data->pwm_enable >> 2 * nr) & 2))) {
count = -EROFS;
goto leave;
}
if (data->pwm_enable & (1 << (2 * nr))) {
/* PWM mode */
f71882fg_write8(data, F71882FG_REG_PWM(nr), val);
data->pwm[nr] = val;
} else {
/* RPM mode */
int target, full_speed;
full_speed = f71882fg_read16(data,
F71882FG_REG_FAN_FULL_SPEED(nr));
target = fan_to_reg(val * fan_from_reg(full_speed) / 255);
f71882fg_write16(data, F71882FG_REG_FAN_TARGET(nr), target);
data->fan_target[nr] = target;
data->fan_full_speed[nr] = full_speed;
}
leave:
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_enable(struct device *dev,
struct device_attribute *devattr, char *buf)
{
int result = 0;
struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
switch ((data->pwm_enable >> 2 * nr) & 3) {
case 0:
case 1:
result = 2; /* Normal auto mode */
break;
case 2:
result = 1; /* Manual mode */
break;
case 3:
if (data->type == f8000)
result = 3; /* Thermostat mode */
else
result = 1; /* Manual mode */
break;
}
return sprintf(buf, "%d\n", result);
}
static ssize_t store_pwm_enable(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
/* Special case for F8000 auto PWM mode / Thermostat mode */
if (data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 1)) {
switch (val) {
case 2:
data->pwm_enable &= ~(2 << (2 * nr));
break; /* Normal auto mode */
case 3:
data->pwm_enable |= 2 << (2 * nr);
break; /* Thermostat mode */
default:
count = -EINVAL;
goto leave;
}
} else {
switch (val) {
case 1:
data->pwm_enable |= 2 << (2 * nr);
break; /* Manual */
case 2:
data->pwm_enable &= ~(2 << (2 * nr));
break; /* Normal auto mode */
default:
count = -EINVAL;
goto leave;
}
}
f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable);
leave:
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_auto_point_pwm(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int result;
struct f71882fg_data *data = f71882fg_update_device(dev);
int pwm = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
mutex_lock(&data->update_lock);
if (data->pwm_enable & (1 << (2 * pwm))) {
/* PWM mode */
result = data->pwm_auto_point_pwm[pwm][point];
} else {
/* RPM mode */
result = 32 * 255 / (32 + data->pwm_auto_point_pwm[pwm][point]);
}
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", result);
}
static ssize_t store_pwm_auto_point_pwm(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int pwm = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
long val = simple_strtol(buf, NULL, 10);
val = SENSORS_LIMIT(val, 0, 255);
mutex_lock(&data->update_lock);
data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
if (data->pwm_enable & (1 << (2 * pwm))) {
/* PWM mode */
} else {
/* RPM mode */
if (val < 29) /* Prevent negative numbers */
val = 255;
else
val = (255 - val) * 32 / val;
}
f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm, point), val);
data->pwm_auto_point_pwm[pwm][point] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int result = 0;
struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
mutex_lock(&data->update_lock);
if (nr & 1)
result = data->pwm_auto_point_hyst[nr / 2] >> 4;
else
result = data->pwm_auto_point_hyst[nr / 2] & 0x0f;
result = 1000 * (data->pwm_auto_point_temp[nr][point] - result);
mutex_unlock(&data->update_lock);
return sprintf(buf, "%d\n", result);
}
static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
long val = simple_strtol(buf, NULL, 10) / 1000;
u8 reg;
mutex_lock(&data->update_lock);
data->pwm_auto_point_temp[nr][point] =
f71882fg_read8(data, F71882FG_REG_POINT_TEMP(nr, point));
val = SENSORS_LIMIT(val, data->pwm_auto_point_temp[nr][point] - 15,
data->pwm_auto_point_temp[nr][point]);
val = data->pwm_auto_point_temp[nr][point] - val;
reg = f71882fg_read8(data, F71882FG_REG_FAN_HYST(nr / 2));
if (nr & 1)
reg = (reg & 0x0f) | (val << 4);
else
reg = (reg & 0xf0) | val;
f71882fg_write8(data, F71882FG_REG_FAN_HYST(nr / 2), reg);
data->pwm_auto_point_hyst[nr / 2] = reg;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_interpolate(struct device *dev,
struct device_attribute *devattr, char *buf)
{
int result;
struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
result = (data->pwm_auto_point_mapping[nr] >> 4) & 1;
return sprintf(buf, "%d\n", result);
}
static ssize_t store_pwm_interpolate(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->pwm_auto_point_mapping[nr] =
f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr));
if (val)
val = data->pwm_auto_point_mapping[nr] | (1 << 4);
else
val = data->pwm_auto_point_mapping[nr] & (~(1 << 4));
f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val);
data->pwm_auto_point_mapping[nr] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_auto_point_channel(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int result;
struct f71882fg_data *data = f71882fg_update_device(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
int temp_start = (data->type == f8000) ? 0 : 1;
result = 1 << ((data->pwm_auto_point_mapping[nr] & 3) - temp_start);
return sprintf(buf, "%d\n", result);
}
static ssize_t store_pwm_auto_point_channel(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int nr = to_sensor_dev_attr_2(devattr)->index;
int temp_start = (data->type == f8000) ? 0 : 1;
long val = simple_strtol(buf, NULL, 10);
switch (val) {
case 1:
val = 0;
break;
case 2:
val = 1;
break;
case 4:
val = 2;
break;
default:
return -EINVAL;
}
val += temp_start;
mutex_lock(&data->update_lock);
data->pwm_auto_point_mapping[nr] =
f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr));
val = (data->pwm_auto_point_mapping[nr] & 0xfc) | val;
f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val);
data->pwm_auto_point_mapping[nr] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm_auto_point_temp(struct device *dev,
struct device_attribute *devattr,
char *buf)
{
int result;
struct f71882fg_data *data = f71882fg_update_device(dev);
int pwm = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
result = data->pwm_auto_point_temp[pwm][point];
return sprintf(buf, "%d\n", 1000 * result);
}
static ssize_t store_pwm_auto_point_temp(struct device *dev,
struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71882fg_data *data = dev_get_drvdata(dev);
int pwm = to_sensor_dev_attr_2(devattr)->index;
int point = to_sensor_dev_attr_2(devattr)->nr;
long val = simple_strtol(buf, NULL, 10) / 1000;
val = SENSORS_LIMIT(val, 0, 255);
mutex_lock(&data->update_lock);
f71882fg_write8(data, F71882FG_REG_POINT_TEMP(pwm, point), val);
data->pwm_auto_point_temp[pwm][point] = val;
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_name(struct device *dev, struct device_attribute *devattr, static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
return sprintf(buf, DRVNAME "\n"); struct f71882fg_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", f71882fg_names[data->type]);
} }
static int __devinit f71882fg_create_sysfs_files(struct platform_device *pdev,
struct sensor_device_attribute_2 *attr, int count)
{
int err, i;
for (i = 0; i < count; i++) {
err = device_create_file(&pdev->dev, &attr[i].dev_attr);
if (err)
return err;
}
return 0;
}
static int __devinit f71882fg_probe(struct platform_device * pdev) static int __devinit f71882fg_probe(struct platform_device *pdev)
{ {
struct f71882fg_data *data; struct f71882fg_data *data;
int err, i; struct f71882fg_sio_data *sio_data = pdev->dev.platform_data;
int err, i, nr_fans = (sio_data->type == f71882fg) ? 4 : 3;
u8 start_reg; u8 start_reg;
if (!(data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL))) data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL);
if (!data)
return -ENOMEM; return -ENOMEM;
data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start;
data->type = sio_data->type;
mutex_init(&data->update_lock); mutex_init(&data->update_lock);
platform_set_drvdata(pdev, data); platform_set_drvdata(pdev, data);
start_reg = f71882fg_read8(data, F71882FG_REG_START);
if (start_reg & 0x04) {
dev_warn(&pdev->dev, "Hardware monitor is powered down\n");
err = -ENODEV;
goto exit_free;
}
if (!(start_reg & 0x03)) {
dev_warn(&pdev->dev, "Hardware monitoring not activated\n");
err = -ENODEV;
goto exit_free;
}
data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE);
/* If it is a 71862 and the fan / pwm part is enabled sanity check
the pwm settings */
if (data->type == f71862fg && (start_reg & 0x02)) {
if ((data->pwm_enable & 0x15) != 0x15) {
dev_err(&pdev->dev,
"Invalid (reserved) pwm settings: 0x%02x\n",
(unsigned int)data->pwm_enable);
err = -ENODEV;
goto exit_free;
}
}
/* Register sysfs interface files */ /* Register sysfs interface files */
for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) { err = device_create_file(&pdev->dev, &dev_attr_name);
err = device_create_file(&pdev->dev, &f71882fg_dev_attr[i]);
if (err) if (err)
goto exit_unregister_sysfs; goto exit_unregister_sysfs;
}
start_reg = f71882fg_read8(data, F71882FG_REG_START);
if (start_reg & 0x01) { if (start_reg & 0x01) {
for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) { switch (data->type) {
err = device_create_file(&pdev->dev, case f71882fg:
&f71882fg_in_temp_attr[i].dev_attr); err = f71882fg_create_sysfs_files(pdev,
f71882fg_in_temp_attr,
ARRAY_SIZE(f71882fg_in_temp_attr));
if (err) if (err)
goto exit_unregister_sysfs; goto exit_unregister_sysfs;
/* fall through! */
case f71862fg:
err = f71882fg_create_sysfs_files(pdev,
f718x2fg_in_temp_attr,
ARRAY_SIZE(f718x2fg_in_temp_attr));
break;
case f8000:
err = f71882fg_create_sysfs_files(pdev,
f8000_in_temp_attr,
ARRAY_SIZE(f8000_in_temp_attr));
break;
} }
if (err)
goto exit_unregister_sysfs;
} }
if (start_reg & 0x02) { if (start_reg & 0x02) {
for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) { err = f71882fg_create_sysfs_files(pdev, fxxxx_fan_attr,
err = device_create_file(&pdev->dev, ARRAY_SIZE(fxxxx_fan_attr));
&f71882fg_fan_attr[i].dev_attr);
if (err) if (err)
goto exit_unregister_sysfs; goto exit_unregister_sysfs;
switch (data->type) {
case f71862fg:
err = f71882fg_create_sysfs_files(pdev,
f71862fg_fan_attr,
ARRAY_SIZE(f71862fg_fan_attr));
break;
case f71882fg:
err = f71882fg_create_sysfs_files(pdev,
f71882fg_fan_attr,
ARRAY_SIZE(f71882fg_fan_attr));
break;
case f8000:
err = f71882fg_create_sysfs_files(pdev,
f8000_fan_attr,
ARRAY_SIZE(f8000_fan_attr));
break;
} }
if (err)
goto exit_unregister_sysfs;
for (i = 0; i < nr_fans; i++)
dev_info(&pdev->dev, "Fan: %d is in %s mode\n", i + 1,
(data->pwm_enable & (1 << 2 * i)) ?
"duty-cycle" : "RPM");
} }
data->hwmon_dev = hwmon_device_register(&pdev->dev); data->hwmon_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->hwmon_dev)) { if (IS_ERR(data->hwmon_dev)) {
err = PTR_ERR(data->hwmon_dev); err = PTR_ERR(data->hwmon_dev);
data->hwmon_dev = NULL;
goto exit_unregister_sysfs; goto exit_unregister_sysfs;
} }
return 0; return 0;
exit_unregister_sysfs: exit_unregister_sysfs:
for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) f71882fg_remove(pdev); /* Will unregister the sysfs files for us */
device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]); return err; /* f71882fg_remove() also frees our data */
exit_free:
for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
device_remove_file(&pdev->dev,
&f71882fg_in_temp_attr[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
kfree(data); kfree(data);
return err; return err;
} }
static int __devexit f71882fg_remove(struct platform_device *pdev) static int f71882fg_remove(struct platform_device *pdev)
{ {
int i; int i;
struct f71882fg_data *data = platform_get_drvdata(pdev); struct f71882fg_data *data = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
if (data->hwmon_dev)
hwmon_device_unregister(data->hwmon_dev); hwmon_device_unregister(data->hwmon_dev);
for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) /* Note we are not looping over all attr arrays we have as the ones
device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]); below are supersets of the ones skipped. */
device_remove_file(&pdev->dev, &dev_attr_name);
for (i = 0; i < ARRAY_SIZE(f718x2fg_in_temp_attr); i++)
device_remove_file(&pdev->dev,
&f718x2fg_in_temp_attr[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++)
device_remove_file(&pdev->dev, device_remove_file(&pdev->dev,
&f71882fg_in_temp_attr[i].dev_attr); &f71882fg_in_temp_attr[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(fxxxx_fan_attr); i++)
device_remove_file(&pdev->dev, &fxxxx_fan_attr[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++)
device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr); device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(f8000_fan_attr); i++)
device_remove_file(&pdev->dev, &f8000_fan_attr[i].dev_attr);
kfree(data); kfree(data);
return 0; return 0;
} }
static int __init f71882fg_find(int sioaddr, unsigned short *address) static int __init f71882fg_find(int sioaddr, unsigned short *address,
struct f71882fg_sio_data *sio_data)
{ {
int err = -ENODEV; int err = -ENODEV;
u16 devid; u16 devid;
u8 start_reg;
struct f71882fg_data data;
superio_enter(sioaddr); superio_enter(sioaddr);
...@@ -842,7 +1877,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address) ...@@ -842,7 +1877,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address)
} }
devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID);
if (devid != SIO_F71882_ID) { switch (devid) {
case SIO_F71862_ID:
sio_data->type = f71862fg;
break;
case SIO_F71882_ID:
sio_data->type = f71882fg;
break;
case SIO_F8000_ID:
sio_data->type = f8000;
break;
default:
printk(KERN_INFO DRVNAME ": Unsupported Fintek device\n"); printk(KERN_INFO DRVNAME ": Unsupported Fintek device\n");
goto exit; goto exit;
} }
...@@ -861,24 +1906,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address) ...@@ -861,24 +1906,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address)
} }
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */ *address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
data.addr = *address;
start_reg = f71882fg_read8(&data, F71882FG_REG_START);
if (!(start_reg & 0x03)) {
printk(KERN_WARNING DRVNAME
": Hardware monitoring not activated\n");
goto exit;
}
err = 0; err = 0;
printk(KERN_INFO DRVNAME ": Found F71882FG chip at %#x, revision %d\n", printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %d\n",
(unsigned int)*address, f71882fg_names[sio_data->type], (unsigned int)*address,
(int)superio_inb(sioaddr, SIO_REG_DEVREV)); (int)superio_inb(sioaddr, SIO_REG_DEVREV));
exit: exit:
superio_exit(sioaddr); superio_exit(sioaddr);
return err; return err;
} }
static int __init f71882fg_device_add(unsigned short address) static int __init f71882fg_device_add(unsigned short address,
const struct f71882fg_sio_data *sio_data)
{ {
struct resource res = { struct resource res = {
.start = address, .start = address,
...@@ -892,12 +1930,23 @@ static int __init f71882fg_device_add(unsigned short address) ...@@ -892,12 +1930,23 @@ static int __init f71882fg_device_add(unsigned short address)
return -ENOMEM; return -ENOMEM;
res.name = f71882fg_pdev->name; res.name = f71882fg_pdev->name;
err = acpi_check_resource_conflict(&res);
if (err)
return err;
err = platform_device_add_resources(f71882fg_pdev, &res, 1); err = platform_device_add_resources(f71882fg_pdev, &res, 1);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device resource addition failed\n"); printk(KERN_ERR DRVNAME ": Device resource addition failed\n");
goto exit_device_put; goto exit_device_put;
} }
err = platform_device_add_data(f71882fg_pdev, sio_data,
sizeof(struct f71882fg_sio_data));
if (err) {
printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
goto exit_device_put;
}
err = platform_device_add(f71882fg_pdev); err = platform_device_add(f71882fg_pdev);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device addition failed\n"); printk(KERN_ERR DRVNAME ": Device addition failed\n");
...@@ -916,14 +1965,20 @@ static int __init f71882fg_init(void) ...@@ -916,14 +1965,20 @@ static int __init f71882fg_init(void)
{ {
int err = -ENODEV; int err = -ENODEV;
unsigned short address; unsigned short address;
struct f71882fg_sio_data sio_data;
memset(&sio_data, 0, sizeof(sio_data));
if (f71882fg_find(0x2e, &address) && f71882fg_find(0x4e, &address)) if (f71882fg_find(0x2e, &address, &sio_data) &&
f71882fg_find(0x4e, &address, &sio_data))
goto exit; goto exit;
if ((err = platform_driver_register(&f71882fg_driver))) err = platform_driver_register(&f71882fg_driver);
if (err)
goto exit; goto exit;
if ((err = f71882fg_device_add(address))) err = f71882fg_device_add(address, &sio_data);
if (err)
goto exit_driver; goto exit_driver;
return 0; return 0;
...@@ -941,7 +1996,7 @@ static void __exit f71882fg_exit(void) ...@@ -941,7 +1996,7 @@ static void __exit f71882fg_exit(void)
} }
MODULE_DESCRIPTION("F71882FG Hardware Monitoring Driver"); MODULE_DESCRIPTION("F71882FG Hardware Monitoring Driver");
MODULE_AUTHOR("Hans Edgington (hans@edgington.nl)"); MODULE_AUTHOR("Hans Edgington, Hans de Goede (hdegoede@redhat.com)");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
module_init(f71882fg_init); module_init(f71882fg_init);
......
/* fschmd.c /* fschmd.c
* *
* Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> * Copyright (C) 2007,2008 Hans de Goede <hdegoede@redhat.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
...@@ -42,11 +42,20 @@ ...@@ -42,11 +42,20 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/dmi.h> #include <linux/dmi.h>
#include <linux/fs.h>
#include <linux/watchdog.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <linux/kref.h>
/* Addresses to scan */ /* Addresses to scan */
static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END };
/* Insmod parameters */ /* Insmod parameters */
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) ")");
I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd);
/* /*
...@@ -63,13 +72,20 @@ I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); ...@@ -63,13 +72,20 @@ I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd);
#define FSCHMD_REG_EVENT_STATE 0x04 #define FSCHMD_REG_EVENT_STATE 0x04
#define FSCHMD_REG_CONTROL 0x05 #define FSCHMD_REG_CONTROL 0x05
#define FSCHMD_CONTROL_ALERT_LED_MASK 0x01 #define FSCHMD_CONTROL_ALERT_LED 0x01
/* watchdog (support to be implemented) */ /* watchdog */
#define FSCHMD_REG_WDOG_PRESET 0x28 #define FSCHMD_REG_WDOG_PRESET 0x28
#define FSCHMD_REG_WDOG_STATE 0x23 #define FSCHMD_REG_WDOG_STATE 0x23
#define FSCHMD_REG_WDOG_CONTROL 0x21 #define FSCHMD_REG_WDOG_CONTROL 0x21
#define FSCHMD_WDOG_CONTROL_TRIGGER 0x10
#define FSCHMD_WDOG_CONTROL_STARTED 0x10 /* the same as trigger */
#define FSCHMD_WDOG_CONTROL_STOP 0x20
#define FSCHMD_WDOG_CONTROL_RESOLUTION 0x40
#define FSCHMD_WDOG_STATE_CARDRESET 0x02
/* voltages, weird order is to keep the same order as the old drivers */ /* voltages, weird order is to keep the same order as the old drivers */
static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 };
...@@ -115,8 +131,8 @@ static const u8 FSCHMD_REG_FAN_RIPPLE[5][6] = { ...@@ -115,8 +131,8 @@ static const u8 FSCHMD_REG_FAN_RIPPLE[5][6] = {
static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 }; static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 };
/* Fan status register bitmasks */ /* Fan status register bitmasks */
#define FSCHMD_FAN_ALARM_MASK 0x04 /* called fault by FSC! */ #define FSCHMD_FAN_ALARM 0x04 /* called fault by FSC! */
#define FSCHMD_FAN_NOT_PRESENT_MASK 0x08 /* not documented */ #define FSCHMD_FAN_NOT_PRESENT 0x08 /* not documented */
/* actual temperature registers */ /* actual temperature registers */
...@@ -158,14 +174,11 @@ static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; */ ...@@ -158,14 +174,11 @@ static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; */
static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 }; static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 };
/* temp status register bitmasks */ /* temp status register bitmasks */
#define FSCHMD_TEMP_WORKING_MASK 0x01 #define FSCHMD_TEMP_WORKING 0x01
#define FSCHMD_TEMP_ALERT_MASK 0x02 #define FSCHMD_TEMP_ALERT 0x02
/* there only really is an alarm if the sensor is working and alert == 1 */ /* there only really is an alarm if the sensor is working and alert == 1 */
#define FSCHMD_TEMP_ALARM_MASK \ #define FSCHMD_TEMP_ALARM_MASK \
(FSCHMD_TEMP_WORKING_MASK | FSCHMD_TEMP_ALERT_MASK) (FSCHMD_TEMP_WORKING | FSCHMD_TEMP_ALERT)
/* our driver name */
#define FSCHMD_NAME "fschmd"
/* /*
* Functions declarations * Functions declarations
...@@ -195,7 +208,7 @@ MODULE_DEVICE_TABLE(i2c, fschmd_id); ...@@ -195,7 +208,7 @@ MODULE_DEVICE_TABLE(i2c, fschmd_id);
static struct i2c_driver fschmd_driver = { static struct i2c_driver fschmd_driver = {
.class = I2C_CLASS_HWMON, .class = I2C_CLASS_HWMON,
.driver = { .driver = {
.name = FSCHMD_NAME, .name = "fschmd",
}, },
.probe = fschmd_probe, .probe = fschmd_probe,
.remove = fschmd_remove, .remove = fschmd_remove,
...@@ -209,14 +222,26 @@ static struct i2c_driver fschmd_driver = { ...@@ -209,14 +222,26 @@ static struct i2c_driver fschmd_driver = {
*/ */
struct fschmd_data { struct fschmd_data {
struct i2c_client *client;
struct device *hwmon_dev; struct device *hwmon_dev;
struct mutex update_lock; struct mutex update_lock;
struct mutex watchdog_lock;
struct list_head list; /* member of the watchdog_data_list */
struct kref kref;
struct miscdevice watchdog_miscdev;
int kind; int kind;
unsigned long watchdog_is_open;
char watchdog_expect_close;
char watchdog_name[10]; /* must be unique to avoid sysfs conflict */
char valid; /* zero until following fields are valid */ char valid; /* zero until following fields are valid */
unsigned long last_updated; /* in jiffies */ unsigned long last_updated; /* in jiffies */
/* register values */ /* register values */
u8 revision; /* chip revision */
u8 global_control; /* global control register */ u8 global_control; /* global control register */
u8 watchdog_control; /* watchdog control register */
u8 watchdog_state; /* watchdog status register */
u8 watchdog_preset; /* watchdog counter preset on trigger val */
u8 volt[3]; /* 12, 5, battery voltage */ u8 volt[3]; /* 12, 5, battery voltage */
u8 temp_act[5]; /* temperature */ u8 temp_act[5]; /* temperature */
u8 temp_status[5]; /* status of sensor */ u8 temp_status[5]; /* status of sensor */
...@@ -228,11 +253,28 @@ struct fschmd_data { ...@@ -228,11 +253,28 @@ struct fschmd_data {
}; };
/* Global variables to hold information read from special DMI tables, which are /* Global variables to hold information read from special DMI tables, which are
available on FSC machines with an fscher or later chip. */ available on FSC machines with an fscher or later chip. There is no need to
protect these with a lock as they are only modified from our attach function
which always gets called with the i2c-core lock held and never accessed
before the attach function is done with them. */
static int dmi_mult[3] = { 490, 200, 100 }; static int dmi_mult[3] = { 490, 200, 100 };
static int dmi_offset[3] = { 0, 0, 0 }; static int dmi_offset[3] = { 0, 0, 0 };
static int dmi_vref = -1; static int dmi_vref = -1;
/* Somewhat ugly :( global data pointer list with all fschmd devices, so that
we can find our device data as when using misc_register there is no other
method to get to ones device data from the open fop. */
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 fschmd_release_resources(struct kref *ref)
{
struct fschmd_data *data = container_of(ref, struct fschmd_data, kref);
kfree(data);
}
/* /*
* Sysfs attr show / store functions * Sysfs attr show / store functions
...@@ -300,7 +342,7 @@ static ssize_t show_temp_fault(struct device *dev, ...@@ -300,7 +342,7 @@ static ssize_t show_temp_fault(struct device *dev,
struct fschmd_data *data = fschmd_update_device(dev); struct fschmd_data *data = fschmd_update_device(dev);
/* bit 0 set means sensor working ok, so no fault! */ /* bit 0 set means sensor working ok, so no fault! */
if (data->temp_status[index] & FSCHMD_TEMP_WORKING_MASK) if (data->temp_status[index] & FSCHMD_TEMP_WORKING)
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
else else
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
...@@ -385,7 +427,7 @@ static ssize_t show_fan_alarm(struct device *dev, ...@@ -385,7 +427,7 @@ static ssize_t show_fan_alarm(struct device *dev,
int index = to_sensor_dev_attr(devattr)->index; int index = to_sensor_dev_attr(devattr)->index;
struct fschmd_data *data = fschmd_update_device(dev); struct fschmd_data *data = fschmd_update_device(dev);
if (data->fan_status[index] & FSCHMD_FAN_ALARM_MASK) if (data->fan_status[index] & FSCHMD_FAN_ALARM)
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
...@@ -397,7 +439,7 @@ static ssize_t show_fan_fault(struct device *dev, ...@@ -397,7 +439,7 @@ static ssize_t show_fan_fault(struct device *dev,
int index = to_sensor_dev_attr(devattr)->index; int index = to_sensor_dev_attr(devattr)->index;
struct fschmd_data *data = fschmd_update_device(dev); struct fschmd_data *data = fschmd_update_device(dev);
if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT_MASK) if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT)
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
...@@ -449,7 +491,7 @@ static ssize_t show_alert_led(struct device *dev, ...@@ -449,7 +491,7 @@ static ssize_t show_alert_led(struct device *dev,
{ {
struct fschmd_data *data = fschmd_update_device(dev); struct fschmd_data *data = fschmd_update_device(dev);
if (data->global_control & FSCHMD_CONTROL_ALERT_LED_MASK) if (data->global_control & FSCHMD_CONTROL_ALERT_LED)
return sprintf(buf, "1\n"); return sprintf(buf, "1\n");
else else
return sprintf(buf, "0\n"); return sprintf(buf, "0\n");
...@@ -467,9 +509,9 @@ static ssize_t store_alert_led(struct device *dev, ...@@ -467,9 +509,9 @@ static ssize_t store_alert_led(struct device *dev,
reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL); reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL);
if (v) if (v)
reg |= FSCHMD_CONTROL_ALERT_LED_MASK; reg |= FSCHMD_CONTROL_ALERT_LED;
else else
reg &= ~FSCHMD_CONTROL_ALERT_LED_MASK; reg &= ~FSCHMD_CONTROL_ALERT_LED;
i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg); i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg);
...@@ -551,7 +593,265 @@ static struct sensor_device_attribute fschmd_fan_attr[] = { ...@@ -551,7 +593,265 @@ static struct sensor_device_attribute fschmd_fan_attr[] = {
/* /*
* Real code * Watchdog routines
*/
static int watchdog_set_timeout(struct fschmd_data *data, int timeout)
{
int ret, resolution;
int kind = data->kind + 1; /* 0-x array index -> 1-x module param */
/* 2 second or 60 second resolution? */
if (timeout <= 510 || kind == fscpos || kind == fscscy)
resolution = 2;
else
resolution = 60;
if (timeout < resolution || timeout > (resolution * 255))
return -EINVAL;
mutex_lock(&data->watchdog_lock);
if (!data->client) {
ret = -ENODEV;
goto leave;
}
if (resolution == 2)
data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_RESOLUTION;
else
data->watchdog_control |= FSCHMD_WDOG_CONTROL_RESOLUTION;
data->watchdog_preset = DIV_ROUND_UP(timeout, resolution);
/* Write new timeout value */
i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_PRESET,
data->watchdog_preset);
/* Write new control register, do not trigger! */
i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL,
data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER);
ret = data->watchdog_preset * resolution;
leave:
mutex_unlock(&data->watchdog_lock);
return ret;
}
static int watchdog_get_timeout(struct fschmd_data *data)
{
int timeout;
mutex_lock(&data->watchdog_lock);
if (data->watchdog_control & FSCHMD_WDOG_CONTROL_RESOLUTION)
timeout = data->watchdog_preset * 60;
else
timeout = data->watchdog_preset * 2;
mutex_unlock(&data->watchdog_lock);
return timeout;
}
static int watchdog_trigger(struct fschmd_data *data)
{
int ret = 0;
mutex_lock(&data->watchdog_lock);
if (!data->client) {
ret = -ENODEV;
goto leave;
}
data->watchdog_control |= FSCHMD_WDOG_CONTROL_TRIGGER;
i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL,
data->watchdog_control);
leave:
mutex_unlock(&data->watchdog_lock);
return ret;
}
static int watchdog_stop(struct fschmd_data *data)
{
int ret = 0;
mutex_lock(&data->watchdog_lock);
if (!data->client) {
ret = -ENODEV;
goto leave;
}
data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED;
/* Don't store the stop flag in our watchdog control register copy, as
its a write only bit (read always returns 0) */
i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL,
data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP);
leave:
mutex_unlock(&data->watchdog_lock);
return ret;
}
static int watchdog_open(struct inode *inode, struct file *filp)
{
struct fschmd_data *pos, *data = NULL;
/* We get called from drivers/char/misc.c with misc_mtx hold, and we
call misc_register() from fschmd_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;
}
}
/* Note we can never not have found data, so we don't check for this */
kref_get(&data->kref);
mutex_unlock(&watchdog_data_mutex);
if (test_and_set_bit(0, &data->watchdog_is_open))
return -EBUSY;
/* Start the watchdog */
watchdog_trigger(data);
filp->private_data = data;
return nonseekable_open(inode, filp);
}
static int watchdog_release(struct inode *inode, struct file *filp)
{
struct fschmd_data *data = filp->private_data;
if (data->watchdog_expect_close) {
watchdog_stop(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);
mutex_lock(&watchdog_data_mutex);
kref_put(&data->kref, fschmd_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 fschmd_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 = "FSC watchdog"
};
int i, ret = 0;
struct fschmd_data *data = filp->private_data;
switch (cmd) {
case WDIOC_GETSUPPORT:
ident.firmware_version = data->revision;
if (!nowayout)
ident.options |= WDIOF_MAGICCLOSE;
if (copy_to_user((void __user *)arg, &ident, sizeof(ident)))
ret = -EFAULT;
break;
case WDIOC_GETSTATUS:
ret = put_user(0, (int __user *)arg);
break;
case WDIOC_GETBOOTSTATUS:
if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET)
ret = put_user(WDIOF_CARDRESET, (int __user *)arg);
else
ret = put_user(0, (int __user *)arg);
break;
case WDIOC_KEEPALIVE:
ret = watchdog_trigger(data);
break;
case WDIOC_GETTIMEOUT:
i = watchdog_get_timeout(data);
ret = put_user(i, (int __user *)arg);
break;
case WDIOC_SETTIMEOUT:
if (get_user(i, (int __user *)arg)) {
ret = -EFAULT;
break;
}
ret = watchdog_set_timeout(data, i);
if (ret > 0)
ret = put_user(ret, (int __user *)arg);
break;
case WDIOC_SETOPTIONS:
if (get_user(i, (int __user *)arg)) {
ret = -EFAULT;
break;
}
if (i & WDIOS_DISABLECARD)
ret = watchdog_stop(data);
else if (i & WDIOS_ENABLECARD)
ret = watchdog_trigger(data);
else
ret = -EINVAL;
break;
default:
ret = -ENOTTY;
}
return ret;
}
static struct file_operations watchdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.open = watchdog_open,
.release = watchdog_release,
.write = watchdog_write,
.ioctl = watchdog_ioctl,
};
/*
* Detect, register, unregister and update device functions
*/ */
/* DMI decode routine to read voltage scaling factors from special DMI tables, /* DMI decode routine to read voltage scaling factors from special DMI tables,
...@@ -661,9 +961,9 @@ static int fschmd_probe(struct i2c_client *client, ...@@ -661,9 +961,9 @@ static int fschmd_probe(struct i2c_client *client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
struct fschmd_data *data; struct fschmd_data *data;
u8 revision;
const char * const names[5] = { "Poseidon", "Hermes", "Scylla", const char * const names[5] = { "Poseidon", "Hermes", "Scylla",
"Heracles", "Heimdall" }; "Heracles", "Heimdall" };
const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 };
int i, err; int i, err;
enum chips kind = id->driver_data; enum chips kind = id->driver_data;
...@@ -673,6 +973,13 @@ static int fschmd_probe(struct i2c_client *client, ...@@ -673,6 +973,13 @@ static int fschmd_probe(struct i2c_client *client,
i2c_set_clientdata(client, data); i2c_set_clientdata(client, data);
mutex_init(&data->update_lock); 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;
if (kind == fscpos) { if (kind == fscpos) {
/* The Poseidon has hardwired temp limits, fill these /* The Poseidon has hardwired temp limits, fill these
...@@ -683,16 +990,27 @@ static int fschmd_probe(struct i2c_client *client, ...@@ -683,16 +990,27 @@ static int fschmd_probe(struct i2c_client *client,
} }
/* Read the special DMI table for fscher and newer chips */ /* Read the special DMI table for fscher and newer chips */
if (kind == fscher || kind >= fschrc) { if ((kind == fscher || kind >= fschrc) && dmi_vref == -1) {
dmi_walk(fschmd_dmi_decode); dmi_walk(fschmd_dmi_decode);
if (dmi_vref == -1) { if (dmi_vref == -1) {
printk(KERN_WARNING FSCHMD_NAME dev_warn(&client->dev,
": Couldn't get voltage scaling factors from " "Couldn't get voltage scaling factors from "
"BIOS DMI table, using builtin defaults\n"); "BIOS DMI table, using builtin defaults\n");
dmi_vref = 33; dmi_vref = 33;
} }
} }
/* 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,
FSCHMD_REG_CONTROL);
data->watchdog_control = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_CONTROL);
data->watchdog_state = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_STATE);
data->watchdog_preset = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_PRESET);
/* i2c kind goes from 1-5, we want from 0-4 to address arrays */ /* i2c kind goes from 1-5, we want from 0-4 to address arrays */
data->kind = kind - 1; data->kind = kind - 1;
...@@ -735,9 +1053,43 @@ static int fschmd_probe(struct i2c_client *client, ...@@ -735,9 +1053,43 @@ static int fschmd_probe(struct i2c_client *client,
goto exit_detach; goto exit_detach;
} }
revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); /* We take the data_mutex lock early so that watchdog_open() cannot
printk(KERN_INFO FSCHMD_NAME ": Detected FSC %s chip, revision: %d\n", run when misc_register() has completed, but we've not yet added
names[data->kind], (int) revision); 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);
watchdog_set_timeout(data, 60);
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);
dev_info(&client->dev, "Detected FSC %s chip, revision: %d\n",
names[data->kind], (int) data->revision);
return 0; return 0;
...@@ -751,6 +1103,24 @@ static int fschmd_remove(struct i2c_client *client) ...@@ -751,6 +1103,24 @@ static int fschmd_remove(struct i2c_client *client)
struct fschmd_data *data = i2c_get_clientdata(client); struct fschmd_data *data = i2c_get_clientdata(client);
int i; int i;
/* 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_stop(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);
}
/* Check if registered in case we're called from fschmd_detect /* Check if registered in case we're called from fschmd_detect
to cleanup after an error */ to cleanup after an error */
if (data->hwmon_dev) if (data->hwmon_dev)
...@@ -765,7 +1135,10 @@ static int fschmd_remove(struct i2c_client *client) ...@@ -765,7 +1135,10 @@ static int fschmd_remove(struct i2c_client *client)
device_remove_file(&client->dev, device_remove_file(&client->dev,
&fschmd_fan_attr[i].dev_attr); &fschmd_fan_attr[i].dev_attr);
kfree(data); mutex_lock(&watchdog_data_mutex);
kref_put(&data->kref, fschmd_release_resources);
mutex_unlock(&watchdog_data_mutex);
return 0; return 0;
} }
...@@ -798,7 +1171,7 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) ...@@ -798,7 +1171,7 @@ static struct fschmd_data *fschmd_update_device(struct device *dev)
data->temp_act[i] < data->temp_max[i]) data->temp_act[i] < data->temp_max[i])
i2c_smbus_write_byte_data(client, i2c_smbus_write_byte_data(client,
FSCHMD_REG_TEMP_STATE[data->kind][i], FSCHMD_REG_TEMP_STATE[data->kind][i],
FSCHMD_TEMP_ALERT_MASK); FSCHMD_TEMP_ALERT);
} }
for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) {
...@@ -816,28 +1189,17 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) ...@@ -816,28 +1189,17 @@ static struct fschmd_data *fschmd_update_device(struct device *dev)
FSCHMD_REG_FAN_MIN[data->kind][i]); FSCHMD_REG_FAN_MIN[data->kind][i]);
/* reset fan status if speed is back to > 0 */ /* reset fan status if speed is back to > 0 */
if ((data->fan_status[i] & FSCHMD_FAN_ALARM_MASK) && if ((data->fan_status[i] & FSCHMD_FAN_ALARM) &&
data->fan_act[i]) data->fan_act[i])
i2c_smbus_write_byte_data(client, i2c_smbus_write_byte_data(client,
FSCHMD_REG_FAN_STATE[data->kind][i], FSCHMD_REG_FAN_STATE[data->kind][i],
FSCHMD_FAN_ALARM_MASK); FSCHMD_FAN_ALARM);
} }
for (i = 0; i < 3; i++) for (i = 0; i < 3; i++)
data->volt[i] = i2c_smbus_read_byte_data(client, data->volt[i] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_VOLT[i]); FSCHMD_REG_VOLT[i]);
data->global_control = i2c_smbus_read_byte_data(client,
FSCHMD_REG_CONTROL);
/* To be implemented in the future
data->watchdog[0] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_PRESET);
data->watchdog[1] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_STATE);
data->watchdog[2] = i2c_smbus_read_byte_data(client,
FSCHMD_REG_WDOG_CONTROL); */
data->last_updated = jiffies; data->last_updated = jiffies;
data->valid = 1; data->valid = 1;
} }
...@@ -857,7 +1219,7 @@ static void __exit fschmd_exit(void) ...@@ -857,7 +1219,7 @@ static void __exit fschmd_exit(void)
i2c_del_driver(&fschmd_driver); i2c_del_driver(&fschmd_driver);
} }
MODULE_AUTHOR("Hans de Goede <j.w.r.degoede@hhs.nl>"); MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and " MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and "
"Heimdall driver"); "Heimdall driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
......
...@@ -490,6 +490,13 @@ static unsigned long chipset_ids[] = { ...@@ -490,6 +490,13 @@ static unsigned long chipset_ids[] = {
0 0
}; };
static struct pci_device_id i5k_amb_ids[] __devinitdata = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, i5k_amb_ids);
static int __devinit i5k_amb_probe(struct platform_device *pdev) static int __devinit i5k_amb_probe(struct platform_device *pdev)
{ {
struct i5k_amb_data *data; struct i5k_amb_data *data;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
IT8712F Super I/O chip w/LPC interface IT8712F Super I/O chip w/LPC interface
IT8716F Super I/O chip w/LPC interface IT8716F Super I/O chip w/LPC interface
IT8718F 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 IT8726F Super I/O chip w/LPC interface
Sis950 A clone of the IT8705F Sis950 A clone of the IT8705F
...@@ -48,11 +49,12 @@ ...@@ -48,11 +49,12 @@
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/dmi.h> #include <linux/dmi.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
#define DRVNAME "it87" #define DRVNAME "it87"
enum chips { it87, it8712, it8716, it8718 }; enum chips { it87, it8712, it8716, it8718, it8720 };
static unsigned short force_id; static unsigned short force_id;
module_param(force_id, ushort, 0); module_param(force_id, ushort, 0);
...@@ -64,7 +66,10 @@ static struct platform_device *pdev; ...@@ -64,7 +66,10 @@ static struct platform_device *pdev;
#define DEV 0x07 /* Register: Logical device select */ #define DEV 0x07 /* Register: Logical device select */
#define VAL 0x2f /* The value to read/write */ #define VAL 0x2f /* The value to read/write */
#define PME 0x04 /* The device with the fan registers in it */ #define PME 0x04 /* The device with the fan registers in it */
#define GPIO 0x07 /* The device with the IT8718F VID value in it */
/* The device with the IT8718F/IT8720F VID value in it */
#define GPIO 0x07
#define DEVID 0x20 /* Register: Device ID */ #define DEVID 0x20 /* Register: Device ID */
#define DEVREV 0x22 /* Register: Device Revision */ #define DEVREV 0x22 /* Register: Device Revision */
...@@ -113,6 +118,7 @@ superio_exit(void) ...@@ -113,6 +118,7 @@ superio_exit(void)
#define IT8705F_DEVID 0x8705 #define IT8705F_DEVID 0x8705
#define IT8716F_DEVID 0x8716 #define IT8716F_DEVID 0x8716
#define IT8718F_DEVID 0x8718 #define IT8718F_DEVID 0x8718
#define IT8720F_DEVID 0x8720
#define IT8726F_DEVID 0x8726 #define IT8726F_DEVID 0x8726
#define IT87_ACT_REG 0x30 #define IT87_ACT_REG 0x30
#define IT87_BASE_REG 0x60 #define IT87_BASE_REG 0x60
...@@ -150,8 +156,8 @@ static int fix_pwm_polarity; ...@@ -150,8 +156,8 @@ static int fix_pwm_polarity;
#define IT87_REG_ALARM2 0x02 #define IT87_REG_ALARM2 0x02
#define IT87_REG_ALARM3 0x03 #define IT87_REG_ALARM3 0x03
/* The IT8718F has the VID value in a different register, in Super-I/O /* The IT8718F and IT8720F have the VID value in a different register, in
configuration space. */ Super-I/O configuration space. */
#define IT87_REG_VID 0x0a #define IT87_REG_VID 0x0a
/* The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b /* The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b
for fan divisors. Later IT8712F revisions must use 16-bit tachometer for fan divisors. Later IT8712F revisions must use 16-bit tachometer
...@@ -282,7 +288,8 @@ static inline int has_16bit_fans(const struct it87_data *data) ...@@ -282,7 +288,8 @@ static inline int has_16bit_fans(const struct it87_data *data)
return (data->type == it87 && data->revision >= 0x03) return (data->type == it87 && data->revision >= 0x03)
|| (data->type == it8712 && data->revision >= 0x08) || (data->type == it8712 && data->revision >= 0x08)
|| data->type == it8716 || data->type == it8716
|| data->type == it8718; || data->type == it8718
|| data->type == it8720;
} }
static int it87_probe(struct platform_device *pdev); static int it87_probe(struct platform_device *pdev);
...@@ -992,6 +999,9 @@ static int __init it87_find(unsigned short *address, ...@@ -992,6 +999,9 @@ static int __init it87_find(unsigned short *address,
case IT8718F_DEVID: case IT8718F_DEVID:
sio_data->type = it8718; sio_data->type = it8718;
break; break;
case IT8720F_DEVID:
sio_data->type = it8720;
break;
case 0xffff: /* No device at all */ case 0xffff: /* No device at all */
goto exit; goto exit;
default: default:
...@@ -1022,7 +1032,8 @@ static int __init it87_find(unsigned short *address, ...@@ -1022,7 +1032,8 @@ static int __init it87_find(unsigned short *address,
int reg; int reg;
superio_select(GPIO); superio_select(GPIO);
if (chip_type == it8718) if ((chip_type == it8718) ||
(chip_type == it8720))
sio_data->vid_value = superio_inb(IT87_SIO_VID_REG); sio_data->vid_value = superio_inb(IT87_SIO_VID_REG);
reg = superio_inb(IT87_SIO_PINX2_REG); reg = superio_inb(IT87_SIO_PINX2_REG);
...@@ -1068,6 +1079,7 @@ static int __devinit it87_probe(struct platform_device *pdev) ...@@ -1068,6 +1079,7 @@ static int __devinit it87_probe(struct platform_device *pdev)
"it8712", "it8712",
"it8716", "it8716",
"it8718", "it8718",
"it8720",
}; };
res = platform_get_resource(pdev, IORESOURCE_IO, 0); res = platform_get_resource(pdev, IORESOURCE_IO, 0);
...@@ -1226,7 +1238,7 @@ static int __devinit it87_probe(struct platform_device *pdev) ...@@ -1226,7 +1238,7 @@ static int __devinit it87_probe(struct platform_device *pdev)
} }
if (data->type == it8712 || data->type == it8716 if (data->type == it8712 || data->type == it8716
|| data->type == it8718) { || data->type == it8718 || data->type == it8720) {
data->vrm = vid_which_vrm(); data->vrm = vid_which_vrm();
/* VID reading from Super-I/O config space if available */ /* VID reading from Super-I/O config space if available */
data->vid = sio_data->vid_value; data->vid = sio_data->vid_value;
...@@ -1374,7 +1386,7 @@ static void __devinit it87_init_device(struct platform_device *pdev) ...@@ -1374,7 +1386,7 @@ static void __devinit it87_init_device(struct platform_device *pdev)
it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127); it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127);
} }
/* Check if temperature channnels are reset manually or by some reason */ /* Check if temperature channels are reset manually or by some reason */
tmp = it87_read_value(data, IT87_REG_TEMP_ENABLE); tmp = it87_read_value(data, IT87_REG_TEMP_ENABLE);
if ((tmp & 0x3f) == 0) { if ((tmp & 0x3f) == 0) {
/* Temp1,Temp3=thermistor; Temp2=thermal diode */ /* Temp1,Temp3=thermistor; Temp2=thermal diode */
...@@ -1513,7 +1525,8 @@ static struct it87_data *it87_update_device(struct device *dev) ...@@ -1513,7 +1525,8 @@ static struct it87_data *it87_update_device(struct device *dev)
data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE);
/* The 8705 does not have VID capability. /* The 8705 does not have VID capability.
The 8718 does not use IT87_REG_VID for the same purpose. */ The 8718 and the 8720 don't use IT87_REG_VID for the
same purpose. */
if (data->type == it8712 || data->type == it8716) { if (data->type == it8712 || data->type == it8716) {
data->vid = it87_read_value(data, IT87_REG_VID); data->vid = it87_read_value(data, IT87_REG_VID);
/* The older IT8712F revisions had only 5 VID pins, /* The older IT8712F revisions had only 5 VID pins,
...@@ -1540,6 +1553,10 @@ static int __init it87_device_add(unsigned short address, ...@@ -1540,6 +1553,10 @@ static int __init it87_device_add(unsigned short address,
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address); pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
...@@ -1608,7 +1625,7 @@ static void __exit sm_it87_exit(void) ...@@ -1608,7 +1625,7 @@ static void __exit sm_it87_exit(void)
MODULE_AUTHOR("Chris Gauthron, " MODULE_AUTHOR("Chris Gauthron, "
"Jean Delvare <khali@linux-fr.org>"); "Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8726F, SiS950 driver"); MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8720F/8726F, SiS950 driver");
module_param(update_vbat, bool, 0); module_param(update_vbat, bool, 0);
MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value");
module_param(fix_pwm_polarity, bool, 0); module_param(fix_pwm_polarity, bool, 0);
......
...@@ -37,9 +37,13 @@ ...@@ -37,9 +37,13 @@
#define DRVNAME "lm70" #define DRVNAME "lm70"
#define LM70_CHIP_LM70 0 /* original NS LM70 */
#define LM70_CHIP_TMP121 1 /* TI TMP121/TMP123 */
struct lm70 { struct lm70 {
struct device *hwmon_dev; struct device *hwmon_dev;
struct mutex lock; struct mutex lock;
unsigned int chip;
}; };
/* sysfs hook function */ /* sysfs hook function */
...@@ -47,7 +51,7 @@ static ssize_t lm70_sense_temp(struct device *dev, ...@@ -47,7 +51,7 @@ static ssize_t lm70_sense_temp(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
struct spi_device *spi = to_spi_device(dev); struct spi_device *spi = to_spi_device(dev);
int status, val; int status, val = 0;
u8 rxbuf[2]; u8 rxbuf[2];
s16 raw=0; s16 raw=0;
struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev);
...@@ -65,12 +69,12 @@ static ssize_t lm70_sense_temp(struct device *dev, ...@@ -65,12 +69,12 @@ static ssize_t lm70_sense_temp(struct device *dev,
"spi_write_then_read failed with status %d\n", status); "spi_write_then_read failed with status %d\n", status);
goto out; goto out;
} }
dev_dbg(dev, "rxbuf[1] : 0x%x rxbuf[0] : 0x%x\n", rxbuf[1], rxbuf[0]); raw = (rxbuf[0] << 8) + rxbuf[1];
dev_dbg(dev, "rxbuf[0] : 0x%02x rxbuf[1] : 0x%02x raw=0x%04x\n",
raw = (rxbuf[1] << 8) + rxbuf[0]; rxbuf[0], rxbuf[1], raw);
dev_dbg(dev, "raw=0x%x\n", raw);
/* /*
* LM70:
* The "raw" temperature read into rxbuf[] is a 16-bit signed 2's * The "raw" temperature read into rxbuf[] is a 16-bit signed 2's
* complement value. Only the MSB 11 bits (1 sign + 10 temperature * complement value. Only the MSB 11 bits (1 sign + 10 temperature
* bits) are meaningful; the LSB 5 bits are to be discarded. * bits) are meaningful; the LSB 5 bits are to be discarded.
...@@ -80,8 +84,21 @@ static ssize_t lm70_sense_temp(struct device *dev, ...@@ -80,8 +84,21 @@ static ssize_t lm70_sense_temp(struct device *dev,
* by 0.25. Also multiply by 1000 to represent in millidegrees * by 0.25. Also multiply by 1000 to represent in millidegrees
* Celsius. * Celsius.
* So it's equivalent to multiplying by 0.25 * 1000 = 250. * So it's equivalent to multiplying by 0.25 * 1000 = 250.
*
* TMP121/TMP123:
* 13 bits of 2's complement data, discard LSB 3 bits,
* resolution 0.0625 degrees celsius.
*/ */
val = ((int)raw/32) * 250; switch (p_lm70->chip) {
case LM70_CHIP_LM70:
val = ((int)raw / 32) * 250;
break;
case LM70_CHIP_TMP121:
val = ((int)raw / 8) * 625 / 10;
break;
}
status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */
out: out:
mutex_unlock(&p_lm70->lock); mutex_unlock(&p_lm70->lock);
...@@ -93,27 +110,39 @@ static DEVICE_ATTR(temp1_input, S_IRUGO, lm70_sense_temp, NULL); ...@@ -93,27 +110,39 @@ static DEVICE_ATTR(temp1_input, S_IRUGO, lm70_sense_temp, NULL);
static ssize_t lm70_show_name(struct device *dev, struct device_attribute static ssize_t lm70_show_name(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
return sprintf(buf, "lm70\n"); struct lm70 *p_lm70 = dev_get_drvdata(dev);
int ret;
switch (p_lm70->chip) {
case LM70_CHIP_LM70:
ret = sprintf(buf, "lm70\n");
break;
case LM70_CHIP_TMP121:
ret = sprintf(buf, "tmp121\n");
break;
default:
ret = -EINVAL;
}
return ret;
} }
static DEVICE_ATTR(name, S_IRUGO, lm70_show_name, NULL); static DEVICE_ATTR(name, S_IRUGO, lm70_show_name, NULL);
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static int __devinit lm70_probe(struct spi_device *spi) static int __devinit common_probe(struct spi_device *spi, int chip)
{ {
struct lm70 *p_lm70; struct lm70 *p_lm70;
int status; int status;
/* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */ /* NOTE: we assume 8-bit words, and convert to 16 bits manually */
if ((spi->mode & (SPI_CPOL|SPI_CPHA)) || !(spi->mode & SPI_3WIRE))
return -EINVAL;
p_lm70 = kzalloc(sizeof *p_lm70, GFP_KERNEL); p_lm70 = kzalloc(sizeof *p_lm70, GFP_KERNEL);
if (!p_lm70) if (!p_lm70)
return -ENOMEM; return -ENOMEM;
mutex_init(&p_lm70->lock); mutex_init(&p_lm70->lock);
p_lm70->chip = chip;
/* sysfs hook */ /* sysfs hook */
p_lm70->hwmon_dev = hwmon_device_register(&spi->dev); p_lm70->hwmon_dev = hwmon_device_register(&spi->dev);
...@@ -141,6 +170,24 @@ static int __devinit lm70_probe(struct spi_device *spi) ...@@ -141,6 +170,24 @@ static int __devinit lm70_probe(struct spi_device *spi)
return status; return status;
} }
static int __devinit lm70_probe(struct spi_device *spi)
{
/* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */
if ((spi->mode & (SPI_CPOL | SPI_CPHA)) || !(spi->mode & SPI_3WIRE))
return -EINVAL;
return common_probe(spi, LM70_CHIP_LM70);
}
static int __devinit tmp121_probe(struct spi_device *spi)
{
/* signaling is SPI_MODE_0 with only MISO connected */
if (spi->mode & (SPI_CPOL | SPI_CPHA))
return -EINVAL;
return common_probe(spi, LM70_CHIP_TMP121);
}
static int __devexit lm70_remove(struct spi_device *spi) static int __devexit lm70_remove(struct spi_device *spi)
{ {
struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev);
...@@ -154,6 +201,15 @@ static int __devexit lm70_remove(struct spi_device *spi) ...@@ -154,6 +201,15 @@ static int __devexit lm70_remove(struct spi_device *spi)
return 0; return 0;
} }
static struct spi_driver tmp121_driver = {
.driver = {
.name = "tmp121",
.owner = THIS_MODULE,
},
.probe = tmp121_probe,
.remove = __devexit_p(lm70_remove),
};
static struct spi_driver lm70_driver = { static struct spi_driver lm70_driver = {
.driver = { .driver = {
.name = "lm70", .name = "lm70",
...@@ -165,17 +221,26 @@ static struct spi_driver lm70_driver = { ...@@ -165,17 +221,26 @@ static struct spi_driver lm70_driver = {
static int __init init_lm70(void) static int __init init_lm70(void)
{ {
return spi_register_driver(&lm70_driver); int ret = spi_register_driver(&lm70_driver);
if (ret)
return ret;
ret = spi_register_driver(&tmp121_driver);
if (ret)
spi_unregister_driver(&lm70_driver);
return ret;
} }
static void __exit cleanup_lm70(void) static void __exit cleanup_lm70(void)
{ {
spi_unregister_driver(&lm70_driver); spi_unregister_driver(&lm70_driver);
spi_unregister_driver(&tmp121_driver);
} }
module_init(init_lm70); module_init(init_lm70);
module_exit(cleanup_lm70); module_exit(cleanup_lm70);
MODULE_AUTHOR("Kaiwan N Billimoria"); MODULE_AUTHOR("Kaiwan N Billimoria");
MODULE_DESCRIPTION("National Semiconductor LM70 Linux driver"); MODULE_DESCRIPTION("NS LM70 / TI TMP121/TMP123 Linux driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
/*
* Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller
*
* Copyright (C) 2008 Ira W. Snyder <iws@ovro.caltech.edu>
*
* 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; version 2 of the License.
*
* This driver is based on the ds1621 and ina209 drivers.
*
* Datasheet:
* http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
/* Valid addresses are 0x20 - 0x3f
*
* For now, we do not probe, since some of these addresses
* are known to be unfriendly to probing */
static const unsigned short normal_i2c[] = { I2C_CLIENT_END };
/* Insmod parameters */
I2C_CLIENT_INSMOD_1(ltc4245);
/* Here are names of the chip's registers (a.k.a. commands) */
enum ltc4245_cmd {
LTC4245_STATUS = 0x00, /* readonly */
LTC4245_ALERT = 0x01,
LTC4245_CONTROL = 0x02,
LTC4245_ON = 0x03,
LTC4245_FAULT1 = 0x04,
LTC4245_FAULT2 = 0x05,
LTC4245_GPIO = 0x06,
LTC4245_ADCADR = 0x07,
LTC4245_12VIN = 0x10,
LTC4245_12VSENSE = 0x11,
LTC4245_12VOUT = 0x12,
LTC4245_5VIN = 0x13,
LTC4245_5VSENSE = 0x14,
LTC4245_5VOUT = 0x15,
LTC4245_3VIN = 0x16,
LTC4245_3VSENSE = 0x17,
LTC4245_3VOUT = 0x18,
LTC4245_VEEIN = 0x19,
LTC4245_VEESENSE = 0x1a,
LTC4245_VEEOUT = 0x1b,
LTC4245_GPIOADC1 = 0x1c,
LTC4245_GPIOADC2 = 0x1d,
LTC4245_GPIOADC3 = 0x1e,
};
struct ltc4245_data {
struct device *hwmon_dev;
struct mutex update_lock;
bool valid;
unsigned long last_updated; /* in jiffies */
/* Control registers */
u8 cregs[0x08];
/* Voltage registers */
u8 vregs[0x0f];
};
static struct ltc4245_data *ltc4245_update_device(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ltc4245_data *data = i2c_get_clientdata(client);
s32 val;
int i;
mutex_lock(&data->update_lock);
if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
dev_dbg(&client->dev, "Starting ltc4245 update\n");
/* Read control registers -- 0x00 to 0x07 */
for (i = 0; i < ARRAY_SIZE(data->cregs); i++) {
val = i2c_smbus_read_byte_data(client, i);
if (unlikely(val < 0))
data->cregs[i] = 0;
else
data->cregs[i] = val;
}
/* Read voltage registers -- 0x10 to 0x1f */
for (i = 0; i < ARRAY_SIZE(data->vregs); i++) {
val = i2c_smbus_read_byte_data(client, i+0x10);
if (unlikely(val < 0))
data->vregs[i] = 0;
else
data->vregs[i] = val;
}
data->last_updated = jiffies;
data->valid = 1;
}
mutex_unlock(&data->update_lock);
return data;
}
/* Return the voltage from the given register in millivolts */
static int ltc4245_get_voltage(struct device *dev, u8 reg)
{
struct ltc4245_data *data = ltc4245_update_device(dev);
const u8 regval = data->vregs[reg - 0x10];
u32 voltage = 0;
switch (reg) {
case LTC4245_12VIN:
case LTC4245_12VOUT:
voltage = regval * 55;
break;
case LTC4245_5VIN:
case LTC4245_5VOUT:
voltage = regval * 22;
break;
case LTC4245_3VIN:
case LTC4245_3VOUT:
voltage = regval * 15;
break;
case LTC4245_VEEIN:
case LTC4245_VEEOUT:
voltage = regval * -55;
break;
case LTC4245_GPIOADC1:
case LTC4245_GPIOADC2:
case LTC4245_GPIOADC3:
voltage = regval * 10;
break;
default:
/* If we get here, the developer messed up */
WARN_ON_ONCE(1);
break;
}
return voltage;
}
/* Return the current in the given sense register in milliAmperes */
static unsigned int ltc4245_get_current(struct device *dev, u8 reg)
{
struct ltc4245_data *data = ltc4245_update_device(dev);
const u8 regval = data->vregs[reg - 0x10];
unsigned int voltage;
unsigned int curr;
/* The strange looking conversions that follow are fixed-point
* math, since we cannot do floating point in the kernel.
*
* Step 1: convert sense register to microVolts
* Step 2: convert voltage to milliAmperes
*
* If you play around with the V=IR equation, you come up with
* the following: X uV / Y mOhm == Z mA
*
* With the resistors that are fractions of a milliOhm, we multiply
* the voltage and resistance by 10, to shift the decimal point.
* Now we can use the normal division operator again.
*/
switch (reg) {
case LTC4245_12VSENSE:
voltage = regval * 250; /* voltage in uV */
curr = voltage / 50; /* sense resistor 50 mOhm */
break;
case LTC4245_5VSENSE:
voltage = regval * 125; /* voltage in uV */
curr = (voltage * 10) / 35; /* sense resistor 3.5 mOhm */
break;
case LTC4245_3VSENSE:
voltage = regval * 125; /* voltage in uV */
curr = (voltage * 10) / 25; /* sense resistor 2.5 mOhm */
break;
case LTC4245_VEESENSE:
voltage = regval * 250; /* voltage in uV */
curr = voltage / 100; /* sense resistor 100 mOhm */
break;
default:
/* If we get here, the developer messed up */
WARN_ON_ONCE(1);
curr = 0;
break;
}
return curr;
}
static ssize_t ltc4245_show_voltage(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
const int voltage = ltc4245_get_voltage(dev, attr->index);
return snprintf(buf, PAGE_SIZE, "%d\n", voltage);
}
static ssize_t ltc4245_show_current(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
const unsigned int curr = ltc4245_get_current(dev, attr->index);
return snprintf(buf, PAGE_SIZE, "%u\n", curr);
}
static ssize_t ltc4245_show_power(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
const unsigned int curr = ltc4245_get_current(dev, attr->index);
const int output_voltage = ltc4245_get_voltage(dev, attr->index+1);
/* current in mA * voltage in mV == power in uW */
const unsigned int power = abs(output_voltage * curr);
return snprintf(buf, PAGE_SIZE, "%u\n", power);
}
static ssize_t ltc4245_show_alarm(struct device *dev,
struct device_attribute *da,
char *buf)
{
struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da);
struct ltc4245_data *data = ltc4245_update_device(dev);
const u8 reg = data->cregs[attr->index];
const u32 mask = attr->nr;
return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0);
}
/* These macros are used below in constructing device attribute objects
* for use with sysfs_create_group() to make a sysfs device file
* for each register.
*/
#define LTC4245_VOLTAGE(name, ltc4245_cmd_idx) \
static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
ltc4245_show_voltage, NULL, ltc4245_cmd_idx)
#define LTC4245_CURRENT(name, ltc4245_cmd_idx) \
static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
ltc4245_show_current, NULL, ltc4245_cmd_idx)
#define LTC4245_POWER(name, ltc4245_cmd_idx) \
static SENSOR_DEVICE_ATTR(name, S_IRUGO, \
ltc4245_show_power, NULL, ltc4245_cmd_idx)
#define LTC4245_ALARM(name, mask, reg) \
static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \
ltc4245_show_alarm, NULL, (mask), reg)
/* Construct a sensor_device_attribute structure for each register */
/* Input voltages */
LTC4245_VOLTAGE(in1_input, LTC4245_12VIN);
LTC4245_VOLTAGE(in2_input, LTC4245_5VIN);
LTC4245_VOLTAGE(in3_input, LTC4245_3VIN);
LTC4245_VOLTAGE(in4_input, LTC4245_VEEIN);
/* Input undervoltage alarms */
LTC4245_ALARM(in1_min_alarm, (1 << 0), LTC4245_FAULT1);
LTC4245_ALARM(in2_min_alarm, (1 << 1), LTC4245_FAULT1);
LTC4245_ALARM(in3_min_alarm, (1 << 2), LTC4245_FAULT1);
LTC4245_ALARM(in4_min_alarm, (1 << 3), LTC4245_FAULT1);
/* Currents (via sense resistor) */
LTC4245_CURRENT(curr1_input, LTC4245_12VSENSE);
LTC4245_CURRENT(curr2_input, LTC4245_5VSENSE);
LTC4245_CURRENT(curr3_input, LTC4245_3VSENSE);
LTC4245_CURRENT(curr4_input, LTC4245_VEESENSE);
/* Overcurrent alarms */
LTC4245_ALARM(curr1_max_alarm, (1 << 4), LTC4245_FAULT1);
LTC4245_ALARM(curr2_max_alarm, (1 << 5), LTC4245_FAULT1);
LTC4245_ALARM(curr3_max_alarm, (1 << 6), LTC4245_FAULT1);
LTC4245_ALARM(curr4_max_alarm, (1 << 7), LTC4245_FAULT1);
/* Output voltages */
LTC4245_VOLTAGE(in5_input, LTC4245_12VOUT);
LTC4245_VOLTAGE(in6_input, LTC4245_5VOUT);
LTC4245_VOLTAGE(in7_input, LTC4245_3VOUT);
LTC4245_VOLTAGE(in8_input, LTC4245_VEEOUT);
/* Power Bad alarms */
LTC4245_ALARM(in5_min_alarm, (1 << 0), LTC4245_FAULT2);
LTC4245_ALARM(in6_min_alarm, (1 << 1), LTC4245_FAULT2);
LTC4245_ALARM(in7_min_alarm, (1 << 2), LTC4245_FAULT2);
LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2);
/* GPIO voltages */
LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC1);
LTC4245_VOLTAGE(in10_input, LTC4245_GPIOADC2);
LTC4245_VOLTAGE(in11_input, LTC4245_GPIOADC3);
/* Power Consumption (virtual) */
LTC4245_POWER(power1_input, LTC4245_12VSENSE);
LTC4245_POWER(power2_input, LTC4245_5VSENSE);
LTC4245_POWER(power3_input, LTC4245_3VSENSE);
LTC4245_POWER(power4_input, LTC4245_VEESENSE);
/* Finally, construct an array of pointers to members of the above objects,
* as required for sysfs_create_group()
*/
static struct attribute *ltc4245_attributes[] = {
&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_in1_min_alarm.dev_attr.attr,
&sensor_dev_attr_in2_min_alarm.dev_attr.attr,
&sensor_dev_attr_in3_min_alarm.dev_attr.attr,
&sensor_dev_attr_in4_min_alarm.dev_attr.attr,
&sensor_dev_attr_curr1_input.dev_attr.attr,
&sensor_dev_attr_curr2_input.dev_attr.attr,
&sensor_dev_attr_curr3_input.dev_attr.attr,
&sensor_dev_attr_curr4_input.dev_attr.attr,
&sensor_dev_attr_curr1_max_alarm.dev_attr.attr,
&sensor_dev_attr_curr2_max_alarm.dev_attr.attr,
&sensor_dev_attr_curr3_max_alarm.dev_attr.attr,
&sensor_dev_attr_curr4_max_alarm.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_in5_min_alarm.dev_attr.attr,
&sensor_dev_attr_in6_min_alarm.dev_attr.attr,
&sensor_dev_attr_in7_min_alarm.dev_attr.attr,
&sensor_dev_attr_in8_min_alarm.dev_attr.attr,
&sensor_dev_attr_in9_input.dev_attr.attr,
&sensor_dev_attr_in10_input.dev_attr.attr,
&sensor_dev_attr_in11_input.dev_attr.attr,
&sensor_dev_attr_power1_input.dev_attr.attr,
&sensor_dev_attr_power2_input.dev_attr.attr,
&sensor_dev_attr_power3_input.dev_attr.attr,
&sensor_dev_attr_power4_input.dev_attr.attr,
NULL,
};
static const struct attribute_group ltc4245_group = {
.attrs = ltc4245_attributes,
};
static int ltc4245_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct ltc4245_data *data;
int ret;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
ret = -ENOMEM;
goto out_kzalloc;
}
i2c_set_clientdata(client, data);
mutex_init(&data->update_lock);
/* Initialize the LTC4245 chip */
/* TODO */
/* Register sysfs hooks */
ret = sysfs_create_group(&client->dev.kobj, &ltc4245_group);
if (ret)
goto out_sysfs_create_group;
data->hwmon_dev = hwmon_device_register(&client->dev);
if (IS_ERR(data->hwmon_dev)) {
ret = PTR_ERR(data->hwmon_dev);
goto out_hwmon_device_register;
}
return 0;
out_hwmon_device_register:
sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
out_sysfs_create_group:
kfree(data);
out_kzalloc:
return ret;
}
static int ltc4245_remove(struct i2c_client *client)
{
struct ltc4245_data *data = i2c_get_clientdata(client);
hwmon_device_unregister(data->hwmon_dev);
sysfs_remove_group(&client->dev.kobj, &ltc4245_group);
kfree(data);
return 0;
}
/* Check that some bits in a control register appear at all possible
* locations without changing value
*
* @client: the i2c client to use
* @reg: the register to read
* @bits: the bits to check (0xff checks all bits,
* 0x03 checks only the last two bits)
*
* return -ERRNO if the register read failed
* return -ENODEV if the register value doesn't stay constant at all
* possible addresses
*
* return 0 for success
*/
static int ltc4245_check_control_reg(struct i2c_client *client, u8 reg, u8 bits)
{
int i;
s32 v, voff1, voff2;
/* Read register and check for error */
v = i2c_smbus_read_byte_data(client, reg);
if (v < 0)
return v;
v &= bits;
for (i = 0x00; i < 0xff; i += 0x20) {
voff1 = i2c_smbus_read_byte_data(client, reg + i);
if (voff1 < 0)
return voff1;
voff2 = i2c_smbus_read_byte_data(client, reg + i + 0x08);
if (voff2 < 0)
return voff2;
voff1 &= bits;
voff2 &= bits;
if (v != voff1 || v != voff2)
return -ENODEV;
}
return 0;
}
static int ltc4245_detect(struct i2c_client *client,
int kind,
struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
if (kind < 0) { /* probed detection - check the chip type */
s32 v; /* 8 bits from the chip, or -ERRNO */
/* Chip registers 0x00-0x07 are control registers
* Chip registers 0x10-0x1f are data registers
*
* Address bits b7-b5 are ignored. This makes the chip "repeat"
* in steps of 0x20. Any control registers should appear with
* the same values across all duplicated addresses.
*
* Register 0x02 bit b2 is reserved, expect 0
* Register 0x07 bits b7 to b4 are reserved, expect 0
*
* Registers 0x01, 0x02 are control registers and should not
* change on their own.
*
* Register 0x06 bits b6 and b7 are control bits, and should
* not change on their own.
*
* Register 0x07 bits b3 to b0 are control bits, and should
* not change on their own.
*/
/* read register 0x02 reserved bit, expect 0 */
v = i2c_smbus_read_byte_data(client, LTC4245_CONTROL);
if (v < 0 || (v & 0x04) != 0)
return -ENODEV;
/* read register 0x07 reserved bits, expect 0 */
v = i2c_smbus_read_byte_data(client, LTC4245_ADCADR);
if (v < 0 || (v & 0xf0) != 0)
return -ENODEV;
/* check that the alert register appears at all locations */
if (ltc4245_check_control_reg(client, LTC4245_ALERT, 0xff))
return -ENODEV;
/* check that the control register appears at all locations */
if (ltc4245_check_control_reg(client, LTC4245_CONTROL, 0xff))
return -ENODEV;
/* check that register 0x06 bits b6 and b7 stay constant */
if (ltc4245_check_control_reg(client, LTC4245_GPIO, 0xc0))
return -ENODEV;
/* check that register 0x07 bits b3-b0 stay constant */
if (ltc4245_check_control_reg(client, LTC4245_ADCADR, 0x0f))
return -ENODEV;
}
strlcpy(info->type, "ltc4245", I2C_NAME_SIZE);
dev_info(&adapter->dev, "ltc4245 %s at address 0x%02x\n",
kind < 0 ? "probed" : "forced",
client->addr);
return 0;
}
static const struct i2c_device_id ltc4245_id[] = {
{ "ltc4245", ltc4245 },
{ }
};
MODULE_DEVICE_TABLE(i2c, ltc4245_id);
/* This is the driver that will be inserted */
static struct i2c_driver ltc4245_driver = {
.class = I2C_CLASS_HWMON,
.driver = {
.name = "ltc4245",
},
.probe = ltc4245_probe,
.remove = ltc4245_remove,
.id_table = ltc4245_id,
.detect = ltc4245_detect,
.address_data = &addr_data,
};
static int __init ltc4245_init(void)
{
return i2c_add_driver(&ltc4245_driver);
}
static void __exit ltc4245_exit(void)
{
i2c_del_driver(&ltc4245_driver);
}
MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>");
MODULE_DESCRIPTION("LTC4245 driver");
MODULE_LICENSE("GPL");
module_init(ltc4245_init);
module_exit(ltc4245_exit);
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <linux/hwmon-vid.h> #include <linux/hwmon-vid.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static u8 devid; static u8 devid;
...@@ -1627,6 +1628,11 @@ static int __init pc87360_device_add(unsigned short address) ...@@ -1627,6 +1628,11 @@ static int __init pc87360_device_add(unsigned short address)
continue; continue;
res.start = extra_isa[i]; res.start = extra_isa[i];
res.end = extra_isa[i] + PC87360_EXTENT - 1; res.end = extra_isa[i] + PC87360_EXTENT - 1;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit_device_put;
err = platform_device_add_resources(pdev, &res, 1); err = platform_device_add_resources(pdev, &res, 1);
if (err) { if (err) {
printk(KERN_ERR "pc87360: Device resource[%d] " printk(KERN_ERR "pc87360: Device resource[%d] "
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static unsigned short force_id; static unsigned short force_id;
...@@ -524,6 +525,10 @@ static int __init pc87427_device_add(unsigned short address) ...@@ -524,6 +525,10 @@ static int __init pc87427_device_add(unsigned short address)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address); pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -62,6 +62,7 @@ ...@@ -62,6 +62,7 @@
#include <linux/jiffies.h> #include <linux/jiffies.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
...@@ -727,6 +728,10 @@ static int __devinit sis5595_device_add(unsigned short address) ...@@ -727,6 +728,10 @@ static int __devinit sis5595_device_add(unsigned short address)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc("sis5595", address); pdev = platform_device_alloc("sis5595", address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static unsigned short force_id; static unsigned short force_id;
...@@ -303,6 +304,10 @@ static int __init smsc47b397_device_add(unsigned short address) ...@@ -303,6 +304,10 @@ static int __init smsc47b397_device_add(unsigned short address)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address); pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static unsigned short force_id; static unsigned short force_id;
...@@ -705,6 +706,10 @@ static int __init smsc47m1_device_add(unsigned short address, ...@@ -705,6 +706,10 @@ static int __init smsc47m1_device_add(unsigned short address,
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address); pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
#include <linux/init.h> #include <linux/init.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/sysfs.h> #include <linux/sysfs.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
...@@ -783,6 +784,10 @@ static int __devinit via686a_device_add(unsigned short address) ...@@ -783,6 +784,10 @@ static int __devinit via686a_device_add(unsigned short address)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc("via686a", address); pdev = platform_device_alloc("via686a", address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static int uch_config = -1; static int uch_config = -1;
...@@ -1259,6 +1260,10 @@ static int __init vt1211_device_add(unsigned short address) ...@@ -1259,6 +1260,10 @@ static int __init vt1211_device_add(unsigned short address)
} }
res.name = pdev->name; res.name = pdev->name;
err = acpi_check_resource_conflict(&res);
if (err)
goto EXIT;
err = platform_device_add_resources(pdev, &res, 1); err = platform_device_add_resources(pdev, &res, 1);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device resource addition failed " printk(KERN_ERR DRVNAME ": Device resource addition failed "
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include <linux/hwmon-vid.h> #include <linux/hwmon-vid.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
static int force_addr; static int force_addr;
...@@ -894,6 +895,10 @@ static int __devinit vt8231_device_add(unsigned short address) ...@@ -894,6 +895,10 @@ static int __devinit vt8231_device_add(unsigned short address)
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc("vt8231", address); pdev = platform_device_alloc("vt8231", address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
#include <linux/hwmon-vid.h> #include <linux/hwmon-vid.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
#include "lm75.h" #include "lm75.h"
...@@ -502,7 +503,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) ...@@ -502,7 +503,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev)
} }
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++) {
/* pwmcfg, tolarance mapped for i=0, i=1 to same reg */ /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */
if (i != 1) { if (i != 1) {
pwmcfg = w83627ehf_read_value(data, pwmcfg = w83627ehf_read_value(data,
W83627EHF_REG_PWM_ENABLE[i]); W83627EHF_REG_PWM_ENABLE[i]);
...@@ -1544,6 +1545,11 @@ static int __init sensors_w83627ehf_init(void) ...@@ -1544,6 +1545,11 @@ static int __init sensors_w83627ehf_init(void)
res.start = address + IOREGION_OFFSET; res.start = address + IOREGION_OFFSET;
res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1;
res.flags = IORESOURCE_IO; res.flags = IORESOURCE_IO;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
err = platform_device_add_resources(pdev, &res, 1); err = platform_device_add_resources(pdev, &res, 1);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device resource addition failed " printk(KERN_ERR DRVNAME ": Device resource addition failed "
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/acpi.h>
#include <asm/io.h> #include <asm/io.h>
#include "lm75.h" #include "lm75.h"
...@@ -1793,6 +1794,10 @@ static int __init w83627hf_device_add(unsigned short address, ...@@ -1793,6 +1794,10 @@ static int __init w83627hf_device_add(unsigned short address,
}; };
int err; int err;
err = acpi_check_resource_conflict(&res);
if (err)
goto exit;
pdev = platform_device_alloc(DRVNAME, address); pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) { if (!pdev) {
err = -ENOMEM; err = -ENOMEM;
......
...@@ -58,7 +58,10 @@ static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, ...@@ -58,7 +58,10 @@ static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
0x2e, 0x2f, I2C_CLIENT_END }; 0x2e, 0x2f, I2C_CLIENT_END };
/* Insmod parameters */ /* Insmod parameters */
I2C_CLIENT_INSMOD_4(w83781d, w83782d, w83783s, as99127f); I2C_CLIENT_INSMOD_4(w83781d, w83782d, w83783s, as99127f);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
static unsigned short force_subclients[4];
module_param_array(force_subclients, short, NULL, 0);
MODULE_PARM_DESC(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}"); "{bus, clientaddr, subclientaddr1, subclientaddr2}");
static int reset; static int reset;
......
...@@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, ...@@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f,
/* Insmod parameters */ /* Insmod parameters */
I2C_CLIENT_INSMOD_1(w83791d); I2C_CLIENT_INSMOD_1(w83791d);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
static unsigned short force_subclients[4];
module_param_array(force_subclients, short, NULL, 0);
MODULE_PARM_DESC(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}"); "{bus, clientaddr, subclientaddr1, subclientaddr2}");
static int reset; static int reset;
......
...@@ -51,7 +51,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, ...@@ -51,7 +51,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f,
/* Insmod parameters */ /* Insmod parameters */
I2C_CLIENT_INSMOD_1(w83792d); I2C_CLIENT_INSMOD_1(w83792d);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
static unsigned short force_subclients[4];
module_param_array(force_subclients, short, NULL, 0);
MODULE_PARM_DESC(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}"); "{bus, clientaddr, subclientaddr1, subclientaddr2}");
static int init; static int init;
......
...@@ -42,7 +42,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, ...@@ -42,7 +42,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f,
/* Insmod parameters */ /* Insmod parameters */
I2C_CLIENT_INSMOD_1(w83793); I2C_CLIENT_INSMOD_1(w83793);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
static unsigned short force_subclients[4];
module_param_array(force_subclients, short, NULL, 0);
MODULE_PARM_DESC(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}"); "{bus, clientaddr, subclientaddr1, subclientaddr2}");
static int reset; static int reset;
......
/* /*
* spi_lm70llp.c - driver for lm70llp eval board for the LM70 sensor * spi_lm70llp.c - driver for LM70EVAL-LLP board for the LM70 sensor
* *
* Copyright (C) 2006 Kaiwan N Billimoria <kaiwan@designergraphix.com> * Copyright (C) 2006 Kaiwan N Billimoria <kaiwan@designergraphix.com>
* *
...@@ -40,8 +40,12 @@ ...@@ -40,8 +40,12 @@
* master controller driver. The hwmon/lm70 driver is a "SPI protocol * master controller driver. The hwmon/lm70 driver is a "SPI protocol
* driver", layered on top of this one and usable without the lm70llp. * driver", layered on top of this one and usable without the lm70llp.
* *
* Datasheet and Schematic:
* The LM70 is a temperature sensor chip from National Semiconductor; its * The LM70 is a temperature sensor chip from National Semiconductor; its
* datasheet is available at http://www.national.com/pf/LM/LM70.html * datasheet is available at http://www.national.com/pf/LM/LM70.html
* The schematic for this particular board (the LM70EVAL-LLP) is
* available (on page 4) here:
* http://www.national.com/appinfo/tempsensors/files/LM70LLPEVALmanual.pdf
* *
* Also see Documentation/spi/spi-lm70llp. The SPI<->parport code here is * Also see Documentation/spi/spi-lm70llp. The SPI<->parport code here is
* (heavily) based on spi-butterfly by David Brownell. * (heavily) based on spi-butterfly by David Brownell.
...@@ -64,7 +68,7 @@ ...@@ -64,7 +68,7 @@
* *
* Note that parport pin 13 actually gets inverted by the transistor * Note that parport pin 13 actually gets inverted by the transistor
* arrangement which lets either the parport or the LM70 drive the * arrangement which lets either the parport or the LM70 drive the
* SI/SO signal. * SI/SO signal (see the schematic for details).
*/ */
#define DRVNAME "spi-lm70llp" #define DRVNAME "spi-lm70llp"
...@@ -106,12 +110,16 @@ static inline struct spi_lm70llp *spidev_to_pp(struct spi_device *spi) ...@@ -106,12 +110,16 @@ static inline struct spi_lm70llp *spidev_to_pp(struct spi_device *spi)
static inline void deassertCS(struct spi_lm70llp *pp) static inline void deassertCS(struct spi_lm70llp *pp)
{ {
u8 data = parport_read_data(pp->port); u8 data = parport_read_data(pp->port);
data &= ~0x80; /* pull D7/SI-out low while de-asserted */
parport_write_data(pp->port, data | nCS); parport_write_data(pp->port, data | nCS);
} }
static inline void assertCS(struct spi_lm70llp *pp) static inline void assertCS(struct spi_lm70llp *pp)
{ {
u8 data = parport_read_data(pp->port); u8 data = parport_read_data(pp->port);
data |= 0x80; /* pull D7/SI-out high so lm70 drives SO-in */
parport_write_data(pp->port, data & ~nCS); parport_write_data(pp->port, data & ~nCS);
} }
...@@ -184,22 +192,7 @@ static void lm70_chipselect(struct spi_device *spi, int value) ...@@ -184,22 +192,7 @@ static void lm70_chipselect(struct spi_device *spi, int value)
*/ */
static u32 lm70_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits) static u32 lm70_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
{ {
static u32 sio=0; return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
static int first_time=1;
/* First time: perform SPI bitbang and return the LSB of
* the result of the SPI call.
*/
if (first_time) {
sio = bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits);
first_time=0;
return (sio & 0x00ff);
}
/* Return the MSB of the result of the SPI call */
else {
first_time=1;
return (sio >> 8);
}
} }
static void spi_lm70llp_attach(struct parport *p) static void spi_lm70llp_attach(struct parport *p)
...@@ -293,10 +286,9 @@ static void spi_lm70llp_attach(struct parport *p) ...@@ -293,10 +286,9 @@ static void spi_lm70llp_attach(struct parport *p)
status = -ENODEV; status = -ENODEV;
goto out_bitbang_stop; goto out_bitbang_stop;
} }
pp->spidev_lm70->bits_per_word = 16; pp->spidev_lm70->bits_per_word = 8;
lm70llp = pp; lm70llp = pp;
return; return;
out_bitbang_stop: out_bitbang_stop:
...@@ -326,7 +318,6 @@ static void spi_lm70llp_detach(struct parport *p) ...@@ -326,7 +318,6 @@ static void spi_lm70llp_detach(struct parport *p)
/* power down */ /* power down */
parport_write_data(pp->port, 0); parport_write_data(pp->port, 0);
msleep(10);
parport_release(pp->pd); parport_release(pp->pd);
parport_unregister_device(pp->pd); parport_unregister_device(pp->pd);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment