mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-06-17 20:25:19 +00:00
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
30 changed files with 4115 additions and 300 deletions
|
@ -151,15 +151,6 @@ Who: Thomas Gleixner <tglx@linutronix.de>
|
||||||
|
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
What: I2C interface of the it87 driver
|
|
||||||
When: January 2007
|
|
||||||
Why: The ISA interface is faster and should be always available. The I2C
|
|
||||||
probing is also known to cause trouble in at least one case (see
|
|
||||||
bug #5889.)
|
|
||||||
Who: Jean Delvare <khali@linux-fr.org>
|
|
||||||
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports
|
What: Unused EXPORT_SYMBOL/EXPORT_SYMBOL_GPL exports
|
||||||
(temporary transition config option provided until then)
|
(temporary transition config option provided until then)
|
||||||
The transition config option will also be removed at the same time.
|
The transition config option will also be removed at the same time.
|
||||||
|
|
|
@ -6,6 +6,10 @@ Supported chips:
|
||||||
Prefix: 'f71805f'
|
Prefix: 'f71805f'
|
||||||
Addresses scanned: none, address read from Super I/O config space
|
Addresses scanned: none, address read from Super I/O config space
|
||||||
Datasheet: Provided by Fintek on request
|
Datasheet: Provided by Fintek on request
|
||||||
|
* Fintek F71872F/FG
|
||||||
|
Prefix: 'f71872f'
|
||||||
|
Addresses scanned: none, address read from Super I/O config space
|
||||||
|
Datasheet: Provided by Fintek on request
|
||||||
|
|
||||||
Author: Jean Delvare <khali@linux-fr.org>
|
Author: Jean Delvare <khali@linux-fr.org>
|
||||||
|
|
||||||
|
@ -13,8 +17,8 @@ Thanks to Denis Kieft from Barracuda Networks for the donation of a
|
||||||
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
|
test system (custom Jetway K8M8MS motherboard, with CPU and RAM) and
|
||||||
for providing initial documentation.
|
for providing initial documentation.
|
||||||
|
|
||||||
Thanks to Kris Chen from Fintek for answering technical questions and
|
Thanks to Kris Chen and Aaron Huang from Fintek for answering technical
|
||||||
providing additional documentation.
|
questions and providing additional documentation.
|
||||||
|
|
||||||
Thanks to Chris Lin from Jetway for providing wiring schematics and
|
Thanks to Chris Lin from Jetway for providing wiring schematics and
|
||||||
answering technical questions.
|
answering technical questions.
|
||||||
|
@ -28,8 +32,11 @@ capabilities. It can monitor up to 9 voltages (counting its own power
|
||||||
source), 3 fans and 3 temperature sensors.
|
source), 3 fans and 3 temperature sensors.
|
||||||
|
|
||||||
This chip also has fan controlling features, using either DC or PWM, in
|
This chip also has fan controlling features, using either DC or PWM, in
|
||||||
three different modes (one manual, two automatic). The driver doesn't
|
three different modes (one manual, two automatic).
|
||||||
support these features yet.
|
|
||||||
|
The Fintek F71872F/FG Super I/O chip is almost the same, with two
|
||||||
|
additional internal voltages monitored (VSB and battery). It also features
|
||||||
|
6 VID inputs. The VID inputs are not yet supported by this driver.
|
||||||
|
|
||||||
The driver assumes that no more than one chip is present, which seems
|
The driver assumes that no more than one chip is present, which seems
|
||||||
reasonable.
|
reasonable.
|
||||||
|
@ -42,7 +49,8 @@ Voltages are sampled by an 8-bit ADC with a LSB of 8 mV. The supported
|
||||||
range is thus from 0 to 2.040 V. Voltage values outside of this range
|
range is thus from 0 to 2.040 V. Voltage values outside of this range
|
||||||
need external resistors. An exception is in0, which is used to monitor
|
need external resistors. An exception is in0, which is used to monitor
|
||||||
the chip's own power source (+3.3V), and is divided internally by a
|
the chip's own power source (+3.3V), and is divided internally by a
|
||||||
factor 2.
|
factor 2. For the F71872F/FG, in9 (VSB) and in10 (battery) are also
|
||||||
|
divided internally by a factor 2.
|
||||||
|
|
||||||
The two LSB of the voltage limit registers are not used (always 0), so
|
The two LSB of the voltage limit registers are not used (always 0), so
|
||||||
you can only set the limits in steps of 32 mV (before scaling).
|
you can only set the limits in steps of 32 mV (before scaling).
|
||||||
|
@ -61,9 +69,12 @@ in5 VIN5 +12V 200K 20K 11.00 1.05 V
|
||||||
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
|
in6 VIN6 VCC1.5V 10K - 1.00 1.50 V
|
||||||
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
|
in7 VIN7 VCORE 10K - 1.00 ~1.40 V (1)
|
||||||
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
|
in8 VIN8 VSB5V 200K 47K 1.00 0.95 V
|
||||||
|
in10 VSB VSB3.3V int. int. 2.00 1.65 V (3)
|
||||||
|
in9 VBAT VBATTERY int. int. 2.00 1.50 V (3)
|
||||||
|
|
||||||
(1) Depends on your hardware setup.
|
(1) Depends on your hardware setup.
|
||||||
(2) Obviously not correct, swapping R1 and R2 would make more sense.
|
(2) Obviously not correct, swapping R1 and R2 would make more sense.
|
||||||
|
(3) F71872F/FG only.
|
||||||
|
|
||||||
These values can be used as hints at best, as motherboard manufacturers
|
These values can be used as hints at best, as motherboard manufacturers
|
||||||
are free to use a completely different setup. As a matter of fact, the
|
are free to use a completely different setup. As a matter of fact, the
|
||||||
|
@ -103,3 +114,38 @@ sensor. Each channel can be used for connecting either a thermal diode
|
||||||
or a thermistor. The driver reports the currently selected mode, but
|
or a thermistor. The driver reports the currently selected mode, but
|
||||||
doesn't allow changing it. In theory, the BIOS should have configured
|
doesn't allow changing it. In theory, the BIOS should have configured
|
||||||
everything properly.
|
everything properly.
|
||||||
|
|
||||||
|
|
||||||
|
Fan Control
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Both PWM (pulse-width modulation) and DC fan speed control methods are
|
||||||
|
supported. The right one to use depends on external circuitry on the
|
||||||
|
motherboard, so the driver assumes that the BIOS set the method
|
||||||
|
properly. The driver will report the method, but won't let you change
|
||||||
|
it.
|
||||||
|
|
||||||
|
When the PWM method is used, you can select the operating frequency,
|
||||||
|
from 187.5 kHz (default) to 31 Hz. The best frequency depends on the
|
||||||
|
fan model. As a rule of thumb, lower frequencies seem to give better
|
||||||
|
control, but may generate annoying high-pitch noise. Fintek recommends
|
||||||
|
not going below 1 kHz, as the fan tachometers get confused by lower
|
||||||
|
frequencies as well.
|
||||||
|
|
||||||
|
When the DC method is used, Fintek recommends not going below 5 V, which
|
||||||
|
corresponds to a pwm value of 106 for the driver. The driver doesn't
|
||||||
|
enforce this limit though.
|
||||||
|
|
||||||
|
Three different fan control modes are supported:
|
||||||
|
|
||||||
|
* Manual mode
|
||||||
|
You ask for a specific PWM duty cycle or DC voltage.
|
||||||
|
|
||||||
|
* Fan speed mode
|
||||||
|
You ask for a specific fan speed. This mode assumes that pwm1
|
||||||
|
corresponds to fan1, pwm2 to fan2 and pwm3 to fan3.
|
||||||
|
|
||||||
|
* Temperature mode
|
||||||
|
You define 3 temperature/fan speed trip points, and the fan speed is
|
||||||
|
adjusted depending on the measured temperature, using interpolation.
|
||||||
|
This mode is not yet supported by the driver.
|
||||||
|
|
|
@ -9,8 +9,7 @@ Supported chips:
|
||||||
http://www.ite.com.tw/
|
http://www.ite.com.tw/
|
||||||
* IT8712F
|
* IT8712F
|
||||||
Prefix: 'it8712'
|
Prefix: 'it8712'
|
||||||
Addresses scanned: I2C 0x2d
|
Addresses scanned: from Super I/O config space (8 I/O ports)
|
||||||
from Super I/O config space (8 I/O ports)
|
|
||||||
Datasheet: Publicly available at the ITE website
|
Datasheet: Publicly available at the ITE website
|
||||||
http://www.ite.com.tw/
|
http://www.ite.com.tw/
|
||||||
* IT8716F
|
* IT8716F
|
||||||
|
@ -53,6 +52,18 @@ Module Parameters
|
||||||
misconfigured by BIOS - PWM values would be inverted. This option tries
|
misconfigured by BIOS - PWM values would be inverted. This option tries
|
||||||
to fix this. Please contact your BIOS manufacturer and ask him for fix.
|
to fix this. Please contact your BIOS manufacturer and ask him for fix.
|
||||||
|
|
||||||
|
|
||||||
|
Hardware Interfaces
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
All the chips suported by this driver are LPC Super-I/O chips, accessed
|
||||||
|
through the LPC bus (ISA-like I/O ports). The IT8712F additionally has an
|
||||||
|
SMBus interface to the hardware monitoring functions. This driver no
|
||||||
|
longer supports this interface though, as it is slower and less reliable
|
||||||
|
than the ISA access, and was only available on a small number of
|
||||||
|
motherboard models.
|
||||||
|
|
||||||
|
|
||||||
Description
|
Description
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ Supported chips:
|
||||||
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
|
Datasheet: http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf
|
||||||
|
|
||||||
Author: Rudolf Marek
|
Author: Rudolf Marek
|
||||||
Contact: Rudolf Marek <r.marek@sh.cvut.cz>
|
Contact: Rudolf Marek <r.marek@assembler.cz>
|
||||||
|
|
||||||
Description
|
Description
|
||||||
-----------
|
-----------
|
||||||
|
|
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.
|
temp[1-*]_type Sensor type selection.
|
||||||
Integers 1 to 4 or thermistor Beta value (typically 3435)
|
Integers 1 to 6 or thermistor Beta value (typically 3435)
|
||||||
RW
|
RW
|
||||||
1: PII/Celeron Diode
|
1: PII/Celeron Diode
|
||||||
2: 3904 transistor
|
2: 3904 transistor
|
||||||
3: thermal diode
|
3: thermal diode
|
||||||
4: thermistor (default/unknown Beta)
|
4: thermistor (default/unknown Beta)
|
||||||
|
5: AMD AMDSI
|
||||||
|
6: Intel PECI
|
||||||
Not all types are supported by all chips
|
Not all types are supported by all chips
|
||||||
|
|
||||||
temp[1-*]_max Temperature max value.
|
temp[1-*]_max Temperature max value.
|
||||||
|
|
|
@ -10,7 +10,7 @@ Supported chips:
|
||||||
Authors:
|
Authors:
|
||||||
Jean Delvare <khali@linux-fr.org>
|
Jean Delvare <khali@linux-fr.org>
|
||||||
Yuan Mu (Winbond)
|
Yuan Mu (Winbond)
|
||||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
Rudolf Marek <r.marek@assembler.cz>
|
||||||
|
|
||||||
Description
|
Description
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -18,7 +18,7 @@ Credits:
|
||||||
and Mark Studebaker <mdsxyz123@yahoo.com>
|
and Mark Studebaker <mdsxyz123@yahoo.com>
|
||||||
w83792d.c:
|
w83792d.c:
|
||||||
Chunhao Huang <DZShen@Winbond.com.tw>,
|
Chunhao Huang <DZShen@Winbond.com.tw>,
|
||||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
Rudolf Marek <r.marek@assembler.cz>
|
||||||
|
|
||||||
Additional contributors:
|
Additional contributors:
|
||||||
Sven Anders <anders@anduras.de>
|
Sven Anders <anders@anduras.de>
|
||||||
|
|
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
|
ALI1563 I2C DRIVER
|
||||||
P: Rudolf Marek
|
P: Rudolf Marek
|
||||||
M: r.marek@sh.cvut.cz
|
M: r.marek@assembler.cz
|
||||||
L: i2c@lm-sensors.org
|
L: i2c@lm-sensors.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
|
||||||
|
@ -296,6 +296,13 @@ L: info-linux@geode.amd.com
|
||||||
W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html
|
W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html
|
||||||
S: Supported
|
S: Supported
|
||||||
|
|
||||||
|
AMS (Apple Motion Sensor) DRIVER
|
||||||
|
P: Stelian Pop
|
||||||
|
M: stelian@popies.net
|
||||||
|
P: Michael Hanselmann
|
||||||
|
M: linux-kernel@hansmi.ch
|
||||||
|
S: Supported
|
||||||
|
|
||||||
AMSO1100 RNIC DRIVER
|
AMSO1100 RNIC DRIVER
|
||||||
P: Tom Tucker
|
P: Tom Tucker
|
||||||
M: tom@opengridcomputing.com
|
M: tom@opengridcomputing.com
|
||||||
|
@ -3436,6 +3443,12 @@ M: bezaur@gmail.com
|
||||||
L: lm-sensors@lm-sensors.org
|
L: lm-sensors@lm-sensors.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
|
|
||||||
|
W83793 HARDWARE MONITORING DRIVER
|
||||||
|
P: Rudolf Marek
|
||||||
|
M: r.marek@assembler.cz
|
||||||
|
L: lm-sensors@lm-sensors.org
|
||||||
|
S: Maintained
|
||||||
|
|
||||||
W83L51xD SD/MMC CARD INTERFACE DRIVER
|
W83L51xD SD/MMC CARD INTERFACE DRIVER
|
||||||
P: Pierre Ossman
|
P: Pierre Ossman
|
||||||
M: drzeus-wbsd@drzeus.cx
|
M: drzeus-wbsd@drzeus.cx
|
||||||
|
|
|
@ -106,6 +106,31 @@ config SENSORS_K8TEMP
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called k8temp.
|
will be called k8temp.
|
||||||
|
|
||||||
|
config SENSORS_AMS
|
||||||
|
tristate "Apple Motion Sensor driver"
|
||||||
|
depends on HWMON && PPC_PMAC && !PPC64 && INPUT && ((ADB_PMU && I2C = y) || (ADB_PMU && !I2C) || I2C) && EXPERIMENTAL
|
||||||
|
help
|
||||||
|
Support for the motion sensor included in PowerBooks. Includes
|
||||||
|
implementations for PMU and I2C.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called ams.
|
||||||
|
|
||||||
|
config SENSORS_AMS_PMU
|
||||||
|
bool "PMU variant"
|
||||||
|
depends on SENSORS_AMS && ADB_PMU
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
PMU variant of motion sensor, found in late 2005 PowerBooks.
|
||||||
|
|
||||||
|
config SENSORS_AMS_I2C
|
||||||
|
bool "I2C variant"
|
||||||
|
depends on SENSORS_AMS && I2C
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
I2C variant of motion sensor, found in early 2005 PowerBooks and
|
||||||
|
iBooks.
|
||||||
|
|
||||||
config SENSORS_ASB100
|
config SENSORS_ASB100
|
||||||
tristate "Asus ASB100 Bach"
|
tristate "Asus ASB100 Bach"
|
||||||
depends on HWMON && I2C && EXPERIMENTAL
|
depends on HWMON && I2C && EXPERIMENTAL
|
||||||
|
@ -142,11 +167,12 @@ config SENSORS_DS1621
|
||||||
will be called ds1621.
|
will be called ds1621.
|
||||||
|
|
||||||
config SENSORS_F71805F
|
config SENSORS_F71805F
|
||||||
tristate "Fintek F71805F/FG"
|
tristate "Fintek F71805F/FG and F71872F/FG"
|
||||||
depends on HWMON && EXPERIMENTAL
|
depends on HWMON && EXPERIMENTAL
|
||||||
help
|
help
|
||||||
If you say yes here you get support for hardware monitoring
|
If you say yes here you get support for hardware monitoring
|
||||||
features of the Fintek F71805F/FG chips.
|
features of the Fintek F71805F/FG and F71872F/FG Super-I/O
|
||||||
|
chips.
|
||||||
|
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called f71805f.
|
will be called f71805f.
|
||||||
|
@ -353,6 +379,19 @@ config SENSORS_PC87360
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called pc87360.
|
will be called pc87360.
|
||||||
|
|
||||||
|
config SENSORS_PC87427
|
||||||
|
tristate "National Semiconductor PC87427"
|
||||||
|
depends on HWMON && EXPERIMENTAL
|
||||||
|
help
|
||||||
|
If you say yes here you get access to the hardware monitoring
|
||||||
|
functions of the National Semiconductor PC87427 Super-I/O chip.
|
||||||
|
The chip has two distinct logical devices, one for fan speed
|
||||||
|
monitoring and control, and one for voltage and temperature
|
||||||
|
monitoring. Only fan speed monitoring is supported right now.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called pc87427.
|
||||||
|
|
||||||
config SENSORS_SIS5595
|
config SENSORS_SIS5595
|
||||||
tristate "Silicon Integrated Systems Corp. SiS5595"
|
tristate "Silicon Integrated Systems Corp. SiS5595"
|
||||||
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
depends on HWMON && I2C && PCI && EXPERIMENTAL
|
||||||
|
@ -474,6 +513,16 @@ config SENSORS_W83792D
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called w83792d.
|
will be called w83792d.
|
||||||
|
|
||||||
|
config SENSORS_W83793
|
||||||
|
tristate "Winbond W83793"
|
||||||
|
depends on HWMON && I2C && EXPERIMENTAL
|
||||||
|
help
|
||||||
|
If you say yes here you get support for the Winbond W83793
|
||||||
|
hardware monitoring chip.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called w83793.
|
||||||
|
|
||||||
config SENSORS_W83L785TS
|
config SENSORS_W83L785TS
|
||||||
tristate "Winbond W83L785TS-S"
|
tristate "Winbond W83L785TS-S"
|
||||||
depends on HWMON && I2C && EXPERIMENTAL
|
depends on HWMON && I2C && EXPERIMENTAL
|
||||||
|
@ -527,6 +576,9 @@ config SENSORS_HDAPS
|
||||||
This driver also provides an absolute input class device, allowing
|
This driver also provides an absolute input class device, allowing
|
||||||
the laptop to act as a pinball machine-esque joystick.
|
the laptop to act as a pinball machine-esque joystick.
|
||||||
|
|
||||||
|
If your ThinkPad is not recognized by the driver, please update to latest
|
||||||
|
BIOS. This is especially the case for some R52 ThinkPads.
|
||||||
|
|
||||||
Say Y here if you have an applicable laptop and want to experience
|
Say Y here if you have an applicable laptop and want to experience
|
||||||
the awesome power of hdaps.
|
the awesome power of hdaps.
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_HWMON_VID) += hwmon-vid.o
|
||||||
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
|
obj-$(CONFIG_SENSORS_ASB100) += asb100.o
|
||||||
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
|
obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o
|
||||||
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
|
obj-$(CONFIG_SENSORS_W83792D) += w83792d.o
|
||||||
|
obj-$(CONFIG_SENSORS_W83793) += w83793.o
|
||||||
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
||||||
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ obj-$(CONFIG_SENSORS_ADM1025) += adm1025.o
|
||||||
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
obj-$(CONFIG_SENSORS_ADM1026) += adm1026.o
|
||||||
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
|
obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o
|
||||||
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o
|
||||||
|
obj-$(CONFIG_SENSORS_AMS) += ams/
|
||||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||||
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
obj-$(CONFIG_SENSORS_DS1621) += ds1621.o
|
||||||
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
obj-$(CONFIG_SENSORS_F71805F) += f71805f.o
|
||||||
|
@ -41,6 +43,7 @@ obj-$(CONFIG_SENSORS_LM90) += lm90.o
|
||||||
obj-$(CONFIG_SENSORS_LM92) += lm92.o
|
obj-$(CONFIG_SENSORS_LM92) += lm92.o
|
||||||
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
obj-$(CONFIG_SENSORS_MAX1619) += max1619.o
|
||||||
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
obj-$(CONFIG_SENSORS_PC87360) += pc87360.o
|
||||||
|
obj-$(CONFIG_SENSORS_PC87427) += pc87427.o
|
||||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||||
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
||||||
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
|
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
|
||||||
|
|
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
|
* f71805f.c - driver for the Fintek F71805F/FG and F71872F/FG Super-I/O
|
||||||
* hardware monitoring features
|
* chips integrated hardware monitoring features
|
||||||
* Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org>
|
* Copyright (C) 2005-2006 Jean Delvare <khali@linux-fr.org>
|
||||||
*
|
*
|
||||||
* The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates
|
* The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates
|
||||||
* complete hardware monitoring features: voltage, fan and temperature
|
* complete hardware monitoring features: voltage, fan and temperature
|
||||||
* sensors, and manual and automatic fan speed control.
|
* sensors, and manual and automatic fan speed control.
|
||||||
*
|
*
|
||||||
|
* The F71872F/FG is almost the same, with two more voltages monitored,
|
||||||
|
* and 6 VID inputs.
|
||||||
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
@ -37,6 +40,7 @@
|
||||||
static struct platform_device *pdev;
|
static struct platform_device *pdev;
|
||||||
|
|
||||||
#define DRVNAME "f71805f"
|
#define DRVNAME "f71805f"
|
||||||
|
enum kinds { f71805f, f71872f };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Super-I/O constants and functions
|
* Super-I/O constants and functions
|
||||||
|
@ -48,11 +52,13 @@ static struct platform_device *pdev;
|
||||||
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
|
#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */
|
||||||
#define SIO_REG_DEVREV 0x22 /* Device revision */
|
#define SIO_REG_DEVREV 0x22 /* Device revision */
|
||||||
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
|
#define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */
|
||||||
|
#define SIO_REG_FNSEL1 0x29 /* Multi Function Select 1 (F71872F) */
|
||||||
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
#define SIO_REG_ENABLE 0x30 /* Logical device enable */
|
||||||
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
|
#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */
|
||||||
|
|
||||||
#define SIO_FINTEK_ID 0x1934
|
#define SIO_FINTEK_ID 0x1934
|
||||||
#define SIO_F71805F_ID 0x0406
|
#define SIO_F71805F_ID 0x0406
|
||||||
|
#define SIO_F71872F_ID 0x0341
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
superio_inb(int base, int reg)
|
superio_inb(int base, int reg)
|
||||||
|
@ -96,22 +102,25 @@ superio_exit(int base)
|
||||||
* ISA constants
|
* ISA constants
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define REGION_LENGTH 2
|
#define REGION_LENGTH 8
|
||||||
#define ADDR_REG_OFFSET 0
|
#define ADDR_REG_OFFSET 5
|
||||||
#define DATA_REG_OFFSET 1
|
#define DATA_REG_OFFSET 6
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Registers
|
* Registers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* in nr from 0 to 8 (8-bit values) */
|
/* in nr from 0 to 10 (8-bit values) */
|
||||||
#define F71805F_REG_IN(nr) (0x10 + (nr))
|
#define F71805F_REG_IN(nr) (0x10 + (nr))
|
||||||
#define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr))
|
#define F71805F_REG_IN_HIGH(nr) ((nr) < 10 ? 0x40 + 2 * (nr) : 0x2E)
|
||||||
#define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr))
|
#define F71805F_REG_IN_LOW(nr) ((nr) < 10 ? 0x41 + 2 * (nr) : 0x2F)
|
||||||
/* fan nr from 0 to 2 (12-bit values, two registers) */
|
/* fan nr from 0 to 2 (12-bit values, two registers) */
|
||||||
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
|
#define F71805F_REG_FAN(nr) (0x20 + 2 * (nr))
|
||||||
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
|
#define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr))
|
||||||
|
#define F71805F_REG_FAN_TARGET(nr) (0x69 + 16 * (nr))
|
||||||
#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr))
|
#define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr))
|
||||||
|
#define F71805F_REG_PWM_FREQ(nr) (0x63 + 16 * (nr))
|
||||||
|
#define F71805F_REG_PWM_DUTY(nr) (0x6B + 16 * (nr))
|
||||||
/* temp nr from 0 to 2 (8-bit values) */
|
/* temp nr from 0 to 2 (8-bit values) */
|
||||||
#define F71805F_REG_TEMP(nr) (0x1B + (nr))
|
#define F71805F_REG_TEMP(nr) (0x1B + (nr))
|
||||||
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
|
#define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr))
|
||||||
|
@ -122,6 +131,14 @@ superio_exit(int base)
|
||||||
/* status nr from 0 to 2 */
|
/* status nr from 0 to 2 */
|
||||||
#define F71805F_REG_STATUS(nr) (0x36 + (nr))
|
#define F71805F_REG_STATUS(nr) (0x36 + (nr))
|
||||||
|
|
||||||
|
/* individual register bits */
|
||||||
|
#define FAN_CTRL_DC_MODE 0x10
|
||||||
|
#define FAN_CTRL_LATCH_FULL 0x08
|
||||||
|
#define FAN_CTRL_MODE_MASK 0x03
|
||||||
|
#define FAN_CTRL_MODE_SPEED 0x00
|
||||||
|
#define FAN_CTRL_MODE_TEMPERATURE 0x01
|
||||||
|
#define FAN_CTRL_MODE_MANUAL 0x02
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Data structures and manipulation thereof
|
* Data structures and manipulation thereof
|
||||||
*/
|
*/
|
||||||
|
@ -138,12 +155,16 @@ struct f71805f_data {
|
||||||
unsigned long last_limits; /* In jiffies */
|
unsigned long last_limits; /* In jiffies */
|
||||||
|
|
||||||
/* Register values */
|
/* Register values */
|
||||||
u8 in[9];
|
u8 in[11];
|
||||||
u8 in_high[9];
|
u8 in_high[11];
|
||||||
u8 in_low[9];
|
u8 in_low[11];
|
||||||
|
u16 has_in;
|
||||||
u16 fan[3];
|
u16 fan[3];
|
||||||
u16 fan_low[3];
|
u16 fan_low[3];
|
||||||
u8 fan_enabled; /* Read once at init time */
|
u16 fan_target[3];
|
||||||
|
u8 fan_ctrl[3];
|
||||||
|
u8 pwm[3];
|
||||||
|
u8 pwm_freq[3];
|
||||||
u8 temp[3];
|
u8 temp[3];
|
||||||
u8 temp_high[3];
|
u8 temp_high[3];
|
||||||
u8 temp_hyst[3];
|
u8 temp_hyst[3];
|
||||||
|
@ -151,6 +172,11 @@ struct f71805f_data {
|
||||||
unsigned long alarms;
|
unsigned long alarms;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct f71805f_sio_data {
|
||||||
|
enum kinds kind;
|
||||||
|
u8 fnsel1;
|
||||||
|
};
|
||||||
|
|
||||||
static inline long in_from_reg(u8 reg)
|
static inline long in_from_reg(u8 reg)
|
||||||
{
|
{
|
||||||
return (reg * 8);
|
return (reg * 8);
|
||||||
|
@ -200,6 +226,33 @@ static inline u16 fan_to_reg(long rpm)
|
||||||
return (1500000 / rpm);
|
return (1500000 / rpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline unsigned long pwm_freq_from_reg(u8 reg)
|
||||||
|
{
|
||||||
|
unsigned long clock = (reg & 0x80) ? 48000000UL : 1000000UL;
|
||||||
|
|
||||||
|
reg &= 0x7f;
|
||||||
|
if (reg == 0)
|
||||||
|
reg++;
|
||||||
|
return clock / (reg << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 pwm_freq_to_reg(unsigned long val)
|
||||||
|
{
|
||||||
|
if (val >= 187500) /* The highest we can do */
|
||||||
|
return 0x80;
|
||||||
|
if (val >= 1475) /* Use 48 MHz clock */
|
||||||
|
return 0x80 | (48000000UL / (val << 8));
|
||||||
|
if (val < 31) /* The lowest we can do */
|
||||||
|
return 0x7f;
|
||||||
|
else /* Use 1 MHz clock */
|
||||||
|
return 1000000UL / (val << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int pwm_mode_from_reg(u8 reg)
|
||||||
|
{
|
||||||
|
return !(reg & FAN_CTRL_DC_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
static inline long temp_from_reg(u8 reg)
|
static inline long temp_from_reg(u8 reg)
|
||||||
{
|
{
|
||||||
return (reg * 1000);
|
return (reg * 1000);
|
||||||
|
@ -274,16 +327,21 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
|
||||||
/* Limit registers cache is refreshed after 60 seconds */
|
/* Limit registers cache is refreshed after 60 seconds */
|
||||||
if (time_after(jiffies, data->last_updated + 60 * HZ)
|
if (time_after(jiffies, data->last_updated + 60 * HZ)
|
||||||
|| !data->valid) {
|
|| !data->valid) {
|
||||||
for (nr = 0; nr < 9; nr++) {
|
for (nr = 0; nr < 11; nr++) {
|
||||||
|
if (!(data->has_in & (1 << nr)))
|
||||||
|
continue;
|
||||||
data->in_high[nr] = f71805f_read8(data,
|
data->in_high[nr] = f71805f_read8(data,
|
||||||
F71805F_REG_IN_HIGH(nr));
|
F71805F_REG_IN_HIGH(nr));
|
||||||
data->in_low[nr] = f71805f_read8(data,
|
data->in_low[nr] = f71805f_read8(data,
|
||||||
F71805F_REG_IN_LOW(nr));
|
F71805F_REG_IN_LOW(nr));
|
||||||
}
|
}
|
||||||
for (nr = 0; nr < 3; nr++) {
|
for (nr = 0; nr < 3; nr++) {
|
||||||
if (data->fan_enabled & (1 << nr))
|
data->fan_low[nr] = f71805f_read16(data,
|
||||||
data->fan_low[nr] = f71805f_read16(data,
|
F71805F_REG_FAN_LOW(nr));
|
||||||
F71805F_REG_FAN_LOW(nr));
|
data->fan_target[nr] = f71805f_read16(data,
|
||||||
|
F71805F_REG_FAN_TARGET(nr));
|
||||||
|
data->pwm_freq[nr] = f71805f_read8(data,
|
||||||
|
F71805F_REG_PWM_FREQ(nr));
|
||||||
}
|
}
|
||||||
for (nr = 0; nr < 3; nr++) {
|
for (nr = 0; nr < 3; nr++) {
|
||||||
data->temp_high[nr] = f71805f_read8(data,
|
data->temp_high[nr] = f71805f_read8(data,
|
||||||
|
@ -299,14 +357,19 @@ static struct f71805f_data *f71805f_update_device(struct device *dev)
|
||||||
/* Measurement registers cache is refreshed after 1 second */
|
/* Measurement registers cache is refreshed after 1 second */
|
||||||
if (time_after(jiffies, data->last_updated + HZ)
|
if (time_after(jiffies, data->last_updated + HZ)
|
||||||
|| !data->valid) {
|
|| !data->valid) {
|
||||||
for (nr = 0; nr < 9; nr++) {
|
for (nr = 0; nr < 11; nr++) {
|
||||||
|
if (!(data->has_in & (1 << nr)))
|
||||||
|
continue;
|
||||||
data->in[nr] = f71805f_read8(data,
|
data->in[nr] = f71805f_read8(data,
|
||||||
F71805F_REG_IN(nr));
|
F71805F_REG_IN(nr));
|
||||||
}
|
}
|
||||||
for (nr = 0; nr < 3; nr++) {
|
for (nr = 0; nr < 3; nr++) {
|
||||||
if (data->fan_enabled & (1 << nr))
|
data->fan[nr] = f71805f_read16(data,
|
||||||
data->fan[nr] = f71805f_read16(data,
|
F71805F_REG_FAN(nr));
|
||||||
F71805F_REG_FAN(nr));
|
data->fan_ctrl[nr] = f71805f_read8(data,
|
||||||
|
F71805F_REG_FAN_CTRL(nr));
|
||||||
|
data->pwm[nr] = f71805f_read8(data,
|
||||||
|
F71805F_REG_PWM_DUTY(nr));
|
||||||
}
|
}
|
||||||
for (nr = 0; nr < 3; nr++) {
|
for (nr = 0; nr < 3; nr++) {
|
||||||
data->temp[nr] = f71805f_read8(data,
|
data->temp[nr] = f71805f_read8(data,
|
||||||
|
@ -333,35 +396,43 @@ static ssize_t show_in0(struct device *dev, struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = f71805f_update_device(dev);
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
return sprintf(buf, "%ld\n", in0_from_reg(data->in[0]));
|
return sprintf(buf, "%ld\n", in0_from_reg(data->in[nr]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_in0_max(struct device *dev, struct device_attribute
|
static ssize_t show_in0_max(struct device *dev, struct device_attribute
|
||||||
*devattr, char *buf)
|
*devattr, char *buf)
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = f71805f_update_device(dev);
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0]));
|
return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[nr]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_in0_min(struct device *dev, struct device_attribute
|
static ssize_t show_in0_min(struct device *dev, struct device_attribute
|
||||||
*devattr, char *buf)
|
*devattr, char *buf)
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = f71805f_update_device(dev);
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0]));
|
return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[nr]));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_in0_max(struct device *dev, struct device_attribute
|
static ssize_t set_in0_max(struct device *dev, struct device_attribute
|
||||||
*devattr, const char *buf, size_t count)
|
*devattr, const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = dev_get_drvdata(dev);
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
long val = simple_strtol(buf, NULL, 10);
|
long val = simple_strtol(buf, NULL, 10);
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
data->in_high[0] = in0_to_reg(val);
|
data->in_high[nr] = in0_to_reg(val);
|
||||||
f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]);
|
f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]);
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
|
@ -371,11 +442,13 @@ static ssize_t set_in0_min(struct device *dev, struct device_attribute
|
||||||
*devattr, const char *buf, size_t count)
|
*devattr, const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = dev_get_drvdata(dev);
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
long val = simple_strtol(buf, NULL, 10);
|
long val = simple_strtol(buf, NULL, 10);
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
data->in_low[0] = in0_to_reg(val);
|
data->in_low[nr] = in0_to_reg(val);
|
||||||
f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]);
|
f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]);
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
|
@ -463,6 +536,16 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute
|
||||||
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr]));
|
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t show_fan_target(struct device *dev, struct device_attribute
|
||||||
|
*devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
|
return sprintf(buf, "%ld\n", fan_from_reg(data->fan_target[nr]));
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t set_fan_min(struct device *dev, struct device_attribute
|
static ssize_t set_fan_min(struct device *dev, struct device_attribute
|
||||||
*devattr, const char *buf, size_t count)
|
*devattr, const char *buf, size_t count)
|
||||||
{
|
{
|
||||||
|
@ -479,6 +562,157 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t set_fan_target(struct device *dev, struct device_attribute
|
||||||
|
*devattr, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
long val = simple_strtol(buf, NULL, 10);
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
data->fan_target[nr] = fan_to_reg(val);
|
||||||
|
f71805f_write16(data, F71805F_REG_FAN_TARGET(nr),
|
||||||
|
data->fan_target[nr]);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", (int)data->pwm[nr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_pwm_enable(struct device *dev, struct device_attribute
|
||||||
|
*devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
int mode;
|
||||||
|
|
||||||
|
switch (data->fan_ctrl[nr] & FAN_CTRL_MODE_MASK) {
|
||||||
|
case FAN_CTRL_MODE_SPEED:
|
||||||
|
mode = 3;
|
||||||
|
break;
|
||||||
|
case FAN_CTRL_MODE_TEMPERATURE:
|
||||||
|
mode = 2;
|
||||||
|
break;
|
||||||
|
default: /* MANUAL */
|
||||||
|
mode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_pwm_freq(struct device *dev, struct device_attribute
|
||||||
|
*devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
|
return sprintf(buf, "%lu\n", pwm_freq_from_reg(data->pwm_freq[nr]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_pwm_mode(struct device *dev, struct device_attribute
|
||||||
|
*devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", pwm_mode_from_reg(data->fan_ctrl[nr]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_pwm(struct device *dev, struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||||
|
|
||||||
|
if (val > 255)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
data->pwm[nr] = val;
|
||||||
|
f71805f_write8(data, F71805F_REG_PWM_DUTY(nr), data->pwm[nr]);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct attribute *f71805f_attr_pwm[];
|
||||||
|
|
||||||
|
static ssize_t set_pwm_enable(struct device *dev, struct device_attribute
|
||||||
|
*devattr, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||||
|
u8 reg;
|
||||||
|
|
||||||
|
if (val < 1 || val > 3)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (val > 1) { /* Automatic mode, user can't set PWM value */
|
||||||
|
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
|
||||||
|
S_IRUGO))
|
||||||
|
dev_dbg(dev, "chmod -w pwm%d failed\n", nr + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(nr))
|
||||||
|
& ~FAN_CTRL_MODE_MASK;
|
||||||
|
switch (val) {
|
||||||
|
case 1:
|
||||||
|
reg |= FAN_CTRL_MODE_MANUAL;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
reg |= FAN_CTRL_MODE_TEMPERATURE;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
reg |= FAN_CTRL_MODE_SPEED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data->fan_ctrl[nr] = reg;
|
||||||
|
f71805f_write8(data, F71805F_REG_FAN_CTRL(nr), reg);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
if (val == 1) { /* Manual mode, user can set PWM value */
|
||||||
|
if (sysfs_chmod_file(&dev->kobj, f71805f_attr_pwm[nr],
|
||||||
|
S_IRUGO | S_IWUSR))
|
||||||
|
dev_dbg(dev, "chmod +w pwm%d failed\n", nr + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_pwm_freq(struct device *dev, struct device_attribute
|
||||||
|
*devattr, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct f71805f_data *data = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
int nr = attr->index;
|
||||||
|
unsigned long val = simple_strtoul(buf, NULL, 10);
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
data->pwm_freq[nr] = pwm_freq_to_reg(val);
|
||||||
|
f71805f_write8(data, F71805F_REG_PWM_FREQ(nr), data->pwm_freq[nr]);
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
|
static ssize_t show_temp(struct device *dev, struct device_attribute *devattr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
|
@ -557,7 +791,7 @@ static ssize_t show_alarms_in(struct device *dev, struct device_attribute
|
||||||
{
|
{
|
||||||
struct f71805f_data *data = f71805f_update_device(dev);
|
struct f71805f_data *data = f71805f_update_device(dev);
|
||||||
|
|
||||||
return sprintf(buf, "%lu\n", data->alarms & 0x1ff);
|
return sprintf(buf, "%lu\n", data->alarms & 0x7ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t show_alarms_fan(struct device *dev, struct device_attribute
|
static ssize_t show_alarms_fan(struct device *dev, struct device_attribute
|
||||||
|
@ -594,9 +828,11 @@ static ssize_t show_name(struct device *dev, struct device_attribute
|
||||||
return sprintf(buf, "%s\n", data->name);
|
return sprintf(buf, "%s\n", data->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL);
|
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL, 0);
|
||||||
static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max);
|
static SENSOR_DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR,
|
||||||
static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min);
|
show_in0_max, set_in0_max, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR,
|
||||||
|
show_in0_min, set_in0_min, 0);
|
||||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
|
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in, NULL, 1);
|
||||||
static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(in1_max, S_IRUGO | S_IWUSR,
|
||||||
show_in_max, set_in_max, 1);
|
show_in_max, set_in_max, 1);
|
||||||
|
@ -637,16 +873,32 @@ static SENSOR_DEVICE_ATTR(in8_max, S_IRUGO | S_IWUSR,
|
||||||
show_in_max, set_in_max, 8);
|
show_in_max, set_in_max, 8);
|
||||||
static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(in8_min, S_IRUGO | S_IWUSR,
|
||||||
show_in_min, set_in_min, 8);
|
show_in_min, set_in_min, 8);
|
||||||
|
static SENSOR_DEVICE_ATTR(in9_input, S_IRUGO, show_in0, NULL, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(in9_max, S_IRUGO | S_IWUSR,
|
||||||
|
show_in0_max, set_in0_max, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(in9_min, S_IRUGO | S_IWUSR,
|
||||||
|
show_in0_min, set_in0_min, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(in10_input, S_IRUGO, show_in0, NULL, 10);
|
||||||
|
static SENSOR_DEVICE_ATTR(in10_max, S_IRUGO | S_IWUSR,
|
||||||
|
show_in0_max, set_in0_max, 10);
|
||||||
|
static SENSOR_DEVICE_ATTR(in10_min, S_IRUGO | S_IWUSR,
|
||||||
|
show_in0_min, set_in0_min, 10);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR,
|
||||||
show_fan_min, set_fan_min, 0);
|
show_fan_min, set_fan_min, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_target, set_fan_target, 0);
|
||||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1);
|
||||||
static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR,
|
||||||
show_fan_min, set_fan_min, 1);
|
show_fan_min, set_fan_min, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan2_target, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_target, set_fan_target, 1);
|
||||||
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
|
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2);
|
||||||
static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(fan3_min, S_IRUGO | S_IWUSR,
|
||||||
show_fan_min, set_fan_min, 2);
|
show_fan_min, set_fan_min, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_target, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_target, set_fan_target, 2);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
|
||||||
|
@ -667,6 +919,27 @@ static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IRUGO | S_IWUSR,
|
||||||
show_temp_hyst, set_temp_hyst, 2);
|
show_temp_hyst, set_temp_hyst, 2);
|
||||||
static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2);
|
static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2);
|
||||||
|
|
||||||
|
/* pwm (value) files are created read-only, write permission is
|
||||||
|
then added or removed dynamically as needed */
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO, show_pwm, set_pwm, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_enable, set_pwm_enable, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm1_freq, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_freq, set_pwm_freq, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO, show_pwm, set_pwm, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_enable, set_pwm_enable, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm2_freq, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_freq, set_pwm_freq, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm2_mode, S_IRUGO, show_pwm_mode, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO, show_pwm, set_pwm, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_enable, set_pwm_enable, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm3_freq, S_IRUGO | S_IWUSR,
|
||||||
|
show_pwm_freq, set_pwm_freq, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm3_mode, S_IRUGO, show_pwm_mode, NULL, 2);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0);
|
static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
|
static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1);
|
||||||
static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2);
|
static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2);
|
||||||
|
@ -676,6 +949,8 @@ static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5);
|
||||||
static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6);
|
static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6);
|
||||||
static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7);
|
static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7);
|
||||||
static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8);
|
static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8);
|
||||||
|
static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10);
|
||||||
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11);
|
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, show_alarm, NULL, 11);
|
||||||
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
|
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO, show_alarm, NULL, 12);
|
||||||
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13);
|
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO, show_alarm, NULL, 13);
|
||||||
|
@ -689,9 +964,9 @@ static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL);
|
||||||
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
|
||||||
|
|
||||||
static struct attribute *f71805f_attributes[] = {
|
static struct attribute *f71805f_attributes[] = {
|
||||||
&dev_attr_in0_input.attr,
|
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||||
&dev_attr_in0_max.attr,
|
&sensor_dev_attr_in0_max.dev_attr.attr,
|
||||||
&dev_attr_in0_min.attr,
|
&sensor_dev_attr_in0_min.dev_attr.attr,
|
||||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in1_max.dev_attr.attr,
|
&sensor_dev_attr_in1_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_in1_min.dev_attr.attr,
|
&sensor_dev_attr_in1_min.dev_attr.attr,
|
||||||
|
@ -701,9 +976,6 @@ static struct attribute *f71805f_attributes[] = {
|
||||||
&sensor_dev_attr_in3_input.dev_attr.attr,
|
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in3_max.dev_attr.attr,
|
&sensor_dev_attr_in3_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_in3_min.dev_attr.attr,
|
&sensor_dev_attr_in3_min.dev_attr.attr,
|
||||||
&sensor_dev_attr_in4_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in4_max.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in4_min.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in5_max.dev_attr.attr,
|
&sensor_dev_attr_in5_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_in5_min.dev_attr.attr,
|
&sensor_dev_attr_in5_min.dev_attr.attr,
|
||||||
|
@ -713,9 +985,29 @@ static struct attribute *f71805f_attributes[] = {
|
||||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in7_max.dev_attr.attr,
|
&sensor_dev_attr_in7_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_in7_min.dev_attr.attr,
|
&sensor_dev_attr_in7_min.dev_attr.attr,
|
||||||
&sensor_dev_attr_in8_input.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in8_max.dev_attr.attr,
|
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in8_min.dev_attr.attr,
|
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan1_target.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_target.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_target.dev_attr.attr,
|
||||||
|
|
||||||
|
&sensor_dev_attr_pwm1.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_enable.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_mode.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm2.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm2_enable.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm2_mode.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm3.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm3_enable.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm3_mode.dev_attr.attr,
|
||||||
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||||
|
@ -734,11 +1026,9 @@ static struct attribute *f71805f_attributes[] = {
|
||||||
&sensor_dev_attr_in1_alarm.dev_attr.attr,
|
&sensor_dev_attr_in1_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in2_alarm.dev_attr.attr,
|
&sensor_dev_attr_in2_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in3_alarm.dev_attr.attr,
|
&sensor_dev_attr_in3_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in4_alarm.dev_attr.attr,
|
|
||||||
&sensor_dev_attr_in5_alarm.dev_attr.attr,
|
&sensor_dev_attr_in5_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in6_alarm.dev_attr.attr,
|
&sensor_dev_attr_in6_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in7_alarm.dev_attr.attr,
|
&sensor_dev_attr_in7_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_in8_alarm.dev_attr.attr,
|
|
||||||
&dev_attr_alarms_in.attr,
|
&dev_attr_alarms_in.attr,
|
||||||
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
|
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_temp2_alarm.dev_attr.attr,
|
&sensor_dev_attr_temp2_alarm.dev_attr.attr,
|
||||||
|
@ -754,29 +1044,59 @@ static const struct attribute_group f71805f_group = {
|
||||||
.attrs = f71805f_attributes,
|
.attrs = f71805f_attributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct attribute *f71805f_attributes_fan[3][4] = {
|
static struct attribute *f71805f_attributes_optin[4][5] = {
|
||||||
{
|
{
|
||||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan1_min.dev_attr.attr,
|
&sensor_dev_attr_in4_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
|
&sensor_dev_attr_in4_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in4_alarm.dev_attr.attr,
|
||||||
NULL
|
NULL
|
||||||
}, {
|
}, {
|
||||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
&sensor_dev_attr_in8_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan2_min.dev_attr.attr,
|
&sensor_dev_attr_in8_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
|
&sensor_dev_attr_in8_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in8_alarm.dev_attr.attr,
|
||||||
NULL
|
NULL
|
||||||
}, {
|
}, {
|
||||||
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
&sensor_dev_attr_in9_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan3_min.dev_attr.attr,
|
&sensor_dev_attr_in9_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_fan3_alarm.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
|
NULL
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group f71805f_group_fan[3] = {
|
static const struct attribute_group f71805f_group_optin[4] = {
|
||||||
{ .attrs = f71805f_attributes_fan[0] },
|
{ .attrs = f71805f_attributes_optin[0] },
|
||||||
{ .attrs = f71805f_attributes_fan[1] },
|
{ .attrs = f71805f_attributes_optin[1] },
|
||||||
{ .attrs = f71805f_attributes_fan[2] },
|
{ .attrs = f71805f_attributes_optin[2] },
|
||||||
|
{ .attrs = f71805f_attributes_optin[3] },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* We don't include pwm_freq files in the arrays above, because they must be
|
||||||
|
created conditionally (only if pwm_mode is 1 == PWM) */
|
||||||
|
static struct attribute *f71805f_attributes_pwm_freq[] = {
|
||||||
|
&sensor_dev_attr_pwm1_freq.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm2_freq.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm3_freq.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group f71805f_group_pwm_freq = {
|
||||||
|
.attrs = f71805f_attributes_pwm_freq,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* We also need an indexed access to pwmN files to toggle writability */
|
||||||
|
static struct attribute *f71805f_attr_pwm[] = {
|
||||||
|
&sensor_dev_attr_pwm1.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm2.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm3.dev_attr.attr,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -798,18 +1118,30 @@ static void __devinit f71805f_init_device(struct f71805f_data *data)
|
||||||
/* Fan monitoring can be disabled. If it is, we won't be polling
|
/* Fan monitoring can be disabled. If it is, we won't be polling
|
||||||
the register values, and won't create the related sysfs files. */
|
the register values, and won't create the related sysfs files. */
|
||||||
for (i = 0; i < 3; i++) {
|
for (i = 0; i < 3; i++) {
|
||||||
reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i));
|
data->fan_ctrl[i] = f71805f_read8(data,
|
||||||
if (!(reg & 0x80))
|
F71805F_REG_FAN_CTRL(i));
|
||||||
data->fan_enabled |= (1 << i);
|
/* Clear latch full bit, else "speed mode" fan speed control
|
||||||
|
doesn't work */
|
||||||
|
if (data->fan_ctrl[i] & FAN_CTRL_LATCH_FULL) {
|
||||||
|
data->fan_ctrl[i] &= ~FAN_CTRL_LATCH_FULL;
|
||||||
|
f71805f_write8(data, F71805F_REG_FAN_CTRL(i),
|
||||||
|
data->fan_ctrl[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __devinit f71805f_probe(struct platform_device *pdev)
|
static int __devinit f71805f_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
|
struct f71805f_sio_data *sio_data = pdev->dev.platform_data;
|
||||||
struct f71805f_data *data;
|
struct f71805f_data *data;
|
||||||
struct resource *res;
|
struct resource *res;
|
||||||
int i, err;
|
int i, err;
|
||||||
|
|
||||||
|
static const char *names[] = {
|
||||||
|
"f71805f",
|
||||||
|
"f71872f",
|
||||||
|
};
|
||||||
|
|
||||||
if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
|
if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
printk(KERN_ERR DRVNAME ": Out of memory\n");
|
printk(KERN_ERR DRVNAME ": Out of memory\n");
|
||||||
|
@ -819,24 +1151,69 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
||||||
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
|
||||||
data->addr = res->start;
|
data->addr = res->start;
|
||||||
mutex_init(&data->lock);
|
mutex_init(&data->lock);
|
||||||
data->name = "f71805f";
|
data->name = names[sio_data->kind];
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
platform_set_drvdata(pdev, data);
|
platform_set_drvdata(pdev, data);
|
||||||
|
|
||||||
|
/* Some voltage inputs depend on chip model and configuration */
|
||||||
|
switch (sio_data->kind) {
|
||||||
|
case f71805f:
|
||||||
|
data->has_in = 0x1ff;
|
||||||
|
break;
|
||||||
|
case f71872f:
|
||||||
|
data->has_in = 0x6ef;
|
||||||
|
if (sio_data->fnsel1 & 0x01)
|
||||||
|
data->has_in |= (1 << 4); /* in4 */
|
||||||
|
if (sio_data->fnsel1 & 0x02)
|
||||||
|
data->has_in |= (1 << 8); /* in8 */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
/* Initialize the F71805F chip */
|
/* Initialize the F71805F chip */
|
||||||
f71805f_init_device(data);
|
f71805f_init_device(data);
|
||||||
|
|
||||||
/* Register sysfs interface files */
|
/* Register sysfs interface files */
|
||||||
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
|
if ((err = sysfs_create_group(&pdev->dev.kobj, &f71805f_group)))
|
||||||
goto exit_free;
|
goto exit_free;
|
||||||
for (i = 0; i < 3; i++) {
|
if (data->has_in & (1 << 4)) { /* in4 */
|
||||||
if (!(data->fan_enabled & (1 << i)))
|
|
||||||
continue;
|
|
||||||
if ((err = sysfs_create_group(&pdev->dev.kobj,
|
if ((err = sysfs_create_group(&pdev->dev.kobj,
|
||||||
&f71805f_group_fan[i])))
|
&f71805f_group_optin[0])))
|
||||||
goto exit_remove_files;
|
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);
|
data->class_dev = hwmon_device_register(&pdev->dev);
|
||||||
if (IS_ERR(data->class_dev)) {
|
if (IS_ERR(data->class_dev)) {
|
||||||
|
@ -849,8 +1226,9 @@ static int __devinit f71805f_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
exit_remove_files:
|
exit_remove_files:
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
||||||
for (i = 0; i < 3; i++)
|
for (i = 0; i < 4; i++)
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
|
||||||
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
|
||||||
exit_free:
|
exit_free:
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
kfree(data);
|
kfree(data);
|
||||||
|
@ -866,8 +1244,9 @@ static int __devexit f71805f_remove(struct platform_device *pdev)
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
hwmon_device_unregister(data->class_dev);
|
hwmon_device_unregister(data->class_dev);
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group);
|
||||||
for (i = 0; i < 3; i++)
|
for (i = 0; i < 4; i++)
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_fan[i]);
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_optin[i]);
|
||||||
|
sysfs_remove_group(&pdev->dev.kobj, &f71805f_group_pwm_freq);
|
||||||
kfree(data);
|
kfree(data);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -882,7 +1261,8 @@ static struct platform_driver f71805f_driver = {
|
||||||
.remove = __devexit_p(f71805f_remove),
|
.remove = __devexit_p(f71805f_remove),
|
||||||
};
|
};
|
||||||
|
|
||||||
static int __init f71805f_device_add(unsigned short address)
|
static int __init f71805f_device_add(unsigned short address,
|
||||||
|
const struct f71805f_sio_data *sio_data)
|
||||||
{
|
{
|
||||||
struct resource res = {
|
struct resource res = {
|
||||||
.start = address,
|
.start = address,
|
||||||
|
@ -906,26 +1286,45 @@ static int __init f71805f_device_add(unsigned short address)
|
||||||
goto exit_device_put;
|
goto exit_device_put;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pdev->dev.platform_data = kmalloc(sizeof(struct f71805f_sio_data),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!pdev->dev.platform_data) {
|
||||||
|
err = -ENOMEM;
|
||||||
|
printk(KERN_ERR DRVNAME ": Platform data allocation failed\n");
|
||||||
|
goto exit_device_put;
|
||||||
|
}
|
||||||
|
memcpy(pdev->dev.platform_data, sio_data,
|
||||||
|
sizeof(struct f71805f_sio_data));
|
||||||
|
|
||||||
err = platform_device_add(pdev);
|
err = platform_device_add(pdev);
|
||||||
if (err) {
|
if (err) {
|
||||||
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
|
printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n",
|
||||||
err);
|
err);
|
||||||
goto exit_device_put;
|
goto exit_kfree_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
exit_kfree_data:
|
||||||
|
kfree(pdev->dev.platform_data);
|
||||||
|
pdev->dev.platform_data = NULL;
|
||||||
exit_device_put:
|
exit_device_put:
|
||||||
platform_device_put(pdev);
|
platform_device_put(pdev);
|
||||||
exit:
|
exit:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __init f71805f_find(int sioaddr, unsigned short *address)
|
static int __init f71805f_find(int sioaddr, unsigned short *address,
|
||||||
|
struct f71805f_sio_data *sio_data)
|
||||||
{
|
{
|
||||||
int err = -ENODEV;
|
int err = -ENODEV;
|
||||||
u16 devid;
|
u16 devid;
|
||||||
|
|
||||||
|
static const char *names[] = {
|
||||||
|
"F71805F/FG",
|
||||||
|
"F71872F/FG",
|
||||||
|
};
|
||||||
|
|
||||||
superio_enter(sioaddr);
|
superio_enter(sioaddr);
|
||||||
|
|
||||||
devid = superio_inw(sioaddr, SIO_REG_MANID);
|
devid = superio_inw(sioaddr, SIO_REG_MANID);
|
||||||
|
@ -933,7 +1332,15 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
devid = superio_inw(sioaddr, SIO_REG_DEVID);
|
devid = superio_inw(sioaddr, SIO_REG_DEVID);
|
||||||
if (devid != SIO_F71805F_ID) {
|
switch (devid) {
|
||||||
|
case SIO_F71805F_ID:
|
||||||
|
sio_data->kind = f71805f;
|
||||||
|
break;
|
||||||
|
case SIO_F71872F_ID:
|
||||||
|
sio_data->kind = f71872f;
|
||||||
|
sio_data->fnsel1 = superio_inb(sioaddr, SIO_REG_FNSEL1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
|
printk(KERN_INFO DRVNAME ": Unsupported Fintek device, "
|
||||||
"skipping\n");
|
"skipping\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
|
@ -952,10 +1359,12 @@ static int __init f71805f_find(int sioaddr, unsigned short *address)
|
||||||
"skipping\n");
|
"skipping\n");
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
*address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */
|
||||||
|
|
||||||
err = 0;
|
err = 0;
|
||||||
printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n",
|
printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %u\n",
|
||||||
*address, superio_inb(sioaddr, SIO_REG_DEVREV));
|
names[sio_data->kind], *address,
|
||||||
|
superio_inb(sioaddr, SIO_REG_DEVREV));
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
superio_exit(sioaddr);
|
superio_exit(sioaddr);
|
||||||
|
@ -966,9 +1375,10 @@ static int __init f71805f_init(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
unsigned short address;
|
unsigned short address;
|
||||||
|
struct f71805f_sio_data sio_data;
|
||||||
|
|
||||||
if (f71805f_find(0x2e, &address)
|
if (f71805f_find(0x2e, &address, &sio_data)
|
||||||
&& f71805f_find(0x4e, &address))
|
&& f71805f_find(0x4e, &address, &sio_data))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
err = platform_driver_register(&f71805f_driver);
|
err = platform_driver_register(&f71805f_driver);
|
||||||
|
@ -976,7 +1386,7 @@ static int __init f71805f_init(void)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
/* Sets global pdev as a side effect */
|
/* Sets global pdev as a side effect */
|
||||||
err = f71805f_device_add(address);
|
err = f71805f_device_add(address, &sio_data);
|
||||||
if (err)
|
if (err)
|
||||||
goto exit_driver;
|
goto exit_driver;
|
||||||
|
|
||||||
|
@ -990,13 +1400,16 @@ exit:
|
||||||
|
|
||||||
static void __exit f71805f_exit(void)
|
static void __exit f71805f_exit(void)
|
||||||
{
|
{
|
||||||
|
kfree(pdev->dev.platform_data);
|
||||||
|
pdev->dev.platform_data = NULL;
|
||||||
platform_device_unregister(pdev);
|
platform_device_unregister(pdev);
|
||||||
|
|
||||||
platform_driver_unregister(&f71805f_driver);
|
platform_driver_unregister(&f71805f_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
|
MODULE_AUTHOR("Jean Delvare <khali@linux-fr>");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
MODULE_DESCRIPTION("F71805F hardware monitoring driver");
|
MODULE_DESCRIPTION("F71805F/F71872F hardware monitoring driver");
|
||||||
|
|
||||||
module_init(f71805f_init);
|
module_init(f71805f_init);
|
||||||
module_exit(f71805f_exit);
|
module_exit(f71805f_exit);
|
||||||
|
|
|
@ -478,74 +478,64 @@ static struct attribute_group hdaps_attribute_group = {
|
||||||
/* Module stuff */
|
/* Module stuff */
|
||||||
|
|
||||||
/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
|
/* hdaps_dmi_match - found a match. return one, short-circuiting the hunt. */
|
||||||
static int hdaps_dmi_match(struct dmi_system_id *id)
|
static int __init hdaps_dmi_match(struct dmi_system_id *id)
|
||||||
{
|
{
|
||||||
printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
|
printk(KERN_INFO "hdaps: %s detected.\n", id->ident);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* hdaps_dmi_match_invert - found an inverted match. */
|
/* hdaps_dmi_match_invert - found an inverted match. */
|
||||||
static int hdaps_dmi_match_invert(struct dmi_system_id *id)
|
static int __init hdaps_dmi_match_invert(struct dmi_system_id *id)
|
||||||
{
|
{
|
||||||
hdaps_invert = 1;
|
hdaps_invert = 1;
|
||||||
printk(KERN_INFO "hdaps: inverting axis readings.\n");
|
printk(KERN_INFO "hdaps: inverting axis readings.\n");
|
||||||
return hdaps_dmi_match(id);
|
return hdaps_dmi_match(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HDAPS_DMI_MATCH_NORMAL(model) { \
|
#define HDAPS_DMI_MATCH_NORMAL(vendor, model) { \
|
||||||
.ident = "IBM " model, \
|
.ident = vendor " " model, \
|
||||||
.callback = hdaps_dmi_match, \
|
.callback = hdaps_dmi_match, \
|
||||||
.matches = { \
|
.matches = { \
|
||||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HDAPS_DMI_MATCH_INVERT(model) { \
|
#define HDAPS_DMI_MATCH_INVERT(vendor, model) { \
|
||||||
.ident = "IBM " model, \
|
.ident = vendor " " model, \
|
||||||
.callback = hdaps_dmi_match_invert, \
|
.callback = hdaps_dmi_match_invert, \
|
||||||
.matches = { \
|
.matches = { \
|
||||||
DMI_MATCH(DMI_BOARD_VENDOR, "IBM"), \
|
DMI_MATCH(DMI_BOARD_VENDOR, vendor), \
|
||||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HDAPS_DMI_MATCH_LENOVO(model) { \
|
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
||||||
.ident = "Lenovo " model, \
|
"ThinkPad T42p", so the order of the entries matters.
|
||||||
.callback = hdaps_dmi_match_invert, \
|
If your ThinkPad is not recognized, please update to latest
|
||||||
.matches = { \
|
BIOS. This is especially the case for some R52 ThinkPads. */
|
||||||
DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), \
|
static struct dmi_system_id __initdata hdaps_whitelist[] = {
|
||||||
DMI_MATCH(DMI_PRODUCT_VERSION, model) \
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad R50p"),
|
||||||
} \
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R50"),
|
||||||
}
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R51"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad R52"),
|
||||||
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T41p"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T41"),
|
||||||
|
HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"),
|
||||||
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X40"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad X41"),
|
||||||
|
HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad X60"),
|
||||||
|
HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad Z60m"),
|
||||||
|
{ .ident = NULL }
|
||||||
|
};
|
||||||
|
|
||||||
static int __init hdaps_init(void)
|
static int __init hdaps_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Note that HDAPS_DMI_MATCH_NORMAL("ThinkPad T42") would match
|
|
||||||
"ThinkPad T42p", so the order of the entries matters */
|
|
||||||
struct dmi_system_id hdaps_whitelist[] = {
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"),
|
|
||||||
HDAPS_DMI_MATCH_INVERT("ThinkPad R50p"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R50"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R51"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad R52"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad H"), /* R52 (1846AQG) */
|
|
||||||
HDAPS_DMI_MATCH_INVERT("ThinkPad T41p"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T41"),
|
|
||||||
HDAPS_DMI_MATCH_INVERT("ThinkPad T42p"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T42"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad T43"),
|
|
||||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60p"),
|
|
||||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad T60"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad X40"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad X41"),
|
|
||||||
HDAPS_DMI_MATCH_LENOVO("ThinkPad X60"),
|
|
||||||
HDAPS_DMI_MATCH_NORMAL("ThinkPad Z60m"),
|
|
||||||
{ .ident = NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!dmi_check_system(hdaps_whitelist)) {
|
if (!dmi_check_system(hdaps_whitelist)) {
|
||||||
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
|
printk(KERN_WARNING "hdaps: supported laptop not found!\n");
|
||||||
ret = -ENODEV;
|
ret = -ENODEV;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
hwmon-vid.c - VID/VRM/VRD voltage conversions
|
hwmon-vid.c - VID/VRM/VRD voltage conversions
|
||||||
|
|
||||||
Copyright (c) 2004 Rudolf Marek <r.marek@sh.cvut.cz>
|
Copyright (c) 2004 Rudolf Marek <r.marek@assembler.cz>
|
||||||
|
|
||||||
Partly imported from i2c-vid.h of the lm_sensors project
|
Partly imported from i2c-vid.h of the lm_sensors project
|
||||||
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
|
Copyright (c) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>
|
||||||
|
@ -232,7 +232,7 @@ u8 vid_which_vrm(void)
|
||||||
EXPORT_SYMBOL(vid_from_reg);
|
EXPORT_SYMBOL(vid_from_reg);
|
||||||
EXPORT_SYMBOL(vid_which_vrm);
|
EXPORT_SYMBOL(vid_which_vrm);
|
||||||
|
|
||||||
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>");
|
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
|
||||||
|
|
||||||
MODULE_DESCRIPTION("hwmon-vid driver");
|
MODULE_DESCRIPTION("hwmon-vid driver");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
monitoring.
|
monitoring.
|
||||||
|
|
||||||
Supports: IT8705F Super I/O chip w/LPC interface
|
Supports: IT8705F Super I/O chip w/LPC interface
|
||||||
IT8712F Super I/O chip w/LPC interface & SMBus
|
IT8712F Super I/O chip w/LPC interface
|
||||||
IT8716F Super I/O chip w/LPC interface
|
IT8716F Super I/O chip w/LPC interface
|
||||||
IT8718F Super I/O chip w/LPC interface
|
IT8718F Super I/O chip w/LPC interface
|
||||||
Sis950 A clone of the IT8705F
|
Sis950 A clone of the IT8705F
|
||||||
|
@ -41,12 +41,8 @@
|
||||||
#include <asm/io.h>
|
#include <asm/io.h>
|
||||||
|
|
||||||
|
|
||||||
/* Addresses to scan */
|
|
||||||
static unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END };
|
|
||||||
static unsigned short isa_address;
|
static unsigned short isa_address;
|
||||||
|
enum chips { it87, it8712, it8716, it8718 };
|
||||||
/* Insmod parameters */
|
|
||||||
I2C_CLIENT_INSMOD_4(it87, it8712, it8716, it8718);
|
|
||||||
|
|
||||||
#define REG 0x2e /* The register to read/write */
|
#define REG 0x2e /* The register to read/write */
|
||||||
#define DEV 0x07 /* Register: Logical device select */
|
#define DEV 0x07 /* Register: Logical device select */
|
||||||
|
@ -162,8 +158,6 @@ static u8 vid_value;
|
||||||
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2)
|
#define IT87_REG_TEMP_HIGH(nr) (0x40 + (nr) * 2)
|
||||||
#define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2)
|
#define IT87_REG_TEMP_LOW(nr) (0x41 + (nr) * 2)
|
||||||
|
|
||||||
#define IT87_REG_I2C_ADDR 0x48
|
|
||||||
|
|
||||||
#define IT87_REG_VIN_ENABLE 0x50
|
#define IT87_REG_VIN_ENABLE 0x50
|
||||||
#define IT87_REG_TEMP_ENABLE 0x51
|
#define IT87_REG_TEMP_ENABLE 0x51
|
||||||
|
|
||||||
|
@ -242,33 +236,22 @@ struct it87_data {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static int it87_attach_adapter(struct i2c_adapter *adapter);
|
static int it87_detect(struct i2c_adapter *adapter);
|
||||||
static int it87_isa_attach_adapter(struct i2c_adapter *adapter);
|
|
||||||
static int it87_detect(struct i2c_adapter *adapter, int address, int kind);
|
|
||||||
static int it87_detach_client(struct i2c_client *client);
|
static int it87_detach_client(struct i2c_client *client);
|
||||||
|
|
||||||
static int it87_read_value(struct i2c_client *client, u8 reg);
|
static int it87_read_value(struct i2c_client *client, u8 reg);
|
||||||
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value);
|
static void it87_write_value(struct i2c_client *client, u8 reg, u8 value);
|
||||||
static struct it87_data *it87_update_device(struct device *dev);
|
static struct it87_data *it87_update_device(struct device *dev);
|
||||||
static int it87_check_pwm(struct i2c_client *client);
|
static int it87_check_pwm(struct i2c_client *client);
|
||||||
static void it87_init_client(struct i2c_client *client, struct it87_data *data);
|
static void it87_init_client(struct i2c_client *client, struct it87_data *data);
|
||||||
|
|
||||||
|
|
||||||
static struct i2c_driver it87_driver = {
|
|
||||||
.driver = {
|
|
||||||
.name = "it87",
|
|
||||||
},
|
|
||||||
.id = I2C_DRIVERID_IT87,
|
|
||||||
.attach_adapter = it87_attach_adapter,
|
|
||||||
.detach_client = it87_detach_client,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct i2c_driver it87_isa_driver = {
|
static struct i2c_driver it87_isa_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.name = "it87-isa",
|
.name = "it87-isa",
|
||||||
},
|
},
|
||||||
.attach_adapter = it87_isa_attach_adapter,
|
.attach_adapter = it87_detect,
|
||||||
.detach_client = it87_detach_client,
|
.detach_client = it87_detach_client,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -850,22 +833,6 @@ static const struct attribute_group it87_group_opt = {
|
||||||
.attrs = it87_attributes_opt,
|
.attrs = it87_attributes_opt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This function is called when:
|
|
||||||
* it87_driver is inserted (when this module is loaded), for each
|
|
||||||
available adapter
|
|
||||||
* when a new adapter is inserted (and it87_driver is still present) */
|
|
||||||
static int it87_attach_adapter(struct i2c_adapter *adapter)
|
|
||||||
{
|
|
||||||
if (!(adapter->class & I2C_CLASS_HWMON))
|
|
||||||
return 0;
|
|
||||||
return i2c_probe(adapter, &addr_data, it87_detect);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int it87_isa_attach_adapter(struct i2c_adapter *adapter)
|
|
||||||
{
|
|
||||||
return it87_detect(adapter, isa_address, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SuperIO detection - will change isa_address if a chip is found */
|
/* SuperIO detection - will change isa_address if a chip is found */
|
||||||
static int __init it87_find(unsigned short *address)
|
static int __init it87_find(unsigned short *address)
|
||||||
{
|
{
|
||||||
|
@ -916,29 +883,20 @@ exit:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called by i2c_probe */
|
/* This function is called by i2c_probe */
|
||||||
static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
|
static int it87_detect(struct i2c_adapter *adapter)
|
||||||
{
|
{
|
||||||
int i;
|
|
||||||
struct i2c_client *new_client;
|
struct i2c_client *new_client;
|
||||||
struct it87_data *data;
|
struct it87_data *data;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
const char *name = "";
|
const char *name;
|
||||||
int is_isa = i2c_is_isa_adapter(adapter);
|
|
||||||
int enable_pwm_interface;
|
int enable_pwm_interface;
|
||||||
|
|
||||||
if (!is_isa &&
|
|
||||||
!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
|
|
||||||
goto ERROR0;
|
|
||||||
|
|
||||||
/* Reserve the ISA region */
|
/* Reserve the ISA region */
|
||||||
if (is_isa)
|
if (!request_region(isa_address, IT87_EXTENT,
|
||||||
if (!request_region(address, IT87_EXTENT,
|
it87_isa_driver.driver.name)){
|
||||||
it87_isa_driver.driver.name))
|
err = -EBUSY;
|
||||||
goto ERROR0;
|
goto ERROR0;
|
||||||
|
}
|
||||||
/* For now, we presume we have a valid client. We create the
|
|
||||||
client structure, even though we cannot fill it completely yet.
|
|
||||||
But it allows us to access it87_{read,write}_value. */
|
|
||||||
|
|
||||||
if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) {
|
if (!(data = kzalloc(sizeof(struct it87_data), GFP_KERNEL))) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
|
@ -946,80 +904,46 @@ static int it87_detect(struct i2c_adapter *adapter, int address, int kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
new_client = &data->client;
|
new_client = &data->client;
|
||||||
if (is_isa)
|
mutex_init(&data->lock);
|
||||||
mutex_init(&data->lock);
|
|
||||||
i2c_set_clientdata(new_client, data);
|
i2c_set_clientdata(new_client, data);
|
||||||
new_client->addr = address;
|
new_client->addr = isa_address;
|
||||||
new_client->adapter = adapter;
|
new_client->adapter = adapter;
|
||||||
new_client->driver = is_isa ? &it87_isa_driver : &it87_driver;
|
new_client->driver = &it87_isa_driver;
|
||||||
new_client->flags = 0;
|
|
||||||
|
|
||||||
/* Now, we do the remaining detection. */
|
/* Now, we do the remaining detection. */
|
||||||
|
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|
||||||
if (kind < 0) {
|
|| it87_read_value(new_client, IT87_REG_CHIPID) != 0x90) {
|
||||||
if ((it87_read_value(new_client, IT87_REG_CONFIG) & 0x80)
|
err = -ENODEV;
|
||||||
|| (!is_isa
|
goto ERROR2;
|
||||||
&& it87_read_value(new_client, IT87_REG_I2C_ADDR) != address)) {
|
|
||||||
err = -ENODEV;
|
|
||||||
goto ERROR2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Determine the chip type. */
|
/* Determine the chip type. */
|
||||||
if (kind <= 0) {
|
switch (chip_type) {
|
||||||
i = it87_read_value(new_client, IT87_REG_CHIPID);
|
case IT8712F_DEVID:
|
||||||
if (i == 0x90) {
|
data->type = it8712;
|
||||||
kind = it87;
|
|
||||||
if (is_isa) {
|
|
||||||
switch (chip_type) {
|
|
||||||
case IT8712F_DEVID:
|
|
||||||
kind = it8712;
|
|
||||||
break;
|
|
||||||
case IT8716F_DEVID:
|
|
||||||
kind = it8716;
|
|
||||||
break;
|
|
||||||
case IT8718F_DEVID:
|
|
||||||
kind = it8718;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (kind == 0)
|
|
||||||
dev_info(&adapter->dev,
|
|
||||||
"Ignoring 'force' parameter for unknown chip at "
|
|
||||||
"adapter %d, address 0x%02x\n",
|
|
||||||
i2c_adapter_id(adapter), address);
|
|
||||||
err = -ENODEV;
|
|
||||||
goto ERROR2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kind == it87) {
|
|
||||||
name = "it87";
|
|
||||||
} else if (kind == it8712) {
|
|
||||||
name = "it8712";
|
name = "it8712";
|
||||||
} else if (kind == it8716) {
|
break;
|
||||||
|
case IT8716F_DEVID:
|
||||||
|
data->type = it8716;
|
||||||
name = "it8716";
|
name = "it8716";
|
||||||
} else if (kind == it8718) {
|
break;
|
||||||
|
case IT8718F_DEVID:
|
||||||
|
data->type = it8718;
|
||||||
name = "it8718";
|
name = "it8718";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data->type = it87;
|
||||||
|
name = "it87";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fill in the remaining client fields and put it into the global list */
|
/* Fill in the remaining client fields and put it into the global list */
|
||||||
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
strlcpy(new_client->name, name, I2C_NAME_SIZE);
|
||||||
data->type = kind;
|
|
||||||
data->valid = 0;
|
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
/* Tell the I2C layer a new client has arrived */
|
/* Tell the I2C layer a new client has arrived */
|
||||||
if ((err = i2c_attach_client(new_client)))
|
if ((err = i2c_attach_client(new_client)))
|
||||||
goto ERROR2;
|
goto ERROR2;
|
||||||
|
|
||||||
if (!is_isa)
|
|
||||||
dev_info(&new_client->dev, "The I2C interface to IT87xxF "
|
|
||||||
"hardware monitoring chips is deprecated. Please "
|
|
||||||
"report if you still rely on it.\n");
|
|
||||||
|
|
||||||
/* Check PWM configuration */
|
/* Check PWM configuration */
|
||||||
enable_pwm_interface = it87_check_pwm(new_client);
|
enable_pwm_interface = it87_check_pwm(new_client);
|
||||||
|
|
||||||
|
@ -1129,8 +1053,7 @@ ERROR3:
|
||||||
ERROR2:
|
ERROR2:
|
||||||
kfree(data);
|
kfree(data);
|
||||||
ERROR1:
|
ERROR1:
|
||||||
if (is_isa)
|
release_region(isa_address, IT87_EXTENT);
|
||||||
release_region(address, IT87_EXTENT);
|
|
||||||
ERROR0:
|
ERROR0:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
@ -1147,50 +1070,39 @@ static int it87_detach_client(struct i2c_client *client)
|
||||||
if ((err = i2c_detach_client(client)))
|
if ((err = i2c_detach_client(client)))
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
if(i2c_is_isa_client(client))
|
release_region(client->addr, IT87_EXTENT);
|
||||||
release_region(client->addr, IT87_EXTENT);
|
|
||||||
kfree(data);
|
kfree(data);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The SMBus locks itself, but ISA access must be locked explicitly!
|
/* ISA access must be locked explicitly!
|
||||||
We don't want to lock the whole ISA bus, so we lock each client
|
|
||||||
separately.
|
|
||||||
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
|
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
|
||||||
would slow down the IT87 access and should not be necessary. */
|
would slow down the IT87 access and should not be necessary. */
|
||||||
static int it87_read_value(struct i2c_client *client, u8 reg)
|
static int it87_read_value(struct i2c_client *client, u8 reg)
|
||||||
{
|
{
|
||||||
struct it87_data *data = i2c_get_clientdata(client);
|
struct it87_data *data = i2c_get_clientdata(client);
|
||||||
|
|
||||||
int res;
|
int res;
|
||||||
if (i2c_is_isa_client(client)) {
|
|
||||||
mutex_lock(&data->lock);
|
mutex_lock(&data->lock);
|
||||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||||
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
|
res = inb_p(client->addr + IT87_DATA_REG_OFFSET);
|
||||||
mutex_unlock(&data->lock);
|
mutex_unlock(&data->lock);
|
||||||
return res;
|
|
||||||
} else
|
return res;
|
||||||
return i2c_smbus_read_byte_data(client, reg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The SMBus locks itself, but ISA access muse be locked explicitly!
|
/* ISA access must be locked explicitly!
|
||||||
We don't want to lock the whole ISA bus, so we lock each client
|
|
||||||
separately.
|
|
||||||
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
|
We ignore the IT87 BUSY flag at this moment - it could lead to deadlocks,
|
||||||
would slow down the IT87 access and should not be necessary. */
|
would slow down the IT87 access and should not be necessary. */
|
||||||
static int it87_write_value(struct i2c_client *client, u8 reg, u8 value)
|
static void it87_write_value(struct i2c_client *client, u8 reg, u8 value)
|
||||||
{
|
{
|
||||||
struct it87_data *data = i2c_get_clientdata(client);
|
struct it87_data *data = i2c_get_clientdata(client);
|
||||||
|
|
||||||
if (i2c_is_isa_client(client)) {
|
mutex_lock(&data->lock);
|
||||||
mutex_lock(&data->lock);
|
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
||||||
outb_p(reg, client->addr + IT87_ADDR_REG_OFFSET);
|
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
|
||||||
outb_p(value, client->addr + IT87_DATA_REG_OFFSET);
|
mutex_unlock(&data->lock);
|
||||||
mutex_unlock(&data->lock);
|
|
||||||
return 0;
|
|
||||||
} else
|
|
||||||
return i2c_smbus_write_byte_data(client, reg, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return 1 if and only if the PWM interface is safe to use */
|
/* Return 1 if and only if the PWM interface is safe to use */
|
||||||
|
@ -1426,26 +1338,14 @@ static int __init sm_it87_init(void)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
res = i2c_add_driver(&it87_driver);
|
if ((res = it87_find(&isa_address)))
|
||||||
if (res)
|
|
||||||
return res;
|
return res;
|
||||||
|
return i2c_isa_add_driver(&it87_isa_driver);
|
||||||
if (!it87_find(&isa_address)) {
|
|
||||||
res = i2c_isa_add_driver(&it87_isa_driver);
|
|
||||||
if (res) {
|
|
||||||
i2c_del_driver(&it87_driver);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit sm_it87_exit(void)
|
static void __exit sm_it87_exit(void)
|
||||||
{
|
{
|
||||||
if (isa_address)
|
i2c_isa_del_driver(&it87_isa_driver);
|
||||||
i2c_isa_del_driver(&it87_isa_driver);
|
|
||||||
i2c_del_driver(&it87_driver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* k8temp.c - Linux kernel module for hardware monitoring
|
* k8temp.c - Linux kernel module for hardware monitoring
|
||||||
*
|
*
|
||||||
* Copyright (C) 2006 Rudolf Marek <r.marek@sh.cvut.cz>
|
* Copyright (C) 2006 Rudolf Marek <r.marek@assembler.cz>
|
||||||
*
|
*
|
||||||
* Inspired from the w83785 and amd756 drivers.
|
* Inspired from the w83785 and amd756 drivers.
|
||||||
*
|
*
|
||||||
|
@ -286,7 +286,7 @@ static void __exit k8temp_exit(void)
|
||||||
pci_unregister_driver(&k8temp_driver);
|
pci_unregister_driver(&k8temp_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
MODULE_AUTHOR("Rudolf Marek <r.marek@sh.cvut.cz>");
|
MODULE_AUTHOR("Rudolf Marek <r.marek@assembler.cz>");
|
||||||
MODULE_DESCRIPTION("AMD K8 core temperature monitor");
|
MODULE_DESCRIPTION("AMD K8 core temperature monitor");
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
|
||||||
|
|
|
@ -1000,7 +1000,7 @@ static int pc87360_detect(struct i2c_adapter *adapter)
|
||||||
(i&0x02) ? "external" : "internal");
|
(i&0x02) ? "external" : "internal");
|
||||||
|
|
||||||
data->vid_conf = confreg[3];
|
data->vid_conf = confreg[3];
|
||||||
data->vrm = 90;
|
data->vrm = vid_which_vrm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Fan clock dividers may be needed before any data is read */
|
/* Fan clock dividers may be needed before any data is read */
|
||||||
|
|
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
|
the Winbond W83627EHF Super-I/O chip
|
||||||
Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
|
Copyright (C) 2005 Jean Delvare <khali@linux-fr.org>
|
||||||
Copyright (C) 2006 Yuan Mu (Winbond),
|
Copyright (C) 2006 Yuan Mu (Winbond),
|
||||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
Rudolf Marek <r.marek@assembler.cz>
|
||||||
David Hubbard <david.c.hubbard@gmail.com>
|
David Hubbard <david.c.hubbard@gmail.com>
|
||||||
|
|
||||||
Shamelessly ripped from the w83627hf driver
|
Shamelessly ripped from the w83627hf driver
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
monitoring
|
monitoring
|
||||||
Copyright (C) 2004, 2005 Winbond Electronics Corp.
|
Copyright (C) 2004, 2005 Winbond Electronics Corp.
|
||||||
Chunhao Huang <DZShen@Winbond.com.tw>,
|
Chunhao Huang <DZShen@Winbond.com.tw>,
|
||||||
Rudolf Marek <r.marek@sh.cvut.cz>
|
Rudolf Marek <r.marek@assembler.cz>
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
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
|
* i2c-ali1563.c - i2c driver for the ALi 1563 Southbridge
|
||||||
*
|
*
|
||||||
* Copyright (C) 2004 Patrick Mochel
|
* Copyright (C) 2004 Patrick Mochel
|
||||||
* 2005 Rudolf Marek <r.marek@sh.cvut.cz>
|
* 2005 Rudolf Marek <r.marek@assembler.cz>
|
||||||
*
|
*
|
||||||
* The 1563 southbridge is deceptively similar to the 1533, with a
|
* The 1563 southbridge is deceptively similar to the 1533, with a
|
||||||
* few notable exceptions. One of those happens to be the fact they
|
* few notable exceptions. One of those happens to be the fact they
|
||||||
|
|
|
@ -142,7 +142,6 @@
|
||||||
#define I2C_DRIVERID_MTP008 1023
|
#define I2C_DRIVERID_MTP008 1023
|
||||||
#define I2C_DRIVERID_DS1621 1024
|
#define I2C_DRIVERID_DS1621 1024
|
||||||
#define I2C_DRIVERID_ADM1024 1025
|
#define I2C_DRIVERID_ADM1024 1025
|
||||||
#define I2C_DRIVERID_IT87 1026
|
|
||||||
#define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */
|
#define I2C_DRIVERID_CH700X 1027 /* single driver for CH7003-7009 digital pc to tv encoders */
|
||||||
#define I2C_DRIVERID_FSCPOS 1028
|
#define I2C_DRIVERID_FSCPOS 1028
|
||||||
#define I2C_DRIVERID_FSCSCY 1029
|
#define I2C_DRIVERID_FSCSCY 1029
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue