forked from Minki/linux
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
This commit is contained in:
commit
bbc7610c06
@ -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
|
||||
(temporary transition config option provided until then)
|
||||
The transition config option will also be removed at the same time.
|
||||
|
@ -6,6 +6,10 @@ Supported chips:
|
||||
Prefix: 'f71805f'
|
||||
Addresses scanned: none, address read from Super I/O config space
|
||||
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>
|
||||
|
||||
@ -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
|
||||
for providing initial documentation.
|
||||
|
||||
Thanks to Kris Chen from Fintek for answering technical questions and
|
||||
providing additional documentation.
|
||||
Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
|
||||
questions and providing additional documentation.
|
||||
|
||||
Thanks to Chris Lin from Jetway for providing wiring schematics and
|
||||
answering technical questions.
|
||||
@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power
|
||||
source), 3 fans and 3 temperature sensors.
|
||||
|
||||
This chip also has fan controlling features, using either DC or PWM, in
|
||||
three different modes (one manual, two automatic). The driver doesn't
|
||||
support these features yet.
|
||||
three different modes (one manual, two automatic).
|
||||
|
||||
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
|
||||
reasonable.
|
||||
@ -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
|
||||
need external resistors. An exception is in0, which is used to monitor
|
||||
the chip's own power source (+3.3V), and is divided internally by a
|
||||
factor 2.
|
||||
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
|
||||
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
|
||||
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
|
||||
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
|
||||
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
|
||||
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.
|
||||
(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
|
||||
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
|
||||
or a thermistor. The driver reports the currently selected mode, but
|
||||
doesn't allow changing it. In theory, the BIOS should have configured
|
||||
everything properly.
|
||||
|
||||
|
||||
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:
|
||||
http://www.ite.com.tw/
|
||||
* IT8712F
|
||||
Prefix: 'it8712'
|
||||
Addresses scanned: I2C 0x2d
|
||||
from Super I/O config space (8 I/O ports)
|
||||
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||
Datasheet: Publicly available at the ITE website
|
||||
http://www.ite.com.tw/
|
||||
* IT8716F
|
||||
@ -53,6 +52,18 @@ Module Parameters
|
||||
misconfigured by BIOS - PWM values would be inverted. This option tries
|
||||
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
|
||||
-----------
|
||||
|
||||
|
@ -8,7 +8,7 @@ Supported chips:
|
||||
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
|
||||
|
||||
Author: Rudolf Marek
|
||||
Contact: Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Contact: Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
38
Documentation/hwmon/pc87427
Normal file
38
Documentation/hwmon/pc87427
Normal file
@ -0,0 +1,38 @@
|
||||
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
|
||||
****************
|
||||
|
||||
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
|
||||
1: PII/Celeron Diode
|
||||
2: 3904 transistor
|
||||
3: thermal diode
|
||||
4: thermistor (default/unknown Beta)
|
||||
5: AMD AMDSI
|
||||
6: Intel PECI
|
||||
Not all types are supported by all chips
|
||||
|
||||
temp[1-*]_max Temperature max value.
|
||||
|
@ -10,7 +10,7 @@ Supported chips:
|
||||
Authors:
|
||||
Jean Delvare <khali@linux-fr.org>
|
||||
Yuan Mu (Winbond)
|
||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
@ -18,7 +18,7 @@ Credits:
|
||||
and Mark Studebaker <mdsxyz123@yahoo.com>
|
||||
w83792d.c:
|
||||
Chunhao Huang <DZShen@Winbond.com.tw>,
|
||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
||||
Rudolf Marek <r.marek@assembler.cz>
|
||||
|
||||
Additional contributors:
|
||||
Sven Anders <anders@anduras.de>
|
||||
|
110
Documentation/hwmon/w83793
Normal file
110
Documentation/hwmon/w83793
Normal file
@ -0,0 +1,110 @@
|
||||
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.
|
15
MAINTAINERS
15
MAINTAINERS
@ -277,7 +277,7 @@ S: Maintained
|
||||
|
||||
ALI1563 I2C DRIVER
|
||||
P: Rudolf Marek
|
||||
M: r.marek@sh.cvut.cz
|
||||
M: r.marek@assembler.cz
|
||||
L: i2c@lm-sensors.org
|
||||
S: Maintained
|
||||
|
||||
@ -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
|
||||
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
|
||||
P: Tom Tucker
|
||||
M: tom@opengridcomputing.com
|
||||
@ -3436,6 +3443,12 @@ M: bezaur@gmail.com
|
||||
L: lm-sensors@lm-sensors.org
|
||||
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
|
||||
P: Pierre Ossman
|
||||
M: drzeus-wbsd@drzeus.cx
|
||||
|
@ -106,6 +106,31 @@ config SENSORS_K8TEMP
|
||||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Asus ASB100 Bach"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
@ -142,11 +167,12 @@ config SENSORS_DS1621
|
||||
will be called ds1621.
|
||||
|
||||
config SENSORS_F71805F
|
||||
tristate "Fintek F71805F/FG"
|
||||
tristate "Fintek F71805F/FG and F71872F/FG"
|
||||
depends on HWMON && EXPERIMENTAL
|
||||
help
|
||||
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
|
||||
will be called f71805f.
|
||||
@ -353,6 +379,19 @@ config SENSORS_PC87360
|
||||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Silicon Integrated Systems Corp. SiS5595"
|
||||
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
||||
@ -474,6 +513,16 @@ config SENSORS_W83792D
|
||||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Winbond W83L785TS-S"
|
||||
depends on HWMON && I2C && EXPERIMENTAL
|
||||
@ -527,6 +576,9 @@ config SENSORS_HDAPS
|
||||
This driver also provides an absolute input class device, allowing
|
||||
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
|
||||
the awesome power of hdaps.
|
||||
|
||||
|
@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
|
||||
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
|
||||
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
|
||||
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
|
||||
obj-$(CONFIG_SENSORS_W83793) += w83793.o
|
||||
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
||||
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||
|
||||
@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
|
||||
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
||||
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
|
||||
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
||||
obj-$(CONFIG_SENSORS_AMS) += ams/
|
||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||
@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
|
||||
obj-$(CONFIG_SENSORS_LM92) += lm92.o
|
||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
||||
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
|
||||
|
8
drivers/hwmon/ams/Makefile
Normal file
8
drivers/hwmon/ams/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
# 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
|
265
drivers/hwmon/ams/ams-core.c
Normal file
265
drivers/hwmon/ams/ams-core.c
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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);
|
299
drivers/hwmon/ams/ams-i2c.c
Normal file
299
drivers/hwmon/ams/ams-i2c.c
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
160
drivers/hwmon/ams/ams-input.c
Normal file
160
drivers/hwmon/ams/ams-input.c
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
207
drivers/hwmon/ams/ams-pmu.c
Normal file
207
drivers/hwmon/ams/ams-pmu.c
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
72
drivers/hwmon/ams/ams.h
Normal file
72
drivers/hwmon/ams/ams.h
Normal file
@ -0,0 +1,72 @@
|
||||
#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);
|
@ -1,12 +1,15 @@
|
||||
/*
|
||||
* f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated
|
||||
* hardware monitoring features
|
||||
* f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O
|
||||
* chips integrated hardware monitoring features
|
||||
* 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
|
||||
* complete hardware monitoring features: voltage, fan and temperature
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
@ -37,6 +40,7 @@
|
||||
static struct platform_device *pdev;
|
||||
|
||||
#define DRVNAME "f71805f"
|
||||
enum kinds { f71805f, f71872f };
|
||||
|
||||
/*
|
||||
* Super-I/O constants and functions
|
||||
@ -48,11 +52,13 @@ static struct platform_device *pdev;
|
||||
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
|
||||
#define SIO_REG_DEVREV 0x22 /* Device revision */
|
||||
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
|
||||
#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */
|
||||
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
||||
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
|
||||
|
||||
#define SIO_FINTEK_ID 0x1934
|
||||
#define SIO_F71805F_ID 0x0406
|
||||
#define SIO_F71872F_ID 0x0341
|
||||
|
||||
static inline int
|
||||
superio_inb(int base, int reg)
|
||||
@ -96,22 +102,25 @@ superio_exit(int base)
|
||||
* ISA constants
|
||||
*/
|
||||
|
||||
#define REGION_LENGTH 2
|
||||
#define ADDR_REG_OFFSET 0
|
||||
#define DATA_REG_OFFSET 1
|
||||
#define REGION_LENGTH 8
|
||||
#define ADDR_REG_OFFSET 5
|
||||
#define DATA_REG_OFFSET 6
|
||||
|
||||
/*
|
||||
* 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_HIGH(nr) (0x40 + 2 * (nr))
|
||||
#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr))
|
||||
#define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E)
|
||||
#define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F)
|
||||
/* fan nr from 0 to 2 (12-bit values, two registers) */
|
||||
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
|
||||
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
|
||||
#define F71805F_REG_FAN_TARGET(nr) (0x69 + 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) */
|
||||
#define F71805F_REG_TEMP(nr) (0x1B + (nr))
|
||||
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
|
||||
@ -122,6 +131,14 @@ superio_exit(int base)
|
||||
/* status nr from 0 to 2 */
|
||||
#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
|
||||
*/
|
||||
@ -138,12 +155,16 @@ struct f71805f_data {
|
||||
unsigned long last_limits; /* In jiffies */
|
||||
|
||||
/* Register values */
|
||||
u8 in[9];
|
||||
u8 in_high[9];
|
||||
u8 in_low[9];
|
||||
u8 in[11];
|
||||
u8 in_high[11];
|
||||
u8 in_low[11];
|
||||
u16 has_in;
|
||||
u16 fan[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_high[3];
|
||||
u8 temp_hyst[3];
|
||||
@ -151,6 +172,11 @@ struct f71805f_data {
|
||||
unsigned long alarms;
|
||||
};
|
||||
|
||||
struct f71805f_sio_data {
|
||||
enum kinds kind;
|
||||
u8 fnsel1;
|
||||
};
|
||||
|
||||
static inline long in_from_reg(u8 reg)
|
||||
{
|
||||
return (reg * 8);
|
||||
@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long 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)
|
||||
{
|
||||
return (reg * 1000);
|
||||
@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
|
||||
/* Limit registers cache is refreshed after 60 seconds */
|
||||
if (time_after(jiffies, data->last_updated + 60 * HZ)
|
||||
|| !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,
|
||||
F71805F_REG_IN_HIGH(nr));
|
||||
data->in_low[nr] = f71805f_read8(data,
|
||||
F71805F_REG_IN_LOW(nr));
|
||||
}
|
||||
for (nr = 0; nr < 3; nr++) {
|
||||
if (data->fan_enabled & (1 << nr))
|
||||
data->fan_low[nr] = f71805f_read16(data,
|
||||
F71805F_REG_FAN_LOW(nr));
|
||||
data->fan_low[nr] = f71805f_read16(data,
|
||||
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++) {
|
||||
data->temp_high[nr] = f71805f_read8(data,
|
||||
@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
|
||||
/* Measurement registers cache is refreshed after 1 second */
|
||||
if (time_after(jiffies, data->last_updated + HZ)
|
||||
|| !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,
|
||||
F71805F_REG_IN(nr));
|
||||
}
|
||||
for (nr = 0; nr < 3; nr++) {
|
||||
if (data->fan_enabled & (1 << nr))
|
||||
data->fan[nr] = f71805f_read16(data,
|
||||
F71805F_REG_FAN(nr));
|
||||
data->fan[nr] = f71805f_read16(data,
|
||||
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++) {
|
||||
data->temp[nr] = f71805f_read8(data,
|
||||
@ -333,35 +396,43 @@ static ssize_t show_in0(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", 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
|
||||
*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", 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
|
||||
*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", 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
|
||||
*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->in_high[0] = in0_to_reg(val);
|
||||
f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]);
|
||||
data->in_high[nr] = in0_to_reg(val);
|
||||
f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
@ -371,11 +442,13 @@ static ssize_t set_in0_min(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->in_low[0] = in0_to_reg(val);
|
||||
f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]);
|
||||
data->in_low[nr] = in0_to_reg(val);
|
||||
f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
return count;
|
||||
@ -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]));
|
||||
}
|
||||
|
||||
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
|
||||
*devattr, const char *buf, size_t count)
|
||||
{
|
||||
@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute
|
||||
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,
|
||||
char *buf)
|
||||
{
|
||||
@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute
|
||||
{
|
||||
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
|
||||
@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
|
||||
return sprintf(buf, "%s\n", data->name);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL);
|
||||
static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max);
|
||||
static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min);
|
||||
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR,
|
||||
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_max, S_IRUGO | S_IWUSR,
|
||||
show_in_max, set_in_max, 1);
|
||||
@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR,
|
||||
show_in_max, set_in_max, 8);
|
||||
static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
|
||||
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_min, S_IRUGO | S_IWUSR,
|
||||
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_min, S_IRUGO | S_IWUSR,
|
||||
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_min, S_IRUGO | S_IWUSR,
|
||||
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_max, 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);
|
||||
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(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
|
||||
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);
|
||||
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(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(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
|
||||
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);
|
||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||
|
||||
static struct attribute *f71805f_attributes[] = {
|
||||
&dev_attr_in0_input.attr,
|
||||
&dev_attr_in0_max.attr,
|
||||
&dev_attr_in0_min.attr,
|
||||
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in0_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_min.dev_attr.attr,
|
||||
@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = {
|
||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in3_max.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_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in5_min.dev_attr.attr,
|
||||
@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = {
|
||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_max.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_in8_min.dev_attr.attr,
|
||||
|
||||
&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_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_max.dev_attr.attr,
|
||||
@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = {
|
||||
&sensor_dev_attr_in1_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_in2_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_in6_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,
|
||||
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_alarm.dev_attr.attr,
|
||||
@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = {
|
||||
.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_fan1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_alarm.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_in4_alarm.dev_attr.attr,
|
||||
NULL
|
||||
}, {
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
|
||||
&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
|
||||
}, {
|
||||
&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_in9_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in9_alarm.dev_attr.attr,
|
||||
NULL
|
||||
}, {
|
||||
&sensor_dev_attr_in10_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_max.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_min.dev_attr.attr,
|
||||
&sensor_dev_attr_in10_alarm.dev_attr.attr,
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
static const struct attribute_group f71805f_group_fan[3] = {
|
||||
{ .attrs = f71805f_attributes_fan[0] },
|
||||
{ .attrs = f71805f_attributes_fan[1] },
|
||||
{ .attrs = f71805f_attributes_fan[2] },
|
||||
static const struct attribute_group f71805f_group_optin[4] = {
|
||||
{ .attrs = f71805f_attributes_optin[0] },
|
||||
{ .attrs = f71805f_attributes_optin[1] },
|
||||
{ .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)
|
||||
/* Fan monitoring can be disabled. If it is, we won't be polling
|
||||
the register values, and won't create the related sysfs files. */
|
||||
for (i = 0; i < 3; i++) {
|
||||
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i));
|
||||
if (!(reg & 0x80))
|
||||
data->fan_enabled |= (1 << i);
|
||||
data->fan_ctrl[i] = f71805f_read8(data,
|
||||
F71805F_REG_FAN_CTRL(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)
|
||||
{
|
||||
struct f71805f_sio_data *sio_data = pdev->dev.platform_data;
|
||||
struct f71805f_data *data;
|
||||
struct resource *res;
|
||||
int i, err;
|
||||
|
||||
static const char *names[] = {
|
||||
"f71805f",
|
||||
"f71872f",
|
||||
};
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
printk(KERN_ERR DRVNAME ": Out of memory\n");
|
||||
@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||
data->addr = res->start;
|
||||
mutex_init(&data->lock);
|
||||
data->name = "f71805f";
|
||||
data->name = names[sio_data->kind];
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
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 */
|
||||
f71805f_init_device(data);
|
||||
|
||||
/* Register sysfs interface files */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
|
||||
goto exit_free;
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (!(data->fan_enabled & (1 << i)))
|
||||
continue;
|
||||
if (data->has_in & (1 << 4)) { /* in4 */
|
||||
if ((err = sysfs_create_group(&pdev->dev.kobj,
|
||||
&f71805f_group_fan[i])))
|
||||
&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,
|
||||
&f71805f_group_optin[2])))
|
||||
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);
|
||||
if (IS_ERR(data->class_dev)) {
|
||||
@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
||||
|
||||
exit_remove_files:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
||||
for (i = 0; i < 3; i++)
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
|
||||
for (i = 0; i < 4; i++)
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
|
||||
exit_free:
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
kfree(data);
|
||||
@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
hwmon_device_unregister(data->class_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
||||
for (i = 0; i < 3; i++)
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
|
||||
for (i = 0; i < 4; i++)
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = {
|
||||
.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 = {
|
||||
.start = address,
|
||||
@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address)
|
||||
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);
|
||||
if (err) {
|
||||
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
|
||||
err);
|
||||
goto exit_device_put;
|
||||
goto exit_kfree_data;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
exit_kfree_data:
|
||||
kfree(pdev->dev.platform_data);
|
||||
pdev->dev.platform_data = NULL;
|
||||
exit_device_put:
|
||||
platform_device_put(pdev);
|
||||
exit:
|
||||
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;
|
||||
u16 devid;
|
||||
|
||||
static const char *names[] = {
|
||||
"F71805F/FG",
|
||||
"F71872F/FG",
|
||||
};
|
||||
|
||||
superio_enter(sioaddr);
|
||||
|
||||
devid = superio_inw(sioaddr, SIO_REG_MANID);
|
||||
@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
|
||||
goto exit;
|
||||
|
||||
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, "
|
||||
"skipping\n");
|
||||
goto exit;
|
||||
@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
|
||||
"skipping\n");
|
||||
goto exit;
|
||||
}
|
||||
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
|
||||
|
||||
err = 0;
|
||||
printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n",
|
||||
*address, superio_inb(sioaddr, SIO_REG_DEVREV));
|
||||
printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n",
|
||||
names[sio_data->kind], *address,
|
||||
superio_inb(sioaddr, SIO_REG_DEVREV));
|
||||
|
||||
exit:
|
||||
superio_exit(sioaddr);
|
||||
@ -966,9 +1375,10 @@ static int __init f71805f_init(void)
|
||||
{
|
||||
int err;
|
||||
unsigned short address;
|
||||
struct f71805f_sio_data sio_data;
|
||||
|
||||
if (f71805f_find(0x2e, &address)
|
||||
&& f71805f_find(0x4e, &address))
|
||||
if (f71805f_find(0x2e, &address, &sio_data)
|
||||
&& f71805f_find(0x4e, &address, &sio_data))
|
||||
return -ENODEV;
|
||||
|
||||
err = platform_driver_register(&f71805f_driver);
|
||||
@ -976,7 +1386,7 @@ static int __init f71805f_init(void)
|
||||
goto exit;
|
||||
|
||||
/* Sets global pdev as a side effect */
|
||||
err = f71805f_device_add(address);
|
||||
err = f71805f_device_add(address, &sio_data);
|
||||
if (err)
|
||||
goto exit_driver;
|
||||
|
||||
@ -990,13 +1400,16 @@ exit:
|
||||
|
||||
static void __exit f71805f_exit(void)
|
||||
{
|
||||
kfree(pdev->dev.platform_data);
|
||||
pdev->dev.platform_data = NULL;
|
||||
platform_device_unregister(pdev);
|
||||
|
||||
platform_driver_unregister(&f71805f_driver);
|
||||
}
|
||||
|
||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("F71805F hardware monitoring driver");
|
||||
MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver");
|
||||
|
||||
module_init(f71805f_init);
|
||||
module_exit(f71805f_exit);
|
||||
|
@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = {
|
||||
/* Module stuff */
|
||||
|
||||
/* 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);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
printk(KERN_INFO "hdaps: inverting axis readings.\n");
|
||||
return hdaps_dmi_match(id);
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_NORMAL(model) { \
|
||||
.ident = "IBM " model, \
|
||||
#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
|
||||
.ident = vendor " " model, \
|
||||
.callback = hdaps_dmi_match, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_INVERT(model) { \
|
||||
.ident = "IBM " model, \
|
||||
#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
|
||||
.ident = vendor " " model, \
|
||||
.callback = hdaps_dmi_match_invert, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define HDAPS_DMI_MATCH_LENOVO(model) { \
|
||||
.ident = "Lenovo " model, \
|
||||
.callback = hdaps_dmi_match_invert, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||
} \
|
||||
}
|
||||
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
||||
"ThinkPad T42p", so the order of the entries matters.
|
||||
If your ThinkPad is not recognized, please update to latest
|
||||
BIOS. This is especially the case for some R52 ThinkPads. */
|
||||
static struct dmi_system_id __initdata hdaps_whitelist[] = {
|
||||
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)
|
||||
{
|
||||
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)) {
|
||||
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
|
||||
ret = -ENODEV;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
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
|
||||
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
|
||||
@ -232,7 +232,7 @@ u8 vid_which_vrm(void)
|
||||
EXPORT_SYMBOL(vid_from_reg);
|
||||
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_LICENSE("GPL");
|
||||
|
@ -3,7 +3,7 @@
|
||||
monitoring.
|
||||
|
||||
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
|
||||
IT8718F Super I/O chip w/LPC interface
|
||||
Sis950 A clone of the IT8705F
|
||||
@ -41,12 +41,8 @@
|
||||
#include <asm/io.h>
|
||||
|
||||
|
||||
/* Addresses to scan */
|
||||
static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
|
||||
static unsigned short isa_address;
|
||||
|
||||
/* Insmod parameters */
|
||||
I2C_CLIENT_INSMOD_4(it87, it8712, it8716, it8718);
|
||||
enum chips { it87, it8712, it8716, it8718 };
|
||||
|
||||
#define REG 0x2e /* The register to read/write */
|
||||
#define DEV 0x07 /* Register: Logical device select */
|
||||
@ -162,8 +158,6 @@ static u8 vid_value;
|
||||
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (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_TEMP_ENABLE 0x51
|
||||
|
||||
@ -242,33 +236,22 @@ struct it87_data {
|
||||
};
|
||||
|
||||
|
||||
static int it87_attach_adapter(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_detect(struct i2c_adapter *adapter);
|
||||
static int it87_detach_client(struct i2c_client *client);
|
||||
|
||||
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 int it87_check_pwm(struct i2c_client *client);
|
||||
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 = {
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "it87-isa",
|
||||
},
|
||||
.attach_adapter = it87_isa_attach_adapter,
|
||||
.attach_adapter = it87_detect,
|
||||
.detach_client = it87_detach_client,
|
||||
};
|
||||
|
||||
@ -850,22 +833,6 @@ static const struct attribute_group it87_group_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 */
|
||||
static int __init it87_find(unsigned short *address)
|
||||
{
|
||||
@ -916,29 +883,20 @@ exit:
|
||||
}
|
||||
|
||||
/* 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 it87_data *data;
|
||||
int err = 0;
|
||||
const char *name = "";
|
||||
int is_isa = i2c_is_isa_adapter(adapter);
|
||||
const char *name;
|
||||
int enable_pwm_interface;
|
||||
|
||||
if (!is_isa &&
|
||||
!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
goto ERROR0;
|
||||
|
||||
/* Reserve the ISA region */
|
||||
if (is_isa)
|
||||
if (!request_region(address, IT87_EXTENT,
|
||||
it87_isa_driver.driver.name))
|
||||
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 (!request_region(isa_address, IT87_EXTENT,
|
||||
it87_isa_driver.driver.name)){
|
||||
err = -EBUSY;
|
||||
goto ERROR0;
|
||||
}
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) {
|
||||
err = -ENOMEM;
|
||||
@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||
}
|
||||
|
||||
new_client = &data->client;
|
||||
if (is_isa)
|
||||
mutex_init(&data->lock);
|
||||
mutex_init(&data->lock);
|
||||
i2c_set_clientdata(new_client, data);
|
||||
new_client->addr = address;
|
||||
new_client->addr = isa_address;
|
||||
new_client->adapter = adapter;
|
||||
new_client->driver = is_isa ? &it87_isa_driver : &it87_driver;
|
||||
new_client->flags = 0;
|
||||
new_client->driver = &it87_isa_driver;
|
||||
|
||||
/* Now, we do the remaining detection. */
|
||||
|
||||
if (kind < 0) {
|
||||
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|
||||
|| (!is_isa
|
||||
&& it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|
||||
|| it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) {
|
||||
err = -ENODEV;
|
||||
goto ERROR2;
|
||||
}
|
||||
|
||||
/* Determine the chip type. */
|
||||
if (kind <= 0) {
|
||||
i = it87_read_value(new_client, IT87_REG_CHIPID);
|
||||
if (i == 0x90) {
|
||||
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) {
|
||||
switch (chip_type) {
|
||||
case IT8712F_DEVID:
|
||||
data->type = it8712;
|
||||
name = "it8712";
|
||||
} else if (kind == it8716) {
|
||||
break;
|
||||
case IT8716F_DEVID:
|
||||
data->type = it8716;
|
||||
name = "it8716";
|
||||
} else if (kind == it8718) {
|
||||
break;
|
||||
case IT8718F_DEVID:
|
||||
data->type = it8718;
|
||||
name = "it8718";
|
||||
break;
|
||||
default:
|
||||
data->type = it87;
|
||||
name = "it87";
|
||||
}
|
||||
|
||||
/* Fill in the remaining client fields and put it into the global list */
|
||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||
data->type = kind;
|
||||
data->valid = 0;
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
/* Tell the I2C layer a new client has arrived */
|
||||
if ((err = i2c_attach_client(new_client)))
|
||||
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 */
|
||||
enable_pwm_interface = it87_check_pwm(new_client);
|
||||
|
||||
@ -1129,8 +1053,7 @@ ERROR3:
|
||||
ERROR2:
|
||||
kfree(data);
|
||||
ERROR1:
|
||||
if (is_isa)
|
||||
release_region(address, IT87_EXTENT);
|
||||
release_region(isa_address, IT87_EXTENT);
|
||||
ERROR0:
|
||||
return err;
|
||||
}
|
||||
@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client)
|
||||
if ((err = i2c_detach_client(client)))
|
||||
return err;
|
||||
|
||||
if(i2c_is_isa_client(client))
|
||||
release_region(client->addr, IT87_EXTENT);
|
||||
release_region(client->addr, IT87_EXTENT);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The SMBus locks itself, but ISA access must be locked explicitly!
|
||||
We don't want to lock the whole ISA bus, so we lock each client
|
||||
separately.
|
||||
/* ISA access must be locked explicitly!
|
||||
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. */
|
||||
static int it87_read_value(struct i2c_client *client, u8 reg)
|
||||
{
|
||||
struct it87_data *data = i2c_get_clientdata(client);
|
||||
|
||||
int res;
|
||||
if (i2c_is_isa_client(client)) {
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
|
||||
mutex_unlock(&data->lock);
|
||||
return res;
|
||||
} else
|
||||
return i2c_smbus_read_byte_data(client, reg);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* The SMBus locks itself, but ISA access muse be locked explicitly!
|
||||
We don't want to lock the whole ISA bus, so we lock each client
|
||||
separately.
|
||||
/* ISA access must be locked explicitly!
|
||||
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. */
|
||||
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);
|
||||
|
||||
if (i2c_is_isa_client(client)) {
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
} else
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
mutex_lock(&data->lock);
|
||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
/* Return 1 if and only if the PWM interface is safe to use */
|
||||
@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = i2c_add_driver(&it87_driver);
|
||||
if (res)
|
||||
if ((res = it87_find(&isa_address)))
|
||||
return res;
|
||||
|
||||
if (!it87_find(&isa_address)) {
|
||||
res = i2c_isa_add_driver(&it87_isa_driver);
|
||||
if (res) {
|
||||
i2c_del_driver(&it87_driver);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return i2c_isa_add_driver(&it87_isa_driver);
|
||||
}
|
||||
|
||||
static void __exit sm_it87_exit(void)
|
||||
{
|
||||
if (isa_address)
|
||||
i2c_isa_del_driver(&it87_isa_driver);
|
||||
i2c_del_driver(&it87_driver);
|
||||
i2c_isa_del_driver(&it87_isa_driver);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
@ -286,7 +286,7 @@ static void __exit k8temp_exit(void)
|
||||
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_LICENSE("GPL");
|
||||
|
||||
|
@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter)
|
||||
(i&0x02) ? "external" : "internal");
|
||||
|
||||
data->vid_conf = confreg[3];
|
||||
data->vrm = 90;
|
||||
data->vrm = vid_which_vrm();
|
||||
}
|
||||
|
||||
/* Fan clock dividers may be needed before any data is read */
|
||||
|
627
drivers/hwmon/pc87427.c
Normal file
627
drivers/hwmon/pc87427.c
Normal file
@ -0,0 +1,627 @@
|
||||
/*
|
||||
* 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 @@
|
||||
the Winbond W83627EHF Super-I/O chip
|
||||
Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
|
||||
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>
|
||||
|
||||
Shamelessly ripped from the w83627hf driver
|
||||
|
@ -3,7 +3,7 @@
|
||||
monitoring
|
||||
Copyright (C) 2004, 2005 Winbond Electronics Corp.
|
||||
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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
1609
drivers/hwmon/w83793.c
Normal file
1609
drivers/hwmon/w83793.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@
|
||||
* i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge
|
||||
*
|
||||
* 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
|
||||
* few notable exceptions. One of those happens to be the fact they
|
||||
|
@ -142,7 +142,6 @@
|
||||
#define I2C_DRIVERID_MTP008 1023
|
||||
#define I2C_DRIVERID_DS1621 1024
|
||||
#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_FSCPOS 1028
|
||||
#define I2C_DRIVERID_FSCSCY 1029
|
||||
|
Loading…
Reference in New Issue
Block a user