Commit bbc7610c 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:
  hwmon: Add MAINTAINERS entry for new ams driver
  hwmon: New AMS hardware monitoring driver
  hwmon/w83793: Add documentation and maintainer
  hwmon: New Winbond W83793 hardware monitoring driver
  hwmon: Update Rudolf Marek's e-mail address
  hwmon/f71805f: Fix the device address decoding
  hwmon/f71805f: Always create all fan inputs
  hwmon/f71805f: Add support for the Fintek F71872F/FG chip
  hwmon: New PC87427 hardware monitoring driver
  hwmon/it87: Remove the SMBus interface support
  hwmon/hdaps: Update the list of supported devices
  hwmon/hdaps: Move the DMI detection data to .data
  hwmon/pc87360: Autodetect the VRM version
  hwmon/f71805f: Document the fan control features
  hwmon/f71805f: Add support for "speed mode" fan speed control
  hwmon/f71805f: Support DC fan speed control mode
  hwmon/f71805f: Let the user adjust the PWM base frequency
  hwmon/f71805f: Add manual fan speed control
  hwmon/f71805f: Store the fan control registers
parents 5cbded58 284f42b6
...@@ -151,15 +151,6 @@ Who: Thomas Gleixner <tglx@linutronix.de> ...@@ -151,15 +151,6 @@ Who: Thomas Gleixner <tglx@linutronix.de>
--------------------------- ---------------------------
What: I2C interface of the it87 driver
When: January 2007
Why: The ISA interface is faster and should be always available. The I2C
probing is also known to cause trouble in at least one case (see
bug #5889.)
Who: Jean Delvare <khali@linux-fr.org>
---------------------------
What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports
(temporary transition config option provided until then) (temporary transition config option provided until then)
The transition config option will also be removed at the same time. The transition config option will also be removed at the same time.
......
...@@ -6,6 +6,10 @@ Supported chips: ...@@ -6,6 +6,10 @@ Supported chips:
Prefix: 'f71805f' Prefix: 'f71805f'
Addresses scanned: none, address read from Super I/O config space Addresses scanned: none, address read from Super I/O config space
Datasheet: Provided by Fintek on request Datasheet: Provided by Fintek on request
* Fintek F71872F/FG
Prefix: 'f71872f'
Addresses scanned: none, address read from Super I/O config space
Datasheet: Provided by Fintek on request
Author: Jean Delvare <khali@linux-fr.org> Author: Jean Delvare <khali@linux-fr.org>
...@@ -13,8 +17,8 @@ Thanks to Denis Kieft from Barracuda Networks for the donation of a ...@@ -13,8 +17,8 @@ Thanks to Denis Kieft from Barracuda Networks for the donation of a
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
for providing initial documentation. for providing initial documentation.
Thanks to Kris Chen from Fintek for answering technical questions and Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
providing additional documentation. questions and providing additional documentation.
Thanks to Chris Lin from Jetway for providing wiring schematics and Thanks to Chris Lin from Jetway for providing wiring schematics and
answering technical questions. answering technical questions.
...@@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power ...@@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power
source), 3 fans and 3 temperature sensors. source), 3 fans and 3 temperature sensors.
This chip also has fan controlling features, using either DC or PWM, in This chip also has fan controlling features, using either DC or PWM, in
three different modes (one manual, two automatic). The driver doesn't three different modes (one manual, two automatic).
support these features yet.
The Fintek F71872F/FG Super I/O chip is almost the same, with two
additional internal voltages monitored (VSB and battery). It also features
6 VID inputs. The VID inputs are not yet supported by this driver.
The driver assumes that no more than one chip is present, which seems The driver assumes that no more than one chip is present, which seems
reasonable. reasonable.
...@@ -42,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported ...@@ -42,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
range is thus from 0 to 2.040 V. Voltage values outside of this range range is thus from 0 to 2.040 V. Voltage values outside of this range
need external resistors. An exception is in0, which is used to monitor need external resistors. An exception is in0, which is used to monitor
the chip's own power source (+3.3V), and is divided internally by a the chip's own power source (+3.3V), and is divided internally by a
factor 2. factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
divided internally by a factor 2.
The two LSB of the voltage limit registers are not used (always 0), so The two LSB of the voltage limit registers are not used (always 0), so
you can only set the limits in steps of 32 mV (before scaling). you can only set the limits in steps of 32 mV (before scaling).
...@@ -61,9 +69,12 @@ in5 VIN5 +12V 200K 20K 11.00 1.05 V ...@@ -61,9 +69,12 @@ in5 VIN5 +12V 200K 20K 11.00 1.05 V
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1) in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
in10 VSB VSB3.3V int. int. 2.00 1.65 V (3)
in9 VBAT VBATTERY int. int. 2.00 1.50 V (3)
(1) Depends on your hardware setup. (1) Depends on your hardware setup.
(2) Obviously not correct, swapping R1 and R2 would make more sense. (2) Obviously not correct, swapping R1 and R2 would make more sense.
(3) F71872F/FG only.
These values can be used as hints at best, as motherboard manufacturers These values can be used as hints at best, as motherboard manufacturers
are free to use a completely different setup. As a matter of fact, the are free to use a completely different setup. As a matter of fact, the
...@@ -103,3 +114,38 @@ sensor. Each channel can be used for connecting either a thermal diode ...@@ -103,3 +114,38 @@ sensor. Each channel can be used for connecting either a thermal diode
or a thermistor. The driver reports the currently selected mode, but or a thermistor. The driver reports the currently selected mode, but
doesn't allow changing it. In theory, the BIOS should have configured doesn't allow changing it. In theory, the BIOS should have configured
everything properly. everything properly.
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. The driver will report the method, but won't let you change
it.
When the PWM method is used, you can select the operating frequency,
from 187.5 kHz (default) to 31 Hz. The best frequency depends on the
fan model. As a rule of thumb, lower frequencies seem to give better
control, but may generate annoying high-pitch noise. Fintek recommends
not going below 1 kHz, as the fan tachometers get confused by lower
frequencies as well.
When the DC method is used, Fintek recommends not going below 5 V, which
corresponds to a pwm value of 106 for the driver. The driver doesn't
enforce this limit though.
Three different fan control modes are supported:
* Manual mode
You ask for a specific PWM duty cycle or DC voltage.
* Fan speed mode
You ask for a specific fan speed. This mode assumes that pwm1
corresponds to fan1, pwm2 to fan2 and pwm3 to fan3.
* Temperature mode
You define 3 temperature/fan speed trip points, and the fan speed is
adjusted depending on the measured temperature, using interpolation.
This mode is not yet supported by the driver.
...@@ -9,8 +9,7 @@ Supported chips: ...@@ -9,8 +9,7 @@ Supported chips:
http://www.ite.com.tw/ http://www.ite.com.tw/
* IT8712F * IT8712F
Prefix: 'it8712' Prefix: 'it8712'
Addresses scanned: I2C 0x2d Addresses scanned: from Super I/O config space (8 I/O ports)
from Super I/O config space (8 I/O ports)
Datasheet: Publicly available at the ITE website Datasheet: Publicly available at the ITE website
http://www.ite.com.tw/ http://www.ite.com.tw/
* IT8716F * IT8716F
...@@ -53,6 +52,18 @@ Module Parameters ...@@ -53,6 +52,18 @@ Module Parameters
misconfigured by BIOS - PWM values would be inverted. This option tries misconfigured by BIOS - PWM values would be inverted. This option tries
to fix this. Please contact your BIOS manufacturer and ask him for fix. to fix this. Please contact your BIOS manufacturer and ask him for fix.
Hardware Interfaces
-------------------
All the chips suported by this driver are LPC Super-I/O chips, accessed
through the LPC bus (ISA-like I/O ports). The IT8712F additionally has an
SMBus interface to the hardware monitoring functions. This driver no
longer supports this interface though, as it is slower and less reliable
than the ISA access, and was only available on a small number of
motherboard models.
Description Description
----------- -----------
......
...@@ -8,7 +8,7 @@ Supported chips: ...@@ -8,7 +8,7 @@ Supported chips:
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
Author: Rudolf Marek Author: Rudolf Marek
Contact: Rudolf Marek <r.marek@sh.cvut.cz> Contact: Rudolf Marek <r.marek@assembler.cz>
Description Description
----------- -----------
......
Kernel driver pc87427
=====================
Supported chips:
* National Semiconductor PC87427
Prefix: 'pc87427'
Addresses scanned: none, address read from Super I/O config space
Datasheet: http://www.winbond.com.tw/E-WINBONDHTM/partner/apc_007.html
Author: Jean Delvare <khali@linux-fr.org>
Thanks to Amir Habibi at Candelis for setting up a test system, and to
Michael Kress for testing several iterations of this driver.
Description
-----------
The National Semiconductor Super I/O chip includes complete hardware
monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
6 temperature sensors. Only the fans are supported at the moment.
This chip also has fan controlling features, which are not yet supported
by this driver either.
The driver assumes that no more than one chip is present, which seems
reasonable.
Fan Monitoring
--------------
Fan rotation speeds are reported as 14-bit values from a gated clock
signal. Speeds down to 83 RPM can be measured.
An alarm is triggered if the rotation speed drops below a programmable
limit. Another alarm is triggered if the speed is too low to to be measured
(including stalled or missing fan).
...@@ -208,12 +208,14 @@ temp[1-*]_auto_point[1-*]_temp_hyst ...@@ -208,12 +208,14 @@ temp[1-*]_auto_point[1-*]_temp_hyst
**************** ****************
temp[1-*]_type Sensor type selection. temp[1-*]_type Sensor type selection.
Integers 1 to 4 or thermistor Beta value (typically 3435) Integers 1 to 6 or thermistor Beta value (typically 3435)
RW RW
1: PII/Celeron Diode 1: PII/Celeron Diode
2: 3904 transistor 2: 3904 transistor
3: thermal diode 3: thermal diode
4: thermistor (default/unknown Beta) 4: thermistor (default/unknown Beta)
5: AMD AMDSI
6: Intel PECI
Not all types are supported by all chips Not all types are supported by all chips
temp[1-*]_max Temperature max value. temp[1-*]_max Temperature max value.
......
...@@ -10,7 +10,7 @@ Supported chips: ...@@ -10,7 +10,7 @@ Supported chips:
Authors: Authors:
Jean Delvare <khali@linux-fr.org> Jean Delvare <khali@linux-fr.org>
Yuan Mu (Winbond) Yuan Mu (Winbond)
Rudolf Marek <r.marek@sh.cvut.cz> Rudolf Marek <r.marek@assembler.cz>
Description Description
----------- -----------
......
...@@ -18,7 +18,7 @@ Credits: ...@@ -18,7 +18,7 @@ Credits:
and Mark Studebaker <mdsxyz123@yahoo.com> and Mark Studebaker <mdsxyz123@yahoo.com>
w83792d.c: w83792d.c:
Chunhao Huang <DZShen@Winbond.com.tw>, Chunhao Huang <DZShen@Winbond.com.tw>,
Rudolf Marek <r.marek@sh.cvut.cz> Rudolf Marek <r.marek@assembler.cz>
Additional contributors: Additional contributors:
Sven Anders <anders@anduras.de> Sven Anders <anders@anduras.de>
......
Kernel driver w83793
====================
Supported chips:
* Winbond W83793G/W83793R
Prefix: 'w83793'
Addresses scanned: I2C 0x2c - 0x2f
Datasheet: Still not published
Authors:
Yuan Mu (Winbond Electronics)
Rudolf Marek <r.marek@assembler.cz>
Module parameters
-----------------
* reset int
(default 0)
This parameter is not recommended, it will lose motherboard specific
settings. Use 'reset=1' to reset the chip when loading this module.
* force_subclients=bus,caddr,saddr1,saddr2
This is used to force the i2c addresses for subclients of
a certain chip. Typical usage is `force_subclients=0,0x2f,0x4a,0x4b'
to force the subclients of chip 0x2f on bus 0 to i2c addresses
0x4a and 0x4b.
Description
-----------
This driver implements support for Winbond W83793G/W83793R chips.
* Exported features
This driver exports 10 voltage sensors, up to 12 fan tachometer inputs,
6 remote temperatures, up to 8 sets of PWM fan controls, SmartFan
(automatic fan speed control) on all temperature/PWM combinations, 2
sets of 6-pin CPU VID input.
* Sensor resolutions
If your motherboard maker used the reference design, the resolution of
voltage0-2 is 2mV, resolution of voltage3/4/5 is 16mV, 8mV for voltage6,
24mV for voltage7/8. Temp1-4 have a 0.25 degree Celsius resolution,
temp5-6 have a 1 degree Celsiis resolution.
* Temperature sensor types
Temp1-4 have 3 possible types. It can be read from (and written to)
temp[1-4]_type.
- If the value of 0, the related temperature channel stops
monitoring.
- If the value is 3, it starts monitoring using a remote termal diode
(default).
- If the value is 5, it starts monitoring using the temperature sensor
in AMD CPU and get result by AMDSI.
- If the value is 6, it starts monitoring using the temperature sensor
in Intel CPU and get result by PECI.
Temp5-6 can be connected to external thermistors (value of
temp[5-6]_type is 4). They can also be disabled (value is 0).
* Alarm mechanism
For voltage sensors, an alarm triggers if the measured value is below
the low voltage limit or over the high voltage limit.
For temperature sensors, an alarm triggers if the measured value goes
above the high temperature limit, and wears off only after the measured
value drops below the hysteresis value.
For fan sensors, an alarm triggers if the measured value is below the
low speed limit.
* SmartFan/PWM control
If you want to set a pwm fan to manual mode, you just need to make sure it
is not controlled by any temp channel, for example, you want to set fan1
to manual mode, you need to check the value of temp[1-6]_fan_map, make
sure bit 0 is cleared in the 6 values. And then set the pwm1 value to
control the fan.
Each temperature channel can control all the 8 PWM outputs (by setting the
corresponding bit in tempX_fan_map), you can set the temperature channel
mode using temp[1-6]_pwm_enable, 2 is Thermal Cruise mode and 3
is the SmartFanII mode. Temperature channels will try to speed up or
slow down all controlled fans, this means one fan can receive different
PWM value requests from different temperature channels, but the chip
will always pick the safest (max) PWM value for each fan.
In Thermal Cruise mode, the chip attempts to keep the temperature at a
predefined value, within a tolerance margin. So if tempX_input >
thermal_cruiseX + toleranceX, the chip will increase the PWM value,
if tempX_input < thermal_cruiseX - toleranceX, the chip will decrease
the PWM value. If the temperature is within the tolerance range, the PWM
value is left unchanged.
SmartFanII works differently, you have to define up to 7 PWM, temperature
trip points, defining a PWM/temperature curve which the chip will follow.
While not fundamentally different from the Thermal Cruise mode, the
implementation is quite different, giving you a finer-grained control.
* Chassis
If the case open alarm triggers, it will stay in this state unless cleared
by any write to the sysfs file "chassis".
* VID and VRM
The VRM version is detected automatically, don't modify the it unless you
*do* know the cpu VRM version and it's not properly detected.
Notes
-----
Only Fan1-5 and PWM1-3 are guaranteed to always exist, other fan inputs and
PWM outputs may or may not exist depending on the chip pin configuration.
...@@ -277,7 +277,7 @@ S: Maintained ...@@ -277,7 +277,7 @@ S: Maintained
ALI1563 I2C DRIVER ALI1563 I2C DRIVER
P: Rudolf Marek P: Rudolf Marek
M: r.marek@sh.cvut.cz M: r.marek@assembler.cz
L: i2c@lm-sensors.org L: i2c@lm-sensors.org
S: Maintained S: Maintained
...@@ -296,6 +296,13 @@ L: info-linux@geode.amd.com ...@@ -296,6 +296,13 @@ L: info-linux@geode.amd.com
W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html
S: Supported S: Supported
AMS (Apple Motion Sensor) DRIVER
P: Stelian Pop
M: stelian@popies.net
P: Michael Hanselmann
M: linux-kernel@hansmi.ch
S: Supported
AMSO1100 RNIC DRIVER AMSO1100 RNIC DRIVER
P: Tom Tucker P: Tom Tucker
M: tom@opengridcomputing.com M: tom@opengridcomputing.com
...@@ -3436,6 +3443,12 @@ M: bezaur@gmail.com ...@@ -3436,6 +3443,12 @@ M: bezaur@gmail.com
L: lm-sensors@lm-sensors.org L: lm-sensors@lm-sensors.org
S: Maintained S: Maintained
W83793 HARDWARE MONITORING DRIVER
P: Rudolf Marek
M: r.marek@assembler.cz
L: lm-sensors@lm-sensors.org
S: Maintained
W83L51xD SD/MMC CARD INTERFACE DRIVER W83L51xD SD/MMC CARD INTERFACE DRIVER
P: Pierre Ossman P: Pierre Ossman
M: drzeus-wbsd@drzeus.cx M: drzeus-wbsd@drzeus.cx
......
...@@ -106,6 +106,31 @@ config SENSORS_K8TEMP ...@@ -106,6 +106,31 @@ config SENSORS_K8TEMP
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 k8temp. will be called k8temp.
config SENSORS_AMS
tristate "Apple Motion Sensor driver"
depends on HWMON && PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL
help
Support for the motion sensor included in PowerBooks. Includes
implementations for PMU and I2C.
This driver can also be built as a module. If so, the module
will be called ams.
config SENSORS_AMS_PMU
bool "PMU variant"
depends on SENSORS_AMS && ADB_PMU
default y
help
PMU variant of motion sensor, found in late 2005 PowerBooks.
config SENSORS_AMS_I2C
bool "I2C variant"
depends on SENSORS_AMS && I2C
default y
help
I2C variant of motion sensor, found in early 2005 PowerBooks and
iBooks.
config SENSORS_ASB100 config SENSORS_ASB100
tristate "Asus ASB100 Bach" tristate "Asus ASB100 Bach"
depends on HWMON && I2C && EXPERIMENTAL depends on HWMON && I2C && EXPERIMENTAL
...@@ -142,11 +167,12 @@ config SENSORS_DS1621 ...@@ -142,11 +167,12 @@ config SENSORS_DS1621
will be called ds1621. will be called ds1621.
config SENSORS_F71805F config SENSORS_F71805F
tristate "Fintek F71805F/FG" tristate "Fintek F71805F/FG and F71872F/FG"
depends on HWMON && EXPERIMENTAL depends on HWMON && 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 F71805F/FG chips. features of the Fintek F71805F/FG and F71872F/FG 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 f71805f. will be called f71805f.
...@@ -353,6 +379,19 @@ config SENSORS_PC87360 ...@@ -353,6 +379,19 @@ config SENSORS_PC87360
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 pc87360. will be called pc87360.
config SENSORS_PC87427
tristate "National Semiconductor PC87427"
depends on HWMON && EXPERIMENTAL
help
If you say yes here you get access to the hardware monitoring
functions of the National Semiconductor PC87427 Super-I/O chip.
The chip has two distinct logical devices, one for fan speed
monitoring and control, and one for voltage and temperature
monitoring. Only fan speed monitoring is supported right now.
This driver can also be built as a module. If so, the module
will be called pc87427.
config SENSORS_SIS5595 config SENSORS_SIS5595
tristate "Silicon Integrated Systems Corp. SiS5595" tristate "Silicon Integrated Systems Corp. SiS5595"
depends on HWMON && I2C && PCI && EXPERIMENTAL depends on HWMON && I2C && PCI && EXPERIMENTAL
...@@ -474,6 +513,16 @@ config SENSORS_W83792D ...@@ -474,6 +513,16 @@ config SENSORS_W83792D
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 w83792d. will be called w83792d.
config SENSORS_W83793
tristate "Winbond W83793"
depends on HWMON && I2C && EXPERIMENTAL
help
If you say yes here you get support for the Winbond W83793
hardware monitoring chip.
This driver can also be built as a module. If so, the module
will be called w83793.
config SENSORS_W83L785TS config SENSORS_W83L785TS
tristate "Winbond W83L785TS-S" tristate "Winbond W83L785TS-S"
depends on HWMON && I2C && EXPERIMENTAL depends on HWMON && I2C && EXPERIMENTAL
...@@ -527,6 +576,9 @@ config SENSORS_HDAPS ...@@ -527,6 +576,9 @@ config SENSORS_HDAPS
This driver also provides an absolute input class device, allowing This driver also provides an absolute input class device, allowing
the laptop to act as a pinball machine-esque joystick. the laptop to act as a pinball machine-esque joystick.
If your ThinkPad is not recognized by the driver, please update to latest
BIOS. This is especially the case for some R52 ThinkPads.
Say Y here if you have an applicable laptop and want to experience Say Y here if you have an applicable laptop and want to experience
the awesome power of hdaps. the awesome power of hdaps.
......
...@@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o ...@@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
obj-$(CONFIG_SENSORS_ASB100) += asb100.o obj-$(CONFIG_SENSORS_ASB100) += asb100.o
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
obj-$(CONFIG_SENSORS_W83793) += w83793.o
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
...@@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o ...@@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
obj-$(CONFIG_SENSORS_AMS) += ams/
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
...@@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o ...@@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM92) += lm92.o
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
......
#
# Makefile for Apple Motion Sensor driver
#
ams-y := ams-core.o ams-input.o
ams-$(CONFIG_SENSORS_AMS_PMU) += ams-pmu.o
ams-$(CONFIG_SENSORS_AMS_I2C) += ams-i2c.o
obj-$(CONFIG_SENSORS_AMS) += ams.o
/*
* Apple Motion Sensor driver
*
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>
#include <asm/pmac_pfunc.h>
#include <asm/of_platform.h>
#include "ams.h"
/* There is only one motion sensor per machine */
struct ams ams_info;
static unsigned int verbose;
module_param(verbose, bool, 0644);
MODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output");
/* Call with ams_info.lock held! */
void ams_sensors(s8 *x, s8 *y, s8 *z)
{
u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2;
if (orient & 0x80)
/* X and Y swapped */
ams_info.get_xyz(y, x, z);
else
ams_info.get_xyz(x, y, z);
if (orient & 0x04)
*z = ~(*z);
if (orient & 0x02)
*y = ~(*y);
if (orient & 0x01)
*x = ~(*x);
}
static ssize_t ams_show_current(struct device *dev,
struct device_attribute *attr, char *buf)
{
s8 x, y, z;
mutex_lock(&ams_info.lock);
ams_sensors(&x, &y, &z);
mutex_unlock(&ams_info.lock);
return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z);
}
static DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL);
static void ams_handle_irq(void *data)
{
enum ams_irq irq = *((enum ams_irq *)data);
spin_lock(&ams_info.irq_lock);
ams_info.worker_irqs |= irq;
schedule_work(&ams_info.worker);
spin_unlock(&ams_info.irq_lock);
}
static enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL;
static struct pmf_irq_client ams_freefall_client = {
.owner = THIS_MODULE,
.handler = ams_handle_irq,
.data = &ams_freefall_irq_data,
};
static enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK;
static struct pmf_irq_client ams_shock_client = {
.owner = THIS_MODULE,
.handler = ams_handle_irq,
.data = &ams_shock_irq_data,
};
/* Once hard disk parking is implemented in the kernel, this function can
* trigger it.
*/
static void ams_worker(struct work_struct *work)
{
mutex_lock(&ams_info.lock);
if (ams_info.has_device) {
unsigned long flags;
spin_lock_irqsave(&ams_info.irq_lock, flags);
if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) {
if (verbose)
printk(KERN_INFO "ams: freefall detected!\n");
ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL;
/* we must call this with interrupts enabled */
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
ams_info.clear_irq(AMS_IRQ_FREEFALL);
spin_lock_irqsave(&ams_info.irq_lock, flags);
}
if (ams_info.worker_irqs & AMS_IRQ_SHOCK) {
if (verbose)
printk(KERN_INFO "ams: shock detected!\n");
ams_info.worker_irqs &= ~AMS_IRQ_SHOCK;
/* we must call this with interrupts enabled */
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
ams_info.clear_irq(AMS_IRQ_SHOCK);
spin_lock_irqsave(&ams_info.irq_lock, flags);
}
spin_unlock_irqrestore(&ams_info.irq_lock, flags);
}
mutex_unlock(&ams_info.lock);
}
/* Call with ams_info.lock held! */
int ams_sensor_attach(void)
{
int result;
u32 *prop;
/* Get orientation */
prop = (u32*)get_property(ams_info.of_node, "orientation", NULL);
if (!prop)
return -ENODEV;
ams_info.orient1 = *prop;
ams_info.orient2 = *(prop + 1);
/* Register freefall interrupt handler */
result = pmf_register_irq_client(ams_info.of_node,
"accel-int-1",
&ams_freefall_client);
if (result < 0)
return -ENODEV;
/* Reset saved irqs */
ams_info.worker_irqs = 0;
/* Register shock interrupt handler */
result = pmf_register_irq_client(ams_info.of_node,
"accel-int-2",
&ams_shock_client);
if (result < 0)
goto release_freefall;
/* Create device */
ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL);
if (!ams_info.of_dev) {
result = -ENODEV;
goto release_shock;
}
/* Create attributes */
result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current);
if (result)
goto release_of;
ams_info.vflag = !!(ams_info.get_vendor() & 0x10);
/* Init input device */
result = ams_input_init();
if (result)
goto release_device_file;
return result;
release_device_file:
device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
release_of:
of_device_unregister(ams_info.of_dev);
release_shock:
pmf_unregister_irq_client(&ams_shock_client);
release_freefall:
pmf_unregister_irq_client(&ams_freefall_client);
return result;
}
int __init ams_init(void)
{
struct device_node *np;
spin_lock_init(&ams_info.irq_lock);
mutex_init(&ams_info.lock);
INIT_WORK(&ams_info.worker, ams_worker);
#ifdef CONFIG_SENSORS_AMS_I2C
np = of_find_node_by_name(NULL, "accelerometer");
if (np && device_is_compatible(np, "AAPL,accelerometer_1"))
/* Found I2C motion sensor */
return ams_i2c_init(np);
#endif
#ifdef CONFIG_SENSORS_AMS_PMU
np = of_find_node_by_name(NULL, "sms");
if (np && device_is_compatible(np, "sms"))
/* Found PMU motion sensor */
return ams_pmu_init(np);
#endif
printk(KERN_ERR "ams: No motion sensor found.\n");
return -ENODEV;
}
void ams_exit(void)
{
mutex_lock(&ams_info.lock);
if (ams_info.has_device) {
/* Remove input device */
ams_input_exit();
/* Shut down implementation */
ams_info.exit();
/* Flush interrupt worker
*
* We do this after ams_info.exit(), because an interrupt might
* have arrived before disabling them.
*/
flush_scheduled_work();
/* Remove attributes */
device_remove_file(&ams_info.of_dev->dev, &dev_attr_current);
/* Remove device */
of_device_unregister(ams_info.of_dev);
/* Remove handler */
pmf_unregister_irq_client(&ams_shock_client);
pmf_unregister_irq_client(&ams_freefall_client);
}
mutex_unlock(&ams_info.lock);
}
MODULE_AUTHOR("Stelian Pop, Michael Hanselmann");
MODULE_DESCRIPTION("Apple Motion Sensor driver");
MODULE_LICENSE("GPL");
module_init(ams_init);
module_exit(ams_exit);
/*
* Apple Motion Sensor driver (I2C variant)
*
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
*
* Clean room implementation based on the reverse engineered Mac OS X driver by
* Johannes Berg <johannes@sipsolutions.net>, documentation available at
* http://johannes.sipsolutions.net/PowerBook/Apple_Motion_Sensor_Specification
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/delay.h>
#include "ams.h"
/* AMS registers */
#define AMS_COMMAND 0x00 /* command register */
#define AMS_STATUS 0x01 /* status register */
#define AMS_CTRL1 0x02 /* read control 1 (number of values) */
#define AMS_CTRL2 0x03 /* read control 2 (offset?) */
#define AMS_CTRL3 0x04 /* read control 3 (size of each value?) */
#define AMS_DATA1 0x05 /* read data 1 */
#define AMS_DATA2 0x06 /* read data 2 */
#define AMS_DATA3 0x07 /* read data 3 */
#define AMS_DATA4 0x08 /* read data 4 */
#define AMS_DATAX 0x20 /* data X */
#define AMS_DATAY 0x21 /* data Y */
#define AMS_DATAZ 0x22 /* data Z */
#define AMS_FREEFALL 0x24 /* freefall int control */
#define AMS_SHOCK 0x25 /* shock int control */
#define AMS_SENSLOW 0x26 /* sensitivity low limit */
#define AMS_SENSHIGH 0x27 /* sensitivity high limit */
#define AMS_CTRLX 0x28 /* control X */
#define AMS_CTRLY 0x29 /* control Y */
#define AMS_CTRLZ 0x2A /* control Z */
#define AMS_UNKNOWN1 0x2B /* unknown 1 */
#define AMS_UNKNOWN2 0x2C /* unknown 2 */
#define AMS_UNKNOWN3 0x2D /* unknown 3 */
#define AMS_VENDOR 0x2E /* vendor */
/* AMS commands - use with the AMS_COMMAND register */
enum ams_i2c_cmd {
AMS_CMD_NOOP = 0,
AMS_CMD_VERSION,
AMS_CMD_READMEM,
AMS_CMD_WRITEMEM,
AMS_CMD_ERASEMEM,
AMS_CMD_READEE,
AMS_CMD_WRITEEE,
AMS_CMD_RESET,
AMS_CMD_START,
};
static int ams_i2c_attach(struct i2c_adapter *adapter);
static int ams_i2c_detach(struct i2c_adapter *adapter);
static struct i2c_driver ams_i2c_driver = {
.driver = {
.name = "ams",
.owner = THIS_MODULE,
},
.attach_adapter = ams_i2c_attach,
.detach_adapter = ams_i2c_detach,
};
static s32 ams_i2c_read(u8 reg)
{
return i2c_smbus_read_byte_data(&ams_info.i2c_client, reg);
}
static int ams_i2c_write(u8 reg, u8 value)
{
return i2c_smbus_write_byte_data(&ams_info.i2c_client, reg, value);
}
static int ams_i2c_cmd(enum ams_i2c_cmd cmd)
{
s32 result;
int remaining = HZ / 20;
ams_i2c_write(AMS_COMMAND, cmd);
mdelay(5);
while (remaining) {
result = ams_i2c_read(AMS_COMMAND);
if (result == 0 || result & 0x80)
return 0;
remaining = schedule_timeout(remaining);
}
return -1;
}
static void ams_i2c_set_irq(enum ams_irq reg, char enable)
{
if (reg & AMS_IRQ_FREEFALL) {
u8 val = ams_i2c_read(AMS_CTRLX);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_i2c_write(AMS_CTRLX, val);
}
if (reg & AMS_IRQ_SHOCK) {
u8 val = ams_i2c_read(AMS_CTRLY);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_i2c_write(AMS_CTRLY, val);
}
if (reg & AMS_IRQ_GLOBAL) {
u8 val = ams_i2c_read(AMS_CTRLZ);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_i2c_write(AMS_CTRLZ, val);
}
}
static void ams_i2c_clear_irq(enum ams_irq reg)
{
if (reg & AMS_IRQ_FREEFALL)
ams_i2c_write(AMS_FREEFALL, 0);
if (reg & AMS_IRQ_SHOCK)
ams_i2c_write(AMS_SHOCK, 0);
}
static u8 ams_i2c_get_vendor(void)
{
return ams_i2c_read(AMS_VENDOR);
}
static void ams_i2c_get_xyz(s8 *x, s8 *y, s8 *z)
{
*x = ams_i2c_read(AMS_DATAX);
*y = ams_i2c_read(AMS_DATAY);
*z = ams_i2c_read(AMS_DATAZ);
}
static int ams_i2c_attach(struct i2c_adapter *adapter)
{
unsigned long bus;
int vmaj, vmin;
int result;
/* There can be only one */
if (unlikely(ams_info.has_device))
return -ENODEV;
if (strncmp(adapter->name, "uni-n", 5))
return -ENODEV;
bus = simple_strtoul(adapter->name + 6, NULL, 10);
if (bus != ams_info.i2c_bus)
return -ENODEV;
ams_info.i2c_client.addr = ams_info.i2c_address;
ams_info.i2c_client.adapter = adapter;
ams_info.i2c_client.driver = &ams_i2c_driver;
strcpy(ams_info.i2c_client.name, "Apple Motion Sensor");
if (ams_i2c_cmd(AMS_CMD_RESET)) {
printk(KERN_INFO "ams: Failed to reset the device\n");
return -ENODEV;
}
if (ams_i2c_cmd(AMS_CMD_START)) {
printk(KERN_INFO "ams: Failed to start the device\n");
return -ENODEV;
}
/* get version/vendor information */
ams_i2c_write(AMS_CTRL1, 0x02);
ams_i2c_write(AMS_CTRL2, 0x85);
ams_i2c_write(AMS_CTRL3, 0x01);
ams_i2c_cmd(AMS_CMD_READMEM);
vmaj = ams_i2c_read(AMS_DATA1);
vmin = ams_i2c_read(AMS_DATA2);
if (vmaj != 1 || vmin != 52) {
printk(KERN_INFO "ams: Incorrect device version (%d.%d)\n",
vmaj, vmin);
return -ENODEV;
}
ams_i2c_cmd(AMS_CMD_VERSION);
vmaj = ams_i2c_read(AMS_DATA1);
vmin = ams_i2c_read(AMS_DATA2);
if (vmaj != 0 || vmin != 1) {
printk(KERN_INFO "ams: Incorrect firmware version (%d.%d)\n",
vmaj, vmin);
return -ENODEV;
}
/* Disable interrupts */
ams_i2c_set_irq(AMS_IRQ_ALL, 0);
result = ams_sensor_attach();
if (result < 0)
return result;
/* Set default values */
ams_i2c_write(AMS_SENSLOW, 0x15);
ams_i2c_write(AMS_SENSHIGH, 0x60);
ams_i2c_write(AMS_CTRLX, 0x08);
ams_i2c_write(AMS_CTRLY, 0x0F);
ams_i2c_write(AMS_CTRLZ, 0x4F);
ams_i2c_write(AMS_UNKNOWN1, 0x14);
/* Clear interrupts */
ams_i2c_clear_irq(AMS_IRQ_ALL);
ams_info.has_device = 1;
/* Enable interrupts */
ams_i2c_set_irq(AMS_IRQ_ALL, 1);
printk(KERN_INFO "ams: Found I2C based motion sensor\n");
return 0;
}
static int ams_i2c_detach(struct i2c_adapter *adapter)
{
if (ams_info.has_device) {
/* Disable interrupts */
ams_i2c_set_irq(AMS_IRQ_ALL, 0);
/* Clear interrupts */
ams_i2c_clear_irq(AMS_IRQ_ALL);
printk(KERN_INFO "ams: Unloading\n");
ams_info.has_device = 0;
}
return 0;
}
static void ams_i2c_exit(void)
{
i2c_del_driver(&ams_i2c_driver);
}
int __init ams_i2c_init(struct device_node *np)
{
char *tmp_bus;
int result;
u32 *prop;
mutex_lock(&ams_info.lock);
/* Set implementation stuff */
ams_info.of_node = np;
ams_info.exit = ams_i2c_exit;
ams_info.get_vendor = ams_i2c_get_vendor;
ams_info.get_xyz = ams_i2c_get_xyz;
ams_info.clear_irq = ams_i2c_clear_irq;
ams_info.bustype = BUS_I2C;
/* look for bus either using "reg" or by path */
prop = (u32*)get_property(ams_info.of_node, "reg", NULL);
if (!prop) {
result = -ENODEV;
goto exit;
}
tmp_bus = strstr(ams_info.of_node->full_name, "/i2c-bus@");
if (tmp_bus)
ams_info.i2c_bus = *(tmp_bus + 9) - '0';
else
ams_info.i2c_bus = ((*prop) >> 8) & 0x0f;
ams_info.i2c_address = ((*prop) & 0xff) >> 1;
result = i2c_add_driver(&ams_i2c_driver);
exit:
mutex_unlock(&ams_info.lock);
return result;
}
/*
* Apple Motion Sensor driver (joystick emulation)
*
* Copyright (C) 2005 Stelian Pop (stelian@popies.net)
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/delay.h>
#include "ams.h"
static unsigned int joystick;
module_param(joystick, bool, 0644);
MODULE_PARM_DESC(joystick, "Enable the input class device on module load");
static unsigned int invert;
module_param(invert, bool, 0644);
MODULE_PARM_DESC(invert, "Invert input data on X and Y axis");
static int ams_input_kthread(void *data)
{
s8 x, y, z;
while (!kthread_should_stop()) {
mutex_lock(&ams_info.lock);
ams_sensors(&x, &y, &z);
x -= ams_info.xcalib;
y -= ams_info.ycalib;
z -= ams_info.zcalib;
input_report_abs(ams_info.idev, ABS_X, invert ? -x : x);
input_report_abs(ams_info.idev, ABS_Y, invert ? -y : y);
input_report_abs(ams_info.idev, ABS_Z, z);
input_sync(ams_info.idev);
mutex_unlock(&ams_info.lock);
msleep(25);
}
return 0;
}
static int ams_input_open(struct input_dev *dev)
{
ams_info.kthread = kthread_run(ams_input_kthread, NULL, "kams");
return IS_ERR(ams_info.kthread) ? PTR_ERR(ams_info.kthread) : 0;
}
static void ams_input_close(struct input_dev *dev)
{
kthread_stop(ams_info.kthread);
}
/* Call with ams_info.lock held! */
static void ams_input_enable(void)
{
s8 x, y, z;
if (ams_info.idev)
return;
ams_sensors(&x, &y, &z);
ams_info.xcalib = x;
ams_info.ycalib = y;
ams_info.zcalib = z;
ams_info.idev = input_allocate_device();
if (!ams_info.idev)
return;
ams_info.idev->name = "Apple Motion Sensor";
ams_info.idev->id.bustype = ams_info.bustype;
ams_info.idev->id.vendor = 0;
ams_info.idev->open = ams_input_open;
ams_info.idev->close = ams_input_close;
ams_info.idev->cdev.dev = &ams_info.of_dev->dev;
input_set_abs_params(ams_info.idev, ABS_X, -50, 50, 3, 0);
input_set_abs_params(ams_info.idev, ABS_Y, -50, 50, 3, 0);
input_set_abs_params(ams_info.idev, ABS_Z, -50, 50, 3, 0);
set_bit(EV_ABS, ams_info.idev->evbit);
set_bit(EV_KEY, ams_info.idev->evbit);
set_bit(BTN_TOUCH, ams_info.idev->keybit);
if (input_register_device(ams_info.idev)) {
input_free_device(ams_info.idev);
ams_info.idev = NULL;
return;
}
}
/* Call with ams_info.lock held! */
static void ams_input_disable(void)
{
if (ams_info.idev) {
input_unregister_device(ams_info.idev);
ams_info.idev = NULL;
}
}
static ssize_t ams_input_show_joystick(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", joystick);
}
static ssize_t ams_input_store_joystick(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
if (sscanf(buf, "%d\n", &joystick) != 1)
return -EINVAL;
mutex_lock(&ams_info.lock);
if (joystick)
ams_input_enable();
else
ams_input_disable();
mutex_unlock(&ams_info.lock);
return count;
}
static DEVICE_ATTR(joystick, S_IRUGO | S_IWUSR,
ams_input_show_joystick, ams_input_store_joystick);
/* Call with ams_info.lock held! */
int ams_input_init(void)
{
int result;
result = device_create_file(&ams_info.of_dev->dev, &dev_attr_joystick);
if (!result && joystick)
ams_input_enable();
return result;
}
/* Call with ams_info.lock held! */
void ams_input_exit()
{
ams_input_disable();
device_remove_file(&ams_info.of_dev->dev, &dev_attr_joystick);
}
/*
* Apple Motion Sensor driver (PMU variant)
*
* Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include "ams.h"
/* Attitude */
#define AMS_X 0x00
#define AMS_Y 0x01
#define AMS_Z 0x02
/* Not exactly known, maybe chip vendor */
#define AMS_VENDOR 0x03
/* Freefall registers */
#define AMS_FF_CLEAR 0x04
#define AMS_FF_ENABLE 0x05
#define AMS_FF_LOW_LIMIT 0x06
#define AMS_FF_DEBOUNCE 0x07
/* Shock registers */
#define AMS_SHOCK_CLEAR 0x08
#define AMS_SHOCK_ENABLE 0x09
#define AMS_SHOCK_HIGH_LIMIT 0x0a
#define AMS_SHOCK_DEBOUNCE 0x0b
/* Global interrupt and power control register */
#define AMS_CONTROL 0x0c
static u8 ams_pmu_cmd;
static void ams_pmu_req_complete(struct adb_request *req)
{
complete((struct completion *)req->arg);
}
/* Only call this function from task context */
static void ams_pmu_set_register(u8 reg, u8 value)
{
static struct adb_request req;
DECLARE_COMPLETION(req_complete);
req.arg = &req_complete;
if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value))
return;
wait_for_completion(&req_complete);
}
/* Only call this function from task context */
static u8 ams_pmu_get_register(u8 reg)
{
static struct adb_request req;
DECLARE_COMPLETION(req_complete);
req.arg = &req_complete;
if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg))
return 0;
wait_for_completion(&req_complete);
if (req.reply_len > 0)
return req.reply[0];
else
return 0;
}
/* Enables or disables the specified interrupts */
static void ams_pmu_set_irq(enum ams_irq reg, char enable)
{
if (reg & AMS_IRQ_FREEFALL) {
u8 val = ams_pmu_get_register(AMS_FF_ENABLE);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_pmu_set_register(AMS_FF_ENABLE, val);
}
if (reg & AMS_IRQ_SHOCK) {
u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_pmu_set_register(AMS_SHOCK_ENABLE, val);
}
if (reg & AMS_IRQ_GLOBAL) {
u8 val = ams_pmu_get_register(AMS_CONTROL);
if (enable)
val |= 0x80;
else
val &= ~0x80;
ams_pmu_set_register(AMS_CONTROL, val);
}
}
static void ams_pmu_clear_irq(enum ams_irq reg)
{
if (reg & AMS_IRQ_FREEFALL)
ams_pmu_set_register(AMS_FF_CLEAR, 0x00);
if (reg & AMS_IRQ_SHOCK)
ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00);
}
static u8 ams_pmu_get_vendor(void)
{
return ams_pmu_get_register(AMS_VENDOR);
}
static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z)
{
*x = ams_pmu_get_register(AMS_X);
*y = ams_pmu_get_register(AMS_Y);
*z = ams_pmu_get_register(AMS_Z);
}
static void ams_pmu_exit(void)
{
/* Disable interrupts */
ams_pmu_set_irq(AMS_IRQ_ALL, 0);
/* Clear interrupts */
ams_pmu_clear_irq(AMS_IRQ_ALL);
ams_info.has_device = 0;
printk(KERN_INFO "ams: Unloading\n");
}
int __init ams_pmu_init(struct device_node *np)
{
u32 *prop;
int result;
mutex_lock(&ams_info.lock);
/* Set implementation stuff */
ams_info.of_node = np;
ams_info.exit = ams_pmu_exit;
ams_info.get_vendor = ams_pmu_get_vendor;
ams_info.get_xyz = ams_pmu_get_xyz;
ams_info.clear_irq = ams_pmu_clear_irq;
ams_info.bustype = BUS_HOST;
/* Get PMU command, should be 0x4e, but we can never know */
prop = (u32*)get_property(ams_info.of_node, "reg", NULL);
if (!prop) {
result = -ENODEV;
goto exit;
}
ams_pmu_cmd = ((*prop) >> 8) & 0xff;
/* Disable interrupts */
ams_pmu_set_irq(AMS_IRQ_ALL, 0);
/* Clear interrupts */
ams_pmu_clear_irq(AMS_IRQ_ALL);
result = ams_sensor_attach();
if (result < 0)
goto exit;
/* Set default values */
ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15);
ams_pmu_set_register(AMS_FF_ENABLE, 0x08);
ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14);
ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60);
ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f);
ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14);
ams_pmu_set_register(AMS_CONTROL, 0x4f);
/* Clear interrupts */
ams_pmu_clear_irq(AMS_IRQ_ALL);
ams_info.has_device = 1;
/* Enable interrupts */
ams_pmu_set_irq(AMS_IRQ_ALL, 1);
printk(KERN_INFO "ams: Found PMU based motion sensor\n");
result = 0;
exit:
mutex_unlock(&ams_info.lock);
return result;
}
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <asm/of_device.h>
enum ams_irq {
AMS_IRQ_FREEFALL = 0x01,
AMS_IRQ_SHOCK = 0x02,
AMS_IRQ_GLOBAL = 0x04,
AMS_IRQ_ALL =
AMS_IRQ_FREEFALL |
AMS_IRQ_SHOCK |
AMS_IRQ_GLOBAL,
};
struct ams {
/* Locks */
spinlock_t irq_lock;
struct mutex lock;
/* General properties */
struct device_node *of_node;
struct of_device *of_dev;
char has_device;
char vflag;
u32 orient1;
u32 orient2;
/* Interrupt worker */
struct work_struct worker;
u8 worker_irqs;
/* Implementation
*
* Only call these functions with the main lock held.
*/
void (*exit)(void);
void (*get_xyz)(s8 *x, s8 *y, s8 *z);
u8 (*get_vendor)(void);
void (*clear_irq)(enum ams_irq reg);
#ifdef CONFIG_SENSORS_AMS_I2C
/* I2C properties */
int i2c_bus;
int i2c_address;
struct i2c_client i2c_client;
#endif
/* Joystick emulation */
struct task_struct *kthread;
struct input_dev *idev;
__u16 bustype;
/* calibrated null values */
int xcalib, ycalib, zcalib;
};
extern struct ams ams_info;
extern void ams_sensors(s8 *x, s8 *y, s8 *z);
extern int ams_sensor_attach(void);
extern int ams_pmu_init(struct device_node *np);
extern int ams_i2c_init(struct device_node *np);
extern int ams_input_init(void);
extern void ams_input_exit(void);
/* /*
* f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated * f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O
* hardware monitoring features * chips integrated hardware monitoring features
* Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org> * Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org>
* *
* The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates * The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates
* complete hardware monitoring features: voltage, fan and temperature * complete hardware monitoring features: voltage, fan and temperature
* sensors, and manual and automatic fan speed control. * sensors, and manual and automatic fan speed control.
* *
* The F71872F/FG is almost the same, with two more voltages monitored,
* and 6 VID inputs.
*
* 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
* the Free Software Foundation; either version 2 of the License, or * the Free Software Foundation; either version 2 of the License, or
...@@ -37,6 +40,7 @@ ...@@ -37,6 +40,7 @@
static struct platform_device *pdev; static struct platform_device *pdev;
#define DRVNAME "f71805f" #define DRVNAME "f71805f"
enum kinds { f71805f, f71872f };
/* /*
* Super-I/O constants and functions * Super-I/O constants and functions
...@@ -48,11 +52,13 @@ static struct platform_device *pdev; ...@@ -48,11 +52,13 @@ static struct platform_device *pdev;
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
#define SIO_REG_DEVREV 0x22 /* Device revision */ #define SIO_REG_DEVREV 0x22 /* Device revision */
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ #define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */
#define SIO_REG_ENABLE 0x30 /* Logical device enable */ #define SIO_REG_ENABLE 0x30 /* Logical device enable */
#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 #define SIO_FINTEK_ID 0x1934
#define SIO_F71805F_ID 0x0406 #define SIO_F71805F_ID 0x0406
#define SIO_F71872F_ID 0x0341
static inline int static inline int
superio_inb(int base, int reg) superio_inb(int base, int reg)
...@@ -96,22 +102,25 @@ superio_exit(int base) ...@@ -96,22 +102,25 @@ superio_exit(int base)
* ISA constants * ISA constants
*/ */
#define REGION_LENGTH 2 #define REGION_LENGTH 8
#define ADDR_REG_OFFSET 0 #define ADDR_REG_OFFSET 5
#define DATA_REG_OFFSET 1 #define DATA_REG_OFFSET 6
/* /*
* Registers * Registers
*/ */
/* in nr from 0 to 8 (8-bit values) */ /* in nr from 0 to 10 (8-bit values) */
#define F71805F_REG_IN(nr) (0x10 + (nr)) #define F71805F_REG_IN(nr) (0x10 + (nr))
#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr)) #define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E)
#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr)) #define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F)
/* fan nr from 0 to 2 (12-bit values, two registers) */ /* fan nr from 0 to 2 (12-bit values, two registers) */
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr)) #define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr)) #define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
#define F71805F_REG_FAN_TARGET(nr) (0x69 + 16 * (nr))
#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr)) #define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr))
#define F71805F_REG_PWM_FREQ(nr) (0x63 + 16 * (nr))
#define F71805F_REG_PWM_DUTY(nr) (0x6B + 16 * (nr))
/* temp nr from 0 to 2 (8-bit values) */ /* temp nr from 0 to 2 (8-bit values) */
#define F71805F_REG_TEMP(nr) (0x1B + (nr)) #define F71805F_REG_TEMP(nr) (0x1B + (nr))
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) #define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
...@@ -122,6 +131,14 @@ superio_exit(int base) ...@@ -122,6 +131,14 @@ superio_exit(int base)
/* status nr from 0 to 2 */ /* status nr from 0 to 2 */
#define F71805F_REG_STATUS(nr) (0x36 + (nr)) #define F71805F_REG_STATUS(nr) (0x36 + (nr))
/* individual register bits */
#define FAN_CTRL_DC_MODE 0x10
#define FAN_CTRL_LATCH_FULL 0x08
#define FAN_CTRL_MODE_MASK 0x03
#define FAN_CTRL_MODE_SPEED 0x00
#define FAN_CTRL_MODE_TEMPERATURE 0x01
#define FAN_CTRL_MODE_MANUAL 0x02
/* /*
* Data structures and manipulation thereof * Data structures and manipulation thereof
*/ */
...@@ -138,12 +155,16 @@ struct f71805f_data { ...@@ -138,12 +155,16 @@ struct f71805f_data {
unsigned long last_limits; /* In jiffies */ unsigned long last_limits; /* In jiffies */
/* Register values */ /* Register values */
u8 in[9]; u8 in[11];
u8 in_high[9]; u8 in_high[11];
u8 in_low[9]; u8 in_low[11];
u16 has_in;
u16 fan[3]; u16 fan[3];
u16 fan_low[3]; u16 fan_low[3];
u8 fan_enabled; /* Read once at init time */ u16 fan_target[3];
u8 fan_ctrl[3];
u8 pwm[3];
u8 pwm_freq[3];
u8 temp[3]; u8 temp[3];
u8 temp_high[3]; u8 temp_high[3];
u8 temp_hyst[3]; u8 temp_hyst[3];
...@@ -151,6 +172,11 @@ struct f71805f_data { ...@@ -151,6 +172,11 @@ struct f71805f_data {
unsigned long alarms; unsigned long alarms;
}; };
struct f71805f_sio_data {
enum kinds kind;
u8 fnsel1;
};
static inline long in_from_reg(u8 reg) static inline long in_from_reg(u8 reg)
{ {
return (reg * 8); return (reg * 8);
...@@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long rpm) ...@@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long rpm)
return (1500000 / rpm); return (1500000 / rpm);
} }
static inline unsigned long pwm_freq_from_reg(u8 reg)
{
unsigned long clock = (reg & 0x80) ? 48000000UL : 1000000UL;
reg &= 0x7f;
if (reg == 0)
reg++;
return clock / (reg << 8);
}
static inline u8 pwm_freq_to_reg(unsigned long val)
{
if (val >= 187500) /* The highest we can do */
return 0x80;
if (val >= 1475) /* Use 48 MHz clock */
return 0x80 | (48000000UL / (val << 8));
if (val < 31) /* The lowest we can do */
return 0x7f;
else /* Use 1 MHz clock */
return 1000000UL / (val << 8);
}
static inline int pwm_mode_from_reg(u8 reg)
{
return !(reg & FAN_CTRL_DC_MODE);
}
static inline long temp_from_reg(u8 reg) static inline long temp_from_reg(u8 reg)
{ {
return (reg * 1000); return (reg * 1000);
...@@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev) ...@@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
/* Limit registers cache is refreshed after 60 seconds */ /* Limit registers cache is refreshed after 60 seconds */
if (time_after(jiffies, data->last_updated + 60 * HZ) if (time_after(jiffies, data->last_updated + 60 * HZ)
|| !data->valid) { || !data->valid) {
for (nr = 0; nr < 9; nr++) { for (nr = 0; nr < 11; nr++) {
if (!(data->has_in & (1 << nr)))
continue;
data->in_high[nr] = f71805f_read8(data, data->in_high[nr] = f71805f_read8(data,
F71805F_REG_IN_HIGH(nr)); F71805F_REG_IN_HIGH(nr));
data->in_low[nr] = f71805f_read8(data, data->in_low[nr] = f71805f_read8(data,
F71805F_REG_IN_LOW(nr)); F71805F_REG_IN_LOW(nr));
} }
for (nr = 0; nr < 3; nr++) { for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr)) data->fan_low[nr] = f71805f_read16(data,
data->fan_low[nr] = f71805f_read16(data, F71805F_REG_FAN_LOW(nr));
F71805F_REG_FAN_LOW(nr)); data->fan_target[nr] = f71805f_read16(data,
F71805F_REG_FAN_TARGET(nr));
data->pwm_freq[nr] = f71805f_read8(data,
F71805F_REG_PWM_FREQ(nr));
} }
for (nr = 0; nr < 3; nr++) { for (nr = 0; nr < 3; nr++) {
data->temp_high[nr] = f71805f_read8(data, data->temp_high[nr] = f71805f_read8(data,
...@@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev) ...@@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
/* Measurement registers cache is refreshed after 1 second */ /* Measurement registers cache is refreshed after 1 second */
if (time_after(jiffies, data->last_updated + HZ) if (time_after(jiffies, data->last_updated + HZ)
|| !data->valid) { || !data->valid) {
for (nr = 0; nr < 9; nr++) { for (nr = 0; nr < 11; nr++) {
if (!(data->has_in & (1 << nr)))
continue;
data->in[nr] = f71805f_read8(data, data->in[nr] = f71805f_read8(data,
F71805F_REG_IN(nr)); F71805F_REG_IN(nr));
} }
for (nr = 0; nr < 3; nr++) { for (nr = 0; nr < 3; nr++) {
if (data->fan_enabled & (1 << nr)) data->fan[nr] = f71805f_read16(data,
data->fan[nr] = f71805f_read16(data, F71805F_REG_FAN(nr));
F71805F_REG_FAN(nr)); data->fan_ctrl[nr] = f71805f_read8(data,
F71805F_REG_FAN_CTRL(nr));
data->pwm[nr] = f71805f_read8(data,
F71805F_REG_PWM_DUTY(nr));
} }
for (nr = 0; nr < 3; nr++) { for (nr = 0; nr < 3; nr++) {
data->temp[nr] = f71805f_read8(data, data->temp[nr] = f71805f_read8(data,
...@@ -333,35 +396,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr, ...@@ -333,35 +396,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
struct f71805f_data *data = f71805f_update_device(dev); struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in[0])); return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr]));
} }
static ssize_t show_in0_max(struct device *dev, struct device_attribute static ssize_t show_in0_max(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71805f_data *data = f71805f_update_device(dev); struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0])); return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr]));
} }
static ssize_t show_in0_min(struct device *dev, struct device_attribute static ssize_t show_in0_min(struct device *dev, struct device_attribute
*devattr, char *buf) *devattr, char *buf)
{ {
struct f71805f_data *data = f71805f_update_device(dev); struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0])); return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr]));
} }
static ssize_t set_in0_max(struct device *dev, struct device_attribute static ssize_t set_in0_max(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71805f_data *data = dev_get_drvdata(dev); struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10); long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->in_high[0] = in0_to_reg(val); data->in_high[nr] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]); f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
return count; return count;
...@@ -371,11 +442,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute ...@@ -371,11 +442,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
struct f71805f_data *data = dev_get_drvdata(dev); struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10); long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
data->in_low[0] = in0_to_reg(val); data->in_low[nr] = in0_to_reg(val);
f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]); f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
return count; return count;
...@@ -463,6 +536,16 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute ...@@ -463,6 +536,16 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr])); return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr]));
} }
static ssize_t show_fan_target(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_target[nr]));
}
static ssize_t set_fan_min(struct device *dev, struct device_attribute static ssize_t set_fan_min(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count) *devattr, const char *buf, size_t count)
{ {
...@@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute ...@@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute
return count; return count;
} }
static ssize_t set_fan_target(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
long val = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->fan_target[nr] = fan_to_reg(val);
f71805f_write16(data, F71805F_REG_FAN_TARGET(nr),
data->fan_target[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%d\n", (int)data->pwm[nr]);
}
static ssize_t show_pwm_enable(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
int mode;
switch (data->fan_ctrl[nr] & FAN_CTRL_MODE_MASK) {
case FAN_CTRL_MODE_SPEED:
mode = 3;
break;
case FAN_CTRL_MODE_TEMPERATURE:
mode = 2;
break;
default: /* MANUAL */
mode = 1;
}
return sprintf(buf, "%d\n", mode);
}
static ssize_t show_pwm_freq(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%lu\n", pwm_freq_from_reg(data->pwm_freq[nr]));
}
static ssize_t show_pwm_mode(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct f71805f_data *data = f71805f_update_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
return sprintf(buf, "%d\n", pwm_mode_from_reg(data->fan_ctrl[nr]));
}
static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
if (val > 255)
return -EINVAL;
mutex_lock(&data->update_lock);
data->pwm[nr] = val;
f71805f_write8(data, F71805F_REG_PWM_DUTY(nr), data->pwm[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static struct attribute *f71805f_attr_pwm[];
static ssize_t set_pwm_enable(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
u8 reg;
if (val < 1 || val > 3)
return -EINVAL;
if (val > 1) { /* Automatic mode, user can't set PWM value */
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
S_IRUGO))
dev_dbg(dev, "chmod -w pwm%d failed\n", nr + 1);
}
mutex_lock(&data->update_lock);
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(nr))
& ~FAN_CTRL_MODE_MASK;
switch (val) {
case 1:
reg |= FAN_CTRL_MODE_MANUAL;
break;
case 2:
reg |= FAN_CTRL_MODE_TEMPERATURE;
break;
case 3:
reg |= FAN_CTRL_MODE_SPEED;
break;
}
data->fan_ctrl[nr] = reg;
f71805f_write8(data, F71805F_REG_FAN_CTRL(nr), reg);
mutex_unlock(&data->update_lock);
if (val == 1) { /* Manual mode, user can set PWM value */
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
S_IRUGO | S_IWUSR))
dev_dbg(dev, "chmod +w pwm%d failed\n", nr + 1);
}
return count;
}
static ssize_t set_pwm_freq(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct f71805f_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->pwm_freq[nr] = pwm_freq_to_reg(val);
f71805f_write8(data, F71805F_REG_PWM_FREQ(nr), data->pwm_freq[nr]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
char *buf) char *buf)
{ {
...@@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute ...@@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute
{ {
struct f71805f_data *data = f71805f_update_device(dev); struct f71805f_data *data = f71805f_update_device(dev);
return sprintf(buf, "%lu\n", data->alarms & 0x1ff); return sprintf(buf, "%lu\n", data->alarms & 0x7ff);
} }
static ssize_t show_alarms_fan(struct device *dev, struct device_attribute static ssize_t show_alarms_fan(struct device *dev, struct device_attribute
...@@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute ...@@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
return sprintf(buf, "%s\n", data->name); return sprintf(buf, "%s\n", data->name);
} }
static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL); static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0);
static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max); static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR,
static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min); show_in0_max, set_in0_max, 0);
static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR,
show_in0_min, set_in0_min, 0);
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1); static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR,
show_in_max, set_in_max, 1); show_in_max, set_in_max, 1);
...@@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR, ...@@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR,
show_in_max, set_in_max, 8); show_in_max, set_in_max, 8);
static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
show_in_min, set_in_min, 8); show_in_min, set_in_min, 8);
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9);
static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR,
show_in0_max, set_in0_max, 9);
static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR,
show_in0_min, set_in0_min, 9);
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10);
static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR,
show_in0_max, set_in0_max, 10);
static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR,
show_in0_min, set_in0_min, 10);
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0); static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 0); show_fan_min, set_fan_min, 0);
static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 0);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 1); show_fan_min, set_fan_min, 1);
static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 1);
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR,
show_fan_min, set_fan_min, 2); show_fan_min, set_fan_min, 2);
static SENSOR_DEVICE_ATTR(fan3_target, S_IRUGO | S_IWUSR,
show_fan_target, set_fan_target, 2);
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
...@@ -667,6 +919,27 @@ static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR, ...@@ -667,6 +919,27 @@ static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR,
show_temp_hyst, set_temp_hyst, 2); show_temp_hyst, set_temp_hyst, 2);
static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2); static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2);
/* pwm (value) files are created read-only, write permission is
then added or removed dynamically as needed */
static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, show_pwm, set_pwm, 0);
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 0);
static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 0);
static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0);
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO, show_pwm, set_pwm, 1);
static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 1);
static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 1);
static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1);
static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO, show_pwm, set_pwm, 2);
static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR,
show_pwm_enable, set_pwm_enable, 2);
static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR,
show_pwm_freq, set_pwm_freq, 2);
static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2);
static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0);
static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2);
...@@ -676,6 +949,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); ...@@ -676,6 +949,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5);
static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6);
static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7);
static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8); static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8);
static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9);
static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10);
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11); static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11);
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12); static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13); static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13);
...@@ -689,9 +964,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL); ...@@ -689,9 +964,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL);
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static struct attribute *f71805f_attributes[] = { static struct attribute *f71805f_attributes[] = {
&dev_attr_in0_input.attr, &sensor_dev_attr_in0_input.dev_attr.attr,
&dev_attr_in0_max.attr, &sensor_dev_attr_in0_max.dev_attr.attr,
&dev_attr_in0_min.attr, &sensor_dev_attr_in0_min.dev_attr.attr,
&sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr,
&sensor_dev_attr_in1_max.dev_attr.attr, &sensor_dev_attr_in1_max.dev_attr.attr,
&sensor_dev_attr_in1_min.dev_attr.attr, &sensor_dev_attr_in1_min.dev_attr.attr,
...@@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = { ...@@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in3_input.dev_attr.attr, &sensor_dev_attr_in3_input.dev_attr.attr,
&sensor_dev_attr_in3_max.dev_attr.attr, &sensor_dev_attr_in3_max.dev_attr.attr,
&sensor_dev_attr_in3_min.dev_attr.attr, &sensor_dev_attr_in3_min.dev_attr.attr,
&sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_in4_max.dev_attr.attr,
&sensor_dev_attr_in4_min.dev_attr.attr,
&sensor_dev_attr_in5_input.dev_attr.attr, &sensor_dev_attr_in5_input.dev_attr.attr,
&sensor_dev_attr_in5_max.dev_attr.attr, &sensor_dev_attr_in5_max.dev_attr.attr,
&sensor_dev_attr_in5_min.dev_attr.attr, &sensor_dev_attr_in5_min.dev_attr.attr,
...@@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = { ...@@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in7_input.dev_attr.attr, &sensor_dev_attr_in7_input.dev_attr.attr,
&sensor_dev_attr_in7_max.dev_attr.attr, &sensor_dev_attr_in7_max.dev_attr.attr,
&sensor_dev_attr_in7_min.dev_attr.attr, &sensor_dev_attr_in7_min.dev_attr.attr,
&sensor_dev_attr_in8_input.dev_attr.attr,
&sensor_dev_attr_in8_max.dev_attr.attr, &sensor_dev_attr_fan1_input.dev_attr.attr,
&sensor_dev_attr_in8_min.dev_attr.attr, &sensor_dev_attr_fan1_min.dev_attr.attr,
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
&sensor_dev_attr_fan1_target.dev_attr.attr,
&sensor_dev_attr_fan2_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr,
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
&sensor_dev_attr_fan2_target.dev_attr.attr,
&sensor_dev_attr_fan3_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr,
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
&sensor_dev_attr_fan3_target.dev_attr.attr,
&sensor_dev_attr_pwm1.dev_attr.attr,
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
&sensor_dev_attr_pwm1_mode.dev_attr.attr,
&sensor_dev_attr_pwm2.dev_attr.attr,
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
&sensor_dev_attr_pwm2_mode.dev_attr.attr,
&sensor_dev_attr_pwm3.dev_attr.attr,
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
&sensor_dev_attr_pwm3_mode.dev_attr.attr,
&sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp1_input.dev_attr.attr,
&sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr,
...@@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = { ...@@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = {
&sensor_dev_attr_in1_alarm.dev_attr.attr, &sensor_dev_attr_in1_alarm.dev_attr.attr,
&sensor_dev_attr_in2_alarm.dev_attr.attr, &sensor_dev_attr_in2_alarm.dev_attr.attr,
&sensor_dev_attr_in3_alarm.dev_attr.attr, &sensor_dev_attr_in3_alarm.dev_attr.attr,
&sensor_dev_attr_in4_alarm.dev_attr.attr,
&sensor_dev_attr_in5_alarm.dev_attr.attr, &sensor_dev_attr_in5_alarm.dev_attr.attr,
&sensor_dev_attr_in6_alarm.dev_attr.attr, &sensor_dev_attr_in6_alarm.dev_attr.attr,
&sensor_dev_attr_in7_alarm.dev_attr.attr, &sensor_dev_attr_in7_alarm.dev_attr.attr,
&sensor_dev_attr_in8_alarm.dev_attr.attr,
&dev_attr_alarms_in.attr, &dev_attr_alarms_in.attr,
&sensor_dev_attr_temp1_alarm.dev_attr.attr, &sensor_dev_attr_temp1_alarm.dev_attr.attr,
&sensor_dev_attr_temp2_alarm.dev_attr.attr, &sensor_dev_attr_temp2_alarm.dev_attr.attr,
...@@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = { ...@@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = {
.attrs = f71805f_attributes, .attrs = f71805f_attributes,
}; };
static struct attribute *f71805f_attributes_fan[3][4] = { static struct attribute *f71805f_attributes_optin[4][5] = {
{ {
&sensor_dev_attr_fan1_input.dev_attr.attr, &sensor_dev_attr_in4_input.dev_attr.attr,
&sensor_dev_attr_fan1_min.dev_attr.attr, &sensor_dev_attr_in4_max.dev_attr.attr,
&sensor_dev_attr_fan1_alarm.dev_attr.attr, &sensor_dev_attr_in4_min.dev_attr.attr,
&sensor_dev_attr_in4_alarm.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_in8_input.dev_attr.attr,
&sensor_dev_attr_in8_max.dev_attr.attr,
&sensor_dev_attr_in8_min.dev_attr.attr,
&sensor_dev_attr_in8_alarm.dev_attr.attr,
NULL NULL
}, { }, {
&sensor_dev_attr_fan2_input.dev_attr.attr, &sensor_dev_attr_in9_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr, &sensor_dev_attr_in9_max.dev_attr.attr,
&sensor_dev_attr_fan2_alarm.dev_attr.attr, &sensor_dev_attr_in9_min.dev_attr.attr,
&sensor_dev_attr_in9_alarm.dev_attr.attr,
NULL NULL
}, { }, {
&sensor_dev_attr_fan3_input.dev_attr.attr, &sensor_dev_attr_in10_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr, &sensor_dev_attr_in10_max.dev_attr.attr,
&sensor_dev_attr_fan3_alarm.dev_attr.attr, &sensor_dev_attr_in10_min.dev_attr.attr,
&sensor_dev_attr_in10_alarm.dev_attr.attr,
NULL NULL
} }
}; };
static const struct attribute_group f71805f_group_fan[3] = { static const struct attribute_group f71805f_group_optin[4] = {
{ .attrs = f71805f_attributes_fan[0] }, { .attrs = f71805f_attributes_optin[0] },
{ .attrs = f71805f_attributes_fan[1] }, { .attrs = f71805f_attributes_optin[1] },
{ .attrs = f71805f_attributes_fan[2] }, { .attrs = f71805f_attributes_optin[2] },
{ .attrs = f71805f_attributes_optin[3] },
};
/* We don't include pwm_freq files in the arrays above, because they must be
created conditionally (only if pwm_mode is 1 == PWM) */
static struct attribute *f71805f_attributes_pwm_freq[] = {
&sensor_dev_attr_pwm1_freq.dev_attr.attr,
&sensor_dev_attr_pwm2_freq.dev_attr.attr,
&sensor_dev_attr_pwm3_freq.dev_attr.attr,
NULL
};
static const struct attribute_group f71805f_group_pwm_freq = {
.attrs = f71805f_attributes_pwm_freq,
};
/* We also need an indexed access to pwmN files to toggle writability */
static struct attribute *f71805f_attr_pwm[] = {
&sensor_dev_attr_pwm1.dev_attr.attr,
&sensor_dev_attr_pwm2.dev_attr.attr,
&sensor_dev_attr_pwm3.dev_attr.attr,
}; };
/* /*
...@@ -798,18 +1118,30 @@ static void __devinit f71805f_init_device(struct f71805f_data *data) ...@@ -798,18 +1118,30 @@ static void __devinit f71805f_init_device(struct f71805f_data *data)
/* Fan monitoring can be disabled. If it is, we won't be polling /* Fan monitoring can be disabled. If it is, we won't be polling
the register values, and won't create the related sysfs files. */ the register values, and won't create the related sysfs files. */
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i)); data->fan_ctrl[i] = f71805f_read8(data,
if (!(reg & 0x80)) F71805F_REG_FAN_CTRL(i));
data->fan_enabled |= (1 << i); /* Clear latch full bit, else "speed mode" fan speed control
doesn't work */
if (data->fan_ctrl[i] & FAN_CTRL_LATCH_FULL) {
data->fan_ctrl[i] &= ~FAN_CTRL_LATCH_FULL;
f71805f_write8(data, F71805F_REG_FAN_CTRL(i),
data->fan_ctrl[i]);
}
} }
} }
static int __devinit f71805f_probe(struct platform_device *pdev) static int __devinit f71805f_probe(struct platform_device *pdev)
{ {
struct f71805f_sio_data *sio_data = pdev->dev.platform_data;
struct f71805f_data *data; struct f71805f_data *data;
struct resource *res; struct resource *res;
int i, err; int i, err;
static const char *names[] = {
"f71805f",
"f71872f",
};
if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) { if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
err = -ENOMEM; err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Out of memory\n"); printk(KERN_ERR DRVNAME ": Out of memory\n");
...@@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
res = platform_get_resource(pdev, IORESOURCE_IO, 0); res = platform_get_resource(pdev, IORESOURCE_IO, 0);
data->addr = res->start; data->addr = res->start;
mutex_init(&data->lock); mutex_init(&data->lock);
data->name = "f71805f"; data->name = names[sio_data->kind];
mutex_init(&data->update_lock); mutex_init(&data->update_lock);
platform_set_drvdata(pdev, data); platform_set_drvdata(pdev, data);
/* Some voltage inputs depend on chip model and configuration */
switch (sio_data->kind) {
case f71805f:
data->has_in = 0x1ff;
break;
case f71872f:
data->has_in = 0x6ef;
if (sio_data->fnsel1 & 0x01)
data->has_in |= (1 << 4); /* in4 */
if (sio_data->fnsel1 & 0x02)
data->has_in |= (1 << 8); /* in8 */
break;
}
/* Initialize the F71805F chip */ /* Initialize the F71805F chip */
f71805f_init_device(data); f71805f_init_device(data);
/* Register sysfs interface files */ /* Register sysfs interface files */
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group))) if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
goto exit_free; goto exit_free;
for (i = 0; i < 3; i++) { if (data->has_in & (1 << 4)) { /* in4 */
if (!(data->fan_enabled & (1 << i))) if ((err = sysfs_create_group(&pdev->dev.kobj,
continue; &f71805f_group_optin[0])))
goto exit_remove_files;
}
if (data->has_in & (1 << 8)) { /* in8 */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[1])))
goto exit_remove_files;
}
if (data->has_in & (1 << 9)) { /* in9 (F71872F/FG only) */
if ((err = sysfs_create_group(&pdev->dev.kobj, if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_fan[i]))) &f71805f_group_optin[2])))
goto exit_remove_files; goto exit_remove_files;
} }
if (data->has_in & (1 << 10)) { /* in9 (F71872F/FG only) */
if ((err = sysfs_create_group(&pdev->dev.kobj,
&f71805f_group_optin[3])))
goto exit_remove_files;
}
for (i = 0; i < 3; i++) {
/* If control mode is PWM, create pwm_freq file */
if (!(data->fan_ctrl[i] & FAN_CTRL_DC_MODE)) {
if ((err = sysfs_create_file(&pdev->dev.kobj,
f71805f_attributes_pwm_freq[i])))
goto exit_remove_files;
}
/* If PWM is in manual mode, add write permission */
if (data->fan_ctrl[i] & FAN_CTRL_MODE_MANUAL) {
if ((err = sysfs_chmod_file(&pdev->dev.kobj,
f71805f_attr_pwm[i],
S_IRUGO | S_IWUSR))) {
dev_err(&pdev->dev, "chmod +w pwm%d failed\n",
i + 1);
goto exit_remove_files;
}
}
}
data->class_dev = hwmon_device_register(&pdev->dev); data->class_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->class_dev)) { if (IS_ERR(data->class_dev)) {
...@@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev) ...@@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
exit_remove_files: exit_remove_files:
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
for (i = 0; i < 3; i++) for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
exit_free: exit_free:
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
kfree(data); kfree(data);
...@@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev) ...@@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
hwmon_device_unregister(data->class_dev); hwmon_device_unregister(data->class_dev);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
for (i = 0; i < 3; i++) for (i = 0; i < 4; i++)
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]); sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
kfree(data); kfree(data);
return 0; return 0;
...@@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = { ...@@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = {
.remove = __devexit_p(f71805f_remove), .remove = __devexit_p(f71805f_remove),
}; };
static int __init f71805f_device_add(unsigned short address) static int __init f71805f_device_add(unsigned short address,
const struct f71805f_sio_data *sio_data)
{ {
struct resource res = { struct resource res = {
.start = address, .start = address,
...@@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address) ...@@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address)
goto exit_device_put; goto exit_device_put;
} }
pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data),
GFP_KERNEL);
if (!pdev->dev.platform_data) {
err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
goto exit_device_put;
}
memcpy(pdev->dev.platform_data, sio_data,
sizeof(struct f71805f_sio_data));
err = platform_device_add(pdev); err = platform_device_add(pdev);
if (err) { if (err) {
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
err); err);
goto exit_device_put; goto exit_kfree_data;
} }
return 0; return 0;
exit_kfree_data:
kfree(pdev->dev.platform_data);
pdev->dev.platform_data = NULL;
exit_device_put: exit_device_put:
platform_device_put(pdev); platform_device_put(pdev);
exit: exit:
return err; return err;
} }
static int __init f71805f_find(int sioaddr, unsigned short *address) static int __init f71805f_find(int sioaddr, unsigned short *address,
struct f71805f_sio_data *sio_data)
{ {
int err = -ENODEV; int err = -ENODEV;
u16 devid; u16 devid;
static const char *names[] = {
"F71805F/FG",
"F71872F/FG",
};
superio_enter(sioaddr); superio_enter(sioaddr);
devid = superio_inw(sioaddr, SIO_REG_MANID); devid = superio_inw(sioaddr, SIO_REG_MANID);
...@@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address) ...@@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
goto exit; goto exit;
devid = superio_inw(sioaddr, SIO_REG_DEVID); devid = superio_inw(sioaddr, SIO_REG_DEVID);
if (devid != SIO_F71805F_ID) { switch (devid) {
case SIO_F71805F_ID:
sio_data->kind = f71805f;
break;
case SIO_F71872F_ID:
sio_data->kind = f71872f;
sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1);
break;
default:
printk(KERN_INFO DRVNAME ": Unsupported Fintek device, " printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
"skipping\n"); "skipping\n");
goto exit; goto exit;
...@@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address) ...@@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
"skipping\n"); "skipping\n");
goto exit; goto exit;
} }
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
err = 0; err = 0;
printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n", printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n",
*address, superio_inb(sioaddr, SIO_REG_DEVREV)); names[sio_data->kind], *address,
superio_inb(sioaddr, SIO_REG_DEVREV));
exit: exit:
superio_exit(sioaddr); superio_exit(sioaddr);
...@@ -966,9 +1375,10 @@ static int __init f71805f_init(void) ...@@ -966,9 +1375,10 @@ static int __init f71805f_init(void)
{ {
int err; int err;
unsigned short address; unsigned short address;
struct f71805f_sio_data sio_data;
if (f71805f_find(0x2e, &address) if (f71805f_find(0x2e, &address, &sio_data)
&& f71805f_find(0x4e, &address)) && f71805f_find(0x4e, &address, &sio_data))
return -ENODEV; return -ENODEV;
err = platform_driver_register(&f71805f_driver); err = platform_driver_register(&f71805f_driver);
...@@ -976,7 +1386,7 @@ static int __init f71805f_init(void) ...@@ -976,7 +1386,7 @@ static int __init f71805f_init(void)
goto exit; goto exit;
/* Sets global pdev as a side effect */ /* Sets global pdev as a side effect */
err = f71805f_device_add(address); err = f71805f_device_add(address, &sio_data);
if (err) if (err)
goto exit_driver; goto exit_driver;
...@@ -990,13 +1400,16 @@ exit: ...@@ -990,13 +1400,16 @@ exit:
static void __exit f71805f_exit(void) static void __exit f71805f_exit(void)
{ {
kfree(pdev->dev.platform_data);
pdev->dev.platform_data = NULL;
platform_device_unregister(pdev); platform_device_unregister(pdev);
platform_driver_unregister(&f71805f_driver); platform_driver_unregister(&f71805f_driver);
} }
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>"); MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("F71805F hardware monitoring driver"); MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver");
module_init(f71805f_init); module_init(f71805f_init);
module_exit(f71805f_exit); module_exit(f71805f_exit);
...@@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = { ...@@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = {
/* Module stuff */ /* Module stuff */
/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */ /* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
static int hdaps_dmi_match(struct dmi_system_id *id) static int __init hdaps_dmi_match(struct dmi_system_id *id)
{ {
printk(KERN_INFO "hdaps: %s detected.\n", id->ident); printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
return 1; return 1;
} }
/* hdaps_dmi_match_invert - found an inverted match. */ /* hdaps_dmi_match_invert - found an inverted match. */
static int hdaps_dmi_match_invert(struct dmi_system_id *id) static int __init hdaps_dmi_match_invert(struct dmi_system_id *id)
{ {
hdaps_invert = 1; hdaps_invert = 1;
printk(KERN_INFO "hdaps: inverting axis readings.\n"); printk(KERN_INFO "hdaps: inverting axis readings.\n");
return hdaps_dmi_match(id); return hdaps_dmi_match(id);
} }
#define HDAPS_DMI_MATCH_NORMAL(model) { \ #define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
.ident = "IBM " model, \ .ident = vendor " " model, \
.callback = hdaps_dmi_match, \ .callback = hdaps_dmi_match, \
.matches = { \ .matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \ } \
} }
#define HDAPS_DMI_MATCH_INVERT(model) { \ #define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
.ident = "IBM " model, \ .ident = vendor " " model, \
.callback = hdaps_dmi_match_invert, \ .callback = hdaps_dmi_match_invert, \
.matches = { \ .matches = { \
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \ DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
DMI_MATCH(DMI_PRODUCT_VERSION, model) \ DMI_MATCH(DMI_PRODUCT_VERSION, model) \
} \ } \
} }
#define HDAPS_DMI_MATCH_LENOVO(model) { \ /* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
.ident = "Lenovo " model, \ "ThinkPad T42p", so the order of the entries matters.
.callback = hdaps_dmi_match_invert, \ If your ThinkPad is not recognized, please update to latest
.matches = { \ BIOS. This is especially the case for some R52 ThinkPads. */
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \ static struct dmi_system_id __initdata hdaps_whitelist[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, model) \ HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
} \ HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
} HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
{ .ident = NULL }
};
static int __init hdaps_init(void) static int __init hdaps_init(void)
{ {
int ret; int ret;
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
"ThinkPad T42p", so the order of the entries matters */
struct dmi_system_id hdaps_whitelist[] = {
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),
HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */
HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"),
HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"),
HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"),
{ .ident = NULL }
};
if (!dmi_check_system(hdaps_whitelist)) { if (!dmi_check_system(hdaps_whitelist)) {
printk(KERN_WARNING "hdaps: supported laptop not found!\n"); printk(KERN_WARNING "hdaps: supported laptop not found!\n");
ret = -ENODEV; ret = -ENODEV;
......
/* /*
hwmon-vid.c - VID/VRM/VRD voltage conversions hwmon-vid.c - VID/VRM/VRD voltage conversions
Copyright (c) 2004 Rudolf Marek <r.marek@sh.cvut.cz> Copyright (c) 2004 Rudolf Marek <r.marek@assembler.cz>
Partly imported from i2c-vid.h of the lm_sensors project Partly imported from i2c-vid.h of the lm_sensors project
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
...@@ -232,7 +232,7 @@ u8 vid_which_vrm(void) ...@@ -232,7 +232,7 @@ u8 vid_which_vrm(void)
EXPORT_SYMBOL(vid_from_reg); EXPORT_SYMBOL(vid_from_reg);
EXPORT_SYMBOL(vid_which_vrm); EXPORT_SYMBOL(vid_which_vrm);
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>"); MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
MODULE_DESCRIPTION("hwmon-vid driver"); MODULE_DESCRIPTION("hwmon-vid driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
monitoring. monitoring.
Supports: IT8705F Super I/O chip w/LPC interface Supports: IT8705F Super I/O chip w/LPC interface
IT8712F Super I/O chip w/LPC interface & SMBus 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
Sis950 A clone of the IT8705F Sis950 A clone of the IT8705F
...@@ -41,12 +41,8 @@ ...@@ -41,12 +41,8 @@
#include <asm/io.h> #include <asm/io.h>
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
static unsigned short isa_address; static unsigned short isa_address;
enum chips { it87, it8712, it8716, it8718 };
/* Insmod parameters */
I2C_CLIENT_INSMOD_4(it87, it8712, it8716, it8718);
#define REG 0x2e /* The register to read/write */ #define REG 0x2e /* The register to read/write */
#define DEV 0x07 /* Register: Logical device select */ #define DEV 0x07 /* Register: Logical device select */
...@@ -162,8 +158,6 @@ static u8 vid_value; ...@@ -162,8 +158,6 @@ static u8 vid_value;
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2) #define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2)
#define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2) #define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2)
#define IT87_REG_I2C_ADDR 0x48
#define IT87_REG_VIN_ENABLE 0x50 #define IT87_REG_VIN_ENABLE 0x50
#define IT87_REG_TEMP_ENABLE 0x51 #define IT87_REG_TEMP_ENABLE 0x51
...@@ -242,33 +236,22 @@ struct it87_data { ...@@ -242,33 +236,22 @@ struct it87_data {
}; };
static int it87_attach_adapter(struct i2c_adapter *adapter); static int it87_detect(struct i2c_adapter *adapter);
static int it87_isa_attach_adapter(struct i2c_adapter *adapter);
static int it87_detect(struct i2c_adapter *adapter, int address, int kind);
static int it87_detach_client(struct i2c_client *client); static int it87_detach_client(struct i2c_client *client);
static int it87_read_value(struct i2c_client *client, u8 reg); static int it87_read_value(struct i2c_client *client, u8 reg);
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value); static void it87_write_value(struct i2c_client *client, u8 reg, u8 value);
static struct it87_data *it87_update_device(struct device *dev); static struct it87_data *it87_update_device(struct device *dev);
static int it87_check_pwm(struct i2c_client *client); static int it87_check_pwm(struct i2c_client *client);
static void it87_init_client(struct i2c_client *client, struct it87_data *data); static void it87_init_client(struct i2c_client *client, struct it87_data *data);
static struct i2c_driver it87_driver = {
.driver = {
.name = "it87",
},
.id = I2C_DRIVERID_IT87,
.attach_adapter = it87_attach_adapter,
.detach_client = it87_detach_client,
};
static struct i2c_driver it87_isa_driver = { static struct i2c_driver it87_isa_driver = {
.driver = { .driver = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.name = "it87-isa", .name = "it87-isa",
}, },
.attach_adapter = it87_isa_attach_adapter, .attach_adapter = it87_detect,
.detach_client = it87_detach_client, .detach_client = it87_detach_client,
}; };
...@@ -850,22 +833,6 @@ static const struct attribute_group it87_group_opt = { ...@@ -850,22 +833,6 @@ static const struct attribute_group it87_group_opt = {
.attrs = it87_attributes_opt, .attrs = it87_attributes_opt,
}; };
/* This function is called when:
* it87_driver is inserted (when this module is loaded), for each
available adapter
* when a new adapter is inserted (and it87_driver is still present) */
static int it87_attach_adapter(struct i2c_adapter *adapter)
{
if (!(adapter->class & I2C_CLASS_HWMON))
return 0;
return i2c_probe(adapter, &addr_data, it87_detect);
}
static int it87_isa_attach_adapter(struct i2c_adapter *adapter)
{
return it87_detect(adapter, isa_address, -1);
}
/* SuperIO detection - will change isa_address if a chip is found */ /* SuperIO detection - will change isa_address if a chip is found */
static int __init it87_find(unsigned short *address) static int __init it87_find(unsigned short *address)
{ {
...@@ -916,29 +883,20 @@ exit: ...@@ -916,29 +883,20 @@ exit:
} }
/* This function is called by i2c_probe */ /* This function is called by i2c_probe */
static int it87_detect(struct i2c_adapter *adapter, int address, int kind) static int it87_detect(struct i2c_adapter *adapter)
{ {
int i;
struct i2c_client *new_client; struct i2c_client *new_client;
struct it87_data *data; struct it87_data *data;
int err = 0; int err = 0;
const char *name = ""; const char *name;
int is_isa = i2c_is_isa_adapter(adapter);
int enable_pwm_interface; int enable_pwm_interface;
if (!is_isa &&
!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
goto ERROR0;
/* Reserve the ISA region */ /* Reserve the ISA region */
if (is_isa) if (!request_region(isa_address, IT87_EXTENT,
if (!request_region(address, IT87_EXTENT, it87_isa_driver.driver.name)){
it87_isa_driver.driver.name)) err = -EBUSY;
goto ERROR0; goto ERROR0;
}
/* For now, we presume we have a valid client. We create the
client structure, even though we cannot fill it completely yet.
But it allows us to access it87_{read,write}_value. */
if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) { if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) {
err = -ENOMEM; err = -ENOMEM;
...@@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind) ...@@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
} }
new_client = &data->client; new_client = &data->client;
if (is_isa) mutex_init(&data->lock);
mutex_init(&data->lock);
i2c_set_clientdata(new_client, data); i2c_set_clientdata(new_client, data);
new_client->addr = address; new_client->addr = isa_address;
new_client->adapter = adapter; new_client->adapter = adapter;
new_client->driver = is_isa ? &it87_isa_driver : &it87_driver; new_client->driver = &it87_isa_driver;
new_client->flags = 0;
/* Now, we do the remaining detection. */ /* Now, we do the remaining detection. */
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
if (kind < 0) { || it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) {
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80) err = -ENODEV;
|| (!is_isa goto ERROR2;
&& it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) {
err = -ENODEV;
goto ERROR2;
}
} }
/* Determine the chip type. */ /* Determine the chip type. */
if (kind <= 0) { switch (chip_type) {
i = it87_read_value(new_client, IT87_REG_CHIPID); case IT8712F_DEVID:
if (i == 0x90) { data->type = it8712;
kind = it87;
if (is_isa) {
switch (chip_type) {
case IT8712F_DEVID:
kind = it8712;
break;
case IT8716F_DEVID:
kind = it8716;
break;
case IT8718F_DEVID:
kind = it8718;
break;
}
}
}
else {
if (kind == 0)
dev_info(&adapter->dev,
"Ignoring 'force' parameter for unknown chip at "
"adapter %d, address 0x%02x\n",
i2c_adapter_id(adapter), address);
err = -ENODEV;
goto ERROR2;
}
}
if (kind == it87) {
name = "it87";
} else if (kind == it8712) {
name = "it8712"; name = "it8712";
} else if (kind == it8716) { break;
case IT8716F_DEVID:
data->type = it8716;
name = "it8716"; name = "it8716";
} else if (kind == it8718) { break;
case IT8718F_DEVID:
data->type = it8718;
name = "it8718"; name = "it8718";
break;
default:
data->type = it87;
name = "it87";
} }
/* Fill in the remaining client fields and put it into the global list */ /* Fill in the remaining client fields and put it into the global list */
strlcpy(new_client->name, name, I2C_NAME_SIZE); strlcpy(new_client->name, name, I2C_NAME_SIZE);
data->type = kind;
data->valid = 0;
mutex_init(&data->update_lock); mutex_init(&data->update_lock);
/* Tell the I2C layer a new client has arrived */ /* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(new_client))) if ((err = i2c_attach_client(new_client)))
goto ERROR2; goto ERROR2;
if (!is_isa)
dev_info(&new_client->dev, "The I2C interface to IT87xxF "
"hardware monitoring chips is deprecated. Please "
"report if you still rely on it.\n");
/* Check PWM configuration */ /* Check PWM configuration */
enable_pwm_interface = it87_check_pwm(new_client); enable_pwm_interface = it87_check_pwm(new_client);
...@@ -1129,8 +1053,7 @@ ERROR3: ...@@ -1129,8 +1053,7 @@ ERROR3:
ERROR2: ERROR2:
kfree(data); kfree(data);
ERROR1: ERROR1:
if (is_isa) release_region(isa_address, IT87_EXTENT);
release_region(address, IT87_EXTENT);
ERROR0: ERROR0:
return err; return err;
} }
...@@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client) ...@@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client)
if ((err = i2c_detach_client(client))) if ((err = i2c_detach_client(client)))
return err; return err;
if(i2c_is_isa_client(client)) release_region(client->addr, IT87_EXTENT);
release_region(client->addr, IT87_EXTENT);
kfree(data); kfree(data);
return 0; return 0;
} }
/* The SMBus locks itself, but ISA access must be locked explicitly! /* ISA access must be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
would slow down the IT87 access and should not be necessary. */ would slow down the IT87 access and should not be necessary. */
static int it87_read_value(struct i2c_client *client, u8 reg) static int it87_read_value(struct i2c_client *client, u8 reg)
{ {
struct it87_data *data = i2c_get_clientdata(client); struct it87_data *data = i2c_get_clientdata(client);
int res; int res;
if (i2c_is_isa_client(client)) {
mutex_lock(&data->lock); mutex_lock(&data->lock);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
res = inb_p(client->addr + IT87_DATA_REG_OFFSET); res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
mutex_unlock(&data->lock); mutex_unlock(&data->lock);
return res;
} else return res;
return i2c_smbus_read_byte_data(client, reg);
} }
/* The SMBus locks itself, but ISA access muse be locked explicitly! /* ISA access must be locked explicitly!
We don't want to lock the whole ISA bus, so we lock each client
separately.
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks, We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
would slow down the IT87 access and should not be necessary. */ would slow down the IT87 access and should not be necessary. */
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value) static void it87_write_value(struct i2c_client *client, u8 reg, u8 value)
{ {
struct it87_data *data = i2c_get_clientdata(client); struct it87_data *data = i2c_get_clientdata(client);
if (i2c_is_isa_client(client)) { mutex_lock(&data->lock);
mutex_lock(&data->lock); outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET); outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
outb_p(value, client->addr + IT87_DATA_REG_OFFSET); mutex_unlock(&data->lock);
mutex_unlock(&data->lock);
return 0;
} else
return i2c_smbus_write_byte_data(client, reg, value);
} }
/* Return 1 if and only if the PWM interface is safe to use */ /* Return 1 if and only if the PWM interface is safe to use */
...@@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void) ...@@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void)
{ {
int res; int res;
res = i2c_add_driver(&it87_driver); if ((res = it87_find(&isa_address)))
if (res)
return res; return res;
return i2c_isa_add_driver(&it87_isa_driver);
if (!it87_find(&isa_address)) {
res = i2c_isa_add_driver(&it87_isa_driver);
if (res) {
i2c_del_driver(&it87_driver);
return res;
}
}
return 0;
} }
static void __exit sm_it87_exit(void) static void __exit sm_it87_exit(void)
{ {
if (isa_address) i2c_isa_del_driver(&it87_isa_driver);
i2c_isa_del_driver(&it87_isa_driver);
i2c_del_driver(&it87_driver);
} }
......
/* /*
* k8temp.c - Linux kernel module for hardware monitoring * k8temp.c - Linux kernel module for hardware monitoring
* *
* Copyright (C) 2006 Rudolf Marek <r.marek@sh.cvut.cz> * Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
* *
* Inspired from the w83785 and amd756 drivers. * Inspired from the w83785 and amd756 drivers.
* *
...@@ -286,7 +286,7 @@ static void __exit k8temp_exit(void) ...@@ -286,7 +286,7 @@ static void __exit k8temp_exit(void)
pci_unregister_driver(&k8temp_driver); pci_unregister_driver(&k8temp_driver);
} }
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>"); MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
MODULE_DESCRIPTION("AMD K8 core temperature monitor"); MODULE_DESCRIPTION("AMD K8 core temperature monitor");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
......
...@@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter) ...@@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter)
(i&0x02) ? "external" : "internal"); (i&0x02) ? "external" : "internal");
data->vid_conf = confreg[3]; data->vid_conf = confreg[3];
data->vrm = 90; data->vrm = vid_which_vrm();
} }
/* Fan clock dividers may be needed before any data is read */ /* Fan clock dividers may be needed before any data is read */
......
/*
* pc87427.c - hardware monitoring driver for the
* National Semiconductor PC87427 Super-I/O chip
* Copyright (C) 2006 Jean Delvare <khali@linux-fr.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Supports the following chips:
*
* Chip #vin #fan #pwm #temp devid
* PC87427 - 8 - - 0xF2
*
* This driver assumes that no more than one chip is present.
* Only fan inputs are supported so far, although the chip can do much more.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <asm/io.h>
static struct platform_device *pdev;
#define DRVNAME "pc87427"
/* The lock mutex protects both the I/O accesses (needed because the
device is using banked registers) and the register cache (needed to keep
the data in the registers and the cache in sync at any time). */
struct pc87427_data {
struct class_device *class_dev;
struct mutex lock;
int address[2];
const char *name;
unsigned long last_updated; /* in jiffies */
u8 fan_enabled; /* bit vector */
u16 fan[8]; /* register values */
u16 fan_min[8]; /* register values */
u8 fan_status[8]; /* register values */
};
/*
* Super-I/O registers and operations
*/
#define SIOREG_LDSEL 0x07 /* Logical device select */
#define SIOREG_DEVID 0x20 /* Device ID */
#define SIOREG_ACT 0x30 /* Device activation */
#define SIOREG_MAP 0x50 /* I/O or memory mapping */
#define SIOREG_IOBASE 0x60 /* I/O base address */
static const u8 logdev[2] = { 0x09, 0x14 };
static const char *logdev_str[2] = { DRVNAME " FMC", DRVNAME " HMC" };
#define LD_FAN 0
#define LD_IN 1
#define LD_TEMP 1
static inline void superio_outb(int sioaddr, int reg, int val)
{
outb(reg, sioaddr);
outb(val, sioaddr + 1);
}
static inline int superio_inb(int sioaddr, int reg)
{
outb(reg, sioaddr);
return inb(sioaddr + 1);
}
static inline void superio_exit(int sioaddr)
{
outb(0x02, sioaddr);
outb(0x02, sioaddr + 1);
}
/*
* Logical devices
*/
#define REGION_LENGTH 32
#define PC87427_REG_BANK 0x0f
#define BANK_FM(nr) (nr)
#define BANK_FT(nr) (0x08 + (nr))
#define BANK_FC(nr) (0x10 + (nr) * 2)
/*
* I/O access functions
*/
/* ldi is the logical device index */
static inline int pc87427_read8(struct pc87427_data *data, u8 ldi, u8 reg)
{
return inb(data->address[ldi] + reg);
}
/* Must be called with data->lock held, except during init */
static inline int pc87427_read8_bank(struct pc87427_data *data, u8 ldi,
u8 bank, u8 reg)
{
outb(bank, data->address[ldi] + PC87427_REG_BANK);
return inb(data->address[ldi] + reg);
}
/* Must be called with data->lock held, except during init */
static inline void pc87427_write8_bank(struct pc87427_data *data, u8 ldi,
u8 bank, u8 reg, u8 value)
{
outb(bank, data->address[ldi] + PC87427_REG_BANK);
outb(value, data->address[ldi] + reg);
}
/*
* Fan registers and conversions
*/
/* fan data registers are 16-bit wide */
#define PC87427_REG_FAN 0x12
#define PC87427_REG_FAN_MIN 0x14
#define PC87427_REG_FAN_STATUS 0x10
#define FAN_STATUS_STALL (1 << 3)
#define FAN_STATUS_LOSPD (1 << 1)
#define FAN_STATUS_MONEN (1 << 0)
/* Dedicated function to read all registers related to a given fan input.
This saves us quite a few locks and bank selections.
Must be called with data->lock held.
nr is from 0 to 7 */
static void pc87427_readall_fan(struct pc87427_data *data, u8 nr)
{
int iobase = data->address[LD_FAN];
outb(BANK_FM(nr), iobase + PC87427_REG_BANK);
data->fan[nr] = inw(iobase + PC87427_REG_FAN);
data->fan_min[nr] = inw(iobase + PC87427_REG_FAN_MIN);
data->fan_status[nr] = inb(iobase + PC87427_REG_FAN_STATUS);
/* Clear fan alarm bits */
outb(data->fan_status[nr], iobase + PC87427_REG_FAN_STATUS);
}
/* The 2 LSB of fan speed registers are used for something different.
The actual 2 LSB of the measurements are not available. */
static inline unsigned long fan_from_reg(u16 reg)
{
reg &= 0xfffc;
if (reg == 0x0000 || reg == 0xfffc)
return 0;
return 5400000UL / reg;
}
/* The 2 LSB of the fan speed limit registers are not significant. */
static inline u16 fan_to_reg(unsigned long val)
{
if (val < 83UL)
return 0xffff;
if (val >= 1350000UL)
return 0x0004;
return ((1350000UL + val / 2) / val) << 2;
}
/*
* Data interface
*/
static struct pc87427_data *pc87427_update_device(struct device *dev)
{
struct pc87427_data *data = dev_get_drvdata(dev);
int i;
mutex_lock(&data->lock);
if (!time_after(jiffies, data->last_updated + HZ)
&& data->last_updated)
goto done;
/* Fans */
for (i = 0; i < 8; i++) {
if (!(data->fan_enabled & (1 << i)))
continue;
pc87427_readall_fan(data, i);
}
data->last_updated = jiffies;
done:
mutex_unlock(&data->lock);
return data;
}
static ssize_t show_fan_input(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct pc87427_data *data = pc87427_update_device(dev);
int nr = attr->index;
return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr]));
}
static ssize_t show_fan_min(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct pc87427_data *data = pc87427_update_device(dev);
int nr = attr->index;
return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr]));
}
static ssize_t show_fan_alarm(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct pc87427_data *data = pc87427_update_device(dev);
int nr = attr->index;
return sprintf(buf, "%d\n", !!(data->fan_status[nr]
& FAN_STATUS_LOSPD));
}
static ssize_t show_fan_fault(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
struct pc87427_data *data = pc87427_update_device(dev);
int nr = attr->index;
return sprintf(buf, "%d\n", !!(data->fan_status[nr]
& FAN_STATUS_STALL));
}
static ssize_t set_fan_min(struct device *dev, struct device_attribute
*devattr, const char *buf, size_t count)
{
struct pc87427_data *data = dev_get_drvdata(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
int nr = attr->index;
unsigned long val = simple_strtoul(buf, NULL, 10);
int iobase = data->address[LD_FAN];
mutex_lock(&data->lock);
outb(BANK_FM(nr), iobase + PC87427_REG_BANK);
/* The low speed limit registers are read-only while monitoring
is enabled, so we have to disable monitoring, then change the
limit, and finally enable monitoring again. */
outb(0, iobase + PC87427_REG_FAN_STATUS);
data->fan_min[nr] = fan_to_reg(val);
outw(data->fan_min[nr], iobase + PC87427_REG_FAN_MIN);
outb(FAN_STATUS_MONEN, iobase + PC87427_REG_FAN_STATUS);
mutex_unlock(&data->lock);
return count;
}
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0);
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1);
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_input, NULL, 2);
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_input, NULL, 3);
static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_input, NULL, 4);
static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_input, NULL, 5);
static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_input, NULL, 6);
static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_input, NULL, 7);
static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 0);
static SENSOR_DEVICE_ATTR(fan2_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 1);
static SENSOR_DEVICE_ATTR(fan3_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 2);
static SENSOR_DEVICE_ATTR(fan4_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 3);
static SENSOR_DEVICE_ATTR(fan5_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 4);
static SENSOR_DEVICE_ATTR(fan6_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 5);
static SENSOR_DEVICE_ATTR(fan7_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 6);
static SENSOR_DEVICE_ATTR(fan8_min, S_IWUSR | S_IRUGO,
show_fan_min, set_fan_min, 7);
static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0);
static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1);
static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2);
static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3);
static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO, show_fan_alarm, NULL, 4);
static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO, show_fan_alarm, NULL, 5);
static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO, show_fan_alarm, NULL, 6);
static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO, show_fan_alarm, NULL, 7);
static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL, 0);
static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, NULL, 1);
static SENSOR_DEVICE_ATTR(fan3_fault, S_IRUGO, show_fan_fault, NULL, 2);
static SENSOR_DEVICE_ATTR(fan4_fault, S_IRUGO, show_fan_fault, NULL, 3);
static SENSOR_DEVICE_ATTR(fan5_fault, S_IRUGO, show_fan_fault, NULL, 4);
static SENSOR_DEVICE_ATTR(fan6_fault, S_IRUGO, show_fan_fault, NULL, 5);
static SENSOR_DEVICE_ATTR(fan7_fault, S_IRUGO, show_fan_fault, NULL, 6);
static SENSOR_DEVICE_ATTR(fan8_fault, S_IRUGO, show_fan_fault, NULL, 7);
static struct attribute *pc87427_attributes_fan[8][5] = {
{
&sensor_dev_attr_fan1_input.dev_attr.attr,
&sensor_dev_attr_fan1_min.dev_attr.attr,
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
&sensor_dev_attr_fan1_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan2_input.dev_attr.attr,
&sensor_dev_attr_fan2_min.dev_attr.attr,
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
&sensor_dev_attr_fan2_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan3_input.dev_attr.attr,
&sensor_dev_attr_fan3_min.dev_attr.attr,
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
&sensor_dev_attr_fan3_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan4_input.dev_attr.attr,
&sensor_dev_attr_fan4_min.dev_attr.attr,
&sensor_dev_attr_fan4_alarm.dev_attr.attr,
&sensor_dev_attr_fan4_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan5_input.dev_attr.attr,
&sensor_dev_attr_fan5_min.dev_attr.attr,
&sensor_dev_attr_fan5_alarm.dev_attr.attr,
&sensor_dev_attr_fan5_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan6_input.dev_attr.attr,
&sensor_dev_attr_fan6_min.dev_attr.attr,
&sensor_dev_attr_fan6_alarm.dev_attr.attr,
&sensor_dev_attr_fan6_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan7_input.dev_attr.attr,
&sensor_dev_attr_fan7_min.dev_attr.attr,
&sensor_dev_attr_fan7_alarm.dev_attr.attr,
&sensor_dev_attr_fan7_fault.dev_attr.attr,
NULL
}, {
&sensor_dev_attr_fan8_input.dev_attr.attr,
&sensor_dev_attr_fan8_min.dev_attr.attr,
&sensor_dev_attr_fan8_alarm.dev_attr.attr,
&sensor_dev_attr_fan8_fault.dev_attr.attr,
NULL
}
};
static const struct attribute_group pc87427_group_fan[8] = {
{ .attrs = pc87427_attributes_fan[0] },
{ .attrs = pc87427_attributes_fan[1] },
{ .attrs = pc87427_attributes_fan[2] },
{ .attrs = pc87427_attributes_fan[3] },
{ .attrs = pc87427_attributes_fan[4] },
{ .attrs = pc87427_attributes_fan[5] },
{ .attrs = pc87427_attributes_fan[6] },
{ .attrs = pc87427_attributes_fan[7] },
};
static ssize_t show_name(struct device *dev, struct device_attribute
*devattr, char *buf)
{
struct pc87427_data *data = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", data->name);
}
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
/*
* Device detection, attach and detach
*/
static void __devinit pc87427_init_device(struct device *dev)
{
struct pc87427_data *data = dev_get_drvdata(dev);
int i;
u8 reg;
/* The FMC module should be ready */
reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK);
if (!(reg & 0x80))
dev_warn(dev, "FMC module not ready!\n");
/* Check which fans are enabled */
for (i = 0; i < 8; i++) {
reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i),
PC87427_REG_FAN_STATUS);
if (reg & FAN_STATUS_MONEN)
data->fan_enabled |= (1 << i);
}
if (!data->fan_enabled) {
dev_dbg(dev, "Enabling all fan inputs\n");
for (i = 0; i < 8; i++)
pc87427_write8_bank(data, LD_FAN, BANK_FM(i),
PC87427_REG_FAN_STATUS,
FAN_STATUS_MONEN);
data->fan_enabled = 0xff;
}
}
static int __devinit pc87427_probe(struct platform_device *pdev)
{
struct pc87427_data *data;
struct resource *res;
int i, err;
if (!(data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL))) {
err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Out of memory\n");
goto exit;
}
/* This will need to be revisited when we add support for
temperature and voltage monitoring. */
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
data->address[0] = res->start;
mutex_init(&data->lock);
data->name = "pc87427";
platform_set_drvdata(pdev, data);
pc87427_init_device(&pdev->dev);
/* Register sysfs hooks */
if ((err = device_create_file(&pdev->dev, &dev_attr_name)))
goto exit_kfree;
for (i = 0; i < 8; i++) {
if (!(data->fan_enabled & (1 << i)))
continue;
if ((err = sysfs_create_group(&pdev->dev.kobj,
&pc87427_group_fan[i])))
goto exit_remove_files;
}
data->class_dev = hwmon_device_register(&pdev->dev);
if (IS_ERR(data->class_dev)) {
err = PTR_ERR(data->class_dev);
dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
goto exit_remove_files;
}
return 0;
exit_remove_files:
for (i = 0; i < 8; i++) {
if (!(data->fan_enabled & (1 << i)))
continue;
sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
}
exit_kfree:
platform_set_drvdata(pdev, NULL);
kfree(data);
exit:
return err;
}
static int __devexit pc87427_remove(struct platform_device *pdev)
{
struct pc87427_data *data = platform_get_drvdata(pdev);
int i;
platform_set_drvdata(pdev, NULL);
hwmon_device_unregister(data->class_dev);
device_remove_file(&pdev->dev, &dev_attr_name);
for (i = 0; i < 8; i++) {
if (!(data->fan_enabled & (1 << i)))
continue;
sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
}
kfree(data);
return 0;
}
static struct platform_driver pc87427_driver = {
.driver = {
.owner = THIS_MODULE,
.name = DRVNAME,
},
.probe = pc87427_probe,
.remove = __devexit_p(pc87427_remove),
};
static int __init pc87427_device_add(unsigned short address)
{
struct resource res = {
.start = address,
.end = address + REGION_LENGTH - 1,
.name = logdev_str[0],
.flags = IORESOURCE_IO,
};
int err;
pdev = platform_device_alloc(DRVNAME, address);
if (!pdev) {
err = -ENOMEM;
printk(KERN_ERR DRVNAME ": Device allocation failed\n");
goto exit;
}
err = platform_device_add_resources(pdev, &res, 1);
if (err) {
printk(KERN_ERR DRVNAME ": Device resource addition failed "
"(%d)\n", err);
goto exit_device_put;
}
err = platform_device_add(pdev);
if (err) {
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
err);
goto exit_device_put;
}
return 0;
exit_device_put:
platform_device_put(pdev);
exit:
return err;
}
static int __init pc87427_find(int sioaddr, unsigned short *address)
{
u16 val;
int i, err = 0;
/* Identify device */
val = superio_inb(sioaddr, SIOREG_DEVID);
if (val != 0xf2) { /* PC87427 */
err = -ENODEV;
goto exit;
}
for (i = 0; i < 2; i++) {
address[i] = 0;
/* Select logical device */
superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]);
val = superio_inb(sioaddr, SIOREG_ACT);
if (!(val & 0x01)) {
printk(KERN_INFO DRVNAME ": Logical device 0x%02x "
"not activated\n", logdev[i]);
continue;
}
val = superio_inb(sioaddr, SIOREG_MAP);
if (val & 0x01) {
printk(KERN_WARNING DRVNAME ": Logical device 0x%02x "
"is memory-mapped, can't use\n", logdev[i]);
continue;
}
val = (superio_inb(sioaddr, SIOREG_IOBASE) << 8)
| superio_inb(sioaddr, SIOREG_IOBASE + 1);
if (!val) {
printk(KERN_INFO DRVNAME ": I/O base address not set "
"for logical device 0x%02x\n", logdev[i]);
continue;
}
address[i] = val;
}
exit:
superio_exit(sioaddr);
return err;
}
static int __init pc87427_init(void)
{
int err;
unsigned short address[2];
if (pc87427_find(0x2e, address)
&& pc87427_find(0x4e, address))
return -ENODEV;
/* For now the driver only handles fans so we only care about the
first address. */
if (!address[0])
return -ENODEV;
err = platform_driver_register(&pc87427_driver);
if (err)
goto exit;
/* Sets global pdev as a side effect */
err = pc87427_device_add(address[0]);
if (err)
goto exit_driver;
return 0;
exit_driver:
platform_driver_unregister(&pc87427_driver);
exit:
return err;
}
static void __exit pc87427_exit(void)
{
platform_device_unregister(pdev);
platform_driver_unregister(&pc87427_driver);
}
MODULE_AUTHOR("Jean Delvare <khali@linux-fr.org>");
MODULE_DESCRIPTION("PC87427 hardware monitoring driver");
MODULE_LICENSE("GPL");
module_init(pc87427_init);
module_exit(pc87427_exit);
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
the Winbond W83627EHF Super-I/O chip the Winbond W83627EHF Super-I/O chip
Copyright (C) 2005 Jean Delvare <khali@linux-fr.org> Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
Copyright (C) 2006 Yuan Mu (Winbond), Copyright (C) 2006 Yuan Mu (Winbond),
Rudolf Marek <r.marek@sh.cvut.cz> Rudolf Marek <r.marek@assembler.cz>
David Hubbard <david.c.hubbard@gmail.com> David Hubbard <david.c.hubbard@gmail.com>
Shamelessly ripped from the w83627hf driver Shamelessly ripped from the w83627hf driver
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
monitoring monitoring
Copyright (C) 2004, 2005 Winbond Electronics Corp. Copyright (C) 2004, 2005 Winbond Electronics Corp.
Chunhao Huang <DZShen@Winbond.com.tw>, Chunhao Huang <DZShen@Winbond.com.tw>,
Rudolf Marek <r.marek@sh.cvut.cz> Rudolf Marek <r.marek@assembler.cz>
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
......
/*
w83793.c - Linux kernel driver for hardware monitoring
Copyright (C) 2006 Winbond Electronics Corp.
Yuan Mu
Rudolf Marek <r.marek@assembler.cz>
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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
/*
Supports following chips:
Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA
w83793 10 12 8 6 0x7b 0x5ca3 yes no
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-vid.h>
#include <linux/hwmon-sysfs.h>
#include <linux/err.h>
#include <linux/mutex.h>
/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END };
/* Insmod parameters */
I2C_CLIENT_INSMOD_1(w83793);
I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: "
"{bus, clientaddr, subclientaddr1, subclientaddr2}");
static int reset;
module_param(reset, bool, 0);
MODULE_PARM_DESC(reset, "Set to 1 to reset chip, not recommended");
/*
Address 0x00, 0x0d, 0x0e, 0x0f in all three banks are reserved
as ID, Bank Select registers
*/
#define W83793_REG_BANKSEL 0x00
#define W83793_REG_VENDORID 0x0d
#define W83793_REG_CHIPID 0x0e
#define W83793_REG_DEVICEID 0x0f
#define W83793_REG_CONFIG 0x40
#define W83793_REG_MFC 0x58
#define W83793_REG_FANIN_CTRL 0x5c
#define W83793_REG_FANIN_SEL 0x5d
#define W83793_REG_I2C_ADDR 0x0b
#define W83793_REG_I2C_SUBADDR 0x0c
#define W83793_REG_VID_INA 0x05
#define W83793_REG_VID_INB 0x06
#define W83793_REG_VID_LATCHA 0x07
#define W83793_REG_VID_LATCHB 0x08
#define W83793_REG_VID_CTRL 0x59
static u16 W83793_REG_TEMP_MODE[2] = { 0x5e, 0x5f };
#define TEMP_READ 0
#define TEMP_CRIT 1
#define TEMP_CRIT_HYST 2
#define TEMP_WARN 3
#define TEMP_WARN_HYST 4
/* only crit and crit_hyst affect real-time alarm status
current crit crit_hyst warn warn_hyst */
static u16 W83793_REG_TEMP[][5] = {
{0x1c, 0x78, 0x79, 0x7a, 0x7b},
{0x1d, 0x7c, 0x7d, 0x7e, 0x7f},
{0x1e, 0x80, 0x81, 0x82, 0x83},
{0x1f, 0x84, 0x85, 0x86, 0x87},
{0x20, 0x88, 0x89, 0x8a, 0x8b},
{0x21, 0x8c, 0x8d, 0x8e, 0x8f},
};
#define W83793_REG_TEMP_LOW_BITS 0x22
#define W83793_REG_BEEP(index) (0x53 + (index))
#define W83793_REG_ALARM(index) (0x4b + (index))
#define W83793_REG_CLR_CHASSIS 0x4a /* SMI MASK4 */
#define W83793_REG_IRQ_CTRL 0x50
#define W83793_REG_OVT_CTRL 0x51
#define W83793_REG_OVT_BEEP 0x52
#define IN_READ 0
#define IN_MAX 1
#define IN_LOW 2
static const u16 W83793_REG_IN[][3] = {
/* Current, High, Low */
{0x10, 0x60, 0x61}, /* Vcore A */
{0x11, 0x62, 0x63}, /* Vcore B */
{0x12, 0x64, 0x65}, /* Vtt */
{0x14, 0x6a, 0x6b}, /* VSEN1 */
{0x15, 0x6c, 0x6d}, /* VSEN2 */
{0x16, 0x6e, 0x6f}, /* +3VSEN */
{0x17, 0x70, 0x71}, /* +12VSEN */
{0x18, 0x72, 0x73}, /* 5VDD */
{0x19, 0x74, 0x75}, /* 5VSB */
{0x1a, 0x76, 0x77}, /* VBAT */
};
/* Low Bits of Vcore A/B Vtt Read/High/Low */
static const u16 W83793_REG_IN_LOW_BITS[] = { 0x1b, 0x68, 0x69 };
static u8 scale_in[] = { 2, 2, 2, 16, 16, 16, 8, 24, 24, 16 };
#define W83793_REG_FAN(index) (0x23 + 2 * (index)) /* High byte */
#define W83793_REG_FAN_MIN(index) (0x90 + 2 * (index)) /* High byte */
#define W83793_REG_PWM_DEFAULT 0xb2
#define W83793_REG_PWM_ENABLE 0x207
#define W83793_REG_PWM_UPTIME 0xc3 /* Unit in 0.1 second */
#define W83793_REG_PWM_DOWNTIME 0xc4 /* Unit in 0.1 second */
#define W83793_REG_TEMP_CRITICAL 0xc5
#define PWM_DUTY 0
#define PWM_START 1
#define PWM_NONSTOP 2
#define W83793_REG_PWM(index, nr) (((nr) == 0 ? 0xb3 : \
(nr) == 1 ? 0x220 : 0x218) + (index))
/* bit field, fan1 is bit0, fan2 is bit1 ... */
#define W83793_REG_TEMP_FAN_MAP(index) (0x201 + (index))
#define W83793_REG_TEMP_TOL(index) (0x208 + (index))
#define W83793_REG_TEMP_CRUISE(index) (0x210 + (index))
#define W83793_REG_PWM_STOP_TIME(index) (0x228 + (index))
#define W83793_REG_SF2_TEMP(index, nr) (0x230 + ((index) << 4) + (nr))
#define W83793_REG_SF2_PWM(index, nr) (0x238 + ((index) << 4) + (nr))
static inline unsigned long FAN_FROM_REG(u16 val)
{
if ((val >= 0xfff) || (val == 0))
return 0;
return (1350000UL / val);
}
static inline u16 FAN_TO_REG(long rpm)
{
if (rpm <= 0)
return 0x0fff;
return SENSORS_LIMIT((1350000 + (rpm >> 1)) / rpm, 1, 0xffe);
}
static inline unsigned long TIME_FROM_REG(u8 reg)
{
return (reg * 100);
}
static inline u8 TIME_TO_REG(unsigned long val)
{
return SENSORS_LIMIT((val + 50) / 100, 0, 0xff);
}
static inline long TEMP_FROM_REG(s8 reg)
{
return (reg * 1000);
}
static inline s8 TEMP_TO_REG(long val, s8 min, s8 max)
{
return SENSORS_LIMIT((val + (val < 0 ? -500 : 500)) / 1000, min, max);
}
struct w83793_data {
struct i2c_client client;
struct i2c_client *lm75[2];
struct class_device *class_dev;
struct mutex update_lock;
char valid; /* !=0 if following fields are valid */
unsigned long last_updated; /* In jiffies */
unsigned long last_nonvolatile; /* In jiffies, last time we update the
nonvolatile registers */
u8 bank;
u8 vrm;
u8 vid[2];
u8 in[10][3]; /* Register value, read/high/low */
u8 in_low_bits[3]; /* Additional resolution for VCore A/B Vtt */
u16 has_fan; /* Only fan1- fan5 has own pins */
u16 fan[12]; /* Register value combine */
u16 fan_min[12]; /* Register value combine */
s8 temp[6][5]; /* current, crit, crit_hyst,warn, warn_hyst */
u8 temp_low_bits; /* Additional resolution TD1-TD4 */
u8 temp_mode[2]; /* byte 0: Temp D1-D4 mode each has 2 bits
byte 1: Temp R1,R2 mode, each has 1 bit */
u8 temp_critical; /* If reached all fan will be at full speed */
u8 temp_fan_map[6]; /* Temp controls which pwm fan, bit field */
u8 has_pwm;
u8 pwm_enable; /* Register value, each Temp has 1 bit */
u8 pwm_uptime; /* Register value */
u8 pwm_downtime; /* Register value */
u8 pwm_default; /* All fan default pwm, next poweron valid */
u8 pwm[8][3]; /* Register value */
u8 pwm_stop_time[8];
u8 temp_cruise[6];
u8 alarms[5]; /* realtime status registers */
u8 beeps[5];
u8 beep_enable;
u8 tolerance[3]; /* Temp tolerance(Smart Fan I/II) */
u8 sf2_pwm[6][7]; /* Smart FanII: Fan duty cycle */
u8 sf2_temp[6][7]; /* Smart FanII: Temp level point */
};
static u8 w83793_read_value(struct i2c_client *client, u16 reg);
static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value);
static int w83793_attach_adapter(struct i2c_adapter *adapter);
static int w83793_detect(struct i2c_adapter *adapter, int address, int kind);
static int w83793_detach_client(struct i2c_client *client);
static void w83793_init_client(struct i2c_client *client);
static void w83793_update_nonvolatile(struct device *dev);
static struct w83793_data *w83793_update_device(struct device *dev);
static struct i2c_driver w83793_driver = {
.driver = {
.name = "w83793",
},
.attach_adapter = w83793_attach_adapter,
.detach_client = w83793_detach_client,
};
static ssize_t
show_vrm(struct device *dev, struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
return sprintf(buf, "%d\n", data->vrm);
}
static ssize_t
show_vid(struct device *dev, struct device_attribute *attr, char *buf)
{
struct w83793_data *data = w83793_update_device(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int index = sensor_attr->index;
return sprintf(buf, "%d\n", vid_from_reg(data->vid[index], data->vrm));
}
static ssize_t
store_vrm(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
data->vrm = simple_strtoul(buf, NULL, 10);
return count;
}
#define ALARM_STATUS 0
#define BEEP_ENABLE 1
static ssize_t
show_alarm_beep(struct device *dev, struct device_attribute *attr, char *buf)
{
struct w83793_data *data = w83793_update_device(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index >> 3;
int bit = sensor_attr->index & 0x07;
u8 val;
if (ALARM_STATUS == nr) {
val = (data->alarms[index] >> (bit)) & 1;
} else { /* BEEP_ENABLE */
val = (data->beeps[index] >> (bit)) & 1;
}
return sprintf(buf, "%u\n", val);
}
static ssize_t
store_beep(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int index = sensor_attr->index >> 3;
int shift = sensor_attr->index & 0x07;
u8 beep_bit = 1 << shift;
u8 val;
val = simple_strtoul(buf, NULL, 10);
if (val != 0 && val != 1)
return -EINVAL;
mutex_lock(&data->update_lock);
data->beeps[index] = w83793_read_value(client, W83793_REG_BEEP(index));
data->beeps[index] &= ~beep_bit;
data->beeps[index] |= val << shift;
w83793_write_value(client, W83793_REG_BEEP(index), data->beeps[index]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t
show_beep_enable(struct device *dev, struct device_attribute *attr, char *buf)
{
struct w83793_data *data = w83793_update_device(dev);
return sprintf(buf, "%u\n", (data->beep_enable >> 1) & 0x01);
}
static ssize_t
store_beep_enable(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
u8 val = simple_strtoul(buf, NULL, 10);
if (val != 0 && val != 1)
return -EINVAL;
mutex_lock(&data->update_lock);
data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP)
& 0xfd;
data->beep_enable |= val << 1;
w83793_write_value(client, W83793_REG_OVT_BEEP, data->beep_enable);
mutex_unlock(&data->update_lock);
return count;
}
/* Write any value to clear chassis alarm */
static ssize_t
store_chassis_clear(struct device *dev,
struct device_attribute *attr, const char *buf,
size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
u8 val;
mutex_lock(&data->update_lock);
val = w83793_read_value(client, W83793_REG_CLR_CHASSIS);
val |= 0x80;
w83793_write_value(client, W83793_REG_CLR_CHASSIS, val);
mutex_unlock(&data->update_lock);
return count;
}
#define FAN_INPUT 0
#define FAN_MIN 1
static ssize_t
show_fan(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
u16 val;
if (FAN_INPUT == nr) {
val = data->fan[index] & 0x0fff;
} else {
val = data->fan_min[index] & 0x0fff;
}
return sprintf(buf, "%lu\n", FAN_FROM_REG(val));
}
static ssize_t
store_fan_min(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int index = sensor_attr->index;
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
u16 val = FAN_TO_REG(simple_strtoul(buf, NULL, 10));
mutex_lock(&data->update_lock);
data->fan_min[index] = val;
w83793_write_value(client, W83793_REG_FAN_MIN(index),
(val >> 8) & 0xff);
w83793_write_value(client, W83793_REG_FAN_MIN(index) + 1, val & 0xff);
mutex_unlock(&data->update_lock);
return count;
}
#define PWM_DUTY 0
#define PWM_START 1
#define PWM_NONSTOP 2
#define PWM_STOP_TIME 3
static ssize_t
show_pwm(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
struct w83793_data *data = w83793_update_device(dev);
u16 val;
int nr = sensor_attr->nr;
int index = sensor_attr->index;
if (PWM_STOP_TIME == nr)
val = TIME_FROM_REG(data->pwm_stop_time[index]);
else
val = (data->pwm[index][nr] & 0x3f) << 2;
return sprintf(buf, "%d\n", val);
}
static ssize_t
store_pwm(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
u8 val;
mutex_lock(&data->update_lock);
if (PWM_STOP_TIME == nr) {
val = TIME_TO_REG(simple_strtoul(buf, NULL, 10));
data->pwm_stop_time[index] = val;
w83793_write_value(client, W83793_REG_PWM_STOP_TIME(index),
val);
} else {
val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 0xff)
>> 2;
data->pwm[index][nr] =
w83793_read_value(client, W83793_REG_PWM(index, nr)) & 0xc0;
data->pwm[index][nr] |= val;
w83793_write_value(client, W83793_REG_PWM(index, nr),
data->pwm[index][nr]);
}
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t
show_temp(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
long temp = TEMP_FROM_REG(data->temp[index][nr]);
if (TEMP_READ == nr && index < 4) { /* Only TD1-TD4 have low bits */
int low = ((data->temp_low_bits >> (index * 2)) & 0x03) * 250;
temp += temp > 0 ? low : -low;
}
return sprintf(buf, "%ld\n", temp);
}
static ssize_t
store_temp(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
long tmp = simple_strtol(buf, NULL, 10);
mutex_lock(&data->update_lock);
data->temp[index][nr] = TEMP_TO_REG(tmp, -128, 127);
w83793_write_value(client, W83793_REG_TEMP[index][nr],
data->temp[index][nr]);
mutex_unlock(&data->update_lock);
return count;
}
/*
TD1-TD4
each has 4 mode:(2 bits)
0: Stop monitor
1: Use internal temp sensor(default)
2: Use sensor in AMD CPU and get result by AMDSI
3: Use sensor in Intel CPU and get result by PECI
TR1-TR2
each has 2 mode:(1 bit)
0: Disable temp sensor monitor
1: To enable temp sensors monitor
*/
/* 0 disable, 5 AMDSI, 6 PECI */
static u8 TO_TEMP_MODE[] = { 0, 0, 5, 6 };
static ssize_t
show_temp_mode(struct device *dev, struct device_attribute *attr, char *buf)
{
struct w83793_data *data = w83793_update_device(dev);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int index = sensor_attr->index;
u8 mask = (index < 4) ? 0x03 : 0x01;
u8 shift = (index < 4) ? (2 * index) : (index - 4);
u8 tmp;
index = (index < 4) ? 0 : 1;
tmp = (data->temp_mode[index] >> shift) & mask;
/* for the internal sensor, found out if diode or thermistor */
if (tmp == 1) {
tmp = index == 0 ? 3 : 4;
} else {
tmp = TO_TEMP_MODE[tmp];
}
return sprintf(buf, "%d\n", tmp);
}
static ssize_t
store_temp_mode(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int index = sensor_attr->index;
u8 mask = (index < 4) ? 0x03 : 0x01;
u8 shift = (index < 4) ? (2 * index) : (index - 4);
u8 val = simple_strtoul(buf, NULL, 10);
/* transform the sysfs interface values into table above */
if ((val == 5 || val == 6) && (index < 4)) {
val -= 3;
} else if ((val == 3 && index < 4)
|| (val == 4 && index >= 4)
|| val == 0) {
/* transform diode or thermistor into internal enable */
val = !!val;
} else {
return -EINVAL;
}
index = (index < 4) ? 0 : 1;
mutex_lock(&data->update_lock);
data->temp_mode[index] =
w83793_read_value(client, W83793_REG_TEMP_MODE[index]);
data->temp_mode[index] &= ~(mask << shift);
data->temp_mode[index] |= val << shift;
w83793_write_value(client, W83793_REG_TEMP_MODE[index],
data->temp_mode[index]);
mutex_unlock(&data->update_lock);
return count;
}
#define SETUP_PWM_DEFAULT 0
#define SETUP_PWM_UPTIME 1 /* Unit in 0.1s */
#define SETUP_PWM_DOWNTIME 2 /* Unit in 0.1s */
#define SETUP_TEMP_CRITICAL 3
static ssize_t
show_sf_setup(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
struct w83793_data *data = w83793_update_device(dev);
u32 val = 0;
if (SETUP_PWM_DEFAULT == nr) {
val = (data->pwm_default & 0x3f) << 2;
} else if (SETUP_PWM_UPTIME == nr) {
val = TIME_FROM_REG(data->pwm_uptime);
} else if (SETUP_PWM_DOWNTIME == nr) {
val = TIME_FROM_REG(data->pwm_downtime);
} else if (SETUP_TEMP_CRITICAL == nr) {
val = TEMP_FROM_REG(data->temp_critical & 0x7f);
}
return sprintf(buf, "%d\n", val);
}
static ssize_t
store_sf_setup(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
if (SETUP_PWM_DEFAULT == nr) {
data->pwm_default =
w83793_read_value(client, W83793_REG_PWM_DEFAULT) & 0xc0;
data->pwm_default |= SENSORS_LIMIT(simple_strtoul(buf, NULL,
10),
0, 0xff) >> 2;
w83793_write_value(client, W83793_REG_PWM_DEFAULT,
data->pwm_default);
} else if (SETUP_PWM_UPTIME == nr) {
data->pwm_uptime = TIME_TO_REG(simple_strtoul(buf, NULL, 10));
data->pwm_uptime += data->pwm_uptime == 0 ? 1 : 0;
w83793_write_value(client, W83793_REG_PWM_UPTIME,
data->pwm_uptime);
} else if (SETUP_PWM_DOWNTIME == nr) {
data->pwm_downtime = TIME_TO_REG(simple_strtoul(buf, NULL, 10));
data->pwm_downtime += data->pwm_downtime == 0 ? 1 : 0;
w83793_write_value(client, W83793_REG_PWM_DOWNTIME,
data->pwm_downtime);
} else { /* SETUP_TEMP_CRITICAL */
data->temp_critical =
w83793_read_value(client, W83793_REG_TEMP_CRITICAL) & 0x80;
data->temp_critical |= TEMP_TO_REG(simple_strtol(buf, NULL, 10),
0, 0x7f);
w83793_write_value(client, W83793_REG_TEMP_CRITICAL,
data->temp_critical);
}
mutex_unlock(&data->update_lock);
return count;
}
/*
Temp SmartFan control
TEMP_FAN_MAP
Temp channel control which pwm fan, bitfield, bit 0 indicate pwm1...
It's possible two or more temp channels control the same fan, w83793
always prefers to pick the most critical request and applies it to
the related Fan.
It's possible one fan is not in any mapping of 6 temp channels, this
means the fan is manual mode
TEMP_PWM_ENABLE
Each temp channel has its own SmartFan mode, and temp channel
control fans that are set by TEMP_FAN_MAP
0: SmartFanII mode
1: Thermal Cruise Mode
TEMP_CRUISE
Target temperature in thermal cruise mode, w83793 will try to turn
fan speed to keep the temperature of target device around this
temperature.
TEMP_TOLERANCE
If Temp higher or lower than target with this tolerance, w83793
will take actions to speed up or slow down the fan to keep the
temperature within the tolerance range.
*/
#define TEMP_FAN_MAP 0
#define TEMP_PWM_ENABLE 1
#define TEMP_CRUISE 2
#define TEMP_TOLERANCE 3
static ssize_t
show_sf_ctrl(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
u32 val;
if (TEMP_FAN_MAP == nr) {
val = data->temp_fan_map[index];
} else if (TEMP_PWM_ENABLE == nr) {
/* +2 to transfrom into 2 and 3 to conform with sysfs intf */
val = ((data->pwm_enable >> index) & 0x01) + 2;
} else if (TEMP_CRUISE == nr) {
val = TEMP_FROM_REG(data->temp_cruise[index] & 0x7f);
} else { /* TEMP_TOLERANCE */
val = data->tolerance[index >> 1] >> ((index & 0x01) ? 4 : 0);
val = TEMP_FROM_REG(val & 0x0f);
}
return sprintf(buf, "%d\n", val);
}
static ssize_t
store_sf_ctrl(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
u32 val;
mutex_lock(&data->update_lock);
if (TEMP_FAN_MAP == nr) {
val = simple_strtoul(buf, NULL, 10) & 0xff;
w83793_write_value(client, W83793_REG_TEMP_FAN_MAP(index), val);
data->temp_fan_map[index] = val;
} else if (TEMP_PWM_ENABLE == nr) {
val = simple_strtoul(buf, NULL, 10);
if (2 == val || 3 == val) {
data->pwm_enable =
w83793_read_value(client, W83793_REG_PWM_ENABLE);
if (val - 2)
data->pwm_enable |= 1 << index;
else
data->pwm_enable &= ~(1 << index);
w83793_write_value(client, W83793_REG_PWM_ENABLE,
data->pwm_enable);
} else {
mutex_unlock(&data->update_lock);
return -EINVAL;
}
} else if (TEMP_CRUISE == nr) {
data->temp_cruise[index] =
w83793_read_value(client, W83793_REG_TEMP_CRUISE(index));
val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x7f);
data->temp_cruise[index] &= 0x80;
data->temp_cruise[index] |= val;
w83793_write_value(client, W83793_REG_TEMP_CRUISE(index),
data->temp_cruise[index]);
} else { /* TEMP_TOLERANCE */
int i = index >> 1;
u8 shift = (index & 0x01) ? 4 : 0;
data->tolerance[i] =
w83793_read_value(client, W83793_REG_TEMP_TOL(i));
val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x0f);
data->tolerance[i] &= ~(0x0f << shift);
data->tolerance[i] |= val << shift;
w83793_write_value(client, W83793_REG_TEMP_TOL(i),
data->tolerance[i]);
}
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t
show_sf2_pwm(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
return sprintf(buf, "%d\n", (data->sf2_pwm[index][nr] & 0x3f) << 2);
}
static ssize_t
store_sf2_pwm(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
u8 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 0, 0xff) >> 2;
mutex_lock(&data->update_lock);
data->sf2_pwm[index][nr] =
w83793_read_value(client, W83793_REG_SF2_PWM(index, nr)) & 0xc0;
data->sf2_pwm[index][nr] |= val;
w83793_write_value(client, W83793_REG_SF2_PWM(index, nr),
data->sf2_pwm[index][nr]);
mutex_unlock(&data->update_lock);
return count;
}
static ssize_t
show_sf2_temp(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
return sprintf(buf, "%ld\n",
TEMP_FROM_REG(data->sf2_temp[index][nr] & 0x7f));
}
static ssize_t
store_sf2_temp(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
u8 val = TEMP_TO_REG(simple_strtol(buf, NULL, 10), 0, 0x7f);
mutex_lock(&data->update_lock);
data->sf2_temp[index][nr] =
w83793_read_value(client, W83793_REG_SF2_TEMP(index, nr)) & 0x80;
data->sf2_temp[index][nr] |= val;
w83793_write_value(client, W83793_REG_SF2_TEMP(index, nr),
data->sf2_temp[index][nr]);
mutex_unlock(&data->update_lock);
return count;
}
/* only Vcore A/B and Vtt have additional 2 bits precision */
static ssize_t
show_in(struct device *dev, struct device_attribute *attr, char *buf)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct w83793_data *data = w83793_update_device(dev);
u16 val = data->in[index][nr];
if (index < 3) {
val <<= 2;
val += (data->in_low_bits[nr] >> (index * 2)) & 0x3;
}
return sprintf(buf, "%d\n", val * scale_in[index]);
}
static ssize_t
store_in(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct sensor_device_attribute_2 *sensor_attr =
to_sensor_dev_attr_2(attr);
int nr = sensor_attr->nr;
int index = sensor_attr->index;
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
u32 val;
val =
(simple_strtoul(buf, NULL, 10) +
scale_in[index] / 2) / scale_in[index];
mutex_lock(&data->update_lock);
if (index > 2) {
val = SENSORS_LIMIT(val, 0, 255);
} else {
val = SENSORS_LIMIT(val, 0, 0x3FF);
data->in_low_bits[nr] =
w83793_read_value(client, W83793_REG_IN_LOW_BITS[nr]);
data->in_low_bits[nr] &= ~(0x03 << (2 * index));
data->in_low_bits[nr] |= (val & 0x03) << (2 * index);
w83793_write_value(client, W83793_REG_IN_LOW_BITS[nr],
data->in_low_bits[nr]);
val >>= 2;
}
data->in[index][nr] = val;
w83793_write_value(client, W83793_REG_IN[index][nr],
data->in[index][nr]);
mutex_unlock(&data->update_lock);
return count;
}
#define NOT_USED -1
#define SENSOR_ATTR_IN(index) \
SENSOR_ATTR_2(in##index##_input, S_IRUGO, show_in, NULL, \
IN_READ, index), \
SENSOR_ATTR_2(in##index##_max, S_IRUGO | S_IWUSR, show_in, \
store_in, IN_MAX, index), \
SENSOR_ATTR_2(in##index##_min, S_IRUGO | S_IWUSR, show_in, \
store_in, IN_LOW, index), \
SENSOR_ATTR_2(in##index##_alarm, S_IRUGO, show_alarm_beep, \
NULL, ALARM_STATUS, index + ((index > 2) ? 1 : 0)), \
SENSOR_ATTR_2(in##index##_beep, S_IWUSR | S_IRUGO, \
show_alarm_beep, store_beep, BEEP_ENABLE, \
index + ((index > 2) ? 1 : 0))
#define SENSOR_ATTR_FAN(index) \
SENSOR_ATTR_2(fan##index##_alarm, S_IRUGO, show_alarm_beep, \
NULL, ALARM_STATUS, index + 17), \
SENSOR_ATTR_2(fan##index##_beep, S_IWUSR | S_IRUGO, \
show_alarm_beep, store_beep, BEEP_ENABLE, index + 17), \
SENSOR_ATTR_2(fan##index##_input, S_IRUGO, show_fan, \
NULL, FAN_INPUT, index - 1), \
SENSOR_ATTR_2(fan##index##_min, S_IWUSR | S_IRUGO, \
show_fan, store_fan_min, FAN_MIN, index - 1)
#define SENSOR_ATTR_PWM(index) \
SENSOR_ATTR_2(pwm##index, S_IWUSR | S_IRUGO, show_pwm, \
store_pwm, PWM_DUTY, index - 1), \
SENSOR_ATTR_2(pwm##index##_nonstop, S_IWUSR | S_IRUGO, \
show_pwm, store_pwm, PWM_NONSTOP, index - 1), \
SENSOR_ATTR_2(pwm##index##_start, S_IWUSR | S_IRUGO, \
show_pwm, store_pwm, PWM_START, index - 1), \
SENSOR_ATTR_2(pwm##index##_stop_time, S_IWUSR | S_IRUGO, \
show_pwm, store_pwm, PWM_STOP_TIME, index - 1)
#define SENSOR_ATTR_TEMP(index) \
SENSOR_ATTR_2(temp##index##_type, S_IRUGO | S_IWUSR, \
show_temp_mode, store_temp_mode, NOT_USED, index - 1), \
SENSOR_ATTR_2(temp##index##_input, S_IRUGO, show_temp, \
NULL, TEMP_READ, index - 1), \
SENSOR_ATTR_2(temp##index##_max, S_IRUGO | S_IWUSR, show_temp, \
store_temp, TEMP_CRIT, index - 1), \
SENSOR_ATTR_2(temp##index##_max_hyst, S_IRUGO | S_IWUSR, \
show_temp, store_temp, TEMP_CRIT_HYST, index - 1), \
SENSOR_ATTR_2(temp##index##_warn, S_IRUGO | S_IWUSR, show_temp, \
store_temp, TEMP_WARN, index - 1), \
SENSOR_ATTR_2(temp##index##_warn_hyst, S_IRUGO | S_IWUSR, \
show_temp, store_temp, TEMP_WARN_HYST, index - 1), \
SENSOR_ATTR_2(temp##index##_alarm, S_IRUGO, \
show_alarm_beep, NULL, ALARM_STATUS, index + 11), \
SENSOR_ATTR_2(temp##index##_beep, S_IWUSR | S_IRUGO, \
show_alarm_beep, store_beep, BEEP_ENABLE, index + 11), \
SENSOR_ATTR_2(temp##index##_auto_channels_pwm, \
S_IRUGO | S_IWUSR, show_sf_ctrl, store_sf_ctrl, \
TEMP_FAN_MAP, index - 1), \
SENSOR_ATTR_2(temp##index##_pwm_enable, S_IWUSR | S_IRUGO, \
show_sf_ctrl, store_sf_ctrl, TEMP_PWM_ENABLE, \
index - 1), \
SENSOR_ATTR_2(thermal_cruise##index, S_IRUGO | S_IWUSR, \
show_sf_ctrl, store_sf_ctrl, TEMP_CRUISE, index - 1), \
SENSOR_ATTR_2(tolerance##index, S_IRUGO | S_IWUSR, show_sf_ctrl,\
store_sf_ctrl, TEMP_TOLERANCE, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point1_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 0, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point2_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 1, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point3_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 2, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point4_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 3, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point5_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 4, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point6_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 5, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point7_pwm, S_IRUGO | S_IWUSR, \
show_sf2_pwm, store_sf2_pwm, 6, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point1_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 0, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point2_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 1, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point3_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 2, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point4_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 3, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point5_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 4, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point6_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 5, index - 1), \
SENSOR_ATTR_2(temp##index##_auto_point7_temp, S_IRUGO | S_IWUSR,\
show_sf2_temp, store_sf2_temp, 6, index - 1)
static struct sensor_device_attribute_2 w83793_sensor_attr_2[] = {
SENSOR_ATTR_IN(0),
SENSOR_ATTR_IN(1),
SENSOR_ATTR_IN(2),
SENSOR_ATTR_IN(3),
SENSOR_ATTR_IN(4),
SENSOR_ATTR_IN(5),
SENSOR_ATTR_IN(6),
SENSOR_ATTR_IN(7),
SENSOR_ATTR_IN(8),
SENSOR_ATTR_IN(9),
SENSOR_ATTR_TEMP(1),
SENSOR_ATTR_TEMP(2),
SENSOR_ATTR_TEMP(3),
SENSOR_ATTR_TEMP(4),
SENSOR_ATTR_TEMP(5),
SENSOR_ATTR_TEMP(6),
SENSOR_ATTR_FAN(1),
SENSOR_ATTR_FAN(2),
SENSOR_ATTR_FAN(3),
SENSOR_ATTR_FAN(4),
SENSOR_ATTR_FAN(5),
SENSOR_ATTR_PWM(1),
SENSOR_ATTR_PWM(2),
SENSOR_ATTR_PWM(3),
};
/* Fan6-Fan12 */
static struct sensor_device_attribute_2 w83793_left_fan[] = {
SENSOR_ATTR_FAN(6),
SENSOR_ATTR_FAN(7),
SENSOR_ATTR_FAN(8),
SENSOR_ATTR_FAN(9),
SENSOR_ATTR_FAN(10),
SENSOR_ATTR_FAN(11),
SENSOR_ATTR_FAN(12),
};
/* Pwm4-Pwm8 */
static struct sensor_device_attribute_2 w83793_left_pwm[] = {
SENSOR_ATTR_PWM(4),
SENSOR_ATTR_PWM(5),
SENSOR_ATTR_PWM(6),
SENSOR_ATTR_PWM(7),
SENSOR_ATTR_PWM(8),
};
static struct sensor_device_attribute_2 sda_single_files[] = {
SENSOR_ATTR_2(cpu0_vid, S_IRUGO, show_vid, NULL, NOT_USED, 0),
SENSOR_ATTR_2(cpu1_vid, S_IRUGO, show_vid, NULL, NOT_USED, 1),
SENSOR_ATTR_2(vrm, S_IWUSR | S_IRUGO, show_vrm, store_vrm,
NOT_USED, NOT_USED),
SENSOR_ATTR_2(chassis, S_IWUSR | S_IRUGO, show_alarm_beep,
store_chassis_clear, ALARM_STATUS, 30),
SENSOR_ATTR_2(beep_enable, S_IWUSR | S_IRUGO, show_beep_enable,
store_beep_enable, NOT_USED, NOT_USED),
SENSOR_ATTR_2(pwm_default, S_IWUSR | S_IRUGO, show_sf_setup,
store_sf_setup, SETUP_PWM_DEFAULT, NOT_USED),
SENSOR_ATTR_2(pwm_uptime, S_IWUSR | S_IRUGO, show_sf_setup,
store_sf_setup, SETUP_PWM_UPTIME, NOT_USED),
SENSOR_ATTR_2(pwm_downtime, S_IWUSR | S_IRUGO, show_sf_setup,
store_sf_setup, SETUP_PWM_DOWNTIME, NOT_USED),
SENSOR_ATTR_2(temp_critical, S_IWUSR | S_IRUGO, show_sf_setup,
store_sf_setup, SETUP_TEMP_CRITICAL, NOT_USED),
};
static void w83793_init_client(struct i2c_client *client)
{
if (reset) {
w83793_write_value(client, W83793_REG_CONFIG, 0x80);
}
/* Start monitoring */
w83793_write_value(client, W83793_REG_CONFIG,
w83793_read_value(client, W83793_REG_CONFIG) | 0x01);
}
static int w83793_attach_adapter(struct i2c_adapter *adapter)
{
if (!(adapter->class & I2C_CLASS_HWMON))
return 0;
return i2c_probe(adapter, &addr_data, w83793_detect);
}
static int w83793_detach_client(struct i2c_client *client)
{
struct w83793_data *data = i2c_get_clientdata(client);
struct device *dev = &client->dev;
int err, i;
/* main client */
if (data) {
hwmon_device_unregister(data->class_dev);
for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++)
device_remove_file(dev,
&w83793_sensor_attr_2[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(sda_single_files); i++)
device_remove_file(dev, &sda_single_files[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++)
device_remove_file(dev, &w83793_left_fan[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++)
device_remove_file(dev, &w83793_left_pwm[i].dev_attr);
}
if ((err = i2c_detach_client(client)))
return err;
/* main client */
if (data)
kfree(data);
/* subclient */
else
kfree(client);
return 0;
}
static int
w83793_create_subclient(struct i2c_adapter *adapter,
struct i2c_client *client, int addr,
struct i2c_client **sub_cli)
{
int err = 0;
struct i2c_client *sub_client;
(*sub_cli) = sub_client =
kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
if (!(sub_client)) {
return -ENOMEM;
}
sub_client->addr = 0x48 + addr;
i2c_set_clientdata(sub_client, NULL);
sub_client->adapter = adapter;
sub_client->driver = &w83793_driver;
strlcpy(sub_client->name, "w83793 subclient", I2C_NAME_SIZE);
if ((err = i2c_attach_client(sub_client))) {
dev_err(&client->dev, "subclient registration "
"at address 0x%x failed\n", sub_client->addr);
kfree(sub_client);
}
return err;
}
static int
w83793_detect_subclients(struct i2c_adapter *adapter, int address,
int kind, struct i2c_client *client)
{
int i, id, err;
u8 tmp;
struct w83793_data *data = i2c_get_clientdata(client);
id = i2c_adapter_id(adapter);
if (force_subclients[0] == id && force_subclients[1] == address) {
for (i = 2; i <= 3; i++) {
if (force_subclients[i] < 0x48
|| force_subclients[i] > 0x4f) {
dev_err(&client->dev,
"invalid subclient "
"address %d; must be 0x48-0x4f\n",
force_subclients[i]);
err = -EINVAL;
goto ERROR_SC_0;
}
}
w83793_write_value(client, W83793_REG_I2C_SUBADDR,
(force_subclients[2] & 0x07) |
((force_subclients[3] & 0x07) << 4));
}
tmp = w83793_read_value(client, W83793_REG_I2C_SUBADDR);
if (!(tmp & 0x08)) {
err =
w83793_create_subclient(adapter, client, tmp & 0x7,
&data->lm75[0]);
if (err < 0)
goto ERROR_SC_0;
}
if (!(tmp & 0x80)) {
if ((data->lm75[0] != NULL)
&& ((tmp & 0x7) == ((tmp >> 4) & 0x7))) {
dev_err(&client->dev,
"duplicate addresses 0x%x, "
"use force_subclients\n", data->lm75[0]->addr);
err = -ENODEV;
goto ERROR_SC_1;
}
err = w83793_create_subclient(adapter, client,
(tmp >> 4) & 0x7, &data->lm75[1]);
if (err < 0)
goto ERROR_SC_1;
}
return 0;
/* Undo inits in case of errors */
ERROR_SC_1:
if (data->lm75[0] != NULL) {
i2c_detach_client(data->lm75[0]);
kfree(data->lm75[0]);
}
ERROR_SC_0:
return err;
}
static int w83793_detect(struct i2c_adapter *adapter, int address, int kind)
{
int i;
u8 tmp, val;
struct i2c_client *client;
struct device *dev;
struct w83793_data *data;
int files_fan = ARRAY_SIZE(w83793_left_fan) / 7;
int files_pwm = ARRAY_SIZE(w83793_left_pwm) / 5;
int err = 0;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
goto exit;
}
/* OK. For now, we presume we have a valid client. We now create the
client structure, even though we cannot fill it completely yet.
But it allows us to access w83793_{read,write}_value. */
if (!(data = kzalloc(sizeof(struct w83793_data), GFP_KERNEL))) {
err = -ENOMEM;
goto exit;
}
client = &data->client;
dev = &client->dev;
i2c_set_clientdata(client, data);
client->addr = address;
client->adapter = adapter;
client->driver = &w83793_driver;
data->bank = i2c_smbus_read_byte_data(client, W83793_REG_BANKSEL);
/* Now, we do the remaining detection. */
if (kind < 0) {
tmp = data->bank & 0x80 ? 0x5c : 0xa3;
/* Check Winbond vendor ID */
if (tmp != i2c_smbus_read_byte_data(client,
W83793_REG_VENDORID)) {
pr_debug("w83793: Detection failed at check "
"vendor id\n");
err = -ENODEV;
goto free_mem;
}
/* If Winbond chip, address of chip and W83793_REG_I2C_ADDR
should match */
if ((data->bank & 0x07) == 0
&& i2c_smbus_read_byte_data(client, W83793_REG_I2C_ADDR) !=
(address << 1)) {
pr_debug("w83793: Detection failed at check "
"i2c addr\n");
err = -ENODEV;
goto free_mem;
}
}
/* We have either had a force parameter, or we have already detected the
Winbond. Determine the chip type now */
if (kind <= 0) {
if (0x7b == w83793_read_value(client, W83793_REG_CHIPID)) {
kind = w83793;
} else {
if (kind == 0)
dev_warn(&adapter->dev, "w83793: Ignoring "
"'force' parameter for unknown chip "
"at address 0x%02x\n", address);
err = -ENODEV;
goto free_mem;
}
}
/* Fill in the remaining client fields and put into the global list */
strlcpy(client->name, "w83793", I2C_NAME_SIZE);
mutex_init(&data->update_lock);
/* Tell the I2C layer a new client has arrived */
if ((err = i2c_attach_client(client)))
goto free_mem;
if ((err = w83793_detect_subclients(adapter, address, kind, client)))
goto detach_client;
/* Initialize the chip */
w83793_init_client(client);
data->vrm = vid_which_vrm();
/*
Only fan 1-5 has their own input pins,
Pwm 1-3 has their own pins
*/
data->has_fan = 0x1f;
data->has_pwm = 0x07;
tmp = w83793_read_value(client, W83793_REG_MFC);
val = w83793_read_value(client, W83793_REG_FANIN_CTRL);
/* check the function of pins 49-56 */
if (!(tmp & 0x80)) {
data->has_pwm |= 0x18; /* pwm 4,5 */
if (val & 0x01) { /* fan 6 */
data->has_fan |= 0x20;
data->has_pwm |= 0x20;
}
if (val & 0x02) { /* fan 7 */
data->has_fan |= 0x40;
data->has_pwm |= 0x40;
}
if (!(tmp & 0x40) && (val & 0x04)) { /* fan 8 */
data->has_fan |= 0x80;
data->has_pwm |= 0x80;
}
}
if (0x08 == (tmp & 0x0c)) {
if (val & 0x08) /* fan 9 */
data->has_fan |= 0x100;
if (val & 0x10) /* fan 10 */
data->has_fan |= 0x200;
}
if (0x20 == (tmp & 0x30)) {
if (val & 0x20) /* fan 11 */
data->has_fan |= 0x400;
if (val & 0x40) /* fan 12 */
data->has_fan |= 0x800;
}
if ((tmp & 0x01) && (val & 0x04)) { /* fan 8, second location */
data->has_fan |= 0x80;
data->has_pwm |= 0x80;
}
/* Register sysfs hooks */
for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++) {
err = device_create_file(dev,
&w83793_sensor_attr_2[i].dev_attr);
if (err)
goto exit_remove;
}
for (i = 0; i < ARRAY_SIZE(sda_single_files); i++) {
err = device_create_file(dev, &sda_single_files[i].dev_attr);
if (err)
goto exit_remove;
}
for (i = 5; i < 12; i++) {
int j;
if (!(data->has_fan & (1 << i)))
continue;
for (j = 0; j < files_fan; j++) {
err = device_create_file(dev,
&w83793_left_fan[(i - 5) * files_fan
+ j].dev_attr);
if (err)
goto exit_remove;
}
}
for (i = 3; i < 8; i++) {
int j;
if (!(data->has_pwm & (1 << i)))
continue;
for (j = 0; j < files_pwm; j++) {
err = device_create_file(dev,
&w83793_left_pwm[(i - 3) * files_pwm
+ j].dev_attr);
if (err)
goto exit_remove;
}
}
data->class_dev = hwmon_device_register(dev);
if (IS_ERR(data->class_dev)) {
err = PTR_ERR(data->class_dev);
goto exit_remove;
}
return 0;
/* Unregister sysfs hooks */
exit_remove:
for (i = 0; i < ARRAY_SIZE(w83793_sensor_attr_2); i++)
device_remove_file(dev, &w83793_sensor_attr_2[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(sda_single_files); i++)
device_remove_file(dev, &sda_single_files[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(w83793_left_fan); i++)
device_remove_file(dev, &w83793_left_fan[i].dev_attr);
for (i = 0; i < ARRAY_SIZE(w83793_left_pwm); i++)
device_remove_file(dev, &w83793_left_pwm[i].dev_attr);
if (data->lm75[0] != NULL) {
i2c_detach_client(data->lm75[0]);
kfree(data->lm75[0]);
}
if (data->lm75[1] != NULL) {
i2c_detach_client(data->lm75[1]);
kfree(data->lm75[1]);
}
detach_client:
i2c_detach_client(client);
free_mem:
kfree(data);
exit:
return err;
}
static void w83793_update_nonvolatile(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
int i, j;
/*
They are somewhat "stable" registers, and to update them everytime
takes so much time, it's just not worthy. Update them in a long
interval to avoid exception.
*/
if (!(time_after(jiffies, data->last_nonvolatile + HZ * 300)
|| !data->valid))
return;
/* update voltage limits */
for (i = 1; i < 3; i++) {
for (j = 0; j < ARRAY_SIZE(data->in); j++) {
data->in[j][i] =
w83793_read_value(client, W83793_REG_IN[j][i]);
}
data->in_low_bits[i] =
w83793_read_value(client, W83793_REG_IN_LOW_BITS[i]);
}
for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) {
/* Update the Fan measured value and limits */
if (!(data->has_fan & (1 << i))) {
continue;
}
data->fan_min[i] =
w83793_read_value(client, W83793_REG_FAN_MIN(i)) << 8;
data->fan_min[i] |=
w83793_read_value(client, W83793_REG_FAN_MIN(i) + 1);
}
for (i = 0; i < ARRAY_SIZE(data->temp_fan_map); i++) {
data->temp_fan_map[i] =
w83793_read_value(client, W83793_REG_TEMP_FAN_MAP(i));
for (j = 1; j < 5; j++) {
data->temp[i][j] =
w83793_read_value(client, W83793_REG_TEMP[i][j]);
}
data->temp_cruise[i] =
w83793_read_value(client, W83793_REG_TEMP_CRUISE(i));
for (j = 0; j < 7; j++) {
data->sf2_pwm[i][j] =
w83793_read_value(client, W83793_REG_SF2_PWM(i, j));
data->sf2_temp[i][j] =
w83793_read_value(client,
W83793_REG_SF2_TEMP(i, j));
}
}
for (i = 0; i < ARRAY_SIZE(data->temp_mode); i++)
data->temp_mode[i] =
w83793_read_value(client, W83793_REG_TEMP_MODE[i]);
for (i = 0; i < ARRAY_SIZE(data->tolerance); i++) {
data->tolerance[i] =
w83793_read_value(client, W83793_REG_TEMP_TOL(i));
}
for (i = 0; i < ARRAY_SIZE(data->pwm); i++) {
if (!(data->has_pwm & (1 << i)))
continue;
data->pwm[i][PWM_NONSTOP] =
w83793_read_value(client, W83793_REG_PWM(i, PWM_NONSTOP));
data->pwm[i][PWM_START] =
w83793_read_value(client, W83793_REG_PWM(i, PWM_START));
data->pwm_stop_time[i] =
w83793_read_value(client, W83793_REG_PWM_STOP_TIME(i));
}
data->pwm_default = w83793_read_value(client, W83793_REG_PWM_DEFAULT);
data->pwm_enable = w83793_read_value(client, W83793_REG_PWM_ENABLE);
data->pwm_uptime = w83793_read_value(client, W83793_REG_PWM_UPTIME);
data->pwm_downtime = w83793_read_value(client, W83793_REG_PWM_DOWNTIME);
data->temp_critical =
w83793_read_value(client, W83793_REG_TEMP_CRITICAL);
data->beep_enable = w83793_read_value(client, W83793_REG_OVT_BEEP);
for (i = 0; i < ARRAY_SIZE(data->beeps); i++) {
data->beeps[i] = w83793_read_value(client, W83793_REG_BEEP(i));
}
data->last_nonvolatile = jiffies;
}
static struct w83793_data *w83793_update_device(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct w83793_data *data = i2c_get_clientdata(client);
int i;
mutex_lock(&data->update_lock);
if (!(time_after(jiffies, data->last_updated + HZ * 2)
|| !data->valid))
goto END;
/* Update the voltages measured value and limits */
for (i = 0; i < ARRAY_SIZE(data->in); i++)
data->in[i][IN_READ] =
w83793_read_value(client, W83793_REG_IN[i][IN_READ]);
data->in_low_bits[IN_READ] =
w83793_read_value(client, W83793_REG_IN_LOW_BITS[IN_READ]);
for (i = 0; i < ARRAY_SIZE(data->fan); i++) {
if (!(data->has_fan & (1 << i))) {
continue;
}
data->fan[i] =
w83793_read_value(client, W83793_REG_FAN(i)) << 8;
data->fan[i] |=
w83793_read_value(client, W83793_REG_FAN(i) + 1);
}
for (i = 0; i < ARRAY_SIZE(data->temp); i++)
data->temp[i][TEMP_READ] =
w83793_read_value(client, W83793_REG_TEMP[i][TEMP_READ]);
data->temp_low_bits =
w83793_read_value(client, W83793_REG_TEMP_LOW_BITS);
for (i = 0; i < ARRAY_SIZE(data->pwm); i++) {
if (data->has_pwm & (1 << i))
data->pwm[i][PWM_DUTY] =
w83793_read_value(client,
W83793_REG_PWM(i, PWM_DUTY));
}
for (i = 0; i < ARRAY_SIZE(data->alarms); i++)
data->alarms[i] =
w83793_read_value(client, W83793_REG_ALARM(i));
data->vid[0] = w83793_read_value(client, W83793_REG_VID_INA);
data->vid[1] = w83793_read_value(client, W83793_REG_VID_INB);
w83793_update_nonvolatile(dev);
data->last_updated = jiffies;
data->valid = 1;
END:
mutex_unlock(&data->update_lock);
return data;
}
/* Ignore the possibility that somebody change bank outside the driver
Must be called with data->update_lock held, except during initialization */
static u8 w83793_read_value(struct i2c_client *client, u16 reg)
{
struct w83793_data *data = i2c_get_clientdata(client);
u8 res = 0xff;
u8 new_bank = reg >> 8;
new_bank |= data->bank & 0xfc;
if (data->bank != new_bank) {
if (i2c_smbus_write_byte_data
(client, W83793_REG_BANKSEL, new_bank) >= 0)
data->bank = new_bank;
else {
dev_err(&client->dev,
"set bank to %d failed, fall back "
"to bank %d, read reg 0x%x error\n",
new_bank, data->bank, reg);
res = 0x0; /* read 0x0 from the chip */
goto END;
}
}
res = i2c_smbus_read_byte_data(client, reg & 0xff);
END:
return res;
}
/* Must be called with data->update_lock held, except during initialization */
static int w83793_write_value(struct i2c_client *client, u16 reg, u8 value)
{
struct w83793_data *data = i2c_get_clientdata(client);
int res;
u8 new_bank = reg >> 8;
new_bank |= data->bank & 0xfc;
if (data->bank != new_bank) {
if ((res = i2c_smbus_write_byte_data
(client, W83793_REG_BANKSEL, new_bank)) >= 0)
data->bank = new_bank;
else {
dev_err(&client->dev,
"set bank to %d failed, fall back "
"to bank %d, write reg 0x%x error\n",
new_bank, data->bank, reg);
goto END;
}
}
res = i2c_smbus_write_byte_data(client, reg & 0xff, value);
END:
return res;
}
static int __init sensors_w83793_init(void)
{
return i2c_add_driver(&w83793_driver);
}
static void __exit sensors_w83793_exit(void)
{
i2c_del_driver(&w83793_driver);
}
MODULE_AUTHOR("Yuan Mu");
MODULE_DESCRIPTION("w83793 driver");
MODULE_LICENSE("GPL");
module_init(sensors_w83793_init);
module_exit(sensors_w83793_exit);
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge * i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge
* *
* Copyright (C) 2004 Patrick Mochel * Copyright (C) 2004 Patrick Mochel
* 2005 Rudolf Marek <r.marek@sh.cvut.cz> * 2005 Rudolf Marek <r.marek@assembler.cz>
* *
* The 1563 southbridge is deceptively similar to the 1533, with a * The 1563 southbridge is deceptively similar to the 1533, with a
* few notable exceptions. One of those happens to be the fact they * few notable exceptions. One of those happens to be the fact they
......
...@@ -142,7 +142,6 @@ ...@@ -142,7 +142,6 @@
#define I2C_DRIVERID_MTP008 1023 #define I2C_DRIVERID_MTP008 1023
#define I2C_DRIVERID_DS1621 1024 #define I2C_DRIVERID_DS1621 1024
#define I2C_DRIVERID_ADM1024 1025 #define I2C_DRIVERID_ADM1024 1025
#define I2C_DRIVERID_IT87 1026
#define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */ #define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */
#define I2C_DRIVERID_FSCPOS 1028 #define I2C_DRIVERID_FSCPOS 1028
#define I2C_DRIVERID_FSCSCY 1029 #define I2C_DRIVERID_FSCSCY 1029
......
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